Files
houseplan-card/custom_components/houseplan/frontend/houseplan-assets/backdrop-pick-CAEOSiNJ.js
T
Claudeandclaude[bot] 66ae952c2a refactor(editor): диалог «Оптимизировать планы» — свой модуль с узким портом (#642)
Первый вынос из монолита по образцу live-*/RadarSetupController.
src/optimize-plans-dialog.ts: OptimizePlansDialogState, чистые
preflightDiagnostics/preflightVersionsDiffer и класс OptimizePlansDialog
(open/preview/toggleLivePositions/run/copyDiagnostics/
reportPreflightFailure/render, clipboardFallback) за портом из 18 членов.
Модуль не знает HouseplanEditorHostPort; импортирует его только рантайм,
карточка берёт тип.

Инлайн-фолбэк буфера обмена — WeakMap по объекту диалога
(CODE-REVIEW-295-r1 M2), дедуп dev-лога — поле класса: из карточки и порта
ушли _preflightClipboardFallback и _reportedPreflightFingerprint, пять
делегатов и две стрелки-заглушки. Харнесс (13 смоков, wall-draw-click,
golden) зовёт card._editorRuntime.optimizePlans.* — меняется только адрес.

11 текстовых утверждений i18n.test.mjs о разметке и тосте диалога и гварды
8 мутантов переехали в юнит test/optimize-plans-dialog.test.mjs
(test-build); новый мутант optimize-dialog-imports-host-port.

monolith-baseline: delegates 159→154, portMembers 350→348,
hostRefs 4948→4869, portPrivates 96→94, harnessPrivates 107→101,
bundleBytes 2499647→2499182. Потолки ядер опущены на выигрыш;
потолок lazy editor gzip пересчитан (+555 Б gzip при −465 Б сырых).

Issue: #642
User-Visible: no
2026-09-24 08:00:58 +00:00

258 lines
75 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="d85694de8aee015f4930ff31f4c2022c14e45b0ad38f1dd1e723229f27f1e7b1";import{A as e,c as a,s as t,l as i,d as n,ap as s,h as r,j as o,g as l,dH as c,dI as p,dJ as d,cN as h,dK as u,cM as m,dL as g,cS as _,cR as b,dM as f,bt as v,bs as w,bu as y,av as k,aC as $,aD as x,dN as S}from"./houseplan-card-C2ifrS7A.js";import{c as C,f as M,C as L}from"./form-kit.styles-ORjqyA2O.js";const z=C+M(L,{withSwitch:!1}),P=new WeakSet;let D=null;function T(e){const a=e.renderRoot;if(!a||P.has(a))return;const t=a.ownerDocument??globalThis.document,i=t?.defaultView?.CSSStyleSheet;if(i&&"adoptedStyleSheets"in a)return D||(D=new i,D.replaceSync(z)),a.adoptedStyleSheets=[...a.adoptedStyleSheets,D],void P.add(a);if(a.querySelector("style[data-hp-form-kit]"))return void P.add(a);const n=t.createElement("style");n.dataset.hpFormKit="true",n.textContent=z,a.appendChild(n),P.add(a)}function H({title:t,help:i,body:n,id:s}){return a`<section class="hpf-card" data-card=${s??e}>
${t?a`<div class="hpf-head"><h3>${t}</h3>${i??e}</div>`:e}
<div class="hpf-body">${n}</div>
</section>`}function E({title:t,help:i}){return a`<div class="hpf-sub"><h4>${t}</h4>${i??e}</div>`}function F({label:t,htmlFor:i,help:n,control:s,hint:r,error:o}){return a`<div class="hpf-field">
<span class="hpf-label hpf-labelrow">${i?a`<label for=${i}>${t}</label>`:t}${n??e}</span>
${s}
${o?a`<p class="hpf-error" role="alert">${o}</p>`:r?a`<p class="hpf-hint">${r}</p>`:e}
</div>`}function q(e,{narrow:t=!1}={}){return a`<div class="hpf-grid ${t?"hpf-grid-narrow":""}">${e}</div>`}function A({icon:t,title:i,caption:n,help:s,checked:r,disabled:o=!1,compact:l=!1,onChange:c,id:p}){const d=p&&n?`${p}-caption`:void 0;return a`<label class="hpf-toggle ${t?"":"hpf-noicon"} ${l?"hpf-compact":""}">
${t?a`<span class="hpf-toggle-icon" aria-hidden="true"><ha-icon icon=${t}></ha-icon></span>`:e}
<span class="hpf-toggle-title"><span>${i}</span>${s??e}</span>
${n&&!l?a`<span class="hpf-toggle-caption" id=${d??e}>${n}</span>`:e}
<input type="checkbox" id=${p??e} .checked=${r} ?disabled=${o}
aria-label=${i} aria-describedby=${d??e}
@change=${e=>c(e.target.checked)} />
</label>`}function R({name:t,value:i,options:n,onChange:s,ariaLabel:r,disabled:o=!1}){return a`<div class="hpf-seg" role="radiogroup" aria-label=${r??e}
aria-disabled=${o?"true":e}>
${n.map(n=>a`<label>
<input type="radio" name=${t} value=${n.value} .checked=${n.value===i}
?disabled=${o||!!n.disabled} aria-describedby=${n.describedBy??e}
@change=${()=>s(n.value)} />
${n.sample?a`<span class="hpf-line ${"dashed"===n.sample?"hpf-line-dashed":""}" aria-hidden="true"></span>`:e}
${n.icon?a`<ha-icon icon=${n.icon} aria-hidden="true"></ha-icon>`:e}
<span>${n.label}</span>
</label>`)}
</div>`}function I(t,i){return a`<div class="hpf-tiles" role="group" aria-label=${i??e}>
${t.map(t=>a`<label class="hpf-tile" title=${t.title??e}>
<input type="checkbox" id=${t.id??e} .checked=${t.checked} ?disabled=${t.disabled}
aria-label=${t.title??t.label}
@change=${e=>t.onChange(e.target.checked)} />
<ha-icon icon=${t.icon} aria-hidden="true"></ha-icon>
<span>${t.label}</span>
</label>`)}
</div>`}function B({picker:t,hex:i,opacity:n,opacityLabel:s,onOpacity:r,resetLabel:o,onReset:l,disabled:c=!1}){const p=void 0===n?null:Math.round(100*Math.min(1,Math.max(0,n)));return a`<div class="hpf-colorfield">
${t}
<code class="hpf-hex">${i.toUpperCase()}</code>
${null===p?e:a`<span class="hpf-opacity">${s??""}
<span class="hpf-unit"><input type="number" inputmode="numeric" min="0" max="100" step="1"
.value=${String(p)} ?disabled=${c} aria-label=${s??"Opacity"}
@input=${e=>{const a=Number(e.target.value);Number.isFinite(a)&&r&&r(Math.min(1,Math.max(0,a/100)))}} /><span>%</span></span></span>`}
${o&&l?a`<button class="hpf-link" type="button" ?disabled=${c} @click=${l}>${o}</button>`:e}
</div>`}function N({label:e,hex:t,opacity:i,opacityLabel:n,picker:s,onOpacity:r}){const o=Math.round(100*Math.min(1,Math.max(0,i)));return a`<div class="hpf-colortile">
<div class="hpf-colortile-swatch" style=${`background:${t};color:${function(e){const a=/^#?([0-9a-f]{6})$/i.exec(e.trim());if(!a)return!0;const t=parseInt(a[1],16);return(.299*(t>>16&255)+.587*(t>>8&255)+.114*(255&t))/255>.6}(t)?"#1f2a30":"#fff"}`}>
${s}<span>${e}</span>
</div>
<div class="hpf-colortile-meta">
<code class="hpf-hex">${t.toUpperCase()}</code>
<span class="hpf-unit" title=${n}><input type="number" inputmode="numeric" min="0" max="100" step="1"
.value=${String(o)} aria-label=${n}
@input=${e=>{const a=Number(e.target.value);Number.isFinite(a)&&r(Math.min(1,Math.max(0,a/100)))}} /><span>%</span></span>
</div>
</div>`}function O(e){return a`<div class="hpf-colortiles">${e}</div>`}function G({label:t,help:i,picker:n,action:s}){return a`<div class="hpf-colorrow">
<span class="hpf-labelrow">${t}${i??e}</span>
${n}${s??e}
</div>`}function W({label:t,help:i,name:n,value:s,options:r,onChange:o}){return a`<div class="hpf-inline hpf-wrap">
<span class="hpf-labelrow">${t}${i??e}</span>
<div class="hpf-radios" role="radiogroup" aria-label=${t}>
${r.map(e=>a`<label><input type="radio" name=${n} .checked=${e.value===s}
@change=${()=>o(e.value)} /><span>${e.label}</span></label>`)}
</div>
</div>`}function U({text:t,kind:i="info",icon:n,action:s,role:r,id:o}){return a`<div id=${o??e} class="hpf-callout ${"warning"===i?"hpf-warning":""}" role=${r??e}>
<ha-icon icon=${n??("warning"===i?"mdi:alert-outline":"mdi:information-outline")}></ha-icon>
<p>${t}${s?a` ${s}`:e}</p>
</div>`}function V({id:t,describedBy:i,value:n,unit:s,min:r,max:o,step:l,placeholder:c,ariaLabel:p,invalid:d,disabled:h,wide:u,onInput:m,onChange:g}){return a`<span class="hpf-unit ${u?"hpf-unit-wide":""}">
<input id=${t??e} type="number" inputmode="decimal" .value=${n}
aria-describedby=${i??e}
min=${r??e} max=${o??e} step=${l??e}
placeholder=${c??e} aria-label=${p??e}
aria-invalid=${d?"true":e} ?disabled=${h}
@input=${e=>m(e.target.value)}
@change=${g?e=>g(e.target.value,e.target):e} />
<span>${s}</span>
</span>`}function j(e,a,t,i){if(!e.trim())return null;const n=Number(e);if(!Number.isFinite(n)||!Number.isFinite(i)||i<=0)return null;const s=Math.min(t,Math.max(a,n)),r=a+Math.round((s-a)/i)*i,o=function(e){const a=String(e).toLowerCase();if(!a.includes("e"))return(a.split(".")[1]||"").length;const[t,i]=a.split("e"),n=(t.split(".")[1]||"").length;return Math.max(0,n-Number(i))}(i);return Math.min(t,Math.max(a,Number(r.toFixed(o))))}function Z({min:e,max:t,step:i,value:n,unit:s,ariaLabel:r,disabled:o,slider:l,onInput:c,id:p}){return a`<div class="hpf-range">
${l}
${V({id:p,value:String(n),unit:s,min:e,max:t,step:i,ariaLabel:r,disabled:o,onInput:()=>{},onChange:(a,s)=>{const r=j(a,e,t,i);null!==r?(s.value=String(r),c(r)):s.value=String(n)}})}
</div>`}function K(e,t){return a`<div class="hpf-range-ends" aria-hidden="true">
<span class="hpf-range-ends-track"><span>${e}</span><span>${t}</span></span>
</div>`}function Q(e,t,{disabled:i=!1}={}){return a`<button class="hpf-link" type="button" ?disabled=${i} @click=${t}>${e}</button>`}function Y({text:t,action:i}){return a`<span class="hpf-status" role="status" aria-live="polite">${t??""}${i??e}</span>`}function J({title:t,tag:i,body:n}){return a`<div class="hpf-tint">
<div class="hpf-tint-head"><h4>${t}</h4>${i?a`<span class="hpf-tag">${i}</span>`:e}</div>
${n}
</div>`}function X({open:t,current:i,placeholder:n,ariaLabel:s,filter:r,filterPlaceholder:o,candidates:l,selected:c,emptyText:p,onToggle:d,onFilter:h,onPick:u,id:m,toolbar:g,disabled:_=!1}){return a`<div class="hpf-picker ${t?"open":""}">
<button id=${m??e} type="button" class="hpf-select hpf-pick" aria-haspopup="listbox"
aria-expanded=${t?"true":"false"} aria-label=${s} ?disabled=${_} @click=${d}>
${i?a`<b>${i.label}</b><span class="hpf-pick-ref">${i.sub}</span>`:a`<span class="hpf-pick-ph">${n}</span>`}
</button>
${t?a`<div class="hpf-panel">
<div class="hpf-toolbar">
<input class="hpf-input" type="text" autocomplete="off" placeholder=${o}
aria-label=${o} .value=${r}
@input=${e=>h(e.target.value)} />
${g??e}
</div>
<div class="hpf-list" role="listbox" aria-label=${s}>
${l.length?l.map(e=>a`<button type="button" role="option" class="hpf-cand ${e.value===c?"sel":""}"
aria-selected=${e.value===c?"true":"false"} @click=${()=>u(e.value)}>
<span>${e.label}</span><small>${e.sub}</small>
</button>`):a`<p class="hpf-hint">${p}</p>`}
</div>
</div>`:e}
</div>`}var ee={"space.scale_changed":"Changing the scale recalculates the dimensions of the whole space when you save. Points on the plan stay where they are.","space.plan.help":"Use an image as a background to draw over. Rooms and devices stay editable on top of it. Switching to drawing by hand keeps the uploaded image available to use again.","space.plan.help.aria":"Help: floor plan","space.source_draw_hint":"Outline rooms in the plan editor.","space.source_file_hint":"Add an image to draw over.","space.plan_kept":"Your uploaded image is kept. Switch back to use it again.","space.show_borders_hint":"Show patterned outlines around the walls.","space.show_borders.help":"When off, patterned wall outlines and zero-thickness wall lines are hidden. Plain, light wall bands stay visible, so rooms remain separated. Doors, windows, room fills and lighting keep working.","space.show_borders.help.aria":"Help: room borders","space.layers_label":"Visible layers","space.layers.help":"Hiding changes only what you see on the plan. Decorative elements remain available in the background editor. Doors and windows stay in the plan editor, while their sensors, sunlight and lamp glow keep working.","space.layers.help.aria":"Help: visible layers","space.layer_decor":"Decorative layer","space.layer_openings":"Doors, windows & gates","space.fill_lqi_hint":"Uses average room LQI: low at 40 or below, high at 180 or above. Colors and opacity follow the general palette.","space.fill_light_hint":"Uses separate colors for any light on, all lights off, and no light sources. Colors and opacity follow the general palette.","space.temp_range":"Comfort range","space.temp_min":"Minimum","space.temp_max":"Maximum","space.temp_range_hint":"Below the minimum: cold. Within the range: comfort. Above the maximum: hot. Without a temperature reading, light-source glow can still add base shading.","space.show_names_hint":"Display room names and their selected values.","space.show_names.help":"Hiding room names also hides their values without clearing your selections. Room-card positions stay where you placed them.","space.show_names.help.aria":"Help: room names","space.card_values":"Values on the card","space.label_lqi_short":"Zigbee signal","space.values_hidden":"Values are saved but hidden.","space.values_hidden_action":"Show room names","space.card_font_hint":"Scales every room card in this space.","space.card_font.help":"100% is the default size. This scales every room card in this space while keeping individual card sizes and positions. Changes apply when you save.","space.card_font.help.aria":"Help: room-card font size","space.sun_inherit_short":"Use general settings","space.north_general":"Use general settings · {v}","space.north_custom":"Custom direction","space.north_deg":"North direction","space.compass_aria":"Compass: north at {v}","space.glow_enabled_hint":"Show a soft glow around lights that are on.","space.glow_enabled.help":"Lamp glow and sunlight are independent. Hiding opening symbols does not stop either effect. Window glass still lets sunlight in when the window is closed.","space.glow_enabled.help.aria":"Help: light-source glow","space.error_title":"Enter a name for the space.","space.error_plan":"Add a floor-plan image or switch to drawing by hand.","space.error_scale":"Enter a scale within the allowed range.","space.error_temp_value":"Enter a number.","space.error_temp_range":"The maximum must be higher than the minimum.","space.error_north":"Enter a whole number between 0° and 359°.","btn.reset_100":"Reset to 100%","dialog.review_fields":"Review {n} field(s)","dialog.discard_title":"Discard your changes?","dialog.discard_message":"Your changes haven’t been saved.","dialog.discard_confirm":"Discard","dialog.discard_keep":"Continue","gs.show_room_tooltip_hint":"Hover a room on the plan to see its summary.","gs.radar_show_live_hint":"Hides or shows all live radar targets. This does not start or stop observation recording.","gs.bg_custom_hint":"Custom background color.","gs.bg_theme_hint":"Using the theme background.","gs.sun_rays_hint":"Draw sun rays through window openings.","gs.error_glow_radius":"Enter a radius greater than zero.","gs.error_north":"Enter a whole number between 0° and 359°.","gs.opacity_of":"{name} opacity","room.fill_inherit_hint":"Use the fill mode set for the whole space.","room.fill_following":"Following the space: {mode}.","room.fill_mode_label":"Fill mode","room.temp_min_label":"Minimum","room.temp_max_label":"Maximum","room.sizes_hint":"On top of the space-wide card size ({v}%).","room.error_temp_range":"Enter numbers for the comfort bounds.","room.error_name":"Enter a name or choose an area.","marker.binding.help":"A bound marker shows the live state of a Home Assistant device or entity and can act on it. A virtual device is a plain marker with no binding.","marker.binding.help.aria":"Help: binding to a Home Assistant device","marker.binding_virtual_hint":"A virtual device is a plain marker with no binding.","marker.room_hint_auto":"Placed by the device’s Home Assistant area.","marker.room_hint_override":"Overrides the placement by device area.","marker.light_role_seg_auto":"Auto","marker.light_role_seg_always":"Always","marker.light_role_seg_never":"Never","marker.glow_radius_empty_hint":"Empty: uses the general radius ({v}).","marker.icon.help":"Empty means automatic: the icon comes from the icon rules for this device. Pick one to override, or pin the automatic choice.","marker.icon.help.aria":"Help: marker icon","marker.icon_custom_hint":"Custom icon for this marker.","marker.display.help":"How the marker face reacts to state and activity. The value modes replace the icon with a Home Assistant value.","marker.display.help.aria":"Help: display mode","marker.size.help":"Scales and rotates only this marker on the plan. The tap area grows with the icon.","marker.size.help.aria":"Help: icon size and rotation","marker.size_short":"Size","marker.error_binding":"Choose a device or entity to bind.","marker.error_virtual_name":"Enter a name for the virtual device.","marker.error_run_target":"Choose what to run.","marker.error_value_badge_source":"Choose a value source for the badge.","fill.seg_none":"None","fill.seg_lqi":"Zigbee","fill.seg_light":"Lights","fill.seg_temp":"Temperature","fill.seg_custom":"Custom"};const ae={en:ee,ru:{"space.scale_changed":"Смена масштаба пересчитает размеры всего пространства при сохранении. Точки на плане останутся на месте.","space.plan.help":"Изображение под планом, поверх которого рисуют. Комнаты и устройства остаются редактируемыми. При переходе на рисование вручную загруженное изображение сохраняется и доступно снова.","space.plan.help.aria":"Справка: подложка плана","space.source_draw_hint":"Обвести комнаты в редакторе плана.","space.source_file_hint":"Добавить картинку и рисовать поверх неё.","space.plan_kept":"Загруженное изображение сохранено. Переключитесь обратно, чтобы снова использовать его.","space.show_borders_hint":"Показывать узорную обводку стен.","space.show_borders.help":"Когда выключено, узорная обводка стен и линии стен нулевой толщины скрыты. Светлые полосы стен остаются видны, комнаты не сливаются. Двери, окна, заливки комнат и освещение продолжают работать.","space.show_borders.help.aria":"Справка: границы комнат","space.layers_label":"Видимые слои","space.layers.help":"Скрытие меняет только то, что видно на плане. Декоративные элементы остаются в редакторе подложки. Двери и окна остаются в редакторе плана, а их датчики, солнечный свет и свечение ламп продолжают работать.","space.layers.help.aria":"Справка: видимые слои","space.layer_decor":"Декоративный слой","space.layer_openings":"Двери, окна и ворота","space.fill_lqi_hint":"По среднему LQI комнаты: слабый — 40 и ниже, сильный — 180 и выше. Цвета и прозрачность — из общей палитры.","space.fill_light_hint":"Отдельные цвета для «горит хотя бы одна», «все выключены» и «источников нет». Цвета и прозрачность — из общей палитры.","space.temp_range":"Комфортный диапазон","space.temp_min":"Минимум","space.temp_max":"Максимум","space.temp_range_hint":"Ниже минимума — холодно. В диапазоне — комфорт. Выше максимума — жарко. Без показаний температуры свечение источников света всё равно даёт базовую заливку.","space.show_names_hint":"Названия комнат и выбранные значения на плане.","space.show_names.help":"Скрытие названий скрывает и значения, не сбрасывая выбор. Положение карточек комнат сохраняется.","space.show_names.help.aria":"Справка: названия комнат","space.card_values":"Значения на карточке","space.label_lqi_short":"Сигнал Zigbee","space.values_hidden":"Значения сохранены, но скрыты.","space.values_hidden_action":"Показать названия комнат","space.card_font_hint":"Масштабирует каждую карточку комнаты в этом пространстве.","space.card_font.help":"100 % — размер по умолчанию. Масштабирует все карточки комнат пространства, сохраняя индивидуальные размеры и положение. Применяется при сохранении.","space.card_font.help.aria":"Справка: размер шрифта карточек","space.sun_inherit_short":"Как в общих","space.north_general":"Как в общих · {v}","space.north_custom":"Своё направление","space.north_deg":"Направление на север","space.compass_aria":"Компас: север {v}","space.glow_enabled_hint":"Мягкое свечение вокруг включённых ламп.","space.glow_enabled.help":"Свечение ламп и солнечный свет независимы. Скрытие символов проёмов не отключает ни то, ни другое. Стекло окна пропускает солнце и при закрытом окне.","space.glow_enabled.help.aria":"Справка: свечение источников света","space.error_title":"Введите название пространства.","space.error_plan":"Добавьте изображение плана или переключитесь на рисование вручную.","space.error_scale":"Введите масштаб в допустимом диапазоне.","space.error_temp_value":"Введите число.","space.error_temp_range":"Максимум должен быть больше минимума.","space.error_north":"Введите целое число от 0° до 359°.","btn.reset_100":"Сбросить до 100%","dialog.review_fields":"Проверьте поля: {n}","dialog.discard_title":"Отменить изменения?","dialog.discard_message":"Изменения не сохранены.","dialog.discard_confirm":"Не сохранять","dialog.discard_keep":"Вернуться","gs.show_room_tooltip_hint":"Наведите на комнату на плане, чтобы увидеть сводку.","gs.radar_show_live_hint":"Показывает или скрывает все живые цели радаров. Запись наблюдения не запускается и не останавливается.","gs.bg_custom_hint":"Свой цвет фона.","gs.bg_theme_hint":"Используется фон темы.","gs.sun_rays_hint":"Рисовать солнечные лучи через оконные проёмы.","gs.error_glow_radius":"Введите радиус больше нуля.","gs.error_north":"Введите целое число от 0° до 359°.","gs.opacity_of":"Прозрачность: {name}","room.fill_inherit_hint":"Использовать режим заливки всего пространства.","room.fill_following":"Как у пространства: {mode}.","room.fill_mode_label":"Режим заливки","room.temp_min_label":"Минимум","room.temp_max_label":"Максимум","room.sizes_hint":"Поверх размера карточек всего пространства ({v}%).","room.error_temp_range":"Введите числа для границ комфорта.","room.error_name":"Введите название или выберите зону.","marker.binding.help":"Привязанный маркер показывает живое состояние устройства или сущности Home Assistant и может им управлять. Виртуальное устройство — просто маркер без привязки.","marker.binding.help.aria":"Справка: привязка к устройству Home Assistant","marker.binding_virtual_hint":"Виртуальное устройство — просто маркер без привязки.","marker.room_hint_auto":"Размещается по зоне устройства в Home Assistant.","marker.room_hint_override":"Переопределяет размещение по зоне устройства.","marker.light_role_seg_auto":"Авто","marker.light_role_seg_always":"Всегда","marker.light_role_seg_never":"Никогда","marker.glow_radius_empty_hint":"Пусто: общий радиус ({v}).","marker.icon.help":"Пусто — автоматически: иконка берётся из правил иконок для этого устройства. Выберите свою, чтобы переопределить, или закрепите автоматическую.","marker.icon.help.aria":"Справка: иконка маркера","marker.icon_custom_hint":"Своя иконка для этого маркера.","marker.display.help":"Как лицо маркера реагирует на состояние и активность. Режимы значения заменяют иконку значением из Home Assistant.","marker.display.help.aria":"Справка: режим отображения","marker.size.help":"Масштабирует и поворачивает только этот маркер на плане. Область нажатия растёт вместе с иконкой.","marker.size.help.aria":"Справка: размер и поворот иконки","marker.size_short":"Размер","marker.error_binding":"Выберите устройство или сущность для привязки.","marker.error_virtual_name":"Введите название виртуального устройства.","marker.error_run_target":"Выберите действие для запуска.","marker.error_value_badge_source":"Выберите источник значения для бейджа.","fill.seg_none":"Нет","fill.seg_lqi":"Zigbee","fill.seg_light":"Свет","fill.seg_temp":"Температура","fill.seg_custom":"Свой цвет"},de:{"space.scale_changed":"Eine Änderung des Maßstabs berechnet beim Speichern die Maße des gesamten Bereichs neu. Punkte auf dem Plan bleiben, wo sie sind.","space.plan.help":"Ein Bild als Hintergrund, über das gezeichnet wird. Räume und Geräte bleiben darüber bearbeitbar. Beim Wechsel zum Zeichnen von Hand bleibt das hochgeladene Bild erhalten.","space.plan.help.aria":"Hilfe: Grundriss","space.source_draw_hint":"Räume im Plan-Editor umreißen.","space.source_file_hint":"Ein Bild hinzufügen und darüber zeichnen.","space.plan_kept":"Das hochgeladene Bild bleibt erhalten. Wechseln Sie zurück, um es wieder zu nutzen.","space.show_borders_hint":"Gemusterte Umrisse um die Wände anzeigen.","space.show_borders.help":"Ausgeschaltet werden gemusterte Wandumrisse und Linien von Wänden ohne Dicke ausgeblendet. Helle Wandbänder bleiben sichtbar, Räume bleiben getrennt. Türen, Fenster, Raumfüllungen und Beleuchtung funktionieren weiter.","space.show_borders.help.aria":"Hilfe: Raumgrenzen","space.layers_label":"Sichtbare Ebenen","space.layers.help":"Ausblenden ändert nur, was auf dem Plan zu sehen ist. Dekorative Elemente bleiben im Hintergrund-Editor verfügbar. Türen und Fenster bleiben im Plan-Editor; ihre Sensoren, Sonnenlicht und Lampenschein funktionieren weiter.","space.layers.help.aria":"Hilfe: sichtbare Ebenen","space.layer_decor":"Dekorative Ebene","space.layer_openings":"Türen, Fenster & Tore","space.fill_lqi_hint":"Nach mittlerem LQI des Raums: schwach bei 40 oder weniger, stark bei 180 oder mehr. Farben und Deckkraft folgen der allgemeinen Palette.","space.fill_light_hint":"Eigene Farben für „mindestens eine Lampe an“, „alle aus“ und „keine Lichtquellen“. Farben und Deckkraft folgen der allgemeinen Palette.","space.temp_range":"Komfortbereich","space.temp_min":"Minimum","space.temp_max":"Maximum","space.temp_range_hint":"Unter dem Minimum: kalt. Im Bereich: Komfort. Über dem Maximum: heiß. Ohne Temperaturwert kann der Lampenschein weiterhin eine Grundschattierung geben.","space.show_names_hint":"Raumnamen und ausgewählte Werte anzeigen.","space.show_names.help":"Das Ausblenden der Raumnamen blendet auch ihre Werte aus, ohne die Auswahl zu löschen. Die Positionen der Raumkarten bleiben erhalten.","space.show_names.help.aria":"Hilfe: Raumnamen","space.card_values":"Werte auf der Karte","space.label_lqi_short":"Zigbee-Signal","space.values_hidden":"Werte sind gespeichert, aber ausgeblendet.","space.values_hidden_action":"Raumnamen anzeigen","space.card_font_hint":"Skaliert jede Raumkarte in diesem Bereich.","space.card_font.help":"100 % ist die Standardgröße. Skaliert alle Raumkarten dieses Bereichs, individuelle Größen und Positionen bleiben erhalten. Gilt nach dem Speichern.","space.card_font.help.aria":"Hilfe: Schriftgröße der Raumkarten","space.sun_inherit_short":"Wie allgemein","space.north_general":"Wie allgemein · {v}","space.north_custom":"Eigene Richtung","space.north_deg":"Nordrichtung","space.compass_aria":"Kompass: Norden bei {v}","space.glow_enabled_hint":"Sanftes Leuchten um eingeschaltete Lampen.","space.glow_enabled.help":"Lampenschein und Sonnenlicht sind unabhängig. Das Ausblenden von Öffnungssymbolen stoppt keinen der Effekte. Fensterglas lässt Sonnenlicht auch bei geschlossenem Fenster durch.","space.glow_enabled.help.aria":"Hilfe: Leuchten der Lichtquellen","space.error_title":"Geben Sie einen Namen für den Bereich ein.","space.error_plan":"Fügen Sie ein Grundrissbild hinzu oder wechseln Sie zum Zeichnen von Hand.","space.error_scale":"Geben Sie einen Maßstab im zulässigen Bereich ein.","space.error_temp_value":"Geben Sie eine Zahl ein.","space.error_temp_range":"Das Maximum muss über dem Minimum liegen.","space.error_north":"Geben Sie eine ganze Zahl zwischen 0° und 359° ein.","btn.reset_100":"Auf 100 % zurücksetzen","dialog.review_fields":"{n} Feld(er) prüfen","dialog.discard_title":"Änderungen verwerfen?","dialog.discard_message":"Ihre Änderungen wurden nicht gespeichert.","dialog.discard_confirm":"Verwerfen","dialog.discard_keep":"Fortfahren","gs.show_room_tooltip_hint":"Zeigen Sie auf einen Raum im Plan, um seine Übersicht zu sehen.","gs.radar_show_live_hint":"Blendet alle Live-Radarziele ein oder aus. Die Beobachtungsaufzeichnung wird dadurch nicht gestartet oder gestoppt.","gs.bg_custom_hint":"Eigene Hintergrundfarbe.","gs.bg_theme_hint":"Hintergrund des Themes wird verwendet.","gs.sun_rays_hint":"Sonnenstrahlen durch Fensteröffnungen zeichnen.","gs.error_glow_radius":"Geben Sie einen Radius größer als null ein.","gs.error_north":"Geben Sie eine ganze Zahl zwischen 0° und 359° ein.","gs.opacity_of":"Deckkraft: {name}","room.fill_inherit_hint":"Den Füllmodus des gesamten Bereichs verwenden.","room.fill_following":"Wie der Bereich: {mode}.","room.fill_mode_label":"Füllmodus","room.temp_min_label":"Minimum","room.temp_max_label":"Maximum","room.sizes_hint":"Zusätzlich zur bereichsweiten Kartengröße ({v} %).","room.error_temp_range":"Geben Sie Zahlen für die Komfortgrenzen ein.","room.error_name":"Geben Sie einen Namen ein oder wählen Sie einen Bereich.","marker.binding.help":"Ein verknüpfter Marker zeigt den Live-Zustand eines Home-Assistant-Geräts oder einer Entität und kann darauf wirken. Ein virtuelles Gerät ist ein einfacher Marker ohne Verknüpfung.","marker.binding.help.aria":"Hilfe: Verknüpfung mit einem Home-Assistant-Gerät","marker.binding_virtual_hint":"Ein virtuelles Gerät ist ein einfacher Marker ohne Verknüpfung.","marker.room_hint_auto":"Platzierung nach dem Home-Assistant-Bereich des Geräts.","marker.room_hint_override":"Überschreibt die Platzierung nach Gerätebereich.","marker.light_role_seg_auto":"Auto","marker.light_role_seg_always":"Immer","marker.light_role_seg_never":"Nie","marker.glow_radius_empty_hint":"Leer: allgemeiner Radius ({v}).","marker.icon.help":"Leer bedeutet automatisch: Das Symbol kommt aus den Symbolregeln für dieses Gerät. Wählen Sie eines zum Überschreiben oder heften Sie die automatische Wahl an.","marker.icon.help.aria":"Hilfe: Marker-Symbol","marker.icon_custom_hint":"Eigenes Symbol für diesen Marker.","marker.display.help":"Wie das Marker-Gesicht auf Zustand und Aktivität reagiert. Die Wert-Modi ersetzen das Symbol durch einen Home-Assistant-Wert.","marker.display.help.aria":"Hilfe: Anzeigemodus","marker.size.help":"Skaliert und dreht nur diesen Marker auf dem Plan. Die Tippfläche wächst mit dem Symbol.","marker.size.help.aria":"Hilfe: Symbolgröße und Drehung","marker.size_short":"Größe","marker.error_binding":"Wählen Sie ein Gerät oder eine Entität zum Verknüpfen.","marker.error_virtual_name":"Geben Sie einen Namen für das virtuelle Gerät ein.","marker.error_run_target":"Wählen Sie aus, was ausgeführt werden soll.","marker.error_value_badge_source":"Wählen Sie eine Wertquelle für das Badge.","fill.seg_none":"Keine","fill.seg_lqi":"Zigbee","fill.seg_light":"Licht","fill.seg_temp":"Temperatur","fill.seg_custom":"Eigene"},fr:{"space.scale_changed":"Changer l’échelle recalcule les dimensions de tout l’espace à l’enregistrement. Les points du plan restent en place.","space.plan.help":"Une image en arrière-plan sur laquelle dessiner. Les pièces et les appareils restent modifiables par-dessus. Passer au dessin à la main conserve l’image téléversée pour la réutiliser.","space.plan.help.aria":"Aide : fond de plan","space.source_draw_hint":"Tracer les pièces dans l’éditeur de plan.","space.source_file_hint":"Ajouter une image et dessiner par-dessus.","space.plan_kept":"Votre image téléversée est conservée. Revenez en arrière pour la réutiliser.","space.show_borders_hint":"Afficher les contours hachurés autour des murs.","space.show_borders.help":"Désactivé, les contours hachurés des murs et les lignes des murs sans épaisseur sont masqués. Les bandes claires des murs restent visibles, les pièces restent séparées. Portes, fenêtres, remplissages et éclairage continuent de fonctionner.","space.show_borders.help.aria":"Aide : bordures des pièces","space.layers_label":"Calques visibles","space.layers.help":"Masquer ne change que ce qui est visible sur le plan. Les éléments décoratifs restent disponibles dans l’éditeur de fond. Portes et fenêtres restent dans l’éditeur de plan ; leurs capteurs, la lumière du soleil et la lueur des lampes continuent de fonctionner.","space.layers.help.aria":"Aide : calques visibles","space.layer_decor":"Calque décoratif","space.layer_openings":"Portes, fenêtres et portails","space.fill_lqi_hint":"Selon le LQI moyen de la pièce : faible à 40 ou moins, fort à 180 ou plus. Couleurs et opacité suivent la palette générale.","space.fill_light_hint":"Couleurs distinctes pour « au moins une lampe allumée », « toutes éteintes » et « aucune source ». Couleurs et opacité suivent la palette générale.","space.temp_range":"Plage de confort","space.temp_min":"Minimum","space.temp_max":"Maximum","space.temp_range_hint":"Sous le minimum : froid. Dans la plage : confort. Au-dessus du maximum : chaud. Sans mesure de température, la lueur des sources lumineuses peut encore donner un ombrage de base.","space.show_names_hint":"Afficher les noms des pièces et les valeurs choisies.","space.show_names.help":"Masquer les noms masque aussi leurs valeurs sans effacer vos choix. La position des cartes des pièces est conservée.","space.show_names.help.aria":"Aide : noms des pièces","space.card_values":"Valeurs sur la carte","space.label_lqi_short":"Signal Zigbee","space.values_hidden":"Les valeurs sont enregistrées mais masquées.","space.values_hidden_action":"Afficher les noms des pièces","space.card_font_hint":"Met à l’échelle chaque carte de pièce de cet espace.","space.card_font.help":"100 % est la taille par défaut. Met à l’échelle toutes les cartes de pièces de cet espace en conservant tailles et positions individuelles. Appliqué à l’enregistrement.","space.card_font.help.aria":"Aide : taille de police des cartes","space.sun_inherit_short":"Comme les réglages généraux","space.north_general":"Comme les réglages généraux · {v}","space.north_custom":"Direction personnalisée","space.north_deg":"Direction du nord","space.compass_aria":"Boussole : nord à {v}","space.glow_enabled_hint":"Lueur douce autour des lampes allumées.","space.glow_enabled.help":"La lueur des lampes et la lumière du soleil sont indépendantes. Masquer les symboles d’ouvertures n’arrête aucun des deux effets. Le vitrage laisse passer le soleil même fenêtre fermée.","space.glow_enabled.help.aria":"Aide : lueur des sources lumineuses","space.error_title":"Saisissez un nom pour l’espace.","space.error_plan":"Ajoutez une image du plan ou passez au dessin à la main.","space.error_scale":"Saisissez une échelle dans la plage autorisée.","space.error_temp_value":"Saisissez un nombre.","space.error_temp_range":"Le maximum doit être supérieur au minimum.","space.error_north":"Saisissez un nombre entier entre 0° et 359°.","btn.reset_100":"Réinitialiser à 100 %","dialog.review_fields":"Vérifiez {n} champ(s)","dialog.discard_title":"Abandonner vos modifications ?","dialog.discard_message":"Vos modifications n’ont pas été enregistrées.","dialog.discard_confirm":"Abandonner","dialog.discard_keep":"Continuer","gs.show_room_tooltip_hint":"Survolez une pièce sur le plan pour voir son résumé.","gs.radar_show_live_hint":"Affiche ou masque toutes les cibles radar en direct. Cela ne démarre ni n’arrête l’enregistrement d’observation.","gs.bg_custom_hint":"Couleur de fond personnalisée.","gs.bg_theme_hint":"Fond du thème utilisé.","gs.sun_rays_hint":"Tracer les rayons du soleil à travers les ouvertures des fenêtres.","gs.error_glow_radius":"Saisissez un rayon supérieur à zéro.","gs.error_north":"Saisissez un nombre entier entre 0° et 359°.","gs.opacity_of":"Opacité : {name}","room.fill_inherit_hint":"Utiliser le mode de remplissage de tout l’espace.","room.fill_following":"Comme l’espace : {mode}.","room.fill_mode_label":"Mode de remplissage","room.temp_min_label":"Minimum","room.temp_max_label":"Maximum","room.sizes_hint":"En plus de la taille des cartes de l’espace ({v} %).","room.error_temp_range":"Saisissez des nombres pour les bornes de confort.","room.error_name":"Saisissez un nom ou choisissez une zone.","marker.binding.help":"Un marqueur lié affiche l’état en direct d’un appareil ou d’une entité Home Assistant et peut agir dessus. Un appareil virtuel est un simple marqueur sans liaison.","marker.binding.help.aria":"Aide : liaison à un appareil Home Assistant","marker.binding_virtual_hint":"Un appareil virtuel est un simple marqueur sans liaison.","marker.room_hint_auto":"Placé selon la zone Home Assistant de l’appareil.","marker.room_hint_override":"Remplace le placement par zone de l’appareil.","marker.light_role_seg_auto":"Auto","marker.light_role_seg_always":"Toujours","marker.light_role_seg_never":"Jamais","marker.glow_radius_empty_hint":"Vide : rayon général ({v}).","marker.icon.help":"Vide signifie automatique : l’icône vient des règles d’icônes de cet appareil. Choisissez-en une pour remplacer, ou épinglez le choix automatique.","marker.icon.help.aria":"Aide : icône du marqueur","marker.icon_custom_hint":"Icône personnalisée pour ce marqueur.","marker.display.help":"Comment la face du marqueur réagit à l’état et à l’activité. Les modes valeur remplacent l’icône par une valeur Home Assistant.","marker.display.help.aria":"Aide : mode d’affichage","marker.size.help":"Redimensionne et fait pivoter uniquement ce marqueur sur le plan. La zone de toucher grandit avec l’icône.","marker.size.help.aria":"Aide : taille et rotation de l’icône","marker.size_short":"Taille","marker.error_binding":"Choisissez un appareil ou une entité à lier.","marker.error_virtual_name":"Saisissez un nom pour l’appareil virtuel.","marker.error_run_target":"Choisissez l’action à exécuter.","marker.error_value_badge_source":"Choisissez une source de valeur pour le badge.","fill.seg_none":"Aucun","fill.seg_lqi":"Zigbee","fill.seg_light":"Lumières","fill.seg_temp":"Température","fill.seg_custom":"Perso"}};function te(e,a){const t=ae[e]?.[a]??ee[a];return"string"==typeof t&&t.trim().length>0}function ie(e,a,i){return t(ae[e]?.[a]??ee[a]??a,i)}function ne(t){const n=i(t.hass,t._config?.language);return{st:(e,a)=>ie(n,e,a),shelp:t=>{const i=`${t}.aria`;return te(n,t)&&te(n,i)?a`<hp-help data-help-key=${t} .text=${ie(n,t)} .ariaLabel=${ie(n,i)}></hp-help>`:e}}}const se=new Set(["busy","pickSaved","saved","savedBusy","savedAspect","cellCmInput","tempMinInput","tempMaxInput","northDegInput","cellCmTouched","displayTouched","deleteBlockers","copy"]);function re(e){return r({...e,planFile:e.planFile?e.planFile.name:null},se)}function oe(e,a){n(e,"space",re(a))}function le(e,a,t=!1){const i=[];e.title.trim()||i.push({field:`${a}-title`,message:"space.error_title"});const n=e.cellCmInput??String(e.cellCm),r=s(n),o=null===r?null:d(r,t);if((null===o||o<c||o>p)&&i.push({field:`${a}-cell-cm`,message:"space.error_scale"}),"file"!==e.source||e.planFile||e.planUrl||i.push({field:`${a}-plan`,message:"space.error_plan"}),"temp"===e.fillMode){const t=s(e.tempMinInput??String(e.tempMin)),n=s(e.tempMaxInput??String(e.tempMax));null===t&&i.push({field:`${a}-temp-min`,message:"space.error_temp_value"}),null===n&&i.push({field:`${a}-temp-max`,message:"space.error_temp_value"}),null!==t&&null!==n&&t>=n&&i.push({field:`${a}-temp-max`,message:"space.error_temp_range"})}if(null!==e.northDeg){const t=s(e.northDegInput??String(e.northDeg));null!==t&&Number.isInteger(t)&&t>=0&&t<=359||i.push({field:`${a}-north-deg`,message:"space.error_north"})}return i}const ce=c,pe=p,de=(e,a)=>{e.host._spaceDialog=a};function he(t){T(t.host);const{host:i}=t,n=i._spaceDialog,s=i._t.bind(i),{st:r}=ne(i),c=e=>`${t.idPrefix}-${e}`,p=le(n,t.idPrefix,i._imperial),d=e=>p.find(a=>a.field===e),h=function(e,a){return o(e,"space",re(a))}(i,n),u=h&&0===p.length&&!n.busy,m=i._importTotal>0&&"create"===n.mode?s("import.progress",{i:i._importTotal-i._importQueue.length,n:i._importTotal}):"",g=()=>{!function(e){l(e,"space")}(i),i._spaceDialog=null,i._importQueue=[],i._importTotal=0},_=async e=>{if(!h||n.busy)return void g();const a=e.currentTarget;await i._confirmDanger({key:"discard-space-dialog",kind:"warning",title:r("dialog.discard_title"),message:r("dialog.discard_message"),objectName:n.title.trim()||void 0,confirmLabel:r("dialog.discard_confirm"),cancelLabel:r("dialog.discard_keep"),icon:"mdi:content-save-off-outline",confirmIcon:"mdi:content-save-off-outline"})?g():a?.rejectClose?.()};return{title:`${"create"===n.mode?s("space.new"):s("space.header")}${m?` · ${m}`:""}`,badge:"edit"===n.mode?n.title.trim():"",requestClose:_,body:a`<div class="body hpf-form">
${ue(t,n,c,d)}
${ge(t,n,c,d)}
${_e(t,n,c)}
${be(t,n,c,d)}
${n.deleteBlockers?U({kind:"warning",role:"alert",text:s("space.delete_blocked",{n:String(n.deleteBlockers)})}):e}
</div>`,footer:a`<div class="row dialog-action-footer hpf-footer hpf-footer-space" slot="footer">
${"edit"===n.mode&&t.copySpace?a`<div class="dialog-action-group dialog-action-copy">
<button class="btn ghost hpf-mobile-icon" aria-label=${s("btn.copy")} title=${s("btn.copy")}
@click=${()=>t.copySpace()} ?disabled=${n.busy}>
<ha-icon icon="mdi:content-copy"></ha-icon><span class="hpf-action-label">${s("btn.copy")}</span>
</button>
</div>`:e}
${"edit"===n.mode&&t.deleteSpace?a`<div class="dialog-action-group dialog-action-danger">
<button class="btn danger hpf-mobile-icon" aria-label=${s("btn.delete")} title=${s("btn.delete")}
@click=${()=>t.deleteSpace()} ?disabled=${n.busy}>
<ha-icon icon="mdi:delete-outline"></ha-icon><span class="hpf-action-label">${s("btn.delete")}</span>
</button>
</div>`:e}
${Y(p.length?{action:Q(r("dialog.review_fields",{n:String(p.length)}),()=>{const e=p[0];if(!e)return;const a=i.renderRoot.querySelector(`#${e.field}`);a?.scrollIntoView({block:"center"}),a?.focus()})}:{})}
<div class="dialog-action-group dialog-action-commit">
${i._importTotal>0&&"create"===n.mode?a`<button class="btn ghost" @click=${()=>t.skipImport()}>${s("btn.skip")}</button>`:e}
<button class="btn ghost" data-hp="dialog-cancel" @click=${_}>${s("btn.cancel")}</button>
<button class="btn on" data-hp="dialog-confirm" @click=${()=>t.save()} ?disabled=${!u}
title=${p[0]?r(p[0].message):""}>
<ha-icon icon="mdi:check"></ha-icon>${n.busy?"…":s("btn.save")}
</button>
</div>
</div>`}}function ue(t,i,n,r){const{host:o}=t,l=o._t.bind(o),{st:c,shelp:p}=ne(o),m="edit"===i.mode&&i.spaceId?o._serverCfg?.spaces.find(e=>e.id===i.spaceId):void 0,g=m&&Number(m.cell_cm)>0?Number(m.cell_cm):null,_=null!==g&&Math.abs(i.cellCm-g)>1e-9,b=r(n("title")),f=r(n("cell-cm")),v=r(n("plan"));return H({id:"basics",title:l("space.card_basics"),body:a`
${q([F({label:l("space.title_label"),htmlFor:n("title"),error:b?c(b.message):void 0,control:a`<input id=${n("title")} class="hpf-input" type="text" maxlength="80"
placeholder=${l("space.title_ph")} .value=${i.title}
aria-invalid=${b?"true":e}
@input=${e=>de(t,{...i,title:e.target.value})} />`}),F({label:l("space.scale_label"),htmlFor:n("cell-cm"),help:t.help("space.cell_cm.help"),error:f?c(f.message):void 0,control:V({id:n("cell-cm"),wide:!0,value:i.cellCmInput??h(i.cellCm,o._imperial),unit:l(o._imperial?"space.scale_unit_imperial":"space.scale_unit"),min:Number(h(ce,o._imperial)),max:Number(h(pe,o._imperial)),step:.1,invalid:!!f,onInput:e=>{const a=s(e),n=null==a?null:d(a,o._imperial);de(t,{...i,cellCmInput:e,cellCmTouched:!0,cellCm:null!=n&&n>=ce&&n<=pe?n:i.cellCm})}})})],{narrow:!0})}
${_?U({kind:"warning",text:c("space.scale_changed")}):e}
${E({title:l("space.plan_label"),help:p("space.plan.help")})}
<div id=${n("plan")} tabindex="-1" aria-invalid=${v?"true":e}>
${w="plansrc",y=i.source,k=[{value:"draw",title:l("space.source_draw"),caption:c("space.source_draw_hint")},{value:"file",title:l("space.source_file"),caption:c("space.source_file_hint")}],$=e=>de(t,u(i,e)),x=l("space.plan_label"),a`<div class="hpf-choices" role="radiogroup" aria-label=${x??e}>
${k.map(e=>a`<label class="hpf-choice">
<input type="radio" name=${w} .checked=${e.value===y} @change=${()=>$(e.value)} />
<strong>${e.title}</strong><small>${e.caption}</small>
</label>`)}
</div>`}
</div>
${"file"===i.source?me(t,i):e}
${"draw"===i.source&&i.planUrl?U({text:c("space.plan_kept")}):e}
${v?a`<p class="hpf-error" role="alert">${c(v.message)}</p>`:e}`});var w,y,k,$,x}function me(t,i){const{host:n}=t,s=n._t.bind(n);return a`<div class="planrow">
${i.planFile?a`<span class="planname">${i.planFile.name}</span>`:i.planUrl?a`<img class="planprev" src=${n._display(i.planUrl)} alt=${s("space.plan_alt")} />`:a`<span class="planname muted">${s("space.no_plan")}</span>`}
<span class="fileupload">
<button class="btn filebtn" type="button" @click=${e=>e.currentTarget.nextElementSibling?.click()}>
<ha-icon icon="mdi:upload"></ha-icon>${i.planUrl||i.planFile?s("btn.replace"):s("btn.upload")}
</button>
<input type="file" hidden accept=".svg,.png,.jpg,.jpeg,.webp,image/svg+xml,image/png,image/jpeg,image/webp"
@change=${e=>t.pickPlanFile(e)} />
</span>
<button class="btn ghost" type="button" @click=${()=>t.toggleServerPlans()}
title=${s("space.pick_saved_hint")}>
<ha-icon icon="mdi:folder-image"></ha-icon>${s("space.pick_saved")}
</button>
</div>
${i.pickSaved?t.renderServerPlans(i):e}`}function ge(e,t,i,n){const{host:r}=e,o=r._t.bind(r),{st:l,shelp:c}=ne(r),p=n(i("temp-min")),d=n(i("temp-max")),h="custom"===t.fillMode?G({label:o("space.custom_fill"),picker:B({hex:(t.customFill||m).c,opacity:(t.customFill||m).a,opacityLabel:o("space.opacity"),onOpacity:a=>de(e,{...t,customFill:{c:(t.customFill||m).c,a:a}}),resetLabel:t.customFill?o("btn.reset"):void 0,onReset:()=>de(e,{...t,customFill:null}),picker:a`<hp-color-opacity .label=${o("space.custom_fill")} hide-label flat-swatch .opacityLabel=${o("space.opacity")}
.pickerLabels=${r._colorPickerLabels}
.color=${(t.customFill||m).c} .opacity=${(t.customFill||m).a}
@hp-color-opacity-change=${a=>{de(e,{...t,customFill:{c:a.detail.color,a:a.detail.opacity}})}}></hp-color-opacity>`})}):"temp"===t.fillMode?a`<div class="hpf-field">
<span class="hpf-label hpf-labelrow">${l("space.temp_range")}</span>
${q([F({label:l("space.temp_min"),htmlFor:i("temp-min"),error:p?l(p.message):void 0,control:V({id:i("temp-min"),wide:!0,value:t.tempMinInput??String(t.tempMin),unit:"°C",step:.5,invalid:!!p,onInput:a=>{const i=s(a);de(e,{...t,tempMinInput:a,...null!==i?{tempMin:i}:{}})}})}),F({label:l("space.temp_max"),htmlFor:i("temp-max"),error:d?l(d.message):void 0,control:V({id:i("temp-max"),wide:!0,value:t.tempMaxInput??String(t.tempMax),unit:"°C",step:.5,invalid:!!d,onInput:a=>{const i=s(a);de(e,{...t,tempMaxInput:a,...null!==i?{tempMax:i}:{}})}})})])}
<div class="hpf-templegend" aria-hidden="true">
<span>${o("gs.temp_cold")}</span><span>${o("gs.temp_ok")}</span><span>${o("gs.temp_hot")}</span>
</div>
<p class="hpf-hint">${l("space.temp_range_hint")}</p>
</div>`:a`<p class="hpf-hint">${l("lqi"===t.fillMode?"space.fill_lqi_hint":"space.fill_light_hint")}</p>`;return H({id:"appearance",title:o("space.display_section"),body:a`
${A({id:i("show-borders"),icon:"mdi:border-none-variant",title:o("space.show_borders"),caption:l("space.show_borders_hint"),help:c("space.show_borders.help"),checked:t.showBorders,onChange:a=>de(e,g(t,"showBorders",a))})}
${F({label:o("space.zero_wall_style"),help:e.help("space.zero_wall_style.help"),control:R({name:`${e.idPrefix}-zero-wall-style`,value:"solid"===t.zeroWallStyle?"solid":"dashed",ariaLabel:o("space.zero_wall_style"),options:[{value:"dashed",label:o("space.zero_wall_dashed"),sample:"dashed"},{value:"solid",label:o("space.zero_wall_solid"),sample:"solid"}],onChange:a=>de(e,{...t,zeroWallStyle:"solid"===a?"solid":"dashed"})})})}
${G({label:o("space.room_color"),picker:B({hex:t.roomColor,opacity:t.roomOpacity,opacityLabel:o("space.opacity"),onOpacity:a=>de(e,{...t,roomOpacity:a}),resetLabel:t.roomColor!==_||t.roomOpacity!==b?o("btn.reset"):void 0,onReset:()=>de(e,{...t,roomColor:_,roomOpacity:b}),picker:a`<hp-color-opacity .label=${o("space.room_color")} hide-label flat-swatch .opacityLabel=${o("space.opacity")}
.pickerLabels=${r._colorPickerLabels}
.color=${t.roomColor} .opacity=${t.roomOpacity} .showOpacity=${!0}
@hp-color-opacity-change=${a=>{de(e,{...t,roomColor:a.detail.color,roomOpacity:a.detail.opacity})}}></hp-color-opacity>`})})}
${E({title:o("space.fill_label"),help:e.help("space.fill_mode.help")})}
${R({name:`${e.idPrefix}-fill-mode`,value:"none"===t.fillMode?"custom":t.fillMode,ariaLabel:o("space.fill_label"),options:f.map(e=>({value:e,label:l(`fill.seg_${e}`)})),onChange:a=>de(e,{...t,fillMode:a})})}
${h}
${E({title:l("space.layers_label"),help:c("space.layers.help")})}
${u=[A({id:i("layer-decor"),compact:!0,icon:"mdi:sofa-outline",title:l("space.layer_decor"),checked:!t.hideDecor,onChange:a=>de(e,{...t,hideDecor:!a})}),A({id:i("layer-openings"),compact:!0,icon:"mdi:door",title:l("space.layer_openings"),checked:!t.hideOpenings,onChange:a=>de(e,{...t,hideOpenings:!a})}),A({id:i("show-lqi"),compact:!0,icon:"mdi:zigbee",title:o("space.show_lqi"),checked:t.showLqi,onChange:a=>de(e,{...t,showLqi:a})})],a`<div class="hpf-compact-list">${u}</div>`}`});var u}function _e(t,i,n){const{host:s}=t,r=s._t.bind(s),{st:o,shelp:l}=ne(s),c=Math.round(100*i.cardFontScale),p=[["labelTemp","space.label_temp","mdi:thermometer",void 0],["labelHum","space.label_hum","mdi:water-outline",void 0],["labelLqi","space.label_lqi_short","mdi:zigbee","space.label_lqi"],["labelLight","space.label_light","mdi:lightbulb-outline",void 0]].map(([e,a,s,l])=>({id:n(`tile-${e}`),label:"space.label_lqi_short"===a?o(a):r(a),icon:s,title:l?r(l):void 0,checked:i[e],disabled:!i.showNames,onChange:a=>de(t,{...i,[e]:a})}));return H({id:"room-cards",title:r("space.roomcard_section"),body:a`
${A({id:n("show-names"),icon:"mdi:format-text",title:r("space.show_names"),caption:o("space.show_names_hint"),help:l("space.show_names.help"),checked:i.showNames,onChange:e=>de(t,g(i,"showNames",e))})}
<div class="hpf-field">
<span class="hpf-label hpf-labelrow">${o("space.card_values")}</span>
${I(p,o("space.card_values"))}
${i.showNames?e:U({icon:"mdi:eye-off-outline",text:o("space.values_hidden"),action:Q(o("space.values_hidden_action"),()=>de(t,g(i,"showNames",!0)))})}
</div>
<div class="hpf-field">
<div class="hpf-headline">
<span class="hpf-label hpf-labelrow"><label for=${n("card-font")}>${r("space.card_font")}</label>${l("space.card_font.help")}</span>
${Q(o("btn.reset_100"),()=>de(t,{...i,cardFontScale:1}),{disabled:100===c})}
</div>
${Z({id:n("card-font"),min:50,max:300,step:5,value:c,unit:"%",ariaLabel:r("space.card_font"),slider:t.rangeInput(50,300,5,c,e=>de(t,{...i,cardFontScale:e/100}),!1,r("space.card_font")),onInput:e=>de(t,{...i,cardFontScale:e/100})})}
${K("50%","300%")}
<p class="hpf-hint">${o("space.card_font_hint")}</p>
</div>
<div class="hpf-tint hpf-preview">${s._renderCardPreview(i.cardFontScale,1,1)}</div>`})}function be(t,i,n,r){const{host:o}=t,l=o._t.bind(o),{st:c,shelp:p}=ne(o),d=v(o._settings,{}),h=i.northDeg??d,u=r(n("north-deg")),m=null===d?"—":`${d}°`;return H({id:"sun",title:l("space.card_sun"),body:a`
${F({label:l("space.bg_mode"),help:t.help("space.bg_mode.help"),control:R({name:`${t.idPrefix}-bg-mode`,value:i.bgMode??"",ariaLabel:l("space.bg_mode"),options:[{value:"",label:l("space.sun_inherit")},{value:"static",label:l("gs.bg_static")},{value:"daynight",label:l("gs.bg_daynight")}],onChange:e=>de(t,{...i,bgMode:"static"===e||"daynight"===e?e:null})})})}
${"static"===(i.bgMode??w(o._settings,{}))?G({label:l("space.bg_color"),picker:B({hex:i.bgColor||y(o._settings,{bgColor:null})||o._stageBgHex(),resetLabel:i.bgColor?l("space.bg_inherit"):void 0,onReset:()=>de(t,{...i,bgColor:null}),picker:a`<hp-color-opacity .label=${l("space.bg_color")} hide-label flat-swatch .pickerLabels=${o._colorPickerLabels}
.color=${i.bgColor||y(o._settings,{bgColor:null})||o._stageBgHex()}
.opacity=${1} .showOpacity=${!1}
@hp-color-opacity-change=${e=>de(t,{...i,bgColor:e.detail.color})}></hp-color-opacity>`})}):e}
${W({label:l("space.sun_rays"),name:`${t.idPrefix}-sun-rays`,value:null===i.sunRays?"":i.sunRays?"1":"0",options:[{value:"",label:c("space.sun_inherit_short")},{value:"1",label:l("space.sun_on")},{value:"0",label:l("space.sun_off")}],onChange:e=>de(t,{...i,sunRays:""===e?null:"1"===e})})}
${a`<hr class="hpf-divider" />`}
<div class="hpf-north">
<span class="hpf-labelrow"><label for=${n("north-mode")}>${l("space.north")}</label>${t.help("space.north.help")}</span>
<select id=${n("north-mode")} class="hpf-select"
@change=${e=>{const a="custom"===e.target.value?d??0:null;de(t,{...i,northDeg:a,northDegInput:null===a?"":String(a)})}}>
<option value="inherit" ?selected=${null===i.northDeg}>${c("space.north_general",{v:m})}</option>
<option value="custom" ?selected=${null!==i.northDeg}>${c("space.north_custom")}</option>
</select>
<span class="hpf-north-n" aria-hidden="true">N</span>
${g=h,_=c("space.compass_aria",{v:null===h?"—":`${h}°`}),a`<span class="hpf-compass ${null===g?"hpf-unset":""}" role="img" aria-label=${_}>
<svg viewBox="0 0 24 24" style=${`transform: rotate(${null===g?0:(g%360+360)%360}deg)`} aria-hidden="true">
<path d="M12 3v18M12 3l-4 7h8z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</span>`}
</div>
${null!==i.northDeg?F({label:c("space.north_deg"),htmlFor:n("north-deg"),error:u?c(u.message):void 0,control:a`<div class="hpf-inline hpf-wrap">
${V({id:n("north-deg"),value:i.northDegInput??String(i.northDeg),unit:"°",min:0,max:359,step:1,invalid:!!u,onInput:e=>{const a=s(e);de(t,{...i,northDegInput:e,...null!==a&&Number.isInteger(a)&&a>=0&&a<=359?{northDeg:a}:{}})}})}
${Q(c("space.sun_inherit_short"),()=>de(t,{...i,northDeg:null,northDegInput:""}))}
</div>`}):e}
${A({id:n("glow"),icon:"mdi:lightbulb-outline",title:l("space.glow_enabled"),caption:c("space.glow_enabled_hint"),help:p("space.glow_enabled.help"),checked:i.glowEnabled,onChange:e=>de(t,{...i,glowEnabled:e})})}`});var g,_}function fe(e){const a=e.visualViewport;return a&&Number.isFinite(a.width)&&Number.isFinite(a.height)?{left:Number(a.offsetLeft)||0,top:Number(a.offsetTop)||0,width:Math.max(0,Number(a.width)||0),height:Math.max(0,Number(a.height)||0)}:{left:0,top:0,width:e.innerWidth,height:e.innerHeight}}function ve(e,a,t,i=7,n=8){const s=t.left+n,r=t.top+n,o=Math.max(s,t.left+t.width-n),l=Math.max(r,t.top+t.height-n),c=Math.max(0,o-s),p=Math.max(0,l-r),d=Math.min(Math.max(0,a.width),c),h=Math.min(Math.max(0,a.height),p);let u=e.left;u+d>o&&(u=e.right-d),u=Math.min(Math.max(s,u),Math.max(s,o-d));const m=e.bottom+i,g=e.top-i-h;let _="bottom",b=m;return m+h>l&&g>=r?(_="top",b=Math.min(Math.max(r,g),Math.max(r,l-h))):b=Math.min(Math.max(r,m),Math.max(r,l-h)),{left:Math.round(u),top:Math.round(b),side:_,maxWidth:Math.round(c),maxHeight:Math.round(p)}}class we{constructor(e,a,t){this.owner=e,this.name=a,this.keydown=t,this.host=null,this.root=null}window(){return this.owner.ownerDocument.defaultView}dialog(){return this.owner.closest("hp-dialog")}usesPopover(e=!1){const a=this.window();return!e&&!!a&&function(e){const a=e.HTMLElement?.prototype;return"function"==typeof a?.showPopover&&"function"==typeof a?.hidePopover}(a)}get hasFallback(){return!!this.host}containsPath(e){return e.includes(this.owner)||!!this.host&&e.includes(this.host)}ownsActiveElement(){let e=this.owner.ownerDocument.activeElement;for(;e?.shadowRoot?.activeElement;)e=e.shadowRoot.activeElement;return!(!e||e!==this.owner&&!this.owner.shadowRoot?.contains(e)&&!this.root?.contains(e))}renderFallback(e,t){const i=this.ensureFallback();return i?(k(a`<style>${t}</style>${e}`,i),i):null}surface(e,a){return a?this.owner.shadowRoot?.querySelector(e)||null:this.root?.querySelector(e)||null}destroy(){this.root&&k(e,this.root),this.host?.remove(),this.host=null,this.root=null}ensureFallback(){if(this.root?.isConnected)return this.root;const e=this.dialog()?.overlayPortal()||this.owner.ownerDocument.body;if(!e)return null;const a=this.owner.ownerDocument.createElement("div");return a.dataset.hpOverlay=this.name,a.style.display="contents",this.keydown&&a.addEventListener("keydown",this.keydown,!0),e.append(a),this.host=a,this.root=a.attachShadow({mode:"open"}),this.root}}let ye=0;class ke extends x{constructor(){super(...arguments),this.text="",this.ariaLabel="",this.pointerHover=!1,this._open=!1,this._openTimer=0,this._closeTimer=0,this._positionRaf=0,this._forceFallback=!1,this._hoverOwned=!1,this._overlayDispose=null,this._scrollDialog=null,this._descriptionId="hp-help-description-"+ ++ye,this._surfacePointerEnter=()=>{const e=this._window();this._closeTimer&&e?.clearTimeout(this._closeTimer),this._closeTimer=0},this._surfacePointerLeave=()=>{this._scheduleClose()},this._outsidePointerDown=e=>{if(!this._open)return;const a=e.composedPath();this._floating.containsPath(a)||this._closeHelp(!1,"outside")},this._dialogScroll=e=>{const a=this._floating.containsPath(e.composedPath()),t=e.target,i=this._dialog(),n=t instanceof Node&&(t===i||t instanceof Element&&t.contains(this));var s;this._open&&(s=n,!a&&s)&&this._closeHelp(!1,"scroll")},this._keyDown=e=>{if(!this._open||e.defaultPrevented)return;if("Escape"===e.key)return e.preventDefault(),e.stopImmediatePropagation(),void this._closeHelp(this._floating.ownsActiveElement(),"escape");const a=this.renderRoot.querySelector(".trigger");if(!a||!e.composedPath().includes(a))return;const t=this._surface();if(!t||t.scrollHeight<=t.clientHeight)return;const i=Math.max(40,.8*t.clientHeight),n={ArrowDown:40,ArrowUp:-40,PageDown:i,PageUp:-i};if("Home"===e.key)t.scrollTop=0;else if("End"===e.key)t.scrollTop=t.scrollHeight;else{if(!(e.key in n))return;t.scrollBy({top:n[e.key],behavior:"auto"})}e.preventDefault(),e.stopImmediatePropagation()},this._floating=new we(this,"help",this._keyDown),this._queuePosition=()=>{if(!this._open||this._positionRaf)return;const e=this._window();e&&(this._positionRaf=e.requestAnimationFrame(()=>{this._positionRaf=0,this._position()||this._closeHelp()}))}}connectedCallback(){super.connectedCallback(),this.addEventListener("keydown",this._keyDown,!0)}willUpdate(){this.toggleAttribute("data-has-content",this._hasContent())}disconnectedCallback(){const e=this.ownerDocument.defaultView;this._unsubscribeOpenListeners(),this.removeEventListener("keydown",this._keyDown,!0),this._clearTimers(),this._positionRaf&&e?.cancelAnimationFrame(this._positionRaf),this._positionRaf=0,this._closeHelp(!1,"disconnect"),super.disconnectedCallback()}updated(e){if(e.has("pointerHover")&&!this.pointerHover){const e=this._window();this._openTimer&&e?.clearTimeout(this._openTimer),this._openTimer=0,this._hoverOwned&&this._closeHelp(!1,"outside")}this._hasContent()||!this._open?this._open&&e.has("text")&&(this._usesPopover()||this._renderFallback(),this._queuePosition()):this._closeHelp()}_window(){return this._floating.window()}_dialog(){return this._floating.dialog()}_usesPopover(){return this._floating.usesPopover(this._forceFallback)}_hasContent(){return e=this.text,a=this.ariaLabel,"string"==typeof e&&e.trim().length>0&&"string"==typeof a&&a.trim().length>0;var e,a}_clearTimers(){const e=this._window();this._openTimer&&e?.clearTimeout(this._openTimer),this._closeTimer&&e?.clearTimeout(this._closeTimer),this._openTimer=0,this._closeTimer=0}_scheduleOpen(){const e=this._window();this.pointerHover&&e&&!this._open&&!this._openTimer&&(this._closeTimer&&e.clearTimeout(this._closeTimer),this._closeTimer=0,this._openTimer=e.setTimeout(()=>{this._openTimer=0,this.isConnected&&(this._hoverOwned=!0,this._openHelp())},300))}_scheduleClose(){const e=this._window(),a=this.renderRoot.querySelector(".trigger");e&&!a?.matches(":focus-visible")&&(this._openTimer&&e.clearTimeout(this._openTimer),this._closeTimer&&e.clearTimeout(this._closeTimer),this._openTimer=0,this._closeTimer=e.setTimeout(()=>{this._closeTimer=0,this.isConnected&&this._closeHelp()},150))}_triggerPointerEnter(e){this.pointerHover&&"mouse"===e.pointerType&&this._scheduleOpen()}_triggerPointerLeave(e){"mouse"===e.pointerType&&this._scheduleClose()}_triggerFocus(){queueMicrotask(()=>{const e=this.renderRoot.querySelector(".trigger");e?.matches(":focus-visible")&&(this._hoverOwned=!1,this._openHelp())})}_triggerBlur(){this._scheduleClose()}_triggerClick(){this._open?this._closeHelp():(this._hoverOwned=!1,this._openHelp())}_subscribeOpenListeners(){this.ownerDocument.addEventListener("pointerdown",this._outsidePointerDown,!0),this.ownerDocument.addEventListener("keydown",this._keyDown,!0);const e=this._window();e?.addEventListener("resize",this._queuePosition),e?.addEventListener("orientationchange",this._queuePosition),e?.visualViewport?.addEventListener("resize",this._queuePosition),e?.visualViewport?.addEventListener("scroll",this._queuePosition),this._scrollDialog=this._dialog(),this._scrollDialog?.addEventListener("scroll",this._dialogScroll,!0)}_unsubscribeOpenListeners(){this.ownerDocument.removeEventListener("pointerdown",this._outsidePointerDown,!0),this.ownerDocument.removeEventListener("keydown",this._keyDown,!0);const e=this._window();e?.removeEventListener("resize",this._queuePosition),e?.removeEventListener("orientationchange",this._queuePosition),e?.visualViewport?.removeEventListener("resize",this._queuePosition),e?.visualViewport?.removeEventListener("scroll",this._queuePosition),this._scrollDialog?.removeEventListener("scroll",this._dialogScroll,!0),this._scrollDialog=null}async _openHelp(){!this._open&&this._hasContent()&&(this._clearTimers(),this._forceFallback=!1,this._open=!0,this._subscribeOpenListeners(),await this.updateComplete,this._open&&(this._usesPopover()||this._renderFallback(),this._position()?this._overlayDispose=this._dialog()?.registerOverlay({owner:this,group:"transient",close:e=>this._closeHelp("escape"===e&&this._floating.ownsActiveElement(),e)})||null:this._closeHelp()))}_closeHelp(e=!1,a="exclusive"){if(!this._open&&!this._floating.hasFallback)return;this._clearTimers();const t=this._overlayDispose;this._overlayDispose=null,t?.();const i=this.renderRoot.querySelector(".tooltip");if(i?.hidePopover)try{i.matches(":popover-open")&&i.hidePopover()}catch{}this._open=!1,this._hoverOwned=!1,this._unsubscribeOpenListeners(),this._floating.destroy(),e&&this.updateComplete.then(()=>this.renderRoot.querySelector(".trigger")?.focus())}_tooltipTemplate(t){return a`<div class="tooltip" data-side="bottom" popover=${t?"manual":e}
role="tooltip" aria-hidden="true" tabindex="-1"
@pointerenter=${this._surfacePointerEnter} @pointerleave=${this._surfacePointerLeave}>${this.text}</div>`}_renderFallback(){const e=ke.styles.cssText;this._floating.renderFallback(this._tooltipTemplate(!1),e)}_surface(){return this._floating.surface(".tooltip",this._usesPopover())}_position(){if(!this._open)return!1;const e=this._window(),a=this.renderRoot.querySelector(".trigger");let t=this._surface();if(!e||!a?.isConnected||!t?.isConnected)return!1;const i=fe(e);if(t.style.maxWidth=`${Math.max(0,Math.min(320,i.width-16))}px`,t.style.maxHeight=`${Math.max(0,i.height-16)}px`,t.style.visibility="hidden",this._usesPopover()&&t.showPopover)try{t.matches(":popover-open")||t.showPopover()}catch{if(this._forceFallback=!0,this._renderFallback(),t=this._surface(),!t?.isConnected)return!1;t.style.visibility="hidden"}const n=a.getBoundingClientRect(),s=t.getBoundingClientRect();if(!(n.width&&n.height&&s.width&&s.height))return!1;const r=ve(n,s,i);return t.style.left=`${r.left}px`,t.style.top=`${r.top}px`,t.dataset.side=r.side,t.style.visibility="",!0}render(){if(!this._hasContent())return e;const t=this.ariaLabel.trim();return a`
<span id=${this._descriptionId} class="sr-only" role="tooltip"
aria-hidden=${this._open?"false":"true"}>${this.text}</span>
<button class="trigger" type="button" aria-label=${t}
aria-describedby=${this._open?this._descriptionId:e}
aria-expanded=${this._open?"true":"false"}
@pointerenter=${this._triggerPointerEnter} @pointerleave=${this._triggerPointerLeave}
@focus=${this._triggerFocus} @blur=${this._triggerBlur} @click=${this._triggerClick}>
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<path d=${S}></path>
</svg>
</button>
${this._usesPopover()?this._tooltipTemplate(!0):e}
`}}ke.properties={text:{type:String},ariaLabel:{type:String,attribute:"aria-label"},pointerHover:{type:Boolean,attribute:"data-pointer-hover",reflect:!0},_open:{state:!0},_forceFallback:{state:!0}},ke.styles=$`:host{display:none;flex:none;vertical-align:middle;}:host([data-has-content]){display:inline-flex;}.trigger{width:32px;height:32px;display:inline-flex;align-items:center;justify-content:center;flex:none;box-sizing:border-box;padding:0;color:var(--secondary-text-color,#9aa4ad);background:transparent;border:0;border-radius:50%;cursor:help;-webkit-tap-highlight-color:transparent;}.trigger svg{display:block;width:18px;height:18px;fill:currentColor;}:host([data-pointer-hover]) .trigger:hover,.trigger:focus-visible,.trigger[aria-expanded='true']{color:var(--primary-text-color,#fff);background:rgb(127 127 127 / 0.18);outline:none;}.trigger:focus-visible{box-shadow:0 0 0 2px var(--primary-color,#03a9f4);}.tooltip{position:fixed;z-index:2147483647;inset:auto;top:0;left:0;width:max-content;max-width:min(320px,calc(100vw - 16px));max-height:calc(100vh - 16px);margin:0;box-sizing:border-box;padding:9px 11px;overflow:auto;color:var(--primary-text-color,#fff);background:var(--card-background-color,#202126);border:1px solid var(--divider-color,rgb(255 255 255 / 0.2));border-radius:9px;box-shadow:0 8px 24px rgb(0 0 0 / 0.32);font:400 13px/1.4 system-ui,sans-serif;text-align:start;white-space:normal;overflow-wrap:anywhere;opacity:1;transform:translateY(0);transition:opacity 120ms ease,transform 120ms ease;}.tooltip[data-side='top']{transform-origin:bottom center;}.tooltip[data-side='bottom']{transform-origin:top center;}.tooltip[popover]:not(:popover-open){display:none;}@starting-style{.tooltip{opacity:0;transform:translateY(3px);}}.sr-only{position:fixed;inset:0 auto auto 0;width:1px;height:1px;padding:0;margin:0;overflow:hidden;clip:rect(0,0,0,0);clip-path:inset(50%);white-space:nowrap;border:0;}@media (pointer:coarse){.trigger{width:40px;height:40px;}}@media (prefers-reduced-motion:reduce){.tooltip{transition:none;}}`,customElements.get("hp-help")||customElements.define("hp-help",ke);function $e(e,a,t){const i=(e?.spaces||[]).find(e=>e?.id===t),n=new Set((i?.rooms||[]).map(e=>String(e?.id||"")).filter(Boolean)),s=[...new Set((e?.markers||[]).filter(e=>!0!==e?.removed&&"string"==typeof e?.id).filter(e=>e.space===t||"string"==typeof e.room_id&&n.has(e.room_id)||a?.[String(e.id)]?.s===t).map(e=>String(e.id)))].sort((e,a)=>e.localeCompare(a)),r=(e?.markers||[]).filter(e=>!0!==e?.removed&&"string"==typeof e?.id).filter(e=>!s.includes(String(e.id))).filter(e=>((e,a)=>(e?.vacuum?.map_routes||[]).filter(e=>e&&e.space===a&&"string"==typeof e.id).map(e=>String(e.id)))(e,t).length>0).map(e=>String(e.id)).sort((e,a)=>e.localeCompare(a));return{markerIds:s,count:s.length,routeMarkerIds:r,routeCount:r.length}}function xe(e,a,t){return t?`${e} ${a.replace("{count}",String(t))}`:e}const Se=4096,Ce=1e6,Me=()=>({kind:"unknown",width:null,height:null,alpha:!1,decodedBytes:null}),Le=(e,a,t)=>{if(!Number.isInteger(e)||!Number.isInteger(a)||e<=0||a<=0||e>Ce||a>Ce)return{kind:"unknown",width:null,height:null,alpha:!1,decodedBytes:null};const i=e*a*4;return{kind:Math.max(e,a)>16384?"hard":i>134217728?"warn":"safe",width:e,height:a,alpha:t,decodedBytes:i}},ze=(e,a)=>(e[a]<<24|e[a+1]<<16|e[a+2]<<8|e[a+3])>>>0,Pe=(e,a)=>e[a]<<8|e[a+1],De=(e,a)=>e[a]|e[a+1]<<8|e[a+2]<<16,Te=(e,a)=>e[a]|e[a+1]<<8,He=(e,a,t)=>{for(let i=0;i<t.length;i++)if(e[a+i]!==t.charCodeAt(i))return!1;return!0};function Ee(e,a){try{return"png"===a?function(e){if(e.length<33||2303741511!==ze(e,0)||218765834!==ze(e,4))return{kind:"unknown",width:null,height:null,alpha:!1,decodedBytes:null};if(!He(e,12,"IHDR"))return{kind:"unknown",width:null,height:null,alpha:!1,decodedBytes:null};const a=ze(e,16),t=ze(e,20),i=e[25];let n=4===i||6===i;if(!n){let a=33;for(let t=0;t<64&&a+8<=e.length;t++){const t=ze(e,a);if(He(e,a+4,"tRNS")){n=!0;break}if(He(e,a+4,"IDAT")||He(e,a+4,"IEND"))break;if(t>e.length)break;a+=12+t}}return Le(a,t,n)}(e):"jpg"===a?function(e){if(e.length<4||255!==e[0]||216!==e[1])return{kind:"unknown",width:null,height:null,alpha:!1,decodedBytes:null};let a=2;for(let t=0;t<256&&a+4<=e.length;t++){if(255!==e[a]){a++;continue}const t=e[a+1];if(216===t||t>=208&&t<=217){a+=2;continue}if(t>=192&&t<=207&&196!==t&&200!==t&&204!==t)return a+9>e.length?Me():Le(Pe(e,a+7),Pe(e,a+5),!1);if(218===t)return Me();const i=Pe(e,a+2);if(i<2)return Me();a+=2+i}return{kind:"unknown",width:null,height:null,alpha:!1,decodedBytes:null}}(e):"webp"===a?function(e){if(e.length<30||!He(e,0,"RIFF")||!He(e,8,"WEBP"))return{kind:"unknown",width:null,height:null,alpha:!1,decodedBytes:null};if(He(e,12,"VP8X")){const a=!!(16&e[20]);return Le(De(e,24)+1,De(e,27)+1,a)}if(He(e,12,"VP8 "))return 157!==e[23]||1!==e[24]||42!==e[25]?{kind:"unknown",width:null,height:null,alpha:!1,decodedBytes:null}:Le(16383&Te(e,26),16383&Te(e,28),!1);if(He(e,12,"VP8L")){if(47!==e[20])return{kind:"unknown",width:null,height:null,alpha:!1,decodedBytes:null};const a=e[21]|e[22]<<8|e[23]<<16|e[24]<<24;return Le(1+(16383&a),1+(a>>14&16383),1==(a>>28&1))}return{kind:"unknown",width:null,height:null,alpha:!1,decodedBytes:null}}(e):{kind:"unknown",width:null,height:null,alpha:!1,decodedBytes:null}}catch{return{kind:"unknown",width:null,height:null,alpha:!1,decodedBytes:null}}}function Fe(e,a,t=4096){const i=Math.min(1,t/Math.max(e,a));return{width:Math.max(1,Math.round(e*i)),height:Math.max(1,Math.round(a*i))}}const qe={"image/svg+xml":"svg","image/png":"png","image/jpeg":"jpg","image/webp":"webp"};function Ae(e){return new Promise((a,t)=>{const i=new FileReader;i.onerror=()=>t(i.error||new Error("read failed")),i.onload=()=>{const e=String(i.result||""),n=e.indexOf(",");n<0?t(new Error("unexpected data url")):a(e.slice(n+1))},i.readAsDataURL(e)})}async function Re(e,a=1.414){const t=URL.createObjectURL(e);try{return await new Promise(e=>{const i=new Image;i.onload=()=>e(i.naturalWidth&&i.naturalHeight?i.naturalWidth/i.naturalHeight:a),i.onerror=()=>e(a),i.src=t})}finally{URL.revokeObjectURL(t)}}async function Ie(e,a,t){const[i,n]=await Promise.all([Ae(e),Re(e)]);return{ext:a,b64:i,aspect:n,name:t}}async function Be(e,a=1/0){const t=function(e){return qe[e.type]||(e.name.toLowerCase().endsWith(".svg")?"svg":"")}(e);if(!t)return{kind:"reject"};if("svg"===t)return{kind:"pass",ext:t};const i=Ee(new Uint8Array(await e.arrayBuffer()),t);return"safe"===i.kind&&e.size<=a?{kind:"pass",ext:t}:{kind:"guard",state:{file:e,ext:t,probe:i,busy:!1}}}async function Ne(e){const a=createImageBitmap(e.file,{imageOrientation:"from-image"}),t=await Promise.race([a,new Promise((e,a)=>{setTimeout(()=>a(new Error("decode timeout")),globalThis.__HP_BACKDROP_TIMEOUT_MS??1e4)})]);try{const{width:a,height:i}=Fe(t.width,t.height,Se),n=e.probe.alpha,s=n?"image/png":"image/jpeg";let r;if("function"==typeof OffscreenCanvas){const e=new OffscreenCanvas(a,i),n=e.getContext("2d");if(!n)throw new Error("no 2d context");n.drawImage(t,0,0,a,i),r=await e.convertToBlob({type:s,quality:.9})}else{const e=document.createElement("canvas");e.width=a,e.height=i;const n=e.getContext("2d");if(!n)throw new Error("no 2d context");n.drawImage(t,0,0,a,i),r=await new Promise((a,t)=>{e.toBlob(e=>e?a(e):t(new Error("encode failed")),s,.9)})}const o=n?"png":"jpg";return{blob:r,ext:o,name:`${e.file.name.replace(/\.[^.]+$/,"")||"plan"}-reduced.${o}`}}finally{t.close()}}const Oe=e=>(e/1048576).toFixed(e>=10485760?0:1);function Ge(e,t,i,n,s,r=!0){const o=e._backdropGuard;if(!o)return null;const{probe:l}=o,c="hard"===l.kind,p=l.width&&l.height?Fe(l.width,l.height,Se):null,d=c?e._t("backdrop.too_large_body",{w:l.width??0,h:l.height??0,limit:16384}):"unknown"===l.kind?e._t("backdrop.unknown_body"):e._t("backdrop.large_body",{w:l.width??0,h:l.height??0,fileMb:Oe(o.file.size),decodedMb:Oe(l.decodedBytes??0)}),h=()=>{e._backdropGuard?.busy||i()},u=()=>e._backdropGuard?.file===o.file;return a`<hp-dialog .hass=${n} data-kind="decor"
.title=${e._t(c?"backdrop.too_large_title":"backdrop.large_title")}
icon="mdi:image-size-select-large" dismiss-on-scrim @hp-close=${()=>h()}>
<div class="body"><p>${d}</p>
${p?a`<p>${e._t("backdrop.reduced_dimensions",{w:p.width,h:p.height})}</p>`:null}
</div>
<div class="row" slot="footer">
<button class="btn ghost" data-hp="dialog-cancel"
?disabled=${o.busy} @click=${()=>h()}>
${e._t("btn.cancel")}</button>
<span class="spacer"></span>
${c?null:a`
${r?a`
<button class="btn ghost" data-hp="dialog-confirm"
?disabled=${o.busy} @click=${()=>(async()=>{if(e._backdropGuard?.busy)return;if(s)return e._backdropGuard={...o,busy:!0},e.requestUpdate?.(),await s(o.file,o.file.name),void(u()&&i());const a=await Ie(o.file,o.ext,o.file.name);u()&&(t(a),i())})()}>
${e._t("backdrop.keep_original")}</button>`:null}
<button class="btn on" data-hp="dialog-confirm"
?disabled=${o.busy} @click=${()=>(async()=>{if(!e._backdropGuard?.busy){e._backdropGuard={...o,busy:!0},e.requestUpdate?.();try{const e=await Ne(o);if(s)return await s(e.blob,e.name),void(u()&&i());const a=await Ie(e.blob,e.ext,e.name);if(!u())return;t(a),i()}catch{if(!u())return;i(),e._showToast(e._t("backdrop.downscale_failed"))}}})()}>
${o.busy?e._t("backdrop.reducing"):e._t("backdrop.use_downscaled")}
</button>`}
</div>
</hp-dialog>`}export{Ie as A,Ge as B,le as C,ce as D,pe as E,we as F,$e as G,xe as H,X as a,F as b,U as c,R as d,T as e,H as f,A as g,B as h,E as i,J as j,q as k,K as l,Y as m,he as n,O as o,G as p,ie as q,Z as r,ne as s,Q as t,V as u,N as v,fe as w,ve as x,Be as y,oe as z};