Bundle frontend with esbuild, serve fonts offline, fix dashboard

- Add esbuild bundling: JS (IIFE, minified, sourcemapped) and CSS into
  single dist/ files, replacing 15+ individual CSS links and CDN scripts
- Bundle Chart.js and ELK.js from npm instead of CDN (fully offline)
- Serve DM Sans and Orbitron fonts locally from static/fonts/
- Fix dashboard automation card stretching full width (max-width: 500px)
- Fix time_of_day condition not localized in automation cards
- Add Chrome browser tools context file for MCP testing workflow
- Update frontend context with bundling docs and Chrome tools reference

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-14 22:20:20 +03:00
parent 46d77052ad
commit 49c2a63d68
16 changed files with 978 additions and 23 deletions

View File

@@ -0,0 +1,17 @@
/* CSS bundle entry — imports all stylesheets in correct order */
@import './fonts.css';
@import './base.css';
@import './layout.css';
@import './components.css';
@import './cards.css';
@import './modal.css';
@import './calibration.css';
@import './advanced-calibration.css';
@import './dashboard.css';
@import './streams.css';
@import './patterns.css';
@import './automations.css';
@import './tree-nav.css';
@import './tutorials.css';
@import './graph-editor.css';
@import './mobile.css';

View File

@@ -306,6 +306,10 @@
flex-shrink: 0;
}
.dashboard-automation {
max-width: 500px;
}
.dashboard-automation .dashboard-target-metrics {
min-width: 48px;
}

View File

@@ -0,0 +1,31 @@
/* Local font faces — no external CDN dependency */
/* DM Sans — latin-ext */
@font-face {
font-family: 'DM Sans';
font-style: normal;
font-weight: 400 700;
font-display: swap;
src: url('../fonts/dm-sans-latin-ext.woff2') format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* DM Sans — latin */
@font-face {
font-family: 'DM Sans';
font-style: normal;
font-weight: 400 700;
font-display: swap;
src: url('../fonts/dm-sans-latin.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* Orbitron 700 — latin */
@font-face {
font-family: 'Orbitron';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url('../fonts/orbitron-700-latin.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

View File

@@ -2,7 +2,7 @@
* Graph layout via ELK.js — converts entity data into positioned nodes/edges.
*/
/* global ELK */
import ELK from 'elkjs/lib/elk.bundled.js';
const NODE_WIDTH = 190;
const NODE_HEIGHT = 56;

View File

@@ -681,7 +681,8 @@ function renderDashboardAutomation(automation, sceneMap = new Map()) {
return `${apps} (${matchLabel})`;
}
if (c.condition_type === 'startup') return t('automations.condition.startup');
return c.condition_type;
if (c.condition_type === 'time_of_day') return t('automations.condition.time_of_day');
return t(`automations.condition.${c.condition_type}`) || c.condition_type;
});
const logic = automation.condition_logic === 'and' ? ' & ' : ' | ';
condSummary = parts.join(logic);

View File

@@ -3,6 +3,10 @@
* History is seeded from the server-side ring buffer on init.
*/
import { Chart, registerables } from 'chart.js';
Chart.register(...registerables);
window.Chart = Chart; // expose globally for targets.js, dashboard.js
import { API_BASE, getHeaders } from '../core/api.js';
import { t } from '../core/i18n.js';
import { dashboardPollInterval } from '../core/state.js';

View File

@@ -5,9 +5,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LED Grab</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='0.9em' font-size='90'>💡</text></svg>">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&family=Orbitron:wght@700&display=swap" rel="stylesheet">
<!-- PWA -->
<meta name="theme-color" content="#1a1a1a">
<meta name="apple-mobile-web-app-capable" content="yes">
@@ -15,23 +12,7 @@
<meta name="apple-mobile-web-app-title" content="LED Grab">
<link rel="apple-touch-icon" href="/static/icons/icon-192.png">
<link rel="manifest" href="/manifest.json">
<link rel="stylesheet" href="/static/css/base.css">
<link rel="stylesheet" href="/static/css/layout.css">
<link rel="stylesheet" href="/static/css/components.css">
<link rel="stylesheet" href="/static/css/cards.css">
<link rel="stylesheet" href="/static/css/modal.css">
<link rel="stylesheet" href="/static/css/calibration.css">
<link rel="stylesheet" href="/static/css/advanced-calibration.css">
<link rel="stylesheet" href="/static/css/dashboard.css">
<link rel="stylesheet" href="/static/css/streams.css">
<link rel="stylesheet" href="/static/css/patterns.css">
<link rel="stylesheet" href="/static/css/automations.css">
<link rel="stylesheet" href="/static/css/tree-nav.css">
<link rel="stylesheet" href="/static/css/tutorials.css">
<link rel="stylesheet" href="/static/css/graph-editor.css">
<link rel="stylesheet" href="/static/css/mobile.css">
<script src="https://cdn.jsdelivr.net/npm/chart.js@4/dist/chart.umd.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/elkjs@0.9.3/lib/elk.bundled.js"></script>
<link rel="stylesheet" href="/static/dist/app.bundle.css">
</head>
<body style="visibility: hidden;">
<canvas id="bg-anim-canvas"></canvas>
@@ -221,7 +202,7 @@
</div>
</div>
<script type="module" src="/static/js/app.js"></script>
<script defer src="/static/dist/app.bundle.js"></script>
<script>
// Initialize ambient background
const savedBgAnim = localStorage.getItem('bgAnim') || 'off';