From 6df47224381a48faa75296b552d0e118eb9e8538 Mon Sep 17 00:00:00 2001 From: Sergey Matyunin Date: Mon, 17 Aug 2026 12:12:05 +0300 Subject: [PATCH 1/5] docs(spec): define plan-only space export Issue: #167 User-Visible: no --- docs/specs/167-plan-only-export.md | 471 +++++++++++++++++++++++++++++ docs/specs/README.md | 3 +- 2 files changed, 473 insertions(+), 1 deletion(-) create mode 100644 docs/specs/167-plan-only-export.md diff --git a/docs/specs/167-plan-only-export.md b/docs/specs/167-plan-only-export.md new file mode 100644 index 00000000..f3159fd5 --- /dev/null +++ b/docs/specs/167-plan-only-export.md @@ -0,0 +1,471 @@ +# Issue #167 — экспорт «только планировка» + +Дата: 2026-08-17 + +Тип: `feature` · приоритет: `P1` · пользовательская ценность: 7/10 · +сложность: 5/10 · риск: 6/10 + +Issue: [#167](https://github.com/Matysh/houseplan-card/issues/167) + +Ветка: `issue/167-plan-only-export` + +Зависимость: [#50](https://github.com/Matysh/houseplan-card/issues/50) — выполнена +и выпущена в stable v1.62.0. + +Канонические документы: [SCOPE](../SCOPE.md), +[CONFIG-COMPATIBILITY](../CONFIG-COMPATIBILITY.md), +[TOUCH-SUPPORT](../TOUCH-SUPPORT.md), [USER-GUIDE](../USER-GUIDE.md), +[USER-GUIDE.ru](../USER-GUIDE.ru.md), +[ТЗ #50](050-config-export-import.md). + +## 1. Сценарий и персона + +Владелец уже нарисовал этаж и хочет: + +- перенести его геометрию в другой Home Assistant, где устройства и Area имеют + другие идентификаторы; +- передать чистый шаблон планировки другому пользователю; +- сохранить архитектурную заготовку без раскрытия HA-привязок. + +В General settings он открывает действующий экспорт, выбирает «Current space» +и включает «Plan only». Полученный JSON импортируется существующим потоком как +новое пространство: комнаты, стены, проёмы, декор и фон остаются, а устройства +и автоматические привязки на новом экземпляре настраиваются заново. + +Это сценарии J4/J6 из `docs/SCOPE.md`: первоначальная настройка и дальнейшее +обслуживание House Plan. Нового поведения обычного View задача не вводит. + +## 2. Что человек увидит до и после + +**До:** экспорт текущего пространства всегда содержит его маркеры, layout и +HA-привязки. Для чистого переноса пользователь должен вручную редактировать +JSON, рискуя повредить структуру или случайно оставить идентификаторы. + +**После:** рядом с выбором текущего пространства доступен выключенный по +умолчанию флажок «Plan only». В этом режиме файл сохраняет переносимую +планировку, но не содержит маркеров, layout и известных структурных +HA-привязок. Preview импорта явно сообщает, что файл содержит только +планировку; импорт добавляет новое несвязанное пространство существующим +безопасным механизмом #50. + +Обычный full export и обычный export current space работают как раньше. + +## 3. Проблема и подтверждённая причина + +1. `houseplan/export/create` принимает только `kind`, `space_id` и версию + карточки; отдельного намерения «только планировка» нет. +2. `create_export()` для `kind == "space"` намеренно выбирает маркеры этого + пространства и соответствующий live layout. +3. HA-привязки находятся не только в маркерах. Они есть в `room.area`, + `room.settings.temp_source|hum_source`, `opening.contact|lock` и live-text + декора; поэтому одного удаления массива `markers` недостаточно. +4. Современный live text хранит ссылку прямо в `decor.text` токеном вида + `{sensor.kitchen}`; legacy-конфиги дополнительно могут содержать поля + `entity`, `attr`, `unit` и placeholder `{}`. +5. Действующий импорт пространства уже умеет remap внутренних id, добавить + новое пространство без замены существующего, отсоединить недоступный + content и показать preview. Новый импорт-процесс не требуется. + +## 4. Scope + +В задачу входят: + +1. опция «Plan only» только для экспорта текущего пространства; +2. schema-aware проекция переносимой геометрии и визуальных настроек; +3. полное удаление маркеров, marker layout и структурных HA-привязок; +4. статическая нейтрализация live-text токенов по решению владельца; +5. аддитивный признак `transfer.plan_only: true` в JSON; +6. строгая проверка plan-only инварианта при чтении файла; +7. существующий preview/apply пространства с явным plan-only статусом; +8. одинаковый контракт в RU/EN; +9. unit, backend, smoke, golden и executable mutation coverage; +10. пользовательская документация и оба changelog. + +## 5. Non-scope + +В задачу не входят: + +- полноценная анонимизация пользовательского содержимого; +- удаление или замена названий пространства и комнат, статического текста, + имён файлов, внешних URL и иных пользовательских строк; +- встраивание backdrop или attachment bytes в JSON — действует content- + контракт #50; +- экспорт нескольких выбранных пространств; +- новый формат файла, отдельный import endpoint или replace существующего + пространства; +- сопоставление Area, устройств и сущностей при импорте; +- перенос marker icon, actions, vacuum paths, runtime states, histories, + trails, known/new-device bookkeeping; +- создание PDF/изображения чистого плана — это сценарий #53; +- дополнительное privacy-предупреждение специально для plan-only; +- изменение редакторов, View, kiosk или touch-жестов; +- миграция сохранённого server config либо layout store. + +## 6. Пользовательский и UX-контракт + +### 6.1. Диалог экспорта + +В существующем диалоге: + +1. Full backup и Current space остаются взаимоисключающими radio options. +2. Флажок «Plan only» показывается и доступен только при выбранном Current + space и наличии текущего пространства. +3. При каждом открытии диалога флажок выключен. +4. Переключение на Full backup сбрасывает флажок; возврат к Current space не + включает его автоматически. +5. На сервер отправляется `plan_only: true` только при Current space + + включённом флажке. При всех остальных состояниях поле отсутствует или false. +6. Действующий `backup.privacy_warning` сохраняется без изменений. Новое + предупреждение, требующее отдельного подтверждения, не добавляется. +7. Label и короткий нейтральный hint должны объяснять результат, но не обещать + анонимизацию: «Сохранить комнаты, стены, проёмы и декор без устройств и + привязок Home Assistant». + +Флажок следует существующей keyboard/focus семантике `ha-checkbox`, имеет +доступную подпись и не уменьшает действующие touch targets. + +### 6.2. Preview импорта + +Для plan-only файла preview: + +- явно показывает информационную строку «Файл содержит только планировку»; +- показывает `markers = 0`, `layout = 0`, device/entity/virtual bindings = 0; +- не показывает duplicate policy, поскольку дубликатов устройств нет; +- не показывает missing Area, поскольку `room.area` очищен; +- показывает обычные counts комнат, стен, проёмов, декора и content; +- сохраняет действующие final-name, content detach и confirmation правила #50; +- после revalidate продолжает показывать plan-only статус. + +Кнопка применения остаётся «Add space». Импорт никогда не заменяет текущее +пространство и не вводит отдельного Undo. + +## 7. Контракт экспортируемой модели + +### 7.1. Envelope и совместимость формата + +Файл остаётся обычным envelope #50: + +```json +{ + "kind": "space", + "transfer": { + "plan_only": true, + "dropped_marker_links": 0 + }, + "payload": { + "config": { "spaces": ["…"], "markers": [] }, + "layout": {} + }, + "placement_manifest": [], + "content_manifest": ["…"] +} +``` + +- `export_version` и `model_version` не повышаются только из-за этой опции. +- `transfer.plan_only` допускается только как strict boolean и только при + `kind == "space"`. +- Поле присутствует только при true. У обычного space export документ при + фиксированных входе и времени остаётся семантически и структурно идентичен + прежнему, без `plan_only: false`. +- `plan_only: true` у `kind == "full"` отклоняется как `invalid_format`. +- Старые файлы без поля читаются как обычный экспорт. + +### 7.2. Что сохраняется + +Экспорт строит новую проекцию из текущего известного portable-plan allowlist, +а не копирует произвольные объекты с последующим чёрным списком. Сохраняются: + +- одно пространство: внутренний id, title и известные собственные визуальные + настройки; +- rooms: внутренние id, title, polygon/geometry, толщина/вид стен и известные + визуальные room settings; +- walls, drafts, partitions, columns, open spans и иные поддерживаемые + геометрические примитивы пространства; +- openings/open boundaries: id, тип, геометрия, ориентация и flip-поля; +- decor/backdrop: тип, геометрия, transform, style, статический текст и + переносимые content references; +- `plan_url` и backdrop transforms по действующему content manifest #50. + +Внутренние House Plan id сохраняются только внутри файла и затем remap-ятся +существующим `build_space_merge()`. Они не являются HA-привязками. + +### 7.3. Что удаляется или нейтрализуется + +Обязательная проекция: + +| Источник | Результат plan-only | +|---|---| +| `config.markers` | `[]`; marker config целиком отсутствует | +| `payload.layout` | `{}`; удаляются все позиции, включая room labels | +| `placement_manifest` | `[]` | +| marker attachment/content entries | отсутствуют | +| `room.area` | отсутствует или canonical unbound value | +| `room.settings.temp_source` | отсутствует | +| `room.settings.hum_source` | отсутствует | +| `opening.contact` / `opening.lock` | отсутствуют | +| contact-specific `opening.invert` | отсутствует как часть binding behavior | +| decor legacy `entity` / `attr` / `unit` | отсутствуют | +| valid live tokens и legacy `{}` в `decor.text` | заменены на `—` | +| `known_devices` / `new_device_ids` | не переносятся | + +`flip_h`, `flip_v` и другие геометрические параметры проёма не являются +HA-binding behavior и сохраняются. + +### 7.4. Live text + +Используется тот же синтаксический контракт live-text, что во фронтенде, без +подстановки runtime state: + +- каждый валидный HA live token `{sensor.kitchen}` заменяется одним символом + `—`; +- legacy placeholder `{}` также заменяется на `—`; +- окружающий пользовательский текст, whitespace и форматирование сохраняются; +- malformed braces, которые parser не признаёт live token, остаются обычным + статическим текстом; +- legacy `entity`, `attr`, `unit` удаляются независимо от наличия placeholder. + +Пример: `Температура {sensor.kitchen} °C` → `Температура — °C`. + +Это принятое владельцем решение Q1. Текущее значение сущности не читается и +не записывается: экспорт остаётся deterministic относительно server config. + +### 7.5. Граница privacy-обещания + +Режим гарантирует отсутствие HA-specific identifier/binding в известных +структурных позициях модели и распознанных live tokens. Он не сканирует и не +анонимизирует произвольный пользовательский текст. Поэтому сохраняются названия +пространства/комнат, статические decor labels, filenames и внешние URL, даже +если пользователь сам написал в них строку, похожую на entity id. + +Это принятое владельцем решение Q2. Дополнительное UI-предупреждение не +добавляется. + +Неизвестные поля внутри экспортируемых model objects не копируются автоматически: +новое переносимое поле сначала должно быть классифицировано как geometry, +presentation, user content или HA binding. Это fail-closed защита от утечки +нового binding-поля в будущей версии. + +## 8. Контракт API, парсинга и импорта + +### 8.1. Export endpoint + +`houseplan/export/create` получает optional strict boolean `plan_only`. + +- `plan_only == true` требует `kind == "space"` и валидный `space_id`. +- Право доступа, readiness, limits, source fingerprint, signing/content и + download contract остаются от #50. +- Проекция строится на backend; frontend не получает полный config для + самостоятельной очистки. +- Экспорт не читает HA runtime states и не выполняет network requests. + +### 8.2. Проверка входящего файла + +`parse_document()` не доверяет одному флагу. Для +`kind == "space" && transfer.plan_only == true` он дополнительно проверяет: + +- ровно одно пространство; +- `markers == []`, `layout == {}`, `placement_manifest == []`; +- отсутствие marker-owned content; +- отсутствие Area/temp/hum/opening/decor legacy bindings; +- отсутствие валидных live-text токенов и legacy `{}`; +- согласованность обычного content manifest. + +Нарушение возвращает существующий стабильный `invalid_format`; файл не +попадает в preview/apply. Это предотвращает ложную маркировку вручную +отредактированного файла как «только планировка». + +### 8.3. Preview, revalidate и apply + +- `create_preview()` возвращает `plan_only: true` для валидного файла. +- Кандидат и `revalidate_candidate()` сохраняют это значение. +- Existing space merge remap-ит внутренние id, добавляет suffix к конфликтному + title и не меняет global settings. +- Content availability/detach повторно проверяется перед apply под действующим + lock по контракту #50. +- Apply не добавляет маркеры и layout, а комнаты остаются unbound. +- Events, revision conflict, token ownership/expiry и capacity limits не + меняются. + +## 9. Модель данных, миграция и compatibility + +Server config, layout store и localStorage не получают новых полей. Опция +существует только в краткоживущем состоянии export dialog и в export envelope. + +Прямая миграция не нужна: новая версия читает прежние full/space файлы без +изменений. Обратная совместимость best-effort: старая версия, поддерживающая +тот же `export_version` и игнорирующая additive transfer metadata, увидит +структурно валидный обычный space export с нулём маркеров. При этом именно +новая версия обязана проверить усиленный plan-only инвариант. + +Ordinary full и space exports, обычный preview/apply и existing import Undo не +меняются. + +## 10. i18n, accessibility и touch + +Нужны синхронные RU/EN keys минимум для: + +- label «Plan only»; +- короткого hint без обещания анонимизации; +- informational preview line. + +Новых error keys и дополнительного privacy warning нет; invalid document +использует `backup.error.invalid_format`. + +Диалог остаётся keyboard-operable: label связан с checkbox, visible focus и +screen-reader name обеспечиваются действующим компонентом. Preview status +доступен как обычный текст, не только цветом. + +Touch View и kiosk не затронуты. General settings/editor остаётся desktop- +first по `TOUCH-SUPPORT`, но диалог не должен переполнять узкий viewport и +действующие touch targets не уменьшаются. + +## 11. Acceptance criteria и доказательства + +1. При Current space пользователь видит выключенный «Plan only»; при Full + backup опции нет, а request не содержит true. +2. Plan-only export содержит одно пространство, `markers: []`, `layout: {}`, + empty placement manifest и `transfer.plan_only: true`. +3. Геометрия rooms/walls/drafts/partitions/columns/openings/open spans, + decor/backdrop и переносимые визуальные настройки сохраняются по allowlist. +4. Area, temperature/humidity source, opening contact/lock/invert, marker data, + known/new bookkeeping и legacy decor binding fields отсутствуют. +5. Все валидные inline live tokens и legacy `{}` заменены на `—` с сохранением + окружающего текста; runtime value в файл не попадает. +6. Названия, статический текст, filenames и external URLs сохраняются; UX не + обещает полную анонимизацию и не добавляет отдельного предупреждения. +7. Импорт plan-only файла на чистый целевой instance создаёт новое пространство + с той же планировкой, нулём устройств/позиций/привязок и unbound rooms. +8. Preview и revalidate явно сохраняют `plan_only: true`, показывают нулевые + binding counts и не предлагают duplicate policy. +9. File с true, но с маркером, layout или известной HA-привязкой отклоняется + как `invalid_format` до preview. +10. Обычные full/space export и import проходят неизменённые regression + fixtures; normal space document не получает `plan_only: false`. +11. RU/EN тексты синхронны, checkbox доступен с клавиатуры и диалог проходит + narrow-viewport smoke. +12. Typecheck, unit и build зелёные; targeted backend import/export tests + зелёные в Linux CI. +13. Targeted golden подтверждает export dialog и plan-only preview; diff + просмотрен человеком и не имеет непреднамеренных изменений. +14. Все обязательные executable mutants из §12.3 действительно делают + соответствующий guard красным. +15. Оба changelog и обе пользовательские инструкции обновлены в том же + пользовательском коммите. + +## 12. План автотестов + +### 12.1. Backend unit/integration + +Расширить `tests_backend/test_ha_import_export.py`: + +- export normal space с фиксированным временем — прежний fixture без нового + поля; +- plan-only projection полного representative space со всеми типами geometry, + marker/layout, room/opening bindings, modern и legacy live text; +- preserve static names/text/URLs/content owner и drop marker attachments; +- reject `plan_only=true` для full; +- reject non-boolean plan_only; +- reject forged plan-only files по одному для marker, layout, room area, + temp/hum, opening refs, legacy decor refs и inline token; +- preview/revalidate/apply happy path на same и foreign instance; +- missing internal backdrop + detach confirmation по действующему контракту; +- capacity, revision conflict, expired/foreign token regressions; +- ordinary full/space fixtures без изменений. + +Полный HA harness канонически выполняется в Linux CI: Windows-путь блокируется +зависимостью `fcntl` и не является локальным release gate. + +### 12.2. Frontend unit, smoke и golden + +- unit: export dialog state по умолчанию, reset при Full, request payload; +- smoke: checkbox видим только для Current space, keyboard change и narrow + viewport; +- import smoke: `plan_only` line есть, duplicate controls отсутствуют; +- RU/EN i18n parity; +- добавить/обновить deterministic golden scenarios для export dialog с + включённой опцией и import preview, поднять версию golden matrix; +- review actual/expected/diff до принятия baseline. + +В implementation loop выполняются только действующие быстрые gates: +`typecheck`, `unit`, `build`. Golden и smoke — перед бетой по runbook. + +### 12.3. Executable mutation gate + +Mutation harness обязан временно внести каждую поломку, запустить названный +guard, получить non-zero и восстановить файл: + +1. оставить один inline live token либо `room.area` в проекции — backend + plan-only privacy test падает; +2. проигнорировать `plan_only` и вернуть marker/layout — projection/roundtrip + test падает; +3. прогнать normal space export через lossy projection или записать + `plan_only: false` — fixed ordinary-export regression падает; +4. не перенести `plan_only` через preview/revalidate или не показать строку — + backend preview test либо frontend smoke падает; +5. отключить строгую проверку forged plan-only документа — negative parser + test падает. + +Gate считается доказанным только если лог содержит имя каждого mutant, +ожидаемый guard и зафиксированный non-zero exit; простой список будущих +мутантов acceptance criterion не выполняет. + +## 13. Риски и меры + +| Риск | Мера | +|---|---| +| Новый HA-binding field утечёт в файл | allowlist projection + fail-closed parser + mutation gate | +| Очистка затронет обычный export | отдельная true-ветка и fixed regression обычного файла | +| Live text потеряет полезную подпись | заменять только token, сохранять окружающий static text | +| Пользователь сочтёт файл полностью анонимным | нейтральный hint точно говорит «без устройств и HA-привязок», документация перечисляет сохраняемые данные | +| Backdrop не откроется на другом instance | действующие content preview/detach правила #50, без ложного обещания embed | +| Frontend и backend расходятся в понимании token | единые fixtures grammar и forged-file tests | +| Новый checkbox ломает узкий диалог | narrow-viewport smoke + golden review | + +Производительность: проекция и проверка линейны по размеру одного пространства, +не выполняются в render loop и не меняют View performance budget. + +Security: доступ остаётся только у `may_write`; backend не обращается к +entity states или сети. Режим уменьшает объём структурных HA-данных, но не +является средством анонимизации пользовательского текста. + +## 14. Rollback + +Откат — удалить UI option и обработку true на export endpoint. Сохранённые +server config/layout не менялись, поэтому миграция назад не нужна. Уже созданный +plan-only файл остаётся структурно обычным space export с нулём маркеров и +может быть импортирован по базовому контракту #50; additive metadata безопасно +игнорируется совместимой версией. + +Если реализация не может доказать отсутствие известных HA-привязок, режим не +выпускается частично: обычный экспорт #50 остаётся доступен. + +## 15. Release-артефакты + +Задача пользовательская (`User-Visible: yes`). В том же продуктовом коммите +обязательны: + +- `docs/CHANGELOG.md` и `docs/CHANGELOG.ru.md`; +- раздел экспорта/импорта в `docs/USER-GUIDE.md` и + `docs/USER-GUIDE.ru.md`, включая точную privacy-границу; +- при необходимости `docs/CONFIG-COMPATIBILITY.md` и архитектурное описание + additive `transfer.plan_only`; +- deterministic golden actual/expected/diff и обновлённая golden matrix; +- smoke/mutation logs согласно принятому тестовому контракту; +- перед бетой — golden, smoke и performance gates по runbook; +- terminal commit trailers `Issue: #167` и `User-Visible: yes`. + +Push ветки выполняется после задачи; issue не закрывается до пакетного выпуска +беты. Перевод в `S4-spec-review` в рамках этого шага не выполняется. + +## 16. Принятые предположения + +1. `plan_only` — optional additive metadata внутри существующего export + version, а не новый kind или новая версия формата. +2. Геометрический `flip_h|flip_v` сохраняется, contact-specific `invert` + удаляется вместе с binding. +3. Неизвестные поля в plan-only проекцию автоматически не попадают; обычный + export остаётся lossless. +4. Privacy invariant относится к структурным HA-полям и валидным live tokens, + но не к произвольным пользовательским строкам. +5. Новый informational label/hint допустим; отдельное предупреждение или новое + подтверждение по решению владельца запрещено. diff --git a/docs/specs/README.md b/docs/specs/README.md index 946277ce..9874890c 100644 --- a/docs/specs/README.md +++ b/docs/specs/README.md @@ -1,6 +1,6 @@ # Спецификации задач P1 и P2 -Актуально на 2026-08-14. +Актуально на 2026-08-17. GitHub Issues и GitHub Projects (v2) остаются единственным каноническим backlog проекта. Этот каталог содержит развёрнутые ТЗ: каждое ТЗ ссылается на issue, а issue — на соответствующий файл. Статус, приоритет и факт завершения меняются только в GitHub. @@ -50,6 +50,7 @@ GitHub Issues и GitHub Projects (v2) остаются единственным | [#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) | | [#164](https://github.com/Matysh/houseplan-card/issues/164) Активный цикл стиральной машины должен быть жёлтым | [164-washer-active-cycle.md](164-washer-active-cycle.md) | +| [#167](https://github.com/Matysh/houseplan-card/issues/167) Экспорт «только планировка» | [167-plan-only-export.md](167-plan-only-export.md) | ## P2 From 7f397a687507b3df7b99ff1a4c3b2a46ff2a8c22 Mon Sep 17 00:00:00 2001 From: Sergey Matyunin Date: Mon, 17 Aug 2026 12:33:52 +0300 Subject: [PATCH 2/5] feat: add plan-only space export Issue: #167 User-Visible: yes --- .github/workflows/mutation-gate.yml | 11 +- .../houseplan/frontend/houseplan-card.js | 21 +- custom_components/houseplan/import_export.py | 271 ++++++++++++++++-- custom_components/houseplan/websocket_api.py | 2 + demo/golden/harness.mjs | 14 +- demo/golden/matrix.mjs | 5 +- demo/smoke_backup_transfer.mjs | 65 ++++- demo/srv/assets/houseplan-card.js | 21 +- dist/houseplan-card.js | 21 +- docs/ARCHITECTURE.md | 10 +- docs/CHANGELOG.md | 5 + docs/CHANGELOG.ru.md | 5 + docs/CONFIG-COMPATIBILITY.md | 17 ++ docs/USER-GUIDE.md | 13 + docs/USER-GUIDE.ru.md | 13 + docs/specs/167-plan-only-export.md | 86 ++++-- scripts/backend-test-guard.mjs | 13 + scripts/mutation-gate.mjs | 66 +++++ src/houseplan-card.ts | 16 +- src/i18n/en.json | 3 + src/i18n/ru.json | 3 + src/styles.ts | 4 + test/golden-matrix.test.mjs | 4 +- tests_backend/test_ha_import_export.py | 254 ++++++++++++++++ 24 files changed, 859 insertions(+), 84 deletions(-) create mode 100644 scripts/backend-test-guard.mjs diff --git a/.github/workflows/mutation-gate.yml b/.github/workflows/mutation-gate.yml index b8c40c35..b6a304e1 100644 --- a/.github/workflows/mutation-gate.yml +++ b/.github/workflows/mutation-gate.yml @@ -27,8 +27,8 @@ concurrency: jobs: mutants: runs-on: ubuntu-latest - # Шесть мутантов × (сборка + браузерный смок) — это десятки минут, и это - # нормально: гейт предрелизный. Час — потолок против зависшего Chromium. + # Все мутанты × (сборка + свой guard) — это десятки минут, и это нормально: + # гейт предрелизный. Час — потолок против зависшего Chromium. timeout-minutes: 60 steps: - uses: actions/checkout@v7 @@ -43,6 +43,13 @@ jobs: - run: npm ci + - uses: actions/setup-python@v7 + with: + python-version: '3.13' + + - name: Установить backend test dependencies + run: pip install pytest voluptuous pytest-homeassistant-custom-component home-assistant-frontend + - name: Кэш браузеров Playwright id: pw uses: actions/cache@v6 diff --git a/custom_components/houseplan/frontend/houseplan-card.js b/custom_components/houseplan/frontend/houseplan-card.js index 090f3dc8..e19774d8 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__="da33c4f75cf2469e330b33f11fd5fa04d36861af0c75eb110758b7fff2688fcb";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__="7a788419e5d1a6e4b944a5d436d2cffd00795c3237791689b1401b73925353f8";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.text}${t.suffix}`)} ${null!=t.lqiText?j`${t.lqiText}`:G} - `;var o}function Io(t,e,i){return{kind:"none",reason:"none",generation:t,expiresAt:null,color:e,diameterScale:i,animated:!1,reducedMotionIndicator:"none"}}const Eo=new WeakMap;function zo(t){const e=function(t){const e=t?.connection||t;return!e||"object"!=typeof e&&"function"!=typeof e?null:e}(t);if(!e)return null;let i=Eo.get(e);return i||(i={revision:0,loaded:!1,configEntries:{},manifests:{},listeners:new Set,refs:0},Eo.set(e,i)),i}function Fo(t){t.revision++;for(const e of[...t.listeners])try{e()}catch{}}function Oo(t,e){const i=zo(t);if(!i)return()=>{};i.refs++,i.listeners.add(e),async function(t,e){e.loading||"function"!=typeof t?.callWS?e.loading:e.loaded&&e.loadedAt&&Date.now()-e.loadedAt<3e5||(e.loading=(async()=>{const[i,s]=await Promise.allSettled([t.callWS({type:"config_entries/get"}),t.callWS({type:"manifest/list"})]);"fulfilled"===i.status&&Array.isArray(i.value)&&(e.configEntries=Object.fromEntries(i.value.filter(t=>"string"==typeof t?.entry_id).map(t=>[t.entry_id,t]))),"fulfilled"===s.status&&Array.isArray(s.value)&&(e.manifests=Object.fromEntries(s.value.filter(t=>"string"==typeof t?.domain).map(t=>[t.domain,t]))),e.loaded=!0,e.loadedAt=Date.now(),e.loading=void 0,Fo(e)})().catch(()=>{e.loaded=!0,e.loadedAt=Date.now(),e.loading=void 0,Fo(e)}),e.loading)}(t,i),async function(t,e){if(e.unsubscribe||e.subscribing)return e.subscribing;const i=t?.connection?.subscribeMessage;"function"==typeof i&&(e.subscribing=(async()=>{try{e.unsubscribe=await i.call(t.connection,t=>{const i=Array.isArray(t)?t:Array.isArray(t?.entries)?t.entries:t?[t]:[];let s=!1;const n={...e.configEntries};for(const t of i){const e=t?.entry||t?.data?.entry||("string"==typeof t?.entry_id?t:null),i=e?.entry_id;if("string"!=typeof i)continue;const o=t?.type||t?.action||t?.data?.action;"removed"===o||"remove"===o?delete n[i]:n[i]=e,s=!0}s&&(e.configEntries=n,Fo(e))},{type:"config_entries/subscribe"})}catch{}finally{0===e.refs&&(e.unsubscribe?.(),e.unsubscribe=void 0),e.subscribing=void 0}})(),e.subscribing)}(t,i);let s=!1;return()=>{s||(s=!0,i.listeners.delete(e),i.refs=Math.max(0,i.refs-1),i.refs||(i.unsubscribe?.(),i.unsubscribe=void 0))}}function No(t){const e=zo(t);return e?{revision:e.revision,loaded:e.loaded,configEntries:e.configEntries,manifests:e.manifests}:{revision:0,loaded:!1,configEntries:{},manifests:{}}}function Lo(t,e,i=No(t),s){const n=i.manifests[e];return function(t,e){if("function"!=typeof t?.localize)return"";try{const i=`component.${e}.title`,s=t.localize(i);return"string"==typeof s&&s!==i?s:""}catch{return""}}(t,e)||String(n?.name||"")||String(s||"")||e.replace(/_/g," ")}function Ho(t,e,i,s,n){const o=String(e||"").trim();if(!o)return null;const r=n?s.configEntries[n]:null;return{domain:o,label:Lo(t,o,s,r?.title),configEntryId:n,configEntryTitle:r?.title,confidence:i}}function qo(t){const e={"registry-owner":0,"entity-platform":1,"identifier-fallback":2},i=new Set;return t.filter(t=>!!t).sort((t,i)=>e[t.confidence]-e[i.confidence]||t.label.localeCompare(i.label)||t.domain.localeCompare(i.domain)).filter(t=>{const e=`${t.domain}\n${t.configEntryId||""}`;return!i.has(e)&&(i.add(e),!0)})}function Bo(t,e,i,s=No(t)){const n=i.entities?.[e]||t?.entities?.[e];if(!n)return null;const o=n.config_entry_id||void 0,r=o?s.configEntries[o]?.domain:"";return Ho(t,n.platform||r,"entity-platform",s,o)}var jo={"editor.context_actions":"Actions: {object}","editor.tool_options":"Tool options: {tool}","editor.palette":"Palette: {tool}","editor.open_group":"Tool group: {group}","editor.group_active":"{group} — active: {item}","editor.disabled_action":"{action} is unavailable: {reason}","btn.properties":"Properties","btn.keep_as_walls":"Keep as closed walls","title.markup_partition":"Partition: draw one independent physical wall","title.markup_select":"Select and edit walls, columns and saved outlines","title.markup_column":"Column: click a grid point to place a square column","markup.partition":"Partition","markup.column":"Column","markup.hint_partition":"two clicks draw one partition · Shift locks to 45°","markup.hint_column":"click a grid point to place a square column","history.draft_segment":"Add room-draft segment","history.draft_merge":"Join unfinished room outlines","history.draft_segment_delete":"Remove room-draft segment","history.partition_add":"Add partition","history.column_add":"Add column","history.physical_edit":"Change physical object","history.physical_delete":"Delete physical object","history.physical_move":"Move physical object","history.contour_to_partitions":"Convert contour to closed walls","toast.column_duplicate":"A column with the same centre and outer size already exists","confirm.delete_draft":"Delete the whole unfinished room contour?","confirm.delete_draft_segment":"Delete this draft segment? The remaining contour may split in two.","physical.partition_properties":"Partition properties","physical.column_properties":"Column properties","physical.draft_properties":"Draft segment properties","physical.shape":"Shape","physical.square":"Square","physical.circle":"Circle","physical.diameter":"Diameter","physical.side":"Side","physical.rotation":"Rotation angle","physical.length":"Length","physical.allowed_range":"Allowed: 1–{max} {unit}","physical.delete_segment":"Delete segment","physical.delete_draft":"Delete entire outline","physical.partition_size_title":"Thickness of the partition created by the second click (1–100 cm).","physical.column_size_title":"Outer side of the square column placed by the click (1–150 cm).","card.title":"House plan","count.devices":"{n} dev.","empty.no_spaces":"No spaces yet.","empty.add_first":"Add the first space and upload a floor plan.","empty.install":'Install the House Plan integration and add it in "Devices & services".',"btn.add_space":"Add space","btn.cancel":"Cancel","btn.save":"Save","btn.close":"Close","btn.delete":"Delete","btn.edit":"Edit","btn.open_in_ha":"Open in HA","btn.reset":"Reset","btn.attach":"Attach…","btn.upload":"Upload…","btn.replace":"Replace…","title.zoom_in":"Zoom in","title.zoom_out":"Zoom out","title.zoom_fit":"Fit all","title.add_device":"Add a device to the plan","title.show_all":"Show hidden and disabled devices as service markers (this tab only)","title.markup":"Room markup: grid, lines, outlines","title.configure_space":"Configure space","title.add_space":"Add space","title.markup_add":"Room outline: connect grid dots until the room closes; Shift locks the direction in 45° steps","title.markup_merge":"Merge: click one room, then the neighbour it shares a wall with","title.markup_split":"Split a room: click the room, then two points on its walls","title.markup_delroom":"Delete room: click inside a room and confirm","title.markup_boundary":"Boundary: click two points on a solid shared wall to make that stretch virtual; click a dashed stretch to restore it","title.need_plan":"Upload a floor-plan image","markup.add":"Room outline","markup.merge":"Merge","markup.split":"Split","markup.resize":"Resize","title.markup_resize":"Resize rooms: drag a wall handle; click a room for a corner scale frame","markup.hint_resize":"drag a wall handle · click a room — corner frame · Esc cancels a drag · Ctrl+Z — undo a step","markup.opening":"Opening","markup.boundary":"Boundary","markup.delete_room":"Delete room","history.undo":"Undo","history.redo":"Redo","history.undo_named":"Undo: {name} (Ctrl+Z)","history.redo_named":"Redo: {name} (Ctrl+Shift+Z / Ctrl+Y)","history.undo_empty":"Nothing to undo","history.redo_empty":"Nothing to redo","history.undone":"Undone: {name}","history.redone":"Redone: {name}","history.add_room":"Create room","history.split_room":"Split room","history.merge_rooms":"Merge rooms","history.resize_room":"Resize room","history.open_boundary":"Open boundary","history.close_boundary":"Restore wall","history.wall_thickness":"Change wall thickness","history.add_opening":"Add door or window opening","history.edit_opening":"Edit opening","history.move_opening":"Move opening","history.delete_opening":"Delete opening","history.delete_room":"Delete room","history.decor_add":"Add decor object","history.decor_edit":"Edit decor object","history.decor_move":"Move decor object","history.decor_transform":"Transform decor object","history.decor_delete":"Delete decor object","history.backdrop_transform":"Transform plan backdrop","title.markup_opening":"Openings: click a wall to place, click an opening to edit","opening.new":"New opening","opening.edit":"Opening","opening.door":"Door","opening.window":"Window","opening.gate":"Gate","opening.type_label":"Type","opening.length_label":"Length, cm","opening.contact_label":"Open/close sensor","opening.lock_label":"Lock","opening.none":"— none —","opening.invert":"Invert open/closed","opening.flip_h":"Hinge on the other jamb","opening.flip_v":"Opens to the other side","opening.open":"Open","opening.closed":"Closed","opening.locked":"Locked","opening.unlocked":"Unlocked","opening.state_unknown":"unavailable","opening.no_entities":"No sensors bound — a static symbol on the plan.","toast.opening_no_wall":"Click next to a room wall — openings sit on walls","markup.delete":"Delete","markup.hint_points":"points: {n} · Shift — 45° steps · Esc/Ctrl+Z — undo a dot · first point or Ctrl/Cmd+click — close","markup.hint_start":"click a grid dot to start the outline","tip.lqi":"average zigbee signal:","tip.area":"area: {value}","info.device_header":"Device on the plan","info.model":"Model","info.state":"State","info.link":"Link","info.manuals":"Manuals","info.none":"No additional information","marker.new_device":"New device","marker.name_label":"Name (shown on the plan)","marker.name_ph":"Name","marker.binding_label":"Bind to an HA device","marker.binding_disabled":"disabled in Home Assistant","marker.virtual_option":"Virtual device (no binding)","marker.search_ph":"Search device / group…","marker.nothing_found":"nothing found","marker.room_label":"Room","marker.room_override":" (override placement)","marker.room_choose":"— select a room —","marker.room_auto":"— by device area (auto) —","marker.icon_label":"Icon","marker.icon_ph":"mdi:… (empty = auto)","marker.display_label":"Display","display.badge":"Icon + state","display.icon_ripple":"Icon + state and activity","display.static_icon":"Always static icon","marker.display_hint":"Icon + state changes the plate for work, open, alarm and unavailable states. Icon + state and activity additionally shows a short pulse for events and a continuous pulse for ongoing work, movement or presence. Value + state replaces the icon with one unambiguous HA value. A static icon never reacts to device states.","marker.display_hint_badge":"The icon and dynamic plate show device state without the ordinary activity pulse. Red alarms remain visible.","marker.display_hint_icon":"The icon and dynamic plate show device state without the ordinary activity pulse. Red alarms remain visible.","marker.display_hint_icon_ripple":"The icon, dynamic plate and pulse show a short pulse for events and a continuous pulse for work, motion or presence. Red alarms have separate priority.","marker.display_hint_value":"One unambiguous Home Assistant value replaces the icon while the plate continues to show state. Red alarms remain visible.","marker.display_hint_static_icon":"The black plate and icon always stay the same. State, activity, unavailability and alarms do not change the face.","marker.static_alarm_warning":"This device can report alarms. Static display hides the marker's visual alarm indication.","marker.preview.title":"Display preview","marker.preview.actual":"Now","marker.preview.example":"Example","marker.preview.integration":"Provided by","marker.preview.source":"Display source","marker.preview.current_state":"Current state","marker.preview.result":"On the plan","marker.preview.details":"Source details","marker.preview.select_source":"Choose a Home Assistant device or entity to see its actual display.","marker.preview.unknown_provider":"Unknown integration","marker.preview.virtual_provider":"House Plan · virtual device","marker.preview.no_source":"No active source","marker.preview.no_state":"No current state","marker.preview.mixed_states":"Several different states","marker.preview.multiple_sources":"{n} sources","marker.preview.more_sources":"+{n} more","marker.preview.scaled":"The preview is scaled to {n}% to fit. The saved size is unchanged.","marker.preview.demo_activity":"Show activity example","marker.preview.demo_notice":"Temporary activity example; the actual device state is unchanged.","marker.preview.demo_short":"Show short activity","marker.preview.demo_continuous":"Show continuous activity","marker.preview.stop_continuous":"Stop continuous activity","marker.preview.demo_short_notice":"Short activity example; the actual device state is unchanged.","marker.preview.demo_continuous_notice":"Continuous activity example; the actual device state is unchanged.","marker.preview.demo_already_visible":"The current state already shows a real activity or alarm effect.","marker.preview.reduced_motion":"System reduced-motion is enabled, so ordinary activity is shown as a dot.","marker.preview.reason.neutral":"Neutral dark plate","marker.preview.reason.working":"Yellow plate: the device is working now","marker.preview.reason.working_activity":"Yellow plate and activity effect: the device is working now","marker.preview.reason.open":"Orange plate: physically open or unlocked","marker.preview.reason.cover_icon_state":"Neutral plate; the cover entity controls the displayed state and icon","marker.preview.reason.presence":"Activity effect while presence is detected","marker.preview.reason.event":"Short activity effect after a detected event","marker.preview.reason.transition":"Activity effect while the device is moving or changing state","marker.preview.reason.media_neutral":"Media devices stay on a neutral dark plate while available","marker.preview.reason.unavailable":"Subdued neutral plate: unavailable or off","marker.preview.reason.alarm":"Red alarm plate; alarm indication is always shown","marker.preview.reason.live_states_disabled":"Live-state styling is disabled for this card","marker.preview.reason.value_no_state":"No usable state value; the icon is shown instead","marker.preview.reason.value_ambiguous_sources":"Several possible value sources; the icon is shown instead","marker.preview.reason.value_non_scalar":"The source did not return a simple value; the icon is shown instead","marker.preview.reason.value_virtual":"A virtual device has no Home Assistant value; the icon is shown instead","marker.preview.reason.vacuum_live_plan_only":"Live vacuum position and trail are available only on the full plan","marker.preview.reason.hidden_design_preview":"The device is hidden on the plan; this design preview remains visible","marker.preview.reason.composite_power_source":"State comes from the device Power entity; auxiliary switches are ignored","marker.preview.reason.activity_display_disabled":"The selected display mode does not show ordinary activity effects","marker.preview.reason.ha_disabled":"The Home Assistant binding is disabled and will be hidden on the plan","marker.preview.reason.orphaned":"The saved Home Assistant binding can no longer be found","marker.preview.reason.static_icon":"Static mode: device state does not change the icon","marker.activity_color":"Activity pulse color","marker.ripple_size":"Activity pulse size","marker.activity_alarm_note":"Color and size affect ordinary activity only and do not change red alarms.","marker.pulse_a11y_alarm":"Alarm","marker.pulse_a11y_event":"Recent event","marker.pulse_a11y_presence":"Presence detected","marker.pulse_a11y_transition":"Changing state","marker.pulse_a11y_running":"Working","marker.size_label":"Icon size / rotation","marker.angle_label":"Rotate","marker.model_label":"Model","marker.model_ph":"e.g. Aqara T&H","marker.link_label":"Link","marker.desc_label":"Description","marker.desc_ph":"Notes, specs…","marker.manuals_label":"Manuals (PDF etc.)","marker.sub_device":"device","marker.sub_z2m_group":" · Z2M group","marker.sub_group":"group","marker.sub_helper":"helper","space.new":"New space","space.header":"Space","space.title_label":"Title","space.title_ph":"e.g. Garage","space.plan_label":"Floor plan (background)","space.no_plan":"no plan image","space.plan_alt":"plan","room.new":"New room","room.name_label":"Display name","room.name_ph":"e.g. Terrace","room.area_label":"Home Assistant area (unassigned)","room.no_area_option":"— no area —","room.default_name":"Room","device.unnamed":"unnamed","device.light_group":"light group","device.fallback":"device","device.virtual":"virtual device","confirm.delete_room":'Delete room "{name}"?',"confirm.merge_rooms":'Merge rooms "{a}" and "{b}"?',"confirm.remove_marker":'Delete "{name}" from the plan? The device will disappear completely and stop contributing to plan data. You can add it again later.',"confirm.erase_decor":"Erase the {kind} object? You can undo this action from the editor history.","confirm.delete_space":'Delete space "{title}" with all its rooms and markup?',"toast.pos_save_failed":"Failed to save position: {err}","toast.no_entity":"The device has no suitable entity","toast.ha_disabled_action":"A Home Assistant object that is disabled cannot be used on the plan.","toast.ha_disabled_show_device":"A device disabled in Home Assistant cannot be shown on the plan. Enable it in Home Assistant first.","toast.ha_disabled_show_entity":"An entity disabled in Home Assistant cannot be shown on the plan. Enable it in Home Assistant first.","toast.ha_disabled_add":"A disabled Home Assistant object cannot be added to the plan. Enable it in Home Assistant first.","toast.ha_binding_unverified":"The object status could not be verified through the Home Assistant registry. Display and actions are temporarily unavailable.","toast.markup_needs_server":"Markup is available after the config is moved to the server","toast.conflict":"Config was changed in another window — data refreshed, repeat your last action","toast.cfg_save_failed":"Failed to save config: {err}","toast.room_overlap":"The outline overlaps room “{name}” — rooms must not overlap","toast.contour_min_edges":"Draw at least two edges before closing the outline","toast.contour_cannot_close":"The outline cannot close because it is degenerate or intersects itself","toast.merge_not_adjacent":"Only rooms that share a wall can be merged","toast.rooms_merged":"Rooms merged into “{name}”","toast.split_pick_wall":"Start the cut on the room’s wall","toast.split_bad_cut":"The cut must run wall to wall inside the room, without crossing walls or itself","merge.header":"Merge rooms","merge.hint":"The merged room keeps one name and one area. The other area is released — its devices leave the plan until another room claims it.","merge.keep":"Keep","merge.no_area":"no area","room.split_header":"New room from the split","toast.room_saved":"Room saved ({n}). Devices added: {added}. Outline the next one or exit markup.","toast.room_saved_no_area":"Room saved ({n}, no area). Outline the next one or exit markup.","toast.marker_needs_server":"Device editing is available after the config is moved to the server","toast.virtual_name_required":"Enter a name for the virtual device","toast.marker_saved":"Device saved","toast.marker_removed":"Device deleted from the plan","toast.integration_missing":"The House Plan integration is not installed — management unavailable","toast.plan_formats":"Supported formats: SVG, PNG, JPG, WebP","toast.plan_required":"Upload a floor plan — it is required","toast.space_added_onboard":"Space added. Outline the rooms: click grid dots and close the contour.","toast.space_added":"Space added","toast.space_saved":"Space saved","toast.space_deleted":"Space deleted","toast.delete_failed":"Delete failed: {err}","toast.error":"Error: {err}","toast.file_failed":'File "{name}" was not uploaded: {err}',"toast.files_attached":"Files attached: {n}","err.unknown":"unknown error","err.code":"code {code}","err.too_large":"file larger than {mb} MB","err.bad_ext":"unsupported type (PDF/image expected)","err.unauthorized":"administrator rights required","editor.title":"Title","editor.default_floor":"Default space","editor.icon_size":"Icon size, % of plan width","editor.show_temperature":"Show temperature","editor.live_states":"Live states (on/off, open…)","editor.show_signal":"Show zigbee signal (LQI)","editor.language":"Interface language","editor.lang_auto":"Auto (HA profile)","editor.lang_en":"English","editor.lang_ru":"Русский","title.icon_rules":"Icon rules: which MDI icon devices get by name","rules.title":"Icon rules","rules.hint":"Rules are checked top-down against “device name + model” (case-insensitive regex); the first match wins. When nothing matches, the entity device class decides, then the generic chip icon.","rules.pattern_ph":"regex, e.g. plug|socket","rules.icon_ph":"mdi:power-socket-de","rules.add":"Add rule","rules.reset":"Reset to defaults","rules.test_ph":"Try a device name…","rules.invalid":"invalid regex","rules.saved":"Icon rules saved","btn.up":"Up","btn.down":"Down","tap.info":"Device card","tap.more_info":"HA more-info dialog","tap.toggle":"Toggle state","marker.tap_label":"Tap action for this device","tap.toggle_note":"The resolved state is shown below. Secure devices are never toggled from the plan.","import.title":"Create spaces from HA floors","import.hint":"Your Home Assistant already knows these floors. Pick the ones to turn into plan spaces — you will upload a floor-plan image for each one next. Rooms are then outlined by hand on the plan.","import.start":"Create {n} space(s)","import.manual":"Start from scratch","import.progress":"Floor {i} of {n}","import.done":"Spaces created. Outline the rooms: click grid dots and close the contour.","btn.skip":"Skip","space.scale_label":"Scale (grid cell size)","space.scale_unit":"cm per cell","space.display_section":"Display","space.show_borders":"Always show room borders","space.show_names":"Show room names (drag to move)","space.room_color":"Border & name color","space.opacity":"Opacity","space.fill_label":"Room fill","fill.none":"None","fill.lqi":"Zigbee signal","fill.light":"Lights","fill.custom":"Custom color","space.custom_fill":"Fill color","space.source_file":"I have a floor-plan image","space.source_draw":"No image — I'll outline rooms by hand","space.orientation":"Canvas","orient.landscape":"Landscape","orient.portrait":"Portrait","orient.square":"Square","fill.temp":"Temperature","space.temp_min":"Comfort from","space.temp_max":"to","tip.temp_avg":"average temperature:","space_card.button":"Open the space plan","space_card.not_found":"Space “{id}” not found","space_card.loading":"Loading…","continuity.restore_plan":"Restoring floor plan…","continuity.restore_connection":"Restoring device connection…","continuity.retry":"Try again","editor.space":"Space","editor.show_button":"Show button","editor.button_label":"Button label","editor.button_target":"Target dashboard path","marker.sub_entity":"entity","title.general_settings":"General settings","gs.title":"General settings","gs.hint":"Fill colors apply to every space; each color has its own opacity. Which fill mode a space uses is set in that space's dialog.","gs.light_group":"Fill: lights","gs.light_on":"Lights on","gs.light_off":"All lights off","gs.temp_group":"Fill: temperature","gs.temp_cold":"Cold","gs.temp_ok":"Comfortable","gs.temp_hot":"Hot","gs.lqi_group":"Fill: zigbee signal","gs.lqi_low":"Weak signal","gs.lqi_high":"Strong signal","gs.reset":"Reset to defaults","gs.saved":"General settings saved","space.show_lqi":"Show zigbee signal (LQI) next to devices","space.hide_decor":"Hide the decorative layer","space.hide_decor_tip":"Lines, shapes, labels and furniture stay where they are — visible in the backdrop editor, not on the plan.","space.hide_openings":"Hide openings","space.hide_openings_tip":"Door, window and gate symbols are not drawn, but the openings keep working: light passes through, the sun comes in at a window, contact sensors still open them. The plan editor always shows them.","gs.light_none":"No light sources","mode.plan":"Plan editor","mode.devices":"Device editor","display.value":"Value + state","marker.subarea":"no area, manual","device.new":"New device — open its editor to dismiss","opening.unlock_action":"Unlock","opening.lock_action":"Lock","opening.lock_pending":"Working…","title.close_editor":"Close editor (back to view)","devbar.add":"Add","devbar.show_all":"Hidden and disabled","devbar.rules":"Icon rules","space.roomcard_section":"Room card shows:","space.label_temp":"Temperature","space.label_hum":"Humidity","space.label_lqi":"Average Zigbee signal","space.label_light":"Lights on/off","roomcard.light_on":"On","roomcard.light_off":"Off","roomcard.light_partial":"{on} of {total}","toast.split_pick_inside":"Intermediate cut points must be inside the room","mode.decor":"Background editor","decor.select":"Select","decor.line":"Line","decor.rect":"Rectangle","decor.ellipse":"Oval","decor.text":"Text","decor.erase":"Erase","decor.erase_confirm_title":"Erase object?","decor.color":"Color","decor.width":"Line width","decor.line_style":"Line style","decor.line_style_solid":"Solid","decor.line_style_dashed":"Dashed","decor.fill":"Fill","decor.fill_color":"Fill color","decor.length":"Length","decor.size":"Size","decor.angle":"Rotation angle, °","decor.text_size":"Text size","decor.backdrop_properties":"Plan backdrop properties","decor.text_title":"Text label","decor.object_title":"Edit {kind}","decor.text_label":"Text","decor.live_group":"Insert HA variable","decor.live_entity":"Entity","decor.live_entity_ph":"choose an entity","decor.live_attr":"Value","decor.live_attr_ph":"choose state or attribute","decor.live_state":"State","decor.backdrop":"Backdrop image","decor.backdrop_hint":"Drag to move; pull a corner to resize; use the top handle to rotate. Shift changes proportions or frees the angle.","decor.backdrop_reset":"Reset the picture","decor.backdrop_reset_done":"The picture is back at its original place and size","marker.icon_auto":"Auto: {icon} (by icon rules; pick one to override)","marker.icon_pin_auto":"Pin","mode.plan_tip":"Plan editor — the geometry of the home: draw and split/merge rooms, bind them to HA areas, place doors, windows and gates, move room cards, set the scale","mode.devices_tip":"Device editor — everything about icons: drag to position, click to edit binding/icon/display, add virtual devices, icon rules","mode.decor_tip":"Background editor — purely visual decor under the plan: lines, rectangles, ovals and text labels that never react to clicks","space.glow_enabled":"Light-source glow","gs.glow_group":"Light-source glow","gs.glow_base":"House darkness","gs.glow_light":"Default light color / intensity","gs.wall_group":"Walls","gs.wall_fill":"Wall fill","gs.glow_radius":"Glow radius","gs.unit_m":"m","gs.unit_ft":"ft","marker.controls_label":"Controls other light sources","marker.controls_hint":"With tap action “Toggle”, a click flips every source added here. The marker’s own bound entity is controlled directly and cannot be added; use the switch below to classify it as a light.","marker.controls_filter":"Search lights and switches…","info.controls":"Controls","marker.glow_radius_label":"Glow radius","marker.glow_radius.help":"Sets the glow radius in metres or feet; an empty value uses the radius from general settings.","marker.glow_radius.help.aria":"Help: glow radius","markup.wallthick":"Thickness","markup.select":"Select","title.markup_wallthick":"Thickness — click a wall to set how thick it is. Empty or zero removes the thickness.","markup.hint_wallthick":"click a wall · Esc closes without applying","wallthick.field":"Thickness","wallthick.unit_cm":"cm","wallthick.unit_in":"in","wallthick.apply_room":"Apply to all walls of this room","markup.draw_wall_title":"Thickness of each new room-wall segment (1–100 cm). Shared walls keep the neighbour's value.","toast.wallthick_pick":"Click a wall (not an open boundary)","toast.wallthick_open":"Open boundaries have no thickness","toast.wallthick_set":"Wall thickness set","toast.wallthick_cleared":"Wall thickness removed","toast.physical_range":"Enter a value from 1 to {max} {unit}","toast.physical_angle":"Enter a rotation angle from 0° up to, but not including, 90°","toast.physical_limit":"The space has reached the limit for this type of geometry","markup.boundary_hint":"Select a shared room boundary or a dashed stretch","markup.boundary_hint_open":"Click the start of the open stretch","markup.boundary_hint_second":"Choose the end on the selected boundary · Esc to cancel","markup.boundary_hint_retry":"Choose the end on the selected boundary","markup.boundary_hint_restore":"Click to restore the wall","markup.boundary_hint_outer":"Only a boundary shared by two rooms can be opened","markup.boundary_hint_blocked":"a partition, column or unfinished contour is above this boundary","markup.boundary_hint_ambiguous":"Choose a stretch farther from the junction","markup.boundary_cancel":"Cancel the unfinished Boundary action","toast.openwall_pick":"This tool works with room boundaries","toast.openwall_shared_only":"Only a shared wall between two rooms can be opened","toast.openwall_short":"Stretch too short — pick a farther point","toast.boundary_opened":"Boundary stretch opened","toast.boundary_restored":"Wall restored on this stretch","toast.boundary_same_edge":"Choose the end point on the selected wall","toast.boundary_ambiguous":"Choose a stretch slightly farther from the junction","toast.boundary_blocked":"This boundary is covered by independent masonry. Move or delete it with Select first","toast.delete_room_pick":"Click inside the room you want to delete","toast.openwall_openings_removed":"Openings on the virtual stretch were removed","toast.openwall_opened":"Boundary “{a}” ↔ “{b}” is now open","toast.openwall_closed":"Boundary “{a}” ↔ “{b}” is closed again","toast.opening_on_virtual":"Openings cannot sit on a virtual wall","marker.from_ha_option":"Pick from the HA list","marker.show_entities":"Show entities","marker.show_entities_tip":"Adds not only devices to the list, but all their entities too","marker.pick_ph":"Choose a device…","room.open_area":"Open the HA area","view.volumetric":"Volumetric view","view.flat":"Flat view","kiosk.title":"This screen's sizes","kiosk.hint":"Stored on this device only — every wall tablet or TV can have its own comfortable sizes.","kiosk.icon_scale":"Device icon size","kiosk.font_scale":"Room card text size","editor.kiosk":"Wall device (kiosk) mode","editor.cycle":"Auto-switch spaces every N seconds (kiosk, 0 = off)","room.settings_title":"Room settings","room.settings_section":"Room settings (override the space)","room.fill_label":"Fill in THIS room","fill.inherit":"As the space","room.custom_fill_space":"Space color","room.custom_fill_own":"Room color","room.temp_src_label":"Temperature source","room.hum_src_label":"Humidity source","room.src_average":"Average over the room's sensors (default)","room.src_pick":"A specific HA device or entity","room.src_ph":"Choose a source…","toast.room_updated":"Room updated","space.card_font":"Room-card font size (whole space)","room.sizes_section":"Font sizes","room.name_scale":"Room name size","room.label_scale":"Metrics size","preview.room_name":"Living room","toast.cfg_reload_failed":"Could not reload the plan from the server: {err}","room.settings_short":"Room settings","room.unnamed":"Unnamed room","marker.use_climate_temp":"Include the device temperature in the room","marker.use_climate_temp_tip":"Adds an air conditioner or thermostat current_temperature to the room average. Configure the external value badge separately below.","marker.value_badge_title":"Value badge","marker.value_badge.help":"Shows one selected value next to the icon. It does not affect room metrics, light, Glow or the tap action.","marker.value_badge.help.aria":"Help: device value badge","marker.value_badge_enabled":"Show a value badge","marker.value_badge_source":"Value","marker.value_badge_source.help":"Choose a specific state, supported attribute, or derived value of this device.","marker.value_badge_source.help.aria":"Help: value badge source","marker.value_badge_position":"Position","marker.value_badge_position.help":"The chosen side stays fixed and does not flip automatically at a plan edge.","marker.value_badge_position.help.aria":"Help: value badge position","marker.value_badge_right":"Right","marker.value_badge_bottom":"Below","marker.value_badge_left":"Left","marker.value_badge_top":"Above","marker.value_badge_empty":"This device has no available values","marker.value_badge_static":"A static icon does not show live values. The setting is preserved.","marker.value_badge_missing":"Source unavailable","marker.value_badge_missing_hint":"The saved source is currently unavailable. Keep it, disable the badge, or choose another value.","marker.value_badge_duplicate":"The same value is already shown inside the icon.","marker.value_badge_state":"State · {name}","marker.value_badge_marker_state":"Light-source state · {name}","marker.value_badge_lqi":"Average Zigbee signal quality","marker.value_badge_attr_current_temperature":"Current temperature · {name}","marker.value_badge_attr_temperature":"Target temperature · {name}","marker.value_badge_attr_current_humidity":"Current humidity · {name}","marker.value_badge_attr_humidity":"Humidity · {name}","marker.value_badge_attr_current_position":"Position · {name}","marker.value_badge_attr_percentage":"Speed · {name}","marker.value_badge_attr_brightness":"Brightness · {name}","marker.value_badge_attr_volume_level":"Volume · {name}","marker.value_badge_attr_battery_level":"Battery · {name}","marker.value_badge_attr_fan_speed":"Fan speed · {name}","marker.light_role_label":"Is this device a light source?","marker.light_role.help":"Controls whether this marker itself is a spatial light source. Linked lights above remain independent room-light controls.","marker.light_role.help.aria":"Help: whether this device is a light source","marker.light_role_auto_yes":"Auto (light source)","marker.light_role_auto_no":"Auto (not a light source)","marker.light_role_always":"Always a light source","marker.light_role_never":"Never a light source","marker.light_entity_label":"Leading light entity","marker.light_entity.help":"For a composite device, choose the entity whose state, colour and brightness represent this plan source. The automatic fallback remains compatible with older plans.","marker.light_entity.help.aria":"Help: leading light entity","marker.light_entity_auto":"Automatic ({entity})","marker.light_entity_none":"no controllable entity","marker.light_entity_missing":"The saved entity {entity} is unavailable. House Plan temporarily uses {fallback}; the saved choice will be restored if it returns.","marker.glow_color_label":"Glow colour and brightness","marker.glow_mode.help":"Use live source values, override only its colour, or fix both colour and brightness. The minimum is 1%; to disable the source, choose “Never”.","marker.glow_mode.help.aria":"Help: glow colour and brightness","marker.glow_mode_auto":"From source","marker.glow_mode_color":"Set colour","marker.glow_mode_fixed":"Set colour and brightness","marker.glow_color":"Glow colour","marker.glow_brightness":"Brightness","marker.glow_disabled_never":"Glow settings are unavailable because this marker is explicitly not a light source.","marker.glow_disabled_auto":"Auto mode found no spatial light source for this marker.","marker.glow_disabled_no_entity":"No active controllable Home Assistant entity is available for this spatial source.","marker.glow_passive_hint":"This source has no own Home Assistant data. Set its colour and brightness manually; its radius remains available.","marker.control_broken":"Saved source is missing or no longer marked as a light","marker.control_missing_label":"Missing plan light","marker.control_passive":"passive source","toast.marker_control_cycle":"This link would create a circular chain of light controls.","toast.marker_binding_required":"Choose a Home Assistant device before linking another light source.","confirm.unlock":"Unlock “{name}”?","toast.files_migrate_failed":"Attachments could not be moved to the new binding, links keep pointing at the old files: {err}","space.pick_saved":"Already uploaded","space.pick_saved_hint":"Plans stored on the server, including ones you detached earlier","space.no_saved":"No plans stored on the server yet.","space.loading":"Loading…","space.used_by":"in use: {list}","space.in_use":"A space still uses this plan — detach it first","btn.use":"Use","confirm.delete_plan":'Delete the plan file "{name}" from the server? This cannot be undone.',"toast.plans_list_failed":"Could not list the stored plans: {err}","toast.plan_delete_failed":"Could not delete the plan: {err}","marker.hide":"Hide","marker.hide_tip":'The device will disappear from the plan after saving but will still count toward the room signal. Restore it through "Hidden and disabled" in the device editor.',"marker.show":"Show","marker.show_tip":"The device will appear on the plan again after saving.","marker.hidden_ghost":"Device hidden by the user","marker.ha_disabled_device":"The device is disabled in Home Assistant and hidden from the plan.","marker.ha_disabled_entity":"The entity is disabled in Home Assistant and hidden from the plan.","marker.ha_disabled_all_entities":"The device has no active Home Assistant entities, so it is hidden from the plan.","marker.ha_registry_limited":"The full Home Assistant registry is unavailable to this user. The unverified object cannot be shown or used for now.","marker.delete_tip":"Completely delete the device from the plan and every aggregate. You can add it again later.","tap.run":"Run automation/script/scene","tap.cover":"Open/close (curtains/blinds)","marker.run_target_label":"What to run","marker.run_search_ph":"Search: automation, script or scene…","marker.run_target_gone":"Target {id} not found — pick again","marker.tap_confirm":"Ask for confirmation","marker.tap_confirm_tip":"Show a confirmation dialog before acting — a guard against accidental taps.","marker.toggle_hint_single":"Target: {name} ({id}).","marker.virtual_light_target":"Virtual light: {name}.","marker.virtual_light_current":"Manual state: {state} → {effect}.","marker.virtual_light_state_on":"on","marker.virtual_light_state_off":"off","marker.toggle_hint_group":"Will toggle {count} source(s): {names}.","marker.toggle_hint_current":"Now: {state} → {effect}.","marker.toggle_hint_group_current":"Currently on: {on} of {count} → {effect}.","marker.toggle_hint_skipped":"Will be skipped ({count}): {targets}.","marker.toggle_effect_turn_on":"will turn on","marker.toggle_effect_turn_off":"will turn off","marker.toggle_effect_open":"will open","marker.toggle_effect_close":"will close","marker.toggle_effect_stop":"will stop","marker.toggle_effect_toggle":"state will toggle","marker.toggle_skip_missing":"not found","marker.toggle_skip_ha_disabled":"disabled in HA","marker.toggle_skip_unavailable":"unavailable","marker.toggle_skip_unsupported":"toggle is unsupported","marker.toggle_skip_secure":"blocked for security","marker.toggle_none_no_binding":"This device has no binding, so there is no state to toggle. Tapping will do nothing.","marker.toggle_none_no_actionable_entity":"This device has no state that can be toggled. Tapping will do nothing.","marker.toggle_none_configured_targets_missing":"The configured targets are unavailable. The device's own entity will not be substituted for them.","marker.toggle_none_ha_disabled":"The target is disabled in Home Assistant and cannot be used on the plan.","marker.toggle_none_unavailable":"The target is currently unavailable. Tapping will do nothing.","marker.toggle_none_unsupported":"Home Assistant exposes no safe toggle operation for this entity. Tapping will do nothing.","marker.toggle_none_secure":"Toggling locks, alarms and secure gates from the plan is blocked for security.","run.automation":"automation","run.script":"script","run.scene":"scene","confirm.tap_run":'Run "{name}"?',"confirm.tap_toggle":'Toggle "{name}"?',"confirm.tap_cover":'Open/close "{name}"?',"toast.run_started":"Started: {name}","toast.run_target_missing":"Run target not found — check the device settings","toast.run_target_required":"Pick an automation, script or scene","toast.tap_target_changed":"The action target changed. Try again.","toast.virtual_light_toggle_failed":"Could not toggle the virtual light: {err}","toast.value_badge_source_required":"Choose a value for the badge","btn.run":"Run","vac.section":"Robot vacuum: live position","vac.autocal":"Set up automatically","vac.live":"Live position on the plan","vac.trail":"Show the robot's path","vac.cal_maps":"Calibrated maps: {maps}","vac.autocal_no_rooms":"The integration reports no room list — open “Fit manually”","vac.autocal_no_match":"Room names did not match (need ≥3 in common) — open “Fit manually”","vac.autocal_done":"Done: bound via {rooms} rooms. Start a cleanup and check","vac.cal_need_pos":"The robot is not reporting coordinates — start a cleanup and pause it","vac.cal_done":"Calibration saved. Start a cleanup and check","vac.cal_cancelled":"Calibration cancelled","vac.fit":"Fit manually","vac.fit_hint":"Drag the robot map into place, stretch by the corners","vac.fit_rotate":"Rotate 90°","vac.fit_mirror":"Mirror","vac.trail_never":"Never","vac.trail_cleaning":"While cleaning","vac.trail_always":"Always","gs.bg_group":"Stage background","gs.bg_color":"Background around the plan","gs.bg_default":"Theme default","gs.bg_theme":"theme default","gs.bg_mode":"Plan background","gs.bg_static":"Static color","gs.bg_daynight":"Follows the Sun","gs.bg_daynight_hint":"The background moves through dawn, day, dusk and night using sun.sun, or your local clock when sun data is unavailable. No compass is needed for the background.","gs.sun_group":"Sun","gs.sun_missing":"The sun.sun entity was not found — the background follows your local clock; window rays are unavailable.","gs.north":"North on the plan","gs.north_ph":"not set","gs.north_hint":"Set north only for window rays (1° steps, 15° with Shift); the Sun background works without it.","gs.north_clear":"Clear","gs.north_letter":"N","gs.sun_rays":"Sunlight through windows","gs.about_group":"About","gs.about_version":"Houseplan Card v{v}","gs.about_github":"GitHub · docs & issues","gs.about_telegram":"Telegram chat","space.bg_color":"Background around the plan","space.bg_inherit":"Inherit general","space.bg_inherited":"inherits general settings","space.bg_mode":"Plan background","space.north":"North on the plan (override)","space.north_inherited":"inherited: {v}","space.sun_rays":"Sunlight through windows","space.sun_inherit":"Inherit general","space.sun_on":"On","space.sun_off":"Off","canvas.far_objects":"{n} object(s) far from the plan","canvas.show_far":"Show","canvas.home_tip":"The plan is over there — click to fit it","gs.grid_group":"Plan maintenance","gs.grid_hint":"Updates data models, aligns plan elements to the grid and merges redundant wall fragments. An exact report is shown before anything is stored.","gs.align_all":"Optimize plans","gs.align_title":"Optimize plans","gs.align_none":"All plans already use the current optimized data model.","gs.align_count":"{n} of {total} elements will move, by at most {cm} cm.","gs.align_where":"The largest shift is in “{s}”.","gs.align_turned":"Openings whose angle is corrected: {n}.","gs.align_removed_drafts":"Invalid outlines collapsed by the grid and removed: {n}.","gs.optimize_changes":"Model migrations: {m}; plans canonicalized: {c}; merged real-wall fragments: {w}; virtual fragments: {s}.","gs.optimize_glow_migration":"Legacy Glow: {spaces} spaces → no data fill + independent Glow; {rooms} rooms → inherited data fill + independent Glow.","gs.align_warn":"Elements deliberately placed between grid nodes will move. One undo is available after the operation, only until the next plan edit.","gs.align_run":"Optimize","gs.align_done":"Plans optimized: {n} elements moved, {m} records maintained","gs.optimize_undo":"Undo last optimization","gs.optimize_undone":"The last optimization was undone","decor.furniture":"Furniture","furn.title":"Furniture library","furn.symbol":"Symbol","furn.group_furniture":"Furniture","furn.group_appliance":"Appliances","furn.group_sanitary":"Plumbing","furn.group_other":"Other","furn.width":"Width","furn.depth":"Depth","furn.pick_hint":"Pick a symbol, then click on the plan.","furn.place_hint":"Click on the plan — the piece lands against the nearest wall. Shift places it free.","furn.sym_sofa":"Sofa","furn.sym_armchair":"Armchair","furn.sym_coffee_table":"Coffee table","furn.sym_table_dining":"Dining table","furn.sym_table_round":"Round table","furn.sym_chair":"Chair","furn.sym_desk":"Desk","furn.sym_bed_double":"Double bed","furn.sym_bed_single":"Single bed","furn.sym_nightstand":"Nightstand","furn.sym_wardrobe":"Wardrobe","furn.sym_bookshelf":"Bookshelf","furn.sym_fridge":"Fridge","furn.sym_stove":"Cooker","furn.sym_dishwasher":"Dishwasher","furn.sym_washer":"Washing machine","furn.sym_dryer":"Tumble dryer","furn.sym_tv":"TV","furn.sym_ac":"Air conditioner","furn.sym_water_heater":"Water heater","furn.sym_toilet":"Toilet","furn.sym_bathtub":"Bathtub","furn.sym_shower":"Shower","furn.sym_sink":"Washbasin","furn.sym_kitchen_sink":"Kitchen sink","furn.sym_bidet":"Bidet","furn.sym_stairs":"Stairs","furn.sym_fireplace":"Fireplace","furn.sym_plant":"Plant","furn.sym_rug":"Rug","common.yes":"Yes","common.no":"No","vac.diag_source":"Source","vac.diag_platform":"Integration","vac.diag_status":"Status","vac.diag_position":"Position","vac.diag_rooms":"Rooms","vac.diag_rooms_value":"{total} · {matched} names match · {readiness}","vac.autocal_ready":"auto-calibration available","vac.autocal_not_ready":"need 3 matching names","vac.diag_path":"Integration path","vac.diag_map":"Map ID","vac.source_none":"not selected","vac.source_status_ok":"Ready","vac.source_status_missing":"Missing","vac.source_status_disabled":"Disabled in Home Assistant","vac.source_status_unavailable":"Unavailable","vac.source_status_unverified":"Cannot verify with current permissions","vac.source_status_unsupported":"No position data","vac.source_status_none":"No source","vac.source_banner_missing":"The saved source no longer exists. It was not replaced automatically; choose another source or restore it in Home Assistant.","vac.source_banner_disabled":"The saved source is disabled in Home Assistant. Enable it there or choose another source.","vac.source_banner_unverified":"Current Home Assistant permissions cannot verify this saved source. It remains pinned and will not be replaced automatically.","vac.choose_source":"Choose source","vac.source_auto":"Automatic","vac.source_auto_hint":"Use a compatible entity from this device","vac.all_cameras":"All cameras","vac.all_cameras_warn":"A camera may not provide robot data. Choose one only if it is your vacuum map.","vac.all_cameras_empty":"No other camera entities found.","vac.platform_unknown":"unknown integration","vac.cap_position":"position","vac.cap_rooms_short":"rooms","vac.cap_path":"path","vac.cap_map":"map ID","vac.cap_none":"no robot data detected","vac.xcme_hint":"Enable these Xiaomi Cloud Map Extractor attributes:","vac.documentation":"Documentation","vac.residual_title":"Check automatic calibration","vac.residual_message":"The matched rooms disagree by up to {error}. Apply this approximate calibration, refine it manually, or cancel without changing the saved setup.","vac.apply_proposal":"Apply","gs.backup_group":"Backup and transfer","gs.backup_hint":"Download a portable JSON backup or preview and import a backup made by House Plan.","backup.export_open":"Export","backup.import_open":"Import","backup.export_title":"Export House Plan","backup.import_title":"Import House Plan","backup.export_hint":"Choose whether the backup should contain the whole House Plan configuration or only the current space.","backup.full":"Full backup","backup.current_space":"Current space","backup.current_space_title":"Current space: {title}","backup.no_current_space":"No current space","backup.privacy_warning":"The archive keeps names, Home Assistant identifiers and exact coordinates. Internal plans and attachments are referenced, not embedded; runtime states and vacuum trails are not included.","backup.download":"Download JSON","backup.export_done":"Backup downloaded","backup.reading":"Checking the backup…","backup.revalidated":"The plan changed after this preview. The summary was refreshed; review it and confirm again.","backup.error.unauthorized":"You do not have permission to export or import this plan.","backup.error.not_ready":"House Plan is not ready yet. Try again in a moment.","backup.error.too_large":"The backup exceeds the 8 MiB limit.","backup.error.invalid_json":"The selected file is not valid JSON.","backup.error.invalid_format":"The selected file is not a House Plan backup.","backup.error.unsupported_export_version":"This backup format is not supported by the installed version.","backup.error.future_model":"The backup was created by a newer House Plan data model.","backup.error.invalid_config":"The backup contains an invalid House Plan configuration.","backup.error.invalid_layout":"The backup contains invalid object positions.","backup.error.invalid_content":"The backup contains invalid or inconsistent content references.","backup.error.space_not_found":"The selected space no longer exists.","backup.error.capacity_exceeded":"Adding this backup would exceed the plan limits.","backup.error.preview_expired":"The preview expired. Select the backup file again.","backup.error.preview_owner_mismatch":"This preview belongs to another Home Assistant user.","backup.error.conflict":"The plan changed after the preview. Review the refreshed summary.","backup.error.content_confirmation_required":"Confirm that unavailable local content may be detached.","backup.error.commit_failed":"The backup could not be applied safely. The previous plan was restored or is pending recovery.","backup.error.missing_plan":"A referenced plan file disappeared after the preview. Review the backup again.","backup.error.missing_content":"A referenced local attachment disappeared after the preview. Review the backup again.","backup.error.marker_control_missing":"A linked plan light is missing from the backup.","backup.error.marker_control_not_light":"A linked plan target is no longer marked as a light source.","backup.error.marker_control_self":"A light source cannot control itself.","backup.error.marker_control_cycle":"The backup contains a circular chain of light controls.","backup.error.duplicate_marker_control":"The backup contains a duplicate plan-light link.","backup.error.no_backup":"There is no import or optimization snapshot to restore.","backup.same_source":"Created on this Home Assistant instance","backup.foreign_source":"Created on another Home Assistant instance","backup.created":"Created: {value}","backup.versions":"Card {card}; integration {integration}; data model {model}","backup.count_spaces":"Spaces: {n}","backup.count_rooms":"Rooms: {n}","backup.count_walls":"Walls: {n}","backup.count_openings":"Openings: {n}","backup.count_decor":"Decor objects: {n}","backup.count_markers":"Devices: {n}","backup.count_layout":"Positions: {n}","backup.bindings":"Bindings — devices: {device}, entities: {entity}, virtual: {virtual}; positions without a space: {legacy}","backup.binding_status":"Target status — active: {active}, disabled: {disabled}, missing: {missing}","backup.missing_areas":"Areas missing on the target: {areas}","backup.dropped_marker_links":"Plan-light links outside this transferred space were omitted: {n}.","backup.replace_warning":"This replaces the current configuration and layout. Uploaded files are never deleted. One undo remains available until the next plan edit.","backup.foreign_bookkeeping":"Instance-specific known/new-device bookkeeping will not be imported.","backup.final_name":"New space name","backup.target_settings":"The target instance's global settings remain unchanged.","backup.duplicates":"Bindings already present: {n}","backup.skip":"Skip duplicate devices","backup.virtual_copy":"Add safe static virtual copies","backup.content":"Referenced content","backup.content_available":"available locally","backup.content_external":"external link","backup.content_detach_required":"will be detached","backup.confirm_detach":"I understand that unavailable internal plans and attachments will be detached from the imported configuration.","backup.replace":"Replace","backup.add":"Add space","backup.space_done":"Space imported: {rooms} rooms, {markers} devices","backup.full_done":"Backup restored: {spaces} spaces, {rooms} rooms, {markers} devices","backup.undo_import":"Undo last full import","backup.import_undone":"Full import undone"};const Wo={en:jo,ru:{"editor.context_actions":"Действия: {object}","editor.tool_options":"Параметры инструмента: {tool}","editor.palette":"Палитра: {tool}","editor.open_group":"Группа инструментов: {group}","editor.group_active":"{group} — активно: {item}","editor.disabled_action":"{action} недоступно: {reason}","btn.properties":"Свойства","btn.keep_as_walls":"Оставить замкнутыми стенами","title.markup_partition":"Перегородка: нарисуйте одну независимую физическую стену","title.markup_select":"Выбор и редактирование стен, колонн и сохранённых контуров","title.markup_column":"Колонна: кликните точку сетки для квадратной колонны","markup.partition":"Перегородка","markup.column":"Колонна","markup.hint_partition":"два клика рисуют одну перегородку · Shift фиксирует шаг 45°","markup.hint_column":"кликните точку сетки, чтобы поставить квадратную колонну","history.draft_segment":"Добавление сегмента черновика комнаты","history.draft_merge":"Соединение незавершённых контуров комнаты","history.draft_segment_delete":"Удаление сегмента черновика комнаты","history.partition_add":"Добавление перегородки","history.column_add":"Добавление колонны","history.physical_edit":"Изменение физического объекта","history.physical_delete":"Удаление физического объекта","history.physical_move":"Перемещение физического объекта","history.contour_to_partitions":"Преобразование контура в замкнутые стены","toast.column_duplicate":"Колонна с тем же центром и внешним размером уже существует","confirm.delete_draft":"Удалить весь незавершённый контур комнаты?","confirm.delete_draft_segment":"Удалить этот сегмент черновика? Оставшийся контур может разделиться на два.","physical.partition_properties":"Свойства перегородки","physical.column_properties":"Свойства колонны","physical.draft_properties":"Свойства сегмента черновика","physical.shape":"Форма","physical.square":"Квадрат","physical.circle":"Круг","physical.diameter":"Диаметр","physical.side":"Сторона","physical.rotation":"Угол поворота","physical.length":"Длина","physical.allowed_range":"Допустимо: 1–{max} {unit}","physical.delete_segment":"Удалить сегмент","physical.delete_draft":"Удалить весь контур","physical.partition_size_title":"Толщина перегородки, создаваемой вторым кликом (1–100 см).","physical.column_size_title":"Внешняя сторона квадратной колонны, создаваемой кликом (1–150 см).","card.title":"План дома","count.devices":"{n} устр.","empty.no_spaces":"Пространств пока нет.","empty.add_first":"Добавьте первое пространство и загрузите план этажа.","empty.install":"Установите интеграцию House Plan и добавьте запись в «Устройства и службы».","btn.add_space":"Добавить пространство","btn.cancel":"Отмена","btn.save":"Сохранить","btn.close":"Закрыть","btn.delete":"Удалить","btn.edit":"Редактировать","btn.open_in_ha":"Открыть в HA","btn.reset":"Сброс","btn.attach":"Прикрепить…","btn.upload":"Загрузить…","btn.replace":"Заменить…","title.zoom_in":"Приблизить","title.zoom_out":"Отдалить","title.zoom_fit":"Вписать всё","title.add_device":"Добавить устройство на план","title.show_all":"Показать скрытые и деактивированные устройства служебными маркерами (только в этой вкладке)","title.markup":"Разметка комнат: сетка, линии, контуры","title.configure_space":"Настроить пространство","title.add_space":"Добавить пространство","title.markup_add":"Контур комнаты: соединяйте точки сетки линиями до замыкания комнаты; Shift фиксирует направление с шагом 45°","title.markup_merge":"Объединить: клик по одной комнате, затем по соседней с общей стеной","title.markup_split":"Разделить комнату: клик по комнате, затем две точки на её стенах","title.markup_delroom":"Удалить комнату: кликните внутри комнаты и подтвердите удаление","title.markup_boundary":"Граница: две точки на сплошной общей стене делают участок виртуальным; клик по пунктиру восстанавливает стену","title.need_plan":"Загрузите подложку (план этажа)","markup.add":"Контур комнаты","markup.merge":"Объединить","markup.split":"Разделить","markup.resize":"Размер","title.markup_resize":"Изменение размера комнат: тяните ручку стены; клик по комнате — угловая рамка масштаба","markup.hint_resize":"тяните ручку стены · клик по комнате — угловая рамка · Esc отменяет перетаскивание · Ctrl+Z — отмена шага","markup.opening":"Проём","markup.boundary":"Граница","markup.delete_room":"Удалить комнату","history.undo":"Отменить","history.redo":"Повторить","history.undo_named":"Отменить: {name} (Ctrl+Z)","history.redo_named":"Повторить: {name} (Ctrl+Shift+Z / Ctrl+Y)","history.undo_empty":"Нет операций для отмены","history.redo_empty":"Нет операций для повтора","history.undone":"Отменено: {name}","history.redone":"Повторено: {name}","history.add_room":"Создание комнаты","history.split_room":"Разделение комнаты","history.merge_rooms":"Объединение комнат","history.resize_room":"Изменение размера комнаты","history.open_boundary":"Открытие границы","history.close_boundary":"Восстановление стены","history.wall_thickness":"Изменение толщины стены","history.add_opening":"Добавление дверного или оконного проёма","history.edit_opening":"Изменение проёма","history.move_opening":"Перемещение проёма","history.delete_opening":"Удаление проёма","history.delete_room":"Удаление комнаты","history.decor_add":"Добавление объекта декора","history.decor_edit":"Изменение объекта декора","history.decor_move":"Перемещение объекта декора","history.decor_transform":"Трансформация объекта декора","history.decor_delete":"Удаление объекта декора","history.backdrop_transform":"Трансформация подложки плана","title.markup_opening":"Проёмы: клик по стене — добавить, клик по проёму — редактировать","opening.new":"Новый проём","opening.edit":"Проём","opening.door":"Дверь","opening.window":"Окно","opening.gate":"Ворота","opening.type_label":"Тип","opening.length_label":"Длина, см","opening.contact_label":"Датчик открытия","opening.lock_label":"Замок","opening.none":"— нет —","opening.invert":"Инвертировать открыто/закрыто","opening.flip_h":"Петли с другой стороны","opening.flip_v":"Открывается в другую сторону","opening.open":"Открыто","opening.closed":"Закрыто","opening.locked":"Заперто","opening.unlocked":"Не заперто","opening.state_unknown":"недоступно","opening.no_entities":"Датчики не привязаны — статичный символ на плане.","toast.opening_no_wall":"Кликните рядом со стеной комнаты — проёмы ставятся на стены","markup.delete":"Удалить","markup.hint_points":"точек: {n} · Shift — шаг 45° · Esc/Ctrl+Z — убрать точку · клик по первой или Ctrl/Cmd+клик — замкнуть","markup.hint_start":"кликните точку сетки, чтобы начать контур","tip.lqi":"средний сигнал zigbee:","tip.area":"площадь: {value}","info.device_header":"Устройство на плане","info.model":"Модель","info.state":"Состояние","info.link":"Ссылка","info.manuals":"Инструкции","info.none":"Нет дополнительной информации","marker.new_device":"Новое устройство","marker.name_label":"Имя (отображается на плане)","marker.name_ph":"Название","marker.binding_label":"Привязка к устройству HA","marker.binding_disabled":"деактивировано в Home Assistant","marker.virtual_option":"Виртуальное устройство (без привязки)","marker.search_ph":"Поиск устройства / группы…","marker.nothing_found":"ничего не найдено","marker.room_label":"Комната","marker.room_override":" (переопределить размещение)","marker.room_choose":"— выберите комнату —","marker.room_auto":"— по зоне устройства (авто) —","marker.icon_label":"Иконка","marker.icon_ph":"mdi:… (пусто = авто)","marker.display_label":"Отображение","display.badge":"Значок + состояние","display.icon_ripple":"Значок + состояние и активность","display.static_icon":"Всегда статичный значок","marker.display_hint":"Значок + состояние меняет подложку при работе, открытии, тревоге и недоступности. Значок + состояние и активность дополнительно показывает короткую пульсацию для событий и постоянную — для работы, движения или присутствия. Значение + состояние заменяет значок одним однозначным значением HA. Статичный значок не реагирует на состояния устройства.","marker.display_hint_badge":"Значок и динамическая подложка показывают состояние устройства без обычной пульсации активности. Красная тревога сохраняется.","marker.display_hint_icon":"Значок и динамическая подложка показывают состояние устройства без обычной пульсации активности. Красная тревога сохраняется.","marker.display_hint_icon_ripple":"Значок, динамическая подложка и пульсация: короткая — для событий, постоянная — для работы, движения или присутствия. Красная тревога имеет отдельный приоритет.","marker.display_hint_value":"Значок заменяется одним однозначным значением Home Assistant; подложка продолжает показывать состояние. Красная тревога сохраняется.","marker.display_hint_static_icon":"Всегда одинаковые чёрная подложка и значок. Состояния, активность, недоступность и тревоги не меняют отображение.","marker.static_alarm_warning":"Это устройство может сообщать о тревогах. Статичный режим скрывает визуальную тревогу маркера.","marker.preview.title":"Предпросмотр отображения","marker.preview.actual":"Сейчас","marker.preview.example":"Пример","marker.preview.integration":"Интеграция","marker.preview.source":"Источник отображения","marker.preview.current_state":"Текущее состояние","marker.preview.result":"На плане","marker.preview.details":"Подробнее об источниках","marker.preview.select_source":"Выберите устройство или сущность Home Assistant, чтобы увидеть фактическое отображение.","marker.preview.unknown_provider":"Интеграция неизвестна","marker.preview.virtual_provider":"House Plan · виртуальное устройство","marker.preview.no_source":"Нет активного источника","marker.preview.no_state":"Нет текущего состояния","marker.preview.mixed_states":"Несколько разных состояний","marker.preview.multiple_sources":"Источников: {n}","marker.preview.more_sources":"ещё {n}","marker.preview.scaled":"Предпросмотр уменьшен до {n}%, чтобы поместиться. Сохранённый размер не изменится.","marker.preview.demo_activity":"Показать пример активности","marker.preview.demo_notice":"Временный пример активности; фактическое состояние устройства не меняется.","marker.preview.demo_short":"Показать краткую активность","marker.preview.demo_continuous":"Показать постоянную активность","marker.preview.stop_continuous":"Остановить постоянную активность","marker.preview.demo_short_notice":"Пример краткой активности; фактическое состояние устройства не меняется.","marker.preview.demo_continuous_notice":"Пример постоянной активности; фактическое состояние устройства не меняется.","marker.preview.demo_already_visible":"Текущее состояние уже показывает реальную активность или тревогу.","marker.preview.reduced_motion":"В системе включено уменьшение движения, поэтому обычная активность показана точкой.","marker.preview.reason.neutral":"Нейтральная тёмная подложка","marker.preview.reason.working":"Жёлтая подложка: устройство сейчас работает","marker.preview.reason.working_activity":"Жёлтая подложка и эффект активности: устройство сейчас работает","marker.preview.reason.open":"Оранжевая подложка: физически открыто или разблокировано","marker.preview.reason.cover_icon_state":"Нейтральная подложка; состояние и значок определяет сущность шторы или заслонки","marker.preview.reason.presence":"Эффект активности, пока обнаружено присутствие","marker.preview.reason.event":"Короткий эффект активности после события","marker.preview.reason.transition":"Эффект активности во время движения или смены состояния","marker.preview.reason.media_neutral":"Медиаустройство остаётся на нейтральной тёмной подложке, пока доступно","marker.preview.reason.unavailable":"Приглушённая нейтральная подложка: отключено или недоступно","marker.preview.reason.alarm":"Красная тревожная подложка; тревога отображается всегда","marker.preview.reason.live_states_disabled":"Оформление по живым состояниям отключено в настройках карточки","marker.preview.reason.value_no_state":"Нет подходящего значения состояния — вместо него показан значок","marker.preview.reason.value_ambiguous_sources":"Подходит несколько источников значения — вместо него показан значок","marker.preview.reason.value_non_scalar":"Источник вернул не простое значение — вместо него показан значок","marker.preview.reason.value_virtual":"У виртуального устройства нет значения HA — вместо него показан значок","marker.preview.reason.vacuum_live_plan_only":"Живая позиция и след пылесоса доступны только на полном плане","marker.preview.reason.hidden_design_preview":"Устройство скрыто на плане, но его оформление видно в предпросмотре","marker.preview.reason.composite_power_source":"Состояние берётся из сущности питания устройства; вспомогательные переключатели не учитываются","marker.preview.reason.activity_display_disabled":"Выбранный режим отображения не показывает обычные эффекты активности","marker.preview.reason.ha_disabled":"Привязка отключена в Home Assistant, поэтому устройство будет скрыто на плане","marker.preview.reason.orphaned":"Сохранённая привязка Home Assistant больше не найдена","marker.preview.reason.static_icon":"Статичный режим: состояние устройства не меняет значок","marker.activity_color":"Цвет пульсации активности","marker.ripple_size":"Размер пульсации активности","marker.activity_alarm_note":"Цвет и размер относятся только к обычной активности и не влияют на красную тревогу.","marker.pulse_a11y_alarm":"Тревога","marker.pulse_a11y_event":"Недавнее событие","marker.pulse_a11y_presence":"Обнаружено присутствие","marker.pulse_a11y_transition":"Изменение состояния","marker.pulse_a11y_running":"Работает","marker.size_label":"Размер / поворот значка","marker.angle_label":"Поворот","marker.model_label":"Модель","marker.model_ph":"напр. Aqara T&H","marker.link_label":"Ссылка","marker.desc_label":"Описание","marker.desc_ph":"Заметки, характеристики…","marker.manuals_label":"Инструкции (PDF и т.п.)","marker.sub_device":"устройство","marker.sub_z2m_group":" · Z2M-группа","marker.sub_group":"группа","marker.sub_helper":"хелпер","space.new":"Новое пространство","space.header":"Пространство","space.title_label":"Название","space.title_ph":"Например: Гараж","space.plan_label":"Подложка (план)","space.no_plan":"нет подложки","space.plan_alt":"план","room.new":"Новая комната","room.name_label":"Отображаемое имя","room.name_ph":"Например: Терраса","room.area_label":"Зона Home Assistant (свободные)","room.no_area_option":"— без зоны —","room.default_name":"Комната","device.unnamed":"без имени","device.light_group":"группа света","device.fallback":"устройство","device.virtual":"виртуальное устройство","confirm.delete_room":"Удалить комнату «{name}»?","confirm.merge_rooms":"Слить комнаты «{a}» и «{b}»?","confirm.remove_marker":"Удалить «{name}» с плана? Устройство исчезнет полностью и перестанет участвовать в данных плана. Позже его можно будет добавить заново.","confirm.erase_decor":"Стереть объект «{kind}»? Действие можно отменить из истории редактора.","confirm.delete_space":"Удалить пространство «{title}» со всеми комнатами и разметкой?","toast.pos_save_failed":"Не удалось сохранить позицию: {err}","toast.no_entity":"У устройства нет подходящей сущности","toast.ha_disabled_action":"Деактивированный объект Home Assistant нельзя использовать на плане.","toast.ha_disabled_show_device":"Деактивированное в Home Assistant устройство нельзя показать на плане. Сначала активируйте его в Home Assistant.","toast.ha_disabled_show_entity":"Деактивированную в Home Assistant сущность нельзя показать на плане. Сначала активируйте её в Home Assistant.","toast.ha_disabled_add":"Деактивированный объект Home Assistant нельзя добавить на план. Сначала активируйте его в Home Assistant.","toast.ha_binding_unverified":"Статус объекта не удалось подтвердить по реестру Home Assistant. Отображение и действия временно недоступны.","toast.markup_needs_server":"Разметка доступна после переноса конфига на сервер","toast.conflict":"Конфиг изменён в другом окне — данные обновлены, повторите последнее действие","toast.cfg_save_failed":"Не удалось сохранить конфиг: {err}","toast.room_overlap":"Контур накладывается на комнату «{name}» — комнаты не должны накладываться","toast.contour_min_edges":"Чтобы замкнуть контур, сначала нарисуйте минимум две грани","toast.contour_cannot_close":"Контур нельзя замкнуть: он вырожден или пересекает сам себя","toast.merge_not_adjacent":"Объединять можно только комнаты с общей стеной","toast.rooms_merged":"Комнаты объединены в «{name}»","toast.split_pick_wall":"Начните разрез на стене комнаты","toast.split_bad_cut":"Разрез — от стены до стены внутри комнаты, без пересечения стен и самого себя","merge.header":"Объединение комнат","merge.hint":"У объединённой комнаты одно имя и одна зона. Вторая зона освобождается — её устройства уйдут с плана, пока их не заберёт другая комната.","merge.keep":"Оставить","merge.no_area":"без зоны","room.split_header":"Новая комната после разделения","toast.room_saved":"Комната сохранена ({n}). Устройств добавлено: {added}. Обведите следующую или выйдите из разметки.","toast.room_saved_no_area":"Комната сохранена ({n}, без зоны). Обведите следующую или выйдите из разметки.","toast.marker_needs_server":"Редактирование устройств доступно после переноса конфига на сервер","toast.virtual_name_required":"Укажите имя виртуального устройства","toast.marker_saved":"Устройство сохранено","toast.marker_removed":"Устройство удалено с плана","toast.integration_missing":"Интеграция House Plan не установлена — управление недоступно","toast.plan_formats":"Поддерживаются SVG, PNG, JPG, WebP","toast.plan_required":"Загрузите подложку — план этажа обязателен","toast.space_added_onboard":"Пространство добавлено. Обведите комнаты: кликайте по точкам сетки и замкните контур.","toast.space_added":"Пространство добавлено","toast.space_saved":"Пространство сохранено","toast.space_deleted":"Пространство удалено","toast.delete_failed":"Ошибка удаления: {err}","toast.error":"Ошибка: {err}","toast.file_failed":"Файл «{name}» не загружен: {err}","toast.files_attached":"Прикреплено файлов: {n}","err.unknown":"неизвестная ошибка","err.code":"код {code}","err.too_large":"файл больше {mb} МБ","err.bad_ext":"недопустимый тип (нужен PDF/изображение)","err.unauthorized":"нужны права администратора","editor.title":"Заголовок","editor.default_floor":"Пространство по умолчанию","editor.icon_size":"Размер иконок, % ширины плана","editor.show_temperature":"Показывать температуру","editor.live_states":"Живые состояния (вкл/выкл, открыто…)","editor.show_signal":"Показывать сигнал zigbee (LQI)","editor.language":"Язык интерфейса","editor.lang_auto":"Авто (профиль HA)","editor.lang_en":"English","editor.lang_ru":"Русский","title.icon_rules":"Правила иконок: какая MDI-иконка достаётся устройству по имени","rules.title":"Правила иконок","rules.hint":"Правила проверяются сверху вниз по строке «имя устройства + модель» (regex без учёта регистра); срабатывает первое совпадение. Если ничего не подошло — решает device class сущности, затем — иконка-заглушка.","rules.pattern_ph":"regex, напр. розетк|plug","rules.icon_ph":"mdi:power-socket-de","rules.add":"Добавить правило","rules.reset":"Сбросить к умолчаниям","rules.test_ph":"Проверьте имя устройства…","rules.invalid":"некорректный regex","rules.saved":"Правила иконок сохранены","btn.up":"Вверх","btn.down":"Вниз","tap.info":"Карточка устройства","tap.more_info":"Диалог HA (more-info)","tap.toggle":"Переключить состояние","marker.tap_label":"Действие по нажатию для этого устройства","tap.toggle_note":"Под селектором показано, какое состояние изменится. Защищённые устройства не переключаются с плана.","import.title":"Создать пространства из этажей HA","import.hint":"Home Assistant уже знает эти этажи. Отметьте, какие превратить в пространства плана — далее для каждого попросим картинку плана. Комнаты затем обводятся вручную по плану.","import.start":"Создать: {n}","import.manual":"Начать с нуля","import.progress":"Этаж {i} из {n}","import.done":"Пространства созданы. Обведите комнаты: кликайте по точкам сетки и замкните контур.","btn.skip":"Пропустить","space.scale_label":"Масштаб (размер клетки сетки)","space.scale_unit":"см на клетку","space.display_section":"Отображение","space.show_borders":"Всегда отображать границы комнат","space.show_names":"Отображать названия комнат (перетаскиваются)","space.room_color":"Цвет границ и названий","space.opacity":"Прозрачность","space.fill_label":"Заливка комнат","fill.none":"Нет","fill.lqi":"По силе зигби-сигнала","fill.light":"По освещению","fill.custom":"Свой цвет","space.custom_fill":"Цвет заливки","space.source_file":"У меня есть картинка плана","space.source_draw":"Нет подложки — нарисую комнаты вручную","space.orientation":"Холст","orient.landscape":"Альбомный","orient.portrait":"Портретный","orient.square":"Квадрат","fill.temp":"По температуре","space.temp_min":"Комфорт от","space.temp_max":"до","tip.temp_avg":"средняя температура:","space_card.button":"Перейти к пространству","space_card.not_found":"Пространство «{id}» не найдено","space_card.loading":"Загрузка…","continuity.restore_plan":"Восстанавливаем план…","continuity.restore_connection":"Восстанавливаем подключение к устройствам…","continuity.retry":"Повторить","editor.space":"Пространство","editor.show_button":"Показывать кнопку","editor.button_label":"Текст кнопки","editor.button_target":"Путь дашборда (куда вести)","marker.sub_entity":"сущность","title.general_settings":"Общие настройки","gs.title":"Общие настройки","gs.hint":"Цвета заливок действуют на все пространства; у каждого цвета своя прозрачность. Какой режим заливки использует пространство — задаётся в его диалоге.","gs.light_group":"Заливка: освещение","gs.light_on":"Свет включён","gs.light_off":"Весь свет выключен","gs.temp_group":"Заливка: температура","gs.temp_cold":"Холодно","gs.temp_ok":"Комфорт","gs.temp_hot":"Жарко","gs.lqi_group":"Заливка: зигби-сигнал","gs.lqi_low":"Слабый сигнал","gs.lqi_high":"Сильный сигнал","gs.reset":"Сбросить к умолчаниям","gs.saved":"Общие настройки сохранены","space.show_lqi":"Показывать зигби-сигнал (LQI) у устройств","space.hide_decor":"Скрыть декоративный слой","space.hide_decor_tip":"Линии, фигуры, надписи и мебель остаются на месте — их видно в редакторе подложки, но не на плане.","space.hide_openings":"Скрыть проёмы","space.hide_openings_tip":"Двери, окна и ворота не рисуются, но продолжают работать: свет проходит, солнце светит в окна, датчики открытия срабатывают. В редакторе плана проёмы видно всегда.","gs.light_none":"Нет источников света","mode.plan":"Редактор плана","mode.devices":"Редактор устройств","display.value":"Значение + состояние","marker.subarea":"без зоны, вручную","device.new":"Новое устройство — откройте его редактор, чтобы снять отметку","opening.unlock_action":"Открыть замок","opening.lock_action":"Закрыть замок","opening.lock_pending":"Выполняется…","title.close_editor":"Закрыть редактор (вернуться к просмотру)","devbar.add":"Добавить","devbar.show_all":"Скрытые и деактивированные","devbar.rules":"Правила иконок","space.roomcard_section":"В карточке комнаты:","space.label_temp":"Температура","space.label_hum":"Влажность","space.label_lqi":"Средний Zigbee-сигнал","space.label_light":"Свет вкл/выкл","roomcard.light_on":"Вкл","roomcard.light_off":"Выкл","roomcard.light_partial":"{on} из {total}","toast.split_pick_inside":"Промежуточные точки разреза — внутри комнаты","mode.decor":"Редактор подложки","decor.select":"Выбрать","decor.line":"Линия","decor.rect":"Прямоугольник","decor.ellipse":"Овал","decor.text":"Надпись","decor.erase":"Стереть","decor.erase_confirm_title":"Стереть объект?","decor.color":"Цвет","decor.width":"Толщина линии","decor.line_style":"Стиль линии","decor.line_style_solid":"Сплошная","decor.line_style_dashed":"Пунктирная","decor.fill":"Заливка","decor.fill_color":"Цвет заливки","decor.length":"Длина","decor.size":"Размер","decor.angle":"Угол поворота, °","decor.text_size":"Размер текста","decor.backdrop_properties":"Свойства картинки-подложки","decor.text_title":"Надпись","decor.object_title":"Редактирование: {kind}","decor.text_label":"Текст","decor.live_group":"Вставить переменную HA","decor.live_entity":"Сущность","decor.live_entity_ph":"выберите сущность","decor.live_attr":"Значение","decor.live_attr_ph":"выберите состояние или атрибут","decor.live_state":"Состояние","decor.backdrop":"Картинка-подложка","decor.backdrop_hint":"Тяните картинку — перемещение; угол — размер; верхнюю ручку — поворот. Shift меняет пропорции или снимает шаг угла.","decor.backdrop_reset":"Вернуть картинку","decor.backdrop_reset_done":"Картинка вернулась на своё место и в свой размер","marker.icon_auto":"Авто: {icon} (по правилам иконок; выберите свою, чтобы заменить)","marker.icon_pin_auto":"Закрепить","mode.plan_tip":"Редактор плана — геометрия дома: рисование и объединение/разделение комнат, привязка к зонам HA, двери, окна и ворота, карточки комнат, масштаб","mode.devices_tip":"Редактор устройств — всё про значки: перетаскивание, клик — настройка привязки/иконки/отображения, виртуальные устройства, правила иконок","mode.decor_tip":"Редактор подложки — чисто визуальный декор под планом: линии, прямоугольники, овалы и надписи, не реагирующие на клики","space.glow_enabled":"Свечение источников света","gs.glow_group":"Свечение источников света","gs.glow_base":"Темнота дома","gs.glow_light":"Цвет света по умолчанию / интенсивность","gs.wall_group":"Стены","gs.wall_fill":"Цвет заливки стен","gs.glow_radius":"Радиус свечения","gs.unit_m":"м","gs.unit_ft":"фут","marker.controls_label":"Управляет другими источниками света","marker.controls_hint":"При действии «Переключить» клик разом переключает все добавленные здесь источники. Собственная сущность маркера управляется напрямую и сюда не добавляется; чтобы считать её светом, включите флаг ниже.","marker.controls_filter":"Поиск ламп и выключателей…","info.controls":"Управляет","marker.glow_radius_label":"Радиус свечения","marker.glow_radius.help":"Задаёт радиус свечения в метрах или футах; пустое значение использует радиус из общих настроек.","marker.glow_radius.help.aria":"Подсказка: радиус свечения","markup.wallthick":"Толщина","markup.select":"Выбрать","title.markup_wallthick":"Толщина — клик по стене задаёт толщину. Пустое поле или ноль убирает толщину.","markup.hint_wallthick":"клик по стене · Esc закрывает без применения","wallthick.field":"Толщина","wallthick.unit_cm":"см","wallthick.unit_in":"дюйм","wallthick.apply_room":"Применить ко всем стенам комнаты","markup.draw_wall_title":"Толщина каждого нового сегмента стены комнаты (1–100 см). Общие стены сохраняют значение соседа.","toast.wallthick_pick":"Кликните по стене (не по открытой границе)","toast.wallthick_open":"У открытой границы нет толщины","toast.wallthick_set":"Толщина стены задана","toast.wallthick_cleared":"Толщина стены убрана","toast.physical_range":"Введите значение от 1 до {max} {unit}","toast.physical_angle":"Введите угол поворота от 0° до 90°, не включая 90°","toast.physical_limit":"В пространстве достигнут лимит объектов этого типа","markup.boundary_hint":"Выберите общую границу комнат или пунктирный участок","markup.boundary_hint_open":"Кликните начало открытого участка","markup.boundary_hint_second":"Укажите конец на выбранной границе · Esc — отмена","markup.boundary_hint_retry":"Укажите конец на выбранной границе","markup.boundary_hint_restore":"Кликните, чтобы восстановить стену","markup.boundary_hint_outer":"Открыть можно только общую границу двух комнат","markup.boundary_hint_blocked":"над границей находится перегородка, колонна или незавершённый контур","markup.boundary_hint_ambiguous":"Выберите участок чуть дальше от стыка","markup.boundary_cancel":"Отменить незавершённое действие «Граница»","toast.openwall_pick":"Инструмент работает с границами комнат","toast.openwall_shared_only":"Открыть можно только общую стену двух комнат","toast.openwall_short":"Слишком короткий отрезок — кликните дальше","toast.boundary_opened":"Участок границы открыт","toast.boundary_restored":"Стена на участке восстановлена","toast.boundary_same_edge":"Выберите конечную точку на выбранной стене","toast.boundary_ambiguous":"Выберите участок чуть дальше от стыка","toast.boundary_blocked":"Граница перекрыта независимой стеной. Сначала переместите или удалите её инструментом «Выбрать»","toast.delete_room_pick":"Кликните внутри комнаты, которую нужно удалить","toast.openwall_openings_removed":"Проёмы на виртуальном отрезке удалены","toast.openwall_opened":"Граница «{a}» ↔ «{b}» теперь открыта","toast.openwall_closed":"Граница «{a}» ↔ «{b}» снова закрыта","toast.opening_on_virtual":"Проёмы на виртуальной стене запрещены","marker.from_ha_option":"Выбрать из списка HA","marker.show_entities":"Отображать сущности","marker.show_entities_tip":"Добавляет в список не только устройства, но и все их сущности","marker.pick_ph":"Выберите устройство…","room.open_area":"Открыть зону в HA","view.volumetric":"Объёмный вид","view.flat":"Плоский вид","kiosk.title":"Размеры на этом экране","kiosk.hint":"Хранится только на этом устройстве — у каждого настенного планшета или ТВ свои удобные размеры.","kiosk.icon_scale":"Размер значков устройств","kiosk.font_scale":"Размер текста карточек комнат","editor.kiosk":"Режим настенного устройства (киоск)","editor.cycle":"Автосмена пространств каждые N секунд (киоск, 0 = выкл)","room.settings_title":"Настройки комнаты","room.settings_section":"Настройки комнаты (переопределяют пространство)","room.fill_label":"Заливка в ЭТОЙ комнате","fill.inherit":"Как у пространства","room.custom_fill_space":"Цвет пространства","room.custom_fill_own":"Цвет комнаты","room.temp_src_label":"Источник температуры","room.hum_src_label":"Источник влажности","room.src_average":"Средняя по датчикам комнаты (по умолчанию)","room.src_pick":"Конкретное устройство или сущность HA","room.src_ph":"Выберите источник…","toast.room_updated":"Комната обновлена","space.card_font":"Размер шрифта карточек комнат (всё пространство)","room.sizes_section":"Размеры шрифтов","room.name_scale":"Размер названия","room.label_scale":"Размер подписей","preview.room_name":"Гостиная","toast.cfg_reload_failed":"Не удалось перечитать план с сервера: {err}","room.settings_short":"Настройки комнаты","room.unnamed":"Комната без имени","marker.use_climate_temp":"Учитывать температуру устройства в комнате","marker.use_climate_temp_tip":"Добавляет current_temperature кондиционера или термостата в среднюю температуру комнаты. Внешний бейдж со значением настраивается отдельно ниже.","marker.value_badge_title":"Бейдж со значением","marker.value_badge.help":"Показывает одно выбранное значение рядом со значком. Не влияет на комнатные показатели, свет, Glow и действие по нажатию.","marker.value_badge.help.aria":"Подсказка: бейдж со значением устройства","marker.value_badge_enabled":"Отображать бейдж со значением","marker.value_badge_source":"Значение","marker.value_badge_source.help":"Выберите конкретное состояние, поддерживаемый атрибут или производное значение этого устройства.","marker.value_badge_source.help.aria":"Подсказка: источник значения бейджа","marker.value_badge_position":"Расположение","marker.value_badge_position.help":"Выбранная сторона сохраняется постоянно и не переворачивается автоматически у края плана.","marker.value_badge_position.help.aria":"Подсказка: расположение бейджа","marker.value_badge_right":"Справа","marker.value_badge_bottom":"Снизу","marker.value_badge_left":"Слева","marker.value_badge_top":"Сверху","marker.value_badge_empty":"У этого устройства нет доступных значений","marker.value_badge_static":"Статичный значок не показывает живые значения. Настройка сохранится.","marker.value_badge_missing":"Источник недоступен","marker.value_badge_missing_hint":"Сохранённый источник сейчас недоступен. Можно сохранить его, отключить бейдж или выбрать другой.","marker.value_badge_duplicate":"То же значение уже показано внутри значка.","marker.value_badge_state":"Состояние · {name}","marker.value_badge_marker_state":"Состояние источника света · {name}","marker.value_badge_lqi":"Качество Zigbee-сигнала (среднее)","marker.value_badge_attr_current_temperature":"Текущая температура · {name}","marker.value_badge_attr_temperature":"Целевая температура · {name}","marker.value_badge_attr_current_humidity":"Текущая влажность · {name}","marker.value_badge_attr_humidity":"Влажность · {name}","marker.value_badge_attr_current_position":"Положение · {name}","marker.value_badge_attr_percentage":"Скорость · {name}","marker.value_badge_attr_brightness":"Яркость · {name}","marker.value_badge_attr_volume_level":"Громкость · {name}","marker.value_badge_attr_battery_level":"Заряд батареи · {name}","marker.value_badge_attr_fan_speed":"Скорость вентилятора · {name}","marker.light_role_label":"Является источником света","marker.light_role.help":"Определяет, является ли сам маркер пространственным источником света. Привязанные выше лампы остаются независимыми источниками для комнаты.","marker.light_role.help.aria":"Подсказка: является ли устройство источником света","marker.light_role_auto_yes":"Автоматически (источник света)","marker.light_role_auto_no":"Автоматически (не источник света)","marker.light_role_always":"Всегда источник света","marker.light_role_never":"Никогда не источник света","marker.light_entity_label":"Ведущая сущность света","marker.light_entity.help":"Для составного устройства выберите сущность, состояние, цвет и яркость которой представляют этот источник на плане. Автоматический вариант сохраняет совместимость со старыми планами.","marker.light_entity.help.aria":"Подсказка: ведущая сущность света","marker.light_entity_auto":"Автоматически ({entity})","marker.light_entity_none":"нет управляемой сущности","marker.light_entity_missing":"Сохранённая сущность {entity} недоступна. Временно используется {fallback}; выбор восстановится, если сущность вернётся.","marker.glow_color_label":"Цвет и яркость свечения","marker.glow_mode.help":"Используйте данные источника, задайте только цвет или зафиксируйте цвет и яркость. Минимум — 1%; чтобы отключить источник, выберите «Никогда».","marker.glow_mode.help.aria":"Подсказка: цвет и яркость свечения","marker.glow_mode_auto":"Из источника","marker.glow_mode_color":"Задать цвет","marker.glow_mode_fixed":"Задать цвет и яркость","marker.glow_color":"Цвет свечения","marker.glow_brightness":"Яркость","marker.glow_disabled_never":"Настройки свечения недоступны: маркер явно исключён из источников света.","marker.glow_disabled_auto":"В автоматическом режиме у этого маркера не найден пространственный источник света.","marker.glow_disabled_no_entity":"Для пространственного источника нет активной управляемой сущности Home Assistant.","marker.glow_passive_hint":"У источника нет собственных данных Home Assistant. Задайте цвет и яркость вручную; радиус остаётся доступным.","marker.control_broken":"Сохранённый источник отсутствует или больше не отмечен как источник света","marker.control_missing_label":"Отсутствующий источник на плане","marker.control_passive":"пассивный источник","toast.marker_control_cycle":"Такая связь создаст циклическую цепочку управления светом.","toast.marker_binding_required":"Сначала выберите устройство Home Assistant, затем добавьте управляемый источник света.","confirm.unlock":"Открыть замок «{name}»?","toast.files_migrate_failed":"Не удалось перенести вложения к новой привязке, ссылки остались на старые файлы: {err}","space.pick_saved":"Уже загруженные","space.pick_saved_hint":"Планы, сохранённые на сервере, включая отцеплённые ранее","space.no_saved":"На сервере пока нет сохранённых планов.","space.loading":"Загрузка…","space.used_by":"используется: {list}","space.in_use":"План используется пространством — сначала отцепите его","btn.use":"Выбрать","confirm.delete_plan":"Удалить файл плана «{name}» с сервера? Действие необратимо.","toast.plans_list_failed":"Не удалось получить список планов: {err}","toast.plan_delete_failed":"Не удалось удалить план: {err}","marker.hide":"Скрыть","marker.hide_tip":"Устройство исчезнет с плана после сохранения, но продолжит участвовать в расчёте сигнала комнаты. Вернуть его можно через кнопку «Скрытые и деактивированные» в редакторе устройств.","marker.show":"Показать","marker.show_tip":"Устройство снова появится на плане после сохранения.","marker.hidden_ghost":"Устройство скрыто пользователем","marker.ha_disabled_device":"Устройство деактивировано в Home Assistant и скрыто с плана.","marker.ha_disabled_entity":"Сущность деактивирована в Home Assistant и скрыта с плана.","marker.ha_disabled_all_entities":"У устройства нет активных сущностей Home Assistant, поэтому оно скрыто с плана.","marker.ha_registry_limited":"Полный реестр Home Assistant недоступен этому пользователю. Неподтверждённый объект временно нельзя показывать или использовать.","marker.delete_tip":"Полностью удалить устройство с плана и из всех расчётов. Позже его можно добавить заново.","tap.run":"Запустить автоматизацию/скрипт/сцену","tap.cover":"Открыть/закрыть (шторы/жалюзи)","marker.run_target_label":"Что запускать","marker.run_search_ph":"Поиск: автоматизация, скрипт или сцена…","marker.run_target_gone":"Цель {id} не найдена — выберите заново","marker.tap_confirm":"Спрашивать подтверждение","marker.tap_confirm_tip":"Перед выполнением показать диалог подтверждения — защита от случайных нажатий.","marker.toggle_hint_single":"Цель: {name} ({id}).","marker.virtual_light_target":"Виртуальный свет: {name}.","marker.virtual_light_current":"Ручное состояние: {state} → {effect}.","marker.virtual_light_state_on":"включён","marker.virtual_light_state_off":"выключен","marker.toggle_hint_group":"Переключатся источники ({count}): {names}.","marker.toggle_hint_current":"Сейчас: {state} → {effect}.","marker.toggle_hint_group_current":"Сейчас включено {on} из {count} → {effect}.","marker.toggle_hint_skipped":"Не будут затронуты ({count}): {targets}.","marker.toggle_effect_turn_on":"включится","marker.toggle_effect_turn_off":"выключится","marker.toggle_effect_open":"откроется","marker.toggle_effect_close":"закроется","marker.toggle_effect_stop":"остановится","marker.toggle_effect_toggle":"состояние переключится","marker.toggle_skip_missing":"не найдено","marker.toggle_skip_ha_disabled":"деактивировано в HA","marker.toggle_skip_unavailable":"недоступно","marker.toggle_skip_unsupported":"переключение не поддерживается","marker.toggle_skip_secure":"заблокировано из соображений безопасности","marker.toggle_none_no_binding":"У устройства нет привязки, состояние переключать нечем. По нажатию ничего не произойдёт.","marker.toggle_none_no_actionable_entity":"У устройства нет состояния, которое можно переключить. По нажатию ничего не произойдёт.","marker.toggle_none_configured_targets_missing":"Настроенные цели сейчас недоступны. Собственная сущность устройства не будет подставлена вместо них.","marker.toggle_none_ha_disabled":"Цель деактивирована в Home Assistant и не может использоваться на плане.","marker.toggle_none_unavailable":"Цель сейчас недоступна. По нажатию ничего не произойдёт.","marker.toggle_none_unsupported":"Для этой сущности Home Assistant не предоставляет безопасного переключения. По нажатию ничего не произойдёт.","marker.toggle_none_secure":"Переключение замков, сигнализации и защитных ворот с плана заблокировано из соображений безопасности.","run.automation":"автоматизация","run.script":"скрипт","run.scene":"сцена","confirm.tap_run":"Запустить «{name}»?","confirm.tap_toggle":"Переключить «{name}»?","confirm.tap_cover":"Открыть/закрыть «{name}»?","toast.run_started":"Запущено: {name}","toast.run_target_missing":"Цель запуска не найдена — проверьте настройки устройства","toast.run_target_required":"Выберите автоматизацию, скрипт или сцену","toast.tap_target_changed":"Цель действия изменилась. Повторите попытку.","toast.virtual_light_toggle_failed":"Не удалось переключить виртуальный свет: {err}","toast.value_badge_source_required":"Выберите значение для бейджа","btn.run":"Выполнить","vac.section":"Робот-пылесос: живая позиция","vac.autocal":"Настроить автоматически","vac.live":"Живая позиция на плане","vac.trail":"Показывать путь робота","vac.cal_maps":"Откалиброваны карты: {maps}","vac.autocal_no_rooms":"Интеграция не отдаёт список комнат — откройте «Подогнать вручную»","vac.autocal_no_match":"Не совпали имена комнат (нужно ≥3 общих) — откройте «Подогнать вручную»","vac.autocal_done":"Готово: привязка по {rooms} комнатам. Запустите уборку и проверьте","vac.cal_need_pos":"Робот сейчас не отдаёт координаты — запустите уборку и поставьте на паузу","vac.cal_done":"Калибровка сохранена. Запустите уборку и проверьте","vac.cal_cancelled":"Калибровка отменена","vac.fit":"Подогнать вручную","vac.fit_hint":"Перетащите карту робота на место, растяните за уголки","vac.fit_rotate":"Повернуть 90°","vac.fit_mirror":"Отразить","vac.trail_never":"Не показывать никогда","vac.trail_cleaning":"Во время уборки","vac.trail_always":"Показывать всегда","gs.bg_group":"Фон сцены","gs.bg_color":"Цвет фона вокруг плана","gs.bg_default":"Как в теме","gs.bg_theme":"по умолчанию — из темы","gs.bg_mode":"Фон плана","gs.bg_static":"Статичный цвет","gs.bg_daynight":"Следует за Солнцем","gs.bg_daynight_hint":"Фон проходит рассвет, день, закат и ночь по данным sun.sun, а при их недоступности — по локальным часам. Компас для фона не нужен.","gs.sun_group":"Солнце","gs.sun_missing":"Сущность sun.sun не найдена — фон следует локальным часам, а оконные лучи недоступны.","gs.north":"Север на плане","gs.north_ph":"не задан","gs.north_hint":"Север нужен только для оконных лучей (шаг 1°, с Shift 15°); солнечный фон работает без него.","gs.north_clear":"Сбросить","gs.north_letter":"С","gs.sun_rays":"Солнце в окнах","gs.about_group":"О карточке","gs.about_version":"Houseplan Card v{v}","gs.about_github":"GitHub · документация и issues","gs.about_telegram":"Чат в Telegram","space.bg_color":"Цвет фона вокруг плана","space.bg_inherit":"Наследовать общий","space.bg_inherited":"наследуется из общих настроек","space.bg_mode":"Фон плана","space.north":"Север на плане (переопределение)","space.north_inherited":"наследуется: {v}","space.sun_rays":"Солнце в окнах","space.sun_inherit":"Наследовать общий","space.sun_on":"Вкл","space.sun_off":"Выкл","canvas.far_objects":"Объектов далеко от плана: {n}","canvas.show_far":"Показать","canvas.home_tip":"План там — нажмите, чтобы вписать","gs.grid_group":"Обслуживание планов","gs.grid_hint":"Обновляет модели данных, выравнивает элементы по сетке и объединяет лишние фрагменты стен. Перед записью будет показан точный отчёт.","gs.align_all":"Оптимизировать планы","gs.align_title":"Оптимизировать планы","gs.align_none":"Все планы уже используют актуальную и оптимальную модель данных.","gs.align_count":"Сдвинется элементов: {n} из {total}, максимум на {cm} см.","gs.align_where":"Наибольший сдвиг — в пространстве «{s}».","gs.align_turned":"Проёмов с исправлением угла: {n}.","gs.align_removed_drafts":"Схлопнувшиеся на сетке некорректные контуры удалены: {n}.","gs.optimize_changes":"Миграций модели: {m}; канонизировано планов: {c}; объединено отрезков реальных стен: {w}; виртуальных: {s}.","gs.optimize_glow_migration":"Старый Glow: пространств — {spaces} → без заливки данных + независимый Glow; комнат — {rooms} → наследуемая заливка + независимый Glow.","gs.align_warn":"Элементы, намеренно поставленные между узлами, будут сдвинуты. После операции доступна одна отмена — только до следующего изменения плана.","gs.align_run":"Оптимизировать","gs.align_done":"Планы оптимизированы: сдвинуто элементов — {n}, обслужено записей — {m}","gs.optimize_undo":"Отменить последнюю оптимизацию","gs.optimize_undone":"Последняя оптимизация отменена","decor.furniture":"Мебель","furn.title":"Библиотека мебели","furn.symbol":"Символ","furn.group_furniture":"Мебель","furn.group_appliance":"Техника","furn.group_sanitary":"Сантехника","furn.group_other":"Прочее","furn.width":"Ширина","furn.depth":"Глубина","furn.pick_hint":"Выберите символ и кликните по плану.","furn.place_hint":"Кликните по плану — предмет встанет к ближайшей стене. Shift — свободно.","furn.sym_sofa":"Диван","furn.sym_armchair":"Кресло","furn.sym_coffee_table":"Журнальный столик","furn.sym_table_dining":"Обеденный стол","furn.sym_table_round":"Круглый стол","furn.sym_chair":"Стул","furn.sym_desk":"Письменный стол","furn.sym_bed_double":"Двуспальная кровать","furn.sym_bed_single":"Односпальная кровать","furn.sym_nightstand":"Тумбочка","furn.sym_wardrobe":"Шкаф","furn.sym_bookshelf":"Стеллаж","furn.sym_fridge":"Холодильник","furn.sym_stove":"Плита","furn.sym_dishwasher":"Посудомоечная машина","furn.sym_washer":"Стиральная машина","furn.sym_dryer":"Сушильная машина","furn.sym_tv":"Телевизор","furn.sym_ac":"Кондиционер","furn.sym_water_heater":"Бойлер","furn.sym_toilet":"Унитаз","furn.sym_bathtub":"Ванна","furn.sym_shower":"Душ","furn.sym_sink":"Раковина","furn.sym_kitchen_sink":"Кухонная мойка","furn.sym_bidet":"Биде","furn.sym_stairs":"Лестница","furn.sym_fireplace":"Камин","furn.sym_plant":"Растение","furn.sym_rug":"Ковёр","common.yes":"Да","common.no":"Нет","vac.diag_source":"Источник","vac.diag_platform":"Интеграция","vac.diag_status":"Статус","vac.diag_position":"Позиция","vac.diag_rooms":"Комнаты","vac.diag_rooms_value":"{total} · совпало имён: {matched} · {readiness}","vac.autocal_ready":"автокалибровка доступна","vac.autocal_not_ready":"нужно 3 совпавших имени","vac.diag_path":"Путь интеграции","vac.diag_map":"ID карты","vac.source_none":"не выбран","vac.source_status_ok":"Готов","vac.source_status_missing":"Не найден","vac.source_status_disabled":"Деактивирован в Home Assistant","vac.source_status_unavailable":"Недоступен","vac.source_status_unverified":"Нельзя проверить с текущими правами","vac.source_status_unsupported":"Нет данных позиции","vac.source_status_none":"Нет источника","vac.source_banner_missing":"Сохранённый источник больше не существует. Он не заменён автоматически: выберите другой или восстановите его в Home Assistant.","vac.source_banner_disabled":"Сохранённый источник деактивирован в Home Assistant. Активируйте его там или выберите другой.","vac.source_banner_unverified":"Текущих прав Home Assistant недостаточно для проверки источника. Привязка сохранена и не будет заменена автоматически.","vac.choose_source":"Выбрать источник","vac.source_auto":"Автоматически","vac.source_auto_hint":"Использовать совместимую сущность этого устройства","vac.all_cameras":"Все камеры","vac.all_cameras_warn":"Камера может не отдавать данные робота. Выбирайте её только если это карта вашего пылесоса.","vac.all_cameras_empty":"Других сущностей камеры не найдено.","vac.platform_unknown":"интеграция неизвестна","vac.cap_position":"позиция","vac.cap_rooms_short":"комнаты","vac.cap_path":"путь","vac.cap_map":"ID карты","vac.cap_none":"данные робота не обнаружены","vac.xcme_hint":"Включите атрибуты Xiaomi Cloud Map Extractor:","vac.documentation":"Документация","vac.residual_title":"Проверьте автокалибровку","vac.residual_message":"Совпавшие комнаты расходятся максимум на {error}. Примените приблизительную калибровку, подгоните её вручную либо отмените без изменения сохранённых настроек.","vac.apply_proposal":"Применить","gs.backup_group":"Резервная копия и перенос","gs.backup_hint":"Скачайте переносимую JSON-копию или проверьте и импортируйте копию, созданную House Plan.","backup.export_open":"Экспорт","backup.import_open":"Импорт","backup.export_title":"Экспорт House Plan","backup.import_title":"Импорт House Plan","backup.export_hint":"Выберите, должна ли копия содержать всю конфигурацию House Plan или только текущее пространство.","backup.full":"Полная копия","backup.current_space":"Текущее пространство","backup.current_space_title":"Текущее пространство: {title}","backup.no_current_space":"Нет текущего пространства","backup.privacy_warning":"Архив сохраняет названия, идентификаторы Home Assistant и точные координаты. Внутренние планы и вложения указываются ссылками, но не вкладываются; текущие состояния и маршруты пылесосов не включаются.","backup.download":"Скачать JSON","backup.export_done":"Резервная копия скачана","backup.reading":"Проверяем резервную копию…","backup.revalidated":"После предпросмотра план изменился. Сводка обновлена — проверьте её и подтвердите ещё раз.","backup.error.unauthorized":"У вас нет прав на экспорт или импорт этого плана.","backup.error.not_ready":"House Plan ещё не готов. Повторите попытку через несколько секунд.","backup.error.too_large":"Размер резервной копии превышает 8 МиБ.","backup.error.invalid_json":"Выбранный файл не является корректным JSON.","backup.error.invalid_format":"Выбранный файл не является резервной копией House Plan.","backup.error.unsupported_export_version":"Эта версия формата резервной копии не поддерживается установленной версией.","backup.error.future_model":"Резервная копия создана в более новой версии модели данных House Plan.","backup.error.invalid_config":"Резервная копия содержит некорректную конфигурацию House Plan.","backup.error.invalid_layout":"Резервная копия содержит некорректные позиции объектов.","backup.error.invalid_content":"Резервная копия содержит некорректные или несогласованные ссылки на файлы.","backup.error.space_not_found":"Выбранное пространство больше не существует.","backup.error.capacity_exceeded":"Добавление этой копии превысит ограничения размера плана.","backup.error.preview_expired":"Время предпросмотра истекло. Выберите файл резервной копии заново.","backup.error.preview_owner_mismatch":"Этот предпросмотр принадлежит другому пользователю Home Assistant.","backup.error.conflict":"После предпросмотра план изменился. Проверьте обновлённую сводку.","backup.error.content_confirmation_required":"Подтвердите отсоединение недоступных локальных файлов.","backup.error.commit_failed":"Не удалось безопасно применить копию. Предыдущий план восстановлен или ожидает восстановления.","backup.error.missing_plan":"После предпросмотра исчез связанный файл плана. Проверьте копию ещё раз.","backup.error.missing_content":"После предпросмотра исчез связанный локальный файл. Проверьте копию ещё раз.","backup.error.marker_control_missing":"В резервной копии отсутствует связанный источник света на плане.","backup.error.marker_control_not_light":"Связанная цель больше не отмечена как источник света.","backup.error.marker_control_self":"Источник света не может управлять самим собой.","backup.error.marker_control_cycle":"Резервная копия содержит циклическую цепочку управления светом.","backup.error.duplicate_marker_control":"Резервная копия содержит повторяющуюся связь с источником света.","backup.error.no_backup":"Нет снимка импорта или оптимизации, который можно восстановить.","backup.same_source":"Создано на этом экземпляре Home Assistant","backup.foreign_source":"Создано на другом экземпляре Home Assistant","backup.created":"Создано: {value}","backup.versions":"Карточка {card}; интеграция {integration}; модель данных {model}","backup.count_spaces":"Пространств: {n}","backup.count_rooms":"Комнат: {n}","backup.count_walls":"Стен: {n}","backup.count_openings":"Проёмов: {n}","backup.count_decor":"Объектов декора: {n}","backup.count_markers":"Устройств: {n}","backup.count_layout":"Позиций: {n}","backup.bindings":"Привязки — устройства: {device}, сущности: {entity}, виртуальные: {virtual}; позиций без пространства: {legacy}","backup.binding_status":"Статус на целевом экземпляре — активных: {active}, деактивированных: {disabled}, отсутствующих: {missing}","backup.missing_areas":"На целевом экземпляре отсутствуют зоны: {areas}","backup.dropped_marker_links":"Связи с источниками света вне переносимого пространства пропущены: {n}.","backup.replace_warning":"Текущая конфигурация и расположение будут заменены. Загруженные файлы не удаляются. Одну отмену можно выполнить до следующего изменения плана.","backup.foreign_bookkeeping":"Служебные списки известных и новых устройств другого экземпляра импортированы не будут.","backup.final_name":"Название нового пространства","backup.target_settings":"Глобальные настройки целевого экземпляра не изменятся.","backup.duplicates":"Уже существующих привязок: {n}","backup.skip":"Пропустить повторяющиеся устройства","backup.virtual_copy":"Добавить безопасные статичные виртуальные копии","backup.content":"Связанное содержимое","backup.content_available":"доступно локально","backup.content_external":"внешняя ссылка","backup.content_detach_required":"будет отвязано","backup.confirm_detach":"Я понимаю, что недоступные внутренние планы и вложения будут отвязаны от импортируемой конфигурации.","backup.replace":"Заменить","backup.add":"Добавить пространство","backup.space_done":"Пространство импортировано: комнат — {rooms}, устройств — {markers}","backup.full_done":"Копия восстановлена: пространств — {spaces}, комнат — {rooms}, устройств — {markers}","backup.undo_import":"Отменить последний полный импорт","backup.import_undone":"Полный импорт отменён"}};function Uo(t,e){if(e&&e in Wo)return e;return(t?.locale?.language||t?.language||"en").toLowerCase().startsWith("ru")?"ru":"en"}function Go(t,e,i){return Ki(Wo[t][e]??jo[e]??e,i)}function Vo(t,e){const i=Wo[t][e]??Wo.en[e];return"string"==typeof i&&i.trim().length>0}class Ko extends ht{constructor(){super(...arguments),this.deviceName="",this._metadataConnection=null,this._demoUntil=0,this._demoTimer=0,this._demoGeneration=1,this._demoKind=null,this._reducedMotion=!1,this._onMotionChange=t=>{this._reducedMotion=t.matches,this.requestUpdate()},this._startShortDemo=()=>{const t=this.presentation;t&&"icon_ripple"===t.display&&"none"===t.pulse.kind&&(this._demoGeneration++,this._demoKind="short",this._demoUntil=Date.now()+3300,window.clearTimeout(this._demoTimer),this._demoTimer=window.setTimeout(()=>{this._demoUntil=0,this._demoTimer=0,this._demoKind=null,this.requestUpdate()},3330),this.requestUpdate())},this._toggleContinuousDemo=()=>{const t=this.presentation;t&&"icon_ripple"===t.display&&"none"===t.pulse.kind&&("continuous"===this._demoKind?this._clearDemo():(this._clearDemo(),this._demoGeneration++,this._demoKind="continuous",this._demoUntil=Number.POSITIVE_INFINITY),this.requestUpdate())}}connectedCallback(){super.connectedCallback(),this._motionMedia=window.matchMedia?.("(prefers-reduced-motion: reduce)"),this._reducedMotion=!!this._motionMedia?.matches,this._motionMedia?.addEventListener?.("change",this._onMotionChange),this._ensureMetadata()}disconnectedCallback(){this._metadataRelease?.(),this._metadataRelease=void 0,this._metadataConnection=null,this._clearDemo(),this._motionMedia?.removeEventListener?.("change",this._onMotionChange),this._motionMedia=void 0,super.disconnectedCallback()}willUpdate(t){t.has("hass")&&this._ensureMetadata();const e=this.presentation,i=t.get("presentation");t.has("presentation")&&i?.binding!==e?.binding&&this._clearDemo(),e&&this._demoKind&&("icon_ripple"!==e.display||"none"!==e.pulse.kind)&&this._clearDemo()}_ensureMetadata(){const t=this.hass?.connection||null;t&&t!==this._metadataConnection&&(this._metadataRelease?.(),this._metadataConnection=t,this._metadataRelease=Oo(this.hass,()=>this.requestUpdate()))}_clearDemo(){window.clearTimeout(this._demoTimer),this._demoTimer=0,this._demoUntil=0,this._demoKind=null}get _lang(){return Uo(this.hass)}_t(t,e){return Go(this._lang,t,e)}_providerText(t){return"houseplan"===t.domain?this._t("marker.preview.virtual_provider"):t.label.toLowerCase()===t.domain.toLowerCase()?t.domain:`${t.label} (${t.domain})`}_providers(){return this.presentation&&this.registry?function(t,e,i,s=No(t)){if("virtual"===e)return[{domain:"houseplan",label:"House Plan",confidence:"registry-owner"}];const n=e.indexOf(":");if(n<1)return[];const o=e.slice(0,n),r=e.slice(n+1);if("entity"===o){const e=Bo(t,r,i,s);return e?[e]:[]}if("device"!==o)return[];const a=i.devices?.[r]||t?.devices?.[r];if(!a)return[];const l=[a.config_entry_id,...Array.isArray(a.config_entries)?a.config_entries:[]].filter(t=>"string"==typeof t&&!!t).map(e=>{const i=s.configEntries[e];return Ho(t,i?.domain,"registry-owner",s,e)});if(l.some(Boolean))return qo(l);const c=[];for(const[e,n]of Object.entries(i.entities||{}))n?.device_id===r&&null==n?.disabled_by&&c.push(Bo(t,e,i,s));return c.some(Boolean)?qo(c):qo((Array.isArray(a.identifiers)?a.identifiers:[]).map(e=>{const i=Array.isArray(e)?e[0]:null;return Ho(t,i,"identifier-fallback",s)}))}(this.hass,this.presentation.binding,this.registry,No(this.hass)):[]}_providerSummary(t){if(!t.length)return this._t("marker.preview.unknown_provider");const e=t.slice(0,2).map(t=>this._providerText(t)),i=t.length-e.length;return e.join(", ")+(i>0?` · ${this._t("marker.preview.more_sources",{n:i})}`:"")}_sourceSummary(t){const e=t.visualSources;return e.length?1===e.length?`${e[0].name} · ${e[0].eid} · ${this._sourceProvider(e[0].eid)}`:2===e.length?e.map(t=>t.name).join(" · "):this._t("marker.preview.multiple_sources",{n:e.length}):this._t("marker.preview.no_source")}_stateSummary(t){if(!t.visualSources.length)return this._t("marker.preview.no_state");if(1===t.visualSources.length)return t.visualSources[0].stateText||this._t("marker.preview.no_state");const e=[...new Set(t.visualSources.map(t=>t.stateText).filter(Boolean))];return 1===e.length?e[0]:this._t("marker.preview.mixed_states")}_reason(t){return this._t(`marker.preview.reason.${t}`)}_sourceProvider(t){if(!this.registry)return this._t("marker.preview.unknown_provider");const e=Bo(this.hass,t,this.registry,No(this.hass));return e?this._providerText(e):this._t("marker.preview.unknown_provider")}render(){const t=this.presentation;if(!t)return G;const e=!!this._demoKind&&this._demoUntil>Date.now()&&"icon_ripple"===t.display&&"none"===t.pulse.kind,i=e?(()=>{const e=function(t,e,i,s,n=null){return{kind:e,reason:"short"===e?"event":"running",generation:Math.max(1,Math.trunc(i)),expiresAt:"short"===e?n:null,color:s?null:t.color,diameterScale:s?1:t.diameterScale,animated:!s,reducedMotionIndicator:s?"dot":"none"}}(t.pulse,this._demoKind,this._demoGeneration,this._reducedMotion,"short"===this._demoKind?this._demoUntil:null);return{...t,pulse:e,classes:[...t.classes.filter(t=>!t.startsWith("pulse-")),`pulse-${e.kind}`,...e.generation%2==0?["pulse-gen2"]:[]]}})():t,s=this._providers(),n="none"!==t.pulse.kind,o=i.scale*(i.pulse.animated?i.pulse.diameterScale:1);let r=-o/2,a=o/2,l=-o/2,c=o/2;if(i.valueBadge){const t=i.scale*Math.min(4,Math.max(.9,.29*i.valueBadge.text.length+.28)),e=.7*i.scale,s=.6*i.scale;"right"===i.valueBadge.position&&(a=Math.max(a,s+t)),"left"===i.valueBadge.position&&(r=Math.min(r,-s-t)),"top"===i.valueBadge.position&&(l=Math.min(l,-s-e)),"bottom"===i.valueBadge.position&&(c=Math.max(c,s+e))}if(null!=i.lqiText){const t=i.scale*("bottom"===i.valueBadge?.position?1.55:.95);c=Math.max(c,t)}const h=Math.min(1,2.35/Math.max(1,a-r),2.35/Math.max(1,c-l)),d=Math.round(100*h),u=[`left:calc(50% - ${54*((r+a)/2)*h}px)`,`top:calc(50% - ${54*((l+c)/2)*h}px)`,h<1?`transform:scale(${h})`:""].filter(Boolean).join(";"),p=["dev",...i.classes,"virtual"===i.binding?"virtual":"",i.haDisabled?"ghost ha-disabled":"",null!=i.valueText?"valonly":""].filter(Boolean).join(" "),_=Ro(i).join(";"),m=e?this._t("continuous"===this._demoKind?"marker.preview.demo_continuous_notice":"marker.preview.demo_short_notice"):this._reason(t.explanation.reason),g=[this.deviceName,this._providerSummary(s),this._stateSummary(t),m,t.valueFullText||"",fo(t.valueBadge)].filter(Boolean).join(". ");return j`
+ `;var o}function Io(t,e,i){return{kind:"none",reason:"none",generation:t,expiresAt:null,color:e,diameterScale:i,animated:!1,reducedMotionIndicator:"none"}}const Eo=new WeakMap;function zo(t){const e=function(t){const e=t?.connection||t;return!e||"object"!=typeof e&&"function"!=typeof e?null:e}(t);if(!e)return null;let i=Eo.get(e);return i||(i={revision:0,loaded:!1,configEntries:{},manifests:{},listeners:new Set,refs:0},Eo.set(e,i)),i}function Fo(t){t.revision++;for(const e of[...t.listeners])try{e()}catch{}}function Oo(t,e){const i=zo(t);if(!i)return()=>{};i.refs++,i.listeners.add(e),async function(t,e){e.loading||"function"!=typeof t?.callWS?e.loading:e.loaded&&e.loadedAt&&Date.now()-e.loadedAt<3e5||(e.loading=(async()=>{const[i,s]=await Promise.allSettled([t.callWS({type:"config_entries/get"}),t.callWS({type:"manifest/list"})]);"fulfilled"===i.status&&Array.isArray(i.value)&&(e.configEntries=Object.fromEntries(i.value.filter(t=>"string"==typeof t?.entry_id).map(t=>[t.entry_id,t]))),"fulfilled"===s.status&&Array.isArray(s.value)&&(e.manifests=Object.fromEntries(s.value.filter(t=>"string"==typeof t?.domain).map(t=>[t.domain,t]))),e.loaded=!0,e.loadedAt=Date.now(),e.loading=void 0,Fo(e)})().catch(()=>{e.loaded=!0,e.loadedAt=Date.now(),e.loading=void 0,Fo(e)}),e.loading)}(t,i),async function(t,e){if(e.unsubscribe||e.subscribing)return e.subscribing;const i=t?.connection?.subscribeMessage;"function"==typeof i&&(e.subscribing=(async()=>{try{e.unsubscribe=await i.call(t.connection,t=>{const i=Array.isArray(t)?t:Array.isArray(t?.entries)?t.entries:t?[t]:[];let s=!1;const n={...e.configEntries};for(const t of i){const e=t?.entry||t?.data?.entry||("string"==typeof t?.entry_id?t:null),i=e?.entry_id;if("string"!=typeof i)continue;const o=t?.type||t?.action||t?.data?.action;"removed"===o||"remove"===o?delete n[i]:n[i]=e,s=!0}s&&(e.configEntries=n,Fo(e))},{type:"config_entries/subscribe"})}catch{}finally{0===e.refs&&(e.unsubscribe?.(),e.unsubscribe=void 0),e.subscribing=void 0}})(),e.subscribing)}(t,i);let s=!1;return()=>{s||(s=!0,i.listeners.delete(e),i.refs=Math.max(0,i.refs-1),i.refs||(i.unsubscribe?.(),i.unsubscribe=void 0))}}function No(t){const e=zo(t);return e?{revision:e.revision,loaded:e.loaded,configEntries:e.configEntries,manifests:e.manifests}:{revision:0,loaded:!1,configEntries:{},manifests:{}}}function Lo(t,e,i=No(t),s){const n=i.manifests[e];return function(t,e){if("function"!=typeof t?.localize)return"";try{const i=`component.${e}.title`,s=t.localize(i);return"string"==typeof s&&s!==i?s:""}catch{return""}}(t,e)||String(n?.name||"")||String(s||"")||e.replace(/_/g," ")}function Ho(t,e,i,s,n){const o=String(e||"").trim();if(!o)return null;const r=n?s.configEntries[n]:null;return{domain:o,label:Lo(t,o,s,r?.title),configEntryId:n,configEntryTitle:r?.title,confidence:i}}function qo(t){const e={"registry-owner":0,"entity-platform":1,"identifier-fallback":2},i=new Set;return t.filter(t=>!!t).sort((t,i)=>e[t.confidence]-e[i.confidence]||t.label.localeCompare(i.label)||t.domain.localeCompare(i.domain)).filter(t=>{const e=`${t.domain}\n${t.configEntryId||""}`;return!i.has(e)&&(i.add(e),!0)})}function Bo(t,e,i,s=No(t)){const n=i.entities?.[e]||t?.entities?.[e];if(!n)return null;const o=n.config_entry_id||void 0,r=o?s.configEntries[o]?.domain:"";return Ho(t,n.platform||r,"entity-platform",s,o)}var jo={"editor.context_actions":"Actions: {object}","editor.tool_options":"Tool options: {tool}","editor.palette":"Palette: {tool}","editor.open_group":"Tool group: {group}","editor.group_active":"{group} — active: {item}","editor.disabled_action":"{action} is unavailable: {reason}","btn.properties":"Properties","btn.keep_as_walls":"Keep as closed walls","title.markup_partition":"Partition: draw one independent physical wall","title.markup_select":"Select and edit walls, columns and saved outlines","title.markup_column":"Column: click a grid point to place a square column","markup.partition":"Partition","markup.column":"Column","markup.hint_partition":"two clicks draw one partition · Shift locks to 45°","markup.hint_column":"click a grid point to place a square column","history.draft_segment":"Add room-draft segment","history.draft_merge":"Join unfinished room outlines","history.draft_segment_delete":"Remove room-draft segment","history.partition_add":"Add partition","history.column_add":"Add column","history.physical_edit":"Change physical object","history.physical_delete":"Delete physical object","history.physical_move":"Move physical object","history.contour_to_partitions":"Convert contour to closed walls","toast.column_duplicate":"A column with the same centre and outer size already exists","confirm.delete_draft":"Delete the whole unfinished room contour?","confirm.delete_draft_segment":"Delete this draft segment? The remaining contour may split in two.","physical.partition_properties":"Partition properties","physical.column_properties":"Column properties","physical.draft_properties":"Draft segment properties","physical.shape":"Shape","physical.square":"Square","physical.circle":"Circle","physical.diameter":"Diameter","physical.side":"Side","physical.rotation":"Rotation angle","physical.length":"Length","physical.allowed_range":"Allowed: 1–{max} {unit}","physical.delete_segment":"Delete segment","physical.delete_draft":"Delete entire outline","physical.partition_size_title":"Thickness of the partition created by the second click (1–100 cm).","physical.column_size_title":"Outer side of the square column placed by the click (1–150 cm).","card.title":"House plan","count.devices":"{n} dev.","empty.no_spaces":"No spaces yet.","empty.add_first":"Add the first space and upload a floor plan.","empty.install":'Install the House Plan integration and add it in "Devices & services".',"btn.add_space":"Add space","btn.cancel":"Cancel","btn.save":"Save","btn.close":"Close","btn.delete":"Delete","btn.edit":"Edit","btn.open_in_ha":"Open in HA","btn.reset":"Reset","btn.attach":"Attach…","btn.upload":"Upload…","btn.replace":"Replace…","title.zoom_in":"Zoom in","title.zoom_out":"Zoom out","title.zoom_fit":"Fit all","title.add_device":"Add a device to the plan","title.show_all":"Show hidden and disabled devices as service markers (this tab only)","title.markup":"Room markup: grid, lines, outlines","title.configure_space":"Configure space","title.add_space":"Add space","title.markup_add":"Room outline: connect grid dots until the room closes; Shift locks the direction in 45° steps","title.markup_merge":"Merge: click one room, then the neighbour it shares a wall with","title.markup_split":"Split a room: click the room, then two points on its walls","title.markup_delroom":"Delete room: click inside a room and confirm","title.markup_boundary":"Boundary: click two points on a solid shared wall to make that stretch virtual; click a dashed stretch to restore it","title.need_plan":"Upload a floor-plan image","markup.add":"Room outline","markup.merge":"Merge","markup.split":"Split","markup.resize":"Resize","title.markup_resize":"Resize rooms: drag a wall handle; click a room for a corner scale frame","markup.hint_resize":"drag a wall handle · click a room — corner frame · Esc cancels a drag · Ctrl+Z — undo a step","markup.opening":"Opening","markup.boundary":"Boundary","markup.delete_room":"Delete room","history.undo":"Undo","history.redo":"Redo","history.undo_named":"Undo: {name} (Ctrl+Z)","history.redo_named":"Redo: {name} (Ctrl+Shift+Z / Ctrl+Y)","history.undo_empty":"Nothing to undo","history.redo_empty":"Nothing to redo","history.undone":"Undone: {name}","history.redone":"Redone: {name}","history.add_room":"Create room","history.split_room":"Split room","history.merge_rooms":"Merge rooms","history.resize_room":"Resize room","history.open_boundary":"Open boundary","history.close_boundary":"Restore wall","history.wall_thickness":"Change wall thickness","history.add_opening":"Add door or window opening","history.edit_opening":"Edit opening","history.move_opening":"Move opening","history.delete_opening":"Delete opening","history.delete_room":"Delete room","history.decor_add":"Add decor object","history.decor_edit":"Edit decor object","history.decor_move":"Move decor object","history.decor_transform":"Transform decor object","history.decor_delete":"Delete decor object","history.backdrop_transform":"Transform plan backdrop","title.markup_opening":"Openings: click a wall to place, click an opening to edit","opening.new":"New opening","opening.edit":"Opening","opening.door":"Door","opening.window":"Window","opening.gate":"Gate","opening.type_label":"Type","opening.length_label":"Length, cm","opening.contact_label":"Open/close sensor","opening.lock_label":"Lock","opening.none":"— none —","opening.invert":"Invert open/closed","opening.flip_h":"Hinge on the other jamb","opening.flip_v":"Opens to the other side","opening.open":"Open","opening.closed":"Closed","opening.locked":"Locked","opening.unlocked":"Unlocked","opening.state_unknown":"unavailable","opening.no_entities":"No sensors bound — a static symbol on the plan.","toast.opening_no_wall":"Click next to a room wall — openings sit on walls","markup.delete":"Delete","markup.hint_points":"points: {n} · Shift — 45° steps · Esc/Ctrl+Z — undo a dot · first point or Ctrl/Cmd+click — close","markup.hint_start":"click a grid dot to start the outline","tip.lqi":"average zigbee signal:","tip.area":"area: {value}","info.device_header":"Device on the plan","info.model":"Model","info.state":"State","info.link":"Link","info.manuals":"Manuals","info.none":"No additional information","marker.new_device":"New device","marker.name_label":"Name (shown on the plan)","marker.name_ph":"Name","marker.binding_label":"Bind to an HA device","marker.binding_disabled":"disabled in Home Assistant","marker.virtual_option":"Virtual device (no binding)","marker.search_ph":"Search device / group…","marker.nothing_found":"nothing found","marker.room_label":"Room","marker.room_override":" (override placement)","marker.room_choose":"— select a room —","marker.room_auto":"— by device area (auto) —","marker.icon_label":"Icon","marker.icon_ph":"mdi:… (empty = auto)","marker.display_label":"Display","display.badge":"Icon + state","display.icon_ripple":"Icon + state and activity","display.static_icon":"Always static icon","marker.display_hint":"Icon + state changes the plate for work, open, alarm and unavailable states. Icon + state and activity additionally shows a short pulse for events and a continuous pulse for ongoing work, movement or presence. Value + state replaces the icon with one unambiguous HA value. A static icon never reacts to device states.","marker.display_hint_badge":"The icon and dynamic plate show device state without the ordinary activity pulse. Red alarms remain visible.","marker.display_hint_icon":"The icon and dynamic plate show device state without the ordinary activity pulse. Red alarms remain visible.","marker.display_hint_icon_ripple":"The icon, dynamic plate and pulse show a short pulse for events and a continuous pulse for work, motion or presence. Red alarms have separate priority.","marker.display_hint_value":"One unambiguous Home Assistant value replaces the icon while the plate continues to show state. Red alarms remain visible.","marker.display_hint_static_icon":"The black plate and icon always stay the same. State, activity, unavailability and alarms do not change the face.","marker.static_alarm_warning":"This device can report alarms. Static display hides the marker's visual alarm indication.","marker.preview.title":"Display preview","marker.preview.actual":"Now","marker.preview.example":"Example","marker.preview.integration":"Provided by","marker.preview.source":"Display source","marker.preview.current_state":"Current state","marker.preview.result":"On the plan","marker.preview.details":"Source details","marker.preview.select_source":"Choose a Home Assistant device or entity to see its actual display.","marker.preview.unknown_provider":"Unknown integration","marker.preview.virtual_provider":"House Plan · virtual device","marker.preview.no_source":"No active source","marker.preview.no_state":"No current state","marker.preview.mixed_states":"Several different states","marker.preview.multiple_sources":"{n} sources","marker.preview.more_sources":"+{n} more","marker.preview.scaled":"The preview is scaled to {n}% to fit. The saved size is unchanged.","marker.preview.demo_activity":"Show activity example","marker.preview.demo_notice":"Temporary activity example; the actual device state is unchanged.","marker.preview.demo_short":"Show short activity","marker.preview.demo_continuous":"Show continuous activity","marker.preview.stop_continuous":"Stop continuous activity","marker.preview.demo_short_notice":"Short activity example; the actual device state is unchanged.","marker.preview.demo_continuous_notice":"Continuous activity example; the actual device state is unchanged.","marker.preview.demo_already_visible":"The current state already shows a real activity or alarm effect.","marker.preview.reduced_motion":"System reduced-motion is enabled, so ordinary activity is shown as a dot.","marker.preview.reason.neutral":"Neutral dark plate","marker.preview.reason.working":"Yellow plate: the device is working now","marker.preview.reason.working_activity":"Yellow plate and activity effect: the device is working now","marker.preview.reason.open":"Orange plate: physically open or unlocked","marker.preview.reason.cover_icon_state":"Neutral plate; the cover entity controls the displayed state and icon","marker.preview.reason.presence":"Activity effect while presence is detected","marker.preview.reason.event":"Short activity effect after a detected event","marker.preview.reason.transition":"Activity effect while the device is moving or changing state","marker.preview.reason.media_neutral":"Media devices stay on a neutral dark plate while available","marker.preview.reason.unavailable":"Subdued neutral plate: unavailable or off","marker.preview.reason.alarm":"Red alarm plate; alarm indication is always shown","marker.preview.reason.live_states_disabled":"Live-state styling is disabled for this card","marker.preview.reason.value_no_state":"No usable state value; the icon is shown instead","marker.preview.reason.value_ambiguous_sources":"Several possible value sources; the icon is shown instead","marker.preview.reason.value_non_scalar":"The source did not return a simple value; the icon is shown instead","marker.preview.reason.value_virtual":"A virtual device has no Home Assistant value; the icon is shown instead","marker.preview.reason.vacuum_live_plan_only":"Live vacuum position and trail are available only on the full plan","marker.preview.reason.hidden_design_preview":"The device is hidden on the plan; this design preview remains visible","marker.preview.reason.composite_power_source":"State comes from the device Power entity; auxiliary switches are ignored","marker.preview.reason.activity_display_disabled":"The selected display mode does not show ordinary activity effects","marker.preview.reason.ha_disabled":"The Home Assistant binding is disabled and will be hidden on the plan","marker.preview.reason.orphaned":"The saved Home Assistant binding can no longer be found","marker.preview.reason.static_icon":"Static mode: device state does not change the icon","marker.activity_color":"Activity pulse color","marker.ripple_size":"Activity pulse size","marker.activity_alarm_note":"Color and size affect ordinary activity only and do not change red alarms.","marker.pulse_a11y_alarm":"Alarm","marker.pulse_a11y_event":"Recent event","marker.pulse_a11y_presence":"Presence detected","marker.pulse_a11y_transition":"Changing state","marker.pulse_a11y_running":"Working","marker.size_label":"Icon size / rotation","marker.angle_label":"Rotate","marker.model_label":"Model","marker.model_ph":"e.g. Aqara T&H","marker.link_label":"Link","marker.desc_label":"Description","marker.desc_ph":"Notes, specs…","marker.manuals_label":"Manuals (PDF etc.)","marker.sub_device":"device","marker.sub_z2m_group":" · Z2M group","marker.sub_group":"group","marker.sub_helper":"helper","space.new":"New space","space.header":"Space","space.title_label":"Title","space.title_ph":"e.g. Garage","space.plan_label":"Floor plan (background)","space.no_plan":"no plan image","space.plan_alt":"plan","room.new":"New room","room.name_label":"Display name","room.name_ph":"e.g. Terrace","room.area_label":"Home Assistant area (unassigned)","room.no_area_option":"— no area —","room.default_name":"Room","device.unnamed":"unnamed","device.light_group":"light group","device.fallback":"device","device.virtual":"virtual device","confirm.delete_room":'Delete room "{name}"?',"confirm.merge_rooms":'Merge rooms "{a}" and "{b}"?',"confirm.remove_marker":'Delete "{name}" from the plan? The device will disappear completely and stop contributing to plan data. You can add it again later.',"confirm.erase_decor":"Erase the {kind} object? You can undo this action from the editor history.","confirm.delete_space":'Delete space "{title}" with all its rooms and markup?',"toast.pos_save_failed":"Failed to save position: {err}","toast.no_entity":"The device has no suitable entity","toast.ha_disabled_action":"A Home Assistant object that is disabled cannot be used on the plan.","toast.ha_disabled_show_device":"A device disabled in Home Assistant cannot be shown on the plan. Enable it in Home Assistant first.","toast.ha_disabled_show_entity":"An entity disabled in Home Assistant cannot be shown on the plan. Enable it in Home Assistant first.","toast.ha_disabled_add":"A disabled Home Assistant object cannot be added to the plan. Enable it in Home Assistant first.","toast.ha_binding_unverified":"The object status could not be verified through the Home Assistant registry. Display and actions are temporarily unavailable.","toast.markup_needs_server":"Markup is available after the config is moved to the server","toast.conflict":"Config was changed in another window — data refreshed, repeat your last action","toast.cfg_save_failed":"Failed to save config: {err}","toast.room_overlap":"The outline overlaps room “{name}” — rooms must not overlap","toast.contour_min_edges":"Draw at least two edges before closing the outline","toast.contour_cannot_close":"The outline cannot close because it is degenerate or intersects itself","toast.merge_not_adjacent":"Only rooms that share a wall can be merged","toast.rooms_merged":"Rooms merged into “{name}”","toast.split_pick_wall":"Start the cut on the room’s wall","toast.split_bad_cut":"The cut must run wall to wall inside the room, without crossing walls or itself","merge.header":"Merge rooms","merge.hint":"The merged room keeps one name and one area. The other area is released — its devices leave the plan until another room claims it.","merge.keep":"Keep","merge.no_area":"no area","room.split_header":"New room from the split","toast.room_saved":"Room saved ({n}). Devices added: {added}. Outline the next one or exit markup.","toast.room_saved_no_area":"Room saved ({n}, no area). Outline the next one or exit markup.","toast.marker_needs_server":"Device editing is available after the config is moved to the server","toast.virtual_name_required":"Enter a name for the virtual device","toast.marker_saved":"Device saved","toast.marker_removed":"Device deleted from the plan","toast.integration_missing":"The House Plan integration is not installed — management unavailable","toast.plan_formats":"Supported formats: SVG, PNG, JPG, WebP","toast.plan_required":"Upload a floor plan — it is required","toast.space_added_onboard":"Space added. Outline the rooms: click grid dots and close the contour.","toast.space_added":"Space added","toast.space_saved":"Space saved","toast.space_deleted":"Space deleted","toast.delete_failed":"Delete failed: {err}","toast.error":"Error: {err}","toast.file_failed":'File "{name}" was not uploaded: {err}',"toast.files_attached":"Files attached: {n}","err.unknown":"unknown error","err.code":"code {code}","err.too_large":"file larger than {mb} MB","err.bad_ext":"unsupported type (PDF/image expected)","err.unauthorized":"administrator rights required","editor.title":"Title","editor.default_floor":"Default space","editor.icon_size":"Icon size, % of plan width","editor.show_temperature":"Show temperature","editor.live_states":"Live states (on/off, open…)","editor.show_signal":"Show zigbee signal (LQI)","editor.language":"Interface language","editor.lang_auto":"Auto (HA profile)","editor.lang_en":"English","editor.lang_ru":"Русский","title.icon_rules":"Icon rules: which MDI icon devices get by name","rules.title":"Icon rules","rules.hint":"Rules are checked top-down against “device name + model” (case-insensitive regex); the first match wins. When nothing matches, the entity device class decides, then the generic chip icon.","rules.pattern_ph":"regex, e.g. plug|socket","rules.icon_ph":"mdi:power-socket-de","rules.add":"Add rule","rules.reset":"Reset to defaults","rules.test_ph":"Try a device name…","rules.invalid":"invalid regex","rules.saved":"Icon rules saved","btn.up":"Up","btn.down":"Down","tap.info":"Device card","tap.more_info":"HA more-info dialog","tap.toggle":"Toggle state","marker.tap_label":"Tap action for this device","tap.toggle_note":"The resolved state is shown below. Secure devices are never toggled from the plan.","import.title":"Create spaces from HA floors","import.hint":"Your Home Assistant already knows these floors. Pick the ones to turn into plan spaces — you will upload a floor-plan image for each one next. Rooms are then outlined by hand on the plan.","import.start":"Create {n} space(s)","import.manual":"Start from scratch","import.progress":"Floor {i} of {n}","import.done":"Spaces created. Outline the rooms: click grid dots and close the contour.","btn.skip":"Skip","space.scale_label":"Scale (grid cell size)","space.scale_unit":"cm per cell","space.display_section":"Display","space.show_borders":"Always show room borders","space.show_names":"Show room names (drag to move)","space.room_color":"Border & name color","space.opacity":"Opacity","space.fill_label":"Room fill","fill.none":"None","fill.lqi":"Zigbee signal","fill.light":"Lights","fill.custom":"Custom color","space.custom_fill":"Fill color","space.source_file":"I have a floor-plan image","space.source_draw":"No image — I'll outline rooms by hand","space.orientation":"Canvas","orient.landscape":"Landscape","orient.portrait":"Portrait","orient.square":"Square","fill.temp":"Temperature","space.temp_min":"Comfort from","space.temp_max":"to","tip.temp_avg":"average temperature:","space_card.button":"Open the space plan","space_card.not_found":"Space “{id}” not found","space_card.loading":"Loading…","continuity.restore_plan":"Restoring floor plan…","continuity.restore_connection":"Restoring device connection…","continuity.retry":"Try again","editor.space":"Space","editor.show_button":"Show button","editor.button_label":"Button label","editor.button_target":"Target dashboard path","marker.sub_entity":"entity","title.general_settings":"General settings","gs.title":"General settings","gs.hint":"Fill colors apply to every space; each color has its own opacity. Which fill mode a space uses is set in that space's dialog.","gs.light_group":"Fill: lights","gs.light_on":"Lights on","gs.light_off":"All lights off","gs.temp_group":"Fill: temperature","gs.temp_cold":"Cold","gs.temp_ok":"Comfortable","gs.temp_hot":"Hot","gs.lqi_group":"Fill: zigbee signal","gs.lqi_low":"Weak signal","gs.lqi_high":"Strong signal","gs.reset":"Reset to defaults","gs.saved":"General settings saved","space.show_lqi":"Show zigbee signal (LQI) next to devices","space.hide_decor":"Hide the decorative layer","space.hide_decor_tip":"Lines, shapes, labels and furniture stay where they are — visible in the backdrop editor, not on the plan.","space.hide_openings":"Hide openings","space.hide_openings_tip":"Door, window and gate symbols are not drawn, but the openings keep working: light passes through, the sun comes in at a window, contact sensors still open them. The plan editor always shows them.","gs.light_none":"No light sources","mode.plan":"Plan editor","mode.devices":"Device editor","display.value":"Value + state","marker.subarea":"no area, manual","device.new":"New device — open its editor to dismiss","opening.unlock_action":"Unlock","opening.lock_action":"Lock","opening.lock_pending":"Working…","title.close_editor":"Close editor (back to view)","devbar.add":"Add","devbar.show_all":"Hidden and disabled","devbar.rules":"Icon rules","space.roomcard_section":"Room card shows:","space.label_temp":"Temperature","space.label_hum":"Humidity","space.label_lqi":"Average Zigbee signal","space.label_light":"Lights on/off","roomcard.light_on":"On","roomcard.light_off":"Off","roomcard.light_partial":"{on} of {total}","toast.split_pick_inside":"Intermediate cut points must be inside the room","mode.decor":"Background editor","decor.select":"Select","decor.line":"Line","decor.rect":"Rectangle","decor.ellipse":"Oval","decor.text":"Text","decor.erase":"Erase","decor.erase_confirm_title":"Erase object?","decor.color":"Color","decor.width":"Line width","decor.line_style":"Line style","decor.line_style_solid":"Solid","decor.line_style_dashed":"Dashed","decor.fill":"Fill","decor.fill_color":"Fill color","decor.length":"Length","decor.size":"Size","decor.angle":"Rotation angle, °","decor.text_size":"Text size","decor.backdrop_properties":"Plan backdrop properties","decor.text_title":"Text label","decor.object_title":"Edit {kind}","decor.text_label":"Text","decor.live_group":"Insert HA variable","decor.live_entity":"Entity","decor.live_entity_ph":"choose an entity","decor.live_attr":"Value","decor.live_attr_ph":"choose state or attribute","decor.live_state":"State","decor.backdrop":"Backdrop image","decor.backdrop_hint":"Drag to move; pull a corner to resize; use the top handle to rotate. Shift changes proportions or frees the angle.","decor.backdrop_reset":"Reset the picture","decor.backdrop_reset_done":"The picture is back at its original place and size","marker.icon_auto":"Auto: {icon} (by icon rules; pick one to override)","marker.icon_pin_auto":"Pin","mode.plan_tip":"Plan editor — the geometry of the home: draw and split/merge rooms, bind them to HA areas, place doors, windows and gates, move room cards, set the scale","mode.devices_tip":"Device editor — everything about icons: drag to position, click to edit binding/icon/display, add virtual devices, icon rules","mode.decor_tip":"Background editor — purely visual decor under the plan: lines, rectangles, ovals and text labels that never react to clicks","space.glow_enabled":"Light-source glow","gs.glow_group":"Light-source glow","gs.glow_base":"House darkness","gs.glow_light":"Default light color / intensity","gs.wall_group":"Walls","gs.wall_fill":"Wall fill","gs.glow_radius":"Glow radius","gs.unit_m":"m","gs.unit_ft":"ft","marker.controls_label":"Controls other light sources","marker.controls_hint":"With tap action “Toggle”, a click flips every source added here. The marker’s own bound entity is controlled directly and cannot be added; use the switch below to classify it as a light.","marker.controls_filter":"Search lights and switches…","info.controls":"Controls","marker.glow_radius_label":"Glow radius","marker.glow_radius.help":"Sets the glow radius in metres or feet; an empty value uses the radius from general settings.","marker.glow_radius.help.aria":"Help: glow radius","markup.wallthick":"Thickness","markup.select":"Select","title.markup_wallthick":"Thickness — click a wall to set how thick it is. Empty or zero removes the thickness.","markup.hint_wallthick":"click a wall · Esc closes without applying","wallthick.field":"Thickness","wallthick.unit_cm":"cm","wallthick.unit_in":"in","wallthick.apply_room":"Apply to all walls of this room","markup.draw_wall_title":"Thickness of each new room-wall segment (1–100 cm). Shared walls keep the neighbour's value.","toast.wallthick_pick":"Click a wall (not an open boundary)","toast.wallthick_open":"Open boundaries have no thickness","toast.wallthick_set":"Wall thickness set","toast.wallthick_cleared":"Wall thickness removed","toast.physical_range":"Enter a value from 1 to {max} {unit}","toast.physical_angle":"Enter a rotation angle from 0° up to, but not including, 90°","toast.physical_limit":"The space has reached the limit for this type of geometry","markup.boundary_hint":"Select a shared room boundary or a dashed stretch","markup.boundary_hint_open":"Click the start of the open stretch","markup.boundary_hint_second":"Choose the end on the selected boundary · Esc to cancel","markup.boundary_hint_retry":"Choose the end on the selected boundary","markup.boundary_hint_restore":"Click to restore the wall","markup.boundary_hint_outer":"Only a boundary shared by two rooms can be opened","markup.boundary_hint_blocked":"a partition, column or unfinished contour is above this boundary","markup.boundary_hint_ambiguous":"Choose a stretch farther from the junction","markup.boundary_cancel":"Cancel the unfinished Boundary action","toast.openwall_pick":"This tool works with room boundaries","toast.openwall_shared_only":"Only a shared wall between two rooms can be opened","toast.openwall_short":"Stretch too short — pick a farther point","toast.boundary_opened":"Boundary stretch opened","toast.boundary_restored":"Wall restored on this stretch","toast.boundary_same_edge":"Choose the end point on the selected wall","toast.boundary_ambiguous":"Choose a stretch slightly farther from the junction","toast.boundary_blocked":"This boundary is covered by independent masonry. Move or delete it with Select first","toast.delete_room_pick":"Click inside the room you want to delete","toast.openwall_openings_removed":"Openings on the virtual stretch were removed","toast.openwall_opened":"Boundary “{a}” ↔ “{b}” is now open","toast.openwall_closed":"Boundary “{a}” ↔ “{b}” is closed again","toast.opening_on_virtual":"Openings cannot sit on a virtual wall","marker.from_ha_option":"Pick from the HA list","marker.show_entities":"Show entities","marker.show_entities_tip":"Adds not only devices to the list, but all their entities too","marker.pick_ph":"Choose a device…","room.open_area":"Open the HA area","view.volumetric":"Volumetric view","view.flat":"Flat view","kiosk.title":"This screen's sizes","kiosk.hint":"Stored on this device only — every wall tablet or TV can have its own comfortable sizes.","kiosk.icon_scale":"Device icon size","kiosk.font_scale":"Room card text size","editor.kiosk":"Wall device (kiosk) mode","editor.cycle":"Auto-switch spaces every N seconds (kiosk, 0 = off)","room.settings_title":"Room settings","room.settings_section":"Room settings (override the space)","room.fill_label":"Fill in THIS room","fill.inherit":"As the space","room.custom_fill_space":"Space color","room.custom_fill_own":"Room color","room.temp_src_label":"Temperature source","room.hum_src_label":"Humidity source","room.src_average":"Average over the room's sensors (default)","room.src_pick":"A specific HA device or entity","room.src_ph":"Choose a source…","toast.room_updated":"Room updated","space.card_font":"Room-card font size (whole space)","room.sizes_section":"Font sizes","room.name_scale":"Room name size","room.label_scale":"Metrics size","preview.room_name":"Living room","toast.cfg_reload_failed":"Could not reload the plan from the server: {err}","room.settings_short":"Room settings","room.unnamed":"Unnamed room","marker.use_climate_temp":"Include the device temperature in the room","marker.use_climate_temp_tip":"Adds an air conditioner or thermostat current_temperature to the room average. Configure the external value badge separately below.","marker.value_badge_title":"Value badge","marker.value_badge.help":"Shows one selected value next to the icon. It does not affect room metrics, light, Glow or the tap action.","marker.value_badge.help.aria":"Help: device value badge","marker.value_badge_enabled":"Show a value badge","marker.value_badge_source":"Value","marker.value_badge_source.help":"Choose a specific state, supported attribute, or derived value of this device.","marker.value_badge_source.help.aria":"Help: value badge source","marker.value_badge_position":"Position","marker.value_badge_position.help":"The chosen side stays fixed and does not flip automatically at a plan edge.","marker.value_badge_position.help.aria":"Help: value badge position","marker.value_badge_right":"Right","marker.value_badge_bottom":"Below","marker.value_badge_left":"Left","marker.value_badge_top":"Above","marker.value_badge_empty":"This device has no available values","marker.value_badge_static":"A static icon does not show live values. The setting is preserved.","marker.value_badge_missing":"Source unavailable","marker.value_badge_missing_hint":"The saved source is currently unavailable. Keep it, disable the badge, or choose another value.","marker.value_badge_duplicate":"The same value is already shown inside the icon.","marker.value_badge_state":"State · {name}","marker.value_badge_marker_state":"Light-source state · {name}","marker.value_badge_lqi":"Average Zigbee signal quality","marker.value_badge_attr_current_temperature":"Current temperature · {name}","marker.value_badge_attr_temperature":"Target temperature · {name}","marker.value_badge_attr_current_humidity":"Current humidity · {name}","marker.value_badge_attr_humidity":"Humidity · {name}","marker.value_badge_attr_current_position":"Position · {name}","marker.value_badge_attr_percentage":"Speed · {name}","marker.value_badge_attr_brightness":"Brightness · {name}","marker.value_badge_attr_volume_level":"Volume · {name}","marker.value_badge_attr_battery_level":"Battery · {name}","marker.value_badge_attr_fan_speed":"Fan speed · {name}","marker.light_role_label":"Is this device a light source?","marker.light_role.help":"Controls whether this marker itself is a spatial light source. Linked lights above remain independent room-light controls.","marker.light_role.help.aria":"Help: whether this device is a light source","marker.light_role_auto_yes":"Auto (light source)","marker.light_role_auto_no":"Auto (not a light source)","marker.light_role_always":"Always a light source","marker.light_role_never":"Never a light source","marker.light_entity_label":"Leading light entity","marker.light_entity.help":"For a composite device, choose the entity whose state, colour and brightness represent this plan source. The automatic fallback remains compatible with older plans.","marker.light_entity.help.aria":"Help: leading light entity","marker.light_entity_auto":"Automatic ({entity})","marker.light_entity_none":"no controllable entity","marker.light_entity_missing":"The saved entity {entity} is unavailable. House Plan temporarily uses {fallback}; the saved choice will be restored if it returns.","marker.glow_color_label":"Glow colour and brightness","marker.glow_mode.help":"Use live source values, override only its colour, or fix both colour and brightness. The minimum is 1%; to disable the source, choose “Never”.","marker.glow_mode.help.aria":"Help: glow colour and brightness","marker.glow_mode_auto":"From source","marker.glow_mode_color":"Set colour","marker.glow_mode_fixed":"Set colour and brightness","marker.glow_color":"Glow colour","marker.glow_brightness":"Brightness","marker.glow_disabled_never":"Glow settings are unavailable because this marker is explicitly not a light source.","marker.glow_disabled_auto":"Auto mode found no spatial light source for this marker.","marker.glow_disabled_no_entity":"No active controllable Home Assistant entity is available for this spatial source.","marker.glow_passive_hint":"This source has no own Home Assistant data. Set its colour and brightness manually; its radius remains available.","marker.control_broken":"Saved source is missing or no longer marked as a light","marker.control_missing_label":"Missing plan light","marker.control_passive":"passive source","toast.marker_control_cycle":"This link would create a circular chain of light controls.","toast.marker_binding_required":"Choose a Home Assistant device before linking another light source.","confirm.unlock":"Unlock “{name}”?","toast.files_migrate_failed":"Attachments could not be moved to the new binding, links keep pointing at the old files: {err}","space.pick_saved":"Already uploaded","space.pick_saved_hint":"Plans stored on the server, including ones you detached earlier","space.no_saved":"No plans stored on the server yet.","space.loading":"Loading…","space.used_by":"in use: {list}","space.in_use":"A space still uses this plan — detach it first","btn.use":"Use","confirm.delete_plan":'Delete the plan file "{name}" from the server? This cannot be undone.',"toast.plans_list_failed":"Could not list the stored plans: {err}","toast.plan_delete_failed":"Could not delete the plan: {err}","marker.hide":"Hide","marker.hide_tip":'The device will disappear from the plan after saving but will still count toward the room signal. Restore it through "Hidden and disabled" in the device editor.',"marker.show":"Show","marker.show_tip":"The device will appear on the plan again after saving.","marker.hidden_ghost":"Device hidden by the user","marker.ha_disabled_device":"The device is disabled in Home Assistant and hidden from the plan.","marker.ha_disabled_entity":"The entity is disabled in Home Assistant and hidden from the plan.","marker.ha_disabled_all_entities":"The device has no active Home Assistant entities, so it is hidden from the plan.","marker.ha_registry_limited":"The full Home Assistant registry is unavailable to this user. The unverified object cannot be shown or used for now.","marker.delete_tip":"Completely delete the device from the plan and every aggregate. You can add it again later.","tap.run":"Run automation/script/scene","tap.cover":"Open/close (curtains/blinds)","marker.run_target_label":"What to run","marker.run_search_ph":"Search: automation, script or scene…","marker.run_target_gone":"Target {id} not found — pick again","marker.tap_confirm":"Ask for confirmation","marker.tap_confirm_tip":"Show a confirmation dialog before acting — a guard against accidental taps.","marker.toggle_hint_single":"Target: {name} ({id}).","marker.virtual_light_target":"Virtual light: {name}.","marker.virtual_light_current":"Manual state: {state} → {effect}.","marker.virtual_light_state_on":"on","marker.virtual_light_state_off":"off","marker.toggle_hint_group":"Will toggle {count} source(s): {names}.","marker.toggle_hint_current":"Now: {state} → {effect}.","marker.toggle_hint_group_current":"Currently on: {on} of {count} → {effect}.","marker.toggle_hint_skipped":"Will be skipped ({count}): {targets}.","marker.toggle_effect_turn_on":"will turn on","marker.toggle_effect_turn_off":"will turn off","marker.toggle_effect_open":"will open","marker.toggle_effect_close":"will close","marker.toggle_effect_stop":"will stop","marker.toggle_effect_toggle":"state will toggle","marker.toggle_skip_missing":"not found","marker.toggle_skip_ha_disabled":"disabled in HA","marker.toggle_skip_unavailable":"unavailable","marker.toggle_skip_unsupported":"toggle is unsupported","marker.toggle_skip_secure":"blocked for security","marker.toggle_none_no_binding":"This device has no binding, so there is no state to toggle. Tapping will do nothing.","marker.toggle_none_no_actionable_entity":"This device has no state that can be toggled. Tapping will do nothing.","marker.toggle_none_configured_targets_missing":"The configured targets are unavailable. The device's own entity will not be substituted for them.","marker.toggle_none_ha_disabled":"The target is disabled in Home Assistant and cannot be used on the plan.","marker.toggle_none_unavailable":"The target is currently unavailable. Tapping will do nothing.","marker.toggle_none_unsupported":"Home Assistant exposes no safe toggle operation for this entity. Tapping will do nothing.","marker.toggle_none_secure":"Toggling locks, alarms and secure gates from the plan is blocked for security.","run.automation":"automation","run.script":"script","run.scene":"scene","confirm.tap_run":'Run "{name}"?',"confirm.tap_toggle":'Toggle "{name}"?',"confirm.tap_cover":'Open/close "{name}"?',"toast.run_started":"Started: {name}","toast.run_target_missing":"Run target not found — check the device settings","toast.run_target_required":"Pick an automation, script or scene","toast.tap_target_changed":"The action target changed. Try again.","toast.virtual_light_toggle_failed":"Could not toggle the virtual light: {err}","toast.value_badge_source_required":"Choose a value for the badge","btn.run":"Run","vac.section":"Robot vacuum: live position","vac.autocal":"Set up automatically","vac.live":"Live position on the plan","vac.trail":"Show the robot's path","vac.cal_maps":"Calibrated maps: {maps}","vac.autocal_no_rooms":"The integration reports no room list — open “Fit manually”","vac.autocal_no_match":"Room names did not match (need ≥3 in common) — open “Fit manually”","vac.autocal_done":"Done: bound via {rooms} rooms. Start a cleanup and check","vac.cal_need_pos":"The robot is not reporting coordinates — start a cleanup and pause it","vac.cal_done":"Calibration saved. Start a cleanup and check","vac.cal_cancelled":"Calibration cancelled","vac.fit":"Fit manually","vac.fit_hint":"Drag the robot map into place, stretch by the corners","vac.fit_rotate":"Rotate 90°","vac.fit_mirror":"Mirror","vac.trail_never":"Never","vac.trail_cleaning":"While cleaning","vac.trail_always":"Always","gs.bg_group":"Stage background","gs.bg_color":"Background around the plan","gs.bg_default":"Theme default","gs.bg_theme":"theme default","gs.bg_mode":"Plan background","gs.bg_static":"Static color","gs.bg_daynight":"Follows the Sun","gs.bg_daynight_hint":"The background moves through dawn, day, dusk and night using sun.sun, or your local clock when sun data is unavailable. No compass is needed for the background.","gs.sun_group":"Sun","gs.sun_missing":"The sun.sun entity was not found — the background follows your local clock; window rays are unavailable.","gs.north":"North on the plan","gs.north_ph":"not set","gs.north_hint":"Set north only for window rays (1° steps, 15° with Shift); the Sun background works without it.","gs.north_clear":"Clear","gs.north_letter":"N","gs.sun_rays":"Sunlight through windows","gs.about_group":"About","gs.about_version":"Houseplan Card v{v}","gs.about_github":"GitHub · docs & issues","gs.about_telegram":"Telegram chat","space.bg_color":"Background around the plan","space.bg_inherit":"Inherit general","space.bg_inherited":"inherits general settings","space.bg_mode":"Plan background","space.north":"North on the plan (override)","space.north_inherited":"inherited: {v}","space.sun_rays":"Sunlight through windows","space.sun_inherit":"Inherit general","space.sun_on":"On","space.sun_off":"Off","canvas.far_objects":"{n} object(s) far from the plan","canvas.show_far":"Show","canvas.home_tip":"The plan is over there — click to fit it","gs.grid_group":"Plan maintenance","gs.grid_hint":"Updates data models, aligns plan elements to the grid and merges redundant wall fragments. An exact report is shown before anything is stored.","gs.align_all":"Optimize plans","gs.align_title":"Optimize plans","gs.align_none":"All plans already use the current optimized data model.","gs.align_count":"{n} of {total} elements will move, by at most {cm} cm.","gs.align_where":"The largest shift is in “{s}”.","gs.align_turned":"Openings whose angle is corrected: {n}.","gs.align_removed_drafts":"Invalid outlines collapsed by the grid and removed: {n}.","gs.optimize_changes":"Model migrations: {m}; plans canonicalized: {c}; merged real-wall fragments: {w}; virtual fragments: {s}.","gs.optimize_glow_migration":"Legacy Glow: {spaces} spaces → no data fill + independent Glow; {rooms} rooms → inherited data fill + independent Glow.","gs.align_warn":"Elements deliberately placed between grid nodes will move. One undo is available after the operation, only until the next plan edit.","gs.align_run":"Optimize","gs.align_done":"Plans optimized: {n} elements moved, {m} records maintained","gs.optimize_undo":"Undo last optimization","gs.optimize_undone":"The last optimization was undone","decor.furniture":"Furniture","furn.title":"Furniture library","furn.symbol":"Symbol","furn.group_furniture":"Furniture","furn.group_appliance":"Appliances","furn.group_sanitary":"Plumbing","furn.group_other":"Other","furn.width":"Width","furn.depth":"Depth","furn.pick_hint":"Pick a symbol, then click on the plan.","furn.place_hint":"Click on the plan — the piece lands against the nearest wall. Shift places it free.","furn.sym_sofa":"Sofa","furn.sym_armchair":"Armchair","furn.sym_coffee_table":"Coffee table","furn.sym_table_dining":"Dining table","furn.sym_table_round":"Round table","furn.sym_chair":"Chair","furn.sym_desk":"Desk","furn.sym_bed_double":"Double bed","furn.sym_bed_single":"Single bed","furn.sym_nightstand":"Nightstand","furn.sym_wardrobe":"Wardrobe","furn.sym_bookshelf":"Bookshelf","furn.sym_fridge":"Fridge","furn.sym_stove":"Cooker","furn.sym_dishwasher":"Dishwasher","furn.sym_washer":"Washing machine","furn.sym_dryer":"Tumble dryer","furn.sym_tv":"TV","furn.sym_ac":"Air conditioner","furn.sym_water_heater":"Water heater","furn.sym_toilet":"Toilet","furn.sym_bathtub":"Bathtub","furn.sym_shower":"Shower","furn.sym_sink":"Washbasin","furn.sym_kitchen_sink":"Kitchen sink","furn.sym_bidet":"Bidet","furn.sym_stairs":"Stairs","furn.sym_fireplace":"Fireplace","furn.sym_plant":"Plant","furn.sym_rug":"Rug","common.yes":"Yes","common.no":"No","vac.diag_source":"Source","vac.diag_platform":"Integration","vac.diag_status":"Status","vac.diag_position":"Position","vac.diag_rooms":"Rooms","vac.diag_rooms_value":"{total} · {matched} names match · {readiness}","vac.autocal_ready":"auto-calibration available","vac.autocal_not_ready":"need 3 matching names","vac.diag_path":"Integration path","vac.diag_map":"Map ID","vac.source_none":"not selected","vac.source_status_ok":"Ready","vac.source_status_missing":"Missing","vac.source_status_disabled":"Disabled in Home Assistant","vac.source_status_unavailable":"Unavailable","vac.source_status_unverified":"Cannot verify with current permissions","vac.source_status_unsupported":"No position data","vac.source_status_none":"No source","vac.source_banner_missing":"The saved source no longer exists. It was not replaced automatically; choose another source or restore it in Home Assistant.","vac.source_banner_disabled":"The saved source is disabled in Home Assistant. Enable it there or choose another source.","vac.source_banner_unverified":"Current Home Assistant permissions cannot verify this saved source. It remains pinned and will not be replaced automatically.","vac.choose_source":"Choose source","vac.source_auto":"Automatic","vac.source_auto_hint":"Use a compatible entity from this device","vac.all_cameras":"All cameras","vac.all_cameras_warn":"A camera may not provide robot data. Choose one only if it is your vacuum map.","vac.all_cameras_empty":"No other camera entities found.","vac.platform_unknown":"unknown integration","vac.cap_position":"position","vac.cap_rooms_short":"rooms","vac.cap_path":"path","vac.cap_map":"map ID","vac.cap_none":"no robot data detected","vac.xcme_hint":"Enable these Xiaomi Cloud Map Extractor attributes:","vac.documentation":"Documentation","vac.residual_title":"Check automatic calibration","vac.residual_message":"The matched rooms disagree by up to {error}. Apply this approximate calibration, refine it manually, or cancel without changing the saved setup.","vac.apply_proposal":"Apply","gs.backup_group":"Backup and transfer","gs.backup_hint":"Download a portable JSON backup or preview and import a backup made by House Plan.","backup.export_open":"Export","backup.import_open":"Import","backup.export_title":"Export House Plan","backup.import_title":"Import House Plan","backup.export_hint":"Choose whether the backup should contain the whole House Plan configuration or only the current space.","backup.full":"Full backup","backup.current_space":"Current space","backup.current_space_title":"Current space: {title}","backup.no_current_space":"No current space","backup.plan_only":"Plan only","backup.plan_only_hint":"Keep rooms, walls, openings, decor and room-label positions without devices or Home Assistant bindings.","backup.plan_only_preview":"This file contains the plan only","backup.privacy_warning":"The archive keeps names, Home Assistant identifiers and exact coordinates. Internal plans and attachments are referenced, not embedded; runtime states and vacuum trails are not included.","backup.download":"Download JSON","backup.export_done":"Backup downloaded","backup.reading":"Checking the backup…","backup.revalidated":"The plan changed after this preview. The summary was refreshed; review it and confirm again.","backup.error.unauthorized":"You do not have permission to export or import this plan.","backup.error.not_ready":"House Plan is not ready yet. Try again in a moment.","backup.error.too_large":"The backup exceeds the 8 MiB limit.","backup.error.invalid_json":"The selected file is not valid JSON.","backup.error.invalid_format":"The selected file is not a House Plan backup.","backup.error.unsupported_export_version":"This backup format is not supported by the installed version.","backup.error.future_model":"The backup was created by a newer House Plan data model.","backup.error.invalid_config":"The backup contains an invalid House Plan configuration.","backup.error.invalid_layout":"The backup contains invalid object positions.","backup.error.invalid_content":"The backup contains invalid or inconsistent content references.","backup.error.space_not_found":"The selected space no longer exists.","backup.error.capacity_exceeded":"Adding this backup would exceed the plan limits.","backup.error.preview_expired":"The preview expired. Select the backup file again.","backup.error.preview_owner_mismatch":"This preview belongs to another Home Assistant user.","backup.error.conflict":"The plan changed after the preview. Review the refreshed summary.","backup.error.content_confirmation_required":"Confirm that unavailable local content may be detached.","backup.error.commit_failed":"The backup could not be applied safely. The previous plan was restored or is pending recovery.","backup.error.missing_plan":"A referenced plan file disappeared after the preview. Review the backup again.","backup.error.missing_content":"A referenced local attachment disappeared after the preview. Review the backup again.","backup.error.marker_control_missing":"A linked plan light is missing from the backup.","backup.error.marker_control_not_light":"A linked plan target is no longer marked as a light source.","backup.error.marker_control_self":"A light source cannot control itself.","backup.error.marker_control_cycle":"The backup contains a circular chain of light controls.","backup.error.duplicate_marker_control":"The backup contains a duplicate plan-light link.","backup.error.no_backup":"There is no import or optimization snapshot to restore.","backup.same_source":"Created on this Home Assistant instance","backup.foreign_source":"Created on another Home Assistant instance","backup.created":"Created: {value}","backup.versions":"Card {card}; integration {integration}; data model {model}","backup.count_spaces":"Spaces: {n}","backup.count_rooms":"Rooms: {n}","backup.count_walls":"Walls: {n}","backup.count_openings":"Openings: {n}","backup.count_decor":"Decor objects: {n}","backup.count_markers":"Devices: {n}","backup.count_layout":"Positions: {n}","backup.bindings":"Bindings — devices: {device}, entities: {entity}, virtual: {virtual}; positions without a space: {legacy}","backup.binding_status":"Target status — active: {active}, disabled: {disabled}, missing: {missing}","backup.missing_areas":"Areas missing on the target: {areas}","backup.dropped_marker_links":"Plan-light links outside this transferred space were omitted: {n}.","backup.replace_warning":"This replaces the current configuration and layout. Uploaded files are never deleted. One undo remains available until the next plan edit.","backup.foreign_bookkeeping":"Instance-specific known/new-device bookkeeping will not be imported.","backup.final_name":"New space name","backup.target_settings":"The target instance's global settings remain unchanged.","backup.duplicates":"Bindings already present: {n}","backup.skip":"Skip duplicate devices","backup.virtual_copy":"Add safe static virtual copies","backup.content":"Referenced content","backup.content_available":"available locally","backup.content_external":"external link","backup.content_detach_required":"will be detached","backup.confirm_detach":"I understand that unavailable internal plans and attachments will be detached from the imported configuration.","backup.replace":"Replace","backup.add":"Add space","backup.space_done":"Space imported: {rooms} rooms, {markers} devices","backup.full_done":"Backup restored: {spaces} spaces, {rooms} rooms, {markers} devices","backup.undo_import":"Undo last full import","backup.import_undone":"Full import undone"};const Wo={en:jo,ru:{"editor.context_actions":"Действия: {object}","editor.tool_options":"Параметры инструмента: {tool}","editor.palette":"Палитра: {tool}","editor.open_group":"Группа инструментов: {group}","editor.group_active":"{group} — активно: {item}","editor.disabled_action":"{action} недоступно: {reason}","btn.properties":"Свойства","btn.keep_as_walls":"Оставить замкнутыми стенами","title.markup_partition":"Перегородка: нарисуйте одну независимую физическую стену","title.markup_select":"Выбор и редактирование стен, колонн и сохранённых контуров","title.markup_column":"Колонна: кликните точку сетки для квадратной колонны","markup.partition":"Перегородка","markup.column":"Колонна","markup.hint_partition":"два клика рисуют одну перегородку · Shift фиксирует шаг 45°","markup.hint_column":"кликните точку сетки, чтобы поставить квадратную колонну","history.draft_segment":"Добавление сегмента черновика комнаты","history.draft_merge":"Соединение незавершённых контуров комнаты","history.draft_segment_delete":"Удаление сегмента черновика комнаты","history.partition_add":"Добавление перегородки","history.column_add":"Добавление колонны","history.physical_edit":"Изменение физического объекта","history.physical_delete":"Удаление физического объекта","history.physical_move":"Перемещение физического объекта","history.contour_to_partitions":"Преобразование контура в замкнутые стены","toast.column_duplicate":"Колонна с тем же центром и внешним размером уже существует","confirm.delete_draft":"Удалить весь незавершённый контур комнаты?","confirm.delete_draft_segment":"Удалить этот сегмент черновика? Оставшийся контур может разделиться на два.","physical.partition_properties":"Свойства перегородки","physical.column_properties":"Свойства колонны","physical.draft_properties":"Свойства сегмента черновика","physical.shape":"Форма","physical.square":"Квадрат","physical.circle":"Круг","physical.diameter":"Диаметр","physical.side":"Сторона","physical.rotation":"Угол поворота","physical.length":"Длина","physical.allowed_range":"Допустимо: 1–{max} {unit}","physical.delete_segment":"Удалить сегмент","physical.delete_draft":"Удалить весь контур","physical.partition_size_title":"Толщина перегородки, создаваемой вторым кликом (1–100 см).","physical.column_size_title":"Внешняя сторона квадратной колонны, создаваемой кликом (1–150 см).","card.title":"План дома","count.devices":"{n} устр.","empty.no_spaces":"Пространств пока нет.","empty.add_first":"Добавьте первое пространство и загрузите план этажа.","empty.install":"Установите интеграцию House Plan и добавьте запись в «Устройства и службы».","btn.add_space":"Добавить пространство","btn.cancel":"Отмена","btn.save":"Сохранить","btn.close":"Закрыть","btn.delete":"Удалить","btn.edit":"Редактировать","btn.open_in_ha":"Открыть в HA","btn.reset":"Сброс","btn.attach":"Прикрепить…","btn.upload":"Загрузить…","btn.replace":"Заменить…","title.zoom_in":"Приблизить","title.zoom_out":"Отдалить","title.zoom_fit":"Вписать всё","title.add_device":"Добавить устройство на план","title.show_all":"Показать скрытые и деактивированные устройства служебными маркерами (только в этой вкладке)","title.markup":"Разметка комнат: сетка, линии, контуры","title.configure_space":"Настроить пространство","title.add_space":"Добавить пространство","title.markup_add":"Контур комнаты: соединяйте точки сетки линиями до замыкания комнаты; Shift фиксирует направление с шагом 45°","title.markup_merge":"Объединить: клик по одной комнате, затем по соседней с общей стеной","title.markup_split":"Разделить комнату: клик по комнате, затем две точки на её стенах","title.markup_delroom":"Удалить комнату: кликните внутри комнаты и подтвердите удаление","title.markup_boundary":"Граница: две точки на сплошной общей стене делают участок виртуальным; клик по пунктиру восстанавливает стену","title.need_plan":"Загрузите подложку (план этажа)","markup.add":"Контур комнаты","markup.merge":"Объединить","markup.split":"Разделить","markup.resize":"Размер","title.markup_resize":"Изменение размера комнат: тяните ручку стены; клик по комнате — угловая рамка масштаба","markup.hint_resize":"тяните ручку стены · клик по комнате — угловая рамка · Esc отменяет перетаскивание · Ctrl+Z — отмена шага","markup.opening":"Проём","markup.boundary":"Граница","markup.delete_room":"Удалить комнату","history.undo":"Отменить","history.redo":"Повторить","history.undo_named":"Отменить: {name} (Ctrl+Z)","history.redo_named":"Повторить: {name} (Ctrl+Shift+Z / Ctrl+Y)","history.undo_empty":"Нет операций для отмены","history.redo_empty":"Нет операций для повтора","history.undone":"Отменено: {name}","history.redone":"Повторено: {name}","history.add_room":"Создание комнаты","history.split_room":"Разделение комнаты","history.merge_rooms":"Объединение комнат","history.resize_room":"Изменение размера комнаты","history.open_boundary":"Открытие границы","history.close_boundary":"Восстановление стены","history.wall_thickness":"Изменение толщины стены","history.add_opening":"Добавление дверного или оконного проёма","history.edit_opening":"Изменение проёма","history.move_opening":"Перемещение проёма","history.delete_opening":"Удаление проёма","history.delete_room":"Удаление комнаты","history.decor_add":"Добавление объекта декора","history.decor_edit":"Изменение объекта декора","history.decor_move":"Перемещение объекта декора","history.decor_transform":"Трансформация объекта декора","history.decor_delete":"Удаление объекта декора","history.backdrop_transform":"Трансформация подложки плана","title.markup_opening":"Проёмы: клик по стене — добавить, клик по проёму — редактировать","opening.new":"Новый проём","opening.edit":"Проём","opening.door":"Дверь","opening.window":"Окно","opening.gate":"Ворота","opening.type_label":"Тип","opening.length_label":"Длина, см","opening.contact_label":"Датчик открытия","opening.lock_label":"Замок","opening.none":"— нет —","opening.invert":"Инвертировать открыто/закрыто","opening.flip_h":"Петли с другой стороны","opening.flip_v":"Открывается в другую сторону","opening.open":"Открыто","opening.closed":"Закрыто","opening.locked":"Заперто","opening.unlocked":"Не заперто","opening.state_unknown":"недоступно","opening.no_entities":"Датчики не привязаны — статичный символ на плане.","toast.opening_no_wall":"Кликните рядом со стеной комнаты — проёмы ставятся на стены","markup.delete":"Удалить","markup.hint_points":"точек: {n} · Shift — шаг 45° · Esc/Ctrl+Z — убрать точку · клик по первой или Ctrl/Cmd+клик — замкнуть","markup.hint_start":"кликните точку сетки, чтобы начать контур","tip.lqi":"средний сигнал zigbee:","tip.area":"площадь: {value}","info.device_header":"Устройство на плане","info.model":"Модель","info.state":"Состояние","info.link":"Ссылка","info.manuals":"Инструкции","info.none":"Нет дополнительной информации","marker.new_device":"Новое устройство","marker.name_label":"Имя (отображается на плане)","marker.name_ph":"Название","marker.binding_label":"Привязка к устройству HA","marker.binding_disabled":"деактивировано в Home Assistant","marker.virtual_option":"Виртуальное устройство (без привязки)","marker.search_ph":"Поиск устройства / группы…","marker.nothing_found":"ничего не найдено","marker.room_label":"Комната","marker.room_override":" (переопределить размещение)","marker.room_choose":"— выберите комнату —","marker.room_auto":"— по зоне устройства (авто) —","marker.icon_label":"Иконка","marker.icon_ph":"mdi:… (пусто = авто)","marker.display_label":"Отображение","display.badge":"Значок + состояние","display.icon_ripple":"Значок + состояние и активность","display.static_icon":"Всегда статичный значок","marker.display_hint":"Значок + состояние меняет подложку при работе, открытии, тревоге и недоступности. Значок + состояние и активность дополнительно показывает короткую пульсацию для событий и постоянную — для работы, движения или присутствия. Значение + состояние заменяет значок одним однозначным значением HA. Статичный значок не реагирует на состояния устройства.","marker.display_hint_badge":"Значок и динамическая подложка показывают состояние устройства без обычной пульсации активности. Красная тревога сохраняется.","marker.display_hint_icon":"Значок и динамическая подложка показывают состояние устройства без обычной пульсации активности. Красная тревога сохраняется.","marker.display_hint_icon_ripple":"Значок, динамическая подложка и пульсация: короткая — для событий, постоянная — для работы, движения или присутствия. Красная тревога имеет отдельный приоритет.","marker.display_hint_value":"Значок заменяется одним однозначным значением Home Assistant; подложка продолжает показывать состояние. Красная тревога сохраняется.","marker.display_hint_static_icon":"Всегда одинаковые чёрная подложка и значок. Состояния, активность, недоступность и тревоги не меняют отображение.","marker.static_alarm_warning":"Это устройство может сообщать о тревогах. Статичный режим скрывает визуальную тревогу маркера.","marker.preview.title":"Предпросмотр отображения","marker.preview.actual":"Сейчас","marker.preview.example":"Пример","marker.preview.integration":"Интеграция","marker.preview.source":"Источник отображения","marker.preview.current_state":"Текущее состояние","marker.preview.result":"На плане","marker.preview.details":"Подробнее об источниках","marker.preview.select_source":"Выберите устройство или сущность Home Assistant, чтобы увидеть фактическое отображение.","marker.preview.unknown_provider":"Интеграция неизвестна","marker.preview.virtual_provider":"House Plan · виртуальное устройство","marker.preview.no_source":"Нет активного источника","marker.preview.no_state":"Нет текущего состояния","marker.preview.mixed_states":"Несколько разных состояний","marker.preview.multiple_sources":"Источников: {n}","marker.preview.more_sources":"ещё {n}","marker.preview.scaled":"Предпросмотр уменьшен до {n}%, чтобы поместиться. Сохранённый размер не изменится.","marker.preview.demo_activity":"Показать пример активности","marker.preview.demo_notice":"Временный пример активности; фактическое состояние устройства не меняется.","marker.preview.demo_short":"Показать краткую активность","marker.preview.demo_continuous":"Показать постоянную активность","marker.preview.stop_continuous":"Остановить постоянную активность","marker.preview.demo_short_notice":"Пример краткой активности; фактическое состояние устройства не меняется.","marker.preview.demo_continuous_notice":"Пример постоянной активности; фактическое состояние устройства не меняется.","marker.preview.demo_already_visible":"Текущее состояние уже показывает реальную активность или тревогу.","marker.preview.reduced_motion":"В системе включено уменьшение движения, поэтому обычная активность показана точкой.","marker.preview.reason.neutral":"Нейтральная тёмная подложка","marker.preview.reason.working":"Жёлтая подложка: устройство сейчас работает","marker.preview.reason.working_activity":"Жёлтая подложка и эффект активности: устройство сейчас работает","marker.preview.reason.open":"Оранжевая подложка: физически открыто или разблокировано","marker.preview.reason.cover_icon_state":"Нейтральная подложка; состояние и значок определяет сущность шторы или заслонки","marker.preview.reason.presence":"Эффект активности, пока обнаружено присутствие","marker.preview.reason.event":"Короткий эффект активности после события","marker.preview.reason.transition":"Эффект активности во время движения или смены состояния","marker.preview.reason.media_neutral":"Медиаустройство остаётся на нейтральной тёмной подложке, пока доступно","marker.preview.reason.unavailable":"Приглушённая нейтральная подложка: отключено или недоступно","marker.preview.reason.alarm":"Красная тревожная подложка; тревога отображается всегда","marker.preview.reason.live_states_disabled":"Оформление по живым состояниям отключено в настройках карточки","marker.preview.reason.value_no_state":"Нет подходящего значения состояния — вместо него показан значок","marker.preview.reason.value_ambiguous_sources":"Подходит несколько источников значения — вместо него показан значок","marker.preview.reason.value_non_scalar":"Источник вернул не простое значение — вместо него показан значок","marker.preview.reason.value_virtual":"У виртуального устройства нет значения HA — вместо него показан значок","marker.preview.reason.vacuum_live_plan_only":"Живая позиция и след пылесоса доступны только на полном плане","marker.preview.reason.hidden_design_preview":"Устройство скрыто на плане, но его оформление видно в предпросмотре","marker.preview.reason.composite_power_source":"Состояние берётся из сущности питания устройства; вспомогательные переключатели не учитываются","marker.preview.reason.activity_display_disabled":"Выбранный режим отображения не показывает обычные эффекты активности","marker.preview.reason.ha_disabled":"Привязка отключена в Home Assistant, поэтому устройство будет скрыто на плане","marker.preview.reason.orphaned":"Сохранённая привязка Home Assistant больше не найдена","marker.preview.reason.static_icon":"Статичный режим: состояние устройства не меняет значок","marker.activity_color":"Цвет пульсации активности","marker.ripple_size":"Размер пульсации активности","marker.activity_alarm_note":"Цвет и размер относятся только к обычной активности и не влияют на красную тревогу.","marker.pulse_a11y_alarm":"Тревога","marker.pulse_a11y_event":"Недавнее событие","marker.pulse_a11y_presence":"Обнаружено присутствие","marker.pulse_a11y_transition":"Изменение состояния","marker.pulse_a11y_running":"Работает","marker.size_label":"Размер / поворот значка","marker.angle_label":"Поворот","marker.model_label":"Модель","marker.model_ph":"напр. Aqara T&H","marker.link_label":"Ссылка","marker.desc_label":"Описание","marker.desc_ph":"Заметки, характеристики…","marker.manuals_label":"Инструкции (PDF и т.п.)","marker.sub_device":"устройство","marker.sub_z2m_group":" · Z2M-группа","marker.sub_group":"группа","marker.sub_helper":"хелпер","space.new":"Новое пространство","space.header":"Пространство","space.title_label":"Название","space.title_ph":"Например: Гараж","space.plan_label":"Подложка (план)","space.no_plan":"нет подложки","space.plan_alt":"план","room.new":"Новая комната","room.name_label":"Отображаемое имя","room.name_ph":"Например: Терраса","room.area_label":"Зона Home Assistant (свободные)","room.no_area_option":"— без зоны —","room.default_name":"Комната","device.unnamed":"без имени","device.light_group":"группа света","device.fallback":"устройство","device.virtual":"виртуальное устройство","confirm.delete_room":"Удалить комнату «{name}»?","confirm.merge_rooms":"Слить комнаты «{a}» и «{b}»?","confirm.remove_marker":"Удалить «{name}» с плана? Устройство исчезнет полностью и перестанет участвовать в данных плана. Позже его можно будет добавить заново.","confirm.erase_decor":"Стереть объект «{kind}»? Действие можно отменить из истории редактора.","confirm.delete_space":"Удалить пространство «{title}» со всеми комнатами и разметкой?","toast.pos_save_failed":"Не удалось сохранить позицию: {err}","toast.no_entity":"У устройства нет подходящей сущности","toast.ha_disabled_action":"Деактивированный объект Home Assistant нельзя использовать на плане.","toast.ha_disabled_show_device":"Деактивированное в Home Assistant устройство нельзя показать на плане. Сначала активируйте его в Home Assistant.","toast.ha_disabled_show_entity":"Деактивированную в Home Assistant сущность нельзя показать на плане. Сначала активируйте её в Home Assistant.","toast.ha_disabled_add":"Деактивированный объект Home Assistant нельзя добавить на план. Сначала активируйте его в Home Assistant.","toast.ha_binding_unverified":"Статус объекта не удалось подтвердить по реестру Home Assistant. Отображение и действия временно недоступны.","toast.markup_needs_server":"Разметка доступна после переноса конфига на сервер","toast.conflict":"Конфиг изменён в другом окне — данные обновлены, повторите последнее действие","toast.cfg_save_failed":"Не удалось сохранить конфиг: {err}","toast.room_overlap":"Контур накладывается на комнату «{name}» — комнаты не должны накладываться","toast.contour_min_edges":"Чтобы замкнуть контур, сначала нарисуйте минимум две грани","toast.contour_cannot_close":"Контур нельзя замкнуть: он вырожден или пересекает сам себя","toast.merge_not_adjacent":"Объединять можно только комнаты с общей стеной","toast.rooms_merged":"Комнаты объединены в «{name}»","toast.split_pick_wall":"Начните разрез на стене комнаты","toast.split_bad_cut":"Разрез — от стены до стены внутри комнаты, без пересечения стен и самого себя","merge.header":"Объединение комнат","merge.hint":"У объединённой комнаты одно имя и одна зона. Вторая зона освобождается — её устройства уйдут с плана, пока их не заберёт другая комната.","merge.keep":"Оставить","merge.no_area":"без зоны","room.split_header":"Новая комната после разделения","toast.room_saved":"Комната сохранена ({n}). Устройств добавлено: {added}. Обведите следующую или выйдите из разметки.","toast.room_saved_no_area":"Комната сохранена ({n}, без зоны). Обведите следующую или выйдите из разметки.","toast.marker_needs_server":"Редактирование устройств доступно после переноса конфига на сервер","toast.virtual_name_required":"Укажите имя виртуального устройства","toast.marker_saved":"Устройство сохранено","toast.marker_removed":"Устройство удалено с плана","toast.integration_missing":"Интеграция House Plan не установлена — управление недоступно","toast.plan_formats":"Поддерживаются SVG, PNG, JPG, WebP","toast.plan_required":"Загрузите подложку — план этажа обязателен","toast.space_added_onboard":"Пространство добавлено. Обведите комнаты: кликайте по точкам сетки и замкните контур.","toast.space_added":"Пространство добавлено","toast.space_saved":"Пространство сохранено","toast.space_deleted":"Пространство удалено","toast.delete_failed":"Ошибка удаления: {err}","toast.error":"Ошибка: {err}","toast.file_failed":"Файл «{name}» не загружен: {err}","toast.files_attached":"Прикреплено файлов: {n}","err.unknown":"неизвестная ошибка","err.code":"код {code}","err.too_large":"файл больше {mb} МБ","err.bad_ext":"недопустимый тип (нужен PDF/изображение)","err.unauthorized":"нужны права администратора","editor.title":"Заголовок","editor.default_floor":"Пространство по умолчанию","editor.icon_size":"Размер иконок, % ширины плана","editor.show_temperature":"Показывать температуру","editor.live_states":"Живые состояния (вкл/выкл, открыто…)","editor.show_signal":"Показывать сигнал zigbee (LQI)","editor.language":"Язык интерфейса","editor.lang_auto":"Авто (профиль HA)","editor.lang_en":"English","editor.lang_ru":"Русский","title.icon_rules":"Правила иконок: какая MDI-иконка достаётся устройству по имени","rules.title":"Правила иконок","rules.hint":"Правила проверяются сверху вниз по строке «имя устройства + модель» (regex без учёта регистра); срабатывает первое совпадение. Если ничего не подошло — решает device class сущности, затем — иконка-заглушка.","rules.pattern_ph":"regex, напр. розетк|plug","rules.icon_ph":"mdi:power-socket-de","rules.add":"Добавить правило","rules.reset":"Сбросить к умолчаниям","rules.test_ph":"Проверьте имя устройства…","rules.invalid":"некорректный regex","rules.saved":"Правила иконок сохранены","btn.up":"Вверх","btn.down":"Вниз","tap.info":"Карточка устройства","tap.more_info":"Диалог HA (more-info)","tap.toggle":"Переключить состояние","marker.tap_label":"Действие по нажатию для этого устройства","tap.toggle_note":"Под селектором показано, какое состояние изменится. Защищённые устройства не переключаются с плана.","import.title":"Создать пространства из этажей HA","import.hint":"Home Assistant уже знает эти этажи. Отметьте, какие превратить в пространства плана — далее для каждого попросим картинку плана. Комнаты затем обводятся вручную по плану.","import.start":"Создать: {n}","import.manual":"Начать с нуля","import.progress":"Этаж {i} из {n}","import.done":"Пространства созданы. Обведите комнаты: кликайте по точкам сетки и замкните контур.","btn.skip":"Пропустить","space.scale_label":"Масштаб (размер клетки сетки)","space.scale_unit":"см на клетку","space.display_section":"Отображение","space.show_borders":"Всегда отображать границы комнат","space.show_names":"Отображать названия комнат (перетаскиваются)","space.room_color":"Цвет границ и названий","space.opacity":"Прозрачность","space.fill_label":"Заливка комнат","fill.none":"Нет","fill.lqi":"По силе зигби-сигнала","fill.light":"По освещению","fill.custom":"Свой цвет","space.custom_fill":"Цвет заливки","space.source_file":"У меня есть картинка плана","space.source_draw":"Нет подложки — нарисую комнаты вручную","space.orientation":"Холст","orient.landscape":"Альбомный","orient.portrait":"Портретный","orient.square":"Квадрат","fill.temp":"По температуре","space.temp_min":"Комфорт от","space.temp_max":"до","tip.temp_avg":"средняя температура:","space_card.button":"Перейти к пространству","space_card.not_found":"Пространство «{id}» не найдено","space_card.loading":"Загрузка…","continuity.restore_plan":"Восстанавливаем план…","continuity.restore_connection":"Восстанавливаем подключение к устройствам…","continuity.retry":"Повторить","editor.space":"Пространство","editor.show_button":"Показывать кнопку","editor.button_label":"Текст кнопки","editor.button_target":"Путь дашборда (куда вести)","marker.sub_entity":"сущность","title.general_settings":"Общие настройки","gs.title":"Общие настройки","gs.hint":"Цвета заливок действуют на все пространства; у каждого цвета своя прозрачность. Какой режим заливки использует пространство — задаётся в его диалоге.","gs.light_group":"Заливка: освещение","gs.light_on":"Свет включён","gs.light_off":"Весь свет выключен","gs.temp_group":"Заливка: температура","gs.temp_cold":"Холодно","gs.temp_ok":"Комфорт","gs.temp_hot":"Жарко","gs.lqi_group":"Заливка: зигби-сигнал","gs.lqi_low":"Слабый сигнал","gs.lqi_high":"Сильный сигнал","gs.reset":"Сбросить к умолчаниям","gs.saved":"Общие настройки сохранены","space.show_lqi":"Показывать зигби-сигнал (LQI) у устройств","space.hide_decor":"Скрыть декоративный слой","space.hide_decor_tip":"Линии, фигуры, надписи и мебель остаются на месте — их видно в редакторе подложки, но не на плане.","space.hide_openings":"Скрыть проёмы","space.hide_openings_tip":"Двери, окна и ворота не рисуются, но продолжают работать: свет проходит, солнце светит в окна, датчики открытия срабатывают. В редакторе плана проёмы видно всегда.","gs.light_none":"Нет источников света","mode.plan":"Редактор плана","mode.devices":"Редактор устройств","display.value":"Значение + состояние","marker.subarea":"без зоны, вручную","device.new":"Новое устройство — откройте его редактор, чтобы снять отметку","opening.unlock_action":"Открыть замок","opening.lock_action":"Закрыть замок","opening.lock_pending":"Выполняется…","title.close_editor":"Закрыть редактор (вернуться к просмотру)","devbar.add":"Добавить","devbar.show_all":"Скрытые и деактивированные","devbar.rules":"Правила иконок","space.roomcard_section":"В карточке комнаты:","space.label_temp":"Температура","space.label_hum":"Влажность","space.label_lqi":"Средний Zigbee-сигнал","space.label_light":"Свет вкл/выкл","roomcard.light_on":"Вкл","roomcard.light_off":"Выкл","roomcard.light_partial":"{on} из {total}","toast.split_pick_inside":"Промежуточные точки разреза — внутри комнаты","mode.decor":"Редактор подложки","decor.select":"Выбрать","decor.line":"Линия","decor.rect":"Прямоугольник","decor.ellipse":"Овал","decor.text":"Надпись","decor.erase":"Стереть","decor.erase_confirm_title":"Стереть объект?","decor.color":"Цвет","decor.width":"Толщина линии","decor.line_style":"Стиль линии","decor.line_style_solid":"Сплошная","decor.line_style_dashed":"Пунктирная","decor.fill":"Заливка","decor.fill_color":"Цвет заливки","decor.length":"Длина","decor.size":"Размер","decor.angle":"Угол поворота, °","decor.text_size":"Размер текста","decor.backdrop_properties":"Свойства картинки-подложки","decor.text_title":"Надпись","decor.object_title":"Редактирование: {kind}","decor.text_label":"Текст","decor.live_group":"Вставить переменную HA","decor.live_entity":"Сущность","decor.live_entity_ph":"выберите сущность","decor.live_attr":"Значение","decor.live_attr_ph":"выберите состояние или атрибут","decor.live_state":"Состояние","decor.backdrop":"Картинка-подложка","decor.backdrop_hint":"Тяните картинку — перемещение; угол — размер; верхнюю ручку — поворот. Shift меняет пропорции или снимает шаг угла.","decor.backdrop_reset":"Вернуть картинку","decor.backdrop_reset_done":"Картинка вернулась на своё место и в свой размер","marker.icon_auto":"Авто: {icon} (по правилам иконок; выберите свою, чтобы заменить)","marker.icon_pin_auto":"Закрепить","mode.plan_tip":"Редактор плана — геометрия дома: рисование и объединение/разделение комнат, привязка к зонам HA, двери, окна и ворота, карточки комнат, масштаб","mode.devices_tip":"Редактор устройств — всё про значки: перетаскивание, клик — настройка привязки/иконки/отображения, виртуальные устройства, правила иконок","mode.decor_tip":"Редактор подложки — чисто визуальный декор под планом: линии, прямоугольники, овалы и надписи, не реагирующие на клики","space.glow_enabled":"Свечение источников света","gs.glow_group":"Свечение источников света","gs.glow_base":"Темнота дома","gs.glow_light":"Цвет света по умолчанию / интенсивность","gs.wall_group":"Стены","gs.wall_fill":"Цвет заливки стен","gs.glow_radius":"Радиус свечения","gs.unit_m":"м","gs.unit_ft":"фут","marker.controls_label":"Управляет другими источниками света","marker.controls_hint":"При действии «Переключить» клик разом переключает все добавленные здесь источники. Собственная сущность маркера управляется напрямую и сюда не добавляется; чтобы считать её светом, включите флаг ниже.","marker.controls_filter":"Поиск ламп и выключателей…","info.controls":"Управляет","marker.glow_radius_label":"Радиус свечения","marker.glow_radius.help":"Задаёт радиус свечения в метрах или футах; пустое значение использует радиус из общих настроек.","marker.glow_radius.help.aria":"Подсказка: радиус свечения","markup.wallthick":"Толщина","markup.select":"Выбрать","title.markup_wallthick":"Толщина — клик по стене задаёт толщину. Пустое поле или ноль убирает толщину.","markup.hint_wallthick":"клик по стене · Esc закрывает без применения","wallthick.field":"Толщина","wallthick.unit_cm":"см","wallthick.unit_in":"дюйм","wallthick.apply_room":"Применить ко всем стенам комнаты","markup.draw_wall_title":"Толщина каждого нового сегмента стены комнаты (1–100 см). Общие стены сохраняют значение соседа.","toast.wallthick_pick":"Кликните по стене (не по открытой границе)","toast.wallthick_open":"У открытой границы нет толщины","toast.wallthick_set":"Толщина стены задана","toast.wallthick_cleared":"Толщина стены убрана","toast.physical_range":"Введите значение от 1 до {max} {unit}","toast.physical_angle":"Введите угол поворота от 0° до 90°, не включая 90°","toast.physical_limit":"В пространстве достигнут лимит объектов этого типа","markup.boundary_hint":"Выберите общую границу комнат или пунктирный участок","markup.boundary_hint_open":"Кликните начало открытого участка","markup.boundary_hint_second":"Укажите конец на выбранной границе · Esc — отмена","markup.boundary_hint_retry":"Укажите конец на выбранной границе","markup.boundary_hint_restore":"Кликните, чтобы восстановить стену","markup.boundary_hint_outer":"Открыть можно только общую границу двух комнат","markup.boundary_hint_blocked":"над границей находится перегородка, колонна или незавершённый контур","markup.boundary_hint_ambiguous":"Выберите участок чуть дальше от стыка","markup.boundary_cancel":"Отменить незавершённое действие «Граница»","toast.openwall_pick":"Инструмент работает с границами комнат","toast.openwall_shared_only":"Открыть можно только общую стену двух комнат","toast.openwall_short":"Слишком короткий отрезок — кликните дальше","toast.boundary_opened":"Участок границы открыт","toast.boundary_restored":"Стена на участке восстановлена","toast.boundary_same_edge":"Выберите конечную точку на выбранной стене","toast.boundary_ambiguous":"Выберите участок чуть дальше от стыка","toast.boundary_blocked":"Граница перекрыта независимой стеной. Сначала переместите или удалите её инструментом «Выбрать»","toast.delete_room_pick":"Кликните внутри комнаты, которую нужно удалить","toast.openwall_openings_removed":"Проёмы на виртуальном отрезке удалены","toast.openwall_opened":"Граница «{a}» ↔ «{b}» теперь открыта","toast.openwall_closed":"Граница «{a}» ↔ «{b}» снова закрыта","toast.opening_on_virtual":"Проёмы на виртуальной стене запрещены","marker.from_ha_option":"Выбрать из списка HA","marker.show_entities":"Отображать сущности","marker.show_entities_tip":"Добавляет в список не только устройства, но и все их сущности","marker.pick_ph":"Выберите устройство…","room.open_area":"Открыть зону в HA","view.volumetric":"Объёмный вид","view.flat":"Плоский вид","kiosk.title":"Размеры на этом экране","kiosk.hint":"Хранится только на этом устройстве — у каждого настенного планшета или ТВ свои удобные размеры.","kiosk.icon_scale":"Размер значков устройств","kiosk.font_scale":"Размер текста карточек комнат","editor.kiosk":"Режим настенного устройства (киоск)","editor.cycle":"Автосмена пространств каждые N секунд (киоск, 0 = выкл)","room.settings_title":"Настройки комнаты","room.settings_section":"Настройки комнаты (переопределяют пространство)","room.fill_label":"Заливка в ЭТОЙ комнате","fill.inherit":"Как у пространства","room.custom_fill_space":"Цвет пространства","room.custom_fill_own":"Цвет комнаты","room.temp_src_label":"Источник температуры","room.hum_src_label":"Источник влажности","room.src_average":"Средняя по датчикам комнаты (по умолчанию)","room.src_pick":"Конкретное устройство или сущность HA","room.src_ph":"Выберите источник…","toast.room_updated":"Комната обновлена","space.card_font":"Размер шрифта карточек комнат (всё пространство)","room.sizes_section":"Размеры шрифтов","room.name_scale":"Размер названия","room.label_scale":"Размер подписей","preview.room_name":"Гостиная","toast.cfg_reload_failed":"Не удалось перечитать план с сервера: {err}","room.settings_short":"Настройки комнаты","room.unnamed":"Комната без имени","marker.use_climate_temp":"Учитывать температуру устройства в комнате","marker.use_climate_temp_tip":"Добавляет current_temperature кондиционера или термостата в среднюю температуру комнаты. Внешний бейдж со значением настраивается отдельно ниже.","marker.value_badge_title":"Бейдж со значением","marker.value_badge.help":"Показывает одно выбранное значение рядом со значком. Не влияет на комнатные показатели, свет, Glow и действие по нажатию.","marker.value_badge.help.aria":"Подсказка: бейдж со значением устройства","marker.value_badge_enabled":"Отображать бейдж со значением","marker.value_badge_source":"Значение","marker.value_badge_source.help":"Выберите конкретное состояние, поддерживаемый атрибут или производное значение этого устройства.","marker.value_badge_source.help.aria":"Подсказка: источник значения бейджа","marker.value_badge_position":"Расположение","marker.value_badge_position.help":"Выбранная сторона сохраняется постоянно и не переворачивается автоматически у края плана.","marker.value_badge_position.help.aria":"Подсказка: расположение бейджа","marker.value_badge_right":"Справа","marker.value_badge_bottom":"Снизу","marker.value_badge_left":"Слева","marker.value_badge_top":"Сверху","marker.value_badge_empty":"У этого устройства нет доступных значений","marker.value_badge_static":"Статичный значок не показывает живые значения. Настройка сохранится.","marker.value_badge_missing":"Источник недоступен","marker.value_badge_missing_hint":"Сохранённый источник сейчас недоступен. Можно сохранить его, отключить бейдж или выбрать другой.","marker.value_badge_duplicate":"То же значение уже показано внутри значка.","marker.value_badge_state":"Состояние · {name}","marker.value_badge_marker_state":"Состояние источника света · {name}","marker.value_badge_lqi":"Качество Zigbee-сигнала (среднее)","marker.value_badge_attr_current_temperature":"Текущая температура · {name}","marker.value_badge_attr_temperature":"Целевая температура · {name}","marker.value_badge_attr_current_humidity":"Текущая влажность · {name}","marker.value_badge_attr_humidity":"Влажность · {name}","marker.value_badge_attr_current_position":"Положение · {name}","marker.value_badge_attr_percentage":"Скорость · {name}","marker.value_badge_attr_brightness":"Яркость · {name}","marker.value_badge_attr_volume_level":"Громкость · {name}","marker.value_badge_attr_battery_level":"Заряд батареи · {name}","marker.value_badge_attr_fan_speed":"Скорость вентилятора · {name}","marker.light_role_label":"Является источником света","marker.light_role.help":"Определяет, является ли сам маркер пространственным источником света. Привязанные выше лампы остаются независимыми источниками для комнаты.","marker.light_role.help.aria":"Подсказка: является ли устройство источником света","marker.light_role_auto_yes":"Автоматически (источник света)","marker.light_role_auto_no":"Автоматически (не источник света)","marker.light_role_always":"Всегда источник света","marker.light_role_never":"Никогда не источник света","marker.light_entity_label":"Ведущая сущность света","marker.light_entity.help":"Для составного устройства выберите сущность, состояние, цвет и яркость которой представляют этот источник на плане. Автоматический вариант сохраняет совместимость со старыми планами.","marker.light_entity.help.aria":"Подсказка: ведущая сущность света","marker.light_entity_auto":"Автоматически ({entity})","marker.light_entity_none":"нет управляемой сущности","marker.light_entity_missing":"Сохранённая сущность {entity} недоступна. Временно используется {fallback}; выбор восстановится, если сущность вернётся.","marker.glow_color_label":"Цвет и яркость свечения","marker.glow_mode.help":"Используйте данные источника, задайте только цвет или зафиксируйте цвет и яркость. Минимум — 1%; чтобы отключить источник, выберите «Никогда».","marker.glow_mode.help.aria":"Подсказка: цвет и яркость свечения","marker.glow_mode_auto":"Из источника","marker.glow_mode_color":"Задать цвет","marker.glow_mode_fixed":"Задать цвет и яркость","marker.glow_color":"Цвет свечения","marker.glow_brightness":"Яркость","marker.glow_disabled_never":"Настройки свечения недоступны: маркер явно исключён из источников света.","marker.glow_disabled_auto":"В автоматическом режиме у этого маркера не найден пространственный источник света.","marker.glow_disabled_no_entity":"Для пространственного источника нет активной управляемой сущности Home Assistant.","marker.glow_passive_hint":"У источника нет собственных данных Home Assistant. Задайте цвет и яркость вручную; радиус остаётся доступным.","marker.control_broken":"Сохранённый источник отсутствует или больше не отмечен как источник света","marker.control_missing_label":"Отсутствующий источник на плане","marker.control_passive":"пассивный источник","toast.marker_control_cycle":"Такая связь создаст циклическую цепочку управления светом.","toast.marker_binding_required":"Сначала выберите устройство Home Assistant, затем добавьте управляемый источник света.","confirm.unlock":"Открыть замок «{name}»?","toast.files_migrate_failed":"Не удалось перенести вложения к новой привязке, ссылки остались на старые файлы: {err}","space.pick_saved":"Уже загруженные","space.pick_saved_hint":"Планы, сохранённые на сервере, включая отцеплённые ранее","space.no_saved":"На сервере пока нет сохранённых планов.","space.loading":"Загрузка…","space.used_by":"используется: {list}","space.in_use":"План используется пространством — сначала отцепите его","btn.use":"Выбрать","confirm.delete_plan":"Удалить файл плана «{name}» с сервера? Действие необратимо.","toast.plans_list_failed":"Не удалось получить список планов: {err}","toast.plan_delete_failed":"Не удалось удалить план: {err}","marker.hide":"Скрыть","marker.hide_tip":"Устройство исчезнет с плана после сохранения, но продолжит участвовать в расчёте сигнала комнаты. Вернуть его можно через кнопку «Скрытые и деактивированные» в редакторе устройств.","marker.show":"Показать","marker.show_tip":"Устройство снова появится на плане после сохранения.","marker.hidden_ghost":"Устройство скрыто пользователем","marker.ha_disabled_device":"Устройство деактивировано в Home Assistant и скрыто с плана.","marker.ha_disabled_entity":"Сущность деактивирована в Home Assistant и скрыта с плана.","marker.ha_disabled_all_entities":"У устройства нет активных сущностей Home Assistant, поэтому оно скрыто с плана.","marker.ha_registry_limited":"Полный реестр Home Assistant недоступен этому пользователю. Неподтверждённый объект временно нельзя показывать или использовать.","marker.delete_tip":"Полностью удалить устройство с плана и из всех расчётов. Позже его можно добавить заново.","tap.run":"Запустить автоматизацию/скрипт/сцену","tap.cover":"Открыть/закрыть (шторы/жалюзи)","marker.run_target_label":"Что запускать","marker.run_search_ph":"Поиск: автоматизация, скрипт или сцена…","marker.run_target_gone":"Цель {id} не найдена — выберите заново","marker.tap_confirm":"Спрашивать подтверждение","marker.tap_confirm_tip":"Перед выполнением показать диалог подтверждения — защита от случайных нажатий.","marker.toggle_hint_single":"Цель: {name} ({id}).","marker.virtual_light_target":"Виртуальный свет: {name}.","marker.virtual_light_current":"Ручное состояние: {state} → {effect}.","marker.virtual_light_state_on":"включён","marker.virtual_light_state_off":"выключен","marker.toggle_hint_group":"Переключатся источники ({count}): {names}.","marker.toggle_hint_current":"Сейчас: {state} → {effect}.","marker.toggle_hint_group_current":"Сейчас включено {on} из {count} → {effect}.","marker.toggle_hint_skipped":"Не будут затронуты ({count}): {targets}.","marker.toggle_effect_turn_on":"включится","marker.toggle_effect_turn_off":"выключится","marker.toggle_effect_open":"откроется","marker.toggle_effect_close":"закроется","marker.toggle_effect_stop":"остановится","marker.toggle_effect_toggle":"состояние переключится","marker.toggle_skip_missing":"не найдено","marker.toggle_skip_ha_disabled":"деактивировано в HA","marker.toggle_skip_unavailable":"недоступно","marker.toggle_skip_unsupported":"переключение не поддерживается","marker.toggle_skip_secure":"заблокировано из соображений безопасности","marker.toggle_none_no_binding":"У устройства нет привязки, состояние переключать нечем. По нажатию ничего не произойдёт.","marker.toggle_none_no_actionable_entity":"У устройства нет состояния, которое можно переключить. По нажатию ничего не произойдёт.","marker.toggle_none_configured_targets_missing":"Настроенные цели сейчас недоступны. Собственная сущность устройства не будет подставлена вместо них.","marker.toggle_none_ha_disabled":"Цель деактивирована в Home Assistant и не может использоваться на плане.","marker.toggle_none_unavailable":"Цель сейчас недоступна. По нажатию ничего не произойдёт.","marker.toggle_none_unsupported":"Для этой сущности Home Assistant не предоставляет безопасного переключения. По нажатию ничего не произойдёт.","marker.toggle_none_secure":"Переключение замков, сигнализации и защитных ворот с плана заблокировано из соображений безопасности.","run.automation":"автоматизация","run.script":"скрипт","run.scene":"сцена","confirm.tap_run":"Запустить «{name}»?","confirm.tap_toggle":"Переключить «{name}»?","confirm.tap_cover":"Открыть/закрыть «{name}»?","toast.run_started":"Запущено: {name}","toast.run_target_missing":"Цель запуска не найдена — проверьте настройки устройства","toast.run_target_required":"Выберите автоматизацию, скрипт или сцену","toast.tap_target_changed":"Цель действия изменилась. Повторите попытку.","toast.virtual_light_toggle_failed":"Не удалось переключить виртуальный свет: {err}","toast.value_badge_source_required":"Выберите значение для бейджа","btn.run":"Выполнить","vac.section":"Робот-пылесос: живая позиция","vac.autocal":"Настроить автоматически","vac.live":"Живая позиция на плане","vac.trail":"Показывать путь робота","vac.cal_maps":"Откалиброваны карты: {maps}","vac.autocal_no_rooms":"Интеграция не отдаёт список комнат — откройте «Подогнать вручную»","vac.autocal_no_match":"Не совпали имена комнат (нужно ≥3 общих) — откройте «Подогнать вручную»","vac.autocal_done":"Готово: привязка по {rooms} комнатам. Запустите уборку и проверьте","vac.cal_need_pos":"Робот сейчас не отдаёт координаты — запустите уборку и поставьте на паузу","vac.cal_done":"Калибровка сохранена. Запустите уборку и проверьте","vac.cal_cancelled":"Калибровка отменена","vac.fit":"Подогнать вручную","vac.fit_hint":"Перетащите карту робота на место, растяните за уголки","vac.fit_rotate":"Повернуть 90°","vac.fit_mirror":"Отразить","vac.trail_never":"Не показывать никогда","vac.trail_cleaning":"Во время уборки","vac.trail_always":"Показывать всегда","gs.bg_group":"Фон сцены","gs.bg_color":"Цвет фона вокруг плана","gs.bg_default":"Как в теме","gs.bg_theme":"по умолчанию — из темы","gs.bg_mode":"Фон плана","gs.bg_static":"Статичный цвет","gs.bg_daynight":"Следует за Солнцем","gs.bg_daynight_hint":"Фон проходит рассвет, день, закат и ночь по данным sun.sun, а при их недоступности — по локальным часам. Компас для фона не нужен.","gs.sun_group":"Солнце","gs.sun_missing":"Сущность sun.sun не найдена — фон следует локальным часам, а оконные лучи недоступны.","gs.north":"Север на плане","gs.north_ph":"не задан","gs.north_hint":"Север нужен только для оконных лучей (шаг 1°, с Shift 15°); солнечный фон работает без него.","gs.north_clear":"Сбросить","gs.north_letter":"С","gs.sun_rays":"Солнце в окнах","gs.about_group":"О карточке","gs.about_version":"Houseplan Card v{v}","gs.about_github":"GitHub · документация и issues","gs.about_telegram":"Чат в Telegram","space.bg_color":"Цвет фона вокруг плана","space.bg_inherit":"Наследовать общий","space.bg_inherited":"наследуется из общих настроек","space.bg_mode":"Фон плана","space.north":"Север на плане (переопределение)","space.north_inherited":"наследуется: {v}","space.sun_rays":"Солнце в окнах","space.sun_inherit":"Наследовать общий","space.sun_on":"Вкл","space.sun_off":"Выкл","canvas.far_objects":"Объектов далеко от плана: {n}","canvas.show_far":"Показать","canvas.home_tip":"План там — нажмите, чтобы вписать","gs.grid_group":"Обслуживание планов","gs.grid_hint":"Обновляет модели данных, выравнивает элементы по сетке и объединяет лишние фрагменты стен. Перед записью будет показан точный отчёт.","gs.align_all":"Оптимизировать планы","gs.align_title":"Оптимизировать планы","gs.align_none":"Все планы уже используют актуальную и оптимальную модель данных.","gs.align_count":"Сдвинется элементов: {n} из {total}, максимум на {cm} см.","gs.align_where":"Наибольший сдвиг — в пространстве «{s}».","gs.align_turned":"Проёмов с исправлением угла: {n}.","gs.align_removed_drafts":"Схлопнувшиеся на сетке некорректные контуры удалены: {n}.","gs.optimize_changes":"Миграций модели: {m}; канонизировано планов: {c}; объединено отрезков реальных стен: {w}; виртуальных: {s}.","gs.optimize_glow_migration":"Старый Glow: пространств — {spaces} → без заливки данных + независимый Glow; комнат — {rooms} → наследуемая заливка + независимый Glow.","gs.align_warn":"Элементы, намеренно поставленные между узлами, будут сдвинуты. После операции доступна одна отмена — только до следующего изменения плана.","gs.align_run":"Оптимизировать","gs.align_done":"Планы оптимизированы: сдвинуто элементов — {n}, обслужено записей — {m}","gs.optimize_undo":"Отменить последнюю оптимизацию","gs.optimize_undone":"Последняя оптимизация отменена","decor.furniture":"Мебель","furn.title":"Библиотека мебели","furn.symbol":"Символ","furn.group_furniture":"Мебель","furn.group_appliance":"Техника","furn.group_sanitary":"Сантехника","furn.group_other":"Прочее","furn.width":"Ширина","furn.depth":"Глубина","furn.pick_hint":"Выберите символ и кликните по плану.","furn.place_hint":"Кликните по плану — предмет встанет к ближайшей стене. Shift — свободно.","furn.sym_sofa":"Диван","furn.sym_armchair":"Кресло","furn.sym_coffee_table":"Журнальный столик","furn.sym_table_dining":"Обеденный стол","furn.sym_table_round":"Круглый стол","furn.sym_chair":"Стул","furn.sym_desk":"Письменный стол","furn.sym_bed_double":"Двуспальная кровать","furn.sym_bed_single":"Односпальная кровать","furn.sym_nightstand":"Тумбочка","furn.sym_wardrobe":"Шкаф","furn.sym_bookshelf":"Стеллаж","furn.sym_fridge":"Холодильник","furn.sym_stove":"Плита","furn.sym_dishwasher":"Посудомоечная машина","furn.sym_washer":"Стиральная машина","furn.sym_dryer":"Сушильная машина","furn.sym_tv":"Телевизор","furn.sym_ac":"Кондиционер","furn.sym_water_heater":"Бойлер","furn.sym_toilet":"Унитаз","furn.sym_bathtub":"Ванна","furn.sym_shower":"Душ","furn.sym_sink":"Раковина","furn.sym_kitchen_sink":"Кухонная мойка","furn.sym_bidet":"Биде","furn.sym_stairs":"Лестница","furn.sym_fireplace":"Камин","furn.sym_plant":"Растение","furn.sym_rug":"Ковёр","common.yes":"Да","common.no":"Нет","vac.diag_source":"Источник","vac.diag_platform":"Интеграция","vac.diag_status":"Статус","vac.diag_position":"Позиция","vac.diag_rooms":"Комнаты","vac.diag_rooms_value":"{total} · совпало имён: {matched} · {readiness}","vac.autocal_ready":"автокалибровка доступна","vac.autocal_not_ready":"нужно 3 совпавших имени","vac.diag_path":"Путь интеграции","vac.diag_map":"ID карты","vac.source_none":"не выбран","vac.source_status_ok":"Готов","vac.source_status_missing":"Не найден","vac.source_status_disabled":"Деактивирован в Home Assistant","vac.source_status_unavailable":"Недоступен","vac.source_status_unverified":"Нельзя проверить с текущими правами","vac.source_status_unsupported":"Нет данных позиции","vac.source_status_none":"Нет источника","vac.source_banner_missing":"Сохранённый источник больше не существует. Он не заменён автоматически: выберите другой или восстановите его в Home Assistant.","vac.source_banner_disabled":"Сохранённый источник деактивирован в Home Assistant. Активируйте его там или выберите другой.","vac.source_banner_unverified":"Текущих прав Home Assistant недостаточно для проверки источника. Привязка сохранена и не будет заменена автоматически.","vac.choose_source":"Выбрать источник","vac.source_auto":"Автоматически","vac.source_auto_hint":"Использовать совместимую сущность этого устройства","vac.all_cameras":"Все камеры","vac.all_cameras_warn":"Камера может не отдавать данные робота. Выбирайте её только если это карта вашего пылесоса.","vac.all_cameras_empty":"Других сущностей камеры не найдено.","vac.platform_unknown":"интеграция неизвестна","vac.cap_position":"позиция","vac.cap_rooms_short":"комнаты","vac.cap_path":"путь","vac.cap_map":"ID карты","vac.cap_none":"данные робота не обнаружены","vac.xcme_hint":"Включите атрибуты Xiaomi Cloud Map Extractor:","vac.documentation":"Документация","vac.residual_title":"Проверьте автокалибровку","vac.residual_message":"Совпавшие комнаты расходятся максимум на {error}. Примените приблизительную калибровку, подгоните её вручную либо отмените без изменения сохранённых настроек.","vac.apply_proposal":"Применить","gs.backup_group":"Резервная копия и перенос","gs.backup_hint":"Скачайте переносимую JSON-копию или проверьте и импортируйте копию, созданную House Plan.","backup.export_open":"Экспорт","backup.import_open":"Импорт","backup.export_title":"Экспорт House Plan","backup.import_title":"Импорт House Plan","backup.export_hint":"Выберите, должна ли копия содержать всю конфигурацию House Plan или только текущее пространство.","backup.full":"Полная копия","backup.current_space":"Текущее пространство","backup.current_space_title":"Текущее пространство: {title}","backup.no_current_space":"Нет текущего пространства","backup.plan_only":"Только планировка","backup.plan_only_hint":"Сохранить комнаты, стены, проёмы, декор и позиции подписей комнат без устройств и привязок Home Assistant.","backup.plan_only_preview":"Файл содержит только планировку","backup.privacy_warning":"Архив сохраняет названия, идентификаторы Home Assistant и точные координаты. Внутренние планы и вложения указываются ссылками, но не вкладываются; текущие состояния и маршруты пылесосов не включаются.","backup.download":"Скачать JSON","backup.export_done":"Резервная копия скачана","backup.reading":"Проверяем резервную копию…","backup.revalidated":"После предпросмотра план изменился. Сводка обновлена — проверьте её и подтвердите ещё раз.","backup.error.unauthorized":"У вас нет прав на экспорт или импорт этого плана.","backup.error.not_ready":"House Plan ещё не готов. Повторите попытку через несколько секунд.","backup.error.too_large":"Размер резервной копии превышает 8 МиБ.","backup.error.invalid_json":"Выбранный файл не является корректным JSON.","backup.error.invalid_format":"Выбранный файл не является резервной копией House Plan.","backup.error.unsupported_export_version":"Эта версия формата резервной копии не поддерживается установленной версией.","backup.error.future_model":"Резервная копия создана в более новой версии модели данных House Plan.","backup.error.invalid_config":"Резервная копия содержит некорректную конфигурацию House Plan.","backup.error.invalid_layout":"Резервная копия содержит некорректные позиции объектов.","backup.error.invalid_content":"Резервная копия содержит некорректные или несогласованные ссылки на файлы.","backup.error.space_not_found":"Выбранное пространство больше не существует.","backup.error.capacity_exceeded":"Добавление этой копии превысит ограничения размера плана.","backup.error.preview_expired":"Время предпросмотра истекло. Выберите файл резервной копии заново.","backup.error.preview_owner_mismatch":"Этот предпросмотр принадлежит другому пользователю Home Assistant.","backup.error.conflict":"После предпросмотра план изменился. Проверьте обновлённую сводку.","backup.error.content_confirmation_required":"Подтвердите отсоединение недоступных локальных файлов.","backup.error.commit_failed":"Не удалось безопасно применить копию. Предыдущий план восстановлен или ожидает восстановления.","backup.error.missing_plan":"После предпросмотра исчез связанный файл плана. Проверьте копию ещё раз.","backup.error.missing_content":"После предпросмотра исчез связанный локальный файл. Проверьте копию ещё раз.","backup.error.marker_control_missing":"В резервной копии отсутствует связанный источник света на плане.","backup.error.marker_control_not_light":"Связанная цель больше не отмечена как источник света.","backup.error.marker_control_self":"Источник света не может управлять самим собой.","backup.error.marker_control_cycle":"Резервная копия содержит циклическую цепочку управления светом.","backup.error.duplicate_marker_control":"Резервная копия содержит повторяющуюся связь с источником света.","backup.error.no_backup":"Нет снимка импорта или оптимизации, который можно восстановить.","backup.same_source":"Создано на этом экземпляре Home Assistant","backup.foreign_source":"Создано на другом экземпляре Home Assistant","backup.created":"Создано: {value}","backup.versions":"Карточка {card}; интеграция {integration}; модель данных {model}","backup.count_spaces":"Пространств: {n}","backup.count_rooms":"Комнат: {n}","backup.count_walls":"Стен: {n}","backup.count_openings":"Проёмов: {n}","backup.count_decor":"Объектов декора: {n}","backup.count_markers":"Устройств: {n}","backup.count_layout":"Позиций: {n}","backup.bindings":"Привязки — устройства: {device}, сущности: {entity}, виртуальные: {virtual}; позиций без пространства: {legacy}","backup.binding_status":"Статус на целевом экземпляре — активных: {active}, деактивированных: {disabled}, отсутствующих: {missing}","backup.missing_areas":"На целевом экземпляре отсутствуют зоны: {areas}","backup.dropped_marker_links":"Связи с источниками света вне переносимого пространства пропущены: {n}.","backup.replace_warning":"Текущая конфигурация и расположение будут заменены. Загруженные файлы не удаляются. Одну отмену можно выполнить до следующего изменения плана.","backup.foreign_bookkeeping":"Служебные списки известных и новых устройств другого экземпляра импортированы не будут.","backup.final_name":"Название нового пространства","backup.target_settings":"Глобальные настройки целевого экземпляра не изменятся.","backup.duplicates":"Уже существующих привязок: {n}","backup.skip":"Пропустить повторяющиеся устройства","backup.virtual_copy":"Добавить безопасные статичные виртуальные копии","backup.content":"Связанное содержимое","backup.content_available":"доступно локально","backup.content_external":"внешняя ссылка","backup.content_detach_required":"будет отвязано","backup.confirm_detach":"Я понимаю, что недоступные внутренние планы и вложения будут отвязаны от импортируемой конфигурации.","backup.replace":"Заменить","backup.add":"Добавить пространство","backup.space_done":"Пространство импортировано: комнат — {rooms}, устройств — {markers}","backup.full_done":"Копия восстановлена: пространств — {spaces}, комнат — {rooms}, устройств — {markers}","backup.undo_import":"Отменить последний полный импорт","backup.import_undone":"Полный импорт отменён"}};function Uo(t,e){if(e&&e in Wo)return e;return(t?.locale?.language||t?.language||"en").toLowerCase().startsWith("ru")?"ru":"en"}function Go(t,e,i){return Ki(Wo[t][e]??jo[e]??e,i)}function Vo(t,e){const i=Wo[t][e]??Wo.en[e];return"string"==typeof i&&i.trim().length>0}class Ko extends ht{constructor(){super(...arguments),this.deviceName="",this._metadataConnection=null,this._demoUntil=0,this._demoTimer=0,this._demoGeneration=1,this._demoKind=null,this._reducedMotion=!1,this._onMotionChange=t=>{this._reducedMotion=t.matches,this.requestUpdate()},this._startShortDemo=()=>{const t=this.presentation;t&&"icon_ripple"===t.display&&"none"===t.pulse.kind&&(this._demoGeneration++,this._demoKind="short",this._demoUntil=Date.now()+3300,window.clearTimeout(this._demoTimer),this._demoTimer=window.setTimeout(()=>{this._demoUntil=0,this._demoTimer=0,this._demoKind=null,this.requestUpdate()},3330),this.requestUpdate())},this._toggleContinuousDemo=()=>{const t=this.presentation;t&&"icon_ripple"===t.display&&"none"===t.pulse.kind&&("continuous"===this._demoKind?this._clearDemo():(this._clearDemo(),this._demoGeneration++,this._demoKind="continuous",this._demoUntil=Number.POSITIVE_INFINITY),this.requestUpdate())}}connectedCallback(){super.connectedCallback(),this._motionMedia=window.matchMedia?.("(prefers-reduced-motion: reduce)"),this._reducedMotion=!!this._motionMedia?.matches,this._motionMedia?.addEventListener?.("change",this._onMotionChange),this._ensureMetadata()}disconnectedCallback(){this._metadataRelease?.(),this._metadataRelease=void 0,this._metadataConnection=null,this._clearDemo(),this._motionMedia?.removeEventListener?.("change",this._onMotionChange),this._motionMedia=void 0,super.disconnectedCallback()}willUpdate(t){t.has("hass")&&this._ensureMetadata();const e=this.presentation,i=t.get("presentation");t.has("presentation")&&i?.binding!==e?.binding&&this._clearDemo(),e&&this._demoKind&&("icon_ripple"!==e.display||"none"!==e.pulse.kind)&&this._clearDemo()}_ensureMetadata(){const t=this.hass?.connection||null;t&&t!==this._metadataConnection&&(this._metadataRelease?.(),this._metadataConnection=t,this._metadataRelease=Oo(this.hass,()=>this.requestUpdate()))}_clearDemo(){window.clearTimeout(this._demoTimer),this._demoTimer=0,this._demoUntil=0,this._demoKind=null}get _lang(){return Uo(this.hass)}_t(t,e){return Go(this._lang,t,e)}_providerText(t){return"houseplan"===t.domain?this._t("marker.preview.virtual_provider"):t.label.toLowerCase()===t.domain.toLowerCase()?t.domain:`${t.label} (${t.domain})`}_providers(){return this.presentation&&this.registry?function(t,e,i,s=No(t)){if("virtual"===e)return[{domain:"houseplan",label:"House Plan",confidence:"registry-owner"}];const n=e.indexOf(":");if(n<1)return[];const o=e.slice(0,n),r=e.slice(n+1);if("entity"===o){const e=Bo(t,r,i,s);return e?[e]:[]}if("device"!==o)return[];const a=i.devices?.[r]||t?.devices?.[r];if(!a)return[];const l=[a.config_entry_id,...Array.isArray(a.config_entries)?a.config_entries:[]].filter(t=>"string"==typeof t&&!!t).map(e=>{const i=s.configEntries[e];return Ho(t,i?.domain,"registry-owner",s,e)});if(l.some(Boolean))return qo(l);const c=[];for(const[e,n]of Object.entries(i.entities||{}))n?.device_id===r&&null==n?.disabled_by&&c.push(Bo(t,e,i,s));return c.some(Boolean)?qo(c):qo((Array.isArray(a.identifiers)?a.identifiers:[]).map(e=>{const i=Array.isArray(e)?e[0]:null;return Ho(t,i,"identifier-fallback",s)}))}(this.hass,this.presentation.binding,this.registry,No(this.hass)):[]}_providerSummary(t){if(!t.length)return this._t("marker.preview.unknown_provider");const e=t.slice(0,2).map(t=>this._providerText(t)),i=t.length-e.length;return e.join(", ")+(i>0?` · ${this._t("marker.preview.more_sources",{n:i})}`:"")}_sourceSummary(t){const e=t.visualSources;return e.length?1===e.length?`${e[0].name} · ${e[0].eid} · ${this._sourceProvider(e[0].eid)}`:2===e.length?e.map(t=>t.name).join(" · "):this._t("marker.preview.multiple_sources",{n:e.length}):this._t("marker.preview.no_source")}_stateSummary(t){if(!t.visualSources.length)return this._t("marker.preview.no_state");if(1===t.visualSources.length)return t.visualSources[0].stateText||this._t("marker.preview.no_state");const e=[...new Set(t.visualSources.map(t=>t.stateText).filter(Boolean))];return 1===e.length?e[0]:this._t("marker.preview.mixed_states")}_reason(t){return this._t(`marker.preview.reason.${t}`)}_sourceProvider(t){if(!this.registry)return this._t("marker.preview.unknown_provider");const e=Bo(this.hass,t,this.registry,No(this.hass));return e?this._providerText(e):this._t("marker.preview.unknown_provider")}render(){const t=this.presentation;if(!t)return G;const e=!!this._demoKind&&this._demoUntil>Date.now()&&"icon_ripple"===t.display&&"none"===t.pulse.kind,i=e?(()=>{const e=function(t,e,i,s,n=null){return{kind:e,reason:"short"===e?"event":"running",generation:Math.max(1,Math.trunc(i)),expiresAt:"short"===e?n:null,color:s?null:t.color,diameterScale:s?1:t.diameterScale,animated:!s,reducedMotionIndicator:s?"dot":"none"}}(t.pulse,this._demoKind,this._demoGeneration,this._reducedMotion,"short"===this._demoKind?this._demoUntil:null);return{...t,pulse:e,classes:[...t.classes.filter(t=>!t.startsWith("pulse-")),`pulse-${e.kind}`,...e.generation%2==0?["pulse-gen2"]:[]]}})():t,s=this._providers(),n="none"!==t.pulse.kind,o=i.scale*(i.pulse.animated?i.pulse.diameterScale:1);let r=-o/2,a=o/2,l=-o/2,c=o/2;if(i.valueBadge){const t=i.scale*Math.min(4,Math.max(.9,.29*i.valueBadge.text.length+.28)),e=.7*i.scale,s=.6*i.scale;"right"===i.valueBadge.position&&(a=Math.max(a,s+t)),"left"===i.valueBadge.position&&(r=Math.min(r,-s-t)),"top"===i.valueBadge.position&&(l=Math.min(l,-s-e)),"bottom"===i.valueBadge.position&&(c=Math.max(c,s+e))}if(null!=i.lqiText){const t=i.scale*("bottom"===i.valueBadge?.position?1.55:.95);c=Math.max(c,t)}const h=Math.min(1,2.35/Math.max(1,a-r),2.35/Math.max(1,c-l)),d=Math.round(100*h),u=[`left:calc(50% - ${54*((r+a)/2)*h}px)`,`top:calc(50% - ${54*((l+c)/2)*h}px)`,h<1?`transform:scale(${h})`:""].filter(Boolean).join(";"),p=["dev",...i.classes,"virtual"===i.binding?"virtual":"",i.haDisabled?"ghost ha-disabled":"",null!=i.valueText?"valonly":""].filter(Boolean).join(" "),_=Ro(i).join(";"),m=e?this._t("continuous"===this._demoKind?"marker.preview.demo_continuous_notice":"marker.preview.demo_short_notice"):this._reason(t.explanation.reason),g=[this.deviceName,this._providerSummary(s),this._stateSummary(t),m,t.valueFullText||"",fo(t.valueBadge)].filter(Boolean).join(". ");return j`
${this._t("marker.preview.title")} @@ -4293,7 +4297,7 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="da33c4f75cf2469e330b33f11fd5fa04d368 ${r} - `}const Xu={window:120,door:90,gate:300};function Zu(t){return Xu[t]}function Ju(t,e){const i=[t[0],t[1]],s=[e[0],e[1]];return function(t,e){return t[0]-e[0]||t[1]-e[1]}(i,s)<=0?{a:i,b:s}:{a:s,b:i}}function Qu(t){const e=new Map;return t.forEach((t,i)=>{if(!t.kind||t.open)return;const s=Ju(t.a,t.b);if(Math.hypot(s.b[0]-s.a[0],s.b[1]-s.a[1])<=1e-9)return;const n=function(t,e){const i=t=>(Math.abs(t)<=5e-10?0:Math.round(1e6*t)/1e6).toFixed(6);return`${i(t[0])},${i(t[1])}>${i(e[0])},${i(e[1])}`}(s.a,s.b),o=e.get(n);if(o)return o.physicalHalfWidth=Math.max(o.physicalHalfWidth,t.half||0),void(o.sourceOrder=Math.min(o.sourceOrder,i));e.set(n,{segmentKey:n,a:s.a,b:s.b,physicalHalfWidth:Math.max(0,t.half||0),sourceOrder:i})}),[...e.values()]}function tp(t,e){if(Math.abs(t.distance-e.distance)>1e-9)return t.distance-e.distance;if(Math.abs(t.perpendicular-e.perpendicular)>1e-9)return t.perpendicular-e.perpendicular;return(t.target.segmentKeye.target.segmentKey?1:0)||t.target.sourceOrder-e.target.sourceOrder}function ep(t){const e=Qu(t.intervals).map(e=>{const i=function(t,e){const i=e.b[0]-e.a[0],s=e.b[1]-e.a[1],n=Math.hypot(i,s),o=i/n,r=s/n,a=(t[0]-e.a[0])*o+(t[1]-e.a[1])*r,l=Math.max(0,Math.min(n,a)),c=e.a[0]+l*o,h=e.a[1]+l*r;return{along:l,length:n,x:c,y:h,distance:Math.hypot(t[0]-c,t[1]-h),perpendicular:Math.abs((t[0]-e.a[0])*r-(t[1]-e.a[1])*o)}}(t.pointer,e);return{target:e,...i,envelope:Math.max(t.baseTolerance,e.physicalHalfWidth+t.bodyPointerPadding)}}).filter(t=>t.distance<=t.envelope+1e-9).filter(e=>!function(t,e,i,s,n){const o=e.b[0]-e.a[0],r=e.b[1]-e.a[1],a=Math.hypot(o,r);if(!(a>1e-9))return!1;const l=o/a,c=r/a;for(const o of i){if(!o.open&&o.kind)continue;const i=Ju(o.a,o.b),r=i.b[0]-i.a[0],a=i.b[1]-i.a[1],h=Math.hypot(r,a);if(!(h>1e-9))continue;const d=r/h,u=a/h;if(Math.abs(l*u-c*d)>1e-6)continue;if(Math.abs((i.a[0]-e.a[0])*c-(i.a[1]-e.a[1])*l)>n)continue;const p=(t[0]-i.a[0])*d+(t[1]-i.a[1])*u,_=1e-9;if(!(p<=_||p>=h-_)&&Math.abs((t[0]-i.a[0])*u-(t[1]-i.a[1])*d)<=s+1e-9)return!0}return!1}(t.pointer,e.target,t.intervals,e.envelope,Math.max(1e-9,Math.min(t.baseTolerance,.04*t.gridStep)))).sort(tp),i=e[0];if(!i)return null;const{target:s,length:n}=i,o=s.b[0]-s.a[0],r=s.b[1]-s.a[1],a=o/n,l=r/n,c=Math.min(Math.max(0,t.renderedLength)/2,n/2);let h=i.along;const d=Math.max(t.gridStep,1e-9),u=n/2;h=Math.abs(h-u)<=d/2?u:Math.round(h/d)*d,h=Math.max(c,Math.min(n-c,h));const p=s.a[0]+a*h,_=s.a[1]+l*h,m=Math.max(0,t.renderedLength)/2,g=h-m,f=h+m,v=Math.max(0,g),y=Math.max(0,n-f),b=[s.a[0]+a*(g-v/2),s.a[1]+l*(g-v/2)],w=[s.a[0]+a*(f+y/2),s.a[1]+l*(f+y/2)];let k=180*Math.atan2(r,o)/Math.PI;k>=90?k-=180:k<-90&&(k+=180);const $=Math.abs(h-u)<=1e-9;return{presetRevision:t.preset.revision,geometryRevision:t.geometryRevision,pointer:[t.pointer[0],t.pointer[1]],type:t.preset.type,lengthCm:t.preset.lengthCm,flipH:t.preset.flipH,flipV:t.preset.flipV,x:p,y:_,angle:k,renderedLength:t.renderedLength,target:s,measure:{labels:[{distance:v,midpoint:b},{distance:y,midpoint:w}],guide:$?{x:p,y:_,angle:k}:null}}}function ip(t,e,i,s,n=1e-6){return t.presetRevision===i&&t.geometryRevision===s&&Math.hypot(t.pointer[0]-e[0],t.pointer[1]-e[1])<=n}const sp="1.64.0",np={badge:"display.badge",icon_ripple:"display.icon_ripple",value:"display.value",static_icon:"display.static_icon"},op={badge:"marker.display_hint_badge",icon_ripple:"marker.display_hint_icon_ripple",value:"marker.display_hint_value",static_icon:"marker.display_hint_static_icon"},rp=2e3,ap=1e3,lp=1500,cp=t=>{const e=String(t??"").trim().replace(",",".");if(!/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)$/.test(e))return null;const i=Number(e);return Number.isFinite(i)?i:null},hp=(t,e)=>{if(!t.has(e))return{hit:!1};const i=t.get(e);return t.delete(e),t.set(e,i),{hit:!0,value:i}},dp=(t,e,i,s)=>{for(t.delete(e),t.set(e,i);t.size>s;){const e=t.keys().next().value;if(void 0===e)break;t.delete(e)}},up=new Map;let pp=0;const _p=t=>`${window.innerWidth}x${window.innerHeight}|${location.pathname}|${JSON.stringify(t??{})}`;let mp=1e4;const gp="houseplan_card_layout_v1",fp="houseplan_card_cfg_v1",vp="houseplan_card_zoom_v1",yp="houseplan_card_nav_v1",bp="houseplan_card_kiosk_v1",wp="houseplan_card_view_v1",kp=1e3,$p=[[0,1],[45,.88],[70,.62],[86,.32],[100,0]],xp=(t,e)=>{const i=Math.min(t.x,e.x),s=Math.min(t.y,e.y);return{x:i,y:s,w:Math.max(t.x+t.w,e.x+e.w)-i,h:Math.max(t.y+t.h,e.y+e.h)-s}},Sp=new Set(["select","draw","partition","column","merge","split","resize","opening","boundary","wallthick","delroom"]),Mp=(t,e,i)=>{const s=new Event(e,{bubbles:!0,composed:!0});s.detail=i??{},t.dispatchEvent(s)},Cp=t=>{history.pushState(null,"",t),Mp(window,"location-changed",{replace:!1})},Dp=(t,e)=>{let i,s=null;const n=(...n)=>{clearTimeout(i),s=n,i=window.setTimeout(()=>{i=void 0;const e=s;s=null,e&&t(...e)},e)};return n.flush=()=>{if(void 0===i)return;clearTimeout(i),i=void 0;const e=s;s=null,e&&t(...e)},n.pending=()=>void 0!==i,n},Tp=t=>{try{t.target?.setPointerCapture?.(t.pointerId)}catch{}};class Pp extends ht{constructor(){super(...arguments),this._space="f1",this._layout={},this._serverStorage=!1,this._loadOk=!1,this._serverCanWrite=null,this._loading=!1,this._loadTries=0,this._serverCfg=null,this._cfgRev=0,this._cfgContentFingerprint="",this._unsubCfg=null,this._unsubLayout=null,this._unsubVirtual=null,this._liveSyncAttempt=null,this._liveSyncGeneration=0,this._liveSyncConnection=null,this._layoutRev=0,this._layoutContentFingerprint="",this._virtualLights=rn(null),this._canOptimizeUndo=!1,this._undoKind=null,this._devices=[],this._regSignature="",this._defPos={},this._newSyncKey="",this._tip=null,this._hoverRoom=null,this._selId=null,this._toast="",this._mode="view",this._pendingNavMode=null,this._decorTool="select",this._decorStyle={...jr},this._decorDraft=null,this._decorMove=null,this._decorSel=null,this._decorEraseConfirm=null,this._decorTextDialog=null,this._decorShapeDialog=null,this._backdropDialog=null,this._decorTextSelection={start:0,end:0},this._furnPalette=null,this._dtBox=null,this._dtDrag=null,this._bdDrag=null,this._slide="",this._reducedMotion=!1,this._onMotionChange=t=>{this._reducedMotion=t.matches,t.matches&&this._modeTransitionPreparing?this._modeTransitionForceAtomic=!0:t.matches&&this._modeTransition.active&&this._cancelModeTransition(!0),this.requestUpdate()},this._editorChromeMode="plan",this._modeTransitionVisual=null,this._modeTransitionPreparing=!1,this._modeTransitionForceAtomic=!1,this._modeTransitionRequest=0,this._modeTransitionTargetZoom=1,this._modeTransitionEditorCamera=null,this._modeTransition=new hu({frame:t=>this._applyModeTransitionFrame(t),settled:t=>this._settleModeTransition(t)}),this._editorSecondary=new Od({root:()=>this.renderRoot,requestUpdate:()=>this.requestUpdate(),updateComplete:()=>this.updateComplete,clearTip:()=>{this._tip=null}}),this._editorSecondaryCopy={groupActive:(t,e)=>this._t("editor.group_active",{group:t,item:e}),openGroup:t=>this._t("editor.open_group",{group:t}),disabledAction:(t,e)=>this._t("editor.disabled_action",{action:t,reason:e})},this._tool="draw",this._geometryHistory=new Zd(50),this._wallDialog=null,this._drawWallField=null,this._activeDraftId=null,this._resumeDraftBySpace={},this._physicalSel=null,this._physicalDialog=null,this._physicalDrag=null,this._physicalRotate=null,this._physicalLastTap=null,this._physicalPickCycle=null,this._wallUnionCache=null,this._isoGeometryCache=new Map,this._isoFallback=new Set,this._openingTunnelCache=null,this._openingWallIndexCache=new Map,this._openingPlacementIntervalsCache=null,this._planSnapGeometryCache=null,this._physicalBodiesCache=null,this._cleanFloorCache=new Map,this._glowClipCache=new Map,this._lightBarrierCache=null,this._glowFeatherUnits=null,this._glowRenderedSources=new Map,this._glowLastAppearance=new Map,this._glowEnteringSources=new Set,this._glowEnterRafs=new Map,this._glowFadeTimers=new Map,this._glowFeatherSuspendUntil=0,this._glowFeatherResumeTimer=0,this._glowSourceSeq=0,this._glowScreenBlend=!1,this._duplicateColumnId=null,this._duplicateColumnTimer=0,this._rszSel=null,this._rszDrag=null,this._rszPreview=null,this._rszLive=null,this._path=[],this._cursorPt=null,this._planSnapHover=null,this._mergeSel=null,this._openingPreset=null,this._openingPresetRevision=0,this._openingHoverCandidate=null,this._openingDialog=null,this._openingInfo=null,this._opDrag=null,this._opMeasure=null,this._mergeDialog=null,this._openWallAnchor=null,this._boundaryPointerType="mouse",this._boundaryRestoreGuard=null,this._boundaryTargetMemo=null,this._boundaryPreviewMemo=null,this._splitSel=null,this._pendingSplit=null,this._areaSel="",this._nameSel="",this._roomDialog=!1,this._roomEditId=null,this._roomFill="",this._roomCustomFill=null,this._roomTempSrc="",this._roomHumSrc="",this._roomSrcOpen=null,this._roomSrcFilter="",this._roomNameScale=1,this._roomLabelScale=1,this._zoom=1,this._view=null,this._zoomBySpace={},this._viewModeSnap=null,this._pointers=new Map,this._panStart=null,this._panLock=null,this._pinchStart=null,this._suppressClick=!1,this._touchContacts=new Map,this._touchSequenceMultitouch=!1,this._touchClickBlockUntil=0,this._connectedPath="",this._routeDepartureHandled=!1,this._onLocationChanged=()=>{this._connectedPath&&location.pathname!==this._connectedPath?this._leaveCardRoute():location.pathname===this._connectedPath&&(this._routeDepartureHandled=!1)},this._touchGestureGuard={capture:!0,handleEvent:t=>this._guardTouchGesture(t)},this._hdrH=118,this._booting=!0,this._bootFading=!1,this._bootSettling=!1,this._bootSettleRaf=0,this._bootLastH=-1,this._bootStart=0,this._bootLastChange=0,this._bootSoft=!1,this._tapConfirm=null,this._onboardingShown=!1,this._rulesDialog=null,this._alignDialog=null,this._settingsDialog=null,this._backupExportDialog=null,this._backupImportDialog=null,this._sunRaysCache=null,this._dayCycleTimer=0,this._dayCycleClockKey="",this._compassDrag=!1,this._importDialog=null,this._importQueue=[],this._importTotal=0,this._rulesCompiledSrc="",this._infoCard=null,this._nativeMoreInfoEntity=null,this._markerDialog=null,this._spaceDialog=null,this._keyHandler=t=>this._onKey(t),this._warmVp=null,this._warmVpArmed=!1,this._warmLongReturn=!1,this._warmRevivePending=!1,this._warmGen=++pp,this._warmKey=null,this._warmSlot=null,this._hashApplied=!1,this._navApplied=!1,this._labs={active:Object.freeze([]),space:""},this._viewPreference={},this._renderProjection="flat",this._kioskScale={icon:1,font:1},this._kioskDialog=!1,this._activityRt=new Map,this._vacRt=new Map,this._vacViewKey="",this._vacLastView=null,this._vacRaf=0,this._vacSrvTrails={},this._vacJumpOnce=!1,this._continuity=this._newContinuityController(),this._continuityHistory=[],this._continuityEpoch=0,this._continuityDataReady=!0,this._continuityPaintToken=-1,this._continuityDisposed=!1,this._renderSnapshotAt=Date.now(),this._hassSequence=0,this._visibleDeviceSnapshot=null,this._candidateDeviceSnapshot=null,this._stagedDeviceSnapshotToken=-1,this._capturedSnapshotSequence=-1,this._capturedSnapshotDevices=null,this._capturedSnapshotLayout=null,this._capturedSnapshotActivity="",this._capturedSnapshotConfigEpoch=-1,this._capturedSnapshotVirtual="",this._lastValidStageSize=null,this._pendingRefitSize=null,this._refitRaf=0,this._pageVisibility=t=>{if(this._continuity.visibility(t),this._dayCycleVisibility(t),"hidden"!==t.kind){if(this._vacJumpOnce=!0,!t.long){const t=Date.now();let e=!1;for(const i of this._activityRt.values())!i.flashKind||(i.expiresAt||i.flashTs+Cd)>t||(i.flashTs=0,i.flashKind=null,i.expiresAt=0,e=!0);return void(e&&this.requestUpdate())}Date.now()-this._renderSnapshotAt>1e3&&this._continuity.note("device-snapshot-stale"),this._continuityDataReady=!1,this._continuityPaintToken=-1,this._resumeSettling=!0,this._loading?this.requestUpdate():this._loadFromServer()}else this._modeTransitionPreparing?this._modeTransitionForceAtomic=!0:this._cancelModeTransition(!0)},this._resumeSettling=!1,this._viewportInvalidAt=0,this._vacFit=null,this._vacAllCamerasFor=null,this._vacAllCameraCache=null,this._vacCalConfirm=null,this._kioskDots=!1,this._cyclePausedUntil=0,this._swipeStart=null,this._lastTap=0,this._onLabsSnapshot=t=>{const e=this._effectiveProjection();this._labs=t;const i=this._effectiveProjection();this._convertProjectionView(e,i),this.requestUpdate()},this._onHashChange=()=>{const t=this._hashSpace();t&&this._model.find(e=>e.id===t)&&t!==this._space&&(this._activeDraftId&&(this._resumeDraftBySpace[this._space]=this._activeDraftId),this._space=t,this._selId=null,this._path=[],this._clearPlanSnapHover(),this._clearOpeningPlacement(!0),this._tool="draw",this._openWallAnchor=null,this._boundaryRestoreGuard=null,this._activeDraftId=null,this._draftSegmentCms=[],this._closingWallCm=null,"plan"===this._mode&&"draw"===this._tool&&this._resumeLastDraft(),this._restoreZoom(),this.requestUpdate())},this._drag=null,this._rlResize=null,this._holdFired=!1,this._retryContinuity=()=>{this._continuityDataReady=!1,this._continuityPaintToken=-1,this._continuity.retry(this._continuity.recoveryReason||"plan"),this._loading||this._loadFromServer()},this._cfgEpoch=0,this._modelCache=null,this._decorSnapCache=null,this._markerPreviewMemo=null,this._markerPreviewDevicesMemo=null,this._showHidden=!1,this._connHooked=null,this._connectionWasLost=!1,this._haRegistryConnection=null,this._haRegistryRev=-1,this._haBindingCacheKey="",this._planHassMemo=null,this._onHaRegistryUpdate=()=>{const t=xn(this.hass);t.revision===this._haRegistryRev&&this._devices.length||(this._haRegistryRev=t.revision,this._planHassMemo=null,this._regSignature="",this._maybeRebuildDevices(),this.requestUpdate())},this._onConnReady=()=>{if(this._loadTries=0,clearTimeout(this._loadRetryTimer),this._loadRetryTimer=void 0,function(t){const e=vn(t);e&&(wn(t,e),kn(t,e))}(this.hass),this._saveConfigDebounced.pending()&&this._saveConfigDebounced.flush(),this._cfgWriting)return clearTimeout(this._reloadRetry),void(this._reloadRetry=window.setTimeout(this._onConnReady,400));!this._connectionWasLost&&this._continuity.hasCompleteFrame?this._beginContinuityCandidate("connection-ready",!1,"plan"):(this._continuityDataReady=!1,this._continuityPaintToken=-1),this._loading||this._loadFromServer()},this._onConnLost=()=>{this._booting&&!this._continuity.hasCompleteFrame||(this._connectionWasLost=!0,this._continuityDataReady=!1,this._continuityPaintToken=-1,this._continuity.connectionLost())},this._signer=new Wc(()=>this.requestUpdate()),this._dirtyPos=new Set,this._sentPos=new Map,this._persistLayout=Dp(()=>{if(this._serverStorage){const t=[...this._dirtyPos];this._dirtyPos.clear();for(const e of t){const t=this._layout[e];t&&(this._sentPos.set(e,t),this.hass.callWS({type:"houseplan/layout/update",device_id:e,pos:t}).then(t=>this._noteLayoutRev(t)).catch(t=>this._showToast(this._t("toast.pos_save_failed",{err:this._errText(t)}))).finally(()=>{this._sentPos.get(e)===t&&this._sentPos.delete(e)}))}this._cacheSnapshot()}else localStorage.setItem(gp,JSON.stringify(this._layout))},600),this._frame=null,this._showFar=!1,this._writesPending=0,this._writeChain=Promise.resolve(),this._undoGeometry=()=>{if(this._cancelBoundaryAnchor())return;if(this._physicalDrag||this._physicalRotate)return void this._cancelPhysicalGesture();if(this._decorDraft)return this._decorDraft=null,void this.requestUpdate();if(this._decorMove||this._dtDrag||this._bdDrag)return void this._cancelDecorGesture();if(this._rszDrag)return void this._rszCancelDrag();const t=this._geometryHistory.undo();t&&(this._applyGeometryState(t.before)?this._showToast(this._t("history.undone",{name:t.name})):this._geometryHistory.clear())},this._redoGeometry=()=>{if(this._cancelBoundaryAnchor())return;if(this._physicalDrag||this._physicalRotate)return void this._cancelPhysicalGesture();if(this._decorDraft)return this._decorDraft=null,void this.requestUpdate();if(this._decorMove||this._dtDrag||this._bdDrag)return void this._cancelDecorGesture();if(this._rszDrag)return void this._rszCancelDrag();const t=this._geometryHistory.redo();t&&(this._applyGeometryState(t.after)?this._showToast(this._t("history.redone",{name:t.name})):this._geometryHistory.clear())},this._saveConfigDebounced=Dp(()=>{this._serverCfg&&this._writeConfig().catch(t=>{"conflict"===t?.code?(this._showToast(this._t("toast.conflict")),this._cancelPath(),this._reloadConfigOnly(!0)):this._showToast(this._t("toast.cfg_save_failed",{err:this._errText(t)}))})},500),this._draftSegmentCms=[],this._closingWallCm=null,this._savePhysicalDialog=()=>{const t=this._physicalDialog,e=this._curSpaceCfg;if(!t||!e)return;const i=cp(t.cm);if(null==i)return void this._showPhysicalRange("column"===t.kind?ll:100);const s=this._imperial?2.54*i:i,n="column"===t.kind?ll:100;if(!Number.isFinite(s)||s<1||s>n)return void this._showPhysicalRange(n);if("column"===t.kind){const e=this._spaceModel().wall_columns.find(e=>e.id===t.id);if(!e)return;const i=cp(t.angle||"0");if("circle"!==t.shape&&(null==i||i<0||i>=90))return void this._showToast(this._t("toast.physical_angle"));const n="circle"===t.shape?{id:t.id,shape:"circle",center:e.center,cm:s}:{id:t.id,shape:"square",center:e.center,cm:s,angle:i};if(this._spaceModel().wall_columns.some(e=>e.id!==t.id&&Sl(e,n,.02*this._gridPitch)))return void this._showToast(this._t("toast.column_duplicate"))}const o=this._geometrySnapshot();if("partition"===t.kind){const i=(e.partitions||[]).find(e=>e.id===t.id);i&&(i.cm=s)}else if("column"===t.kind){const i=(e.wall_columns||[]).find(e=>e.id===t.id);i&&(i.cm=cl(s),i.shape="circle"===t.shape?"circle":"square","square"===i.shape?i.angle=cp(t.angle||"0"):delete i.angle)}else{const i=(e.room_drafts||[]).find(e=>e.id===t.id);i?.segments?.[t.segment||0]&&(i.segments[t.segment||0].cm=s)}this._recordGeometry(this._t("history.physical_edit"),o),this._physicalDialog=null,this._saveConfig()},this._deletePhysicalSelection=()=>{const t=this._physicalSel,e=this._curSpaceCfg;if(!t||!e)return;if("draft"===t.kind)return void this._deleteDraftWhole();const i=this._geometrySnapshot(),s="partition"===t.kind?"partitions":"column"===t.kind?"wall_columns":"room_drafts";e[s]=(e[s]||[]).filter(e=>e.id!==t.id),e[s].length||delete e[s],this._activeDraftId===t.id&&this._cancelPath(),this._physicalSel=null,this._physicalDialog=null,this._recordGeometry(this._t("history.physical_delete"),i),this._saveConfig()},this._deleteDraftWhole=()=>{const t="draft"===this._physicalDialog?.kind?this._physicalDialog.id:"draft"===this._physicalSel?.kind?this._physicalSel.id:null,e=this._curSpaceCfg;if(!t||!e||!confirm(this._t("confirm.delete_draft")))return;const i=this._geometrySnapshot();e.room_drafts=(e.room_drafts||[]).filter(e=>e.id!==t),e.room_drafts.length||delete e.room_drafts,this._activeDraftId===t&&this._cancelPath(),this._physicalSel=null,this._physicalDialog=null,this._recordGeometry(this._t("history.physical_delete"),i),this._saveConfig()},this._deleteDraftSegment=()=>{const t=this._physicalDialog,e=this._curSpaceCfg;if(!t||"draft"!==t.kind||!e)return;if(!confirm(this._t("confirm.delete_draft_segment")))return;const i=(e.room_drafts||[]).findIndex(e=>e.id===t.id);if(i<0)return;const s=e.room_drafts[i],n=Math.max(0,Math.min(s.segments.length-1,t.segment||0)),o=[],r=s.points.slice(0,n+1),a=s.points.slice(n+1);if(r.length>=2&&o.push({id:s.id,points:r,segments:s.segments.slice(0,n)}),a.length>=2&&o.push({id:o.length?`${s.id}-${Date.now().toString(36)}`:s.id,points:a,segments:s.segments.slice(n+1)}),2===o.length&&e.room_drafts.length>=200)return void this._showToast(this._t("toast.physical_limit"));const l=this._geometrySnapshot();e.room_drafts.splice(i,1,...o),e.room_drafts.length||delete e.room_drafts,this._activeDraftId===s.id&&this._cancelPath(),this._physicalDialog=null,this._physicalSel=o.length?{kind:"draft",id:o[0].id}:null,this._recordGeometry(this._t("history.draft_segment_delete"),l),this._saveConfig()},this._keepClosedAsPartitions=()=>{if(!this._contourClosed||this._pendingSplit||!this._curSpaceCfg)return;const t=this._curSpaceCfg,e=this._path.slice(0,-1);if((t.partitions||[]).length+e.length>2e3)return void this._showToast(this._t("toast.physical_limit"));const i=this._geometrySnapshot(),s=[...this._draftSegmentCms,this._closingWallCm||this._drawWallCm||ea];t.partitions||=[];const n=Date.now().toString(36);for(let i=0;it.id!==this._activeDraftId),t.room_drafts.length||delete t.room_drafts),this._recordGeometry(this._t("history.contour_to_partitions"),i),this._saveConfig(),this._roomDialog=!1,this._path=[],delete this._resumeDraftBySpace[this._space],this._activeDraftId=null,this._draftSegmentCms=[],this._closingWallCm=null},this._toggleServerPlans=async()=>{const t=this._spaceDialog;if(t)if(t.pickSaved)this._spaceDialog={...t,pickSaved:!1};else{this._spaceDialog={...t,pickSaved:!0,savedBusy:!0};try{const t=await this.hass.callWS({type:"houseplan/plans/list"}),e=this._spaceDialog;e&&(this._spaceDialog={...e,saved:t?.plans||[],savedBusy:!1})}catch(t){const e=this._spaceDialog;e&&(this._spaceDialog={...e,saved:[],savedBusy:!1}),this._showToast(this._t("toast.plans_list_failed",{err:this._errText(t)}))}}},this._aspectJob=null,this._dayCycleTick=()=>{if(!this.isConnected||"hidden"===this.ownerDocument.visibilityState)return;const t=this._dayCycleState();if(!t)return this._dayCycleTimer&&(clearInterval(this._dayCycleTimer),this._dayCycleTimer=0),void(this._dayCycleClockKey="");const e=Cr(t);e!==this._dayCycleClockKey&&(this._dayCycleClockKey=e,this.requestUpdate())},this._sunShown=!1,this._sunOut=!1,this._sunOutTimer=0,this._openSettingsDialog=()=>{if(!this._norm)return;const t=this._glowRadiusCm,e=this._imperial?Math.round(t/30.48*10)/10:Math.round(t)/100;this._settingsDialog={colors:JSON.parse(JSON.stringify(this._fillColors)),glowRadius:e,bgColor:es(this._settings,{bgColor:null})||null,northDeg:Fr(this._settings,{}),bgMode:Or(this._settings,{}),sunRays:Nr(this._settings,{}),busy:!1}},this._openAlignDialog=()=>{if(!this._norm||!this._serverCfg)return;const t=this._serverCfg.spaces||[],e=Xd(this._serverCfg,this._layout||{}),i=Math.ceil(10*e.report.maxShiftCm)/10,s=t.find(t=>null!=t?.id&&String(t.id)===e.report.maxSpace),n=t.length>1&&s?String(s.title||s.id):"";this._alignDialog={report:e.report,config:e.config,layout:e.layout,cm:i,where:n,changed:e.changed,busy:!1}},this._optimizeUndoBusy=!1,this._openBackupExport=()=>{this._settingsDialog=null,this._backupExportDialog={kind:"full",busy:!1,error:""}},this._openRulesDialog=()=>{if(!this._norm)return;const t=this._settings.icon_rules,e=(t&&t.length?t:jt).map(t=>({...t}));this._rulesDialog={rules:e,test:"",busy:!1}},this._climateCache=null,this._gearPtCache=new WeakMap}get _canEdit(){return!!this._norm&&(!0===this._serverCanWrite||!1!==this._serverCanWrite&&!0===this.hass?.user?.is_admin)}get _kiosk(){return!!this._config?.kiosk}_showKioskDots(){this._kioskDots=!0,clearTimeout(this._kioskDotsTimer),this._kioskDotsTimer=window.setTimeout(()=>this._kioskDots=!1,2500)}get _modeTransitionBusy(){return this._modeTransitionPreparing||this._modeTransition.active}_cssColor(t,e){const i=String(t||"").trim();if(!i)return e;const s=document.createElement("span");s.style.cssText=`position:absolute;visibility:hidden;color:${i}`,this.renderRoot.append(s);const n=getComputedStyle(s).color||e;return s.remove(),n}_currentModeVisual(t=this._mode){const e=this._modeTransition.presented||this._modeTransitionVisual;if(e)return{...e,viewport:{...e.viewport,viewBox:{...e.viewport.viewBox}}};const i=this._stageEl;if(!i||i.clientWidth<=0||i.clientHeight<=0)return null;const s=this.renderRoot.querySelector(".editorchrome"),n=this._viewOr(this._baseVb()),o=this.renderRoot.querySelector(".hp-paper"),r=this.renderRoot.querySelector(".hp-backdrop"),a=this.renderRoot.querySelector(".zoomwrap"),l=a?getComputedStyle(a).filter:"",c=Number(/brightness\(([^)]+)\)/.exec(l)?.[1]);return{presentedMode:t,editorChromeHeight:"view"===t?0:s?.getBoundingClientRect().height||0,stageWidth:i.clientWidth,stageHeight:i.clientHeight,viewport:ou(n,i.clientWidth),stageColor:getComputedStyle(i).backgroundColor||"rgb(255, 255, 255)",paperColor:o?getComputedStyle(o).fill:"rgb(255, 255, 255)",sceneBrightness:Number.isFinite(c)?c:1,architectureOpacity:"decor"===t?.35:1,backdropOpacity:r&&Number(getComputedStyle(r).opacity)||1,viewWeight:"view"===t?1:0,editorWeight:"view"===t?0:1,toolbarContentOpacity:"view"===t?0:1}}_viewForModeTarget(t,e,i,s,n){const o=Di(this._baseVb(),s/Math.max(1,n)),r=Math.min(Pp.ZOOM_MAX,Math.max(Pp.ZOOM_MIN,t)),a=o.w/r,l=o.h/r,c=e??o.x+o.w/2,h=i??o.y+o.h/2;return this._clampView({x:c-a/2,y:h-l/2,w:a,h:l},o)}_targetStageColor(t){return"view"!==t?"rgb(255, 255, 255)":this._cssColor(this._stageBg(this._spaceDisplayForRender()),this._cssColor("var(--ha-card-background, var(--card-background-color, #111))","rgb(17, 17, 17)"))}_targetPaperColor(t){return"view"===t&&this._spaceModel().bg?this._cssColor("var(--ha-card-background, var(--card-background-color, #111))","rgb(17, 17, 17)"):"rgb(255, 255, 255)"}_targetBrightness(t){return 1}_applyModeTransitionFrame(t){t.targetMode===this._mode&&(this._modeTransitionVisual=t.presented,this._view={...t.presented.viewport.viewBox},this.requestUpdate())}_settleModeTransition(t){if(t.targetMode!==this._mode)return;const e=this._modeTransitionRequest;this._view={...t.to.viewport.viewBox},this._zoom=this._modeTransitionTargetZoom,this._modeTransitionVisual=null,this._modeTransitionPreparing=!1,this._lastValidStageSize=[t.to.stageWidth,t.to.stageHeight],"view"===this._mode&&(this._saveZoom(),this._viewModeSnap=null,this._modeTransitionEditorCamera=null),this.requestUpdate(),this.updateComplete.then(()=>{if(!this.isConnected||e!==this._modeTransitionRequest||this._modeTransitionBusy||t.targetMode!==this._mode)return;const i=this._stageEl;if(i&&i.clientWidth>0&&i.clientHeight>0&&(Math.abs(i.clientWidth-t.to.stageWidth)>.5||Math.abs(i.clientHeight-t.to.stageHeight)>.5)){const t=this._view;this._lastValidStageSize=[i.clientWidth,i.clientHeight],this._applyView(this._zoom,t?t.x+t.w/2:void 0,t?t.y+t.h/2:void 0),this.requestUpdate()}const s=this.renderRoot.activeElement;(!s||!s.isConnected||!!s.closest?.(".editorchrome, .stage"))&&this.renderRoot.querySelector(".modetab.active")?.focus?.({preventScroll:!0})})}_cancelModeTransition(t=!0){const e=!!this._modeTransition.state;this._modeTransitionRequest++,this._modeTransitionPreparing=!1,this._modeTransitionForceAtomic=!1,this._modeTransition.cancel(t),t&&e||(this._modeTransitionVisual=null)}_adoptMode(t){this._cancelModeTransition(!1),this._mode=t,"view"!==t&&(this._editorChromeMode=t)}_commitViewModeAtomic(t,e,i,s){if(this._modeTransitionPreparing=!1,this._modeTransitionVisual=null,this._modeTransitionForceAtomic=!1,this._zoom=e,t){const n=this._stageEl?.clientWidth||t.stageWidth,o=Math.max(1,t.stageHeight+t.editorChromeHeight);this._view=this._viewForModeTarget(e,i,s,n,o),this._lastValidStageSize=[n,o]}else this._view=null;this._viewModeSnap=null,this._modeTransitionEditorCamera=null,this._saveZoom(),this.requestUpdate()}_prepareModeTransition(t,e,i,s,n,o){this.updateComplete.then(()=>{if(!this.isConnected||t!==this._modeTransitionRequest||this._mode!==i)return void(t===this._modeTransitionRequest&&(this._modeTransitionPreparing=!1,this._modeTransitionVisual=null,this._modeTransitionForceAtomic=!1,this.requestUpdate()));const r=this.renderRoot.querySelector(".editorchrome"),a=r?.querySelector(".editorchrome-inner"),l="view"===i?0:a?.scrollHeight||a?.getBoundingClientRect().height||0,c=Math.max(1,e.stageHeight+e.editorChromeHeight),h=Math.max(1,c-l),d=this._stageEl?.clientWidth||e.stageWidth;if(d<=0||h<=0)return this._modeTransitionPreparing=!1,this._modeTransitionVisual=null,this._applyView(s,n,o),void this.requestUpdate();const u=this._viewForModeTarget(s,n,o,d,h),p={presentedMode:i,editorChromeHeight:l,stageWidth:d,stageHeight:h,viewport:ou(u,d),stageColor:this._targetStageColor(i),paperColor:this._targetPaperColor(i),sceneBrightness:this._targetBrightness(i),architectureOpacity:"decor"===i?.35:1,backdropOpacity:"decor"===i&&"backdrop"!==this._decorTool?.5:1,viewWeight:"view"===i?1:0,editorWeight:"view"===i?0:1,toolbarContentOpacity:"view"===i?0:1};this._modeTransitionPreparing=!1;const _=this._modeTransitionForceAtomic;this._modeTransitionForceAtomic=!1,this._modeTransition.start(e,p,i,this._reducedMotion||_?0:220)})}_slideTo(t,e){if(t===this._space)return;this._cancelModeTransition(!0);const i=window.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches;this._activeDraftId&&(this._resumeDraftBySpace[this._space]=this._activeDraftId),this._space=t,this._path=[],this._clearPlanSnapHover(),this._clearOpeningPlacement(!0),this._tool="draw",this._openWallAnchor=null,this._boundaryRestoreGuard=null,this._activeDraftId=null,this._draftSegmentCms=[],this._closingWallCm=null,this._selId=null,this._physicalSel=null,this._editorSecondary.closeForNavigation(),this._physicalDialog=null,this._physicalDrag=null,"plan"===this._mode&&"draw"===this._tool&&this._resumeLastDraft(),this._restoreZoom(),i||(this._slide=e,clearTimeout(this._slideTimer),this._slideTimer=window.setTimeout(()=>{this._slideTimer=void 0,this._slide="",this.requestUpdate()},190),this.requestUpdate())}_pickSpace(t){if(t===this._space)return;const e=this._model.map(t=>t.id),i=e.indexOf(this._space),s=e.indexOf(t);this._navApplied=!0,this._showFar=!1,this._frame=null,this._slideTo(t,i>=0&&s0&&Date.now()>=this._cyclePausedUntil&&this._model.length>1&&this._zoom<=1.001){const t=this._model.map(t=>t.id),e=t.indexOf(this._space);this._slideTo(t[(e+1)%t.length],"left"),this._showKioskDots()}}get _editing(){return"plan"===this._mode||"devices"===this._mode||"decor"===this._mode}get _markup(){return"plan"===this._mode}_newContinuityController(){return new Hc(()=>{this._resumeSettling="steady"!==this._continuity.state,this._continuityEpoch++,this.isConnected&&this.requestUpdate()})}get _labsIso(){return this._labs.active.includes("iso")}get _desiredProjection(){return"view"===this._mode&&this._labsIso&&"iso"===this._viewPreference[this._space]?"iso":"flat"}_saveViewPreference(){try{localStorage.setItem(wp,JSON.stringify(this._viewPreference))}catch{}}_logicalViewCenter(t){const e=this._view;if(!e)return null;const i=[e.x+e.w/2,e.y+e.h/2],s="iso"===t?Iu(i):i;return{x:s[0],y:s[1]}}_convertProjectionView(t,e){if(t===e)return;const i=this._logicalViewCenter(t);this._view=null;const s=i?"iso"===e?Au([i.x,i.y],0):[i.x,i.y]:null;this._applyView(this._zoom,s?.[0],s?.[1]),this._warmPatch({vp:this._warmViewportState()}),this.requestUpdate()}_setProjection(t){if(!this._labsIso||"view"!==this._mode)return;const e=this._effectiveProjection();if(this._viewPreference={...this._viewPreference,[this._space]:t},"iso"===t){const t=this._isoSceneKey();t&&this._isoFallback.delete(t)}this._saveViewPreference();const i=this._effectiveProjection();this._convertProjectionView(e,i)}_hashSpace(){return vu(window.location.hash||"")}connectedCallback(){this._connectedPath=location.pathname,this._routeDepartureHandled=!1,window.addEventListener("location-changed",this._onLocationChanged),window.addEventListener("popstate",this._onLocationChanged),this._continuityDisposed&&(this._continuity=this._newContinuityController(),this._continuityDisposed=!1,this._continuityPaintToken=-1);const t=(e=this.ownerDocument,Jd.get(e)?.resolved);var e;void 0!==t&&(this._glowScreenBlend=t),this._continuityUnsub?.(),this._continuityUnsub=Nc(this.ownerDocument,this._pageVisibility),super.connectedCallback(),this._motionMedia=window.matchMedia?.("(prefers-reduced-motion: reduce)"),this._reducedMotion=!!this._motionMedia?.matches,this._motionMedia?.addEventListener?.("change",this._onMotionChange),function(t){const e=Jd.get(t);if(e)return e.promise;const i={promise:Promise.resolve(!1)};return i.promise=tu(t).catch(()=>!1).then(t=>(i.resolved=t,t)),Jd.set(t,i),i.promise}(this.ownerDocument).then(t=>{t!==this._glowScreenBlend&&(this._glowScreenBlend=t,this.isConnected&&this.requestUpdate())}),this.hass&&this._ensureHaRegistryAuthority(),window.addEventListener("keydown",this._keyHandler),this._signer.start(()=>this.hass,()=>$s(this._serverCfg)),this._config?.kiosk&&Number(this._config?.cycle)>0&&(clearInterval(this._cycleTimer),this._cycleTimer=window.setInterval(()=>this._cycleTick(),1e3*Number(this._config.cycle))),window.addEventListener("hashchange",this._onHashChange),this._labsUnsub?.(),this._labsUnsub=function(t,e){Su.add(e);const i=Du(t);return"undefined"==typeof window&&e(i),()=>{Su.delete(e)}}(sp,this._onLabsSnapshot),this._booting?this._bootWatch():this._bootFading&&(clearTimeout(this._bootTimer),this._bootTimer=window.setTimeout(()=>{this._bootFading=!1},220)),this._bootSoft&&(clearTimeout(this._bootSoftTimer),this._bootSoftTimer=window.setTimeout(()=>{this._bootSoft=!1},lp)),!this._loadOk&&this._serverCfg&&this.hass&&this._scheduleLoadRetry(),!this._warmSlot&&this._config&&this._warmAdopt(),this._loadOk&&this._ensureLiveSyncSubscriptions(),this._warmVp&&!this._warmRevivePending&&void 0===this._warmReviveTimer&&(this._warmRevivePending=!0,this._warmReviveTimer=window.setTimeout(()=>this._warmReviveDialog(),0)),this._warmLongReturn&&this._beginResumeSettle(),this._warmLongReturn=!1,this.requestUpdate()}disconnectedCallback(){this._connectedPath&&location.pathname!==this._connectedPath&&this._leaveCardRoute(),window.removeEventListener("location-changed",this._onLocationChanged),window.removeEventListener("popstate",this._onLocationChanged),this._continuityUnsub?.(),this._continuityUnsub=void 0,this._motionMedia?.removeEventListener?.("change",this._onMotionChange),this._motionMedia=void 0,this._vacRaf&&(cancelAnimationFrame(this._vacRaf),this._vacRaf=0),this._refitRaf&&(cancelAnimationFrame(this._refitRaf),this._refitRaf=0),this._dayCycleTimer&&(clearInterval(this._dayCycleTimer),this._dayCycleTimer=0),this._dayCycleClockKey="",this._bootSettleRaf&&(cancelAnimationFrame(this._bootSettleRaf),this._bootSettleRaf=0),this._bootSettling=!1;for(const t of this._activityRt.values())clearTimeout(t.timer);window.removeEventListener("keydown",this._keyHandler),clearInterval(this._cycleTimer),clearTimeout(this._kioskDotsTimer),clearTimeout(this._kioskHoldTimer),clearTimeout(this._reloadRetry),clearTimeout(this._loadRetryTimer),this._loadRetryTimer=void 0,this._connHooked?.removeEventListener?.("ready",this._onConnReady),this._connHooked?.removeEventListener?.("disconnected",this._onConnLost),this._connHooked?.removeEventListener?.("reconnect-error",this._onConnLost),this._connHooked=null,this._haRegistryRelease?.(),this._haRegistryRelease=void 0,this._haRegistryConnection=null,this._signer.dispose(),clearTimeout(this._toastTimer),clearTimeout(this._slideTimer),this._modeTransition.dispose(),this._modeTransitionVisual=null,this._modeTransitionPreparing=!1,this._modeTransitionForceAtomic=!1,this._modeTransitionRequest++,this._slideTimer=void 0,this._slide="",clearTimeout(this._bootTimer),this._bootTimer=void 0,clearTimeout(this._bootSoftTimer),this._saveConfigDebounced.flush(),window.removeEventListener("hashchange",this._onHashChange),this._labsUnsub?.(),this._labsUnsub=void 0,clearTimeout(this._holdTimer),this._roViewport?.disconnect(),this._roViewport=void 0,this._roHdr?.disconnect(),this._roHdr=void 0,this._onWinResize&&(window.removeEventListener("resize",this._onWinResize),this._onWinResize=void 0),this._unsubCfg&&(this._unsubCfg(),this._unsubCfg=null),this._unsubLayout&&(this._unsubLayout(),this._unsubLayout=null),this._unsubVirtual&&(this._unsubVirtual(),this._unsubVirtual=null),this._unsubTrail&&(this._unsubTrail(),this._unsubTrail=void 0),this._liveSyncGeneration++,this._liveSyncAttempt=null,this._liveSyncConnection=null,clearTimeout(this._layoutSyncTimer),clearTimeout(this._duplicateColumnTimer);for(const t of this._glowFadeTimers.values())clearTimeout(t);for(const t of this._glowEnterRafs.values())cancelAnimationFrame(t);clearTimeout(this._glowFeatherResumeTimer),this._glowFeatherResumeTimer=0,this._glowFeatherSuspendUntil=0,this._glowFadeTimers.clear(),this._glowEnterRafs.clear(),this._glowEnteringSources.clear(),this._glowRenderedSources.clear(),this._glowLastAppearance.clear(),this._glowSourceSeq=0,this._warmSnapshot(),this._warmRevivePending=!1,clearTimeout(this._warmReviveTimer),this._warmReviveTimer=void 0,this._warmRelease(),this._openWallAnchor=null,this._boundaryRestoreGuard=null,this._clearPlanSnapHover(),this._clearOpeningPlacement(!0),this._touchContacts.clear(),this._touchSequenceMultitouch=!1,this._touchClickBlockUntil=0,this._editorSecondary.reset(),this._resumeSettling=!1,this._continuityHistory=[...this._continuityHistory,...this._continuity.trace].slice(-80),this._continuity.dispose(),this._continuityDisposed=!0,super.disconnectedCallback()}_onKey(t){if("Escape"===t.key&&this._vacFit)return this._vacFit=null,this._showToast(this._t("vac.cal_cancelled")),void t.stopPropagation();if("Escape"===t.key){if(this._tapConfirm)return void(this._tapConfirm=null);if(this._vacCalConfirm)return void(this._vacCalConfirm=null);if(this._decorEraseConfirm)return void(this._decorEraseConfirm=null);if(this._openingInfo)return void(this._openingInfo=null);if(this._infoCard)return void this._closeInfoCard();if(this._rulesDialog)return void(this._rulesDialog=null);if(this._alignDialog)return void(this._alignDialog=null);if(this._backupImportDialog)return void(this._backupImportDialog=null);if(this._backupExportDialog)return void(this._backupExportDialog=null);if(this._settingsDialog)return void(this._settingsDialog=null);if(this._markerDialog)return void(this._markerDialog=null);if(this._openingDialog)return void(this._openingDialog=null);if(this._physicalDialog)return void(this._physicalDialog=null);if(this._backdropDialog)return void(this._backdropDialog=null);if(this._decorShapeDialog)return void(this._decorShapeDialog=null);if(this._decorTextDialog)return void(this._decorTextDialog=null);if(this._spaceDialog&&!this._roomDialog)return this._spaceDialog=null,this._importQueue=[],void(this._importTotal=0);if(this._editorSecondary.hasOpenGroup)return t.preventDefault(),void this._editorSecondary.closeGroup(!0)}const e=t.composedPath?.()||[t.target],i=e.some(t=>t?.matches?.('input, textarea, select, [contenteditable="true"]')),s=e.some(t=>t?.classList?.contains?.("editor-secondary")),n=t.ctrlKey||t.metaKey,o=t.key.toLowerCase(),r=/^[a-z]$/.test(o),a="z"===o||!r&&"KeyZ"===t.code,l="y"===o||!r&&"KeyY"===t.code,c=n&&(a&&t.shiftKey||l),h=n&&a&&!t.shiftKey;if("decor"===this._mode){if((h||c)&&i)return;return c?(t.preventDefault(),void this._redoGeometry()):h?(t.preventDefault(),this._decorDraft?void(this._decorDraft=null):this._decorMove||this._dtDrag||this._bdDrag?void this._cancelDecorGesture():void this._undoGeometry()):"Delete"!==t.key&&"Backspace"!==t.key||!this._decorSel||i||s?void("Escape"===t.key&&(t.preventDefault(),this._decorDraft?this._decorDraft=null:this._decorMove||this._dtDrag||this._bdDrag?this._cancelDecorGesture():"furniture"===this._decorTool?(this._furnPalette=null,this._decorTool="select"):this._decorSel?this._decorSel=null:"select"!==this._decorTool?this._decorTool="select":this._setMode("view"))):(t.preventDefault(),void this._decorDeleteSel())}if(this._markup&&(!h&&!c||!i)){if(("Delete"===t.key||"Backspace"===t.key)&&this._physicalSel&&!i&&!s)return t.preventDefault(),void this._deletePhysicalSelection();if(c)return t.preventDefault(),void this._redoGeometry();if(h)return t.preventDefault(),this._rszDrag?void this._rszCancelDrag():"draw"!==this._tool&&"partition"!==this._tool||!this._path.length?"split"===this._tool&&this._splitSel?.pts?.length?(this._splitSel={...this._splitSel,pts:this._splitSel.pts.slice(0,-1)},void(this._splitSel.pts.length||(this._cursorPt=null))):void this._undoGeometry():void(this._activeDraftId&&this._path.length>1?this._undoGeometry():this._undoPoint());if("Escape"===t.key)return this._physicalDrag||this._physicalRotate?(t.preventDefault(),void this._cancelPhysicalGesture()):this._roomDialog?(t.preventDefault(),void this._roomDialogCancel()):"draw"===this._tool&&this._path.length||"partition"===this._tool&&this._path.length?(t.preventDefault(),void this._undoPoint()):this._physicalSel?(t.preventDefault(),void(this._physicalSel=null)):"resize"===this._tool?(t.preventDefault(),this._rszDrag?void this._rszCancelDrag():void(this._rszSel?this._rszSel=null:this._tool="draw")):"split"===this._tool?(t.preventDefault(),void(this._splitSel?.pts?.length?(this._splitSel={...this._splitSel,pts:this._splitSel.pts.slice(0,-1)},this._splitSel.pts.length||(this._cursorPt=null)):this._splitSel?this._splitSel=null:this._tool="draw")):"merge"===this._tool?(t.preventDefault(),void(this._mergeSel?this._mergeSel=null:this._tool="draw")):this._wallDialog?(t.preventDefault(),void(this._wallDialog=null)):"boundary"===this._tool?(t.preventDefault(),void(this._openWallAnchor?this._cancelBoundaryAnchor():this._tool="draw")):void("opening"!==this._tool&&"wallthick"!==this._tool&&"delroom"!==this._tool&&"partition"!==this._tool&&"column"!==this._tool||(t.preventDefault(),"opening"===this._tool&&this._clearOpeningPlacement(!0),this._tool="draw"))}}_undoPoint(){if(this._path.length){if(this._contourClosed)return this._path=this._path.slice(0,-1),void(this._closingWallCm=null);if(this._activeDraftId&&this._path.length>1&&this._curSpaceCfg){const t=this._geometrySnapshot();this._path=this._path.slice(0,-1),this._draftSegmentCms=this._draftSegmentCms.slice(0,-1);const e=this._curSpaceCfg,i=(e.room_drafts||[]).findIndex(t=>t.id===this._activeDraftId);return void(i>=0&&(this._path.length<2?(e.room_drafts.splice(i,1),e.room_drafts.length||delete e.room_drafts,this._activeDraftId=null):e.room_drafts[i]={id:this._activeDraftId,points:this._path.map(t=>[t[0]/kp,t[1]/kp]),segments:this._draftSegmentCms.map(t=>({cm:t}))},this._recordGeometry(this._t("history.draft_segment_delete"),t),this._saveConfig()))}this._path=this._path.slice(0,-1)}}static getConfigElement(){return document.createElement("houseplan-card-editor")}static getStubConfig(){return{type:"custom:houseplan-card"}}static _warmBootReset(t){for(const t of up.values())for(const e of t)clearTimeout(e.evict);up.clear(),mp=t&&t>0?t:1e4}static _warmBootStats(){let t=0,e=0;const i=[];for(const s of up.values())for(const n of s)t++,n.dlg&&(e++,i.push(n.dlg.kind));return{keys:up.size,slots:t,dlgs:e,drafts:i}}setConfig(t){this._config={icon_size:2.5,show_temperature:!0,live_states:!0,show_signal:!0,...t},this._config.kiosk&&(this._booting=!1,this._bootFading=!1),t.default_floor&&(this._space=t.default_floor);try{this._zoomBySpace=JSON.parse(localStorage.getItem(vp)||"{}")||{}}catch{this._zoomBySpace={}}try{const t=JSON.parse(localStorage.getItem(wp)||"{}")||{};this._viewPreference=Object.fromEntries(Object.entries(t).filter(t=>"flat"===t[1]||"iso"===t[1]))}catch{this._viewPreference={}}this._labs=Du(sp);try{const t=JSON.parse(localStorage.getItem(bp)||"null");this._kioskScale={icon:Cs(t?.icon),font:Cs(t?.font)}}catch{}try{const t=JSON.parse(localStorage.getItem(fp)||"null");t&&t.config&&Array.isArray(t.config.spaces)&&(this._serverCfg=t.config,this._cfgEpoch++,this._cfgRev=t.rev||0,this._cfgContentFingerprint=t.config_fingerprint||qc(t.config),this._layout=t.layout||{},this._layoutRev=t.layout_rev||0,this._layoutContentFingerprint=t.layout_fingerprint||qc(this._layout),this._virtualLights=rn(t.virtual_lights,this._cfgRev),this._serverStorage=!0,this._adoptInitialSpace(this._model))}catch{}"view"!==this._mode||this._view||(this._zoom=this._zoomBySpace[this._space]||1),this.isConnected&&(this._warmAdopt(),this._warmLongReturn&&this._beginResumeSettle(),this._warmLongReturn=!1)}_warmAdopt(){if(this._config?.kiosk)return;const t=_p(this._config);if(this._warmKey===t&&this._warmSlot)return;this._warmSlot&&this._warmRelease();const e=this.parentNode,i=this._warmIdx(e),s=up.get(t);if(!s||!s.length)return;const n=s.find(t=>t.owner===this._warmGen);if(n)return this._warmLongReturn=!!n.freed&&Date.now()-n.freed>=zc,clearTimeout(n.evict),n.evict=0,n.freed=0,n.live=!0,this._warmSlot=n,this._warmKey=t,n.frameFingerprint&&this._continuity.adoptCompleteFrame(n.frameFingerprint),void(!this._devices.length&&n.devices?.length&&(this._devices=[...n.devices]));const{slot:o,sure:r}=((t,e,i,s)=>{const n=t=>{const e=!!i&&t.place?.deref()===i;return e&&t.idx===s?4:t.live?0:e?3:2};let o=null,r=0,a=0,l=null;for(const i of t){if(i.owner===e)continue;l=i;const t=n(i);t<=0||(t>r?(o=i,r=t,a=1):t===r&&a++)}return!o||a>1?{slot:o||l,sure:!1}:{slot:o,sure:!0}})(s,this._warmGen,e,i);o&&(this._warmLongReturn=!!o.freed&&Date.now()-o.freed>=zc,this._booting=!1,this._bootFading=!1,this._hdrH=o.hdrH,this._bootSoft=!0,this.isConnected&&(clearTimeout(this._bootSoftTimer),this._bootSoftTimer=window.setTimeout(()=>{this._bootSoft=!1},lp)),this._warmKey=t,r?(clearTimeout(o.evict),o.evict=0,o.owner=this._warmGen,o.place=e?new WeakRef(e):null,o.idx=i,o.live=!0,this._warmSlot=o,this._warmVp=o.vp,o.frameFingerprint&&this._continuity.adoptCompleteFrame(o.frameFingerprint),!this._devices.length&&o.devices?.length&&(this._devices=[...o.devices]),this._warmAdoptViewport(this._config)):(this._warmSlot={owner:this._warmGen,path:location.pathname,place:e?new WeakRef(e):null,idx:i,live:!0,hdrH:o.hdrH,stageH:o.stageH,vp:null,frameFingerprint:"",devices:null,dlg:null,freed:0,evict:0},s.push(this._warmSlot),this._warmTrim(s)))}_warmIdx(t){const e=t?.children;if(!e)return-1;for(let t=0;t4;){const e=t.findIndex(t=>!t.live);if(e<0)break;clearTimeout(t[e].evict),t.splice(e,1)}}_warmScheduleEvict(t,e){if(clearTimeout(t.evict),!(t.dlg||t.vp&&"view"!==t.vp.mode))return;const i=t.freed,s=t.owner;t.evict=window.setTimeout(()=>{if(t.evict=0,t.freed!==i||t.owner!==s)return;var n;t.dlg=null,t.vp=(n=t.vp)&&"view"!==n.mode?{...n,mode:"view",zoom:n.snap?.space===n.space?n.snap.zoom:n.zoom,view:null,snap:null,tool:"draw",decorTool:"select",showHidden:!1,selId:null,rszSel:null,decorSel:null}:n,t.frameFingerprint="";const o=up.get(e);if(!t.live&&o&&o.length>1){const e=o.indexOf(t);e>=0&&o.splice(e,1)}},mp+250)}_warmAdoptViewport(t){const e=this._warmVp;if(!e||this._warmSlot?.path!==location.pathname)return void(this._warmVp=null);if(this._hashApplied||!this._model.find(t=>t.id===e.space))return void(this._warmVp=null);this._space=e.space,this._navApplied=!0,this._adoptMode("view"!==e.mode&&this._canEdit&&!t.kiosk?e.mode:"view"),this._pendingNavMode="view"===e.mode||this._canEdit||t.kiosk?null:e.mode,this._zoom=e.zoom;const i=this._effectiveProjection(),s=i===e.projection&&this._labsIso===e.activeLabsIso;if(this._view=s&&e.view?{...e.view}:null,this._viewModeSnap=s&&e.snap?{...e.snap}:null,!s&&e.logicalCenter){const t="iso"===i?Au([e.logicalCenter.x,e.logicalCenter.y],0):[e.logicalCenter.x,e.logicalCenter.y];this._applyView(e.zoom,t[0],t[1])}var n;this._tool="openwall"===(n=e.tool)||"closewall"===n?"boundary":"opening"===n?"draw":"string"==typeof n&&Sp.has(n)?n:"draw",this._decorTool=e.decorTool,this._showHidden=e.showHidden,this._showFar!==e.showFar&&(this._showFar=e.showFar,this._frame=null),this._selId=e.selId,this._rszSel=e.rszSel,this._decorSel=e.decorSel,this._warmVpArmed=!0}_warmPatch(t,e=!1){if(this._config?.kiosk)return;const i=_p(this._config);if(!this._warmSlot||this._warmKey===i){if(!this._warmSlot){if(!e)return;const t=this.parentNode;this._warmKey=i,this._warmSlot={owner:this._warmGen,path:location.pathname,place:t?new WeakRef(t):null,idx:this._warmIdx(t),live:!0,hdrH:this._hdrH,stageH:0,vp:null,frameFingerprint:"",devices:null,dlg:null,freed:0,evict:0};const s=up.get(i)||[];for(s.push(this._warmSlot),up.set(i,s),this._warmTrim(s);up.size>8;){const t=up.keys().next().value;if(void 0===t||t===i)break;for(const e of up.get(t)||[])clearTimeout(e.evict);up.delete(t)}}Object.assign(this._warmSlot,t)}}_warmViewportState(){const t=this._effectiveProjection();return{space:this._space,mode:this._mode,projection:t,activeLabsIso:this._labsIso,logicalCenter:this._logicalViewCenter(t),zoom:this._zoom,view:this._view?{...this._view}:null,snap:this._viewModeSnap?{...this._viewModeSnap}:null,tool:this._tool,decorTool:this._decorTool,showHidden:this._showHidden,showFar:this._showFar,selId:this._selId,rszSel:this._rszSel,decorSel:this._decorSel}}_warmDialogState(){const t=(t,e)=>({kind:t,space:this._space,mode:this._mode,data:e});return this._tapConfirm||this._alignDialog||this._mergeDialog||this._importDialog||this._backupExportDialog||this._backupImportDialog?null:this._openingInfo?t("openingInfo",this._openingInfo.id):this._infoCard?t("info",this._infoCard.id):this._rulesDialog?this._rulesDialog.busy?null:t("rules",this._rulesDialog):this._settingsDialog?this._settingsDialog.busy?null:t("settings",this._settingsDialog):this._markerDialog?this._markerDialog.busy?null:t("marker",this._markerDialog):this._openingDialog?t("opening",this._openingDialog):this._backdropDialog?t("backdrop",this._backdropDialog):this._decorShapeDialog?t("decorShape",this._decorShapeDialog):this._decorTextDialog?t("decorText",this._decorTextDialog):this._roomDialog?t("room",{editId:this._roomEditId,fill:this._roomFill,customFill:this._roomCustomFill,tempSrc:this._roomTempSrc,humSrc:this._roomHumSrc,srcOpen:this._roomSrcOpen,srcFilter:this._roomSrcFilter,nameScale:this._roomNameScale,labelScale:this._roomLabelScale,areaSel:this._areaSel,nameSel:this._nameSel,pendingSplit:this._pendingSplit,path:this._path}):this._spaceDialog?this._spaceDialog.busy?null:t("space",this._spaceDialog):null}_warmSnapshot(){if(this._booting||this._config?.kiosk||"steady"!==this._continuity.state)return;const t={vp:this._warmViewportState(),frameFingerprint:this._continuity.frameFingerprint,devices:this._devices};if(this._warmRevivePending||(t.dlg=this._warmDialogState()),this.isConnected&&this._warmSlot?.owner===this._warmGen){const e=this.parentNode;t.place=e?new WeakRef(e):null,t.idx=this._warmIdx(e)}this._warmPatch(t)}_warmReviveDialog(){this._warmRevivePending=!1;const t=this._warmSlot;if(this._warmReviveTimer=void 0,!t||!t.dlg)return;const e=t.dlg,i=t.freed;if(t.dlg=null,t.freed=0,clearTimeout(t.evict),t.evict=0,i&&!(Date.now()-i>mp)&&e.space===this._space&&e.mode===this._mode){switch(e.kind){case"space":this._spaceDialog={...e.data,busy:!1,savedBusy:!1};break;case"marker":this._markerDialog={...e.data,busy:!1};break;case"settings":this._settingsDialog={...e.data,busy:!1};break;case"rules":this._rulesDialog={...e.data,busy:!1};break;case"opening":this._openingDialog={...e.data};break;case"backdrop":this._backdropDialog={...e.data};break;case"decorShape":this._decorShapeDialog={...e.data};break;case"decorText":{this._decorTextDialog={...e.data};const t=String(this._decorTextDialog?.text??"").length;this._decorTextSelection={start:t,end:t};break}case"room":{const t=e.data;this._roomEditId=t.editId,this._roomFill=t.fill,this._roomCustomFill=t.customFill||null,this._roomTempSrc=t.tempSrc,this._roomHumSrc=t.humSrc,this._roomSrcOpen=t.srcOpen,this._roomSrcFilter=t.srcFilter,this._roomNameScale=t.nameScale,this._roomLabelScale=t.labelScale,this._areaSel=t.areaSel,this._nameSel=t.nameSel,this._pendingSplit=t.pendingSplit,this._path=t.path,this._roomDialog=!0;break}case"info":{const t=this._devices.find(t=>t.id===e.data);t&&(this._infoCard=t);break}case"openingInfo":{const t=(this._curSpaceCfg?.openings||[]).find(t=>t.id===e.data);t&&(this._openingInfo=t);break}}this.requestUpdate()}}_cacheSnapshot(){if(this._serverCfg)try{this._cfgContentFingerprint=qc(this._serverCfg),this._layoutContentFingerprint=qc(this._layout),this._virtualLights=function(t,e,i){if(t.configRev===i)return t;const s=new Set((Array.isArray(e?.markers)?e.markers:[]).filter(hn).map(t=>t.id));return{...t,configRev:i,off:new Set([...t.off].filter(t=>s.has(t)))}}(this._virtualLights,this._serverCfg,this._cfgRev),localStorage.setItem(fp,JSON.stringify({config:this._serverCfg,rev:this._cfgRev,config_fingerprint:this._cfgContentFingerprint,layout:this._layout,layout_rev:this._layoutRev,layout_fingerprint:this._layoutContentFingerprint,virtual_lights:ln(this._virtualLights)}))}catch{}}_beginContinuityCandidate(t,e,i="plan"){return this._booting&&!this._continuity.hasCompleteFrame?this._continuity.token:(this._continuityDataReady=e,this._continuityPaintToken=-1,this._stagedDeviceSnapshotToken=-1,this._resumeSettling=!0,this._continuity.beginCandidate(t,i))}_continuityStageValid(){const t=this._stageEl;return!!t&&t.clientWidth>0&&t.clientHeight>0}_continuityAssetsReady(){if(!this._model.length)return!1;const t=this._model.length?this._spaceModel():null;return!t?.bg?.href||this._signer.isReady(this.hass,t.bg.href)}_initialSpaceSelection(t){return function(t){const e=new Set(t.spaceIds.filter(t=>!!t));if(!e.size)return{id:null,source:"none"};const i=[["hash",!1===t.acceptHash?null:t.hashSpace],["current",t.preserveCurrent?t.currentSpace:null],["saved",t.savedSpace],["default",t.defaultSpace],["first",t.spaceIds[0]]];for(const[t,s]of i)if(s&&e.has(s))return{id:s,source:t};return{id:null,source:"none"}}({spaceIds:t.map(t=>t.id),hashSpace:this._hashSpace(),acceptHash:!this._hashApplied,currentSpace:this._space,preserveCurrent:this._hashApplied||this._navApplied||this._warmVpArmed,savedSpace:this._savedNav()?.space,defaultSpace:this._config?.default_floor})}_adoptInitialSpace(t){const e=this._initialSpaceSelection(t);return e.id?(this._space=e.id,"hash"===e.source&&(this._hashApplied=!0),"saved"===e.source&&(this._navApplied=!0),e):e}_candidateBackdrop(t,e=this._space){const i=Al(t),s=this._initialSpaceSelection(i).id||(i.some(t=>t.id===e)?e:i[0]?.id);return i.find(t=>t.id===s)?.bg?.href||""}_visualFrameFingerprint(){const t=this._stageEl,e=t?[t.clientWidth,t.clientHeight]:[0,0];return Bc([this._cfgRev,this._cfgContentFingerprint||qc(this._serverCfg),this._layoutRev,this._layoutContentFingerprint||qc(this._layout),this._space,this._mode,this._view,e,this._glowScreenBlend?"screen":"normal",this.hass?.themes?.darkMode??this.hass?.themes?.default_theme??""])}_settleContinuityFrame(){if(this._booting||!this._continuityStageValid())return;if(!this._continuity.hasCompleteFrame&&"steady"===this._continuity.state)return void(this._continuityAssetsReady()&&(this._renderSnapshotAt=Date.now(),this._continuity.markCompleteFrame(this._visualFrameFingerprint())));if(!this._continuityDataReady)return;if(!["holding","offline-stale","overlay-pending","overlay-visible","candidate-ready"].includes(this._continuity.state))return;const t=this._continuity.token;if(this._candidateDeviceSnapshot&&this._candidateDeviceSnapshot!==this._visibleDeviceSnapshot&&this._stagedDeviceSnapshotToken!==t)return this._stagedDeviceSnapshotToken=t,void this.requestUpdate();this._continuityPaintToken!==t&&(this._continuityPaintToken=t,this._continuity.candidateReady(t)&&this._continuity.commitAfterPaint(t,{updateComplete:()=>this.updateComplete,stageValid:()=>this.isConnected&&this._continuityStageValid(),assetsReady:()=>this._continuityAssetsReady(),frameFingerprint:()=>this._visualFrameFingerprint()}).then(e=>{e&&t===this._continuity.token?(this._resumeSettling=!1,this._renderSnapshotAt=Date.now(),this._candidateDeviceSnapshot&&(this._visibleDeviceSnapshot=this._candidateDeviceSnapshot),this._candidateDeviceSnapshot=null,this._stagedDeviceSnapshotToken=-1,this._warmSnapshot()):t===this._continuity.token&&(this._continuityPaintToken=-1,this._stagedDeviceSnapshotToken=-1,this._candidateDeviceSnapshot=null,this.requestUpdate())}))}_onBackdropLoaded(t,e){this._signer.markLoaded(this.hass,t,e),this._continuity.note("asset-ready"),this._continuityPaintToken=-1,"steady"!==this._continuity.state&&this.requestUpdate()}_renderRecoveryOverlay(){if(!this._continuity.overlayVisible&&"recovery-error"!==this._continuity.state)return G;const t="connection"===this._continuity.recoveryReason;return j`
`}const Xu={window:120,door:90,gate:300};function Zu(t){return Xu[t]}function Ju(t,e){const i=[t[0],t[1]],s=[e[0],e[1]];return function(t,e){return t[0]-e[0]||t[1]-e[1]}(i,s)<=0?{a:i,b:s}:{a:s,b:i}}function Qu(t){const e=new Map;return t.forEach((t,i)=>{if(!t.kind||t.open)return;const s=Ju(t.a,t.b);if(Math.hypot(s.b[0]-s.a[0],s.b[1]-s.a[1])<=1e-9)return;const n=function(t,e){const i=t=>(Math.abs(t)<=5e-10?0:Math.round(1e6*t)/1e6).toFixed(6);return`${i(t[0])},${i(t[1])}>${i(e[0])},${i(e[1])}`}(s.a,s.b),o=e.get(n);if(o)return o.physicalHalfWidth=Math.max(o.physicalHalfWidth,t.half||0),void(o.sourceOrder=Math.min(o.sourceOrder,i));e.set(n,{segmentKey:n,a:s.a,b:s.b,physicalHalfWidth:Math.max(0,t.half||0),sourceOrder:i})}),[...e.values()]}function tp(t,e){if(Math.abs(t.distance-e.distance)>1e-9)return t.distance-e.distance;if(Math.abs(t.perpendicular-e.perpendicular)>1e-9)return t.perpendicular-e.perpendicular;return(t.target.segmentKeye.target.segmentKey?1:0)||t.target.sourceOrder-e.target.sourceOrder}function ep(t){const e=Qu(t.intervals).map(e=>{const i=function(t,e){const i=e.b[0]-e.a[0],s=e.b[1]-e.a[1],n=Math.hypot(i,s),o=i/n,r=s/n,a=(t[0]-e.a[0])*o+(t[1]-e.a[1])*r,l=Math.max(0,Math.min(n,a)),c=e.a[0]+l*o,h=e.a[1]+l*r;return{along:l,length:n,x:c,y:h,distance:Math.hypot(t[0]-c,t[1]-h),perpendicular:Math.abs((t[0]-e.a[0])*r-(t[1]-e.a[1])*o)}}(t.pointer,e);return{target:e,...i,envelope:Math.max(t.baseTolerance,e.physicalHalfWidth+t.bodyPointerPadding)}}).filter(t=>t.distance<=t.envelope+1e-9).filter(e=>!function(t,e,i,s,n){const o=e.b[0]-e.a[0],r=e.b[1]-e.a[1],a=Math.hypot(o,r);if(!(a>1e-9))return!1;const l=o/a,c=r/a;for(const o of i){if(!o.open&&o.kind)continue;const i=Ju(o.a,o.b),r=i.b[0]-i.a[0],a=i.b[1]-i.a[1],h=Math.hypot(r,a);if(!(h>1e-9))continue;const d=r/h,u=a/h;if(Math.abs(l*u-c*d)>1e-6)continue;if(Math.abs((i.a[0]-e.a[0])*c-(i.a[1]-e.a[1])*l)>n)continue;const p=(t[0]-i.a[0])*d+(t[1]-i.a[1])*u,_=1e-9;if(!(p<=_||p>=h-_)&&Math.abs((t[0]-i.a[0])*u-(t[1]-i.a[1])*d)<=s+1e-9)return!0}return!1}(t.pointer,e.target,t.intervals,e.envelope,Math.max(1e-9,Math.min(t.baseTolerance,.04*t.gridStep)))).sort(tp),i=e[0];if(!i)return null;const{target:s,length:n}=i,o=s.b[0]-s.a[0],r=s.b[1]-s.a[1],a=o/n,l=r/n,c=Math.min(Math.max(0,t.renderedLength)/2,n/2);let h=i.along;const d=Math.max(t.gridStep,1e-9),u=n/2;h=Math.abs(h-u)<=d/2?u:Math.round(h/d)*d,h=Math.max(c,Math.min(n-c,h));const p=s.a[0]+a*h,_=s.a[1]+l*h,m=Math.max(0,t.renderedLength)/2,g=h-m,f=h+m,v=Math.max(0,g),y=Math.max(0,n-f),b=[s.a[0]+a*(g-v/2),s.a[1]+l*(g-v/2)],w=[s.a[0]+a*(f+y/2),s.a[1]+l*(f+y/2)];let k=180*Math.atan2(r,o)/Math.PI;k>=90?k-=180:k<-90&&(k+=180);const $=Math.abs(h-u)<=1e-9;return{presetRevision:t.preset.revision,geometryRevision:t.geometryRevision,pointer:[t.pointer[0],t.pointer[1]],type:t.preset.type,lengthCm:t.preset.lengthCm,flipH:t.preset.flipH,flipV:t.preset.flipV,x:p,y:_,angle:k,renderedLength:t.renderedLength,target:s,measure:{labels:[{distance:v,midpoint:b},{distance:y,midpoint:w}],guide:$?{x:p,y:_,angle:k}:null}}}function ip(t,e,i,s,n=1e-6){return t.presetRevision===i&&t.geometryRevision===s&&Math.hypot(t.pointer[0]-e[0],t.pointer[1]-e[1])<=n}const sp="1.64.0",np={badge:"display.badge",icon_ripple:"display.icon_ripple",value:"display.value",static_icon:"display.static_icon"},op={badge:"marker.display_hint_badge",icon_ripple:"marker.display_hint_icon_ripple",value:"marker.display_hint_value",static_icon:"marker.display_hint_static_icon"},rp=2e3,ap=1e3,lp=1500,cp=t=>{const e=String(t??"").trim().replace(",",".");if(!/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)$/.test(e))return null;const i=Number(e);return Number.isFinite(i)?i:null},hp=(t,e)=>{if(!t.has(e))return{hit:!1};const i=t.get(e);return t.delete(e),t.set(e,i),{hit:!0,value:i}},dp=(t,e,i,s)=>{for(t.delete(e),t.set(e,i);t.size>s;){const e=t.keys().next().value;if(void 0===e)break;t.delete(e)}},up=new Map;let pp=0;const _p=t=>`${window.innerWidth}x${window.innerHeight}|${location.pathname}|${JSON.stringify(t??{})}`;let mp=1e4;const gp="houseplan_card_layout_v1",fp="houseplan_card_cfg_v1",vp="houseplan_card_zoom_v1",yp="houseplan_card_nav_v1",bp="houseplan_card_kiosk_v1",wp="houseplan_card_view_v1",kp=1e3,$p=[[0,1],[45,.88],[70,.62],[86,.32],[100,0]],xp=(t,e)=>{const i=Math.min(t.x,e.x),s=Math.min(t.y,e.y);return{x:i,y:s,w:Math.max(t.x+t.w,e.x+e.w)-i,h:Math.max(t.y+t.h,e.y+e.h)-s}},Sp=new Set(["select","draw","partition","column","merge","split","resize","opening","boundary","wallthick","delroom"]),Mp=(t,e,i)=>{const s=new Event(e,{bubbles:!0,composed:!0});s.detail=i??{},t.dispatchEvent(s)},Cp=t=>{history.pushState(null,"",t),Mp(window,"location-changed",{replace:!1})},Dp=(t,e)=>{let i,s=null;const n=(...n)=>{clearTimeout(i),s=n,i=window.setTimeout(()=>{i=void 0;const e=s;s=null,e&&t(...e)},e)};return n.flush=()=>{if(void 0===i)return;clearTimeout(i),i=void 0;const e=s;s=null,e&&t(...e)},n.pending=()=>void 0!==i,n},Tp=t=>{try{t.target?.setPointerCapture?.(t.pointerId)}catch{}};class Pp extends ht{constructor(){super(...arguments),this._space="f1",this._layout={},this._serverStorage=!1,this._loadOk=!1,this._serverCanWrite=null,this._loading=!1,this._loadTries=0,this._serverCfg=null,this._cfgRev=0,this._cfgContentFingerprint="",this._unsubCfg=null,this._unsubLayout=null,this._unsubVirtual=null,this._liveSyncAttempt=null,this._liveSyncGeneration=0,this._liveSyncConnection=null,this._layoutRev=0,this._layoutContentFingerprint="",this._virtualLights=rn(null),this._canOptimizeUndo=!1,this._undoKind=null,this._devices=[],this._regSignature="",this._defPos={},this._newSyncKey="",this._tip=null,this._hoverRoom=null,this._selId=null,this._toast="",this._mode="view",this._pendingNavMode=null,this._decorTool="select",this._decorStyle={...jr},this._decorDraft=null,this._decorMove=null,this._decorSel=null,this._decorEraseConfirm=null,this._decorTextDialog=null,this._decorShapeDialog=null,this._backdropDialog=null,this._decorTextSelection={start:0,end:0},this._furnPalette=null,this._dtBox=null,this._dtDrag=null,this._bdDrag=null,this._slide="",this._reducedMotion=!1,this._onMotionChange=t=>{this._reducedMotion=t.matches,t.matches&&this._modeTransitionPreparing?this._modeTransitionForceAtomic=!0:t.matches&&this._modeTransition.active&&this._cancelModeTransition(!0),this.requestUpdate()},this._editorChromeMode="plan",this._modeTransitionVisual=null,this._modeTransitionPreparing=!1,this._modeTransitionForceAtomic=!1,this._modeTransitionRequest=0,this._modeTransitionTargetZoom=1,this._modeTransitionEditorCamera=null,this._modeTransition=new hu({frame:t=>this._applyModeTransitionFrame(t),settled:t=>this._settleModeTransition(t)}),this._editorSecondary=new Od({root:()=>this.renderRoot,requestUpdate:()=>this.requestUpdate(),updateComplete:()=>this.updateComplete,clearTip:()=>{this._tip=null}}),this._editorSecondaryCopy={groupActive:(t,e)=>this._t("editor.group_active",{group:t,item:e}),openGroup:t=>this._t("editor.open_group",{group:t}),disabledAction:(t,e)=>this._t("editor.disabled_action",{action:t,reason:e})},this._tool="draw",this._geometryHistory=new Zd(50),this._wallDialog=null,this._drawWallField=null,this._activeDraftId=null,this._resumeDraftBySpace={},this._physicalSel=null,this._physicalDialog=null,this._physicalDrag=null,this._physicalRotate=null,this._physicalLastTap=null,this._physicalPickCycle=null,this._wallUnionCache=null,this._isoGeometryCache=new Map,this._isoFallback=new Set,this._openingTunnelCache=null,this._openingWallIndexCache=new Map,this._openingPlacementIntervalsCache=null,this._planSnapGeometryCache=null,this._physicalBodiesCache=null,this._cleanFloorCache=new Map,this._glowClipCache=new Map,this._lightBarrierCache=null,this._glowFeatherUnits=null,this._glowRenderedSources=new Map,this._glowLastAppearance=new Map,this._glowEnteringSources=new Set,this._glowEnterRafs=new Map,this._glowFadeTimers=new Map,this._glowFeatherSuspendUntil=0,this._glowFeatherResumeTimer=0,this._glowSourceSeq=0,this._glowScreenBlend=!1,this._duplicateColumnId=null,this._duplicateColumnTimer=0,this._rszSel=null,this._rszDrag=null,this._rszPreview=null,this._rszLive=null,this._path=[],this._cursorPt=null,this._planSnapHover=null,this._mergeSel=null,this._openingPreset=null,this._openingPresetRevision=0,this._openingHoverCandidate=null,this._openingDialog=null,this._openingInfo=null,this._opDrag=null,this._opMeasure=null,this._mergeDialog=null,this._openWallAnchor=null,this._boundaryPointerType="mouse",this._boundaryRestoreGuard=null,this._boundaryTargetMemo=null,this._boundaryPreviewMemo=null,this._splitSel=null,this._pendingSplit=null,this._areaSel="",this._nameSel="",this._roomDialog=!1,this._roomEditId=null,this._roomFill="",this._roomCustomFill=null,this._roomTempSrc="",this._roomHumSrc="",this._roomSrcOpen=null,this._roomSrcFilter="",this._roomNameScale=1,this._roomLabelScale=1,this._zoom=1,this._view=null,this._zoomBySpace={},this._viewModeSnap=null,this._pointers=new Map,this._panStart=null,this._panLock=null,this._pinchStart=null,this._suppressClick=!1,this._touchContacts=new Map,this._touchSequenceMultitouch=!1,this._touchClickBlockUntil=0,this._connectedPath="",this._routeDepartureHandled=!1,this._onLocationChanged=()=>{this._connectedPath&&location.pathname!==this._connectedPath?this._leaveCardRoute():location.pathname===this._connectedPath&&(this._routeDepartureHandled=!1)},this._touchGestureGuard={capture:!0,handleEvent:t=>this._guardTouchGesture(t)},this._hdrH=118,this._booting=!0,this._bootFading=!1,this._bootSettling=!1,this._bootSettleRaf=0,this._bootLastH=-1,this._bootStart=0,this._bootLastChange=0,this._bootSoft=!1,this._tapConfirm=null,this._onboardingShown=!1,this._rulesDialog=null,this._alignDialog=null,this._settingsDialog=null,this._backupExportDialog=null,this._backupImportDialog=null,this._sunRaysCache=null,this._dayCycleTimer=0,this._dayCycleClockKey="",this._compassDrag=!1,this._importDialog=null,this._importQueue=[],this._importTotal=0,this._rulesCompiledSrc="",this._infoCard=null,this._nativeMoreInfoEntity=null,this._markerDialog=null,this._spaceDialog=null,this._keyHandler=t=>this._onKey(t),this._warmVp=null,this._warmVpArmed=!1,this._warmLongReturn=!1,this._warmRevivePending=!1,this._warmGen=++pp,this._warmKey=null,this._warmSlot=null,this._hashApplied=!1,this._navApplied=!1,this._labs={active:Object.freeze([]),space:""},this._viewPreference={},this._renderProjection="flat",this._kioskScale={icon:1,font:1},this._kioskDialog=!1,this._activityRt=new Map,this._vacRt=new Map,this._vacViewKey="",this._vacLastView=null,this._vacRaf=0,this._vacSrvTrails={},this._vacJumpOnce=!1,this._continuity=this._newContinuityController(),this._continuityHistory=[],this._continuityEpoch=0,this._continuityDataReady=!0,this._continuityPaintToken=-1,this._continuityDisposed=!1,this._renderSnapshotAt=Date.now(),this._hassSequence=0,this._visibleDeviceSnapshot=null,this._candidateDeviceSnapshot=null,this._stagedDeviceSnapshotToken=-1,this._capturedSnapshotSequence=-1,this._capturedSnapshotDevices=null,this._capturedSnapshotLayout=null,this._capturedSnapshotActivity="",this._capturedSnapshotConfigEpoch=-1,this._capturedSnapshotVirtual="",this._lastValidStageSize=null,this._pendingRefitSize=null,this._refitRaf=0,this._pageVisibility=t=>{if(this._continuity.visibility(t),this._dayCycleVisibility(t),"hidden"!==t.kind){if(this._vacJumpOnce=!0,!t.long){const t=Date.now();let e=!1;for(const i of this._activityRt.values())!i.flashKind||(i.expiresAt||i.flashTs+Cd)>t||(i.flashTs=0,i.flashKind=null,i.expiresAt=0,e=!0);return void(e&&this.requestUpdate())}Date.now()-this._renderSnapshotAt>1e3&&this._continuity.note("device-snapshot-stale"),this._continuityDataReady=!1,this._continuityPaintToken=-1,this._resumeSettling=!0,this._loading?this.requestUpdate():this._loadFromServer()}else this._modeTransitionPreparing?this._modeTransitionForceAtomic=!0:this._cancelModeTransition(!0)},this._resumeSettling=!1,this._viewportInvalidAt=0,this._vacFit=null,this._vacAllCamerasFor=null,this._vacAllCameraCache=null,this._vacCalConfirm=null,this._kioskDots=!1,this._cyclePausedUntil=0,this._swipeStart=null,this._lastTap=0,this._onLabsSnapshot=t=>{const e=this._effectiveProjection();this._labs=t;const i=this._effectiveProjection();this._convertProjectionView(e,i),this.requestUpdate()},this._onHashChange=()=>{const t=this._hashSpace();t&&this._model.find(e=>e.id===t)&&t!==this._space&&(this._activeDraftId&&(this._resumeDraftBySpace[this._space]=this._activeDraftId),this._space=t,this._selId=null,this._path=[],this._clearPlanSnapHover(),this._clearOpeningPlacement(!0),this._tool="draw",this._openWallAnchor=null,this._boundaryRestoreGuard=null,this._activeDraftId=null,this._draftSegmentCms=[],this._closingWallCm=null,"plan"===this._mode&&"draw"===this._tool&&this._resumeLastDraft(),this._restoreZoom(),this.requestUpdate())},this._drag=null,this._rlResize=null,this._holdFired=!1,this._retryContinuity=()=>{this._continuityDataReady=!1,this._continuityPaintToken=-1,this._continuity.retry(this._continuity.recoveryReason||"plan"),this._loading||this._loadFromServer()},this._cfgEpoch=0,this._modelCache=null,this._decorSnapCache=null,this._markerPreviewMemo=null,this._markerPreviewDevicesMemo=null,this._showHidden=!1,this._connHooked=null,this._connectionWasLost=!1,this._haRegistryConnection=null,this._haRegistryRev=-1,this._haBindingCacheKey="",this._planHassMemo=null,this._onHaRegistryUpdate=()=>{const t=xn(this.hass);t.revision===this._haRegistryRev&&this._devices.length||(this._haRegistryRev=t.revision,this._planHassMemo=null,this._regSignature="",this._maybeRebuildDevices(),this.requestUpdate())},this._onConnReady=()=>{if(this._loadTries=0,clearTimeout(this._loadRetryTimer),this._loadRetryTimer=void 0,function(t){const e=vn(t);e&&(wn(t,e),kn(t,e))}(this.hass),this._saveConfigDebounced.pending()&&this._saveConfigDebounced.flush(),this._cfgWriting)return clearTimeout(this._reloadRetry),void(this._reloadRetry=window.setTimeout(this._onConnReady,400));!this._connectionWasLost&&this._continuity.hasCompleteFrame?this._beginContinuityCandidate("connection-ready",!1,"plan"):(this._continuityDataReady=!1,this._continuityPaintToken=-1),this._loading||this._loadFromServer()},this._onConnLost=()=>{this._booting&&!this._continuity.hasCompleteFrame||(this._connectionWasLost=!0,this._continuityDataReady=!1,this._continuityPaintToken=-1,this._continuity.connectionLost())},this._signer=new Wc(()=>this.requestUpdate()),this._dirtyPos=new Set,this._sentPos=new Map,this._persistLayout=Dp(()=>{if(this._serverStorage){const t=[...this._dirtyPos];this._dirtyPos.clear();for(const e of t){const t=this._layout[e];t&&(this._sentPos.set(e,t),this.hass.callWS({type:"houseplan/layout/update",device_id:e,pos:t}).then(t=>this._noteLayoutRev(t)).catch(t=>this._showToast(this._t("toast.pos_save_failed",{err:this._errText(t)}))).finally(()=>{this._sentPos.get(e)===t&&this._sentPos.delete(e)}))}this._cacheSnapshot()}else localStorage.setItem(gp,JSON.stringify(this._layout))},600),this._frame=null,this._showFar=!1,this._writesPending=0,this._writeChain=Promise.resolve(),this._undoGeometry=()=>{if(this._cancelBoundaryAnchor())return;if(this._physicalDrag||this._physicalRotate)return void this._cancelPhysicalGesture();if(this._decorDraft)return this._decorDraft=null,void this.requestUpdate();if(this._decorMove||this._dtDrag||this._bdDrag)return void this._cancelDecorGesture();if(this._rszDrag)return void this._rszCancelDrag();const t=this._geometryHistory.undo();t&&(this._applyGeometryState(t.before)?this._showToast(this._t("history.undone",{name:t.name})):this._geometryHistory.clear())},this._redoGeometry=()=>{if(this._cancelBoundaryAnchor())return;if(this._physicalDrag||this._physicalRotate)return void this._cancelPhysicalGesture();if(this._decorDraft)return this._decorDraft=null,void this.requestUpdate();if(this._decorMove||this._dtDrag||this._bdDrag)return void this._cancelDecorGesture();if(this._rszDrag)return void this._rszCancelDrag();const t=this._geometryHistory.redo();t&&(this._applyGeometryState(t.after)?this._showToast(this._t("history.redone",{name:t.name})):this._geometryHistory.clear())},this._saveConfigDebounced=Dp(()=>{this._serverCfg&&this._writeConfig().catch(t=>{"conflict"===t?.code?(this._showToast(this._t("toast.conflict")),this._cancelPath(),this._reloadConfigOnly(!0)):this._showToast(this._t("toast.cfg_save_failed",{err:this._errText(t)}))})},500),this._draftSegmentCms=[],this._closingWallCm=null,this._savePhysicalDialog=()=>{const t=this._physicalDialog,e=this._curSpaceCfg;if(!t||!e)return;const i=cp(t.cm);if(null==i)return void this._showPhysicalRange("column"===t.kind?ll:100);const s=this._imperial?2.54*i:i,n="column"===t.kind?ll:100;if(!Number.isFinite(s)||s<1||s>n)return void this._showPhysicalRange(n);if("column"===t.kind){const e=this._spaceModel().wall_columns.find(e=>e.id===t.id);if(!e)return;const i=cp(t.angle||"0");if("circle"!==t.shape&&(null==i||i<0||i>=90))return void this._showToast(this._t("toast.physical_angle"));const n="circle"===t.shape?{id:t.id,shape:"circle",center:e.center,cm:s}:{id:t.id,shape:"square",center:e.center,cm:s,angle:i};if(this._spaceModel().wall_columns.some(e=>e.id!==t.id&&Sl(e,n,.02*this._gridPitch)))return void this._showToast(this._t("toast.column_duplicate"))}const o=this._geometrySnapshot();if("partition"===t.kind){const i=(e.partitions||[]).find(e=>e.id===t.id);i&&(i.cm=s)}else if("column"===t.kind){const i=(e.wall_columns||[]).find(e=>e.id===t.id);i&&(i.cm=cl(s),i.shape="circle"===t.shape?"circle":"square","square"===i.shape?i.angle=cp(t.angle||"0"):delete i.angle)}else{const i=(e.room_drafts||[]).find(e=>e.id===t.id);i?.segments?.[t.segment||0]&&(i.segments[t.segment||0].cm=s)}this._recordGeometry(this._t("history.physical_edit"),o),this._physicalDialog=null,this._saveConfig()},this._deletePhysicalSelection=()=>{const t=this._physicalSel,e=this._curSpaceCfg;if(!t||!e)return;if("draft"===t.kind)return void this._deleteDraftWhole();const i=this._geometrySnapshot(),s="partition"===t.kind?"partitions":"column"===t.kind?"wall_columns":"room_drafts";e[s]=(e[s]||[]).filter(e=>e.id!==t.id),e[s].length||delete e[s],this._activeDraftId===t.id&&this._cancelPath(),this._physicalSel=null,this._physicalDialog=null,this._recordGeometry(this._t("history.physical_delete"),i),this._saveConfig()},this._deleteDraftWhole=()=>{const t="draft"===this._physicalDialog?.kind?this._physicalDialog.id:"draft"===this._physicalSel?.kind?this._physicalSel.id:null,e=this._curSpaceCfg;if(!t||!e||!confirm(this._t("confirm.delete_draft")))return;const i=this._geometrySnapshot();e.room_drafts=(e.room_drafts||[]).filter(e=>e.id!==t),e.room_drafts.length||delete e.room_drafts,this._activeDraftId===t&&this._cancelPath(),this._physicalSel=null,this._physicalDialog=null,this._recordGeometry(this._t("history.physical_delete"),i),this._saveConfig()},this._deleteDraftSegment=()=>{const t=this._physicalDialog,e=this._curSpaceCfg;if(!t||"draft"!==t.kind||!e)return;if(!confirm(this._t("confirm.delete_draft_segment")))return;const i=(e.room_drafts||[]).findIndex(e=>e.id===t.id);if(i<0)return;const s=e.room_drafts[i],n=Math.max(0,Math.min(s.segments.length-1,t.segment||0)),o=[],r=s.points.slice(0,n+1),a=s.points.slice(n+1);if(r.length>=2&&o.push({id:s.id,points:r,segments:s.segments.slice(0,n)}),a.length>=2&&o.push({id:o.length?`${s.id}-${Date.now().toString(36)}`:s.id,points:a,segments:s.segments.slice(n+1)}),2===o.length&&e.room_drafts.length>=200)return void this._showToast(this._t("toast.physical_limit"));const l=this._geometrySnapshot();e.room_drafts.splice(i,1,...o),e.room_drafts.length||delete e.room_drafts,this._activeDraftId===s.id&&this._cancelPath(),this._physicalDialog=null,this._physicalSel=o.length?{kind:"draft",id:o[0].id}:null,this._recordGeometry(this._t("history.draft_segment_delete"),l),this._saveConfig()},this._keepClosedAsPartitions=()=>{if(!this._contourClosed||this._pendingSplit||!this._curSpaceCfg)return;const t=this._curSpaceCfg,e=this._path.slice(0,-1);if((t.partitions||[]).length+e.length>2e3)return void this._showToast(this._t("toast.physical_limit"));const i=this._geometrySnapshot(),s=[...this._draftSegmentCms,this._closingWallCm||this._drawWallCm||ea];t.partitions||=[];const n=Date.now().toString(36);for(let i=0;it.id!==this._activeDraftId),t.room_drafts.length||delete t.room_drafts),this._recordGeometry(this._t("history.contour_to_partitions"),i),this._saveConfig(),this._roomDialog=!1,this._path=[],delete this._resumeDraftBySpace[this._space],this._activeDraftId=null,this._draftSegmentCms=[],this._closingWallCm=null},this._toggleServerPlans=async()=>{const t=this._spaceDialog;if(t)if(t.pickSaved)this._spaceDialog={...t,pickSaved:!1};else{this._spaceDialog={...t,pickSaved:!0,savedBusy:!0};try{const t=await this.hass.callWS({type:"houseplan/plans/list"}),e=this._spaceDialog;e&&(this._spaceDialog={...e,saved:t?.plans||[],savedBusy:!1})}catch(t){const e=this._spaceDialog;e&&(this._spaceDialog={...e,saved:[],savedBusy:!1}),this._showToast(this._t("toast.plans_list_failed",{err:this._errText(t)}))}}},this._aspectJob=null,this._dayCycleTick=()=>{if(!this.isConnected||"hidden"===this.ownerDocument.visibilityState)return;const t=this._dayCycleState();if(!t)return this._dayCycleTimer&&(clearInterval(this._dayCycleTimer),this._dayCycleTimer=0),void(this._dayCycleClockKey="");const e=Cr(t);e!==this._dayCycleClockKey&&(this._dayCycleClockKey=e,this.requestUpdate())},this._sunShown=!1,this._sunOut=!1,this._sunOutTimer=0,this._openSettingsDialog=()=>{if(!this._norm)return;const t=this._glowRadiusCm,e=this._imperial?Math.round(t/30.48*10)/10:Math.round(t)/100;this._settingsDialog={colors:JSON.parse(JSON.stringify(this._fillColors)),glowRadius:e,bgColor:es(this._settings,{bgColor:null})||null,northDeg:Fr(this._settings,{}),bgMode:Or(this._settings,{}),sunRays:Nr(this._settings,{}),busy:!1}},this._openAlignDialog=()=>{if(!this._norm||!this._serverCfg)return;const t=this._serverCfg.spaces||[],e=Xd(this._serverCfg,this._layout||{}),i=Math.ceil(10*e.report.maxShiftCm)/10,s=t.find(t=>null!=t?.id&&String(t.id)===e.report.maxSpace),n=t.length>1&&s?String(s.title||s.id):"";this._alignDialog={report:e.report,config:e.config,layout:e.layout,cm:i,where:n,changed:e.changed,busy:!1}},this._optimizeUndoBusy=!1,this._openBackupExport=()=>{this._settingsDialog=null,this._backupExportDialog={kind:"full",planOnly:!1,busy:!1,error:""}},this._openRulesDialog=()=>{if(!this._norm)return;const t=this._settings.icon_rules,e=(t&&t.length?t:jt).map(t=>({...t}));this._rulesDialog={rules:e,test:"",busy:!1}},this._climateCache=null,this._gearPtCache=new WeakMap}get _canEdit(){return!!this._norm&&(!0===this._serverCanWrite||!1!==this._serverCanWrite&&!0===this.hass?.user?.is_admin)}get _kiosk(){return!!this._config?.kiosk}_showKioskDots(){this._kioskDots=!0,clearTimeout(this._kioskDotsTimer),this._kioskDotsTimer=window.setTimeout(()=>this._kioskDots=!1,2500)}get _modeTransitionBusy(){return this._modeTransitionPreparing||this._modeTransition.active}_cssColor(t,e){const i=String(t||"").trim();if(!i)return e;const s=document.createElement("span");s.style.cssText=`position:absolute;visibility:hidden;color:${i}`,this.renderRoot.append(s);const n=getComputedStyle(s).color||e;return s.remove(),n}_currentModeVisual(t=this._mode){const e=this._modeTransition.presented||this._modeTransitionVisual;if(e)return{...e,viewport:{...e.viewport,viewBox:{...e.viewport.viewBox}}};const i=this._stageEl;if(!i||i.clientWidth<=0||i.clientHeight<=0)return null;const s=this.renderRoot.querySelector(".editorchrome"),n=this._viewOr(this._baseVb()),o=this.renderRoot.querySelector(".hp-paper"),r=this.renderRoot.querySelector(".hp-backdrop"),a=this.renderRoot.querySelector(".zoomwrap"),l=a?getComputedStyle(a).filter:"",c=Number(/brightness\(([^)]+)\)/.exec(l)?.[1]);return{presentedMode:t,editorChromeHeight:"view"===t?0:s?.getBoundingClientRect().height||0,stageWidth:i.clientWidth,stageHeight:i.clientHeight,viewport:ou(n,i.clientWidth),stageColor:getComputedStyle(i).backgroundColor||"rgb(255, 255, 255)",paperColor:o?getComputedStyle(o).fill:"rgb(255, 255, 255)",sceneBrightness:Number.isFinite(c)?c:1,architectureOpacity:"decor"===t?.35:1,backdropOpacity:r&&Number(getComputedStyle(r).opacity)||1,viewWeight:"view"===t?1:0,editorWeight:"view"===t?0:1,toolbarContentOpacity:"view"===t?0:1}}_viewForModeTarget(t,e,i,s,n){const o=Di(this._baseVb(),s/Math.max(1,n)),r=Math.min(Pp.ZOOM_MAX,Math.max(Pp.ZOOM_MIN,t)),a=o.w/r,l=o.h/r,c=e??o.x+o.w/2,h=i??o.y+o.h/2;return this._clampView({x:c-a/2,y:h-l/2,w:a,h:l},o)}_targetStageColor(t){return"view"!==t?"rgb(255, 255, 255)":this._cssColor(this._stageBg(this._spaceDisplayForRender()),this._cssColor("var(--ha-card-background, var(--card-background-color, #111))","rgb(17, 17, 17)"))}_targetPaperColor(t){return"view"===t&&this._spaceModel().bg?this._cssColor("var(--ha-card-background, var(--card-background-color, #111))","rgb(17, 17, 17)"):"rgb(255, 255, 255)"}_targetBrightness(t){return 1}_applyModeTransitionFrame(t){t.targetMode===this._mode&&(this._modeTransitionVisual=t.presented,this._view={...t.presented.viewport.viewBox},this.requestUpdate())}_settleModeTransition(t){if(t.targetMode!==this._mode)return;const e=this._modeTransitionRequest;this._view={...t.to.viewport.viewBox},this._zoom=this._modeTransitionTargetZoom,this._modeTransitionVisual=null,this._modeTransitionPreparing=!1,this._lastValidStageSize=[t.to.stageWidth,t.to.stageHeight],"view"===this._mode&&(this._saveZoom(),this._viewModeSnap=null,this._modeTransitionEditorCamera=null),this.requestUpdate(),this.updateComplete.then(()=>{if(!this.isConnected||e!==this._modeTransitionRequest||this._modeTransitionBusy||t.targetMode!==this._mode)return;const i=this._stageEl;if(i&&i.clientWidth>0&&i.clientHeight>0&&(Math.abs(i.clientWidth-t.to.stageWidth)>.5||Math.abs(i.clientHeight-t.to.stageHeight)>.5)){const t=this._view;this._lastValidStageSize=[i.clientWidth,i.clientHeight],this._applyView(this._zoom,t?t.x+t.w/2:void 0,t?t.y+t.h/2:void 0),this.requestUpdate()}const s=this.renderRoot.activeElement;(!s||!s.isConnected||!!s.closest?.(".editorchrome, .stage"))&&this.renderRoot.querySelector(".modetab.active")?.focus?.({preventScroll:!0})})}_cancelModeTransition(t=!0){const e=!!this._modeTransition.state;this._modeTransitionRequest++,this._modeTransitionPreparing=!1,this._modeTransitionForceAtomic=!1,this._modeTransition.cancel(t),t&&e||(this._modeTransitionVisual=null)}_adoptMode(t){this._cancelModeTransition(!1),this._mode=t,"view"!==t&&(this._editorChromeMode=t)}_commitViewModeAtomic(t,e,i,s){if(this._modeTransitionPreparing=!1,this._modeTransitionVisual=null,this._modeTransitionForceAtomic=!1,this._zoom=e,t){const n=this._stageEl?.clientWidth||t.stageWidth,o=Math.max(1,t.stageHeight+t.editorChromeHeight);this._view=this._viewForModeTarget(e,i,s,n,o),this._lastValidStageSize=[n,o]}else this._view=null;this._viewModeSnap=null,this._modeTransitionEditorCamera=null,this._saveZoom(),this.requestUpdate()}_prepareModeTransition(t,e,i,s,n,o){this.updateComplete.then(()=>{if(!this.isConnected||t!==this._modeTransitionRequest||this._mode!==i)return void(t===this._modeTransitionRequest&&(this._modeTransitionPreparing=!1,this._modeTransitionVisual=null,this._modeTransitionForceAtomic=!1,this.requestUpdate()));const r=this.renderRoot.querySelector(".editorchrome"),a=r?.querySelector(".editorchrome-inner"),l="view"===i?0:a?.scrollHeight||a?.getBoundingClientRect().height||0,c=Math.max(1,e.stageHeight+e.editorChromeHeight),h=Math.max(1,c-l),d=this._stageEl?.clientWidth||e.stageWidth;if(d<=0||h<=0)return this._modeTransitionPreparing=!1,this._modeTransitionVisual=null,this._applyView(s,n,o),void this.requestUpdate();const u=this._viewForModeTarget(s,n,o,d,h),p={presentedMode:i,editorChromeHeight:l,stageWidth:d,stageHeight:h,viewport:ou(u,d),stageColor:this._targetStageColor(i),paperColor:this._targetPaperColor(i),sceneBrightness:this._targetBrightness(i),architectureOpacity:"decor"===i?.35:1,backdropOpacity:"decor"===i&&"backdrop"!==this._decorTool?.5:1,viewWeight:"view"===i?1:0,editorWeight:"view"===i?0:1,toolbarContentOpacity:"view"===i?0:1};this._modeTransitionPreparing=!1;const _=this._modeTransitionForceAtomic;this._modeTransitionForceAtomic=!1,this._modeTransition.start(e,p,i,this._reducedMotion||_?0:220)})}_slideTo(t,e){if(t===this._space)return;this._cancelModeTransition(!0);const i=window.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches;this._activeDraftId&&(this._resumeDraftBySpace[this._space]=this._activeDraftId),this._space=t,this._path=[],this._clearPlanSnapHover(),this._clearOpeningPlacement(!0),this._tool="draw",this._openWallAnchor=null,this._boundaryRestoreGuard=null,this._activeDraftId=null,this._draftSegmentCms=[],this._closingWallCm=null,this._selId=null,this._physicalSel=null,this._editorSecondary.closeForNavigation(),this._physicalDialog=null,this._physicalDrag=null,"plan"===this._mode&&"draw"===this._tool&&this._resumeLastDraft(),this._restoreZoom(),i||(this._slide=e,clearTimeout(this._slideTimer),this._slideTimer=window.setTimeout(()=>{this._slideTimer=void 0,this._slide="",this.requestUpdate()},190),this.requestUpdate())}_pickSpace(t){if(t===this._space)return;const e=this._model.map(t=>t.id),i=e.indexOf(this._space),s=e.indexOf(t);this._navApplied=!0,this._showFar=!1,this._frame=null,this._slideTo(t,i>=0&&s0&&Date.now()>=this._cyclePausedUntil&&this._model.length>1&&this._zoom<=1.001){const t=this._model.map(t=>t.id),e=t.indexOf(this._space);this._slideTo(t[(e+1)%t.length],"left"),this._showKioskDots()}}get _editing(){return"plan"===this._mode||"devices"===this._mode||"decor"===this._mode}get _markup(){return"plan"===this._mode}_newContinuityController(){return new Hc(()=>{this._resumeSettling="steady"!==this._continuity.state,this._continuityEpoch++,this.isConnected&&this.requestUpdate()})}get _labsIso(){return this._labs.active.includes("iso")}get _desiredProjection(){return"view"===this._mode&&this._labsIso&&"iso"===this._viewPreference[this._space]?"iso":"flat"}_saveViewPreference(){try{localStorage.setItem(wp,JSON.stringify(this._viewPreference))}catch{}}_logicalViewCenter(t){const e=this._view;if(!e)return null;const i=[e.x+e.w/2,e.y+e.h/2],s="iso"===t?Iu(i):i;return{x:s[0],y:s[1]}}_convertProjectionView(t,e){if(t===e)return;const i=this._logicalViewCenter(t);this._view=null;const s=i?"iso"===e?Au([i.x,i.y],0):[i.x,i.y]:null;this._applyView(this._zoom,s?.[0],s?.[1]),this._warmPatch({vp:this._warmViewportState()}),this.requestUpdate()}_setProjection(t){if(!this._labsIso||"view"!==this._mode)return;const e=this._effectiveProjection();if(this._viewPreference={...this._viewPreference,[this._space]:t},"iso"===t){const t=this._isoSceneKey();t&&this._isoFallback.delete(t)}this._saveViewPreference();const i=this._effectiveProjection();this._convertProjectionView(e,i)}_hashSpace(){return vu(window.location.hash||"")}connectedCallback(){this._connectedPath=location.pathname,this._routeDepartureHandled=!1,window.addEventListener("location-changed",this._onLocationChanged),window.addEventListener("popstate",this._onLocationChanged),this._continuityDisposed&&(this._continuity=this._newContinuityController(),this._continuityDisposed=!1,this._continuityPaintToken=-1);const t=(e=this.ownerDocument,Jd.get(e)?.resolved);var e;void 0!==t&&(this._glowScreenBlend=t),this._continuityUnsub?.(),this._continuityUnsub=Nc(this.ownerDocument,this._pageVisibility),super.connectedCallback(),this._motionMedia=window.matchMedia?.("(prefers-reduced-motion: reduce)"),this._reducedMotion=!!this._motionMedia?.matches,this._motionMedia?.addEventListener?.("change",this._onMotionChange),function(t){const e=Jd.get(t);if(e)return e.promise;const i={promise:Promise.resolve(!1)};return i.promise=tu(t).catch(()=>!1).then(t=>(i.resolved=t,t)),Jd.set(t,i),i.promise}(this.ownerDocument).then(t=>{t!==this._glowScreenBlend&&(this._glowScreenBlend=t,this.isConnected&&this.requestUpdate())}),this.hass&&this._ensureHaRegistryAuthority(),window.addEventListener("keydown",this._keyHandler),this._signer.start(()=>this.hass,()=>$s(this._serverCfg)),this._config?.kiosk&&Number(this._config?.cycle)>0&&(clearInterval(this._cycleTimer),this._cycleTimer=window.setInterval(()=>this._cycleTick(),1e3*Number(this._config.cycle))),window.addEventListener("hashchange",this._onHashChange),this._labsUnsub?.(),this._labsUnsub=function(t,e){Su.add(e);const i=Du(t);return"undefined"==typeof window&&e(i),()=>{Su.delete(e)}}(sp,this._onLabsSnapshot),this._booting?this._bootWatch():this._bootFading&&(clearTimeout(this._bootTimer),this._bootTimer=window.setTimeout(()=>{this._bootFading=!1},220)),this._bootSoft&&(clearTimeout(this._bootSoftTimer),this._bootSoftTimer=window.setTimeout(()=>{this._bootSoft=!1},lp)),!this._loadOk&&this._serverCfg&&this.hass&&this._scheduleLoadRetry(),!this._warmSlot&&this._config&&this._warmAdopt(),this._loadOk&&this._ensureLiveSyncSubscriptions(),this._warmVp&&!this._warmRevivePending&&void 0===this._warmReviveTimer&&(this._warmRevivePending=!0,this._warmReviveTimer=window.setTimeout(()=>this._warmReviveDialog(),0)),this._warmLongReturn&&this._beginResumeSettle(),this._warmLongReturn=!1,this.requestUpdate()}disconnectedCallback(){this._connectedPath&&location.pathname!==this._connectedPath&&this._leaveCardRoute(),window.removeEventListener("location-changed",this._onLocationChanged),window.removeEventListener("popstate",this._onLocationChanged),this._continuityUnsub?.(),this._continuityUnsub=void 0,this._motionMedia?.removeEventListener?.("change",this._onMotionChange),this._motionMedia=void 0,this._vacRaf&&(cancelAnimationFrame(this._vacRaf),this._vacRaf=0),this._refitRaf&&(cancelAnimationFrame(this._refitRaf),this._refitRaf=0),this._dayCycleTimer&&(clearInterval(this._dayCycleTimer),this._dayCycleTimer=0),this._dayCycleClockKey="",this._bootSettleRaf&&(cancelAnimationFrame(this._bootSettleRaf),this._bootSettleRaf=0),this._bootSettling=!1;for(const t of this._activityRt.values())clearTimeout(t.timer);window.removeEventListener("keydown",this._keyHandler),clearInterval(this._cycleTimer),clearTimeout(this._kioskDotsTimer),clearTimeout(this._kioskHoldTimer),clearTimeout(this._reloadRetry),clearTimeout(this._loadRetryTimer),this._loadRetryTimer=void 0,this._connHooked?.removeEventListener?.("ready",this._onConnReady),this._connHooked?.removeEventListener?.("disconnected",this._onConnLost),this._connHooked?.removeEventListener?.("reconnect-error",this._onConnLost),this._connHooked=null,this._haRegistryRelease?.(),this._haRegistryRelease=void 0,this._haRegistryConnection=null,this._signer.dispose(),clearTimeout(this._toastTimer),clearTimeout(this._slideTimer),this._modeTransition.dispose(),this._modeTransitionVisual=null,this._modeTransitionPreparing=!1,this._modeTransitionForceAtomic=!1,this._modeTransitionRequest++,this._slideTimer=void 0,this._slide="",clearTimeout(this._bootTimer),this._bootTimer=void 0,clearTimeout(this._bootSoftTimer),this._saveConfigDebounced.flush(),window.removeEventListener("hashchange",this._onHashChange),this._labsUnsub?.(),this._labsUnsub=void 0,clearTimeout(this._holdTimer),this._roViewport?.disconnect(),this._roViewport=void 0,this._roHdr?.disconnect(),this._roHdr=void 0,this._onWinResize&&(window.removeEventListener("resize",this._onWinResize),this._onWinResize=void 0),this._unsubCfg&&(this._unsubCfg(),this._unsubCfg=null),this._unsubLayout&&(this._unsubLayout(),this._unsubLayout=null),this._unsubVirtual&&(this._unsubVirtual(),this._unsubVirtual=null),this._unsubTrail&&(this._unsubTrail(),this._unsubTrail=void 0),this._liveSyncGeneration++,this._liveSyncAttempt=null,this._liveSyncConnection=null,clearTimeout(this._layoutSyncTimer),clearTimeout(this._duplicateColumnTimer);for(const t of this._glowFadeTimers.values())clearTimeout(t);for(const t of this._glowEnterRafs.values())cancelAnimationFrame(t);clearTimeout(this._glowFeatherResumeTimer),this._glowFeatherResumeTimer=0,this._glowFeatherSuspendUntil=0,this._glowFadeTimers.clear(),this._glowEnterRafs.clear(),this._glowEnteringSources.clear(),this._glowRenderedSources.clear(),this._glowLastAppearance.clear(),this._glowSourceSeq=0,this._warmSnapshot(),this._warmRevivePending=!1,clearTimeout(this._warmReviveTimer),this._warmReviveTimer=void 0,this._warmRelease(),this._openWallAnchor=null,this._boundaryRestoreGuard=null,this._clearPlanSnapHover(),this._clearOpeningPlacement(!0),this._touchContacts.clear(),this._touchSequenceMultitouch=!1,this._touchClickBlockUntil=0,this._editorSecondary.reset(),this._resumeSettling=!1,this._continuityHistory=[...this._continuityHistory,...this._continuity.trace].slice(-80),this._continuity.dispose(),this._continuityDisposed=!0,super.disconnectedCallback()}_onKey(t){if("Escape"===t.key&&this._vacFit)return this._vacFit=null,this._showToast(this._t("vac.cal_cancelled")),void t.stopPropagation();if("Escape"===t.key){if(this._tapConfirm)return void(this._tapConfirm=null);if(this._vacCalConfirm)return void(this._vacCalConfirm=null);if(this._decorEraseConfirm)return void(this._decorEraseConfirm=null);if(this._openingInfo)return void(this._openingInfo=null);if(this._infoCard)return void this._closeInfoCard();if(this._rulesDialog)return void(this._rulesDialog=null);if(this._alignDialog)return void(this._alignDialog=null);if(this._backupImportDialog)return void(this._backupImportDialog=null);if(this._backupExportDialog)return void(this._backupExportDialog=null);if(this._settingsDialog)return void(this._settingsDialog=null);if(this._markerDialog)return void(this._markerDialog=null);if(this._openingDialog)return void(this._openingDialog=null);if(this._physicalDialog)return void(this._physicalDialog=null);if(this._backdropDialog)return void(this._backdropDialog=null);if(this._decorShapeDialog)return void(this._decorShapeDialog=null);if(this._decorTextDialog)return void(this._decorTextDialog=null);if(this._spaceDialog&&!this._roomDialog)return this._spaceDialog=null,this._importQueue=[],void(this._importTotal=0);if(this._editorSecondary.hasOpenGroup)return t.preventDefault(),void this._editorSecondary.closeGroup(!0)}const e=t.composedPath?.()||[t.target],i=e.some(t=>t?.matches?.('input, textarea, select, [contenteditable="true"]')),s=e.some(t=>t?.classList?.contains?.("editor-secondary")),n=t.ctrlKey||t.metaKey,o=t.key.toLowerCase(),r=/^[a-z]$/.test(o),a="z"===o||!r&&"KeyZ"===t.code,l="y"===o||!r&&"KeyY"===t.code,c=n&&(a&&t.shiftKey||l),h=n&&a&&!t.shiftKey;if("decor"===this._mode){if((h||c)&&i)return;return c?(t.preventDefault(),void this._redoGeometry()):h?(t.preventDefault(),this._decorDraft?void(this._decorDraft=null):this._decorMove||this._dtDrag||this._bdDrag?void this._cancelDecorGesture():void this._undoGeometry()):"Delete"!==t.key&&"Backspace"!==t.key||!this._decorSel||i||s?void("Escape"===t.key&&(t.preventDefault(),this._decorDraft?this._decorDraft=null:this._decorMove||this._dtDrag||this._bdDrag?this._cancelDecorGesture():"furniture"===this._decorTool?(this._furnPalette=null,this._decorTool="select"):this._decorSel?this._decorSel=null:"select"!==this._decorTool?this._decorTool="select":this._setMode("view"))):(t.preventDefault(),void this._decorDeleteSel())}if(this._markup&&(!h&&!c||!i)){if(("Delete"===t.key||"Backspace"===t.key)&&this._physicalSel&&!i&&!s)return t.preventDefault(),void this._deletePhysicalSelection();if(c)return t.preventDefault(),void this._redoGeometry();if(h)return t.preventDefault(),this._rszDrag?void this._rszCancelDrag():"draw"!==this._tool&&"partition"!==this._tool||!this._path.length?"split"===this._tool&&this._splitSel?.pts?.length?(this._splitSel={...this._splitSel,pts:this._splitSel.pts.slice(0,-1)},void(this._splitSel.pts.length||(this._cursorPt=null))):void this._undoGeometry():void(this._activeDraftId&&this._path.length>1?this._undoGeometry():this._undoPoint());if("Escape"===t.key)return this._physicalDrag||this._physicalRotate?(t.preventDefault(),void this._cancelPhysicalGesture()):this._roomDialog?(t.preventDefault(),void this._roomDialogCancel()):"draw"===this._tool&&this._path.length||"partition"===this._tool&&this._path.length?(t.preventDefault(),void this._undoPoint()):this._physicalSel?(t.preventDefault(),void(this._physicalSel=null)):"resize"===this._tool?(t.preventDefault(),this._rszDrag?void this._rszCancelDrag():void(this._rszSel?this._rszSel=null:this._tool="draw")):"split"===this._tool?(t.preventDefault(),void(this._splitSel?.pts?.length?(this._splitSel={...this._splitSel,pts:this._splitSel.pts.slice(0,-1)},this._splitSel.pts.length||(this._cursorPt=null)):this._splitSel?this._splitSel=null:this._tool="draw")):"merge"===this._tool?(t.preventDefault(),void(this._mergeSel?this._mergeSel=null:this._tool="draw")):this._wallDialog?(t.preventDefault(),void(this._wallDialog=null)):"boundary"===this._tool?(t.preventDefault(),void(this._openWallAnchor?this._cancelBoundaryAnchor():this._tool="draw")):void("opening"!==this._tool&&"wallthick"!==this._tool&&"delroom"!==this._tool&&"partition"!==this._tool&&"column"!==this._tool||(t.preventDefault(),"opening"===this._tool&&this._clearOpeningPlacement(!0),this._tool="draw"))}}_undoPoint(){if(this._path.length){if(this._contourClosed)return this._path=this._path.slice(0,-1),void(this._closingWallCm=null);if(this._activeDraftId&&this._path.length>1&&this._curSpaceCfg){const t=this._geometrySnapshot();this._path=this._path.slice(0,-1),this._draftSegmentCms=this._draftSegmentCms.slice(0,-1);const e=this._curSpaceCfg,i=(e.room_drafts||[]).findIndex(t=>t.id===this._activeDraftId);return void(i>=0&&(this._path.length<2?(e.room_drafts.splice(i,1),e.room_drafts.length||delete e.room_drafts,this._activeDraftId=null):e.room_drafts[i]={id:this._activeDraftId,points:this._path.map(t=>[t[0]/kp,t[1]/kp]),segments:this._draftSegmentCms.map(t=>({cm:t}))},this._recordGeometry(this._t("history.draft_segment_delete"),t),this._saveConfig()))}this._path=this._path.slice(0,-1)}}static getConfigElement(){return document.createElement("houseplan-card-editor")}static getStubConfig(){return{type:"custom:houseplan-card"}}static _warmBootReset(t){for(const t of up.values())for(const e of t)clearTimeout(e.evict);up.clear(),mp=t&&t>0?t:1e4}static _warmBootStats(){let t=0,e=0;const i=[];for(const s of up.values())for(const n of s)t++,n.dlg&&(e++,i.push(n.dlg.kind));return{keys:up.size,slots:t,dlgs:e,drafts:i}}setConfig(t){this._config={icon_size:2.5,show_temperature:!0,live_states:!0,show_signal:!0,...t},this._config.kiosk&&(this._booting=!1,this._bootFading=!1),t.default_floor&&(this._space=t.default_floor);try{this._zoomBySpace=JSON.parse(localStorage.getItem(vp)||"{}")||{}}catch{this._zoomBySpace={}}try{const t=JSON.parse(localStorage.getItem(wp)||"{}")||{};this._viewPreference=Object.fromEntries(Object.entries(t).filter(t=>"flat"===t[1]||"iso"===t[1]))}catch{this._viewPreference={}}this._labs=Du(sp);try{const t=JSON.parse(localStorage.getItem(bp)||"null");this._kioskScale={icon:Cs(t?.icon),font:Cs(t?.font)}}catch{}try{const t=JSON.parse(localStorage.getItem(fp)||"null");t&&t.config&&Array.isArray(t.config.spaces)&&(this._serverCfg=t.config,this._cfgEpoch++,this._cfgRev=t.rev||0,this._cfgContentFingerprint=t.config_fingerprint||qc(t.config),this._layout=t.layout||{},this._layoutRev=t.layout_rev||0,this._layoutContentFingerprint=t.layout_fingerprint||qc(this._layout),this._virtualLights=rn(t.virtual_lights,this._cfgRev),this._serverStorage=!0,this._adoptInitialSpace(this._model))}catch{}"view"!==this._mode||this._view||(this._zoom=this._zoomBySpace[this._space]||1),this.isConnected&&(this._warmAdopt(),this._warmLongReturn&&this._beginResumeSettle(),this._warmLongReturn=!1)}_warmAdopt(){if(this._config?.kiosk)return;const t=_p(this._config);if(this._warmKey===t&&this._warmSlot)return;this._warmSlot&&this._warmRelease();const e=this.parentNode,i=this._warmIdx(e),s=up.get(t);if(!s||!s.length)return;const n=s.find(t=>t.owner===this._warmGen);if(n)return this._warmLongReturn=!!n.freed&&Date.now()-n.freed>=zc,clearTimeout(n.evict),n.evict=0,n.freed=0,n.live=!0,this._warmSlot=n,this._warmKey=t,n.frameFingerprint&&this._continuity.adoptCompleteFrame(n.frameFingerprint),void(!this._devices.length&&n.devices?.length&&(this._devices=[...n.devices]));const{slot:o,sure:r}=((t,e,i,s)=>{const n=t=>{const e=!!i&&t.place?.deref()===i;return e&&t.idx===s?4:t.live?0:e?3:2};let o=null,r=0,a=0,l=null;for(const i of t){if(i.owner===e)continue;l=i;const t=n(i);t<=0||(t>r?(o=i,r=t,a=1):t===r&&a++)}return!o||a>1?{slot:o||l,sure:!1}:{slot:o,sure:!0}})(s,this._warmGen,e,i);o&&(this._warmLongReturn=!!o.freed&&Date.now()-o.freed>=zc,this._booting=!1,this._bootFading=!1,this._hdrH=o.hdrH,this._bootSoft=!0,this.isConnected&&(clearTimeout(this._bootSoftTimer),this._bootSoftTimer=window.setTimeout(()=>{this._bootSoft=!1},lp)),this._warmKey=t,r?(clearTimeout(o.evict),o.evict=0,o.owner=this._warmGen,o.place=e?new WeakRef(e):null,o.idx=i,o.live=!0,this._warmSlot=o,this._warmVp=o.vp,o.frameFingerprint&&this._continuity.adoptCompleteFrame(o.frameFingerprint),!this._devices.length&&o.devices?.length&&(this._devices=[...o.devices]),this._warmAdoptViewport(this._config)):(this._warmSlot={owner:this._warmGen,path:location.pathname,place:e?new WeakRef(e):null,idx:i,live:!0,hdrH:o.hdrH,stageH:o.stageH,vp:null,frameFingerprint:"",devices:null,dlg:null,freed:0,evict:0},s.push(this._warmSlot),this._warmTrim(s)))}_warmIdx(t){const e=t?.children;if(!e)return-1;for(let t=0;t4;){const e=t.findIndex(t=>!t.live);if(e<0)break;clearTimeout(t[e].evict),t.splice(e,1)}}_warmScheduleEvict(t,e){if(clearTimeout(t.evict),!(t.dlg||t.vp&&"view"!==t.vp.mode))return;const i=t.freed,s=t.owner;t.evict=window.setTimeout(()=>{if(t.evict=0,t.freed!==i||t.owner!==s)return;var n;t.dlg=null,t.vp=(n=t.vp)&&"view"!==n.mode?{...n,mode:"view",zoom:n.snap?.space===n.space?n.snap.zoom:n.zoom,view:null,snap:null,tool:"draw",decorTool:"select",showHidden:!1,selId:null,rszSel:null,decorSel:null}:n,t.frameFingerprint="";const o=up.get(e);if(!t.live&&o&&o.length>1){const e=o.indexOf(t);e>=0&&o.splice(e,1)}},mp+250)}_warmAdoptViewport(t){const e=this._warmVp;if(!e||this._warmSlot?.path!==location.pathname)return void(this._warmVp=null);if(this._hashApplied||!this._model.find(t=>t.id===e.space))return void(this._warmVp=null);this._space=e.space,this._navApplied=!0,this._adoptMode("view"!==e.mode&&this._canEdit&&!t.kiosk?e.mode:"view"),this._pendingNavMode="view"===e.mode||this._canEdit||t.kiosk?null:e.mode,this._zoom=e.zoom;const i=this._effectiveProjection(),s=i===e.projection&&this._labsIso===e.activeLabsIso;if(this._view=s&&e.view?{...e.view}:null,this._viewModeSnap=s&&e.snap?{...e.snap}:null,!s&&e.logicalCenter){const t="iso"===i?Au([e.logicalCenter.x,e.logicalCenter.y],0):[e.logicalCenter.x,e.logicalCenter.y];this._applyView(e.zoom,t[0],t[1])}var n;this._tool="openwall"===(n=e.tool)||"closewall"===n?"boundary":"opening"===n?"draw":"string"==typeof n&&Sp.has(n)?n:"draw",this._decorTool=e.decorTool,this._showHidden=e.showHidden,this._showFar!==e.showFar&&(this._showFar=e.showFar,this._frame=null),this._selId=e.selId,this._rszSel=e.rszSel,this._decorSel=e.decorSel,this._warmVpArmed=!0}_warmPatch(t,e=!1){if(this._config?.kiosk)return;const i=_p(this._config);if(!this._warmSlot||this._warmKey===i){if(!this._warmSlot){if(!e)return;const t=this.parentNode;this._warmKey=i,this._warmSlot={owner:this._warmGen,path:location.pathname,place:t?new WeakRef(t):null,idx:this._warmIdx(t),live:!0,hdrH:this._hdrH,stageH:0,vp:null,frameFingerprint:"",devices:null,dlg:null,freed:0,evict:0};const s=up.get(i)||[];for(s.push(this._warmSlot),up.set(i,s),this._warmTrim(s);up.size>8;){const t=up.keys().next().value;if(void 0===t||t===i)break;for(const e of up.get(t)||[])clearTimeout(e.evict);up.delete(t)}}Object.assign(this._warmSlot,t)}}_warmViewportState(){const t=this._effectiveProjection();return{space:this._space,mode:this._mode,projection:t,activeLabsIso:this._labsIso,logicalCenter:this._logicalViewCenter(t),zoom:this._zoom,view:this._view?{...this._view}:null,snap:this._viewModeSnap?{...this._viewModeSnap}:null,tool:this._tool,decorTool:this._decorTool,showHidden:this._showHidden,showFar:this._showFar,selId:this._selId,rszSel:this._rszSel,decorSel:this._decorSel}}_warmDialogState(){const t=(t,e)=>({kind:t,space:this._space,mode:this._mode,data:e});return this._tapConfirm||this._alignDialog||this._mergeDialog||this._importDialog||this._backupExportDialog||this._backupImportDialog?null:this._openingInfo?t("openingInfo",this._openingInfo.id):this._infoCard?t("info",this._infoCard.id):this._rulesDialog?this._rulesDialog.busy?null:t("rules",this._rulesDialog):this._settingsDialog?this._settingsDialog.busy?null:t("settings",this._settingsDialog):this._markerDialog?this._markerDialog.busy?null:t("marker",this._markerDialog):this._openingDialog?t("opening",this._openingDialog):this._backdropDialog?t("backdrop",this._backdropDialog):this._decorShapeDialog?t("decorShape",this._decorShapeDialog):this._decorTextDialog?t("decorText",this._decorTextDialog):this._roomDialog?t("room",{editId:this._roomEditId,fill:this._roomFill,customFill:this._roomCustomFill,tempSrc:this._roomTempSrc,humSrc:this._roomHumSrc,srcOpen:this._roomSrcOpen,srcFilter:this._roomSrcFilter,nameScale:this._roomNameScale,labelScale:this._roomLabelScale,areaSel:this._areaSel,nameSel:this._nameSel,pendingSplit:this._pendingSplit,path:this._path}):this._spaceDialog?this._spaceDialog.busy?null:t("space",this._spaceDialog):null}_warmSnapshot(){if(this._booting||this._config?.kiosk||"steady"!==this._continuity.state)return;const t={vp:this._warmViewportState(),frameFingerprint:this._continuity.frameFingerprint,devices:this._devices};if(this._warmRevivePending||(t.dlg=this._warmDialogState()),this.isConnected&&this._warmSlot?.owner===this._warmGen){const e=this.parentNode;t.place=e?new WeakRef(e):null,t.idx=this._warmIdx(e)}this._warmPatch(t)}_warmReviveDialog(){this._warmRevivePending=!1;const t=this._warmSlot;if(this._warmReviveTimer=void 0,!t||!t.dlg)return;const e=t.dlg,i=t.freed;if(t.dlg=null,t.freed=0,clearTimeout(t.evict),t.evict=0,i&&!(Date.now()-i>mp)&&e.space===this._space&&e.mode===this._mode){switch(e.kind){case"space":this._spaceDialog={...e.data,busy:!1,savedBusy:!1};break;case"marker":this._markerDialog={...e.data,busy:!1};break;case"settings":this._settingsDialog={...e.data,busy:!1};break;case"rules":this._rulesDialog={...e.data,busy:!1};break;case"opening":this._openingDialog={...e.data};break;case"backdrop":this._backdropDialog={...e.data};break;case"decorShape":this._decorShapeDialog={...e.data};break;case"decorText":{this._decorTextDialog={...e.data};const t=String(this._decorTextDialog?.text??"").length;this._decorTextSelection={start:t,end:t};break}case"room":{const t=e.data;this._roomEditId=t.editId,this._roomFill=t.fill,this._roomCustomFill=t.customFill||null,this._roomTempSrc=t.tempSrc,this._roomHumSrc=t.humSrc,this._roomSrcOpen=t.srcOpen,this._roomSrcFilter=t.srcFilter,this._roomNameScale=t.nameScale,this._roomLabelScale=t.labelScale,this._areaSel=t.areaSel,this._nameSel=t.nameSel,this._pendingSplit=t.pendingSplit,this._path=t.path,this._roomDialog=!0;break}case"info":{const t=this._devices.find(t=>t.id===e.data);t&&(this._infoCard=t);break}case"openingInfo":{const t=(this._curSpaceCfg?.openings||[]).find(t=>t.id===e.data);t&&(this._openingInfo=t);break}}this.requestUpdate()}}_cacheSnapshot(){if(this._serverCfg)try{this._cfgContentFingerprint=qc(this._serverCfg),this._layoutContentFingerprint=qc(this._layout),this._virtualLights=function(t,e,i){if(t.configRev===i)return t;const s=new Set((Array.isArray(e?.markers)?e.markers:[]).filter(hn).map(t=>t.id));return{...t,configRev:i,off:new Set([...t.off].filter(t=>s.has(t)))}}(this._virtualLights,this._serverCfg,this._cfgRev),localStorage.setItem(fp,JSON.stringify({config:this._serverCfg,rev:this._cfgRev,config_fingerprint:this._cfgContentFingerprint,layout:this._layout,layout_rev:this._layoutRev,layout_fingerprint:this._layoutContentFingerprint,virtual_lights:ln(this._virtualLights)}))}catch{}}_beginContinuityCandidate(t,e,i="plan"){return this._booting&&!this._continuity.hasCompleteFrame?this._continuity.token:(this._continuityDataReady=e,this._continuityPaintToken=-1,this._stagedDeviceSnapshotToken=-1,this._resumeSettling=!0,this._continuity.beginCandidate(t,i))}_continuityStageValid(){const t=this._stageEl;return!!t&&t.clientWidth>0&&t.clientHeight>0}_continuityAssetsReady(){if(!this._model.length)return!1;const t=this._model.length?this._spaceModel():null;return!t?.bg?.href||this._signer.isReady(this.hass,t.bg.href)}_initialSpaceSelection(t){return function(t){const e=new Set(t.spaceIds.filter(t=>!!t));if(!e.size)return{id:null,source:"none"};const i=[["hash",!1===t.acceptHash?null:t.hashSpace],["current",t.preserveCurrent?t.currentSpace:null],["saved",t.savedSpace],["default",t.defaultSpace],["first",t.spaceIds[0]]];for(const[t,s]of i)if(s&&e.has(s))return{id:s,source:t};return{id:null,source:"none"}}({spaceIds:t.map(t=>t.id),hashSpace:this._hashSpace(),acceptHash:!this._hashApplied,currentSpace:this._space,preserveCurrent:this._hashApplied||this._navApplied||this._warmVpArmed,savedSpace:this._savedNav()?.space,defaultSpace:this._config?.default_floor})}_adoptInitialSpace(t){const e=this._initialSpaceSelection(t);return e.id?(this._space=e.id,"hash"===e.source&&(this._hashApplied=!0),"saved"===e.source&&(this._navApplied=!0),e):e}_candidateBackdrop(t,e=this._space){const i=Al(t),s=this._initialSpaceSelection(i).id||(i.some(t=>t.id===e)?e:i[0]?.id);return i.find(t=>t.id===s)?.bg?.href||""}_visualFrameFingerprint(){const t=this._stageEl,e=t?[t.clientWidth,t.clientHeight]:[0,0];return Bc([this._cfgRev,this._cfgContentFingerprint||qc(this._serverCfg),this._layoutRev,this._layoutContentFingerprint||qc(this._layout),this._space,this._mode,this._view,e,this._glowScreenBlend?"screen":"normal",this.hass?.themes?.darkMode??this.hass?.themes?.default_theme??""])}_settleContinuityFrame(){if(this._booting||!this._continuityStageValid())return;if(!this._continuity.hasCompleteFrame&&"steady"===this._continuity.state)return void(this._continuityAssetsReady()&&(this._renderSnapshotAt=Date.now(),this._continuity.markCompleteFrame(this._visualFrameFingerprint())));if(!this._continuityDataReady)return;if(!["holding","offline-stale","overlay-pending","overlay-visible","candidate-ready"].includes(this._continuity.state))return;const t=this._continuity.token;if(this._candidateDeviceSnapshot&&this._candidateDeviceSnapshot!==this._visibleDeviceSnapshot&&this._stagedDeviceSnapshotToken!==t)return this._stagedDeviceSnapshotToken=t,void this.requestUpdate();this._continuityPaintToken!==t&&(this._continuityPaintToken=t,this._continuity.candidateReady(t)&&this._continuity.commitAfterPaint(t,{updateComplete:()=>this.updateComplete,stageValid:()=>this.isConnected&&this._continuityStageValid(),assetsReady:()=>this._continuityAssetsReady(),frameFingerprint:()=>this._visualFrameFingerprint()}).then(e=>{e&&t===this._continuity.token?(this._resumeSettling=!1,this._renderSnapshotAt=Date.now(),this._candidateDeviceSnapshot&&(this._visibleDeviceSnapshot=this._candidateDeviceSnapshot),this._candidateDeviceSnapshot=null,this._stagedDeviceSnapshotToken=-1,this._warmSnapshot()):t===this._continuity.token&&(this._continuityPaintToken=-1,this._stagedDeviceSnapshotToken=-1,this._candidateDeviceSnapshot=null,this.requestUpdate())}))}_onBackdropLoaded(t,e){this._signer.markLoaded(this.hass,t,e),this._continuity.note("asset-ready"),this._continuityPaintToken=-1,"steady"!==this._continuity.state&&this.requestUpdate()}_renderRecoveryOverlay(){if(!this._continuity.overlayVisible&&"recovery-error"!==this._continuity.state)return G;const t="connection"===this._continuity.recoveryReason;return j`
t.stopPropagation()} @click=${t=>t.stopPropagation()} @@ -4818,17 +4822,23 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="da33c4f75cf2469e330b33f11fd5fa04d368 ${this._t("gs.north_letter")} ${null===t?"—":t+"°"} - `}_stageBg(t){const e=this._settingsDialog,i=this._spaceDialog,s=e?e.bgColor||"":es(this._settings,{bgColor:null});return(i&&"edit"===i.mode&&i.spaceId===this._space?i.bgColor||"":t.bgColor||"")||s}_stageBgHex(){const t=this._stageEl;if(t){const e=getComputedStyle(t).backgroundColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);if(e)return"#"+e.slice(1,4).map(t=>(+t).toString(16).padStart(2,"0")).join("")}return"#111111"}async _runAlignToGrid(){const t=this._alignDialog;if(t&&!t.busy&&this._serverCfg){this._clearGeometryGesture(),this._alignDialog={...t,busy:!0};try{this._saveConfigDebounced.pending()&&this._saveConfigDebounced.flush(),await this._writeChain;const e=await this.hass.callWS({type:"houseplan/plan/optimize",config:t.config,layout:t.layout,expected_config_rev:this._cfgRev,expected_layout_rev:this._layoutRev});this._serverCfg=t.config,this._layout=t.layout,this._geometryHistory.clear(),this._cfgRev=e?.config_rev??this._cfgRev+1,this._layoutRev=e?.layout_rev??this._layoutRev+1,this._canOptimizeUndo=!!e?.can_undo,this._undoKind=e?.can_undo?"optimize":null,this._dirtyPos.clear(),this._sentPos.clear(),this._cfgEpoch++,this._modelCache=null,this._frame=null,this._cacheSnapshot(),this._alignDialog=null,this.requestUpdate(),this._showToast(this._t("gs.align_done",{n:String(t.report.moved),m:String(t.report.migrated+t.report.canonicalized+t.report.wallsMerged+t.report.spansMerged)}))}catch(t){this._alignDialog&&(this._alignDialog={...this._alignDialog,busy:!1}),"conflict"===t?.code&&await Promise.all([this._reloadConfigOnly(!0),this._reloadLayoutOnly()]),this._showToast(this._t("toast.error",{err:this._errText(t)}))}}}async _undoPlanOptimization(){if(!this._canOptimizeUndo||this._optimizeUndoBusy)return;const t=this._undoKind;this._clearGeometryGesture(),this._optimizeUndoBusy=!0,this.requestUpdate();try{await this.hass.callWS({type:"houseplan/plan/optimize_undo",expected_config_rev:this._cfgRev,expected_layout_rev:this._layoutRev});const[e,i]=await Promise.all([this.hass.callWS({type:"houseplan/config/get"}),this.hass.callWS({type:"houseplan/layout/get"})]);this._serverCfg=e?.config||this._serverCfg,this._cfgRev=e?.rev??this._cfgRev,this._layout=i?.layout||this._layout,this._geometryHistory.clear(),this._layoutRev=i?.rev??this._layoutRev,this._canOptimizeUndo=!1,this._undoKind=null,this._cfgEpoch++,this._modelCache=null,this._frame=null,this._cacheSnapshot(),this.requestUpdate(),this._showToast(this._t("import"===t?"backup.import_undone":"gs.optimize_undone"))}catch(t){this._canOptimizeUndo=!1,this._undoKind=null,this._showToast(this._t("toast.error",{err:this._errText(t)}))}finally{this._optimizeUndoBusy=!1,this.requestUpdate()}}async _runBackupExport(){const t=this._backupExportDialog;if(t&&!t.busy){this._backupExportDialog={...t,busy:!0,error:""};try{const e=await this.hass.callWS({type:"houseplan/export/create",kind:t.kind,space_id:"space"===t.kind?this._space:void 0,card_version:sp}),i=new Blob([JSON.stringify(e.document,null,2)+"\n"],{type:"application/json;charset=utf-8"}),s=URL.createObjectURL(i),n=document.createElement("a");n.href=s,n.download=e.filename||`houseplan-${t.kind}.json`,n.style.display="none",document.body.append(n),n.click(),n.remove(),window.setTimeout(()=>URL.revokeObjectURL(s),0),this._backupExportDialog=null,this._showToast(this._t("backup.export_done"))}catch(t){this._backupExportDialog&&(this._backupExportDialog={...this._backupExportDialog,busy:!1,error:this._backupErrorText(t)})}}}async _pickBackupImport(t){const e=t.target,i=e.files?.[0];if(e.value="",i){this._settingsDialog=null,this._backupImportDialog={filename:i.name,size:i.size,token:"",preview:null,expectedConfigRev:this._cfgRev,expectedLayoutRev:this._layoutRev,duplicatePolicy:"skip",confirmMissing:!1,busy:!0,error:""};try{this._saveConfigDebounced.pending()&&this._saveConfigDebounced.flush(),await this._writeChain,this._persistLayout.pending()&&this._persistLayout.flush();const t="/api/houseplan/import/preview?duplicate_policy=skip",e={method:"POST",body:i,headers:{"content-type":"application/json"}},s=this.hass?.fetchWithAuth?await this.hass.fetchWithAuth(t,e):await fetch(t,{...e,headers:{...e.headers,...this.hass?.auth?.data?.access_token?{authorization:`Bearer ${this.hass.auth.data.access_token}`}:{}}}),n=await s.json().catch(()=>({}));if(!s.ok||n.error)throw Object.assign(new Error(n.message||n.error||`HTTP ${s.status}`),{code:n.error});this._backupImportDialog&&(this._backupImportDialog={...this._backupImportDialog,token:n.token,preview:n.preview,expectedConfigRev:n.expected_config_rev,expectedLayoutRev:n.expected_layout_rev,busy:!1})}catch(t){this._backupImportDialog&&(this._backupImportDialog={...this._backupImportDialog,busy:!1,error:this._backupErrorText(t)})}}}async _setBackupDuplicatePolicy(t){const e=this._backupImportDialog;if(e&&!e.busy&&e.token&&t!==e.duplicatePolicy){this._backupImportDialog={...e,duplicatePolicy:t,busy:!0,error:""};try{const i=await this.hass.callWS({type:"houseplan/import/revalidate",token:e.token,duplicate_policy:t});this._backupImportDialog&&(this._backupImportDialog={...this._backupImportDialog,preview:{...e.preview,...i.preview},expectedConfigRev:i.expected_config_rev,expectedLayoutRev:i.expected_layout_rev,confirmMissing:!1,busy:!1})}catch(t){this._backupImportDialog&&(this._backupImportDialog={...this._backupImportDialog,duplicatePolicy:e.duplicatePolicy,preview:e.preview,confirmMissing:e.confirmMissing,busy:!1,error:this._backupErrorText(t)})}}}async _applyBackupImport(){const t=this._backupImportDialog;if(!t||t.busy||!t.token||!t.preview)return;if(t.preview.confirmation_required&&!t.confirmMissing)return;this._backupImportDialog={...t,busy:!0,error:""};const e=this._space;try{const i=await this.hass.callWS({type:"houseplan/import/apply",token:t.token,expected_config_rev:t.expectedConfigRev,expected_layout_rev:t.expectedLayoutRev,duplicate_policy:t.duplicatePolicy,confirm_missing_content:t.confirmMissing}),[s,n]=await Promise.all([this.hass.callWS({type:"houseplan/config/get"}),this.hass.callWS({type:"houseplan/layout/get"})]);this._adoptStructuralResponses(s,n),this._geometryHistory.clear(),this._dirtyPos.clear(),this._sentPos.clear(),this._defPos={},this._cfgEpoch++,this._modelCache=null,this._frame=null,this._visibleDeviceSnapshot=null,this._candidateDeviceSnapshot=null,this._stagedDeviceSnapshotToken=-1,this._capturedSnapshotConfigEpoch=-1,this._regSignature="",this._signer.invalidate(this.hass),this._resign(),this._maybeRebuildDevices();const o=this._serverCfg?.spaces||[];this._space="space"===i.kind&&i.space_id?i.space_id:o.some(t=>t.id===e)?e:o[0]?.id||this._space,this._backupImportDialog=null,this._cacheSnapshot(),this.requestUpdate();const r="space"===i.kind?t.preview.counts:i.counts;this._showToast(this._t("space"===i.kind?"backup.space_done":"backup.full_done",{spaces:String(r?.spaces||0),rooms:String(r?.rooms||0),markers:String(r?.markers||0)}))}catch(t){if("conflict"===t?.code&&this._backupImportDialog?.token)try{const t=this._backupImportDialog,e=await this.hass.callWS({type:"houseplan/import/revalidate",token:t.token,duplicate_policy:t.duplicatePolicy});return void(this._backupImportDialog={...t,preview:{...t.preview,...e.preview},expectedConfigRev:e.expected_config_rev,expectedLayoutRev:e.expected_layout_rev,confirmMissing:!1,busy:!1,error:this._t("backup.revalidated")})}catch(e){t=e}this._backupImportDialog&&(this._backupImportDialog={...this._backupImportDialog,busy:!1,error:this._backupErrorText(t)})}}_renderBackupExportDialog(){const t=this._backupExportDialog,e=(this._serverCfg?.spaces||[]).find(t=>t.id===this._space);return j``}_stageBg(t){const e=this._settingsDialog,i=this._spaceDialog,s=e?e.bgColor||"":es(this._settings,{bgColor:null});return(i&&"edit"===i.mode&&i.spaceId===this._space?i.bgColor||"":t.bgColor||"")||s}_stageBgHex(){const t=this._stageEl;if(t){const e=getComputedStyle(t).backgroundColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);if(e)return"#"+e.slice(1,4).map(t=>(+t).toString(16).padStart(2,"0")).join("")}return"#111111"}async _runAlignToGrid(){const t=this._alignDialog;if(t&&!t.busy&&this._serverCfg){this._clearGeometryGesture(),this._alignDialog={...t,busy:!0};try{this._saveConfigDebounced.pending()&&this._saveConfigDebounced.flush(),await this._writeChain;const e=await this.hass.callWS({type:"houseplan/plan/optimize",config:t.config,layout:t.layout,expected_config_rev:this._cfgRev,expected_layout_rev:this._layoutRev});this._serverCfg=t.config,this._layout=t.layout,this._geometryHistory.clear(),this._cfgRev=e?.config_rev??this._cfgRev+1,this._layoutRev=e?.layout_rev??this._layoutRev+1,this._canOptimizeUndo=!!e?.can_undo,this._undoKind=e?.can_undo?"optimize":null,this._dirtyPos.clear(),this._sentPos.clear(),this._cfgEpoch++,this._modelCache=null,this._frame=null,this._cacheSnapshot(),this._alignDialog=null,this.requestUpdate(),this._showToast(this._t("gs.align_done",{n:String(t.report.moved),m:String(t.report.migrated+t.report.canonicalized+t.report.wallsMerged+t.report.spansMerged)}))}catch(t){this._alignDialog&&(this._alignDialog={...this._alignDialog,busy:!1}),"conflict"===t?.code&&await Promise.all([this._reloadConfigOnly(!0),this._reloadLayoutOnly()]),this._showToast(this._t("toast.error",{err:this._errText(t)}))}}}async _undoPlanOptimization(){if(!this._canOptimizeUndo||this._optimizeUndoBusy)return;const t=this._undoKind;this._clearGeometryGesture(),this._optimizeUndoBusy=!0,this.requestUpdate();try{await this.hass.callWS({type:"houseplan/plan/optimize_undo",expected_config_rev:this._cfgRev,expected_layout_rev:this._layoutRev});const[e,i]=await Promise.all([this.hass.callWS({type:"houseplan/config/get"}),this.hass.callWS({type:"houseplan/layout/get"})]);this._serverCfg=e?.config||this._serverCfg,this._cfgRev=e?.rev??this._cfgRev,this._layout=i?.layout||this._layout,this._geometryHistory.clear(),this._layoutRev=i?.rev??this._layoutRev,this._canOptimizeUndo=!1,this._undoKind=null,this._cfgEpoch++,this._modelCache=null,this._frame=null,this._cacheSnapshot(),this.requestUpdate(),this._showToast(this._t("import"===t?"backup.import_undone":"gs.optimize_undone"))}catch(t){this._canOptimizeUndo=!1,this._undoKind=null,this._showToast(this._t("toast.error",{err:this._errText(t)}))}finally{this._optimizeUndoBusy=!1,this.requestUpdate()}}async _runBackupExport(){const t=this._backupExportDialog;if(t&&!t.busy){this._backupExportDialog={...t,busy:!0,error:""};try{const e=await this.hass.callWS({type:"houseplan/export/create",kind:t.kind,space_id:"space"===t.kind?this._space:void 0,..."space"===t.kind&&t.planOnly?{plan_only:!0}:{},card_version:sp}),i=new Blob([JSON.stringify(e.document,null,2)+"\n"],{type:"application/json;charset=utf-8"}),s=URL.createObjectURL(i),n=document.createElement("a");n.href=s,n.download=e.filename||`houseplan-${t.kind}.json`,n.style.display="none",document.body.append(n),n.click(),n.remove(),window.setTimeout(()=>URL.revokeObjectURL(s),0),this._backupExportDialog=null,this._showToast(this._t("backup.export_done"))}catch(t){this._backupExportDialog&&(this._backupExportDialog={...this._backupExportDialog,busy:!1,error:this._backupErrorText(t)})}}}async _pickBackupImport(t){const e=t.target,i=e.files?.[0];if(e.value="",i){this._settingsDialog=null,this._backupImportDialog={filename:i.name,size:i.size,token:"",preview:null,expectedConfigRev:this._cfgRev,expectedLayoutRev:this._layoutRev,duplicatePolicy:"skip",confirmMissing:!1,busy:!0,error:""};try{this._saveConfigDebounced.pending()&&this._saveConfigDebounced.flush(),await this._writeChain,this._persistLayout.pending()&&this._persistLayout.flush();const t="/api/houseplan/import/preview?duplicate_policy=skip",e={method:"POST",body:i,headers:{"content-type":"application/json"}},s=this.hass?.fetchWithAuth?await this.hass.fetchWithAuth(t,e):await fetch(t,{...e,headers:{...e.headers,...this.hass?.auth?.data?.access_token?{authorization:`Bearer ${this.hass.auth.data.access_token}`}:{}}}),n=await s.json().catch(()=>({}));if(!s.ok||n.error)throw Object.assign(new Error(n.message||n.error||`HTTP ${s.status}`),{code:n.error});this._backupImportDialog&&(this._backupImportDialog={...this._backupImportDialog,token:n.token,preview:n.preview,expectedConfigRev:n.expected_config_rev,expectedLayoutRev:n.expected_layout_rev,busy:!1})}catch(t){this._backupImportDialog&&(this._backupImportDialog={...this._backupImportDialog,busy:!1,error:this._backupErrorText(t)})}}}async _setBackupDuplicatePolicy(t){const e=this._backupImportDialog;if(e&&!e.busy&&e.token&&t!==e.duplicatePolicy){this._backupImportDialog={...e,duplicatePolicy:t,busy:!0,error:""};try{const i=await this.hass.callWS({type:"houseplan/import/revalidate",token:e.token,duplicate_policy:t});this._backupImportDialog&&(this._backupImportDialog={...this._backupImportDialog,preview:{...e.preview,...i.preview},expectedConfigRev:i.expected_config_rev,expectedLayoutRev:i.expected_layout_rev,confirmMissing:!1,busy:!1})}catch(t){this._backupImportDialog&&(this._backupImportDialog={...this._backupImportDialog,duplicatePolicy:e.duplicatePolicy,preview:e.preview,confirmMissing:e.confirmMissing,busy:!1,error:this._backupErrorText(t)})}}}async _applyBackupImport(){const t=this._backupImportDialog;if(!t||t.busy||!t.token||!t.preview)return;if(t.preview.confirmation_required&&!t.confirmMissing)return;this._backupImportDialog={...t,busy:!0,error:""};const e=this._space;try{const i=await this.hass.callWS({type:"houseplan/import/apply",token:t.token,expected_config_rev:t.expectedConfigRev,expected_layout_rev:t.expectedLayoutRev,duplicate_policy:t.duplicatePolicy,confirm_missing_content:t.confirmMissing}),[s,n]=await Promise.all([this.hass.callWS({type:"houseplan/config/get"}),this.hass.callWS({type:"houseplan/layout/get"})]);this._adoptStructuralResponses(s,n),this._geometryHistory.clear(),this._dirtyPos.clear(),this._sentPos.clear(),this._defPos={},this._cfgEpoch++,this._modelCache=null,this._frame=null,this._visibleDeviceSnapshot=null,this._candidateDeviceSnapshot=null,this._stagedDeviceSnapshotToken=-1,this._capturedSnapshotConfigEpoch=-1,this._regSignature="",this._signer.invalidate(this.hass),this._resign(),this._maybeRebuildDevices();const o=this._serverCfg?.spaces||[];this._space="space"===i.kind&&i.space_id?i.space_id:o.some(t=>t.id===e)?e:o[0]?.id||this._space,this._backupImportDialog=null,this._cacheSnapshot(),this.requestUpdate();const r="space"===i.kind?t.preview.counts:i.counts;this._showToast(this._t("space"===i.kind?"backup.space_done":"backup.full_done",{spaces:String(r?.spaces||0),rooms:String(r?.rooms||0),markers:String(r?.markers||0)}))}catch(t){if("conflict"===t?.code&&this._backupImportDialog?.token)try{const t=this._backupImportDialog,e=await this.hass.callWS({type:"houseplan/import/revalidate",token:t.token,duplicate_policy:t.duplicatePolicy});return void(this._backupImportDialog={...t,preview:{...t.preview,...e.preview},expectedConfigRev:e.expected_config_rev,expectedLayoutRev:e.expected_layout_rev,confirmMissing:!1,busy:!1,error:this._t("backup.revalidated")})}catch(e){t=e}this._backupImportDialog&&(this._backupImportDialog={...this._backupImportDialog,busy:!1,error:this._backupErrorText(t)})}}_renderBackupExportDialog(){const t=this._backupExportDialog,e=(this._serverCfg?.spaces||[]).find(t=>t.id===this._space);return j`this._backupExportDialog=null}>
${this._t("backup.export_hint")}
+ ${"space"===t.kind&&e?j``:G}
${this._t("backup.privacy_warning")}
${t.error?j``:G}
@@ -4849,6 +4859,7 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="da33c4f75cf2469e330b33f11fd5fa04d368 ${e?j`
${this._t("full"===e.kind?"backup.full":"backup.current_space")} + ${e.plan_only?j`${this._t("backup.plan_only_preview")}`:G} ${this._t("same"===e.source?"backup.same_source":"backup.foreign_source")} ${this._t("backup.created",{value:e.created_at||"—"})} ${this._t("backup.versions",{card:e.card_version||"—",integration:e.integration_version||"—",model:String(e.model_version??"—")})} diff --git a/custom_components/houseplan/import_export.py b/custom_components/houseplan/import_export.py index 97769b27..a5408051 100644 --- a/custom_components/houseplan/import_export.py +++ b/custom_components/houseplan/import_export.py @@ -53,6 +53,34 @@ from .validation import ( FORMAT = "houseplan-export" _PROTO_KEYS = {"__proto__", "prototype", "constructor"} _SAFE_FILE = re.compile(r"[^A-Za-z0-9._-]+") +_LIVE_TEXT_TOKEN = re.compile(r"\{([^{}\r\n]+)\}") +_LIVE_TEXT_ENTITY = re.compile(r"^[a-z0-9_]+\.[a-z0-9_]+$") +_LIVE_TEXT_ATTRIBUTE = re.compile(r"^[a-zA-Z0-9_.-]+$") +_PLAN_ONLY_DASH = "—" + +_SPACE_PLAN_FIELDS = ( + "id", "title", "cell_cm", "plan_url", "plan_aspect", "plan_x", "plan_y", + "plan_scale", "plan_scale_x", "plan_scale_y", "plan_angle", "view_box", +) +_SPACE_DISPLAY_FIELDS = ( + "show_borders", "show_names", "room_color", "bg_color", "room_opacity", + "fill_mode", "custom_fill", "glow_enabled", "temp_min", "temp_max", + "show_lqi", "hide_decor", "hide_openings", "label_temp", "label_hum", + "label_lqi", "label_light", "card_font_scale", "north_deg", "bg_mode", + "sun_rays", +) +_ROOM_PLAN_FIELDS = ("id", "name", "open_to", "x", "y", "w", "h", "poly") +_ROOM_DISPLAY_FIELDS = ( + "fill_mode", "custom_fill", "glow", "name_scale", "label_scale", +) +_DECOR_COMMON_FIELDS = ("id", "kind", "color", "opacity", "width_cm", "width") +_DECOR_KIND_FIELDS = { + "line": ("x1", "y1", "x2", "y2", "line_style"), + "rect": ("x", "y", "w", "h", "angle", "fill", "fill_color", "fill_opacity"), + "ellipse": ("x", "y", "w", "h", "angle", "fill", "fill_color", "fill_opacity"), + "text": ("x", "y", "text", "size", "size_cm", "scale", "angle"), + "furniture": ("symbol", "x", "y", "w", "h", "angle"), +} class ImportFailure(Exception): @@ -139,6 +167,126 @@ def live_layout(config: dict[str, Any], layout: dict[str, Any]) -> dict[str, Any } +def _pick_fields(source: dict[str, Any], fields: tuple[str, ...]) -> dict[str, Any]: + """Copy only fields explicitly classified as portable plan data.""" + return {key: _json_copy(source[key]) for key in fields if key in source} + + +def _is_live_text_reference(raw: str) -> bool: + """Mirror ``liveTextReference`` without evaluating Home Assistant state.""" + ref = raw.strip() + if not ref: + return False + entity = ref + attribute = "" + colon = ref.find(":") + if colon >= 0: + entity = ref[:colon].strip() + attribute = ref[colon + 1:].strip() + else: + parts = ref.split(".") + if len(parts) > 2: + entity = ".".join(parts[:2]) + attribute = ".".join(parts[2:]) + if _LIVE_TEXT_ENTITY.fullmatch(entity) is None: + return False + if colon >= 0 and not attribute: + return False + return not attribute or _LIVE_TEXT_ATTRIBUTE.fullmatch(attribute) is not None + + +def _plan_only_text(value: str) -> str: + """Freeze every recognized live reference while preserving authored copy.""" + replaced = _LIVE_TEXT_TOKEN.sub( + lambda match: _PLAN_ONLY_DASH + if _is_live_text_reference(match.group(1)) else match.group(0), + value, + ) + return replaced.replace("{}", _PLAN_ONLY_DASH) + + +def _project_plan_only_room(room: dict[str, Any]) -> dict[str, Any]: + projected = _pick_fields(room, _ROOM_PLAN_FIELDS) + if "settings" in room: + settings = room.get("settings") + projected["settings"] = ( + _pick_fields(settings, _ROOM_DISPLAY_FIELDS) + if isinstance(settings, dict) else None + ) + return projected + + +def _project_plan_only_decor(shape: dict[str, Any]) -> dict[str, Any]: + kind = str(shape.get("kind", "")) + projected = _pick_fields( + shape, _DECOR_COMMON_FIELDS + _DECOR_KIND_FIELDS.get(kind, ()), + ) + if kind == "text" and isinstance(projected.get("text"), str): + projected["text"] = _plan_only_text(projected["text"]) + return projected + + +def _project_plan_only_space(space: dict[str, Any]) -> dict[str, Any]: + """Build the fail-closed geometry/presentation projection for #167.""" + projected = _pick_fields(space, _SPACE_PLAN_FIELDS) + if "settings" in space: + projected["settings"] = _pick_fields( + space.get("settings") or {}, _SPACE_DISPLAY_FIELDS, + ) + projected["rooms"] = [ + _project_plan_only_room(room) for room in space.get("rooms") or [] + ] + collections: tuple[tuple[str, tuple[str, ...]], ...] = ( + ("walls", ("key", "cm", "a", "b")), + ("room_drafts", ("id", "points", "segments")), + ("partitions", ("id", "a", "b", "cm")), + ("wall_columns", ("id", "shape", "center", "cm", "angle")), + ("open_spans", ("a", "b")), + ) + for name, fields in collections: + if name not in space: + continue + values = [] + for item in space.get(name) or []: + selected = _pick_fields(item, fields) + if name == "room_drafts" and "segments" in selected: + selected["segments"] = [ + _pick_fields(segment, ("cm",)) + for segment in selected.get("segments") or [] + ] + values.append(selected) + projected[name] = values + if "openings" in space: + projected["openings"] = [ + _pick_fields( + opening, + ("id", "type", "x", "y", "angle", "length", "flip_h", "flip_v"), + ) + for opening in space.get("openings") or [] + ] + if "decor" in space: + projected["decor"] = [ + _project_plan_only_decor(shape) for shape in space.get("decor") or [] + ] + return projected + + +def _plan_only_room_label_layout( + layout: dict[str, Any], space: dict[str, Any], +) -> dict[str, Any]: + space_id = str(space.get("id", "")) + room_ids = {str(room.get("id", "")) for room in space.get("rooms") or []} + return { + key: _pick_fields(pos, ("x", "y", "s")) + for key, pos in layout.items() + if isinstance(key, str) + and key.startswith("rl_") + and key[3:] in room_ids + and isinstance(pos, dict) + and str(pos.get("s", "")) == space_id + } + + def _marker_owned(marker: dict[str, Any], space: dict[str, Any], layout: dict[str, Any]) -> bool: marker_id = str(marker.get("id", "")) room_ids = {str(room.get("id")) for room in space.get("rooms") or []} @@ -254,9 +402,12 @@ def create_export( *, kind: str, space_id: str | None, + plan_only: bool = False, card_version: str, config_root: Path, ) -> tuple[dict[str, Any], str]: + if not isinstance(plan_only, bool) or plan_only and kind != "space": + raise ImportFailure("invalid_format", "Plan-only export requires one space") raw_config = _json_copy( config_data.get("config") or {"spaces": [], "markers": [], "settings": {}} ) @@ -283,36 +434,41 @@ def create_export( key: pos for key, pos in layout.items() if isinstance(pos, dict) and str(pos.get("s")) == str(space_id) } - selected_markers = [ - m for m in config.get("markers") or [] - if m.get("removed") is not True and _marker_owned(m, space, selected_layout) - ] - selected_ids = {str(marker.get("id")) for marker in selected_markers} - for marker in selected_markers: - controls = marker.get("controls") - if isinstance(controls, list): - kept = [] - for ref in controls: - if isinstance(ref, str) and ref.startswith("marker:") \ - and ref[len("marker:"):] not in selected_ids: - dropped_marker_links += 1 - continue - kept.append(ref) - marker["controls"] = kept or None - badge = marker.get("value_badge") - source = badge.get("source") if isinstance(badge, dict) else None - ref = source.get("ref") if isinstance(source, dict) \ - and source.get("kind") == "derived_marker_state" else None - if isinstance(ref, str) and ref.startswith("marker:") \ - and ref[len("marker:"):] not in selected_ids: - badge["enabled"] = False - badge["source"] = None - dropped_marker_links += 1 - config = { - "spaces": [_json_copy(space)], - "markers": _json_copy(selected_markers), - } - layout = selected_layout + if plan_only: + projected_space = _project_plan_only_space(space) + config = {"spaces": [projected_space], "markers": []} + layout = _plan_only_room_label_layout(selected_layout, projected_space) + else: + selected_markers = [ + m for m in config.get("markers") or [] + if m.get("removed") is not True and _marker_owned(m, space, selected_layout) + ] + selected_ids = {str(marker.get("id")) for marker in selected_markers} + for marker in selected_markers: + controls = marker.get("controls") + if isinstance(controls, list): + kept = [] + for ref in controls: + if isinstance(ref, str) and ref.startswith("marker:") \ + and ref[len("marker:"):] not in selected_ids: + dropped_marker_links += 1 + continue + kept.append(ref) + marker["controls"] = kept or None + badge = marker.get("value_badge") + source = badge.get("source") if isinstance(badge, dict) else None + ref = source.get("ref") if isinstance(source, dict) \ + and source.get("kind") == "derived_marker_state" else None + if isinstance(ref, str) and ref.startswith("marker:") \ + and ref[len("marker:"):] not in selected_ids: + badge["enabled"] = False + badge["source"] = None + dropped_marker_links += 1 + config = { + "spaces": [_json_copy(space)], + "markers": _json_copy(selected_markers), + } + layout = selected_layout elif kind != "full": raise ImportFailure("invalid_format", "Unknown export kind") document = { @@ -327,7 +483,10 @@ def create_export( "payload": {"config": config, "layout": layout}, "placement_manifest": placement_manifest(config, layout), "content_manifest": content_manifest(config, config_root), - "transfer": {"dropped_marker_links": dropped_marker_links}, + "transfer": { + "dropped_marker_links": dropped_marker_links, + **({"plan_only": True} if plan_only else {}), + }, } if len(json.dumps( document, ensure_ascii=False, separators=(",", ":"), allow_nan=False @@ -416,10 +575,13 @@ def parse_document(raw: bytes) -> dict[str, Any]: if document["kind"] == "space" and placement_ids != set(layout): raise ImportFailure("invalid_format", "Placement manifest does not match layout") dropped_marker_links = _transfer_dropped_marker_links(document) + plan_only = _transfer_plan_only(document) document["transfer"] = { **(document.get("transfer") or {}), "dropped_marker_links": dropped_marker_links, } + if plan_only: + _validate_plan_only_document(document, config, layout, placement) if len(json.dumps( config, ensure_ascii=False, separators=(",", ":"), allow_nan=False ).encode("utf-8")) > MAX_CONFIG_BYTES: @@ -490,6 +652,51 @@ def _transfer_dropped_marker_links(document: dict[str, Any]) -> int: return value +def _transfer_plan_only(document: dict[str, Any]) -> bool: + """Read strict additive plan-only metadata without widening old exports.""" + transfer = document.get("transfer") + if transfer is None: + return False + if not isinstance(transfer, dict): + raise ImportFailure("invalid_format", "Transfer metadata must be an object") + value = transfer.get("plan_only", False) + if not isinstance(value, bool): + raise ImportFailure("invalid_format", "Plan-only metadata must be a boolean") + if value and document.get("kind") != "space": + raise ImportFailure("invalid_format", "Plan-only metadata requires one space") + return value + + +def _validate_plan_only_document( + document: dict[str, Any], + config: dict[str, Any], + layout: dict[str, Any], + placement: list[Any], +) -> None: + """Reject forged plan-only flags unless every privacy invariant is true.""" + spaces = config.get("spaces") or [] + if len(spaces) != 1 or config.get("markers") != []: + raise ImportFailure("invalid_format", "Plan-only export has invalid owners") + expected_config = CONFIG_SCHEMA({ + "spaces": [_project_plan_only_space(spaces[0])], + "markers": [], + }) + if config != expected_config: + raise ImportFailure("invalid_format", "Plan-only export contains private fields") + expected_layout = _plan_only_room_label_layout(layout, spaces[0]) + if layout != expected_layout: + raise ImportFailure("invalid_format", "Plan-only export contains device layout") + expected_placement = placement_manifest(config, layout) + if placement != expected_placement: + raise ImportFailure("invalid_format", "Plan-only placement manifest is not canonical") + content = document.get("content_manifest") + if not isinstance(content, list) or any( + not isinstance(item, dict) or item.get("owner") != "space" + for item in content + ): + raise ImportFailure("invalid_format", "Plan-only export contains private content") + + def _drop_invalid_import_marker_links( config: dict[str, Any], *, clean_ids: set[str] | None = None, ) -> int: @@ -964,6 +1171,7 @@ def create_preview( runtime.import_previews[token] = candidate preview = { "kind": document["kind"], + "plan_only": _transfer_plan_only(document), "created_at": document.get("created_at"), "card_version": document.get("card_version"), "integration_version": document.get("integration_version"), @@ -1043,6 +1251,7 @@ def revalidate_candidate( return { "preview": { "kind": document["kind"], + "plan_only": _transfer_plan_only(document), "counts": _counts(incoming["config"], incoming["layout"]), "current_counts": _counts(current_config, current_layout), "source": "same" if candidate.get("same_source") else "foreign", diff --git a/custom_components/houseplan/websocket_api.py b/custom_components/houseplan/websocket_api.py index b5214335..6ec08216 100755 --- a/custom_components/houseplan/websocket_api.py +++ b/custom_components/houseplan/websocket_api.py @@ -263,6 +263,7 @@ async def _commit_import_pair( vol.Required("type"): "houseplan/export/create", vol.Required("kind"): vol.In(["full", "space"]), vol.Optional("space_id"): str, + vol.Optional("plan_only", default=False): bool, vol.Optional("card_version", default=""): str, } ) @@ -287,6 +288,7 @@ async def ws_export_create(hass: HomeAssistant, connection, msg: dict[str, Any]) layout_data, kind=msg["kind"], space_id=msg.get("space_id"), + plan_only=msg.get("plan_only", False), card_version=msg.get("card_version", ""), config_root=Path(hass.config.path("")), ) diff --git a/demo/golden/harness.mjs b/demo/golden/harness.mjs index 2f63fcbd..5546d0ab 100644 --- a/demo/golden/harness.mjs +++ b/demo/golden/harness.mjs @@ -561,6 +561,13 @@ export async function prepareGoldenScenario(page, scenario) { await dialog?.updateComplete; dialog?.renderRoot?.querySelector('.close')?.focus(); } + } else if (scenario.dialog === 'backup-export-plan-only') { + card._openBackupExport(); + card._backupExportDialog = { + ...card._backupExportDialog, kind: 'space', planOnly: true, + }; + card.requestUpdate(); + await card.updateComplete; } else if (scenario.dialog === 'backup-full' || scenario.dialog === 'backup-space') { const full = scenario.dialog === 'backup-full'; card._backupImportDialog = { @@ -568,10 +575,11 @@ export async function prepareGoldenScenario(page, scenario) { size: 12345, token: 'golden-token', preview: { - kind: full ? 'full' : 'space', source: full ? 'foreign' : 'same', + kind: full ? 'full' : 'space', plan_only: !full, + source: full ? 'foreign' : 'same', created_at: '2026-08-11T10:00:00Z', space_title: 'Ground (2)', - counts: { spaces: 1, rooms: 4, markers: 12, layout: 15 }, - duplicates: full ? 0 : 2, + counts: { spaces: 1, rooms: 4, markers: full ? 12 : 0, layout: full ? 15 : 4 }, + duplicates: 0, confirmation_required: full, content: full ? [{ url: '/api/houseplan/content/plans/_/ground.svg', state: 'detach_required' }] diff --git a/demo/golden/matrix.mjs b/demo/golden/matrix.mjs index a9186c7b..106acb85 100644 --- a/demo/golden/matrix.mjs +++ b/demo/golden/matrix.mjs @@ -1,7 +1,7 @@ import { fixtureWallKey } from '../fixtures/visual-matrix.mjs'; /** Data-only HP-QA-01 capture matrix. Bump when framing or scenarios change. */ -export const GOLDEN_MATRIX_VERSION = 23; +export const GOLDEN_MATRIX_VERSION = 24; const stage = { capture: 'stage', threshold: { maxChannelDelta: 10, maxDiffRatio: 0.0005 } }; const page = { capture: 'page', threshold: { maxChannelDelta: 10, maxDiffRatio: 0.0008 } }; @@ -248,6 +248,9 @@ export const GOLDEN_SCENARIOS = Object.freeze([ { id: 'backup-full-preview-desktop-en', fixture: 'visual', space: 'golden-geometry', dialog: 'backup-full', language: 'en', theme: 'dark', viewport: { width: 1000, height: 900 }, ...page }, + { id: 'backup-plan-only-export-desktop-en', fixture: 'visual', space: 'golden-geometry', + dialog: 'backup-export-plan-only', language: 'en', theme: 'dark', + viewport: { width: 1000, height: 900 }, ...page }, { id: 'backup-space-preview-mobile-ru', fixture: 'visual', space: 'golden-geometry', dialog: 'backup-space', language: 'ru', theme: 'light', viewport: { width: 390, height: 820 }, ...page }, diff --git a/demo/smoke_backup_transfer.mjs b/demo/smoke_backup_transfer.mjs index ce3c464b..0ea7d13a 100644 --- a/demo/smoke_backup_transfer.mjs +++ b/demo/smoke_backup_transfer.mjs @@ -19,6 +19,40 @@ const result = await page.evaluate(async () => { await card.updateComplete; const exportRadios = root().querySelectorAll('input[name="backup-kind"]').length; const exportWarning = !!root().querySelector('.backupwarn'); + const planOnlyHiddenForFull = !root().querySelector('.backupplanonly'); + root().querySelector('input[name="backup-kind"][value="space"]')?.click(); + await card.updateComplete; + const planOnly = root().querySelector('.backupplanonly input[type="checkbox"]'); + planOnly?.click(); + await card.updateComplete; + const planOnlySpace = { + visible: !!planOnly, + checked: card._backupExportDialog?.planOnly === true, + labelled: root().querySelector('.backupplanonly')?.textContent + .includes(card._t('backup.plan_only')) === true, + }; + root().querySelector('input[name="backup-kind"][value="full"]')?.click(); + await card.updateComplete; + const planOnlyResetForFull = card._backupExportDialog?.planOnly === false + && !root().querySelector('.backupplanonly'); + + let sentExport = null; + card.hass = { + ...card.hass, + callWS: async (message) => { + sentExport = message; + return { document: { smoke: true }, filename: 'plan-only-smoke.json' }; + }, + }; + card._backupExportDialog = { + kind: 'space', planOnly: true, busy: false, error: '', + }; + await card._runBackupExport(); + const planOnlyRequest = { + kind: sentExport?.kind, + planOnly: sentExport?.plan_only, + space: sentExport?.space_id, + }; card._backupExportDialog = null; card._backupImportDialog = { @@ -40,7 +74,31 @@ const result = await page.evaluate(async () => { noHorizontalOverflow: root().querySelector('hp-dialog').scrollWidth <= root().querySelector('hp-dialog').clientWidth, }; - return { group, actions, keyboardImport, exportRadios, exportWarning, importSafe }; + card._backupImportDialog = { + filename: 'houseplan-space-plan-only.json', size: 2048, token: 'plan-only', + preview: { + kind: 'space', plan_only: true, source: 'same', + created_at: '2026-08-17T00:00:00Z', space_title: 'Ground (2)', + counts: { spaces: 1, rooms: 2, markers: 0, layout: 2 }, + bindings: { device: 0, entity: 0, virtual: 0, active: 0, disabled: 0, missing: 0 }, + duplicates: 0, confirmation_required: false, content: [], + }, + expectedConfigRev: 1, expectedLayoutRev: 2, duplicatePolicy: 'skip', + confirmMissing: false, busy: false, error: '', + }; + await card.updateComplete; + const planOnlyPreview = { + visible: root().querySelector('.backupplanonlystatus')?.textContent + === card._t('backup.plan_only_preview'), + noDuplicatePolicy: !root().querySelector('.backupchoices'), + noHorizontalOverflow: root().querySelector('hp-dialog').scrollWidth + <= root().querySelector('hp-dialog').clientWidth, + }; + return { + group, actions, keyboardImport, exportRadios, exportWarning, + planOnlyHiddenForFull, planOnlySpace, planOnlyResetForFull, planOnlyRequest, + importSafe, planOnlyPreview, + }; }); checkAll(result, { @@ -49,6 +107,11 @@ checkAll(result, { keyboardImport: true, exportRadios: 2, exportWarning: true, + planOnlyHiddenForFull: true, + planOnlySpace: { visible: true, checked: true, labelled: true }, + planOnlyResetForFull: true, + planOnlyRequest: { kind: 'space', planOnly: true, space: 'f1' }, importSafe: { danger: true, disabledUntilConfirmed: true, noHorizontalOverflow: true }, + planOnlyPreview: { visible: true, noDuplicatePolicy: true, noHorizontalOverflow: true }, }); await finish(browser, result); diff --git a/demo/srv/assets/houseplan-card.js b/demo/srv/assets/houseplan-card.js index 090f3dc8..e19774d8 100755 --- a/demo/srv/assets/houseplan-card.js +++ b/demo/srv/assets/houseplan-card.js @@ -1,4 +1,4 @@ -globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="da33c4f75cf2469e330b33f11fd5fa04d36861af0c75eb110758b7fff2688fcb";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__="7a788419e5d1a6e4b944a5d436d2cffd00795c3237791689b1401b73925353f8";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.text}${t.suffix}`)} ${null!=t.lqiText?j`${t.lqiText}`:G} - `;var o}function Io(t,e,i){return{kind:"none",reason:"none",generation:t,expiresAt:null,color:e,diameterScale:i,animated:!1,reducedMotionIndicator:"none"}}const Eo=new WeakMap;function zo(t){const e=function(t){const e=t?.connection||t;return!e||"object"!=typeof e&&"function"!=typeof e?null:e}(t);if(!e)return null;let i=Eo.get(e);return i||(i={revision:0,loaded:!1,configEntries:{},manifests:{},listeners:new Set,refs:0},Eo.set(e,i)),i}function Fo(t){t.revision++;for(const e of[...t.listeners])try{e()}catch{}}function Oo(t,e){const i=zo(t);if(!i)return()=>{};i.refs++,i.listeners.add(e),async function(t,e){e.loading||"function"!=typeof t?.callWS?e.loading:e.loaded&&e.loadedAt&&Date.now()-e.loadedAt<3e5||(e.loading=(async()=>{const[i,s]=await Promise.allSettled([t.callWS({type:"config_entries/get"}),t.callWS({type:"manifest/list"})]);"fulfilled"===i.status&&Array.isArray(i.value)&&(e.configEntries=Object.fromEntries(i.value.filter(t=>"string"==typeof t?.entry_id).map(t=>[t.entry_id,t]))),"fulfilled"===s.status&&Array.isArray(s.value)&&(e.manifests=Object.fromEntries(s.value.filter(t=>"string"==typeof t?.domain).map(t=>[t.domain,t]))),e.loaded=!0,e.loadedAt=Date.now(),e.loading=void 0,Fo(e)})().catch(()=>{e.loaded=!0,e.loadedAt=Date.now(),e.loading=void 0,Fo(e)}),e.loading)}(t,i),async function(t,e){if(e.unsubscribe||e.subscribing)return e.subscribing;const i=t?.connection?.subscribeMessage;"function"==typeof i&&(e.subscribing=(async()=>{try{e.unsubscribe=await i.call(t.connection,t=>{const i=Array.isArray(t)?t:Array.isArray(t?.entries)?t.entries:t?[t]:[];let s=!1;const n={...e.configEntries};for(const t of i){const e=t?.entry||t?.data?.entry||("string"==typeof t?.entry_id?t:null),i=e?.entry_id;if("string"!=typeof i)continue;const o=t?.type||t?.action||t?.data?.action;"removed"===o||"remove"===o?delete n[i]:n[i]=e,s=!0}s&&(e.configEntries=n,Fo(e))},{type:"config_entries/subscribe"})}catch{}finally{0===e.refs&&(e.unsubscribe?.(),e.unsubscribe=void 0),e.subscribing=void 0}})(),e.subscribing)}(t,i);let s=!1;return()=>{s||(s=!0,i.listeners.delete(e),i.refs=Math.max(0,i.refs-1),i.refs||(i.unsubscribe?.(),i.unsubscribe=void 0))}}function No(t){const e=zo(t);return e?{revision:e.revision,loaded:e.loaded,configEntries:e.configEntries,manifests:e.manifests}:{revision:0,loaded:!1,configEntries:{},manifests:{}}}function Lo(t,e,i=No(t),s){const n=i.manifests[e];return function(t,e){if("function"!=typeof t?.localize)return"";try{const i=`component.${e}.title`,s=t.localize(i);return"string"==typeof s&&s!==i?s:""}catch{return""}}(t,e)||String(n?.name||"")||String(s||"")||e.replace(/_/g," ")}function Ho(t,e,i,s,n){const o=String(e||"").trim();if(!o)return null;const r=n?s.configEntries[n]:null;return{domain:o,label:Lo(t,o,s,r?.title),configEntryId:n,configEntryTitle:r?.title,confidence:i}}function qo(t){const e={"registry-owner":0,"entity-platform":1,"identifier-fallback":2},i=new Set;return t.filter(t=>!!t).sort((t,i)=>e[t.confidence]-e[i.confidence]||t.label.localeCompare(i.label)||t.domain.localeCompare(i.domain)).filter(t=>{const e=`${t.domain}\n${t.configEntryId||""}`;return!i.has(e)&&(i.add(e),!0)})}function Bo(t,e,i,s=No(t)){const n=i.entities?.[e]||t?.entities?.[e];if(!n)return null;const o=n.config_entry_id||void 0,r=o?s.configEntries[o]?.domain:"";return Ho(t,n.platform||r,"entity-platform",s,o)}var jo={"editor.context_actions":"Actions: {object}","editor.tool_options":"Tool options: {tool}","editor.palette":"Palette: {tool}","editor.open_group":"Tool group: {group}","editor.group_active":"{group} — active: {item}","editor.disabled_action":"{action} is unavailable: {reason}","btn.properties":"Properties","btn.keep_as_walls":"Keep as closed walls","title.markup_partition":"Partition: draw one independent physical wall","title.markup_select":"Select and edit walls, columns and saved outlines","title.markup_column":"Column: click a grid point to place a square column","markup.partition":"Partition","markup.column":"Column","markup.hint_partition":"two clicks draw one partition · Shift locks to 45°","markup.hint_column":"click a grid point to place a square column","history.draft_segment":"Add room-draft segment","history.draft_merge":"Join unfinished room outlines","history.draft_segment_delete":"Remove room-draft segment","history.partition_add":"Add partition","history.column_add":"Add column","history.physical_edit":"Change physical object","history.physical_delete":"Delete physical object","history.physical_move":"Move physical object","history.contour_to_partitions":"Convert contour to closed walls","toast.column_duplicate":"A column with the same centre and outer size already exists","confirm.delete_draft":"Delete the whole unfinished room contour?","confirm.delete_draft_segment":"Delete this draft segment? The remaining contour may split in two.","physical.partition_properties":"Partition properties","physical.column_properties":"Column properties","physical.draft_properties":"Draft segment properties","physical.shape":"Shape","physical.square":"Square","physical.circle":"Circle","physical.diameter":"Diameter","physical.side":"Side","physical.rotation":"Rotation angle","physical.length":"Length","physical.allowed_range":"Allowed: 1–{max} {unit}","physical.delete_segment":"Delete segment","physical.delete_draft":"Delete entire outline","physical.partition_size_title":"Thickness of the partition created by the second click (1–100 cm).","physical.column_size_title":"Outer side of the square column placed by the click (1–150 cm).","card.title":"House plan","count.devices":"{n} dev.","empty.no_spaces":"No spaces yet.","empty.add_first":"Add the first space and upload a floor plan.","empty.install":'Install the House Plan integration and add it in "Devices & services".',"btn.add_space":"Add space","btn.cancel":"Cancel","btn.save":"Save","btn.close":"Close","btn.delete":"Delete","btn.edit":"Edit","btn.open_in_ha":"Open in HA","btn.reset":"Reset","btn.attach":"Attach…","btn.upload":"Upload…","btn.replace":"Replace…","title.zoom_in":"Zoom in","title.zoom_out":"Zoom out","title.zoom_fit":"Fit all","title.add_device":"Add a device to the plan","title.show_all":"Show hidden and disabled devices as service markers (this tab only)","title.markup":"Room markup: grid, lines, outlines","title.configure_space":"Configure space","title.add_space":"Add space","title.markup_add":"Room outline: connect grid dots until the room closes; Shift locks the direction in 45° steps","title.markup_merge":"Merge: click one room, then the neighbour it shares a wall with","title.markup_split":"Split a room: click the room, then two points on its walls","title.markup_delroom":"Delete room: click inside a room and confirm","title.markup_boundary":"Boundary: click two points on a solid shared wall to make that stretch virtual; click a dashed stretch to restore it","title.need_plan":"Upload a floor-plan image","markup.add":"Room outline","markup.merge":"Merge","markup.split":"Split","markup.resize":"Resize","title.markup_resize":"Resize rooms: drag a wall handle; click a room for a corner scale frame","markup.hint_resize":"drag a wall handle · click a room — corner frame · Esc cancels a drag · Ctrl+Z — undo a step","markup.opening":"Opening","markup.boundary":"Boundary","markup.delete_room":"Delete room","history.undo":"Undo","history.redo":"Redo","history.undo_named":"Undo: {name} (Ctrl+Z)","history.redo_named":"Redo: {name} (Ctrl+Shift+Z / Ctrl+Y)","history.undo_empty":"Nothing to undo","history.redo_empty":"Nothing to redo","history.undone":"Undone: {name}","history.redone":"Redone: {name}","history.add_room":"Create room","history.split_room":"Split room","history.merge_rooms":"Merge rooms","history.resize_room":"Resize room","history.open_boundary":"Open boundary","history.close_boundary":"Restore wall","history.wall_thickness":"Change wall thickness","history.add_opening":"Add door or window opening","history.edit_opening":"Edit opening","history.move_opening":"Move opening","history.delete_opening":"Delete opening","history.delete_room":"Delete room","history.decor_add":"Add decor object","history.decor_edit":"Edit decor object","history.decor_move":"Move decor object","history.decor_transform":"Transform decor object","history.decor_delete":"Delete decor object","history.backdrop_transform":"Transform plan backdrop","title.markup_opening":"Openings: click a wall to place, click an opening to edit","opening.new":"New opening","opening.edit":"Opening","opening.door":"Door","opening.window":"Window","opening.gate":"Gate","opening.type_label":"Type","opening.length_label":"Length, cm","opening.contact_label":"Open/close sensor","opening.lock_label":"Lock","opening.none":"— none —","opening.invert":"Invert open/closed","opening.flip_h":"Hinge on the other jamb","opening.flip_v":"Opens to the other side","opening.open":"Open","opening.closed":"Closed","opening.locked":"Locked","opening.unlocked":"Unlocked","opening.state_unknown":"unavailable","opening.no_entities":"No sensors bound — a static symbol on the plan.","toast.opening_no_wall":"Click next to a room wall — openings sit on walls","markup.delete":"Delete","markup.hint_points":"points: {n} · Shift — 45° steps · Esc/Ctrl+Z — undo a dot · first point or Ctrl/Cmd+click — close","markup.hint_start":"click a grid dot to start the outline","tip.lqi":"average zigbee signal:","tip.area":"area: {value}","info.device_header":"Device on the plan","info.model":"Model","info.state":"State","info.link":"Link","info.manuals":"Manuals","info.none":"No additional information","marker.new_device":"New device","marker.name_label":"Name (shown on the plan)","marker.name_ph":"Name","marker.binding_label":"Bind to an HA device","marker.binding_disabled":"disabled in Home Assistant","marker.virtual_option":"Virtual device (no binding)","marker.search_ph":"Search device / group…","marker.nothing_found":"nothing found","marker.room_label":"Room","marker.room_override":" (override placement)","marker.room_choose":"— select a room —","marker.room_auto":"— by device area (auto) —","marker.icon_label":"Icon","marker.icon_ph":"mdi:… (empty = auto)","marker.display_label":"Display","display.badge":"Icon + state","display.icon_ripple":"Icon + state and activity","display.static_icon":"Always static icon","marker.display_hint":"Icon + state changes the plate for work, open, alarm and unavailable states. Icon + state and activity additionally shows a short pulse for events and a continuous pulse for ongoing work, movement or presence. Value + state replaces the icon with one unambiguous HA value. A static icon never reacts to device states.","marker.display_hint_badge":"The icon and dynamic plate show device state without the ordinary activity pulse. Red alarms remain visible.","marker.display_hint_icon":"The icon and dynamic plate show device state without the ordinary activity pulse. Red alarms remain visible.","marker.display_hint_icon_ripple":"The icon, dynamic plate and pulse show a short pulse for events and a continuous pulse for work, motion or presence. Red alarms have separate priority.","marker.display_hint_value":"One unambiguous Home Assistant value replaces the icon while the plate continues to show state. Red alarms remain visible.","marker.display_hint_static_icon":"The black plate and icon always stay the same. State, activity, unavailability and alarms do not change the face.","marker.static_alarm_warning":"This device can report alarms. Static display hides the marker's visual alarm indication.","marker.preview.title":"Display preview","marker.preview.actual":"Now","marker.preview.example":"Example","marker.preview.integration":"Provided by","marker.preview.source":"Display source","marker.preview.current_state":"Current state","marker.preview.result":"On the plan","marker.preview.details":"Source details","marker.preview.select_source":"Choose a Home Assistant device or entity to see its actual display.","marker.preview.unknown_provider":"Unknown integration","marker.preview.virtual_provider":"House Plan · virtual device","marker.preview.no_source":"No active source","marker.preview.no_state":"No current state","marker.preview.mixed_states":"Several different states","marker.preview.multiple_sources":"{n} sources","marker.preview.more_sources":"+{n} more","marker.preview.scaled":"The preview is scaled to {n}% to fit. The saved size is unchanged.","marker.preview.demo_activity":"Show activity example","marker.preview.demo_notice":"Temporary activity example; the actual device state is unchanged.","marker.preview.demo_short":"Show short activity","marker.preview.demo_continuous":"Show continuous activity","marker.preview.stop_continuous":"Stop continuous activity","marker.preview.demo_short_notice":"Short activity example; the actual device state is unchanged.","marker.preview.demo_continuous_notice":"Continuous activity example; the actual device state is unchanged.","marker.preview.demo_already_visible":"The current state already shows a real activity or alarm effect.","marker.preview.reduced_motion":"System reduced-motion is enabled, so ordinary activity is shown as a dot.","marker.preview.reason.neutral":"Neutral dark plate","marker.preview.reason.working":"Yellow plate: the device is working now","marker.preview.reason.working_activity":"Yellow plate and activity effect: the device is working now","marker.preview.reason.open":"Orange plate: physically open or unlocked","marker.preview.reason.cover_icon_state":"Neutral plate; the cover entity controls the displayed state and icon","marker.preview.reason.presence":"Activity effect while presence is detected","marker.preview.reason.event":"Short activity effect after a detected event","marker.preview.reason.transition":"Activity effect while the device is moving or changing state","marker.preview.reason.media_neutral":"Media devices stay on a neutral dark plate while available","marker.preview.reason.unavailable":"Subdued neutral plate: unavailable or off","marker.preview.reason.alarm":"Red alarm plate; alarm indication is always shown","marker.preview.reason.live_states_disabled":"Live-state styling is disabled for this card","marker.preview.reason.value_no_state":"No usable state value; the icon is shown instead","marker.preview.reason.value_ambiguous_sources":"Several possible value sources; the icon is shown instead","marker.preview.reason.value_non_scalar":"The source did not return a simple value; the icon is shown instead","marker.preview.reason.value_virtual":"A virtual device has no Home Assistant value; the icon is shown instead","marker.preview.reason.vacuum_live_plan_only":"Live vacuum position and trail are available only on the full plan","marker.preview.reason.hidden_design_preview":"The device is hidden on the plan; this design preview remains visible","marker.preview.reason.composite_power_source":"State comes from the device Power entity; auxiliary switches are ignored","marker.preview.reason.activity_display_disabled":"The selected display mode does not show ordinary activity effects","marker.preview.reason.ha_disabled":"The Home Assistant binding is disabled and will be hidden on the plan","marker.preview.reason.orphaned":"The saved Home Assistant binding can no longer be found","marker.preview.reason.static_icon":"Static mode: device state does not change the icon","marker.activity_color":"Activity pulse color","marker.ripple_size":"Activity pulse size","marker.activity_alarm_note":"Color and size affect ordinary activity only and do not change red alarms.","marker.pulse_a11y_alarm":"Alarm","marker.pulse_a11y_event":"Recent event","marker.pulse_a11y_presence":"Presence detected","marker.pulse_a11y_transition":"Changing state","marker.pulse_a11y_running":"Working","marker.size_label":"Icon size / rotation","marker.angle_label":"Rotate","marker.model_label":"Model","marker.model_ph":"e.g. Aqara T&H","marker.link_label":"Link","marker.desc_label":"Description","marker.desc_ph":"Notes, specs…","marker.manuals_label":"Manuals (PDF etc.)","marker.sub_device":"device","marker.sub_z2m_group":" · Z2M group","marker.sub_group":"group","marker.sub_helper":"helper","space.new":"New space","space.header":"Space","space.title_label":"Title","space.title_ph":"e.g. Garage","space.plan_label":"Floor plan (background)","space.no_plan":"no plan image","space.plan_alt":"plan","room.new":"New room","room.name_label":"Display name","room.name_ph":"e.g. Terrace","room.area_label":"Home Assistant area (unassigned)","room.no_area_option":"— no area —","room.default_name":"Room","device.unnamed":"unnamed","device.light_group":"light group","device.fallback":"device","device.virtual":"virtual device","confirm.delete_room":'Delete room "{name}"?',"confirm.merge_rooms":'Merge rooms "{a}" and "{b}"?',"confirm.remove_marker":'Delete "{name}" from the plan? The device will disappear completely and stop contributing to plan data. You can add it again later.',"confirm.erase_decor":"Erase the {kind} object? You can undo this action from the editor history.","confirm.delete_space":'Delete space "{title}" with all its rooms and markup?',"toast.pos_save_failed":"Failed to save position: {err}","toast.no_entity":"The device has no suitable entity","toast.ha_disabled_action":"A Home Assistant object that is disabled cannot be used on the plan.","toast.ha_disabled_show_device":"A device disabled in Home Assistant cannot be shown on the plan. Enable it in Home Assistant first.","toast.ha_disabled_show_entity":"An entity disabled in Home Assistant cannot be shown on the plan. Enable it in Home Assistant first.","toast.ha_disabled_add":"A disabled Home Assistant object cannot be added to the plan. Enable it in Home Assistant first.","toast.ha_binding_unverified":"The object status could not be verified through the Home Assistant registry. Display and actions are temporarily unavailable.","toast.markup_needs_server":"Markup is available after the config is moved to the server","toast.conflict":"Config was changed in another window — data refreshed, repeat your last action","toast.cfg_save_failed":"Failed to save config: {err}","toast.room_overlap":"The outline overlaps room “{name}” — rooms must not overlap","toast.contour_min_edges":"Draw at least two edges before closing the outline","toast.contour_cannot_close":"The outline cannot close because it is degenerate or intersects itself","toast.merge_not_adjacent":"Only rooms that share a wall can be merged","toast.rooms_merged":"Rooms merged into “{name}”","toast.split_pick_wall":"Start the cut on the room’s wall","toast.split_bad_cut":"The cut must run wall to wall inside the room, without crossing walls or itself","merge.header":"Merge rooms","merge.hint":"The merged room keeps one name and one area. The other area is released — its devices leave the plan until another room claims it.","merge.keep":"Keep","merge.no_area":"no area","room.split_header":"New room from the split","toast.room_saved":"Room saved ({n}). Devices added: {added}. Outline the next one or exit markup.","toast.room_saved_no_area":"Room saved ({n}, no area). Outline the next one or exit markup.","toast.marker_needs_server":"Device editing is available after the config is moved to the server","toast.virtual_name_required":"Enter a name for the virtual device","toast.marker_saved":"Device saved","toast.marker_removed":"Device deleted from the plan","toast.integration_missing":"The House Plan integration is not installed — management unavailable","toast.plan_formats":"Supported formats: SVG, PNG, JPG, WebP","toast.plan_required":"Upload a floor plan — it is required","toast.space_added_onboard":"Space added. Outline the rooms: click grid dots and close the contour.","toast.space_added":"Space added","toast.space_saved":"Space saved","toast.space_deleted":"Space deleted","toast.delete_failed":"Delete failed: {err}","toast.error":"Error: {err}","toast.file_failed":'File "{name}" was not uploaded: {err}',"toast.files_attached":"Files attached: {n}","err.unknown":"unknown error","err.code":"code {code}","err.too_large":"file larger than {mb} MB","err.bad_ext":"unsupported type (PDF/image expected)","err.unauthorized":"administrator rights required","editor.title":"Title","editor.default_floor":"Default space","editor.icon_size":"Icon size, % of plan width","editor.show_temperature":"Show temperature","editor.live_states":"Live states (on/off, open…)","editor.show_signal":"Show zigbee signal (LQI)","editor.language":"Interface language","editor.lang_auto":"Auto (HA profile)","editor.lang_en":"English","editor.lang_ru":"Русский","title.icon_rules":"Icon rules: which MDI icon devices get by name","rules.title":"Icon rules","rules.hint":"Rules are checked top-down against “device name + model” (case-insensitive regex); the first match wins. When nothing matches, the entity device class decides, then the generic chip icon.","rules.pattern_ph":"regex, e.g. plug|socket","rules.icon_ph":"mdi:power-socket-de","rules.add":"Add rule","rules.reset":"Reset to defaults","rules.test_ph":"Try a device name…","rules.invalid":"invalid regex","rules.saved":"Icon rules saved","btn.up":"Up","btn.down":"Down","tap.info":"Device card","tap.more_info":"HA more-info dialog","tap.toggle":"Toggle state","marker.tap_label":"Tap action for this device","tap.toggle_note":"The resolved state is shown below. Secure devices are never toggled from the plan.","import.title":"Create spaces from HA floors","import.hint":"Your Home Assistant already knows these floors. Pick the ones to turn into plan spaces — you will upload a floor-plan image for each one next. Rooms are then outlined by hand on the plan.","import.start":"Create {n} space(s)","import.manual":"Start from scratch","import.progress":"Floor {i} of {n}","import.done":"Spaces created. Outline the rooms: click grid dots and close the contour.","btn.skip":"Skip","space.scale_label":"Scale (grid cell size)","space.scale_unit":"cm per cell","space.display_section":"Display","space.show_borders":"Always show room borders","space.show_names":"Show room names (drag to move)","space.room_color":"Border & name color","space.opacity":"Opacity","space.fill_label":"Room fill","fill.none":"None","fill.lqi":"Zigbee signal","fill.light":"Lights","fill.custom":"Custom color","space.custom_fill":"Fill color","space.source_file":"I have a floor-plan image","space.source_draw":"No image — I'll outline rooms by hand","space.orientation":"Canvas","orient.landscape":"Landscape","orient.portrait":"Portrait","orient.square":"Square","fill.temp":"Temperature","space.temp_min":"Comfort from","space.temp_max":"to","tip.temp_avg":"average temperature:","space_card.button":"Open the space plan","space_card.not_found":"Space “{id}” not found","space_card.loading":"Loading…","continuity.restore_plan":"Restoring floor plan…","continuity.restore_connection":"Restoring device connection…","continuity.retry":"Try again","editor.space":"Space","editor.show_button":"Show button","editor.button_label":"Button label","editor.button_target":"Target dashboard path","marker.sub_entity":"entity","title.general_settings":"General settings","gs.title":"General settings","gs.hint":"Fill colors apply to every space; each color has its own opacity. Which fill mode a space uses is set in that space's dialog.","gs.light_group":"Fill: lights","gs.light_on":"Lights on","gs.light_off":"All lights off","gs.temp_group":"Fill: temperature","gs.temp_cold":"Cold","gs.temp_ok":"Comfortable","gs.temp_hot":"Hot","gs.lqi_group":"Fill: zigbee signal","gs.lqi_low":"Weak signal","gs.lqi_high":"Strong signal","gs.reset":"Reset to defaults","gs.saved":"General settings saved","space.show_lqi":"Show zigbee signal (LQI) next to devices","space.hide_decor":"Hide the decorative layer","space.hide_decor_tip":"Lines, shapes, labels and furniture stay where they are — visible in the backdrop editor, not on the plan.","space.hide_openings":"Hide openings","space.hide_openings_tip":"Door, window and gate symbols are not drawn, but the openings keep working: light passes through, the sun comes in at a window, contact sensors still open them. The plan editor always shows them.","gs.light_none":"No light sources","mode.plan":"Plan editor","mode.devices":"Device editor","display.value":"Value + state","marker.subarea":"no area, manual","device.new":"New device — open its editor to dismiss","opening.unlock_action":"Unlock","opening.lock_action":"Lock","opening.lock_pending":"Working…","title.close_editor":"Close editor (back to view)","devbar.add":"Add","devbar.show_all":"Hidden and disabled","devbar.rules":"Icon rules","space.roomcard_section":"Room card shows:","space.label_temp":"Temperature","space.label_hum":"Humidity","space.label_lqi":"Average Zigbee signal","space.label_light":"Lights on/off","roomcard.light_on":"On","roomcard.light_off":"Off","roomcard.light_partial":"{on} of {total}","toast.split_pick_inside":"Intermediate cut points must be inside the room","mode.decor":"Background editor","decor.select":"Select","decor.line":"Line","decor.rect":"Rectangle","decor.ellipse":"Oval","decor.text":"Text","decor.erase":"Erase","decor.erase_confirm_title":"Erase object?","decor.color":"Color","decor.width":"Line width","decor.line_style":"Line style","decor.line_style_solid":"Solid","decor.line_style_dashed":"Dashed","decor.fill":"Fill","decor.fill_color":"Fill color","decor.length":"Length","decor.size":"Size","decor.angle":"Rotation angle, °","decor.text_size":"Text size","decor.backdrop_properties":"Plan backdrop properties","decor.text_title":"Text label","decor.object_title":"Edit {kind}","decor.text_label":"Text","decor.live_group":"Insert HA variable","decor.live_entity":"Entity","decor.live_entity_ph":"choose an entity","decor.live_attr":"Value","decor.live_attr_ph":"choose state or attribute","decor.live_state":"State","decor.backdrop":"Backdrop image","decor.backdrop_hint":"Drag to move; pull a corner to resize; use the top handle to rotate. Shift changes proportions or frees the angle.","decor.backdrop_reset":"Reset the picture","decor.backdrop_reset_done":"The picture is back at its original place and size","marker.icon_auto":"Auto: {icon} (by icon rules; pick one to override)","marker.icon_pin_auto":"Pin","mode.plan_tip":"Plan editor — the geometry of the home: draw and split/merge rooms, bind them to HA areas, place doors, windows and gates, move room cards, set the scale","mode.devices_tip":"Device editor — everything about icons: drag to position, click to edit binding/icon/display, add virtual devices, icon rules","mode.decor_tip":"Background editor — purely visual decor under the plan: lines, rectangles, ovals and text labels that never react to clicks","space.glow_enabled":"Light-source glow","gs.glow_group":"Light-source glow","gs.glow_base":"House darkness","gs.glow_light":"Default light color / intensity","gs.wall_group":"Walls","gs.wall_fill":"Wall fill","gs.glow_radius":"Glow radius","gs.unit_m":"m","gs.unit_ft":"ft","marker.controls_label":"Controls other light sources","marker.controls_hint":"With tap action “Toggle”, a click flips every source added here. The marker’s own bound entity is controlled directly and cannot be added; use the switch below to classify it as a light.","marker.controls_filter":"Search lights and switches…","info.controls":"Controls","marker.glow_radius_label":"Glow radius","marker.glow_radius.help":"Sets the glow radius in metres or feet; an empty value uses the radius from general settings.","marker.glow_radius.help.aria":"Help: glow radius","markup.wallthick":"Thickness","markup.select":"Select","title.markup_wallthick":"Thickness — click a wall to set how thick it is. Empty or zero removes the thickness.","markup.hint_wallthick":"click a wall · Esc closes without applying","wallthick.field":"Thickness","wallthick.unit_cm":"cm","wallthick.unit_in":"in","wallthick.apply_room":"Apply to all walls of this room","markup.draw_wall_title":"Thickness of each new room-wall segment (1–100 cm). Shared walls keep the neighbour's value.","toast.wallthick_pick":"Click a wall (not an open boundary)","toast.wallthick_open":"Open boundaries have no thickness","toast.wallthick_set":"Wall thickness set","toast.wallthick_cleared":"Wall thickness removed","toast.physical_range":"Enter a value from 1 to {max} {unit}","toast.physical_angle":"Enter a rotation angle from 0° up to, but not including, 90°","toast.physical_limit":"The space has reached the limit for this type of geometry","markup.boundary_hint":"Select a shared room boundary or a dashed stretch","markup.boundary_hint_open":"Click the start of the open stretch","markup.boundary_hint_second":"Choose the end on the selected boundary · Esc to cancel","markup.boundary_hint_retry":"Choose the end on the selected boundary","markup.boundary_hint_restore":"Click to restore the wall","markup.boundary_hint_outer":"Only a boundary shared by two rooms can be opened","markup.boundary_hint_blocked":"a partition, column or unfinished contour is above this boundary","markup.boundary_hint_ambiguous":"Choose a stretch farther from the junction","markup.boundary_cancel":"Cancel the unfinished Boundary action","toast.openwall_pick":"This tool works with room boundaries","toast.openwall_shared_only":"Only a shared wall between two rooms can be opened","toast.openwall_short":"Stretch too short — pick a farther point","toast.boundary_opened":"Boundary stretch opened","toast.boundary_restored":"Wall restored on this stretch","toast.boundary_same_edge":"Choose the end point on the selected wall","toast.boundary_ambiguous":"Choose a stretch slightly farther from the junction","toast.boundary_blocked":"This boundary is covered by independent masonry. Move or delete it with Select first","toast.delete_room_pick":"Click inside the room you want to delete","toast.openwall_openings_removed":"Openings on the virtual stretch were removed","toast.openwall_opened":"Boundary “{a}” ↔ “{b}” is now open","toast.openwall_closed":"Boundary “{a}” ↔ “{b}” is closed again","toast.opening_on_virtual":"Openings cannot sit on a virtual wall","marker.from_ha_option":"Pick from the HA list","marker.show_entities":"Show entities","marker.show_entities_tip":"Adds not only devices to the list, but all their entities too","marker.pick_ph":"Choose a device…","room.open_area":"Open the HA area","view.volumetric":"Volumetric view","view.flat":"Flat view","kiosk.title":"This screen's sizes","kiosk.hint":"Stored on this device only — every wall tablet or TV can have its own comfortable sizes.","kiosk.icon_scale":"Device icon size","kiosk.font_scale":"Room card text size","editor.kiosk":"Wall device (kiosk) mode","editor.cycle":"Auto-switch spaces every N seconds (kiosk, 0 = off)","room.settings_title":"Room settings","room.settings_section":"Room settings (override the space)","room.fill_label":"Fill in THIS room","fill.inherit":"As the space","room.custom_fill_space":"Space color","room.custom_fill_own":"Room color","room.temp_src_label":"Temperature source","room.hum_src_label":"Humidity source","room.src_average":"Average over the room's sensors (default)","room.src_pick":"A specific HA device or entity","room.src_ph":"Choose a source…","toast.room_updated":"Room updated","space.card_font":"Room-card font size (whole space)","room.sizes_section":"Font sizes","room.name_scale":"Room name size","room.label_scale":"Metrics size","preview.room_name":"Living room","toast.cfg_reload_failed":"Could not reload the plan from the server: {err}","room.settings_short":"Room settings","room.unnamed":"Unnamed room","marker.use_climate_temp":"Include the device temperature in the room","marker.use_climate_temp_tip":"Adds an air conditioner or thermostat current_temperature to the room average. Configure the external value badge separately below.","marker.value_badge_title":"Value badge","marker.value_badge.help":"Shows one selected value next to the icon. It does not affect room metrics, light, Glow or the tap action.","marker.value_badge.help.aria":"Help: device value badge","marker.value_badge_enabled":"Show a value badge","marker.value_badge_source":"Value","marker.value_badge_source.help":"Choose a specific state, supported attribute, or derived value of this device.","marker.value_badge_source.help.aria":"Help: value badge source","marker.value_badge_position":"Position","marker.value_badge_position.help":"The chosen side stays fixed and does not flip automatically at a plan edge.","marker.value_badge_position.help.aria":"Help: value badge position","marker.value_badge_right":"Right","marker.value_badge_bottom":"Below","marker.value_badge_left":"Left","marker.value_badge_top":"Above","marker.value_badge_empty":"This device has no available values","marker.value_badge_static":"A static icon does not show live values. The setting is preserved.","marker.value_badge_missing":"Source unavailable","marker.value_badge_missing_hint":"The saved source is currently unavailable. Keep it, disable the badge, or choose another value.","marker.value_badge_duplicate":"The same value is already shown inside the icon.","marker.value_badge_state":"State · {name}","marker.value_badge_marker_state":"Light-source state · {name}","marker.value_badge_lqi":"Average Zigbee signal quality","marker.value_badge_attr_current_temperature":"Current temperature · {name}","marker.value_badge_attr_temperature":"Target temperature · {name}","marker.value_badge_attr_current_humidity":"Current humidity · {name}","marker.value_badge_attr_humidity":"Humidity · {name}","marker.value_badge_attr_current_position":"Position · {name}","marker.value_badge_attr_percentage":"Speed · {name}","marker.value_badge_attr_brightness":"Brightness · {name}","marker.value_badge_attr_volume_level":"Volume · {name}","marker.value_badge_attr_battery_level":"Battery · {name}","marker.value_badge_attr_fan_speed":"Fan speed · {name}","marker.light_role_label":"Is this device a light source?","marker.light_role.help":"Controls whether this marker itself is a spatial light source. Linked lights above remain independent room-light controls.","marker.light_role.help.aria":"Help: whether this device is a light source","marker.light_role_auto_yes":"Auto (light source)","marker.light_role_auto_no":"Auto (not a light source)","marker.light_role_always":"Always a light source","marker.light_role_never":"Never a light source","marker.light_entity_label":"Leading light entity","marker.light_entity.help":"For a composite device, choose the entity whose state, colour and brightness represent this plan source. The automatic fallback remains compatible with older plans.","marker.light_entity.help.aria":"Help: leading light entity","marker.light_entity_auto":"Automatic ({entity})","marker.light_entity_none":"no controllable entity","marker.light_entity_missing":"The saved entity {entity} is unavailable. House Plan temporarily uses {fallback}; the saved choice will be restored if it returns.","marker.glow_color_label":"Glow colour and brightness","marker.glow_mode.help":"Use live source values, override only its colour, or fix both colour and brightness. The minimum is 1%; to disable the source, choose “Never”.","marker.glow_mode.help.aria":"Help: glow colour and brightness","marker.glow_mode_auto":"From source","marker.glow_mode_color":"Set colour","marker.glow_mode_fixed":"Set colour and brightness","marker.glow_color":"Glow colour","marker.glow_brightness":"Brightness","marker.glow_disabled_never":"Glow settings are unavailable because this marker is explicitly not a light source.","marker.glow_disabled_auto":"Auto mode found no spatial light source for this marker.","marker.glow_disabled_no_entity":"No active controllable Home Assistant entity is available for this spatial source.","marker.glow_passive_hint":"This source has no own Home Assistant data. Set its colour and brightness manually; its radius remains available.","marker.control_broken":"Saved source is missing or no longer marked as a light","marker.control_missing_label":"Missing plan light","marker.control_passive":"passive source","toast.marker_control_cycle":"This link would create a circular chain of light controls.","toast.marker_binding_required":"Choose a Home Assistant device before linking another light source.","confirm.unlock":"Unlock “{name}”?","toast.files_migrate_failed":"Attachments could not be moved to the new binding, links keep pointing at the old files: {err}","space.pick_saved":"Already uploaded","space.pick_saved_hint":"Plans stored on the server, including ones you detached earlier","space.no_saved":"No plans stored on the server yet.","space.loading":"Loading…","space.used_by":"in use: {list}","space.in_use":"A space still uses this plan — detach it first","btn.use":"Use","confirm.delete_plan":'Delete the plan file "{name}" from the server? This cannot be undone.',"toast.plans_list_failed":"Could not list the stored plans: {err}","toast.plan_delete_failed":"Could not delete the plan: {err}","marker.hide":"Hide","marker.hide_tip":'The device will disappear from the plan after saving but will still count toward the room signal. Restore it through "Hidden and disabled" in the device editor.',"marker.show":"Show","marker.show_tip":"The device will appear on the plan again after saving.","marker.hidden_ghost":"Device hidden by the user","marker.ha_disabled_device":"The device is disabled in Home Assistant and hidden from the plan.","marker.ha_disabled_entity":"The entity is disabled in Home Assistant and hidden from the plan.","marker.ha_disabled_all_entities":"The device has no active Home Assistant entities, so it is hidden from the plan.","marker.ha_registry_limited":"The full Home Assistant registry is unavailable to this user. The unverified object cannot be shown or used for now.","marker.delete_tip":"Completely delete the device from the plan and every aggregate. You can add it again later.","tap.run":"Run automation/script/scene","tap.cover":"Open/close (curtains/blinds)","marker.run_target_label":"What to run","marker.run_search_ph":"Search: automation, script or scene…","marker.run_target_gone":"Target {id} not found — pick again","marker.tap_confirm":"Ask for confirmation","marker.tap_confirm_tip":"Show a confirmation dialog before acting — a guard against accidental taps.","marker.toggle_hint_single":"Target: {name} ({id}).","marker.virtual_light_target":"Virtual light: {name}.","marker.virtual_light_current":"Manual state: {state} → {effect}.","marker.virtual_light_state_on":"on","marker.virtual_light_state_off":"off","marker.toggle_hint_group":"Will toggle {count} source(s): {names}.","marker.toggle_hint_current":"Now: {state} → {effect}.","marker.toggle_hint_group_current":"Currently on: {on} of {count} → {effect}.","marker.toggle_hint_skipped":"Will be skipped ({count}): {targets}.","marker.toggle_effect_turn_on":"will turn on","marker.toggle_effect_turn_off":"will turn off","marker.toggle_effect_open":"will open","marker.toggle_effect_close":"will close","marker.toggle_effect_stop":"will stop","marker.toggle_effect_toggle":"state will toggle","marker.toggle_skip_missing":"not found","marker.toggle_skip_ha_disabled":"disabled in HA","marker.toggle_skip_unavailable":"unavailable","marker.toggle_skip_unsupported":"toggle is unsupported","marker.toggle_skip_secure":"blocked for security","marker.toggle_none_no_binding":"This device has no binding, so there is no state to toggle. Tapping will do nothing.","marker.toggle_none_no_actionable_entity":"This device has no state that can be toggled. Tapping will do nothing.","marker.toggle_none_configured_targets_missing":"The configured targets are unavailable. The device's own entity will not be substituted for them.","marker.toggle_none_ha_disabled":"The target is disabled in Home Assistant and cannot be used on the plan.","marker.toggle_none_unavailable":"The target is currently unavailable. Tapping will do nothing.","marker.toggle_none_unsupported":"Home Assistant exposes no safe toggle operation for this entity. Tapping will do nothing.","marker.toggle_none_secure":"Toggling locks, alarms and secure gates from the plan is blocked for security.","run.automation":"automation","run.script":"script","run.scene":"scene","confirm.tap_run":'Run "{name}"?',"confirm.tap_toggle":'Toggle "{name}"?',"confirm.tap_cover":'Open/close "{name}"?',"toast.run_started":"Started: {name}","toast.run_target_missing":"Run target not found — check the device settings","toast.run_target_required":"Pick an automation, script or scene","toast.tap_target_changed":"The action target changed. Try again.","toast.virtual_light_toggle_failed":"Could not toggle the virtual light: {err}","toast.value_badge_source_required":"Choose a value for the badge","btn.run":"Run","vac.section":"Robot vacuum: live position","vac.autocal":"Set up automatically","vac.live":"Live position on the plan","vac.trail":"Show the robot's path","vac.cal_maps":"Calibrated maps: {maps}","vac.autocal_no_rooms":"The integration reports no room list — open “Fit manually”","vac.autocal_no_match":"Room names did not match (need ≥3 in common) — open “Fit manually”","vac.autocal_done":"Done: bound via {rooms} rooms. Start a cleanup and check","vac.cal_need_pos":"The robot is not reporting coordinates — start a cleanup and pause it","vac.cal_done":"Calibration saved. Start a cleanup and check","vac.cal_cancelled":"Calibration cancelled","vac.fit":"Fit manually","vac.fit_hint":"Drag the robot map into place, stretch by the corners","vac.fit_rotate":"Rotate 90°","vac.fit_mirror":"Mirror","vac.trail_never":"Never","vac.trail_cleaning":"While cleaning","vac.trail_always":"Always","gs.bg_group":"Stage background","gs.bg_color":"Background around the plan","gs.bg_default":"Theme default","gs.bg_theme":"theme default","gs.bg_mode":"Plan background","gs.bg_static":"Static color","gs.bg_daynight":"Follows the Sun","gs.bg_daynight_hint":"The background moves through dawn, day, dusk and night using sun.sun, or your local clock when sun data is unavailable. No compass is needed for the background.","gs.sun_group":"Sun","gs.sun_missing":"The sun.sun entity was not found — the background follows your local clock; window rays are unavailable.","gs.north":"North on the plan","gs.north_ph":"not set","gs.north_hint":"Set north only for window rays (1° steps, 15° with Shift); the Sun background works without it.","gs.north_clear":"Clear","gs.north_letter":"N","gs.sun_rays":"Sunlight through windows","gs.about_group":"About","gs.about_version":"Houseplan Card v{v}","gs.about_github":"GitHub · docs & issues","gs.about_telegram":"Telegram chat","space.bg_color":"Background around the plan","space.bg_inherit":"Inherit general","space.bg_inherited":"inherits general settings","space.bg_mode":"Plan background","space.north":"North on the plan (override)","space.north_inherited":"inherited: {v}","space.sun_rays":"Sunlight through windows","space.sun_inherit":"Inherit general","space.sun_on":"On","space.sun_off":"Off","canvas.far_objects":"{n} object(s) far from the plan","canvas.show_far":"Show","canvas.home_tip":"The plan is over there — click to fit it","gs.grid_group":"Plan maintenance","gs.grid_hint":"Updates data models, aligns plan elements to the grid and merges redundant wall fragments. An exact report is shown before anything is stored.","gs.align_all":"Optimize plans","gs.align_title":"Optimize plans","gs.align_none":"All plans already use the current optimized data model.","gs.align_count":"{n} of {total} elements will move, by at most {cm} cm.","gs.align_where":"The largest shift is in “{s}”.","gs.align_turned":"Openings whose angle is corrected: {n}.","gs.align_removed_drafts":"Invalid outlines collapsed by the grid and removed: {n}.","gs.optimize_changes":"Model migrations: {m}; plans canonicalized: {c}; merged real-wall fragments: {w}; virtual fragments: {s}.","gs.optimize_glow_migration":"Legacy Glow: {spaces} spaces → no data fill + independent Glow; {rooms} rooms → inherited data fill + independent Glow.","gs.align_warn":"Elements deliberately placed between grid nodes will move. One undo is available after the operation, only until the next plan edit.","gs.align_run":"Optimize","gs.align_done":"Plans optimized: {n} elements moved, {m} records maintained","gs.optimize_undo":"Undo last optimization","gs.optimize_undone":"The last optimization was undone","decor.furniture":"Furniture","furn.title":"Furniture library","furn.symbol":"Symbol","furn.group_furniture":"Furniture","furn.group_appliance":"Appliances","furn.group_sanitary":"Plumbing","furn.group_other":"Other","furn.width":"Width","furn.depth":"Depth","furn.pick_hint":"Pick a symbol, then click on the plan.","furn.place_hint":"Click on the plan — the piece lands against the nearest wall. Shift places it free.","furn.sym_sofa":"Sofa","furn.sym_armchair":"Armchair","furn.sym_coffee_table":"Coffee table","furn.sym_table_dining":"Dining table","furn.sym_table_round":"Round table","furn.sym_chair":"Chair","furn.sym_desk":"Desk","furn.sym_bed_double":"Double bed","furn.sym_bed_single":"Single bed","furn.sym_nightstand":"Nightstand","furn.sym_wardrobe":"Wardrobe","furn.sym_bookshelf":"Bookshelf","furn.sym_fridge":"Fridge","furn.sym_stove":"Cooker","furn.sym_dishwasher":"Dishwasher","furn.sym_washer":"Washing machine","furn.sym_dryer":"Tumble dryer","furn.sym_tv":"TV","furn.sym_ac":"Air conditioner","furn.sym_water_heater":"Water heater","furn.sym_toilet":"Toilet","furn.sym_bathtub":"Bathtub","furn.sym_shower":"Shower","furn.sym_sink":"Washbasin","furn.sym_kitchen_sink":"Kitchen sink","furn.sym_bidet":"Bidet","furn.sym_stairs":"Stairs","furn.sym_fireplace":"Fireplace","furn.sym_plant":"Plant","furn.sym_rug":"Rug","common.yes":"Yes","common.no":"No","vac.diag_source":"Source","vac.diag_platform":"Integration","vac.diag_status":"Status","vac.diag_position":"Position","vac.diag_rooms":"Rooms","vac.diag_rooms_value":"{total} · {matched} names match · {readiness}","vac.autocal_ready":"auto-calibration available","vac.autocal_not_ready":"need 3 matching names","vac.diag_path":"Integration path","vac.diag_map":"Map ID","vac.source_none":"not selected","vac.source_status_ok":"Ready","vac.source_status_missing":"Missing","vac.source_status_disabled":"Disabled in Home Assistant","vac.source_status_unavailable":"Unavailable","vac.source_status_unverified":"Cannot verify with current permissions","vac.source_status_unsupported":"No position data","vac.source_status_none":"No source","vac.source_banner_missing":"The saved source no longer exists. It was not replaced automatically; choose another source or restore it in Home Assistant.","vac.source_banner_disabled":"The saved source is disabled in Home Assistant. Enable it there or choose another source.","vac.source_banner_unverified":"Current Home Assistant permissions cannot verify this saved source. It remains pinned and will not be replaced automatically.","vac.choose_source":"Choose source","vac.source_auto":"Automatic","vac.source_auto_hint":"Use a compatible entity from this device","vac.all_cameras":"All cameras","vac.all_cameras_warn":"A camera may not provide robot data. Choose one only if it is your vacuum map.","vac.all_cameras_empty":"No other camera entities found.","vac.platform_unknown":"unknown integration","vac.cap_position":"position","vac.cap_rooms_short":"rooms","vac.cap_path":"path","vac.cap_map":"map ID","vac.cap_none":"no robot data detected","vac.xcme_hint":"Enable these Xiaomi Cloud Map Extractor attributes:","vac.documentation":"Documentation","vac.residual_title":"Check automatic calibration","vac.residual_message":"The matched rooms disagree by up to {error}. Apply this approximate calibration, refine it manually, or cancel without changing the saved setup.","vac.apply_proposal":"Apply","gs.backup_group":"Backup and transfer","gs.backup_hint":"Download a portable JSON backup or preview and import a backup made by House Plan.","backup.export_open":"Export","backup.import_open":"Import","backup.export_title":"Export House Plan","backup.import_title":"Import House Plan","backup.export_hint":"Choose whether the backup should contain the whole House Plan configuration or only the current space.","backup.full":"Full backup","backup.current_space":"Current space","backup.current_space_title":"Current space: {title}","backup.no_current_space":"No current space","backup.privacy_warning":"The archive keeps names, Home Assistant identifiers and exact coordinates. Internal plans and attachments are referenced, not embedded; runtime states and vacuum trails are not included.","backup.download":"Download JSON","backup.export_done":"Backup downloaded","backup.reading":"Checking the backup…","backup.revalidated":"The plan changed after this preview. The summary was refreshed; review it and confirm again.","backup.error.unauthorized":"You do not have permission to export or import this plan.","backup.error.not_ready":"House Plan is not ready yet. Try again in a moment.","backup.error.too_large":"The backup exceeds the 8 MiB limit.","backup.error.invalid_json":"The selected file is not valid JSON.","backup.error.invalid_format":"The selected file is not a House Plan backup.","backup.error.unsupported_export_version":"This backup format is not supported by the installed version.","backup.error.future_model":"The backup was created by a newer House Plan data model.","backup.error.invalid_config":"The backup contains an invalid House Plan configuration.","backup.error.invalid_layout":"The backup contains invalid object positions.","backup.error.invalid_content":"The backup contains invalid or inconsistent content references.","backup.error.space_not_found":"The selected space no longer exists.","backup.error.capacity_exceeded":"Adding this backup would exceed the plan limits.","backup.error.preview_expired":"The preview expired. Select the backup file again.","backup.error.preview_owner_mismatch":"This preview belongs to another Home Assistant user.","backup.error.conflict":"The plan changed after the preview. Review the refreshed summary.","backup.error.content_confirmation_required":"Confirm that unavailable local content may be detached.","backup.error.commit_failed":"The backup could not be applied safely. The previous plan was restored or is pending recovery.","backup.error.missing_plan":"A referenced plan file disappeared after the preview. Review the backup again.","backup.error.missing_content":"A referenced local attachment disappeared after the preview. Review the backup again.","backup.error.marker_control_missing":"A linked plan light is missing from the backup.","backup.error.marker_control_not_light":"A linked plan target is no longer marked as a light source.","backup.error.marker_control_self":"A light source cannot control itself.","backup.error.marker_control_cycle":"The backup contains a circular chain of light controls.","backup.error.duplicate_marker_control":"The backup contains a duplicate plan-light link.","backup.error.no_backup":"There is no import or optimization snapshot to restore.","backup.same_source":"Created on this Home Assistant instance","backup.foreign_source":"Created on another Home Assistant instance","backup.created":"Created: {value}","backup.versions":"Card {card}; integration {integration}; data model {model}","backup.count_spaces":"Spaces: {n}","backup.count_rooms":"Rooms: {n}","backup.count_walls":"Walls: {n}","backup.count_openings":"Openings: {n}","backup.count_decor":"Decor objects: {n}","backup.count_markers":"Devices: {n}","backup.count_layout":"Positions: {n}","backup.bindings":"Bindings — devices: {device}, entities: {entity}, virtual: {virtual}; positions without a space: {legacy}","backup.binding_status":"Target status — active: {active}, disabled: {disabled}, missing: {missing}","backup.missing_areas":"Areas missing on the target: {areas}","backup.dropped_marker_links":"Plan-light links outside this transferred space were omitted: {n}.","backup.replace_warning":"This replaces the current configuration and layout. Uploaded files are never deleted. One undo remains available until the next plan edit.","backup.foreign_bookkeeping":"Instance-specific known/new-device bookkeeping will not be imported.","backup.final_name":"New space name","backup.target_settings":"The target instance's global settings remain unchanged.","backup.duplicates":"Bindings already present: {n}","backup.skip":"Skip duplicate devices","backup.virtual_copy":"Add safe static virtual copies","backup.content":"Referenced content","backup.content_available":"available locally","backup.content_external":"external link","backup.content_detach_required":"will be detached","backup.confirm_detach":"I understand that unavailable internal plans and attachments will be detached from the imported configuration.","backup.replace":"Replace","backup.add":"Add space","backup.space_done":"Space imported: {rooms} rooms, {markers} devices","backup.full_done":"Backup restored: {spaces} spaces, {rooms} rooms, {markers} devices","backup.undo_import":"Undo last full import","backup.import_undone":"Full import undone"};const Wo={en:jo,ru:{"editor.context_actions":"Действия: {object}","editor.tool_options":"Параметры инструмента: {tool}","editor.palette":"Палитра: {tool}","editor.open_group":"Группа инструментов: {group}","editor.group_active":"{group} — активно: {item}","editor.disabled_action":"{action} недоступно: {reason}","btn.properties":"Свойства","btn.keep_as_walls":"Оставить замкнутыми стенами","title.markup_partition":"Перегородка: нарисуйте одну независимую физическую стену","title.markup_select":"Выбор и редактирование стен, колонн и сохранённых контуров","title.markup_column":"Колонна: кликните точку сетки для квадратной колонны","markup.partition":"Перегородка","markup.column":"Колонна","markup.hint_partition":"два клика рисуют одну перегородку · Shift фиксирует шаг 45°","markup.hint_column":"кликните точку сетки, чтобы поставить квадратную колонну","history.draft_segment":"Добавление сегмента черновика комнаты","history.draft_merge":"Соединение незавершённых контуров комнаты","history.draft_segment_delete":"Удаление сегмента черновика комнаты","history.partition_add":"Добавление перегородки","history.column_add":"Добавление колонны","history.physical_edit":"Изменение физического объекта","history.physical_delete":"Удаление физического объекта","history.physical_move":"Перемещение физического объекта","history.contour_to_partitions":"Преобразование контура в замкнутые стены","toast.column_duplicate":"Колонна с тем же центром и внешним размером уже существует","confirm.delete_draft":"Удалить весь незавершённый контур комнаты?","confirm.delete_draft_segment":"Удалить этот сегмент черновика? Оставшийся контур может разделиться на два.","physical.partition_properties":"Свойства перегородки","physical.column_properties":"Свойства колонны","physical.draft_properties":"Свойства сегмента черновика","physical.shape":"Форма","physical.square":"Квадрат","physical.circle":"Круг","physical.diameter":"Диаметр","physical.side":"Сторона","physical.rotation":"Угол поворота","physical.length":"Длина","physical.allowed_range":"Допустимо: 1–{max} {unit}","physical.delete_segment":"Удалить сегмент","physical.delete_draft":"Удалить весь контур","physical.partition_size_title":"Толщина перегородки, создаваемой вторым кликом (1–100 см).","physical.column_size_title":"Внешняя сторона квадратной колонны, создаваемой кликом (1–150 см).","card.title":"План дома","count.devices":"{n} устр.","empty.no_spaces":"Пространств пока нет.","empty.add_first":"Добавьте первое пространство и загрузите план этажа.","empty.install":"Установите интеграцию House Plan и добавьте запись в «Устройства и службы».","btn.add_space":"Добавить пространство","btn.cancel":"Отмена","btn.save":"Сохранить","btn.close":"Закрыть","btn.delete":"Удалить","btn.edit":"Редактировать","btn.open_in_ha":"Открыть в HA","btn.reset":"Сброс","btn.attach":"Прикрепить…","btn.upload":"Загрузить…","btn.replace":"Заменить…","title.zoom_in":"Приблизить","title.zoom_out":"Отдалить","title.zoom_fit":"Вписать всё","title.add_device":"Добавить устройство на план","title.show_all":"Показать скрытые и деактивированные устройства служебными маркерами (только в этой вкладке)","title.markup":"Разметка комнат: сетка, линии, контуры","title.configure_space":"Настроить пространство","title.add_space":"Добавить пространство","title.markup_add":"Контур комнаты: соединяйте точки сетки линиями до замыкания комнаты; Shift фиксирует направление с шагом 45°","title.markup_merge":"Объединить: клик по одной комнате, затем по соседней с общей стеной","title.markup_split":"Разделить комнату: клик по комнате, затем две точки на её стенах","title.markup_delroom":"Удалить комнату: кликните внутри комнаты и подтвердите удаление","title.markup_boundary":"Граница: две точки на сплошной общей стене делают участок виртуальным; клик по пунктиру восстанавливает стену","title.need_plan":"Загрузите подложку (план этажа)","markup.add":"Контур комнаты","markup.merge":"Объединить","markup.split":"Разделить","markup.resize":"Размер","title.markup_resize":"Изменение размера комнат: тяните ручку стены; клик по комнате — угловая рамка масштаба","markup.hint_resize":"тяните ручку стены · клик по комнате — угловая рамка · Esc отменяет перетаскивание · Ctrl+Z — отмена шага","markup.opening":"Проём","markup.boundary":"Граница","markup.delete_room":"Удалить комнату","history.undo":"Отменить","history.redo":"Повторить","history.undo_named":"Отменить: {name} (Ctrl+Z)","history.redo_named":"Повторить: {name} (Ctrl+Shift+Z / Ctrl+Y)","history.undo_empty":"Нет операций для отмены","history.redo_empty":"Нет операций для повтора","history.undone":"Отменено: {name}","history.redone":"Повторено: {name}","history.add_room":"Создание комнаты","history.split_room":"Разделение комнаты","history.merge_rooms":"Объединение комнат","history.resize_room":"Изменение размера комнаты","history.open_boundary":"Открытие границы","history.close_boundary":"Восстановление стены","history.wall_thickness":"Изменение толщины стены","history.add_opening":"Добавление дверного или оконного проёма","history.edit_opening":"Изменение проёма","history.move_opening":"Перемещение проёма","history.delete_opening":"Удаление проёма","history.delete_room":"Удаление комнаты","history.decor_add":"Добавление объекта декора","history.decor_edit":"Изменение объекта декора","history.decor_move":"Перемещение объекта декора","history.decor_transform":"Трансформация объекта декора","history.decor_delete":"Удаление объекта декора","history.backdrop_transform":"Трансформация подложки плана","title.markup_opening":"Проёмы: клик по стене — добавить, клик по проёму — редактировать","opening.new":"Новый проём","opening.edit":"Проём","opening.door":"Дверь","opening.window":"Окно","opening.gate":"Ворота","opening.type_label":"Тип","opening.length_label":"Длина, см","opening.contact_label":"Датчик открытия","opening.lock_label":"Замок","opening.none":"— нет —","opening.invert":"Инвертировать открыто/закрыто","opening.flip_h":"Петли с другой стороны","opening.flip_v":"Открывается в другую сторону","opening.open":"Открыто","opening.closed":"Закрыто","opening.locked":"Заперто","opening.unlocked":"Не заперто","opening.state_unknown":"недоступно","opening.no_entities":"Датчики не привязаны — статичный символ на плане.","toast.opening_no_wall":"Кликните рядом со стеной комнаты — проёмы ставятся на стены","markup.delete":"Удалить","markup.hint_points":"точек: {n} · Shift — шаг 45° · Esc/Ctrl+Z — убрать точку · клик по первой или Ctrl/Cmd+клик — замкнуть","markup.hint_start":"кликните точку сетки, чтобы начать контур","tip.lqi":"средний сигнал zigbee:","tip.area":"площадь: {value}","info.device_header":"Устройство на плане","info.model":"Модель","info.state":"Состояние","info.link":"Ссылка","info.manuals":"Инструкции","info.none":"Нет дополнительной информации","marker.new_device":"Новое устройство","marker.name_label":"Имя (отображается на плане)","marker.name_ph":"Название","marker.binding_label":"Привязка к устройству HA","marker.binding_disabled":"деактивировано в Home Assistant","marker.virtual_option":"Виртуальное устройство (без привязки)","marker.search_ph":"Поиск устройства / группы…","marker.nothing_found":"ничего не найдено","marker.room_label":"Комната","marker.room_override":" (переопределить размещение)","marker.room_choose":"— выберите комнату —","marker.room_auto":"— по зоне устройства (авто) —","marker.icon_label":"Иконка","marker.icon_ph":"mdi:… (пусто = авто)","marker.display_label":"Отображение","display.badge":"Значок + состояние","display.icon_ripple":"Значок + состояние и активность","display.static_icon":"Всегда статичный значок","marker.display_hint":"Значок + состояние меняет подложку при работе, открытии, тревоге и недоступности. Значок + состояние и активность дополнительно показывает короткую пульсацию для событий и постоянную — для работы, движения или присутствия. Значение + состояние заменяет значок одним однозначным значением HA. Статичный значок не реагирует на состояния устройства.","marker.display_hint_badge":"Значок и динамическая подложка показывают состояние устройства без обычной пульсации активности. Красная тревога сохраняется.","marker.display_hint_icon":"Значок и динамическая подложка показывают состояние устройства без обычной пульсации активности. Красная тревога сохраняется.","marker.display_hint_icon_ripple":"Значок, динамическая подложка и пульсация: короткая — для событий, постоянная — для работы, движения или присутствия. Красная тревога имеет отдельный приоритет.","marker.display_hint_value":"Значок заменяется одним однозначным значением Home Assistant; подложка продолжает показывать состояние. Красная тревога сохраняется.","marker.display_hint_static_icon":"Всегда одинаковые чёрная подложка и значок. Состояния, активность, недоступность и тревоги не меняют отображение.","marker.static_alarm_warning":"Это устройство может сообщать о тревогах. Статичный режим скрывает визуальную тревогу маркера.","marker.preview.title":"Предпросмотр отображения","marker.preview.actual":"Сейчас","marker.preview.example":"Пример","marker.preview.integration":"Интеграция","marker.preview.source":"Источник отображения","marker.preview.current_state":"Текущее состояние","marker.preview.result":"На плане","marker.preview.details":"Подробнее об источниках","marker.preview.select_source":"Выберите устройство или сущность Home Assistant, чтобы увидеть фактическое отображение.","marker.preview.unknown_provider":"Интеграция неизвестна","marker.preview.virtual_provider":"House Plan · виртуальное устройство","marker.preview.no_source":"Нет активного источника","marker.preview.no_state":"Нет текущего состояния","marker.preview.mixed_states":"Несколько разных состояний","marker.preview.multiple_sources":"Источников: {n}","marker.preview.more_sources":"ещё {n}","marker.preview.scaled":"Предпросмотр уменьшен до {n}%, чтобы поместиться. Сохранённый размер не изменится.","marker.preview.demo_activity":"Показать пример активности","marker.preview.demo_notice":"Временный пример активности; фактическое состояние устройства не меняется.","marker.preview.demo_short":"Показать краткую активность","marker.preview.demo_continuous":"Показать постоянную активность","marker.preview.stop_continuous":"Остановить постоянную активность","marker.preview.demo_short_notice":"Пример краткой активности; фактическое состояние устройства не меняется.","marker.preview.demo_continuous_notice":"Пример постоянной активности; фактическое состояние устройства не меняется.","marker.preview.demo_already_visible":"Текущее состояние уже показывает реальную активность или тревогу.","marker.preview.reduced_motion":"В системе включено уменьшение движения, поэтому обычная активность показана точкой.","marker.preview.reason.neutral":"Нейтральная тёмная подложка","marker.preview.reason.working":"Жёлтая подложка: устройство сейчас работает","marker.preview.reason.working_activity":"Жёлтая подложка и эффект активности: устройство сейчас работает","marker.preview.reason.open":"Оранжевая подложка: физически открыто или разблокировано","marker.preview.reason.cover_icon_state":"Нейтральная подложка; состояние и значок определяет сущность шторы или заслонки","marker.preview.reason.presence":"Эффект активности, пока обнаружено присутствие","marker.preview.reason.event":"Короткий эффект активности после события","marker.preview.reason.transition":"Эффект активности во время движения или смены состояния","marker.preview.reason.media_neutral":"Медиаустройство остаётся на нейтральной тёмной подложке, пока доступно","marker.preview.reason.unavailable":"Приглушённая нейтральная подложка: отключено или недоступно","marker.preview.reason.alarm":"Красная тревожная подложка; тревога отображается всегда","marker.preview.reason.live_states_disabled":"Оформление по живым состояниям отключено в настройках карточки","marker.preview.reason.value_no_state":"Нет подходящего значения состояния — вместо него показан значок","marker.preview.reason.value_ambiguous_sources":"Подходит несколько источников значения — вместо него показан значок","marker.preview.reason.value_non_scalar":"Источник вернул не простое значение — вместо него показан значок","marker.preview.reason.value_virtual":"У виртуального устройства нет значения HA — вместо него показан значок","marker.preview.reason.vacuum_live_plan_only":"Живая позиция и след пылесоса доступны только на полном плане","marker.preview.reason.hidden_design_preview":"Устройство скрыто на плане, но его оформление видно в предпросмотре","marker.preview.reason.composite_power_source":"Состояние берётся из сущности питания устройства; вспомогательные переключатели не учитываются","marker.preview.reason.activity_display_disabled":"Выбранный режим отображения не показывает обычные эффекты активности","marker.preview.reason.ha_disabled":"Привязка отключена в Home Assistant, поэтому устройство будет скрыто на плане","marker.preview.reason.orphaned":"Сохранённая привязка Home Assistant больше не найдена","marker.preview.reason.static_icon":"Статичный режим: состояние устройства не меняет значок","marker.activity_color":"Цвет пульсации активности","marker.ripple_size":"Размер пульсации активности","marker.activity_alarm_note":"Цвет и размер относятся только к обычной активности и не влияют на красную тревогу.","marker.pulse_a11y_alarm":"Тревога","marker.pulse_a11y_event":"Недавнее событие","marker.pulse_a11y_presence":"Обнаружено присутствие","marker.pulse_a11y_transition":"Изменение состояния","marker.pulse_a11y_running":"Работает","marker.size_label":"Размер / поворот значка","marker.angle_label":"Поворот","marker.model_label":"Модель","marker.model_ph":"напр. Aqara T&H","marker.link_label":"Ссылка","marker.desc_label":"Описание","marker.desc_ph":"Заметки, характеристики…","marker.manuals_label":"Инструкции (PDF и т.п.)","marker.sub_device":"устройство","marker.sub_z2m_group":" · Z2M-группа","marker.sub_group":"группа","marker.sub_helper":"хелпер","space.new":"Новое пространство","space.header":"Пространство","space.title_label":"Название","space.title_ph":"Например: Гараж","space.plan_label":"Подложка (план)","space.no_plan":"нет подложки","space.plan_alt":"план","room.new":"Новая комната","room.name_label":"Отображаемое имя","room.name_ph":"Например: Терраса","room.area_label":"Зона Home Assistant (свободные)","room.no_area_option":"— без зоны —","room.default_name":"Комната","device.unnamed":"без имени","device.light_group":"группа света","device.fallback":"устройство","device.virtual":"виртуальное устройство","confirm.delete_room":"Удалить комнату «{name}»?","confirm.merge_rooms":"Слить комнаты «{a}» и «{b}»?","confirm.remove_marker":"Удалить «{name}» с плана? Устройство исчезнет полностью и перестанет участвовать в данных плана. Позже его можно будет добавить заново.","confirm.erase_decor":"Стереть объект «{kind}»? Действие можно отменить из истории редактора.","confirm.delete_space":"Удалить пространство «{title}» со всеми комнатами и разметкой?","toast.pos_save_failed":"Не удалось сохранить позицию: {err}","toast.no_entity":"У устройства нет подходящей сущности","toast.ha_disabled_action":"Деактивированный объект Home Assistant нельзя использовать на плане.","toast.ha_disabled_show_device":"Деактивированное в Home Assistant устройство нельзя показать на плане. Сначала активируйте его в Home Assistant.","toast.ha_disabled_show_entity":"Деактивированную в Home Assistant сущность нельзя показать на плане. Сначала активируйте её в Home Assistant.","toast.ha_disabled_add":"Деактивированный объект Home Assistant нельзя добавить на план. Сначала активируйте его в Home Assistant.","toast.ha_binding_unverified":"Статус объекта не удалось подтвердить по реестру Home Assistant. Отображение и действия временно недоступны.","toast.markup_needs_server":"Разметка доступна после переноса конфига на сервер","toast.conflict":"Конфиг изменён в другом окне — данные обновлены, повторите последнее действие","toast.cfg_save_failed":"Не удалось сохранить конфиг: {err}","toast.room_overlap":"Контур накладывается на комнату «{name}» — комнаты не должны накладываться","toast.contour_min_edges":"Чтобы замкнуть контур, сначала нарисуйте минимум две грани","toast.contour_cannot_close":"Контур нельзя замкнуть: он вырожден или пересекает сам себя","toast.merge_not_adjacent":"Объединять можно только комнаты с общей стеной","toast.rooms_merged":"Комнаты объединены в «{name}»","toast.split_pick_wall":"Начните разрез на стене комнаты","toast.split_bad_cut":"Разрез — от стены до стены внутри комнаты, без пересечения стен и самого себя","merge.header":"Объединение комнат","merge.hint":"У объединённой комнаты одно имя и одна зона. Вторая зона освобождается — её устройства уйдут с плана, пока их не заберёт другая комната.","merge.keep":"Оставить","merge.no_area":"без зоны","room.split_header":"Новая комната после разделения","toast.room_saved":"Комната сохранена ({n}). Устройств добавлено: {added}. Обведите следующую или выйдите из разметки.","toast.room_saved_no_area":"Комната сохранена ({n}, без зоны). Обведите следующую или выйдите из разметки.","toast.marker_needs_server":"Редактирование устройств доступно после переноса конфига на сервер","toast.virtual_name_required":"Укажите имя виртуального устройства","toast.marker_saved":"Устройство сохранено","toast.marker_removed":"Устройство удалено с плана","toast.integration_missing":"Интеграция House Plan не установлена — управление недоступно","toast.plan_formats":"Поддерживаются SVG, PNG, JPG, WebP","toast.plan_required":"Загрузите подложку — план этажа обязателен","toast.space_added_onboard":"Пространство добавлено. Обведите комнаты: кликайте по точкам сетки и замкните контур.","toast.space_added":"Пространство добавлено","toast.space_saved":"Пространство сохранено","toast.space_deleted":"Пространство удалено","toast.delete_failed":"Ошибка удаления: {err}","toast.error":"Ошибка: {err}","toast.file_failed":"Файл «{name}» не загружен: {err}","toast.files_attached":"Прикреплено файлов: {n}","err.unknown":"неизвестная ошибка","err.code":"код {code}","err.too_large":"файл больше {mb} МБ","err.bad_ext":"недопустимый тип (нужен PDF/изображение)","err.unauthorized":"нужны права администратора","editor.title":"Заголовок","editor.default_floor":"Пространство по умолчанию","editor.icon_size":"Размер иконок, % ширины плана","editor.show_temperature":"Показывать температуру","editor.live_states":"Живые состояния (вкл/выкл, открыто…)","editor.show_signal":"Показывать сигнал zigbee (LQI)","editor.language":"Язык интерфейса","editor.lang_auto":"Авто (профиль HA)","editor.lang_en":"English","editor.lang_ru":"Русский","title.icon_rules":"Правила иконок: какая MDI-иконка достаётся устройству по имени","rules.title":"Правила иконок","rules.hint":"Правила проверяются сверху вниз по строке «имя устройства + модель» (regex без учёта регистра); срабатывает первое совпадение. Если ничего не подошло — решает device class сущности, затем — иконка-заглушка.","rules.pattern_ph":"regex, напр. розетк|plug","rules.icon_ph":"mdi:power-socket-de","rules.add":"Добавить правило","rules.reset":"Сбросить к умолчаниям","rules.test_ph":"Проверьте имя устройства…","rules.invalid":"некорректный regex","rules.saved":"Правила иконок сохранены","btn.up":"Вверх","btn.down":"Вниз","tap.info":"Карточка устройства","tap.more_info":"Диалог HA (more-info)","tap.toggle":"Переключить состояние","marker.tap_label":"Действие по нажатию для этого устройства","tap.toggle_note":"Под селектором показано, какое состояние изменится. Защищённые устройства не переключаются с плана.","import.title":"Создать пространства из этажей HA","import.hint":"Home Assistant уже знает эти этажи. Отметьте, какие превратить в пространства плана — далее для каждого попросим картинку плана. Комнаты затем обводятся вручную по плану.","import.start":"Создать: {n}","import.manual":"Начать с нуля","import.progress":"Этаж {i} из {n}","import.done":"Пространства созданы. Обведите комнаты: кликайте по точкам сетки и замкните контур.","btn.skip":"Пропустить","space.scale_label":"Масштаб (размер клетки сетки)","space.scale_unit":"см на клетку","space.display_section":"Отображение","space.show_borders":"Всегда отображать границы комнат","space.show_names":"Отображать названия комнат (перетаскиваются)","space.room_color":"Цвет границ и названий","space.opacity":"Прозрачность","space.fill_label":"Заливка комнат","fill.none":"Нет","fill.lqi":"По силе зигби-сигнала","fill.light":"По освещению","fill.custom":"Свой цвет","space.custom_fill":"Цвет заливки","space.source_file":"У меня есть картинка плана","space.source_draw":"Нет подложки — нарисую комнаты вручную","space.orientation":"Холст","orient.landscape":"Альбомный","orient.portrait":"Портретный","orient.square":"Квадрат","fill.temp":"По температуре","space.temp_min":"Комфорт от","space.temp_max":"до","tip.temp_avg":"средняя температура:","space_card.button":"Перейти к пространству","space_card.not_found":"Пространство «{id}» не найдено","space_card.loading":"Загрузка…","continuity.restore_plan":"Восстанавливаем план…","continuity.restore_connection":"Восстанавливаем подключение к устройствам…","continuity.retry":"Повторить","editor.space":"Пространство","editor.show_button":"Показывать кнопку","editor.button_label":"Текст кнопки","editor.button_target":"Путь дашборда (куда вести)","marker.sub_entity":"сущность","title.general_settings":"Общие настройки","gs.title":"Общие настройки","gs.hint":"Цвета заливок действуют на все пространства; у каждого цвета своя прозрачность. Какой режим заливки использует пространство — задаётся в его диалоге.","gs.light_group":"Заливка: освещение","gs.light_on":"Свет включён","gs.light_off":"Весь свет выключен","gs.temp_group":"Заливка: температура","gs.temp_cold":"Холодно","gs.temp_ok":"Комфорт","gs.temp_hot":"Жарко","gs.lqi_group":"Заливка: зигби-сигнал","gs.lqi_low":"Слабый сигнал","gs.lqi_high":"Сильный сигнал","gs.reset":"Сбросить к умолчаниям","gs.saved":"Общие настройки сохранены","space.show_lqi":"Показывать зигби-сигнал (LQI) у устройств","space.hide_decor":"Скрыть декоративный слой","space.hide_decor_tip":"Линии, фигуры, надписи и мебель остаются на месте — их видно в редакторе подложки, но не на плане.","space.hide_openings":"Скрыть проёмы","space.hide_openings_tip":"Двери, окна и ворота не рисуются, но продолжают работать: свет проходит, солнце светит в окна, датчики открытия срабатывают. В редакторе плана проёмы видно всегда.","gs.light_none":"Нет источников света","mode.plan":"Редактор плана","mode.devices":"Редактор устройств","display.value":"Значение + состояние","marker.subarea":"без зоны, вручную","device.new":"Новое устройство — откройте его редактор, чтобы снять отметку","opening.unlock_action":"Открыть замок","opening.lock_action":"Закрыть замок","opening.lock_pending":"Выполняется…","title.close_editor":"Закрыть редактор (вернуться к просмотру)","devbar.add":"Добавить","devbar.show_all":"Скрытые и деактивированные","devbar.rules":"Правила иконок","space.roomcard_section":"В карточке комнаты:","space.label_temp":"Температура","space.label_hum":"Влажность","space.label_lqi":"Средний Zigbee-сигнал","space.label_light":"Свет вкл/выкл","roomcard.light_on":"Вкл","roomcard.light_off":"Выкл","roomcard.light_partial":"{on} из {total}","toast.split_pick_inside":"Промежуточные точки разреза — внутри комнаты","mode.decor":"Редактор подложки","decor.select":"Выбрать","decor.line":"Линия","decor.rect":"Прямоугольник","decor.ellipse":"Овал","decor.text":"Надпись","decor.erase":"Стереть","decor.erase_confirm_title":"Стереть объект?","decor.color":"Цвет","decor.width":"Толщина линии","decor.line_style":"Стиль линии","decor.line_style_solid":"Сплошная","decor.line_style_dashed":"Пунктирная","decor.fill":"Заливка","decor.fill_color":"Цвет заливки","decor.length":"Длина","decor.size":"Размер","decor.angle":"Угол поворота, °","decor.text_size":"Размер текста","decor.backdrop_properties":"Свойства картинки-подложки","decor.text_title":"Надпись","decor.object_title":"Редактирование: {kind}","decor.text_label":"Текст","decor.live_group":"Вставить переменную HA","decor.live_entity":"Сущность","decor.live_entity_ph":"выберите сущность","decor.live_attr":"Значение","decor.live_attr_ph":"выберите состояние или атрибут","decor.live_state":"Состояние","decor.backdrop":"Картинка-подложка","decor.backdrop_hint":"Тяните картинку — перемещение; угол — размер; верхнюю ручку — поворот. Shift меняет пропорции или снимает шаг угла.","decor.backdrop_reset":"Вернуть картинку","decor.backdrop_reset_done":"Картинка вернулась на своё место и в свой размер","marker.icon_auto":"Авто: {icon} (по правилам иконок; выберите свою, чтобы заменить)","marker.icon_pin_auto":"Закрепить","mode.plan_tip":"Редактор плана — геометрия дома: рисование и объединение/разделение комнат, привязка к зонам HA, двери, окна и ворота, карточки комнат, масштаб","mode.devices_tip":"Редактор устройств — всё про значки: перетаскивание, клик — настройка привязки/иконки/отображения, виртуальные устройства, правила иконок","mode.decor_tip":"Редактор подложки — чисто визуальный декор под планом: линии, прямоугольники, овалы и надписи, не реагирующие на клики","space.glow_enabled":"Свечение источников света","gs.glow_group":"Свечение источников света","gs.glow_base":"Темнота дома","gs.glow_light":"Цвет света по умолчанию / интенсивность","gs.wall_group":"Стены","gs.wall_fill":"Цвет заливки стен","gs.glow_radius":"Радиус свечения","gs.unit_m":"м","gs.unit_ft":"фут","marker.controls_label":"Управляет другими источниками света","marker.controls_hint":"При действии «Переключить» клик разом переключает все добавленные здесь источники. Собственная сущность маркера управляется напрямую и сюда не добавляется; чтобы считать её светом, включите флаг ниже.","marker.controls_filter":"Поиск ламп и выключателей…","info.controls":"Управляет","marker.glow_radius_label":"Радиус свечения","marker.glow_radius.help":"Задаёт радиус свечения в метрах или футах; пустое значение использует радиус из общих настроек.","marker.glow_radius.help.aria":"Подсказка: радиус свечения","markup.wallthick":"Толщина","markup.select":"Выбрать","title.markup_wallthick":"Толщина — клик по стене задаёт толщину. Пустое поле или ноль убирает толщину.","markup.hint_wallthick":"клик по стене · Esc закрывает без применения","wallthick.field":"Толщина","wallthick.unit_cm":"см","wallthick.unit_in":"дюйм","wallthick.apply_room":"Применить ко всем стенам комнаты","markup.draw_wall_title":"Толщина каждого нового сегмента стены комнаты (1–100 см). Общие стены сохраняют значение соседа.","toast.wallthick_pick":"Кликните по стене (не по открытой границе)","toast.wallthick_open":"У открытой границы нет толщины","toast.wallthick_set":"Толщина стены задана","toast.wallthick_cleared":"Толщина стены убрана","toast.physical_range":"Введите значение от 1 до {max} {unit}","toast.physical_angle":"Введите угол поворота от 0° до 90°, не включая 90°","toast.physical_limit":"В пространстве достигнут лимит объектов этого типа","markup.boundary_hint":"Выберите общую границу комнат или пунктирный участок","markup.boundary_hint_open":"Кликните начало открытого участка","markup.boundary_hint_second":"Укажите конец на выбранной границе · Esc — отмена","markup.boundary_hint_retry":"Укажите конец на выбранной границе","markup.boundary_hint_restore":"Кликните, чтобы восстановить стену","markup.boundary_hint_outer":"Открыть можно только общую границу двух комнат","markup.boundary_hint_blocked":"над границей находится перегородка, колонна или незавершённый контур","markup.boundary_hint_ambiguous":"Выберите участок чуть дальше от стыка","markup.boundary_cancel":"Отменить незавершённое действие «Граница»","toast.openwall_pick":"Инструмент работает с границами комнат","toast.openwall_shared_only":"Открыть можно только общую стену двух комнат","toast.openwall_short":"Слишком короткий отрезок — кликните дальше","toast.boundary_opened":"Участок границы открыт","toast.boundary_restored":"Стена на участке восстановлена","toast.boundary_same_edge":"Выберите конечную точку на выбранной стене","toast.boundary_ambiguous":"Выберите участок чуть дальше от стыка","toast.boundary_blocked":"Граница перекрыта независимой стеной. Сначала переместите или удалите её инструментом «Выбрать»","toast.delete_room_pick":"Кликните внутри комнаты, которую нужно удалить","toast.openwall_openings_removed":"Проёмы на виртуальном отрезке удалены","toast.openwall_opened":"Граница «{a}» ↔ «{b}» теперь открыта","toast.openwall_closed":"Граница «{a}» ↔ «{b}» снова закрыта","toast.opening_on_virtual":"Проёмы на виртуальной стене запрещены","marker.from_ha_option":"Выбрать из списка HA","marker.show_entities":"Отображать сущности","marker.show_entities_tip":"Добавляет в список не только устройства, но и все их сущности","marker.pick_ph":"Выберите устройство…","room.open_area":"Открыть зону в HA","view.volumetric":"Объёмный вид","view.flat":"Плоский вид","kiosk.title":"Размеры на этом экране","kiosk.hint":"Хранится только на этом устройстве — у каждого настенного планшета или ТВ свои удобные размеры.","kiosk.icon_scale":"Размер значков устройств","kiosk.font_scale":"Размер текста карточек комнат","editor.kiosk":"Режим настенного устройства (киоск)","editor.cycle":"Автосмена пространств каждые N секунд (киоск, 0 = выкл)","room.settings_title":"Настройки комнаты","room.settings_section":"Настройки комнаты (переопределяют пространство)","room.fill_label":"Заливка в ЭТОЙ комнате","fill.inherit":"Как у пространства","room.custom_fill_space":"Цвет пространства","room.custom_fill_own":"Цвет комнаты","room.temp_src_label":"Источник температуры","room.hum_src_label":"Источник влажности","room.src_average":"Средняя по датчикам комнаты (по умолчанию)","room.src_pick":"Конкретное устройство или сущность HA","room.src_ph":"Выберите источник…","toast.room_updated":"Комната обновлена","space.card_font":"Размер шрифта карточек комнат (всё пространство)","room.sizes_section":"Размеры шрифтов","room.name_scale":"Размер названия","room.label_scale":"Размер подписей","preview.room_name":"Гостиная","toast.cfg_reload_failed":"Не удалось перечитать план с сервера: {err}","room.settings_short":"Настройки комнаты","room.unnamed":"Комната без имени","marker.use_climate_temp":"Учитывать температуру устройства в комнате","marker.use_climate_temp_tip":"Добавляет current_temperature кондиционера или термостата в среднюю температуру комнаты. Внешний бейдж со значением настраивается отдельно ниже.","marker.value_badge_title":"Бейдж со значением","marker.value_badge.help":"Показывает одно выбранное значение рядом со значком. Не влияет на комнатные показатели, свет, Glow и действие по нажатию.","marker.value_badge.help.aria":"Подсказка: бейдж со значением устройства","marker.value_badge_enabled":"Отображать бейдж со значением","marker.value_badge_source":"Значение","marker.value_badge_source.help":"Выберите конкретное состояние, поддерживаемый атрибут или производное значение этого устройства.","marker.value_badge_source.help.aria":"Подсказка: источник значения бейджа","marker.value_badge_position":"Расположение","marker.value_badge_position.help":"Выбранная сторона сохраняется постоянно и не переворачивается автоматически у края плана.","marker.value_badge_position.help.aria":"Подсказка: расположение бейджа","marker.value_badge_right":"Справа","marker.value_badge_bottom":"Снизу","marker.value_badge_left":"Слева","marker.value_badge_top":"Сверху","marker.value_badge_empty":"У этого устройства нет доступных значений","marker.value_badge_static":"Статичный значок не показывает живые значения. Настройка сохранится.","marker.value_badge_missing":"Источник недоступен","marker.value_badge_missing_hint":"Сохранённый источник сейчас недоступен. Можно сохранить его, отключить бейдж или выбрать другой.","marker.value_badge_duplicate":"То же значение уже показано внутри значка.","marker.value_badge_state":"Состояние · {name}","marker.value_badge_marker_state":"Состояние источника света · {name}","marker.value_badge_lqi":"Качество Zigbee-сигнала (среднее)","marker.value_badge_attr_current_temperature":"Текущая температура · {name}","marker.value_badge_attr_temperature":"Целевая температура · {name}","marker.value_badge_attr_current_humidity":"Текущая влажность · {name}","marker.value_badge_attr_humidity":"Влажность · {name}","marker.value_badge_attr_current_position":"Положение · {name}","marker.value_badge_attr_percentage":"Скорость · {name}","marker.value_badge_attr_brightness":"Яркость · {name}","marker.value_badge_attr_volume_level":"Громкость · {name}","marker.value_badge_attr_battery_level":"Заряд батареи · {name}","marker.value_badge_attr_fan_speed":"Скорость вентилятора · {name}","marker.light_role_label":"Является источником света","marker.light_role.help":"Определяет, является ли сам маркер пространственным источником света. Привязанные выше лампы остаются независимыми источниками для комнаты.","marker.light_role.help.aria":"Подсказка: является ли устройство источником света","marker.light_role_auto_yes":"Автоматически (источник света)","marker.light_role_auto_no":"Автоматически (не источник света)","marker.light_role_always":"Всегда источник света","marker.light_role_never":"Никогда не источник света","marker.light_entity_label":"Ведущая сущность света","marker.light_entity.help":"Для составного устройства выберите сущность, состояние, цвет и яркость которой представляют этот источник на плане. Автоматический вариант сохраняет совместимость со старыми планами.","marker.light_entity.help.aria":"Подсказка: ведущая сущность света","marker.light_entity_auto":"Автоматически ({entity})","marker.light_entity_none":"нет управляемой сущности","marker.light_entity_missing":"Сохранённая сущность {entity} недоступна. Временно используется {fallback}; выбор восстановится, если сущность вернётся.","marker.glow_color_label":"Цвет и яркость свечения","marker.glow_mode.help":"Используйте данные источника, задайте только цвет или зафиксируйте цвет и яркость. Минимум — 1%; чтобы отключить источник, выберите «Никогда».","marker.glow_mode.help.aria":"Подсказка: цвет и яркость свечения","marker.glow_mode_auto":"Из источника","marker.glow_mode_color":"Задать цвет","marker.glow_mode_fixed":"Задать цвет и яркость","marker.glow_color":"Цвет свечения","marker.glow_brightness":"Яркость","marker.glow_disabled_never":"Настройки свечения недоступны: маркер явно исключён из источников света.","marker.glow_disabled_auto":"В автоматическом режиме у этого маркера не найден пространственный источник света.","marker.glow_disabled_no_entity":"Для пространственного источника нет активной управляемой сущности Home Assistant.","marker.glow_passive_hint":"У источника нет собственных данных Home Assistant. Задайте цвет и яркость вручную; радиус остаётся доступным.","marker.control_broken":"Сохранённый источник отсутствует или больше не отмечен как источник света","marker.control_missing_label":"Отсутствующий источник на плане","marker.control_passive":"пассивный источник","toast.marker_control_cycle":"Такая связь создаст циклическую цепочку управления светом.","toast.marker_binding_required":"Сначала выберите устройство Home Assistant, затем добавьте управляемый источник света.","confirm.unlock":"Открыть замок «{name}»?","toast.files_migrate_failed":"Не удалось перенести вложения к новой привязке, ссылки остались на старые файлы: {err}","space.pick_saved":"Уже загруженные","space.pick_saved_hint":"Планы, сохранённые на сервере, включая отцеплённые ранее","space.no_saved":"На сервере пока нет сохранённых планов.","space.loading":"Загрузка…","space.used_by":"используется: {list}","space.in_use":"План используется пространством — сначала отцепите его","btn.use":"Выбрать","confirm.delete_plan":"Удалить файл плана «{name}» с сервера? Действие необратимо.","toast.plans_list_failed":"Не удалось получить список планов: {err}","toast.plan_delete_failed":"Не удалось удалить план: {err}","marker.hide":"Скрыть","marker.hide_tip":"Устройство исчезнет с плана после сохранения, но продолжит участвовать в расчёте сигнала комнаты. Вернуть его можно через кнопку «Скрытые и деактивированные» в редакторе устройств.","marker.show":"Показать","marker.show_tip":"Устройство снова появится на плане после сохранения.","marker.hidden_ghost":"Устройство скрыто пользователем","marker.ha_disabled_device":"Устройство деактивировано в Home Assistant и скрыто с плана.","marker.ha_disabled_entity":"Сущность деактивирована в Home Assistant и скрыта с плана.","marker.ha_disabled_all_entities":"У устройства нет активных сущностей Home Assistant, поэтому оно скрыто с плана.","marker.ha_registry_limited":"Полный реестр Home Assistant недоступен этому пользователю. Неподтверждённый объект временно нельзя показывать или использовать.","marker.delete_tip":"Полностью удалить устройство с плана и из всех расчётов. Позже его можно добавить заново.","tap.run":"Запустить автоматизацию/скрипт/сцену","tap.cover":"Открыть/закрыть (шторы/жалюзи)","marker.run_target_label":"Что запускать","marker.run_search_ph":"Поиск: автоматизация, скрипт или сцена…","marker.run_target_gone":"Цель {id} не найдена — выберите заново","marker.tap_confirm":"Спрашивать подтверждение","marker.tap_confirm_tip":"Перед выполнением показать диалог подтверждения — защита от случайных нажатий.","marker.toggle_hint_single":"Цель: {name} ({id}).","marker.virtual_light_target":"Виртуальный свет: {name}.","marker.virtual_light_current":"Ручное состояние: {state} → {effect}.","marker.virtual_light_state_on":"включён","marker.virtual_light_state_off":"выключен","marker.toggle_hint_group":"Переключатся источники ({count}): {names}.","marker.toggle_hint_current":"Сейчас: {state} → {effect}.","marker.toggle_hint_group_current":"Сейчас включено {on} из {count} → {effect}.","marker.toggle_hint_skipped":"Не будут затронуты ({count}): {targets}.","marker.toggle_effect_turn_on":"включится","marker.toggle_effect_turn_off":"выключится","marker.toggle_effect_open":"откроется","marker.toggle_effect_close":"закроется","marker.toggle_effect_stop":"остановится","marker.toggle_effect_toggle":"состояние переключится","marker.toggle_skip_missing":"не найдено","marker.toggle_skip_ha_disabled":"деактивировано в HA","marker.toggle_skip_unavailable":"недоступно","marker.toggle_skip_unsupported":"переключение не поддерживается","marker.toggle_skip_secure":"заблокировано из соображений безопасности","marker.toggle_none_no_binding":"У устройства нет привязки, состояние переключать нечем. По нажатию ничего не произойдёт.","marker.toggle_none_no_actionable_entity":"У устройства нет состояния, которое можно переключить. По нажатию ничего не произойдёт.","marker.toggle_none_configured_targets_missing":"Настроенные цели сейчас недоступны. Собственная сущность устройства не будет подставлена вместо них.","marker.toggle_none_ha_disabled":"Цель деактивирована в Home Assistant и не может использоваться на плане.","marker.toggle_none_unavailable":"Цель сейчас недоступна. По нажатию ничего не произойдёт.","marker.toggle_none_unsupported":"Для этой сущности Home Assistant не предоставляет безопасного переключения. По нажатию ничего не произойдёт.","marker.toggle_none_secure":"Переключение замков, сигнализации и защитных ворот с плана заблокировано из соображений безопасности.","run.automation":"автоматизация","run.script":"скрипт","run.scene":"сцена","confirm.tap_run":"Запустить «{name}»?","confirm.tap_toggle":"Переключить «{name}»?","confirm.tap_cover":"Открыть/закрыть «{name}»?","toast.run_started":"Запущено: {name}","toast.run_target_missing":"Цель запуска не найдена — проверьте настройки устройства","toast.run_target_required":"Выберите автоматизацию, скрипт или сцену","toast.tap_target_changed":"Цель действия изменилась. Повторите попытку.","toast.virtual_light_toggle_failed":"Не удалось переключить виртуальный свет: {err}","toast.value_badge_source_required":"Выберите значение для бейджа","btn.run":"Выполнить","vac.section":"Робот-пылесос: живая позиция","vac.autocal":"Настроить автоматически","vac.live":"Живая позиция на плане","vac.trail":"Показывать путь робота","vac.cal_maps":"Откалиброваны карты: {maps}","vac.autocal_no_rooms":"Интеграция не отдаёт список комнат — откройте «Подогнать вручную»","vac.autocal_no_match":"Не совпали имена комнат (нужно ≥3 общих) — откройте «Подогнать вручную»","vac.autocal_done":"Готово: привязка по {rooms} комнатам. Запустите уборку и проверьте","vac.cal_need_pos":"Робот сейчас не отдаёт координаты — запустите уборку и поставьте на паузу","vac.cal_done":"Калибровка сохранена. Запустите уборку и проверьте","vac.cal_cancelled":"Калибровка отменена","vac.fit":"Подогнать вручную","vac.fit_hint":"Перетащите карту робота на место, растяните за уголки","vac.fit_rotate":"Повернуть 90°","vac.fit_mirror":"Отразить","vac.trail_never":"Не показывать никогда","vac.trail_cleaning":"Во время уборки","vac.trail_always":"Показывать всегда","gs.bg_group":"Фон сцены","gs.bg_color":"Цвет фона вокруг плана","gs.bg_default":"Как в теме","gs.bg_theme":"по умолчанию — из темы","gs.bg_mode":"Фон плана","gs.bg_static":"Статичный цвет","gs.bg_daynight":"Следует за Солнцем","gs.bg_daynight_hint":"Фон проходит рассвет, день, закат и ночь по данным sun.sun, а при их недоступности — по локальным часам. Компас для фона не нужен.","gs.sun_group":"Солнце","gs.sun_missing":"Сущность sun.sun не найдена — фон следует локальным часам, а оконные лучи недоступны.","gs.north":"Север на плане","gs.north_ph":"не задан","gs.north_hint":"Север нужен только для оконных лучей (шаг 1°, с Shift 15°); солнечный фон работает без него.","gs.north_clear":"Сбросить","gs.north_letter":"С","gs.sun_rays":"Солнце в окнах","gs.about_group":"О карточке","gs.about_version":"Houseplan Card v{v}","gs.about_github":"GitHub · документация и issues","gs.about_telegram":"Чат в Telegram","space.bg_color":"Цвет фона вокруг плана","space.bg_inherit":"Наследовать общий","space.bg_inherited":"наследуется из общих настроек","space.bg_mode":"Фон плана","space.north":"Север на плане (переопределение)","space.north_inherited":"наследуется: {v}","space.sun_rays":"Солнце в окнах","space.sun_inherit":"Наследовать общий","space.sun_on":"Вкл","space.sun_off":"Выкл","canvas.far_objects":"Объектов далеко от плана: {n}","canvas.show_far":"Показать","canvas.home_tip":"План там — нажмите, чтобы вписать","gs.grid_group":"Обслуживание планов","gs.grid_hint":"Обновляет модели данных, выравнивает элементы по сетке и объединяет лишние фрагменты стен. Перед записью будет показан точный отчёт.","gs.align_all":"Оптимизировать планы","gs.align_title":"Оптимизировать планы","gs.align_none":"Все планы уже используют актуальную и оптимальную модель данных.","gs.align_count":"Сдвинется элементов: {n} из {total}, максимум на {cm} см.","gs.align_where":"Наибольший сдвиг — в пространстве «{s}».","gs.align_turned":"Проёмов с исправлением угла: {n}.","gs.align_removed_drafts":"Схлопнувшиеся на сетке некорректные контуры удалены: {n}.","gs.optimize_changes":"Миграций модели: {m}; канонизировано планов: {c}; объединено отрезков реальных стен: {w}; виртуальных: {s}.","gs.optimize_glow_migration":"Старый Glow: пространств — {spaces} → без заливки данных + независимый Glow; комнат — {rooms} → наследуемая заливка + независимый Glow.","gs.align_warn":"Элементы, намеренно поставленные между узлами, будут сдвинуты. После операции доступна одна отмена — только до следующего изменения плана.","gs.align_run":"Оптимизировать","gs.align_done":"Планы оптимизированы: сдвинуто элементов — {n}, обслужено записей — {m}","gs.optimize_undo":"Отменить последнюю оптимизацию","gs.optimize_undone":"Последняя оптимизация отменена","decor.furniture":"Мебель","furn.title":"Библиотека мебели","furn.symbol":"Символ","furn.group_furniture":"Мебель","furn.group_appliance":"Техника","furn.group_sanitary":"Сантехника","furn.group_other":"Прочее","furn.width":"Ширина","furn.depth":"Глубина","furn.pick_hint":"Выберите символ и кликните по плану.","furn.place_hint":"Кликните по плану — предмет встанет к ближайшей стене. Shift — свободно.","furn.sym_sofa":"Диван","furn.sym_armchair":"Кресло","furn.sym_coffee_table":"Журнальный столик","furn.sym_table_dining":"Обеденный стол","furn.sym_table_round":"Круглый стол","furn.sym_chair":"Стул","furn.sym_desk":"Письменный стол","furn.sym_bed_double":"Двуспальная кровать","furn.sym_bed_single":"Односпальная кровать","furn.sym_nightstand":"Тумбочка","furn.sym_wardrobe":"Шкаф","furn.sym_bookshelf":"Стеллаж","furn.sym_fridge":"Холодильник","furn.sym_stove":"Плита","furn.sym_dishwasher":"Посудомоечная машина","furn.sym_washer":"Стиральная машина","furn.sym_dryer":"Сушильная машина","furn.sym_tv":"Телевизор","furn.sym_ac":"Кондиционер","furn.sym_water_heater":"Бойлер","furn.sym_toilet":"Унитаз","furn.sym_bathtub":"Ванна","furn.sym_shower":"Душ","furn.sym_sink":"Раковина","furn.sym_kitchen_sink":"Кухонная мойка","furn.sym_bidet":"Биде","furn.sym_stairs":"Лестница","furn.sym_fireplace":"Камин","furn.sym_plant":"Растение","furn.sym_rug":"Ковёр","common.yes":"Да","common.no":"Нет","vac.diag_source":"Источник","vac.diag_platform":"Интеграция","vac.diag_status":"Статус","vac.diag_position":"Позиция","vac.diag_rooms":"Комнаты","vac.diag_rooms_value":"{total} · совпало имён: {matched} · {readiness}","vac.autocal_ready":"автокалибровка доступна","vac.autocal_not_ready":"нужно 3 совпавших имени","vac.diag_path":"Путь интеграции","vac.diag_map":"ID карты","vac.source_none":"не выбран","vac.source_status_ok":"Готов","vac.source_status_missing":"Не найден","vac.source_status_disabled":"Деактивирован в Home Assistant","vac.source_status_unavailable":"Недоступен","vac.source_status_unverified":"Нельзя проверить с текущими правами","vac.source_status_unsupported":"Нет данных позиции","vac.source_status_none":"Нет источника","vac.source_banner_missing":"Сохранённый источник больше не существует. Он не заменён автоматически: выберите другой или восстановите его в Home Assistant.","vac.source_banner_disabled":"Сохранённый источник деактивирован в Home Assistant. Активируйте его там или выберите другой.","vac.source_banner_unverified":"Текущих прав Home Assistant недостаточно для проверки источника. Привязка сохранена и не будет заменена автоматически.","vac.choose_source":"Выбрать источник","vac.source_auto":"Автоматически","vac.source_auto_hint":"Использовать совместимую сущность этого устройства","vac.all_cameras":"Все камеры","vac.all_cameras_warn":"Камера может не отдавать данные робота. Выбирайте её только если это карта вашего пылесоса.","vac.all_cameras_empty":"Других сущностей камеры не найдено.","vac.platform_unknown":"интеграция неизвестна","vac.cap_position":"позиция","vac.cap_rooms_short":"комнаты","vac.cap_path":"путь","vac.cap_map":"ID карты","vac.cap_none":"данные робота не обнаружены","vac.xcme_hint":"Включите атрибуты Xiaomi Cloud Map Extractor:","vac.documentation":"Документация","vac.residual_title":"Проверьте автокалибровку","vac.residual_message":"Совпавшие комнаты расходятся максимум на {error}. Примените приблизительную калибровку, подгоните её вручную либо отмените без изменения сохранённых настроек.","vac.apply_proposal":"Применить","gs.backup_group":"Резервная копия и перенос","gs.backup_hint":"Скачайте переносимую JSON-копию или проверьте и импортируйте копию, созданную House Plan.","backup.export_open":"Экспорт","backup.import_open":"Импорт","backup.export_title":"Экспорт House Plan","backup.import_title":"Импорт House Plan","backup.export_hint":"Выберите, должна ли копия содержать всю конфигурацию House Plan или только текущее пространство.","backup.full":"Полная копия","backup.current_space":"Текущее пространство","backup.current_space_title":"Текущее пространство: {title}","backup.no_current_space":"Нет текущего пространства","backup.privacy_warning":"Архив сохраняет названия, идентификаторы Home Assistant и точные координаты. Внутренние планы и вложения указываются ссылками, но не вкладываются; текущие состояния и маршруты пылесосов не включаются.","backup.download":"Скачать JSON","backup.export_done":"Резервная копия скачана","backup.reading":"Проверяем резервную копию…","backup.revalidated":"После предпросмотра план изменился. Сводка обновлена — проверьте её и подтвердите ещё раз.","backup.error.unauthorized":"У вас нет прав на экспорт или импорт этого плана.","backup.error.not_ready":"House Plan ещё не готов. Повторите попытку через несколько секунд.","backup.error.too_large":"Размер резервной копии превышает 8 МиБ.","backup.error.invalid_json":"Выбранный файл не является корректным JSON.","backup.error.invalid_format":"Выбранный файл не является резервной копией House Plan.","backup.error.unsupported_export_version":"Эта версия формата резервной копии не поддерживается установленной версией.","backup.error.future_model":"Резервная копия создана в более новой версии модели данных House Plan.","backup.error.invalid_config":"Резервная копия содержит некорректную конфигурацию House Plan.","backup.error.invalid_layout":"Резервная копия содержит некорректные позиции объектов.","backup.error.invalid_content":"Резервная копия содержит некорректные или несогласованные ссылки на файлы.","backup.error.space_not_found":"Выбранное пространство больше не существует.","backup.error.capacity_exceeded":"Добавление этой копии превысит ограничения размера плана.","backup.error.preview_expired":"Время предпросмотра истекло. Выберите файл резервной копии заново.","backup.error.preview_owner_mismatch":"Этот предпросмотр принадлежит другому пользователю Home Assistant.","backup.error.conflict":"После предпросмотра план изменился. Проверьте обновлённую сводку.","backup.error.content_confirmation_required":"Подтвердите отсоединение недоступных локальных файлов.","backup.error.commit_failed":"Не удалось безопасно применить копию. Предыдущий план восстановлен или ожидает восстановления.","backup.error.missing_plan":"После предпросмотра исчез связанный файл плана. Проверьте копию ещё раз.","backup.error.missing_content":"После предпросмотра исчез связанный локальный файл. Проверьте копию ещё раз.","backup.error.marker_control_missing":"В резервной копии отсутствует связанный источник света на плане.","backup.error.marker_control_not_light":"Связанная цель больше не отмечена как источник света.","backup.error.marker_control_self":"Источник света не может управлять самим собой.","backup.error.marker_control_cycle":"Резервная копия содержит циклическую цепочку управления светом.","backup.error.duplicate_marker_control":"Резервная копия содержит повторяющуюся связь с источником света.","backup.error.no_backup":"Нет снимка импорта или оптимизации, который можно восстановить.","backup.same_source":"Создано на этом экземпляре Home Assistant","backup.foreign_source":"Создано на другом экземпляре Home Assistant","backup.created":"Создано: {value}","backup.versions":"Карточка {card}; интеграция {integration}; модель данных {model}","backup.count_spaces":"Пространств: {n}","backup.count_rooms":"Комнат: {n}","backup.count_walls":"Стен: {n}","backup.count_openings":"Проёмов: {n}","backup.count_decor":"Объектов декора: {n}","backup.count_markers":"Устройств: {n}","backup.count_layout":"Позиций: {n}","backup.bindings":"Привязки — устройства: {device}, сущности: {entity}, виртуальные: {virtual}; позиций без пространства: {legacy}","backup.binding_status":"Статус на целевом экземпляре — активных: {active}, деактивированных: {disabled}, отсутствующих: {missing}","backup.missing_areas":"На целевом экземпляре отсутствуют зоны: {areas}","backup.dropped_marker_links":"Связи с источниками света вне переносимого пространства пропущены: {n}.","backup.replace_warning":"Текущая конфигурация и расположение будут заменены. Загруженные файлы не удаляются. Одну отмену можно выполнить до следующего изменения плана.","backup.foreign_bookkeeping":"Служебные списки известных и новых устройств другого экземпляра импортированы не будут.","backup.final_name":"Название нового пространства","backup.target_settings":"Глобальные настройки целевого экземпляра не изменятся.","backup.duplicates":"Уже существующих привязок: {n}","backup.skip":"Пропустить повторяющиеся устройства","backup.virtual_copy":"Добавить безопасные статичные виртуальные копии","backup.content":"Связанное содержимое","backup.content_available":"доступно локально","backup.content_external":"внешняя ссылка","backup.content_detach_required":"будет отвязано","backup.confirm_detach":"Я понимаю, что недоступные внутренние планы и вложения будут отвязаны от импортируемой конфигурации.","backup.replace":"Заменить","backup.add":"Добавить пространство","backup.space_done":"Пространство импортировано: комнат — {rooms}, устройств — {markers}","backup.full_done":"Копия восстановлена: пространств — {spaces}, комнат — {rooms}, устройств — {markers}","backup.undo_import":"Отменить последний полный импорт","backup.import_undone":"Полный импорт отменён"}};function Uo(t,e){if(e&&e in Wo)return e;return(t?.locale?.language||t?.language||"en").toLowerCase().startsWith("ru")?"ru":"en"}function Go(t,e,i){return Ki(Wo[t][e]??jo[e]??e,i)}function Vo(t,e){const i=Wo[t][e]??Wo.en[e];return"string"==typeof i&&i.trim().length>0}class Ko extends ht{constructor(){super(...arguments),this.deviceName="",this._metadataConnection=null,this._demoUntil=0,this._demoTimer=0,this._demoGeneration=1,this._demoKind=null,this._reducedMotion=!1,this._onMotionChange=t=>{this._reducedMotion=t.matches,this.requestUpdate()},this._startShortDemo=()=>{const t=this.presentation;t&&"icon_ripple"===t.display&&"none"===t.pulse.kind&&(this._demoGeneration++,this._demoKind="short",this._demoUntil=Date.now()+3300,window.clearTimeout(this._demoTimer),this._demoTimer=window.setTimeout(()=>{this._demoUntil=0,this._demoTimer=0,this._demoKind=null,this.requestUpdate()},3330),this.requestUpdate())},this._toggleContinuousDemo=()=>{const t=this.presentation;t&&"icon_ripple"===t.display&&"none"===t.pulse.kind&&("continuous"===this._demoKind?this._clearDemo():(this._clearDemo(),this._demoGeneration++,this._demoKind="continuous",this._demoUntil=Number.POSITIVE_INFINITY),this.requestUpdate())}}connectedCallback(){super.connectedCallback(),this._motionMedia=window.matchMedia?.("(prefers-reduced-motion: reduce)"),this._reducedMotion=!!this._motionMedia?.matches,this._motionMedia?.addEventListener?.("change",this._onMotionChange),this._ensureMetadata()}disconnectedCallback(){this._metadataRelease?.(),this._metadataRelease=void 0,this._metadataConnection=null,this._clearDemo(),this._motionMedia?.removeEventListener?.("change",this._onMotionChange),this._motionMedia=void 0,super.disconnectedCallback()}willUpdate(t){t.has("hass")&&this._ensureMetadata();const e=this.presentation,i=t.get("presentation");t.has("presentation")&&i?.binding!==e?.binding&&this._clearDemo(),e&&this._demoKind&&("icon_ripple"!==e.display||"none"!==e.pulse.kind)&&this._clearDemo()}_ensureMetadata(){const t=this.hass?.connection||null;t&&t!==this._metadataConnection&&(this._metadataRelease?.(),this._metadataConnection=t,this._metadataRelease=Oo(this.hass,()=>this.requestUpdate()))}_clearDemo(){window.clearTimeout(this._demoTimer),this._demoTimer=0,this._demoUntil=0,this._demoKind=null}get _lang(){return Uo(this.hass)}_t(t,e){return Go(this._lang,t,e)}_providerText(t){return"houseplan"===t.domain?this._t("marker.preview.virtual_provider"):t.label.toLowerCase()===t.domain.toLowerCase()?t.domain:`${t.label} (${t.domain})`}_providers(){return this.presentation&&this.registry?function(t,e,i,s=No(t)){if("virtual"===e)return[{domain:"houseplan",label:"House Plan",confidence:"registry-owner"}];const n=e.indexOf(":");if(n<1)return[];const o=e.slice(0,n),r=e.slice(n+1);if("entity"===o){const e=Bo(t,r,i,s);return e?[e]:[]}if("device"!==o)return[];const a=i.devices?.[r]||t?.devices?.[r];if(!a)return[];const l=[a.config_entry_id,...Array.isArray(a.config_entries)?a.config_entries:[]].filter(t=>"string"==typeof t&&!!t).map(e=>{const i=s.configEntries[e];return Ho(t,i?.domain,"registry-owner",s,e)});if(l.some(Boolean))return qo(l);const c=[];for(const[e,n]of Object.entries(i.entities||{}))n?.device_id===r&&null==n?.disabled_by&&c.push(Bo(t,e,i,s));return c.some(Boolean)?qo(c):qo((Array.isArray(a.identifiers)?a.identifiers:[]).map(e=>{const i=Array.isArray(e)?e[0]:null;return Ho(t,i,"identifier-fallback",s)}))}(this.hass,this.presentation.binding,this.registry,No(this.hass)):[]}_providerSummary(t){if(!t.length)return this._t("marker.preview.unknown_provider");const e=t.slice(0,2).map(t=>this._providerText(t)),i=t.length-e.length;return e.join(", ")+(i>0?` · ${this._t("marker.preview.more_sources",{n:i})}`:"")}_sourceSummary(t){const e=t.visualSources;return e.length?1===e.length?`${e[0].name} · ${e[0].eid} · ${this._sourceProvider(e[0].eid)}`:2===e.length?e.map(t=>t.name).join(" · "):this._t("marker.preview.multiple_sources",{n:e.length}):this._t("marker.preview.no_source")}_stateSummary(t){if(!t.visualSources.length)return this._t("marker.preview.no_state");if(1===t.visualSources.length)return t.visualSources[0].stateText||this._t("marker.preview.no_state");const e=[...new Set(t.visualSources.map(t=>t.stateText).filter(Boolean))];return 1===e.length?e[0]:this._t("marker.preview.mixed_states")}_reason(t){return this._t(`marker.preview.reason.${t}`)}_sourceProvider(t){if(!this.registry)return this._t("marker.preview.unknown_provider");const e=Bo(this.hass,t,this.registry,No(this.hass));return e?this._providerText(e):this._t("marker.preview.unknown_provider")}render(){const t=this.presentation;if(!t)return G;const e=!!this._demoKind&&this._demoUntil>Date.now()&&"icon_ripple"===t.display&&"none"===t.pulse.kind,i=e?(()=>{const e=function(t,e,i,s,n=null){return{kind:e,reason:"short"===e?"event":"running",generation:Math.max(1,Math.trunc(i)),expiresAt:"short"===e?n:null,color:s?null:t.color,diameterScale:s?1:t.diameterScale,animated:!s,reducedMotionIndicator:s?"dot":"none"}}(t.pulse,this._demoKind,this._demoGeneration,this._reducedMotion,"short"===this._demoKind?this._demoUntil:null);return{...t,pulse:e,classes:[...t.classes.filter(t=>!t.startsWith("pulse-")),`pulse-${e.kind}`,...e.generation%2==0?["pulse-gen2"]:[]]}})():t,s=this._providers(),n="none"!==t.pulse.kind,o=i.scale*(i.pulse.animated?i.pulse.diameterScale:1);let r=-o/2,a=o/2,l=-o/2,c=o/2;if(i.valueBadge){const t=i.scale*Math.min(4,Math.max(.9,.29*i.valueBadge.text.length+.28)),e=.7*i.scale,s=.6*i.scale;"right"===i.valueBadge.position&&(a=Math.max(a,s+t)),"left"===i.valueBadge.position&&(r=Math.min(r,-s-t)),"top"===i.valueBadge.position&&(l=Math.min(l,-s-e)),"bottom"===i.valueBadge.position&&(c=Math.max(c,s+e))}if(null!=i.lqiText){const t=i.scale*("bottom"===i.valueBadge?.position?1.55:.95);c=Math.max(c,t)}const h=Math.min(1,2.35/Math.max(1,a-r),2.35/Math.max(1,c-l)),d=Math.round(100*h),u=[`left:calc(50% - ${54*((r+a)/2)*h}px)`,`top:calc(50% - ${54*((l+c)/2)*h}px)`,h<1?`transform:scale(${h})`:""].filter(Boolean).join(";"),p=["dev",...i.classes,"virtual"===i.binding?"virtual":"",i.haDisabled?"ghost ha-disabled":"",null!=i.valueText?"valonly":""].filter(Boolean).join(" "),_=Ro(i).join(";"),m=e?this._t("continuous"===this._demoKind?"marker.preview.demo_continuous_notice":"marker.preview.demo_short_notice"):this._reason(t.explanation.reason),g=[this.deviceName,this._providerSummary(s),this._stateSummary(t),m,t.valueFullText||"",fo(t.valueBadge)].filter(Boolean).join(". ");return j`
+ `;var o}function Io(t,e,i){return{kind:"none",reason:"none",generation:t,expiresAt:null,color:e,diameterScale:i,animated:!1,reducedMotionIndicator:"none"}}const Eo=new WeakMap;function zo(t){const e=function(t){const e=t?.connection||t;return!e||"object"!=typeof e&&"function"!=typeof e?null:e}(t);if(!e)return null;let i=Eo.get(e);return i||(i={revision:0,loaded:!1,configEntries:{},manifests:{},listeners:new Set,refs:0},Eo.set(e,i)),i}function Fo(t){t.revision++;for(const e of[...t.listeners])try{e()}catch{}}function Oo(t,e){const i=zo(t);if(!i)return()=>{};i.refs++,i.listeners.add(e),async function(t,e){e.loading||"function"!=typeof t?.callWS?e.loading:e.loaded&&e.loadedAt&&Date.now()-e.loadedAt<3e5||(e.loading=(async()=>{const[i,s]=await Promise.allSettled([t.callWS({type:"config_entries/get"}),t.callWS({type:"manifest/list"})]);"fulfilled"===i.status&&Array.isArray(i.value)&&(e.configEntries=Object.fromEntries(i.value.filter(t=>"string"==typeof t?.entry_id).map(t=>[t.entry_id,t]))),"fulfilled"===s.status&&Array.isArray(s.value)&&(e.manifests=Object.fromEntries(s.value.filter(t=>"string"==typeof t?.domain).map(t=>[t.domain,t]))),e.loaded=!0,e.loadedAt=Date.now(),e.loading=void 0,Fo(e)})().catch(()=>{e.loaded=!0,e.loadedAt=Date.now(),e.loading=void 0,Fo(e)}),e.loading)}(t,i),async function(t,e){if(e.unsubscribe||e.subscribing)return e.subscribing;const i=t?.connection?.subscribeMessage;"function"==typeof i&&(e.subscribing=(async()=>{try{e.unsubscribe=await i.call(t.connection,t=>{const i=Array.isArray(t)?t:Array.isArray(t?.entries)?t.entries:t?[t]:[];let s=!1;const n={...e.configEntries};for(const t of i){const e=t?.entry||t?.data?.entry||("string"==typeof t?.entry_id?t:null),i=e?.entry_id;if("string"!=typeof i)continue;const o=t?.type||t?.action||t?.data?.action;"removed"===o||"remove"===o?delete n[i]:n[i]=e,s=!0}s&&(e.configEntries=n,Fo(e))},{type:"config_entries/subscribe"})}catch{}finally{0===e.refs&&(e.unsubscribe?.(),e.unsubscribe=void 0),e.subscribing=void 0}})(),e.subscribing)}(t,i);let s=!1;return()=>{s||(s=!0,i.listeners.delete(e),i.refs=Math.max(0,i.refs-1),i.refs||(i.unsubscribe?.(),i.unsubscribe=void 0))}}function No(t){const e=zo(t);return e?{revision:e.revision,loaded:e.loaded,configEntries:e.configEntries,manifests:e.manifests}:{revision:0,loaded:!1,configEntries:{},manifests:{}}}function Lo(t,e,i=No(t),s){const n=i.manifests[e];return function(t,e){if("function"!=typeof t?.localize)return"";try{const i=`component.${e}.title`,s=t.localize(i);return"string"==typeof s&&s!==i?s:""}catch{return""}}(t,e)||String(n?.name||"")||String(s||"")||e.replace(/_/g," ")}function Ho(t,e,i,s,n){const o=String(e||"").trim();if(!o)return null;const r=n?s.configEntries[n]:null;return{domain:o,label:Lo(t,o,s,r?.title),configEntryId:n,configEntryTitle:r?.title,confidence:i}}function qo(t){const e={"registry-owner":0,"entity-platform":1,"identifier-fallback":2},i=new Set;return t.filter(t=>!!t).sort((t,i)=>e[t.confidence]-e[i.confidence]||t.label.localeCompare(i.label)||t.domain.localeCompare(i.domain)).filter(t=>{const e=`${t.domain}\n${t.configEntryId||""}`;return!i.has(e)&&(i.add(e),!0)})}function Bo(t,e,i,s=No(t)){const n=i.entities?.[e]||t?.entities?.[e];if(!n)return null;const o=n.config_entry_id||void 0,r=o?s.configEntries[o]?.domain:"";return Ho(t,n.platform||r,"entity-platform",s,o)}var jo={"editor.context_actions":"Actions: {object}","editor.tool_options":"Tool options: {tool}","editor.palette":"Palette: {tool}","editor.open_group":"Tool group: {group}","editor.group_active":"{group} — active: {item}","editor.disabled_action":"{action} is unavailable: {reason}","btn.properties":"Properties","btn.keep_as_walls":"Keep as closed walls","title.markup_partition":"Partition: draw one independent physical wall","title.markup_select":"Select and edit walls, columns and saved outlines","title.markup_column":"Column: click a grid point to place a square column","markup.partition":"Partition","markup.column":"Column","markup.hint_partition":"two clicks draw one partition · Shift locks to 45°","markup.hint_column":"click a grid point to place a square column","history.draft_segment":"Add room-draft segment","history.draft_merge":"Join unfinished room outlines","history.draft_segment_delete":"Remove room-draft segment","history.partition_add":"Add partition","history.column_add":"Add column","history.physical_edit":"Change physical object","history.physical_delete":"Delete physical object","history.physical_move":"Move physical object","history.contour_to_partitions":"Convert contour to closed walls","toast.column_duplicate":"A column with the same centre and outer size already exists","confirm.delete_draft":"Delete the whole unfinished room contour?","confirm.delete_draft_segment":"Delete this draft segment? The remaining contour may split in two.","physical.partition_properties":"Partition properties","physical.column_properties":"Column properties","physical.draft_properties":"Draft segment properties","physical.shape":"Shape","physical.square":"Square","physical.circle":"Circle","physical.diameter":"Diameter","physical.side":"Side","physical.rotation":"Rotation angle","physical.length":"Length","physical.allowed_range":"Allowed: 1–{max} {unit}","physical.delete_segment":"Delete segment","physical.delete_draft":"Delete entire outline","physical.partition_size_title":"Thickness of the partition created by the second click (1–100 cm).","physical.column_size_title":"Outer side of the square column placed by the click (1–150 cm).","card.title":"House plan","count.devices":"{n} dev.","empty.no_spaces":"No spaces yet.","empty.add_first":"Add the first space and upload a floor plan.","empty.install":'Install the House Plan integration and add it in "Devices & services".',"btn.add_space":"Add space","btn.cancel":"Cancel","btn.save":"Save","btn.close":"Close","btn.delete":"Delete","btn.edit":"Edit","btn.open_in_ha":"Open in HA","btn.reset":"Reset","btn.attach":"Attach…","btn.upload":"Upload…","btn.replace":"Replace…","title.zoom_in":"Zoom in","title.zoom_out":"Zoom out","title.zoom_fit":"Fit all","title.add_device":"Add a device to the plan","title.show_all":"Show hidden and disabled devices as service markers (this tab only)","title.markup":"Room markup: grid, lines, outlines","title.configure_space":"Configure space","title.add_space":"Add space","title.markup_add":"Room outline: connect grid dots until the room closes; Shift locks the direction in 45° steps","title.markup_merge":"Merge: click one room, then the neighbour it shares a wall with","title.markup_split":"Split a room: click the room, then two points on its walls","title.markup_delroom":"Delete room: click inside a room and confirm","title.markup_boundary":"Boundary: click two points on a solid shared wall to make that stretch virtual; click a dashed stretch to restore it","title.need_plan":"Upload a floor-plan image","markup.add":"Room outline","markup.merge":"Merge","markup.split":"Split","markup.resize":"Resize","title.markup_resize":"Resize rooms: drag a wall handle; click a room for a corner scale frame","markup.hint_resize":"drag a wall handle · click a room — corner frame · Esc cancels a drag · Ctrl+Z — undo a step","markup.opening":"Opening","markup.boundary":"Boundary","markup.delete_room":"Delete room","history.undo":"Undo","history.redo":"Redo","history.undo_named":"Undo: {name} (Ctrl+Z)","history.redo_named":"Redo: {name} (Ctrl+Shift+Z / Ctrl+Y)","history.undo_empty":"Nothing to undo","history.redo_empty":"Nothing to redo","history.undone":"Undone: {name}","history.redone":"Redone: {name}","history.add_room":"Create room","history.split_room":"Split room","history.merge_rooms":"Merge rooms","history.resize_room":"Resize room","history.open_boundary":"Open boundary","history.close_boundary":"Restore wall","history.wall_thickness":"Change wall thickness","history.add_opening":"Add door or window opening","history.edit_opening":"Edit opening","history.move_opening":"Move opening","history.delete_opening":"Delete opening","history.delete_room":"Delete room","history.decor_add":"Add decor object","history.decor_edit":"Edit decor object","history.decor_move":"Move decor object","history.decor_transform":"Transform decor object","history.decor_delete":"Delete decor object","history.backdrop_transform":"Transform plan backdrop","title.markup_opening":"Openings: click a wall to place, click an opening to edit","opening.new":"New opening","opening.edit":"Opening","opening.door":"Door","opening.window":"Window","opening.gate":"Gate","opening.type_label":"Type","opening.length_label":"Length, cm","opening.contact_label":"Open/close sensor","opening.lock_label":"Lock","opening.none":"— none —","opening.invert":"Invert open/closed","opening.flip_h":"Hinge on the other jamb","opening.flip_v":"Opens to the other side","opening.open":"Open","opening.closed":"Closed","opening.locked":"Locked","opening.unlocked":"Unlocked","opening.state_unknown":"unavailable","opening.no_entities":"No sensors bound — a static symbol on the plan.","toast.opening_no_wall":"Click next to a room wall — openings sit on walls","markup.delete":"Delete","markup.hint_points":"points: {n} · Shift — 45° steps · Esc/Ctrl+Z — undo a dot · first point or Ctrl/Cmd+click — close","markup.hint_start":"click a grid dot to start the outline","tip.lqi":"average zigbee signal:","tip.area":"area: {value}","info.device_header":"Device on the plan","info.model":"Model","info.state":"State","info.link":"Link","info.manuals":"Manuals","info.none":"No additional information","marker.new_device":"New device","marker.name_label":"Name (shown on the plan)","marker.name_ph":"Name","marker.binding_label":"Bind to an HA device","marker.binding_disabled":"disabled in Home Assistant","marker.virtual_option":"Virtual device (no binding)","marker.search_ph":"Search device / group…","marker.nothing_found":"nothing found","marker.room_label":"Room","marker.room_override":" (override placement)","marker.room_choose":"— select a room —","marker.room_auto":"— by device area (auto) —","marker.icon_label":"Icon","marker.icon_ph":"mdi:… (empty = auto)","marker.display_label":"Display","display.badge":"Icon + state","display.icon_ripple":"Icon + state and activity","display.static_icon":"Always static icon","marker.display_hint":"Icon + state changes the plate for work, open, alarm and unavailable states. Icon + state and activity additionally shows a short pulse for events and a continuous pulse for ongoing work, movement or presence. Value + state replaces the icon with one unambiguous HA value. A static icon never reacts to device states.","marker.display_hint_badge":"The icon and dynamic plate show device state without the ordinary activity pulse. Red alarms remain visible.","marker.display_hint_icon":"The icon and dynamic plate show device state without the ordinary activity pulse. Red alarms remain visible.","marker.display_hint_icon_ripple":"The icon, dynamic plate and pulse show a short pulse for events and a continuous pulse for work, motion or presence. Red alarms have separate priority.","marker.display_hint_value":"One unambiguous Home Assistant value replaces the icon while the plate continues to show state. Red alarms remain visible.","marker.display_hint_static_icon":"The black plate and icon always stay the same. State, activity, unavailability and alarms do not change the face.","marker.static_alarm_warning":"This device can report alarms. Static display hides the marker's visual alarm indication.","marker.preview.title":"Display preview","marker.preview.actual":"Now","marker.preview.example":"Example","marker.preview.integration":"Provided by","marker.preview.source":"Display source","marker.preview.current_state":"Current state","marker.preview.result":"On the plan","marker.preview.details":"Source details","marker.preview.select_source":"Choose a Home Assistant device or entity to see its actual display.","marker.preview.unknown_provider":"Unknown integration","marker.preview.virtual_provider":"House Plan · virtual device","marker.preview.no_source":"No active source","marker.preview.no_state":"No current state","marker.preview.mixed_states":"Several different states","marker.preview.multiple_sources":"{n} sources","marker.preview.more_sources":"+{n} more","marker.preview.scaled":"The preview is scaled to {n}% to fit. The saved size is unchanged.","marker.preview.demo_activity":"Show activity example","marker.preview.demo_notice":"Temporary activity example; the actual device state is unchanged.","marker.preview.demo_short":"Show short activity","marker.preview.demo_continuous":"Show continuous activity","marker.preview.stop_continuous":"Stop continuous activity","marker.preview.demo_short_notice":"Short activity example; the actual device state is unchanged.","marker.preview.demo_continuous_notice":"Continuous activity example; the actual device state is unchanged.","marker.preview.demo_already_visible":"The current state already shows a real activity or alarm effect.","marker.preview.reduced_motion":"System reduced-motion is enabled, so ordinary activity is shown as a dot.","marker.preview.reason.neutral":"Neutral dark plate","marker.preview.reason.working":"Yellow plate: the device is working now","marker.preview.reason.working_activity":"Yellow plate and activity effect: the device is working now","marker.preview.reason.open":"Orange plate: physically open or unlocked","marker.preview.reason.cover_icon_state":"Neutral plate; the cover entity controls the displayed state and icon","marker.preview.reason.presence":"Activity effect while presence is detected","marker.preview.reason.event":"Short activity effect after a detected event","marker.preview.reason.transition":"Activity effect while the device is moving or changing state","marker.preview.reason.media_neutral":"Media devices stay on a neutral dark plate while available","marker.preview.reason.unavailable":"Subdued neutral plate: unavailable or off","marker.preview.reason.alarm":"Red alarm plate; alarm indication is always shown","marker.preview.reason.live_states_disabled":"Live-state styling is disabled for this card","marker.preview.reason.value_no_state":"No usable state value; the icon is shown instead","marker.preview.reason.value_ambiguous_sources":"Several possible value sources; the icon is shown instead","marker.preview.reason.value_non_scalar":"The source did not return a simple value; the icon is shown instead","marker.preview.reason.value_virtual":"A virtual device has no Home Assistant value; the icon is shown instead","marker.preview.reason.vacuum_live_plan_only":"Live vacuum position and trail are available only on the full plan","marker.preview.reason.hidden_design_preview":"The device is hidden on the plan; this design preview remains visible","marker.preview.reason.composite_power_source":"State comes from the device Power entity; auxiliary switches are ignored","marker.preview.reason.activity_display_disabled":"The selected display mode does not show ordinary activity effects","marker.preview.reason.ha_disabled":"The Home Assistant binding is disabled and will be hidden on the plan","marker.preview.reason.orphaned":"The saved Home Assistant binding can no longer be found","marker.preview.reason.static_icon":"Static mode: device state does not change the icon","marker.activity_color":"Activity pulse color","marker.ripple_size":"Activity pulse size","marker.activity_alarm_note":"Color and size affect ordinary activity only and do not change red alarms.","marker.pulse_a11y_alarm":"Alarm","marker.pulse_a11y_event":"Recent event","marker.pulse_a11y_presence":"Presence detected","marker.pulse_a11y_transition":"Changing state","marker.pulse_a11y_running":"Working","marker.size_label":"Icon size / rotation","marker.angle_label":"Rotate","marker.model_label":"Model","marker.model_ph":"e.g. Aqara T&H","marker.link_label":"Link","marker.desc_label":"Description","marker.desc_ph":"Notes, specs…","marker.manuals_label":"Manuals (PDF etc.)","marker.sub_device":"device","marker.sub_z2m_group":" · Z2M group","marker.sub_group":"group","marker.sub_helper":"helper","space.new":"New space","space.header":"Space","space.title_label":"Title","space.title_ph":"e.g. Garage","space.plan_label":"Floor plan (background)","space.no_plan":"no plan image","space.plan_alt":"plan","room.new":"New room","room.name_label":"Display name","room.name_ph":"e.g. Terrace","room.area_label":"Home Assistant area (unassigned)","room.no_area_option":"— no area —","room.default_name":"Room","device.unnamed":"unnamed","device.light_group":"light group","device.fallback":"device","device.virtual":"virtual device","confirm.delete_room":'Delete room "{name}"?',"confirm.merge_rooms":'Merge rooms "{a}" and "{b}"?',"confirm.remove_marker":'Delete "{name}" from the plan? The device will disappear completely and stop contributing to plan data. You can add it again later.',"confirm.erase_decor":"Erase the {kind} object? You can undo this action from the editor history.","confirm.delete_space":'Delete space "{title}" with all its rooms and markup?',"toast.pos_save_failed":"Failed to save position: {err}","toast.no_entity":"The device has no suitable entity","toast.ha_disabled_action":"A Home Assistant object that is disabled cannot be used on the plan.","toast.ha_disabled_show_device":"A device disabled in Home Assistant cannot be shown on the plan. Enable it in Home Assistant first.","toast.ha_disabled_show_entity":"An entity disabled in Home Assistant cannot be shown on the plan. Enable it in Home Assistant first.","toast.ha_disabled_add":"A disabled Home Assistant object cannot be added to the plan. Enable it in Home Assistant first.","toast.ha_binding_unverified":"The object status could not be verified through the Home Assistant registry. Display and actions are temporarily unavailable.","toast.markup_needs_server":"Markup is available after the config is moved to the server","toast.conflict":"Config was changed in another window — data refreshed, repeat your last action","toast.cfg_save_failed":"Failed to save config: {err}","toast.room_overlap":"The outline overlaps room “{name}” — rooms must not overlap","toast.contour_min_edges":"Draw at least two edges before closing the outline","toast.contour_cannot_close":"The outline cannot close because it is degenerate or intersects itself","toast.merge_not_adjacent":"Only rooms that share a wall can be merged","toast.rooms_merged":"Rooms merged into “{name}”","toast.split_pick_wall":"Start the cut on the room’s wall","toast.split_bad_cut":"The cut must run wall to wall inside the room, without crossing walls or itself","merge.header":"Merge rooms","merge.hint":"The merged room keeps one name and one area. The other area is released — its devices leave the plan until another room claims it.","merge.keep":"Keep","merge.no_area":"no area","room.split_header":"New room from the split","toast.room_saved":"Room saved ({n}). Devices added: {added}. Outline the next one or exit markup.","toast.room_saved_no_area":"Room saved ({n}, no area). Outline the next one or exit markup.","toast.marker_needs_server":"Device editing is available after the config is moved to the server","toast.virtual_name_required":"Enter a name for the virtual device","toast.marker_saved":"Device saved","toast.marker_removed":"Device deleted from the plan","toast.integration_missing":"The House Plan integration is not installed — management unavailable","toast.plan_formats":"Supported formats: SVG, PNG, JPG, WebP","toast.plan_required":"Upload a floor plan — it is required","toast.space_added_onboard":"Space added. Outline the rooms: click grid dots and close the contour.","toast.space_added":"Space added","toast.space_saved":"Space saved","toast.space_deleted":"Space deleted","toast.delete_failed":"Delete failed: {err}","toast.error":"Error: {err}","toast.file_failed":'File "{name}" was not uploaded: {err}',"toast.files_attached":"Files attached: {n}","err.unknown":"unknown error","err.code":"code {code}","err.too_large":"file larger than {mb} MB","err.bad_ext":"unsupported type (PDF/image expected)","err.unauthorized":"administrator rights required","editor.title":"Title","editor.default_floor":"Default space","editor.icon_size":"Icon size, % of plan width","editor.show_temperature":"Show temperature","editor.live_states":"Live states (on/off, open…)","editor.show_signal":"Show zigbee signal (LQI)","editor.language":"Interface language","editor.lang_auto":"Auto (HA profile)","editor.lang_en":"English","editor.lang_ru":"Русский","title.icon_rules":"Icon rules: which MDI icon devices get by name","rules.title":"Icon rules","rules.hint":"Rules are checked top-down against “device name + model” (case-insensitive regex); the first match wins. When nothing matches, the entity device class decides, then the generic chip icon.","rules.pattern_ph":"regex, e.g. plug|socket","rules.icon_ph":"mdi:power-socket-de","rules.add":"Add rule","rules.reset":"Reset to defaults","rules.test_ph":"Try a device name…","rules.invalid":"invalid regex","rules.saved":"Icon rules saved","btn.up":"Up","btn.down":"Down","tap.info":"Device card","tap.more_info":"HA more-info dialog","tap.toggle":"Toggle state","marker.tap_label":"Tap action for this device","tap.toggle_note":"The resolved state is shown below. Secure devices are never toggled from the plan.","import.title":"Create spaces from HA floors","import.hint":"Your Home Assistant already knows these floors. Pick the ones to turn into plan spaces — you will upload a floor-plan image for each one next. Rooms are then outlined by hand on the plan.","import.start":"Create {n} space(s)","import.manual":"Start from scratch","import.progress":"Floor {i} of {n}","import.done":"Spaces created. Outline the rooms: click grid dots and close the contour.","btn.skip":"Skip","space.scale_label":"Scale (grid cell size)","space.scale_unit":"cm per cell","space.display_section":"Display","space.show_borders":"Always show room borders","space.show_names":"Show room names (drag to move)","space.room_color":"Border & name color","space.opacity":"Opacity","space.fill_label":"Room fill","fill.none":"None","fill.lqi":"Zigbee signal","fill.light":"Lights","fill.custom":"Custom color","space.custom_fill":"Fill color","space.source_file":"I have a floor-plan image","space.source_draw":"No image — I'll outline rooms by hand","space.orientation":"Canvas","orient.landscape":"Landscape","orient.portrait":"Portrait","orient.square":"Square","fill.temp":"Temperature","space.temp_min":"Comfort from","space.temp_max":"to","tip.temp_avg":"average temperature:","space_card.button":"Open the space plan","space_card.not_found":"Space “{id}” not found","space_card.loading":"Loading…","continuity.restore_plan":"Restoring floor plan…","continuity.restore_connection":"Restoring device connection…","continuity.retry":"Try again","editor.space":"Space","editor.show_button":"Show button","editor.button_label":"Button label","editor.button_target":"Target dashboard path","marker.sub_entity":"entity","title.general_settings":"General settings","gs.title":"General settings","gs.hint":"Fill colors apply to every space; each color has its own opacity. Which fill mode a space uses is set in that space's dialog.","gs.light_group":"Fill: lights","gs.light_on":"Lights on","gs.light_off":"All lights off","gs.temp_group":"Fill: temperature","gs.temp_cold":"Cold","gs.temp_ok":"Comfortable","gs.temp_hot":"Hot","gs.lqi_group":"Fill: zigbee signal","gs.lqi_low":"Weak signal","gs.lqi_high":"Strong signal","gs.reset":"Reset to defaults","gs.saved":"General settings saved","space.show_lqi":"Show zigbee signal (LQI) next to devices","space.hide_decor":"Hide the decorative layer","space.hide_decor_tip":"Lines, shapes, labels and furniture stay where they are — visible in the backdrop editor, not on the plan.","space.hide_openings":"Hide openings","space.hide_openings_tip":"Door, window and gate symbols are not drawn, but the openings keep working: light passes through, the sun comes in at a window, contact sensors still open them. The plan editor always shows them.","gs.light_none":"No light sources","mode.plan":"Plan editor","mode.devices":"Device editor","display.value":"Value + state","marker.subarea":"no area, manual","device.new":"New device — open its editor to dismiss","opening.unlock_action":"Unlock","opening.lock_action":"Lock","opening.lock_pending":"Working…","title.close_editor":"Close editor (back to view)","devbar.add":"Add","devbar.show_all":"Hidden and disabled","devbar.rules":"Icon rules","space.roomcard_section":"Room card shows:","space.label_temp":"Temperature","space.label_hum":"Humidity","space.label_lqi":"Average Zigbee signal","space.label_light":"Lights on/off","roomcard.light_on":"On","roomcard.light_off":"Off","roomcard.light_partial":"{on} of {total}","toast.split_pick_inside":"Intermediate cut points must be inside the room","mode.decor":"Background editor","decor.select":"Select","decor.line":"Line","decor.rect":"Rectangle","decor.ellipse":"Oval","decor.text":"Text","decor.erase":"Erase","decor.erase_confirm_title":"Erase object?","decor.color":"Color","decor.width":"Line width","decor.line_style":"Line style","decor.line_style_solid":"Solid","decor.line_style_dashed":"Dashed","decor.fill":"Fill","decor.fill_color":"Fill color","decor.length":"Length","decor.size":"Size","decor.angle":"Rotation angle, °","decor.text_size":"Text size","decor.backdrop_properties":"Plan backdrop properties","decor.text_title":"Text label","decor.object_title":"Edit {kind}","decor.text_label":"Text","decor.live_group":"Insert HA variable","decor.live_entity":"Entity","decor.live_entity_ph":"choose an entity","decor.live_attr":"Value","decor.live_attr_ph":"choose state or attribute","decor.live_state":"State","decor.backdrop":"Backdrop image","decor.backdrop_hint":"Drag to move; pull a corner to resize; use the top handle to rotate. Shift changes proportions or frees the angle.","decor.backdrop_reset":"Reset the picture","decor.backdrop_reset_done":"The picture is back at its original place and size","marker.icon_auto":"Auto: {icon} (by icon rules; pick one to override)","marker.icon_pin_auto":"Pin","mode.plan_tip":"Plan editor — the geometry of the home: draw and split/merge rooms, bind them to HA areas, place doors, windows and gates, move room cards, set the scale","mode.devices_tip":"Device editor — everything about icons: drag to position, click to edit binding/icon/display, add virtual devices, icon rules","mode.decor_tip":"Background editor — purely visual decor under the plan: lines, rectangles, ovals and text labels that never react to clicks","space.glow_enabled":"Light-source glow","gs.glow_group":"Light-source glow","gs.glow_base":"House darkness","gs.glow_light":"Default light color / intensity","gs.wall_group":"Walls","gs.wall_fill":"Wall fill","gs.glow_radius":"Glow radius","gs.unit_m":"m","gs.unit_ft":"ft","marker.controls_label":"Controls other light sources","marker.controls_hint":"With tap action “Toggle”, a click flips every source added here. The marker’s own bound entity is controlled directly and cannot be added; use the switch below to classify it as a light.","marker.controls_filter":"Search lights and switches…","info.controls":"Controls","marker.glow_radius_label":"Glow radius","marker.glow_radius.help":"Sets the glow radius in metres or feet; an empty value uses the radius from general settings.","marker.glow_radius.help.aria":"Help: glow radius","markup.wallthick":"Thickness","markup.select":"Select","title.markup_wallthick":"Thickness — click a wall to set how thick it is. Empty or zero removes the thickness.","markup.hint_wallthick":"click a wall · Esc closes without applying","wallthick.field":"Thickness","wallthick.unit_cm":"cm","wallthick.unit_in":"in","wallthick.apply_room":"Apply to all walls of this room","markup.draw_wall_title":"Thickness of each new room-wall segment (1–100 cm). Shared walls keep the neighbour's value.","toast.wallthick_pick":"Click a wall (not an open boundary)","toast.wallthick_open":"Open boundaries have no thickness","toast.wallthick_set":"Wall thickness set","toast.wallthick_cleared":"Wall thickness removed","toast.physical_range":"Enter a value from 1 to {max} {unit}","toast.physical_angle":"Enter a rotation angle from 0° up to, but not including, 90°","toast.physical_limit":"The space has reached the limit for this type of geometry","markup.boundary_hint":"Select a shared room boundary or a dashed stretch","markup.boundary_hint_open":"Click the start of the open stretch","markup.boundary_hint_second":"Choose the end on the selected boundary · Esc to cancel","markup.boundary_hint_retry":"Choose the end on the selected boundary","markup.boundary_hint_restore":"Click to restore the wall","markup.boundary_hint_outer":"Only a boundary shared by two rooms can be opened","markup.boundary_hint_blocked":"a partition, column or unfinished contour is above this boundary","markup.boundary_hint_ambiguous":"Choose a stretch farther from the junction","markup.boundary_cancel":"Cancel the unfinished Boundary action","toast.openwall_pick":"This tool works with room boundaries","toast.openwall_shared_only":"Only a shared wall between two rooms can be opened","toast.openwall_short":"Stretch too short — pick a farther point","toast.boundary_opened":"Boundary stretch opened","toast.boundary_restored":"Wall restored on this stretch","toast.boundary_same_edge":"Choose the end point on the selected wall","toast.boundary_ambiguous":"Choose a stretch slightly farther from the junction","toast.boundary_blocked":"This boundary is covered by independent masonry. Move or delete it with Select first","toast.delete_room_pick":"Click inside the room you want to delete","toast.openwall_openings_removed":"Openings on the virtual stretch were removed","toast.openwall_opened":"Boundary “{a}” ↔ “{b}” is now open","toast.openwall_closed":"Boundary “{a}” ↔ “{b}” is closed again","toast.opening_on_virtual":"Openings cannot sit on a virtual wall","marker.from_ha_option":"Pick from the HA list","marker.show_entities":"Show entities","marker.show_entities_tip":"Adds not only devices to the list, but all their entities too","marker.pick_ph":"Choose a device…","room.open_area":"Open the HA area","view.volumetric":"Volumetric view","view.flat":"Flat view","kiosk.title":"This screen's sizes","kiosk.hint":"Stored on this device only — every wall tablet or TV can have its own comfortable sizes.","kiosk.icon_scale":"Device icon size","kiosk.font_scale":"Room card text size","editor.kiosk":"Wall device (kiosk) mode","editor.cycle":"Auto-switch spaces every N seconds (kiosk, 0 = off)","room.settings_title":"Room settings","room.settings_section":"Room settings (override the space)","room.fill_label":"Fill in THIS room","fill.inherit":"As the space","room.custom_fill_space":"Space color","room.custom_fill_own":"Room color","room.temp_src_label":"Temperature source","room.hum_src_label":"Humidity source","room.src_average":"Average over the room's sensors (default)","room.src_pick":"A specific HA device or entity","room.src_ph":"Choose a source…","toast.room_updated":"Room updated","space.card_font":"Room-card font size (whole space)","room.sizes_section":"Font sizes","room.name_scale":"Room name size","room.label_scale":"Metrics size","preview.room_name":"Living room","toast.cfg_reload_failed":"Could not reload the plan from the server: {err}","room.settings_short":"Room settings","room.unnamed":"Unnamed room","marker.use_climate_temp":"Include the device temperature in the room","marker.use_climate_temp_tip":"Adds an air conditioner or thermostat current_temperature to the room average. Configure the external value badge separately below.","marker.value_badge_title":"Value badge","marker.value_badge.help":"Shows one selected value next to the icon. It does not affect room metrics, light, Glow or the tap action.","marker.value_badge.help.aria":"Help: device value badge","marker.value_badge_enabled":"Show a value badge","marker.value_badge_source":"Value","marker.value_badge_source.help":"Choose a specific state, supported attribute, or derived value of this device.","marker.value_badge_source.help.aria":"Help: value badge source","marker.value_badge_position":"Position","marker.value_badge_position.help":"The chosen side stays fixed and does not flip automatically at a plan edge.","marker.value_badge_position.help.aria":"Help: value badge position","marker.value_badge_right":"Right","marker.value_badge_bottom":"Below","marker.value_badge_left":"Left","marker.value_badge_top":"Above","marker.value_badge_empty":"This device has no available values","marker.value_badge_static":"A static icon does not show live values. The setting is preserved.","marker.value_badge_missing":"Source unavailable","marker.value_badge_missing_hint":"The saved source is currently unavailable. Keep it, disable the badge, or choose another value.","marker.value_badge_duplicate":"The same value is already shown inside the icon.","marker.value_badge_state":"State · {name}","marker.value_badge_marker_state":"Light-source state · {name}","marker.value_badge_lqi":"Average Zigbee signal quality","marker.value_badge_attr_current_temperature":"Current temperature · {name}","marker.value_badge_attr_temperature":"Target temperature · {name}","marker.value_badge_attr_current_humidity":"Current humidity · {name}","marker.value_badge_attr_humidity":"Humidity · {name}","marker.value_badge_attr_current_position":"Position · {name}","marker.value_badge_attr_percentage":"Speed · {name}","marker.value_badge_attr_brightness":"Brightness · {name}","marker.value_badge_attr_volume_level":"Volume · {name}","marker.value_badge_attr_battery_level":"Battery · {name}","marker.value_badge_attr_fan_speed":"Fan speed · {name}","marker.light_role_label":"Is this device a light source?","marker.light_role.help":"Controls whether this marker itself is a spatial light source. Linked lights above remain independent room-light controls.","marker.light_role.help.aria":"Help: whether this device is a light source","marker.light_role_auto_yes":"Auto (light source)","marker.light_role_auto_no":"Auto (not a light source)","marker.light_role_always":"Always a light source","marker.light_role_never":"Never a light source","marker.light_entity_label":"Leading light entity","marker.light_entity.help":"For a composite device, choose the entity whose state, colour and brightness represent this plan source. The automatic fallback remains compatible with older plans.","marker.light_entity.help.aria":"Help: leading light entity","marker.light_entity_auto":"Automatic ({entity})","marker.light_entity_none":"no controllable entity","marker.light_entity_missing":"The saved entity {entity} is unavailable. House Plan temporarily uses {fallback}; the saved choice will be restored if it returns.","marker.glow_color_label":"Glow colour and brightness","marker.glow_mode.help":"Use live source values, override only its colour, or fix both colour and brightness. The minimum is 1%; to disable the source, choose “Never”.","marker.glow_mode.help.aria":"Help: glow colour and brightness","marker.glow_mode_auto":"From source","marker.glow_mode_color":"Set colour","marker.glow_mode_fixed":"Set colour and brightness","marker.glow_color":"Glow colour","marker.glow_brightness":"Brightness","marker.glow_disabled_never":"Glow settings are unavailable because this marker is explicitly not a light source.","marker.glow_disabled_auto":"Auto mode found no spatial light source for this marker.","marker.glow_disabled_no_entity":"No active controllable Home Assistant entity is available for this spatial source.","marker.glow_passive_hint":"This source has no own Home Assistant data. Set its colour and brightness manually; its radius remains available.","marker.control_broken":"Saved source is missing or no longer marked as a light","marker.control_missing_label":"Missing plan light","marker.control_passive":"passive source","toast.marker_control_cycle":"This link would create a circular chain of light controls.","toast.marker_binding_required":"Choose a Home Assistant device before linking another light source.","confirm.unlock":"Unlock “{name}”?","toast.files_migrate_failed":"Attachments could not be moved to the new binding, links keep pointing at the old files: {err}","space.pick_saved":"Already uploaded","space.pick_saved_hint":"Plans stored on the server, including ones you detached earlier","space.no_saved":"No plans stored on the server yet.","space.loading":"Loading…","space.used_by":"in use: {list}","space.in_use":"A space still uses this plan — detach it first","btn.use":"Use","confirm.delete_plan":'Delete the plan file "{name}" from the server? This cannot be undone.',"toast.plans_list_failed":"Could not list the stored plans: {err}","toast.plan_delete_failed":"Could not delete the plan: {err}","marker.hide":"Hide","marker.hide_tip":'The device will disappear from the plan after saving but will still count toward the room signal. Restore it through "Hidden and disabled" in the device editor.',"marker.show":"Show","marker.show_tip":"The device will appear on the plan again after saving.","marker.hidden_ghost":"Device hidden by the user","marker.ha_disabled_device":"The device is disabled in Home Assistant and hidden from the plan.","marker.ha_disabled_entity":"The entity is disabled in Home Assistant and hidden from the plan.","marker.ha_disabled_all_entities":"The device has no active Home Assistant entities, so it is hidden from the plan.","marker.ha_registry_limited":"The full Home Assistant registry is unavailable to this user. The unverified object cannot be shown or used for now.","marker.delete_tip":"Completely delete the device from the plan and every aggregate. You can add it again later.","tap.run":"Run automation/script/scene","tap.cover":"Open/close (curtains/blinds)","marker.run_target_label":"What to run","marker.run_search_ph":"Search: automation, script or scene…","marker.run_target_gone":"Target {id} not found — pick again","marker.tap_confirm":"Ask for confirmation","marker.tap_confirm_tip":"Show a confirmation dialog before acting — a guard against accidental taps.","marker.toggle_hint_single":"Target: {name} ({id}).","marker.virtual_light_target":"Virtual light: {name}.","marker.virtual_light_current":"Manual state: {state} → {effect}.","marker.virtual_light_state_on":"on","marker.virtual_light_state_off":"off","marker.toggle_hint_group":"Will toggle {count} source(s): {names}.","marker.toggle_hint_current":"Now: {state} → {effect}.","marker.toggle_hint_group_current":"Currently on: {on} of {count} → {effect}.","marker.toggle_hint_skipped":"Will be skipped ({count}): {targets}.","marker.toggle_effect_turn_on":"will turn on","marker.toggle_effect_turn_off":"will turn off","marker.toggle_effect_open":"will open","marker.toggle_effect_close":"will close","marker.toggle_effect_stop":"will stop","marker.toggle_effect_toggle":"state will toggle","marker.toggle_skip_missing":"not found","marker.toggle_skip_ha_disabled":"disabled in HA","marker.toggle_skip_unavailable":"unavailable","marker.toggle_skip_unsupported":"toggle is unsupported","marker.toggle_skip_secure":"blocked for security","marker.toggle_none_no_binding":"This device has no binding, so there is no state to toggle. Tapping will do nothing.","marker.toggle_none_no_actionable_entity":"This device has no state that can be toggled. Tapping will do nothing.","marker.toggle_none_configured_targets_missing":"The configured targets are unavailable. The device's own entity will not be substituted for them.","marker.toggle_none_ha_disabled":"The target is disabled in Home Assistant and cannot be used on the plan.","marker.toggle_none_unavailable":"The target is currently unavailable. Tapping will do nothing.","marker.toggle_none_unsupported":"Home Assistant exposes no safe toggle operation for this entity. Tapping will do nothing.","marker.toggle_none_secure":"Toggling locks, alarms and secure gates from the plan is blocked for security.","run.automation":"automation","run.script":"script","run.scene":"scene","confirm.tap_run":'Run "{name}"?',"confirm.tap_toggle":'Toggle "{name}"?',"confirm.tap_cover":'Open/close "{name}"?',"toast.run_started":"Started: {name}","toast.run_target_missing":"Run target not found — check the device settings","toast.run_target_required":"Pick an automation, script or scene","toast.tap_target_changed":"The action target changed. Try again.","toast.virtual_light_toggle_failed":"Could not toggle the virtual light: {err}","toast.value_badge_source_required":"Choose a value for the badge","btn.run":"Run","vac.section":"Robot vacuum: live position","vac.autocal":"Set up automatically","vac.live":"Live position on the plan","vac.trail":"Show the robot's path","vac.cal_maps":"Calibrated maps: {maps}","vac.autocal_no_rooms":"The integration reports no room list — open “Fit manually”","vac.autocal_no_match":"Room names did not match (need ≥3 in common) — open “Fit manually”","vac.autocal_done":"Done: bound via {rooms} rooms. Start a cleanup and check","vac.cal_need_pos":"The robot is not reporting coordinates — start a cleanup and pause it","vac.cal_done":"Calibration saved. Start a cleanup and check","vac.cal_cancelled":"Calibration cancelled","vac.fit":"Fit manually","vac.fit_hint":"Drag the robot map into place, stretch by the corners","vac.fit_rotate":"Rotate 90°","vac.fit_mirror":"Mirror","vac.trail_never":"Never","vac.trail_cleaning":"While cleaning","vac.trail_always":"Always","gs.bg_group":"Stage background","gs.bg_color":"Background around the plan","gs.bg_default":"Theme default","gs.bg_theme":"theme default","gs.bg_mode":"Plan background","gs.bg_static":"Static color","gs.bg_daynight":"Follows the Sun","gs.bg_daynight_hint":"The background moves through dawn, day, dusk and night using sun.sun, or your local clock when sun data is unavailable. No compass is needed for the background.","gs.sun_group":"Sun","gs.sun_missing":"The sun.sun entity was not found — the background follows your local clock; window rays are unavailable.","gs.north":"North on the plan","gs.north_ph":"not set","gs.north_hint":"Set north only for window rays (1° steps, 15° with Shift); the Sun background works without it.","gs.north_clear":"Clear","gs.north_letter":"N","gs.sun_rays":"Sunlight through windows","gs.about_group":"About","gs.about_version":"Houseplan Card v{v}","gs.about_github":"GitHub · docs & issues","gs.about_telegram":"Telegram chat","space.bg_color":"Background around the plan","space.bg_inherit":"Inherit general","space.bg_inherited":"inherits general settings","space.bg_mode":"Plan background","space.north":"North on the plan (override)","space.north_inherited":"inherited: {v}","space.sun_rays":"Sunlight through windows","space.sun_inherit":"Inherit general","space.sun_on":"On","space.sun_off":"Off","canvas.far_objects":"{n} object(s) far from the plan","canvas.show_far":"Show","canvas.home_tip":"The plan is over there — click to fit it","gs.grid_group":"Plan maintenance","gs.grid_hint":"Updates data models, aligns plan elements to the grid and merges redundant wall fragments. An exact report is shown before anything is stored.","gs.align_all":"Optimize plans","gs.align_title":"Optimize plans","gs.align_none":"All plans already use the current optimized data model.","gs.align_count":"{n} of {total} elements will move, by at most {cm} cm.","gs.align_where":"The largest shift is in “{s}”.","gs.align_turned":"Openings whose angle is corrected: {n}.","gs.align_removed_drafts":"Invalid outlines collapsed by the grid and removed: {n}.","gs.optimize_changes":"Model migrations: {m}; plans canonicalized: {c}; merged real-wall fragments: {w}; virtual fragments: {s}.","gs.optimize_glow_migration":"Legacy Glow: {spaces} spaces → no data fill + independent Glow; {rooms} rooms → inherited data fill + independent Glow.","gs.align_warn":"Elements deliberately placed between grid nodes will move. One undo is available after the operation, only until the next plan edit.","gs.align_run":"Optimize","gs.align_done":"Plans optimized: {n} elements moved, {m} records maintained","gs.optimize_undo":"Undo last optimization","gs.optimize_undone":"The last optimization was undone","decor.furniture":"Furniture","furn.title":"Furniture library","furn.symbol":"Symbol","furn.group_furniture":"Furniture","furn.group_appliance":"Appliances","furn.group_sanitary":"Plumbing","furn.group_other":"Other","furn.width":"Width","furn.depth":"Depth","furn.pick_hint":"Pick a symbol, then click on the plan.","furn.place_hint":"Click on the plan — the piece lands against the nearest wall. Shift places it free.","furn.sym_sofa":"Sofa","furn.sym_armchair":"Armchair","furn.sym_coffee_table":"Coffee table","furn.sym_table_dining":"Dining table","furn.sym_table_round":"Round table","furn.sym_chair":"Chair","furn.sym_desk":"Desk","furn.sym_bed_double":"Double bed","furn.sym_bed_single":"Single bed","furn.sym_nightstand":"Nightstand","furn.sym_wardrobe":"Wardrobe","furn.sym_bookshelf":"Bookshelf","furn.sym_fridge":"Fridge","furn.sym_stove":"Cooker","furn.sym_dishwasher":"Dishwasher","furn.sym_washer":"Washing machine","furn.sym_dryer":"Tumble dryer","furn.sym_tv":"TV","furn.sym_ac":"Air conditioner","furn.sym_water_heater":"Water heater","furn.sym_toilet":"Toilet","furn.sym_bathtub":"Bathtub","furn.sym_shower":"Shower","furn.sym_sink":"Washbasin","furn.sym_kitchen_sink":"Kitchen sink","furn.sym_bidet":"Bidet","furn.sym_stairs":"Stairs","furn.sym_fireplace":"Fireplace","furn.sym_plant":"Plant","furn.sym_rug":"Rug","common.yes":"Yes","common.no":"No","vac.diag_source":"Source","vac.diag_platform":"Integration","vac.diag_status":"Status","vac.diag_position":"Position","vac.diag_rooms":"Rooms","vac.diag_rooms_value":"{total} · {matched} names match · {readiness}","vac.autocal_ready":"auto-calibration available","vac.autocal_not_ready":"need 3 matching names","vac.diag_path":"Integration path","vac.diag_map":"Map ID","vac.source_none":"not selected","vac.source_status_ok":"Ready","vac.source_status_missing":"Missing","vac.source_status_disabled":"Disabled in Home Assistant","vac.source_status_unavailable":"Unavailable","vac.source_status_unverified":"Cannot verify with current permissions","vac.source_status_unsupported":"No position data","vac.source_status_none":"No source","vac.source_banner_missing":"The saved source no longer exists. It was not replaced automatically; choose another source or restore it in Home Assistant.","vac.source_banner_disabled":"The saved source is disabled in Home Assistant. Enable it there or choose another source.","vac.source_banner_unverified":"Current Home Assistant permissions cannot verify this saved source. It remains pinned and will not be replaced automatically.","vac.choose_source":"Choose source","vac.source_auto":"Automatic","vac.source_auto_hint":"Use a compatible entity from this device","vac.all_cameras":"All cameras","vac.all_cameras_warn":"A camera may not provide robot data. Choose one only if it is your vacuum map.","vac.all_cameras_empty":"No other camera entities found.","vac.platform_unknown":"unknown integration","vac.cap_position":"position","vac.cap_rooms_short":"rooms","vac.cap_path":"path","vac.cap_map":"map ID","vac.cap_none":"no robot data detected","vac.xcme_hint":"Enable these Xiaomi Cloud Map Extractor attributes:","vac.documentation":"Documentation","vac.residual_title":"Check automatic calibration","vac.residual_message":"The matched rooms disagree by up to {error}. Apply this approximate calibration, refine it manually, or cancel without changing the saved setup.","vac.apply_proposal":"Apply","gs.backup_group":"Backup and transfer","gs.backup_hint":"Download a portable JSON backup or preview and import a backup made by House Plan.","backup.export_open":"Export","backup.import_open":"Import","backup.export_title":"Export House Plan","backup.import_title":"Import House Plan","backup.export_hint":"Choose whether the backup should contain the whole House Plan configuration or only the current space.","backup.full":"Full backup","backup.current_space":"Current space","backup.current_space_title":"Current space: {title}","backup.no_current_space":"No current space","backup.plan_only":"Plan only","backup.plan_only_hint":"Keep rooms, walls, openings, decor and room-label positions without devices or Home Assistant bindings.","backup.plan_only_preview":"This file contains the plan only","backup.privacy_warning":"The archive keeps names, Home Assistant identifiers and exact coordinates. Internal plans and attachments are referenced, not embedded; runtime states and vacuum trails are not included.","backup.download":"Download JSON","backup.export_done":"Backup downloaded","backup.reading":"Checking the backup…","backup.revalidated":"The plan changed after this preview. The summary was refreshed; review it and confirm again.","backup.error.unauthorized":"You do not have permission to export or import this plan.","backup.error.not_ready":"House Plan is not ready yet. Try again in a moment.","backup.error.too_large":"The backup exceeds the 8 MiB limit.","backup.error.invalid_json":"The selected file is not valid JSON.","backup.error.invalid_format":"The selected file is not a House Plan backup.","backup.error.unsupported_export_version":"This backup format is not supported by the installed version.","backup.error.future_model":"The backup was created by a newer House Plan data model.","backup.error.invalid_config":"The backup contains an invalid House Plan configuration.","backup.error.invalid_layout":"The backup contains invalid object positions.","backup.error.invalid_content":"The backup contains invalid or inconsistent content references.","backup.error.space_not_found":"The selected space no longer exists.","backup.error.capacity_exceeded":"Adding this backup would exceed the plan limits.","backup.error.preview_expired":"The preview expired. Select the backup file again.","backup.error.preview_owner_mismatch":"This preview belongs to another Home Assistant user.","backup.error.conflict":"The plan changed after the preview. Review the refreshed summary.","backup.error.content_confirmation_required":"Confirm that unavailable local content may be detached.","backup.error.commit_failed":"The backup could not be applied safely. The previous plan was restored or is pending recovery.","backup.error.missing_plan":"A referenced plan file disappeared after the preview. Review the backup again.","backup.error.missing_content":"A referenced local attachment disappeared after the preview. Review the backup again.","backup.error.marker_control_missing":"A linked plan light is missing from the backup.","backup.error.marker_control_not_light":"A linked plan target is no longer marked as a light source.","backup.error.marker_control_self":"A light source cannot control itself.","backup.error.marker_control_cycle":"The backup contains a circular chain of light controls.","backup.error.duplicate_marker_control":"The backup contains a duplicate plan-light link.","backup.error.no_backup":"There is no import or optimization snapshot to restore.","backup.same_source":"Created on this Home Assistant instance","backup.foreign_source":"Created on another Home Assistant instance","backup.created":"Created: {value}","backup.versions":"Card {card}; integration {integration}; data model {model}","backup.count_spaces":"Spaces: {n}","backup.count_rooms":"Rooms: {n}","backup.count_walls":"Walls: {n}","backup.count_openings":"Openings: {n}","backup.count_decor":"Decor objects: {n}","backup.count_markers":"Devices: {n}","backup.count_layout":"Positions: {n}","backup.bindings":"Bindings — devices: {device}, entities: {entity}, virtual: {virtual}; positions without a space: {legacy}","backup.binding_status":"Target status — active: {active}, disabled: {disabled}, missing: {missing}","backup.missing_areas":"Areas missing on the target: {areas}","backup.dropped_marker_links":"Plan-light links outside this transferred space were omitted: {n}.","backup.replace_warning":"This replaces the current configuration and layout. Uploaded files are never deleted. One undo remains available until the next plan edit.","backup.foreign_bookkeeping":"Instance-specific known/new-device bookkeeping will not be imported.","backup.final_name":"New space name","backup.target_settings":"The target instance's global settings remain unchanged.","backup.duplicates":"Bindings already present: {n}","backup.skip":"Skip duplicate devices","backup.virtual_copy":"Add safe static virtual copies","backup.content":"Referenced content","backup.content_available":"available locally","backup.content_external":"external link","backup.content_detach_required":"will be detached","backup.confirm_detach":"I understand that unavailable internal plans and attachments will be detached from the imported configuration.","backup.replace":"Replace","backup.add":"Add space","backup.space_done":"Space imported: {rooms} rooms, {markers} devices","backup.full_done":"Backup restored: {spaces} spaces, {rooms} rooms, {markers} devices","backup.undo_import":"Undo last full import","backup.import_undone":"Full import undone"};const Wo={en:jo,ru:{"editor.context_actions":"Действия: {object}","editor.tool_options":"Параметры инструмента: {tool}","editor.palette":"Палитра: {tool}","editor.open_group":"Группа инструментов: {group}","editor.group_active":"{group} — активно: {item}","editor.disabled_action":"{action} недоступно: {reason}","btn.properties":"Свойства","btn.keep_as_walls":"Оставить замкнутыми стенами","title.markup_partition":"Перегородка: нарисуйте одну независимую физическую стену","title.markup_select":"Выбор и редактирование стен, колонн и сохранённых контуров","title.markup_column":"Колонна: кликните точку сетки для квадратной колонны","markup.partition":"Перегородка","markup.column":"Колонна","markup.hint_partition":"два клика рисуют одну перегородку · Shift фиксирует шаг 45°","markup.hint_column":"кликните точку сетки, чтобы поставить квадратную колонну","history.draft_segment":"Добавление сегмента черновика комнаты","history.draft_merge":"Соединение незавершённых контуров комнаты","history.draft_segment_delete":"Удаление сегмента черновика комнаты","history.partition_add":"Добавление перегородки","history.column_add":"Добавление колонны","history.physical_edit":"Изменение физического объекта","history.physical_delete":"Удаление физического объекта","history.physical_move":"Перемещение физического объекта","history.contour_to_partitions":"Преобразование контура в замкнутые стены","toast.column_duplicate":"Колонна с тем же центром и внешним размером уже существует","confirm.delete_draft":"Удалить весь незавершённый контур комнаты?","confirm.delete_draft_segment":"Удалить этот сегмент черновика? Оставшийся контур может разделиться на два.","physical.partition_properties":"Свойства перегородки","physical.column_properties":"Свойства колонны","physical.draft_properties":"Свойства сегмента черновика","physical.shape":"Форма","physical.square":"Квадрат","physical.circle":"Круг","physical.diameter":"Диаметр","physical.side":"Сторона","physical.rotation":"Угол поворота","physical.length":"Длина","physical.allowed_range":"Допустимо: 1–{max} {unit}","physical.delete_segment":"Удалить сегмент","physical.delete_draft":"Удалить весь контур","physical.partition_size_title":"Толщина перегородки, создаваемой вторым кликом (1–100 см).","physical.column_size_title":"Внешняя сторона квадратной колонны, создаваемой кликом (1–150 см).","card.title":"План дома","count.devices":"{n} устр.","empty.no_spaces":"Пространств пока нет.","empty.add_first":"Добавьте первое пространство и загрузите план этажа.","empty.install":"Установите интеграцию House Plan и добавьте запись в «Устройства и службы».","btn.add_space":"Добавить пространство","btn.cancel":"Отмена","btn.save":"Сохранить","btn.close":"Закрыть","btn.delete":"Удалить","btn.edit":"Редактировать","btn.open_in_ha":"Открыть в HA","btn.reset":"Сброс","btn.attach":"Прикрепить…","btn.upload":"Загрузить…","btn.replace":"Заменить…","title.zoom_in":"Приблизить","title.zoom_out":"Отдалить","title.zoom_fit":"Вписать всё","title.add_device":"Добавить устройство на план","title.show_all":"Показать скрытые и деактивированные устройства служебными маркерами (только в этой вкладке)","title.markup":"Разметка комнат: сетка, линии, контуры","title.configure_space":"Настроить пространство","title.add_space":"Добавить пространство","title.markup_add":"Контур комнаты: соединяйте точки сетки линиями до замыкания комнаты; Shift фиксирует направление с шагом 45°","title.markup_merge":"Объединить: клик по одной комнате, затем по соседней с общей стеной","title.markup_split":"Разделить комнату: клик по комнате, затем две точки на её стенах","title.markup_delroom":"Удалить комнату: кликните внутри комнаты и подтвердите удаление","title.markup_boundary":"Граница: две точки на сплошной общей стене делают участок виртуальным; клик по пунктиру восстанавливает стену","title.need_plan":"Загрузите подложку (план этажа)","markup.add":"Контур комнаты","markup.merge":"Объединить","markup.split":"Разделить","markup.resize":"Размер","title.markup_resize":"Изменение размера комнат: тяните ручку стены; клик по комнате — угловая рамка масштаба","markup.hint_resize":"тяните ручку стены · клик по комнате — угловая рамка · Esc отменяет перетаскивание · Ctrl+Z — отмена шага","markup.opening":"Проём","markup.boundary":"Граница","markup.delete_room":"Удалить комнату","history.undo":"Отменить","history.redo":"Повторить","history.undo_named":"Отменить: {name} (Ctrl+Z)","history.redo_named":"Повторить: {name} (Ctrl+Shift+Z / Ctrl+Y)","history.undo_empty":"Нет операций для отмены","history.redo_empty":"Нет операций для повтора","history.undone":"Отменено: {name}","history.redone":"Повторено: {name}","history.add_room":"Создание комнаты","history.split_room":"Разделение комнаты","history.merge_rooms":"Объединение комнат","history.resize_room":"Изменение размера комнаты","history.open_boundary":"Открытие границы","history.close_boundary":"Восстановление стены","history.wall_thickness":"Изменение толщины стены","history.add_opening":"Добавление дверного или оконного проёма","history.edit_opening":"Изменение проёма","history.move_opening":"Перемещение проёма","history.delete_opening":"Удаление проёма","history.delete_room":"Удаление комнаты","history.decor_add":"Добавление объекта декора","history.decor_edit":"Изменение объекта декора","history.decor_move":"Перемещение объекта декора","history.decor_transform":"Трансформация объекта декора","history.decor_delete":"Удаление объекта декора","history.backdrop_transform":"Трансформация подложки плана","title.markup_opening":"Проёмы: клик по стене — добавить, клик по проёму — редактировать","opening.new":"Новый проём","opening.edit":"Проём","opening.door":"Дверь","opening.window":"Окно","opening.gate":"Ворота","opening.type_label":"Тип","opening.length_label":"Длина, см","opening.contact_label":"Датчик открытия","opening.lock_label":"Замок","opening.none":"— нет —","opening.invert":"Инвертировать открыто/закрыто","opening.flip_h":"Петли с другой стороны","opening.flip_v":"Открывается в другую сторону","opening.open":"Открыто","opening.closed":"Закрыто","opening.locked":"Заперто","opening.unlocked":"Не заперто","opening.state_unknown":"недоступно","opening.no_entities":"Датчики не привязаны — статичный символ на плане.","toast.opening_no_wall":"Кликните рядом со стеной комнаты — проёмы ставятся на стены","markup.delete":"Удалить","markup.hint_points":"точек: {n} · Shift — шаг 45° · Esc/Ctrl+Z — убрать точку · клик по первой или Ctrl/Cmd+клик — замкнуть","markup.hint_start":"кликните точку сетки, чтобы начать контур","tip.lqi":"средний сигнал zigbee:","tip.area":"площадь: {value}","info.device_header":"Устройство на плане","info.model":"Модель","info.state":"Состояние","info.link":"Ссылка","info.manuals":"Инструкции","info.none":"Нет дополнительной информации","marker.new_device":"Новое устройство","marker.name_label":"Имя (отображается на плане)","marker.name_ph":"Название","marker.binding_label":"Привязка к устройству HA","marker.binding_disabled":"деактивировано в Home Assistant","marker.virtual_option":"Виртуальное устройство (без привязки)","marker.search_ph":"Поиск устройства / группы…","marker.nothing_found":"ничего не найдено","marker.room_label":"Комната","marker.room_override":" (переопределить размещение)","marker.room_choose":"— выберите комнату —","marker.room_auto":"— по зоне устройства (авто) —","marker.icon_label":"Иконка","marker.icon_ph":"mdi:… (пусто = авто)","marker.display_label":"Отображение","display.badge":"Значок + состояние","display.icon_ripple":"Значок + состояние и активность","display.static_icon":"Всегда статичный значок","marker.display_hint":"Значок + состояние меняет подложку при работе, открытии, тревоге и недоступности. Значок + состояние и активность дополнительно показывает короткую пульсацию для событий и постоянную — для работы, движения или присутствия. Значение + состояние заменяет значок одним однозначным значением HA. Статичный значок не реагирует на состояния устройства.","marker.display_hint_badge":"Значок и динамическая подложка показывают состояние устройства без обычной пульсации активности. Красная тревога сохраняется.","marker.display_hint_icon":"Значок и динамическая подложка показывают состояние устройства без обычной пульсации активности. Красная тревога сохраняется.","marker.display_hint_icon_ripple":"Значок, динамическая подложка и пульсация: короткая — для событий, постоянная — для работы, движения или присутствия. Красная тревога имеет отдельный приоритет.","marker.display_hint_value":"Значок заменяется одним однозначным значением Home Assistant; подложка продолжает показывать состояние. Красная тревога сохраняется.","marker.display_hint_static_icon":"Всегда одинаковые чёрная подложка и значок. Состояния, активность, недоступность и тревоги не меняют отображение.","marker.static_alarm_warning":"Это устройство может сообщать о тревогах. Статичный режим скрывает визуальную тревогу маркера.","marker.preview.title":"Предпросмотр отображения","marker.preview.actual":"Сейчас","marker.preview.example":"Пример","marker.preview.integration":"Интеграция","marker.preview.source":"Источник отображения","marker.preview.current_state":"Текущее состояние","marker.preview.result":"На плане","marker.preview.details":"Подробнее об источниках","marker.preview.select_source":"Выберите устройство или сущность Home Assistant, чтобы увидеть фактическое отображение.","marker.preview.unknown_provider":"Интеграция неизвестна","marker.preview.virtual_provider":"House Plan · виртуальное устройство","marker.preview.no_source":"Нет активного источника","marker.preview.no_state":"Нет текущего состояния","marker.preview.mixed_states":"Несколько разных состояний","marker.preview.multiple_sources":"Источников: {n}","marker.preview.more_sources":"ещё {n}","marker.preview.scaled":"Предпросмотр уменьшен до {n}%, чтобы поместиться. Сохранённый размер не изменится.","marker.preview.demo_activity":"Показать пример активности","marker.preview.demo_notice":"Временный пример активности; фактическое состояние устройства не меняется.","marker.preview.demo_short":"Показать краткую активность","marker.preview.demo_continuous":"Показать постоянную активность","marker.preview.stop_continuous":"Остановить постоянную активность","marker.preview.demo_short_notice":"Пример краткой активности; фактическое состояние устройства не меняется.","marker.preview.demo_continuous_notice":"Пример постоянной активности; фактическое состояние устройства не меняется.","marker.preview.demo_already_visible":"Текущее состояние уже показывает реальную активность или тревогу.","marker.preview.reduced_motion":"В системе включено уменьшение движения, поэтому обычная активность показана точкой.","marker.preview.reason.neutral":"Нейтральная тёмная подложка","marker.preview.reason.working":"Жёлтая подложка: устройство сейчас работает","marker.preview.reason.working_activity":"Жёлтая подложка и эффект активности: устройство сейчас работает","marker.preview.reason.open":"Оранжевая подложка: физически открыто или разблокировано","marker.preview.reason.cover_icon_state":"Нейтральная подложка; состояние и значок определяет сущность шторы или заслонки","marker.preview.reason.presence":"Эффект активности, пока обнаружено присутствие","marker.preview.reason.event":"Короткий эффект активности после события","marker.preview.reason.transition":"Эффект активности во время движения или смены состояния","marker.preview.reason.media_neutral":"Медиаустройство остаётся на нейтральной тёмной подложке, пока доступно","marker.preview.reason.unavailable":"Приглушённая нейтральная подложка: отключено или недоступно","marker.preview.reason.alarm":"Красная тревожная подложка; тревога отображается всегда","marker.preview.reason.live_states_disabled":"Оформление по живым состояниям отключено в настройках карточки","marker.preview.reason.value_no_state":"Нет подходящего значения состояния — вместо него показан значок","marker.preview.reason.value_ambiguous_sources":"Подходит несколько источников значения — вместо него показан значок","marker.preview.reason.value_non_scalar":"Источник вернул не простое значение — вместо него показан значок","marker.preview.reason.value_virtual":"У виртуального устройства нет значения HA — вместо него показан значок","marker.preview.reason.vacuum_live_plan_only":"Живая позиция и след пылесоса доступны только на полном плане","marker.preview.reason.hidden_design_preview":"Устройство скрыто на плане, но его оформление видно в предпросмотре","marker.preview.reason.composite_power_source":"Состояние берётся из сущности питания устройства; вспомогательные переключатели не учитываются","marker.preview.reason.activity_display_disabled":"Выбранный режим отображения не показывает обычные эффекты активности","marker.preview.reason.ha_disabled":"Привязка отключена в Home Assistant, поэтому устройство будет скрыто на плане","marker.preview.reason.orphaned":"Сохранённая привязка Home Assistant больше не найдена","marker.preview.reason.static_icon":"Статичный режим: состояние устройства не меняет значок","marker.activity_color":"Цвет пульсации активности","marker.ripple_size":"Размер пульсации активности","marker.activity_alarm_note":"Цвет и размер относятся только к обычной активности и не влияют на красную тревогу.","marker.pulse_a11y_alarm":"Тревога","marker.pulse_a11y_event":"Недавнее событие","marker.pulse_a11y_presence":"Обнаружено присутствие","marker.pulse_a11y_transition":"Изменение состояния","marker.pulse_a11y_running":"Работает","marker.size_label":"Размер / поворот значка","marker.angle_label":"Поворот","marker.model_label":"Модель","marker.model_ph":"напр. Aqara T&H","marker.link_label":"Ссылка","marker.desc_label":"Описание","marker.desc_ph":"Заметки, характеристики…","marker.manuals_label":"Инструкции (PDF и т.п.)","marker.sub_device":"устройство","marker.sub_z2m_group":" · Z2M-группа","marker.sub_group":"группа","marker.sub_helper":"хелпер","space.new":"Новое пространство","space.header":"Пространство","space.title_label":"Название","space.title_ph":"Например: Гараж","space.plan_label":"Подложка (план)","space.no_plan":"нет подложки","space.plan_alt":"план","room.new":"Новая комната","room.name_label":"Отображаемое имя","room.name_ph":"Например: Терраса","room.area_label":"Зона Home Assistant (свободные)","room.no_area_option":"— без зоны —","room.default_name":"Комната","device.unnamed":"без имени","device.light_group":"группа света","device.fallback":"устройство","device.virtual":"виртуальное устройство","confirm.delete_room":"Удалить комнату «{name}»?","confirm.merge_rooms":"Слить комнаты «{a}» и «{b}»?","confirm.remove_marker":"Удалить «{name}» с плана? Устройство исчезнет полностью и перестанет участвовать в данных плана. Позже его можно будет добавить заново.","confirm.erase_decor":"Стереть объект «{kind}»? Действие можно отменить из истории редактора.","confirm.delete_space":"Удалить пространство «{title}» со всеми комнатами и разметкой?","toast.pos_save_failed":"Не удалось сохранить позицию: {err}","toast.no_entity":"У устройства нет подходящей сущности","toast.ha_disabled_action":"Деактивированный объект Home Assistant нельзя использовать на плане.","toast.ha_disabled_show_device":"Деактивированное в Home Assistant устройство нельзя показать на плане. Сначала активируйте его в Home Assistant.","toast.ha_disabled_show_entity":"Деактивированную в Home Assistant сущность нельзя показать на плане. Сначала активируйте её в Home Assistant.","toast.ha_disabled_add":"Деактивированный объект Home Assistant нельзя добавить на план. Сначала активируйте его в Home Assistant.","toast.ha_binding_unverified":"Статус объекта не удалось подтвердить по реестру Home Assistant. Отображение и действия временно недоступны.","toast.markup_needs_server":"Разметка доступна после переноса конфига на сервер","toast.conflict":"Конфиг изменён в другом окне — данные обновлены, повторите последнее действие","toast.cfg_save_failed":"Не удалось сохранить конфиг: {err}","toast.room_overlap":"Контур накладывается на комнату «{name}» — комнаты не должны накладываться","toast.contour_min_edges":"Чтобы замкнуть контур, сначала нарисуйте минимум две грани","toast.contour_cannot_close":"Контур нельзя замкнуть: он вырожден или пересекает сам себя","toast.merge_not_adjacent":"Объединять можно только комнаты с общей стеной","toast.rooms_merged":"Комнаты объединены в «{name}»","toast.split_pick_wall":"Начните разрез на стене комнаты","toast.split_bad_cut":"Разрез — от стены до стены внутри комнаты, без пересечения стен и самого себя","merge.header":"Объединение комнат","merge.hint":"У объединённой комнаты одно имя и одна зона. Вторая зона освобождается — её устройства уйдут с плана, пока их не заберёт другая комната.","merge.keep":"Оставить","merge.no_area":"без зоны","room.split_header":"Новая комната после разделения","toast.room_saved":"Комната сохранена ({n}). Устройств добавлено: {added}. Обведите следующую или выйдите из разметки.","toast.room_saved_no_area":"Комната сохранена ({n}, без зоны). Обведите следующую или выйдите из разметки.","toast.marker_needs_server":"Редактирование устройств доступно после переноса конфига на сервер","toast.virtual_name_required":"Укажите имя виртуального устройства","toast.marker_saved":"Устройство сохранено","toast.marker_removed":"Устройство удалено с плана","toast.integration_missing":"Интеграция House Plan не установлена — управление недоступно","toast.plan_formats":"Поддерживаются SVG, PNG, JPG, WebP","toast.plan_required":"Загрузите подложку — план этажа обязателен","toast.space_added_onboard":"Пространство добавлено. Обведите комнаты: кликайте по точкам сетки и замкните контур.","toast.space_added":"Пространство добавлено","toast.space_saved":"Пространство сохранено","toast.space_deleted":"Пространство удалено","toast.delete_failed":"Ошибка удаления: {err}","toast.error":"Ошибка: {err}","toast.file_failed":"Файл «{name}» не загружен: {err}","toast.files_attached":"Прикреплено файлов: {n}","err.unknown":"неизвестная ошибка","err.code":"код {code}","err.too_large":"файл больше {mb} МБ","err.bad_ext":"недопустимый тип (нужен PDF/изображение)","err.unauthorized":"нужны права администратора","editor.title":"Заголовок","editor.default_floor":"Пространство по умолчанию","editor.icon_size":"Размер иконок, % ширины плана","editor.show_temperature":"Показывать температуру","editor.live_states":"Живые состояния (вкл/выкл, открыто…)","editor.show_signal":"Показывать сигнал zigbee (LQI)","editor.language":"Язык интерфейса","editor.lang_auto":"Авто (профиль HA)","editor.lang_en":"English","editor.lang_ru":"Русский","title.icon_rules":"Правила иконок: какая MDI-иконка достаётся устройству по имени","rules.title":"Правила иконок","rules.hint":"Правила проверяются сверху вниз по строке «имя устройства + модель» (regex без учёта регистра); срабатывает первое совпадение. Если ничего не подошло — решает device class сущности, затем — иконка-заглушка.","rules.pattern_ph":"regex, напр. розетк|plug","rules.icon_ph":"mdi:power-socket-de","rules.add":"Добавить правило","rules.reset":"Сбросить к умолчаниям","rules.test_ph":"Проверьте имя устройства…","rules.invalid":"некорректный regex","rules.saved":"Правила иконок сохранены","btn.up":"Вверх","btn.down":"Вниз","tap.info":"Карточка устройства","tap.more_info":"Диалог HA (more-info)","tap.toggle":"Переключить состояние","marker.tap_label":"Действие по нажатию для этого устройства","tap.toggle_note":"Под селектором показано, какое состояние изменится. Защищённые устройства не переключаются с плана.","import.title":"Создать пространства из этажей HA","import.hint":"Home Assistant уже знает эти этажи. Отметьте, какие превратить в пространства плана — далее для каждого попросим картинку плана. Комнаты затем обводятся вручную по плану.","import.start":"Создать: {n}","import.manual":"Начать с нуля","import.progress":"Этаж {i} из {n}","import.done":"Пространства созданы. Обведите комнаты: кликайте по точкам сетки и замкните контур.","btn.skip":"Пропустить","space.scale_label":"Масштаб (размер клетки сетки)","space.scale_unit":"см на клетку","space.display_section":"Отображение","space.show_borders":"Всегда отображать границы комнат","space.show_names":"Отображать названия комнат (перетаскиваются)","space.room_color":"Цвет границ и названий","space.opacity":"Прозрачность","space.fill_label":"Заливка комнат","fill.none":"Нет","fill.lqi":"По силе зигби-сигнала","fill.light":"По освещению","fill.custom":"Свой цвет","space.custom_fill":"Цвет заливки","space.source_file":"У меня есть картинка плана","space.source_draw":"Нет подложки — нарисую комнаты вручную","space.orientation":"Холст","orient.landscape":"Альбомный","orient.portrait":"Портретный","orient.square":"Квадрат","fill.temp":"По температуре","space.temp_min":"Комфорт от","space.temp_max":"до","tip.temp_avg":"средняя температура:","space_card.button":"Перейти к пространству","space_card.not_found":"Пространство «{id}» не найдено","space_card.loading":"Загрузка…","continuity.restore_plan":"Восстанавливаем план…","continuity.restore_connection":"Восстанавливаем подключение к устройствам…","continuity.retry":"Повторить","editor.space":"Пространство","editor.show_button":"Показывать кнопку","editor.button_label":"Текст кнопки","editor.button_target":"Путь дашборда (куда вести)","marker.sub_entity":"сущность","title.general_settings":"Общие настройки","gs.title":"Общие настройки","gs.hint":"Цвета заливок действуют на все пространства; у каждого цвета своя прозрачность. Какой режим заливки использует пространство — задаётся в его диалоге.","gs.light_group":"Заливка: освещение","gs.light_on":"Свет включён","gs.light_off":"Весь свет выключен","gs.temp_group":"Заливка: температура","gs.temp_cold":"Холодно","gs.temp_ok":"Комфорт","gs.temp_hot":"Жарко","gs.lqi_group":"Заливка: зигби-сигнал","gs.lqi_low":"Слабый сигнал","gs.lqi_high":"Сильный сигнал","gs.reset":"Сбросить к умолчаниям","gs.saved":"Общие настройки сохранены","space.show_lqi":"Показывать зигби-сигнал (LQI) у устройств","space.hide_decor":"Скрыть декоративный слой","space.hide_decor_tip":"Линии, фигуры, надписи и мебель остаются на месте — их видно в редакторе подложки, но не на плане.","space.hide_openings":"Скрыть проёмы","space.hide_openings_tip":"Двери, окна и ворота не рисуются, но продолжают работать: свет проходит, солнце светит в окна, датчики открытия срабатывают. В редакторе плана проёмы видно всегда.","gs.light_none":"Нет источников света","mode.plan":"Редактор плана","mode.devices":"Редактор устройств","display.value":"Значение + состояние","marker.subarea":"без зоны, вручную","device.new":"Новое устройство — откройте его редактор, чтобы снять отметку","opening.unlock_action":"Открыть замок","opening.lock_action":"Закрыть замок","opening.lock_pending":"Выполняется…","title.close_editor":"Закрыть редактор (вернуться к просмотру)","devbar.add":"Добавить","devbar.show_all":"Скрытые и деактивированные","devbar.rules":"Правила иконок","space.roomcard_section":"В карточке комнаты:","space.label_temp":"Температура","space.label_hum":"Влажность","space.label_lqi":"Средний Zigbee-сигнал","space.label_light":"Свет вкл/выкл","roomcard.light_on":"Вкл","roomcard.light_off":"Выкл","roomcard.light_partial":"{on} из {total}","toast.split_pick_inside":"Промежуточные точки разреза — внутри комнаты","mode.decor":"Редактор подложки","decor.select":"Выбрать","decor.line":"Линия","decor.rect":"Прямоугольник","decor.ellipse":"Овал","decor.text":"Надпись","decor.erase":"Стереть","decor.erase_confirm_title":"Стереть объект?","decor.color":"Цвет","decor.width":"Толщина линии","decor.line_style":"Стиль линии","decor.line_style_solid":"Сплошная","decor.line_style_dashed":"Пунктирная","decor.fill":"Заливка","decor.fill_color":"Цвет заливки","decor.length":"Длина","decor.size":"Размер","decor.angle":"Угол поворота, °","decor.text_size":"Размер текста","decor.backdrop_properties":"Свойства картинки-подложки","decor.text_title":"Надпись","decor.object_title":"Редактирование: {kind}","decor.text_label":"Текст","decor.live_group":"Вставить переменную HA","decor.live_entity":"Сущность","decor.live_entity_ph":"выберите сущность","decor.live_attr":"Значение","decor.live_attr_ph":"выберите состояние или атрибут","decor.live_state":"Состояние","decor.backdrop":"Картинка-подложка","decor.backdrop_hint":"Тяните картинку — перемещение; угол — размер; верхнюю ручку — поворот. Shift меняет пропорции или снимает шаг угла.","decor.backdrop_reset":"Вернуть картинку","decor.backdrop_reset_done":"Картинка вернулась на своё место и в свой размер","marker.icon_auto":"Авто: {icon} (по правилам иконок; выберите свою, чтобы заменить)","marker.icon_pin_auto":"Закрепить","mode.plan_tip":"Редактор плана — геометрия дома: рисование и объединение/разделение комнат, привязка к зонам HA, двери, окна и ворота, карточки комнат, масштаб","mode.devices_tip":"Редактор устройств — всё про значки: перетаскивание, клик — настройка привязки/иконки/отображения, виртуальные устройства, правила иконок","mode.decor_tip":"Редактор подложки — чисто визуальный декор под планом: линии, прямоугольники, овалы и надписи, не реагирующие на клики","space.glow_enabled":"Свечение источников света","gs.glow_group":"Свечение источников света","gs.glow_base":"Темнота дома","gs.glow_light":"Цвет света по умолчанию / интенсивность","gs.wall_group":"Стены","gs.wall_fill":"Цвет заливки стен","gs.glow_radius":"Радиус свечения","gs.unit_m":"м","gs.unit_ft":"фут","marker.controls_label":"Управляет другими источниками света","marker.controls_hint":"При действии «Переключить» клик разом переключает все добавленные здесь источники. Собственная сущность маркера управляется напрямую и сюда не добавляется; чтобы считать её светом, включите флаг ниже.","marker.controls_filter":"Поиск ламп и выключателей…","info.controls":"Управляет","marker.glow_radius_label":"Радиус свечения","marker.glow_radius.help":"Задаёт радиус свечения в метрах или футах; пустое значение использует радиус из общих настроек.","marker.glow_radius.help.aria":"Подсказка: радиус свечения","markup.wallthick":"Толщина","markup.select":"Выбрать","title.markup_wallthick":"Толщина — клик по стене задаёт толщину. Пустое поле или ноль убирает толщину.","markup.hint_wallthick":"клик по стене · Esc закрывает без применения","wallthick.field":"Толщина","wallthick.unit_cm":"см","wallthick.unit_in":"дюйм","wallthick.apply_room":"Применить ко всем стенам комнаты","markup.draw_wall_title":"Толщина каждого нового сегмента стены комнаты (1–100 см). Общие стены сохраняют значение соседа.","toast.wallthick_pick":"Кликните по стене (не по открытой границе)","toast.wallthick_open":"У открытой границы нет толщины","toast.wallthick_set":"Толщина стены задана","toast.wallthick_cleared":"Толщина стены убрана","toast.physical_range":"Введите значение от 1 до {max} {unit}","toast.physical_angle":"Введите угол поворота от 0° до 90°, не включая 90°","toast.physical_limit":"В пространстве достигнут лимит объектов этого типа","markup.boundary_hint":"Выберите общую границу комнат или пунктирный участок","markup.boundary_hint_open":"Кликните начало открытого участка","markup.boundary_hint_second":"Укажите конец на выбранной границе · Esc — отмена","markup.boundary_hint_retry":"Укажите конец на выбранной границе","markup.boundary_hint_restore":"Кликните, чтобы восстановить стену","markup.boundary_hint_outer":"Открыть можно только общую границу двух комнат","markup.boundary_hint_blocked":"над границей находится перегородка, колонна или незавершённый контур","markup.boundary_hint_ambiguous":"Выберите участок чуть дальше от стыка","markup.boundary_cancel":"Отменить незавершённое действие «Граница»","toast.openwall_pick":"Инструмент работает с границами комнат","toast.openwall_shared_only":"Открыть можно только общую стену двух комнат","toast.openwall_short":"Слишком короткий отрезок — кликните дальше","toast.boundary_opened":"Участок границы открыт","toast.boundary_restored":"Стена на участке восстановлена","toast.boundary_same_edge":"Выберите конечную точку на выбранной стене","toast.boundary_ambiguous":"Выберите участок чуть дальше от стыка","toast.boundary_blocked":"Граница перекрыта независимой стеной. Сначала переместите или удалите её инструментом «Выбрать»","toast.delete_room_pick":"Кликните внутри комнаты, которую нужно удалить","toast.openwall_openings_removed":"Проёмы на виртуальном отрезке удалены","toast.openwall_opened":"Граница «{a}» ↔ «{b}» теперь открыта","toast.openwall_closed":"Граница «{a}» ↔ «{b}» снова закрыта","toast.opening_on_virtual":"Проёмы на виртуальной стене запрещены","marker.from_ha_option":"Выбрать из списка HA","marker.show_entities":"Отображать сущности","marker.show_entities_tip":"Добавляет в список не только устройства, но и все их сущности","marker.pick_ph":"Выберите устройство…","room.open_area":"Открыть зону в HA","view.volumetric":"Объёмный вид","view.flat":"Плоский вид","kiosk.title":"Размеры на этом экране","kiosk.hint":"Хранится только на этом устройстве — у каждого настенного планшета или ТВ свои удобные размеры.","kiosk.icon_scale":"Размер значков устройств","kiosk.font_scale":"Размер текста карточек комнат","editor.kiosk":"Режим настенного устройства (киоск)","editor.cycle":"Автосмена пространств каждые N секунд (киоск, 0 = выкл)","room.settings_title":"Настройки комнаты","room.settings_section":"Настройки комнаты (переопределяют пространство)","room.fill_label":"Заливка в ЭТОЙ комнате","fill.inherit":"Как у пространства","room.custom_fill_space":"Цвет пространства","room.custom_fill_own":"Цвет комнаты","room.temp_src_label":"Источник температуры","room.hum_src_label":"Источник влажности","room.src_average":"Средняя по датчикам комнаты (по умолчанию)","room.src_pick":"Конкретное устройство или сущность HA","room.src_ph":"Выберите источник…","toast.room_updated":"Комната обновлена","space.card_font":"Размер шрифта карточек комнат (всё пространство)","room.sizes_section":"Размеры шрифтов","room.name_scale":"Размер названия","room.label_scale":"Размер подписей","preview.room_name":"Гостиная","toast.cfg_reload_failed":"Не удалось перечитать план с сервера: {err}","room.settings_short":"Настройки комнаты","room.unnamed":"Комната без имени","marker.use_climate_temp":"Учитывать температуру устройства в комнате","marker.use_climate_temp_tip":"Добавляет current_temperature кондиционера или термостата в среднюю температуру комнаты. Внешний бейдж со значением настраивается отдельно ниже.","marker.value_badge_title":"Бейдж со значением","marker.value_badge.help":"Показывает одно выбранное значение рядом со значком. Не влияет на комнатные показатели, свет, Glow и действие по нажатию.","marker.value_badge.help.aria":"Подсказка: бейдж со значением устройства","marker.value_badge_enabled":"Отображать бейдж со значением","marker.value_badge_source":"Значение","marker.value_badge_source.help":"Выберите конкретное состояние, поддерживаемый атрибут или производное значение этого устройства.","marker.value_badge_source.help.aria":"Подсказка: источник значения бейджа","marker.value_badge_position":"Расположение","marker.value_badge_position.help":"Выбранная сторона сохраняется постоянно и не переворачивается автоматически у края плана.","marker.value_badge_position.help.aria":"Подсказка: расположение бейджа","marker.value_badge_right":"Справа","marker.value_badge_bottom":"Снизу","marker.value_badge_left":"Слева","marker.value_badge_top":"Сверху","marker.value_badge_empty":"У этого устройства нет доступных значений","marker.value_badge_static":"Статичный значок не показывает живые значения. Настройка сохранится.","marker.value_badge_missing":"Источник недоступен","marker.value_badge_missing_hint":"Сохранённый источник сейчас недоступен. Можно сохранить его, отключить бейдж или выбрать другой.","marker.value_badge_duplicate":"То же значение уже показано внутри значка.","marker.value_badge_state":"Состояние · {name}","marker.value_badge_marker_state":"Состояние источника света · {name}","marker.value_badge_lqi":"Качество Zigbee-сигнала (среднее)","marker.value_badge_attr_current_temperature":"Текущая температура · {name}","marker.value_badge_attr_temperature":"Целевая температура · {name}","marker.value_badge_attr_current_humidity":"Текущая влажность · {name}","marker.value_badge_attr_humidity":"Влажность · {name}","marker.value_badge_attr_current_position":"Положение · {name}","marker.value_badge_attr_percentage":"Скорость · {name}","marker.value_badge_attr_brightness":"Яркость · {name}","marker.value_badge_attr_volume_level":"Громкость · {name}","marker.value_badge_attr_battery_level":"Заряд батареи · {name}","marker.value_badge_attr_fan_speed":"Скорость вентилятора · {name}","marker.light_role_label":"Является источником света","marker.light_role.help":"Определяет, является ли сам маркер пространственным источником света. Привязанные выше лампы остаются независимыми источниками для комнаты.","marker.light_role.help.aria":"Подсказка: является ли устройство источником света","marker.light_role_auto_yes":"Автоматически (источник света)","marker.light_role_auto_no":"Автоматически (не источник света)","marker.light_role_always":"Всегда источник света","marker.light_role_never":"Никогда не источник света","marker.light_entity_label":"Ведущая сущность света","marker.light_entity.help":"Для составного устройства выберите сущность, состояние, цвет и яркость которой представляют этот источник на плане. Автоматический вариант сохраняет совместимость со старыми планами.","marker.light_entity.help.aria":"Подсказка: ведущая сущность света","marker.light_entity_auto":"Автоматически ({entity})","marker.light_entity_none":"нет управляемой сущности","marker.light_entity_missing":"Сохранённая сущность {entity} недоступна. Временно используется {fallback}; выбор восстановится, если сущность вернётся.","marker.glow_color_label":"Цвет и яркость свечения","marker.glow_mode.help":"Используйте данные источника, задайте только цвет или зафиксируйте цвет и яркость. Минимум — 1%; чтобы отключить источник, выберите «Никогда».","marker.glow_mode.help.aria":"Подсказка: цвет и яркость свечения","marker.glow_mode_auto":"Из источника","marker.glow_mode_color":"Задать цвет","marker.glow_mode_fixed":"Задать цвет и яркость","marker.glow_color":"Цвет свечения","marker.glow_brightness":"Яркость","marker.glow_disabled_never":"Настройки свечения недоступны: маркер явно исключён из источников света.","marker.glow_disabled_auto":"В автоматическом режиме у этого маркера не найден пространственный источник света.","marker.glow_disabled_no_entity":"Для пространственного источника нет активной управляемой сущности Home Assistant.","marker.glow_passive_hint":"У источника нет собственных данных Home Assistant. Задайте цвет и яркость вручную; радиус остаётся доступным.","marker.control_broken":"Сохранённый источник отсутствует или больше не отмечен как источник света","marker.control_missing_label":"Отсутствующий источник на плане","marker.control_passive":"пассивный источник","toast.marker_control_cycle":"Такая связь создаст циклическую цепочку управления светом.","toast.marker_binding_required":"Сначала выберите устройство Home Assistant, затем добавьте управляемый источник света.","confirm.unlock":"Открыть замок «{name}»?","toast.files_migrate_failed":"Не удалось перенести вложения к новой привязке, ссылки остались на старые файлы: {err}","space.pick_saved":"Уже загруженные","space.pick_saved_hint":"Планы, сохранённые на сервере, включая отцеплённые ранее","space.no_saved":"На сервере пока нет сохранённых планов.","space.loading":"Загрузка…","space.used_by":"используется: {list}","space.in_use":"План используется пространством — сначала отцепите его","btn.use":"Выбрать","confirm.delete_plan":"Удалить файл плана «{name}» с сервера? Действие необратимо.","toast.plans_list_failed":"Не удалось получить список планов: {err}","toast.plan_delete_failed":"Не удалось удалить план: {err}","marker.hide":"Скрыть","marker.hide_tip":"Устройство исчезнет с плана после сохранения, но продолжит участвовать в расчёте сигнала комнаты. Вернуть его можно через кнопку «Скрытые и деактивированные» в редакторе устройств.","marker.show":"Показать","marker.show_tip":"Устройство снова появится на плане после сохранения.","marker.hidden_ghost":"Устройство скрыто пользователем","marker.ha_disabled_device":"Устройство деактивировано в Home Assistant и скрыто с плана.","marker.ha_disabled_entity":"Сущность деактивирована в Home Assistant и скрыта с плана.","marker.ha_disabled_all_entities":"У устройства нет активных сущностей Home Assistant, поэтому оно скрыто с плана.","marker.ha_registry_limited":"Полный реестр Home Assistant недоступен этому пользователю. Неподтверждённый объект временно нельзя показывать или использовать.","marker.delete_tip":"Полностью удалить устройство с плана и из всех расчётов. Позже его можно добавить заново.","tap.run":"Запустить автоматизацию/скрипт/сцену","tap.cover":"Открыть/закрыть (шторы/жалюзи)","marker.run_target_label":"Что запускать","marker.run_search_ph":"Поиск: автоматизация, скрипт или сцена…","marker.run_target_gone":"Цель {id} не найдена — выберите заново","marker.tap_confirm":"Спрашивать подтверждение","marker.tap_confirm_tip":"Перед выполнением показать диалог подтверждения — защита от случайных нажатий.","marker.toggle_hint_single":"Цель: {name} ({id}).","marker.virtual_light_target":"Виртуальный свет: {name}.","marker.virtual_light_current":"Ручное состояние: {state} → {effect}.","marker.virtual_light_state_on":"включён","marker.virtual_light_state_off":"выключен","marker.toggle_hint_group":"Переключатся источники ({count}): {names}.","marker.toggle_hint_current":"Сейчас: {state} → {effect}.","marker.toggle_hint_group_current":"Сейчас включено {on} из {count} → {effect}.","marker.toggle_hint_skipped":"Не будут затронуты ({count}): {targets}.","marker.toggle_effect_turn_on":"включится","marker.toggle_effect_turn_off":"выключится","marker.toggle_effect_open":"откроется","marker.toggle_effect_close":"закроется","marker.toggle_effect_stop":"остановится","marker.toggle_effect_toggle":"состояние переключится","marker.toggle_skip_missing":"не найдено","marker.toggle_skip_ha_disabled":"деактивировано в HA","marker.toggle_skip_unavailable":"недоступно","marker.toggle_skip_unsupported":"переключение не поддерживается","marker.toggle_skip_secure":"заблокировано из соображений безопасности","marker.toggle_none_no_binding":"У устройства нет привязки, состояние переключать нечем. По нажатию ничего не произойдёт.","marker.toggle_none_no_actionable_entity":"У устройства нет состояния, которое можно переключить. По нажатию ничего не произойдёт.","marker.toggle_none_configured_targets_missing":"Настроенные цели сейчас недоступны. Собственная сущность устройства не будет подставлена вместо них.","marker.toggle_none_ha_disabled":"Цель деактивирована в Home Assistant и не может использоваться на плане.","marker.toggle_none_unavailable":"Цель сейчас недоступна. По нажатию ничего не произойдёт.","marker.toggle_none_unsupported":"Для этой сущности Home Assistant не предоставляет безопасного переключения. По нажатию ничего не произойдёт.","marker.toggle_none_secure":"Переключение замков, сигнализации и защитных ворот с плана заблокировано из соображений безопасности.","run.automation":"автоматизация","run.script":"скрипт","run.scene":"сцена","confirm.tap_run":"Запустить «{name}»?","confirm.tap_toggle":"Переключить «{name}»?","confirm.tap_cover":"Открыть/закрыть «{name}»?","toast.run_started":"Запущено: {name}","toast.run_target_missing":"Цель запуска не найдена — проверьте настройки устройства","toast.run_target_required":"Выберите автоматизацию, скрипт или сцену","toast.tap_target_changed":"Цель действия изменилась. Повторите попытку.","toast.virtual_light_toggle_failed":"Не удалось переключить виртуальный свет: {err}","toast.value_badge_source_required":"Выберите значение для бейджа","btn.run":"Выполнить","vac.section":"Робот-пылесос: живая позиция","vac.autocal":"Настроить автоматически","vac.live":"Живая позиция на плане","vac.trail":"Показывать путь робота","vac.cal_maps":"Откалиброваны карты: {maps}","vac.autocal_no_rooms":"Интеграция не отдаёт список комнат — откройте «Подогнать вручную»","vac.autocal_no_match":"Не совпали имена комнат (нужно ≥3 общих) — откройте «Подогнать вручную»","vac.autocal_done":"Готово: привязка по {rooms} комнатам. Запустите уборку и проверьте","vac.cal_need_pos":"Робот сейчас не отдаёт координаты — запустите уборку и поставьте на паузу","vac.cal_done":"Калибровка сохранена. Запустите уборку и проверьте","vac.cal_cancelled":"Калибровка отменена","vac.fit":"Подогнать вручную","vac.fit_hint":"Перетащите карту робота на место, растяните за уголки","vac.fit_rotate":"Повернуть 90°","vac.fit_mirror":"Отразить","vac.trail_never":"Не показывать никогда","vac.trail_cleaning":"Во время уборки","vac.trail_always":"Показывать всегда","gs.bg_group":"Фон сцены","gs.bg_color":"Цвет фона вокруг плана","gs.bg_default":"Как в теме","gs.bg_theme":"по умолчанию — из темы","gs.bg_mode":"Фон плана","gs.bg_static":"Статичный цвет","gs.bg_daynight":"Следует за Солнцем","gs.bg_daynight_hint":"Фон проходит рассвет, день, закат и ночь по данным sun.sun, а при их недоступности — по локальным часам. Компас для фона не нужен.","gs.sun_group":"Солнце","gs.sun_missing":"Сущность sun.sun не найдена — фон следует локальным часам, а оконные лучи недоступны.","gs.north":"Север на плане","gs.north_ph":"не задан","gs.north_hint":"Север нужен только для оконных лучей (шаг 1°, с Shift 15°); солнечный фон работает без него.","gs.north_clear":"Сбросить","gs.north_letter":"С","gs.sun_rays":"Солнце в окнах","gs.about_group":"О карточке","gs.about_version":"Houseplan Card v{v}","gs.about_github":"GitHub · документация и issues","gs.about_telegram":"Чат в Telegram","space.bg_color":"Цвет фона вокруг плана","space.bg_inherit":"Наследовать общий","space.bg_inherited":"наследуется из общих настроек","space.bg_mode":"Фон плана","space.north":"Север на плане (переопределение)","space.north_inherited":"наследуется: {v}","space.sun_rays":"Солнце в окнах","space.sun_inherit":"Наследовать общий","space.sun_on":"Вкл","space.sun_off":"Выкл","canvas.far_objects":"Объектов далеко от плана: {n}","canvas.show_far":"Показать","canvas.home_tip":"План там — нажмите, чтобы вписать","gs.grid_group":"Обслуживание планов","gs.grid_hint":"Обновляет модели данных, выравнивает элементы по сетке и объединяет лишние фрагменты стен. Перед записью будет показан точный отчёт.","gs.align_all":"Оптимизировать планы","gs.align_title":"Оптимизировать планы","gs.align_none":"Все планы уже используют актуальную и оптимальную модель данных.","gs.align_count":"Сдвинется элементов: {n} из {total}, максимум на {cm} см.","gs.align_where":"Наибольший сдвиг — в пространстве «{s}».","gs.align_turned":"Проёмов с исправлением угла: {n}.","gs.align_removed_drafts":"Схлопнувшиеся на сетке некорректные контуры удалены: {n}.","gs.optimize_changes":"Миграций модели: {m}; канонизировано планов: {c}; объединено отрезков реальных стен: {w}; виртуальных: {s}.","gs.optimize_glow_migration":"Старый Glow: пространств — {spaces} → без заливки данных + независимый Glow; комнат — {rooms} → наследуемая заливка + независимый Glow.","gs.align_warn":"Элементы, намеренно поставленные между узлами, будут сдвинуты. После операции доступна одна отмена — только до следующего изменения плана.","gs.align_run":"Оптимизировать","gs.align_done":"Планы оптимизированы: сдвинуто элементов — {n}, обслужено записей — {m}","gs.optimize_undo":"Отменить последнюю оптимизацию","gs.optimize_undone":"Последняя оптимизация отменена","decor.furniture":"Мебель","furn.title":"Библиотека мебели","furn.symbol":"Символ","furn.group_furniture":"Мебель","furn.group_appliance":"Техника","furn.group_sanitary":"Сантехника","furn.group_other":"Прочее","furn.width":"Ширина","furn.depth":"Глубина","furn.pick_hint":"Выберите символ и кликните по плану.","furn.place_hint":"Кликните по плану — предмет встанет к ближайшей стене. Shift — свободно.","furn.sym_sofa":"Диван","furn.sym_armchair":"Кресло","furn.sym_coffee_table":"Журнальный столик","furn.sym_table_dining":"Обеденный стол","furn.sym_table_round":"Круглый стол","furn.sym_chair":"Стул","furn.sym_desk":"Письменный стол","furn.sym_bed_double":"Двуспальная кровать","furn.sym_bed_single":"Односпальная кровать","furn.sym_nightstand":"Тумбочка","furn.sym_wardrobe":"Шкаф","furn.sym_bookshelf":"Стеллаж","furn.sym_fridge":"Холодильник","furn.sym_stove":"Плита","furn.sym_dishwasher":"Посудомоечная машина","furn.sym_washer":"Стиральная машина","furn.sym_dryer":"Сушильная машина","furn.sym_tv":"Телевизор","furn.sym_ac":"Кондиционер","furn.sym_water_heater":"Бойлер","furn.sym_toilet":"Унитаз","furn.sym_bathtub":"Ванна","furn.sym_shower":"Душ","furn.sym_sink":"Раковина","furn.sym_kitchen_sink":"Кухонная мойка","furn.sym_bidet":"Биде","furn.sym_stairs":"Лестница","furn.sym_fireplace":"Камин","furn.sym_plant":"Растение","furn.sym_rug":"Ковёр","common.yes":"Да","common.no":"Нет","vac.diag_source":"Источник","vac.diag_platform":"Интеграция","vac.diag_status":"Статус","vac.diag_position":"Позиция","vac.diag_rooms":"Комнаты","vac.diag_rooms_value":"{total} · совпало имён: {matched} · {readiness}","vac.autocal_ready":"автокалибровка доступна","vac.autocal_not_ready":"нужно 3 совпавших имени","vac.diag_path":"Путь интеграции","vac.diag_map":"ID карты","vac.source_none":"не выбран","vac.source_status_ok":"Готов","vac.source_status_missing":"Не найден","vac.source_status_disabled":"Деактивирован в Home Assistant","vac.source_status_unavailable":"Недоступен","vac.source_status_unverified":"Нельзя проверить с текущими правами","vac.source_status_unsupported":"Нет данных позиции","vac.source_status_none":"Нет источника","vac.source_banner_missing":"Сохранённый источник больше не существует. Он не заменён автоматически: выберите другой или восстановите его в Home Assistant.","vac.source_banner_disabled":"Сохранённый источник деактивирован в Home Assistant. Активируйте его там или выберите другой.","vac.source_banner_unverified":"Текущих прав Home Assistant недостаточно для проверки источника. Привязка сохранена и не будет заменена автоматически.","vac.choose_source":"Выбрать источник","vac.source_auto":"Автоматически","vac.source_auto_hint":"Использовать совместимую сущность этого устройства","vac.all_cameras":"Все камеры","vac.all_cameras_warn":"Камера может не отдавать данные робота. Выбирайте её только если это карта вашего пылесоса.","vac.all_cameras_empty":"Других сущностей камеры не найдено.","vac.platform_unknown":"интеграция неизвестна","vac.cap_position":"позиция","vac.cap_rooms_short":"комнаты","vac.cap_path":"путь","vac.cap_map":"ID карты","vac.cap_none":"данные робота не обнаружены","vac.xcme_hint":"Включите атрибуты Xiaomi Cloud Map Extractor:","vac.documentation":"Документация","vac.residual_title":"Проверьте автокалибровку","vac.residual_message":"Совпавшие комнаты расходятся максимум на {error}. Примените приблизительную калибровку, подгоните её вручную либо отмените без изменения сохранённых настроек.","vac.apply_proposal":"Применить","gs.backup_group":"Резервная копия и перенос","gs.backup_hint":"Скачайте переносимую JSON-копию или проверьте и импортируйте копию, созданную House Plan.","backup.export_open":"Экспорт","backup.import_open":"Импорт","backup.export_title":"Экспорт House Plan","backup.import_title":"Импорт House Plan","backup.export_hint":"Выберите, должна ли копия содержать всю конфигурацию House Plan или только текущее пространство.","backup.full":"Полная копия","backup.current_space":"Текущее пространство","backup.current_space_title":"Текущее пространство: {title}","backup.no_current_space":"Нет текущего пространства","backup.plan_only":"Только планировка","backup.plan_only_hint":"Сохранить комнаты, стены, проёмы, декор и позиции подписей комнат без устройств и привязок Home Assistant.","backup.plan_only_preview":"Файл содержит только планировку","backup.privacy_warning":"Архив сохраняет названия, идентификаторы Home Assistant и точные координаты. Внутренние планы и вложения указываются ссылками, но не вкладываются; текущие состояния и маршруты пылесосов не включаются.","backup.download":"Скачать JSON","backup.export_done":"Резервная копия скачана","backup.reading":"Проверяем резервную копию…","backup.revalidated":"После предпросмотра план изменился. Сводка обновлена — проверьте её и подтвердите ещё раз.","backup.error.unauthorized":"У вас нет прав на экспорт или импорт этого плана.","backup.error.not_ready":"House Plan ещё не готов. Повторите попытку через несколько секунд.","backup.error.too_large":"Размер резервной копии превышает 8 МиБ.","backup.error.invalid_json":"Выбранный файл не является корректным JSON.","backup.error.invalid_format":"Выбранный файл не является резервной копией House Plan.","backup.error.unsupported_export_version":"Эта версия формата резервной копии не поддерживается установленной версией.","backup.error.future_model":"Резервная копия создана в более новой версии модели данных House Plan.","backup.error.invalid_config":"Резервная копия содержит некорректную конфигурацию House Plan.","backup.error.invalid_layout":"Резервная копия содержит некорректные позиции объектов.","backup.error.invalid_content":"Резервная копия содержит некорректные или несогласованные ссылки на файлы.","backup.error.space_not_found":"Выбранное пространство больше не существует.","backup.error.capacity_exceeded":"Добавление этой копии превысит ограничения размера плана.","backup.error.preview_expired":"Время предпросмотра истекло. Выберите файл резервной копии заново.","backup.error.preview_owner_mismatch":"Этот предпросмотр принадлежит другому пользователю Home Assistant.","backup.error.conflict":"После предпросмотра план изменился. Проверьте обновлённую сводку.","backup.error.content_confirmation_required":"Подтвердите отсоединение недоступных локальных файлов.","backup.error.commit_failed":"Не удалось безопасно применить копию. Предыдущий план восстановлен или ожидает восстановления.","backup.error.missing_plan":"После предпросмотра исчез связанный файл плана. Проверьте копию ещё раз.","backup.error.missing_content":"После предпросмотра исчез связанный локальный файл. Проверьте копию ещё раз.","backup.error.marker_control_missing":"В резервной копии отсутствует связанный источник света на плане.","backup.error.marker_control_not_light":"Связанная цель больше не отмечена как источник света.","backup.error.marker_control_self":"Источник света не может управлять самим собой.","backup.error.marker_control_cycle":"Резервная копия содержит циклическую цепочку управления светом.","backup.error.duplicate_marker_control":"Резервная копия содержит повторяющуюся связь с источником света.","backup.error.no_backup":"Нет снимка импорта или оптимизации, который можно восстановить.","backup.same_source":"Создано на этом экземпляре Home Assistant","backup.foreign_source":"Создано на другом экземпляре Home Assistant","backup.created":"Создано: {value}","backup.versions":"Карточка {card}; интеграция {integration}; модель данных {model}","backup.count_spaces":"Пространств: {n}","backup.count_rooms":"Комнат: {n}","backup.count_walls":"Стен: {n}","backup.count_openings":"Проёмов: {n}","backup.count_decor":"Объектов декора: {n}","backup.count_markers":"Устройств: {n}","backup.count_layout":"Позиций: {n}","backup.bindings":"Привязки — устройства: {device}, сущности: {entity}, виртуальные: {virtual}; позиций без пространства: {legacy}","backup.binding_status":"Статус на целевом экземпляре — активных: {active}, деактивированных: {disabled}, отсутствующих: {missing}","backup.missing_areas":"На целевом экземпляре отсутствуют зоны: {areas}","backup.dropped_marker_links":"Связи с источниками света вне переносимого пространства пропущены: {n}.","backup.replace_warning":"Текущая конфигурация и расположение будут заменены. Загруженные файлы не удаляются. Одну отмену можно выполнить до следующего изменения плана.","backup.foreign_bookkeeping":"Служебные списки известных и новых устройств другого экземпляра импортированы не будут.","backup.final_name":"Название нового пространства","backup.target_settings":"Глобальные настройки целевого экземпляра не изменятся.","backup.duplicates":"Уже существующих привязок: {n}","backup.skip":"Пропустить повторяющиеся устройства","backup.virtual_copy":"Добавить безопасные статичные виртуальные копии","backup.content":"Связанное содержимое","backup.content_available":"доступно локально","backup.content_external":"внешняя ссылка","backup.content_detach_required":"будет отвязано","backup.confirm_detach":"Я понимаю, что недоступные внутренние планы и вложения будут отвязаны от импортируемой конфигурации.","backup.replace":"Заменить","backup.add":"Добавить пространство","backup.space_done":"Пространство импортировано: комнат — {rooms}, устройств — {markers}","backup.full_done":"Копия восстановлена: пространств — {spaces}, комнат — {rooms}, устройств — {markers}","backup.undo_import":"Отменить последний полный импорт","backup.import_undone":"Полный импорт отменён"}};function Uo(t,e){if(e&&e in Wo)return e;return(t?.locale?.language||t?.language||"en").toLowerCase().startsWith("ru")?"ru":"en"}function Go(t,e,i){return Ki(Wo[t][e]??jo[e]??e,i)}function Vo(t,e){const i=Wo[t][e]??Wo.en[e];return"string"==typeof i&&i.trim().length>0}class Ko extends ht{constructor(){super(...arguments),this.deviceName="",this._metadataConnection=null,this._demoUntil=0,this._demoTimer=0,this._demoGeneration=1,this._demoKind=null,this._reducedMotion=!1,this._onMotionChange=t=>{this._reducedMotion=t.matches,this.requestUpdate()},this._startShortDemo=()=>{const t=this.presentation;t&&"icon_ripple"===t.display&&"none"===t.pulse.kind&&(this._demoGeneration++,this._demoKind="short",this._demoUntil=Date.now()+3300,window.clearTimeout(this._demoTimer),this._demoTimer=window.setTimeout(()=>{this._demoUntil=0,this._demoTimer=0,this._demoKind=null,this.requestUpdate()},3330),this.requestUpdate())},this._toggleContinuousDemo=()=>{const t=this.presentation;t&&"icon_ripple"===t.display&&"none"===t.pulse.kind&&("continuous"===this._demoKind?this._clearDemo():(this._clearDemo(),this._demoGeneration++,this._demoKind="continuous",this._demoUntil=Number.POSITIVE_INFINITY),this.requestUpdate())}}connectedCallback(){super.connectedCallback(),this._motionMedia=window.matchMedia?.("(prefers-reduced-motion: reduce)"),this._reducedMotion=!!this._motionMedia?.matches,this._motionMedia?.addEventListener?.("change",this._onMotionChange),this._ensureMetadata()}disconnectedCallback(){this._metadataRelease?.(),this._metadataRelease=void 0,this._metadataConnection=null,this._clearDemo(),this._motionMedia?.removeEventListener?.("change",this._onMotionChange),this._motionMedia=void 0,super.disconnectedCallback()}willUpdate(t){t.has("hass")&&this._ensureMetadata();const e=this.presentation,i=t.get("presentation");t.has("presentation")&&i?.binding!==e?.binding&&this._clearDemo(),e&&this._demoKind&&("icon_ripple"!==e.display||"none"!==e.pulse.kind)&&this._clearDemo()}_ensureMetadata(){const t=this.hass?.connection||null;t&&t!==this._metadataConnection&&(this._metadataRelease?.(),this._metadataConnection=t,this._metadataRelease=Oo(this.hass,()=>this.requestUpdate()))}_clearDemo(){window.clearTimeout(this._demoTimer),this._demoTimer=0,this._demoUntil=0,this._demoKind=null}get _lang(){return Uo(this.hass)}_t(t,e){return Go(this._lang,t,e)}_providerText(t){return"houseplan"===t.domain?this._t("marker.preview.virtual_provider"):t.label.toLowerCase()===t.domain.toLowerCase()?t.domain:`${t.label} (${t.domain})`}_providers(){return this.presentation&&this.registry?function(t,e,i,s=No(t)){if("virtual"===e)return[{domain:"houseplan",label:"House Plan",confidence:"registry-owner"}];const n=e.indexOf(":");if(n<1)return[];const o=e.slice(0,n),r=e.slice(n+1);if("entity"===o){const e=Bo(t,r,i,s);return e?[e]:[]}if("device"!==o)return[];const a=i.devices?.[r]||t?.devices?.[r];if(!a)return[];const l=[a.config_entry_id,...Array.isArray(a.config_entries)?a.config_entries:[]].filter(t=>"string"==typeof t&&!!t).map(e=>{const i=s.configEntries[e];return Ho(t,i?.domain,"registry-owner",s,e)});if(l.some(Boolean))return qo(l);const c=[];for(const[e,n]of Object.entries(i.entities||{}))n?.device_id===r&&null==n?.disabled_by&&c.push(Bo(t,e,i,s));return c.some(Boolean)?qo(c):qo((Array.isArray(a.identifiers)?a.identifiers:[]).map(e=>{const i=Array.isArray(e)?e[0]:null;return Ho(t,i,"identifier-fallback",s)}))}(this.hass,this.presentation.binding,this.registry,No(this.hass)):[]}_providerSummary(t){if(!t.length)return this._t("marker.preview.unknown_provider");const e=t.slice(0,2).map(t=>this._providerText(t)),i=t.length-e.length;return e.join(", ")+(i>0?` · ${this._t("marker.preview.more_sources",{n:i})}`:"")}_sourceSummary(t){const e=t.visualSources;return e.length?1===e.length?`${e[0].name} · ${e[0].eid} · ${this._sourceProvider(e[0].eid)}`:2===e.length?e.map(t=>t.name).join(" · "):this._t("marker.preview.multiple_sources",{n:e.length}):this._t("marker.preview.no_source")}_stateSummary(t){if(!t.visualSources.length)return this._t("marker.preview.no_state");if(1===t.visualSources.length)return t.visualSources[0].stateText||this._t("marker.preview.no_state");const e=[...new Set(t.visualSources.map(t=>t.stateText).filter(Boolean))];return 1===e.length?e[0]:this._t("marker.preview.mixed_states")}_reason(t){return this._t(`marker.preview.reason.${t}`)}_sourceProvider(t){if(!this.registry)return this._t("marker.preview.unknown_provider");const e=Bo(this.hass,t,this.registry,No(this.hass));return e?this._providerText(e):this._t("marker.preview.unknown_provider")}render(){const t=this.presentation;if(!t)return G;const e=!!this._demoKind&&this._demoUntil>Date.now()&&"icon_ripple"===t.display&&"none"===t.pulse.kind,i=e?(()=>{const e=function(t,e,i,s,n=null){return{kind:e,reason:"short"===e?"event":"running",generation:Math.max(1,Math.trunc(i)),expiresAt:"short"===e?n:null,color:s?null:t.color,diameterScale:s?1:t.diameterScale,animated:!s,reducedMotionIndicator:s?"dot":"none"}}(t.pulse,this._demoKind,this._demoGeneration,this._reducedMotion,"short"===this._demoKind?this._demoUntil:null);return{...t,pulse:e,classes:[...t.classes.filter(t=>!t.startsWith("pulse-")),`pulse-${e.kind}`,...e.generation%2==0?["pulse-gen2"]:[]]}})():t,s=this._providers(),n="none"!==t.pulse.kind,o=i.scale*(i.pulse.animated?i.pulse.diameterScale:1);let r=-o/2,a=o/2,l=-o/2,c=o/2;if(i.valueBadge){const t=i.scale*Math.min(4,Math.max(.9,.29*i.valueBadge.text.length+.28)),e=.7*i.scale,s=.6*i.scale;"right"===i.valueBadge.position&&(a=Math.max(a,s+t)),"left"===i.valueBadge.position&&(r=Math.min(r,-s-t)),"top"===i.valueBadge.position&&(l=Math.min(l,-s-e)),"bottom"===i.valueBadge.position&&(c=Math.max(c,s+e))}if(null!=i.lqiText){const t=i.scale*("bottom"===i.valueBadge?.position?1.55:.95);c=Math.max(c,t)}const h=Math.min(1,2.35/Math.max(1,a-r),2.35/Math.max(1,c-l)),d=Math.round(100*h),u=[`left:calc(50% - ${54*((r+a)/2)*h}px)`,`top:calc(50% - ${54*((l+c)/2)*h}px)`,h<1?`transform:scale(${h})`:""].filter(Boolean).join(";"),p=["dev",...i.classes,"virtual"===i.binding?"virtual":"",i.haDisabled?"ghost ha-disabled":"",null!=i.valueText?"valonly":""].filter(Boolean).join(" "),_=Ro(i).join(";"),m=e?this._t("continuous"===this._demoKind?"marker.preview.demo_continuous_notice":"marker.preview.demo_short_notice"):this._reason(t.explanation.reason),g=[this.deviceName,this._providerSummary(s),this._stateSummary(t),m,t.valueFullText||"",fo(t.valueBadge)].filter(Boolean).join(". ");return j`
${this._t("marker.preview.title")} @@ -4293,7 +4297,7 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="da33c4f75cf2469e330b33f11fd5fa04d368 ${r} - `}const Xu={window:120,door:90,gate:300};function Zu(t){return Xu[t]}function Ju(t,e){const i=[t[0],t[1]],s=[e[0],e[1]];return function(t,e){return t[0]-e[0]||t[1]-e[1]}(i,s)<=0?{a:i,b:s}:{a:s,b:i}}function Qu(t){const e=new Map;return t.forEach((t,i)=>{if(!t.kind||t.open)return;const s=Ju(t.a,t.b);if(Math.hypot(s.b[0]-s.a[0],s.b[1]-s.a[1])<=1e-9)return;const n=function(t,e){const i=t=>(Math.abs(t)<=5e-10?0:Math.round(1e6*t)/1e6).toFixed(6);return`${i(t[0])},${i(t[1])}>${i(e[0])},${i(e[1])}`}(s.a,s.b),o=e.get(n);if(o)return o.physicalHalfWidth=Math.max(o.physicalHalfWidth,t.half||0),void(o.sourceOrder=Math.min(o.sourceOrder,i));e.set(n,{segmentKey:n,a:s.a,b:s.b,physicalHalfWidth:Math.max(0,t.half||0),sourceOrder:i})}),[...e.values()]}function tp(t,e){if(Math.abs(t.distance-e.distance)>1e-9)return t.distance-e.distance;if(Math.abs(t.perpendicular-e.perpendicular)>1e-9)return t.perpendicular-e.perpendicular;return(t.target.segmentKeye.target.segmentKey?1:0)||t.target.sourceOrder-e.target.sourceOrder}function ep(t){const e=Qu(t.intervals).map(e=>{const i=function(t,e){const i=e.b[0]-e.a[0],s=e.b[1]-e.a[1],n=Math.hypot(i,s),o=i/n,r=s/n,a=(t[0]-e.a[0])*o+(t[1]-e.a[1])*r,l=Math.max(0,Math.min(n,a)),c=e.a[0]+l*o,h=e.a[1]+l*r;return{along:l,length:n,x:c,y:h,distance:Math.hypot(t[0]-c,t[1]-h),perpendicular:Math.abs((t[0]-e.a[0])*r-(t[1]-e.a[1])*o)}}(t.pointer,e);return{target:e,...i,envelope:Math.max(t.baseTolerance,e.physicalHalfWidth+t.bodyPointerPadding)}}).filter(t=>t.distance<=t.envelope+1e-9).filter(e=>!function(t,e,i,s,n){const o=e.b[0]-e.a[0],r=e.b[1]-e.a[1],a=Math.hypot(o,r);if(!(a>1e-9))return!1;const l=o/a,c=r/a;for(const o of i){if(!o.open&&o.kind)continue;const i=Ju(o.a,o.b),r=i.b[0]-i.a[0],a=i.b[1]-i.a[1],h=Math.hypot(r,a);if(!(h>1e-9))continue;const d=r/h,u=a/h;if(Math.abs(l*u-c*d)>1e-6)continue;if(Math.abs((i.a[0]-e.a[0])*c-(i.a[1]-e.a[1])*l)>n)continue;const p=(t[0]-i.a[0])*d+(t[1]-i.a[1])*u,_=1e-9;if(!(p<=_||p>=h-_)&&Math.abs((t[0]-i.a[0])*u-(t[1]-i.a[1])*d)<=s+1e-9)return!0}return!1}(t.pointer,e.target,t.intervals,e.envelope,Math.max(1e-9,Math.min(t.baseTolerance,.04*t.gridStep)))).sort(tp),i=e[0];if(!i)return null;const{target:s,length:n}=i,o=s.b[0]-s.a[0],r=s.b[1]-s.a[1],a=o/n,l=r/n,c=Math.min(Math.max(0,t.renderedLength)/2,n/2);let h=i.along;const d=Math.max(t.gridStep,1e-9),u=n/2;h=Math.abs(h-u)<=d/2?u:Math.round(h/d)*d,h=Math.max(c,Math.min(n-c,h));const p=s.a[0]+a*h,_=s.a[1]+l*h,m=Math.max(0,t.renderedLength)/2,g=h-m,f=h+m,v=Math.max(0,g),y=Math.max(0,n-f),b=[s.a[0]+a*(g-v/2),s.a[1]+l*(g-v/2)],w=[s.a[0]+a*(f+y/2),s.a[1]+l*(f+y/2)];let k=180*Math.atan2(r,o)/Math.PI;k>=90?k-=180:k<-90&&(k+=180);const $=Math.abs(h-u)<=1e-9;return{presetRevision:t.preset.revision,geometryRevision:t.geometryRevision,pointer:[t.pointer[0],t.pointer[1]],type:t.preset.type,lengthCm:t.preset.lengthCm,flipH:t.preset.flipH,flipV:t.preset.flipV,x:p,y:_,angle:k,renderedLength:t.renderedLength,target:s,measure:{labels:[{distance:v,midpoint:b},{distance:y,midpoint:w}],guide:$?{x:p,y:_,angle:k}:null}}}function ip(t,e,i,s,n=1e-6){return t.presetRevision===i&&t.geometryRevision===s&&Math.hypot(t.pointer[0]-e[0],t.pointer[1]-e[1])<=n}const sp="1.64.0",np={badge:"display.badge",icon_ripple:"display.icon_ripple",value:"display.value",static_icon:"display.static_icon"},op={badge:"marker.display_hint_badge",icon_ripple:"marker.display_hint_icon_ripple",value:"marker.display_hint_value",static_icon:"marker.display_hint_static_icon"},rp=2e3,ap=1e3,lp=1500,cp=t=>{const e=String(t??"").trim().replace(",",".");if(!/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)$/.test(e))return null;const i=Number(e);return Number.isFinite(i)?i:null},hp=(t,e)=>{if(!t.has(e))return{hit:!1};const i=t.get(e);return t.delete(e),t.set(e,i),{hit:!0,value:i}},dp=(t,e,i,s)=>{for(t.delete(e),t.set(e,i);t.size>s;){const e=t.keys().next().value;if(void 0===e)break;t.delete(e)}},up=new Map;let pp=0;const _p=t=>`${window.innerWidth}x${window.innerHeight}|${location.pathname}|${JSON.stringify(t??{})}`;let mp=1e4;const gp="houseplan_card_layout_v1",fp="houseplan_card_cfg_v1",vp="houseplan_card_zoom_v1",yp="houseplan_card_nav_v1",bp="houseplan_card_kiosk_v1",wp="houseplan_card_view_v1",kp=1e3,$p=[[0,1],[45,.88],[70,.62],[86,.32],[100,0]],xp=(t,e)=>{const i=Math.min(t.x,e.x),s=Math.min(t.y,e.y);return{x:i,y:s,w:Math.max(t.x+t.w,e.x+e.w)-i,h:Math.max(t.y+t.h,e.y+e.h)-s}},Sp=new Set(["select","draw","partition","column","merge","split","resize","opening","boundary","wallthick","delroom"]),Mp=(t,e,i)=>{const s=new Event(e,{bubbles:!0,composed:!0});s.detail=i??{},t.dispatchEvent(s)},Cp=t=>{history.pushState(null,"",t),Mp(window,"location-changed",{replace:!1})},Dp=(t,e)=>{let i,s=null;const n=(...n)=>{clearTimeout(i),s=n,i=window.setTimeout(()=>{i=void 0;const e=s;s=null,e&&t(...e)},e)};return n.flush=()=>{if(void 0===i)return;clearTimeout(i),i=void 0;const e=s;s=null,e&&t(...e)},n.pending=()=>void 0!==i,n},Tp=t=>{try{t.target?.setPointerCapture?.(t.pointerId)}catch{}};class Pp extends ht{constructor(){super(...arguments),this._space="f1",this._layout={},this._serverStorage=!1,this._loadOk=!1,this._serverCanWrite=null,this._loading=!1,this._loadTries=0,this._serverCfg=null,this._cfgRev=0,this._cfgContentFingerprint="",this._unsubCfg=null,this._unsubLayout=null,this._unsubVirtual=null,this._liveSyncAttempt=null,this._liveSyncGeneration=0,this._liveSyncConnection=null,this._layoutRev=0,this._layoutContentFingerprint="",this._virtualLights=rn(null),this._canOptimizeUndo=!1,this._undoKind=null,this._devices=[],this._regSignature="",this._defPos={},this._newSyncKey="",this._tip=null,this._hoverRoom=null,this._selId=null,this._toast="",this._mode="view",this._pendingNavMode=null,this._decorTool="select",this._decorStyle={...jr},this._decorDraft=null,this._decorMove=null,this._decorSel=null,this._decorEraseConfirm=null,this._decorTextDialog=null,this._decorShapeDialog=null,this._backdropDialog=null,this._decorTextSelection={start:0,end:0},this._furnPalette=null,this._dtBox=null,this._dtDrag=null,this._bdDrag=null,this._slide="",this._reducedMotion=!1,this._onMotionChange=t=>{this._reducedMotion=t.matches,t.matches&&this._modeTransitionPreparing?this._modeTransitionForceAtomic=!0:t.matches&&this._modeTransition.active&&this._cancelModeTransition(!0),this.requestUpdate()},this._editorChromeMode="plan",this._modeTransitionVisual=null,this._modeTransitionPreparing=!1,this._modeTransitionForceAtomic=!1,this._modeTransitionRequest=0,this._modeTransitionTargetZoom=1,this._modeTransitionEditorCamera=null,this._modeTransition=new hu({frame:t=>this._applyModeTransitionFrame(t),settled:t=>this._settleModeTransition(t)}),this._editorSecondary=new Od({root:()=>this.renderRoot,requestUpdate:()=>this.requestUpdate(),updateComplete:()=>this.updateComplete,clearTip:()=>{this._tip=null}}),this._editorSecondaryCopy={groupActive:(t,e)=>this._t("editor.group_active",{group:t,item:e}),openGroup:t=>this._t("editor.open_group",{group:t}),disabledAction:(t,e)=>this._t("editor.disabled_action",{action:t,reason:e})},this._tool="draw",this._geometryHistory=new Zd(50),this._wallDialog=null,this._drawWallField=null,this._activeDraftId=null,this._resumeDraftBySpace={},this._physicalSel=null,this._physicalDialog=null,this._physicalDrag=null,this._physicalRotate=null,this._physicalLastTap=null,this._physicalPickCycle=null,this._wallUnionCache=null,this._isoGeometryCache=new Map,this._isoFallback=new Set,this._openingTunnelCache=null,this._openingWallIndexCache=new Map,this._openingPlacementIntervalsCache=null,this._planSnapGeometryCache=null,this._physicalBodiesCache=null,this._cleanFloorCache=new Map,this._glowClipCache=new Map,this._lightBarrierCache=null,this._glowFeatherUnits=null,this._glowRenderedSources=new Map,this._glowLastAppearance=new Map,this._glowEnteringSources=new Set,this._glowEnterRafs=new Map,this._glowFadeTimers=new Map,this._glowFeatherSuspendUntil=0,this._glowFeatherResumeTimer=0,this._glowSourceSeq=0,this._glowScreenBlend=!1,this._duplicateColumnId=null,this._duplicateColumnTimer=0,this._rszSel=null,this._rszDrag=null,this._rszPreview=null,this._rszLive=null,this._path=[],this._cursorPt=null,this._planSnapHover=null,this._mergeSel=null,this._openingPreset=null,this._openingPresetRevision=0,this._openingHoverCandidate=null,this._openingDialog=null,this._openingInfo=null,this._opDrag=null,this._opMeasure=null,this._mergeDialog=null,this._openWallAnchor=null,this._boundaryPointerType="mouse",this._boundaryRestoreGuard=null,this._boundaryTargetMemo=null,this._boundaryPreviewMemo=null,this._splitSel=null,this._pendingSplit=null,this._areaSel="",this._nameSel="",this._roomDialog=!1,this._roomEditId=null,this._roomFill="",this._roomCustomFill=null,this._roomTempSrc="",this._roomHumSrc="",this._roomSrcOpen=null,this._roomSrcFilter="",this._roomNameScale=1,this._roomLabelScale=1,this._zoom=1,this._view=null,this._zoomBySpace={},this._viewModeSnap=null,this._pointers=new Map,this._panStart=null,this._panLock=null,this._pinchStart=null,this._suppressClick=!1,this._touchContacts=new Map,this._touchSequenceMultitouch=!1,this._touchClickBlockUntil=0,this._connectedPath="",this._routeDepartureHandled=!1,this._onLocationChanged=()=>{this._connectedPath&&location.pathname!==this._connectedPath?this._leaveCardRoute():location.pathname===this._connectedPath&&(this._routeDepartureHandled=!1)},this._touchGestureGuard={capture:!0,handleEvent:t=>this._guardTouchGesture(t)},this._hdrH=118,this._booting=!0,this._bootFading=!1,this._bootSettling=!1,this._bootSettleRaf=0,this._bootLastH=-1,this._bootStart=0,this._bootLastChange=0,this._bootSoft=!1,this._tapConfirm=null,this._onboardingShown=!1,this._rulesDialog=null,this._alignDialog=null,this._settingsDialog=null,this._backupExportDialog=null,this._backupImportDialog=null,this._sunRaysCache=null,this._dayCycleTimer=0,this._dayCycleClockKey="",this._compassDrag=!1,this._importDialog=null,this._importQueue=[],this._importTotal=0,this._rulesCompiledSrc="",this._infoCard=null,this._nativeMoreInfoEntity=null,this._markerDialog=null,this._spaceDialog=null,this._keyHandler=t=>this._onKey(t),this._warmVp=null,this._warmVpArmed=!1,this._warmLongReturn=!1,this._warmRevivePending=!1,this._warmGen=++pp,this._warmKey=null,this._warmSlot=null,this._hashApplied=!1,this._navApplied=!1,this._labs={active:Object.freeze([]),space:""},this._viewPreference={},this._renderProjection="flat",this._kioskScale={icon:1,font:1},this._kioskDialog=!1,this._activityRt=new Map,this._vacRt=new Map,this._vacViewKey="",this._vacLastView=null,this._vacRaf=0,this._vacSrvTrails={},this._vacJumpOnce=!1,this._continuity=this._newContinuityController(),this._continuityHistory=[],this._continuityEpoch=0,this._continuityDataReady=!0,this._continuityPaintToken=-1,this._continuityDisposed=!1,this._renderSnapshotAt=Date.now(),this._hassSequence=0,this._visibleDeviceSnapshot=null,this._candidateDeviceSnapshot=null,this._stagedDeviceSnapshotToken=-1,this._capturedSnapshotSequence=-1,this._capturedSnapshotDevices=null,this._capturedSnapshotLayout=null,this._capturedSnapshotActivity="",this._capturedSnapshotConfigEpoch=-1,this._capturedSnapshotVirtual="",this._lastValidStageSize=null,this._pendingRefitSize=null,this._refitRaf=0,this._pageVisibility=t=>{if(this._continuity.visibility(t),this._dayCycleVisibility(t),"hidden"!==t.kind){if(this._vacJumpOnce=!0,!t.long){const t=Date.now();let e=!1;for(const i of this._activityRt.values())!i.flashKind||(i.expiresAt||i.flashTs+Cd)>t||(i.flashTs=0,i.flashKind=null,i.expiresAt=0,e=!0);return void(e&&this.requestUpdate())}Date.now()-this._renderSnapshotAt>1e3&&this._continuity.note("device-snapshot-stale"),this._continuityDataReady=!1,this._continuityPaintToken=-1,this._resumeSettling=!0,this._loading?this.requestUpdate():this._loadFromServer()}else this._modeTransitionPreparing?this._modeTransitionForceAtomic=!0:this._cancelModeTransition(!0)},this._resumeSettling=!1,this._viewportInvalidAt=0,this._vacFit=null,this._vacAllCamerasFor=null,this._vacAllCameraCache=null,this._vacCalConfirm=null,this._kioskDots=!1,this._cyclePausedUntil=0,this._swipeStart=null,this._lastTap=0,this._onLabsSnapshot=t=>{const e=this._effectiveProjection();this._labs=t;const i=this._effectiveProjection();this._convertProjectionView(e,i),this.requestUpdate()},this._onHashChange=()=>{const t=this._hashSpace();t&&this._model.find(e=>e.id===t)&&t!==this._space&&(this._activeDraftId&&(this._resumeDraftBySpace[this._space]=this._activeDraftId),this._space=t,this._selId=null,this._path=[],this._clearPlanSnapHover(),this._clearOpeningPlacement(!0),this._tool="draw",this._openWallAnchor=null,this._boundaryRestoreGuard=null,this._activeDraftId=null,this._draftSegmentCms=[],this._closingWallCm=null,"plan"===this._mode&&"draw"===this._tool&&this._resumeLastDraft(),this._restoreZoom(),this.requestUpdate())},this._drag=null,this._rlResize=null,this._holdFired=!1,this._retryContinuity=()=>{this._continuityDataReady=!1,this._continuityPaintToken=-1,this._continuity.retry(this._continuity.recoveryReason||"plan"),this._loading||this._loadFromServer()},this._cfgEpoch=0,this._modelCache=null,this._decorSnapCache=null,this._markerPreviewMemo=null,this._markerPreviewDevicesMemo=null,this._showHidden=!1,this._connHooked=null,this._connectionWasLost=!1,this._haRegistryConnection=null,this._haRegistryRev=-1,this._haBindingCacheKey="",this._planHassMemo=null,this._onHaRegistryUpdate=()=>{const t=xn(this.hass);t.revision===this._haRegistryRev&&this._devices.length||(this._haRegistryRev=t.revision,this._planHassMemo=null,this._regSignature="",this._maybeRebuildDevices(),this.requestUpdate())},this._onConnReady=()=>{if(this._loadTries=0,clearTimeout(this._loadRetryTimer),this._loadRetryTimer=void 0,function(t){const e=vn(t);e&&(wn(t,e),kn(t,e))}(this.hass),this._saveConfigDebounced.pending()&&this._saveConfigDebounced.flush(),this._cfgWriting)return clearTimeout(this._reloadRetry),void(this._reloadRetry=window.setTimeout(this._onConnReady,400));!this._connectionWasLost&&this._continuity.hasCompleteFrame?this._beginContinuityCandidate("connection-ready",!1,"plan"):(this._continuityDataReady=!1,this._continuityPaintToken=-1),this._loading||this._loadFromServer()},this._onConnLost=()=>{this._booting&&!this._continuity.hasCompleteFrame||(this._connectionWasLost=!0,this._continuityDataReady=!1,this._continuityPaintToken=-1,this._continuity.connectionLost())},this._signer=new Wc(()=>this.requestUpdate()),this._dirtyPos=new Set,this._sentPos=new Map,this._persistLayout=Dp(()=>{if(this._serverStorage){const t=[...this._dirtyPos];this._dirtyPos.clear();for(const e of t){const t=this._layout[e];t&&(this._sentPos.set(e,t),this.hass.callWS({type:"houseplan/layout/update",device_id:e,pos:t}).then(t=>this._noteLayoutRev(t)).catch(t=>this._showToast(this._t("toast.pos_save_failed",{err:this._errText(t)}))).finally(()=>{this._sentPos.get(e)===t&&this._sentPos.delete(e)}))}this._cacheSnapshot()}else localStorage.setItem(gp,JSON.stringify(this._layout))},600),this._frame=null,this._showFar=!1,this._writesPending=0,this._writeChain=Promise.resolve(),this._undoGeometry=()=>{if(this._cancelBoundaryAnchor())return;if(this._physicalDrag||this._physicalRotate)return void this._cancelPhysicalGesture();if(this._decorDraft)return this._decorDraft=null,void this.requestUpdate();if(this._decorMove||this._dtDrag||this._bdDrag)return void this._cancelDecorGesture();if(this._rszDrag)return void this._rszCancelDrag();const t=this._geometryHistory.undo();t&&(this._applyGeometryState(t.before)?this._showToast(this._t("history.undone",{name:t.name})):this._geometryHistory.clear())},this._redoGeometry=()=>{if(this._cancelBoundaryAnchor())return;if(this._physicalDrag||this._physicalRotate)return void this._cancelPhysicalGesture();if(this._decorDraft)return this._decorDraft=null,void this.requestUpdate();if(this._decorMove||this._dtDrag||this._bdDrag)return void this._cancelDecorGesture();if(this._rszDrag)return void this._rszCancelDrag();const t=this._geometryHistory.redo();t&&(this._applyGeometryState(t.after)?this._showToast(this._t("history.redone",{name:t.name})):this._geometryHistory.clear())},this._saveConfigDebounced=Dp(()=>{this._serverCfg&&this._writeConfig().catch(t=>{"conflict"===t?.code?(this._showToast(this._t("toast.conflict")),this._cancelPath(),this._reloadConfigOnly(!0)):this._showToast(this._t("toast.cfg_save_failed",{err:this._errText(t)}))})},500),this._draftSegmentCms=[],this._closingWallCm=null,this._savePhysicalDialog=()=>{const t=this._physicalDialog,e=this._curSpaceCfg;if(!t||!e)return;const i=cp(t.cm);if(null==i)return void this._showPhysicalRange("column"===t.kind?ll:100);const s=this._imperial?2.54*i:i,n="column"===t.kind?ll:100;if(!Number.isFinite(s)||s<1||s>n)return void this._showPhysicalRange(n);if("column"===t.kind){const e=this._spaceModel().wall_columns.find(e=>e.id===t.id);if(!e)return;const i=cp(t.angle||"0");if("circle"!==t.shape&&(null==i||i<0||i>=90))return void this._showToast(this._t("toast.physical_angle"));const n="circle"===t.shape?{id:t.id,shape:"circle",center:e.center,cm:s}:{id:t.id,shape:"square",center:e.center,cm:s,angle:i};if(this._spaceModel().wall_columns.some(e=>e.id!==t.id&&Sl(e,n,.02*this._gridPitch)))return void this._showToast(this._t("toast.column_duplicate"))}const o=this._geometrySnapshot();if("partition"===t.kind){const i=(e.partitions||[]).find(e=>e.id===t.id);i&&(i.cm=s)}else if("column"===t.kind){const i=(e.wall_columns||[]).find(e=>e.id===t.id);i&&(i.cm=cl(s),i.shape="circle"===t.shape?"circle":"square","square"===i.shape?i.angle=cp(t.angle||"0"):delete i.angle)}else{const i=(e.room_drafts||[]).find(e=>e.id===t.id);i?.segments?.[t.segment||0]&&(i.segments[t.segment||0].cm=s)}this._recordGeometry(this._t("history.physical_edit"),o),this._physicalDialog=null,this._saveConfig()},this._deletePhysicalSelection=()=>{const t=this._physicalSel,e=this._curSpaceCfg;if(!t||!e)return;if("draft"===t.kind)return void this._deleteDraftWhole();const i=this._geometrySnapshot(),s="partition"===t.kind?"partitions":"column"===t.kind?"wall_columns":"room_drafts";e[s]=(e[s]||[]).filter(e=>e.id!==t.id),e[s].length||delete e[s],this._activeDraftId===t.id&&this._cancelPath(),this._physicalSel=null,this._physicalDialog=null,this._recordGeometry(this._t("history.physical_delete"),i),this._saveConfig()},this._deleteDraftWhole=()=>{const t="draft"===this._physicalDialog?.kind?this._physicalDialog.id:"draft"===this._physicalSel?.kind?this._physicalSel.id:null,e=this._curSpaceCfg;if(!t||!e||!confirm(this._t("confirm.delete_draft")))return;const i=this._geometrySnapshot();e.room_drafts=(e.room_drafts||[]).filter(e=>e.id!==t),e.room_drafts.length||delete e.room_drafts,this._activeDraftId===t&&this._cancelPath(),this._physicalSel=null,this._physicalDialog=null,this._recordGeometry(this._t("history.physical_delete"),i),this._saveConfig()},this._deleteDraftSegment=()=>{const t=this._physicalDialog,e=this._curSpaceCfg;if(!t||"draft"!==t.kind||!e)return;if(!confirm(this._t("confirm.delete_draft_segment")))return;const i=(e.room_drafts||[]).findIndex(e=>e.id===t.id);if(i<0)return;const s=e.room_drafts[i],n=Math.max(0,Math.min(s.segments.length-1,t.segment||0)),o=[],r=s.points.slice(0,n+1),a=s.points.slice(n+1);if(r.length>=2&&o.push({id:s.id,points:r,segments:s.segments.slice(0,n)}),a.length>=2&&o.push({id:o.length?`${s.id}-${Date.now().toString(36)}`:s.id,points:a,segments:s.segments.slice(n+1)}),2===o.length&&e.room_drafts.length>=200)return void this._showToast(this._t("toast.physical_limit"));const l=this._geometrySnapshot();e.room_drafts.splice(i,1,...o),e.room_drafts.length||delete e.room_drafts,this._activeDraftId===s.id&&this._cancelPath(),this._physicalDialog=null,this._physicalSel=o.length?{kind:"draft",id:o[0].id}:null,this._recordGeometry(this._t("history.draft_segment_delete"),l),this._saveConfig()},this._keepClosedAsPartitions=()=>{if(!this._contourClosed||this._pendingSplit||!this._curSpaceCfg)return;const t=this._curSpaceCfg,e=this._path.slice(0,-1);if((t.partitions||[]).length+e.length>2e3)return void this._showToast(this._t("toast.physical_limit"));const i=this._geometrySnapshot(),s=[...this._draftSegmentCms,this._closingWallCm||this._drawWallCm||ea];t.partitions||=[];const n=Date.now().toString(36);for(let i=0;it.id!==this._activeDraftId),t.room_drafts.length||delete t.room_drafts),this._recordGeometry(this._t("history.contour_to_partitions"),i),this._saveConfig(),this._roomDialog=!1,this._path=[],delete this._resumeDraftBySpace[this._space],this._activeDraftId=null,this._draftSegmentCms=[],this._closingWallCm=null},this._toggleServerPlans=async()=>{const t=this._spaceDialog;if(t)if(t.pickSaved)this._spaceDialog={...t,pickSaved:!1};else{this._spaceDialog={...t,pickSaved:!0,savedBusy:!0};try{const t=await this.hass.callWS({type:"houseplan/plans/list"}),e=this._spaceDialog;e&&(this._spaceDialog={...e,saved:t?.plans||[],savedBusy:!1})}catch(t){const e=this._spaceDialog;e&&(this._spaceDialog={...e,saved:[],savedBusy:!1}),this._showToast(this._t("toast.plans_list_failed",{err:this._errText(t)}))}}},this._aspectJob=null,this._dayCycleTick=()=>{if(!this.isConnected||"hidden"===this.ownerDocument.visibilityState)return;const t=this._dayCycleState();if(!t)return this._dayCycleTimer&&(clearInterval(this._dayCycleTimer),this._dayCycleTimer=0),void(this._dayCycleClockKey="");const e=Cr(t);e!==this._dayCycleClockKey&&(this._dayCycleClockKey=e,this.requestUpdate())},this._sunShown=!1,this._sunOut=!1,this._sunOutTimer=0,this._openSettingsDialog=()=>{if(!this._norm)return;const t=this._glowRadiusCm,e=this._imperial?Math.round(t/30.48*10)/10:Math.round(t)/100;this._settingsDialog={colors:JSON.parse(JSON.stringify(this._fillColors)),glowRadius:e,bgColor:es(this._settings,{bgColor:null})||null,northDeg:Fr(this._settings,{}),bgMode:Or(this._settings,{}),sunRays:Nr(this._settings,{}),busy:!1}},this._openAlignDialog=()=>{if(!this._norm||!this._serverCfg)return;const t=this._serverCfg.spaces||[],e=Xd(this._serverCfg,this._layout||{}),i=Math.ceil(10*e.report.maxShiftCm)/10,s=t.find(t=>null!=t?.id&&String(t.id)===e.report.maxSpace),n=t.length>1&&s?String(s.title||s.id):"";this._alignDialog={report:e.report,config:e.config,layout:e.layout,cm:i,where:n,changed:e.changed,busy:!1}},this._optimizeUndoBusy=!1,this._openBackupExport=()=>{this._settingsDialog=null,this._backupExportDialog={kind:"full",busy:!1,error:""}},this._openRulesDialog=()=>{if(!this._norm)return;const t=this._settings.icon_rules,e=(t&&t.length?t:jt).map(t=>({...t}));this._rulesDialog={rules:e,test:"",busy:!1}},this._climateCache=null,this._gearPtCache=new WeakMap}get _canEdit(){return!!this._norm&&(!0===this._serverCanWrite||!1!==this._serverCanWrite&&!0===this.hass?.user?.is_admin)}get _kiosk(){return!!this._config?.kiosk}_showKioskDots(){this._kioskDots=!0,clearTimeout(this._kioskDotsTimer),this._kioskDotsTimer=window.setTimeout(()=>this._kioskDots=!1,2500)}get _modeTransitionBusy(){return this._modeTransitionPreparing||this._modeTransition.active}_cssColor(t,e){const i=String(t||"").trim();if(!i)return e;const s=document.createElement("span");s.style.cssText=`position:absolute;visibility:hidden;color:${i}`,this.renderRoot.append(s);const n=getComputedStyle(s).color||e;return s.remove(),n}_currentModeVisual(t=this._mode){const e=this._modeTransition.presented||this._modeTransitionVisual;if(e)return{...e,viewport:{...e.viewport,viewBox:{...e.viewport.viewBox}}};const i=this._stageEl;if(!i||i.clientWidth<=0||i.clientHeight<=0)return null;const s=this.renderRoot.querySelector(".editorchrome"),n=this._viewOr(this._baseVb()),o=this.renderRoot.querySelector(".hp-paper"),r=this.renderRoot.querySelector(".hp-backdrop"),a=this.renderRoot.querySelector(".zoomwrap"),l=a?getComputedStyle(a).filter:"",c=Number(/brightness\(([^)]+)\)/.exec(l)?.[1]);return{presentedMode:t,editorChromeHeight:"view"===t?0:s?.getBoundingClientRect().height||0,stageWidth:i.clientWidth,stageHeight:i.clientHeight,viewport:ou(n,i.clientWidth),stageColor:getComputedStyle(i).backgroundColor||"rgb(255, 255, 255)",paperColor:o?getComputedStyle(o).fill:"rgb(255, 255, 255)",sceneBrightness:Number.isFinite(c)?c:1,architectureOpacity:"decor"===t?.35:1,backdropOpacity:r&&Number(getComputedStyle(r).opacity)||1,viewWeight:"view"===t?1:0,editorWeight:"view"===t?0:1,toolbarContentOpacity:"view"===t?0:1}}_viewForModeTarget(t,e,i,s,n){const o=Di(this._baseVb(),s/Math.max(1,n)),r=Math.min(Pp.ZOOM_MAX,Math.max(Pp.ZOOM_MIN,t)),a=o.w/r,l=o.h/r,c=e??o.x+o.w/2,h=i??o.y+o.h/2;return this._clampView({x:c-a/2,y:h-l/2,w:a,h:l},o)}_targetStageColor(t){return"view"!==t?"rgb(255, 255, 255)":this._cssColor(this._stageBg(this._spaceDisplayForRender()),this._cssColor("var(--ha-card-background, var(--card-background-color, #111))","rgb(17, 17, 17)"))}_targetPaperColor(t){return"view"===t&&this._spaceModel().bg?this._cssColor("var(--ha-card-background, var(--card-background-color, #111))","rgb(17, 17, 17)"):"rgb(255, 255, 255)"}_targetBrightness(t){return 1}_applyModeTransitionFrame(t){t.targetMode===this._mode&&(this._modeTransitionVisual=t.presented,this._view={...t.presented.viewport.viewBox},this.requestUpdate())}_settleModeTransition(t){if(t.targetMode!==this._mode)return;const e=this._modeTransitionRequest;this._view={...t.to.viewport.viewBox},this._zoom=this._modeTransitionTargetZoom,this._modeTransitionVisual=null,this._modeTransitionPreparing=!1,this._lastValidStageSize=[t.to.stageWidth,t.to.stageHeight],"view"===this._mode&&(this._saveZoom(),this._viewModeSnap=null,this._modeTransitionEditorCamera=null),this.requestUpdate(),this.updateComplete.then(()=>{if(!this.isConnected||e!==this._modeTransitionRequest||this._modeTransitionBusy||t.targetMode!==this._mode)return;const i=this._stageEl;if(i&&i.clientWidth>0&&i.clientHeight>0&&(Math.abs(i.clientWidth-t.to.stageWidth)>.5||Math.abs(i.clientHeight-t.to.stageHeight)>.5)){const t=this._view;this._lastValidStageSize=[i.clientWidth,i.clientHeight],this._applyView(this._zoom,t?t.x+t.w/2:void 0,t?t.y+t.h/2:void 0),this.requestUpdate()}const s=this.renderRoot.activeElement;(!s||!s.isConnected||!!s.closest?.(".editorchrome, .stage"))&&this.renderRoot.querySelector(".modetab.active")?.focus?.({preventScroll:!0})})}_cancelModeTransition(t=!0){const e=!!this._modeTransition.state;this._modeTransitionRequest++,this._modeTransitionPreparing=!1,this._modeTransitionForceAtomic=!1,this._modeTransition.cancel(t),t&&e||(this._modeTransitionVisual=null)}_adoptMode(t){this._cancelModeTransition(!1),this._mode=t,"view"!==t&&(this._editorChromeMode=t)}_commitViewModeAtomic(t,e,i,s){if(this._modeTransitionPreparing=!1,this._modeTransitionVisual=null,this._modeTransitionForceAtomic=!1,this._zoom=e,t){const n=this._stageEl?.clientWidth||t.stageWidth,o=Math.max(1,t.stageHeight+t.editorChromeHeight);this._view=this._viewForModeTarget(e,i,s,n,o),this._lastValidStageSize=[n,o]}else this._view=null;this._viewModeSnap=null,this._modeTransitionEditorCamera=null,this._saveZoom(),this.requestUpdate()}_prepareModeTransition(t,e,i,s,n,o){this.updateComplete.then(()=>{if(!this.isConnected||t!==this._modeTransitionRequest||this._mode!==i)return void(t===this._modeTransitionRequest&&(this._modeTransitionPreparing=!1,this._modeTransitionVisual=null,this._modeTransitionForceAtomic=!1,this.requestUpdate()));const r=this.renderRoot.querySelector(".editorchrome"),a=r?.querySelector(".editorchrome-inner"),l="view"===i?0:a?.scrollHeight||a?.getBoundingClientRect().height||0,c=Math.max(1,e.stageHeight+e.editorChromeHeight),h=Math.max(1,c-l),d=this._stageEl?.clientWidth||e.stageWidth;if(d<=0||h<=0)return this._modeTransitionPreparing=!1,this._modeTransitionVisual=null,this._applyView(s,n,o),void this.requestUpdate();const u=this._viewForModeTarget(s,n,o,d,h),p={presentedMode:i,editorChromeHeight:l,stageWidth:d,stageHeight:h,viewport:ou(u,d),stageColor:this._targetStageColor(i),paperColor:this._targetPaperColor(i),sceneBrightness:this._targetBrightness(i),architectureOpacity:"decor"===i?.35:1,backdropOpacity:"decor"===i&&"backdrop"!==this._decorTool?.5:1,viewWeight:"view"===i?1:0,editorWeight:"view"===i?0:1,toolbarContentOpacity:"view"===i?0:1};this._modeTransitionPreparing=!1;const _=this._modeTransitionForceAtomic;this._modeTransitionForceAtomic=!1,this._modeTransition.start(e,p,i,this._reducedMotion||_?0:220)})}_slideTo(t,e){if(t===this._space)return;this._cancelModeTransition(!0);const i=window.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches;this._activeDraftId&&(this._resumeDraftBySpace[this._space]=this._activeDraftId),this._space=t,this._path=[],this._clearPlanSnapHover(),this._clearOpeningPlacement(!0),this._tool="draw",this._openWallAnchor=null,this._boundaryRestoreGuard=null,this._activeDraftId=null,this._draftSegmentCms=[],this._closingWallCm=null,this._selId=null,this._physicalSel=null,this._editorSecondary.closeForNavigation(),this._physicalDialog=null,this._physicalDrag=null,"plan"===this._mode&&"draw"===this._tool&&this._resumeLastDraft(),this._restoreZoom(),i||(this._slide=e,clearTimeout(this._slideTimer),this._slideTimer=window.setTimeout(()=>{this._slideTimer=void 0,this._slide="",this.requestUpdate()},190),this.requestUpdate())}_pickSpace(t){if(t===this._space)return;const e=this._model.map(t=>t.id),i=e.indexOf(this._space),s=e.indexOf(t);this._navApplied=!0,this._showFar=!1,this._frame=null,this._slideTo(t,i>=0&&s0&&Date.now()>=this._cyclePausedUntil&&this._model.length>1&&this._zoom<=1.001){const t=this._model.map(t=>t.id),e=t.indexOf(this._space);this._slideTo(t[(e+1)%t.length],"left"),this._showKioskDots()}}get _editing(){return"plan"===this._mode||"devices"===this._mode||"decor"===this._mode}get _markup(){return"plan"===this._mode}_newContinuityController(){return new Hc(()=>{this._resumeSettling="steady"!==this._continuity.state,this._continuityEpoch++,this.isConnected&&this.requestUpdate()})}get _labsIso(){return this._labs.active.includes("iso")}get _desiredProjection(){return"view"===this._mode&&this._labsIso&&"iso"===this._viewPreference[this._space]?"iso":"flat"}_saveViewPreference(){try{localStorage.setItem(wp,JSON.stringify(this._viewPreference))}catch{}}_logicalViewCenter(t){const e=this._view;if(!e)return null;const i=[e.x+e.w/2,e.y+e.h/2],s="iso"===t?Iu(i):i;return{x:s[0],y:s[1]}}_convertProjectionView(t,e){if(t===e)return;const i=this._logicalViewCenter(t);this._view=null;const s=i?"iso"===e?Au([i.x,i.y],0):[i.x,i.y]:null;this._applyView(this._zoom,s?.[0],s?.[1]),this._warmPatch({vp:this._warmViewportState()}),this.requestUpdate()}_setProjection(t){if(!this._labsIso||"view"!==this._mode)return;const e=this._effectiveProjection();if(this._viewPreference={...this._viewPreference,[this._space]:t},"iso"===t){const t=this._isoSceneKey();t&&this._isoFallback.delete(t)}this._saveViewPreference();const i=this._effectiveProjection();this._convertProjectionView(e,i)}_hashSpace(){return vu(window.location.hash||"")}connectedCallback(){this._connectedPath=location.pathname,this._routeDepartureHandled=!1,window.addEventListener("location-changed",this._onLocationChanged),window.addEventListener("popstate",this._onLocationChanged),this._continuityDisposed&&(this._continuity=this._newContinuityController(),this._continuityDisposed=!1,this._continuityPaintToken=-1);const t=(e=this.ownerDocument,Jd.get(e)?.resolved);var e;void 0!==t&&(this._glowScreenBlend=t),this._continuityUnsub?.(),this._continuityUnsub=Nc(this.ownerDocument,this._pageVisibility),super.connectedCallback(),this._motionMedia=window.matchMedia?.("(prefers-reduced-motion: reduce)"),this._reducedMotion=!!this._motionMedia?.matches,this._motionMedia?.addEventListener?.("change",this._onMotionChange),function(t){const e=Jd.get(t);if(e)return e.promise;const i={promise:Promise.resolve(!1)};return i.promise=tu(t).catch(()=>!1).then(t=>(i.resolved=t,t)),Jd.set(t,i),i.promise}(this.ownerDocument).then(t=>{t!==this._glowScreenBlend&&(this._glowScreenBlend=t,this.isConnected&&this.requestUpdate())}),this.hass&&this._ensureHaRegistryAuthority(),window.addEventListener("keydown",this._keyHandler),this._signer.start(()=>this.hass,()=>$s(this._serverCfg)),this._config?.kiosk&&Number(this._config?.cycle)>0&&(clearInterval(this._cycleTimer),this._cycleTimer=window.setInterval(()=>this._cycleTick(),1e3*Number(this._config.cycle))),window.addEventListener("hashchange",this._onHashChange),this._labsUnsub?.(),this._labsUnsub=function(t,e){Su.add(e);const i=Du(t);return"undefined"==typeof window&&e(i),()=>{Su.delete(e)}}(sp,this._onLabsSnapshot),this._booting?this._bootWatch():this._bootFading&&(clearTimeout(this._bootTimer),this._bootTimer=window.setTimeout(()=>{this._bootFading=!1},220)),this._bootSoft&&(clearTimeout(this._bootSoftTimer),this._bootSoftTimer=window.setTimeout(()=>{this._bootSoft=!1},lp)),!this._loadOk&&this._serverCfg&&this.hass&&this._scheduleLoadRetry(),!this._warmSlot&&this._config&&this._warmAdopt(),this._loadOk&&this._ensureLiveSyncSubscriptions(),this._warmVp&&!this._warmRevivePending&&void 0===this._warmReviveTimer&&(this._warmRevivePending=!0,this._warmReviveTimer=window.setTimeout(()=>this._warmReviveDialog(),0)),this._warmLongReturn&&this._beginResumeSettle(),this._warmLongReturn=!1,this.requestUpdate()}disconnectedCallback(){this._connectedPath&&location.pathname!==this._connectedPath&&this._leaveCardRoute(),window.removeEventListener("location-changed",this._onLocationChanged),window.removeEventListener("popstate",this._onLocationChanged),this._continuityUnsub?.(),this._continuityUnsub=void 0,this._motionMedia?.removeEventListener?.("change",this._onMotionChange),this._motionMedia=void 0,this._vacRaf&&(cancelAnimationFrame(this._vacRaf),this._vacRaf=0),this._refitRaf&&(cancelAnimationFrame(this._refitRaf),this._refitRaf=0),this._dayCycleTimer&&(clearInterval(this._dayCycleTimer),this._dayCycleTimer=0),this._dayCycleClockKey="",this._bootSettleRaf&&(cancelAnimationFrame(this._bootSettleRaf),this._bootSettleRaf=0),this._bootSettling=!1;for(const t of this._activityRt.values())clearTimeout(t.timer);window.removeEventListener("keydown",this._keyHandler),clearInterval(this._cycleTimer),clearTimeout(this._kioskDotsTimer),clearTimeout(this._kioskHoldTimer),clearTimeout(this._reloadRetry),clearTimeout(this._loadRetryTimer),this._loadRetryTimer=void 0,this._connHooked?.removeEventListener?.("ready",this._onConnReady),this._connHooked?.removeEventListener?.("disconnected",this._onConnLost),this._connHooked?.removeEventListener?.("reconnect-error",this._onConnLost),this._connHooked=null,this._haRegistryRelease?.(),this._haRegistryRelease=void 0,this._haRegistryConnection=null,this._signer.dispose(),clearTimeout(this._toastTimer),clearTimeout(this._slideTimer),this._modeTransition.dispose(),this._modeTransitionVisual=null,this._modeTransitionPreparing=!1,this._modeTransitionForceAtomic=!1,this._modeTransitionRequest++,this._slideTimer=void 0,this._slide="",clearTimeout(this._bootTimer),this._bootTimer=void 0,clearTimeout(this._bootSoftTimer),this._saveConfigDebounced.flush(),window.removeEventListener("hashchange",this._onHashChange),this._labsUnsub?.(),this._labsUnsub=void 0,clearTimeout(this._holdTimer),this._roViewport?.disconnect(),this._roViewport=void 0,this._roHdr?.disconnect(),this._roHdr=void 0,this._onWinResize&&(window.removeEventListener("resize",this._onWinResize),this._onWinResize=void 0),this._unsubCfg&&(this._unsubCfg(),this._unsubCfg=null),this._unsubLayout&&(this._unsubLayout(),this._unsubLayout=null),this._unsubVirtual&&(this._unsubVirtual(),this._unsubVirtual=null),this._unsubTrail&&(this._unsubTrail(),this._unsubTrail=void 0),this._liveSyncGeneration++,this._liveSyncAttempt=null,this._liveSyncConnection=null,clearTimeout(this._layoutSyncTimer),clearTimeout(this._duplicateColumnTimer);for(const t of this._glowFadeTimers.values())clearTimeout(t);for(const t of this._glowEnterRafs.values())cancelAnimationFrame(t);clearTimeout(this._glowFeatherResumeTimer),this._glowFeatherResumeTimer=0,this._glowFeatherSuspendUntil=0,this._glowFadeTimers.clear(),this._glowEnterRafs.clear(),this._glowEnteringSources.clear(),this._glowRenderedSources.clear(),this._glowLastAppearance.clear(),this._glowSourceSeq=0,this._warmSnapshot(),this._warmRevivePending=!1,clearTimeout(this._warmReviveTimer),this._warmReviveTimer=void 0,this._warmRelease(),this._openWallAnchor=null,this._boundaryRestoreGuard=null,this._clearPlanSnapHover(),this._clearOpeningPlacement(!0),this._touchContacts.clear(),this._touchSequenceMultitouch=!1,this._touchClickBlockUntil=0,this._editorSecondary.reset(),this._resumeSettling=!1,this._continuityHistory=[...this._continuityHistory,...this._continuity.trace].slice(-80),this._continuity.dispose(),this._continuityDisposed=!0,super.disconnectedCallback()}_onKey(t){if("Escape"===t.key&&this._vacFit)return this._vacFit=null,this._showToast(this._t("vac.cal_cancelled")),void t.stopPropagation();if("Escape"===t.key){if(this._tapConfirm)return void(this._tapConfirm=null);if(this._vacCalConfirm)return void(this._vacCalConfirm=null);if(this._decorEraseConfirm)return void(this._decorEraseConfirm=null);if(this._openingInfo)return void(this._openingInfo=null);if(this._infoCard)return void this._closeInfoCard();if(this._rulesDialog)return void(this._rulesDialog=null);if(this._alignDialog)return void(this._alignDialog=null);if(this._backupImportDialog)return void(this._backupImportDialog=null);if(this._backupExportDialog)return void(this._backupExportDialog=null);if(this._settingsDialog)return void(this._settingsDialog=null);if(this._markerDialog)return void(this._markerDialog=null);if(this._openingDialog)return void(this._openingDialog=null);if(this._physicalDialog)return void(this._physicalDialog=null);if(this._backdropDialog)return void(this._backdropDialog=null);if(this._decorShapeDialog)return void(this._decorShapeDialog=null);if(this._decorTextDialog)return void(this._decorTextDialog=null);if(this._spaceDialog&&!this._roomDialog)return this._spaceDialog=null,this._importQueue=[],void(this._importTotal=0);if(this._editorSecondary.hasOpenGroup)return t.preventDefault(),void this._editorSecondary.closeGroup(!0)}const e=t.composedPath?.()||[t.target],i=e.some(t=>t?.matches?.('input, textarea, select, [contenteditable="true"]')),s=e.some(t=>t?.classList?.contains?.("editor-secondary")),n=t.ctrlKey||t.metaKey,o=t.key.toLowerCase(),r=/^[a-z]$/.test(o),a="z"===o||!r&&"KeyZ"===t.code,l="y"===o||!r&&"KeyY"===t.code,c=n&&(a&&t.shiftKey||l),h=n&&a&&!t.shiftKey;if("decor"===this._mode){if((h||c)&&i)return;return c?(t.preventDefault(),void this._redoGeometry()):h?(t.preventDefault(),this._decorDraft?void(this._decorDraft=null):this._decorMove||this._dtDrag||this._bdDrag?void this._cancelDecorGesture():void this._undoGeometry()):"Delete"!==t.key&&"Backspace"!==t.key||!this._decorSel||i||s?void("Escape"===t.key&&(t.preventDefault(),this._decorDraft?this._decorDraft=null:this._decorMove||this._dtDrag||this._bdDrag?this._cancelDecorGesture():"furniture"===this._decorTool?(this._furnPalette=null,this._decorTool="select"):this._decorSel?this._decorSel=null:"select"!==this._decorTool?this._decorTool="select":this._setMode("view"))):(t.preventDefault(),void this._decorDeleteSel())}if(this._markup&&(!h&&!c||!i)){if(("Delete"===t.key||"Backspace"===t.key)&&this._physicalSel&&!i&&!s)return t.preventDefault(),void this._deletePhysicalSelection();if(c)return t.preventDefault(),void this._redoGeometry();if(h)return t.preventDefault(),this._rszDrag?void this._rszCancelDrag():"draw"!==this._tool&&"partition"!==this._tool||!this._path.length?"split"===this._tool&&this._splitSel?.pts?.length?(this._splitSel={...this._splitSel,pts:this._splitSel.pts.slice(0,-1)},void(this._splitSel.pts.length||(this._cursorPt=null))):void this._undoGeometry():void(this._activeDraftId&&this._path.length>1?this._undoGeometry():this._undoPoint());if("Escape"===t.key)return this._physicalDrag||this._physicalRotate?(t.preventDefault(),void this._cancelPhysicalGesture()):this._roomDialog?(t.preventDefault(),void this._roomDialogCancel()):"draw"===this._tool&&this._path.length||"partition"===this._tool&&this._path.length?(t.preventDefault(),void this._undoPoint()):this._physicalSel?(t.preventDefault(),void(this._physicalSel=null)):"resize"===this._tool?(t.preventDefault(),this._rszDrag?void this._rszCancelDrag():void(this._rszSel?this._rszSel=null:this._tool="draw")):"split"===this._tool?(t.preventDefault(),void(this._splitSel?.pts?.length?(this._splitSel={...this._splitSel,pts:this._splitSel.pts.slice(0,-1)},this._splitSel.pts.length||(this._cursorPt=null)):this._splitSel?this._splitSel=null:this._tool="draw")):"merge"===this._tool?(t.preventDefault(),void(this._mergeSel?this._mergeSel=null:this._tool="draw")):this._wallDialog?(t.preventDefault(),void(this._wallDialog=null)):"boundary"===this._tool?(t.preventDefault(),void(this._openWallAnchor?this._cancelBoundaryAnchor():this._tool="draw")):void("opening"!==this._tool&&"wallthick"!==this._tool&&"delroom"!==this._tool&&"partition"!==this._tool&&"column"!==this._tool||(t.preventDefault(),"opening"===this._tool&&this._clearOpeningPlacement(!0),this._tool="draw"))}}_undoPoint(){if(this._path.length){if(this._contourClosed)return this._path=this._path.slice(0,-1),void(this._closingWallCm=null);if(this._activeDraftId&&this._path.length>1&&this._curSpaceCfg){const t=this._geometrySnapshot();this._path=this._path.slice(0,-1),this._draftSegmentCms=this._draftSegmentCms.slice(0,-1);const e=this._curSpaceCfg,i=(e.room_drafts||[]).findIndex(t=>t.id===this._activeDraftId);return void(i>=0&&(this._path.length<2?(e.room_drafts.splice(i,1),e.room_drafts.length||delete e.room_drafts,this._activeDraftId=null):e.room_drafts[i]={id:this._activeDraftId,points:this._path.map(t=>[t[0]/kp,t[1]/kp]),segments:this._draftSegmentCms.map(t=>({cm:t}))},this._recordGeometry(this._t("history.draft_segment_delete"),t),this._saveConfig()))}this._path=this._path.slice(0,-1)}}static getConfigElement(){return document.createElement("houseplan-card-editor")}static getStubConfig(){return{type:"custom:houseplan-card"}}static _warmBootReset(t){for(const t of up.values())for(const e of t)clearTimeout(e.evict);up.clear(),mp=t&&t>0?t:1e4}static _warmBootStats(){let t=0,e=0;const i=[];for(const s of up.values())for(const n of s)t++,n.dlg&&(e++,i.push(n.dlg.kind));return{keys:up.size,slots:t,dlgs:e,drafts:i}}setConfig(t){this._config={icon_size:2.5,show_temperature:!0,live_states:!0,show_signal:!0,...t},this._config.kiosk&&(this._booting=!1,this._bootFading=!1),t.default_floor&&(this._space=t.default_floor);try{this._zoomBySpace=JSON.parse(localStorage.getItem(vp)||"{}")||{}}catch{this._zoomBySpace={}}try{const t=JSON.parse(localStorage.getItem(wp)||"{}")||{};this._viewPreference=Object.fromEntries(Object.entries(t).filter(t=>"flat"===t[1]||"iso"===t[1]))}catch{this._viewPreference={}}this._labs=Du(sp);try{const t=JSON.parse(localStorage.getItem(bp)||"null");this._kioskScale={icon:Cs(t?.icon),font:Cs(t?.font)}}catch{}try{const t=JSON.parse(localStorage.getItem(fp)||"null");t&&t.config&&Array.isArray(t.config.spaces)&&(this._serverCfg=t.config,this._cfgEpoch++,this._cfgRev=t.rev||0,this._cfgContentFingerprint=t.config_fingerprint||qc(t.config),this._layout=t.layout||{},this._layoutRev=t.layout_rev||0,this._layoutContentFingerprint=t.layout_fingerprint||qc(this._layout),this._virtualLights=rn(t.virtual_lights,this._cfgRev),this._serverStorage=!0,this._adoptInitialSpace(this._model))}catch{}"view"!==this._mode||this._view||(this._zoom=this._zoomBySpace[this._space]||1),this.isConnected&&(this._warmAdopt(),this._warmLongReturn&&this._beginResumeSettle(),this._warmLongReturn=!1)}_warmAdopt(){if(this._config?.kiosk)return;const t=_p(this._config);if(this._warmKey===t&&this._warmSlot)return;this._warmSlot&&this._warmRelease();const e=this.parentNode,i=this._warmIdx(e),s=up.get(t);if(!s||!s.length)return;const n=s.find(t=>t.owner===this._warmGen);if(n)return this._warmLongReturn=!!n.freed&&Date.now()-n.freed>=zc,clearTimeout(n.evict),n.evict=0,n.freed=0,n.live=!0,this._warmSlot=n,this._warmKey=t,n.frameFingerprint&&this._continuity.adoptCompleteFrame(n.frameFingerprint),void(!this._devices.length&&n.devices?.length&&(this._devices=[...n.devices]));const{slot:o,sure:r}=((t,e,i,s)=>{const n=t=>{const e=!!i&&t.place?.deref()===i;return e&&t.idx===s?4:t.live?0:e?3:2};let o=null,r=0,a=0,l=null;for(const i of t){if(i.owner===e)continue;l=i;const t=n(i);t<=0||(t>r?(o=i,r=t,a=1):t===r&&a++)}return!o||a>1?{slot:o||l,sure:!1}:{slot:o,sure:!0}})(s,this._warmGen,e,i);o&&(this._warmLongReturn=!!o.freed&&Date.now()-o.freed>=zc,this._booting=!1,this._bootFading=!1,this._hdrH=o.hdrH,this._bootSoft=!0,this.isConnected&&(clearTimeout(this._bootSoftTimer),this._bootSoftTimer=window.setTimeout(()=>{this._bootSoft=!1},lp)),this._warmKey=t,r?(clearTimeout(o.evict),o.evict=0,o.owner=this._warmGen,o.place=e?new WeakRef(e):null,o.idx=i,o.live=!0,this._warmSlot=o,this._warmVp=o.vp,o.frameFingerprint&&this._continuity.adoptCompleteFrame(o.frameFingerprint),!this._devices.length&&o.devices?.length&&(this._devices=[...o.devices]),this._warmAdoptViewport(this._config)):(this._warmSlot={owner:this._warmGen,path:location.pathname,place:e?new WeakRef(e):null,idx:i,live:!0,hdrH:o.hdrH,stageH:o.stageH,vp:null,frameFingerprint:"",devices:null,dlg:null,freed:0,evict:0},s.push(this._warmSlot),this._warmTrim(s)))}_warmIdx(t){const e=t?.children;if(!e)return-1;for(let t=0;t4;){const e=t.findIndex(t=>!t.live);if(e<0)break;clearTimeout(t[e].evict),t.splice(e,1)}}_warmScheduleEvict(t,e){if(clearTimeout(t.evict),!(t.dlg||t.vp&&"view"!==t.vp.mode))return;const i=t.freed,s=t.owner;t.evict=window.setTimeout(()=>{if(t.evict=0,t.freed!==i||t.owner!==s)return;var n;t.dlg=null,t.vp=(n=t.vp)&&"view"!==n.mode?{...n,mode:"view",zoom:n.snap?.space===n.space?n.snap.zoom:n.zoom,view:null,snap:null,tool:"draw",decorTool:"select",showHidden:!1,selId:null,rszSel:null,decorSel:null}:n,t.frameFingerprint="";const o=up.get(e);if(!t.live&&o&&o.length>1){const e=o.indexOf(t);e>=0&&o.splice(e,1)}},mp+250)}_warmAdoptViewport(t){const e=this._warmVp;if(!e||this._warmSlot?.path!==location.pathname)return void(this._warmVp=null);if(this._hashApplied||!this._model.find(t=>t.id===e.space))return void(this._warmVp=null);this._space=e.space,this._navApplied=!0,this._adoptMode("view"!==e.mode&&this._canEdit&&!t.kiosk?e.mode:"view"),this._pendingNavMode="view"===e.mode||this._canEdit||t.kiosk?null:e.mode,this._zoom=e.zoom;const i=this._effectiveProjection(),s=i===e.projection&&this._labsIso===e.activeLabsIso;if(this._view=s&&e.view?{...e.view}:null,this._viewModeSnap=s&&e.snap?{...e.snap}:null,!s&&e.logicalCenter){const t="iso"===i?Au([e.logicalCenter.x,e.logicalCenter.y],0):[e.logicalCenter.x,e.logicalCenter.y];this._applyView(e.zoom,t[0],t[1])}var n;this._tool="openwall"===(n=e.tool)||"closewall"===n?"boundary":"opening"===n?"draw":"string"==typeof n&&Sp.has(n)?n:"draw",this._decorTool=e.decorTool,this._showHidden=e.showHidden,this._showFar!==e.showFar&&(this._showFar=e.showFar,this._frame=null),this._selId=e.selId,this._rszSel=e.rszSel,this._decorSel=e.decorSel,this._warmVpArmed=!0}_warmPatch(t,e=!1){if(this._config?.kiosk)return;const i=_p(this._config);if(!this._warmSlot||this._warmKey===i){if(!this._warmSlot){if(!e)return;const t=this.parentNode;this._warmKey=i,this._warmSlot={owner:this._warmGen,path:location.pathname,place:t?new WeakRef(t):null,idx:this._warmIdx(t),live:!0,hdrH:this._hdrH,stageH:0,vp:null,frameFingerprint:"",devices:null,dlg:null,freed:0,evict:0};const s=up.get(i)||[];for(s.push(this._warmSlot),up.set(i,s),this._warmTrim(s);up.size>8;){const t=up.keys().next().value;if(void 0===t||t===i)break;for(const e of up.get(t)||[])clearTimeout(e.evict);up.delete(t)}}Object.assign(this._warmSlot,t)}}_warmViewportState(){const t=this._effectiveProjection();return{space:this._space,mode:this._mode,projection:t,activeLabsIso:this._labsIso,logicalCenter:this._logicalViewCenter(t),zoom:this._zoom,view:this._view?{...this._view}:null,snap:this._viewModeSnap?{...this._viewModeSnap}:null,tool:this._tool,decorTool:this._decorTool,showHidden:this._showHidden,showFar:this._showFar,selId:this._selId,rszSel:this._rszSel,decorSel:this._decorSel}}_warmDialogState(){const t=(t,e)=>({kind:t,space:this._space,mode:this._mode,data:e});return this._tapConfirm||this._alignDialog||this._mergeDialog||this._importDialog||this._backupExportDialog||this._backupImportDialog?null:this._openingInfo?t("openingInfo",this._openingInfo.id):this._infoCard?t("info",this._infoCard.id):this._rulesDialog?this._rulesDialog.busy?null:t("rules",this._rulesDialog):this._settingsDialog?this._settingsDialog.busy?null:t("settings",this._settingsDialog):this._markerDialog?this._markerDialog.busy?null:t("marker",this._markerDialog):this._openingDialog?t("opening",this._openingDialog):this._backdropDialog?t("backdrop",this._backdropDialog):this._decorShapeDialog?t("decorShape",this._decorShapeDialog):this._decorTextDialog?t("decorText",this._decorTextDialog):this._roomDialog?t("room",{editId:this._roomEditId,fill:this._roomFill,customFill:this._roomCustomFill,tempSrc:this._roomTempSrc,humSrc:this._roomHumSrc,srcOpen:this._roomSrcOpen,srcFilter:this._roomSrcFilter,nameScale:this._roomNameScale,labelScale:this._roomLabelScale,areaSel:this._areaSel,nameSel:this._nameSel,pendingSplit:this._pendingSplit,path:this._path}):this._spaceDialog?this._spaceDialog.busy?null:t("space",this._spaceDialog):null}_warmSnapshot(){if(this._booting||this._config?.kiosk||"steady"!==this._continuity.state)return;const t={vp:this._warmViewportState(),frameFingerprint:this._continuity.frameFingerprint,devices:this._devices};if(this._warmRevivePending||(t.dlg=this._warmDialogState()),this.isConnected&&this._warmSlot?.owner===this._warmGen){const e=this.parentNode;t.place=e?new WeakRef(e):null,t.idx=this._warmIdx(e)}this._warmPatch(t)}_warmReviveDialog(){this._warmRevivePending=!1;const t=this._warmSlot;if(this._warmReviveTimer=void 0,!t||!t.dlg)return;const e=t.dlg,i=t.freed;if(t.dlg=null,t.freed=0,clearTimeout(t.evict),t.evict=0,i&&!(Date.now()-i>mp)&&e.space===this._space&&e.mode===this._mode){switch(e.kind){case"space":this._spaceDialog={...e.data,busy:!1,savedBusy:!1};break;case"marker":this._markerDialog={...e.data,busy:!1};break;case"settings":this._settingsDialog={...e.data,busy:!1};break;case"rules":this._rulesDialog={...e.data,busy:!1};break;case"opening":this._openingDialog={...e.data};break;case"backdrop":this._backdropDialog={...e.data};break;case"decorShape":this._decorShapeDialog={...e.data};break;case"decorText":{this._decorTextDialog={...e.data};const t=String(this._decorTextDialog?.text??"").length;this._decorTextSelection={start:t,end:t};break}case"room":{const t=e.data;this._roomEditId=t.editId,this._roomFill=t.fill,this._roomCustomFill=t.customFill||null,this._roomTempSrc=t.tempSrc,this._roomHumSrc=t.humSrc,this._roomSrcOpen=t.srcOpen,this._roomSrcFilter=t.srcFilter,this._roomNameScale=t.nameScale,this._roomLabelScale=t.labelScale,this._areaSel=t.areaSel,this._nameSel=t.nameSel,this._pendingSplit=t.pendingSplit,this._path=t.path,this._roomDialog=!0;break}case"info":{const t=this._devices.find(t=>t.id===e.data);t&&(this._infoCard=t);break}case"openingInfo":{const t=(this._curSpaceCfg?.openings||[]).find(t=>t.id===e.data);t&&(this._openingInfo=t);break}}this.requestUpdate()}}_cacheSnapshot(){if(this._serverCfg)try{this._cfgContentFingerprint=qc(this._serverCfg),this._layoutContentFingerprint=qc(this._layout),this._virtualLights=function(t,e,i){if(t.configRev===i)return t;const s=new Set((Array.isArray(e?.markers)?e.markers:[]).filter(hn).map(t=>t.id));return{...t,configRev:i,off:new Set([...t.off].filter(t=>s.has(t)))}}(this._virtualLights,this._serverCfg,this._cfgRev),localStorage.setItem(fp,JSON.stringify({config:this._serverCfg,rev:this._cfgRev,config_fingerprint:this._cfgContentFingerprint,layout:this._layout,layout_rev:this._layoutRev,layout_fingerprint:this._layoutContentFingerprint,virtual_lights:ln(this._virtualLights)}))}catch{}}_beginContinuityCandidate(t,e,i="plan"){return this._booting&&!this._continuity.hasCompleteFrame?this._continuity.token:(this._continuityDataReady=e,this._continuityPaintToken=-1,this._stagedDeviceSnapshotToken=-1,this._resumeSettling=!0,this._continuity.beginCandidate(t,i))}_continuityStageValid(){const t=this._stageEl;return!!t&&t.clientWidth>0&&t.clientHeight>0}_continuityAssetsReady(){if(!this._model.length)return!1;const t=this._model.length?this._spaceModel():null;return!t?.bg?.href||this._signer.isReady(this.hass,t.bg.href)}_initialSpaceSelection(t){return function(t){const e=new Set(t.spaceIds.filter(t=>!!t));if(!e.size)return{id:null,source:"none"};const i=[["hash",!1===t.acceptHash?null:t.hashSpace],["current",t.preserveCurrent?t.currentSpace:null],["saved",t.savedSpace],["default",t.defaultSpace],["first",t.spaceIds[0]]];for(const[t,s]of i)if(s&&e.has(s))return{id:s,source:t};return{id:null,source:"none"}}({spaceIds:t.map(t=>t.id),hashSpace:this._hashSpace(),acceptHash:!this._hashApplied,currentSpace:this._space,preserveCurrent:this._hashApplied||this._navApplied||this._warmVpArmed,savedSpace:this._savedNav()?.space,defaultSpace:this._config?.default_floor})}_adoptInitialSpace(t){const e=this._initialSpaceSelection(t);return e.id?(this._space=e.id,"hash"===e.source&&(this._hashApplied=!0),"saved"===e.source&&(this._navApplied=!0),e):e}_candidateBackdrop(t,e=this._space){const i=Al(t),s=this._initialSpaceSelection(i).id||(i.some(t=>t.id===e)?e:i[0]?.id);return i.find(t=>t.id===s)?.bg?.href||""}_visualFrameFingerprint(){const t=this._stageEl,e=t?[t.clientWidth,t.clientHeight]:[0,0];return Bc([this._cfgRev,this._cfgContentFingerprint||qc(this._serverCfg),this._layoutRev,this._layoutContentFingerprint||qc(this._layout),this._space,this._mode,this._view,e,this._glowScreenBlend?"screen":"normal",this.hass?.themes?.darkMode??this.hass?.themes?.default_theme??""])}_settleContinuityFrame(){if(this._booting||!this._continuityStageValid())return;if(!this._continuity.hasCompleteFrame&&"steady"===this._continuity.state)return void(this._continuityAssetsReady()&&(this._renderSnapshotAt=Date.now(),this._continuity.markCompleteFrame(this._visualFrameFingerprint())));if(!this._continuityDataReady)return;if(!["holding","offline-stale","overlay-pending","overlay-visible","candidate-ready"].includes(this._continuity.state))return;const t=this._continuity.token;if(this._candidateDeviceSnapshot&&this._candidateDeviceSnapshot!==this._visibleDeviceSnapshot&&this._stagedDeviceSnapshotToken!==t)return this._stagedDeviceSnapshotToken=t,void this.requestUpdate();this._continuityPaintToken!==t&&(this._continuityPaintToken=t,this._continuity.candidateReady(t)&&this._continuity.commitAfterPaint(t,{updateComplete:()=>this.updateComplete,stageValid:()=>this.isConnected&&this._continuityStageValid(),assetsReady:()=>this._continuityAssetsReady(),frameFingerprint:()=>this._visualFrameFingerprint()}).then(e=>{e&&t===this._continuity.token?(this._resumeSettling=!1,this._renderSnapshotAt=Date.now(),this._candidateDeviceSnapshot&&(this._visibleDeviceSnapshot=this._candidateDeviceSnapshot),this._candidateDeviceSnapshot=null,this._stagedDeviceSnapshotToken=-1,this._warmSnapshot()):t===this._continuity.token&&(this._continuityPaintToken=-1,this._stagedDeviceSnapshotToken=-1,this._candidateDeviceSnapshot=null,this.requestUpdate())}))}_onBackdropLoaded(t,e){this._signer.markLoaded(this.hass,t,e),this._continuity.note("asset-ready"),this._continuityPaintToken=-1,"steady"!==this._continuity.state&&this.requestUpdate()}_renderRecoveryOverlay(){if(!this._continuity.overlayVisible&&"recovery-error"!==this._continuity.state)return G;const t="connection"===this._continuity.recoveryReason;return j`
`}const Xu={window:120,door:90,gate:300};function Zu(t){return Xu[t]}function Ju(t,e){const i=[t[0],t[1]],s=[e[0],e[1]];return function(t,e){return t[0]-e[0]||t[1]-e[1]}(i,s)<=0?{a:i,b:s}:{a:s,b:i}}function Qu(t){const e=new Map;return t.forEach((t,i)=>{if(!t.kind||t.open)return;const s=Ju(t.a,t.b);if(Math.hypot(s.b[0]-s.a[0],s.b[1]-s.a[1])<=1e-9)return;const n=function(t,e){const i=t=>(Math.abs(t)<=5e-10?0:Math.round(1e6*t)/1e6).toFixed(6);return`${i(t[0])},${i(t[1])}>${i(e[0])},${i(e[1])}`}(s.a,s.b),o=e.get(n);if(o)return o.physicalHalfWidth=Math.max(o.physicalHalfWidth,t.half||0),void(o.sourceOrder=Math.min(o.sourceOrder,i));e.set(n,{segmentKey:n,a:s.a,b:s.b,physicalHalfWidth:Math.max(0,t.half||0),sourceOrder:i})}),[...e.values()]}function tp(t,e){if(Math.abs(t.distance-e.distance)>1e-9)return t.distance-e.distance;if(Math.abs(t.perpendicular-e.perpendicular)>1e-9)return t.perpendicular-e.perpendicular;return(t.target.segmentKeye.target.segmentKey?1:0)||t.target.sourceOrder-e.target.sourceOrder}function ep(t){const e=Qu(t.intervals).map(e=>{const i=function(t,e){const i=e.b[0]-e.a[0],s=e.b[1]-e.a[1],n=Math.hypot(i,s),o=i/n,r=s/n,a=(t[0]-e.a[0])*o+(t[1]-e.a[1])*r,l=Math.max(0,Math.min(n,a)),c=e.a[0]+l*o,h=e.a[1]+l*r;return{along:l,length:n,x:c,y:h,distance:Math.hypot(t[0]-c,t[1]-h),perpendicular:Math.abs((t[0]-e.a[0])*r-(t[1]-e.a[1])*o)}}(t.pointer,e);return{target:e,...i,envelope:Math.max(t.baseTolerance,e.physicalHalfWidth+t.bodyPointerPadding)}}).filter(t=>t.distance<=t.envelope+1e-9).filter(e=>!function(t,e,i,s,n){const o=e.b[0]-e.a[0],r=e.b[1]-e.a[1],a=Math.hypot(o,r);if(!(a>1e-9))return!1;const l=o/a,c=r/a;for(const o of i){if(!o.open&&o.kind)continue;const i=Ju(o.a,o.b),r=i.b[0]-i.a[0],a=i.b[1]-i.a[1],h=Math.hypot(r,a);if(!(h>1e-9))continue;const d=r/h,u=a/h;if(Math.abs(l*u-c*d)>1e-6)continue;if(Math.abs((i.a[0]-e.a[0])*c-(i.a[1]-e.a[1])*l)>n)continue;const p=(t[0]-i.a[0])*d+(t[1]-i.a[1])*u,_=1e-9;if(!(p<=_||p>=h-_)&&Math.abs((t[0]-i.a[0])*u-(t[1]-i.a[1])*d)<=s+1e-9)return!0}return!1}(t.pointer,e.target,t.intervals,e.envelope,Math.max(1e-9,Math.min(t.baseTolerance,.04*t.gridStep)))).sort(tp),i=e[0];if(!i)return null;const{target:s,length:n}=i,o=s.b[0]-s.a[0],r=s.b[1]-s.a[1],a=o/n,l=r/n,c=Math.min(Math.max(0,t.renderedLength)/2,n/2);let h=i.along;const d=Math.max(t.gridStep,1e-9),u=n/2;h=Math.abs(h-u)<=d/2?u:Math.round(h/d)*d,h=Math.max(c,Math.min(n-c,h));const p=s.a[0]+a*h,_=s.a[1]+l*h,m=Math.max(0,t.renderedLength)/2,g=h-m,f=h+m,v=Math.max(0,g),y=Math.max(0,n-f),b=[s.a[0]+a*(g-v/2),s.a[1]+l*(g-v/2)],w=[s.a[0]+a*(f+y/2),s.a[1]+l*(f+y/2)];let k=180*Math.atan2(r,o)/Math.PI;k>=90?k-=180:k<-90&&(k+=180);const $=Math.abs(h-u)<=1e-9;return{presetRevision:t.preset.revision,geometryRevision:t.geometryRevision,pointer:[t.pointer[0],t.pointer[1]],type:t.preset.type,lengthCm:t.preset.lengthCm,flipH:t.preset.flipH,flipV:t.preset.flipV,x:p,y:_,angle:k,renderedLength:t.renderedLength,target:s,measure:{labels:[{distance:v,midpoint:b},{distance:y,midpoint:w}],guide:$?{x:p,y:_,angle:k}:null}}}function ip(t,e,i,s,n=1e-6){return t.presetRevision===i&&t.geometryRevision===s&&Math.hypot(t.pointer[0]-e[0],t.pointer[1]-e[1])<=n}const sp="1.64.0",np={badge:"display.badge",icon_ripple:"display.icon_ripple",value:"display.value",static_icon:"display.static_icon"},op={badge:"marker.display_hint_badge",icon_ripple:"marker.display_hint_icon_ripple",value:"marker.display_hint_value",static_icon:"marker.display_hint_static_icon"},rp=2e3,ap=1e3,lp=1500,cp=t=>{const e=String(t??"").trim().replace(",",".");if(!/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)$/.test(e))return null;const i=Number(e);return Number.isFinite(i)?i:null},hp=(t,e)=>{if(!t.has(e))return{hit:!1};const i=t.get(e);return t.delete(e),t.set(e,i),{hit:!0,value:i}},dp=(t,e,i,s)=>{for(t.delete(e),t.set(e,i);t.size>s;){const e=t.keys().next().value;if(void 0===e)break;t.delete(e)}},up=new Map;let pp=0;const _p=t=>`${window.innerWidth}x${window.innerHeight}|${location.pathname}|${JSON.stringify(t??{})}`;let mp=1e4;const gp="houseplan_card_layout_v1",fp="houseplan_card_cfg_v1",vp="houseplan_card_zoom_v1",yp="houseplan_card_nav_v1",bp="houseplan_card_kiosk_v1",wp="houseplan_card_view_v1",kp=1e3,$p=[[0,1],[45,.88],[70,.62],[86,.32],[100,0]],xp=(t,e)=>{const i=Math.min(t.x,e.x),s=Math.min(t.y,e.y);return{x:i,y:s,w:Math.max(t.x+t.w,e.x+e.w)-i,h:Math.max(t.y+t.h,e.y+e.h)-s}},Sp=new Set(["select","draw","partition","column","merge","split","resize","opening","boundary","wallthick","delroom"]),Mp=(t,e,i)=>{const s=new Event(e,{bubbles:!0,composed:!0});s.detail=i??{},t.dispatchEvent(s)},Cp=t=>{history.pushState(null,"",t),Mp(window,"location-changed",{replace:!1})},Dp=(t,e)=>{let i,s=null;const n=(...n)=>{clearTimeout(i),s=n,i=window.setTimeout(()=>{i=void 0;const e=s;s=null,e&&t(...e)},e)};return n.flush=()=>{if(void 0===i)return;clearTimeout(i),i=void 0;const e=s;s=null,e&&t(...e)},n.pending=()=>void 0!==i,n},Tp=t=>{try{t.target?.setPointerCapture?.(t.pointerId)}catch{}};class Pp extends ht{constructor(){super(...arguments),this._space="f1",this._layout={},this._serverStorage=!1,this._loadOk=!1,this._serverCanWrite=null,this._loading=!1,this._loadTries=0,this._serverCfg=null,this._cfgRev=0,this._cfgContentFingerprint="",this._unsubCfg=null,this._unsubLayout=null,this._unsubVirtual=null,this._liveSyncAttempt=null,this._liveSyncGeneration=0,this._liveSyncConnection=null,this._layoutRev=0,this._layoutContentFingerprint="",this._virtualLights=rn(null),this._canOptimizeUndo=!1,this._undoKind=null,this._devices=[],this._regSignature="",this._defPos={},this._newSyncKey="",this._tip=null,this._hoverRoom=null,this._selId=null,this._toast="",this._mode="view",this._pendingNavMode=null,this._decorTool="select",this._decorStyle={...jr},this._decorDraft=null,this._decorMove=null,this._decorSel=null,this._decorEraseConfirm=null,this._decorTextDialog=null,this._decorShapeDialog=null,this._backdropDialog=null,this._decorTextSelection={start:0,end:0},this._furnPalette=null,this._dtBox=null,this._dtDrag=null,this._bdDrag=null,this._slide="",this._reducedMotion=!1,this._onMotionChange=t=>{this._reducedMotion=t.matches,t.matches&&this._modeTransitionPreparing?this._modeTransitionForceAtomic=!0:t.matches&&this._modeTransition.active&&this._cancelModeTransition(!0),this.requestUpdate()},this._editorChromeMode="plan",this._modeTransitionVisual=null,this._modeTransitionPreparing=!1,this._modeTransitionForceAtomic=!1,this._modeTransitionRequest=0,this._modeTransitionTargetZoom=1,this._modeTransitionEditorCamera=null,this._modeTransition=new hu({frame:t=>this._applyModeTransitionFrame(t),settled:t=>this._settleModeTransition(t)}),this._editorSecondary=new Od({root:()=>this.renderRoot,requestUpdate:()=>this.requestUpdate(),updateComplete:()=>this.updateComplete,clearTip:()=>{this._tip=null}}),this._editorSecondaryCopy={groupActive:(t,e)=>this._t("editor.group_active",{group:t,item:e}),openGroup:t=>this._t("editor.open_group",{group:t}),disabledAction:(t,e)=>this._t("editor.disabled_action",{action:t,reason:e})},this._tool="draw",this._geometryHistory=new Zd(50),this._wallDialog=null,this._drawWallField=null,this._activeDraftId=null,this._resumeDraftBySpace={},this._physicalSel=null,this._physicalDialog=null,this._physicalDrag=null,this._physicalRotate=null,this._physicalLastTap=null,this._physicalPickCycle=null,this._wallUnionCache=null,this._isoGeometryCache=new Map,this._isoFallback=new Set,this._openingTunnelCache=null,this._openingWallIndexCache=new Map,this._openingPlacementIntervalsCache=null,this._planSnapGeometryCache=null,this._physicalBodiesCache=null,this._cleanFloorCache=new Map,this._glowClipCache=new Map,this._lightBarrierCache=null,this._glowFeatherUnits=null,this._glowRenderedSources=new Map,this._glowLastAppearance=new Map,this._glowEnteringSources=new Set,this._glowEnterRafs=new Map,this._glowFadeTimers=new Map,this._glowFeatherSuspendUntil=0,this._glowFeatherResumeTimer=0,this._glowSourceSeq=0,this._glowScreenBlend=!1,this._duplicateColumnId=null,this._duplicateColumnTimer=0,this._rszSel=null,this._rszDrag=null,this._rszPreview=null,this._rszLive=null,this._path=[],this._cursorPt=null,this._planSnapHover=null,this._mergeSel=null,this._openingPreset=null,this._openingPresetRevision=0,this._openingHoverCandidate=null,this._openingDialog=null,this._openingInfo=null,this._opDrag=null,this._opMeasure=null,this._mergeDialog=null,this._openWallAnchor=null,this._boundaryPointerType="mouse",this._boundaryRestoreGuard=null,this._boundaryTargetMemo=null,this._boundaryPreviewMemo=null,this._splitSel=null,this._pendingSplit=null,this._areaSel="",this._nameSel="",this._roomDialog=!1,this._roomEditId=null,this._roomFill="",this._roomCustomFill=null,this._roomTempSrc="",this._roomHumSrc="",this._roomSrcOpen=null,this._roomSrcFilter="",this._roomNameScale=1,this._roomLabelScale=1,this._zoom=1,this._view=null,this._zoomBySpace={},this._viewModeSnap=null,this._pointers=new Map,this._panStart=null,this._panLock=null,this._pinchStart=null,this._suppressClick=!1,this._touchContacts=new Map,this._touchSequenceMultitouch=!1,this._touchClickBlockUntil=0,this._connectedPath="",this._routeDepartureHandled=!1,this._onLocationChanged=()=>{this._connectedPath&&location.pathname!==this._connectedPath?this._leaveCardRoute():location.pathname===this._connectedPath&&(this._routeDepartureHandled=!1)},this._touchGestureGuard={capture:!0,handleEvent:t=>this._guardTouchGesture(t)},this._hdrH=118,this._booting=!0,this._bootFading=!1,this._bootSettling=!1,this._bootSettleRaf=0,this._bootLastH=-1,this._bootStart=0,this._bootLastChange=0,this._bootSoft=!1,this._tapConfirm=null,this._onboardingShown=!1,this._rulesDialog=null,this._alignDialog=null,this._settingsDialog=null,this._backupExportDialog=null,this._backupImportDialog=null,this._sunRaysCache=null,this._dayCycleTimer=0,this._dayCycleClockKey="",this._compassDrag=!1,this._importDialog=null,this._importQueue=[],this._importTotal=0,this._rulesCompiledSrc="",this._infoCard=null,this._nativeMoreInfoEntity=null,this._markerDialog=null,this._spaceDialog=null,this._keyHandler=t=>this._onKey(t),this._warmVp=null,this._warmVpArmed=!1,this._warmLongReturn=!1,this._warmRevivePending=!1,this._warmGen=++pp,this._warmKey=null,this._warmSlot=null,this._hashApplied=!1,this._navApplied=!1,this._labs={active:Object.freeze([]),space:""},this._viewPreference={},this._renderProjection="flat",this._kioskScale={icon:1,font:1},this._kioskDialog=!1,this._activityRt=new Map,this._vacRt=new Map,this._vacViewKey="",this._vacLastView=null,this._vacRaf=0,this._vacSrvTrails={},this._vacJumpOnce=!1,this._continuity=this._newContinuityController(),this._continuityHistory=[],this._continuityEpoch=0,this._continuityDataReady=!0,this._continuityPaintToken=-1,this._continuityDisposed=!1,this._renderSnapshotAt=Date.now(),this._hassSequence=0,this._visibleDeviceSnapshot=null,this._candidateDeviceSnapshot=null,this._stagedDeviceSnapshotToken=-1,this._capturedSnapshotSequence=-1,this._capturedSnapshotDevices=null,this._capturedSnapshotLayout=null,this._capturedSnapshotActivity="",this._capturedSnapshotConfigEpoch=-1,this._capturedSnapshotVirtual="",this._lastValidStageSize=null,this._pendingRefitSize=null,this._refitRaf=0,this._pageVisibility=t=>{if(this._continuity.visibility(t),this._dayCycleVisibility(t),"hidden"!==t.kind){if(this._vacJumpOnce=!0,!t.long){const t=Date.now();let e=!1;for(const i of this._activityRt.values())!i.flashKind||(i.expiresAt||i.flashTs+Cd)>t||(i.flashTs=0,i.flashKind=null,i.expiresAt=0,e=!0);return void(e&&this.requestUpdate())}Date.now()-this._renderSnapshotAt>1e3&&this._continuity.note("device-snapshot-stale"),this._continuityDataReady=!1,this._continuityPaintToken=-1,this._resumeSettling=!0,this._loading?this.requestUpdate():this._loadFromServer()}else this._modeTransitionPreparing?this._modeTransitionForceAtomic=!0:this._cancelModeTransition(!0)},this._resumeSettling=!1,this._viewportInvalidAt=0,this._vacFit=null,this._vacAllCamerasFor=null,this._vacAllCameraCache=null,this._vacCalConfirm=null,this._kioskDots=!1,this._cyclePausedUntil=0,this._swipeStart=null,this._lastTap=0,this._onLabsSnapshot=t=>{const e=this._effectiveProjection();this._labs=t;const i=this._effectiveProjection();this._convertProjectionView(e,i),this.requestUpdate()},this._onHashChange=()=>{const t=this._hashSpace();t&&this._model.find(e=>e.id===t)&&t!==this._space&&(this._activeDraftId&&(this._resumeDraftBySpace[this._space]=this._activeDraftId),this._space=t,this._selId=null,this._path=[],this._clearPlanSnapHover(),this._clearOpeningPlacement(!0),this._tool="draw",this._openWallAnchor=null,this._boundaryRestoreGuard=null,this._activeDraftId=null,this._draftSegmentCms=[],this._closingWallCm=null,"plan"===this._mode&&"draw"===this._tool&&this._resumeLastDraft(),this._restoreZoom(),this.requestUpdate())},this._drag=null,this._rlResize=null,this._holdFired=!1,this._retryContinuity=()=>{this._continuityDataReady=!1,this._continuityPaintToken=-1,this._continuity.retry(this._continuity.recoveryReason||"plan"),this._loading||this._loadFromServer()},this._cfgEpoch=0,this._modelCache=null,this._decorSnapCache=null,this._markerPreviewMemo=null,this._markerPreviewDevicesMemo=null,this._showHidden=!1,this._connHooked=null,this._connectionWasLost=!1,this._haRegistryConnection=null,this._haRegistryRev=-1,this._haBindingCacheKey="",this._planHassMemo=null,this._onHaRegistryUpdate=()=>{const t=xn(this.hass);t.revision===this._haRegistryRev&&this._devices.length||(this._haRegistryRev=t.revision,this._planHassMemo=null,this._regSignature="",this._maybeRebuildDevices(),this.requestUpdate())},this._onConnReady=()=>{if(this._loadTries=0,clearTimeout(this._loadRetryTimer),this._loadRetryTimer=void 0,function(t){const e=vn(t);e&&(wn(t,e),kn(t,e))}(this.hass),this._saveConfigDebounced.pending()&&this._saveConfigDebounced.flush(),this._cfgWriting)return clearTimeout(this._reloadRetry),void(this._reloadRetry=window.setTimeout(this._onConnReady,400));!this._connectionWasLost&&this._continuity.hasCompleteFrame?this._beginContinuityCandidate("connection-ready",!1,"plan"):(this._continuityDataReady=!1,this._continuityPaintToken=-1),this._loading||this._loadFromServer()},this._onConnLost=()=>{this._booting&&!this._continuity.hasCompleteFrame||(this._connectionWasLost=!0,this._continuityDataReady=!1,this._continuityPaintToken=-1,this._continuity.connectionLost())},this._signer=new Wc(()=>this.requestUpdate()),this._dirtyPos=new Set,this._sentPos=new Map,this._persistLayout=Dp(()=>{if(this._serverStorage){const t=[...this._dirtyPos];this._dirtyPos.clear();for(const e of t){const t=this._layout[e];t&&(this._sentPos.set(e,t),this.hass.callWS({type:"houseplan/layout/update",device_id:e,pos:t}).then(t=>this._noteLayoutRev(t)).catch(t=>this._showToast(this._t("toast.pos_save_failed",{err:this._errText(t)}))).finally(()=>{this._sentPos.get(e)===t&&this._sentPos.delete(e)}))}this._cacheSnapshot()}else localStorage.setItem(gp,JSON.stringify(this._layout))},600),this._frame=null,this._showFar=!1,this._writesPending=0,this._writeChain=Promise.resolve(),this._undoGeometry=()=>{if(this._cancelBoundaryAnchor())return;if(this._physicalDrag||this._physicalRotate)return void this._cancelPhysicalGesture();if(this._decorDraft)return this._decorDraft=null,void this.requestUpdate();if(this._decorMove||this._dtDrag||this._bdDrag)return void this._cancelDecorGesture();if(this._rszDrag)return void this._rszCancelDrag();const t=this._geometryHistory.undo();t&&(this._applyGeometryState(t.before)?this._showToast(this._t("history.undone",{name:t.name})):this._geometryHistory.clear())},this._redoGeometry=()=>{if(this._cancelBoundaryAnchor())return;if(this._physicalDrag||this._physicalRotate)return void this._cancelPhysicalGesture();if(this._decorDraft)return this._decorDraft=null,void this.requestUpdate();if(this._decorMove||this._dtDrag||this._bdDrag)return void this._cancelDecorGesture();if(this._rszDrag)return void this._rszCancelDrag();const t=this._geometryHistory.redo();t&&(this._applyGeometryState(t.after)?this._showToast(this._t("history.redone",{name:t.name})):this._geometryHistory.clear())},this._saveConfigDebounced=Dp(()=>{this._serverCfg&&this._writeConfig().catch(t=>{"conflict"===t?.code?(this._showToast(this._t("toast.conflict")),this._cancelPath(),this._reloadConfigOnly(!0)):this._showToast(this._t("toast.cfg_save_failed",{err:this._errText(t)}))})},500),this._draftSegmentCms=[],this._closingWallCm=null,this._savePhysicalDialog=()=>{const t=this._physicalDialog,e=this._curSpaceCfg;if(!t||!e)return;const i=cp(t.cm);if(null==i)return void this._showPhysicalRange("column"===t.kind?ll:100);const s=this._imperial?2.54*i:i,n="column"===t.kind?ll:100;if(!Number.isFinite(s)||s<1||s>n)return void this._showPhysicalRange(n);if("column"===t.kind){const e=this._spaceModel().wall_columns.find(e=>e.id===t.id);if(!e)return;const i=cp(t.angle||"0");if("circle"!==t.shape&&(null==i||i<0||i>=90))return void this._showToast(this._t("toast.physical_angle"));const n="circle"===t.shape?{id:t.id,shape:"circle",center:e.center,cm:s}:{id:t.id,shape:"square",center:e.center,cm:s,angle:i};if(this._spaceModel().wall_columns.some(e=>e.id!==t.id&&Sl(e,n,.02*this._gridPitch)))return void this._showToast(this._t("toast.column_duplicate"))}const o=this._geometrySnapshot();if("partition"===t.kind){const i=(e.partitions||[]).find(e=>e.id===t.id);i&&(i.cm=s)}else if("column"===t.kind){const i=(e.wall_columns||[]).find(e=>e.id===t.id);i&&(i.cm=cl(s),i.shape="circle"===t.shape?"circle":"square","square"===i.shape?i.angle=cp(t.angle||"0"):delete i.angle)}else{const i=(e.room_drafts||[]).find(e=>e.id===t.id);i?.segments?.[t.segment||0]&&(i.segments[t.segment||0].cm=s)}this._recordGeometry(this._t("history.physical_edit"),o),this._physicalDialog=null,this._saveConfig()},this._deletePhysicalSelection=()=>{const t=this._physicalSel,e=this._curSpaceCfg;if(!t||!e)return;if("draft"===t.kind)return void this._deleteDraftWhole();const i=this._geometrySnapshot(),s="partition"===t.kind?"partitions":"column"===t.kind?"wall_columns":"room_drafts";e[s]=(e[s]||[]).filter(e=>e.id!==t.id),e[s].length||delete e[s],this._activeDraftId===t.id&&this._cancelPath(),this._physicalSel=null,this._physicalDialog=null,this._recordGeometry(this._t("history.physical_delete"),i),this._saveConfig()},this._deleteDraftWhole=()=>{const t="draft"===this._physicalDialog?.kind?this._physicalDialog.id:"draft"===this._physicalSel?.kind?this._physicalSel.id:null,e=this._curSpaceCfg;if(!t||!e||!confirm(this._t("confirm.delete_draft")))return;const i=this._geometrySnapshot();e.room_drafts=(e.room_drafts||[]).filter(e=>e.id!==t),e.room_drafts.length||delete e.room_drafts,this._activeDraftId===t&&this._cancelPath(),this._physicalSel=null,this._physicalDialog=null,this._recordGeometry(this._t("history.physical_delete"),i),this._saveConfig()},this._deleteDraftSegment=()=>{const t=this._physicalDialog,e=this._curSpaceCfg;if(!t||"draft"!==t.kind||!e)return;if(!confirm(this._t("confirm.delete_draft_segment")))return;const i=(e.room_drafts||[]).findIndex(e=>e.id===t.id);if(i<0)return;const s=e.room_drafts[i],n=Math.max(0,Math.min(s.segments.length-1,t.segment||0)),o=[],r=s.points.slice(0,n+1),a=s.points.slice(n+1);if(r.length>=2&&o.push({id:s.id,points:r,segments:s.segments.slice(0,n)}),a.length>=2&&o.push({id:o.length?`${s.id}-${Date.now().toString(36)}`:s.id,points:a,segments:s.segments.slice(n+1)}),2===o.length&&e.room_drafts.length>=200)return void this._showToast(this._t("toast.physical_limit"));const l=this._geometrySnapshot();e.room_drafts.splice(i,1,...o),e.room_drafts.length||delete e.room_drafts,this._activeDraftId===s.id&&this._cancelPath(),this._physicalDialog=null,this._physicalSel=o.length?{kind:"draft",id:o[0].id}:null,this._recordGeometry(this._t("history.draft_segment_delete"),l),this._saveConfig()},this._keepClosedAsPartitions=()=>{if(!this._contourClosed||this._pendingSplit||!this._curSpaceCfg)return;const t=this._curSpaceCfg,e=this._path.slice(0,-1);if((t.partitions||[]).length+e.length>2e3)return void this._showToast(this._t("toast.physical_limit"));const i=this._geometrySnapshot(),s=[...this._draftSegmentCms,this._closingWallCm||this._drawWallCm||ea];t.partitions||=[];const n=Date.now().toString(36);for(let i=0;it.id!==this._activeDraftId),t.room_drafts.length||delete t.room_drafts),this._recordGeometry(this._t("history.contour_to_partitions"),i),this._saveConfig(),this._roomDialog=!1,this._path=[],delete this._resumeDraftBySpace[this._space],this._activeDraftId=null,this._draftSegmentCms=[],this._closingWallCm=null},this._toggleServerPlans=async()=>{const t=this._spaceDialog;if(t)if(t.pickSaved)this._spaceDialog={...t,pickSaved:!1};else{this._spaceDialog={...t,pickSaved:!0,savedBusy:!0};try{const t=await this.hass.callWS({type:"houseplan/plans/list"}),e=this._spaceDialog;e&&(this._spaceDialog={...e,saved:t?.plans||[],savedBusy:!1})}catch(t){const e=this._spaceDialog;e&&(this._spaceDialog={...e,saved:[],savedBusy:!1}),this._showToast(this._t("toast.plans_list_failed",{err:this._errText(t)}))}}},this._aspectJob=null,this._dayCycleTick=()=>{if(!this.isConnected||"hidden"===this.ownerDocument.visibilityState)return;const t=this._dayCycleState();if(!t)return this._dayCycleTimer&&(clearInterval(this._dayCycleTimer),this._dayCycleTimer=0),void(this._dayCycleClockKey="");const e=Cr(t);e!==this._dayCycleClockKey&&(this._dayCycleClockKey=e,this.requestUpdate())},this._sunShown=!1,this._sunOut=!1,this._sunOutTimer=0,this._openSettingsDialog=()=>{if(!this._norm)return;const t=this._glowRadiusCm,e=this._imperial?Math.round(t/30.48*10)/10:Math.round(t)/100;this._settingsDialog={colors:JSON.parse(JSON.stringify(this._fillColors)),glowRadius:e,bgColor:es(this._settings,{bgColor:null})||null,northDeg:Fr(this._settings,{}),bgMode:Or(this._settings,{}),sunRays:Nr(this._settings,{}),busy:!1}},this._openAlignDialog=()=>{if(!this._norm||!this._serverCfg)return;const t=this._serverCfg.spaces||[],e=Xd(this._serverCfg,this._layout||{}),i=Math.ceil(10*e.report.maxShiftCm)/10,s=t.find(t=>null!=t?.id&&String(t.id)===e.report.maxSpace),n=t.length>1&&s?String(s.title||s.id):"";this._alignDialog={report:e.report,config:e.config,layout:e.layout,cm:i,where:n,changed:e.changed,busy:!1}},this._optimizeUndoBusy=!1,this._openBackupExport=()=>{this._settingsDialog=null,this._backupExportDialog={kind:"full",planOnly:!1,busy:!1,error:""}},this._openRulesDialog=()=>{if(!this._norm)return;const t=this._settings.icon_rules,e=(t&&t.length?t:jt).map(t=>({...t}));this._rulesDialog={rules:e,test:"",busy:!1}},this._climateCache=null,this._gearPtCache=new WeakMap}get _canEdit(){return!!this._norm&&(!0===this._serverCanWrite||!1!==this._serverCanWrite&&!0===this.hass?.user?.is_admin)}get _kiosk(){return!!this._config?.kiosk}_showKioskDots(){this._kioskDots=!0,clearTimeout(this._kioskDotsTimer),this._kioskDotsTimer=window.setTimeout(()=>this._kioskDots=!1,2500)}get _modeTransitionBusy(){return this._modeTransitionPreparing||this._modeTransition.active}_cssColor(t,e){const i=String(t||"").trim();if(!i)return e;const s=document.createElement("span");s.style.cssText=`position:absolute;visibility:hidden;color:${i}`,this.renderRoot.append(s);const n=getComputedStyle(s).color||e;return s.remove(),n}_currentModeVisual(t=this._mode){const e=this._modeTransition.presented||this._modeTransitionVisual;if(e)return{...e,viewport:{...e.viewport,viewBox:{...e.viewport.viewBox}}};const i=this._stageEl;if(!i||i.clientWidth<=0||i.clientHeight<=0)return null;const s=this.renderRoot.querySelector(".editorchrome"),n=this._viewOr(this._baseVb()),o=this.renderRoot.querySelector(".hp-paper"),r=this.renderRoot.querySelector(".hp-backdrop"),a=this.renderRoot.querySelector(".zoomwrap"),l=a?getComputedStyle(a).filter:"",c=Number(/brightness\(([^)]+)\)/.exec(l)?.[1]);return{presentedMode:t,editorChromeHeight:"view"===t?0:s?.getBoundingClientRect().height||0,stageWidth:i.clientWidth,stageHeight:i.clientHeight,viewport:ou(n,i.clientWidth),stageColor:getComputedStyle(i).backgroundColor||"rgb(255, 255, 255)",paperColor:o?getComputedStyle(o).fill:"rgb(255, 255, 255)",sceneBrightness:Number.isFinite(c)?c:1,architectureOpacity:"decor"===t?.35:1,backdropOpacity:r&&Number(getComputedStyle(r).opacity)||1,viewWeight:"view"===t?1:0,editorWeight:"view"===t?0:1,toolbarContentOpacity:"view"===t?0:1}}_viewForModeTarget(t,e,i,s,n){const o=Di(this._baseVb(),s/Math.max(1,n)),r=Math.min(Pp.ZOOM_MAX,Math.max(Pp.ZOOM_MIN,t)),a=o.w/r,l=o.h/r,c=e??o.x+o.w/2,h=i??o.y+o.h/2;return this._clampView({x:c-a/2,y:h-l/2,w:a,h:l},o)}_targetStageColor(t){return"view"!==t?"rgb(255, 255, 255)":this._cssColor(this._stageBg(this._spaceDisplayForRender()),this._cssColor("var(--ha-card-background, var(--card-background-color, #111))","rgb(17, 17, 17)"))}_targetPaperColor(t){return"view"===t&&this._spaceModel().bg?this._cssColor("var(--ha-card-background, var(--card-background-color, #111))","rgb(17, 17, 17)"):"rgb(255, 255, 255)"}_targetBrightness(t){return 1}_applyModeTransitionFrame(t){t.targetMode===this._mode&&(this._modeTransitionVisual=t.presented,this._view={...t.presented.viewport.viewBox},this.requestUpdate())}_settleModeTransition(t){if(t.targetMode!==this._mode)return;const e=this._modeTransitionRequest;this._view={...t.to.viewport.viewBox},this._zoom=this._modeTransitionTargetZoom,this._modeTransitionVisual=null,this._modeTransitionPreparing=!1,this._lastValidStageSize=[t.to.stageWidth,t.to.stageHeight],"view"===this._mode&&(this._saveZoom(),this._viewModeSnap=null,this._modeTransitionEditorCamera=null),this.requestUpdate(),this.updateComplete.then(()=>{if(!this.isConnected||e!==this._modeTransitionRequest||this._modeTransitionBusy||t.targetMode!==this._mode)return;const i=this._stageEl;if(i&&i.clientWidth>0&&i.clientHeight>0&&(Math.abs(i.clientWidth-t.to.stageWidth)>.5||Math.abs(i.clientHeight-t.to.stageHeight)>.5)){const t=this._view;this._lastValidStageSize=[i.clientWidth,i.clientHeight],this._applyView(this._zoom,t?t.x+t.w/2:void 0,t?t.y+t.h/2:void 0),this.requestUpdate()}const s=this.renderRoot.activeElement;(!s||!s.isConnected||!!s.closest?.(".editorchrome, .stage"))&&this.renderRoot.querySelector(".modetab.active")?.focus?.({preventScroll:!0})})}_cancelModeTransition(t=!0){const e=!!this._modeTransition.state;this._modeTransitionRequest++,this._modeTransitionPreparing=!1,this._modeTransitionForceAtomic=!1,this._modeTransition.cancel(t),t&&e||(this._modeTransitionVisual=null)}_adoptMode(t){this._cancelModeTransition(!1),this._mode=t,"view"!==t&&(this._editorChromeMode=t)}_commitViewModeAtomic(t,e,i,s){if(this._modeTransitionPreparing=!1,this._modeTransitionVisual=null,this._modeTransitionForceAtomic=!1,this._zoom=e,t){const n=this._stageEl?.clientWidth||t.stageWidth,o=Math.max(1,t.stageHeight+t.editorChromeHeight);this._view=this._viewForModeTarget(e,i,s,n,o),this._lastValidStageSize=[n,o]}else this._view=null;this._viewModeSnap=null,this._modeTransitionEditorCamera=null,this._saveZoom(),this.requestUpdate()}_prepareModeTransition(t,e,i,s,n,o){this.updateComplete.then(()=>{if(!this.isConnected||t!==this._modeTransitionRequest||this._mode!==i)return void(t===this._modeTransitionRequest&&(this._modeTransitionPreparing=!1,this._modeTransitionVisual=null,this._modeTransitionForceAtomic=!1,this.requestUpdate()));const r=this.renderRoot.querySelector(".editorchrome"),a=r?.querySelector(".editorchrome-inner"),l="view"===i?0:a?.scrollHeight||a?.getBoundingClientRect().height||0,c=Math.max(1,e.stageHeight+e.editorChromeHeight),h=Math.max(1,c-l),d=this._stageEl?.clientWidth||e.stageWidth;if(d<=0||h<=0)return this._modeTransitionPreparing=!1,this._modeTransitionVisual=null,this._applyView(s,n,o),void this.requestUpdate();const u=this._viewForModeTarget(s,n,o,d,h),p={presentedMode:i,editorChromeHeight:l,stageWidth:d,stageHeight:h,viewport:ou(u,d),stageColor:this._targetStageColor(i),paperColor:this._targetPaperColor(i),sceneBrightness:this._targetBrightness(i),architectureOpacity:"decor"===i?.35:1,backdropOpacity:"decor"===i&&"backdrop"!==this._decorTool?.5:1,viewWeight:"view"===i?1:0,editorWeight:"view"===i?0:1,toolbarContentOpacity:"view"===i?0:1};this._modeTransitionPreparing=!1;const _=this._modeTransitionForceAtomic;this._modeTransitionForceAtomic=!1,this._modeTransition.start(e,p,i,this._reducedMotion||_?0:220)})}_slideTo(t,e){if(t===this._space)return;this._cancelModeTransition(!0);const i=window.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches;this._activeDraftId&&(this._resumeDraftBySpace[this._space]=this._activeDraftId),this._space=t,this._path=[],this._clearPlanSnapHover(),this._clearOpeningPlacement(!0),this._tool="draw",this._openWallAnchor=null,this._boundaryRestoreGuard=null,this._activeDraftId=null,this._draftSegmentCms=[],this._closingWallCm=null,this._selId=null,this._physicalSel=null,this._editorSecondary.closeForNavigation(),this._physicalDialog=null,this._physicalDrag=null,"plan"===this._mode&&"draw"===this._tool&&this._resumeLastDraft(),this._restoreZoom(),i||(this._slide=e,clearTimeout(this._slideTimer),this._slideTimer=window.setTimeout(()=>{this._slideTimer=void 0,this._slide="",this.requestUpdate()},190),this.requestUpdate())}_pickSpace(t){if(t===this._space)return;const e=this._model.map(t=>t.id),i=e.indexOf(this._space),s=e.indexOf(t);this._navApplied=!0,this._showFar=!1,this._frame=null,this._slideTo(t,i>=0&&s0&&Date.now()>=this._cyclePausedUntil&&this._model.length>1&&this._zoom<=1.001){const t=this._model.map(t=>t.id),e=t.indexOf(this._space);this._slideTo(t[(e+1)%t.length],"left"),this._showKioskDots()}}get _editing(){return"plan"===this._mode||"devices"===this._mode||"decor"===this._mode}get _markup(){return"plan"===this._mode}_newContinuityController(){return new Hc(()=>{this._resumeSettling="steady"!==this._continuity.state,this._continuityEpoch++,this.isConnected&&this.requestUpdate()})}get _labsIso(){return this._labs.active.includes("iso")}get _desiredProjection(){return"view"===this._mode&&this._labsIso&&"iso"===this._viewPreference[this._space]?"iso":"flat"}_saveViewPreference(){try{localStorage.setItem(wp,JSON.stringify(this._viewPreference))}catch{}}_logicalViewCenter(t){const e=this._view;if(!e)return null;const i=[e.x+e.w/2,e.y+e.h/2],s="iso"===t?Iu(i):i;return{x:s[0],y:s[1]}}_convertProjectionView(t,e){if(t===e)return;const i=this._logicalViewCenter(t);this._view=null;const s=i?"iso"===e?Au([i.x,i.y],0):[i.x,i.y]:null;this._applyView(this._zoom,s?.[0],s?.[1]),this._warmPatch({vp:this._warmViewportState()}),this.requestUpdate()}_setProjection(t){if(!this._labsIso||"view"!==this._mode)return;const e=this._effectiveProjection();if(this._viewPreference={...this._viewPreference,[this._space]:t},"iso"===t){const t=this._isoSceneKey();t&&this._isoFallback.delete(t)}this._saveViewPreference();const i=this._effectiveProjection();this._convertProjectionView(e,i)}_hashSpace(){return vu(window.location.hash||"")}connectedCallback(){this._connectedPath=location.pathname,this._routeDepartureHandled=!1,window.addEventListener("location-changed",this._onLocationChanged),window.addEventListener("popstate",this._onLocationChanged),this._continuityDisposed&&(this._continuity=this._newContinuityController(),this._continuityDisposed=!1,this._continuityPaintToken=-1);const t=(e=this.ownerDocument,Jd.get(e)?.resolved);var e;void 0!==t&&(this._glowScreenBlend=t),this._continuityUnsub?.(),this._continuityUnsub=Nc(this.ownerDocument,this._pageVisibility),super.connectedCallback(),this._motionMedia=window.matchMedia?.("(prefers-reduced-motion: reduce)"),this._reducedMotion=!!this._motionMedia?.matches,this._motionMedia?.addEventListener?.("change",this._onMotionChange),function(t){const e=Jd.get(t);if(e)return e.promise;const i={promise:Promise.resolve(!1)};return i.promise=tu(t).catch(()=>!1).then(t=>(i.resolved=t,t)),Jd.set(t,i),i.promise}(this.ownerDocument).then(t=>{t!==this._glowScreenBlend&&(this._glowScreenBlend=t,this.isConnected&&this.requestUpdate())}),this.hass&&this._ensureHaRegistryAuthority(),window.addEventListener("keydown",this._keyHandler),this._signer.start(()=>this.hass,()=>$s(this._serverCfg)),this._config?.kiosk&&Number(this._config?.cycle)>0&&(clearInterval(this._cycleTimer),this._cycleTimer=window.setInterval(()=>this._cycleTick(),1e3*Number(this._config.cycle))),window.addEventListener("hashchange",this._onHashChange),this._labsUnsub?.(),this._labsUnsub=function(t,e){Su.add(e);const i=Du(t);return"undefined"==typeof window&&e(i),()=>{Su.delete(e)}}(sp,this._onLabsSnapshot),this._booting?this._bootWatch():this._bootFading&&(clearTimeout(this._bootTimer),this._bootTimer=window.setTimeout(()=>{this._bootFading=!1},220)),this._bootSoft&&(clearTimeout(this._bootSoftTimer),this._bootSoftTimer=window.setTimeout(()=>{this._bootSoft=!1},lp)),!this._loadOk&&this._serverCfg&&this.hass&&this._scheduleLoadRetry(),!this._warmSlot&&this._config&&this._warmAdopt(),this._loadOk&&this._ensureLiveSyncSubscriptions(),this._warmVp&&!this._warmRevivePending&&void 0===this._warmReviveTimer&&(this._warmRevivePending=!0,this._warmReviveTimer=window.setTimeout(()=>this._warmReviveDialog(),0)),this._warmLongReturn&&this._beginResumeSettle(),this._warmLongReturn=!1,this.requestUpdate()}disconnectedCallback(){this._connectedPath&&location.pathname!==this._connectedPath&&this._leaveCardRoute(),window.removeEventListener("location-changed",this._onLocationChanged),window.removeEventListener("popstate",this._onLocationChanged),this._continuityUnsub?.(),this._continuityUnsub=void 0,this._motionMedia?.removeEventListener?.("change",this._onMotionChange),this._motionMedia=void 0,this._vacRaf&&(cancelAnimationFrame(this._vacRaf),this._vacRaf=0),this._refitRaf&&(cancelAnimationFrame(this._refitRaf),this._refitRaf=0),this._dayCycleTimer&&(clearInterval(this._dayCycleTimer),this._dayCycleTimer=0),this._dayCycleClockKey="",this._bootSettleRaf&&(cancelAnimationFrame(this._bootSettleRaf),this._bootSettleRaf=0),this._bootSettling=!1;for(const t of this._activityRt.values())clearTimeout(t.timer);window.removeEventListener("keydown",this._keyHandler),clearInterval(this._cycleTimer),clearTimeout(this._kioskDotsTimer),clearTimeout(this._kioskHoldTimer),clearTimeout(this._reloadRetry),clearTimeout(this._loadRetryTimer),this._loadRetryTimer=void 0,this._connHooked?.removeEventListener?.("ready",this._onConnReady),this._connHooked?.removeEventListener?.("disconnected",this._onConnLost),this._connHooked?.removeEventListener?.("reconnect-error",this._onConnLost),this._connHooked=null,this._haRegistryRelease?.(),this._haRegistryRelease=void 0,this._haRegistryConnection=null,this._signer.dispose(),clearTimeout(this._toastTimer),clearTimeout(this._slideTimer),this._modeTransition.dispose(),this._modeTransitionVisual=null,this._modeTransitionPreparing=!1,this._modeTransitionForceAtomic=!1,this._modeTransitionRequest++,this._slideTimer=void 0,this._slide="",clearTimeout(this._bootTimer),this._bootTimer=void 0,clearTimeout(this._bootSoftTimer),this._saveConfigDebounced.flush(),window.removeEventListener("hashchange",this._onHashChange),this._labsUnsub?.(),this._labsUnsub=void 0,clearTimeout(this._holdTimer),this._roViewport?.disconnect(),this._roViewport=void 0,this._roHdr?.disconnect(),this._roHdr=void 0,this._onWinResize&&(window.removeEventListener("resize",this._onWinResize),this._onWinResize=void 0),this._unsubCfg&&(this._unsubCfg(),this._unsubCfg=null),this._unsubLayout&&(this._unsubLayout(),this._unsubLayout=null),this._unsubVirtual&&(this._unsubVirtual(),this._unsubVirtual=null),this._unsubTrail&&(this._unsubTrail(),this._unsubTrail=void 0),this._liveSyncGeneration++,this._liveSyncAttempt=null,this._liveSyncConnection=null,clearTimeout(this._layoutSyncTimer),clearTimeout(this._duplicateColumnTimer);for(const t of this._glowFadeTimers.values())clearTimeout(t);for(const t of this._glowEnterRafs.values())cancelAnimationFrame(t);clearTimeout(this._glowFeatherResumeTimer),this._glowFeatherResumeTimer=0,this._glowFeatherSuspendUntil=0,this._glowFadeTimers.clear(),this._glowEnterRafs.clear(),this._glowEnteringSources.clear(),this._glowRenderedSources.clear(),this._glowLastAppearance.clear(),this._glowSourceSeq=0,this._warmSnapshot(),this._warmRevivePending=!1,clearTimeout(this._warmReviveTimer),this._warmReviveTimer=void 0,this._warmRelease(),this._openWallAnchor=null,this._boundaryRestoreGuard=null,this._clearPlanSnapHover(),this._clearOpeningPlacement(!0),this._touchContacts.clear(),this._touchSequenceMultitouch=!1,this._touchClickBlockUntil=0,this._editorSecondary.reset(),this._resumeSettling=!1,this._continuityHistory=[...this._continuityHistory,...this._continuity.trace].slice(-80),this._continuity.dispose(),this._continuityDisposed=!0,super.disconnectedCallback()}_onKey(t){if("Escape"===t.key&&this._vacFit)return this._vacFit=null,this._showToast(this._t("vac.cal_cancelled")),void t.stopPropagation();if("Escape"===t.key){if(this._tapConfirm)return void(this._tapConfirm=null);if(this._vacCalConfirm)return void(this._vacCalConfirm=null);if(this._decorEraseConfirm)return void(this._decorEraseConfirm=null);if(this._openingInfo)return void(this._openingInfo=null);if(this._infoCard)return void this._closeInfoCard();if(this._rulesDialog)return void(this._rulesDialog=null);if(this._alignDialog)return void(this._alignDialog=null);if(this._backupImportDialog)return void(this._backupImportDialog=null);if(this._backupExportDialog)return void(this._backupExportDialog=null);if(this._settingsDialog)return void(this._settingsDialog=null);if(this._markerDialog)return void(this._markerDialog=null);if(this._openingDialog)return void(this._openingDialog=null);if(this._physicalDialog)return void(this._physicalDialog=null);if(this._backdropDialog)return void(this._backdropDialog=null);if(this._decorShapeDialog)return void(this._decorShapeDialog=null);if(this._decorTextDialog)return void(this._decorTextDialog=null);if(this._spaceDialog&&!this._roomDialog)return this._spaceDialog=null,this._importQueue=[],void(this._importTotal=0);if(this._editorSecondary.hasOpenGroup)return t.preventDefault(),void this._editorSecondary.closeGroup(!0)}const e=t.composedPath?.()||[t.target],i=e.some(t=>t?.matches?.('input, textarea, select, [contenteditable="true"]')),s=e.some(t=>t?.classList?.contains?.("editor-secondary")),n=t.ctrlKey||t.metaKey,o=t.key.toLowerCase(),r=/^[a-z]$/.test(o),a="z"===o||!r&&"KeyZ"===t.code,l="y"===o||!r&&"KeyY"===t.code,c=n&&(a&&t.shiftKey||l),h=n&&a&&!t.shiftKey;if("decor"===this._mode){if((h||c)&&i)return;return c?(t.preventDefault(),void this._redoGeometry()):h?(t.preventDefault(),this._decorDraft?void(this._decorDraft=null):this._decorMove||this._dtDrag||this._bdDrag?void this._cancelDecorGesture():void this._undoGeometry()):"Delete"!==t.key&&"Backspace"!==t.key||!this._decorSel||i||s?void("Escape"===t.key&&(t.preventDefault(),this._decorDraft?this._decorDraft=null:this._decorMove||this._dtDrag||this._bdDrag?this._cancelDecorGesture():"furniture"===this._decorTool?(this._furnPalette=null,this._decorTool="select"):this._decorSel?this._decorSel=null:"select"!==this._decorTool?this._decorTool="select":this._setMode("view"))):(t.preventDefault(),void this._decorDeleteSel())}if(this._markup&&(!h&&!c||!i)){if(("Delete"===t.key||"Backspace"===t.key)&&this._physicalSel&&!i&&!s)return t.preventDefault(),void this._deletePhysicalSelection();if(c)return t.preventDefault(),void this._redoGeometry();if(h)return t.preventDefault(),this._rszDrag?void this._rszCancelDrag():"draw"!==this._tool&&"partition"!==this._tool||!this._path.length?"split"===this._tool&&this._splitSel?.pts?.length?(this._splitSel={...this._splitSel,pts:this._splitSel.pts.slice(0,-1)},void(this._splitSel.pts.length||(this._cursorPt=null))):void this._undoGeometry():void(this._activeDraftId&&this._path.length>1?this._undoGeometry():this._undoPoint());if("Escape"===t.key)return this._physicalDrag||this._physicalRotate?(t.preventDefault(),void this._cancelPhysicalGesture()):this._roomDialog?(t.preventDefault(),void this._roomDialogCancel()):"draw"===this._tool&&this._path.length||"partition"===this._tool&&this._path.length?(t.preventDefault(),void this._undoPoint()):this._physicalSel?(t.preventDefault(),void(this._physicalSel=null)):"resize"===this._tool?(t.preventDefault(),this._rszDrag?void this._rszCancelDrag():void(this._rszSel?this._rszSel=null:this._tool="draw")):"split"===this._tool?(t.preventDefault(),void(this._splitSel?.pts?.length?(this._splitSel={...this._splitSel,pts:this._splitSel.pts.slice(0,-1)},this._splitSel.pts.length||(this._cursorPt=null)):this._splitSel?this._splitSel=null:this._tool="draw")):"merge"===this._tool?(t.preventDefault(),void(this._mergeSel?this._mergeSel=null:this._tool="draw")):this._wallDialog?(t.preventDefault(),void(this._wallDialog=null)):"boundary"===this._tool?(t.preventDefault(),void(this._openWallAnchor?this._cancelBoundaryAnchor():this._tool="draw")):void("opening"!==this._tool&&"wallthick"!==this._tool&&"delroom"!==this._tool&&"partition"!==this._tool&&"column"!==this._tool||(t.preventDefault(),"opening"===this._tool&&this._clearOpeningPlacement(!0),this._tool="draw"))}}_undoPoint(){if(this._path.length){if(this._contourClosed)return this._path=this._path.slice(0,-1),void(this._closingWallCm=null);if(this._activeDraftId&&this._path.length>1&&this._curSpaceCfg){const t=this._geometrySnapshot();this._path=this._path.slice(0,-1),this._draftSegmentCms=this._draftSegmentCms.slice(0,-1);const e=this._curSpaceCfg,i=(e.room_drafts||[]).findIndex(t=>t.id===this._activeDraftId);return void(i>=0&&(this._path.length<2?(e.room_drafts.splice(i,1),e.room_drafts.length||delete e.room_drafts,this._activeDraftId=null):e.room_drafts[i]={id:this._activeDraftId,points:this._path.map(t=>[t[0]/kp,t[1]/kp]),segments:this._draftSegmentCms.map(t=>({cm:t}))},this._recordGeometry(this._t("history.draft_segment_delete"),t),this._saveConfig()))}this._path=this._path.slice(0,-1)}}static getConfigElement(){return document.createElement("houseplan-card-editor")}static getStubConfig(){return{type:"custom:houseplan-card"}}static _warmBootReset(t){for(const t of up.values())for(const e of t)clearTimeout(e.evict);up.clear(),mp=t&&t>0?t:1e4}static _warmBootStats(){let t=0,e=0;const i=[];for(const s of up.values())for(const n of s)t++,n.dlg&&(e++,i.push(n.dlg.kind));return{keys:up.size,slots:t,dlgs:e,drafts:i}}setConfig(t){this._config={icon_size:2.5,show_temperature:!0,live_states:!0,show_signal:!0,...t},this._config.kiosk&&(this._booting=!1,this._bootFading=!1),t.default_floor&&(this._space=t.default_floor);try{this._zoomBySpace=JSON.parse(localStorage.getItem(vp)||"{}")||{}}catch{this._zoomBySpace={}}try{const t=JSON.parse(localStorage.getItem(wp)||"{}")||{};this._viewPreference=Object.fromEntries(Object.entries(t).filter(t=>"flat"===t[1]||"iso"===t[1]))}catch{this._viewPreference={}}this._labs=Du(sp);try{const t=JSON.parse(localStorage.getItem(bp)||"null");this._kioskScale={icon:Cs(t?.icon),font:Cs(t?.font)}}catch{}try{const t=JSON.parse(localStorage.getItem(fp)||"null");t&&t.config&&Array.isArray(t.config.spaces)&&(this._serverCfg=t.config,this._cfgEpoch++,this._cfgRev=t.rev||0,this._cfgContentFingerprint=t.config_fingerprint||qc(t.config),this._layout=t.layout||{},this._layoutRev=t.layout_rev||0,this._layoutContentFingerprint=t.layout_fingerprint||qc(this._layout),this._virtualLights=rn(t.virtual_lights,this._cfgRev),this._serverStorage=!0,this._adoptInitialSpace(this._model))}catch{}"view"!==this._mode||this._view||(this._zoom=this._zoomBySpace[this._space]||1),this.isConnected&&(this._warmAdopt(),this._warmLongReturn&&this._beginResumeSettle(),this._warmLongReturn=!1)}_warmAdopt(){if(this._config?.kiosk)return;const t=_p(this._config);if(this._warmKey===t&&this._warmSlot)return;this._warmSlot&&this._warmRelease();const e=this.parentNode,i=this._warmIdx(e),s=up.get(t);if(!s||!s.length)return;const n=s.find(t=>t.owner===this._warmGen);if(n)return this._warmLongReturn=!!n.freed&&Date.now()-n.freed>=zc,clearTimeout(n.evict),n.evict=0,n.freed=0,n.live=!0,this._warmSlot=n,this._warmKey=t,n.frameFingerprint&&this._continuity.adoptCompleteFrame(n.frameFingerprint),void(!this._devices.length&&n.devices?.length&&(this._devices=[...n.devices]));const{slot:o,sure:r}=((t,e,i,s)=>{const n=t=>{const e=!!i&&t.place?.deref()===i;return e&&t.idx===s?4:t.live?0:e?3:2};let o=null,r=0,a=0,l=null;for(const i of t){if(i.owner===e)continue;l=i;const t=n(i);t<=0||(t>r?(o=i,r=t,a=1):t===r&&a++)}return!o||a>1?{slot:o||l,sure:!1}:{slot:o,sure:!0}})(s,this._warmGen,e,i);o&&(this._warmLongReturn=!!o.freed&&Date.now()-o.freed>=zc,this._booting=!1,this._bootFading=!1,this._hdrH=o.hdrH,this._bootSoft=!0,this.isConnected&&(clearTimeout(this._bootSoftTimer),this._bootSoftTimer=window.setTimeout(()=>{this._bootSoft=!1},lp)),this._warmKey=t,r?(clearTimeout(o.evict),o.evict=0,o.owner=this._warmGen,o.place=e?new WeakRef(e):null,o.idx=i,o.live=!0,this._warmSlot=o,this._warmVp=o.vp,o.frameFingerprint&&this._continuity.adoptCompleteFrame(o.frameFingerprint),!this._devices.length&&o.devices?.length&&(this._devices=[...o.devices]),this._warmAdoptViewport(this._config)):(this._warmSlot={owner:this._warmGen,path:location.pathname,place:e?new WeakRef(e):null,idx:i,live:!0,hdrH:o.hdrH,stageH:o.stageH,vp:null,frameFingerprint:"",devices:null,dlg:null,freed:0,evict:0},s.push(this._warmSlot),this._warmTrim(s)))}_warmIdx(t){const e=t?.children;if(!e)return-1;for(let t=0;t4;){const e=t.findIndex(t=>!t.live);if(e<0)break;clearTimeout(t[e].evict),t.splice(e,1)}}_warmScheduleEvict(t,e){if(clearTimeout(t.evict),!(t.dlg||t.vp&&"view"!==t.vp.mode))return;const i=t.freed,s=t.owner;t.evict=window.setTimeout(()=>{if(t.evict=0,t.freed!==i||t.owner!==s)return;var n;t.dlg=null,t.vp=(n=t.vp)&&"view"!==n.mode?{...n,mode:"view",zoom:n.snap?.space===n.space?n.snap.zoom:n.zoom,view:null,snap:null,tool:"draw",decorTool:"select",showHidden:!1,selId:null,rszSel:null,decorSel:null}:n,t.frameFingerprint="";const o=up.get(e);if(!t.live&&o&&o.length>1){const e=o.indexOf(t);e>=0&&o.splice(e,1)}},mp+250)}_warmAdoptViewport(t){const e=this._warmVp;if(!e||this._warmSlot?.path!==location.pathname)return void(this._warmVp=null);if(this._hashApplied||!this._model.find(t=>t.id===e.space))return void(this._warmVp=null);this._space=e.space,this._navApplied=!0,this._adoptMode("view"!==e.mode&&this._canEdit&&!t.kiosk?e.mode:"view"),this._pendingNavMode="view"===e.mode||this._canEdit||t.kiosk?null:e.mode,this._zoom=e.zoom;const i=this._effectiveProjection(),s=i===e.projection&&this._labsIso===e.activeLabsIso;if(this._view=s&&e.view?{...e.view}:null,this._viewModeSnap=s&&e.snap?{...e.snap}:null,!s&&e.logicalCenter){const t="iso"===i?Au([e.logicalCenter.x,e.logicalCenter.y],0):[e.logicalCenter.x,e.logicalCenter.y];this._applyView(e.zoom,t[0],t[1])}var n;this._tool="openwall"===(n=e.tool)||"closewall"===n?"boundary":"opening"===n?"draw":"string"==typeof n&&Sp.has(n)?n:"draw",this._decorTool=e.decorTool,this._showHidden=e.showHidden,this._showFar!==e.showFar&&(this._showFar=e.showFar,this._frame=null),this._selId=e.selId,this._rszSel=e.rszSel,this._decorSel=e.decorSel,this._warmVpArmed=!0}_warmPatch(t,e=!1){if(this._config?.kiosk)return;const i=_p(this._config);if(!this._warmSlot||this._warmKey===i){if(!this._warmSlot){if(!e)return;const t=this.parentNode;this._warmKey=i,this._warmSlot={owner:this._warmGen,path:location.pathname,place:t?new WeakRef(t):null,idx:this._warmIdx(t),live:!0,hdrH:this._hdrH,stageH:0,vp:null,frameFingerprint:"",devices:null,dlg:null,freed:0,evict:0};const s=up.get(i)||[];for(s.push(this._warmSlot),up.set(i,s),this._warmTrim(s);up.size>8;){const t=up.keys().next().value;if(void 0===t||t===i)break;for(const e of up.get(t)||[])clearTimeout(e.evict);up.delete(t)}}Object.assign(this._warmSlot,t)}}_warmViewportState(){const t=this._effectiveProjection();return{space:this._space,mode:this._mode,projection:t,activeLabsIso:this._labsIso,logicalCenter:this._logicalViewCenter(t),zoom:this._zoom,view:this._view?{...this._view}:null,snap:this._viewModeSnap?{...this._viewModeSnap}:null,tool:this._tool,decorTool:this._decorTool,showHidden:this._showHidden,showFar:this._showFar,selId:this._selId,rszSel:this._rszSel,decorSel:this._decorSel}}_warmDialogState(){const t=(t,e)=>({kind:t,space:this._space,mode:this._mode,data:e});return this._tapConfirm||this._alignDialog||this._mergeDialog||this._importDialog||this._backupExportDialog||this._backupImportDialog?null:this._openingInfo?t("openingInfo",this._openingInfo.id):this._infoCard?t("info",this._infoCard.id):this._rulesDialog?this._rulesDialog.busy?null:t("rules",this._rulesDialog):this._settingsDialog?this._settingsDialog.busy?null:t("settings",this._settingsDialog):this._markerDialog?this._markerDialog.busy?null:t("marker",this._markerDialog):this._openingDialog?t("opening",this._openingDialog):this._backdropDialog?t("backdrop",this._backdropDialog):this._decorShapeDialog?t("decorShape",this._decorShapeDialog):this._decorTextDialog?t("decorText",this._decorTextDialog):this._roomDialog?t("room",{editId:this._roomEditId,fill:this._roomFill,customFill:this._roomCustomFill,tempSrc:this._roomTempSrc,humSrc:this._roomHumSrc,srcOpen:this._roomSrcOpen,srcFilter:this._roomSrcFilter,nameScale:this._roomNameScale,labelScale:this._roomLabelScale,areaSel:this._areaSel,nameSel:this._nameSel,pendingSplit:this._pendingSplit,path:this._path}):this._spaceDialog?this._spaceDialog.busy?null:t("space",this._spaceDialog):null}_warmSnapshot(){if(this._booting||this._config?.kiosk||"steady"!==this._continuity.state)return;const t={vp:this._warmViewportState(),frameFingerprint:this._continuity.frameFingerprint,devices:this._devices};if(this._warmRevivePending||(t.dlg=this._warmDialogState()),this.isConnected&&this._warmSlot?.owner===this._warmGen){const e=this.parentNode;t.place=e?new WeakRef(e):null,t.idx=this._warmIdx(e)}this._warmPatch(t)}_warmReviveDialog(){this._warmRevivePending=!1;const t=this._warmSlot;if(this._warmReviveTimer=void 0,!t||!t.dlg)return;const e=t.dlg,i=t.freed;if(t.dlg=null,t.freed=0,clearTimeout(t.evict),t.evict=0,i&&!(Date.now()-i>mp)&&e.space===this._space&&e.mode===this._mode){switch(e.kind){case"space":this._spaceDialog={...e.data,busy:!1,savedBusy:!1};break;case"marker":this._markerDialog={...e.data,busy:!1};break;case"settings":this._settingsDialog={...e.data,busy:!1};break;case"rules":this._rulesDialog={...e.data,busy:!1};break;case"opening":this._openingDialog={...e.data};break;case"backdrop":this._backdropDialog={...e.data};break;case"decorShape":this._decorShapeDialog={...e.data};break;case"decorText":{this._decorTextDialog={...e.data};const t=String(this._decorTextDialog?.text??"").length;this._decorTextSelection={start:t,end:t};break}case"room":{const t=e.data;this._roomEditId=t.editId,this._roomFill=t.fill,this._roomCustomFill=t.customFill||null,this._roomTempSrc=t.tempSrc,this._roomHumSrc=t.humSrc,this._roomSrcOpen=t.srcOpen,this._roomSrcFilter=t.srcFilter,this._roomNameScale=t.nameScale,this._roomLabelScale=t.labelScale,this._areaSel=t.areaSel,this._nameSel=t.nameSel,this._pendingSplit=t.pendingSplit,this._path=t.path,this._roomDialog=!0;break}case"info":{const t=this._devices.find(t=>t.id===e.data);t&&(this._infoCard=t);break}case"openingInfo":{const t=(this._curSpaceCfg?.openings||[]).find(t=>t.id===e.data);t&&(this._openingInfo=t);break}}this.requestUpdate()}}_cacheSnapshot(){if(this._serverCfg)try{this._cfgContentFingerprint=qc(this._serverCfg),this._layoutContentFingerprint=qc(this._layout),this._virtualLights=function(t,e,i){if(t.configRev===i)return t;const s=new Set((Array.isArray(e?.markers)?e.markers:[]).filter(hn).map(t=>t.id));return{...t,configRev:i,off:new Set([...t.off].filter(t=>s.has(t)))}}(this._virtualLights,this._serverCfg,this._cfgRev),localStorage.setItem(fp,JSON.stringify({config:this._serverCfg,rev:this._cfgRev,config_fingerprint:this._cfgContentFingerprint,layout:this._layout,layout_rev:this._layoutRev,layout_fingerprint:this._layoutContentFingerprint,virtual_lights:ln(this._virtualLights)}))}catch{}}_beginContinuityCandidate(t,e,i="plan"){return this._booting&&!this._continuity.hasCompleteFrame?this._continuity.token:(this._continuityDataReady=e,this._continuityPaintToken=-1,this._stagedDeviceSnapshotToken=-1,this._resumeSettling=!0,this._continuity.beginCandidate(t,i))}_continuityStageValid(){const t=this._stageEl;return!!t&&t.clientWidth>0&&t.clientHeight>0}_continuityAssetsReady(){if(!this._model.length)return!1;const t=this._model.length?this._spaceModel():null;return!t?.bg?.href||this._signer.isReady(this.hass,t.bg.href)}_initialSpaceSelection(t){return function(t){const e=new Set(t.spaceIds.filter(t=>!!t));if(!e.size)return{id:null,source:"none"};const i=[["hash",!1===t.acceptHash?null:t.hashSpace],["current",t.preserveCurrent?t.currentSpace:null],["saved",t.savedSpace],["default",t.defaultSpace],["first",t.spaceIds[0]]];for(const[t,s]of i)if(s&&e.has(s))return{id:s,source:t};return{id:null,source:"none"}}({spaceIds:t.map(t=>t.id),hashSpace:this._hashSpace(),acceptHash:!this._hashApplied,currentSpace:this._space,preserveCurrent:this._hashApplied||this._navApplied||this._warmVpArmed,savedSpace:this._savedNav()?.space,defaultSpace:this._config?.default_floor})}_adoptInitialSpace(t){const e=this._initialSpaceSelection(t);return e.id?(this._space=e.id,"hash"===e.source&&(this._hashApplied=!0),"saved"===e.source&&(this._navApplied=!0),e):e}_candidateBackdrop(t,e=this._space){const i=Al(t),s=this._initialSpaceSelection(i).id||(i.some(t=>t.id===e)?e:i[0]?.id);return i.find(t=>t.id===s)?.bg?.href||""}_visualFrameFingerprint(){const t=this._stageEl,e=t?[t.clientWidth,t.clientHeight]:[0,0];return Bc([this._cfgRev,this._cfgContentFingerprint||qc(this._serverCfg),this._layoutRev,this._layoutContentFingerprint||qc(this._layout),this._space,this._mode,this._view,e,this._glowScreenBlend?"screen":"normal",this.hass?.themes?.darkMode??this.hass?.themes?.default_theme??""])}_settleContinuityFrame(){if(this._booting||!this._continuityStageValid())return;if(!this._continuity.hasCompleteFrame&&"steady"===this._continuity.state)return void(this._continuityAssetsReady()&&(this._renderSnapshotAt=Date.now(),this._continuity.markCompleteFrame(this._visualFrameFingerprint())));if(!this._continuityDataReady)return;if(!["holding","offline-stale","overlay-pending","overlay-visible","candidate-ready"].includes(this._continuity.state))return;const t=this._continuity.token;if(this._candidateDeviceSnapshot&&this._candidateDeviceSnapshot!==this._visibleDeviceSnapshot&&this._stagedDeviceSnapshotToken!==t)return this._stagedDeviceSnapshotToken=t,void this.requestUpdate();this._continuityPaintToken!==t&&(this._continuityPaintToken=t,this._continuity.candidateReady(t)&&this._continuity.commitAfterPaint(t,{updateComplete:()=>this.updateComplete,stageValid:()=>this.isConnected&&this._continuityStageValid(),assetsReady:()=>this._continuityAssetsReady(),frameFingerprint:()=>this._visualFrameFingerprint()}).then(e=>{e&&t===this._continuity.token?(this._resumeSettling=!1,this._renderSnapshotAt=Date.now(),this._candidateDeviceSnapshot&&(this._visibleDeviceSnapshot=this._candidateDeviceSnapshot),this._candidateDeviceSnapshot=null,this._stagedDeviceSnapshotToken=-1,this._warmSnapshot()):t===this._continuity.token&&(this._continuityPaintToken=-1,this._stagedDeviceSnapshotToken=-1,this._candidateDeviceSnapshot=null,this.requestUpdate())}))}_onBackdropLoaded(t,e){this._signer.markLoaded(this.hass,t,e),this._continuity.note("asset-ready"),this._continuityPaintToken=-1,"steady"!==this._continuity.state&&this.requestUpdate()}_renderRecoveryOverlay(){if(!this._continuity.overlayVisible&&"recovery-error"!==this._continuity.state)return G;const t="connection"===this._continuity.recoveryReason;return j`
t.stopPropagation()} @click=${t=>t.stopPropagation()} @@ -4818,17 +4822,23 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="da33c4f75cf2469e330b33f11fd5fa04d368 ${this._t("gs.north_letter")} ${null===t?"—":t+"°"} - `}_stageBg(t){const e=this._settingsDialog,i=this._spaceDialog,s=e?e.bgColor||"":es(this._settings,{bgColor:null});return(i&&"edit"===i.mode&&i.spaceId===this._space?i.bgColor||"":t.bgColor||"")||s}_stageBgHex(){const t=this._stageEl;if(t){const e=getComputedStyle(t).backgroundColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);if(e)return"#"+e.slice(1,4).map(t=>(+t).toString(16).padStart(2,"0")).join("")}return"#111111"}async _runAlignToGrid(){const t=this._alignDialog;if(t&&!t.busy&&this._serverCfg){this._clearGeometryGesture(),this._alignDialog={...t,busy:!0};try{this._saveConfigDebounced.pending()&&this._saveConfigDebounced.flush(),await this._writeChain;const e=await this.hass.callWS({type:"houseplan/plan/optimize",config:t.config,layout:t.layout,expected_config_rev:this._cfgRev,expected_layout_rev:this._layoutRev});this._serverCfg=t.config,this._layout=t.layout,this._geometryHistory.clear(),this._cfgRev=e?.config_rev??this._cfgRev+1,this._layoutRev=e?.layout_rev??this._layoutRev+1,this._canOptimizeUndo=!!e?.can_undo,this._undoKind=e?.can_undo?"optimize":null,this._dirtyPos.clear(),this._sentPos.clear(),this._cfgEpoch++,this._modelCache=null,this._frame=null,this._cacheSnapshot(),this._alignDialog=null,this.requestUpdate(),this._showToast(this._t("gs.align_done",{n:String(t.report.moved),m:String(t.report.migrated+t.report.canonicalized+t.report.wallsMerged+t.report.spansMerged)}))}catch(t){this._alignDialog&&(this._alignDialog={...this._alignDialog,busy:!1}),"conflict"===t?.code&&await Promise.all([this._reloadConfigOnly(!0),this._reloadLayoutOnly()]),this._showToast(this._t("toast.error",{err:this._errText(t)}))}}}async _undoPlanOptimization(){if(!this._canOptimizeUndo||this._optimizeUndoBusy)return;const t=this._undoKind;this._clearGeometryGesture(),this._optimizeUndoBusy=!0,this.requestUpdate();try{await this.hass.callWS({type:"houseplan/plan/optimize_undo",expected_config_rev:this._cfgRev,expected_layout_rev:this._layoutRev});const[e,i]=await Promise.all([this.hass.callWS({type:"houseplan/config/get"}),this.hass.callWS({type:"houseplan/layout/get"})]);this._serverCfg=e?.config||this._serverCfg,this._cfgRev=e?.rev??this._cfgRev,this._layout=i?.layout||this._layout,this._geometryHistory.clear(),this._layoutRev=i?.rev??this._layoutRev,this._canOptimizeUndo=!1,this._undoKind=null,this._cfgEpoch++,this._modelCache=null,this._frame=null,this._cacheSnapshot(),this.requestUpdate(),this._showToast(this._t("import"===t?"backup.import_undone":"gs.optimize_undone"))}catch(t){this._canOptimizeUndo=!1,this._undoKind=null,this._showToast(this._t("toast.error",{err:this._errText(t)}))}finally{this._optimizeUndoBusy=!1,this.requestUpdate()}}async _runBackupExport(){const t=this._backupExportDialog;if(t&&!t.busy){this._backupExportDialog={...t,busy:!0,error:""};try{const e=await this.hass.callWS({type:"houseplan/export/create",kind:t.kind,space_id:"space"===t.kind?this._space:void 0,card_version:sp}),i=new Blob([JSON.stringify(e.document,null,2)+"\n"],{type:"application/json;charset=utf-8"}),s=URL.createObjectURL(i),n=document.createElement("a");n.href=s,n.download=e.filename||`houseplan-${t.kind}.json`,n.style.display="none",document.body.append(n),n.click(),n.remove(),window.setTimeout(()=>URL.revokeObjectURL(s),0),this._backupExportDialog=null,this._showToast(this._t("backup.export_done"))}catch(t){this._backupExportDialog&&(this._backupExportDialog={...this._backupExportDialog,busy:!1,error:this._backupErrorText(t)})}}}async _pickBackupImport(t){const e=t.target,i=e.files?.[0];if(e.value="",i){this._settingsDialog=null,this._backupImportDialog={filename:i.name,size:i.size,token:"",preview:null,expectedConfigRev:this._cfgRev,expectedLayoutRev:this._layoutRev,duplicatePolicy:"skip",confirmMissing:!1,busy:!0,error:""};try{this._saveConfigDebounced.pending()&&this._saveConfigDebounced.flush(),await this._writeChain,this._persistLayout.pending()&&this._persistLayout.flush();const t="/api/houseplan/import/preview?duplicate_policy=skip",e={method:"POST",body:i,headers:{"content-type":"application/json"}},s=this.hass?.fetchWithAuth?await this.hass.fetchWithAuth(t,e):await fetch(t,{...e,headers:{...e.headers,...this.hass?.auth?.data?.access_token?{authorization:`Bearer ${this.hass.auth.data.access_token}`}:{}}}),n=await s.json().catch(()=>({}));if(!s.ok||n.error)throw Object.assign(new Error(n.message||n.error||`HTTP ${s.status}`),{code:n.error});this._backupImportDialog&&(this._backupImportDialog={...this._backupImportDialog,token:n.token,preview:n.preview,expectedConfigRev:n.expected_config_rev,expectedLayoutRev:n.expected_layout_rev,busy:!1})}catch(t){this._backupImportDialog&&(this._backupImportDialog={...this._backupImportDialog,busy:!1,error:this._backupErrorText(t)})}}}async _setBackupDuplicatePolicy(t){const e=this._backupImportDialog;if(e&&!e.busy&&e.token&&t!==e.duplicatePolicy){this._backupImportDialog={...e,duplicatePolicy:t,busy:!0,error:""};try{const i=await this.hass.callWS({type:"houseplan/import/revalidate",token:e.token,duplicate_policy:t});this._backupImportDialog&&(this._backupImportDialog={...this._backupImportDialog,preview:{...e.preview,...i.preview},expectedConfigRev:i.expected_config_rev,expectedLayoutRev:i.expected_layout_rev,confirmMissing:!1,busy:!1})}catch(t){this._backupImportDialog&&(this._backupImportDialog={...this._backupImportDialog,duplicatePolicy:e.duplicatePolicy,preview:e.preview,confirmMissing:e.confirmMissing,busy:!1,error:this._backupErrorText(t)})}}}async _applyBackupImport(){const t=this._backupImportDialog;if(!t||t.busy||!t.token||!t.preview)return;if(t.preview.confirmation_required&&!t.confirmMissing)return;this._backupImportDialog={...t,busy:!0,error:""};const e=this._space;try{const i=await this.hass.callWS({type:"houseplan/import/apply",token:t.token,expected_config_rev:t.expectedConfigRev,expected_layout_rev:t.expectedLayoutRev,duplicate_policy:t.duplicatePolicy,confirm_missing_content:t.confirmMissing}),[s,n]=await Promise.all([this.hass.callWS({type:"houseplan/config/get"}),this.hass.callWS({type:"houseplan/layout/get"})]);this._adoptStructuralResponses(s,n),this._geometryHistory.clear(),this._dirtyPos.clear(),this._sentPos.clear(),this._defPos={},this._cfgEpoch++,this._modelCache=null,this._frame=null,this._visibleDeviceSnapshot=null,this._candidateDeviceSnapshot=null,this._stagedDeviceSnapshotToken=-1,this._capturedSnapshotConfigEpoch=-1,this._regSignature="",this._signer.invalidate(this.hass),this._resign(),this._maybeRebuildDevices();const o=this._serverCfg?.spaces||[];this._space="space"===i.kind&&i.space_id?i.space_id:o.some(t=>t.id===e)?e:o[0]?.id||this._space,this._backupImportDialog=null,this._cacheSnapshot(),this.requestUpdate();const r="space"===i.kind?t.preview.counts:i.counts;this._showToast(this._t("space"===i.kind?"backup.space_done":"backup.full_done",{spaces:String(r?.spaces||0),rooms:String(r?.rooms||0),markers:String(r?.markers||0)}))}catch(t){if("conflict"===t?.code&&this._backupImportDialog?.token)try{const t=this._backupImportDialog,e=await this.hass.callWS({type:"houseplan/import/revalidate",token:t.token,duplicate_policy:t.duplicatePolicy});return void(this._backupImportDialog={...t,preview:{...t.preview,...e.preview},expectedConfigRev:e.expected_config_rev,expectedLayoutRev:e.expected_layout_rev,confirmMissing:!1,busy:!1,error:this._t("backup.revalidated")})}catch(e){t=e}this._backupImportDialog&&(this._backupImportDialog={...this._backupImportDialog,busy:!1,error:this._backupErrorText(t)})}}_renderBackupExportDialog(){const t=this._backupExportDialog,e=(this._serverCfg?.spaces||[]).find(t=>t.id===this._space);return j``}_stageBg(t){const e=this._settingsDialog,i=this._spaceDialog,s=e?e.bgColor||"":es(this._settings,{bgColor:null});return(i&&"edit"===i.mode&&i.spaceId===this._space?i.bgColor||"":t.bgColor||"")||s}_stageBgHex(){const t=this._stageEl;if(t){const e=getComputedStyle(t).backgroundColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);if(e)return"#"+e.slice(1,4).map(t=>(+t).toString(16).padStart(2,"0")).join("")}return"#111111"}async _runAlignToGrid(){const t=this._alignDialog;if(t&&!t.busy&&this._serverCfg){this._clearGeometryGesture(),this._alignDialog={...t,busy:!0};try{this._saveConfigDebounced.pending()&&this._saveConfigDebounced.flush(),await this._writeChain;const e=await this.hass.callWS({type:"houseplan/plan/optimize",config:t.config,layout:t.layout,expected_config_rev:this._cfgRev,expected_layout_rev:this._layoutRev});this._serverCfg=t.config,this._layout=t.layout,this._geometryHistory.clear(),this._cfgRev=e?.config_rev??this._cfgRev+1,this._layoutRev=e?.layout_rev??this._layoutRev+1,this._canOptimizeUndo=!!e?.can_undo,this._undoKind=e?.can_undo?"optimize":null,this._dirtyPos.clear(),this._sentPos.clear(),this._cfgEpoch++,this._modelCache=null,this._frame=null,this._cacheSnapshot(),this._alignDialog=null,this.requestUpdate(),this._showToast(this._t("gs.align_done",{n:String(t.report.moved),m:String(t.report.migrated+t.report.canonicalized+t.report.wallsMerged+t.report.spansMerged)}))}catch(t){this._alignDialog&&(this._alignDialog={...this._alignDialog,busy:!1}),"conflict"===t?.code&&await Promise.all([this._reloadConfigOnly(!0),this._reloadLayoutOnly()]),this._showToast(this._t("toast.error",{err:this._errText(t)}))}}}async _undoPlanOptimization(){if(!this._canOptimizeUndo||this._optimizeUndoBusy)return;const t=this._undoKind;this._clearGeometryGesture(),this._optimizeUndoBusy=!0,this.requestUpdate();try{await this.hass.callWS({type:"houseplan/plan/optimize_undo",expected_config_rev:this._cfgRev,expected_layout_rev:this._layoutRev});const[e,i]=await Promise.all([this.hass.callWS({type:"houseplan/config/get"}),this.hass.callWS({type:"houseplan/layout/get"})]);this._serverCfg=e?.config||this._serverCfg,this._cfgRev=e?.rev??this._cfgRev,this._layout=i?.layout||this._layout,this._geometryHistory.clear(),this._layoutRev=i?.rev??this._layoutRev,this._canOptimizeUndo=!1,this._undoKind=null,this._cfgEpoch++,this._modelCache=null,this._frame=null,this._cacheSnapshot(),this.requestUpdate(),this._showToast(this._t("import"===t?"backup.import_undone":"gs.optimize_undone"))}catch(t){this._canOptimizeUndo=!1,this._undoKind=null,this._showToast(this._t("toast.error",{err:this._errText(t)}))}finally{this._optimizeUndoBusy=!1,this.requestUpdate()}}async _runBackupExport(){const t=this._backupExportDialog;if(t&&!t.busy){this._backupExportDialog={...t,busy:!0,error:""};try{const e=await this.hass.callWS({type:"houseplan/export/create",kind:t.kind,space_id:"space"===t.kind?this._space:void 0,..."space"===t.kind&&t.planOnly?{plan_only:!0}:{},card_version:sp}),i=new Blob([JSON.stringify(e.document,null,2)+"\n"],{type:"application/json;charset=utf-8"}),s=URL.createObjectURL(i),n=document.createElement("a");n.href=s,n.download=e.filename||`houseplan-${t.kind}.json`,n.style.display="none",document.body.append(n),n.click(),n.remove(),window.setTimeout(()=>URL.revokeObjectURL(s),0),this._backupExportDialog=null,this._showToast(this._t("backup.export_done"))}catch(t){this._backupExportDialog&&(this._backupExportDialog={...this._backupExportDialog,busy:!1,error:this._backupErrorText(t)})}}}async _pickBackupImport(t){const e=t.target,i=e.files?.[0];if(e.value="",i){this._settingsDialog=null,this._backupImportDialog={filename:i.name,size:i.size,token:"",preview:null,expectedConfigRev:this._cfgRev,expectedLayoutRev:this._layoutRev,duplicatePolicy:"skip",confirmMissing:!1,busy:!0,error:""};try{this._saveConfigDebounced.pending()&&this._saveConfigDebounced.flush(),await this._writeChain,this._persistLayout.pending()&&this._persistLayout.flush();const t="/api/houseplan/import/preview?duplicate_policy=skip",e={method:"POST",body:i,headers:{"content-type":"application/json"}},s=this.hass?.fetchWithAuth?await this.hass.fetchWithAuth(t,e):await fetch(t,{...e,headers:{...e.headers,...this.hass?.auth?.data?.access_token?{authorization:`Bearer ${this.hass.auth.data.access_token}`}:{}}}),n=await s.json().catch(()=>({}));if(!s.ok||n.error)throw Object.assign(new Error(n.message||n.error||`HTTP ${s.status}`),{code:n.error});this._backupImportDialog&&(this._backupImportDialog={...this._backupImportDialog,token:n.token,preview:n.preview,expectedConfigRev:n.expected_config_rev,expectedLayoutRev:n.expected_layout_rev,busy:!1})}catch(t){this._backupImportDialog&&(this._backupImportDialog={...this._backupImportDialog,busy:!1,error:this._backupErrorText(t)})}}}async _setBackupDuplicatePolicy(t){const e=this._backupImportDialog;if(e&&!e.busy&&e.token&&t!==e.duplicatePolicy){this._backupImportDialog={...e,duplicatePolicy:t,busy:!0,error:""};try{const i=await this.hass.callWS({type:"houseplan/import/revalidate",token:e.token,duplicate_policy:t});this._backupImportDialog&&(this._backupImportDialog={...this._backupImportDialog,preview:{...e.preview,...i.preview},expectedConfigRev:i.expected_config_rev,expectedLayoutRev:i.expected_layout_rev,confirmMissing:!1,busy:!1})}catch(t){this._backupImportDialog&&(this._backupImportDialog={...this._backupImportDialog,duplicatePolicy:e.duplicatePolicy,preview:e.preview,confirmMissing:e.confirmMissing,busy:!1,error:this._backupErrorText(t)})}}}async _applyBackupImport(){const t=this._backupImportDialog;if(!t||t.busy||!t.token||!t.preview)return;if(t.preview.confirmation_required&&!t.confirmMissing)return;this._backupImportDialog={...t,busy:!0,error:""};const e=this._space;try{const i=await this.hass.callWS({type:"houseplan/import/apply",token:t.token,expected_config_rev:t.expectedConfigRev,expected_layout_rev:t.expectedLayoutRev,duplicate_policy:t.duplicatePolicy,confirm_missing_content:t.confirmMissing}),[s,n]=await Promise.all([this.hass.callWS({type:"houseplan/config/get"}),this.hass.callWS({type:"houseplan/layout/get"})]);this._adoptStructuralResponses(s,n),this._geometryHistory.clear(),this._dirtyPos.clear(),this._sentPos.clear(),this._defPos={},this._cfgEpoch++,this._modelCache=null,this._frame=null,this._visibleDeviceSnapshot=null,this._candidateDeviceSnapshot=null,this._stagedDeviceSnapshotToken=-1,this._capturedSnapshotConfigEpoch=-1,this._regSignature="",this._signer.invalidate(this.hass),this._resign(),this._maybeRebuildDevices();const o=this._serverCfg?.spaces||[];this._space="space"===i.kind&&i.space_id?i.space_id:o.some(t=>t.id===e)?e:o[0]?.id||this._space,this._backupImportDialog=null,this._cacheSnapshot(),this.requestUpdate();const r="space"===i.kind?t.preview.counts:i.counts;this._showToast(this._t("space"===i.kind?"backup.space_done":"backup.full_done",{spaces:String(r?.spaces||0),rooms:String(r?.rooms||0),markers:String(r?.markers||0)}))}catch(t){if("conflict"===t?.code&&this._backupImportDialog?.token)try{const t=this._backupImportDialog,e=await this.hass.callWS({type:"houseplan/import/revalidate",token:t.token,duplicate_policy:t.duplicatePolicy});return void(this._backupImportDialog={...t,preview:{...t.preview,...e.preview},expectedConfigRev:e.expected_config_rev,expectedLayoutRev:e.expected_layout_rev,confirmMissing:!1,busy:!1,error:this._t("backup.revalidated")})}catch(e){t=e}this._backupImportDialog&&(this._backupImportDialog={...this._backupImportDialog,busy:!1,error:this._backupErrorText(t)})}}_renderBackupExportDialog(){const t=this._backupExportDialog,e=(this._serverCfg?.spaces||[]).find(t=>t.id===this._space);return j`this._backupExportDialog=null}>
${this._t("backup.export_hint")}
+ ${"space"===t.kind&&e?j``:G}
${this._t("backup.privacy_warning")}
${t.error?j``:G}
@@ -4849,6 +4859,7 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="da33c4f75cf2469e330b33f11fd5fa04d368 ${e?j`
${this._t("full"===e.kind?"backup.full":"backup.current_space")} + ${e.plan_only?j`${this._t("backup.plan_only_preview")}`:G} ${this._t("same"===e.source?"backup.same_source":"backup.foreign_source")} ${this._t("backup.created",{value:e.created_at||"—"})} ${this._t("backup.versions",{card:e.card_version||"—",integration:e.integration_version||"—",model:String(e.model_version??"—")})} diff --git a/dist/houseplan-card.js b/dist/houseplan-card.js index 090f3dc8..e19774d8 100755 --- a/dist/houseplan-card.js +++ b/dist/houseplan-card.js @@ -1,4 +1,4 @@ -globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="da33c4f75cf2469e330b33f11fd5fa04d36861af0c75eb110758b7fff2688fcb";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__="7a788419e5d1a6e4b944a5d436d2cffd00795c3237791689b1401b73925353f8";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.text}${t.suffix}`)} ${null!=t.lqiText?j`${t.lqiText}`:G} - `;var o}function Io(t,e,i){return{kind:"none",reason:"none",generation:t,expiresAt:null,color:e,diameterScale:i,animated:!1,reducedMotionIndicator:"none"}}const Eo=new WeakMap;function zo(t){const e=function(t){const e=t?.connection||t;return!e||"object"!=typeof e&&"function"!=typeof e?null:e}(t);if(!e)return null;let i=Eo.get(e);return i||(i={revision:0,loaded:!1,configEntries:{},manifests:{},listeners:new Set,refs:0},Eo.set(e,i)),i}function Fo(t){t.revision++;for(const e of[...t.listeners])try{e()}catch{}}function Oo(t,e){const i=zo(t);if(!i)return()=>{};i.refs++,i.listeners.add(e),async function(t,e){e.loading||"function"!=typeof t?.callWS?e.loading:e.loaded&&e.loadedAt&&Date.now()-e.loadedAt<3e5||(e.loading=(async()=>{const[i,s]=await Promise.allSettled([t.callWS({type:"config_entries/get"}),t.callWS({type:"manifest/list"})]);"fulfilled"===i.status&&Array.isArray(i.value)&&(e.configEntries=Object.fromEntries(i.value.filter(t=>"string"==typeof t?.entry_id).map(t=>[t.entry_id,t]))),"fulfilled"===s.status&&Array.isArray(s.value)&&(e.manifests=Object.fromEntries(s.value.filter(t=>"string"==typeof t?.domain).map(t=>[t.domain,t]))),e.loaded=!0,e.loadedAt=Date.now(),e.loading=void 0,Fo(e)})().catch(()=>{e.loaded=!0,e.loadedAt=Date.now(),e.loading=void 0,Fo(e)}),e.loading)}(t,i),async function(t,e){if(e.unsubscribe||e.subscribing)return e.subscribing;const i=t?.connection?.subscribeMessage;"function"==typeof i&&(e.subscribing=(async()=>{try{e.unsubscribe=await i.call(t.connection,t=>{const i=Array.isArray(t)?t:Array.isArray(t?.entries)?t.entries:t?[t]:[];let s=!1;const n={...e.configEntries};for(const t of i){const e=t?.entry||t?.data?.entry||("string"==typeof t?.entry_id?t:null),i=e?.entry_id;if("string"!=typeof i)continue;const o=t?.type||t?.action||t?.data?.action;"removed"===o||"remove"===o?delete n[i]:n[i]=e,s=!0}s&&(e.configEntries=n,Fo(e))},{type:"config_entries/subscribe"})}catch{}finally{0===e.refs&&(e.unsubscribe?.(),e.unsubscribe=void 0),e.subscribing=void 0}})(),e.subscribing)}(t,i);let s=!1;return()=>{s||(s=!0,i.listeners.delete(e),i.refs=Math.max(0,i.refs-1),i.refs||(i.unsubscribe?.(),i.unsubscribe=void 0))}}function No(t){const e=zo(t);return e?{revision:e.revision,loaded:e.loaded,configEntries:e.configEntries,manifests:e.manifests}:{revision:0,loaded:!1,configEntries:{},manifests:{}}}function Lo(t,e,i=No(t),s){const n=i.manifests[e];return function(t,e){if("function"!=typeof t?.localize)return"";try{const i=`component.${e}.title`,s=t.localize(i);return"string"==typeof s&&s!==i?s:""}catch{return""}}(t,e)||String(n?.name||"")||String(s||"")||e.replace(/_/g," ")}function Ho(t,e,i,s,n){const o=String(e||"").trim();if(!o)return null;const r=n?s.configEntries[n]:null;return{domain:o,label:Lo(t,o,s,r?.title),configEntryId:n,configEntryTitle:r?.title,confidence:i}}function qo(t){const e={"registry-owner":0,"entity-platform":1,"identifier-fallback":2},i=new Set;return t.filter(t=>!!t).sort((t,i)=>e[t.confidence]-e[i.confidence]||t.label.localeCompare(i.label)||t.domain.localeCompare(i.domain)).filter(t=>{const e=`${t.domain}\n${t.configEntryId||""}`;return!i.has(e)&&(i.add(e),!0)})}function Bo(t,e,i,s=No(t)){const n=i.entities?.[e]||t?.entities?.[e];if(!n)return null;const o=n.config_entry_id||void 0,r=o?s.configEntries[o]?.domain:"";return Ho(t,n.platform||r,"entity-platform",s,o)}var jo={"editor.context_actions":"Actions: {object}","editor.tool_options":"Tool options: {tool}","editor.palette":"Palette: {tool}","editor.open_group":"Tool group: {group}","editor.group_active":"{group} — active: {item}","editor.disabled_action":"{action} is unavailable: {reason}","btn.properties":"Properties","btn.keep_as_walls":"Keep as closed walls","title.markup_partition":"Partition: draw one independent physical wall","title.markup_select":"Select and edit walls, columns and saved outlines","title.markup_column":"Column: click a grid point to place a square column","markup.partition":"Partition","markup.column":"Column","markup.hint_partition":"two clicks draw one partition · Shift locks to 45°","markup.hint_column":"click a grid point to place a square column","history.draft_segment":"Add room-draft segment","history.draft_merge":"Join unfinished room outlines","history.draft_segment_delete":"Remove room-draft segment","history.partition_add":"Add partition","history.column_add":"Add column","history.physical_edit":"Change physical object","history.physical_delete":"Delete physical object","history.physical_move":"Move physical object","history.contour_to_partitions":"Convert contour to closed walls","toast.column_duplicate":"A column with the same centre and outer size already exists","confirm.delete_draft":"Delete the whole unfinished room contour?","confirm.delete_draft_segment":"Delete this draft segment? The remaining contour may split in two.","physical.partition_properties":"Partition properties","physical.column_properties":"Column properties","physical.draft_properties":"Draft segment properties","physical.shape":"Shape","physical.square":"Square","physical.circle":"Circle","physical.diameter":"Diameter","physical.side":"Side","physical.rotation":"Rotation angle","physical.length":"Length","physical.allowed_range":"Allowed: 1–{max} {unit}","physical.delete_segment":"Delete segment","physical.delete_draft":"Delete entire outline","physical.partition_size_title":"Thickness of the partition created by the second click (1–100 cm).","physical.column_size_title":"Outer side of the square column placed by the click (1–150 cm).","card.title":"House plan","count.devices":"{n} dev.","empty.no_spaces":"No spaces yet.","empty.add_first":"Add the first space and upload a floor plan.","empty.install":'Install the House Plan integration and add it in "Devices & services".',"btn.add_space":"Add space","btn.cancel":"Cancel","btn.save":"Save","btn.close":"Close","btn.delete":"Delete","btn.edit":"Edit","btn.open_in_ha":"Open in HA","btn.reset":"Reset","btn.attach":"Attach…","btn.upload":"Upload…","btn.replace":"Replace…","title.zoom_in":"Zoom in","title.zoom_out":"Zoom out","title.zoom_fit":"Fit all","title.add_device":"Add a device to the plan","title.show_all":"Show hidden and disabled devices as service markers (this tab only)","title.markup":"Room markup: grid, lines, outlines","title.configure_space":"Configure space","title.add_space":"Add space","title.markup_add":"Room outline: connect grid dots until the room closes; Shift locks the direction in 45° steps","title.markup_merge":"Merge: click one room, then the neighbour it shares a wall with","title.markup_split":"Split a room: click the room, then two points on its walls","title.markup_delroom":"Delete room: click inside a room and confirm","title.markup_boundary":"Boundary: click two points on a solid shared wall to make that stretch virtual; click a dashed stretch to restore it","title.need_plan":"Upload a floor-plan image","markup.add":"Room outline","markup.merge":"Merge","markup.split":"Split","markup.resize":"Resize","title.markup_resize":"Resize rooms: drag a wall handle; click a room for a corner scale frame","markup.hint_resize":"drag a wall handle · click a room — corner frame · Esc cancels a drag · Ctrl+Z — undo a step","markup.opening":"Opening","markup.boundary":"Boundary","markup.delete_room":"Delete room","history.undo":"Undo","history.redo":"Redo","history.undo_named":"Undo: {name} (Ctrl+Z)","history.redo_named":"Redo: {name} (Ctrl+Shift+Z / Ctrl+Y)","history.undo_empty":"Nothing to undo","history.redo_empty":"Nothing to redo","history.undone":"Undone: {name}","history.redone":"Redone: {name}","history.add_room":"Create room","history.split_room":"Split room","history.merge_rooms":"Merge rooms","history.resize_room":"Resize room","history.open_boundary":"Open boundary","history.close_boundary":"Restore wall","history.wall_thickness":"Change wall thickness","history.add_opening":"Add door or window opening","history.edit_opening":"Edit opening","history.move_opening":"Move opening","history.delete_opening":"Delete opening","history.delete_room":"Delete room","history.decor_add":"Add decor object","history.decor_edit":"Edit decor object","history.decor_move":"Move decor object","history.decor_transform":"Transform decor object","history.decor_delete":"Delete decor object","history.backdrop_transform":"Transform plan backdrop","title.markup_opening":"Openings: click a wall to place, click an opening to edit","opening.new":"New opening","opening.edit":"Opening","opening.door":"Door","opening.window":"Window","opening.gate":"Gate","opening.type_label":"Type","opening.length_label":"Length, cm","opening.contact_label":"Open/close sensor","opening.lock_label":"Lock","opening.none":"— none —","opening.invert":"Invert open/closed","opening.flip_h":"Hinge on the other jamb","opening.flip_v":"Opens to the other side","opening.open":"Open","opening.closed":"Closed","opening.locked":"Locked","opening.unlocked":"Unlocked","opening.state_unknown":"unavailable","opening.no_entities":"No sensors bound — a static symbol on the plan.","toast.opening_no_wall":"Click next to a room wall — openings sit on walls","markup.delete":"Delete","markup.hint_points":"points: {n} · Shift — 45° steps · Esc/Ctrl+Z — undo a dot · first point or Ctrl/Cmd+click — close","markup.hint_start":"click a grid dot to start the outline","tip.lqi":"average zigbee signal:","tip.area":"area: {value}","info.device_header":"Device on the plan","info.model":"Model","info.state":"State","info.link":"Link","info.manuals":"Manuals","info.none":"No additional information","marker.new_device":"New device","marker.name_label":"Name (shown on the plan)","marker.name_ph":"Name","marker.binding_label":"Bind to an HA device","marker.binding_disabled":"disabled in Home Assistant","marker.virtual_option":"Virtual device (no binding)","marker.search_ph":"Search device / group…","marker.nothing_found":"nothing found","marker.room_label":"Room","marker.room_override":" (override placement)","marker.room_choose":"— select a room —","marker.room_auto":"— by device area (auto) —","marker.icon_label":"Icon","marker.icon_ph":"mdi:… (empty = auto)","marker.display_label":"Display","display.badge":"Icon + state","display.icon_ripple":"Icon + state and activity","display.static_icon":"Always static icon","marker.display_hint":"Icon + state changes the plate for work, open, alarm and unavailable states. Icon + state and activity additionally shows a short pulse for events and a continuous pulse for ongoing work, movement or presence. Value + state replaces the icon with one unambiguous HA value. A static icon never reacts to device states.","marker.display_hint_badge":"The icon and dynamic plate show device state without the ordinary activity pulse. Red alarms remain visible.","marker.display_hint_icon":"The icon and dynamic plate show device state without the ordinary activity pulse. Red alarms remain visible.","marker.display_hint_icon_ripple":"The icon, dynamic plate and pulse show a short pulse for events and a continuous pulse for work, motion or presence. Red alarms have separate priority.","marker.display_hint_value":"One unambiguous Home Assistant value replaces the icon while the plate continues to show state. Red alarms remain visible.","marker.display_hint_static_icon":"The black plate and icon always stay the same. State, activity, unavailability and alarms do not change the face.","marker.static_alarm_warning":"This device can report alarms. Static display hides the marker's visual alarm indication.","marker.preview.title":"Display preview","marker.preview.actual":"Now","marker.preview.example":"Example","marker.preview.integration":"Provided by","marker.preview.source":"Display source","marker.preview.current_state":"Current state","marker.preview.result":"On the plan","marker.preview.details":"Source details","marker.preview.select_source":"Choose a Home Assistant device or entity to see its actual display.","marker.preview.unknown_provider":"Unknown integration","marker.preview.virtual_provider":"House Plan · virtual device","marker.preview.no_source":"No active source","marker.preview.no_state":"No current state","marker.preview.mixed_states":"Several different states","marker.preview.multiple_sources":"{n} sources","marker.preview.more_sources":"+{n} more","marker.preview.scaled":"The preview is scaled to {n}% to fit. The saved size is unchanged.","marker.preview.demo_activity":"Show activity example","marker.preview.demo_notice":"Temporary activity example; the actual device state is unchanged.","marker.preview.demo_short":"Show short activity","marker.preview.demo_continuous":"Show continuous activity","marker.preview.stop_continuous":"Stop continuous activity","marker.preview.demo_short_notice":"Short activity example; the actual device state is unchanged.","marker.preview.demo_continuous_notice":"Continuous activity example; the actual device state is unchanged.","marker.preview.demo_already_visible":"The current state already shows a real activity or alarm effect.","marker.preview.reduced_motion":"System reduced-motion is enabled, so ordinary activity is shown as a dot.","marker.preview.reason.neutral":"Neutral dark plate","marker.preview.reason.working":"Yellow plate: the device is working now","marker.preview.reason.working_activity":"Yellow plate and activity effect: the device is working now","marker.preview.reason.open":"Orange plate: physically open or unlocked","marker.preview.reason.cover_icon_state":"Neutral plate; the cover entity controls the displayed state and icon","marker.preview.reason.presence":"Activity effect while presence is detected","marker.preview.reason.event":"Short activity effect after a detected event","marker.preview.reason.transition":"Activity effect while the device is moving or changing state","marker.preview.reason.media_neutral":"Media devices stay on a neutral dark plate while available","marker.preview.reason.unavailable":"Subdued neutral plate: unavailable or off","marker.preview.reason.alarm":"Red alarm plate; alarm indication is always shown","marker.preview.reason.live_states_disabled":"Live-state styling is disabled for this card","marker.preview.reason.value_no_state":"No usable state value; the icon is shown instead","marker.preview.reason.value_ambiguous_sources":"Several possible value sources; the icon is shown instead","marker.preview.reason.value_non_scalar":"The source did not return a simple value; the icon is shown instead","marker.preview.reason.value_virtual":"A virtual device has no Home Assistant value; the icon is shown instead","marker.preview.reason.vacuum_live_plan_only":"Live vacuum position and trail are available only on the full plan","marker.preview.reason.hidden_design_preview":"The device is hidden on the plan; this design preview remains visible","marker.preview.reason.composite_power_source":"State comes from the device Power entity; auxiliary switches are ignored","marker.preview.reason.activity_display_disabled":"The selected display mode does not show ordinary activity effects","marker.preview.reason.ha_disabled":"The Home Assistant binding is disabled and will be hidden on the plan","marker.preview.reason.orphaned":"The saved Home Assistant binding can no longer be found","marker.preview.reason.static_icon":"Static mode: device state does not change the icon","marker.activity_color":"Activity pulse color","marker.ripple_size":"Activity pulse size","marker.activity_alarm_note":"Color and size affect ordinary activity only and do not change red alarms.","marker.pulse_a11y_alarm":"Alarm","marker.pulse_a11y_event":"Recent event","marker.pulse_a11y_presence":"Presence detected","marker.pulse_a11y_transition":"Changing state","marker.pulse_a11y_running":"Working","marker.size_label":"Icon size / rotation","marker.angle_label":"Rotate","marker.model_label":"Model","marker.model_ph":"e.g. Aqara T&H","marker.link_label":"Link","marker.desc_label":"Description","marker.desc_ph":"Notes, specs…","marker.manuals_label":"Manuals (PDF etc.)","marker.sub_device":"device","marker.sub_z2m_group":" · Z2M group","marker.sub_group":"group","marker.sub_helper":"helper","space.new":"New space","space.header":"Space","space.title_label":"Title","space.title_ph":"e.g. Garage","space.plan_label":"Floor plan (background)","space.no_plan":"no plan image","space.plan_alt":"plan","room.new":"New room","room.name_label":"Display name","room.name_ph":"e.g. Terrace","room.area_label":"Home Assistant area (unassigned)","room.no_area_option":"— no area —","room.default_name":"Room","device.unnamed":"unnamed","device.light_group":"light group","device.fallback":"device","device.virtual":"virtual device","confirm.delete_room":'Delete room "{name}"?',"confirm.merge_rooms":'Merge rooms "{a}" and "{b}"?',"confirm.remove_marker":'Delete "{name}" from the plan? The device will disappear completely and stop contributing to plan data. You can add it again later.',"confirm.erase_decor":"Erase the {kind} object? You can undo this action from the editor history.","confirm.delete_space":'Delete space "{title}" with all its rooms and markup?',"toast.pos_save_failed":"Failed to save position: {err}","toast.no_entity":"The device has no suitable entity","toast.ha_disabled_action":"A Home Assistant object that is disabled cannot be used on the plan.","toast.ha_disabled_show_device":"A device disabled in Home Assistant cannot be shown on the plan. Enable it in Home Assistant first.","toast.ha_disabled_show_entity":"An entity disabled in Home Assistant cannot be shown on the plan. Enable it in Home Assistant first.","toast.ha_disabled_add":"A disabled Home Assistant object cannot be added to the plan. Enable it in Home Assistant first.","toast.ha_binding_unverified":"The object status could not be verified through the Home Assistant registry. Display and actions are temporarily unavailable.","toast.markup_needs_server":"Markup is available after the config is moved to the server","toast.conflict":"Config was changed in another window — data refreshed, repeat your last action","toast.cfg_save_failed":"Failed to save config: {err}","toast.room_overlap":"The outline overlaps room “{name}” — rooms must not overlap","toast.contour_min_edges":"Draw at least two edges before closing the outline","toast.contour_cannot_close":"The outline cannot close because it is degenerate or intersects itself","toast.merge_not_adjacent":"Only rooms that share a wall can be merged","toast.rooms_merged":"Rooms merged into “{name}”","toast.split_pick_wall":"Start the cut on the room’s wall","toast.split_bad_cut":"The cut must run wall to wall inside the room, without crossing walls or itself","merge.header":"Merge rooms","merge.hint":"The merged room keeps one name and one area. The other area is released — its devices leave the plan until another room claims it.","merge.keep":"Keep","merge.no_area":"no area","room.split_header":"New room from the split","toast.room_saved":"Room saved ({n}). Devices added: {added}. Outline the next one or exit markup.","toast.room_saved_no_area":"Room saved ({n}, no area). Outline the next one or exit markup.","toast.marker_needs_server":"Device editing is available after the config is moved to the server","toast.virtual_name_required":"Enter a name for the virtual device","toast.marker_saved":"Device saved","toast.marker_removed":"Device deleted from the plan","toast.integration_missing":"The House Plan integration is not installed — management unavailable","toast.plan_formats":"Supported formats: SVG, PNG, JPG, WebP","toast.plan_required":"Upload a floor plan — it is required","toast.space_added_onboard":"Space added. Outline the rooms: click grid dots and close the contour.","toast.space_added":"Space added","toast.space_saved":"Space saved","toast.space_deleted":"Space deleted","toast.delete_failed":"Delete failed: {err}","toast.error":"Error: {err}","toast.file_failed":'File "{name}" was not uploaded: {err}',"toast.files_attached":"Files attached: {n}","err.unknown":"unknown error","err.code":"code {code}","err.too_large":"file larger than {mb} MB","err.bad_ext":"unsupported type (PDF/image expected)","err.unauthorized":"administrator rights required","editor.title":"Title","editor.default_floor":"Default space","editor.icon_size":"Icon size, % of plan width","editor.show_temperature":"Show temperature","editor.live_states":"Live states (on/off, open…)","editor.show_signal":"Show zigbee signal (LQI)","editor.language":"Interface language","editor.lang_auto":"Auto (HA profile)","editor.lang_en":"English","editor.lang_ru":"Русский","title.icon_rules":"Icon rules: which MDI icon devices get by name","rules.title":"Icon rules","rules.hint":"Rules are checked top-down against “device name + model” (case-insensitive regex); the first match wins. When nothing matches, the entity device class decides, then the generic chip icon.","rules.pattern_ph":"regex, e.g. plug|socket","rules.icon_ph":"mdi:power-socket-de","rules.add":"Add rule","rules.reset":"Reset to defaults","rules.test_ph":"Try a device name…","rules.invalid":"invalid regex","rules.saved":"Icon rules saved","btn.up":"Up","btn.down":"Down","tap.info":"Device card","tap.more_info":"HA more-info dialog","tap.toggle":"Toggle state","marker.tap_label":"Tap action for this device","tap.toggle_note":"The resolved state is shown below. Secure devices are never toggled from the plan.","import.title":"Create spaces from HA floors","import.hint":"Your Home Assistant already knows these floors. Pick the ones to turn into plan spaces — you will upload a floor-plan image for each one next. Rooms are then outlined by hand on the plan.","import.start":"Create {n} space(s)","import.manual":"Start from scratch","import.progress":"Floor {i} of {n}","import.done":"Spaces created. Outline the rooms: click grid dots and close the contour.","btn.skip":"Skip","space.scale_label":"Scale (grid cell size)","space.scale_unit":"cm per cell","space.display_section":"Display","space.show_borders":"Always show room borders","space.show_names":"Show room names (drag to move)","space.room_color":"Border & name color","space.opacity":"Opacity","space.fill_label":"Room fill","fill.none":"None","fill.lqi":"Zigbee signal","fill.light":"Lights","fill.custom":"Custom color","space.custom_fill":"Fill color","space.source_file":"I have a floor-plan image","space.source_draw":"No image — I'll outline rooms by hand","space.orientation":"Canvas","orient.landscape":"Landscape","orient.portrait":"Portrait","orient.square":"Square","fill.temp":"Temperature","space.temp_min":"Comfort from","space.temp_max":"to","tip.temp_avg":"average temperature:","space_card.button":"Open the space plan","space_card.not_found":"Space “{id}” not found","space_card.loading":"Loading…","continuity.restore_plan":"Restoring floor plan…","continuity.restore_connection":"Restoring device connection…","continuity.retry":"Try again","editor.space":"Space","editor.show_button":"Show button","editor.button_label":"Button label","editor.button_target":"Target dashboard path","marker.sub_entity":"entity","title.general_settings":"General settings","gs.title":"General settings","gs.hint":"Fill colors apply to every space; each color has its own opacity. Which fill mode a space uses is set in that space's dialog.","gs.light_group":"Fill: lights","gs.light_on":"Lights on","gs.light_off":"All lights off","gs.temp_group":"Fill: temperature","gs.temp_cold":"Cold","gs.temp_ok":"Comfortable","gs.temp_hot":"Hot","gs.lqi_group":"Fill: zigbee signal","gs.lqi_low":"Weak signal","gs.lqi_high":"Strong signal","gs.reset":"Reset to defaults","gs.saved":"General settings saved","space.show_lqi":"Show zigbee signal (LQI) next to devices","space.hide_decor":"Hide the decorative layer","space.hide_decor_tip":"Lines, shapes, labels and furniture stay where they are — visible in the backdrop editor, not on the plan.","space.hide_openings":"Hide openings","space.hide_openings_tip":"Door, window and gate symbols are not drawn, but the openings keep working: light passes through, the sun comes in at a window, contact sensors still open them. The plan editor always shows them.","gs.light_none":"No light sources","mode.plan":"Plan editor","mode.devices":"Device editor","display.value":"Value + state","marker.subarea":"no area, manual","device.new":"New device — open its editor to dismiss","opening.unlock_action":"Unlock","opening.lock_action":"Lock","opening.lock_pending":"Working…","title.close_editor":"Close editor (back to view)","devbar.add":"Add","devbar.show_all":"Hidden and disabled","devbar.rules":"Icon rules","space.roomcard_section":"Room card shows:","space.label_temp":"Temperature","space.label_hum":"Humidity","space.label_lqi":"Average Zigbee signal","space.label_light":"Lights on/off","roomcard.light_on":"On","roomcard.light_off":"Off","roomcard.light_partial":"{on} of {total}","toast.split_pick_inside":"Intermediate cut points must be inside the room","mode.decor":"Background editor","decor.select":"Select","decor.line":"Line","decor.rect":"Rectangle","decor.ellipse":"Oval","decor.text":"Text","decor.erase":"Erase","decor.erase_confirm_title":"Erase object?","decor.color":"Color","decor.width":"Line width","decor.line_style":"Line style","decor.line_style_solid":"Solid","decor.line_style_dashed":"Dashed","decor.fill":"Fill","decor.fill_color":"Fill color","decor.length":"Length","decor.size":"Size","decor.angle":"Rotation angle, °","decor.text_size":"Text size","decor.backdrop_properties":"Plan backdrop properties","decor.text_title":"Text label","decor.object_title":"Edit {kind}","decor.text_label":"Text","decor.live_group":"Insert HA variable","decor.live_entity":"Entity","decor.live_entity_ph":"choose an entity","decor.live_attr":"Value","decor.live_attr_ph":"choose state or attribute","decor.live_state":"State","decor.backdrop":"Backdrop image","decor.backdrop_hint":"Drag to move; pull a corner to resize; use the top handle to rotate. Shift changes proportions or frees the angle.","decor.backdrop_reset":"Reset the picture","decor.backdrop_reset_done":"The picture is back at its original place and size","marker.icon_auto":"Auto: {icon} (by icon rules; pick one to override)","marker.icon_pin_auto":"Pin","mode.plan_tip":"Plan editor — the geometry of the home: draw and split/merge rooms, bind them to HA areas, place doors, windows and gates, move room cards, set the scale","mode.devices_tip":"Device editor — everything about icons: drag to position, click to edit binding/icon/display, add virtual devices, icon rules","mode.decor_tip":"Background editor — purely visual decor under the plan: lines, rectangles, ovals and text labels that never react to clicks","space.glow_enabled":"Light-source glow","gs.glow_group":"Light-source glow","gs.glow_base":"House darkness","gs.glow_light":"Default light color / intensity","gs.wall_group":"Walls","gs.wall_fill":"Wall fill","gs.glow_radius":"Glow radius","gs.unit_m":"m","gs.unit_ft":"ft","marker.controls_label":"Controls other light sources","marker.controls_hint":"With tap action “Toggle”, a click flips every source added here. The marker’s own bound entity is controlled directly and cannot be added; use the switch below to classify it as a light.","marker.controls_filter":"Search lights and switches…","info.controls":"Controls","marker.glow_radius_label":"Glow radius","marker.glow_radius.help":"Sets the glow radius in metres or feet; an empty value uses the radius from general settings.","marker.glow_radius.help.aria":"Help: glow radius","markup.wallthick":"Thickness","markup.select":"Select","title.markup_wallthick":"Thickness — click a wall to set how thick it is. Empty or zero removes the thickness.","markup.hint_wallthick":"click a wall · Esc closes without applying","wallthick.field":"Thickness","wallthick.unit_cm":"cm","wallthick.unit_in":"in","wallthick.apply_room":"Apply to all walls of this room","markup.draw_wall_title":"Thickness of each new room-wall segment (1–100 cm). Shared walls keep the neighbour's value.","toast.wallthick_pick":"Click a wall (not an open boundary)","toast.wallthick_open":"Open boundaries have no thickness","toast.wallthick_set":"Wall thickness set","toast.wallthick_cleared":"Wall thickness removed","toast.physical_range":"Enter a value from 1 to {max} {unit}","toast.physical_angle":"Enter a rotation angle from 0° up to, but not including, 90°","toast.physical_limit":"The space has reached the limit for this type of geometry","markup.boundary_hint":"Select a shared room boundary or a dashed stretch","markup.boundary_hint_open":"Click the start of the open stretch","markup.boundary_hint_second":"Choose the end on the selected boundary · Esc to cancel","markup.boundary_hint_retry":"Choose the end on the selected boundary","markup.boundary_hint_restore":"Click to restore the wall","markup.boundary_hint_outer":"Only a boundary shared by two rooms can be opened","markup.boundary_hint_blocked":"a partition, column or unfinished contour is above this boundary","markup.boundary_hint_ambiguous":"Choose a stretch farther from the junction","markup.boundary_cancel":"Cancel the unfinished Boundary action","toast.openwall_pick":"This tool works with room boundaries","toast.openwall_shared_only":"Only a shared wall between two rooms can be opened","toast.openwall_short":"Stretch too short — pick a farther point","toast.boundary_opened":"Boundary stretch opened","toast.boundary_restored":"Wall restored on this stretch","toast.boundary_same_edge":"Choose the end point on the selected wall","toast.boundary_ambiguous":"Choose a stretch slightly farther from the junction","toast.boundary_blocked":"This boundary is covered by independent masonry. Move or delete it with Select first","toast.delete_room_pick":"Click inside the room you want to delete","toast.openwall_openings_removed":"Openings on the virtual stretch were removed","toast.openwall_opened":"Boundary “{a}” ↔ “{b}” is now open","toast.openwall_closed":"Boundary “{a}” ↔ “{b}” is closed again","toast.opening_on_virtual":"Openings cannot sit on a virtual wall","marker.from_ha_option":"Pick from the HA list","marker.show_entities":"Show entities","marker.show_entities_tip":"Adds not only devices to the list, but all their entities too","marker.pick_ph":"Choose a device…","room.open_area":"Open the HA area","view.volumetric":"Volumetric view","view.flat":"Flat view","kiosk.title":"This screen's sizes","kiosk.hint":"Stored on this device only — every wall tablet or TV can have its own comfortable sizes.","kiosk.icon_scale":"Device icon size","kiosk.font_scale":"Room card text size","editor.kiosk":"Wall device (kiosk) mode","editor.cycle":"Auto-switch spaces every N seconds (kiosk, 0 = off)","room.settings_title":"Room settings","room.settings_section":"Room settings (override the space)","room.fill_label":"Fill in THIS room","fill.inherit":"As the space","room.custom_fill_space":"Space color","room.custom_fill_own":"Room color","room.temp_src_label":"Temperature source","room.hum_src_label":"Humidity source","room.src_average":"Average over the room's sensors (default)","room.src_pick":"A specific HA device or entity","room.src_ph":"Choose a source…","toast.room_updated":"Room updated","space.card_font":"Room-card font size (whole space)","room.sizes_section":"Font sizes","room.name_scale":"Room name size","room.label_scale":"Metrics size","preview.room_name":"Living room","toast.cfg_reload_failed":"Could not reload the plan from the server: {err}","room.settings_short":"Room settings","room.unnamed":"Unnamed room","marker.use_climate_temp":"Include the device temperature in the room","marker.use_climate_temp_tip":"Adds an air conditioner or thermostat current_temperature to the room average. Configure the external value badge separately below.","marker.value_badge_title":"Value badge","marker.value_badge.help":"Shows one selected value next to the icon. It does not affect room metrics, light, Glow or the tap action.","marker.value_badge.help.aria":"Help: device value badge","marker.value_badge_enabled":"Show a value badge","marker.value_badge_source":"Value","marker.value_badge_source.help":"Choose a specific state, supported attribute, or derived value of this device.","marker.value_badge_source.help.aria":"Help: value badge source","marker.value_badge_position":"Position","marker.value_badge_position.help":"The chosen side stays fixed and does not flip automatically at a plan edge.","marker.value_badge_position.help.aria":"Help: value badge position","marker.value_badge_right":"Right","marker.value_badge_bottom":"Below","marker.value_badge_left":"Left","marker.value_badge_top":"Above","marker.value_badge_empty":"This device has no available values","marker.value_badge_static":"A static icon does not show live values. The setting is preserved.","marker.value_badge_missing":"Source unavailable","marker.value_badge_missing_hint":"The saved source is currently unavailable. Keep it, disable the badge, or choose another value.","marker.value_badge_duplicate":"The same value is already shown inside the icon.","marker.value_badge_state":"State · {name}","marker.value_badge_marker_state":"Light-source state · {name}","marker.value_badge_lqi":"Average Zigbee signal quality","marker.value_badge_attr_current_temperature":"Current temperature · {name}","marker.value_badge_attr_temperature":"Target temperature · {name}","marker.value_badge_attr_current_humidity":"Current humidity · {name}","marker.value_badge_attr_humidity":"Humidity · {name}","marker.value_badge_attr_current_position":"Position · {name}","marker.value_badge_attr_percentage":"Speed · {name}","marker.value_badge_attr_brightness":"Brightness · {name}","marker.value_badge_attr_volume_level":"Volume · {name}","marker.value_badge_attr_battery_level":"Battery · {name}","marker.value_badge_attr_fan_speed":"Fan speed · {name}","marker.light_role_label":"Is this device a light source?","marker.light_role.help":"Controls whether this marker itself is a spatial light source. Linked lights above remain independent room-light controls.","marker.light_role.help.aria":"Help: whether this device is a light source","marker.light_role_auto_yes":"Auto (light source)","marker.light_role_auto_no":"Auto (not a light source)","marker.light_role_always":"Always a light source","marker.light_role_never":"Never a light source","marker.light_entity_label":"Leading light entity","marker.light_entity.help":"For a composite device, choose the entity whose state, colour and brightness represent this plan source. The automatic fallback remains compatible with older plans.","marker.light_entity.help.aria":"Help: leading light entity","marker.light_entity_auto":"Automatic ({entity})","marker.light_entity_none":"no controllable entity","marker.light_entity_missing":"The saved entity {entity} is unavailable. House Plan temporarily uses {fallback}; the saved choice will be restored if it returns.","marker.glow_color_label":"Glow colour and brightness","marker.glow_mode.help":"Use live source values, override only its colour, or fix both colour and brightness. The minimum is 1%; to disable the source, choose “Never”.","marker.glow_mode.help.aria":"Help: glow colour and brightness","marker.glow_mode_auto":"From source","marker.glow_mode_color":"Set colour","marker.glow_mode_fixed":"Set colour and brightness","marker.glow_color":"Glow colour","marker.glow_brightness":"Brightness","marker.glow_disabled_never":"Glow settings are unavailable because this marker is explicitly not a light source.","marker.glow_disabled_auto":"Auto mode found no spatial light source for this marker.","marker.glow_disabled_no_entity":"No active controllable Home Assistant entity is available for this spatial source.","marker.glow_passive_hint":"This source has no own Home Assistant data. Set its colour and brightness manually; its radius remains available.","marker.control_broken":"Saved source is missing or no longer marked as a light","marker.control_missing_label":"Missing plan light","marker.control_passive":"passive source","toast.marker_control_cycle":"This link would create a circular chain of light controls.","toast.marker_binding_required":"Choose a Home Assistant device before linking another light source.","confirm.unlock":"Unlock “{name}”?","toast.files_migrate_failed":"Attachments could not be moved to the new binding, links keep pointing at the old files: {err}","space.pick_saved":"Already uploaded","space.pick_saved_hint":"Plans stored on the server, including ones you detached earlier","space.no_saved":"No plans stored on the server yet.","space.loading":"Loading…","space.used_by":"in use: {list}","space.in_use":"A space still uses this plan — detach it first","btn.use":"Use","confirm.delete_plan":'Delete the plan file "{name}" from the server? This cannot be undone.',"toast.plans_list_failed":"Could not list the stored plans: {err}","toast.plan_delete_failed":"Could not delete the plan: {err}","marker.hide":"Hide","marker.hide_tip":'The device will disappear from the plan after saving but will still count toward the room signal. Restore it through "Hidden and disabled" in the device editor.',"marker.show":"Show","marker.show_tip":"The device will appear on the plan again after saving.","marker.hidden_ghost":"Device hidden by the user","marker.ha_disabled_device":"The device is disabled in Home Assistant and hidden from the plan.","marker.ha_disabled_entity":"The entity is disabled in Home Assistant and hidden from the plan.","marker.ha_disabled_all_entities":"The device has no active Home Assistant entities, so it is hidden from the plan.","marker.ha_registry_limited":"The full Home Assistant registry is unavailable to this user. The unverified object cannot be shown or used for now.","marker.delete_tip":"Completely delete the device from the plan and every aggregate. You can add it again later.","tap.run":"Run automation/script/scene","tap.cover":"Open/close (curtains/blinds)","marker.run_target_label":"What to run","marker.run_search_ph":"Search: automation, script or scene…","marker.run_target_gone":"Target {id} not found — pick again","marker.tap_confirm":"Ask for confirmation","marker.tap_confirm_tip":"Show a confirmation dialog before acting — a guard against accidental taps.","marker.toggle_hint_single":"Target: {name} ({id}).","marker.virtual_light_target":"Virtual light: {name}.","marker.virtual_light_current":"Manual state: {state} → {effect}.","marker.virtual_light_state_on":"on","marker.virtual_light_state_off":"off","marker.toggle_hint_group":"Will toggle {count} source(s): {names}.","marker.toggle_hint_current":"Now: {state} → {effect}.","marker.toggle_hint_group_current":"Currently on: {on} of {count} → {effect}.","marker.toggle_hint_skipped":"Will be skipped ({count}): {targets}.","marker.toggle_effect_turn_on":"will turn on","marker.toggle_effect_turn_off":"will turn off","marker.toggle_effect_open":"will open","marker.toggle_effect_close":"will close","marker.toggle_effect_stop":"will stop","marker.toggle_effect_toggle":"state will toggle","marker.toggle_skip_missing":"not found","marker.toggle_skip_ha_disabled":"disabled in HA","marker.toggle_skip_unavailable":"unavailable","marker.toggle_skip_unsupported":"toggle is unsupported","marker.toggle_skip_secure":"blocked for security","marker.toggle_none_no_binding":"This device has no binding, so there is no state to toggle. Tapping will do nothing.","marker.toggle_none_no_actionable_entity":"This device has no state that can be toggled. Tapping will do nothing.","marker.toggle_none_configured_targets_missing":"The configured targets are unavailable. The device's own entity will not be substituted for them.","marker.toggle_none_ha_disabled":"The target is disabled in Home Assistant and cannot be used on the plan.","marker.toggle_none_unavailable":"The target is currently unavailable. Tapping will do nothing.","marker.toggle_none_unsupported":"Home Assistant exposes no safe toggle operation for this entity. Tapping will do nothing.","marker.toggle_none_secure":"Toggling locks, alarms and secure gates from the plan is blocked for security.","run.automation":"automation","run.script":"script","run.scene":"scene","confirm.tap_run":'Run "{name}"?',"confirm.tap_toggle":'Toggle "{name}"?',"confirm.tap_cover":'Open/close "{name}"?',"toast.run_started":"Started: {name}","toast.run_target_missing":"Run target not found — check the device settings","toast.run_target_required":"Pick an automation, script or scene","toast.tap_target_changed":"The action target changed. Try again.","toast.virtual_light_toggle_failed":"Could not toggle the virtual light: {err}","toast.value_badge_source_required":"Choose a value for the badge","btn.run":"Run","vac.section":"Robot vacuum: live position","vac.autocal":"Set up automatically","vac.live":"Live position on the plan","vac.trail":"Show the robot's path","vac.cal_maps":"Calibrated maps: {maps}","vac.autocal_no_rooms":"The integration reports no room list — open “Fit manually”","vac.autocal_no_match":"Room names did not match (need ≥3 in common) — open “Fit manually”","vac.autocal_done":"Done: bound via {rooms} rooms. Start a cleanup and check","vac.cal_need_pos":"The robot is not reporting coordinates — start a cleanup and pause it","vac.cal_done":"Calibration saved. Start a cleanup and check","vac.cal_cancelled":"Calibration cancelled","vac.fit":"Fit manually","vac.fit_hint":"Drag the robot map into place, stretch by the corners","vac.fit_rotate":"Rotate 90°","vac.fit_mirror":"Mirror","vac.trail_never":"Never","vac.trail_cleaning":"While cleaning","vac.trail_always":"Always","gs.bg_group":"Stage background","gs.bg_color":"Background around the plan","gs.bg_default":"Theme default","gs.bg_theme":"theme default","gs.bg_mode":"Plan background","gs.bg_static":"Static color","gs.bg_daynight":"Follows the Sun","gs.bg_daynight_hint":"The background moves through dawn, day, dusk and night using sun.sun, or your local clock when sun data is unavailable. No compass is needed for the background.","gs.sun_group":"Sun","gs.sun_missing":"The sun.sun entity was not found — the background follows your local clock; window rays are unavailable.","gs.north":"North on the plan","gs.north_ph":"not set","gs.north_hint":"Set north only for window rays (1° steps, 15° with Shift); the Sun background works without it.","gs.north_clear":"Clear","gs.north_letter":"N","gs.sun_rays":"Sunlight through windows","gs.about_group":"About","gs.about_version":"Houseplan Card v{v}","gs.about_github":"GitHub · docs & issues","gs.about_telegram":"Telegram chat","space.bg_color":"Background around the plan","space.bg_inherit":"Inherit general","space.bg_inherited":"inherits general settings","space.bg_mode":"Plan background","space.north":"North on the plan (override)","space.north_inherited":"inherited: {v}","space.sun_rays":"Sunlight through windows","space.sun_inherit":"Inherit general","space.sun_on":"On","space.sun_off":"Off","canvas.far_objects":"{n} object(s) far from the plan","canvas.show_far":"Show","canvas.home_tip":"The plan is over there — click to fit it","gs.grid_group":"Plan maintenance","gs.grid_hint":"Updates data models, aligns plan elements to the grid and merges redundant wall fragments. An exact report is shown before anything is stored.","gs.align_all":"Optimize plans","gs.align_title":"Optimize plans","gs.align_none":"All plans already use the current optimized data model.","gs.align_count":"{n} of {total} elements will move, by at most {cm} cm.","gs.align_where":"The largest shift is in “{s}”.","gs.align_turned":"Openings whose angle is corrected: {n}.","gs.align_removed_drafts":"Invalid outlines collapsed by the grid and removed: {n}.","gs.optimize_changes":"Model migrations: {m}; plans canonicalized: {c}; merged real-wall fragments: {w}; virtual fragments: {s}.","gs.optimize_glow_migration":"Legacy Glow: {spaces} spaces → no data fill + independent Glow; {rooms} rooms → inherited data fill + independent Glow.","gs.align_warn":"Elements deliberately placed between grid nodes will move. One undo is available after the operation, only until the next plan edit.","gs.align_run":"Optimize","gs.align_done":"Plans optimized: {n} elements moved, {m} records maintained","gs.optimize_undo":"Undo last optimization","gs.optimize_undone":"The last optimization was undone","decor.furniture":"Furniture","furn.title":"Furniture library","furn.symbol":"Symbol","furn.group_furniture":"Furniture","furn.group_appliance":"Appliances","furn.group_sanitary":"Plumbing","furn.group_other":"Other","furn.width":"Width","furn.depth":"Depth","furn.pick_hint":"Pick a symbol, then click on the plan.","furn.place_hint":"Click on the plan — the piece lands against the nearest wall. Shift places it free.","furn.sym_sofa":"Sofa","furn.sym_armchair":"Armchair","furn.sym_coffee_table":"Coffee table","furn.sym_table_dining":"Dining table","furn.sym_table_round":"Round table","furn.sym_chair":"Chair","furn.sym_desk":"Desk","furn.sym_bed_double":"Double bed","furn.sym_bed_single":"Single bed","furn.sym_nightstand":"Nightstand","furn.sym_wardrobe":"Wardrobe","furn.sym_bookshelf":"Bookshelf","furn.sym_fridge":"Fridge","furn.sym_stove":"Cooker","furn.sym_dishwasher":"Dishwasher","furn.sym_washer":"Washing machine","furn.sym_dryer":"Tumble dryer","furn.sym_tv":"TV","furn.sym_ac":"Air conditioner","furn.sym_water_heater":"Water heater","furn.sym_toilet":"Toilet","furn.sym_bathtub":"Bathtub","furn.sym_shower":"Shower","furn.sym_sink":"Washbasin","furn.sym_kitchen_sink":"Kitchen sink","furn.sym_bidet":"Bidet","furn.sym_stairs":"Stairs","furn.sym_fireplace":"Fireplace","furn.sym_plant":"Plant","furn.sym_rug":"Rug","common.yes":"Yes","common.no":"No","vac.diag_source":"Source","vac.diag_platform":"Integration","vac.diag_status":"Status","vac.diag_position":"Position","vac.diag_rooms":"Rooms","vac.diag_rooms_value":"{total} · {matched} names match · {readiness}","vac.autocal_ready":"auto-calibration available","vac.autocal_not_ready":"need 3 matching names","vac.diag_path":"Integration path","vac.diag_map":"Map ID","vac.source_none":"not selected","vac.source_status_ok":"Ready","vac.source_status_missing":"Missing","vac.source_status_disabled":"Disabled in Home Assistant","vac.source_status_unavailable":"Unavailable","vac.source_status_unverified":"Cannot verify with current permissions","vac.source_status_unsupported":"No position data","vac.source_status_none":"No source","vac.source_banner_missing":"The saved source no longer exists. It was not replaced automatically; choose another source or restore it in Home Assistant.","vac.source_banner_disabled":"The saved source is disabled in Home Assistant. Enable it there or choose another source.","vac.source_banner_unverified":"Current Home Assistant permissions cannot verify this saved source. It remains pinned and will not be replaced automatically.","vac.choose_source":"Choose source","vac.source_auto":"Automatic","vac.source_auto_hint":"Use a compatible entity from this device","vac.all_cameras":"All cameras","vac.all_cameras_warn":"A camera may not provide robot data. Choose one only if it is your vacuum map.","vac.all_cameras_empty":"No other camera entities found.","vac.platform_unknown":"unknown integration","vac.cap_position":"position","vac.cap_rooms_short":"rooms","vac.cap_path":"path","vac.cap_map":"map ID","vac.cap_none":"no robot data detected","vac.xcme_hint":"Enable these Xiaomi Cloud Map Extractor attributes:","vac.documentation":"Documentation","vac.residual_title":"Check automatic calibration","vac.residual_message":"The matched rooms disagree by up to {error}. Apply this approximate calibration, refine it manually, or cancel without changing the saved setup.","vac.apply_proposal":"Apply","gs.backup_group":"Backup and transfer","gs.backup_hint":"Download a portable JSON backup or preview and import a backup made by House Plan.","backup.export_open":"Export","backup.import_open":"Import","backup.export_title":"Export House Plan","backup.import_title":"Import House Plan","backup.export_hint":"Choose whether the backup should contain the whole House Plan configuration or only the current space.","backup.full":"Full backup","backup.current_space":"Current space","backup.current_space_title":"Current space: {title}","backup.no_current_space":"No current space","backup.privacy_warning":"The archive keeps names, Home Assistant identifiers and exact coordinates. Internal plans and attachments are referenced, not embedded; runtime states and vacuum trails are not included.","backup.download":"Download JSON","backup.export_done":"Backup downloaded","backup.reading":"Checking the backup…","backup.revalidated":"The plan changed after this preview. The summary was refreshed; review it and confirm again.","backup.error.unauthorized":"You do not have permission to export or import this plan.","backup.error.not_ready":"House Plan is not ready yet. Try again in a moment.","backup.error.too_large":"The backup exceeds the 8 MiB limit.","backup.error.invalid_json":"The selected file is not valid JSON.","backup.error.invalid_format":"The selected file is not a House Plan backup.","backup.error.unsupported_export_version":"This backup format is not supported by the installed version.","backup.error.future_model":"The backup was created by a newer House Plan data model.","backup.error.invalid_config":"The backup contains an invalid House Plan configuration.","backup.error.invalid_layout":"The backup contains invalid object positions.","backup.error.invalid_content":"The backup contains invalid or inconsistent content references.","backup.error.space_not_found":"The selected space no longer exists.","backup.error.capacity_exceeded":"Adding this backup would exceed the plan limits.","backup.error.preview_expired":"The preview expired. Select the backup file again.","backup.error.preview_owner_mismatch":"This preview belongs to another Home Assistant user.","backup.error.conflict":"The plan changed after the preview. Review the refreshed summary.","backup.error.content_confirmation_required":"Confirm that unavailable local content may be detached.","backup.error.commit_failed":"The backup could not be applied safely. The previous plan was restored or is pending recovery.","backup.error.missing_plan":"A referenced plan file disappeared after the preview. Review the backup again.","backup.error.missing_content":"A referenced local attachment disappeared after the preview. Review the backup again.","backup.error.marker_control_missing":"A linked plan light is missing from the backup.","backup.error.marker_control_not_light":"A linked plan target is no longer marked as a light source.","backup.error.marker_control_self":"A light source cannot control itself.","backup.error.marker_control_cycle":"The backup contains a circular chain of light controls.","backup.error.duplicate_marker_control":"The backup contains a duplicate plan-light link.","backup.error.no_backup":"There is no import or optimization snapshot to restore.","backup.same_source":"Created on this Home Assistant instance","backup.foreign_source":"Created on another Home Assistant instance","backup.created":"Created: {value}","backup.versions":"Card {card}; integration {integration}; data model {model}","backup.count_spaces":"Spaces: {n}","backup.count_rooms":"Rooms: {n}","backup.count_walls":"Walls: {n}","backup.count_openings":"Openings: {n}","backup.count_decor":"Decor objects: {n}","backup.count_markers":"Devices: {n}","backup.count_layout":"Positions: {n}","backup.bindings":"Bindings — devices: {device}, entities: {entity}, virtual: {virtual}; positions without a space: {legacy}","backup.binding_status":"Target status — active: {active}, disabled: {disabled}, missing: {missing}","backup.missing_areas":"Areas missing on the target: {areas}","backup.dropped_marker_links":"Plan-light links outside this transferred space were omitted: {n}.","backup.replace_warning":"This replaces the current configuration and layout. Uploaded files are never deleted. One undo remains available until the next plan edit.","backup.foreign_bookkeeping":"Instance-specific known/new-device bookkeeping will not be imported.","backup.final_name":"New space name","backup.target_settings":"The target instance's global settings remain unchanged.","backup.duplicates":"Bindings already present: {n}","backup.skip":"Skip duplicate devices","backup.virtual_copy":"Add safe static virtual copies","backup.content":"Referenced content","backup.content_available":"available locally","backup.content_external":"external link","backup.content_detach_required":"will be detached","backup.confirm_detach":"I understand that unavailable internal plans and attachments will be detached from the imported configuration.","backup.replace":"Replace","backup.add":"Add space","backup.space_done":"Space imported: {rooms} rooms, {markers} devices","backup.full_done":"Backup restored: {spaces} spaces, {rooms} rooms, {markers} devices","backup.undo_import":"Undo last full import","backup.import_undone":"Full import undone"};const Wo={en:jo,ru:{"editor.context_actions":"Действия: {object}","editor.tool_options":"Параметры инструмента: {tool}","editor.palette":"Палитра: {tool}","editor.open_group":"Группа инструментов: {group}","editor.group_active":"{group} — активно: {item}","editor.disabled_action":"{action} недоступно: {reason}","btn.properties":"Свойства","btn.keep_as_walls":"Оставить замкнутыми стенами","title.markup_partition":"Перегородка: нарисуйте одну независимую физическую стену","title.markup_select":"Выбор и редактирование стен, колонн и сохранённых контуров","title.markup_column":"Колонна: кликните точку сетки для квадратной колонны","markup.partition":"Перегородка","markup.column":"Колонна","markup.hint_partition":"два клика рисуют одну перегородку · Shift фиксирует шаг 45°","markup.hint_column":"кликните точку сетки, чтобы поставить квадратную колонну","history.draft_segment":"Добавление сегмента черновика комнаты","history.draft_merge":"Соединение незавершённых контуров комнаты","history.draft_segment_delete":"Удаление сегмента черновика комнаты","history.partition_add":"Добавление перегородки","history.column_add":"Добавление колонны","history.physical_edit":"Изменение физического объекта","history.physical_delete":"Удаление физического объекта","history.physical_move":"Перемещение физического объекта","history.contour_to_partitions":"Преобразование контура в замкнутые стены","toast.column_duplicate":"Колонна с тем же центром и внешним размером уже существует","confirm.delete_draft":"Удалить весь незавершённый контур комнаты?","confirm.delete_draft_segment":"Удалить этот сегмент черновика? Оставшийся контур может разделиться на два.","physical.partition_properties":"Свойства перегородки","physical.column_properties":"Свойства колонны","physical.draft_properties":"Свойства сегмента черновика","physical.shape":"Форма","physical.square":"Квадрат","physical.circle":"Круг","physical.diameter":"Диаметр","physical.side":"Сторона","physical.rotation":"Угол поворота","physical.length":"Длина","physical.allowed_range":"Допустимо: 1–{max} {unit}","physical.delete_segment":"Удалить сегмент","physical.delete_draft":"Удалить весь контур","physical.partition_size_title":"Толщина перегородки, создаваемой вторым кликом (1–100 см).","physical.column_size_title":"Внешняя сторона квадратной колонны, создаваемой кликом (1–150 см).","card.title":"План дома","count.devices":"{n} устр.","empty.no_spaces":"Пространств пока нет.","empty.add_first":"Добавьте первое пространство и загрузите план этажа.","empty.install":"Установите интеграцию House Plan и добавьте запись в «Устройства и службы».","btn.add_space":"Добавить пространство","btn.cancel":"Отмена","btn.save":"Сохранить","btn.close":"Закрыть","btn.delete":"Удалить","btn.edit":"Редактировать","btn.open_in_ha":"Открыть в HA","btn.reset":"Сброс","btn.attach":"Прикрепить…","btn.upload":"Загрузить…","btn.replace":"Заменить…","title.zoom_in":"Приблизить","title.zoom_out":"Отдалить","title.zoom_fit":"Вписать всё","title.add_device":"Добавить устройство на план","title.show_all":"Показать скрытые и деактивированные устройства служебными маркерами (только в этой вкладке)","title.markup":"Разметка комнат: сетка, линии, контуры","title.configure_space":"Настроить пространство","title.add_space":"Добавить пространство","title.markup_add":"Контур комнаты: соединяйте точки сетки линиями до замыкания комнаты; Shift фиксирует направление с шагом 45°","title.markup_merge":"Объединить: клик по одной комнате, затем по соседней с общей стеной","title.markup_split":"Разделить комнату: клик по комнате, затем две точки на её стенах","title.markup_delroom":"Удалить комнату: кликните внутри комнаты и подтвердите удаление","title.markup_boundary":"Граница: две точки на сплошной общей стене делают участок виртуальным; клик по пунктиру восстанавливает стену","title.need_plan":"Загрузите подложку (план этажа)","markup.add":"Контур комнаты","markup.merge":"Объединить","markup.split":"Разделить","markup.resize":"Размер","title.markup_resize":"Изменение размера комнат: тяните ручку стены; клик по комнате — угловая рамка масштаба","markup.hint_resize":"тяните ручку стены · клик по комнате — угловая рамка · Esc отменяет перетаскивание · Ctrl+Z — отмена шага","markup.opening":"Проём","markup.boundary":"Граница","markup.delete_room":"Удалить комнату","history.undo":"Отменить","history.redo":"Повторить","history.undo_named":"Отменить: {name} (Ctrl+Z)","history.redo_named":"Повторить: {name} (Ctrl+Shift+Z / Ctrl+Y)","history.undo_empty":"Нет операций для отмены","history.redo_empty":"Нет операций для повтора","history.undone":"Отменено: {name}","history.redone":"Повторено: {name}","history.add_room":"Создание комнаты","history.split_room":"Разделение комнаты","history.merge_rooms":"Объединение комнат","history.resize_room":"Изменение размера комнаты","history.open_boundary":"Открытие границы","history.close_boundary":"Восстановление стены","history.wall_thickness":"Изменение толщины стены","history.add_opening":"Добавление дверного или оконного проёма","history.edit_opening":"Изменение проёма","history.move_opening":"Перемещение проёма","history.delete_opening":"Удаление проёма","history.delete_room":"Удаление комнаты","history.decor_add":"Добавление объекта декора","history.decor_edit":"Изменение объекта декора","history.decor_move":"Перемещение объекта декора","history.decor_transform":"Трансформация объекта декора","history.decor_delete":"Удаление объекта декора","history.backdrop_transform":"Трансформация подложки плана","title.markup_opening":"Проёмы: клик по стене — добавить, клик по проёму — редактировать","opening.new":"Новый проём","opening.edit":"Проём","opening.door":"Дверь","opening.window":"Окно","opening.gate":"Ворота","opening.type_label":"Тип","opening.length_label":"Длина, см","opening.contact_label":"Датчик открытия","opening.lock_label":"Замок","opening.none":"— нет —","opening.invert":"Инвертировать открыто/закрыто","opening.flip_h":"Петли с другой стороны","opening.flip_v":"Открывается в другую сторону","opening.open":"Открыто","opening.closed":"Закрыто","opening.locked":"Заперто","opening.unlocked":"Не заперто","opening.state_unknown":"недоступно","opening.no_entities":"Датчики не привязаны — статичный символ на плане.","toast.opening_no_wall":"Кликните рядом со стеной комнаты — проёмы ставятся на стены","markup.delete":"Удалить","markup.hint_points":"точек: {n} · Shift — шаг 45° · Esc/Ctrl+Z — убрать точку · клик по первой или Ctrl/Cmd+клик — замкнуть","markup.hint_start":"кликните точку сетки, чтобы начать контур","tip.lqi":"средний сигнал zigbee:","tip.area":"площадь: {value}","info.device_header":"Устройство на плане","info.model":"Модель","info.state":"Состояние","info.link":"Ссылка","info.manuals":"Инструкции","info.none":"Нет дополнительной информации","marker.new_device":"Новое устройство","marker.name_label":"Имя (отображается на плане)","marker.name_ph":"Название","marker.binding_label":"Привязка к устройству HA","marker.binding_disabled":"деактивировано в Home Assistant","marker.virtual_option":"Виртуальное устройство (без привязки)","marker.search_ph":"Поиск устройства / группы…","marker.nothing_found":"ничего не найдено","marker.room_label":"Комната","marker.room_override":" (переопределить размещение)","marker.room_choose":"— выберите комнату —","marker.room_auto":"— по зоне устройства (авто) —","marker.icon_label":"Иконка","marker.icon_ph":"mdi:… (пусто = авто)","marker.display_label":"Отображение","display.badge":"Значок + состояние","display.icon_ripple":"Значок + состояние и активность","display.static_icon":"Всегда статичный значок","marker.display_hint":"Значок + состояние меняет подложку при работе, открытии, тревоге и недоступности. Значок + состояние и активность дополнительно показывает короткую пульсацию для событий и постоянную — для работы, движения или присутствия. Значение + состояние заменяет значок одним однозначным значением HA. Статичный значок не реагирует на состояния устройства.","marker.display_hint_badge":"Значок и динамическая подложка показывают состояние устройства без обычной пульсации активности. Красная тревога сохраняется.","marker.display_hint_icon":"Значок и динамическая подложка показывают состояние устройства без обычной пульсации активности. Красная тревога сохраняется.","marker.display_hint_icon_ripple":"Значок, динамическая подложка и пульсация: короткая — для событий, постоянная — для работы, движения или присутствия. Красная тревога имеет отдельный приоритет.","marker.display_hint_value":"Значок заменяется одним однозначным значением Home Assistant; подложка продолжает показывать состояние. Красная тревога сохраняется.","marker.display_hint_static_icon":"Всегда одинаковые чёрная подложка и значок. Состояния, активность, недоступность и тревоги не меняют отображение.","marker.static_alarm_warning":"Это устройство может сообщать о тревогах. Статичный режим скрывает визуальную тревогу маркера.","marker.preview.title":"Предпросмотр отображения","marker.preview.actual":"Сейчас","marker.preview.example":"Пример","marker.preview.integration":"Интеграция","marker.preview.source":"Источник отображения","marker.preview.current_state":"Текущее состояние","marker.preview.result":"На плане","marker.preview.details":"Подробнее об источниках","marker.preview.select_source":"Выберите устройство или сущность Home Assistant, чтобы увидеть фактическое отображение.","marker.preview.unknown_provider":"Интеграция неизвестна","marker.preview.virtual_provider":"House Plan · виртуальное устройство","marker.preview.no_source":"Нет активного источника","marker.preview.no_state":"Нет текущего состояния","marker.preview.mixed_states":"Несколько разных состояний","marker.preview.multiple_sources":"Источников: {n}","marker.preview.more_sources":"ещё {n}","marker.preview.scaled":"Предпросмотр уменьшен до {n}%, чтобы поместиться. Сохранённый размер не изменится.","marker.preview.demo_activity":"Показать пример активности","marker.preview.demo_notice":"Временный пример активности; фактическое состояние устройства не меняется.","marker.preview.demo_short":"Показать краткую активность","marker.preview.demo_continuous":"Показать постоянную активность","marker.preview.stop_continuous":"Остановить постоянную активность","marker.preview.demo_short_notice":"Пример краткой активности; фактическое состояние устройства не меняется.","marker.preview.demo_continuous_notice":"Пример постоянной активности; фактическое состояние устройства не меняется.","marker.preview.demo_already_visible":"Текущее состояние уже показывает реальную активность или тревогу.","marker.preview.reduced_motion":"В системе включено уменьшение движения, поэтому обычная активность показана точкой.","marker.preview.reason.neutral":"Нейтральная тёмная подложка","marker.preview.reason.working":"Жёлтая подложка: устройство сейчас работает","marker.preview.reason.working_activity":"Жёлтая подложка и эффект активности: устройство сейчас работает","marker.preview.reason.open":"Оранжевая подложка: физически открыто или разблокировано","marker.preview.reason.cover_icon_state":"Нейтральная подложка; состояние и значок определяет сущность шторы или заслонки","marker.preview.reason.presence":"Эффект активности, пока обнаружено присутствие","marker.preview.reason.event":"Короткий эффект активности после события","marker.preview.reason.transition":"Эффект активности во время движения или смены состояния","marker.preview.reason.media_neutral":"Медиаустройство остаётся на нейтральной тёмной подложке, пока доступно","marker.preview.reason.unavailable":"Приглушённая нейтральная подложка: отключено или недоступно","marker.preview.reason.alarm":"Красная тревожная подложка; тревога отображается всегда","marker.preview.reason.live_states_disabled":"Оформление по живым состояниям отключено в настройках карточки","marker.preview.reason.value_no_state":"Нет подходящего значения состояния — вместо него показан значок","marker.preview.reason.value_ambiguous_sources":"Подходит несколько источников значения — вместо него показан значок","marker.preview.reason.value_non_scalar":"Источник вернул не простое значение — вместо него показан значок","marker.preview.reason.value_virtual":"У виртуального устройства нет значения HA — вместо него показан значок","marker.preview.reason.vacuum_live_plan_only":"Живая позиция и след пылесоса доступны только на полном плане","marker.preview.reason.hidden_design_preview":"Устройство скрыто на плане, но его оформление видно в предпросмотре","marker.preview.reason.composite_power_source":"Состояние берётся из сущности питания устройства; вспомогательные переключатели не учитываются","marker.preview.reason.activity_display_disabled":"Выбранный режим отображения не показывает обычные эффекты активности","marker.preview.reason.ha_disabled":"Привязка отключена в Home Assistant, поэтому устройство будет скрыто на плане","marker.preview.reason.orphaned":"Сохранённая привязка Home Assistant больше не найдена","marker.preview.reason.static_icon":"Статичный режим: состояние устройства не меняет значок","marker.activity_color":"Цвет пульсации активности","marker.ripple_size":"Размер пульсации активности","marker.activity_alarm_note":"Цвет и размер относятся только к обычной активности и не влияют на красную тревогу.","marker.pulse_a11y_alarm":"Тревога","marker.pulse_a11y_event":"Недавнее событие","marker.pulse_a11y_presence":"Обнаружено присутствие","marker.pulse_a11y_transition":"Изменение состояния","marker.pulse_a11y_running":"Работает","marker.size_label":"Размер / поворот значка","marker.angle_label":"Поворот","marker.model_label":"Модель","marker.model_ph":"напр. Aqara T&H","marker.link_label":"Ссылка","marker.desc_label":"Описание","marker.desc_ph":"Заметки, характеристики…","marker.manuals_label":"Инструкции (PDF и т.п.)","marker.sub_device":"устройство","marker.sub_z2m_group":" · Z2M-группа","marker.sub_group":"группа","marker.sub_helper":"хелпер","space.new":"Новое пространство","space.header":"Пространство","space.title_label":"Название","space.title_ph":"Например: Гараж","space.plan_label":"Подложка (план)","space.no_plan":"нет подложки","space.plan_alt":"план","room.new":"Новая комната","room.name_label":"Отображаемое имя","room.name_ph":"Например: Терраса","room.area_label":"Зона Home Assistant (свободные)","room.no_area_option":"— без зоны —","room.default_name":"Комната","device.unnamed":"без имени","device.light_group":"группа света","device.fallback":"устройство","device.virtual":"виртуальное устройство","confirm.delete_room":"Удалить комнату «{name}»?","confirm.merge_rooms":"Слить комнаты «{a}» и «{b}»?","confirm.remove_marker":"Удалить «{name}» с плана? Устройство исчезнет полностью и перестанет участвовать в данных плана. Позже его можно будет добавить заново.","confirm.erase_decor":"Стереть объект «{kind}»? Действие можно отменить из истории редактора.","confirm.delete_space":"Удалить пространство «{title}» со всеми комнатами и разметкой?","toast.pos_save_failed":"Не удалось сохранить позицию: {err}","toast.no_entity":"У устройства нет подходящей сущности","toast.ha_disabled_action":"Деактивированный объект Home Assistant нельзя использовать на плане.","toast.ha_disabled_show_device":"Деактивированное в Home Assistant устройство нельзя показать на плане. Сначала активируйте его в Home Assistant.","toast.ha_disabled_show_entity":"Деактивированную в Home Assistant сущность нельзя показать на плане. Сначала активируйте её в Home Assistant.","toast.ha_disabled_add":"Деактивированный объект Home Assistant нельзя добавить на план. Сначала активируйте его в Home Assistant.","toast.ha_binding_unverified":"Статус объекта не удалось подтвердить по реестру Home Assistant. Отображение и действия временно недоступны.","toast.markup_needs_server":"Разметка доступна после переноса конфига на сервер","toast.conflict":"Конфиг изменён в другом окне — данные обновлены, повторите последнее действие","toast.cfg_save_failed":"Не удалось сохранить конфиг: {err}","toast.room_overlap":"Контур накладывается на комнату «{name}» — комнаты не должны накладываться","toast.contour_min_edges":"Чтобы замкнуть контур, сначала нарисуйте минимум две грани","toast.contour_cannot_close":"Контур нельзя замкнуть: он вырожден или пересекает сам себя","toast.merge_not_adjacent":"Объединять можно только комнаты с общей стеной","toast.rooms_merged":"Комнаты объединены в «{name}»","toast.split_pick_wall":"Начните разрез на стене комнаты","toast.split_bad_cut":"Разрез — от стены до стены внутри комнаты, без пересечения стен и самого себя","merge.header":"Объединение комнат","merge.hint":"У объединённой комнаты одно имя и одна зона. Вторая зона освобождается — её устройства уйдут с плана, пока их не заберёт другая комната.","merge.keep":"Оставить","merge.no_area":"без зоны","room.split_header":"Новая комната после разделения","toast.room_saved":"Комната сохранена ({n}). Устройств добавлено: {added}. Обведите следующую или выйдите из разметки.","toast.room_saved_no_area":"Комната сохранена ({n}, без зоны). Обведите следующую или выйдите из разметки.","toast.marker_needs_server":"Редактирование устройств доступно после переноса конфига на сервер","toast.virtual_name_required":"Укажите имя виртуального устройства","toast.marker_saved":"Устройство сохранено","toast.marker_removed":"Устройство удалено с плана","toast.integration_missing":"Интеграция House Plan не установлена — управление недоступно","toast.plan_formats":"Поддерживаются SVG, PNG, JPG, WebP","toast.plan_required":"Загрузите подложку — план этажа обязателен","toast.space_added_onboard":"Пространство добавлено. Обведите комнаты: кликайте по точкам сетки и замкните контур.","toast.space_added":"Пространство добавлено","toast.space_saved":"Пространство сохранено","toast.space_deleted":"Пространство удалено","toast.delete_failed":"Ошибка удаления: {err}","toast.error":"Ошибка: {err}","toast.file_failed":"Файл «{name}» не загружен: {err}","toast.files_attached":"Прикреплено файлов: {n}","err.unknown":"неизвестная ошибка","err.code":"код {code}","err.too_large":"файл больше {mb} МБ","err.bad_ext":"недопустимый тип (нужен PDF/изображение)","err.unauthorized":"нужны права администратора","editor.title":"Заголовок","editor.default_floor":"Пространство по умолчанию","editor.icon_size":"Размер иконок, % ширины плана","editor.show_temperature":"Показывать температуру","editor.live_states":"Живые состояния (вкл/выкл, открыто…)","editor.show_signal":"Показывать сигнал zigbee (LQI)","editor.language":"Язык интерфейса","editor.lang_auto":"Авто (профиль HA)","editor.lang_en":"English","editor.lang_ru":"Русский","title.icon_rules":"Правила иконок: какая MDI-иконка достаётся устройству по имени","rules.title":"Правила иконок","rules.hint":"Правила проверяются сверху вниз по строке «имя устройства + модель» (regex без учёта регистра); срабатывает первое совпадение. Если ничего не подошло — решает device class сущности, затем — иконка-заглушка.","rules.pattern_ph":"regex, напр. розетк|plug","rules.icon_ph":"mdi:power-socket-de","rules.add":"Добавить правило","rules.reset":"Сбросить к умолчаниям","rules.test_ph":"Проверьте имя устройства…","rules.invalid":"некорректный regex","rules.saved":"Правила иконок сохранены","btn.up":"Вверх","btn.down":"Вниз","tap.info":"Карточка устройства","tap.more_info":"Диалог HA (more-info)","tap.toggle":"Переключить состояние","marker.tap_label":"Действие по нажатию для этого устройства","tap.toggle_note":"Под селектором показано, какое состояние изменится. Защищённые устройства не переключаются с плана.","import.title":"Создать пространства из этажей HA","import.hint":"Home Assistant уже знает эти этажи. Отметьте, какие превратить в пространства плана — далее для каждого попросим картинку плана. Комнаты затем обводятся вручную по плану.","import.start":"Создать: {n}","import.manual":"Начать с нуля","import.progress":"Этаж {i} из {n}","import.done":"Пространства созданы. Обведите комнаты: кликайте по точкам сетки и замкните контур.","btn.skip":"Пропустить","space.scale_label":"Масштаб (размер клетки сетки)","space.scale_unit":"см на клетку","space.display_section":"Отображение","space.show_borders":"Всегда отображать границы комнат","space.show_names":"Отображать названия комнат (перетаскиваются)","space.room_color":"Цвет границ и названий","space.opacity":"Прозрачность","space.fill_label":"Заливка комнат","fill.none":"Нет","fill.lqi":"По силе зигби-сигнала","fill.light":"По освещению","fill.custom":"Свой цвет","space.custom_fill":"Цвет заливки","space.source_file":"У меня есть картинка плана","space.source_draw":"Нет подложки — нарисую комнаты вручную","space.orientation":"Холст","orient.landscape":"Альбомный","orient.portrait":"Портретный","orient.square":"Квадрат","fill.temp":"По температуре","space.temp_min":"Комфорт от","space.temp_max":"до","tip.temp_avg":"средняя температура:","space_card.button":"Перейти к пространству","space_card.not_found":"Пространство «{id}» не найдено","space_card.loading":"Загрузка…","continuity.restore_plan":"Восстанавливаем план…","continuity.restore_connection":"Восстанавливаем подключение к устройствам…","continuity.retry":"Повторить","editor.space":"Пространство","editor.show_button":"Показывать кнопку","editor.button_label":"Текст кнопки","editor.button_target":"Путь дашборда (куда вести)","marker.sub_entity":"сущность","title.general_settings":"Общие настройки","gs.title":"Общие настройки","gs.hint":"Цвета заливок действуют на все пространства; у каждого цвета своя прозрачность. Какой режим заливки использует пространство — задаётся в его диалоге.","gs.light_group":"Заливка: освещение","gs.light_on":"Свет включён","gs.light_off":"Весь свет выключен","gs.temp_group":"Заливка: температура","gs.temp_cold":"Холодно","gs.temp_ok":"Комфорт","gs.temp_hot":"Жарко","gs.lqi_group":"Заливка: зигби-сигнал","gs.lqi_low":"Слабый сигнал","gs.lqi_high":"Сильный сигнал","gs.reset":"Сбросить к умолчаниям","gs.saved":"Общие настройки сохранены","space.show_lqi":"Показывать зигби-сигнал (LQI) у устройств","space.hide_decor":"Скрыть декоративный слой","space.hide_decor_tip":"Линии, фигуры, надписи и мебель остаются на месте — их видно в редакторе подложки, но не на плане.","space.hide_openings":"Скрыть проёмы","space.hide_openings_tip":"Двери, окна и ворота не рисуются, но продолжают работать: свет проходит, солнце светит в окна, датчики открытия срабатывают. В редакторе плана проёмы видно всегда.","gs.light_none":"Нет источников света","mode.plan":"Редактор плана","mode.devices":"Редактор устройств","display.value":"Значение + состояние","marker.subarea":"без зоны, вручную","device.new":"Новое устройство — откройте его редактор, чтобы снять отметку","opening.unlock_action":"Открыть замок","opening.lock_action":"Закрыть замок","opening.lock_pending":"Выполняется…","title.close_editor":"Закрыть редактор (вернуться к просмотру)","devbar.add":"Добавить","devbar.show_all":"Скрытые и деактивированные","devbar.rules":"Правила иконок","space.roomcard_section":"В карточке комнаты:","space.label_temp":"Температура","space.label_hum":"Влажность","space.label_lqi":"Средний Zigbee-сигнал","space.label_light":"Свет вкл/выкл","roomcard.light_on":"Вкл","roomcard.light_off":"Выкл","roomcard.light_partial":"{on} из {total}","toast.split_pick_inside":"Промежуточные точки разреза — внутри комнаты","mode.decor":"Редактор подложки","decor.select":"Выбрать","decor.line":"Линия","decor.rect":"Прямоугольник","decor.ellipse":"Овал","decor.text":"Надпись","decor.erase":"Стереть","decor.erase_confirm_title":"Стереть объект?","decor.color":"Цвет","decor.width":"Толщина линии","decor.line_style":"Стиль линии","decor.line_style_solid":"Сплошная","decor.line_style_dashed":"Пунктирная","decor.fill":"Заливка","decor.fill_color":"Цвет заливки","decor.length":"Длина","decor.size":"Размер","decor.angle":"Угол поворота, °","decor.text_size":"Размер текста","decor.backdrop_properties":"Свойства картинки-подложки","decor.text_title":"Надпись","decor.object_title":"Редактирование: {kind}","decor.text_label":"Текст","decor.live_group":"Вставить переменную HA","decor.live_entity":"Сущность","decor.live_entity_ph":"выберите сущность","decor.live_attr":"Значение","decor.live_attr_ph":"выберите состояние или атрибут","decor.live_state":"Состояние","decor.backdrop":"Картинка-подложка","decor.backdrop_hint":"Тяните картинку — перемещение; угол — размер; верхнюю ручку — поворот. Shift меняет пропорции или снимает шаг угла.","decor.backdrop_reset":"Вернуть картинку","decor.backdrop_reset_done":"Картинка вернулась на своё место и в свой размер","marker.icon_auto":"Авто: {icon} (по правилам иконок; выберите свою, чтобы заменить)","marker.icon_pin_auto":"Закрепить","mode.plan_tip":"Редактор плана — геометрия дома: рисование и объединение/разделение комнат, привязка к зонам HA, двери, окна и ворота, карточки комнат, масштаб","mode.devices_tip":"Редактор устройств — всё про значки: перетаскивание, клик — настройка привязки/иконки/отображения, виртуальные устройства, правила иконок","mode.decor_tip":"Редактор подложки — чисто визуальный декор под планом: линии, прямоугольники, овалы и надписи, не реагирующие на клики","space.glow_enabled":"Свечение источников света","gs.glow_group":"Свечение источников света","gs.glow_base":"Темнота дома","gs.glow_light":"Цвет света по умолчанию / интенсивность","gs.wall_group":"Стены","gs.wall_fill":"Цвет заливки стен","gs.glow_radius":"Радиус свечения","gs.unit_m":"м","gs.unit_ft":"фут","marker.controls_label":"Управляет другими источниками света","marker.controls_hint":"При действии «Переключить» клик разом переключает все добавленные здесь источники. Собственная сущность маркера управляется напрямую и сюда не добавляется; чтобы считать её светом, включите флаг ниже.","marker.controls_filter":"Поиск ламп и выключателей…","info.controls":"Управляет","marker.glow_radius_label":"Радиус свечения","marker.glow_radius.help":"Задаёт радиус свечения в метрах или футах; пустое значение использует радиус из общих настроек.","marker.glow_radius.help.aria":"Подсказка: радиус свечения","markup.wallthick":"Толщина","markup.select":"Выбрать","title.markup_wallthick":"Толщина — клик по стене задаёт толщину. Пустое поле или ноль убирает толщину.","markup.hint_wallthick":"клик по стене · Esc закрывает без применения","wallthick.field":"Толщина","wallthick.unit_cm":"см","wallthick.unit_in":"дюйм","wallthick.apply_room":"Применить ко всем стенам комнаты","markup.draw_wall_title":"Толщина каждого нового сегмента стены комнаты (1–100 см). Общие стены сохраняют значение соседа.","toast.wallthick_pick":"Кликните по стене (не по открытой границе)","toast.wallthick_open":"У открытой границы нет толщины","toast.wallthick_set":"Толщина стены задана","toast.wallthick_cleared":"Толщина стены убрана","toast.physical_range":"Введите значение от 1 до {max} {unit}","toast.physical_angle":"Введите угол поворота от 0° до 90°, не включая 90°","toast.physical_limit":"В пространстве достигнут лимит объектов этого типа","markup.boundary_hint":"Выберите общую границу комнат или пунктирный участок","markup.boundary_hint_open":"Кликните начало открытого участка","markup.boundary_hint_second":"Укажите конец на выбранной границе · Esc — отмена","markup.boundary_hint_retry":"Укажите конец на выбранной границе","markup.boundary_hint_restore":"Кликните, чтобы восстановить стену","markup.boundary_hint_outer":"Открыть можно только общую границу двух комнат","markup.boundary_hint_blocked":"над границей находится перегородка, колонна или незавершённый контур","markup.boundary_hint_ambiguous":"Выберите участок чуть дальше от стыка","markup.boundary_cancel":"Отменить незавершённое действие «Граница»","toast.openwall_pick":"Инструмент работает с границами комнат","toast.openwall_shared_only":"Открыть можно только общую стену двух комнат","toast.openwall_short":"Слишком короткий отрезок — кликните дальше","toast.boundary_opened":"Участок границы открыт","toast.boundary_restored":"Стена на участке восстановлена","toast.boundary_same_edge":"Выберите конечную точку на выбранной стене","toast.boundary_ambiguous":"Выберите участок чуть дальше от стыка","toast.boundary_blocked":"Граница перекрыта независимой стеной. Сначала переместите или удалите её инструментом «Выбрать»","toast.delete_room_pick":"Кликните внутри комнаты, которую нужно удалить","toast.openwall_openings_removed":"Проёмы на виртуальном отрезке удалены","toast.openwall_opened":"Граница «{a}» ↔ «{b}» теперь открыта","toast.openwall_closed":"Граница «{a}» ↔ «{b}» снова закрыта","toast.opening_on_virtual":"Проёмы на виртуальной стене запрещены","marker.from_ha_option":"Выбрать из списка HA","marker.show_entities":"Отображать сущности","marker.show_entities_tip":"Добавляет в список не только устройства, но и все их сущности","marker.pick_ph":"Выберите устройство…","room.open_area":"Открыть зону в HA","view.volumetric":"Объёмный вид","view.flat":"Плоский вид","kiosk.title":"Размеры на этом экране","kiosk.hint":"Хранится только на этом устройстве — у каждого настенного планшета или ТВ свои удобные размеры.","kiosk.icon_scale":"Размер значков устройств","kiosk.font_scale":"Размер текста карточек комнат","editor.kiosk":"Режим настенного устройства (киоск)","editor.cycle":"Автосмена пространств каждые N секунд (киоск, 0 = выкл)","room.settings_title":"Настройки комнаты","room.settings_section":"Настройки комнаты (переопределяют пространство)","room.fill_label":"Заливка в ЭТОЙ комнате","fill.inherit":"Как у пространства","room.custom_fill_space":"Цвет пространства","room.custom_fill_own":"Цвет комнаты","room.temp_src_label":"Источник температуры","room.hum_src_label":"Источник влажности","room.src_average":"Средняя по датчикам комнаты (по умолчанию)","room.src_pick":"Конкретное устройство или сущность HA","room.src_ph":"Выберите источник…","toast.room_updated":"Комната обновлена","space.card_font":"Размер шрифта карточек комнат (всё пространство)","room.sizes_section":"Размеры шрифтов","room.name_scale":"Размер названия","room.label_scale":"Размер подписей","preview.room_name":"Гостиная","toast.cfg_reload_failed":"Не удалось перечитать план с сервера: {err}","room.settings_short":"Настройки комнаты","room.unnamed":"Комната без имени","marker.use_climate_temp":"Учитывать температуру устройства в комнате","marker.use_climate_temp_tip":"Добавляет current_temperature кондиционера или термостата в среднюю температуру комнаты. Внешний бейдж со значением настраивается отдельно ниже.","marker.value_badge_title":"Бейдж со значением","marker.value_badge.help":"Показывает одно выбранное значение рядом со значком. Не влияет на комнатные показатели, свет, Glow и действие по нажатию.","marker.value_badge.help.aria":"Подсказка: бейдж со значением устройства","marker.value_badge_enabled":"Отображать бейдж со значением","marker.value_badge_source":"Значение","marker.value_badge_source.help":"Выберите конкретное состояние, поддерживаемый атрибут или производное значение этого устройства.","marker.value_badge_source.help.aria":"Подсказка: источник значения бейджа","marker.value_badge_position":"Расположение","marker.value_badge_position.help":"Выбранная сторона сохраняется постоянно и не переворачивается автоматически у края плана.","marker.value_badge_position.help.aria":"Подсказка: расположение бейджа","marker.value_badge_right":"Справа","marker.value_badge_bottom":"Снизу","marker.value_badge_left":"Слева","marker.value_badge_top":"Сверху","marker.value_badge_empty":"У этого устройства нет доступных значений","marker.value_badge_static":"Статичный значок не показывает живые значения. Настройка сохранится.","marker.value_badge_missing":"Источник недоступен","marker.value_badge_missing_hint":"Сохранённый источник сейчас недоступен. Можно сохранить его, отключить бейдж или выбрать другой.","marker.value_badge_duplicate":"То же значение уже показано внутри значка.","marker.value_badge_state":"Состояние · {name}","marker.value_badge_marker_state":"Состояние источника света · {name}","marker.value_badge_lqi":"Качество Zigbee-сигнала (среднее)","marker.value_badge_attr_current_temperature":"Текущая температура · {name}","marker.value_badge_attr_temperature":"Целевая температура · {name}","marker.value_badge_attr_current_humidity":"Текущая влажность · {name}","marker.value_badge_attr_humidity":"Влажность · {name}","marker.value_badge_attr_current_position":"Положение · {name}","marker.value_badge_attr_percentage":"Скорость · {name}","marker.value_badge_attr_brightness":"Яркость · {name}","marker.value_badge_attr_volume_level":"Громкость · {name}","marker.value_badge_attr_battery_level":"Заряд батареи · {name}","marker.value_badge_attr_fan_speed":"Скорость вентилятора · {name}","marker.light_role_label":"Является источником света","marker.light_role.help":"Определяет, является ли сам маркер пространственным источником света. Привязанные выше лампы остаются независимыми источниками для комнаты.","marker.light_role.help.aria":"Подсказка: является ли устройство источником света","marker.light_role_auto_yes":"Автоматически (источник света)","marker.light_role_auto_no":"Автоматически (не источник света)","marker.light_role_always":"Всегда источник света","marker.light_role_never":"Никогда не источник света","marker.light_entity_label":"Ведущая сущность света","marker.light_entity.help":"Для составного устройства выберите сущность, состояние, цвет и яркость которой представляют этот источник на плане. Автоматический вариант сохраняет совместимость со старыми планами.","marker.light_entity.help.aria":"Подсказка: ведущая сущность света","marker.light_entity_auto":"Автоматически ({entity})","marker.light_entity_none":"нет управляемой сущности","marker.light_entity_missing":"Сохранённая сущность {entity} недоступна. Временно используется {fallback}; выбор восстановится, если сущность вернётся.","marker.glow_color_label":"Цвет и яркость свечения","marker.glow_mode.help":"Используйте данные источника, задайте только цвет или зафиксируйте цвет и яркость. Минимум — 1%; чтобы отключить источник, выберите «Никогда».","marker.glow_mode.help.aria":"Подсказка: цвет и яркость свечения","marker.glow_mode_auto":"Из источника","marker.glow_mode_color":"Задать цвет","marker.glow_mode_fixed":"Задать цвет и яркость","marker.glow_color":"Цвет свечения","marker.glow_brightness":"Яркость","marker.glow_disabled_never":"Настройки свечения недоступны: маркер явно исключён из источников света.","marker.glow_disabled_auto":"В автоматическом режиме у этого маркера не найден пространственный источник света.","marker.glow_disabled_no_entity":"Для пространственного источника нет активной управляемой сущности Home Assistant.","marker.glow_passive_hint":"У источника нет собственных данных Home Assistant. Задайте цвет и яркость вручную; радиус остаётся доступным.","marker.control_broken":"Сохранённый источник отсутствует или больше не отмечен как источник света","marker.control_missing_label":"Отсутствующий источник на плане","marker.control_passive":"пассивный источник","toast.marker_control_cycle":"Такая связь создаст циклическую цепочку управления светом.","toast.marker_binding_required":"Сначала выберите устройство Home Assistant, затем добавьте управляемый источник света.","confirm.unlock":"Открыть замок «{name}»?","toast.files_migrate_failed":"Не удалось перенести вложения к новой привязке, ссылки остались на старые файлы: {err}","space.pick_saved":"Уже загруженные","space.pick_saved_hint":"Планы, сохранённые на сервере, включая отцеплённые ранее","space.no_saved":"На сервере пока нет сохранённых планов.","space.loading":"Загрузка…","space.used_by":"используется: {list}","space.in_use":"План используется пространством — сначала отцепите его","btn.use":"Выбрать","confirm.delete_plan":"Удалить файл плана «{name}» с сервера? Действие необратимо.","toast.plans_list_failed":"Не удалось получить список планов: {err}","toast.plan_delete_failed":"Не удалось удалить план: {err}","marker.hide":"Скрыть","marker.hide_tip":"Устройство исчезнет с плана после сохранения, но продолжит участвовать в расчёте сигнала комнаты. Вернуть его можно через кнопку «Скрытые и деактивированные» в редакторе устройств.","marker.show":"Показать","marker.show_tip":"Устройство снова появится на плане после сохранения.","marker.hidden_ghost":"Устройство скрыто пользователем","marker.ha_disabled_device":"Устройство деактивировано в Home Assistant и скрыто с плана.","marker.ha_disabled_entity":"Сущность деактивирована в Home Assistant и скрыта с плана.","marker.ha_disabled_all_entities":"У устройства нет активных сущностей Home Assistant, поэтому оно скрыто с плана.","marker.ha_registry_limited":"Полный реестр Home Assistant недоступен этому пользователю. Неподтверждённый объект временно нельзя показывать или использовать.","marker.delete_tip":"Полностью удалить устройство с плана и из всех расчётов. Позже его можно добавить заново.","tap.run":"Запустить автоматизацию/скрипт/сцену","tap.cover":"Открыть/закрыть (шторы/жалюзи)","marker.run_target_label":"Что запускать","marker.run_search_ph":"Поиск: автоматизация, скрипт или сцена…","marker.run_target_gone":"Цель {id} не найдена — выберите заново","marker.tap_confirm":"Спрашивать подтверждение","marker.tap_confirm_tip":"Перед выполнением показать диалог подтверждения — защита от случайных нажатий.","marker.toggle_hint_single":"Цель: {name} ({id}).","marker.virtual_light_target":"Виртуальный свет: {name}.","marker.virtual_light_current":"Ручное состояние: {state} → {effect}.","marker.virtual_light_state_on":"включён","marker.virtual_light_state_off":"выключен","marker.toggle_hint_group":"Переключатся источники ({count}): {names}.","marker.toggle_hint_current":"Сейчас: {state} → {effect}.","marker.toggle_hint_group_current":"Сейчас включено {on} из {count} → {effect}.","marker.toggle_hint_skipped":"Не будут затронуты ({count}): {targets}.","marker.toggle_effect_turn_on":"включится","marker.toggle_effect_turn_off":"выключится","marker.toggle_effect_open":"откроется","marker.toggle_effect_close":"закроется","marker.toggle_effect_stop":"остановится","marker.toggle_effect_toggle":"состояние переключится","marker.toggle_skip_missing":"не найдено","marker.toggle_skip_ha_disabled":"деактивировано в HA","marker.toggle_skip_unavailable":"недоступно","marker.toggle_skip_unsupported":"переключение не поддерживается","marker.toggle_skip_secure":"заблокировано из соображений безопасности","marker.toggle_none_no_binding":"У устройства нет привязки, состояние переключать нечем. По нажатию ничего не произойдёт.","marker.toggle_none_no_actionable_entity":"У устройства нет состояния, которое можно переключить. По нажатию ничего не произойдёт.","marker.toggle_none_configured_targets_missing":"Настроенные цели сейчас недоступны. Собственная сущность устройства не будет подставлена вместо них.","marker.toggle_none_ha_disabled":"Цель деактивирована в Home Assistant и не может использоваться на плане.","marker.toggle_none_unavailable":"Цель сейчас недоступна. По нажатию ничего не произойдёт.","marker.toggle_none_unsupported":"Для этой сущности Home Assistant не предоставляет безопасного переключения. По нажатию ничего не произойдёт.","marker.toggle_none_secure":"Переключение замков, сигнализации и защитных ворот с плана заблокировано из соображений безопасности.","run.automation":"автоматизация","run.script":"скрипт","run.scene":"сцена","confirm.tap_run":"Запустить «{name}»?","confirm.tap_toggle":"Переключить «{name}»?","confirm.tap_cover":"Открыть/закрыть «{name}»?","toast.run_started":"Запущено: {name}","toast.run_target_missing":"Цель запуска не найдена — проверьте настройки устройства","toast.run_target_required":"Выберите автоматизацию, скрипт или сцену","toast.tap_target_changed":"Цель действия изменилась. Повторите попытку.","toast.virtual_light_toggle_failed":"Не удалось переключить виртуальный свет: {err}","toast.value_badge_source_required":"Выберите значение для бейджа","btn.run":"Выполнить","vac.section":"Робот-пылесос: живая позиция","vac.autocal":"Настроить автоматически","vac.live":"Живая позиция на плане","vac.trail":"Показывать путь робота","vac.cal_maps":"Откалиброваны карты: {maps}","vac.autocal_no_rooms":"Интеграция не отдаёт список комнат — откройте «Подогнать вручную»","vac.autocal_no_match":"Не совпали имена комнат (нужно ≥3 общих) — откройте «Подогнать вручную»","vac.autocal_done":"Готово: привязка по {rooms} комнатам. Запустите уборку и проверьте","vac.cal_need_pos":"Робот сейчас не отдаёт координаты — запустите уборку и поставьте на паузу","vac.cal_done":"Калибровка сохранена. Запустите уборку и проверьте","vac.cal_cancelled":"Калибровка отменена","vac.fit":"Подогнать вручную","vac.fit_hint":"Перетащите карту робота на место, растяните за уголки","vac.fit_rotate":"Повернуть 90°","vac.fit_mirror":"Отразить","vac.trail_never":"Не показывать никогда","vac.trail_cleaning":"Во время уборки","vac.trail_always":"Показывать всегда","gs.bg_group":"Фон сцены","gs.bg_color":"Цвет фона вокруг плана","gs.bg_default":"Как в теме","gs.bg_theme":"по умолчанию — из темы","gs.bg_mode":"Фон плана","gs.bg_static":"Статичный цвет","gs.bg_daynight":"Следует за Солнцем","gs.bg_daynight_hint":"Фон проходит рассвет, день, закат и ночь по данным sun.sun, а при их недоступности — по локальным часам. Компас для фона не нужен.","gs.sun_group":"Солнце","gs.sun_missing":"Сущность sun.sun не найдена — фон следует локальным часам, а оконные лучи недоступны.","gs.north":"Север на плане","gs.north_ph":"не задан","gs.north_hint":"Север нужен только для оконных лучей (шаг 1°, с Shift 15°); солнечный фон работает без него.","gs.north_clear":"Сбросить","gs.north_letter":"С","gs.sun_rays":"Солнце в окнах","gs.about_group":"О карточке","gs.about_version":"Houseplan Card v{v}","gs.about_github":"GitHub · документация и issues","gs.about_telegram":"Чат в Telegram","space.bg_color":"Цвет фона вокруг плана","space.bg_inherit":"Наследовать общий","space.bg_inherited":"наследуется из общих настроек","space.bg_mode":"Фон плана","space.north":"Север на плане (переопределение)","space.north_inherited":"наследуется: {v}","space.sun_rays":"Солнце в окнах","space.sun_inherit":"Наследовать общий","space.sun_on":"Вкл","space.sun_off":"Выкл","canvas.far_objects":"Объектов далеко от плана: {n}","canvas.show_far":"Показать","canvas.home_tip":"План там — нажмите, чтобы вписать","gs.grid_group":"Обслуживание планов","gs.grid_hint":"Обновляет модели данных, выравнивает элементы по сетке и объединяет лишние фрагменты стен. Перед записью будет показан точный отчёт.","gs.align_all":"Оптимизировать планы","gs.align_title":"Оптимизировать планы","gs.align_none":"Все планы уже используют актуальную и оптимальную модель данных.","gs.align_count":"Сдвинется элементов: {n} из {total}, максимум на {cm} см.","gs.align_where":"Наибольший сдвиг — в пространстве «{s}».","gs.align_turned":"Проёмов с исправлением угла: {n}.","gs.align_removed_drafts":"Схлопнувшиеся на сетке некорректные контуры удалены: {n}.","gs.optimize_changes":"Миграций модели: {m}; канонизировано планов: {c}; объединено отрезков реальных стен: {w}; виртуальных: {s}.","gs.optimize_glow_migration":"Старый Glow: пространств — {spaces} → без заливки данных + независимый Glow; комнат — {rooms} → наследуемая заливка + независимый Glow.","gs.align_warn":"Элементы, намеренно поставленные между узлами, будут сдвинуты. После операции доступна одна отмена — только до следующего изменения плана.","gs.align_run":"Оптимизировать","gs.align_done":"Планы оптимизированы: сдвинуто элементов — {n}, обслужено записей — {m}","gs.optimize_undo":"Отменить последнюю оптимизацию","gs.optimize_undone":"Последняя оптимизация отменена","decor.furniture":"Мебель","furn.title":"Библиотека мебели","furn.symbol":"Символ","furn.group_furniture":"Мебель","furn.group_appliance":"Техника","furn.group_sanitary":"Сантехника","furn.group_other":"Прочее","furn.width":"Ширина","furn.depth":"Глубина","furn.pick_hint":"Выберите символ и кликните по плану.","furn.place_hint":"Кликните по плану — предмет встанет к ближайшей стене. Shift — свободно.","furn.sym_sofa":"Диван","furn.sym_armchair":"Кресло","furn.sym_coffee_table":"Журнальный столик","furn.sym_table_dining":"Обеденный стол","furn.sym_table_round":"Круглый стол","furn.sym_chair":"Стул","furn.sym_desk":"Письменный стол","furn.sym_bed_double":"Двуспальная кровать","furn.sym_bed_single":"Односпальная кровать","furn.sym_nightstand":"Тумбочка","furn.sym_wardrobe":"Шкаф","furn.sym_bookshelf":"Стеллаж","furn.sym_fridge":"Холодильник","furn.sym_stove":"Плита","furn.sym_dishwasher":"Посудомоечная машина","furn.sym_washer":"Стиральная машина","furn.sym_dryer":"Сушильная машина","furn.sym_tv":"Телевизор","furn.sym_ac":"Кондиционер","furn.sym_water_heater":"Бойлер","furn.sym_toilet":"Унитаз","furn.sym_bathtub":"Ванна","furn.sym_shower":"Душ","furn.sym_sink":"Раковина","furn.sym_kitchen_sink":"Кухонная мойка","furn.sym_bidet":"Биде","furn.sym_stairs":"Лестница","furn.sym_fireplace":"Камин","furn.sym_plant":"Растение","furn.sym_rug":"Ковёр","common.yes":"Да","common.no":"Нет","vac.diag_source":"Источник","vac.diag_platform":"Интеграция","vac.diag_status":"Статус","vac.diag_position":"Позиция","vac.diag_rooms":"Комнаты","vac.diag_rooms_value":"{total} · совпало имён: {matched} · {readiness}","vac.autocal_ready":"автокалибровка доступна","vac.autocal_not_ready":"нужно 3 совпавших имени","vac.diag_path":"Путь интеграции","vac.diag_map":"ID карты","vac.source_none":"не выбран","vac.source_status_ok":"Готов","vac.source_status_missing":"Не найден","vac.source_status_disabled":"Деактивирован в Home Assistant","vac.source_status_unavailable":"Недоступен","vac.source_status_unverified":"Нельзя проверить с текущими правами","vac.source_status_unsupported":"Нет данных позиции","vac.source_status_none":"Нет источника","vac.source_banner_missing":"Сохранённый источник больше не существует. Он не заменён автоматически: выберите другой или восстановите его в Home Assistant.","vac.source_banner_disabled":"Сохранённый источник деактивирован в Home Assistant. Активируйте его там или выберите другой.","vac.source_banner_unverified":"Текущих прав Home Assistant недостаточно для проверки источника. Привязка сохранена и не будет заменена автоматически.","vac.choose_source":"Выбрать источник","vac.source_auto":"Автоматически","vac.source_auto_hint":"Использовать совместимую сущность этого устройства","vac.all_cameras":"Все камеры","vac.all_cameras_warn":"Камера может не отдавать данные робота. Выбирайте её только если это карта вашего пылесоса.","vac.all_cameras_empty":"Других сущностей камеры не найдено.","vac.platform_unknown":"интеграция неизвестна","vac.cap_position":"позиция","vac.cap_rooms_short":"комнаты","vac.cap_path":"путь","vac.cap_map":"ID карты","vac.cap_none":"данные робота не обнаружены","vac.xcme_hint":"Включите атрибуты Xiaomi Cloud Map Extractor:","vac.documentation":"Документация","vac.residual_title":"Проверьте автокалибровку","vac.residual_message":"Совпавшие комнаты расходятся максимум на {error}. Примените приблизительную калибровку, подгоните её вручную либо отмените без изменения сохранённых настроек.","vac.apply_proposal":"Применить","gs.backup_group":"Резервная копия и перенос","gs.backup_hint":"Скачайте переносимую JSON-копию или проверьте и импортируйте копию, созданную House Plan.","backup.export_open":"Экспорт","backup.import_open":"Импорт","backup.export_title":"Экспорт House Plan","backup.import_title":"Импорт House Plan","backup.export_hint":"Выберите, должна ли копия содержать всю конфигурацию House Plan или только текущее пространство.","backup.full":"Полная копия","backup.current_space":"Текущее пространство","backup.current_space_title":"Текущее пространство: {title}","backup.no_current_space":"Нет текущего пространства","backup.privacy_warning":"Архив сохраняет названия, идентификаторы Home Assistant и точные координаты. Внутренние планы и вложения указываются ссылками, но не вкладываются; текущие состояния и маршруты пылесосов не включаются.","backup.download":"Скачать JSON","backup.export_done":"Резервная копия скачана","backup.reading":"Проверяем резервную копию…","backup.revalidated":"После предпросмотра план изменился. Сводка обновлена — проверьте её и подтвердите ещё раз.","backup.error.unauthorized":"У вас нет прав на экспорт или импорт этого плана.","backup.error.not_ready":"House Plan ещё не готов. Повторите попытку через несколько секунд.","backup.error.too_large":"Размер резервной копии превышает 8 МиБ.","backup.error.invalid_json":"Выбранный файл не является корректным JSON.","backup.error.invalid_format":"Выбранный файл не является резервной копией House Plan.","backup.error.unsupported_export_version":"Эта версия формата резервной копии не поддерживается установленной версией.","backup.error.future_model":"Резервная копия создана в более новой версии модели данных House Plan.","backup.error.invalid_config":"Резервная копия содержит некорректную конфигурацию House Plan.","backup.error.invalid_layout":"Резервная копия содержит некорректные позиции объектов.","backup.error.invalid_content":"Резервная копия содержит некорректные или несогласованные ссылки на файлы.","backup.error.space_not_found":"Выбранное пространство больше не существует.","backup.error.capacity_exceeded":"Добавление этой копии превысит ограничения размера плана.","backup.error.preview_expired":"Время предпросмотра истекло. Выберите файл резервной копии заново.","backup.error.preview_owner_mismatch":"Этот предпросмотр принадлежит другому пользователю Home Assistant.","backup.error.conflict":"После предпросмотра план изменился. Проверьте обновлённую сводку.","backup.error.content_confirmation_required":"Подтвердите отсоединение недоступных локальных файлов.","backup.error.commit_failed":"Не удалось безопасно применить копию. Предыдущий план восстановлен или ожидает восстановления.","backup.error.missing_plan":"После предпросмотра исчез связанный файл плана. Проверьте копию ещё раз.","backup.error.missing_content":"После предпросмотра исчез связанный локальный файл. Проверьте копию ещё раз.","backup.error.marker_control_missing":"В резервной копии отсутствует связанный источник света на плане.","backup.error.marker_control_not_light":"Связанная цель больше не отмечена как источник света.","backup.error.marker_control_self":"Источник света не может управлять самим собой.","backup.error.marker_control_cycle":"Резервная копия содержит циклическую цепочку управления светом.","backup.error.duplicate_marker_control":"Резервная копия содержит повторяющуюся связь с источником света.","backup.error.no_backup":"Нет снимка импорта или оптимизации, который можно восстановить.","backup.same_source":"Создано на этом экземпляре Home Assistant","backup.foreign_source":"Создано на другом экземпляре Home Assistant","backup.created":"Создано: {value}","backup.versions":"Карточка {card}; интеграция {integration}; модель данных {model}","backup.count_spaces":"Пространств: {n}","backup.count_rooms":"Комнат: {n}","backup.count_walls":"Стен: {n}","backup.count_openings":"Проёмов: {n}","backup.count_decor":"Объектов декора: {n}","backup.count_markers":"Устройств: {n}","backup.count_layout":"Позиций: {n}","backup.bindings":"Привязки — устройства: {device}, сущности: {entity}, виртуальные: {virtual}; позиций без пространства: {legacy}","backup.binding_status":"Статус на целевом экземпляре — активных: {active}, деактивированных: {disabled}, отсутствующих: {missing}","backup.missing_areas":"На целевом экземпляре отсутствуют зоны: {areas}","backup.dropped_marker_links":"Связи с источниками света вне переносимого пространства пропущены: {n}.","backup.replace_warning":"Текущая конфигурация и расположение будут заменены. Загруженные файлы не удаляются. Одну отмену можно выполнить до следующего изменения плана.","backup.foreign_bookkeeping":"Служебные списки известных и новых устройств другого экземпляра импортированы не будут.","backup.final_name":"Название нового пространства","backup.target_settings":"Глобальные настройки целевого экземпляра не изменятся.","backup.duplicates":"Уже существующих привязок: {n}","backup.skip":"Пропустить повторяющиеся устройства","backup.virtual_copy":"Добавить безопасные статичные виртуальные копии","backup.content":"Связанное содержимое","backup.content_available":"доступно локально","backup.content_external":"внешняя ссылка","backup.content_detach_required":"будет отвязано","backup.confirm_detach":"Я понимаю, что недоступные внутренние планы и вложения будут отвязаны от импортируемой конфигурации.","backup.replace":"Заменить","backup.add":"Добавить пространство","backup.space_done":"Пространство импортировано: комнат — {rooms}, устройств — {markers}","backup.full_done":"Копия восстановлена: пространств — {spaces}, комнат — {rooms}, устройств — {markers}","backup.undo_import":"Отменить последний полный импорт","backup.import_undone":"Полный импорт отменён"}};function Uo(t,e){if(e&&e in Wo)return e;return(t?.locale?.language||t?.language||"en").toLowerCase().startsWith("ru")?"ru":"en"}function Go(t,e,i){return Ki(Wo[t][e]??jo[e]??e,i)}function Vo(t,e){const i=Wo[t][e]??Wo.en[e];return"string"==typeof i&&i.trim().length>0}class Ko extends ht{constructor(){super(...arguments),this.deviceName="",this._metadataConnection=null,this._demoUntil=0,this._demoTimer=0,this._demoGeneration=1,this._demoKind=null,this._reducedMotion=!1,this._onMotionChange=t=>{this._reducedMotion=t.matches,this.requestUpdate()},this._startShortDemo=()=>{const t=this.presentation;t&&"icon_ripple"===t.display&&"none"===t.pulse.kind&&(this._demoGeneration++,this._demoKind="short",this._demoUntil=Date.now()+3300,window.clearTimeout(this._demoTimer),this._demoTimer=window.setTimeout(()=>{this._demoUntil=0,this._demoTimer=0,this._demoKind=null,this.requestUpdate()},3330),this.requestUpdate())},this._toggleContinuousDemo=()=>{const t=this.presentation;t&&"icon_ripple"===t.display&&"none"===t.pulse.kind&&("continuous"===this._demoKind?this._clearDemo():(this._clearDemo(),this._demoGeneration++,this._demoKind="continuous",this._demoUntil=Number.POSITIVE_INFINITY),this.requestUpdate())}}connectedCallback(){super.connectedCallback(),this._motionMedia=window.matchMedia?.("(prefers-reduced-motion: reduce)"),this._reducedMotion=!!this._motionMedia?.matches,this._motionMedia?.addEventListener?.("change",this._onMotionChange),this._ensureMetadata()}disconnectedCallback(){this._metadataRelease?.(),this._metadataRelease=void 0,this._metadataConnection=null,this._clearDemo(),this._motionMedia?.removeEventListener?.("change",this._onMotionChange),this._motionMedia=void 0,super.disconnectedCallback()}willUpdate(t){t.has("hass")&&this._ensureMetadata();const e=this.presentation,i=t.get("presentation");t.has("presentation")&&i?.binding!==e?.binding&&this._clearDemo(),e&&this._demoKind&&("icon_ripple"!==e.display||"none"!==e.pulse.kind)&&this._clearDemo()}_ensureMetadata(){const t=this.hass?.connection||null;t&&t!==this._metadataConnection&&(this._metadataRelease?.(),this._metadataConnection=t,this._metadataRelease=Oo(this.hass,()=>this.requestUpdate()))}_clearDemo(){window.clearTimeout(this._demoTimer),this._demoTimer=0,this._demoUntil=0,this._demoKind=null}get _lang(){return Uo(this.hass)}_t(t,e){return Go(this._lang,t,e)}_providerText(t){return"houseplan"===t.domain?this._t("marker.preview.virtual_provider"):t.label.toLowerCase()===t.domain.toLowerCase()?t.domain:`${t.label} (${t.domain})`}_providers(){return this.presentation&&this.registry?function(t,e,i,s=No(t)){if("virtual"===e)return[{domain:"houseplan",label:"House Plan",confidence:"registry-owner"}];const n=e.indexOf(":");if(n<1)return[];const o=e.slice(0,n),r=e.slice(n+1);if("entity"===o){const e=Bo(t,r,i,s);return e?[e]:[]}if("device"!==o)return[];const a=i.devices?.[r]||t?.devices?.[r];if(!a)return[];const l=[a.config_entry_id,...Array.isArray(a.config_entries)?a.config_entries:[]].filter(t=>"string"==typeof t&&!!t).map(e=>{const i=s.configEntries[e];return Ho(t,i?.domain,"registry-owner",s,e)});if(l.some(Boolean))return qo(l);const c=[];for(const[e,n]of Object.entries(i.entities||{}))n?.device_id===r&&null==n?.disabled_by&&c.push(Bo(t,e,i,s));return c.some(Boolean)?qo(c):qo((Array.isArray(a.identifiers)?a.identifiers:[]).map(e=>{const i=Array.isArray(e)?e[0]:null;return Ho(t,i,"identifier-fallback",s)}))}(this.hass,this.presentation.binding,this.registry,No(this.hass)):[]}_providerSummary(t){if(!t.length)return this._t("marker.preview.unknown_provider");const e=t.slice(0,2).map(t=>this._providerText(t)),i=t.length-e.length;return e.join(", ")+(i>0?` · ${this._t("marker.preview.more_sources",{n:i})}`:"")}_sourceSummary(t){const e=t.visualSources;return e.length?1===e.length?`${e[0].name} · ${e[0].eid} · ${this._sourceProvider(e[0].eid)}`:2===e.length?e.map(t=>t.name).join(" · "):this._t("marker.preview.multiple_sources",{n:e.length}):this._t("marker.preview.no_source")}_stateSummary(t){if(!t.visualSources.length)return this._t("marker.preview.no_state");if(1===t.visualSources.length)return t.visualSources[0].stateText||this._t("marker.preview.no_state");const e=[...new Set(t.visualSources.map(t=>t.stateText).filter(Boolean))];return 1===e.length?e[0]:this._t("marker.preview.mixed_states")}_reason(t){return this._t(`marker.preview.reason.${t}`)}_sourceProvider(t){if(!this.registry)return this._t("marker.preview.unknown_provider");const e=Bo(this.hass,t,this.registry,No(this.hass));return e?this._providerText(e):this._t("marker.preview.unknown_provider")}render(){const t=this.presentation;if(!t)return G;const e=!!this._demoKind&&this._demoUntil>Date.now()&&"icon_ripple"===t.display&&"none"===t.pulse.kind,i=e?(()=>{const e=function(t,e,i,s,n=null){return{kind:e,reason:"short"===e?"event":"running",generation:Math.max(1,Math.trunc(i)),expiresAt:"short"===e?n:null,color:s?null:t.color,diameterScale:s?1:t.diameterScale,animated:!s,reducedMotionIndicator:s?"dot":"none"}}(t.pulse,this._demoKind,this._demoGeneration,this._reducedMotion,"short"===this._demoKind?this._demoUntil:null);return{...t,pulse:e,classes:[...t.classes.filter(t=>!t.startsWith("pulse-")),`pulse-${e.kind}`,...e.generation%2==0?["pulse-gen2"]:[]]}})():t,s=this._providers(),n="none"!==t.pulse.kind,o=i.scale*(i.pulse.animated?i.pulse.diameterScale:1);let r=-o/2,a=o/2,l=-o/2,c=o/2;if(i.valueBadge){const t=i.scale*Math.min(4,Math.max(.9,.29*i.valueBadge.text.length+.28)),e=.7*i.scale,s=.6*i.scale;"right"===i.valueBadge.position&&(a=Math.max(a,s+t)),"left"===i.valueBadge.position&&(r=Math.min(r,-s-t)),"top"===i.valueBadge.position&&(l=Math.min(l,-s-e)),"bottom"===i.valueBadge.position&&(c=Math.max(c,s+e))}if(null!=i.lqiText){const t=i.scale*("bottom"===i.valueBadge?.position?1.55:.95);c=Math.max(c,t)}const h=Math.min(1,2.35/Math.max(1,a-r),2.35/Math.max(1,c-l)),d=Math.round(100*h),u=[`left:calc(50% - ${54*((r+a)/2)*h}px)`,`top:calc(50% - ${54*((l+c)/2)*h}px)`,h<1?`transform:scale(${h})`:""].filter(Boolean).join(";"),p=["dev",...i.classes,"virtual"===i.binding?"virtual":"",i.haDisabled?"ghost ha-disabled":"",null!=i.valueText?"valonly":""].filter(Boolean).join(" "),_=Ro(i).join(";"),m=e?this._t("continuous"===this._demoKind?"marker.preview.demo_continuous_notice":"marker.preview.demo_short_notice"):this._reason(t.explanation.reason),g=[this.deviceName,this._providerSummary(s),this._stateSummary(t),m,t.valueFullText||"",fo(t.valueBadge)].filter(Boolean).join(". ");return j`
+ `;var o}function Io(t,e,i){return{kind:"none",reason:"none",generation:t,expiresAt:null,color:e,diameterScale:i,animated:!1,reducedMotionIndicator:"none"}}const Eo=new WeakMap;function zo(t){const e=function(t){const e=t?.connection||t;return!e||"object"!=typeof e&&"function"!=typeof e?null:e}(t);if(!e)return null;let i=Eo.get(e);return i||(i={revision:0,loaded:!1,configEntries:{},manifests:{},listeners:new Set,refs:0},Eo.set(e,i)),i}function Fo(t){t.revision++;for(const e of[...t.listeners])try{e()}catch{}}function Oo(t,e){const i=zo(t);if(!i)return()=>{};i.refs++,i.listeners.add(e),async function(t,e){e.loading||"function"!=typeof t?.callWS?e.loading:e.loaded&&e.loadedAt&&Date.now()-e.loadedAt<3e5||(e.loading=(async()=>{const[i,s]=await Promise.allSettled([t.callWS({type:"config_entries/get"}),t.callWS({type:"manifest/list"})]);"fulfilled"===i.status&&Array.isArray(i.value)&&(e.configEntries=Object.fromEntries(i.value.filter(t=>"string"==typeof t?.entry_id).map(t=>[t.entry_id,t]))),"fulfilled"===s.status&&Array.isArray(s.value)&&(e.manifests=Object.fromEntries(s.value.filter(t=>"string"==typeof t?.domain).map(t=>[t.domain,t]))),e.loaded=!0,e.loadedAt=Date.now(),e.loading=void 0,Fo(e)})().catch(()=>{e.loaded=!0,e.loadedAt=Date.now(),e.loading=void 0,Fo(e)}),e.loading)}(t,i),async function(t,e){if(e.unsubscribe||e.subscribing)return e.subscribing;const i=t?.connection?.subscribeMessage;"function"==typeof i&&(e.subscribing=(async()=>{try{e.unsubscribe=await i.call(t.connection,t=>{const i=Array.isArray(t)?t:Array.isArray(t?.entries)?t.entries:t?[t]:[];let s=!1;const n={...e.configEntries};for(const t of i){const e=t?.entry||t?.data?.entry||("string"==typeof t?.entry_id?t:null),i=e?.entry_id;if("string"!=typeof i)continue;const o=t?.type||t?.action||t?.data?.action;"removed"===o||"remove"===o?delete n[i]:n[i]=e,s=!0}s&&(e.configEntries=n,Fo(e))},{type:"config_entries/subscribe"})}catch{}finally{0===e.refs&&(e.unsubscribe?.(),e.unsubscribe=void 0),e.subscribing=void 0}})(),e.subscribing)}(t,i);let s=!1;return()=>{s||(s=!0,i.listeners.delete(e),i.refs=Math.max(0,i.refs-1),i.refs||(i.unsubscribe?.(),i.unsubscribe=void 0))}}function No(t){const e=zo(t);return e?{revision:e.revision,loaded:e.loaded,configEntries:e.configEntries,manifests:e.manifests}:{revision:0,loaded:!1,configEntries:{},manifests:{}}}function Lo(t,e,i=No(t),s){const n=i.manifests[e];return function(t,e){if("function"!=typeof t?.localize)return"";try{const i=`component.${e}.title`,s=t.localize(i);return"string"==typeof s&&s!==i?s:""}catch{return""}}(t,e)||String(n?.name||"")||String(s||"")||e.replace(/_/g," ")}function Ho(t,e,i,s,n){const o=String(e||"").trim();if(!o)return null;const r=n?s.configEntries[n]:null;return{domain:o,label:Lo(t,o,s,r?.title),configEntryId:n,configEntryTitle:r?.title,confidence:i}}function qo(t){const e={"registry-owner":0,"entity-platform":1,"identifier-fallback":2},i=new Set;return t.filter(t=>!!t).sort((t,i)=>e[t.confidence]-e[i.confidence]||t.label.localeCompare(i.label)||t.domain.localeCompare(i.domain)).filter(t=>{const e=`${t.domain}\n${t.configEntryId||""}`;return!i.has(e)&&(i.add(e),!0)})}function Bo(t,e,i,s=No(t)){const n=i.entities?.[e]||t?.entities?.[e];if(!n)return null;const o=n.config_entry_id||void 0,r=o?s.configEntries[o]?.domain:"";return Ho(t,n.platform||r,"entity-platform",s,o)}var jo={"editor.context_actions":"Actions: {object}","editor.tool_options":"Tool options: {tool}","editor.palette":"Palette: {tool}","editor.open_group":"Tool group: {group}","editor.group_active":"{group} — active: {item}","editor.disabled_action":"{action} is unavailable: {reason}","btn.properties":"Properties","btn.keep_as_walls":"Keep as closed walls","title.markup_partition":"Partition: draw one independent physical wall","title.markup_select":"Select and edit walls, columns and saved outlines","title.markup_column":"Column: click a grid point to place a square column","markup.partition":"Partition","markup.column":"Column","markup.hint_partition":"two clicks draw one partition · Shift locks to 45°","markup.hint_column":"click a grid point to place a square column","history.draft_segment":"Add room-draft segment","history.draft_merge":"Join unfinished room outlines","history.draft_segment_delete":"Remove room-draft segment","history.partition_add":"Add partition","history.column_add":"Add column","history.physical_edit":"Change physical object","history.physical_delete":"Delete physical object","history.physical_move":"Move physical object","history.contour_to_partitions":"Convert contour to closed walls","toast.column_duplicate":"A column with the same centre and outer size already exists","confirm.delete_draft":"Delete the whole unfinished room contour?","confirm.delete_draft_segment":"Delete this draft segment? The remaining contour may split in two.","physical.partition_properties":"Partition properties","physical.column_properties":"Column properties","physical.draft_properties":"Draft segment properties","physical.shape":"Shape","physical.square":"Square","physical.circle":"Circle","physical.diameter":"Diameter","physical.side":"Side","physical.rotation":"Rotation angle","physical.length":"Length","physical.allowed_range":"Allowed: 1–{max} {unit}","physical.delete_segment":"Delete segment","physical.delete_draft":"Delete entire outline","physical.partition_size_title":"Thickness of the partition created by the second click (1–100 cm).","physical.column_size_title":"Outer side of the square column placed by the click (1–150 cm).","card.title":"House plan","count.devices":"{n} dev.","empty.no_spaces":"No spaces yet.","empty.add_first":"Add the first space and upload a floor plan.","empty.install":'Install the House Plan integration and add it in "Devices & services".',"btn.add_space":"Add space","btn.cancel":"Cancel","btn.save":"Save","btn.close":"Close","btn.delete":"Delete","btn.edit":"Edit","btn.open_in_ha":"Open in HA","btn.reset":"Reset","btn.attach":"Attach…","btn.upload":"Upload…","btn.replace":"Replace…","title.zoom_in":"Zoom in","title.zoom_out":"Zoom out","title.zoom_fit":"Fit all","title.add_device":"Add a device to the plan","title.show_all":"Show hidden and disabled devices as service markers (this tab only)","title.markup":"Room markup: grid, lines, outlines","title.configure_space":"Configure space","title.add_space":"Add space","title.markup_add":"Room outline: connect grid dots until the room closes; Shift locks the direction in 45° steps","title.markup_merge":"Merge: click one room, then the neighbour it shares a wall with","title.markup_split":"Split a room: click the room, then two points on its walls","title.markup_delroom":"Delete room: click inside a room and confirm","title.markup_boundary":"Boundary: click two points on a solid shared wall to make that stretch virtual; click a dashed stretch to restore it","title.need_plan":"Upload a floor-plan image","markup.add":"Room outline","markup.merge":"Merge","markup.split":"Split","markup.resize":"Resize","title.markup_resize":"Resize rooms: drag a wall handle; click a room for a corner scale frame","markup.hint_resize":"drag a wall handle · click a room — corner frame · Esc cancels a drag · Ctrl+Z — undo a step","markup.opening":"Opening","markup.boundary":"Boundary","markup.delete_room":"Delete room","history.undo":"Undo","history.redo":"Redo","history.undo_named":"Undo: {name} (Ctrl+Z)","history.redo_named":"Redo: {name} (Ctrl+Shift+Z / Ctrl+Y)","history.undo_empty":"Nothing to undo","history.redo_empty":"Nothing to redo","history.undone":"Undone: {name}","history.redone":"Redone: {name}","history.add_room":"Create room","history.split_room":"Split room","history.merge_rooms":"Merge rooms","history.resize_room":"Resize room","history.open_boundary":"Open boundary","history.close_boundary":"Restore wall","history.wall_thickness":"Change wall thickness","history.add_opening":"Add door or window opening","history.edit_opening":"Edit opening","history.move_opening":"Move opening","history.delete_opening":"Delete opening","history.delete_room":"Delete room","history.decor_add":"Add decor object","history.decor_edit":"Edit decor object","history.decor_move":"Move decor object","history.decor_transform":"Transform decor object","history.decor_delete":"Delete decor object","history.backdrop_transform":"Transform plan backdrop","title.markup_opening":"Openings: click a wall to place, click an opening to edit","opening.new":"New opening","opening.edit":"Opening","opening.door":"Door","opening.window":"Window","opening.gate":"Gate","opening.type_label":"Type","opening.length_label":"Length, cm","opening.contact_label":"Open/close sensor","opening.lock_label":"Lock","opening.none":"— none —","opening.invert":"Invert open/closed","opening.flip_h":"Hinge on the other jamb","opening.flip_v":"Opens to the other side","opening.open":"Open","opening.closed":"Closed","opening.locked":"Locked","opening.unlocked":"Unlocked","opening.state_unknown":"unavailable","opening.no_entities":"No sensors bound — a static symbol on the plan.","toast.opening_no_wall":"Click next to a room wall — openings sit on walls","markup.delete":"Delete","markup.hint_points":"points: {n} · Shift — 45° steps · Esc/Ctrl+Z — undo a dot · first point or Ctrl/Cmd+click — close","markup.hint_start":"click a grid dot to start the outline","tip.lqi":"average zigbee signal:","tip.area":"area: {value}","info.device_header":"Device on the plan","info.model":"Model","info.state":"State","info.link":"Link","info.manuals":"Manuals","info.none":"No additional information","marker.new_device":"New device","marker.name_label":"Name (shown on the plan)","marker.name_ph":"Name","marker.binding_label":"Bind to an HA device","marker.binding_disabled":"disabled in Home Assistant","marker.virtual_option":"Virtual device (no binding)","marker.search_ph":"Search device / group…","marker.nothing_found":"nothing found","marker.room_label":"Room","marker.room_override":" (override placement)","marker.room_choose":"— select a room —","marker.room_auto":"— by device area (auto) —","marker.icon_label":"Icon","marker.icon_ph":"mdi:… (empty = auto)","marker.display_label":"Display","display.badge":"Icon + state","display.icon_ripple":"Icon + state and activity","display.static_icon":"Always static icon","marker.display_hint":"Icon + state changes the plate for work, open, alarm and unavailable states. Icon + state and activity additionally shows a short pulse for events and a continuous pulse for ongoing work, movement or presence. Value + state replaces the icon with one unambiguous HA value. A static icon never reacts to device states.","marker.display_hint_badge":"The icon and dynamic plate show device state without the ordinary activity pulse. Red alarms remain visible.","marker.display_hint_icon":"The icon and dynamic plate show device state without the ordinary activity pulse. Red alarms remain visible.","marker.display_hint_icon_ripple":"The icon, dynamic plate and pulse show a short pulse for events and a continuous pulse for work, motion or presence. Red alarms have separate priority.","marker.display_hint_value":"One unambiguous Home Assistant value replaces the icon while the plate continues to show state. Red alarms remain visible.","marker.display_hint_static_icon":"The black plate and icon always stay the same. State, activity, unavailability and alarms do not change the face.","marker.static_alarm_warning":"This device can report alarms. Static display hides the marker's visual alarm indication.","marker.preview.title":"Display preview","marker.preview.actual":"Now","marker.preview.example":"Example","marker.preview.integration":"Provided by","marker.preview.source":"Display source","marker.preview.current_state":"Current state","marker.preview.result":"On the plan","marker.preview.details":"Source details","marker.preview.select_source":"Choose a Home Assistant device or entity to see its actual display.","marker.preview.unknown_provider":"Unknown integration","marker.preview.virtual_provider":"House Plan · virtual device","marker.preview.no_source":"No active source","marker.preview.no_state":"No current state","marker.preview.mixed_states":"Several different states","marker.preview.multiple_sources":"{n} sources","marker.preview.more_sources":"+{n} more","marker.preview.scaled":"The preview is scaled to {n}% to fit. The saved size is unchanged.","marker.preview.demo_activity":"Show activity example","marker.preview.demo_notice":"Temporary activity example; the actual device state is unchanged.","marker.preview.demo_short":"Show short activity","marker.preview.demo_continuous":"Show continuous activity","marker.preview.stop_continuous":"Stop continuous activity","marker.preview.demo_short_notice":"Short activity example; the actual device state is unchanged.","marker.preview.demo_continuous_notice":"Continuous activity example; the actual device state is unchanged.","marker.preview.demo_already_visible":"The current state already shows a real activity or alarm effect.","marker.preview.reduced_motion":"System reduced-motion is enabled, so ordinary activity is shown as a dot.","marker.preview.reason.neutral":"Neutral dark plate","marker.preview.reason.working":"Yellow plate: the device is working now","marker.preview.reason.working_activity":"Yellow plate and activity effect: the device is working now","marker.preview.reason.open":"Orange plate: physically open or unlocked","marker.preview.reason.cover_icon_state":"Neutral plate; the cover entity controls the displayed state and icon","marker.preview.reason.presence":"Activity effect while presence is detected","marker.preview.reason.event":"Short activity effect after a detected event","marker.preview.reason.transition":"Activity effect while the device is moving or changing state","marker.preview.reason.media_neutral":"Media devices stay on a neutral dark plate while available","marker.preview.reason.unavailable":"Subdued neutral plate: unavailable or off","marker.preview.reason.alarm":"Red alarm plate; alarm indication is always shown","marker.preview.reason.live_states_disabled":"Live-state styling is disabled for this card","marker.preview.reason.value_no_state":"No usable state value; the icon is shown instead","marker.preview.reason.value_ambiguous_sources":"Several possible value sources; the icon is shown instead","marker.preview.reason.value_non_scalar":"The source did not return a simple value; the icon is shown instead","marker.preview.reason.value_virtual":"A virtual device has no Home Assistant value; the icon is shown instead","marker.preview.reason.vacuum_live_plan_only":"Live vacuum position and trail are available only on the full plan","marker.preview.reason.hidden_design_preview":"The device is hidden on the plan; this design preview remains visible","marker.preview.reason.composite_power_source":"State comes from the device Power entity; auxiliary switches are ignored","marker.preview.reason.activity_display_disabled":"The selected display mode does not show ordinary activity effects","marker.preview.reason.ha_disabled":"The Home Assistant binding is disabled and will be hidden on the plan","marker.preview.reason.orphaned":"The saved Home Assistant binding can no longer be found","marker.preview.reason.static_icon":"Static mode: device state does not change the icon","marker.activity_color":"Activity pulse color","marker.ripple_size":"Activity pulse size","marker.activity_alarm_note":"Color and size affect ordinary activity only and do not change red alarms.","marker.pulse_a11y_alarm":"Alarm","marker.pulse_a11y_event":"Recent event","marker.pulse_a11y_presence":"Presence detected","marker.pulse_a11y_transition":"Changing state","marker.pulse_a11y_running":"Working","marker.size_label":"Icon size / rotation","marker.angle_label":"Rotate","marker.model_label":"Model","marker.model_ph":"e.g. Aqara T&H","marker.link_label":"Link","marker.desc_label":"Description","marker.desc_ph":"Notes, specs…","marker.manuals_label":"Manuals (PDF etc.)","marker.sub_device":"device","marker.sub_z2m_group":" · Z2M group","marker.sub_group":"group","marker.sub_helper":"helper","space.new":"New space","space.header":"Space","space.title_label":"Title","space.title_ph":"e.g. Garage","space.plan_label":"Floor plan (background)","space.no_plan":"no plan image","space.plan_alt":"plan","room.new":"New room","room.name_label":"Display name","room.name_ph":"e.g. Terrace","room.area_label":"Home Assistant area (unassigned)","room.no_area_option":"— no area —","room.default_name":"Room","device.unnamed":"unnamed","device.light_group":"light group","device.fallback":"device","device.virtual":"virtual device","confirm.delete_room":'Delete room "{name}"?',"confirm.merge_rooms":'Merge rooms "{a}" and "{b}"?',"confirm.remove_marker":'Delete "{name}" from the plan? The device will disappear completely and stop contributing to plan data. You can add it again later.',"confirm.erase_decor":"Erase the {kind} object? You can undo this action from the editor history.","confirm.delete_space":'Delete space "{title}" with all its rooms and markup?',"toast.pos_save_failed":"Failed to save position: {err}","toast.no_entity":"The device has no suitable entity","toast.ha_disabled_action":"A Home Assistant object that is disabled cannot be used on the plan.","toast.ha_disabled_show_device":"A device disabled in Home Assistant cannot be shown on the plan. Enable it in Home Assistant first.","toast.ha_disabled_show_entity":"An entity disabled in Home Assistant cannot be shown on the plan. Enable it in Home Assistant first.","toast.ha_disabled_add":"A disabled Home Assistant object cannot be added to the plan. Enable it in Home Assistant first.","toast.ha_binding_unverified":"The object status could not be verified through the Home Assistant registry. Display and actions are temporarily unavailable.","toast.markup_needs_server":"Markup is available after the config is moved to the server","toast.conflict":"Config was changed in another window — data refreshed, repeat your last action","toast.cfg_save_failed":"Failed to save config: {err}","toast.room_overlap":"The outline overlaps room “{name}” — rooms must not overlap","toast.contour_min_edges":"Draw at least two edges before closing the outline","toast.contour_cannot_close":"The outline cannot close because it is degenerate or intersects itself","toast.merge_not_adjacent":"Only rooms that share a wall can be merged","toast.rooms_merged":"Rooms merged into “{name}”","toast.split_pick_wall":"Start the cut on the room’s wall","toast.split_bad_cut":"The cut must run wall to wall inside the room, without crossing walls or itself","merge.header":"Merge rooms","merge.hint":"The merged room keeps one name and one area. The other area is released — its devices leave the plan until another room claims it.","merge.keep":"Keep","merge.no_area":"no area","room.split_header":"New room from the split","toast.room_saved":"Room saved ({n}). Devices added: {added}. Outline the next one or exit markup.","toast.room_saved_no_area":"Room saved ({n}, no area). Outline the next one or exit markup.","toast.marker_needs_server":"Device editing is available after the config is moved to the server","toast.virtual_name_required":"Enter a name for the virtual device","toast.marker_saved":"Device saved","toast.marker_removed":"Device deleted from the plan","toast.integration_missing":"The House Plan integration is not installed — management unavailable","toast.plan_formats":"Supported formats: SVG, PNG, JPG, WebP","toast.plan_required":"Upload a floor plan — it is required","toast.space_added_onboard":"Space added. Outline the rooms: click grid dots and close the contour.","toast.space_added":"Space added","toast.space_saved":"Space saved","toast.space_deleted":"Space deleted","toast.delete_failed":"Delete failed: {err}","toast.error":"Error: {err}","toast.file_failed":'File "{name}" was not uploaded: {err}',"toast.files_attached":"Files attached: {n}","err.unknown":"unknown error","err.code":"code {code}","err.too_large":"file larger than {mb} MB","err.bad_ext":"unsupported type (PDF/image expected)","err.unauthorized":"administrator rights required","editor.title":"Title","editor.default_floor":"Default space","editor.icon_size":"Icon size, % of plan width","editor.show_temperature":"Show temperature","editor.live_states":"Live states (on/off, open…)","editor.show_signal":"Show zigbee signal (LQI)","editor.language":"Interface language","editor.lang_auto":"Auto (HA profile)","editor.lang_en":"English","editor.lang_ru":"Русский","title.icon_rules":"Icon rules: which MDI icon devices get by name","rules.title":"Icon rules","rules.hint":"Rules are checked top-down against “device name + model” (case-insensitive regex); the first match wins. When nothing matches, the entity device class decides, then the generic chip icon.","rules.pattern_ph":"regex, e.g. plug|socket","rules.icon_ph":"mdi:power-socket-de","rules.add":"Add rule","rules.reset":"Reset to defaults","rules.test_ph":"Try a device name…","rules.invalid":"invalid regex","rules.saved":"Icon rules saved","btn.up":"Up","btn.down":"Down","tap.info":"Device card","tap.more_info":"HA more-info dialog","tap.toggle":"Toggle state","marker.tap_label":"Tap action for this device","tap.toggle_note":"The resolved state is shown below. Secure devices are never toggled from the plan.","import.title":"Create spaces from HA floors","import.hint":"Your Home Assistant already knows these floors. Pick the ones to turn into plan spaces — you will upload a floor-plan image for each one next. Rooms are then outlined by hand on the plan.","import.start":"Create {n} space(s)","import.manual":"Start from scratch","import.progress":"Floor {i} of {n}","import.done":"Spaces created. Outline the rooms: click grid dots and close the contour.","btn.skip":"Skip","space.scale_label":"Scale (grid cell size)","space.scale_unit":"cm per cell","space.display_section":"Display","space.show_borders":"Always show room borders","space.show_names":"Show room names (drag to move)","space.room_color":"Border & name color","space.opacity":"Opacity","space.fill_label":"Room fill","fill.none":"None","fill.lqi":"Zigbee signal","fill.light":"Lights","fill.custom":"Custom color","space.custom_fill":"Fill color","space.source_file":"I have a floor-plan image","space.source_draw":"No image — I'll outline rooms by hand","space.orientation":"Canvas","orient.landscape":"Landscape","orient.portrait":"Portrait","orient.square":"Square","fill.temp":"Temperature","space.temp_min":"Comfort from","space.temp_max":"to","tip.temp_avg":"average temperature:","space_card.button":"Open the space plan","space_card.not_found":"Space “{id}” not found","space_card.loading":"Loading…","continuity.restore_plan":"Restoring floor plan…","continuity.restore_connection":"Restoring device connection…","continuity.retry":"Try again","editor.space":"Space","editor.show_button":"Show button","editor.button_label":"Button label","editor.button_target":"Target dashboard path","marker.sub_entity":"entity","title.general_settings":"General settings","gs.title":"General settings","gs.hint":"Fill colors apply to every space; each color has its own opacity. Which fill mode a space uses is set in that space's dialog.","gs.light_group":"Fill: lights","gs.light_on":"Lights on","gs.light_off":"All lights off","gs.temp_group":"Fill: temperature","gs.temp_cold":"Cold","gs.temp_ok":"Comfortable","gs.temp_hot":"Hot","gs.lqi_group":"Fill: zigbee signal","gs.lqi_low":"Weak signal","gs.lqi_high":"Strong signal","gs.reset":"Reset to defaults","gs.saved":"General settings saved","space.show_lqi":"Show zigbee signal (LQI) next to devices","space.hide_decor":"Hide the decorative layer","space.hide_decor_tip":"Lines, shapes, labels and furniture stay where they are — visible in the backdrop editor, not on the plan.","space.hide_openings":"Hide openings","space.hide_openings_tip":"Door, window and gate symbols are not drawn, but the openings keep working: light passes through, the sun comes in at a window, contact sensors still open them. The plan editor always shows them.","gs.light_none":"No light sources","mode.plan":"Plan editor","mode.devices":"Device editor","display.value":"Value + state","marker.subarea":"no area, manual","device.new":"New device — open its editor to dismiss","opening.unlock_action":"Unlock","opening.lock_action":"Lock","opening.lock_pending":"Working…","title.close_editor":"Close editor (back to view)","devbar.add":"Add","devbar.show_all":"Hidden and disabled","devbar.rules":"Icon rules","space.roomcard_section":"Room card shows:","space.label_temp":"Temperature","space.label_hum":"Humidity","space.label_lqi":"Average Zigbee signal","space.label_light":"Lights on/off","roomcard.light_on":"On","roomcard.light_off":"Off","roomcard.light_partial":"{on} of {total}","toast.split_pick_inside":"Intermediate cut points must be inside the room","mode.decor":"Background editor","decor.select":"Select","decor.line":"Line","decor.rect":"Rectangle","decor.ellipse":"Oval","decor.text":"Text","decor.erase":"Erase","decor.erase_confirm_title":"Erase object?","decor.color":"Color","decor.width":"Line width","decor.line_style":"Line style","decor.line_style_solid":"Solid","decor.line_style_dashed":"Dashed","decor.fill":"Fill","decor.fill_color":"Fill color","decor.length":"Length","decor.size":"Size","decor.angle":"Rotation angle, °","decor.text_size":"Text size","decor.backdrop_properties":"Plan backdrop properties","decor.text_title":"Text label","decor.object_title":"Edit {kind}","decor.text_label":"Text","decor.live_group":"Insert HA variable","decor.live_entity":"Entity","decor.live_entity_ph":"choose an entity","decor.live_attr":"Value","decor.live_attr_ph":"choose state or attribute","decor.live_state":"State","decor.backdrop":"Backdrop image","decor.backdrop_hint":"Drag to move; pull a corner to resize; use the top handle to rotate. Shift changes proportions or frees the angle.","decor.backdrop_reset":"Reset the picture","decor.backdrop_reset_done":"The picture is back at its original place and size","marker.icon_auto":"Auto: {icon} (by icon rules; pick one to override)","marker.icon_pin_auto":"Pin","mode.plan_tip":"Plan editor — the geometry of the home: draw and split/merge rooms, bind them to HA areas, place doors, windows and gates, move room cards, set the scale","mode.devices_tip":"Device editor — everything about icons: drag to position, click to edit binding/icon/display, add virtual devices, icon rules","mode.decor_tip":"Background editor — purely visual decor under the plan: lines, rectangles, ovals and text labels that never react to clicks","space.glow_enabled":"Light-source glow","gs.glow_group":"Light-source glow","gs.glow_base":"House darkness","gs.glow_light":"Default light color / intensity","gs.wall_group":"Walls","gs.wall_fill":"Wall fill","gs.glow_radius":"Glow radius","gs.unit_m":"m","gs.unit_ft":"ft","marker.controls_label":"Controls other light sources","marker.controls_hint":"With tap action “Toggle”, a click flips every source added here. The marker’s own bound entity is controlled directly and cannot be added; use the switch below to classify it as a light.","marker.controls_filter":"Search lights and switches…","info.controls":"Controls","marker.glow_radius_label":"Glow radius","marker.glow_radius.help":"Sets the glow radius in metres or feet; an empty value uses the radius from general settings.","marker.glow_radius.help.aria":"Help: glow radius","markup.wallthick":"Thickness","markup.select":"Select","title.markup_wallthick":"Thickness — click a wall to set how thick it is. Empty or zero removes the thickness.","markup.hint_wallthick":"click a wall · Esc closes without applying","wallthick.field":"Thickness","wallthick.unit_cm":"cm","wallthick.unit_in":"in","wallthick.apply_room":"Apply to all walls of this room","markup.draw_wall_title":"Thickness of each new room-wall segment (1–100 cm). Shared walls keep the neighbour's value.","toast.wallthick_pick":"Click a wall (not an open boundary)","toast.wallthick_open":"Open boundaries have no thickness","toast.wallthick_set":"Wall thickness set","toast.wallthick_cleared":"Wall thickness removed","toast.physical_range":"Enter a value from 1 to {max} {unit}","toast.physical_angle":"Enter a rotation angle from 0° up to, but not including, 90°","toast.physical_limit":"The space has reached the limit for this type of geometry","markup.boundary_hint":"Select a shared room boundary or a dashed stretch","markup.boundary_hint_open":"Click the start of the open stretch","markup.boundary_hint_second":"Choose the end on the selected boundary · Esc to cancel","markup.boundary_hint_retry":"Choose the end on the selected boundary","markup.boundary_hint_restore":"Click to restore the wall","markup.boundary_hint_outer":"Only a boundary shared by two rooms can be opened","markup.boundary_hint_blocked":"a partition, column or unfinished contour is above this boundary","markup.boundary_hint_ambiguous":"Choose a stretch farther from the junction","markup.boundary_cancel":"Cancel the unfinished Boundary action","toast.openwall_pick":"This tool works with room boundaries","toast.openwall_shared_only":"Only a shared wall between two rooms can be opened","toast.openwall_short":"Stretch too short — pick a farther point","toast.boundary_opened":"Boundary stretch opened","toast.boundary_restored":"Wall restored on this stretch","toast.boundary_same_edge":"Choose the end point on the selected wall","toast.boundary_ambiguous":"Choose a stretch slightly farther from the junction","toast.boundary_blocked":"This boundary is covered by independent masonry. Move or delete it with Select first","toast.delete_room_pick":"Click inside the room you want to delete","toast.openwall_openings_removed":"Openings on the virtual stretch were removed","toast.openwall_opened":"Boundary “{a}” ↔ “{b}” is now open","toast.openwall_closed":"Boundary “{a}” ↔ “{b}” is closed again","toast.opening_on_virtual":"Openings cannot sit on a virtual wall","marker.from_ha_option":"Pick from the HA list","marker.show_entities":"Show entities","marker.show_entities_tip":"Adds not only devices to the list, but all their entities too","marker.pick_ph":"Choose a device…","room.open_area":"Open the HA area","view.volumetric":"Volumetric view","view.flat":"Flat view","kiosk.title":"This screen's sizes","kiosk.hint":"Stored on this device only — every wall tablet or TV can have its own comfortable sizes.","kiosk.icon_scale":"Device icon size","kiosk.font_scale":"Room card text size","editor.kiosk":"Wall device (kiosk) mode","editor.cycle":"Auto-switch spaces every N seconds (kiosk, 0 = off)","room.settings_title":"Room settings","room.settings_section":"Room settings (override the space)","room.fill_label":"Fill in THIS room","fill.inherit":"As the space","room.custom_fill_space":"Space color","room.custom_fill_own":"Room color","room.temp_src_label":"Temperature source","room.hum_src_label":"Humidity source","room.src_average":"Average over the room's sensors (default)","room.src_pick":"A specific HA device or entity","room.src_ph":"Choose a source…","toast.room_updated":"Room updated","space.card_font":"Room-card font size (whole space)","room.sizes_section":"Font sizes","room.name_scale":"Room name size","room.label_scale":"Metrics size","preview.room_name":"Living room","toast.cfg_reload_failed":"Could not reload the plan from the server: {err}","room.settings_short":"Room settings","room.unnamed":"Unnamed room","marker.use_climate_temp":"Include the device temperature in the room","marker.use_climate_temp_tip":"Adds an air conditioner or thermostat current_temperature to the room average. Configure the external value badge separately below.","marker.value_badge_title":"Value badge","marker.value_badge.help":"Shows one selected value next to the icon. It does not affect room metrics, light, Glow or the tap action.","marker.value_badge.help.aria":"Help: device value badge","marker.value_badge_enabled":"Show a value badge","marker.value_badge_source":"Value","marker.value_badge_source.help":"Choose a specific state, supported attribute, or derived value of this device.","marker.value_badge_source.help.aria":"Help: value badge source","marker.value_badge_position":"Position","marker.value_badge_position.help":"The chosen side stays fixed and does not flip automatically at a plan edge.","marker.value_badge_position.help.aria":"Help: value badge position","marker.value_badge_right":"Right","marker.value_badge_bottom":"Below","marker.value_badge_left":"Left","marker.value_badge_top":"Above","marker.value_badge_empty":"This device has no available values","marker.value_badge_static":"A static icon does not show live values. The setting is preserved.","marker.value_badge_missing":"Source unavailable","marker.value_badge_missing_hint":"The saved source is currently unavailable. Keep it, disable the badge, or choose another value.","marker.value_badge_duplicate":"The same value is already shown inside the icon.","marker.value_badge_state":"State · {name}","marker.value_badge_marker_state":"Light-source state · {name}","marker.value_badge_lqi":"Average Zigbee signal quality","marker.value_badge_attr_current_temperature":"Current temperature · {name}","marker.value_badge_attr_temperature":"Target temperature · {name}","marker.value_badge_attr_current_humidity":"Current humidity · {name}","marker.value_badge_attr_humidity":"Humidity · {name}","marker.value_badge_attr_current_position":"Position · {name}","marker.value_badge_attr_percentage":"Speed · {name}","marker.value_badge_attr_brightness":"Brightness · {name}","marker.value_badge_attr_volume_level":"Volume · {name}","marker.value_badge_attr_battery_level":"Battery · {name}","marker.value_badge_attr_fan_speed":"Fan speed · {name}","marker.light_role_label":"Is this device a light source?","marker.light_role.help":"Controls whether this marker itself is a spatial light source. Linked lights above remain independent room-light controls.","marker.light_role.help.aria":"Help: whether this device is a light source","marker.light_role_auto_yes":"Auto (light source)","marker.light_role_auto_no":"Auto (not a light source)","marker.light_role_always":"Always a light source","marker.light_role_never":"Never a light source","marker.light_entity_label":"Leading light entity","marker.light_entity.help":"For a composite device, choose the entity whose state, colour and brightness represent this plan source. The automatic fallback remains compatible with older plans.","marker.light_entity.help.aria":"Help: leading light entity","marker.light_entity_auto":"Automatic ({entity})","marker.light_entity_none":"no controllable entity","marker.light_entity_missing":"The saved entity {entity} is unavailable. House Plan temporarily uses {fallback}; the saved choice will be restored if it returns.","marker.glow_color_label":"Glow colour and brightness","marker.glow_mode.help":"Use live source values, override only its colour, or fix both colour and brightness. The minimum is 1%; to disable the source, choose “Never”.","marker.glow_mode.help.aria":"Help: glow colour and brightness","marker.glow_mode_auto":"From source","marker.glow_mode_color":"Set colour","marker.glow_mode_fixed":"Set colour and brightness","marker.glow_color":"Glow colour","marker.glow_brightness":"Brightness","marker.glow_disabled_never":"Glow settings are unavailable because this marker is explicitly not a light source.","marker.glow_disabled_auto":"Auto mode found no spatial light source for this marker.","marker.glow_disabled_no_entity":"No active controllable Home Assistant entity is available for this spatial source.","marker.glow_passive_hint":"This source has no own Home Assistant data. Set its colour and brightness manually; its radius remains available.","marker.control_broken":"Saved source is missing or no longer marked as a light","marker.control_missing_label":"Missing plan light","marker.control_passive":"passive source","toast.marker_control_cycle":"This link would create a circular chain of light controls.","toast.marker_binding_required":"Choose a Home Assistant device before linking another light source.","confirm.unlock":"Unlock “{name}”?","toast.files_migrate_failed":"Attachments could not be moved to the new binding, links keep pointing at the old files: {err}","space.pick_saved":"Already uploaded","space.pick_saved_hint":"Plans stored on the server, including ones you detached earlier","space.no_saved":"No plans stored on the server yet.","space.loading":"Loading…","space.used_by":"in use: {list}","space.in_use":"A space still uses this plan — detach it first","btn.use":"Use","confirm.delete_plan":'Delete the plan file "{name}" from the server? This cannot be undone.',"toast.plans_list_failed":"Could not list the stored plans: {err}","toast.plan_delete_failed":"Could not delete the plan: {err}","marker.hide":"Hide","marker.hide_tip":'The device will disappear from the plan after saving but will still count toward the room signal. Restore it through "Hidden and disabled" in the device editor.',"marker.show":"Show","marker.show_tip":"The device will appear on the plan again after saving.","marker.hidden_ghost":"Device hidden by the user","marker.ha_disabled_device":"The device is disabled in Home Assistant and hidden from the plan.","marker.ha_disabled_entity":"The entity is disabled in Home Assistant and hidden from the plan.","marker.ha_disabled_all_entities":"The device has no active Home Assistant entities, so it is hidden from the plan.","marker.ha_registry_limited":"The full Home Assistant registry is unavailable to this user. The unverified object cannot be shown or used for now.","marker.delete_tip":"Completely delete the device from the plan and every aggregate. You can add it again later.","tap.run":"Run automation/script/scene","tap.cover":"Open/close (curtains/blinds)","marker.run_target_label":"What to run","marker.run_search_ph":"Search: automation, script or scene…","marker.run_target_gone":"Target {id} not found — pick again","marker.tap_confirm":"Ask for confirmation","marker.tap_confirm_tip":"Show a confirmation dialog before acting — a guard against accidental taps.","marker.toggle_hint_single":"Target: {name} ({id}).","marker.virtual_light_target":"Virtual light: {name}.","marker.virtual_light_current":"Manual state: {state} → {effect}.","marker.virtual_light_state_on":"on","marker.virtual_light_state_off":"off","marker.toggle_hint_group":"Will toggle {count} source(s): {names}.","marker.toggle_hint_current":"Now: {state} → {effect}.","marker.toggle_hint_group_current":"Currently on: {on} of {count} → {effect}.","marker.toggle_hint_skipped":"Will be skipped ({count}): {targets}.","marker.toggle_effect_turn_on":"will turn on","marker.toggle_effect_turn_off":"will turn off","marker.toggle_effect_open":"will open","marker.toggle_effect_close":"will close","marker.toggle_effect_stop":"will stop","marker.toggle_effect_toggle":"state will toggle","marker.toggle_skip_missing":"not found","marker.toggle_skip_ha_disabled":"disabled in HA","marker.toggle_skip_unavailable":"unavailable","marker.toggle_skip_unsupported":"toggle is unsupported","marker.toggle_skip_secure":"blocked for security","marker.toggle_none_no_binding":"This device has no binding, so there is no state to toggle. Tapping will do nothing.","marker.toggle_none_no_actionable_entity":"This device has no state that can be toggled. Tapping will do nothing.","marker.toggle_none_configured_targets_missing":"The configured targets are unavailable. The device's own entity will not be substituted for them.","marker.toggle_none_ha_disabled":"The target is disabled in Home Assistant and cannot be used on the plan.","marker.toggle_none_unavailable":"The target is currently unavailable. Tapping will do nothing.","marker.toggle_none_unsupported":"Home Assistant exposes no safe toggle operation for this entity. Tapping will do nothing.","marker.toggle_none_secure":"Toggling locks, alarms and secure gates from the plan is blocked for security.","run.automation":"automation","run.script":"script","run.scene":"scene","confirm.tap_run":'Run "{name}"?',"confirm.tap_toggle":'Toggle "{name}"?',"confirm.tap_cover":'Open/close "{name}"?',"toast.run_started":"Started: {name}","toast.run_target_missing":"Run target not found — check the device settings","toast.run_target_required":"Pick an automation, script or scene","toast.tap_target_changed":"The action target changed. Try again.","toast.virtual_light_toggle_failed":"Could not toggle the virtual light: {err}","toast.value_badge_source_required":"Choose a value for the badge","btn.run":"Run","vac.section":"Robot vacuum: live position","vac.autocal":"Set up automatically","vac.live":"Live position on the plan","vac.trail":"Show the robot's path","vac.cal_maps":"Calibrated maps: {maps}","vac.autocal_no_rooms":"The integration reports no room list — open “Fit manually”","vac.autocal_no_match":"Room names did not match (need ≥3 in common) — open “Fit manually”","vac.autocal_done":"Done: bound via {rooms} rooms. Start a cleanup and check","vac.cal_need_pos":"The robot is not reporting coordinates — start a cleanup and pause it","vac.cal_done":"Calibration saved. Start a cleanup and check","vac.cal_cancelled":"Calibration cancelled","vac.fit":"Fit manually","vac.fit_hint":"Drag the robot map into place, stretch by the corners","vac.fit_rotate":"Rotate 90°","vac.fit_mirror":"Mirror","vac.trail_never":"Never","vac.trail_cleaning":"While cleaning","vac.trail_always":"Always","gs.bg_group":"Stage background","gs.bg_color":"Background around the plan","gs.bg_default":"Theme default","gs.bg_theme":"theme default","gs.bg_mode":"Plan background","gs.bg_static":"Static color","gs.bg_daynight":"Follows the Sun","gs.bg_daynight_hint":"The background moves through dawn, day, dusk and night using sun.sun, or your local clock when sun data is unavailable. No compass is needed for the background.","gs.sun_group":"Sun","gs.sun_missing":"The sun.sun entity was not found — the background follows your local clock; window rays are unavailable.","gs.north":"North on the plan","gs.north_ph":"not set","gs.north_hint":"Set north only for window rays (1° steps, 15° with Shift); the Sun background works without it.","gs.north_clear":"Clear","gs.north_letter":"N","gs.sun_rays":"Sunlight through windows","gs.about_group":"About","gs.about_version":"Houseplan Card v{v}","gs.about_github":"GitHub · docs & issues","gs.about_telegram":"Telegram chat","space.bg_color":"Background around the plan","space.bg_inherit":"Inherit general","space.bg_inherited":"inherits general settings","space.bg_mode":"Plan background","space.north":"North on the plan (override)","space.north_inherited":"inherited: {v}","space.sun_rays":"Sunlight through windows","space.sun_inherit":"Inherit general","space.sun_on":"On","space.sun_off":"Off","canvas.far_objects":"{n} object(s) far from the plan","canvas.show_far":"Show","canvas.home_tip":"The plan is over there — click to fit it","gs.grid_group":"Plan maintenance","gs.grid_hint":"Updates data models, aligns plan elements to the grid and merges redundant wall fragments. An exact report is shown before anything is stored.","gs.align_all":"Optimize plans","gs.align_title":"Optimize plans","gs.align_none":"All plans already use the current optimized data model.","gs.align_count":"{n} of {total} elements will move, by at most {cm} cm.","gs.align_where":"The largest shift is in “{s}”.","gs.align_turned":"Openings whose angle is corrected: {n}.","gs.align_removed_drafts":"Invalid outlines collapsed by the grid and removed: {n}.","gs.optimize_changes":"Model migrations: {m}; plans canonicalized: {c}; merged real-wall fragments: {w}; virtual fragments: {s}.","gs.optimize_glow_migration":"Legacy Glow: {spaces} spaces → no data fill + independent Glow; {rooms} rooms → inherited data fill + independent Glow.","gs.align_warn":"Elements deliberately placed between grid nodes will move. One undo is available after the operation, only until the next plan edit.","gs.align_run":"Optimize","gs.align_done":"Plans optimized: {n} elements moved, {m} records maintained","gs.optimize_undo":"Undo last optimization","gs.optimize_undone":"The last optimization was undone","decor.furniture":"Furniture","furn.title":"Furniture library","furn.symbol":"Symbol","furn.group_furniture":"Furniture","furn.group_appliance":"Appliances","furn.group_sanitary":"Plumbing","furn.group_other":"Other","furn.width":"Width","furn.depth":"Depth","furn.pick_hint":"Pick a symbol, then click on the plan.","furn.place_hint":"Click on the plan — the piece lands against the nearest wall. Shift places it free.","furn.sym_sofa":"Sofa","furn.sym_armchair":"Armchair","furn.sym_coffee_table":"Coffee table","furn.sym_table_dining":"Dining table","furn.sym_table_round":"Round table","furn.sym_chair":"Chair","furn.sym_desk":"Desk","furn.sym_bed_double":"Double bed","furn.sym_bed_single":"Single bed","furn.sym_nightstand":"Nightstand","furn.sym_wardrobe":"Wardrobe","furn.sym_bookshelf":"Bookshelf","furn.sym_fridge":"Fridge","furn.sym_stove":"Cooker","furn.sym_dishwasher":"Dishwasher","furn.sym_washer":"Washing machine","furn.sym_dryer":"Tumble dryer","furn.sym_tv":"TV","furn.sym_ac":"Air conditioner","furn.sym_water_heater":"Water heater","furn.sym_toilet":"Toilet","furn.sym_bathtub":"Bathtub","furn.sym_shower":"Shower","furn.sym_sink":"Washbasin","furn.sym_kitchen_sink":"Kitchen sink","furn.sym_bidet":"Bidet","furn.sym_stairs":"Stairs","furn.sym_fireplace":"Fireplace","furn.sym_plant":"Plant","furn.sym_rug":"Rug","common.yes":"Yes","common.no":"No","vac.diag_source":"Source","vac.diag_platform":"Integration","vac.diag_status":"Status","vac.diag_position":"Position","vac.diag_rooms":"Rooms","vac.diag_rooms_value":"{total} · {matched} names match · {readiness}","vac.autocal_ready":"auto-calibration available","vac.autocal_not_ready":"need 3 matching names","vac.diag_path":"Integration path","vac.diag_map":"Map ID","vac.source_none":"not selected","vac.source_status_ok":"Ready","vac.source_status_missing":"Missing","vac.source_status_disabled":"Disabled in Home Assistant","vac.source_status_unavailable":"Unavailable","vac.source_status_unverified":"Cannot verify with current permissions","vac.source_status_unsupported":"No position data","vac.source_status_none":"No source","vac.source_banner_missing":"The saved source no longer exists. It was not replaced automatically; choose another source or restore it in Home Assistant.","vac.source_banner_disabled":"The saved source is disabled in Home Assistant. Enable it there or choose another source.","vac.source_banner_unverified":"Current Home Assistant permissions cannot verify this saved source. It remains pinned and will not be replaced automatically.","vac.choose_source":"Choose source","vac.source_auto":"Automatic","vac.source_auto_hint":"Use a compatible entity from this device","vac.all_cameras":"All cameras","vac.all_cameras_warn":"A camera may not provide robot data. Choose one only if it is your vacuum map.","vac.all_cameras_empty":"No other camera entities found.","vac.platform_unknown":"unknown integration","vac.cap_position":"position","vac.cap_rooms_short":"rooms","vac.cap_path":"path","vac.cap_map":"map ID","vac.cap_none":"no robot data detected","vac.xcme_hint":"Enable these Xiaomi Cloud Map Extractor attributes:","vac.documentation":"Documentation","vac.residual_title":"Check automatic calibration","vac.residual_message":"The matched rooms disagree by up to {error}. Apply this approximate calibration, refine it manually, or cancel without changing the saved setup.","vac.apply_proposal":"Apply","gs.backup_group":"Backup and transfer","gs.backup_hint":"Download a portable JSON backup or preview and import a backup made by House Plan.","backup.export_open":"Export","backup.import_open":"Import","backup.export_title":"Export House Plan","backup.import_title":"Import House Plan","backup.export_hint":"Choose whether the backup should contain the whole House Plan configuration or only the current space.","backup.full":"Full backup","backup.current_space":"Current space","backup.current_space_title":"Current space: {title}","backup.no_current_space":"No current space","backup.plan_only":"Plan only","backup.plan_only_hint":"Keep rooms, walls, openings, decor and room-label positions without devices or Home Assistant bindings.","backup.plan_only_preview":"This file contains the plan only","backup.privacy_warning":"The archive keeps names, Home Assistant identifiers and exact coordinates. Internal plans and attachments are referenced, not embedded; runtime states and vacuum trails are not included.","backup.download":"Download JSON","backup.export_done":"Backup downloaded","backup.reading":"Checking the backup…","backup.revalidated":"The plan changed after this preview. The summary was refreshed; review it and confirm again.","backup.error.unauthorized":"You do not have permission to export or import this plan.","backup.error.not_ready":"House Plan is not ready yet. Try again in a moment.","backup.error.too_large":"The backup exceeds the 8 MiB limit.","backup.error.invalid_json":"The selected file is not valid JSON.","backup.error.invalid_format":"The selected file is not a House Plan backup.","backup.error.unsupported_export_version":"This backup format is not supported by the installed version.","backup.error.future_model":"The backup was created by a newer House Plan data model.","backup.error.invalid_config":"The backup contains an invalid House Plan configuration.","backup.error.invalid_layout":"The backup contains invalid object positions.","backup.error.invalid_content":"The backup contains invalid or inconsistent content references.","backup.error.space_not_found":"The selected space no longer exists.","backup.error.capacity_exceeded":"Adding this backup would exceed the plan limits.","backup.error.preview_expired":"The preview expired. Select the backup file again.","backup.error.preview_owner_mismatch":"This preview belongs to another Home Assistant user.","backup.error.conflict":"The plan changed after the preview. Review the refreshed summary.","backup.error.content_confirmation_required":"Confirm that unavailable local content may be detached.","backup.error.commit_failed":"The backup could not be applied safely. The previous plan was restored or is pending recovery.","backup.error.missing_plan":"A referenced plan file disappeared after the preview. Review the backup again.","backup.error.missing_content":"A referenced local attachment disappeared after the preview. Review the backup again.","backup.error.marker_control_missing":"A linked plan light is missing from the backup.","backup.error.marker_control_not_light":"A linked plan target is no longer marked as a light source.","backup.error.marker_control_self":"A light source cannot control itself.","backup.error.marker_control_cycle":"The backup contains a circular chain of light controls.","backup.error.duplicate_marker_control":"The backup contains a duplicate plan-light link.","backup.error.no_backup":"There is no import or optimization snapshot to restore.","backup.same_source":"Created on this Home Assistant instance","backup.foreign_source":"Created on another Home Assistant instance","backup.created":"Created: {value}","backup.versions":"Card {card}; integration {integration}; data model {model}","backup.count_spaces":"Spaces: {n}","backup.count_rooms":"Rooms: {n}","backup.count_walls":"Walls: {n}","backup.count_openings":"Openings: {n}","backup.count_decor":"Decor objects: {n}","backup.count_markers":"Devices: {n}","backup.count_layout":"Positions: {n}","backup.bindings":"Bindings — devices: {device}, entities: {entity}, virtual: {virtual}; positions without a space: {legacy}","backup.binding_status":"Target status — active: {active}, disabled: {disabled}, missing: {missing}","backup.missing_areas":"Areas missing on the target: {areas}","backup.dropped_marker_links":"Plan-light links outside this transferred space were omitted: {n}.","backup.replace_warning":"This replaces the current configuration and layout. Uploaded files are never deleted. One undo remains available until the next plan edit.","backup.foreign_bookkeeping":"Instance-specific known/new-device bookkeeping will not be imported.","backup.final_name":"New space name","backup.target_settings":"The target instance's global settings remain unchanged.","backup.duplicates":"Bindings already present: {n}","backup.skip":"Skip duplicate devices","backup.virtual_copy":"Add safe static virtual copies","backup.content":"Referenced content","backup.content_available":"available locally","backup.content_external":"external link","backup.content_detach_required":"will be detached","backup.confirm_detach":"I understand that unavailable internal plans and attachments will be detached from the imported configuration.","backup.replace":"Replace","backup.add":"Add space","backup.space_done":"Space imported: {rooms} rooms, {markers} devices","backup.full_done":"Backup restored: {spaces} spaces, {rooms} rooms, {markers} devices","backup.undo_import":"Undo last full import","backup.import_undone":"Full import undone"};const Wo={en:jo,ru:{"editor.context_actions":"Действия: {object}","editor.tool_options":"Параметры инструмента: {tool}","editor.palette":"Палитра: {tool}","editor.open_group":"Группа инструментов: {group}","editor.group_active":"{group} — активно: {item}","editor.disabled_action":"{action} недоступно: {reason}","btn.properties":"Свойства","btn.keep_as_walls":"Оставить замкнутыми стенами","title.markup_partition":"Перегородка: нарисуйте одну независимую физическую стену","title.markup_select":"Выбор и редактирование стен, колонн и сохранённых контуров","title.markup_column":"Колонна: кликните точку сетки для квадратной колонны","markup.partition":"Перегородка","markup.column":"Колонна","markup.hint_partition":"два клика рисуют одну перегородку · Shift фиксирует шаг 45°","markup.hint_column":"кликните точку сетки, чтобы поставить квадратную колонну","history.draft_segment":"Добавление сегмента черновика комнаты","history.draft_merge":"Соединение незавершённых контуров комнаты","history.draft_segment_delete":"Удаление сегмента черновика комнаты","history.partition_add":"Добавление перегородки","history.column_add":"Добавление колонны","history.physical_edit":"Изменение физического объекта","history.physical_delete":"Удаление физического объекта","history.physical_move":"Перемещение физического объекта","history.contour_to_partitions":"Преобразование контура в замкнутые стены","toast.column_duplicate":"Колонна с тем же центром и внешним размером уже существует","confirm.delete_draft":"Удалить весь незавершённый контур комнаты?","confirm.delete_draft_segment":"Удалить этот сегмент черновика? Оставшийся контур может разделиться на два.","physical.partition_properties":"Свойства перегородки","physical.column_properties":"Свойства колонны","physical.draft_properties":"Свойства сегмента черновика","physical.shape":"Форма","physical.square":"Квадрат","physical.circle":"Круг","physical.diameter":"Диаметр","physical.side":"Сторона","physical.rotation":"Угол поворота","physical.length":"Длина","physical.allowed_range":"Допустимо: 1–{max} {unit}","physical.delete_segment":"Удалить сегмент","physical.delete_draft":"Удалить весь контур","physical.partition_size_title":"Толщина перегородки, создаваемой вторым кликом (1–100 см).","physical.column_size_title":"Внешняя сторона квадратной колонны, создаваемой кликом (1–150 см).","card.title":"План дома","count.devices":"{n} устр.","empty.no_spaces":"Пространств пока нет.","empty.add_first":"Добавьте первое пространство и загрузите план этажа.","empty.install":"Установите интеграцию House Plan и добавьте запись в «Устройства и службы».","btn.add_space":"Добавить пространство","btn.cancel":"Отмена","btn.save":"Сохранить","btn.close":"Закрыть","btn.delete":"Удалить","btn.edit":"Редактировать","btn.open_in_ha":"Открыть в HA","btn.reset":"Сброс","btn.attach":"Прикрепить…","btn.upload":"Загрузить…","btn.replace":"Заменить…","title.zoom_in":"Приблизить","title.zoom_out":"Отдалить","title.zoom_fit":"Вписать всё","title.add_device":"Добавить устройство на план","title.show_all":"Показать скрытые и деактивированные устройства служебными маркерами (только в этой вкладке)","title.markup":"Разметка комнат: сетка, линии, контуры","title.configure_space":"Настроить пространство","title.add_space":"Добавить пространство","title.markup_add":"Контур комнаты: соединяйте точки сетки линиями до замыкания комнаты; Shift фиксирует направление с шагом 45°","title.markup_merge":"Объединить: клик по одной комнате, затем по соседней с общей стеной","title.markup_split":"Разделить комнату: клик по комнате, затем две точки на её стенах","title.markup_delroom":"Удалить комнату: кликните внутри комнаты и подтвердите удаление","title.markup_boundary":"Граница: две точки на сплошной общей стене делают участок виртуальным; клик по пунктиру восстанавливает стену","title.need_plan":"Загрузите подложку (план этажа)","markup.add":"Контур комнаты","markup.merge":"Объединить","markup.split":"Разделить","markup.resize":"Размер","title.markup_resize":"Изменение размера комнат: тяните ручку стены; клик по комнате — угловая рамка масштаба","markup.hint_resize":"тяните ручку стены · клик по комнате — угловая рамка · Esc отменяет перетаскивание · Ctrl+Z — отмена шага","markup.opening":"Проём","markup.boundary":"Граница","markup.delete_room":"Удалить комнату","history.undo":"Отменить","history.redo":"Повторить","history.undo_named":"Отменить: {name} (Ctrl+Z)","history.redo_named":"Повторить: {name} (Ctrl+Shift+Z / Ctrl+Y)","history.undo_empty":"Нет операций для отмены","history.redo_empty":"Нет операций для повтора","history.undone":"Отменено: {name}","history.redone":"Повторено: {name}","history.add_room":"Создание комнаты","history.split_room":"Разделение комнаты","history.merge_rooms":"Объединение комнат","history.resize_room":"Изменение размера комнаты","history.open_boundary":"Открытие границы","history.close_boundary":"Восстановление стены","history.wall_thickness":"Изменение толщины стены","history.add_opening":"Добавление дверного или оконного проёма","history.edit_opening":"Изменение проёма","history.move_opening":"Перемещение проёма","history.delete_opening":"Удаление проёма","history.delete_room":"Удаление комнаты","history.decor_add":"Добавление объекта декора","history.decor_edit":"Изменение объекта декора","history.decor_move":"Перемещение объекта декора","history.decor_transform":"Трансформация объекта декора","history.decor_delete":"Удаление объекта декора","history.backdrop_transform":"Трансформация подложки плана","title.markup_opening":"Проёмы: клик по стене — добавить, клик по проёму — редактировать","opening.new":"Новый проём","opening.edit":"Проём","opening.door":"Дверь","opening.window":"Окно","opening.gate":"Ворота","opening.type_label":"Тип","opening.length_label":"Длина, см","opening.contact_label":"Датчик открытия","opening.lock_label":"Замок","opening.none":"— нет —","opening.invert":"Инвертировать открыто/закрыто","opening.flip_h":"Петли с другой стороны","opening.flip_v":"Открывается в другую сторону","opening.open":"Открыто","opening.closed":"Закрыто","opening.locked":"Заперто","opening.unlocked":"Не заперто","opening.state_unknown":"недоступно","opening.no_entities":"Датчики не привязаны — статичный символ на плане.","toast.opening_no_wall":"Кликните рядом со стеной комнаты — проёмы ставятся на стены","markup.delete":"Удалить","markup.hint_points":"точек: {n} · Shift — шаг 45° · Esc/Ctrl+Z — убрать точку · клик по первой или Ctrl/Cmd+клик — замкнуть","markup.hint_start":"кликните точку сетки, чтобы начать контур","tip.lqi":"средний сигнал zigbee:","tip.area":"площадь: {value}","info.device_header":"Устройство на плане","info.model":"Модель","info.state":"Состояние","info.link":"Ссылка","info.manuals":"Инструкции","info.none":"Нет дополнительной информации","marker.new_device":"Новое устройство","marker.name_label":"Имя (отображается на плане)","marker.name_ph":"Название","marker.binding_label":"Привязка к устройству HA","marker.binding_disabled":"деактивировано в Home Assistant","marker.virtual_option":"Виртуальное устройство (без привязки)","marker.search_ph":"Поиск устройства / группы…","marker.nothing_found":"ничего не найдено","marker.room_label":"Комната","marker.room_override":" (переопределить размещение)","marker.room_choose":"— выберите комнату —","marker.room_auto":"— по зоне устройства (авто) —","marker.icon_label":"Иконка","marker.icon_ph":"mdi:… (пусто = авто)","marker.display_label":"Отображение","display.badge":"Значок + состояние","display.icon_ripple":"Значок + состояние и активность","display.static_icon":"Всегда статичный значок","marker.display_hint":"Значок + состояние меняет подложку при работе, открытии, тревоге и недоступности. Значок + состояние и активность дополнительно показывает короткую пульсацию для событий и постоянную — для работы, движения или присутствия. Значение + состояние заменяет значок одним однозначным значением HA. Статичный значок не реагирует на состояния устройства.","marker.display_hint_badge":"Значок и динамическая подложка показывают состояние устройства без обычной пульсации активности. Красная тревога сохраняется.","marker.display_hint_icon":"Значок и динамическая подложка показывают состояние устройства без обычной пульсации активности. Красная тревога сохраняется.","marker.display_hint_icon_ripple":"Значок, динамическая подложка и пульсация: короткая — для событий, постоянная — для работы, движения или присутствия. Красная тревога имеет отдельный приоритет.","marker.display_hint_value":"Значок заменяется одним однозначным значением Home Assistant; подложка продолжает показывать состояние. Красная тревога сохраняется.","marker.display_hint_static_icon":"Всегда одинаковые чёрная подложка и значок. Состояния, активность, недоступность и тревоги не меняют отображение.","marker.static_alarm_warning":"Это устройство может сообщать о тревогах. Статичный режим скрывает визуальную тревогу маркера.","marker.preview.title":"Предпросмотр отображения","marker.preview.actual":"Сейчас","marker.preview.example":"Пример","marker.preview.integration":"Интеграция","marker.preview.source":"Источник отображения","marker.preview.current_state":"Текущее состояние","marker.preview.result":"На плане","marker.preview.details":"Подробнее об источниках","marker.preview.select_source":"Выберите устройство или сущность Home Assistant, чтобы увидеть фактическое отображение.","marker.preview.unknown_provider":"Интеграция неизвестна","marker.preview.virtual_provider":"House Plan · виртуальное устройство","marker.preview.no_source":"Нет активного источника","marker.preview.no_state":"Нет текущего состояния","marker.preview.mixed_states":"Несколько разных состояний","marker.preview.multiple_sources":"Источников: {n}","marker.preview.more_sources":"ещё {n}","marker.preview.scaled":"Предпросмотр уменьшен до {n}%, чтобы поместиться. Сохранённый размер не изменится.","marker.preview.demo_activity":"Показать пример активности","marker.preview.demo_notice":"Временный пример активности; фактическое состояние устройства не меняется.","marker.preview.demo_short":"Показать краткую активность","marker.preview.demo_continuous":"Показать постоянную активность","marker.preview.stop_continuous":"Остановить постоянную активность","marker.preview.demo_short_notice":"Пример краткой активности; фактическое состояние устройства не меняется.","marker.preview.demo_continuous_notice":"Пример постоянной активности; фактическое состояние устройства не меняется.","marker.preview.demo_already_visible":"Текущее состояние уже показывает реальную активность или тревогу.","marker.preview.reduced_motion":"В системе включено уменьшение движения, поэтому обычная активность показана точкой.","marker.preview.reason.neutral":"Нейтральная тёмная подложка","marker.preview.reason.working":"Жёлтая подложка: устройство сейчас работает","marker.preview.reason.working_activity":"Жёлтая подложка и эффект активности: устройство сейчас работает","marker.preview.reason.open":"Оранжевая подложка: физически открыто или разблокировано","marker.preview.reason.cover_icon_state":"Нейтральная подложка; состояние и значок определяет сущность шторы или заслонки","marker.preview.reason.presence":"Эффект активности, пока обнаружено присутствие","marker.preview.reason.event":"Короткий эффект активности после события","marker.preview.reason.transition":"Эффект активности во время движения или смены состояния","marker.preview.reason.media_neutral":"Медиаустройство остаётся на нейтральной тёмной подложке, пока доступно","marker.preview.reason.unavailable":"Приглушённая нейтральная подложка: отключено или недоступно","marker.preview.reason.alarm":"Красная тревожная подложка; тревога отображается всегда","marker.preview.reason.live_states_disabled":"Оформление по живым состояниям отключено в настройках карточки","marker.preview.reason.value_no_state":"Нет подходящего значения состояния — вместо него показан значок","marker.preview.reason.value_ambiguous_sources":"Подходит несколько источников значения — вместо него показан значок","marker.preview.reason.value_non_scalar":"Источник вернул не простое значение — вместо него показан значок","marker.preview.reason.value_virtual":"У виртуального устройства нет значения HA — вместо него показан значок","marker.preview.reason.vacuum_live_plan_only":"Живая позиция и след пылесоса доступны только на полном плане","marker.preview.reason.hidden_design_preview":"Устройство скрыто на плане, но его оформление видно в предпросмотре","marker.preview.reason.composite_power_source":"Состояние берётся из сущности питания устройства; вспомогательные переключатели не учитываются","marker.preview.reason.activity_display_disabled":"Выбранный режим отображения не показывает обычные эффекты активности","marker.preview.reason.ha_disabled":"Привязка отключена в Home Assistant, поэтому устройство будет скрыто на плане","marker.preview.reason.orphaned":"Сохранённая привязка Home Assistant больше не найдена","marker.preview.reason.static_icon":"Статичный режим: состояние устройства не меняет значок","marker.activity_color":"Цвет пульсации активности","marker.ripple_size":"Размер пульсации активности","marker.activity_alarm_note":"Цвет и размер относятся только к обычной активности и не влияют на красную тревогу.","marker.pulse_a11y_alarm":"Тревога","marker.pulse_a11y_event":"Недавнее событие","marker.pulse_a11y_presence":"Обнаружено присутствие","marker.pulse_a11y_transition":"Изменение состояния","marker.pulse_a11y_running":"Работает","marker.size_label":"Размер / поворот значка","marker.angle_label":"Поворот","marker.model_label":"Модель","marker.model_ph":"напр. Aqara T&H","marker.link_label":"Ссылка","marker.desc_label":"Описание","marker.desc_ph":"Заметки, характеристики…","marker.manuals_label":"Инструкции (PDF и т.п.)","marker.sub_device":"устройство","marker.sub_z2m_group":" · Z2M-группа","marker.sub_group":"группа","marker.sub_helper":"хелпер","space.new":"Новое пространство","space.header":"Пространство","space.title_label":"Название","space.title_ph":"Например: Гараж","space.plan_label":"Подложка (план)","space.no_plan":"нет подложки","space.plan_alt":"план","room.new":"Новая комната","room.name_label":"Отображаемое имя","room.name_ph":"Например: Терраса","room.area_label":"Зона Home Assistant (свободные)","room.no_area_option":"— без зоны —","room.default_name":"Комната","device.unnamed":"без имени","device.light_group":"группа света","device.fallback":"устройство","device.virtual":"виртуальное устройство","confirm.delete_room":"Удалить комнату «{name}»?","confirm.merge_rooms":"Слить комнаты «{a}» и «{b}»?","confirm.remove_marker":"Удалить «{name}» с плана? Устройство исчезнет полностью и перестанет участвовать в данных плана. Позже его можно будет добавить заново.","confirm.erase_decor":"Стереть объект «{kind}»? Действие можно отменить из истории редактора.","confirm.delete_space":"Удалить пространство «{title}» со всеми комнатами и разметкой?","toast.pos_save_failed":"Не удалось сохранить позицию: {err}","toast.no_entity":"У устройства нет подходящей сущности","toast.ha_disabled_action":"Деактивированный объект Home Assistant нельзя использовать на плане.","toast.ha_disabled_show_device":"Деактивированное в Home Assistant устройство нельзя показать на плане. Сначала активируйте его в Home Assistant.","toast.ha_disabled_show_entity":"Деактивированную в Home Assistant сущность нельзя показать на плане. Сначала активируйте её в Home Assistant.","toast.ha_disabled_add":"Деактивированный объект Home Assistant нельзя добавить на план. Сначала активируйте его в Home Assistant.","toast.ha_binding_unverified":"Статус объекта не удалось подтвердить по реестру Home Assistant. Отображение и действия временно недоступны.","toast.markup_needs_server":"Разметка доступна после переноса конфига на сервер","toast.conflict":"Конфиг изменён в другом окне — данные обновлены, повторите последнее действие","toast.cfg_save_failed":"Не удалось сохранить конфиг: {err}","toast.room_overlap":"Контур накладывается на комнату «{name}» — комнаты не должны накладываться","toast.contour_min_edges":"Чтобы замкнуть контур, сначала нарисуйте минимум две грани","toast.contour_cannot_close":"Контур нельзя замкнуть: он вырожден или пересекает сам себя","toast.merge_not_adjacent":"Объединять можно только комнаты с общей стеной","toast.rooms_merged":"Комнаты объединены в «{name}»","toast.split_pick_wall":"Начните разрез на стене комнаты","toast.split_bad_cut":"Разрез — от стены до стены внутри комнаты, без пересечения стен и самого себя","merge.header":"Объединение комнат","merge.hint":"У объединённой комнаты одно имя и одна зона. Вторая зона освобождается — её устройства уйдут с плана, пока их не заберёт другая комната.","merge.keep":"Оставить","merge.no_area":"без зоны","room.split_header":"Новая комната после разделения","toast.room_saved":"Комната сохранена ({n}). Устройств добавлено: {added}. Обведите следующую или выйдите из разметки.","toast.room_saved_no_area":"Комната сохранена ({n}, без зоны). Обведите следующую или выйдите из разметки.","toast.marker_needs_server":"Редактирование устройств доступно после переноса конфига на сервер","toast.virtual_name_required":"Укажите имя виртуального устройства","toast.marker_saved":"Устройство сохранено","toast.marker_removed":"Устройство удалено с плана","toast.integration_missing":"Интеграция House Plan не установлена — управление недоступно","toast.plan_formats":"Поддерживаются SVG, PNG, JPG, WebP","toast.plan_required":"Загрузите подложку — план этажа обязателен","toast.space_added_onboard":"Пространство добавлено. Обведите комнаты: кликайте по точкам сетки и замкните контур.","toast.space_added":"Пространство добавлено","toast.space_saved":"Пространство сохранено","toast.space_deleted":"Пространство удалено","toast.delete_failed":"Ошибка удаления: {err}","toast.error":"Ошибка: {err}","toast.file_failed":"Файл «{name}» не загружен: {err}","toast.files_attached":"Прикреплено файлов: {n}","err.unknown":"неизвестная ошибка","err.code":"код {code}","err.too_large":"файл больше {mb} МБ","err.bad_ext":"недопустимый тип (нужен PDF/изображение)","err.unauthorized":"нужны права администратора","editor.title":"Заголовок","editor.default_floor":"Пространство по умолчанию","editor.icon_size":"Размер иконок, % ширины плана","editor.show_temperature":"Показывать температуру","editor.live_states":"Живые состояния (вкл/выкл, открыто…)","editor.show_signal":"Показывать сигнал zigbee (LQI)","editor.language":"Язык интерфейса","editor.lang_auto":"Авто (профиль HA)","editor.lang_en":"English","editor.lang_ru":"Русский","title.icon_rules":"Правила иконок: какая MDI-иконка достаётся устройству по имени","rules.title":"Правила иконок","rules.hint":"Правила проверяются сверху вниз по строке «имя устройства + модель» (regex без учёта регистра); срабатывает первое совпадение. Если ничего не подошло — решает device class сущности, затем — иконка-заглушка.","rules.pattern_ph":"regex, напр. розетк|plug","rules.icon_ph":"mdi:power-socket-de","rules.add":"Добавить правило","rules.reset":"Сбросить к умолчаниям","rules.test_ph":"Проверьте имя устройства…","rules.invalid":"некорректный regex","rules.saved":"Правила иконок сохранены","btn.up":"Вверх","btn.down":"Вниз","tap.info":"Карточка устройства","tap.more_info":"Диалог HA (more-info)","tap.toggle":"Переключить состояние","marker.tap_label":"Действие по нажатию для этого устройства","tap.toggle_note":"Под селектором показано, какое состояние изменится. Защищённые устройства не переключаются с плана.","import.title":"Создать пространства из этажей HA","import.hint":"Home Assistant уже знает эти этажи. Отметьте, какие превратить в пространства плана — далее для каждого попросим картинку плана. Комнаты затем обводятся вручную по плану.","import.start":"Создать: {n}","import.manual":"Начать с нуля","import.progress":"Этаж {i} из {n}","import.done":"Пространства созданы. Обведите комнаты: кликайте по точкам сетки и замкните контур.","btn.skip":"Пропустить","space.scale_label":"Масштаб (размер клетки сетки)","space.scale_unit":"см на клетку","space.display_section":"Отображение","space.show_borders":"Всегда отображать границы комнат","space.show_names":"Отображать названия комнат (перетаскиваются)","space.room_color":"Цвет границ и названий","space.opacity":"Прозрачность","space.fill_label":"Заливка комнат","fill.none":"Нет","fill.lqi":"По силе зигби-сигнала","fill.light":"По освещению","fill.custom":"Свой цвет","space.custom_fill":"Цвет заливки","space.source_file":"У меня есть картинка плана","space.source_draw":"Нет подложки — нарисую комнаты вручную","space.orientation":"Холст","orient.landscape":"Альбомный","orient.portrait":"Портретный","orient.square":"Квадрат","fill.temp":"По температуре","space.temp_min":"Комфорт от","space.temp_max":"до","tip.temp_avg":"средняя температура:","space_card.button":"Перейти к пространству","space_card.not_found":"Пространство «{id}» не найдено","space_card.loading":"Загрузка…","continuity.restore_plan":"Восстанавливаем план…","continuity.restore_connection":"Восстанавливаем подключение к устройствам…","continuity.retry":"Повторить","editor.space":"Пространство","editor.show_button":"Показывать кнопку","editor.button_label":"Текст кнопки","editor.button_target":"Путь дашборда (куда вести)","marker.sub_entity":"сущность","title.general_settings":"Общие настройки","gs.title":"Общие настройки","gs.hint":"Цвета заливок действуют на все пространства; у каждого цвета своя прозрачность. Какой режим заливки использует пространство — задаётся в его диалоге.","gs.light_group":"Заливка: освещение","gs.light_on":"Свет включён","gs.light_off":"Весь свет выключен","gs.temp_group":"Заливка: температура","gs.temp_cold":"Холодно","gs.temp_ok":"Комфорт","gs.temp_hot":"Жарко","gs.lqi_group":"Заливка: зигби-сигнал","gs.lqi_low":"Слабый сигнал","gs.lqi_high":"Сильный сигнал","gs.reset":"Сбросить к умолчаниям","gs.saved":"Общие настройки сохранены","space.show_lqi":"Показывать зигби-сигнал (LQI) у устройств","space.hide_decor":"Скрыть декоративный слой","space.hide_decor_tip":"Линии, фигуры, надписи и мебель остаются на месте — их видно в редакторе подложки, но не на плане.","space.hide_openings":"Скрыть проёмы","space.hide_openings_tip":"Двери, окна и ворота не рисуются, но продолжают работать: свет проходит, солнце светит в окна, датчики открытия срабатывают. В редакторе плана проёмы видно всегда.","gs.light_none":"Нет источников света","mode.plan":"Редактор плана","mode.devices":"Редактор устройств","display.value":"Значение + состояние","marker.subarea":"без зоны, вручную","device.new":"Новое устройство — откройте его редактор, чтобы снять отметку","opening.unlock_action":"Открыть замок","opening.lock_action":"Закрыть замок","opening.lock_pending":"Выполняется…","title.close_editor":"Закрыть редактор (вернуться к просмотру)","devbar.add":"Добавить","devbar.show_all":"Скрытые и деактивированные","devbar.rules":"Правила иконок","space.roomcard_section":"В карточке комнаты:","space.label_temp":"Температура","space.label_hum":"Влажность","space.label_lqi":"Средний Zigbee-сигнал","space.label_light":"Свет вкл/выкл","roomcard.light_on":"Вкл","roomcard.light_off":"Выкл","roomcard.light_partial":"{on} из {total}","toast.split_pick_inside":"Промежуточные точки разреза — внутри комнаты","mode.decor":"Редактор подложки","decor.select":"Выбрать","decor.line":"Линия","decor.rect":"Прямоугольник","decor.ellipse":"Овал","decor.text":"Надпись","decor.erase":"Стереть","decor.erase_confirm_title":"Стереть объект?","decor.color":"Цвет","decor.width":"Толщина линии","decor.line_style":"Стиль линии","decor.line_style_solid":"Сплошная","decor.line_style_dashed":"Пунктирная","decor.fill":"Заливка","decor.fill_color":"Цвет заливки","decor.length":"Длина","decor.size":"Размер","decor.angle":"Угол поворота, °","decor.text_size":"Размер текста","decor.backdrop_properties":"Свойства картинки-подложки","decor.text_title":"Надпись","decor.object_title":"Редактирование: {kind}","decor.text_label":"Текст","decor.live_group":"Вставить переменную HA","decor.live_entity":"Сущность","decor.live_entity_ph":"выберите сущность","decor.live_attr":"Значение","decor.live_attr_ph":"выберите состояние или атрибут","decor.live_state":"Состояние","decor.backdrop":"Картинка-подложка","decor.backdrop_hint":"Тяните картинку — перемещение; угол — размер; верхнюю ручку — поворот. Shift меняет пропорции или снимает шаг угла.","decor.backdrop_reset":"Вернуть картинку","decor.backdrop_reset_done":"Картинка вернулась на своё место и в свой размер","marker.icon_auto":"Авто: {icon} (по правилам иконок; выберите свою, чтобы заменить)","marker.icon_pin_auto":"Закрепить","mode.plan_tip":"Редактор плана — геометрия дома: рисование и объединение/разделение комнат, привязка к зонам HA, двери, окна и ворота, карточки комнат, масштаб","mode.devices_tip":"Редактор устройств — всё про значки: перетаскивание, клик — настройка привязки/иконки/отображения, виртуальные устройства, правила иконок","mode.decor_tip":"Редактор подложки — чисто визуальный декор под планом: линии, прямоугольники, овалы и надписи, не реагирующие на клики","space.glow_enabled":"Свечение источников света","gs.glow_group":"Свечение источников света","gs.glow_base":"Темнота дома","gs.glow_light":"Цвет света по умолчанию / интенсивность","gs.wall_group":"Стены","gs.wall_fill":"Цвет заливки стен","gs.glow_radius":"Радиус свечения","gs.unit_m":"м","gs.unit_ft":"фут","marker.controls_label":"Управляет другими источниками света","marker.controls_hint":"При действии «Переключить» клик разом переключает все добавленные здесь источники. Собственная сущность маркера управляется напрямую и сюда не добавляется; чтобы считать её светом, включите флаг ниже.","marker.controls_filter":"Поиск ламп и выключателей…","info.controls":"Управляет","marker.glow_radius_label":"Радиус свечения","marker.glow_radius.help":"Задаёт радиус свечения в метрах или футах; пустое значение использует радиус из общих настроек.","marker.glow_radius.help.aria":"Подсказка: радиус свечения","markup.wallthick":"Толщина","markup.select":"Выбрать","title.markup_wallthick":"Толщина — клик по стене задаёт толщину. Пустое поле или ноль убирает толщину.","markup.hint_wallthick":"клик по стене · Esc закрывает без применения","wallthick.field":"Толщина","wallthick.unit_cm":"см","wallthick.unit_in":"дюйм","wallthick.apply_room":"Применить ко всем стенам комнаты","markup.draw_wall_title":"Толщина каждого нового сегмента стены комнаты (1–100 см). Общие стены сохраняют значение соседа.","toast.wallthick_pick":"Кликните по стене (не по открытой границе)","toast.wallthick_open":"У открытой границы нет толщины","toast.wallthick_set":"Толщина стены задана","toast.wallthick_cleared":"Толщина стены убрана","toast.physical_range":"Введите значение от 1 до {max} {unit}","toast.physical_angle":"Введите угол поворота от 0° до 90°, не включая 90°","toast.physical_limit":"В пространстве достигнут лимит объектов этого типа","markup.boundary_hint":"Выберите общую границу комнат или пунктирный участок","markup.boundary_hint_open":"Кликните начало открытого участка","markup.boundary_hint_second":"Укажите конец на выбранной границе · Esc — отмена","markup.boundary_hint_retry":"Укажите конец на выбранной границе","markup.boundary_hint_restore":"Кликните, чтобы восстановить стену","markup.boundary_hint_outer":"Открыть можно только общую границу двух комнат","markup.boundary_hint_blocked":"над границей находится перегородка, колонна или незавершённый контур","markup.boundary_hint_ambiguous":"Выберите участок чуть дальше от стыка","markup.boundary_cancel":"Отменить незавершённое действие «Граница»","toast.openwall_pick":"Инструмент работает с границами комнат","toast.openwall_shared_only":"Открыть можно только общую стену двух комнат","toast.openwall_short":"Слишком короткий отрезок — кликните дальше","toast.boundary_opened":"Участок границы открыт","toast.boundary_restored":"Стена на участке восстановлена","toast.boundary_same_edge":"Выберите конечную точку на выбранной стене","toast.boundary_ambiguous":"Выберите участок чуть дальше от стыка","toast.boundary_blocked":"Граница перекрыта независимой стеной. Сначала переместите или удалите её инструментом «Выбрать»","toast.delete_room_pick":"Кликните внутри комнаты, которую нужно удалить","toast.openwall_openings_removed":"Проёмы на виртуальном отрезке удалены","toast.openwall_opened":"Граница «{a}» ↔ «{b}» теперь открыта","toast.openwall_closed":"Граница «{a}» ↔ «{b}» снова закрыта","toast.opening_on_virtual":"Проёмы на виртуальной стене запрещены","marker.from_ha_option":"Выбрать из списка HA","marker.show_entities":"Отображать сущности","marker.show_entities_tip":"Добавляет в список не только устройства, но и все их сущности","marker.pick_ph":"Выберите устройство…","room.open_area":"Открыть зону в HA","view.volumetric":"Объёмный вид","view.flat":"Плоский вид","kiosk.title":"Размеры на этом экране","kiosk.hint":"Хранится только на этом устройстве — у каждого настенного планшета или ТВ свои удобные размеры.","kiosk.icon_scale":"Размер значков устройств","kiosk.font_scale":"Размер текста карточек комнат","editor.kiosk":"Режим настенного устройства (киоск)","editor.cycle":"Автосмена пространств каждые N секунд (киоск, 0 = выкл)","room.settings_title":"Настройки комнаты","room.settings_section":"Настройки комнаты (переопределяют пространство)","room.fill_label":"Заливка в ЭТОЙ комнате","fill.inherit":"Как у пространства","room.custom_fill_space":"Цвет пространства","room.custom_fill_own":"Цвет комнаты","room.temp_src_label":"Источник температуры","room.hum_src_label":"Источник влажности","room.src_average":"Средняя по датчикам комнаты (по умолчанию)","room.src_pick":"Конкретное устройство или сущность HA","room.src_ph":"Выберите источник…","toast.room_updated":"Комната обновлена","space.card_font":"Размер шрифта карточек комнат (всё пространство)","room.sizes_section":"Размеры шрифтов","room.name_scale":"Размер названия","room.label_scale":"Размер подписей","preview.room_name":"Гостиная","toast.cfg_reload_failed":"Не удалось перечитать план с сервера: {err}","room.settings_short":"Настройки комнаты","room.unnamed":"Комната без имени","marker.use_climate_temp":"Учитывать температуру устройства в комнате","marker.use_climate_temp_tip":"Добавляет current_temperature кондиционера или термостата в среднюю температуру комнаты. Внешний бейдж со значением настраивается отдельно ниже.","marker.value_badge_title":"Бейдж со значением","marker.value_badge.help":"Показывает одно выбранное значение рядом со значком. Не влияет на комнатные показатели, свет, Glow и действие по нажатию.","marker.value_badge.help.aria":"Подсказка: бейдж со значением устройства","marker.value_badge_enabled":"Отображать бейдж со значением","marker.value_badge_source":"Значение","marker.value_badge_source.help":"Выберите конкретное состояние, поддерживаемый атрибут или производное значение этого устройства.","marker.value_badge_source.help.aria":"Подсказка: источник значения бейджа","marker.value_badge_position":"Расположение","marker.value_badge_position.help":"Выбранная сторона сохраняется постоянно и не переворачивается автоматически у края плана.","marker.value_badge_position.help.aria":"Подсказка: расположение бейджа","marker.value_badge_right":"Справа","marker.value_badge_bottom":"Снизу","marker.value_badge_left":"Слева","marker.value_badge_top":"Сверху","marker.value_badge_empty":"У этого устройства нет доступных значений","marker.value_badge_static":"Статичный значок не показывает живые значения. Настройка сохранится.","marker.value_badge_missing":"Источник недоступен","marker.value_badge_missing_hint":"Сохранённый источник сейчас недоступен. Можно сохранить его, отключить бейдж или выбрать другой.","marker.value_badge_duplicate":"То же значение уже показано внутри значка.","marker.value_badge_state":"Состояние · {name}","marker.value_badge_marker_state":"Состояние источника света · {name}","marker.value_badge_lqi":"Качество Zigbee-сигнала (среднее)","marker.value_badge_attr_current_temperature":"Текущая температура · {name}","marker.value_badge_attr_temperature":"Целевая температура · {name}","marker.value_badge_attr_current_humidity":"Текущая влажность · {name}","marker.value_badge_attr_humidity":"Влажность · {name}","marker.value_badge_attr_current_position":"Положение · {name}","marker.value_badge_attr_percentage":"Скорость · {name}","marker.value_badge_attr_brightness":"Яркость · {name}","marker.value_badge_attr_volume_level":"Громкость · {name}","marker.value_badge_attr_battery_level":"Заряд батареи · {name}","marker.value_badge_attr_fan_speed":"Скорость вентилятора · {name}","marker.light_role_label":"Является источником света","marker.light_role.help":"Определяет, является ли сам маркер пространственным источником света. Привязанные выше лампы остаются независимыми источниками для комнаты.","marker.light_role.help.aria":"Подсказка: является ли устройство источником света","marker.light_role_auto_yes":"Автоматически (источник света)","marker.light_role_auto_no":"Автоматически (не источник света)","marker.light_role_always":"Всегда источник света","marker.light_role_never":"Никогда не источник света","marker.light_entity_label":"Ведущая сущность света","marker.light_entity.help":"Для составного устройства выберите сущность, состояние, цвет и яркость которой представляют этот источник на плане. Автоматический вариант сохраняет совместимость со старыми планами.","marker.light_entity.help.aria":"Подсказка: ведущая сущность света","marker.light_entity_auto":"Автоматически ({entity})","marker.light_entity_none":"нет управляемой сущности","marker.light_entity_missing":"Сохранённая сущность {entity} недоступна. Временно используется {fallback}; выбор восстановится, если сущность вернётся.","marker.glow_color_label":"Цвет и яркость свечения","marker.glow_mode.help":"Используйте данные источника, задайте только цвет или зафиксируйте цвет и яркость. Минимум — 1%; чтобы отключить источник, выберите «Никогда».","marker.glow_mode.help.aria":"Подсказка: цвет и яркость свечения","marker.glow_mode_auto":"Из источника","marker.glow_mode_color":"Задать цвет","marker.glow_mode_fixed":"Задать цвет и яркость","marker.glow_color":"Цвет свечения","marker.glow_brightness":"Яркость","marker.glow_disabled_never":"Настройки свечения недоступны: маркер явно исключён из источников света.","marker.glow_disabled_auto":"В автоматическом режиме у этого маркера не найден пространственный источник света.","marker.glow_disabled_no_entity":"Для пространственного источника нет активной управляемой сущности Home Assistant.","marker.glow_passive_hint":"У источника нет собственных данных Home Assistant. Задайте цвет и яркость вручную; радиус остаётся доступным.","marker.control_broken":"Сохранённый источник отсутствует или больше не отмечен как источник света","marker.control_missing_label":"Отсутствующий источник на плане","marker.control_passive":"пассивный источник","toast.marker_control_cycle":"Такая связь создаст циклическую цепочку управления светом.","toast.marker_binding_required":"Сначала выберите устройство Home Assistant, затем добавьте управляемый источник света.","confirm.unlock":"Открыть замок «{name}»?","toast.files_migrate_failed":"Не удалось перенести вложения к новой привязке, ссылки остались на старые файлы: {err}","space.pick_saved":"Уже загруженные","space.pick_saved_hint":"Планы, сохранённые на сервере, включая отцеплённые ранее","space.no_saved":"На сервере пока нет сохранённых планов.","space.loading":"Загрузка…","space.used_by":"используется: {list}","space.in_use":"План используется пространством — сначала отцепите его","btn.use":"Выбрать","confirm.delete_plan":"Удалить файл плана «{name}» с сервера? Действие необратимо.","toast.plans_list_failed":"Не удалось получить список планов: {err}","toast.plan_delete_failed":"Не удалось удалить план: {err}","marker.hide":"Скрыть","marker.hide_tip":"Устройство исчезнет с плана после сохранения, но продолжит участвовать в расчёте сигнала комнаты. Вернуть его можно через кнопку «Скрытые и деактивированные» в редакторе устройств.","marker.show":"Показать","marker.show_tip":"Устройство снова появится на плане после сохранения.","marker.hidden_ghost":"Устройство скрыто пользователем","marker.ha_disabled_device":"Устройство деактивировано в Home Assistant и скрыто с плана.","marker.ha_disabled_entity":"Сущность деактивирована в Home Assistant и скрыта с плана.","marker.ha_disabled_all_entities":"У устройства нет активных сущностей Home Assistant, поэтому оно скрыто с плана.","marker.ha_registry_limited":"Полный реестр Home Assistant недоступен этому пользователю. Неподтверждённый объект временно нельзя показывать или использовать.","marker.delete_tip":"Полностью удалить устройство с плана и из всех расчётов. Позже его можно добавить заново.","tap.run":"Запустить автоматизацию/скрипт/сцену","tap.cover":"Открыть/закрыть (шторы/жалюзи)","marker.run_target_label":"Что запускать","marker.run_search_ph":"Поиск: автоматизация, скрипт или сцена…","marker.run_target_gone":"Цель {id} не найдена — выберите заново","marker.tap_confirm":"Спрашивать подтверждение","marker.tap_confirm_tip":"Перед выполнением показать диалог подтверждения — защита от случайных нажатий.","marker.toggle_hint_single":"Цель: {name} ({id}).","marker.virtual_light_target":"Виртуальный свет: {name}.","marker.virtual_light_current":"Ручное состояние: {state} → {effect}.","marker.virtual_light_state_on":"включён","marker.virtual_light_state_off":"выключен","marker.toggle_hint_group":"Переключатся источники ({count}): {names}.","marker.toggle_hint_current":"Сейчас: {state} → {effect}.","marker.toggle_hint_group_current":"Сейчас включено {on} из {count} → {effect}.","marker.toggle_hint_skipped":"Не будут затронуты ({count}): {targets}.","marker.toggle_effect_turn_on":"включится","marker.toggle_effect_turn_off":"выключится","marker.toggle_effect_open":"откроется","marker.toggle_effect_close":"закроется","marker.toggle_effect_stop":"остановится","marker.toggle_effect_toggle":"состояние переключится","marker.toggle_skip_missing":"не найдено","marker.toggle_skip_ha_disabled":"деактивировано в HA","marker.toggle_skip_unavailable":"недоступно","marker.toggle_skip_unsupported":"переключение не поддерживается","marker.toggle_skip_secure":"заблокировано из соображений безопасности","marker.toggle_none_no_binding":"У устройства нет привязки, состояние переключать нечем. По нажатию ничего не произойдёт.","marker.toggle_none_no_actionable_entity":"У устройства нет состояния, которое можно переключить. По нажатию ничего не произойдёт.","marker.toggle_none_configured_targets_missing":"Настроенные цели сейчас недоступны. Собственная сущность устройства не будет подставлена вместо них.","marker.toggle_none_ha_disabled":"Цель деактивирована в Home Assistant и не может использоваться на плане.","marker.toggle_none_unavailable":"Цель сейчас недоступна. По нажатию ничего не произойдёт.","marker.toggle_none_unsupported":"Для этой сущности Home Assistant не предоставляет безопасного переключения. По нажатию ничего не произойдёт.","marker.toggle_none_secure":"Переключение замков, сигнализации и защитных ворот с плана заблокировано из соображений безопасности.","run.automation":"автоматизация","run.script":"скрипт","run.scene":"сцена","confirm.tap_run":"Запустить «{name}»?","confirm.tap_toggle":"Переключить «{name}»?","confirm.tap_cover":"Открыть/закрыть «{name}»?","toast.run_started":"Запущено: {name}","toast.run_target_missing":"Цель запуска не найдена — проверьте настройки устройства","toast.run_target_required":"Выберите автоматизацию, скрипт или сцену","toast.tap_target_changed":"Цель действия изменилась. Повторите попытку.","toast.virtual_light_toggle_failed":"Не удалось переключить виртуальный свет: {err}","toast.value_badge_source_required":"Выберите значение для бейджа","btn.run":"Выполнить","vac.section":"Робот-пылесос: живая позиция","vac.autocal":"Настроить автоматически","vac.live":"Живая позиция на плане","vac.trail":"Показывать путь робота","vac.cal_maps":"Откалиброваны карты: {maps}","vac.autocal_no_rooms":"Интеграция не отдаёт список комнат — откройте «Подогнать вручную»","vac.autocal_no_match":"Не совпали имена комнат (нужно ≥3 общих) — откройте «Подогнать вручную»","vac.autocal_done":"Готово: привязка по {rooms} комнатам. Запустите уборку и проверьте","vac.cal_need_pos":"Робот сейчас не отдаёт координаты — запустите уборку и поставьте на паузу","vac.cal_done":"Калибровка сохранена. Запустите уборку и проверьте","vac.cal_cancelled":"Калибровка отменена","vac.fit":"Подогнать вручную","vac.fit_hint":"Перетащите карту робота на место, растяните за уголки","vac.fit_rotate":"Повернуть 90°","vac.fit_mirror":"Отразить","vac.trail_never":"Не показывать никогда","vac.trail_cleaning":"Во время уборки","vac.trail_always":"Показывать всегда","gs.bg_group":"Фон сцены","gs.bg_color":"Цвет фона вокруг плана","gs.bg_default":"Как в теме","gs.bg_theme":"по умолчанию — из темы","gs.bg_mode":"Фон плана","gs.bg_static":"Статичный цвет","gs.bg_daynight":"Следует за Солнцем","gs.bg_daynight_hint":"Фон проходит рассвет, день, закат и ночь по данным sun.sun, а при их недоступности — по локальным часам. Компас для фона не нужен.","gs.sun_group":"Солнце","gs.sun_missing":"Сущность sun.sun не найдена — фон следует локальным часам, а оконные лучи недоступны.","gs.north":"Север на плане","gs.north_ph":"не задан","gs.north_hint":"Север нужен только для оконных лучей (шаг 1°, с Shift 15°); солнечный фон работает без него.","gs.north_clear":"Сбросить","gs.north_letter":"С","gs.sun_rays":"Солнце в окнах","gs.about_group":"О карточке","gs.about_version":"Houseplan Card v{v}","gs.about_github":"GitHub · документация и issues","gs.about_telegram":"Чат в Telegram","space.bg_color":"Цвет фона вокруг плана","space.bg_inherit":"Наследовать общий","space.bg_inherited":"наследуется из общих настроек","space.bg_mode":"Фон плана","space.north":"Север на плане (переопределение)","space.north_inherited":"наследуется: {v}","space.sun_rays":"Солнце в окнах","space.sun_inherit":"Наследовать общий","space.sun_on":"Вкл","space.sun_off":"Выкл","canvas.far_objects":"Объектов далеко от плана: {n}","canvas.show_far":"Показать","canvas.home_tip":"План там — нажмите, чтобы вписать","gs.grid_group":"Обслуживание планов","gs.grid_hint":"Обновляет модели данных, выравнивает элементы по сетке и объединяет лишние фрагменты стен. Перед записью будет показан точный отчёт.","gs.align_all":"Оптимизировать планы","gs.align_title":"Оптимизировать планы","gs.align_none":"Все планы уже используют актуальную и оптимальную модель данных.","gs.align_count":"Сдвинется элементов: {n} из {total}, максимум на {cm} см.","gs.align_where":"Наибольший сдвиг — в пространстве «{s}».","gs.align_turned":"Проёмов с исправлением угла: {n}.","gs.align_removed_drafts":"Схлопнувшиеся на сетке некорректные контуры удалены: {n}.","gs.optimize_changes":"Миграций модели: {m}; канонизировано планов: {c}; объединено отрезков реальных стен: {w}; виртуальных: {s}.","gs.optimize_glow_migration":"Старый Glow: пространств — {spaces} → без заливки данных + независимый Glow; комнат — {rooms} → наследуемая заливка + независимый Glow.","gs.align_warn":"Элементы, намеренно поставленные между узлами, будут сдвинуты. После операции доступна одна отмена — только до следующего изменения плана.","gs.align_run":"Оптимизировать","gs.align_done":"Планы оптимизированы: сдвинуто элементов — {n}, обслужено записей — {m}","gs.optimize_undo":"Отменить последнюю оптимизацию","gs.optimize_undone":"Последняя оптимизация отменена","decor.furniture":"Мебель","furn.title":"Библиотека мебели","furn.symbol":"Символ","furn.group_furniture":"Мебель","furn.group_appliance":"Техника","furn.group_sanitary":"Сантехника","furn.group_other":"Прочее","furn.width":"Ширина","furn.depth":"Глубина","furn.pick_hint":"Выберите символ и кликните по плану.","furn.place_hint":"Кликните по плану — предмет встанет к ближайшей стене. Shift — свободно.","furn.sym_sofa":"Диван","furn.sym_armchair":"Кресло","furn.sym_coffee_table":"Журнальный столик","furn.sym_table_dining":"Обеденный стол","furn.sym_table_round":"Круглый стол","furn.sym_chair":"Стул","furn.sym_desk":"Письменный стол","furn.sym_bed_double":"Двуспальная кровать","furn.sym_bed_single":"Односпальная кровать","furn.sym_nightstand":"Тумбочка","furn.sym_wardrobe":"Шкаф","furn.sym_bookshelf":"Стеллаж","furn.sym_fridge":"Холодильник","furn.sym_stove":"Плита","furn.sym_dishwasher":"Посудомоечная машина","furn.sym_washer":"Стиральная машина","furn.sym_dryer":"Сушильная машина","furn.sym_tv":"Телевизор","furn.sym_ac":"Кондиционер","furn.sym_water_heater":"Бойлер","furn.sym_toilet":"Унитаз","furn.sym_bathtub":"Ванна","furn.sym_shower":"Душ","furn.sym_sink":"Раковина","furn.sym_kitchen_sink":"Кухонная мойка","furn.sym_bidet":"Биде","furn.sym_stairs":"Лестница","furn.sym_fireplace":"Камин","furn.sym_plant":"Растение","furn.sym_rug":"Ковёр","common.yes":"Да","common.no":"Нет","vac.diag_source":"Источник","vac.diag_platform":"Интеграция","vac.diag_status":"Статус","vac.diag_position":"Позиция","vac.diag_rooms":"Комнаты","vac.diag_rooms_value":"{total} · совпало имён: {matched} · {readiness}","vac.autocal_ready":"автокалибровка доступна","vac.autocal_not_ready":"нужно 3 совпавших имени","vac.diag_path":"Путь интеграции","vac.diag_map":"ID карты","vac.source_none":"не выбран","vac.source_status_ok":"Готов","vac.source_status_missing":"Не найден","vac.source_status_disabled":"Деактивирован в Home Assistant","vac.source_status_unavailable":"Недоступен","vac.source_status_unverified":"Нельзя проверить с текущими правами","vac.source_status_unsupported":"Нет данных позиции","vac.source_status_none":"Нет источника","vac.source_banner_missing":"Сохранённый источник больше не существует. Он не заменён автоматически: выберите другой или восстановите его в Home Assistant.","vac.source_banner_disabled":"Сохранённый источник деактивирован в Home Assistant. Активируйте его там или выберите другой.","vac.source_banner_unverified":"Текущих прав Home Assistant недостаточно для проверки источника. Привязка сохранена и не будет заменена автоматически.","vac.choose_source":"Выбрать источник","vac.source_auto":"Автоматически","vac.source_auto_hint":"Использовать совместимую сущность этого устройства","vac.all_cameras":"Все камеры","vac.all_cameras_warn":"Камера может не отдавать данные робота. Выбирайте её только если это карта вашего пылесоса.","vac.all_cameras_empty":"Других сущностей камеры не найдено.","vac.platform_unknown":"интеграция неизвестна","vac.cap_position":"позиция","vac.cap_rooms_short":"комнаты","vac.cap_path":"путь","vac.cap_map":"ID карты","vac.cap_none":"данные робота не обнаружены","vac.xcme_hint":"Включите атрибуты Xiaomi Cloud Map Extractor:","vac.documentation":"Документация","vac.residual_title":"Проверьте автокалибровку","vac.residual_message":"Совпавшие комнаты расходятся максимум на {error}. Примените приблизительную калибровку, подгоните её вручную либо отмените без изменения сохранённых настроек.","vac.apply_proposal":"Применить","gs.backup_group":"Резервная копия и перенос","gs.backup_hint":"Скачайте переносимую JSON-копию или проверьте и импортируйте копию, созданную House Plan.","backup.export_open":"Экспорт","backup.import_open":"Импорт","backup.export_title":"Экспорт House Plan","backup.import_title":"Импорт House Plan","backup.export_hint":"Выберите, должна ли копия содержать всю конфигурацию House Plan или только текущее пространство.","backup.full":"Полная копия","backup.current_space":"Текущее пространство","backup.current_space_title":"Текущее пространство: {title}","backup.no_current_space":"Нет текущего пространства","backup.plan_only":"Только планировка","backup.plan_only_hint":"Сохранить комнаты, стены, проёмы, декор и позиции подписей комнат без устройств и привязок Home Assistant.","backup.plan_only_preview":"Файл содержит только планировку","backup.privacy_warning":"Архив сохраняет названия, идентификаторы Home Assistant и точные координаты. Внутренние планы и вложения указываются ссылками, но не вкладываются; текущие состояния и маршруты пылесосов не включаются.","backup.download":"Скачать JSON","backup.export_done":"Резервная копия скачана","backup.reading":"Проверяем резервную копию…","backup.revalidated":"После предпросмотра план изменился. Сводка обновлена — проверьте её и подтвердите ещё раз.","backup.error.unauthorized":"У вас нет прав на экспорт или импорт этого плана.","backup.error.not_ready":"House Plan ещё не готов. Повторите попытку через несколько секунд.","backup.error.too_large":"Размер резервной копии превышает 8 МиБ.","backup.error.invalid_json":"Выбранный файл не является корректным JSON.","backup.error.invalid_format":"Выбранный файл не является резервной копией House Plan.","backup.error.unsupported_export_version":"Эта версия формата резервной копии не поддерживается установленной версией.","backup.error.future_model":"Резервная копия создана в более новой версии модели данных House Plan.","backup.error.invalid_config":"Резервная копия содержит некорректную конфигурацию House Plan.","backup.error.invalid_layout":"Резервная копия содержит некорректные позиции объектов.","backup.error.invalid_content":"Резервная копия содержит некорректные или несогласованные ссылки на файлы.","backup.error.space_not_found":"Выбранное пространство больше не существует.","backup.error.capacity_exceeded":"Добавление этой копии превысит ограничения размера плана.","backup.error.preview_expired":"Время предпросмотра истекло. Выберите файл резервной копии заново.","backup.error.preview_owner_mismatch":"Этот предпросмотр принадлежит другому пользователю Home Assistant.","backup.error.conflict":"После предпросмотра план изменился. Проверьте обновлённую сводку.","backup.error.content_confirmation_required":"Подтвердите отсоединение недоступных локальных файлов.","backup.error.commit_failed":"Не удалось безопасно применить копию. Предыдущий план восстановлен или ожидает восстановления.","backup.error.missing_plan":"После предпросмотра исчез связанный файл плана. Проверьте копию ещё раз.","backup.error.missing_content":"После предпросмотра исчез связанный локальный файл. Проверьте копию ещё раз.","backup.error.marker_control_missing":"В резервной копии отсутствует связанный источник света на плане.","backup.error.marker_control_not_light":"Связанная цель больше не отмечена как источник света.","backup.error.marker_control_self":"Источник света не может управлять самим собой.","backup.error.marker_control_cycle":"Резервная копия содержит циклическую цепочку управления светом.","backup.error.duplicate_marker_control":"Резервная копия содержит повторяющуюся связь с источником света.","backup.error.no_backup":"Нет снимка импорта или оптимизации, который можно восстановить.","backup.same_source":"Создано на этом экземпляре Home Assistant","backup.foreign_source":"Создано на другом экземпляре Home Assistant","backup.created":"Создано: {value}","backup.versions":"Карточка {card}; интеграция {integration}; модель данных {model}","backup.count_spaces":"Пространств: {n}","backup.count_rooms":"Комнат: {n}","backup.count_walls":"Стен: {n}","backup.count_openings":"Проёмов: {n}","backup.count_decor":"Объектов декора: {n}","backup.count_markers":"Устройств: {n}","backup.count_layout":"Позиций: {n}","backup.bindings":"Привязки — устройства: {device}, сущности: {entity}, виртуальные: {virtual}; позиций без пространства: {legacy}","backup.binding_status":"Статус на целевом экземпляре — активных: {active}, деактивированных: {disabled}, отсутствующих: {missing}","backup.missing_areas":"На целевом экземпляре отсутствуют зоны: {areas}","backup.dropped_marker_links":"Связи с источниками света вне переносимого пространства пропущены: {n}.","backup.replace_warning":"Текущая конфигурация и расположение будут заменены. Загруженные файлы не удаляются. Одну отмену можно выполнить до следующего изменения плана.","backup.foreign_bookkeeping":"Служебные списки известных и новых устройств другого экземпляра импортированы не будут.","backup.final_name":"Название нового пространства","backup.target_settings":"Глобальные настройки целевого экземпляра не изменятся.","backup.duplicates":"Уже существующих привязок: {n}","backup.skip":"Пропустить повторяющиеся устройства","backup.virtual_copy":"Добавить безопасные статичные виртуальные копии","backup.content":"Связанное содержимое","backup.content_available":"доступно локально","backup.content_external":"внешняя ссылка","backup.content_detach_required":"будет отвязано","backup.confirm_detach":"Я понимаю, что недоступные внутренние планы и вложения будут отвязаны от импортируемой конфигурации.","backup.replace":"Заменить","backup.add":"Добавить пространство","backup.space_done":"Пространство импортировано: комнат — {rooms}, устройств — {markers}","backup.full_done":"Копия восстановлена: пространств — {spaces}, комнат — {rooms}, устройств — {markers}","backup.undo_import":"Отменить последний полный импорт","backup.import_undone":"Полный импорт отменён"}};function Uo(t,e){if(e&&e in Wo)return e;return(t?.locale?.language||t?.language||"en").toLowerCase().startsWith("ru")?"ru":"en"}function Go(t,e,i){return Ki(Wo[t][e]??jo[e]??e,i)}function Vo(t,e){const i=Wo[t][e]??Wo.en[e];return"string"==typeof i&&i.trim().length>0}class Ko extends ht{constructor(){super(...arguments),this.deviceName="",this._metadataConnection=null,this._demoUntil=0,this._demoTimer=0,this._demoGeneration=1,this._demoKind=null,this._reducedMotion=!1,this._onMotionChange=t=>{this._reducedMotion=t.matches,this.requestUpdate()},this._startShortDemo=()=>{const t=this.presentation;t&&"icon_ripple"===t.display&&"none"===t.pulse.kind&&(this._demoGeneration++,this._demoKind="short",this._demoUntil=Date.now()+3300,window.clearTimeout(this._demoTimer),this._demoTimer=window.setTimeout(()=>{this._demoUntil=0,this._demoTimer=0,this._demoKind=null,this.requestUpdate()},3330),this.requestUpdate())},this._toggleContinuousDemo=()=>{const t=this.presentation;t&&"icon_ripple"===t.display&&"none"===t.pulse.kind&&("continuous"===this._demoKind?this._clearDemo():(this._clearDemo(),this._demoGeneration++,this._demoKind="continuous",this._demoUntil=Number.POSITIVE_INFINITY),this.requestUpdate())}}connectedCallback(){super.connectedCallback(),this._motionMedia=window.matchMedia?.("(prefers-reduced-motion: reduce)"),this._reducedMotion=!!this._motionMedia?.matches,this._motionMedia?.addEventListener?.("change",this._onMotionChange),this._ensureMetadata()}disconnectedCallback(){this._metadataRelease?.(),this._metadataRelease=void 0,this._metadataConnection=null,this._clearDemo(),this._motionMedia?.removeEventListener?.("change",this._onMotionChange),this._motionMedia=void 0,super.disconnectedCallback()}willUpdate(t){t.has("hass")&&this._ensureMetadata();const e=this.presentation,i=t.get("presentation");t.has("presentation")&&i?.binding!==e?.binding&&this._clearDemo(),e&&this._demoKind&&("icon_ripple"!==e.display||"none"!==e.pulse.kind)&&this._clearDemo()}_ensureMetadata(){const t=this.hass?.connection||null;t&&t!==this._metadataConnection&&(this._metadataRelease?.(),this._metadataConnection=t,this._metadataRelease=Oo(this.hass,()=>this.requestUpdate()))}_clearDemo(){window.clearTimeout(this._demoTimer),this._demoTimer=0,this._demoUntil=0,this._demoKind=null}get _lang(){return Uo(this.hass)}_t(t,e){return Go(this._lang,t,e)}_providerText(t){return"houseplan"===t.domain?this._t("marker.preview.virtual_provider"):t.label.toLowerCase()===t.domain.toLowerCase()?t.domain:`${t.label} (${t.domain})`}_providers(){return this.presentation&&this.registry?function(t,e,i,s=No(t)){if("virtual"===e)return[{domain:"houseplan",label:"House Plan",confidence:"registry-owner"}];const n=e.indexOf(":");if(n<1)return[];const o=e.slice(0,n),r=e.slice(n+1);if("entity"===o){const e=Bo(t,r,i,s);return e?[e]:[]}if("device"!==o)return[];const a=i.devices?.[r]||t?.devices?.[r];if(!a)return[];const l=[a.config_entry_id,...Array.isArray(a.config_entries)?a.config_entries:[]].filter(t=>"string"==typeof t&&!!t).map(e=>{const i=s.configEntries[e];return Ho(t,i?.domain,"registry-owner",s,e)});if(l.some(Boolean))return qo(l);const c=[];for(const[e,n]of Object.entries(i.entities||{}))n?.device_id===r&&null==n?.disabled_by&&c.push(Bo(t,e,i,s));return c.some(Boolean)?qo(c):qo((Array.isArray(a.identifiers)?a.identifiers:[]).map(e=>{const i=Array.isArray(e)?e[0]:null;return Ho(t,i,"identifier-fallback",s)}))}(this.hass,this.presentation.binding,this.registry,No(this.hass)):[]}_providerSummary(t){if(!t.length)return this._t("marker.preview.unknown_provider");const e=t.slice(0,2).map(t=>this._providerText(t)),i=t.length-e.length;return e.join(", ")+(i>0?` · ${this._t("marker.preview.more_sources",{n:i})}`:"")}_sourceSummary(t){const e=t.visualSources;return e.length?1===e.length?`${e[0].name} · ${e[0].eid} · ${this._sourceProvider(e[0].eid)}`:2===e.length?e.map(t=>t.name).join(" · "):this._t("marker.preview.multiple_sources",{n:e.length}):this._t("marker.preview.no_source")}_stateSummary(t){if(!t.visualSources.length)return this._t("marker.preview.no_state");if(1===t.visualSources.length)return t.visualSources[0].stateText||this._t("marker.preview.no_state");const e=[...new Set(t.visualSources.map(t=>t.stateText).filter(Boolean))];return 1===e.length?e[0]:this._t("marker.preview.mixed_states")}_reason(t){return this._t(`marker.preview.reason.${t}`)}_sourceProvider(t){if(!this.registry)return this._t("marker.preview.unknown_provider");const e=Bo(this.hass,t,this.registry,No(this.hass));return e?this._providerText(e):this._t("marker.preview.unknown_provider")}render(){const t=this.presentation;if(!t)return G;const e=!!this._demoKind&&this._demoUntil>Date.now()&&"icon_ripple"===t.display&&"none"===t.pulse.kind,i=e?(()=>{const e=function(t,e,i,s,n=null){return{kind:e,reason:"short"===e?"event":"running",generation:Math.max(1,Math.trunc(i)),expiresAt:"short"===e?n:null,color:s?null:t.color,diameterScale:s?1:t.diameterScale,animated:!s,reducedMotionIndicator:s?"dot":"none"}}(t.pulse,this._demoKind,this._demoGeneration,this._reducedMotion,"short"===this._demoKind?this._demoUntil:null);return{...t,pulse:e,classes:[...t.classes.filter(t=>!t.startsWith("pulse-")),`pulse-${e.kind}`,...e.generation%2==0?["pulse-gen2"]:[]]}})():t,s=this._providers(),n="none"!==t.pulse.kind,o=i.scale*(i.pulse.animated?i.pulse.diameterScale:1);let r=-o/2,a=o/2,l=-o/2,c=o/2;if(i.valueBadge){const t=i.scale*Math.min(4,Math.max(.9,.29*i.valueBadge.text.length+.28)),e=.7*i.scale,s=.6*i.scale;"right"===i.valueBadge.position&&(a=Math.max(a,s+t)),"left"===i.valueBadge.position&&(r=Math.min(r,-s-t)),"top"===i.valueBadge.position&&(l=Math.min(l,-s-e)),"bottom"===i.valueBadge.position&&(c=Math.max(c,s+e))}if(null!=i.lqiText){const t=i.scale*("bottom"===i.valueBadge?.position?1.55:.95);c=Math.max(c,t)}const h=Math.min(1,2.35/Math.max(1,a-r),2.35/Math.max(1,c-l)),d=Math.round(100*h),u=[`left:calc(50% - ${54*((r+a)/2)*h}px)`,`top:calc(50% - ${54*((l+c)/2)*h}px)`,h<1?`transform:scale(${h})`:""].filter(Boolean).join(";"),p=["dev",...i.classes,"virtual"===i.binding?"virtual":"",i.haDisabled?"ghost ha-disabled":"",null!=i.valueText?"valonly":""].filter(Boolean).join(" "),_=Ro(i).join(";"),m=e?this._t("continuous"===this._demoKind?"marker.preview.demo_continuous_notice":"marker.preview.demo_short_notice"):this._reason(t.explanation.reason),g=[this.deviceName,this._providerSummary(s),this._stateSummary(t),m,t.valueFullText||"",fo(t.valueBadge)].filter(Boolean).join(". ");return j`
${this._t("marker.preview.title")} @@ -4293,7 +4297,7 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="da33c4f75cf2469e330b33f11fd5fa04d368 ${r} - `}const Xu={window:120,door:90,gate:300};function Zu(t){return Xu[t]}function Ju(t,e){const i=[t[0],t[1]],s=[e[0],e[1]];return function(t,e){return t[0]-e[0]||t[1]-e[1]}(i,s)<=0?{a:i,b:s}:{a:s,b:i}}function Qu(t){const e=new Map;return t.forEach((t,i)=>{if(!t.kind||t.open)return;const s=Ju(t.a,t.b);if(Math.hypot(s.b[0]-s.a[0],s.b[1]-s.a[1])<=1e-9)return;const n=function(t,e){const i=t=>(Math.abs(t)<=5e-10?0:Math.round(1e6*t)/1e6).toFixed(6);return`${i(t[0])},${i(t[1])}>${i(e[0])},${i(e[1])}`}(s.a,s.b),o=e.get(n);if(o)return o.physicalHalfWidth=Math.max(o.physicalHalfWidth,t.half||0),void(o.sourceOrder=Math.min(o.sourceOrder,i));e.set(n,{segmentKey:n,a:s.a,b:s.b,physicalHalfWidth:Math.max(0,t.half||0),sourceOrder:i})}),[...e.values()]}function tp(t,e){if(Math.abs(t.distance-e.distance)>1e-9)return t.distance-e.distance;if(Math.abs(t.perpendicular-e.perpendicular)>1e-9)return t.perpendicular-e.perpendicular;return(t.target.segmentKeye.target.segmentKey?1:0)||t.target.sourceOrder-e.target.sourceOrder}function ep(t){const e=Qu(t.intervals).map(e=>{const i=function(t,e){const i=e.b[0]-e.a[0],s=e.b[1]-e.a[1],n=Math.hypot(i,s),o=i/n,r=s/n,a=(t[0]-e.a[0])*o+(t[1]-e.a[1])*r,l=Math.max(0,Math.min(n,a)),c=e.a[0]+l*o,h=e.a[1]+l*r;return{along:l,length:n,x:c,y:h,distance:Math.hypot(t[0]-c,t[1]-h),perpendicular:Math.abs((t[0]-e.a[0])*r-(t[1]-e.a[1])*o)}}(t.pointer,e);return{target:e,...i,envelope:Math.max(t.baseTolerance,e.physicalHalfWidth+t.bodyPointerPadding)}}).filter(t=>t.distance<=t.envelope+1e-9).filter(e=>!function(t,e,i,s,n){const o=e.b[0]-e.a[0],r=e.b[1]-e.a[1],a=Math.hypot(o,r);if(!(a>1e-9))return!1;const l=o/a,c=r/a;for(const o of i){if(!o.open&&o.kind)continue;const i=Ju(o.a,o.b),r=i.b[0]-i.a[0],a=i.b[1]-i.a[1],h=Math.hypot(r,a);if(!(h>1e-9))continue;const d=r/h,u=a/h;if(Math.abs(l*u-c*d)>1e-6)continue;if(Math.abs((i.a[0]-e.a[0])*c-(i.a[1]-e.a[1])*l)>n)continue;const p=(t[0]-i.a[0])*d+(t[1]-i.a[1])*u,_=1e-9;if(!(p<=_||p>=h-_)&&Math.abs((t[0]-i.a[0])*u-(t[1]-i.a[1])*d)<=s+1e-9)return!0}return!1}(t.pointer,e.target,t.intervals,e.envelope,Math.max(1e-9,Math.min(t.baseTolerance,.04*t.gridStep)))).sort(tp),i=e[0];if(!i)return null;const{target:s,length:n}=i,o=s.b[0]-s.a[0],r=s.b[1]-s.a[1],a=o/n,l=r/n,c=Math.min(Math.max(0,t.renderedLength)/2,n/2);let h=i.along;const d=Math.max(t.gridStep,1e-9),u=n/2;h=Math.abs(h-u)<=d/2?u:Math.round(h/d)*d,h=Math.max(c,Math.min(n-c,h));const p=s.a[0]+a*h,_=s.a[1]+l*h,m=Math.max(0,t.renderedLength)/2,g=h-m,f=h+m,v=Math.max(0,g),y=Math.max(0,n-f),b=[s.a[0]+a*(g-v/2),s.a[1]+l*(g-v/2)],w=[s.a[0]+a*(f+y/2),s.a[1]+l*(f+y/2)];let k=180*Math.atan2(r,o)/Math.PI;k>=90?k-=180:k<-90&&(k+=180);const $=Math.abs(h-u)<=1e-9;return{presetRevision:t.preset.revision,geometryRevision:t.geometryRevision,pointer:[t.pointer[0],t.pointer[1]],type:t.preset.type,lengthCm:t.preset.lengthCm,flipH:t.preset.flipH,flipV:t.preset.flipV,x:p,y:_,angle:k,renderedLength:t.renderedLength,target:s,measure:{labels:[{distance:v,midpoint:b},{distance:y,midpoint:w}],guide:$?{x:p,y:_,angle:k}:null}}}function ip(t,e,i,s,n=1e-6){return t.presetRevision===i&&t.geometryRevision===s&&Math.hypot(t.pointer[0]-e[0],t.pointer[1]-e[1])<=n}const sp="1.64.0",np={badge:"display.badge",icon_ripple:"display.icon_ripple",value:"display.value",static_icon:"display.static_icon"},op={badge:"marker.display_hint_badge",icon_ripple:"marker.display_hint_icon_ripple",value:"marker.display_hint_value",static_icon:"marker.display_hint_static_icon"},rp=2e3,ap=1e3,lp=1500,cp=t=>{const e=String(t??"").trim().replace(",",".");if(!/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)$/.test(e))return null;const i=Number(e);return Number.isFinite(i)?i:null},hp=(t,e)=>{if(!t.has(e))return{hit:!1};const i=t.get(e);return t.delete(e),t.set(e,i),{hit:!0,value:i}},dp=(t,e,i,s)=>{for(t.delete(e),t.set(e,i);t.size>s;){const e=t.keys().next().value;if(void 0===e)break;t.delete(e)}},up=new Map;let pp=0;const _p=t=>`${window.innerWidth}x${window.innerHeight}|${location.pathname}|${JSON.stringify(t??{})}`;let mp=1e4;const gp="houseplan_card_layout_v1",fp="houseplan_card_cfg_v1",vp="houseplan_card_zoom_v1",yp="houseplan_card_nav_v1",bp="houseplan_card_kiosk_v1",wp="houseplan_card_view_v1",kp=1e3,$p=[[0,1],[45,.88],[70,.62],[86,.32],[100,0]],xp=(t,e)=>{const i=Math.min(t.x,e.x),s=Math.min(t.y,e.y);return{x:i,y:s,w:Math.max(t.x+t.w,e.x+e.w)-i,h:Math.max(t.y+t.h,e.y+e.h)-s}},Sp=new Set(["select","draw","partition","column","merge","split","resize","opening","boundary","wallthick","delroom"]),Mp=(t,e,i)=>{const s=new Event(e,{bubbles:!0,composed:!0});s.detail=i??{},t.dispatchEvent(s)},Cp=t=>{history.pushState(null,"",t),Mp(window,"location-changed",{replace:!1})},Dp=(t,e)=>{let i,s=null;const n=(...n)=>{clearTimeout(i),s=n,i=window.setTimeout(()=>{i=void 0;const e=s;s=null,e&&t(...e)},e)};return n.flush=()=>{if(void 0===i)return;clearTimeout(i),i=void 0;const e=s;s=null,e&&t(...e)},n.pending=()=>void 0!==i,n},Tp=t=>{try{t.target?.setPointerCapture?.(t.pointerId)}catch{}};class Pp extends ht{constructor(){super(...arguments),this._space="f1",this._layout={},this._serverStorage=!1,this._loadOk=!1,this._serverCanWrite=null,this._loading=!1,this._loadTries=0,this._serverCfg=null,this._cfgRev=0,this._cfgContentFingerprint="",this._unsubCfg=null,this._unsubLayout=null,this._unsubVirtual=null,this._liveSyncAttempt=null,this._liveSyncGeneration=0,this._liveSyncConnection=null,this._layoutRev=0,this._layoutContentFingerprint="",this._virtualLights=rn(null),this._canOptimizeUndo=!1,this._undoKind=null,this._devices=[],this._regSignature="",this._defPos={},this._newSyncKey="",this._tip=null,this._hoverRoom=null,this._selId=null,this._toast="",this._mode="view",this._pendingNavMode=null,this._decorTool="select",this._decorStyle={...jr},this._decorDraft=null,this._decorMove=null,this._decorSel=null,this._decorEraseConfirm=null,this._decorTextDialog=null,this._decorShapeDialog=null,this._backdropDialog=null,this._decorTextSelection={start:0,end:0},this._furnPalette=null,this._dtBox=null,this._dtDrag=null,this._bdDrag=null,this._slide="",this._reducedMotion=!1,this._onMotionChange=t=>{this._reducedMotion=t.matches,t.matches&&this._modeTransitionPreparing?this._modeTransitionForceAtomic=!0:t.matches&&this._modeTransition.active&&this._cancelModeTransition(!0),this.requestUpdate()},this._editorChromeMode="plan",this._modeTransitionVisual=null,this._modeTransitionPreparing=!1,this._modeTransitionForceAtomic=!1,this._modeTransitionRequest=0,this._modeTransitionTargetZoom=1,this._modeTransitionEditorCamera=null,this._modeTransition=new hu({frame:t=>this._applyModeTransitionFrame(t),settled:t=>this._settleModeTransition(t)}),this._editorSecondary=new Od({root:()=>this.renderRoot,requestUpdate:()=>this.requestUpdate(),updateComplete:()=>this.updateComplete,clearTip:()=>{this._tip=null}}),this._editorSecondaryCopy={groupActive:(t,e)=>this._t("editor.group_active",{group:t,item:e}),openGroup:t=>this._t("editor.open_group",{group:t}),disabledAction:(t,e)=>this._t("editor.disabled_action",{action:t,reason:e})},this._tool="draw",this._geometryHistory=new Zd(50),this._wallDialog=null,this._drawWallField=null,this._activeDraftId=null,this._resumeDraftBySpace={},this._physicalSel=null,this._physicalDialog=null,this._physicalDrag=null,this._physicalRotate=null,this._physicalLastTap=null,this._physicalPickCycle=null,this._wallUnionCache=null,this._isoGeometryCache=new Map,this._isoFallback=new Set,this._openingTunnelCache=null,this._openingWallIndexCache=new Map,this._openingPlacementIntervalsCache=null,this._planSnapGeometryCache=null,this._physicalBodiesCache=null,this._cleanFloorCache=new Map,this._glowClipCache=new Map,this._lightBarrierCache=null,this._glowFeatherUnits=null,this._glowRenderedSources=new Map,this._glowLastAppearance=new Map,this._glowEnteringSources=new Set,this._glowEnterRafs=new Map,this._glowFadeTimers=new Map,this._glowFeatherSuspendUntil=0,this._glowFeatherResumeTimer=0,this._glowSourceSeq=0,this._glowScreenBlend=!1,this._duplicateColumnId=null,this._duplicateColumnTimer=0,this._rszSel=null,this._rszDrag=null,this._rszPreview=null,this._rszLive=null,this._path=[],this._cursorPt=null,this._planSnapHover=null,this._mergeSel=null,this._openingPreset=null,this._openingPresetRevision=0,this._openingHoverCandidate=null,this._openingDialog=null,this._openingInfo=null,this._opDrag=null,this._opMeasure=null,this._mergeDialog=null,this._openWallAnchor=null,this._boundaryPointerType="mouse",this._boundaryRestoreGuard=null,this._boundaryTargetMemo=null,this._boundaryPreviewMemo=null,this._splitSel=null,this._pendingSplit=null,this._areaSel="",this._nameSel="",this._roomDialog=!1,this._roomEditId=null,this._roomFill="",this._roomCustomFill=null,this._roomTempSrc="",this._roomHumSrc="",this._roomSrcOpen=null,this._roomSrcFilter="",this._roomNameScale=1,this._roomLabelScale=1,this._zoom=1,this._view=null,this._zoomBySpace={},this._viewModeSnap=null,this._pointers=new Map,this._panStart=null,this._panLock=null,this._pinchStart=null,this._suppressClick=!1,this._touchContacts=new Map,this._touchSequenceMultitouch=!1,this._touchClickBlockUntil=0,this._connectedPath="",this._routeDepartureHandled=!1,this._onLocationChanged=()=>{this._connectedPath&&location.pathname!==this._connectedPath?this._leaveCardRoute():location.pathname===this._connectedPath&&(this._routeDepartureHandled=!1)},this._touchGestureGuard={capture:!0,handleEvent:t=>this._guardTouchGesture(t)},this._hdrH=118,this._booting=!0,this._bootFading=!1,this._bootSettling=!1,this._bootSettleRaf=0,this._bootLastH=-1,this._bootStart=0,this._bootLastChange=0,this._bootSoft=!1,this._tapConfirm=null,this._onboardingShown=!1,this._rulesDialog=null,this._alignDialog=null,this._settingsDialog=null,this._backupExportDialog=null,this._backupImportDialog=null,this._sunRaysCache=null,this._dayCycleTimer=0,this._dayCycleClockKey="",this._compassDrag=!1,this._importDialog=null,this._importQueue=[],this._importTotal=0,this._rulesCompiledSrc="",this._infoCard=null,this._nativeMoreInfoEntity=null,this._markerDialog=null,this._spaceDialog=null,this._keyHandler=t=>this._onKey(t),this._warmVp=null,this._warmVpArmed=!1,this._warmLongReturn=!1,this._warmRevivePending=!1,this._warmGen=++pp,this._warmKey=null,this._warmSlot=null,this._hashApplied=!1,this._navApplied=!1,this._labs={active:Object.freeze([]),space:""},this._viewPreference={},this._renderProjection="flat",this._kioskScale={icon:1,font:1},this._kioskDialog=!1,this._activityRt=new Map,this._vacRt=new Map,this._vacViewKey="",this._vacLastView=null,this._vacRaf=0,this._vacSrvTrails={},this._vacJumpOnce=!1,this._continuity=this._newContinuityController(),this._continuityHistory=[],this._continuityEpoch=0,this._continuityDataReady=!0,this._continuityPaintToken=-1,this._continuityDisposed=!1,this._renderSnapshotAt=Date.now(),this._hassSequence=0,this._visibleDeviceSnapshot=null,this._candidateDeviceSnapshot=null,this._stagedDeviceSnapshotToken=-1,this._capturedSnapshotSequence=-1,this._capturedSnapshotDevices=null,this._capturedSnapshotLayout=null,this._capturedSnapshotActivity="",this._capturedSnapshotConfigEpoch=-1,this._capturedSnapshotVirtual="",this._lastValidStageSize=null,this._pendingRefitSize=null,this._refitRaf=0,this._pageVisibility=t=>{if(this._continuity.visibility(t),this._dayCycleVisibility(t),"hidden"!==t.kind){if(this._vacJumpOnce=!0,!t.long){const t=Date.now();let e=!1;for(const i of this._activityRt.values())!i.flashKind||(i.expiresAt||i.flashTs+Cd)>t||(i.flashTs=0,i.flashKind=null,i.expiresAt=0,e=!0);return void(e&&this.requestUpdate())}Date.now()-this._renderSnapshotAt>1e3&&this._continuity.note("device-snapshot-stale"),this._continuityDataReady=!1,this._continuityPaintToken=-1,this._resumeSettling=!0,this._loading?this.requestUpdate():this._loadFromServer()}else this._modeTransitionPreparing?this._modeTransitionForceAtomic=!0:this._cancelModeTransition(!0)},this._resumeSettling=!1,this._viewportInvalidAt=0,this._vacFit=null,this._vacAllCamerasFor=null,this._vacAllCameraCache=null,this._vacCalConfirm=null,this._kioskDots=!1,this._cyclePausedUntil=0,this._swipeStart=null,this._lastTap=0,this._onLabsSnapshot=t=>{const e=this._effectiveProjection();this._labs=t;const i=this._effectiveProjection();this._convertProjectionView(e,i),this.requestUpdate()},this._onHashChange=()=>{const t=this._hashSpace();t&&this._model.find(e=>e.id===t)&&t!==this._space&&(this._activeDraftId&&(this._resumeDraftBySpace[this._space]=this._activeDraftId),this._space=t,this._selId=null,this._path=[],this._clearPlanSnapHover(),this._clearOpeningPlacement(!0),this._tool="draw",this._openWallAnchor=null,this._boundaryRestoreGuard=null,this._activeDraftId=null,this._draftSegmentCms=[],this._closingWallCm=null,"plan"===this._mode&&"draw"===this._tool&&this._resumeLastDraft(),this._restoreZoom(),this.requestUpdate())},this._drag=null,this._rlResize=null,this._holdFired=!1,this._retryContinuity=()=>{this._continuityDataReady=!1,this._continuityPaintToken=-1,this._continuity.retry(this._continuity.recoveryReason||"plan"),this._loading||this._loadFromServer()},this._cfgEpoch=0,this._modelCache=null,this._decorSnapCache=null,this._markerPreviewMemo=null,this._markerPreviewDevicesMemo=null,this._showHidden=!1,this._connHooked=null,this._connectionWasLost=!1,this._haRegistryConnection=null,this._haRegistryRev=-1,this._haBindingCacheKey="",this._planHassMemo=null,this._onHaRegistryUpdate=()=>{const t=xn(this.hass);t.revision===this._haRegistryRev&&this._devices.length||(this._haRegistryRev=t.revision,this._planHassMemo=null,this._regSignature="",this._maybeRebuildDevices(),this.requestUpdate())},this._onConnReady=()=>{if(this._loadTries=0,clearTimeout(this._loadRetryTimer),this._loadRetryTimer=void 0,function(t){const e=vn(t);e&&(wn(t,e),kn(t,e))}(this.hass),this._saveConfigDebounced.pending()&&this._saveConfigDebounced.flush(),this._cfgWriting)return clearTimeout(this._reloadRetry),void(this._reloadRetry=window.setTimeout(this._onConnReady,400));!this._connectionWasLost&&this._continuity.hasCompleteFrame?this._beginContinuityCandidate("connection-ready",!1,"plan"):(this._continuityDataReady=!1,this._continuityPaintToken=-1),this._loading||this._loadFromServer()},this._onConnLost=()=>{this._booting&&!this._continuity.hasCompleteFrame||(this._connectionWasLost=!0,this._continuityDataReady=!1,this._continuityPaintToken=-1,this._continuity.connectionLost())},this._signer=new Wc(()=>this.requestUpdate()),this._dirtyPos=new Set,this._sentPos=new Map,this._persistLayout=Dp(()=>{if(this._serverStorage){const t=[...this._dirtyPos];this._dirtyPos.clear();for(const e of t){const t=this._layout[e];t&&(this._sentPos.set(e,t),this.hass.callWS({type:"houseplan/layout/update",device_id:e,pos:t}).then(t=>this._noteLayoutRev(t)).catch(t=>this._showToast(this._t("toast.pos_save_failed",{err:this._errText(t)}))).finally(()=>{this._sentPos.get(e)===t&&this._sentPos.delete(e)}))}this._cacheSnapshot()}else localStorage.setItem(gp,JSON.stringify(this._layout))},600),this._frame=null,this._showFar=!1,this._writesPending=0,this._writeChain=Promise.resolve(),this._undoGeometry=()=>{if(this._cancelBoundaryAnchor())return;if(this._physicalDrag||this._physicalRotate)return void this._cancelPhysicalGesture();if(this._decorDraft)return this._decorDraft=null,void this.requestUpdate();if(this._decorMove||this._dtDrag||this._bdDrag)return void this._cancelDecorGesture();if(this._rszDrag)return void this._rszCancelDrag();const t=this._geometryHistory.undo();t&&(this._applyGeometryState(t.before)?this._showToast(this._t("history.undone",{name:t.name})):this._geometryHistory.clear())},this._redoGeometry=()=>{if(this._cancelBoundaryAnchor())return;if(this._physicalDrag||this._physicalRotate)return void this._cancelPhysicalGesture();if(this._decorDraft)return this._decorDraft=null,void this.requestUpdate();if(this._decorMove||this._dtDrag||this._bdDrag)return void this._cancelDecorGesture();if(this._rszDrag)return void this._rszCancelDrag();const t=this._geometryHistory.redo();t&&(this._applyGeometryState(t.after)?this._showToast(this._t("history.redone",{name:t.name})):this._geometryHistory.clear())},this._saveConfigDebounced=Dp(()=>{this._serverCfg&&this._writeConfig().catch(t=>{"conflict"===t?.code?(this._showToast(this._t("toast.conflict")),this._cancelPath(),this._reloadConfigOnly(!0)):this._showToast(this._t("toast.cfg_save_failed",{err:this._errText(t)}))})},500),this._draftSegmentCms=[],this._closingWallCm=null,this._savePhysicalDialog=()=>{const t=this._physicalDialog,e=this._curSpaceCfg;if(!t||!e)return;const i=cp(t.cm);if(null==i)return void this._showPhysicalRange("column"===t.kind?ll:100);const s=this._imperial?2.54*i:i,n="column"===t.kind?ll:100;if(!Number.isFinite(s)||s<1||s>n)return void this._showPhysicalRange(n);if("column"===t.kind){const e=this._spaceModel().wall_columns.find(e=>e.id===t.id);if(!e)return;const i=cp(t.angle||"0");if("circle"!==t.shape&&(null==i||i<0||i>=90))return void this._showToast(this._t("toast.physical_angle"));const n="circle"===t.shape?{id:t.id,shape:"circle",center:e.center,cm:s}:{id:t.id,shape:"square",center:e.center,cm:s,angle:i};if(this._spaceModel().wall_columns.some(e=>e.id!==t.id&&Sl(e,n,.02*this._gridPitch)))return void this._showToast(this._t("toast.column_duplicate"))}const o=this._geometrySnapshot();if("partition"===t.kind){const i=(e.partitions||[]).find(e=>e.id===t.id);i&&(i.cm=s)}else if("column"===t.kind){const i=(e.wall_columns||[]).find(e=>e.id===t.id);i&&(i.cm=cl(s),i.shape="circle"===t.shape?"circle":"square","square"===i.shape?i.angle=cp(t.angle||"0"):delete i.angle)}else{const i=(e.room_drafts||[]).find(e=>e.id===t.id);i?.segments?.[t.segment||0]&&(i.segments[t.segment||0].cm=s)}this._recordGeometry(this._t("history.physical_edit"),o),this._physicalDialog=null,this._saveConfig()},this._deletePhysicalSelection=()=>{const t=this._physicalSel,e=this._curSpaceCfg;if(!t||!e)return;if("draft"===t.kind)return void this._deleteDraftWhole();const i=this._geometrySnapshot(),s="partition"===t.kind?"partitions":"column"===t.kind?"wall_columns":"room_drafts";e[s]=(e[s]||[]).filter(e=>e.id!==t.id),e[s].length||delete e[s],this._activeDraftId===t.id&&this._cancelPath(),this._physicalSel=null,this._physicalDialog=null,this._recordGeometry(this._t("history.physical_delete"),i),this._saveConfig()},this._deleteDraftWhole=()=>{const t="draft"===this._physicalDialog?.kind?this._physicalDialog.id:"draft"===this._physicalSel?.kind?this._physicalSel.id:null,e=this._curSpaceCfg;if(!t||!e||!confirm(this._t("confirm.delete_draft")))return;const i=this._geometrySnapshot();e.room_drafts=(e.room_drafts||[]).filter(e=>e.id!==t),e.room_drafts.length||delete e.room_drafts,this._activeDraftId===t&&this._cancelPath(),this._physicalSel=null,this._physicalDialog=null,this._recordGeometry(this._t("history.physical_delete"),i),this._saveConfig()},this._deleteDraftSegment=()=>{const t=this._physicalDialog,e=this._curSpaceCfg;if(!t||"draft"!==t.kind||!e)return;if(!confirm(this._t("confirm.delete_draft_segment")))return;const i=(e.room_drafts||[]).findIndex(e=>e.id===t.id);if(i<0)return;const s=e.room_drafts[i],n=Math.max(0,Math.min(s.segments.length-1,t.segment||0)),o=[],r=s.points.slice(0,n+1),a=s.points.slice(n+1);if(r.length>=2&&o.push({id:s.id,points:r,segments:s.segments.slice(0,n)}),a.length>=2&&o.push({id:o.length?`${s.id}-${Date.now().toString(36)}`:s.id,points:a,segments:s.segments.slice(n+1)}),2===o.length&&e.room_drafts.length>=200)return void this._showToast(this._t("toast.physical_limit"));const l=this._geometrySnapshot();e.room_drafts.splice(i,1,...o),e.room_drafts.length||delete e.room_drafts,this._activeDraftId===s.id&&this._cancelPath(),this._physicalDialog=null,this._physicalSel=o.length?{kind:"draft",id:o[0].id}:null,this._recordGeometry(this._t("history.draft_segment_delete"),l),this._saveConfig()},this._keepClosedAsPartitions=()=>{if(!this._contourClosed||this._pendingSplit||!this._curSpaceCfg)return;const t=this._curSpaceCfg,e=this._path.slice(0,-1);if((t.partitions||[]).length+e.length>2e3)return void this._showToast(this._t("toast.physical_limit"));const i=this._geometrySnapshot(),s=[...this._draftSegmentCms,this._closingWallCm||this._drawWallCm||ea];t.partitions||=[];const n=Date.now().toString(36);for(let i=0;it.id!==this._activeDraftId),t.room_drafts.length||delete t.room_drafts),this._recordGeometry(this._t("history.contour_to_partitions"),i),this._saveConfig(),this._roomDialog=!1,this._path=[],delete this._resumeDraftBySpace[this._space],this._activeDraftId=null,this._draftSegmentCms=[],this._closingWallCm=null},this._toggleServerPlans=async()=>{const t=this._spaceDialog;if(t)if(t.pickSaved)this._spaceDialog={...t,pickSaved:!1};else{this._spaceDialog={...t,pickSaved:!0,savedBusy:!0};try{const t=await this.hass.callWS({type:"houseplan/plans/list"}),e=this._spaceDialog;e&&(this._spaceDialog={...e,saved:t?.plans||[],savedBusy:!1})}catch(t){const e=this._spaceDialog;e&&(this._spaceDialog={...e,saved:[],savedBusy:!1}),this._showToast(this._t("toast.plans_list_failed",{err:this._errText(t)}))}}},this._aspectJob=null,this._dayCycleTick=()=>{if(!this.isConnected||"hidden"===this.ownerDocument.visibilityState)return;const t=this._dayCycleState();if(!t)return this._dayCycleTimer&&(clearInterval(this._dayCycleTimer),this._dayCycleTimer=0),void(this._dayCycleClockKey="");const e=Cr(t);e!==this._dayCycleClockKey&&(this._dayCycleClockKey=e,this.requestUpdate())},this._sunShown=!1,this._sunOut=!1,this._sunOutTimer=0,this._openSettingsDialog=()=>{if(!this._norm)return;const t=this._glowRadiusCm,e=this._imperial?Math.round(t/30.48*10)/10:Math.round(t)/100;this._settingsDialog={colors:JSON.parse(JSON.stringify(this._fillColors)),glowRadius:e,bgColor:es(this._settings,{bgColor:null})||null,northDeg:Fr(this._settings,{}),bgMode:Or(this._settings,{}),sunRays:Nr(this._settings,{}),busy:!1}},this._openAlignDialog=()=>{if(!this._norm||!this._serverCfg)return;const t=this._serverCfg.spaces||[],e=Xd(this._serverCfg,this._layout||{}),i=Math.ceil(10*e.report.maxShiftCm)/10,s=t.find(t=>null!=t?.id&&String(t.id)===e.report.maxSpace),n=t.length>1&&s?String(s.title||s.id):"";this._alignDialog={report:e.report,config:e.config,layout:e.layout,cm:i,where:n,changed:e.changed,busy:!1}},this._optimizeUndoBusy=!1,this._openBackupExport=()=>{this._settingsDialog=null,this._backupExportDialog={kind:"full",busy:!1,error:""}},this._openRulesDialog=()=>{if(!this._norm)return;const t=this._settings.icon_rules,e=(t&&t.length?t:jt).map(t=>({...t}));this._rulesDialog={rules:e,test:"",busy:!1}},this._climateCache=null,this._gearPtCache=new WeakMap}get _canEdit(){return!!this._norm&&(!0===this._serverCanWrite||!1!==this._serverCanWrite&&!0===this.hass?.user?.is_admin)}get _kiosk(){return!!this._config?.kiosk}_showKioskDots(){this._kioskDots=!0,clearTimeout(this._kioskDotsTimer),this._kioskDotsTimer=window.setTimeout(()=>this._kioskDots=!1,2500)}get _modeTransitionBusy(){return this._modeTransitionPreparing||this._modeTransition.active}_cssColor(t,e){const i=String(t||"").trim();if(!i)return e;const s=document.createElement("span");s.style.cssText=`position:absolute;visibility:hidden;color:${i}`,this.renderRoot.append(s);const n=getComputedStyle(s).color||e;return s.remove(),n}_currentModeVisual(t=this._mode){const e=this._modeTransition.presented||this._modeTransitionVisual;if(e)return{...e,viewport:{...e.viewport,viewBox:{...e.viewport.viewBox}}};const i=this._stageEl;if(!i||i.clientWidth<=0||i.clientHeight<=0)return null;const s=this.renderRoot.querySelector(".editorchrome"),n=this._viewOr(this._baseVb()),o=this.renderRoot.querySelector(".hp-paper"),r=this.renderRoot.querySelector(".hp-backdrop"),a=this.renderRoot.querySelector(".zoomwrap"),l=a?getComputedStyle(a).filter:"",c=Number(/brightness\(([^)]+)\)/.exec(l)?.[1]);return{presentedMode:t,editorChromeHeight:"view"===t?0:s?.getBoundingClientRect().height||0,stageWidth:i.clientWidth,stageHeight:i.clientHeight,viewport:ou(n,i.clientWidth),stageColor:getComputedStyle(i).backgroundColor||"rgb(255, 255, 255)",paperColor:o?getComputedStyle(o).fill:"rgb(255, 255, 255)",sceneBrightness:Number.isFinite(c)?c:1,architectureOpacity:"decor"===t?.35:1,backdropOpacity:r&&Number(getComputedStyle(r).opacity)||1,viewWeight:"view"===t?1:0,editorWeight:"view"===t?0:1,toolbarContentOpacity:"view"===t?0:1}}_viewForModeTarget(t,e,i,s,n){const o=Di(this._baseVb(),s/Math.max(1,n)),r=Math.min(Pp.ZOOM_MAX,Math.max(Pp.ZOOM_MIN,t)),a=o.w/r,l=o.h/r,c=e??o.x+o.w/2,h=i??o.y+o.h/2;return this._clampView({x:c-a/2,y:h-l/2,w:a,h:l},o)}_targetStageColor(t){return"view"!==t?"rgb(255, 255, 255)":this._cssColor(this._stageBg(this._spaceDisplayForRender()),this._cssColor("var(--ha-card-background, var(--card-background-color, #111))","rgb(17, 17, 17)"))}_targetPaperColor(t){return"view"===t&&this._spaceModel().bg?this._cssColor("var(--ha-card-background, var(--card-background-color, #111))","rgb(17, 17, 17)"):"rgb(255, 255, 255)"}_targetBrightness(t){return 1}_applyModeTransitionFrame(t){t.targetMode===this._mode&&(this._modeTransitionVisual=t.presented,this._view={...t.presented.viewport.viewBox},this.requestUpdate())}_settleModeTransition(t){if(t.targetMode!==this._mode)return;const e=this._modeTransitionRequest;this._view={...t.to.viewport.viewBox},this._zoom=this._modeTransitionTargetZoom,this._modeTransitionVisual=null,this._modeTransitionPreparing=!1,this._lastValidStageSize=[t.to.stageWidth,t.to.stageHeight],"view"===this._mode&&(this._saveZoom(),this._viewModeSnap=null,this._modeTransitionEditorCamera=null),this.requestUpdate(),this.updateComplete.then(()=>{if(!this.isConnected||e!==this._modeTransitionRequest||this._modeTransitionBusy||t.targetMode!==this._mode)return;const i=this._stageEl;if(i&&i.clientWidth>0&&i.clientHeight>0&&(Math.abs(i.clientWidth-t.to.stageWidth)>.5||Math.abs(i.clientHeight-t.to.stageHeight)>.5)){const t=this._view;this._lastValidStageSize=[i.clientWidth,i.clientHeight],this._applyView(this._zoom,t?t.x+t.w/2:void 0,t?t.y+t.h/2:void 0),this.requestUpdate()}const s=this.renderRoot.activeElement;(!s||!s.isConnected||!!s.closest?.(".editorchrome, .stage"))&&this.renderRoot.querySelector(".modetab.active")?.focus?.({preventScroll:!0})})}_cancelModeTransition(t=!0){const e=!!this._modeTransition.state;this._modeTransitionRequest++,this._modeTransitionPreparing=!1,this._modeTransitionForceAtomic=!1,this._modeTransition.cancel(t),t&&e||(this._modeTransitionVisual=null)}_adoptMode(t){this._cancelModeTransition(!1),this._mode=t,"view"!==t&&(this._editorChromeMode=t)}_commitViewModeAtomic(t,e,i,s){if(this._modeTransitionPreparing=!1,this._modeTransitionVisual=null,this._modeTransitionForceAtomic=!1,this._zoom=e,t){const n=this._stageEl?.clientWidth||t.stageWidth,o=Math.max(1,t.stageHeight+t.editorChromeHeight);this._view=this._viewForModeTarget(e,i,s,n,o),this._lastValidStageSize=[n,o]}else this._view=null;this._viewModeSnap=null,this._modeTransitionEditorCamera=null,this._saveZoom(),this.requestUpdate()}_prepareModeTransition(t,e,i,s,n,o){this.updateComplete.then(()=>{if(!this.isConnected||t!==this._modeTransitionRequest||this._mode!==i)return void(t===this._modeTransitionRequest&&(this._modeTransitionPreparing=!1,this._modeTransitionVisual=null,this._modeTransitionForceAtomic=!1,this.requestUpdate()));const r=this.renderRoot.querySelector(".editorchrome"),a=r?.querySelector(".editorchrome-inner"),l="view"===i?0:a?.scrollHeight||a?.getBoundingClientRect().height||0,c=Math.max(1,e.stageHeight+e.editorChromeHeight),h=Math.max(1,c-l),d=this._stageEl?.clientWidth||e.stageWidth;if(d<=0||h<=0)return this._modeTransitionPreparing=!1,this._modeTransitionVisual=null,this._applyView(s,n,o),void this.requestUpdate();const u=this._viewForModeTarget(s,n,o,d,h),p={presentedMode:i,editorChromeHeight:l,stageWidth:d,stageHeight:h,viewport:ou(u,d),stageColor:this._targetStageColor(i),paperColor:this._targetPaperColor(i),sceneBrightness:this._targetBrightness(i),architectureOpacity:"decor"===i?.35:1,backdropOpacity:"decor"===i&&"backdrop"!==this._decorTool?.5:1,viewWeight:"view"===i?1:0,editorWeight:"view"===i?0:1,toolbarContentOpacity:"view"===i?0:1};this._modeTransitionPreparing=!1;const _=this._modeTransitionForceAtomic;this._modeTransitionForceAtomic=!1,this._modeTransition.start(e,p,i,this._reducedMotion||_?0:220)})}_slideTo(t,e){if(t===this._space)return;this._cancelModeTransition(!0);const i=window.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches;this._activeDraftId&&(this._resumeDraftBySpace[this._space]=this._activeDraftId),this._space=t,this._path=[],this._clearPlanSnapHover(),this._clearOpeningPlacement(!0),this._tool="draw",this._openWallAnchor=null,this._boundaryRestoreGuard=null,this._activeDraftId=null,this._draftSegmentCms=[],this._closingWallCm=null,this._selId=null,this._physicalSel=null,this._editorSecondary.closeForNavigation(),this._physicalDialog=null,this._physicalDrag=null,"plan"===this._mode&&"draw"===this._tool&&this._resumeLastDraft(),this._restoreZoom(),i||(this._slide=e,clearTimeout(this._slideTimer),this._slideTimer=window.setTimeout(()=>{this._slideTimer=void 0,this._slide="",this.requestUpdate()},190),this.requestUpdate())}_pickSpace(t){if(t===this._space)return;const e=this._model.map(t=>t.id),i=e.indexOf(this._space),s=e.indexOf(t);this._navApplied=!0,this._showFar=!1,this._frame=null,this._slideTo(t,i>=0&&s0&&Date.now()>=this._cyclePausedUntil&&this._model.length>1&&this._zoom<=1.001){const t=this._model.map(t=>t.id),e=t.indexOf(this._space);this._slideTo(t[(e+1)%t.length],"left"),this._showKioskDots()}}get _editing(){return"plan"===this._mode||"devices"===this._mode||"decor"===this._mode}get _markup(){return"plan"===this._mode}_newContinuityController(){return new Hc(()=>{this._resumeSettling="steady"!==this._continuity.state,this._continuityEpoch++,this.isConnected&&this.requestUpdate()})}get _labsIso(){return this._labs.active.includes("iso")}get _desiredProjection(){return"view"===this._mode&&this._labsIso&&"iso"===this._viewPreference[this._space]?"iso":"flat"}_saveViewPreference(){try{localStorage.setItem(wp,JSON.stringify(this._viewPreference))}catch{}}_logicalViewCenter(t){const e=this._view;if(!e)return null;const i=[e.x+e.w/2,e.y+e.h/2],s="iso"===t?Iu(i):i;return{x:s[0],y:s[1]}}_convertProjectionView(t,e){if(t===e)return;const i=this._logicalViewCenter(t);this._view=null;const s=i?"iso"===e?Au([i.x,i.y],0):[i.x,i.y]:null;this._applyView(this._zoom,s?.[0],s?.[1]),this._warmPatch({vp:this._warmViewportState()}),this.requestUpdate()}_setProjection(t){if(!this._labsIso||"view"!==this._mode)return;const e=this._effectiveProjection();if(this._viewPreference={...this._viewPreference,[this._space]:t},"iso"===t){const t=this._isoSceneKey();t&&this._isoFallback.delete(t)}this._saveViewPreference();const i=this._effectiveProjection();this._convertProjectionView(e,i)}_hashSpace(){return vu(window.location.hash||"")}connectedCallback(){this._connectedPath=location.pathname,this._routeDepartureHandled=!1,window.addEventListener("location-changed",this._onLocationChanged),window.addEventListener("popstate",this._onLocationChanged),this._continuityDisposed&&(this._continuity=this._newContinuityController(),this._continuityDisposed=!1,this._continuityPaintToken=-1);const t=(e=this.ownerDocument,Jd.get(e)?.resolved);var e;void 0!==t&&(this._glowScreenBlend=t),this._continuityUnsub?.(),this._continuityUnsub=Nc(this.ownerDocument,this._pageVisibility),super.connectedCallback(),this._motionMedia=window.matchMedia?.("(prefers-reduced-motion: reduce)"),this._reducedMotion=!!this._motionMedia?.matches,this._motionMedia?.addEventListener?.("change",this._onMotionChange),function(t){const e=Jd.get(t);if(e)return e.promise;const i={promise:Promise.resolve(!1)};return i.promise=tu(t).catch(()=>!1).then(t=>(i.resolved=t,t)),Jd.set(t,i),i.promise}(this.ownerDocument).then(t=>{t!==this._glowScreenBlend&&(this._glowScreenBlend=t,this.isConnected&&this.requestUpdate())}),this.hass&&this._ensureHaRegistryAuthority(),window.addEventListener("keydown",this._keyHandler),this._signer.start(()=>this.hass,()=>$s(this._serverCfg)),this._config?.kiosk&&Number(this._config?.cycle)>0&&(clearInterval(this._cycleTimer),this._cycleTimer=window.setInterval(()=>this._cycleTick(),1e3*Number(this._config.cycle))),window.addEventListener("hashchange",this._onHashChange),this._labsUnsub?.(),this._labsUnsub=function(t,e){Su.add(e);const i=Du(t);return"undefined"==typeof window&&e(i),()=>{Su.delete(e)}}(sp,this._onLabsSnapshot),this._booting?this._bootWatch():this._bootFading&&(clearTimeout(this._bootTimer),this._bootTimer=window.setTimeout(()=>{this._bootFading=!1},220)),this._bootSoft&&(clearTimeout(this._bootSoftTimer),this._bootSoftTimer=window.setTimeout(()=>{this._bootSoft=!1},lp)),!this._loadOk&&this._serverCfg&&this.hass&&this._scheduleLoadRetry(),!this._warmSlot&&this._config&&this._warmAdopt(),this._loadOk&&this._ensureLiveSyncSubscriptions(),this._warmVp&&!this._warmRevivePending&&void 0===this._warmReviveTimer&&(this._warmRevivePending=!0,this._warmReviveTimer=window.setTimeout(()=>this._warmReviveDialog(),0)),this._warmLongReturn&&this._beginResumeSettle(),this._warmLongReturn=!1,this.requestUpdate()}disconnectedCallback(){this._connectedPath&&location.pathname!==this._connectedPath&&this._leaveCardRoute(),window.removeEventListener("location-changed",this._onLocationChanged),window.removeEventListener("popstate",this._onLocationChanged),this._continuityUnsub?.(),this._continuityUnsub=void 0,this._motionMedia?.removeEventListener?.("change",this._onMotionChange),this._motionMedia=void 0,this._vacRaf&&(cancelAnimationFrame(this._vacRaf),this._vacRaf=0),this._refitRaf&&(cancelAnimationFrame(this._refitRaf),this._refitRaf=0),this._dayCycleTimer&&(clearInterval(this._dayCycleTimer),this._dayCycleTimer=0),this._dayCycleClockKey="",this._bootSettleRaf&&(cancelAnimationFrame(this._bootSettleRaf),this._bootSettleRaf=0),this._bootSettling=!1;for(const t of this._activityRt.values())clearTimeout(t.timer);window.removeEventListener("keydown",this._keyHandler),clearInterval(this._cycleTimer),clearTimeout(this._kioskDotsTimer),clearTimeout(this._kioskHoldTimer),clearTimeout(this._reloadRetry),clearTimeout(this._loadRetryTimer),this._loadRetryTimer=void 0,this._connHooked?.removeEventListener?.("ready",this._onConnReady),this._connHooked?.removeEventListener?.("disconnected",this._onConnLost),this._connHooked?.removeEventListener?.("reconnect-error",this._onConnLost),this._connHooked=null,this._haRegistryRelease?.(),this._haRegistryRelease=void 0,this._haRegistryConnection=null,this._signer.dispose(),clearTimeout(this._toastTimer),clearTimeout(this._slideTimer),this._modeTransition.dispose(),this._modeTransitionVisual=null,this._modeTransitionPreparing=!1,this._modeTransitionForceAtomic=!1,this._modeTransitionRequest++,this._slideTimer=void 0,this._slide="",clearTimeout(this._bootTimer),this._bootTimer=void 0,clearTimeout(this._bootSoftTimer),this._saveConfigDebounced.flush(),window.removeEventListener("hashchange",this._onHashChange),this._labsUnsub?.(),this._labsUnsub=void 0,clearTimeout(this._holdTimer),this._roViewport?.disconnect(),this._roViewport=void 0,this._roHdr?.disconnect(),this._roHdr=void 0,this._onWinResize&&(window.removeEventListener("resize",this._onWinResize),this._onWinResize=void 0),this._unsubCfg&&(this._unsubCfg(),this._unsubCfg=null),this._unsubLayout&&(this._unsubLayout(),this._unsubLayout=null),this._unsubVirtual&&(this._unsubVirtual(),this._unsubVirtual=null),this._unsubTrail&&(this._unsubTrail(),this._unsubTrail=void 0),this._liveSyncGeneration++,this._liveSyncAttempt=null,this._liveSyncConnection=null,clearTimeout(this._layoutSyncTimer),clearTimeout(this._duplicateColumnTimer);for(const t of this._glowFadeTimers.values())clearTimeout(t);for(const t of this._glowEnterRafs.values())cancelAnimationFrame(t);clearTimeout(this._glowFeatherResumeTimer),this._glowFeatherResumeTimer=0,this._glowFeatherSuspendUntil=0,this._glowFadeTimers.clear(),this._glowEnterRafs.clear(),this._glowEnteringSources.clear(),this._glowRenderedSources.clear(),this._glowLastAppearance.clear(),this._glowSourceSeq=0,this._warmSnapshot(),this._warmRevivePending=!1,clearTimeout(this._warmReviveTimer),this._warmReviveTimer=void 0,this._warmRelease(),this._openWallAnchor=null,this._boundaryRestoreGuard=null,this._clearPlanSnapHover(),this._clearOpeningPlacement(!0),this._touchContacts.clear(),this._touchSequenceMultitouch=!1,this._touchClickBlockUntil=0,this._editorSecondary.reset(),this._resumeSettling=!1,this._continuityHistory=[...this._continuityHistory,...this._continuity.trace].slice(-80),this._continuity.dispose(),this._continuityDisposed=!0,super.disconnectedCallback()}_onKey(t){if("Escape"===t.key&&this._vacFit)return this._vacFit=null,this._showToast(this._t("vac.cal_cancelled")),void t.stopPropagation();if("Escape"===t.key){if(this._tapConfirm)return void(this._tapConfirm=null);if(this._vacCalConfirm)return void(this._vacCalConfirm=null);if(this._decorEraseConfirm)return void(this._decorEraseConfirm=null);if(this._openingInfo)return void(this._openingInfo=null);if(this._infoCard)return void this._closeInfoCard();if(this._rulesDialog)return void(this._rulesDialog=null);if(this._alignDialog)return void(this._alignDialog=null);if(this._backupImportDialog)return void(this._backupImportDialog=null);if(this._backupExportDialog)return void(this._backupExportDialog=null);if(this._settingsDialog)return void(this._settingsDialog=null);if(this._markerDialog)return void(this._markerDialog=null);if(this._openingDialog)return void(this._openingDialog=null);if(this._physicalDialog)return void(this._physicalDialog=null);if(this._backdropDialog)return void(this._backdropDialog=null);if(this._decorShapeDialog)return void(this._decorShapeDialog=null);if(this._decorTextDialog)return void(this._decorTextDialog=null);if(this._spaceDialog&&!this._roomDialog)return this._spaceDialog=null,this._importQueue=[],void(this._importTotal=0);if(this._editorSecondary.hasOpenGroup)return t.preventDefault(),void this._editorSecondary.closeGroup(!0)}const e=t.composedPath?.()||[t.target],i=e.some(t=>t?.matches?.('input, textarea, select, [contenteditable="true"]')),s=e.some(t=>t?.classList?.contains?.("editor-secondary")),n=t.ctrlKey||t.metaKey,o=t.key.toLowerCase(),r=/^[a-z]$/.test(o),a="z"===o||!r&&"KeyZ"===t.code,l="y"===o||!r&&"KeyY"===t.code,c=n&&(a&&t.shiftKey||l),h=n&&a&&!t.shiftKey;if("decor"===this._mode){if((h||c)&&i)return;return c?(t.preventDefault(),void this._redoGeometry()):h?(t.preventDefault(),this._decorDraft?void(this._decorDraft=null):this._decorMove||this._dtDrag||this._bdDrag?void this._cancelDecorGesture():void this._undoGeometry()):"Delete"!==t.key&&"Backspace"!==t.key||!this._decorSel||i||s?void("Escape"===t.key&&(t.preventDefault(),this._decorDraft?this._decorDraft=null:this._decorMove||this._dtDrag||this._bdDrag?this._cancelDecorGesture():"furniture"===this._decorTool?(this._furnPalette=null,this._decorTool="select"):this._decorSel?this._decorSel=null:"select"!==this._decorTool?this._decorTool="select":this._setMode("view"))):(t.preventDefault(),void this._decorDeleteSel())}if(this._markup&&(!h&&!c||!i)){if(("Delete"===t.key||"Backspace"===t.key)&&this._physicalSel&&!i&&!s)return t.preventDefault(),void this._deletePhysicalSelection();if(c)return t.preventDefault(),void this._redoGeometry();if(h)return t.preventDefault(),this._rszDrag?void this._rszCancelDrag():"draw"!==this._tool&&"partition"!==this._tool||!this._path.length?"split"===this._tool&&this._splitSel?.pts?.length?(this._splitSel={...this._splitSel,pts:this._splitSel.pts.slice(0,-1)},void(this._splitSel.pts.length||(this._cursorPt=null))):void this._undoGeometry():void(this._activeDraftId&&this._path.length>1?this._undoGeometry():this._undoPoint());if("Escape"===t.key)return this._physicalDrag||this._physicalRotate?(t.preventDefault(),void this._cancelPhysicalGesture()):this._roomDialog?(t.preventDefault(),void this._roomDialogCancel()):"draw"===this._tool&&this._path.length||"partition"===this._tool&&this._path.length?(t.preventDefault(),void this._undoPoint()):this._physicalSel?(t.preventDefault(),void(this._physicalSel=null)):"resize"===this._tool?(t.preventDefault(),this._rszDrag?void this._rszCancelDrag():void(this._rszSel?this._rszSel=null:this._tool="draw")):"split"===this._tool?(t.preventDefault(),void(this._splitSel?.pts?.length?(this._splitSel={...this._splitSel,pts:this._splitSel.pts.slice(0,-1)},this._splitSel.pts.length||(this._cursorPt=null)):this._splitSel?this._splitSel=null:this._tool="draw")):"merge"===this._tool?(t.preventDefault(),void(this._mergeSel?this._mergeSel=null:this._tool="draw")):this._wallDialog?(t.preventDefault(),void(this._wallDialog=null)):"boundary"===this._tool?(t.preventDefault(),void(this._openWallAnchor?this._cancelBoundaryAnchor():this._tool="draw")):void("opening"!==this._tool&&"wallthick"!==this._tool&&"delroom"!==this._tool&&"partition"!==this._tool&&"column"!==this._tool||(t.preventDefault(),"opening"===this._tool&&this._clearOpeningPlacement(!0),this._tool="draw"))}}_undoPoint(){if(this._path.length){if(this._contourClosed)return this._path=this._path.slice(0,-1),void(this._closingWallCm=null);if(this._activeDraftId&&this._path.length>1&&this._curSpaceCfg){const t=this._geometrySnapshot();this._path=this._path.slice(0,-1),this._draftSegmentCms=this._draftSegmentCms.slice(0,-1);const e=this._curSpaceCfg,i=(e.room_drafts||[]).findIndex(t=>t.id===this._activeDraftId);return void(i>=0&&(this._path.length<2?(e.room_drafts.splice(i,1),e.room_drafts.length||delete e.room_drafts,this._activeDraftId=null):e.room_drafts[i]={id:this._activeDraftId,points:this._path.map(t=>[t[0]/kp,t[1]/kp]),segments:this._draftSegmentCms.map(t=>({cm:t}))},this._recordGeometry(this._t("history.draft_segment_delete"),t),this._saveConfig()))}this._path=this._path.slice(0,-1)}}static getConfigElement(){return document.createElement("houseplan-card-editor")}static getStubConfig(){return{type:"custom:houseplan-card"}}static _warmBootReset(t){for(const t of up.values())for(const e of t)clearTimeout(e.evict);up.clear(),mp=t&&t>0?t:1e4}static _warmBootStats(){let t=0,e=0;const i=[];for(const s of up.values())for(const n of s)t++,n.dlg&&(e++,i.push(n.dlg.kind));return{keys:up.size,slots:t,dlgs:e,drafts:i}}setConfig(t){this._config={icon_size:2.5,show_temperature:!0,live_states:!0,show_signal:!0,...t},this._config.kiosk&&(this._booting=!1,this._bootFading=!1),t.default_floor&&(this._space=t.default_floor);try{this._zoomBySpace=JSON.parse(localStorage.getItem(vp)||"{}")||{}}catch{this._zoomBySpace={}}try{const t=JSON.parse(localStorage.getItem(wp)||"{}")||{};this._viewPreference=Object.fromEntries(Object.entries(t).filter(t=>"flat"===t[1]||"iso"===t[1]))}catch{this._viewPreference={}}this._labs=Du(sp);try{const t=JSON.parse(localStorage.getItem(bp)||"null");this._kioskScale={icon:Cs(t?.icon),font:Cs(t?.font)}}catch{}try{const t=JSON.parse(localStorage.getItem(fp)||"null");t&&t.config&&Array.isArray(t.config.spaces)&&(this._serverCfg=t.config,this._cfgEpoch++,this._cfgRev=t.rev||0,this._cfgContentFingerprint=t.config_fingerprint||qc(t.config),this._layout=t.layout||{},this._layoutRev=t.layout_rev||0,this._layoutContentFingerprint=t.layout_fingerprint||qc(this._layout),this._virtualLights=rn(t.virtual_lights,this._cfgRev),this._serverStorage=!0,this._adoptInitialSpace(this._model))}catch{}"view"!==this._mode||this._view||(this._zoom=this._zoomBySpace[this._space]||1),this.isConnected&&(this._warmAdopt(),this._warmLongReturn&&this._beginResumeSettle(),this._warmLongReturn=!1)}_warmAdopt(){if(this._config?.kiosk)return;const t=_p(this._config);if(this._warmKey===t&&this._warmSlot)return;this._warmSlot&&this._warmRelease();const e=this.parentNode,i=this._warmIdx(e),s=up.get(t);if(!s||!s.length)return;const n=s.find(t=>t.owner===this._warmGen);if(n)return this._warmLongReturn=!!n.freed&&Date.now()-n.freed>=zc,clearTimeout(n.evict),n.evict=0,n.freed=0,n.live=!0,this._warmSlot=n,this._warmKey=t,n.frameFingerprint&&this._continuity.adoptCompleteFrame(n.frameFingerprint),void(!this._devices.length&&n.devices?.length&&(this._devices=[...n.devices]));const{slot:o,sure:r}=((t,e,i,s)=>{const n=t=>{const e=!!i&&t.place?.deref()===i;return e&&t.idx===s?4:t.live?0:e?3:2};let o=null,r=0,a=0,l=null;for(const i of t){if(i.owner===e)continue;l=i;const t=n(i);t<=0||(t>r?(o=i,r=t,a=1):t===r&&a++)}return!o||a>1?{slot:o||l,sure:!1}:{slot:o,sure:!0}})(s,this._warmGen,e,i);o&&(this._warmLongReturn=!!o.freed&&Date.now()-o.freed>=zc,this._booting=!1,this._bootFading=!1,this._hdrH=o.hdrH,this._bootSoft=!0,this.isConnected&&(clearTimeout(this._bootSoftTimer),this._bootSoftTimer=window.setTimeout(()=>{this._bootSoft=!1},lp)),this._warmKey=t,r?(clearTimeout(o.evict),o.evict=0,o.owner=this._warmGen,o.place=e?new WeakRef(e):null,o.idx=i,o.live=!0,this._warmSlot=o,this._warmVp=o.vp,o.frameFingerprint&&this._continuity.adoptCompleteFrame(o.frameFingerprint),!this._devices.length&&o.devices?.length&&(this._devices=[...o.devices]),this._warmAdoptViewport(this._config)):(this._warmSlot={owner:this._warmGen,path:location.pathname,place:e?new WeakRef(e):null,idx:i,live:!0,hdrH:o.hdrH,stageH:o.stageH,vp:null,frameFingerprint:"",devices:null,dlg:null,freed:0,evict:0},s.push(this._warmSlot),this._warmTrim(s)))}_warmIdx(t){const e=t?.children;if(!e)return-1;for(let t=0;t4;){const e=t.findIndex(t=>!t.live);if(e<0)break;clearTimeout(t[e].evict),t.splice(e,1)}}_warmScheduleEvict(t,e){if(clearTimeout(t.evict),!(t.dlg||t.vp&&"view"!==t.vp.mode))return;const i=t.freed,s=t.owner;t.evict=window.setTimeout(()=>{if(t.evict=0,t.freed!==i||t.owner!==s)return;var n;t.dlg=null,t.vp=(n=t.vp)&&"view"!==n.mode?{...n,mode:"view",zoom:n.snap?.space===n.space?n.snap.zoom:n.zoom,view:null,snap:null,tool:"draw",decorTool:"select",showHidden:!1,selId:null,rszSel:null,decorSel:null}:n,t.frameFingerprint="";const o=up.get(e);if(!t.live&&o&&o.length>1){const e=o.indexOf(t);e>=0&&o.splice(e,1)}},mp+250)}_warmAdoptViewport(t){const e=this._warmVp;if(!e||this._warmSlot?.path!==location.pathname)return void(this._warmVp=null);if(this._hashApplied||!this._model.find(t=>t.id===e.space))return void(this._warmVp=null);this._space=e.space,this._navApplied=!0,this._adoptMode("view"!==e.mode&&this._canEdit&&!t.kiosk?e.mode:"view"),this._pendingNavMode="view"===e.mode||this._canEdit||t.kiosk?null:e.mode,this._zoom=e.zoom;const i=this._effectiveProjection(),s=i===e.projection&&this._labsIso===e.activeLabsIso;if(this._view=s&&e.view?{...e.view}:null,this._viewModeSnap=s&&e.snap?{...e.snap}:null,!s&&e.logicalCenter){const t="iso"===i?Au([e.logicalCenter.x,e.logicalCenter.y],0):[e.logicalCenter.x,e.logicalCenter.y];this._applyView(e.zoom,t[0],t[1])}var n;this._tool="openwall"===(n=e.tool)||"closewall"===n?"boundary":"opening"===n?"draw":"string"==typeof n&&Sp.has(n)?n:"draw",this._decorTool=e.decorTool,this._showHidden=e.showHidden,this._showFar!==e.showFar&&(this._showFar=e.showFar,this._frame=null),this._selId=e.selId,this._rszSel=e.rszSel,this._decorSel=e.decorSel,this._warmVpArmed=!0}_warmPatch(t,e=!1){if(this._config?.kiosk)return;const i=_p(this._config);if(!this._warmSlot||this._warmKey===i){if(!this._warmSlot){if(!e)return;const t=this.parentNode;this._warmKey=i,this._warmSlot={owner:this._warmGen,path:location.pathname,place:t?new WeakRef(t):null,idx:this._warmIdx(t),live:!0,hdrH:this._hdrH,stageH:0,vp:null,frameFingerprint:"",devices:null,dlg:null,freed:0,evict:0};const s=up.get(i)||[];for(s.push(this._warmSlot),up.set(i,s),this._warmTrim(s);up.size>8;){const t=up.keys().next().value;if(void 0===t||t===i)break;for(const e of up.get(t)||[])clearTimeout(e.evict);up.delete(t)}}Object.assign(this._warmSlot,t)}}_warmViewportState(){const t=this._effectiveProjection();return{space:this._space,mode:this._mode,projection:t,activeLabsIso:this._labsIso,logicalCenter:this._logicalViewCenter(t),zoom:this._zoom,view:this._view?{...this._view}:null,snap:this._viewModeSnap?{...this._viewModeSnap}:null,tool:this._tool,decorTool:this._decorTool,showHidden:this._showHidden,showFar:this._showFar,selId:this._selId,rszSel:this._rszSel,decorSel:this._decorSel}}_warmDialogState(){const t=(t,e)=>({kind:t,space:this._space,mode:this._mode,data:e});return this._tapConfirm||this._alignDialog||this._mergeDialog||this._importDialog||this._backupExportDialog||this._backupImportDialog?null:this._openingInfo?t("openingInfo",this._openingInfo.id):this._infoCard?t("info",this._infoCard.id):this._rulesDialog?this._rulesDialog.busy?null:t("rules",this._rulesDialog):this._settingsDialog?this._settingsDialog.busy?null:t("settings",this._settingsDialog):this._markerDialog?this._markerDialog.busy?null:t("marker",this._markerDialog):this._openingDialog?t("opening",this._openingDialog):this._backdropDialog?t("backdrop",this._backdropDialog):this._decorShapeDialog?t("decorShape",this._decorShapeDialog):this._decorTextDialog?t("decorText",this._decorTextDialog):this._roomDialog?t("room",{editId:this._roomEditId,fill:this._roomFill,customFill:this._roomCustomFill,tempSrc:this._roomTempSrc,humSrc:this._roomHumSrc,srcOpen:this._roomSrcOpen,srcFilter:this._roomSrcFilter,nameScale:this._roomNameScale,labelScale:this._roomLabelScale,areaSel:this._areaSel,nameSel:this._nameSel,pendingSplit:this._pendingSplit,path:this._path}):this._spaceDialog?this._spaceDialog.busy?null:t("space",this._spaceDialog):null}_warmSnapshot(){if(this._booting||this._config?.kiosk||"steady"!==this._continuity.state)return;const t={vp:this._warmViewportState(),frameFingerprint:this._continuity.frameFingerprint,devices:this._devices};if(this._warmRevivePending||(t.dlg=this._warmDialogState()),this.isConnected&&this._warmSlot?.owner===this._warmGen){const e=this.parentNode;t.place=e?new WeakRef(e):null,t.idx=this._warmIdx(e)}this._warmPatch(t)}_warmReviveDialog(){this._warmRevivePending=!1;const t=this._warmSlot;if(this._warmReviveTimer=void 0,!t||!t.dlg)return;const e=t.dlg,i=t.freed;if(t.dlg=null,t.freed=0,clearTimeout(t.evict),t.evict=0,i&&!(Date.now()-i>mp)&&e.space===this._space&&e.mode===this._mode){switch(e.kind){case"space":this._spaceDialog={...e.data,busy:!1,savedBusy:!1};break;case"marker":this._markerDialog={...e.data,busy:!1};break;case"settings":this._settingsDialog={...e.data,busy:!1};break;case"rules":this._rulesDialog={...e.data,busy:!1};break;case"opening":this._openingDialog={...e.data};break;case"backdrop":this._backdropDialog={...e.data};break;case"decorShape":this._decorShapeDialog={...e.data};break;case"decorText":{this._decorTextDialog={...e.data};const t=String(this._decorTextDialog?.text??"").length;this._decorTextSelection={start:t,end:t};break}case"room":{const t=e.data;this._roomEditId=t.editId,this._roomFill=t.fill,this._roomCustomFill=t.customFill||null,this._roomTempSrc=t.tempSrc,this._roomHumSrc=t.humSrc,this._roomSrcOpen=t.srcOpen,this._roomSrcFilter=t.srcFilter,this._roomNameScale=t.nameScale,this._roomLabelScale=t.labelScale,this._areaSel=t.areaSel,this._nameSel=t.nameSel,this._pendingSplit=t.pendingSplit,this._path=t.path,this._roomDialog=!0;break}case"info":{const t=this._devices.find(t=>t.id===e.data);t&&(this._infoCard=t);break}case"openingInfo":{const t=(this._curSpaceCfg?.openings||[]).find(t=>t.id===e.data);t&&(this._openingInfo=t);break}}this.requestUpdate()}}_cacheSnapshot(){if(this._serverCfg)try{this._cfgContentFingerprint=qc(this._serverCfg),this._layoutContentFingerprint=qc(this._layout),this._virtualLights=function(t,e,i){if(t.configRev===i)return t;const s=new Set((Array.isArray(e?.markers)?e.markers:[]).filter(hn).map(t=>t.id));return{...t,configRev:i,off:new Set([...t.off].filter(t=>s.has(t)))}}(this._virtualLights,this._serverCfg,this._cfgRev),localStorage.setItem(fp,JSON.stringify({config:this._serverCfg,rev:this._cfgRev,config_fingerprint:this._cfgContentFingerprint,layout:this._layout,layout_rev:this._layoutRev,layout_fingerprint:this._layoutContentFingerprint,virtual_lights:ln(this._virtualLights)}))}catch{}}_beginContinuityCandidate(t,e,i="plan"){return this._booting&&!this._continuity.hasCompleteFrame?this._continuity.token:(this._continuityDataReady=e,this._continuityPaintToken=-1,this._stagedDeviceSnapshotToken=-1,this._resumeSettling=!0,this._continuity.beginCandidate(t,i))}_continuityStageValid(){const t=this._stageEl;return!!t&&t.clientWidth>0&&t.clientHeight>0}_continuityAssetsReady(){if(!this._model.length)return!1;const t=this._model.length?this._spaceModel():null;return!t?.bg?.href||this._signer.isReady(this.hass,t.bg.href)}_initialSpaceSelection(t){return function(t){const e=new Set(t.spaceIds.filter(t=>!!t));if(!e.size)return{id:null,source:"none"};const i=[["hash",!1===t.acceptHash?null:t.hashSpace],["current",t.preserveCurrent?t.currentSpace:null],["saved",t.savedSpace],["default",t.defaultSpace],["first",t.spaceIds[0]]];for(const[t,s]of i)if(s&&e.has(s))return{id:s,source:t};return{id:null,source:"none"}}({spaceIds:t.map(t=>t.id),hashSpace:this._hashSpace(),acceptHash:!this._hashApplied,currentSpace:this._space,preserveCurrent:this._hashApplied||this._navApplied||this._warmVpArmed,savedSpace:this._savedNav()?.space,defaultSpace:this._config?.default_floor})}_adoptInitialSpace(t){const e=this._initialSpaceSelection(t);return e.id?(this._space=e.id,"hash"===e.source&&(this._hashApplied=!0),"saved"===e.source&&(this._navApplied=!0),e):e}_candidateBackdrop(t,e=this._space){const i=Al(t),s=this._initialSpaceSelection(i).id||(i.some(t=>t.id===e)?e:i[0]?.id);return i.find(t=>t.id===s)?.bg?.href||""}_visualFrameFingerprint(){const t=this._stageEl,e=t?[t.clientWidth,t.clientHeight]:[0,0];return Bc([this._cfgRev,this._cfgContentFingerprint||qc(this._serverCfg),this._layoutRev,this._layoutContentFingerprint||qc(this._layout),this._space,this._mode,this._view,e,this._glowScreenBlend?"screen":"normal",this.hass?.themes?.darkMode??this.hass?.themes?.default_theme??""])}_settleContinuityFrame(){if(this._booting||!this._continuityStageValid())return;if(!this._continuity.hasCompleteFrame&&"steady"===this._continuity.state)return void(this._continuityAssetsReady()&&(this._renderSnapshotAt=Date.now(),this._continuity.markCompleteFrame(this._visualFrameFingerprint())));if(!this._continuityDataReady)return;if(!["holding","offline-stale","overlay-pending","overlay-visible","candidate-ready"].includes(this._continuity.state))return;const t=this._continuity.token;if(this._candidateDeviceSnapshot&&this._candidateDeviceSnapshot!==this._visibleDeviceSnapshot&&this._stagedDeviceSnapshotToken!==t)return this._stagedDeviceSnapshotToken=t,void this.requestUpdate();this._continuityPaintToken!==t&&(this._continuityPaintToken=t,this._continuity.candidateReady(t)&&this._continuity.commitAfterPaint(t,{updateComplete:()=>this.updateComplete,stageValid:()=>this.isConnected&&this._continuityStageValid(),assetsReady:()=>this._continuityAssetsReady(),frameFingerprint:()=>this._visualFrameFingerprint()}).then(e=>{e&&t===this._continuity.token?(this._resumeSettling=!1,this._renderSnapshotAt=Date.now(),this._candidateDeviceSnapshot&&(this._visibleDeviceSnapshot=this._candidateDeviceSnapshot),this._candidateDeviceSnapshot=null,this._stagedDeviceSnapshotToken=-1,this._warmSnapshot()):t===this._continuity.token&&(this._continuityPaintToken=-1,this._stagedDeviceSnapshotToken=-1,this._candidateDeviceSnapshot=null,this.requestUpdate())}))}_onBackdropLoaded(t,e){this._signer.markLoaded(this.hass,t,e),this._continuity.note("asset-ready"),this._continuityPaintToken=-1,"steady"!==this._continuity.state&&this.requestUpdate()}_renderRecoveryOverlay(){if(!this._continuity.overlayVisible&&"recovery-error"!==this._continuity.state)return G;const t="connection"===this._continuity.recoveryReason;return j`
`}const Xu={window:120,door:90,gate:300};function Zu(t){return Xu[t]}function Ju(t,e){const i=[t[0],t[1]],s=[e[0],e[1]];return function(t,e){return t[0]-e[0]||t[1]-e[1]}(i,s)<=0?{a:i,b:s}:{a:s,b:i}}function Qu(t){const e=new Map;return t.forEach((t,i)=>{if(!t.kind||t.open)return;const s=Ju(t.a,t.b);if(Math.hypot(s.b[0]-s.a[0],s.b[1]-s.a[1])<=1e-9)return;const n=function(t,e){const i=t=>(Math.abs(t)<=5e-10?0:Math.round(1e6*t)/1e6).toFixed(6);return`${i(t[0])},${i(t[1])}>${i(e[0])},${i(e[1])}`}(s.a,s.b),o=e.get(n);if(o)return o.physicalHalfWidth=Math.max(o.physicalHalfWidth,t.half||0),void(o.sourceOrder=Math.min(o.sourceOrder,i));e.set(n,{segmentKey:n,a:s.a,b:s.b,physicalHalfWidth:Math.max(0,t.half||0),sourceOrder:i})}),[...e.values()]}function tp(t,e){if(Math.abs(t.distance-e.distance)>1e-9)return t.distance-e.distance;if(Math.abs(t.perpendicular-e.perpendicular)>1e-9)return t.perpendicular-e.perpendicular;return(t.target.segmentKeye.target.segmentKey?1:0)||t.target.sourceOrder-e.target.sourceOrder}function ep(t){const e=Qu(t.intervals).map(e=>{const i=function(t,e){const i=e.b[0]-e.a[0],s=e.b[1]-e.a[1],n=Math.hypot(i,s),o=i/n,r=s/n,a=(t[0]-e.a[0])*o+(t[1]-e.a[1])*r,l=Math.max(0,Math.min(n,a)),c=e.a[0]+l*o,h=e.a[1]+l*r;return{along:l,length:n,x:c,y:h,distance:Math.hypot(t[0]-c,t[1]-h),perpendicular:Math.abs((t[0]-e.a[0])*r-(t[1]-e.a[1])*o)}}(t.pointer,e);return{target:e,...i,envelope:Math.max(t.baseTolerance,e.physicalHalfWidth+t.bodyPointerPadding)}}).filter(t=>t.distance<=t.envelope+1e-9).filter(e=>!function(t,e,i,s,n){const o=e.b[0]-e.a[0],r=e.b[1]-e.a[1],a=Math.hypot(o,r);if(!(a>1e-9))return!1;const l=o/a,c=r/a;for(const o of i){if(!o.open&&o.kind)continue;const i=Ju(o.a,o.b),r=i.b[0]-i.a[0],a=i.b[1]-i.a[1],h=Math.hypot(r,a);if(!(h>1e-9))continue;const d=r/h,u=a/h;if(Math.abs(l*u-c*d)>1e-6)continue;if(Math.abs((i.a[0]-e.a[0])*c-(i.a[1]-e.a[1])*l)>n)continue;const p=(t[0]-i.a[0])*d+(t[1]-i.a[1])*u,_=1e-9;if(!(p<=_||p>=h-_)&&Math.abs((t[0]-i.a[0])*u-(t[1]-i.a[1])*d)<=s+1e-9)return!0}return!1}(t.pointer,e.target,t.intervals,e.envelope,Math.max(1e-9,Math.min(t.baseTolerance,.04*t.gridStep)))).sort(tp),i=e[0];if(!i)return null;const{target:s,length:n}=i,o=s.b[0]-s.a[0],r=s.b[1]-s.a[1],a=o/n,l=r/n,c=Math.min(Math.max(0,t.renderedLength)/2,n/2);let h=i.along;const d=Math.max(t.gridStep,1e-9),u=n/2;h=Math.abs(h-u)<=d/2?u:Math.round(h/d)*d,h=Math.max(c,Math.min(n-c,h));const p=s.a[0]+a*h,_=s.a[1]+l*h,m=Math.max(0,t.renderedLength)/2,g=h-m,f=h+m,v=Math.max(0,g),y=Math.max(0,n-f),b=[s.a[0]+a*(g-v/2),s.a[1]+l*(g-v/2)],w=[s.a[0]+a*(f+y/2),s.a[1]+l*(f+y/2)];let k=180*Math.atan2(r,o)/Math.PI;k>=90?k-=180:k<-90&&(k+=180);const $=Math.abs(h-u)<=1e-9;return{presetRevision:t.preset.revision,geometryRevision:t.geometryRevision,pointer:[t.pointer[0],t.pointer[1]],type:t.preset.type,lengthCm:t.preset.lengthCm,flipH:t.preset.flipH,flipV:t.preset.flipV,x:p,y:_,angle:k,renderedLength:t.renderedLength,target:s,measure:{labels:[{distance:v,midpoint:b},{distance:y,midpoint:w}],guide:$?{x:p,y:_,angle:k}:null}}}function ip(t,e,i,s,n=1e-6){return t.presetRevision===i&&t.geometryRevision===s&&Math.hypot(t.pointer[0]-e[0],t.pointer[1]-e[1])<=n}const sp="1.64.0",np={badge:"display.badge",icon_ripple:"display.icon_ripple",value:"display.value",static_icon:"display.static_icon"},op={badge:"marker.display_hint_badge",icon_ripple:"marker.display_hint_icon_ripple",value:"marker.display_hint_value",static_icon:"marker.display_hint_static_icon"},rp=2e3,ap=1e3,lp=1500,cp=t=>{const e=String(t??"").trim().replace(",",".");if(!/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)$/.test(e))return null;const i=Number(e);return Number.isFinite(i)?i:null},hp=(t,e)=>{if(!t.has(e))return{hit:!1};const i=t.get(e);return t.delete(e),t.set(e,i),{hit:!0,value:i}},dp=(t,e,i,s)=>{for(t.delete(e),t.set(e,i);t.size>s;){const e=t.keys().next().value;if(void 0===e)break;t.delete(e)}},up=new Map;let pp=0;const _p=t=>`${window.innerWidth}x${window.innerHeight}|${location.pathname}|${JSON.stringify(t??{})}`;let mp=1e4;const gp="houseplan_card_layout_v1",fp="houseplan_card_cfg_v1",vp="houseplan_card_zoom_v1",yp="houseplan_card_nav_v1",bp="houseplan_card_kiosk_v1",wp="houseplan_card_view_v1",kp=1e3,$p=[[0,1],[45,.88],[70,.62],[86,.32],[100,0]],xp=(t,e)=>{const i=Math.min(t.x,e.x),s=Math.min(t.y,e.y);return{x:i,y:s,w:Math.max(t.x+t.w,e.x+e.w)-i,h:Math.max(t.y+t.h,e.y+e.h)-s}},Sp=new Set(["select","draw","partition","column","merge","split","resize","opening","boundary","wallthick","delroom"]),Mp=(t,e,i)=>{const s=new Event(e,{bubbles:!0,composed:!0});s.detail=i??{},t.dispatchEvent(s)},Cp=t=>{history.pushState(null,"",t),Mp(window,"location-changed",{replace:!1})},Dp=(t,e)=>{let i,s=null;const n=(...n)=>{clearTimeout(i),s=n,i=window.setTimeout(()=>{i=void 0;const e=s;s=null,e&&t(...e)},e)};return n.flush=()=>{if(void 0===i)return;clearTimeout(i),i=void 0;const e=s;s=null,e&&t(...e)},n.pending=()=>void 0!==i,n},Tp=t=>{try{t.target?.setPointerCapture?.(t.pointerId)}catch{}};class Pp extends ht{constructor(){super(...arguments),this._space="f1",this._layout={},this._serverStorage=!1,this._loadOk=!1,this._serverCanWrite=null,this._loading=!1,this._loadTries=0,this._serverCfg=null,this._cfgRev=0,this._cfgContentFingerprint="",this._unsubCfg=null,this._unsubLayout=null,this._unsubVirtual=null,this._liveSyncAttempt=null,this._liveSyncGeneration=0,this._liveSyncConnection=null,this._layoutRev=0,this._layoutContentFingerprint="",this._virtualLights=rn(null),this._canOptimizeUndo=!1,this._undoKind=null,this._devices=[],this._regSignature="",this._defPos={},this._newSyncKey="",this._tip=null,this._hoverRoom=null,this._selId=null,this._toast="",this._mode="view",this._pendingNavMode=null,this._decorTool="select",this._decorStyle={...jr},this._decorDraft=null,this._decorMove=null,this._decorSel=null,this._decorEraseConfirm=null,this._decorTextDialog=null,this._decorShapeDialog=null,this._backdropDialog=null,this._decorTextSelection={start:0,end:0},this._furnPalette=null,this._dtBox=null,this._dtDrag=null,this._bdDrag=null,this._slide="",this._reducedMotion=!1,this._onMotionChange=t=>{this._reducedMotion=t.matches,t.matches&&this._modeTransitionPreparing?this._modeTransitionForceAtomic=!0:t.matches&&this._modeTransition.active&&this._cancelModeTransition(!0),this.requestUpdate()},this._editorChromeMode="plan",this._modeTransitionVisual=null,this._modeTransitionPreparing=!1,this._modeTransitionForceAtomic=!1,this._modeTransitionRequest=0,this._modeTransitionTargetZoom=1,this._modeTransitionEditorCamera=null,this._modeTransition=new hu({frame:t=>this._applyModeTransitionFrame(t),settled:t=>this._settleModeTransition(t)}),this._editorSecondary=new Od({root:()=>this.renderRoot,requestUpdate:()=>this.requestUpdate(),updateComplete:()=>this.updateComplete,clearTip:()=>{this._tip=null}}),this._editorSecondaryCopy={groupActive:(t,e)=>this._t("editor.group_active",{group:t,item:e}),openGroup:t=>this._t("editor.open_group",{group:t}),disabledAction:(t,e)=>this._t("editor.disabled_action",{action:t,reason:e})},this._tool="draw",this._geometryHistory=new Zd(50),this._wallDialog=null,this._drawWallField=null,this._activeDraftId=null,this._resumeDraftBySpace={},this._physicalSel=null,this._physicalDialog=null,this._physicalDrag=null,this._physicalRotate=null,this._physicalLastTap=null,this._physicalPickCycle=null,this._wallUnionCache=null,this._isoGeometryCache=new Map,this._isoFallback=new Set,this._openingTunnelCache=null,this._openingWallIndexCache=new Map,this._openingPlacementIntervalsCache=null,this._planSnapGeometryCache=null,this._physicalBodiesCache=null,this._cleanFloorCache=new Map,this._glowClipCache=new Map,this._lightBarrierCache=null,this._glowFeatherUnits=null,this._glowRenderedSources=new Map,this._glowLastAppearance=new Map,this._glowEnteringSources=new Set,this._glowEnterRafs=new Map,this._glowFadeTimers=new Map,this._glowFeatherSuspendUntil=0,this._glowFeatherResumeTimer=0,this._glowSourceSeq=0,this._glowScreenBlend=!1,this._duplicateColumnId=null,this._duplicateColumnTimer=0,this._rszSel=null,this._rszDrag=null,this._rszPreview=null,this._rszLive=null,this._path=[],this._cursorPt=null,this._planSnapHover=null,this._mergeSel=null,this._openingPreset=null,this._openingPresetRevision=0,this._openingHoverCandidate=null,this._openingDialog=null,this._openingInfo=null,this._opDrag=null,this._opMeasure=null,this._mergeDialog=null,this._openWallAnchor=null,this._boundaryPointerType="mouse",this._boundaryRestoreGuard=null,this._boundaryTargetMemo=null,this._boundaryPreviewMemo=null,this._splitSel=null,this._pendingSplit=null,this._areaSel="",this._nameSel="",this._roomDialog=!1,this._roomEditId=null,this._roomFill="",this._roomCustomFill=null,this._roomTempSrc="",this._roomHumSrc="",this._roomSrcOpen=null,this._roomSrcFilter="",this._roomNameScale=1,this._roomLabelScale=1,this._zoom=1,this._view=null,this._zoomBySpace={},this._viewModeSnap=null,this._pointers=new Map,this._panStart=null,this._panLock=null,this._pinchStart=null,this._suppressClick=!1,this._touchContacts=new Map,this._touchSequenceMultitouch=!1,this._touchClickBlockUntil=0,this._connectedPath="",this._routeDepartureHandled=!1,this._onLocationChanged=()=>{this._connectedPath&&location.pathname!==this._connectedPath?this._leaveCardRoute():location.pathname===this._connectedPath&&(this._routeDepartureHandled=!1)},this._touchGestureGuard={capture:!0,handleEvent:t=>this._guardTouchGesture(t)},this._hdrH=118,this._booting=!0,this._bootFading=!1,this._bootSettling=!1,this._bootSettleRaf=0,this._bootLastH=-1,this._bootStart=0,this._bootLastChange=0,this._bootSoft=!1,this._tapConfirm=null,this._onboardingShown=!1,this._rulesDialog=null,this._alignDialog=null,this._settingsDialog=null,this._backupExportDialog=null,this._backupImportDialog=null,this._sunRaysCache=null,this._dayCycleTimer=0,this._dayCycleClockKey="",this._compassDrag=!1,this._importDialog=null,this._importQueue=[],this._importTotal=0,this._rulesCompiledSrc="",this._infoCard=null,this._nativeMoreInfoEntity=null,this._markerDialog=null,this._spaceDialog=null,this._keyHandler=t=>this._onKey(t),this._warmVp=null,this._warmVpArmed=!1,this._warmLongReturn=!1,this._warmRevivePending=!1,this._warmGen=++pp,this._warmKey=null,this._warmSlot=null,this._hashApplied=!1,this._navApplied=!1,this._labs={active:Object.freeze([]),space:""},this._viewPreference={},this._renderProjection="flat",this._kioskScale={icon:1,font:1},this._kioskDialog=!1,this._activityRt=new Map,this._vacRt=new Map,this._vacViewKey="",this._vacLastView=null,this._vacRaf=0,this._vacSrvTrails={},this._vacJumpOnce=!1,this._continuity=this._newContinuityController(),this._continuityHistory=[],this._continuityEpoch=0,this._continuityDataReady=!0,this._continuityPaintToken=-1,this._continuityDisposed=!1,this._renderSnapshotAt=Date.now(),this._hassSequence=0,this._visibleDeviceSnapshot=null,this._candidateDeviceSnapshot=null,this._stagedDeviceSnapshotToken=-1,this._capturedSnapshotSequence=-1,this._capturedSnapshotDevices=null,this._capturedSnapshotLayout=null,this._capturedSnapshotActivity="",this._capturedSnapshotConfigEpoch=-1,this._capturedSnapshotVirtual="",this._lastValidStageSize=null,this._pendingRefitSize=null,this._refitRaf=0,this._pageVisibility=t=>{if(this._continuity.visibility(t),this._dayCycleVisibility(t),"hidden"!==t.kind){if(this._vacJumpOnce=!0,!t.long){const t=Date.now();let e=!1;for(const i of this._activityRt.values())!i.flashKind||(i.expiresAt||i.flashTs+Cd)>t||(i.flashTs=0,i.flashKind=null,i.expiresAt=0,e=!0);return void(e&&this.requestUpdate())}Date.now()-this._renderSnapshotAt>1e3&&this._continuity.note("device-snapshot-stale"),this._continuityDataReady=!1,this._continuityPaintToken=-1,this._resumeSettling=!0,this._loading?this.requestUpdate():this._loadFromServer()}else this._modeTransitionPreparing?this._modeTransitionForceAtomic=!0:this._cancelModeTransition(!0)},this._resumeSettling=!1,this._viewportInvalidAt=0,this._vacFit=null,this._vacAllCamerasFor=null,this._vacAllCameraCache=null,this._vacCalConfirm=null,this._kioskDots=!1,this._cyclePausedUntil=0,this._swipeStart=null,this._lastTap=0,this._onLabsSnapshot=t=>{const e=this._effectiveProjection();this._labs=t;const i=this._effectiveProjection();this._convertProjectionView(e,i),this.requestUpdate()},this._onHashChange=()=>{const t=this._hashSpace();t&&this._model.find(e=>e.id===t)&&t!==this._space&&(this._activeDraftId&&(this._resumeDraftBySpace[this._space]=this._activeDraftId),this._space=t,this._selId=null,this._path=[],this._clearPlanSnapHover(),this._clearOpeningPlacement(!0),this._tool="draw",this._openWallAnchor=null,this._boundaryRestoreGuard=null,this._activeDraftId=null,this._draftSegmentCms=[],this._closingWallCm=null,"plan"===this._mode&&"draw"===this._tool&&this._resumeLastDraft(),this._restoreZoom(),this.requestUpdate())},this._drag=null,this._rlResize=null,this._holdFired=!1,this._retryContinuity=()=>{this._continuityDataReady=!1,this._continuityPaintToken=-1,this._continuity.retry(this._continuity.recoveryReason||"plan"),this._loading||this._loadFromServer()},this._cfgEpoch=0,this._modelCache=null,this._decorSnapCache=null,this._markerPreviewMemo=null,this._markerPreviewDevicesMemo=null,this._showHidden=!1,this._connHooked=null,this._connectionWasLost=!1,this._haRegistryConnection=null,this._haRegistryRev=-1,this._haBindingCacheKey="",this._planHassMemo=null,this._onHaRegistryUpdate=()=>{const t=xn(this.hass);t.revision===this._haRegistryRev&&this._devices.length||(this._haRegistryRev=t.revision,this._planHassMemo=null,this._regSignature="",this._maybeRebuildDevices(),this.requestUpdate())},this._onConnReady=()=>{if(this._loadTries=0,clearTimeout(this._loadRetryTimer),this._loadRetryTimer=void 0,function(t){const e=vn(t);e&&(wn(t,e),kn(t,e))}(this.hass),this._saveConfigDebounced.pending()&&this._saveConfigDebounced.flush(),this._cfgWriting)return clearTimeout(this._reloadRetry),void(this._reloadRetry=window.setTimeout(this._onConnReady,400));!this._connectionWasLost&&this._continuity.hasCompleteFrame?this._beginContinuityCandidate("connection-ready",!1,"plan"):(this._continuityDataReady=!1,this._continuityPaintToken=-1),this._loading||this._loadFromServer()},this._onConnLost=()=>{this._booting&&!this._continuity.hasCompleteFrame||(this._connectionWasLost=!0,this._continuityDataReady=!1,this._continuityPaintToken=-1,this._continuity.connectionLost())},this._signer=new Wc(()=>this.requestUpdate()),this._dirtyPos=new Set,this._sentPos=new Map,this._persistLayout=Dp(()=>{if(this._serverStorage){const t=[...this._dirtyPos];this._dirtyPos.clear();for(const e of t){const t=this._layout[e];t&&(this._sentPos.set(e,t),this.hass.callWS({type:"houseplan/layout/update",device_id:e,pos:t}).then(t=>this._noteLayoutRev(t)).catch(t=>this._showToast(this._t("toast.pos_save_failed",{err:this._errText(t)}))).finally(()=>{this._sentPos.get(e)===t&&this._sentPos.delete(e)}))}this._cacheSnapshot()}else localStorage.setItem(gp,JSON.stringify(this._layout))},600),this._frame=null,this._showFar=!1,this._writesPending=0,this._writeChain=Promise.resolve(),this._undoGeometry=()=>{if(this._cancelBoundaryAnchor())return;if(this._physicalDrag||this._physicalRotate)return void this._cancelPhysicalGesture();if(this._decorDraft)return this._decorDraft=null,void this.requestUpdate();if(this._decorMove||this._dtDrag||this._bdDrag)return void this._cancelDecorGesture();if(this._rszDrag)return void this._rszCancelDrag();const t=this._geometryHistory.undo();t&&(this._applyGeometryState(t.before)?this._showToast(this._t("history.undone",{name:t.name})):this._geometryHistory.clear())},this._redoGeometry=()=>{if(this._cancelBoundaryAnchor())return;if(this._physicalDrag||this._physicalRotate)return void this._cancelPhysicalGesture();if(this._decorDraft)return this._decorDraft=null,void this.requestUpdate();if(this._decorMove||this._dtDrag||this._bdDrag)return void this._cancelDecorGesture();if(this._rszDrag)return void this._rszCancelDrag();const t=this._geometryHistory.redo();t&&(this._applyGeometryState(t.after)?this._showToast(this._t("history.redone",{name:t.name})):this._geometryHistory.clear())},this._saveConfigDebounced=Dp(()=>{this._serverCfg&&this._writeConfig().catch(t=>{"conflict"===t?.code?(this._showToast(this._t("toast.conflict")),this._cancelPath(),this._reloadConfigOnly(!0)):this._showToast(this._t("toast.cfg_save_failed",{err:this._errText(t)}))})},500),this._draftSegmentCms=[],this._closingWallCm=null,this._savePhysicalDialog=()=>{const t=this._physicalDialog,e=this._curSpaceCfg;if(!t||!e)return;const i=cp(t.cm);if(null==i)return void this._showPhysicalRange("column"===t.kind?ll:100);const s=this._imperial?2.54*i:i,n="column"===t.kind?ll:100;if(!Number.isFinite(s)||s<1||s>n)return void this._showPhysicalRange(n);if("column"===t.kind){const e=this._spaceModel().wall_columns.find(e=>e.id===t.id);if(!e)return;const i=cp(t.angle||"0");if("circle"!==t.shape&&(null==i||i<0||i>=90))return void this._showToast(this._t("toast.physical_angle"));const n="circle"===t.shape?{id:t.id,shape:"circle",center:e.center,cm:s}:{id:t.id,shape:"square",center:e.center,cm:s,angle:i};if(this._spaceModel().wall_columns.some(e=>e.id!==t.id&&Sl(e,n,.02*this._gridPitch)))return void this._showToast(this._t("toast.column_duplicate"))}const o=this._geometrySnapshot();if("partition"===t.kind){const i=(e.partitions||[]).find(e=>e.id===t.id);i&&(i.cm=s)}else if("column"===t.kind){const i=(e.wall_columns||[]).find(e=>e.id===t.id);i&&(i.cm=cl(s),i.shape="circle"===t.shape?"circle":"square","square"===i.shape?i.angle=cp(t.angle||"0"):delete i.angle)}else{const i=(e.room_drafts||[]).find(e=>e.id===t.id);i?.segments?.[t.segment||0]&&(i.segments[t.segment||0].cm=s)}this._recordGeometry(this._t("history.physical_edit"),o),this._physicalDialog=null,this._saveConfig()},this._deletePhysicalSelection=()=>{const t=this._physicalSel,e=this._curSpaceCfg;if(!t||!e)return;if("draft"===t.kind)return void this._deleteDraftWhole();const i=this._geometrySnapshot(),s="partition"===t.kind?"partitions":"column"===t.kind?"wall_columns":"room_drafts";e[s]=(e[s]||[]).filter(e=>e.id!==t.id),e[s].length||delete e[s],this._activeDraftId===t.id&&this._cancelPath(),this._physicalSel=null,this._physicalDialog=null,this._recordGeometry(this._t("history.physical_delete"),i),this._saveConfig()},this._deleteDraftWhole=()=>{const t="draft"===this._physicalDialog?.kind?this._physicalDialog.id:"draft"===this._physicalSel?.kind?this._physicalSel.id:null,e=this._curSpaceCfg;if(!t||!e||!confirm(this._t("confirm.delete_draft")))return;const i=this._geometrySnapshot();e.room_drafts=(e.room_drafts||[]).filter(e=>e.id!==t),e.room_drafts.length||delete e.room_drafts,this._activeDraftId===t&&this._cancelPath(),this._physicalSel=null,this._physicalDialog=null,this._recordGeometry(this._t("history.physical_delete"),i),this._saveConfig()},this._deleteDraftSegment=()=>{const t=this._physicalDialog,e=this._curSpaceCfg;if(!t||"draft"!==t.kind||!e)return;if(!confirm(this._t("confirm.delete_draft_segment")))return;const i=(e.room_drafts||[]).findIndex(e=>e.id===t.id);if(i<0)return;const s=e.room_drafts[i],n=Math.max(0,Math.min(s.segments.length-1,t.segment||0)),o=[],r=s.points.slice(0,n+1),a=s.points.slice(n+1);if(r.length>=2&&o.push({id:s.id,points:r,segments:s.segments.slice(0,n)}),a.length>=2&&o.push({id:o.length?`${s.id}-${Date.now().toString(36)}`:s.id,points:a,segments:s.segments.slice(n+1)}),2===o.length&&e.room_drafts.length>=200)return void this._showToast(this._t("toast.physical_limit"));const l=this._geometrySnapshot();e.room_drafts.splice(i,1,...o),e.room_drafts.length||delete e.room_drafts,this._activeDraftId===s.id&&this._cancelPath(),this._physicalDialog=null,this._physicalSel=o.length?{kind:"draft",id:o[0].id}:null,this._recordGeometry(this._t("history.draft_segment_delete"),l),this._saveConfig()},this._keepClosedAsPartitions=()=>{if(!this._contourClosed||this._pendingSplit||!this._curSpaceCfg)return;const t=this._curSpaceCfg,e=this._path.slice(0,-1);if((t.partitions||[]).length+e.length>2e3)return void this._showToast(this._t("toast.physical_limit"));const i=this._geometrySnapshot(),s=[...this._draftSegmentCms,this._closingWallCm||this._drawWallCm||ea];t.partitions||=[];const n=Date.now().toString(36);for(let i=0;it.id!==this._activeDraftId),t.room_drafts.length||delete t.room_drafts),this._recordGeometry(this._t("history.contour_to_partitions"),i),this._saveConfig(),this._roomDialog=!1,this._path=[],delete this._resumeDraftBySpace[this._space],this._activeDraftId=null,this._draftSegmentCms=[],this._closingWallCm=null},this._toggleServerPlans=async()=>{const t=this._spaceDialog;if(t)if(t.pickSaved)this._spaceDialog={...t,pickSaved:!1};else{this._spaceDialog={...t,pickSaved:!0,savedBusy:!0};try{const t=await this.hass.callWS({type:"houseplan/plans/list"}),e=this._spaceDialog;e&&(this._spaceDialog={...e,saved:t?.plans||[],savedBusy:!1})}catch(t){const e=this._spaceDialog;e&&(this._spaceDialog={...e,saved:[],savedBusy:!1}),this._showToast(this._t("toast.plans_list_failed",{err:this._errText(t)}))}}},this._aspectJob=null,this._dayCycleTick=()=>{if(!this.isConnected||"hidden"===this.ownerDocument.visibilityState)return;const t=this._dayCycleState();if(!t)return this._dayCycleTimer&&(clearInterval(this._dayCycleTimer),this._dayCycleTimer=0),void(this._dayCycleClockKey="");const e=Cr(t);e!==this._dayCycleClockKey&&(this._dayCycleClockKey=e,this.requestUpdate())},this._sunShown=!1,this._sunOut=!1,this._sunOutTimer=0,this._openSettingsDialog=()=>{if(!this._norm)return;const t=this._glowRadiusCm,e=this._imperial?Math.round(t/30.48*10)/10:Math.round(t)/100;this._settingsDialog={colors:JSON.parse(JSON.stringify(this._fillColors)),glowRadius:e,bgColor:es(this._settings,{bgColor:null})||null,northDeg:Fr(this._settings,{}),bgMode:Or(this._settings,{}),sunRays:Nr(this._settings,{}),busy:!1}},this._openAlignDialog=()=>{if(!this._norm||!this._serverCfg)return;const t=this._serverCfg.spaces||[],e=Xd(this._serverCfg,this._layout||{}),i=Math.ceil(10*e.report.maxShiftCm)/10,s=t.find(t=>null!=t?.id&&String(t.id)===e.report.maxSpace),n=t.length>1&&s?String(s.title||s.id):"";this._alignDialog={report:e.report,config:e.config,layout:e.layout,cm:i,where:n,changed:e.changed,busy:!1}},this._optimizeUndoBusy=!1,this._openBackupExport=()=>{this._settingsDialog=null,this._backupExportDialog={kind:"full",planOnly:!1,busy:!1,error:""}},this._openRulesDialog=()=>{if(!this._norm)return;const t=this._settings.icon_rules,e=(t&&t.length?t:jt).map(t=>({...t}));this._rulesDialog={rules:e,test:"",busy:!1}},this._climateCache=null,this._gearPtCache=new WeakMap}get _canEdit(){return!!this._norm&&(!0===this._serverCanWrite||!1!==this._serverCanWrite&&!0===this.hass?.user?.is_admin)}get _kiosk(){return!!this._config?.kiosk}_showKioskDots(){this._kioskDots=!0,clearTimeout(this._kioskDotsTimer),this._kioskDotsTimer=window.setTimeout(()=>this._kioskDots=!1,2500)}get _modeTransitionBusy(){return this._modeTransitionPreparing||this._modeTransition.active}_cssColor(t,e){const i=String(t||"").trim();if(!i)return e;const s=document.createElement("span");s.style.cssText=`position:absolute;visibility:hidden;color:${i}`,this.renderRoot.append(s);const n=getComputedStyle(s).color||e;return s.remove(),n}_currentModeVisual(t=this._mode){const e=this._modeTransition.presented||this._modeTransitionVisual;if(e)return{...e,viewport:{...e.viewport,viewBox:{...e.viewport.viewBox}}};const i=this._stageEl;if(!i||i.clientWidth<=0||i.clientHeight<=0)return null;const s=this.renderRoot.querySelector(".editorchrome"),n=this._viewOr(this._baseVb()),o=this.renderRoot.querySelector(".hp-paper"),r=this.renderRoot.querySelector(".hp-backdrop"),a=this.renderRoot.querySelector(".zoomwrap"),l=a?getComputedStyle(a).filter:"",c=Number(/brightness\(([^)]+)\)/.exec(l)?.[1]);return{presentedMode:t,editorChromeHeight:"view"===t?0:s?.getBoundingClientRect().height||0,stageWidth:i.clientWidth,stageHeight:i.clientHeight,viewport:ou(n,i.clientWidth),stageColor:getComputedStyle(i).backgroundColor||"rgb(255, 255, 255)",paperColor:o?getComputedStyle(o).fill:"rgb(255, 255, 255)",sceneBrightness:Number.isFinite(c)?c:1,architectureOpacity:"decor"===t?.35:1,backdropOpacity:r&&Number(getComputedStyle(r).opacity)||1,viewWeight:"view"===t?1:0,editorWeight:"view"===t?0:1,toolbarContentOpacity:"view"===t?0:1}}_viewForModeTarget(t,e,i,s,n){const o=Di(this._baseVb(),s/Math.max(1,n)),r=Math.min(Pp.ZOOM_MAX,Math.max(Pp.ZOOM_MIN,t)),a=o.w/r,l=o.h/r,c=e??o.x+o.w/2,h=i??o.y+o.h/2;return this._clampView({x:c-a/2,y:h-l/2,w:a,h:l},o)}_targetStageColor(t){return"view"!==t?"rgb(255, 255, 255)":this._cssColor(this._stageBg(this._spaceDisplayForRender()),this._cssColor("var(--ha-card-background, var(--card-background-color, #111))","rgb(17, 17, 17)"))}_targetPaperColor(t){return"view"===t&&this._spaceModel().bg?this._cssColor("var(--ha-card-background, var(--card-background-color, #111))","rgb(17, 17, 17)"):"rgb(255, 255, 255)"}_targetBrightness(t){return 1}_applyModeTransitionFrame(t){t.targetMode===this._mode&&(this._modeTransitionVisual=t.presented,this._view={...t.presented.viewport.viewBox},this.requestUpdate())}_settleModeTransition(t){if(t.targetMode!==this._mode)return;const e=this._modeTransitionRequest;this._view={...t.to.viewport.viewBox},this._zoom=this._modeTransitionTargetZoom,this._modeTransitionVisual=null,this._modeTransitionPreparing=!1,this._lastValidStageSize=[t.to.stageWidth,t.to.stageHeight],"view"===this._mode&&(this._saveZoom(),this._viewModeSnap=null,this._modeTransitionEditorCamera=null),this.requestUpdate(),this.updateComplete.then(()=>{if(!this.isConnected||e!==this._modeTransitionRequest||this._modeTransitionBusy||t.targetMode!==this._mode)return;const i=this._stageEl;if(i&&i.clientWidth>0&&i.clientHeight>0&&(Math.abs(i.clientWidth-t.to.stageWidth)>.5||Math.abs(i.clientHeight-t.to.stageHeight)>.5)){const t=this._view;this._lastValidStageSize=[i.clientWidth,i.clientHeight],this._applyView(this._zoom,t?t.x+t.w/2:void 0,t?t.y+t.h/2:void 0),this.requestUpdate()}const s=this.renderRoot.activeElement;(!s||!s.isConnected||!!s.closest?.(".editorchrome, .stage"))&&this.renderRoot.querySelector(".modetab.active")?.focus?.({preventScroll:!0})})}_cancelModeTransition(t=!0){const e=!!this._modeTransition.state;this._modeTransitionRequest++,this._modeTransitionPreparing=!1,this._modeTransitionForceAtomic=!1,this._modeTransition.cancel(t),t&&e||(this._modeTransitionVisual=null)}_adoptMode(t){this._cancelModeTransition(!1),this._mode=t,"view"!==t&&(this._editorChromeMode=t)}_commitViewModeAtomic(t,e,i,s){if(this._modeTransitionPreparing=!1,this._modeTransitionVisual=null,this._modeTransitionForceAtomic=!1,this._zoom=e,t){const n=this._stageEl?.clientWidth||t.stageWidth,o=Math.max(1,t.stageHeight+t.editorChromeHeight);this._view=this._viewForModeTarget(e,i,s,n,o),this._lastValidStageSize=[n,o]}else this._view=null;this._viewModeSnap=null,this._modeTransitionEditorCamera=null,this._saveZoom(),this.requestUpdate()}_prepareModeTransition(t,e,i,s,n,o){this.updateComplete.then(()=>{if(!this.isConnected||t!==this._modeTransitionRequest||this._mode!==i)return void(t===this._modeTransitionRequest&&(this._modeTransitionPreparing=!1,this._modeTransitionVisual=null,this._modeTransitionForceAtomic=!1,this.requestUpdate()));const r=this.renderRoot.querySelector(".editorchrome"),a=r?.querySelector(".editorchrome-inner"),l="view"===i?0:a?.scrollHeight||a?.getBoundingClientRect().height||0,c=Math.max(1,e.stageHeight+e.editorChromeHeight),h=Math.max(1,c-l),d=this._stageEl?.clientWidth||e.stageWidth;if(d<=0||h<=0)return this._modeTransitionPreparing=!1,this._modeTransitionVisual=null,this._applyView(s,n,o),void this.requestUpdate();const u=this._viewForModeTarget(s,n,o,d,h),p={presentedMode:i,editorChromeHeight:l,stageWidth:d,stageHeight:h,viewport:ou(u,d),stageColor:this._targetStageColor(i),paperColor:this._targetPaperColor(i),sceneBrightness:this._targetBrightness(i),architectureOpacity:"decor"===i?.35:1,backdropOpacity:"decor"===i&&"backdrop"!==this._decorTool?.5:1,viewWeight:"view"===i?1:0,editorWeight:"view"===i?0:1,toolbarContentOpacity:"view"===i?0:1};this._modeTransitionPreparing=!1;const _=this._modeTransitionForceAtomic;this._modeTransitionForceAtomic=!1,this._modeTransition.start(e,p,i,this._reducedMotion||_?0:220)})}_slideTo(t,e){if(t===this._space)return;this._cancelModeTransition(!0);const i=window.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches;this._activeDraftId&&(this._resumeDraftBySpace[this._space]=this._activeDraftId),this._space=t,this._path=[],this._clearPlanSnapHover(),this._clearOpeningPlacement(!0),this._tool="draw",this._openWallAnchor=null,this._boundaryRestoreGuard=null,this._activeDraftId=null,this._draftSegmentCms=[],this._closingWallCm=null,this._selId=null,this._physicalSel=null,this._editorSecondary.closeForNavigation(),this._physicalDialog=null,this._physicalDrag=null,"plan"===this._mode&&"draw"===this._tool&&this._resumeLastDraft(),this._restoreZoom(),i||(this._slide=e,clearTimeout(this._slideTimer),this._slideTimer=window.setTimeout(()=>{this._slideTimer=void 0,this._slide="",this.requestUpdate()},190),this.requestUpdate())}_pickSpace(t){if(t===this._space)return;const e=this._model.map(t=>t.id),i=e.indexOf(this._space),s=e.indexOf(t);this._navApplied=!0,this._showFar=!1,this._frame=null,this._slideTo(t,i>=0&&s0&&Date.now()>=this._cyclePausedUntil&&this._model.length>1&&this._zoom<=1.001){const t=this._model.map(t=>t.id),e=t.indexOf(this._space);this._slideTo(t[(e+1)%t.length],"left"),this._showKioskDots()}}get _editing(){return"plan"===this._mode||"devices"===this._mode||"decor"===this._mode}get _markup(){return"plan"===this._mode}_newContinuityController(){return new Hc(()=>{this._resumeSettling="steady"!==this._continuity.state,this._continuityEpoch++,this.isConnected&&this.requestUpdate()})}get _labsIso(){return this._labs.active.includes("iso")}get _desiredProjection(){return"view"===this._mode&&this._labsIso&&"iso"===this._viewPreference[this._space]?"iso":"flat"}_saveViewPreference(){try{localStorage.setItem(wp,JSON.stringify(this._viewPreference))}catch{}}_logicalViewCenter(t){const e=this._view;if(!e)return null;const i=[e.x+e.w/2,e.y+e.h/2],s="iso"===t?Iu(i):i;return{x:s[0],y:s[1]}}_convertProjectionView(t,e){if(t===e)return;const i=this._logicalViewCenter(t);this._view=null;const s=i?"iso"===e?Au([i.x,i.y],0):[i.x,i.y]:null;this._applyView(this._zoom,s?.[0],s?.[1]),this._warmPatch({vp:this._warmViewportState()}),this.requestUpdate()}_setProjection(t){if(!this._labsIso||"view"!==this._mode)return;const e=this._effectiveProjection();if(this._viewPreference={...this._viewPreference,[this._space]:t},"iso"===t){const t=this._isoSceneKey();t&&this._isoFallback.delete(t)}this._saveViewPreference();const i=this._effectiveProjection();this._convertProjectionView(e,i)}_hashSpace(){return vu(window.location.hash||"")}connectedCallback(){this._connectedPath=location.pathname,this._routeDepartureHandled=!1,window.addEventListener("location-changed",this._onLocationChanged),window.addEventListener("popstate",this._onLocationChanged),this._continuityDisposed&&(this._continuity=this._newContinuityController(),this._continuityDisposed=!1,this._continuityPaintToken=-1);const t=(e=this.ownerDocument,Jd.get(e)?.resolved);var e;void 0!==t&&(this._glowScreenBlend=t),this._continuityUnsub?.(),this._continuityUnsub=Nc(this.ownerDocument,this._pageVisibility),super.connectedCallback(),this._motionMedia=window.matchMedia?.("(prefers-reduced-motion: reduce)"),this._reducedMotion=!!this._motionMedia?.matches,this._motionMedia?.addEventListener?.("change",this._onMotionChange),function(t){const e=Jd.get(t);if(e)return e.promise;const i={promise:Promise.resolve(!1)};return i.promise=tu(t).catch(()=>!1).then(t=>(i.resolved=t,t)),Jd.set(t,i),i.promise}(this.ownerDocument).then(t=>{t!==this._glowScreenBlend&&(this._glowScreenBlend=t,this.isConnected&&this.requestUpdate())}),this.hass&&this._ensureHaRegistryAuthority(),window.addEventListener("keydown",this._keyHandler),this._signer.start(()=>this.hass,()=>$s(this._serverCfg)),this._config?.kiosk&&Number(this._config?.cycle)>0&&(clearInterval(this._cycleTimer),this._cycleTimer=window.setInterval(()=>this._cycleTick(),1e3*Number(this._config.cycle))),window.addEventListener("hashchange",this._onHashChange),this._labsUnsub?.(),this._labsUnsub=function(t,e){Su.add(e);const i=Du(t);return"undefined"==typeof window&&e(i),()=>{Su.delete(e)}}(sp,this._onLabsSnapshot),this._booting?this._bootWatch():this._bootFading&&(clearTimeout(this._bootTimer),this._bootTimer=window.setTimeout(()=>{this._bootFading=!1},220)),this._bootSoft&&(clearTimeout(this._bootSoftTimer),this._bootSoftTimer=window.setTimeout(()=>{this._bootSoft=!1},lp)),!this._loadOk&&this._serverCfg&&this.hass&&this._scheduleLoadRetry(),!this._warmSlot&&this._config&&this._warmAdopt(),this._loadOk&&this._ensureLiveSyncSubscriptions(),this._warmVp&&!this._warmRevivePending&&void 0===this._warmReviveTimer&&(this._warmRevivePending=!0,this._warmReviveTimer=window.setTimeout(()=>this._warmReviveDialog(),0)),this._warmLongReturn&&this._beginResumeSettle(),this._warmLongReturn=!1,this.requestUpdate()}disconnectedCallback(){this._connectedPath&&location.pathname!==this._connectedPath&&this._leaveCardRoute(),window.removeEventListener("location-changed",this._onLocationChanged),window.removeEventListener("popstate",this._onLocationChanged),this._continuityUnsub?.(),this._continuityUnsub=void 0,this._motionMedia?.removeEventListener?.("change",this._onMotionChange),this._motionMedia=void 0,this._vacRaf&&(cancelAnimationFrame(this._vacRaf),this._vacRaf=0),this._refitRaf&&(cancelAnimationFrame(this._refitRaf),this._refitRaf=0),this._dayCycleTimer&&(clearInterval(this._dayCycleTimer),this._dayCycleTimer=0),this._dayCycleClockKey="",this._bootSettleRaf&&(cancelAnimationFrame(this._bootSettleRaf),this._bootSettleRaf=0),this._bootSettling=!1;for(const t of this._activityRt.values())clearTimeout(t.timer);window.removeEventListener("keydown",this._keyHandler),clearInterval(this._cycleTimer),clearTimeout(this._kioskDotsTimer),clearTimeout(this._kioskHoldTimer),clearTimeout(this._reloadRetry),clearTimeout(this._loadRetryTimer),this._loadRetryTimer=void 0,this._connHooked?.removeEventListener?.("ready",this._onConnReady),this._connHooked?.removeEventListener?.("disconnected",this._onConnLost),this._connHooked?.removeEventListener?.("reconnect-error",this._onConnLost),this._connHooked=null,this._haRegistryRelease?.(),this._haRegistryRelease=void 0,this._haRegistryConnection=null,this._signer.dispose(),clearTimeout(this._toastTimer),clearTimeout(this._slideTimer),this._modeTransition.dispose(),this._modeTransitionVisual=null,this._modeTransitionPreparing=!1,this._modeTransitionForceAtomic=!1,this._modeTransitionRequest++,this._slideTimer=void 0,this._slide="",clearTimeout(this._bootTimer),this._bootTimer=void 0,clearTimeout(this._bootSoftTimer),this._saveConfigDebounced.flush(),window.removeEventListener("hashchange",this._onHashChange),this._labsUnsub?.(),this._labsUnsub=void 0,clearTimeout(this._holdTimer),this._roViewport?.disconnect(),this._roViewport=void 0,this._roHdr?.disconnect(),this._roHdr=void 0,this._onWinResize&&(window.removeEventListener("resize",this._onWinResize),this._onWinResize=void 0),this._unsubCfg&&(this._unsubCfg(),this._unsubCfg=null),this._unsubLayout&&(this._unsubLayout(),this._unsubLayout=null),this._unsubVirtual&&(this._unsubVirtual(),this._unsubVirtual=null),this._unsubTrail&&(this._unsubTrail(),this._unsubTrail=void 0),this._liveSyncGeneration++,this._liveSyncAttempt=null,this._liveSyncConnection=null,clearTimeout(this._layoutSyncTimer),clearTimeout(this._duplicateColumnTimer);for(const t of this._glowFadeTimers.values())clearTimeout(t);for(const t of this._glowEnterRafs.values())cancelAnimationFrame(t);clearTimeout(this._glowFeatherResumeTimer),this._glowFeatherResumeTimer=0,this._glowFeatherSuspendUntil=0,this._glowFadeTimers.clear(),this._glowEnterRafs.clear(),this._glowEnteringSources.clear(),this._glowRenderedSources.clear(),this._glowLastAppearance.clear(),this._glowSourceSeq=0,this._warmSnapshot(),this._warmRevivePending=!1,clearTimeout(this._warmReviveTimer),this._warmReviveTimer=void 0,this._warmRelease(),this._openWallAnchor=null,this._boundaryRestoreGuard=null,this._clearPlanSnapHover(),this._clearOpeningPlacement(!0),this._touchContacts.clear(),this._touchSequenceMultitouch=!1,this._touchClickBlockUntil=0,this._editorSecondary.reset(),this._resumeSettling=!1,this._continuityHistory=[...this._continuityHistory,...this._continuity.trace].slice(-80),this._continuity.dispose(),this._continuityDisposed=!0,super.disconnectedCallback()}_onKey(t){if("Escape"===t.key&&this._vacFit)return this._vacFit=null,this._showToast(this._t("vac.cal_cancelled")),void t.stopPropagation();if("Escape"===t.key){if(this._tapConfirm)return void(this._tapConfirm=null);if(this._vacCalConfirm)return void(this._vacCalConfirm=null);if(this._decorEraseConfirm)return void(this._decorEraseConfirm=null);if(this._openingInfo)return void(this._openingInfo=null);if(this._infoCard)return void this._closeInfoCard();if(this._rulesDialog)return void(this._rulesDialog=null);if(this._alignDialog)return void(this._alignDialog=null);if(this._backupImportDialog)return void(this._backupImportDialog=null);if(this._backupExportDialog)return void(this._backupExportDialog=null);if(this._settingsDialog)return void(this._settingsDialog=null);if(this._markerDialog)return void(this._markerDialog=null);if(this._openingDialog)return void(this._openingDialog=null);if(this._physicalDialog)return void(this._physicalDialog=null);if(this._backdropDialog)return void(this._backdropDialog=null);if(this._decorShapeDialog)return void(this._decorShapeDialog=null);if(this._decorTextDialog)return void(this._decorTextDialog=null);if(this._spaceDialog&&!this._roomDialog)return this._spaceDialog=null,this._importQueue=[],void(this._importTotal=0);if(this._editorSecondary.hasOpenGroup)return t.preventDefault(),void this._editorSecondary.closeGroup(!0)}const e=t.composedPath?.()||[t.target],i=e.some(t=>t?.matches?.('input, textarea, select, [contenteditable="true"]')),s=e.some(t=>t?.classList?.contains?.("editor-secondary")),n=t.ctrlKey||t.metaKey,o=t.key.toLowerCase(),r=/^[a-z]$/.test(o),a="z"===o||!r&&"KeyZ"===t.code,l="y"===o||!r&&"KeyY"===t.code,c=n&&(a&&t.shiftKey||l),h=n&&a&&!t.shiftKey;if("decor"===this._mode){if((h||c)&&i)return;return c?(t.preventDefault(),void this._redoGeometry()):h?(t.preventDefault(),this._decorDraft?void(this._decorDraft=null):this._decorMove||this._dtDrag||this._bdDrag?void this._cancelDecorGesture():void this._undoGeometry()):"Delete"!==t.key&&"Backspace"!==t.key||!this._decorSel||i||s?void("Escape"===t.key&&(t.preventDefault(),this._decorDraft?this._decorDraft=null:this._decorMove||this._dtDrag||this._bdDrag?this._cancelDecorGesture():"furniture"===this._decorTool?(this._furnPalette=null,this._decorTool="select"):this._decorSel?this._decorSel=null:"select"!==this._decorTool?this._decorTool="select":this._setMode("view"))):(t.preventDefault(),void this._decorDeleteSel())}if(this._markup&&(!h&&!c||!i)){if(("Delete"===t.key||"Backspace"===t.key)&&this._physicalSel&&!i&&!s)return t.preventDefault(),void this._deletePhysicalSelection();if(c)return t.preventDefault(),void this._redoGeometry();if(h)return t.preventDefault(),this._rszDrag?void this._rszCancelDrag():"draw"!==this._tool&&"partition"!==this._tool||!this._path.length?"split"===this._tool&&this._splitSel?.pts?.length?(this._splitSel={...this._splitSel,pts:this._splitSel.pts.slice(0,-1)},void(this._splitSel.pts.length||(this._cursorPt=null))):void this._undoGeometry():void(this._activeDraftId&&this._path.length>1?this._undoGeometry():this._undoPoint());if("Escape"===t.key)return this._physicalDrag||this._physicalRotate?(t.preventDefault(),void this._cancelPhysicalGesture()):this._roomDialog?(t.preventDefault(),void this._roomDialogCancel()):"draw"===this._tool&&this._path.length||"partition"===this._tool&&this._path.length?(t.preventDefault(),void this._undoPoint()):this._physicalSel?(t.preventDefault(),void(this._physicalSel=null)):"resize"===this._tool?(t.preventDefault(),this._rszDrag?void this._rszCancelDrag():void(this._rszSel?this._rszSel=null:this._tool="draw")):"split"===this._tool?(t.preventDefault(),void(this._splitSel?.pts?.length?(this._splitSel={...this._splitSel,pts:this._splitSel.pts.slice(0,-1)},this._splitSel.pts.length||(this._cursorPt=null)):this._splitSel?this._splitSel=null:this._tool="draw")):"merge"===this._tool?(t.preventDefault(),void(this._mergeSel?this._mergeSel=null:this._tool="draw")):this._wallDialog?(t.preventDefault(),void(this._wallDialog=null)):"boundary"===this._tool?(t.preventDefault(),void(this._openWallAnchor?this._cancelBoundaryAnchor():this._tool="draw")):void("opening"!==this._tool&&"wallthick"!==this._tool&&"delroom"!==this._tool&&"partition"!==this._tool&&"column"!==this._tool||(t.preventDefault(),"opening"===this._tool&&this._clearOpeningPlacement(!0),this._tool="draw"))}}_undoPoint(){if(this._path.length){if(this._contourClosed)return this._path=this._path.slice(0,-1),void(this._closingWallCm=null);if(this._activeDraftId&&this._path.length>1&&this._curSpaceCfg){const t=this._geometrySnapshot();this._path=this._path.slice(0,-1),this._draftSegmentCms=this._draftSegmentCms.slice(0,-1);const e=this._curSpaceCfg,i=(e.room_drafts||[]).findIndex(t=>t.id===this._activeDraftId);return void(i>=0&&(this._path.length<2?(e.room_drafts.splice(i,1),e.room_drafts.length||delete e.room_drafts,this._activeDraftId=null):e.room_drafts[i]={id:this._activeDraftId,points:this._path.map(t=>[t[0]/kp,t[1]/kp]),segments:this._draftSegmentCms.map(t=>({cm:t}))},this._recordGeometry(this._t("history.draft_segment_delete"),t),this._saveConfig()))}this._path=this._path.slice(0,-1)}}static getConfigElement(){return document.createElement("houseplan-card-editor")}static getStubConfig(){return{type:"custom:houseplan-card"}}static _warmBootReset(t){for(const t of up.values())for(const e of t)clearTimeout(e.evict);up.clear(),mp=t&&t>0?t:1e4}static _warmBootStats(){let t=0,e=0;const i=[];for(const s of up.values())for(const n of s)t++,n.dlg&&(e++,i.push(n.dlg.kind));return{keys:up.size,slots:t,dlgs:e,drafts:i}}setConfig(t){this._config={icon_size:2.5,show_temperature:!0,live_states:!0,show_signal:!0,...t},this._config.kiosk&&(this._booting=!1,this._bootFading=!1),t.default_floor&&(this._space=t.default_floor);try{this._zoomBySpace=JSON.parse(localStorage.getItem(vp)||"{}")||{}}catch{this._zoomBySpace={}}try{const t=JSON.parse(localStorage.getItem(wp)||"{}")||{};this._viewPreference=Object.fromEntries(Object.entries(t).filter(t=>"flat"===t[1]||"iso"===t[1]))}catch{this._viewPreference={}}this._labs=Du(sp);try{const t=JSON.parse(localStorage.getItem(bp)||"null");this._kioskScale={icon:Cs(t?.icon),font:Cs(t?.font)}}catch{}try{const t=JSON.parse(localStorage.getItem(fp)||"null");t&&t.config&&Array.isArray(t.config.spaces)&&(this._serverCfg=t.config,this._cfgEpoch++,this._cfgRev=t.rev||0,this._cfgContentFingerprint=t.config_fingerprint||qc(t.config),this._layout=t.layout||{},this._layoutRev=t.layout_rev||0,this._layoutContentFingerprint=t.layout_fingerprint||qc(this._layout),this._virtualLights=rn(t.virtual_lights,this._cfgRev),this._serverStorage=!0,this._adoptInitialSpace(this._model))}catch{}"view"!==this._mode||this._view||(this._zoom=this._zoomBySpace[this._space]||1),this.isConnected&&(this._warmAdopt(),this._warmLongReturn&&this._beginResumeSettle(),this._warmLongReturn=!1)}_warmAdopt(){if(this._config?.kiosk)return;const t=_p(this._config);if(this._warmKey===t&&this._warmSlot)return;this._warmSlot&&this._warmRelease();const e=this.parentNode,i=this._warmIdx(e),s=up.get(t);if(!s||!s.length)return;const n=s.find(t=>t.owner===this._warmGen);if(n)return this._warmLongReturn=!!n.freed&&Date.now()-n.freed>=zc,clearTimeout(n.evict),n.evict=0,n.freed=0,n.live=!0,this._warmSlot=n,this._warmKey=t,n.frameFingerprint&&this._continuity.adoptCompleteFrame(n.frameFingerprint),void(!this._devices.length&&n.devices?.length&&(this._devices=[...n.devices]));const{slot:o,sure:r}=((t,e,i,s)=>{const n=t=>{const e=!!i&&t.place?.deref()===i;return e&&t.idx===s?4:t.live?0:e?3:2};let o=null,r=0,a=0,l=null;for(const i of t){if(i.owner===e)continue;l=i;const t=n(i);t<=0||(t>r?(o=i,r=t,a=1):t===r&&a++)}return!o||a>1?{slot:o||l,sure:!1}:{slot:o,sure:!0}})(s,this._warmGen,e,i);o&&(this._warmLongReturn=!!o.freed&&Date.now()-o.freed>=zc,this._booting=!1,this._bootFading=!1,this._hdrH=o.hdrH,this._bootSoft=!0,this.isConnected&&(clearTimeout(this._bootSoftTimer),this._bootSoftTimer=window.setTimeout(()=>{this._bootSoft=!1},lp)),this._warmKey=t,r?(clearTimeout(o.evict),o.evict=0,o.owner=this._warmGen,o.place=e?new WeakRef(e):null,o.idx=i,o.live=!0,this._warmSlot=o,this._warmVp=o.vp,o.frameFingerprint&&this._continuity.adoptCompleteFrame(o.frameFingerprint),!this._devices.length&&o.devices?.length&&(this._devices=[...o.devices]),this._warmAdoptViewport(this._config)):(this._warmSlot={owner:this._warmGen,path:location.pathname,place:e?new WeakRef(e):null,idx:i,live:!0,hdrH:o.hdrH,stageH:o.stageH,vp:null,frameFingerprint:"",devices:null,dlg:null,freed:0,evict:0},s.push(this._warmSlot),this._warmTrim(s)))}_warmIdx(t){const e=t?.children;if(!e)return-1;for(let t=0;t4;){const e=t.findIndex(t=>!t.live);if(e<0)break;clearTimeout(t[e].evict),t.splice(e,1)}}_warmScheduleEvict(t,e){if(clearTimeout(t.evict),!(t.dlg||t.vp&&"view"!==t.vp.mode))return;const i=t.freed,s=t.owner;t.evict=window.setTimeout(()=>{if(t.evict=0,t.freed!==i||t.owner!==s)return;var n;t.dlg=null,t.vp=(n=t.vp)&&"view"!==n.mode?{...n,mode:"view",zoom:n.snap?.space===n.space?n.snap.zoom:n.zoom,view:null,snap:null,tool:"draw",decorTool:"select",showHidden:!1,selId:null,rszSel:null,decorSel:null}:n,t.frameFingerprint="";const o=up.get(e);if(!t.live&&o&&o.length>1){const e=o.indexOf(t);e>=0&&o.splice(e,1)}},mp+250)}_warmAdoptViewport(t){const e=this._warmVp;if(!e||this._warmSlot?.path!==location.pathname)return void(this._warmVp=null);if(this._hashApplied||!this._model.find(t=>t.id===e.space))return void(this._warmVp=null);this._space=e.space,this._navApplied=!0,this._adoptMode("view"!==e.mode&&this._canEdit&&!t.kiosk?e.mode:"view"),this._pendingNavMode="view"===e.mode||this._canEdit||t.kiosk?null:e.mode,this._zoom=e.zoom;const i=this._effectiveProjection(),s=i===e.projection&&this._labsIso===e.activeLabsIso;if(this._view=s&&e.view?{...e.view}:null,this._viewModeSnap=s&&e.snap?{...e.snap}:null,!s&&e.logicalCenter){const t="iso"===i?Au([e.logicalCenter.x,e.logicalCenter.y],0):[e.logicalCenter.x,e.logicalCenter.y];this._applyView(e.zoom,t[0],t[1])}var n;this._tool="openwall"===(n=e.tool)||"closewall"===n?"boundary":"opening"===n?"draw":"string"==typeof n&&Sp.has(n)?n:"draw",this._decorTool=e.decorTool,this._showHidden=e.showHidden,this._showFar!==e.showFar&&(this._showFar=e.showFar,this._frame=null),this._selId=e.selId,this._rszSel=e.rszSel,this._decorSel=e.decorSel,this._warmVpArmed=!0}_warmPatch(t,e=!1){if(this._config?.kiosk)return;const i=_p(this._config);if(!this._warmSlot||this._warmKey===i){if(!this._warmSlot){if(!e)return;const t=this.parentNode;this._warmKey=i,this._warmSlot={owner:this._warmGen,path:location.pathname,place:t?new WeakRef(t):null,idx:this._warmIdx(t),live:!0,hdrH:this._hdrH,stageH:0,vp:null,frameFingerprint:"",devices:null,dlg:null,freed:0,evict:0};const s=up.get(i)||[];for(s.push(this._warmSlot),up.set(i,s),this._warmTrim(s);up.size>8;){const t=up.keys().next().value;if(void 0===t||t===i)break;for(const e of up.get(t)||[])clearTimeout(e.evict);up.delete(t)}}Object.assign(this._warmSlot,t)}}_warmViewportState(){const t=this._effectiveProjection();return{space:this._space,mode:this._mode,projection:t,activeLabsIso:this._labsIso,logicalCenter:this._logicalViewCenter(t),zoom:this._zoom,view:this._view?{...this._view}:null,snap:this._viewModeSnap?{...this._viewModeSnap}:null,tool:this._tool,decorTool:this._decorTool,showHidden:this._showHidden,showFar:this._showFar,selId:this._selId,rszSel:this._rszSel,decorSel:this._decorSel}}_warmDialogState(){const t=(t,e)=>({kind:t,space:this._space,mode:this._mode,data:e});return this._tapConfirm||this._alignDialog||this._mergeDialog||this._importDialog||this._backupExportDialog||this._backupImportDialog?null:this._openingInfo?t("openingInfo",this._openingInfo.id):this._infoCard?t("info",this._infoCard.id):this._rulesDialog?this._rulesDialog.busy?null:t("rules",this._rulesDialog):this._settingsDialog?this._settingsDialog.busy?null:t("settings",this._settingsDialog):this._markerDialog?this._markerDialog.busy?null:t("marker",this._markerDialog):this._openingDialog?t("opening",this._openingDialog):this._backdropDialog?t("backdrop",this._backdropDialog):this._decorShapeDialog?t("decorShape",this._decorShapeDialog):this._decorTextDialog?t("decorText",this._decorTextDialog):this._roomDialog?t("room",{editId:this._roomEditId,fill:this._roomFill,customFill:this._roomCustomFill,tempSrc:this._roomTempSrc,humSrc:this._roomHumSrc,srcOpen:this._roomSrcOpen,srcFilter:this._roomSrcFilter,nameScale:this._roomNameScale,labelScale:this._roomLabelScale,areaSel:this._areaSel,nameSel:this._nameSel,pendingSplit:this._pendingSplit,path:this._path}):this._spaceDialog?this._spaceDialog.busy?null:t("space",this._spaceDialog):null}_warmSnapshot(){if(this._booting||this._config?.kiosk||"steady"!==this._continuity.state)return;const t={vp:this._warmViewportState(),frameFingerprint:this._continuity.frameFingerprint,devices:this._devices};if(this._warmRevivePending||(t.dlg=this._warmDialogState()),this.isConnected&&this._warmSlot?.owner===this._warmGen){const e=this.parentNode;t.place=e?new WeakRef(e):null,t.idx=this._warmIdx(e)}this._warmPatch(t)}_warmReviveDialog(){this._warmRevivePending=!1;const t=this._warmSlot;if(this._warmReviveTimer=void 0,!t||!t.dlg)return;const e=t.dlg,i=t.freed;if(t.dlg=null,t.freed=0,clearTimeout(t.evict),t.evict=0,i&&!(Date.now()-i>mp)&&e.space===this._space&&e.mode===this._mode){switch(e.kind){case"space":this._spaceDialog={...e.data,busy:!1,savedBusy:!1};break;case"marker":this._markerDialog={...e.data,busy:!1};break;case"settings":this._settingsDialog={...e.data,busy:!1};break;case"rules":this._rulesDialog={...e.data,busy:!1};break;case"opening":this._openingDialog={...e.data};break;case"backdrop":this._backdropDialog={...e.data};break;case"decorShape":this._decorShapeDialog={...e.data};break;case"decorText":{this._decorTextDialog={...e.data};const t=String(this._decorTextDialog?.text??"").length;this._decorTextSelection={start:t,end:t};break}case"room":{const t=e.data;this._roomEditId=t.editId,this._roomFill=t.fill,this._roomCustomFill=t.customFill||null,this._roomTempSrc=t.tempSrc,this._roomHumSrc=t.humSrc,this._roomSrcOpen=t.srcOpen,this._roomSrcFilter=t.srcFilter,this._roomNameScale=t.nameScale,this._roomLabelScale=t.labelScale,this._areaSel=t.areaSel,this._nameSel=t.nameSel,this._pendingSplit=t.pendingSplit,this._path=t.path,this._roomDialog=!0;break}case"info":{const t=this._devices.find(t=>t.id===e.data);t&&(this._infoCard=t);break}case"openingInfo":{const t=(this._curSpaceCfg?.openings||[]).find(t=>t.id===e.data);t&&(this._openingInfo=t);break}}this.requestUpdate()}}_cacheSnapshot(){if(this._serverCfg)try{this._cfgContentFingerprint=qc(this._serverCfg),this._layoutContentFingerprint=qc(this._layout),this._virtualLights=function(t,e,i){if(t.configRev===i)return t;const s=new Set((Array.isArray(e?.markers)?e.markers:[]).filter(hn).map(t=>t.id));return{...t,configRev:i,off:new Set([...t.off].filter(t=>s.has(t)))}}(this._virtualLights,this._serverCfg,this._cfgRev),localStorage.setItem(fp,JSON.stringify({config:this._serverCfg,rev:this._cfgRev,config_fingerprint:this._cfgContentFingerprint,layout:this._layout,layout_rev:this._layoutRev,layout_fingerprint:this._layoutContentFingerprint,virtual_lights:ln(this._virtualLights)}))}catch{}}_beginContinuityCandidate(t,e,i="plan"){return this._booting&&!this._continuity.hasCompleteFrame?this._continuity.token:(this._continuityDataReady=e,this._continuityPaintToken=-1,this._stagedDeviceSnapshotToken=-1,this._resumeSettling=!0,this._continuity.beginCandidate(t,i))}_continuityStageValid(){const t=this._stageEl;return!!t&&t.clientWidth>0&&t.clientHeight>0}_continuityAssetsReady(){if(!this._model.length)return!1;const t=this._model.length?this._spaceModel():null;return!t?.bg?.href||this._signer.isReady(this.hass,t.bg.href)}_initialSpaceSelection(t){return function(t){const e=new Set(t.spaceIds.filter(t=>!!t));if(!e.size)return{id:null,source:"none"};const i=[["hash",!1===t.acceptHash?null:t.hashSpace],["current",t.preserveCurrent?t.currentSpace:null],["saved",t.savedSpace],["default",t.defaultSpace],["first",t.spaceIds[0]]];for(const[t,s]of i)if(s&&e.has(s))return{id:s,source:t};return{id:null,source:"none"}}({spaceIds:t.map(t=>t.id),hashSpace:this._hashSpace(),acceptHash:!this._hashApplied,currentSpace:this._space,preserveCurrent:this._hashApplied||this._navApplied||this._warmVpArmed,savedSpace:this._savedNav()?.space,defaultSpace:this._config?.default_floor})}_adoptInitialSpace(t){const e=this._initialSpaceSelection(t);return e.id?(this._space=e.id,"hash"===e.source&&(this._hashApplied=!0),"saved"===e.source&&(this._navApplied=!0),e):e}_candidateBackdrop(t,e=this._space){const i=Al(t),s=this._initialSpaceSelection(i).id||(i.some(t=>t.id===e)?e:i[0]?.id);return i.find(t=>t.id===s)?.bg?.href||""}_visualFrameFingerprint(){const t=this._stageEl,e=t?[t.clientWidth,t.clientHeight]:[0,0];return Bc([this._cfgRev,this._cfgContentFingerprint||qc(this._serverCfg),this._layoutRev,this._layoutContentFingerprint||qc(this._layout),this._space,this._mode,this._view,e,this._glowScreenBlend?"screen":"normal",this.hass?.themes?.darkMode??this.hass?.themes?.default_theme??""])}_settleContinuityFrame(){if(this._booting||!this._continuityStageValid())return;if(!this._continuity.hasCompleteFrame&&"steady"===this._continuity.state)return void(this._continuityAssetsReady()&&(this._renderSnapshotAt=Date.now(),this._continuity.markCompleteFrame(this._visualFrameFingerprint())));if(!this._continuityDataReady)return;if(!["holding","offline-stale","overlay-pending","overlay-visible","candidate-ready"].includes(this._continuity.state))return;const t=this._continuity.token;if(this._candidateDeviceSnapshot&&this._candidateDeviceSnapshot!==this._visibleDeviceSnapshot&&this._stagedDeviceSnapshotToken!==t)return this._stagedDeviceSnapshotToken=t,void this.requestUpdate();this._continuityPaintToken!==t&&(this._continuityPaintToken=t,this._continuity.candidateReady(t)&&this._continuity.commitAfterPaint(t,{updateComplete:()=>this.updateComplete,stageValid:()=>this.isConnected&&this._continuityStageValid(),assetsReady:()=>this._continuityAssetsReady(),frameFingerprint:()=>this._visualFrameFingerprint()}).then(e=>{e&&t===this._continuity.token?(this._resumeSettling=!1,this._renderSnapshotAt=Date.now(),this._candidateDeviceSnapshot&&(this._visibleDeviceSnapshot=this._candidateDeviceSnapshot),this._candidateDeviceSnapshot=null,this._stagedDeviceSnapshotToken=-1,this._warmSnapshot()):t===this._continuity.token&&(this._continuityPaintToken=-1,this._stagedDeviceSnapshotToken=-1,this._candidateDeviceSnapshot=null,this.requestUpdate())}))}_onBackdropLoaded(t,e){this._signer.markLoaded(this.hass,t,e),this._continuity.note("asset-ready"),this._continuityPaintToken=-1,"steady"!==this._continuity.state&&this.requestUpdate()}_renderRecoveryOverlay(){if(!this._continuity.overlayVisible&&"recovery-error"!==this._continuity.state)return G;const t="connection"===this._continuity.recoveryReason;return j`
t.stopPropagation()} @click=${t=>t.stopPropagation()} @@ -4818,17 +4822,23 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="da33c4f75cf2469e330b33f11fd5fa04d368 ${this._t("gs.north_letter")} ${null===t?"—":t+"°"} - `}_stageBg(t){const e=this._settingsDialog,i=this._spaceDialog,s=e?e.bgColor||"":es(this._settings,{bgColor:null});return(i&&"edit"===i.mode&&i.spaceId===this._space?i.bgColor||"":t.bgColor||"")||s}_stageBgHex(){const t=this._stageEl;if(t){const e=getComputedStyle(t).backgroundColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);if(e)return"#"+e.slice(1,4).map(t=>(+t).toString(16).padStart(2,"0")).join("")}return"#111111"}async _runAlignToGrid(){const t=this._alignDialog;if(t&&!t.busy&&this._serverCfg){this._clearGeometryGesture(),this._alignDialog={...t,busy:!0};try{this._saveConfigDebounced.pending()&&this._saveConfigDebounced.flush(),await this._writeChain;const e=await this.hass.callWS({type:"houseplan/plan/optimize",config:t.config,layout:t.layout,expected_config_rev:this._cfgRev,expected_layout_rev:this._layoutRev});this._serverCfg=t.config,this._layout=t.layout,this._geometryHistory.clear(),this._cfgRev=e?.config_rev??this._cfgRev+1,this._layoutRev=e?.layout_rev??this._layoutRev+1,this._canOptimizeUndo=!!e?.can_undo,this._undoKind=e?.can_undo?"optimize":null,this._dirtyPos.clear(),this._sentPos.clear(),this._cfgEpoch++,this._modelCache=null,this._frame=null,this._cacheSnapshot(),this._alignDialog=null,this.requestUpdate(),this._showToast(this._t("gs.align_done",{n:String(t.report.moved),m:String(t.report.migrated+t.report.canonicalized+t.report.wallsMerged+t.report.spansMerged)}))}catch(t){this._alignDialog&&(this._alignDialog={...this._alignDialog,busy:!1}),"conflict"===t?.code&&await Promise.all([this._reloadConfigOnly(!0),this._reloadLayoutOnly()]),this._showToast(this._t("toast.error",{err:this._errText(t)}))}}}async _undoPlanOptimization(){if(!this._canOptimizeUndo||this._optimizeUndoBusy)return;const t=this._undoKind;this._clearGeometryGesture(),this._optimizeUndoBusy=!0,this.requestUpdate();try{await this.hass.callWS({type:"houseplan/plan/optimize_undo",expected_config_rev:this._cfgRev,expected_layout_rev:this._layoutRev});const[e,i]=await Promise.all([this.hass.callWS({type:"houseplan/config/get"}),this.hass.callWS({type:"houseplan/layout/get"})]);this._serverCfg=e?.config||this._serverCfg,this._cfgRev=e?.rev??this._cfgRev,this._layout=i?.layout||this._layout,this._geometryHistory.clear(),this._layoutRev=i?.rev??this._layoutRev,this._canOptimizeUndo=!1,this._undoKind=null,this._cfgEpoch++,this._modelCache=null,this._frame=null,this._cacheSnapshot(),this.requestUpdate(),this._showToast(this._t("import"===t?"backup.import_undone":"gs.optimize_undone"))}catch(t){this._canOptimizeUndo=!1,this._undoKind=null,this._showToast(this._t("toast.error",{err:this._errText(t)}))}finally{this._optimizeUndoBusy=!1,this.requestUpdate()}}async _runBackupExport(){const t=this._backupExportDialog;if(t&&!t.busy){this._backupExportDialog={...t,busy:!0,error:""};try{const e=await this.hass.callWS({type:"houseplan/export/create",kind:t.kind,space_id:"space"===t.kind?this._space:void 0,card_version:sp}),i=new Blob([JSON.stringify(e.document,null,2)+"\n"],{type:"application/json;charset=utf-8"}),s=URL.createObjectURL(i),n=document.createElement("a");n.href=s,n.download=e.filename||`houseplan-${t.kind}.json`,n.style.display="none",document.body.append(n),n.click(),n.remove(),window.setTimeout(()=>URL.revokeObjectURL(s),0),this._backupExportDialog=null,this._showToast(this._t("backup.export_done"))}catch(t){this._backupExportDialog&&(this._backupExportDialog={...this._backupExportDialog,busy:!1,error:this._backupErrorText(t)})}}}async _pickBackupImport(t){const e=t.target,i=e.files?.[0];if(e.value="",i){this._settingsDialog=null,this._backupImportDialog={filename:i.name,size:i.size,token:"",preview:null,expectedConfigRev:this._cfgRev,expectedLayoutRev:this._layoutRev,duplicatePolicy:"skip",confirmMissing:!1,busy:!0,error:""};try{this._saveConfigDebounced.pending()&&this._saveConfigDebounced.flush(),await this._writeChain,this._persistLayout.pending()&&this._persistLayout.flush();const t="/api/houseplan/import/preview?duplicate_policy=skip",e={method:"POST",body:i,headers:{"content-type":"application/json"}},s=this.hass?.fetchWithAuth?await this.hass.fetchWithAuth(t,e):await fetch(t,{...e,headers:{...e.headers,...this.hass?.auth?.data?.access_token?{authorization:`Bearer ${this.hass.auth.data.access_token}`}:{}}}),n=await s.json().catch(()=>({}));if(!s.ok||n.error)throw Object.assign(new Error(n.message||n.error||`HTTP ${s.status}`),{code:n.error});this._backupImportDialog&&(this._backupImportDialog={...this._backupImportDialog,token:n.token,preview:n.preview,expectedConfigRev:n.expected_config_rev,expectedLayoutRev:n.expected_layout_rev,busy:!1})}catch(t){this._backupImportDialog&&(this._backupImportDialog={...this._backupImportDialog,busy:!1,error:this._backupErrorText(t)})}}}async _setBackupDuplicatePolicy(t){const e=this._backupImportDialog;if(e&&!e.busy&&e.token&&t!==e.duplicatePolicy){this._backupImportDialog={...e,duplicatePolicy:t,busy:!0,error:""};try{const i=await this.hass.callWS({type:"houseplan/import/revalidate",token:e.token,duplicate_policy:t});this._backupImportDialog&&(this._backupImportDialog={...this._backupImportDialog,preview:{...e.preview,...i.preview},expectedConfigRev:i.expected_config_rev,expectedLayoutRev:i.expected_layout_rev,confirmMissing:!1,busy:!1})}catch(t){this._backupImportDialog&&(this._backupImportDialog={...this._backupImportDialog,duplicatePolicy:e.duplicatePolicy,preview:e.preview,confirmMissing:e.confirmMissing,busy:!1,error:this._backupErrorText(t)})}}}async _applyBackupImport(){const t=this._backupImportDialog;if(!t||t.busy||!t.token||!t.preview)return;if(t.preview.confirmation_required&&!t.confirmMissing)return;this._backupImportDialog={...t,busy:!0,error:""};const e=this._space;try{const i=await this.hass.callWS({type:"houseplan/import/apply",token:t.token,expected_config_rev:t.expectedConfigRev,expected_layout_rev:t.expectedLayoutRev,duplicate_policy:t.duplicatePolicy,confirm_missing_content:t.confirmMissing}),[s,n]=await Promise.all([this.hass.callWS({type:"houseplan/config/get"}),this.hass.callWS({type:"houseplan/layout/get"})]);this._adoptStructuralResponses(s,n),this._geometryHistory.clear(),this._dirtyPos.clear(),this._sentPos.clear(),this._defPos={},this._cfgEpoch++,this._modelCache=null,this._frame=null,this._visibleDeviceSnapshot=null,this._candidateDeviceSnapshot=null,this._stagedDeviceSnapshotToken=-1,this._capturedSnapshotConfigEpoch=-1,this._regSignature="",this._signer.invalidate(this.hass),this._resign(),this._maybeRebuildDevices();const o=this._serverCfg?.spaces||[];this._space="space"===i.kind&&i.space_id?i.space_id:o.some(t=>t.id===e)?e:o[0]?.id||this._space,this._backupImportDialog=null,this._cacheSnapshot(),this.requestUpdate();const r="space"===i.kind?t.preview.counts:i.counts;this._showToast(this._t("space"===i.kind?"backup.space_done":"backup.full_done",{spaces:String(r?.spaces||0),rooms:String(r?.rooms||0),markers:String(r?.markers||0)}))}catch(t){if("conflict"===t?.code&&this._backupImportDialog?.token)try{const t=this._backupImportDialog,e=await this.hass.callWS({type:"houseplan/import/revalidate",token:t.token,duplicate_policy:t.duplicatePolicy});return void(this._backupImportDialog={...t,preview:{...t.preview,...e.preview},expectedConfigRev:e.expected_config_rev,expectedLayoutRev:e.expected_layout_rev,confirmMissing:!1,busy:!1,error:this._t("backup.revalidated")})}catch(e){t=e}this._backupImportDialog&&(this._backupImportDialog={...this._backupImportDialog,busy:!1,error:this._backupErrorText(t)})}}_renderBackupExportDialog(){const t=this._backupExportDialog,e=(this._serverCfg?.spaces||[]).find(t=>t.id===this._space);return j``}_stageBg(t){const e=this._settingsDialog,i=this._spaceDialog,s=e?e.bgColor||"":es(this._settings,{bgColor:null});return(i&&"edit"===i.mode&&i.spaceId===this._space?i.bgColor||"":t.bgColor||"")||s}_stageBgHex(){const t=this._stageEl;if(t){const e=getComputedStyle(t).backgroundColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);if(e)return"#"+e.slice(1,4).map(t=>(+t).toString(16).padStart(2,"0")).join("")}return"#111111"}async _runAlignToGrid(){const t=this._alignDialog;if(t&&!t.busy&&this._serverCfg){this._clearGeometryGesture(),this._alignDialog={...t,busy:!0};try{this._saveConfigDebounced.pending()&&this._saveConfigDebounced.flush(),await this._writeChain;const e=await this.hass.callWS({type:"houseplan/plan/optimize",config:t.config,layout:t.layout,expected_config_rev:this._cfgRev,expected_layout_rev:this._layoutRev});this._serverCfg=t.config,this._layout=t.layout,this._geometryHistory.clear(),this._cfgRev=e?.config_rev??this._cfgRev+1,this._layoutRev=e?.layout_rev??this._layoutRev+1,this._canOptimizeUndo=!!e?.can_undo,this._undoKind=e?.can_undo?"optimize":null,this._dirtyPos.clear(),this._sentPos.clear(),this._cfgEpoch++,this._modelCache=null,this._frame=null,this._cacheSnapshot(),this._alignDialog=null,this.requestUpdate(),this._showToast(this._t("gs.align_done",{n:String(t.report.moved),m:String(t.report.migrated+t.report.canonicalized+t.report.wallsMerged+t.report.spansMerged)}))}catch(t){this._alignDialog&&(this._alignDialog={...this._alignDialog,busy:!1}),"conflict"===t?.code&&await Promise.all([this._reloadConfigOnly(!0),this._reloadLayoutOnly()]),this._showToast(this._t("toast.error",{err:this._errText(t)}))}}}async _undoPlanOptimization(){if(!this._canOptimizeUndo||this._optimizeUndoBusy)return;const t=this._undoKind;this._clearGeometryGesture(),this._optimizeUndoBusy=!0,this.requestUpdate();try{await this.hass.callWS({type:"houseplan/plan/optimize_undo",expected_config_rev:this._cfgRev,expected_layout_rev:this._layoutRev});const[e,i]=await Promise.all([this.hass.callWS({type:"houseplan/config/get"}),this.hass.callWS({type:"houseplan/layout/get"})]);this._serverCfg=e?.config||this._serverCfg,this._cfgRev=e?.rev??this._cfgRev,this._layout=i?.layout||this._layout,this._geometryHistory.clear(),this._layoutRev=i?.rev??this._layoutRev,this._canOptimizeUndo=!1,this._undoKind=null,this._cfgEpoch++,this._modelCache=null,this._frame=null,this._cacheSnapshot(),this.requestUpdate(),this._showToast(this._t("import"===t?"backup.import_undone":"gs.optimize_undone"))}catch(t){this._canOptimizeUndo=!1,this._undoKind=null,this._showToast(this._t("toast.error",{err:this._errText(t)}))}finally{this._optimizeUndoBusy=!1,this.requestUpdate()}}async _runBackupExport(){const t=this._backupExportDialog;if(t&&!t.busy){this._backupExportDialog={...t,busy:!0,error:""};try{const e=await this.hass.callWS({type:"houseplan/export/create",kind:t.kind,space_id:"space"===t.kind?this._space:void 0,..."space"===t.kind&&t.planOnly?{plan_only:!0}:{},card_version:sp}),i=new Blob([JSON.stringify(e.document,null,2)+"\n"],{type:"application/json;charset=utf-8"}),s=URL.createObjectURL(i),n=document.createElement("a");n.href=s,n.download=e.filename||`houseplan-${t.kind}.json`,n.style.display="none",document.body.append(n),n.click(),n.remove(),window.setTimeout(()=>URL.revokeObjectURL(s),0),this._backupExportDialog=null,this._showToast(this._t("backup.export_done"))}catch(t){this._backupExportDialog&&(this._backupExportDialog={...this._backupExportDialog,busy:!1,error:this._backupErrorText(t)})}}}async _pickBackupImport(t){const e=t.target,i=e.files?.[0];if(e.value="",i){this._settingsDialog=null,this._backupImportDialog={filename:i.name,size:i.size,token:"",preview:null,expectedConfigRev:this._cfgRev,expectedLayoutRev:this._layoutRev,duplicatePolicy:"skip",confirmMissing:!1,busy:!0,error:""};try{this._saveConfigDebounced.pending()&&this._saveConfigDebounced.flush(),await this._writeChain,this._persistLayout.pending()&&this._persistLayout.flush();const t="/api/houseplan/import/preview?duplicate_policy=skip",e={method:"POST",body:i,headers:{"content-type":"application/json"}},s=this.hass?.fetchWithAuth?await this.hass.fetchWithAuth(t,e):await fetch(t,{...e,headers:{...e.headers,...this.hass?.auth?.data?.access_token?{authorization:`Bearer ${this.hass.auth.data.access_token}`}:{}}}),n=await s.json().catch(()=>({}));if(!s.ok||n.error)throw Object.assign(new Error(n.message||n.error||`HTTP ${s.status}`),{code:n.error});this._backupImportDialog&&(this._backupImportDialog={...this._backupImportDialog,token:n.token,preview:n.preview,expectedConfigRev:n.expected_config_rev,expectedLayoutRev:n.expected_layout_rev,busy:!1})}catch(t){this._backupImportDialog&&(this._backupImportDialog={...this._backupImportDialog,busy:!1,error:this._backupErrorText(t)})}}}async _setBackupDuplicatePolicy(t){const e=this._backupImportDialog;if(e&&!e.busy&&e.token&&t!==e.duplicatePolicy){this._backupImportDialog={...e,duplicatePolicy:t,busy:!0,error:""};try{const i=await this.hass.callWS({type:"houseplan/import/revalidate",token:e.token,duplicate_policy:t});this._backupImportDialog&&(this._backupImportDialog={...this._backupImportDialog,preview:{...e.preview,...i.preview},expectedConfigRev:i.expected_config_rev,expectedLayoutRev:i.expected_layout_rev,confirmMissing:!1,busy:!1})}catch(t){this._backupImportDialog&&(this._backupImportDialog={...this._backupImportDialog,duplicatePolicy:e.duplicatePolicy,preview:e.preview,confirmMissing:e.confirmMissing,busy:!1,error:this._backupErrorText(t)})}}}async _applyBackupImport(){const t=this._backupImportDialog;if(!t||t.busy||!t.token||!t.preview)return;if(t.preview.confirmation_required&&!t.confirmMissing)return;this._backupImportDialog={...t,busy:!0,error:""};const e=this._space;try{const i=await this.hass.callWS({type:"houseplan/import/apply",token:t.token,expected_config_rev:t.expectedConfigRev,expected_layout_rev:t.expectedLayoutRev,duplicate_policy:t.duplicatePolicy,confirm_missing_content:t.confirmMissing}),[s,n]=await Promise.all([this.hass.callWS({type:"houseplan/config/get"}),this.hass.callWS({type:"houseplan/layout/get"})]);this._adoptStructuralResponses(s,n),this._geometryHistory.clear(),this._dirtyPos.clear(),this._sentPos.clear(),this._defPos={},this._cfgEpoch++,this._modelCache=null,this._frame=null,this._visibleDeviceSnapshot=null,this._candidateDeviceSnapshot=null,this._stagedDeviceSnapshotToken=-1,this._capturedSnapshotConfigEpoch=-1,this._regSignature="",this._signer.invalidate(this.hass),this._resign(),this._maybeRebuildDevices();const o=this._serverCfg?.spaces||[];this._space="space"===i.kind&&i.space_id?i.space_id:o.some(t=>t.id===e)?e:o[0]?.id||this._space,this._backupImportDialog=null,this._cacheSnapshot(),this.requestUpdate();const r="space"===i.kind?t.preview.counts:i.counts;this._showToast(this._t("space"===i.kind?"backup.space_done":"backup.full_done",{spaces:String(r?.spaces||0),rooms:String(r?.rooms||0),markers:String(r?.markers||0)}))}catch(t){if("conflict"===t?.code&&this._backupImportDialog?.token)try{const t=this._backupImportDialog,e=await this.hass.callWS({type:"houseplan/import/revalidate",token:t.token,duplicate_policy:t.duplicatePolicy});return void(this._backupImportDialog={...t,preview:{...t.preview,...e.preview},expectedConfigRev:e.expected_config_rev,expectedLayoutRev:e.expected_layout_rev,confirmMissing:!1,busy:!1,error:this._t("backup.revalidated")})}catch(e){t=e}this._backupImportDialog&&(this._backupImportDialog={...this._backupImportDialog,busy:!1,error:this._backupErrorText(t)})}}_renderBackupExportDialog(){const t=this._backupExportDialog,e=(this._serverCfg?.spaces||[]).find(t=>t.id===this._space);return j`this._backupExportDialog=null}>
${this._t("backup.export_hint")}
+ ${"space"===t.kind&&e?j``:G}
${this._t("backup.privacy_warning")}
${t.error?j``:G}
@@ -4849,6 +4859,7 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="da33c4f75cf2469e330b33f11fd5fa04d368 ${e?j`
${this._t("full"===e.kind?"backup.full":"backup.current_space")} + ${e.plan_only?j`${this._t("backup.plan_only_preview")}`:G} ${this._t("same"===e.source?"backup.same_source":"backup.foreign_source")} ${this._t("backup.created",{value:e.created_at||"—"})} ${this._t("backup.versions",{card:e.card_version||"—",integration:e.integration_version||"—",model:String(e.model_version??"—")})} diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 234776d4..a6b8b344 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -539,7 +539,7 @@ spans are clipped per atomic body. | `houseplan/files/migrate` | `from_id`, `to_id` | `{mapping}` — COPY, never move | | `houseplan/files/cleanup` | `marker_id`, `keep?` | replacement-only collection | | `houseplan/content/sign` | `paths[]` | `{urls}` — authSig for ``/`` fetches | -| `houseplan/export/create` | `kind`, `space_id?`, `card_version` | consistent versioned JSON document + safe filename | +| `houseplan/export/create` | `kind`, `space_id?`, `plan_only?`, `card_version` | consistent versioned JSON document + safe filename; plan-only is valid only for one space | | `houseplan/import/revalidate` | preview `token`, `duplicate_policy?` | refreshed bounded preview and current expected revisions | | `houseplan/import/apply` | token, both expected revisions, content confirmation | crash-resumable paired config/layout commit; full import gets one-deep undo | @@ -570,6 +570,14 @@ versions, and retains the parsed candidate only in memory for ten minutes. Its opaque token is bound to the HA user, normalized-candidate digest and the exact config/layout revisions. Parsed candidates are capped globally as well as per user. + +Plan-only export is a server-owned, fail-closed projection rather than a +client-side scrub. It removes every marker and all device layout, preserves +only canonical room-label placements, and copies one space through explicit +geometry/presentation allowlists. The parser recomputes that projection and +its placement manifest before showing a plan-only preview, so manually adding +a private field while keeping `transfer.plan_only: true` is rejected. + The browser never parses imported configuration. Full import and maintenance share the `optimize_pending` crash-recovery intent and the one-deep backup slot; the backup carries `kind: optimize|import`, while every layout-store writer diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index de798a43..33b33bf4 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -2,6 +2,11 @@ ## Unreleased +- Current-space export can now create a **Plan only** JSON template with rooms, + walls, openings, decor, backdrop and room-label positions, while removing + devices and structural Home Assistant bindings. Import preview identifies + the template before adding it as a new space + ([#167](https://github.com/Matysh/houseplan-card/issues/167)). - Composite appliances such as washing machines now use an explicit Home Assistant Status/Run state/Job state to show the yellow working marker during an active cycle. Power-on alone remains neutral, Power-off still fades stale diff --git a/docs/CHANGELOG.ru.md b/docs/CHANGELOG.ru.md index c56a921b..dfce9f3c 100755 --- a/docs/CHANGELOG.ru.md +++ b/docs/CHANGELOG.ru.md @@ -8,6 +8,11 @@ ## Unreleased +- Экспорт текущего пространства теперь может создать JSON-шаблон **«Только + планировка»** с комнатами, стенами, проёмами, декором, подложкой и позициями + подписей комнат, но без устройств и структурных привязок Home Assistant. + Предпросмотр помечает такой файл до добавления новым пространством + ([#167](https://github.com/Matysh/houseplan-card/issues/167)). - Составная техника, например стиральная машина, теперь использует явный Status/Run state/Job state Home Assistant и получает жёлтую подложку во время активного цикла. Одного Power=`on` по-прежнему недостаточно, Power=`off` diff --git a/docs/CONFIG-COMPATIBILITY.md b/docs/CONFIG-COMPATIBILITY.md index 47d8eabd..b2679314 100644 --- a/docs/CONFIG-COMPATIBILITY.md +++ b/docs/CONFIG-COMPATIBILITY.md @@ -69,6 +69,23 @@ that fallback: Import preview and apply therefore operate on the same normalized candidate. Explicit `static` and `daynight` survive same-instance and foreign transfer. +## Additive plan-only space transfer (#167) + +`houseplan/export/create` accepts `plan_only: true` only for a one-space +export. The resulting version-1 envelope adds `transfer.plan_only: true`, +contains no markers and retains only canonical `rl_` room-label +layout. Normal full/space exports never write `plan_only: false`, so their +existing document shape and lossless compatibility remain unchanged; an +absent field still means an ordinary export. + +Plan-only data is a fail-closed allowlist projection of supported geometry, +presentation and content references. Known Area, temperature/humidity, +opening and decor bindings are removed and recognized live-text references are +frozen as `—`. Import rejects a true flag on a full export, non-boolean values, +or any document whose projected config, layout, placement or content owner no +longer satisfies that privacy contract. There is no persisted config/layout +migration: the new field exists only in the portable envelope. + ## Legacy device tap action The historical marker token `tap_action: cover` remains accepted indefinitely. diff --git a/docs/USER-GUIDE.md b/docs/USER-GUIDE.md index 466be9f2..7c225ea7 100644 --- a/docs/USER-GUIDE.md +++ b/docs/USER-GUIDE.md @@ -507,6 +507,19 @@ assigns new internal IDs and adds the copy without replacing global settings. Internal uploaded files are not embedded in JSON; an import to another HA instance must explicitly detach those links. +For **Current space**, enable **Plan only** to transfer the architectural +template without devices or Home Assistant bindings. It keeps rooms, walls, +openings, decor, backdrop transforms and manually positioned room labels, but +removes real and virtual markers, device positions, Area assignments, +temperature/humidity sources and opening contacts/locks. Live values in text +labels become `—`; surrounding static text stays intact. The import preview +marks this file as plan-only and adds it through the normal space-import flow. + +Plan-only is not full anonymisation: space and room names, static text, file +names, exact coordinates and external URLs remain in the JSON. Internal plan +files are still referenced rather than embedded and may need to be detached on +another Home Assistant instance. + ### Storage locations | Data | Location | diff --git a/docs/USER-GUIDE.ru.md b/docs/USER-GUIDE.ru.md index ba3f0e02..3d5972a3 100644 --- a/docs/USER-GUIDE.ru.md +++ b/docs/USER-GUIDE.ru.md @@ -1158,6 +1158,14 @@ show_signal: true импорте оно получает новые внутренние ID и добавляется рядом с существующими; глобальные настройки не меняются и отмена не создаётся. +Для текущего пространства можно включить **Только планировка**. Такой файл +сохраняет комнаты, стены, проёмы, декор, трансформации подложки и вручную +расставленные подписи комнат, но удаляет реальные и виртуальные маркеры, +позиции устройств, Area, источники температуры/влажности и контакты/замки +проёмов. Живые значения в текстовых подписях заменяются на `—`, а окружающий +статический текст остаётся. Предпросмотр явно помечает файл как планировку и +добавляет её обычным импортом пространства. + Файл сначала загружается в безопасный серверный предпросмотр: House Plan показывает тип, версии, количество объектов, источник и состояние ссылок, но ничего не записывает до подтверждения. Если привязка к устройству HA уже есть, @@ -1169,6 +1177,11 @@ JSON хранит названия, идентификаторы HA и точн переносе на другой экземпляр такие ссылки нужно явно согласиться отвязать. Текущие состояния устройств и маршруты пылесосов в экспорт не входят. +Режим «Только планировка» не является полной анонимизацией: в файле остаются +названия пространства и комнат, статический текст, имена файлов, точные +координаты и внешние URL. Структурные привязки Home Assistant при этом не +переносятся. + ### Где лежат данные | Данные | Хранение | diff --git a/docs/specs/167-plan-only-export.md b/docs/specs/167-plan-only-export.md index f3159fd5..b4857f26 100644 --- a/docs/specs/167-plan-only-export.md +++ b/docs/specs/167-plan-only-export.md @@ -37,14 +37,15 @@ Issue: [#167](https://github.com/Matysh/houseplan-card/issues/167) ## 2. Что человек увидит до и после -**До:** экспорт текущего пространства всегда содержит его маркеры, layout и +**До:** экспорт текущего пространства всегда содержит его маркеры, device layout и HA-привязки. Для чистого переноса пользователь должен вручную редактировать JSON, рискуя повредить структуру или случайно оставить идентификаторы. **После:** рядом с выбором текущего пространства доступен выключенный по умолчанию флажок «Plan only». В этом режиме файл сохраняет переносимую -планировку, но не содержит маркеров, layout и известных структурных -HA-привязок. Preview импорта явно сообщает, что файл содержит только +планировку и вручную расставленные подписи комнат, но не содержит маркеров, +device layout и известных структурных HA-привязок. Preview импорта явно +сообщает, что файл содержит только планировку; импорт добавляет новое несвязанное пространство существующим безопасным механизмом #50. @@ -72,7 +73,9 @@ HA-привязок. Preview импорта явно сообщает, что ф 1. опция «Plan only» только для экспорта текущего пространства; 2. schema-aware проекция переносимой геометрии и визуальных настроек; -3. полное удаление маркеров, marker layout и структурных HA-привязок; +3. полное удаление реальных и виртуальных маркеров, marker/auto-device/light- + group layout и структурных HA-привязок при сохранении безопасных позиций + подписей комнат `rl_`; 4. статическая нейтрализация live-text токенов по решению владельца; 5. аддитивный признак `transfer.plan_only: true` в JSON; 6. строгая проверка plan-only инварианта при чтении файла; @@ -96,6 +99,9 @@ HA-привязок. Preview импорта явно сообщает, что ф - сопоставление Area, устройств и сущностей при импорте; - перенос marker icon, actions, vacuum paths, runtime states, histories, trails, known/new-device bookkeeping; +- сохранение виртуальных маркеров вроде пользовательских заметок «Котёл»: + `binding: virtual` не делает marker частью архитектурной геометрии, поэтому + он удаляется вместе со всеми остальными маркерами; - создание PDF/изображения чистого плана — это сценарий #53; - дополнительное privacy-предупреждение специально для plan-only; - изменение редакторов, View, kiosk или touch-жестов; @@ -129,7 +135,8 @@ HA-привязок. Preview импорта явно сообщает, что ф Для plan-only файла preview: - явно показывает информационную строку «Файл содержит только планировку»; -- показывает `markers = 0`, `layout = 0`, device/entity/virtual bindings = 0; +- показывает `markers = 0`, device/entity/virtual bindings = 0, а `layout` + считает только сохранённые позиции подписей комнат; - не показывает duplicate policy, поскольку дубликатов устройств нет; - не показывает missing Area, поскольку `room.area` очищен; - показывает обычные counts комнат, стен, проёмов, декора и content; @@ -154,9 +161,21 @@ HA-привязок. Preview импорта явно сообщает, что ф }, "payload": { "config": { "spaces": ["…"], "markers": [] }, - "layout": {} + "layout": { + "rl_room-kitchen": { "x": 0.42, "y": 0.31, "s": "floor-1" } + } }, - "placement_manifest": [], + "placement_manifest": [ + { + "layout_id": "rl_room-kitchen", + "space_id": "floor-1", + "owner": "room_label", + "owner_id": "room-kitchen", + "binding": null, + "label": null, + "icon": null + } + ], "content_manifest": ["…"] } ``` @@ -196,8 +215,8 @@ HA-привязок. Preview импорта явно сообщает, что ф | Источник | Результат plan-only | |---|---| | `config.markers` | `[]`; marker config целиком отсутствует | -| `payload.layout` | `{}`; удаляются все позиции, включая room labels | -| `placement_manifest` | `[]` | +| `payload.layout` | только `rl_` для комнаты экспортируемого пространства; marker, `v_*`, `lg_*`, auto-device и неизвестные позиции удаляются | +| `placement_manifest` | только canonical `room_label` entries, точно соответствующие сохранённым `rl_*` ключам | | marker attachment/content entries | отсутствуют | | `room.area` | отсутствует или canonical unbound value | | `room.settings.temp_source` | отсутствует | @@ -208,6 +227,9 @@ HA-привязок. Preview импорта явно сообщает, что ф | valid live tokens и legacy `{}` в `decor.text` | заменены на `—` | | `known_devices` / `new_device_ids` | не переносятся | +Реальные и виртуальные markers удаляются одинаково: `binding: virtual`, имя или +статичная иконка не переводят marker в архитектурный decor. + `flip_h`, `flip_v` и другие геометрические параметры проёма не являются HA-binding behavior и сохраняются. @@ -264,7 +286,12 @@ presentation, user content или HA binding. Это fail-closed защита о `kind == "space" && transfer.plan_only == true` он дополнительно проверяет: - ровно одно пространство; -- `markers == []`, `layout == {}`, `placement_manifest == []`; +- `markers == []`; +- каждый layout key строго равен `rl_` существующей комнаты + экспортируемого пространства, а `pos.s` равен id этого пространства; +- каждый placement entry canonical: `owner == "room_label"`, `owner_id` + совпадает с room id, `binding|label|icon == null`, и set записей точно + совпадает с layout; - отсутствие marker-owned content; - отсутствие Area/temp/hum/opening/decor legacy bindings; - отсутствие валидных live-text токенов и legacy `{}`; @@ -282,7 +309,8 @@ presentation, user content или HA binding. Это fail-closed защита о title и не меняет global settings. - Content availability/detach повторно проверяется перед apply под действующим lock по контракту #50. -- Apply не добавляет маркеры и layout, а комнаты остаются unbound. +- Apply не добавляет маркеры; существующий remap переносит только room-label + layout на новые room/space ids, а комнаты остаются unbound. - Events, revision conflict, token ownership/expiry и capacity limits не меняются. @@ -323,8 +351,9 @@ first по `TOUCH-SUPPORT`, но диалог не должен переполн 1. При Current space пользователь видит выключенный «Plan only»; при Full backup опции нет, а request не содержит true. -2. Plan-only export содержит одно пространство, `markers: []`, `layout: {}`, - empty placement manifest и `transfer.plan_only: true`. +2. Plan-only export содержит одно пространство, `markers: []`, только валидные + `rl_` layout/room-label placement entries и + `transfer.plan_only: true`. 3. Геометрия rooms/walls/drafts/partitions/columns/openings/open spans, decor/backdrop и переносимые визуальные настройки сохраняются по allowlist. 4. Area, temperature/humidity source, opening contact/lock/invert, marker data, @@ -334,11 +363,12 @@ first по `TOUCH-SUPPORT`, но диалог не должен переполн 6. Названия, статический текст, filenames и external URLs сохраняются; UX не обещает полную анонимизацию и не добавляет отдельного предупреждения. 7. Импорт plan-only файла на чистый целевой instance создаёт новое пространство - с той же планировкой, нулём устройств/позиций/привязок и unbound rooms. + с той же планировкой, нулём устройств/HA-привязок, remap-нутыми позициями + подписей комнат и unbound rooms. 8. Preview и revalidate явно сохраняют `plan_only: true`, показывают нулевые binding counts и не предлагают duplicate policy. -9. File с true, но с маркером, layout или известной HA-привязкой отклоняется - как `invalid_format` до preview. +9. File с true, но с маркером, не-room-label layout, несогласованным placement + или известной HA-привязкой отклоняется как `invalid_format` до preview. 10. Обычные full/space export и import проходят неизменённые regression fixtures; normal space document не получает `plan_only: false`. 11. RU/EN тексты синхронны, checkbox доступен с клавиатуры и диалог проходит @@ -361,11 +391,13 @@ first по `TOUCH-SUPPORT`, но диалог не должен переполн - export normal space с фиксированным временем — прежний fixture без нового поля; - plan-only projection полного representative space со всеми типами geometry, - marker/layout, room/opening bindings, modern и legacy live text; + real/virtual marker layout, safe room-label layout, room/opening bindings, + modern и legacy live text; - preserve static names/text/URLs/content owner и drop marker attachments; - reject `plan_only=true` для full; - reject non-boolean plan_only; -- reject forged plan-only files по одному для marker, layout, room area, +- reject forged plan-only files по одному для marker, чужого/невалидного + room-label layout/placement, room area, temp/hum, opening refs, legacy decor refs и inline token; - preview/revalidate/apply happy path на same и foreign instance; - missing internal backdrop + detach confirmation по действующему контракту; @@ -383,7 +415,8 @@ first по `TOUCH-SUPPORT`, но диалог не должен переполн - import smoke: `plan_only` line есть, duplicate controls отсутствуют; - RU/EN i18n parity; - добавить/обновить deterministic golden scenarios для export dialog с - включённой опцией и import preview, поднять версию golden matrix; + включённой опцией и import preview; после слитого #166 поднять жёстко + проверяемый `GOLDEN_MATRIX_VERSION` с 23 до 24; - review actual/expected/diff до принятия baseline. В implementation loop выполняются только действующие быстрые gates: @@ -396,8 +429,8 @@ guard, получить non-zero и восстановить файл: 1. оставить один inline live token либо `room.area` в проекции — backend plan-only privacy test падает; -2. проигнорировать `plan_only` и вернуть marker/layout — projection/roundtrip - test падает; +2. проигнорировать `plan_only` и вернуть marker либо не-room-label layout — + projection/roundtrip test падает; 3. прогнать normal space export через lossy projection или записать `plan_only: false` — fixed ordinary-export regression падает; 4. не перенести `plan_only` через preview/revalidate или не показать строку — @@ -461,11 +494,14 @@ Push ветки выполняется после задачи; issue не за 1. `plan_only` — optional additive metadata внутри существующего export version, а не новый kind или новая версия формата. -2. Геометрический `flip_h|flip_v` сохраняется, contact-specific `invert` +2. Безопасные ручные позиции подписей комнат `rl_` сохраняются и + remap-ятся; весь остальной layout удаляется. +3. Реальные и виртуальные markers удаляются одинаково. +4. Геометрический `flip_h|flip_v` сохраняется, contact-specific `invert` удаляется вместе с binding. -3. Неизвестные поля в plan-only проекцию автоматически не попадают; обычный +5. Неизвестные поля в plan-only проекцию автоматически не попадают; обычный export остаётся lossless. -4. Privacy invariant относится к структурным HA-полям и валидным live tokens, +6. Privacy invariant относится к структурным HA-полям и валидным live tokens, но не к произвольным пользовательским строкам. -5. Новый informational label/hint допустим; отдельное предупреждение или новое +7. Новый informational label/hint допустим; отдельное предупреждение или новое подтверждение по решению владельца запрещено. diff --git a/scripts/backend-test-guard.mjs b/scripts/backend-test-guard.mjs new file mode 100644 index 00000000..d3331c92 --- /dev/null +++ b/scripts/backend-test-guard.mjs @@ -0,0 +1,13 @@ +#!/usr/bin/env node +import { spawnSync } from 'node:child_process'; + +const pattern = process.argv[2]; +if (!pattern) { + console.error('usage: node scripts/backend-test-guard.mjs '); + process.exit(2); +} + +const result = spawnSync(process.env.PYTHON || 'python', [ + '-m', 'pytest', 'tests_backend/test_ha_import_export.py', '-q', '-k', pattern, +], { stdio: 'inherit' }); +process.exit(result.status ?? 2); diff --git a/scripts/mutation-gate.mjs b/scripts/mutation-gate.mjs index bba8f532..d002eb1f 100644 --- a/scripts/mutation-gate.mjs +++ b/scripts/mutation-gate.mjs @@ -165,6 +165,72 @@ export const MUTANTS = [ replace: ' glowEnabled: false, allLightsOff: true, northDeg: 0,', }], }, + { + id: 'plan-only-room-area-restored', + guard: 'node scripts/backend-test-guard.mjs plan_only_export_projects', + because: 'возврат room.area нарушает основное обещание чистого шаблона; ' + + 'projection/roundtrip тест обязан увидеть HA Area даже при нулевых markers', + patches: [{ + file: 'custom_components/houseplan/import_export.py', + find: '_ROOM_PLAN_FIELDS = ("id", "name", "open_to", "x", "y", "w", "h", "poly")', + replace: '_ROOM_PLAN_FIELDS = ("id", "name", "area", "open_to", "x", "y", "w", "h", "poly")', + }], + }, + { + id: 'plan-only-projector-bypassed', + guard: 'node scripts/backend-test-guard.mjs plan_only_export_projects', + because: 'игнорирование request-флага возвращает markers и device layout; ' + + 'полный representative export обязан доказать, что true включает lossy projector', + patches: [{ + file: 'custom_components/houseplan/import_export.py', + find: ' if plan_only:\n projected_space = _project_plan_only_space(space)', + replace: ' if False and plan_only:\n projected_space = _project_plan_only_space(space)', + }], + }, + { + id: 'ordinary-export-plan-only-false-emitted', + guard: 'node scripts/backend-test-guard.mjs ordinary_space_export_is_unchanged', + because: 'аддитивное поле не должно менять обычный space export даже значением false; ' + + 'fixed regression обязан сохранить прежнюю структуру документа', + patches: [{ + file: 'custom_components/houseplan/import_export.py', + find: '**({"plan_only": True} if plan_only else {}),', + replace: '**({"plan_only": plan_only}),', + }], + }, + { + id: 'plan-only-revalidate-flag-dropped', + guard: 'node scripts/backend-test-guard.mjs plan_only_export_projects', + because: 'revalidate не имеет права превращать plan-only preview в обычный space preview; ' + + 'roundtrip test проверяет сохранение флага после обновления revisions', + patches: [{ + file: 'custom_components/houseplan/import_export.py', + find: ' "plan_only": _transfer_plan_only(document),\n "counts":', + replace: ' "plan_only": False,\n "counts":', + }], + }, + { + id: 'plan-only-forged-file-trusted', + guard: 'node scripts/backend-test-guard.mjs forged_plan_only_privacy_claim', + because: 'флаг файла не является доказательством приватности; negative parser matrix ' + + 'обязана покраснеть, если schema-aware проверка больше не вызывается', + patches: [{ + file: 'custom_components/houseplan/import_export.py', + find: ' if plan_only:\n _validate_plan_only_document(document, config, layout, placement)', + replace: ' if False and plan_only:\n _validate_plan_only_document(document, config, layout, placement)', + }], + }, + { + id: 'plan-only-preview-label-hidden', + guard: 'node demo/smoke_backup_transfer.mjs', + because: 'валидный plan-only файл без явной строки в preview выглядит обычной копией; ' + + 'browser smoke обязан проверять пользовательский статус, а не только backend-флаг', + patches: [{ + file: 'src/houseplan-card.ts', + find: "${p.plan_only ? html`${this._t('backup.plan_only_preview')}` : nothing}", + replace: "${false ? html`${this._t('backup.plan_only_preview')}` : nothing}", + }], + }, ]; // --- механика --------------------------------------------------------------- diff --git a/src/houseplan-card.ts b/src/houseplan-card.ts index b86ecc30..a47cb356 100755 --- a/src/houseplan-card.ts +++ b/src/houseplan-card.ts @@ -1396,7 +1396,7 @@ class HouseplanCard extends LitElement { busy: boolean; } | null = null; private _backupExportDialog: { - kind: 'full' | 'space'; busy: boolean; error: string; + kind: 'full' | 'space'; planOnly: boolean; busy: boolean; error: string; } | null = null; private _backupImportDialog: { filename: string; size: number; token: string; preview: any; @@ -13088,7 +13088,7 @@ class HouseplanCard extends LitElement { private _openBackupExport = (): void => { this._settingsDialog = null; - this._backupExportDialog = { kind: 'full', busy: false, error: '' }; + this._backupExportDialog = { kind: 'full', planOnly: false, busy: false, error: '' }; }; private async _runBackupExport(): Promise { @@ -13100,6 +13100,7 @@ class HouseplanCard extends LitElement { type: 'houseplan/export/create', kind: d.kind, space_id: d.kind === 'space' ? this._space : undefined, + ...(d.kind === 'space' && d.planOnly ? { plan_only: true } : {}), card_version: CARD_VERSION, }); const blob = new Blob([JSON.stringify(response.document, null, 2) + '\n'], { @@ -13285,7 +13286,8 @@ class HouseplanCard extends LitElement {
${this._t('backup.export_hint')}
+ ${d.kind === 'space' && currentSpace ? html`` : nothing}
${this._t('backup.privacy_warning')}
${d.error ? html`` : nothing}
@@ -13320,6 +13329,7 @@ class HouseplanCard extends LitElement { ${p ? html`
${this._t(p.kind === 'full' ? 'backup.full' : 'backup.current_space')} + ${p.plan_only ? html`${this._t('backup.plan_only_preview')}` : nothing} ${this._t(p.source === 'same' ? 'backup.same_source' : 'backup.foreign_source')} ${this._t('backup.created', { value: p.created_at || '—' })} ${this._t('backup.versions', { diff --git a/src/i18n/en.json b/src/i18n/en.json index bdf9463a..74d9e14d 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -825,6 +825,9 @@ "backup.current_space": "Current space", "backup.current_space_title": "Current space: {title}", "backup.no_current_space": "No current space", + "backup.plan_only": "Plan only", + "backup.plan_only_hint": "Keep rooms, walls, openings, decor and room-label positions without devices or Home Assistant bindings.", + "backup.plan_only_preview": "This file contains the plan only", "backup.privacy_warning": "The archive keeps names, Home Assistant identifiers and exact coordinates. Internal plans and attachments are referenced, not embedded; runtime states and vacuum trails are not included.", "backup.download": "Download JSON", "backup.export_done": "Backup downloaded", diff --git a/src/i18n/ru.json b/src/i18n/ru.json index 3ab02c72..12d7cbba 100644 --- a/src/i18n/ru.json +++ b/src/i18n/ru.json @@ -825,6 +825,9 @@ "backup.current_space": "Текущее пространство", "backup.current_space_title": "Текущее пространство: {title}", "backup.no_current_space": "Нет текущего пространства", + "backup.plan_only": "Только планировка", + "backup.plan_only_hint": "Сохранить комнаты, стены, проёмы, декор и позиции подписей комнат без устройств и привязок Home Assistant.", + "backup.plan_only_preview": "Файл содержит только планировку", "backup.privacy_warning": "Архив сохраняет названия, идентификаторы Home Assistant и точные координаты. Внутренние планы и вложения указываются ссылками, но не вкладываются; текущие состояния и маршруты пылесосов не включаются.", "backup.download": "Скачать JSON", "backup.export_done": "Резервная копия скачана", diff --git a/src/styles.ts b/src/styles.ts index aee66714..773366e4 100644 --- a/src/styles.ts +++ b/src/styles.ts @@ -2867,6 +2867,10 @@ export const cardStyles = css` .backupupload > .btn { width: 100%; justify-content: center; } .backupupload input { display: none; } .backupbody { min-width: 0; } + .backupplanonly { margin-inline-start: var(--sp-4) !important; align-items: flex-start !important; } + .backupplanonly > span:first-of-type { display: grid; gap: 2px; white-space: normal; } + .backupplanonly small { color: var(--secondary-text-color); line-height: 1.35; } + .backupplanonlystatus { color: var(--hp-accent) !important; font-weight: 700; } .backupfile, .backupsummary, .backupcontent { display: flex; flex-direction: column; diff --git a/test/golden-matrix.test.mjs b/test/golden-matrix.test.mjs index 6076247a..eb49d64f 100644 --- a/test/golden-matrix.test.mjs +++ b/test/golden-matrix.test.mjs @@ -85,7 +85,7 @@ test('golden matrix covers required geometry, rendering and adaptive surfaces', 'fill-light', 'fill-temp', 'fill-lqi', 'lighting', 'hover', 'zoom-040', 'zoom-250', 'warm-remount', 'dialog-mobile', 'color-popover', 'tray-wide', 'tray-medium', 'sun-window', 'tray-narrow', 'opaque-glow-two-doorways', 'filled-tunnel', 'opening-placement', - 'backup-full', 'backup-space', 'value-badge-positions', 'isometric-geometry', + 'backup-full', 'backup-space', 'backup-plan-only', 'value-badge-positions', 'isometric-geometry', 'isometric-live-layers', 'isometric-no-borders', 'isometric-touch-kiosk', 'isometric-large-warm-remount', 'split-corner-wall', 'plan-snap-endpoint', 'plan-snap-line-gaps', 'wall-junctions', 'isometric-wall-junctions', @@ -193,7 +193,7 @@ test('sun-ray golden requires browser-painted light from a state-only sun entity assert.ok(scenario); const fixture = prepareGoldenFixture(scenario); const space = fixture.config.spaces.find((item) => item.id === scenario.space); - assert.equal(GOLDEN_MATRIX_VERSION, 23); + assert.equal(GOLDEN_MATRIX_VERSION, 24); assert.equal(space.settings.sun_rays, true); assert.equal(scenario.northDeg, 90, 'the sign-sensitive golden must keep a non-zero north direction'); diff --git a/tests_backend/test_ha_import_export.py b/tests_backend/test_ha_import_export.py index fbe2d4cf..80199667 100644 --- a/tests_backend/test_ha_import_export.py +++ b/tests_backend/test_ha_import_export.py @@ -270,6 +270,249 @@ def test_full_export_has_versioned_envelope_and_live_layout(tmp_path: Path) -> N assert filename.startswith("houseplan-full-") and filename.endswith(".json") +def _plan_only_source() -> tuple[dict[str, Any], dict[str, Any]]: + config = _config() + space = config["spaces"][0] + space.update({ + "cell_cm": 5, + "plan_url": "https://example.invalid/floor.svg", + "plan_x": 0.1, + "plan_y": -0.2, + "plan_scale_x": 1.2, + "plan_scale_y": 0.8, + "plan_angle": 15, + "settings": { + "show_names": True, "north_deg": 90, "sun_rays": True, + "future_binding": "sensor.secret", + }, + "future_space": {"entity": "sensor.secret"}, + "openings": [{ + "id": "window", "type": "window", "x": 0.5, "y": 0, + "angle": 0, "length": 0.2, "contact": "binary_sensor.window", + "lock": "lock.window", "invert": True, "flip_h": True, + "future_opening": "sensor.secret", + }], + "walls": [{"key": "wall-1", "cm": 20, "future_wall": "sensor.secret"}], + "room_drafts": [{ + "id": "draft", "points": [[0, 0], [0.2, 0]], + "segments": [{"cm": 10, "future_segment": "sensor.secret"}], + }], + "partitions": [{"id": "partition", "a": [0, 0.5], "b": [1, 0.5], "cm": 12}], + "wall_columns": [{"id": "column", "shape": "circle", "center": [0.2, 0.2], "cm": 30}], + "open_spans": [{"a": [0.4, 0], "b": [0.6, 0], "future_span": "sensor.secret"}], + "decor": [ + { + "id": "modern", "kind": "text", "x": 0.2, "y": 0.2, + "text": "Temp {sensor.kitchen} / {climate.hall.current_temperature} °C", + "future_decor": "sensor.secret", + }, + { + "id": "legacy", "kind": "text", "x": 0.3, "y": 0.3, + "text": "Tank {} / {}", "entity": "sensor.tank", + "attr": "level", "unit": "%", + }, + { + "id": "static", "kind": "text", "x": 0.4, "y": 0.4, + "text": "Literal {not a reference} and sensor.user_text", + }, + { + "id": "furniture", "kind": "furniture", "symbol": "sofa", + "x": 0.5, "y": 0.5, "w": 0.2, "h": 0.1, "angle": 10, + }, + ], + }) + room = space["rooms"][0] + room.update({ + "area": "living-area", + "settings": { + "fill_mode": "custom", "custom_fill": {"c": "#123456", "a": 0.4}, + "temp_source": "sensor.room_temp", "hum_source": "sensor.room_humidity", + "future_room_binding": "sensor.secret", + }, + "future_room": "sensor.secret", + }) + config["markers"].append({ + "id": "note", "binding": "virtual", "space": "ground", + "room_id": "living", "name": "Boiler", "icon": "mdi:fire", + }) + layout = { + "lamp": {"x": 0.4, "y": 0.5, "s": "ground"}, + "note": {"x": 0.2, "y": 0.3, "s": "ground"}, + "lg_light.group": {"x": 0.1, "y": 0.1, "s": "ground"}, + "auto-device": {"x": 0.8, "y": 0.8, "s": "ground"}, + "rl_living": {"x": 0.45, "y": 0.55, "s": "ground", "future": "drop"}, + "rl_other": {"x": 0.1, "y": 0.1, "s": "other"}, + } + return config, layout + + +def test_plan_only_export_projects_geometry_and_round_trips_room_labels(tmp_path: Path) -> None: + config, layout = _plan_only_source() + runtime = SimpleNamespace(instance_id="instance-a", import_previews={}) + document, _ = create_export( + runtime, {"config": config}, {"layout": layout}, kind="space", + space_id="ground", plan_only=True, card_version="review", config_root=tmp_path, + ) + payload = document["payload"] + exported = payload["config"]["spaces"][0] + assert document["transfer"] == {"dropped_marker_links": 0, "plan_only": True} + assert payload["config"]["markers"] == [] + assert payload["layout"] == { + "rl_living": {"x": 0.45, "y": 0.55, "s": "ground"}, + } + assert document["placement_manifest"] == [{ + "layout_id": "rl_living", "space_id": "ground", "owner": "room_label", + "owner_id": "living", "binding": None, "label": None, "icon": None, + }] + assert document["content_manifest"][0]["url"] == "https://example.invalid/floor.svg" + assert all(item.get("owner") != "marker" for item in document["content_manifest"]) + + assert "future_space" not in exported + assert exported["settings"] == { + "show_names": True, "north_deg": 90, "sun_rays": True, "bg_mode": "static", + } + room = exported["rooms"][0] + assert "area" not in room and "future_room" not in room + assert room["settings"] == { + "fill_mode": "custom", "custom_fill": {"c": "#123456", "a": 0.4}, + } + opening = exported["openings"][0] + assert opening["flip_h"] is True + assert not {"contact", "lock", "invert", "future_opening"} & set(opening) + by_id = {item["id"]: item for item in exported["decor"]} + assert by_id["modern"]["text"] == "Temp — / — °C" + assert by_id["legacy"]["text"] == "Tank — / —" + assert not {"entity", "attr", "unit"} & set(by_id["legacy"]) + assert by_id["static"]["text"] == "Literal {not a reference} and sensor.user_text" + assert by_id["furniture"]["symbol"] == "sofa" + + parsed = parse_document(json.dumps(document).encode()) + preview = create_preview( + runtime, json.dumps(document).encode(), owner_id="alice", duplicate_policy="skip", + current_config_data={"config": {"spaces": [], "markers": []}, "rev": 0}, + current_layout_data={"layout": {}, "rev": 0}, config_root=tmp_path, + registry_snapshot={"areas": set()}, + ) + assert preview["preview"]["plan_only"] is True + assert preview["preview"]["counts"]["markers"] == 0 + assert preview["preview"]["counts"]["layout"] == 1 + assert preview["preview"]["duplicates"] == 0 + assert preview["preview"]["missing_areas"] == [] + assert preview["preview"]["bindings"] == { + "device": 0, "entity": 0, "virtual": 0, + "active": 0, "disabled": 0, "missing": 0, + } + candidate = get_candidate(runtime, preview["token"], "alice") + refreshed = revalidate_candidate( + candidate, {"config": {"spaces": [], "markers": []}, "rev": 2}, + {"layout": {}, "rev": 3}, duplicate_policy="skip", config_root=tmp_path, + ) + assert refreshed["preview"]["plan_only"] is True + merged, merged_layout, details = prepare_apply( + candidate, {"spaces": [], "markers": []}, {}, confirm_missing_content=False, + ) + assert merged["markers"] == [] + imported_room = merged["spaces"][0]["rooms"][0] + assert imported_room.get("area") is None + assert merged_layout == { + "rl_" + imported_room["id"]: { + "x": 0.45, "y": 0.55, "s": details["space_id"], + }, + } + assert parsed["transfer"]["plan_only"] is True + + +def test_ordinary_space_export_is_unchanged_when_plan_only_is_false(tmp_path: Path) -> None: + config, layout = _plan_only_source() + runtime = SimpleNamespace(instance_id="instance-a") + implicit, _implicit_name = create_export( + runtime, {"config": config}, {"layout": layout}, kind="space", + space_id="ground", card_version="review", config_root=tmp_path, + ) + explicit, _explicit_name = create_export( + runtime, {"config": config}, {"layout": layout}, kind="space", + space_id="ground", plan_only=False, card_version="review", config_root=tmp_path, + ) + for value in (implicit, explicit): + value.pop("created_at") + assert implicit == explicit + assert "plan_only" not in implicit["transfer"] + assert implicit["payload"]["config"]["markers"] + assert set(implicit["payload"]["layout"]) == set(layout) + + +@pytest.mark.parametrize( + "mutation", + [ + "marker", "layout", "area", "temperature", "opening", "legacy_decor", + "live_token", "unknown", "placement", "marker_content", "invalid_content", + ], +) +def test_parser_rejects_forged_plan_only_privacy_claim( + tmp_path: Path, mutation: str, +) -> None: + config, layout = _plan_only_source() + document, _ = create_export( + SimpleNamespace(instance_id="instance-a"), {"config": config}, {"layout": layout}, + kind="space", space_id="ground", plan_only=True, + card_version="review", config_root=tmp_path, + ) + space = document["payload"]["config"]["spaces"][0] + if mutation == "marker": + document["payload"]["config"]["markers"].append({ + "id": "forged", "binding": "virtual", "name": "Forged", + }) + elif mutation == "layout": + document["payload"]["layout"]["sensor.secret"] = { + "x": 0.1, "y": 0.2, "s": "ground", + } + document["placement_manifest"].append({ + "layout_id": "sensor.secret", "space_id": "ground", + "owner": "auto_device", "owner_id": "sensor.secret", + "binding": "device:sensor.secret", "label": None, "icon": None, + }) + elif mutation == "area": + space["rooms"][0]["area"] = "secret-area" + elif mutation == "temperature": + space["rooms"][0].setdefault("settings", {})["temp_source"] = "sensor.secret" + elif mutation == "opening": + space["openings"][0]["contact"] = "binary_sensor.secret" + elif mutation == "legacy_decor": + space["decor"][0]["entity"] = "sensor.secret" + elif mutation == "live_token": + space["decor"][0]["text"] = "Leaked {sensor.secret}" + elif mutation == "unknown": + space["future_binding"] = "sensor.secret" + elif mutation == "placement": + document["placement_manifest"][0]["owner"] = "auto_device" + elif mutation == "marker_content": + document["content_manifest"].append({"owner": "marker"}) + elif mutation == "invalid_content": + document["content_manifest"].append("sensor.secret") + with pytest.raises(ImportFailure) as invalid: + parse_document(json.dumps(document).encode()) + assert invalid.value.code == "invalid_format" + + +@pytest.mark.parametrize("value", [1, "true", None, {}, []]) +def test_parser_requires_strict_plan_only_boolean(tmp_path: Path, value: Any) -> None: + document = _document(tmp_path, "space") + document["transfer"]["plan_only"] = value + with pytest.raises(ImportFailure) as invalid: + parse_document(json.dumps(document).encode()) + assert invalid.value.code == "invalid_format" + + +def test_plan_only_cannot_be_requested_for_full_export(tmp_path: Path) -> None: + with pytest.raises(ImportFailure) as invalid: + create_export( + SimpleNamespace(instance_id="instance-a"), {"config": _config()}, {"layout": {}}, + kind="full", space_id=None, plan_only=True, + card_version="review", config_root=tmp_path, + ) + assert invalid.value.code == "invalid_format" + + def test_full_export_import_round_trip_restores_model_version(tmp_path: Path) -> None: """The portable envelope version must return to the persisted config.""" document = _document(tmp_path) @@ -830,6 +1073,17 @@ async def test_export_and_revalidate_ws_endpoints_use_server_owned_state( assert document["model_version"] == PLAN_MODEL_VERSION assert "model_version" not in document["payload"]["config"] + plan_exported = _Connection() + await wsapi.ws_export_create.__wrapped__(hass, plan_exported, { + "id": 44, "type": "houseplan/export/create", "kind": "space", + "space_id": "ground", "plan_only": True, "card_version": "review", + }) + assert plan_exported.error is None and plan_exported.result + plan_document = plan_exported.result["document"] + assert plan_document["transfer"]["plan_only"] is True + assert plan_document["payload"]["config"]["markers"] == [] + assert plan_document["payload"]["layout"] == {} + refreshed = _Connection() await wsapi.ws_import_revalidate.__wrapped__(hass, refreshed, { "id": 41, "type": "houseplan/import/revalidate", From b369eb5dcd10ea841e38ca61271fc811b86d4455 Mon Sep 17 00:00:00 2001 From: Sergey Matyunin Date: Mon, 17 Aug 2026 12:34:45 +0300 Subject: [PATCH 3/5] test: preserve ordinary space layout scope Issue: #167 User-Visible: no --- tests_backend/test_ha_import_export.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests_backend/test_ha_import_export.py b/tests_backend/test_ha_import_export.py index 80199667..b2971d49 100644 --- a/tests_backend/test_ha_import_export.py +++ b/tests_backend/test_ha_import_export.py @@ -438,7 +438,9 @@ def test_ordinary_space_export_is_unchanged_when_plan_only_is_false(tmp_path: Pa assert implicit == explicit assert "plan_only" not in implicit["transfer"] assert implicit["payload"]["config"]["markers"] - assert set(implicit["payload"]["layout"]) == set(layout) + assert set(implicit["payload"]["layout"]) == { + key for key, pos in layout.items() if pos.get("s") == "ground" + } @pytest.mark.parametrize( From 2e4747361913c8aaa131937c5be8cd823f1b2704 Mon Sep 17 00:00:00 2001 From: Sergey Matyunin Date: Mon, 17 Aug 2026 12:36:47 +0300 Subject: [PATCH 4/5] docs: refresh source-bound screenshots Issue: #167 User-Visible: no --- docs/images/06-device-editor.png | Bin 292594 -> 292595 bytes docs/images/screenshots.json | 24 ++++++++++++------------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/images/06-device-editor.png b/docs/images/06-device-editor.png index 5910d291cb5f55dd1f9607844e33bdecb5d98952..fec6ef6660178a80b6d9f8ebff7b3220ae49dfc5 100644 GIT binary patch delta 147255 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�QTm=P1mjkzw0>r>@2_jud9bc( zc*s^J;*wgC+|M;U?E7m_vx&ClVmdfKv^}F#l3A!IQtZeFig!j?ik4ZTh_>GzSY@4W zOR05aR$ws6XgCQSJZ#G>@6jpwnaM_Y*Gl2{>}*HnpovLV5te)VuOF!uEi0Wp zM`_5ISA$U)TyPo|ZK_1P?cj+elJ?fv`sJCVkBaqu7>E46t8M#Ub@6XcKy1G+>?`7m&6>nG3{-cV z$IPZdSX;17Q(lM}a9%%ARj;_Zg{+|k$#mD3vy)*sYU09`pl6pW`C6>_qOcAK3(JDqKg^S)o;1ys+pi}HCYh*`jo|Fr#3LBoRJrW-zlzb3#nLliDZ>G9i3NOpET(%OzxVC?fy9(5T7sD_S zqAENin*w>)CsNFy=5Oo`h{!#&P}}P<<;bUOkoD;m~Y3fM3CO3K45s2gd5kVRJY==~CTuGctNa1r^S@ANjbMr1&df zIctAY?20>cV#Bc8|8&hV%mq5L1f+{%BkO&M+x)aV*!}~u_JObRWr!xK=V&^%sLK8q zLM({a*E3K)QO`r}qg}15o4vQYr)}OsVlN7H^C9|HzxTVRn@gTE{iVE;%L$CP9smdD z>`!a!*1FbmwwH}ze(e^h9?@zQq&IU4o_`qTepYEJ;<4pZj5vYyTGj1Xht^UAcBsn4 zNwxea-0}9F_5*}=AmOOx>Bqy&+IVzqK2i=L;eH37gUMCNLhZu*{H$^4C(O@kpgxj& zD#eQtf3lpfwKy~JQiPi6>@)eCEr2N#!3=)w1;w8pEj#nGv87>Xq2z{3SN-mp)>Jb3 zF^eONkz$X-N){oUU?_YjqnXx9{{K|<-ce0-;kPhNnxP6f5Q;RV385H@2xuVm7Mg%y zLg)~bB2o|2u1Doi%5jIWte& z&)!mO_|e0k9k;R|SDb(ccGrh{CwTWCtOovc3Upc0SDCnR;Y79fnFO#yn$X_n!@mGA zgoxU{eXLDY?8|E+FD|6no?f%^80v0Mx511xUJ&Fx@?AVZq?Dd$3(B z`XG++ptGO-MjlW200ao8NIXc{01tMXx#%c;QtvjU(7`n=lt-%Z%bksLJ#JjxCajtC zea9=aQ<}FI(OM@3o_3=hdh)+``7DkeLpv?Anfj+NFcV7VS5nfjM{#;Nd7{{`0DEVw zj88muu1RpxGh*_gHZQcI&hJR;Ch4JzGgniqtN4kv_AHY|< zSrR2n4(%_%8lIG^HHpMcqk^va=h)~drLT)$8)zxO73LagAzT`kMBl3tp_a?$6oZxx zFQjxqw>vGSJ1ay>ebmvnAZ@8+U|lYY*GPK&6GxfNr_UV4?Fk-~8GggkSJn)l)p4U^Llq;z^H~OT3*lGNW|F?y zb`=(x-z3gUv738=%P>8)3fTl{=_zv`q!w72dCnRh!5^e}-5E|OD&@ge44Ahb7w?Lj zUAw5ohe^L%Laks?V6&M?5HXlWwHs?>M5aIusBDtqa${2kQ;0Od z@pN$^o0t5jL=$Gq2^7rrMJXZGLL6B+Y+HLcm0pA^EyX+Y+z@&3wZmIA$G_4}sy6}z zYN+Ys1@PViaSg{E(byD|kO@z}wY(xb8|yOvLDq;U=QXm@5pl z3-gpCm=#3DgEa%xKh~EK=iu3&7lrOmf~^biX2io}G0_DHE!*hmHk&{gD(#TU`@Y&xS-P&!Uo5T zl)A}_q$m2W+m@XbJm;tSzAqA@=YnfIDt*O{0u~-kVQS*IezMa8uGUhgU@aCIH_X4lx&P%<|)BvV4N*i3ZPce!yA z<*TdQM9I&Wh{DWs`#JNWsUC8+JTA?`LxG0#W(1MoBURL&Ou74bdHs-ASI4m?6!%_l zMr4rv2I>15kN^u$fRY&5Sbr(@S&$+0il>mH=d#}mhQyvfan16|E*ETP2*?n- zZPkgEHW?OCL^_0Pc5;rcE?~ieMN}#D=Z(RI+Z}IJDia_GN58QtY}e%0-M3a5*O=kc zp>y5qQl?V#fbL9|lp%+ko65u-jU0Q_mxeNmaK|!xlubxUAARH2!7{O#T3zEVICcJJ zq`4%pE_2qJoLFT07JXRNX_>L^Q;ZyJ5D^TXc1aBGF!Rj2D9LG?;wh)2Q4qwlxYt6y zj&oDqw(hEWBYxzIeerMm?QkIXP>MxkjYgc0VY&0)vgGNDmXQ!Nb;YT$?H zUZ>jCf^~HctI?I89|!*Mq3bt~&%jx1+BZt-iLwfH7u3Vu{|5iR{BFT+cP{$(_U7gL z6K@-A0t3QiT8?DoL;htC-8?`47296>`0;B>OPQhkjplBF1INslV~L}@egDliwFUD2 z?c61$NX z5MwD)){7N+ic*h}_u>)`GU)d?o4K)Ymj^E17m(gf;rMt@N50qCG$Pn612Ws)i}W3y zPqzH}%_{kr6ZE70y#Jn~Z?@kXud;KgftK3%&o?SV(hX9D95+zzcMfSl7Be9bva097 z`-C#7UUtg=pA9DF{Ou1!iNhb7@d=sEXbAZ^=Ze)E))x5kSP<{(@vh+=7*NIg;)OZq zA6Hj^h<`&o;6c$wwmF1b(o^c0tvp0;DVv$r@nnrG?TaX?shL(gCVDSgW{5!%E4)Yo zpT>0s1UO+oHP0DI|&P50`>@q7l1-Yvg8(@(Qe{E=G&WW zuy6NFt#Q=erFAuo#beUDi1&!6#I5S8kdu9Zzw1ad#hYsC(*?`N6!47VM%J|dJI$8* zdf!vlP~WL6@Q0ib6Tiq|%b@{{626!PY_N3X?IeNFbhl5GlljVN2GUf7sB&jPtJNY* zFf3@rVu^nLm$hz2H#GgHmnA_uj2h!+p^qU`KK`<=_D5gaFo;&!Xo4s)aZhWqmCu>Z zbA=wch{+1soRTRQ4@EF@kvTKWb#~aA^`b=)JTt>XE=`KT(Y9*X=cz2+Owl!LWJ;2p zOI{0D6&P>SL#5}j(mq*ks8<^9PG*e!B(4A8s%7yvIv?oQCxY#Y7-w?GT68dp*gq9YCN6|libBr!$IpmQ*~1DH+alL zy-`Gpl?758?h)X_PJt=`wok2WZ2>kG;uV-5gc6QwXWWqMNiGSV&n2Mp^k+yF8Nup# z_b8-Y$nnI~o@7E%n>W|d+aaHfX0b0H`c`@I+Cjqi1wq9}wNg`mr?6-y#~JC62aR#4 z(dnf*REEBANj9CS#-rQS!y#e94l#`I>So`{4r<%+7Awo0rP%?1Hi zbO;}{`ykFzGkD=%><)5J>bVraP3Mxbk>Unq8H;!t<7N0_DMvat(l^kf+fXNd6)(+z z=I>3;`vF=PiCj}ihiHo+qE}@QePCf?_E&J_VD~CKdjwQncdkG@ck4i=;0hHwo?S*l zdfGOaPveybMK(ebDJA?@v9d6Z?_g;B{&NLT-qpQ+UaX(nO`A%%Fwy@5Mx|GcK~x&y zq7US)RY-L|8@kw~OSw^+*hq($F*HZ|CmU)ef=bL*4fFWvR(x^`83N(5KJuRwW96L;h$G{JJ755&03)7+KU|4J4*nP(XlqFCmqZPQ zBv!q7v+#Ai^JLeM(ica`Sln2i$Nt#7jCBk<89$jR3vIjybvL)M#2jis0rVwU=9L#1$Z{<6OiNnd@rf2{ND(Um9HQ zQCzi*gdOQ4BuUP>nD~W?e9r@C!DKY-naFxJr-CQtY+G8opz;iXjG~HfI$!Cqew~)K zT%>1`INJmf!2iN}1=V_el<<*AX(Amxcrb4G^KE7Yzv-Ar5rmksf&m+l^)LIj(l+4U%Ed=Qo*aT_xrJ@)Fro@ zJcb4wze;M`K-X8)E+ zaj&qRS+Z<|rWforEWuqoP$hC2|BwgZcF|#dNM|{`qM4cTQ+8T_i zWL(JMkV`;h1J8rNkboO>FDcsnX z?+ysn*)a~6T)=J@=4J}-bvdr+oxk!K3oCX;K0AXvh)jDeczoRP8BEu9nl5$m$l_~r zZ0=&EqRHp|gTD+Xw*TbhCgF)`(J5xv8*e*`T@Drd;cs-e#+G=&!#n`0RqMnY9OcAs z?u%P&U608$vc*4bjWEk-&hCjYQ5v6+9JZQGtk*#Eb|+(N-Vd%SLbz;hxW%RUWeiAy zP&!QJZz_)@A|nmJ%;bRdA+yrd-_y3y@5NwpR)Tmqg$q-0BPMgqe5>TS*DHk{eo(0d zT%&dbl6u0)jIZ(Yi!es=(w}rwv52bbUaPe6T1b~qDMeOJkySo-s#(2!cgq!NlosMa zfy$Isp|Rx@~S(1T0ph*)t|f!0L>caTQ9!I;BiZ84Hfn-ng(T$>k= z2p;7P%f|`p*nt5a^7?HOrM-E_y)0T-kvKBtpMG0qJi|qis9kZDfvGqN#aOyrNiRfV z=OkNt6jxNx!Ce&dGMnWET#$MxIn)FQ*$O)qx;gAlkH=8#@*enTy85avE0>JLrcQ%Q zulNJ%*kvcCk0Y2I7=5P2W2U8pwK54ghH&kX7a=gcf$_TEkC)w{oUnC+WR3 zwtnlpGS?O)D@6*g2*kn7<-;fWIXa$G{&uR7)rbP-lW{MbL zLT`z`A`D7@p=w}r65OH`6J0SfOwxMQxmzNWFU75T2rdRXqAw_9s6XKi1eXQ}d!s~Y zY43&hyHu{J@5j_-~soh$gy?N5rrX|Cv4wgdkAc~Hq^&?iYUh3P6ST6yb6tQlF&zdVG84&+@clv$G7@h@b$Heyq{JnBb9 zXXvmU1AhK?CoAx*Y1j$4HNAXcG6X`Xe9{k55V@O12$v^14?5C<8Vlxeb!>1cn#QFZ zSH@k-p|Nf%EJV6k9GEcFSjMsY3IJbHo^GSMht)p{5c4XY`-BPr7tA$5;U%RE@B+f0 z5M-QS${^@gCtITeXctD2YaTAzM5gPsB`vX1vh9_lzdBiockub{+`z>VJ0bY%)7pzT zA3{kk5AUclJ(}g66UtWjtkr)vYA{>!XM8Va=?OO8YHjse6?fVFT1G4h@cY5_hkDg0L0 z|10XE4DUbuf5WH$#ngNu)=TwC{1?Y_1+TS3n(?ip>WQWL-&EhNd-72CqB`czPkzx_1p!g1gZl82OTUVp zw=l2YC9S56%+Cg^A-$hcJ^0+j`EFxTImmijc6dIB5BYF~y@hT)Ko(WER$tuoO*+=M z3|VL1{t{`~!h&=2+ZX(B9n+U5{T~1k>s8!!9Y~Ct>@J%kV@R#;tE=&-dls?nDGPFw z=V0V%%RgFvxmUwCccw^%p=aXMp<&#nS z;zV7yfFONi`-t1@zQHLX!*!6T2b$A5)tq>vjIdwtxsZ#RxCdO6P0ML%Foz3*95$ie z!;JVrl1gWC3%&NLAtZ;L?Ck;A1!$yYu_YYRwJ?aLNwMKpse4r{A+kFnk@6439@sCq zDmbTyU;)QA18Ku4r z#3=fjuw#Y>J?x`1t=Xy*DbnhAU^edCm^lL)M(Sq8Zy@inkzJ_qhMNgV+{EXm#>Izs z)~1Uu&Ol_hzWubLg4MG+ZXlCI8eJ>9BNVbwVGE?>kJ%;&pwX3UB^NX7Bo<8ZCPihc z8OZT?Ul9fw<>U_CCRXI(_eYLV@>vjVLHd6l4UkqWLV=t9`A|Lfe2L?Cz#TraN+byw zm&;WP?oa(xAWK3TMkm0c`Sn|jxZ{Jk&)=Xgz!d9-&K+)^`f-3f{L+yRrTTv2xwE9A zNP_v3ZqGX%sbk@6W%$lP1UpriB;4SfQ9;4YW_P zq_7E?Mney7H>A5cBTiuCz;ty0J=GMHCozSuR3IPwX@b@@YG~FO*y!B`<}1Pgf2MWK zgQ3(Ujo-?$&nq->;7`=#!jgk{_Noq^GRvzBJtc=7LXd_ZZC%GigO3ijv# zutMsj=}{_dn0p*PSIC_Rn>nyfefmzQ09(Ap%obexdDZ*x&OL<_BX!D0NrqiO!&7=$$xJ$TLwm?&951SNn*IyidplPWAuVcPjyCQ2ReTjIVVelw8!4N7;L89rD! z#vbL0PP~HB0#2N6mbL>OXf$>{=FV@M<0l!Tcd=?&Ev*4L{Y1hP1`w-DZ|wkCPub zc+&3M1HW&2_3lMC;bk1h{cM4!CH@5y7@SiyrHm|@Rqq;|^?NDR=MDA?6tun^A6>X? zfdJpUB!n0FJWiUB*}Kf-``V-*GVU)kbuEhrF7fNk@)v{?@5GO`c=WO73InY1$wzsH z_APN!R9?nVjp=ow`LkWO4-&t3J1J0i$cZ{7hK2Pr2>9~qy1b;r*{d2qIxzA;lMh0* z&I*`Ob(D7(#Jt{NhRu{S7^;~oS$*`pFpl4%&nV>NCh!84r$Bs$v=jOI^9`*m zTEBn1D6OqaNj;*Fcg<7|jd^HTuRCDavgqiHY7OwdqLFMl$qVGjEKgu!A>-lrZ(R7C zGO_yP9bEl32`RK1={%50fkfkF{yJxJ8W~Qa#VL|4`h0~+38IOQ=oxYmt45B{P)2fqxqgN0yBhbCosIfO(v886vT~0;d9g-~ z*7U}mNGwhm`FP6V;M&k9@psKraDk2n?*0z3#x3PZXRhvNl7KE@JA|`$VtQE~G=syF zdONJvsS` z>5`@DC#~lrlADJ#{`2l^<9zgNOwBD$fQ<;2QJb0mn;`+E#7uw6^6?^9Fk8EknWBQCzFqu)8K-sQorK;BR66W<@(Z)rO zUkX*T=*7o_o7u7_l6G%Zq!t+!mq0+bLJ+7Wdo@fGD$v1}_fDefTo(t=DYoHp?Hp#> zj6ZAZ7W&#F{+-xbn%6zD$V=Te_mz4;yYXj}dueD|#_E25L>{zzC{8IlQ@wzj$xZ;_&XgB-&*=j`nY(6|b zCuYIddst#!cmc9rQCq~>oPxDa>v>kAtU4|2Gd7*4KuT!sC8&aYxaQ3gWD*a&_;oJD zElNlxD5TB(yb_W#`3Wwc2DNP0f|}<)v;K<4WDqK^2Z>4wekb!{lt1hOPw3HpstDR= z3ldk28X9v~#sm49#7?BAar2Bg*$H`k&JQ^813pQYb-kV10~5y!!ZxrJ!w9~gfR`h3 z{ynRAaA5=o)^)c9a{KML?1D+47sNNQJ^5a8b3^uQ7pqzp311th3`mYl!%j3+z>hlu!&1d`}@1tDC z*|^SGLrXU8?M#kO;=g-G6wuq;KV^O+xo_?3f=;p+G)ctNBt=;tD58|mS*i zvrT+^xSq2dW|6+ZAWokD)m~lYv|#PBCj=)yvbmMY)u1FxkL_2^;-}A%m#}NHzx=w> zw-cV(4wA1EU8bTqt}M5R)wS9VMgS_HH7wye_OT783tLU%ouv7PE0v(FO>5(N06N}vMfa(V-!Z8nv^~l{k z4);S(n@h0aq7LQ`j*&4knMBg*CT4lC()^*nF?Rvmz3)G~+9bn?JtQr2vxIXVUJVCC zw6SbmK|kZCRK+m!VR@T}4K8yZW~Z2$`?yE!maY0}fIF{CU$G2vK*a_AG0GF1Zg0Ou znKKckdnVjlp=!uR_b(q^EjY=`%)UcW&B6ffTM(9-e&w=(j|E_9!;CAUx|rzIP6z+d zJeO(t;xtN9Ftb5e9xlnSZ7iIw^(`!>VQ&-tF!jb)wU`uxC{Q=M{cG5(18Ul>mO5Zu zbWs4G&a9u$CRZs-cr9ZAO(Nw#??(wJsg<1)jk1ix9IA)AgHz)k9Y}l?7gV4BH#z^s@&f z#@ zo}Y>jlqkX7F7{b`VWbssMq(A~Snf?LsB^~7^d^UbzzetfT2YUse7FLBKQbM+**wjN+P0*7J;2K#w7-7BM@SVF-^ z5#m~+GRqe;kHp(5-R``!XLv3$3wmO44~;~$uJs4np9293yo!9NX+-kjpORCYT7g4mnLl&i>f`ik@5zgdtqP$xjUdK z;U<)P$x~|ouFnZ#^rH`SqSdeva8E_0xe{8&2~=Dgaoe;k`tf~nluVQ09ZaTbQA`%V1ld}d zr}(7Voconp%sJl{q-qwrLr1ETjZ_D^%MGHEzS-;yMWg1w>eQwlbC=pfWKM2#;ZapQ+3j?z5MQyfcVc=_INkN zbzO2t;p+OQ^9MWsYIo%uG$hmrhaTv5gp>2Vp9iL<(&3V#V@BXPhEz@uR|Aa;@TyuE z5XSy}U+CLM`lo|TSTwKG=*mYIRG5#037L+J2XC$ylR7}e)zT9Sr{BSH8tQxjKNpTs z$Wz8dC;1vEAq^eJwEm}V;l-9~qKr}kqbpoiz}IAwn*@sw@awl1n8 z+=J*mpGMimUzWgaPKX8s&$)${A9iyJd*WbZ4s2mZ(a&!N%Z zUW{59zs63MKnfnjIxVBR*g+gOrG6ehAh+7kpU=Bp z7R5gXh@j)-$a*h#lBC=SJ4!E|8l8ty#SJrMY#aU?jARXRy#0e93*93_1OL0I@_%mg yo16TA=4W83si^+_TljlTgo=ua{=ct`mP!!Cm&}%iGoW0d($_K3uDyed`u_m_!K?8A diff --git a/docs/images/screenshots.json b/docs/images/screenshots.json index 169acaea..fa1e0025 100644 --- a/docs/images/screenshots.json +++ b/docs/images/screenshots.json @@ -1,7 +1,7 @@ { "version": 1, "fixture": "synthetic-only", - "sourceFingerprint": "da33c4f75cf2469e330b33f11fd5fa04d36861af0c75eb110758b7fff2688fcb", + "sourceFingerprint": "7a788419e5d1a6e4b944a5d436d2cffd00795c3237791689b1401b73925353f8", "captureScriptSha256": "34f2219790d46efd8250e7a1bd829cb8fc0b0547e1260635fefa52407551b41b", "command": "npm run build && node demo/docs/capture.mjs", "scenarios": { @@ -13,7 +13,7 @@ }, "theme": "dark", "language": "en", - "sourceSha256": "da33c4f75cf2469e330b33f11fd5fa04d36861af0c75eb110758b7fff2688fcb", + "sourceSha256": "7a788419e5d1a6e4b944a5d436d2cffd00795c3237791689b1401b73925353f8", "imageSha256": "d36b6f9f8139f31ef73a780c6511a640a26055efd9d7a24c24fd48b1d8379bf0" }, "view-touch": { @@ -24,7 +24,7 @@ }, "theme": "dark", "language": "en", - "sourceSha256": "da33c4f75cf2469e330b33f11fd5fa04d36861af0c75eb110758b7fff2688fcb", + "sourceSha256": "7a788419e5d1a6e4b944a5d436d2cffd00795c3237791689b1401b73925353f8", "imageSha256": "358e25ff9984d0fb0c03cfbb848df40c425fdfe64cd5f4f613b1e754ca9d4249" }, "space-create": { @@ -35,7 +35,7 @@ }, "theme": "dark", "language": "en", - "sourceSha256": "da33c4f75cf2469e330b33f11fd5fa04d36861af0c75eb110758b7fff2688fcb", + "sourceSha256": "7a788419e5d1a6e4b944a5d436d2cffd00795c3237791689b1401b73925353f8", "imageSha256": "c53db2e5c642a5549c13f3c93a5b359fed69bdb2621bf71a243a877ffcb95e6b" }, "room-contour-close": { @@ -46,7 +46,7 @@ }, "theme": "dark", "language": "en", - "sourceSha256": "da33c4f75cf2469e330b33f11fd5fa04d36861af0c75eb110758b7fff2688fcb", + "sourceSha256": "7a788419e5d1a6e4b944a5d436d2cffd00795c3237791689b1401b73925353f8", "imageSha256": "2f4770869f04c8d7f1e8ff33af1e8bd79f45ad6d97be6d5e6135b3a82fec9e4c" }, "plan-context-tray": { @@ -57,7 +57,7 @@ }, "theme": "dark", "language": "en", - "sourceSha256": "da33c4f75cf2469e330b33f11fd5fa04d36861af0c75eb110758b7fff2688fcb", + "sourceSha256": "7a788419e5d1a6e4b944a5d436d2cffd00795c3237791689b1401b73925353f8", "imageSha256": "47d8fc2ac1c14cb699e990ca9c00d89b9ac57f6cc76b40420ceb26b4d7f50df4" }, "device-editor": { @@ -68,8 +68,8 @@ }, "theme": "dark", "language": "en", - "sourceSha256": "da33c4f75cf2469e330b33f11fd5fa04d36861af0c75eb110758b7fff2688fcb", - "imageSha256": "7a22a81e2abedf5a520bd36b7509d953cbfa9962b48c0ffbc233666a6215c04b" + "sourceSha256": "7a788419e5d1a6e4b944a5d436d2cffd00795c3237791689b1401b73925353f8", + "imageSha256": "7c3e25534fc819c45431907616c3d523961505859ee68af27cce2daec9f04fb0" }, "device-display-preview": { "file": "06-device-display-preview.png", @@ -79,7 +79,7 @@ }, "theme": "dark", "language": "en", - "sourceSha256": "da33c4f75cf2469e330b33f11fd5fa04d36861af0c75eb110758b7fff2688fcb", + "sourceSha256": "7a788419e5d1a6e4b944a5d436d2cffd00795c3237791689b1401b73925353f8", "imageSha256": "f6014caed7b7d28790b8548996ba09d806a4e4d51fbb7e8d3fb3c582ebe49167" }, "background-editor": { @@ -90,7 +90,7 @@ }, "theme": "dark", "language": "en", - "sourceSha256": "da33c4f75cf2469e330b33f11fd5fa04d36861af0c75eb110758b7fff2688fcb", + "sourceSha256": "7a788419e5d1a6e4b944a5d436d2cffd00795c3237791689b1401b73925353f8", "imageSha256": "4e7b3b1220a3fe104cf11e5b32a31b343dc95c45fd2cbe3365d145f0a66a560d" }, "room-card": { @@ -101,7 +101,7 @@ }, "theme": "dark", "language": "en", - "sourceSha256": "da33c4f75cf2469e330b33f11fd5fa04d36861af0c75eb110758b7fff2688fcb", + "sourceSha256": "7a788419e5d1a6e4b944a5d436d2cffd00795c3237791689b1401b73925353f8", "imageSha256": "176abba71d41cfb045a33f82a794d9fbb2a5d3c48e6df66e6ec3a8e448311b11" }, "device-info": { @@ -112,7 +112,7 @@ }, "theme": "dark", "language": "en", - "sourceSha256": "da33c4f75cf2469e330b33f11fd5fa04d36861af0c75eb110758b7fff2688fcb", + "sourceSha256": "7a788419e5d1a6e4b944a5d436d2cffd00795c3237791689b1401b73925353f8", "imageSha256": "2199ed88b215bf2bff63bf665028f78b2aa6a92c3032b803931790f2ef071893" } } From a1b8861eff0c502e686e1039f689bfabba80a0c8 Mon Sep 17 00:00:00 2001 From: Sergey Matyunin Date: Mon, 17 Aug 2026 13:16:04 +0300 Subject: [PATCH 5/5] fix: preserve plan-only room label scale Issue: #167 User-Visible: yes --- .github/workflows/mutation-gate.yml | 7 ++++- custom_components/houseplan/import_export.py | 27 +++++++++++++------- docs/CHANGELOG.md | 4 +-- docs/CHANGELOG.ru.md | 5 ++-- docs/USER-GUIDE.md | 5 ++-- docs/USER-GUIDE.ru.md | 4 +-- docs/specs/167-plan-only-export.md | 17 ++++++------ scripts/backend-test-guard.mjs | 3 ++- tests_backend/test_ha_import_export.py | 14 +++++++--- 9 files changed, 55 insertions(+), 31 deletions(-) diff --git a/.github/workflows/mutation-gate.yml b/.github/workflows/mutation-gate.yml index b6a304e1..55d77621 100644 --- a/.github/workflows/mutation-gate.yml +++ b/.github/workflows/mutation-gate.yml @@ -13,6 +13,11 @@ name: Mutation gate on: workflow_dispatch: + inputs: + ref: + description: Git ref whose mutation guards must be proved + required: false + default: dev schedule: # Понедельник, 05:20 UTC — до начала рабочего дня владельца. - cron: '20 5 * * 1' @@ -33,7 +38,7 @@ jobs: steps: - uses: actions/checkout@v7 with: - ref: dev + ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || 'dev' }} fetch-depth: 0 - uses: actions/setup-node@v7 diff --git a/custom_components/houseplan/import_export.py b/custom_components/houseplan/import_export.py index a5408051..ace3bf41 100644 --- a/custom_components/houseplan/import_export.py +++ b/custom_components/houseplan/import_export.py @@ -9,6 +9,7 @@ from __future__ import annotations import copy import hashlib import json +import math import re import secrets import time @@ -276,15 +277,23 @@ def _plan_only_room_label_layout( ) -> dict[str, Any]: space_id = str(space.get("id", "")) room_ids = {str(room.get("id", "")) for room in space.get("rooms") or []} - return { - key: _pick_fields(pos, ("x", "y", "s")) - for key, pos in layout.items() - if isinstance(key, str) - and key.startswith("rl_") - and key[3:] in room_ids - and isinstance(pos, dict) - and str(pos.get("s", "")) == space_id - } + projected: dict[str, Any] = {} + for key, pos in layout.items(): + if not ( + isinstance(key, str) + and key.startswith("rl_") + and key[3:] in room_ids + and isinstance(pos, dict) + and str(pos.get("s", "")) == space_id + ): + continue + value = _pick_fields(pos, ("x", "y", "s")) + scale = pos.get("k") + if isinstance(scale, (int, float)) and not isinstance(scale, bool) \ + and math.isfinite(scale) and 0.5 <= scale <= 3: + value["k"] = scale + projected[key] = value + return projected def _marker_owned(marker: dict[str, Any], space: dict[str, Any], layout: dict[str, Any]) -> bool: diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 33b33bf4..ab58f929 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -3,8 +3,8 @@ ## Unreleased - Current-space export can now create a **Plan only** JSON template with rooms, - walls, openings, decor, backdrop and room-label positions, while removing - devices and structural Home Assistant bindings. Import preview identifies + walls, openings, decor, backdrop, room-label positions and scale, while + removing devices and structural Home Assistant bindings. Import preview identifies the template before adding it as a new space ([#167](https://github.com/Matysh/houseplan-card/issues/167)). - Composite appliances such as washing machines now use an explicit Home diff --git a/docs/CHANGELOG.ru.md b/docs/CHANGELOG.ru.md index dfce9f3c..1fb6113f 100755 --- a/docs/CHANGELOG.ru.md +++ b/docs/CHANGELOG.ru.md @@ -9,8 +9,9 @@ ## Unreleased - Экспорт текущего пространства теперь может создать JSON-шаблон **«Только - планировка»** с комнатами, стенами, проёмами, декором, подложкой и позициями - подписей комнат, но без устройств и структурных привязок Home Assistant. + планировка»** с комнатами, стенами, проёмами, декором, подложкой, позициями и + масштабом подписей комнат, но без устройств и структурных привязок Home + Assistant. Предпросмотр помечает такой файл до добавления новым пространством ([#167](https://github.com/Matysh/houseplan-card/issues/167)). - Составная техника, например стиральная машина, теперь использует явный diff --git a/docs/USER-GUIDE.md b/docs/USER-GUIDE.md index 7c225ea7..374f2156 100644 --- a/docs/USER-GUIDE.md +++ b/docs/USER-GUIDE.md @@ -509,8 +509,9 @@ instance must explicitly detach those links. For **Current space**, enable **Plan only** to transfer the architectural template without devices or Home Assistant bindings. It keeps rooms, walls, -openings, decor, backdrop transforms and manually positioned room labels, but -removes real and virtual markers, device positions, Area assignments, +openings, decor, backdrop transforms and manually positioned room labels at +their chosen scale, but removes real and virtual markers, device positions, +Area assignments, temperature/humidity sources and opening contacts/locks. Live values in text labels become `—`; surrounding static text stays intact. The import preview marks this file as plan-only and adds it through the normal space-import flow. diff --git a/docs/USER-GUIDE.ru.md b/docs/USER-GUIDE.ru.md index 3d5972a3..fa94c8ba 100644 --- a/docs/USER-GUIDE.ru.md +++ b/docs/USER-GUIDE.ru.md @@ -1159,8 +1159,8 @@ show_signal: true глобальные настройки не меняются и отмена не создаётся. Для текущего пространства можно включить **Только планировка**. Такой файл -сохраняет комнаты, стены, проёмы, декор, трансформации подложки и вручную -расставленные подписи комнат, но удаляет реальные и виртуальные маркеры, +сохраняет комнаты, стены, проёмы, декор, трансформации подложки, позиции и +выбранный масштаб подписей комнат, но удаляет реальные и виртуальные маркеры, позиции устройств, Area, источники температуры/влажности и контакты/замки проёмов. Живые значения в текстовых подписях заменяются на `—`, а окружающий статический текст остаётся. Предпросмотр явно помечает файл как планировку и diff --git a/docs/specs/167-plan-only-export.md b/docs/specs/167-plan-only-export.md index b4857f26..360607b8 100644 --- a/docs/specs/167-plan-only-export.md +++ b/docs/specs/167-plan-only-export.md @@ -162,7 +162,7 @@ device layout и известных структурных HA-привязок. "payload": { "config": { "spaces": ["…"], "markers": [] }, "layout": { - "rl_room-kitchen": { "x": 0.42, "y": 0.31, "s": "floor-1" } + "rl_room-kitchen": { "x": 0.42, "y": 0.31, "s": "floor-1", "k": 1.4 } } }, "placement_manifest": [ @@ -215,7 +215,7 @@ device layout и известных структурных HA-привязок. | Источник | Результат plan-only | |---|---| | `config.markers` | `[]`; marker config целиком отсутствует | -| `payload.layout` | только `rl_` для комнаты экспортируемого пространства; marker, `v_*`, `lg_*`, auto-device и неизвестные позиции удаляются | +| `payload.layout` | только `rl_` для комнаты экспортируемого пространства: обязательные `x/y/s` и опциональный конечный масштаб карточки `k` в диапазоне `0.5..3`; marker, `v_*`, `lg_*`, auto-device, неизвестные поля и невалидный `k` удаляются | | `placement_manifest` | только canonical `room_label` entries, точно соответствующие сохранённым `rl_*` ключам | | marker attachment/content entries | отсутствуют | | `room.area` | отсутствует или canonical unbound value | @@ -288,7 +288,8 @@ presentation, user content или HA binding. Это fail-closed защита о - ровно одно пространство; - `markers == []`; - каждый layout key строго равен `rl_` существующей комнаты - экспортируемого пространства, а `pos.s` равен id этого пространства; + экспортируемого пространства, `pos.s` равен id этого пространства, а запись + содержит только `x/y/s` и опциональный конечный `k` в диапазоне `0.5..3`; - каждый placement entry canonical: `owner == "room_label"`, `owner_id` совпадает с room id, `binding|label|icon == null`, и set записей точно совпадает с layout; @@ -363,8 +364,8 @@ first по `TOUCH-SUPPORT`, но диалог не должен переполн 6. Названия, статический текст, filenames и external URLs сохраняются; UX не обещает полную анонимизацию и не добавляет отдельного предупреждения. 7. Импорт plan-only файла на чистый целевой instance создаёт новое пространство - с той же планировкой, нулём устройств/HA-привязок, remap-нутыми позициями - подписей комнат и unbound rooms. + с той же планировкой, нулём устройств/HA-привязок, remap-нутыми позициями и + масштабом подписей комнат и unbound rooms. 8. Preview и revalidate явно сохраняют `plan_only: true`, показывают нулевые binding counts и не предлагают duplicate policy. 9. File с true, но с маркером, не-room-label layout, несогласованным placement @@ -397,7 +398,7 @@ first по `TOUCH-SUPPORT`, но диалог не должен переполн - reject `plan_only=true` для full; - reject non-boolean plan_only; - reject forged plan-only files по одному для marker, чужого/невалидного - room-label layout/placement, room area, + room-label layout/placement/scale, room area, temp/hum, opening refs, legacy decor refs и inline token; - preview/revalidate/apply happy path на same и foreign instance; - missing internal backdrop + detach confirmation по действующему контракту; @@ -494,8 +495,8 @@ Push ветки выполняется после задачи; issue не за 1. `plan_only` — optional additive metadata внутри существующего export version, а не новый kind или новая версия формата. -2. Безопасные ручные позиции подписей комнат `rl_` сохраняются и - remap-ятся; весь остальной layout удаляется. +2. Безопасные ручные позиции и конечный масштаб `k` подписей комнат + `rl_` сохраняются и remap-ятся; весь остальной layout удаляется. 3. Реальные и виртуальные markers удаляются одинаково. 4. Геометрический `flip_h|flip_v` сохраняется, contact-specific `invert` удаляется вместе с binding. diff --git a/scripts/backend-test-guard.mjs b/scripts/backend-test-guard.mjs index d3331c92..b6af1f91 100644 --- a/scripts/backend-test-guard.mjs +++ b/scripts/backend-test-guard.mjs @@ -7,7 +7,8 @@ if (!pattern) { process.exit(2); } -const result = spawnSync(process.env.PYTHON || 'python', [ +const python = process.env.PYTHON || (process.platform === 'win32' ? 'python' : 'python3'); +const result = spawnSync(python, [ '-m', 'pytest', 'tests_backend/test_ha_import_export.py', '-q', '-k', pattern, ], { stdio: 'inherit' }); process.exit(result.status ?? 2); diff --git a/tests_backend/test_ha_import_export.py b/tests_backend/test_ha_import_export.py index b2971d49..d4701b9f 100644 --- a/tests_backend/test_ha_import_export.py +++ b/tests_backend/test_ha_import_export.py @@ -340,7 +340,10 @@ def _plan_only_source() -> tuple[dict[str, Any], dict[str, Any]]: "note": {"x": 0.2, "y": 0.3, "s": "ground"}, "lg_light.group": {"x": 0.1, "y": 0.1, "s": "ground"}, "auto-device": {"x": 0.8, "y": 0.8, "s": "ground"}, - "rl_living": {"x": 0.45, "y": 0.55, "s": "ground", "future": "drop"}, + "rl_living": { + "x": 0.45, "y": 0.55, "s": "ground", "k": 1.4, + "future": "drop", + }, "rl_other": {"x": 0.1, "y": 0.1, "s": "other"}, } return config, layout @@ -358,7 +361,7 @@ def test_plan_only_export_projects_geometry_and_round_trips_room_labels(tmp_path assert document["transfer"] == {"dropped_marker_links": 0, "plan_only": True} assert payload["config"]["markers"] == [] assert payload["layout"] == { - "rl_living": {"x": 0.45, "y": 0.55, "s": "ground"}, + "rl_living": {"x": 0.45, "y": 0.55, "s": "ground", "k": 1.4}, } assert document["placement_manifest"] == [{ "layout_id": "rl_living", "space_id": "ground", "owner": "room_label", @@ -416,7 +419,7 @@ def test_plan_only_export_projects_geometry_and_round_trips_room_labels(tmp_path assert imported_room.get("area") is None assert merged_layout == { "rl_" + imported_room["id"]: { - "x": 0.45, "y": 0.55, "s": details["space_id"], + "x": 0.45, "y": 0.55, "s": details["space_id"], "k": 1.4, }, } assert parsed["transfer"]["plan_only"] is True @@ -447,7 +450,8 @@ def test_ordinary_space_export_is_unchanged_when_plan_only_is_false(tmp_path: Pa "mutation", [ "marker", "layout", "area", "temperature", "opening", "legacy_decor", - "live_token", "unknown", "placement", "marker_content", "invalid_content", + "live_token", "unknown", "placement", "layout_scale", + "marker_content", "invalid_content", ], ) def test_parser_rejects_forged_plan_only_privacy_claim( @@ -487,6 +491,8 @@ def test_parser_rejects_forged_plan_only_privacy_claim( space["future_binding"] = "sensor.secret" elif mutation == "placement": document["placement_manifest"][0]["owner"] = "auto_device" + elif mutation == "layout_scale": + document["payload"]["layout"]["rl_living"]["k"] = "sensor.secret" elif mutation == "marker_content": document["content_manifest"].append({"owner": "marker"}) elif mutation == "invalid_content":

Y);vnZ|wd3i3 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≺g9@R!*7-@72+TAck_Vu3Ch~q`ts-4T@rEZ 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 F{{o$2d~W~% delta 147264 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 z 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