Repository navigation
Expand file tree
/
Copy pathxpower-flow-card.js
More file actions
1177 lines (1122 loc) · 114 KB
/
Copy pathxpower-flow-card.js
File metadata and controls
1177 lines (1122 loc) · 114 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// xPower Flow Card — Modern power flow card for solar hybrid inverters
// Copyright (C) 2025 BTNBx — MIT License
const V='1.3.47';
/* ═══════════════════════════════════════
CHANGELOG — full history in CHANGELOG.md
═══════════════════════════════════════
## v1.3.47
- New `mppt_scale` option (0.8–1.6, default 1) to enlarge the PV1/PV2/PV3 side info (dual/triple MPPT); also available as the `--xpf-mppt-scale` CSS variable
- New animated weather icons (`weather_entity`): rain, pouring, thunder, thunder with rain, snow, sleet, hail, fog, wind, wind with clouds, exceptional; twinkling stars on clear nights. Animations respect reduced motion
*/
/* ═══════════════════════════════════════
xPower Flow Card — i18n
═══════════════════════════════════════ */
const LANG={
pt:{solar:'SOLAR',grid:'REDE',load:'CASA',battery:'BATERIA',ev:'CARRO',inverter:'Inversor',appliance:'Eletrodom\u00e9sticos',garage:'Garagem',heatpump:'Bomba de Calor',extra:'Extra',
autarky:'Autossufici\u00EAncia',energy_src:'Origem da energia',runtime_to:'at\u00E9',
charge:'\u25BE',discharge:'\u25B4',import_:'\u25BE',export_:'\u25B4',
daily:'\u25B8',solar24:'SOLAR (24h)',load24:'CASA (24h)',grid24:'REDE (24h)',bat24:'BATERIA (24h)',
unavailable:'--',autodetect:'Auto-detetar',invalid:'entidade n\u00E3o existe',
editor_title:'xPower Flow Card',editor_lang:'Idioma',editor_entities:'Entidades',
editor_options:'Op\u00E7\u00F5es',editor_soc:'SOC M\u00EDnimo (%)',
editor_capacity:'Capacidade Bateria (Wh)',editor_inverter_name:'Nome Inversor',
editor_preset:'Marca / Preset',editor_polarity:'Polaridade',
editor_bat_pol:'Bateria: negativo =',editor_grid_pol:'Rede: positivo =',
charging:'carga',discharging:'descarga',importing:'importar',exporting:'exportar'},
en:{solar:'SOLAR',grid:'GRID',load:'HOME',battery:'BATTERY',ev:'EV',inverter:'Inverter',appliance:'Appliances',garage:'Garage',heatpump:'Heat Pump',extra:'Extra',
autarky:'Self-sufficiency',energy_src:'Energy source',runtime_to:'to',
charge:'\u25BE',discharge:'\u25B4',import_:'\u25BE',export_:'\u25B4',
daily:'\u25B8',solar24:'SOLAR (24h)',load24:'HOME (24h)',grid24:'GRID (24h)',bat24:'BATTERY (24h)',
unavailable:'--',autodetect:'Auto-detect',invalid:'entity not found',
editor_title:'xPower Flow Card',editor_lang:'Language',editor_entities:'Entities',
editor_options:'Options',editor_soc:'Shutdown SOC (%)',
editor_capacity:'Battery Capacity (Wh)',editor_inverter_name:'Inverter Name',
editor_preset:'Brand / Preset',editor_polarity:'Polarity',
editor_bat_pol:'Battery: negative =',editor_grid_pol:'Grid: positive =',
charging:'charging',discharging:'discharging',importing:'import',exporting:'export'},
sv:{solar:'SOL',grid:'N\u00c4T',load:'HEM',battery:'BATTERI',ev:'ELBIL',inverter:'V\u00e4xelriktare',appliance:'Vitvaror',garage:'Garage',heatpump:'V\u00e4rmepump',extra:'\u00d6vrigt',
autarky:'Sj\u00e4lvf\u00f6rs\u00f6rjning',energy_src:'Energik\u00e4lla',runtime_to:'till',
charge:'\u25BE',discharge:'\u25B4',import_:'\u25BE',export_:'\u25B4',
daily:'\u25B8',solar24:'SOL (24h)',load24:'HEM (24h)',grid24:'N\u00c4T (24h)',bat24:'BATTERI (24h)',
unavailable:'--',autodetect:'Auto-detektera',invalid:'entitet hittades inte',
editor_title:'xPower Flow Card',editor_lang:'Spr\u00e5k',editor_entities:'Entiteter',
editor_options:'Alternativ',editor_soc:'Avstängnings-SOC (%)',
editor_capacity:'Batterikapacitet (Wh)',editor_inverter_name:'V\u00e4xelriktarnamn',
editor_preset:'M\u00e4rke / Preset',editor_polarity:'Polaritet',
editor_bat_pol:'Batteri: negativ =',editor_grid_pol:'N\u00e4t: positiv =',
charging:'laddning',discharging:'urladdning',importing:'import',exporting:'export'},
de:{solar:'SOLAR',grid:'NETZ',load:'HAUS',battery:'BATTERIE',ev:'E-AUTO',inverter:'Wechselrichter',appliance:'Ger\u00e4te',garage:'Garage',heatpump:'W\u00e4rmepumpe',extra:'Sonstiges',
autarky:'Autarkie',energy_src:'Energiequelle',runtime_to:'bis',
charge:'\u25BE',discharge:'\u25B4',import_:'\u25BE',export_:'\u25B4',
daily:'\u25B8',solar24:'SOLAR (24h)',load24:'HAUS (24h)',grid24:'NETZ (24h)',bat24:'BATTERIE (24h)',
unavailable:'--',autodetect:'Auto-Erkennung',invalid:'Entit\u00E4t existiert nicht',
editor_title:'xPower Flow Card',editor_lang:'Sprache',editor_entities:'Entit\u00E4ten',
editor_options:'Optionen',editor_soc:'Abschalt-SOC (%)',
editor_capacity:'Batteriekapazit\u00E4t (Wh)',editor_inverter_name:'Wechselrichter Name',
editor_preset:'Marke / Vorlage',editor_polarity:'Polarit\u00E4t',
editor_bat_pol:'Batterie: negativ =',editor_grid_pol:'Netz: positiv =',
charging:'Laden',discharging:'Entladen',importing:'Bezug',exporting:'Einspeisung'},
fr:{solar:'SOLAIRE',grid:'R\u00C9SEAU',load:'MAISON',battery:'BATTERIE',ev:'VOITURE',inverter:'Onduleur',appliance:'\u00c9lectrom\u00e9nager',garage:'Garage',heatpump:'Pompe \u00e0 chaleur',extra:'Autre',
autarky:'Autosuffisance',energy_src:'Source d\u2019\u00e9nergie',runtime_to:'jusqu\u0027\u00E0',
charge:'\u25BE',discharge:'\u25B4',import_:'\u25BE',export_:'\u25B4',
daily:'\u25B8',solar24:'SOLAIRE (24h)',load24:'MAISON (24h)',grid24:'R\u00C9SEAU (24h)',bat24:'BATTERIE (24h)',
unavailable:'--',autodetect:'D\u00E9tection auto',invalid:'entit\u00E9 introuvable',
editor_title:'xPower Flow Card',editor_lang:'Langue',editor_entities:'Entit\u00E9s',
editor_options:'Options',editor_soc:'SOC Minimum (%)',
editor_capacity:'Capacit\u00E9 Batterie (Wh)',editor_inverter_name:'Nom Onduleur',
editor_preset:'Marque / Pr\u00E9r\u00E9glage',editor_polarity:'Polarit\u00E9',
editor_bat_pol:'Batterie: n\u00E9gatif =',editor_grid_pol:'R\u00E9seau: positif =',
charging:'charge',discharging:'d\u00E9charge',importing:'importation',exporting:'exportation'},
es:{solar:'SOLAR',grid:'RED',load:'HOGAR',battery:'BATER\u00CDAS',ev:'COCHE',inverter:'Inversor',appliance:'Electrodom\u00e9sticos',garage:'Garaje',heatpump:'Bomba de Calor',extra:'Extra',
autarky:'Autosuficiencia',energy_src:'Origen de la energ\u00eda',runtime_to:'hasta',
charge:'\u25BE',discharge:'\u25B4',import_:'\u25BE',export_:'\u25B4',
daily:'\u25B8',solar24:'SOLAR (24h)',load24:'HOGAR (24h)',grid24:'RED (24h)',bat24:'BATERÍA (24h)',
unavailable:'--',autodetect:'Autodetectar',invalid:'entidad no existe',
editor_title:'xPower Flow Card',editor_lang:'Idioma',editor_entities:'Entidades',
editor_options:'Opciones',editor_soc:'SOC M\u00EDnimo (%)',
editor_capacity:'Capacidad Bater\u00EDa (Wh)',editor_inverter_name:'Nombre Inversor',
editor_preset:'Marca / Preset',editor_polarity:'Polaridad',
editor_bat_pol:'Bater\u00EDa: negativo =',editor_grid_pol:'Red: positivo =',
charging:'carga',discharging:'descarga',importing:'importaci\u00F3n',exporting:'exportaci\u00F3n'},
it:{solar:'SOLARE',grid:'RETE',load:'CASA',battery:'BATTERIA',ev:'AUTO',inverter:'Inverter',appliance:'Elettrodomestici',garage:'Garage',heatpump:'Pompa di Calore',extra:'Extra',
autarky:'Autosufficienza',energy_src:'Origine dell\u2019energia',runtime_to:'fino a',
charge:'\u25BE',discharge:'\u25B4',import_:'\u25BE',export_:'\u25B4',
daily:'\u25B8',solar24:'SOLARE (24h)',load24:'CASA (24h)',grid24:'RETE (24h)',bat24:'BATTERIA (24h)',
unavailable:'--',autodetect:'Rilevamento auto',invalid:'entit\u00E0 inesistente',
editor_title:'xPower Flow Card',editor_lang:'Lingua',editor_entities:'Entit\u00E0',
editor_options:'Opzioni',editor_soc:'SOC Minimo (%)',
editor_capacity:'Capacit\u00E0 Batteria (Wh)',editor_inverter_name:'Nome Inverter',
editor_preset:'Marca / Preset',editor_polarity:'Polarit\u00E0',
editor_bat_pol:'Batteria: negativo =',editor_grid_pol:'Rete: positivo =',
charging:'carica',discharging:'scarica',importing:'importazione',exporting:'esportazione'},
nl:{solar:'ZONNE',grid:'NET',load:'HUIS',battery:'BATTERIJ',ev:'EV',inverter:'Omvormer',appliance:'Apparaten',garage:'Garage',heatpump:'Warmtepomp',extra:'Overig',
autarky:'Zelfvoorziening',energy_src:'Energiebron',runtime_to:'tot',
charge:'\u25BE',discharge:'\u25B4',import_:'\u25BE',export_:'\u25B4',
daily:'\u25B8',solar24:'ZONNE (24h)',load24:'HUIS (24h)',grid24:'NET (24h)',bat24:'BATTERIJ (24h)',
unavailable:'--',autodetect:'Auto-detectie',invalid:'entiteit bestaat niet',
editor_title:'xPower Flow Card',editor_lang:'Taal',editor_entities:'Entiteiten',
editor_options:'Opties',editor_soc:'Uitschakel-SOC (%)',
editor_capacity:'Batterijcapaciteit (Wh)',editor_inverter_name:'Omvormer Naam',
editor_preset:'Merk / Preset',editor_polarity:'Polariteit',
editor_bat_pol:'Batterij: negatief =',editor_grid_pol:'Net: positief =',
charging:'laden',discharging:'ontladen',importing:'import',exporting:'export'},
pl:{solar:'SOLAR',grid:'SIE\u0106',load:'DOM',battery:'BATERIA',ev:'EV',inverter:'Falownik',appliance:'AGD',garage:'Gara\u017c',heatpump:'Pompa Ciep\u0142a',extra:'Inne',
autarky:'Samowystarczalno\u015B\u0107',energy_src:'\u0179r\u00f3d\u0142o energii',runtime_to:'do',
charge:'\u25BE',discharge:'\u25B4',import_:'\u25BE',export_:'\u25B4',
daily:'\u25B8',solar24:'SOLAR (24h)',load24:'DOM (24h)',grid24:'SIE\u0106 (24h)',bat24:'BATERIA (24h)',
unavailable:'--',autodetect:'Autowykrywanie',invalid:'encja nie istnieje',
editor_title:'xPower Flow Card',editor_lang:'J\u0119zyk',editor_entities:'Encje',
editor_options:'Opcje',editor_soc:'Min. SOC (%)',
editor_capacity:'Pojemno\u015B\u0107 baterii (Wh)',editor_inverter_name:'Nazwa falownika',
editor_preset:'Marka / Preset',editor_polarity:'Polaryzacja',
editor_bat_pol:'Bateria: ujemny =',editor_grid_pol:'Sie\u0107: dodatni =',
charging:'\u0142adowanie',discharging:'roz\u0142adowanie',importing:'pobieranie',exporting:'oddawanie'}
};
const PRESETS={
deye:{
label:'Deye (Solarman)',inverter_name:'DEYE',
bat_polarity:'negative',grid_polarity:'positive',
e:{solar:'sensor.deye_pv1_power',battery:'sensor.deye_battery_power',soc:'sensor.deye_battery',
grid:'sensor.deye_external_ct1_power',load:'sensor.deye_load_l1_power',
grid_voltage:'sensor.deye_grid_l1_voltage',battery_voltage:'sensor.deye_battery_voltage',
pv_voltage:'sensor.deye_pv1_voltage',temperature:'sensor.deye_temperature',
frequency:'sensor.deye_output_frequency',grid_status:'binary_sensor.deye_grid',
daily_solar:'sensor.deye_today_production',daily_import:'sensor.deye_today_energy_import',
daily_export:'sensor.deye_today_energy_export',daily_load:'sensor.deye_today_load_consumption',
daily_charge:'sensor.deye_today_battery_charge',daily_discharge:'sensor.deye_today_battery_discharge',
battery_temperature:'sensor.deye_battery_temperature'}
},
sunsynk:{
label:'Sunsynk',inverter_name:'SUNSYNK',
bat_polarity:'negative',grid_polarity:'positive',
e:{solar:'sensor.sunsynk_pv1_power',battery:'sensor.sunsynk_battery_power',soc:'sensor.sunsynk_battery_soc',
grid:'sensor.sunsynk_grid_power',load:'sensor.sunsynk_essential_power',
grid_voltage:'sensor.sunsynk_grid_voltage',battery_voltage:'sensor.sunsynk_battery_voltage',
pv_voltage:'sensor.sunsynk_pv1_voltage',temperature:'sensor.sunsynk_inverter_temperature',
frequency:'sensor.sunsynk_grid_frequency',grid_status:'binary_sensor.sunsynk_grid_connected',
daily_solar:'sensor.sunsynk_day_pv_energy',daily_import:'sensor.sunsynk_day_grid_import',
daily_export:'sensor.sunsynk_day_grid_export',daily_load:'sensor.sunsynk_day_load_energy',
daily_charge:'sensor.sunsynk_day_battery_charge',daily_discharge:'sensor.sunsynk_day_battery_discharge',
battery_temperature:'sensor.sunsynk_battery_temperature'}
},
huawei:{
label:'Huawei (FusionSolar)',inverter_name:'HUAWEI',
bat_polarity:'positive',grid_polarity:'positive',
e:{solar:'sensor.inverter_input_power',battery:'sensor.battery_charge_discharge_power',
soc:'sensor.battery_state_of_capacity',grid:'sensor.power_meter_active_power',
load:'sensor.house_consumption_power',grid_voltage:'sensor.grid_voltage',
battery_voltage:'sensor.battery_voltage',pv_voltage:'sensor.pv_voltage',
temperature:'sensor.inverter_internal_temperature',frequency:'sensor.grid_frequency',
grid_status:'',daily_solar:'sensor.daily_yield',daily_import:'sensor.daily_grid_import',
daily_export:'sensor.daily_grid_export',daily_load:'sensor.daily_house_consumption',
daily_charge:'sensor.daily_battery_charge',daily_discharge:'sensor.daily_battery_discharge',
battery_temperature:'sensor.battery_temperature'}
},
fronius:{
label:'Fronius (Gen24)',inverter_name:'FRONIUS',
bat_polarity:'positive',grid_polarity:'positive',
e:{solar:'sensor.fronius_power_photovoltaics',battery:'sensor.fronius_power_battery',
soc:'sensor.fronius_battery_soc',grid:'sensor.fronius_power_grid',
load:'sensor.fronius_power_load',grid_voltage:'sensor.fronius_voltage_ac',
battery_voltage:'sensor.fronius_voltage_battery',pv_voltage:'sensor.fronius_voltage_dc',
temperature:'sensor.fronius_temperature',frequency:'sensor.fronius_frequency',
grid_status:'',daily_solar:'sensor.fronius_energy_day',daily_import:'',
daily_export:'',daily_load:'',daily_charge:'',daily_discharge:'',
battery_temperature:''}
},
growatt:{
label:'Growatt',inverter_name:'GROWATT',
bat_polarity:'negative',grid_polarity:'positive',
e:{solar:'sensor.growatt_pv_power',battery:'sensor.growatt_battery_power',
soc:'sensor.growatt_battery_soc',grid:'sensor.growatt_grid_power',
load:'sensor.growatt_load_power',grid_voltage:'sensor.growatt_grid_voltage',
battery_voltage:'sensor.growatt_battery_voltage',pv_voltage:'sensor.growatt_pv_voltage',
temperature:'sensor.growatt_inverter_temperature',frequency:'sensor.growatt_grid_frequency',
grid_status:'',daily_solar:'sensor.growatt_today_generation',daily_import:'sensor.growatt_today_import',
daily_export:'sensor.growatt_today_export',daily_load:'sensor.growatt_today_load',
daily_charge:'sensor.growatt_today_charge',daily_discharge:'sensor.growatt_today_discharge',
battery_temperature:''}
},
victron:{
label:'Victron (Venus OS)',inverter_name:'VICTRON',
bat_polarity:'positive',grid_polarity:'positive',
e:{solar:'sensor.pv_power',solar2:'',battery:'sensor.battery_power',soc:'sensor.battery_soc',
grid:'sensor.grid_power',load:'sensor.ac_consumption',grid_voltage:'sensor.grid_voltage',
battery_voltage:'sensor.battery_voltage',pv_voltage:'sensor.pv_voltage',pv_voltage2:'',
temperature:'sensor.inverter_temperature',frequency:'sensor.grid_frequency',
grid_status:'',daily_solar:'sensor.daily_solar_yield',daily_import:'sensor.daily_grid_import',
daily_export:'sensor.daily_grid_export',daily_load:'sensor.daily_consumption',
daily_charge:'sensor.daily_battery_charge',daily_discharge:'sensor.daily_battery_discharge',
battery_temperature:'sensor.battery_temperature'}
},
solaredge:{
label:'SolarEdge (Modbus)',inverter_name:'SOLAREDGE',
bat_polarity:'positive',grid_polarity:'negative',
e:{solar:'sensor.solaredge_dc_power',battery:'sensor.solaredge_storage_power',
soc:'sensor.solaredge_storage_level',grid:'sensor.solaredge_m1_ac_power',
load:'sensor.solaredge_ac_power',grid_voltage:'sensor.solaredge_m1_ac_voltage',
battery_voltage:'sensor.solaredge_storage_voltage',pv_voltage:'sensor.solaredge_dc_voltage',
temperature:'sensor.solaredge_temperature',frequency:'sensor.solaredge_m1_ac_frequency',
grid_status:'',daily_solar:'sensor.solaredge_energy_today',daily_import:'',
daily_export:'',daily_load:'',daily_charge:'',daily_discharge:'',
battery_temperature:''}
},
solis:{
label:'Solis (SolisCloud)',inverter_name:'SOLIS',
bat_polarity:'negative',grid_polarity:'positive',
e:{solar:'sensor.solis_pv_power',battery:'sensor.solis_battery_power',soc:'sensor.solis_battery_soc',
grid:'sensor.solis_grid_power',load:'sensor.solis_house_load',grid_voltage:'sensor.solis_grid_voltage',
battery_voltage:'sensor.solis_battery_voltage',pv_voltage:'sensor.solis_pv_voltage',
temperature:'sensor.solis_inverter_temperature',frequency:'sensor.solis_grid_frequency',
grid_status:'',daily_solar:'sensor.solis_today_energy_generation',daily_import:'sensor.solis_today_grid_import',
daily_export:'sensor.solis_today_grid_export',daily_load:'sensor.solis_today_load_consumption',
daily_charge:'sensor.solis_today_battery_charge',daily_discharge:'sensor.solis_today_battery_discharge',
battery_temperature:'sensor.solis_battery_temperature'}
},
custom:{
label:'Custom',inverter_name:'Inverter',
bat_polarity:'negative',grid_polarity:'positive',
e:{solar:'',battery:'',soc:'',grid:'',load:'',grid_voltage:'',battery_voltage:'',
pv_voltage:'',temperature:'',frequency:'',grid_status:'',daily_solar:'',daily_import:'',
daily_export:'',daily_load:'',daily_charge:'',daily_discharge:'',battery_temperature:''}
}
};
const DEFAULTS={
preset:'deye',
...PRESETS.deye.e,
solar2:'',solar3:'',pv_voltage2:'',pv_voltage3:'',sun_entity:'sun.sun',
battery_charge:'',battery_discharge:'',
grid_voltage_l2:'',grid_voltage_l3:'',
bat_polarity:'negative',grid_polarity:'positive',
shutdown_soc:20,battery_capacity:5120,grid_threshold:0,font_size:24,mppt_scale:1,language:'',
inverter_name:'DEYE',
weather_temp:'',weather_humidity:'',weather_entity:'',
price_sensor:'',
ev_power:'',ev_soc:'',daily_ev:'',
extra1_power:'',extra1_name:'',extra1_icon:'appliance',
extra2_power:'',extra2_name:'',extra2_icon:'heatpump',
extra3_power:'',extra3_name:'',extra3_icon:'garage',
import_cost:'',export_cost:'',
compact:false,sparkline_shared_scale:false,
theme:'auto',
animations:'auto',
flow_speed:'proportional',
card_style:'xpower',
energy_frame:true,
arrow_style:'arrow',
power_unit:'auto',
temp_unit:'auto'
};
const ENTITY_FIELDS=[
{key:'solar',label:'Solar Power (MPPT1)'},{key:'solar2',label:'Solar Power (MPPT2)'},{key:'solar3',label:'Solar Power (MPPT3)'},
{key:'battery',label:'Battery Power'},
{key:'battery_charge',label:'Battery Charge Power (split, optional)'},
{key:'battery_discharge',label:'Battery Discharge Power (split, optional)'},
{key:'soc',label:'Battery SOC'},{key:'grid',label:'Grid Power'},
{key:'load',label:'Load Power'},{key:'grid_voltage',label:'Grid Voltage (L1)'},
{key:'grid_voltage_l2',label:'Grid Voltage L2 (optional)'},{key:'grid_voltage_l3',label:'Grid Voltage L3 (optional)'},
{key:'battery_voltage',label:'Battery Voltage'},
{key:'temperature',label:'Inverter Temp.'},{key:'battery_temperature',label:'Battery Temp.'},
{key:'frequency',label:'Grid Frequency'},{key:'grid_status',label:'Grid Status'},
{key:'pv_voltage',label:'PV Voltage (MPPT1)'},{key:'pv_voltage2',label:'PV Voltage (MPPT2)'},{key:'pv_voltage3',label:'PV Voltage (MPPT3)'},{key:'sun_entity',label:'Sun Entity (solar ring)'},
{key:'daily_solar',label:'Daily Solar'},{key:'daily_import',label:'Daily Import'},
{key:'daily_export',label:'Daily Export'},{key:'daily_load',label:'Daily Load'},
{key:'daily_charge',label:'Daily Charge'},{key:'daily_discharge',label:'Daily Discharge'},
{key:'weather_entity',label:'Weather Entity (sun icon)'},{key:'weather_temp',label:'Weather Temp.'},{key:'weather_humidity',label:'Weather Humidity'},
{key:'price_sensor',label:'Electricity Price (optional)'},
{key:'ev_power',label:'EV Charger Power (optional)'},{key:'ev_soc',label:'EV SOC (optional)'},{key:'daily_ev',label:'Daily EV Energy (optional)'},
{key:'extra1_power',label:'Extra Consumer 1 Power (optional)'},
{key:'extra2_power',label:'Extra Consumer 2 Power (optional)'},
{key:'extra3_power',label:'Extra Consumer 3 Power (optional)'},
{key:'import_cost',label:'Daily Import Cost'},{key:'export_cost',label:'Daily Export Earnings'}
];
const ENT_KEYS=ENTITY_FIELDS.map(f=>f.key);
const SUNTICKS=(()=>{let s='';const N=96;const cl=t=>{const m=(a,b)=>Math.round(a+(b-a)*t).toString(16).padStart(2,'0');return '#'+m(255,224)+m(167,83)+m(38,63);};for(let i=0;i<N;i++){const t=i/(N-1),a=(-135+270*t)*Math.PI/180,sn=Math.sin(a),cs=Math.cos(a);s+=`<line class="srtk" stroke="${cl(t)}" x1="${(250+32*sn).toFixed(2)}" y1="${(38-32*cs).toFixed(2)}" x2="${(250+40*sn).toFixed(2)}" y2="${(38-40*cs).toFixed(2)}"/>`;}return s;})();
const MPPT_ROWS=[1,2,3].map(k=>`<g id="mp${k}" class="mpr" style="display:none"><rect x="108" y="-7" width="90" height="14" fill="transparent" pointer-events="all"/><text id="ml${k}" x="112" y="0" class="pvh" style="text-anchor:start"></text><text id="mw${k}" x="166" y="0" class="pvv" style="text-anchor:end;fill:var(--t1);opacity:0.8"></text><text id="mv${k}" x="172" y="0" class="pvu" style="text-anchor:start"></text></g>`).join('');
const XPF_ARROW={chevron:{up:['M-4.5 2.5 L0 -2.5 L4.5 2.5'],down:['M-4.5 -2.5 L0 2.5 L4.5 -2.5'],flat:['M-5 0 L5 0']},arrow:{up:['M-5 5 L4 -4','M-1 -4 L4 -4 L4 1'],down:['M-5 -4 L4 5','M4 0 L4 5 L-1 5'],flat:['M-6 0 L4.5 0','M1.5 -3 L5 0 L1.5 3']}};
const XPF_NUDGE={chevron:{up:[{transform:'translateY(2px)',opacity:0.4},{transform:'translateY(0)',opacity:1}],down:[{transform:'translateY(-2px)',opacity:0.4},{transform:'translateY(0)',opacity:1}]},arrow:{up:[{transform:'translate(-2px,2px)',opacity:0.4},{transform:'translate(0,0)',opacity:1}],down:[{transform:'translate(-2px,-2px)',opacity:0.4},{transform:'translate(0,0)',opacity:1}]}};
const WXW=(x,y,s)=>`<g transform="translate(${x} ${y}) scale(${s})" fill="none" stroke="#64D2FF" stroke-width="1.8" stroke-linecap="round"><path class="wxa wxwi" d="M-14 0h16a4 4 0 1 0-4-4"/><path class="wxa wxwi" style="animation-delay:.6s" d="M-10 6h20a4 4 0 1 1-4 4"/></g>`;
const WXA=i=>`<g transform="translate(250 38) scale(.9) translate(-32 -36)">${i}</g>`;
const WCP='M19 44h27a9 9 0 0 0 0-18a13 13 0 0 0-25-3a10.6 10.6 0 0 0-2 21z';
const WCL=(t,d,c)=>`<g transform="${t||'translate(0 0)'}"><path class="wxa wxcl ${c||'wxc'}" d="${d||WCP}"/></g>`;
const WDR=a=>`<g stroke="#0A84FF" stroke-width="3" stroke-linecap="round">${a.map((p,i)=>`<line class="wxa wxdr" style="animation-delay:${(p[2]!==undefined?p[2]:i*.25).toFixed(2)}s" x1="${p[0]}" y1="${p[1]}" x2="${p[0]-3}" y2="${p[1]+8}"/>`).join('')}</g>`;
const WBO='<polygon class="wxa wxbo" points="33,40 25,52 31,52 28,62 39,48 33,48 36,40" fill="#FFD60A"/>';
const WSN=(x,y,dl)=>`<g class="wxa wxsn" style="animation-delay:${dl}s" stroke="#fff" stroke-width="2" stroke-linecap="round"><line x1="${x}" y1="${y-4.5}" x2="${x}" y2="${y+4.5}"/><line x1="${x-3.9}" y1="${y-2.2}" x2="${x+3.9}" y2="${y+2.2}"/><line x1="${x-3.9}" y1="${y+2.2}" x2="${x+3.9}" y2="${y-2.2}"/></g>`;
const WHA=[[21,51,3,0],[32,54,3,.35],[43,51,3,.7],[27,59,2.3,.55],[38,59,2.3,.15]].map(p=>`<circle class="wxa wxha" style="animation-delay:${p[3]}s" cx="${p[0]}" cy="${p[1]}" r="${p[2]}" fill="#fff" stroke="#64D2FF" stroke-width="1.5"/>`).join('');
const WST=(x,y,r,dl)=>`<path class="wxa wxst" style="animation-delay:${dl}s" fill="#fff" d="M${x} ${y-r}L${x+r*.28} ${y-r*.28}L${x+r} ${y}L${x+r*.28} ${y+r*.28}L${x} ${y+r}L${x-r*.28} ${y+r*.28}L${x-r} ${y}L${x-r*.28} ${y-r*.28}Z"/>`;
const WXK={partlycloudy:'p',cloudy:'c',rainy:'r',pouring:'po',lightning:'l','lightning-rainy':'lr',snowy:'s','snowy-rainy':'sr',hail:'h',fog:'f',windy:'w','windy-variant':'wv',exceptional:'x'};
const WXL={'':[0,0,1,1],p:[-4.5,-12.6,.75,1],w:[-8,-5,.7,1]};
const WXG={'':'',
p:WXA(WCL('','M21 50h25a8.5 8.5 0 0 0 0-17a12 12 0 0 0-23-2.5a9.8 9.8 0 0 0-2 19.5z')),
c:WXA(WCL('translate(8 -6)','M24 34h20a7 7 0 0 0 0-14a10 10 0 0 0-19-2.3a8 8 0 0 0-1 16.3z','wxc2')+WCL('translate(-2 6)')),
r:WXA(WDR([[24,47],[33,47,.5],[42,47,.25]])+WCL()),
po:WXA(WDR([[22,47],[29,47,.5],[36,47,.25],[43,47,.75]])+WCL()),
l:WXA(WBO+WCL()),
lr:WXA(WDR([[21,48],[44,48,.5]])+WBO+WCL()),
s:WXA(WSN(22,53,0)+WSN(33,56,.7)+WSN(43,52,1.4)+WCL()),
sr:WXA(WDR([[23,48],[42,48,.5]])+WSN(31,54,.3)+WCL()),
h:WXA(WHA+WCL()),
f:WXA(WCL('translate(0 -6)')+'<g stroke="#AEAEB2" stroke-width="3" stroke-linecap="round"><line class="wxa wxfg" x1="14" y1="47" x2="50" y2="47"/><line class="wxa wxfg" style="animation-delay:1.5s" x1="20" y1="54" x2="44" y2="54"/></g>'),
w:WXW(254,44,1.3),
wv:WXA(WCL('translate(0 -4)','M24 34h20a7 7 0 0 0 0-14a10 10 0 0 0-19-2.3a8 8 0 0 0-1 16.3z')+'<g fill="none" stroke="#64D2FF" stroke-width="2.6" stroke-linecap="round"><path class="wxa wxwi" d="M10 40h30a5 5 0 1 0-5-5"/><path class="wxa wxwi" style="animation-delay:.6s" d="M14 48h34a5 5 0 1 1-5 5"/></g>'),
x:WXA('<g class="wxa wxpu"><path d="M32 12L55 52H9z" fill="#FF9F0A" stroke="#FF9F0A" stroke-width="5" stroke-linejoin="round"/><rect x="29.5" y="25" width="5" height="15" rx="2.5" fill="#1c1c1e"/><circle cx="32" cy="46" r="3" fill="#1c1c1e"/></g>')};
const WXN=WST(275,15,4.5,0)+WST(284,29,3,1.2);
const FLBL={solar:t=>t.ps+' (MPPT1)',solar2:t=>t.ps+' (MPPT2)',solar3:t=>t.ps+' (MPPT3)',battery:t=>t.pb,battery_charge:t=>t.pbc+' '+t.sp,battery_discharge:t=>t.pbd+' '+t.sp,soc:t=>t.sc,grid:t=>t.pg,load:t=>t.pl,grid_voltage:t=>t.vg+' (L1)',grid_voltage_l2:t=>t.vg+' L2 '+t.op,grid_voltage_l3:t=>t.vg+' L3 '+t.op,battery_voltage:t=>t.vb,temperature:t=>t.ti,battery_temperature:t=>t.tb,frequency:t=>t.fq,grid_status:t=>t.gs,pv_voltage:t=>t.vp+' (MPPT1)',pv_voltage2:t=>t.vp+' (MPPT2)',pv_voltage3:t=>t.vp+' (MPPT3)',sun_entity:t=>(t&&t.sun)||'Sun Entity (solar ring)',daily_solar:t=>t.dso,daily_import:t=>t.dim,daily_export:t=>t.dex,daily_load:t=>t.dlo,daily_charge:t=>t.dch,daily_discharge:t=>t.ddi,weather_entity:t=>(t&&t.wx)||'Weather Entity (sun icon)',weather_temp:t=>t.wt,weather_humidity:t=>t.wh,ev_power:t=>t.evp+' '+t.op,ev_soc:t=>t.evs+' '+t.op,daily_ev:t=>t.evd+' '+t.op,import_cost:t=>t.cim,export_cost:t=>t.cex};
const EDL={"pt":{"wx":"Entidade Meteo (\u00edcone sol)","ps":"Pot\u00eancia Solar","pb":"Pot\u00eancia Bateria","pbc":"Pot\u00eancia Carga Bat.","pbd":"Pot\u00eancia Descarga Bat.","sc":"SOC Bateria","pg":"Pot\u00eancia Rede","pl":"Pot\u00eancia Consumo","vg":"Tens\u00e3o Rede","vb":"Tens\u00e3o Bateria","vp":"Tens\u00e3o PV","sun":"Entidade Sol (anel solar)","ti":"Temp. Inversor","tb":"Temp. Bateria","fq":"Frequ\u00eancia Rede","gs":"Estado Rede","dso":"Solar Di\u00e1rio","dim":"Importa\u00e7\u00e3o Di\u00e1ria","dex":"Exporta\u00e7\u00e3o Di\u00e1ria","dlo":"Consumo Di\u00e1rio","dch":"Carga Di\u00e1ria","ddi":"Descarga Di\u00e1ria","wt":"Temp. Exterior","wh":"Humidade","evp":"Pot\u00eancia Carregador EV","evs":"SOC EV","evd":"Energia EV Di\u00e1ria","cim":"Custo Importa\u00e7\u00e3o Di\u00e1rio","cex":"Ganhos Exporta\u00e7\u00e3o Di\u00e1rios","op":"(opcional)","sp":"(separado, opcional)"},"de":{"wx":"Wetter-Entit\u00e4t (Sonnensymbol)","ps":"Solarleistung","pb":"Batterieleistung","pbc":"Ladeleistung Bat.","pbd":"Entladeleistung Bat.","sc":"Batterie-SOC","pg":"Netzleistung","pl":"Verbrauchsleistung","vg":"Netzspannung","vb":"Batteriespannung","vp":"PV-Spannung","sun":"Sonnen-Entit\u00e4t (Ring)","ti":"WR-Temp.","tb":"Bat.-Temp.","fq":"Netzfrequenz","gs":"Netzstatus","dso":"Tagesertrag Solar","dim":"Tagesimport","dex":"Tagesexport","dlo":"Tagesverbrauch","dch":"Tagesladung","ddi":"Tagesentladung","wt":"Au\u00dfentemp.","wh":"Luftfeuchte","evp":"EV-Ladeleistung","evs":"EV-SOC","evd":"EV-Tagesenergie","cim":"T\u00e4gl. Importkosten","cex":"T\u00e4gl. Exporterl\u00f6s","op":"(optional)","sp":"(getrennt, optional)"},"fr":{"wx":"Entit\u00e9 m\u00e9t\u00e9o (ic\u00f4ne soleil)","ps":"Puissance solaire","pb":"Puissance batterie","pbc":"Puissance charge bat.","pbd":"Puissance d\u00e9charge bat.","sc":"SOC batterie","pg":"Puissance r\u00e9seau","pl":"Puissance conso.","vg":"Tension r\u00e9seau","vb":"Tension batterie","vp":"Tension PV","sun":"Entit\u00e9 soleil (anneau)","ti":"Temp. onduleur","tb":"Temp. batterie","fq":"Fr\u00e9quence r\u00e9seau","gs":"\u00c9tat r\u00e9seau","dso":"Solaire journalier","dim":"Import journalier","dex":"Export journalier","dlo":"Conso. journali\u00e8re","dch":"Charge journali\u00e8re","ddi":"D\u00e9charge journali\u00e8re","wt":"Temp. ext.","wh":"Humidit\u00e9","evp":"Puissance chargeur VE","evs":"SOC VE","evd":"\u00c9nergie VE journali\u00e8re","cim":"Co\u00fbt import journalier","cex":"Gains export journaliers","op":"(optionnel)","sp":"(s\u00e9par\u00e9, optionnel)"},"es":{"wx":"Entidad tiempo (icono sol)","ps":"Potencia solar","pb":"Potencia bater\u00eda","pbc":"Potencia carga bat.","pbd":"Potencia descarga bat.","sc":"SOC bater\u00eda","pg":"Potencia red","pl":"Potencia consumo","vg":"Tensi\u00f3n red","vb":"Tensi\u00f3n bater\u00eda","vp":"Tensi\u00f3n PV","sun":"Entidad sol (anillo)","ti":"Temp. inversor","tb":"Temp. bater\u00eda","fq":"Frecuencia red","gs":"Estado red","dso":"Solar diario","dim":"Importaci\u00f3n diaria","dex":"Exportaci\u00f3n diaria","dlo":"Consumo diario","dch":"Carga diaria","ddi":"Descarga diaria","wt":"Temp. exterior","wh":"Humedad","evp":"Potencia cargador VE","evs":"SOC VE","evd":"Energ\u00eda VE diaria","cim":"Coste importaci\u00f3n diario","cex":"Ganancias exportaci\u00f3n diarias","op":"(opcional)","sp":"(separado, opcional)"},"it":{"wx":"Entit\u00e0 meteo (icona sole)","ps":"Potenza solare","pb":"Potenza batteria","pbc":"Potenza carica bat.","pbd":"Potenza scarica bat.","sc":"SOC batteria","pg":"Potenza rete","pl":"Potenza consumo","vg":"Tensione rete","vb":"Tensione batteria","vp":"Tensione PV","sun":"Entit\u00e0 sole (anello)","ti":"Temp. inverter","tb":"Temp. batteria","fq":"Frequenza rete","gs":"Stato rete","dso":"Solare giornaliero","dim":"Import giornaliero","dex":"Export giornaliero","dlo":"Consumo giornaliero","dch":"Carica giornaliera","ddi":"Scarica giornaliera","wt":"Temp. esterna","wh":"Umidit\u00e0","evp":"Potenza caricatore EV","evs":"SOC EV","evd":"Energia EV giornaliera","cim":"Costo import giornaliero","cex":"Guadagni export giornalieri","op":"(opzionale)","sp":"(separato, opzionale)"},"nl":{"wx":"Weer-entiteit (zonicoon)","ps":"Zonnevermogen","pb":"Batterijvermogen","pbc":"Laadvermogen bat.","pbd":"Ontlaadvermogen bat.","sc":"Batterij-SOC","pg":"Netvermogen","pl":"Verbruiksvermogen","vg":"Netspanning","vb":"Batterijspanning","vp":"PV-spanning","sun":"Zon-entiteit (ring)","ti":"Omvormer temp.","tb":"Batterij temp.","fq":"Netfrequentie","gs":"Netstatus","dso":"Dagelijks zonne","dim":"Dagelijkse import","dex":"Dagelijkse export","dlo":"Dagelijks verbruik","dch":"Dagelijkse lading","ddi":"Dagelijkse ontlading","wt":"Buitentemp.","wh":"Vochtigheid","evp":"EV-laadvermogen","evs":"EV-SOC","evd":"Dagelijkse EV-energie","cim":"Dagelijkse importkosten","cex":"Dagelijkse exportopbrengst","op":"(optioneel)","sp":"(gescheiden, optioneel)"},"pl":{"wx":"Encja pogody (ikona s\u0142o\u0144ca)","ps":"Moc PV","pb":"Moc baterii","pbc":"Moc \u0142adowania bat.","pbd":"Moc roz\u0142adowania bat.","sc":"SOC baterii","pg":"Moc sieci","pl":"Moc obci\u0105\u017cenia","vg":"Napi\u0119cie sieci","vb":"Napi\u0119cie baterii","vp":"Napi\u0119cie PV","sun":"Encja s\u0142o\u0144ca (pier\u015bcie\u0144)","ti":"Temp. falownika","tb":"Temp. baterii","fq":"Cz\u0119stotliwo\u015b\u0107 sieci","gs":"Stan sieci","dso":"Dzienny solar","dim":"Dzienny import","dex":"Dzienny eksport","dlo":"Dzienne zu\u017cycie","dch":"Dzienne \u0142adowanie","ddi":"Dzienne roz\u0142adowanie","wt":"Temp. zewn.","wh":"Wilgotno\u015b\u0107","evp":"Moc \u0142adowarki EV","evs":"SOC EV","evd":"Dzienna energia EV","cim":"Dzienny koszt importu","cex":"Dzienny zysk z eksportu","op":"(opcjonalnie)","sp":"(osobno, opcjonalnie)"}};
const _lk=(l,h)=>{const k=l||String(h?.locale?.language||h?.language||'en').toLowerCase().slice(0,2);return LANG[k]?k:'en';};
const HIST_POINTS=48;
const HIST_INTERVAL=5*60*1000;
const RUNTIME_MIN_W=50;
const ANIM_MAX_W=6000;
const ANIM_MIN_SPD=1.5;
const ANIM_MAX_SPD=3.5;
class XPowerFlowCardEditor extends HTMLElement{
constructor(){super();this._config={};this._hass=null;this._onchange=this._fire.bind(this);}
_esc(s){return String(s??'').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"').replace(/'/g,''');}
setConfig(config){const merged={...DEFAULTS,...config};if(config.energy_frame===undefined&&config.autarky_frame!==undefined)merged.energy_frame=config.autarky_frame;delete merged.autarky_frame;if(this._ready&&JSON.stringify(merged)===JSON.stringify(this._config))return;this._config=merged;this._render();this._ready=true;}
set hass(hass){const first=!this._hass;this._hass=hass;if(first&&this._ready&&!this._config.language)this._render();}
disconnectedCallback(){const el=this.querySelector('.editor');if(el)el.removeEventListener('change',this._onchange);}
_fire(e){
if(!e.target.matches('input,select'))return;
const cfg={...this._config};const oldLang=this._config.language;
cfg.language=this.querySelector('#ed-lang').value;
cfg.inverter_name=this.querySelector('#ed-inv').value;
cfg.extra1_name=this.querySelector('#ed-ex1name').value;
cfg.extra1_icon=this.querySelector('#ed-ex1icon').value;
cfg.extra2_name=this.querySelector('#ed-ex2name').value;
cfg.extra2_icon=this.querySelector('#ed-ex2icon').value;
cfg.extra3_name=this.querySelector('#ed-ex3name').value;
cfg.extra3_icon=this.querySelector('#ed-ex3icon').value;
cfg.theme=this.querySelector('#ed-theme').value;
cfg.compact=this.querySelector('#ed-compact').value==='true';
cfg.sparkline_shared_scale=this.querySelector('#ed-sshare').value==='true';
cfg.animations=this.querySelector('#ed-anim').value;
cfg.flow_speed=this.querySelector('#ed-fspd').value;
cfg.card_style=this.querySelector('#ed-cstyle').value;
cfg.energy_frame=this.querySelector('#ed-aframe').value!=='false';delete cfg.autarky_frame;
cfg.power_unit=this.querySelector('#ed-punit').value;
cfg.temp_unit=this.querySelector('#ed-tunit').value;
const gthVal=parseInt(this.querySelector('#ed-gth').value,10);
cfg.grid_threshold=isNaN(gthVal)?0:gthVal;
const fsVal=parseInt(this.querySelector('#ed-fsize').value,10);
cfg.font_size=isNaN(fsVal)?24:fsVal;
const mpsVal=parseFloat(this.querySelector('#ed-mps').value);
cfg.mppt_scale=isNaN(mpsVal)?1:Math.min(1.6,Math.max(0.8,mpsVal));
cfg.bat_polarity=this.querySelector('#ed-bpol').value;
cfg.grid_polarity=this.querySelector('#ed-gpol').value;
const socVal=parseInt(this.querySelector('#ed-ssoc').value,10);
cfg.shutdown_soc=isNaN(socVal)?20:socVal;
const capVal=parseInt(this.querySelector('#ed-cap').value,10);
cfg.battery_capacity=isNaN(capVal)?5120:capVal;
const newPreset=this.querySelector('#ed-preset').value;
if(newPreset!==cfg.preset&&PRESETS[newPreset]){
const p=PRESETS[newPreset];
Object.keys(p.e).forEach(k=>{cfg[k]=p.e[k];});
cfg.bat_polarity=p.bat_polarity;
cfg.grid_polarity=p.grid_polarity;
cfg.inverter_name=p.inverter_name;
cfg.preset=newPreset;
this._config=cfg;
this.dispatchEvent(new CustomEvent('config-changed',{detail:{config:cfg},bubbles:true,composed:true}));
this._render();
return;
}
cfg.preset=newPreset;
ENTITY_FIELDS.forEach(f=>{const v=this.querySelector('#ed-'+f.key);if(v){cfg[f.key]=v.value;const bad=v.value&&this._hass&&!this._hass.states[v.value];v.style.borderColor=bad?'#EF5350':'';}});
this._config=cfg;
this.dispatchEvent(new CustomEvent('config-changed',{detail:{config:cfg},bubbles:true,composed:true}));
if(cfg.language!==oldLang)this._render();
}
async _autoDetect(){
if(!this._hass)return;
const cfg={...this._config};const st=this._hass.states;
try{
const prefs=await this._hass.callWS({type:'energy/get_prefs'});
for(const s of (prefs.energy_sources||[])){
if(s.type==='solar'&&s.stat_energy_from&&st[s.stat_energy_from]&&!cfg.daily_solar)cfg.daily_solar=s.stat_energy_from;
if(s.type==='grid'){
const f=(s.flow_from||[])[0],t=(s.flow_to||[])[0];
if(f&&f.stat_energy_from&&st[f.stat_energy_from]&&!cfg.daily_import)cfg.daily_import=f.stat_energy_from;
if(t&&t.stat_energy_to&&st[t.stat_energy_to]&&!cfg.daily_export)cfg.daily_export=t.stat_energy_to;
}
if(s.type==='battery'){
if(s.stat_energy_to&&st[s.stat_energy_to]&&!cfg.daily_charge)cfg.daily_charge=s.stat_energy_to;
if(s.stat_energy_from&&st[s.stat_energy_from]&&!cfg.daily_discharge)cfg.daily_discharge=s.stat_energy_from;
}
}
}catch(e){}
const pow=Object.keys(st).filter(id=>id.startsWith('sensor.')&&st[id].attributes?.device_class==='power');
const pick=re=>pow.find(id=>re.test(id))||'';
if(!cfg.solar)cfg.solar=pick(/pv1?_power|solar_power|photovolta/);
if(!cfg.battery&&!cfg.battery_charge)cfg.battery=pick(/battery_power|storage_power/);
if(!cfg.grid)cfg.grid=pick(/grid_power|ct1?_power|meter_active_power/);
if(!cfg.load)cfg.load=pick(/load_power|house_consumption|essential_power|ac_consumption/);
if(!cfg.soc){const socs=Object.keys(st).filter(id=>id.startsWith('sensor.')&&st[id].attributes?.device_class==='battery');cfg.soc=socs.find(id=>/battery|soc/.test(id))||'';}
this._config=cfg;
this.dispatchEvent(new CustomEvent('config-changed',{detail:{config:cfg},bubbles:true,composed:true}));
this._render();
}
_render(){
const el=this.querySelector('.editor');if(el)el.removeEventListener('change',this._onchange);
const lk=_lk(this._config.language,this._hass);
const L=LANG[lk];
const c=this._config;
const T=EDL[lk];const lbl=f=>T&&FLBL[f.key]?FLBL[f.key](T):f.label;
const selStyle='padding:8px;border:1px solid var(--divider-color);border-radius:4px;background:var(--card-background-color);color:var(--primary-text-color);width:100%;font-size:14px';
this.innerHTML=`
<style>
.editor{font-family:-apple-system,sans-serif;padding:16px}
.editor h3{margin:0 0 12px;font-size:14px;font-weight:600;color:var(--primary-text-color);border-bottom:1px solid var(--divider-color);padding-bottom:8px}
.editor h4{margin:16px 0 8px;font-size:12px;font-weight:600;color:var(--secondary-text-color);text-transform:uppercase;letter-spacing:0.05em}
.field{margin-bottom:8px}
.field label{display:block;font-size:12px;color:var(--secondary-text-color);margin-bottom:4px}
.field input,.field select{width:100%;padding:8px;border:1px solid var(--divider-color,rgba(255,255,255,0.15));border-radius:4px;background:var(--card-background-color,#1c1e21);color:var(--primary-text-color);font-size:14px;box-sizing:border-box;min-height:38px}
.row{display:flex;gap:12px}
.row .field{flex:1}
.grid{display:grid;grid-template-columns:1fr 1fr;gap:8px}
.hint{font-size:10px;color:var(--secondary-text-color);opacity:0.6;margin-top:2px}
</style>
<div class="editor">
<h3>${L.editor_title} <span style="font-size:10px;opacity:0.4">v${V}</span></h3>
<div class="row">
<div class="field">
<label>${L.editor_preset}</label>
<select id="ed-preset" style="${selStyle}">
${Object.keys(PRESETS).map(k=>`<option value="${k}" ${c.preset===k?'selected':''}>${PRESETS[k].label}</option>`).join('')}
</select>
</div>
<div class="field">
<label>${L.editor_lang}</label>
<select id="ed-lang" style="${selStyle}">
<option value="" ${!c.language?'selected':''}>Auto (Home Assistant)</option>
<option value="pt" ${c.language==='pt'?'selected':''}>Portugu\u00EAs</option>
<option value="en" ${c.language==='en'?'selected':''}>English</option>
<option value="de" ${c.language==='de'?'selected':''}>Deutsch</option>
<option value="fr" ${c.language==='fr'?'selected':''}>Fran\u00E7ais</option>
<option value="es" ${c.language==='es'?'selected':''}>Espa\u00F1ol</option>
<option value="it" ${c.language==='it'?'selected':''}>Italiano</option>
<option value="nl" ${c.language==='nl'?'selected':''}>Nederlands</option>
<option value="pl" ${c.language==='pl'?'selected':''}>Polski</option>
<option value="sv" ${c.language==='sv'?'selected':''}>Svenska</option>
</select>
</div>
</div>
<div class="row">
<div class="field">
<label>${L.editor_inverter_name}</label>
<input type="text" id="ed-inv" value="${this._esc(c.inverter_name)}">
</div>
<div class="field">
<label>Theme</label>
<select id="ed-theme" style="${selStyle}">
<option value="auto" ${c.theme==='auto'?'selected':''}>Auto</option>
<option value="dark" ${c.theme==='dark'?'selected':''}>Dark</option>
<option value="light" ${c.theme==='light'?'selected':''}>Light</option>
</select>
</div>
<div class="field">
<label>Card style</label>
<select id="ed-cstyle" style="${selStyle}">
<option value="xpower" ${c.card_style!=='ha'?'selected':''}>xPower</option>
<option value="ha" ${c.card_style==='ha'?'selected':''}>Home Assistant</option>
</select>
</div>
<div class="field">
<label>Energy source frame</label>
<select id="ed-aframe" style="${selStyle}">
<option value="true" ${c.energy_frame!==false?'selected':''}>On</option>
<option value="false" ${c.energy_frame===false?'selected':''}>Off</option>
</select>
</div>
<div class="field">
<label>Layout</label>
<select id="ed-compact" style="${selStyle}">
<option value="false" ${!c.compact?'selected':''}>Full</option>
<option value="true" ${c.compact?'selected':''}>Compact</option>
</select>
</div>
</div>
<h4>${L.editor_polarity}</h4>
<div class="row">
<div class="field">
<label>${L.editor_bat_pol}</label>
<select id="ed-bpol" style="${selStyle}">
<option value="negative" ${c.bat_polarity==='negative'?'selected':''}>${L.charging} (Deye, Sunsynk, Growatt)</option>
<option value="positive" ${c.bat_polarity==='positive'?'selected':''}>${L.charging} (Huawei, Fronius, SolarEdge, Victron, Solis)</option>
</select>
</div>
<div class="field">
<label>${L.editor_grid_pol}</label>
<select id="ed-gpol" style="${selStyle}">
<option value="positive" ${c.grid_polarity==='positive'?'selected':''}>${L.importing} (Deye, Huawei, Growatt)</option>
<option value="negative" ${c.grid_polarity==='negative'?'selected':''}>${L.exporting} (SolarEdge)</option>
</select>
</div>
</div>
<h4>${L.editor_options}</h4>
<div class="row">
<div class="field">
<label>${L.editor_soc}</label>
<input type="number" id="ed-ssoc" min="0" max="100" value="${c.shutdown_soc??20}">
</div>
<div class="field">
<label>${L.editor_capacity}</label>
<input type="number" id="ed-cap" min="0" value="${c.battery_capacity??5120}">
</div>
<div class="field">
<label>Grid min. (W)</label>
<input type="number" id="ed-gth" min="0" value="${c.grid_threshold??0}">
</div>
<div class="field">
<label>Animations</label>
<select id="ed-anim" style="${selStyle}">
<option value="auto" ${c.animations!=='always'?'selected':''}>Auto</option>
<option value="always" ${c.animations==='always'?'selected':''}>Always</option>
</select>
</div>
<div class="field">
<label>Flow speed</label>
<select id="ed-fspd" style="${selStyle}">
<option value="proportional" ${c.flow_speed!=='synced'?'selected':''}>Proportional to power</option>
<option value="synced" ${c.flow_speed==='synced'?'selected':''}>Synced</option>
</select>
</div>
<div class="field">
<label>Power unit</label>
<select id="ed-punit" style="${selStyle}">
<option value="auto" ${(c.power_unit||'auto')==='auto'?'selected':''}>Auto</option>
<option value="W" ${c.power_unit==='W'?'selected':''}>W</option>
<option value="kW" ${c.power_unit==='kW'?'selected':''}>kW</option>
</select>
</div>
<div class="field">
<label>Temp. unit</label>
<select id="ed-tunit" style="${selStyle}">
<option value="auto" ${(c.temp_unit||'auto')==='auto'?'selected':''}>Auto</option>
<option value="C" ${c.temp_unit==='C'?'selected':''}>°C</option>
<option value="F" ${c.temp_unit==='F'?'selected':''}>°F</option>
</select>
</div>
<div class="field">
<label>Sparkline scale</label>
<select id="ed-sshare" style="${selStyle}">
<option value="false" ${!c.sparkline_shared_scale?'selected':''}>Independent</option>
<option value="true" ${c.sparkline_shared_scale?'selected':''}>Shared</option>
</select>
</div>
<div class="field">
<label>Values font (px)</label>
<input type="number" id="ed-fsize" min="10" max="48" value="${c.font_size??24}">
</div>
</div>
<div class="row">
<div class="field">
<label>MPPT text scale</label>
<input type="number" id="ed-mps" min="0.8" max="1.6" step="0.1" value="${c.mppt_scale??1}">
</div>
</div>
<div class="row">
<div class="field">
<label>Extra Consumer 1: Name</label>
<input type="text" id="ed-ex1name" value="${this._esc(c.extra1_name)}" placeholder="Auto">
</div>
<div class="field">
<label>Extra Consumer 1: Icon</label>
<select id="ed-ex1icon" style="${selStyle}">
<option value="appliance" ${c.extra1_icon==='appliance'?'selected':''}>Appliances</option>
<option value="heatpump" ${c.extra1_icon==='heatpump'?'selected':''}>Heat Pump</option>
<option value="garage" ${c.extra1_icon==='garage'?'selected':''}>Garage/Shed</option>
<option value="generic" ${c.extra1_icon==='generic'?'selected':''}>Generic</option>
</select>
</div>
</div>
<div class="row">
<div class="field">
<label>Extra Consumer 2: Name</label>
<input type="text" id="ed-ex2name" value="${this._esc(c.extra2_name)}" placeholder="Auto">
</div>
<div class="field">
<label>Extra Consumer 2: Icon</label>
<select id="ed-ex2icon" style="${selStyle}">
<option value="appliance" ${c.extra2_icon==='appliance'?'selected':''}>Appliances</option>
<option value="heatpump" ${c.extra2_icon==='heatpump'?'selected':''}>Heat Pump</option>
<option value="garage" ${c.extra2_icon==='garage'?'selected':''}>Garage/Shed</option>
<option value="generic" ${c.extra2_icon==='generic'?'selected':''}>Generic</option>
</select>
</div>
</div>
<div class="row">
<div class="field">
<label>Extra Consumer 3: Name</label>
<input type="text" id="ed-ex3name" value="${this._esc(c.extra3_name)}" placeholder="Auto">
</div>
<div class="field">
<label>Extra Consumer 3: Icon</label>
<select id="ed-ex3icon" style="${selStyle}">
<option value="appliance" ${c.extra3_icon==='appliance'?'selected':''}>Appliances</option>
<option value="heatpump" ${c.extra3_icon==='heatpump'?'selected':''}>Heat Pump</option>
<option value="garage" ${c.extra3_icon==='garage'?'selected':''}>Garage/Shed</option>
<option value="generic" ${c.extra3_icon==='generic'?'selected':''}>Generic</option>
</select>
</div>
</div>
<h4>${L.editor_entities} <button id="ed-auto" type="button" style="float:right;margin-top:-4px;padding:4px 10px;font-size:11px;border:1px solid var(--divider-color,rgba(255,255,255,0.15));border-radius:4px;background:var(--card-background-color,#1c1e21);color:var(--primary-text-color);cursor:pointer">\u26A1 ${L.autodetect}</button></h4>
<div class="grid">
${ENTITY_FIELDS.map(f=>{const v=c[f.key];const bad=v&&this._hass&&!this._hass.states[v];return `
<div class="field">
<label>${lbl(f)}</label>
<input type="text" id="ed-${f.key}" value="${this._esc(v)}" placeholder="${this._esc(PRESETS[c.preset||'deye']?.e[f.key])}"${bad?' style="border-color:#EF5350"':''}>
${bad?'<div class="hint" style="color:#EF5350;opacity:1">\u26A0 '+L.invalid+'</div>':''}
</div>
`;}).join('')}
</div>
</div>`;
this.querySelector('.editor').addEventListener('change',this._onchange);
const ab=this.querySelector('#ed-auto');if(ab)ab.addEventListener('click',()=>this._autoDetect());
}
}
customElements.define('xpower-flow-card-editor',XPowerFlowCardEditor);
class XPowerFlowCard extends HTMLElement{
constructor(){super();this.attachShadow({mode:'open'});this._c={};this._h=null;this._prev={solar:0,bat:0,grid:0,load:0};this._hist={solar:[],load:[],grid:[],battery:[]};this._histMax={solar:1,load:1,grid:1,battery:1};this._fs={};this._histTimer=null;this._histLastLoad=0;this._histLoading=false;this._syncSpd=0;this._resync=false;this._rafId=null;this._twv={};this._twq={};this._twRaf=null;this._entList=[];this._srKey='';this._rm=!!(window.matchMedia&&window.matchMedia('(prefers-reduced-motion: reduce)').matches);this._mq=window.matchMedia?window.matchMedia('(prefers-color-scheme: light)'):null;this._onMq=()=>this._applyTheme();this._mwc=new Map();this._io=null;this._ro=null;this._ringKey='';this._auT=null;this._srT=null;this._vis=true;this._srTk=null;this._srLit=-1;this._onVis=()=>{if(!document.hidden&&this._h){this._histLastLoad=0;this._schedule();}};}
static getConfigElement(){return document.createElement('xpower-flow-card-editor');}
static getStubConfig(){return{...DEFAULTS};}
setConfig(c){
this._c={};
Object.keys(DEFAULTS).forEach(k=>{this._c[k]=c[k]!==undefined?c[k]:DEFAULTS[k];});
if(c.energy_frame===undefined&&c.autarky_frame!==undefined)this._c.energy_frame=c.autarky_frame;
// Backward compatibility: migrate old hardcoded keys to the generic slot system
if(!c.extra1_power&&c.appliance_power){this._c.extra1_power=c.appliance_power;this._c.extra1_name=c.appliance_name||'';this._c.extra1_icon='appliance';}
if(!c.extra2_power&&c.heatpump_power){this._c.extra2_power=c.heatpump_power;this._c.extra2_name=c.heatpump_name||'';this._c.extra2_icon='heatpump';}
if(!c.extra3_power&&c.garage_power){this._c.extra3_power=c.garage_power;this._c.extra3_name=c.garage_name||'';this._c.extra3_icon='garage';}
this._lk=_lk(this._c.language,this._h);this._lang=LANG[this._lk];
this._entList=[...new Set(ENT_KEYS.map(k=>this._c[k]).filter(Boolean))];
this._render();
if(this._h){this._applyTheme();this._schedule();}
}
connectedCallback(){
document.addEventListener('visibilitychange',this._onVis);
if(this._mq&&this._mq.addEventListener)this._mq.addEventListener('change',this._onMq);
if(!this._c.compact&&!this._histTimer)this._histTimer=setInterval(()=>{if(this._h&&!document.hidden&&this._vis)this._loadHistory();},HIST_INTERVAL);
// Pause animations + skip updates while scrolled out of view
if(!this._io&&'IntersectionObserver' in window){this._io=new IntersectionObserver(en=>{const v=en[en.length-1].isIntersecting;if(v===this._vis)return;this._vis=v;this.classList.toggle('off',!v);if(v&&this._h)this._schedule();},{threshold:0});this._io.observe(this);}
if(!this._ro&&'ResizeObserver' in window){this._ro=new ResizeObserver(()=>this._ringPath());const cd=this._$('xcard');if(cd)this._ro.observe(cd);}
this._ringKey='';this._ringPath();
}
disconnectedCallback(){
clearTimeout(this._auT);clearTimeout(this._srT);
if(this._ro){this._ro.disconnect();this._ro=null;}
document.removeEventListener('visibilitychange',this._onVis);
if(this._mq&&this._mq.removeEventListener)this._mq.removeEventListener('change',this._onMq);
if(this._io){this._io.disconnect();this._io=null;}this._vis=true;this.classList.remove('off');
if(this._histTimer){clearInterval(this._histTimer);this._histTimer=null;}
if(this._rafId){cancelAnimationFrame(this._rafId);this._rafId=null;}if(this._twRaf){cancelAnimationFrame(this._twRaf);this._twRaf=null;}this._twq={};
}
set hass(h){
const prev=this._h;
this._h=h;
if(!this._c.language){const k=_lk('',h);if(k!==this._lk){this._lk=k;this._lang=LANG[k];this._render();this._applyTheme();this._prev={solar:0,bat:0,grid:0,load:0};this._twv={};this._fs={};this._syncSpd=0;this._schedule();}}
if(!prev||h.themes!==prev.themes)this._applyTheme();
if(document.hidden||!this._vis)return;
const now=Date.now();
if(!this._c.compact&&now-this._histLastLoad>HIST_INTERVAL){this._histLastLoad=now;this._loadHistory();}
if(this._dirty(prev,h))this._schedule();
}
_applyTheme(){
const h=this._h;if(!h)return;
const t=this._c.theme||'auto';let light;
if(t==='auto'){const dm=h.themes?.darkMode;light=dm!==undefined?dm===false:!!(this._mq&&this._mq.matches);}
else light=t==='light';
this.classList.toggle('light',light);
this.classList.toggle('compact',!!this._c.compact);
this.classList.toggle('hast',this._c.card_style==='ha');
this.classList.toggle('noaur',this._c.energy_frame===false);
this.classList.toggle('rm',this._rm&&this._c.animations!=='always');
this._ringKey='';this._ringPath();
}
// Memoised DOM writes — skip when the value is unchanged
_sa(el,a,v){if(!el)return;const m=el.__xa||(el.__xa={});if(m[a]===v)return;m[a]=v;el.setAttribute(a,v);}
_st(el,v){if(!el||el.__xt===v)return;el.__xt=v;el.textContent=v;}
_ss(el,p,v){if(!el)return;const m=el.__xs||(el.__xs={});if(m[p]===v)return;m[p]=v;el.style[p]=v;}
_sc(el,cls){if(!el||el.__xc===cls)return;el.__xc=cls;if(cls)el.setAttribute('class',cls);else el.removeAttribute('class');}
_schedule(){if(!this._rafId)this._rafId=requestAnimationFrame(()=>{this._rafId=null;this._update();});}
_dirty(o,n){
if(!o)return true;
if(o===n)return false;
const os=o.states,ns=n.states;for(const e of this._entList){if(os[e]!==ns[e])return true;}
return false;
}
_gv(e){
if(!e||!this._h||!this._h.states[e])return null;
const s=this._h.states[e].state;
if(s==='unavailable'||s==='unknown')return null;
const v=parseFloat(s);
return isNaN(v)?null:v;
}
_gp(e){const v=this._gv(e);if(v===null)return null;const m=this._c.power_unit||'auto';if(m==='W')return v;if(m==='kW')return v*1000;const u=(this._h.states[e].attributes?.unit_of_measurement||'').toLowerCase();return u==='kw'?v*1000:u==='mw'?v*1000000:v;}
_gs(e){return e&&this._h&&this._h.states[e]?this._h.states[e].state:'';}
_temp(e){const v=this._gv(e);if(v===null)return null;const st=this._h.states[e];return this._tfmt(v,st&&st.attributes&&st.attributes.unit_of_measurement);}
_tfmt(v,su0){const su=(su0||'').toUpperCase();const srcF=su.indexOf('F')>-1;const u=this._c.temp_unit||'auto';let val,unit;if(u==='F'){val=srcF?v:v*9/5+32;unit='\u00B0F';}else if(u==='C'){val=srcF?(v-32)*5/9:v;unit='\u00B0C';}else{val=v;unit=srcF?'\u00B0F':'\u00B0C';}return val.toFixed(0)+unit;}
_fmt(v){if(v===null)return this._lang.unavailable;const a=Math.abs(v);return a>=1000?(a/1000).toFixed(1)+' kW':a.toFixed(0)+' W';}
_wx(c){const g=this._$('wxG'),b=this._$('wxBody');if(!g||!b)return;const H=this._h&&this._h.states;if(!H)return;const ws=c.weather_entity?H[c.weather_entity]:null;let s=ws?ws.state:'';if(s==='unavailable'||s==='unknown')s='';const sn=H[c.sun_entity||'sun.sun'];const night=!!s&&(s==='clear-night'||(sn&&sn.state==='below_horizon'));const k=s?(WXK[s]||''):'';const key=k+'|'+night;if(key===this._wxKey)return;this._wxKey=key;this._wxOn=!!s;
const L=WXL[k];if(L){this._sa(b,'transform',`translate(${L[0]} ${L[1]}) translate(250 38) scale(${L[2]}) translate(-250 -38)`);this._ss(b,'opacity',String(L[3]));}else{this._ss(b,'opacity','0');}
this._ss(this._$('sunBody'),'display',night?'none':'');this._ss(this._$('moonBody'),'display',night?'':'none');g.innerHTML=WXG[k]+(night&&k===''?WXN:'');}
_sunRing(c){const g=this._$('sunRing');if(!g)return;const eid=c.sun_entity||'sun.sun';const st=this._h&&this._h.states?this._h.states[eid]:null;
if(!st||st.state!=='above_horizon'||!st.attributes||!st.attributes.next_rising||!st.attributes.next_setting){this._ss(g,'display','none');return;}
const now=Date.now();const key=st.attributes.next_setting+'|'+st.attributes.next_rising+'|'+Math.floor(now/60000);if(key===this._srKey)return;this._srKey=key;
const set=new Date(st.attributes.next_setting).getTime();
const rise=new Date(st.attributes.next_rising).getTime()-86400000;
if(!(set>rise)){this._ss(g,'display','none');return;}
const p=Math.min(1,Math.max(0,(now-rise)/(set-rise)));
this._ss(g,'display','');
const tk=this._srTk||(this._srTk=g.querySelectorAll('.srtk')),n=tk.length,lit=Math.round(p*n);
if(lit!==this._srLit){this._srLit=lit;for(let i=0;i<n;i++)tk[i].style.opacity=i<lit?'1':'0.2';}
const tf=t=>{const dt=new Date(t);return String(dt.getHours()).padStart(2,'0')+':'+String(dt.getMinutes()).padStart(2,'0');};
const sr=this._$('srx'),ss=this._$('ssx');
this._st(sr,tf(rise));this._st(ss,tf(set));}
_fmtE(v){if(v===null)return this._lang.unavailable;return v.toFixed(1)+' kWh';}
_fmtC(e){if(!e||!this._h||!this._h.states[e])return '';const s=this._h.states[e];const v=parseFloat(s.state);if(isNaN(v))return '';const u=s.attributes?.unit_of_measurement||'€';return u+v.toFixed(2);}
_arrow(c,p){if(c===null)return '';const d=Math.abs(c)-Math.abs(p);return d>5?'\u25B4 ':d<-5?'\u25BE ':'\u25B8 ';}
_mw(s){let w=this._mwc.get(s);if(w!==undefined)return w;const m=this._$('meas');if(!m)return 0;m.textContent=s;w=m.getComputedTextLength()||0;if(w){if(this._mwc.size>2000)this._mwc.clear();this._mwc.set(s,w);}return w;}
_arw(id,cur,prev,col){const g=this._$(id);if(!g)return;if(cur===null){this._ss(g,'display','none');this._twv['_'+id]=null;return;}this._ss(g,'display','');const vx=+g.dataset.vx,vy=+g.dataset.vy;const w=this._mw(this._fmt(cur));this._sa(g,'transform','translate('+(vx-w/2-11).toFixed(1)+','+vy+')');const inner=g.firstElementChild,p1=inner.children[0],p2=inner.children[1];this._sa(inner,'opacity',Math.abs(cur)<10?'0.28':'1');const pv=prev==null?cur:prev,d=Math.abs(cur)-Math.abs(pv),dir=d>5?'up':d<-5?'down':'flat';const style=this._c.arrow_style==='arrow'?'arrow':'chevron';const G=XPF_ARROW[style][dir];this._sa(p1,'d',G[0]);this._sa(p1,'stroke',col);if(G[1]){this._ss(p2,'display','');this._sa(p2,'d',G[1]);this._sa(p2,'stroke',col);}else{this._ss(p2,'display','none');}const last=this._twv['_'+id];const changed=last===undefined||last===null||Math.abs(cur-last)>=1;this._twv['_'+id]=cur;if(changed&&dir!=='flat'&&!(this._rm&&this._c.animations!=='always')){const kf=XPF_NUDGE[style][dir];if(kf){try{inner.animate(kf,{duration:480,easing:'cubic-bezier(.3,0,.2,1)'});}catch(e){}}}}
_eta(totalMin,label){const pad=v=>String(v).padStart(2,'0');const h=Math.floor(totalMin/60),m=totalMin%60;const t=new Date(Date.now()+totalMin*60000);return h+'h '+pad(m)+'m \u25B8 '+label+' @'+pad(t.getHours())+':'+pad(t.getMinutes());}
_norm(raw,type){
if(raw===null)return null;
if(type==='bat'&&this._c.bat_polarity==='positive')return raw*-1;
if(type==='grid'&&this._c.grid_polarity==='negative')return raw*-1;
return raw;
}
_bucket(arr,t0,t1,n){
if(!arr||!arr.length)return[];
const sum=new Array(n).fill(0),cnt=new Array(n).fill(0);
const span=(t1-t0)||1;
for(const p of arr){
const v=parseFloat(p.state);if(isNaN(v))continue;
const ts=new Date(p.last_changed||p.last_updated||t0).getTime();
let i=Math.floor((ts-t0)/span*n);if(i<0)i=0;else if(i>=n)i=n-1;
sum[i]+=Math.abs(v);cnt[i]++;
}
const out=new Array(n);let prev=0;
for(let i=0;i<n;i++){out[i]=cnt[i]?sum[i]/cnt[i]:prev;prev=out[i];}
const sm=[];
for(let i=0;i<n;i++){const a=i>0?out[i-1]:out[i];const b=i<n-1?out[i+1]:out[i];sm.push((a+out[i]*2+b)/4);}
return sm;
}
async _loadHistory(){if(this._histLoading||!this._h)return;this._histLoading=true;try{const c=this._c;const now=new Date();const start=new Date(now.getTime()-24*60*60*1000);const iso=encodeURIComponent(start.toISOString());const list=[c.solar,c.solar2,c.solar3,c.load,c.grid,c.battery,c.battery_charge,c.battery_discharge].filter(Boolean);const uniq=[...new Set(list)];if(!uniq.length)return;const entities=encodeURIComponent(uniq.join(','));const url='history/period/'+iso+'?filter_entity_id='+entities+'&minimal_response&no_attributes&significant_changes_only';const res=await this._h.callApi('GET',url);if(!res||!res.length)return;const t0=start.getTime(),t1=now.getTime();const byId={};for(const series of res){if(series.length)byId[series[0].entity_id]=this._bucket(series,t0,t1,HIST_POINTS);}const setSpark=(key,pts)=>{if(!pts)return;this._hist[key]=pts;this._histMax[key]=pts.length?Math.max(...pts)||1:1;};const sArr=[...new Set([c.solar,c.solar2,c.solar3].filter(Boolean))].map(e=>byId[e]).filter(a=>a&&a.length);if(sArr.length){const n=HIST_POINTS,tot=new Array(n).fill(0);for(const a of sArr)for(let i=0;i<n;i++)tot[i]+=a[i]||0;setSpark('solar',tot);}setSpark('load',byId[c.load]);setSpark('grid',byId[c.grid]);const bch=byId[c.battery_charge],bdis=byId[c.battery_discharge];if(bch||bdis){const n=HIST_POINTS,net=new Array(n);for(let i=0;i<n;i++){const d=bdis?bdis[i]:0,g=bch?bch[i]:0;net[i]=Math.abs(d-g);}setSpark('battery',net);}else{setSpark('battery',byId[c.battery]);}this._drawSparks();}catch(e){console.warn('xPower history:',e);}finally{this._histLoading=false;}}
_render(){this._elc={};this._fsp={};this._syncSpd=0;this._ringKey='';this._mwc=new Map();this._srTk=null;this._srLit=-1;this._srKey='';const L=this._lang;const INV=String(this._c.inverter_name||'').replace(/[<>&]/g,m=>({'<':'<','>':'>','&':'&'}[m]));const s=this.shadowRoot;s.innerHTML=`<style>
:host{--solar:var(--xpf-solar,#FFB300);--battery:var(--xpf-battery,#7C4DFF);--grid:var(--xpf-grid,#42A5F5);--load:var(--xpf-load,#26C6DA);--green:var(--xpf-green,#66BB6A);--red:var(--xpf-red,#EF5350);--orange:var(--xpf-orange,#FFA726);--t1:var(--xpf-text,rgba(255,255,255,0.92));--t3:var(--xpf-text-secondary,rgba(255,255,255,0.45));--xpf-r:var(--xpf-radius,20px);--xpf-vm-size:var(--xpf-font-size,${this._c.font_size||24}px);--mps:var(--xpf-mppt-scale,${Math.min(1.6,Math.max(0.8,Number(this._c.mppt_scale)||1))});--flow-w:var(--xpf-flow-width,3);--flow-dash:var(--xpf-dash-size,100);--fw:var(--xpf-frame-width,1px);--fo:var(--xpf-frame-opacity,0.7);--batf:#fff;--batn:#111;--batt:rgba(255,255,255,0.22)}
:host(.light){--t1:var(--xpf-text,rgba(0,0,0,0.85));--t3:var(--xpf-text-secondary,rgba(0,0,0,0.45));--batf:rgba(0,0,0,0.85);--batn:#fff;--batt:rgba(0,0,0,0.12)}
:host(.light) ha-card{background:var(--xpf-bg,rgba(255,255,255,0.92))}
:host(.light) .fl{stroke:rgba(0,0,0,0.06)}
:host(.light) .ib{fill:rgba(0,0,0,0.03);stroke:rgba(0,0,0,0.08)}
:host(.light) .sb{background:var(--xpf-sparkline-bg,rgba(0,0,0,0.02));border-color:rgba(0,0,0,0.06)}
:host(.light) .sl{opacity:0.4}
ha-card{background:var(--xpf-bg,rgba(12,14,24,0.92));border:1px solid transparent;border-radius:var(--xpf-r);box-shadow:var(--xpf-shadow,0 2px 40px rgba(0,0,0,0.4),inset 0 1px 0 rgba(255,255,255,0.04));padding:var(--xpf-padding,6px 8px 6px);position:relative;overflow:hidden;contain:layout paint style;font-family:-apple-system,sans-serif;--ha-card-background:transparent;--ha-card-border-width:0;--ha-card-border-radius:var(--xpf-r);--ha-card-box-shadow:none}
:host(.hast){--xpf-hbg:var(--ha-card-background,var(--card-background-color,#1c1c1c));--xpf-hsh:var(--ha-card-box-shadow,none);--xpf-hbc:var(--ha-card-border-color,var(--divider-color,transparent));--xpf-hbw:var(--ha-card-border-width,1px);--xpf-hr:var(--ha-card-border-radius,12px)}
:host(.hast) ha-card{background:var(--xpf-bg,var(--xpf-hbg));box-shadow:var(--xpf-shadow,var(--xpf-hsh));border:var(--xpf-hbw) solid var(--xpf-hbc);border-radius:var(--xpf-hr)}
:host(.noaur) #aur{display:none}
#aur{position:absolute;inset:0;width:100%;height:100%;overflow:visible;pointer-events:none}
#aur path{fill:none;stroke-width:var(--fw);stroke-linecap:butt}
#aur #aurhit{stroke:transparent;stroke-width:12;pointer-events:stroke;cursor:pointer}
#au-s,#au-b,#au-g{opacity:var(--fo)}
#aubadge{pointer-events:none}
#aulbl{opacity:0;transition:opacity 0.35s ease}
#aulbl.show{opacity:1}
:host(.rm) #aulbl{transition:none}
#auhit{cursor:default}
.mpr{cursor:pointer}
#mpw{transform:scale(var(--mps,1));transform-box:fill-box;transform-origin:100% 50%}.mpr:hover{opacity:.85}
svg{width:100%;height:auto;display:block}
.fl{fill:none;stroke:rgba(255,255,255,0.04);stroke-width:2;stroke-linecap:round}
.fa{fill:none;stroke-width:var(--flow-w);stroke-linecap:round;stroke-dasharray:var(--flow-dash) var(--flow-dash);transition:stroke 0.8s ease,opacity 0.8s ease}
.fd{animation:fD var(--spd,3s) linear infinite}.fu{animation:fU var(--spd,3s) linear infinite}.fr{animation:fR var(--spd,3s) linear infinite}.fL{animation:fL var(--spd,3s) linear infinite}
@keyframes ledBlink{0%,100%{opacity:1}50%{opacity:0.2}}
.led-on{animation:ledBlink 1.5s ease-in-out infinite}
@keyframes batPulse{0%,100%{opacity:0.5}50%{opacity:0.85}}
.bat-charge{animation:batPulse 1.5s ease-in-out infinite}
:host(.rm) .fa,:host(.rm) .led-on,:host(.rm) .bat-charge{animation:none !important}
:host(.off) .fa,:host(.off) .led-on,:host(.off) .bat-charge,:host(.off) .wxd{animation-play-state:paused}
@keyframes wxdr{0%{transform:translateY(-3px);opacity:0}30%{opacity:1}100%{transform:translateY(5px);opacity:0}}.wxd{animation:wxdr 1s linear infinite}.wxs{animation-duration:1.8s}:host(.rm) .wxd{animation:none}
.wxc{fill:#E5E5EA;stroke:var(--wxcs,none);stroke-width:1.2}
.wxc2{fill:#AEAEB2;stroke:var(--wxcs,none);stroke-width:1.2}
:host(.light){--wxcs:#AEAEB2}
.wxdr{animation:wxdr2 1s linear infinite}@keyframes wxdr2{0%{transform:translateY(-4px);opacity:0}25%{opacity:1}100%{transform:translateY(6px);opacity:0}}
.wxbo{animation:wxbo 2.4s ease-in-out infinite}@keyframes wxbo{0%,38%,52%,100%{opacity:1}44%{opacity:.15}46%{opacity:.35}48%{opacity:1}}
.wxha{animation:wxha 1.1s cubic-bezier(.5,0,.8,.4) infinite}@keyframes wxha{0%{transform:translateY(-5px);opacity:0}20%{opacity:1}80%{transform:translateY(4px)}90%{transform:translateY(2px)}100%{transform:translateY(5px);opacity:0}}
.wxsn{transform-box:fill-box;transform-origin:center;animation:wxsn 2.2s ease-in-out infinite}@keyframes wxsn{0%{transform:translate(0,-4px) rotate(0);opacity:0}25%{opacity:1}100%{transform:translate(2px,6px) rotate(90deg);opacity:0}}
.wxwi{stroke-dasharray:60;animation:wxwi 2.6s ease-in-out infinite}@keyframes wxwi{0%{stroke-dashoffset:60}50%{stroke-dashoffset:0}100%{stroke-dashoffset:-60}}
.wxcl{animation:wxcl 4s ease-in-out infinite}@keyframes wxcl{0%,100%{transform:translateX(0)}50%{transform:translateX(2.5px)}}
.wxst{transform-box:fill-box;transform-origin:center;animation:wxst 2.4s ease-in-out infinite}@keyframes wxst{0%,100%{transform:scale(1);opacity:1}50%{transform:scale(.55);opacity:.4}}
.wxpu{transform-box:fill-box;transform-origin:center;animation:wxpu 1.8s ease-in-out infinite}@keyframes wxpu{0%,100%{transform:scale(1)}50%{transform:scale(1.06)}}
.wxfg{animation:wxfg 3s ease-in-out infinite}@keyframes wxfg{0%,100%{transform:translateX(-2.5px)}50%{transform:translateX(2.5px)}}
:host(.off) .wxa{animation-play-state:paused}
:host(.rm) .wxa{animation:none}#wxBody{transition:opacity .6s ease}
#sunG{transform-origin:250px 38px;transition:opacity 0.8s ease}
#gridIcon,#loadIcon,#batIcon,#evIcon{transition:opacity 0.8s ease}
#vs,#vg,#vl,#vb,#ve{transition:opacity 0.8s ease}
@keyframes fR{from{stroke-dashoffset:200}to{stroke-dashoffset:0}}@keyframes fL{from{stroke-dashoffset:0}to{stroke-dashoffset:200}}@keyframes fD{from{stroke-dashoffset:200}to{stroke-dashoffset:0}}@keyframes fU{from{stroke-dashoffset:0}to{stroke-dashoffset:200}}
.vm{fill:var(--t1);font-size:var(--xpf-vm-size);font-weight:600;text-anchor:middle;dominant-baseline:middle}
.vl{fill:var(--t3);font-size:10px;font-weight:600;letter-spacing:0.14em;text-anchor:middle;dominant-baseline:middle}
.vs{fill:var(--t1);font-size:22px;font-weight:700;text-anchor:middle;dominant-baseline:middle}
.vd{fill:var(--t3);font-size:13px;font-weight:500;text-anchor:middle;dominant-baseline:middle}
.vc{fill:var(--t3);font-size:11px;font-weight:600;text-anchor:middle;dominant-baseline:middle}.srtk{stroke-width:0.7;opacity:0.2;transition:opacity 0.35s ease}.pvh{fill:var(--t3);font-size:8px;font-weight:700;text-anchor:middle;letter-spacing:0.06em;dominant-baseline:middle}.pvv{fill:var(--t3);font-size:9px;font-weight:600;text-anchor:middle;dominant-baseline:middle}.pvu{fill:var(--t3);font-size:8px;font-weight:500;opacity:0.75;text-anchor:middle;dominant-baseline:middle}.srt{fill:var(--t3);font-size:8px;font-weight:600;dominant-baseline:middle;opacity:0;transition:opacity 0.25s ease}#sunRing:hover .srt,#sunRing.srshow .srt{opacity:1}
.ib{fill:rgba(255,255,255,0.02);stroke:rgba(255,255,255,0.06);stroke-width:1}
.il{fill:rgba(255,255,255,0.35);font-size:12px;font-weight:600;letter-spacing:0.05em;text-anchor:middle;dominant-baseline:middle}
#au-s,#au-b,#au-g{transition:stroke-dasharray .6s cubic-bezier(.33,1,.68,1),stroke-dashoffset .6s cubic-bezier(.33,1,.68,1)}:host(.rm) #au-s,:host(.rm) #au-b,:host(.rm) #au-g{transition:none}
.au-track{stroke:rgba(255,255,255,0.10)}
:host(.light) .au-track{stroke:rgba(0,0,0,0.10)}
#main{will-change:transform}.ct{cursor:pointer}.ct:hover{opacity:.85}
.wt{fill:var(--t3);font-size:11px;font-weight:500;text-anchor:start;dominant-baseline:middle;font-family:-apple-system,sans-serif}
.wb{fill:none;stroke:var(--t3);stroke-width:0.5;rx:4;ry:4;opacity:0.4}
.sr{display:flex;gap:6px;margin-top:8mm}
:host(.compact) .sr{display:none}
:host(.compact) ha-card{padding:6px 8px 6px}
.sb{flex:1;background:var(--xpf-sparkline-bg,rgba(255,255,255,0.02));border:1px solid rgba(255,255,255,0.04);border-radius:var(--xpf-sparkline-radius,12px);padding:10px 10px 8px;display:flex;flex-direction:column;gap:2px;overflow:hidden}
.sb-header{display:flex;justify-content:space-between;align-items:baseline}
.sl{font-size:7px;font-weight:600;letter-spacing:0.1em;text-transform:uppercase;opacity:0.3}
.sv{font-size:11px;font-weight:600;opacity:0.8}
.ss .sv{color:var(--green)}.sc .sv{color:var(--load)}.sg .sv{color:var(--grid)}.sbt .sv{color:var(--battery)}
.sb svg{width:100%;height:55px;display:block;margin-top:auto}
.sb path{stroke-linecap:round;stroke-linejoin:round}
.sb{position:relative;cursor:crosshair}
.sb-tip{position:absolute;top:2px;right:4px;font-size:10px;font-weight:600;opacity:0;transition:opacity 0.15s;pointer-events:none;padding:2px 6px;border-radius:4px;background:rgba(0,0,0,0.7)}
.sb-tip.show{opacity:1}
.sb line.cursor{stroke:rgba(255,255,255,0.3);stroke-width:1;stroke-dasharray:2 2;display:none}
.sb circle.cursor-dot{fill:rgba(255,255,255,0.8);display:none}
.ss #hs{fill:none;stroke:rgba(102,187,106,0.7);stroke-width:1.2}.sc #hl{fill:none;stroke:rgba(38,198,218,0.7);stroke-width:1.2}.sg #hg{fill:none;stroke:rgba(66,165,245,0.7);stroke-width:1.2}.sbt #hb2{fill:none;stroke:rgba(124,77,255,0.7);stroke-width:1.2}
.ss #hsa{fill:url(#sgd-s);stroke:none}.sc #hla{fill:url(#sgd-l);stroke:none}.sg #hga{fill:url(#sgd-g);stroke:none}.sbt #hb2a{fill:url(#sgd-b);stroke:none}
</style>
<ha-card id="xcard"><svg id="aur" role="img" aria-label="${L.energy_src}"><path id="aut" class="au-track"/><path id="au-s" stroke="var(--green)" pathLength="100" stroke-dasharray="0 100"/><path id="au-b" stroke="var(--orange)" pathLength="100" stroke-dasharray="0 100"/><path id="au-g" stroke="var(--red)" pathLength="100" stroke-dasharray="0 100"/><path id="aurhit"><title>${L.energy_src}</title></path></svg><svg id="main" viewBox="0 -8 526 478"><defs><linearGradient id="sunrg" x1="0" y1="0" x2="1" y2="0"><stop offset="0" stop-color="#FF8F00"/><stop offset="1" stop-color="#FFD54F"/></linearGradient><clipPath id="bat-clip"><rect x="232" y="342.5" width="32" height="17" rx="5.5"/></clipPath><clipPath id="ev-clip"><rect x="420.5" y="450.5" width="24" height="12" rx="4"/></clipPath><radialGradient id="sundisc" cx="38%" cy="34%" r="72%"><stop offset="0" stop-color="#FFE082"/><stop offset="0.55" stop-color="#FFC107"/><stop offset="1" stop-color="#FF9800"/></radialGradient><radialGradient id="sunhl" cx="50%" cy="50%" r="50%"><stop offset="0.55" stop-color="#FFC107" stop-opacity="0.30"/><stop offset="1" stop-color="#FFC107" stop-opacity="0"/></radialGradient><radialGradient id="moonhl" cx="50%" cy="50%" r="50%"><stop offset="0.55" stop-color="#90CAF9" stop-opacity="0.22"/><stop offset="1" stop-color="#90CAF9" stop-opacity="0"/></radialGradient><radialGradient id="moondisc" cx="35%" cy="35%" r="75%"><stop offset="0" stop-color="#ECEFF1"/><stop offset="1" stop-color="#B0BEC5"/></radialGradient><mask id="moonmask" maskUnits="userSpaceOnUse" x="200" y="-12" width="100" height="100"><rect x="200" y="-12" width="100" height="100" fill="#fff"/><circle cx="259" cy="31" r="14" fill="#000"/></mask><linearGradient id="ivbody" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#FFFFFF"/><stop offset="1" stop-color="#E9ECEF"/></linearGradient><linearGradient id="ivpill" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#34373B"/><stop offset="0.55" stop-color="#2B2E31"/><stop offset="0.56" stop-color="#3C4045"/><stop offset="1" stop-color="#2B2E31"/></linearGradient><linearGradient id="hsrc" gradientUnits="userSpaceOnUse" x1="287.4" y1="225" x2="395" y2="225"><stop id="hs0" offset="0%" stop-color="#66BB6A"/><stop id="hs1" offset="60%" stop-color="#66BB6A"/><stop id="hs2" offset="60%" stop-color="#FFA726"/><stop id="hs3" offset="60%" stop-color="#FFA726"/><stop id="hs4" offset="60%" stop-color="#EF5350"/><stop id="hs5" offset="100%" stop-color="#EF5350"/></linearGradient></defs><g transform="translate(25.5,10) scale(0.95)">
<g id="wicons" style="display:none" transform="translate(-20,0)">
<rect class="wb" x="-2" y="-4" width="86" height="20"/>
<g transform="translate(0,1)"><rect x="6" y="0" width="2.5" height="7" rx="1.2" fill="none" stroke="var(--t3)" stroke-width="0.7"/><circle cx="7.2" cy="9" r="2.5" fill="none" stroke="var(--t3)" stroke-width="0.7"/><line x1="7.2" y1="3" x2="7.2" y2="7" stroke="var(--red)" stroke-width="1" stroke-linecap="round"/><circle cx="7.2" cy="9" r="1.2" fill="var(--red)"/></g>
<text x="14" y="8" class="wt" id="wt"></text>
<line id="wdiv" x1="44" y1="2" x2="44" y2="12" stroke="var(--t3)" stroke-width="0.5" opacity="0.4" style="display:none"/>
<g id="wdrop" transform="translate(2,1)"><path d="M46,9 Q46,4 49,0 Q52,4 52,9 Q52,12 49,12 Q46,12 46,9Z" fill="none" stroke="var(--t3)" stroke-width="0.7"/><path d="M47.2,8.5 Q47.2,5.5 49,2 Q50.8,5.5 50.8,8.5 Q50.8,10.5 49,10.5 Q47.2,10.5 47.2,8.5Z" fill="var(--grid)" opacity="0.5"/></g>
<text x="58" y="8" class="wt" id="wh"></text>
</g>
<g id="priceicons" style="display:none" transform="translate(-20,26)">
<rect class="wb" x="-2" y="-4" width="106" height="20"/>
<circle cx="7" cy="6" r="5.5" fill="none" stroke="var(--t3)" stroke-width="0.7"/>
<text x="7" y="6.5" font-family="-apple-system,sans-serif" font-size="7" font-weight="700" fill="var(--t3)" text-anchor="middle" dominant-baseline="middle">¤</text>
<text x="17" y="8" class="wt" id="pr" font-size="10"></text>
</g>
<path class="fl" d="M250,96 L250,187.7"/><path class="fl" d="M250,262.4 L250,364"/><path class="fl" d="M90,225 L212.7,225"/><path class="fl" d="M287.4,225 L395,225"/>
<path id="fs" class="fa" d="M250,96 L250,187.7" pathLength="100" opacity="0"/><path id="fb" class="fa" d="M250,262.4 L250,364" pathLength="100" opacity="0"/><path id="fg" class="fa" d="M90,225 L212.7,225" pathLength="100" opacity="0"/><path id="fh" class="fa" d="M287.4,225 L395,225" pathLength="100" opacity="0"/>
<g id="nSolar" class="ct"><g id="sunRing" style="display:none">${SUNTICKS}<path id="srhit" d="M224.5,63.5 A36,36 0 1 1 275.5,63.5" fill="none" stroke="transparent" stroke-width="16" pointer-events="stroke" style="cursor:pointer"/><text id="srx" x="215" y="66" class="srt" style="text-anchor:end"></text><text id="ssx" x="285" y="66" class="srt" style="text-anchor:start"></text></g><g id="sunG"><g id="wxBody"><g id="sunBody"><circle cx="250" cy="38" r="28" fill="url(#sunhl)"/><circle cx="250" cy="38" r="19" fill="url(#sundisc)"/></g><g id="moonBody" style="display:none"><circle cx="250" cy="38" r="27" fill="url(#moonhl)"/><circle cx="250" cy="38" r="18" fill="url(#moondisc)" mask="url(#moonmask)"/></g></g><g id="wxG"></g></g><text x="250" y="81" class="vm" style="fill:var(--green)" id="vs"></text><text x="250" y="-10" class="vl">${L.solar}</text><text id="ds" x="338" y="34" class="pvv" style="font-size:13px"></text><text id="pv" x="338" y="48" class="pvu" style="font-size:11px"></text><g id="mpw">${MPPT_ROWS}</g></g>
<g id="aubadge"><g id="au-leaf" transform="translate(498 -2.8)" fill="none" stroke="var(--green)" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"><g transform="translate(-5 -5) scale(0.4167)"><path d="M5 21c.5-4.5 2.5-8 7-10"/><path d="M9 18c6.218 0 10.5-3.288 11-12v-2h-4.014c-9 0-11.986 4-12 9c0 1 0 3 2 5h3z"/></g></g><text x="498" y="8" id="va" font-family="-apple-system,sans-serif" font-size="11" font-weight="800" fill="var(--t1)" text-anchor="middle" dominant-baseline="middle"></text><text id="aulbl" x="511" y="21" font-family="-apple-system,sans-serif" font-size="8" font-weight="500" fill="var(--t3)" text-anchor="end" dominant-baseline="middle">${L.autarky}</text><rect id="auhit" x="482" y="-8" width="32" height="22" fill="transparent" pointer-events="all"/></g>
<g><g transform="translate(250,225) scale(1.65)"><rect x="-19" y="-19" width="38" height="38" rx="5" fill="url(#ivbody)" stroke="rgba(0,0,0,0.18)" stroke-width="0.5"/><rect x="-11" y="18" width="4.5" height="2.4" rx="0.8" fill="rgba(0,0,0,0.35)"/><rect x="-2.25" y="18" width="4.5" height="2.4" rx="0.8" fill="rgba(0,0,0,0.35)"/><rect x="6.5" y="18" width="4.5" height="2.4" rx="0.8" fill="rgba(0,0,0,0.35)"/><rect x="-11" y="-4.6" width="22" height="9.2" rx="4.6" fill="url(#ivpill)"/><circle id="led1" cx="-4" cy="-0.6" r="1.15" fill="rgba(255,255,255,0.12)"/><circle id="led2" cx="0" cy="-0.6" r="1.15" fill="rgba(255,255,255,0.12)"/><circle id="led3" cx="4" cy="-0.6" r="1.15" fill="rgba(255,255,255,0.12)"/><rect id="ivbar" x="-3" y="2.6" width="6" height="0.9" rx="0.45" fill="#E4002B"/></g>${INV?'<text x="250" y="272" class="il">'+INV+'</text>':''}<text x="296" y="264" class="vc" id="tp" text-anchor="start"></text></g>
<g id="nGrid" class="ct"><g id="gridIcon" transform="translate(66,225) scale(1.65) translate(-66,-196)"><rect x="64" y="181" width="4" height="30" rx="1" fill="var(--red)" opacity="0.7"/><rect x="54" y="183" width="24" height="3" rx="1" fill="var(--red)" opacity="0.6"/><rect x="57" y="192" width="18" height="2.5" rx="1" fill="var(--red)" opacity="0.5"/><path d="M60,211 L64,199 L68,199 L72,211" fill="var(--red)" opacity="0.4"/><circle cx="56" cy="184" r="1.5" fill="var(--red)" opacity="0.8"/><circle cx="76" cy="184" r="1.5" fill="var(--red)" opacity="0.8"/><circle cx="58" cy="193" r="1.2" fill="var(--red)" opacity="0.7"/><circle cx="74" cy="193" r="1.2" fill="var(--red)" opacity="0.7"/><line x1="54" y1="184" x2="46" y2="181" stroke="var(--red)" stroke-width="0.8" opacity="0.3"/><line x1="78" y1="184" x2="86" y2="181" stroke="var(--red)" stroke-width="0.8" opacity="0.3"/></g><text x="66" y="265" class="vm" style="fill:var(--red)" id="vg"></text><text x="66" y="190" class="vl">${L.grid}</text><text x="66" y="286" class="vc" id="gv"></text><circle id="gsd" cx="92" cy="189" r="4" fill="rgba(255,255,255,0.12)"/><text x="66" y="300" class="vd" id="dg"></text></g>
<g id="nLoad" class="ct"><g id="loadIcon" transform="translate(434,225) scale(1.65) translate(-434,-188)"><path d="M416,188 L434,174 L452,188 Z" fill="var(--load)" opacity="0.8"/><rect x="420" y="187" width="28" height="18" rx="1" fill="var(--load)" opacity="0.6"/><rect x="430" y="195" width="8" height="10" rx="1" fill="rgba(0,0,0,0.3)"/><rect x="422" y="190" width="6" height="5" rx="0.5" fill="rgba(255,255,255,0.15)"/><rect x="440" y="190" width="6" height="5" rx="0.5" fill="rgba(255,255,255,0.15)"/><rect x="441" y="176" width="5" height="8" rx="1" fill="var(--load)" opacity="0.5"/></g><text x="434" y="268" class="vm" style="fill:var(--load)" id="vl"></text><text x="434" y="190" class="vl">${L.load}</text><text x="434" y="288" class="vd" id="dl"></text></g>
<g id="nBat" class="ct"><g id="batIcon" transform="translate(250,400) scale(2.05) translate(-248,-351)"><rect x="232" y="342.5" width="32" height="17" rx="5.5" fill="var(--batt)"/><rect id="bl" x="232" y="342.5" width="32" height="17" fill="var(--batf)" clip-path="url(#bat-clip)"/><rect x="264.5" y="346.5" width="4" height="9" rx="2" fill="var(--batt)"/><svg id="bpA" x="232" y="342.5" width="32" height="17" viewBox="232 342.5 32 17" preserveAspectRatio="xMinYMin slice"><text id="bp" x="248" y="355.7" font-family="-apple-system,sans-serif" font-size="13.5" font-weight="800" fill="var(--batn)" text-anchor="middle">--</text></svg><svg id="bpB" x="264" y="342.5" width="0" height="17" viewBox="264 342.5 0.01 17" preserveAspectRatio="xMinYMin slice"><text id="bp2" x="248" y="355.7" font-family="-apple-system,sans-serif" font-size="13.5" font-weight="800" fill="var(--batf)" text-anchor="middle">--</text></svg><path id="bbolt" d="M260.1,345.8 L255.7,352 L258.9,352 L257.1,356.8 L262.5,349.9 L259.3,349.9 Z" fill="#fff" style="display:none"/></g><text x="250" y="432" class="vm" style="fill:var(--solar)" id="vb"></text><text id="meas" class="vm" x="0" y="-999" visibility="hidden"></text><g id="as" data-vx="250" data-vy="81"><g><path fill="none" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round"/><path fill="none" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round" style="display:none"/></g></g><g id="ag" data-vx="66" data-vy="265"><g><path fill="none" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round"/><path fill="none" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round" style="display:none"/></g></g><g id="al" data-vx="434" data-vy="268"><g><path fill="none" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round"/><path fill="none" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round" style="display:none"/></g></g><g id="ab" data-vx="250" data-vy="432"><g><path fill="none" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round"/><path fill="none" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round" style="display:none"/></g></g><text x="250" y="372" class="vl">${L.battery}</text><text x="316" y="394" class="vc" id="bv" text-anchor="start"></text><text x="316" y="406" class="vc" id="bt" text-anchor="start"></text><text x="250" y="452" class="vd" id="db"></text><text x="250" y="468" class="vc" id="br" style="fill:var(--t1)"></text></g>
<g id="nEV" class="ct" style="display:none"><path class="fl" d="M434,302 L434,362"/><path id="fe" class="fa" d="M434,302 L434,362" pathLength="100" opacity="0"/><text x="434" y="372" class="vl">${L.ev}</text><g id="evIcon" transform="translate(434,398) scale(1.65) translate(-434,-398)"><path d="M419.5,402.5 Q419.3,398.2 424,397.1 Q426.5,391.6 432,390.9 L437,390.9 Q442.3,391.3 445.3,395.2 Q449.1,396.1 449.4,399.6 Q449.6,402.5 446.6,402.5 L422.4,402.5 Q419.6,402.5 419.5,402.5 Z" fill="var(--load)" opacity="0.8"/><path d="M427.6,395.9 Q429.1,392.6 432.6,392.3 L433.6,392.3 L433.6,395.9 Z" fill="rgba(0,0,0,0.35)"/><path d="M435.1,392.3 L436.9,392.3 Q440.4,392.7 442.4,395.9 L435.1,395.9 Z" fill="rgba(0,0,0,0.35)"/><circle cx="426" cy="402.4" r="3.1" fill="rgba(0,0,0,0.55)"/><circle cx="426" cy="402.4" r="1.4" fill="rgba(255,255,255,0.35)"/><circle cx="442.3" cy="402.4" r="3.1" fill="rgba(0,0,0,0.55)"/><circle cx="442.3" cy="402.4" r="1.4" fill="rgba(255,255,255,0.35)"/><path id="evbolt" d="M452.5,391 L450.5,396 L453.5,396 L451.5,401" fill="rgba(255,255,255,0.15)" stroke="rgba(255,255,255,0.3)" stroke-width="0.5"/></g><text x="434" y="440" class="vm" style="fill:var(--green)" id="ve"></text><g id="evPill" style="display:none"><rect x="420.5" y="450.5" width="24" height="12" rx="4" fill="var(--batt)"/><rect id="evl" x="420.5" y="450.5" width="0" height="12" fill="var(--batf)" clip-path="url(#ev-clip)"/><rect x="444.9" y="453.3" width="3" height="6.4" rx="1.5" fill="var(--batt)"/><svg id="evA" x="420.5" y="450.5" width="24" height="12" viewBox="420.5 450.5 24 12" preserveAspectRatio="xMinYMin slice"><text id="evsoc" x="432.5" y="459.8" font-family="-apple-system,sans-serif" font-size="9.5" font-weight="800" fill="var(--batn)" text-anchor="middle"></text></svg><svg id="evB" x="444.5" y="450.5" width="0" height="12" viewBox="444.5 450.5 0.01 12" preserveAspectRatio="xMinYMin slice"><text id="evsoc2" x="432.5" y="459.8" font-family="-apple-system,sans-serif" font-size="9.5" font-weight="800" fill="var(--batf)" text-anchor="middle"></text></svg></g><text x="434" y="476" class="vd" id="de"></text></g>
<g id="nExtra1" class="ct" style="display:none">
<path class="fl" d="M434,204 L434,160 L381,160"/>
<path id="fex1" class="fa" d="M434,204 L434,160 L381,160" pathLength="100" opacity="0"/>
<text x="369" y="138" class="vl" id="ex1lbl" font-size="8"></text>
<g id="ex1Icon" transform="translate(369,160) scale(1.05)">
<g data-t="appliance" style="display:none"><rect x="-9" y="-10" width="18" height="20" rx="2" fill="var(--load)" opacity="0.6"/><circle cx="0" cy="2" r="6" fill="none" stroke="var(--load)" stroke-width="1.4" opacity="0.9"/><circle cx="0" cy="2" r="3" fill="var(--load)" opacity="0.4"/><rect x="-6" y="-8" width="4" height="2" rx="0.5" fill="var(--load)" opacity="0.5"/><circle cx="-6.5" cy="-8" r="0.9" fill="var(--load)" opacity="0.8"/></g>
<g data-t="heatpump" style="display:none"><rect x="-10" y="-7" width="20" height="14" rx="2" fill="var(--load)" opacity="0.6"/><circle cx="0" cy="0" r="5" fill="none" stroke="var(--load)" stroke-width="1.1" opacity="0.9"/><line x1="0" y1="-5" x2="0" y2="5" stroke="var(--load)" stroke-width="1" opacity="0.7"/><line x1="-5" y1="0" x2="5" y2="0" stroke="var(--load)" stroke-width="1" opacity="0.7"/></g>
<g data-t="garage" style="display:none"><path d="M-11,-2 L0,-11 L11,-2 Z" fill="var(--load)" opacity="0.8"/><rect x="-9" y="-2" width="18" height="12" rx="1" fill="var(--load)" opacity="0.6"/><line x1="-9" y1="1" x2="9" y2="1" stroke="rgba(0,0,0,0.3)" stroke-width="0.8"/><line x1="-9" y1="4" x2="9" y2="4" stroke="rgba(0,0,0,0.3)" stroke-width="0.8"/><line x1="-9" y1="7" x2="9" y2="7" stroke="rgba(0,0,0,0.3)" stroke-width="0.8"/></g>
<g data-t="generic" style="display:none"><path d="M1,-10 L-7,3 L-1,3 L-3,10 L8,-3 L2,-3 Z" fill="var(--load)" opacity="0.8"/></g>
</g>
<text x="369" y="183" class="vm" style="fill:var(--load);font-size:13px" id="ex1val"></text>
</g>
<g id="nExtra2" class="ct" style="display:none">
<path class="fl" d="M434,204 L434,160 L434,112"/>
<path id="fex2" class="fa" d="M434,204 L434,160 L434,112" pathLength="100" opacity="0"/>
<text x="434" y="78" class="vl" id="ex2lbl" font-size="8"></text>
<g id="ex2Icon" transform="translate(434,100) scale(1.05)">
<g data-t="appliance" style="display:none"><rect x="-9" y="-10" width="18" height="20" rx="2" fill="var(--load)" opacity="0.6"/><circle cx="0" cy="2" r="6" fill="none" stroke="var(--load)" stroke-width="1.4" opacity="0.9"/><circle cx="0" cy="2" r="3" fill="var(--load)" opacity="0.4"/><rect x="-6" y="-8" width="4" height="2" rx="0.5" fill="var(--load)" opacity="0.5"/><circle cx="-6.5" cy="-8" r="0.9" fill="var(--load)" opacity="0.8"/></g>
<g data-t="heatpump" style="display:none"><rect x="-10" y="-7" width="20" height="14" rx="2" fill="var(--load)" opacity="0.6"/><circle cx="0" cy="0" r="5" fill="none" stroke="var(--load)" stroke-width="1.1" opacity="0.9"/><line x1="0" y1="-5" x2="0" y2="5" stroke="var(--load)" stroke-width="1" opacity="0.7"/><line x1="-5" y1="0" x2="5" y2="0" stroke="var(--load)" stroke-width="1" opacity="0.7"/></g>
<g data-t="garage" style="display:none"><path d="M-11,-2 L0,-11 L11,-2 Z" fill="var(--load)" opacity="0.8"/><rect x="-9" y="-2" width="18" height="12" rx="1" fill="var(--load)" opacity="0.6"/><line x1="-9" y1="1" x2="9" y2="1" stroke="rgba(0,0,0,0.3)" stroke-width="0.8"/><line x1="-9" y1="4" x2="9" y2="4" stroke="rgba(0,0,0,0.3)" stroke-width="0.8"/><line x1="-9" y1="7" x2="9" y2="7" stroke="rgba(0,0,0,0.3)" stroke-width="0.8"/></g>
<g data-t="generic" style="display:none"><path d="M1,-10 L-7,3 L-1,3 L-3,10 L8,-3 L2,-3 Z" fill="var(--load)" opacity="0.8"/></g>
</g>
<text x="410" y="100" style="fill:var(--load);font-size:13px;font-weight:600;text-anchor:end;dominant-baseline:middle" id="ex2val"></text>
</g>
<g id="nExtra3" class="ct" style="display:none">
<path class="fl" d="M434,204 L434,160 L487,160"/>
<path id="fex3" class="fa" d="M434,204 L434,160 L487,160" pathLength="100" opacity="0"/>
<text x="499" y="138" class="vl" id="ex3lbl" font-size="8"></text>
<g id="ex3Icon" transform="translate(499,160) scale(1.05)">
<g data-t="appliance" style="display:none"><rect x="-9" y="-10" width="18" height="20" rx="2" fill="var(--load)" opacity="0.6"/><circle cx="0" cy="2" r="6" fill="none" stroke="var(--load)" stroke-width="1.4" opacity="0.9"/><circle cx="0" cy="2" r="3" fill="var(--load)" opacity="0.4"/><rect x="-6" y="-8" width="4" height="2" rx="0.5" fill="var(--load)" opacity="0.5"/><circle cx="-6.5" cy="-8" r="0.9" fill="var(--load)" opacity="0.8"/></g>
<g data-t="heatpump" style="display:none"><rect x="-10" y="-7" width="20" height="14" rx="2" fill="var(--load)" opacity="0.6"/><circle cx="0" cy="0" r="5" fill="none" stroke="var(--load)" stroke-width="1.1" opacity="0.9"/><line x1="0" y1="-5" x2="0" y2="5" stroke="var(--load)" stroke-width="1" opacity="0.7"/><line x1="-5" y1="0" x2="5" y2="0" stroke="var(--load)" stroke-width="1" opacity="0.7"/></g>
<g data-t="garage" style="display:none"><path d="M-11,-2 L0,-11 L11,-2 Z" fill="var(--load)" opacity="0.8"/><rect x="-9" y="-2" width="18" height="12" rx="1" fill="var(--load)" opacity="0.6"/><line x1="-9" y1="1" x2="9" y2="1" stroke="rgba(0,0,0,0.3)" stroke-width="0.8"/><line x1="-9" y1="4" x2="9" y2="4" stroke="rgba(0,0,0,0.3)" stroke-width="0.8"/><line x1="-9" y1="7" x2="9" y2="7" stroke="rgba(0,0,0,0.3)" stroke-width="0.8"/></g>
<g data-t="generic" style="display:none"><path d="M1,-10 L-7,3 L-1,3 L-3,10 L8,-3 L2,-3 Z" fill="var(--load)" opacity="0.8"/></g>
</g>
<text x="499" y="183" class="vm" style="fill:var(--load);font-size:13px" id="ex3val"></text>
</g>
</g></svg>
<div class="sr" style="margin-top:4px">
<div class="sb sg"><div class="sb-header"><span class="sl">${L.grid24}</span><span class="sv" id="hz"></span></div><svg viewBox="0 0 200 55" preserveAspectRatio="none"><defs><linearGradient id="sgd-g" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="rgba(66,165,245,0.30)"/><stop offset="1" stop-color="rgba(66,165,245,0)"/></linearGradient></defs><path id="hga"/><path id="hg"/><line class="cursor" id="cg" x1="0" y1="0" x2="0" y2="55"/><circle class="cursor-dot" id="dg2" cx="0" cy="0" r="3"/></svg><span class="sb-tip" id="tg"></span></div>
<div class="sb ss"><div class="sb-header"><span class="sl">${L.solar24}</span><span class="sv" id="hv"></span></div><svg viewBox="0 0 200 55" preserveAspectRatio="none"><defs><linearGradient id="sgd-s" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="rgba(102,187,106,0.30)"/><stop offset="1" stop-color="rgba(102,187,106,0)"/></linearGradient></defs><path id="hsa"/><path id="hs"/><line class="cursor" id="cs" x1="0" y1="0" x2="0" y2="55"/><circle class="cursor-dot" id="ds2" cx="0" cy="0" r="3"/></svg><span class="sb-tip" id="ts"></span></div>
<div class="sb sc"><div class="sb-header"><span class="sl">${L.load24}</span><span class="sv" id="hx"></span></div><svg viewBox="0 0 200 55" preserveAspectRatio="none"><defs><linearGradient id="sgd-l" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="rgba(38,198,218,0.30)"/><stop offset="1" stop-color="rgba(38,198,218,0)"/></linearGradient></defs><path id="hla"/><path id="hl"/><line class="cursor" id="cl" x1="0" y1="0" x2="0" y2="55"/><circle class="cursor-dot" id="dl2" cx="0" cy="0" r="3"/></svg><span class="sb-tip" id="tl"></span></div>
<div class="sb sbt"><div class="sb-header"><span class="sl">${L.bat24}</span><span class="sv" id="hy"></span></div><svg viewBox="0 0 200 55" preserveAspectRatio="none"><defs><linearGradient id="sgd-b" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="rgba(124,77,255,0.30)"/><stop offset="1" stop-color="rgba(124,77,255,0)"/></linearGradient></defs><path id="hb2a"/><path id="hb2"/><line class="cursor" id="cb" x1="0" y1="0" x2="0" y2="55"/><circle class="cursor-dot" id="db2" cx="0" cy="0" r="3"/></svg><span class="sb-tip" id="tb"></span></div>
</div></ha-card>`;this._setupTooltips();this._setupClicks();this._ringPath();requestAnimationFrame(()=>this._ringPath());if(this._ro)this._ro.disconnect();if('ResizeObserver' in window){this._ro=new ResizeObserver(()=>this._ringPath());const cd=this._$('xcard');if(cd)this._ro.observe(cd);}}
_$(id){let c=this._elc;if(!c)c=this._elc={};let el=c[id];if(el&&el.isConnected)return el;el=this.shadowRoot.getElementById(id);c[id]=el;return el;}
_moreInfo(entityId){if(!entityId)return;this.dispatchEvent(new CustomEvent('hass-more-info',{detail:{entityId},bubbles:true,composed:true}));}
_setupClicks(){const c=this._c;const bind=(id,entity)=>{const el=this._$(id);if(el&&entity)el.addEventListener('click',()=>this._moreInfo(entity));};bind('nSolar',c.solar);bind('nGrid',c.grid);bind('nLoad',c.load);bind('nBat',c.battery||c.soc);bind('nEV',c.ev_power||c.ev_soc);bind('nExtra1',c.extra1_power);bind('nExtra2',c.extra2_power);bind('nExtra3',c.extra3_power);const hit=this._$('srhit'),sg=this._$('sunRing');if(hit&&sg)hit.addEventListener('click',(e)=>{e.stopPropagation();sg.classList.add('srshow');clearTimeout(this._srT);this._srT=setTimeout(()=>sg.classList.remove('srshow'),3000);});[1,2,3].forEach(k=>{const g=this._$('mp'+k);if(g)g.addEventListener('click',(e)=>{e.stopPropagation();this._moreInfo(g.dataset.e);});});const aH=this._$('auhit'),aB=this._$('aulbl');if(aH&&aB){const show=()=>{aB.classList.add('show');clearTimeout(this._auT);this._auT=setTimeout(()=>aB.classList.remove('show'),3000);};aH.addEventListener('pointerenter',show);aH.addEventListener('pointerdown',e=>{e.stopPropagation();show();});aH.addEventListener('click',e=>e.stopPropagation());aH.addEventListener('pointerleave',()=>{clearTimeout(this._auT);this._auT=setTimeout(()=>aB.classList.remove('show'),600);});}}
// Frame path + stroke width: scales with card width (≈0.75px phone, 1px desktop, 2px TV, max 3px); an explicit --xpf-frame-width wins
_ringPath(){const card=this._$('xcard'),svg=this._$('aur');if(!card||!svg)return;const w=card.clientWidth,h=card.clientHeight;if(!w||!h)return;const cs=getComputedStyle(card);const bw=parseFloat(cs.borderTopWidth)||0;const ov=parseFloat(cs.getPropertyValue('--xpf-frame-width'));const dpr=window.devicePixelRatio||1;const sw=ov>0?ov:Math.min(3,Math.max(dpr>=1.5?0.75:1,Math.round(w/480*100)/100));const i=sw/2,W=w-sw,H=h-sw;let r=(parseFloat(cs.borderTopLeftRadius)||20)-bw-i;r=Math.max(0,Math.min(r,Math.min(W,H)/2));const key=w+'|'+h+'|'+r+'|'+sw;if(key===this._ringKey)return;this._ringKey=key;svg.style.setProperty('--fw',sw+'px');const q=v=>Math.round(v*4)/4;const d='M'+q(i+W/2)+','+q(i)+' H'+q(i+W-r)+' A'+q(r)+','+q(r)+' 0 0 1 '+q(i+W)+','+q(i+r)+' V'+q(i+H-r)+' A'+q(r)+','+q(r)+' 0 0 1 '+q(i+W-r)+','+q(i+H)+' H'+q(i+r)+' A'+q(r)+','+q(r)+' 0 0 1 '+q(i)+','+q(i+H-r)+' V'+q(i+r)+' A'+q(r)+','+q(r)+' 0 0 1 '+q(i+r)+','+q(i)+' Z';this._sa(svg,'viewBox','0 0 '+w+' '+h);['aut','au-s','au-b','au-g','aurhit'].forEach(id=>this._sa(this._$(id),'d',d));}
_setupTooltips(){
const self=this;
const setup=(svgParent,cursorId,dotId,tipId,dataKey,color)=>{
const svg=self.shadowRoot.querySelector('#'+cursorId)?.closest('svg');
if(!svg)return;
const cursor=self._$(cursorId);const dot=self._$(dotId);const tip=self._$(tipId);
if(!cursor||!dot||!tip)return;
const show=(cx)=>{
const data=self._hist[dataKey];if(!data||!data.length)return;
const rect=svg.getBoundingClientRect();
const x=(cx-rect.left)/rect.width;
const idx=Math.min(Math.max(Math.round(x*(data.length-1)),0),data.length-1);
const val=data[idx];const svgX=x*200;
const max=self._sparkM||self._histMax[dataKey]||1;const svgY=2+(1-val/max)*51;
cursor.setAttribute('x1',svgX);cursor.setAttribute('x2',svgX);cursor.style.display='';
dot.setAttribute('cx',svgX);dot.setAttribute('cy',svgY);dot.style.display='';dot.setAttribute('fill',color);
const hoursAgo=(1-idx/(data.length-1))*24;
const when=new Date(Date.now()-hoursAgo*3600000);
const hh=String(when.getHours()).padStart(2,'0');const mm=String(when.getMinutes()).padStart(2,'0');
tip.textContent=(val>=1000?(val/1000).toFixed(1)+' kW':val.toFixed(0)+' W')+' · '+hh+':'+mm;
tip.style.color=color;tip.classList.add('show');
};
const hide=()=>{cursor.style.display='none';dot.style.display='none';tip.classList.remove('show');};
svg.addEventListener('mousemove',(e)=>show(e.clientX));
svg.addEventListener('mouseleave',hide);
svg.addEventListener('touchmove',(e)=>{e.preventDefault();show(e.touches[0].clientX);},{passive:false});
svg.addEventListener('touchend',hide);
};
setup(null,'cg','dg2','tg','grid','#42A5F5');
setup(null,'cs','ds2','ts','solar','#66BB6A');
setup(null,'cl','dl2','tl','load','#26C6DA');
setup(null,'cb','db2','tb','battery','#7C4DFF');
}
_spd(p){const a=Math.abs(p);if(a<10)return 0;let s=Math.max(ANIM_MIN_SPD,ANIM_MAX_SPD-(a/ANIM_MAX_W)*(ANIM_MAX_SPD-ANIM_MIN_SPD));if(a>=3000)s*=0.4;else if(a>=2000)s*=0.6;else if(a>=1000)s*=0.8;return s;}
_sf(el,id,p,d,c,o){if(!el)return;if(Math.abs(p)<10){this._sa(el,'opacity','0');return;}this._sa(el,'stroke',c);this._sa(el,'opacity',o);if(this._fs[id]!==d){this._fs[id]=d;el.setAttribute('class','fa '+d);this._resync=true;}if(this._c.flow_speed!=='synced'){const fp=this._fsp||(this._fsp={});const sp=Math.min(6,Math.max(0.5,2500/Math.abs(p)));const o0=fp[id];if(!o0||Math.abs(sp-o0)/o0>0.15){fp[id]=sp;el.style.setProperty('--spd',sp.toFixed(2)+'s');}}}
_tween(id,target,fmt){const el=this._$(id);if(!el)return;if(target===null||(this._rm&&this._c.animations!=='always')){delete this._twq[id];el.textContent=fmt(target);this._twv[id]=target;return;}const from=this._twv[id];if(from===undefined||from===null||Math.abs(target-from)<1){delete this._twq[id];el.textContent=fmt(target);this._twv[id]=target;return;}this._twq[id]={el,from,to:target,fmt,t0:performance.now()};if(!this._twRaf)this._twRaf=requestAnimationFrame(t=>this._twStep(t));}
_twStep(t){this._twRaf=null;const q=this._twq;let live=false;for(const id in q){const w=q[id];let k=Math.min(1,(t-w.t0)/600);k=1-Math.pow(1-k,3);w.el.textContent=w.fmt(w.from+(w.to-w.from)*k);if(k<1)live=true;else{this._twv[id]=w.to;delete q[id];}}if(live)this._twRaf=requestAnimationFrame(t2=>this._twStep(t2));}
_spark(id,aid,data,mx){const el=this._$(id);const af=this._$(aid);if(!el||!data.length)return;const w=200,h=55,py=2,max=mx||Math.max(...data)||1;const pts=data.map((v,i)=>[(i/(data.length-1))*w,py+(1-v/max)*(h-py*2)]);if(pts.length<2)return;const tension=0.3;const cp=(p0,p1,p2,t)=>[p1[0]+(p2[0]-p0[0])*t,p1[1]+(p2[1]-p0[1])*t];let d='M'+pts[0][0].toFixed(1)+','+pts[0][1].toFixed(1);for(let i=0;i<pts.length-1;i++){const p0=pts[Math.max(0,i-1)];const p1=pts[i];const p2=pts[i+1];const p3=pts[Math.min(pts.length-1,i+2)];const c1=cp(p0,p1,p2,tension);const c2=[p2[0]-(p3[0]-p1[0])*tension,p2[1]-(p3[1]-p1[1])*tension];d+=' C'+c1[0].toFixed(1)+','+c1[1].toFixed(1)+' '+c2[0].toFixed(1)+','+c2[1].toFixed(1)+' '+p2[0].toFixed(1)+','+p2[1].toFixed(1);}el.setAttribute('d',d);if(af){af.setAttribute('d',d+'L'+w+','+h+'L0,'+h+'Z');}}
_drawSparks(){const H=this._hist;let mx=0;if(this._c.sparkline_shared_scale){for(const k of ['solar','load','grid','battery']){const a=H[k];if(a&&a.length){const m=Math.max(...a);if(m>mx)mx=m;}}mx=mx||1;}const M=mx||undefined;this._sparkM=M;this._spark('hs','hsa',H.solar,M);this._spark('hl','hla',H.load,M);this._spark('hg','hga',H.grid,M);this._spark('hb2','hb2a',H.battery,M);}
_update(){if(!this._h||!this.shadowRoot.getElementById('vs'))return;
const c=this._c,L=this._lang;
const sol1=this._gp(c.solar);
const sol2=this._gp(c.solar2);
const sol3=this._gp(c.solar3);
const _sv=[sol1,sol2,sol3].filter(v=>v!==null);
const sol=_sv.length?_sv.reduce((a,b)=>a+b,0):null;
const batCh=this._gp(c.battery_charge);
const batDis=this._gp(c.battery_discharge);
const bat=(batCh!==null||batDis!==null)?(batDis??0)-(batCh??0):this._norm(this._gp(c.battery),'bat');
const soc=this._gv(c.soc);
let grid=this._norm(this._gp(c.grid),'grid');const gth=Number(c.grid_threshold)||0;if(grid!==null&&Math.abs(grid)<gth)grid=0;
const load=this._gp(c.load);
const temp=this._gv(c.temperature);
const gv=this._gv(c.grid_voltage);
const gv2=this._gv(c.grid_voltage_l2);
const gv3=this._gv(c.grid_voltage_l3);
const bv=this._gv(c.battery_voltage);
const pvv=this._gv(c.pv_voltage);
const pvv2=this._gv(c.pv_voltage2);
const pvv3=this._gv(c.pv_voltage3);
const freq=this._gv(c.frequency);
const p=this._prev;
this._tween('vs',sol,v=>this._fmt(v));