diff --git a/components/smart_display/runtime_tiles.h b/components/smart_display/runtime_tiles.h index 40965dc..e4e256d 100644 --- a/components/smart_display/runtime_tiles.h +++ b/components/smart_display/runtime_tiles.h @@ -32,7 +32,9 @@ using settings_screen::rotation; using settings_screen::rotation_supported; using settings_screen::auto_home; using settings_screen::auto_home_seconds; +using settings_screen::page_buttons; inline esphome::ESPPreferenceObject rotation_preference; +inline esphome::ESPPreferenceObject buttons_preference; inline esphome::ESPPreferenceObject swipe_preference; inline esphome::ESPPreferenceObject home_preference; inline esphome::ESPPreferenceObject dark_preference; @@ -141,10 +143,13 @@ inline void load_settings() { swipe_preference = esphome::global_preferences->make_preference(0x53575031); home_preference = esphome::global_preferences->make_preference(0x484F4D31); dark_preference = esphome::global_preferences->make_preference(0x44524B31); + buttons_preference = esphome::global_preferences->make_preference(0x50474231); uint32_t swipe_saved=0; if(swipe_preference.load(&swipe_saved))swipe_pages=swipe_saved==1; uint32_t dark_saved=0; if(dark_preference.load(&dark_saved))settings_screen::dark_mode=dark_saved==1; + uint32_t buttons_saved=1; + if(buttons_preference.load(&buttons_saved))page_buttons=buttons_saved!=0; HomeTimeout home; if(home_preference.load(&home) && home.seconds>=30 && home.seconds<=3600){ auto_home=home.enabled?1:0;auto_home_seconds=(int32_t)home.seconds; @@ -167,6 +172,8 @@ inline void persist_settings() { home_preference.save(&home); uint32_t dark = settings_screen::dark_mode ? 1 : 0; dark_preference.save(&dark); + uint32_t buttons = page_buttons ? 1 : 0; + buttons_preference.save(&buttons); if (rotation_supported) { uint32_t turned = (uint32_t) rotation; rotation_preference.save(&turned); } } inline bool parse_settings(JsonObject obj, screen_settings::Settings &s) { @@ -192,7 +199,8 @@ struct Widgets { int title_x=0,title_y=0,value_x=0,value_y=0; const lv_font_t *value_font{}, *icon_font{}; // Wide cards span both columns; custom cards (clock, forecast, graph) draw into `extra`. // Full (firmware 0.2.62+) takes the whole page: the double-width card's head on top, a control at the bottom. - bool wide=false, full=false; int base_width=0, base_height=0; const lv_font_t *title_font{}; + // `base_y`: the card's row in the profile; without the page bar (firmware 0.2.69+) place_page moves it down. + bool wide=false, full=false; int base_width=0, base_height=0, base_y=0; const lv_font_t *title_font{}; // `extra_full`: the size the parts were built for; a slot that changes between full and double width rebuilds them. lv_obj_t *extra{}; std::string extra_mode; bool extra_full=false; std::array parts{}; lv_point_precise_t *points{}; // Analog clock: centre and radius of the dial, so the second hand can move without a card redraw. @@ -2286,6 +2294,7 @@ inline void bind(size_t index, lv_obj_t *tile, lv_obj_t *title, lv_obj_t *value, widgets[index] = {tile, title, value, circle, icon, index}; auto &w=widgets[index]; w.title_x=lv_obj_get_x(title);w.title_y=lv_obj_get_y(title);w.value_x=lv_obj_get_x(value);w.value_y=lv_obj_get_y(value);w.value_font=lv_obj_get_style_text_font(value,LV_PART_MAIN); w.base_width=lv_obj_get_width(tile);w.base_height=lv_obj_get_height(tile); + w.base_y=lv_obj_get_y(tile); w.title_font=lv_obj_get_style_text_font(title,LV_PART_MAIN); w.icon_font=lv_obj_get_style_text_font(icon,LV_PART_MAIN); w.unit=lv_label_create(tile);lv_obj_set_style_text_font(w.unit,w.value_font,0);lv_obj_remove_flag(w.unit,LV_OBJ_FLAG_CLICKABLE);lv_obj_add_flag(w.unit,LV_OBJ_FLAG_HIDDEN); @@ -2452,7 +2461,11 @@ inline void render_clock(Widgets &w,const Tile &t,bool large,int width,int heigh } // The dial keeps the same size with or without a card behind it. // A full-page card centres its dial; the digital time and date beside a wide dial stay off it. - int dial=std::min(height,width),cx=(w.full?(width-dial)/2:0)+dial/2,cy=height/2,outer=dial/2-1,radius=dial/2-(large?4:2); + int dial=std::min(height,width); + // A single card keeps the dial of the profile's card when it grows without the page bar (firmware 0.2.69+), so the + // calendar block beside it keeps its room; the dial stays in the card's middle. + if(!w.full && !w.wide)dial=std::min(dial,w.base_height-(tile_height(w)-height)); + int cx=(w.full?(width-dial)/2:0)+dial/2,cy=height/2,outer=dial/2-1,radius=dial/2-(large?4:2); for(int i=0;i<12;++i){ float a=i*3.14159265f/6;bool cardinal=i%3==0; if(cardinal && large){ @@ -3129,10 +3142,13 @@ inline void render_slot(size_t slot) { const lv_font_t *icon_font=watch && watch_icon_font ? watch_icon_font : (mini||graph_strip) && mini_icon_font ? mini_icon_font : w.icon_font; if(lv_obj_get_style_text_font(w.icon,LV_PART_MAIN)!=icon_font){set_font(w.icon,icon_font);lv_obj_center(w.icon);} int text_x=watch?0:(mini||graph_strip)?circle_size+(large_tile?8:6):w.title_x; - lv_obj_set_pos(w.title,text_x,watch?0:(mini||graph_strip||!large_tile)?text_y:w.title_y); + // A large card keeps the profile's places for its circle, name and state, moved down by half of what a card + // grows without the page bar, so they stay in its middle. + int lift=std::max(0,(tile_height(w)-w.base_height)/2); + lv_obj_set_pos(w.title,text_x,watch?0:(mini||graph_strip||!large_tile)?text_y:w.title_y+lift); lv_obj_set_pos(w.value,watch?0:(mini||graph_strip)?text_x:w.value_x, - watch?(large_tile?42:19):(mini||graph_strip||!large_tile)?text_y+title_height+line_gap:w.value_y); - lv_obj_set_pos(w.circle,0,(mini||graph_strip||!large_tile)?std::max(0,(header_height-circle_size)/2):12); + watch?(large_tile?42:19):(mini||graph_strip||!large_tile)?text_y+title_height+line_gap:w.value_y+lift); + lv_obj_set_pos(w.circle,0,(mini||graph_strip||!large_tile)?std::max(0,(header_height-circle_size)/2):12+lift); // Use the requested coordinates: LVGL getters still return the previous // layout until its next pass when a slot changes from watch/slider to normal. int text_room=content_w-chart_w-(graph_side?(large_tile?10:6):0)-panel_w; @@ -3455,6 +3471,25 @@ inline void render_header() { } // Inspect actual LVGL coordinates, including padding and the loaded font metrics. +// The Previous and Next bar and the page number between them (show_page binds them). +inline lv_obj_t *nav_prev=nullptr,*nav_next=nullptr,*nav_number=nullptr; +// The three rows of cards. With the Previous and Next bar on the screen they keep the profile's places. Without it +// (one page, or the Page buttons setting off, firmware 0.2.69+) they share the room down to the bottom edge of the +// screen, keeping the margin the sides have, and every card grows by a third of the bar's room. +struct Rows { int top=0, height=0, pitch=0; }; +inline Rows rows(bool bar) { + const auto &first=widgets[0]; + const int gap=widgets[2].tile?widgets[2].base_y-first.base_y-first.base_height:0; + Rows r{first.base_y,first.base_height,first.base_height+gap}; + auto *grid=first.tile?lv_obj_get_parent(first.tile):nullptr;auto *screen=grid?lv_obj_get_parent(grid):nullptr; + if(bar || !screen)return r; + const int room=lv_obj_get_height(screen)-lv_obj_get_x(first.tile)-lv_obj_get_y(grid)-first.base_y; + const int height=(room-2*gap)/3; + if(height>r.height){r.height=height;r.pitch=height+gap;} + return r; +} +inline Rows applied_rows; +inline bool applied_bar=true; inline bool check_tile_geometry() { bool ok=true; if(fill_next=area.y1 && card.y2<=area.y2 && area.y2<=screen.y2-margin && (!w.full || card.y2==area.y2); + if(applied_bar && nav_next){lv_area_t nav;lv_obj_get_coords(nav_next,&nav);placed=placed && area.y2=screen.y2-margin-3; + if(!placed){fits=false;ESP_LOGE("ui_test","Card place FAIL slot=%u card=%d..%d area=%d..%d screen_bottom=%d bar=%d",(unsigned)w.index,card.y1,card.y2,area.y1,area.y2,screen.y2,applied_bar);} } if(!custom && w.full){ // Everything inside the card, the name above the state, a slider or the controls below them. @@ -3570,30 +3611,40 @@ inline unsigned page_count() { // in the very next frame. Every following LVGL refresh draws the next two cards, so the loop, and // the touch polling in it, runs between the steps, and a new swipe drops a fill still under way. // Keepalives and re-packing on the same page draw at once. Nothing is allocated. -inline lv_obj_t *nav_prev=nullptr,*nav_next=nullptr,*nav_number=nullptr; inline int applied_page=-1; -// Slot assignment plus card widths and visibility for a page; contents are untouched. +// Slot assignment plus card places, sizes and visibility for a page; contents are untouched. inline int place_page(int page) { swipe_profile::Lap lap; std::array placement; int pages=place(model,placement); page=std::clamp(page,0,pages-1); + const bool bar=pages>1 && page_buttons; + const Rows r=rows(bar); + applied_rows=r;applied_bar=bar; int wide_width=widgets[0].tile && widgets[1].tile ? lv_obj_get_x(widgets[1].tile)-lv_obj_get_x(widgets[0].tile)+widgets[0].base_width : 2*widgets[0].base_width; // A full card (firmware 0.2.62+) reaches from the first row to the end of the third. - int full_height=widgets[0].tile && widgets[4].tile ? lv_obj_get_y(widgets[4].tile)-lv_obj_get_y(widgets[0].tile)+widgets[0].base_height : 3*widgets[0].base_height; + int full_height=2*r.pitch+r.height; + // The tile area ends under the third row; a card outside it would be cut off. + if(auto *grid=widgets[0].tile?lv_obj_get_parent(widgets[0].tile):nullptr; grid && lv_obj_get_style_height(grid,LV_PART_MAIN)!=r.top+full_height) + lv_obj_set_height(grid,r.top+full_height); for(size_t slot=0;slot(slot/2)*r.pitch; + if(lv_obj_get_style_y(w.tile,LV_PART_MAIN)!=y)lv_obj_set_y(w.tile,y); + lv_obj_set_size(w.tile,w.wide?wide_width:w.base_width,w.full?full_height:r.height);lv_obj_remove_flag(w.tile,LV_OBJ_FLAG_HIDDEN); + } else{lv_obj_add_flag(w.tile,LV_OBJ_FLAG_HIDDEN);hide_extra(w);hide_panel(w);} } - for(auto *control:{nav_prev,nav_next,nav_number})set_hidden(control,pages<=1); + for(auto *control:{nav_prev,nav_next,nav_number})set_hidden(control,!bar); if(page==0)lv_obj_add_state(nav_prev,LV_STATE_DISABLED);else lv_obj_remove_state(nav_prev,LV_STATE_DISABLED); if(page==pages-1)lv_obj_add_state(nav_next,LV_STATE_DISABLED);else lv_obj_remove_state(nav_next,LV_STATE_DISABLED); for(auto *control:{nav_prev,nav_next})if(lv_obj_get_child_count(control)) set_number(lv_obj_get_child(control,0),LV_STYLE_TEXT_OPA,lv_obj_has_state(control,LV_STATE_DISABLED)?LV_OPA_30:LV_OPA_COVER); - label(nav_number,std::to_string(page+1)+" / "+std::to_string(pages)); + // The dots between the two chevrons (firmware 0.2.69+): the page on screen in ink. + if(bar && nav_number)settings_screen::page_dots(nav_number,page,pages,widgets[0].base_height>80); lap(swipe_profile::PLACE); return page; } @@ -3688,6 +3739,11 @@ inline void go_to_page(int page) { *shown_page=std::clamp(page,0,int(page_count())-1); show_page(*shown_page,nav_prev,nav_next,nav_number); } +// The Page buttons setting changed (firmware 0.2.69+): the same page again, with the bar and the cards in their new places. +inline void page_buttons_changed() { + if(!shown_page || !nav_number || applied_page<0)return; + if(applied_bar!=(page_count()>1 && page_buttons))apply_page(applied_page); +} #ifdef SWIPE_PROFILE inline unsigned swipe_test_left=0, swipe_test_back=0; @@ -3806,6 +3862,7 @@ inline void tick() { // open card painted in code is drawn again here, in the same pass, so no frame shows half of each look. inline void restyle() { for (auto &w : widgets) { w.cached_active = -1; w.panel_dirty = true; } + if (nav_number && applied_bar && applied_page >= 0) settings_screen::page_dots(nav_number, applied_page, page_count(), widgets[0].base_height > 80); for (auto &slot : header_slots) { slot.own = true; slot.icon_color = UINT32_MAX; } if (room_label) { dirty_all = true; render(room_label); } if (detail_root && !lv_obj_has_flag(detail_root, LV_OBJ_FLAG_HIDDEN) && detail_index < model.count) show_detail(detail_index); diff --git a/components/smart_display/settings_screen.h b/components/smart_display/settings_screen.h index a484271..ed904ad 100644 --- a/components/smart_display/settings_screen.h +++ b/components/smart_display/settings_screen.h @@ -21,8 +21,9 @@ namespace settings_screen { // Values that are not part of the persisted `screen_settings::Settings` block, because that format is // frozen at version 1: each of these keeps its own preference, so a new option never rewrites the // old ones. The manager sends the older ones as their own keys in the layout message; dark_mode (firmware -// 0.2.54+) only exists as the screen's own setting and entity. -inline int32_t swipe_pages = 0, rotation = 0, auto_home = 1, auto_home_seconds = 120, dark_mode = 0; +// 0.2.54+) only exists as the screen's own setting and entity, like page_buttons (firmware 0.2.69+): off, the +// Previous and Next bar under the tiles goes and the tiles take its room. +inline int32_t swipe_pages = 0, rotation = 0, auto_home = 1, auto_home_seconds = 120, dark_mode = 0, page_buttons = 1; inline bool rotation_supported = false; // ------------------------------------------------------------------ the table @@ -103,7 +104,7 @@ inline SetResult set(const std::string &key, int32_t value) { auto &s = screen_settings::current; const auto before = s; const int32_t before_swipe = swipe_pages, before_rotation = rotation, before_home = auto_home, - before_home_seconds = auto_home_seconds, before_dark = dark_mode; + before_home_seconds = auto_home_seconds, before_dark = dark_mode, before_buttons = page_buttons; int32_t reported = 0; auto flag = [](int32_t v) -> int32_t { return v ? 1 : 0; }; if (key == "standby_enabled") reported = s.standby_enabled = flag(value); @@ -124,9 +125,10 @@ inline SetResult set(const std::string &key, int32_t value) { else if (key == "auto_home") reported = auto_home = flag(value); else if (key == "auto_home_seconds") reported = auto_home_seconds = std::clamp(value, 30, 3600); else if (key == "dark_mode") reported = dark_mode = flag(value); + else if (key == "page_buttons") reported = page_buttons = flag(value); else return SetResult::unknown; if (s == before && swipe_pages == before_swipe && rotation == before_rotation && auto_home == before_home && - auto_home_seconds == before_home_seconds && dark_mode == before_dark) + auto_home_seconds == before_home_seconds && dark_mode == before_dark && page_buttons == before_buttons) return SetResult::same; changed(key.c_str(), reported); return SetResult::changed; @@ -258,7 +260,8 @@ inline constexpr Row night_rows[] = { [] { return screen_settings::current.night_enabled != 0; }), }; -// How the screen behaves under your finger: the clock, going back to the first page, swiping, turning. +// How the screen behaves under your finger: the clock, going back to the first page, swiping, the page buttons, +// turning. inline constexpr Row screen_rows[] = { choice("Clock", []() -> int32_t { return screen_settings::current.clock_24h ? 1 : 0; }, [](int32_t value) { set("clock_24h", value); }, clock_options, 2), @@ -271,6 +274,8 @@ inline constexpr Row screen_rows[] = { [](int32_t value) { set("home_on_standby", value); }), toggle("Swipe between pages", []() -> int32_t { return swipe_pages; }, [](int32_t value) { set("swipe_pages", value); }), + toggle("Page buttons", []() -> int32_t { return page_buttons; }, + [](int32_t value) { set("page_buttons", value); }), choice("Rotation", []() -> int32_t { return rotation / 90; }, [](int32_t value) { set("rotation", std::clamp(value, 0, 3) * 90); }, rotation_options, 4, [] { return rotation_supported; }), @@ -360,6 +365,29 @@ inline Metrics metrics() { large ? 62 : 40, large ? 34 : 22}; } +// The page dots of a pager (firmware 0.2.69+), under the tiles and on this page: one per page, centred in `row`, the +// page on screen in ink and the others a quiet grey. The dots are made once and reused; `row` takes no touches. +inline void page_dots(lv_obj_t *row, int current, int total, bool large) { + const int dot = large ? 8 : 6, gap = large ? 10 : 7; + total = std::clamp(total, 0, 8); + while ((int) lv_obj_get_child_count(row) < total) { + auto *d = lv_obj_create(row); + lv_obj_remove_style_all(d); + lv_obj_remove_flag(d, static_cast(LV_OBJ_FLAG_SCROLLABLE | LV_OBJ_FLAG_CLICKABLE)); + lv_obj_set_style_radius(d, LV_RADIUS_CIRCLE, 0); + } + const int width = total * dot + std::max(0, total - 1) * gap; + for (int i = 0; i < (int) lv_obj_get_child_count(row); ++i) { + auto *d = lv_obj_get_child(row, i); + if (i >= total) { lv_obj_add_flag(d, LV_OBJ_FLAG_HIDDEN); continue; } + lv_obj_remove_flag(d, LV_OBJ_FLAG_HIDDEN); + lv_obj_set_size(d, dot, dot); + lv_obj_align(d, LV_ALIGN_CENTER, i * (dot + gap) + dot / 2 - width / 2, 0); + lv_obj_set_style_bg_color(d, theme::color(i == current ? theme::INK : theme::SUBTLE), 0); + lv_obj_set_style_bg_opa(d, i == current ? LV_OPA_COVER : LV_OPA_40, 0); + } +} + inline lv_obj_t *plain(lv_obj_t *parent, int x, int y, int w, int h) { auto *obj = lv_obj_create(parent); lv_obj_remove_style_all(obj); @@ -647,32 +675,34 @@ inline void draw() { refresh(); if (!paged) return; - // The same pager as the tile pages, in the same place, so it is the one that is already learned. + // The same pager as the tile pages, in the same place, so it is the one that is already learned: a chevron in each + // half, the page dots between them (firmware 0.2.69+). A half lights up under a finger; one that leads nowhere is + // dimmed and takes no touches, so nothing moves from page to page. uint8_t page_number = (uint8_t) (first_row / per_page + 1); uint8_t page_total = (uint8_t) ((count + per_page - 1) / per_page); int y = m.height - m.pager - m.bottom / 2, half = (m.width - 2 * m.pad) / 2 - 20; + const lv_font_t *chevrons = icon_font ? icon_font : row_font; + int chevron_h = lv_font_get_line_height(chevrons); + auto *dots = plain(root, 0, y, m.width, m.pager); + page_dots(dots, page_number - 1, page_total, m.large); for (int side = 0; side < 2; ++side) { bool enabled = side ? page_number < page_total : page_number > 1; - if (!enabled) continue; auto *bar = plain(root, side ? m.width - m.pad - half : m.pad, y, half, m.pager); + auto *glyph = text(bar, side ? "\U000F0142" : "\U000F0141", chevrons, theme::INK, + side ? LV_TEXT_ALIGN_RIGHT : LV_TEXT_ALIGN_LEFT); + lv_obj_set_width(glyph, half - 12); + lv_obj_set_pos(glyph, 6, (m.pager - chevron_h) / 2); + if (!enabled) { lv_obj_set_style_opa(glyph, LV_OPA_30, 0); continue; } lv_obj_add_flag(bar, LV_OBJ_FLAG_CLICKABLE); lv_obj_set_style_bg_opa(bar, LV_OPA_TRANSP, 0); lv_obj_set_style_bg_opa(bar, LV_OPA_COVER, LV_STATE_PRESSED); lv_obj_set_style_bg_color(bar, theme::color(theme::KEY_PRESSED), LV_STATE_PRESSED); lv_obj_set_style_radius(bar, m.radius, 0); - auto *label = text(bar, side ? "Next >" : "< Previous", row_font, theme::INK, - side ? LV_TEXT_ALIGN_RIGHT : LV_TEXT_ALIGN_LEFT); - lv_obj_set_width(label, half - 12); - lv_obj_set_pos(label, 6, (m.pager - label_h) / 2); // CLICKED, not SHORT_CLICKED: LVGL sends no short click after a press of long_press_time (400 ms), so a firm or // slow press did nothing (firmware 0.2.65+). The bar has no hold of its own. lv_obj_add_event_cb(bar, pager_event, LV_EVENT_CLICKED, (void *) (intptr_t) (side ? per_page : -per_page)); } - auto *counter = text(root, std::to_string(page_number) + " / " + std::to_string(page_total), row_font, theme::MUTED, - LV_TEXT_ALIGN_CENTER); - lv_obj_set_width(counter, m.width); - lv_obj_set_pos(counter, 0, y + (m.pager - label_h) / 2); } inline void hide_hold_bar() { diff --git a/docs/EASY_SETUP.md b/docs/EASY_SETUP.md index b2f0154..752e0b5 100644 --- a/docs/EASY_SETUP.md +++ b/docs/EASY_SETUP.md @@ -175,7 +175,12 @@ middle of the screen does nothing, so tapping and dragging tiles never accidentally changes pages. On the CYD, it stays a quick swipe across the screen. Sliders only control their value; detail menus and standby don't change pages. -Six tiles sit on a page. With six or fewer, Previous/Next disappear. +Six tiles sit on a page. Under the tiles, the page buttons: a chevron in each half of the +bar and a dot per page between them; tap anywhere in the left or right half. With six or +fewer tiles they disappear and the tiles grow into their room (firmware 0.2.69+). **Page +buttons** in the **Screen settings** tab takes them away on a screen with more pages too: +then only swiping and *Go to page* tiles change the page, and the editor says which pages +that leaves out of reach. The default standby time is ten minutes. For offline devices, the screen blocks actions. If the connection to Home Assistant drops, the screen immediately shows "HA not connected"; if the app sends nothing for two rounds (about five minutes), @@ -283,6 +288,7 @@ After this, changes to these settings don't need a new firmware flash. | Time format | 24 or 12 hour, without AM/PM | 24 hour | | Return to page 1 | Also closes detail menus on standby | Off | | Swipe between pages | Native horizontal swipe, firmware 0.2.7+ | Off | +| Page buttons | Off: no buttons under the tiles, the tiles take their room, firmware 0.2.69+ | On | | Guition rotation | 0°, 90°, 180°, 270°, firmware 0.2.9+ | 0° | Home Assistant shows the same settings on each screen's ESPHome device: the switch diff --git a/docs/RELEASING.md b/docs/RELEASING.md index ba70dcd..00d2440 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -215,6 +215,29 @@ icons sit off-center in the browser); run `generate_packages.py` afterward. The `MDI_GLYPH_*` substitutions are retired: `TILEn_ICON` in manual profiles must come from the set. No changed preferences or keys. +### Compatibility 0.2.82 / firmware 0.2.69 + +Page buttons (GitHub issue #9): a setting the screen owns, `page_buttons`, on by default. It has a preference record +of its own (`0x50474231`), the switch "Page buttons" in both profiles, and `SETTING_RULES`, `SETTING_ENTITIES` and +`SETTINGS_BESIDE_BLOCK` entries in the app. Like Dark mode it never travels in the layout message, and a screen +without the entity shows no row for it. + +- Firmware: `place_page` places the three rows itself (`runtime_tiles::rows`). With the bar on screen (more than one + page and `page_buttons` on) they keep the profile's places (`Widgets::base_y`, `base_height`). Without it they share + the room down to the bottom edge, keeping the side margin: Guition 108 → 122 px, CYD 52 → 61 px. `tile_scroll` + grows with them. A large card keeps its profile places for the circle, name and state, lowered by half the growth; + the single analog clock keeps the profile's dial. `page_buttons_changed()` (called from `apply_screen_settings`) + places the page again when the bar comes or goes. `check_tile_geometry` checks that every card lies inside the tile + area, clear of the bar and within the bottom margin. +- Page bar: `page_prev` and `page_next` are the two halves of the band under the tiles, running from + `SCROLL_Y + SCROLL_H` to the bottom (Guition 240×60, CYD 160×36). Each holds a chevron in `materialdesign_icons_mini` + (F0141/F0142). `page_number` is a non-clickable `obj` that holds the dots (`settings_screen::page_dots`), so LVGL's + hit test gives every point of the band to one of the halves. The settings page's pager uses the same chevrons and + dots, with a dimmed chevron where it leads nowhere. +- Editor: `strandedPages()` (`model/layout.ts`) and `pageReachWarning()` (`store.ts`) warn when page buttons and + swiping are both off and the Go to page tiles leave a page out of reach, or give it no way back to page 1. The + warning shows above the pages and under the Screen card. + ### Compatibility 0.2.78 / firmware 0.2.65 Repeated navigation tiles: the same `screen.page_` may sit on several pages of one screen. Firmware 0.2.65 accepts diff --git a/docs/SETTINGS.md b/docs/SETTINGS.md index 1127ab9..08c8db0 100644 --- a/docs/SETTINGS.md +++ b/docs/SETTINGS.md @@ -27,6 +27,7 @@ changes them with the entity's own action, and leaves them out of the layout mes | Back to page 1, After | `switch._back_to_page_1`, `number._back_to_page_1_after` | `auto_home`, `auto_home_seconds` | | Also on standby | `switch._back_to_page_1_on_standby` | `home_on_standby` | | Swipe between pages | `switch._swipe_between_pages` | `swipe_pages` | +| Page buttons (0.2.69+) | `switch._page_buttons` | `page_buttons` | | Rotation (Guition) | `select._rotation` | `rotation` | The first four entities and Auto standby existed before 0.2.49; ESP Screens recognizes a screen that owns @@ -52,7 +53,7 @@ The page is a menu of groups, each of which opens a page of its own: |---|---| | Brightness | Brightness, Dark mode, Auto standby, Standby after, Standby brightness | | Night | Night mode, Starts, Ends, Night brightness | -| Screen | Clock, Back to page 1, After, Also on standby, Swipe between pages, Rotation (boards that turn) | +| Screen | Clock, Back to page 1, After, Also on standby, Swipe between pages, Page buttons, Rotation (boards that turn) | | This screen | Screen, Address, Firmware, Home Assistant, Restart | Every change is stored on the screen, applied at once and published on its entity, so Home Assistant and @@ -64,8 +65,9 @@ unknown and takes no changes until it is back. ## The rules the page follows - **Two levels, never three.** A group page is the deepest place a setting can live. -- **No free scrolling.** A group that does not fit gets the same `< Previous / Next >` pager as the tile - pages. A 320x240 board shows five rows, a 480x480 board six. +- **No free scrolling.** A group that does not fit gets the same pager as the tile pages: a chevron in each + half of the bar and a dot per page between them (firmware 0.2.69+). A 320x240 board shows five rows, a + 480x480 board six; with the pager four and five. - **A row is a control, not a form.** Toggles flip on tap, numbers and times have `-` and `+`, a choice cycles through its options in a chip on the right. - **A row that depends on a switch above it is greyed out, not hidden**, so the page never jumps around. diff --git a/docs/TEST_RESULTS_0282.md b/docs/TEST_RESULTS_0282.md new file mode 100644 index 0000000..a400d77 --- /dev/null +++ b/docs/TEST_RESULTS_0282.md @@ -0,0 +1,50 @@ +# Test results app 0.2.82 / firmware 0.2.69 (2026-09-18) + +GitHub issue #9 (lokitol): "when using the next page tiles, is it possible to remove the bottom of the page +next/previous buttons and extend the size of the tiles to utilise all the screen?" Max chose a screen setting with a +warning in the editor for pages that can't be reached. During the round he also asked for new page buttons: only MDI +icons in the corners, no words. He picked proposal B from two renders: bare chevrons and page dots. He kept the whole +half of the bar as the key, lit up under the finger, with no round shadow, and asked for the same buttons on the +screen's settings page. He approved the final renders before release ("ik ben blij, wat mij betreft releasen"). + +## What changed + +- Setting `page_buttons` (default on): preference record `0x50474231`, `settings_screen::set()`, the row "Page + buttons" in the Screen group, switch "Page buttons" in both profiles, `SETTING_RULES`/`SETTING_ENTITIES`/ + `SETTINGS_BESIDE_BLOCK` in the app, a row in the editor and in the Claude skill's entity table. +- `runtime_tiles::place_page` places the rows (`rows()`): the profile's rows with the bar, the whole room down to the + bottom margin without it (Guition 108 → 122 px, CYD 52 → 61 px). Large cards lower their YAML places by half the + growth; the single analog clock keeps the profile's dial. +- Page keys: each half of the band under the tiles (Guition 240×60, CYD 160×36) with a chevron; `page_number` holds + the dots (`settings_screen::page_dots`); the settings page's pager draws the same. +- Editor: `strandedPages()` + `pageReachWarning()`, shown above the pages and under the Screen card. + +## Automated + +- `tools/check.sh`: Python 455 tests, C++ 18/18 (`test_settings_screen.cpp` covers the row and `set("page_buttons")`), + packages, icons, Vitest 81/81 (`strandedPages`, the warning's words and when it stays quiet), vue-tsc, build. +- `tools/check.sh --firmware` (ESPHome 2026.6.2): CYD 1,655,136 B = 90.2 % of the 1,835,008 B slot, +1,632 B against + 0.2.81 (tight band: the delta is stated), Guition 2,029,088 B = 25.0 % of its 8,126,464 B slot. + +## On the host (the real firmware for the Mac, SDL, own API ports 6191/6192) + +- Renders of every card kind, the README showcase layout, the eight full-page cards and a one-page layout, each with the + page buttons on and off, on both boards; the settings page's Screen group (two pages now) and dark mode (switched + while a page is shown, and after a page change). Max checked them all before release. +- `ui_self_test` with the bar on and off, on both boards, with two layouts: every `page_check` PASS (10/10 per run) and + no `Tile geometry`, `Card place`, `Panel bounds` or `Part bounds` failure. `check_tile_geometry` now also checks that + every card lies inside the tile area, clear of the bar, and that without the bar the area reaches the bottom margin. +- LVGL's own hit test (`lv_indev_search_obj`) across the band: every point of the left half is Previous, every point + of the right half Next (the dots take no touches); just above the band a finger gets the card; without the bar the + band belongs to the cards. +- The small slider of 0.2.81 on a taller card, with a virtual finger (a second LVGL pointer): the strip claims the top + and middle of its card, a drag that starts above the strip sets the light (`light.turn_on`), the page stays, and a + still press beside the strip is the tile's tap. 16/16 on both boards with the bar on and off. +- Editor in a browser against a demo add-on whose screen owns its settings: the warning names the pages ("You have 1 + Go to page tile, to page 1, but none to pages 2, 3 and 4") and follows a new Go to page tile at once; the Page + buttons switch sends `PUT .../settings` (200) and the warning goes when it is on. + +## Not tested + +- On real hardware: the new keys under a finger (GT911 and XPT2046), the taller cards, and switching the setting from + the screen's settings page and from Home Assistant. The bench boards were not flashed in this round. diff --git a/guition-4848s040.yaml b/guition-4848s040.yaml index 6c76d4f..a260690 100644 --- a/guition-4848s040.yaml +++ b/guition-4848s040.yaml @@ -2,7 +2,7 @@ # Source profile for packages/guition.yaml (tools/generate_packages.py); see docs/GUITION.md. # A screen gets its tiles from the ESP Screen Manager add-on while it runs. The CYD is its own profile. substitutions: - SCREEN_FIRMWARE_VERSION: "0.2.68" + SCREEN_FIRMWARE_VERSION: "0.2.69" # 0: no drift limit. A press that drifts is still a tap as long as the finger lets go on the tile # (runtime_tiles::event checks where it let go: LVGL keeps the press on the tile and would click it # anywhere), and a quick flick is LVGL's gesture, never a tap (the home_page gesture handler in on_boot). @@ -1458,6 +1458,18 @@ switch: - lambda: 'settings_screen::set("swipe_pages", 1);' turn_off_action: - lambda: 'settings_screen::set("swipe_pages", 0);' + # Off, the Previous and Next bar under the tiles goes and the tiles take its room (firmware 0.2.69+). + - platform: template + id: setting_page_buttons + name: "Page buttons" + icon: "mdi:page-next-outline" + entity_category: config + restore_mode: DISABLED + lambda: return settings_screen::page_buttons != 0; + turn_on_action: + - lambda: 'settings_screen::set("page_buttons", 1);' + turn_off_action: + - lambda: 'settings_screen::set("page_buttons", 0);' # Night hours as Home Assistant times (firmware 0.2.49+); the screen keeps minutes since midnight. datetime: @@ -2486,14 +2498,16 @@ lvgl: clickable: false # ---------- VALUE SLIDER OVERLAY ---------- - # ---------- PAGE NAVIGATION: one full-width bar ---------- + # ---------- PAGE NAVIGATION: the band under the tiles ---------- + # Each half of the band is one key (firmware 0.2.69+): a chevron in its corner, lit up under a finger, and the + # page dots between them. The band reaches from the tile area's end (SCROLL_Y + SCROLL_H) to the bottom edge. - obj: id: page_prev align: BOTTOM_LEFT x: 0 y: 0 - width: 200 - height: 44 + width: 240 + height: 60 pad_all: 0 radius: 0 bg_opa: TRANSP @@ -2515,28 +2529,28 @@ lvgl: - label: align: LEFT_MID x: 24 - text: "< Previous" - text_font: label + text: "\U000F0141" # chevron-left + text_font: materialdesign_icons_mini styles: paint_ink clickable: false - - label: + - obj: id: page_number align: BOTTOM_MID y: 0 - width: 80 - height: 44 - pad_top: 12 + width: 160 + height: 60 + pad_all: 0 + bg_opa: TRANSP border_width: 0 - text: "1 / 2" - text_align: CENTER - text_font: sublabel + clickable: false + scrollable: false - obj: id: page_next align: BOTTOM_RIGHT x: 0 y: 0 - width: 200 - height: 44 + width: 240 + height: 60 pad_all: 0 radius: 0 bg_opa: TRANSP @@ -2556,8 +2570,8 @@ lvgl: - label: align: RIGHT_MID x: -24 - text: "Next >" - text_font: label + text: "\U000F0142" # chevron-right + text_font: materialdesign_icons_mini styles: paint_ink clickable: false @@ -3852,12 +3866,14 @@ script: bool visible = runtime_tiles::enabled ? i<6 && runtime_tiles::widgets[i].index= 6 : i < 6); ok = ok && (lv_obj_has_flag(tiles[i], LV_OBJ_FLAG_HIDDEN) != visible); } + // The bar shows with more than one page, unless the Page buttons setting is off (firmware 0.2.69+). + const bool bar = paginated && (!runtime_tiles::enabled || settings_screen::page_buttons); for (lv_obj_t* control : {id(page_prev), id(page_next), id(page_number)}) - ok = ok && (lv_obj_has_flag(control, LV_OBJ_FLAG_HIDDEN) != paginated); + ok = ok && (lv_obj_has_flag(control, LV_OBJ_FLAG_HIDDEN) != bar); lv_area_t nav, grid; lv_obj_get_coords(id(page_next), &nav); lv_obj_get_coords(id(tile_scroll), &grid); - ok = ok && (!paginated || grid.y2 < nav.y1); + ok = ok && (!bar || grid.y2 < nav.y1); if(runtime_tiles::enabled)ok = runtime_tiles::check_tile_geometry() && ok; ESP_LOGI("ui_test", "page_check=%s count=%d heap=%u frames=%u grid_bottom=%d nav_top=%d", ok ? "PASS" : "FAIL", count, (unsigned) esp_get_free_heap_size(), (unsigned) id(rendered_frames), grid.y2, nav.y1); id(tile_page) = 0; @@ -3911,7 +3927,7 @@ script: lv_obj_add_flag(tiles[i], LV_OBJ_FLAG_HIDDEN); } } - lv_label_set_text(id(page_number), id(tile_page) == 0 ? "1 / 2" : "2 / 2"); + settings_screen::page_dots(id(page_number), id(tile_page), 2, true); if (id(tile_page) == 0) { lv_obj_add_state(id(page_prev), LV_STATE_DISABLED); lv_obj_clear_state(id(page_next), LV_STATE_DISABLED); @@ -3960,6 +3976,8 @@ script: } const auto shown_rotation = id(setting_rotation)->active_index(); if (!id(setting_rotation)->has_state() || !shown_rotation.has_value() || *shown_rotation != (size_t) (settings_screen::rotation / 90)) id(setting_rotation)->update(); + // The Page buttons setting moves the cards into the bar's room or back (firmware 0.2.69+). + if (runtime_tiles::enabled) runtime_tiles::page_buttons_changed(); settings_screen::refresh(); if (!id(ui_refresh).is_running()) id(ui_refresh).execute(); diff --git a/home-like-2432s028.yaml b/home-like-2432s028.yaml index 49ae77d..0afde43 100644 --- a/home-like-2432s028.yaml +++ b/home-like-2432s028.yaml @@ -17,7 +17,7 @@ ############################################################ substitutions: - SCREEN_FIRMWARE_VERSION: "0.2.68" + SCREEN_FIRMWARE_VERSION: "0.2.69" # A tap survives a finger that drifts up to this far (320 px over 58 mm: 1 cm); further is a swipe. TOUCH_MOVE_LIMIT_PX: "56" # Shortest contact that counts as a tap; a resistive panel bounces, a capacitive one does not. @@ -1469,6 +1469,18 @@ switch: - lambda: 'settings_screen::set("swipe_pages", 1);' turn_off_action: - lambda: 'settings_screen::set("swipe_pages", 0);' + # Off, the Previous and Next bar under the tiles goes and the tiles take its room (firmware 0.2.69+). + - platform: template + id: setting_page_buttons + name: "Page buttons" + icon: "mdi:page-next-outline" + entity_category: config + restore_mode: DISABLED + lambda: return settings_screen::page_buttons != 0; + turn_on_action: + - lambda: 'settings_screen::set("page_buttons", 1);' + turn_off_action: + - lambda: 'settings_screen::set("page_buttons", 0);' # Night hours as Home Assistant times (firmware 0.2.49+); the screen keeps minutes since midnight. datetime: @@ -2386,14 +2398,16 @@ lvgl: clickable: false # ---------- VALUE SLIDER OVERLAY ---------- - # ---------- PAGE NAVIGATION: one full-width bar ---------- + # ---------- PAGE NAVIGATION: the band under the tiles ---------- + # Each half of the band is one key (firmware 0.2.69+): a chevron in its corner, lit up under a finger, and the + # page dots between them. The band reaches from the tile area's end (SCROLL_Y + SCROLL_H) to the bottom edge. - obj: id: page_prev align: BOTTOM_LEFT x: 0 y: 0 - width: 130 - height: 32 + width: 160 + height: 36 pad_all: 0 radius: 0 bg_opa: TRANSP @@ -2414,29 +2428,29 @@ lvgl: widgets: - label: align: LEFT_MID - x: 12 - text: "< Previous" - text_font: label + x: 14 + text: "\U000F0141" # chevron-left + text_font: materialdesign_icons_mini styles: paint_ink clickable: false - - label: + - obj: id: page_number align: BOTTOM_MID y: 0 - width: 60 - height: 32 - pad_top: 9 + width: 104 + height: 36 + pad_all: 0 + bg_opa: TRANSP border_width: 0 - text: "1 / 2" - text_align: CENTER - text_font: sublabel + clickable: false + scrollable: false - obj: id: page_next align: BOTTOM_RIGHT x: 0 y: 0 - width: 130 - height: 32 + width: 160 + height: 36 pad_all: 0 radius: 0 bg_opa: TRANSP @@ -2455,9 +2469,9 @@ lvgl: widgets: - label: align: RIGHT_MID - x: -12 - text: "Next >" - text_font: label + x: -14 + text: "\U000F0142" # chevron-right + text_font: materialdesign_icons_mini styles: paint_ink clickable: false @@ -3728,12 +3742,14 @@ script: bool visible = runtime_tiles::enabled ? i<6 && runtime_tiles::widgets[i].index= 6 : i < 6); ok = ok && (lv_obj_has_flag(tiles[i], LV_OBJ_FLAG_HIDDEN) != visible); } + // The bar shows with more than one page, unless the Page buttons setting is off (firmware 0.2.69+). + const bool bar = paginated && (!runtime_tiles::enabled || settings_screen::page_buttons); for (lv_obj_t* control : {id(page_prev), id(page_next), id(page_number)}) - ok = ok && (lv_obj_has_flag(control, LV_OBJ_FLAG_HIDDEN) != paginated); + ok = ok && (lv_obj_has_flag(control, LV_OBJ_FLAG_HIDDEN) != bar); lv_area_t nav, grid; lv_obj_get_coords(id(page_next), &nav); lv_obj_get_coords(id(tile_scroll), &grid); - ok = ok && (!paginated || grid.y2 < nav.y1); + ok = ok && (!bar || grid.y2 < nav.y1); if(runtime_tiles::enabled)ok = runtime_tiles::check_tile_geometry() && ok; ESP_LOGI("ui_test", "page_check=%s count=%d heap=%u frames=%u grid_bottom=%d nav_top=%d", ok ? "PASS" : "FAIL", count, (unsigned) esp_get_free_heap_size(), (unsigned) id(rendered_frames), grid.y2, nav.y1); id(tile_page) = 0; @@ -3783,7 +3799,7 @@ script: lv_obj_add_flag(tiles[i], LV_OBJ_FLAG_HIDDEN); } } - lv_label_set_text(id(page_number), id(tile_page) == 0 ? "1 / 2" : "2 / 2"); + settings_screen::page_dots(id(page_number), id(tile_page), 2, false); if (id(tile_page) == 0) { lv_obj_add_state(id(page_prev), LV_STATE_DISABLED); lv_obj_clear_state(id(page_next), LV_STATE_DISABLED); @@ -3824,6 +3840,8 @@ script: for (auto [entity, minutes] : {std::make_pair(id(setting_night_start), settings.night_start), std::make_pair(id(setting_night_end), settings.night_end)}) { if (!entity->has_state() || entity->hour * 60 + entity->minute != minutes) entity->update(); } + // The Page buttons setting moves the cards into the bar's room or back (firmware 0.2.69+). + if (runtime_tiles::enabled) runtime_tiles::page_buttons_changed(); settings_screen::refresh(); if (!id(ui_refresh).is_running()) id(ui_refresh).execute(); diff --git a/packages/cyd.yaml b/packages/cyd.yaml index acaf17b..f7b39ad 100644 --- a/packages/cyd.yaml +++ b/packages/cyd.yaml @@ -18,7 +18,7 @@ ############################################################ substitutions: - SCREEN_FIRMWARE_VERSION: "0.2.68" + SCREEN_FIRMWARE_VERSION: "0.2.69" # A tap survives a finger that drifts up to this far (320 px over 58 mm: 1 cm); further is a swipe. TOUCH_MOVE_LIMIT_PX: "56" # Shortest contact that counts as a tap; a resistive panel bounces, a capacitive one does not. @@ -1458,6 +1458,18 @@ switch: - lambda: 'settings_screen::set("swipe_pages", 1);' turn_off_action: - lambda: 'settings_screen::set("swipe_pages", 0);' + # Off, the Previous and Next bar under the tiles goes and the tiles take its room (firmware 0.2.69+). + - platform: template + id: setting_page_buttons + name: "Page buttons" + icon: "mdi:page-next-outline" + entity_category: config + restore_mode: DISABLED + lambda: return settings_screen::page_buttons != 0; + turn_on_action: + - lambda: 'settings_screen::set("page_buttons", 1);' + turn_off_action: + - lambda: 'settings_screen::set("page_buttons", 0);' # Night hours as Home Assistant times (firmware 0.2.49+); the screen keeps minutes since midnight. datetime: @@ -2375,14 +2387,16 @@ lvgl: clickable: false # ---------- VALUE SLIDER OVERLAY ---------- - # ---------- PAGE NAVIGATION: one full-width bar ---------- + # ---------- PAGE NAVIGATION: the band under the tiles ---------- + # Each half of the band is one key (firmware 0.2.69+): a chevron in its corner, lit up under a finger, and the + # page dots between them. The band reaches from the tile area's end (SCROLL_Y + SCROLL_H) to the bottom edge. - obj: id: page_prev align: BOTTOM_LEFT x: 0 y: 0 - width: 130 - height: 32 + width: 160 + height: 36 pad_all: 0 radius: 0 bg_opa: TRANSP @@ -2403,29 +2417,29 @@ lvgl: widgets: - label: align: LEFT_MID - x: 12 - text: "< Previous" - text_font: label + x: 14 + text: "\U000F0141" # chevron-left + text_font: materialdesign_icons_mini styles: paint_ink clickable: false - - label: + - obj: id: page_number align: BOTTOM_MID y: 0 - width: 60 - height: 32 - pad_top: 9 + width: 104 + height: 36 + pad_all: 0 + bg_opa: TRANSP border_width: 0 - text: "1 / 2" - text_align: CENTER - text_font: sublabel + clickable: false + scrollable: false - obj: id: page_next align: BOTTOM_RIGHT x: 0 y: 0 - width: 130 - height: 32 + width: 160 + height: 36 pad_all: 0 radius: 0 bg_opa: TRANSP @@ -2444,9 +2458,9 @@ lvgl: widgets: - label: align: RIGHT_MID - x: -12 - text: "Next >" - text_font: label + x: -14 + text: "\U000F0142" # chevron-right + text_font: materialdesign_icons_mini styles: paint_ink clickable: false @@ -3717,12 +3731,14 @@ script: bool visible = runtime_tiles::enabled ? i<6 && runtime_tiles::widgets[i].index= 6 : i < 6); ok = ok && (lv_obj_has_flag(tiles[i], LV_OBJ_FLAG_HIDDEN) != visible); } + // The bar shows with more than one page, unless the Page buttons setting is off (firmware 0.2.69+). + const bool bar = paginated && (!runtime_tiles::enabled || settings_screen::page_buttons); for (lv_obj_t* control : {id(page_prev), id(page_next), id(page_number)}) - ok = ok && (lv_obj_has_flag(control, LV_OBJ_FLAG_HIDDEN) != paginated); + ok = ok && (lv_obj_has_flag(control, LV_OBJ_FLAG_HIDDEN) != bar); lv_area_t nav, grid; lv_obj_get_coords(id(page_next), &nav); lv_obj_get_coords(id(tile_scroll), &grid); - ok = ok && (!paginated || grid.y2 < nav.y1); + ok = ok && (!bar || grid.y2 < nav.y1); if(runtime_tiles::enabled)ok = runtime_tiles::check_tile_geometry() && ok; ESP_LOGI("ui_test", "page_check=%s count=%d heap=%u frames=%u grid_bottom=%d nav_top=%d", ok ? "PASS" : "FAIL", count, (unsigned) esp_get_free_heap_size(), (unsigned) id(rendered_frames), grid.y2, nav.y1); id(tile_page) = 0; @@ -3772,7 +3788,7 @@ script: lv_obj_add_flag(tiles[i], LV_OBJ_FLAG_HIDDEN); } } - lv_label_set_text(id(page_number), id(tile_page) == 0 ? "1 / 2" : "2 / 2"); + settings_screen::page_dots(id(page_number), id(tile_page), 2, false); if (id(tile_page) == 0) { lv_obj_add_state(id(page_prev), LV_STATE_DISABLED); lv_obj_clear_state(id(page_next), LV_STATE_DISABLED); @@ -3813,6 +3829,8 @@ script: for (auto [entity, minutes] : {std::make_pair(id(setting_night_start), settings.night_start), std::make_pair(id(setting_night_end), settings.night_end)}) { if (!entity->has_state() || entity->hour * 60 + entity->minute != minutes) entity->update(); } + // The Page buttons setting moves the cards into the bar's room or back (firmware 0.2.69+). + if (runtime_tiles::enabled) runtime_tiles::page_buttons_changed(); settings_screen::refresh(); if (!id(ui_refresh).is_running()) id(ui_refresh).execute(); diff --git a/packages/guition.yaml b/packages/guition.yaml index 2916b78..43e61c0 100644 --- a/packages/guition.yaml +++ b/packages/guition.yaml @@ -3,7 +3,7 @@ # Source profile for packages/guition.yaml (tools/generate_packages.py); see docs/GUITION.md. # A screen gets its tiles from the ESP Screen Manager add-on while it runs. The CYD is its own profile. substitutions: - SCREEN_FIRMWARE_VERSION: "0.2.68" + SCREEN_FIRMWARE_VERSION: "0.2.69" # 0: no drift limit. A press that drifts is still a tap as long as the finger lets go on the tile # (runtime_tiles::event checks where it let go: LVGL keeps the press on the tile and would click it # anywhere), and a quick flick is LVGL's gesture, never a tap (the home_page gesture handler in on_boot). @@ -1447,6 +1447,18 @@ switch: - lambda: 'settings_screen::set("swipe_pages", 1);' turn_off_action: - lambda: 'settings_screen::set("swipe_pages", 0);' + # Off, the Previous and Next bar under the tiles goes and the tiles take its room (firmware 0.2.69+). + - platform: template + id: setting_page_buttons + name: "Page buttons" + icon: "mdi:page-next-outline" + entity_category: config + restore_mode: DISABLED + lambda: return settings_screen::page_buttons != 0; + turn_on_action: + - lambda: 'settings_screen::set("page_buttons", 1);' + turn_off_action: + - lambda: 'settings_screen::set("page_buttons", 0);' # Night hours as Home Assistant times (firmware 0.2.49+); the screen keeps minutes since midnight. datetime: @@ -2475,14 +2487,16 @@ lvgl: clickable: false # ---------- VALUE SLIDER OVERLAY ---------- - # ---------- PAGE NAVIGATION: one full-width bar ---------- + # ---------- PAGE NAVIGATION: the band under the tiles ---------- + # Each half of the band is one key (firmware 0.2.69+): a chevron in its corner, lit up under a finger, and the + # page dots between them. The band reaches from the tile area's end (SCROLL_Y + SCROLL_H) to the bottom edge. - obj: id: page_prev align: BOTTOM_LEFT x: 0 y: 0 - width: 200 - height: 44 + width: 240 + height: 60 pad_all: 0 radius: 0 bg_opa: TRANSP @@ -2504,28 +2518,28 @@ lvgl: - label: align: LEFT_MID x: 24 - text: "< Previous" - text_font: label + text: "\U000F0141" # chevron-left + text_font: materialdesign_icons_mini styles: paint_ink clickable: false - - label: + - obj: id: page_number align: BOTTOM_MID y: 0 - width: 80 - height: 44 - pad_top: 12 + width: 160 + height: 60 + pad_all: 0 + bg_opa: TRANSP border_width: 0 - text: "1 / 2" - text_align: CENTER - text_font: sublabel + clickable: false + scrollable: false - obj: id: page_next align: BOTTOM_RIGHT x: 0 y: 0 - width: 200 - height: 44 + width: 240 + height: 60 pad_all: 0 radius: 0 bg_opa: TRANSP @@ -2545,8 +2559,8 @@ lvgl: - label: align: RIGHT_MID x: -24 - text: "Next >" - text_font: label + text: "\U000F0142" # chevron-right + text_font: materialdesign_icons_mini styles: paint_ink clickable: false @@ -3841,12 +3855,14 @@ script: bool visible = runtime_tiles::enabled ? i<6 && runtime_tiles::widgets[i].index= 6 : i < 6); ok = ok && (lv_obj_has_flag(tiles[i], LV_OBJ_FLAG_HIDDEN) != visible); } + // The bar shows with more than one page, unless the Page buttons setting is off (firmware 0.2.69+). + const bool bar = paginated && (!runtime_tiles::enabled || settings_screen::page_buttons); for (lv_obj_t* control : {id(page_prev), id(page_next), id(page_number)}) - ok = ok && (lv_obj_has_flag(control, LV_OBJ_FLAG_HIDDEN) != paginated); + ok = ok && (lv_obj_has_flag(control, LV_OBJ_FLAG_HIDDEN) != bar); lv_area_t nav, grid; lv_obj_get_coords(id(page_next), &nav); lv_obj_get_coords(id(tile_scroll), &grid); - ok = ok && (!paginated || grid.y2 < nav.y1); + ok = ok && (!bar || grid.y2 < nav.y1); if(runtime_tiles::enabled)ok = runtime_tiles::check_tile_geometry() && ok; ESP_LOGI("ui_test", "page_check=%s count=%d heap=%u frames=%u grid_bottom=%d nav_top=%d", ok ? "PASS" : "FAIL", count, (unsigned) esp_get_free_heap_size(), (unsigned) id(rendered_frames), grid.y2, nav.y1); id(tile_page) = 0; @@ -3900,7 +3916,7 @@ script: lv_obj_add_flag(tiles[i], LV_OBJ_FLAG_HIDDEN); } } - lv_label_set_text(id(page_number), id(tile_page) == 0 ? "1 / 2" : "2 / 2"); + settings_screen::page_dots(id(page_number), id(tile_page), 2, true); if (id(tile_page) == 0) { lv_obj_add_state(id(page_prev), LV_STATE_DISABLED); lv_obj_clear_state(id(page_next), LV_STATE_DISABLED); @@ -3949,6 +3965,8 @@ script: } const auto shown_rotation = id(setting_rotation)->active_index(); if (!id(setting_rotation)->has_state() || !shown_rotation.has_value() || *shown_rotation != (size_t) (settings_screen::rotation / 90)) id(setting_rotation)->update(); + // The Page buttons setting moves the cards into the bar's room or back (firmware 0.2.69+). + if (runtime_tiles::enabled) runtime_tiles::page_buttons_changed(); settings_screen::refresh(); if (!id(ui_refresh).is_running()) id(ui_refresh).execute(); diff --git a/screen_manager/CHANGELOG.md b/screen_manager/CHANGELOG.md index 4ffede7..50714f2 100644 --- a/screen_manager/CHANGELOG.md +++ b/screen_manager/CHANGELOG.md @@ -1,3 +1,14 @@ +## 0.2.82 (firmware 0.2.69) + +The page buttons can make way, and the tiles fill the screen. + +- **Page buttons: a setting to take the bar under the tiles away.** Turned off, a screen with more pages has no buttons under its tiles, and the three rows of cards share their room: every card grows by 14 px on a Guition (108 to 122) and 9 px on a CYD (52 to 61). Pages then change by swiping or with *Go to page* tiles. It sits with the other screen settings: on the screen's own settings page (Screen), in the editor's *Screen settings*, and as `switch._page_buttons` in Home Assistant. On by default. (GitHub issue #9) +- **The editor says which pages you can't reach.** With the page buttons and swiping both off, only *Go to page* tiles change the page. The editor then says where your *Go to page* tiles lead and which pages that leaves out ("You have 3 Go to page tiles, to pages 1, 2 and 3, but none to page 4, so you can't reach it"), and it names a page with no way back to page 1. The warning shows above the pages and under the setting. +- **New page buttons: a chevron in each corner and a dot per page.** The words *Previous* and *Next* and the "2 / 4" are gone. Each half of the bar is still one big key and lights up under your finger. On the first and last page the chevron that leads nowhere is dimmed. The settings page on the screen uses the same buttons. +- **A screen with one page uses the whole screen.** Its bar was already hidden, but the tiles kept their size. Now they grow into that room the same way. +- A card keeps its layout as it grows: the name, state and circle stay in its middle, and the analog clock on a single tile keeps its dial size so the date beside it still fits. The small slider of 0.2.81 still claims the whole card when the card is taller. +- Needs firmware 0.2.69: press **Update** on the screen. Includes everything from 0.2.81. CYD firmware: 1,655,136 bytes, 90.2 % of the update slot (1,632 bytes more than 0.2.81). + ## 0.2.81 (firmware 0.2.68) A card's slider takes the whole card, and a quick drag on it never turns a page. diff --git a/screen_manager/app/claude_skill.py b/screen_manager/app/claude_skill.py index 358c4cc..9372597 100644 --- a/screen_manager/app/claude_skill.py +++ b/screen_manager/app/claude_skill.py @@ -16,7 +16,7 @@ from core import (ALERT_CAMERA_FIELD, ALERT_ENDINGS, ALERT_EVENT, ALERT_FALLBACK ALERT_MIN_FIRMWARE, ALERT_SUGGESTED_ICONS, AUTO_STANDBY_MIN_FIRMWARE, BROADCAST_DISMISS, BROADCAST_SHOW, CONTROLS, DISPLAYS, FULL_PAGE_MIN_FIRMWARE, MAX_PAGES, MAX_SLOTS, MAX_TILES, PAGE_TILE_REPEAT_MIN_FIRMWARE, SETTINGS_PAGE_MIN_FIRMWARE, SLOTS_PER_PAGE, TILE_BACKGROUNDS, TILE_EVENTS, TILE_RESULT_EVENT, WAKE_SLEEP_MIN_FIRMWARE, - SETTING_ENTITIES_MIN_FIRMWARE, DARK_MODE_MIN_FIRMWARE) + SETTING_ENTITIES_MIN_FIRMWARE, DARK_MODE_MIN_FIRMWARE, PAGE_BUTTONS_MIN_FIRMWARE) # Full-page tiles, navigation tiles and forty-eight tiles per screen. FULL_PAGE_VERSION = '.'.join(str(part) for part in FULL_PAGE_MIN_FIRMWARE) # The same navigation tile on several pages (app 0.2.78). @@ -286,6 +286,7 @@ Every screen has these entities in Home Assistant, on its ESPHome device. `_back_to_page_1` | On: after `number._back_to_page_1_after` seconds without a touch (30 to 3600) the screen closes a card and goes back to page 1. Pressing Wake, an alert or Auto standby don't count as a touch. | | `switch._back_to_page_1_on_standby` | On: going into standby also goes back to page 1. | | `switch._swipe_between_pages` | On: swipe between pages. | +| `switch._page_buttons` | On: the Previous and Next bar under the tiles on a screen with more than one page. Off: no bar, the tiles take its room, and only swiping or Go to page tiles change the page. Firmware {PAGE_BUTTONS_MIN_FIRMWARE} or newer. | | `select._rotation` | Guition only: `0°`, `90°`, `180°` or `270°`. | Wake and Sleep are buttons: press them with the `button.press` action. They save nothing, so an automation may press them as often as it likes, on every motion too. To reach several screens at once, list their buttons under `entity_id`. Don't target an area or a device with `button.press`: that presses every other button there as well, the Wake and Sleep of the same screen included. diff --git a/screen_manager/app/core.py b/screen_manager/app/core.py index e107b48..2522c03 100644 --- a/screen_manager/app/core.py +++ b/screen_manager/app/core.py @@ -37,7 +37,7 @@ WEEKDAYS = ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'] REPO = 'https://github.com/MaxGramser/homeassistant_espscreen' REFS = {'cyd': 'main', 'guition': 'main'} # Firmware shipped with this app release; screens below it get an update offer. -FIRMWARE_VERSION = '0.2.68' +FIRMWARE_VERSION = '0.2.69' # The Auto standby switch a screen offers Home Assistant automations. AUTO_STANDBY_MIN_FIRMWARE = '0.2.41' # The settings page the screen opens itself, and the screen.settings tile that opens it. @@ -48,6 +48,8 @@ WAKE_SLEEP_MIN_FIRMWARE = '0.2.45' SETTING_ENTITIES_MIN_FIRMWARE = '0.2.49' # Dark mode: the screen's dark look, a setting and entity of its own (components/smart_display/theme.h). DARK_MODE_MIN_FIRMWARE = '0.2.54' +# Page buttons: the Previous and Next bar under the tiles, a setting and entity of its own; off, the tiles take its room. +PAGE_BUTTONS_MIN_FIRMWARE = '0.2.69' ATTRS = frozenset('brightness percentage current_position current_tilt_position current_temperature temperature current_humidity min_temp max_temp target_temp_step supported_color_modes hvac_modes hvac_action hs_color color_temp_kelvin min_color_temp_kelvin max_color_temp_kelvin fan_speed_list unit_of_measurement battery_level fan_speed volume_level is_volume_muted media_title options min max step temperature_unit supported_features device_class next_rising next_setting finishes_at duration remaining humidity wind_speed wind_speed_unit apparent_temperature fan_modes swing_modes fan_mode swing_mode'.split()) # Attributes whose boolean value the screen needs; every other bool stays behind. BOOL_ATTRS = frozenset(['is_volume_muted']) @@ -276,12 +278,15 @@ SETTING_RULES = { # The dark look for a screen beside a bed (firmware 0.2.54+). Only a screen that owns its settings has it: it # never travels in the layout message. 'dark_mode': (False, None, None), + # The Previous and Next bar under the tiles (firmware 0.2.69+); off, the tiles grow into its room. Only a screen + # that owns its settings has it, like dark_mode. + 'page_buttons': (True, None, None), } # Firmware before 0.2.44 accepts a `settings` object with exactly its own eleven keys and refuses any # other size, so everything added after it travels as its own key in the layout message. Old firmware # ignores a key it does not know; a new screen with an old add-on keeps what it saved itself. # docs/SETTINGS.md walks through adding one. -SETTINGS_BESIDE_BLOCK = ('swipe_pages', 'rotation', 'auto_home', 'auto_home_seconds', 'dark_mode') +SETTINGS_BESIDE_BLOCK = ('swipe_pages', 'rotation', 'auto_home', 'auto_home_seconds', 'dark_mode', 'page_buttons') # ----- The screen owns its settings (firmware 0.2.49+) ----- # A screen offers every setting as an entity of its own device, and the settings page on the screen, Home @@ -306,6 +311,7 @@ SETTING_ENTITIES = { 'swipe_pages': ('switch', 'Swipe between pages'), 'rotation': ('select', 'Rotation'), 'dark_mode': ('switch', 'Dark mode'), + 'page_buttons': ('switch', 'Page buttons'), } # Entities firmware 0.2.49 added; one of them on a device means the screen owns its settings. The first five # existed before, so they cannot tell. diff --git a/screen_manager/app/server.py b/screen_manager/app/server.py index 5e73677..3a9fecd 100644 --- a/screen_manager/app/server.py +++ b/screen_manager/app/server.py @@ -699,8 +699,9 @@ class Manager: values = validate_settings({}) if (self.firmware_version(inbox, screen) or (0, 0, 0)) < (0, 2, 44): keys = [key for key in keys if key not in ('auto_home', 'auto_home_seconds')] - # Dark mode came after the screens took over their settings: firmware that gets them with the layout lacks it. - keys = [key for key in keys if key != 'dark_mode'] + # Dark mode and the page buttons came after the screens took over their settings: firmware that gets them + # with the layout lacks them. + keys = [key for key in keys if key not in ('dark_mode', 'page_buttons')] return {'owner': 'layout', 'values': values, 'keys': keys, 'unavailable': []} # Only the settings this screen has an entity for: one added in later firmware stays out of the panel. keys = [key for key in keys if key in entities] diff --git a/screen_manager/app/static/assets/index-DUJYFAbE.css b/screen_manager/app/static/assets/index-CKR1N3aP.css similarity index 64% rename from screen_manager/app/static/assets/index-DUJYFAbE.css rename to screen_manager/app/static/assets/index-CKR1N3aP.css index 7263bed..87bee2a 100644 --- a/screen_manager/app/static/assets/index-DUJYFAbE.css +++ b/screen_manager/app/static/assets/index-CKR1N3aP.css @@ -1 +1 @@ -:root{color-scheme:light dark;--ground: #f5f6f8;--surface: #ffffff;--surface-2: #f9fafb;--line: #e4e7ec;--line-strong: #d3d8e0;--ink: #16181d;--ink-2: #565d6b;--muted: #7a8290;--accent: #2f63f0;--accent-ink: #ffffff;--accent-soft: #edf2ff;--good: #1e9a5f;--good-soft: #e6f6ee;--warn: #b56d00;--warn-soft: #fff3df;--danger: #c6372e;--danger-soft: #fdecea;--seg: #eef0f3;--shadow: 0 12px 40px rgba(20, 24, 40, .12), 0 1px 2px rgba(20, 24, 40, .06);--code-bg: #f1f3f6;--ui-font: -apple-system, BlinkMacSystemFont, system-ui, "SF Pro Text", "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;--mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace}@media(prefers-color-scheme:dark){:root{--ground: #0e1014;--surface: #161a21;--surface-2: #1b2029;--line: #262c37;--line-strong: #333b49;--ink: #e9ecf1;--ink-2: #b4bbc7;--muted: #8a93a3;--accent: #6d93ff;--accent-ink: #0b1633;--accent-soft: #1b2540;--good: #3fbf7f;--good-soft: #14302a;--warn: #e0a03a;--warn-soft: #302610;--danger: #f07167;--danger-soft: #3a1b19;--seg: #222834;--shadow: 0 16px 48px rgba(0, 0, 0, .55), 0 1px 2px rgba(0, 0, 0, .4);--code-bg: #12151b}}@font-face{font-family:Tile Icons;src:url(./tile-icons-BawP5y9D.woff) format("woff");font-display:block}@font-face{font-family:Bar Roboto;font-weight:400;src:url(./bar-roboto-400-H0q_-5Ia.woff) format("woff");font-display:block}@font-face{font-family:Bar Roboto;font-weight:500;src:url(./bar-roboto-500-Bn7ZL9yA.woff) format("woff");font-display:block}*{box-sizing:border-box}html,body,#app{height:100%}body{margin:0;background:var(--ground);color:var(--ink);font:13px/1.45 var(--ui-font);-webkit-font-smoothing:antialiased;font-variant-numeric:tabular-nums}button,input,select,textarea{font:inherit;color:inherit}button{background:none;border:0;padding:0;cursor:pointer;border-radius:8px}button:disabled{opacity:.45;cursor:default}button:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible,[tabindex]:focus-visible,summary:focus-visible{outline:2px solid var(--accent);outline-offset:2px}input,select,textarea{background:var(--surface);border:1px solid var(--line-strong);border-radius:8px;padding:7px 10px;width:100%;color:var(--ink)}input::placeholder,textarea::placeholder{color:var(--muted)}select{appearance:auto}h1,h2,h3,h4{margin:0;font-weight:600;letter-spacing:-.01em}h1{font-size:20px;letter-spacing:-.02em}h2{font-size:15px}h3,h4{font-size:13px}p{margin:0}small,.hint{display:block;color:var(--muted);font-size:11.5px;font-weight:400;line-height:1.4}.hint.warn,small.warn{color:var(--warn)}code{font-family:var(--mono);font-size:.92em;background:var(--code-bg);padding:1px 6px;border-radius:5px;overflow-wrap:anywhere}pre{margin:0;padding:12px 14px;background:var(--code-bg);border:1px solid var(--line);border-radius:10px;overflow:auto;font:12px/1.5 var(--mono);color:var(--ink);white-space:pre-wrap;overflow-wrap:anywhere}a{color:var(--accent);text-decoration:none}a:hover{text-decoration:underline}.mdi{font-family:Tile Icons;font-style:normal;font-weight:400;line-height:1}[hidden]{display:none!important}.app{display:grid;grid-template-columns:232px minmax(0,1fr);height:100%;min-height:100vh}.app.dragging{user-select:none;-webkit-user-select:none;cursor:grabbing}.main{display:flex;flex-direction:column;min-width:0;min-height:0}.side{background:var(--surface);border-right:1px solid var(--line);display:flex;flex-direction:column;padding:14px 10px;gap:4px;min-height:0;overflow:auto;position:sticky;top:0;height:100vh}.brand{display:flex;align-items:center;gap:9px;padding:4px 8px 6px;font-weight:600;font-size:13px}.brand .mark{width:22px;height:22px;border-radius:6px;background:var(--ink);color:var(--surface);display:grid;place-items:center;font-size:14px;line-height:1}.conn{display:block;padding:0 8px 8px;font-size:11px;color:var(--muted)}.conn:before{content:"●";margin-right:6px;color:var(--warn)}.conn.online:before{color:var(--good)}.side .label{font-size:10.5px;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);padding:10px 8px 4px}.nav-item{display:flex;align-items:center;gap:9px;width:100%;padding:7px 8px;border-radius:8px;text-align:left;color:var(--ink-2)}.nav-item:hover{background:var(--surface-2);color:var(--ink)}.nav-item[aria-current=true]{background:var(--seg);color:var(--ink);font-weight:500}.nav-item .dot{width:7px;height:7px;border-radius:50%;background:var(--good);flex:none}.nav-item .dot.off{background:var(--line-strong)}.nav-item .unsaved{width:7px;height:7px;border-radius:50%;background:var(--warn);flex:none}.nav-item .txt{display:grid;min-width:0;flex:1}.nav-item .txt small{font-size:11px;color:var(--muted);font-weight:400;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.nav-item .mdi{font-size:16px;width:18px;text-align:center;color:var(--muted)}.nav-item .plus{width:18px;text-align:center;font-size:14px}.nav-item.ghost{color:var(--muted)}.nav-item.ghost:hover{color:var(--accent);background:var(--accent-soft)}.pill{font-size:10.5px;font-weight:600;padding:2px 7px;border-radius:999px;background:var(--warn-soft);color:var(--warn);flex:none}.side .spacer{flex:1}.screen-item{display:grid;gap:4px}.screen-update{display:grid;gap:6px;padding:2px 8px 8px 24px}.screen-update.available{grid-template-columns:1fr auto;align-items:center}.screen-update.available:has(.screen-host){grid-template-columns:1fr}.screen-update small.failed{color:var(--danger)}.screen-host{display:flex;gap:6px;align-items:center}.screen-host input{padding:5px 8px;font-size:12px}.pending{margin:4px 0;padding:10px;border:1px dashed var(--line-strong);border-radius:10px;display:grid;gap:6px}.pending strong{font-size:12.5px}.pending-actions{display:flex;flex-wrap:wrap;gap:6px}.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:6px 12px;border-radius:8px;font-weight:600;font-size:12.5px;white-space:nowrap}.btn.primary{background:var(--accent);color:var(--accent-ink)}.btn.primary:hover:not(:disabled){filter:brightness(1.06)}.btn.quiet{color:var(--ink-2);border:1px solid var(--line-strong);background:var(--surface)}.btn.quiet:hover:not(:disabled){background:var(--surface-2);color:var(--ink)}.btn.danger{color:var(--danger)}.btn.danger:hover:not(:disabled){background:var(--danger-soft)}.btn.mini{padding:4px 9px;font-size:11.5px}.btn.link{color:var(--accent);padding:0;font-weight:600}.icon-btn{width:30px;height:30px;display:grid;place-items:center;border-radius:8px;color:var(--ink-2);font-size:15px;flex:none}.icon-btn:hover{background:var(--seg);color:var(--ink)}.chip{display:inline-flex;align-items:center;gap:6px;padding:3px 9px;border-radius:999px;font-size:11.5px;font-weight:500;color:var(--ink-2);background:var(--surface-2);border:1px solid var(--line);white-space:nowrap}.chip .dot{width:6px;height:6px;border-radius:50%;background:var(--good);flex:none}.chip.off .dot{background:var(--line-strong)}.chip.dirty{color:var(--warn);background:var(--warn-soft);border-color:transparent}.chip.sent,.chip.good{color:var(--good);background:var(--good-soft);border-color:transparent}.chip.update{color:var(--warn);background:var(--warn-soft);border-color:transparent}.seg{display:inline-flex;padding:2px;background:var(--seg);border-radius:8px;gap:2px}.seg button{padding:4px 11px;border-radius:6px;font-size:12px;font-weight:500;color:var(--ink-2);white-space:nowrap}.seg button[aria-pressed=true]{background:var(--surface);color:var(--ink);box-shadow:0 1px 2px #14182824}.seg.wrap{flex-wrap:wrap}.seg button:hover:not(:disabled):not([aria-pressed=true]){color:var(--ink)}.spin{display:inline-block;width:14px;height:14px;border:2px solid var(--line-strong);border-top-color:var(--accent);border-radius:50%;animation:spin .8s linear infinite;flex:none}.spin.small{width:11px;height:11px;border-width:1.5px}@keyframes spin{to{transform:rotate(360deg)}}.switch{width:34px;height:20px;border-radius:10px;background:var(--line-strong);position:relative;flex:none;padding:0}.switch:after{content:"";position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:#fff;box-shadow:0 1px 2px #0003;transition:left .12s}.switch[aria-checked=true]{background:var(--accent)}.switch[aria-checked=true]:after{left:16px}.switch.unknown:after{left:9px;opacity:.6}@media(prefers-reduced-motion:reduce){.switch:after,.drawer,.spin{transition:none;animation:none}}.main-head{display:flex;align-items:center;gap:12px;padding:0 18px;min-height:52px;border-bottom:1px solid var(--line);background:var(--surface);position:sticky;top:0;z-index:4}.main-head h1{font-size:15px}.main-head .eyebrow{font-size:10.5px;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:var(--muted)}.head-right{margin-left:auto;display:flex;align-items:center;gap:8px;position:relative}.menu{position:absolute;top:38px;right:0;min-width:210px;background:var(--surface);border:1px solid var(--line);border-radius:10px;box-shadow:var(--shadow);padding:6px;display:grid;z-index:6}.menu button{text-align:left;padding:7px 10px;border-radius:7px;color:var(--ink);display:flex;justify-content:space-between;gap:12px;width:100%}.menu button small{color:var(--muted);display:inline}.menu button:hover:not(:disabled){background:var(--surface-2)}.body{position:relative;display:grid;grid-template-columns:minmax(0,1fr) 300px;flex:1;min-height:0;overflow:hidden}.canvas{overflow:auto;padding:22px 22px 26px;min-width:0}.canvas-head{display:flex;align-items:baseline;gap:12px;margin-bottom:14px;color:var(--muted);font-size:12px;flex-wrap:wrap}.canvas-head b{color:var(--ink);font-weight:600;font-size:13px}.canvas-head .warn{color:var(--warn)}.pages{display:flex;gap:18px;align-items:flex-start}.page{flex:0 0 292px;display:grid;gap:8px}.page-label{display:flex;justify-content:space-between;align-items:center;font-size:11.5px;color:var(--muted);padding:0 4px;min-height:22px}.page-label .btn.mini{color:var(--muted)}.page-label .btn.mini:hover{color:var(--danger);background:var(--danger-soft)}.page.ghost .device{display:grid;place-items:center;border:1.5px dashed var(--line-strong);background:transparent;color:var(--muted);font-weight:500;cursor:pointer}.page.ghost .device:hover,.page.ghost .device:focus-visible{color:var(--accent);border-color:var(--accent)}.page.ghost .device.drop{color:var(--accent);border-color:var(--accent);background:var(--accent-soft)}.page.ghost.disabled .device{opacity:.5;cursor:default}.device{background:#e7e7e7;border-radius:18px;padding:10px;display:grid;grid-template-rows:auto minmax(0,1fr);gap:8px;color:#1b1b1b;aspect-ratio:1 / 1}.device.cyd{aspect-ratio:4 / 3}.bar-wrap{cursor:pointer;border-radius:8px;padding:0 2px}.bar-wrap:hover{background:#dedede}.bar-wrap.selected{outline:2px solid var(--accent);outline-offset:1px}.bar-wrap svg{display:block;width:100%;height:auto}.tiles{display:grid;grid-template-columns:1fr 1fr;grid-auto-rows:minmax(0,1fr);gap:8px;min-height:0}.tile{position:relative;background:#fff;border:1px solid #dcdcdc;border-radius:12px;padding:8px 9px;display:flex;flex-direction:column;justify-content:space-between;gap:4px;min-height:0;cursor:pointer;text-align:left;overflow:hidden;color:#1b1b1b;touch-action:manipulation}.tile:hover{border-color:#b9c4d3}.tile.chosen{outline:2px solid var(--accent);outline-offset:1px}.tile.wide{grid-column:span 2;flex-direction:row;align-items:center;gap:8px}.tile.bare{background:transparent;border-style:dashed;border-color:#c3c9d0}.tile.placeholder{opacity:.55;border:2px dashed var(--accent);background:var(--accent-soft);cursor:default}.tile .ic{font-size:18px;line-height:1;color:#1b1b1b;flex:none}.tile .lead{display:grid;gap:2px;min-width:0;flex:1;align-content:end}.tile.wide .lead{display:flex;align-items:center;gap:8px;align-content:initial}.tile .tx{display:grid;min-width:0;gap:1px}.tile .head{display:flex;align-items:center;gap:8px;min-width:0;flex:1}.tile .head.top{flex:none}.tile .nm{font-size:11.5px;font-weight:600;line-height:1.2;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tile .st{font-size:10.5px;color:#5a5f66;line-height:1.2;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.device.cyd .tile{padding:6px 7px}.device.cyd .tile .nm{font-size:10.5px}.device.cyd .tile .ic{font-size:15px}.device.cyd .tile .head{gap:6px}.device.cyd .tile:has(>.mini-slider){padding-block:4px;gap:3px}.device.cyd .tile .mini-slider{height:8px}.tile .mini-slider{height:10px;border-radius:5px;background:linear-gradient(to right,#ffbf38 60%,#fff1d3 60%);flex:none;width:100%}.tile.wide .mini-slider{width:70px}.tile .ctl{flex:none;display:flex;align-items:center;gap:4px;color:#1b1b1b}.tile .tog{width:30px;height:17px;border-radius:9px;background:#2196f3;position:relative}.tile .tog:after{content:"";position:absolute;top:2px;right:2px;width:13px;height:13px;border-radius:50%;background:#fff}.tile .tog.off{background:#c9ccd1}.tile .tog.off:after{right:auto;left:2px}.tile .ic.lit{color:#d9930f}.tile .big{font-size:19px;font-weight:600;letter-spacing:-.02em;line-height:1.05;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tile .big small{display:inline;font-size:10.5px;font-weight:500;color:#5a5f66;margin-left:2px}.device.cyd .tile .big{font-size:16px}.tile .range{width:56px;height:14px;border-radius:7px;background:linear-gradient(to right,#2196f3 35%,#d3e8fb 35%)}.tile .key{height:20px;min-width:22px;padding:0 5px;border-radius:6px;background:#eceef1;display:grid;place-items:center;font-size:12px}.tile .stp{display:flex;align-items:center;gap:5px;height:20px;padding:0 7px;border-radius:10px;background:#eceef1;font-size:11px;font-weight:600}.tile .stp .mdi{font-size:12px}.tile .run{height:20px;padding:0 9px;border-radius:10px;background:#eceef1;font-size:11px;font-weight:600;line-height:20px}.tile .remove{position:absolute;top:5px;right:5px;width:22px;height:22px;border-radius:50%;background:#ffffffd9;color:#5a5f66;border:1px solid #dcdcdc;font-size:10px;line-height:1;display:grid;place-items:center;opacity:0;transition:opacity .12s}.tile:hover .remove,.tile:focus-within .remove{opacity:1}.tile .remove:hover{color:var(--danger);border-color:#e7b4b0;background:#fff}@media(hover:none){.tile .remove{opacity:1}}.tile .clockface{width:46px;height:46px;margin:auto}.tile.wide .clockface{margin:0;flex:none}.cell{min-height:0;border:1px dashed #cfd4da;border-radius:12px;background:transparent;color:#a3abb5;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:1px;font-weight:500;cursor:pointer;padding:6px}.cell span{font-size:18px;line-height:1}.cell small{font-size:10.5px;color:inherit}.cell:hover,.cell:focus-visible{border-color:#9eb6cf;color:#4b5563;background:#f6f7f9}.cell.insert-here{border:2px dashed var(--accent);color:var(--accent);background:var(--accent-soft)}.drag-ghost{position:fixed;left:0;top:0;pointer-events:none;z-index:50;opacity:.92;box-shadow:var(--shadow);border-radius:12px}.library{border-left:1px solid var(--line);background:var(--surface);display:flex;flex-direction:column;min-height:0}.lib-head{padding:14px 14px 10px;display:grid;gap:10px;border-bottom:1px solid var(--line)}.lib-title{display:flex;align-items:baseline;justify-content:space-between;font-weight:600;gap:8px}.lib-title small{color:var(--muted);font-weight:400;font-size:11.5px}.filters{display:flex;gap:6px;overflow-x:auto;scrollbar-width:none;padding-bottom:2px}.filters::-webkit-scrollbar{display:none}.filters button{flex:none;display:inline-flex;align-items:center;gap:5px;padding:4px 10px;border-radius:999px;font-size:11.5px;font-weight:500;color:var(--ink-2);background:var(--surface-2);border:1px solid var(--line)}.filters button[aria-pressed=true]{background:var(--ink);color:var(--surface);border-color:var(--ink)}.filters .domain-icon{width:16px;height:16px;font-size:11px;border-radius:50%;display:grid;place-items:center}.lib-list{overflow:auto;padding:8px;display:grid;gap:2px;align-content:start;flex:1}.ent{display:flex;align-items:center;gap:10px;padding:8px;border-radius:9px;width:100%;text-align:left;cursor:grab;touch-action:manipulation}.ent:hover:not(:disabled){background:var(--surface-2)}.ent:disabled{opacity:.55;cursor:default}.ent .av{width:30px;height:30px;border-radius:9px;display:grid;place-items:center;font-size:17px;flex:none}.ent .tx{display:grid;min-width:0;flex:1}.ent .tx b{font-weight:500;font-size:12.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ent .tx small{font-size:11px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ent .add{width:24px;height:24px;border-radius:50%;display:grid;place-items:center;color:var(--accent);background:var(--accent-soft);font-size:15px;font-weight:600;flex:none}.ent .add.done{color:var(--good);background:var(--good-soft);font-size:12px}.lib-foot{padding:10px 14px;border-top:1px solid var(--line);font-size:11.5px;color:var(--muted)}.domain-icon{display:inline-grid;place-items:center;border-radius:50%;font-weight:500}.drawer{position:absolute;top:0;right:0;bottom:0;width:360px;max-width:100%;background:var(--surface);border-left:1px solid var(--line);box-shadow:var(--shadow);transform:translate(102%);transition:transform .18s cubic-bezier(.2,.7,.2,1);display:flex;flex-direction:column;min-height:0;z-index:5}.drawer.open{transform:none}.dr-head{display:flex;align-items:center;gap:10px;padding:12px 12px 12px 16px;border-bottom:1px solid var(--line)}.dr-head .av{width:34px;height:34px;border-radius:10px;display:grid;place-items:center;font-size:20px;flex:none}.dr-head .tx{flex:1;min-width:0;display:grid}.dr-head .tx b{font-weight:600;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dr-head .tx small{color:var(--muted);font-size:11.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dr-head .tx small.mono{font-family:var(--mono)}.dr-body{overflow:auto;padding:14px 16px 18px;display:grid;gap:16px;align-content:start;flex:1}.f{display:grid;gap:7px}.f>.f-label{font-size:11px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--muted)}.f input,.f select{font-size:13px}.row{display:flex;align-items:center;gap:10px;padding:7px 10px 7px 8px;border:1px solid var(--line-strong);border-radius:9px;background:var(--surface);width:100%;text-align:left}.row .av{width:30px;height:30px;border-radius:8px;background:var(--surface-2);display:grid;place-items:center;font-size:18px;flex:none}.row .tx{flex:1;min-width:0;display:grid}.row .tx b{font-weight:500}.row .tx small{font-size:11px}.row .link{color:var(--accent);font-weight:600;font-size:12px;flex:none}.sw{display:grid;grid-template-columns:repeat(4,1fr);gap:6px}.sw button{display:grid;justify-items:center;gap:4px;padding:6px 4px 5px;border-radius:9px;border:1.5px solid transparent;font-size:10.5px;color:var(--ink-2)}.sw button i{width:30px;height:22px;border-radius:6px;border:1px solid rgba(0,0,0,.08);display:block}.sw button i.auto{background:linear-gradient(135deg,#fff 50%,#e5e7eb 50%)}.sw button i.none{background:linear-gradient(135deg,transparent 46%,#8b95a1 46%,#8b95a1 54%,transparent 54%),#e7e7e7;border-style:dashed}.sw button[aria-pressed=true]{border-color:var(--accent);background:var(--accent-soft);color:var(--ink)}.picker{display:grid;gap:10px;padding:10px;border:1px solid var(--line);border-radius:10px;background:var(--surface-2)}.picker input{background:var(--surface)}.icon-list{display:grid;gap:12px;max-height:300px;overflow:auto;padding-right:2px}.icon-list small{margin-bottom:6px}.icon-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(40px,1fr));gap:6px}.icon-choice{display:grid;place-items:center;height:40px;background:var(--surface);color:var(--ink);border:1px solid var(--line);border-radius:9px}.icon-choice .mdi{font-size:22px}.icon-choice[aria-pressed=true]{background:var(--accent-soft);color:var(--accent);border-color:var(--accent)}.icon-choice.icon-auto{display:flex;justify-content:flex-start;gap:10px;padding:0 12px;font-size:12.5px;font-weight:500;height:38px}.action-list{display:grid;gap:6px;max-height:300px;overflow:auto;padding-right:2px}.action-choice{display:grid;gap:2px;padding:8px 12px;background:var(--surface);color:var(--ink);border:1px solid var(--line);border-radius:9px;font-size:12.5px;text-align:left}.action-choice strong{font-weight:600}.action-choice[aria-pressed=true]{background:var(--accent-soft);border-color:var(--accent)}.action-number{display:flex;align-items:center;gap:8px}.action-number input{flex:1;min-width:0}.items{display:grid;gap:6px}.item{display:flex;align-items:center;gap:8px;padding:7px 8px;border:1px solid var(--line-strong);border-radius:9px;background:var(--surface);width:100%;text-align:left;cursor:pointer;touch-action:pan-y}.item:hover{border-color:var(--accent)}.item.selected{border-color:var(--accent);background:var(--accent-soft)}.item.is-hidden{border-style:dashed}.item.is-hidden .av,.item.is-hidden b{opacity:.55}.item.is-overflow{border-style:dashed;border-color:var(--warn)}.item.is-overflow small{color:var(--warn)}.item.dragging-chip{border:2px dashed var(--accent);background:var(--accent-soft);cursor:grabbing}.item.just-added{animation:chip-added 1.2s ease-out}@keyframes chip-added{0%{background:var(--accent-soft)}to{background:var(--surface)}}.item .grip{color:var(--line-strong);font-size:13px;cursor:grab;letter-spacing:-2px;flex:none}.item .av{width:26px;height:26px;border-radius:7px;background:var(--surface-2);display:grid;place-items:center;font-size:15px;flex:none}.item .tx{flex:1;min-width:0;display:grid}.item .tx b{font-weight:500;font-size:12.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.item .tx small{color:var(--muted);font-size:11px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.item .x{color:var(--muted);width:22px;height:22px;display:grid;place-items:center;border-radius:6px;flex:none;font-size:11px}.item .x:hover{color:var(--danger);background:var(--danger-soft)}.ghost-btn{border:1.5px dashed var(--line-strong);border-radius:9px;padding:8px;color:var(--accent);font-weight:600;width:100%;background:transparent}.ghost-btn:hover:not(:disabled){border-color:var(--accent);background:var(--accent-soft)}.dr-foot{margin-top:auto;display:flex;align-items:center;gap:8px;padding:10px 12px;border-top:1px solid var(--line);background:var(--surface-2);flex-wrap:wrap}.dr-foot .spacer{flex:1}.live{display:grid;gap:6px;padding:10px;border:1px solid var(--line);border-radius:10px;background:#e7e7e7;color:#1b1b1b;justify-items:start}.live small{color:#5a5f66}.live svg{display:block;height:28px}.option{display:flex;align-items:center;gap:10px;padding:8px 10px;border:1px solid var(--line);border-radius:9px;background:var(--surface);width:100%;text-align:left}.option:hover:not(:disabled){border-color:var(--accent)}.option .mdi{font-size:20px;width:26px;text-align:center;color:var(--ink-2);flex:none}.option .tx{display:grid;min-width:0}.option .tx strong{font-weight:500;font-size:12.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.option .tx small{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.options{display:grid;gap:6px}.inspection-tile{display:grid;gap:3px;padding:8px 10px;border:1px solid var(--line);border-radius:9px;margin-bottom:6px}.inspection-tile strong{font-size:12.5px}.settings{padding:22px;overflow:auto;display:grid;gap:14px;align-content:start;grid-column:1 / -1}.settings .status{font-size:12px;color:var(--muted)}.set-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:14px}.set-card{background:var(--surface);border:1px solid var(--line);border-radius:12px;padding:6px 14px 8px}.set-card h4{display:flex;align-items:center;gap:8px;padding:10px 0 8px}.set-card h4 .mdi{color:var(--muted);font-size:16px}.srow{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:8px 0;border-top:1px solid var(--line)}.srow>.s-label{color:var(--ink-2)}.srow.inactive .s-label,.srow.inactive .s-control{opacity:.45}.settings.offline .set-grid{opacity:.6}.step{display:inline-flex;align-items:center;gap:2px}.step button{width:26px;height:26px;border-radius:6px;color:var(--ink-2);background:var(--seg);display:grid;place-items:center}.step button .mdi{font-size:15px}.step button:hover:not(:disabled){color:var(--ink);background:var(--line-strong)}.step output{min-width:56px;text-align:center;font-weight:500}.panel{padding:22px 26px 40px;overflow:auto;display:grid;gap:18px;align-content:start;max-width:960px;width:100%}.panel.wide{max-width:1120px}.panel-head{display:flex;align-items:flex-start;gap:12px}.panel-head .tx{display:grid;gap:2px;flex:1;min-width:0}.panel-head h1{font-size:20px}.panel-head p{color:var(--muted);font-size:13px}.panel p,.panel li{color:var(--ink-2);font-size:13px}.card{background:var(--surface);border:1px solid var(--line);border-radius:12px;padding:16px 18px;display:grid;gap:12px;align-content:start}.card h2{font-size:14px}.card-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:14px}.field{display:grid;gap:6px}.field>.f-label,fieldset legend{font-size:11px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--muted)}fieldset{border:0;padding:0;margin:0;display:grid;gap:8px;min-width:0}fieldset:disabled{opacity:.6}.boards{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:8px}.board{display:flex;align-items:center;gap:10px;padding:10px 12px;border:1px solid var(--line-strong);border-radius:10px;cursor:pointer}.board:has(input:checked){border-color:var(--accent);background:var(--accent-soft)}.board input{width:auto;margin:0;accent-color:var(--accent)}.board b{display:block;font-weight:600}.actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.status-line{font-size:12.5px;color:var(--ink-2)}.status-line.ok{color:var(--good)}.status-line.error{color:var(--danger)}.progress-head{display:flex;align-items:center;gap:10px;font-weight:600;font-size:14px}.outcome{width:22px;height:22px;border-radius:50%;display:grid;place-items:center;color:#fff;font-size:12px;font-weight:700}.outcome.ok{background:var(--good)}.outcome.bad{background:var(--danger)}.steps{margin:0;padding-left:20px;display:grid;gap:8px}.steps li b{color:var(--ink)}.key-box{display:flex;align-items:center;gap:10px;padding:10px 12px;border:1px solid var(--line);border-radius:10px;background:var(--surface-2);flex-wrap:wrap}.key-box code{flex:1;min-width:0;background:none;padding:0;font-size:12px}.log{max-height:320px}details.log-wrap summary{cursor:pointer;font-weight:500;color:var(--ink-2);font-size:12.5px}details.log-wrap pre{margin-top:8px}.check{display:flex;align-items:flex-start;gap:10px;cursor:pointer}.check input{width:18px;height:18px;margin:1px 0 0;accent-color:var(--accent)}.check span{display:grid;gap:2px;font-weight:500}.node-line{display:flex;align-items:center;gap:6px;flex-wrap:wrap}.tools{display:flex;gap:10px;flex-wrap:wrap}.tool{display:flex;align-items:center;gap:10px;padding:10px 12px;border:1px solid var(--line);border-radius:10px;background:var(--surface);text-align:left;min-width:220px}.tool:hover{border-color:var(--accent)}.tool .tool-icon{width:30px;height:30px;border-radius:8px;background:var(--seg);display:grid;place-items:center;font-size:15px;color:var(--ink-2);flex:none}.tool .tx{display:grid}.tool .tx b{font-weight:600}.yaml-editor{display:grid;grid-template-columns:44px minmax(0,1fr);border:1px solid var(--line-strong);border-radius:10px;overflow:hidden;background:var(--code-bg);min-height:280px;max-height:60vh}.yaml-gutter{padding:12px 8px;text-align:right;color:var(--muted);font:12px/1.5 var(--mono);white-space:pre;user-select:none;overflow:hidden;border-right:1px solid var(--line)}.yaml-editor textarea{border:0;border-radius:0;background:transparent;resize:none;font:12px/1.5 var(--mono);padding:12px;white-space:pre;overflow:auto;min-height:280px;tab-size:2}.file-row{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.file-row code{font-size:12px}.notice{display:flex;gap:12px;padding:12px 14px;border:1px solid var(--line);border-radius:10px;background:var(--surface-2)}.notice .mdi{font-size:20px;color:var(--ink-2)}.notice strong{display:block;font-weight:600;margin-bottom:2px}.alerts section{display:grid;gap:10px}.alerts h2{font-size:15px}.alerts-nav{display:flex;flex-wrap:wrap;gap:6px}.alerts-nav button{background:var(--accent-soft);color:var(--accent);padding:5px 11px;font-size:12px;border-radius:20px;font-weight:500}.alert-hero{display:grid;grid-template-columns:minmax(0,300px) 1fr;gap:20px;align-items:center}.alert-mock{background:#101820;border-radius:16px;padding:22px 18px;display:grid;place-items:center}.alert-mock-card{position:relative;width:100%;background:#ffe1c6;border:1px solid #0001;border-radius:14px;padding:12px 12px 48px 52px;color:#1b1b1b;display:grid;gap:2px}.alert-mock-icon{position:absolute;left:12px;top:12px;font-size:30px}.alert-mock-card strong{font-size:15px;line-height:1.2}.alert-mock-card>span{font-size:12px;color:#3a3a3c}.alert-mock-card .fake{position:absolute;right:10px;bottom:10px;padding:6px 16px;border-radius:999px;background:#1b1b1b;color:#fff;font-size:13px}.alert-screen{border:1px solid var(--line);border-radius:10px;padding:10px 12px;display:grid;gap:8px;background:var(--surface)}.alert-screen-head{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap}.copy-line{display:flex;align-items:flex-start;gap:8px;flex-wrap:wrap}.copy-line .copy-label{flex:none;width:96px;font-size:12px;color:var(--muted);padding-top:2px}.copy-line>code{flex:1;min-width:0;padding:4px 8px}.copy-line pre{flex:1;min-width:0}.table-scroll{overflow-x:auto}table{width:100%;border-collapse:collapse;font-size:12.5px;min-width:560px}th,td{text-align:left;vertical-align:top;padding:8px 12px 8px 0;border-bottom:1px solid var(--line)}th{font-size:10.5px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);font-weight:600}td code{white-space:nowrap}.chips{display:flex;flex-wrap:wrap;gap:6px}.chips .chip .mdi{font-size:16px}.chips .chip code{background:none;padding:0}.alert-icon-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(100px,1fr));gap:8px}.alert-icon{display:grid;justify-items:center;align-content:start;gap:4px;min-height:86px;padding:10px 6px 8px;background:var(--surface);color:var(--ink);border:1px solid var(--line);border-radius:10px;font-weight:500}.alert-icon:hover{border-color:var(--accent)}.alert-icon .mdi{font-size:26px}.alert-icon code{background:none;padding:0;font-size:11px;color:var(--accent);text-align:center;white-space:normal}.alert-icon small{font-size:11px;text-align:center}.swatches{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:8px}.swatch{display:flex;align-items:center;gap:10px;padding:7px;border:1px solid var(--line);border-radius:10px;background:var(--surface);color:var(--ink);font-size:12px;text-align:left;font-weight:500}.swatch:hover{border-color:var(--accent)}.swatch i{width:32px;height:32px;border-radius:8px;border:1px solid #0001;flex:none;display:block}.swatch span{display:grid;gap:2px;min-width:0}.swatch code{background:none;padding:0}.alerts-list{padding-left:18px;margin:0;display:grid;gap:8px}.alerts-list b{color:var(--ink)}.subhead{font-size:11px;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);margin-top:6px}.empty{margin:auto;max-width:420px;text-align:center;display:grid;gap:10px;justify-items:center;padding:60px 20px}.empty-icon{width:48px;height:48px;border-radius:14px;background:var(--seg);color:var(--muted);display:grid;place-items:center;font-size:26px}.empty h2{font-size:18px}.empty p{color:var(--muted)}.toast{position:fixed;left:50%;bottom:20px;transform:translate(-50%);display:flex;align-items:center;gap:14px;padding:10px 16px;background:var(--ink);color:var(--surface);border-radius:10px;box-shadow:var(--shadow);font-size:13px;z-index:60;max-width:min(92vw,560px)}.toast button{color:#9fc4ff;font-weight:650}@media(max-width:960px){.app{grid-template-columns:1fr;min-height:100vh}.side{position:static;height:auto;flex-direction:row;flex-wrap:wrap;align-items:center;border-right:0;border-bottom:1px solid var(--line);padding:10px;gap:4px 6px}.side .label,.side .spacer,.conn{display:none}.side #screens,.screen-item{display:contents}.screen-update{display:none}.side #pending{flex-basis:100%}.nav-item{width:auto}.more{display:contents}.main-head{flex-wrap:wrap;padding:10px 14px;gap:8px;position:static}.head-right{margin-left:0;width:100%}.body{grid-template-columns:1fr;overflow:visible}.canvas{padding:16px;overflow:visible}.pages{overflow-x:auto;padding-bottom:8px}.library{border-left:0;border-top:1px solid var(--line)}.lib-list{flex:none;max-height:360px}.drawer{position:fixed;top:0;width:100%}.panel{padding:16px}.alert-hero{grid-template-columns:1fr}}.progress{position:relative;height:6px;border-radius:3px;background:var(--seg);overflow:hidden}.progress i{position:absolute;inset:0 auto 0 0;background:var(--accent);border-radius:3px;transition:width .4s}.progress-text{display:flex;justify-content:space-between;gap:8px;font-size:11px;color:var(--muted)}.progress-text span:last-child{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:1;text-align:right}.whatsnew summary{cursor:pointer;font-size:11.5px;color:var(--accent);font-weight:500;list-style:none}.whatsnew summary::-webkit-details-marker{display:none}.whatsnew ul{margin:6px 0 0;padding-left:14px;display:grid;gap:4px;font-size:11px;color:var(--ink-2)}.whatsnew ul li{line-height:1.35}.log-lines{max-height:160px;font-size:11px}.palette-backdrop{position:fixed;inset:0;background:#0a0c1247;z-index:70;display:grid;align-items:start;justify-items:center;padding:12vh 16px 16px}.palette{width:min(640px,100%);background:var(--surface);border:1px solid var(--line);border-radius:14px;box-shadow:var(--shadow);overflow:hidden;display:flex;flex-direction:column;max-height:70vh}.palette input{border:0;border-radius:0;border-bottom:1px solid var(--line);padding:14px 16px;font-size:15px;background:transparent}.palette-list{overflow:auto;padding:6px;display:grid;gap:2px}.palette-group{font-size:10.5px;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);padding:8px 10px 4px}.palette-item{display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:8px;width:100%;text-align:left;color:var(--ink)}.palette-item .mdi,.palette-item .glyph{width:22px;text-align:center;color:var(--muted);font-size:16px;flex:none}.palette-item .tx{flex:1;min-width:0;display:grid}.palette-item .tx small{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.palette-item .hint-key{font-size:10.5px;color:var(--muted);border:1px solid var(--line);border-radius:4px;padding:1px 5px}.palette-item.active,.palette-item:hover{background:var(--accent-soft)}.palette-empty{padding:18px;color:var(--muted);font-size:12.5px;text-align:center}.side .search-btn{display:flex;align-items:center;gap:8px;width:100%;margin:4px 0 6px;padding:7px 10px;border:1px solid var(--line);border-radius:8px;background:var(--surface-2);color:var(--muted);font-size:12px;text-align:left}.side .search-btn kbd{margin-left:auto;font:10.5px var(--ui-font);border:1px solid var(--line);border-radius:4px;padding:1px 5px;background:var(--surface)}.side .search-btn:hover{color:var(--ink);border-color:var(--line-strong)}.menu .sub{display:grid;padding:2px 0 4px 10px}.menu .sub button{font-size:12.5px;padding:6px 10px}.menu .sep{height:1px;background:var(--line);margin:4px 6px}.lib-row{display:flex;gap:6px;align-items:center}.lib-row select{padding:5px 8px;font-size:12px;flex:1;min-width:0}.lib-row .chip-toggle{flex:none;padding:5px 10px;border-radius:999px;font-size:11.5px;font-weight:500;color:var(--ink-2);background:var(--surface-2);border:1px solid var(--line)}.lib-row .chip-toggle[aria-pressed=true]{background:var(--ink);color:var(--surface);border-color:var(--ink)}.ent .av.on{background:#fff3d6!important;color:#b7791f!important}.ent .av.gone{background:var(--seg)!important;color:var(--muted)!important}.try-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px}.try-grid .field.wide{grid-column:1 / -1}.tile.full{grid-column:span 2;grid-row:span 3;flex-direction:column;align-items:center;justify-content:center;text-align:center;gap:6px}.tile.full .ic{font-size:34px}.tile.full .lead{flex:0 0 auto;align-content:center;justify-items:center}.tile.full .nm{font-size:14px}.tile.full .st{font-size:11px}.tile.full .ctl{margin-top:6px}.tile.full .mini-slider{width:60%}.tile .goto{font-size:10.5px;color:#5a5f66}.device.cyd .tile.full .ic{font-size:26px} +:root{color-scheme:light dark;--ground: #f5f6f8;--surface: #ffffff;--surface-2: #f9fafb;--line: #e4e7ec;--line-strong: #d3d8e0;--ink: #16181d;--ink-2: #565d6b;--muted: #7a8290;--accent: #2f63f0;--accent-ink: #ffffff;--accent-soft: #edf2ff;--good: #1e9a5f;--good-soft: #e6f6ee;--warn: #b56d00;--warn-soft: #fff3df;--danger: #c6372e;--danger-soft: #fdecea;--seg: #eef0f3;--shadow: 0 12px 40px rgba(20, 24, 40, .12), 0 1px 2px rgba(20, 24, 40, .06);--code-bg: #f1f3f6;--ui-font: -apple-system, BlinkMacSystemFont, system-ui, "SF Pro Text", "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;--mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace}@media(prefers-color-scheme:dark){:root{--ground: #0e1014;--surface: #161a21;--surface-2: #1b2029;--line: #262c37;--line-strong: #333b49;--ink: #e9ecf1;--ink-2: #b4bbc7;--muted: #8a93a3;--accent: #6d93ff;--accent-ink: #0b1633;--accent-soft: #1b2540;--good: #3fbf7f;--good-soft: #14302a;--warn: #e0a03a;--warn-soft: #302610;--danger: #f07167;--danger-soft: #3a1b19;--seg: #222834;--shadow: 0 16px 48px rgba(0, 0, 0, .55), 0 1px 2px rgba(0, 0, 0, .4);--code-bg: #12151b}}@font-face{font-family:Tile Icons;src:url(./tile-icons-BawP5y9D.woff) format("woff");font-display:block}@font-face{font-family:Bar Roboto;font-weight:400;src:url(./bar-roboto-400-H0q_-5Ia.woff) format("woff");font-display:block}@font-face{font-family:Bar Roboto;font-weight:500;src:url(./bar-roboto-500-Bn7ZL9yA.woff) format("woff");font-display:block}*{box-sizing:border-box}html,body,#app{height:100%}body{margin:0;background:var(--ground);color:var(--ink);font:13px/1.45 var(--ui-font);-webkit-font-smoothing:antialiased;font-variant-numeric:tabular-nums}button,input,select,textarea{font:inherit;color:inherit}button{background:none;border:0;padding:0;cursor:pointer;border-radius:8px}button:disabled{opacity:.45;cursor:default}button:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible,[tabindex]:focus-visible,summary:focus-visible{outline:2px solid var(--accent);outline-offset:2px}input,select,textarea{background:var(--surface);border:1px solid var(--line-strong);border-radius:8px;padding:7px 10px;width:100%;color:var(--ink)}input::placeholder,textarea::placeholder{color:var(--muted)}select{appearance:auto}h1,h2,h3,h4{margin:0;font-weight:600;letter-spacing:-.01em}h1{font-size:20px;letter-spacing:-.02em}h2{font-size:15px}h3,h4{font-size:13px}p{margin:0}small,.hint{display:block;color:var(--muted);font-size:11.5px;font-weight:400;line-height:1.4}.hint.warn,small.warn{color:var(--warn)}code{font-family:var(--mono);font-size:.92em;background:var(--code-bg);padding:1px 6px;border-radius:5px;overflow-wrap:anywhere}pre{margin:0;padding:12px 14px;background:var(--code-bg);border:1px solid var(--line);border-radius:10px;overflow:auto;font:12px/1.5 var(--mono);color:var(--ink);white-space:pre-wrap;overflow-wrap:anywhere}a{color:var(--accent);text-decoration:none}a:hover{text-decoration:underline}.mdi{font-family:Tile Icons;font-style:normal;font-weight:400;line-height:1}[hidden]{display:none!important}.app{display:grid;grid-template-columns:232px minmax(0,1fr);height:100%;min-height:100vh}.app.dragging{user-select:none;-webkit-user-select:none;cursor:grabbing}.main{display:flex;flex-direction:column;min-width:0;min-height:0}.side{background:var(--surface);border-right:1px solid var(--line);display:flex;flex-direction:column;padding:14px 10px;gap:4px;min-height:0;overflow:auto;position:sticky;top:0;height:100vh}.brand{display:flex;align-items:center;gap:9px;padding:4px 8px 6px;font-weight:600;font-size:13px}.brand .mark{width:22px;height:22px;border-radius:6px;background:var(--ink);color:var(--surface);display:grid;place-items:center;font-size:14px;line-height:1}.conn{display:block;padding:0 8px 8px;font-size:11px;color:var(--muted)}.conn:before{content:"●";margin-right:6px;color:var(--warn)}.conn.online:before{color:var(--good)}.side .label{font-size:10.5px;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);padding:10px 8px 4px}.nav-item{display:flex;align-items:center;gap:9px;width:100%;padding:7px 8px;border-radius:8px;text-align:left;color:var(--ink-2)}.nav-item:hover{background:var(--surface-2);color:var(--ink)}.nav-item[aria-current=true]{background:var(--seg);color:var(--ink);font-weight:500}.nav-item .dot{width:7px;height:7px;border-radius:50%;background:var(--good);flex:none}.nav-item .dot.off{background:var(--line-strong)}.nav-item .unsaved{width:7px;height:7px;border-radius:50%;background:var(--warn);flex:none}.nav-item .txt{display:grid;min-width:0;flex:1}.nav-item .txt small{font-size:11px;color:var(--muted);font-weight:400;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.nav-item .mdi{font-size:16px;width:18px;text-align:center;color:var(--muted)}.nav-item .plus{width:18px;text-align:center;font-size:14px}.nav-item.ghost{color:var(--muted)}.nav-item.ghost:hover{color:var(--accent);background:var(--accent-soft)}.pill{font-size:10.5px;font-weight:600;padding:2px 7px;border-radius:999px;background:var(--warn-soft);color:var(--warn);flex:none}.side .spacer{flex:1}.screen-item{display:grid;gap:4px}.screen-update{display:grid;gap:6px;padding:2px 8px 8px 24px}.screen-update.available{grid-template-columns:1fr auto;align-items:center}.screen-update.available:has(.screen-host){grid-template-columns:1fr}.screen-update small.failed{color:var(--danger)}.screen-host{display:flex;gap:6px;align-items:center}.screen-host input{padding:5px 8px;font-size:12px}.pending{margin:4px 0;padding:10px;border:1px dashed var(--line-strong);border-radius:10px;display:grid;gap:6px}.pending strong{font-size:12.5px}.pending-actions{display:flex;flex-wrap:wrap;gap:6px}.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:6px 12px;border-radius:8px;font-weight:600;font-size:12.5px;white-space:nowrap}.btn.primary{background:var(--accent);color:var(--accent-ink)}.btn.primary:hover:not(:disabled){filter:brightness(1.06)}.btn.quiet{color:var(--ink-2);border:1px solid var(--line-strong);background:var(--surface)}.btn.quiet:hover:not(:disabled){background:var(--surface-2);color:var(--ink)}.btn.danger{color:var(--danger)}.btn.danger:hover:not(:disabled){background:var(--danger-soft)}.btn.mini{padding:4px 9px;font-size:11.5px}.btn.link{color:var(--accent);padding:0;font-weight:600}.icon-btn{width:30px;height:30px;display:grid;place-items:center;border-radius:8px;color:var(--ink-2);font-size:15px;flex:none}.icon-btn:hover{background:var(--seg);color:var(--ink)}.chip{display:inline-flex;align-items:center;gap:6px;padding:3px 9px;border-radius:999px;font-size:11.5px;font-weight:500;color:var(--ink-2);background:var(--surface-2);border:1px solid var(--line);white-space:nowrap}.chip .dot{width:6px;height:6px;border-radius:50%;background:var(--good);flex:none}.chip.off .dot{background:var(--line-strong)}.chip.dirty{color:var(--warn);background:var(--warn-soft);border-color:transparent}.chip.sent,.chip.good{color:var(--good);background:var(--good-soft);border-color:transparent}.chip.update{color:var(--warn);background:var(--warn-soft);border-color:transparent}.seg{display:inline-flex;padding:2px;background:var(--seg);border-radius:8px;gap:2px}.seg button{padding:4px 11px;border-radius:6px;font-size:12px;font-weight:500;color:var(--ink-2);white-space:nowrap}.seg button[aria-pressed=true]{background:var(--surface);color:var(--ink);box-shadow:0 1px 2px #14182824}.seg.wrap{flex-wrap:wrap}.seg button:hover:not(:disabled):not([aria-pressed=true]){color:var(--ink)}.spin{display:inline-block;width:14px;height:14px;border:2px solid var(--line-strong);border-top-color:var(--accent);border-radius:50%;animation:spin .8s linear infinite;flex:none}.spin.small{width:11px;height:11px;border-width:1.5px}@keyframes spin{to{transform:rotate(360deg)}}.switch{width:34px;height:20px;border-radius:10px;background:var(--line-strong);position:relative;flex:none;padding:0}.switch:after{content:"";position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:#fff;box-shadow:0 1px 2px #0003;transition:left .12s}.switch[aria-checked=true]{background:var(--accent)}.switch[aria-checked=true]:after{left:16px}.switch.unknown:after{left:9px;opacity:.6}@media(prefers-reduced-motion:reduce){.switch:after,.drawer,.spin{transition:none;animation:none}}.main-head{display:flex;align-items:center;gap:12px;padding:0 18px;min-height:52px;border-bottom:1px solid var(--line);background:var(--surface);position:sticky;top:0;z-index:4}.main-head h1{font-size:15px}.main-head .eyebrow{font-size:10.5px;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:var(--muted)}.head-right{margin-left:auto;display:flex;align-items:center;gap:8px;position:relative}.menu{position:absolute;top:38px;right:0;min-width:210px;background:var(--surface);border:1px solid var(--line);border-radius:10px;box-shadow:var(--shadow);padding:6px;display:grid;z-index:6}.menu button{text-align:left;padding:7px 10px;border-radius:7px;color:var(--ink);display:flex;justify-content:space-between;gap:12px;width:100%}.menu button small{color:var(--muted);display:inline}.menu button:hover:not(:disabled){background:var(--surface-2)}.body{position:relative;display:grid;grid-template-columns:minmax(0,1fr) 300px;flex:1;min-height:0;overflow:hidden}.canvas{overflow:auto;padding:22px 22px 26px;min-width:0}.canvas-head{display:flex;align-items:baseline;gap:12px;margin-bottom:14px;color:var(--muted);font-size:12px;flex-wrap:wrap}.canvas-head b{color:var(--ink);font-weight:600;font-size:13px}.canvas-head .warn{color:var(--warn)}.pages{display:flex;gap:18px;align-items:flex-start}.page{flex:0 0 292px;display:grid;gap:8px}.page-label{display:flex;justify-content:space-between;align-items:center;font-size:11.5px;color:var(--muted);padding:0 4px;min-height:22px}.page-label .btn.mini{color:var(--muted)}.page-label .btn.mini:hover{color:var(--danger);background:var(--danger-soft)}.page.ghost .device{display:grid;place-items:center;border:1.5px dashed var(--line-strong);background:transparent;color:var(--muted);font-weight:500;cursor:pointer}.page.ghost .device:hover,.page.ghost .device:focus-visible{color:var(--accent);border-color:var(--accent)}.page.ghost .device.drop{color:var(--accent);border-color:var(--accent);background:var(--accent-soft)}.page.ghost.disabled .device{opacity:.5;cursor:default}.device{background:#e7e7e7;border-radius:18px;padding:10px;display:grid;grid-template-rows:auto minmax(0,1fr);gap:8px;color:#1b1b1b;aspect-ratio:1 / 1}.device.cyd{aspect-ratio:4 / 3}.bar-wrap{cursor:pointer;border-radius:8px;padding:0 2px}.bar-wrap:hover{background:#dedede}.bar-wrap.selected{outline:2px solid var(--accent);outline-offset:1px}.bar-wrap svg{display:block;width:100%;height:auto}.tiles{display:grid;grid-template-columns:1fr 1fr;grid-auto-rows:minmax(0,1fr);gap:8px;min-height:0}.tile{position:relative;background:#fff;border:1px solid #dcdcdc;border-radius:12px;padding:8px 9px;display:flex;flex-direction:column;justify-content:space-between;gap:4px;min-height:0;cursor:pointer;text-align:left;overflow:hidden;color:#1b1b1b;touch-action:manipulation}.tile:hover{border-color:#b9c4d3}.tile.chosen{outline:2px solid var(--accent);outline-offset:1px}.tile.wide{grid-column:span 2;flex-direction:row;align-items:center;gap:8px}.tile.bare{background:transparent;border-style:dashed;border-color:#c3c9d0}.tile.placeholder{opacity:.55;border:2px dashed var(--accent);background:var(--accent-soft);cursor:default}.tile .ic{font-size:18px;line-height:1;color:#1b1b1b;flex:none}.tile .lead{display:grid;gap:2px;min-width:0;flex:1;align-content:end}.tile.wide .lead{display:flex;align-items:center;gap:8px;align-content:initial}.tile .tx{display:grid;min-width:0;gap:1px}.tile .head{display:flex;align-items:center;gap:8px;min-width:0;flex:1}.tile .head.top{flex:none}.tile .nm{font-size:11.5px;font-weight:600;line-height:1.2;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tile .st{font-size:10.5px;color:#5a5f66;line-height:1.2;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.device.cyd .tile{padding:6px 7px}.device.cyd .tile .nm{font-size:10.5px}.device.cyd .tile .ic{font-size:15px}.device.cyd .tile .head{gap:6px}.device.cyd .tile:has(>.mini-slider){padding-block:4px;gap:3px}.device.cyd .tile .mini-slider{height:8px}.tile .mini-slider{height:10px;border-radius:5px;background:linear-gradient(to right,#ffbf38 60%,#fff1d3 60%);flex:none;width:100%}.tile.wide .mini-slider{width:70px}.tile .ctl{flex:none;display:flex;align-items:center;gap:4px;color:#1b1b1b}.tile .tog{width:30px;height:17px;border-radius:9px;background:#2196f3;position:relative}.tile .tog:after{content:"";position:absolute;top:2px;right:2px;width:13px;height:13px;border-radius:50%;background:#fff}.tile .tog.off{background:#c9ccd1}.tile .tog.off:after{right:auto;left:2px}.tile .ic.lit{color:#d9930f}.tile .big{font-size:19px;font-weight:600;letter-spacing:-.02em;line-height:1.05;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tile .big small{display:inline;font-size:10.5px;font-weight:500;color:#5a5f66;margin-left:2px}.device.cyd .tile .big{font-size:16px}.tile .range{width:56px;height:14px;border-radius:7px;background:linear-gradient(to right,#2196f3 35%,#d3e8fb 35%)}.tile .key{height:20px;min-width:22px;padding:0 5px;border-radius:6px;background:#eceef1;display:grid;place-items:center;font-size:12px}.tile .stp{display:flex;align-items:center;gap:5px;height:20px;padding:0 7px;border-radius:10px;background:#eceef1;font-size:11px;font-weight:600}.tile .stp .mdi{font-size:12px}.tile .run{height:20px;padding:0 9px;border-radius:10px;background:#eceef1;font-size:11px;font-weight:600;line-height:20px}.tile .remove{position:absolute;top:5px;right:5px;width:22px;height:22px;border-radius:50%;background:#ffffffd9;color:#5a5f66;border:1px solid #dcdcdc;font-size:10px;line-height:1;display:grid;place-items:center;opacity:0;transition:opacity .12s}.tile:hover .remove,.tile:focus-within .remove{opacity:1}.tile .remove:hover{color:var(--danger);border-color:#e7b4b0;background:#fff}@media(hover:none){.tile .remove{opacity:1}}.tile .clockface{width:46px;height:46px;margin:auto}.tile.wide .clockface{margin:0;flex:none}.cell{min-height:0;border:1px dashed #cfd4da;border-radius:12px;background:transparent;color:#a3abb5;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:1px;font-weight:500;cursor:pointer;padding:6px}.cell span{font-size:18px;line-height:1}.cell small{font-size:10.5px;color:inherit}.cell:hover,.cell:focus-visible{border-color:#9eb6cf;color:#4b5563;background:#f6f7f9}.cell.insert-here{border:2px dashed var(--accent);color:var(--accent);background:var(--accent-soft)}.drag-ghost{position:fixed;left:0;top:0;pointer-events:none;z-index:50;opacity:.92;box-shadow:var(--shadow);border-radius:12px}.library{border-left:1px solid var(--line);background:var(--surface);display:flex;flex-direction:column;min-height:0}.lib-head{padding:14px 14px 10px;display:grid;gap:10px;border-bottom:1px solid var(--line)}.lib-title{display:flex;align-items:baseline;justify-content:space-between;font-weight:600;gap:8px}.lib-title small{color:var(--muted);font-weight:400;font-size:11.5px}.filters{display:flex;gap:6px;overflow-x:auto;scrollbar-width:none;padding-bottom:2px}.filters::-webkit-scrollbar{display:none}.filters button{flex:none;display:inline-flex;align-items:center;gap:5px;padding:4px 10px;border-radius:999px;font-size:11.5px;font-weight:500;color:var(--ink-2);background:var(--surface-2);border:1px solid var(--line)}.filters button[aria-pressed=true]{background:var(--ink);color:var(--surface);border-color:var(--ink)}.filters .domain-icon{width:16px;height:16px;font-size:11px;border-radius:50%;display:grid;place-items:center}.lib-list{overflow:auto;padding:8px;display:grid;gap:2px;align-content:start;flex:1}.ent{display:flex;align-items:center;gap:10px;padding:8px;border-radius:9px;width:100%;text-align:left;cursor:grab;touch-action:manipulation}.ent:hover:not(:disabled){background:var(--surface-2)}.ent:disabled{opacity:.55;cursor:default}.ent .av{width:30px;height:30px;border-radius:9px;display:grid;place-items:center;font-size:17px;flex:none}.ent .tx{display:grid;min-width:0;flex:1}.ent .tx b{font-weight:500;font-size:12.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ent .tx small{font-size:11px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ent .add{width:24px;height:24px;border-radius:50%;display:grid;place-items:center;color:var(--accent);background:var(--accent-soft);font-size:15px;font-weight:600;flex:none}.ent .add.done{color:var(--good);background:var(--good-soft);font-size:12px}.lib-foot{padding:10px 14px;border-top:1px solid var(--line);font-size:11.5px;color:var(--muted)}.domain-icon{display:inline-grid;place-items:center;border-radius:50%;font-weight:500}.drawer{position:absolute;top:0;right:0;bottom:0;width:360px;max-width:100%;background:var(--surface);border-left:1px solid var(--line);box-shadow:var(--shadow);transform:translate(102%);transition:transform .18s cubic-bezier(.2,.7,.2,1);display:flex;flex-direction:column;min-height:0;z-index:5}.drawer.open{transform:none}.dr-head{display:flex;align-items:center;gap:10px;padding:12px 12px 12px 16px;border-bottom:1px solid var(--line)}.dr-head .av{width:34px;height:34px;border-radius:10px;display:grid;place-items:center;font-size:20px;flex:none}.dr-head .tx{flex:1;min-width:0;display:grid}.dr-head .tx b{font-weight:600;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dr-head .tx small{color:var(--muted);font-size:11.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dr-head .tx small.mono{font-family:var(--mono)}.dr-body{overflow:auto;padding:14px 16px 18px;display:grid;gap:16px;align-content:start;flex:1}.f{display:grid;gap:7px}.f>.f-label{font-size:11px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--muted)}.f input,.f select{font-size:13px}.row{display:flex;align-items:center;gap:10px;padding:7px 10px 7px 8px;border:1px solid var(--line-strong);border-radius:9px;background:var(--surface);width:100%;text-align:left}.row .av{width:30px;height:30px;border-radius:8px;background:var(--surface-2);display:grid;place-items:center;font-size:18px;flex:none}.row .tx{flex:1;min-width:0;display:grid}.row .tx b{font-weight:500}.row .tx small{font-size:11px}.row .link{color:var(--accent);font-weight:600;font-size:12px;flex:none}.sw{display:grid;grid-template-columns:repeat(4,1fr);gap:6px}.sw button{display:grid;justify-items:center;gap:4px;padding:6px 4px 5px;border-radius:9px;border:1.5px solid transparent;font-size:10.5px;color:var(--ink-2)}.sw button i{width:30px;height:22px;border-radius:6px;border:1px solid rgba(0,0,0,.08);display:block}.sw button i.auto{background:linear-gradient(135deg,#fff 50%,#e5e7eb 50%)}.sw button i.none{background:linear-gradient(135deg,transparent 46%,#8b95a1 46%,#8b95a1 54%,transparent 54%),#e7e7e7;border-style:dashed}.sw button[aria-pressed=true]{border-color:var(--accent);background:var(--accent-soft);color:var(--ink)}.picker{display:grid;gap:10px;padding:10px;border:1px solid var(--line);border-radius:10px;background:var(--surface-2)}.picker input{background:var(--surface)}.icon-list{display:grid;gap:12px;max-height:300px;overflow:auto;padding-right:2px}.icon-list small{margin-bottom:6px}.icon-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(40px,1fr));gap:6px}.icon-choice{display:grid;place-items:center;height:40px;background:var(--surface);color:var(--ink);border:1px solid var(--line);border-radius:9px}.icon-choice .mdi{font-size:22px}.icon-choice[aria-pressed=true]{background:var(--accent-soft);color:var(--accent);border-color:var(--accent)}.icon-choice.icon-auto{display:flex;justify-content:flex-start;gap:10px;padding:0 12px;font-size:12.5px;font-weight:500;height:38px}.action-list{display:grid;gap:6px;max-height:300px;overflow:auto;padding-right:2px}.action-choice{display:grid;gap:2px;padding:8px 12px;background:var(--surface);color:var(--ink);border:1px solid var(--line);border-radius:9px;font-size:12.5px;text-align:left}.action-choice strong{font-weight:600}.action-choice[aria-pressed=true]{background:var(--accent-soft);border-color:var(--accent)}.action-number{display:flex;align-items:center;gap:8px}.action-number input{flex:1;min-width:0}.items{display:grid;gap:6px}.item{display:flex;align-items:center;gap:8px;padding:7px 8px;border:1px solid var(--line-strong);border-radius:9px;background:var(--surface);width:100%;text-align:left;cursor:pointer;touch-action:pan-y}.item:hover{border-color:var(--accent)}.item.selected{border-color:var(--accent);background:var(--accent-soft)}.item.is-hidden{border-style:dashed}.item.is-hidden .av,.item.is-hidden b{opacity:.55}.item.is-overflow{border-style:dashed;border-color:var(--warn)}.item.is-overflow small{color:var(--warn)}.item.dragging-chip{border:2px dashed var(--accent);background:var(--accent-soft);cursor:grabbing}.item.just-added{animation:chip-added 1.2s ease-out}@keyframes chip-added{0%{background:var(--accent-soft)}to{background:var(--surface)}}.item .grip{color:var(--line-strong);font-size:13px;cursor:grab;letter-spacing:-2px;flex:none}.item .av{width:26px;height:26px;border-radius:7px;background:var(--surface-2);display:grid;place-items:center;font-size:15px;flex:none}.item .tx{flex:1;min-width:0;display:grid}.item .tx b{font-weight:500;font-size:12.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.item .tx small{color:var(--muted);font-size:11px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.item .x{color:var(--muted);width:22px;height:22px;display:grid;place-items:center;border-radius:6px;flex:none;font-size:11px}.item .x:hover{color:var(--danger);background:var(--danger-soft)}.ghost-btn{border:1.5px dashed var(--line-strong);border-radius:9px;padding:8px;color:var(--accent);font-weight:600;width:100%;background:transparent}.ghost-btn:hover:not(:disabled){border-color:var(--accent);background:var(--accent-soft)}.dr-foot{margin-top:auto;display:flex;align-items:center;gap:8px;padding:10px 12px;border-top:1px solid var(--line);background:var(--surface-2);flex-wrap:wrap}.dr-foot .spacer{flex:1}.live{display:grid;gap:6px;padding:10px;border:1px solid var(--line);border-radius:10px;background:#e7e7e7;color:#1b1b1b;justify-items:start}.live small{color:#5a5f66}.live svg{display:block;height:28px}.option{display:flex;align-items:center;gap:10px;padding:8px 10px;border:1px solid var(--line);border-radius:9px;background:var(--surface);width:100%;text-align:left}.option:hover:not(:disabled){border-color:var(--accent)}.option .mdi{font-size:20px;width:26px;text-align:center;color:var(--ink-2);flex:none}.option .tx{display:grid;min-width:0}.option .tx strong{font-weight:500;font-size:12.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.option .tx small{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.options{display:grid;gap:6px}.inspection-tile{display:grid;gap:3px;padding:8px 10px;border:1px solid var(--line);border-radius:9px;margin-bottom:6px}.inspection-tile strong{font-size:12.5px}.settings{padding:22px;overflow:auto;display:grid;gap:14px;align-content:start;grid-column:1 / -1}.settings .status{font-size:12px;color:var(--muted)}.set-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:14px}.set-card{background:var(--surface);border:1px solid var(--line);border-radius:12px;padding:6px 14px 8px}.set-card h4{display:flex;align-items:center;gap:8px;padding:10px 0 8px}.set-card h4 .mdi{color:var(--muted);font-size:16px}.set-card>.hint{margin:6px 0 4px}.srow{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:8px 0;border-top:1px solid var(--line)}.srow>.s-label{color:var(--ink-2)}.srow.inactive .s-label,.srow.inactive .s-control{opacity:.45}.settings.offline .set-grid{opacity:.6}.step{display:inline-flex;align-items:center;gap:2px}.step button{width:26px;height:26px;border-radius:6px;color:var(--ink-2);background:var(--seg);display:grid;place-items:center}.step button .mdi{font-size:15px}.step button:hover:not(:disabled){color:var(--ink);background:var(--line-strong)}.step output{min-width:56px;text-align:center;font-weight:500}.panel{padding:22px 26px 40px;overflow:auto;display:grid;gap:18px;align-content:start;max-width:960px;width:100%}.panel.wide{max-width:1120px}.panel-head{display:flex;align-items:flex-start;gap:12px}.panel-head .tx{display:grid;gap:2px;flex:1;min-width:0}.panel-head h1{font-size:20px}.panel-head p{color:var(--muted);font-size:13px}.panel p,.panel li{color:var(--ink-2);font-size:13px}.card{background:var(--surface);border:1px solid var(--line);border-radius:12px;padding:16px 18px;display:grid;gap:12px;align-content:start}.card h2{font-size:14px}.card-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:14px}.field{display:grid;gap:6px}.field>.f-label,fieldset legend{font-size:11px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--muted)}fieldset{border:0;padding:0;margin:0;display:grid;gap:8px;min-width:0}fieldset:disabled{opacity:.6}.boards{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:8px}.board{display:flex;align-items:center;gap:10px;padding:10px 12px;border:1px solid var(--line-strong);border-radius:10px;cursor:pointer}.board:has(input:checked){border-color:var(--accent);background:var(--accent-soft)}.board input{width:auto;margin:0;accent-color:var(--accent)}.board b{display:block;font-weight:600}.actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.status-line{font-size:12.5px;color:var(--ink-2)}.status-line.ok{color:var(--good)}.status-line.error{color:var(--danger)}.progress-head{display:flex;align-items:center;gap:10px;font-weight:600;font-size:14px}.outcome{width:22px;height:22px;border-radius:50%;display:grid;place-items:center;color:#fff;font-size:12px;font-weight:700}.outcome.ok{background:var(--good)}.outcome.bad{background:var(--danger)}.steps{margin:0;padding-left:20px;display:grid;gap:8px}.steps li b{color:var(--ink)}.key-box{display:flex;align-items:center;gap:10px;padding:10px 12px;border:1px solid var(--line);border-radius:10px;background:var(--surface-2);flex-wrap:wrap}.key-box code{flex:1;min-width:0;background:none;padding:0;font-size:12px}.log{max-height:320px}details.log-wrap summary{cursor:pointer;font-weight:500;color:var(--ink-2);font-size:12.5px}details.log-wrap pre{margin-top:8px}.check{display:flex;align-items:flex-start;gap:10px;cursor:pointer}.check input{width:18px;height:18px;margin:1px 0 0;accent-color:var(--accent)}.check span{display:grid;gap:2px;font-weight:500}.node-line{display:flex;align-items:center;gap:6px;flex-wrap:wrap}.tools{display:flex;gap:10px;flex-wrap:wrap}.tool{display:flex;align-items:center;gap:10px;padding:10px 12px;border:1px solid var(--line);border-radius:10px;background:var(--surface);text-align:left;min-width:220px}.tool:hover{border-color:var(--accent)}.tool .tool-icon{width:30px;height:30px;border-radius:8px;background:var(--seg);display:grid;place-items:center;font-size:15px;color:var(--ink-2);flex:none}.tool .tx{display:grid}.tool .tx b{font-weight:600}.yaml-editor{display:grid;grid-template-columns:44px minmax(0,1fr);border:1px solid var(--line-strong);border-radius:10px;overflow:hidden;background:var(--code-bg);min-height:280px;max-height:60vh}.yaml-gutter{padding:12px 8px;text-align:right;color:var(--muted);font:12px/1.5 var(--mono);white-space:pre;user-select:none;overflow:hidden;border-right:1px solid var(--line)}.yaml-editor textarea{border:0;border-radius:0;background:transparent;resize:none;font:12px/1.5 var(--mono);padding:12px;white-space:pre;overflow:auto;min-height:280px;tab-size:2}.file-row{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.file-row code{font-size:12px}.notice{display:flex;gap:12px;padding:12px 14px;border:1px solid var(--line);border-radius:10px;background:var(--surface-2)}.notice .mdi{font-size:20px;color:var(--ink-2)}.notice strong{display:block;font-weight:600;margin-bottom:2px}.alerts section{display:grid;gap:10px}.alerts h2{font-size:15px}.alerts-nav{display:flex;flex-wrap:wrap;gap:6px}.alerts-nav button{background:var(--accent-soft);color:var(--accent);padding:5px 11px;font-size:12px;border-radius:20px;font-weight:500}.alert-hero{display:grid;grid-template-columns:minmax(0,300px) 1fr;gap:20px;align-items:center}.alert-mock{background:#101820;border-radius:16px;padding:22px 18px;display:grid;place-items:center}.alert-mock-card{position:relative;width:100%;background:#ffe1c6;border:1px solid #0001;border-radius:14px;padding:12px 12px 48px 52px;color:#1b1b1b;display:grid;gap:2px}.alert-mock-icon{position:absolute;left:12px;top:12px;font-size:30px}.alert-mock-card strong{font-size:15px;line-height:1.2}.alert-mock-card>span{font-size:12px;color:#3a3a3c}.alert-mock-card .fake{position:absolute;right:10px;bottom:10px;padding:6px 16px;border-radius:999px;background:#1b1b1b;color:#fff;font-size:13px}.alert-screen{border:1px solid var(--line);border-radius:10px;padding:10px 12px;display:grid;gap:8px;background:var(--surface)}.alert-screen-head{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap}.copy-line{display:flex;align-items:flex-start;gap:8px;flex-wrap:wrap}.copy-line .copy-label{flex:none;width:96px;font-size:12px;color:var(--muted);padding-top:2px}.copy-line>code{flex:1;min-width:0;padding:4px 8px}.copy-line pre{flex:1;min-width:0}.table-scroll{overflow-x:auto}table{width:100%;border-collapse:collapse;font-size:12.5px;min-width:560px}th,td{text-align:left;vertical-align:top;padding:8px 12px 8px 0;border-bottom:1px solid var(--line)}th{font-size:10.5px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);font-weight:600}td code{white-space:nowrap}.chips{display:flex;flex-wrap:wrap;gap:6px}.chips .chip .mdi{font-size:16px}.chips .chip code{background:none;padding:0}.alert-icon-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(100px,1fr));gap:8px}.alert-icon{display:grid;justify-items:center;align-content:start;gap:4px;min-height:86px;padding:10px 6px 8px;background:var(--surface);color:var(--ink);border:1px solid var(--line);border-radius:10px;font-weight:500}.alert-icon:hover{border-color:var(--accent)}.alert-icon .mdi{font-size:26px}.alert-icon code{background:none;padding:0;font-size:11px;color:var(--accent);text-align:center;white-space:normal}.alert-icon small{font-size:11px;text-align:center}.swatches{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:8px}.swatch{display:flex;align-items:center;gap:10px;padding:7px;border:1px solid var(--line);border-radius:10px;background:var(--surface);color:var(--ink);font-size:12px;text-align:left;font-weight:500}.swatch:hover{border-color:var(--accent)}.swatch i{width:32px;height:32px;border-radius:8px;border:1px solid #0001;flex:none;display:block}.swatch span{display:grid;gap:2px;min-width:0}.swatch code{background:none;padding:0}.alerts-list{padding-left:18px;margin:0;display:grid;gap:8px}.alerts-list b{color:var(--ink)}.subhead{font-size:11px;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);margin-top:6px}.empty{margin:auto;max-width:420px;text-align:center;display:grid;gap:10px;justify-items:center;padding:60px 20px}.empty-icon{width:48px;height:48px;border-radius:14px;background:var(--seg);color:var(--muted);display:grid;place-items:center;font-size:26px}.empty h2{font-size:18px}.empty p{color:var(--muted)}.toast{position:fixed;left:50%;bottom:20px;transform:translate(-50%);display:flex;align-items:center;gap:14px;padding:10px 16px;background:var(--ink);color:var(--surface);border-radius:10px;box-shadow:var(--shadow);font-size:13px;z-index:60;max-width:min(92vw,560px)}.toast button{color:#9fc4ff;font-weight:650}@media(max-width:960px){.app{grid-template-columns:1fr;min-height:100vh}.side{position:static;height:auto;flex-direction:row;flex-wrap:wrap;align-items:center;border-right:0;border-bottom:1px solid var(--line);padding:10px;gap:4px 6px}.side .label,.side .spacer,.conn{display:none}.side #screens,.screen-item{display:contents}.screen-update{display:none}.side #pending{flex-basis:100%}.nav-item{width:auto}.more{display:contents}.main-head{flex-wrap:wrap;padding:10px 14px;gap:8px;position:static}.head-right{margin-left:0;width:100%}.body{grid-template-columns:1fr;overflow:visible}.canvas{padding:16px;overflow:visible}.pages{overflow-x:auto;padding-bottom:8px}.library{border-left:0;border-top:1px solid var(--line)}.lib-list{flex:none;max-height:360px}.drawer{position:fixed;top:0;width:100%}.panel{padding:16px}.alert-hero{grid-template-columns:1fr}}.progress{position:relative;height:6px;border-radius:3px;background:var(--seg);overflow:hidden}.progress i{position:absolute;inset:0 auto 0 0;background:var(--accent);border-radius:3px;transition:width .4s}.progress-text{display:flex;justify-content:space-between;gap:8px;font-size:11px;color:var(--muted)}.progress-text span:last-child{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:1;text-align:right}.whatsnew summary{cursor:pointer;font-size:11.5px;color:var(--accent);font-weight:500;list-style:none}.whatsnew summary::-webkit-details-marker{display:none}.whatsnew ul{margin:6px 0 0;padding-left:14px;display:grid;gap:4px;font-size:11px;color:var(--ink-2)}.whatsnew ul li{line-height:1.35}.log-lines{max-height:160px;font-size:11px}.palette-backdrop{position:fixed;inset:0;background:#0a0c1247;z-index:70;display:grid;align-items:start;justify-items:center;padding:12vh 16px 16px}.palette{width:min(640px,100%);background:var(--surface);border:1px solid var(--line);border-radius:14px;box-shadow:var(--shadow);overflow:hidden;display:flex;flex-direction:column;max-height:70vh}.palette input{border:0;border-radius:0;border-bottom:1px solid var(--line);padding:14px 16px;font-size:15px;background:transparent}.palette-list{overflow:auto;padding:6px;display:grid;gap:2px}.palette-group{font-size:10.5px;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);padding:8px 10px 4px}.palette-item{display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:8px;width:100%;text-align:left;color:var(--ink)}.palette-item .mdi,.palette-item .glyph{width:22px;text-align:center;color:var(--muted);font-size:16px;flex:none}.palette-item .tx{flex:1;min-width:0;display:grid}.palette-item .tx small{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.palette-item .hint-key{font-size:10.5px;color:var(--muted);border:1px solid var(--line);border-radius:4px;padding:1px 5px}.palette-item.active,.palette-item:hover{background:var(--accent-soft)}.palette-empty{padding:18px;color:var(--muted);font-size:12.5px;text-align:center}.side .search-btn{display:flex;align-items:center;gap:8px;width:100%;margin:4px 0 6px;padding:7px 10px;border:1px solid var(--line);border-radius:8px;background:var(--surface-2);color:var(--muted);font-size:12px;text-align:left}.side .search-btn kbd{margin-left:auto;font:10.5px var(--ui-font);border:1px solid var(--line);border-radius:4px;padding:1px 5px;background:var(--surface)}.side .search-btn:hover{color:var(--ink);border-color:var(--line-strong)}.menu .sub{display:grid;padding:2px 0 4px 10px}.menu .sub button{font-size:12.5px;padding:6px 10px}.menu .sep{height:1px;background:var(--line);margin:4px 6px}.lib-row{display:flex;gap:6px;align-items:center}.lib-row select{padding:5px 8px;font-size:12px;flex:1;min-width:0}.lib-row .chip-toggle{flex:none;padding:5px 10px;border-radius:999px;font-size:11.5px;font-weight:500;color:var(--ink-2);background:var(--surface-2);border:1px solid var(--line)}.lib-row .chip-toggle[aria-pressed=true]{background:var(--ink);color:var(--surface);border-color:var(--ink)}.ent .av.on{background:#fff3d6!important;color:#b7791f!important}.ent .av.gone{background:var(--seg)!important;color:var(--muted)!important}.try-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px}.try-grid .field.wide{grid-column:1 / -1}.tile.full{grid-column:span 2;grid-row:span 3;flex-direction:column;align-items:center;justify-content:center;text-align:center;gap:6px}.tile.full .ic{font-size:34px}.tile.full .lead{flex:0 0 auto;align-content:center;justify-items:center}.tile.full .nm{font-size:14px}.tile.full .st{font-size:11px}.tile.full .ctl{margin-top:6px}.tile.full .mini-slider{width:60%}.tile .goto{font-size:10.5px;color:#5a5f66}.device.cyd .tile.full .ic{font-size:26px} diff --git a/screen_manager/app/static/assets/index-Cp13kZoB.js b/screen_manager/app/static/assets/index-Cp13kZoB.js new file mode 100644 index 0000000..df47690 --- /dev/null +++ b/screen_manager/app/static/assets/index-Cp13kZoB.js @@ -0,0 +1,28 @@ +(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const o of document.querySelectorAll('link[rel="modulepreload"]'))s(o);new MutationObserver(o=>{for(const l of o)if(l.type==="childList")for(const a of l.addedNodes)a.tagName==="LINK"&&a.rel==="modulepreload"&&s(a)}).observe(document,{childList:!0,subtree:!0});function n(o){const l={};return o.integrity&&(l.integrity=o.integrity),o.referrerPolicy&&(l.referrerPolicy=o.referrerPolicy),o.crossOrigin==="use-credentials"?l.credentials="include":o.crossOrigin==="anonymous"?l.credentials="omit":l.credentials="same-origin",l}function s(o){if(o.ep)return;o.ep=!0;const l=n(o);fetch(o.href,l)}})();function Yo(e){const t=Object.create(null);for(const n of e.split(","))t[n]=1;return n=>n in t}const Ae={},dn=[],Tt=()=>{},yl=()=>!1,js=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&(e.charCodeAt(2)>122||e.charCodeAt(2)<97),Bs=e=>e.startsWith("onUpdate:"),je=Object.assign,zo=(e,t)=>{const n=e.indexOf(t);n>-1&&e.splice(n,1)},Xa=Object.prototype.hasOwnProperty,be=(e,t)=>Xa.call(e,t),oe=Array.isArray,Gt=e=>ds(e)==="[object Map]",Rt=e=>ds(e)==="[object Set]",bi=e=>ds(e)==="[object Date]",ue=e=>typeof e=="function",Ie=e=>typeof e=="string",Et=e=>typeof e=="symbol",Se=e=>e!==null&&typeof e=="object",gl=e=>(Se(e)||ue(e))&&ue(e.then)&&ue(e.catch),bl=Object.prototype.toString,ds=e=>bl.call(e),Za=e=>ds(e).slice(8,-1),wl=e=>ds(e)==="[object Object]",Jo=e=>Ie(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e,Yn=Yo(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),Vs=e=>{const t=Object.create(null);return(n=>t[n]||(t[n]=e(n)))},Qa=/-\w/g,ze=Vs(e=>e.replace(Qa,t=>t.slice(1).toUpperCase())),er=/\B([A-Z])/g,on=Vs(e=>e.replace(er,"-$1").toLowerCase()),Ws=Vs(e=>e.charAt(0).toUpperCase()+e.slice(1)),lo=Vs(e=>e?`on${Ws(e)}`:""),At=(e,t)=>!Object.is(e,t),ks=(e,...t)=>{for(let n=0;n{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,writable:s,value:n})},Ks=e=>{const t=parseFloat(e);return isNaN(t)?e:t};let wi;const qs=()=>wi||(wi=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{});function Pe(e){if(oe(e)){const t={};for(let n=0;nn.startsWith("/*")?"":n).split(tr).forEach(n=>{if(n){const s=n.split(nr);s.length>1&&(t[s[0].trim()]=s[1].trim())}}),t}function ie(e){let t="";if(Ie(e))t=e;else if(oe(e))for(let n=0;nmt(n,t))}const $l=e=>!!(e&&e.__v_isRef===!0),m=e=>Ie(e)?e:e==null?"":oe(e)||Se(e)&&(e.toString===bl||!ue(e.toString))?$l(e)?m(e.value):JSON.stringify(e,xl,2):String(e),xl=(e,t)=>$l(t)?xl(e,t.value):Gt(t)?{[`Map(${t.size})`]:[...t.entries()].reduce((n,[s,o],l)=>(n[ao(s,l)+" =>"]=o,n),{})}:Rt(t)?{[`Set(${t.size})`]:[...t.values()].map(n=>ao(n))}:Et(t)?ao(t):Se(t)&&!oe(t)&&!wl(t)?String(t):t,ao=(e,t="")=>{var n;return Et(e)?`Symbol(${(n=e.description)!=null?n:t})`:e};let Re;class ur{constructor(t=!1){this.detached=t,this._active=!0,this._on=0,this.effects=[],this.cleanups=[],this._isPaused=!1,this._warnOnRun=!0,this.__v_skip=!0,!t&&Re&&(Re.active?(this.parent=Re,this.index=(Re.scopes||(Re.scopes=[])).push(this)-1):(this._active=!1,this._warnOnRun=!1))}get active(){return this._active}pause(){if(this._active){this._isPaused=!0;let t,n;if(this.scopes){const s=this.scopes.slice();for(t=0,n=s.length;t0&&--this._on===0){if(Re===this)Re=this.prevScope;else{let t=Re;for(;t;){if(t.prevScope===this){t.prevScope=this.prevScope;break}t=t.prevScope}}this.prevScope=void 0}}stop(t){if(this._active){this._active=!1;let n,s;for(n=0,s=this.effects.length;n0)return;if(Jn){let t=Jn;for(Jn=void 0;t;){const n=t.next;t.next=void 0,t.flags&=-9,t=n}}let e;for(;zn;){let t=zn;for(zn=void 0;t;){const n=t.next;if(t.next=void 0,t.flags&=-9,t.flags&1)try{t.trigger()}catch(s){e||(e=s)}t=n}}if(e)throw e}function Tl(e){for(let t=e.deps;t;t=t.nextDep)t.version=-1,t.prevActiveLink=t.dep.activeLink,t.dep.activeLink=t}function El(e){let t,n=e.depsTail,s=n;for(;s;){const o=s.prevDep;s.version===-1?(s===n&&(n=o),Qo(s),dr(s)):t=s,s.dep.activeLink=s.prevActiveLink,s.prevActiveLink=void 0,s=o}e.deps=t,e.depsTail=n}function Co(e){for(let t=e.deps;t;t=t.nextDep)if(t.dep.version!==t.version||t.dep.computed&&(Pl(t.dep.computed)||t.dep.version!==t.version))return!0;return!!e._dirty}function Pl(e){if(e.flags&4&&!(e.flags&16)||(e.flags&=-17,e.globalVersion===Qn)||(e.globalVersion=Qn,!e.isSSR&&e.flags&128&&(!e.deps&&!e._dirty||!Co(e))))return;e.flags|=2;const t=e.dep,n=Te,s=vt;Te=e,vt=!0;try{Tl(e);const o=e.fn(e._value);(t.version===0||At(o,e._value))&&(e.flags|=128,e._value=o,t.version++)}catch(o){throw t.version++,o}finally{Te=n,vt=s,El(e),e.flags&=-3}}function Qo(e,t=!1){const{dep:n,prevSub:s,nextSub:o}=e;if(s&&(s.nextSub=o,e.prevSub=void 0),o&&(o.prevSub=s,e.nextSub=void 0),n.subs===e&&(n.subs=s,!s&&n.computed)){n.computed.flags&=-5;for(let l=n.computed.deps;l;l=l.nextDep)Qo(l,!0)}!t&&!--n.sc&&n.map&&n.map.delete(n.key)}function dr(e){const{prevDep:t,nextDep:n}=e;t&&(t.nextDep=n,e.prevDep=void 0),n&&(n.prevDep=t,e.nextDep=void 0)}let vt=!0;const Ol=[];function Ht(){Ol.push(vt),vt=!1}function Ut(){const e=Ol.pop();vt=e===void 0?!0:e}function $i(e){const{cleanup:t}=e;if(e.cleanup=void 0,t){const n=Te;Te=void 0;try{t()}finally{Te=n}}}let Qn=0;class fr{constructor(t,n){this.sub=t,this.dep=n,this.version=n.version,this.nextDep=this.prevDep=this.nextSub=this.prevSub=this.prevActiveLink=void 0}}class ei{constructor(t){this.computed=t,this.version=0,this.activeLink=void 0,this.subs=void 0,this.map=void 0,this.key=void 0,this.sc=0,this.__v_skip=!0}track(t){if(!Te||!vt||Te===this.computed)return;let n=this.activeLink;if(n===void 0||n.sub!==Te)n=this.activeLink=new fr(Te,this),Te.deps?(n.prevDep=Te.depsTail,Te.depsTail.nextDep=n,Te.depsTail=n):Te.deps=Te.depsTail=n,Il(n);else if(n.version===-1&&(n.version=this.version,n.nextDep)){const s=n.nextDep;s.prevDep=n.prevDep,n.prevDep&&(n.prevDep.nextDep=s),n.prevDep=Te.depsTail,n.nextDep=void 0,Te.depsTail.nextDep=n,Te.depsTail=n,Te.deps===n&&(Te.deps=s)}return n}trigger(t){this.version++,Qn++,this.notify(t)}notify(t){Xo();try{for(let n=this.subs;n;n=n.prevSub)n.sub.notify()&&n.sub.dep.notify()}finally{Zo()}}}function Il(e){if(e.dep.sc++,e.sub.flags&4){const t=e.dep.computed;if(t&&!e.dep.subs){t.flags|=20;for(let s=t.deps;s;s=s.nextDep)Il(s)}const n=e.dep.subs;n!==e&&(e.prevSub=n,n&&(n.nextSub=e)),e.dep.subs=e}}const Ao=new WeakMap,fn=Symbol(""),To=Symbol(""),es=Symbol("");function Be(e,t,n){if(vt&&Te){let s=Ao.get(e);s||Ao.set(e,s=new Map);let o=s.get(n);o||(s.set(n,o=new ei),o.map=s,o.key=n),o.track()}}function Dt(e,t,n,s,o,l){const a=Ao.get(e);if(!a){Qn++;return}const u=d=>{d&&d.trigger()};if(Xo(),t==="clear")a.forEach(u);else{const d=oe(e),f=d&&Jo(n);if(d&&n==="length"){const r=Number(s);a.forEach((h,b)=>{(b==="length"||b===es||!Et(b)&&b>=r)&&u(h)})}else switch((n!==void 0||a.has(void 0))&&u(a.get(n)),f&&u(a.get(es)),t){case"add":d?f&&u(a.get("length")):(u(a.get(fn)),Gt(e)&&u(a.get(To)));break;case"delete":d||(u(a.get(fn)),Gt(e)&&u(a.get(To)));break;case"set":Gt(e)&&u(a.get(fn));break}}Zo()}function $n(e){const t=ve(e);return t===e||(Be(t,"iterate",es),ut(e))?t:Pt(e)?Xt(e)?t.map(n=>Qt(dt(n))):t.map(Qt):t.map(dt)}function Ys(e){return Be(e=ve(e),"iterate",es),e}function St(e,t){return Pt(e)?Qt(Xt(e)?dt(t):t):dt(t)}const pr={__proto__:null,[Symbol.iterator](){return uo(this,Symbol.iterator,e=>St(this,e))},concat(...e){return $n(this).concat(...e.map(t=>oe(t)?$n(t):t))},entries(){return uo(this,"entries",e=>(e[1]=St(this,e[1]),e))},every(e,t){return Ot(this,"every",e,t,void 0,arguments)},filter(e,t){return Ot(this,"filter",e,t,n=>n.map(s=>St(this,s)),arguments)},find(e,t){return Ot(this,"find",e,t,n=>St(this,n),arguments)},findIndex(e,t){return Ot(this,"findIndex",e,t,void 0,arguments)},findLast(e,t){return Ot(this,"findLast",e,t,n=>St(this,n),arguments)},findLastIndex(e,t){return Ot(this,"findLastIndex",e,t,void 0,arguments)},forEach(e,t){return Ot(this,"forEach",e,t,void 0,arguments)},includes(...e){return co(this,"includes",e)},indexOf(...e){return co(this,"indexOf",e)},join(e){return $n(this).join(e)},lastIndexOf(...e){return co(this,"lastIndexOf",e)},map(e,t){return Ot(this,"map",e,t,void 0,arguments)},pop(){return Nn(this,"pop")},push(...e){return Nn(this,"push",e)},reduce(e,...t){return xi(this,"reduce",e,t)},reduceRight(e,...t){return xi(this,"reduceRight",e,t)},shift(){return Nn(this,"shift")},some(e,t){return Ot(this,"some",e,t,void 0,arguments)},splice(...e){return Nn(this,"splice",e)},toReversed(){return $n(this).toReversed()},toSorted(e){return $n(this).toSorted(e)},toSpliced(...e){return $n(this).toSpliced(...e)},unshift(...e){return Nn(this,"unshift",e)},values(){return uo(this,"values",e=>St(this,e))}};function uo(e,t,n){const s=Ys(e),o=s[t]();return s!==e&&!ut(e)&&(o._next=o.next,o.next=()=>{const l=o._next();return l.done||(l.value=n(l.value)),l}),o}const hr=Array.prototype;function Ot(e,t,n,s,o,l){const a=Ys(e),u=a!==e&&!ut(e),d=a[t];if(d!==hr[t]){const h=d.apply(e,l);return u?dt(h):h}let f=n;a!==e&&(u?f=function(h,b){return n.call(this,St(e,h),b,e)}:n.length>2&&(f=function(h,b){return n.call(this,h,b,e)}));const r=d.call(a,f,s);return u&&o?o(r):r}function xi(e,t,n,s){const o=Ys(e),l=o!==e&&!ut(e);let a=n,u=!1;o!==e&&(l?(u=s.length===0,a=function(f,r,h){return u&&(u=!1,f=St(e,f)),n.call(this,f,St(e,r),h,e)}):n.length>3&&(a=function(f,r,h){return n.call(this,f,r,h,e)}));const d=o[t](a,...s);return u?St(e,d):d}function co(e,t,n){const s=ve(e);Be(s,"iterate",es);const o=s[t](...n);return(o===-1||o===!1)&&si(n[0])?(n[0]=ve(n[0]),s[t](...n)):o}function Nn(e,t,n=[]){Ht(),Xo();const s=ve(e)[t].apply(e,n);return Zo(),Ut(),s}const vr=Yo("__proto__,__v_isRef,__isVue"),Ml=new Set(Object.getOwnPropertyNames(Symbol).filter(e=>e!=="arguments"&&e!=="caller").map(e=>Symbol[e]).filter(Et));function mr(e){Et(e)||(e=String(e));const t=ve(this);return Be(t,"has",e),t.hasOwnProperty(e)}class Ll{constructor(t=!1,n=!1){this._isReadonly=t,this._isShallow=n}get(t,n,s){if(n==="__v_skip")return t.__v_skip;const o=this._isReadonly,l=this._isShallow;if(n==="__v_isReactive")return!o;if(n==="__v_isReadonly")return o;if(n==="__v_isShallow")return l;if(n==="__v_raw")return s===(o?l?Cr:Rl:l?Nl:Fl).get(t)||Object.getPrototypeOf(t)===Object.getPrototypeOf(s)?t:void 0;const a=oe(t);if(!o){let d;if(a&&(d=pr[n]))return d;if(n==="hasOwnProperty")return mr}const u=Reflect.get(t,n,We(t)?t:s);if((Et(n)?Ml.has(n):vr(n))||(o||Be(t,"get",n),l))return u;if(We(u)){const d=a&&Jo(n)?u:u.value;return o&&Se(d)?Po(d):d}return Se(u)?o?Po(u):bn(u):u}}class Dl extends Ll{constructor(t=!1){super(!1,t)}set(t,n,s,o){let l=t[n];const a=oe(t)&&Jo(n);if(!this._isShallow){const f=Pt(l);if(!ut(s)&&!Pt(s)&&(l=ve(l),s=ve(s)),!a&&We(l)&&!We(s))return f||(l.value=s),!0}const u=a?Number(n)e,ys=e=>Reflect.getPrototypeOf(e);function _r(e,t,n){return function(...s){const o=this.__v_raw,l=ve(o),a=Gt(l),u=e==="entries"||e===Symbol.iterator&&a,d=e==="keys"&&a,f=o[e](...s),r=n?Eo:t?Qt:dt;return!t&&Be(l,"iterate",d?To:fn),je(Object.create(f),{next(){const{value:h,done:b}=f.next();return b?{value:h,done:b}:{value:u?[r(h[0]),r(h[1])]:r(h),done:b}}})}}function gs(e){return function(...t){return e==="delete"?!1:e==="clear"?void 0:this}}function kr(e,t){const n={get(o){const l=this.__v_raw,a=ve(l),u=ve(o);e||(At(o,u)&&Be(a,"get",o),Be(a,"get",u));const{has:d}=ys(a),f=t?Eo:e?Qt:dt;if(d.call(a,o))return f(l.get(o));if(d.call(a,u))return f(l.get(u));l!==a&&l.get(o)},get size(){const o=this.__v_raw;return!e&&Be(ve(o),"iterate",fn),o.size},has(o){const l=this.__v_raw,a=ve(l),u=ve(o);return e||(At(o,u)&&Be(a,"has",o),Be(a,"has",u)),o===u?l.has(o):l.has(o)||l.has(u)},forEach(o,l){const a=this,u=a.__v_raw,d=ve(u),f=t?Eo:e?Qt:dt;return!e&&Be(d,"iterate",fn),u.forEach((r,h)=>o.call(l,f(r),f(h),a))}};return je(n,e?{add:gs("add"),set:gs("set"),delete:gs("delete"),clear:gs("clear")}:{add(o){const l=ve(this),a=ys(l),u=ve(o),d=!t&&!ut(o)&&!Pt(o)?u:o;return a.has.call(l,d)||At(o,d)&&a.has.call(l,o)||At(u,d)&&a.has.call(l,u)||(l.add(d),Dt(l,"add",d,d)),this},set(o,l){!t&&!ut(l)&&!Pt(l)&&(l=ve(l));const a=ve(this),{has:u,get:d}=ys(a);let f=u.call(a,o);f||(o=ve(o),f=u.call(a,o));const r=d.call(a,o);return a.set(o,l),f?At(l,r)&&Dt(a,"set",o,l):Dt(a,"add",o,l),this},delete(o){const l=ve(this),{has:a,get:u}=ys(l);let d=a.call(l,o);d||(o=ve(o),d=a.call(l,o)),u&&u.call(l,o);const f=l.delete(o);return d&&Dt(l,"delete",o,void 0),f},clear(){const o=ve(this),l=o.size!==0,a=o.clear();return l&&Dt(o,"clear",void 0,void 0),a}}),["keys","values","entries",Symbol.iterator].forEach(o=>{n[o]=_r(o,e,t)}),n}function ti(e,t){const n=kr(e,t);return(s,o,l)=>o==="__v_isReactive"?!e:o==="__v_isReadonly"?e:o==="__v_raw"?s:Reflect.get(be(n,o)&&o in s?n:s,o,l)}const $r={get:ti(!1,!1)},xr={get:ti(!1,!0)},Sr={get:ti(!0,!1)};const Fl=new WeakMap,Nl=new WeakMap,Rl=new WeakMap,Cr=new WeakMap;function Ar(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}function bn(e){return Pt(e)?e:ni(e,!1,gr,$r,Fl)}function Tr(e){return ni(e,!1,wr,xr,Nl)}function Po(e){return ni(e,!0,br,Sr,Rl)}function ni(e,t,n,s,o){if(!Se(e)||e.__v_raw&&!(t&&e.__v_isReactive)||e.__v_skip||!Object.isExtensible(e))return e;const l=o.get(e);if(l)return l;const a=Ar(Za(e));if(a===0)return e;const u=new Proxy(e,a===2?s:n);return o.set(e,u),u}function Xt(e){return Pt(e)?Xt(e.__v_raw):!!(e&&e.__v_isReactive)}function Pt(e){return!!(e&&e.__v_isReadonly)}function ut(e){return!!(e&&e.__v_isShallow)}function si(e){return e?!!e.__v_raw:!1}function ve(e){const t=e&&e.__v_raw;return t?ve(t):e}function Er(e){return!be(e,"__v_skip")&&Object.isExtensible(e)&&_l(e,"__v_skip",!0),e}const dt=e=>Se(e)?bn(e):e,Qt=e=>Se(e)?Po(e):e;function We(e){return e?e.__v_isRef===!0:!1}function pe(e){return Pr(e,!1)}function Pr(e,t){return We(e)?e:new Or(e,t)}class Or{constructor(t,n){this.dep=new ei,this.__v_isRef=!0,this.__v_isShallow=!1,this._rawValue=n?t:ve(t),this._value=n?t:dt(t),this.__v_isShallow=n}get value(){return this.dep.track(),this._value}set value(t){const n=this._rawValue,s=this.__v_isShallow||ut(t)||Pt(t);t=s?t:ve(t),At(t,n)&&(this._rawValue=t,this._value=s?t:dt(t),this.dep.trigger())}}function y(e){return We(e)?e.value:e}const Ir={get:(e,t,n)=>t==="__v_raw"?e:y(Reflect.get(e,t,n)),set:(e,t,n,s)=>{const o=e[t];return We(o)&&!We(n)?(o.value=n,!0):Reflect.set(e,t,n,s)}};function Hl(e){return Xt(e)?e:new Proxy(e,Ir)}class Mr{constructor(t,n,s){this.fn=t,this.setter=n,this._value=void 0,this.dep=new ei(this),this.__v_isRef=!0,this.deps=void 0,this.depsTail=void 0,this.flags=16,this.globalVersion=Qn-1,this.next=void 0,this.effect=this,this.__v_isReadonly=!n,this.isSSR=s}notify(){if(this.flags|=16,!(this.flags&8)&&Te!==this)return Al(this,!0),!0}get value(){const t=this.dep.track();return Pl(this),t&&(t.version=this.dep.version),this._value}set value(t){this.setter&&this.setter(t)}}function Lr(e,t,n=!1){let s,o;return ue(e)?s=e:(s=e.get,o=e.set),new Mr(s,o,n)}const bs={},Ts=new WeakMap;let un;function Dr(e,t=!1,n=un){if(n){let s=Ts.get(n);s||Ts.set(n,s=[]),s.push(e)}}function Fr(e,t,n=Ae){const{immediate:s,deep:o,once:l,scheduler:a,augmentJob:u,call:d}=n,f=S=>o?S:ut(S)||o===!1||o===0?Ft(S,1):Ft(S);let r,h,b,k,R=!1,C=!1;if(We(e)?(h=()=>e.value,R=ut(e)):Xt(e)?(h=()=>f(e),R=!0):oe(e)?(C=!0,R=e.some(S=>Xt(S)||ut(S)),h=()=>e.map(S=>{if(We(S))return S.value;if(Xt(S))return f(S);if(ue(S))return d?d(S,2):S()})):ue(e)?t?h=d?()=>d(e,2):e:h=()=>{if(b){Ht();try{b()}finally{Ut()}}const S=un;un=r;try{return d?d(e,3,[k]):e(k)}finally{un=S}}:h=Tt,t&&o){const S=h,V=o===!0?1/0:o;h=()=>Ft(S(),V)}const x=cr(),P=()=>{r.stop(),x&&x.active&&zo(x.effects,r)};if(l&&t){const S=t;t=(...V)=>{const I=S(...V);return P(),I}}let E=C?new Array(e.length).fill(bs):bs;const W=S=>{if(!(!(r.flags&1)||!r.dirty&&!S))if(t){const V=r.run();if(S||o||R||(C?V.some((I,T)=>At(I,E[T])):At(V,E))){b&&b();const I=un;un=r;try{const T=[V,E===bs?void 0:C&&E[0]===bs?[]:E,k];E=V,d?d(t,3,T):t(...T)}finally{un=I}}}else r.run()};return u&&u(W),r=new Sl(h),r.scheduler=a?()=>a(W,!1):W,k=S=>Dr(S,!1,r),b=r.onStop=()=>{const S=Ts.get(r);if(S){if(d)d(S,4);else for(const V of S)V();Ts.delete(r)}},t?s?W(!0):E=r.run():a?a(W.bind(null,!0),!0):r.run(),P.pause=r.pause.bind(r),P.resume=r.resume.bind(r),P.stop=P,P}function Ft(e,t=1/0,n){if(t<=0||!Se(e)||e.__v_skip||(n=n||new Map,(n.get(e)||0)>=t))return e;if(n.set(e,t),t--,We(e))Ft(e.value,t,n);else if(oe(e))for(let s=0;s{Ft(s,t,n)});else if(wl(e)){for(const s in e)Ft(e[s],t,n);for(const s of Object.getOwnPropertySymbols(e))Object.prototype.propertyIsEnumerable.call(e,s)&&Ft(e[s],t,n)}return e}function fs(e,t,n,s){try{return s?e(...s):e()}catch(o){zs(o,t,n)}}function yt(e,t,n,s){if(ue(e)){const o=fs(e,t,n,s);return o&&gl(o)&&o.catch(l=>{zs(l,t,n)}),o}if(oe(e)){const o=[];for(let l=0;l>>1,o=Ye[s],l=ts(o);l=ts(n)?Ye.push(e):Ye.splice(Rr(t),0,e),e.flags|=1,jl()}}function jl(){Es||(Es=Ul.then(Vl))}function Hr(e){if(!oe(e))Yt&&e.id===-1?Yt.splice(xn+1,0,e):e.flags&1||(Cn.push(e),e.flags|=1);else for(let t=0;tts(n)-ts(s));if(Cn.length=0,Yt){for(let n=0;ne.id==null?e.flags&2?-1:1/0:e.id;function Vl(e){try{for($t=0;$t{s._d&&Ni(-1);const l=Ps(t),a=hn.length;let u;try{u=e(...o)}finally{for(let d=hn.length;d>a;d--)va();Ps(l),s._d&&Ni(1)}return u};return s._n=!0,s._c=!0,s._d=!0,s}function we(e,t){if(nt===null)return e;const n=eo(nt),s=e.dirs||(e.dirs=[]);for(let o=0;o1)return n&&ue(t)?t.call(s&&s.proxy):t}}const Br=Symbol.for("v-scx"),Vr=()=>$s(Br);function pn(e,t,n){return Kl(e,t,n)}function Kl(e,t,n=Ae){const{immediate:s,deep:o,flush:l,once:a}=n,u=je({},n),d=t&&s||!t&&l!=="post";let f;if(os){if(l==="sync"){const k=Vr();f=k.__watcherHandles||(k.__watcherHandles=[])}else if(!d){const k=()=>{};return k.stop=Tt,k.resume=Tt,k.pause=Tt,k}}const r=Ve;u.call=(k,R,C)=>yt(k,r,R,C);let h=!1;l==="post"?u.scheduler=k=>{Ge(k,r&&r.suspense)}:l!=="sync"&&(h=!0,u.scheduler=(k,R)=>{R?k():oi(k)}),u.augmentJob=k=>{t&&(k.flags|=4),h&&(k.flags|=2,r&&(k.id=r.uid,k.i=r))};const b=Fr(e,t,u);return os&&(f?f.push(b):d&&b()),b}function Wr(e,t,n){const s=this.proxy,o=Ie(e)?e.includes(".")?ql(s,e):()=>s[e]:e.bind(s,s);let l;ue(t)?l=t:(l=t.handler,n=t);const a=ps(this),u=Kl(o,l.bind(s),n);return a(),u}function ql(e,t){const n=t.split(".");return()=>{let s=e;for(let o=0;oe.__isTeleport,fo=Symbol("_leaveCb");function qr(e){let t=e[0];if(e.length>1){for(const n of e)if(n.type!==jt){t=n;break}}return t}function Yl(e){if(!li(e))return Gs(e.type)&&e.children?qr(e.children):e;if(e.component)return e.component.subTree;const{shapeFlag:t,children:n}=e;if(n){if(t&16)return n[0];if(t&32&&ue(n.default))return n.default()}}function ii(e,t){if(e.shapeFlag&6&&e.component){e.transition=t;const n=e.component.subTree;ii(Gs(n.type)&&Yl(n)||n,t)}else e.shapeFlag&128?(e.ssContent.transition=t.clone(e.ssContent),e.ssFallback.transition=t.clone(e.ssFallback)):e.transition=t}function Ee(e,t){return ue(e)?je({name:e.name},t,{setup:e}):e}function zl(e){e.ids=[e.ids[0]+e.ids[2]+++"-",0,0]}function Ci(e,t){let n;return!!((n=Object.getOwnPropertyDescriptor(e,t))&&!n.configurable)}const Os=new WeakMap;function Gn(e,t,n,s,o=!1){if(oe(e)){e.forEach((C,x)=>Gn(C,t&&(oe(t)?t[x]:t),n,s,o));return}if(Xn(s)&&!o){s.shapeFlag&512&&s.type.__asyncResolved&&s.component.subTree.component&&Gn(e,t,n,s.component.subTree);return}const l=s.shapeFlag&4?eo(s.component):s.el,a=o?null:l,{i:u,r:d}=e,f=t&&t.r,r=u.refs===Ae?u.refs={}:u.refs,h=u.setupState,b=ve(h),k=h===Ae?yl:C=>Ci(r,C)?!1:be(b,C),R=(C,x)=>!(x&&Ci(r,x));if(f!=null&&f!==d){if(Ai(t),Ie(f))r[f]=null,k(f)&&(h[f]=null);else if(We(f)){const C=t;R(f,C.k)&&(f.value=null),C.k&&(r[C.k]=null)}}if(ue(d))fs(d,u,12,[a,r]);else{const C=Ie(d),x=We(d);if(C||x){const P=()=>{if(e.f){const E=C?k(d)?h[d]:r[d]:R()||!e.k?d.value:r[e.k];if(o)oe(E)&&zo(E,l);else if(oe(E))E.includes(l)||E.push(l);else if(C)r[d]=[l],k(d)&&(h[d]=r[d]);else{const W=[l];R(d,e.k)&&(d.value=W),e.k&&(r[e.k]=W)}}else C?(r[d]=a,k(d)&&(h[d]=a)):x&&(R(d,e.k)&&(d.value=a),e.k&&(r[e.k]=a))};if(a){const E=()=>{P(),Os.delete(e)};E.id=-1,Os.set(e,E),Ge(E,n)}else Ai(e),P()}}}function Ai(e){const t=Os.get(e);t&&(t.flags|=8,Os.delete(e))}qs().requestIdleCallback;qs().cancelIdleCallback;const Xn=e=>!!e.type.__asyncLoader,li=e=>e.type.__isKeepAlive;function Yr(e,t){Jl(e,"a",t)}function zr(e,t){Jl(e,"da",t)}function Jl(e,t,n=Ve){const s=e.__wdc||(e.__wdc=()=>{let o=n;for(;o;){if(o.isDeactivated)return;o=o.parent}return e()});if(Xs(t,s,n),n){let o=n.parent;for(;o&&o.parent;)li(o.parent.vnode)&&Jr(s,t,n,o),o=o.parent}}function Jr(e,t,n,s){const o=Xs(t,e,s,!0);Gl(()=>{zo(s[t],o)},n)}function Xs(e,t,n=Ve,s=!1){if(n){const o=n[e]||(n[e]=[]),l=t.__weh||(t.__weh=(...a)=>{Ht();const u=ps(n),d=yt(t,n,e,a);return u(),Ut(),d});return s?o.unshift(l):o.push(l),l}}const Wt=e=>(t,n=Ve)=>{(!os||e==="sp")&&Xs(e,(...s)=>t(...s),n)},Gr=Wt("bm"),kn=Wt("m"),Xr=Wt("bu"),Zr=Wt("u"),In=Wt("bum"),Gl=Wt("um"),Qr=Wt("sp"),eu=Wt("rtg"),tu=Wt("rtc");function nu(e,t=Ve){Xs("ec",e,t)}const su="components",Xl=Symbol.for("v-ndc");function ou(e){return Ie(e)?iu(su,e,!1)||e:e||Xl}function iu(e,t,n=!0,s=!1){const o=nt||Ve;if(o){const l=o.type;{const u=Vu(l,!1);if(u&&(u===t||u===ze(t)||u===Ws(ze(t))))return l}const a=Ti(o[e]||l[e],t)||Ti(o.appContext[e],t);return!a&&s?l:a}}function Ti(e,t){return e&&(e[t]||e[ze(t)]||e[Ws(ze(t))])}function le(e,t,n,s){let o;const l=n,a=oe(e);if(a||Ie(e)){const u=a&&Xt(e);let d=!1,f=!1;u&&(d=!ut(e),f=Pt(e),e=Ys(e)),o=new Array(e.length);for(let r=0,h=e.length;rt(u,d,void 0,l));else{const u=Object.keys(e);o=new Array(u.length);for(let d=0,f=u.length;de?ba(e)?eo(e):Oo(e.parent):null,Zn=je(Object.create(null),{$:e=>e,$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs,$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>Oo(e.parent),$root:e=>Oo(e.root),$host:e=>e.ce,$emit:e=>e.emit,$options:e=>Ql(e),$forceUpdate:e=>e.f||(e.f=()=>{oi(e.update)}),$nextTick:e=>e.n||(e.n=Js.bind(e.proxy)),$watch:e=>Wr.bind(e)}),po=(e,t)=>e!==Ae&&!e.__isScriptSetup&&be(e,t),lu={get({_:e},t){if(t==="__v_skip")return!0;const{ctx:n,setupState:s,data:o,props:l,accessCache:a,type:u,appContext:d}=e;if(t[0]!=="$"){const b=a[t];if(b!==void 0)switch(b){case 1:return s[t];case 2:return o[t];case 4:return n[t];case 3:return l[t]}else{if(po(s,t))return a[t]=1,s[t];if(o!==Ae&&be(o,t))return a[t]=2,o[t];if(be(l,t))return a[t]=3,l[t];if(n!==Ae&&be(n,t))return a[t]=4,n[t];Io&&(a[t]=0)}}const f=Zn[t];let r,h;if(f)return t==="$attrs"&&Be(e.attrs,"get",""),f(e);if((r=u.__cssModules)&&(r=r[t]))return r;if(n!==Ae&&be(n,t))return a[t]=4,n[t];if(h=d.config.globalProperties,be(h,t))return h[t]},set({_:e},t,n){const{data:s,setupState:o,ctx:l}=e;return po(o,t)?(o[t]=n,!0):s!==Ae&&be(s,t)?(s[t]=n,!0):be(e.props,t)||t[0]==="$"&&t.slice(1)in e?!1:(l[t]=n,!0)},has({_:{data:e,setupState:t,accessCache:n,ctx:s,appContext:o,props:l,type:a}},u){let d;return!!(n[u]||e!==Ae&&u[0]!=="$"&&be(e,u)||po(t,u)||be(l,u)||be(s,u)||be(Zn,u)||be(o.config.globalProperties,u)||(d=a.__cssModules)&&d[u])},defineProperty(e,t,n){return n.get!=null?e._.accessCache[t]=0:be(n,"value")&&this.set(e,t,n.value,null),Reflect.defineProperty(e,t,n)}};function Ei(e){return oe(e)?e.reduce((t,n)=>(t[n]=null,t),{}):e}let Io=!0;function au(e){const t=Ql(e),n=e.proxy,s=e.ctx;Io=!1,t.beforeCreate&&Pi(t.beforeCreate,e,"bc");const{data:o,computed:l,methods:a,watch:u,provide:d,inject:f,created:r,beforeMount:h,mounted:b,beforeUpdate:k,updated:R,activated:C,deactivated:x,beforeDestroy:P,beforeUnmount:E,destroyed:W,unmounted:S,render:V,renderTracked:I,renderTriggered:T,errorCaptured:w,serverPrefetch:A,expose:te,inheritAttrs:$e,components:Me,directives:X,filters:j}=t;if(f&&ru(f,s,null),a)for(const de in a){const he=a[de];ue(he)&&(s[de]=he.bind(n))}if(o){const de=o.call(n,n);Se(de)&&(e.data=bn(de))}if(Io=!0,l)for(const de in l){const he=l[de],Q=ue(he)?he.bind(n,n):ue(he.get)?he.get.bind(n,n):Tt,q=!ue(he)&&ue(he.set)?he.set.bind(n):Tt,B=$({get:Q,set:q});Object.defineProperty(s,de,{enumerable:!0,configurable:!0,get:()=>B.value,set:ne=>B.value=ne})}if(u)for(const de in u)Zl(u[de],s,n,de);if(d){const de=ue(d)?d.call(n):d;Reflect.ownKeys(de).forEach(he=>{jr(he,de[he])})}r&&Pi(r,e,"c");function ce(de,he){oe(he)?he.forEach(Q=>de(Q.bind(n))):he&&de(he.bind(n))}if(ce(Gr,h),ce(kn,b),ce(Xr,k),ce(Zr,R),ce(Yr,C),ce(zr,x),ce(nu,w),ce(tu,I),ce(eu,T),ce(In,E),ce(Gl,S),ce(Qr,A),oe(te))if(te.length){const de=e.exposed||(e.exposed={});te.forEach(he=>{Object.defineProperty(de,he,{get:()=>n[he],set:Q=>n[he]=Q,enumerable:!0})})}else e.exposed||(e.exposed={});V&&e.render===Tt&&(e.render=V),$e!=null&&(e.inheritAttrs=$e),Me&&(e.components=Me),X&&(e.directives=X),A&&zl(e)}function ru(e,t,n=Tt){oe(e)&&(e=Mo(e));for(const s in e){const o=e[s];let l;Se(o)?"default"in o?l=$s(o.from||s,o.default,!0):l=$s(o.from||s):l=$s(o),We(l)?Object.defineProperty(t,s,{enumerable:!0,configurable:!0,get:()=>l.value,set:a=>l.value=a}):t[s]=l}}function Pi(e,t,n){yt(oe(e)?e.map(s=>s.bind(t.proxy)):e.bind(t.proxy),t,n)}function Zl(e,t,n,s){let o=s.includes(".")?ql(n,s):()=>n[s];if(Ie(e)){const l=t[e];ue(l)&&pn(o,l)}else if(ue(e))pn(o,e.bind(n));else if(Se(e))if(oe(e))e.forEach(l=>Zl(l,t,n,s));else{const l=ue(e.handler)?e.handler.bind(n):t[e.handler];ue(l)&&pn(o,l,e)}}function Ql(e){const t=e.type,{mixins:n,extends:s}=t,{mixins:o,optionsCache:l,config:{optionMergeStrategies:a}}=e.appContext,u=l.get(t);let d;return u?d=u:!o.length&&!n&&!s?d=t:(d={},o.length&&o.forEach(f=>Is(d,f,a,!0)),Is(d,t,a)),Se(t)&&l.set(t,d),d}function Is(e,t,n,s=!1){const{mixins:o,extends:l}=t;l&&Is(e,l,n,!0),o&&o.forEach(a=>Is(e,a,n,!0));for(const a in t)if(!(s&&a==="expose")){const u=uu[a]||n&&n[a];e[a]=u?u(e[a],t[a]):t[a]}return e}const uu={data:Oi,props:Ii,emits:Ii,methods:jn,computed:jn,beforeCreate:Ke,created:Ke,beforeMount:Ke,mounted:Ke,beforeUpdate:Ke,updated:Ke,beforeDestroy:Ke,beforeUnmount:Ke,destroyed:Ke,unmounted:Ke,activated:Ke,deactivated:Ke,errorCaptured:Ke,serverPrefetch:Ke,components:jn,directives:jn,watch:du,provide:Oi,inject:cu};function Oi(e,t){return t?e?function(){return je(ue(e)?e.call(this,this):e,ue(t)?t.call(this,this):t)}:t:e}function cu(e,t){return jn(Mo(e),Mo(t))}function Mo(e){if(oe(e)){const t={};for(let n=0;nt==="modelValue"||t==="model-value"?e.modelModifiers:e[`${t}Modifiers`]||e[`${ze(t)}Modifiers`]||e[`${on(t)}Modifiers`];function vu(e,t,...n){if(e.isUnmounted)return;const s=e.vnode.props||Ae;let o=n;const l=t.startsWith("update:"),a=l&&hu(s,t.slice(7));a&&(a.trim&&(o=n.map(r=>Ie(r)?r.trim():r)),a.number&&(o=o.map(Ks)));let u,d=s[u=lo(t)]||s[u=lo(ze(t))];!d&&l&&(d=s[u=lo(on(t))]),d&&yt(d,e,6,o);const f=s[u+"Once"];if(f){if(!e.emitted)e.emitted={};else if(e.emitted[u])return;e.emitted[u]=!0,yt(f,e,6,o)}}const mu=new WeakMap;function ta(e,t,n=!1){const s=n?mu:t.emitsCache,o=s.get(e);if(o!==void 0)return o;const l=e.emits;let a={},u=!1;if(!ue(e)){const d=f=>{const r=ta(f,t,!0);r&&(u=!0,je(a,r))};!n&&t.mixins.length&&t.mixins.forEach(d),e.extends&&d(e.extends),e.mixins&&e.mixins.forEach(d)}return!l&&!u?(Se(e)&&s.set(e,null),null):(oe(l)?l.forEach(d=>a[d]=null):je(a,l),Se(e)&&s.set(e,a),a)}function Zs(e,t){return!e||!js(t)?!1:(t=t.slice(2),t=t==="Once"?t:t.replace(/Once$/,""),be(e,t[0].toLowerCase()+t.slice(1))||be(e,on(t))||be(e,t))}function Mi(e){const{type:t,vnode:n,proxy:s,withProxy:o,propsOptions:[l],slots:a,attrs:u,emit:d,render:f,renderCache:r,props:h,data:b,setupState:k,ctx:R,inheritAttrs:C}=e,x=Ps(e);let P,E;try{if(n.shapeFlag&4){const S=o||s,V=S;P=Ct(f.call(V,S,r,h,k,b,R)),E=u}else{const S=t;P=Ct(S.length>1?S(h,{attrs:u,slots:a,emit:d}):S(h,null)),E=t.props?u:yu(u)}}catch(S){hn.length=0,zs(S,e,1),P=_e(jt)}let W=P;if(E&&C!==!1){const S=Object.keys(E),{shapeFlag:V}=W;S.length&&V&7&&(l&&S.some(Bs)&&(E=gu(E,l)),W=Pn(W,E,!1,!0))}if(n.dirs&&(W=Pn(W,null,!1,!0),W.dirs=W.dirs?W.dirs.concat(n.dirs):n.dirs),n.transition){const S=Gs(W.type)&&Yl(W)||W;ii(S,n.transition)}return P=W,Ps(x),P}const yu=e=>{let t;for(const n in e)(n==="class"||n==="style"||js(n))&&((t||(t={}))[n]=e[n]);return t},gu=(e,t)=>{const n={};for(const s in e)(!Bs(s)||!(s.slice(9)in t))&&(n[s]=e[s]);return n};function bu(e,t,n){const{props:s,children:o,component:l}=e,{props:a,children:u,patchFlag:d}=t,f=l.emitsOptions;if(t.dirs||t.transition)return!0;if(n&&d>=0){if(d&1024)return!0;if(d&16)return s?Li(s,a,f):!!a;if(d&8){const r=t.dynamicProps;for(let h=0;hObject.create(sa),ia=e=>Object.getPrototypeOf(e)===sa;function _u(e,t,n,s=!1){const o={},l=oa();e.propsDefaults=Object.create(null),la(e,t,o,l);for(const a in e.propsOptions[0])a in o||(o[a]=void 0);n?e.props=s?o:Tr(o):e.type.props?e.props=o:e.props=l,e.attrs=l}function ku(e,t,n,s){const{props:o,attrs:l,vnode:{patchFlag:a}}=e,u=ve(o),[d]=e.propsOptions;let f=!1;if((s||a>0)&&!(a&16)){if(a&8){const r=e.vnode.dynamicProps;for(let h=0;h{d=!0;const[b,k]=aa(h,t,!0);je(a,b),k&&u.push(...k)};!n&&t.mixins.length&&t.mixins.forEach(r),e.extends&&r(e.extends),e.mixins&&e.mixins.forEach(r)}if(!l&&!d)return Se(e)&&s.set(e,dn),dn;if(oe(l))for(let r=0;re==="_"||e==="_ctx"||e==="$stable",ri=e=>oe(e)?e.map(Ct):[Ct(e)],xu=(e,t,n)=>{if(t._n)return t;const s=Ur((...o)=>ri(t(...o)),n);return s._c=!1,s},ra=(e,t,n)=>{const s=e._ctx;for(const o in e){if(ai(o))continue;const l=e[o];if(ue(l))t[o]=xu(o,l,s);else if(l!=null){const a=ri(l);t[o]=()=>a}}},ua=(e,t)=>{const n=ri(t);e.slots.default=()=>n},ca=(e,t,n)=>{for(const s in t)(n||!ai(s))&&(e[s]=t[s])},Su=(e,t,n)=>{const s=e.slots=oa();if(e.vnode.shapeFlag&32){const o=t._;o?(ca(s,t,n),n&&_l(s,"_",o,!0)):ra(t,s)}else t&&ua(e,t)},Cu=(e,t,n)=>{const{vnode:s,slots:o}=e;let l=!0,a=Ae;if(s.shapeFlag&32){const u=t._;u?n&&u===1?l=!1:ca(o,t,n):(l=!t.$stable,ra(t,o)),a=t}else t&&(ua(e,t),a={default:1});if(l)for(const u in o)!ai(u)&&a[u]==null&&delete o[u]},Ge=Ou;function Au(e){return Tu(e)}function Tu(e,t){const n=qs();n.__VUE__=!0;const{insert:s,remove:o,patchProp:l,createElement:a,createText:u,createComment:d,setText:f,setElementText:r,parentNode:h,nextSibling:b,setScopeId:k=Tt,insertStaticContent:R}=e,C=(g,_,O,H=null,M=null,D=null,z=void 0,Y=null,K=!!_.dynamicChildren)=>{if(g===_)return;g&&!Rn(g,_)&&(H=fe(g),ne(g,M,D,!0),g=null),_.patchFlag===-2&&(K=!1,_.dynamicChildren=null),_.dynamicChildren&&g&&g.dynamicChildren&&g.dynamicChildren.hasOnce&&(_.dynamicChildren===dn&&(_.dynamicChildren=[]),_.dynamicChildren.hasOnce=!0);const{type:L,ref:se,shapeFlag:G}=_;switch(L){case Qs:x(g,_,O,H);break;case jt:P(g,_,O,H);break;case xs:g==null&&E(_,O,H,z);break;case U:Me(g,_,O,H,M,D,z,Y,K);break;default:G&1?V(g,_,O,H,M,D,z,Y,K):G&6?X(g,_,O,H,M,D,z,Y,K):(G&64||G&128)&&L.process(g,_,O,H,M,D,z,Y,K,ln)}se!=null&&M?Gn(se,g&&g.ref,D,_||g,!_):se==null&&g&&g.ref!=null&&Gn(g.ref,null,D,g,!0)},x=(g,_,O,H)=>{if(g==null)s(_.el=u(_.children),O,H);else{const M=_.el=g.el;_.children!==g.children&&f(M,_.children)}},P=(g,_,O,H)=>{g==null?s(_.el=d(_.children||""),O,H):_.el=g.el},E=(g,_,O,H)=>{[g.el,g.anchor]=R(g.children,_,O,H,g.el,g.anchor)},W=({el:g,anchor:_},O,H)=>{let M;for(;g&&g!==_;)M=b(g),s(g,O,H),g=M;s(_,O,H)},S=({el:g,anchor:_})=>{let O;for(;g&&g!==_;)O=b(g),o(g),g=O;o(_)},V=(g,_,O,H,M,D,z,Y,K)=>{if(_.type==="svg"?z="svg":_.type==="math"&&(z="mathml"),g==null)I(_,O,H,M,D,z,Y,K);else{const L=g.el&&g.el._isVueCE?g.el:null;try{L&&L._beginPatch(),A(g,_,M,D,z,Y,K)}finally{L&&L._endPatch()}}},I=(g,_,O,H,M,D,z,Y)=>{let K,L;const{props:se,shapeFlag:G,transition:ee,dirs:ae}=g;if(K=g.el=a(g.type,D,se&&se.is,se),G&8?r(K,g.children):G&16&&w(g.children,K,null,H,M,ho(g,D),z,Y),ae&&an(g,null,H,"created"),T(K,g,g.scopeId,z,H),se){for(const Ce in se)Ce!=="value"&&!Yn(Ce)&&l(K,Ce,null,se[Ce],D,H);"value"in se&&l(K,"value",null,se.value,D),(L=se.onVnodeBeforeMount)&&kt(L,H,g)}ae&&an(g,null,H,"beforeMount");const ye=Eu(M,ee);ye&&ee.beforeEnter(K),s(K,_,O),((L=se&&se.onVnodeMounted)||ye||ae)&&Ge(()=>{L&&kt(L,H,g),ye&&ee.enter(K),ae&&an(g,null,H,"mounted")},M)},T=(g,_,O,H,M)=>{if(O&&k(g,O),H)for(let D=0;D{for(let L=K;L{const Y=_.el=g.el;let{patchFlag:K,dynamicChildren:L,dirs:se}=_;K|=g.patchFlag&16;const G=g.props||Ae,ee=_.props||Ae;let ae;if(O&&rn(O,!1),(ae=ee.onVnodeBeforeUpdate)&&kt(ae,O,_,g),se&&an(_,g,O,"beforeUpdate"),O&&rn(O,!0),L&&(!g.dynamicChildren||g.dynamicChildren.length!==L.length)&&(K=0,z=!1,L=null),(G.innerHTML&&ee.innerHTML==null||G.textContent&&ee.textContent==null)&&r(Y,""),L?te(g.dynamicChildren,L,Y,O,H,ho(_,M),D):z||he(g,_,Y,null,O,H,ho(_,M),D,!1),K>0){if(K&16)$e(Y,G,ee,O,M);else if(K&2&&G.class!==ee.class&&l(Y,"class",null,ee.class,M),K&4&&l(Y,"style",G.style,ee.style,M),K&8){const ye=_.dynamicProps;for(let Ce=0;Ce{ae&&kt(ae,O,_,g),se&&an(_,g,O,"updated")},H)},te=(g,_,O,H,M,D,z)=>{for(let Y=0;Y<_.length;Y++){const K=g[Y],L=_[Y],se=K.el&&(K.type===U||!Rn(K,L)||K.shapeFlag&198)?h(K.el):O;C(K,L,se,null,H,M,D,z,!0)}},$e=(g,_,O,H,M)=>{if(_!==O){if(_!==Ae)for(const D in _)!Yn(D)&&!(D in O)&&l(g,D,_[D],null,M,H);for(const D in O){if(Yn(D))continue;const z=O[D],Y=_[D];z!==Y&&D!=="value"&&l(g,D,Y,z,M,H)}"value"in O&&l(g,"value",_.value,O.value,M)}},Me=(g,_,O,H,M,D,z,Y,K)=>{const L=_.el=g?g.el:u(""),se=_.anchor=g?g.anchor:u("");let{patchFlag:G,dynamicChildren:ee,slotScopeIds:ae}=_;ae&&(Y=Y?Y.concat(ae):ae),g==null?(s(L,O,H),s(se,O,H),w(_.children||[],O,se,M,D,z,Y,K)):G>0&&G&64&&ee&&g.dynamicChildren&&g.dynamicChildren.length===ee.length?(te(g.dynamicChildren,ee,O,M,D,z,Y),(_.key!=null||M&&_===M.subTree)&&da(g,_,!0)):he(g,_,O,se,M,D,z,Y,K)},X=(g,_,O,H,M,D,z,Y,K)=>{_.slotScopeIds=Y,g==null?_.shapeFlag&512?M.ctx.activate(_,O,H,z,K):j(_,O,H,M,D,z,K):J(g,_,K)},j=(g,_,O,H,M,D,z)=>{const Y=g.component=Nu(g,H,M);if(li(g)&&(Y.ctx.renderer=ln),Hu(Y,!1,z),Y.asyncDep){if(M&&M.registerDep(Y,ce,z),!g.el){const K=Y.subTree=_e(jt);P(null,K,_,O),g.placeholder=K.el}}else ce(Y,g,_,O,M,D,z)},J=(g,_,O)=>{const H=_.component=g.component;if(bu(g,_,O))if(H.asyncDep&&!H.asyncResolved){_.el=g.el,de(H,_,O);return}else H.next=_,H.update();else _.el=g.el,H.vnode=_},ce=(g,_,O,H,M,D,z)=>{const Y=()=>{if(g.isMounted){let{next:G,bu:ee,u:ae,parent:ye,vnode:Ce}=g;{const wt=fa(g);if(wt){G&&(G.el=Ce.el,de(g,G,z)),wt.asyncDep.then(()=>{Ge(()=>{g.isUnmounted||L()},M)});return}}let xe=G,De;rn(g,!1),G?(G.el=Ce.el,de(g,G,z)):G=Ce,ee&&ks(ee),(De=G.props&&G.props.onVnodeBeforeUpdate)&&kt(De,ye,G,Ce),rn(g,!0);const Ne=Mi(g),bt=g.subTree;g.subTree=Ne,C(bt,Ne,h(bt.el),fe(bt),g,M,D),G.el=Ne.el,xe===null&&wu(g,Ne.el),ae&&Ge(ae,M),(De=G.props&&G.props.onVnodeUpdated)&&Ge(()=>kt(De,ye,G,Ce),M)}else{let G;const{el:ee,props:ae}=_,{bm:ye,m:Ce,parent:xe,root:De,type:Ne}=g,bt=Xn(_);rn(g,!1),ye&&ks(ye),!bt&&(G=ae&&ae.onVnodeBeforeMount)&&kt(G,xe,_),rn(g,!0);{De.ce&&De.ce._hasShadowRoot()&&De.ce._injectChildStyle(Ne,g.parent?g.parent.type:void 0);const wt=g.subTree=Mi(g);C(null,wt,O,H,g,M,D),_.el=wt.el}if(Ce&&Ge(Ce,M),!bt&&(G=ae&&ae.onVnodeMounted)){const wt=_;Ge(()=>kt(G,xe,wt),M)}(_.shapeFlag&256||xe&&Xn(xe.vnode)&&xe.vnode.shapeFlag&256)&&g.a&&Ge(g.a,M),g.isMounted=!0,_=O=H=null}};g.scope.on();const K=g.effect=new Sl(Y);g.scope.off();const L=g.update=K.run.bind(K),se=g.job=K.runIfDirty.bind(K);se.i=g,se.id=g.uid,K.scheduler=()=>oi(se),rn(g,!0),L()},de=(g,_,O)=>{_.component=g;const H=g.vnode.props;g.vnode=_,g.next=null,ku(g,_.props,H,O),Cu(g,_.children,O),Ht(),Si(g),Ut()},he=(g,_,O,H,M,D,z,Y,K=!1)=>{const L=g&&g.children,se=g?g.shapeFlag:0,G=_.children,{patchFlag:ee,shapeFlag:ae}=_;if(ee>0){if(ee&128){q(L,G,O,H,M,D,z,Y,K);return}else if(ee&256){Q(L,G,O,H,M,D,z,Y,K);return}}ae&8?(se&16&&N(L,M,D),G!==L&&r(O,G)):se&16?ae&16?q(L,G,O,H,M,D,z,Y,K):N(L,M,D,!0):(se&8&&r(O,""),ae&16&&w(G,O,H,M,D,z,Y,K))},Q=(g,_,O,H,M,D,z,Y,K)=>{g=g||dn,_=_||dn;const L=g.length,se=_.length,G=Math.min(L,se);let ee;for(ee=0;eese?N(g,M,D,!0,!1,G):w(_,O,H,M,D,z,Y,K,G)},q=(g,_,O,H,M,D,z,Y,K)=>{let L=0;const se=_.length;let G=g.length-1,ee=se-1;for(;L<=G&&L<=ee;){const ae=g[L],ye=_[L]=K?Mt(_[L]):Ct(_[L]);if(Rn(ae,ye))C(ae,ye,O,null,M,D,z,Y,K);else break;L++}for(;L<=G&&L<=ee;){const ae=g[G],ye=_[ee]=K?Mt(_[ee]):Ct(_[ee]);if(Rn(ae,ye))C(ae,ye,O,null,M,D,z,Y,K);else break;G--,ee--}if(L>G){if(L<=ee){const ae=ee+1,ye=aeee)for(;L<=G;)ne(g[L],M,D,!0),L++;else{const ae=L,ye=L,Ce=new Map;for(L=ye;L<=ee;L++){const et=_[L]=K?Mt(_[L]):Ct(_[L]);et.key!=null&&Ce.set(et.key,L)}let xe,De=0;const Ne=ee-ye+1;let bt=!1,wt=0;const Fn=new Array(Ne);for(L=0;L=Ne){ne(et,M,D,!0);continue}let _t;if(et.key!=null)_t=Ce.get(et.key);else for(xe=ye;xe<=ee;xe++)if(Fn[xe-ye]===0&&Rn(et,_[xe])){_t=xe;break}_t===void 0?ne(et,M,D,!0):(Fn[_t-ye]=L+1,_t>=wt?wt=_t:bt=!0,C(et,_[_t],O,null,M,D,z,Y,K),De++)}const mi=bt?Pu(Fn):dn;for(xe=mi.length-1,L=Ne-1;L>=0;L--){const et=ye+L,_t=_[et],yi=_[et+1],gi=et+1{const{el:D,type:z,transition:Y,children:K,shapeFlag:L}=g;if(L&6){B(g.component.subTree,_,O,H);return}if(L&128){g.suspense.move(_,O,H);return}if(L&64){z.move(g,_,O,ln);return}if(z===U){s(D,_,O);for(let G=0;GY.enter(D),M));else{const{leave:G,delayLeave:ee,afterLeave:ae}=Y,ye=()=>{g.ctx.isUnmounted?o(D):s(D,_,O)},Ce=()=>{const xe=D._isLeaving||!!D[fo];D._isLeaving&&D[fo](!0),Y.persisted&&!xe?ye():G(D,()=>{ye(),ae&&ae()})};ee?ee(D,ye,Ce):Ce()}else s(D,_,O)},ne=(g,_,O,H=!1,M=!1)=>{const{type:D,props:z,ref:Y,children:K,dynamicChildren:L,shapeFlag:se,patchFlag:G,dirs:ee,cacheIndex:ae,memo:ye}=g;if((G===-2||L&&L.hasOnce)&&(M=!1),Y!=null&&(Ht(),Gn(Y,null,O,g,!0),Ut()),ae!=null&&(!g.ctx||g.ctx===_)&&(_.renderCache[ae]=void 0),se&256){_.ctx.deactivate(g);return}const Ce=se&1&&ee,xe=!Xn(g);let De;if(xe&&(De=z&&z.onVnodeBeforeUnmount)&&kt(De,_,g),se&6)me(g.component,O,H);else{if(se&128){g.suspense.unmount(O,H);return}Ce&&an(g,null,_,"beforeUnmount"),se&64?g.type.remove(g,_,O,ln,H):L&&!L.hasOnce&&(D!==U||G>0&&G&64)?N(L,_,O,!1,!0):(D===U&&G&384||!M&&se&16)&&N(K,_,O),H&&Qe(g)}const Ne=ye!=null&&ae==null;(xe&&(De=z&&z.onVnodeUnmounted)||Ce||Ne)&&Ge(()=>{De&&kt(De,_,g),Ce&&an(g,null,_,"unmounted"),Ne&&(g.el=null)},O)},Qe=g=>{const{type:_,el:O,anchor:H,transition:M}=g;if(_===U){Ln(O,H);return}if(_===xs){S(g),M&&!M.persisted&&M.afterLeave&&M.afterLeave();return}const D=()=>{o(O),M&&!M.persisted&&M.afterLeave&&M.afterLeave()};if(g.shapeFlag&1&&M&&!M.persisted){const{leave:z,delayLeave:Y}=M,K=()=>z(O,D);Y?Y(g.el,D,K):K()}else D()},Ln=(g,_)=>{let O;for(;g!==_;)O=b(g),o(g),g=O;o(_)},me=(g,_,O)=>{const{bum:H,scope:M,job:D,subTree:z,um:Y,m:K,a:L}=g;Fi(K),Fi(L),H&&ks(H),M.stop(),D?(D.flags|=8,ne(z,g,_,O)):g.vnode.el&&z&&(z.transition=g.vnode.transition,ne(z,g,_,O)),Y&&Ge(Y,_),Ge(()=>{g.isUnmounted=!0},_)},N=(g,_,O,H=!1,M=!1,D=0)=>{for(let z=D;z{if(g.shapeFlag&6)return fe(g.component.subTree);if(g.shapeFlag&128)return g.suspense.next();const _=b(g.anchor||g.el),O=_&&_[Kr];return O?b(O):_};let at=!1;const Dn=(g,_,O)=>{let H;g==null?_._vnode&&(ne(_._vnode,null,null,!0),H=_._vnode.component):C(_._vnode||null,g,_,null,null,null,O),_._vnode=g,at||(at=!0,Si(H),Bl(),at=!1)},ln={p:C,um:ne,m:B,r:Qe,mt:j,mc:w,pc:he,pbc:te,n:fe,o:e};return{render:Dn,hydrate:void 0,createApp:pu(Dn)}}function ho({type:e,props:t},n){return n==="svg"&&e==="foreignObject"||n==="mathml"&&e==="annotation-xml"&&t&&t.encoding&&t.encoding.includes("html")?void 0:n}function rn({effect:e,job:t},n){n?(e.flags|=32,t.flags|=4):(e.flags&=-33,t.flags&=-5)}function Eu(e,t){return(!e||e&&!e.pendingBranch)&&t&&!t.persisted}function da(e,t,n=!1){const s=e.children,o=t.children;if(oe(s)&&oe(o))for(let l=0;l>1,e[n[u]]0&&(t[s]=n[l-1]),n[l]=s)}}for(l=n.length,a=n[l-1];l-- >0;)n[l]=a,a=t[a];return n}function fa(e){const t=e.subTree.component;if(t)return t.asyncDep&&!t.asyncResolved?t:fa(t)}function Fi(e){if(e)for(let t=0;te.__isSuspense;function Ou(e,t){t&&t.pendingBranch?oe(e)?t.effects.push(...e):t.effects.push(e):Hr(e)}const U=Symbol.for("v-fgt"),Qs=Symbol.for("v-txt"),jt=Symbol.for("v-cmt"),xs=Symbol.for("v-stc"),hn=[];let st=null;function p(e=!1){hn.push(st=e?null:[])}function va(){hn.pop(),st=hn[hn.length-1]||null}let ns=1;function Ni(e,t=!1){ns+=e,e<0&&st&&t&&(st.hasOnce=!0)}function ma(e){return e.dynamicChildren=ns>0?st||dn:null,va(),ns>0&&st&&st.push(e),e}function v(e,t,n,s,o,l){return ma(i(e,t,n,s,o,l,!0))}function Xe(e,t,n,s,o){return ma(_e(e,t,n,s,o,!0))}function ya(e){return e?e.__v_isVNode===!0:!1}function Rn(e,t){return e.type===t.type&&e.key===t.key}const ga=({key:e})=>e??null,Ss=({ref:e,ref_key:t,ref_for:n})=>(typeof e=="number"&&(e=""+e),e!=null?Ie(e)||We(e)||ue(e)?{i:nt,r:e,k:t,f:!!n}:e:null);function i(e,t=null,n=null,s=0,o=null,l=e===U?0:1,a=!1,u=!1){const d={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&ga(t),ref:t&&Ss(t),scopeId:Wl,slotScopeIds:null,children:n,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetStart:null,targetAnchor:null,staticCount:0,shapeFlag:l,patchFlag:s,dynamicProps:o,dynamicChildren:null,appContext:null,ctx:nt};return u?(Ms(d,n),l&128&&e.normalize(d)):n&&(d.shapeFlag|=Ie(n)?8:16),ns>0&&!a&&st&&(d.patchFlag>0||l&6)&&d.patchFlag!==32&&st.push(d),d}const _e=Iu;function Iu(e,t=null,n=null,s=0,o=null,l=!1){if((!e||e===Xl)&&(e=jt),ya(e)){const u=Pn(e,t,!0);return n&&Ms(u,n),ns>0&&!l&&st&&(u.shapeFlag&6?st[st.indexOf(e)]=u:st.push(u)),u.patchFlag=-2,u}if(Wu(e)&&(e=e.__vccOpts),t){t=Mu(t);let{class:u,style:d}=t;u&&!Ie(u)&&(t.class=ie(u)),Se(d)&&(si(d)&&!oe(d)&&(d=je({},d)),t.style=Pe(d))}const a=Ie(e)?1:ha(e)?128:Gs(e)?64:Se(e)?4:ue(e)?2:0;return i(e,t,n,s,o,a,l,!0)}function Mu(e){return e?si(e)||ia(e)?je({},e):e:null}function Pn(e,t,n=!1,s=!1){const{props:o,ref:l,patchFlag:a,children:u,transition:d}=e,f=t?Lu(o||{},t):o,r={__v_isVNode:!0,__v_skip:!0,type:e.type,props:f,key:f&&ga(f),ref:t&&t.ref?n&&l?oe(l)?l.concat(Ss(t)):[l,Ss(t)]:Ss(t):l,scopeId:e.scopeId,slotScopeIds:e.slotScopeIds,children:u,target:e.target,targetStart:e.targetStart,targetAnchor:e.targetAnchor,staticCount:e.staticCount,shapeFlag:e.shapeFlag,patchFlag:t&&e.type!==U?a===-1?16:a|16:a,dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren,appContext:e.appContext,dirs:e.dirs,transition:d,component:e.component,suspense:e.suspense,ssContent:e.ssContent&&Pn(e.ssContent),ssFallback:e.ssFallback&&Pn(e.ssFallback),placeholder:e.placeholder,el:e.el,anchor:e.anchor,ctx:e.ctx,ce:e.ce,cacheIndex:e.cacheIndex};return d&&s&&ii(r,d.clone(r)),r}function Z(e=" ",t=0){return _e(Qs,null,e,t)}function Cs(e,t){const n=_e(xs,null,e);return n.staticCount=t,n}function F(e="",t=!1){return t?(p(),Xe(jt,null,e)):_e(jt,null,e)}function Ct(e){return e==null||typeof e=="boolean"?_e(jt):oe(e)?_e(U,null,e.slice()):ya(e)?Mt(e):_e(Qs,null,String(e))}function Mt(e){return e.el===null&&e.patchFlag!==-1||e.memo?e:Pn(e)}function Ms(e,t){let n=0;const{shapeFlag:s}=e;if(t==null)t=null;else if(oe(t))n=16;else if(typeof t=="object")if(s&65){const o=t.default;o&&(o._c&&(o._d=!1),Ms(e,o()),o._c&&(o._d=!0));return}else{n=32;const o=t._;!o&&!ia(t)?t._ctx=nt:o===3&&nt&&(nt.slots._===1?t._=1:(t._=2,e.patchFlag|=1024))}else if(ue(t)){if(s&65){Ms(e,{default:t});return}t={default:t,_ctx:nt},n=32}else t=String(t),s&64?(n=16,t=[Z(t)]):n=8;e.children=t,e.shapeFlag|=n}function Lu(...e){const t={};for(let n=0;nVe||nt;let Ls,ss;{const e=qs(),t=(n,s)=>{let o;return(o=e[n])||(o=e[n]=[]),o.push(s),l=>{o.length>1?o.forEach(a=>a(l)):o[0](l)}};Ls=t("__VUE_INSTANCE_SETTERS__",n=>Ve=n),ss=t("__VUE_SSR_SETTERS__",n=>os=n)}const ps=e=>{const t=Ve;return Ls(e),e.scope.on(),()=>{e.scope.off(),Ls(t)}},Ri=()=>{Ve&&Ve.scope.off(),Ls(null)};function ba(e){return e.vnode.shapeFlag&4}let os=!1;function Hu(e,t=!1,n=!1){t&&ss(t);const{props:s,children:o}=e.vnode,l=ba(e);_u(e,s,l,t),Su(e,o,n||t);const a=l?Uu(e,t):void 0;return t&&ss(!1),a}function Uu(e,t){const n=e.type;e.accessCache=Object.create(null),e.proxy=new Proxy(e.ctx,lu);const{setup:s}=n;if(s){Ht();const o=e.setupContext=s.length>1?Bu(e):null,l=ps(e),a=fs(s,e,0,[e.props,o]),u=gl(a);if(Ut(),l(),(u||e.sp)&&!Xn(e)&&zl(e),u){if(a.then(Ri,Ri),t)return a.then(d=>{ss(!0);try{Hi(e,d,t)}finally{ss(!1)}}).catch(d=>{zs(d,e,0)});e.asyncDep=a}else Hi(e,a)}else wa(e)}function Hi(e,t,n){ue(t)?e.type.__ssrInlineRender?e.ssrRender=t:e.render=t:Se(t)&&(e.setupState=Hl(t)),wa(e)}function wa(e,t,n){const s=e.type;e.render||(e.render=s.render||Tt);{const o=ps(e);Ht();try{au(e)}finally{Ut(),o()}}}const ju={get(e,t){return Be(e,"get",""),e[t]}};function Bu(e){const t=n=>{e.exposed=n||{}};return{attrs:new Proxy(e.attrs,ju),slots:e.slots,emit:e.emit,expose:t}}function eo(e){return e.exposed?e.exposeProxy||(e.exposeProxy=new Proxy(Hl(Er(e.exposed)),{get(t,n){if(n in t)return t[n];if(n in Zn)return Zn[n](e)},has(t,n){return n in t||n in Zn}})):e.proxy}function Vu(e,t=!0){return ue(e)?e.displayName||e.name:e.name||t&&e.__name}function Wu(e){return ue(e)&&"__vccOpts"in e}const $=(e,t)=>Lr(e,t,os),Ku="3.5.43";let Do;const Ui=typeof window<"u"&&window.trustedTypes;if(Ui)try{Do=Ui.createPolicy("vue",{createHTML:e=>e})}catch{}const _a=Do?e=>Do.createHTML(e):e=>e,qu="http://www.w3.org/2000/svg",Yu="http://www.w3.org/1998/Math/MathML",It=typeof document<"u"?document:null,ji=It&&It.createElement("template"),zu={insert:(e,t,n)=>{t.insertBefore(e,n||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,s)=>{const o=t==="svg"?It.createElementNS(qu,e):t==="mathml"?It.createElementNS(Yu,e):n?It.createElement(e,{is:n}):It.createElement(e);return e==="select"&&s&&s.multiple!=null&&o.setAttribute("multiple",s.multiple),o},createText:e=>It.createTextNode(e),createComment:e=>It.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>It.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},insertStaticContent(e,t,n,s,o,l){const a=n?n.previousSibling:t.lastChild;if(o&&(o===l||o.nextSibling))for(;t.insertBefore(o.cloneNode(!0),n),!(o===l||!(o=o.nextSibling)););else{ji.innerHTML=_a(s==="svg"?`${e}`:s==="mathml"?`${e}`:e);const u=ji.content;if(s==="svg"||s==="mathml"){const d=u.firstChild;for(;d.firstChild;)u.appendChild(d.firstChild);u.removeChild(d)}t.insertBefore(u,n)}return[a?a.nextSibling:t.firstChild,n?n.previousSibling:t.lastChild]}},Ju=Symbol("_vtc");function Gu(e,t,n){const s=e[Ju];s&&(t=(t?[t,...s]:[...s]).join(" ")),t==null?e.removeAttribute("class"):n?e.setAttribute("class",t):e.className=t}const Bi=Symbol("_vod"),Xu=Symbol("_vsh"),Zu=Symbol(""),Qu=/(?:^|;)\s*display\s*:/;function ec(e,t,n){const s=e.style,o=Ie(n);let l=!1;if(n&&!o){if(t)if(Ie(t))for(const a of t.split(";")){const u=a.slice(0,a.indexOf(":")).trim();n[u]==null&&Bn(s,u,"")}else for(const a in t)n[a]==null&&Bn(s,a,"");for(const a in n){a==="display"&&(l=!0);const u=n[a];u!=null?nc(e,a,!Ie(t)&&t?t[a]:void 0,u)||Bn(s,a,u):Bn(s,a,"")}}else if(o){if(t!==n){const a=s[Zu];a&&(n+=";"+a),s.cssText=n,l=Qu.test(n)}}else t&&e.removeAttribute("style");Bi in e&&(e[Bi]=l?s.display:"",e[Xu]&&(s.display="none"))}const ws=/\s*!important$/;function Bn(e,t,n){if(oe(n))n.forEach(s=>Bn(e,t,s));else if(n==null&&(n=""),t.startsWith("--"))ws.test(n)?e.setProperty(t,n.replace(ws,""),"important"):e.setProperty(t,n);else{const s=tc(e,t);ws.test(n)?e.setProperty(on(s),n.replace(ws,""),"important"):e[s]=n}}const Vi=["Webkit","Moz","ms"],vo={};function tc(e,t){const n=vo[t];if(n)return n;let s=ze(t);if(s!=="filter"&&s in e)return vo[t]=s;s=Ws(s);for(let o=0;omo||(rc.then(()=>mo=0),mo=Date.now());function cc(e,t){const n=s=>{if(!s._vts)s._vts=Date.now();else if(s._vts<=n.attached)return;const o=n.value;if(oe(o)){const l=s.stopImmediatePropagation;s.stopImmediatePropagation=()=>{l.call(s),s._stopped=!0};const a=o.slice(),u=[s];for(let d=0;de.charCodeAt(0)===111&&e.charCodeAt(1)===110&&e.charCodeAt(2)>96&&e.charCodeAt(2)<123,dc=(e,t,n,s,o,l)=>{const a=o==="svg";t==="class"?Gu(e,s,a):t==="style"?ec(e,n,s):js(t)?Bs(t)||oc(e,t,n,s,l):(t[0]==="."?(t=t.slice(1),!0):t[0]==="^"?(t=t.slice(1),!1):fc(e,t,s,a))?(qi(e,t,s),!e.tagName.includes("-")&&(t==="value"||t==="checked"||t==="selected")&&Ki(e,t,s,a,l,t!=="value")):e._isVueCE&&(pc(e,t)||e._def.__asyncLoader&&(/[A-Z]/.test(t)||!Ie(s)))?qi(e,ze(t),s,l,t):(t==="true-value"?e._trueValue=s:t==="false-value"&&(e._falseValue=s),Ki(e,t,s,a))};function fc(e,t,n,s){if(s)return!!(t==="innerHTML"||t==="textContent"||t in e&&zi(t)&&ue(n));if(t==="spellcheck"||t==="draggable"||t==="translate"||t==="autocorrect"||t==="sandbox"&&e.tagName==="IFRAME"||t==="form"||t==="list"&&e.tagName==="INPUT"||t==="type"&&e.tagName==="TEXTAREA")return!1;if(t==="width"||t==="height"){const o=e.tagName;if(o==="IMG"||o==="VIDEO"||o==="CANVAS"||o==="SOURCE")return!1}return zi(t)&&Ie(n)?!1:t in e}function pc(e,t){const n=e._def.props;if(!n)return!1;const s=ze(t);return Array.isArray(n)?n.some(o=>ze(o)===s):Object.keys(n).some(o=>ze(o)===s)}const en=e=>{const t=e.props["onUpdate:modelValue"]||!1;return oe(t)?n=>ks(t,n):t};function hc(e){e.target.composing=!0}function Ji(e){const t=e.target;t.composing&&(t.composing=!1,t.dispatchEvent(new Event("input")))}const ot=Symbol("_assign"),_s=Symbol("_initialValue");function yo(e,t,n){return t&&(e=e.trim()),n&&(e=Ks(e)),e}const Fe={created(e,{modifiers:{lazy:t,trim:n,number:s}},o){e.parentNode&&(e.type==="text"?e[_s]=e.defaultValue.replace(/[\r\n]/g,""):e.type==="textarea"&&(e[_s]=e.defaultValue.replace(/\r\n?/g,` +`))),e[ot]=en(o);const l=s||o.props&&o.props.type==="number";Nt(e,t?"change":"input",a=>{a.target.composing||e[ot](yo(e.value,n,l))}),(n||l)&&Nt(e,"change",()=>{e.value=yo(e.value,n,l)}),t||(Nt(e,"compositionstart",hc),Nt(e,"compositionend",Ji),Nt(e,"change",Ji))},mounted(e,{value:t,modifiers:{trim:n,number:s}}){const o=t??"",l=e[_s];delete e[_s],l!==void 0&&(e.type==="text"||e.type==="textarea")&&e.value!==l?e[ot](yo(e.value,n,s)):e.value=o},beforeUpdate(e,{value:t,oldValue:n,modifiers:{lazy:s,trim:o,number:l}},a){if(e[ot]=en(a),e.composing)return;const u=(l||e.type==="number")&&!/^0\d/.test(e.value)?Ks(e.value):e.value,d=t??"";if(u===d)return;const f=e.getRootNode();(f instanceof Document||f instanceof ShadowRoot)&&f.activeElement===e&&e.type!=="range"&&(s&&t===n||o&&e.value.trim()===d)||(e.value=d)}},vc={deep:!0,created(e,t,n){e[ot]=en(n),Nt(e,"change",()=>{const s=e._modelValue,o=On(e),l=e.checked,a=e[ot];if(oe(s)){const u=Go(s,o),d=u!==-1;if(l&&!d)a(s.concat(o));else if(!l&&d){const f=[...s];f.splice(u,1),a(f)}}else if(Rt(s)){const u=new Set(s);l?u.add(o):u.delete(o),a(u)}else a(ka(e,l))})},mounted:Gi,beforeUpdate(e,t,n){e[ot]=en(n),Gi(e,t,n)}};function Gi(e,{value:t,oldValue:n},s){e._modelValue=t;let o;if(oe(t))o=Go(t,s.props.value)>-1;else if(Rt(t))o=t.has(s.props.value);else{if(t===n)return;o=mt(t,ka(e,!0))}e.checked!==o&&(e.checked=o)}const Xi={created(e,{value:t},n){e.checked=mt(t,n.props.value),e[ot]=en(n),Nt(e,"change",()=>{e[ot](On(e))})},beforeUpdate(e,{value:t,oldValue:n},s){e[ot]=en(s),t!==n&&(e.checked=mt(t,s.props.value))}},vn={deep:!0,created(e,{value:t,modifiers:{number:n}},s){e._modelValue=t,Nt(e,"change",()=>{const o=Array.prototype.filter.call(e.options,d=>d.selected).map(d=>n?Ks(On(d)):On(d)),l=e.multiple,a=l?Rt(e._modelValue)?new Set(o):o:o[0],u=e._pendingValue=[l,l?oe(a)?o.slice():o:a];try{e[ot](a)}finally{Js(()=>{e._pendingValue===u&&(e._pendingValue=void 0)})}}),e[ot]=en(s)},mounted(e,{value:t}){Zi(e,t)},beforeUpdate(e,{value:t},n){e._modelValue=t,e[ot]=en(n)},updated(e,{value:t}){const n=e._pendingValue;e._pendingValue=void 0,(!n||n[0]!==e.multiple||!mc(t,n[1],n[0]))&&Zi(e,t)}};function mc(e,t,n){if(!n||oe(e))return mt(e,t);if(Rt(e)){if(e.size!==t.length)return!1;for(const s of t)if(!e.has(s))return!1;return!0}return!1}function Zi(e,t){const n=e.multiple,s=oe(t);if(!(n&&!s&&!Rt(t))){for(let o=0,l=e.options.length;oString(f)===String(u)):a.selected=Go(t,u)>-1}else a.selected=t.has(u);else if(mt(On(a),t)){e.selectedIndex!==o&&(e.selectedIndex=o);return}}!n&&e.selectedIndex!==-1&&(e.selectedIndex=-1)}}function On(e){return"_value"in e?e._value:e.value}function ka(e,t){const n=t?"_trueValue":"_falseValue";return n in e?e[n]:t}const yc=["ctrl","shift","alt","meta"],gc={stop:e=>e.stopPropagation(),prevent:e=>e.preventDefault(),self:e=>e.target!==e.currentTarget,ctrl:e=>!e.ctrlKey,shift:e=>!e.shiftKey,alt:e=>!e.altKey,meta:e=>!e.metaKey,left:e=>"button"in e&&e.button!==0,middle:e=>"button"in e&&e.button!==1,right:e=>"button"in e&&e.button!==2,exact:(e,t)=>yc.some(n=>e[`${n}Key`]&&!t.includes(n))},ft=(e,t)=>{if(!e)return e;const n=e._withMods||(e._withMods={}),s=t.join(".");return n[s]||(n[s]=((o,...l)=>{for(let a=0;a{const n=e._withKeys||(e._withKeys={}),s=t.join(".");return n[s]||(n[s]=(o=>{if(!("key"in o))return;const l=on(o.key);if(t.some(a=>a===l||bc[a]===l))return e(o)}))},wc=je({patchProp:dc},zu);let Qi;function _c(){return Qi||(Qi=Au(wc))}const kc=((...e)=>{const t=_c().createApp(...e),{mount:n}=t;return t.mount=s=>{const o=xc(s);if(!o)return;const l=t._component;!ue(l)&&!l.render&&!l.template&&(l.template=o.innerHTML),o.nodeType===1&&(o.textContent="");const a=n(o,!1,$c(o));return o instanceof Element&&(o.removeAttribute("v-cloak"),o.setAttribute("data-v-app","")),a},t});function $c(e){if(e instanceof SVGElement)return"svg";if(typeof MathMLElement=="function"&&e instanceof MathMLElement)return"mathml"}function xc(e){return Ie(e)?document.querySelector(e):e}const Sc=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],Cc=["Su","Mo","Tu","We","Th","Fr","Sa"],xa={clock:"clock-outline",analog:"clock-outline",date:"calendar"},qe=e=>JSON.stringify(e),ke=e=>String.fromCodePoint(parseInt(e,16));function ui(e,t=new Date){let n=t.getHours();return e||(n=n%12||12),`${String(n).padStart(2,"0")}:${String(t.getMinutes()).padStart(2,"0")}`}const ci=(e=new Date)=>`${Cc[e.getDay()]} ${e.getDate()} ${Sc[e.getMonth()]}`;function Ac(e,t=Math.floor(Date.now()/1e3)){const n=t-e,s=Math.abs(n),o=l=>Math.floor(s/l);return n<0?s<3600?`In ${Math.max(1,o(60))} min`:s<86400?o(3600)===1?"In 1 hour":`In ${o(3600)} hours`:s<172800?"Tomorrow":`In ${o(86400)} days`:s<60?"Just now":s<3600?`${o(60)} min ago`:s<86400?o(3600)===1?"1 hour ago":`${o(3600)} hours ago`:s<172800?"Yesterday":s<604800?`${o(86400)} days ago`:s<2592e3?o(604800)===1?"1 week ago":`${o(604800)} weeks ago`:s<31536e3?o(2592e3)===1?"1 month ago":`${o(2592e3)} months ago`:o(31536e3)===1?"1 year ago":`${o(31536e3)} years ago`}const Tc={guition:{width:448,top:36,name:27,text:21,icon:26},cyd:{width:298,top:24,name:18,text:14,icon:18}};let Hn;const Fo=new Map,Ec=()=>Fo.clear();function cn(e,t){const n=`${t}|${e}`;let s=Fo.get(n);if(!s){if(Hn===void 0&&(Hn=document.createElement("canvas").getContext("2d")),Hn){Hn.font=t;const o=Hn.measureText(e);s={left:-o.actualBoundingBoxLeft,right:o.actualBoundingBoxRight,top:-o.actualBoundingBoxAscent,bottom:o.actualBoundingBoxDescent,advance:o.width}}else{const o=Number(/(\d+(?:\.\d+)?)px/.exec(t)?.[1]||14),l=[...e].length*o*.55;s={left:0,right:l,top:-o*.72,bottom:o*.02,advance:l}}Fo.set(n,s)}return s}const Pc=e=>({name:`500 ${e.name}px "Bar Roboto"`,text:`400 ${e.text}px "Bar Roboto"`,icon:`${e.icon}px "Tile Icons"`});function Oc(e){return{icon:Math.max(2,Math.floor((e*4+5)/10)),item:Math.max(6,Math.floor((e*125+50)/100)),name:Math.max(8,Math.floor((e*16+5)/10))}}function Sa(e,t,n,s){const o=Pc(t),l=cn("0",o.text),a=Math.round(l.bottom-l.top),u=Oc(a),d=cn(String.fromCodePoint(983376),o.icon),f=Math.round(d.bottom-d.top),r=e.map((S,V)=>{const I=s(S),T={index:V,item:S,view:I,shown:I.shown,width:0};return I.analog?(T.dial=f,T.width=f,T):(I.icon&&(T.icon={glyph:ke(I.icon),ink:cn(ke(I.icon),o.icon)},T.width+=T.icon.ink.right-T.icon.ink.left),I.text&&(T.text={value:I.text,ink:cn(I.text,o.text)},T.width+=(T.icon?u.icon:0)+T.text.ink.right-T.text.ink.left),T)}),h=r.filter(S=>S.shown),b=cn(n,o.name).advance,k=Math.min(b,Math.floor(t.width*35/100)),R=S=>S.reduce((V,I)=>V+I.width,0)+Math.max(0,S.length-1)*u.item;let C=0;for(;Ct.width;)C++;const x=h.slice(C),P=new Set(h.slice(0,C).map(S=>S.index));let E=t.width-R(x);for(const S of x)S.x=E,E+=S.width+u.item;const W=x.length?x[0].x-u.name:t.width;return{metrics:t,fonts:o,cap:a,zero:l,gaps:u,parts:r,placed:x,dropped:P,nameText:n,natural:b,nameRoom:W}}function Ic(e,t,n){if(cn(e,t).advance<=n)return e;const s=[...e];for(;s.length&&cn(s.join("")+"...",t).advance>n;)s.pop();return s.join("")+"..."}function Mc(e){"fonts"in document&&Promise.all([document.fonts.load('500 27px "Bar Roboto"',"Studio 0"),document.fonts.load('400 21px "Bar Roboto"',"Away 0"),document.fonts.load('26px "Tile Icons"',String.fromCodePoint(983376))]).then(()=>{Ec(),e()}).catch(()=>{})}let Ca="";function Lc(e){Ca=e||""}async function di(e,t={}){const n=await fetch(`api/${e}`,{...t,headers:{"Content-Type":"application/json","X-Screen-CSRF":Ca,...t.headers}});if(!n.ok){let s="That didn't work. Refresh the page and try again.";try{const o=(await n.json())?.error;typeof o=="string"&&o.trim()&&(s=o)}catch{}throw new Error(s)}return n}const gt=async e=>(await di(e)).json(),it=async(e,t,n,s={})=>{const l=await(await di(e,{method:t,body:n===void 0?void 0:JSON.stringify(n),...s})).text();return l?JSON.parse(l):null},Le=6,Bt=8,is=Bt*Le,Dc=["single","wide","full"],tn=e=>e===!0?"wide":e===!1?"single":e,Mn=e=>/^screen\.page_[1-8]$/.test(e)?Number(e.slice(-1)):0,Je=e=>Dc.includes(e.options?.size)?e.options.size:"single",No=e=>Je(e)!=="single",Aa=e=>Je(e)==="full",Ta=e=>e-e%Le,Ea=e=>e-e%2,ct=e=>Math.floor(e/Le),fi=e=>tn(e)==="full"?Le:tn(e)==="wide"?2:1,mn=(e,t)=>tn(t)==="full"?Array.from({length:Le},(n,s)=>Ta(e)+s):tn(t)==="wide"?[e,e+1]:[e],el=(e,t)=>tn(t)==="full"?Ta(e):tn(t)==="wide"?Ea(e):e,He=e=>e.tiles.map(t=>({tile:t,slot:t.slot}));function Pa(e){let t=0;return e.map(n=>{const s=Je(n);s==="full"&&t%Le?t+=Le-t%Le:s==="wide"&&t%2===1&&t++;const o=t;return t+=fi(s),o})}function Fc(e){const t=Pa(e);return e.some((n,s)=>n.slot!==t[s])}function to(e){if(e.tiles.some(t=>!Number.isInteger(t.slot))){const t=Pa(e.tiles);e.tiles.forEach((n,s)=>n.slot=t[s])}e.tiles.sort((t,n)=>t.slot-n.slot)}function Tn(e){const t=new Set;for(const{tile:n,slot:s}of e)for(const o of mn(s,Je(n)))t.add(o);return t}const ls=(e,t,n)=>Number.isInteger(t)&&t>=0&&t!e.has(s));function Ds(e,t,n=0){for(let s=n;so.includes(f))?u.push(d):a.push({tile:d,slot:d.slot}));for(const d of u){const f=Je(d),r=Tn(a);let h=l.map(b=>el(b,f)).find(b=>ls(r,b,f));if(h===void 0&&(h=Oa(r,f,d.slot)),h<0)return null;a.push({tile:d,slot:h})}return a.sort((d,f)=>d.slot-f.slot)}function pt(e,t=1){const n=Math.max(0,...e.map(({tile:s,slot:o})=>o+fi(Je(s))));return Math.min(Bt,Math.max(1,Math.ceil(n/Le),t||1))}function Nc(e,t){const n=Array.from({length:t},()=>new Set);let s=0;for(const{tile:f,slot:r}of e){const h=Mn(f.entity)-1,b=ct(r);h<0||h>=t||b>=t||(s++,n[b].add(h))}const o=f=>{const r=new Set([0]),h=[0];for(;h.length;)for(const b of f(h.shift()))r.has(b)||(r.add(b),h.push(b));return r},l=o(f=>[...n[f]]),a=o(f=>n.flatMap((r,h)=>r.has(f)?[h]:[])),u=Array.from({length:t},(f,r)=>r),d=f=>f.map(r=>r+1);return{tiles:s,targets:d(u.filter(f=>n.some(r=>r.has(f)))),unreachable:d(u.filter(f=>!l.has(f))),noWayBack:d(u.filter(f=>l.has(f)&&!a.has(f)))}}function Rc(e){const t=e.split(".")[0];return t==="sun"?{options:{display:"sunpath",size:"wide"}}:t==="weather"?{options:{display:"forecast",size:"wide"}}:Mn(e)?{}:t==="screen"?{options:{display:"digital",size:"wide"}}:{}}const Ma=e=>({entity:e,name:"",slot:-1,...Rc(e)});function La(e,t){const n=e.entity.split(".")[0],s=t.controls?.[n],o=e.options||{};if(!s||!["wide","full"].includes(o.size)||(o.display||"standard")!=="standard"||o.inline==="slider")return null;const l=o.controls??(o.size==="full"?"none":s.default);return l==="none"?null:l}function Hc(e,t){const n=La(e,t);return n?t.controls?.[e.entity.split(".")[0]]?.choices.find(s=>s.key===n)?.label.toLocaleLowerCase()||n:"none"}const Ro=e=>(/^(\d+)\.(\d+)\.(\d+)$/.exec(e||"")||[]).slice(1).map(Number);function nn(e,t){const[n,s]=[Ro(e),Ro(t)];if(n.length!==3||s.length!==3)return!1;for(let o=0;o<3;o++)if(n[o]!==s[o])return n[o]>s[o];return!0}const Uc=(e,t,n,s)=>nn(e,`${t}.${n}.${s}`),jc=is;function Bc(e){return Ro(e).length!==3?10:nn(e,"0.2.62")?jc:nn(e,"0.2.7")?20:10}const Da={standard:"standard",watch:"large value",forecast:"weather forecast",graph:"graph",digital:"digital clock",analog:"analog clock",sunpath:"sun path"},Vc={single:"normal",wide:"double",full:"full page"},tl=["light","switch","input_boolean","fan","media_player","climate"],Wc=["light","fan","cover","number","input_number","media_player"],Kc={light:["Light","☀","#ad7600","#fff3d3"],climate:["Climate","❄","#c86620","#ffebdc"],vacuum:["Vacuum","◉","#008577","#def3ed"],fan:["Fan","✣","#008aab","#def5fa"],cover:["Cover","▤","#8053af","#eee5f8"],media_player:["Media","▶","#007cad","#def2fc"],sensor:["Sensor","⌁","#3476b1","#e5effa"],binary_sensor:["Status","◈","#ad7600","#fff3d3"],switch:["Switch","⏻","#ad7600","#fff3d3"],input_boolean:["Switch","⏻","#ad7600","#fff3d3"],scene:["Scene","✦","#8053af","#eee5f8"],script:["Script","▷","#8053af","#eee5f8"],weather:["Weather","☁","#007cad","#def2fc"],number:["Value","±","#008577","#def3ed"],input_number:["Value","±","#008577","#def3ed"],select:["Select","≡","#5862af","#eaecfa"],input_select:["Select","≡","#5862af","#eaecfa"],button:["Action","↗","#5862af","#eaecfa"],input_button:["Action","↗","#5862af","#eaecfa"],screen:["Screen","◷","#25282c","#e9ecf1"],sun:["Sun","☼","#c86620","#ffebdc"],timer:["Timer","⏱","#008577","#def3ed"],person:["Person","☺","#2f7d32","#e1f2e2"],camera:["Camera","◧","#3d4a57","#e6ebf0"],image:["Image","◧","#3d4a57","#e6ebf0"]},xt=e=>Kc[e.split(".")[0]]||["Entity","◇","#637184","#edf0f4"],c=bn({inventory:{screens:[],entities:[]},connected:!1,reachable:!0,selected:null,layout:null,dirty:!1,busy:!1,saved:0,tab:"layout",selectedTile:null,inspector:null,iconPickerOpen:!1,actionPickerOpen:!1,actionSearch:"",insertAt:-1,filter:"",search:"",capabilities:{},entityActions:{},topbarPreviews:{},topbarAdded:null,topbarOverflow:[],settingEdits:{},settingPending:!1,updating:[],toast:null,now:Date.now(),fontsVersion:0,route:location.hash,overrideProfile:null,overrideFriendly:"",drag:{active:!1,moving:null,preview:null},menuOpen:!1,liveStates:{},room:"",hidePlaced:!1,palette:!1,firmwareJob:null}),lt=$(()=>c.inventory.screens.find(e=>e.id===c.selected)),as=e=>(e&&"firmware_known"in e?e.firmware_known:e?.firmware)||"",Fa=$(()=>as(lt.value)),Zt=(e,t,n)=>Uc(Fa.value,e,t,n),yn=$(()=>{const e=lt.value?.tile_limit;return typeof e=="number"&&Number.isInteger(e)&&e>0?e:Bc(Fa.value)}),nl=$(()=>{const e=lt.value?.full_page;return typeof e=="boolean"?e:Zt(0,2,62)}),qc=$(()=>{const e=lt.value?.page_tiles_repeat;return typeof e=="boolean"?e:Zt(0,2,65)}),no=e=>qc.value&&Mn(e)>0,pi=$(()=>lt.value?.board==="guition"),Na=$(()=>Tc[pi.value?"guition":"cyd"]),go=$(()=>c.selectedTile&&c.layout?.tiles.includes(c.selectedTile)?c.selectedTile:void 0),hi=e=>!!c.selectedTile&&ve(c.selectedTile)===ve(e);let sl=0;function ge(e,t){c.toast={message:e,action:t},clearTimeout(sl),sl=window.setTimeout(()=>c.toast=null,t?8e3:5e3)}function Yc(){c.toast=null}async function ht(e,t,n="API key"){try{if(!navigator.clipboard||!window.isSecureContext)throw new Error;await navigator.clipboard.writeText(e),ge(`${n} copied.`)}catch{if(t){const s=document.createRange();s.selectNodeContents(t);const o=window.getSelection();o?.removeAllRanges(),o?.addRange(s)}ge(document.execCommand("copy")?`${n} copied.`:`${n} is selected. Copy with Ctrl+C or Command+C.`)}}function Fs(){const e="/config/integrations/dashboard";try{window.top.location.assign(e)}catch{window.open(e,"_blank")}}const zc=["","#settings","#new-screen","#firmware","#alerts","#override"],rt=$(()=>zc.includes(c.route)?c.route:"");function Oe(e){if(location.hash===e){c.route=e;return}location.hash=e}window.addEventListener("hashchange",()=>{c.route=location.hash,window.scrollTo(0,0)});function hs(e){return c.inventory.entities.find(t=>t.id===e)?.name||c.inventory.builtin?.find(t=>t.id===e)?.name||e}let bo={source:null,byName:{}};function vs(e){return bo.source!==c.inventory.icons&&(bo={source:c.inventory.icons,byName:Object.fromEntries((c.inventory.icons?.groups||[]).flatMap(t=>t.icons.map(n=>[n.name,n])))}),e?bo.byName[e]:void 0}function sn(e){const t=c.inventory.icons;if(!t)return"F0335";const n=c.inventory.entities.find(o=>o.id===e),s=e.split(".")[0];return t.builtin?.[e]?t.builtin[e]:n?.icon?n.icon:s==="weather"?t.weather[n?.state||""]||t.weather.partlycloudy:s==="sun"?t.sun[n?.state||""]||t.sun.below_horizon:t.defaults[s]||t.fallback}const Vn=e=>vs(e.options?.icon)?.cp||sn(e.entity),wo=new Set;async function so(e){const t=[...new Set(e)].filter(n=>!wo.has(n)&&!n.startsWith("screen."));if(t.length){t.forEach(n=>wo.add(n));try{for(let n=0;n`entity=${encodeURIComponent(o)}`).join("&");Object.assign(c.capabilities,(await gt(`capabilities?${s}`)).capabilities||{})}}catch{t.forEach(n=>wo.delete(n))}}}const _o=new Set;async function Jc(e){if(!_o.has(e)){_o.add(e);try{c.entityActions[e]=(await gt(`entity-actions?entity=${encodeURIComponent(e)}`)).actions}catch{_o.delete(e)}}}let ko=!1;async function oo(){const e=[...new Set((c.layout?.tiles||[]).map(t=>t.entity).filter(t=>!t.startsWith("screen.")))];if(!(!e.length||ko)){ko=!0;try{for(let t=0;t`entity=${encodeURIComponent(s)}`).join("&");Object.assign(c.liveStates,(await gt(`states?${n}`)).states||{})}}catch{}finally{ko=!1}}}function Gc(e){const t=c.liveStates[e];if(t)return t;const n=c.inventory.entities.find(s=>s.id===e);return n?.state?{state:n.state,word:null,a:{}}:null}let Ho=0;function Ze(){c.dirty=!0,c.saved=0,Ho++}function Ra(e){if(e===c.selected&&c.layout&&c.dirty){Ue(),c.tab="layout",c.menuOpen=!1,Oe("");return}if(e!==c.selected&&c.dirty&&!confirm("You have unsaved changes. Open a different screen anyway?"))return;e!==c.selected&&(us(),c.settingEdits={}),c.selected=e,c.selectedTile=null,c.inspector=null,c.tab="layout",c.menuOpen=!1;const t=c.inventory.screens.find(s=>s.id===e);if(!t){c.layout=null;return}const n=JSON.parse(JSON.stringify(t.layout));to(n),n.pages=pt(He(n),n.pages),c.layout=n,c.insertAt=-1,c.dirty=!1,c.saved=0,io(0),so(n.tiles.map(s=>s.entity)),oo(),Oe("")}const Xc=()=>c.layout?He(c.layout):[];function Zc(e){const t=c.layout,n=t.tiles.map(o=>`${o.entity}@${o.slot}`).join();for(const{tile:o,slot:l}of e)o.slot=l,t.tiles.includes(o)||t.tiles.push(o);to(t),t.pages=pt(He(t),t.pages);const s=t.tiles.map(o=>`${o.entity}@${o.slot}`).join()!==n;return s&&Ze(),s}function ms(e,t){if(!c.layout)return!1;so([e.entity]);const n=Ia(c.layout.tiles,e,t),s=n?Zc(n):!1;return s&&!c.liveStates[e.entity]&&oo(),s}function Ha(e){const t=c.layout;if(!t||!no(e)&&t.tiles.some(o=>o.entity===e)||t.tiles.length>=yn.value)return;const n=Ma(e),s=c.insertAt>=0?c.insertAt:Ds(Tn(He(t)),Je(n));c.insertAt=-1,s>=0&&ms(n,s)&&Uo(n)}function Ua(e){const t=c.layout;if(!t)return;const n=t.tiles.indexOf(e);n<0||(t.tiles.splice(n,1),hi(e)&&Ue(),Ze(),t.pages=pt(He(t),t.pages),ge(`${e.name||hs(e.entity)} removed`,{label:"Undo",run:()=>ms(e,e.slot)}))}function ol(){const e=c.layout;e&&(e.pages=Math.min(Bt,pt(He(e),e.pages)+1),Ze())}function Qc(e){const t=c.layout;if(t){for(const n of t.tiles)ct(n.slot)>e&&(n.slot-=Le);t.pages=Math.max(1,pt(He(t),t.pages)-1),Ze()}}function ed(e,t){const n=c.layout;if(!n||!Number.isInteger(t)||t<0||t>=Bt||t===ct(e.slot))return!1;const s=Ds(Tn(He(n).filter(l=>l.tile!==e)),Je(e),t*Le),o=ms(e,s>=0&&ct(s)===t?s:t*Le);return o||ge(`There is no room for this tile on page ${t+1}.`),o}function td(){const e=c.layout;if(!e)return 1;const t=c.drag.preview||He(e),n=pt(t,e.pages);return c.drag.active&&nf.key!=="none"&&o.controls.includes(f.key))?.key||"none"),Ze();const d=c.layout;if(d){if(Aa(e)&&a!=="full"){const f=ct(e.slot),r=d.tiles.filter(k=>k!==e&&ct(k.slot)===f),h=Tn(He(d).filter(k=>k.tile!==e&&!r.includes(k.tile))),b=[];for(const k of r){const R=Ds(h,Je(k),(f+1)*Le);if(R<0){b.length=0;break}b.push([k,R]);for(const C of mn(R,Je(k)))h.add(C)}if(b.length===r.length){for(const[k,R]of b)k.slot=R;e.slot=f*Le}else{const k=Ds(Tn(He(d).filter(R=>R.tile!==e)),"full");k>=0?e.slot=k:(e.options.size=a,ge("No page is free for a full-page tile. Free a page first."))}}else if(No(e)&&!l){const f=Tn(He(d).filter(b=>b.tile!==e)),r=Ea(e.slot),h=ls(f,r,"wide")?r:Oa(f,"wide",r);h>=0&&(e.slot=h)}to(d),d.pages=pt(He(d),d.pages)}}function nd(e,t){const n=`screen.page_${t}`,s=c.layout;return!s||n===e.entity||!Mn(n)?!1:!no(n)&&s.tiles.some(o=>o.entity===n)?(ge(`This screen already has a tile that goes to page ${t}.`),!1):(e.entity=n,s.pages=Math.max(pt(He(s),s.pages),t),Ze(),!0)}function Uo(e){hi(e)||(c.iconPickerOpen=!1,c.actionPickerOpen=!1,c.actionSearch=""),c.selectedTile=e,c.inspector={kind:"tile"},so([e.entity])}function Lt(e){c.inspector?.kind==="bar"&&c.inspector.index===e||(c.iconPickerOpen=!1),c.selectedTile=null,c.inspector={kind:"bar",index:e}}function il(){c.selectedTile=null,c.inspector={kind:"bar-add"}}function Ue(){c.inspector=null,c.selectedTile=null}async function jo(){if(c.busy||!c.layout||!c.selected)return;c.busy=!0;const e=Ho,t=c.selected;try{const{settings:n,...s}=c.layout;await it(`screens/${encodeURIComponent(c.selected)}`,"PUT",s),c.selected!==t?ge(`Saved. ${c.inventory.screens.find(o=>o.id===t)?.name||"The screen"} is being updated.`):Ho===e?(c.dirty=!1,c.saved=Date.now(),ge("Saved. Your screen is being updated.")):ge("Saved. Your newest change isn't sent yet: press Save & send again."),await _n()}catch(n){ge(n.message)}finally{c.busy=!1}}const gn=e=>!!(e&&e.alert_action&&nn(as(e),c.inventory.alerts?.min_firmware||"0.2.31"));async function ja(e){try{await it(`screens/${encodeURIComponent(e.id)}/identify`,"POST"),ge(`${e.name} blinks and shows a card for a few seconds.`)}catch(t){ge(t.message)}}async function sd(e,t){return await it("alerts/test","POST",{screen:e,data:t})}const od=["title","tiles","header","pages"];function Ba(e,t){const n=c.layout;if(!n)return;const s=(Array.isArray(e.tiles)?e.tiles:[]).filter(u=>!!(u&&typeof u=="object"&&typeof u.entity=="string"&&u.entity.includes("."))).map(u=>({entity:u.entity,name:typeof u.name=="string"?u.name:"",slot:Number.isInteger(u.slot)?u.slot:-1,...u.options&&typeof u.options=="object"?{options:{...u.options}}:{}})),o=new Set,l=s.filter(u=>no(u.entity)||!o.has(u.entity)&&o.add(u.entity)),a=l.slice(0,yn.value);n.tiles=a,e.header&&Array.isArray(e.header.items)?n.header={items:e.header.items.map(u=>({...u}))}:delete n.header,n.pages=Number.isInteger(e.pages)?e.pages:1,to(n),n.pages=pt(He(n),n.pages),Ue(),Ze(),so(a.map(u=>u.entity)),oo(),io(0),ge(a.lengthn.id===e);!t||!c.layout||Ba(JSON.parse(JSON.stringify(t.layout)),`Layout of ${t.name} copied`)}function ld(){const e=c.layout;if(!e)return"";const t={esp_screens_layout:1};for(const n of od)e[n]!==void 0&&(t[n]=e[n]);return JSON.stringify(t,null,2)}function Va(){const e=ld();if(!e)return;const t=`${(lt.value?.name||"screen").toLowerCase().replace(/[^a-z0-9]+/g,"-")}.layout.json`,n=URL.createObjectURL(new Blob([e],{type:"application/json"})),s=document.createElement("a");s.href=n,s.download=t,s.click(),setTimeout(()=>URL.revokeObjectURL(n),5e3),ht(e,void 0,"Layout JSON")}function ad(e){let t;try{t=JSON.parse(e)}catch{ge("That isn't JSON. Export a layout first, or paste one from another ESP Screens.");return}if(!t||typeof t!="object"||!Array.isArray(t.tiles)){ge("No tiles in this file. Expected a layout exported from ESP Screens.");return}Ba(t,"Layout imported")}function ll(e){const t=c.inventory.updates?.target,n=c.inventory.changelog;if(!Array.isArray(n)||!t)return[];const s=as(e),o=[];for(const l of n)if(!(nn(l.firmware,t)&&l.firmware!==t)&&!(s&&nn(s,l.firmware)))for(const a of l.lines)o.includes(a)||o.push(a);return o}let $o=!1;async function rd(){if(!$o){$o=!0;try{const e=await gt("firmware");c.firmwareJob={job:e.job,logs:e.logs||[]}}catch{}finally{$o=!1}}}const Bo=()=>c.inventory.screens.some(e=>e.update?.state==="running")||c.updating.length>0;function Sn(e){const t=e.update||{};if(!(t.state==="running"||c.updating.includes(e.id)))return null;const n=c.firmwareJob?.job?.stage;return t.phase==="verify"?{percent:78,text:qn.verify}:t.phase==="settle"?{percent:92,text:qn.settle}:t.phase==="install"||!t.phase?n==="upload"?{percent:66,text:"Writing the firmware over Wi-Fi…"}:n?{percent:40,text:"Building the firmware…"}:{percent:12,text:qn.install}:{percent:12,text:qn[t.phase]||"Starting update…"}}const Vt=()=>c.layout?.header?.items??(c.layout?.settings?.show_clock??!0?[{type:"clock"}]:[]),zt=()=>c.inventory.header?.max_items||6;function rs(e){c.layout&&(c.layout.header={items:e},Ze(),io())}let al=0;function io(e=150){clearTimeout(al),al=window.setTimeout(async()=>{const t=Vt();if(t.some(n=>n.type==="entity"))try{const n=await it("header-preview","POST",{header:{items:t}});t.forEach((s,o)=>c.topbarPreviews[qe(s)]=n.items[o])}catch{}},e)}function Wn(e){return e.type==="entity"?hs(e.entity):c.inventory.header?.builtin.find(t=>t.type===e.type)?.label||e.type}function qt(e){const t=new Date(c.now);if(e.type==="clock")return{text:ui(wn().clock_24h!==!1,t),shown:!0};if(e.type==="date")return{text:ci(t),shown:!0};if(e.type==="analog")return{analog:!0,shown:!0};const n=c.topbarPreviews[qe(e)];return n?{icon:n.i||null,text:n.k==="ago"?Ac(n.e,Math.floor(c.now/1e3)):n.t,color:n.c?`#${n.c}`:null,shown:n.shown}:{icon:e.icon==="none"?null:vs(e.icon)?.cp||sn(e.entity),text:"…",shown:!0,loading:!0}}function xo(e,t){const n=[...Vt()];return t<0||t>=n.length||e===t?!1:(n.splice(t,0,...n.splice(e,1)),rs(n),!0)}function rl(e){const t=[...Vt()],[n]=t.splice(e,1);n&&(c.inspector?.kind==="bar"&&Ue(),rs(t),ge(`${Wn(n)} removed from the top bar`,{label:"Undo",run:()=>{const s=[...Vt()];s.splice(Math.min(e,s.length),0,n),rs(s)}}))}function So(e){const t=Vt();if(t.length>=zt())return ge(`The top bar has room for ${zt()} items.`);if(t.some(n=>qe(n)===qe(e)))return ge("This is already in the top bar.");c.topbarAdded={key:qe(e),time:Date.now()},rs([...t,e]),Lt(t.length)}const ud=[{title:"Brightness",icon:"F0599",rows:[{key:"brightness",label:"Brightness",kind:"number",min:5,max:100,step:5,unit:"%"},{key:"dark_mode",label:"Dark mode",kind:"toggle"},{key:"standby_enabled",label:"Auto standby",kind:"toggle"},{key:"standby_seconds",label:"Standby after",kind:"duration",min:60,max:86400,needs:"standby_enabled"},{key:"standby_brightness",label:"Standby brightness",kind:"number",min:0,max:100,step:5,unit:"%",needs:"standby_enabled",cap:"brightness"}]},{title:"Night",icon:"F0594",rows:[{key:"night_enabled",label:"Night mode",kind:"toggle"},{key:"night_start",label:"Starts",kind:"moment",needs:"night_enabled"},{key:"night_end",label:"Ends",kind:"moment",needs:"night_enabled"},{key:"night_brightness",label:"Night brightness",kind:"number",min:0,max:100,step:5,unit:"%",needs:"night_enabled",cap:"brightness"}]},{title:"Screen",icon:"F0379",rows:[{key:"clock_24h",label:"Clock",kind:"choice",options:[[!1,"12 hour"],[!0,"24 hour"]]},{key:"auto_home",label:"Back to page 1",kind:"toggle"},{key:"auto_home_seconds",label:"After",kind:"duration",min:30,max:3600,needs:"auto_home"},{key:"home_on_standby",label:"Also on standby",kind:"toggle"},{key:"swipe_pages",label:"Swipe between pages",kind:"toggle"},{key:"page_buttons",label:"Page buttons",kind:"toggle"},{key:"rotation",label:"Rotation",kind:"choice",options:[[0,"0°"],[90,"90°"],[180,"180°"],[270,"270°"]]}]}];function Wa(e=Xc(),t=c.layout?pt(e,c.layout.pages):1){const n=wn();if(t<2||n.page_buttons!==!1||n.swipe_pages!==!1)return"";const{tiles:s,targets:o,unreachable:l,noWayBack:a}=Nc(e,t);if(!l.length&&!a.length)return"";const u=h=>h.length===1?`page ${h[0]}`:`pages ${h.slice(0,-1).join(", ")} and ${h[h.length-1]}`,d=l.filter(h=>!o.includes(h)),f=l.filter(h=>o.includes(h));let r="Page buttons and swiping are off, so only Go to page tiles change the page. ";return r+=s?`You have ${s} Go to page ${s===1?"tile":"tiles"}, to ${u(o)}`:"You have no Go to page tiles",r+=d.length?`${s?", but none":""} to ${u(d)}, so you can't reach ${d.length===1?"it":"them"}.`:".",f.length&&(r+=` The tiles to ${u(f)} are only on pages you can't reach.`),a.length&&(r+=` From ${u(a)} no tile leads back to page 1.`),r}const Ka=4e3;let Jt={},En=null,Ns=0,As=null;const vi=()=>lt.value?.settings;function wn(){const e=vi(),t={...e?.values||{}};for(const[n,s]of Object.entries(c.settingEdits))t[n]=s.value;return t}const cd=e=>e<300?30:e<900?60:e<3600?300:e<7200?900:1800;function ul(e,t,n,s,o){if(e.kind==="moment"){let u=s&&t%60?Math.floor(t/60)*60+(n>0?60:0):t+n*(s?60:15);return u%=1440,u<0?u+1440:u}const l=e.kind==="duration"?cd(n<0?t-1:t):e.step,a=e.cap?Math.min(e.max,o[e.cap]):e.max;return Math.min(a,Math.max(e.min,t+n*l))}function dd(e){if(e<60)return`${e} sec`;if(e<3600)return`${Math.floor(e/60)} min`;const t=Math.floor(e/3600),n=Math.floor(e%3600/60);return n?`${t} h ${String(n).padStart(2,"0")}`:`${t} h`}function fd(e,t){const n=Math.floor(e/60),s=String(e%60).padStart(2,"0");return t?`${String(n).padStart(2,"0")}:${s}`:`${n%12||12}:${s} ${n<12?"AM":"PM"}`}function pd(e,t){const n=t[e.key];return n==null?"—":e.kind==="number"?`${n}${e.unit||""}`:e.kind==="duration"?dd(n):e.kind==="moment"?fd(n,t.clock_24h!==!1):""}function Kn(e,t,n){if(En&&En!==c.selected&&Object.keys(Jt).length){us(),ge("Still saving the other screen's settings. Try again in a moment.");return}En=c.selected;const s=wn();if(c.settingEdits[e]={value:t,at:Date.now()},Jt[e]=t,e==="brightness")for(const o of["standby_brightness","night_brightness"])s[o]>t&&(c.settingEdits[o]={value:t,at:Date.now()});c.settingPending=!0,clearTimeout(Ns),Ns=window.setTimeout(()=>us(),n)}async function us(e=!1){if(clearTimeout(Ns),As||!Object.keys(Jt).length||!En)return;const t=En,n=Jt;Jt={};const s=di(`screens/${encodeURIComponent(t)}/settings`,{method:"PUT",body:JSON.stringify({settings:n}),keepalive:e});As=s;try{const o=await(await s).json(),l=c.inventory.screens.find(a=>a.id===t);l&&(l.settings=o)}catch(o){if(ge(o.message),t===c.selected)for(const l of Object.keys(n))delete c.settingEdits[l];if(t===c.selected&&n.brightness!==void 0)for(const l of["standby_brightness","night_brightness"])delete c.settingEdits[l]}finally{As=null,Object.keys(Jt).length?Ns=window.setTimeout(()=>us(),150):En=null,c.settingPending=!!Object.keys(Jt).length,t===c.selected&&Rs(),setTimeout(()=>{t===c.selected&&Rs()},Ka+100)}}function Rs(){const e=vi();for(const[t,n]of Object.entries(c.settingEdits))Jt[t]!==void 0||As||(e&&e.values[t]===n.value||Date.now()-n.at>Ka)&&delete c.settingEdits[t]}const qn={install:"Building and installing…",verify:"Waiting for the screen to come back…",settle:"Checking that it stays stable…"};async function Vo(e,t){c.updating.push(e.id);try{await it(`screens/${encodeURIComponent(e.id)}/update`,"POST",t?{host:t}:{}),await _n()}catch(n){c.updating=c.updating.filter(s=>s!==e.id),ge(n.message)}}async function cl(){try{await it("updates/run","POST"),await _n()}catch(e){ge(e.message)}}async function hd(e){try{await it("updates","PUT",{auto:e}),c.inventory.updates&&(c.inventory.updates.auto=e),ge(e?"Screens will now update automatically at night.":"Automatic updates are off.")}catch(t){ge(t.message)}}async function vd(){try{c.inventory.claude_skill=await it("claude-skill","POST"),ge(c.inventory.claude_skill?.restart?"Skill installed. Restart Claude Code once so it finds the new skills folder.":"Skill installed. Claude Code picks it up right away.")}catch(e){ge(e.message)}}async function _n(e=!0){try{const t=await gt(e?"inventory":"inventory?light=1");c.inventory=e?t:{...c.inventory,...t},t.csrf&&Lc(t.csrf),c.connected=!!c.inventory.connected,c.reachable=!0;for(const n of c.inventory.screens)n.update?.state==="running"&&(c.updating=c.updating.filter(s=>s!==n.id));c.selected&&Rs()}catch{c.reachable=!1}}function md(e){c.inventory={...c.inventory,...e},c.connected=!!c.inventory.connected;for(const t of c.inventory.screens)t.update?.state==="running"&&(c.updating=c.updating.filter(n=>n!==t.id));c.selected&&Rs()}let dl=0,Wo=Date.now(),Hs=!1,Un=null;function yd(){Un||typeof EventSource>"u"||(Un=new EventSource("api/events"),Un.onopen=()=>{Hs=!0,cs()},Un.onmessage=e=>{document.hidden||md(JSON.parse(e.data))},Un.onerror=()=>{Hs=!1,cs()})}function cs(){clearTimeout(dl);const e=Hs?6e4:c.inventory.updates?.busy?3e3:1e4;dl=window.setTimeout(async()=>{if(!document.hidden){const t=Date.now()-Wo>=3e5;t&&(Wo=Date.now()),(t||!Hs)&&await _n(t)}cs()},e)}let fl=!1;function gd(){fl||(fl=!0,_n(),yd(),cs(),Mc(()=>c.fontsVersion++),setInterval(()=>{!c.layout||document.hidden||c.drag.active||(c.now=Date.now(),io(0))},3e4),setInterval(()=>{!document.hidden&&c.layout&&c.tab==="layout"&&rt.value===""&&oo()},8e3),setInterval(()=>{!document.hidden&&Bo()?rd():c.firmwareJob&&!Bo()&&(c.firmwareJob=null)},3e3),document.addEventListener("visibilitychange",async()=>{document.hidden||(Wo=Date.now(),c.now=Date.now(),await _n(),cs())}),window.addEventListener("pagehide",()=>us(!0)),window.addEventListener("beforeunload",e=>{c.dirty&&(e.preventDefault(),e.returnValue="")}))}const bd={class:"side"},wd={id:"screens"},_d=["aria-current","onClick"],kd={class:"txt"},$d={key:0,class:"unsaved",role:"img","aria-label":"Unsaved changes",title:"Unsaved changes"},xd={key:1,class:"pill"},Sd={key:2,class:"spin small"},Cd=["onSubmit"],Ad=["disabled","title","onClick"],Td={key:2,class:"whatsnew"},Ed=["aria-valuenow"],Pd={class:"progress-text"},Od=["title"],Id=["title"],Md={id:"pending"},Ld={class:"pending-actions"},Dd=["onClick"],Fd=["aria-current"],Nd={class:"more"},Rd=["aria-current"],Hd={class:"mdi"},Ud=["aria-current"],jd={class:"mdi"},Bd=["aria-current"],Vd={class:"mdi"},Wd=Ee({__name:"Sidebar",setup(e){const t=pe(null),n=pe("");function s(d){const f=d.update||{};return f.state==="running"||c.updating.includes(d.id)?{kind:"running",text:qn[f.phase||""]||"Starting update…"}:f.state==="queued"?{kind:"queued",text:"Queued for the update"}:f.available&&d.online?{kind:"available",text:`Update ${f.target}`}:f.result&&Date.now()/1e3-f.result.time<86400?{kind:f.result.state==="failed"?"failed":"done",text:f.result.message}:null}function o(d){const f=d.update||{};f.host&&f.profile?Vo(d):f.profile&&(t.value=d.id,n.value="")}function l(d){const f=n.value.trim();f&&(t.value=null,Vo(d,f))}const a=()=>{const d=c.firmwareJob?.logs||[];return d.length?d[d.length-1]:""},u=d=>d.installed?"Installed, but not yet in Home Assistant. Add the discovered ESPHome device under Settings → Devices & services, paste the API key there, and turn on “Allow the device to perform Home Assistant actions” under Configure.":d.downloaded?"Firmware downloaded, not yet in Home Assistant. Put it on the screen with ESPHome Web, then add the discovered ESPHome device under Settings → Devices & services, paste the API key there, and allow the Home Assistant actions under Configure.":`Not yet in Home Assistant. Already flashed? Add the ESPHome device under Settings → Devices & services, then allow the Home Assistant actions under Configure. Not flashed yet? Firmware & USB → ${d.file}.`;return(d,f)=>(p(),v("aside",bd,[f[20]||(f[20]=i("div",{class:"brand"},[i("span",{class:"mark"},"▦"),i("span",null,"ESP Screens")],-1)),i("span",{id:"connection",class:ie(["conn",{online:y(c).connected}]),role:"status"},m(y(c).reachable?y(c).connected?"Home Assistant connected":"Reconnecting to Home Assistant…":"Management page unreachable · retrying…"),3),i("button",{type:"button",class:"search-btn",id:"open-palette",onClick:f[0]||(f[0]=r=>y(c).palette=!0)},[...f[10]||(f[10]=[Z("⌕ Search",-1),i("kbd",null,"⌘K",-1)])]),f[21]||(f[21]=i("div",{class:"label"},"Screens",-1)),i("div",wd,[(p(!0),v(U,null,le(y(c).inventory.screens,r=>(p(),v("div",{key:r.id,class:ie(["screen-item",{selected:r.id===y(c).selected&&y(rt)===""}])},[i("button",{type:"button",class:"nav-item","aria-current":r.id===y(c).selected&&y(rt)===""?"true":"false",onClick:h=>y(Ra)(r.id)},[i("span",{class:ie(["dot",{off:!r.online}])},null,2),i("span",kd,[i("span",null,m(r.name),1),i("small",null,m(r.online?"Online":"Offline")+m(r.area?" · "+r.area:"")+" · "+m(r.firmware||"unknown"),1)]),r.id===y(c).selected&&y(c).dirty?(p(),v("span",$d)):F("",!0),s(r)?.kind==="available"?(p(),v("span",xd,"Update")):s(r)?.kind==="running"?(p(),v("span",Sd)):F("",!0)],8,_d),s(r)?(p(),v("div",{key:0,class:ie(["screen-update",s(r).kind])},[s(r).kind==="available"?(p(),v(U,{key:0},[t.value===r.id?(p(),v(U,{key:0},[f[12]||(f[12]=i("small",null,"The IP address, once; new firmware reports it itself.",-1)),i("form",{class:"screen-host",onSubmit:ft(h=>l(r),["prevent"])},[we(i("input",{"onUpdate:modelValue":f[1]||(f[1]=h=>n.value=h),placeholder:"IP address, e.g. 192.168.1.50",required:"",pattern:"[A-Za-z0-9][A-Za-z0-9.\\-]*","aria-label":"IP address",autofocus:""},null,512),[[Fe,n.value]]),f[11]||(f[11]=i("button",{type:"submit",class:"btn mini primary"},"Start",-1)),i("button",{type:"button",class:"icon-btn","aria-label":"Cancel",onClick:f[2]||(f[2]=h=>t.value=null)},"✕")],40,Cd)],64)):(p(),v(U,{key:1},[i("small",null,m(s(r).text),1),i("button",{type:"button",class:"btn mini primary",disabled:!r.update?.profile,title:r.update?.profile?"":"No ESPHome profile found with this device name.",onClick:h=>o(r)},"Update",8,Ad)],64)),y(ll)(r).length?(p(),v("details",Td,[f[13]||(f[13]=i("summary",null,"What's new",-1)),i("ul",null,[(p(!0),v(U,null,le(y(ll)(r).slice(0,8),h=>(p(),v("li",{key:h},m(h),1))),128))])])):F("",!0)],64)):s(r).kind==="running"&&y(Sn)(r)?(p(),v(U,{key:1},[i("div",{class:"progress",role:"progressbar","aria-valuenow":y(Sn)(r).percent,"aria-valuemin":"0","aria-valuemax":"100"},[i("i",{style:Pe({width:y(Sn)(r).percent+"%"})},null,4)],8,Ed),i("div",Pd,[i("span",null,m(y(Sn)(r).percent)+" %",1),i("span",{title:a()},m(y(Sn)(r).text),9,Od)]),a()?(p(),v("small",{key:0,title:a(),style:{"white-space":"nowrap",overflow:"hidden","text-overflow":"ellipsis"}},m(a()),9,Id)):F("",!0),i("button",{type:"button",class:"btn link mini",style:{"justify-self":"start"},onClick:f[3]||(f[3]=h=>y(Oe)("#firmware"))},"Full log")],64)):(p(),v("small",{key:2,class:ie({failed:s(r).kind==="failed"})},m(s(r).text),3))],2)):F("",!0)],2))),128))]),i("div",Md,[(p(!0),v(U,null,le(y(c).inventory.pending||[],r=>(p(),v("div",{key:r.file,class:"pending"},[i("strong",null,m(r.friendly),1),i("small",null,m(u(r)),1),i("div",Ld,[i("button",{type:"button",class:"btn mini quiet",onClick:f[4]||(f[4]=(...h)=>y(Fs)&&y(Fs)(...h))},"Open Devices & services"),r.api_key?(p(),v("button",{key:0,type:"button",class:"btn mini quiet",onClick:h=>y(ht)(r.api_key)},"Copy API key",8,Dd)):F("",!0)])]))),128))]),i("button",{id:"new-screen",type:"button",class:"nav-item ghost","aria-current":y(rt)==="#new-screen"?"true":"false",onClick:f[5]||(f[5]=r=>y(Oe)("#new-screen"))},[...f[14]||(f[14]=[i("span",{class:"plus"},"+",-1),i("span",{class:"txt"},"New screen",-1)])],8,Fd),f[22]||(f[22]=i("div",{class:"spacer"},null,-1)),i("div",Nd,[f[19]||(f[19]=i("div",{class:"label"},"More",-1)),i("button",{id:"open-firmware",type:"button",class:"nav-item","aria-current":y(rt)==="#firmware"?"true":"false",onClick:f[6]||(f[6]=r=>y(Oe)("#firmware"))},[i("span",Hd,m(y(ke)("F0241")),1),f[15]||(f[15]=i("span",{class:"txt"},"Firmware & USB",-1))],8,Rd),i("button",{id:"open-alerts",type:"button",class:"nav-item","aria-current":y(rt)==="#alerts"?"true":"false",onClick:f[7]||(f[7]=r=>y(Oe)("#alerts"))},[i("span",jd,m(y(ke)("F0594")),1),f[16]||(f[16]=i("span",{class:"txt"},"Alerts",-1))],8,Ud),i("button",{id:"open-settings",type:"button",class:"nav-item","aria-current":y(rt)==="#settings"?"true":"false",onClick:f[8]||(f[8]=r=>y(Oe)("#settings"))},[i("span",Vd,m(y(ke)("F0493")),1),f[17]||(f[17]=i("span",{class:"txt"},"Settings",-1))],8,Bd),i("button",{id:"refresh",type:"button",class:"nav-item ghost",onClick:f[9]||(f[9]=r=>y(_n)())},[...f[18]||(f[18]=[i("span",{class:"plus"},"↻",-1),i("span",{class:"txt"},"Refresh",-1)])])])]))}}),Kd={key:0,id:"toast",class:"toast",role:"status"},qd=Ee({__name:"Toast",setup(e){return(t,n)=>y(c).toast?(p(),v("div",Kd,[i("span",null,m(y(c).toast.message),1),y(c).toast.action?(p(),v("button",{key:0,type:"button",onClick:n[0]||(n[0]=s=>{y(c).toast.action.run(),y(Yc)()})},m(y(c).toast.action.label),1)):F("",!0)])):F("",!0)}}),Yd={class:"palette-list"},zd={class:"palette-group"},Jd=["onMouseenter","onClick"],Gd={key:0,class:"mdi"},Xd={key:1,class:"glyph"},Zd={class:"tx"},Qd={key:0},ef={key:2,class:"hint-key"},tf={key:0,class:"palette-empty"},nf=Ee({__name:"CommandPalette",setup(e){const t=pe(""),n=pe(0),s=pe(null),o=$(()=>{const f=t.value.trim().toLocaleLowerCase(),r=[];for(const k of c.inventory.screens)r.push({group:"Screens",label:k.name,detail:`${k.online?"Online":"Offline"} · ${k.firmware||"unknown"}`,glyphText:"▦",run:()=>Ra(k.id)});const h=lt.value,b=[{group:"Actions",label:"New screen",detail:"Connect and install",glyphText:"+",run:()=>Oe("#new-screen")},{group:"Actions",label:"Firmware & USB",icon:"F0241",run:()=>Oe("#firmware")},{group:"Actions",label:"Alerts",detail:"Cheatsheet and a test alert",icon:"F0594",run:()=>Oe("#alerts")},{group:"Actions",label:"Settings",detail:"Updates, Claude",icon:"F0493",run:()=>Oe("#settings")}];if(h&&c.layout&&b.unshift({group:"Actions",label:"Save & send",detail:c.dirty?"Unsaved changes":"Nothing to save",key:"⌘S",run:()=>jo()},{group:"Actions",label:"Layout",detail:h.name,run:()=>{Oe(""),c.tab="layout"}},{group:"Actions",label:"Screen settings",detail:h.name,run:()=>{Oe(""),c.tab="settings"}},{group:"Actions",label:"Identify this screen",detail:gn(h)?"Blinks the backlight and shows a card":"Needs a newer firmware",run:()=>{gn(h)&&ja(h)}},{group:"Actions",label:"Export layout",detail:"Download and copy as JSON",run:Va}),r.push(...b),h&&c.layout&&f){const k=new Set(c.layout.tiles.map(C=>C.entity)),R=c.layout.tiles.length>=yn.value;for(const C of c.inventory.entities)if(!(C.tile===!1||k.has(C.id))&&`${C.name} ${C.id} ${C.area||""} ${C.device||""}`.toLocaleLowerCase().includes(f)&&(r.push({group:"Add to this screen",label:C.name,detail:[xt(C.id)[0],C.area].filter(Boolean).join(" · "),icon:c.inventory.icons?sn(C.id):void 0,run:()=>{R||Ha(C.id)}}),r.length>60))break}return f?r.filter(k=>`${k.label} ${k.detail||""}`.toLocaleLowerCase().includes(f)):r}),l=$(()=>{const f=[];return o.value.forEach((r,h)=>{(f.find(k=>k.group===r.group)||(f.push({group:r.group,items:[]}),f[f.length-1])).items.push({item:r,index:h})}),f});function a(){c.palette=!1}function u(f){a(),f.run()}function d(f){if(f.key==="Escape")f.preventDefault(),a();else if(f.key==="ArrowDown")f.preventDefault(),n.value=Math.min(o.value.length-1,n.value+1);else if(f.key==="ArrowUp")f.preventDefault(),n.value=Math.max(0,n.value-1);else if(f.key==="Enter"){f.preventDefault();const r=o.value[n.value];r&&u(r)}}return pn(t,()=>n.value=0),pn(()=>c.palette,async f=>{f&&(t.value="",n.value=0,await Js(),s.value?.focus())}),(f,r)=>y(c).palette?(p(),v("div",{key:0,class:"palette-backdrop",onClick:a},[i("div",{class:"palette",role:"dialog","aria-label":"Search",onClick:r[1]||(r[1]=ft(()=>{},["stop"])),onKeydown:d},[we(i("input",{ref_key:"input",ref:s,"onUpdate:modelValue":r[0]||(r[0]=h=>t.value=h),id:"palette-input",placeholder:"Search screens, entities, actions…","aria-label":"Search",autocomplete:"off"},null,512),[[Fe,t.value]]),i("div",Yd,[(p(!0),v(U,null,le(l.value,h=>(p(),v(U,{key:h.group},[i("div",zd,m(h.group),1),(p(!0),v(U,null,le(h.items,({item:b,index:k})=>(p(),v("button",{key:k,type:"button",class:ie(["palette-item",{active:k===n.value}]),onMouseenter:R=>n.value=k,onClick:R=>u(b)},[b.icon?(p(),v("span",Gd,m(y(ke)(b.icon)),1)):(p(),v("span",Xd,m(b.glyphText||"›"),1)),i("span",Zd,[i("span",null,m(b.label),1),b.detail?(p(),v("small",Qd,m(b.detail),1)):F("",!0)]),b.key?(p(),v("kbd",ef,m(b.key),1)):F("",!0)],42,Jd))),128))],64))),128)),o.value.length?F("",!0):(p(),v("p",tf,"Nothing matches. Type part of a screen, entity or action."))])],32)])):F("",!0)}}),re={source:null,element:null,ghost:null,timer:0,start:null,offset:{x:0,y:0},pointerId:null,suppressUntil:0,last:null,scroller:0,target:null},qa={mounted(e,t){e.__dragSource=t.value,e.addEventListener("pointerdown",s=>{const o=e.__dragSource;if(!(s.button!==0||e.disabled||s.target.closest(".remove"))){s.pointerType!=="touch"&&s.preventDefault(),Object.assign(re,{source:o,element:e,start:{x:s.clientX,y:s.clientY},pointerId:s.pointerId});try{e.setPointerCapture(s.pointerId)}catch{}clearTimeout(re.timer),s.pointerType==="touch"&&(re.timer=window.setTimeout(()=>pl(s),260))}}),e.addEventListener("pointermove",s=>{if(c.drag.active||!re.start||re.element!==e)return;const o=Math.hypot(s.clientX-re.start.x,s.clientY-re.start.y);if(s.pointerType==="touch"){o>10&&(clearTimeout(re.timer),re.start=null);return}o>=6&&pl(s)});const n=()=>{re.element===e&&!c.drag.active&&(clearTimeout(re.timer),re.start=null)};e.addEventListener("pointerup",n),e.addEventListener("pointercancel",n)},updated(e,t){e.__dragSource=t.value}};function pl(e){if(c.drag.active||!re.start||!re.source||!re.element)return;c.drag.active=!0,c.drag.moving=re.source.kind==="tile"?re.source.tile:Ma(re.source.id),c.drag.preview=null,re.target=null,getSelection()?.removeAllRanges();const t=re.element.getBoundingClientRect(),n=re.element.cloneNode(!0);n.classList.add("drag-ghost"),n.style.width=`${t.width}px`,n.style.height=`${t.height}px`,re.offset={x:e.clientX-t.left,y:e.clientY-t.top},document.body.append(n),re.ghost=n,document.addEventListener("pointermove",Ko),document.addEventListener("pointerup",Us),document.addEventListener("pointercancel",Us);try{re.element.releasePointerCapture(re.pointerId)}catch{}try{document.documentElement.setPointerCapture(re.pointerId)}catch{}document.addEventListener("touchmove",Ya,{passive:!1}),re.scroller=window.setInterval(()=>{if(!re.last)return;const{x:s,y:o}=sf(document),l=s?ml(re.last.x,vl(s,"x")):0,a=o?ml(re.last.y,vl(o,"y")):0;l&&s.scrollBy(l,0),a&&o.scrollBy(0,a),(l||a)&&qo(za(re.last.x,re.last.y))},16),qo(-1),Ko(e)}function hl(e,t){if(!e)return!1;const n=t==="x"?e.scrollWidth>e.clientWidth:e.scrollHeight>e.clientHeight,s=getComputedStyle(e)[t==="x"?"overflowX":"overflowY"];return n&&(s==="auto"||s==="scroll")}function sf(e){const t=e.querySelector(".pages"),n=e.querySelector(".canvas");return{x:hl(t,"x")?t:n,y:hl(n,"y")?n:e.scrollingElement}}function vl(e,t){const n=t==="x"?window.innerWidth:window.innerHeight;if(e===e.ownerDocument.scrollingElement)return[0,n];const s=e.getBoundingClientRect();return t==="x"?[Math.max(s.left,0),Math.min(s.right,n)]:[Math.max(s.top,0),Math.min(s.bottom,n)]}function ml(e,[t,n]){return en-60?12:0}function Ya(e){c.drag.active&&e.preventDefault()}function Us(e){e.pointerId===re.pointerId&&of(e.type==="pointerup")}function Ko(e){!re.ghost||e.pointerId!==re.pointerId||(re.last={x:e.clientX,y:e.clientY},re.ghost.style.transform=`translate(${e.clientX-re.offset.x}px, ${e.clientY-re.offset.y}px)`,qo(za(e.clientX,e.clientY)))}function za(e,t){let n=null,s=1/0;for(const l of document.querySelectorAll(".pages [data-slot]")){const a=l.getBoundingClientRect(),u=Math.hypot(Math.max(a.left-e,0,e-a.right),Math.max(a.top-t,0,t-a.bottom));u16)return-1;let o=Number(n.cell.dataset.slot);return n.cell.classList.contains("wide")&&e>(n.r.left+n.r.right)/2&&(o+=1),o}function qo(e){re.target===e||!c.layout||!c.drag.moving||(re.target=e,c.drag.preview=e>=0?Ia(c.layout.tiles,c.drag.moving,e):null)}function of(e){const t=c.drag.preview,n=c.drag.moving;document.removeEventListener("pointermove",Ko),document.removeEventListener("pointerup",Us),document.removeEventListener("pointercancel",Us),document.removeEventListener("touchmove",Ya);try{document.documentElement.releasePointerCapture(re.pointerId)}catch{}if(re.ghost?.remove(),Object.assign(re,{ghost:null,suppressUntil:Date.now()+400,last:null,start:null,target:null,element:null,source:null}),clearInterval(re.scroller),c.drag.active=!1,c.drag.preview=null,c.drag.moving=null,e&&t&&n&&c.layout){const s=c.layout.tiles.map(l=>`${l.entity}@${l.slot}`).join(),o=!c.layout.tiles.includes(n);for(const{tile:l,slot:a}of t)l.slot=a,c.layout.tiles.includes(l)||c.layout.tiles.push(l);c.layout.tiles.sort((l,a)=>l.slot-a.slot),c.layout.tiles.map(l=>`${l.entity}@${l.slot}`).join()!==s&&Ze(),o&&ms(n,n.slot)}}window.addEventListener("click",e=>{Date.now()t.tile.name||hs(t.tile.entity)),s=$(()=>Aa(t.tile)),o=$(()=>No(t.tile)&&!s.value),l=$(()=>Mn(t.tile.entity)),a=$(()=>c.inventory.backgrounds?.[t.tile.options?.background||""]?.color),u=$(()=>t.tile.options?.background==="none"),d=$(()=>t.tile.options?.display||"standard"),f=$(()=>d.value!=="standard"?Da[d.value]||d.value:""),r=$(()=>La(t.tile,c.inventory)),h=$(()=>t.tile.entity.split(".")[0]),b=$(()=>c.inventory.icons?.controls||{}),k=Q=>b.value[Q]?ke(b.value[Q]):"",R=$(()=>hi(t.tile)&&c.inspector?.kind==="tile"),C=$(()=>!t.placeholder&&c.layout?.tiles.includes(t.tile)),x=$(()=>`${n.value}, slot ${t.slot%Le+1} on page ${ct(t.slot)+1}. Enter: configure, arrow keys: move`),P=$(()=>new Date(c.now)),E=$(()=>(P.value.getHours()%12+P.value.getMinutes()/60)*30),W=$(()=>P.value.getMinutes()*6),S=$(()=>h.value==="screen"?null:Gc(t.tile.entity)),V=$(()=>!S.value||["unavailable","unknown",""].includes(S.value.state)),I=$(()=>!!S.value&&!V.value&&S.value.state!=="off"&&S.value.state!=="closed"&&S.value.state!=="standby"&&S.value.state!=="idle"&&S.value.state!=="docked"),T=$(()=>["light","switch","input_boolean","fan"].includes(h.value)&&S.value?.state==="on"),w=$(()=>S.value?.a?.unit_of_measurement),A=Q=>Q.charAt(0).toUpperCase()+Q.slice(1).replace(/_/g," "),te={"clear-night":"Clear, night",cloudy:"Cloudy",exceptional:"Exceptional",fog:"Fog",hail:"Hail",lightning:"Lightning","lightning-rainy":"Lightning, rainy",partlycloudy:"Partly cloudy",pouring:"Pouring",rainy:"Rainy",snowy:"Snowy","snowy-rainy":"Snowy, rainy",sunny:"Sunny",windy:"Windy","windy-variant":"Windy"},$e=["scene","script","button","input_button"],Me=$(()=>{const Q=S.value;if(!Q||$e.includes(h.value))return f.value;if(V.value)return Q.state==="unknown"?"Unknown":"Unavailable";const q=Q.a||{};return h.value==="climate"?`${q.current_temperature!==void 0?`${q.current_temperature}° · `:""}${Q.word||A(Q.state)}`:h.value==="weather"?`${Q.word||te[Q.state]||A(Q.state)}${q.temperature!==void 0?` · ${q.temperature}°`:""}`:h.value==="cover"&&q.current_position!==void 0&&q.current_position>0&&q.current_position<100?`${Q.word||A(Q.state)} · ${q.current_position} %`:h.value==="media_player"&&q.media_title?`${Q.word||A(Q.state)} · ${q.media_title}`:h.value==="sensor"?`${Q.state}${w.value?` ${w.value}`:""}`:(h.value==="timer",Q.word||A(Q.state))}),X=$(()=>S.value&&!V.value?S.value.state:"—"),j=$(()=>{const Q=S.value;if(!Q||V.value)return 0;const q=Q.a||{};if(h.value==="light")return Q.state==="on"?q.brightness!==void 0?Math.round(q.brightness/255*100):100:0;if(h.value==="fan")return Q.state==="on"?q.percentage??100:0;if(h.value==="cover")return 100-(q.current_position??(Q.state==="open"?100:0));if(h.value==="media_player")return Math.round((q.volume_level??0)*100);if(h.value==="number"||h.value==="input_number"){const B=Number(Q.state),ne=Number(q.min??0),Qe=Number(q.max??100);return Number.isFinite(B)&&Qe>ne?Math.round((B-ne)/(Qe-ne)*100):0}return 0}),J=$(()=>({background:`linear-gradient(to right, ${j.value?"#ffbf38":"#c9ccd1"} ${j.value}%, ${j.value?"#fff1d3":"#e6e8ec"} ${j.value}%)`})),ce=$(()=>({background:`linear-gradient(to right, #2196f3 ${j.value}%, #d3e8fb ${j.value}%)`})),de=$(()=>{const Q=S.value?.a?.temperature;return Q!=null?`${Q}°`:"—"});async function he(Q){if(Q.key==="Enter"||Q.key===" "){Q.preventDefault(),Uo(t.tile);return}const q={ArrowLeft:-1,ArrowRight:1,ArrowUp:-2,ArrowDown:2}[Q.key];q&&(Q.preventDefault(),ms(t.tile,t.tile.slot+(s.value?Math.sign(q)*Le:o.value?Math.sign(q)*2:q))&&(await Js(),document.querySelector(`.pages [data-slot="${t.tile.slot}"]`)?.focus()))}return(Q,q)=>we((p(),v("div",{class:ie(["tile",{wide:o.value,full:s.value,bare:u.value,placeholder:e.placeholder||!C.value,chosen:R.value}]),"data-slot":e.slot,style:Pe(a.value&&!u.value?{backgroundColor:a.value}:void 0),tabindex:C.value?0:-1,role:C.value?"button":void 0,"aria-label":C.value?x.value:void 0,onClick:q[1]||(q[1]=B=>C.value&&y(Uo)(e.tile)),onKeydown:q[2]||(q[2]=B=>C.value&&he(B))},[d.value==="analog"?(p(),v(U,{key:0},[(p(),v("svg",af,[q[3]||(q[3]=i("circle",{cx:"30",cy:"30",r:"27",fill:"#fff",stroke:"#c9ccd1"},null,-1)),(p(),v(U,null,le([0,90,180,270],B=>i("line",{key:B,x1:"30",y1:"5",x2:"30",y2:"9",stroke:"#1b1b1b","stroke-width":"1.5",transform:`rotate(${B} 30 30)`},null,8,rf)),64)),i("line",{x1:"30",y1:"30",x2:"30",y2:"16",stroke:"#1b1b1b","stroke-width":"2.4","stroke-linecap":"round",transform:`rotate(${E.value} 30 30)`},null,8,uf),i("line",{x1:"30",y1:"30",x2:"30",y2:"11",stroke:"#1b1b1b","stroke-width":"1.6","stroke-linecap":"round",transform:`rotate(${W.value} 30 30)`},null,8,cf),q[4]||(q[4]=i("circle",{cx:"30",cy:"30",r:"1.8",fill:"#1b1b1b"},null,-1))])),o.value?(p(),v("span",df,[i("span",ff,[i("span",pf,m(n.value),1),i("span",hf,m(f.value),1)])])):F("",!0)],64)):d.value==="digital"&&h.value==="screen"?(p(),v(U,{key:1},[i("span",vf,m(y(ke)(y(Vn)(e.tile))),1),i("span",mf,[i("span",yf,m(String(P.value.getHours()).padStart(2,"0"))+":"+m(String(P.value.getMinutes()).padStart(2,"0")),1),i("span",gf,m(n.value),1)])],64)):s.value?(p(),v(U,{key:2},[i("span",{class:ie(["ic mdi",{lit:T.value}])},m(y(ke)(y(Vn)(e.tile))),3),i("span",bf,[i("span",wf,m(n.value),1),l.value?(p(),v("span",_f,"Page "+m(l.value)+" ›",1)):d.value==="watch"?(p(),v("span",kf,[Z(m(X.value),1),w.value&&!V.value?(p(),v("small",$f,m(w.value),1)):F("",!0)])):Me.value?(p(),v("span",{key:2,class:ie(["st",{off:V.value}])},m(Me.value),3)):F("",!0)]),e.tile.options?.inline==="slider"?(p(),v("span",{key:0,class:"mini-slider",style:Pe(J.value)},null,4)):F("",!0),r.value?(p(),v("span",xf,[r.value==="toggle"?(p(),v("span",{key:0,class:ie(["tog",{off:!I.value}])},null,2)):r.value==="setpoint"?(p(),v("span",Sf,[i("span",Cf,m(k("minus")||"−"),1),i("b",null,m(de.value),1),i("span",Af,m(k("plus")||"+"),1)])):r.value==="volume"?(p(),v(U,{key:2},[i("span",{class:"range",style:Pe(ce.value)},null,4),i("span",Tf,m(k("volume-high")),1)],64)):r.value==="playback"?(p(),v(U,{key:3},[i("span",Ef,m(k("skip-previous")),1),i("span",Pf,m(k(I.value?"pause":"play")||k("play")),1),i("span",Of,m(k("skip-next")),1)],64)):r.value==="buttons"&&h.value==="cover"?(p(),v(U,{key:4},[i("span",If,m(k("arrow-expand-horizontal")),1),i("span",Mf,m(k("stop")),1),i("span",Lf,m(k("arrow-collapse-horizontal")),1)],64)):r.value==="run"?(p(),v("span",Df,m({scene:"Activate",script:"Run"}[h.value]||"Press"),1)):(p(),v("span",{key:6,class:"range",style:Pe({background:`linear-gradient(to right, #2196f3 ${j.value}%, #d3e8fb ${j.value}%)`})},null,4))])):F("",!0)],64)):o.value?(p(),v(U,{key:3},[i("span",Ff,[i("span",{class:ie(["ic mdi",{lit:T.value}])},m(y(ke)(y(Vn)(e.tile))),3),i("span",Nf,[i("span",Rf,m(n.value),1),l.value?(p(),v("span",Hf,"Page "+m(l.value)+" ›",1)):d.value==="watch"?(p(),v("span",Uf,[Z(m(X.value),1),w.value&&!V.value?(p(),v("small",jf,m(w.value),1)):F("",!0)])):Me.value?(p(),v("span",{key:2,class:ie(["st",{off:V.value}])},m(Me.value),3)):F("",!0)])]),e.tile.options?.inline==="slider"?(p(),v("span",{key:0,class:"mini-slider",style:Pe(J.value)},null,4)):F("",!0),r.value?(p(),v("span",Bf,[r.value==="toggle"?(p(),v("span",{key:0,class:ie(["tog",{off:!I.value}])},null,2)):r.value==="setpoint"?(p(),v("span",Vf,[i("span",Wf,m(k("minus")||"−"),1),i("b",null,m(de.value),1),i("span",Kf,m(k("plus")||"+"),1)])):r.value==="stepper"&&h.value.endsWith("select")?(p(),v(U,{key:2},[i("span",qf,m(k("chevron-left")),1),i("span",Yf,m(k("chevron-right")),1)],64)):r.value==="stepper"?(p(),v("span",zf,[i("span",Jf,m(k("minus")||"−"),1),i("b",null,m(X.value),1),i("span",Gf,m(k("plus")||"+"),1)])):r.value==="mode"?(p(),v(U,{key:4},[i("span",Xf,m(k("power")),1),i("span",Zf,m(k("fire")),1),i("span",Qf,m(k("snowflake")),1)],64)):r.value==="volume"?(p(),v(U,{key:5},[i("span",{class:"range",style:Pe(ce.value)},null,4),i("span",ep,m(k("volume-high")),1)],64)):r.value==="playback"?(p(),v(U,{key:6},[i("span",tp,m(k("skip-previous")),1),i("span",np,m(k(I.value?"pause":"play")||k("play")),1),i("span",sp,m(k("skip-next")),1)],64)):r.value==="buttons"&&h.value==="cover"?(p(),v(U,{key:7},[i("span",op,m(k("arrow-expand-horizontal")),1),i("span",ip,m(k("stop")),1),i("span",lp,m(k("arrow-collapse-horizontal")),1)],64)):r.value==="buttons"&&h.value==="vacuum"?(p(),v(U,{key:8},[i("span",ap,m(k("play")),1),i("span",rp,m(k("stop")),1),i("span",up,m(k("home-map-marker")),1)],64)):r.value==="buttons"&&h.value==="timer"?(p(),v(U,{key:9},[i("span",cp,m(k("play")),1),i("span",dp,m(k("close")),1)],64)):r.value==="run"?(p(),v("span",fp,m({scene:"Activate",script:"Run"}[h.value]||"Press"),1)):(p(),v("span",{key:11,class:"range",style:Pe({background:`linear-gradient(to right, #2196f3 ${j.value}%, #d3e8fb ${j.value}%)`})},null,4))])):F("",!0)],64)):(p(),v(U,{key:4},[i("span",{class:ie(["head",{top:d.value==="watch"}])},[i("span",{class:ie(["ic mdi",{lit:T.value}])},m(y(ke)(y(Vn)(e.tile))),3),i("span",pp,[i("span",hp,m(n.value),1),l.value?(p(),v("span",vp,"Page "+m(l.value)+" ›",1)):d.value!=="watch"&&Me.value?(p(),v("span",{key:1,class:ie(["st",{off:V.value}])},m(Me.value),3)):F("",!0)])],2),d.value==="watch"?(p(),v("span",mp,[Z(m(X.value),1),w.value&&!V.value?(p(),v("small",yp,m(w.value),1)):F("",!0)])):F("",!0),e.tile.options?.inline==="slider"?(p(),v("span",{key:1,class:"mini-slider",style:Pe(J.value)},null,4)):F("",!0)],64)),C.value?(p(),v("button",{key:5,type:"button",class:"remove",title:"Remove tile","aria-label":`Remove ${n.value}`,onClick:q[0]||(q[0]=ft(B=>y(Ua)(e.tile),["stop"]))},"✕",8,gp)):F("",!0)],46,lf)),[[y(qa),{kind:"tile",tile:e.tile}]])}}),wp=["viewBox","aria-label"],_p=["y"],kp=["cx","cy","r","stroke","stroke-width"],$p=["x1","y1","x2","y2","stroke","stroke-width"],xp=["x","y","fill"],Sp=["x","y"],Ja=Ee({__name:"TopbarSvg",props:{items:{},nameText:{},single:{type:Boolean}},setup(e,{expose:t}){const n=e,s=$(()=>{c.fontsVersion,c.now,c.topbarPreviews;const x=n.items||Vt();return Sa(x,Na.value,n.nameText??(c.layout?.title||"Home"),qt)}),o=$(()=>s.value.metrics),l=$(()=>n.single?Math.round(o.value.text*1.4):o.value.top+Math.round(o.value.name*.45)),a=$(()=>n.single?Math.round(o.value.text*1.05):o.value.top),u=$(()=>{if(!n.single)return s.value.placed;const x=s.value.parts[0];return x&&(x.x=0),x?[x]:[]}),d=$(()=>n.single?`-2 0 ${Math.max(1,u.value[0]?.width||1)+4} ${l.value}`:`0 0 ${o.value.width} ${l.value}`),f=$(()=>a.value+(s.value.zero.top+s.value.zero.bottom)/2),r=$(()=>Ic(s.value.nameText,s.value.fonts.name,Math.min(s.value.natural,s.value.nameRoom))),h=$(()=>new Date(c.now));function b(x){const P=h.value;return[[(P.getHours()%12+P.getMinutes()/60)*30,x*.24],[P.getMinutes()*6,x*.34]].map(([E,W])=>{const S=E*Math.PI/180;return{x2:W*Math.sin(S),y2:-W*Math.cos(S)}})}const k=x=>x.x-x.icon.ink.left,R=x=>(x.icon?x.x+(x.icon.ink.right-x.icon.ink.left)+s.value.gaps.icon:x.x)-x.text.ink.left,C=$(()=>`${(Math.max(1,u.value[0]?.width||1)+4)*(26/o.value.icon)}px`);return t({lay:s}),(x,P)=>(p(),v("svg",{viewBox:d.value,role:"img","aria-label":e.single?"How it looks on the screen":`Top bar: ${s.value.nameText}`,style:Pe(e.single?{width:C.value}:void 0)},[e.single?F("",!0):(p(),v("text",{key:0,x:"0",y:o.value.top,fill:"#1b1b1b",style:Pe({font:s.value.fonts.name})},m(r.value),13,_p)),i("g",null,[(p(!0),v(U,null,le(u.value,E=>(p(),v(U,{key:E.index},[E.dial?(p(),v(U,{key:0},[i("circle",{cx:E.x+E.dial/2,cy:f.value,r:E.dial/2-Math.max(1,Math.round(E.dial/10))/2,fill:"none",stroke:E.view.color||"#46525e","stroke-width":Math.max(1,Math.round(E.dial/10))},null,8,kp),(p(!0),v(U,null,le(b(E.dial),(W,S)=>(p(),v("line",{key:S,x1:E.x+E.dial/2,y1:f.value,x2:E.x+E.dial/2+W.x2,y2:f.value+W.y2,stroke:E.view.color||"#46525e","stroke-width":Math.max(1,Math.round(E.dial*.075)),"stroke-linecap":"round"},null,8,$p))),128))],64)):(p(),v(U,{key:1},[E.icon?(p(),v("text",{key:0,x:k(E),y:f.value-(E.icon.ink.top+E.icon.ink.bottom)/2,fill:E.view.color||"#46525e",style:Pe({font:s.value.fonts.icon})},m(E.icon.glyph),13,xp)):F("",!0),E.text?(p(),v("text",{key:1,x:R(E),y:a.value,fill:"#46525e",style:Pe({font:s.value.fonts.text})},m(E.text.value),13,Sp)):F("",!0)],64))],64))),128))])],12,wp))}}),Cp={class:"page"},Ap={class:"page-label"},Tp={key:1},Ep={class:"tiles"},Pp=["data-slot","aria-label","onClick"],Op=Ee({__name:"DevicePage",props:{page:{},entries:{},pages:{},moving:{}},setup(e){const t=e,n=$(()=>new Map(t.entries.map(f=>[f.slot,f]))),s=$(()=>new Set(t.entries.flatMap(f=>mn(f.slot,Je(f.tile)).slice(1)))),o=$(()=>Array.from({length:Le},(f,r)=>t.page*Le+r).filter(f=>!s.value.has(f))),l=$(()=>t.pages>1&&!t.entries.some(f=>ct(f.slot)===t.page)),a=$(()=>c.inspector?.kind==="bar"||c.inspector?.kind==="bar-add"),u=$(()=>t.entries.filter(f=>ct(f.slot)===t.page).reduce((f,r)=>f+fi(Je(r.tile)),0));function d(f){const r=c.insertAt===f;c.insertAt=r?-1:f,c.insertAt>=0&&document.querySelector("#search")?.focus()}return(f,r)=>(p(),v("div",Cp,[i("div",Ap,[i("span",null,"Page "+m(e.page+1),1),l.value?(p(),v("button",{key:0,type:"button",class:"btn mini",title:"The pages after this one shift up one slot",onClick:r[0]||(r[0]=h=>y(Qc)(e.page))},"Remove page")):(p(),v("span",Tp,m(u.value)+" / "+m(y(Le)),1))]),i("div",{class:ie(["device",{cyd:!y(pi)}])},[i("div",{class:ie(["bar-wrap",{selected:a.value}]),title:"Edit top bar",role:"button",tabindex:"0",onClick:r[1]||(r[1]=h=>y(Lt)(0)),onKeydown:r[2]||(r[2]=$a(ft(h=>y(Lt)(0),["prevent"]),["enter"]))},[_e(Ja)],34),i("div",Ep,[(p(!0),v(U,null,le(o.value,h=>(p(),v(U,{key:h},[n.value.get(h)?(p(),Xe(bp,{key:0,tile:n.value.get(h).tile,slot:h,placeholder:n.value.get(h).tile===e.moving},null,8,["tile","slot","placeholder"])):(p(),v("button",{key:1,type:"button",class:ie(["cell",{"insert-here":y(c).insertAt===h}]),"data-slot":h,title:"Empty slot. Click to add a tile here, or drag one over.","aria-label":`Empty slot ${h%y(Le)+1} on page ${e.page+1}: add the next tile here`,onClick:b=>d(h)},[r[3]||(r[3]=i("span",null,"+",-1)),i("small",null,m(y(c).insertAt===h?"Next tile goes here":"Empty"),1)],10,Pp))],64))),128))])],2)]))}}),Ip={class:"library",id:"library"},Mp={class:"lib-head"},Lp={class:"lib-title"},Dp={class:"filters",id:"filters"},Fp=["aria-pressed","onClick"],Np={class:"lib-row"},Rp=["value"],Hp=["aria-pressed"],Up={class:"lib-list",id:"results","aria-live":"polite"},jp=["title","disabled","onClick"],Bp={class:"tx"},Vp={key:0,class:"hint"},Wp={key:1,class:"hint"},Kp={class:"lib-foot"},qp=Ee({__name:"Library",setup(e){const t=[["","All"],["light","Lights"],["climate","Climate"],["switch","Switches"],["binary_sensor","Status"],["button","Actions"],["script","Scripts"],["fan","Fans"],["cover","Covers"],["scene","Scenes"],["vacuum","Vacuum"],["sensor","Sensors"],["media_player","Media"],["weather","Weather"],["number","Values"],["select","Selects"],["person","People"],["timer","Timers"],["screen","Screen"]],n={switch:"input_boolean",number:"input_number",select:"input_select",weather:"sun",button:"input_button"},s=$(()=>new Set(c.layout?.tiles.map(r=>r.entity)||[])),o=r=>s.value.has(r)&&!no(r),l=$(()=>[...new Set(c.inventory.entities.map(r=>r.area).filter(r=>!!r))].sort((r,h)=>r.localeCompare(h))),a=$(()=>{const r=c.search.toLocaleLowerCase(),h=c.filter,b=c.room;return[...c.inventory.builtin||[],...c.inventory.entities].filter(k=>k.tile!==!1&&(pi.value||!["camera","image"].includes(k.id.split(".")[0]))&&(!h||k.id.startsWith(h+".")||n[h]===k.id.split(".")[0])&&(!b||k.area===b)&&(!c.hidePlaced||!o(k.id))&&`${k.name} ${k.id} ${k.device||""} ${k.area||""}`.toLocaleLowerCase().includes(r))}),u=$(()=>(c.layout?.tiles.length||0)>=yn.value),d=$(()=>c.inventory.entities.length),f=r=>{const h=r.id.split(".")[0];return r.state==="unavailable"||r.state==="unknown"?"gone":["light","switch","input_boolean","fan"].includes(h)&&r.state==="on"?"on":""};return(r,h)=>(p(),v("aside",Ip,[i("div",Mp,[i("div",Lp,[h[3]||(h[3]=Z("Library ",-1)),i("small",null,m(d.value)+" entities",1)]),we(i("input",{id:"search","onUpdate:modelValue":h[0]||(h[0]=b=>y(c).search=b),type:"search",placeholder:"Search by name, device, or room…",autocomplete:"off","aria-label":"Add an entity"},null,512),[[Fe,y(c).search]]),i("div",Dp,[(p(),v(U,null,le(t,([b,k])=>i("button",{key:b,type:"button","aria-pressed":y(c).filter===b?"true":"false",onClick:R=>y(c).filter=b},[b?(p(),v("span",{key:0,class:"domain-icon",style:Pe({color:y(xt)(b+".")[2],background:y(xt)(b+".")[3]}),"aria-hidden":"true"},m(y(xt)(b+".")[1]),5)):F("",!0),Z(m(k),1)],8,Fp)),64))]),i("div",Np,[we(i("select",{id:"room","onUpdate:modelValue":h[1]||(h[1]=b=>y(c).room=b),"aria-label":"Room"},[h[4]||(h[4]=i("option",{value:""},"All rooms",-1)),(p(!0),v(U,null,le(l.value,b=>(p(),v("option",{key:b,value:b},m(b),9,Rp))),128))],512),[[vn,y(c).room]]),i("button",{type:"button",class:"chip-toggle",id:"hide-placed","aria-pressed":y(c).hidePlaced?"true":"false",title:"Hide what is already on this screen",onClick:h[2]||(h[2]=b=>y(c).hidePlaced=!y(c).hidePlaced)},"Hide placed",8,Hp)])]),i("div",Up,[(p(!0),v(U,null,le(a.value.slice(0,80),b=>we((p(),v("button",{key:b.id,type:"button",class:"ent",title:b.id,disabled:o(b.id)||u.value,onClick:k=>y(Ha)(b.id)},[i("span",{class:ie(["av mdi",f(b)]),style:Pe({color:y(xt)(b.id)[2],background:y(xt)(b.id)[3]})},m(y(c).inventory.icons?y(ke)(y(sn)(b.id)):y(xt)(b.id)[1]),7),i("span",Bp,[i("b",null,m(b.name),1),i("small",null,m([y(xt)(b.id)[0],b.area,b.device].filter(Boolean).join(" · ")),1)]),i("span",{class:ie(["add",{done:o(b.id)}])},m(o(b.id)?"✓":"+"),3)],8,jp)),[[y(qa),{kind:"entity",id:b.id}]])),128)),a.value.length?a.value.length>80?(p(),v("p",Wp,m(a.value.length)+" results. Keep typing to narrow it down.",1)):F("",!0):(p(),v("p",Vp,m(y(c).hidePlaced&&!y(c).search&&!y(c).filter&&!y(c).room?"Everything here is already on this screen.":"No entities found. Try a different name, room or filter."),1))]),i("div",Kp,m(u.value?`This screen holds ${y(yn)} tiles${y(yn)<48?"; update its firmware for 48":""}.`:"Drag onto a page, or tap + to add to the first free slot. ⌘K searches everything."),1)]))}}),Yp={class:"canvas-head"},zp={id:"count"},Jp={key:0,id:"no-tiles"},Gp={key:1},Xp={key:2,id:"positions-hint",class:"warn"},Zp={key:3,id:"page-reach-hint",class:"warn"},Qp={class:"pages",id:"layout-preview","aria-label":"Screen layout"},eh={class:"page-label"},th=["tabindex"],nh=Ee({__name:"LayoutView",setup(e){const t=$(()=>c.layout),n=$(()=>c.drag.preview||He(t.value)),s=$(()=>pt(n.value,t.value.pages)),o=$(()=>td()),l=$(()=>s.valueFc(t.value.tiles)&&!Zt(0,2,26)?`Empty slots and fixed positions work from firmware 0.2.26. This screen (firmware ${lt.value?.firmware||"unknown"}) shifts the tiles up to the first free slot until that update.`:""),u=$(()=>Wa(n.value,s.value));function d(f){f.target.closest(".device, .page-label, .canvas-head")||c.inspector&&Ue()}return(f,r)=>(p(),v(U,null,[i("div",{class:"canvas",id:"canvas",onClick:d},[i("div",Yp,[i("b",zp,m(s.value)+" "+m(s.value===1?"page":"pages")+" · "+m(t.value.tiles.length)+" / "+m(y(yn))+" tiles",1),t.value.tiles.length?(p(),v("span",Gp,"Tap the top bar or a tile to change it. Drag to move; drop on the next page for a new one.")):(p(),v("span",Jp,"Add your first light, scene, or device from the library.")),a.value?(p(),v("span",Xp,m(a.value),1)):F("",!0),u.value?(p(),v("span",Zp,m(u.value),1)):F("",!0)]),i("div",Qp,[(p(!0),v(U,null,le(o.value,h=>(p(),Xe(Op,{key:h,page:h-1,entries:n.value,pages:s.value,moving:y(c).drag.moving},null,8,["page","entries","pages","moving"]))),128)),i("div",{class:ie(["page ghost",{disabled:!l.value}])},[i("div",eh,[i("span",null,"Page "+m(o.value+1),1)]),i("div",{class:ie(["device",{cyd:y(lt)?.board!=="guition"}]),id:"add-page",role:"button",tabindex:l.value?0:-1,onClick:r[0]||(r[0]=h=>l.value&&y(ol)()),onKeydown:r[1]||(r[1]=$a(ft(h=>l.value&&y(ol)(),["prevent"]),["enter"]))},m(l.value?"+ Add page":`${y(Bt)} pages is the most`),43,th)],2)])]),_e(qp)],64))}}),sh={class:"status",id:"settings-status",role:"status"},oh={key:0,class:"set-grid",id:"settings-groups"},ih={class:"mdi"},lh=["data-setting","title","onClick"],ah=["id"],rh={class:"s-control"},uh=["id","aria-checked","aria-labelledby","disabled","onClick"],ch=["aria-labelledby"],dh=["aria-pressed","disabled","onClick"],fh={key:2,class:"step"},ph=["aria-label","disabled","onPointerdown","onClick"],hh={class:"mdi"},vh=["id","aria-labelledby"],mh=["aria-label","disabled","onPointerdown","onClick"],yh={class:"mdi"},gh={key:0,class:"hint warn",id:"settings-page-reach"},bh=Ee({__name:"SettingsTab",setup(e){const t=$(()=>vi()),n=$(()=>wn()),s=$(()=>t.value?.owner==="screen"&&!lt.value?.online),o=$(()=>ud.map(C=>({...C,rows:C.rows.filter(x=>t.value?.keys.includes(x.key))})).filter(C=>C.rows.length)),l=$(()=>Wa()),a=C=>s.value||!!t.value?.unavailable.includes(C.key),u=C=>C.needs?!!n.value[C.needs]:!0,d=$(()=>s.value?"This screen is offline. Its settings can change once it's back.":c.settingPending?"Saving…":t.value?.owner==="screen"?"Changes apply on the screen at once, and show up here when they change there.":"Changes apply at once. Firmware 0.2.49 lets the screen keep them itself."),f=(C,x)=>{const P=n.value[C.key];return a(C)||!u(C)||P===null||P===void 0?!0:C.kind!=="moment"&&ul(C,P,x,!1,n.value)===P},r=new WeakMap;function h(C,x,P){const E=wn(),W=ul(C,E[C.key],x,P,E);W!==E[C.key]&&Kn(C.key,W,600)}function b(C,x,P){const E=C.currentTarget;if(E.disabled||C.button!==0)return;const W={timer:0,held:!1};r.set(E,W);let S=0;W.timer=window.setTimeout(function V(){W.held=!0,S+=1,h(x,P,S>5),W.timer=window.setTimeout(V,180)},450)}function k(C){const x=r.get(C.currentTarget);x&&clearTimeout(x.timer)}function R(C,x,P){const E=r.get(C.currentTarget);if(E?.held){E.held=!1;return}h(x,P,!1)}return(C,x)=>(p(),v("div",{class:ie(["settings",{offline:s.value}]),id:"general-settings"},[i("span",sh,m(d.value),1),t.value?(p(),v("div",oh,[(p(!0),v(U,null,le(o.value,P=>(p(),v("section",{key:P.title,class:"set-card"},[i("h4",null,[i("span",ih,m(y(ke)(P.icon)),1),Z(m(P.title),1)]),(p(!0),v(U,null,le(P.rows,E=>(p(),v("div",{key:E.key,class:ie(["srow",[`setting-${E.kind}`,{inactive:!u(E)||a(E)}]]),"data-setting":E.key,title:a(E)&&!s.value?"This entity is off in Home Assistant, or the screen is restarting.":"",onClick:W=>E.kind==="toggle"&&W.target.closest(".srow")===W.currentTarget&&!W.target.closest("button")&&!a(E)&&y(Kn)(E.key,!n.value[E.key],150)},[i("span",{class:"s-label",id:`setting-label-${E.key}`},m(E.label),9,ah),i("div",rh,[E.kind==="toggle"?(p(),v("button",{key:0,type:"button",class:ie(["switch",{unknown:n.value[E.key]===null||n.value[E.key]===void 0}]),role:"switch",id:`setting-${E.key}`,"aria-checked":n.value[E.key]?"true":"false","aria-labelledby":`setting-label-${E.key}`,disabled:a(E),onClick:ft(W=>y(Kn)(E.key,!n.value[E.key],150),["stop"])},null,10,uh)):E.kind==="choice"?(p(),v("div",{key:1,class:"seg",role:"group","aria-labelledby":`setting-label-${E.key}`},[(p(!0),v(U,null,le(E.options,([W,S])=>(p(),v("button",{key:String(W),type:"button","aria-pressed":n.value[E.key]===W?"true":"false",disabled:a(E),onClick:V=>y(Kn)(E.key,W,150)},m(S),9,dh))),128))],8,ch)):(p(),v("div",fh,[i("button",{type:"button","aria-label":`${E.label} lower`,disabled:f(E,-1),onPointerdown:W=>b(W,E,-1),onPointerup:k,onPointercancel:k,onPointerleave:k,onClick:W=>R(W,E,-1)},[i("span",hh,m(y(ke)("F0374")),1)],40,ph),i("output",{id:`setting-${E.key}`,"aria-labelledby":`setting-label-${E.key}`},m(y(pd)(E,n.value)),9,vh),i("button",{type:"button","aria-label":`${E.label} higher`,disabled:f(E,1),onPointerdown:W=>b(W,E,1),onPointerup:k,onPointercancel:k,onPointerleave:k,onClick:W=>R(W,E,1)},[i("span",yh,m(y(ke)("F0415")),1)],40,mh)]))])],10,lh))),128)),l.value&&P.rows.some(E=>E.key==="page_buttons")?(p(),v("p",gh,m(l.value),1)):F("",!0)]))),128))])):F("",!0)],2))}}),wh={class:"seg wrap",role:"group"},_h=["disabled","aria-pressed","onClick"],tt=Ee({__name:"Segmented",props:{choices:{},value:{},disabled:{type:Boolean}},emits:["pick"],setup(e,{emit:t}){const n=t;return(s,o)=>(p(),v("div",wh,[(p(!0),v(U,null,le(e.choices,([l,a])=>(p(),v("button",{key:String(l),type:"button",disabled:e.disabled,"aria-pressed":String(l)===String(e.value)?"true":"false",onClick:u=>n("pick",l)},m(a),9,_h))),128))]))}}),kh={class:"f"},$h={key:0},xh={key:1,class:"warn"},Sh=["aria-expanded"],Ch={class:"tx"},Ah={key:0,class:"mono"},Th={class:"link"},Eh={key:0,class:"picker"},Ph={class:"action-list"},Oh=["aria-pressed","onClick"],Ih={key:0,class:"hint"},Mh={key:1,class:"warn"},Lh={class:"f-label"},Dh=["aria-label","value","onChange"],Fh=["value"],Nh={key:3,class:"action-number"},Rh=["aria-label","value","onInput"],Hh=["onClick"],Uh={key:4,class:"action-number"},jh=["aria-label","min","max","step","value","placeholder","onInput"],Bh={key:0},Vh=["aria-label","value","placeholder","onInput"],Wh={key:6},Kh={key:0,class:"warn"},qh={key:3},Yh=Ee({__name:"ActionPicker",props:{tile:{}},setup(e){const t=e,n=$(()=>c.entityActions[t.tile.entity]),s=$(()=>t.tile.options?.action),o=$(()=>n.value?.find(S=>S.action===s.value?.action)),l=$(()=>c.actionPickerOpen||!s.value),a=$(()=>{const S=c.actionSearch.trim().toLocaleLowerCase();return(n.value||[]).filter(V=>`${V.name} ${V.action} ${V.description}`.toLocaleLowerCase().includes(S))});n.value===void 0&&Jc(t.tile.entity);function u(S){const V=S.action===s.value?.action;t.tile.options={...t.tile.options,action:V?s.value:{action:S.action}},c.actionPickerOpen=!1,Ze()}function d(S,V){const I={...t.tile.options?.action?.data||{}};V===void 0?delete I[S]:I[S]=V,t.tile.options={...t.tile.options,action:{action:s.value.action,...Object.keys(I).length?{data:I}:{}}},Ze()}const f=$(()=>{if(!o.value)return[];const S=t.tile.options?.action?.data||{};return o.value.fields.filter(V=>V.required&&S[V.key]===void 0).map(V=>V.name)}),r=S=>Object.keys(S.selector||{})[0]||"text",h=S=>(S.selector||{})[r(S)]||{},b=S=>S.example===void 0?"":typeof S.example=="string"?S.example:JSON.stringify(S.example);function k(S){const V=h(S),I=r(S);return Array.isArray(S.options)?S.options.map(T=>[T,T]):I==="select"&&Array.isArray(V.options)&&!V.multiple?V.options.map(T=>T&&typeof T=="object"?[String(T.value),String(T.label??T.value)]:[String(T),String(T)]):null}const R=S=>"#"+S.map(V=>Math.max(0,Math.min(255,Number(V)||0)).toString(16).padStart(2,"0")).join(""),C=S=>Array.isArray(S)&&S.length===3?R(S):"#ffffff",x=S=>S===void 0?"":typeof S=="string"?S:JSON.stringify(S);function P(S,V){const I=V.trim();if(!I)return d(S.key,void 0);if(r(S)==="text")return d(S.key,V);try{d(S.key,JSON.parse(I))}catch{d(S.key,V)}}function E(S,V){d(S.key,V===""||!Number.isFinite(Number(V))?void 0:Number(V))}const W=S=>h(S).unit_of_measurement||r(S)==="color_temp"&&h(S).unit||"";return(S,V)=>(p(),v("div",kh,[V[3]||(V[3]=i("span",{class:"f-label"},"Action",-1)),n.value===void 0?(p(),v("small",$h,"Asking Home Assistant which actions this entity has…")):n.value===null?(p(),v("small",xh,"Home Assistant isn't answering right now. Try again in a moment.")):(p(),v(U,{key:2},[i("button",{type:"button",class:"row","aria-expanded":l.value?"true":"false",onClick:V[0]||(V[0]=I=>y(c).actionPickerOpen=!l.value)},[i("span",Ch,[i("b",null,m(o.value?o.value.name:s.value?s.value.action:"Choose an action"),1),s.value?(p(),v("small",Ah,m(s.value.action),1)):F("",!0)]),i("span",Th,m(l.value?"Close":"Change"),1)],8,Sh),l.value?(p(),v("div",Eh,[we(i("input",{"onUpdate:modelValue":V[1]||(V[1]=I=>y(c).actionSearch=I),type:"search",placeholder:"Search, for example toggle, position, or play","aria-label":"Search for an action"},null,512),[[Fe,y(c).actionSearch]]),i("div",Ph,[(p(!0),v(U,null,le(a.value,I=>(p(),v("button",{key:I.action,type:"button",class:"action-choice","aria-pressed":I.action===s.value?.action?"true":"false",onClick:T=>u(I)},[i("strong",null,m(I.name),1),i("small",null,m(I.description?`${I.action} · ${I.description}`:I.action),1)],8,Oh))),128)),a.value.length?F("",!0):(p(),v("p",Ih,"No action found."))])])):F("",!0),s.value&&!o.value?(p(),v("small",Mh,"Home Assistant doesn't offer this action for this entity any more, so a tap does nothing. Choose another one.")):F("",!0),o.value&&!l.value?(p(),v(U,{key:2},[(p(!0),v(U,null,le(o.value.fields,I=>(p(),v("div",{key:I.key,class:"f"},[i("span",Lh,m(I.required?I.name:`${I.name} (optional)`),1),r(I)==="boolean"?(p(),Xe(tt,{key:0,choices:[["","Not set"],["true","On"],["false","Off"]],value:s.value?.data?.[I.key]===void 0?"":String(s.value?.data?.[I.key]),onPick:T=>d(I.key,T===""?void 0:T==="true")},null,8,["value","onPick"])):k(I)&&k(I).length<=4?(p(),Xe(tt,{key:1,choices:[["","Not set"],...k(I)],value:s.value?.data?.[I.key]===void 0?"":String(s.value?.data?.[I.key]),onPick:T=>d(I.key,T===""?void 0:T)},null,8,["choices","value","onPick"])):k(I)?(p(),v("select",{key:2,"aria-label":I.name,value:String(s.value?.data?.[I.key]??""),onChange:T=>d(I.key,T.target.value===""?void 0:T.target.value)},[V[2]||(V[2]=i("option",{value:""},"Not set",-1)),(p(!0),v(U,null,le(k(I),([T,w])=>(p(),v("option",{key:T,value:T},m(w),9,Fh))),128))],40,Dh)):r(I)==="color_rgb"?(p(),v("div",Nh,[i("input",{type:"color","aria-label":I.name,value:C(s.value?.data?.[I.key]),onInput:T=>d(I.key,[1,3,5].map(w=>parseInt(T.target.value.slice(w,w+2),16)))},null,40,Rh),i("button",{type:"button",class:"btn quiet mini",onClick:T=>d(I.key,void 0)},"Not set",8,Hh)])):r(I)==="number"||r(I)==="color_temp"?(p(),v("div",Uh,[i("input",{type:"number","aria-label":I.name,min:h(I).min,max:h(I).max,step:h(I).step==="any"?"any":h(I).step,value:s.value?.data?.[I.key]??"",placeholder:b(I),onInput:T=>E(I,T.target.value)},null,40,jh),W(I)?(p(),v("span",Bh,m(W(I)),1)):F("",!0)])):(p(),v("input",{key:5,type:"text","aria-label":I.name,value:x(s.value?.data?.[I.key]),placeholder:b(I),onInput:T=>P(I,T.target.value)},null,40,Vh)),I.description?(p(),v("small",Wh,m(I.description),1)):F("",!0)]))),128)),f.value.length?(p(),v("small",Kh,"Home Assistant needs "+m(f.value.join(", "))+".",1)):F("",!0)],64)):F("",!0),y(Zt)(0,2,58)?F("",!0):(p(),v("small",qh,"The screen performs an action from firmware 0.2.58: press Update on the screen. Until then a tap works as Automatic."))],64))]))}}),zh={class:"f"},Jh=["aria-expanded"],Gh={class:"av mdi"},Xh={class:"tx"},Zh={class:"link"},Qh={key:0,class:"picker"},ev=["aria-pressed","title"],tv={class:"mdi"},nv=["aria-pressed"],sv={class:"icon-list"},ov={class:"icon-grid"},iv=["title","aria-label","aria-pressed","onClick"],lv={class:"mdi"},av={key:0,class:"hint"},rv={key:1},Ga=Ee({__name:"IconPicker",props:{selected:{},automatic:{},autoLabel:{},allowNone:{type:Boolean},note:{}},emits:["pick"],setup(e,{emit:t}){const n=e,s=t,o=pe(""),l=$(()=>vs(n.selected)),a=$(()=>n.selected==="none"?"":ke(l.value?.cp||n.automatic)),u=$(()=>n.selected==="none"?"No icon":l.value?.label||n.autoLabel),d=$(()=>{const f=o.value.trim().toLocaleLowerCase();return(c.inventory.icons?.groups||[]).map(r=>({label:r.label,icons:r.icons.filter(h=>!f||`${h.label} ${h.name.replaceAll("-"," ")} ${r.label}`.toLocaleLowerCase().includes(f))})).filter(r=>r.icons.length)});return(f,r)=>(p(),v("div",zh,[r[5]||(r[5]=i("span",{class:"f-label"},"Icon",-1)),i("button",{type:"button",class:"row","aria-expanded":y(c).iconPickerOpen?"true":"false",onClick:r[0]||(r[0]=h=>y(c).iconPickerOpen=!y(c).iconPickerOpen)},[i("span",Gh,m(a.value),1),i("span",Xh,[i("b",null,m(u.value),1)]),i("span",Zh,m(y(c).iconPickerOpen?"Close":"Change"),1)],8,Jh),y(c).iconPickerOpen?(p(),v("div",Qh,[we(i("input",{"onUpdate:modelValue":r[1]||(r[1]=h=>o.value=h),type:"search",placeholder:"Search, for example lamp, music, or door","aria-label":"Search for an icon"},null,512),[[Fe,o.value]]),i("button",{type:"button",class:"icon-choice icon-auto","aria-pressed":e.selected==="auto"?"true":"false",title:e.autoLabel,onClick:r[2]||(r[2]=h=>s("pick","auto"))},[i("span",tv,m(y(ke)(e.automatic)),1),i("span",null,m(e.autoLabel),1)],8,ev),e.allowNone?(p(),v("button",{key:0,type:"button",class:"icon-choice icon-auto","aria-pressed":e.selected==="none"?"true":"false",title:"No icon",onClick:r[3]||(r[3]=h=>s("pick","none"))},[...r[4]||(r[4]=[i("span",{class:"mdi"},null,-1),i("span",null,"No icon, text only",-1)])],8,nv)):F("",!0),i("div",sv,[(p(!0),v(U,null,le(d.value,h=>(p(),v("section",{key:h.label},[i("small",null,m(h.label),1),i("div",ov,[(p(!0),v(U,null,le(h.icons,b=>(p(),v("button",{key:b.name,type:"button",class:"icon-choice",title:b.label,"aria-label":`Icon: ${b.label}`,"aria-pressed":e.selected===b.name?"true":"false",onClick:k=>s("pick",b.name)},[i("span",lv,m(y(ke)(b.cp)),1)],8,iv))),128))])]))),128)),d.value.length?F("",!0):(p(),v("p",av,"No icon found."))])])):F("",!0),e.note?(p(),v("small",rv,m(e.note),1)):F("",!0)]))}}),uv={class:"dr-head"},cv={class:"tx"},dv={class:"mono"},fv={class:"dr-body"},pv={class:"f"},hv=["value","placeholder"],vv={key:1,class:"f"},mv={key:2,class:"f"},yv={key:0,class:"warn"},gv={class:"f"},bv={key:0},wv={key:3,class:"f"},_v={key:4,class:"f"},kv={key:5,class:"f"},$v={key:7,class:"f"},xv={key:0,class:"warn"},Sv={key:8,class:"f"},Cv={class:"f"},Av={class:"sw"},Tv=["aria-label","aria-pressed","onClick"],Ev={class:"dr-foot"},Pv=Ee({__name:"TileInspector",props:{tile:{}},setup(e){const t=e,n=$(()=>t.tile.entity.split(".")[0]),s=$(()=>hs(t.tile.entity)),o=$(()=>Mn(t.tile.entity)),l=$(()=>c.layout?pt(He(c.layout),c.layout.pages):1),a=$(()=>ct(t.tile.slot)+1),u=q=>!c.layout?.tiles.some(B=>ct(B.slot)===q-1),d=$(()=>{const q=Array.from({length:Math.min(Bt,l.value+1)},(B,ne)=>ne+1);return o.value>q.length&&q.push(o.value),q.map(B=>[B,u(B)?`${B} (empty)`:String(B)])}),f=$(()=>nl.value?o.value>l.value?{text:`This screen has no page ${o.value}, so a tap opens its last page. Choose another page.`,warn:!0}:{text:"A tap on the tile opens that page.",warn:!1}:{text:"Navigation tiles need firmware 0.2.62: press Update on the screen first.",warn:!1}),r=$(()=>!c.layout?.tiles.some(q=>ve(q)!==ve(t.tile)&&ct(q.slot)===a.value-1)),h=$(()=>{const q=Array.from({length:l.value},(B,ne)=>[ne+1,String(ne+1)]);return l.valueo.value?[["single","Normal"],["wide","Double-width"]]:[["single","Normal"],["wide","Double-width"],["full","Full page"]]),k=$(()=>o.value?"":nl.value?"Full page: one big button that lights up while on; a slider, controls or a graph sit at the bottom of it.":"A full-page tile needs firmware 0.2.62: press Update on the screen first."),R=$(()=>c.capabilities[t.tile.entity]),C=(q,B)=>t.tile.options?.[q]??B,x=$(()=>C("display",n.value==="screen"?"digital":"standard")),P=$(()=>{const q=n.value==="screen"?[["digital","Digital clock"],["analog","Analog clock"]]:[["standard","Name and status"],["watch","Large value"]],B=R.value;return n.value==="weather"&&(!B||B.displays.includes("forecast")||x.value==="forecast")&&q.push(["forecast","Weather forecast"]),n.value==="sensor"&&(!B||B.displays.includes("graph")||x.value==="graph")&&q.push(["graph","Graph"]),n.value==="sun"&&q.push(["sunpath","Sun path"]),q}),E=$(()=>{const q=R.value;return q&&x.value==="graph"&&!q.displays.includes("graph")?"Home Assistant has no numbers for this entity, so the graph stays empty. Choose another display.":q&&x.value==="forecast"&&!q.displays.includes("forecast")?"This weather service has no daily forecast in Home Assistant. Choose another display.":""}),W=$(()=>C("size","single")),S=$(()=>c.inventory.controls?.[n.value]),V=$(()=>C("controls",W.value==="full"?"none":S.value?.default)),I=$(()=>{const q=R.value;return(S.value?.choices||[]).filter(B=>!q||B.key==="none"||B.key===V.value||q.controls.includes(B.key)).map(B=>[B.key,B.label])}),T=$(()=>{const q=R.value;return q&&V.value!=="none"&&!q.controls.includes(V.value)?{text:"Home Assistant doesn't offer this control for this entity, so it stays empty on the screen. Choose another one.",warn:!0}:{text:Zt(0,2,19)?W.value==="full"?"At the bottom of the full-page tile; a tap anywhere else works as set below.":"On the right of the double-width tile, like the rows in Home Assistant. Tapping the name works as set below.":"The screen shows direct control from firmware 0.2.19; until then the tile stays as it was.",warn:!1}}),w=$(()=>C("tap","auto")),A=$(()=>{const q=[["auto","Automatic"],["detail","Open control"],["none","View only"]];return((R.value?R.value.toggle:tl.includes(n.value))||w.value==="toggle")&&q.push(["toggle","On / off"]),q.push(["action","Perform action"]),q}),te=$(()=>w.value==="toggle"&&R.value&&!R.value.toggle?{text:"Home Assistant can't turn this on and off, so a tap does nothing. Choose another option.",warn:!0}:w.value==="toggle"&&!tl.includes(n.value)&&!Zt(0,2,58)?{text:"The screen switches this from firmware 0.2.58: press Update on the screen. Until then a tap opens its card.",warn:!1}:w.value==="toggle"?{text:"Hold the tile to open its card.",warn:!1}:null),$e=$(()=>C("inline","none")),Me=$(()=>Wc.includes(n.value)&&(!R.value||R.value.inline||$e.value==="slider")),X=$(()=>$e.value==="slider"&&R.value&&!R.value.inline),j=$(()=>C("history_hours",24)),J=$(()=>Object.entries(c.inventory.backgrounds||{})),ce=$(()=>!!c.inventory.entities.find(q=>q.id===t.tile.entity)?.icon),de=$(()=>!!c.inventory.icons&&(n.value!=="screen"||o.value>0)&&!["forecast","sunpath"].includes(x.value));function he(q){t.tile.name=q,Ze()}function Q(){c.inspector={kind:"inspect",entity:t.tile.entity}}return(q,B)=>(p(),v(U,null,[i("div",uv,[i("span",{class:"av mdi",style:Pe({color:y(xt)(e.tile.entity)[2],background:y(xt)(e.tile.entity)[3]})},m(y(ke)(y(Vn)(e.tile))),5),i("span",cv,[i("b",null,m(e.tile.name||s.value),1),i("small",dv,m(e.tile.entity),1)]),i("button",{type:"button",class:"icon-btn","aria-label":"Close",onClick:B[0]||(B[0]=(...ne)=>y(Ue)&&y(Ue)(...ne))},"✕")]),i("div",fv,[i("div",pv,[B[12]||(B[12]=i("label",{class:"f-label",for:"tile-name"},"Name on the screen",-1)),i("input",{id:"tile-name",value:e.tile.name,placeholder:s.value,maxlength:"60",onInput:B[1]||(B[1]=ne=>he(ne.target.value))},null,40,hv)]),de.value?(p(),Xe(Ga,{key:0,selected:e.tile.options?.icon||"auto",automatic:y(sn)(e.tile.entity),"auto-label":`Automatic (${ce.value?"from Home Assistant":"default"})`,note:y(Zt)(0,2,18)?"":"The screen shows a chosen icon from firmware 0.2.18.",onPick:B[2]||(B[2]=ne=>y(Kt)(e.tile,"icon",ne))},null,8,["selected","automatic","auto-label","note"])):F("",!0),o.value?(p(),v("div",vv,[B[13]||(B[13]=i("span",{class:"f-label"},"Goes to page",-1)),_e(tt,{choices:d.value,value:o.value,onPick:B[3]||(B[3]=ne=>y(nd)(e.tile,Number(ne)))},null,8,["choices","value"]),i("small",{class:ie({warn:f.value.warn})},m(f.value.text),3)])):(p(),v("div",mv,[B[14]||(B[14]=i("span",{class:"f-label"},"Display",-1)),_e(tt,{choices:P.value,value:x.value,onPick:B[4]||(B[4]=ne=>y(Kt)(e.tile,"display",ne))},null,8,["choices","value"]),E.value?(p(),v("small",yv,m(E.value),1)):F("",!0)])),i("div",gv,[B[15]||(B[15]=i("span",{class:"f-label"},"Size",-1)),_e(tt,{choices:b.value,value:W.value,onPick:B[5]||(B[5]=ne=>y(Kt)(e.tile,"size",ne))},null,8,["choices","value"]),k.value?(p(),v("small",bv,m(k.value),1)):F("",!0)]),h.value.length>1?(p(),v("div",wv,[B[16]||(B[16]=i("span",{class:"f-label"},"Page",-1)),_e(tt,{choices:h.value,value:a.value,onPick:B[6]||(B[6]=ne=>y(ed)(e.tile,Number(ne)-1))},null,8,["choices","value"])])):F("",!0),S.value&&W.value!=="single"&&!o.value?(p(),v("div",_v,[B[17]||(B[17]=i("span",{class:"f-label"},"Direct control on the tile",-1)),_e(tt,{choices:I.value,value:V.value,onPick:B[7]||(B[7]=ne=>y(Kt)(e.tile,"controls",ne))},null,8,["choices","value"]),i("small",{class:ie({warn:T.value.warn})},m(T.value.text),3)])):F("",!0),n.value!=="screen"&&!o.value?(p(),v("div",kv,[B[18]||(B[18]=i("span",{class:"f-label"},"On tap",-1)),_e(tt,{choices:A.value,value:w.value,onPick:B[8]||(B[8]=ne=>y(Kt)(e.tile,"tap",ne))},null,8,["choices","value"]),te.value?(p(),v("small",{key:0,class:ie({warn:te.value.warn})},m(te.value.text),3)):F("",!0)])):F("",!0),n.value!=="screen"&&!o.value&&w.value==="action"?(p(),Xe(Yh,{key:6,tile:e.tile},null,8,["tile"])):F("",!0),Me.value&&!o.value?(p(),v("div",$v,[B[19]||(B[19]=i("span",{class:"f-label"},"Small slider on the tile",-1)),_e(tt,{choices:[["none","No"],["slider","Yes, control directly"]],value:$e.value,onPick:B[9]||(B[9]=ne=>y(Kt)(e.tile,"inline",ne))},null,8,["value"]),X.value?(p(),v("small",xv,"Home Assistant has nothing a slider can change for this entity. Choose No.")):F("",!0)])):F("",!0),n.value==="sensor"&&!o.value?(p(),v("div",Sv,[B[20]||(B[20]=i("span",{class:"f-label"},"History",-1)),_e(tt,{choices:[[1,"1 hour"],[6,"6 hours"],[24,"24 hours"]],value:j.value,onPick:B[10]||(B[10]=ne=>y(Kt)(e.tile,"history_hours",Number(ne)))},null,8,["value"])])):F("",!0),i("div",Cv,[B[21]||(B[21]=i("span",{class:"f-label"},"Pastel background",-1)),i("div",Av,[(p(!0),v(U,null,le(J.value,([ne,Qe])=>(p(),v("button",{key:ne,type:"button","aria-label":`Background: ${Qe.label}`,"aria-pressed":(e.tile.options?.background||"auto")===ne?"true":"false",onClick:Ln=>y(Kt)(e.tile,"background",ne)},[i("i",{class:ie(Qe.color?"":ne==="none"?"none":"auto"),style:Pe(Qe.color?{background:Qe.color}:void 0)},null,6),Z(m(Qe.label),1)],8,Tv))),128))])])]),i("div",Ev,[i("button",{type:"button",class:"btn danger",onClick:B[11]||(B[11]=ne=>y(Ua)(e.tile))},"Remove"),B[22]||(B[22]=i("span",{class:"spacer"},null,-1)),n.value!=="screen"?(p(),v("button",{key:0,type:"button",class:"btn quiet",onClick:Q},"Read current data")):F("",!0)])],64))}}),Ov={class:"dr-head"},Iv={class:"av mdi",style:{background:"var(--seg)",color:"var(--ink-2)"}},Mv={class:"tx"},Lv={class:"dr-body"},Dv={class:"f"},Fv=["value"],Nv={class:"f"},Rv={class:"f-label",id:"topbar-caption"},Hv={style:{"text-transform":"none","letter-spacing":"0","font-weight":"500"}},Uv={class:"items",id:"topbar-chips",role:"list","aria-labelledby":"topbar-caption"},jv=["data-index","aria-label","onPointerdown","onClick","onKeydown"],Bv={class:"tx"},Vv=["aria-label","onClick"],Wv=["disabled","title"],Kv={class:"live",id:"topbar-live"},qv={class:"f"},Yv={class:"f"},zv={key:1,class:"f"},Jv={key:2},Gv={key:0,class:"dr-foot"},Xv=["disabled"],Zv=["disabled"],Qv=Ee({__name:"TopbarInspector",props:{index:{}},setup(e){const t=e,n=$(()=>Vt()),s=$(()=>n.value[t.index]),o=$(()=>(c.fontsVersion,c.now,c.topbarPreviews,Sa(n.value,Na.value,c.layout?.title||"Home",qt))),l=$(()=>o.value.dropped),a=$(()=>c.inventory.header?.min_firmware||"0.2.32"),u=$(()=>{const[X,j,J]=a.value.split(".").map(Number);return Zt(X,j,J)}),d=$(()=>u.value?l.value.size?"Not everything fits next to the name: the screen drops the dashed items. Remove one or choose a shorter name.":"Drag to reorder; tap an item to configure it.":`Sensors, the date, and the analog clock appear from firmware ${a.value}; until that update, this screen shows the name and, if the time is in the bar, the clock.`),f=(X,j)=>{const J=qt(X);return J.shown?l.value.has(j)?"Doesn't fit next to the name":J.analog?"Dial":J.text:"Hidden: not active right now"},r=X=>{const j=qt(X);return j.analog||X.type!=="entity"?vs(xa[X.type])?.cp:j.icon},h=X=>c.topbarAdded?.key===qe(X)&&Date.now()-c.topbarAdded.time<1200;function b(X){c.layout&&(c.layout.title=X,Ze())}function k(X){const j=[...n.value];j[t.index]={...j[t.index],...X},rs(j)}const R=$(()=>s.value?qt(s.value).shown?l.value.has(t.index)?"Doesn't currently fit next to the name":"How it looks on the screen":"Hidden right now: not active":""),C=$(()=>wn().clock_24h!==!1),x=$(()=>({clock:ui(C.value,new Date(c.now)),analog:"Small dial with the time",date:ci(new Date(c.now))})),P=pe({index:-1,active:!1,moved:!1});let E=null,W=0,S=null,V=0;function I(X,j){X.button!==0||X.target.closest(".x")||(X.pointerType!=="touch"&&X.preventDefault(),P.value={index:j,active:!1,moved:!1},E={x:X.clientX,y:X.clientY},S=X.pointerId,clearTimeout(W),X.pointerType==="touch"&&(W=window.setTimeout(T,260)),document.addEventListener("pointermove",A),document.addEventListener("pointerup",te),document.addEventListener("pointercancel",te))}function T(){P.value.active=!0,document.addEventListener("touchmove",w,{passive:!1})}function w(X){P.value.active&&X.preventDefault()}function A(X){if(X.pointerId!==S||!E)return;if(!P.value.active){const ce=Math.hypot(X.clientX-E.x,X.clientY-E.y);if(X.pointerType==="touch"){ce>10&&(clearTimeout(W),E=null);return}if(ce<6)return;T()}const j=[...document.querySelectorAll(".items .item[data-index]")];let J=P.value.index;if(j.forEach((ce,de)=>{const he=ce.getBoundingClientRect();deP.value.index&&X.clientY>he.top+he.height/2&&(J=Math.max(J,de))}),J!==P.value.index&&c.layout){const ce=[...n.value];ce.splice(J,0,...ce.splice(P.value.index,1)),c.layout.header={items:ce};const de=t.index===P.value.index;P.value.index=J,P.value.moved=!0,de&&Lt(J)}}function te(X){X.pointerId===S&&(clearTimeout(W),document.removeEventListener("pointermove",A),document.removeEventListener("pointerup",te),document.removeEventListener("pointercancel",te),document.removeEventListener("touchmove",w),P.value.active&&(V=Date.now()+400,P.value.moved&&Ze()),P.value={index:-1,active:!1,moved:!1},E=null,S=null)}function $e(X){Date.now()(p(),v(U,null,[i("div",Ov,[i("span",Iv,m(s.value?y(ke)(r(s.value)||"F0150"):y(ke)("F0150")),1),i("span",Mv,[i("b",null,m(s.value?s.value.type==="entity"?y(hs)(s.value.entity):y(Wn)(s.value):"Top bar"),1),i("small",{class:ie({mono:s.value?.type==="entity"})},m(s.value?s.value.type==="entity"?s.value.entity:"From the screen itself, also works without Home Assistant":`${n.value.length} of ${y(zt)()} items`),3)]),i("button",{type:"button",class:"icon-btn","aria-label":"Close",onClick:j[0]||(j[0]=(...J)=>y(Ue)&&y(Ue)(...J))},"✕")]),i("div",Lv,[i("div",Dv,[j[10]||(j[10]=i("label",{class:"f-label",for:"title"},"Left: name",-1)),i("input",{id:"title",value:y(c).layout?.title,maxlength:"60",placeholder:"For example: Living room",onInput:j[1]||(j[1]=J=>b(J.target.value))},null,40,Fv)]),i("div",Nv,[i("span",Rv,[j[11]||(j[11]=Z("Right: time, sensors, or a clock ",-1)),i("span",Hv," · "+m(n.value.length)+" / "+m(y(zt)()),1)]),i("div",Uv,[(p(!0),v(U,null,le(n.value,(J,ce)=>(p(),v("div",{key:y(qe)(J)+ce,class:ie(["item",{selected:ce===e.index,"is-hidden":!y(qt)(J).shown,"is-overflow":l.value.has(ce),"just-added":h(J),"dragging-chip":P.value.active&&P.value.index===ce}]),role:"listitem",tabindex:"0","data-index":ce,"aria-label":`${y(Wn)(J)}, slot ${ce+1}. Enter: configure, arrows: move`,onPointerdown:de=>I(de,ce),onClick:de=>$e(ce),onKeydown:de=>Me(de,ce)},[j[12]||(j[12]=i("span",{class:"grip","aria-hidden":"true"},"⋮⋮",-1)),i("span",{class:"av mdi",style:Pe(y(qt)(J).color?{color:y(qt)(J).color}:void 0)},m(r(J)?y(ke)(r(J)):""),5),i("span",Bv,[i("b",null,m(y(Wn)(J)),1),i("small",null,m(f(J,ce)),1)]),i("button",{type:"button",class:"x","aria-label":`Remove ${y(Wn)(J)} from the top bar`,onClick:ft(de=>y(rl)(ce),["stop"])},"✕",8,Vv)],42,jv))),128)),i("button",{type:"button",class:"ghost-btn",id:"topbar-add",disabled:n.value.length>=y(zt)(),title:n.value.length>=y(zt)()?`Maximum ${y(zt)()} items`:"Add the time, date, analog clock, or an entity",onClick:j[2]||(j[2]=(...J)=>y(il)&&y(il)(...J))},"+ Add time, date, or an entity",8,Wv)]),i("small",{id:"topbar-hint",class:ie({warn:l.value.size>0&&u.value})},m(d.value),3)]),s.value?(p(),v(U,{key:0},[i("div",Kv,[i("small",null,m(R.value),1),_e(Ja,{items:[s.value],single:""},null,8,["items"])]),s.value.type==="entity"?(p(),v(U,{key:0},[i("div",qv,[j[13]||(j[13]=i("span",{class:"f-label"},"What to show",-1)),_e(tt,{choices:(y(c).inventory.header?.contents||[]).map(J=>[J.key,J.label]),value:s.value.content,onPick:j[3]||(j[3]=J=>k({content:J}))},null,8,["choices","value"]),j[14]||(j[14]=i("small",null,"Last changed keeps counting on the screen itself: “Just now”, “5 min ago”, “Yesterday”.",-1))]),_e(Ga,{selected:s.value.icon||"auto",automatic:y(c).topbarPreviews[y(qe)(s.value)]?.auto_icon||y(sn)(s.value.entity),"auto-label":"Automatic (like Home Assistant)","allow-none":"",onPick:j[4]||(j[4]=J=>k({icon:J}))},null,8,["selected","automatic"]),i("div",Yv,[j[15]||(j[15]=i("span",{class:"f-label"},"Show",-1)),_e(tt,{choices:(y(c).inventory.header?.shows||[]).map(J=>[J.key,J.label]),value:s.value.show,onPick:j[5]||(j[5]=J=>k({show:J}))},null,8,["choices","value"]),j[16]||(j[16]=i("small",null,"Only when active hides the item as long as it's off, closed, away, or 0. Handy for an open door, a running washing machine, or who's home.",-1))])],64)):s.value.type!=="date"?(p(),v("div",zv,[j[17]||(j[17]=i("span",{class:"f-label"},"Format",-1)),_e(tt,{choices:[["24","24 hour"],["12","12 hour"]],value:C.value?"24":"12",onPick:j[6]||(j[6]=J=>y(Kn)("clock_24h",J==="24",150))},null,8,["value"]),j[18]||(j[18]=i("small",null,"The screen's own clock setting: it applies at once to every clock on this screen, including the clock tiles.",-1))])):(p(),v("small",Jv,m(x.value.date)+" today. The date follows the screen's own clock.",1))],64)):F("",!0)]),s.value?(p(),v("div",Gv,[i("button",{type:"button",class:"btn danger",onClick:j[7]||(j[7]=J=>y(rl)(e.index))},"Remove"),j[19]||(j[19]=i("span",{class:"spacer"},null,-1)),i("button",{type:"button",class:"btn quiet",disabled:e.index===0,onClick:j[8]||(j[8]=J=>y(xo)(e.index,e.index-1)&&y(Lt)(e.index-1))},"↑ Up",8,Xv),i("button",{type:"button",class:"btn quiet",disabled:e.index>=n.value.length-1,onClick:j[9]||(j[9]=J=>y(xo)(e.index,e.index+1)&&y(Lt)(e.index+1))},"↓ Down",8,Zv)])):F("",!0)],64))}}),em={class:"dr-head"},tm={class:"tx"},nm={class:"dr-body"},sm={class:"f"},om={class:"options"},im=["disabled","onClick"],lm={class:"mdi"},am={class:"tx"},rm={key:0,class:"f"},um={class:"options"},cm=["disabled","onClick"],dm={class:"mdi"},fm={class:"tx"},pm={class:"f"},hm={class:"options"},vm=["disabled","onClick"],mm={class:"mdi"},ym={class:"tx"},gm={key:0,class:"hint"},bm={key:1,class:"hint"},wm={class:"dr-foot"},_m=Ee({__name:"TopbarAdd",setup(e){const t=pe(""),n=$(()=>new Set(Vt().map(qe))),s=$(()=>wn().clock_24h!==!1),o=$(()=>({clock:ui(s.value,new Date(c.now)),analog:"Small dial with the time",date:ci(new Date(c.now))})),l=$(()=>c.inventory.header?.suggestions?.[c.selected||""]||[]),a=$(()=>{const d=t.value.trim().toLocaleLowerCase();return c.inventory.entities.filter(f=>`${f.name} ${f.id} ${f.area||""} ${f.device||""}`.toLocaleLowerCase().includes(d))}),u=d=>({type:"entity",entity:d,content:"state",icon:"auto",show:"always"});return(d,f)=>(p(),v(U,null,[i("div",em,[f[4]||(f[4]=i("span",{class:"av",style:{background:"var(--seg)",color:"var(--ink-2)","font-size":"18px"}},"+",-1)),i("span",tm,[f[3]||(f[3]=i("b",null,"Add to the top bar",-1)),i("small",null,m(y(Vt)().length)+" of "+m(y(zt)())+" slots used",1)]),i("button",{type:"button",class:"icon-btn","aria-label":"Close",onClick:f[0]||(f[0]=(...r)=>y(Ue)&&y(Ue)(...r))},"✕")]),i("div",nm,[i("div",sm,[f[5]||(f[5]=i("span",{class:"f-label"},"From the screen itself",-1)),i("div",om,[(p(!0),v(U,null,le(y(c).inventory.header?.builtin||[],r=>(p(),v("button",{key:r.type,type:"button",class:"option",disabled:n.value.has(y(qe)({type:r.type})),onClick:h=>y(So)({type:r.type})},[i("span",lm,m(y(ke)(y(vs)(y(xa)[r.type])?.cp||"F0150")),1),i("span",am,[i("strong",null,m(r.label),1),i("small",null,m(n.value.has(y(qe)({type:r.type}))?"Already added":o.value[r.type]),1)])],8,im))),128))])]),l.value.length?(p(),v("div",rm,[f[6]||(f[6]=i("span",{class:"f-label"},"Suggestions from Home Assistant",-1)),i("div",um,[(p(!0),v(U,null,le(l.value,r=>(p(),v("button",{key:y(qe)(r.item),type:"button",class:"option",disabled:n.value.has(y(qe)(r.item)),onClick:h=>y(So)(r.item)},[i("span",dm,m(y(ke)(r.icon||y(sn)(r.item.entity))),1),i("span",fm,[i("strong",null,m(r.label),1),i("small",null,m(n.value.has(y(qe)(r.item))?"Already added":[r.name,r.area].filter(Boolean).join(" · ")),1)])],8,cm))),128))])])):F("",!0),i("div",pm,[f[7]||(f[7]=i("label",{class:"f-label",for:"topbar-search"},"An entity",-1)),we(i("input",{id:"topbar-search","onUpdate:modelValue":f[1]||(f[1]=r=>t.value=r),type:"search",placeholder:"Search by name, room, or entity, e.g. temperature or door","aria-label":"Search for an entity for the top bar"},null,512),[[Fe,t.value]]),i("div",hm,[(p(!0),v(U,null,le(a.value.slice(0,40),r=>(p(),v("button",{key:r.id,type:"button",class:"option",disabled:n.value.has(y(qe)(u(r.id))),onClick:h=>y(So)(u(r.id))},[i("span",mm,m(y(ke)(y(sn)(r.id))),1),i("span",ym,[i("strong",null,m(r.name),1),i("small",null,m([r.area,r.id].filter(Boolean).join(" · ")),1)])],8,vm))),128)),a.value.length?a.value.length>40?(p(),v("p",bm,m(a.value.length)+" results. Keep typing to narrow it down.",1)):F("",!0):(p(),v("p",gm,"No entities found."))])])]),i("div",wm,[f[8]||(f[8]=i("span",{class:"spacer"},null,-1)),i("button",{type:"button",class:"btn quiet",onClick:f[2]||(f[2]=(...r)=>y(Ue)&&y(Ue)(...r))},"Cancel")])],64))}}),km={class:"dr-head"},$m={class:"tx"},xm={class:"dr-body",id:"inspector-section"},Sm={id:"inspection-summary"},Cm={key:0,class:"hint warn"},Am={key:1,class:"hint"},Tm={id:"inspection"},Em=Ee({__name:"InspectPanel",props:{entity:{}},setup(e){const t=e,n=pe(null),s=pe("Reading…"),o=pe("");async function l(){if(c.selected){n.value=null,o.value="",s.value="Fetching current HA status...";try{const u=await gt(`screens/${encodeURIComponent(c.selected)}/inspect`),d=t.entity?u.tiles.filter(f=>f.entity===t.entity):u.tiles;n.value=d,s.value=JSON.stringify(t.entity?d[0]:u,null,2)}catch(u){o.value=u.message,s.value="",ge(u.message)}}}const a=u=>{const d=c.layout?.tiles.find(f=>f.entity===u)?.options||{};return`Small slider: ${d.inline==="slider"?"yes":"no"} · Display: ${Da[d.display||"standard"]||d.display} · Size: ${Vc[d.size]||"normal"} · Control: ${Hc({entity:u,options:d},c.inventory)} · Background: ${c.inventory.backgrounds?.[d.background||"auto"]?.label||"Default"}`};return kn(l),pn(()=>t.entity,l),(u,d)=>(p(),v(U,null,[i("div",km,[d[2]||(d[2]=i("span",{class:"av",style:{background:"var(--seg)",color:"var(--ink-2)","font-size":"16px"}},"⌕",-1)),i("span",$m,[d[1]||(d[1]=i("b",null,"Current data",-1)),i("small",{class:ie({mono:e.entity})},m(e.entity||"Connection, status, and supported properties"),3)]),i("button",{type:"button",class:"icon-btn","aria-label":"Close",onClick:d[0]||(d[0]=(...f)=>y(Ue)&&y(Ue)(...f))},"✕")]),i("div",xm,[i("div",Sm,[o.value?(p(),v("p",Cm,m(o.value),1)):n.value?F("",!0):(p(),v("p",Am,"Fetching current HA status...")),(p(!0),v(U,null,le(n.value||[],(f,r)=>(p(),v("article",{key:r,class:"inspection-tile"},[i("strong",null,m(f.entity),1),i("span",null,"Status: "+m(f.word||f.state),1),i("small",null,m(a(f.entity)),1)]))),128))]),i("pre",Tm,m(s.value),1)]),i("div",{class:"dr-foot"},[i("button",{type:"button",class:"btn quiet",onClick:l},"Read again"),d[3]||(d[3]=i("span",{class:"spacer"},null,-1))])],64))}}),Pm=["aria-hidden"],Om=Ee({__name:"Drawer",setup(e){const t=$(()=>!!(c.inspector&&(c.inspector.kind!=="tile"||go.value)));return(n,s)=>(p(),v("aside",{class:ie(["drawer",{open:t.value}]),id:"tile-sheet","aria-hidden":t.value?"false":"true",onClick:s[0]||(s[0]=ft(()=>{},["stop"]))},[y(c).inspector?(p(),v(U,{key:0},[y(c).inspector.kind==="tile"&&y(go)?(p(),Xe(Pv,{key:0,tile:y(go)},null,8,["tile"])):y(c).inspector.kind==="bar"?(p(),Xe(Qv,{key:1,index:y(c).inspector.index},null,8,["index"])):y(c).inspector.kind==="bar-add"?(p(),Xe(_m,{key:2})):y(c).inspector.kind==="inspect"?(p(),Xe(Em,{key:3,entity:y(c).inspector.entity},null,8,["entity"])):F("",!0)],64)):F("",!0)],10,Pm))}}),Im={class:"main-head"},Mm={class:"eyebrow",id:"screen-name"},Lm=["title"],Dm={class:"head-right"},Fm={class:"seg",role:"tablist"},Nm=["aria-pressed"],Rm=["aria-pressed"],Hm={key:1,id:"dirty",class:"chip dirty"},Um=["disabled"],jm={key:0,class:"spin small"},Bm=["aria-expanded"],Vm={key:3,class:"menu",role:"menu"},Wm=["disabled","title"],Km=["disabled","aria-expanded"],qm={key:0,class:"sub"},Ym=["onClick"],zm=["disabled","title"],Jm={class:"body",id:"body"},Gm=Ee({__name:"ScreenView",setup(e){const t=$(()=>lt.value),n=$(()=>t.value.online?`${t.value.delivery} · ${t.value.status}`:"Offline · changes are saved"),s=$(()=>t.value.update?.available&&t.value.online&&t.value.update?.profile&&t.value.update?.host),o=$(()=>c.inventory.screens.filter(C=>C.id!==t.value.id&&C.layout?.tiles?.length)),l=pe(!1),a=pe(null);function u(){c.menuOpen=!1,l.value=!1}function d(){u(),c.overrideProfile=t.value.update?.profile||null,c.overrideFriendly=t.value.name,Oe("#override")}function f(){u(),c.selectedTile=null,c.inspector={kind:"inspect"}}function r(C){u(),!(c.dirty&&!confirm("Replace the unsaved layout with a copy of the other screen's?"))&&id(C)}function h(){u(),a.value?.click()}async function b(C){const x=C.target,P=x.files?.[0];x.value="",P&&(c.dirty&&!confirm("Replace the unsaved layout with the imported one?")||ad(await P.text()))}function k(C){if(C.key==="Escape")if(c.menuOpen)u();else{if(c.palette)return;c.inspector&&!C.target?.closest?.(".picker")&&Ue()}else(C.metaKey||C.ctrlKey)&&C.key.toLowerCase()==="s"&&(C.preventDefault(),c.dirty&&jo())}function R(C){c.menuOpen&&!C.target.closest(".head-right .menu, #more")&&u()}return kn(()=>{document.addEventListener("keydown",k),document.addEventListener("click",R)}),In(()=>{document.removeEventListener("keydown",k),document.removeEventListener("click",R)}),(C,x)=>(p(),v(U,null,[i("header",Im,[i("div",null,[i("span",Mm,m(t.value.name),1),i("h1",null,m(y(c).layout?.title||"Home"),1)]),i("span",{id:"delivery",class:ie(["chip",{off:!t.value.online}]),title:n.value},[x[9]||(x[9]=i("span",{class:"dot"},null,-1)),Z(m(n.value),1)],10,Lm),i("div",Dm,[i("div",Fm,[i("button",{type:"button",id:"tab-layout",role:"tab","aria-pressed":y(c).tab==="layout"?"true":"false",onClick:x[0]||(x[0]=P=>y(c).tab="layout")},"Layout",8,Nm),i("button",{type:"button",id:"tab-settings",role:"tab","aria-pressed":y(c).tab==="settings"?"true":"false",onClick:x[1]||(x[1]=P=>{y(c).tab="settings",y(Ue)()})},"Screen settings",8,Rm)]),y(c).dirty?(p(),v("span",Hm,"Unsaved changes")):(p(),v("span",{key:0,id:"dirty",class:ie(["chip",{sent:y(c).saved}])},m(y(c).saved?"Saved · sent to screen":"All saved"),3)),y(c).dirty?(p(),v("button",{key:2,id:"save",type:"button",class:"btn primary",disabled:y(c).busy,title:"⌘S",onClick:x[2]||(x[2]=P=>y(jo)())},[y(c).busy?(p(),v("span",jm)):F("",!0),Z(m(y(c).busy?"Saving…":"Save & send"),1)],8,Um)):F("",!0),i("button",{id:"more",type:"button",class:"icon-btn","aria-label":"More","aria-haspopup":"menu","aria-expanded":y(c).menuOpen?"true":"false",onClick:x[3]||(x[3]=ft(P=>{y(c).menuOpen=!y(c).menuOpen,l.value=!1},["stop"]))},"···",8,Bm),y(c).menuOpen?(p(),v("div",Vm,[i("button",{type:"button",role:"menuitem",id:"identify",disabled:!y(gn)(t.value)||!t.value.online,title:y(gn)(t.value)?"":"Needs firmware 0.2.31 or newer",onClick:x[4]||(x[4]=P=>{u(),y(ja)(t.value)})},[...x[10]||(x[10]=[Z("Identify ",-1),i("small",null,"blink the screen",-1)])],8,Wm),i("button",{type:"button",role:"menuitem",id:"inspect",onClick:f},[...x[11]||(x[11]=[Z("Read current data ",-1),i("small",null,"from Home Assistant",-1)])]),x[17]||(x[17]=i("div",{class:"sep"},null,-1)),i("button",{type:"button",role:"menuitem",id:"copy-layout",disabled:!o.value.length,"aria-expanded":l.value?"true":"false",onClick:x[5]||(x[5]=ft(P=>l.value=!l.value,["stop"]))},[x[12]||(x[12]=Z("Copy layout from… ",-1)),i("small",null,m(o.value.length?`${o.value.length} screen${o.value.length===1?"":"s"}`:"no other screen"),1)],8,Km),l.value?(p(),v("div",qm,[(p(!0),v(U,null,le(o.value,P=>(p(),v("button",{key:P.id,type:"button",role:"menuitem",onClick:E=>r(P.id)},[Z(m(P.name)+" ",1),i("small",null,m(P.layout.tiles.length)+" tiles",1)],8,Ym))),128))])):F("",!0),i("button",{type:"button",role:"menuitem",id:"export-layout",onClick:x[6]||(x[6]=P=>{u(),y(Va)()})},[...x[13]||(x[13]=[Z("Export layout ",-1),i("small",null,"JSON",-1)])]),i("button",{type:"button",role:"menuitem",id:"import-layout",onClick:h},[...x[14]||(x[14]=[Z("Import layout… ",-1),i("small",null,"JSON",-1)])]),x[18]||(x[18]=i("div",{class:"sep"},null,-1)),i("button",{type:"button",role:"menuitem",id:"open-override",disabled:!t.value.update?.profile,title:t.value.update?.profile?"":"No ESPHome profile found for this screen",onClick:d},[...x[15]||(x[15]=[Z("Override YAML ",-1),i("small",null,"advanced",-1)])],8,zm),i("button",{type:"button",role:"menuitem",onClick:x[7]||(x[7]=P=>{u(),y(Oe)("#firmware")})},"Firmware & USB"),s.value?(p(),v("button",{key:1,type:"button",role:"menuitem",onClick:x[8]||(x[8]=P=>{u(),y(Vo)(t.value)})},[x[16]||(x[16]=Z("Update firmware ",-1)),i("small",null,m(t.value.update?.target),1)])):F("",!0)])):F("",!0),i("input",{ref_key:"fileInput",ref:a,type:"file",accept:"application/json,.json",hidden:"",onChange:b},null,544)])]),i("div",Jm,[y(c).tab==="layout"?(p(),Xe(nh,{key:0})):(p(),Xe(bh,{key:1})),_e(Om)])],64))}}),Xm={key:0,id:"choose",class:"empty"},Zm={key:1,id:"empty",class:"empty"},Qm=Ee({__name:"EmptyState",setup(e){return(t,n)=>y(c).inventory.screens.length?(p(),v("section",Xm,[...n[1]||(n[1]=[i("span",{class:"empty-icon"},"▦",-1),i("h2",null,"Choose a screen",-1),i("p",null,"Pick one of your screens on the left to change its tiles, top bar and settings.",-1)])])):(p(),v("section",Zm,[n[2]||(n[2]=i("span",{class:"empty-icon"},"▦",-1)),n[3]||(n[3]=i("h2",null,"Start with a screen",-1)),n[4]||(n[4]=i("p",null,"Connect a CYD or Guition via USB to your Home Assistant machine. Profile, firmware, and the first flash happen in one go.",-1)),i("button",{id:"start",type:"button",class:"btn primary",onClick:n[0]||(n[0]=s=>y(Oe)("#new-screen"))},"Install new screen")]))}}),ey={class:"panel",id:"settings-view"},ty={class:"panel-head"},ny={class:"card-grid"},sy={class:"card"},oy={class:"tools"},iy={key:0,class:"card updates",id:"updates"},ly={id:"updates-hint"},ay=["aria-valuenow"],ry={class:"progress-text"},uy={key:0,class:"log-lines"},cy={key:2,class:"whatsnew"},dy={class:"check"},fy=["checked"],py={class:"card"},hy={class:"tools"},vy={class:"card",id:"claude"},my={class:"actions"},yy=["disabled"],gy={id:"claude-path"},by=Ee({__name:"AppSettingsView",setup(e){const t=$(()=>c.inventory.updates),n=$(()=>c.inventory.screens.filter(h=>h.update?.available).length),s=$(()=>t.value?t.value.busy?`Updating to firmware ${t.value.target}…`:c.inventory.screens.length?n.value?`Firmware ${t.value.target} is available for ${n.value} screen${n.value===1?"":"s"}.`:`All screens have firmware ${t.value.target}.`:`No screen paired yet. New screens get firmware ${t.value.target}.`:""),o=$(()=>c.inventory.screens.find(h=>h.update?.state==="running"||c.updating.includes(h.id))),l=$(()=>o.value?Sn(o.value):null),a=$(()=>(c.firmwareJob?.logs||[]).slice(-12).join(` +`)),u=$(()=>{const h=c.inventory.changelog,b=t.value?.target;return!Array.isArray(h)||!b?[]:h.filter(k=>k.firmware===b).flatMap(k=>k.lines).slice(0,10)}),d=$(()=>c.inventory.claude_skill),f=pe(!1);async function r(){f.value=!0;try{await vd()}finally{f.value=!1}}return(h,b)=>(p(),v("div",ey,[i("div",ty,[b[6]||(b[6]=i("div",{class:"tx"},[i("span",{class:"eyebrow"},"Settings"),i("h1",{id:"settings-title"},"Everything around your screens.")],-1)),i("button",{type:"button",class:"btn quiet",id:"close-settings",onClick:b[0]||(b[0]=k=>y(Oe)(""))},"← My screens")]),i("div",ny,[i("section",sy,[b[9]||(b[9]=i("h2",null,"Screens",-1)),b[10]||(b[10]=i("p",null,"Add a screen, or build and reinstall the firmware of one you already have.",-1)),i("div",oy,[i("button",{type:"button",class:"tool",onClick:b[1]||(b[1]=k=>y(Oe)("#new-screen"))},[...b[7]||(b[7]=[i("span",{class:"tool-icon"},"+",-1),i("span",{class:"tx"},[i("b",null,"New screen"),i("small",null,"Connect and install")],-1)])]),i("button",{type:"button",class:"tool",onClick:b[2]||(b[2]=k=>y(Oe)("#firmware"))},[...b[8]||(b[8]=[i("span",{class:"tool-icon"},"⇪",-1),i("span",{class:"tx"},[i("b",null,"Firmware & USB"),i("small",null,"Build and install via USB or Wi-Fi")],-1)])])])]),t.value?(p(),v("section",iy,[b[12]||(b[12]=i("h2",null,"Firmware updates",-1)),i("p",ly,m(s.value),1),o.value&&l.value?(p(),v(U,{key:0},[i("div",{class:"progress",role:"progressbar","aria-valuenow":l.value.percent,"aria-valuemin":"0","aria-valuemax":"100"},[i("i",{style:Pe({width:l.value.percent+"%"})},null,4)],8,ay),i("div",ry,[i("span",null,m(o.value.name)+" · "+m(l.value.percent)+" %",1),i("span",null,m(l.value.text),1)]),a.value?(p(),v("pre",uy,m(a.value),1)):F("",!0)],64)):F("",!0),t.value.pending&&!t.value.busy&&t.value.pending>=2?(p(),v("button",{key:1,id:"update-all",type:"button",class:"btn primary",onClick:b[3]||(b[3]=(...k)=>y(cl)&&y(cl)(...k))},"Update all "+m(t.value.pending)+" screens",1)):F("",!0),u.value.length&&!y(Bo)()?(p(),v("details",cy,[i("summary",null,"What's new in firmware "+m(t.value.target),1),i("ul",null,[(p(!0),v(U,null,le(u.value,k=>(p(),v("li",{key:k},m(k),1))),128))])])):F("",!0),i("label",dy,[i("input",{type:"checkbox",id:"auto-update",checked:!!t.value.auto,onChange:b[4]||(b[4]=k=>y(hd)(k.target.checked))},null,40,fy),b[11]||(b[11]=i("span",null,[Z("Update automatically every night"),i("small",null,"Between 03:00 and 06:00, one screen at a time. Stops on an error and reports it in Home Assistant.")],-1))])])):F("",!0),i("section",py,[b[14]||(b[14]=i("h2",null,"Alerts",-1)),b[15]||(b[15]=i("p",null,"A card over the whole screen from an automation, on one screen or on all of them. The cheatsheet has the action names, fields, icons, and colors, and a form to try one.",-1)),i("div",hy,[i("button",{type:"button",class:"tool",onClick:b[5]||(b[5]=k=>y(Oe)("#alerts"))},[...b[13]||(b[13]=[i("span",{class:"tool-icon"},"!",-1),i("span",{class:"tx"},[i("b",null,"Alerts"),i("small",null,"Full-screen notifications, from an automation")],-1)])])])]),i("section",vy,[b[18]||(b[18]=i("h2",null,"Claude",-1)),b[19]||(b[19]=i("p",null,'Teach Claude about ESP Screens, then ask: "Show an alert on all my screens when the mailbox is full." Claude knows the alert events, every field, color, and icon.',-1)),i("div",my,[i("button",{type:"button",id:"claude-install",class:ie(["btn",d.value?.installed&&d.value.current?"quiet":"primary"]),disabled:!d.value||f.value,onClick:r},m(!d.value||!d.value.installed?"Install for Claude Code":d.value.current?"Install again":"Update the skill"),11,yy),i("span",{id:"claude-status",class:ie(["chip",d.value&&d.value.installed?d.value.current?"good":"update":""]),role:"status"},m(d.value?d.value.installed?d.value.current?"● Installed":"Installed · a newer version is ready":"Not installed":"Loading…"),3)]),i("small",null,[b[16]||(b[16]=Z("For Claude Code in Home Assistant. Writes one file to ",-1)),i("code",gy,m(d.value?.path||"/homeassistant/.claude/skills/esp-screens"),1),b[17]||(b[17]=Z("; installing again replaces it.",-1))]),b[20]||(b[20]=i("div",{class:"actions"},[i("a",{class:"btn quiet",id:"claude-download",href:"api/claude-skill.zip",download:"esp-screens.zip"},"Download for claude.ai"),i("small",null,"Upload the zip in Claude under Customize → Skills.")],-1))])])]))}}),wy={class:"panel",id:"installer"},_y={class:"panel-head"},ky={class:"tx"},$y={id:"install-title"},xy={key:0},Sy={class:"boards"},Cy={class:"board"},Ay={class:"board"},Ty={class:"field"},Ey={class:"node-line"},Py={id:"node-preview"},Oy={key:0,class:"field",id:"node-label"},Iy={key:1,id:"wifi-fields",class:"wifi"},My={class:"hint",id:"wifi-status"},Ly={key:0,class:"field",id:"wifi-ssid-label"},Dy={key:1,class:"field",id:"wifi-password-label"},Fy={class:"field"},Ny={key:0,value:"usb"},Ry=["value"],Hy={id:"target-hint"},Uy={key:2,class:"hint",id:"install-note"},jy={class:"actions"},By=["disabled"],Vy={id:"install-status",class:"status-line error",role:"status"},Wy={key:1,id:"install-progress",class:"card"},Ky={class:"progress-head"},qy={key:0,class:"spin",id:"progress-spin"},Yy={id:"progress-title"},zy={id:"progress-detail"},Jy={key:0,id:"install-download",class:"card",style:{background:"var(--surface-2)"}},Gy=["href","download"],Xy={class:"steps",id:"download-steps"},Zy={key:1,id:"install-result",class:"field"},Qy={class:"key-box"},eg={class:"steps",id:"install-steps"},tg=["open"],ng={id:"install-log",class:"log"},sg={class:"actions"},og="https://web.esphome.io/?dashboard_install",ig="Download · flash from your own computer",lg=Ee({__name:"InstallerView",setup(e){const t=bn({board:"cyd",friendly_name:"",name:"",wifi_ssid:"",wifi_password:"",target:""}),n=bn({view:"setup",file:null,friendly:"",board:"cyd",target:"",apiKey:null,nodeEdited:!1,jobState:null,picked:!1,action:null}),s=pe(!1),o=pe(null),l=pe(null),a=pe([]),u=pe(""),d=pe(""),f=pe(!1),r=pe(!1),h=pe(null);let b=0;function k(me){return me.toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g,"").replace(/[^a-z0-9]+/g,"-").replace(/^[^a-z]+/,"").slice(0,30).replace(/-+$/,"")||"screen"}function R(me){const N=me.replace(/^\/dev\/serial\/by-id\/usb-/,"").replace(/-if\d+(-port\d+)?$/,"").replace(/_/g," ");return`USB · ${N===me?me.replace(/^\/dev\//,""):N}`}pn(()=>t.friendly_name,()=>{n.nodeEdited||(t.name=k(t.friendly_name))});const C=$(()=>t.name||"…"),x=$(()=>o.value?.ports||[]),P=$(()=>o.value?.wifi),E=$(()=>P.value?.state==="new"||P.value?.state==="missing"),W=$(()=>P.value?.missing||[]),S=$(()=>P.value?.state==="new"?"Fill in once: ESP Screens saves this in ESPHome secrets.yaml, later screens use it automatically.":"Your ESPHome secrets.yaml is still missing Wi-Fi details. ESP Screens only fills in the missing lines."),V=$(()=>P.value?.state==="ready"?"Wi-Fi comes from your ESPHome secrets.yaml.":P.value?.state==="invalid"?"secrets.yaml in the ESPHome folder isn't valid YAML. Fix the file first; it won't be overwritten.":""),I=$(()=>t.target==="usb"?"Connect the screen with a USB data cable to the Home Assistant machine; the list refreshes on its own. Or choose Download to use your own computer.":t.target==="download"?"Download the firmware and put it on the screen from your own computer, in Chrome or Edge. After that, updates go over Wi-Fi.":t.target?x.value.length>1?"More than one board connected: choose this screen's port.":"Once over USB; after that, everything is wireless.":"The profile goes into the ESPHome folder. You can install later via Firmware & USB, or from ESPHome Device Builder."),T=$(()=>t.target==="download"?"Build & download":t.target?"Install":"Save profile"),w=$(()=>o.value?.job?.state==="running"&&!(n.file&&o.value.job.file===n.file)),A=$(()=>f.value||P.value?.state==="invalid"||t.target==="usb"||!!t.target&&(w.value||!o.value?.available));function te(){const me=t.target;(me==="download"||me===""?n.picked:x.value.includes(me))||(t.target=x.value[0]||"usb")}async function $e(){let me;try{me=await gt("firmware")}catch(at){u.value=at.message;return}o.value=me;const N=me.job,fe=N&&n.file&&N.file===n.file;if(fe&&(n.jobState=N.state),n.view==="setup"){if(fe&&N.state==="running"){Me(N,me.logs||[]);return}te(),u.value=w.value?`A build or installation is already running (${N.file}). Wait for it to finish.`:!me.available&&t.target?"The ESPHome CLI is missing from this installation; only saving the profile is possible.":V.value}else n.view==="progress"&&fe&&(l.value=N,a.value=me.logs||[],N.state!=="running"&&N.state!=="success"&&(r.value=!0))}function Me(me,N){n.view="progress",n.jobState=me.state,n.action=me.action,l.value=me,a.value=N}const X=$(()=>l.value?.state==="running"),j=$(()=>n.view==="done"||l.value?.state==="success"),J=$(()=>n.action==="download"),ce=$(()=>n.view==="done"?"Profile saved.":X.value?"One moment…":j.value?J.value?"Ready to download.":"Done.":"That didn't work."),de=$(()=>n.view==="done"?`${n.file} is in the ESPHome folder`:X.value?l.value?.stage==="upload"?`Writing firmware to ${n.friendly}…`:"Building firmware…":j.value?J.value?`Firmware for ${n.friendly} is ready`:`Firmware is on ${n.friendly}`:J.value?"Build failed":"Install failed"),he=$(()=>n.view==="done"?"Install it later from Firmware & USB: choose this profile, then the USB port of the Home Assistant machine, or Download to put the firmware on the screen from your own computer. Save the API key for pairing:":X.value?l.value?.stage==="upload"?"Don't disconnect the USB cable yet.":`A first build takes a few minutes on a Raspberry Pi. You can leave this page: the ${J.value?"build":"installation"} keeps running and you'll find it again under New screen.`:j.value?J.value?"Put it on the screen from your own computer, then pair the screen with Home Assistant.":`The screen boots up and connects to your Wi-Fi.${n.board==="cyd"?" The CYD first asks for a touch calibration: tap the crosshairs.":""} Pair it with Home Assistant now:`:a.value.filter(me=>/error/i.test(me)).pop()||a.value.filter(me=>/failed/i.test(me)).pop()||"See the log below."),Q=$(()=>({href:`api/firmware/profiles/${encodeURIComponent(n.file||"")}/download`,name:(n.file||"").replace(/\.yaml$/,"")+".factory.bin"})),q=$(()=>[["Go to Home Assistant → Settings → Devices & services.",` This happens outside ESP Screens. Home Assistant discovers ${n.friendly} as an ESPHome device; click Add. Not discovered? Add ESPHome manually with the screen's IP address. `],["Paste the API key"," above when Home Assistant asks for an encryption key."],["Allow HA actions:"," ESPHome integration → Configure → “Allow the device to perform Home Assistant actions”. Without this, the screen sees everything but controls nothing."],["Choose your tiles."," Back in ESP Screens, the screen appears in the list on the left within about thirty seconds; until then it's shown there as “not yet in Home Assistant”."]]);async function B(me){const N=me.target;if(n.nodeEdited||(t.name=k(t.friendly_name)),!!N.reportValidity()){f.value=!0,d.value="";try{const fe={board:t.board,friendly_name:t.friendly_name,name:t.name,target:t.target};E.value&&(W.value.includes("wifi_ssid")&&(fe.wifi_ssid=t.wifi_ssid),W.value.includes("wifi_password")&&(fe.wifi_password=t.wifi_password));const at=await it("firmware/profiles","POST",fe);Object.assign(n,{file:at.file,apiKey:at.api_key,friendly:t.friendly_name.trim(),board:t.board,target:t.target}),t.wifi_password="",at.job?Me(at.job,[]):n.view="done"}catch(fe){d.value=fe.message}finally{f.value=!1}}}async function ne(){try{if(!J.value){const{ports:N}=await gt("firmware");!N.includes(n.target)&&N.length===1&&(n.target=N[0])}const me=await it("firmware/jobs","POST",J.value?{file:n.file,action:"download"}:{file:n.file,action:"install",target:n.target});Me(me,[])}catch(me){ge(me.message)}}function Qe(){Object.assign(n,{view:"setup",file:null,apiKey:null,nodeEdited:!1,jobState:null,target:"",picked:!1,action:null}),Object.assign(t,{board:"cyd",friendly_name:"",name:"",wifi_ssid:"",wifi_password:"",target:""}),s.value=!1,l.value=null,a.value=[],d.value="",u.value="",r.value=!1,$e()}function Ln(){n.view==="progress"&&n.jobState!=="running"&&(n.view="done"),Oe("")}return kn(()=>{$e(),b=window.setInterval($e,3e3)}),In(()=>clearInterval(b)),(me,N)=>(p(),v("div",wy,[i("div",_y,[i("div",ky,[N[13]||(N[13]=i("span",{class:"eyebrow"},"New screen",-1)),i("h1",$y,m(n.view==="setup"?"Connect and install.":ce.value),1),n.view==="setup"?(p(),v("p",xy,"ESP Screens creates the screen's own profile with unique keys and builds the firmware. Connect the screen with a USB data cable to the machine running Home Assistant, or download the firmware and put it on the screen from your own computer.")):F("",!0)]),i("button",{type:"button",class:"btn quiet",id:"close-install","aria-label":"Close",onClick:Ln},"← Back")]),n.view==="setup"?(p(),v("form",{key:0,id:"install-form",class:"card",onSubmit:ft(B,["prevent"])},[i("fieldset",null,[N[16]||(N[16]=i("legend",null,"Which screen do you have?",-1)),i("div",Sy,[i("label",Cy,[we(i("input",{type:"radio",name:"board",value:"cyd","onUpdate:modelValue":N[0]||(N[0]=fe=>t.board=fe)},null,512),[[Xi,t.board]]),N[14]||(N[14]=i("span",null,[i("b",null,"CYD · 2.8 inch"),i("small",null,"ESP32-2432S028 · 320 × 240")],-1))]),i("label",Ay,[we(i("input",{type:"radio",name:"board",value:"guition","onUpdate:modelValue":N[1]||(N[1]=fe=>t.board=fe)},null,512),[[Xi,t.board]]),N[15]||(N[15]=i("span",null,[i("b",null,"Guition · 4 inch"),i("small",null,"ESP32-S3-4848S040 · 480 × 480 · GT911")],-1))])])]),i("div",Ty,[N[18]||(N[18]=i("label",{class:"f-label",for:"friendly_name"},"Name",-1)),we(i("input",{id:"friendly_name",name:"friendly_name","onUpdate:modelValue":N[2]||(N[2]=fe=>t.friendly_name=fe),required:"",maxlength:"60",placeholder:"Living room screen",autocomplete:"off"},null,512),[[Fe,t.friendly_name]]),i("small",Ey,[N[17]||(N[17]=Z("Device name ",-1)),i("code",Py,m(C.value),1),i("button",{type:"button",class:"btn link mini",id:"edit-node",onClick:N[3]||(N[3]=fe=>{n.nodeEdited=!0,s.value=!0})},"customize")]),N[19]||(N[19]=i("small",null,"Every screen gets its own profile with its own keys. Four screens? Go through this four times with a different name.",-1))]),s.value?(p(),v("div",Oy,[N[20]||(N[20]=i("label",{class:"f-label",for:"node-name"},"Device name",-1)),we(i("input",{id:"node-name",name:"name","onUpdate:modelValue":N[4]||(N[4]=fe=>t.name=fe),pattern:"[a-z][a-z0-9\\-]{0,29}",maxlength:"30",autocomplete:"off",onInput:N[5]||(N[5]=fe=>n.nodeEdited=!0)},null,544),[[Fe,t.name]]),N[21]||(N[21]=i("small",null,"Lowercase letters, digits, and dashes. A different name for each screen.",-1))])):F("",!0),E.value?(p(),v("fieldset",Iy,[N[24]||(N[24]=i("legend",null,"Wi-Fi",-1)),i("p",My,m(S.value),1),W.value.includes("wifi_ssid")?(p(),v("div",Ly,[N[22]||(N[22]=i("label",{class:"f-label",for:"wifi_ssid"},"Wi-Fi name (2.4 GHz)",-1)),we(i("input",{id:"wifi_ssid",name:"wifi_ssid","onUpdate:modelValue":N[6]||(N[6]=fe=>t.wifi_ssid=fe),autocomplete:"off"},null,512),[[Fe,t.wifi_ssid]])])):F("",!0),W.value.includes("wifi_password")?(p(),v("div",Dy,[N[23]||(N[23]=i("label",{class:"f-label",for:"wifi_password"},"Wi-Fi password",-1)),we(i("input",{id:"wifi_password",name:"wifi_password",type:"password","onUpdate:modelValue":N[7]||(N[7]=fe=>t.wifi_password=fe),autocomplete:"new-password"},null,512),[[Fe,t.wifi_password]])])):F("",!0)])):F("",!0),i("div",Fy,[N[26]||(N[26]=i("label",{class:"f-label",for:"install-target"},"Install via",-1)),we(i("select",{id:"install-target",name:"target","onUpdate:modelValue":N[8]||(N[8]=fe=>t.target=fe),onChange:N[9]||(N[9]=fe=>{n.picked=!0,$e()})},[x.value.length?F("",!0):(p(),v("option",Ny,"USB · no board found on the Home Assistant machine yet")),(p(!0),v(U,null,le(x.value,fe=>(p(),v("option",{key:fe,value:fe},m(R(fe)),9,Ry))),128)),i("option",{value:"download"},m(ig)),N[25]||(N[25]=i("option",{value:""},"Later · save profile only",-1))],544),[[vn,t.target]]),i("small",Hy,m(I.value),1)]),u.value?(p(),v("p",Uy,m(u.value),1)):F("",!0),i("div",jy,[i("button",{type:"submit",class:"btn primary",id:"install-go",disabled:A.value},m(T.value),9,By),i("span",Vy,m(d.value),1)])],32)):(p(),v("div",Wy,[i("div",Ky,[X.value?(p(),v("span",qy)):(p(),v("span",{key:1,class:ie(["outcome",j.value?"ok":"bad"]),id:"progress-mark"},m(j.value?"✓":"✕"),3)),i("strong",Yy,m(de.value),1)]),i("p",zy,m(he.value),1),j.value&&J.value&&n.view!=="done"?(p(),v("div",Jy,[i("a",{class:"btn primary",id:"download-firmware",href:Q.value.href,download:Q.value.name},"Download "+m(Q.value.name),9,Gy),i("ol",Xy,[N[30]||(N[30]=i("li",null,[i("b",null,"Plug the screen into your computer"),Z(" with a USB data cable.")],-1)),i("li",null,[i("b",null,[N[27]||(N[27]=Z("Open ",-1)),i("a",{href:og,target:"_blank",rel:"noopener"},"ESPHome Web")]),N[28]||(N[28]=Z(" in Chrome or Edge, click Connect and choose the screen's USB port.",-1))]),i("li",null,[N[29]||(N[29]=i("b",null,"Click Install",-1)),Z(" and select "+m(Q.value.name)+". The screen restarts and joins your Wi-Fi."+m(n.board==="cyd"?" The CYD first asks for a touch calibration: tap the crosshairs.":""),1)])]),N[31]||(N[31]=i("small",null,"The file holds your Wi-Fi password and the screen's keys: keep it to yourself.",-1))])):F("",!0),j.value?(p(),v("div",Zy,[i("div",Qy,[N[32]||(N[32]=i("span",null,"API key",-1)),i("code",{id:"api-key",ref_key:"keyBox",ref:h},m(n.apiKey||""),513),i("button",{type:"button",class:"btn quiet mini",id:"copy-key",onClick:N[10]||(N[10]=fe=>y(ht)(n.apiKey||"",h.value))},"Copy")]),i("ol",eg,[(p(!0),v(U,null,le(q.value,([fe,at],Dn)=>(p(),v("li",{key:Dn},[i("b",null,m(fe),1),Z(m(at),1),Dn===0?(p(),v("button",{key:0,type:"button",class:"btn quiet mini",onClick:N[11]||(N[11]=(...ln)=>y(Fs)&&y(Fs)(...ln))},"Open Devices & services")):F("",!0)]))),128))])])):F("",!0),n.view!=="done"?(p(),v("details",{key:2,id:"install-log-wrap",class:"log-wrap",open:r.value,onToggle:N[12]||(N[12]=fe=>r.value=fe.target.open)},[N[33]||(N[33]=i("summary",null,"ESPHome log",-1)),i("pre",ng,m(a.value.join(` +`)),1)],40,tg)):F("",!0),i("div",sg,[!X.value&&!j.value?(p(),v("button",{key:0,type:"button",class:"btn primary",id:"install-retry",onClick:ne},"Retry")):F("",!0),i("button",{type:"button",class:"btn quiet",id:"install-close",onClick:Qe},m(j.value?"Install another screen":"Start over"),1),i("button",{type:"button",class:ie(["btn",j.value?"primary":"quiet"]),onClick:Ln},m(j.value?"Done":"Close"),3)])]))]))}}),ag={class:"panel",id:"firmware-dialog"},rg={class:"panel-head"},ug={class:"card"},cg={class:"card-grid"},dg={class:"field"},fg=["value"],pg={class:"field"},hg={key:0,value:"usb"},vg=["value"],mg={key:0,class:"field",id:"firmware-host-label"},yg=["placeholder"],gg={class:"actions"},bg=["disabled"],wg=["disabled"],_g=["disabled"],kg={key:0,class:"spin"},$g={id:"firmware-status",class:"status-line",role:"status"},xg={key:0,id:"firmware-download",class:"card",style:{background:"var(--surface-2)"}},Sg=["href","download"],Cg={id:"firmware-log",class:"log"},Ag="https://web.esphome.io/?dashboard_install",Tg="Download · flash from your own computer",Eg=Ee({__name:"FirmwareView",setup(e){const t=pe(null),n=pe(""),s=pe("ota"),o=pe("");let l=0;async function a(C=!1){try{const x=await gt("firmware");t.value=x,(C||!n.value)&&(n.value=x.profiles[0]?.file||"");const P=x.ports||[];s.value.startsWith("/")&&!P.includes(s.value)&&(s.value=P[0]||"usb"),s.value==="usb"&&P.length&&(s.value=P[0])}catch(x){ge(x.message)}}const u=$(()=>t.value?.job?.state==="running"),d=$(()=>!t.value||u.value||!t.value.available||!t.value.profiles?.length),f=$(()=>d.value||s.value==="usb"),r=$(()=>t.value?.ports||[]),h=$(()=>t.value?t.value.available?t.value.job?`${t.value.job.file} · ${t.value.job.action} · ${t.value.job.state}`:"Choose the intended profile, then a USB port, an IP address, or Download.":"The ESPHome CLI is missing. Update the app to 0.2.0.":"Loading…"),b=$(()=>s.value==="download"&&!!n.value&&!!t.value?.downloads?.includes(n.value)),k=$(()=>({href:`api/firmware/profiles/${encodeURIComponent(n.value)}/download`,name:n.value.replace(/\.yaml$/,"")+".factory.bin"}));async function R(C){try{await it("firmware/jobs","POST",{file:n.value,action:C==="validate"?"validate":C==="build"?"build":s.value==="download"?"download":"install",target:s.value==="ota"?o.value.trim():s.value==="download"?"":s.value}),await a()}catch(x){ge(x.message)}}return kn(()=>{a(!0),l=window.setInterval(()=>a(),3e3)}),In(()=>clearInterval(l)),(C,x)=>(p(),v("div",ag,[i("div",rg,[x[7]||(x[7]=Cs('
Firmware & USB

Build and install screen firmware

The built-in ESPHome CLI uses your own profile (YAML with keys) and secrets. Choose the screen's profile, then a USB port (screen connected to the Home Assistant machine), Wi-Fi / OTA with the hostname, or Download to put the firmware on the screen from your own computer. Check only validates, Build only compiles, Build & install puts the firmware on the screen. Your tiles stay as they are in ESP Screens. The first build takes a few minutes.

',1)),i("button",{type:"button",class:"btn quiet",id:"close-firmware",onClick:x[0]||(x[0]=P=>y(Oe)(""))},"← Back")]),i("div",ug,[i("div",cg,[i("div",dg,[x[8]||(x[8]=i("label",{class:"f-label",for:"firmware-file"},"ESPHome profile",-1)),we(i("select",{id:"firmware-file","onUpdate:modelValue":x[1]||(x[1]=P=>n.value=P)},[(p(!0),v(U,null,le(t.value?.profiles||[],P=>(p(),v("option",{key:P.file,value:P.file},m(P.file),9,fg))),128))],512),[[vn,n.value]])]),i("div",pg,[x[10]||(x[10]=i("label",{class:"f-label",for:"firmware-port"},"Install to",-1)),we(i("select",{id:"firmware-port","onUpdate:modelValue":x[2]||(x[2]=P=>s.value=P)},[x[9]||(x[9]=i("option",{value:"ota"},"Wi-Fi / OTA",-1)),r.value.length?F("",!0):(p(),v("option",hg,"USB · no board found on the Home Assistant machine yet")),(p(!0),v(U,null,le(r.value,P=>(p(),v("option",{key:P,value:P},m(P),9,vg))),128)),i("option",{value:"download"},m(Tg))],512),[[vn,s.value]])]),s.value==="ota"?(p(),v("div",mg,[x[11]||(x[11]=i("label",{class:"f-label",for:"firmware-host"},"IP address or hostname",-1)),we(i("input",{id:"firmware-host","onUpdate:modelValue":x[3]||(x[3]=P=>o.value=P),placeholder:n.value?n.value.replace(/\.yaml$/,".local"):"screen-livingroom.local"},null,8,yg),[[Fe,o.value]])])):F("",!0)]),i("div",gg,[i("button",{type:"button",class:"btn quiet",id:"firmware-validate",disabled:d.value,onClick:x[4]||(x[4]=P=>R("validate"))},"Check",8,bg),i("button",{type:"button",class:"btn quiet",id:"firmware-build",disabled:d.value,onClick:x[5]||(x[5]=P=>R("build"))},"Build only",8,wg),i("button",{type:"button",class:"btn primary",id:"firmware-install",disabled:f.value,onClick:x[6]||(x[6]=P=>R("install"))},m(s.value==="download"?"Build & download":"Build & install"),9,_g),u.value?(p(),v("span",kg)):F("",!0)]),i("p",$g,m(h.value),1),b.value?(p(),v("div",xg,[i("a",{class:"btn primary",id:"firmware-download-link",href:k.value.href,download:k.value.name},"Download "+m(k.value.name),9,Sg),i("small",null,[x[12]||(x[12]=Z("Put it on the screen with ",-1)),i("a",{href:Ag,target:"_blank",rel:"noopener"},"ESPHome Web"),x[13]||(x[13]=Z(" in Chrome or Edge: Connect, choose the screen's USB port, Install, and select this file. It holds your Wi-Fi password and the screen's keys: keep it to yourself.",-1))])])):F("",!0)]),i("pre",Cg,m((t.value?.logs||[]).join(` +`)),1)]))}}),Pg={class:"panel wide alerts",id:"alerts-dialog"},Og={class:"panel-head"},Ig={key:0,class:"hint"},Mg={class:"alert-hero"},Lg={class:"alert-mock",id:"alert-mock","aria-hidden":"true"},Dg={class:"mdi alert-mock-icon",id:"alert-mock-icon"},Fg={class:"alerts-nav","aria-label":"Sections"},Ng=["data-jump","onClick"],Rg={id:"alerts-try",class:"card"},Hg={class:"field"},Ug={value:"all"},jg=["value","disabled"],Bg={class:"field"},Vg={class:"field"},Wg={class:"field"},Kg={id:"try-icons"},qg=["value"],Yg={class:"field"},zg=["value"],Jg={class:"field"},Gg={class:"field"},Xg={class:"check field"},Zg={class:"actions field wide"},Qg=["disabled"],eb={class:"status-line",id:"try-result",role:"status"},tb={id:"alerts-screens",class:"card"},nb={id:"alerts-min-firmware"},sb={id:"alerts-screen-list",class:"options"},ob={key:0,class:"hint"},ib={class:"alert-screen-head"},lb={class:"copy-label"},ab=["onClick"],rb={id:"alerts-howto",class:"card"},ub={class:"field"},cb=["value"],db={key:0,value:""},fb={class:"copy-line"},pb={id:"alerts-example"},hb={id:"alerts-all",class:"card"},vb={id:"alerts-all-event"},mb={id:"alerts-all-dismiss"},yb={class:"copy-line"},gb={id:"alerts-all-example"},bb={id:"alerts-fields",class:"card"},wb={class:"table-scroll"},_b={id:"alerts-field-table"},kb={key:0},$b={id:"alerts-icons",class:"card"},xb={class:"chips",id:"alerts-suggested"},Sb=["onClick"],Cb={class:"mdi"},Ab={class:"field"},Tb={id:"alerts-icon-groups"},Eb={class:"subhead"},Pb={class:"alert-icon-grid"},Ob=["title","onClick"],Ib={class:"mdi"},Mb={key:0},Lb={key:0,class:"hint"},Db={id:"alerts-colors",class:"card"},Fb={class:"swatches",id:"alerts-swatches"},Nb=["onClick"],Rb={id:"alerts-events",class:"card"},Hb={id:"alerts-event-name"},Ub={class:"table-scroll"},jb={id:"alerts-ending-table"},Bb={class:"copy-line"},Vb={id:"alerts-wait-example"},Wb={id:"alerts-tips",class:"card"},Kb={class:"alerts-list"},qb=Ee({__name:"AlertsView",setup(e){const t=$(()=>c.inventory.alerts),n=bn({screen:"all",title:"Someone is at the door",subtitle:"Door 3, back",icon:"doorbell",color:"orange",button_text:"Coming",timeout:30,flash:!0}),s=pe(!1),o=pe(""),l=$(()=>c.inventory.screens.filter(T=>gn(T)&&T.online));async function a(){s.value=!0,o.value="";try{const{screen:T,...w}=n,A=await sd(T,w),te=T==="all"?`${A.sent} screen${A.sent===1?"":"s"}`:c.inventory.screens.find($e=>$e.id===T)?.name||"the screen";o.value=A.sent?`Sent to ${te}.${A.skipped?` ${A.skipped} skipped (offline or old firmware).`:""}${A.unusable?.length?` Left empty: ${A.unusable.join(", ")}.`:""}`:"No screen could show it: check that a screen is online with firmware "+(t.value?.min_firmware||"0.2.31")+" or newer."}catch(T){o.value=T.message}finally{s.value=!1}}const u=$(()=>c.inventory.icons),d=pe(""),f=pe(""),r=T=>`"${String(T).replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`,h=T=>T.type==="string"?/^[a-z][a-z0-9-]*$/.test(T.example)?T.example:r(T.example):T.example===!0?"true":T.example===!1?"false":String(T.example),b=$(()=>c.inventory.screens.filter(T=>T.alert_action)),k=$(()=>d.value||b.value[0]?.alert_action||""),R=$(()=>{const T=(t.value?.fields||[]).map(w=>` ${w.name}: ${h(w)}`);return`action: ${k.value||"esphome._show_alert"} +data: +${T.join(` +`)}`}),C=$(()=>["# Doorbell: show the alert and wait until someone presses the button.","actions:",` - action: ${k.value||"esphome._show_alert"}`," data:",' title: "Someone is at the door"',' subtitle: "Door 3, back"'," icon: doorbell"," color: orange",' button_text: "Coming"'," timeout: 0"," flash: true"," - wait_for_trigger:"," - trigger: event",` event_type: ${t.value?.event||"esphome.screen_alert"}`," event_data:"," action: ok",' timeout: "00:05:00"'," - if:"," - condition: template",' value_template: "{{ wait.trigger is not none }}"'," then:"," - action: notify.notify"," data:",' message: "Someone is coming to the door."'].join(` +`)),x=$(()=>{const T=(t.value?.fields||[]).map(A=>` ${A.name}: ${h(A)}`),w=t.value?.camera;return w&&T.push(` # ${w.name}: ${w.example} # a Guition shows its picture on the card`),`event: ${t.value?.broadcast?.show||"esp_screens_show_alert"} +event_data: +${T.join(` +`)}`}),P=$(()=>{if(!t.value||!u.value)return[];const T=f.value.trim().toLowerCase();return[...u.value.groups,{label:"Also available: control and weather icons",icons:t.value.extra_icons}].map(A=>({label:A.label,icons:A.icons.filter(te=>!T||te.name.includes(T)||(te.label||"").toLowerCase().includes(T))})).filter(A=>A.icons.length)}),E=$(()=>t.value?.suggested_icons?.find(T=>T.name==="doorbell")),W=$(()=>t.value?.colors?.find(T=>T.name==="orange")),S={string:"text",int:"number",bool:"on / off"};function V(T){document.getElementById(T)?.scrollIntoView({behavior:"smooth",block:"start"})}const I=[["alerts-try","Try it"],["alerts-screens","Your screens"],["alerts-howto","In an automation"],["alerts-all","All screens"],["alerts-fields","The fields"],["alerts-icons","Icons"],["alerts-colors","Colors"],["alerts-behaviour","Behavior"],["alerts-events","Events"],["alerts-tips","Tips"]];return(T,w)=>(p(),v("div",Pg,[i("div",Og,[w[15]||(w[15]=i("div",{class:"tx"},[i("span",{class:"eyebrow"},"Alerts · cheatsheet"),i("h1",null,"A full-screen notification, from an automation."),i("p",null,[Z("Every screen has the "),i("code",null,"show_alert"),Z(" action. It places a card over everything, wakes the screen, and keeps the backlight at normal brightness until someone taps the button or the timeout runs out. Home Assistant itself shows no explanation or dropdowns for ESPHome actions; everything you can fill in is listed here.")])],-1)),i("button",{type:"button",class:"btn quiet",id:"close-alerts",onClick:w[0]||(w[0]=A=>y(Oe)(""))},"← Back")]),t.value?(p(),v(U,{key:1},[i("div",Mg,[i("div",Lg,[i("div",{class:"alert-mock-card",style:Pe(W.value?{background:W.value.color}:void 0)},[i("span",Dg,m(E.value?y(ke)(E.value.cp):""),1),w[16]||(w[16]=i("strong",{id:"alert-mock-title"},"Someone is at the door",-1)),w[17]||(w[17]=i("span",{id:"alert-mock-subtitle"},"Door 3, back",-1)),w[18]||(w[18]=i("span",{class:"fake",id:"alert-mock-button"},"Coming",-1))],4)]),i("nav",Fg,[(p(),v(U,null,le(I,([A,te])=>i("button",{key:A,type:"button","data-jump":A,onClick:$e=>V(A)},m(te),9,Ng)),64))])]),i("section",Rg,[w[28]||(w[28]=i("h2",null,"Try it",-1)),w[29]||(w[29]=i("p",null,"Send one alert now, with the same fields an automation sends. The screen shows the card and, with blinking on, flashes its backlight.",-1)),i("form",{class:"try-grid",onSubmit:ft(a,["prevent"])},[i("div",Hg,[w[19]||(w[19]=i("label",{class:"f-label",for:"try-screen"},"Screen",-1)),we(i("select",{id:"try-screen","onUpdate:modelValue":w[1]||(w[1]=A=>n.screen=A)},[i("option",Ug,"All screens ("+m(l.value.length)+" ready)",1),(p(!0),v(U,null,le(y(c).inventory.screens,A=>(p(),v("option",{key:A.id,value:A.id,disabled:!y(gn)(A)||!A.online},m(A.name)+m(y(gn)(A)&&A.online?"":" · not ready"),9,jg))),128))],512),[[vn,n.screen]])]),i("div",Bg,[w[20]||(w[20]=i("label",{class:"f-label",for:"try-title"},"Title",-1)),we(i("input",{id:"try-title","onUpdate:modelValue":w[2]||(w[2]=A=>n.title=A),maxlength:"64"},null,512),[[Fe,n.title]])]),i("div",Vg,[w[21]||(w[21]=i("label",{class:"f-label",for:"try-subtitle"},"Subtitle",-1)),we(i("input",{id:"try-subtitle","onUpdate:modelValue":w[3]||(w[3]=A=>n.subtitle=A),maxlength:"240"},null,512),[[Fe,n.subtitle]])]),i("div",Wg,[w[22]||(w[22]=i("label",{class:"f-label",for:"try-icon"},"Icon",-1)),we(i("input",{id:"try-icon","onUpdate:modelValue":w[4]||(w[4]=A=>n.icon=A),list:"try-icons",placeholder:"doorbell"},null,512),[[Fe,n.icon]]),i("datalist",Kg,[(p(!0),v(U,null,le(t.value.suggested_icons,A=>(p(),v("option",{key:A.name,value:A.name},m(A.label||A.name),9,qg))),128))])]),i("div",Yg,[w[24]||(w[24]=i("label",{class:"f-label",for:"try-color"},"Color",-1)),we(i("select",{id:"try-color","onUpdate:modelValue":w[5]||(w[5]=A=>n.color=A)},[w[23]||(w[23]=i("option",{value:""},"White (default)",-1)),(p(!0),v(U,null,le(t.value.colors,A=>(p(),v("option",{key:A.name,value:A.name},m(A.label),9,zg))),128))],512),[[vn,n.color]])]),i("div",Jg,[w[25]||(w[25]=i("label",{class:"f-label",for:"try-button"},"Button text",-1)),we(i("input",{id:"try-button","onUpdate:modelValue":w[6]||(w[6]=A=>n.button_text=A),maxlength:"16"},null,512),[[Fe,n.button_text]])]),i("div",Gg,[w[26]||(w[26]=i("label",{class:"f-label",for:"try-timeout"},"Timeout (seconds, 0 waits for the button)",-1)),we(i("input",{id:"try-timeout","onUpdate:modelValue":w[7]||(w[7]=A=>n.timeout=A),type:"number",min:"0",max:"86400"},null,512),[[Fe,n.timeout,void 0,{number:!0}]])]),i("label",Xg,[we(i("input",{type:"checkbox",id:"try-flash","onUpdate:modelValue":w[8]||(w[8]=A=>n.flash=A)},null,512),[[vc,n.flash]]),w[27]||(w[27]=i("span",null,[Z("Blink the backlight"),i("small",null,"Four times when the alert arrives.")],-1))]),i("div",Zg,[i("button",{type:"submit",class:"btn primary",id:"try-send",disabled:s.value||!l.value.length},m(s.value?"Sending…":"Send alert"),9,Qg),i("span",eb,m(o.value),1)])],32)]),i("section",tb,[w[32]||(w[32]=i("h2",null,"Your screens",-1)),i("p",null,[w[30]||(w[30]=Z("Every screen has its own action, with the device name in it. From firmware ",-1)),i("code",nb,m(t.value.min_firmware),1),w[31]||(w[31]=Z("; update an older screen with its Update button under Screens.",-1))]),i("div",sb,[y(c).inventory.screens.length?F("",!0):(p(),v("p",ob,"No screen paired yet. Install one via New screen; the action appears once Home Assistant sees the screen.")),(p(!0),v(U,null,le(y(c).inventory.screens,A=>(p(),v("div",{key:A.id,class:"alert-screen"},[i("div",ib,[i("strong",null,m(A.name),1),i("span",{class:ie(["chip",y(nn)(y(as)(A),t.value.min_firmware)&&A.alert_action?"good":"update"])},m(y(nn)(y(as)(A),t.value.min_firmware)&&A.alert_action?`● firmware ${A.firmware}`:A.alert_action?`Update needed · firmware ${A.firmware||"unknown"}`:"Device name unknown · update the screen"),3)]),(p(!0),v(U,null,le([["Show alert",A.alert_action],["Dismiss alert",A.dismiss_action]],([te,$e])=>(p(),v("div",{key:te,class:"copy-line"},[i("span",lb,m(te),1),i("code",null,m($e||"esphome._show_alert"),1),$e?(p(),v("button",{key:0,type:"button",class:"btn quiet mini",onClick:Me=>y(ht)($e,void 0,"Action name")},"Copy",8,ab)):F("",!0)]))),128))]))),128))])]),i("section",rb,[w[34]||(w[34]=Cs('

In an automation

  1. Add action and search for ESPHome. Choose your screen's action: Home Assistant calls it "Performs the action show_alert of the node device name".
  2. Fill in the fields. Home Assistant asks for all seven; leave what you don't use empty ("", 0, off).
  3. Prefer YAML? Choose Edit in YAML on the action and paste the example below.
',2)),i("div",ub,[w[33]||(w[33]=i("label",{class:"f-label",for:"alerts-example-screen"},"Example for",-1)),we(i("select",{id:"alerts-example-screen","onUpdate:modelValue":w[9]||(w[9]=A=>d.value=A),style:{"max-width":"360px"}},[(p(!0),v(U,null,le(b.value,A=>(p(),v("option",{key:A.id,value:A.alert_action},m(A.name),9,cb))),128)),b.value.length?F("",!0):(p(),v("option",db,"a screen (not paired yet)"))],512),[[vn,d.value]])]),i("div",fb,[i("pre",pb,m(R.value),1),i("button",{type:"button",class:"btn quiet mini",id:"alerts-example-copy",onClick:w[10]||(w[10]=A=>y(ht)(R.value,void 0,"YAML"))},"Copy YAML")])]),i("section",hb,[w[42]||(w[42]=i("h2",null,"All screens at once",-1)),i("p",null,[w[35]||(w[35]=Z("Fire the event ",-1)),i("code",vb,m(t.value.broadcast?.show||"esp_screens_show_alert"),1),w[36]||(w[36]=Z(" with the same fields, and ESP Screens passes the alert on to every screen that is online, including screens you add later. ",-1)),i("code",mb,m(t.value.broadcast?.dismiss||"esp_screens_dismiss_alert"),1),w[37]||(w[37]=Z(" clears it everywhere. In an automation, add the ",-1)),w[38]||(w[38]=i("b",null,"Event",-1)),w[39]||(w[39]=Z(" action or paste the example under ",-1)),w[40]||(w[40]=i("b",null,"Edit in YAML",-1)),w[41]||(w[41]=Z(". Leave out the fields you don't use. ESP Screens has to be running for this.",-1))]),i("div",yb,[i("pre",gb,m(x.value),1),i("button",{type:"button",class:"btn quiet mini",id:"alerts-all-copy",onClick:w[11]||(w[11]=A=>y(ht)(x.value,void 0,"YAML"))},"Copy YAML")])]),i("section",bb,[w[46]||(w[46]=i("h2",null,"The fields",-1)),w[47]||(w[47]=i("p",null,"Text is free-form; the limit is in bytes, and an accented letter counts as two. What doesn't fit gets an ellipsis (title) or is dropped (subtitle).",-1)),i("div",wb,[i("table",_b,[w[45]||(w[45]=i("tr",null,[i("th",null,"Field"),i("th",null,"Type"),i("th",null,"What it does"),i("th",null,"Example"),i("th",null,"Limit")],-1)),(p(!0),v(U,null,le(t.value.fields,A=>(p(),v("tr",{key:A.name},[i("td",null,[i("code",null,m(A.name),1),i("small",null,m(A.label),1)]),i("td",null,m(S[A.type]||A.type),1),i("td",null,m(A.help),1),i("td",null,[i("code",null,m(typeof A.example=="string"?A.example:String(A.example)),1)]),i("td",null,m(t.value.limits.cyd[A.name]?`CYD ${t.value.limits.cyd[A.name]} · Guition ${t.value.limits.guition[A.name]} bytes`:A.type==="int"?"0 to 86400 s":"—"),1)]))),128)),t.value.camera?(p(),v("tr",kb,[i("td",null,[i("code",null,m(t.value.camera.name),1),i("small",null,m(t.value.camera.label),1)]),w[43]||(w[43]=i("td",null,"entity",-1)),i("td",null,m(t.value.camera.help),1),i("td",null,[i("code",null,m(t.value.camera.example),1)]),w[44]||(w[44]=i("td",null,"Guition, firmware 0.2.57+",-1))])):F("",!0)])])]),i("section",$b,[w[49]||(w[49]=i("h2",null,"Icons",-1)),w[50]||(w[50]=i("p",null,[Z("Fill in the name in "),i("code",null,"icon"),Z(", for example "),i("code",null,"doorbell"),Z(". "),i("code",null,"mdi:doorbell"),Z(" and the hex codepoint ("),i("code",null,"F12E6"),Z(") also work. Only these icons are included in the firmware; any other name shows the warning triangle. Tap an icon to copy its name.")],-1)),w[51]||(w[51]=i("p",{class:"subhead"},"Handy for notifications",-1)),i("div",xb,[(p(!0),v(U,null,le(t.value.suggested_icons,A=>(p(),v("button",{key:A.name,type:"button",class:"chip",onClick:te=>y(ht)(A.name,void 0,"Icon name")},[i("span",Cb,m(y(ke)(A.cp)),1),i("code",null,m(A.name),1)],8,Sb))),128))]),i("div",Ab,[w[48]||(w[48]=i("label",{class:"f-label",for:"alerts-icon-search"},"Search for an icon",-1)),we(i("input",{id:"alerts-icon-search","onUpdate:modelValue":w[12]||(w[12]=A=>f.value=A),type:"search",placeholder:"Search by name or description…",autocomplete:"off"},null,512),[[Fe,f.value]])]),i("div",Tb,[(p(!0),v(U,null,le(P.value,A=>(p(),v("div",{key:A.label,class:"field"},[i("p",Eb,m(A.label)+" · "+m(A.icons.length),1),i("div",Pb,[(p(!0),v(U,null,le(A.icons,te=>(p(),v("button",{key:te.name,type:"button",class:"alert-icon",title:`Copy ${te.name}`,onClick:$e=>y(ht)(te.name,void 0,"Icon name")},[i("span",Ib,m(y(ke)(te.cp)),1),i("code",null,m(te.name),1),te.label?(p(),v("small",Mb,m(te.label),1)):F("",!0)],8,Ob))),128))])]))),128)),P.value.length?F("",!0):(p(),v("p",Lb,'No icon for "'+m(f.value)+'". Unknown names show the warning triangle ('+m(t.value.fallback_icon)+").",1))])]),i("section",Db,[w[53]||(w[53]=i("h2",null,"Colors",-1)),w[54]||(w[54]=i("p",null,[Z("Fill in the name in "),i("code",null,"color"),Z(". These are the pastel shades of the tiles, with dark text. Empty or unknown gives the white card. Tap to copy.")],-1)),i("div",Fb,[i("button",{type:"button",class:"swatch",onClick:w[13]||(w[13]=A=>y(ht)("",void 0,"Empty color"))},[...w[52]||(w[52]=[i("i",{style:{background:"#ffffff"}},null,-1),i("span",null,[i("code",null,"empty"),i("small",null,"White (default)")],-1)])]),(p(!0),v(U,null,le(t.value.colors,A=>(p(),v("button",{key:A.name,type:"button",class:"swatch",onClick:te=>y(ht)(A.name,void 0,"Color name")},[i("i",{style:Pe({background:A.color})},null,4),i("span",null,[i("code",null,m(A.name),1),i("small",null,m(A.label)+" · "+m(A.color),1)])],8,Nb))),128))])]),w[75]||(w[75]=Cs('

Behavior

  • Wakes the screen. An alert immediately wakes a dimmed or dark screen and appears above every page, every open light card, and the standby layer.
  • On until the button. As long as the card is showing, the backlight stays at normal brightness. Standby and night hours wait. After the button, the regular standby time starts again.
  • Timeout. timeout: 0 waits for the button, however long that takes. With timeout: 60 the card disappears after a minute, but the button closes it sooner.
  • Blinking. flash: true blinks the backlight four times when the alert arrives (about a second) and leaves the screen on afterward.
  • Replacing. A new alert replaces the current one; there's no stack. The replaced alert reports itself as replaced.
  • Closing remotely. dismiss_alert clears the card, for example if the door is already open. Without an alert, that action does nothing.
  • Tapping beside the card does nothing; only the button closes it. That way a wake-up tap never accidentally dismisses the alert.
',1)),i("section",Rb,[w[66]||(w[66]=i("h2",null,"Events",-1)),i("p",null,[w[55]||(w[55]=Z("Every end fires the event ",-1)),i("code",Hb,m(t.value.event),1),w[56]||(w[56]=Z(" with ",-1)),w[57]||(w[57]=i("code",null,"action",-1)),w[58]||(w[58]=Z(", ",-1)),w[59]||(w[59]=i("code",null,"title",-1)),w[60]||(w[60]=Z(", ",-1)),w[61]||(w[61]=i("code",null,"screen",-1)),w[62]||(w[62]=Z(" (the device name) and the ",-1)),w[63]||(w[63]=i("code",null,"device_id",-1)),w[64]||(w[64]=Z(" that Home Assistant adds. Visible under Settings → Tools → Events.",-1))]),i("div",Ub,[i("table",jb,[w[65]||(w[65]=i("tr",null,[i("th",null,"action"),i("th",null,"When")],-1)),(p(!0),v(U,null,le(t.value.endings,A=>(p(),v("tr",{key:A.action},[i("td",null,[i("code",null,m(A.action),1)]),i("td",null,m(A.label),1)]))),128))])]),w[67]||(w[67]=i("p",{class:"subhead"},"Waiting until someone presses the button",-1)),i("div",Bb,[i("pre",Vb,m(C.value),1),i("button",{type:"button",class:"btn quiet mini",id:"alerts-wait-copy",onClick:w[14]||(w[14]=A=>y(ht)(C.value,void 0,"YAML"))},"Copy YAML")])]),i("section",Wb,[w[74]||(w[74]=i("h2",null,"Tips",-1)),i("ul",Kb,[w[72]||(w[72]=Cs("
  • Use a color per kind of notification: orange for the door, red for smoke or water, green for "done". The text always stays dark and readable.
  • Keep the title short; a screen shows about twenty characters on one line and ends a longer title with an ellipsis. Put details in the subtitle.
  • A custom button text turns the notification into a question: "Coming", "Seen", "Turn off". The event then tells you who responded.
  • For an alert that resolves itself (the door opens, the laundry gets taken out) close it in the same automation with dismiss_alert.
  • ",4)),i("li",null,[w[68]||(w[68]=Z("Every screen? Fire ",-1)),i("code",null,m(t.value.broadcast?.show||"esp_screens_show_alert"),1),w[69]||(w[69]=Z(" once (see All screens at once). The screens still work independently and each reports with its own ",-1)),w[70]||(w[70]=i("code",null,"screen",-1)),w[71]||(w[71]=Z(".",-1))]),w[73]||(w[73]=i("li",null,[Z("Rather "),i("b",null,"ask Claude"),Z(" for the automation? Install the ESP Screens skill under Settings → Claude.")],-1))])])],64)):(p(),v("p",Ig,"The cheatsheet is still loading; one moment."))]))}}),Yb={class:"panel",id:"override-dialog"},zb={class:"panel-head"},Jb={class:"tx"},Gb={id:"override-title"},Xb={class:"notice"},Zb={class:"mdi"},Qb={class:"file-row"},e1={id:"override-file"},t1={class:"yaml-editor",id:"yaml-editor-wrap"},n1={class:"actions"},s1={id:"override-count",class:"hint"},o1={class:"actions"},i1=["disabled"],l1=["disabled"],a1=`# Hardware-specific changes for this screen. +# This file is kept when the shared firmware package updates. +# Do not add esphome:, api:, ota:, wifi: or packages: here. + +# Example: a CYD with the ST7789V display controller. +# "!extend" changes the display the shared package already defines; +# a bare id would add a second, incomplete display and fail the build. +display: + - id: !extend my_display + model: ST7789V +`,r1=Ee({__name:"OverrideView",setup(e){const t=$(()=>c.overrideProfile),n=pe(""),s=pe(""),o=pe(!1),l=pe("Loading…"),a=pe(""),u=pe(!1),d=pe(null),f=pe(null),r=$(()=>n.value.split(` +`).length),h=$(()=>Array.from({length:r.value},(I,T)=>T+1).join(` +`)),b=$(()=>`${n.value.length} characters · ${r.value} line${r.value===1?"":"s"}`);let k=!0;function R(I,T=""){l.value=I,a.value=T}async function C(){if(!t.value){R("No ESPHome profile was found for this screen.","error");return}R("Loading…");try{const I=await gt(`firmware/profiles/${encodeURIComponent(t.value)}/override`);s.value=I.override_file,o.value=!!I.attached,n.value=I.exists&&I.content!==`{} +`?I.content:"",R(I.attached?"Changes here apply on the next build.":"Save once to attach this file to the profile.")}catch(I){R(I.message,"error")}}async function x(I=!1){if(!(!t.value||u.value)){u.value=!0,R("Checking and saving…");try{const T=await it(`firmware/profiles/${encodeURIComponent(t.value)}/override`,"PUT",{content:n.value});if(s.value=T.override_file,o.value=!0,!I){R("Saved. The override is kept during firmware updates.","ok"),ge("Override YAML saved.");return}R("Saved. ESPHome is checking the complete profile…"),await it("firmware/jobs","POST",{file:t.value,action:"validate"}),P()}catch(T){R(T.message,"error")}finally{u.value=!1}}}function P(){const I=Date.now(),T=t.value,w=async()=>{if(!(!k||t.value!==T))try{const A=await gt("firmware"),te=A.job;if(te&&te.file===T&&te.state==="running")R(`ESPHome is checking the profile… ${te.stage||""}`.trim());else if(te&&te.file===T&&te.state==="success"){R("The complete profile is valid. Safe to build and install.","ok");return}else if(te&&te.file===T&&te.state==="failed"){const $e=(A.logs||[]).filter(Me=>/error|failed/i.test(Me)).pop();R($e||"ESPHome rejected the complete profile. See Firmware & USB for the full log.","error");return}Date.now()-I<72e5&&setTimeout(w,1200)}catch(A){R(A.message,"error")}};w()}function E(){(!n.value.trim()||confirm("Replace the current text with the example?"))&&(n.value=a1,R("Example loaded. Save it when you are ready."),d.value?.focus())}function W(){(!n.value.trim()||confirm("Clear the local override?"))&&(n.value="",R("The override will be empty after you save."),d.value?.focus())}function S(I){const T=d.value;if(I.key==="Tab"){I.preventDefault();const w=T.selectionStart,A=T.selectionEnd;T.setRangeText(" ",w,A,"end"),n.value=T.value}else(I.ctrlKey||I.metaKey)&&I.key.toLowerCase()==="s"&&(I.preventDefault(),x(!1))}function V(){f.value&&d.value&&(f.value.scrollTop=d.value.scrollTop)}return kn(C),In(()=>{k=!1}),(I,T)=>(p(),v("div",Yb,[i("div",zb,[i("div",Jb,[T[4]||(T[4]=i("span",{class:"eyebrow"},"Advanced · local override",-1)),i("h1",Gb,"Override YAML"+m(y(c).overrideFriendly?` · ${y(c).overrideFriendly}`:""),1),T[5]||(T[5]=i("p",null,"This small file is yours. It is loaded after the shared screen package and stays in place when ESP Screen Manager updates. Change hardware-specific details here, such as a display model.",-1))]),i("button",{type:"button",class:"btn quiet",id:"close-override",onClick:T[0]||(T[0]=w=>y(Oe)(""))},"← Back")]),i("div",Xb,[i("span",Zb,m(y(ke)("F0493")),1),T[6]||(T[6]=i("div",null,[i("strong",null,"What is safe to change?"),i("span",null,"Display, touchscreen, substitutions, pins and other device details. Name, Wi-Fi, API, OTA and package links remain managed.")],-1))]),i("div",Qb,[i("code",e1,m(s.value||t.value||""),1),i("span",{id:"override-state",class:ie(["chip",{good:o.value}])},m(o.value?"Active":"Ready to attach"),3)]),i("div",t1,[i("div",{ref_key:"gutter",ref:f,class:"yaml-gutter",id:"override-gutter","aria-hidden":"true"},m(h.value),513),we(i("textarea",{ref_key:"editor",ref:d,id:"override-editor","onUpdate:modelValue":T[1]||(T[1]=w=>n.value=w),spellcheck:"false",autocapitalize:"off",autocomplete:"off",autocorrect:"off","aria-label":"Local override YAML",placeholder:`# Example +display: + - id: !extend my_display + model: ST7789V`,onKeydown:S,onScroll:V},null,544),[[Fe,n.value]])]),i("div",n1,[i("button",{type:"button",class:"btn quiet mini",id:"override-example",onClick:E},"Use example"),i("button",{type:"button",class:"btn quiet mini",id:"override-empty",onClick:W},"Clear"),i("span",s1,m(b.value),1)]),i("p",{id:"override-status",class:ie(["status-line",a.value]),role:"status"},m(l.value),3),i("div",o1,[i("button",{type:"button",class:"btn quiet",id:"override-check",disabled:u.value||!t.value,onClick:T[2]||(T[2]=w=>x(!0))},"Save & check",8,i1),i("button",{type:"button",class:"btn primary",id:"override-save",disabled:u.value||!t.value,onClick:T[3]||(T[3]=w=>x(!1))},"Save override",8,l1)])]))}}),u1={class:"main"},c1=Ee({__name:"App",setup(e){const t=$(()=>rt.value==="#settings"?by:rt.value==="#new-screen"?lg:rt.value==="#firmware"?Eg:rt.value==="#alerts"?qb:rt.value==="#override"?r1:lt.value&&c.layout?Gm:Qm);function n(s){(s.metaKey||s.ctrlKey)&&s.key.toLowerCase()==="k"&&(s.preventDefault(),c.palette=!c.palette)}return kn(()=>document.addEventListener("keydown",n)),In(()=>document.removeEventListener("keydown",n)),(s,o)=>(p(),v("div",{class:ie(["app",{dragging:y(c).drag.active}])},[_e(Wd),i("main",u1,[(p(),Xe(ou(t.value)))]),_e(qd),_e(nf)],2))}});kc(c1).mount("#app");gd(); diff --git a/screen_manager/app/static/assets/index-Cp1JAouO.js b/screen_manager/app/static/assets/index-Cp1JAouO.js deleted file mode 100644 index d7377ef..0000000 --- a/screen_manager/app/static/assets/index-Cp1JAouO.js +++ /dev/null @@ -1,28 +0,0 @@ -(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const o of document.querySelectorAll('link[rel="modulepreload"]'))s(o);new MutationObserver(o=>{for(const l of o)if(l.type==="childList")for(const a of l.addedNodes)a.tagName==="LINK"&&a.rel==="modulepreload"&&s(a)}).observe(document,{childList:!0,subtree:!0});function n(o){const l={};return o.integrity&&(l.integrity=o.integrity),o.referrerPolicy&&(l.referrerPolicy=o.referrerPolicy),o.crossOrigin==="use-credentials"?l.credentials="include":o.crossOrigin==="anonymous"?l.credentials="omit":l.credentials="same-origin",l}function s(o){if(o.ep)return;o.ep=!0;const l=n(o);fetch(o.href,l)}})();function Yo(e){const t=Object.create(null);for(const n of e.split(","))t[n]=1;return n=>n in t}const Te={},dn=[],At=()=>{},yl=()=>!1,js=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&(e.charCodeAt(2)>122||e.charCodeAt(2)<97),Bs=e=>e.startsWith("onUpdate:"),Ue=Object.assign,zo=(e,t)=>{const n=e.indexOf(t);n>-1&&e.splice(n,1)},Ga=Object.prototype.hasOwnProperty,be=(e,t)=>Ga.call(e,t),oe=Array.isArray,Gt=e=>cs(e)==="[object Map]",Rt=e=>cs(e)==="[object Set]",bi=e=>cs(e)==="[object Date]",ue=e=>typeof e=="function",Ie=e=>typeof e=="string",Et=e=>typeof e=="symbol",Se=e=>e!==null&&typeof e=="object",gl=e=>(Se(e)||ue(e))&&ue(e.then)&&ue(e.catch),bl=Object.prototype.toString,cs=e=>bl.call(e),Xa=e=>cs(e).slice(8,-1),wl=e=>cs(e)==="[object Object]",Jo=e=>Ie(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e,qn=Yo(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),Vs=e=>{const t=Object.create(null);return(n=>t[n]||(t[n]=e(n)))},Za=/-\w/g,ze=Vs(e=>e.replace(Za,t=>t.slice(1).toUpperCase())),Qa=/\B([A-Z])/g,on=Vs(e=>e.replace(Qa,"-$1").toLowerCase()),Ws=Vs(e=>e.charAt(0).toUpperCase()+e.slice(1)),lo=Vs(e=>e?`on${Ws(e)}`:""),Tt=(e,t)=>!Object.is(e,t),ks=(e,...t)=>{for(let n=0;n{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,writable:s,value:n})},Ks=e=>{const t=parseFloat(e);return isNaN(t)?e:t};let wi;const qs=()=>wi||(wi=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{});function Pe(e){if(oe(e)){const t={};for(let n=0;nn.startsWith("/*")?"":n).split(er).forEach(n=>{if(n){const s=n.split(tr);s.length>1&&(t[s[0].trim()]=s[1].trim())}}),t}function ie(e){let t="";if(Ie(e))t=e;else if(oe(e))for(let n=0;nvt(n,t))}const $l=e=>!!(e&&e.__v_isRef===!0),v=e=>Ie(e)?e:e==null?"":oe(e)||Se(e)&&(e.toString===bl||!ue(e.toString))?$l(e)?v(e.value):JSON.stringify(e,xl,2):String(e),xl=(e,t)=>$l(t)?xl(e,t.value):Gt(t)?{[`Map(${t.size})`]:[...t.entries()].reduce((n,[s,o],l)=>(n[ao(s,l)+" =>"]=o,n),{})}:Rt(t)?{[`Set(${t.size})`]:[...t.values()].map(n=>ao(n))}:Et(t)?ao(t):Se(t)&&!oe(t)&&!wl(t)?String(t):t,ao=(e,t="")=>{var n;return Et(e)?`Symbol(${(n=e.description)!=null?n:t})`:e};let Re;class rr{constructor(t=!1){this.detached=t,this._active=!0,this._on=0,this.effects=[],this.cleanups=[],this._isPaused=!1,this._warnOnRun=!0,this.__v_skip=!0,!t&&Re&&(Re.active?(this.parent=Re,this.index=(Re.scopes||(Re.scopes=[])).push(this)-1):(this._active=!1,this._warnOnRun=!1))}get active(){return this._active}pause(){if(this._active){this._isPaused=!0;let t,n;if(this.scopes){const s=this.scopes.slice();for(t=0,n=s.length;t0&&--this._on===0){if(Re===this)Re=this.prevScope;else{let t=Re;for(;t;){if(t.prevScope===this){t.prevScope=this.prevScope;break}t=t.prevScope}}this.prevScope=void 0}}stop(t){if(this._active){this._active=!1;let n,s;for(n=0,s=this.effects.length;n0)return;if(zn){let t=zn;for(zn=void 0;t;){const n=t.next;t.next=void 0,t.flags&=-9,t=n}}let e;for(;Yn;){let t=Yn;for(Yn=void 0;t;){const n=t.next;if(t.next=void 0,t.flags&=-9,t.flags&1)try{t.trigger()}catch(s){e||(e=s)}t=n}}if(e)throw e}function Al(e){for(let t=e.deps;t;t=t.nextDep)t.version=-1,t.prevActiveLink=t.dep.activeLink,t.dep.activeLink=t}function El(e){let t,n=e.depsTail,s=n;for(;s;){const o=s.prevDep;s.version===-1?(s===n&&(n=o),Qo(s),cr(s)):t=s,s.dep.activeLink=s.prevActiveLink,s.prevActiveLink=void 0,s=o}e.deps=t,e.depsTail=n}function Co(e){for(let t=e.deps;t;t=t.nextDep)if(t.dep.version!==t.version||t.dep.computed&&(Pl(t.dep.computed)||t.dep.version!==t.version))return!0;return!!e._dirty}function Pl(e){if(e.flags&4&&!(e.flags&16)||(e.flags&=-17,e.globalVersion===Zn)||(e.globalVersion=Zn,!e.isSSR&&e.flags&128&&(!e.deps&&!e._dirty||!Co(e))))return;e.flags|=2;const t=e.dep,n=Ae,s=pt;Ae=e,pt=!0;try{Al(e);const o=e.fn(e._value);(t.version===0||Tt(o,e._value))&&(e.flags|=128,e._value=o,t.version++)}catch(o){throw t.version++,o}finally{Ae=n,pt=s,El(e),e.flags&=-3}}function Qo(e,t=!1){const{dep:n,prevSub:s,nextSub:o}=e;if(s&&(s.nextSub=o,e.prevSub=void 0),o&&(o.prevSub=s,e.nextSub=void 0),n.subs===e&&(n.subs=s,!s&&n.computed)){n.computed.flags&=-5;for(let l=n.computed.deps;l;l=l.nextDep)Qo(l,!0)}!t&&!--n.sc&&n.map&&n.map.delete(n.key)}function cr(e){const{prevDep:t,nextDep:n}=e;t&&(t.nextDep=n,e.prevDep=void 0),n&&(n.prevDep=t,e.nextDep=void 0)}let pt=!0;const Ol=[];function Ht(){Ol.push(pt),pt=!1}function Ut(){const e=Ol.pop();pt=e===void 0?!0:e}function $i(e){const{cleanup:t}=e;if(e.cleanup=void 0,t){const n=Ae;Ae=void 0;try{t()}finally{Ae=n}}}let Zn=0;class dr{constructor(t,n){this.sub=t,this.dep=n,this.version=n.version,this.nextDep=this.prevDep=this.nextSub=this.prevSub=this.prevActiveLink=void 0}}class ei{constructor(t){this.computed=t,this.version=0,this.activeLink=void 0,this.subs=void 0,this.map=void 0,this.key=void 0,this.sc=0,this.__v_skip=!0}track(t){if(!Ae||!pt||Ae===this.computed)return;let n=this.activeLink;if(n===void 0||n.sub!==Ae)n=this.activeLink=new dr(Ae,this),Ae.deps?(n.prevDep=Ae.depsTail,Ae.depsTail.nextDep=n,Ae.depsTail=n):Ae.deps=Ae.depsTail=n,Il(n);else if(n.version===-1&&(n.version=this.version,n.nextDep)){const s=n.nextDep;s.prevDep=n.prevDep,n.prevDep&&(n.prevDep.nextDep=s),n.prevDep=Ae.depsTail,n.nextDep=void 0,Ae.depsTail.nextDep=n,Ae.depsTail=n,Ae.deps===n&&(Ae.deps=s)}return n}trigger(t){this.version++,Zn++,this.notify(t)}notify(t){Xo();try{for(let n=this.subs;n;n=n.prevSub)n.sub.notify()&&n.sub.dep.notify()}finally{Zo()}}}function Il(e){if(e.dep.sc++,e.sub.flags&4){const t=e.dep.computed;if(t&&!e.dep.subs){t.flags|=20;for(let s=t.deps;s;s=s.nextDep)Il(s)}const n=e.dep.subs;n!==e&&(e.prevSub=n,n&&(n.nextSub=e)),e.dep.subs=e}}const To=new WeakMap,fn=Symbol(""),Ao=Symbol(""),Qn=Symbol("");function je(e,t,n){if(pt&&Ae){let s=To.get(e);s||To.set(e,s=new Map);let o=s.get(n);o||(s.set(n,o=new ei),o.map=s,o.key=n),o.track()}}function Dt(e,t,n,s,o,l){const a=To.get(e);if(!a){Zn++;return}const r=d=>{d&&d.trigger()};if(Xo(),t==="clear")a.forEach(r);else{const d=oe(e),f=d&&Jo(n);if(d&&n==="length"){const c=Number(s);a.forEach((y,b)=>{(b==="length"||b===Qn||!Et(b)&&b>=c)&&r(y)})}else switch((n!==void 0||a.has(void 0))&&r(a.get(n)),f&&r(a.get(Qn)),t){case"add":d?f&&r(a.get("length")):(r(a.get(fn)),Gt(e)&&r(a.get(Ao)));break;case"delete":d||(r(a.get(fn)),Gt(e)&&r(a.get(Ao)));break;case"set":Gt(e)&&r(a.get(fn));break}}Zo()}function kn(e){const t=ve(e);return t===e||(je(t,"iterate",Qn),ut(e))?t:Pt(e)?Xt(e)?t.map(n=>Qt(ct(n))):t.map(Qt):t.map(ct)}function Ys(e){return je(e=ve(e),"iterate",Qn),e}function St(e,t){return Pt(e)?Qt(Xt(e)?ct(t):t):ct(t)}const fr={__proto__:null,[Symbol.iterator](){return uo(this,Symbol.iterator,e=>St(this,e))},concat(...e){return kn(this).concat(...e.map(t=>oe(t)?kn(t):t))},entries(){return uo(this,"entries",e=>(e[1]=St(this,e[1]),e))},every(e,t){return Ot(this,"every",e,t,void 0,arguments)},filter(e,t){return Ot(this,"filter",e,t,n=>n.map(s=>St(this,s)),arguments)},find(e,t){return Ot(this,"find",e,t,n=>St(this,n),arguments)},findIndex(e,t){return Ot(this,"findIndex",e,t,void 0,arguments)},findLast(e,t){return Ot(this,"findLast",e,t,n=>St(this,n),arguments)},findLastIndex(e,t){return Ot(this,"findLastIndex",e,t,void 0,arguments)},forEach(e,t){return Ot(this,"forEach",e,t,void 0,arguments)},includes(...e){return co(this,"includes",e)},indexOf(...e){return co(this,"indexOf",e)},join(e){return kn(this).join(e)},lastIndexOf(...e){return co(this,"lastIndexOf",e)},map(e,t){return Ot(this,"map",e,t,void 0,arguments)},pop(){return Fn(this,"pop")},push(...e){return Fn(this,"push",e)},reduce(e,...t){return xi(this,"reduce",e,t)},reduceRight(e,...t){return xi(this,"reduceRight",e,t)},shift(){return Fn(this,"shift")},some(e,t){return Ot(this,"some",e,t,void 0,arguments)},splice(...e){return Fn(this,"splice",e)},toReversed(){return kn(this).toReversed()},toSorted(e){return kn(this).toSorted(e)},toSpliced(...e){return kn(this).toSpliced(...e)},unshift(...e){return Fn(this,"unshift",e)},values(){return uo(this,"values",e=>St(this,e))}};function uo(e,t,n){const s=Ys(e),o=s[t]();return s!==e&&!ut(e)&&(o._next=o.next,o.next=()=>{const l=o._next();return l.done||(l.value=n(l.value)),l}),o}const pr=Array.prototype;function Ot(e,t,n,s,o,l){const a=Ys(e),r=a!==e&&!ut(e),d=a[t];if(d!==pr[t]){const y=d.apply(e,l);return r?ct(y):y}let f=n;a!==e&&(r?f=function(y,b){return n.call(this,St(e,y),b,e)}:n.length>2&&(f=function(y,b){return n.call(this,y,b,e)}));const c=d.call(a,f,s);return r&&o?o(c):c}function xi(e,t,n,s){const o=Ys(e),l=o!==e&&!ut(e);let a=n,r=!1;o!==e&&(l?(r=s.length===0,a=function(f,c,y){return r&&(r=!1,f=St(e,f)),n.call(this,f,St(e,c),y,e)}):n.length>3&&(a=function(f,c,y){return n.call(this,f,c,y,e)}));const d=o[t](a,...s);return r?St(e,d):d}function co(e,t,n){const s=ve(e);je(s,"iterate",Qn);const o=s[t](...n);return(o===-1||o===!1)&&si(n[0])?(n[0]=ve(n[0]),s[t](...n)):o}function Fn(e,t,n=[]){Ht(),Xo();const s=ve(e)[t].apply(e,n);return Zo(),Ut(),s}const hr=Yo("__proto__,__v_isRef,__isVue"),Ml=new Set(Object.getOwnPropertyNames(Symbol).filter(e=>e!=="arguments"&&e!=="caller").map(e=>Symbol[e]).filter(Et));function vr(e){Et(e)||(e=String(e));const t=ve(this);return je(t,"has",e),t.hasOwnProperty(e)}class Ll{constructor(t=!1,n=!1){this._isReadonly=t,this._isShallow=n}get(t,n,s){if(n==="__v_skip")return t.__v_skip;const o=this._isReadonly,l=this._isShallow;if(n==="__v_isReactive")return!o;if(n==="__v_isReadonly")return o;if(n==="__v_isShallow")return l;if(n==="__v_raw")return s===(o?l?Sr:Rl:l?Nl:Fl).get(t)||Object.getPrototypeOf(t)===Object.getPrototypeOf(s)?t:void 0;const a=oe(t);if(!o){let d;if(a&&(d=fr[n]))return d;if(n==="hasOwnProperty")return vr}const r=Reflect.get(t,n,We(t)?t:s);if((Et(n)?Ml.has(n):hr(n))||(o||je(t,"get",n),l))return r;if(We(r)){const d=a&&Jo(n)?r:r.value;return o&&Se(d)?Po(d):d}return Se(r)?o?Po(r):bn(r):r}}class Dl extends Ll{constructor(t=!1){super(!1,t)}set(t,n,s,o){let l=t[n];const a=oe(t)&&Jo(n);if(!this._isShallow){const f=Pt(l);if(!ut(s)&&!Pt(s)&&(l=ve(l),s=ve(s)),!a&&We(l)&&!We(s))return f||(l.value=s),!0}const r=a?Number(n)e,ys=e=>Reflect.getPrototypeOf(e);function wr(e,t,n){return function(...s){const o=this.__v_raw,l=ve(o),a=Gt(l),r=e==="entries"||e===Symbol.iterator&&a,d=e==="keys"&&a,f=o[e](...s),c=n?Eo:t?Qt:ct;return!t&&je(l,"iterate",d?Ao:fn),Ue(Object.create(f),{next(){const{value:y,done:b}=f.next();return b?{value:y,done:b}:{value:r?[c(y[0]),c(y[1])]:c(y),done:b}}})}}function gs(e){return function(...t){return e==="delete"?!1:e==="clear"?void 0:this}}function _r(e,t){const n={get(o){const l=this.__v_raw,a=ve(l),r=ve(o);e||(Tt(o,r)&&je(a,"get",o),je(a,"get",r));const{has:d}=ys(a),f=t?Eo:e?Qt:ct;if(d.call(a,o))return f(l.get(o));if(d.call(a,r))return f(l.get(r));l!==a&&l.get(o)},get size(){const o=this.__v_raw;return!e&&je(ve(o),"iterate",fn),o.size},has(o){const l=this.__v_raw,a=ve(l),r=ve(o);return e||(Tt(o,r)&&je(a,"has",o),je(a,"has",r)),o===r?l.has(o):l.has(o)||l.has(r)},forEach(o,l){const a=this,r=a.__v_raw,d=ve(r),f=t?Eo:e?Qt:ct;return!e&&je(d,"iterate",fn),r.forEach((c,y)=>o.call(l,f(c),f(y),a))}};return Ue(n,e?{add:gs("add"),set:gs("set"),delete:gs("delete"),clear:gs("clear")}:{add(o){const l=ve(this),a=ys(l),r=ve(o),d=!t&&!ut(o)&&!Pt(o)?r:o;return a.has.call(l,d)||Tt(o,d)&&a.has.call(l,o)||Tt(r,d)&&a.has.call(l,r)||(l.add(d),Dt(l,"add",d,d)),this},set(o,l){!t&&!ut(l)&&!Pt(l)&&(l=ve(l));const a=ve(this),{has:r,get:d}=ys(a);let f=r.call(a,o);f||(o=ve(o),f=r.call(a,o));const c=d.call(a,o);return a.set(o,l),f?Tt(l,c)&&Dt(a,"set",o,l):Dt(a,"add",o,l),this},delete(o){const l=ve(this),{has:a,get:r}=ys(l);let d=a.call(l,o);d||(o=ve(o),d=a.call(l,o)),r&&r.call(l,o);const f=l.delete(o);return d&&Dt(l,"delete",o,void 0),f},clear(){const o=ve(this),l=o.size!==0,a=o.clear();return l&&Dt(o,"clear",void 0,void 0),a}}),["keys","values","entries",Symbol.iterator].forEach(o=>{n[o]=wr(o,e,t)}),n}function ti(e,t){const n=_r(e,t);return(s,o,l)=>o==="__v_isReactive"?!e:o==="__v_isReadonly"?e:o==="__v_raw"?s:Reflect.get(be(n,o)&&o in s?n:s,o,l)}const kr={get:ti(!1,!1)},$r={get:ti(!1,!0)},xr={get:ti(!0,!1)};const Fl=new WeakMap,Nl=new WeakMap,Rl=new WeakMap,Sr=new WeakMap;function Cr(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}function bn(e){return Pt(e)?e:ni(e,!1,yr,kr,Fl)}function Tr(e){return ni(e,!1,br,$r,Nl)}function Po(e){return ni(e,!0,gr,xr,Rl)}function ni(e,t,n,s,o){if(!Se(e)||e.__v_raw&&!(t&&e.__v_isReactive)||e.__v_skip||!Object.isExtensible(e))return e;const l=o.get(e);if(l)return l;const a=Cr(Xa(e));if(a===0)return e;const r=new Proxy(e,a===2?s:n);return o.set(e,r),r}function Xt(e){return Pt(e)?Xt(e.__v_raw):!!(e&&e.__v_isReactive)}function Pt(e){return!!(e&&e.__v_isReadonly)}function ut(e){return!!(e&&e.__v_isShallow)}function si(e){return e?!!e.__v_raw:!1}function ve(e){const t=e&&e.__v_raw;return t?ve(t):e}function Ar(e){return!be(e,"__v_skip")&&Object.isExtensible(e)&&_l(e,"__v_skip",!0),e}const ct=e=>Se(e)?bn(e):e,Qt=e=>Se(e)?Po(e):e;function We(e){return e?e.__v_isRef===!0:!1}function pe(e){return Er(e,!1)}function Er(e,t){return We(e)?e:new Pr(e,t)}class Pr{constructor(t,n){this.dep=new ei,this.__v_isRef=!0,this.__v_isShallow=!1,this._rawValue=n?t:ve(t),this._value=n?t:ct(t),this.__v_isShallow=n}get value(){return this.dep.track(),this._value}set value(t){const n=this._rawValue,s=this.__v_isShallow||ut(t)||Pt(t);t=s?t:ve(t),Tt(t,n)&&(this._rawValue=t,this._value=s?t:ct(t),this.dep.trigger())}}function m(e){return We(e)?e.value:e}const Or={get:(e,t,n)=>t==="__v_raw"?e:m(Reflect.get(e,t,n)),set:(e,t,n,s)=>{const o=e[t];return We(o)&&!We(n)?(o.value=n,!0):Reflect.set(e,t,n,s)}};function Hl(e){return Xt(e)?e:new Proxy(e,Or)}class Ir{constructor(t,n,s){this.fn=t,this.setter=n,this._value=void 0,this.dep=new ei(this),this.__v_isRef=!0,this.deps=void 0,this.depsTail=void 0,this.flags=16,this.globalVersion=Zn-1,this.next=void 0,this.effect=this,this.__v_isReadonly=!n,this.isSSR=s}notify(){if(this.flags|=16,!(this.flags&8)&&Ae!==this)return Tl(this,!0),!0}get value(){const t=this.dep.track();return Pl(this),t&&(t.version=this.dep.version),this._value}set value(t){this.setter&&this.setter(t)}}function Mr(e,t,n=!1){let s,o;return ue(e)?s=e:(s=e.get,o=e.set),new Ir(s,o,n)}const bs={},As=new WeakMap;let un;function Lr(e,t=!1,n=un){if(n){let s=As.get(n);s||As.set(n,s=[]),s.push(e)}}function Dr(e,t,n=Te){const{immediate:s,deep:o,once:l,scheduler:a,augmentJob:r,call:d}=n,f=C=>o?C:ut(C)||o===!1||o===0?Ft(C,1):Ft(C);let c,y,b,k,I=!1,E=!1;if(We(e)?(y=()=>e.value,I=ut(e)):Xt(e)?(y=()=>f(e),I=!0):oe(e)?(E=!0,I=e.some(C=>Xt(C)||ut(C)),y=()=>e.map(C=>{if(We(C))return C.value;if(Xt(C))return f(C);if(ue(C))return d?d(C,2):C()})):ue(e)?t?y=d?()=>d(e,2):e:y=()=>{if(b){Ht();try{b()}finally{Ut()}}const C=un;un=c;try{return d?d(e,3,[k]):e(k)}finally{un=C}}:y=At,t&&o){const C=y,V=o===!0?1/0:o;y=()=>Ft(C(),V)}const x=ur(),S=()=>{c.stop(),x&&x.active&&zo(x.effects,c)};if(l&&t){const C=t;t=(...V)=>{const O=C(...V);return S(),O}}let M=E?new Array(e.length).fill(bs):bs;const G=C=>{if(!(!(c.flags&1)||!c.dirty&&!C))if(t){const V=c.run();if(C||o||I||(E?V.some((O,A)=>Tt(O,M[A])):Tt(V,M))){b&&b();const O=un;un=c;try{const A=[V,M===bs?void 0:E&&M[0]===bs?[]:M,k];M=V,d?d(t,3,A):t(...A)}finally{un=O}}}else c.run()};return r&&r(G),c=new Sl(y),c.scheduler=a?()=>a(G,!1):G,k=C=>Lr(C,!1,c),b=c.onStop=()=>{const C=As.get(c);if(C){if(d)d(C,4);else for(const V of C)V();As.delete(c)}},t?s?G(!0):M=c.run():a?a(G.bind(null,!0),!0):c.run(),S.pause=c.pause.bind(c),S.resume=c.resume.bind(c),S.stop=S,S}function Ft(e,t=1/0,n){if(t<=0||!Se(e)||e.__v_skip||(n=n||new Map,(n.get(e)||0)>=t))return e;if(n.set(e,t),t--,We(e))Ft(e.value,t,n);else if(oe(e))for(let s=0;s{Ft(s,t,n)});else if(wl(e)){for(const s in e)Ft(e[s],t,n);for(const s of Object.getOwnPropertySymbols(e))Object.prototype.propertyIsEnumerable.call(e,s)&&Ft(e[s],t,n)}return e}function ds(e,t,n,s){try{return s?e(...s):e()}catch(o){zs(o,t,n)}}function mt(e,t,n,s){if(ue(e)){const o=ds(e,t,n,s);return o&&gl(o)&&o.catch(l=>{zs(l,t,n)}),o}if(oe(e)){const o=[];for(let l=0;l>>1,o=Ye[s],l=es(o);l=es(n)?Ye.push(e):Ye.splice(Nr(t),0,e),e.flags|=1,jl()}}function jl(){Es||(Es=Ul.then(Vl))}function Rr(e){if(!oe(e))Yt&&e.id===-1?Yt.splice($n+1,0,e):e.flags&1||(Sn.push(e),e.flags|=1);else for(let t=0;tes(n)-es(s));if(Sn.length=0,Yt){for(let n=0;ne.id==null?e.flags&2?-1:1/0:e.id;function Vl(e){try{for($t=0;$t{s._d&&Ni(-1);const l=Ps(t),a=hn.length;let r;try{r=e(...o)}finally{for(let d=hn.length;d>a;d--)va();Ps(l),s._d&&Ni(1)}return r};return s._n=!0,s._c=!0,s._d=!0,s}function we(e,t){if(nt===null)return e;const n=eo(nt),s=e.dirs||(e.dirs=[]);for(let o=0;o1)return n&&ue(t)?t.call(s&&s.proxy):t}}const jr=Symbol.for("v-scx"),Br=()=>$s(jr);function pn(e,t,n){return Kl(e,t,n)}function Kl(e,t,n=Te){const{immediate:s,deep:o,flush:l,once:a}=n,r=Ue({},n),d=t&&s||!t&&l!=="post";let f;if(ss){if(l==="sync"){const k=Br();f=k.__watcherHandles||(k.__watcherHandles=[])}else if(!d){const k=()=>{};return k.stop=At,k.resume=At,k.pause=At,k}}const c=Be;r.call=(k,I,E)=>mt(k,c,I,E);let y=!1;l==="post"?r.scheduler=k=>{Ge(k,c&&c.suspense)}:l!=="sync"&&(y=!0,r.scheduler=(k,I)=>{I?k():oi(k)}),r.augmentJob=k=>{t&&(k.flags|=4),y&&(k.flags|=2,c&&(k.id=c.uid,k.i=c))};const b=Dr(e,t,r);return ss&&(f?f.push(b):d&&b()),b}function Vr(e,t,n){const s=this.proxy,o=Ie(e)?e.includes(".")?ql(s,e):()=>s[e]:e.bind(s,s);let l;ue(t)?l=t:(l=t.handler,n=t);const a=fs(this),r=Kl(o,l.bind(s),n);return a(),r}function ql(e,t){const n=t.split(".");return()=>{let s=e;for(let o=0;oe.__isTeleport,fo=Symbol("_leaveCb");function Kr(e){let t=e[0];if(e.length>1){for(const n of e)if(n.type!==jt){t=n;break}}return t}function Yl(e){if(!li(e))return Gs(e.type)&&e.children?Kr(e.children):e;if(e.component)return e.component.subTree;const{shapeFlag:t,children:n}=e;if(n){if(t&16)return n[0];if(t&32&&ue(n.default))return n.default()}}function ii(e,t){if(e.shapeFlag&6&&e.component){e.transition=t;const n=e.component.subTree;ii(Gs(n.type)&&Yl(n)||n,t)}else e.shapeFlag&128?(e.ssContent.transition=t.clone(e.ssContent),e.ssFallback.transition=t.clone(e.ssFallback)):e.transition=t}function Ee(e,t){return ue(e)?Ue({name:e.name},t,{setup:e}):e}function zl(e){e.ids=[e.ids[0]+e.ids[2]+++"-",0,0]}function Ci(e,t){let n;return!!((n=Object.getOwnPropertyDescriptor(e,t))&&!n.configurable)}const Os=new WeakMap;function Jn(e,t,n,s,o=!1){if(oe(e)){e.forEach((E,x)=>Jn(E,t&&(oe(t)?t[x]:t),n,s,o));return}if(Gn(s)&&!o){s.shapeFlag&512&&s.type.__asyncResolved&&s.component.subTree.component&&Jn(e,t,n,s.component.subTree);return}const l=s.shapeFlag&4?eo(s.component):s.el,a=o?null:l,{i:r,r:d}=e,f=t&&t.r,c=r.refs===Te?r.refs={}:r.refs,y=r.setupState,b=ve(y),k=y===Te?yl:E=>Ci(c,E)?!1:be(b,E),I=(E,x)=>!(x&&Ci(c,x));if(f!=null&&f!==d){if(Ti(t),Ie(f))c[f]=null,k(f)&&(y[f]=null);else if(We(f)){const E=t;I(f,E.k)&&(f.value=null),E.k&&(c[E.k]=null)}}if(ue(d))ds(d,r,12,[a,c]);else{const E=Ie(d),x=We(d);if(E||x){const S=()=>{if(e.f){const M=E?k(d)?y[d]:c[d]:I()||!e.k?d.value:c[e.k];if(o)oe(M)&&zo(M,l);else if(oe(M))M.includes(l)||M.push(l);else if(E)c[d]=[l],k(d)&&(y[d]=c[d]);else{const G=[l];I(d,e.k)&&(d.value=G),e.k&&(c[e.k]=G)}}else E?(c[d]=a,k(d)&&(y[d]=a)):x&&(I(d,e.k)&&(d.value=a),e.k&&(c[e.k]=a))};if(a){const M=()=>{S(),Os.delete(e)};M.id=-1,Os.set(e,M),Ge(M,n)}else Ti(e),S()}}}function Ti(e){const t=Os.get(e);t&&(t.flags|=8,Os.delete(e))}qs().requestIdleCallback;qs().cancelIdleCallback;const Gn=e=>!!e.type.__asyncLoader,li=e=>e.type.__isKeepAlive;function qr(e,t){Jl(e,"a",t)}function Yr(e,t){Jl(e,"da",t)}function Jl(e,t,n=Be){const s=e.__wdc||(e.__wdc=()=>{let o=n;for(;o;){if(o.isDeactivated)return;o=o.parent}return e()});if(Xs(t,s,n),n){let o=n.parent;for(;o&&o.parent;)li(o.parent.vnode)&&zr(s,t,n,o),o=o.parent}}function zr(e,t,n,s){const o=Xs(t,e,s,!0);Gl(()=>{zo(s[t],o)},n)}function Xs(e,t,n=Be,s=!1){if(n){const o=n[e]||(n[e]=[]),l=t.__weh||(t.__weh=(...a)=>{Ht();const r=fs(n),d=mt(t,n,e,a);return r(),Ut(),d});return s?o.unshift(l):o.push(l),l}}const Wt=e=>(t,n=Be)=>{(!ss||e==="sp")&&Xs(e,(...s)=>t(...s),n)},Jr=Wt("bm"),_n=Wt("m"),Gr=Wt("bu"),Xr=Wt("u"),In=Wt("bum"),Gl=Wt("um"),Zr=Wt("sp"),Qr=Wt("rtg"),eu=Wt("rtc");function tu(e,t=Be){Xs("ec",e,t)}const nu="components",Xl=Symbol.for("v-ndc");function su(e){return Ie(e)?ou(nu,e,!1)||e:e||Xl}function ou(e,t,n=!0,s=!1){const o=nt||Be;if(o){const l=o.type;{const r=Bu(l,!1);if(r&&(r===t||r===ze(t)||r===Ws(ze(t))))return l}const a=Ai(o[e]||l[e],t)||Ai(o.appContext[e],t);return!a&&s?l:a}}function Ai(e,t){return e&&(e[t]||e[ze(t)]||e[Ws(ze(t))])}function le(e,t,n,s){let o;const l=n,a=oe(e);if(a||Ie(e)){const r=a&&Xt(e);let d=!1,f=!1;r&&(d=!ut(e),f=Pt(e),e=Ys(e)),o=new Array(e.length);for(let c=0,y=e.length;ct(r,d,void 0,l));else{const r=Object.keys(e);o=new Array(r.length);for(let d=0,f=r.length;de?ba(e)?eo(e):Oo(e.parent):null,Xn=Ue(Object.create(null),{$:e=>e,$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs,$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>Oo(e.parent),$root:e=>Oo(e.root),$host:e=>e.ce,$emit:e=>e.emit,$options:e=>Ql(e),$forceUpdate:e=>e.f||(e.f=()=>{oi(e.update)}),$nextTick:e=>e.n||(e.n=Js.bind(e.proxy)),$watch:e=>Vr.bind(e)}),po=(e,t)=>e!==Te&&!e.__isScriptSetup&&be(e,t),iu={get({_:e},t){if(t==="__v_skip")return!0;const{ctx:n,setupState:s,data:o,props:l,accessCache:a,type:r,appContext:d}=e;if(t[0]!=="$"){const b=a[t];if(b!==void 0)switch(b){case 1:return s[t];case 2:return o[t];case 4:return n[t];case 3:return l[t]}else{if(po(s,t))return a[t]=1,s[t];if(o!==Te&&be(o,t))return a[t]=2,o[t];if(be(l,t))return a[t]=3,l[t];if(n!==Te&&be(n,t))return a[t]=4,n[t];Io&&(a[t]=0)}}const f=Xn[t];let c,y;if(f)return t==="$attrs"&&je(e.attrs,"get",""),f(e);if((c=r.__cssModules)&&(c=c[t]))return c;if(n!==Te&&be(n,t))return a[t]=4,n[t];if(y=d.config.globalProperties,be(y,t))return y[t]},set({_:e},t,n){const{data:s,setupState:o,ctx:l}=e;return po(o,t)?(o[t]=n,!0):s!==Te&&be(s,t)?(s[t]=n,!0):be(e.props,t)||t[0]==="$"&&t.slice(1)in e?!1:(l[t]=n,!0)},has({_:{data:e,setupState:t,accessCache:n,ctx:s,appContext:o,props:l,type:a}},r){let d;return!!(n[r]||e!==Te&&r[0]!=="$"&&be(e,r)||po(t,r)||be(l,r)||be(s,r)||be(Xn,r)||be(o.config.globalProperties,r)||(d=a.__cssModules)&&d[r])},defineProperty(e,t,n){return n.get!=null?e._.accessCache[t]=0:be(n,"value")&&this.set(e,t,n.value,null),Reflect.defineProperty(e,t,n)}};function Ei(e){return oe(e)?e.reduce((t,n)=>(t[n]=null,t),{}):e}let Io=!0;function lu(e){const t=Ql(e),n=e.proxy,s=e.ctx;Io=!1,t.beforeCreate&&Pi(t.beforeCreate,e,"bc");const{data:o,computed:l,methods:a,watch:r,provide:d,inject:f,created:c,beforeMount:y,mounted:b,beforeUpdate:k,updated:I,activated:E,deactivated:x,beforeDestroy:S,beforeUnmount:M,destroyed:G,unmounted:C,render:V,renderTracked:O,renderTriggered:A,errorCaptured:w,serverPrefetch:T,expose:te,inheritAttrs:$e,components:Me,directives:X,filters:j}=t;if(f&&au(f,s,null),a)for(const de in a){const he=a[de];ue(he)&&(s[de]=he.bind(n))}if(o){const de=o.call(n,n);Se(de)&&(e.data=bn(de))}if(Io=!0,l)for(const de in l){const he=l[de],Q=ue(he)?he.bind(n,n):ue(he.get)?he.get.bind(n,n):At,K=!ue(he)&&ue(he.set)?he.set.bind(n):At,B=$({get:Q,set:K});Object.defineProperty(s,de,{enumerable:!0,configurable:!0,get:()=>B.value,set:ne=>B.value=ne})}if(r)for(const de in r)Zl(r[de],s,n,de);if(d){const de=ue(d)?d.call(n):d;Reflect.ownKeys(de).forEach(he=>{Ur(he,de[he])})}c&&Pi(c,e,"c");function ce(de,he){oe(he)?he.forEach(Q=>de(Q.bind(n))):he&&de(he.bind(n))}if(ce(Jr,y),ce(_n,b),ce(Gr,k),ce(Xr,I),ce(qr,E),ce(Yr,x),ce(tu,w),ce(eu,O),ce(Qr,A),ce(In,M),ce(Gl,C),ce(Zr,T),oe(te))if(te.length){const de=e.exposed||(e.exposed={});te.forEach(he=>{Object.defineProperty(de,he,{get:()=>n[he],set:Q=>n[he]=Q,enumerable:!0})})}else e.exposed||(e.exposed={});V&&e.render===At&&(e.render=V),$e!=null&&(e.inheritAttrs=$e),Me&&(e.components=Me),X&&(e.directives=X),T&&zl(e)}function au(e,t,n=At){oe(e)&&(e=Mo(e));for(const s in e){const o=e[s];let l;Se(o)?"default"in o?l=$s(o.from||s,o.default,!0):l=$s(o.from||s):l=$s(o),We(l)?Object.defineProperty(t,s,{enumerable:!0,configurable:!0,get:()=>l.value,set:a=>l.value=a}):t[s]=l}}function Pi(e,t,n){mt(oe(e)?e.map(s=>s.bind(t.proxy)):e.bind(t.proxy),t,n)}function Zl(e,t,n,s){let o=s.includes(".")?ql(n,s):()=>n[s];if(Ie(e)){const l=t[e];ue(l)&&pn(o,l)}else if(ue(e))pn(o,e.bind(n));else if(Se(e))if(oe(e))e.forEach(l=>Zl(l,t,n,s));else{const l=ue(e.handler)?e.handler.bind(n):t[e.handler];ue(l)&&pn(o,l,e)}}function Ql(e){const t=e.type,{mixins:n,extends:s}=t,{mixins:o,optionsCache:l,config:{optionMergeStrategies:a}}=e.appContext,r=l.get(t);let d;return r?d=r:!o.length&&!n&&!s?d=t:(d={},o.length&&o.forEach(f=>Is(d,f,a,!0)),Is(d,t,a)),Se(t)&&l.set(t,d),d}function Is(e,t,n,s=!1){const{mixins:o,extends:l}=t;l&&Is(e,l,n,!0),o&&o.forEach(a=>Is(e,a,n,!0));for(const a in t)if(!(s&&a==="expose")){const r=ru[a]||n&&n[a];e[a]=r?r(e[a],t[a]):t[a]}return e}const ru={data:Oi,props:Ii,emits:Ii,methods:Un,computed:Un,beforeCreate:Ke,created:Ke,beforeMount:Ke,mounted:Ke,beforeUpdate:Ke,updated:Ke,beforeDestroy:Ke,beforeUnmount:Ke,destroyed:Ke,unmounted:Ke,activated:Ke,deactivated:Ke,errorCaptured:Ke,serverPrefetch:Ke,components:Un,directives:Un,watch:cu,provide:Oi,inject:uu};function Oi(e,t){return t?e?function(){return Ue(ue(e)?e.call(this,this):e,ue(t)?t.call(this,this):t)}:t:e}function uu(e,t){return Un(Mo(e),Mo(t))}function Mo(e){if(oe(e)){const t={};for(let n=0;nt==="modelValue"||t==="model-value"?e.modelModifiers:e[`${t}Modifiers`]||e[`${ze(t)}Modifiers`]||e[`${on(t)}Modifiers`];function hu(e,t,...n){if(e.isUnmounted)return;const s=e.vnode.props||Te;let o=n;const l=t.startsWith("update:"),a=l&&pu(s,t.slice(7));a&&(a.trim&&(o=n.map(c=>Ie(c)?c.trim():c)),a.number&&(o=o.map(Ks)));let r,d=s[r=lo(t)]||s[r=lo(ze(t))];!d&&l&&(d=s[r=lo(on(t))]),d&&mt(d,e,6,o);const f=s[r+"Once"];if(f){if(!e.emitted)e.emitted={};else if(e.emitted[r])return;e.emitted[r]=!0,mt(f,e,6,o)}}const vu=new WeakMap;function ta(e,t,n=!1){const s=n?vu:t.emitsCache,o=s.get(e);if(o!==void 0)return o;const l=e.emits;let a={},r=!1;if(!ue(e)){const d=f=>{const c=ta(f,t,!0);c&&(r=!0,Ue(a,c))};!n&&t.mixins.length&&t.mixins.forEach(d),e.extends&&d(e.extends),e.mixins&&e.mixins.forEach(d)}return!l&&!r?(Se(e)&&s.set(e,null),null):(oe(l)?l.forEach(d=>a[d]=null):Ue(a,l),Se(e)&&s.set(e,a),a)}function Zs(e,t){return!e||!js(t)?!1:(t=t.slice(2),t=t==="Once"?t:t.replace(/Once$/,""),be(e,t[0].toLowerCase()+t.slice(1))||be(e,on(t))||be(e,t))}function Mi(e){const{type:t,vnode:n,proxy:s,withProxy:o,propsOptions:[l],slots:a,attrs:r,emit:d,render:f,renderCache:c,props:y,data:b,setupState:k,ctx:I,inheritAttrs:E}=e,x=Ps(e);let S,M;try{if(n.shapeFlag&4){const C=o||s,V=C;S=Ct(f.call(V,C,c,y,k,b,I)),M=r}else{const C=t;S=Ct(C.length>1?C(y,{attrs:r,slots:a,emit:d}):C(y,null)),M=t.props?r:mu(r)}}catch(C){hn.length=0,zs(C,e,1),S=_e(jt)}let G=S;if(M&&E!==!1){const C=Object.keys(M),{shapeFlag:V}=G;C.length&&V&7&&(l&&C.some(Bs)&&(M=yu(M,l)),G=En(G,M,!1,!0))}if(n.dirs&&(G=En(G,null,!1,!0),G.dirs=G.dirs?G.dirs.concat(n.dirs):n.dirs),n.transition){const C=Gs(G.type)&&Yl(G)||G;ii(C,n.transition)}return S=G,Ps(x),S}const mu=e=>{let t;for(const n in e)(n==="class"||n==="style"||js(n))&&((t||(t={}))[n]=e[n]);return t},yu=(e,t)=>{const n={};for(const s in e)(!Bs(s)||!(s.slice(9)in t))&&(n[s]=e[s]);return n};function gu(e,t,n){const{props:s,children:o,component:l}=e,{props:a,children:r,patchFlag:d}=t,f=l.emitsOptions;if(t.dirs||t.transition)return!0;if(n&&d>=0){if(d&1024)return!0;if(d&16)return s?Li(s,a,f):!!a;if(d&8){const c=t.dynamicProps;for(let y=0;yObject.create(sa),ia=e=>Object.getPrototypeOf(e)===sa;function wu(e,t,n,s=!1){const o={},l=oa();e.propsDefaults=Object.create(null),la(e,t,o,l);for(const a in e.propsOptions[0])a in o||(o[a]=void 0);n?e.props=s?o:Tr(o):e.type.props?e.props=o:e.props=l,e.attrs=l}function _u(e,t,n,s){const{props:o,attrs:l,vnode:{patchFlag:a}}=e,r=ve(o),[d]=e.propsOptions;let f=!1;if((s||a>0)&&!(a&16)){if(a&8){const c=e.vnode.dynamicProps;for(let y=0;y{d=!0;const[b,k]=aa(y,t,!0);Ue(a,b),k&&r.push(...k)};!n&&t.mixins.length&&t.mixins.forEach(c),e.extends&&c(e.extends),e.mixins&&e.mixins.forEach(c)}if(!l&&!d)return Se(e)&&s.set(e,dn),dn;if(oe(l))for(let c=0;ce==="_"||e==="_ctx"||e==="$stable",ri=e=>oe(e)?e.map(Ct):[Ct(e)],$u=(e,t,n)=>{if(t._n)return t;const s=Hr((...o)=>ri(t(...o)),n);return s._c=!1,s},ra=(e,t,n)=>{const s=e._ctx;for(const o in e){if(ai(o))continue;const l=e[o];if(ue(l))t[o]=$u(o,l,s);else if(l!=null){const a=ri(l);t[o]=()=>a}}},ua=(e,t)=>{const n=ri(t);e.slots.default=()=>n},ca=(e,t,n)=>{for(const s in t)(n||!ai(s))&&(e[s]=t[s])},xu=(e,t,n)=>{const s=e.slots=oa();if(e.vnode.shapeFlag&32){const o=t._;o?(ca(s,t,n),n&&_l(s,"_",o,!0)):ra(t,s)}else t&&ua(e,t)},Su=(e,t,n)=>{const{vnode:s,slots:o}=e;let l=!0,a=Te;if(s.shapeFlag&32){const r=t._;r?n&&r===1?l=!1:ca(o,t,n):(l=!t.$stable,ra(t,o)),a=t}else t&&(ua(e,t),a={default:1});if(l)for(const r in o)!ai(r)&&a[r]==null&&delete o[r]},Ge=Pu;function Cu(e){return Tu(e)}function Tu(e,t){const n=qs();n.__VUE__=!0;const{insert:s,remove:o,patchProp:l,createElement:a,createText:r,createComment:d,setText:f,setElementText:c,parentNode:y,nextSibling:b,setScopeId:k=At,insertStaticContent:I}=e,E=(g,_,P,H=null,L=null,F=null,Y=void 0,q=null,W=!!_.dynamicChildren)=>{if(g===_)return;g&&!Nn(g,_)&&(H=fe(g),ne(g,L,F,!0),g=null),_.patchFlag===-2&&(W=!1,_.dynamicChildren=null),_.dynamicChildren&&g&&g.dynamicChildren&&g.dynamicChildren.hasOnce&&(_.dynamicChildren===dn&&(_.dynamicChildren=[]),_.dynamicChildren.hasOnce=!0);const{type:D,ref:se,shapeFlag:J}=_;switch(D){case Qs:x(g,_,P,H);break;case jt:S(g,_,P,H);break;case xs:g==null&&M(_,P,H,Y);break;case U:Me(g,_,P,H,L,F,Y,q,W);break;default:J&1?V(g,_,P,H,L,F,Y,q,W):J&6?X(g,_,P,H,L,F,Y,q,W):(J&64||J&128)&&D.process(g,_,P,H,L,F,Y,q,W,ln)}se!=null&&L?Jn(se,g&&g.ref,F,_||g,!_):se==null&&g&&g.ref!=null&&Jn(g.ref,null,F,g,!0)},x=(g,_,P,H)=>{if(g==null)s(_.el=r(_.children),P,H);else{const L=_.el=g.el;_.children!==g.children&&f(L,_.children)}},S=(g,_,P,H)=>{g==null?s(_.el=d(_.children||""),P,H):_.el=g.el},M=(g,_,P,H)=>{[g.el,g.anchor]=I(g.children,_,P,H,g.el,g.anchor)},G=({el:g,anchor:_},P,H)=>{let L;for(;g&&g!==_;)L=b(g),s(g,P,H),g=L;s(_,P,H)},C=({el:g,anchor:_})=>{let P;for(;g&&g!==_;)P=b(g),o(g),g=P;o(_)},V=(g,_,P,H,L,F,Y,q,W)=>{if(_.type==="svg"?Y="svg":_.type==="math"&&(Y="mathml"),g==null)O(_,P,H,L,F,Y,q,W);else{const D=g.el&&g.el._isVueCE?g.el:null;try{D&&D._beginPatch(),T(g,_,L,F,Y,q,W)}finally{D&&D._endPatch()}}},O=(g,_,P,H,L,F,Y,q)=>{let W,D;const{props:se,shapeFlag:J,transition:ee,dirs:ae}=g;if(W=g.el=a(g.type,F,se&&se.is,se),J&8?c(W,g.children):J&16&&w(g.children,W,null,H,L,ho(g,F),Y,q),ae&&an(g,null,H,"created"),A(W,g,g.scopeId,Y,H),se){for(const Ce in se)Ce!=="value"&&!qn(Ce)&&l(W,Ce,null,se[Ce],F,H);"value"in se&&l(W,"value",null,se.value,F),(D=se.onVnodeBeforeMount)&&kt(D,H,g)}ae&&an(g,null,H,"beforeMount");const ye=Au(L,ee);ye&&ee.beforeEnter(W),s(W,_,P),((D=se&&se.onVnodeMounted)||ye||ae)&&Ge(()=>{D&&kt(D,H,g),ye&&ee.enter(W),ae&&an(g,null,H,"mounted")},L)},A=(g,_,P,H,L)=>{if(P&&k(g,P),H)for(let F=0;F{for(let D=W;D{const q=_.el=g.el;let{patchFlag:W,dynamicChildren:D,dirs:se}=_;W|=g.patchFlag&16;const J=g.props||Te,ee=_.props||Te;let ae;if(P&&rn(P,!1),(ae=ee.onVnodeBeforeUpdate)&&kt(ae,P,_,g),se&&an(_,g,P,"beforeUpdate"),P&&rn(P,!0),D&&(!g.dynamicChildren||g.dynamicChildren.length!==D.length)&&(W=0,Y=!1,D=null),(J.innerHTML&&ee.innerHTML==null||J.textContent&&ee.textContent==null)&&c(q,""),D?te(g.dynamicChildren,D,q,P,H,ho(_,L),F):Y||he(g,_,q,null,P,H,ho(_,L),F,!1),W>0){if(W&16)$e(q,J,ee,P,L);else if(W&2&&J.class!==ee.class&&l(q,"class",null,ee.class,L),W&4&&l(q,"style",J.style,ee.style,L),W&8){const ye=_.dynamicProps;for(let Ce=0;Ce{ae&&kt(ae,P,_,g),se&&an(_,g,P,"updated")},H)},te=(g,_,P,H,L,F,Y)=>{for(let q=0;q<_.length;q++){const W=g[q],D=_[q],se=W.el&&(W.type===U||!Nn(W,D)||W.shapeFlag&198)?y(W.el):P;E(W,D,se,null,H,L,F,Y,!0)}},$e=(g,_,P,H,L)=>{if(_!==P){if(_!==Te)for(const F in _)!qn(F)&&!(F in P)&&l(g,F,_[F],null,L,H);for(const F in P){if(qn(F))continue;const Y=P[F],q=_[F];Y!==q&&F!=="value"&&l(g,F,q,Y,L,H)}"value"in P&&l(g,"value",_.value,P.value,L)}},Me=(g,_,P,H,L,F,Y,q,W)=>{const D=_.el=g?g.el:r(""),se=_.anchor=g?g.anchor:r("");let{patchFlag:J,dynamicChildren:ee,slotScopeIds:ae}=_;ae&&(q=q?q.concat(ae):ae),g==null?(s(D,P,H),s(se,P,H),w(_.children||[],P,se,L,F,Y,q,W)):J>0&&J&64&&ee&&g.dynamicChildren&&g.dynamicChildren.length===ee.length?(te(g.dynamicChildren,ee,P,L,F,Y,q),(_.key!=null||L&&_===L.subTree)&&da(g,_,!0)):he(g,_,P,se,L,F,Y,q,W)},X=(g,_,P,H,L,F,Y,q,W)=>{_.slotScopeIds=q,g==null?_.shapeFlag&512?L.ctx.activate(_,P,H,Y,W):j(_,P,H,L,F,Y,W):z(g,_,W)},j=(g,_,P,H,L,F,Y)=>{const q=g.component=Fu(g,H,L);if(li(g)&&(q.ctx.renderer=ln),Ru(q,!1,Y),q.asyncDep){if(L&&L.registerDep(q,ce,Y),!g.el){const W=q.subTree=_e(jt);S(null,W,_,P),g.placeholder=W.el}}else ce(q,g,_,P,L,F,Y)},z=(g,_,P)=>{const H=_.component=g.component;if(gu(g,_,P))if(H.asyncDep&&!H.asyncResolved){_.el=g.el,de(H,_,P);return}else H.next=_,H.update();else _.el=g.el,H.vnode=_},ce=(g,_,P,H,L,F,Y)=>{const q=()=>{if(g.isMounted){let{next:J,bu:ee,u:ae,parent:ye,vnode:Ce}=g;{const wt=fa(g);if(wt){J&&(J.el=Ce.el,de(g,J,Y)),wt.asyncDep.then(()=>{Ge(()=>{g.isUnmounted||D()},L)});return}}let xe=J,De;rn(g,!1),J?(J.el=Ce.el,de(g,J,Y)):J=Ce,ee&&ks(ee),(De=J.props&&J.props.onVnodeBeforeUpdate)&&kt(De,ye,J,Ce),rn(g,!0);const Ne=Mi(g),bt=g.subTree;g.subTree=Ne,E(bt,Ne,y(bt.el),fe(bt),g,L,F),J.el=Ne.el,xe===null&&bu(g,Ne.el),ae&&Ge(ae,L),(De=J.props&&J.props.onVnodeUpdated)&&Ge(()=>kt(De,ye,J,Ce),L)}else{let J;const{el:ee,props:ae}=_,{bm:ye,m:Ce,parent:xe,root:De,type:Ne}=g,bt=Gn(_);rn(g,!1),ye&&ks(ye),!bt&&(J=ae&&ae.onVnodeBeforeMount)&&kt(J,xe,_),rn(g,!0);{De.ce&&De.ce._hasShadowRoot()&&De.ce._injectChildStyle(Ne,g.parent?g.parent.type:void 0);const wt=g.subTree=Mi(g);E(null,wt,P,H,g,L,F),_.el=wt.el}if(Ce&&Ge(Ce,L),!bt&&(J=ae&&ae.onVnodeMounted)){const wt=_;Ge(()=>kt(J,xe,wt),L)}(_.shapeFlag&256||xe&&Gn(xe.vnode)&&xe.vnode.shapeFlag&256)&&g.a&&Ge(g.a,L),g.isMounted=!0,_=P=H=null}};g.scope.on();const W=g.effect=new Sl(q);g.scope.off();const D=g.update=W.run.bind(W),se=g.job=W.runIfDirty.bind(W);se.i=g,se.id=g.uid,W.scheduler=()=>oi(se),rn(g,!0),D()},de=(g,_,P)=>{_.component=g;const H=g.vnode.props;g.vnode=_,g.next=null,_u(g,_.props,H,P),Su(g,_.children,P),Ht(),Si(g),Ut()},he=(g,_,P,H,L,F,Y,q,W=!1)=>{const D=g&&g.children,se=g?g.shapeFlag:0,J=_.children,{patchFlag:ee,shapeFlag:ae}=_;if(ee>0){if(ee&128){K(D,J,P,H,L,F,Y,q,W);return}else if(ee&256){Q(D,J,P,H,L,F,Y,q,W);return}}ae&8?(se&16&&N(D,L,F),J!==D&&c(P,J)):se&16?ae&16?K(D,J,P,H,L,F,Y,q,W):N(D,L,F,!0):(se&8&&c(P,""),ae&16&&w(J,P,H,L,F,Y,q,W))},Q=(g,_,P,H,L,F,Y,q,W)=>{g=g||dn,_=_||dn;const D=g.length,se=_.length,J=Math.min(D,se);let ee;for(ee=0;eese?N(g,L,F,!0,!1,J):w(_,P,H,L,F,Y,q,W,J)},K=(g,_,P,H,L,F,Y,q,W)=>{let D=0;const se=_.length;let J=g.length-1,ee=se-1;for(;D<=J&&D<=ee;){const ae=g[D],ye=_[D]=W?Mt(_[D]):Ct(_[D]);if(Nn(ae,ye))E(ae,ye,P,null,L,F,Y,q,W);else break;D++}for(;D<=J&&D<=ee;){const ae=g[J],ye=_[ee]=W?Mt(_[ee]):Ct(_[ee]);if(Nn(ae,ye))E(ae,ye,P,null,L,F,Y,q,W);else break;J--,ee--}if(D>J){if(D<=ee){const ae=ee+1,ye=aeee)for(;D<=J;)ne(g[D],L,F,!0),D++;else{const ae=D,ye=D,Ce=new Map;for(D=ye;D<=ee;D++){const et=_[D]=W?Mt(_[D]):Ct(_[D]);et.key!=null&&Ce.set(et.key,D)}let xe,De=0;const Ne=ee-ye+1;let bt=!1,wt=0;const Dn=new Array(Ne);for(D=0;D=Ne){ne(et,L,F,!0);continue}let _t;if(et.key!=null)_t=Ce.get(et.key);else for(xe=ye;xe<=ee;xe++)if(Dn[xe-ye]===0&&Nn(et,_[xe])){_t=xe;break}_t===void 0?ne(et,L,F,!0):(Dn[_t-ye]=D+1,_t>=wt?wt=_t:bt=!0,E(et,_[_t],P,null,L,F,Y,q,W),De++)}const mi=bt?Eu(Dn):dn;for(xe=mi.length-1,D=Ne-1;D>=0;D--){const et=ye+D,_t=_[et],yi=_[et+1],gi=et+1{const{el:F,type:Y,transition:q,children:W,shapeFlag:D}=g;if(D&6){B(g.component.subTree,_,P,H);return}if(D&128){g.suspense.move(_,P,H);return}if(D&64){Y.move(g,_,P,ln);return}if(Y===U){s(F,_,P);for(let J=0;Jq.enter(F),L));else{const{leave:J,delayLeave:ee,afterLeave:ae}=q,ye=()=>{g.ctx.isUnmounted?o(F):s(F,_,P)},Ce=()=>{const xe=F._isLeaving||!!F[fo];F._isLeaving&&F[fo](!0),q.persisted&&!xe?ye():J(F,()=>{ye(),ae&&ae()})};ee?ee(F,ye,Ce):Ce()}else s(F,_,P)},ne=(g,_,P,H=!1,L=!1)=>{const{type:F,props:Y,ref:q,children:W,dynamicChildren:D,shapeFlag:se,patchFlag:J,dirs:ee,cacheIndex:ae,memo:ye}=g;if((J===-2||D&&D.hasOnce)&&(L=!1),q!=null&&(Ht(),Jn(q,null,P,g,!0),Ut()),ae!=null&&(!g.ctx||g.ctx===_)&&(_.renderCache[ae]=void 0),se&256){_.ctx.deactivate(g);return}const Ce=se&1&&ee,xe=!Gn(g);let De;if(xe&&(De=Y&&Y.onVnodeBeforeUnmount)&&kt(De,_,g),se&6)me(g.component,P,H);else{if(se&128){g.suspense.unmount(P,H);return}Ce&&an(g,null,_,"beforeUnmount"),se&64?g.type.remove(g,_,P,ln,H):D&&!D.hasOnce&&(F!==U||J>0&&J&64)?N(D,_,P,!1,!0):(F===U&&J&384||!L&&se&16)&&N(W,_,P),H&&Qe(g)}const Ne=ye!=null&&ae==null;(xe&&(De=Y&&Y.onVnodeUnmounted)||Ce||Ne)&&Ge(()=>{De&&kt(De,_,g),Ce&&an(g,null,_,"unmounted"),Ne&&(g.el=null)},P)},Qe=g=>{const{type:_,el:P,anchor:H,transition:L}=g;if(_===U){Mn(P,H);return}if(_===xs){C(g),L&&!L.persisted&&L.afterLeave&&L.afterLeave();return}const F=()=>{o(P),L&&!L.persisted&&L.afterLeave&&L.afterLeave()};if(g.shapeFlag&1&&L&&!L.persisted){const{leave:Y,delayLeave:q}=L,W=()=>Y(P,F);q?q(g.el,F,W):W()}else F()},Mn=(g,_)=>{let P;for(;g!==_;)P=b(g),o(g),g=P;o(_)},me=(g,_,P)=>{const{bum:H,scope:L,job:F,subTree:Y,um:q,m:W,a:D}=g;Fi(W),Fi(D),H&&ks(H),L.stop(),F?(F.flags|=8,ne(Y,g,_,P)):g.vnode.el&&Y&&(Y.transition=g.vnode.transition,ne(Y,g,_,P)),q&&Ge(q,_),Ge(()=>{g.isUnmounted=!0},_)},N=(g,_,P,H=!1,L=!1,F=0)=>{for(let Y=F;Y{if(g.shapeFlag&6)return fe(g.component.subTree);if(g.shapeFlag&128)return g.suspense.next();const _=b(g.anchor||g.el),P=_&&_[Wr];return P?b(P):_};let at=!1;const Ln=(g,_,P)=>{let H;g==null?_._vnode&&(ne(_._vnode,null,null,!0),H=_._vnode.component):E(_._vnode||null,g,_,null,null,null,P),_._vnode=g,at||(at=!0,Si(H),Bl(),at=!1)},ln={p:E,um:ne,m:B,r:Qe,mt:j,mc:w,pc:he,pbc:te,n:fe,o:e};return{render:Ln,hydrate:void 0,createApp:fu(Ln)}}function ho({type:e,props:t},n){return n==="svg"&&e==="foreignObject"||n==="mathml"&&e==="annotation-xml"&&t&&t.encoding&&t.encoding.includes("html")?void 0:n}function rn({effect:e,job:t},n){n?(e.flags|=32,t.flags|=4):(e.flags&=-33,t.flags&=-5)}function Au(e,t){return(!e||e&&!e.pendingBranch)&&t&&!t.persisted}function da(e,t,n=!1){const s=e.children,o=t.children;if(oe(s)&&oe(o))for(let l=0;l>1,e[n[r]]0&&(t[s]=n[l-1]),n[l]=s)}}for(l=n.length,a=n[l-1];l-- >0;)n[l]=a,a=t[a];return n}function fa(e){const t=e.subTree.component;if(t)return t.asyncDep&&!t.asyncResolved?t:fa(t)}function Fi(e){if(e)for(let t=0;te.__isSuspense;function Pu(e,t){t&&t.pendingBranch?oe(e)?t.effects.push(...e):t.effects.push(e):Rr(e)}const U=Symbol.for("v-fgt"),Qs=Symbol.for("v-txt"),jt=Symbol.for("v-cmt"),xs=Symbol.for("v-stc"),hn=[];let st=null;function p(e=!1){hn.push(st=e?null:[])}function va(){hn.pop(),st=hn[hn.length-1]||null}let ts=1;function Ni(e,t=!1){ts+=e,e<0&&st&&t&&(st.hasOnce=!0)}function ma(e){return e.dynamicChildren=ts>0?st||dn:null,va(),ts>0&&st&&st.push(e),e}function h(e,t,n,s,o,l){return ma(i(e,t,n,s,o,l,!0))}function Xe(e,t,n,s,o){return ma(_e(e,t,n,s,o,!0))}function ya(e){return e?e.__v_isVNode===!0:!1}function Nn(e,t){return e.type===t.type&&e.key===t.key}const ga=({key:e})=>e??null,Ss=({ref:e,ref_key:t,ref_for:n})=>(typeof e=="number"&&(e=""+e),e!=null?Ie(e)||We(e)||ue(e)?{i:nt,r:e,k:t,f:!!n}:e:null);function i(e,t=null,n=null,s=0,o=null,l=e===U?0:1,a=!1,r=!1){const d={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&ga(t),ref:t&&Ss(t),scopeId:Wl,slotScopeIds:null,children:n,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetStart:null,targetAnchor:null,staticCount:0,shapeFlag:l,patchFlag:s,dynamicProps:o,dynamicChildren:null,appContext:null,ctx:nt};return r?(Ms(d,n),l&128&&e.normalize(d)):n&&(d.shapeFlag|=Ie(n)?8:16),ts>0&&!a&&st&&(d.patchFlag>0||l&6)&&d.patchFlag!==32&&st.push(d),d}const _e=Ou;function Ou(e,t=null,n=null,s=0,o=null,l=!1){if((!e||e===Xl)&&(e=jt),ya(e)){const r=En(e,t,!0);return n&&Ms(r,n),ts>0&&!l&&st&&(r.shapeFlag&6?st[st.indexOf(e)]=r:st.push(r)),r.patchFlag=-2,r}if(Vu(e)&&(e=e.__vccOpts),t){t=Iu(t);let{class:r,style:d}=t;r&&!Ie(r)&&(t.class=ie(r)),Se(d)&&(si(d)&&!oe(d)&&(d=Ue({},d)),t.style=Pe(d))}const a=Ie(e)?1:ha(e)?128:Gs(e)?64:Se(e)?4:ue(e)?2:0;return i(e,t,n,s,o,a,l,!0)}function Iu(e){return e?si(e)||ia(e)?Ue({},e):e:null}function En(e,t,n=!1,s=!1){const{props:o,ref:l,patchFlag:a,children:r,transition:d}=e,f=t?Mu(o||{},t):o,c={__v_isVNode:!0,__v_skip:!0,type:e.type,props:f,key:f&&ga(f),ref:t&&t.ref?n&&l?oe(l)?l.concat(Ss(t)):[l,Ss(t)]:Ss(t):l,scopeId:e.scopeId,slotScopeIds:e.slotScopeIds,children:r,target:e.target,targetStart:e.targetStart,targetAnchor:e.targetAnchor,staticCount:e.staticCount,shapeFlag:e.shapeFlag,patchFlag:t&&e.type!==U?a===-1?16:a|16:a,dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren,appContext:e.appContext,dirs:e.dirs,transition:d,component:e.component,suspense:e.suspense,ssContent:e.ssContent&&En(e.ssContent),ssFallback:e.ssFallback&&En(e.ssFallback),placeholder:e.placeholder,el:e.el,anchor:e.anchor,ctx:e.ctx,ce:e.ce,cacheIndex:e.cacheIndex};return d&&s&&ii(c,d.clone(c)),c}function Z(e=" ",t=0){return _e(Qs,null,e,t)}function Cs(e,t){const n=_e(xs,null,e);return n.staticCount=t,n}function R(e="",t=!1){return t?(p(),Xe(jt,null,e)):_e(jt,null,e)}function Ct(e){return e==null||typeof e=="boolean"?_e(jt):oe(e)?_e(U,null,e.slice()):ya(e)?Mt(e):_e(Qs,null,String(e))}function Mt(e){return e.el===null&&e.patchFlag!==-1||e.memo?e:En(e)}function Ms(e,t){let n=0;const{shapeFlag:s}=e;if(t==null)t=null;else if(oe(t))n=16;else if(typeof t=="object")if(s&65){const o=t.default;o&&(o._c&&(o._d=!1),Ms(e,o()),o._c&&(o._d=!0));return}else{n=32;const o=t._;!o&&!ia(t)?t._ctx=nt:o===3&&nt&&(nt.slots._===1?t._=1:(t._=2,e.patchFlag|=1024))}else if(ue(t)){if(s&65){Ms(e,{default:t});return}t={default:t,_ctx:nt},n=32}else t=String(t),s&64?(n=16,t=[Z(t)]):n=8;e.children=t,e.shapeFlag|=n}function Mu(...e){const t={};for(let n=0;nBe||nt;let Ls,ns;{const e=qs(),t=(n,s)=>{let o;return(o=e[n])||(o=e[n]=[]),o.push(s),l=>{o.length>1?o.forEach(a=>a(l)):o[0](l)}};Ls=t("__VUE_INSTANCE_SETTERS__",n=>Be=n),ns=t("__VUE_SSR_SETTERS__",n=>ss=n)}const fs=e=>{const t=Be;return Ls(e),e.scope.on(),()=>{e.scope.off(),Ls(t)}},Ri=()=>{Be&&Be.scope.off(),Ls(null)};function ba(e){return e.vnode.shapeFlag&4}let ss=!1;function Ru(e,t=!1,n=!1){t&&ns(t);const{props:s,children:o}=e.vnode,l=ba(e);wu(e,s,l,t),xu(e,o,n||t);const a=l?Hu(e,t):void 0;return t&&ns(!1),a}function Hu(e,t){const n=e.type;e.accessCache=Object.create(null),e.proxy=new Proxy(e.ctx,iu);const{setup:s}=n;if(s){Ht();const o=e.setupContext=s.length>1?ju(e):null,l=fs(e),a=ds(s,e,0,[e.props,o]),r=gl(a);if(Ut(),l(),(r||e.sp)&&!Gn(e)&&zl(e),r){if(a.then(Ri,Ri),t)return a.then(d=>{ns(!0);try{Hi(e,d,t)}finally{ns(!1)}}).catch(d=>{zs(d,e,0)});e.asyncDep=a}else Hi(e,a)}else wa(e)}function Hi(e,t,n){ue(t)?e.type.__ssrInlineRender?e.ssrRender=t:e.render=t:Se(t)&&(e.setupState=Hl(t)),wa(e)}function wa(e,t,n){const s=e.type;e.render||(e.render=s.render||At);{const o=fs(e);Ht();try{lu(e)}finally{Ut(),o()}}}const Uu={get(e,t){return je(e,"get",""),e[t]}};function ju(e){const t=n=>{e.exposed=n||{}};return{attrs:new Proxy(e.attrs,Uu),slots:e.slots,emit:e.emit,expose:t}}function eo(e){return e.exposed?e.exposeProxy||(e.exposeProxy=new Proxy(Hl(Ar(e.exposed)),{get(t,n){if(n in t)return t[n];if(n in Xn)return Xn[n](e)},has(t,n){return n in t||n in Xn}})):e.proxy}function Bu(e,t=!0){return ue(e)?e.displayName||e.name:e.name||t&&e.__name}function Vu(e){return ue(e)&&"__vccOpts"in e}const $=(e,t)=>Mr(e,t,ss),Wu="3.5.43";let Do;const Ui=typeof window<"u"&&window.trustedTypes;if(Ui)try{Do=Ui.createPolicy("vue",{createHTML:e=>e})}catch{}const _a=Do?e=>Do.createHTML(e):e=>e,Ku="http://www.w3.org/2000/svg",qu="http://www.w3.org/1998/Math/MathML",It=typeof document<"u"?document:null,ji=It&&It.createElement("template"),Yu={insert:(e,t,n)=>{t.insertBefore(e,n||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,s)=>{const o=t==="svg"?It.createElementNS(Ku,e):t==="mathml"?It.createElementNS(qu,e):n?It.createElement(e,{is:n}):It.createElement(e);return e==="select"&&s&&s.multiple!=null&&o.setAttribute("multiple",s.multiple),o},createText:e=>It.createTextNode(e),createComment:e=>It.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>It.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},insertStaticContent(e,t,n,s,o,l){const a=n?n.previousSibling:t.lastChild;if(o&&(o===l||o.nextSibling))for(;t.insertBefore(o.cloneNode(!0),n),!(o===l||!(o=o.nextSibling)););else{ji.innerHTML=_a(s==="svg"?`${e}`:s==="mathml"?`${e}`:e);const r=ji.content;if(s==="svg"||s==="mathml"){const d=r.firstChild;for(;d.firstChild;)r.appendChild(d.firstChild);r.removeChild(d)}t.insertBefore(r,n)}return[a?a.nextSibling:t.firstChild,n?n.previousSibling:t.lastChild]}},zu=Symbol("_vtc");function Ju(e,t,n){const s=e[zu];s&&(t=(t?[t,...s]:[...s]).join(" ")),t==null?e.removeAttribute("class"):n?e.setAttribute("class",t):e.className=t}const Bi=Symbol("_vod"),Gu=Symbol("_vsh"),Xu=Symbol(""),Zu=/(?:^|;)\s*display\s*:/;function Qu(e,t,n){const s=e.style,o=Ie(n);let l=!1;if(n&&!o){if(t)if(Ie(t))for(const a of t.split(";")){const r=a.slice(0,a.indexOf(":")).trim();n[r]==null&&jn(s,r,"")}else for(const a in t)n[a]==null&&jn(s,a,"");for(const a in n){a==="display"&&(l=!0);const r=n[a];r!=null?tc(e,a,!Ie(t)&&t?t[a]:void 0,r)||jn(s,a,r):jn(s,a,"")}}else if(o){if(t!==n){const a=s[Xu];a&&(n+=";"+a),s.cssText=n,l=Zu.test(n)}}else t&&e.removeAttribute("style");Bi in e&&(e[Bi]=l?s.display:"",e[Gu]&&(s.display="none"))}const ws=/\s*!important$/;function jn(e,t,n){if(oe(n))n.forEach(s=>jn(e,t,s));else if(n==null&&(n=""),t.startsWith("--"))ws.test(n)?e.setProperty(t,n.replace(ws,""),"important"):e.setProperty(t,n);else{const s=ec(e,t);ws.test(n)?e.setProperty(on(s),n.replace(ws,""),"important"):e[s]=n}}const Vi=["Webkit","Moz","ms"],vo={};function ec(e,t){const n=vo[t];if(n)return n;let s=ze(t);if(s!=="filter"&&s in e)return vo[t]=s;s=Ws(s);for(let o=0;omo||(ac.then(()=>mo=0),mo=Date.now());function uc(e,t){const n=s=>{if(!s._vts)s._vts=Date.now();else if(s._vts<=n.attached)return;const o=n.value;if(oe(o)){const l=s.stopImmediatePropagation;s.stopImmediatePropagation=()=>{l.call(s),s._stopped=!0};const a=o.slice(),r=[s];for(let d=0;de.charCodeAt(0)===111&&e.charCodeAt(1)===110&&e.charCodeAt(2)>96&&e.charCodeAt(2)<123,cc=(e,t,n,s,o,l)=>{const a=o==="svg";t==="class"?Ju(e,s,a):t==="style"?Qu(e,n,s):js(t)?Bs(t)||sc(e,t,n,s,l):(t[0]==="."?(t=t.slice(1),!0):t[0]==="^"?(t=t.slice(1),!1):dc(e,t,s,a))?(qi(e,t,s),!e.tagName.includes("-")&&(t==="value"||t==="checked"||t==="selected")&&Ki(e,t,s,a,l,t!=="value")):e._isVueCE&&(fc(e,t)||e._def.__asyncLoader&&(/[A-Z]/.test(t)||!Ie(s)))?qi(e,ze(t),s,l,t):(t==="true-value"?e._trueValue=s:t==="false-value"&&(e._falseValue=s),Ki(e,t,s,a))};function dc(e,t,n,s){if(s)return!!(t==="innerHTML"||t==="textContent"||t in e&&zi(t)&&ue(n));if(t==="spellcheck"||t==="draggable"||t==="translate"||t==="autocorrect"||t==="sandbox"&&e.tagName==="IFRAME"||t==="form"||t==="list"&&e.tagName==="INPUT"||t==="type"&&e.tagName==="TEXTAREA")return!1;if(t==="width"||t==="height"){const o=e.tagName;if(o==="IMG"||o==="VIDEO"||o==="CANVAS"||o==="SOURCE")return!1}return zi(t)&&Ie(n)?!1:t in e}function fc(e,t){const n=e._def.props;if(!n)return!1;const s=ze(t);return Array.isArray(n)?n.some(o=>ze(o)===s):Object.keys(n).some(o=>ze(o)===s)}const en=e=>{const t=e.props["onUpdate:modelValue"]||!1;return oe(t)?n=>ks(t,n):t};function pc(e){e.target.composing=!0}function Ji(e){const t=e.target;t.composing&&(t.composing=!1,t.dispatchEvent(new Event("input")))}const ot=Symbol("_assign"),_s=Symbol("_initialValue");function yo(e,t,n){return t&&(e=e.trim()),n&&(e=Ks(e)),e}const Fe={created(e,{modifiers:{lazy:t,trim:n,number:s}},o){e.parentNode&&(e.type==="text"?e[_s]=e.defaultValue.replace(/[\r\n]/g,""):e.type==="textarea"&&(e[_s]=e.defaultValue.replace(/\r\n?/g,` -`))),e[ot]=en(o);const l=s||o.props&&o.props.type==="number";Nt(e,t?"change":"input",a=>{a.target.composing||e[ot](yo(e.value,n,l))}),(n||l)&&Nt(e,"change",()=>{e.value=yo(e.value,n,l)}),t||(Nt(e,"compositionstart",pc),Nt(e,"compositionend",Ji),Nt(e,"change",Ji))},mounted(e,{value:t,modifiers:{trim:n,number:s}}){const o=t??"",l=e[_s];delete e[_s],l!==void 0&&(e.type==="text"||e.type==="textarea")&&e.value!==l?e[ot](yo(e.value,n,s)):e.value=o},beforeUpdate(e,{value:t,oldValue:n,modifiers:{lazy:s,trim:o,number:l}},a){if(e[ot]=en(a),e.composing)return;const r=(l||e.type==="number")&&!/^0\d/.test(e.value)?Ks(e.value):e.value,d=t??"";if(r===d)return;const f=e.getRootNode();(f instanceof Document||f instanceof ShadowRoot)&&f.activeElement===e&&e.type!=="range"&&(s&&t===n||o&&e.value.trim()===d)||(e.value=d)}},hc={deep:!0,created(e,t,n){e[ot]=en(n),Nt(e,"change",()=>{const s=e._modelValue,o=Pn(e),l=e.checked,a=e[ot];if(oe(s)){const r=Go(s,o),d=r!==-1;if(l&&!d)a(s.concat(o));else if(!l&&d){const f=[...s];f.splice(r,1),a(f)}}else if(Rt(s)){const r=new Set(s);l?r.add(o):r.delete(o),a(r)}else a(ka(e,l))})},mounted:Gi,beforeUpdate(e,t,n){e[ot]=en(n),Gi(e,t,n)}};function Gi(e,{value:t,oldValue:n},s){e._modelValue=t;let o;if(oe(t))o=Go(t,s.props.value)>-1;else if(Rt(t))o=t.has(s.props.value);else{if(t===n)return;o=vt(t,ka(e,!0))}e.checked!==o&&(e.checked=o)}const Xi={created(e,{value:t},n){e.checked=vt(t,n.props.value),e[ot]=en(n),Nt(e,"change",()=>{e[ot](Pn(e))})},beforeUpdate(e,{value:t,oldValue:n},s){e[ot]=en(s),t!==n&&(e.checked=vt(t,s.props.value))}},vn={deep:!0,created(e,{value:t,modifiers:{number:n}},s){e._modelValue=t,Nt(e,"change",()=>{const o=Array.prototype.filter.call(e.options,d=>d.selected).map(d=>n?Ks(Pn(d)):Pn(d)),l=e.multiple,a=l?Rt(e._modelValue)?new Set(o):o:o[0],r=e._pendingValue=[l,l?oe(a)?o.slice():o:a];try{e[ot](a)}finally{Js(()=>{e._pendingValue===r&&(e._pendingValue=void 0)})}}),e[ot]=en(s)},mounted(e,{value:t}){Zi(e,t)},beforeUpdate(e,{value:t},n){e._modelValue=t,e[ot]=en(n)},updated(e,{value:t}){const n=e._pendingValue;e._pendingValue=void 0,(!n||n[0]!==e.multiple||!vc(t,n[1],n[0]))&&Zi(e,t)}};function vc(e,t,n){if(!n||oe(e))return vt(e,t);if(Rt(e)){if(e.size!==t.length)return!1;for(const s of t)if(!e.has(s))return!1;return!0}return!1}function Zi(e,t){const n=e.multiple,s=oe(t);if(!(n&&!s&&!Rt(t))){for(let o=0,l=e.options.length;oString(f)===String(r)):a.selected=Go(t,r)>-1}else a.selected=t.has(r);else if(vt(Pn(a),t)){e.selectedIndex!==o&&(e.selectedIndex=o);return}}!n&&e.selectedIndex!==-1&&(e.selectedIndex=-1)}}function Pn(e){return"_value"in e?e._value:e.value}function ka(e,t){const n=t?"_trueValue":"_falseValue";return n in e?e[n]:t}const mc=["ctrl","shift","alt","meta"],yc={stop:e=>e.stopPropagation(),prevent:e=>e.preventDefault(),self:e=>e.target!==e.currentTarget,ctrl:e=>!e.ctrlKey,shift:e=>!e.shiftKey,alt:e=>!e.altKey,meta:e=>!e.metaKey,left:e=>"button"in e&&e.button!==0,middle:e=>"button"in e&&e.button!==1,right:e=>"button"in e&&e.button!==2,exact:(e,t)=>mc.some(n=>e[`${n}Key`]&&!t.includes(n))},dt=(e,t)=>{if(!e)return e;const n=e._withMods||(e._withMods={}),s=t.join(".");return n[s]||(n[s]=((o,...l)=>{for(let a=0;a{const n=e._withKeys||(e._withKeys={}),s=t.join(".");return n[s]||(n[s]=(o=>{if(!("key"in o))return;const l=on(o.key);if(t.some(a=>a===l||gc[a]===l))return e(o)}))},bc=Ue({patchProp:cc},Yu);let Qi;function wc(){return Qi||(Qi=Cu(bc))}const _c=((...e)=>{const t=wc().createApp(...e),{mount:n}=t;return t.mount=s=>{const o=$c(s);if(!o)return;const l=t._component;!ue(l)&&!l.render&&!l.template&&(l.template=o.innerHTML),o.nodeType===1&&(o.textContent="");const a=n(o,!1,kc(o));return o instanceof Element&&(o.removeAttribute("v-cloak"),o.setAttribute("data-v-app","")),a},t});function kc(e){if(e instanceof SVGElement)return"svg";if(typeof MathMLElement=="function"&&e instanceof MathMLElement)return"mathml"}function $c(e){return Ie(e)?document.querySelector(e):e}const xc=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],Sc=["Su","Mo","Tu","We","Th","Fr","Sa"],xa={clock:"clock-outline",analog:"clock-outline",date:"calendar"},qe=e=>JSON.stringify(e),ke=e=>String.fromCodePoint(parseInt(e,16));function ui(e,t=new Date){let n=t.getHours();return e||(n=n%12||12),`${String(n).padStart(2,"0")}:${String(t.getMinutes()).padStart(2,"0")}`}const ci=(e=new Date)=>`${Sc[e.getDay()]} ${e.getDate()} ${xc[e.getMonth()]}`;function Cc(e,t=Math.floor(Date.now()/1e3)){const n=t-e,s=Math.abs(n),o=l=>Math.floor(s/l);return n<0?s<3600?`In ${Math.max(1,o(60))} min`:s<86400?o(3600)===1?"In 1 hour":`In ${o(3600)} hours`:s<172800?"Tomorrow":`In ${o(86400)} days`:s<60?"Just now":s<3600?`${o(60)} min ago`:s<86400?o(3600)===1?"1 hour ago":`${o(3600)} hours ago`:s<172800?"Yesterday":s<604800?`${o(86400)} days ago`:s<2592e3?o(604800)===1?"1 week ago":`${o(604800)} weeks ago`:s<31536e3?o(2592e3)===1?"1 month ago":`${o(2592e3)} months ago`:o(31536e3)===1?"1 year ago":`${o(31536e3)} years ago`}const Tc={guition:{width:448,top:36,name:27,text:21,icon:26},cyd:{width:298,top:24,name:18,text:14,icon:18}};let Rn;const Fo=new Map,Ac=()=>Fo.clear();function cn(e,t){const n=`${t}|${e}`;let s=Fo.get(n);if(!s){if(Rn===void 0&&(Rn=document.createElement("canvas").getContext("2d")),Rn){Rn.font=t;const o=Rn.measureText(e);s={left:-o.actualBoundingBoxLeft,right:o.actualBoundingBoxRight,top:-o.actualBoundingBoxAscent,bottom:o.actualBoundingBoxDescent,advance:o.width}}else{const o=Number(/(\d+(?:\.\d+)?)px/.exec(t)?.[1]||14),l=[...e].length*o*.55;s={left:0,right:l,top:-o*.72,bottom:o*.02,advance:l}}Fo.set(n,s)}return s}const Ec=e=>({name:`500 ${e.name}px "Bar Roboto"`,text:`400 ${e.text}px "Bar Roboto"`,icon:`${e.icon}px "Tile Icons"`});function Pc(e){return{icon:Math.max(2,Math.floor((e*4+5)/10)),item:Math.max(6,Math.floor((e*125+50)/100)),name:Math.max(8,Math.floor((e*16+5)/10))}}function Sa(e,t,n,s){const o=Ec(t),l=cn("0",o.text),a=Math.round(l.bottom-l.top),r=Pc(a),d=cn(String.fromCodePoint(983376),o.icon),f=Math.round(d.bottom-d.top),c=e.map((C,V)=>{const O=s(C),A={index:V,item:C,view:O,shown:O.shown,width:0};return O.analog?(A.dial=f,A.width=f,A):(O.icon&&(A.icon={glyph:ke(O.icon),ink:cn(ke(O.icon),o.icon)},A.width+=A.icon.ink.right-A.icon.ink.left),O.text&&(A.text={value:O.text,ink:cn(O.text,o.text)},A.width+=(A.icon?r.icon:0)+A.text.ink.right-A.text.ink.left),A)}),y=c.filter(C=>C.shown),b=cn(n,o.name).advance,k=Math.min(b,Math.floor(t.width*35/100)),I=C=>C.reduce((V,O)=>V+O.width,0)+Math.max(0,C.length-1)*r.item;let E=0;for(;Et.width;)E++;const x=y.slice(E),S=new Set(y.slice(0,E).map(C=>C.index));let M=t.width-I(x);for(const C of x)C.x=M,M+=C.width+r.item;const G=x.length?x[0].x-r.name:t.width;return{metrics:t,fonts:o,cap:a,zero:l,gaps:r,parts:c,placed:x,dropped:S,nameText:n,natural:b,nameRoom:G}}function Oc(e,t,n){if(cn(e,t).advance<=n)return e;const s=[...e];for(;s.length&&cn(s.join("")+"...",t).advance>n;)s.pop();return s.join("")+"..."}function Ic(e){"fonts"in document&&Promise.all([document.fonts.load('500 27px "Bar Roboto"',"Studio 0"),document.fonts.load('400 21px "Bar Roboto"',"Away 0"),document.fonts.load('26px "Tile Icons"',String.fromCodePoint(983376))]).then(()=>{Ac(),e()}).catch(()=>{})}let Ca="";function Mc(e){Ca=e||""}async function di(e,t={}){const n=await fetch(`api/${e}`,{...t,headers:{"Content-Type":"application/json","X-Screen-CSRF":Ca,...t.headers}});if(!n.ok){let s="That didn't work. Refresh the page and try again.";try{const o=(await n.json())?.error;typeof o=="string"&&o.trim()&&(s=o)}catch{}throw new Error(s)}return n}const yt=async e=>(await di(e)).json(),it=async(e,t,n,s={})=>{const l=await(await di(e,{method:t,body:n===void 0?void 0:JSON.stringify(n),...s})).text();return l?JSON.parse(l):null},Le=6,Bt=8,os=Bt*Le,Lc=["single","wide","full"],tn=e=>e===!0?"wide":e===!1?"single":e,ps=e=>/^screen\.page_[1-8]$/.test(e)?Number(e.slice(-1)):0,Je=e=>Lc.includes(e.options?.size)?e.options.size:"single",No=e=>Je(e)!=="single",Ta=e=>Je(e)==="full",Aa=e=>e-e%Le,Ea=e=>e-e%2,ht=e=>Math.floor(e/Le),fi=e=>tn(e)==="full"?Le:tn(e)==="wide"?2:1,mn=(e,t)=>tn(t)==="full"?Array.from({length:Le},(n,s)=>Aa(e)+s):tn(t)==="wide"?[e,e+1]:[e],el=(e,t)=>tn(t)==="full"?Aa(e):tn(t)==="wide"?Ea(e):e,Ve=e=>e.tiles.map(t=>({tile:t,slot:t.slot}));function Pa(e){let t=0;return e.map(n=>{const s=Je(n);s==="full"&&t%Le?t+=Le-t%Le:s==="wide"&&t%2===1&&t++;const o=t;return t+=fi(s),o})}function Dc(e){const t=Pa(e);return e.some((n,s)=>n.slot!==t[s])}function to(e){if(e.tiles.some(t=>!Number.isInteger(t.slot))){const t=Pa(e.tiles);e.tiles.forEach((n,s)=>n.slot=t[s])}e.tiles.sort((t,n)=>t.slot-n.slot)}function Tn(e){const t=new Set;for(const{tile:n,slot:s}of e)for(const o of mn(s,Je(n)))t.add(o);return t}const is=(e,t,n)=>Number.isInteger(t)&&t>=0&&t!e.has(s));function Ds(e,t,n=0){for(let s=n;so.includes(f))?r.push(d):a.push({tile:d,slot:d.slot}));for(const d of r){const f=Je(d),c=Tn(a);let y=l.map(b=>el(b,f)).find(b=>is(c,b,f));if(y===void 0&&(y=Oa(c,f,d.slot)),y<0)return null;a.push({tile:d,slot:y})}return a.sort((d,f)=>d.slot-f.slot)}function gt(e,t=1){const n=Math.max(0,...e.map(({tile:s,slot:o})=>o+fi(Je(s))));return Math.min(Bt,Math.max(1,Math.ceil(n/Le),t||1))}function Fc(e){const t=e.split(".")[0];return t==="sun"?{options:{display:"sunpath",size:"wide"}}:t==="weather"?{options:{display:"forecast",size:"wide"}}:ps(e)?{}:t==="screen"?{options:{display:"digital",size:"wide"}}:{}}const Ma=e=>({entity:e,name:"",slot:-1,...Fc(e)});function La(e,t){const n=e.entity.split(".")[0],s=t.controls?.[n],o=e.options||{};if(!s||!["wide","full"].includes(o.size)||(o.display||"standard")!=="standard"||o.inline==="slider")return null;const l=o.controls??(o.size==="full"?"none":s.default);return l==="none"?null:l}function Nc(e,t){const n=La(e,t);return n?t.controls?.[e.entity.split(".")[0]]?.choices.find(s=>s.key===n)?.label.toLocaleLowerCase()||n:"none"}const Ro=e=>(/^(\d+)\.(\d+)\.(\d+)$/.exec(e||"")||[]).slice(1).map(Number);function nn(e,t){const[n,s]=[Ro(e),Ro(t)];if(n.length!==3||s.length!==3)return!1;for(let o=0;o<3;o++)if(n[o]!==s[o])return n[o]>s[o];return!0}const Rc=(e,t,n,s)=>nn(e,`${t}.${n}.${s}`),Hc=os;function Uc(e){return Ro(e).length!==3?10:nn(e,"0.2.62")?Hc:nn(e,"0.2.7")?20:10}const Da={standard:"standard",watch:"large value",forecast:"weather forecast",graph:"graph",digital:"digital clock",analog:"analog clock",sunpath:"sun path"},jc={single:"normal",wide:"double",full:"full page"},tl=["light","switch","input_boolean","fan","media_player","climate"],Bc=["light","fan","cover","number","input_number","media_player"],Vc={light:["Light","☀","#ad7600","#fff3d3"],climate:["Climate","❄","#c86620","#ffebdc"],vacuum:["Vacuum","◉","#008577","#def3ed"],fan:["Fan","✣","#008aab","#def5fa"],cover:["Cover","▤","#8053af","#eee5f8"],media_player:["Media","▶","#007cad","#def2fc"],sensor:["Sensor","⌁","#3476b1","#e5effa"],binary_sensor:["Status","◈","#ad7600","#fff3d3"],switch:["Switch","⏻","#ad7600","#fff3d3"],input_boolean:["Switch","⏻","#ad7600","#fff3d3"],scene:["Scene","✦","#8053af","#eee5f8"],script:["Script","▷","#8053af","#eee5f8"],weather:["Weather","☁","#007cad","#def2fc"],number:["Value","±","#008577","#def3ed"],input_number:["Value","±","#008577","#def3ed"],select:["Select","≡","#5862af","#eaecfa"],input_select:["Select","≡","#5862af","#eaecfa"],button:["Action","↗","#5862af","#eaecfa"],input_button:["Action","↗","#5862af","#eaecfa"],screen:["Screen","◷","#25282c","#e9ecf1"],sun:["Sun","☼","#c86620","#ffebdc"],timer:["Timer","⏱","#008577","#def3ed"],person:["Person","☺","#2f7d32","#e1f2e2"],camera:["Camera","◧","#3d4a57","#e6ebf0"],image:["Image","◧","#3d4a57","#e6ebf0"]},xt=e=>Vc[e.split(".")[0]]||["Entity","◇","#637184","#edf0f4"],u=bn({inventory:{screens:[],entities:[]},connected:!1,reachable:!0,selected:null,layout:null,dirty:!1,busy:!1,saved:0,tab:"layout",selectedTile:null,inspector:null,iconPickerOpen:!1,actionPickerOpen:!1,actionSearch:"",insertAt:-1,filter:"",search:"",capabilities:{},entityActions:{},topbarPreviews:{},topbarAdded:null,topbarOverflow:[],settingEdits:{},settingPending:!1,updating:[],toast:null,now:Date.now(),fontsVersion:0,route:location.hash,overrideProfile:null,overrideFriendly:"",drag:{active:!1,moving:null,preview:null},menuOpen:!1,liveStates:{},room:"",hidePlaced:!1,palette:!1,firmwareJob:null}),lt=$(()=>u.inventory.screens.find(e=>e.id===u.selected)),ls=e=>(e&&"firmware_known"in e?e.firmware_known:e?.firmware)||"",Fa=$(()=>ls(lt.value)),Zt=(e,t,n)=>Rc(Fa.value,e,t,n),yn=$(()=>{const e=lt.value?.tile_limit;return typeof e=="number"&&Number.isInteger(e)&&e>0?e:Uc(Fa.value)}),nl=$(()=>{const e=lt.value?.full_page;return typeof e=="boolean"?e:Zt(0,2,62)}),Wc=$(()=>{const e=lt.value?.page_tiles_repeat;return typeof e=="boolean"?e:Zt(0,2,65)}),no=e=>Wc.value&&ps(e)>0,pi=$(()=>lt.value?.board==="guition"),Na=$(()=>Tc[pi.value?"guition":"cyd"]),go=$(()=>u.selectedTile&&u.layout?.tiles.includes(u.selectedTile)?u.selectedTile:void 0),hi=e=>!!u.selectedTile&&ve(u.selectedTile)===ve(e);let sl=0;function ge(e,t){u.toast={message:e,action:t},clearTimeout(sl),sl=window.setTimeout(()=>u.toast=null,t?8e3:5e3)}function Kc(){u.toast=null}async function ft(e,t,n="API key"){try{if(!navigator.clipboard||!window.isSecureContext)throw new Error;await navigator.clipboard.writeText(e),ge(`${n} copied.`)}catch{if(t){const s=document.createRange();s.selectNodeContents(t);const o=window.getSelection();o?.removeAllRanges(),o?.addRange(s)}ge(document.execCommand("copy")?`${n} copied.`:`${n} is selected. Copy with Ctrl+C or Command+C.`)}}function Fs(){const e="/config/integrations/dashboard";try{window.top.location.assign(e)}catch{window.open(e,"_blank")}}const qc=["","#settings","#new-screen","#firmware","#alerts","#override"],rt=$(()=>qc.includes(u.route)?u.route:"");function Oe(e){if(location.hash===e){u.route=e;return}location.hash=e}window.addEventListener("hashchange",()=>{u.route=location.hash,window.scrollTo(0,0)});function hs(e){return u.inventory.entities.find(t=>t.id===e)?.name||u.inventory.builtin?.find(t=>t.id===e)?.name||e}let bo={source:null,byName:{}};function vs(e){return bo.source!==u.inventory.icons&&(bo={source:u.inventory.icons,byName:Object.fromEntries((u.inventory.icons?.groups||[]).flatMap(t=>t.icons.map(n=>[n.name,n])))}),e?bo.byName[e]:void 0}function sn(e){const t=u.inventory.icons;if(!t)return"F0335";const n=u.inventory.entities.find(o=>o.id===e),s=e.split(".")[0];return t.builtin?.[e]?t.builtin[e]:n?.icon?n.icon:s==="weather"?t.weather[n?.state||""]||t.weather.partlycloudy:s==="sun"?t.sun[n?.state||""]||t.sun.below_horizon:t.defaults[s]||t.fallback}const Bn=e=>vs(e.options?.icon)?.cp||sn(e.entity),wo=new Set;async function so(e){const t=[...new Set(e)].filter(n=>!wo.has(n)&&!n.startsWith("screen."));if(t.length){t.forEach(n=>wo.add(n));try{for(let n=0;n`entity=${encodeURIComponent(o)}`).join("&");Object.assign(u.capabilities,(await yt(`capabilities?${s}`)).capabilities||{})}}catch{t.forEach(n=>wo.delete(n))}}}const _o=new Set;async function Yc(e){if(!_o.has(e)){_o.add(e);try{u.entityActions[e]=(await yt(`entity-actions?entity=${encodeURIComponent(e)}`)).actions}catch{_o.delete(e)}}}let ko=!1;async function oo(){const e=[...new Set((u.layout?.tiles||[]).map(t=>t.entity).filter(t=>!t.startsWith("screen.")))];if(!(!e.length||ko)){ko=!0;try{for(let t=0;t`entity=${encodeURIComponent(s)}`).join("&");Object.assign(u.liveStates,(await yt(`states?${n}`)).states||{})}}catch{}finally{ko=!1}}}function zc(e){const t=u.liveStates[e];if(t)return t;const n=u.inventory.entities.find(s=>s.id===e);return n?.state?{state:n.state,word:null,a:{}}:null}let Ho=0;function Ze(){u.dirty=!0,u.saved=0,Ho++}function Ra(e){if(e===u.selected&&u.layout&&u.dirty){He(),u.tab="layout",u.menuOpen=!1,Oe("");return}if(e!==u.selected&&u.dirty&&!confirm("You have unsaved changes. Open a different screen anyway?"))return;e!==u.selected&&(rs(),u.settingEdits={}),u.selected=e,u.selectedTile=null,u.inspector=null,u.tab="layout",u.menuOpen=!1;const t=u.inventory.screens.find(s=>s.id===e);if(!t){u.layout=null;return}const n=JSON.parse(JSON.stringify(t.layout));to(n),n.pages=gt(Ve(n),n.pages),u.layout=n,u.insertAt=-1,u.dirty=!1,u.saved=0,io(0),so(n.tiles.map(s=>s.entity)),oo(),Oe("")}function Jc(e){const t=u.layout,n=t.tiles.map(o=>`${o.entity}@${o.slot}`).join();for(const{tile:o,slot:l}of e)o.slot=l,t.tiles.includes(o)||t.tiles.push(o);to(t),t.pages=gt(Ve(t),t.pages);const s=t.tiles.map(o=>`${o.entity}@${o.slot}`).join()!==n;return s&&Ze(),s}function ms(e,t){if(!u.layout)return!1;so([e.entity]);const n=Ia(u.layout.tiles,e,t),s=n?Jc(n):!1;return s&&!u.liveStates[e.entity]&&oo(),s}function Ha(e){const t=u.layout;if(!t||!no(e)&&t.tiles.some(o=>o.entity===e)||t.tiles.length>=yn.value)return;const n=Ma(e),s=u.insertAt>=0?u.insertAt:Ds(Tn(Ve(t)),Je(n));u.insertAt=-1,s>=0&&ms(n,s)&&Uo(n)}function Ua(e){const t=u.layout;if(!t)return;const n=t.tiles.indexOf(e);n<0||(t.tiles.splice(n,1),hi(e)&&He(),Ze(),t.pages=gt(Ve(t),t.pages),ge(`${e.name||hs(e.entity)} removed`,{label:"Undo",run:()=>ms(e,e.slot)}))}function ol(){const e=u.layout;e&&(e.pages=Math.min(Bt,gt(Ve(e),e.pages)+1),Ze())}function Gc(e){const t=u.layout;if(t){for(const n of t.tiles)ht(n.slot)>e&&(n.slot-=Le);t.pages=Math.max(1,gt(Ve(t),t.pages)-1),Ze()}}function Xc(e,t){const n=u.layout;if(!n||!Number.isInteger(t)||t<0||t>=Bt||t===ht(e.slot))return!1;const s=Ds(Tn(Ve(n).filter(l=>l.tile!==e)),Je(e),t*Le),o=ms(e,s>=0&&ht(s)===t?s:t*Le);return o||ge(`There is no room for this tile on page ${t+1}.`),o}function Zc(){const e=u.layout;if(!e)return 1;const t=u.drag.preview||Ve(e),n=gt(t,e.pages);return u.drag.active&&nf.key!=="none"&&o.controls.includes(f.key))?.key||"none"),Ze();const d=u.layout;if(d){if(Ta(e)&&a!=="full"){const f=ht(e.slot),c=d.tiles.filter(k=>k!==e&&ht(k.slot)===f),y=Tn(Ve(d).filter(k=>k.tile!==e&&!c.includes(k.tile))),b=[];for(const k of c){const I=Ds(y,Je(k),(f+1)*Le);if(I<0){b.length=0;break}b.push([k,I]);for(const E of mn(I,Je(k)))y.add(E)}if(b.length===c.length){for(const[k,I]of b)k.slot=I;e.slot=f*Le}else{const k=Ds(Tn(Ve(d).filter(I=>I.tile!==e)),"full");k>=0?e.slot=k:(e.options.size=a,ge("No page is free for a full-page tile. Free a page first."))}}else if(No(e)&&!l){const f=Tn(Ve(d).filter(b=>b.tile!==e)),c=Ea(e.slot),y=is(f,c,"wide")?c:Oa(f,"wide",c);y>=0&&(e.slot=y)}to(d),d.pages=gt(Ve(d),d.pages)}}function Qc(e,t){const n=`screen.page_${t}`,s=u.layout;return!s||n===e.entity||!ps(n)?!1:!no(n)&&s.tiles.some(o=>o.entity===n)?(ge(`This screen already has a tile that goes to page ${t}.`),!1):(e.entity=n,s.pages=Math.max(gt(Ve(s),s.pages),t),Ze(),!0)}function Uo(e){hi(e)||(u.iconPickerOpen=!1,u.actionPickerOpen=!1,u.actionSearch=""),u.selectedTile=e,u.inspector={kind:"tile"},so([e.entity])}function Lt(e){u.inspector?.kind==="bar"&&u.inspector.index===e||(u.iconPickerOpen=!1),u.selectedTile=null,u.inspector={kind:"bar",index:e}}function il(){u.selectedTile=null,u.inspector={kind:"bar-add"}}function He(){u.inspector=null,u.selectedTile=null}async function jo(){if(u.busy||!u.layout||!u.selected)return;u.busy=!0;const e=Ho,t=u.selected;try{const{settings:n,...s}=u.layout;await it(`screens/${encodeURIComponent(u.selected)}`,"PUT",s),u.selected!==t?ge(`Saved. ${u.inventory.screens.find(o=>o.id===t)?.name||"The screen"} is being updated.`):Ho===e?(u.dirty=!1,u.saved=Date.now(),ge("Saved. Your screen is being updated.")):ge("Saved. Your newest change isn't sent yet: press Save & send again."),await wn()}catch(n){ge(n.message)}finally{u.busy=!1}}const gn=e=>!!(e&&e.alert_action&&nn(ls(e),u.inventory.alerts?.min_firmware||"0.2.31"));async function ja(e){try{await it(`screens/${encodeURIComponent(e.id)}/identify`,"POST"),ge(`${e.name} blinks and shows a card for a few seconds.`)}catch(t){ge(t.message)}}async function ed(e,t){return await it("alerts/test","POST",{screen:e,data:t})}const td=["title","tiles","header","pages"];function Ba(e,t){const n=u.layout;if(!n)return;const s=(Array.isArray(e.tiles)?e.tiles:[]).filter(r=>!!(r&&typeof r=="object"&&typeof r.entity=="string"&&r.entity.includes("."))).map(r=>({entity:r.entity,name:typeof r.name=="string"?r.name:"",slot:Number.isInteger(r.slot)?r.slot:-1,...r.options&&typeof r.options=="object"?{options:{...r.options}}:{}})),o=new Set,l=s.filter(r=>no(r.entity)||!o.has(r.entity)&&o.add(r.entity)),a=l.slice(0,yn.value);n.tiles=a,e.header&&Array.isArray(e.header.items)?n.header={items:e.header.items.map(r=>({...r}))}:delete n.header,n.pages=Number.isInteger(e.pages)?e.pages:1,to(n),n.pages=gt(Ve(n),n.pages),He(),Ze(),so(a.map(r=>r.entity)),oo(),io(0),ge(a.lengthn.id===e);!t||!u.layout||Ba(JSON.parse(JSON.stringify(t.layout)),`Layout of ${t.name} copied`)}function sd(){const e=u.layout;if(!e)return"";const t={esp_screens_layout:1};for(const n of td)e[n]!==void 0&&(t[n]=e[n]);return JSON.stringify(t,null,2)}function Va(){const e=sd();if(!e)return;const t=`${(lt.value?.name||"screen").toLowerCase().replace(/[^a-z0-9]+/g,"-")}.layout.json`,n=URL.createObjectURL(new Blob([e],{type:"application/json"})),s=document.createElement("a");s.href=n,s.download=t,s.click(),setTimeout(()=>URL.revokeObjectURL(n),5e3),ft(e,void 0,"Layout JSON")}function od(e){let t;try{t=JSON.parse(e)}catch{ge("That isn't JSON. Export a layout first, or paste one from another ESP Screens.");return}if(!t||typeof t!="object"||!Array.isArray(t.tiles)){ge("No tiles in this file. Expected a layout exported from ESP Screens.");return}Ba(t,"Layout imported")}function ll(e){const t=u.inventory.updates?.target,n=u.inventory.changelog;if(!Array.isArray(n)||!t)return[];const s=ls(e),o=[];for(const l of n)if(!(nn(l.firmware,t)&&l.firmware!==t)&&!(s&&nn(s,l.firmware)))for(const a of l.lines)o.includes(a)||o.push(a);return o}let $o=!1;async function id(){if(!$o){$o=!0;try{const e=await yt("firmware");u.firmwareJob={job:e.job,logs:e.logs||[]}}catch{}finally{$o=!1}}}const Bo=()=>u.inventory.screens.some(e=>e.update?.state==="running")||u.updating.length>0;function xn(e){const t=e.update||{};if(!(t.state==="running"||u.updating.includes(e.id)))return null;const n=u.firmwareJob?.job?.stage;return t.phase==="verify"?{percent:78,text:Kn.verify}:t.phase==="settle"?{percent:92,text:Kn.settle}:t.phase==="install"||!t.phase?n==="upload"?{percent:66,text:"Writing the firmware over Wi-Fi…"}:n?{percent:40,text:"Building the firmware…"}:{percent:12,text:Kn.install}:{percent:12,text:Kn[t.phase]||"Starting update…"}}const Vt=()=>u.layout?.header?.items??(u.layout?.settings?.show_clock??!0?[{type:"clock"}]:[]),zt=()=>u.inventory.header?.max_items||6;function as(e){u.layout&&(u.layout.header={items:e},Ze(),io())}let al=0;function io(e=150){clearTimeout(al),al=window.setTimeout(async()=>{const t=Vt();if(t.some(n=>n.type==="entity"))try{const n=await it("header-preview","POST",{header:{items:t}});t.forEach((s,o)=>u.topbarPreviews[qe(s)]=n.items[o])}catch{}},e)}function Vn(e){return e.type==="entity"?hs(e.entity):u.inventory.header?.builtin.find(t=>t.type===e.type)?.label||e.type}function qt(e){const t=new Date(u.now);if(e.type==="clock")return{text:ui(On().clock_24h!==!1,t),shown:!0};if(e.type==="date")return{text:ci(t),shown:!0};if(e.type==="analog")return{analog:!0,shown:!0};const n=u.topbarPreviews[qe(e)];return n?{icon:n.i||null,text:n.k==="ago"?Cc(n.e,Math.floor(u.now/1e3)):n.t,color:n.c?`#${n.c}`:null,shown:n.shown}:{icon:e.icon==="none"?null:vs(e.icon)?.cp||sn(e.entity),text:"…",shown:!0,loading:!0}}function xo(e,t){const n=[...Vt()];return t<0||t>=n.length||e===t?!1:(n.splice(t,0,...n.splice(e,1)),as(n),!0)}function rl(e){const t=[...Vt()],[n]=t.splice(e,1);n&&(u.inspector?.kind==="bar"&&He(),as(t),ge(`${Vn(n)} removed from the top bar`,{label:"Undo",run:()=>{const s=[...Vt()];s.splice(Math.min(e,s.length),0,n),as(s)}}))}function So(e){const t=Vt();if(t.length>=zt())return ge(`The top bar has room for ${zt()} items.`);if(t.some(n=>qe(n)===qe(e)))return ge("This is already in the top bar.");u.topbarAdded={key:qe(e),time:Date.now()},as([...t,e]),Lt(t.length)}const ld=[{title:"Brightness",icon:"F0599",rows:[{key:"brightness",label:"Brightness",kind:"number",min:5,max:100,step:5,unit:"%"},{key:"dark_mode",label:"Dark mode",kind:"toggle"},{key:"standby_enabled",label:"Auto standby",kind:"toggle"},{key:"standby_seconds",label:"Standby after",kind:"duration",min:60,max:86400,needs:"standby_enabled"},{key:"standby_brightness",label:"Standby brightness",kind:"number",min:0,max:100,step:5,unit:"%",needs:"standby_enabled",cap:"brightness"}]},{title:"Night",icon:"F0594",rows:[{key:"night_enabled",label:"Night mode",kind:"toggle"},{key:"night_start",label:"Starts",kind:"moment",needs:"night_enabled"},{key:"night_end",label:"Ends",kind:"moment",needs:"night_enabled"},{key:"night_brightness",label:"Night brightness",kind:"number",min:0,max:100,step:5,unit:"%",needs:"night_enabled",cap:"brightness"}]},{title:"Screen",icon:"F0379",rows:[{key:"clock_24h",label:"Clock",kind:"choice",options:[[!1,"12 hour"],[!0,"24 hour"]]},{key:"auto_home",label:"Back to page 1",kind:"toggle"},{key:"auto_home_seconds",label:"After",kind:"duration",min:30,max:3600,needs:"auto_home"},{key:"home_on_standby",label:"Also on standby",kind:"toggle"},{key:"swipe_pages",label:"Swipe between pages",kind:"toggle"},{key:"rotation",label:"Rotation",kind:"choice",options:[[0,"0°"],[90,"90°"],[180,"180°"],[270,"270°"]]}]}],Wa=4e3;let Jt={},An=null,Ns=0,Ts=null;const vi=()=>lt.value?.settings;function On(){const e=vi(),t={...e?.values||{}};for(const[n,s]of Object.entries(u.settingEdits))t[n]=s.value;return t}const ad=e=>e<300?30:e<900?60:e<3600?300:e<7200?900:1800;function ul(e,t,n,s,o){if(e.kind==="moment"){let r=s&&t%60?Math.floor(t/60)*60+(n>0?60:0):t+n*(s?60:15);return r%=1440,r<0?r+1440:r}const l=e.kind==="duration"?ad(n<0?t-1:t):e.step,a=e.cap?Math.min(e.max,o[e.cap]):e.max;return Math.min(a,Math.max(e.min,t+n*l))}function rd(e){if(e<60)return`${e} sec`;if(e<3600)return`${Math.floor(e/60)} min`;const t=Math.floor(e/3600),n=Math.floor(e%3600/60);return n?`${t} h ${String(n).padStart(2,"0")}`:`${t} h`}function ud(e,t){const n=Math.floor(e/60),s=String(e%60).padStart(2,"0");return t?`${String(n).padStart(2,"0")}:${s}`:`${n%12||12}:${s} ${n<12?"AM":"PM"}`}function cd(e,t){const n=t[e.key];return n==null?"—":e.kind==="number"?`${n}${e.unit||""}`:e.kind==="duration"?rd(n):e.kind==="moment"?ud(n,t.clock_24h!==!1):""}function Wn(e,t,n){if(An&&An!==u.selected&&Object.keys(Jt).length){rs(),ge("Still saving the other screen's settings. Try again in a moment.");return}An=u.selected;const s=On();if(u.settingEdits[e]={value:t,at:Date.now()},Jt[e]=t,e==="brightness")for(const o of["standby_brightness","night_brightness"])s[o]>t&&(u.settingEdits[o]={value:t,at:Date.now()});u.settingPending=!0,clearTimeout(Ns),Ns=window.setTimeout(()=>rs(),n)}async function rs(e=!1){if(clearTimeout(Ns),Ts||!Object.keys(Jt).length||!An)return;const t=An,n=Jt;Jt={};const s=di(`screens/${encodeURIComponent(t)}/settings`,{method:"PUT",body:JSON.stringify({settings:n}),keepalive:e});Ts=s;try{const o=await(await s).json(),l=u.inventory.screens.find(a=>a.id===t);l&&(l.settings=o)}catch(o){if(ge(o.message),t===u.selected)for(const l of Object.keys(n))delete u.settingEdits[l];if(t===u.selected&&n.brightness!==void 0)for(const l of["standby_brightness","night_brightness"])delete u.settingEdits[l]}finally{Ts=null,Object.keys(Jt).length?Ns=window.setTimeout(()=>rs(),150):An=null,u.settingPending=!!Object.keys(Jt).length,t===u.selected&&Rs(),setTimeout(()=>{t===u.selected&&Rs()},Wa+100)}}function Rs(){const e=vi();for(const[t,n]of Object.entries(u.settingEdits))Jt[t]!==void 0||Ts||(e&&e.values[t]===n.value||Date.now()-n.at>Wa)&&delete u.settingEdits[t]}const Kn={install:"Building and installing…",verify:"Waiting for the screen to come back…",settle:"Checking that it stays stable…"};async function Vo(e,t){u.updating.push(e.id);try{await it(`screens/${encodeURIComponent(e.id)}/update`,"POST",t?{host:t}:{}),await wn()}catch(n){u.updating=u.updating.filter(s=>s!==e.id),ge(n.message)}}async function cl(){try{await it("updates/run","POST"),await wn()}catch(e){ge(e.message)}}async function dd(e){try{await it("updates","PUT",{auto:e}),u.inventory.updates&&(u.inventory.updates.auto=e),ge(e?"Screens will now update automatically at night.":"Automatic updates are off.")}catch(t){ge(t.message)}}async function fd(){try{u.inventory.claude_skill=await it("claude-skill","POST"),ge(u.inventory.claude_skill?.restart?"Skill installed. Restart Claude Code once so it finds the new skills folder.":"Skill installed. Claude Code picks it up right away.")}catch(e){ge(e.message)}}async function wn(e=!0){try{const t=await yt(e?"inventory":"inventory?light=1");u.inventory=e?t:{...u.inventory,...t},t.csrf&&Mc(t.csrf),u.connected=!!u.inventory.connected,u.reachable=!0;for(const n of u.inventory.screens)n.update?.state==="running"&&(u.updating=u.updating.filter(s=>s!==n.id));u.selected&&Rs()}catch{u.reachable=!1}}function pd(e){u.inventory={...u.inventory,...e},u.connected=!!u.inventory.connected;for(const t of u.inventory.screens)t.update?.state==="running"&&(u.updating=u.updating.filter(n=>n!==t.id));u.selected&&Rs()}let dl=0,Wo=Date.now(),Hs=!1,Hn=null;function hd(){Hn||typeof EventSource>"u"||(Hn=new EventSource("api/events"),Hn.onopen=()=>{Hs=!0,us()},Hn.onmessage=e=>{document.hidden||pd(JSON.parse(e.data))},Hn.onerror=()=>{Hs=!1,us()})}function us(){clearTimeout(dl);const e=Hs?6e4:u.inventory.updates?.busy?3e3:1e4;dl=window.setTimeout(async()=>{if(!document.hidden){const t=Date.now()-Wo>=3e5;t&&(Wo=Date.now()),(t||!Hs)&&await wn(t)}us()},e)}let fl=!1;function vd(){fl||(fl=!0,wn(),hd(),us(),Ic(()=>u.fontsVersion++),setInterval(()=>{!u.layout||document.hidden||u.drag.active||(u.now=Date.now(),io(0))},3e4),setInterval(()=>{!document.hidden&&u.layout&&u.tab==="layout"&&rt.value===""&&oo()},8e3),setInterval(()=>{!document.hidden&&Bo()?id():u.firmwareJob&&!Bo()&&(u.firmwareJob=null)},3e3),document.addEventListener("visibilitychange",async()=>{document.hidden||(Wo=Date.now(),u.now=Date.now(),await wn(),us())}),window.addEventListener("pagehide",()=>rs(!0)),window.addEventListener("beforeunload",e=>{u.dirty&&(e.preventDefault(),e.returnValue="")}))}const md={class:"side"},yd={id:"screens"},gd=["aria-current","onClick"],bd={class:"txt"},wd={key:0,class:"unsaved",role:"img","aria-label":"Unsaved changes",title:"Unsaved changes"},_d={key:1,class:"pill"},kd={key:2,class:"spin small"},$d=["onSubmit"],xd=["disabled","title","onClick"],Sd={key:2,class:"whatsnew"},Cd=["aria-valuenow"],Td={class:"progress-text"},Ad=["title"],Ed=["title"],Pd={id:"pending"},Od={class:"pending-actions"},Id=["onClick"],Md=["aria-current"],Ld={class:"more"},Dd=["aria-current"],Fd={class:"mdi"},Nd=["aria-current"],Rd={class:"mdi"},Hd=["aria-current"],Ud={class:"mdi"},jd=Ee({__name:"Sidebar",setup(e){const t=pe(null),n=pe("");function s(d){const f=d.update||{};return f.state==="running"||u.updating.includes(d.id)?{kind:"running",text:Kn[f.phase||""]||"Starting update…"}:f.state==="queued"?{kind:"queued",text:"Queued for the update"}:f.available&&d.online?{kind:"available",text:`Update ${f.target}`}:f.result&&Date.now()/1e3-f.result.time<86400?{kind:f.result.state==="failed"?"failed":"done",text:f.result.message}:null}function o(d){const f=d.update||{};f.host&&f.profile?Vo(d):f.profile&&(t.value=d.id,n.value="")}function l(d){const f=n.value.trim();f&&(t.value=null,Vo(d,f))}const a=()=>{const d=u.firmwareJob?.logs||[];return d.length?d[d.length-1]:""},r=d=>d.installed?"Installed, but not yet in Home Assistant. Add the discovered ESPHome device under Settings → Devices & services, paste the API key there, and turn on “Allow the device to perform Home Assistant actions” under Configure.":d.downloaded?"Firmware downloaded, not yet in Home Assistant. Put it on the screen with ESPHome Web, then add the discovered ESPHome device under Settings → Devices & services, paste the API key there, and allow the Home Assistant actions under Configure.":`Not yet in Home Assistant. Already flashed? Add the ESPHome device under Settings → Devices & services, then allow the Home Assistant actions under Configure. Not flashed yet? Firmware & USB → ${d.file}.`;return(d,f)=>(p(),h("aside",md,[f[20]||(f[20]=i("div",{class:"brand"},[i("span",{class:"mark"},"▦"),i("span",null,"ESP Screens")],-1)),i("span",{id:"connection",class:ie(["conn",{online:m(u).connected}]),role:"status"},v(m(u).reachable?m(u).connected?"Home Assistant connected":"Reconnecting to Home Assistant…":"Management page unreachable · retrying…"),3),i("button",{type:"button",class:"search-btn",id:"open-palette",onClick:f[0]||(f[0]=c=>m(u).palette=!0)},[...f[10]||(f[10]=[Z("⌕ Search",-1),i("kbd",null,"⌘K",-1)])]),f[21]||(f[21]=i("div",{class:"label"},"Screens",-1)),i("div",yd,[(p(!0),h(U,null,le(m(u).inventory.screens,c=>(p(),h("div",{key:c.id,class:ie(["screen-item",{selected:c.id===m(u).selected&&m(rt)===""}])},[i("button",{type:"button",class:"nav-item","aria-current":c.id===m(u).selected&&m(rt)===""?"true":"false",onClick:y=>m(Ra)(c.id)},[i("span",{class:ie(["dot",{off:!c.online}])},null,2),i("span",bd,[i("span",null,v(c.name),1),i("small",null,v(c.online?"Online":"Offline")+v(c.area?" · "+c.area:"")+" · "+v(c.firmware||"unknown"),1)]),c.id===m(u).selected&&m(u).dirty?(p(),h("span",wd)):R("",!0),s(c)?.kind==="available"?(p(),h("span",_d,"Update")):s(c)?.kind==="running"?(p(),h("span",kd)):R("",!0)],8,gd),s(c)?(p(),h("div",{key:0,class:ie(["screen-update",s(c).kind])},[s(c).kind==="available"?(p(),h(U,{key:0},[t.value===c.id?(p(),h(U,{key:0},[f[12]||(f[12]=i("small",null,"The IP address, once; new firmware reports it itself.",-1)),i("form",{class:"screen-host",onSubmit:dt(y=>l(c),["prevent"])},[we(i("input",{"onUpdate:modelValue":f[1]||(f[1]=y=>n.value=y),placeholder:"IP address, e.g. 192.168.1.50",required:"",pattern:"[A-Za-z0-9][A-Za-z0-9.\\-]*","aria-label":"IP address",autofocus:""},null,512),[[Fe,n.value]]),f[11]||(f[11]=i("button",{type:"submit",class:"btn mini primary"},"Start",-1)),i("button",{type:"button",class:"icon-btn","aria-label":"Cancel",onClick:f[2]||(f[2]=y=>t.value=null)},"✕")],40,$d)],64)):(p(),h(U,{key:1},[i("small",null,v(s(c).text),1),i("button",{type:"button",class:"btn mini primary",disabled:!c.update?.profile,title:c.update?.profile?"":"No ESPHome profile found with this device name.",onClick:y=>o(c)},"Update",8,xd)],64)),m(ll)(c).length?(p(),h("details",Sd,[f[13]||(f[13]=i("summary",null,"What's new",-1)),i("ul",null,[(p(!0),h(U,null,le(m(ll)(c).slice(0,8),y=>(p(),h("li",{key:y},v(y),1))),128))])])):R("",!0)],64)):s(c).kind==="running"&&m(xn)(c)?(p(),h(U,{key:1},[i("div",{class:"progress",role:"progressbar","aria-valuenow":m(xn)(c).percent,"aria-valuemin":"0","aria-valuemax":"100"},[i("i",{style:Pe({width:m(xn)(c).percent+"%"})},null,4)],8,Cd),i("div",Td,[i("span",null,v(m(xn)(c).percent)+" %",1),i("span",{title:a()},v(m(xn)(c).text),9,Ad)]),a()?(p(),h("small",{key:0,title:a(),style:{"white-space":"nowrap",overflow:"hidden","text-overflow":"ellipsis"}},v(a()),9,Ed)):R("",!0),i("button",{type:"button",class:"btn link mini",style:{"justify-self":"start"},onClick:f[3]||(f[3]=y=>m(Oe)("#firmware"))},"Full log")],64)):(p(),h("small",{key:2,class:ie({failed:s(c).kind==="failed"})},v(s(c).text),3))],2)):R("",!0)],2))),128))]),i("div",Pd,[(p(!0),h(U,null,le(m(u).inventory.pending||[],c=>(p(),h("div",{key:c.file,class:"pending"},[i("strong",null,v(c.friendly),1),i("small",null,v(r(c)),1),i("div",Od,[i("button",{type:"button",class:"btn mini quiet",onClick:f[4]||(f[4]=(...y)=>m(Fs)&&m(Fs)(...y))},"Open Devices & services"),c.api_key?(p(),h("button",{key:0,type:"button",class:"btn mini quiet",onClick:y=>m(ft)(c.api_key)},"Copy API key",8,Id)):R("",!0)])]))),128))]),i("button",{id:"new-screen",type:"button",class:"nav-item ghost","aria-current":m(rt)==="#new-screen"?"true":"false",onClick:f[5]||(f[5]=c=>m(Oe)("#new-screen"))},[...f[14]||(f[14]=[i("span",{class:"plus"},"+",-1),i("span",{class:"txt"},"New screen",-1)])],8,Md),f[22]||(f[22]=i("div",{class:"spacer"},null,-1)),i("div",Ld,[f[19]||(f[19]=i("div",{class:"label"},"More",-1)),i("button",{id:"open-firmware",type:"button",class:"nav-item","aria-current":m(rt)==="#firmware"?"true":"false",onClick:f[6]||(f[6]=c=>m(Oe)("#firmware"))},[i("span",Fd,v(m(ke)("F0241")),1),f[15]||(f[15]=i("span",{class:"txt"},"Firmware & USB",-1))],8,Dd),i("button",{id:"open-alerts",type:"button",class:"nav-item","aria-current":m(rt)==="#alerts"?"true":"false",onClick:f[7]||(f[7]=c=>m(Oe)("#alerts"))},[i("span",Rd,v(m(ke)("F0594")),1),f[16]||(f[16]=i("span",{class:"txt"},"Alerts",-1))],8,Nd),i("button",{id:"open-settings",type:"button",class:"nav-item","aria-current":m(rt)==="#settings"?"true":"false",onClick:f[8]||(f[8]=c=>m(Oe)("#settings"))},[i("span",Ud,v(m(ke)("F0493")),1),f[17]||(f[17]=i("span",{class:"txt"},"Settings",-1))],8,Hd),i("button",{id:"refresh",type:"button",class:"nav-item ghost",onClick:f[9]||(f[9]=c=>m(wn)())},[...f[18]||(f[18]=[i("span",{class:"plus"},"↻",-1),i("span",{class:"txt"},"Refresh",-1)])])])]))}}),Bd={key:0,id:"toast",class:"toast",role:"status"},Vd=Ee({__name:"Toast",setup(e){return(t,n)=>m(u).toast?(p(),h("div",Bd,[i("span",null,v(m(u).toast.message),1),m(u).toast.action?(p(),h("button",{key:0,type:"button",onClick:n[0]||(n[0]=s=>{m(u).toast.action.run(),m(Kc)()})},v(m(u).toast.action.label),1)):R("",!0)])):R("",!0)}}),Wd={class:"palette-list"},Kd={class:"palette-group"},qd=["onMouseenter","onClick"],Yd={key:0,class:"mdi"},zd={key:1,class:"glyph"},Jd={class:"tx"},Gd={key:0},Xd={key:2,class:"hint-key"},Zd={key:0,class:"palette-empty"},Qd=Ee({__name:"CommandPalette",setup(e){const t=pe(""),n=pe(0),s=pe(null),o=$(()=>{const f=t.value.trim().toLocaleLowerCase(),c=[];for(const k of u.inventory.screens)c.push({group:"Screens",label:k.name,detail:`${k.online?"Online":"Offline"} · ${k.firmware||"unknown"}`,glyphText:"▦",run:()=>Ra(k.id)});const y=lt.value,b=[{group:"Actions",label:"New screen",detail:"Connect and install",glyphText:"+",run:()=>Oe("#new-screen")},{group:"Actions",label:"Firmware & USB",icon:"F0241",run:()=>Oe("#firmware")},{group:"Actions",label:"Alerts",detail:"Cheatsheet and a test alert",icon:"F0594",run:()=>Oe("#alerts")},{group:"Actions",label:"Settings",detail:"Updates, Claude",icon:"F0493",run:()=>Oe("#settings")}];if(y&&u.layout&&b.unshift({group:"Actions",label:"Save & send",detail:u.dirty?"Unsaved changes":"Nothing to save",key:"⌘S",run:()=>jo()},{group:"Actions",label:"Layout",detail:y.name,run:()=>{Oe(""),u.tab="layout"}},{group:"Actions",label:"Screen settings",detail:y.name,run:()=>{Oe(""),u.tab="settings"}},{group:"Actions",label:"Identify this screen",detail:gn(y)?"Blinks the backlight and shows a card":"Needs a newer firmware",run:()=>{gn(y)&&ja(y)}},{group:"Actions",label:"Export layout",detail:"Download and copy as JSON",run:Va}),c.push(...b),y&&u.layout&&f){const k=new Set(u.layout.tiles.map(E=>E.entity)),I=u.layout.tiles.length>=yn.value;for(const E of u.inventory.entities)if(!(E.tile===!1||k.has(E.id))&&`${E.name} ${E.id} ${E.area||""} ${E.device||""}`.toLocaleLowerCase().includes(f)&&(c.push({group:"Add to this screen",label:E.name,detail:[xt(E.id)[0],E.area].filter(Boolean).join(" · "),icon:u.inventory.icons?sn(E.id):void 0,run:()=>{I||Ha(E.id)}}),c.length>60))break}return f?c.filter(k=>`${k.label} ${k.detail||""}`.toLocaleLowerCase().includes(f)):c}),l=$(()=>{const f=[];return o.value.forEach((c,y)=>{(f.find(k=>k.group===c.group)||(f.push({group:c.group,items:[]}),f[f.length-1])).items.push({item:c,index:y})}),f});function a(){u.palette=!1}function r(f){a(),f.run()}function d(f){if(f.key==="Escape")f.preventDefault(),a();else if(f.key==="ArrowDown")f.preventDefault(),n.value=Math.min(o.value.length-1,n.value+1);else if(f.key==="ArrowUp")f.preventDefault(),n.value=Math.max(0,n.value-1);else if(f.key==="Enter"){f.preventDefault();const c=o.value[n.value];c&&r(c)}}return pn(t,()=>n.value=0),pn(()=>u.palette,async f=>{f&&(t.value="",n.value=0,await Js(),s.value?.focus())}),(f,c)=>m(u).palette?(p(),h("div",{key:0,class:"palette-backdrop",onClick:a},[i("div",{class:"palette",role:"dialog","aria-label":"Search",onClick:c[1]||(c[1]=dt(()=>{},["stop"])),onKeydown:d},[we(i("input",{ref_key:"input",ref:s,"onUpdate:modelValue":c[0]||(c[0]=y=>t.value=y),id:"palette-input",placeholder:"Search screens, entities, actions…","aria-label":"Search",autocomplete:"off"},null,512),[[Fe,t.value]]),i("div",Wd,[(p(!0),h(U,null,le(l.value,y=>(p(),h(U,{key:y.group},[i("div",Kd,v(y.group),1),(p(!0),h(U,null,le(y.items,({item:b,index:k})=>(p(),h("button",{key:k,type:"button",class:ie(["palette-item",{active:k===n.value}]),onMouseenter:I=>n.value=k,onClick:I=>r(b)},[b.icon?(p(),h("span",Yd,v(m(ke)(b.icon)),1)):(p(),h("span",zd,v(b.glyphText||"›"),1)),i("span",Jd,[i("span",null,v(b.label),1),b.detail?(p(),h("small",Gd,v(b.detail),1)):R("",!0)]),b.key?(p(),h("kbd",Xd,v(b.key),1)):R("",!0)],42,qd))),128))],64))),128)),o.value.length?R("",!0):(p(),h("p",Zd,"Nothing matches. Type part of a screen, entity or action."))])],32)])):R("",!0)}}),re={source:null,element:null,ghost:null,timer:0,start:null,offset:{x:0,y:0},pointerId:null,suppressUntil:0,last:null,scroller:0,target:null},Ka={mounted(e,t){e.__dragSource=t.value,e.addEventListener("pointerdown",s=>{const o=e.__dragSource;if(!(s.button!==0||e.disabled||s.target.closest(".remove"))){s.pointerType!=="touch"&&s.preventDefault(),Object.assign(re,{source:o,element:e,start:{x:s.clientX,y:s.clientY},pointerId:s.pointerId});try{e.setPointerCapture(s.pointerId)}catch{}clearTimeout(re.timer),s.pointerType==="touch"&&(re.timer=window.setTimeout(()=>pl(s),260))}}),e.addEventListener("pointermove",s=>{if(u.drag.active||!re.start||re.element!==e)return;const o=Math.hypot(s.clientX-re.start.x,s.clientY-re.start.y);if(s.pointerType==="touch"){o>10&&(clearTimeout(re.timer),re.start=null);return}o>=6&&pl(s)});const n=()=>{re.element===e&&!u.drag.active&&(clearTimeout(re.timer),re.start=null)};e.addEventListener("pointerup",n),e.addEventListener("pointercancel",n)},updated(e,t){e.__dragSource=t.value}};function pl(e){if(u.drag.active||!re.start||!re.source||!re.element)return;u.drag.active=!0,u.drag.moving=re.source.kind==="tile"?re.source.tile:Ma(re.source.id),u.drag.preview=null,re.target=null,getSelection()?.removeAllRanges();const t=re.element.getBoundingClientRect(),n=re.element.cloneNode(!0);n.classList.add("drag-ghost"),n.style.width=`${t.width}px`,n.style.height=`${t.height}px`,re.offset={x:e.clientX-t.left,y:e.clientY-t.top},document.body.append(n),re.ghost=n,document.addEventListener("pointermove",Ko),document.addEventListener("pointerup",Us),document.addEventListener("pointercancel",Us);try{re.element.releasePointerCapture(re.pointerId)}catch{}try{document.documentElement.setPointerCapture(re.pointerId)}catch{}document.addEventListener("touchmove",qa,{passive:!1}),re.scroller=window.setInterval(()=>{if(!re.last)return;const{x:s,y:o}=ef(document),l=s?ml(re.last.x,vl(s,"x")):0,a=o?ml(re.last.y,vl(o,"y")):0;l&&s.scrollBy(l,0),a&&o.scrollBy(0,a),(l||a)&&qo(Ya(re.last.x,re.last.y))},16),qo(-1),Ko(e)}function hl(e,t){if(!e)return!1;const n=t==="x"?e.scrollWidth>e.clientWidth:e.scrollHeight>e.clientHeight,s=getComputedStyle(e)[t==="x"?"overflowX":"overflowY"];return n&&(s==="auto"||s==="scroll")}function ef(e){const t=e.querySelector(".pages"),n=e.querySelector(".canvas");return{x:hl(t,"x")?t:n,y:hl(n,"y")?n:e.scrollingElement}}function vl(e,t){const n=t==="x"?window.innerWidth:window.innerHeight;if(e===e.ownerDocument.scrollingElement)return[0,n];const s=e.getBoundingClientRect();return t==="x"?[Math.max(s.left,0),Math.min(s.right,n)]:[Math.max(s.top,0),Math.min(s.bottom,n)]}function ml(e,[t,n]){return en-60?12:0}function qa(e){u.drag.active&&e.preventDefault()}function Us(e){e.pointerId===re.pointerId&&tf(e.type==="pointerup")}function Ko(e){!re.ghost||e.pointerId!==re.pointerId||(re.last={x:e.clientX,y:e.clientY},re.ghost.style.transform=`translate(${e.clientX-re.offset.x}px, ${e.clientY-re.offset.y}px)`,qo(Ya(e.clientX,e.clientY)))}function Ya(e,t){let n=null,s=1/0;for(const l of document.querySelectorAll(".pages [data-slot]")){const a=l.getBoundingClientRect(),r=Math.hypot(Math.max(a.left-e,0,e-a.right),Math.max(a.top-t,0,t-a.bottom));r16)return-1;let o=Number(n.cell.dataset.slot);return n.cell.classList.contains("wide")&&e>(n.r.left+n.r.right)/2&&(o+=1),o}function qo(e){re.target===e||!u.layout||!u.drag.moving||(re.target=e,u.drag.preview=e>=0?Ia(u.layout.tiles,u.drag.moving,e):null)}function tf(e){const t=u.drag.preview,n=u.drag.moving;document.removeEventListener("pointermove",Ko),document.removeEventListener("pointerup",Us),document.removeEventListener("pointercancel",Us),document.removeEventListener("touchmove",qa);try{document.documentElement.releasePointerCapture(re.pointerId)}catch{}if(re.ghost?.remove(),Object.assign(re,{ghost:null,suppressUntil:Date.now()+400,last:null,start:null,target:null,element:null,source:null}),clearInterval(re.scroller),u.drag.active=!1,u.drag.preview=null,u.drag.moving=null,e&&t&&n&&u.layout){const s=u.layout.tiles.map(l=>`${l.entity}@${l.slot}`).join(),o=!u.layout.tiles.includes(n);for(const{tile:l,slot:a}of t)l.slot=a,u.layout.tiles.includes(l)||u.layout.tiles.push(l);u.layout.tiles.sort((l,a)=>l.slot-a.slot),u.layout.tiles.map(l=>`${l.entity}@${l.slot}`).join()!==s&&Ze(),o&&ms(n,n.slot)}}window.addEventListener("click",e=>{Date.now()t.tile.name||hs(t.tile.entity)),s=$(()=>Ta(t.tile)),o=$(()=>No(t.tile)&&!s.value),l=$(()=>ps(t.tile.entity)),a=$(()=>u.inventory.backgrounds?.[t.tile.options?.background||""]?.color),r=$(()=>t.tile.options?.background==="none"),d=$(()=>t.tile.options?.display||"standard"),f=$(()=>d.value!=="standard"?Da[d.value]||d.value:""),c=$(()=>La(t.tile,u.inventory)),y=$(()=>t.tile.entity.split(".")[0]),b=$(()=>u.inventory.icons?.controls||{}),k=Q=>b.value[Q]?ke(b.value[Q]):"",I=$(()=>hi(t.tile)&&u.inspector?.kind==="tile"),E=$(()=>!t.placeholder&&u.layout?.tiles.includes(t.tile)),x=$(()=>`${n.value}, slot ${t.slot%Le+1} on page ${ht(t.slot)+1}. Enter: configure, arrow keys: move`),S=$(()=>new Date(u.now)),M=$(()=>(S.value.getHours()%12+S.value.getMinutes()/60)*30),G=$(()=>S.value.getMinutes()*6),C=$(()=>y.value==="screen"?null:zc(t.tile.entity)),V=$(()=>!C.value||["unavailable","unknown",""].includes(C.value.state)),O=$(()=>!!C.value&&!V.value&&C.value.state!=="off"&&C.value.state!=="closed"&&C.value.state!=="standby"&&C.value.state!=="idle"&&C.value.state!=="docked"),A=$(()=>["light","switch","input_boolean","fan"].includes(y.value)&&C.value?.state==="on"),w=$(()=>C.value?.a?.unit_of_measurement),T=Q=>Q.charAt(0).toUpperCase()+Q.slice(1).replace(/_/g," "),te={"clear-night":"Clear, night",cloudy:"Cloudy",exceptional:"Exceptional",fog:"Fog",hail:"Hail",lightning:"Lightning","lightning-rainy":"Lightning, rainy",partlycloudy:"Partly cloudy",pouring:"Pouring",rainy:"Rainy",snowy:"Snowy","snowy-rainy":"Snowy, rainy",sunny:"Sunny",windy:"Windy","windy-variant":"Windy"},$e=["scene","script","button","input_button"],Me=$(()=>{const Q=C.value;if(!Q||$e.includes(y.value))return f.value;if(V.value)return Q.state==="unknown"?"Unknown":"Unavailable";const K=Q.a||{};return y.value==="climate"?`${K.current_temperature!==void 0?`${K.current_temperature}° · `:""}${Q.word||T(Q.state)}`:y.value==="weather"?`${Q.word||te[Q.state]||T(Q.state)}${K.temperature!==void 0?` · ${K.temperature}°`:""}`:y.value==="cover"&&K.current_position!==void 0&&K.current_position>0&&K.current_position<100?`${Q.word||T(Q.state)} · ${K.current_position} %`:y.value==="media_player"&&K.media_title?`${Q.word||T(Q.state)} · ${K.media_title}`:y.value==="sensor"?`${Q.state}${w.value?` ${w.value}`:""}`:(y.value==="timer",Q.word||T(Q.state))}),X=$(()=>C.value&&!V.value?C.value.state:"—"),j=$(()=>{const Q=C.value;if(!Q||V.value)return 0;const K=Q.a||{};if(y.value==="light")return Q.state==="on"?K.brightness!==void 0?Math.round(K.brightness/255*100):100:0;if(y.value==="fan")return Q.state==="on"?K.percentage??100:0;if(y.value==="cover")return 100-(K.current_position??(Q.state==="open"?100:0));if(y.value==="media_player")return Math.round((K.volume_level??0)*100);if(y.value==="number"||y.value==="input_number"){const B=Number(Q.state),ne=Number(K.min??0),Qe=Number(K.max??100);return Number.isFinite(B)&&Qe>ne?Math.round((B-ne)/(Qe-ne)*100):0}return 0}),z=$(()=>({background:`linear-gradient(to right, ${j.value?"#ffbf38":"#c9ccd1"} ${j.value}%, ${j.value?"#fff1d3":"#e6e8ec"} ${j.value}%)`})),ce=$(()=>({background:`linear-gradient(to right, #2196f3 ${j.value}%, #d3e8fb ${j.value}%)`})),de=$(()=>{const Q=C.value?.a?.temperature;return Q!=null?`${Q}°`:"—"});async function he(Q){if(Q.key==="Enter"||Q.key===" "){Q.preventDefault(),Uo(t.tile);return}const K={ArrowLeft:-1,ArrowRight:1,ArrowUp:-2,ArrowDown:2}[Q.key];K&&(Q.preventDefault(),ms(t.tile,t.tile.slot+(s.value?Math.sign(K)*Le:o.value?Math.sign(K)*2:K))&&(await Js(),document.querySelector(`.pages [data-slot="${t.tile.slot}"]`)?.focus()))}return(Q,K)=>we((p(),h("div",{class:ie(["tile",{wide:o.value,full:s.value,bare:r.value,placeholder:e.placeholder||!E.value,chosen:I.value}]),"data-slot":e.slot,style:Pe(a.value&&!r.value?{backgroundColor:a.value}:void 0),tabindex:E.value?0:-1,role:E.value?"button":void 0,"aria-label":E.value?x.value:void 0,onClick:K[1]||(K[1]=B=>E.value&&m(Uo)(e.tile)),onKeydown:K[2]||(K[2]=B=>E.value&&he(B))},[d.value==="analog"?(p(),h(U,{key:0},[(p(),h("svg",sf,[K[3]||(K[3]=i("circle",{cx:"30",cy:"30",r:"27",fill:"#fff",stroke:"#c9ccd1"},null,-1)),(p(),h(U,null,le([0,90,180,270],B=>i("line",{key:B,x1:"30",y1:"5",x2:"30",y2:"9",stroke:"#1b1b1b","stroke-width":"1.5",transform:`rotate(${B} 30 30)`},null,8,of)),64)),i("line",{x1:"30",y1:"30",x2:"30",y2:"16",stroke:"#1b1b1b","stroke-width":"2.4","stroke-linecap":"round",transform:`rotate(${M.value} 30 30)`},null,8,lf),i("line",{x1:"30",y1:"30",x2:"30",y2:"11",stroke:"#1b1b1b","stroke-width":"1.6","stroke-linecap":"round",transform:`rotate(${G.value} 30 30)`},null,8,af),K[4]||(K[4]=i("circle",{cx:"30",cy:"30",r:"1.8",fill:"#1b1b1b"},null,-1))])),o.value?(p(),h("span",rf,[i("span",uf,[i("span",cf,v(n.value),1),i("span",df,v(f.value),1)])])):R("",!0)],64)):d.value==="digital"&&y.value==="screen"?(p(),h(U,{key:1},[i("span",ff,v(m(ke)(m(Bn)(e.tile))),1),i("span",pf,[i("span",hf,v(String(S.value.getHours()).padStart(2,"0"))+":"+v(String(S.value.getMinutes()).padStart(2,"0")),1),i("span",vf,v(n.value),1)])],64)):s.value?(p(),h(U,{key:2},[i("span",{class:ie(["ic mdi",{lit:A.value}])},v(m(ke)(m(Bn)(e.tile))),3),i("span",mf,[i("span",yf,v(n.value),1),l.value?(p(),h("span",gf,"Page "+v(l.value)+" ›",1)):d.value==="watch"?(p(),h("span",bf,[Z(v(X.value),1),w.value&&!V.value?(p(),h("small",wf,v(w.value),1)):R("",!0)])):Me.value?(p(),h("span",{key:2,class:ie(["st",{off:V.value}])},v(Me.value),3)):R("",!0)]),e.tile.options?.inline==="slider"?(p(),h("span",{key:0,class:"mini-slider",style:Pe(z.value)},null,4)):R("",!0),c.value?(p(),h("span",_f,[c.value==="toggle"?(p(),h("span",{key:0,class:ie(["tog",{off:!O.value}])},null,2)):c.value==="setpoint"?(p(),h("span",kf,[i("span",$f,v(k("minus")||"−"),1),i("b",null,v(de.value),1),i("span",xf,v(k("plus")||"+"),1)])):c.value==="volume"?(p(),h(U,{key:2},[i("span",{class:"range",style:Pe(ce.value)},null,4),i("span",Sf,v(k("volume-high")),1)],64)):c.value==="playback"?(p(),h(U,{key:3},[i("span",Cf,v(k("skip-previous")),1),i("span",Tf,v(k(O.value?"pause":"play")||k("play")),1),i("span",Af,v(k("skip-next")),1)],64)):c.value==="buttons"&&y.value==="cover"?(p(),h(U,{key:4},[i("span",Ef,v(k("arrow-expand-horizontal")),1),i("span",Pf,v(k("stop")),1),i("span",Of,v(k("arrow-collapse-horizontal")),1)],64)):c.value==="run"?(p(),h("span",If,v({scene:"Activate",script:"Run"}[y.value]||"Press"),1)):(p(),h("span",{key:6,class:"range",style:Pe({background:`linear-gradient(to right, #2196f3 ${j.value}%, #d3e8fb ${j.value}%)`})},null,4))])):R("",!0)],64)):o.value?(p(),h(U,{key:3},[i("span",Mf,[i("span",{class:ie(["ic mdi",{lit:A.value}])},v(m(ke)(m(Bn)(e.tile))),3),i("span",Lf,[i("span",Df,v(n.value),1),l.value?(p(),h("span",Ff,"Page "+v(l.value)+" ›",1)):d.value==="watch"?(p(),h("span",Nf,[Z(v(X.value),1),w.value&&!V.value?(p(),h("small",Rf,v(w.value),1)):R("",!0)])):Me.value?(p(),h("span",{key:2,class:ie(["st",{off:V.value}])},v(Me.value),3)):R("",!0)])]),e.tile.options?.inline==="slider"?(p(),h("span",{key:0,class:"mini-slider",style:Pe(z.value)},null,4)):R("",!0),c.value?(p(),h("span",Hf,[c.value==="toggle"?(p(),h("span",{key:0,class:ie(["tog",{off:!O.value}])},null,2)):c.value==="setpoint"?(p(),h("span",Uf,[i("span",jf,v(k("minus")||"−"),1),i("b",null,v(de.value),1),i("span",Bf,v(k("plus")||"+"),1)])):c.value==="stepper"&&y.value.endsWith("select")?(p(),h(U,{key:2},[i("span",Vf,v(k("chevron-left")),1),i("span",Wf,v(k("chevron-right")),1)],64)):c.value==="stepper"?(p(),h("span",Kf,[i("span",qf,v(k("minus")||"−"),1),i("b",null,v(X.value),1),i("span",Yf,v(k("plus")||"+"),1)])):c.value==="mode"?(p(),h(U,{key:4},[i("span",zf,v(k("power")),1),i("span",Jf,v(k("fire")),1),i("span",Gf,v(k("snowflake")),1)],64)):c.value==="volume"?(p(),h(U,{key:5},[i("span",{class:"range",style:Pe(ce.value)},null,4),i("span",Xf,v(k("volume-high")),1)],64)):c.value==="playback"?(p(),h(U,{key:6},[i("span",Zf,v(k("skip-previous")),1),i("span",Qf,v(k(O.value?"pause":"play")||k("play")),1),i("span",ep,v(k("skip-next")),1)],64)):c.value==="buttons"&&y.value==="cover"?(p(),h(U,{key:7},[i("span",tp,v(k("arrow-expand-horizontal")),1),i("span",np,v(k("stop")),1),i("span",sp,v(k("arrow-collapse-horizontal")),1)],64)):c.value==="buttons"&&y.value==="vacuum"?(p(),h(U,{key:8},[i("span",op,v(k("play")),1),i("span",ip,v(k("stop")),1),i("span",lp,v(k("home-map-marker")),1)],64)):c.value==="buttons"&&y.value==="timer"?(p(),h(U,{key:9},[i("span",ap,v(k("play")),1),i("span",rp,v(k("close")),1)],64)):c.value==="run"?(p(),h("span",up,v({scene:"Activate",script:"Run"}[y.value]||"Press"),1)):(p(),h("span",{key:11,class:"range",style:Pe({background:`linear-gradient(to right, #2196f3 ${j.value}%, #d3e8fb ${j.value}%)`})},null,4))])):R("",!0)],64)):(p(),h(U,{key:4},[i("span",{class:ie(["head",{top:d.value==="watch"}])},[i("span",{class:ie(["ic mdi",{lit:A.value}])},v(m(ke)(m(Bn)(e.tile))),3),i("span",cp,[i("span",dp,v(n.value),1),l.value?(p(),h("span",fp,"Page "+v(l.value)+" ›",1)):d.value!=="watch"&&Me.value?(p(),h("span",{key:1,class:ie(["st",{off:V.value}])},v(Me.value),3)):R("",!0)])],2),d.value==="watch"?(p(),h("span",pp,[Z(v(X.value),1),w.value&&!V.value?(p(),h("small",hp,v(w.value),1)):R("",!0)])):R("",!0),e.tile.options?.inline==="slider"?(p(),h("span",{key:1,class:"mini-slider",style:Pe(z.value)},null,4)):R("",!0)],64)),E.value?(p(),h("button",{key:5,type:"button",class:"remove",title:"Remove tile","aria-label":`Remove ${n.value}`,onClick:K[0]||(K[0]=dt(B=>m(Ua)(e.tile),["stop"]))},"✕",8,vp)):R("",!0)],46,nf)),[[m(Ka),{kind:"tile",tile:e.tile}]])}}),yp=["viewBox","aria-label"],gp=["y"],bp=["cx","cy","r","stroke","stroke-width"],wp=["x1","y1","x2","y2","stroke","stroke-width"],_p=["x","y","fill"],kp=["x","y"],za=Ee({__name:"TopbarSvg",props:{items:{},nameText:{},single:{type:Boolean}},setup(e,{expose:t}){const n=e,s=$(()=>{u.fontsVersion,u.now,u.topbarPreviews;const x=n.items||Vt();return Sa(x,Na.value,n.nameText??(u.layout?.title||"Home"),qt)}),o=$(()=>s.value.metrics),l=$(()=>n.single?Math.round(o.value.text*1.4):o.value.top+Math.round(o.value.name*.45)),a=$(()=>n.single?Math.round(o.value.text*1.05):o.value.top),r=$(()=>{if(!n.single)return s.value.placed;const x=s.value.parts[0];return x&&(x.x=0),x?[x]:[]}),d=$(()=>n.single?`-2 0 ${Math.max(1,r.value[0]?.width||1)+4} ${l.value}`:`0 0 ${o.value.width} ${l.value}`),f=$(()=>a.value+(s.value.zero.top+s.value.zero.bottom)/2),c=$(()=>Oc(s.value.nameText,s.value.fonts.name,Math.min(s.value.natural,s.value.nameRoom))),y=$(()=>new Date(u.now));function b(x){const S=y.value;return[[(S.getHours()%12+S.getMinutes()/60)*30,x*.24],[S.getMinutes()*6,x*.34]].map(([M,G])=>{const C=M*Math.PI/180;return{x2:G*Math.sin(C),y2:-G*Math.cos(C)}})}const k=x=>x.x-x.icon.ink.left,I=x=>(x.icon?x.x+(x.icon.ink.right-x.icon.ink.left)+s.value.gaps.icon:x.x)-x.text.ink.left,E=$(()=>`${(Math.max(1,r.value[0]?.width||1)+4)*(26/o.value.icon)}px`);return t({lay:s}),(x,S)=>(p(),h("svg",{viewBox:d.value,role:"img","aria-label":e.single?"How it looks on the screen":`Top bar: ${s.value.nameText}`,style:Pe(e.single?{width:E.value}:void 0)},[e.single?R("",!0):(p(),h("text",{key:0,x:"0",y:o.value.top,fill:"#1b1b1b",style:Pe({font:s.value.fonts.name})},v(c.value),13,gp)),i("g",null,[(p(!0),h(U,null,le(r.value,M=>(p(),h(U,{key:M.index},[M.dial?(p(),h(U,{key:0},[i("circle",{cx:M.x+M.dial/2,cy:f.value,r:M.dial/2-Math.max(1,Math.round(M.dial/10))/2,fill:"none",stroke:M.view.color||"#46525e","stroke-width":Math.max(1,Math.round(M.dial/10))},null,8,bp),(p(!0),h(U,null,le(b(M.dial),(G,C)=>(p(),h("line",{key:C,x1:M.x+M.dial/2,y1:f.value,x2:M.x+M.dial/2+G.x2,y2:f.value+G.y2,stroke:M.view.color||"#46525e","stroke-width":Math.max(1,Math.round(M.dial*.075)),"stroke-linecap":"round"},null,8,wp))),128))],64)):(p(),h(U,{key:1},[M.icon?(p(),h("text",{key:0,x:k(M),y:f.value-(M.icon.ink.top+M.icon.ink.bottom)/2,fill:M.view.color||"#46525e",style:Pe({font:s.value.fonts.icon})},v(M.icon.glyph),13,_p)):R("",!0),M.text?(p(),h("text",{key:1,x:I(M),y:a.value,fill:"#46525e",style:Pe({font:s.value.fonts.text})},v(M.text.value),13,kp)):R("",!0)],64))],64))),128))])],12,yp))}}),$p={class:"page"},xp={class:"page-label"},Sp={key:1},Cp={class:"tiles"},Tp=["data-slot","aria-label","onClick"],Ap=Ee({__name:"DevicePage",props:{page:{},entries:{},pages:{},moving:{}},setup(e){const t=e,n=$(()=>new Map(t.entries.map(f=>[f.slot,f]))),s=$(()=>new Set(t.entries.flatMap(f=>mn(f.slot,Je(f.tile)).slice(1)))),o=$(()=>Array.from({length:Le},(f,c)=>t.page*Le+c).filter(f=>!s.value.has(f))),l=$(()=>t.pages>1&&!t.entries.some(f=>ht(f.slot)===t.page)),a=$(()=>u.inspector?.kind==="bar"||u.inspector?.kind==="bar-add"),r=$(()=>t.entries.filter(f=>ht(f.slot)===t.page).reduce((f,c)=>f+fi(Je(c.tile)),0));function d(f){const c=u.insertAt===f;u.insertAt=c?-1:f,u.insertAt>=0&&document.querySelector("#search")?.focus()}return(f,c)=>(p(),h("div",$p,[i("div",xp,[i("span",null,"Page "+v(e.page+1),1),l.value?(p(),h("button",{key:0,type:"button",class:"btn mini",title:"The pages after this one shift up one slot",onClick:c[0]||(c[0]=y=>m(Gc)(e.page))},"Remove page")):(p(),h("span",Sp,v(r.value)+" / "+v(m(Le)),1))]),i("div",{class:ie(["device",{cyd:!m(pi)}])},[i("div",{class:ie(["bar-wrap",{selected:a.value}]),title:"Edit top bar",role:"button",tabindex:"0",onClick:c[1]||(c[1]=y=>m(Lt)(0)),onKeydown:c[2]||(c[2]=$a(dt(y=>m(Lt)(0),["prevent"]),["enter"]))},[_e(za)],34),i("div",Cp,[(p(!0),h(U,null,le(o.value,y=>(p(),h(U,{key:y},[n.value.get(y)?(p(),Xe(mp,{key:0,tile:n.value.get(y).tile,slot:y,placeholder:n.value.get(y).tile===e.moving},null,8,["tile","slot","placeholder"])):(p(),h("button",{key:1,type:"button",class:ie(["cell",{"insert-here":m(u).insertAt===y}]),"data-slot":y,title:"Empty slot. Click to add a tile here, or drag one over.","aria-label":`Empty slot ${y%m(Le)+1} on page ${e.page+1}: add the next tile here`,onClick:b=>d(y)},[c[3]||(c[3]=i("span",null,"+",-1)),i("small",null,v(m(u).insertAt===y?"Next tile goes here":"Empty"),1)],10,Tp))],64))),128))])],2)]))}}),Ep={class:"library",id:"library"},Pp={class:"lib-head"},Op={class:"lib-title"},Ip={class:"filters",id:"filters"},Mp=["aria-pressed","onClick"],Lp={class:"lib-row"},Dp=["value"],Fp=["aria-pressed"],Np={class:"lib-list",id:"results","aria-live":"polite"},Rp=["title","disabled","onClick"],Hp={class:"tx"},Up={key:0,class:"hint"},jp={key:1,class:"hint"},Bp={class:"lib-foot"},Vp=Ee({__name:"Library",setup(e){const t=[["","All"],["light","Lights"],["climate","Climate"],["switch","Switches"],["binary_sensor","Status"],["button","Actions"],["script","Scripts"],["fan","Fans"],["cover","Covers"],["scene","Scenes"],["vacuum","Vacuum"],["sensor","Sensors"],["media_player","Media"],["weather","Weather"],["number","Values"],["select","Selects"],["person","People"],["timer","Timers"],["screen","Screen"]],n={switch:"input_boolean",number:"input_number",select:"input_select",weather:"sun",button:"input_button"},s=$(()=>new Set(u.layout?.tiles.map(c=>c.entity)||[])),o=c=>s.value.has(c)&&!no(c),l=$(()=>[...new Set(u.inventory.entities.map(c=>c.area).filter(c=>!!c))].sort((c,y)=>c.localeCompare(y))),a=$(()=>{const c=u.search.toLocaleLowerCase(),y=u.filter,b=u.room;return[...u.inventory.builtin||[],...u.inventory.entities].filter(k=>k.tile!==!1&&(pi.value||!["camera","image"].includes(k.id.split(".")[0]))&&(!y||k.id.startsWith(y+".")||n[y]===k.id.split(".")[0])&&(!b||k.area===b)&&(!u.hidePlaced||!o(k.id))&&`${k.name} ${k.id} ${k.device||""} ${k.area||""}`.toLocaleLowerCase().includes(c))}),r=$(()=>(u.layout?.tiles.length||0)>=yn.value),d=$(()=>u.inventory.entities.length),f=c=>{const y=c.id.split(".")[0];return c.state==="unavailable"||c.state==="unknown"?"gone":["light","switch","input_boolean","fan"].includes(y)&&c.state==="on"?"on":""};return(c,y)=>(p(),h("aside",Ep,[i("div",Pp,[i("div",Op,[y[3]||(y[3]=Z("Library ",-1)),i("small",null,v(d.value)+" entities",1)]),we(i("input",{id:"search","onUpdate:modelValue":y[0]||(y[0]=b=>m(u).search=b),type:"search",placeholder:"Search by name, device, or room…",autocomplete:"off","aria-label":"Add an entity"},null,512),[[Fe,m(u).search]]),i("div",Ip,[(p(),h(U,null,le(t,([b,k])=>i("button",{key:b,type:"button","aria-pressed":m(u).filter===b?"true":"false",onClick:I=>m(u).filter=b},[b?(p(),h("span",{key:0,class:"domain-icon",style:Pe({color:m(xt)(b+".")[2],background:m(xt)(b+".")[3]}),"aria-hidden":"true"},v(m(xt)(b+".")[1]),5)):R("",!0),Z(v(k),1)],8,Mp)),64))]),i("div",Lp,[we(i("select",{id:"room","onUpdate:modelValue":y[1]||(y[1]=b=>m(u).room=b),"aria-label":"Room"},[y[4]||(y[4]=i("option",{value:""},"All rooms",-1)),(p(!0),h(U,null,le(l.value,b=>(p(),h("option",{key:b,value:b},v(b),9,Dp))),128))],512),[[vn,m(u).room]]),i("button",{type:"button",class:"chip-toggle",id:"hide-placed","aria-pressed":m(u).hidePlaced?"true":"false",title:"Hide what is already on this screen",onClick:y[2]||(y[2]=b=>m(u).hidePlaced=!m(u).hidePlaced)},"Hide placed",8,Fp)])]),i("div",Np,[(p(!0),h(U,null,le(a.value.slice(0,80),b=>we((p(),h("button",{key:b.id,type:"button",class:"ent",title:b.id,disabled:o(b.id)||r.value,onClick:k=>m(Ha)(b.id)},[i("span",{class:ie(["av mdi",f(b)]),style:Pe({color:m(xt)(b.id)[2],background:m(xt)(b.id)[3]})},v(m(u).inventory.icons?m(ke)(m(sn)(b.id)):m(xt)(b.id)[1]),7),i("span",Hp,[i("b",null,v(b.name),1),i("small",null,v([m(xt)(b.id)[0],b.area,b.device].filter(Boolean).join(" · ")),1)]),i("span",{class:ie(["add",{done:o(b.id)}])},v(o(b.id)?"✓":"+"),3)],8,Rp)),[[m(Ka),{kind:"entity",id:b.id}]])),128)),a.value.length?a.value.length>80?(p(),h("p",jp,v(a.value.length)+" results. Keep typing to narrow it down.",1)):R("",!0):(p(),h("p",Up,v(m(u).hidePlaced&&!m(u).search&&!m(u).filter&&!m(u).room?"Everything here is already on this screen.":"No entities found. Try a different name, room or filter."),1))]),i("div",Bp,v(r.value?`This screen holds ${m(yn)} tiles${m(yn)<48?"; update its firmware for 48":""}.`:"Drag onto a page, or tap + to add to the first free slot. ⌘K searches everything."),1)]))}}),Wp={class:"canvas-head"},Kp={id:"count"},qp={key:0,id:"no-tiles"},Yp={key:1},zp={key:2,id:"positions-hint",class:"warn"},Jp={class:"pages",id:"layout-preview","aria-label":"Screen layout"},Gp={class:"page-label"},Xp=["tabindex"],Zp=Ee({__name:"LayoutView",setup(e){const t=$(()=>u.layout),n=$(()=>u.drag.preview||Ve(t.value)),s=$(()=>gt(n.value,t.value.pages)),o=$(()=>Zc()),l=$(()=>s.valueDc(t.value.tiles)&&!Zt(0,2,26)?`Empty slots and fixed positions work from firmware 0.2.26. This screen (firmware ${lt.value?.firmware||"unknown"}) shifts the tiles up to the first free slot until that update.`:"");function r(d){d.target.closest(".device, .page-label, .canvas-head")||u.inspector&&He()}return(d,f)=>(p(),h(U,null,[i("div",{class:"canvas",id:"canvas",onClick:r},[i("div",Wp,[i("b",Kp,v(s.value)+" "+v(s.value===1?"page":"pages")+" · "+v(t.value.tiles.length)+" / "+v(m(yn))+" tiles",1),t.value.tiles.length?(p(),h("span",Yp,"Tap the top bar or a tile to change it. Drag to move; drop on the next page for a new one.")):(p(),h("span",qp,"Add your first light, scene, or device from the library.")),a.value?(p(),h("span",zp,v(a.value),1)):R("",!0)]),i("div",Jp,[(p(!0),h(U,null,le(o.value,c=>(p(),Xe(Ap,{key:c,page:c-1,entries:n.value,pages:s.value,moving:m(u).drag.moving},null,8,["page","entries","pages","moving"]))),128)),i("div",{class:ie(["page ghost",{disabled:!l.value}])},[i("div",Gp,[i("span",null,"Page "+v(o.value+1),1)]),i("div",{class:ie(["device",{cyd:m(lt)?.board!=="guition"}]),id:"add-page",role:"button",tabindex:l.value?0:-1,onClick:f[0]||(f[0]=c=>l.value&&m(ol)()),onKeydown:f[1]||(f[1]=$a(dt(c=>l.value&&m(ol)(),["prevent"]),["enter"]))},v(l.value?"+ Add page":`${m(Bt)} pages is the most`),43,Xp)],2)])]),_e(Vp)],64))}}),Qp={class:"status",id:"settings-status",role:"status"},eh={key:0,class:"set-grid",id:"settings-groups"},th={class:"mdi"},nh=["data-setting","title","onClick"],sh=["id"],oh={class:"s-control"},ih=["id","aria-checked","aria-labelledby","disabled","onClick"],lh=["aria-labelledby"],ah=["aria-pressed","disabled","onClick"],rh={key:2,class:"step"},uh=["aria-label","disabled","onPointerdown","onClick"],ch={class:"mdi"},dh=["id","aria-labelledby"],fh=["aria-label","disabled","onPointerdown","onClick"],ph={class:"mdi"},hh=Ee({__name:"SettingsTab",setup(e){const t=$(()=>vi()),n=$(()=>On()),s=$(()=>t.value?.owner==="screen"&&!lt.value?.online),o=$(()=>ld.map(I=>({...I,rows:I.rows.filter(E=>t.value?.keys.includes(E.key))})).filter(I=>I.rows.length)),l=I=>s.value||!!t.value?.unavailable.includes(I.key),a=I=>I.needs?!!n.value[I.needs]:!0,r=$(()=>s.value?"This screen is offline. Its settings can change once it's back.":u.settingPending?"Saving…":t.value?.owner==="screen"?"Changes apply on the screen at once, and show up here when they change there.":"Changes apply at once. Firmware 0.2.49 lets the screen keep them itself."),d=(I,E)=>{const x=n.value[I.key];return l(I)||!a(I)||x===null||x===void 0?!0:I.kind!=="moment"&&ul(I,x,E,!1,n.value)===x},f=new WeakMap;function c(I,E,x){const S=On(),M=ul(I,S[I.key],E,x,S);M!==S[I.key]&&Wn(I.key,M,600)}function y(I,E,x){const S=I.currentTarget;if(S.disabled||I.button!==0)return;const M={timer:0,held:!1};f.set(S,M);let G=0;M.timer=window.setTimeout(function C(){M.held=!0,G+=1,c(E,x,G>5),M.timer=window.setTimeout(C,180)},450)}function b(I){const E=f.get(I.currentTarget);E&&clearTimeout(E.timer)}function k(I,E,x){const S=f.get(I.currentTarget);if(S?.held){S.held=!1;return}c(E,x,!1)}return(I,E)=>(p(),h("div",{class:ie(["settings",{offline:s.value}]),id:"general-settings"},[i("span",Qp,v(r.value),1),t.value?(p(),h("div",eh,[(p(!0),h(U,null,le(o.value,x=>(p(),h("section",{key:x.title,class:"set-card"},[i("h4",null,[i("span",th,v(m(ke)(x.icon)),1),Z(v(x.title),1)]),(p(!0),h(U,null,le(x.rows,S=>(p(),h("div",{key:S.key,class:ie(["srow",[`setting-${S.kind}`,{inactive:!a(S)||l(S)}]]),"data-setting":S.key,title:l(S)&&!s.value?"This entity is off in Home Assistant, or the screen is restarting.":"",onClick:M=>S.kind==="toggle"&&M.target.closest(".srow")===M.currentTarget&&!M.target.closest("button")&&!l(S)&&m(Wn)(S.key,!n.value[S.key],150)},[i("span",{class:"s-label",id:`setting-label-${S.key}`},v(S.label),9,sh),i("div",oh,[S.kind==="toggle"?(p(),h("button",{key:0,type:"button",class:ie(["switch",{unknown:n.value[S.key]===null||n.value[S.key]===void 0}]),role:"switch",id:`setting-${S.key}`,"aria-checked":n.value[S.key]?"true":"false","aria-labelledby":`setting-label-${S.key}`,disabled:l(S),onClick:dt(M=>m(Wn)(S.key,!n.value[S.key],150),["stop"])},null,10,ih)):S.kind==="choice"?(p(),h("div",{key:1,class:"seg",role:"group","aria-labelledby":`setting-label-${S.key}`},[(p(!0),h(U,null,le(S.options,([M,G])=>(p(),h("button",{key:String(M),type:"button","aria-pressed":n.value[S.key]===M?"true":"false",disabled:l(S),onClick:C=>m(Wn)(S.key,M,150)},v(G),9,ah))),128))],8,lh)):(p(),h("div",rh,[i("button",{type:"button","aria-label":`${S.label} lower`,disabled:d(S,-1),onPointerdown:M=>y(M,S,-1),onPointerup:b,onPointercancel:b,onPointerleave:b,onClick:M=>k(M,S,-1)},[i("span",ch,v(m(ke)("F0374")),1)],40,uh),i("output",{id:`setting-${S.key}`,"aria-labelledby":`setting-label-${S.key}`},v(m(cd)(S,n.value)),9,dh),i("button",{type:"button","aria-label":`${S.label} higher`,disabled:d(S,1),onPointerdown:M=>y(M,S,1),onPointerup:b,onPointercancel:b,onPointerleave:b,onClick:M=>k(M,S,1)},[i("span",ph,v(m(ke)("F0415")),1)],40,fh)]))])],10,nh))),128))]))),128))])):R("",!0)],2))}}),vh={class:"seg wrap",role:"group"},mh=["disabled","aria-pressed","onClick"],tt=Ee({__name:"Segmented",props:{choices:{},value:{},disabled:{type:Boolean}},emits:["pick"],setup(e,{emit:t}){const n=t;return(s,o)=>(p(),h("div",vh,[(p(!0),h(U,null,le(e.choices,([l,a])=>(p(),h("button",{key:String(l),type:"button",disabled:e.disabled,"aria-pressed":String(l)===String(e.value)?"true":"false",onClick:r=>n("pick",l)},v(a),9,mh))),128))]))}}),yh={class:"f"},gh={key:0},bh={key:1,class:"warn"},wh=["aria-expanded"],_h={class:"tx"},kh={key:0,class:"mono"},$h={class:"link"},xh={key:0,class:"picker"},Sh={class:"action-list"},Ch=["aria-pressed","onClick"],Th={key:0,class:"hint"},Ah={key:1,class:"warn"},Eh={class:"f-label"},Ph=["aria-label","value","onChange"],Oh=["value"],Ih={key:3,class:"action-number"},Mh=["aria-label","value","onInput"],Lh=["onClick"],Dh={key:4,class:"action-number"},Fh=["aria-label","min","max","step","value","placeholder","onInput"],Nh={key:0},Rh=["aria-label","value","placeholder","onInput"],Hh={key:6},Uh={key:0,class:"warn"},jh={key:3},Bh=Ee({__name:"ActionPicker",props:{tile:{}},setup(e){const t=e,n=$(()=>u.entityActions[t.tile.entity]),s=$(()=>t.tile.options?.action),o=$(()=>n.value?.find(C=>C.action===s.value?.action)),l=$(()=>u.actionPickerOpen||!s.value),a=$(()=>{const C=u.actionSearch.trim().toLocaleLowerCase();return(n.value||[]).filter(V=>`${V.name} ${V.action} ${V.description}`.toLocaleLowerCase().includes(C))});n.value===void 0&&Yc(t.tile.entity);function r(C){const V=C.action===s.value?.action;t.tile.options={...t.tile.options,action:V?s.value:{action:C.action}},u.actionPickerOpen=!1,Ze()}function d(C,V){const O={...t.tile.options?.action?.data||{}};V===void 0?delete O[C]:O[C]=V,t.tile.options={...t.tile.options,action:{action:s.value.action,...Object.keys(O).length?{data:O}:{}}},Ze()}const f=$(()=>{if(!o.value)return[];const C=t.tile.options?.action?.data||{};return o.value.fields.filter(V=>V.required&&C[V.key]===void 0).map(V=>V.name)}),c=C=>Object.keys(C.selector||{})[0]||"text",y=C=>(C.selector||{})[c(C)]||{},b=C=>C.example===void 0?"":typeof C.example=="string"?C.example:JSON.stringify(C.example);function k(C){const V=y(C),O=c(C);return Array.isArray(C.options)?C.options.map(A=>[A,A]):O==="select"&&Array.isArray(V.options)&&!V.multiple?V.options.map(A=>A&&typeof A=="object"?[String(A.value),String(A.label??A.value)]:[String(A),String(A)]):null}const I=C=>"#"+C.map(V=>Math.max(0,Math.min(255,Number(V)||0)).toString(16).padStart(2,"0")).join(""),E=C=>Array.isArray(C)&&C.length===3?I(C):"#ffffff",x=C=>C===void 0?"":typeof C=="string"?C:JSON.stringify(C);function S(C,V){const O=V.trim();if(!O)return d(C.key,void 0);if(c(C)==="text")return d(C.key,V);try{d(C.key,JSON.parse(O))}catch{d(C.key,V)}}function M(C,V){d(C.key,V===""||!Number.isFinite(Number(V))?void 0:Number(V))}const G=C=>y(C).unit_of_measurement||c(C)==="color_temp"&&y(C).unit||"";return(C,V)=>(p(),h("div",yh,[V[3]||(V[3]=i("span",{class:"f-label"},"Action",-1)),n.value===void 0?(p(),h("small",gh,"Asking Home Assistant which actions this entity has…")):n.value===null?(p(),h("small",bh,"Home Assistant isn't answering right now. Try again in a moment.")):(p(),h(U,{key:2},[i("button",{type:"button",class:"row","aria-expanded":l.value?"true":"false",onClick:V[0]||(V[0]=O=>m(u).actionPickerOpen=!l.value)},[i("span",_h,[i("b",null,v(o.value?o.value.name:s.value?s.value.action:"Choose an action"),1),s.value?(p(),h("small",kh,v(s.value.action),1)):R("",!0)]),i("span",$h,v(l.value?"Close":"Change"),1)],8,wh),l.value?(p(),h("div",xh,[we(i("input",{"onUpdate:modelValue":V[1]||(V[1]=O=>m(u).actionSearch=O),type:"search",placeholder:"Search, for example toggle, position, or play","aria-label":"Search for an action"},null,512),[[Fe,m(u).actionSearch]]),i("div",Sh,[(p(!0),h(U,null,le(a.value,O=>(p(),h("button",{key:O.action,type:"button",class:"action-choice","aria-pressed":O.action===s.value?.action?"true":"false",onClick:A=>r(O)},[i("strong",null,v(O.name),1),i("small",null,v(O.description?`${O.action} · ${O.description}`:O.action),1)],8,Ch))),128)),a.value.length?R("",!0):(p(),h("p",Th,"No action found."))])])):R("",!0),s.value&&!o.value?(p(),h("small",Ah,"Home Assistant doesn't offer this action for this entity any more, so a tap does nothing. Choose another one.")):R("",!0),o.value&&!l.value?(p(),h(U,{key:2},[(p(!0),h(U,null,le(o.value.fields,O=>(p(),h("div",{key:O.key,class:"f"},[i("span",Eh,v(O.required?O.name:`${O.name} (optional)`),1),c(O)==="boolean"?(p(),Xe(tt,{key:0,choices:[["","Not set"],["true","On"],["false","Off"]],value:s.value?.data?.[O.key]===void 0?"":String(s.value?.data?.[O.key]),onPick:A=>d(O.key,A===""?void 0:A==="true")},null,8,["value","onPick"])):k(O)&&k(O).length<=4?(p(),Xe(tt,{key:1,choices:[["","Not set"],...k(O)],value:s.value?.data?.[O.key]===void 0?"":String(s.value?.data?.[O.key]),onPick:A=>d(O.key,A===""?void 0:A)},null,8,["choices","value","onPick"])):k(O)?(p(),h("select",{key:2,"aria-label":O.name,value:String(s.value?.data?.[O.key]??""),onChange:A=>d(O.key,A.target.value===""?void 0:A.target.value)},[V[2]||(V[2]=i("option",{value:""},"Not set",-1)),(p(!0),h(U,null,le(k(O),([A,w])=>(p(),h("option",{key:A,value:A},v(w),9,Oh))),128))],40,Ph)):c(O)==="color_rgb"?(p(),h("div",Ih,[i("input",{type:"color","aria-label":O.name,value:E(s.value?.data?.[O.key]),onInput:A=>d(O.key,[1,3,5].map(w=>parseInt(A.target.value.slice(w,w+2),16)))},null,40,Mh),i("button",{type:"button",class:"btn quiet mini",onClick:A=>d(O.key,void 0)},"Not set",8,Lh)])):c(O)==="number"||c(O)==="color_temp"?(p(),h("div",Dh,[i("input",{type:"number","aria-label":O.name,min:y(O).min,max:y(O).max,step:y(O).step==="any"?"any":y(O).step,value:s.value?.data?.[O.key]??"",placeholder:b(O),onInput:A=>M(O,A.target.value)},null,40,Fh),G(O)?(p(),h("span",Nh,v(G(O)),1)):R("",!0)])):(p(),h("input",{key:5,type:"text","aria-label":O.name,value:x(s.value?.data?.[O.key]),placeholder:b(O),onInput:A=>S(O,A.target.value)},null,40,Rh)),O.description?(p(),h("small",Hh,v(O.description),1)):R("",!0)]))),128)),f.value.length?(p(),h("small",Uh,"Home Assistant needs "+v(f.value.join(", "))+".",1)):R("",!0)],64)):R("",!0),m(Zt)(0,2,58)?R("",!0):(p(),h("small",jh,"The screen performs an action from firmware 0.2.58: press Update on the screen. Until then a tap works as Automatic."))],64))]))}}),Vh={class:"f"},Wh=["aria-expanded"],Kh={class:"av mdi"},qh={class:"tx"},Yh={class:"link"},zh={key:0,class:"picker"},Jh=["aria-pressed","title"],Gh={class:"mdi"},Xh=["aria-pressed"],Zh={class:"icon-list"},Qh={class:"icon-grid"},ev=["title","aria-label","aria-pressed","onClick"],tv={class:"mdi"},nv={key:0,class:"hint"},sv={key:1},Ja=Ee({__name:"IconPicker",props:{selected:{},automatic:{},autoLabel:{},allowNone:{type:Boolean},note:{}},emits:["pick"],setup(e,{emit:t}){const n=e,s=t,o=pe(""),l=$(()=>vs(n.selected)),a=$(()=>n.selected==="none"?"":ke(l.value?.cp||n.automatic)),r=$(()=>n.selected==="none"?"No icon":l.value?.label||n.autoLabel),d=$(()=>{const f=o.value.trim().toLocaleLowerCase();return(u.inventory.icons?.groups||[]).map(c=>({label:c.label,icons:c.icons.filter(y=>!f||`${y.label} ${y.name.replaceAll("-"," ")} ${c.label}`.toLocaleLowerCase().includes(f))})).filter(c=>c.icons.length)});return(f,c)=>(p(),h("div",Vh,[c[5]||(c[5]=i("span",{class:"f-label"},"Icon",-1)),i("button",{type:"button",class:"row","aria-expanded":m(u).iconPickerOpen?"true":"false",onClick:c[0]||(c[0]=y=>m(u).iconPickerOpen=!m(u).iconPickerOpen)},[i("span",Kh,v(a.value),1),i("span",qh,[i("b",null,v(r.value),1)]),i("span",Yh,v(m(u).iconPickerOpen?"Close":"Change"),1)],8,Wh),m(u).iconPickerOpen?(p(),h("div",zh,[we(i("input",{"onUpdate:modelValue":c[1]||(c[1]=y=>o.value=y),type:"search",placeholder:"Search, for example lamp, music, or door","aria-label":"Search for an icon"},null,512),[[Fe,o.value]]),i("button",{type:"button",class:"icon-choice icon-auto","aria-pressed":e.selected==="auto"?"true":"false",title:e.autoLabel,onClick:c[2]||(c[2]=y=>s("pick","auto"))},[i("span",Gh,v(m(ke)(e.automatic)),1),i("span",null,v(e.autoLabel),1)],8,Jh),e.allowNone?(p(),h("button",{key:0,type:"button",class:"icon-choice icon-auto","aria-pressed":e.selected==="none"?"true":"false",title:"No icon",onClick:c[3]||(c[3]=y=>s("pick","none"))},[...c[4]||(c[4]=[i("span",{class:"mdi"},null,-1),i("span",null,"No icon, text only",-1)])],8,Xh)):R("",!0),i("div",Zh,[(p(!0),h(U,null,le(d.value,y=>(p(),h("section",{key:y.label},[i("small",null,v(y.label),1),i("div",Qh,[(p(!0),h(U,null,le(y.icons,b=>(p(),h("button",{key:b.name,type:"button",class:"icon-choice",title:b.label,"aria-label":`Icon: ${b.label}`,"aria-pressed":e.selected===b.name?"true":"false",onClick:k=>s("pick",b.name)},[i("span",tv,v(m(ke)(b.cp)),1)],8,ev))),128))])]))),128)),d.value.length?R("",!0):(p(),h("p",nv,"No icon found."))])])):R("",!0),e.note?(p(),h("small",sv,v(e.note),1)):R("",!0)]))}}),ov={class:"dr-head"},iv={class:"tx"},lv={class:"mono"},av={class:"dr-body"},rv={class:"f"},uv=["value","placeholder"],cv={key:1,class:"f"},dv={key:2,class:"f"},fv={key:0,class:"warn"},pv={class:"f"},hv={key:0},vv={key:3,class:"f"},mv={key:4,class:"f"},yv={key:5,class:"f"},gv={key:7,class:"f"},bv={key:0,class:"warn"},wv={key:8,class:"f"},_v={class:"f"},kv={class:"sw"},$v=["aria-label","aria-pressed","onClick"],xv={class:"dr-foot"},Sv=Ee({__name:"TileInspector",props:{tile:{}},setup(e){const t=e,n=$(()=>t.tile.entity.split(".")[0]),s=$(()=>hs(t.tile.entity)),o=$(()=>ps(t.tile.entity)),l=$(()=>u.layout?gt(Ve(u.layout),u.layout.pages):1),a=$(()=>ht(t.tile.slot)+1),r=K=>!u.layout?.tiles.some(B=>ht(B.slot)===K-1),d=$(()=>{const K=Array.from({length:Math.min(Bt,l.value+1)},(B,ne)=>ne+1);return o.value>K.length&&K.push(o.value),K.map(B=>[B,r(B)?`${B} (empty)`:String(B)])}),f=$(()=>nl.value?o.value>l.value?{text:`This screen has no page ${o.value}, so a tap opens its last page. Choose another page.`,warn:!0}:{text:"A tap on the tile opens that page.",warn:!1}:{text:"Navigation tiles need firmware 0.2.62: press Update on the screen first.",warn:!1}),c=$(()=>!u.layout?.tiles.some(K=>ve(K)!==ve(t.tile)&&ht(K.slot)===a.value-1)),y=$(()=>{const K=Array.from({length:l.value},(B,ne)=>[ne+1,String(ne+1)]);return l.valueo.value?[["single","Normal"],["wide","Double-width"]]:[["single","Normal"],["wide","Double-width"],["full","Full page"]]),k=$(()=>o.value?"":nl.value?"Full page: one big button that lights up while on; a slider, controls or a graph sit at the bottom of it.":"A full-page tile needs firmware 0.2.62: press Update on the screen first."),I=$(()=>u.capabilities[t.tile.entity]),E=(K,B)=>t.tile.options?.[K]??B,x=$(()=>E("display",n.value==="screen"?"digital":"standard")),S=$(()=>{const K=n.value==="screen"?[["digital","Digital clock"],["analog","Analog clock"]]:[["standard","Name and status"],["watch","Large value"]],B=I.value;return n.value==="weather"&&(!B||B.displays.includes("forecast")||x.value==="forecast")&&K.push(["forecast","Weather forecast"]),n.value==="sensor"&&(!B||B.displays.includes("graph")||x.value==="graph")&&K.push(["graph","Graph"]),n.value==="sun"&&K.push(["sunpath","Sun path"]),K}),M=$(()=>{const K=I.value;return K&&x.value==="graph"&&!K.displays.includes("graph")?"Home Assistant has no numbers for this entity, so the graph stays empty. Choose another display.":K&&x.value==="forecast"&&!K.displays.includes("forecast")?"This weather service has no daily forecast in Home Assistant. Choose another display.":""}),G=$(()=>E("size","single")),C=$(()=>u.inventory.controls?.[n.value]),V=$(()=>E("controls",G.value==="full"?"none":C.value?.default)),O=$(()=>{const K=I.value;return(C.value?.choices||[]).filter(B=>!K||B.key==="none"||B.key===V.value||K.controls.includes(B.key)).map(B=>[B.key,B.label])}),A=$(()=>{const K=I.value;return K&&V.value!=="none"&&!K.controls.includes(V.value)?{text:"Home Assistant doesn't offer this control for this entity, so it stays empty on the screen. Choose another one.",warn:!0}:{text:Zt(0,2,19)?G.value==="full"?"At the bottom of the full-page tile; a tap anywhere else works as set below.":"On the right of the double-width tile, like the rows in Home Assistant. Tapping the name works as set below.":"The screen shows direct control from firmware 0.2.19; until then the tile stays as it was.",warn:!1}}),w=$(()=>E("tap","auto")),T=$(()=>{const K=[["auto","Automatic"],["detail","Open control"],["none","View only"]];return((I.value?I.value.toggle:tl.includes(n.value))||w.value==="toggle")&&K.push(["toggle","On / off"]),K.push(["action","Perform action"]),K}),te=$(()=>w.value==="toggle"&&I.value&&!I.value.toggle?{text:"Home Assistant can't turn this on and off, so a tap does nothing. Choose another option.",warn:!0}:w.value==="toggle"&&!tl.includes(n.value)&&!Zt(0,2,58)?{text:"The screen switches this from firmware 0.2.58: press Update on the screen. Until then a tap opens its card.",warn:!1}:w.value==="toggle"?{text:"Hold the tile to open its card.",warn:!1}:null),$e=$(()=>E("inline","none")),Me=$(()=>Bc.includes(n.value)&&(!I.value||I.value.inline||$e.value==="slider")),X=$(()=>$e.value==="slider"&&I.value&&!I.value.inline),j=$(()=>E("history_hours",24)),z=$(()=>Object.entries(u.inventory.backgrounds||{})),ce=$(()=>!!u.inventory.entities.find(K=>K.id===t.tile.entity)?.icon),de=$(()=>!!u.inventory.icons&&(n.value!=="screen"||o.value>0)&&!["forecast","sunpath"].includes(x.value));function he(K){t.tile.name=K,Ze()}function Q(){u.inspector={kind:"inspect",entity:t.tile.entity}}return(K,B)=>(p(),h(U,null,[i("div",ov,[i("span",{class:"av mdi",style:Pe({color:m(xt)(e.tile.entity)[2],background:m(xt)(e.tile.entity)[3]})},v(m(ke)(m(Bn)(e.tile))),5),i("span",iv,[i("b",null,v(e.tile.name||s.value),1),i("small",lv,v(e.tile.entity),1)]),i("button",{type:"button",class:"icon-btn","aria-label":"Close",onClick:B[0]||(B[0]=(...ne)=>m(He)&&m(He)(...ne))},"✕")]),i("div",av,[i("div",rv,[B[12]||(B[12]=i("label",{class:"f-label",for:"tile-name"},"Name on the screen",-1)),i("input",{id:"tile-name",value:e.tile.name,placeholder:s.value,maxlength:"60",onInput:B[1]||(B[1]=ne=>he(ne.target.value))},null,40,uv)]),de.value?(p(),Xe(Ja,{key:0,selected:e.tile.options?.icon||"auto",automatic:m(sn)(e.tile.entity),"auto-label":`Automatic (${ce.value?"from Home Assistant":"default"})`,note:m(Zt)(0,2,18)?"":"The screen shows a chosen icon from firmware 0.2.18.",onPick:B[2]||(B[2]=ne=>m(Kt)(e.tile,"icon",ne))},null,8,["selected","automatic","auto-label","note"])):R("",!0),o.value?(p(),h("div",cv,[B[13]||(B[13]=i("span",{class:"f-label"},"Goes to page",-1)),_e(tt,{choices:d.value,value:o.value,onPick:B[3]||(B[3]=ne=>m(Qc)(e.tile,Number(ne)))},null,8,["choices","value"]),i("small",{class:ie({warn:f.value.warn})},v(f.value.text),3)])):(p(),h("div",dv,[B[14]||(B[14]=i("span",{class:"f-label"},"Display",-1)),_e(tt,{choices:S.value,value:x.value,onPick:B[4]||(B[4]=ne=>m(Kt)(e.tile,"display",ne))},null,8,["choices","value"]),M.value?(p(),h("small",fv,v(M.value),1)):R("",!0)])),i("div",pv,[B[15]||(B[15]=i("span",{class:"f-label"},"Size",-1)),_e(tt,{choices:b.value,value:G.value,onPick:B[5]||(B[5]=ne=>m(Kt)(e.tile,"size",ne))},null,8,["choices","value"]),k.value?(p(),h("small",hv,v(k.value),1)):R("",!0)]),y.value.length>1?(p(),h("div",vv,[B[16]||(B[16]=i("span",{class:"f-label"},"Page",-1)),_e(tt,{choices:y.value,value:a.value,onPick:B[6]||(B[6]=ne=>m(Xc)(e.tile,Number(ne)-1))},null,8,["choices","value"])])):R("",!0),C.value&&G.value!=="single"&&!o.value?(p(),h("div",mv,[B[17]||(B[17]=i("span",{class:"f-label"},"Direct control on the tile",-1)),_e(tt,{choices:O.value,value:V.value,onPick:B[7]||(B[7]=ne=>m(Kt)(e.tile,"controls",ne))},null,8,["choices","value"]),i("small",{class:ie({warn:A.value.warn})},v(A.value.text),3)])):R("",!0),n.value!=="screen"&&!o.value?(p(),h("div",yv,[B[18]||(B[18]=i("span",{class:"f-label"},"On tap",-1)),_e(tt,{choices:T.value,value:w.value,onPick:B[8]||(B[8]=ne=>m(Kt)(e.tile,"tap",ne))},null,8,["choices","value"]),te.value?(p(),h("small",{key:0,class:ie({warn:te.value.warn})},v(te.value.text),3)):R("",!0)])):R("",!0),n.value!=="screen"&&!o.value&&w.value==="action"?(p(),Xe(Bh,{key:6,tile:e.tile},null,8,["tile"])):R("",!0),Me.value&&!o.value?(p(),h("div",gv,[B[19]||(B[19]=i("span",{class:"f-label"},"Small slider on the tile",-1)),_e(tt,{choices:[["none","No"],["slider","Yes, control directly"]],value:$e.value,onPick:B[9]||(B[9]=ne=>m(Kt)(e.tile,"inline",ne))},null,8,["value"]),X.value?(p(),h("small",bv,"Home Assistant has nothing a slider can change for this entity. Choose No.")):R("",!0)])):R("",!0),n.value==="sensor"&&!o.value?(p(),h("div",wv,[B[20]||(B[20]=i("span",{class:"f-label"},"History",-1)),_e(tt,{choices:[[1,"1 hour"],[6,"6 hours"],[24,"24 hours"]],value:j.value,onPick:B[10]||(B[10]=ne=>m(Kt)(e.tile,"history_hours",Number(ne)))},null,8,["value"])])):R("",!0),i("div",_v,[B[21]||(B[21]=i("span",{class:"f-label"},"Pastel background",-1)),i("div",kv,[(p(!0),h(U,null,le(z.value,([ne,Qe])=>(p(),h("button",{key:ne,type:"button","aria-label":`Background: ${Qe.label}`,"aria-pressed":(e.tile.options?.background||"auto")===ne?"true":"false",onClick:Mn=>m(Kt)(e.tile,"background",ne)},[i("i",{class:ie(Qe.color?"":ne==="none"?"none":"auto"),style:Pe(Qe.color?{background:Qe.color}:void 0)},null,6),Z(v(Qe.label),1)],8,$v))),128))])])]),i("div",xv,[i("button",{type:"button",class:"btn danger",onClick:B[11]||(B[11]=ne=>m(Ua)(e.tile))},"Remove"),B[22]||(B[22]=i("span",{class:"spacer"},null,-1)),n.value!=="screen"?(p(),h("button",{key:0,type:"button",class:"btn quiet",onClick:Q},"Read current data")):R("",!0)])],64))}}),Cv={class:"dr-head"},Tv={class:"av mdi",style:{background:"var(--seg)",color:"var(--ink-2)"}},Av={class:"tx"},Ev={class:"dr-body"},Pv={class:"f"},Ov=["value"],Iv={class:"f"},Mv={class:"f-label",id:"topbar-caption"},Lv={style:{"text-transform":"none","letter-spacing":"0","font-weight":"500"}},Dv={class:"items",id:"topbar-chips",role:"list","aria-labelledby":"topbar-caption"},Fv=["data-index","aria-label","onPointerdown","onClick","onKeydown"],Nv={class:"tx"},Rv=["aria-label","onClick"],Hv=["disabled","title"],Uv={class:"live",id:"topbar-live"},jv={class:"f"},Bv={class:"f"},Vv={key:1,class:"f"},Wv={key:2},Kv={key:0,class:"dr-foot"},qv=["disabled"],Yv=["disabled"],zv=Ee({__name:"TopbarInspector",props:{index:{}},setup(e){const t=e,n=$(()=>Vt()),s=$(()=>n.value[t.index]),o=$(()=>(u.fontsVersion,u.now,u.topbarPreviews,Sa(n.value,Na.value,u.layout?.title||"Home",qt))),l=$(()=>o.value.dropped),a=$(()=>u.inventory.header?.min_firmware||"0.2.32"),r=$(()=>{const[X,j,z]=a.value.split(".").map(Number);return Zt(X,j,z)}),d=$(()=>r.value?l.value.size?"Not everything fits next to the name: the screen drops the dashed items. Remove one or choose a shorter name.":"Drag to reorder; tap an item to configure it.":`Sensors, the date, and the analog clock appear from firmware ${a.value}; until that update, this screen shows the name and, if the time is in the bar, the clock.`),f=(X,j)=>{const z=qt(X);return z.shown?l.value.has(j)?"Doesn't fit next to the name":z.analog?"Dial":z.text:"Hidden: not active right now"},c=X=>{const j=qt(X);return j.analog||X.type!=="entity"?vs(xa[X.type])?.cp:j.icon},y=X=>u.topbarAdded?.key===qe(X)&&Date.now()-u.topbarAdded.time<1200;function b(X){u.layout&&(u.layout.title=X,Ze())}function k(X){const j=[...n.value];j[t.index]={...j[t.index],...X},as(j)}const I=$(()=>s.value?qt(s.value).shown?l.value.has(t.index)?"Doesn't currently fit next to the name":"How it looks on the screen":"Hidden right now: not active":""),E=$(()=>On().clock_24h!==!1),x=$(()=>({clock:ui(E.value,new Date(u.now)),analog:"Small dial with the time",date:ci(new Date(u.now))})),S=pe({index:-1,active:!1,moved:!1});let M=null,G=0,C=null,V=0;function O(X,j){X.button!==0||X.target.closest(".x")||(X.pointerType!=="touch"&&X.preventDefault(),S.value={index:j,active:!1,moved:!1},M={x:X.clientX,y:X.clientY},C=X.pointerId,clearTimeout(G),X.pointerType==="touch"&&(G=window.setTimeout(A,260)),document.addEventListener("pointermove",T),document.addEventListener("pointerup",te),document.addEventListener("pointercancel",te))}function A(){S.value.active=!0,document.addEventListener("touchmove",w,{passive:!1})}function w(X){S.value.active&&X.preventDefault()}function T(X){if(X.pointerId!==C||!M)return;if(!S.value.active){const ce=Math.hypot(X.clientX-M.x,X.clientY-M.y);if(X.pointerType==="touch"){ce>10&&(clearTimeout(G),M=null);return}if(ce<6)return;A()}const j=[...document.querySelectorAll(".items .item[data-index]")];let z=S.value.index;if(j.forEach((ce,de)=>{const he=ce.getBoundingClientRect();deS.value.index&&X.clientY>he.top+he.height/2&&(z=Math.max(z,de))}),z!==S.value.index&&u.layout){const ce=[...n.value];ce.splice(z,0,...ce.splice(S.value.index,1)),u.layout.header={items:ce};const de=t.index===S.value.index;S.value.index=z,S.value.moved=!0,de&&Lt(z)}}function te(X){X.pointerId===C&&(clearTimeout(G),document.removeEventListener("pointermove",T),document.removeEventListener("pointerup",te),document.removeEventListener("pointercancel",te),document.removeEventListener("touchmove",w),S.value.active&&(V=Date.now()+400,S.value.moved&&Ze()),S.value={index:-1,active:!1,moved:!1},M=null,C=null)}function $e(X){Date.now()(p(),h(U,null,[i("div",Cv,[i("span",Tv,v(s.value?m(ke)(c(s.value)||"F0150"):m(ke)("F0150")),1),i("span",Av,[i("b",null,v(s.value?s.value.type==="entity"?m(hs)(s.value.entity):m(Vn)(s.value):"Top bar"),1),i("small",{class:ie({mono:s.value?.type==="entity"})},v(s.value?s.value.type==="entity"?s.value.entity:"From the screen itself, also works without Home Assistant":`${n.value.length} of ${m(zt)()} items`),3)]),i("button",{type:"button",class:"icon-btn","aria-label":"Close",onClick:j[0]||(j[0]=(...z)=>m(He)&&m(He)(...z))},"✕")]),i("div",Ev,[i("div",Pv,[j[10]||(j[10]=i("label",{class:"f-label",for:"title"},"Left: name",-1)),i("input",{id:"title",value:m(u).layout?.title,maxlength:"60",placeholder:"For example: Living room",onInput:j[1]||(j[1]=z=>b(z.target.value))},null,40,Ov)]),i("div",Iv,[i("span",Mv,[j[11]||(j[11]=Z("Right: time, sensors, or a clock ",-1)),i("span",Lv," · "+v(n.value.length)+" / "+v(m(zt)()),1)]),i("div",Dv,[(p(!0),h(U,null,le(n.value,(z,ce)=>(p(),h("div",{key:m(qe)(z)+ce,class:ie(["item",{selected:ce===e.index,"is-hidden":!m(qt)(z).shown,"is-overflow":l.value.has(ce),"just-added":y(z),"dragging-chip":S.value.active&&S.value.index===ce}]),role:"listitem",tabindex:"0","data-index":ce,"aria-label":`${m(Vn)(z)}, slot ${ce+1}. Enter: configure, arrows: move`,onPointerdown:de=>O(de,ce),onClick:de=>$e(ce),onKeydown:de=>Me(de,ce)},[j[12]||(j[12]=i("span",{class:"grip","aria-hidden":"true"},"⋮⋮",-1)),i("span",{class:"av mdi",style:Pe(m(qt)(z).color?{color:m(qt)(z).color}:void 0)},v(c(z)?m(ke)(c(z)):""),5),i("span",Nv,[i("b",null,v(m(Vn)(z)),1),i("small",null,v(f(z,ce)),1)]),i("button",{type:"button",class:"x","aria-label":`Remove ${m(Vn)(z)} from the top bar`,onClick:dt(de=>m(rl)(ce),["stop"])},"✕",8,Rv)],42,Fv))),128)),i("button",{type:"button",class:"ghost-btn",id:"topbar-add",disabled:n.value.length>=m(zt)(),title:n.value.length>=m(zt)()?`Maximum ${m(zt)()} items`:"Add the time, date, analog clock, or an entity",onClick:j[2]||(j[2]=(...z)=>m(il)&&m(il)(...z))},"+ Add time, date, or an entity",8,Hv)]),i("small",{id:"topbar-hint",class:ie({warn:l.value.size>0&&r.value})},v(d.value),3)]),s.value?(p(),h(U,{key:0},[i("div",Uv,[i("small",null,v(I.value),1),_e(za,{items:[s.value],single:""},null,8,["items"])]),s.value.type==="entity"?(p(),h(U,{key:0},[i("div",jv,[j[13]||(j[13]=i("span",{class:"f-label"},"What to show",-1)),_e(tt,{choices:(m(u).inventory.header?.contents||[]).map(z=>[z.key,z.label]),value:s.value.content,onPick:j[3]||(j[3]=z=>k({content:z}))},null,8,["choices","value"]),j[14]||(j[14]=i("small",null,"Last changed keeps counting on the screen itself: “Just now”, “5 min ago”, “Yesterday”.",-1))]),_e(Ja,{selected:s.value.icon||"auto",automatic:m(u).topbarPreviews[m(qe)(s.value)]?.auto_icon||m(sn)(s.value.entity),"auto-label":"Automatic (like Home Assistant)","allow-none":"",onPick:j[4]||(j[4]=z=>k({icon:z}))},null,8,["selected","automatic"]),i("div",Bv,[j[15]||(j[15]=i("span",{class:"f-label"},"Show",-1)),_e(tt,{choices:(m(u).inventory.header?.shows||[]).map(z=>[z.key,z.label]),value:s.value.show,onPick:j[5]||(j[5]=z=>k({show:z}))},null,8,["choices","value"]),j[16]||(j[16]=i("small",null,"Only when active hides the item as long as it's off, closed, away, or 0. Handy for an open door, a running washing machine, or who's home.",-1))])],64)):s.value.type!=="date"?(p(),h("div",Vv,[j[17]||(j[17]=i("span",{class:"f-label"},"Format",-1)),_e(tt,{choices:[["24","24 hour"],["12","12 hour"]],value:E.value?"24":"12",onPick:j[6]||(j[6]=z=>m(Wn)("clock_24h",z==="24",150))},null,8,["value"]),j[18]||(j[18]=i("small",null,"The screen's own clock setting: it applies at once to every clock on this screen, including the clock tiles.",-1))])):(p(),h("small",Wv,v(x.value.date)+" today. The date follows the screen's own clock.",1))],64)):R("",!0)]),s.value?(p(),h("div",Kv,[i("button",{type:"button",class:"btn danger",onClick:j[7]||(j[7]=z=>m(rl)(e.index))},"Remove"),j[19]||(j[19]=i("span",{class:"spacer"},null,-1)),i("button",{type:"button",class:"btn quiet",disabled:e.index===0,onClick:j[8]||(j[8]=z=>m(xo)(e.index,e.index-1)&&m(Lt)(e.index-1))},"↑ Up",8,qv),i("button",{type:"button",class:"btn quiet",disabled:e.index>=n.value.length-1,onClick:j[9]||(j[9]=z=>m(xo)(e.index,e.index+1)&&m(Lt)(e.index+1))},"↓ Down",8,Yv)])):R("",!0)],64))}}),Jv={class:"dr-head"},Gv={class:"tx"},Xv={class:"dr-body"},Zv={class:"f"},Qv={class:"options"},em=["disabled","onClick"],tm={class:"mdi"},nm={class:"tx"},sm={key:0,class:"f"},om={class:"options"},im=["disabled","onClick"],lm={class:"mdi"},am={class:"tx"},rm={class:"f"},um={class:"options"},cm=["disabled","onClick"],dm={class:"mdi"},fm={class:"tx"},pm={key:0,class:"hint"},hm={key:1,class:"hint"},vm={class:"dr-foot"},mm=Ee({__name:"TopbarAdd",setup(e){const t=pe(""),n=$(()=>new Set(Vt().map(qe))),s=$(()=>On().clock_24h!==!1),o=$(()=>({clock:ui(s.value,new Date(u.now)),analog:"Small dial with the time",date:ci(new Date(u.now))})),l=$(()=>u.inventory.header?.suggestions?.[u.selected||""]||[]),a=$(()=>{const d=t.value.trim().toLocaleLowerCase();return u.inventory.entities.filter(f=>`${f.name} ${f.id} ${f.area||""} ${f.device||""}`.toLocaleLowerCase().includes(d))}),r=d=>({type:"entity",entity:d,content:"state",icon:"auto",show:"always"});return(d,f)=>(p(),h(U,null,[i("div",Jv,[f[4]||(f[4]=i("span",{class:"av",style:{background:"var(--seg)",color:"var(--ink-2)","font-size":"18px"}},"+",-1)),i("span",Gv,[f[3]||(f[3]=i("b",null,"Add to the top bar",-1)),i("small",null,v(m(Vt)().length)+" of "+v(m(zt)())+" slots used",1)]),i("button",{type:"button",class:"icon-btn","aria-label":"Close",onClick:f[0]||(f[0]=(...c)=>m(He)&&m(He)(...c))},"✕")]),i("div",Xv,[i("div",Zv,[f[5]||(f[5]=i("span",{class:"f-label"},"From the screen itself",-1)),i("div",Qv,[(p(!0),h(U,null,le(m(u).inventory.header?.builtin||[],c=>(p(),h("button",{key:c.type,type:"button",class:"option",disabled:n.value.has(m(qe)({type:c.type})),onClick:y=>m(So)({type:c.type})},[i("span",tm,v(m(ke)(m(vs)(m(xa)[c.type])?.cp||"F0150")),1),i("span",nm,[i("strong",null,v(c.label),1),i("small",null,v(n.value.has(m(qe)({type:c.type}))?"Already added":o.value[c.type]),1)])],8,em))),128))])]),l.value.length?(p(),h("div",sm,[f[6]||(f[6]=i("span",{class:"f-label"},"Suggestions from Home Assistant",-1)),i("div",om,[(p(!0),h(U,null,le(l.value,c=>(p(),h("button",{key:m(qe)(c.item),type:"button",class:"option",disabled:n.value.has(m(qe)(c.item)),onClick:y=>m(So)(c.item)},[i("span",lm,v(m(ke)(c.icon||m(sn)(c.item.entity))),1),i("span",am,[i("strong",null,v(c.label),1),i("small",null,v(n.value.has(m(qe)(c.item))?"Already added":[c.name,c.area].filter(Boolean).join(" · ")),1)])],8,im))),128))])])):R("",!0),i("div",rm,[f[7]||(f[7]=i("label",{class:"f-label",for:"topbar-search"},"An entity",-1)),we(i("input",{id:"topbar-search","onUpdate:modelValue":f[1]||(f[1]=c=>t.value=c),type:"search",placeholder:"Search by name, room, or entity, e.g. temperature or door","aria-label":"Search for an entity for the top bar"},null,512),[[Fe,t.value]]),i("div",um,[(p(!0),h(U,null,le(a.value.slice(0,40),c=>(p(),h("button",{key:c.id,type:"button",class:"option",disabled:n.value.has(m(qe)(r(c.id))),onClick:y=>m(So)(r(c.id))},[i("span",dm,v(m(ke)(m(sn)(c.id))),1),i("span",fm,[i("strong",null,v(c.name),1),i("small",null,v([c.area,c.id].filter(Boolean).join(" · ")),1)])],8,cm))),128)),a.value.length?a.value.length>40?(p(),h("p",hm,v(a.value.length)+" results. Keep typing to narrow it down.",1)):R("",!0):(p(),h("p",pm,"No entities found."))])])]),i("div",vm,[f[8]||(f[8]=i("span",{class:"spacer"},null,-1)),i("button",{type:"button",class:"btn quiet",onClick:f[2]||(f[2]=(...c)=>m(He)&&m(He)(...c))},"Cancel")])],64))}}),ym={class:"dr-head"},gm={class:"tx"},bm={class:"dr-body",id:"inspector-section"},wm={id:"inspection-summary"},_m={key:0,class:"hint warn"},km={key:1,class:"hint"},$m={id:"inspection"},xm=Ee({__name:"InspectPanel",props:{entity:{}},setup(e){const t=e,n=pe(null),s=pe("Reading…"),o=pe("");async function l(){if(u.selected){n.value=null,o.value="",s.value="Fetching current HA status...";try{const r=await yt(`screens/${encodeURIComponent(u.selected)}/inspect`),d=t.entity?r.tiles.filter(f=>f.entity===t.entity):r.tiles;n.value=d,s.value=JSON.stringify(t.entity?d[0]:r,null,2)}catch(r){o.value=r.message,s.value="",ge(r.message)}}}const a=r=>{const d=u.layout?.tiles.find(f=>f.entity===r)?.options||{};return`Small slider: ${d.inline==="slider"?"yes":"no"} · Display: ${Da[d.display||"standard"]||d.display} · Size: ${jc[d.size]||"normal"} · Control: ${Nc({entity:r,options:d},u.inventory)} · Background: ${u.inventory.backgrounds?.[d.background||"auto"]?.label||"Default"}`};return _n(l),pn(()=>t.entity,l),(r,d)=>(p(),h(U,null,[i("div",ym,[d[2]||(d[2]=i("span",{class:"av",style:{background:"var(--seg)",color:"var(--ink-2)","font-size":"16px"}},"⌕",-1)),i("span",gm,[d[1]||(d[1]=i("b",null,"Current data",-1)),i("small",{class:ie({mono:e.entity})},v(e.entity||"Connection, status, and supported properties"),3)]),i("button",{type:"button",class:"icon-btn","aria-label":"Close",onClick:d[0]||(d[0]=(...f)=>m(He)&&m(He)(...f))},"✕")]),i("div",bm,[i("div",wm,[o.value?(p(),h("p",_m,v(o.value),1)):n.value?R("",!0):(p(),h("p",km,"Fetching current HA status...")),(p(!0),h(U,null,le(n.value||[],(f,c)=>(p(),h("article",{key:c,class:"inspection-tile"},[i("strong",null,v(f.entity),1),i("span",null,"Status: "+v(f.word||f.state),1),i("small",null,v(a(f.entity)),1)]))),128))]),i("pre",$m,v(s.value),1)]),i("div",{class:"dr-foot"},[i("button",{type:"button",class:"btn quiet",onClick:l},"Read again"),d[3]||(d[3]=i("span",{class:"spacer"},null,-1))])],64))}}),Sm=["aria-hidden"],Cm=Ee({__name:"Drawer",setup(e){const t=$(()=>!!(u.inspector&&(u.inspector.kind!=="tile"||go.value)));return(n,s)=>(p(),h("aside",{class:ie(["drawer",{open:t.value}]),id:"tile-sheet","aria-hidden":t.value?"false":"true",onClick:s[0]||(s[0]=dt(()=>{},["stop"]))},[m(u).inspector?(p(),h(U,{key:0},[m(u).inspector.kind==="tile"&&m(go)?(p(),Xe(Sv,{key:0,tile:m(go)},null,8,["tile"])):m(u).inspector.kind==="bar"?(p(),Xe(zv,{key:1,index:m(u).inspector.index},null,8,["index"])):m(u).inspector.kind==="bar-add"?(p(),Xe(mm,{key:2})):m(u).inspector.kind==="inspect"?(p(),Xe(xm,{key:3,entity:m(u).inspector.entity},null,8,["entity"])):R("",!0)],64)):R("",!0)],10,Sm))}}),Tm={class:"main-head"},Am={class:"eyebrow",id:"screen-name"},Em=["title"],Pm={class:"head-right"},Om={class:"seg",role:"tablist"},Im=["aria-pressed"],Mm=["aria-pressed"],Lm={key:1,id:"dirty",class:"chip dirty"},Dm=["disabled"],Fm={key:0,class:"spin small"},Nm=["aria-expanded"],Rm={key:3,class:"menu",role:"menu"},Hm=["disabled","title"],Um=["disabled","aria-expanded"],jm={key:0,class:"sub"},Bm=["onClick"],Vm=["disabled","title"],Wm={class:"body",id:"body"},Km=Ee({__name:"ScreenView",setup(e){const t=$(()=>lt.value),n=$(()=>t.value.online?`${t.value.delivery} · ${t.value.status}`:"Offline · changes are saved"),s=$(()=>t.value.update?.available&&t.value.online&&t.value.update?.profile&&t.value.update?.host),o=$(()=>u.inventory.screens.filter(E=>E.id!==t.value.id&&E.layout?.tiles?.length)),l=pe(!1),a=pe(null);function r(){u.menuOpen=!1,l.value=!1}function d(){r(),u.overrideProfile=t.value.update?.profile||null,u.overrideFriendly=t.value.name,Oe("#override")}function f(){r(),u.selectedTile=null,u.inspector={kind:"inspect"}}function c(E){r(),!(u.dirty&&!confirm("Replace the unsaved layout with a copy of the other screen's?"))&&nd(E)}function y(){r(),a.value?.click()}async function b(E){const x=E.target,S=x.files?.[0];x.value="",S&&(u.dirty&&!confirm("Replace the unsaved layout with the imported one?")||od(await S.text()))}function k(E){if(E.key==="Escape")if(u.menuOpen)r();else{if(u.palette)return;u.inspector&&!E.target?.closest?.(".picker")&&He()}else(E.metaKey||E.ctrlKey)&&E.key.toLowerCase()==="s"&&(E.preventDefault(),u.dirty&&jo())}function I(E){u.menuOpen&&!E.target.closest(".head-right .menu, #more")&&r()}return _n(()=>{document.addEventListener("keydown",k),document.addEventListener("click",I)}),In(()=>{document.removeEventListener("keydown",k),document.removeEventListener("click",I)}),(E,x)=>(p(),h(U,null,[i("header",Tm,[i("div",null,[i("span",Am,v(t.value.name),1),i("h1",null,v(m(u).layout?.title||"Home"),1)]),i("span",{id:"delivery",class:ie(["chip",{off:!t.value.online}]),title:n.value},[x[9]||(x[9]=i("span",{class:"dot"},null,-1)),Z(v(n.value),1)],10,Em),i("div",Pm,[i("div",Om,[i("button",{type:"button",id:"tab-layout",role:"tab","aria-pressed":m(u).tab==="layout"?"true":"false",onClick:x[0]||(x[0]=S=>m(u).tab="layout")},"Layout",8,Im),i("button",{type:"button",id:"tab-settings",role:"tab","aria-pressed":m(u).tab==="settings"?"true":"false",onClick:x[1]||(x[1]=S=>{m(u).tab="settings",m(He)()})},"Screen settings",8,Mm)]),m(u).dirty?(p(),h("span",Lm,"Unsaved changes")):(p(),h("span",{key:0,id:"dirty",class:ie(["chip",{sent:m(u).saved}])},v(m(u).saved?"Saved · sent to screen":"All saved"),3)),m(u).dirty?(p(),h("button",{key:2,id:"save",type:"button",class:"btn primary",disabled:m(u).busy,title:"⌘S",onClick:x[2]||(x[2]=S=>m(jo)())},[m(u).busy?(p(),h("span",Fm)):R("",!0),Z(v(m(u).busy?"Saving…":"Save & send"),1)],8,Dm)):R("",!0),i("button",{id:"more",type:"button",class:"icon-btn","aria-label":"More","aria-haspopup":"menu","aria-expanded":m(u).menuOpen?"true":"false",onClick:x[3]||(x[3]=dt(S=>{m(u).menuOpen=!m(u).menuOpen,l.value=!1},["stop"]))},"···",8,Nm),m(u).menuOpen?(p(),h("div",Rm,[i("button",{type:"button",role:"menuitem",id:"identify",disabled:!m(gn)(t.value)||!t.value.online,title:m(gn)(t.value)?"":"Needs firmware 0.2.31 or newer",onClick:x[4]||(x[4]=S=>{r(),m(ja)(t.value)})},[...x[10]||(x[10]=[Z("Identify ",-1),i("small",null,"blink the screen",-1)])],8,Hm),i("button",{type:"button",role:"menuitem",id:"inspect",onClick:f},[...x[11]||(x[11]=[Z("Read current data ",-1),i("small",null,"from Home Assistant",-1)])]),x[17]||(x[17]=i("div",{class:"sep"},null,-1)),i("button",{type:"button",role:"menuitem",id:"copy-layout",disabled:!o.value.length,"aria-expanded":l.value?"true":"false",onClick:x[5]||(x[5]=dt(S=>l.value=!l.value,["stop"]))},[x[12]||(x[12]=Z("Copy layout from… ",-1)),i("small",null,v(o.value.length?`${o.value.length} screen${o.value.length===1?"":"s"}`:"no other screen"),1)],8,Um),l.value?(p(),h("div",jm,[(p(!0),h(U,null,le(o.value,S=>(p(),h("button",{key:S.id,type:"button",role:"menuitem",onClick:M=>c(S.id)},[Z(v(S.name)+" ",1),i("small",null,v(S.layout.tiles.length)+" tiles",1)],8,Bm))),128))])):R("",!0),i("button",{type:"button",role:"menuitem",id:"export-layout",onClick:x[6]||(x[6]=S=>{r(),m(Va)()})},[...x[13]||(x[13]=[Z("Export layout ",-1),i("small",null,"JSON",-1)])]),i("button",{type:"button",role:"menuitem",id:"import-layout",onClick:y},[...x[14]||(x[14]=[Z("Import layout… ",-1),i("small",null,"JSON",-1)])]),x[18]||(x[18]=i("div",{class:"sep"},null,-1)),i("button",{type:"button",role:"menuitem",id:"open-override",disabled:!t.value.update?.profile,title:t.value.update?.profile?"":"No ESPHome profile found for this screen",onClick:d},[...x[15]||(x[15]=[Z("Override YAML ",-1),i("small",null,"advanced",-1)])],8,Vm),i("button",{type:"button",role:"menuitem",onClick:x[7]||(x[7]=S=>{r(),m(Oe)("#firmware")})},"Firmware & USB"),s.value?(p(),h("button",{key:1,type:"button",role:"menuitem",onClick:x[8]||(x[8]=S=>{r(),m(Vo)(t.value)})},[x[16]||(x[16]=Z("Update firmware ",-1)),i("small",null,v(t.value.update?.target),1)])):R("",!0)])):R("",!0),i("input",{ref_key:"fileInput",ref:a,type:"file",accept:"application/json,.json",hidden:"",onChange:b},null,544)])]),i("div",Wm,[m(u).tab==="layout"?(p(),Xe(Zp,{key:0})):(p(),Xe(hh,{key:1})),_e(Cm)])],64))}}),qm={key:0,id:"choose",class:"empty"},Ym={key:1,id:"empty",class:"empty"},zm=Ee({__name:"EmptyState",setup(e){return(t,n)=>m(u).inventory.screens.length?(p(),h("section",qm,[...n[1]||(n[1]=[i("span",{class:"empty-icon"},"▦",-1),i("h2",null,"Choose a screen",-1),i("p",null,"Pick one of your screens on the left to change its tiles, top bar and settings.",-1)])])):(p(),h("section",Ym,[n[2]||(n[2]=i("span",{class:"empty-icon"},"▦",-1)),n[3]||(n[3]=i("h2",null,"Start with a screen",-1)),n[4]||(n[4]=i("p",null,"Connect a CYD or Guition via USB to your Home Assistant machine. Profile, firmware, and the first flash happen in one go.",-1)),i("button",{id:"start",type:"button",class:"btn primary",onClick:n[0]||(n[0]=s=>m(Oe)("#new-screen"))},"Install new screen")]))}}),Jm={class:"panel",id:"settings-view"},Gm={class:"panel-head"},Xm={class:"card-grid"},Zm={class:"card"},Qm={class:"tools"},ey={key:0,class:"card updates",id:"updates"},ty={id:"updates-hint"},ny=["aria-valuenow"],sy={class:"progress-text"},oy={key:0,class:"log-lines"},iy={key:2,class:"whatsnew"},ly={class:"check"},ay=["checked"],ry={class:"card"},uy={class:"tools"},cy={class:"card",id:"claude"},dy={class:"actions"},fy=["disabled"],py={id:"claude-path"},hy=Ee({__name:"AppSettingsView",setup(e){const t=$(()=>u.inventory.updates),n=$(()=>u.inventory.screens.filter(y=>y.update?.available).length),s=$(()=>t.value?t.value.busy?`Updating to firmware ${t.value.target}…`:u.inventory.screens.length?n.value?`Firmware ${t.value.target} is available for ${n.value} screen${n.value===1?"":"s"}.`:`All screens have firmware ${t.value.target}.`:`No screen paired yet. New screens get firmware ${t.value.target}.`:""),o=$(()=>u.inventory.screens.find(y=>y.update?.state==="running"||u.updating.includes(y.id))),l=$(()=>o.value?xn(o.value):null),a=$(()=>(u.firmwareJob?.logs||[]).slice(-12).join(` -`)),r=$(()=>{const y=u.inventory.changelog,b=t.value?.target;return!Array.isArray(y)||!b?[]:y.filter(k=>k.firmware===b).flatMap(k=>k.lines).slice(0,10)}),d=$(()=>u.inventory.claude_skill),f=pe(!1);async function c(){f.value=!0;try{await fd()}finally{f.value=!1}}return(y,b)=>(p(),h("div",Jm,[i("div",Gm,[b[6]||(b[6]=i("div",{class:"tx"},[i("span",{class:"eyebrow"},"Settings"),i("h1",{id:"settings-title"},"Everything around your screens.")],-1)),i("button",{type:"button",class:"btn quiet",id:"close-settings",onClick:b[0]||(b[0]=k=>m(Oe)(""))},"← My screens")]),i("div",Xm,[i("section",Zm,[b[9]||(b[9]=i("h2",null,"Screens",-1)),b[10]||(b[10]=i("p",null,"Add a screen, or build and reinstall the firmware of one you already have.",-1)),i("div",Qm,[i("button",{type:"button",class:"tool",onClick:b[1]||(b[1]=k=>m(Oe)("#new-screen"))},[...b[7]||(b[7]=[i("span",{class:"tool-icon"},"+",-1),i("span",{class:"tx"},[i("b",null,"New screen"),i("small",null,"Connect and install")],-1)])]),i("button",{type:"button",class:"tool",onClick:b[2]||(b[2]=k=>m(Oe)("#firmware"))},[...b[8]||(b[8]=[i("span",{class:"tool-icon"},"⇪",-1),i("span",{class:"tx"},[i("b",null,"Firmware & USB"),i("small",null,"Build and install via USB or Wi-Fi")],-1)])])])]),t.value?(p(),h("section",ey,[b[12]||(b[12]=i("h2",null,"Firmware updates",-1)),i("p",ty,v(s.value),1),o.value&&l.value?(p(),h(U,{key:0},[i("div",{class:"progress",role:"progressbar","aria-valuenow":l.value.percent,"aria-valuemin":"0","aria-valuemax":"100"},[i("i",{style:Pe({width:l.value.percent+"%"})},null,4)],8,ny),i("div",sy,[i("span",null,v(o.value.name)+" · "+v(l.value.percent)+" %",1),i("span",null,v(l.value.text),1)]),a.value?(p(),h("pre",oy,v(a.value),1)):R("",!0)],64)):R("",!0),t.value.pending&&!t.value.busy&&t.value.pending>=2?(p(),h("button",{key:1,id:"update-all",type:"button",class:"btn primary",onClick:b[3]||(b[3]=(...k)=>m(cl)&&m(cl)(...k))},"Update all "+v(t.value.pending)+" screens",1)):R("",!0),r.value.length&&!m(Bo)()?(p(),h("details",iy,[i("summary",null,"What's new in firmware "+v(t.value.target),1),i("ul",null,[(p(!0),h(U,null,le(r.value,k=>(p(),h("li",{key:k},v(k),1))),128))])])):R("",!0),i("label",ly,[i("input",{type:"checkbox",id:"auto-update",checked:!!t.value.auto,onChange:b[4]||(b[4]=k=>m(dd)(k.target.checked))},null,40,ay),b[11]||(b[11]=i("span",null,[Z("Update automatically every night"),i("small",null,"Between 03:00 and 06:00, one screen at a time. Stops on an error and reports it in Home Assistant.")],-1))])])):R("",!0),i("section",ry,[b[14]||(b[14]=i("h2",null,"Alerts",-1)),b[15]||(b[15]=i("p",null,"A card over the whole screen from an automation, on one screen or on all of them. The cheatsheet has the action names, fields, icons, and colors, and a form to try one.",-1)),i("div",uy,[i("button",{type:"button",class:"tool",onClick:b[5]||(b[5]=k=>m(Oe)("#alerts"))},[...b[13]||(b[13]=[i("span",{class:"tool-icon"},"!",-1),i("span",{class:"tx"},[i("b",null,"Alerts"),i("small",null,"Full-screen notifications, from an automation")],-1)])])])]),i("section",cy,[b[18]||(b[18]=i("h2",null,"Claude",-1)),b[19]||(b[19]=i("p",null,'Teach Claude about ESP Screens, then ask: "Show an alert on all my screens when the mailbox is full." Claude knows the alert events, every field, color, and icon.',-1)),i("div",dy,[i("button",{type:"button",id:"claude-install",class:ie(["btn",d.value?.installed&&d.value.current?"quiet":"primary"]),disabled:!d.value||f.value,onClick:c},v(!d.value||!d.value.installed?"Install for Claude Code":d.value.current?"Install again":"Update the skill"),11,fy),i("span",{id:"claude-status",class:ie(["chip",d.value&&d.value.installed?d.value.current?"good":"update":""]),role:"status"},v(d.value?d.value.installed?d.value.current?"● Installed":"Installed · a newer version is ready":"Not installed":"Loading…"),3)]),i("small",null,[b[16]||(b[16]=Z("For Claude Code in Home Assistant. Writes one file to ",-1)),i("code",py,v(d.value?.path||"/homeassistant/.claude/skills/esp-screens"),1),b[17]||(b[17]=Z("; installing again replaces it.",-1))]),b[20]||(b[20]=i("div",{class:"actions"},[i("a",{class:"btn quiet",id:"claude-download",href:"api/claude-skill.zip",download:"esp-screens.zip"},"Download for claude.ai"),i("small",null,"Upload the zip in Claude under Customize → Skills.")],-1))])])]))}}),vy={class:"panel",id:"installer"},my={class:"panel-head"},yy={class:"tx"},gy={id:"install-title"},by={key:0},wy={class:"boards"},_y={class:"board"},ky={class:"board"},$y={class:"field"},xy={class:"node-line"},Sy={id:"node-preview"},Cy={key:0,class:"field",id:"node-label"},Ty={key:1,id:"wifi-fields",class:"wifi"},Ay={class:"hint",id:"wifi-status"},Ey={key:0,class:"field",id:"wifi-ssid-label"},Py={key:1,class:"field",id:"wifi-password-label"},Oy={class:"field"},Iy={key:0,value:"usb"},My=["value"],Ly={id:"target-hint"},Dy={key:2,class:"hint",id:"install-note"},Fy={class:"actions"},Ny=["disabled"],Ry={id:"install-status",class:"status-line error",role:"status"},Hy={key:1,id:"install-progress",class:"card"},Uy={class:"progress-head"},jy={key:0,class:"spin",id:"progress-spin"},By={id:"progress-title"},Vy={id:"progress-detail"},Wy={key:0,id:"install-download",class:"card",style:{background:"var(--surface-2)"}},Ky=["href","download"],qy={class:"steps",id:"download-steps"},Yy={key:1,id:"install-result",class:"field"},zy={class:"key-box"},Jy={class:"steps",id:"install-steps"},Gy=["open"],Xy={id:"install-log",class:"log"},Zy={class:"actions"},Qy="https://web.esphome.io/?dashboard_install",eg="Download · flash from your own computer",tg=Ee({__name:"InstallerView",setup(e){const t=bn({board:"cyd",friendly_name:"",name:"",wifi_ssid:"",wifi_password:"",target:""}),n=bn({view:"setup",file:null,friendly:"",board:"cyd",target:"",apiKey:null,nodeEdited:!1,jobState:null,picked:!1,action:null}),s=pe(!1),o=pe(null),l=pe(null),a=pe([]),r=pe(""),d=pe(""),f=pe(!1),c=pe(!1),y=pe(null);let b=0;function k(me){return me.toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g,"").replace(/[^a-z0-9]+/g,"-").replace(/^[^a-z]+/,"").slice(0,30).replace(/-+$/,"")||"screen"}function I(me){const N=me.replace(/^\/dev\/serial\/by-id\/usb-/,"").replace(/-if\d+(-port\d+)?$/,"").replace(/_/g," ");return`USB · ${N===me?me.replace(/^\/dev\//,""):N}`}pn(()=>t.friendly_name,()=>{n.nodeEdited||(t.name=k(t.friendly_name))});const E=$(()=>t.name||"…"),x=$(()=>o.value?.ports||[]),S=$(()=>o.value?.wifi),M=$(()=>S.value?.state==="new"||S.value?.state==="missing"),G=$(()=>S.value?.missing||[]),C=$(()=>S.value?.state==="new"?"Fill in once: ESP Screens saves this in ESPHome secrets.yaml, later screens use it automatically.":"Your ESPHome secrets.yaml is still missing Wi-Fi details. ESP Screens only fills in the missing lines."),V=$(()=>S.value?.state==="ready"?"Wi-Fi comes from your ESPHome secrets.yaml.":S.value?.state==="invalid"?"secrets.yaml in the ESPHome folder isn't valid YAML. Fix the file first; it won't be overwritten.":""),O=$(()=>t.target==="usb"?"Connect the screen with a USB data cable to the Home Assistant machine; the list refreshes on its own. Or choose Download to use your own computer.":t.target==="download"?"Download the firmware and put it on the screen from your own computer, in Chrome or Edge. After that, updates go over Wi-Fi.":t.target?x.value.length>1?"More than one board connected: choose this screen's port.":"Once over USB; after that, everything is wireless.":"The profile goes into the ESPHome folder. You can install later via Firmware & USB, or from ESPHome Device Builder."),A=$(()=>t.target==="download"?"Build & download":t.target?"Install":"Save profile"),w=$(()=>o.value?.job?.state==="running"&&!(n.file&&o.value.job.file===n.file)),T=$(()=>f.value||S.value?.state==="invalid"||t.target==="usb"||!!t.target&&(w.value||!o.value?.available));function te(){const me=t.target;(me==="download"||me===""?n.picked:x.value.includes(me))||(t.target=x.value[0]||"usb")}async function $e(){let me;try{me=await yt("firmware")}catch(at){r.value=at.message;return}o.value=me;const N=me.job,fe=N&&n.file&&N.file===n.file;if(fe&&(n.jobState=N.state),n.view==="setup"){if(fe&&N.state==="running"){Me(N,me.logs||[]);return}te(),r.value=w.value?`A build or installation is already running (${N.file}). Wait for it to finish.`:!me.available&&t.target?"The ESPHome CLI is missing from this installation; only saving the profile is possible.":V.value}else n.view==="progress"&&fe&&(l.value=N,a.value=me.logs||[],N.state!=="running"&&N.state!=="success"&&(c.value=!0))}function Me(me,N){n.view="progress",n.jobState=me.state,n.action=me.action,l.value=me,a.value=N}const X=$(()=>l.value?.state==="running"),j=$(()=>n.view==="done"||l.value?.state==="success"),z=$(()=>n.action==="download"),ce=$(()=>n.view==="done"?"Profile saved.":X.value?"One moment…":j.value?z.value?"Ready to download.":"Done.":"That didn't work."),de=$(()=>n.view==="done"?`${n.file} is in the ESPHome folder`:X.value?l.value?.stage==="upload"?`Writing firmware to ${n.friendly}…`:"Building firmware…":j.value?z.value?`Firmware for ${n.friendly} is ready`:`Firmware is on ${n.friendly}`:z.value?"Build failed":"Install failed"),he=$(()=>n.view==="done"?"Install it later from Firmware & USB: choose this profile, then the USB port of the Home Assistant machine, or Download to put the firmware on the screen from your own computer. Save the API key for pairing:":X.value?l.value?.stage==="upload"?"Don't disconnect the USB cable yet.":`A first build takes a few minutes on a Raspberry Pi. You can leave this page: the ${z.value?"build":"installation"} keeps running and you'll find it again under New screen.`:j.value?z.value?"Put it on the screen from your own computer, then pair the screen with Home Assistant.":`The screen boots up and connects to your Wi-Fi.${n.board==="cyd"?" The CYD first asks for a touch calibration: tap the crosshairs.":""} Pair it with Home Assistant now:`:a.value.filter(me=>/error/i.test(me)).pop()||a.value.filter(me=>/failed/i.test(me)).pop()||"See the log below."),Q=$(()=>({href:`api/firmware/profiles/${encodeURIComponent(n.file||"")}/download`,name:(n.file||"").replace(/\.yaml$/,"")+".factory.bin"})),K=$(()=>[["Go to Home Assistant → Settings → Devices & services.",` This happens outside ESP Screens. Home Assistant discovers ${n.friendly} as an ESPHome device; click Add. Not discovered? Add ESPHome manually with the screen's IP address. `],["Paste the API key"," above when Home Assistant asks for an encryption key."],["Allow HA actions:"," ESPHome integration → Configure → “Allow the device to perform Home Assistant actions”. Without this, the screen sees everything but controls nothing."],["Choose your tiles."," Back in ESP Screens, the screen appears in the list on the left within about thirty seconds; until then it's shown there as “not yet in Home Assistant”."]]);async function B(me){const N=me.target;if(n.nodeEdited||(t.name=k(t.friendly_name)),!!N.reportValidity()){f.value=!0,d.value="";try{const fe={board:t.board,friendly_name:t.friendly_name,name:t.name,target:t.target};M.value&&(G.value.includes("wifi_ssid")&&(fe.wifi_ssid=t.wifi_ssid),G.value.includes("wifi_password")&&(fe.wifi_password=t.wifi_password));const at=await it("firmware/profiles","POST",fe);Object.assign(n,{file:at.file,apiKey:at.api_key,friendly:t.friendly_name.trim(),board:t.board,target:t.target}),t.wifi_password="",at.job?Me(at.job,[]):n.view="done"}catch(fe){d.value=fe.message}finally{f.value=!1}}}async function ne(){try{if(!z.value){const{ports:N}=await yt("firmware");!N.includes(n.target)&&N.length===1&&(n.target=N[0])}const me=await it("firmware/jobs","POST",z.value?{file:n.file,action:"download"}:{file:n.file,action:"install",target:n.target});Me(me,[])}catch(me){ge(me.message)}}function Qe(){Object.assign(n,{view:"setup",file:null,apiKey:null,nodeEdited:!1,jobState:null,target:"",picked:!1,action:null}),Object.assign(t,{board:"cyd",friendly_name:"",name:"",wifi_ssid:"",wifi_password:"",target:""}),s.value=!1,l.value=null,a.value=[],d.value="",r.value="",c.value=!1,$e()}function Mn(){n.view==="progress"&&n.jobState!=="running"&&(n.view="done"),Oe("")}return _n(()=>{$e(),b=window.setInterval($e,3e3)}),In(()=>clearInterval(b)),(me,N)=>(p(),h("div",vy,[i("div",my,[i("div",yy,[N[13]||(N[13]=i("span",{class:"eyebrow"},"New screen",-1)),i("h1",gy,v(n.view==="setup"?"Connect and install.":ce.value),1),n.view==="setup"?(p(),h("p",by,"ESP Screens creates the screen's own profile with unique keys and builds the firmware. Connect the screen with a USB data cable to the machine running Home Assistant, or download the firmware and put it on the screen from your own computer.")):R("",!0)]),i("button",{type:"button",class:"btn quiet",id:"close-install","aria-label":"Close",onClick:Mn},"← Back")]),n.view==="setup"?(p(),h("form",{key:0,id:"install-form",class:"card",onSubmit:dt(B,["prevent"])},[i("fieldset",null,[N[16]||(N[16]=i("legend",null,"Which screen do you have?",-1)),i("div",wy,[i("label",_y,[we(i("input",{type:"radio",name:"board",value:"cyd","onUpdate:modelValue":N[0]||(N[0]=fe=>t.board=fe)},null,512),[[Xi,t.board]]),N[14]||(N[14]=i("span",null,[i("b",null,"CYD · 2.8 inch"),i("small",null,"ESP32-2432S028 · 320 × 240")],-1))]),i("label",ky,[we(i("input",{type:"radio",name:"board",value:"guition","onUpdate:modelValue":N[1]||(N[1]=fe=>t.board=fe)},null,512),[[Xi,t.board]]),N[15]||(N[15]=i("span",null,[i("b",null,"Guition · 4 inch"),i("small",null,"ESP32-S3-4848S040 · 480 × 480 · GT911")],-1))])])]),i("div",$y,[N[18]||(N[18]=i("label",{class:"f-label",for:"friendly_name"},"Name",-1)),we(i("input",{id:"friendly_name",name:"friendly_name","onUpdate:modelValue":N[2]||(N[2]=fe=>t.friendly_name=fe),required:"",maxlength:"60",placeholder:"Living room screen",autocomplete:"off"},null,512),[[Fe,t.friendly_name]]),i("small",xy,[N[17]||(N[17]=Z("Device name ",-1)),i("code",Sy,v(E.value),1),i("button",{type:"button",class:"btn link mini",id:"edit-node",onClick:N[3]||(N[3]=fe=>{n.nodeEdited=!0,s.value=!0})},"customize")]),N[19]||(N[19]=i("small",null,"Every screen gets its own profile with its own keys. Four screens? Go through this four times with a different name.",-1))]),s.value?(p(),h("div",Cy,[N[20]||(N[20]=i("label",{class:"f-label",for:"node-name"},"Device name",-1)),we(i("input",{id:"node-name",name:"name","onUpdate:modelValue":N[4]||(N[4]=fe=>t.name=fe),pattern:"[a-z][a-z0-9\\-]{0,29}",maxlength:"30",autocomplete:"off",onInput:N[5]||(N[5]=fe=>n.nodeEdited=!0)},null,544),[[Fe,t.name]]),N[21]||(N[21]=i("small",null,"Lowercase letters, digits, and dashes. A different name for each screen.",-1))])):R("",!0),M.value?(p(),h("fieldset",Ty,[N[24]||(N[24]=i("legend",null,"Wi-Fi",-1)),i("p",Ay,v(C.value),1),G.value.includes("wifi_ssid")?(p(),h("div",Ey,[N[22]||(N[22]=i("label",{class:"f-label",for:"wifi_ssid"},"Wi-Fi name (2.4 GHz)",-1)),we(i("input",{id:"wifi_ssid",name:"wifi_ssid","onUpdate:modelValue":N[6]||(N[6]=fe=>t.wifi_ssid=fe),autocomplete:"off"},null,512),[[Fe,t.wifi_ssid]])])):R("",!0),G.value.includes("wifi_password")?(p(),h("div",Py,[N[23]||(N[23]=i("label",{class:"f-label",for:"wifi_password"},"Wi-Fi password",-1)),we(i("input",{id:"wifi_password",name:"wifi_password",type:"password","onUpdate:modelValue":N[7]||(N[7]=fe=>t.wifi_password=fe),autocomplete:"new-password"},null,512),[[Fe,t.wifi_password]])])):R("",!0)])):R("",!0),i("div",Oy,[N[26]||(N[26]=i("label",{class:"f-label",for:"install-target"},"Install via",-1)),we(i("select",{id:"install-target",name:"target","onUpdate:modelValue":N[8]||(N[8]=fe=>t.target=fe),onChange:N[9]||(N[9]=fe=>{n.picked=!0,$e()})},[x.value.length?R("",!0):(p(),h("option",Iy,"USB · no board found on the Home Assistant machine yet")),(p(!0),h(U,null,le(x.value,fe=>(p(),h("option",{key:fe,value:fe},v(I(fe)),9,My))),128)),i("option",{value:"download"},v(eg)),N[25]||(N[25]=i("option",{value:""},"Later · save profile only",-1))],544),[[vn,t.target]]),i("small",Ly,v(O.value),1)]),r.value?(p(),h("p",Dy,v(r.value),1)):R("",!0),i("div",Fy,[i("button",{type:"submit",class:"btn primary",id:"install-go",disabled:T.value},v(A.value),9,Ny),i("span",Ry,v(d.value),1)])],32)):(p(),h("div",Hy,[i("div",Uy,[X.value?(p(),h("span",jy)):(p(),h("span",{key:1,class:ie(["outcome",j.value?"ok":"bad"]),id:"progress-mark"},v(j.value?"✓":"✕"),3)),i("strong",By,v(de.value),1)]),i("p",Vy,v(he.value),1),j.value&&z.value&&n.view!=="done"?(p(),h("div",Wy,[i("a",{class:"btn primary",id:"download-firmware",href:Q.value.href,download:Q.value.name},"Download "+v(Q.value.name),9,Ky),i("ol",qy,[N[30]||(N[30]=i("li",null,[i("b",null,"Plug the screen into your computer"),Z(" with a USB data cable.")],-1)),i("li",null,[i("b",null,[N[27]||(N[27]=Z("Open ",-1)),i("a",{href:Qy,target:"_blank",rel:"noopener"},"ESPHome Web")]),N[28]||(N[28]=Z(" in Chrome or Edge, click Connect and choose the screen's USB port.",-1))]),i("li",null,[N[29]||(N[29]=i("b",null,"Click Install",-1)),Z(" and select "+v(Q.value.name)+". The screen restarts and joins your Wi-Fi."+v(n.board==="cyd"?" The CYD first asks for a touch calibration: tap the crosshairs.":""),1)])]),N[31]||(N[31]=i("small",null,"The file holds your Wi-Fi password and the screen's keys: keep it to yourself.",-1))])):R("",!0),j.value?(p(),h("div",Yy,[i("div",zy,[N[32]||(N[32]=i("span",null,"API key",-1)),i("code",{id:"api-key",ref_key:"keyBox",ref:y},v(n.apiKey||""),513),i("button",{type:"button",class:"btn quiet mini",id:"copy-key",onClick:N[10]||(N[10]=fe=>m(ft)(n.apiKey||"",y.value))},"Copy")]),i("ol",Jy,[(p(!0),h(U,null,le(K.value,([fe,at],Ln)=>(p(),h("li",{key:Ln},[i("b",null,v(fe),1),Z(v(at),1),Ln===0?(p(),h("button",{key:0,type:"button",class:"btn quiet mini",onClick:N[11]||(N[11]=(...ln)=>m(Fs)&&m(Fs)(...ln))},"Open Devices & services")):R("",!0)]))),128))])])):R("",!0),n.view!=="done"?(p(),h("details",{key:2,id:"install-log-wrap",class:"log-wrap",open:c.value,onToggle:N[12]||(N[12]=fe=>c.value=fe.target.open)},[N[33]||(N[33]=i("summary",null,"ESPHome log",-1)),i("pre",Xy,v(a.value.join(` -`)),1)],40,Gy)):R("",!0),i("div",Zy,[!X.value&&!j.value?(p(),h("button",{key:0,type:"button",class:"btn primary",id:"install-retry",onClick:ne},"Retry")):R("",!0),i("button",{type:"button",class:"btn quiet",id:"install-close",onClick:Qe},v(j.value?"Install another screen":"Start over"),1),i("button",{type:"button",class:ie(["btn",j.value?"primary":"quiet"]),onClick:Mn},v(j.value?"Done":"Close"),3)])]))]))}}),ng={class:"panel",id:"firmware-dialog"},sg={class:"panel-head"},og={class:"card"},ig={class:"card-grid"},lg={class:"field"},ag=["value"],rg={class:"field"},ug={key:0,value:"usb"},cg=["value"],dg={key:0,class:"field",id:"firmware-host-label"},fg=["placeholder"],pg={class:"actions"},hg=["disabled"],vg=["disabled"],mg=["disabled"],yg={key:0,class:"spin"},gg={id:"firmware-status",class:"status-line",role:"status"},bg={key:0,id:"firmware-download",class:"card",style:{background:"var(--surface-2)"}},wg=["href","download"],_g={id:"firmware-log",class:"log"},kg="https://web.esphome.io/?dashboard_install",$g="Download · flash from your own computer",xg=Ee({__name:"FirmwareView",setup(e){const t=pe(null),n=pe(""),s=pe("ota"),o=pe("");let l=0;async function a(E=!1){try{const x=await yt("firmware");t.value=x,(E||!n.value)&&(n.value=x.profiles[0]?.file||"");const S=x.ports||[];s.value.startsWith("/")&&!S.includes(s.value)&&(s.value=S[0]||"usb"),s.value==="usb"&&S.length&&(s.value=S[0])}catch(x){ge(x.message)}}const r=$(()=>t.value?.job?.state==="running"),d=$(()=>!t.value||r.value||!t.value.available||!t.value.profiles?.length),f=$(()=>d.value||s.value==="usb"),c=$(()=>t.value?.ports||[]),y=$(()=>t.value?t.value.available?t.value.job?`${t.value.job.file} · ${t.value.job.action} · ${t.value.job.state}`:"Choose the intended profile, then a USB port, an IP address, or Download.":"The ESPHome CLI is missing. Update the app to 0.2.0.":"Loading…"),b=$(()=>s.value==="download"&&!!n.value&&!!t.value?.downloads?.includes(n.value)),k=$(()=>({href:`api/firmware/profiles/${encodeURIComponent(n.value)}/download`,name:n.value.replace(/\.yaml$/,"")+".factory.bin"}));async function I(E){try{await it("firmware/jobs","POST",{file:n.value,action:E==="validate"?"validate":E==="build"?"build":s.value==="download"?"download":"install",target:s.value==="ota"?o.value.trim():s.value==="download"?"":s.value}),await a()}catch(x){ge(x.message)}}return _n(()=>{a(!0),l=window.setInterval(()=>a(),3e3)}),In(()=>clearInterval(l)),(E,x)=>(p(),h("div",ng,[i("div",sg,[x[7]||(x[7]=Cs('
    Firmware & USB

    Build and install screen firmware

    The built-in ESPHome CLI uses your own profile (YAML with keys) and secrets. Choose the screen's profile, then a USB port (screen connected to the Home Assistant machine), Wi-Fi / OTA with the hostname, or Download to put the firmware on the screen from your own computer. Check only validates, Build only compiles, Build & install puts the firmware on the screen. Your tiles stay as they are in ESP Screens. The first build takes a few minutes.

    ',1)),i("button",{type:"button",class:"btn quiet",id:"close-firmware",onClick:x[0]||(x[0]=S=>m(Oe)(""))},"← Back")]),i("div",og,[i("div",ig,[i("div",lg,[x[8]||(x[8]=i("label",{class:"f-label",for:"firmware-file"},"ESPHome profile",-1)),we(i("select",{id:"firmware-file","onUpdate:modelValue":x[1]||(x[1]=S=>n.value=S)},[(p(!0),h(U,null,le(t.value?.profiles||[],S=>(p(),h("option",{key:S.file,value:S.file},v(S.file),9,ag))),128))],512),[[vn,n.value]])]),i("div",rg,[x[10]||(x[10]=i("label",{class:"f-label",for:"firmware-port"},"Install to",-1)),we(i("select",{id:"firmware-port","onUpdate:modelValue":x[2]||(x[2]=S=>s.value=S)},[x[9]||(x[9]=i("option",{value:"ota"},"Wi-Fi / OTA",-1)),c.value.length?R("",!0):(p(),h("option",ug,"USB · no board found on the Home Assistant machine yet")),(p(!0),h(U,null,le(c.value,S=>(p(),h("option",{key:S,value:S},v(S),9,cg))),128)),i("option",{value:"download"},v($g))],512),[[vn,s.value]])]),s.value==="ota"?(p(),h("div",dg,[x[11]||(x[11]=i("label",{class:"f-label",for:"firmware-host"},"IP address or hostname",-1)),we(i("input",{id:"firmware-host","onUpdate:modelValue":x[3]||(x[3]=S=>o.value=S),placeholder:n.value?n.value.replace(/\.yaml$/,".local"):"screen-livingroom.local"},null,8,fg),[[Fe,o.value]])])):R("",!0)]),i("div",pg,[i("button",{type:"button",class:"btn quiet",id:"firmware-validate",disabled:d.value,onClick:x[4]||(x[4]=S=>I("validate"))},"Check",8,hg),i("button",{type:"button",class:"btn quiet",id:"firmware-build",disabled:d.value,onClick:x[5]||(x[5]=S=>I("build"))},"Build only",8,vg),i("button",{type:"button",class:"btn primary",id:"firmware-install",disabled:f.value,onClick:x[6]||(x[6]=S=>I("install"))},v(s.value==="download"?"Build & download":"Build & install"),9,mg),r.value?(p(),h("span",yg)):R("",!0)]),i("p",gg,v(y.value),1),b.value?(p(),h("div",bg,[i("a",{class:"btn primary",id:"firmware-download-link",href:k.value.href,download:k.value.name},"Download "+v(k.value.name),9,wg),i("small",null,[x[12]||(x[12]=Z("Put it on the screen with ",-1)),i("a",{href:kg,target:"_blank",rel:"noopener"},"ESPHome Web"),x[13]||(x[13]=Z(" in Chrome or Edge: Connect, choose the screen's USB port, Install, and select this file. It holds your Wi-Fi password and the screen's keys: keep it to yourself.",-1))])])):R("",!0)]),i("pre",_g,v((t.value?.logs||[]).join(` -`)),1)]))}}),Sg={class:"panel wide alerts",id:"alerts-dialog"},Cg={class:"panel-head"},Tg={key:0,class:"hint"},Ag={class:"alert-hero"},Eg={class:"alert-mock",id:"alert-mock","aria-hidden":"true"},Pg={class:"mdi alert-mock-icon",id:"alert-mock-icon"},Og={class:"alerts-nav","aria-label":"Sections"},Ig=["data-jump","onClick"],Mg={id:"alerts-try",class:"card"},Lg={class:"field"},Dg={value:"all"},Fg=["value","disabled"],Ng={class:"field"},Rg={class:"field"},Hg={class:"field"},Ug={id:"try-icons"},jg=["value"],Bg={class:"field"},Vg=["value"],Wg={class:"field"},Kg={class:"field"},qg={class:"check field"},Yg={class:"actions field wide"},zg=["disabled"],Jg={class:"status-line",id:"try-result",role:"status"},Gg={id:"alerts-screens",class:"card"},Xg={id:"alerts-min-firmware"},Zg={id:"alerts-screen-list",class:"options"},Qg={key:0,class:"hint"},eb={class:"alert-screen-head"},tb={class:"copy-label"},nb=["onClick"],sb={id:"alerts-howto",class:"card"},ob={class:"field"},ib=["value"],lb={key:0,value:""},ab={class:"copy-line"},rb={id:"alerts-example"},ub={id:"alerts-all",class:"card"},cb={id:"alerts-all-event"},db={id:"alerts-all-dismiss"},fb={class:"copy-line"},pb={id:"alerts-all-example"},hb={id:"alerts-fields",class:"card"},vb={class:"table-scroll"},mb={id:"alerts-field-table"},yb={key:0},gb={id:"alerts-icons",class:"card"},bb={class:"chips",id:"alerts-suggested"},wb=["onClick"],_b={class:"mdi"},kb={class:"field"},$b={id:"alerts-icon-groups"},xb={class:"subhead"},Sb={class:"alert-icon-grid"},Cb=["title","onClick"],Tb={class:"mdi"},Ab={key:0},Eb={key:0,class:"hint"},Pb={id:"alerts-colors",class:"card"},Ob={class:"swatches",id:"alerts-swatches"},Ib=["onClick"],Mb={id:"alerts-events",class:"card"},Lb={id:"alerts-event-name"},Db={class:"table-scroll"},Fb={id:"alerts-ending-table"},Nb={class:"copy-line"},Rb={id:"alerts-wait-example"},Hb={id:"alerts-tips",class:"card"},Ub={class:"alerts-list"},jb=Ee({__name:"AlertsView",setup(e){const t=$(()=>u.inventory.alerts),n=bn({screen:"all",title:"Someone is at the door",subtitle:"Door 3, back",icon:"doorbell",color:"orange",button_text:"Coming",timeout:30,flash:!0}),s=pe(!1),o=pe(""),l=$(()=>u.inventory.screens.filter(A=>gn(A)&&A.online));async function a(){s.value=!0,o.value="";try{const{screen:A,...w}=n,T=await ed(A,w),te=A==="all"?`${T.sent} screen${T.sent===1?"":"s"}`:u.inventory.screens.find($e=>$e.id===A)?.name||"the screen";o.value=T.sent?`Sent to ${te}.${T.skipped?` ${T.skipped} skipped (offline or old firmware).`:""}${T.unusable?.length?` Left empty: ${T.unusable.join(", ")}.`:""}`:"No screen could show it: check that a screen is online with firmware "+(t.value?.min_firmware||"0.2.31")+" or newer."}catch(A){o.value=A.message}finally{s.value=!1}}const r=$(()=>u.inventory.icons),d=pe(""),f=pe(""),c=A=>`"${String(A).replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`,y=A=>A.type==="string"?/^[a-z][a-z0-9-]*$/.test(A.example)?A.example:c(A.example):A.example===!0?"true":A.example===!1?"false":String(A.example),b=$(()=>u.inventory.screens.filter(A=>A.alert_action)),k=$(()=>d.value||b.value[0]?.alert_action||""),I=$(()=>{const A=(t.value?.fields||[]).map(w=>` ${w.name}: ${y(w)}`);return`action: ${k.value||"esphome._show_alert"} -data: -${A.join(` -`)}`}),E=$(()=>["# Doorbell: show the alert and wait until someone presses the button.","actions:",` - action: ${k.value||"esphome._show_alert"}`," data:",' title: "Someone is at the door"',' subtitle: "Door 3, back"'," icon: doorbell"," color: orange",' button_text: "Coming"'," timeout: 0"," flash: true"," - wait_for_trigger:"," - trigger: event",` event_type: ${t.value?.event||"esphome.screen_alert"}`," event_data:"," action: ok",' timeout: "00:05:00"'," - if:"," - condition: template",' value_template: "{{ wait.trigger is not none }}"'," then:"," - action: notify.notify"," data:",' message: "Someone is coming to the door."'].join(` -`)),x=$(()=>{const A=(t.value?.fields||[]).map(T=>` ${T.name}: ${y(T)}`),w=t.value?.camera;return w&&A.push(` # ${w.name}: ${w.example} # a Guition shows its picture on the card`),`event: ${t.value?.broadcast?.show||"esp_screens_show_alert"} -event_data: -${A.join(` -`)}`}),S=$(()=>{if(!t.value||!r.value)return[];const A=f.value.trim().toLowerCase();return[...r.value.groups,{label:"Also available: control and weather icons",icons:t.value.extra_icons}].map(T=>({label:T.label,icons:T.icons.filter(te=>!A||te.name.includes(A)||(te.label||"").toLowerCase().includes(A))})).filter(T=>T.icons.length)}),M=$(()=>t.value?.suggested_icons?.find(A=>A.name==="doorbell")),G=$(()=>t.value?.colors?.find(A=>A.name==="orange")),C={string:"text",int:"number",bool:"on / off"};function V(A){document.getElementById(A)?.scrollIntoView({behavior:"smooth",block:"start"})}const O=[["alerts-try","Try it"],["alerts-screens","Your screens"],["alerts-howto","In an automation"],["alerts-all","All screens"],["alerts-fields","The fields"],["alerts-icons","Icons"],["alerts-colors","Colors"],["alerts-behaviour","Behavior"],["alerts-events","Events"],["alerts-tips","Tips"]];return(A,w)=>(p(),h("div",Sg,[i("div",Cg,[w[15]||(w[15]=i("div",{class:"tx"},[i("span",{class:"eyebrow"},"Alerts · cheatsheet"),i("h1",null,"A full-screen notification, from an automation."),i("p",null,[Z("Every screen has the "),i("code",null,"show_alert"),Z(" action. It places a card over everything, wakes the screen, and keeps the backlight at normal brightness until someone taps the button or the timeout runs out. Home Assistant itself shows no explanation or dropdowns for ESPHome actions; everything you can fill in is listed here.")])],-1)),i("button",{type:"button",class:"btn quiet",id:"close-alerts",onClick:w[0]||(w[0]=T=>m(Oe)(""))},"← Back")]),t.value?(p(),h(U,{key:1},[i("div",Ag,[i("div",Eg,[i("div",{class:"alert-mock-card",style:Pe(G.value?{background:G.value.color}:void 0)},[i("span",Pg,v(M.value?m(ke)(M.value.cp):""),1),w[16]||(w[16]=i("strong",{id:"alert-mock-title"},"Someone is at the door",-1)),w[17]||(w[17]=i("span",{id:"alert-mock-subtitle"},"Door 3, back",-1)),w[18]||(w[18]=i("span",{class:"fake",id:"alert-mock-button"},"Coming",-1))],4)]),i("nav",Og,[(p(),h(U,null,le(O,([T,te])=>i("button",{key:T,type:"button","data-jump":T,onClick:$e=>V(T)},v(te),9,Ig)),64))])]),i("section",Mg,[w[28]||(w[28]=i("h2",null,"Try it",-1)),w[29]||(w[29]=i("p",null,"Send one alert now, with the same fields an automation sends. The screen shows the card and, with blinking on, flashes its backlight.",-1)),i("form",{class:"try-grid",onSubmit:dt(a,["prevent"])},[i("div",Lg,[w[19]||(w[19]=i("label",{class:"f-label",for:"try-screen"},"Screen",-1)),we(i("select",{id:"try-screen","onUpdate:modelValue":w[1]||(w[1]=T=>n.screen=T)},[i("option",Dg,"All screens ("+v(l.value.length)+" ready)",1),(p(!0),h(U,null,le(m(u).inventory.screens,T=>(p(),h("option",{key:T.id,value:T.id,disabled:!m(gn)(T)||!T.online},v(T.name)+v(m(gn)(T)&&T.online?"":" · not ready"),9,Fg))),128))],512),[[vn,n.screen]])]),i("div",Ng,[w[20]||(w[20]=i("label",{class:"f-label",for:"try-title"},"Title",-1)),we(i("input",{id:"try-title","onUpdate:modelValue":w[2]||(w[2]=T=>n.title=T),maxlength:"64"},null,512),[[Fe,n.title]])]),i("div",Rg,[w[21]||(w[21]=i("label",{class:"f-label",for:"try-subtitle"},"Subtitle",-1)),we(i("input",{id:"try-subtitle","onUpdate:modelValue":w[3]||(w[3]=T=>n.subtitle=T),maxlength:"240"},null,512),[[Fe,n.subtitle]])]),i("div",Hg,[w[22]||(w[22]=i("label",{class:"f-label",for:"try-icon"},"Icon",-1)),we(i("input",{id:"try-icon","onUpdate:modelValue":w[4]||(w[4]=T=>n.icon=T),list:"try-icons",placeholder:"doorbell"},null,512),[[Fe,n.icon]]),i("datalist",Ug,[(p(!0),h(U,null,le(t.value.suggested_icons,T=>(p(),h("option",{key:T.name,value:T.name},v(T.label||T.name),9,jg))),128))])]),i("div",Bg,[w[24]||(w[24]=i("label",{class:"f-label",for:"try-color"},"Color",-1)),we(i("select",{id:"try-color","onUpdate:modelValue":w[5]||(w[5]=T=>n.color=T)},[w[23]||(w[23]=i("option",{value:""},"White (default)",-1)),(p(!0),h(U,null,le(t.value.colors,T=>(p(),h("option",{key:T.name,value:T.name},v(T.label),9,Vg))),128))],512),[[vn,n.color]])]),i("div",Wg,[w[25]||(w[25]=i("label",{class:"f-label",for:"try-button"},"Button text",-1)),we(i("input",{id:"try-button","onUpdate:modelValue":w[6]||(w[6]=T=>n.button_text=T),maxlength:"16"},null,512),[[Fe,n.button_text]])]),i("div",Kg,[w[26]||(w[26]=i("label",{class:"f-label",for:"try-timeout"},"Timeout (seconds, 0 waits for the button)",-1)),we(i("input",{id:"try-timeout","onUpdate:modelValue":w[7]||(w[7]=T=>n.timeout=T),type:"number",min:"0",max:"86400"},null,512),[[Fe,n.timeout,void 0,{number:!0}]])]),i("label",qg,[we(i("input",{type:"checkbox",id:"try-flash","onUpdate:modelValue":w[8]||(w[8]=T=>n.flash=T)},null,512),[[hc,n.flash]]),w[27]||(w[27]=i("span",null,[Z("Blink the backlight"),i("small",null,"Four times when the alert arrives.")],-1))]),i("div",Yg,[i("button",{type:"submit",class:"btn primary",id:"try-send",disabled:s.value||!l.value.length},v(s.value?"Sending…":"Send alert"),9,zg),i("span",Jg,v(o.value),1)])],32)]),i("section",Gg,[w[32]||(w[32]=i("h2",null,"Your screens",-1)),i("p",null,[w[30]||(w[30]=Z("Every screen has its own action, with the device name in it. From firmware ",-1)),i("code",Xg,v(t.value.min_firmware),1),w[31]||(w[31]=Z("; update an older screen with its Update button under Screens.",-1))]),i("div",Zg,[m(u).inventory.screens.length?R("",!0):(p(),h("p",Qg,"No screen paired yet. Install one via New screen; the action appears once Home Assistant sees the screen.")),(p(!0),h(U,null,le(m(u).inventory.screens,T=>(p(),h("div",{key:T.id,class:"alert-screen"},[i("div",eb,[i("strong",null,v(T.name),1),i("span",{class:ie(["chip",m(nn)(m(ls)(T),t.value.min_firmware)&&T.alert_action?"good":"update"])},v(m(nn)(m(ls)(T),t.value.min_firmware)&&T.alert_action?`● firmware ${T.firmware}`:T.alert_action?`Update needed · firmware ${T.firmware||"unknown"}`:"Device name unknown · update the screen"),3)]),(p(!0),h(U,null,le([["Show alert",T.alert_action],["Dismiss alert",T.dismiss_action]],([te,$e])=>(p(),h("div",{key:te,class:"copy-line"},[i("span",tb,v(te),1),i("code",null,v($e||"esphome._show_alert"),1),$e?(p(),h("button",{key:0,type:"button",class:"btn quiet mini",onClick:Me=>m(ft)($e,void 0,"Action name")},"Copy",8,nb)):R("",!0)]))),128))]))),128))])]),i("section",sb,[w[34]||(w[34]=Cs('

    In an automation

    1. Add action and search for ESPHome. Choose your screen's action: Home Assistant calls it "Performs the action show_alert of the node device name".
    2. Fill in the fields. Home Assistant asks for all seven; leave what you don't use empty ("", 0, off).
    3. Prefer YAML? Choose Edit in YAML on the action and paste the example below.
    ',2)),i("div",ob,[w[33]||(w[33]=i("label",{class:"f-label",for:"alerts-example-screen"},"Example for",-1)),we(i("select",{id:"alerts-example-screen","onUpdate:modelValue":w[9]||(w[9]=T=>d.value=T),style:{"max-width":"360px"}},[(p(!0),h(U,null,le(b.value,T=>(p(),h("option",{key:T.id,value:T.alert_action},v(T.name),9,ib))),128)),b.value.length?R("",!0):(p(),h("option",lb,"a screen (not paired yet)"))],512),[[vn,d.value]])]),i("div",ab,[i("pre",rb,v(I.value),1),i("button",{type:"button",class:"btn quiet mini",id:"alerts-example-copy",onClick:w[10]||(w[10]=T=>m(ft)(I.value,void 0,"YAML"))},"Copy YAML")])]),i("section",ub,[w[42]||(w[42]=i("h2",null,"All screens at once",-1)),i("p",null,[w[35]||(w[35]=Z("Fire the event ",-1)),i("code",cb,v(t.value.broadcast?.show||"esp_screens_show_alert"),1),w[36]||(w[36]=Z(" with the same fields, and ESP Screens passes the alert on to every screen that is online, including screens you add later. ",-1)),i("code",db,v(t.value.broadcast?.dismiss||"esp_screens_dismiss_alert"),1),w[37]||(w[37]=Z(" clears it everywhere. In an automation, add the ",-1)),w[38]||(w[38]=i("b",null,"Event",-1)),w[39]||(w[39]=Z(" action or paste the example under ",-1)),w[40]||(w[40]=i("b",null,"Edit in YAML",-1)),w[41]||(w[41]=Z(". Leave out the fields you don't use. ESP Screens has to be running for this.",-1))]),i("div",fb,[i("pre",pb,v(x.value),1),i("button",{type:"button",class:"btn quiet mini",id:"alerts-all-copy",onClick:w[11]||(w[11]=T=>m(ft)(x.value,void 0,"YAML"))},"Copy YAML")])]),i("section",hb,[w[46]||(w[46]=i("h2",null,"The fields",-1)),w[47]||(w[47]=i("p",null,"Text is free-form; the limit is in bytes, and an accented letter counts as two. What doesn't fit gets an ellipsis (title) or is dropped (subtitle).",-1)),i("div",vb,[i("table",mb,[w[45]||(w[45]=i("tr",null,[i("th",null,"Field"),i("th",null,"Type"),i("th",null,"What it does"),i("th",null,"Example"),i("th",null,"Limit")],-1)),(p(!0),h(U,null,le(t.value.fields,T=>(p(),h("tr",{key:T.name},[i("td",null,[i("code",null,v(T.name),1),i("small",null,v(T.label),1)]),i("td",null,v(C[T.type]||T.type),1),i("td",null,v(T.help),1),i("td",null,[i("code",null,v(typeof T.example=="string"?T.example:String(T.example)),1)]),i("td",null,v(t.value.limits.cyd[T.name]?`CYD ${t.value.limits.cyd[T.name]} · Guition ${t.value.limits.guition[T.name]} bytes`:T.type==="int"?"0 to 86400 s":"—"),1)]))),128)),t.value.camera?(p(),h("tr",yb,[i("td",null,[i("code",null,v(t.value.camera.name),1),i("small",null,v(t.value.camera.label),1)]),w[43]||(w[43]=i("td",null,"entity",-1)),i("td",null,v(t.value.camera.help),1),i("td",null,[i("code",null,v(t.value.camera.example),1)]),w[44]||(w[44]=i("td",null,"Guition, firmware 0.2.57+",-1))])):R("",!0)])])]),i("section",gb,[w[49]||(w[49]=i("h2",null,"Icons",-1)),w[50]||(w[50]=i("p",null,[Z("Fill in the name in "),i("code",null,"icon"),Z(", for example "),i("code",null,"doorbell"),Z(". "),i("code",null,"mdi:doorbell"),Z(" and the hex codepoint ("),i("code",null,"F12E6"),Z(") also work. Only these icons are included in the firmware; any other name shows the warning triangle. Tap an icon to copy its name.")],-1)),w[51]||(w[51]=i("p",{class:"subhead"},"Handy for notifications",-1)),i("div",bb,[(p(!0),h(U,null,le(t.value.suggested_icons,T=>(p(),h("button",{key:T.name,type:"button",class:"chip",onClick:te=>m(ft)(T.name,void 0,"Icon name")},[i("span",_b,v(m(ke)(T.cp)),1),i("code",null,v(T.name),1)],8,wb))),128))]),i("div",kb,[w[48]||(w[48]=i("label",{class:"f-label",for:"alerts-icon-search"},"Search for an icon",-1)),we(i("input",{id:"alerts-icon-search","onUpdate:modelValue":w[12]||(w[12]=T=>f.value=T),type:"search",placeholder:"Search by name or description…",autocomplete:"off"},null,512),[[Fe,f.value]])]),i("div",$b,[(p(!0),h(U,null,le(S.value,T=>(p(),h("div",{key:T.label,class:"field"},[i("p",xb,v(T.label)+" · "+v(T.icons.length),1),i("div",Sb,[(p(!0),h(U,null,le(T.icons,te=>(p(),h("button",{key:te.name,type:"button",class:"alert-icon",title:`Copy ${te.name}`,onClick:$e=>m(ft)(te.name,void 0,"Icon name")},[i("span",Tb,v(m(ke)(te.cp)),1),i("code",null,v(te.name),1),te.label?(p(),h("small",Ab,v(te.label),1)):R("",!0)],8,Cb))),128))])]))),128)),S.value.length?R("",!0):(p(),h("p",Eb,'No icon for "'+v(f.value)+'". Unknown names show the warning triangle ('+v(t.value.fallback_icon)+").",1))])]),i("section",Pb,[w[53]||(w[53]=i("h2",null,"Colors",-1)),w[54]||(w[54]=i("p",null,[Z("Fill in the name in "),i("code",null,"color"),Z(". These are the pastel shades of the tiles, with dark text. Empty or unknown gives the white card. Tap to copy.")],-1)),i("div",Ob,[i("button",{type:"button",class:"swatch",onClick:w[13]||(w[13]=T=>m(ft)("",void 0,"Empty color"))},[...w[52]||(w[52]=[i("i",{style:{background:"#ffffff"}},null,-1),i("span",null,[i("code",null,"empty"),i("small",null,"White (default)")],-1)])]),(p(!0),h(U,null,le(t.value.colors,T=>(p(),h("button",{key:T.name,type:"button",class:"swatch",onClick:te=>m(ft)(T.name,void 0,"Color name")},[i("i",{style:Pe({background:T.color})},null,4),i("span",null,[i("code",null,v(T.name),1),i("small",null,v(T.label)+" · "+v(T.color),1)])],8,Ib))),128))])]),w[75]||(w[75]=Cs('

    Behavior

    • Wakes the screen. An alert immediately wakes a dimmed or dark screen and appears above every page, every open light card, and the standby layer.
    • On until the button. As long as the card is showing, the backlight stays at normal brightness. Standby and night hours wait. After the button, the regular standby time starts again.
    • Timeout. timeout: 0 waits for the button, however long that takes. With timeout: 60 the card disappears after a minute, but the button closes it sooner.
    • Blinking. flash: true blinks the backlight four times when the alert arrives (about a second) and leaves the screen on afterward.
    • Replacing. A new alert replaces the current one; there's no stack. The replaced alert reports itself as replaced.
    • Closing remotely. dismiss_alert clears the card, for example if the door is already open. Without an alert, that action does nothing.
    • Tapping beside the card does nothing; only the button closes it. That way a wake-up tap never accidentally dismisses the alert.
    ',1)),i("section",Mb,[w[66]||(w[66]=i("h2",null,"Events",-1)),i("p",null,[w[55]||(w[55]=Z("Every end fires the event ",-1)),i("code",Lb,v(t.value.event),1),w[56]||(w[56]=Z(" with ",-1)),w[57]||(w[57]=i("code",null,"action",-1)),w[58]||(w[58]=Z(", ",-1)),w[59]||(w[59]=i("code",null,"title",-1)),w[60]||(w[60]=Z(", ",-1)),w[61]||(w[61]=i("code",null,"screen",-1)),w[62]||(w[62]=Z(" (the device name) and the ",-1)),w[63]||(w[63]=i("code",null,"device_id",-1)),w[64]||(w[64]=Z(" that Home Assistant adds. Visible under Settings → Tools → Events.",-1))]),i("div",Db,[i("table",Fb,[w[65]||(w[65]=i("tr",null,[i("th",null,"action"),i("th",null,"When")],-1)),(p(!0),h(U,null,le(t.value.endings,T=>(p(),h("tr",{key:T.action},[i("td",null,[i("code",null,v(T.action),1)]),i("td",null,v(T.label),1)]))),128))])]),w[67]||(w[67]=i("p",{class:"subhead"},"Waiting until someone presses the button",-1)),i("div",Nb,[i("pre",Rb,v(E.value),1),i("button",{type:"button",class:"btn quiet mini",id:"alerts-wait-copy",onClick:w[14]||(w[14]=T=>m(ft)(E.value,void 0,"YAML"))},"Copy YAML")])]),i("section",Hb,[w[74]||(w[74]=i("h2",null,"Tips",-1)),i("ul",Ub,[w[72]||(w[72]=Cs("
  • Use a color per kind of notification: orange for the door, red for smoke or water, green for "done". The text always stays dark and readable.
  • Keep the title short; a screen shows about twenty characters on one line and ends a longer title with an ellipsis. Put details in the subtitle.
  • A custom button text turns the notification into a question: "Coming", "Seen", "Turn off". The event then tells you who responded.
  • For an alert that resolves itself (the door opens, the laundry gets taken out) close it in the same automation with dismiss_alert.
  • ",4)),i("li",null,[w[68]||(w[68]=Z("Every screen? Fire ",-1)),i("code",null,v(t.value.broadcast?.show||"esp_screens_show_alert"),1),w[69]||(w[69]=Z(" once (see All screens at once). The screens still work independently and each reports with its own ",-1)),w[70]||(w[70]=i("code",null,"screen",-1)),w[71]||(w[71]=Z(".",-1))]),w[73]||(w[73]=i("li",null,[Z("Rather "),i("b",null,"ask Claude"),Z(" for the automation? Install the ESP Screens skill under Settings → Claude.")],-1))])])],64)):(p(),h("p",Tg,"The cheatsheet is still loading; one moment."))]))}}),Bb={class:"panel",id:"override-dialog"},Vb={class:"panel-head"},Wb={class:"tx"},Kb={id:"override-title"},qb={class:"notice"},Yb={class:"mdi"},zb={class:"file-row"},Jb={id:"override-file"},Gb={class:"yaml-editor",id:"yaml-editor-wrap"},Xb={class:"actions"},Zb={id:"override-count",class:"hint"},Qb={class:"actions"},e1=["disabled"],t1=["disabled"],n1=`# Hardware-specific changes for this screen. -# This file is kept when the shared firmware package updates. -# Do not add esphome:, api:, ota:, wifi: or packages: here. - -# Example: a CYD with the ST7789V display controller. -# "!extend" changes the display the shared package already defines; -# a bare id would add a second, incomplete display and fail the build. -display: - - id: !extend my_display - model: ST7789V -`,s1=Ee({__name:"OverrideView",setup(e){const t=$(()=>u.overrideProfile),n=pe(""),s=pe(""),o=pe(!1),l=pe("Loading…"),a=pe(""),r=pe(!1),d=pe(null),f=pe(null),c=$(()=>n.value.split(` -`).length),y=$(()=>Array.from({length:c.value},(O,A)=>A+1).join(` -`)),b=$(()=>`${n.value.length} characters · ${c.value} line${c.value===1?"":"s"}`);let k=!0;function I(O,A=""){l.value=O,a.value=A}async function E(){if(!t.value){I("No ESPHome profile was found for this screen.","error");return}I("Loading…");try{const O=await yt(`firmware/profiles/${encodeURIComponent(t.value)}/override`);s.value=O.override_file,o.value=!!O.attached,n.value=O.exists&&O.content!==`{} -`?O.content:"",I(O.attached?"Changes here apply on the next build.":"Save once to attach this file to the profile.")}catch(O){I(O.message,"error")}}async function x(O=!1){if(!(!t.value||r.value)){r.value=!0,I("Checking and saving…");try{const A=await it(`firmware/profiles/${encodeURIComponent(t.value)}/override`,"PUT",{content:n.value});if(s.value=A.override_file,o.value=!0,!O){I("Saved. The override is kept during firmware updates.","ok"),ge("Override YAML saved.");return}I("Saved. ESPHome is checking the complete profile…"),await it("firmware/jobs","POST",{file:t.value,action:"validate"}),S()}catch(A){I(A.message,"error")}finally{r.value=!1}}}function S(){const O=Date.now(),A=t.value,w=async()=>{if(!(!k||t.value!==A))try{const T=await yt("firmware"),te=T.job;if(te&&te.file===A&&te.state==="running")I(`ESPHome is checking the profile… ${te.stage||""}`.trim());else if(te&&te.file===A&&te.state==="success"){I("The complete profile is valid. Safe to build and install.","ok");return}else if(te&&te.file===A&&te.state==="failed"){const $e=(T.logs||[]).filter(Me=>/error|failed/i.test(Me)).pop();I($e||"ESPHome rejected the complete profile. See Firmware & USB for the full log.","error");return}Date.now()-O<72e5&&setTimeout(w,1200)}catch(T){I(T.message,"error")}};w()}function M(){(!n.value.trim()||confirm("Replace the current text with the example?"))&&(n.value=n1,I("Example loaded. Save it when you are ready."),d.value?.focus())}function G(){(!n.value.trim()||confirm("Clear the local override?"))&&(n.value="",I("The override will be empty after you save."),d.value?.focus())}function C(O){const A=d.value;if(O.key==="Tab"){O.preventDefault();const w=A.selectionStart,T=A.selectionEnd;A.setRangeText(" ",w,T,"end"),n.value=A.value}else(O.ctrlKey||O.metaKey)&&O.key.toLowerCase()==="s"&&(O.preventDefault(),x(!1))}function V(){f.value&&d.value&&(f.value.scrollTop=d.value.scrollTop)}return _n(E),In(()=>{k=!1}),(O,A)=>(p(),h("div",Bb,[i("div",Vb,[i("div",Wb,[A[4]||(A[4]=i("span",{class:"eyebrow"},"Advanced · local override",-1)),i("h1",Kb,"Override YAML"+v(m(u).overrideFriendly?` · ${m(u).overrideFriendly}`:""),1),A[5]||(A[5]=i("p",null,"This small file is yours. It is loaded after the shared screen package and stays in place when ESP Screen Manager updates. Change hardware-specific details here, such as a display model.",-1))]),i("button",{type:"button",class:"btn quiet",id:"close-override",onClick:A[0]||(A[0]=w=>m(Oe)(""))},"← Back")]),i("div",qb,[i("span",Yb,v(m(ke)("F0493")),1),A[6]||(A[6]=i("div",null,[i("strong",null,"What is safe to change?"),i("span",null,"Display, touchscreen, substitutions, pins and other device details. Name, Wi-Fi, API, OTA and package links remain managed.")],-1))]),i("div",zb,[i("code",Jb,v(s.value||t.value||""),1),i("span",{id:"override-state",class:ie(["chip",{good:o.value}])},v(o.value?"Active":"Ready to attach"),3)]),i("div",Gb,[i("div",{ref_key:"gutter",ref:f,class:"yaml-gutter",id:"override-gutter","aria-hidden":"true"},v(y.value),513),we(i("textarea",{ref_key:"editor",ref:d,id:"override-editor","onUpdate:modelValue":A[1]||(A[1]=w=>n.value=w),spellcheck:"false",autocapitalize:"off",autocomplete:"off",autocorrect:"off","aria-label":"Local override YAML",placeholder:`# Example -display: - - id: !extend my_display - model: ST7789V`,onKeydown:C,onScroll:V},null,544),[[Fe,n.value]])]),i("div",Xb,[i("button",{type:"button",class:"btn quiet mini",id:"override-example",onClick:M},"Use example"),i("button",{type:"button",class:"btn quiet mini",id:"override-empty",onClick:G},"Clear"),i("span",Zb,v(b.value),1)]),i("p",{id:"override-status",class:ie(["status-line",a.value]),role:"status"},v(l.value),3),i("div",Qb,[i("button",{type:"button",class:"btn quiet",id:"override-check",disabled:r.value||!t.value,onClick:A[2]||(A[2]=w=>x(!0))},"Save & check",8,e1),i("button",{type:"button",class:"btn primary",id:"override-save",disabled:r.value||!t.value,onClick:A[3]||(A[3]=w=>x(!1))},"Save override",8,t1)])]))}}),o1={class:"main"},i1=Ee({__name:"App",setup(e){const t=$(()=>rt.value==="#settings"?hy:rt.value==="#new-screen"?tg:rt.value==="#firmware"?xg:rt.value==="#alerts"?jb:rt.value==="#override"?s1:lt.value&&u.layout?Km:zm);function n(s){(s.metaKey||s.ctrlKey)&&s.key.toLowerCase()==="k"&&(s.preventDefault(),u.palette=!u.palette)}return _n(()=>document.addEventListener("keydown",n)),In(()=>document.removeEventListener("keydown",n)),(s,o)=>(p(),h("div",{class:ie(["app",{dragging:m(u).drag.active}])},[_e(jd),i("main",o1,[(p(),Xe(su(t.value)))]),_e(Vd),_e(Qd)],2))}});_c(i1).mount("#app");vd(); diff --git a/screen_manager/app/static/index.html b/screen_manager/app/static/index.html index ee074e3..9009d1b 100644 --- a/screen_manager/app/static/index.html +++ b/screen_manager/app/static/index.html @@ -5,8 +5,8 @@ ESP Screens - - + +
    diff --git a/screen_manager/config.yaml b/screen_manager/config.yaml index 684b7a9..d3da9e5 100644 --- a/screen_manager/config.yaml +++ b/screen_manager/config.yaml @@ -1,5 +1,5 @@ name: ESP Screen Manager -version: "0.2.81" +version: "0.2.82" slug: esp_screen_manager description: Choose tiles for your CYD and Guition touchscreen, without reflashing. url: https://github.com/MaxGramser/homeassistant_espscreen diff --git a/tests/test_guition.py b/tests/test_guition.py index b615ccc..8f84224 100644 --- a/tests/test_guition.py +++ b/tests/test_guition.py @@ -45,7 +45,12 @@ class GuitionTests(unittest.TestCase): self.assertFalse(xxx and yyy) rectangles.append((x,y,w,h)) self.assertEqual(len(rectangles),6) - self.assertIn('height: 44',SOURCE.split('id: page_next',1)[1][:200]) + # The page keys are the two halves of the band under the tiles (firmware 0.2.69+). + band=480-v('SCROLL_Y')-v('SCROLL_H') + self.assertEqual(band,60) + for key in ('page_prev','page_next'): + block=SOURCE.split(f'id: {key}',1)[1][:200] + self.assertIn('width: 240\n',block);self.assertIn(f'height: {band}\n',block) def test_all_tiles_clip_long_titles(self): for i in range(1,11): diff --git a/tests/test_layout.py b/tests/test_layout.py index 2a4130a..58653eb 100644 --- a/tests/test_layout.py +++ b/tests/test_layout.py @@ -37,6 +37,30 @@ class LayoutTests(unittest.TestCase): self.assertLess(grid, nav) self.assertLess(nav, modal) + def test_page_keys_are_the_halves_of_the_band_under_the_tiles(self): + """Firmware 0.2.69+: a chevron in each half of the band, the dots between them take no touches.""" + for name in ('home-like-2432s028.yaml', 'guition-4848s040.yaml'): + source = (ROOT / name).read_text() + values = dict(re.findall(r'^ (\w+): "([^"]*)"', source, re.M)) + band = int(values['DISPLAY_H']) - int(values['SCROLL_Y']) - int(values['SCROLL_H']) + half = int(values['DISPLAY_W']) // 2 + for key, glyph in (('page_prev', 'F0141'), ('page_next', 'F0142')): + block = source.split(f' id: {key}\n', 1)[1].split('\n - ', 1)[0] + self.assertIn(f'width: {half}\n', block, f'{name} {key}') + self.assertIn(f'height: {band}\n', block, f'{name} {key}') + self.assertIn('styles: paint_page_pressed', block, f'{name} {key}: the half lights up under a finger') + self.assertIn(f'\\U000{glyph}', block, f'{name} {key}') + self.assertIn('text_font: materialdesign_icons_mini', block, f'{name} {key}') + self.assertNotIn('Previous', block) + self.assertNotIn('Next >', block) + number = source.split(' id: page_number\n', 1)[1].split('\n - ', 1)[0] + self.assertIn('clickable: false', number, name) + self.assertIn(f'height: {band}\n', number, name) + self.assertIn('settings_screen::page_dots(id(page_number)', source, name) + runtime = (ROOT / 'components/smart_display/runtime_tiles.h').read_text() + self.assertIn('settings_screen::page_dots(nav_number,page,pages,', runtime) + self.assertIn('set_hidden(control,!bar)', runtime) + def test_pagination_is_conditional_and_excess_tiles_are_hidden(self): block = SOURCE.split(' - id: show_tile_page\n', 1)[1].split(' - id: wake_display', 1)[0] self.assertIn('const bool paginated = count > 6;', block) diff --git a/tests/test_screen_owned_settings.py b/tests/test_screen_owned_settings.py index 5e6ba63..28cf90d 100644 --- a/tests/test_screen_owned_settings.py +++ b/tests/test_screen_owned_settings.py @@ -48,12 +48,12 @@ ENTITY_IDS = { 'clock_24h': 'switch.office_1_24_hour_clock', 'auto_home': 'switch.office_1_back_to_page_1', 'auto_home_seconds': 'number.office_1_back_to_page_1_after', 'home_on_standby': 'switch.office_1_back_to_page_1_on_standby', 'swipe_pages': 'switch.office_1_swipe_between_pages', 'rotation': 'select.office_1_rotation', - 'dark_mode': 'switch.office_1_dark_mode', + 'dark_mode': 'switch.office_1_dark_mode', 'page_buttons': 'switch.office_1_page_buttons', } STATES = {'brightness': '80.0', 'standby_enabled': 'on', 'standby_seconds': '600.0', 'standby_brightness': '20.0', 'night_enabled': 'on', 'night_start': '22:30:00', 'night_end': '07:00:00', 'night_brightness': '5.0', 'clock_24h': 'on', 'auto_home': 'on', 'auto_home_seconds': '120.0', 'home_on_standby': 'off', - 'swipe_pages': 'on', 'rotation': '90°', 'dark_mode': 'off'} + 'swipe_pages': 'on', 'rotation': '90°', 'dark_mode': 'off', 'page_buttons': 'on'} def top_block(text, key): diff --git a/tests/test_settings_screen.cpp b/tests/test_settings_screen.cpp index 8dea277..c79d5d7 100644 --- a/tests/test_settings_screen.cpp +++ b/tests/test_settings_screen.cpp @@ -73,6 +73,17 @@ int main() { assert(dark_mode == 0 && value_text(dark) == "Off"); const Page &screen = pages[3]; + // Page buttons (firmware 0.2.69+) follows Swipe between pages: on by default, off takes the bar away. + { + const Row &buttons = row_named(screen, "Page buttons"); + assert(buttons.kind == Kind::toggle && &buttons == &row_named(screen, "Swipe between pages") + 1); + assert(page_buttons == 1 && value_text(buttons) == "On"); + buttons.write(0); + assert(page_buttons == 0 && value_text(buttons) == "Off"); + assert(set("page_buttons", 0) == SetResult::same); + buttons.write(1); + assert(page_buttons == 1); + } const Row &clock = row_named(screen, "Clock"); assert(clock.option_count == 2 && value_text(clock) == "24 hour"); clock.write(0); @@ -151,7 +162,7 @@ int main() { {"night_start", -5, 0}, {"night_end", 2000, 1439}, {"night_brightness", 7, 7}, {"clock_24h", 0, 0}, {"home_on_standby", 1, 1}, {"swipe_pages", 2, 1}, {"rotation", 100, 90}, {"rotation", 400, 270}, {"auto_home", 0, 0}, {"auto_home_seconds", 5, 30}, {"auto_home_seconds", 99999, 3600}, - {"dark_mode", 7, 1}, + {"dark_mode", 7, 1}, {"page_buttons", 0, 0}, }; for (const auto &c : cases) { result = set(c.key, c.value); @@ -160,7 +171,7 @@ int main() { } assert(screen_settings::current.standby_seconds == 86400 && screen_settings::current.brightness == 100); assert(screen_settings::current.night_start == 0 && screen_settings::current.night_end == 1439); - assert(swipe_pages == 1 && rotation == 270 && auto_home == 0 && auto_home_seconds == 3600 && dark_mode == 1); + assert(swipe_pages == 1 && rotation == 270 && auto_home == 0 && auto_home_seconds == 3600 && dark_mode == 1 && page_buttons == 0); assert(screen_settings::current.valid()); // Dark mode again is the same look: nothing stored, applied or reported; off is a change. { diff --git a/web/src/components/LayoutView.vue b/web/src/components/LayoutView.vue index b046356..32a82d2 100644 --- a/web/src/components/LayoutView.vue +++ b/web/src/components/LayoutView.vue @@ -2,7 +2,7 @@ // The pages side by side, like swiping on the screen, and the library on the right. import { computed } from "vue"; import { entriesOf, hasGaps, MAX_PAGES, pageCount } from "../model/layout"; -import { addPage, closeInspector, currentScreen, pagesShown, state, supports, tileLimit } from "../store"; +import { addPage, closeInspector, currentScreen, pageReachWarning, pagesShown, state, supports, tileLimit } from "../store"; import DevicePage from "./DevicePage.vue"; import Library from "./Library.vue"; @@ -14,6 +14,8 @@ const canAdd = computed(() => pages.value < MAX_PAGES); const positionsHint = computed(() => hasGaps(layout.value.tiles) && !supports(0, 2, 26) ? `Empty slots and fixed positions work from firmware 0.2.26. This screen (firmware ${currentScreen.value?.firmware || "unknown"}) shifts the tiles up to the first free slot until that update.` : ""); +// Page buttons and swiping off: a page no Go to page tile reaches, or one without a way back. +const reachHint = computed(() => pageReachWarning(entries.value, pages.value)); function onCanvasClick(e: MouseEvent) { // A click beside the pages closes the drawer; the cards and the bar handle their own clicks. if ((e.target as HTMLElement).closest(".device, .page-label, .canvas-head")) return; @@ -28,6 +30,7 @@ function onCanvasClick(e: MouseEvent) { Add your first light, scene, or device from the library. Tap the top bar or a tile to change it. Drag to move; drop on the next page for a new one. {{ positionsHint }} + {{ reachHint }}
    diff --git a/web/src/components/SettingsTab.vue b/web/src/components/SettingsTab.vue index 041457d..6088152 100644 --- a/web/src/components/SettingsTab.vue +++ b/web/src/components/SettingsTab.vue @@ -3,12 +3,14 @@ // once, like on the screen; no Save needed. import { computed } from "vue"; import { glyph } from "../model/topbar"; -import { currentScreen, SETTING_GROUPS, setSetting, settingText, settingValues, settingsView, state, steppedSetting, type SettingRow } from "../store"; +import { currentScreen, pageReachWarning, SETTING_GROUPS, setSetting, settingText, settingValues, settingsView, state, steppedSetting, type SettingRow } from "../store"; const view = computed(() => settingsView()); const values = computed(() => settingValues()); const offline = computed(() => view.value?.owner === "screen" && !currentScreen.value?.online); const groups = computed(() => SETTING_GROUPS.map((group) => ({ ...group, rows: (group.rows as readonly SettingRow[]).filter((row) => view.value?.keys.includes(row.key)) })).filter((g) => g.rows.length)); +// Page buttons and swiping off: the pages that only Go to page tiles could reach, and don't. +const reachWarning = computed(() => pageReachWarning()); const unavailable = (row: SettingRow) => offline.value || Boolean(view.value?.unavailable.includes(row.key)); const needs = (row: SettingRow) => (row.needs ? Boolean(values.value[row.needs]) : true); const status = computed(() => offline.value @@ -78,6 +80,7 @@ function click(e: MouseEvent, row: SettingRow, direction: number) {
    +

    {{ reachWarning }}

    diff --git a/web/src/model/layout.ts b/web/src/model/layout.ts index f87e0db..dc1ebea 100644 --- a/web/src/model/layout.ts +++ b/web/src/model/layout.ts @@ -105,6 +105,34 @@ export function pageCount(entries: Entry[], wanted = 1) { const last = Math.max(0, ...entries.map(({ tile, slot }) => slot + spanOf(sizeOf(tile)))); return Math.min(MAX_PAGES, Math.max(1, Math.ceil(last / SLOTS_PER_PAGE), wanted || 1)); } +// With the page buttons and swiping both off (firmware 0.2.69+) only Go to page tiles change the page. Pages count +// from 1: `tiles` is how many Go to page tiles lead to a page the screen has, `targets` the pages they lead to, +// `unreachable` the pages no chain of them reaches from page 1, `noWayBack` the reachable ones they never lead back from. +export function strandedPages(entries: Entry[], pages: number) { + const links = Array.from({ length: pages }, () => new Set()); + let tiles = 0; + for (const { tile, slot } of entries) { + const to = pageTarget(tile.entity) - 1, from = pageOf(slot); + if (to < 0 || to >= pages || from >= pages) continue; + tiles++; + links[from].add(to); + } + const reach = (next: (page: number) => number[]) => { + const seen = new Set([0]), queue = [0]; + while (queue.length) for (const page of next(queue.shift()!)) if (!seen.has(page)) { seen.add(page); queue.push(page); } + return seen; + }; + const forward = reach((page) => [...links[page]]); + const back = reach((page) => links.flatMap((to, from) => (to.has(page) ? [from] : []))); + const all = Array.from({ length: pages }, (_, page) => page); + const numbers = (list: number[]) => list.map((page) => page + 1); + return { + tiles, + targets: numbers(all.filter((page) => links.some((to) => to.has(page)))), + unreachable: numbers(all.filter((page) => !forward.has(page))), + noWayBack: numbers(all.filter((page) => forward.has(page) && !back.has(page))), + }; +} // New tiles start with the card that shows the entity best. export function defaultOptions(id: string): Partial { const domain = id.split(".")[0]; diff --git a/web/src/store.ts b/web/src/store.ts index 4c6d8b8..a2e7013 100644 --- a/web/src/store.ts +++ b/web/src/store.ts @@ -4,7 +4,7 @@ import { computed, reactive, toRaw } from "vue"; import { api, getJson, send, setCsrf } from "./api"; import { arrange, cellsOf, entriesOf, firstFree, fits, isFull, isWide, MAX_PAGES, nearestFree, newTile, normalize, occupied, pageCount, pageOf, - pageTarget, rowStart, sizeOf, SLOTS_PER_PAGE, supportsFirmware as supportsVersion, tileLimit as limitFor, + pageTarget, rowStart, sizeOf, SLOTS_PER_PAGE, strandedPages, supportsFirmware as supportsVersion, tileLimit as limitFor, } from "./model/layout"; import { agoText, BAR_METRICS, clockText, dateText, itemKey, type ItemView, whenBarFontsLoad } from "./model/topbar"; import { versionAtLeast } from "./model/layout"; @@ -636,10 +636,29 @@ export const SETTING_GROUPS = [ { key: "auto_home_seconds", label: "After", kind: "duration", min: 30, max: 3600, needs: "auto_home" }, { key: "home_on_standby", label: "Also on standby", kind: "toggle" }, { key: "swipe_pages", label: "Swipe between pages", kind: "toggle" }, + { key: "page_buttons", label: "Page buttons", kind: "toggle" }, { key: "rotation", label: "Rotation", kind: "choice", options: [[0, "0°"], [90, "90°"], [180, "180°"], [270, "270°"]] }, ] }, ] as const; export type SettingRow = (typeof SETTING_GROUPS)[number]["rows"][number] & { min?: number; max?: number; step?: number; unit?: string; needs?: string; cap?: string; options?: readonly (readonly [unknown, string])[] }; +// Page buttons and swiping both off (firmware 0.2.69+): only Go to page tiles change the page, so the editor says which +// pages the Go to page tiles lead to and which page that leaves out, or has no way back to page 1. Empty when either +// is on or still unknown, or every page can be reached and left. +export function pageReachWarning(entries = liveEntries(), pages = state.layout ? pageCount(entries, state.layout.pages) : 1) { + const values = settingValues(); + if (pages < 2 || values.page_buttons !== false || values.swipe_pages !== false) return ""; + const { tiles, targets, unreachable, noWayBack } = strandedPages(entries, pages); + if (!unreachable.length && !noWayBack.length) return ""; + const named = (list: number[]) => (list.length === 1 ? `page ${list[0]}` : `pages ${list.slice(0, -1).join(", ")} and ${list[list.length - 1]}`); + // A page that tiles lead to but only from pages that can't be reached themselves. + const missing = unreachable.filter((page) => !targets.includes(page)), cutOff = unreachable.filter((page) => targets.includes(page)); + let text = "Page buttons and swiping are off, so only Go to page tiles change the page. "; + text += tiles ? `You have ${tiles} Go to page ${tiles === 1 ? "tile" : "tiles"}, to ${named(targets)}` : "You have no Go to page tiles"; + text += missing.length ? `${tiles ? ", but none" : ""} to ${named(missing)}, so you can't reach ${missing.length === 1 ? "it" : "them"}.` : "."; + if (cutOff.length) text += ` The tiles to ${named(cutOff)} are only on pages you can't reach.`; + if (noWayBack.length) text += ` From ${named(noWayBack)} no tile leads back to page 1.`; + return text; +} // Changes made here that the screen has not reported back yet win over what Home Assistant still shows for a // few seconds, so a value never flicks back while it travels. const SETTING_EDIT_MS = 4000; diff --git a/web/src/styles/app.css b/web/src/styles/app.css index 849b360..34842e4 100644 --- a/web/src/styles/app.css +++ b/web/src/styles/app.css @@ -300,6 +300,7 @@ a:hover { text-decoration: underline; } .set-card { background: var(--surface); border: 1px solid var(--line); border-radius: 12px; padding: 6px 14px 8px; } .set-card h4 { display: flex; align-items: center; gap: 8px; padding: 10px 0 8px; } .set-card h4 .mdi { color: var(--muted); font-size: 16px; } +.set-card > .hint { margin: 6px 0 4px; } .srow { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 8px 0; border-top: 1px solid var(--line); } .srow > .s-label { color: var(--ink-2); } .srow.inactive .s-label, .srow.inactive .s-control { opacity: 0.45; } diff --git a/web/tests/layout.spec.ts b/web/tests/layout.spec.ts index afa59ec..3d1892c 100644 --- a/web/tests/layout.spec.ts +++ b/web/tests/layout.spec.ts @@ -2,7 +2,8 @@ import { describe, expect, it } from "vitest"; import { arrange, cellsOf, controlsLabel, defaultOptions, effectiveControls, firstFree, fits, hasGaps, MAX_PAGES, MAX_SLOTS, nearestFree, - newTile, normalize, occupied, packSlots, pageCount, pageStart, pageTarget, rowStart, sizeOf, SLOTS_PER_PAGE, spanOf, tileLimit, versionAtLeast, + newTile, normalize, occupied, packSlots, pageCount, pageStart, pageTarget, rowStart, sizeOf, SLOTS_PER_PAGE, spanOf, strandedPages, tileLimit, + versionAtLeast, } from "../src/model/layout"; import type { Inventory, Layout, Tile } from "../src/types"; @@ -157,3 +158,19 @@ describe("full-page tiles and navigation tiles (firmware 0.2.62+)", () => { expect(effectiveControls(tile("light.a", 0, { size: "full", controls: "toggle" }), inventory)).toBe("toggle"); }); }); + +describe("pages that only Go to page tiles reach (firmware 0.2.69)", () => { + const nav = (page: number, slot: number) => tile(`screen.page_${page}`, slot); + it("finds a page no tile leads to, and one without a way back", () => { + // Page 1 goes to 2 and 3; page 2 goes back to 1; page 3 leads nowhere; nothing goes to page 4. + const layout = entries([nav(2, 0), nav(3, 1), tile("light.a", 2), nav(1, 6), tile("light.b", 12), tile("light.c", 18)]); + expect(strandedPages(layout, 4)).toEqual({ tiles: 3, targets: [1, 2, 3], unreachable: [4], noWayBack: [3] }); + }); + it("follows a chain of pages, and ignores a tile to a page the screen lacks or to its own page", () => { + const chain = entries([nav(2, 0), nav(3, 6), nav(1, 12), nav(8, 13), nav(3, 14)]); + expect(strandedPages(chain, 3)).toEqual({ tiles: 4, targets: [1, 2, 3], unreachable: [], noWayBack: [] }); + // A tile to page 3 that sits on a page nobody reaches leaves page 3 out too. + expect(strandedPages(entries([nav(3, 6)]), 3)).toEqual({ tiles: 1, targets: [3], unreachable: [2, 3], noWayBack: [] }); + expect(strandedPages(entries([tile("light.a", 0)]), 1)).toEqual({ tiles: 0, targets: [], unreachable: [], noWayBack: [] }); + }); +}); diff --git a/web/tests/store.spec.ts b/web/tests/store.spec.ts index bd2f9d7..568f9ad 100644 --- a/web/tests/store.spec.ts +++ b/web/tests/store.spec.ts @@ -1,7 +1,7 @@ // The store: selecting a screen, editing its layout, what's new, progress, copy and import. import { beforeEach, describe, expect, it, vi } from "vitest"; import { - addTile, canAlert, copyLayoutFrom, fullPage, importLayout, layoutJson, liveOf, moveTileToPage, pageTilesRepeat, PHASES, removeTile, + addTile, canAlert, copyLayoutFrom, fullPage, importLayout, layoutJson, liveOf, moveTileToPage, pageReachWarning, pageTilesRepeat, PHASES, removeTile, retargetPageTile, save, select, setTileOption, state, supports, tileLimit, topbarItems, topbarView, updateProgress, whatsNew, } from "../src/store"; import type { Inventory, Screen } from "../src/types"; @@ -408,3 +408,34 @@ describe("several tiles that go to the same page (firmware 0.2.65)", () => { expect(state.dirty).toBe(true); }); }); + +describe("page buttons and swiping both off (firmware 0.2.69)", () => { + const settings = (values: Record) => + Object.assign(state.inventory.screens[0], { settings: { owner: "screen", values, keys: Object.keys(values), unavailable: [] } }); + const tiles = (list: [string, number][]) => { state.layout!.tiles = list.map(([entity, slot]) => ({ entity, name: "", slot, options: {} })); state.layout!.pages = 1; }; + it("says which pages the Go to page tiles lead to, and which one they leave out", () => { + settings({ page_buttons: false, swipe_pages: false }); + select("living"); + tiles([["screen.page_2", 0], ["screen.page_3", 1], ["screen.page_1", 6], ["screen.page_1", 12], ["light.a", 18]]); + expect(pageReachWarning()).toBe("Page buttons and swiping are off, so only Go to page tiles change the page. " + + "You have 4 Go to page tiles, to pages 1, 2 and 3, but none to page 4, so you can't reach it."); + tiles([["light.a", 0], ["light.b", 6], ["switch.c", 12]]); + expect(pageReachWarning()).toBe("Page buttons and swiping are off, so only Go to page tiles change the page. " + + "You have no Go to page tiles to pages 2 and 3, so you can't reach them."); + tiles([["screen.page_2", 0], ["light.a", 6]]); + expect(pageReachWarning()).toMatch(/You have 1 Go to page tile, to page 2\. From page 2 no tile leads back to page 1\.$/); + }); + it("stays quiet while either way of paging is on, the values are unknown, or every page can be reached and left", () => { + select("living"); + tiles([["light.a", 0], ["light.b", 6]]); + for (const values of [{ page_buttons: true, swipe_pages: false }, { page_buttons: false, swipe_pages: true }, { page_buttons: null, swipe_pages: false }, {}]) { + settings(values); + expect(pageReachWarning()).toBe(""); + } + settings({ page_buttons: false, swipe_pages: false }); + tiles([["screen.page_2", 0], ["screen.page_1", 6]]); + expect(pageReachWarning()).toBe(""); + tiles([["light.a", 0]]); + expect(pageReachWarning()).toBe(""); + }); +});