@@ -630,7 +664,7 @@ class PowerFluxCardEditor extends LitElement {
${this._renderEntitySelector(entitySelectorSchema, entities.secondary_consumer_2 || "", 'secondary_consumer_2', this._localize('editor.secondary_sensor'))}
- ${this._renderColorPickerDual('color_consumer_2', 'color_pipe_consumer_2', '#f97316')}
+ ${this._renderColorPickerQuad('color_consumer_2', 'color_pipe_consumer_2', 'color_text_consumer_2', 'color_icon_consumer_2', '#f97316')}
@@ -666,7 +700,7 @@ class PowerFluxCardEditor extends LitElement {
${this._renderEntitySelector(entitySelectorSchema, entities.secondary_consumer_3 || "", 'secondary_consumer_3', this._localize('editor.secondary_sensor'))}
- ${this._renderColorPickerDual('color_consumer_3', 'color_pipe_consumer_3', '#06b6d4')}
+ ${this._renderColorPickerQuad('color_consumer_3', 'color_pipe_consumer_3', 'color_text_consumer_3', 'color_icon_consumer_3', '#06b6d4')}
`;
}
diff --git a/src/power-flux-card.js b/src/power-flux-card.js
index 14d5dcd..4db2e67 100644
--- a/src/power-flux-card.js
+++ b/src/power-flux-card.js
@@ -3,7 +3,7 @@ import lang_en from "./lang-en.js";
import lang_de from "./lang-de.js";
console.log(
- "%c⚡ Power Flux Card v_2.1 ready",
+ "%c⚡ Power Flux Card v_2.4 ready",
"background: #d19525ff; color: #000; padding: 2px 6px; border-radius: 4px; font-weight: bold;"
);
@@ -44,6 +44,8 @@ console.log(
consumer_2_unit_kw: false,
consumer_3_unit_kw: false,
show_consumer_always: false,
+ consumer_1_hide_pipe: false,
+ consumer_1_pipe_threshold: 0,
show_donut_border: false,
show_neon_glow: true,
show_comet_tail: false,
@@ -66,6 +68,8 @@ console.log(
grid_combined: "",
battery: "",
battery_soc: "",
+ battery_charge: "",
+ battery_discharge: "",
house: "",
consumer_1: "",
consumer_2: "",
@@ -128,6 +132,21 @@ console.log(
'color_pipe_consumer_1': '--pipe-consumer-1-color',
'color_pipe_consumer_2': '--pipe-consumer-2-color',
'color_pipe_consumer_3': '--pipe-consumer-3-color',
+ 'color_house': '--neon-pink',
+ 'color_icon_solar': '--icon-solar-color',
+ 'color_icon_grid': '--icon-grid-color',
+ 'color_icon_battery': '--icon-battery-color',
+ 'color_icon_house': '--icon-house-color',
+ 'color_icon_consumer_1': '--icon-consumer-1-color',
+ 'color_icon_consumer_2': '--icon-consumer-2-color',
+ 'color_icon_consumer_3': '--icon-consumer-3-color',
+ 'color_text_solar': '--text-solar-color',
+ 'color_text_grid': '--text-grid-color',
+ 'color_text_battery': '--text-battery-color',
+ 'color_text_house': '--text-house-color',
+ 'color_text_consumer_1': '--text-consumer-1-color',
+ 'color_text_consumer_2': '--text-consumer-2-color',
+ 'color_text_consumer_3': '--text-consumer-3-color',
};
for (const [configKey, cssVar] of Object.entries(colorMap)) {
if (this.config[configKey]) {
@@ -155,7 +174,6 @@ console.log(
--neon-green: #00ff88;
--neon-pink: #ff0080;
--neon-red: #ff3333;
- --grid-grey: #9e9e9e;
--export-purple: #a855f7;
--export-color: #ff3333;
--consumer-1-color: #a855f7;
@@ -167,6 +185,20 @@ console.log(
--pipe-consumer-1-color: var(--consumer-1-color);
--pipe-consumer-2-color: var(--consumer-2-color);
--pipe-consumer-3-color: var(--consumer-3-color);
+ --icon-solar-color: var(--neon-yellow);
+ --icon-grid-color: var(--neon-blue);
+ --icon-battery-color: var(--neon-green);
+ --icon-house-color: var(--neon-pink);
+ --icon-consumer-1-color: var(--consumer-1-color);
+ --icon-consumer-2-color: var(--consumer-2-color);
+ --icon-consumer-3-color: var(--consumer-3-color);
+ --text-solar-color: var(--neon-yellow);
+ --text-grid-color: var(--neon-blue);
+ --text-battery-color: var(--neon-green);
+ --text-house-color: var(--neon-pink);
+ --text-consumer-1-color: var(--consumer-1-color);
+ --text-consumer-2-color: var(--consumer-2-color);
+ --text-consumer-3-color: var(--consumer-3-color);
--flow-dasharray: 0 380;
}
:host([data-theme-light]) {
@@ -175,7 +207,6 @@ console.log(
--neon-green: #059669;
--neon-pink: #db2777;
--neon-red: #dc2626;
- --grid-grey: #6b7280;
--export-purple: #7c3aed;
--export-color: #dc2626;
--consumer-1-color: #7c3aed;
@@ -256,11 +287,6 @@ console.log(
overflow: hidden;
}
- /* Source Colors */
- .src-solar { background: var(--neon-yellow); color: black; }
- .src-grid { background: var(--neon-blue); color: black; }
- .src-battery { background: var(--neon-green); color: black; }
-
/* --- STANDARD VIEW STYLES --- */
.scale-wrapper {
width: 420px;
@@ -394,7 +420,7 @@ console.log(
@keyframes dash { to { stroke-dashoffset: -1500; } }
.flow-text {
- font-size: 10px; font-weight: bold; text-anchor: middle; fill: #fff; filter: transition: opacity 0.3s ease;
+ font-size: 10px; font-weight: bold; text-anchor: middle; fill: #fff; transition: opacity 0.3s ease;
}
.flow-text.no-shadow { filter: none; }
.text-solar { fill: var(--pipe-solar-color); }
@@ -408,35 +434,36 @@ console.log(
_renderIcon(type, val = 0, colorOverride = null) {
if (type === 'solar') {
const animate = Math.round(val) > 0 ? 'spin-slow' : '';
- const color = colorOverride || 'var(--neon-yellow)';
+ const color = colorOverride || 'var(--icon-solar-color)';
return html` this._handleClick(entities.grid_combined || entities.grid)}>
- ${renderMainIcon('grid', isGridExporting ? gridExport : gridImport, iconGrid, gridColor)}
+ ${renderMainIcon('grid', isGridExporting ? gridExport : gridImport, iconGrid, gridIconColor)}
${renderSecondaryOrLabel(labelGridText, showLabelGrid, entities.secondary_grid, hasSecondaryGrid)}
-
+
${isGridExporting ? html`▲ ` : (isGridActive ? html`▼ ` : '')}
${this._formatPower(isGridExporting ? gridExport : gridImport)}
` : ''}
${hasBattery ? html`
-
this._handleClick(entities.battery)}>
${renderMainIcon('battery', battSoc, iconBattery)}
${renderSecondaryOrLabel(labelBatteryText, showLabelBattery, entities.secondary_battery, hasSecondaryBattery)}
-
${Math.round(battSoc)}%
+
${Math.round(battSoc)}%
` : ''}
-
this._handleClick(entities.house)}>
- ${renderMainIcon('house', 0, null, houseDominantColor)}
+ ${renderMainIcon('house', 0, null, this.config.color_icon_house ? 'var(--icon-house-color)' : houseDominantColor)}
${renderLabel(labelHouseText, showLabelHouse)}
-
${this._formatPower(house)}
+
${this._formatPower(houseDisplay)}
${renderConsumer(showC1, 'c1', 'consumer_1', labelC1, 'car', c1Val, this._getConsumerColor(1))}
From 9430bc9bcf1494a2528120c7d5fafadaff0d6ca4 Mon Sep 17 00:00:00 2001
From: jayjojayson <2683358+jayjojayson@users.noreply.github.com>
Date: Tue, 3 Mar 2026 22:31:03 +0000
Subject: [PATCH 16/20] power-flux-card Auto-build
---
dist/power-flux-card.js | 313 ++++++++++++++++++++++++++--------------
1 file changed, 207 insertions(+), 106 deletions(-)
diff --git a/dist/power-flux-card.js b/dist/power-flux-card.js
index e100d7d..d65ef37 100644
--- a/dist/power-flux-card.js
+++ b/dist/power-flux-card.js
@@ -18,7 +18,7 @@ const lang_de = {
"editor.label_toggle": "Label im Kreis anzeigen",
"editor.compact_view": "Kompakte Ansicht (evcc)",
"editor.hide_inactive": "Inaktive Röhren ausblenden",
- "editor.entity": "Entität (Watt)",
+ "editor.entity": "Kombinierter Batterie Sensor (W)",
"editor.label": "Beschriftung",
"editor.icon": "Icon",
"editor.back": "Zurück",
@@ -39,15 +39,22 @@ const lang_de = {
"editor.hide_consumer_icons": "Icons unten ausblenden",
"editor.invert_consumer_1": "Sensorwert invertieren (+/-)",
"editor.secondary_sensor": "Zweiter Sensor (nur Anzeige)",
- "editor.grid_to_battery_sensor": "Netz-zu-Batterie Sensor (Watt)",
+ "editor.grid_to_battery_sensor": "Netz-zu-Batterie Sensor (W, Optional)",
"editor.grid_to_battery_hint": "Optional: separater Sensor für den Netz-zu-Batterie Fluss. Wenn leer, wird der Wert automatisch berechnet.",
"editor.grid_combined_sensor": "Kombinierter Netz-Sensor (W, Optional)",
"editor.grid_combined_hint": "Ein Sensor für Import UND Export: positiv = Import, negativ = Export. Überschreibt den kombinierten Import/Export Sensor.",
- "editor.color_picker": "Kreis Farbe",
- "editor.pipe_color": "Rohr Farbe",
- "editor.export_color": "Export Farbe",
+ "editor.color_picker": "Bubble",
+ "editor.pipe_color": "Pipe",
+ "editor.export_color": "Export",
"editor.consumer_unit_kw": "Sensor meldet in kW",
"editor.show_consumer_always": "Verbraucher bei null Watt anzeigen",
+ "editor.battery_charge_sensor": "Batterie-Ladung Sensor (W, Optional)",
+ "editor.battery_discharge_sensor": "Batterie-Entladung Sensor (W, Optional)",
+ "editor.battery_separate_hint": "Optional: Separate Sensoren für Laden/Entladen. Überschreiben den Hauptsensor für die Berechnung.",
+ "editor.consumer_1_hide_pipe": "Pipe bei geringer Leistung ausblenden",
+ "editor.consumer_pipe_threshold": "Pipe-Schwellenwert (Watt)",
+ "editor.text_color": "Text",
+ "editor.icon_color": "Icon",
},
card: {
"card.label_solar": "Solar",
@@ -55,8 +62,8 @@ const lang_de = {
"card.label_battery": "Batterie",
"card.label_house": "Verbrauch",
"card.label_car": "E-Auto",
- "card.label_import": "Import",
- "card.label_export": "Export",
+ "card.label_heater": "Heizung",
+ "card.label_pool": "Pool",
}
};
const lang_en = {
@@ -74,7 +81,7 @@ const lang_en = {
"editor.label_toggle": "Show Label in Bubble",
"editor.compact_view": "Compact View (evcc)",
"editor.hide_inactive": "Hide Inactive Pipes",
- "editor.entity": "Entity (Watt)",
+ "editor.entity": "Combined Battery Sensor (W)",
"editor.label": "Label",
"editor.icon": "Icon",
"editor.back": "Back",
@@ -95,7 +102,7 @@ const lang_en = {
"editor.hide_consumer_icons": "Hide Consumer Icons",
"editor.invert_consumer_1": "Invert Sensor Value (+/-)",
"editor.secondary_sensor": "Secondary Sensor (display only)",
- "editor.grid_to_battery_sensor": "Grid to Battery Sensor (Watt)",
+ "editor.grid_to_battery_sensor": "Grid to Battery Sensor (W, optional)",
"editor.grid_to_battery_hint": "Optional: separate sensor for grid-to-battery flow. If empty, the value is calculated automatically.",
"editor.grid_combined_sensor": "Combined Grid Sensor (W, Optional)",
"editor.grid_combined_hint": "Single sensor for import AND export: positive = import, negative = export. Overrides combined import/export sensor.",
@@ -104,6 +111,13 @@ const lang_en = {
"editor.export_color": "Export Color",
"editor.consumer_unit_kw": "Sensor reports in kW",
"editor.show_consumer_always": "Show Consumers at zero watts",
+ "editor.battery_charge_sensor": "Battery Charge Sensor (W, Optional)",
+ "editor.battery_discharge_sensor": "Battery Discharge Sensor (W, Optional)",
+ "editor.battery_separate_hint": "Optional: Separate sensors for charge/discharge. Override the main sensor for calculations.",
+ "editor.consumer_1_hide_pipe": "Hide pipe at low power",
+ "editor.consumer_pipe_threshold": "Pipe Threshold (Watts)",
+ "editor.text_color": "Text Color",
+ "editor.icon_color": "Icon Color",
},
card: {
"card.label_solar": "Solar",
@@ -111,8 +125,8 @@ const lang_en = {
"card.label_battery": "Battery",
"card.label_house": "Consumption",
"card.label_car": "Car",
- "card.label_import": "Import",
- "card.label_export": "Export",
+ "card.label_heater": "Heater",
+ "card.label_pool": "Pool",
}
};
@@ -172,7 +186,7 @@ class PowerFluxCardEditor extends LitElement {
if (!this._config || !this.hass) return;
const target = ev.target;
- const key = target.configValue || this._currentConfigValue;
+ const key = target.configValue;
let value;
if (target.tagName === 'HA-SWITCH') {
@@ -191,6 +205,7 @@ class PowerFluxCardEditor extends LitElement {
const entityKeys = [
'solar', 'grid', 'grid_export', 'grid_combined',
'battery', 'battery_soc', 'grid_to_battery',
+ 'battery_charge', 'battery_discharge',
'house',
'consumer_1', 'consumer_2', 'consumer_3',
'secondary_solar', 'secondary_grid', 'secondary_battery',
@@ -286,31 +301,30 @@ class PowerFluxCardEditor extends LitElement {
`;
}
- _renderColorPickerDual(bubbleKey, pipeKey, defaultColor) {
- const bubbleColor = this._config[bubbleKey] || defaultColor;
- const pipeColor = this._config[pipeKey] || defaultColor;
- const hasBubbleCustom = !!this._config[bubbleKey];
- const hasPipeCustom = !!this._config[pipeKey];
+ _renderColorPickerQuad(bubbleKey, pipeKey, textKey, iconKey, defaultColor) {
+ const items = [
+ { key: bubbleKey, label: this._localize('editor.color_picker'), default: defaultColor },
+ ];
+ if (pipeKey) items.push({ key: pipeKey, label: this._localize('editor.pipe_color'), default: defaultColor });
+ items.push({ key: textKey, label: this._localize('editor.text_color'), default: defaultColor });
+ items.push({ key: iconKey, label: this._localize('editor.icon_color'), default: defaultColor });
return html`
-
-
- this._colorChanged(bubbleKey, e)}>
- ${this._localize('editor.color_picker')}
- ${hasBubbleCustom ? html` this._resetColor(bubbleKey)}> ` : ''}
-
-
- this._colorChanged(pipeKey, e)}>
- ${this._localize('editor.pipe_color')}
- ${hasPipeCustom ? html` this._resetColor(pipeKey)}> ` : ''}
-
+
+ ${items.map(item => {
+ const color = this._config[item.key] || item.default;
+ const hasCustom = !!this._config[item.key];
+ return html`
+
+ this._colorChanged(item.key, e)}>
+ ${item.label}
+ ${hasCustom ? html` this._resetColor(item.key)}> ` : ''}
+
+ `;
+ })}
`;
}
@@ -422,8 +436,8 @@ class PowerFluxCardEditor extends LitElement {
-webkit-appearance: none;
border: 2px solid var(--divider-color);
border-radius: 50%;
- width: 36px;
- height: 36px;
+ width: 30px;
+ height: 30px;
padding: 2px;
cursor: pointer;
background: transparent;
@@ -447,11 +461,11 @@ class PowerFluxCardEditor extends LitElement {
.color-reset-btn:hover {
color: var(--primary-color);
}
- .color-picker-dual {
+ .color-picker-quad {
display: flex;
gap: 8px;
}
- .color-picker-dual .color-picker-row {
+ .color-picker-quad .color-picker-row {
flex: 1;
}
`;
@@ -492,7 +506,7 @@ class PowerFluxCardEditor extends LitElement {
${this._renderEntitySelector(entitySelectorSchema, entities.secondary_solar || "", 'secondary_solar', this._localize('editor.secondary_sensor'))}
- ${this._renderColorPickerDual('color_solar', 'color_pipe_solar', '#ffdd00')}
+ ${this._renderColorPickerQuad('color_solar', 'color_pipe_solar', 'color_text_solar', 'color_icon_solar', '#ffdd00')}
@@ -560,7 +574,7 @@ class PowerFluxCardEditor extends LitElement {
${this._renderEntitySelector(entitySelectorSchema, entities.secondary_grid || "", 'secondary_grid', this._localize('editor.secondary_sensor'))}
- ${this._renderColorPickerDual('color_grid', 'color_pipe_grid', '#3b82f6')}
+ ${this._renderColorPickerQuad('color_grid', 'color_pipe_grid', 'color_text_grid', 'color_icon_grid', '#3b82f6')}
${this._renderColorPicker('color_export', this._localize('editor.export_color'), '#ff3333')}
@@ -597,13 +611,14 @@ class PowerFluxCardEditor extends LitElement {
${this._renderEntitySelector(entitySelectorSchema, entities.battery, 'battery', this._localize('editor.entity'))}
- ${this._renderEntitySelector(entitySelectorSchema, entities.battery_soc, 'battery_soc', this._localize('editor.battery_soc_label'))}
+
- ${this._renderEntitySelector(entitySelectorSchema, entities.grid_to_battery || "", 'grid_to_battery', this._localize('editor.grid_to_battery_sensor'))}
- ${this._localize('editor.grid_to_battery_hint')}
+ ${this._localize('editor.battery_separate_hint')}
-
+ ${this._renderEntitySelector(entitySelectorSchema, entities.battery_charge || "", 'battery_charge', this._localize('editor.battery_charge_sensor'))}
+ ${this._renderEntitySelector(entitySelectorSchema, entities.battery_discharge || "", 'battery_discharge', this._localize('editor.battery_discharge_sensor'))}
+
+
+
+ ${this._renderEntitySelector(entitySelectorSchema, entities.battery_soc, 'battery_soc', this._localize('editor.battery_soc_label'))}
+
+
+
+
+ ${this._localize('editor.grid_to_battery_hint')}
+
+ ${this._renderEntitySelector(entitySelectorSchema, entities.grid_to_battery || "", 'grid_to_battery', this._localize('editor.grid_to_battery_sensor'))}
+
${this._renderEntitySelector(entitySelectorSchema, entities.secondary_battery || "", 'secondary_battery', this._localize('editor.secondary_sensor'))}
- ${this._renderColorPickerDual('color_battery', 'color_pipe_battery', '#00ff88')}
+ ${this._renderColorPickerQuad('color_battery', 'color_pipe_battery', 'color_text_battery', 'color_icon_battery', '#00ff88')}
@@ -674,6 +700,8 @@ class PowerFluxCardEditor extends LitElement {
${this._localize('editor.house_sensor_hint')}
+
+ ${this._renderColorPickerQuad('color_house', null, 'color_text_house', 'color_icon_house', '#ff0080')}
@@ -707,6 +735,26 @@ class PowerFluxCardEditor extends LitElement {
>
+
+ ${this._localize('editor.consumer_1_hide_pipe')}
+
+
+
+ ${this._config.consumer_1_hide_pipe === true ? html`
+
+ ` : ''}
+
${this._localize('editor.consumer_unit_kw')}
@@ -754,7 +802,7 @@ class PowerFluxCardEditor extends LitElement {
${this._renderEntitySelector(entitySelectorSchema, entities.secondary_consumer_2 || "", 'secondary_consumer_2', this._localize('editor.secondary_sensor'))}
- ${this._renderColorPickerDual('color_consumer_2', 'color_pipe_consumer_2', '#f97316')}
+ ${this._renderColorPickerQuad('color_consumer_2', 'color_pipe_consumer_2', 'color_text_consumer_2', 'color_icon_consumer_2', '#f97316')}
@@ -790,7 +838,7 @@ class PowerFluxCardEditor extends LitElement {
${this._renderEntitySelector(entitySelectorSchema, entities.secondary_consumer_3 || "", 'secondary_consumer_3', this._localize('editor.secondary_sensor'))}
- ${this._renderColorPickerDual('color_consumer_3', 'color_pipe_consumer_3', '#06b6d4')}
+ ${this._renderColorPickerQuad('color_consumer_3', 'color_pipe_consumer_3', 'color_text_consumer_3', 'color_icon_consumer_3', '#06b6d4')}
`;
}
@@ -955,7 +1003,7 @@ customElements.define("power-flux-card-editor", PowerFluxCardEditor);
console.log(
- "%c⚡ Power Flux Card v_2.1 ready",
+ "%c⚡ Power Flux Card v_2.4 ready",
"background: #d19525ff; color: #000; padding: 2px 6px; border-radius: 4px; font-weight: bold;"
);
@@ -996,6 +1044,8 @@ console.log(
consumer_2_unit_kw: false,
consumer_3_unit_kw: false,
show_consumer_always: false,
+ consumer_1_hide_pipe: false,
+ consumer_1_pipe_threshold: 0,
show_donut_border: false,
show_neon_glow: true,
show_comet_tail: false,
@@ -1018,6 +1068,8 @@ console.log(
grid_combined: "",
battery: "",
battery_soc: "",
+ battery_charge: "",
+ battery_discharge: "",
house: "",
consumer_1: "",
consumer_2: "",
@@ -1080,6 +1132,21 @@ console.log(
'color_pipe_consumer_1': '--pipe-consumer-1-color',
'color_pipe_consumer_2': '--pipe-consumer-2-color',
'color_pipe_consumer_3': '--pipe-consumer-3-color',
+ 'color_house': '--neon-pink',
+ 'color_icon_solar': '--icon-solar-color',
+ 'color_icon_grid': '--icon-grid-color',
+ 'color_icon_battery': '--icon-battery-color',
+ 'color_icon_house': '--icon-house-color',
+ 'color_icon_consumer_1': '--icon-consumer-1-color',
+ 'color_icon_consumer_2': '--icon-consumer-2-color',
+ 'color_icon_consumer_3': '--icon-consumer-3-color',
+ 'color_text_solar': '--text-solar-color',
+ 'color_text_grid': '--text-grid-color',
+ 'color_text_battery': '--text-battery-color',
+ 'color_text_house': '--text-house-color',
+ 'color_text_consumer_1': '--text-consumer-1-color',
+ 'color_text_consumer_2': '--text-consumer-2-color',
+ 'color_text_consumer_3': '--text-consumer-3-color',
};
for (const [configKey, cssVar] of Object.entries(colorMap)) {
if (this.config[configKey]) {
@@ -1107,7 +1174,6 @@ console.log(
--neon-green: #00ff88;
--neon-pink: #ff0080;
--neon-red: #ff3333;
- --grid-grey: #9e9e9e;
--export-purple: #a855f7;
--export-color: #ff3333;
--consumer-1-color: #a855f7;
@@ -1119,6 +1185,20 @@ console.log(
--pipe-consumer-1-color: var(--consumer-1-color);
--pipe-consumer-2-color: var(--consumer-2-color);
--pipe-consumer-3-color: var(--consumer-3-color);
+ --icon-solar-color: var(--neon-yellow);
+ --icon-grid-color: var(--neon-blue);
+ --icon-battery-color: var(--neon-green);
+ --icon-house-color: var(--neon-pink);
+ --icon-consumer-1-color: var(--consumer-1-color);
+ --icon-consumer-2-color: var(--consumer-2-color);
+ --icon-consumer-3-color: var(--consumer-3-color);
+ --text-solar-color: var(--neon-yellow);
+ --text-grid-color: var(--neon-blue);
+ --text-battery-color: var(--neon-green);
+ --text-house-color: var(--neon-pink);
+ --text-consumer-1-color: var(--consumer-1-color);
+ --text-consumer-2-color: var(--consumer-2-color);
+ --text-consumer-3-color: var(--consumer-3-color);
--flow-dasharray: 0 380;
}
:host([data-theme-light]) {
@@ -1127,7 +1207,6 @@ console.log(
--neon-green: #059669;
--neon-pink: #db2777;
--neon-red: #dc2626;
- --grid-grey: #6b7280;
--export-purple: #7c3aed;
--export-color: #dc2626;
--consumer-1-color: #7c3aed;
@@ -1208,11 +1287,6 @@ console.log(
overflow: hidden;
}
- /* Source Colors */
- .src-solar { background: var(--neon-yellow); color: black; }
- .src-grid { background: var(--neon-blue); color: black; }
- .src-battery { background: var(--neon-green); color: black; }
-
/* --- STANDARD VIEW STYLES --- */
.scale-wrapper {
width: 420px;
@@ -1346,7 +1420,7 @@ console.log(
@keyframes dash { to { stroke-dashoffset: -1500; } }
.flow-text {
- font-size: 10px; font-weight: bold; text-anchor: middle; fill: #fff; filter: transition: opacity 0.3s ease;
+ font-size: 10px; font-weight: bold; text-anchor: middle; fill: #fff; transition: opacity 0.3s ease;
}
.flow-text.no-shadow { filter: none; }
.text-solar { fill: var(--pipe-solar-color); }
@@ -1360,35 +1434,36 @@ console.log(
_renderIcon(type, val = 0, colorOverride = null) {
if (type === 'solar') {
const animate = Math.round(val) > 0 ? 'spin-slow' : '';
- const color = colorOverride || 'var(--neon-yellow)';
+ const color = colorOverride || 'var(--icon-solar-color)';
return html` `;
}
if (type === 'grid') {
const animate = Math.round(val) > 0 ? 'pulse' : '';
- const color = colorOverride || 'var(--neon-blue)';
+ const color = colorOverride || 'var(--icon-grid-color)';
return html` `;
}
if (type === 'battery') {
const soc = Math.min(Math.max(val, 0), 100) / 100;
const rectHeight = 14 * soc;
const rectY = 18 - rectHeight;
- const rectColor = soc > 0.2 ? 'var(--neon-green)' : 'var(--neon-red)';
- return html` `;
+ const strokeColor = colorOverride || 'var(--icon-battery-color)';
+ const rectColor = soc > 0.2 ? strokeColor : 'var(--neon-red)';
+ return html` `;
}
if (type === 'house') {
- const strokeColor = colorOverride || 'var(--neon-pink)';
+ const strokeColor = colorOverride || 'var(--icon-house-color)';
return html` `;
}
if (type === 'car') {
- const c = colorOverride || 'var(--consumer-1-color)';
+ const c = colorOverride || 'var(--icon-consumer-1-color)';
return html` `;
}
if (type === 'heater') {
- const c = colorOverride || 'var(--consumer-2-color)';
+ const c = colorOverride || 'var(--icon-consumer-2-color)';
return html` `;
}
if (type === 'pool') {
- const c = colorOverride || 'var(--consumer-3-color)';
+ const c = colorOverride || 'var(--icon-consumer-3-color)';
return html` `;
}
return html``;
@@ -1498,8 +1573,12 @@ console.log(
gridExport = gridMain < 0 ? Math.abs(gridMain) : 0;
}
- const batteryCharge = battery > 0 ? battery : 0;
- const batteryDischarge = battery < 0 ? Math.abs(battery) : 0;
+ // Check for separate battery charge/discharge sensors
+ const hasBattChargeSensor = !!(entities.battery_charge && entities.battery_charge !== "");
+ const hasBattDischargeSensor = !!(entities.battery_discharge && entities.battery_discharge !== "");
+
+ const batteryCharge = hasBattChargeSensor ? Math.abs(getVal(entities.battery_charge)) : (battery > 0 ? battery : 0);
+ const batteryDischarge = hasBattDischargeSensor ? Math.abs(getVal(entities.battery_discharge)) : (battery < 0 ? Math.abs(battery) : 0);
let solarToBatt = 0;
let gridToBatt = 0;
@@ -1593,9 +1672,9 @@ console.log(
const path = this._createBracketPath(s.startPx, s.widthPx, 'down');
let icon = '';
let iconColor = '';
- if (s.type === 'solar') { icon = 'mdi:weather-sunny'; iconColor = 'var(--neon-yellow)'; }
- if (s.type === 'grid') { icon = 'mdi:transmission-tower'; iconColor = 'var(--neon-blue)'; }
- if (s.type === 'battery') { icon = 'mdi:battery-high'; iconColor = 'var(--neon-green)'; }
+ if (s.type === 'solar') { icon = 'mdi:weather-sunny'; iconColor = 'var(--icon-solar-color)'; }
+ if (s.type === 'grid') { icon = 'mdi:transmission-tower'; iconColor = 'var(--icon-grid-color)'; }
+ if (s.type === 'battery') { icon = 'mdi:battery-high'; iconColor = 'var(--icon-battery-color)'; }
return { path, width: s.widthPx, center: s.startPx + (s.widthPx / 2), icon, iconColor, val: s.val, entityId: s.entityId };
});
@@ -1613,10 +1692,10 @@ console.log(
let icon = '';
let iconColor = '';
- if (type === 'house') { icon = 'mdi:home'; iconColor = 'var(--primary-text-color)'; }
- if (type === 'car') { icon = 'mdi:car-electric'; iconColor = this._getConsumerColor(1); }
- if (type === 'export') { icon = 'mdi:arrow-right-box'; iconColor = 'var(--export-purple)'; }
- if (type === 'battery') { icon = 'mdi:battery-charging-high'; iconColor = 'var(--neon-green)'; }
+ if (type === 'house') { icon = 'mdi:home'; iconColor = 'var(--icon-house-color)'; }
+ if (type === 'car') { icon = 'mdi:car-electric'; iconColor = 'var(--icon-consumer-1-color)'; }
+ if (type === 'export') { icon = 'mdi:arrow-right-box'; iconColor = 'var(--export-color)'; }
+ if (type === 'battery') { icon = 'mdi:battery-charging-high'; iconColor = 'var(--icon-battery-color)'; }
const path = this._createBracketPath(bottomX, width, 'up');
bottomBrackets.push({
@@ -1659,14 +1738,19 @@ console.log(
- ${barSegments.map(s => html`
+ ${barSegments.map(s => {
+ const textColor = s.type === 'solar' && this.config.color_text_solar ? 'var(--text-solar-color)'
+ : s.type === 'grid' && this.config.color_text_grid ? 'var(--text-grid-color)'
+ : s.type === 'battery' && this.config.color_text_battery ? 'var(--text-battery-color)'
+ : (s.color === 'var(--export-purple)' ? 'white' : 'black');
+ return html`
s.entityId && this._handleClick(s.entityId)}>
${s.widthPx > 35 ? this._formatPower(s.val) : ''}
- `)}
+ `})}
@@ -1760,9 +1844,9 @@ console.log(
const textClass = showNeonGlow ? 'flow-text' : 'flow-text no-shadow';
// Custom Labels for Consumers
- const labelC1 = this.config.consumer_1_label || "E-Auto";
- const labelC2 = this.config.consumer_2_label || "Heizung";
- const labelC3 = this.config.consumer_3_label || "Pool";
+ const labelC1 = this.config.consumer_1_label || this._localize('card.label_car');
+ const labelC2 = this.config.consumer_2_label || this._localize('card.label_heater');
+ const labelC3 = this.config.consumer_3_label || this._localize('card.label_pool');
const getVal = (entity) => {
const state = this.hass.states[entity];
@@ -1784,6 +1868,11 @@ console.log(
const showC3 = (entities.consumer_3 && (alwaysShowConsumer || Math.round(c3Val) > 0));
const anyBottomVisible = showC1 || showC2 || showC3;
+ // Consumer 1 pipe threshold
+ const hideC1Pipe = this.config.consumer_1_hide_pipe === true;
+ const c1PipeThreshold = this.config.consumer_1_pipe_threshold || 0;
+ const c1PipeActive = showC1 && (!hideC1Pipe || c1Val >= c1PipeThreshold);
+
const solar = hasSolar ? getVal(entities.solar) : 0;
const gridCombinedVal = hasGridCombined ? getVal(entities.grid_combined) : 0;
const gridMain = hasGridCombined ? gridCombinedVal : (hasGrid ? getVal(entities.grid) : 0);
@@ -1813,8 +1902,12 @@ console.log(
}
}
- const batteryCharge = battery > 0 ? battery : 0;
- const batteryDischarge = battery < 0 ? Math.abs(battery) : 0;
+ // Check for separate battery charge/discharge sensors
+ const hasBattChargeSensor = !!(entities.battery_charge && entities.battery_charge !== "");
+ const hasBattDischargeSensor = !!(entities.battery_discharge && entities.battery_discharge !== "");
+
+ const batteryCharge = hasBattChargeSensor ? Math.abs(getVal(entities.battery_charge)) : (battery > 0 ? battery : 0);
+ const batteryDischarge = hasBattDischargeSensor ? Math.abs(getVal(entities.battery_discharge)) : (battery < 0 ? Math.abs(battery) : 0);
let solarToBatt = 0;
let gridToBatt = 0;
@@ -1841,6 +1934,9 @@ console.log(
const gridToHouse = Math.max(0, gridImport - gridToBatt);
const house = solarToHouse + gridToHouse + batteryDischarge;
+ // Use house entity for display if defined, otherwise use calculated value
+ const houseDisplay = (entities.house && entities.house !== "") ? getVal(entities.house) : house;
+
const isTopArcActive = (solarToBatt > 0);
const topShift = (isTopArcActive || (!hideInactive && hasSolar && hasBattery)) ? 0 : 50;
let baseHeight = anyBottomVisible ? 480 : 340;
@@ -1942,8 +2038,10 @@ console.log(
}
}
- const solarColor = isSolarActive ? 'var(--neon-yellow)' : 'var(--secondary-text-color)';
+ const solarColor = isSolarActive ? 'var(--icon-solar-color)' : 'var(--secondary-text-color)';
const gridColor = isGridExporting ? 'var(--export-color)' : (isGridActive ? 'var(--neon-blue)' : 'var(--secondary-text-color)');
+ const gridIconColor = (isGridActive && this.config.color_icon_grid) ? 'var(--icon-grid-color)' : gridColor;
+ const gridTextColor = (isGridActive && this.config.color_text_grid) ? 'var(--text-grid-color)' : gridColor;
const getAnimStyle = (val) => {
if (val <= 1) return "opacity: 0;";
@@ -2023,27 +2121,24 @@ console.log(
const renderMainIcon = (type, val, customIcon, color = null) => {
if (customIcon) {
- const style = color ? `color: ${color};` : (type === 'solar' ? 'color: var(--neon-yellow);' : (type === 'grid' ? 'color: var(--neon-blue);' : (type === 'battery' ? 'color: var(--neon-green);' : '')));
+ const style = color ? `color: ${color};` : (type === 'solar' ? 'color: var(--icon-solar-color);' : (type === 'grid' ? 'color: var(--icon-grid-color);' : (type === 'battery' ? 'color: var(--icon-battery-color);' : (type === 'house' ? 'color: var(--icon-house-color);' : ''))));
return html` `;
}
return this._renderIcon(type, val, color);
};
- const getCustomClass = (icon) => icon ? 'has-custom-icon' : '';
-
const renderConsumer = (isVisible, cssClass, configKey, label, iconType, val, hexColor) => {
if (!isVisible) return html``;
const customIcon = this.config[`${configKey}_icon`];
let iconContent;
- const isCustom = !hideConsumerIcons && !!customIcon;
- const dynamicClass = isCustom ? 'has-custom-icon' : '';
+ const iconColorVar = `var(--icon-${configKey.replace(/_/g, '-')}-color)`;
if (hideConsumerIcons) {
iconContent = html``;
} else if (customIcon) {
- iconContent = html` `;
+ iconContent = html` `;
} else {
iconContent = this._renderIcon(iconType, val);
}
@@ -2051,12 +2146,16 @@ console.log(
const secEntity = entities[`secondary_${configKey}`];
const hasSecondary = !!(secEntity && secEntity !== "");
+ const textStyle = this.config[`color_text_${configKey}`]
+ ? `color: var(--text-${configKey.replace(/_/g, '-')}-color);`
+ : getConsumerColorStyle(hexColor);
+
return html`
- this._handleClick(entities[configKey])}>
${iconContent}
${renderSecondaryOrLabel(label, true, secEntity, hasSecondary)}
-
${this._formatPower(val)}
+
${this._formatPower(val)}
`;
};
@@ -2086,7 +2185,9 @@ console.log(
const pathHouseC2 = "M 210 310 L 210 370";
const pathHouseC3 = "M 255 265 Q 370 265 370 370";
- const houseTextStyle = houseTextCol ? `color: ${houseTextCol};` : '';
+ const houseTextStyle = this.config.color_text_house
+ ? 'color: var(--text-house-color);'
+ : (houseTextCol ? `color: ${houseTextCol};` : '');
const dashArrayVal = showTail ? '30 360' : (showDashedLine ? '13 13' : '0 380');
const strokeWidthVal = showDashedLine ? 4 : 8;
@@ -2107,7 +2208,7 @@ console.log(
-
+
@@ -2120,7 +2221,7 @@ console.log(
-
+
@@ -2136,39 +2237,39 @@ console.log(
${hasSolar ? html`
- this._handleClick(entities.solar)}>
${renderMainIcon('solar', solarVal, iconSolar, solarColor)}
${renderSecondaryOrLabel(labelSolarText, showLabelSolar, entities.secondary_solar, hasSecondarySolar)}
-
${this._formatPower(solarVal)}
+
${this._formatPower(solarVal)}
` : ''}
${hasGrid ? html`
- this._handleClick(entities.grid_combined || entities.grid)}>
- ${renderMainIcon('grid', isGridExporting ? gridExport : gridImport, iconGrid, gridColor)}
+ ${renderMainIcon('grid', isGridExporting ? gridExport : gridImport, iconGrid, gridIconColor)}
${renderSecondaryOrLabel(labelGridText, showLabelGrid, entities.secondary_grid, hasSecondaryGrid)}
-
+
${isGridExporting ? html`▲ ` : (isGridActive ? html`▼ ` : '')}
${this._formatPower(isGridExporting ? gridExport : gridImport)}
` : ''}
${hasBattery ? html`
-
this._handleClick(entities.battery)}>
${renderMainIcon('battery', battSoc, iconBattery)}
${renderSecondaryOrLabel(labelBatteryText, showLabelBattery, entities.secondary_battery, hasSecondaryBattery)}
-
${Math.round(battSoc)}%
+
${Math.round(battSoc)}%
` : ''}
-
this._handleClick(entities.house)}>
- ${renderMainIcon('house', 0, null, houseDominantColor)}
+ ${renderMainIcon('house', 0, null, this.config.color_icon_house ? 'var(--icon-house-color)' : houseDominantColor)}
${renderLabel(labelHouseText, showLabelHouse)}
-
${this._formatPower(house)}
+
${this._formatPower(houseDisplay)}
${renderConsumer(showC1, 'c1', 'consumer_1', labelC1, 'car', c1Val, this._getConsumerColor(1))}
From 2dc24eca1b642c726e3df050a020e707ee664627 Mon Sep 17 00:00:00 2001
From: jayjojayson
Date: Wed, 4 Mar 2026 01:05:14 +0100
Subject: [PATCH 17/20] v_2.4
---
README.md | 10 +++++-----
docs/README-de.md | 10 +++++-----
2 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/README.md b/README.md
index 22fb0cb..3d15de5 100644
--- a/README.md
+++ b/README.md
@@ -133,7 +133,7 @@ entities:
battery_soc: sensor.battery_soc
card_mod:
style: |
- power-flux-card {
+ :host {
{% if states('sensor.solar_power') | float > 0 %}
--icon-solar-color: #00ff88;
{% else %}
@@ -153,7 +153,7 @@ entities:
battery_soc: sensor.battery_soc
card_mod:
style: |
- power-flux-card {
+ :host {
{% if states('sensor.grid_power_combined') | float < 0 %}
--text-grid-color: #ff3333;
{% else %}
@@ -173,7 +173,7 @@ entities:
battery_soc: sensor.battery_soc
card_mod:
style: |
- power-flux-card {
+ :host {
{% set soc = states('sensor.battery_soc') | float %}
{% if soc > 80 %}
--neon-green: #00ff88;
@@ -197,7 +197,7 @@ entities:
consumer_1: sensor.wallbox_power
card_mod:
style: |
- power-flux-card {
+ :host {
{% if states('sensor.wallbox_power') | float > 500 %}
--pipe-consumer-1-color: #a855f7;
--icon-consumer-1-color: #a855f7;
@@ -220,7 +220,7 @@ entities:
consumer_1: sensor.wallbox_power
card_mod:
style: |
- power-flux-card {
+ :host {
{% if states('sensor.solar_power') | float == 0 %}
--icon-solar-color: #555555;
--text-solar-color: #777777;
diff --git a/docs/README-de.md b/docs/README-de.md
index 6523845..8f75370 100644
--- a/docs/README-de.md
+++ b/docs/README-de.md
@@ -135,7 +135,7 @@ entities:
battery_soc: sensor.battery_soc
card_mod:
style: |
- power-flux-card {
+ :host {
{% if states('sensor.solar_power') | float > 0 %}
--icon-solar-color: #00ff88;
{% else %}
@@ -155,7 +155,7 @@ entities:
battery_soc: sensor.battery_soc
card_mod:
style: |
- power-flux-card {
+ :host {
{% if states('sensor.grid_power_combined') | float < 0 %}
--text-grid-color: #ff3333;
{% else %}
@@ -175,7 +175,7 @@ entities:
battery_soc: sensor.battery_soc
card_mod:
style: |
- power-flux-card {
+ :host {
{% set soc = states('sensor.battery_soc') | float %}
{% if soc > 80 %}
--neon-green: #00ff88;
@@ -199,7 +199,7 @@ entities:
consumer_1: sensor.wallbox_power
card_mod:
style: |
- power-flux-card {
+ :host {
{% if states('sensor.wallbox_power') | float > 500 %}
--pipe-consumer-1-color: #a855f7;
--icon-consumer-1-color: #a855f7;
@@ -222,7 +222,7 @@ entities:
consumer_1: sensor.wallbox_power
card_mod:
style: |
- power-flux-card {
+ :host {
{% if states('sensor.solar_power') | float == 0 %}
--icon-solar-color: #555555;
--text-solar-color: #777777;
From 97fd58f671124a57d12fddf94d4ead2e1588aaef Mon Sep 17 00:00:00 2001
From: jayjojayson <2683358+jayjojayson@users.noreply.github.com>
Date: Wed, 4 Mar 2026 01:24:16 +0100
Subject: [PATCH 18/20] v_2.4
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 3d15de5..1ca85ac 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@
[](https://github.com/jayjojayson/power-flux-card/stargazers)
-# Power Flux Card
+# Power Flux Card
The ⚡ Power Flux Card is an advanced, animated energy flow card for Home Assistant. It visualizes the power distribution between Solar, Grid, Battery, and Consumers with beautiful neon effects and diffrent animations.
From 1031af5b3d1731955c3b2c9d21cf05d3cafd96e1 Mon Sep 17 00:00:00 2001
From: jayjojayson <2683358+jayjojayson@users.noreply.github.com>
Date: Wed, 4 Mar 2026 13:04:31 +0100
Subject: [PATCH 19/20] v_2.4
---
.github/FUNDING.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index 24336fc..128e009 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -1,2 +1,3 @@
---
+ko_fi: jayjojayson
custom: ["https://www.paypal.me/quadFlyerFW"]
From f19730ff1da1e8c8815e8df6d04bfd6361369831 Mon Sep 17 00:00:00 2001
From: root
Date: Tue, 10 Mar 2026 10:27:27 +0100
Subject: [PATCH 20/20] Fix: Secondary sensor display - Wh/EUR precision
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Separate Wh from W in getSecondaryVal: Wh values >= 1000
now display as kWh (e.g., 794 Wh -> 0.79 kWh)
- EUR/ct/€ units now show 2 decimal places (0.28 EUR/kWh
instead of 0.3)
- W values continue using _formatPower() as before
Co-Authored-By: Claude Opus 4.6
---
dist/power-flux-card.js | 8 +-------
src/power-flux-card.js | 8 +-------
2 files changed, 2 insertions(+), 14 deletions(-)
diff --git a/dist/power-flux-card.js b/dist/power-flux-card.js
index d65ef37..2400503 100644
--- a/dist/power-flux-card.js
+++ b/dist/power-flux-card.js
@@ -1820,13 +1820,7 @@ console.log(
const val = parseFloat(state.state);
if (isNaN(val)) return state.state + (state.attributes.unit_of_measurement ? ' ' + state.attributes.unit_of_measurement : '');
const unit = state.attributes.unit_of_measurement || '';
- if (unit === 'W' || unit === 'Wh') {
- return this._formatPower(val);
- }
- if (unit === 'kWh' || unit === 'kW') {
- return val.toFixed(1) + ' ' + unit;
- }
- return val.toFixed(1) + (unit ? ' ' + unit : '');
+ if (unit === 'W') { return this._formatPower(val); } if (unit === 'Wh') { if (Math.abs(val) >= 1000) return (val / 1000).toFixed(2) + ' kWh'; return Math.round(val) + ' Wh'; } if (unit === 'kWh' || unit === 'kW') { return val.toFixed(1) + ' ' + unit; } if (unit.includes('EUR') || unit.includes('ct') || unit.includes('€')) { return val.toFixed(2) + ' ' + unit; } return val.toFixed(1) + (unit ? ' ' + unit : '');
};
// Determine existence of main entities
diff --git a/src/power-flux-card.js b/src/power-flux-card.js
index 4db2e67..842366d 100644
--- a/src/power-flux-card.js
+++ b/src/power-flux-card.js
@@ -820,13 +820,7 @@ console.log(
const val = parseFloat(state.state);
if (isNaN(val)) return state.state + (state.attributes.unit_of_measurement ? ' ' + state.attributes.unit_of_measurement : '');
const unit = state.attributes.unit_of_measurement || '';
- if (unit === 'W' || unit === 'Wh') {
- return this._formatPower(val);
- }
- if (unit === 'kWh' || unit === 'kW') {
- return val.toFixed(1) + ' ' + unit;
- }
- return val.toFixed(1) + (unit ? ' ' + unit : '');
+ if (unit === 'W') { return this._formatPower(val); } if (unit === 'Wh') { if (Math.abs(val) >= 1000) return (val / 1000).toFixed(2) + ' kWh'; return Math.round(val) + ' Wh'; } if (unit === 'kWh' || unit === 'kW') { return val.toFixed(1) + ' ' + unit; } if (unit.includes('EUR') || unit.includes('ct') || unit.includes('€')) { return val.toFixed(2) + ' ' + unit; } return val.toFixed(1) + (unit ? ' ' + unit : '');
};
// Determine existence of main entities