diff --git a/README.md b/README.md new file mode 100644 index 0000000..5356bba --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +# Smart Vision IT Solution IoT App + +This repository contains a lightweight web-based IoT dashboard for **Smart Vision IT Solution**. +The app is branded for the business and prepared to connect with an **ESP RainMaker** backend so users can control: + +- Living room light +- Bedroom light +- Main hall fan + +## Features + +- Smart Vision IT Solution branding and contact details +- ESP RainMaker connection form for base URL and access token +- Device cards for light switches and fan power/speed control +- Activity log showing local updates and backend sync events +- Responsive glassmorphism UI for desktop and mobile + +## Contact + +- Phone: [9818429568](tel:9818429568) +- Email: [svitsolution@outlook.com](mailto:svitsolution@outlook.com) + +## Run locally + +Because the project is a static web app, you can run it with any static server. For example: + +```bash +python3 -m http.server 4173 +``` + +Then open `http://localhost:4173` in your browser. + +## ESP RainMaker integration note + +The `script.js` file includes a `RainMakerClient` abstraction that is ready for wiring into your actual ESP RainMaker API flow. Replace the simulated `connect()` and `updateDevice()` methods with your production API requests and authentication handling when your backend credentials and node schema are available. diff --git a/index.html b/index.html new file mode 100644 index 0000000..271b337 --- /dev/null +++ b/index.html @@ -0,0 +1,182 @@ + + + + + + Smart Vision IT Solution + + + + + + + +
+
+
+

ESP RainMaker Enabled IoT App

+

Smart Vision IT Solution

+

+ Manage connected lights and fan devices from one polished control panel backed by + an ESP RainMaker-ready API layer. +

+ +
+
+ RainMaker Cloud Ready +

Remote automation, scenes, and live device updates.

+

+ Built to connect your switch lights and fan controllers with ESP RainMaker services, + status sync, and command delivery. +

+
    +
  • Light switch controls with instant feedback
  • +
  • Fan on/off and speed regulation
  • +
  • Prepared for RainMaker authentication token usage
  • +
+
+
+ +
+
+
+
+

Backend

+

ESP RainMaker Connection

+
+
+ + + +
+

+ Add your ESP RainMaker URL and token to send commands to devices. +

+
+ +
+
+

Highlights

+

App Capabilities

+
+
+
+ 02 + Switch light controls +
+
+ 01 + Fan controller with speed slider +
+
+ 24/7 + Cloud-ready monitoring experience +
+
+ 100% + Branding customized for Smart Vision IT Solution +
+
+
+
+ +
+
+

Controls

+

Rooms & Devices

+

Tap the toggles below to switch lights and control the smart fan.

+
+ +
+
+
+ 💡 +
+

Living Room Light

+

Device ID: light-living-room

+
+
+ +
+ +
+
+ 💡 +
+

Bedroom Light

+

Device ID: light-bedroom

+
+
+ +
+ +
+
+ 🌀 +
+

Main Hall Fan

+

Device ID: fan-main-hall

+
+
+ + +
+
+
+ +
+
+

Activity

+

Recent Commands

+
+
    +
  • Dashboard initialized. Waiting for ESP RainMaker connection.
  • +
+
+ +
+
+

Contact

+

Smart Vision IT Solution

