Compare commits

..

2 Commits

Author SHA1 Message Date
root 9e061b7078 Fix lovelace url_path: cubeops -> cube-ops
HA requires a dashboard's url_path to contain a hyphen, and a key that
fails that check invalidates the entire `lovelace:` block rather than
just its own entry. `cubeops:` was added in 0c35647, so `ha core check`
has been failing since then and all four YAML dashboards -- Voice Alarms,
Family Hub, At-a-Glance and CubeOps -- were refusing to load.

Rename the key to `cube-ops`, and rename the two files alongside it so
they match, which is the convention the other three already follow
(voice-alarms -> voice-alarms.yaml).

This changes the kiosk URL to https://home.vault983.com/cube-ops. Any
wall display still pointed at /cubeops needs updating.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 11:41:17 -04:00
root 92b68ca21a Style kiosk dashboards with the CubeCraft Creations display system
The design system was only ever expressed in the production card's
stylesheet (custom_components/cubecraft_production/www/
cubecraft-production-card.js), which is gitignored, so the dashboards had
no way to share it. Mirror those tokens into an HA theme and apply the
card's own motifs to the wall panels.

themes/cubecraft.yaml carries the brand gradient, Nunito, the 4px scale,
radii, shadows and semantic colours. It uses `modes:` so it follows HA's
light/dark setting: light is the system as authored, dark uses
violet-tinted neutrals with the primary lifted to #8B8AF2 (5.6:1 on the
card surface) since #3B3AB8 is unreadable as an accent on dark. The
gradient itself is unchanged in both modes -- white clears AA across its
whole span.

At-a-Glance, Family Hub and Voice Alarms pick up three motifs from the
card: the .title gradient bar, .column header section labels, and the
4px brand left rule on each tile. Tile colour is cut back to brand
indigo, with green/amber/red reserved for real signals (printer offline,
bin full) -- the same discipline the card uses, where only a blocked
order goes red. CubeOps gets the theme too, so the board and the
dashboards land on the same ramp instead of the card improvising against
HA's defaults.

The card injects its @font-face only on its own page, so a small frontend
module declares Nunito document-wide. It points at the woff2 the
integration already serves rather than vendoring a second copy, which
needed a .gitignore carve-out since www/ is excluded wholesale.

dakboard-dark is left in place; switching a dashboard back is a one-line
change.

