@camstack/ui-library 1.1.11 → 1.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -7834,6 +7834,30 @@ var Activity = createLucideIcon("activity", [["path", {
7834
7834
  * This source code is licensed under the ISC license.
7835
7835
  * See the LICENSE file in the root directory of this source tree.
7836
7836
  */
7837
+ var AirVent = createLucideIcon("air-vent", [
7838
+ ["path", {
7839
+ d: "M18 17.5a2.5 2.5 0 1 1-4 2.03V12",
7840
+ key: "yd12zl"
7841
+ }],
7842
+ ["path", {
7843
+ d: "M6 12H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2",
7844
+ key: "larmp2"
7845
+ }],
7846
+ ["path", {
7847
+ d: "M6 8h12",
7848
+ key: "6g4wlu"
7849
+ }],
7850
+ ["path", {
7851
+ d: "M6.6 15.572A2 2 0 1 0 10 17v-5",
7852
+ key: "1x1kqn"
7853
+ }]
7854
+ ]);
7855
+ /**
7856
+ * @license lucide-react v0.576.0 - ISC
7857
+ *
7858
+ * This source code is licensed under the ISC license.
7859
+ * See the LICENSE file in the root directory of this source tree.
7860
+ */
7837
7861
  var AlarmSmoke = createLucideIcon("alarm-smoke", [
7838
7862
  ["path", {
7839
7863
  d: "M11 21c0-2.5 2-2.5 2-5",
@@ -7954,6 +7978,21 @@ var ArrowUp = createLucideIcon("arrow-up", [["path", {
7954
7978
  * This source code is licensed under the ISC license.
7955
7979
  * See the LICENSE file in the root directory of this source tree.
7956
7980
  */
7981
+ var Ban = createLucideIcon("ban", [["circle", {
7982
+ cx: "12",
7983
+ cy: "12",
7984
+ r: "10",
7985
+ key: "1mglay"
7986
+ }], ["path", {
7987
+ d: "M4.929 4.929 19.07 19.071",
7988
+ key: "196cmz"
7989
+ }]]);
7990
+ /**
7991
+ * @license lucide-react v0.576.0 - ISC
7992
+ *
7993
+ * This source code is licensed under the ISC license.
7994
+ * See the LICENSE file in the root directory of this source tree.
7995
+ */
7957
7996
  var Bath = createLucideIcon("bath", [
7958
7997
  ["path", {
7959
7998
  d: "M10 4 8 6",
@@ -9108,6 +9147,26 @@ var Fan = createLucideIcon("fan", [["path", {
9108
9147
  * This source code is licensed under the ISC license.
9109
9148
  * See the LICENSE file in the root directory of this source tree.
9110
9149
  */
9150
+ var Feather = createLucideIcon("feather", [
9151
+ ["path", {
9152
+ d: "M12.67 19a2 2 0 0 0 1.416-.588l6.154-6.172a6 6 0 0 0-8.49-8.49L5.586 9.914A2 2 0 0 0 5 11.328V18a1 1 0 0 0 1 1z",
9153
+ key: "18jl4k"
9154
+ }],
9155
+ ["path", {
9156
+ d: "M16 8 2 22",
9157
+ key: "vp34q"
9158
+ }],
9159
+ ["path", {
9160
+ d: "M17.5 15H9",
9161
+ key: "1oz8nu"
9162
+ }]
9163
+ ]);
9164
+ /**
9165
+ * @license lucide-react v0.576.0 - ISC
9166
+ *
9167
+ * This source code is licensed under the ISC license.
9168
+ * See the LICENSE file in the root directory of this source tree.
9169
+ */
9111
9170
  var Film = createLucideIcon("film", [
9112
9171
  ["rect", {
9113
9172
  width: "18",
@@ -9549,6 +9608,19 @@ var LayoutGrid = createLucideIcon("layout-grid", [
9549
9608
  * This source code is licensed under the ISC license.
9550
9609
  * See the LICENSE file in the root directory of this source tree.
9551
9610
  */
9611
+ var Leaf = createLucideIcon("leaf", [["path", {
9612
+ d: "M11 20A7 7 0 0 1 9.8 6.1C15.5 5 17 4.48 19 2c1 2 2 4.18 2 8 0 5.5-4.78 10-10 10Z",
9613
+ key: "nnexq3"
9614
+ }], ["path", {
9615
+ d: "M2 21c0-3 1.85-5.36 5.08-6C9.5 14.52 12 13 13 12",
9616
+ key: "mt58a7"
9617
+ }]]);
9618
+ /**
9619
+ * @license lucide-react v0.576.0 - ISC
9620
+ *
9621
+ * This source code is licensed under the ISC license.
9622
+ * See the LICENSE file in the root directory of this source tree.
9623
+ */
9552
9624
  var Lightbulb = createLucideIcon("lightbulb", [
9553
9625
  ["path", {
9554
9626
  d: "M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5",
@@ -9953,6 +10025,46 @@ var MousePointerClick = createLucideIcon("mouse-pointer-click", [
9953
10025
  * This source code is licensed under the ISC license.
9954
10026
  * See the LICENSE file in the root directory of this source tree.
9955
10027
  */
10028
+ var MoveHorizontal = createLucideIcon("move-horizontal", [
10029
+ ["path", {
10030
+ d: "m18 8 4 4-4 4",
10031
+ key: "1ak13k"
10032
+ }],
10033
+ ["path", {
10034
+ d: "M2 12h20",
10035
+ key: "9i4pu4"
10036
+ }],
10037
+ ["path", {
10038
+ d: "m6 8-4 4 4 4",
10039
+ key: "15zrgr"
10040
+ }]
10041
+ ]);
10042
+ /**
10043
+ * @license lucide-react v0.576.0 - ISC
10044
+ *
10045
+ * This source code is licensed under the ISC license.
10046
+ * See the LICENSE file in the root directory of this source tree.
10047
+ */
10048
+ var MoveVertical = createLucideIcon("move-vertical", [
10049
+ ["path", {
10050
+ d: "M12 2v20",
10051
+ key: "t6zp3m"
10052
+ }],
10053
+ ["path", {
10054
+ d: "m8 18 4 4 4-4",
10055
+ key: "bh5tu3"
10056
+ }],
10057
+ ["path", {
10058
+ d: "m8 6 4-4 4 4",
10059
+ key: "ybng9g"
10060
+ }]
10061
+ ]);
10062
+ /**
10063
+ * @license lucide-react v0.576.0 - ISC
10064
+ *
10065
+ * This source code is licensed under the ISC license.
10066
+ * See the LICENSE file in the root directory of this source tree.
10067
+ */
9956
10068
  var Move = createLucideIcon("move", [
9957
10069
  ["path", {
9958
10070
  d: "M12 2v20",
@@ -10784,6 +10896,88 @@ var Smartphone = createLucideIcon("smartphone", [["rect", {
10784
10896
  * This source code is licensed under the ISC license.
10785
10897
  * See the LICENSE file in the root directory of this source tree.
10786
10898
  */
10899
+ var Snowflake = createLucideIcon("snowflake", [
10900
+ ["path", {
10901
+ d: "m10 20-1.25-2.5L6 18",
10902
+ key: "18frcb"
10903
+ }],
10904
+ ["path", {
10905
+ d: "M10 4 8.75 6.5 6 6",
10906
+ key: "7mghy3"
10907
+ }],
10908
+ ["path", {
10909
+ d: "m14 20 1.25-2.5L18 18",
10910
+ key: "1chtki"
10911
+ }],
10912
+ ["path", {
10913
+ d: "m14 4 1.25 2.5L18 6",
10914
+ key: "1b4wsy"
10915
+ }],
10916
+ ["path", {
10917
+ d: "m17 21-3-6h-4",
10918
+ key: "15hhxa"
10919
+ }],
10920
+ ["path", {
10921
+ d: "m17 3-3 6 1.5 3",
10922
+ key: "11697g"
10923
+ }],
10924
+ ["path", {
10925
+ d: "M2 12h6.5L10 9",
10926
+ key: "kv9z4n"
10927
+ }],
10928
+ ["path", {
10929
+ d: "m20 10-1.5 2 1.5 2",
10930
+ key: "1swlpi"
10931
+ }],
10932
+ ["path", {
10933
+ d: "M22 12h-6.5L14 15",
10934
+ key: "1mxi28"
10935
+ }],
10936
+ ["path", {
10937
+ d: "m4 10 1.5 2L4 14",
10938
+ key: "k9enpj"
10939
+ }],
10940
+ ["path", {
10941
+ d: "m7 21 3-6-1.5-3",
10942
+ key: "j8hb9u"
10943
+ }],
10944
+ ["path", {
10945
+ d: "m7 3 3 6h4",
10946
+ key: "1otusx"
10947
+ }]
10948
+ ]);
10949
+ /**
10950
+ * @license lucide-react v0.576.0 - ISC
10951
+ *
10952
+ * This source code is licensed under the ISC license.
10953
+ * See the LICENSE file in the root directory of this source tree.
10954
+ */
10955
+ var Sparkles = createLucideIcon("sparkles", [
10956
+ ["path", {
10957
+ d: "M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z",
10958
+ key: "1s2grr"
10959
+ }],
10960
+ ["path", {
10961
+ d: "M20 2v4",
10962
+ key: "1rf3ol"
10963
+ }],
10964
+ ["path", {
10965
+ d: "M22 4h-4",
10966
+ key: "gwowj6"
10967
+ }],
10968
+ ["circle", {
10969
+ cx: "4",
10970
+ cy: "20",
10971
+ r: "2",
10972
+ key: "6kqj1y"
10973
+ }]
10974
+ ]);
10975
+ /**
10976
+ * @license lucide-react v0.576.0 - ISC
10977
+ *
10978
+ * This source code is licensed under the ISC license.
10979
+ * See the LICENSE file in the root directory of this source tree.
10980
+ */
10787
10981
  var Sprout = createLucideIcon("sprout", [
10788
10982
  ["path", {
10789
10983
  d: "M14 9.536V7a4 4 0 0 1 4-4h1.5a.5.5 0 0 1 .5.5V5a4 4 0 0 1-4 4 4 4 0 0 0-4 4c0 2 1 3 1 5a5 5 0 0 1-1 3",
@@ -14589,6 +14783,10 @@ var DEVICE_TYPE_META = {
14589
14783
  icon: Thermometer,
14590
14784
  label: "Thermostat"
14591
14785
  },
14786
+ [_camstack_types.DeviceType.Climate]: {
14787
+ icon: AirVent,
14788
+ label: "Air conditioner"
14789
+ },
14592
14790
  [_camstack_types.DeviceType.Button]: {
14593
14791
  icon: CircleDot,
14594
14792
  label: "Button"
@@ -14940,6 +15138,7 @@ var PRIORITY = [
14940
15138
  "media-player",
14941
15139
  "alarm-panel",
14942
15140
  "thermostat",
15141
+ "climate",
14943
15142
  "humidifier",
14944
15143
  "water-heater",
14945
15144
  "lock",
@@ -17206,6 +17405,8 @@ var useDeviceManagerGetCreationSchema = trpc.deviceManager.getCreationSchema.use
17206
17405
  var useDeviceManagerCreateDevice = trpc.deviceManager.createDevice.useMutation;
17207
17406
  /** Generated alias around `trpc.deviceManager.testCreationField.useMutation`. */
17208
17407
  var useDeviceManagerTestCreationField = trpc.deviceManager.testCreationField.useMutation;
17408
+ /** Generated alias around `trpc.deviceManager.adoptionListCandidateFilters.useQuery`. */
17409
+ var useDeviceManagerAdoptionListCandidateFilters = trpc.deviceManager.adoptionListCandidateFilters.useQuery;
17209
17410
  /** Generated alias around `trpc.deviceManager.adoptionListCandidates.useQuery`. */
17210
17411
  var useDeviceManagerAdoptionListCandidates = trpc.deviceManager.adoptionListCandidates.useQuery;
17211
17412
  /** Generated alias around `trpc.deviceManager.adoptionRefresh.useMutation`. */
@@ -17216,6 +17417,16 @@ var useDeviceManagerAdoptionAdopt = trpc.deviceManager.adoptionAdopt.useMutation
17216
17417
  var useDeviceManagerAdoptionRelease = trpc.deviceManager.adoptionRelease.useMutation;
17217
17418
  /** Generated alias around `trpc.deviceManager.adoptionResync.useMutation`. */
17218
17419
  var useDeviceManagerAdoptionResync = trpc.deviceManager.adoptionResync.useMutation;
17420
+ /** Generated alias around `trpc.deviceManager.discoveryProviders.useQuery`. */
17421
+ var useDeviceManagerDiscoveryProviders = trpc.deviceManager.discoveryProviders.useQuery;
17422
+ /** Generated alias around `trpc.deviceManager.discoverAllProviders.useMutation`. */
17423
+ var useDeviceManagerDiscoverAllProviders = trpc.deviceManager.discoverAllProviders.useMutation;
17424
+ /** Generated alias around `trpc.deviceManager.discoverProvider.useMutation`. */
17425
+ var useDeviceManagerDiscoverProvider = trpc.deviceManager.discoverProvider.useMutation;
17426
+ /** Generated alias around `trpc.deviceManager.providerCreationType.useQuery`. */
17427
+ var useDeviceManagerProviderCreationType = trpc.deviceManager.providerCreationType.useQuery;
17428
+ /** Generated alias around `trpc.deviceManager.providerDiscoveryParamsSchema.useQuery`. */
17429
+ var useDeviceManagerProviderDiscoveryParamsSchema = trpc.deviceManager.providerDiscoveryParamsSchema.useQuery;
17219
17430
  /** Generated alias around `trpc.deviceManager.testField.useMutation`. */
17220
17431
  var useDeviceManagerTestField = trpc.deviceManager.testField.useMutation;
17221
17432
  /** Generated alias around `trpc.deviceManager.getDeviceStatusAggregate.useQuery`. */
@@ -17246,6 +17457,10 @@ var useDeviceProviderGetDevices = trpc.deviceProvider.getDevices.useQuery;
17246
17457
  var useDeviceProviderSupportsDiscovery = trpc.deviceProvider.supportsDiscovery.useQuery;
17247
17458
  /** Generated alias around `trpc.deviceProvider.discoverDevices.useMutation`. */
17248
17459
  var useDeviceProviderDiscoverDevices = trpc.deviceProvider.discoverDevices.useMutation;
17460
+ /** Generated alias around `trpc.deviceProvider.getDiscoveryParamsSchema.useQuery`. */
17461
+ var useDeviceProviderGetDiscoveryParamsSchema = trpc.deviceProvider.getDiscoveryParamsSchema.useQuery;
17462
+ /** Generated alias around `trpc.deviceProvider.getManualCreationType.useQuery`. */
17463
+ var useDeviceProviderGetManualCreationType = trpc.deviceProvider.getManualCreationType.useQuery;
17249
17464
  /** Generated alias around `trpc.deviceProvider.adoptDiscoveredDevice.useMutation`. */
17250
17465
  var useDeviceProviderAdoptDiscoveredDevice = trpc.deviceProvider.adoptDiscoveredDevice.useMutation;
17251
17466
  /** Generated alias around `trpc.deviceProvider.supportsManualCreation.useQuery`. */
@@ -18893,78 +19108,163 @@ function vacuumGraphicTint(state) {
18893
19108
  };
18894
19109
  }
18895
19110
  }
19111
+ var ROBOT_KEYFRAMES = `
19112
+ @keyframes rg-breathe { 0%,100%{opacity:.78;transform:scale(1)} 50%{opacity:1;transform:scale(1.05)} }
19113
+ @keyframes rg-zzz { 0%{opacity:0;transform:translateY(3px)} 30%{opacity:.85} 100%{opacity:0;transform:translateY(-9px)} }
19114
+ @keyframes rg-spark { 0%,100%{opacity:.25;transform:scale(.9)} 50%{opacity:1;transform:scale(1.1)} }
19115
+ @keyframes rg-pulse { 0%,100%{opacity:.35} 50%{opacity:.85} }
19116
+ @keyframes rg-blink { 0%,49%{opacity:1} 50%,100%{opacity:.3} }
19117
+ @keyframes rg-spin { to { transform:rotate(360deg) } }
19118
+ @media (prefers-reduced-motion: reduce) { .rg-a { animation: none !important; } }
19119
+ `;
18896
19120
  /**
18897
19121
  * VacuumGraphic — a framed, top-down robot-vacuum illustration in the SAME
18898
19122
  * visual style as the cover window (w-28 h-36 rounded bordered box, gradient
18899
- * bg, overflow-hidden, inset white shadow). State-tinted via `vacuumGraphicTint`.
19123
+ * bg, overflow-hidden, inset white shadow). State-tinted via `vacuumGraphicTint`
19124
+ * and state-ANIMATED: it breathes + emits "Zzz" when dormant on the dock, shows
19125
+ * a pulsing electricity bolt while charging, blinks on error, and keeps the
19126
+ * expanding activity ring + a spinning turret while cleaning / returning.
18900
19127
  * Decorative only (`aria-hidden`); the textual state label lives in the hero.
18901
19128
  */
18902
- function VacuumGraphic({ state }) {
19129
+ function VacuumGraphic({ state, batteryLevel }) {
18903
19130
  const tint = vacuumGraphicTint(state);
19131
+ const charging = tint.docked && batteryLevel != null && batteryLevel < 100;
19132
+ const sleeping = tint.docked && !charging;
19133
+ const erroring = state === "error";
19134
+ const bodyStyle = sleeping ? {
19135
+ animation: "rg-breathe 3.4s ease-in-out infinite",
19136
+ transformBox: "fill-box",
19137
+ transformOrigin: "center"
19138
+ } : erroring ? { animation: "rg-blink 1s steps(1) infinite" } : void 0;
18904
19139
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
18905
19140
  "aria-hidden": "true",
18906
19141
  className: "relative w-28 h-36 rounded border-2 border-border bg-gradient-to-b from-surface-hover to-surface overflow-hidden flex-shrink-0",
18907
19142
  style: { boxShadow: "inset 0 0 0 2px rgba(255,255,255,.06)" },
18908
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: cn("absolute inset-0 transition-colors duration-300", tint.glow) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
18909
- className: cn("absolute inset-0 flex items-center justify-center", tint.accent),
18910
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
18911
- viewBox: "0 0 100 100",
18912
- className: "w-20 h-20",
18913
- fill: "none",
18914
- children: [
18915
- tint.docked && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
18916
- x: "34",
18917
- y: "80",
18918
- width: "32",
18919
- height: "8",
18920
- rx: "2",
18921
- fill: "currentColor",
18922
- opacity: "0.35"
18923
- }),
18924
- tint.active && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
18925
- cx: "50",
18926
- cy: "50",
18927
- r: "44",
18928
- stroke: "currentColor",
18929
- strokeWidth: "2",
18930
- opacity: "0.4",
18931
- className: "animate-ping",
18932
- style: { transformOrigin: "center" }
18933
- }),
18934
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
18935
- cx: "50",
18936
- cy: "50",
18937
- r: "34",
18938
- fill: "currentColor",
18939
- fillOpacity: "0.12",
18940
- stroke: "currentColor",
18941
- strokeWidth: "2.5"
18942
- }),
18943
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
18944
- d: "M 24 42 A 34 34 0 0 1 76 42",
18945
- stroke: "currentColor",
18946
- strokeWidth: "3",
18947
- opacity: "0.55",
18948
- strokeLinecap: "round"
18949
- }),
18950
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
18951
- cx: "50",
18952
- cy: "50",
18953
- r: "9",
18954
- fill: "currentColor",
18955
- fillOpacity: "0.25",
18956
- stroke: "currentColor",
18957
- strokeWidth: "2"
18958
- }),
18959
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
18960
- cx: "50",
18961
- cy: "50",
18962
- r: "3",
18963
- fill: "currentColor"
18964
- })
18965
- ]
19143
+ children: [
19144
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: ROBOT_KEYFRAMES }),
19145
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: cn("absolute inset-0 transition-colors duration-300", tint.glow) }),
19146
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
19147
+ className: cn("absolute inset-0 flex items-center justify-center", tint.accent),
19148
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
19149
+ viewBox: "0 0 100 100",
19150
+ className: "w-20 h-20",
19151
+ fill: "none",
19152
+ children: [
19153
+ tint.docked && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
19154
+ x: "34",
19155
+ y: "80",
19156
+ width: "32",
19157
+ height: "8",
19158
+ rx: "2",
19159
+ fill: "currentColor",
19160
+ opacity: "0.35",
19161
+ className: cn(charging && "rg-a"),
19162
+ style: charging ? { animation: "rg-pulse 1.2s ease-in-out infinite" } : void 0
19163
+ }),
19164
+ tint.active && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
19165
+ cx: "50",
19166
+ cy: "50",
19167
+ r: "44",
19168
+ stroke: "currentColor",
19169
+ strokeWidth: "2",
19170
+ opacity: "0.4",
19171
+ className: "animate-ping",
19172
+ style: { transformOrigin: "center" }
19173
+ }),
19174
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", {
19175
+ className: cn((sleeping || erroring) && "rg-a"),
19176
+ style: bodyStyle,
19177
+ children: [
19178
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
19179
+ cx: "50",
19180
+ cy: "50",
19181
+ r: "34",
19182
+ fill: "currentColor",
19183
+ fillOpacity: "0.12",
19184
+ stroke: "currentColor",
19185
+ strokeWidth: "2.5"
19186
+ }),
19187
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
19188
+ d: "M 24 42 A 34 34 0 0 1 76 42",
19189
+ stroke: "currentColor",
19190
+ strokeWidth: "3",
19191
+ opacity: "0.55",
19192
+ strokeLinecap: "round"
19193
+ }),
19194
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", {
19195
+ className: cn(tint.active && "rg-a"),
19196
+ style: tint.active ? {
19197
+ animation: "rg-spin 3s linear infinite",
19198
+ transformBox: "fill-box",
19199
+ transformOrigin: "center"
19200
+ } : void 0,
19201
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
19202
+ cx: "50",
19203
+ cy: "50",
19204
+ r: "9",
19205
+ fill: "currentColor",
19206
+ fillOpacity: "0.25",
19207
+ stroke: "currentColor",
19208
+ strokeWidth: "2"
19209
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
19210
+ cx: "50",
19211
+ cy: "43",
19212
+ r: "2.4",
19213
+ fill: "currentColor"
19214
+ })]
19215
+ })
19216
+ ]
19217
+ }),
19218
+ sleeping && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", {
19219
+ fill: "currentColor",
19220
+ fontFamily: "sans-serif",
19221
+ fontWeight: "700",
19222
+ children: [
19223
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
19224
+ x: "64",
19225
+ y: "30",
19226
+ fontSize: "13",
19227
+ opacity: "0.7",
19228
+ className: "rg-a",
19229
+ style: { animation: "rg-zzz 3.4s ease-in-out infinite" },
19230
+ children: "z"
19231
+ }),
19232
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
19233
+ x: "73",
19234
+ y: "23",
19235
+ fontSize: "10",
19236
+ opacity: "0.6",
19237
+ className: "rg-a",
19238
+ style: { animation: "rg-zzz 3.4s ease-in-out .7s infinite" },
19239
+ children: "z"
19240
+ }),
19241
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
19242
+ x: "80",
19243
+ y: "17",
19244
+ fontSize: "8",
19245
+ opacity: "0.5",
19246
+ className: "rg-a",
19247
+ style: { animation: "rg-zzz 3.4s ease-in-out 1.4s infinite" },
19248
+ children: "z"
19249
+ })
19250
+ ]
19251
+ }),
19252
+ charging && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
19253
+ d: "M53 20 L44 40 L50 40 L46 56 L60 34 L53 34 Z",
19254
+ fill: "#fbbf24",
19255
+ stroke: "#f59e0b",
19256
+ strokeWidth: "1",
19257
+ className: "rg-a",
19258
+ style: {
19259
+ animation: "rg-spark 1s ease-in-out infinite",
19260
+ transformBox: "fill-box",
19261
+ transformOrigin: "center"
19262
+ }
19263
+ })
19264
+ ]
19265
+ })
18966
19266
  })
18967
- })]
19267
+ ]
18968
19268
  });
