@glasshome/widget-sdk 1.18.0 → 1.19.0

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.
@@ -38,9 +38,9 @@ export { isPanelControllable, PanelEntityRow } from "./panel/panel-entity";
38
38
  export { buildDebugData, WidgetDebugTab, type WidgetDebugData, type WidgetDebugEntity, WidgetDebugView, WidgetDialog, type SheetTile, type WidgetDialogProps, type WidgetDialogTab, } from "./dialogs";
39
39
  export { type AggregationPreset, type ReactiveWidgetContext, type ServiceCallFn, type UseWidgetEntityGroupOptions, type UseWidgetEntityGroupResult, type Daylight, type DaylightPhase, useDaylight, useIntersectionPause, useReducedMotion, useWidgetContext, useWidgetDashboard, useWidgetViewer, useWidgetDialog, useWidgetDimensions, useWidgetEntityGroup, WidgetCtx, type WidgetDashboard, type WidgetViewer, type WidgetDialogReturn, type WidgetDimensions, type WidgetDimensionsAccessor, WidgetSizeCtx, } from "./hooks";
40
40
  export { type GestureHandlers, useWidgetGestures } from "./gestures/use-widget-gestures";
41
- export { injectTokens, type SvgColorKey, svgColors, type Tone, ToneSchema, } from "./theming";
41
+ export { injectTokens, type SvgColorKey, svgColors, type Tone, ToneSchema } from "./theming";
42
42
  export { widgetFields } from "./fields";
43
43
  export { type ConfigShape, defineConfig, field, type Field, type ImagePresetSource, type Infer, } from "./config";
44
44
  export { imageSrc } from "./image-src";
45
45
  export { calculateLightGroup, calculateSensorGroup, type CopyState, copyText, countActiveEntities, getEntityAttribute, isEntityActive, type LightGroupResult, type SensorGroupResult, type SensorGroupType, useCopyText, } from "./utils";
46
- export type { EntityView, WidgetStyles, WidgetVariantConfig, } from "./types";
46
+ export type { EntityView, WidgetStyles, WidgetVariantConfig } from "./types";
@@ -7,6 +7,6 @@
7
7
  */
8
8
  export { copyText, type CopyState, useCopyText } from "./clipboard";
9
9
  export { calculateLightGroup, calculateSensorGroup, type LightGroupResult, type SensorGroupResult, type SensorGroupType, } from "./entity-aggregation";
10
- export { countActiveEntities, getEntityAttribute, isEntityActive, } from "./entity-state";
10
+ export { countActiveEntities, getEntityAttribute, isEntityActive } from "./entity-state";
11
11
  export { formatValue } from "./format-value";
12
12
  export { interpretValue } from "./interpret-value";
package/dist/index.js CHANGED
@@ -468,7 +468,7 @@ function kn(e) {
468
468
  return Dn();
469
469
  }
