Build Vynte simulation dashboard
This commit is contained in:
+24
@@ -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?
|
||||
@@ -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...
|
||||
},
|
||||
},
|
||||
])
|
||||
```
|
||||
@@ -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
@@ -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>
|
||||
Generated
+2781
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
+315
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>,
|
||||
)
|
||||
@@ -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"]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
]
|
||||
}
|
||||
@@ -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"]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
})
|
||||
Reference in New Issue
Block a user