18969
19269
  }
18970
19270
  /** Battery % badge — shared between inline + hero. */
@@ -19143,7 +19443,7 @@ function VacuumInlineControl({ trpc, deviceId }) {
19143
19443
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(VacuumIconButton, {
19144
19444
  label: "Return to base",
19145
19445
  busy,
19146
- active: state === "returning",
19446
+ active: state === "returning" || state === "docked",
19147
19447
  onClick: () => press(cap.returnToBase),
19148
19448
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(House, { className: "h-3.5 w-3.5" })
19149
19449
  }),
@@ -19192,7 +19492,10 @@ function VacuumHeroCard({ trpc, deviceId }) {
19192
19492
  children: [
19193
19493
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
19194
19494
  className: "flex items-center gap-6",
19195
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(VacuumGraphic, { state }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
19495
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(VacuumGraphic, {
19496
+ state,
19497
+ batteryLevel: slice?.batteryLevel ?? null
19498
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
19196
19499
  className: "flex flex-col gap-3 flex-1",
19197
19500
  children: [
19198
19501
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -19246,7 +19549,7 @@ function VacuumHeroCard({ trpc, deviceId }) {
19246
19549
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(VacuumHeroButton, {
19247
19550
  label: "Return to base",
19248
19551
  busy,
19249
- active: state === "returning",
19552
+ active: state === "returning" || state === "docked",
19250
19553
  onClick: () => press(cap.returnToBase),
19251
19554
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(House, { className: "h-5 w-5" })
19252
19555
  }),
@@ -19379,112 +19682,206 @@ function lawnMowerGraphicTint(activity) {
19379
19682
  };
19380
19683
  }
19381
19684
  }
19685
+ var MOWER_KEYFRAMES = `
19686
+ @keyframes rg-breathe { 0%,100%{opacity:.78;transform:scale(1)} 50%{opacity:1;transform:scale(1.05)} }
19687
+ @keyframes rg-zzz { 0%{opacity:0;transform:translateY(3px)} 30%{opacity:.85} 100%{opacity:0;transform:translateY(-9px)} }
19688
+ @keyframes rg-spark { 0%,100%{opacity:.25;transform:scale(.9)} 50%{opacity:1;transform:scale(1.1)} }
19689
+ @keyframes rg-pulse { 0%,100%{opacity:.35} 50%{opacity:.85} }
19690
+ @keyframes rg-blink { 0%,49%{opacity:1} 50%,100%{opacity:.3} }
19691
+ @keyframes rg-spin { to { transform:rotate(360deg) } }
19692
+ @media (prefers-reduced-motion: reduce) { .rg-a { animation: none !important; } }
19693
+ `;
19382
19694
  /**
19383
19695
  * LawnMowerGraphic — a framed robot-lawn-mower illustration in the SAME visual
19384
19696
  * style as the cover window (w-28 h-36 rounded bordered box, gradient bg,
19385
19697
  * overflow-hidden, inset white shadow), over a grass-green gradient. Tinted by
19386
- * `lawnMowerGraphicTint`. Decorative only (`aria-hidden`).
19698
+ * `lawnMowerGraphicTint` and state-ANIMATED: it breathes + emits "Zzz" when
19699
+ * dormant on the dock, shows a pulsing electricity bolt while charging, blinks
19700
+ * on error, and spins its cutting deck + streams clippings while mowing.
19701
+ * Decorative only (`aria-hidden`).
19387
19702
  */
19388
- function LawnMowerGraphic({ activity }) {
19703
+ function LawnMowerGraphic({ activity, batteryLevel }) {
19389
19704
  const tint = lawnMowerGraphicTint(activity);
19705
+ const charging = tint.docked && batteryLevel != null && batteryLevel < 100;
19706
+ const sleeping = tint.docked && !charging;
19707
+ const erroring = activity === "error";
19708
+ const bodyStyle = sleeping ? {
19709
+ animation: "rg-breathe 3.4s ease-in-out infinite",
19710
+ transformBox: "fill-box",
19711
+ transformOrigin: "center"
19712
+ } : erroring ? { animation: "rg-blink 1s steps(1) infinite" } : void 0;
19390
19713
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
19391
19714
  "aria-hidden": "true",
19392
19715
  className: "relative w-28 h-36 rounded border-2 border-border bg-gradient-to-b from-emerald-700/40 to-emerald-900/30 overflow-hidden flex-shrink-0",
19393
19716
  style: { boxShadow: "inset 0 0 0 2px rgba(255,255,255,.06)" },
19394
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: cn("absolute inset-0 transition-colors duration-300", tint.glow) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
19395
- className: cn("absolute inset-0 flex items-center justify-center", tint.accent),
19396
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
19397
- viewBox: "0 0 100 100",
19398
- className: "w-20 h-20",
19399
- fill: "none",
19400
- children: [
19401
- tint.docked && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
19402
- x: "30",
19403
- y: "78",
19404
- width: "40",
19405
- height: "9",
19406
- rx: "2",
19407
- fill: "currentColor",
19408
- opacity: "0.35"
19409
- }),
19410
- tint.active && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", {
19411
- stroke: "currentColor",
19412
- strokeWidth: "2.5",
19413
- strokeLinecap: "round",
19414
- opacity: "0.5",
19415
- children: [
19416
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("line", {
19417
- x1: "18",
19418
- y1: "36",
19419
- x2: "10",
19420
- y2: "36",
19421
- className: "animate-pulse"
19422
- }),
19423
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("line", {
19424
- x1: "18",
19425
- y1: "50",
19426
- x2: "6",
19427
- y2: "50",
19428
- className: "animate-pulse"
19429
- }),
19430
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("line", {
19431
- x1: "18",
19432
- y1: "64",
19433
- x2: "10",
19434
- y2: "64",
19435
- className: "animate-pulse"
19436
- })
19437
- ]
19438
- }),
19439
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
19440
- x: "28",
19441
- y: "34",
19442
- width: "44",
19443
- height: "34",
19444
- rx: "8",
19445
- fill: "currentColor",
19446
- fillOpacity: "0.14",
19447
- stroke: "currentColor",
19448
- strokeWidth: "2.5"
19449
- }),
19450
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("line", {
19451
- x1: "34",
19452
- y1: "60",
19453
- x2: "66",
19454
- y2: "60",
19455
- stroke: "currentColor",
19456
- strokeWidth: "2",
19457
- opacity: "0.5"
19458
- }),
19459
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
19460
- cx: "50",
19461
- cy: "48",
19462
- r: "5",
19463
- stroke: "currentColor",
19464
- strokeWidth: "2",
19465
- opacity: "0.6"
19466
- }),
19467
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
19468
- cx: "34",
19469
- cy: "68",
19470
- r: "6",
19471
- fill: "currentColor",
19472
- fillOpacity: "0.3",
19473
- stroke: "currentColor",
19474
- strokeWidth: "2"
19475
- }),
19476
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
19477
- cx: "66",
19478
- cy: "68",
19479
- r: "6",
19480
- fill: "currentColor",
19481
- fillOpacity: "0.3",
19482
- stroke: "currentColor",
19483
- strokeWidth: "2"
19484
- })
19485
- ]
19717
+ children: [
19718
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: MOWER_KEYFRAMES }),
19719
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: cn("absolute inset-0 transition-colors duration-300", tint.glow) }),
19720
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
19721
+ className: cn("absolute inset-0 flex items-center justify-center", tint.accent),
19722
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
19723
+ viewBox: "0 0 100 100",
19724
+ className: "w-20 h-20",
19725
+ fill: "none",
19726
+ children: [
19727
+ tint.docked && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
19728
+ x: "30",
19729
+ y: "78",
19730
+ width: "40",
19731
+ height: "9",
19732
+ rx: "2",
19733
+ fill: "currentColor",
19734
+ opacity: "0.35",
19735
+ className: cn(charging && "rg-a"),
19736
+ style: charging ? { animation: "rg-pulse 1.2s ease-in-out infinite" } : void 0
19737
+ }),
19738
+ tint.active && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", {
19739
+ stroke: "currentColor",
19740
+ strokeWidth: "2.5",
19741
+ strokeLinecap: "round",
19742
+ opacity: "0.5",
19743
+ children: [
19744
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("line", {
19745
+ x1: "18",
19746
+ y1: "36",
19747
+ x2: "10",
19748
+ y2: "36",
19749
+ className: "animate-pulse"
19750
+ }),
19751
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("line", {
19752
+ x1: "18",
19753
+ y1: "50",
19754
+ x2: "6",
19755
+ y2: "50",
19756
+ className: "animate-pulse"
19757
+ }),
19758
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("line", {
19759
+ x1: "18",
19760
+ y1: "64",
19761
+ x2: "10",
19762
+ y2: "64",
19763
+ className: "animate-pulse"
19764
+ })
19765
+ ]
19766
+ }),
19767
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", {
19768
+ className: cn((sleeping || erroring) && "rg-a"),
19769
+ style: bodyStyle,
19770
+ children: [
19771
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
19772
+ x: "28",
19773
+ y: "34",
19774
+ width: "44",
19775
+ height: "34",
19776
+ rx: "8",
19777
+ fill: "currentColor",
19778
+ fillOpacity: "0.14",
19779
+ stroke: "currentColor",
19780
+ strokeWidth: "2.5"
19781
+ }),
19782
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("line", {
19783
+ x1: "34",
19784
+ y1: "60",
19785
+ x2: "66",
19786
+ y2: "60",
19787
+ stroke: "currentColor",
19788
+ strokeWidth: "2",
19789
+ opacity: "0.5"
19790
+ }),
19791
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", {
19792
+ className: cn(tint.active && "rg-a"),
19793
+ style: tint.active ? {
19794
+ animation: "rg-spin 0.9s linear infinite",
19795
+ transformBox: "fill-box",
19796
+ transformOrigin: "center"
19797
+ } : void 0,
19798
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
19799
+ cx: "50",
19800
+ cy: "48",
19801
+ r: "5",
19802
+ stroke: "currentColor",
19803
+ strokeWidth: "2",
19804
+ opacity: "0.6"
19805
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("line", {
19806
+ x1: "45",
19807
+ y1: "48",
19808
+ x2: "55",
19809
+ y2: "48",
19810
+ stroke: "currentColor",
19811
+ strokeWidth: "2",
19812
+ opacity: "0.7"
19813
+ })]
19814
+ }),
19815
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
19816
+ cx: "34",
19817
+ cy: "68",
19818
+ r: "6",
19819
+ fill: "currentColor",
19820
+ fillOpacity: "0.3",
19821
+ stroke: "currentColor",
19822
+ strokeWidth: "2"
19823
+ }),
19824
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
19825
+ cx: "66",
19826
+ cy: "68",
19827
+ r: "6",
19828
+ fill: "currentColor",
19829
+ fillOpacity: "0.3",
19830
+ stroke: "currentColor",
19831
+ strokeWidth: "2"
19832
+ })
19833
+ ]
19834
+ }),
19835
+ sleeping && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", {
19836
+ fill: "currentColor",
19837
+ fontFamily: "sans-serif",
19838
+ fontWeight: "700",
19839
+ children: [
19840
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
19841
+ x: "64",
19842
+ y: "30",
19843
+ fontSize: "13",
19844
+ opacity: "0.7",
19845
+ className: "rg-a",
19846
+ style: { animation: "rg-zzz 3.4s ease-in-out infinite" },
19847
+ children: "z"
19848
+ }),
19849
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
19850
+ x: "73",
19851
+ y: "23",
19852
+ fontSize: "10",
19853
+ opacity: "0.6",
19854
+ className: "rg-a",
19855
+ style: { animation: "rg-zzz 3.4s ease-in-out .7s infinite" },
19856
+ children: "z"
19857
+ }),
19858
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
19859
+ x: "80",
19860
+ y: "17",
19861
+ fontSize: "8",
19862
+ opacity: "0.5",
19863
+ className: "rg-a",
19864
+ style: { animation: "rg-zzz 3.4s ease-in-out 1.4s infinite" },
19865
+ children: "z"
19866
+ })
19867
+ ]
19868
+ }),
19869
+ charging && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
19870
+ d: "M53 20 L44 40 L50 40 L46 56 L60 34 L53 34 Z",
19871
+ fill: "#fbbf24",
19872
+ stroke: "#f59e0b",
19873
+ strokeWidth: "1",
19874
+ className: "rg-a",
19875
+ style: {
19876
+ animation: "rg-spark 1s ease-in-out infinite",
19877
+ transformBox: "fill-box",
19878
+ transformOrigin: "center"
19879
+ }
19880
+ })
19881
+ ]
19882
+ })
19486
19883
  })