470
470
  }), r), F(r, () => e.icon), P((t) => {
471
- var r = X("glasshome-widget-icon glass glass-tint", "relative flex shrink-0 items-center justify-center pointer-events-none transition-all", e.dimmed && "opacity-50", e.class), i = e.color ? { "--widget-icon-color": e.color } : void 0;
471
+ var r = X("glasshome-widget-icon glass glass-tint", "pointer-events-none relative flex shrink-0 items-center justify-center transition-all", e.dimmed && "opacity-50", e.class), i = e.color ? { "--widget-icon-color": e.color } : void 0;
472
472
  return r !== t.e && M(n, t.e = r), t.t = z(n, i, t.t), t;
473
473
  }, {
474
474
  e: void 0,
@@ -706,7 +706,7 @@ function Qn(e) {
706
706
  }
707
707
  //#endregion
708
708
  //#region src/framework/theming/tokens.css?raw
709
- var $n = "/* Authoritative SDK CSS contract. Injected once on first Widget mount\n * via framework/theming/tokens.ts.\n *\n * All widget-internal visual scale is driven from CSS custom properties +\n * container queries on the `.glasshome-widget` shell (`container-type: size`).\n * No JS classifier, no size tier enum.\n */\n\n:root {\n --tone-success: oklch(0.70 0.18 145);\n --tone-warning: oklch(0.80 0.17 75);\n --tone-danger: oklch(0.64 0.22 27);\n --tone-info: oklch(0.66 0.20 245);\n --tone-neutral: oklch(0.65 0.02 250);\n --tone-accent: oklch(0.74 0.18 60);\n\n --widget-glow-default: 0.4;\n}\n\n.dark {\n --tone-success: oklch(0.74 0.18 145);\n --tone-warning: oklch(0.82 0.17 75);\n --tone-danger: oklch(0.68 0.22 27);\n --tone-info: oklch(0.70 0.20 245);\n --tone-neutral: oklch(0.69 0.02 250);\n --tone-accent: oklch(0.78 0.18 60);\n\n --widget-glow-default: 0.5;\n}\n\n@property --widget-color {\n syntax: \"<color>\";\n inherits: true;\n initial-value: oklch(0.65 0.02 250);\n}\n\n/* === Widget shell ===\n * One scale: --widget-unit is 1% of the shell's short side. Each size is a floor,\n * a line through the designed 2x2 and 4x4 sizes, and past 4x4 the share it has\n * at 4x4. A widget sizes its own text with these tokens and scales with every tile. */\n.glasshome-widget {\n --widget-color: var(--tone-neutral);\n --widget-glow-strength: var(--widget-glow-default);\n --widget-radius: var(--radius-xl, 12px);\n\n --widget-unit: 1cqmin;\n --widget-text-value: max(22px, calc(8.5px + 13.4 * var(--widget-unit)), calc(16 * var(--widget-unit)));\n --widget-text-name: max(14px, calc(8.9px + 4.37 * var(--widget-unit)), calc(7.07 * var(--widget-unit)));\n --widget-text-meta: max(11px, calc(8px + 2.44 * var(--widget-unit)), calc(4.88 * var(--widget-unit)));\n --widget-text-sub: max(12px, calc(10.1px + 1.22 * var(--widget-unit)), calc(4.3 * var(--widget-unit)));\n --widget-text-caption: max(11px, calc(3.6 * var(--widget-unit)));\n --widget-control-h: max(36px, calc(30px + 3.84 * var(--widget-unit)));\n --widget-grid-pad: max(10px, calc(1.2px + 5.64 * var(--widget-unit)), calc(6 * var(--widget-unit)));\n --widget-grid-gap: max(6px, calc(2.5px + 2.23 * var(--widget-unit)));\n --widget-icon-box: max(44px, calc(31.3px + 8.14 * var(--widget-unit)), calc(17.7 * var(--widget-unit)));\n --widget-icon-glyph: max(24px, calc(16px + 5.12 * var(--widget-unit)), calc(10 * var(--widget-unit)));\n --widget-pad: clamp(8px, 3.5cqb, 22px);\n --widget-gap: clamp(6px, 2cqb, 14px);\n --widget-title-size: clamp(12px, 2cqi, 18px);\n --widget-subtitle: clamp(12px, 2.5cqi, 20px);\n --widget-status-size: clamp(14px, 5cqi, 36px);\n --widget-value-size: clamp(18px, 7cqi, 48px);\n --widget-badge-size: clamp(11px, 1.6cqi, 14px);\n}\n\n/* Shell material: ui's .glass, worn through the class Widget.tsx carries.\n * Unlayered and class-specific, so these beat the formula's :where() defaults\n * (wash-2 is re-declared there and cannot be set from a utility). The tone\n * stays the formula's transparent default until Widget.tsx sets one inline:\n * a widget without a tone is the plain card, never a neutral-grey wash. */\n.glasshome-widget {\n --widget-wash: 22%;\n --widget-wash-2: 11%;\n --glass-wash: var(--widget-wash);\n --glass-wash-2: var(--widget-wash-2);\n --glass-wash-angle: 135deg;\n --glass-base: color-mix(in srgb, var(--card) var(--material-clarity, 60%), transparent);\n --glass-rim: 0.3;\n --glass-lift: 0.45;\n --glass-shade: 0.05;\n backdrop-filter: var(--widget-backdrop, none);\n -webkit-backdrop-filter: var(--widget-backdrop, none);\n}\n/* A fill widget keeps a neutral card; the fill carries the tone, so a full fill is the toned card. */\n.glasshome-widget:has(.glasshome-widget-slider-fill) {\n --glass-wash: 0%;\n --glass-wash-2: 0%;\n}\n.dark .glasshome-widget {\n --glass-shade: 0;\n}\n\n/* === Slot: content layout === */\n/* Default: column. Flip to row only on wide-and-short widgets (matches the\n * old contentLayout heuristic: aspect > 1 AND height < 150px). */\n.glasshome-widget-content {\n position: relative;\n display: flex;\n flex-direction: column;\n justify-content: space-between;\n width: 100%;\n height: 100%;\n padding: var(--widget-pad);\n gap: var(--widget-gap);\n overflow: hidden;\n border-radius: var(--widget-radius);\n}\n@container widget (min-aspect-ratio: 1) and (max-height: 149px) {\n .glasshome-widget-content {\n flex-direction: row;\n align-items: center;\n justify-content: flex-start;\n }\n}\n\n/* === Compact 1×1 ===\n * Small in BOTH axes → stack the tile: icon on top, label + status centered\n * under it. Icon stays moderate (not a hero) so the text keeps room. Wide\n * tiles (2×1) fail the width test and keep the row layout above. Placed after\n * the row-flip so it wins. */\n@container widget (max-width: 189px) and (max-height: 189px) {\n .glasshome-widget-content {\n flex-direction: column;\n align-items: center;\n justify-content: center;\n text-align: center;\n gap: clamp(4px, 2.5cqmin, 8px);\n }\n .glasshome-widget-content > * { align-items: center; text-align: center; }\n .glasshome-widget-icon {\n --widget-icon-box: clamp(36px, 30cqmin, 56px);\n --widget-icon-glyph: clamp(20px, 17cqmin, 32px);\n }\n .glasshome-widget-title { font-size: clamp(11px, 6cqmin, 14px); max-width: 100%; }\n .glasshome-widget-status,\n .glasshome-widget-value { font-size: clamp(13px, 7cqmin, 17px); }\n .glasshome-widget-content [class*=\"ml-auto\"] { margin-left: 0 !important; }\n}\n.glasshome-widget-content [class*=\"ml-auto\"] {\n position: relative;\n z-index: 30;\n}\n\n/* === Slot: icon ===\n * ui's tinted icon pill (ICON_PILL_TINT): the disc is .glass, so the material shapes it. */\n.glasshome-widget-icon {\n width: var(--widget-icon-box);\n height: var(--widget-icon-box);\n border-radius: var(--radius-sm, 12px);\n --glass-tone: var(--widget-icon-color, var(--widget-color));\n --glass-rim: 0.4;\n --glass-wash: 18%;\n}\n.glasshome-widget-icon-glyph {\n font-size: var(--widget-icon-glyph);\n line-height: 1;\n}\n.glasshome-widget-icon-stack {\n --stack-step: calc(var(--widget-icon-box) * 0.12);\n position: absolute;\n /* The pill's border would otherwise shrink each plate and widen the first step. */\n inset: -1px;\n width: auto;\n height: auto;\n border-radius: var(--radius-sm, 12px);\n}\n.glasshome-widget-icon-stack-2 { translate: var(--stack-step) var(--stack-step); opacity: 0.45; z-index: -2; }\n.glasshome-widget-icon-stack-3 { translate: calc(2 * var(--stack-step)) calc(2 * var(--stack-step)); opacity: 0.2; z-index: -3; }\n\n/* === Slot: text === */\n.glasshome-widget-title {\n font-size: var(--widget-title-size);\n line-height: 1.2;\n color: var(--color-muted-foreground, currentColor);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.glasshome-widget-subtitle {\n font-size: var(--widget-subtitle);\n color: var(--color-muted-foreground, currentColor);\n}\n.glasshome-widget-status {\n font-size: var(--widget-status-size);\n font-weight: 700;\n color: var(--color-foreground, currentColor);\n line-height: 1.1;\n}\n.glasshome-widget-value {\n font-size: var(--widget-value-size);\n font-weight: 700;\n color: var(--color-foreground, currentColor);\n line-height: 1.05;\n}\n.glasshome-widget-badge {\n font-size: var(--widget-badge-size);\n font-weight: 500;\n padding: 0.125rem 0.5rem;\n border-radius: 9999px;\n background: color-mix(in oklch, var(--color-foreground, currentColor) 20%, transparent);\n color: var(--color-foreground, currentColor);\n}\n\n.glasshome-widget-loading {\n background: color-mix(in oklch, var(--widget-color) 20%, transparent);\n}\n\n/* === Slider fill ===\n * Value flows in via --widget-fill-value (0-100). Vertical orientation auto-\n * detected via container query. */\n.glasshome-widget-slider-fill {\n --widget-fill-value: 0;\n background: linear-gradient(\n 135deg,\n color-mix(in srgb, var(--widget-color) calc(var(--widget-wash) * var(--material-tint, 1)), transparent),\n color-mix(\n in srgb,\n var(--widget-color-to, var(--widget-color)) calc(var(--widget-wash-2) * var(--material-tint, 1)),\n transparent\n )\n );\n /* Inset: the clip-path cuts any outer shadow. */\n box-shadow: inset 0 0 calc(var(--material-glow, 0px) * 1.2)\n oklch(from var(--widget-color) l c h / 0.55);\n clip-path: inset(0 calc((100 - var(--widget-fill-value)) * 1%) 0 0 round var(--widget-radius));\n}\n@container widget (max-aspect-ratio: 1) {\n .glasshome-widget-slider-fill {\n clip-path: inset(calc((100 - var(--widget-fill-value)) * 1%) 0 0 0 round var(--widget-radius));\n }\n}\n\n/* === Anatomy: Widget.Content with a Widget.Head lays its parts out as a tile === */\n.glasshome-widget-grid {\n position: relative;\n display: grid;\n grid-template-columns: max-content minmax(0, 1fr);\n grid-template-rows: auto minmax(0, 1fr) auto;\n gap: var(--widget-grid-gap);\n height: 100%;\n padding: var(--widget-grid-pad);\n overflow: hidden;\n isolation: isolate;\n color: var(--foreground);\n}\n\n/* In-flow content spans the tile (the max-content first column would collapse it); absolute parts keep the padding box. */\n.glasshome-widget-grid > :where(:not(.glasshome-widget-backdrop, .glasshome-widget-layer, .glasshome-widget-glyph)) {\n grid-column: 1 / -1;\n}\n.glasshome-widget-layer {\n position: absolute;\n inset: 0;\n z-index: -1;\n overflow: hidden;\n}\n.glasshome-widget-head {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n min-width: 0;\n}\n.glasshome-widget-head-text {\n min-height: var(--widget-icon-box);\n justify-content: center;\n display: flex;\n flex-direction: column;\n gap: 1px;\n min-width: 0;\n flex: 1;\n}\n.glasshome-widget-head-aside {\n display: flex;\n flex-wrap: wrap;\n justify-content: flex-end;\n gap: 6px;\n max-height: 26px;\n overflow: hidden;\n flex-shrink: 1;\n}\n.glasshome-widget-eyebrow {\n font-size: var(--widget-text-meta);\n font-weight: 500;\n line-height: 1.25;\n color: var(--muted-foreground);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.glasshome-widget-name {\n font-size: var(--widget-text-name);\n font-weight: 650;\n line-height: 1.2;\n letter-spacing: -0.01em;\n color: var(--foreground);\n display: -webkit-box;\n -webkit-box-orient: vertical;\n -webkit-line-clamp: 2;\n overflow: hidden;\n overflow-wrap: break-word;\n}\n\n.glasshome-widget-hero {\n display: flex;\n align-items: flex-end;\n justify-content: space-between;\n gap: var(--widget-grid-gap);\n min-height: 0;\n min-width: 0;\n}\n.glasshome-widget-reading {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n z-index: 1;\n}\n.glasshome-widget-reading-value {\n font-size: var(--widget-text-value);\n font-weight: 650;\n line-height: 1;\n letter-spacing: -0.03em;\n font-variant-numeric: tabular-nums;\n color: var(--foreground);\n white-space: nowrap;\n}\n/* Alternating names restart the rise on each change without rebuilding the text. */\n.glasshome-widget-reading-value[data-turn=\"a\"] {\n animation: tile-value-in-a var(--duration-morph) var(--ease-morph);\n}\n.glasshome-widget-reading-value[data-turn=\"b\"] {\n animation: tile-value-in-b var(--duration-morph) var(--ease-morph);\n}\n@keyframes tile-value-in-a {\n from {\n opacity: 0;\n transform: translateY(0.25em);\n }\n}\n@keyframes tile-value-in-b {\n from {\n opacity: 0;\n transform: translateY(0.25em);\n }\n}\n.glasshome-widget-unit {\n font-size: 0.5em;\n font-weight: 550;\n letter-spacing: 0;\n margin-left: 0.08em;\n color: var(--muted-foreground);\n}\n.glasshome-widget-sub {\n font-size: var(--widget-text-sub);\n font-weight: 500;\n color: var(--muted-foreground);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* Placed on the tile, not the hero, so a control row never moves or shrinks it. */\n.glasshome-widget-art {\n position: absolute;\n right: var(--widget-grid-pad);\n bottom: var(--widget-grid-pad);\n height: min(calc(100% - 2 * var(--widget-grid-pad) - var(--widget-icon-box) - var(--widget-grid-gap)), 48cqi);\n display: flex;\n align-items: flex-end;\n justify-content: flex-end;\n pointer-events: none;\n}\n/* Beside art a long word (\"Unlocked\") shrinks to end where the art starts, at about 50cqi; short values keep full size. */\n@container widget (min-width: 180px) and (min-height: 130px) {\n .glasshome-widget-hero[data-art] .glasshome-widget-reading-value {\n font-size: max(18px, min(var(--widget-text-value), calc(50cqi / (var(--value-len, 1) * 0.56))));\n }\n}\n.glasshome-widget-backdrop > img {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n object-fit: cover;\n}\n/* Art keeps its shape and clears a state word at its largest (15cqi, about 3.4em); each art sets --art-aspect. */\n.glasshome-widget-grid .glasshome-widget-art > * {\n height: min(100%, calc((52cqi - 2 * var(--widget-grid-pad) - var(--widget-grid-gap)) / var(--art-aspect, 1)));\n flex-shrink: 0;\n}\n.glasshome-widget-grid .glasshome-widget-art > img {\n width: auto;\n height: auto;\n max-height: 100%;\n max-width: calc(52cqi - 2 * var(--widget-grid-pad) - var(--widget-grid-gap));\n}\n\n.glasshome-widget-controls {\n display: flex;\n gap: 8px;\n min-width: 0;\n}\n\n.glasshome-widget-glyph {\n display: none;\n position: absolute;\n right: -8%;\n bottom: -22%;\n font-size: min(130cqb, 80cqi);\n line-height: 1;\n color: var(--widget-color);\n opacity: 0.16;\n pointer-events: none;\n z-index: -1;\n}\n\n/* Controls sit on the bottom row beside the value, or under it on a narrow tile; the art lifts above them. */\n.glasshome-widget-head {\n grid-column: 1 / -1;\n}\n.glasshome-widget-hero {\n grid-row: 3;\n grid-column: 1 / -1;\n}\n.glasshome-widget-grid:has(> .glasshome-widget-controls) > .glasshome-widget-hero {\n grid-column: 1;\n}\n.glasshome-widget-controls {\n grid-row: 3;\n grid-column: 2;\n align-self: end;\n position: relative;\n z-index: 1;\n}\n.glasshome-widget-grid:has(> .glasshome-widget-hero[data-empty]) > .glasshome-widget-controls {\n grid-column: 1 / -1;\n}\n@container widget (max-width: 519px) {\n .glasshome-widget-grid:has(> .glasshome-widget-controls) {\n grid-template-rows: auto minmax(0, 1fr) auto auto;\n }\n .glasshome-widget-grid:has(> .glasshome-widget-controls) > .glasshome-widget-hero {\n grid-column: 1 / -1;\n }\n .glasshome-widget-controls {\n grid-row: 4;\n grid-column: 1 / -1;\n }\n .glasshome-widget-grid:has(> .glasshome-widget-controls) .glasshome-widget-art {\n bottom: calc(var(--widget-grid-pad) + var(--widget-control-h) + var(--widget-grid-gap));\n height: min(\n calc(\n 100% -\n 2 *\n var(--widget-grid-pad) -\n var(--widget-icon-box) -\n 2 *\n var(--widget-grid-gap) -\n var(--widget-control-h)\n ),\n 48cqi\n );\n }\n}\n@container widget (max-height: 229px) {\n .glasshome-widget-controls {\n display: none;\n }\n .glasshome-widget-grid:has(> .glasshome-widget-controls) {\n grid-template-rows: auto minmax(0, 1fr) auto;\n }\n .glasshome-widget-grid:has(> .glasshome-widget-controls) .glasshome-widget-art {\n bottom: var(--widget-grid-pad);\n height: min(calc(100% - 2 * var(--widget-grid-pad) - var(--widget-icon-box) - var(--widget-grid-gap)), 48cqi);\n }\n}\n@container widget (max-width: 179px) {\n .glasshome-widget-art {\n display: none;\n }\n}\n@container widget (max-height: 169px) {\n .glasshome-widget-head-aside {\n display: none;\n }\n}\n@container widget ((max-height: 129px) or (max-width: 139px)) {\n .glasshome-widget-head-text {\n min-height: 0;\n }\n .glasshome-widget-name {\n -webkit-line-clamp: 1;\n }\n .glasshome-widget-grid {\n display: flex;\n flex-direction: column;\n justify-content: space-between;\n gap: 2px;\n }\n .glasshome-widget-head-icon,\n .glasshome-widget-art,\n .glasshome-widget-eyebrow,\n .glasshome-widget-sub {\n display: none;\n }\n .glasshome-widget-glyph {\n display: block;\n }\n .glasshome-widget-reading-value {\n font-size: clamp(18px, min(34cqb, 20cqi), 30px);\n }\n .glasshome-widget-name {\n font-size: clamp(12px, min(18cqb, 11cqi), 15px);\n font-weight: 550;\n color: var(--muted-foreground);\n }\n}\n@container widget (max-height: 129px) and (min-width: 260px) {\n .glasshome-widget-grid {\n flex-direction: row;\n align-items: center;\n justify-content: space-between;\n gap: var(--widget-grid-gap);\n }\n .glasshome-widget-name {\n font-size: clamp(14px, 22cqb, 18px);\n font-weight: 650;\n color: var(--foreground);\n }\n .glasshome-widget-eyebrow {\n display: block;\n }\n}\n\n.glasshome-widget-backdrop {\n position: absolute;\n inset: 0;\n z-index: -2;\n overflow: hidden;\n}\n.glasshome-widget-backdrop > img.area-night {\n opacity: 0;\n transition: opacity 700ms ease;\n}\n.glasshome-widget-backdrop > img.area-night[data-visible] {\n opacity: 1;\n}\n.glasshome-widget-backdrop > .glasshome-widget-backdrop-light {\n position: absolute;\n inset: 0;\n background: radial-gradient(90% 80% at 25% 30%, oklch(0.8 0.14 70), transparent 70%);\n mix-blend-mode: soft-light;\n opacity: var(--backdrop-light, 0);\n transition: opacity 500ms ease;\n}\n.glasshome-widget-backdrop::after {\n content: \"\";\n position: absolute;\n inset: 0;\n background: linear-gradient(\n to bottom,\n color-mix(in oklch, var(--card) 82%, transparent) 0%,\n color-mix(in oklch, var(--card) 40%, transparent) 22%,\n transparent 42%,\n transparent 62%,\n color-mix(in oklch, var(--card) 45%, transparent) 82%,\n color-mix(in oklch, var(--card) 80%, transparent) 100%\n );\n}\n\n@container widget (max-width: 239px) {\n .glasshome-widget-choice [data-optional] {\n display: none;\n }\n}\n@container widget (max-width: 259px) {\n .glasshome-widget-head-aside {\n display: none;\n }\n}\n@container widget (max-width: 139px) {\n .glasshome-widget-controls {\n display: none;\n }\n}\n\n.glasshome-widget-controls {\n justify-content: flex-start;\n align-items: center;\n}\n.glasshome-widget-control.glasshome-widget-control {\n width: var(--widget-control-h);\n height: var(--widget-control-h);\n}\n.glasshome-widget-choice .glasshome-widget-control.glasshome-widget-control {\n width: calc(var(--widget-control-h) - 8px);\n height: calc(var(--widget-control-h) - 8px);\n}\n@container widget (max-width: 179px) and (min-height: 130px) {\n .glasshome-widget-grid:not(:has(> .glasshome-widget-backdrop)) .glasshome-widget-head-text {\n min-height: 0;\n }\n .glasshome-widget-grid:not(:has(> .glasshome-widget-backdrop)) .glasshome-widget-head {\n flex-direction: column;\n gap: 6px;\n }\n}\n.glasshome-widget-unit[data-degree] {\n vertical-align: top;\n margin-left: 0.02em;\n color: var(--foreground);\n}\n.glasshome-widget-control-wide.glasshome-widget-control-wide {\n height: var(--widget-control-h);\n gap: 6px;\n padding: 0 14px;\n}\n@container widget (max-width: 179px) {\n .glasshome-widget-grid:has(> .glasshome-widget-backdrop) .glasshome-widget-head-icon {\n display: none;\n }\n}\n.glasshome-widget-grid:has(> .glasshome-widget-backdrop) :is(.glasshome-widget-reading-value, .glasshome-widget-sub, .glasshome-widget-name, .glasshome-widget-eyebrow) {\n text-shadow: 0 1px 8px color-mix(in oklch, var(--card) 75%, transparent);\n}\n.glasshome-widget-grid:has(> .glasshome-widget-backdrop) :is(.glasshome-widget-sub, .glasshome-widget-eyebrow) {\n color: color-mix(in oklch, var(--foreground) 82%, transparent);\n}\n/* Control glyphs and words keep their share of a control as it grows past 4x4. */\n.glasshome-widget-grid :is(.glasshome-widget-control, .glasshome-widget-control-wide) :is(iconify-icon, svg) {\n font-size: calc(var(--widget-control-h) * 0.43);\n width: 1em;\n height: 1em;\n}\n:is(.glasshome-widget-controls, .glasshome-widget-control-wide) {\n font-size: max(13px, calc(var(--widget-control-h) * 0.3));\n}\n/* A big tile with no art of its own carries its icon large and faint, so the glass is not an empty box. */\n@container widget (min-width: 300px) and (min-height: 260px) {\n .glasshome-widget-grid:not(:has(> .glasshome-widget-backdrop), :has(> .glasshome-widget-hero[data-art], > .glasshome-widget-art, > .glasshome-widget-layer))\n > .glasshome-widget-glyph {\n display: block;\n right: -3%;\n bottom: -12%;\n font-size: min(78cqb, 52cqi);\n opacity: 0.1;\n }\n}\n/* A one-shot action confirms on its control and on the glyph, the one thing a compact tile shows. */\n.glasshome-widget-grid > .glasshome-widget-glyph {\n transition: opacity var(--duration-state) var(--ease-morph);\n}\n.glasshome-widget[data-confirmed] .glasshome-widget-glyph {\n opacity: 0.5;\n}\n.glasshome-widget-grid [data-confirmed]:is(.glasshome-widget-control, .glasshome-widget-control-wide) {\n --glass-base: color-mix(in srgb, var(--widget-color) 35%, transparent);\n transition: background-color var(--duration-state) var(--ease-morph);\n}\n\n/* === Sheet: what a held tile opens (framework/panel) ===\n * Rendered in a popover or dialog outside the widget's shadow root, so every rule\n * the sheet needs lives here, never in a widget's own utilities. */\n.glasshome-sheet {\n display: flex;\n flex-direction: column;\n gap: 18px;\n color: var(--foreground);\n}\n.glasshome-sheet [data-slot=\"swatch-picker\"] {\n max-width: 360px;\n}\n.glasshome-panel-section {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n.glasshome-panel-label {\n margin: 0;\n font-size: 13px;\n font-weight: 500;\n color: var(--muted-foreground);\n}\n.glasshome-panel-rows {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));\n gap: 8px;\n}\n.glasshome-panel-row-wrap {\n position: relative;\n min-width: 0;\n}\n.glasshome-panel-row {\n --widget-wash: 22%;\n --widget-wash-2: 11%;\n --widget-radius: var(--radius-lg, 16px);\n --widget-icon-box: 34px;\n --widget-icon-glyph: 19px;\n position: relative;\n width: 100%;\n height: 56px;\n justify-content: flex-start;\n gap: 10px;\n padding: 0 12px 0 10px;\n overflow: hidden;\n text-align: left;\n touch-action: pan-y;\n}\n.glasshome-panel-row-static {\n cursor: default;\n scale: none;\n}\n.glasshome-panel-row-fill {\n position: absolute;\n inset: 0;\n pointer-events: none;\n}\n.glasshome-panel-row-icon {\n position: relative;\n display: flex;\n flex-shrink: 0;\n align-items: center;\n justify-content: center;\n}\n.glasshome-panel-row-icon-off {\n color: var(--muted-foreground);\n}\n.glasshome-panel-row-text {\n position: relative;\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n.glasshome-panel-row-name,\n.glasshome-panel-row-state,\n.glasshome-panel-fact-label {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n/* A reading may take a second line rather than lose its end (\"3 Moderate\"). */\n.glasshome-panel-fact-value {\n overflow: hidden;\n display: -webkit-box;\n -webkit-box-orient: vertical;\n -webkit-line-clamp: 2;\n}\n.glasshome-panel-row-name,\n.glasshome-panel-fact-value {\n font-size: 14px;\n font-weight: 500;\n color: var(--foreground);\n}\n.glasshome-panel-row-state,\n.glasshome-panel-fact-label {\n font-size: 12px;\n font-weight: 400;\n color: var(--muted-foreground);\n}\n.glasshome-panel-row-trailing {\n position: absolute;\n top: 50%;\n right: 10px;\n translate: 0 -50%;\n}\n.glasshome-panel-facts {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));\n row-gap: 10px;\n padding: 10px 4px 10px 0;\n overflow: hidden;\n border-radius: var(--radius-lg, 16px);\n --glass-base: color-mix(in srgb, var(--card) var(--material-clarity, 60%), transparent);\n backdrop-filter: blur(calc(var(--material-blur, 24px) * 0.6)) saturate(1.6);\n -webkit-backdrop-filter: blur(calc(var(--material-blur, 24px) * 0.6)) saturate(1.6);\n}\n/* Without blur a see-through strip is just see-through: it takes the card fill. */\n.reduce-blur .glasshome-panel-facts {\n --glass-base: var(--card);\n}\n.glasshome-panel-fact {\n display: flex;\n min-width: 0;\n align-items: center;\n gap: 8px;\n padding: 0 12px;\n box-shadow: -1px 0 0 color-mix(in oklch, var(--foreground) 10%, transparent);\n}\n.glasshome-panel-fact-icon {\n flex-shrink: 0;\n font-size: 18px;\n color: var(--muted-foreground);\n}\n\n/* Inset: the grid cell clips anything drawn outside the tile. */\n.glasshome-widget:focus-visible {\n outline: 2px solid var(--ring);\n outline-offset: -2px;\n}\n\n/* === Hold flood: the dock's hold fill (ui HOLD_MOTION), grown from the finger === */\n.glasshome-widget-hold-flood {\n position: absolute;\n translate: -50% -50%;\n border-radius: 9999px;\n background: color-mix(in oklch, var(--primary) 45%, transparent);\n pointer-events: none;\n scale: 0;\n opacity: 0;\n transition:\n scale var(--duration-state, 200ms) var(--ease-contract, ease-in),\n opacity var(--duration-state, 200ms) var(--ease-contract, ease-in);\n}\n.glasshome-widget-hold-flood[data-hold] {\n scale: 1;\n opacity: 1;\n transition-duration: var(--duration-morph, 400ms);\n transition-timing-function: linear;\n}\n.glasshome-widget-hold-flood[data-fired] {\n scale: 1;\n opacity: 0;\n}\n/* A row of ui buttons in a sheet (scenes, one-tap actions). */\n.glasshome-sheet-actions {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n/* The sheet wears its tile: the tile's tone washed into the glass, its picture as a band behind\n the head that fades into the glass, so the tile reads as having grown open. */\n.glasshome-sheet-surface {\n --glass-wash: 20%;\n --glass-wash-2: 7%;\n --widget-icon-box: 48px;\n --widget-icon-glyph: 26px;\n}\n.glasshome-sheet-band {\n position: absolute;\n inset: 0 0 auto;\n height: 150px;\n overflow: hidden;\n pointer-events: none;\n mask-image: linear-gradient(180deg, #000 30%, transparent);\n}\n.glasshome-sheet-band > img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n opacity: 0.55;\n}\n.glasshome-sheet-surface > [data-slot$=\"-header\"],\n.glasshome-sheet-surface > [data-slot$=\"-body\"] {\n position: relative;\n}\n.glasshome-sheet-icon {\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n", er = !1, tr, nr = /* @__PURE__ */ new WeakSet();
709
+ var $n = "/* Authoritative SDK CSS contract. Injected once on first Widget mount\n * via framework/theming/tokens.ts.\n *\n * All widget-internal visual scale is driven from CSS custom properties +\n * container queries on the `.glasshome-widget` shell (`container-type: size`).\n * No JS classifier, no size tier enum.\n */\n\n:root {\n --tone-success: oklch(0.7 0.18 145);\n --tone-warning: oklch(0.8 0.17 75);\n --tone-danger: oklch(0.64 0.22 27);\n --tone-info: oklch(0.66 0.2 245);\n --tone-neutral: oklch(0.65 0.02 250);\n --tone-accent: oklch(0.74 0.18 60);\n\n --widget-glow-default: 0.4;\n}\n\n.dark {\n --tone-success: oklch(0.74 0.18 145);\n --tone-warning: oklch(0.82 0.17 75);\n --tone-danger: oklch(0.68 0.22 27);\n --tone-info: oklch(0.7 0.2 245);\n --tone-neutral: oklch(0.69 0.02 250);\n --tone-accent: oklch(0.78 0.18 60);\n\n --widget-glow-default: 0.5;\n}\n\n@property --widget-color {\n syntax: \"<color>\";\n inherits: true;\n initial-value: oklch(0.65 0.02 250);\n}\n\n/* === Widget shell ===\n * One scale: --widget-unit is 1% of the shell's short side. Each size is a floor,\n * a line through the designed 2x2 and 4x4 sizes, and past 4x4 the share it has\n * at 4x4. A widget sizes its own text with these tokens and scales with every tile. */\n.glasshome-widget {\n --widget-color: var(--tone-neutral);\n --widget-glow-strength: var(--widget-glow-default);\n --widget-radius: var(--radius-xl, 12px);\n\n --widget-unit: 1cqmin;\n --widget-text-value: max(\n 22px,\n calc(8.5px + 13.4 * var(--widget-unit)),\n calc(16 * var(--widget-unit))\n );\n --widget-text-name: max(\n 14px,\n calc(8.9px + 4.37 * var(--widget-unit)),\n calc(7.07 * var(--widget-unit))\n );\n --widget-text-meta: max(\n 11px,\n calc(8px + 2.44 * var(--widget-unit)),\n calc(4.88 * var(--widget-unit))\n );\n --widget-text-sub: max(\n 12px,\n calc(10.1px + 1.22 * var(--widget-unit)),\n calc(4.3 * var(--widget-unit))\n );\n --widget-text-caption: max(11px, calc(3.6 * var(--widget-unit)));\n --widget-control-h: max(36px, calc(30px + 3.84 * var(--widget-unit)));\n --widget-grid-pad: max(\n 10px,\n calc(1.2px + 5.64 * var(--widget-unit)),\n calc(6 * var(--widget-unit))\n );\n --widget-grid-gap: max(6px, calc(2.5px + 2.23 * var(--widget-unit)));\n --widget-icon-box: max(\n 44px,\n calc(31.3px + 8.14 * var(--widget-unit)),\n calc(17.7 * var(--widget-unit))\n );\n --widget-icon-glyph: max(\n 24px,\n calc(16px + 5.12 * var(--widget-unit)),\n calc(10 * var(--widget-unit))\n );\n --widget-pad: clamp(8px, 3.5cqb, 22px);\n --widget-gap: clamp(6px, 2cqb, 14px);\n --widget-title-size: clamp(12px, 2cqi, 18px);\n --widget-subtitle: clamp(12px, 2.5cqi, 20px);\n --widget-status-size: clamp(14px, 5cqi, 36px);\n --widget-value-size: clamp(18px, 7cqi, 48px);\n --widget-badge-size: clamp(11px, 1.6cqi, 14px);\n}\n\n/* Shell material: ui's .glass, worn through the class Widget.tsx carries.\n * Unlayered and class-specific, so these beat the formula's :where() defaults\n * (wash-2 is re-declared there and cannot be set from a utility). The tone\n * stays the formula's transparent default until Widget.tsx sets one inline:\n * a widget without a tone is the plain card, never a neutral-grey wash. */\n.glasshome-widget {\n --widget-wash: 22%;\n --widget-wash-2: 11%;\n --glass-wash: var(--widget-wash);\n --glass-wash-2: var(--widget-wash-2);\n --glass-wash-angle: 135deg;\n --glass-base: color-mix(in srgb, var(--card) var(--material-clarity, 60%), transparent);\n --glass-rim: 0.3;\n --glass-lift: 0.45;\n --glass-shade: 0.05;\n backdrop-filter: var(--widget-backdrop, none);\n -webkit-backdrop-filter: var(--widget-backdrop, none);\n}\n/* A fill widget keeps a neutral card; the fill carries the tone, so a full fill is the toned card. */\n.glasshome-widget:has(.glasshome-widget-slider-fill) {\n --glass-wash: 0%;\n --glass-wash-2: 0%;\n}\n.dark .glasshome-widget {\n --glass-shade: 0;\n}\n\n/* === Slot: content layout === */\n/* Default: column. Flip to row only on wide-and-short widgets (matches the\n * old contentLayout heuristic: aspect > 1 AND height < 150px). */\n.glasshome-widget-content {\n position: relative;\n display: flex;\n flex-direction: column;\n justify-content: space-between;\n width: 100%;\n height: 100%;\n padding: var(--widget-pad);\n gap: var(--widget-gap);\n overflow: hidden;\n border-radius: var(--widget-radius);\n}\n@container widget (min-aspect-ratio: 1) and (max-height: 149px) {\n .glasshome-widget-content {\n flex-direction: row;\n align-items: center;\n justify-content: flex-start;\n }\n}\n\n/* === Compact 1×1 ===\n * Small in BOTH axes → stack the tile: icon on top, label + status centered\n * under it. Icon stays moderate (not a hero) so the text keeps room. Wide\n * tiles (2×1) fail the width test and keep the row layout above. Placed after\n * the row-flip so it wins. */\n@container widget (max-width: 189px) and (max-height: 189px) {\n .glasshome-widget-content {\n flex-direction: column;\n align-items: center;\n justify-content: center;\n text-align: center;\n gap: clamp(4px, 2.5cqmin, 8px);\n }\n .glasshome-widget-content > * {\n align-items: center;\n text-align: center;\n }\n .glasshome-widget-icon {\n --widget-icon-box: clamp(36px, 30cqmin, 56px);\n --widget-icon-glyph: clamp(20px, 17cqmin, 32px);\n }\n .glasshome-widget-title {\n font-size: clamp(11px, 6cqmin, 14px);\n max-width: 100%;\n }\n .glasshome-widget-status,\n .glasshome-widget-value {\n font-size: clamp(13px, 7cqmin, 17px);\n }\n .glasshome-widget-content [class*=\"ml-auto\"] {\n margin-left: 0 !important;\n }\n}\n.glasshome-widget-content [class*=\"ml-auto\"] {\n position: relative;\n z-index: 30;\n}\n\n/* === Slot: icon ===\n * ui's tinted icon pill (ICON_PILL_TINT): the disc is .glass, so the material shapes it. */\n.glasshome-widget-icon {\n width: var(--widget-icon-box);\n height: var(--widget-icon-box);\n border-radius: var(--radius-sm, 12px);\n --glass-tone: var(--widget-icon-color, var(--widget-color));\n --glass-rim: 0.4;\n --glass-wash: 18%;\n}\n.glasshome-widget-icon-glyph {\n font-size: var(--widget-icon-glyph);\n line-height: 1;\n}\n.glasshome-widget-icon-stack {\n --stack-step: calc(var(--widget-icon-box) * 0.12);\n position: absolute;\n /* The pill's border would otherwise shrink each plate and widen the first step. */\n inset: -1px;\n width: auto;\n height: auto;\n border-radius: var(--radius-sm, 12px);\n}\n.glasshome-widget-icon-stack-2 {\n translate: var(--stack-step) var(--stack-step);\n opacity: 0.45;\n z-index: -2;\n}\n.glasshome-widget-icon-stack-3 {\n translate: calc(2 * var(--stack-step)) calc(2 * var(--stack-step));\n opacity: 0.2;\n z-index: -3;\n}\n\n/* === Slot: text === */\n.glasshome-widget-title {\n font-size: var(--widget-title-size);\n line-height: 1.2;\n color: var(--color-muted-foreground, currentColor);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.glasshome-widget-subtitle {\n font-size: var(--widget-subtitle);\n color: var(--color-muted-foreground, currentColor);\n}\n.glasshome-widget-status {\n font-size: var(--widget-status-size);\n font-weight: 700;\n color: var(--color-foreground, currentColor);\n line-height: 1.1;\n}\n.glasshome-widget-value {\n font-size: var(--widget-value-size);\n font-weight: 700;\n color: var(--color-foreground, currentColor);\n line-height: 1.05;\n}\n.glasshome-widget-badge {\n font-size: var(--widget-badge-size);\n font-weight: 500;\n padding: 0.125rem 0.5rem;\n border-radius: 9999px;\n background: color-mix(in oklch, var(--color-foreground, currentColor) 20%, transparent);\n color: var(--color-foreground, currentColor);\n}\n\n.glasshome-widget-loading {\n background: color-mix(in oklch, var(--widget-color) 20%, transparent);\n}\n\n/* === Slider fill ===\n * Value flows in via --widget-fill-value (0-100). Vertical orientation auto-\n * detected via container query. */\n.glasshome-widget-slider-fill {\n --widget-fill-value: 0;\n background: linear-gradient(\n 135deg,\n color-mix(\n in srgb,\n var(--widget-color) calc(var(--widget-wash) * var(--material-tint, 1)),\n transparent\n ),\n color-mix(\n in srgb,\n var(--widget-color-to, var(--widget-color))\n calc(var(--widget-wash-2) * var(--material-tint, 1)),\n transparent\n )\n );\n /* Inset: the clip-path cuts any outer shadow. */\n box-shadow: inset 0 0 calc(var(--material-glow, 0px) * 1.2)\n oklch(from var(--widget-color) l c h / 0.55);\n clip-path: inset(0 calc((100 - var(--widget-fill-value)) * 1%) 0 0 round var(--widget-radius));\n}\n@container widget (max-aspect-ratio: 1) {\n .glasshome-widget-slider-fill {\n clip-path: inset(calc((100 - var(--widget-fill-value)) * 1%) 0 0 0 round var(--widget-radius));\n }\n}\n\n/* === Anatomy: Widget.Content with a Widget.Head lays its parts out as a tile === */\n.glasshome-widget-grid {\n position: relative;\n display: grid;\n grid-template-columns: max-content minmax(0, 1fr);\n grid-template-rows: auto minmax(0, 1fr) auto;\n gap: var(--widget-grid-gap);\n height: 100%;\n padding: var(--widget-grid-pad);\n overflow: hidden;\n isolation: isolate;\n color: var(--foreground);\n}\n\n/* In-flow content spans the tile (the max-content first column would collapse it); absolute parts keep the padding box. */\n.glasshome-widget-grid\n > :where(:not(.glasshome-widget-backdrop, .glasshome-widget-layer, .glasshome-widget-glyph)) {\n grid-column: 1 / -1;\n}\n.glasshome-widget-layer {\n position: absolute;\n inset: 0;\n z-index: -1;\n overflow: hidden;\n}\n.glasshome-widget-head {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n min-width: 0;\n}\n.glasshome-widget-head-text {\n min-height: var(--widget-icon-box);\n justify-content: center;\n display: flex;\n flex-direction: column;\n gap: 1px;\n min-width: 0;\n flex: 1;\n}\n.glasshome-widget-head-aside {\n display: flex;\n flex-wrap: wrap;\n justify-content: flex-end;\n gap: 6px;\n max-height: 26px;\n overflow: hidden;\n flex-shrink: 1;\n}\n.glasshome-widget-eyebrow {\n font-size: var(--widget-text-meta);\n font-weight: 500;\n line-height: 1.25;\n color: var(--muted-foreground);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.glasshome-widget-name {\n font-size: var(--widget-text-name);\n font-weight: 650;\n line-height: 1.2;\n letter-spacing: -0.01em;\n color: var(--foreground);\n display: -webkit-box;\n -webkit-box-orient: vertical;\n -webkit-line-clamp: 2;\n overflow: hidden;\n overflow-wrap: break-word;\n}\n\n.glasshome-widget-hero {\n display: flex;\n align-items: flex-end;\n justify-content: space-between;\n gap: var(--widget-grid-gap);\n min-height: 0;\n min-width: 0;\n}\n.glasshome-widget-reading {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n z-index: 1;\n}\n.glasshome-widget-reading-value {\n font-size: var(--widget-text-value);\n font-weight: 650;\n line-height: 1;\n letter-spacing: -0.03em;\n font-variant-numeric: tabular-nums;\n color: var(--foreground);\n white-space: nowrap;\n}\n/* Alternating names restart the rise on each change without rebuilding the text. */\n.glasshome-widget-reading-value[data-turn=\"a\"] {\n animation: tile-value-in-a var(--duration-morph) var(--ease-morph);\n}\n.glasshome-widget-reading-value[data-turn=\"b\"] {\n animation: tile-value-in-b var(--duration-morph) var(--ease-morph);\n}\n@keyframes tile-value-in-a {\n from {\n opacity: 0;\n transform: translateY(0.25em);\n }\n}\n@keyframes tile-value-in-b {\n from {\n opacity: 0;\n transform: translateY(0.25em);\n }\n}\n.glasshome-widget-unit {\n font-size: 0.5em;\n font-weight: 550;\n letter-spacing: 0;\n margin-left: 0.08em;\n color: var(--muted-foreground);\n}\n.glasshome-widget-sub {\n font-size: var(--widget-text-sub);\n font-weight: 500;\n color: var(--muted-foreground);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* Placed on the tile, not the hero, so a control row never moves or shrinks it. */\n.glasshome-widget-art {\n position: absolute;\n right: var(--widget-grid-pad);\n bottom: var(--widget-grid-pad);\n height: min(\n calc(100% - 2 * var(--widget-grid-pad) - var(--widget-icon-box) - var(--widget-grid-gap)),\n 48cqi\n );\n display: flex;\n align-items: flex-end;\n justify-content: flex-end;\n pointer-events: none;\n}\n/* Beside art a long word (\"Unlocked\") shrinks to end where the art starts, at about 50cqi; short values keep full size. */\n@container widget (min-width: 180px) and (min-height: 130px) {\n .glasshome-widget-hero[data-art] .glasshome-widget-reading-value {\n font-size: max(18px, min(var(--widget-text-value), calc(50cqi / (var(--value-len, 1) * 0.56))));\n }\n}\n.glasshome-widget-backdrop > img {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n object-fit: cover;\n}\n/* Art keeps its shape and clears a state word at its largest (15cqi, about 3.4em); each art sets --art-aspect. */\n.glasshome-widget-grid .glasshome-widget-art > * {\n height: min(\n 100%,\n calc((52cqi - 2 * var(--widget-grid-pad) - var(--widget-grid-gap)) / var(--art-aspect, 1))\n );\n flex-shrink: 0;\n}\n.glasshome-widget-grid .glasshome-widget-art > img {\n width: auto;\n height: auto;\n max-height: 100%;\n max-width: calc(52cqi - 2 * var(--widget-grid-pad) - var(--widget-grid-gap));\n}\n\n.glasshome-widget-controls {\n display: flex;\n gap: 8px;\n min-width: 0;\n}\n\n.glasshome-widget-glyph {\n display: none;\n position: absolute;\n right: -8%;\n bottom: -22%;\n font-size: min(130cqb, 80cqi);\n line-height: 1;\n color: var(--widget-color);\n opacity: 0.16;\n pointer-events: none;\n z-index: -1;\n}\n\n/* Controls sit on the bottom row beside the value, or under it on a narrow tile; the art lifts above them. */\n.glasshome-widget-head {\n grid-column: 1 / -1;\n}\n.glasshome-widget-hero {\n grid-row: 3;\n grid-column: 1 / -1;\n}\n.glasshome-widget-grid:has(> .glasshome-widget-controls) > .glasshome-widget-hero {\n grid-column: 1;\n}\n.glasshome-widget-controls {\n grid-row: 3;\n grid-column: 2;\n align-self: end;\n position: relative;\n z-index: 1;\n}\n.glasshome-widget-grid:has(> .glasshome-widget-hero[data-empty]) > .glasshome-widget-controls {\n grid-column: 1 / -1;\n}\n@container widget (max-width: 519px) {\n .glasshome-widget-grid:has(> .glasshome-widget-controls) {\n grid-template-rows: auto minmax(0, 1fr) auto auto;\n }\n .glasshome-widget-grid:has(> .glasshome-widget-controls) > .glasshome-widget-hero {\n grid-column: 1 / -1;\n }\n .glasshome-widget-controls {\n grid-row: 4;\n grid-column: 1 / -1;\n }\n .glasshome-widget-grid:has(> .glasshome-widget-controls) .glasshome-widget-art {\n bottom: calc(var(--widget-grid-pad) + var(--widget-control-h) + var(--widget-grid-gap));\n height: min(\n calc(\n 100% - 2 * var(--widget-grid-pad) - var(--widget-icon-box) - 2 * var(--widget-grid-gap) -\n var(--widget-control-h)\n ),\n 48cqi\n );\n }\n}\n@container widget (max-height: 229px) {\n .glasshome-widget-controls {\n display: none;\n }\n .glasshome-widget-grid:has(> .glasshome-widget-controls) {\n grid-template-rows: auto minmax(0, 1fr) auto;\n }\n .glasshome-widget-grid:has(> .glasshome-widget-controls) .glasshome-widget-art {\n bottom: var(--widget-grid-pad);\n height: min(\n calc(100% - 2 * var(--widget-grid-pad) - var(--widget-icon-box) - var(--widget-grid-gap)),\n 48cqi\n );\n }\n}\n@container widget (max-width: 179px) {\n .glasshome-widget-art {\n display: none;\n }\n}\n@container widget (max-height: 169px) {\n .glasshome-widget-head-aside {\n display: none;\n }\n}\n@container widget ((max-height: 129px) or (max-width: 139px)) {\n .glasshome-widget-head-text {\n min-height: 0;\n }\n .glasshome-widget-name {\n -webkit-line-clamp: 1;\n }\n .glasshome-widget-grid {\n display: flex;\n flex-direction: column;\n justify-content: space-between;\n gap: 2px;\n }\n .glasshome-widget-head-icon,\n .glasshome-widget-art,\n .glasshome-widget-eyebrow,\n .glasshome-widget-sub {\n display: none;\n }\n .glasshome-widget-glyph {\n display: block;\n }\n .glasshome-widget-reading-value {\n font-size: clamp(18px, min(34cqb, 20cqi), 30px);\n }\n .glasshome-widget-name {\n font-size: clamp(12px, min(18cqb, 11cqi), 15px);\n font-weight: 550;\n color: var(--muted-foreground);\n }\n}\n@container widget (max-height: 129px) and (min-width: 260px) {\n .glasshome-widget-grid {\n flex-direction: row;\n align-items: center;\n justify-content: space-between;\n gap: var(--widget-grid-gap);\n }\n .glasshome-widget-name {\n font-size: clamp(14px, 22cqb, 18px);\n font-weight: 650;\n color: var(--foreground);\n }\n .glasshome-widget-eyebrow {\n display: block;\n }\n}\n\n.glasshome-widget-backdrop {\n position: absolute;\n inset: 0;\n z-index: -2;\n overflow: hidden;\n}\n.glasshome-widget-backdrop > img.area-night {\n opacity: 0;\n transition: opacity 700ms ease;\n}\n.glasshome-widget-backdrop > img.area-night[data-visible] {\n opacity: 1;\n}\n.glasshome-widget-backdrop > .glasshome-widget-backdrop-light {\n position: absolute;\n inset: 0;\n background: radial-gradient(90% 80% at 25% 30%, oklch(0.8 0.14 70), transparent 70%);\n mix-blend-mode: soft-light;\n opacity: var(--backdrop-light, 0);\n transition: opacity 500ms ease;\n}\n.glasshome-widget-backdrop::after {\n content: \"\";\n position: absolute;\n inset: 0;\n background: linear-gradient(\n to bottom,\n color-mix(in oklch, var(--card) 82%, transparent) 0%,\n color-mix(in oklch, var(--card) 40%, transparent) 22%,\n transparent 42%,\n transparent 62%,\n color-mix(in oklch, var(--card) 45%, transparent) 82%,\n color-mix(in oklch, var(--card) 80%, transparent) 100%\n );\n}\n\n@container widget (max-width: 239px) {\n .glasshome-widget-choice [data-optional] {\n display: none;\n }\n}\n@container widget (max-width: 259px) {\n .glasshome-widget-head-aside {\n display: none;\n }\n}\n@container widget (max-width: 139px) {\n .glasshome-widget-controls {\n display: none;\n }\n}\n\n.glasshome-widget-controls {\n justify-content: flex-start;\n align-items: center;\n}\n.glasshome-widget-control.glasshome-widget-control {\n width: var(--widget-control-h);\n height: var(--widget-control-h);\n}\n.glasshome-widget-choice .glasshome-widget-control.glasshome-widget-control {\n width: calc(var(--widget-control-h) - 8px);\n height: calc(var(--widget-control-h) - 8px);\n}\n@container widget (max-width: 179px) and (min-height: 130px) {\n .glasshome-widget-grid:not(:has(> .glasshome-widget-backdrop)) .glasshome-widget-head-text {\n min-height: 0;\n }\n .glasshome-widget-grid:not(:has(> .glasshome-widget-backdrop)) .glasshome-widget-head {\n flex-direction: column;\n gap: 6px;\n }\n}\n.glasshome-widget-unit[data-degree] {\n vertical-align: top;\n margin-left: 0.02em;\n color: var(--foreground);\n}\n.glasshome-widget-control-wide.glasshome-widget-control-wide {\n height: var(--widget-control-h);\n gap: 6px;\n padding: 0 14px;\n}\n@container widget (max-width: 179px) {\n .glasshome-widget-grid:has(> .glasshome-widget-backdrop) .glasshome-widget-head-icon {\n display: none;\n }\n}\n.glasshome-widget-grid:has(> .glasshome-widget-backdrop)\n :is(\n .glasshome-widget-reading-value,\n .glasshome-widget-sub,\n .glasshome-widget-name,\n .glasshome-widget-eyebrow\n ) {\n text-shadow: 0 1px 8px color-mix(in oklch, var(--card) 75%, transparent);\n}\n.glasshome-widget-grid:has(> .glasshome-widget-backdrop)\n :is(.glasshome-widget-sub, .glasshome-widget-eyebrow) {\n color: color-mix(in oklch, var(--foreground) 82%, transparent);\n}\n/* Control glyphs and words keep their share of a control as it grows past 4x4. */\n.glasshome-widget-grid\n :is(.glasshome-widget-control, .glasshome-widget-control-wide)\n :is(iconify-icon, svg) {\n font-size: calc(var(--widget-control-h) * 0.43);\n width: 1em;\n height: 1em;\n}\n:is(.glasshome-widget-controls, .glasshome-widget-control-wide) {\n font-size: max(13px, calc(var(--widget-control-h) * 0.3));\n}\n/* A big tile with no art of its own carries its icon large and faint, so the glass is not an empty box. */\n@container widget (min-width: 300px) and (min-height: 260px) {\n .glasshome-widget-grid:not(\n :has(> .glasshome-widget-backdrop),\n :has(> .glasshome-widget-hero[data-art], > .glasshome-widget-art, > .glasshome-widget-layer)\n )\n > .glasshome-widget-glyph {\n display: block;\n right: -3%;\n bottom: -12%;\n font-size: min(78cqb, 52cqi);\n opacity: 0.1;\n }\n}\n/* A one-shot action confirms on its control and on the glyph, the one thing a compact tile shows. */\n.glasshome-widget-grid > .glasshome-widget-glyph {\n transition: opacity var(--duration-state) var(--ease-morph);\n}\n.glasshome-widget[data-confirmed] .glasshome-widget-glyph {\n opacity: 0.5;\n}\n.glasshome-widget-grid\n [data-confirmed]:is(.glasshome-widget-control, .glasshome-widget-control-wide) {\n --glass-base: color-mix(in srgb, var(--widget-color) 35%, transparent);\n transition: background-color var(--duration-state) var(--ease-morph);\n}\n\n/* === Sheet: what a held tile opens (framework/panel) ===\n * Rendered in a popover or dialog outside the widget's shadow root, so every rule\n * the sheet needs lives here, never in a widget's own utilities. */\n.glasshome-sheet {\n display: flex;\n flex-direction: column;\n gap: 18px;\n color: var(--foreground);\n}\n.glasshome-sheet [data-slot=\"swatch-picker\"] {\n max-width: 360px;\n}\n.glasshome-panel-section {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n.glasshome-panel-label {\n margin: 0;\n font-size: 13px;\n font-weight: 500;\n color: var(--muted-foreground);\n}\n.glasshome-panel-rows {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));\n gap: 8px;\n}\n.glasshome-panel-row-wrap {\n position: relative;\n min-width: 0;\n}\n.glasshome-panel-row {\n --widget-wash: 22%;\n --widget-wash-2: 11%;\n --widget-radius: var(--radius-lg, 16px);\n --widget-icon-box: 34px;\n --widget-icon-glyph: 19px;\n position: relative;\n width: 100%;\n height: 56px;\n justify-content: flex-start;\n gap: 10px;\n padding: 0 12px 0 10px;\n overflow: hidden;\n text-align: left;\n touch-action: pan-y;\n}\n.glasshome-panel-row-static {\n cursor: default;\n scale: none;\n}\n.glasshome-panel-row-fill {\n position: absolute;\n inset: 0;\n pointer-events: none;\n}\n.glasshome-panel-row-icon {\n position: relative;\n display: flex;\n flex-shrink: 0;\n align-items: center;\n justify-content: center;\n}\n.glasshome-panel-row-icon-off {\n color: var(--muted-foreground);\n}\n.glasshome-panel-row-text {\n position: relative;\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n.glasshome-panel-row-name,\n.glasshome-panel-row-state,\n.glasshome-panel-fact-label {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n/* A reading may take a second line rather than lose its end (\"3 Moderate\"). */\n.glasshome-panel-fact-value {\n overflow: hidden;\n display: -webkit-box;\n -webkit-box-orient: vertical;\n -webkit-line-clamp: 2;\n}\n.glasshome-panel-row-name,\n.glasshome-panel-fact-value {\n font-size: 14px;\n font-weight: 500;\n color: var(--foreground);\n}\n.glasshome-panel-row-state,\n.glasshome-panel-fact-label {\n font-size: 12px;\n font-weight: 400;\n color: var(--muted-foreground);\n}\n.glasshome-panel-row-trailing {\n position: absolute;\n top: 50%;\n right: 10px;\n translate: 0 -50%;\n}\n.glasshome-panel-facts {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));\n row-gap: 10px;\n padding: 10px 4px 10px 0;\n overflow: hidden;\n border-radius: var(--radius-lg, 16px);\n --glass-base: color-mix(in srgb, var(--card) var(--material-clarity, 60%), transparent);\n backdrop-filter: blur(calc(var(--material-blur, 24px) * 0.6)) saturate(1.6);\n -webkit-backdrop-filter: blur(calc(var(--material-blur, 24px) * 0.6)) saturate(1.6);\n}\n/* Without blur a see-through strip is just see-through: it takes the card fill. */\n.reduce-blur .glasshome-panel-facts {\n --glass-base: var(--card);\n}\n.glasshome-panel-fact {\n display: flex;\n min-width: 0;\n align-items: center;\n gap: 8px;\n padding: 0 12px;\n box-shadow: -1px 0 0 color-mix(in oklch, var(--foreground) 10%, transparent);\n}\n.glasshome-panel-fact-icon {\n flex-shrink: 0;\n font-size: 18px;\n color: var(--muted-foreground);\n}\n\n/* Inset: the grid cell clips anything drawn outside the tile. */\n.glasshome-widget:focus-visible {\n outline: 2px solid var(--ring);\n outline-offset: -2px;\n}\n\n/* === Hold flood: the dock's hold fill (ui HOLD_MOTION), grown from the finger === */\n.glasshome-widget-hold-flood {\n position: absolute;\n translate: -50% -50%;\n border-radius: 9999px;\n background: color-mix(in oklch, var(--primary) 45%, transparent);\n pointer-events: none;\n scale: 0;\n opacity: 0;\n transition:\n scale var(--duration-state, 200ms) var(--ease-contract, ease-in),\n opacity var(--duration-state, 200ms) var(--ease-contract, ease-in);\n}\n.glasshome-widget-hold-flood[data-hold] {\n scale: 1;\n opacity: 1;\n transition-duration: var(--duration-morph, 400ms);\n transition-timing-function: linear;\n}\n.glasshome-widget-hold-flood[data-fired] {\n scale: 1;\n opacity: 0;\n}\n/* A row of ui buttons in a sheet (scenes, one-tap actions). */\n.glasshome-sheet-actions {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n/* The sheet wears its tile: the tile's tone washed into the glass, its picture as a band behind\n the head that fades into the glass, so the tile reads as having grown open. */\n.glasshome-sheet-surface {\n --glass-wash: 20%;\n --glass-wash-2: 7%;\n --widget-icon-box: 48px;\n --widget-icon-glyph: 26px;\n}\n.glasshome-sheet-band {\n position: absolute;\n inset: 0 0 auto;\n height: 150px;\n overflow: hidden;\n pointer-events: none;\n mask-image: linear-gradient(180deg, #000 30%, transparent);\n}\n.glasshome-sheet-band > img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n opacity: 0.55;\n}\n.glasshome-sheet-surface > [data-slot$=\"-header\"],\n.glasshome-sheet-surface > [data-slot$=\"-body\"] {\n position: relative;\n}\n.glasshome-sheet-icon {\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n", er = !1, tr, nr = /* @__PURE__ */ new WeakSet();
710
710
  function rr() {
711
711
  return tr || (tr = new CSSStyleSheet(), tr.replaceSync($n.replace(/:root\b/g, ":host").replace(/^\.dark\b/gm, ":host(.dark)"))), tr;
712
712
  }
@@ -1113,7 +1113,7 @@ function Nr(e) {
1113
1113
  }
1114
1114
  //#endregion
1115
1115
  //#region src/framework/core/Widget.tsx
1116
- var Pr = /*#__PURE__*/ B("<span class=glasshome-widget-hold-flood aria-hidden=true>"), Fr = /*#__PURE__*/ B("<div><div class=\"relative h-full w-full\">"), Ir = /*#__PURE__*/ B("<div class=\"glasshome-widget-loading pointer-events-none absolute inset-0 animate-pulse\">"), Lr = /*#__PURE__*/ B("<div class=\"flex h-full w-full flex-col items-center justify-center gap-2 text-center\">"), Rr = /*#__PURE__*/ B("<div class=\"flex items-center justify-center text-muted-foreground\">"), zr = /*#__PURE__*/ B("<h3 class=\"font-semibold text-sm text-foreground\">"), Br = /*#__PURE__*/ B("<p class=\"text-muted-foreground text-xs\">");
1116
+ var Pr = /*#__PURE__*/ B("<span class=glasshome-widget-hold-flood aria-hidden=true>"), Fr = /*#__PURE__*/ B("<div><div class=\"relative h-full w-full\">"), Ir = /*#__PURE__*/ B("<div class=\"glasshome-widget-loading pointer-events-none absolute inset-0 animate-pulse\">"), Lr = /*#__PURE__*/ B("<div class=\"flex h-full w-full flex-col items-center justify-center gap-2 text-center\">"), Rr = /*#__PURE__*/ B("<div class=\"text-muted-foreground flex items-center justify-center\">"), zr = /*#__PURE__*/ B("<h3 class=\"text-foreground text-sm font-semibold\">"), Br = /*#__PURE__*/ B("<p class=\"text-muted-foreground text-xs\">");
1117
1117
  function Vr(t) {
1118
1118
  let n = A(W), r = xn(() => ({ hold: { action: () => (q.get(n ?? {}) ?? q.get(d))?.("edit") } }));
1119
1119
  k(r.dispose);
@@ -1240,7 +1240,7 @@ function Vr(t) {
1240
1240
  return P((t) => R(e, "z-index", K.OVERLAY)), e;
1241
1241
  })();
1242
1242
  })(), null), P((r) => {
1243
- var i = X("glasshome-widget glass", "relative h-full w-full select-none overflow-hidden rounded-xl", f()?.class, t.class), a = m(), o = t.confirmed || void 0, s = h() ? 0 : void 0, c = K.CONTENT;
1243
+ var i = X("glasshome-widget glass", "relative h-full w-full overflow-hidden rounded-xl select-none", f()?.class, t.class), a = m(), o = t.confirmed || void 0, s = h() ? 0 : void 0, c = K.CONTENT;
1244
1244
  return i !== r.e && M(e, r.e = i), r.t = z(e, a, r.t), o !== r.a && L(e, "data-confirmed", r.a = o), s !== r.o && L(e, "tabindex", r.o = s), c !== r.i && R(n, "z-index", r.i = c), r;
1245
1245
  }, {
1246
1246
  e: void 0,
@@ -1687,7 +1687,7 @@ function vi(e) {
1687
1687
  }
1688
1688
  //#endregion
1689
1689
  //#region src/framework/dialogs/debug-view.tsx
1690
- var yi = /*#__PURE__*/ B("<pre class=\"max-h-64 overflow-auto p-3 font-mono text-muted-foreground text-xs leading-relaxed\">"), bi = /*#__PURE__*/ B("<span class=\"flex items-center gap-2\">"), xi = /*#__PURE__*/ B("<section class=\"flex min-w-0 flex-col gap-2\"><header class=\"flex items-center gap-3\"><div class=\"flex min-w-0 flex-1 flex-col\"></div></header><div class=\"flex flex-col gap-2\">"), Si = /*#__PURE__*/ B("<div class=\"flex flex-col\">"), Ci = /*#__PURE__*/ B("<pre class=\"max-h-96 overflow-auto whitespace-pre-wrap p-3 font-mono text-muted-foreground text-xs leading-relaxed\">");
1690
+ var yi = /*#__PURE__*/ B("<pre class=\"text-muted-foreground max-h-64 overflow-auto p-3 font-mono text-xs leading-relaxed\">"), bi = /*#__PURE__*/ B("<span class=\"flex items-center gap-2\">"), xi = /*#__PURE__*/ B("<section class=\"flex min-w-0 flex-col gap-2\"><header class=\"flex items-center gap-3\"><div class=\"flex min-w-0 flex-1 flex-col\"></div></header><div class=\"flex flex-col gap-2\">"), Si = /*#__PURE__*/ B("<div class=\"flex flex-col\">"), Ci = /*#__PURE__*/ B("<pre class=\"text-muted-foreground max-h-96 overflow-auto p-3 font-mono text-xs leading-relaxed whitespace-pre-wrap\">");
1691
1691
  function wi(e, t, n) {
1692
1692
  return {
1693
1693
  widgetConfig: e,
@@ -1773,7 +1773,7 @@ function Fi(e) {
1773
1773
  },
1774
1774
  get children() {
1775
1775
  return [N(Se, {
1776
- class: "py-3 text-muted-foreground text-xs",
1776
+ class: "text-muted-foreground py-3 text-xs",
1777
1777
  get children() {
1778
1778
  var t = bi();
1779
1779
  return F(t, () => e.label, null), F(t, N(w, {
@@ -2071,7 +2071,7 @@ function Ui(e, t) {
2071
2071
  }
2072
2072
  //#endregion
2073
2073
  //#region src/framework/dialogs/WidgetDialog.tsx
2074
- var Wi = /*#__PURE__*/ B("<svg xmlns=http://www.w3.org/2000/svg class=size-3.5 fill=none viewBox=\"0 0 24 24\"stroke=currentColor stroke-width=2><path d=\"M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\"></path><path d=\"M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z\">"), Gi = /*#__PURE__*/ B("<div class=\"rounded-lg bg-muted/30 p-2 text-center md:p-6\"><p class=\"text-muted-foreground text-sm\">No edit options available"), Ki = /*#__PURE__*/ B("<svg xmlns=http://www.w3.org/2000/svg class=size-3.5 fill=none viewBox=\"0 0 24 24\"stroke=currentColor stroke-width=2><line x1=4 y1=21 x2=4 y2=14></line><line x1=4 y1=10 x2=4 y2=3></line><line x1=12 y1=21 x2=12 y2=12></line><line x1=12 y1=8 x2=12 y2=3></line><line x1=20 y1=21 x2=20 y2=16></line><line x1=20 y1=12 x2=20 y2=3></line><line x1=1 y1=14 x2=7 y2=14></line><line x1=9 y1=8 x2=15 y2=8></line><line x1=17 y1=16 x2=23 y2=16>"), qi = /*#__PURE__*/ B("<svg xmlns=http://www.w3.org/2000/svg class=size-3.5 fill=none viewBox=\"0 0 24 24\"stroke=currentColor stroke-width=2><path d=\"m8 2 1.88 1.88\"></path><path d=\"M14.12 3.88 16 2\"></path><path d=\"M9 7.13v-1a3.003 3.003 0 1 1 6 0v1\"></path><path d=\"M12 20c-3.3 0-6-2.7-6-6v-3a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v3c0 3.3-2.7 6-6 6\"></path><path d=\"M12 20v-9\"></path><path d=\"M6.53 9C4.6 8.8 3 7.1 3 5\"></path><path d=\"M6 13H2\"></path><path d=\"M3 21c0-2.1 1.7-3.9 3.8-4\"></path><path d=\"M20.97 5c0 2.1-1.6 3.8-3.5 4\"></path><path d=\"M22 13h-4\"></path><path d=\"M17.2 17c2.1.1 3.8 1.9 3.8 4\">"), Ji = /*#__PURE__*/ B("<div class=glasshome-sheet>"), Yi = /*#__PURE__*/ B("<div class=glasshome-sheet-band aria-hidden=true><img alt>"), Xi = /*#__PURE__*/ B("<span class=\"glasshome-widget-icon glass glass-tint glasshome-sheet-icon\">"), Zi = /*#__PURE__*/ B("<span class=\"inline-flex size-3.5 shrink-0 items-center\">"), Qi = (e) => Wt(e), $i = {
2074
+ var Wi = /*#__PURE__*/ B("<svg xmlns=http://www.w3.org/2000/svg class=size-3.5 fill=none viewBox=\"0 0 24 24\"stroke=currentColor stroke-width=2><path d=\"M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\"></path><path d=\"M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z\">"), Gi = /*#__PURE__*/ B("<div class=\"bg-muted/30 rounded-lg p-2 text-center md:p-6\"><p class=\"text-muted-foreground text-sm\">No edit options available"), Ki = /*#__PURE__*/ B("<svg xmlns=http://www.w3.org/2000/svg class=size-3.5 fill=none viewBox=\"0 0 24 24\"stroke=currentColor stroke-width=2><line x1=4 y1=21 x2=4 y2=14></line><line x1=4 y1=10 x2=4 y2=3></line><line x1=12 y1=21 x2=12 y2=12></line><line x1=12 y1=8 x2=12 y2=3></line><line x1=20 y1=21 x2=20 y2=16></line><line x1=20 y1=12 x2=20 y2=3></line><line x1=1 y1=14 x2=7 y2=14></line><line x1=9 y1=8 x2=15 y2=8></line><line x1=17 y1=16 x2=23 y2=16>"), qi = /*#__PURE__*/ B("<svg xmlns=http://www.w3.org/2000/svg class=size-3.5 fill=none viewBox=\"0 0 24 24\"stroke=currentColor stroke-width=2><path d=\"m8 2 1.88 1.88\"></path><path d=\"M14.12 3.88 16 2\"></path><path d=\"M9 7.13v-1a3.003 3.003 0 1 1 6 0v1\"></path><path d=\"M12 20c-3.3 0-6-2.7-6-6v-3a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v3c0 3.3-2.7 6-6 6\"></path><path d=\"M12 20v-9\"></path><path d=\"M6.53 9C4.6 8.8 3 7.1 3 5\"></path><path d=\"M6 13H2\"></path><path d=\"M3 21c0-2.1 1.7-3.9 3.8-4\"></path><path d=\"M20.97 5c0 2.1-1.6 3.8-3.5 4\"></path><path d=\"M22 13h-4\"></path><path d=\"M17.2 17c2.1.1 3.8 1.9 3.8 4\">"), Ji = /*#__PURE__*/ B("<div class=glasshome-sheet>"), Yi = /*#__PURE__*/ B("<div class=glasshome-sheet-band aria-hidden=true><img alt>"), Xi = /*#__PURE__*/ B("<span class=\"glasshome-widget-icon glass glass-tint glasshome-sheet-icon\">"), Zi = /*#__PURE__*/ B("<span class=\"inline-flex size-3.5 shrink-0 items-center\">"), Qi = (e) => Wt(e), $i = {
2075
2075
  sm: "sm",
2076
2076
  md: "md",
2077
2077
  lg: "lg",
@@ -2107,13 +2107,17 @@ function ta(e) {
2107
2107
  }, [f, p] = O(t.defaultTab ?? "controls"), m = () => t.activeTab ?? f(), h = (e) => {
2108
2108
  t.onActiveTabChange ? t.onActiveTabChange(e) : p(e);
2109
2109
  }, g = () => $i[t.maxWidth ?? "xl"], _ = () => {
2110
- if (t.tabs) return t.tabs;
2110
+ if (t.tabs) return t.tabs.map((e) => ({
2111
+ ...e,
2112
+ icon: () => e.icon,
2113
+ content: () => e.content
2114
+ }));
2111
2115
  let e = [];
2112
2116
  return e.push({
2113
2117
  id: "edit",
2114
2118
  label: "Edit",
2115
- icon: Wi(),
2116
- content: t.editContent ?? (() => {
2119
+ icon: () => Wi(),
2120
+ content: () => t.editContent ?? (() => {
2117
2121
  let e = t.SchemaForm, o = n() ? a() : void 0;
2118
2122
  return !o || !e ? Gi() : N(e, {
2119
2123
  schema: o,
@@ -2129,8 +2133,8 @@ function ta(e) {
2129
2133
  }), e.push({
2130
2134
  id: "controls",
2131
2135
  label: "Controls",
2132
- icon: Ki(),
2133
- content: t.controlsContent ?? N(Be, { get children() {
2136
+ icon: () => Ki(),
2137
+ content: () => t.controlsContent ?? N(Be, { get children() {
2134
2138
  return N(He, { get children() {
2135
2139
  return [N(Ue, { children: "No controls" }), N(Ve, { children: "This widget is shown as is." })];
2136
2140
  } });
@@ -2138,8 +2142,8 @@ function ta(e) {
2138
2142
  }), e.push({
2139
2143
  id: "debug",
2140
2144
  label: "Debug",
2141
- icon: qi(),
2142
- content: t.debugContent ?? N(Vi, { get data() {
2145
+ icon: () => qi(),
2146
+ content: () => t.debugContent ?? N(Vi, { get data() {
2143
2147
  return t.debugData;
2144
2148
  } })
2145
2149
  }), e;
@@ -2273,7 +2277,7 @@ function ta(e) {
2273
2277
  get children() {
2274
2278
  return [(() => {
2275
2279
  var t = Zi();
2276
- return F(t, () => e.icon), t;
2280
+ return F(t, () => e.icon()), t;
2277
2281
  })(), I(() => e.label)];
2278
2282
  }
2279
2283
  })
@@ -2311,7 +2315,7 @@ function ta(e) {
2311
2315
  return e.id;
2312
2316
  },
2313
2317
  get children() {
2314
- return e.content;
2318
+ return e.content();
2315
2319
  }
2316
2320
  })
2317
2321
  });
@@ -2774,17 +2778,31 @@ function Qa() {
2774
2778
  //#region src/version.ts
2775
2779
  var $a = {
2776
2780
  name: "@glasshome/widget-sdk",
2777
- version: "1.18.0",
2781
+ version: "1.19.0",
2778
2782
  description: "SDK for building GlassHome dashboard widgets with SolidJS",
2779
2783
  keywords: [
2784
+ "dashboard",
2780
2785
  "glasshome",
2781
- "widget",
2786
+ "home-assistant",
2782
2787
  "sdk",
2788
+ "smart-home",
2783
2789
  "solidjs",
2784
2790
  "vite",
2785
- "home-assistant",
2786
- "dashboard",
2787
- "smart-home"
2791
+ "widget"
2792
+ ],
2793
+ homepage: "https://github.com/glasshome/widget-sdk#readme",
2794
+ bugs: { url: "https://github.com/glasshome/widget-sdk/issues" },
2795
+ license: "MIT",
2796
+ author: "GlassHome Labs",
2797
+ repository: {
2798
+ type: "git",
2799
+ url: "https://github.com/glasshome/widget-sdk.git"
2800
+ },
2801
+ files: [
2802
+ "dist",
2803
+ "preview",
2804
+ "tailwind-sources.css",
2805
+ "guide"
2788
2806
  ],
2789
2807
  type: "module",
2790
2808
  sideEffects: !1,
@@ -2819,11 +2837,7 @@ var $a = {
2819
2837
  "./tailwind-sources": "./tailwind-sources.css",
2820
2838
  "./package.json": "./package.json"
2821
2839
  },
2822
- files: [
2823
- "dist",
2824
- "preview",
2825
- "tailwind-sources.css"
2826
- ],
2840
+ publishConfig: { access: "public" },
2827
2841
  scripts: {
2828
2842
  build: "vite build && tsc && bun scripts/check-host-singleton.ts",
2829
2843
  dev: "vite build --watch",
@@ -2831,12 +2845,8 @@ var $a = {
2831
2845
  "test:dom": "vitest run",
2832
2846
  "test:dom:watch": "vitest",
2833
2847
  "typecheck:test": "tsc --noEmit -p tsconfig.test.json",
2834
- "check:dead": "fallow dead-code --fail-on-issues"
2835
- },
2836
- peerDependencies: {
2837
- "@glasshome/sync-layer": "0.9.1",
2838
- "@glasshome/ui": "1.21.1",
2839
- "solid-js": "^1.9.11"
2848
+ "check:dead": "fallow dead-code --fail-on-issues",
2849
+ lint: "oxlint --type-aware . && oxfmt --check ."
2840
2850
  },
2841
2851
  dependencies: {
2842
2852
  "@glasshome/widget-contract": "0.3.0",
@@ -2854,20 +2864,19 @@ var $a = {
2854
2864
  "@types/bun": "^1.4.0",
2855
2865
  "@types/node": "^25.2.2",
2856
2866
  fallow: "3.17.0",
2867
+ oxfmt: "0.65.0",
2868
+ oxlint: "1.81.0",
2869
+ "oxlint-tsgolint": "7.0.2001",
2857
2870
  typescript: "^7.0.2",
2858
2871
  vite: "^8.1.0",
2859
2872
  "vite-plugin-solid": "^2.11.12",
2860
2873
  vitest: "^4.1.10"
2861
2874
  },
2862
- repository: {
2863
- type: "git",
2864
- url: "https://github.com/glasshome/widget-sdk.git"
2875
+ peerDependencies: {
2876
+ "@glasshome/sync-layer": "0.9.1",
2877
+ "@glasshome/ui": "1.21.1",
2878
+ "solid-js": "^1.9.11"
2865
2879
  },
2866
- homepage: "https://github.com/glasshome/widget-sdk#readme",
2867
- bugs: { url: "https://github.com/glasshome/widget-sdk/issues" },
2868
- author: "GlassHome Labs",
2869
- license: "MIT",
2870
- publishConfig: { access: "public" },
2871
2880
  engines: { node: ">=18" }
2872
2881
  }.version;
2873
2882
  //#endregion
@@ -0,0 +1,74 @@
1
+ # GlassHome widgets
2
+
3
+ Matches the installed `@glasshome/widget-sdk`. Wins over memory and older docs.
4
+
5
+ ## Loop
6
+
7
+ 1. `bun widget add --name <name>`: `src/<name>/` with a working sensor tile (`index.tsx`, `manifest.json`). Start from it.
8
+ 2. Write it. `examples` in `defineWidget` (label, size, config) are what preview renders. Demo entity ids: `node_modules/@glasshome/sync-layer/dist/demo/ids.d.ts`; a wrong id renders the empty state. Missing a kind of entity: use a stand-in and config to show each state.
9
+ 3. `bun widget build`: typecheck, bundle, validate. Fix every error before previewing.
10
+ 4. `bun widget preview <name> --sizes grid`: every example at common tile sizes, light and dark, into `preview/sweep/` with a contact sheet each. Open the images and look. First run: `bun add -d playwright && bunx playwright install chromium`. A crash fails the render with its error.
11
+ - Narrow: `--sizes 150x156,340x242`, `--theme dark`, `--example 0`.
12
+ - Change state: `--config '<json>'`, `--service '<domain.service>|<entity_id>|<json>'`, `--at 2026-06-15T21:00:00`, `--click '<selector>'`.
13
+ - Inspect: `--eval '<expr>'` prints per render (`root` = shadow root).
14
+ 5. Repeat 3-4 until 1×1, 2×2 and a big tile read right in both themes.
15
+ 6. Live: `bun widget connect <dashboard-url>`.
16
+ 7. Ship: `bun widget publish --name <name> --bump patch|minor|major`. Version lives in `manifest.json`.
17
+
18
+ ## Imports
19
+
20
+ - Everything from `@glasshome/widget-sdk`, UI primitives included.
21
+ - Never `@glasshome/ui` (unchecked version, dead tile on mismatch) or `@glasshome/sync-layer` (second store). Keep `@glasshome/ui` installed; the build reads its CSS.
22
+ - Home Assistant only via SDK hooks (`useEntity`, `useEntities`, `useService`, `useToggle`, …). Declare every read/controlled domain in manifest capabilities.
23
+ - SDK too old for something in the docs: `bun widget upgrade`.
24
+ - Edit the inline `manifest` in `defineWidget`; build copies it into `manifest.json` (name, icon, sizes, capabilities, examples, schema). `version` and `sdkVersion` live only in `manifest.json`.
25
+ - Config: `defineConfig` + `field.*`. `field.entity(domain)` stores a one-item array: read `config.entityIds[0]`.
26
+
27
+ ## Tile
28
+
29
+ Parts inside `Widget.Content`; they lay out and resize themselves. Never measure to lay out.
30
+
31
+ | Need | Part |
32
+ |---|---|
33
+ | Colour | `tone` (`accent info success warning danger neutral`) or `color` on `Widget`; `accent` on `Widget.Content` for state colour |
34
+ | Icon, name, eyebrow, chips | `Widget.Head` (`active`, `count`, `aside`) |
35
+ | Big value, unit, art | `Widget.Hero` |
36
+ | Control row | `Widget.Controls` with `Widget.Stepper`, `Widget.Choice`, `Widget.Action` |
37
+ | Stateless action | `Widget.Action`; `useConfirm()` + `<Widget confirmed>` when the whole tile runs it |
38
+ | Small fact | `Widget.Chip` |
39
+ | Photo behind tile | `Widget.Backdrop` |
40
+ | Own full-bleed layer | `Widget.Layer` |
41
+ | Faint corner icon | `Widget.Glyph` |
42
+ | Controllable continuous value | `Widget.SliderFill` |
43
+
44
+ ## Sizes
45
+
46
+ 1×1 to 8×8. Under 130px tall or 140px wide the tile is compact: `eyebrow` and `sub` hide, so carry state in `tone` and the icon. Tokens, never px: `--widget-text-value`, `--widget-text-name`, `--widget-text-meta`, `--widget-text-sub`, `--widget-text-caption`, `--widget-control-h`, `--widget-grid-pad`, `--widget-grid-gap`, `--widget-icon-box`, `--widget-icon-glyph`. Layout by container query (`@[150px]:…`, `@container widget (…)`). What renders by `useWidgetDimensions()`, inside `<Widget>` only.
47
+
48
+ ## Sheet
49
+
50
+ Render `<WidgetDialog>` next to `<Widget>`, spreading the dialog parts (`ResponsiveDialog*`, `Button`, `SchemaForm`, `Tabs*`, see the scaffold) and `useWidgetDialog().dialogProps`. Hold opens the sheet on its own once `sheet` is passed; no gesture wiring. Only what the tile lacks (group members, colours, modes, forecast). Pass `sheet` only when there is something: `{...(extras() ? { sheet: () => <MySheet /> } : {})}`. Build from `PanelSection`, `PanelRows`, `PanelRow`, `PanelEntityRow`, `PanelFacts`, `ToggleGroup`, `SwatchPicker`, `TemperatureBar`, `ColorDisc`. Never repeat the tile's value, name or art. Widget CSS does not reach it.
51
+
52
+ ## Primitives
53
+
54
+ `Badge`, `Button`, `ButtonGroup`, `CountPill`, `Icon`, `Input`, `Label`, `Progress`, `SchemaForm`, `SectionIcon`, `SectionTitle`, `Select`, `Slider`, `Switch`, `Tabs`, `Toggle`, `ToggleGroup`, `Carousel`, `ColorWheel`, `ColorSlider`, `ResponsiveDialog` + parts. `Icon`, never `iconify-icon`. Never restyle with own classes.
55
+
56
+ ## Rules
57
+
58
+ - No hand-rolling what the SDK has: sliders, steppers, dialogs, sheets, hold gestures, loading/error states.
59
+ - Theme vars only (`--foreground`, `--muted-foreground`, `--card`, `--primary`, `--border`, `--success`, `--warning`, `--destructive`, `--radius`). Never declare them on `:host`; no own colour/radius tokens; no `--color-*`.
60
+ - Own CSS = layout and bespoke art. No custom panel/row/chip/button chrome.
61
+ - Tailwind classes literal in source, never concatenated. `dark:` works; `isDark()` for the boolean.
62
+ - No dot status lamps. No thin vertical bars (read as a text caret). No all-caps. Separator ` · `.
63
+ - One icon per item, not per line. Icons, fills and pictures before text.
64
+ - Lead with the answer (value + unit, or a verdict); one small line explains it. Never show a value twice. States per device class (Open/Closed, Detected/Clear).
65
+ - Continuous values keep the full-tile slider; buttons beside it. A stepper never stands alone.
66
+ - Dialog content scrolls in `ResponsiveDialogBody`; no own `overflow`/`max-height` in a dialog.
67
+ - Groups: one tap brings all members to one state; locks never unlock in one tap; doors, gates, garage doors never join bulk actions.
68
+ - Readings update text in place; never rebuild DOM. `Index` for lists of readings. Over 20 s of changing demo data a widget adds and removes no DOM nodes.
69
+ - Everything sits on an edge; text left, values/actions right. One accent per view; defaults quiet. No card in a card.
70
+ - Motion: colours morph, never snap; time transitions with `--duration-state` / `--ease-morph` (zeroed under reduced motion). Decorative loops scale by `var(--motion-ambient, 0)`. Gate on `useReducedMotion()`; pause offscreen with `useIntersectionPause()`.
71
+
72
+ ## More
73
+
74
+ Docs as markdown: https://glasshome.app/llms.txt, `https://glasshome.app/md/widgets/<page>` (`widget-sheets`, `widget-styling`, `widget-sdk`, `widget-api-reference`, `widget-capabilities`, `widget-previews`). Official widgets as worked examples: https://github.com/glasshome/widgets (`src/button`, `src/sensor`); where they differ from this guide, the guide wins.
package/package.json CHANGED
@@ -1,16 +1,32 @@
1
1
  {
2
2
  "name": "@glasshome/widget-sdk",
3
- "version": "1.18.0",
3
+ "version": "1.19.0",
4
4
  "description": "SDK for building GlassHome dashboard widgets with SolidJS",
5
5
  "keywords": [
6
+ "dashboard",
6
7
  "glasshome",
7
- "widget",
8
+ "home-assistant",
8
9
  "sdk",
10
+ "smart-home",
9
11
  "solidjs",
10
12
  "vite",
11
- "home-assistant",
12
- "dashboard",
13
- "smart-home"
13
+ "widget"
14
+ ],
15
+ "homepage": "https://github.com/glasshome/widget-sdk#readme",
16
+ "bugs": {
17
+ "url": "https://github.com/glasshome/widget-sdk/issues"
18
+ },
19
+ "license": "MIT",
20
+ "author": "GlassHome Labs",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "https://github.com/glasshome/widget-sdk.git"
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "preview",
28
+ "tailwind-sources.css",
29
+ "guide"
14
30
  ],
15
31
  "type": "module",
16
32
  "sideEffects": false,
@@ -45,11 +61,9 @@
45
61
  "./tailwind-sources": "./tailwind-sources.css",
46
62
  "./package.json": "./package.json"
47
63
  },
48
- "files": [
49
- "dist",
50
- "preview",
51
- "tailwind-sources.css"
52
- ],
64
+ "publishConfig": {
65
+ "access": "public"
66
+ },
53
67
  "scripts": {
54
68
  "build": "vite build && tsc && bun scripts/check-host-singleton.ts",
55
69
  "dev": "vite build --watch",
@@ -57,12 +71,8 @@
57
71
  "test:dom": "vitest run",
58
72
  "test:dom:watch": "vitest",
59
73
  "typecheck:test": "tsc --noEmit -p tsconfig.test.json",
60
- "check:dead": "fallow dead-code --fail-on-issues"
61
- },
62
- "peerDependencies": {
63
- "@glasshome/sync-layer": "0.9.1",
64
- "@glasshome/ui": "1.21.1",
65
- "solid-js": "^1.9.11"
74
+ "check:dead": "fallow dead-code --fail-on-issues",
75
+ "lint": "oxlint --type-aware . && oxfmt --check ."
66
76
  },
67
77
  "dependencies": {
68
78
  "@glasshome/widget-contract": "0.3.0",
@@ -80,23 +90,18 @@
80
90
  "@types/bun": "^1.4.0",
81
91
  "@types/node": "^25.2.2",
82
92
  "fallow": "3.17.0",
93
+ "oxfmt": "0.65.0",
94
+ "oxlint": "1.81.0",
95
+ "oxlint-tsgolint": "7.0.2001",
83
96
  "typescript": "^7.0.2",
84
97
  "vite": "^8.1.0",
85
98
  "vite-plugin-solid": "^2.11.12",
86
99
  "vitest": "^4.1.10"
87
100
  },
88
- "repository": {
89
- "type": "git",
90
- "url": "https://github.com/glasshome/widget-sdk.git"
91
- },
92
- "homepage": "https://github.com/glasshome/widget-sdk#readme",
93
- "bugs": {
94
- "url": "https://github.com/glasshome/widget-sdk/issues"
95
- },
96
- "author": "GlassHome Labs",
97
- "license": "MIT",
98
- "publishConfig": {
99
- "access": "public"
101
+ "peerDependencies": {
102
+ "@glasshome/sync-layer": "0.9.1",
103
+ "@glasshome/ui": "1.21.1",
104
+ "solid-js": "^1.9.11"
100
105
  },
101
106
  "engines": {
102
107
  "node": ">=18"
@@ -1,4 +1,4 @@
1
- <!DOCTYPE html>
1
+ <!doctype html>
2
2
  <html lang="en">
3
3
  <head>
4
4
  <meta charset="UTF-8" />
@@ -34,11 +34,16 @@
34
34
  }
35
35
 
36
36
  body {
37
- font-family: system-ui, -apple-system, sans-serif;
37
+ font-family:
38
+ system-ui,
39
+ -apple-system,
40
+ sans-serif;
38
41
  background: var(--color-background);
39
42
  color: var(--color-foreground);
40
43
  min-height: 100vh;
41
- transition: background 0.2s, color 0.2s;
44
+ transition:
45
+ background 0.2s,
46
+ color 0.2s;
42
47
  }
43
48
  </style>
44
49
  </head>