diff --git a/.gitignore b/.gitignore index 069f615..3cffe35 100644 --- a/.gitignore +++ b/.gitignore @@ -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/ diff --git a/configuration.yaml b/configuration.yaml index 9ec639e..913ae1d 100644 --- a/configuration.yaml +++ b/configuration.yaml @@ -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 diff --git a/dashboards/views/at-a-glance-dashboard.yaml b/dashboards/views/at-a-glance-dashboard.yaml index 6b15d7c..c96ccc0 100644 --- a/dashboards/views/at-a-glance-dashboard.yaml +++ b/dashboards/views/at-a-glance-dashboard.yaml @@ -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 diff --git a/dashboards/views/cubeops-dashboard.yaml b/dashboards/views/cubeops-dashboard.yaml index b1e8849..68d3c36 100644 --- a/dashboards/views/cubeops-dashboard.yaml +++ b/dashboards/views/cubeops-dashboard.yaml @@ -13,12 +13,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. diff --git a/dashboards/views/family-hub-dashboard.yaml b/dashboards/views/family-hub-dashboard.yaml index 951330b..3d1ebea 100644 --- a/dashboards/views/family-hub-dashboard.yaml +++ b/dashboards/views/family-hub-dashboard.yaml @@ -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 diff --git a/dashboards/views/voice-alarms-dashboard.yaml b/dashboards/views/voice-alarms-dashboard.yaml index 8cf3d0f..3511c4a 100644 --- a/dashboards/views/voice-alarms-dashboard.yaml +++ b/dashboards/views/voice-alarms-dashboard.yaml @@ -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 diff --git a/themes/cubecraft.yaml b/themes/cubecraft.yaml new file mode 100644 index 0000000..02e3508 --- /dev/null +++ b/themes/cubecraft.yaml @@ -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/cubeops-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" diff --git a/www/cubecraft/cubecraft-brand-font.js b/www/cubecraft/cubecraft-brand-font.js new file mode 100644 index 0000000..1643ce4 --- /dev/null +++ b/www/cubecraft/cubecraft-brand-font.js @@ -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 }); +}