Files
2026-10-29 18:46:39 +08:00

1 line
67 KiB
JavaScript
Generated
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use strict";"require view";"require form";"require uci";"require rpc";"require ui";"require fs";"require utils.asset-upload as assetUpload";"require utils.feed-check as feedCheck";const CONFIG_IMPORT_PATH="/tmp/aurora_config_import.tmp",themeFormField=e=>document.getElementById("widget.cbid.aurora.theme."+e),FEED_HOST="openwrt.eamonxg.fun",MANIFEST_URL=`https://${FEED_HOST}/manifest.json`,MANIFEST_CACHE_KEY="aurora.manifest",TOKENS_ENGINE_VERSION="2.0.0",loadGlobalScript=(e,t)=>new Promise((r,a)=>{const n=E("script",{type:"text/javascript",src:L.resource(e)+(t?"?v="+t:"")});n.async=!1,n.addEventListener("load",r,{once:!0}),n.addEventListener("error",()=>a(new Error(_("Unable to load %s.").format(e))),{once:!0}),document.querySelector("head").appendChild(n)}),COLOR_LIBRARY_VERSION="83b15ee4",colorLibraryReady=(async()=>{const e=[];"function"!=typeof Color&&e.push(loadGlobalScript("utils/color.global.js","83b15ee4")),"undefined"==typeof AuroraTokens&&e.push(loadGlobalScript("utils/tokens.global.js","2.0.0")),await Promise.all(e)})().then(()=>buildColorTokenTables()),callUploadIcon=rpc.declare({object:"luci.aurora",method:"upload_icon",params:["filename"]}),callListIcons=rpc.declare({object:"luci.aurora",method:"list_icons"}),callGetInitData=rpc.declare({object:"luci.aurora",method:"get_init_data"});let _iconsPromise=null;const getIconsOnce=()=>(_iconsPromise||(_iconsPromise=L.resolveDefault(callListIcons(),{icons:[]})),_iconsPromise),callRemoveIcon=rpc.declare({object:"luci.aurora",method:"remove_icon",params:["filename"]}),callPrepareFont=rpc.declare({object:"luci.aurora",method:"prepare_font",params:["sans","mono","sans_stack"],expect:{"":{result:-1}}}),callGetFontStatus=rpc.declare({object:"luci.aurora",method:"get_font_status",params:["job_id"],expect:{"":{state:"missing"}}}),callUploadFont=rpc.declare({object:"luci.aurora",method:"upload_font",params:["slot","family"]}),callRemoveFont=rpc.declare({object:"luci.aurora",method:"remove_font",params:["slot","name"]}),callExportConfig=rpc.declare({object:"luci.aurora",method:"export_config"}),callImportConfig=rpc.declare({object:"luci.aurora",method:"import_config"}),callResetDefaults=rpc.declare({object:"luci.aurora",method:"reset_defaults"}),callWritePwaManifest=rpc.declare({object:"luci.aurora",method:"write_pwa_manifest"}),COLOR_TOKEN_METADATA={bg:{label:_("Background"),description:_("Page canvas behind the header, navigation, content, and login screen."),group:"foundation"},surface:{label:_("Surface"),description:_("Base surface for panels, cards, forms, tables, and neutral controls."),group:"foundation"},text:{label:_("Text"),description:_("Default foreground for headings, body text, icons, and form values."),group:"identity"},brand:{label:_("Brand"),description:_("Accent for primary buttons, active navigation, and selected states."),group:"identity"},on_brand:{label:_("On-Brand Text"),description:_("Text and icons shown on filled brand backgrounds."),group:"identity"},link:{label:_("Link"),description:_("Hyperlinks in page content, help text, and status output."),group:"identity"},info:{label:_("Info"),description:_("Accent for informational alerts, labels, and tooltips."),group:"status"},warning:{label:_("Warning"),description:_("Accent for warnings, notices, and validation messages."),group:"status"},success:{label:_("Success"),description:_("Accent for successful operations and healthy status."),group:"status"},danger:{label:_("Danger"),description:_("Accent for errors, destructive controls, and critical states."),group:"status"}},DERIVED_COLOR_TOKEN_METADATA={text_muted:{label:_("Muted Text"),description:_("Medium-emphasis text for helper copy, metadata, and summaries."),group:"hierarchy"},text_subtle:{label:_("Subtle Text"),description:_("Low-emphasis text for small labels, placeholders, and disabled hints."),group:"hierarchy"},surface_sunken:{label:_("Sunken Surface"),description:_("Inset layer for inputs, code blocks, table headers, and badges."),group:"hierarchy"},surface_overlay:{label:_("Overlay Surface"),description:_("Raised layer for dropdowns, modals, and tooltips."),group:"hierarchy"},control_bg:{label:_("Control Fill"),description:_("Fill for form controls: inputs, selects, textareas, and checkboxes."),group:"hierarchy"},hairline:{label:_("Hairline"),description:_("Separators, dividers, and input or card borders."),group:"hierarchy"},hover_faint:{label:_("Faint Hover"),description:_("Hover fill for menu items, table rows, and neutral controls."),group:"hierarchy"},brand_hover:{label:_("Brand Hover"),description:_("Hover state for filled primary buttons and high-emphasis controls."),group:"brand_interaction"},brand_subtle:{label:_("Subtle Brand"),description:_("Brand-tinted surface for active navigation and selected states."),group:"brand_interaction"},brand_subtle_hover:{label:_("Subtle Brand Hover"),description:_("Hover fill for subtle primary buttons and selected surfaces."),group:"brand_interaction"},focus_ring:{label:_("Focus Ring"),description:_("Focus outline for inputs, selects, and keyboard-operated controls."),group:"brand_interaction"},progress_start:{label:_("Progress Start"),description:_("Leading color for progress meters."),group:"brand_interaction"},progress_end:{label:_("Progress End"),description:_("Trailing color for progress meters."),group:"brand_interaction"},info_surface:{label:_("Info Surface"),description:_("Background for informational alerts, labels, and tooltips."),group:"status_surfaces"},warning_surface:{label:_("Warning Surface"),description:_("Background for warning alerts, notices, and labels."),group:"status_surfaces"},success_surface:{label:_("Success Surface"),description:_("Background for success alerts and healthy-state labels."),group:"status_surfaces"},danger_surface:{label:_("Danger Surface"),description:_("Background for error alerts and destructive-action messages."),group:"status_surfaces"},danger_surface_hover:{label:_("Danger Surface Hover"),description:_("Hover fill for quiet destructive and delete controls."),group:"status_surfaces"},scrim:{label:_("Scrim"),description:_("Dimming backdrop behind modal dialogs."),group:"hierarchy"},mega_menu_bg:{label:_("Mega Menu Background"),description:_("Opaque surface for the expanded mega menu and its header."),group:"hierarchy"},mega_menu_scrim:{label:_("Mega Menu Scrim"),description:_("Light backdrop behind the expanded mega menu."),group:"hierarchy"}},COLOR_GROUPS=[{key:"foundation",title:_("System Surfaces"),description:_("Canvas and panel surfaces that frame the interface.")},{key:"identity",title:_("Text, Links & Brand"),description:_("Foregrounds, hyperlinks, the brand color, and its contrast text.")},{key:"status",title:_("Operational Status Accents"),description:_("Accents for notifications, validation, and status feedback.")}],DERIVED_COLOR_GROUPS=[{key:"brand_interaction",title:_("Brand Interaction Details"),description:_("Derived from the brand color: hover states, subtle fills, focus rings, and progress meters.")},{key:"hierarchy",title:_("Interface Hierarchy"),description:_("Derived from the canvas, surface, and text colors: muted and subtle text, borders, overlays, and menu layers.")},{key:"status_surfaces",title:_("Status Message Surfaces"),description:_("Derived from the status accents: alert, label, and badge backgrounds.")}];let COLOR_TOKENS=[],DERIVED_COLOR_TOKENS=[],ALL_COLOR_TOKENS=[];const buildColorTokenTables=()=>{const e=(e,t,r)=>e.map(e=>{const a=t[e];if(!a)throw new Error(`missing color token metadata for "${e}"`);return Object.assign({key:e},a,r)}),t=new Set(AuroraTokens.INPUTS.concat(AuroraTokens.DERIVED_KEYS)),r=Object.keys(COLOR_TOKEN_METADATA).concat(Object.keys(DERIVED_COLOR_TOKEN_METADATA)).filter(e=>!t.has(e));if(r.length)throw new Error(`stale color token metadata: ${r.join(", ")}`);COLOR_TOKENS=e(AuroraTokens.INPUTS,COLOR_TOKEN_METADATA,{}),DERIVED_COLOR_TOKENS=e(AuroraTokens.DERIVED_KEYS,DERIVED_COLOR_TOKEN_METADATA,{derived:!0}),ALL_COLOR_TOKENS=COLOR_TOKENS.concat(DERIVED_COLOR_TOKENS)},COLOR_FORMAT_HELP=_("Fields accept #hex, rgb(), hsl(), lab(), and oklch(). The picker fills hex; other formats can be typed."),COLOR_TAB_HINT=_("Changes preview here instantly; Save or Save & Apply persists them.")+` ${COLOR_FORMAT_HELP}`,cssTokenName=e=>e.replaceAll("_","-"),colorOptionName=(e,t)=>`${e}_${t}`,toRuntimeColor=e=>{const t=e?.trim?.()||"";if(!t||"function"!=typeof Color)return t;try{return new Color(t).to("srgb").toString({format:"hex"})}catch(e){return t}},toPickerColor=e=>{const t=new Color(e).to("srgb");return t.alpha=1,t.toString({format:"hex"})},sameColorValue=(e,t)=>Boolean(e&&t)&&toRuntimeColor(e).toLowerCase()===toRuntimeColor(t).toLowerCase(),readThemeConfigFromUci=()=>{const e={},t=t=>{const r=uci.get("aurora","theme",t);null!=r&&(e[t]=r)};return t("active_preset"),t("struct_font_sans"),t("struct_font_mono"),["light","dark"].forEach(e=>{ALL_COLOR_TOKENS.forEach(({key:r})=>t(colorOptionName(e,r)))}),e},createColorResolver=()=>{let e=null,t=Promise.resolve();return{resolveMode:(r,a,n=COLOR_TOKENS)=>(t=t.catch(()=>{}).then(async()=>{const{root:t,probe:o}=await(e||(e=new Promise((e,t)=>{const r=document.createElement("iframe");r.setAttribute("aria-hidden","true"),r.tabIndex=-1,r.style.cssText="position:fixed;left:-10000px;top:-10000px;width:1px;height:1px;border:0;visibility:hidden;pointer-events:none;";const a=window.setTimeout(()=>t(new Error(_("Theme stylesheet resolver timed out."))),1e4);r.addEventListener("load",()=>{const n=r.contentDocument,o=n?.querySelector('link[rel="stylesheet"]'),i=()=>{window.clearTimeout(a);const t=n.createElement("span");t.style.cssText="position:absolute;left:-9999px;top:-9999px;visibility:hidden;",n.body.appendChild(t),e({iframe:r,doc:n,root:n.documentElement,probe:t})};n&&o?o.sheet?i():(o.addEventListener("load",i,{once:!0}),o.addEventListener("error",()=>{window.clearTimeout(a),t(new Error(_("Unable to load the Aurora stylesheet.")))},{once:!0})):(window.clearTimeout(a),t(new Error(_("Unable to create the theme color resolver."))))},{once:!0}),r.srcdoc='<!doctype html><html><head><link rel="stylesheet" href="/luci-static/aurora/main.css"></head><body></body></html>',(document.body||document.documentElement).appendChild(r)}),e));t.setAttribute("data-darkmode","dark"===r?"true":"false"),ALL_COLOR_TOKENS.forEach(({key:e})=>{t.style.removeProperty(`--${cssTokenName(e)}`)}),ALL_COLOR_TOKENS.forEach(({key:e})=>{const r=a[e]?.trim();r&&t.style.setProperty(`--${cssTokenName(e)}`,toRuntimeColor(r))});const i=new Map;return n.forEach(({key:e})=>{const t=cssTokenName(e);o.parentElement.style.color="rgb(1 2 3)",o.style.color=`var(--${t}, rgb(1 2 3))`;const r=getComputedStyle(o).color;o.parentElement.style.color="rgb(4 5 6)",o.style.color=`var(--${t}, rgb(4 5 6))`;const a=getComputedStyle(o).color;r&&a&&r===a?i.set(e,{valid:!0,color:r}):i.set(e,{valid:!1,error:_("Invalid, missing, or cyclic color expression.")})}),o.parentElement.style.removeProperty("color"),i}),t),destroy:()=>{e?.then(({iframe:e})=>e.remove()).catch(()=>{}),e=null}}},createColorEditor=(e,t)=>{const r=createColorResolver(),a={light:new Map,dark:new Map},n={light:new Map,dark:new Map},o={light:new Map,dark:new Map},i={light:null,dark:null},s=new Map;let l=null;const d=()=>"true"===document.documentElement.getAttribute("data-darkmode")?"dark":"light",c=(e,t)=>(n[e].has(t)||n[e].set(t,{pending:!0,valid:!1,error:null}),n[e].get(t)),u=(t,r)=>{const n=a[t].get(r);return n?.input?n.input.value:e[colorOptionName(t,r)]||""},p=e=>COLOR_TOKENS.some(t=>t.key===e),m=(e,r)=>u(e,r).trim()||(t?.[colorOptionName(e,r)]||"").trim(),h=e=>{if("undefined"==typeof AuroraTokens)return null;const t={};for(const{key:r}of COLOR_TOKENS){const a=m(e,r);if(!a)return null;t[r]=a}try{return AuroraTokens.resolve(e,t)}catch(e){return null}},g=(e,t)=>Boolean(o[e].get(t)),f=(e,t,r)=>{(e=>DERIVED_COLOR_TOKENS.some(t=>t.key===e))(t)&&o[e].set(t,Boolean(r))},b=(e,t)=>{t&&DERIVED_COLOR_TOKENS.forEach(({key:r})=>{const n=a[e].get(r);if(!n||n.initialized)return;const o=n.input.value.trim(),i=t[r]?.trim()||"",s=Boolean(o&&!sameColorValue(o,i));n.initialized=!0,f(e,r,s),s||(n.input.value="")})},v=e=>{const t=h(e);if(!t)return null;b(e,t);const r={...t};for(const{key:t}of DERIVED_COLOR_TOKENS){const a=u(e,t).trim(),n=c(e,t);if(g(e,t)&&a){if(!n.valid)return null;r[t]=a}}return r},y=e=>{s.has(e)||s.set(e,{value:document.documentElement.style.getPropertyValue(e),priority:document.documentElement.style.getPropertyPriority(e)})},w=e=>{const t=s.get(e);t&&t.value?document.documentElement.style.setProperty(e,t.value,t.priority):document.documentElement.style.removeProperty(e)},E=()=>{s.forEach((e,t)=>{w(t)}),s.clear()},k=e=>{if(e!==d())return;COLOR_TOKENS.forEach(({key:t})=>{if(!c(e,t).valid)return;const r=`--${cssTokenName(t)}`,a=u(e,t).trim();y(r),a?document.documentElement.style.setProperty(r,toRuntimeColor(a)):w(r)});const t=v(e);t&&Object.keys(t).forEach(e=>{if(p(e))return;const r=`--${cssTokenName(e)}`;y(r),document.documentElement.style.setProperty(r,toRuntimeColor(t[e]))})},x=e=>{e?.option.triggerValidation(e.sectionId)},C=(e,t,r,n={})=>{const o=n.validateKeys?.has(t)??!1,i=a[e].get(t),s=c(e,t);if(s.pending=!1,s.valid=Boolean(r?.valid),s.error=r?.error||null,i){if(i.input.setCustomValidity(s.valid?"":s.error),i.element.classList.toggle("cbi-value-error",!s.valid),!s.valid)return i.status.textContent=s.error,void(o&&x(i));try{const e=toRuntimeColor(r.color);i.picker.value=toPickerColor(r.color),i.swatch.style.backgroundColor=e,i.swatch.title=`${_("Resolved color")}: ${e}`,i.token.derived?(r.autoValue&&(i.input.placeholder=toRuntimeColor(r.autoValue)),i.status.textContent=""):i.status.textContent=""}catch(e){s.valid=!1,s.error=_("Resolved color cannot be shown by the picker."),i.input.setCustomValidity(s.error),i.element.classList.add("cbi-value-error"),i.status.textContent=s.error}o&&x(i)}},S=(e,t={})=>{const n=t.validateKeys||new Set;return colorLibraryReady.then(()=>{const t=h(e);b(e,t);const a=COLOR_TOKENS.concat(DERIVED_COLOR_TOKENS.filter(({key:t})=>g(e,t)&&u(e,t).trim()));return r.resolveMode(e,(e=>Object.fromEntries(ALL_COLOR_TOKENS.map(({key:t})=>[t,u(e,t)])))(e),a).then(e=>({automatic:t,results:e}))}).then(t=>{COLOR_TOKENS.forEach(({key:r})=>{C(e,r,t.results.get(r),{validateKeys:n})}),DERIVED_COLOR_TOKENS.forEach(({key:r})=>{if(g(e,r)&&u(e,r).trim()){const a=t.results.get(r);return void C(e,r,{...a,autoValue:t.automatic?.[r]||""},{validateKeys:n})}const a=t.automatic?.[r];C(e,r,a?{valid:!0,color:a,autoValue:a}:{valid:!1,error:_("Unable to generate the automatic derived value.")},{validateKeys:n})}),k(e)}).catch(t=>{ALL_COLOR_TOKENS.forEach(({key:r})=>{C(e,r,{valid:!1,error:t?.message||_("Unable to resolve color expressions.")},{validateKeys:n})})}).finally(()=>(e=>{const t=a[e].values().next().value,r=t?.option?.map?.root;r&&ui.tabs.updateTabs(null,r)})(e))},O=(e,t,r={})=>{const a=(e=>e?p(e)?COLOR_TOKENS.concat(DERIVED_COLOR_TOKENS).map(e=>e.key):[e]:ALL_COLOR_TOKENS.map(e=>e.key))(t),n=new Set(r.validate?[t].filter(Boolean):[]);window.clearTimeout(i[e]),a.forEach(t=>{c(e,t).pending=!0}),i[e]=window.setTimeout(()=>S(e,{validateKeys:n}),120)};return{attach:()=>{O("light"),O("dark"),l=new MutationObserver(e=>{e.some(e=>"data-darkmode"===e.attributeName)&&(E(),k(d()))}),l.observe(document.documentElement,{attributes:!0,attributeFilter:["data-darkmode"]}),window.addEventListener("beforeunload",E,{once:!0})},cleanupPreview:E,destroy:()=>{window.clearTimeout(i.light),window.clearTimeout(i.dark),l?.disconnect(),E(),r.destroy()},flush:()=>(window.clearTimeout(i.light),window.clearTimeout(i.dark),Promise.all([S("light"),S("dark")])),presetColors:t,register:(t,r,n,o,i,s,l)=>{a[t].set(r.key,{element:n,input:o,option:i,sectionId:s,token:r,...l}),o.addEventListener("input",()=>{e[colorOptionName(t,r.key)]=o.value,r.derived&&f(t,r.key,Boolean(o.value.trim())),O(t,r.key,{validate:!0})}),O(t)},resolvedForMode:v,schedule:O,stateFor:c,validate:(e,t,r)=>{if(!r?.trim())return!0;const a=c(e,t);return a.pending?!(!a.valid&&null!=a.error)||a.error:!!a.valid||a.error||_("Invalid color expression.")}}},navWireBar=e=>E("span",{class:"aurora-nav-wire-bar",style:`width:${e};`}),navTopBar=e=>E("span",{class:"aurora-nav-wire-top"},[E("span",{class:"aurora-nav-wire-logo"}),E("span",{class:"aurora-nav-wire-menu"},[0,1,2,3].map(t=>E("span",{class:"aurora-nav-wire-item"+(t===e?" is-active":"")}))),E("span",{class:"aurora-nav-wire-icon"})]),navWireBarActive=e=>E("span",{class:"aurora-nav-wire-bar is-active",style:`width:${e};`}),navUnderlay=e=>E("span",{class:"aurora-nav-wire-underlay"},e.map(e=>navWireBar(e))),NAV_CHOICE_WIREFRAMES={"mega-menu":()=>E("span",{class:"aurora-nav-wire"},[navTopBar(1),navUnderlay(["70%","90%","55%","80%"]),E("span",{class:"aurora-nav-wire-panel is-full"},[E("span",{class:"aurora-nav-wire-col"},[navWireBar("80%"),navWireBar("60%")]),E("span",{class:"aurora-nav-wire-col"},[navWireBar("70%"),navWireBarActive("85%")]),E("span",{class:"aurora-nav-wire-col"},[navWireBar("60%"),navWireBar("70%")])])]),dropdown:()=>E("span",{class:"aurora-nav-wire"},[navTopBar(1),navUnderlay(["70%","90%","55%","80%"]),E("span",{class:"aurora-nav-wire-panel is-anchored"},[E("span",{class:"aurora-nav-wire-col"},[navWireBar("85%"),navWireBarActive("65%"),navWireBar("75%")])])]),sidebar:()=>E("span",{class:"aurora-nav-wire aurora-nav-wire-side"},[E("span",{class:"aurora-nav-wire-rail"},[navWireBar("70%"),navWireBar("90%"),E("span",{class:"aurora-nav-wire-bar is-active",style:"width:75%;"}),E("span",{class:"aurora-nav-wire-bar is-child",style:"width:80%;"}),navWireBar("65%")]),E("span",{class:"aurora-nav-wire-main"},[navWireBar("55%"),E("span",{class:"aurora-nav-wire-bar is-dim",style:"width:85%;"}),E("span",{class:"aurora-nav-wire-bar is-dim",style:"width:70%;"})])])},ensureNavChoiceStyles=()=>{document.getElementById("aurora-nav-choice-styles")||document.head.appendChild(E("style",{id:"aurora-nav-choice-styles"},"\n/* Sized to their content and left-aligned: three choices have no reason to\n span the full row, and letting them stretch flattened the thumbnails into\n 6:1 letterboxes where a sidebar rail no longer read as a sidebar. */\n.aurora-nav-choices {\n display: flex;\n flex-wrap: wrap;\n gap: .75rem;\n}\n.aurora-nav-choices > .cbi-radio {\n border: 1px solid var(--hairline);\n border-radius: calc(var(--radius-base) * 1.5);\n cursor: pointer;\n display: block;\n flex: 0 0 auto;\n padding: .5rem;\n width: 12rem;\n}\n.aurora-nav-choices > .cbi-radio:has(input:checked) {\n border-color: var(--brand);\n box-shadow: 0 0 0 1px var(--brand);\n}\n.aurora-nav-choices > .cbi-radio:focus-within {\n outline: 2px solid var(--focus-ring);\n outline-offset: 2px;\n}\n/* Locked to the shape of a screen, never to the shape of its card. */\n.aurora-nav-wire {\n aspect-ratio: 4 / 3;\n background: var(--bg);\n border: 1px solid var(--hairline);\n border-radius: var(--radius-base);\n display: block;\n margin-bottom: .45rem;\n /* Clips the sidebar rail to the rounded corners; without it the rail's\n square top-left leaves a notch in the frame. */\n overflow: hidden;\n padding: .3rem;\n position: relative;\n}\n.aurora-nav-wire-top {\n align-items: center;\n display: flex;\n gap: .18rem;\n height: .5rem;\n}\n.aurora-nav-wire-menu {\n display: flex;\n flex: 1;\n gap: .18rem;\n justify-content: center;\n}\n.aurora-nav-wire-logo,\n.aurora-nav-wire-item,\n.aurora-nav-wire-bar {\n background: var(--hairline);\n border-radius: 1px;\n}\n.aurora-nav-wire-logo {\n background: var(--text-subtle);\n height: .2rem;\n width: .5rem;\n}\n.aurora-nav-wire-item {\n height: .2rem;\n width: .4rem;\n}\n/* A square, because that corner of the header holds the search icon -- a wide\n bar there reads as one more menu item. */\n.aurora-nav-wire-icon {\n background: var(--hairline);\n border-radius: 1px;\n height: .22rem;\n width: .22rem;\n}\n.aurora-nav-wire-bar {\n height: .18rem;\n}\n.aurora-nav-wire-item.is-active,\n.aurora-nav-wire-bar.is-active {\n background: var(--brand);\n}\n.aurora-nav-wire-bar.is-child {\n margin-left: 18%;\n}\n.aurora-nav-wire-bar.is-dim {\n opacity: .5;\n}\n.aurora-nav-wire-underlay {\n display: flex;\n flex-direction: column;\n gap: .18rem;\n left: .3rem;\n opacity: .3;\n position: absolute;\n right: .3rem;\n top: 1rem;\n}\n/* The panel floats above the page: that is the whole distinction between a\n full-width sheet and a dropdown anchored under one item. */\n.aurora-nav-wire-panel {\n background: var(--surface);\n border: 1px solid var(--hairline);\n border-radius: 3px;\n box-shadow: 0 2px 5px rgba(0, 0, 0, .16);\n display: flex;\n gap: .25rem;\n padding: .25rem;\n position: absolute;\n top: .95rem;\n}\n.aurora-nav-wire-panel.is-full {\n left: .3rem;\n right: .3rem;\n}\n.aurora-nav-wire-panel.is-anchored {\n right: 22%;\n width: 38%;\n}\n.aurora-nav-wire-col,\n.aurora-nav-wire-main {\n display: flex;\n flex: 1;\n flex-direction: column;\n gap: .18rem;\n}\n.aurora-nav-wire-side {\n display: flex;\n gap: .25rem;\n padding: 0;\n}\n.aurora-nav-wire-rail {\n background: var(--surface);\n border-right: 1px solid var(--hairline);\n display: flex;\n flex-direction: column;\n gap: .2rem;\n padding: .3rem;\n width: 32%;\n}\n.aurora-nav-wire-main {\n gap: .2rem;\n padding: .3rem .3rem .3rem 0;\n}\n"))},renderNavChoiceWidget=function(e,t,r){ensureNavChoiceStyles();const a=form.ListValue.prototype.renderWidget.apply(this,arguments);return a.classList.add("aurora-nav-choices"),Array.from(a.childNodes).filter(e=>e.nodeType!==Node.ELEMENT_NODE).forEach(e=>a.removeChild(e)),a.querySelectorAll("input[type=radio]").forEach(e=>{const t=NAV_CHOICE_WIREFRAMES[e.value];if(!t||!e.parentNode)return;const r=t();r.addEventListener("click",()=>e.click()),e.parentNode.insertBefore(r,e)}),a},renderColorField=function(e,t,r){const a=form.Value.prototype.render.apply(this,[e,t,r]);return Promise.resolve(a).then(e=>{const r=e.querySelector('input[type="text"]');if(!r)return e;const a=this.colorToken,n=this.colorMode,o=this.colorEditor,i=colorOptionName(n,a.key),s=o.presetColors?.[i]||"";e.dataset.auroraColorMode=n,e.dataset.auroraColorKind=a.derived?"derived":"base",e.dataset.auroraColorGroup=a.group||"",r.placeholder=a.derived?_("Automatic"):s||_("Saved or preset value");const l=E("input",{type:"color",value:"#000000",style:"position:absolute;inset:0;width:100%;height:100%;margin:0;padding:0;border:0;opacity:0;cursor:pointer;",title:_("Choose a literal color for this token")}),d=E("span",{style:"position:relative;display:inline-block;width:2rem;height:2rem;margin-left:.5rem;border:1px solid currentColor;border-radius:.5rem;vertical-align:middle;background:transparent;overflow:hidden;cursor:pointer;",title:_("Resolved preview color - click to pick")},[l]),c=E("small",{style:"display:block;margin-top:.35rem;opacity:.75;"}),u=E("span",{},[d]);return r.parentNode.appendChild(u),r.parentNode.appendChild(c),l.addEventListener("change",()=>{try{r.value=l.value,r.dispatchEvent(new Event("input"))}catch(e){r.setCustomValidity(_("Unable to convert the selected color."))}}),o.register(n,a,e,r,this,t,{picker:l,status:c,swatch:d}),e})},addColorInputs=(e,t,r,a)=>{r.forEach(r=>{const n=colorOptionName(t,r.key),o=e.option(form.Value,n,r.label);o.rmempty=!0,r.description&&(o.description=r.description),o.colorEditor=a,o.colorMode=t,o.colorToken=r,o.render=renderColorField,o.validate=(e,n)=>a.validate(t,r.key,n),o.write=(e,t)=>{const r=t?.trim();r?uci.set("aurora",e,n,toRuntimeColor(r)):uci.unset("aurora",e,n)},o.remove=e=>{uci.unset("aurora",e,n)}})},createColorSections=(e,t,r)=>{addColorInputs({option:(...r)=>e.taboption(t,...r)},t,COLOR_TOKENS,r);const a=e.taboption(t,form.SectionValue,`_${t}_derived_colors`,form.NamedSection,"theme","aurora");addColorInputs(a.subsection,t,DERIVED_COLOR_GROUPS.flatMap(e=>DERIVED_COLOR_TOKENS.filter(t=>t.group===e.key)),r)},colorGroupFor=(e,t)=>("derived"===e?DERIVED_COLOR_GROUPS:COLOR_GROUPS).find(e=>e.key===t),ensureColorGroupStyles=()=>{document.getElementById("aurora-color-group-styles")||document.head.appendChild(E("style",{id:"aurora-color-group-styles"},'\n/* Both folds -- the per-group headings and the derived block -- share one\n summary treatment. They differ only in how they draw their chevron: the\n groups use a real button so a stray click cannot collapse them, the derived\n block is a plain marker. */\n.aurora-token-group,\n.aurora-derived-fold {\n margin: 0;\n}\n.aurora-token-group > summary,\n.aurora-derived-fold > summary {\n align-items: center;\n border-bottom: 1px solid var(--hairline);\n display: flex;\n gap: 1rem;\n list-style: none;\n padding: 1rem 0 .75rem;\n}\n.aurora-token-group > summary::-webkit-details-marker,\n.aurora-derived-fold > summary::-webkit-details-marker {\n display: none;\n}\n.aurora-token-group > summary {\n cursor: default;\n justify-content: space-between;\n}\n.aurora-derived-fold > summary {\n cursor: pointer;\n}\n.aurora-derived-fold > summary::before {\n color: var(--text-muted);\n content: "\\25B8";\n transition: rotate .2s ease;\n}\n.aurora-derived-fold[open] > summary::before {\n rotate: 90deg;\n}\n.aurora-token-group-title,\n.aurora-derived-fold-title {\n display: block;\n font-size: 1rem;\n font-weight: 700;\n}\n.aurora-token-group-description,\n.aurora-derived-fold-description {\n color: var(--text-muted);\n display: block;\n font-size: .875rem;\n line-height: 1.45;\n}\n.aurora-token-group-description {\n margin-top: .25rem;\n}\n.aurora-token-group-body {\n padding: 1rem 0;\n}\n/* Box + interaction reset; the chevron glyph is reused from the Aurora theme\'s\n .navigation-group-toggle::after, so no SVG is duplicated here. */\n.aurora-token-group-toggle {\n align-items: center;\n appearance: none;\n background: transparent;\n border: 0;\n border-radius: 999px;\n color: var(--text-muted);\n cursor: pointer;\n display: inline-flex;\n flex-shrink: 0;\n height: 1.75rem;\n justify-content: center;\n padding: 0;\n width: 1.75rem;\n}\n.aurora-token-group-toggle:hover {\n color: var(--text);\n}\n.aurora-token-group-toggle:focus-visible {\n outline: 2px solid var(--focus-ring);\n outline-offset: 2px;\n}\n.aurora-token-group-toggle::after {\n transition: rotate .25s ease;\n}\n.aurora-token-group[open] .aurora-token-group-toggle::after {\n rotate: 90deg;\n}\n/* Derived colours: 21 tokens nobody edits on most installs. As a tile grid the\n whole computed palette is visible at once instead of scrolling past 21\n near-identical rows, and each tile still holds its real input. */\n.aurora-derived-host {\n display: block;\n}\n.aurora-derived-host > .cbi-value-title {\n display: none;\n}\n/* The fold only groups rows. Left alone the theme gives this nested section the\n same card chrome as the page card around it -- a border, radius and shadow\n drawn a second time just inside the first. The #maincontent prefix is not\n decoration: the theme sets that chrome from "#maincontent .cbi-section", and\n a plain class selector loses to it. */\n#maincontent .aurora-derived-fold > .cbi-section {\n background: transparent;\n border: 0;\n border-radius: 0;\n box-shadow: none;\n margin: 0;\n padding: 0;\n}\n.aurora-derived-fold .aurora-token-group-body {\n display: grid;\n gap: .5rem;\n grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));\n}\n.aurora-tile {\n align-items: center;\n border: 1px solid var(--hairline);\n border-radius: var(--radius-base);\n display: grid;\n gap: 0 .5rem;\n grid-template-areas: "swatch title" "swatch field";\n grid-template-columns: auto 1fr;\n padding: .5rem;\n}\n/* The theme spaces stacked rows with ".cbi-value + .cbi-value{margin-top}".\n In a grid that is the gap\'s job, and the stray top margin left the first\n tile of each row 12px taller than its neighbours, since grid items stretch\n to the row height while the others start lower. Matching the adjacent-\n sibling specificity is what makes this stick. */\n.aurora-tile + .aurora-tile,\n.aurora-derived-fold .aurora-token-group-body > .cbi-value {\n margin: 0;\n}\n.aurora-tile > .aurora-tile-swatch {\n grid-area: swatch;\n}\n.aurora-tile > .cbi-value-title {\n font-size: .8125rem;\n grid-area: title;\n min-width: 0;\n overflow: hidden;\n text-align: left;\n text-overflow: ellipsis;\n white-space: nowrap;\n width: auto;\n}\n.aurora-tile > .cbi-value-field {\n grid-area: field;\n min-width: 0;\n}\n/* Width only. Shrinking the font and padding squashed the theme\'s pill input\n without shrinking its radius, so the tiles ended up with flattened capsules\n that matched nothing else on the page. */\n.aurora-tile input[type="text"] {\n width: 100%;\n}\n/* The description is the tile\'s tooltip instead; the validation message under\n the input is not hidden -- a rejected colour has to say so. */\n.aurora-tile .cbi-value-description {\n display: none;\n}\n'))},ensureToolbarStyles=()=>{document.getElementById("aurora-studio-head-styles")||document.head.appendChild(E("style",{id:"aurora-studio-head-styles"},'\n.aurora-studio-head {\n align-items: center;\n display: flex;\n flex-wrap: wrap;\n gap: 10px 16px;\n}\n.aurora-studio-versions {\n color: var(--text-subtle, var(--muted-foreground, color-mix(in srgb, currentColor 55%, transparent)));\n display: flex;\n flex: 1 1 auto;\n flex-wrap: wrap;\n font-size: .88em;\n align-items: center;\n gap: 4px 1.6em;\n min-width: 0;\n}\n.aurora-studio-versions b {\n color: var(--text-muted, var(--muted-foreground, color-mix(in srgb, currentColor 70%, transparent)));\n font-weight: 500;\n}\n.aurora-studio-versions .label {\n font-variant-numeric: tabular-nums;\n margin-left: .45em;\n}\n.aurora-studio-versions .arrow {\n margin: 0 .35em;\n}\n.aurora-studio-versions a {\n color: var(--brand, var(--primary, currentColor));\n font-weight: 500;\n text-decoration: none;\n}\n.aurora-studio-versions a:hover {\n text-decoration: underline;\n}\n.aurora-studio-acts {\n display: flex;\n gap: 8px;\n margin-left: auto;\n}\n.cbi-dropdown.aurora-studio-more {\n cursor: pointer;\n height: 38px;\n justify-content: center;\n min-width: 0;\n position: relative;\n width: 38px;\n}\n.cbi-dropdown.aurora-studio-more > ul.dropdown {\n left: auto;\n min-width: 0;\n right: 0;\n top: calc(100% + 6px);\n width: min(280px, calc(100vw - 32px));\n}\n.cbi-dropdown.aurora-studio-more:not([open]) > ul.dropdown {\n display: none;\n}\n.cbi-dropdown.aurora-studio-more > ul.dropdown > li.danger {\n color: var(--danger, var(--destructive, #c0392b));\n}\n.cbi-dropdown.aurora-studio-more > ul.dropdown > li.sep {\n background: var(--hairline, var(--border, color-mix(in srgb, currentColor 18%, transparent)));\n height: 1px;\n margin: 4px 0;\n min-height: 0;\n padding: 0;\n pointer-events: none;\n}\n@media (max-width: 600px) {\n .aurora-studio-acts {\n flex: 1 1 100%;\n }\n .aurora-studio-acts > .cbi-button:not(.aurora-studio-more) {\n flex: 1;\n min-height: 42px;\n }\n .cbi-dropdown.aurora-studio-more {\n height: 42px;\n width: 42px;\n }\n}\n@media (max-width: 600px), (hover: none) {\n .cbi-dropdown.aurora-studio-more > ul.dropdown > li[role="menuitem"] {\n min-height: 42px;\n }\n}\n'))},ensureBgCardStyles=()=>{document.getElementById("aurora-bg-card-styles")||document.head.appendChild(E("style",{id:"aurora-bg-card-styles"},'\n.bg-duo,\n.bg-pane,\n[data-bg-target] {\n --bgp-line: var(--hairline, var(--border, color-mix(in srgb, currentColor 18%, transparent)));\n --bgp-subtle: var(--text-subtle, var(--muted-foreground, color-mix(in srgb, currentColor 60%, transparent)));\n --bgp-muted: var(--text-muted, var(--muted-foreground, color-mix(in srgb, currentColor 65%, transparent)));\n --bgp-control: var(--control-bg, var(--input, color-mix(in srgb, currentColor 8%, transparent)));\n --bgp-surface: var(--surface, var(--background, Canvas));\n --bgp-bg: var(--bg, var(--background, Canvas));\n --bgp-text: var(--text, var(--foreground, CanvasText));\n --bgp-brand: var(--brand, var(--primary, currentColor));\n --bgp-radius: var(--radius-base, var(--radius, .5rem));\n}\n[data-bg-target].cbi-value {\n display: block;\n}\n[data-bg-target] > .cbi-value-title {\n display: none;\n}\n/* 双卡并排;窄屏落回单列 */\n.bg-duo {\n display: grid;\n gap: 1rem;\n grid-template-columns: 1fr 1fr;\n max-width: 760px;\n}\n@media (width < 680px) {\n .bg-duo {\n grid-template-columns: 1fr;\n }\n}\n.bg-duo [data-bg-target].cbi-value {\n background: var(--bgp-surface);\n border: 2px solid var(--bgp-line);\n border-radius: calc(var(--bgp-radius) * 1.8);\n cursor: pointer;\n margin: 0;\n padding: .8rem;\n transition: border-color .15s;\n}\n.bg-duo [data-bg-target].on {\n border-color: var(--bgp-brand);\n}\n.bg-card-head {\n align-items: center;\n display: flex;\n gap: 8px;\n justify-content: space-between;\n margin-bottom: .6rem;\n}\n.bg-card-title {\n font-size: .95rem;\n font-weight: 650;\n white-space: nowrap;\n}\n/* The one rule the whole component stands on: the preview\'s layers are\n absolute, so it has to be the positioned, sized, clipping ancestor. */\n.bg-preview {\n background: var(--bgp-bg);\n border: 1px solid var(--bgp-line);\n border-radius: var(--bgp-radius);\n height: 130px;\n overflow: hidden;\n position: relative;\n}\n/* 共享滑杆面板:跟随选中的卡 */\n.bg-pane {\n margin-top: 1rem;\n max-width: 620px;\n}\n.bg-srow {\n align-items: center;\n display: grid;\n gap: 12px;\n grid-template-columns: minmax(6.5em, max-content) 1fr 3.2em;\n margin-top: 10px;\n}\n.bg-srow label {\n font-size: .9rem;\n white-space: nowrap;\n}\n.bg-srow input[type="range"] {\n accent-color: var(--bgp-brand);\n width: 100%;\n}\n.bg-srow output {\n color: var(--bgp-muted);\n font-size: .85rem;\n font-variant-numeric: tabular-nums;\n text-align: right;\n}\n'))},enhanceColorTokenGroups=e=>{ensureColorGroupStyles();const t=Array.from(e.querySelectorAll("[data-aurora-color-group]"));new Set(t.map(e=>e.parentElement).filter(Boolean)).forEach(e=>{if("true"===e.dataset.auroraTokenGroupsEnhanced)return;const t=Array.from(e.children).filter(e=>e.dataset?.auroraColorGroup);if(0===t.length)return;e.dataset.auroraTokenGroupsEnhanced="true";let r=0;for(;r<t.length;){const a=t[r],n=a.dataset.auroraColorGroup,o=a.dataset.auroraColorKind,i=[];for(;r<t.length&&t[r].dataset.auroraColorGroup===n;)i.push(t[r]),r+=1;const s=colorGroupFor(o,n);if(!s)continue;const l=E("div",{class:"aurora-token-group-body"}),d=E("button",{type:"button",class:"aurora-token-group-toggle navigation-group-toggle","aria-label":_("Expand or collapse this group"),"aria-expanded":"true"}),c=E("summary",{},[E("span",{},[E("span",{class:"aurora-token-group-title"},s.title),E("span",{class:"aurora-token-group-description"},s.description)]),d]),u=E("details",{class:"aurora-token-group",open:""},[c,l]);let p=!0;const m=e=>{p=e,u.open!==e&&(u.open=e),d.setAttribute("aria-expanded",e?"true":"false")};d.addEventListener("click",e=>{e.preventDefault(),e.stopPropagation(),m(!p)}),c.addEventListener("click",e=>{e.target.closest(".aurora-token-group-toggle")||e.preventDefault()}),u.addEventListener("toggle",()=>{u.open!==p&&(u.open=p)}),e.insertBefore(u,a),i.forEach(e=>l.appendChild(e))}})},enhanceDerivedFold=e=>{const t=Array.from(e.querySelectorAll('[data-aurora-color-kind="derived"]'));new Set(t.map(e=>e.closest(".cbi-section")).filter(e=>e&&!e.dataset.auroraDerivedFolded)).forEach(e=>{e.dataset.auroraDerivedFolded="true";const t=E("summary",{},[E("span",{class:"aurora-derived-fold-title"},_("Derived Colors")),E("span",{class:"aurora-derived-fold-description"},_("Computed from the source colors; leave a field empty to keep its automatic value."))]),r=E("details",{class:"aurora-derived-fold"},[t]);e.parentNode.insertBefore(r,e),r.appendChild(e),r.parentElement?.classList.add("aurora-derived-host")}),t.forEach(e=>{if(e.dataset.auroraTileBuilt)return;const t=e.querySelector('input[type="color"]'),r=t?.parentElement;if(!r)return;e.dataset.auroraTileBuilt="true",e.classList.add("aurora-tile"),r.classList.add("aurora-tile-swatch"),r.style.marginLeft="0",e.insertBefore(r,e.firstChild);const a=e.querySelector(".cbi-value-description"),n=a?.textContent?.trim();n&&(e.title=n)})},createRangeControlRenderer=e=>function(t,r,a){const n=this,o=form.Value.prototype.render.apply(this,[t,r,a]);return Promise.resolve(o).then(t=>{const r=t.querySelector("input");if(r){r.type="hidden";const t=()=>"function"==typeof e.max?e.max():Number(e.max),a=parseFloat(String(r.value||n.default||"").trim()),o=a||e.default,i=t=>`${t.toFixed(e.precision)}rem`,s=()=>Number.isFinite(a)?Math.max(t(),a):t(),l=E("span",{style:`margin-left: 10px; min-width: ${e.displayWidth}px; display: inline-block;`},i(o)),d=e=>{r.value=i(e),l.textContent=r.value},c=E("input",{type:"range",min:e.min.toString(),max:s().toString(),step:e.step.toString(),value:o,style:"width: 200px; vertical-align: middle;",input:function(){d(parseFloat(this.value))}});if("function"==typeof e.max){const e=()=>{const e=s();c.max=e.toString(),parseFloat(c.value)>e&&(c.value=e,d(e))};window.addEventListener("resize",e)}r.parentNode.appendChild(c),r.parentNode.appendChild(l)}return t})},renderSpacingControl=createRangeControlRenderer({min:"-0.1",max:"0.5",step:"0.05",default:.25,precision:2,displayWidth:60}),viewportWidthInRem=()=>{const e=parseFloat(window.getComputedStyle(document.documentElement).fontSize),t=window.innerWidth*(23/24);return Math.max(Math.floor(t/e*10)/10,80)},renderContentWidthControl=createRangeControlRenderer({min:"72",max:viewportWidthInRem,step:"1",default:80,precision:1,displayWidth:80}),renderRadiusControl=createRangeControlRenderer({min:"0",max:"1.5",step:"0.125",default:.5,precision:3,displayWidth:70}),generateLqip=e=>new Promise(t=>{const r=new Image,a=e instanceof Blob,n=a?URL.createObjectURL(e):e,o=()=>{a&&URL.revokeObjectURL(n)};r.onload=()=>{const e=document.createElement("canvas");e.width=32,e.height=Math.round(r.naturalHeight*(32/r.naturalWidth)),e.getContext("2d").drawImage(r,0,0,e.width,e.height),o(),e.toBlob(e=>{if(!e)return void t(null);const r=new FileReader;r.onload=e=>t(e.target.result),r.onerror=()=>t(null),r.readAsDataURL(e)},"image/webp",.1)},r.onerror=()=>{o(),t(null)},r.src=n}),toBgUrl=e=>"url('/luci-static/aurora/images/"+e+"')",fromBgUrl=e=>{if(!e||"string"!=typeof e)return"";const t=e.match(/\/images\/([^')]+)/);return t?t[1]:""},IMAGE_NAME_RE=/\.(jpg|jpeg|png|gif|webp|avif|svg|bmp|ico)$/i,PNG_NAME_RE=/\.png$/i,ICO_NAME_RE=/\.ico$/i,isImageFile=e=>IMAGE_NAME_RE.test(e),makeIconListLoader=(e,{prepend:t=[],empty:r=[],valueForIcon:a=e=>e}={})=>function(n){return getIconsOnce().then(L.bind(function(o){const i=(o?.icons||[]).filter(e);return this.keylist=[],this.vallist=[],t.forEach(([e,t])=>this.value(e,t)),i.length>0?i.forEach(e=>this.value(a(e),e)):r.forEach(([e,t])=>this.value(e,t)),form.ListValue.prototype.load.apply(this,[n])},this))},persistDerivedTokens=e=>{e&&["light","dark"].forEach(t=>{const r=e.resolvedForMode(t);r&&Object.keys(r).forEach(e=>{uci.set("aurora","theme",`${t}_${e}`,toRuntimeColor(r[e]))})})},runSavePipeline=function(e,t){const r=L.bind(function(){return colorLibraryReady.catch(()=>{}).then(()=>this.colorEditor?.flush?.()).then(()=>this.super("handleSave",[e])).then(()=>persistDerivedTokens(this.colorEditor)).then(()=>uci.save())},this),a=()=>this.colorEditor?.cleanupPreview();return("function"==typeof this.prepareAuroraFonts?this.prepareAuroraFonts().then(r):r()).then(()=>L.resolveDefault(callWritePwaManifest(),{})).then(t).catch(e=>{throw a(),e})};return view.extend({handleSave:function(e){return runSavePipeline.call(this,e,()=>this.colorEditor?.cleanupPreview())},handleSaveApply:function(e,t){return runSavePipeline.call(this,e,()=>{this.colorEditor?.cleanupPreview(),ui.changes.apply("0"===t)})},handleReset:function(e){return this.colorEditor?.cleanupPreview(),this.super("handleReset",[e]).then(()=>{this.colorEditor?.schedule("light"),this.colorEditor?.schedule("dark")})},load:function(){return Promise.all([uci.load("aurora"),L.resolveDefault(callGetInitData(),{}),colorLibraryReady,L.resolveDefault(ui.menu.load(),null)]).then(([e,t,,r])=>{const a=readThemeConfigFromUci(),n={icons:Array.isArray(t?.icons)?t.icons:[],icon_sizes:t?.icon_sizes&&"object"==typeof t.icon_sizes?t.icon_sizes:{}};return Array.isArray(t?.icons)&&(_iconsPromise=Promise.resolve(n)),[e,{theme:a},t?.versions||{},{fonts:t?.fonts||{}},n,t?.theme_preset||{result:-1,colors:{}},t?.feed||{pm:"unknown",configured:!1,channel:""},r]})},render(e){const t=e[1]?.theme||{},r=e[2],a=e[3]?.fonts||{},n=e[5]?.colors||{},o=e[6]||{pm:"unknown",configured:!1,channel:""},i=e[7];this.colorEditor?.destroy();const s=createColorEditor(t,n);this.colorEditor=s;const l=new form.Map("aurora");let d;const c=this,u={sans:'"Lato", ui-sans-serif, system-ui, sans-serif',mono:'ui-monospace, "SF Mono", Menlo, Monaco, Consolas, monospace'},p={},m=e=>(p[e]||(p[e]=h(e)),p[e]),h=e=>{const t=a?.[e],r=(a?.custom||[]).filter(t=>t?.slot===e&&t?.name).map(e=>({name:"custom-"+e.name,label:e.family||e.name,source:_("Custom"),family:e.family||"",stack:e.stack||"",custom:!0,customName:e.name}));if(Array.isArray(t)&&t.length>0){const e=t.filter(e=>e?.name).map(e=>({name:e.name,label:e.label||e.name,source:e.source||"",family:e.family||"",stack:e.stack||""})).concat(r);if(e.length>0)return e}const n=u[e]||"";return n?[{name:"default",label:"sans"===e?"Lato":_("System Mono"),source:_("Built-in"),stack:n}].concat(r):r},g=e=>{const t=E("ul",{class:"dropdown",role:"menu",id:"aurora-studio-more-menu",tabindex:"-1"},e),r=E("div",{class:"cbi-dropdown cbi-button aurora-studio-more",role:"button",tabindex:"0",title:_("More"),"aria-label":_("More"),"aria-haspopup":"menu","aria-expanded":"false","aria-controls":t.id},[E("span",{"aria-hidden":"true"},"⋯"),t]),a=()=>Array.from(t.querySelectorAll('[role="menuitem"]')),n=e=>{r.contains(e.target)||o(!1)},o=e=>{r.removeAttribute("open"),r.setAttribute("aria-expanded","false"),document.removeEventListener("click",n),e&&r.focus()},i=()=>{r.setAttribute("open",""),r.setAttribute("aria-expanded","true"),document.addEventListener("click",n),a()[0].focus()};return r.addEventListener("click",e=>{t.contains(e.target)?o(!1):r.hasAttribute("open")?o(!0):i()}),r.addEventListener("keydown",e=>{const n=r.hasAttribute("open"),s="Enter"===e.key||" "===e.key;if("Escape"===e.key&&n)o(!0);else if("Tab"===e.key)o(!1);else if(n||!s&&"ArrowDown"!==e.key)if(n&&s&&t.contains(e.target))e.target.click();else{if(!n||!["ArrowDown","ArrowUp","Home","End"].includes(e.key))return;{const t=a(),r=t.indexOf(document.activeElement);t["Home"===e.key?0:"End"===e.key?t.length-1:(r+("ArrowDown"===e.key?1:-1)+t.length)%t.length].focus()}}else i();"Tab"!==e.key&&e.preventDefault()}),r},f=(e,t,r)=>E("span",e,[E("b",{},t),E("span",{class:r?"label success":"label"},r||_("Unknown"))]),b=E("div",{class:"aurora-studio-versions"},[f({id:"theme-version"},_("Theme"),r?.theme?.installed_version),f({id:"config-version"},_("Config"),r?.config?.installed_version)]);ensureToolbarStyles();const v=E("div",{class:"aurora-studio-head"},[b,(()=>{const e=E("li",{role:"menuitem",tabindex:"-1",click:ui.createHandlerFn(c,()=>L.resolveDefault(callExportConfig(),null).then(e=>{if(!e||0!==e.result)throw new Error(e?.error||_("Export failed"));const t=E("form",{method:"post",action:L.env.cgi_base+"/cgi-download",enctype:"application/x-www-form-urlencoded"},[E("input",{type:"hidden",name:"sessionid",value:rpc.getSessionID()}),E("input",{type:"hidden",name:"path",value:e.path}),E("input",{type:"hidden",name:"filename",value:e.filename||"aurora"})]);document.body.appendChild(t),t.submit(),t.parentNode.removeChild(t),ui.addNotification(null,E("p",_("Configuration exported successfully.")),"info")}).catch(e=>{ui.addNotification(null,E("p",_("Export failed: %s").format(e.message||e)),"error")}))},_("Export configuration")),t=E("li",{role:"menuitem",tabindex:"-1",click:ui.createHandlerFn(c,function(e){const t=e.currentTarget||e.target,r=t?.firstChild?.data;return ui.uploadFile(CONFIG_IMPORT_PATH,t).then(L.bind(function(e){if(!e?.name)throw new Error(_("No file selected or upload failed"));return t?.firstChild&&(t.firstChild.data=_("Checking file…")),fs.read(CONFIG_IMPORT_PATH)},this)).then(L.bind(function(e){const t=e||"";ui.showModal(_("Import Aurora Configuration?"),[E("p",{},_("Importing replaces /etc/config/aurora, applies all settings, and reloads the page. Uploaded images stay on disk.")),E("pre",{},t),E("div",{class:"right"},[E("button",{class:"btn",click:ui.createHandlerFn(this,()=>fs.remove(CONFIG_IMPORT_PATH).finally(ui.hideModal))},_("Cancel"))," ",E("button",{class:"btn cbi-button-action important",click:ui.createHandlerFn(this,()=>(ui.showModal(_("Importing..."),[E("p",{class:"spinning"},_("Applying..."))]),L.resolveDefault(callImportConfig(),{}).then(e=>{if(ui.hideModal(),0===e?.result)ui.addNotification(null,E("p",_("Configuration imported successfully.")),"info"),window.location.reload();else{const t=e?.error||_("Unknown error");ui.addNotification(null,E("p",_("Import failed: %s").format(t)),"error")}})))},_("Import"))])])},this)).catch(e=>(ui.addNotification(null,E("p",_("Import failed: %s").format(e.message||e)),"error"),L.resolveDefault(fs.remove(CONFIG_IMPORT_PATH),{}))).finally(()=>{t?.firstChild&&void 0!==r&&(t.firstChild.data=r)})})},_("Import configuration")),r=E("li",{role:"menuitem",tabindex:"-1",class:"danger",click:ui.createHandlerFn(c,()=>ui.showModal(_("Reset All Aurora Settings"),[E("p",{},_("Reset /etc/config/aurora to the packaged Default preset — colors, layout, typography, branding, navigation, and toolbar. Uploaded images stay on disk; custom selections not in the Default preset are cleared.")),E("div",{class:"right"},[E("button",{class:"btn",click:ui.hideModal},_("Cancel"))," ",E("button",{class:"btn cbi-button-negative",click:()=>(ui.showModal(_("Resetting..."),[E("p",{class:"spinning"},_("Restoring..."))]),L.resolveDefault(callResetDefaults(),{}).then(e=>{ui.hideModal(),0===e?.result?(window.location.reload(),ui.addNotification(null,E("p",_("Settings reset successfully.")),"info")):ui.addNotification(null,E("p",_("Error: %s").format(e?.error||_("Unknown"))),"error")}))},_("Reset"))])]))},_("Reset everything to defaults")),a=E("button",{class:"cbi-button cbi-button-action",click:()=>{window.location.href=L.url("admin/system/aurora/marketplace")+"?share=1"}},_("Share to the store"));return E("div",{class:"aurora-config-toolbar aurora-studio-acts"},[a,g([e,t,E("li",{role:"separator",class:"sep"}),r])])})()]);l.description=v;const y=feedCheck.pickPackageManagerPath(i),w=e=>{if(!e)return;const t=o.channel||"snapshots",a="opkg"===o.pm?"opkg":"apk";[["luci-theme-aurora","theme-version",r?.theme?.installed_version],["luci-app-aurora-config","config-version",r?.config?.installed_version]].forEach(([r,n,o])=>{const i=feedCheck.findManifestVersion(e,t,a,r);if(!feedCheck.isNewer(o,i))return;const s=b.querySelector("#"+n);if(!s||s.classList.contains("up"))return;const l=_("%s available").format(i);s.classList.add("up"),s.querySelector(".label").className="label warning",s.appendChild(E("span",{class:"arrow","aria-hidden":"true"},"→")),s.appendChild(y?E("a",{href:L.url(y)},[l]):E("span",{},[l]))})},k=l.section(form.NamedSection,"theme","aurora");k.tab("colors",_("Colors")),k.tab("layout_typography",_("Layout & Typography")),k.tab("icons_branding",_("Branding & Shortcuts"));const x=k.taboption("colors",form.SectionValue,"_colors",form.NamedSection,"theme","aurora").subsection;x.tab("light",_("Light Mode"),COLOR_TAB_HINT),x.tab("dark",_("Dark Mode"),COLOR_TAB_HINT),createColorSections(x,"light",s),createColorSections(x,"dark",s);const C=k.taboption("layout_typography",form.SectionValue,"_structure_layout",form.NamedSection,"theme","aurora",_("Layout"),_("Navigation, spacing, corner radius, and content width. Takes effect after Save & Apply.")).subsection;d=C.option(form.ListValue,"nav_type",_("Navigation Style")),d.description=_("Layout pattern for the primary navigation menu."),d.value("mega-menu",_("Mega Menu")),d.value("dropdown",_("Dropdown")),d.value("sidebar",_("Sidebar")),d.default="mega-menu",d.rmempty=!1,d.widget="radio",d.renderWidget=renderNavChoiceWidget,d=C.option(form.Value,"struct_spacing",_("Spacing Scale")),d.description=_("Base spacing unit that scales padding and gaps across the interface."),d.default="0.25rem",d.placeholder="0.25rem",d.rmempty=!1,d.render=renderSpacingControl,d=C.option(form.Value,"struct_radius_base",_("Corner Radius")),d.description=_("Base corner radius applied to buttons, inputs, cards, and surfaces."),d.default="0.5rem",d.placeholder="0.5rem",d.rmempty=!1,d.render=renderRadiusControl,d=C.option(form.Value,"struct_content_width_centered",_("Content Max Width")),d.description=_("Maximum width of the centered content area."),d.default="80rem",d.placeholder="80rem",d.rmempty=!1,d.render=renderContentWidthControl,d.retain=!0,d.depends("nav_type","mega-menu"),d.depends("nav_type","dropdown");const S=k.taboption("layout_typography",form.SectionValue,"_font_settings",form.NamedSection,"theme","aurora",_("Typography"),_("Sans-serif and monospace typefaces used across the theme. Pick a curated webfont — downloaded once after saving, from pinned, checksum-verified sources, then served locally — or upload your own .woff2 files.")).subsection,O=S.option(form.DummyValue,"_font_table",_("Custom Fonts"));O.rawhtml=!1,O.cfgvalue=()=>"",O.renderWidget=()=>{const e=a?.custom||[],t=/^[a-z]/,r=e=>{const r=e.replace(/\.woff2$/i,""),a=r.split(/[-_]+/).filter(e=>!/^(regular|bold|light|medium|semibold|thin|italic|normal|latin|\d+)$/i.test(e));return(a.length?a:[r]).map(e=>t.test(e)?e.charAt(0).toUpperCase()+e.slice(1):e).join(" ")},n=assetUpload.createAssetManager({badgeHeader:_("Slot"),emptyText:_("No custom fonts uploaded."),rows:e.map(e=>({preview:E("div",{style:"width:34px;height:34px;border-radius:0.4em;background:var(--surface-sunken);border:1px solid var(--hairline);display:flex;align-items:center;justify-content:center;font-weight:700;font-size:0.95em;font-family:"+("mono"===e.slot?"var(--font-mono)":"var(--font-sans)")+";"},"Aa"),name:e.family,badge:"sans"===e.slot?_("Sans-Serif"):_("Monospace"),size:e.size||0,onDelete:ui.createHandlerFn(c,()=>(e=>assetUpload.confirmDelete({title:_("Delete Custom Font"),message:_("Delete '%s'? The interface will fall back to the default typeface.").format(e.family)}).then(t=>{if(t)return L.resolveDefault(callRemoveFont(e.slot,e.name),{}).then(e=>{0===e?.result?window.location.reload():ui.addNotification(null,E("p",_("Delete failed: %s").format(e?.error||_("Unknown"))),"error")})}))(e))})),bar:{hint:_("Drop a .woff2 font here, or click to browse"),sub:_("Only .woff2 files up to 8MB are accepted."),accept:".woff2"},checkFile:e=>assetUpload.checkFile(e,{exts:["woff2"]}),form:{fields:e=>{const t=E("input",{type:"text",class:"cbi-input-text",placeholder:_("Font family name, e.g. MiSans"),value:r(e.name)}),a=E("select",{class:"cbi-input-select"},[E("option",{value:"sans"},_("Sans-Serif")),E("option",{value:"mono"},_("Monospace"))]);return{rows:[{label:_("Name"),control:t},{label:_("Slot"),control:a}],value:()=>({name:t.value.trim(),slot:a.value}),valid:()=>!!t.value.trim(),setDisabled:e=>{t.disabled=e,a.disabled=e}}}},upload:(e,t,r)=>assetUpload.uploadToRouter({tmpPath:"/tmp/aurora_font.tmp",file:e,onProgress:r}).then(()=>L.resolveDefault(callUploadFont(t.slot,t.name),{})).then(e=>{if(0!==e?.result)throw new Error(e?.error||_("Unknown"));window.location.reload()})});return E("div",{},[n,E("p",{style:"opacity:0.6;font-size:0.9em;margin:0.35em 0 0;"},_("Keep custom font files as small as possible, as they are stored in the router's limited flash storage."))])};const T={},R=e=>(e=>m(e).find(e=>"default"===e.name))(e)||m(e)[0],N=(e,r)=>{const a=m(r),n="struct_font_"+r,o=R(r),i=e.option(form.ListValue,n,"sans"===r?_("Sans-Serif Typeface"):_("Monospace Typeface"));i.description="sans"===r?_("Primary font for all interface text — headings, body, menus, forms, and tables."):_("Font for code, command output, and the system log viewer."),i.default=t[n]||o?.stack||"",i.rmempty=!1,a.forEach(e=>{e.stack&&i.value(e.stack,e.source?"%s (%s)".format(e.label,e.source):e.label)}),T[r]=i};N(S,"sans"),N(S,"mono");const A=e=>{const t=T[e]&&T[e].formvalue("theme")||R(e)?.stack||"",r=((e,t)=>m(e).find(e=>e.stack===t))(e,t)||R(e)||{name:"default"};return{preset:r.custom?"default":r.name||"default",stack:r.stack||t}},I=()=>{const e=A("sans"),t=A("mono");return{sans:e.preset,mono:t.preset,sansStack:e.stack,monoStack:t.stack}},D=e=>fetch("/luci-static/aurora/fonts/aurora-font.css?v="+Date.now(),{cache:"no-store"}).then(e=>{if(!e.ok)throw new Error(_("Font CSS file is not available"));return e.text()}).then(t=>{if(!t)throw new Error(_("Font CSS file is empty"));let r=document.getElementById("aurora-preview-fonts");r||(r=document.createElement("style"),r.id="aurora-preview-fonts",document.head.appendChild(r)),r.textContent=t,e.sansStack&&document.documentElement.style.setProperty("--font-sans",e.sansStack),e.monoStack&&document.documentElement.style.setProperty("--font-mono",e.monoStack)}),F=(e,t,r)=>{!e||t<=0||window.setTimeout(()=>{L.resolveDefault(callGetFontStatus(e),{}).then(a=>{if(a&&"done"===a.state){const e=I();e.sans===r.sans&&e.mono===r.mono&&("fallback"!==a.sans_status&&"fallback"!==a.mono_status||ui.addNotification(null,E("p",_("Typeface download failed after retries — the system font stack is in effect. Save again to retry.")),"warning"),D(r))}else a&&"missing"!==a.state&&F(e,t-1,r)})},1500)};this.prepareAuroraFonts=()=>{const e=I(),t=E("p",{class:"spinning"},_("Preparing selected typefaces..."));return ui.showModal(_("Preparing Typography"),[t]),callPrepareFont(e.sans,e.mono,e.sansStack).then(t=>{if(!t||0!==t.result)throw new Error(-1===t?.result?_("The router did not respond. Please try again."):t?.error||_("Unknown error"));return F(t.job_id,20,e),D(e)}).then(()=>{ui.hideModal()}).catch(e=>(ui.hideModal(),ui.addNotification(null,E("p",_("Typography preparation failed: ")+(e.message||String(e))),"warning"),Promise.reject(e)))};const P=k.taboption("icons_branding",form.SectionValue,"_asset_library",form.NamedSection,"theme","aurora",_("Brand Asset Library"),_("Upload and manage images for icons, favicons, PWA assets, and page backgrounds. Files are stored in <code>/www/luci-static/aurora/images/</code>.")).subsection.option(form.DummyValue,"_asset_table"," ");P.load=()=>getIconsOnce(),P.cfgvalue=(e,t)=>t?.icons||[];const M=["jpg","jpeg","png","webp","svg","ico"];P.render=function(e,t,r){return this.load(t).then(e=>{const r=this.cfgvalue(t,e),a=e?.icon_sizes||{},n=window.requestIdleCallback?e=>window.requestIdleCallback(e,{timeout:2e3}):e=>setTimeout(e,100),o=e=>{const t=E("div",{style:"width:34px;height:34px;border-radius:0.4em;background:var(--surface-sunken);border:1px solid var(--hairline);"});return n(()=>{generateLqip("/luci-static/aurora/images/"+e).then(e=>{e&&t.replaceWith(E("img",{src:e,style:"width:34px;height:34px;object-fit:cover;border-radius:0.4em;display:block;",alt:""}))})}),t},i=assetUpload.createAssetManager({badgeHeader:_("Type"),emptyText:_("No brand assets uploaded yet."),rows:r.map(e=>({preview:o(e),name:e,badge:assetUpload.extOf(e).toUpperCase(),size:a[e]||0,onDelete:ui.createHandlerFn(this,()=>(e=>assetUpload.confirmDelete({title:_("Delete Brand Asset"),message:_("Delete '%s' from /www/luci-static/aurora/images/? Theme settings that reference it may need updating.").format(e)}).then(t=>{if(t)return ui.showModal(_("Deleting…"),[E("p",{class:"spinning"},_("Please wait…"))]),L.resolveDefault(callRemoveIcon(e),{}).then(t=>{ui.hideModal(),0===t?.result?(ui.addNotification(null,E("p",_("Deleted: %s").format(e))),window.location.reload()):ui.addNotification(null,E("p",_("Failed to delete: %s").format(t?.error||_("Unknown"))),"error")})}))(e))})),bar:{hint:_("Drop image asset here, or click to browse"),sub:_("JPG · PNG · WebP · SVG · ICO · up to 8 MB each"),accept:".jpg,.jpeg,.png,.webp,.svg,.ico"},checkFile:e=>assetUpload.checkFile(e,{exts:M}),form:{fields:e=>{const t=E("input",{type:"text",class:"cbi-input-text",value:e.name}),a=E("p",{style:"color:var(--warning);font-size:0.9em;margin:0.35em 0 0;display:none;"},""),n=()=>{const e=t.value.trim();e&&r.includes(e)?(a.textContent=_("Will replace the existing '%s'.").format(e),a.style.display=""):a.style.display="none"};t.addEventListener("input",n),n();const o=E("div",{style:"font-size:0.82em;color:var(--text-muted);opacity:0.8;margin-top:0.35em;"},_("Name it login-bg.* to use it as the login page background."));return{rows:[{label:_("Filename (with extension)"),control:E("div",{},[t,a,o])}],value:()=>({name:t.value.trim()}),valid:()=>{const e=t.value.trim();return!!e&&!e.includes("/")&&!e.includes("..")&&M.includes(assetUpload.extOf(e))},setDisabled:e=>{t.disabled=e}}}},upload:(e,t,r)=>assetUpload.uploadToRouter({tmpPath:"/tmp/aurora_icon.tmp",file:e,onProgress:r}).then(()=>L.resolveDefault(callUploadIcon(t.name),{})).then(e=>{if(0!==e?.result)throw new Error(e?.error||_("Unknown"));/^login-bg\./i.test(t.name)&&localStorage.setItem("aurora.pending_bg",t.name),window.location.reload()})});return E("div",{"data-name":this.option},[i])})};const B=(e,{key:t,lqipKey:r,label:a,heading:n,previewKind:o,tunables:i,withUpload:s})=>{(i||[]).forEach(([t,,r,a,n])=>{const o=e.option(form.Value,t,"");o.rmempty=!0,o.cfgvalue=function(e){const a=uci.get("aurora",e,t)||"";return a.endsWith(r)?a.slice(0,-r.length):a},o.validate=function(e,t){const r=(t||"").trim();return!r||!(!/^\d{1,3}$/.test(r)||+r<a||+r>n)||_("Enter a number between %d and %d").format(a,n)},o.write=function(e,a){const n=(a||"").trim();n?uci.set("aurora",e,t,n+r):uci.unset("aurora",e,t)},o.render=function(){return form.Value.prototype.render.apply(this,arguments).then(e=>(e.style.display="none",e))}});let l=e.option(form.ListValue,t,a);l.rmempty=!0,l.load=makeIconListLoader(e=>isImageFile(e)&&!e.endsWith(".svg"),{prepend:[["",_("None")]],valueForIcon:toBgUrl}),l.cfgvalue=function(e){return uci.get("aurora",e,t)||""},l.write=function(e,a){if(!a)return uci.unset("aurora",e,t),void uci.unset("aurora",e,r);uci.set("aurora",e,t,a)};const d=l.render.bind(l);l.render=function(e,a,l){return ensureBgCardStyles(),d(e,a,l).then(e=>{e.dataset.bgTarget=t;const a=e.querySelector(".cbi-value-field")||e,l=e.querySelector("select"),d=a.firstElementChild,c=E("div",{class:"bg-card-head"},[E("span",{class:"bg-card-title"},n)]);a.insertBefore(c,a.firstChild),d&&c.appendChild(d),l&&l.addEventListener("click",e=>e.stopPropagation());const u=(e=>{const t=e=>E("div",{style:"position:absolute;"+e}),r=t("inset:0;background-size:cover;background-position:center;"),a=t("inset:0;display:flex;align-items:center;justify-content:center;color:var(--bgp-subtle);font-size:.85em;");a.textContent=_("No background selected");const n=[r,a];let o,i,s,l,d;return"admin"===e?(o=t("inset:0;background:var(--bgp-bg);"),i=t("inset:27% 5% 5% 5%;border-radius:8px;background:color-mix(in srgb, var(--bgp-bg) 55%, transparent);"),s=t("left:13%;right:13%;top:40%;border-radius:8px;padding:8px 12px;font-size:11px;border:1px solid var(--bgp-line);color:var(--bgp-text);"),s.textContent="Aa 123 · OpenWrt",l=t("top:0;left:0;right:0;height:30px;display:flex;align-items:center;gap:8px;padding:0 10px;font-size:10px;color:var(--bgp-text);border-bottom:1px solid var(--bgp-line);"),l.textContent="☰ OpenWrt",n.push(o,i,s,l)):(o=t("inset:0;background:var(--bgp-bg);opacity:0;"),n.push(o),d=t("left:27%;right:27%;top:22%;bottom:22%;border-radius:10px;background:var(--bgp-surface);border:1px solid var(--bgp-line);box-shadow:0 8px 24px #0004;padding:10px 12px;color:var(--bgp-text);font-size:11px;"),d.appendChild(E("div",{},"Aa 123")),["",""].forEach(()=>d.appendChild(E("div",{style:"height:9px;margin-top:6px;border-radius:5px;background:var(--bgp-control);border:1px solid var(--bgp-line);"}))),n.push(d)),{el:E("div",{class:"bg-preview"},n),setImage(e){r.style.backgroundImage=e?'url("'+e+'")':"",a.style.display=e?"none":"flex"},setVals(t){if(o.style.opacity=String(t.scrim/100),"admin"===e)return l.style.background="color-mix(in srgb, var(--bgp-bg) "+t.alpha+"%, transparent)",l.style.backdropFilter=l.style.webkitBackdropFilter="blur("+t.blur+"px) saturate(150%)",void(s.style.background="color-mix(in srgb, var(--bgp-surface) "+t.alpha+"%, transparent)");d.style.background="color-mix(in srgb, var(--bgp-surface) "+t.alpha+"%, transparent)",d.style.backdropFilter=d.style.webkitBackdropFilter="blur("+t.blur+"px) saturate(150%)"}}})(o);if(a.appendChild(u.el),s&&a.appendChild(E("div",{style:"color:var(--text-muted);font-size:0.85em;margin-top:0.4em;"},_("PNG · JPG · WebP · up to 8 MB"))),s){const e=e=>{const r=assetUpload.checkFile(e,{exts:["jpg","jpeg","png","webp"]});r.ok?assetUpload.uploadToRouter({tmpPath:"/tmp/aurora_icon.tmp",file:e}).then(()=>L.resolveDefault(callUploadIcon(e.name),{})).then(r=>{if(0!==r?.result)throw new Error(r?.error||_("Unknown"));localStorage.setItem("aurora.pending_bg",e.name),localStorage.setItem("aurora.pending_bg_key",t),window.location.reload()}).catch(e=>ui.addNotification(null,E("p",_("Upload failed: %s").format(e.message)),"error")):ui.addNotification(null,E("p",r.err),"error")};u.el.addEventListener("dragover",e=>{e.preventDefault(),u.el.style.outline="2px dashed var(--bgp-brand)"}),u.el.addEventListener("dragleave",()=>{u.el.style.outline=""}),u.el.addEventListener("drop",t=>{t.preventDefault(),u.el.style.outline="";const r=t.dataTransfer&&t.dataTransfer.files[0];r&&e(r)})}const p={},m=E("div",{"data-bg-pane":t}),h=()=>{u.setImage((e=>{const t=(e||"").match(/url\(["']?(.+?)["']?\)/);return t?t[1]:""})(l&&l.value)),u.setVals((()=>{const e={alpha:100,blur:0,scrim:0};return(i||[]).forEach(([t,,,,,r])=>{const a=t.endsWith("_alpha")?"alpha":t.endsWith("_blur")?"blur":"scrim";e[a]=void 0!==p[t]?p[t]:+r}),e})())};return(i||[]).forEach(([e,t,r,a,n,o])=>{const i=uci.get("aurora","theme",e)||"",s=i.endsWith(r)?i.slice(0,-r.length):i;p[e]=""===s?+o:+s;const l=E("output",{},p[e]+r),d=E("input",{type:"range",min:String(a),max:String(n),value:String(p[e]),"data-tunable":e});d.addEventListener("input",()=>{p[e]=+d.value,l.textContent=d.value+r;const t=themeFormField(e);t&&(t.value=+d.value===+o?"":d.value,t.dispatchEvent(new Event("change",{bubbles:!0}))),h()}),m.appendChild(E("div",{class:"bg-srow"},[E("label",{},t),d,l]))}),(i||[]).length&&a.appendChild(m),l&&l.addEventListener("change",function(){h();const e=themeFormField(r);if(!this.value)return void(e&&(e.value=""));const t=this.value.match(/url\(["']?(.+?)["']?\)/);t&&e&&generateLqip(t[1]).then(t=>{t&&e&&(e.value=t)})}),h(),e})};const c=e.option(form.Value,r,"");c.rmempty=!0,c.render=function(e,t,r){return form.Value.prototype.render.apply(this,arguments).then(e=>(e.style.display="none",e))}},V=k.taboption("icons_branding",form.SectionValue,"_background_settings",form.NamedSection,"theme","aurora",_("Page Backgrounds"),_("Pick wallpapers for the login page and the admin interface. Drag the sliders and watch the live preview; Save & Apply makes it real.")).subsection;B(V,{key:"struct_login_bg",lqipKey:"struct_login_bg_lqip",label:"",heading:_("Login Background"),previewKind:"login",tunables:[["struct_login_bg_alpha",_("Surface Opacity"),"%",50,100,"100"],["struct_login_bg_blur",_("Frosted Blur"),"px",0,40,"0"],["struct_login_bg_scrim",_("Backdrop Scrim"),"%",0,70,"0"]]}),B(V,{key:"struct_main_bg",lqipKey:"struct_main_bg_lqip",label:"",heading:_("Main Background"),previewKind:"admin",withUpload:!0,tunables:[["struct_main_bg_alpha",_("Surface Opacity"),"%",50,100,"67"],["struct_main_bg_blur",_("Frosted Blur"),"px",0,40,"20"],["struct_main_bg_scrim",_("Backdrop Scrim"),"%",0,70,"20"]]});const U=k.taboption("icons_branding",form.SectionValue,"_branding_settings",form.NamedSection,"theme","aurora",_("Site Branding"),_("Assign uploaded images to icons, favicons, and PWA metadata. Saved on Save or Save & Apply.")).subsection;d=U.option(form.ListValue,"logo_svg",_("Logo (SVG)")),d.description=_("SVG mark for the browser tab and login page."),d.default="logo.svg",d.rmempty=!1,d.load=makeIconListLoader(isImageFile),d=U.option(form.ListValue,"favicon_png",_("Favicon (PNG)")),d.description=_("PNG fallback when SVG favicons are unsupported."),d.rmempty=!0,d.load=makeIconListLoader(e=>PNG_NAME_RE.test(e),{prepend:[["",_("(None)")]]}),d=U.option(form.ListValue,"favicon_ico",_("Favicon (ICO / Legacy)")),d.description=_("Legacy ICO favicon fallback."),d.default="favicon.ico",d.rmempty=!1,d.load=makeIconListLoader(e=>ICO_NAME_RE.test(e)),[["pwa_apple_touch",_("Apple Touch Icon"),"apple-touch-icon.png",_("Home Screen icon for iOS and iPadOS.")],["pwa_icon_192",_("App Icon 192×192"),"app-icon-192x192.png",_("192×192 icon for the installable web app manifest.")],["pwa_icon_512",_("App Icon 512×512"),"app-icon-512x512.png",_("512×512 icon for the installable web app manifest.")]].forEach(function([e,t,r,a]){d=U.option(form.ListValue,e,t),d.description=a,d.default=r,d.rmempty=!1,d.load=makeIconListLoader(e=>isImageFile(e)&&!/\.svg$/i.test(e))});const K=k.taboption("icons_branding",form.SectionValue,"_toolbar_settings",form.NamedSection,"theme","aurora",_("Shortcut Toolbar"),_("Floating shortcut launcher shown along the right edge of the interface.")).subsection;d=K.option(form.Flag,"toolbar_enabled",_("Show Shortcut Toolbar")),d.description=_("Show the floating launcher on all pages."),d.default="1",d.rmempty=!1,d=K.option(form.SectionValue,"_toolbar_items",form.GridSection,"toolbar_item",_("Toolbar Shortcuts"),_("Add shortcuts, assign icons, and drag rows to reorder them.")),d.depends("toolbar_enabled","1");const W=d.subsection;return W.addremove=!0,W.sortable=!0,W.anonymous=!0,W.nodescriptions=!0,d=W.option(form.Flag,"enabled",_("Enabled")),d.default="1",d.rmempty=!1,d.editable=!0,d=W.option(form.Value,"title",_("Shortcut Label")),d.rmempty=!1,d.placeholder=_("e.g., Network Interfaces"),d.validate=(e,t)=>!!t?.trim()||_("Enter a shortcut label"),d=W.option(form.Value,"url",_("Shortcut URL")),d.rmempty=!1,d.placeholder="/cgi-bin/luci/admin/...",d.validate=(e,t)=>!!t?.trim()||_("Enter a LuCI shortcut URL"),d=W.option(form.ListValue,"icon",_("Icon")),d.rmempty=!1,d.load=makeIconListLoader(()=>!0,{empty:[["",_("(No icons uploaded)")]]}),d.validate=(e,t)=>!!t?.trim()||_("Choose an uploaded icon for this shortcut"),l.render().then(e=>{s.attach(),enhanceColorTokenGroups(e),enhanceDerivedFold(e),requestAnimationFrame(()=>{const e=localStorage.getItem("aurora.pending_bg"),t=localStorage.getItem("aurora.pending_bg_key")||"struct_login_bg";[["struct_login_bg","struct_login_bg_lqip"],["struct_main_bg","struct_main_bg_lqip"]].forEach(([r,a])=>{const n=themeFormField(r),o=themeFormField(a);if(n&&o){if(e&&t===r){localStorage.removeItem("aurora.pending_bg"),localStorage.removeItem("aurora.pending_bg_key");const t=toBgUrl(e);if(n.querySelector(`option[value="${t}"]`))return n.value=t,void n.dispatchEvent(new Event("change"))}if(n.value&&!o.value){const e=n.value.match(/url\(["']?(.+?)["']?\)/);e&&generateLqip(e[1]).then(e=>{e&&(o.value=e)})}}})});const t=Array.from(e.querySelectorAll("[data-bg-target]"));if(2===t.length){const r=E("div",{class:"bg-duo"});t[0].parentNode.insertBefore(r,t[0]),t.forEach(e=>r.appendChild(e));const a=E("div",{class:"bg-pane"});r.parentNode.insertBefore(a,r.nextSibling),e.querySelectorAll("[data-bg-pane]").forEach(e=>a.appendChild(e));const n=e=>{t.forEach(t=>t.classList.toggle("on",t.dataset.bgTarget===e)),a.querySelectorAll("[data-bg-pane]").forEach(t=>{t.style.display=t.dataset.bgPane===e?"":"none"})};t.forEach(e=>e.addEventListener("click",()=>n(e.dataset.bgTarget))),n("struct_main_bg")}return(()=>{if(!o.configured)return Promise.resolve(null);const e=sessionStorage.getItem("aurora.manifest");if(e)try{return Promise.resolve(JSON.parse(e))}catch(e){sessionStorage.removeItem("aurora.manifest")}return fetch(MANIFEST_URL,{credentials:"omit"}).then(e=>e.ok?e.json():null).then(e=>(e&&sessionStorage.setItem("aurora.manifest",JSON.stringify(e)),e)).catch(()=>null)})().then(w),e})}});