19487
- })]
19884
+ ]
19488
19885
  });
19489
19886
  }
19490
19887
  /** Tailwind text tone for a device-code severity. */
@@ -19619,7 +20016,10 @@ function LawnMowerHeroCard({ trpc, deviceId }) {
19619
20016
  className: "flex flex-col items-stretch gap-4 rounded-lg border border-border bg-surface p-4",
19620
20017
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
19621
20018
  className: "flex items-center gap-6",
19622
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LawnMowerGraphic, { activity }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
20019
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LawnMowerGraphic, {
20020
+ activity,
20021
+ batteryLevel: slice?.batteryLevel ?? null
20022
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
19623
20023
  className: "flex flex-col gap-3 flex-1",
19624
20024
  children: [
19625
20025
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -22195,6 +22595,561 @@ function ClimatePanel({ trpc, deviceId, features: _features, layout, optimistic
22195
22595
  });
22196
22596
  }
22197
22597
  //#endregion
22598
+ //#region src/composites/device-controls/ac-panel.tsx
22599
+ /** AC-appropriate short mode labels. Exhaustive over HvacMode. */
22600
+ var MODE_LABEL = {
22601
+ cool: "Cool",
22602
+ heat: "Heat",
22603
+ auto: "Auto",
22604
+ heat_cool: "Heat·Cool",
22605
+ fan_only: "Fan",
22606
+ dry: "Dry",
22607
+ off: "Off"
22608
+ };
22609
+ /** Per-mode icon (lucide). Exhaustive over HvacMode. */
22610
+ var MODE_ICON = {
22611
+ cool: Snowflake,
22612
+ heat: Flame,
22613
+ auto: Sparkles,
22614
+ heat_cool: Thermometer,
22615
+ fan_only: Fan,
22616
+ dry: Droplets,
22617
+ off: Power
22618
+ };
22619
+ /** Mode accent hex — drives the airflow particles, the glow, and the target
22620
+ * value colour. Mirrors the thermostat gauge palette. Exhaustive. */
22621
+ var MODE_ACCENT = {
22622
+ heat: "#f59e0b",
22623
+ cool: "#60a5fa",
22624
+ auto: "#c084fc",
22625
+ heat_cool: "#e879f9",
22626
+ fan_only: "#94a3b8",
22627
+ dry: "#22d3ee",
22628
+ off: "#a1a1aa"
22629
+ };
22630
+ /** Practical AC setpoint range (Celsius) for the target range track. */
22631
+ var TARGET_MIN = 16;
22632
+ var TARGET_MAX = 30;
22633
+ /** Title-case a snake/lower token for a selector option ('medium_low' → 'Medium
22634
+ * low', 'turbo' → 'Turbo'). Shared by the fan + preset selectors. */
22635
+ function fanLabel(token) {
22636
+ const spaced = token.replace(/_/g, " ");
22637
+ return spaced.charAt(0).toUpperCase() + spaced.slice(1);
22638
+ }
22639
+ /** Preset → icon. Unknown presets fall back to a neutral sparkle. */
22640
+ function presetIcon(preset) {
22641
+ switch (preset) {
22642
+ case "turbo": return Zap;
22643
+ case "quiet": return Feather;
22644
+ case "sleep": return Moon;
22645
+ case "eco": return Leaf;
22646
+ case "8c_heat": return Snowflake;
22647
+ case "none": return Ban;
22648
+ default: return Sparkles;
22649
+ }
22650
+ }
22651
+ /**
22652
+ * Fan level → airflow animation period. Higher speed ⇒ shorter period. Unknown
22653
+ * strings fall back to a medium pace. Turbo (a preset) shortens it further.
22654
+ */
22655
+ function airflowPeriodSec(fanMode) {
22656
+ switch (fanMode) {
22657
+ case "high": return 1;
22658
+ case "medium_high": return 1.3;
22659
+ case "medium": return 1.6;
22660
+ case "medium_low": return 1.9;
22661
+ case "low": return 2.2;
22662
+ default: return 1.8;
22663
+ }
22664
+ }
22665
+ var KEYFRAMES = `
22666
+ @keyframes acv-fall { 0%{transform:translateY(-1.75rem);opacity:0} 18%{opacity:.7} 82%{opacity:.7} 100%{transform:translateY(7.5rem);opacity:0} }
22667
+ @keyframes acv-rise { 0%{transform:translateY(7.5rem);opacity:0} 18%{opacity:.7} 82%{opacity:.7} 100%{transform:translateY(-1.75rem);opacity:0} }
22668
+ @keyframes acv-sway { 0%,100%{transform:translateX(-9%)} 50%{transform:translateX(9%)} }
22669
+ @keyframes acv-swing-h { 0%,100%{transform:translateX(-22%)} 50%{transform:translateX(22%)} }
22670
+ @keyframes acv-swing-v { 0%,100%{transform:translateY(-22%)} 50%{transform:translateY(22%)} }
22671
+ @keyframes acv-spin { to { transform:rotate(360deg) } }
22672
+ @keyframes acv-glow { 0%,100%{opacity:.35;transform:scale(1)} 50%{opacity:.6;transform:scale(1.06)} }
22673
+ @media (prefers-reduced-motion: reduce) {
22674
+ .acv-streak, .acv-fan, .acv-sway-wrap, .acv-glow, .acv-swing { animation: none !important; }
22675
+ }
22676
+ `;
22677
+ function AcPanel({ trpc, deviceId, layout, optimistic }) {
22678
+ const { cap, slice, available } = useDeviceCapability({
22679
+ trpc,
22680
+ deviceId,
22681
+ cap: (d) => d.climateControl,
22682
+ slice: (d) => d.state.climateControl
22683
+ });
22684
+ const { display, applyOptimistic } = useOptimisticSlice(slice, { enabled: optimistic ?? true });
22685
+ if (cap === void 0) return null;
22686
+ if (display === void 0) return null;
22687
+ const busy = !available;
22688
+ if (layout === "compact") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
22689
+ "data-mode": display.mode,
22690
+ className: "inline-flex items-center gap-1",
22691
+ children: [
22692
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
22693
+ className: cn("text-xs tabular-nums font-medium", MODE_TEXT_COLOR[display.mode]),
22694
+ children: display.target !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
22695
+ display.target,
22696
+ "°",
22697
+ display.currentTemp !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
22698
+ className: "text-foreground-subtle font-normal",
22699
+ children: [
22700
+ " ",
22701
+ "(",
22702
+ display.currentTemp,
22703
+ "°)"
22704
+ ]
22705
+ })
22706
+ ] }) : display.currentTemp !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
22707
+ className: "text-foreground-subtle",
22708
+ children: [display.currentTemp, "°"]
22709
+ }) : null
22710
+ }),
22711
+ display.target !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
22712
+ type: "button",
22713
+ "aria-label": "Decrease target",
22714
+ disabled: busy,
22715
+ onClick: (e) => {
22716
+ e.stopPropagation();
22717
+ if (!busy && display.target !== null) {
22718
+ const next = display.target - 1;
22719
+ applyOptimistic({ target: next });
22720
+ cap.setTarget({ target: next });
22721
+ }
22722
+ },
22723
+ className: cn("inline-flex h-5 w-5 items-center justify-center rounded text-foreground-subtle hover:text-foreground hover:bg-surface-hover transition-colors text-xs", busy && "opacity-40 cursor-not-allowed"),
22724
+ children: "−"
22725
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
22726
+ type: "button",
22727
+ "aria-label": "Increase target",
22728
+ disabled: busy,
22729
+ onClick: (e) => {
22730
+ e.stopPropagation();
22731
+ if (!busy && display.target !== null) {
22732
+ const next = display.target + 1;
22733
+ applyOptimistic({ target: next });
22734
+ cap.setTarget({ target: next });
22735
+ }
22736
+ },
22737
+ className: cn("inline-flex h-5 w-5 items-center justify-center rounded text-foreground-subtle hover:text-foreground hover:bg-surface-hover transition-colors text-xs", busy && "opacity-40 cursor-not-allowed"),
22738
+ children: "+"
22739
+ })] }),
22740
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
22741
+ "aria-label": "Mode",
22742
+ value: display.mode,
22743
+ disabled: busy,
22744
+ onChange: (e) => {
22745
+ e.stopPropagation();
22746
+ const mode = display.availableModes.find((m) => m === e.target.value);
22747
+ if (!busy && mode) {
22748
+ applyOptimistic({ mode });
22749
+ cap.setMode({ mode });
22750
+ }
22751
+ },
22752
+ className: cn("bg-transparent px-0.5 text-xs text-foreground-subtle hover:text-foreground cursor-pointer", busy && "opacity-40 cursor-not-allowed"),
22753
+ children: display.availableModes.map((m) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
22754
+ value: m,
22755
+ children: MODE_LABEL[m]
22756
+ }, m))
22757
+ })
22758
+ ]
22759
+ });
22760
+ const accent = MODE_ACCENT[display.mode];
22761
+ const on = display.mode !== "off";
22762
+ const CurrentPresetIcon = presetIcon(display.preset);
22763
+ const boost = display.preset === "turbo";
22764
+ const period = Math.max(.55, airflowPeriodSec(display.fanMode) * (boost ? .6 : 1));
22765
+ const showVSwing = display.swingVertical !== null;
22766
+ const showHSwing = display.swingHorizontal !== null;
22767
+ const swaying = on && (display.swingVertical === true || display.swingHorizontal === true);
22768
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
22769
+ "data-mode": display.mode,
22770
+ className: cn("relative flex flex-col items-stretch gap-2.5 overflow-hidden rounded-lg border p-3 transition-colors", MODE_COLOR[display.mode]),
22771
+ children: [
22772
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: KEYFRAMES }),
22773
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
22774
+ className: "relative z-10 flex flex-col items-center justify-center gap-1 overflow-hidden rounded-lg border border-border/50 bg-surface/30 px-3 py-3",
22775
+ children: [
22776
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
22777
+ className: "pointer-events-none absolute inset-0 opacity-60",
22778
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AirflowViz, {
22779
+ mode: display.mode,
22780
+ accent,
22781
+ periodSec: period,
22782
+ swaying
22783
+ })
22784
+ }),
22785
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "pointer-events-none absolute inset-0 bg-gradient-to-b from-surface/50 via-transparent to-surface/50" }),
22786
+ on && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
22787
+ "aria-hidden": true,
22788
+ className: "acv-glow pointer-events-none absolute left-1/2 top-1/2 h-16 w-16 -translate-x-1/2 -translate-y-1/2 rounded-full blur-2xl",
22789
+ style: {
22790
+ background: accent,
22791
+ animation: "acv-glow 3.2s ease-in-out infinite"
22792
+ }
22793
+ }),
22794
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
22795
+ className: "relative z-10 flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wide",
22796
+ style: { color: accent },
22797
+ children: [
22798
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModeIcon, {
22799
+ mode: display.mode,
22800
+ className: "h-3.5 w-3.5"
22801
+ }),
22802
+ MODE_LABEL[display.mode],
22803
+ display.currentTemp !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
22804
+ className: "normal-case text-foreground-subtle",
22805
+ children: [
22806
+ "· ",
22807
+ display.currentTemp,
22808
+ "° now"
22809
+ ]
22810
+ }),
22811
+ display.currentHumidity !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
22812
+ className: "inline-flex items-center gap-0.5 normal-case text-foreground-subtle",
22813
+ children: [
22814
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Droplets, { className: "h-3 w-3" }),
22815
+ display.currentHumidity,
22816
+ "%"
22817
+ ]
22818
+ })
22819
+ ]
22820
+ }),
22821
+ display.target !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
22822
+ className: "relative z-10 flex items-center gap-3",
22823
+ children: [
22824
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RoundStep, {
22825
+ label: "Decrease target",
22826
+ symbol: "−",
22827
+ disabled: busy,
22828
+ onClick: () => {
22829
+ if (busy || display.target === null) return;
22830
+ const next = display.target - 1;
22831
+ applyOptimistic({ target: next });
22832
+ cap.setTarget({ target: next });
22833
+ }
22834
+ }),
22835
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
22836
+ className: "flex items-baseline gap-0.5",
22837
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
22838
+ className: "text-4xl font-bold leading-none tabular-nums",
22839
+ style: { color: accent },
22840
+ children: display.target
22841
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
22842
+ className: "text-lg text-foreground-subtle",
22843
+ children: "°"
22844
+ })]
22845
+ }),
22846
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RoundStep, {
22847
+ label: "Increase target",
22848
+ symbol: "+",
22849
+ disabled: busy,
22850
+ onClick: () => {
22851
+ if (busy || display.target === null) return;
22852
+ const next = display.target + 1;
22853
+ applyOptimistic({ target: next });
22854
+ cap.setTarget({ target: next });
22855
+ }
22856
+ })
22857
+ ]
22858
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
22859
+ className: "relative z-10 w-36",
22860
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RangeTrack, {
22861
+ value: display.target,
22862
+ min: TARGET_MIN,
22863
+ max: TARGET_MAX,
22864
+ accent
22865
+ })
22866
+ })] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
22867
+ className: "relative z-10 flex items-baseline gap-0.5",
22868
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
22869
+ className: "text-4xl font-bold leading-none tabular-nums text-foreground",
22870
+ children: display.currentTemp ?? "—"
22871
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
22872
+ className: "text-lg text-foreground-subtle",
22873
+ children: "°"
22874
+ })]
22875
+ })
22876
+ ]
22877
+ }),
22878
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
22879
+ className: "relative z-10 grid grid-cols-3 gap-2",
22880
+ children: [
22881
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CompactSelect, {
22882
+ label: "Mode",
22883
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
22884
+ style: { color: accent },
22885
+ className: "flex",
22886
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModeIcon, {
22887
+ mode: display.mode,
22888
+ className: "h-4 w-4"
22889
+ })
22890
+ }),
22891
+ value: display.mode,
22892
+ options: display.availableModes.map((m) => ({
22893
+ value: m,
22894
+ label: MODE_LABEL[m]
22895
+ })),
22896
+ disabled: busy,
22897
+ onChange: (v) => {
22898
+ const mode = display.availableModes.find((m) => m === v);
22899
+ if (mode) {
22900
+ applyOptimistic({ mode });
22901
+ cap.setMode({ mode });
22902
+ }
22903
+ }
22904
+ }),
22905
+ display.availableFanModes.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CompactSelect, {
22906
+ label: "Fan",
22907
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Wind, { className: "h-4 w-4 text-sky-500" }),
22908
+ value: display.fanMode,
22909
+ options: display.availableFanModes.map((f) => ({
22910
+ value: f,
22911
+ label: fanLabel(f)
22912
+ })),
22913
+ disabled: busy,
22914
+ onChange: (v) => {
22915
+ applyOptimistic({ fanMode: v });
22916
+ cap.setFanMode({ fanMode: v });
22917
+ }
22918
+ }),
22919
+ display.availablePresets.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CompactSelect, {
22920
+ label: "Preset",
22921
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CurrentPresetIcon, { className: "h-4 w-4 text-emerald-500" }),
22922
+ value: display.preset,
22923
+ options: display.availablePresets.map((p) => ({
22924
+ value: p,
22925
+ label: fanLabel(p)
22926
+ })),
22927
+ disabled: busy,
22928
+ onChange: (v) => {
22929
+ applyOptimistic({ preset: v });
22930
+ cap.setPreset({ preset: v });
22931
+ }
22932
+ })
22933
+ ]
22934
+ }),
22935
+ (showVSwing || showHSwing) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
22936
+ label: "Swing",
22937
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
22938
+ className: "grid grid-cols-2 gap-1",
22939
+ children: [showVSwing && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SwingToggle, {
22940
+ axis: "vertical",
22941
+ on: display.swingVertical ?? false,
22942
+ busy,
22943
+ onToggle: (next) => {
22944
+ applyOptimistic({ swingVertical: next });
22945
+ cap.setSwingVertical({ on: next });
22946
+ }
22947
+ }), showHSwing && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SwingToggle, {
22948
+ axis: "horizontal",
22949
+ on: display.swingHorizontal ?? false,
22950
+ busy,
22951
+ onToggle: (next) => {
22952
+ applyOptimistic({ swingHorizontal: next });
22953
+ cap.setSwingHorizontal({ on: next });
22954
+ }
22955
+ })]
22956
+ })
22957
+ })
22958
+ ]
22959
+ });
22960
+ }
22961
+ /** Labelled control group — a small uppercase header above its control. */
22962
+ function Field({ label, children }) {
22963
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
22964
+ className: "relative z-10 flex flex-col gap-1",
22965
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
22966
+ className: "text-[10px] font-semibold uppercase tracking-wider text-foreground-subtle",
22967
+ children: label
22968
+ }), children]
22969
+ });
22970
+ }
22971
+ function ModeIcon({ mode, className }) {
22972
+ const Icon = MODE_ICON[mode];
22973
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
22974
+ className,
22975
+ "aria-hidden": true
22976
+ });
22977
+ }
22978
+ /** Compact labelled dropdown — a leading icon + native `<select>` + chevron,
22979
+ * used for the Mode / Fan / Preset selectors so all three fit in one row. */
22980
+ function CompactSelect({ label, icon, value, options, disabled, onChange }) {
22981
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
22982
+ className: "flex min-w-0 flex-col gap-1",
22983
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
22984
+ className: "text-[10px] font-semibold uppercase tracking-wider text-foreground-subtle",
22985
+ children: label
22986
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
22987
+ className: cn("flex items-center gap-1 overflow-hidden rounded-lg border border-border/60 bg-surface-subtle px-1.5 py-1.5", disabled && "opacity-40"),
22988
+ children: [
22989
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
22990
+ className: "flex shrink-0",
22991
+ children: icon
22992
+ }),
22993
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
22994
+ "aria-label": label,
22995
+ value,
22996
+ disabled,
22997
+ onChange: (e) => {
22998
+ e.stopPropagation();
22999
+ if (!disabled) onChange(e.target.value);
23000
+ },
23001
+ className: "min-w-0 flex-1 cursor-pointer appearance-none truncate bg-transparent text-xs font-medium text-foreground outline-none",
23002
+ children: options.map((o) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
23003
+ value: o.value,
23004
+ children: o.label
23005
+ }, o.value))
23006
+ }),
23007
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronDown, { className: "h-3 w-3 shrink-0 text-foreground-subtle" })
23008
+ ]
23009
+ })]
23010
+ });
23011
+ }
23012
+ /** Circular ± button for the target. */
23013
+ function RoundStep({ label, symbol, disabled, onClick }) {
23014
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
23015
+ type: "button",
23016
+ "aria-label": label,
23017
+ disabled,
23018
+ onClick,
23019
+ className: cn("flex h-8 w-8 shrink-0 items-center justify-center rounded-full border border-border bg-surface text-lg leading-none text-foreground-subtle transition-colors hover:bg-surface-hover hover:text-foreground active:scale-95", disabled && "opacity-40 cursor-not-allowed"),
23020
+ children: symbol
23021
+ });
23022
+ }
23023
+ /** Thin gradient range track with a knob at the setpoint fraction. */
23024
+ function RangeTrack({ value, min, max, accent }) {
23025
+ const pct = `${Math.min(1, Math.max(0, (value - min) / (max - min))) * 100}%`;
23026
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
23027
+ className: "mt-2 w-full",
23028
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
23029
+ className: "relative h-1.5 w-full rounded-full bg-surface-subtle",
23030
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
23031
+ className: "absolute inset-y-0 left-0 rounded-full",
23032
+ style: {
23033
+ width: pct,
23034
+ background: accent
23035
+ }
23036
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
23037
+ className: "absolute top-1/2 h-3 w-3 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-surface shadow",
23038
+ style: {
23039
+ left: pct,
23040
+ background: accent
23041
+ }
23042
+ })]
23043
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
23044
+ className: "mt-0.5 flex justify-between text-[9px] tabular-nums text-foreground-subtle",
23045
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [min, "°"] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [max, "°"] })]
23046
+ })]
23047
+ });
23048
+ }
23049
+ /**
23050
+ * Animated centerpiece. `fan_only` renders a spinning fan; `off` a dim static
23051
+ * vent; every active mode renders drifting particles that FALL (cool/dry/auto)
23052
+ * or RISE (heat), with a horizontal sweep overlaid when a swing axis is active.
23053
+ */
23054
+ function AirflowViz({ mode, accent, periodSec, swaying }) {
23055
+ const columns = [
23056
+ 0,
23057
+ 1,
23058
+ 2,
23059
+ 3,
23060
+ 4,
23061
+ 5
23062
+ ];
23063
+ const rising = mode === "heat";
23064
+ const isDroplet = mode === "dry";
23065
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
23066
+ "aria-hidden": true,
23067
+ className: "absolute inset-0 overflow-hidden",
23068
+ children: mode === "off" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
23069
+ className: "flex h-full items-center justify-center gap-2 opacity-30",
23070
+ children: columns.map((i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "h-8 w-1 rounded-full bg-foreground-subtle opacity-40" }, i))
23071
+ }) : mode === "fan_only" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
23072
+ className: "flex h-full items-center justify-center",
23073
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FanBlades, {
23074
+ color: accent,
23075
+ periodSec: Math.max(.5, periodSec * .7),
23076
+ size: 56
23077
+ })
23078
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
23079
+ className: cn("acv-sway-wrap absolute inset-0", swaying && "acv-sway"),
23080
+ style: swaying ? { animation: "acv-sway 2.6s ease-in-out infinite" } : void 0,
23081
+ children: columns.map((i) => {
23082
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
23083
+ className: "acv-streak absolute rounded-full",
23084
+ style: {
23085
+ left: `${8 + i * 15}%`,
23086
+ width: isDroplet ? "0.4rem" : "0.3rem",
23087
+ height: isDroplet ? "0.4rem" : "1.9rem",
23088
+ background: accent,
23089
+ boxShadow: `0 0 6px ${accent}`,
23090
+ animationName: rising ? "acv-rise" : "acv-fall",
23091
+ animationTimingFunction: "linear",
23092
+ animationIterationCount: "infinite",
23093
+ animationDuration: `${periodSec}s`,
23094
+ animationDelay: `${i % 3 * (periodSec / 3) + i * .05}s`
23095
+ }
23096
+ }, i);
23097
+ })
23098
+ })
23099
+ });
23100
+ }
23101
+ /** Big spinning fan for `fan_only` mode. */
23102
+ function FanBlades({ color, periodSec, size }) {
23103
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
23104
+ width: size,
23105
+ height: size,
23106
+ viewBox: "0 0 100 100",
23107
+ className: "acv-fan",
23108
+ style: {
23109
+ animation: `acv-spin ${periodSec}s linear infinite`,
23110
+ transformOrigin: "50% 50%"
23111
+ },
23112
+ "aria-hidden": true,
23113
+ children: [[
23114
+ 0,
23115
+ 120,
23116
+ 240
23117
+ ].map((deg) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
23118
+ d: "M50 50 C 58 30, 78 26, 84 12 C 66 20, 54 34, 50 50 Z",
23119
+ fill: color,
23120
+ opacity: .85,
23121
+ transform: `rotate(${deg} 50 50)`
23122
+ }, deg)), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
23123
+ cx: "50",
23124
+ cy: "50",
23125
+ r: "7",
23126
+ fill: color
23127
+ })]
23128
+ });
23129
+ }
23130
+ /** Swing axis toggle — a card with a MoveVertical/Horizontal glyph that
23131
+ * oscillates along its axis when on. On/off is non-destructive → plain tap. */
23132
+ function SwingToggle({ axis, on, busy, onToggle }) {
23133
+ const label = axis === "vertical" ? "Vertical" : "Horizontal";
23134
+ const Icon = axis === "vertical" ? MoveVertical : MoveHorizontal;
23135
+ const anim = axis === "vertical" ? "acv-swing-v" : "acv-swing-h";
23136
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
23137
+ type: "button",
23138
+ "aria-label": `${label} swing`,
23139
+ "aria-pressed": on,
23140
+ disabled: busy,
23141
+ onClick: () => {
23142
+ if (!busy) onToggle(!on);
23143
+ },
23144
+ className: cn("flex items-center justify-center gap-2 rounded-lg border px-2 py-1.5 text-xs font-medium transition-colors", on ? "border-transparent bg-indigo-400 text-indigo-950 shadow-sm" : "border-border/60 bg-surface-subtle text-foreground-subtle hover:bg-surface-hover hover:text-foreground", busy && "opacity-40 cursor-not-allowed"),
23145
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
23146
+ className: "acv-swing h-4 w-4",
23147
+ "aria-hidden": true,
23148
+ style: on ? { animation: `${anim} 1.4s ease-in-out infinite` } : void 0
23149
+ }), label]
23150
+ });
23151
+ }
23152
+ //#endregion
22198
23153
  //#region src/composites/device-controls/media-player-panel.tsx
