diff --git a/firmware/website/.run/dev.run.xml b/firmware/website/.run/dev.run.xml new file mode 100644 index 0000000..46d8ec8 --- /dev/null +++ b/firmware/website/.run/dev.run.xml @@ -0,0 +1,12 @@ + + + + + + + +
+ +{#if $isCaptive} + +{:else} + +{/if} diff --git a/firmware/website/src/Captive.svelte b/firmware/website/src/Captive.svelte new file mode 100644 index 0000000..8cf5ff5 --- /dev/null +++ b/firmware/website/src/Captive.svelte @@ -0,0 +1,5 @@ + + +

{$t("welcome")} - Captive Portal

diff --git a/firmware/website/src/Index.svelte b/firmware/website/src/Index.svelte new file mode 100644 index 0000000..baca94e --- /dev/null +++ b/firmware/website/src/Index.svelte @@ -0,0 +1,5 @@ + + +

{$t("welcome")}

diff --git a/firmware/website/src/app.css b/firmware/website/src/app.css new file mode 100644 index 0000000..bd313c2 --- /dev/null +++ b/firmware/website/src/app.css @@ -0,0 +1,70 @@ +:root { + --bg-color: #1a1a2e; + --card-bg: #16213e; + --accent: #0f3460; + --text: #eaeaea; + --text-muted: #a0a0a0; + --success: #00d26a; + --error: #ff6b6b; + --border: #2a2a4a; + --input-bg: #1a1a2e; + --shadow: rgba(0, 0, 0, 0.3); + --primary: #c41e3a; +} + +[data-theme="light"] { + --bg-color: #faf8f5; + --card-bg: #ffffff; + --accent: #fef2f2; + --text: #1a1a2e; + --text-muted: #6b7280; + --success: #c41e3a; + --error: #dc2626; + --border: #e5d9d0; + --input-bg: #ffffff; + --shadow: rgba(196, 30, 58, 0.1); + --primary: #c41e3a; +} + +[data-theme="light"] .header h1 { + color: var(--primary); +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + background: var(--bg-color); + color: var(--text); + min-height: 100vh; + transition: background 0.3s, color 0.3s; + padding: 12px; +} + +h1 { + font-size: 1.5rem; + color: var(--text); +} + +#app { + max-width: 900px; + margin: 0 auto; +} + +@media (max-width: 600px) { + body { + padding: 6px; + } +} + +@supports (padding: max(0px)) { + body { + padding-left: max(12px, env(safe-area-inset-left)); + padding-right: max(12px, env(safe-area-inset-right)); + padding-bottom: max(12px, env(safe-area-inset-bottom)); + } +} \ No newline at end of file diff --git a/firmware/website/src/compoents/Header.svelte b/firmware/website/src/compoents/Header.svelte new file mode 100644 index 0000000..b91ed79 --- /dev/null +++ b/firmware/website/src/compoents/Header.svelte @@ -0,0 +1,133 @@ + + +
+
+ { + const newLang = currentLangCode === "de" ? "en" : "de"; + handleLangChange(newLang); + }} + /> + + +
+

πŸš‚ System Control

+
+ + diff --git a/firmware/website/src/compoents/Toggle.svelte b/firmware/website/src/compoents/Toggle.svelte new file mode 100644 index 0000000..32c605e --- /dev/null +++ b/firmware/website/src/compoents/Toggle.svelte @@ -0,0 +1,37 @@ + + + + + diff --git a/firmware/website/src/i18n/de.json b/firmware/website/src/i18n/de.json new file mode 100644 index 0000000..7bc9b31 --- /dev/null +++ b/firmware/website/src/i18n/de.json @@ -0,0 +1,6 @@ +{ + "hello": "Hallo Welt", + "welcome": "Willkommen", + "language": "Sprache", + "save": "Speichern" +} \ No newline at end of file diff --git a/firmware/website/src/i18n/en.json b/firmware/website/src/i18n/en.json new file mode 100644 index 0000000..ab909b1 --- /dev/null +++ b/firmware/website/src/i18n/en.json @@ -0,0 +1,6 @@ +{ + "hello": "Hello World", + "welcome": "Welcome", + "language": "Language", + "save": "Save" +} \ No newline at end of file diff --git a/firmware/website/src/i18n/index.ts b/firmware/website/src/i18n/index.ts new file mode 100644 index 0000000..a46f53a --- /dev/null +++ b/firmware/website/src/i18n/index.ts @@ -0,0 +1,12 @@ +import de from './de.json'; +import en from './en.json'; + +export const translations = { de, en }; + +export type Lang = keyof typeof translations; + +export function getInitialLang(): Lang { + const navLang = navigator.language.slice(0, 2); + if (navLang in translations) return navLang as Lang; + return 'en'; +} diff --git a/firmware/website/src/i18n/store.ts b/firmware/website/src/i18n/store.ts new file mode 100644 index 0000000..04f18ff --- /dev/null +++ b/firmware/website/src/i18n/store.ts @@ -0,0 +1,16 @@ +import { writable, derived } from 'svelte/store'; +import { translations, getInitialLang, type Lang } from './index'; + +function getLang(): Lang { + const stored = localStorage.getItem('lang'); + if (stored && stored in translations) return stored as Lang; + return getInitialLang(); +} + +export const lang = writable(getLang()); + +export const t = derived(lang, $lang => { + return (key: string) => { + return translations[$lang][key] || key; + }; +}); diff --git a/firmware/website/src/main.js b/firmware/website/src/main.js new file mode 100644 index 0000000..34d488f --- /dev/null +++ b/firmware/website/src/main.js @@ -0,0 +1,9 @@ +import { mount } from 'svelte' +import App from './App.svelte' +import './app.css' + +const app = mount(App, { + target: document.getElementById('app'), +}) + +export default app diff --git a/firmware/website/src/theme.ts b/firmware/website/src/theme.ts new file mode 100644 index 0000000..84a4732 --- /dev/null +++ b/firmware/website/src/theme.ts @@ -0,0 +1,18 @@ +// src/theme.ts +export function setTheme(theme: 'light' | 'dark') { + document.documentElement.setAttribute('data-theme', theme); + localStorage.setItem('theme', theme); + + const icon = document.getElementById('theme-icon'); + const label = document.getElementById('theme-label'); + const metaTheme = document.querySelector('meta[name="theme-color"]') as HTMLMetaElement | null; + + if (icon) icon.textContent = theme === 'light' ? 'β˜€οΈ' : 'πŸŒ™'; + if (label) label.textContent = theme === 'light' ? 'Light' : 'Dark'; + if (metaTheme) metaTheme.content = theme === 'light' ? '#f0f2f5' : '#1a1a2e'; +} + +export function toggleTheme() { + const current = document.documentElement.getAttribute('data-theme') as 'light' | 'dark' | null || 'dark'; + setTheme(current === 'dark' ? 'light' : 'dark'); +} diff --git a/firmware/website/src/vite-env.d.ts b/firmware/website/src/vite-env.d.ts new file mode 100644 index 0000000..4078e74 --- /dev/null +++ b/firmware/website/src/vite-env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/firmware/website/svelte.config.js b/firmware/website/svelte.config.js new file mode 100644 index 0000000..ffb1d68 --- /dev/null +++ b/firmware/website/svelte.config.js @@ -0,0 +1,17 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +/** @type {import("@sveltejs/vite-plugin-svelte").SvelteConfig} */ +export default { + // Consult https://svelte.dev/docs#compile-time-svelte-preprocess + // for more information about preprocessors + preprocess: vitePreprocess(), + compilerOptions: { + runes: true, + }, + vitePlugin: { + inspector: { + showToggleButton: 'always', + toggleButtonPos: 'bottom-right' + } + } +}