This commit is contained in:
Home Assistant Version Control committed 2026-07-29 04:12:24 +00:00
1 parent 9ea95c3a46
commit 2bc77c43ce
41 files changed
+22649

No files matched your search

@@ -0,0 +1,628 @@
!function(){"use strict";function t(t,e,i,r){var n,s=arguments.length,o=s<3?e:null===r?r=Object.getOwnPropertyDescriptor(e,i):r;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(t,e,i,r);else for(var a=t.length-1;a>=0;a--)(n=t[a])&&(o=(s<3?n(o):s>3?n(e,i,o):n(e,i))||o);return s>3&&o&&Object.defineProperty(e,i,o),o}"function"==typeof SuppressedError&&SuppressedError;const e=globalThis,i=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,r=Symbol(),n=new WeakMap;let s=class{constructor(t,e,i){if(this._$cssResult$=!0,i!==r)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=e}get styleSheet(){let t=this.o;const e=this.t;if(i&&void 0===t){const i=void 0!==e&&1===e.length;i&&(t=n.get(e)),void 0===t&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),i&&n.set(e,t))}return t}toString(){return this.cssText}};const o=t=>new s("string"==typeof t?t:t+"",void 0,r),a=(t,...e)=>{const i=1===t.length?t[0]:e.reduce((e,i,r)=>e+(t=>{if(!0===t._$cssResult$)return t.cssText;if("number"==typeof t)return t;throw Error("Value passed to 'css' function must be a 'css' function result: "+t+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(i)+t[r+1],t[0]);return new s(i,t,r)},l=i?t=>t:t=>t instanceof CSSStyleSheet?(t=>{let e="";for(const i of t.cssRules)e+=i.cssText;return o(e)})(t):t,{is:c,defineProperty:d,getOwnPropertyDescriptor:h,getOwnPropertyNames:p,getOwnPropertySymbols:u,getPrototypeOf:m}=Object,f=globalThis,g=f.trustedTypes,_=g?g.emptyScript:"",b=f.reactiveElementPolyfillSupport,v=(t,e)=>t,$={toAttribute(t,e){switch(e){case Boolean:t=t?_:null;break;case Object:case Array:t=null==t?t:JSON.stringify(t)}return t},fromAttribute(t,e){let i=t;switch(e){case Boolean:i=null!==t;break;case Number:i=null===t?null:Number(t);break;case Object:case Array:try{i=JSON.parse(t)}catch(t){i=null}}return i}},x=(t,e)=>!c(t,e),y={attribute:!0,type:String,converter:$,reflect:!1,useDefault:!1,hasChanged:x};Symbol.metadata??=Symbol("metadata"),f.litPropertyMetadata??=new WeakMap;let w=class extends HTMLElement{static addInitializer(t){this._$Ei(),(this.l??=[]).push(t)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(t,e=y){if(e.state&&(e.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(t)&&((e=Object.create(e)).wrapped=!0),this.elementProperties.set(t,e),!e.noAccessor){const i=Symbol(),r=this.getPropertyDescriptor(t,i,e);void 0!==r&&d(this.prototype,t,r)}}static getPropertyDescriptor(t,e,i){const{get:r,set:n}=h(this.prototype,t)??{get(){return this[e]},set(t){this[e]=t}};return{get:r,set(e){const s=r?.call(this);n?.call(this,e),this.requestUpdate(t,s,i)},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)??y}static _$Ei(){if(this.hasOwnProperty(v("elementProperties")))return;const t=m(this);t.finalize(),void 0!==t.l&&(this.l=[...t.l]),this.elementProperties=new Map(t.elementProperties)}static finalize(){if(this.hasOwnProperty(v("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(v("properties"))){const t=this.properties,e=[...p(t),...u(t)];for(const i of e)this.createProperty(i,t[i])}const t=this[Symbol.metadata];if(null!==t){const e=litPropertyMetadata.get(t);if(void 0!==e)for(const[t,i]of e)this.elementProperties.set(t,i)}this._$Eh=new Map;for(const[t,e]of this.elementProperties){const i=this._$Eu(t,e);void 0!==i&&this._$Eh.set(i,t)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(t){const e=[];if(Array.isArray(t)){const i=new Set(t.flat(1/0).reverse());for(const t of i)e.unshift(l(t))}else void 0!==t&&e.push(l(t));return e}static _$Eu(t,e){const i=e.attribute;return!1===i?void 0:"string"==typeof i?i:"string"==typeof t?t.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise(t=>this.enableUpdating=t),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(t=>t(this))}addController(t){(this._$EO??=new Set).add(t),void 0!==this.renderRoot&&this.isConnected&&t.hostConnected?.()}removeController(t){this._$EO?.delete(t)}_$E_(){const t=new Map,e=this.constructor.elementProperties;for(const i of e.keys())this.hasOwnProperty(i)&&(t.set(i,this[i]),delete this[i]);t.size>0&&(this._$Ep=t)}createRenderRoot(){const t=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return((t,r)=>{if(i)t.adoptedStyleSheets=r.map(t=>t instanceof CSSStyleSheet?t:t.styleSheet);else for(const i of r){const r=document.createElement("style"),n=e.litNonce;void 0!==n&&r.setAttribute("nonce",n),r.textContent=i.cssText,t.appendChild(r)}})(t,this.constructor.elementStyles),t}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EOLine truncated
<ha-card>
<div class="error">Entity not found: ${this._config.entity}</div>
</ha-card>
`;const e=this._config.min??0,i=this._config.max??5e3,r=Math.max(1,i-e),n=this._initialized&&null!==this._readEntityValue(),s=n?this._live:e,o=n?Ct((s-e)/r,0,1):0,a=Pt(o,this._stops),l=this._config.unit??t.attributes.unit_of_measurement??"W",c=this._config.name??t.attributes.friendly_name??this._config.entity,d=this._config.precision??0,h=n?this._format(s,d):"—",p=n?this._secondaryLine(s,l,a):"NO DATA",u={"--c1":a.c1,"--c2":a.c2,"--c3":a.c3,...Lt(this._config.background,zt)},m=qt+280*o,[f,g]=Rt(It,Wt,Bt,m);return q`
<ha-card style=${xt(u)}>
<div class="panel">
<div class="header">
<div>
<div class="title">${c}</div>
<div class="subtitle">
${n?q`Live · ${(i/1e3).toFixed(1)} ${"W"===l?"kW":l} max`:"Awaiting a valid source value"}
</div>
</div>
</div>
<div class="stage">
<div class="gauge ${n?"":"no-data"}">
${this._renderSvg(a,m)}
<div class="center">
<div class="core">
<div class="reading">
<div class="label-now">${n?"Live draw":"Source unavailable"}</div>
<div class="value">
${h}${n?q`<span class="unit-inline">${l}</span>`:J}
</div>
<div class="unit">${p}</div>
</div>
</div>
</div>
<div
class="knob"
style=${xt({left:f/400*100+"%",top:g/400*100+"%"})}
></div>
</div>
</div>
<div class="status">
<span
><span class="pulse-dot ${n?"":"inactive"}"></span
>${n?"Streaming":"No data"}</span
>
<span>Now <b>${n?`${h} ${l}`:"—"}</b></span>
<span>Limit <b>${n?this._formatLimit(i,l):"—"}</b></span>
</div>
</div>
</ha-card>
`}_format(t,e){return e<=0?Math.round(t).toLocaleString():t.toLocaleString(void 0,{minimumFractionDigits:e,maximumFractionDigits:e})}_formatLimit(t,e){return"W"===e&&t>=1e3?`${(t/1e3).toFixed(1)} kW`:`${t.toLocaleString()} ${e}`}_secondaryLine(t,e,i){return"W"===e?`${(t/1e3).toFixed(2)} kW · ${i.mood}`:i.mood}_renderSvg(t,e){const i=[],r=(e-qt)/280;for(let e=0;e<=80;e++){const n=e/80,s=qt+280*n,o=e%8==0,a=128,l=Bt-(o?8:14),[c,d]=Rt(It,Wt,a,s),[h,p]=Rt(It,Wt,l,s),u=n<=r;i.push(G`
<line
x1=${c} y1=${d} x2=${h} y2=${p}
stroke=${u?t.c1:"rgba(255,255,255,0.08)"}
stroke-width=${o?1.6:1}
stroke-linecap="round"
opacity=${u?1:.55}
style=${u?`filter: drop-shadow(0 0 4px ${t.c1})`:""}
/>
`)}return q`
<svg viewBox="0 0 400 400" preserveAspectRatio="xMidYMid meet">
<defs>
<linearGradient id="pg-stroke" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color=${t.c1} />
<stop offset="60%" stop-color=${t.c2} />
<stop offset="100%" stop-color=${t.c3} />
</linearGradient>
<radialGradient id="pg-halo" cx="50%" cy="50%" r="50%">
<stop offset="60%" stop-color="transparent" />
<stop offset="80%" stop-color=${t.c2} stop-opacity="0.5" />
<stop offset="100%" stop-color="transparent" />
</radialGradient>
<radialGradient id="pg-aura" cx="50%" cy="50%" r="50%">
<stop offset="55%" stop-color="transparent" />
<stop offset="72%" stop-color=${t.c1} stop-opacity="0.18" />
<stop offset="86%" stop-color=${t.c2} stop-opacity="0.32" />
<stop offset="100%" stop-color="transparent" />
</radialGradient>
<filter id="pg-blur-lg" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="6" />
</filter>
<filter id="pg-blur-xl" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="14" />
</filter>
</defs>
<g class="spin-slow" style="transform-origin: 200px 200px;">
<circle cx=${It} cy=${Wt} r=${174} fill="url(#pg-aura)" opacity="0.9" />
</g>
<path
d=${Ut(It,Wt,Bt,qt,410)}
stroke="rgba(255,255,255,0.06)"
stroke-width="3"
fill="none"
stroke-linecap="round"
/>
<g>${i}</g>
<path
d=${Ut(It,Wt,Bt,qt,e)}
stroke=${t.c1}
stroke-width="26"
fill="none"
stroke-linecap="round"
opacity="0.35"
filter="url(#pg-blur-xl)"
/>
<path
d=${Ut(It,Wt,Bt,qt,e)}
stroke="url(#pg-stroke)"
stroke-width="14"
fill="none"
stroke-linecap="round"
opacity="0.6"
filter="url(#pg-blur-lg)"
/>
<path
d=${Ut(It,Wt,Bt,qt,e)}
stroke="url(#pg-stroke)"
stroke-width="3"
fill="none"
stroke-linecap="round"
/>
<g class="spin-fast" style="transform-origin: 200px 200px;">
<circle
cx=${It} cy=${Wt} r=${120}
fill="none"
stroke=${t.c2}
stroke-opacity="0.18"
stroke-width="1"
stroke-dasharray="2 8"
/>
</g>
<circle
cx=${It} cy=${Wt} r=${114}
fill="url(#pg-halo)"
opacity="0.7"
class="shimmer"
/>
</svg>
`}};Gt.styles=a`
:host {
--pg-bg: ${o(zt)};
--pg-text: #eef3ff;
--pg-muted: #6b7894;
--c1: #1ee0ff;
--c2: #2a7bff;
--c3: #0a3aa0;
display: block;
}
ha-card {
background: var(--pg-bg);
color: var(--pg-text);
border: 1px solid color-mix(in oklab, currentColor 12%, transparent);
border-radius: var(--ha-card-border-radius, 16px);
box-shadow:
0 20px 40px rgba(0, 0, 0, 0.45),
0 0 0 1px color-mix(in oklab, currentColor 2%, transparent) inset;
overflow: hidden;
font-family: 'Inter', var(--primary-font-family, system-ui, sans-serif);
}
.panel {
position: relative;
padding: 22px 20px 18px;
display: flex;
flex-direction: column;
}
.panel::before {
content: '';
position: absolute;
inset: 0;
pointer-events: none;
background:
radial-gradient(1px 1px at 20% 30%, color-mix(in oklab, currentColor 60%, transparent), transparent),
radial-gradient(1px 1px at 70% 60%, color-mix(in oklab, currentColor 40%, transparent), transparent),
radial-gradient(1px 1px at 40% 80%, color-mix(in oklab, currentColor 50%, transparent), transparent),
radial-gradient(1px 1px at 85% 20%, color-mix(in oklab, currentColor 30%, transparent), transparent),
radial-gradient(1px 1px at 15% 70%, color-mix(in oklab, currentColor 40%, transparent), transparent);
opacity: 0.3;
}
.header {
display: flex;
justify-content: space-between;
align-items: flex-start;
position: relative;
z-index: 1;
}
.title {
font-size: 20px;
font-weight: 600;
line-height: 1.1;
letter-spacing: -0.4px;
}
.subtitle {
font-size: 11px;
color: var(--pg-muted);
margin-top: 4px;
letter-spacing: 0.4px;
}
.stage {
flex: 1;
display: grid;
place-items: center;
position: relative;
margin: 8px 0;
z-index: 1;
}
.gauge {
position: relative;
width: 100%;
max-width: 320px;
aspect-ratio: 1 / 1;
}
.gauge svg {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
.center {
position: absolute;
inset: 0;
display: grid;
place-items: center;
pointer-events: none;
}
.core {
width: 56%;
height: 56%;
border-radius: 50%;
background:
radial-gradient(circle at 50% 40%, rgba(255, 255, 255, 0.06), transparent 60%),
radial-gradient(
circle at 50% 100%,
color-mix(in oklab, var(--c2) 35%, transparent),
transparent 70%
),
#050912;
border: 1px solid rgba(255, 255, 255, 0.06);
box-shadow:
inset 0 0 40px color-mix(in oklab, var(--c2) 30%, transparent),
inset 0 0 0 1px rgba(255, 255, 255, 0.04),
0 0 60px color-mix(in oklab, var(--c1) 20%, transparent);
display: grid;
place-items: center;
transition: box-shadow 0.6s;
}
.reading {
text-align: center;
padding: 0 8px;
}
.label-now {
font-size: 9px;
letter-spacing: 2.5px;
color: var(--pg-muted);
text-transform: uppercase;
font-weight: 600;
margin-bottom: 6px;
}
.value {
font-size: 36px;
font-weight: 300;
letter-spacing: -1.2px;
font-variant-numeric: tabular-nums;
/* Always white inside the dark gauge core regardless of card theme. */
color: #fff;
text-shadow: 0 0 20px color-mix(in oklab, var(--c1) 60%, transparent);
line-height: 1;
}
.unit-inline {
font-size: 16px;
color: color-mix(in oklab, #fff 55%, transparent);
margin-left: 4px;
}
.unit {
font-size: 11px;
color: color-mix(in oklab, #fff 55%, transparent);
margin-top: 6px;
letter-spacing: 1.5px;
font-weight: 500;
}
.knob {
position: absolute;
width: 22px;
height: 22px;
border-radius: 50%;
background: #0a1020;
border: 1.5px solid rgba(255, 255, 255, 0.6);
transform: translate(-50%, -50%);
pointer-events: none;
box-shadow:
0 0 0 4px rgba(0, 0, 0, 0.4),
0 0 20px color-mix(in oklab, var(--c1) 80%, transparent);
display: grid;
place-items: center;
}
.knob::after {
content: '';
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--c1);
box-shadow: 0 0 8px var(--c1);
}
.status {
display: flex;
justify-content: space-between;
gap: 8px;
padding: 6px 4px 0;
font-size: 11px;
color: var(--pg-muted);
font-variant-numeric: tabular-nums;
position: relative;
z-index: 1;
}
.status b {
color: var(--pg-text);
font-weight: 600;
font-family: 'SF Mono', 'JetBrains Mono', 'Menlo', 'Consolas', ui-monospace, monospace;
letter-spacing: 0;
}
.pulse-dot {
display: inline-block;
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--c1);
margin-right: 6px;
animation: pg-pulse 1.4s ease-in-out infinite;
box-shadow: 0 0 8px var(--c1);
}
.pulse-dot.inactive {
background: var(--pg-muted);
animation: none;
box-shadow: none;
}
.gauge.no-data .spin-slow,
.gauge.no-data .spin-fast,
.gauge.no-data .shimmer {
animation: none;
}
@keyframes pg-pulse {
0%,
100% {
opacity: 1;
transform: scale(1);
}
50% {
opacity: 0.4;
transform: scale(0.7);
}
}
@keyframes pg-spin {
to {
transform: rotate(360deg);
}
}
.spin-slow {
animation: pg-spin 24s linear infinite;
transform-origin: 50% 50%;
}
.spin-fast {
animation: pg-spin 9s linear infinite reverse;
transform-origin: 50% 50%;
}
@keyframes pg-shimmer {
0%,
100% {
opacity: 0.85;
}
50% {
opacity: 1;
}
}
.shimmer {
animation: pg-shimmer 2.4s ease-in-out infinite;
}
.error {
padding: 16px;
color: var(--error-color, #db4437);
}
@media (prefers-reduced-motion: reduce) {
.spin-slow,
.spin-fast,
.shimmer,
.pulse-dot {
animation: none;
}
}
`,t([ft({attribute:!1})],Gt.prototype,"hass",void 0),t([gt()],Gt.prototype,"_config",void 0),t([gt()],Gt.prototype,"_animated",void 0),t([gt()],Gt.prototype,"_live",void 0),t([gt()],Gt.prototype,"_stops",void 0),Gt=t([pt(yt)],Gt),(window.customCards=window.customCards||[]).push({type:At,name:"Power Gauge Bar Card",description:"A compact linear power gauge — stack one row per device, sharing color levels.",preview:!0,documentationURL:"https://github.com/Lirum-Labs/ha-power-gauge"});let Vt=class extends dt{constructor(){super(...arguments),this._live={},this._animated={},this._targets={},this._ramps={},this._initialized=new Set,this._ambientStart=0,this._loop=()=>{const t=performance.now(),e=this._config?.rolling_numbers??!0,i=(t-this._ambientStart)/1e3,r=e?.012*Math.sin(i*Math.PI*2/2.6):0,n={};for(const i of Object.keys(this._animated)){const s=this._ramps[i];if(s){const e=Ct((t-s.start)/500,0,1);this._animated[i]=St(s.from,s.to,Ot(e)),e>=1&&delete this._ramps[i]}const o=this._animated[i];n[i]=e?o+r*o:o}this._live=n,this._raf=requestAnimationFrame(this._loop)}}static async getConfigElement(){return await Promise.resolve().then(function(){return re}),document.createElement(kt)}static getStubConfig(){return{type:`custom:${At}`,entities:[],max:5e3}}setConfig(t){if(!t)throw new Error("Invalid configuration");if(!Array.isArray(t.entities)||0===t.entities.length)throw new Error("You need to define at least one entity");this._config={min:0,max:5e3,precision:0,rolling_numbers:!0,...t}}getCardSize(){return Math.max(1,this._config?.entities.length??1)}connectedCallback(){super.connectedCallback(),this._ambientStart=performance.now(),this._loop()}disconnectedCallback(){super.disconnectedCallback(),this._raf&&cancelAnimationFrame(this._raf)}willUpdate(t){if(this.hass&&this._config)for(const t of this._config.entities){const e="string"==typeof t?t:t.entity;if(!e)continue;const i=this._readValue(e);null!==i?this._initialized.has(e)?this._targets[e]!==i&&(this._targets[e]=i,this._ramps[e]={from:this._animated[e]??i,to:i,start:performance.now()}):(this._initialized.add(e),this._targets[e]=i,this._animated[e]=i,this._live[e]=i):(this._initialized.delete(e),delete this._targets[e],delete this._animated[e],delete this._live[e],delete this._ramps[e])}}_readValue(t){if(!this.hass)return null;const e=this.hass.states[t];if(!e)return null;const i=e.state.trim();if(""===i||"unavailable"===i||"unknown"===i)return null;const r=Number(i);return Number.isFinite(r)?r:null}_resolveRow(t){if(!this.hass||!this._config)return null;const e="object"==typeof t&&null!==t,i=e?t.entity:t;if(!i)return null;const r=this.hass.states[i],n={...this._config,...e?t:{}},s=n.min??0,o=n.max??5e3,a=n.unit??r?.attributes.unit_of_measurement??"W",l=n.precision??0;return{entity:i,name:e&&t.name||r?.attributes.friendly_name||i,unit:a,precision:l,min:s,max:o,stops:Ft(n)}}render(){if(!this._config||!this.hass)return J;const t=this._config.entities.map(t=>this._resolveRow(t)).filter(t=>null!==t),e=Lt(this._config.background,Nt);return q`
<ha-card style=${xt(e)}>
<div class="wrap">
${this._config.title?q`<div class="card-title">${this._config.title}</div>`:J}
<div class="stack">${t.map(t=>this._renderRow(t))}</div>
</div>
</ha-card>
`}_renderRow(t){const e=this._initialized.has(t.entity)&&null!==this._readValue(t.entity),i=this._live[t.entity]??this._animated[t.entity]??0,r=Math.max(1,t.max-t.min),n=e?Ct((i-t.min)/r,0,1):0,s=Pt(n,t.stops),o=e?this._format(i,t.precision):"—";return q`
<div
class="row ${e?"":"no-data"}"
style=${xt({"--c1":s.c1,"--c2":s.c2,"--w":100*n+"%"})}
>
<div class="label">${t.name}</div>
<div class="value">
${o}${e?q`<span class="unit">${t.unit}</span>`:J}
</div>
<div class="track" aria-hidden="true">
<div class="fill"></div>
<div class="cap"></div>
</div>
</div>
`}_format(t,e){return e<=0?Math.round(t).toLocaleString():t.toLocaleString(void 0,{minimumFractionDigits:e,maximumFractionDigits:e})}};Vt.styles=a`
:host {
--pg-bg: ${o(Nt)};
--pg-text: #eef3ff;
--pg-muted: #6b7894;
display: block;
}
ha-card {
background: var(--pg-bg);
color: var(--pg-text);
border: 1px solid color-mix(in oklab, currentColor 12%, transparent);
border-radius: var(--ha-card-border-radius, 14px);
box-shadow:
0 12px 28px rgba(0, 0, 0, 0.35),
0 0 0 1px color-mix(in oklab, currentColor 2%, transparent) inset;
overflow: hidden;
font-family: 'Inter', var(--primary-font-family, system-ui, sans-serif);
}
.wrap {
padding: 14px 14px 16px;
display: flex;
flex-direction: column;
gap: 12px;
}
.card-title {
font-size: 13px;
font-weight: 600;
letter-spacing: 0.4px;
text-transform: uppercase;
color: color-mix(in oklab, currentColor 70%, transparent);
padding: 2px 2px 0;
}
.stack {
display: flex;
flex-direction: column;
gap: 10px;
}
.row {
--c1: #1ee0ff;
--c2: #2a7bff;
position: relative;
padding: 10px 14px 12px;
border-radius: 12px;
background: linear-gradient(
180deg,
color-mix(in oklab, currentColor 3%, transparent),
color-mix(in oklab, currentColor 0.5%, transparent)
);
border: 1px solid color-mix(in oklab, currentColor 10%, transparent);
box-shadow: inset 0 1px 0 color-mix(in oklab, currentColor 3%, transparent);
display: grid;
grid-template-columns: 1fr auto;
grid-template-rows: auto auto;
column-gap: 14px;
row-gap: 8px;
align-items: center;
}
.label {
grid-column: 1;
grid-row: 1;
font-size: 12px;
color: color-mix(in oklab, currentColor 78%, transparent);
letter-spacing: 0.2px;
font-weight: 500;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.value {
grid-column: 2;
grid-row: 1;
font-family: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
font-size: 14px;
font-weight: 500;
color: var(--pg-text);
font-variant-numeric: tabular-nums;
letter-spacing: -0.2px;
}
.value .unit {
font-size: 11px;
color: color-mix(in oklab, currentColor 55%, transparent);
margin-left: 4px;
font-weight: 400;
}
.track {
grid-column: 1 / -1;
grid-row: 2;
position: relative;
height: 6px;
border-radius: 999px;
background: color-mix(in oklab, currentColor 8%, transparent);
overflow: hidden;
}
.fill {
position: absolute;
inset: 0;
width: var(--w);
border-radius: 999px;
background: linear-gradient(90deg, var(--c1), var(--c2));
box-shadow:
0 0 8px color-mix(in oklab, var(--c1) 50%, transparent),
0 0 14px color-mix(in oklab, var(--c2) 30%, transparent);
transition: background 0.4s;
}
.cap {
position: absolute;
top: 50%;
left: var(--w);
width: 10px;
height: 10px;
border-radius: 50%;
background: var(--c1);
transform: translate(-50%, -50%);
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.5), 0 0 8px var(--c1);
}
.row.no-data .fill,
.row.no-data .cap {
opacity: 0;
}
@media (prefers-reduced-motion: reduce) {
.fill {
transition: none;
}
}
`,t([ft({attribute:!1})],Vt.prototype,"hass",void 0),t([gt()],Vt.prototype,"_config",void 0),t([gt()],Vt.prototype,"_live",void 0),Vt=t([pt(At)],Vt),console.info("%c POWER-GAUGE %c v0.2.3 ","color:#fff; background:#1ee0ff; font-weight:600; padding:2px 6px; border-radius:3px 0 0 3px;","color:#1ee0ff; background:#0a0f1c; font-weight:600; padding:2px 6px; border-radius:0 3px 3px 0;");const Jt={number:{mode:"box",step:1}},Yt=[{name:"entity",required:!0,selector:{entity:{domain:["sensor","input_number"]}}},{name:"",type:"grid",schema:[{name:"name",selector:{text:{}}},{name:"unit",selector:{text:{}}}]},{name:"",type:"grid",schema:[{name:"min",selector:Jt},{name:"max",selector:Jt},{name:"precision",selector:{number:{mode:"box",min:0,max:4,step:1}}}]},{name:"rolling_numbers",selector:{boolean:{}}},{name:"background",selector:{text:{}}},{name:"",type:"expandable",title:"Color levels",schema:[{name:"",type:"grid",schema:[{name:"normal",selector:Jt},{name:"normal_color",selector:{text:{}}}]},{name:"",type:"grid",schema:[{name:"warning",selector:Jt},{name:"warning_color",selector:{text:{}}}]},{name:"",type:"grid",schema:[{name:"critical",selector:Jt},{name:"critical_color",selector:{text:{}}}]}]}],Zt={entity:"Entity (required)",name:"Name (optional)",unit:"Unit override (optional)",min:"Minimum value",max:"Maximum value",precision:"Decimal places",rolling_numbers:"Rolling numbers (rapid fluctuation around the live value)",background:'Background (CSS color/gradient, "transparent" for HA theme)',normal:"Normal threshold",normal_color:`Normal color (default ${Tt})`,warning:"Warning threshold",warning_color:`Warning color (default ${Ht})`,critical:"Critical threshold",critical_color:`Critical color (default ${jt})`};let Kt=class extends dt{constructor(){super(...arguments),this._computeLabel=t=>Zt[t.name]??t.name}setConfig(t){this._config=t}render(){return this.hass&&this._config?q`
<ha-form
.hass=${this.hass}
.data=${this._config}
.schema=${Yt}
.computeLabel=${this._computeLabel}
@value-changed=${this._valueChanged}
></ha-form>
`:J}_valueChanged(t){const e=t.detail;this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:e.value},bubbles:!0,composed:!0}))}};Kt.styles=a`
:host {
display: block;
}
ha-form {
display: block;
}
`,t([ft({attribute:!1})],Kt.prototype,"hass",void 0),t([gt()],Kt.prototype,"_config",void 0),Kt=t([pt(wt)],Kt);var Qt=Object.freeze({__proto__:null,get PowerGaugeCardEditor(){return Kt}});const Xt={number:{mode:"box",step:1}},te=[{name:"title",selector:{text:{}}},{name:"entities",required:!0,selector:{entity:{multiple:!0,domain:["sensor","input_number"]}}},{name:"",type:"grid",schema:[{name:"min",selector:Xt},{name:"max",selector:Xt},{name:"precision",selector:{number:{mode:"box",min:0,max:4,step:1}}},{name:"unit",selector:{text:{}}}]},{name:"rolling_numbers",selector:{boolean:{}}},{name:"background",selector:{text:{}}},{name:"",type:"expandable",title:"Color levels",schema:[{name:"",type:"grid",schema:[{name:"normal",selector:Xt},{name:"normal_color",selector:{text:{}}}]},{name:"",type:"grid",schema:[{name:"warning",selector:Xt},{name:"warning_color",selector:{text:{}}}]},{name:"",type:"grid",schema:[{name:"critical",selector:Xt},{name:"critical_color",selector:{text:{}}}]}]}],ee={title:"Card title (optional)",entities:"Entities (one bar per entity)",min:"Minimum value",max:"Maximum value",precision:"Decimal places",unit:"Unit override (optional)",rolling_numbers:"Rolling numbers (rapid fluctuation around the live value)",background:'Background (CSS color/gradient, "transparent" for HA theme)',normal:"Normal threshold",normal_color:`Normal color (default ${Tt})`,warning:"Warning threshold",warning_color:`Warning color (default ${Ht})`,critical:"Critical threshold",critical_color:`Critical color (default ${jt})`};let ie=class extends dt{constructor(){super(...arguments),this._computeLabel=t=>ee[t.name]??t.name}setConfig(t){this._config=t}render(){return this.hass&&this._config?q`
<ha-form
.hass=${this.hass}
.data=${this._config}
.schema=${te}
.computeLabel=${this._computeLabel}
@value-changed=${this._valueChanged}
></ha-form>
<p class="hint">
For per-row overrides (custom name, max, or color per device), edit the
card YAML — each entry under <code>entities</code> can be
<code>{ entity, name, max, normal, warning, critical, ... }</code>.
</p>
`:J}_valueChanged(t){const e=t.detail;this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:e.value},bubbles:!0,composed:!0}))}};ie.styles=a`
:host {
display: block;
}
ha-form {
display: block;
}
.hint {
margin: 12px 4px 0;
font-size: 12px;
color: var(--secondary-text-color, #6b7894);
}
code {
font-family: ui-monospace, 'SF Mono', Menlo, monospace;
font-size: 11px;
background: rgba(127, 127, 127, 0.12);
padding: 1px 5px;
border-radius: 4px;
}
`,t([ft({attribute:!1})],ie.prototype,"hass",void 0),t([gt()],ie.prototype,"_config",void 0),ie=t([pt(kt)],ie);var re=Object.freeze({__proto__:null,get PowerGaugeBarCardEditor(){return ie}})}();
@@ -0,0 +1,628 @@
function t(t,e,i,r){var n,s=arguments.length,o=s<3?e:null===r?r=Object.getOwnPropertyDescriptor(e,i):r;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(t,e,i,r);else for(var a=t.length-1;a>=0;a--)(n=t[a])&&(o=(s<3?n(o):s>3?n(e,i,o):n(e,i))||o);return s>3&&o&&Object.defineProperty(e,i,o),o}"function"==typeof SuppressedError&&SuppressedError;const e=globalThis,i=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,r=Symbol(),n=new WeakMap;let s=class{constructor(t,e,i){if(this._$cssResult$=!0,i!==r)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=e}get styleSheet(){let t=this.o;const e=this.t;if(i&&void 0===t){const i=void 0!==e&&1===e.length;i&&(t=n.get(e)),void 0===t&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),i&&n.set(e,t))}return t}toString(){return this.cssText}};const o=t=>new s("string"==typeof t?t:t+"",void 0,r),a=(t,...e)=>{const i=1===t.length?t[0]:e.reduce((e,i,r)=>e+(t=>{if(!0===t._$cssResult$)return t.cssText;if("number"==typeof t)return t;throw Error("Value passed to 'css' function must be a 'css' function result: "+t+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(i)+t[r+1],t[0]);return new s(i,t,r)},l=i?t=>t:t=>t instanceof CSSStyleSheet?(t=>{let e="";for(const i of t.cssRules)e+=i.cssText;return o(e)})(t):t,{is:c,defineProperty:d,getOwnPropertyDescriptor:h,getOwnPropertyNames:p,getOwnPropertySymbols:u,getPrototypeOf:m}=Object,f=globalThis,g=f.trustedTypes,_=g?g.emptyScript:"",b=f.reactiveElementPolyfillSupport,v=(t,e)=>t,$={toAttribute(t,e){switch(e){case Boolean:t=t?_:null;break;case Object:case Array:t=null==t?t:JSON.stringify(t)}return t},fromAttribute(t,e){let i=t;switch(e){case Boolean:i=null!==t;break;case Number:i=null===t?null:Number(t);break;case Object:case Array:try{i=JSON.parse(t)}catch(t){i=null}}return i}},x=(t,e)=>!c(t,e),y={attribute:!0,type:String,converter:$,reflect:!1,useDefault:!1,hasChanged:x};Symbol.metadata??=Symbol("metadata"),f.litPropertyMetadata??=new WeakMap;let w=class extends HTMLElement{static addInitializer(t){this._$Ei(),(this.l??=[]).push(t)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(t,e=y){if(e.state&&(e.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(t)&&((e=Object.create(e)).wrapped=!0),this.elementProperties.set(t,e),!e.noAccessor){const i=Symbol(),r=this.getPropertyDescriptor(t,i,e);void 0!==r&&d(this.prototype,t,r)}}static getPropertyDescriptor(t,e,i){const{get:r,set:n}=h(this.prototype,t)??{get(){return this[e]},set(t){this[e]=t}};return{get:r,set(e){const s=r?.call(this);n?.call(this,e),this.requestUpdate(t,s,i)},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)??y}static _$Ei(){if(this.hasOwnProperty(v("elementProperties")))return;const t=m(this);t.finalize(),void 0!==t.l&&(this.l=[...t.l]),this.elementProperties=new Map(t.elementProperties)}static finalize(){if(this.hasOwnProperty(v("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(v("properties"))){const t=this.properties,e=[...p(t),...u(t)];for(const i of e)this.createProperty(i,t[i])}const t=this[Symbol.metadata];if(null!==t){const e=litPropertyMetadata.get(t);if(void 0!==e)for(const[t,i]of e)this.elementProperties.set(t,i)}this._$Eh=new Map;for(const[t,e]of this.elementProperties){const i=this._$Eu(t,e);void 0!==i&&this._$Eh.set(i,t)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(t){const e=[];if(Array.isArray(t)){const i=new Set(t.flat(1/0).reverse());for(const t of i)e.unshift(l(t))}else void 0!==t&&e.push(l(t));return e}static _$Eu(t,e){const i=e.attribute;return!1===i?void 0:"string"==typeof i?i:"string"==typeof t?t.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise(t=>this.enableUpdating=t),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(t=>t(this))}addController(t){(this._$EO??=new Set).add(t),void 0!==this.renderRoot&&this.isConnected&&t.hostConnected?.()}removeController(t){this._$EO?.delete(t)}_$E_(){const t=new Map,e=this.constructor.elementProperties;for(const i of e.keys())this.hasOwnProperty(i)&&(t.set(i,this[i]),delete this[i]);t.size>0&&(this._$Ep=t)}createRenderRoot(){const t=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return((t,r)=>{if(i)t.adoptedStyleSheets=r.map(t=>t instanceof CSSStyleSheet?t:t.styleSheet);else for(const i of r){const r=document.createElement("style"),n=e.litNonce;void 0!==n&&r.setAttribute("nonce",n),r.textContent=i.cssText,t.appendChild(r)}})(t,this.constructor.elementStyles),t}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(t=>t.hostConnecLine truncated
<ha-card>
<div class="error">Entity not found: ${this._config.entity}</div>
</ha-card>
`;const e=this._config.min??0,i=this._config.max??5e3,r=Math.max(1,i-e),n=this._initialized&&null!==this._readEntityValue(),s=n?this._live:e,o=n?Ct((s-e)/r,0,1):0,a=Pt(o,this._stops),l=this._config.unit??t.attributes.unit_of_measurement??"W",c=this._config.name??t.attributes.friendly_name??this._config.entity,d=this._config.precision??0,h=n?this._format(s,d):"—",p=n?this._secondaryLine(s,l,a):"NO DATA",u={"--c1":a.c1,"--c2":a.c2,"--c3":a.c3,...Lt(this._config.background,zt)},m=qt+280*o,[f,g]=Rt(It,Wt,Bt,m);return q`
<ha-card style=${xt(u)}>
<div class="panel">
<div class="header">
<div>
<div class="title">${c}</div>
<div class="subtitle">
${n?q`Live · ${(i/1e3).toFixed(1)} ${"W"===l?"kW":l} max`:"Awaiting a valid source value"}
</div>
</div>
</div>
<div class="stage">
<div class="gauge ${n?"":"no-data"}">
${this._renderSvg(a,m)}
<div class="center">
<div class="core">
<div class="reading">
<div class="label-now">${n?"Live draw":"Source unavailable"}</div>
<div class="value">
${h}${n?q`<span class="unit-inline">${l}</span>`:J}
</div>
<div class="unit">${p}</div>
</div>
</div>
</div>
<div
class="knob"
style=${xt({left:f/400*100+"%",top:g/400*100+"%"})}
></div>
</div>
</div>
<div class="status">
<span
><span class="pulse-dot ${n?"":"inactive"}"></span
>${n?"Streaming":"No data"}</span
>
<span>Now <b>${n?`${h} ${l}`:"—"}</b></span>
<span>Limit <b>${n?this._formatLimit(i,l):"—"}</b></span>
</div>
</div>
</ha-card>
`}_format(t,e){return e<=0?Math.round(t).toLocaleString():t.toLocaleString(void 0,{minimumFractionDigits:e,maximumFractionDigits:e})}_formatLimit(t,e){return"W"===e&&t>=1e3?`${(t/1e3).toFixed(1)} kW`:`${t.toLocaleString()} ${e}`}_secondaryLine(t,e,i){return"W"===e?`${(t/1e3).toFixed(2)} kW · ${i.mood}`:i.mood}_renderSvg(t,e){const i=[],r=(e-qt)/280;for(let e=0;e<=80;e++){const n=e/80,s=qt+280*n,o=e%8==0,a=128,l=Bt-(o?8:14),[c,d]=Rt(It,Wt,a,s),[h,p]=Rt(It,Wt,l,s),u=n<=r;i.push(G`
<line
x1=${c} y1=${d} x2=${h} y2=${p}
stroke=${u?t.c1:"rgba(255,255,255,0.08)"}
stroke-width=${o?1.6:1}
stroke-linecap="round"
opacity=${u?1:.55}
style=${u?`filter: drop-shadow(0 0 4px ${t.c1})`:""}
/>
`)}return q`
<svg viewBox="0 0 400 400" preserveAspectRatio="xMidYMid meet">
<defs>
<linearGradient id="pg-stroke" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color=${t.c1} />
<stop offset="60%" stop-color=${t.c2} />
<stop offset="100%" stop-color=${t.c3} />
</linearGradient>
<radialGradient id="pg-halo" cx="50%" cy="50%" r="50%">
<stop offset="60%" stop-color="transparent" />
<stop offset="80%" stop-color=${t.c2} stop-opacity="0.5" />
<stop offset="100%" stop-color="transparent" />
</radialGradient>
<radialGradient id="pg-aura" cx="50%" cy="50%" r="50%">
<stop offset="55%" stop-color="transparent" />
<stop offset="72%" stop-color=${t.c1} stop-opacity="0.18" />
<stop offset="86%" stop-color=${t.c2} stop-opacity="0.32" />
<stop offset="100%" stop-color="transparent" />
</radialGradient>
<filter id="pg-blur-lg" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="6" />
</filter>
<filter id="pg-blur-xl" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="14" />
</filter>
</defs>
<g class="spin-slow" style="transform-origin: 200px 200px;">
<circle cx=${It} cy=${Wt} r=${174} fill="url(#pg-aura)" opacity="0.9" />
</g>
<path
d=${Ut(It,Wt,Bt,qt,410)}
stroke="rgba(255,255,255,0.06)"
stroke-width="3"
fill="none"
stroke-linecap="round"
/>
<g>${i}</g>
<path
d=${Ut(It,Wt,Bt,qt,e)}
stroke=${t.c1}
stroke-width="26"
fill="none"
stroke-linecap="round"
opacity="0.35"
filter="url(#pg-blur-xl)"
/>
<path
d=${Ut(It,Wt,Bt,qt,e)}
stroke="url(#pg-stroke)"
stroke-width="14"
fill="none"
stroke-linecap="round"
opacity="0.6"
filter="url(#pg-blur-lg)"
/>
<path
d=${Ut(It,Wt,Bt,qt,e)}
stroke="url(#pg-stroke)"
stroke-width="3"
fill="none"
stroke-linecap="round"
/>
<g class="spin-fast" style="transform-origin: 200px 200px;">
<circle
cx=${It} cy=${Wt} r=${120}
fill="none"
stroke=${t.c2}
stroke-opacity="0.18"
stroke-width="1"
stroke-dasharray="2 8"
/>
</g>
<circle
cx=${It} cy=${Wt} r=${114}
fill="url(#pg-halo)"
opacity="0.7"
class="shimmer"
/>
</svg>
`}};Gt.styles=a`
:host {
--pg-bg: ${o(zt)};
--pg-text: #eef3ff;
--pg-muted: #6b7894;
--c1: #1ee0ff;
--c2: #2a7bff;
--c3: #0a3aa0;
display: block;
}
ha-card {
background: var(--pg-bg);
color: var(--pg-text);
border: 1px solid color-mix(in oklab, currentColor 12%, transparent);
border-radius: var(--ha-card-border-radius, 16px);
box-shadow:
0 20px 40px rgba(0, 0, 0, 0.45),
0 0 0 1px color-mix(in oklab, currentColor 2%, transparent) inset;
overflow: hidden;
font-family: 'Inter', var(--primary-font-family, system-ui, sans-serif);
}
.panel {
position: relative;
padding: 22px 20px 18px;
display: flex;
flex-direction: column;
}
.panel::before {
content: '';
position: absolute;
inset: 0;
pointer-events: none;
background:
radial-gradient(1px 1px at 20% 30%, color-mix(in oklab, currentColor 60%, transparent), transparent),
radial-gradient(1px 1px at 70% 60%, color-mix(in oklab, currentColor 40%, transparent), transparent),
radial-gradient(1px 1px at 40% 80%, color-mix(in oklab, currentColor 50%, transparent), transparent),
radial-gradient(1px 1px at 85% 20%, color-mix(in oklab, currentColor 30%, transparent), transparent),
radial-gradient(1px 1px at 15% 70%, color-mix(in oklab, currentColor 40%, transparent), transparent);
opacity: 0.3;
}
.header {
display: flex;
justify-content: space-between;
align-items: flex-start;
position: relative;
z-index: 1;
}
.title {
font-size: 20px;
font-weight: 600;
line-height: 1.1;
letter-spacing: -0.4px;
}
.subtitle {
font-size: 11px;
color: var(--pg-muted);
margin-top: 4px;
letter-spacing: 0.4px;
}
.stage {
flex: 1;
display: grid;
place-items: center;
position: relative;
margin: 8px 0;
z-index: 1;
}
.gauge {
position: relative;
width: 100%;
max-width: 320px;
aspect-ratio: 1 / 1;
}
.gauge svg {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
.center {
position: absolute;
inset: 0;
display: grid;
place-items: center;
pointer-events: none;
}
.core {
width: 56%;
height: 56%;
border-radius: 50%;
background:
radial-gradient(circle at 50% 40%, rgba(255, 255, 255, 0.06), transparent 60%),
radial-gradient(
circle at 50% 100%,
color-mix(in oklab, var(--c2) 35%, transparent),
transparent 70%
),
#050912;
border: 1px solid rgba(255, 255, 255, 0.06);
box-shadow:
inset 0 0 40px color-mix(in oklab, var(--c2) 30%, transparent),
inset 0 0 0 1px rgba(255, 255, 255, 0.04),
0 0 60px color-mix(in oklab, var(--c1) 20%, transparent);
display: grid;
place-items: center;
transition: box-shadow 0.6s;
}
.reading {
text-align: center;
padding: 0 8px;
}
.label-now {
font-size: 9px;
letter-spacing: 2.5px;
color: var(--pg-muted);
text-transform: uppercase;
font-weight: 600;
margin-bottom: 6px;
}
.value {
font-size: 36px;
font-weight: 300;
letter-spacing: -1.2px;
font-variant-numeric: tabular-nums;
/* Always white inside the dark gauge core regardless of card theme. */
color: #fff;
text-shadow: 0 0 20px color-mix(in oklab, var(--c1) 60%, transparent);
line-height: 1;
}
.unit-inline {
font-size: 16px;
color: color-mix(in oklab, #fff 55%, transparent);
margin-left: 4px;
}
.unit {
font-size: 11px;
color: color-mix(in oklab, #fff 55%, transparent);
margin-top: 6px;
letter-spacing: 1.5px;
font-weight: 500;
}
.knob {
position: absolute;
width: 22px;
height: 22px;
border-radius: 50%;
background: #0a1020;
border: 1.5px solid rgba(255, 255, 255, 0.6);
transform: translate(-50%, -50%);
pointer-events: none;
box-shadow:
0 0 0 4px rgba(0, 0, 0, 0.4),
0 0 20px color-mix(in oklab, var(--c1) 80%, transparent);
display: grid;
place-items: center;
}
.knob::after {
content: '';
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--c1);
box-shadow: 0 0 8px var(--c1);
}
.status {
display: flex;
justify-content: space-between;
gap: 8px;
padding: 6px 4px 0;
font-size: 11px;
color: var(--pg-muted);
font-variant-numeric: tabular-nums;
position: relative;
z-index: 1;
}
.status b {
color: var(--pg-text);
font-weight: 600;
font-family: 'SF Mono', 'JetBrains Mono', 'Menlo', 'Consolas', ui-monospace, monospace;
letter-spacing: 0;
}
.pulse-dot {
display: inline-block;
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--c1);
margin-right: 6px;
animation: pg-pulse 1.4s ease-in-out infinite;
box-shadow: 0 0 8px var(--c1);
}
.pulse-dot.inactive {
background: var(--pg-muted);
animation: none;
box-shadow: none;
}
.gauge.no-data .spin-slow,
.gauge.no-data .spin-fast,
.gauge.no-data .shimmer {
animation: none;
}
@keyframes pg-pulse {
0%,
100% {
opacity: 1;
transform: scale(1);
}
50% {
opacity: 0.4;
transform: scale(0.7);
}
}
@keyframes pg-spin {
to {
transform: rotate(360deg);
}
}
.spin-slow {
animation: pg-spin 24s linear infinite;
transform-origin: 50% 50%;
}
.spin-fast {
animation: pg-spin 9s linear infinite reverse;
transform-origin: 50% 50%;
}
@keyframes pg-shimmer {
0%,
100% {
opacity: 0.85;
}
50% {
opacity: 1;
}
}
.shimmer {
animation: pg-shimmer 2.4s ease-in-out infinite;
}
.error {
padding: 16px;
color: var(--error-color, #db4437);
}
@media (prefers-reduced-motion: reduce) {
.spin-slow,
.spin-fast,
.shimmer,
.pulse-dot {
animation: none;
}
}
`,t([ft({attribute:!1})],Gt.prototype,"hass",void 0),t([gt()],Gt.prototype,"_config",void 0),t([gt()],Gt.prototype,"_animated",void 0),t([gt()],Gt.prototype,"_live",void 0),t([gt()],Gt.prototype,"_stops",void 0),Gt=t([pt(yt)],Gt),(window.customCards=window.customCards||[]).push({type:At,name:"Power Gauge Bar Card",description:"A compact linear power gauge — stack one row per device, sharing color levels.",preview:!0,documentationURL:"https://github.com/Lirum-Labs/ha-power-gauge"});let Vt=class extends dt{constructor(){super(...arguments),this._live={},this._animated={},this._targets={},this._ramps={},this._initialized=new Set,this._ambientStart=0,this._loop=()=>{const t=performance.now(),e=this._config?.rolling_numbers??!0,i=(t-this._ambientStart)/1e3,r=e?.012*Math.sin(i*Math.PI*2/2.6):0,n={};for(const i of Object.keys(this._animated)){const s=this._ramps[i];if(s){const e=Ct((t-s.start)/500,0,1);this._animated[i]=St(s.from,s.to,Ot(e)),e>=1&&delete this._ramps[i]}const o=this._animated[i];n[i]=e?o+r*o:o}this._live=n,this._raf=requestAnimationFrame(this._loop)}}static async getConfigElement(){return await Promise.resolve().then(function(){return re}),document.createElement(kt)}static getStubConfig(){return{type:`custom:${At}`,entities:[],max:5e3}}setConfig(t){if(!t)throw new Error("Invalid configuration");if(!Array.isArray(t.entities)||0===t.entities.length)throw new Error("You need to define at least one entity");this._config={min:0,max:5e3,precision:0,rolling_numbers:!0,...t}}getCardSize(){return Math.max(1,this._config?.entities.length??1)}connectedCallback(){super.connectedCallback(),this._ambientStart=performance.now(),this._loop()}disconnectedCallback(){super.disconnectedCallback(),this._raf&&cancelAnimationFrame(this._raf)}willUpdate(t){if(this.hass&&this._config)for(const t of this._config.entities){const e="string"==typeof t?t:t.entity;if(!e)continue;const i=this._readValue(e);null!==i?this._initialized.has(e)?this._targets[e]!==i&&(this._targets[e]=i,this._ramps[e]={from:this._animated[e]??i,to:i,start:performance.now()}):(this._initialized.add(e),this._targets[e]=i,this._animated[e]=i,this._live[e]=i):(this._initialized.delete(e),delete this._targets[e],delete this._animated[e],delete this._live[e],delete this._ramps[e])}}_readValue(t){if(!this.hass)return null;const e=this.hass.states[t];if(!e)return null;const i=e.state.trim();if(""===i||"unavailable"===i||"unknown"===i)return null;const r=Number(i);return Number.isFinite(r)?r:null}_resolveRow(t){if(!this.hass||!this._config)return null;const e="object"==typeof t&&null!==t,i=e?t.entity:t;if(!i)return null;const r=this.hass.states[i],n={...this._config,...e?t:{}},s=n.min??0,o=n.max??5e3,a=n.unit??r?.attributes.unit_of_measurement??"W",l=n.precision??0;return{entity:i,name:e&&t.name||r?.attributes.friendly_name||i,unit:a,precision:l,min:s,max:o,stops:Ft(n)}}render(){if(!this._config||!this.hass)return J;const t=this._config.entities.map(t=>this._resolveRow(t)).filter(t=>null!==t),e=Lt(this._config.background,Nt);return q`
<ha-card style=${xt(e)}>
<div class="wrap">
${this._config.title?q`<div class="card-title">${this._config.title}</div>`:J}
<div class="stack">${t.map(t=>this._renderRow(t))}</div>
</div>
</ha-card>
`}_renderRow(t){const e=this._initialized.has(t.entity)&&null!==this._readValue(t.entity),i=this._live[t.entity]??this._animated[t.entity]??0,r=Math.max(1,t.max-t.min),n=e?Ct((i-t.min)/r,0,1):0,s=Pt(n,t.stops),o=e?this._format(i,t.precision):"—";return q`
<div
class="row ${e?"":"no-data"}"
style=${xt({"--c1":s.c1,"--c2":s.c2,"--w":100*n+"%"})}
>
<div class="label">${t.name}</div>
<div class="value">
${o}${e?q`<span class="unit">${t.unit}</span>`:J}
</div>
<div class="track" aria-hidden="true">
<div class="fill"></div>
<div class="cap"></div>
</div>
</div>
`}_format(t,e){return e<=0?Math.round(t).toLocaleString():t.toLocaleString(void 0,{minimumFractionDigits:e,maximumFractionDigits:e})}};Vt.styles=a`
:host {
--pg-bg: ${o(Nt)};
--pg-text: #eef3ff;
--pg-muted: #6b7894;
display: block;
}
ha-card {
background: var(--pg-bg);
color: var(--pg-text);
border: 1px solid color-mix(in oklab, currentColor 12%, transparent);
border-radius: var(--ha-card-border-radius, 14px);
box-shadow:
0 12px 28px rgba(0, 0, 0, 0.35),
0 0 0 1px color-mix(in oklab, currentColor 2%, transparent) inset;
overflow: hidden;
font-family: 'Inter', var(--primary-font-family, system-ui, sans-serif);
}
.wrap {
padding: 14px 14px 16px;
display: flex;
flex-direction: column;
gap: 12px;
}
.card-title {
font-size: 13px;
font-weight: 600;
letter-spacing: 0.4px;
text-transform: uppercase;
color: color-mix(in oklab, currentColor 70%, transparent);
padding: 2px 2px 0;
}
.stack {
display: flex;
flex-direction: column;
gap: 10px;
}
.row {
--c1: #1ee0ff;
--c2: #2a7bff;
position: relative;
padding: 10px 14px 12px;
border-radius: 12px;
background: linear-gradient(
180deg,
color-mix(in oklab, currentColor 3%, transparent),
color-mix(in oklab, currentColor 0.5%, transparent)
);
border: 1px solid color-mix(in oklab, currentColor 10%, transparent);
box-shadow: inset 0 1px 0 color-mix(in oklab, currentColor 3%, transparent);
display: grid;
grid-template-columns: 1fr auto;
grid-template-rows: auto auto;
column-gap: 14px;
row-gap: 8px;
align-items: center;
}
.label {
grid-column: 1;
grid-row: 1;
font-size: 12px;
color: color-mix(in oklab, currentColor 78%, transparent);
letter-spacing: 0.2px;
font-weight: 500;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.value {
grid-column: 2;
grid-row: 1;
font-family: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
font-size: 14px;
font-weight: 500;
color: var(--pg-text);
font-variant-numeric: tabular-nums;
letter-spacing: -0.2px;
}
.value .unit {
font-size: 11px;
color: color-mix(in oklab, currentColor 55%, transparent);
margin-left: 4px;
font-weight: 400;
}
.track {
grid-column: 1 / -1;
grid-row: 2;
position: relative;
height: 6px;
border-radius: 999px;
background: color-mix(in oklab, currentColor 8%, transparent);
overflow: hidden;
}
.fill {
position: absolute;
inset: 0;
width: var(--w);
border-radius: 999px;
background: linear-gradient(90deg, var(--c1), var(--c2));
box-shadow:
0 0 8px color-mix(in oklab, var(--c1) 50%, transparent),
0 0 14px color-mix(in oklab, var(--c2) 30%, transparent);
transition: background 0.4s;
}
.cap {
position: absolute;
top: 50%;
left: var(--w);
width: 10px;
height: 10px;
border-radius: 50%;
background: var(--c1);
transform: translate(-50%, -50%);
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.5), 0 0 8px var(--c1);
}
.row.no-data .fill,
.row.no-data .cap {
opacity: 0;
}
@media (prefers-reduced-motion: reduce) {
.fill {
transition: none;
}
}
`,t([ft({attribute:!1})],Vt.prototype,"hass",void 0),t([gt()],Vt.prototype,"_config",void 0),t([gt()],Vt.prototype,"_live",void 0),Vt=t([pt(At)],Vt),console.info("%c POWER-GAUGE %c v0.2.3 ","color:#fff; background:#1ee0ff; font-weight:600; padding:2px 6px; border-radius:3px 0 0 3px;","color:#1ee0ff; background:#0a0f1c; font-weight:600; padding:2px 6px; border-radius:0 3px 3px 0;");const Jt={number:{mode:"box",step:1}},Yt=[{name:"entity",required:!0,selector:{entity:{domain:["sensor","input_number"]}}},{name:"",type:"grid",schema:[{name:"name",selector:{text:{}}},{name:"unit",selector:{text:{}}}]},{name:"",type:"grid",schema:[{name:"min",selector:Jt},{name:"max",selector:Jt},{name:"precision",selector:{number:{mode:"box",min:0,max:4,step:1}}}]},{name:"rolling_numbers",selector:{boolean:{}}},{name:"background",selector:{text:{}}},{name:"",type:"expandable",title:"Color levels",schema:[{name:"",type:"grid",schema:[{name:"normal",selector:Jt},{name:"normal_color",selector:{text:{}}}]},{name:"",type:"grid",schema:[{name:"warning",selector:Jt},{name:"warning_color",selector:{text:{}}}]},{name:"",type:"grid",schema:[{name:"critical",selector:Jt},{name:"critical_color",selector:{text:{}}}]}]}],Zt={entity:"Entity (required)",name:"Name (optional)",unit:"Unit override (optional)",min:"Minimum value",max:"Maximum value",precision:"Decimal places",rolling_numbers:"Rolling numbers (rapid fluctuation around the live value)",background:'Background (CSS color/gradient, "transparent" for HA theme)',normal:"Normal threshold",normal_color:`Normal color (default ${Tt})`,warning:"Warning threshold",warning_color:`Warning color (default ${Ht})`,critical:"Critical threshold",critical_color:`Critical color (default ${jt})`};let Kt=class extends dt{constructor(){super(...arguments),this._computeLabel=t=>Zt[t.name]??t.name}setConfig(t){this._config=t}render(){return this.hass&&this._config?q`
<ha-form
.hass=${this.hass}
.data=${this._config}
.schema=${Yt}
.computeLabel=${this._computeLabel}
@value-changed=${this._valueChanged}
></ha-form>
`:J}_valueChanged(t){const e=t.detail;this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:e.value},bubbles:!0,composed:!0}))}};Kt.styles=a`
:host {
display: block;
}
ha-form {
display: block;
}
`,t([ft({attribute:!1})],Kt.prototype,"hass",void 0),t([gt()],Kt.prototype,"_config",void 0),Kt=t([pt(wt)],Kt);var Qt=Object.freeze({__proto__:null,get PowerGaugeCardEditor(){return Kt}});const Xt={number:{mode:"box",step:1}},te=[{name:"title",selector:{text:{}}},{name:"entities",required:!0,selector:{entity:{multiple:!0,domain:["sensor","input_number"]}}},{name:"",type:"grid",schema:[{name:"min",selector:Xt},{name:"max",selector:Xt},{name:"precision",selector:{number:{mode:"box",min:0,max:4,step:1}}},{name:"unit",selector:{text:{}}}]},{name:"rolling_numbers",selector:{boolean:{}}},{name:"background",selector:{text:{}}},{name:"",type:"expandable",title:"Color levels",schema:[{name:"",type:"grid",schema:[{name:"normal",selector:Xt},{name:"normal_color",selector:{text:{}}}]},{name:"",type:"grid",schema:[{name:"warning",selector:Xt},{name:"warning_color",selector:{text:{}}}]},{name:"",type:"grid",schema:[{name:"critical",selector:Xt},{name:"critical_color",selector:{text:{}}}]}]}],ee={title:"Card title (optional)",entities:"Entities (one bar per entity)",min:"Minimum value",max:"Maximum value",precision:"Decimal places",unit:"Unit override (optional)",rolling_numbers:"Rolling numbers (rapid fluctuation around the live value)",background:'Background (CSS color/gradient, "transparent" for HA theme)',normal:"Normal threshold",normal_color:`Normal color (default ${Tt})`,warning:"Warning threshold",warning_color:`Warning color (default ${Ht})`,critical:"Critical threshold",critical_color:`Critical color (default ${jt})`};let ie=class extends dt{constructor(){super(...arguments),this._computeLabel=t=>ee[t.name]??t.name}setConfig(t){this._config=t}render(){return this.hass&&this._config?q`
<ha-form
.hass=${this.hass}
.data=${this._config}
.schema=${te}
.computeLabel=${this._computeLabel}
@value-changed=${this._valueChanged}
></ha-form>
<p class="hint">
For per-row overrides (custom name, max, or color per device), edit the
card YAML — each entry under <code>entities</code> can be
<code>{ entity, name, max, normal, warning, critical, ... }</code>.
</p>
`:J}_valueChanged(t){const e=t.detail;this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:e.value},bubbles:!0,composed:!0}))}};ie.styles=a`
:host {
display: block;
}
ha-form {
display: block;
}
.hint {
margin: 12px 4px 0;
font-size: 12px;
color: var(--secondary-text-color, #6b7894);
}
code {
font-family: ui-monospace, 'SF Mono', Menlo, monospace;
font-size: 11px;
background: rgba(127, 127, 127, 0.12);
padding: 1px 5px;
border-radius: 4px;
}
`,t([ft({attribute:!1})],ie.prototype,"hass",void 0),t([gt()],ie.prototype,"_config",void 0),ie=t([pt(kt)],ie);var re=Object.freeze({__proto__:null,get PowerGaugeBarCardEditor(){return ie}});