I have created this status block. Intent is to go to home view, if you click on home icon and to settings view if you click on gear.
Enclosed code works great in my browser. If I click exactly on gear, the settings view opens. Clicking somewhere else is going back to home.
Unfortunately, this is not working on the (iPhone) app. My approach is a tap_action on the total button-card and a specific custom field for the specific gear button. My guess is that since the home and gear areas are overlapping it’s luck that this works in browser.
I would like to create 3 areas (left, middle and right). Left and right slightly bigger than icon and rest is middle. In this case I will link left and middle both to the home view. Three columns will give me flexibility for the future.
Tried a lot but was not successful. Can someone provide a tip on how to define the 3 areas
Thanks.
type: custom:mod-card
card_mod:
style: |
ha-card { width: 380px; margin: 0; }
card:
type: vertical-stack
cards:
- type: custom:button-card
name: Warm water
show_state: false
show_icon: false
tap_action:
action: navigate
navigation_path: /huis/0
styles:
grid:
- grid-template-areas: "'home n gear' 'info info info'"
- grid-template-columns: 40px 1fr 40px
- grid-template-rows: auto 1fr
card:
- padding: 4px
- height: 180px
- width: 380px
- overflow: hidden
name:
- font-size: 14px
- font-weight: bold
- text-align: left
- padding-bottom: 4px
custom_fields:
home:
- display: flex
- align-items: center
- justify-content: center
gear:
- display: flex
- align-items: center
- justify-content: center
info:
- padding: 4px
custom_fields:
home: >
<ha-icon icon="mdi:home" style="color: var(--primary-color); width:
38px; height: 38px;"></ha-icon>
gear: |
[[[
// Alarm-badge op het tandwiel + eigen navigatie (geen bubbels)
const anyAlarm =
states['binary_sensor.glbal_qube2']?.state === 'on' ||
states['binary_sensor.al_maxtime_antileg_active_qube2']?.state === 'on' ||
states['binary_sensor.al_maxtime_dhw_active_qube2']?.state === 'on';
const alarmDot = anyAlarm
? '<span style="position:absolute; top:-2px; right:-2px; width:10px; height:10px; background:var(--error-color); border-radius:50%; box-shadow:0 0 0 2px white;"></span>'
: '';
return `
<div style="position:relative; width:38px; height:38px; display:flex; align-items:center; justify-content:center; cursor:pointer;"
onclick="event.stopPropagation(); window.history.pushState(null,'','/huis/wp2-instelling'); window.dispatchEvent(new Event('location-changed'));">
<ha-icon icon="mdi:cog" style="width:22px; height:22px; color: var(--primary-text-color);"></ha-icon>
${alarmDot}
</div>
`;
]]]
info: |
[[[
// ===== Helpers =====
const safeNum = (eid,d=1)=>{const s=states[eid]?.state;if(!s||s==='unknown'||s==='unavailable')return null;const v=parseFloat(s);return isNaN(v)?null:parseFloat(v.toFixed(d));};
const safeTxt = (eid)=>{const s=states[eid]?.state;return (s&&s!=='unknown'&&s!=='unavailable')?s:'';};
// ===== Entities =====
const statusCode = parseInt(states['sensor.unitstatus_qube2']?.state ?? 'NaN'); // 0,1,6,8,9,15,16,17,22
const serviceTxt = safeTxt('sensor.qube_driewegklep_dhw_cv_status_2'); // 'CV' of 'SWW'
const tDHWT = safeNum('sensor.dhw_temp_qube2', 1);
// Alarm => rood randje om de info-zone
const anyAlarm =
states['binary_sensor.glbal_qube2']?.state === 'on' ||
states['binary_sensor.al_maxtime_antileg_active_qube2']?.state === 'on' ||
states['binary_sensor.al_maxtime_dhw_active_qube2']?.state === 'on';
const alarmStyle = anyAlarm ? 'box-shadow: inset 0 0 0 2px var(--error-color); border-radius: 8px;' : '';
// ===== Kleur per status =====
const colorByStatus = {
0:'#000000', 1:'#1e88e5', 6:'#e53935',
8:'#43a047', 9:'#43a047',
15:'#9e9e9e', 16:'#9e9e9e',
17:'#f9a825', 22:'#43a047'
};
const iconColor = colorByStatus[statusCode] ?? '#607d8b';
// ===== Icoonkeuze volgens tabel =====
const pickIcon = (code, service)=>{
if ([0,1,6].includes(code)) return 'mdi:hot-tub'; // altijd SWW
if (code === 22) return 'mdi:hot-tub'; // Heating DHW
return (service === 'CV') ? 'mdi:radiator' : 'mdi:hot-tub';
};
const statusIcon = pickIcon(statusCode, serviceTxt);
// ===== Gauge (klok) =====
const SCALE_MIN = 20, SCALE_MAX = 70;
const START_DEG = -120, END_DEG = 120;
const CX = 60, CY = 60, R = 46;
const clamp=(v,min,max)=>Math.max(min,Math.min(max,v));
const mapTempToDeg=(T)=>{ if(T==null)return START_DEG; const p=(clamp(T,SCALE_MIN,SCALE_MAX)-SCALE_MIN)/(SCALE_MAX-SCALE_MIN); return START_DEG + p*(END_DEG-START_DEG); };
const polarToXY=(cx,cy,r,deg)=>{ const rad=(deg-90)*Math.PI/180; return [cx+r*Math.cos(rad), cy+r*Math.sin(rad)]; };
const arcPath=(cx,cy,r,d1,d2)=>{ const [x1,y1]=polarToXY(cx,cy,r,d1); const [x2,y2]=polarToXY(cx,cy,r,d2); const la=(Math.abs(d2-d1)>180)?1:0; const sw=d2>d1?1:0; return `M ${x1.toFixed(1)} ${y1.toFixed(1)} A ${r} ${r} 0 ${la} ${sw} ${x2.toFixed(1)} ${y2.toFixed(1)}`; };
// Zones (vast)
const A1s=START_DEG, A1e=mapTempToDeg(40); // geel
const A2s=A1e, A2e=mapTempToDeg(55); // groen
const A3s=A2e, A3e=mapTempToDeg(62); // donkergroen
const A4s=A3e, A4e=END_DEG; // rood
const needleDeg = mapTempToDeg(tDHWT ?? SCALE_MIN);
const [nx, ny] = polarToXY(CX, CY, R-6, needleDeg);
const C_Y='#f9a825', C_G='#43a047', C_DG='#1b5e20', C_R='#e53935', C_RING='#e0e0e0';
// Linker status icoon (groot, verticaal gecentreerd)
const colStatus = `
<div style="flex:1; display:flex; flex-direction:column; align-items:center; justify-content:center; height:120px;">
<ha-icon icon="${statusIcon}" style="color:${iconColor}; width:64px; height:64px;"></ha-icon>
</div>
`;
// Gauge in het midden + temperatuur dichter tegen de gauge
const dhwText = (tDHWT!=null) ? `${tDHWT.toFixed(1)} °C` : '...';
const colDHW = `
<div style="text-align:center; flex:1;">
<svg width="120" height="120" viewBox="0 0 120 120">
<path d="${arcPath(CX,CY,R, START_DEG, END_DEG)}" stroke="${C_RING}" stroke-width="10" fill="none" />
<path d="${arcPath(CX,CY,R, A1s, A1e)}" stroke="${C_Y}" stroke-width="10" fill="none" />
<path d="${arcPath(CX,CY,R, A2s, A2e)}" stroke="${C_G}" stroke-width="10" fill="none" />
<path d="${arcPath(CX,CY,R, A3s, A3e)}" stroke="${C_DG}" stroke-width="10" fill="none" />
<path d="${arcPath(CX,CY,R, A4s, A4e)}" stroke="${C_R}" stroke-width="10" fill="none" />
${[20,30,40,50,60,70].map(v=>{
const deg=mapTempToDeg(v); const [tx,ty]=polarToXY(CX,CY,R+8,deg);
return `<text x='${tx.toFixed(1)}' y='${ty.toFixed(1)}' text-anchor='middle' alignment-baseline='middle' font-size='10' fill='#555'>${v}</text>`;
}).join('')}
<circle cx="${CX}" cy="${CY}" r="4" fill="#555"/>
<line x1="${CX}" y1="${CY}" x2="${nx.toFixed(1)}" y2="${ny.toFixed(1)}" stroke="#111" stroke-width="3" stroke-linecap="round"/>
</svg>
<div style="margin-top:-16px; line-height:1; font-size:16px; font-weight:600;">${dhwText}</div>
</div>
`;
// Wrapper met alarmstijl
return `
<div style="display:flex; align-items:center; justify-content:space-between; width:100%; ${alarmStyle}">
<div style="display:flex; gap:8px; flex:1;">
${colStatus}
${colDHW}
</div>
</div>
`;
]]]


