Build Vynte simulation dashboard

This commit is contained in:
2026-06-19 23:39:12 -06:00
commit c5abc8d951
15 changed files with 4445 additions and 0 deletions
+24
View File
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
+73
View File
@@ -0,0 +1,73 @@
# React + TypeScript + Vite
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)
## React Compiler
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
## Expanding the ESLint configuration
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
```js
export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
// Other configs...
// Remove tseslint.configs.recommended and replace with this
tseslint.configs.recommendedTypeChecked,
// Alternatively, use this for stricter rules
tseslint.configs.strictTypeChecked,
// Optionally, add this for stylistic rules
tseslint.configs.stylisticTypeChecked,
// Other configs...
],
languageOptions: {
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
// other options...
},
},
])
```
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
```js
// eslint.config.js
import reactX from 'eslint-plugin-react-x'
import reactDom from 'eslint-plugin-react-dom'
export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
// Other configs...
// Enable lint rules for React
reactX.configs['recommended-typescript'],
// Enable lint rules for React DOM
reactDom.configs.recommended,
],
languageOptions: {
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
// other options...
},
},
])
```
+22
View File
@@ -0,0 +1,22 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
import { defineConfig, globalIgnores } from 'eslint/config'
export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
js.configs.recommended,
tseslint.configs.recommended,
reactHooks.configs.flat.recommended,
reactRefresh.configs.vite,
],
languageOptions: {
globals: globals.browser,
},
},
])
+13
View File
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vynte Algorithm Simulation</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
+2781
View File
File diff suppressed because it is too large Load Diff
+31
View File
@@ -0,0 +1,31 @@
{
"name": "simulation-website",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"lucide-react": "^1.21.0",
"react": "^19.2.6",
"react-dom": "^19.2.6"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@types/node": "^24.12.3",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.1",
"eslint": "^10.3.0",
"eslint-plugin-react-hooks": "^7.1.1",
"eslint-plugin-react-refresh": "^0.5.2",
"globals": "^17.6.0",
"typescript": "~6.0.2",
"typescript-eslint": "^8.59.2",
"vite": "^8.0.12"
}
}
+11
View File
@@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<defs>
<linearGradient id="g" x1="8" x2="56" y1="16" y2="48">
<stop stop-color="#3f79ff"/>
<stop offset=".55" stop-color="#20e8ff"/>
<stop offset="1" stop-color="#2cf29c"/>
</linearGradient>
</defs>
<rect width="64" height="64" rx="14" fill="#03111d"/>
<path d="M12 20h8l12 23 12-23h8L36 50h-8L12 20Z" fill="url(#g)"/>
</svg>

After

Width:  |  Height:  |  Size: 421 B