22199
23154
  /**
22200
23155
  * Exhaustive colour map — one entry per MediaPlayerState.
@@ -24076,6 +25031,26 @@ function ThermostatInlineControl({ trpc, deviceId, features, optimistic }) {
24076
25031
  optimistic
24077
25032
  });
24078
25033
  }
25034
+ function AcInlineControl({ trpc, deviceId, features, optimistic }) {
25035
+ if (!useClimateCapGuard(trpc, deviceId)) return null;
25036
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AcPanel, {
25037
+ trpc,
25038
+ deviceId,
25039
+ features,
25040
+ layout: "compact",
25041
+ optimistic
25042
+ });
25043
+ }
25044
+ function AcHeroCard({ trpc, deviceId, features, optimistic }) {
25045
+ if (!useClimateCapGuard(trpc, deviceId)) return null;
25046
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AcPanel, {
25047
+ trpc,
25048
+ deviceId,
25049
+ features,
25050
+ layout: "full",
25051
+ optimistic
25052
+ });
25053
+ }
24079
25054
  function useMediaPlayerCapGuard(trpc, deviceId) {
24080
25055
  const { cap } = useDeviceCapability({
24081
25056
  trpc,
@@ -24162,6 +25137,10 @@ var THERMOSTAT_CONTROL = {
24162
25137
  InlineControl: ThermostatInlineControl,
24163
25138
  HeroCard: ThermostatHeroCard
24164
25139
  };
25140
+ var CLIMATE_CONTROL = {
25141
+ InlineControl: AcInlineControl,
25142
+ HeroCard: AcHeroCard
25143
+ };
24165
25144
  var MEDIA_PLAYER_CONTROL = {
24166
25145
  InlineControl: MediaPlayerInlineControl,
24167
25146
  HeroCard: MediaPlayerHeroCard
@@ -24219,6 +25198,7 @@ var DEVICE_TYPE_CONTROL = {
24219
25198
  [_camstack_types.DeviceType.Fan]: FAN_CONTROL,
24220
25199
  [_camstack_types.DeviceType.Sensor]: SENSOR_CONTROL,
24221
25200
  [_camstack_types.DeviceType.Thermostat]: THERMOSTAT_CONTROL,
25201
+ [_camstack_types.DeviceType.Climate]: CLIMATE_CONTROL,
24222
25202
  [_camstack_types.DeviceType.Button]: {
24223
25203
  InlineControl: ButtonControl,
24224
25204
  HeroCard: ButtonHeroCard
@@ -24263,6 +25243,7 @@ var CONTROL_FILLS = {
24263
25243
  [_camstack_types.DeviceType.Cover]: true,
24264
25244
  [_camstack_types.DeviceType.Valve]: true,
24265
25245
  [_camstack_types.DeviceType.Thermostat]: true,
25246
+ [_camstack_types.DeviceType.Climate]: true,
24266
25247
  [_camstack_types.DeviceType.Control]: true,
24267
25248
  [_camstack_types.DeviceType.Button]: true,
24268
25249
  [_camstack_types.DeviceType.MediaPlayer]: true,
@@ -43074,6 +44055,7 @@ exports.useDeviceListPageSize = useDeviceListPageSize;
43074
44055
  exports.useDeviceManagerAddLocation = useDeviceManagerAddLocation;
43075
44056
  exports.useDeviceManagerAdoptDevice = useDeviceManagerAdoptDevice;
43076
44057
  exports.useDeviceManagerAdoptionAdopt = useDeviceManagerAdoptionAdopt;
44058
+ exports.useDeviceManagerAdoptionListCandidateFilters = useDeviceManagerAdoptionListCandidateFilters;
43077
44059
  exports.useDeviceManagerAdoptionListCandidates = useDeviceManagerAdoptionListCandidates;
43078
44060
  exports.useDeviceManagerAdoptionRefresh = useDeviceManagerAdoptionRefresh;
43079
44061
  exports.useDeviceManagerAdoptionRelease = useDeviceManagerAdoptionRelease;
@@ -43082,7 +44064,10 @@ exports.useDeviceManagerAllocateDeviceId = useDeviceManagerAllocateDeviceId;
43082
44064
  exports.useDeviceManagerApplyInitialMeta = useDeviceManagerApplyInitialMeta;
43083
44065
  exports.useDeviceManagerCreateDevice = useDeviceManagerCreateDevice;
43084
44066
  exports.useDeviceManagerDisable = useDeviceManagerDisable;
44067
+ exports.useDeviceManagerDiscoverAllProviders = useDeviceManagerDiscoverAllProviders;
43085
44068
  exports.useDeviceManagerDiscoverDevices = useDeviceManagerDiscoverDevices;
44069
+ exports.useDeviceManagerDiscoverProvider = useDeviceManagerDiscoverProvider;
44070
+ exports.useDeviceManagerDiscoveryProviders = useDeviceManagerDiscoveryProviders;
43086
44071
  exports.useDeviceManagerEnable = useDeviceManagerEnable;
43087
44072
  exports.useDeviceManagerGetAllBindings = useDeviceManagerGetAllBindings;
43088
44073
  exports.useDeviceManagerGetBindings = useDeviceManagerGetBindings;
@@ -43108,6 +44093,8 @@ exports.useDeviceManagerLoadMeta = useDeviceManagerLoadMeta;
43108
44093
  exports.useDeviceManagerLoadRuntimeState = useDeviceManagerLoadRuntimeState;
43109
44094
  exports.useDeviceManagerPersistConfig = useDeviceManagerPersistConfig;
43110
44095
  exports.useDeviceManagerProbeStreams = useDeviceManagerProbeStreams;
44096
+ exports.useDeviceManagerProviderCreationType = useDeviceManagerProviderCreationType;
44097
+ exports.useDeviceManagerProviderDiscoveryParamsSchema = useDeviceManagerProviderDiscoveryParamsSchema;
43111
44098
  exports.useDeviceManagerRegisterDevice = useDeviceManagerRegisterDevice;
43112
44099
  exports.useDeviceManagerRemove = useDeviceManagerRemove;
43113
44100
  exports.useDeviceManagerRemoveByIntegration = useDeviceManagerRemoveByIntegration;
@@ -43144,6 +44131,8 @@ exports.useDeviceProviderCreateDevice = useDeviceProviderCreateDevice;
43144
44131
  exports.useDeviceProviderDiscoverDevices = useDeviceProviderDiscoverDevices;
43145
44132
  exports.useDeviceProviderGetChildCreationSchema = useDeviceProviderGetChildCreationSchema;
43146
44133
  exports.useDeviceProviderGetDevices = useDeviceProviderGetDevices;
44134
+ exports.useDeviceProviderGetDiscoveryParamsSchema = useDeviceProviderGetDiscoveryParamsSchema;
44135
+ exports.useDeviceProviderGetManualCreationType = useDeviceProviderGetManualCreationType;
43147
44136
  exports.useDeviceProviderGetStatus = useDeviceProviderGetStatus;
43148
44137
  exports.useDeviceProviderStart = useDeviceProviderStart;
43149
44138
  exports.useDeviceProviderStop = useDeviceProviderStop;