Not yet validated by `ha core check` -- this worktree is outside the
config tree HA actually loads.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 11:27:35 -04:00
10 changed files with 712 additions and 59 deletions
+10 -1
View File
@@ -112,7 +112,16 @@ home-assistant_v2.db-wal
# Dependencies and cache
deps/
tts/
www/
# www/ is HA's public static dir: HACS frontend plugins, camera snapshots and
# TTS junk all land in it, so it stays ignored — except for the small
# hand-written frontend modules we actually author and want in git.
# (git will not descend into an excluded directory, so each level has to be
# re-included before the next one can be narrowed again.)
!www/
www/*
!www/cubecraft/
www/cubecraft/*
!www/cubecraft/*.js
custom_components/
image/
ms365_storage/
+6
View File
@@ -7,6 +7,12 @@ default_config:
# Load frontend themes from the themes folder
frontend:
themes: !include_dir_merge_named themes
# Declares the Nunito @font-face document-wide for the CubeCraft display
# system. A theme can only set font-family, not load the face — see
# www/cubecraft/cubecraft-brand-font.js. Bump ?v= after editing that file to
# get past the browser cache.
extra_module_url:
- /local/cubecraft/cubecraft-brand-font.js?v=1
lovelace:
dashboards: !include dashboards/dashboards.yaml
@@ -2,4 +2,4 @@ mode: yaml
title: CubeOps
icon: mdi:alarm-panel
show_in_sidebar: true
filename: dashboards/views/cubeops-dashboard.yaml
filename: dashboards/views/cube-ops-dashboard.yaml
+5 -1
View File
@@ -1,4 +1,8 @@
voice-alarms: !include voice-alarms.yaml
family-hub: !include family-hub.yaml
at-a-glance: !include at-a-glance.yaml
cubeops: !include cubeops.yaml
# Each key is the dashboard's url_path, and HA requires it to contain a hyphen
# (see homeassistant/components/lovelace: url_path validation). A hyphen-less
# key fails validation for the whole `lovelace:` block, not just its own entry,
# which takes every dashboard here down with it.
cube-ops: !include cube-ops.yaml
+118 -17
View File
@@ -1,13 +1,33 @@
# At-a-Glance — wall panel
#
# Styled with the CubeCraft Creations display system. The tokens live in
# themes/cubecraft.yaml, which mirrors the `:host` block of the production
# card's stylesheet (custom_components/cubecraft_production/www/
# cubecraft-production-card.js). Anything referenced here as --cc-* comes from
# that theme, so this file never hard-codes a brand hex.
#
# Three motifs carry the style, all lifted from the production card:
# .title → gradient title bar, white 800-weight type, brand radius
# .column header → uppercase 700/.05em muted section labels
# article → 8px radius, 4px brand left rule, shadow-sm on each tile
#
# Colour is deliberately restrained: brand indigo for anything informational,
# and green/amber/red kept in reserve so they still mean something when they
# show up. That is the same discipline the production card uses, where only a
# blocked order goes red.
title: At-a-Glance
theme: dakboard-dark
theme: cubecraft
views:
- title: At-a-Glance
path: at-a-glance
icon: mdi:eye-outline
theme: dakboard-dark
theme: cubecraft
type: masonry
cards:
# ── Header ──
# ── Brand title bar ──
# Mirrors `.shell.kiosk .title`: gradient ground, white type at 1.75rem,
# brand radius and the brand shadow. ha-markdown supplies its own 16px
# inset, so ha-card only tops that up to roughly the card's 20px/28px.
- type: markdown
content: |
# {{ now().strftime("%A, %B %d, %Y") }}
@@ -15,21 +35,28 @@ views:
card_mod:
style: |
ha-card {
background: var(--cc-gradient);
color: #fff;
border: none;
border-radius: var(--cc-radius-brand);
box-shadow: var(--cc-shadow-brand);
padding: 6px 12px;
text-align: center;
background: none;
box-shadow: none;
padding: 0 0 4px 0;
}
ha-card h1 {
font-size: 1.2em;
margin: 0;
font-weight: 300;
color: var(--secondary-text-color);
font-size: 1rem;
font-weight: 600;
letter-spacing: 0.02em;
opacity: 0.85;
color: #fff;
}
ha-card h2 {
font-size: 2em;
margin: 2px 0 0 0;
font-weight: 400;
font-size: 1.75rem;
font-weight: 800;
letter-spacing: -0.01em;
color: #fff;
}
# ── Weather Now ──
@@ -41,127 +68,200 @@ views:
round_temperature: true
# ── Indoor Climate ──
# First use of the section-label treatment; the rest of the file aliases
# it as *cc_section. Selectors are listed broadly on purpose — a heading
# card's internals are not a stable API, and any selector that misses
# simply leaves that bit at the theme default rather than breaking.
- type: heading
heading: Indoor Climate
heading_style: title
icon: mdi:home-thermometer-outline
card_mod: &cc_section
style: |
ha-card {
background: none;
border: none;
box-shadow: none;
padding: 0;
margin: 4px 0 -4px 0;
}
ha-card .heading,
ha-card .content p,
ha-card p {
text-transform: uppercase;
letter-spacing: 0.05em;
font-weight: 700;
font-size: 0.9375rem;
color: var(--cc-text-muted, var(--secondary-text-color));
}
ha-card ha-icon,
ha-card ha-svg-icon {
color: var(--cc-accent, var(--primary-color));
}
- type: grid
columns: 3
square: false
cards:
# First use of the `article` treatment; aliased as *cc_tile below.
- type: tile
entity: sensor.living_room_temperature
name: Living Room
icon: mdi:sofa
color: amber
color: primary
card_mod: &cc_tile
style: |
ha-card {
border-radius: var(--cc-radius-md);
border-left: 4px solid var(--cc-accent);
box-shadow: var(--cc-shadow-sm);
}
- type: tile
entity: sensor.office_temperature
name: Office
icon: mdi:desk
color: teal
color: primary
card_mod: *cc_tile
- type: tile
entity: sensor.bedroom_temperature
name: Master Bedroom
icon: mdi:bed
color: purple
color: primary
card_mod: *cc_tile
- type: tile
entity: sensor.upstairs_temperature
name: Upstairs
icon: mdi:stairs
color: indigo
color: primary
card_mod: *cc_tile
- type: tile
entity: sensor.blink_family_room_temperature
name: Family Room
icon: mdi:television
color: red
color: primary
card_mod: *cc_tile
- type: tile
entity: sensor.blink_kitchen_temperature
name: Kitchen
icon: mdi:stove
color: deep-orange
color: primary
card_mod: *cc_tile
# ── 3D Printer Status ──
- type: heading
heading: 3D Printers
heading_style: title
icon: mdi:printer-3d-nozzle
card_mod: *cc_section
- type: grid
columns: 2
square: false
cards:
# Online/offline is a genuine success signal, so these keep green and
# take the success-coloured left rule.
- type: tile
entity: binary_sensor.p1s_01p09c470102673_online
name: PrintsCharming
icon: mdi:printer-3d-nozzle
show_entity_picture: false
color: green
card_mod: &cc_tile_success
style: |
ha-card {
border-radius: var(--cc-radius-md);
border-left: 4px solid var(--success-color);
box-shadow: var(--cc-shadow-sm);
}
- type: tile
entity: binary_sensor.a1_03919d540806387_online
name: Ds-Turd-Flinger
icon: mdi:printer-3d-nozzle
show_entity_picture: false
color: green
card_mod: *cc_tile_success
- type: tile
entity: binary_sensor.a1mini_0309ca580403653_online
name: Polly-Pocket
icon: mdi:printer-3d-nozzle
show_entity_picture: false
color: green
card_mod: *cc_tile_success
- type: tile
entity: binary_sensor.constipation_orion_online
name: Constipation-Orion
icon: mdi:printer-3d-nozzle
show_entity_picture: false
color: green
card_mod: *cc_tile_success
- type: tile
entity: binary_sensor.jordyns_layer_slayer_online
name: Jordyn's Layer Slayer
icon: mdi:printer-3d-nozzle
show_entity_picture: false
color: green
card_mod: *cc_tile_success
- type: tile
entity: binary_sensor.centauri_carbon_sdcp_status
name: Centauri Carbon
icon: mdi:printer-3d-nozzle
show_entity_picture: false
color: green
card_mod: *cc_tile_success
# ── Litter Boxes ──
- type: heading
heading: Litter Boxes
heading_style: title
icon: mdi:cat
card_mod: *cc_section
- type: grid
columns: 2
square: false
cards:
# These are the "something needs doing" tiles — the equivalent of a
# blocked order in the production card, so they take the error rule.
- type: tile
entity: binary_sensor.cat_crapper_3000_wastebin_filled
name: Cat Crapper 3000
icon: mdi:delete-empty
show_entity_picture: false
color: red
card_mod: &cc_tile_alert
style: |
ha-card {
border-radius: var(--cc-radius-md);
border-left: 4px solid var(--error-color);
box-shadow: var(--cc-shadow-sm);
}
- type: tile
entity: binary_sensor.cat_crapper_3000_sand_lack
name: CC3000 Litter
icon: mdi:grain
show_entity_picture: false
color: amber
card_mod:
style: |
ha-card {
border-radius: var(--cc-radius-md);
border-left: 4px solid var(--warning-color);
box-shadow: var(--cc-shadow-sm);
}
- type: tile
entity: binary_sensor.poop_box_wastebin_filled
name: Poop Box
icon: mdi:delete-empty
show_entity_picture: false
color: red
card_mod: *cc_tile_alert
- type: tile
entity: binary_sensor.triple_threat_wastebin_filled
name: Triple Threat
icon: mdi:delete-empty
show_entity_picture: false
color: red
card_mod: *cc_tile_alert
# ── Security ──
- type: heading
@@ -181,6 +281,7 @@ views:
- type: entity
entity: alarm_control_panel.blink_rear
name: Rear
card_mod: *cc_section
# ── Recent Activity ──
- type: logbook
@@ -1,10 +1,12 @@
# CubeOps — Cubecraft Production workboard
#
# Registered as the `cubeops` dashboard in dashboards/dashboards.yaml (mode:
# Registered as the `cube-ops` dashboard in dashboards/dashboards.yaml (mode:
# yaml), so this file is the source of truth. Do not edit it through HA's raw
# configuration editor — that writes to storage and cannot come back to git.
#
# Kiosk URL: https://home.vault983.com/cubeops
# Kiosk URL: https://home.vault983.com/cube-ops
# (Renamed from /cubeops — HA rejects a url_path with no hyphen. Any wall
# display still pointed at the old URL needs updating.)
#
# The card polls every 30 seconds, so a display left open stays current and
# picks back up when a kiosk rotation returns to it.
@@ -13,12 +15,21 @@
# and registers its own Lovelace resource, so there is nothing to add under
# Settings > Dashboards > Resources.
# kiosk-mode plugin (HACS > Frontend). Without it the board still fills the
# screen; you just keep HA's header and sidebar.
# kiosk-mode plugin (HACS > Frontend). NOT currently installed — there is no
# lovelace-kiosk-mode under www/community — so this block is inert today.
# `theme: cubecraft` below already collapses the header via header-height, so
# installing the plugin now only buys you hide_sidebar.
kiosk_mode:
hide_header: true
hide_sidebar: true
# The CubeCraft display system. The card reads HA's theme variables for its
# surfaces, text and borders (see the `--cc-surface` fallbacks in
# cubecraft-production-card.js), so setting this makes the board and the rest
# of the dashboards land on exactly the same ramp instead of the card
# improvising against HA's defaults.
theme: cubecraft
views:
- title: Production
# Panel mode gives the card the whole viewport with no dashboard padding.
+95 -33
View File
@@ -1,41 +1,58 @@
# Family Hub — wall panel
#
# Styled with the CubeCraft Creations display system; see
# dashboards/views/at-a-glance-dashboard.yaml for the motif notes and
# themes/cubecraft.yaml for the tokens. Everything brand-coloured here comes
# through --cc-* variables rather than a literal hex.
title: Family Hub
theme: dakboard-dark
theme: cubecraft
views:
- title: Family Hub
path: family-hub
icon: mdi:home-heart
theme: dakboard-dark
theme: cubecraft
type: masonry
cards:
# ── Date & Greeting Header ──
# ── Brand greeting bar ──
# The production card's `.title` treatment, carrying the date and a
# time-of-day greeting instead of a board name.
- type: markdown
content: |
# {{ now().strftime("%A, %B %d") }}
{% set h = now().hour %}
{% if h < 12 %}Good morning{% elif h < 17 %}Good afternoon{% else %}Good evening{% endif %}, family!
## {% if h < 12 %}Good morning{% elif h < 17 %}Good afternoon{% else %}Good evening{% endif %}, family!
entity_id:
- sensor.time
card_mod:
style: |
ha-card {
background: var(--cc-gradient);
color: #fff;
border: none;
border-radius: var(--cc-radius-brand);
box-shadow: var(--cc-shadow-brand);
padding: 6px 12px;
text-align: center;
background: none;
box-shadow: none;
padding: 0 0 8px 0;
}
ha-card h1 {
font-size: 1.4em;
margin: 0 0 4px 0;
font-weight: 300;
color: var(--secondary-text-color);
}
ha-card p {
font-size: 1.1em;
margin: 0;
font-weight: 400;
font-size: 1rem;
font-weight: 600;
letter-spacing: 0.02em;
opacity: 0.85;
color: #fff;
}
ha-card h2 {
margin: 2px 0 0 0;
font-size: 1.75rem;
font-weight: 800;
letter-spacing: -0.01em;
color: #fff;
}
entity_id:
- sensor.time
# ── Clock ──
# Left on the sunken surface rather than a card, so the gradient bar above
# stays the only heavyweight element in the column.
- type: clock
clock_size: large
clock_style: analog
@@ -48,6 +65,7 @@ views:
style: |
ha-card {
background: none;
border: none;
box-shadow: none;
text-align: center;
}
@@ -73,8 +91,11 @@ views:
card_mod:
style: |
ha-card {
--calendar-day-color: var(--primary-text-color);
--calendar-day-weekday-color: var(--secondary-text-color);
--calendar-day-color: var(--cc-text, var(--primary-text-color));
--calendar-day-weekday-color: var(--cc-text-muted, var(--secondary-text-color));
--fc-today-bg-color: var(--cc-accent-soft);
--fc-event-bg-color: var(--cc-accent);
--fc-event-border-color: var(--cc-accent);
}
# ── Quick Glance ──
@@ -92,64 +113,104 @@ views:
- type: entity
entity: sensor.office_temperature
name: Office
card_mod: &cc_section
style: |
ha-card {
background: none;
border: none;
box-shadow: none;
padding: 0;
margin: 4px 0 -4px 0;
}
ha-card .heading,
ha-card .content p,
ha-card p {
text-transform: uppercase;
letter-spacing: 0.05em;
font-weight: 700;
font-size: 0.9375rem;
color: var(--cc-text-muted, var(--secondary-text-color));
}
ha-card ha-icon,
ha-card ha-svg-icon {
color: var(--cc-accent, var(--primary-color));
}
- type: grid
columns: 3
square: false
cards:
# Outdoor temp
# Temperatures are informational, so they all take the brand rule
# rather than a colour each — see the colour note in at-a-glance.
- type: tile
entity: sensor.openweathermap_temperature
name: Outside
icon: mdi:thermometer
show_entity_picture: false
color: blue
# Living Room
color: primary
card_mod: &cc_tile
style: |
ha-card {
border-radius: var(--cc-radius-md);
border-left: 4px solid var(--cc-accent);
box-shadow: var(--cc-shadow-sm);
}
- type: tile
entity: sensor.living_room_temperature
name: Living Room
icon: mdi:sofa
show_entity_picture: false
color: amber
# Office
color: primary
card_mod: *cc_tile
- type: tile
entity: sensor.office_temperature
name: Office
icon: mdi:desk
show_entity_picture: false
color: teal
# Master Bedroom
color: primary
card_mod: *cc_tile
- type: tile
entity: sensor.bedroom_temperature
name: Master Bedroom
icon: mdi:bed
show_entity_picture: false
color: purple
# Upstairs
color: primary
card_mod: *cc_tile
- type: tile
entity: sensor.upstairs_temperature
name: Upstairs
icon: mdi:stairs
show_entity_picture: false
color: indigo
# Family Room (Blink)
color: primary
card_mod: *cc_tile
- type: tile
entity: sensor.blink_family_room_temperature
name: Family Room
icon: mdi:television
show_entity_picture: false
color: red
color: primary
card_mod: *cc_tile
# ── Shopping List ──
- type: heading
heading: Shopping List
heading_style: title
icon: mdi:cart-outline
card_mod: *cc_section
- type: todo-list
entity: todo.shopping_list
title: Shopping List
hide_completed: true
display_order: none
# ── Chore Tracker Quick View ──
- type: heading
heading: Chores Today
heading_style: title
icon: mdi:check-circle-outline
card_mod: *cc_section
- type: entities
title: Chores Today
show_header_toggle: false
entities:
- entity: binary_sensor.cat_medication_tracker_all_cats_medicated
@@ -171,3 +232,4 @@ views:
- type: entity
entity: binary_sensor.upstairs_occupancy
name: Upstairs
card_mod: *cc_section
+108 -2
View File
@@ -1,23 +1,111 @@
# Voice Alarms — satellite control panel
#
# Styled with the CubeCraft Creations display system; see
# dashboards/views/at-a-glance-dashboard.yaml for the motif notes and
# themes/cubecraft.yaml for the tokens.
#
# This one is a control panel rather than a glanceable board, so the per-tile
# left rule is dropped — the entity rows already carry their own structure and
# a rule on every card would just add noise. It keeps the gradient title bar
# and the section labels.
#
# Note that the theme collapses the HA toolbar (header-height: 0). This
# dashboard is reached by URL rather than the sidebar (show_in_sidebar: false
# in dashboards/voice-alarms.yaml), so navigate away via the sidebar.
title: Voice Alarms
theme: cubecraft
views:
- title: Voice Alarms
path: voice-alarms
icon: mdi:alarm-panel
theme: cubecraft
type: panel
cards:
- type: vertical-stack
cards:
# ── Brand title bar ──
- type: markdown
content: |
# Voice Alarms
## Jarvis & ReSpeaker satellites
card_mod:
style: |
ha-card {
background: var(--cc-gradient);
color: #fff;
border: none;
border-radius: var(--cc-radius-brand);
box-shadow: var(--cc-shadow-brand);
padding: 6px 12px;
}
ha-card h1 {
margin: 0;
font-size: 1.75rem;
font-weight: 800;
letter-spacing: -0.01em;
color: #fff;
}
ha-card h2 {
margin: 2px 0 0 0;
font-size: 1rem;
font-weight: 600;
letter-spacing: 0.02em;
opacity: 0.85;
color: #fff;
}
These alarms are managed by Home Assistant and announced on the selected
Jarvis and ReSpeaker satellites.
- type: markdown
content: >
These alarms are managed by Home Assistant and announced on the
selected Jarvis and ReSpeaker satellites.
card_mod:
style: |
ha-card {
background: none;
border: none;
box-shadow: none;
}
ha-card p {
margin: 0;
font-size: 0.9375rem;
line-height: 1.5;
color: var(--cc-text-muted, var(--secondary-text-color));
}
- type: heading
heading: Satellites
heading_style: title
icon: mdi:speaker-message
card_mod:
style: |
ha-card {
background: none;
border: none;
box-shadow: none;
padding: 0;
margin: 4px 0 -4px 0;
}
ha-card .heading,
ha-card .content p,
ha-card p {
text-transform: uppercase;
letter-spacing: 0.05em;
font-weight: 700;
font-size: 0.9375rem;
color: var(--cc-text-muted, var(--secondary-text-color));
}
ha-card ha-icon,
ha-card ha-svg-icon {
color: var(--cc-accent, var(--primary-color));
}
- type: grid
columns: 2
square: false
cards:
# Card titles take the production card's column-header treatment:
# uppercase, 700, .05em tracking, muted. First use defines the
# anchor the other three alias.
- type: entities
title: Office Jarvis
show_header_toggle: false
@@ -30,6 +118,21 @@ views:
name: Alarm time
- entity: script.test_voice_alarm_office_jarvis
name: Test alarm
card_mod: &cc_panel
style: |
ha-card {
border-radius: var(--cc-radius-brand);
box-shadow: var(--cc-shadow-md);
}
ha-card .card-header,
ha-card h1.card-header {
text-transform: uppercase;
letter-spacing: 0.05em;
font-weight: 700;
font-size: 0.9375rem;
color: var(--cc-text-muted, var(--secondary-text-color));
padding-bottom: 4px;
}
- type: entities
title: ReSpeaker Family Room
@@ -43,6 +146,7 @@ views:
name: Alarm time
- entity: script.test_voice_alarm_respeaker_family_room
name: Test alarm
card_mod: *cc_panel
- type: entities
title: ReSpeaker Living Room
@@ -56,6 +160,7 @@ views:
name: Alarm time
- entity: script.test_voice_alarm_respeaker_living_room
name: Test alarm
card_mod: *cc_panel
- type: entities
title: ReSpeaker Master Bedroom
@@ -69,3 +174,4 @@ views:
name: Alarm time
- entity: script.test_voice_alarm_respeaker_master_bedroom
name: Test alarm
card_mod: *cc_panel
+305
View File
@@ -0,0 +1,305 @@
# CubeCraft Creations display system — Home Assistant theme
#
# Source of truth for the tokens below is the production card's stylesheet:
# custom_components/cubecraft_production/www/cubecraft-production-card.js
# Keep the two in sync. The card reads HA theme variables for its surfaces, so
# once this theme is active the CubeOps board and the rest of a dashboard end up
# on the same surfaces, borders and text colours automatically.
#
# The theme carries `modes:`, so it follows HA's light/dark setting. The brand
# gradient, type, radii and semantic colours are identical in both modes; only
# the surface and text ramps flip.
#
# TUNED FOR WALL PANELS: `header-height: 0px` near the bottom collapses the HA
# toolbar, matching what dakboard-dark did on these dashboards. If you ever want
# CubeCraft on a phone or desktop where you still need the toolbar, copy this
# file to themes/cubecraft-desktop.yaml, rename the top-level key, and delete
# that one line. (HA themes cannot inherit from each other, and this repo's YAML
# is loaded by HA's own loader, so a `<<:` merge anchor is not worth the risk of
# taking the whole themes include down.)
#
# Hiding the sidebar as well needs the kiosk-mode frontend plugin (HACS), which
# is not currently installed — see dashboards/views/cube-ops-dashboard.yaml.
cubecraft:
# ─────────────────────────────────────────────────────────────────────────
# Brand tokens — mirrors the `:host` block in cubecraft-production-card.js.
# Exposed as --cc-* so dashboard card_mod blocks can reference them by name
# instead of hard-coding hexes in a dozen places.
# ─────────────────────────────────────────────────────────────────────────
cc-blue: "#3B3AB8"
cc-purple: "#A035CC"
cc-gradient: "linear-gradient(135deg, #3B3AB8 0%, #A035CC 100%)"
cc-primary-50: "#EBEBF8"
cc-primary-500: "#3B3AB8"
cc-primary-700: "#201F7A"
cc-neutral-0: "#FFFFFF"
cc-neutral-50: "#FAFAFA"
cc-neutral-100: "#F4F4F6"
cc-neutral-150: "#EDEDF1"
cc-neutral-200: "#E4E4EA"
cc-neutral-400: "#9898A8"
cc-neutral-600: "#52525E"
cc-neutral-900: "#18181D"
cc-success: "#16A34A"
cc-warning: "#D97706"
cc-warning-bg: "#FEF3C7"
cc-error: "#DC2626"
cc-error-bg: "#FEE2E2"
# 4px spacing scale
cc-space-1: "4px"
cc-space-2: "8px"
cc-space-3: "12px"
cc-space-4: "16px"
cc-space-5: "20px"
cc-space-6: "24px"
cc-space-7: "28px"
cc-radius-sm: "6px"
cc-radius-md: "8px"
cc-radius-lg: "12px"
cc-radius-xl: "16px"
cc-radius-brand: "20px"
cc-radius-full: "9999px"
cc-shadow-sm: "0 2px 6px 0 rgba(0,0,0,.08)"
cc-shadow-md: "0 4px 12px 0 rgba(0,0,0,.10)"
cc-shadow-brand: "0 4px 20px 0 rgba(59,58,184,.25)"
cc-ease: "cubic-bezier(0,0,.2,1)"
cc-dur: "200ms"
# ─────────────────────────────────────────────────────────────────────────
# Typography — Nunito 400..800.
# The @font-face has to be declared in the document, not a shadow root; that
# is what www/cubecraft/cubecraft-brand-font.js does (wired up via
# frontend.extra_module_url in configuration.yaml). Without it everything
# below falls back to system-ui and the rest of the theme still works.
# ─────────────────────────────────────────────────────────────────────────
cc-font: "'Nunito', 'Nunito Fallback', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif"
primary-font-family: "var(--cc-font)"
ha-font-family-body: "var(--cc-font)"
ha-font-family-heading: "var(--cc-font)"
mdc-typography-font-family: "var(--cc-font)"
paper-font-common-base_-_font-family: "var(--cc-font)"
paper-font-common-nowrap_-_font-family: "var(--cc-font)"
paper-font-body1_-_font-family: "var(--cc-font)"
paper-font-subhead_-_font-family: "var(--cc-font)"
paper-font-headline_-_font-family: "var(--cc-font)"
paper-font-title_-_font-family: "var(--cc-font)"
paper-font-caption_-_font-family: "var(--cc-font)"
# ─────────────────────────────────────────────────────────────────────────
# Shape — brand radius on cards, pills on controls.
# ─────────────────────────────────────────────────────────────────────────
ha-card-border-radius: "var(--cc-radius-brand)"
ha-card-border-width: "0"
ha-dialog-border-radius: "var(--cc-radius-brand)"
ha-badge-border-radius: "var(--cc-radius-full)"
mdc-shape-small: "var(--cc-radius-full)"
mdc-shape-medium: "var(--cc-radius-lg)"
mdc-shape-large: "var(--cc-radius-xl)"
button-border-radius: "var(--cc-radius-full)"
# ─────────────────────────────────────────────────────────────────────────
# Colour — brand ramp. Surfaces/text live in the mode blocks below.
# ─────────────────────────────────────────────────────────────────────────
success-color: "var(--cc-success)"
warning-color: "var(--cc-warning)"
error-color: "var(--cc-error)"
label-badge-green: "var(--cc-success)"
label-badge-yellow: "var(--cc-warning)"
label-badge-red: "var(--cc-error)"
# Text sitting on top of primary-color (buttons, selected states). White
# clears AA against both ends of the gradient (8.5:1 on the blue, 5.4:1 on
# the purple), so it is safe wherever the gradient is used.
text-primary-color: "#FFFFFF"
app-header-text-color: "#FFFFFF"
mdc-theme-on-primary: "#FFFFFF"
# Wall-panel chrome: collapse the HA toolbar. Delete this line for a variant
# you navigate by hand (see the header comment).
header-height: "0px"
modes:
# ═══════════════════════════════════════════════════════════════════════
# Light — the design system as authored: white surfaces, neutral sunken
# backgrounds, near-black text.
# ═══════════════════════════════════════════════════════════════════════
light:
cc-surface: "var(--cc-neutral-0)"
cc-surface-sunken: "var(--cc-neutral-100)"
cc-text: "var(--cc-neutral-900)"
cc-text-muted: "var(--cc-neutral-600)"
cc-border: "var(--cc-neutral-150)"
cc-accent: "var(--cc-primary-500)"
cc-accent-soft: "var(--cc-primary-50)"
primary-color: "#3B3AB8"
accent-color: "#A035CC"
dark-primary-color: "#201F7A"
light-primary-color: "#EBEBF8"
info-color: "#3B3AB8"
primary-text-color: "#18181D"
secondary-text-color: "#52525E"
disabled-text-color: "#9898A8"
primary-background-color: "#F4F4F6"
secondary-background-color: "#EDEDF1"
card-background-color: "#FFFFFF"
ha-card-background: "#FFFFFF"
divider-color: "#EDEDF1"
ha-card-box-shadow: "0 4px 12px 0 rgba(0,0,0,.10)"
app-header-background-color: "#3B3AB8"
more-info-header-background: "#F4F4F6"
sidebar-background-color: "#FFFFFF"
sidebar-text-color: "#52525E"
sidebar-icon-color: "#9898A8"
sidebar-selected-text-color: "#3B3AB8"
sidebar-selected-icon-color: "#3B3AB8"
state-icon-color: "#3B3AB8"
state-icon-active-color: "#A035CC"
state-unavailable-color: "#9898A8"
paper-item-icon-color: "#9898A8"
paper-item-icon-active-color: "#3B3AB8"
switch-checked-color: "#3B3AB8"
switch-checked-track-color: "#3B3AB8"
switch-unchecked-color: "#9898A8"
switch-unchecked-track-color: "#E4E4EA"
slider-color: "#3B3AB8"
slider-bar-color: "#E4E4EA"
label-badge-blue: "#3B3AB8"
label-badge-gray: "#9898A8"
label-badge-text-color: "#18181D"
ha-label-badge-background-color: "#FFFFFF"
ha-label-badge-border-color: "#EDEDF1"
table-row-background-color: "#FFFFFF"
table-row-alternative-background-color: "#FAFAFA"
markdown-code-background-color: "#F4F4F6"
code-editor-background-color: "#FAFAFA"
paper-card-background-color: "#FFFFFF"
paper-card-header-color: "#18181D"
paper-dialog-background-color: "#FFFFFF"
paper-listbox-background-color: "#FFFFFF"
input-fill-color: "#F4F4F6"
input-ink-color: "#18181D"
input-label-ink-color: "#52525E"
iron-overlay-backdrop-background-color: "rgba(24,24,29,.32)"
mdc-theme-primary: "#3B3AB8"
mdc-theme-secondary: "#A035CC"
mdc-theme-surface: "#FFFFFF"
# Weather glyphs, kept in the brand family rather than default sky blue.
weather-icon-sun-color: "#D97706"
weather-icon-rain-color: "#3B3AB8"
weather-icon-cloud-front-color: "#9898A8"
weather-icon-cloud-back-color: "#E4E4EA"
weather-icon-moon-color: "#52525E"
# ═══════════════════════════════════════════════════════════════════════
# Dark — violet-tinted neutrals so the brand gradient sits in the same
# family as the surfaces rather than floating on plain grey. The primary
# is lightened to #8B8AF2 (5.6:1 on the card surface); #3B3AB8 itself is
# too dark to read as an accent here, though it stays correct inside the
# gradient, which is left untouched.
# ═══════════════════════════════════════════════════════════════════════
dark:
cc-surface: "#1C1C27"
cc-surface-sunken: "#16161F"
cc-text: "#EDEDF1"
cc-text-muted: "#9898A8"
cc-border: "#2A2A38"
cc-accent: "#8B8AF2"
cc-accent-soft: "#252438"
primary-color: "#8B8AF2"
accent-color: "#C77BEE"
dark-primary-color: "#3B3AB8"
light-primary-color: "#C9C8FA"
info-color: "#8B8AF2"
primary-text-color: "#EDEDF1"
secondary-text-color: "#9898A8"
disabled-text-color: "#6A6A7C"
primary-background-color: "#0E0E14"
secondary-background-color: "#16161F"
card-background-color: "#1C1C27"
ha-card-background: "#1C1C27"
divider-color: "#2A2A38"
ha-card-box-shadow: "0 4px 12px 0 rgba(0,0,0,.45)"
app-header-background-color: "#16161F"
more-info-header-background: "#16161F"
sidebar-background-color: "#0E0E14"
sidebar-text-color: "#9898A8"
sidebar-icon-color: "#6A6A7C"
sidebar-selected-text-color: "#8B8AF2"
sidebar-selected-icon-color: "#8B8AF2"
state-icon-color: "#8B8AF2"
state-icon-active-color: "#C77BEE"
state-unavailable-color: "#6A6A7C"
paper-item-icon-color: "#6A6A7C"
paper-item-icon-active-color: "#8B8AF2"
switch-checked-color: "#8B8AF2"
switch-checked-track-color: "#8B8AF2"
switch-unchecked-color: "#6A6A7C"
switch-unchecked-track-color: "#2A2A38"
slider-color: "#8B8AF2"
slider-bar-color: "#2A2A38"
# Semantic colours lifted for dark surfaces; the light-mode values are
# too dense to read at a distance on #1C1C27.
success-color: "#4ADE80"
warning-color: "#FBBF24"
error-color: "#F87171"
label-badge-green: "#4ADE80"
label-badge-yellow: "#FBBF24"
label-badge-red: "#F87171"
label-badge-blue: "#8B8AF2"
label-badge-gray: "#6A6A7C"
label-badge-text-color: "#EDEDF1"
ha-label-badge-background-color: "#1C1C27"
ha-label-badge-border-color: "#2A2A38"
table-row-background-color: "#1C1C27"
table-row-alternative-background-color: "#20202C"
markdown-code-background-color: "#252433"
code-editor-background-color: "#16161F"
paper-card-background-color: "#1C1C27"
paper-card-header-color: "#EDEDF1"
paper-dialog-background-color: "#1C1C27"
paper-listbox-background-color: "#1C1C27"
input-fill-color: "#252433"
input-ink-color: "#EDEDF1"
input-label-ink-color: "#9898A8"
iron-overlay-backdrop-background-color: "rgba(0,0,0,.6)"
mdc-theme-primary: "#8B8AF2"
mdc-theme-secondary: "#C77BEE"
mdc-theme-surface: "#1C1C27"
weather-icon-sun-color: "#FBBF24"
weather-icon-rain-color: "#8B8AF2"
weather-icon-cloud-front-color: "#9898A8"
weather-icon-cloud-back-color: "#3A3A4A"
weather-icon-moon-color: "#C9C8FA"
+49
View File
@@ -0,0 +1,49 @@
// CubeCraft Creations brand font loader.
//
// The `cubecraft` theme sets Nunito as the font family, but a theme is only a
// bag of CSS variables — it cannot declare an @font-face. The production card
// declares one for itself (ensureBrandFont() in cubecraft-production-card.js),
// but only on the dashboard where that card is mounted, and only inside that
// page's document. Everything else falls back to system-ui.
//
// Loading this as a frontend module puts the declaration in the document head
// on every dashboard, so the whole UI gets Nunito.
//
// The .woff2 itself is NOT vendored here. It is already shipped and served by
// the cubecraft_production integration at STATIC_URL (see const.py: DOMAIN ->
// "/cubecraft_production"), which keeps a single copy of the file and one
// version to bump. If that integration is ever removed the URL 404s, the
// font-family falls through to the stack in the theme, and nothing else breaks.
//
// Wired up by `frontend.extra_module_url` in configuration.yaml.
const FONT_STYLE_ID = "cubecraft-brand-font";
const FONT_URL = "/cubecraft_production/nunito.woff2";
// extra_module_url modules are evaluated once per page load, but the card may
// have already injected its own copy under a different id — matching on the
// family as well keeps us from stacking duplicate @font-face rules.
function injectBrandFont() {
if (document.getElementById(FONT_STYLE_ID)) return;
if (document.getElementById("cubecraft-production-font")) return;
const style = document.createElement("style");
style.id = FONT_STYLE_ID;
// Variable weight range 400..800 — the card uses 400 (body), 600/700
// (labels, summaries) and 800 (titles, order numbers).
style.textContent =
"@font-face{" +
"font-family:'Nunito';" +
`src:url('${FONT_URL}') format('woff2');` +
"font-weight:400 800;" +
"font-style:normal;" +
"font-display:swap;" +
"}";
document.head.appendChild(style);
}
if (document.head) {
injectBrandFont();
} else {
document.addEventListener("DOMContentLoaded", injectBrandFont, { once: true });
}