+1060
View File
File diff suppressed because it is too large Load Diff
+315
View File
@@ -0,0 +1,315 @@
import {
Activity,
BrainCircuit,
CheckCircle2,
CloudSun,
Gauge,
Info,
Leaf,
RadioTower,
SlidersHorizontal,
Sparkles,
Wind,
Zap,
} from 'lucide-react'
import { useMemo, useState } from 'react'
import './App.css'
type Room = {
id: string
name: string
x: number
y: number
w: number
h: number
temp: number
cfm: number
vent: number
tone: 'blue' | 'cyan' | 'teal' | 'amber'
}
const rooms: Room[] = [
{ id: 'bed-1', name: 'Bedroom 1', x: 14, y: 8, w: 25, h: 25, temp: 71, cfm: 120, vent: 65, tone: 'blue' },
{ id: 'bath', name: 'Bath', x: 41, y: 8, w: 16, h: 25, temp: 73, cfm: 60, vent: 50, tone: 'cyan' },
{ id: 'bed-2', name: 'Bedroom 2', x: 59, y: 8, w: 27, h: 25, temp: 72, cfm: 110, vent: 55, tone: 'blue' },
{ id: 'living', name: 'Living Room', x: 13, y: 36, w: 34, h: 26, temp: 70, cfm: 230, vent: 75, tone: 'blue' },
{ id: 'kitchen', name: 'Kitchen', x: 62, y: 35, w: 24, h: 20, temp: 70, cfm: 140, vent: 70, tone: 'teal' },
{ id: 'suite', name: 'Primary Suite', x: 14, y: 66, w: 31, h: 24, temp: 69, cfm: 150, vent: 60, tone: 'blue' },
{ id: 'hall', name: 'Hall', x: 48, y: 61, w: 13, h: 29, temp: 72, cfm: 80, vent: 50, tone: 'cyan' },
{ id: 'dining', name: 'Dining Room', x: 63, y: 58, w: 24, h: 31, temp: 71, cfm: 130, vent: 45, tone: 'amber' },
]
const ventRows = [
['Living Room', '75%', '230 CFM'],
['Kitchen', '70%', '140 CFM'],
['Primary Suite', '60%', '150 CFM'],
['Bedroom 1', '65%', '120 CFM'],
['Bedroom 2', '55%', '110 CFM'],
['Dining Room', '45%', '130 CFM'],
['Bath', '50%', '60 CFM'],
['Hall', '50%', '80 CFM'],
]
const trendPoints = [
[8, 56, 18],
[17, 49, 29],
[25, 63, 22],
[34, 47, 35],
[43, 66, 28],
[52, 59, 40],
[61, 72, 33],
[71, 61, 27],
[81, 68, 38],
[92, 55, 45],
]
const flowPaths = [
'M50 53 C40 51 35 42 30 37 C24 31 22 22 17 18',
'M50 53 C61 51 63 43 67 39 C72 34 79 29 84 18',
'M50 53 C58 54 64 54 68 47 C73 39 80 39 85 41',
'M50 53 C43 56 36 55 31 51 C25 46 22 44 14 43',
'M50 53 C45 60 38 62 33 66 C26 73 22 80 17 80',
'M50 53 C52 63 52 71 52 87',
'M50 53 C60 58 65 61 70 68 C76 75 81 75 86 75',
'M50 53 C50 43 49 37 49 29 C48 19 50 14 52 11',
]
function PanelTitle({ title, icon }: { title: string; icon?: React.ReactNode }) {
return <h2 className="panel-title">{icon ?? null}{title}<Info size={14} /></h2>
}
function ArcScore({ label, value, caption, variant = 'cyan' }: { label: string; value: number; caption: string; variant?: 'cyan' | 'green' }) {
return (
<div className="arc-score">
<div className={`radial ${variant}`} style={{ '--score': `${value * 3.6}deg` } as React.CSSProperties}>
<span>{value}</span>
<small>/100</small>
</div>
<strong>{label}</strong>
<em>{caption}</em>
</div>
)
}
function Metric({ icon, label, value, unit, note, delta, spark }: { icon: React.ReactNode; label: string; value: string; unit?: string; note: string; delta?: string; spark?: boolean }) {
return (
<div className="metric">
<div className="metric-icon">{icon}</div>
<div><strong>{label}</strong><span>{value}<small>{unit}</small></span><em>{note}</em></div>
{delta && <b>{delta}</b>}
{spark && <svg viewBox="0 0 120 36"><path d="M0 23 C18 8 33 8 52 21 S84 31 120 7" /><path d="M0 22 C20 19 33 31 52 23 S84 10 120 24" /></svg>}
</div>
)
}
function MiniTrend() {
const polyline = (index: number) => trendPoints.map((point, i) => `${i * 10 + 3},${95 - point[index]}`).join(' ')
return (
<svg viewBox="0 0 104 96" className="trend-chart" aria-label="Real-time temperature humidity and efficiency trends">
{[20, 40, 60, 80].map((y) => <line key={y} x1="0" x2="104" y1={y} y2={y} />)}
{[12, 36, 60, 84].map((x) => <line key={x} x1={x} x2={x} y1="8" y2="88" />)}
<polyline className="temp-line" points={polyline(0)} />
<polyline className="humidity-line" points={polyline(1)} />
<polyline className="efficiency-line" points={polyline(2)} />
</svg>
)
}
function Donut() {
const segments = [19, 11, 12, 10, 9, 10, 5, 6, 18]
let start = -90
return (
<svg viewBox="0 0 120 120" className="donut" aria-label="Airflow distribution chart">
{segments.map((segment, index) => {
const end = start + segment * 3.6
const large = end - start > 180 ? 1 : 0
const r = 46
const sx = 60 + r * Math.cos((Math.PI * start) / 180)
const sy = 60 + r * Math.sin((Math.PI * start) / 180)
const ex = 60 + r * Math.cos((Math.PI * end) / 180)
const ey = 60 + r * Math.sin((Math.PI * end) / 180)
const d = `M60 60 L${sx} ${sy} A${r} ${r} 0 ${large} 1 ${ex} ${ey} Z`
start = end
return <path key={segment + index} className={`slice slice-${index}`} d={d} />
})}
<circle cx="60" cy="60" r="27" className="donut-hole" />
<text x="60" y="55" textAnchor="middle">1,240</text>
<text x="60" y="73" textAnchor="middle">CFM</text>
</svg>
)
}
function FloorPlan({ selectedView }: { selectedView: string }) {
return (
<section className={`floorplan-wrap ${selectedView === 'room' ? 'zoomed' : ''}`} aria-label="Whole-home airflow simulation">
<div className="view-tab">Whole-Home View</div>
<svg className="floorplan" viewBox="0 0 100 100" role="img" aria-label="Top-down home floorplan with animated airflow">
<defs>
<filter id="flowGlow" x="-40%" y="-40%" width="180%" height="180%">
<feGaussianBlur stdDeviation="1.9" result="blur" />
<feMerge><feMergeNode in="blur" /><feMergeNode in="SourceGraphic" /></feMerge>
</filter>
<linearGradient id="planDepth" x1="0" x2="1" y1="0" y2="1">
<stop stopColor="#26344a" offset="0" />
<stop stopColor="#07121d" offset="0.5" />
<stop stopColor="#1a130e" offset="1" />
</linearGradient>
<radialGradient id="hubGlow" cx="50%" cy="50%" r="60%">
<stop offset="0" stopColor="#2cf6ff" stopOpacity="0.75" />
<stop offset="1" stopColor="#0b1724" stopOpacity="0.05" />
</radialGradient>
</defs>
<polygon className="foundation" points="10,7 87,7 94,91 6,91" />
<polygon className="floor" points="11,8 86,8 92,89 8,89" />
{rooms.map((room) => (
<g key={room.id} className={`room room-${room.tone}`}>
<rect x={room.x} y={room.y} width={room.w} height={room.h} rx="0.8" />
<path className="room-texture" d={`M${room.x + 2} ${room.y + room.h - 4} h${room.w - 4} M${room.x + 4} ${room.y + 5} h${room.w - 8}`} />
</g>
))}
<path className="wall" d="M39 8 V35 H28 V62 H45 V91" />
<path className="wall" d="M57 8 V34 H61 V58 H47 V91" />
<path className="wall" d="M11 34 H86 M8 63 H45 M61 58 H91" />
<path className="wall" d="M47 34 V62 M61 34 V58" />
<rect className="hub" x="45.2" y="45.5" width="10.5" height="15.5" rx="1.4" />
<circle className="hub-core" cx="50.5" cy="53.2" r="2.4" />
<g filter="url(#flowGlow)">
{flowPaths.map((d, index) => (
<g key={d}>
<path className={`flow flow-${index}`} d={d} />
<path className={`flow-tracer flow-tracer-${index}`} d={d} />
</g>
))}
</g>
{rooms.map((room, index) => (
<g className="vent-node" key={`${room.id}-vent`}>
<rect x={room.x + (index % 2 ? room.w - 6 : 2)} y={room.y + 4} width="4.2" height="4.8" rx=".5" />
<path d={`M${room.x + (index % 2 ? room.w - 5.3 : 2.7)} ${room.y + 5.1} h2.8 M${room.x + (index % 2 ? room.w - 5.3 : 2.7)} ${room.y + 6.5} h2.8 M${room.x + (index % 2 ? room.w - 5.3 : 2.7)} ${room.y + 7.9} h2.8`} />
</g>
))}
</svg>
{rooms.map((room) => (
<div className={`room-badge tone-${room.tone}`} key={room.id} style={{ left: `${room.x + room.w * 0.48}%`, top: `${room.y + room.h * 0.42}%` }}>
<strong>{room.name}</strong>
<span>{room.temp}°F</span>
<small>{room.cfm} CFM <CheckCircle2 size={12} /></small>
</div>
))}
{rooms.slice(0, 7).map((room, index) => (
<div className={`vent-callout callout-${index}`} key={`${room.id}-callout`} style={{ '--vent': `${room.vent}%` } as React.CSSProperties}>
<b>{room.vent}%</b>
<span>Open</span>
</div>
))}
</section>
)
}
function DemoStep({ active, onClick, title, description }: { active: boolean; onClick: () => void; title: string; description: string }) {
return (
<button className={`demo-step ${active ? 'active' : ''}`} onClick={onClick} type="button">
<span className="demo-thumb"><Gauge size={34} /></span>
<span><strong>{title}</strong><em>{description}</em></span>
</button>
)
}
function App() {
const [selectedView, setSelectedView] = useState('whole')
const totalAirflow = useMemo(() => rooms.reduce((sum, room) => sum + room.cfm, 220), [])
return (
<main className="simulation-shell">
<header className="topbar">
<div className="brand-lockup">
<div className="brand">vynte</div>
<div className="divider" />
<div>
<h1>Algorithm Simulation</h1>
<p>Dynamic airflow balancing for total home comfort.</p>
</div>
</div>
<div className="runtime-status"><span /> Simulation Running <small>00:07:32</small></div>
<div className="mode-card"><BrainCircuit size={30} /><span>Mode</span><strong>Optimize Comfort & Efficiency</strong></div>
<div className="weather"><CloudSun size={28} /><strong>Outside 72°F</strong><span>Humidity 58% | AQI 32</span></div>
</header>
<div className="dashboard-grid">
<aside className="left-rail">
<section className="panel score-panel">
<PanelTitle title="Whole-Home Scores" />
<div className="score-row">
<ArcScore label="Comfort Score" value={94} caption="Excellent" />
<ArcScore label="Efficiency Score" value={87} caption="Great" variant="green" />
</div>
<div className="metric-stack">
<Metric icon={<Leaf />} label="System Efficiency" value="92%" note="Optimal" delta="↑ 18% vs. baseline" />
<Metric icon={<RadioTower />} label="Airflow Balance" value="96%" note="Well Balanced" spark />
<Metric icon={<Wind />} label="Total Airflow" value={totalAirflow.toLocaleString()} unit="CFM" note="Optimal Range" />
</div>
</section>
<section className="panel trends-panel">
<PanelTitle title="Real-Time Trends" />
<div className="legend"><span className="temp-key" /> Temp (°F)<span className="humidity-key" /> Humidity (%)<span className="efficiency-key" /> Efficiency (%)</div>
<MiniTrend />
<div className="axis-labels"><span>-15m</span><span>-10m</span><span>-5m</span><span>Now</span></div>
</section>
</aside>
<FloorPlan selectedView={selectedView} />
<aside className="right-rail">
<section className="panel vent-panel">
<PanelTitle title="Vent Control" />
<div className="vent-table">
<div className="vent-head"><span>Vent</span><span>Angle</span><span /><span>Flow</span><span>Status</span></div>
{ventRows.map(([room, angle, flow]) => (
<div className="vent-row" key={room}>
<span>{room}</span>
<b>{angle}</b>
<i><em style={{ width: angle }} /></i>
<small>{flow}</small>
<CheckCircle2 size={16} />
</div>
))}
</div>
</section>
<section className="panel distribution-panel">
<PanelTitle title="Airflow Distribution" />
<div className="distribution">
<Donut />
<ul>
{rooms.map((room) => <li key={room.id}><span className={`dot tone-${room.tone}`} />{room.name}<b>{room.cfm} CFM</b></li>)}
<li><span className="dot muted" />Other / Return<b>220 CFM</b></li>
</ul>
</div>
</section>
<section className="panel insights-panel">
<PanelTitle title="Algorithm Insights" icon={<Sparkles size={15} />} />
<p>Vynte is dynamically balancing comfort and efficiency across your home.</p>
<div className="insight-icons">
<span><Activity />Predicting demand</span>
<span><SlidersHorizontal />Adjusting airflow</span>
<span><Zap />Maximizing efficiency</span>
</div>
</section>
</aside>
</div>
<footer className="demo-strip">
<div className="demo-copy">
<strong>Demo Mode</strong>
<span>This simulation cycles through key views automatically.</span>
</div>
<DemoStep active={selectedView === 'whole'} onClick={() => setSelectedView('whole')} title="Whole-Home View" description="See overall airflow balance and system optimization." />
<DemoStep active={selectedView === 'room'} onClick={() => setSelectedView('room')} title="Zoomed Room View" description="See detailed airflow, temp, and vent activity in each room." />
<DemoStep active={selectedView === 'insights'} onClick={() => setSelectedView('insights')} title="Algorithm Insights" description="Deep dive into how Vynte makes intelligent decisions in real time." />
<div className="countdown"><span>Next view in</span><strong>18</strong><em>sec</em></div>
</footer>
</main>
)
}
export default App
+42
View File
@@ -0,0 +1,42 @@
:root {
--bg: #020b13;
--text: #c7d6e3;
--muted: #8aa0b5;
--cyan: #20e8ff;
--blue: #3d82ff;
--green: #2cf29c;
--border: rgba(54, 152, 222, 0.33);
--panel: rgba(6, 30, 48, 0.86);
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
color: var(--text);
background: var(--bg);
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
* {
box-sizing: border-box;
}
body {
min-width: 360px;
min-height: 100vh;
margin: 0;
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.05), transparent 8px),
#020b13;
}
button,
input,
select,
textarea {
font: inherit;
}
button:focus-visible {
outline: 2px solid var(--cyan);
outline-offset: 3px;
}
+10
View File
@@ -0,0 +1,10 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
)
+25
View File
@@ -0,0 +1,25 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "es2023",
"lib": ["ES2023", "DOM"],
"module": "esnext",
"types": ["vite/client"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"]
}
+7
View File
@@ -0,0 +1,7 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}
+24
View File
@@ -0,0 +1,24 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "es2023",
"lib": ["ES2023"],
"module": "esnext",
"types": ["node"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true
},
"include": ["vite.config.ts"]
}
+7
View File
@@ -0,0 +1,7 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
})