+
+ +
+
+
+ + + + diff --git a/script.js b/script.js new file mode 100644 index 0000000..e588da6 --- /dev/null +++ b/script.js @@ -0,0 +1,133 @@ +const state = { + connected: false, + baseUrl: 'https://api.rainmaker.espressif.com', + authToken: '', + devices: { + 'light-living-room': { type: 'light', power: false }, + 'light-bedroom': { type: 'light', power: false }, + 'fan-main-hall': { type: 'fan', power: false, speed: 3 }, + }, +}; + +const activityLog = document.getElementById('activityLog'); +const connectionMessage = document.getElementById('connectionMessage'); +const baseUrlInput = document.getElementById('baseUrl'); +const authTokenInput = document.getElementById('authToken'); +const connectBtn = document.getElementById('connectBtn'); +const fanSpeedInput = document.getElementById('fanSpeed'); +const fanSpeedValue = document.getElementById('fanSpeedValue'); +const toggles = document.querySelectorAll('.device-toggle'); + +class RainMakerClient { + constructor() { + this.baseUrl = state.baseUrl; + this.authToken = state.authToken; + } + + configure({ baseUrl, authToken }) { + this.baseUrl = baseUrl; + this.authToken = authToken; + } + + async connect() { + if (!this.authToken.trim()) { + throw new Error('An ESP RainMaker access token is required.'); + } + + await this.simulateNetwork(); + return { + ok: true, + message: `Connected to ${this.baseUrl}`, + }; + } + + async updateDevice(deviceId, payload) { + await this.simulateNetwork(); + + return { + ok: true, + endpoint: `${this.baseUrl}/v1/user/nodes/params`, + deviceId, + payload, + }; + } + + simulateNetwork() { + return new Promise((resolve) => { + window.setTimeout(resolve, 320); + }); + } +} + +const rainMakerClient = new RainMakerClient(); + +const appendLog = (message) => { + const entry = document.createElement('li'); + entry.textContent = `${new Date().toLocaleTimeString([], { + hour: '2-digit', + minute: '2-digit', + })} — ${message}`; + activityLog.prepend(entry); +}; + +const syncFormState = () => { + state.baseUrl = baseUrlInput.value.trim() || 'https://api.rainmaker.espressif.com'; + state.authToken = authTokenInput.value.trim(); + rainMakerClient.configure({ + baseUrl: state.baseUrl, + authToken: state.authToken, + }); +}; + +connectBtn.addEventListener('click', async () => { + syncFormState(); + connectBtn.disabled = true; + connectBtn.textContent = 'Connecting...'; + + try { + const result = await rainMakerClient.connect(); + state.connected = true; + connectionMessage.textContent = `${result.message}. Ready to control lights and fan.`; + appendLog('ESP RainMaker backend connected successfully.'); + } catch (error) { + state.connected = false; + connectionMessage.textContent = error.message; + appendLog(`Connection failed: ${error.message}`); + } finally { + connectBtn.disabled = false; + connectBtn.textContent = 'Connect backend'; + } +}); + +const sendDeviceUpdate = async (deviceId, payload) => { + state.devices[deviceId] = { + ...state.devices[deviceId], + ...payload, + }; + + if (!state.connected) { + appendLog( + `Updated ${deviceId} locally. Connect to ESP RainMaker to send the command to the cloud.`, + ); + return; + } + + const result = await rainMakerClient.updateDevice(deviceId, payload); + appendLog(`Synced ${result.deviceId} to ${result.endpoint} with ${JSON.stringify(payload)}.`); +}; + +toggles.forEach((toggle) => { + toggle.addEventListener('change', async (event) => { + const { deviceId } = event.target.dataset; + const power = event.target.checked; + await sendDeviceUpdate(deviceId, { power }); + }); +}); + +fanSpeedInput.addEventListener('input', async (event) => { + const speed = Number(event.target.value); + fanSpeedValue.textContent = String(speed); + await sendDeviceUpdate('fan-main-hall', { speed }); +}); + +appendLog('Smart Vision IT Solution dashboard is ready.'); diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..36822c3 --- /dev/null +++ b/styles.css @@ -0,0 +1,389 @@ +:root { + color-scheme: dark; + --bg: #09111f; + --panel: rgba(12, 24, 46, 0.78); + --panel-border: rgba(134, 197, 255, 0.18); + --accent: #53b3ff; + --accent-strong: #7c5cff; + --text: #eff5ff; + --muted: #adc2e0; + --success: #49dcb1; + --shadow: 0 18px 40px rgba(0, 0, 0, 0.28); +} + +* { + box-sizing: border-box; +} + +html { + scroll-behavior: smooth; +} + +body { + margin: 0; + min-height: 100vh; + font-family: 'Inter', sans-serif; + background: + radial-gradient(circle at top left, rgba(124, 92, 255, 0.25), transparent 28%), + radial-gradient(circle at top right, rgba(83, 179, 255, 0.22), transparent 24%), + linear-gradient(135deg, #040810 0%, #09111f 50%, #0a1730 100%); + color: var(--text); +} + +body::before { + content: ''; + position: fixed; + inset: 0; + background-image: linear-gradient(rgba(255, 255, 255, 0.02) 1px, transparent 1px), + linear-gradient(90deg, rgba(255, 255, 255, 0.02) 1px, transparent 1px); + background-size: 42px 42px; + pointer-events: none; +} + +.page-shell { + width: min(1180px, calc(100% - 32px)); + margin: 0 auto; + padding: 32px 0 48px; +} + +.card { + background: var(--panel); + border: 1px solid var(--panel-border); + box-shadow: var(--shadow); + backdrop-filter: blur(18px); + border-radius: 28px; +} + +.hero { + display: grid; + grid-template-columns: 1.25fr 0.95fr; + gap: 24px; + padding: 32px; +} + +.eyebrow { + margin: 0 0 12px; + text-transform: uppercase; + letter-spacing: 0.18em; + font-size: 0.75rem; + font-weight: 700; + color: var(--accent); +} + +.hero h1, +.section-heading h2, +.status-panel h2, +.device-card h3 { + margin: 0; +} + +.hero h1 { + font-size: clamp(2.4rem, 4vw, 4rem); + line-height: 1.05; + max-width: 11ch; +} + +.hero-copy, +.status-panel p, +.section-heading p, +.device-card p, +.helper-text, +.activity-log li, +.contact-grid a { + color: var(--muted); +} + +.hero-copy { + max-width: 60ch; + font-size: 1.05rem; + line-height: 1.7; + margin: 18px 0 28px; +} + +.hero-actions, +.contact-grid, +.connection-grid, +.device-grid, +.stats-grid { + display: grid; + gap: 16px; +} + +.hero-actions { + grid-template-columns: repeat(2, minmax(0, max-content)); +} + +.button { + appearance: none; + border: 0; + border-radius: 999px; + padding: 14px 22px; + font-weight: 700; + cursor: pointer; + text-decoration: none; + transition: transform 0.2s ease, box-shadow 0.2s ease, background 0.2s ease; +} + +.button:hover { + transform: translateY(-1px); +} + +.button.primary { + background: linear-gradient(135deg, var(--accent), var(--accent-strong)); + color: white; + box-shadow: 0 10px 24px rgba(83, 179, 255, 0.24); +} + +.button.secondary { + background: rgba(255, 255, 255, 0.06); + color: var(--text); + border: 1px solid rgba(255, 255, 255, 0.08); +} + +.status-panel { + padding: 28px; + background: linear-gradient(180deg, rgba(83, 179, 255, 0.12), rgba(124, 92, 255, 0.12)); + border-radius: 24px; + border: 1px solid rgba(255, 255, 255, 0.09); +} + +.status-chip { + display: inline-flex; + align-items: center; + gap: 8px; + border-radius: 999px; + padding: 8px 12px; + font-weight: 700; + font-size: 0.85rem; +} + +.status-chip.live { + background: rgba(73, 220, 177, 0.14); + color: #92ffe0; +} + +.status-panel ul { + padding-left: 18px; + margin: 18px 0 0; + color: var(--muted); + line-height: 1.8; +} + +main { + display: grid; + gap: 24px; + margin-top: 24px; +} + +.connection-grid { + grid-template-columns: 1.05fr 0.95fr; +} + +.connection-card, +.insights-card, +.activity-card, +.contact-card { + padding: 28px; +} + +.connection-form { + display: grid; + gap: 14px; +} + +label { + display: grid; + gap: 8px; + font-size: 0.95rem; + font-weight: 600; +} + +input[type='url'], +input[type='password'] { + width: 100%; + border-radius: 16px; + border: 1px solid rgba(255, 255, 255, 0.1); + background: rgba(3, 10, 22, 0.64); + color: var(--text); + padding: 14px 16px; + font: inherit; +} + +.helper-text { + margin-bottom: 0; +} + +.stats-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + margin-top: 16px; +} + +.stats-grid div { + padding: 20px; + border-radius: 22px; + background: rgba(255, 255, 255, 0.04); + border: 1px solid rgba(255, 255, 255, 0.08); +} + +.stats-grid strong { + display: block; + font-size: 1.8rem; + margin-bottom: 8px; +} + +.controls-section { + display: grid; + gap: 20px; +} + +.device-grid { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +.device-card { + padding: 24px; + display: grid; + gap: 18px; +} + +.device-meta { + display: flex; + align-items: center; + gap: 16px; +} + +.device-icon { + width: 56px; + height: 56px; + display: inline-grid; + place-items: center; + border-radius: 18px; + background: rgba(255, 255, 255, 0.08); + font-size: 1.6rem; +} + +.switch-row, +.range-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; +} + +input[type='checkbox'] { + width: 56px; + height: 28px; + appearance: none; + background: rgba(255, 255, 255, 0.16); + border-radius: 999px; + position: relative; + transition: background 0.2s ease; + cursor: pointer; +} + +input[type='checkbox']::after { + content: ''; + position: absolute; + top: 3px; + left: 4px; + width: 22px; + height: 22px; + border-radius: 50%; + background: white; + transition: transform 0.2s ease; +} + +input[type='checkbox']:checked { + background: linear-gradient(135deg, var(--accent), var(--accent-strong)); +} + +input[type='checkbox']:checked::after { + transform: translateX(26px); +} + +.range-row div { + min-width: 160px; + display: flex; + align-items: center; + gap: 14px; +} + +input[type='range'] { + width: 100%; + accent-color: var(--accent); +} + +.activity-log { + list-style: none; + padding: 0; + margin: 18px 0 0; + display: grid; + gap: 12px; +} + +.activity-log li { + padding: 14px 16px; + border-radius: 16px; + background: rgba(255, 255, 255, 0.04); + border: 1px solid rgba(255, 255, 255, 0.06); +} + +.contact-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + margin-top: 18px; +} + +.contact-grid a { + text-decoration: none; + font-weight: 600; + padding: 18px 20px; + border-radius: 18px; + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.08); +} + +code { + font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace; + color: #a9f0ff; +} + +@media (max-width: 960px) { + .hero, + .connection-grid, + .device-grid, + .contact-grid { + grid-template-columns: 1fr; + } +} + +@media (max-width: 640px) { + .page-shell { + width: min(100% - 20px, 1180px); + padding-top: 20px; + } + + .hero, + .connection-card, + .insights-card, + .activity-card, + .contact-card, + .device-card { + padding: 20px; + border-radius: 22px; + } + + .hero-actions, + .stats-grid { + grid-template-columns: 1fr; + } + + .range-row, + .switch-row { + align-items: flex-start; + flex-direction: column; + } + + .range-row div { + width: 100%; + } +}