diff --git a/assets/js/platform-v3.js b/assets/js/platform-v3.js
new file mode 100644
index 0000000..012e595
--- /dev/null
+++ b/assets/js/platform-v3.js
@@ -0,0 +1,78 @@
+(function(){
+ const KEYS={
+ notes:'360_quick_notes_v1',
+ pomodoro:'360_pomodoro_minutes',
+ focus:'360_focus_mode'
+ };
+ const ROUTES=[
+ ['Home','/'],['AI','/ai'],['Weather','/weather'],['Translator','/translator'],['Stocks','/stocks'],['Chat','/chat'],['News','/news'],['Apps','/apps'],['Games','/games'],['Settings','/settings.html']
+ ];
+
+ function injectUI(){
+ if(document.getElementById('v3Fab')) return;
+ const style=document.createElement('style');
+ style.textContent=`
+ #v3Fab{position:fixed;right:14px;bottom:14px;z-index:1200;border:none;border-radius:999px;padding:10px 14px;background:linear-gradient(120deg,var(--a),var(--a2));font-weight:700;cursor:pointer}
+ #v3Panel{position:fixed;right:14px;bottom:58px;z-index:1200;width:min(360px,92vw);background:rgba(15,23,42,.9);color:#fff;border:1px solid rgba(255,255,255,.15);border-radius:12px;padding:10px;display:none;backdrop-filter:blur(8px)}
+ #v3Panel.open{display:block}.v3row{display:flex;gap:6px;margin-bottom:8px}.v3row>*{flex:1}
+ .v3input{width:100%;padding:8px;border-radius:8px;border:1px solid rgba(255,255,255,.2);background:rgba(255,255,255,.08);color:#fff}
+ .v3btn{padding:8px;border-radius:8px;border:1px solid rgba(255,255,255,.2);background:rgba(255,255,255,.12);color:#fff;cursor:pointer}
+ #v3Cmd{position:fixed;inset:0;background:rgba(2,6,23,.7);z-index:1300;display:none;align-items:flex-start;justify-content:center;padding-top:12vh}
+ #v3Cmd.open{display:flex}#v3CmdBox{width:min(680px,92vw);background:#0f172a;border:1px solid rgba(255,255,255,.15);border-radius:12px;padding:10px}
+ #v3CmdList button{display:block;width:100%;text-align:left;margin-top:6px}
+ body.v3-focus .sidebar, body.v3-focus .auth-top-right, body.v3-focus .settings-panel{display:none!important}
+ `;
+ document.head.appendChild(style);
+
+ const fab=document.createElement('button'); fab.id='v3Fab'; fab.textContent='V3 ⚡';
+ const panel=document.createElement('section'); panel.id='v3Panel'; panel.innerHTML=`
+
Quick Hub (Frontend-only)
+
+
+
+
+ `;
+
+ const cmd=document.createElement('div'); cmd.id='v3Cmd'; cmd.innerHTML=``;
+
+ document.body.append(fab,panel,cmd);
+
+ const notesEl=panel.querySelector('#v3Notes');
+ const pomoEl=panel.querySelector('#v3Pomodoro');
+ notesEl.value=localStorage.getItem(KEYS.notes)||'';
+ pomoEl.value=localStorage.getItem(KEYS.pomodoro)||'25';
+ if(localStorage.getItem(KEYS.focus)==='true') document.body.classList.add('v3-focus');
+
+ fab.onclick=()=>panel.classList.toggle('open');
+ panel.querySelector('#v3FocusBtn').onclick=()=>{document.body.classList.toggle('v3-focus');localStorage.setItem(KEYS.focus,String(document.body.classList.contains('v3-focus')))};
+ panel.querySelector('#v3SaveNotes').onclick=()=>localStorage.setItem(KEYS.notes,notesEl.value);
+ panel.querySelector('#v3ExportNotes').onclick=()=>{const b=new Blob([notesEl.value],{type:'text/plain'});const a=document.createElement('a');a.href=URL.createObjectURL(b);a.download='360-notes.txt';a.click();URL.revokeObjectURL(a.href)};
+ panel.querySelector('#v3PomodoroStart').onclick=()=>{
+ const mins=Math.max(5,Math.min(120,Number(pomoEl.value)||25));
+ localStorage.setItem(KEYS.pomodoro,String(mins));
+ const end=Date.now()+mins*60000;
+ const tick=()=>{
+ const left=Math.max(0,Math.ceil((end-Date.now())/1000));
+ fab.textContent=left>0?`⏱ ${Math.floor(left/60)}:${String(left%60).padStart(2,'0')}`:'Done ✅';
+ if(left>0) requestAnimationFrame(tick); else setTimeout(()=>fab.textContent='V3 ⚡',4000);
+ };tick();
+ };
+
+ const cmdList=cmd.querySelector('#v3CmdList');
+ function renderCmd(q=''){
+ const qq=q.toLowerCase();
+ cmdList.innerHTML='';
+ ROUTES.filter(r=>r[0].toLowerCase().includes(qq)).forEach(([n,u])=>{const b=document.createElement('button');b.className='v3btn';b.textContent=`Go to ${n}`;b.onclick=()=>location.href=u;cmdList.appendChild(b);});
+ }
+ renderCmd();
+ panel.querySelector('#v3CmdBtn').onclick=()=>{cmd.classList.add('open');cmd.querySelector('#v3CmdSearch').focus();};
+ cmd.querySelector('#v3CmdSearch').oninput=(e)=>renderCmd(e.target.value);
+ cmd.addEventListener('click',e=>{if(e.target===cmd) cmd.classList.remove('open');});
+ document.addEventListener('keydown',e=>{
+ if((e.ctrlKey||e.metaKey)&&e.key.toLowerCase()==='k'){e.preventDefault();cmd.classList.add('open');cmd.querySelector('#v3CmdSearch').focus();}
+ if(e.key==='Escape') cmd.classList.remove('open');
+ });
+ }
+
+ document.addEventListener('DOMContentLoaded', injectUI);
+})();
diff --git a/assets/js/widgets.js b/assets/js/widgets.js
new file mode 100644
index 0000000..146b9ce
--- /dev/null
+++ b/assets/js/widgets.js
@@ -0,0 +1,196 @@
+(function () {
+ const STORAGE_KEY = '360_widgets_v1';
+ const TYPES = {
+ weather: { label: 'Weather' },
+ time: { label: 'Time' },
+ note: { label: 'Note' }
+ };
+
+ function loadWidgets() {
+ try { return JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]'); } catch { return []; }
+ }
+ function saveWidgets(w) { localStorage.setItem(STORAGE_KEY, JSON.stringify(w)); }
+ function uid() { return `w_${Date.now()}_${Math.random().toString(36).slice(2,8)}`; }
+ const clamp = (n, min, max) => Math.max(min, Math.min(max, n));
+
+ async function getWeather(lat, lon, unit) {
+ const tempUnit = unit === 'F' ? 'fahrenheit' : 'celsius';
+ const url = `https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lon}¤t=temperature_2m&temperature_unit=${tempUnit}`;
+ const res = await fetch(url);
+ const data = await res.json();
+ return data?.current?.temperature_2m;
+ }
+
+ function applyWidgetStyles(card, header, body, w) {
+ const opacity = clamp(Number(w.opacity ?? 0.85), 0.2, 1);
+ card.style.background = w.bgColor || `rgba(15,23,42,${opacity})`;
+ card.style.color = w.textColor || '#ffffff';
+ card.style.borderRadius = `${clamp(Number(w.radius ?? 12), 0, 48)}px`;
+ card.style.borderColor = w.borderColor || 'var(--br)';
+ body.style.fontSize = `${clamp(Number(w.fontSize ?? 18), 10, 48)}px`;
+ if ((w.shape || 'rounded') === 'pill') card.style.borderRadius = '999px';
+ if ((w.shape || 'rounded') === 'square') card.style.borderRadius = '0px';
+ header.style.background = w.headerColor || 'rgba(59,130,246,.35)';
+ }
+
+ function startIndexMode() {
+ const host = document.getElementById('widgetBoard');
+ if (!host) return;
+ let widgets = loadWidgets();
+
+ function render() {
+ host.innerHTML = '';
+ widgets.forEach(w => {
+ const card = document.createElement('section');
+ card.className = 'home-widget';
+ card.dataset.id = w.id;
+ card.style.left = (w.x || 20) + 'px';
+ card.style.top = (w.y || 20) + 'px';
+ card.style.width = `${clamp(Number(w.width || 220), 140, 600)}px`;
+ card.style.height = `${clamp(Number(w.height || 130), 80, 500)}px`;
+
+ const header = document.createElement('div');
+ header.className = 'home-widget-header';
+ header.textContent = w.title || TYPES[w.type]?.label || 'Widget';
+
+ const body = document.createElement('div');
+ body.className = 'home-widget-body';
+ card.append(header, body);
+ applyWidgetStyles(card, header, body, w);
+ host.appendChild(card);
+
+ if (w.type === 'time') {
+ const locale = w.locale || undefined;
+ const tz = w.timezone || undefined;
+ const f = () => { body.textContent = new Date().toLocaleTimeString(locale, { timeZone: tz, hour: '2-digit', minute: '2-digit', second: '2-digit' }); };
+ f();
+ setInterval(f, 1000);
+ } else if (w.type === 'note') {
+ body.textContent = w.text || 'Empty note';
+ } else if (w.type === 'weather') {
+ body.textContent = 'Loading weather…';
+ const unit = w.unit || 'C';
+ const setFallback = () => body.textContent = 'Weather unavailable';
+ if (!navigator.geolocation) setFallback();
+ else navigator.geolocation.getCurrentPosition(async pos => {
+ try {
+ const t = await getWeather(pos.coords.latitude, pos.coords.longitude, unit);
+ body.textContent = t == null ? 'Weather unavailable' : `${Math.round(t)}°${unit}`;
+ } catch { setFallback(); }
+ }, setFallback);
+ }
+
+ makeDraggable(card, w);
+ });
+ }
+
+ function makeDraggable(el, widget) {
+ const header = el.querySelector('.home-widget-header');
+ let sx=0, sy=0, ox=0, oy=0, dragging=false;
+ header.addEventListener('pointerdown', e => {
+ dragging = true;
+ sx = e.clientX; sy = e.clientY;
+ ox = widget.x || 20; oy = widget.y || 20;
+ el.setPointerCapture(e.pointerId);
+ });
+ header.addEventListener('pointermove', e => {
+ if (!dragging) return;
+ widget.x = Math.max(0, ox + e.clientX - sx);
+ widget.y = Math.max(0, oy + e.clientY - sy);
+ el.style.left = widget.x + 'px';
+ el.style.top = widget.y + 'px';
+ });
+ header.addEventListener('pointerup', () => {
+ dragging = false;
+ saveWidgets(widgets);
+ });
+ }
+
+ render();
+ }
+
+ function startSettingsMode() {
+ const list = document.getElementById('widgetList');
+ if (!list) return;
+ const form = document.getElementById('widgetForm');
+ const fields = {
+ type: document.getElementById('widgetType'),
+ title: document.getElementById('widgetTitle'),
+ width: document.getElementById('widgetWidth'),
+ height: document.getElementById('widgetHeight'),
+ text: document.getElementById('widgetText'),
+ unit: document.getElementById('widgetUnit'),
+ timezone: document.getElementById('widgetTimezone'),
+ locale: document.getElementById('widgetLocale'),
+ shape: document.getElementById('widgetShape'),
+ bgColor: document.getElementById('widgetBgColor'),
+ headerColor: document.getElementById('widgetHeaderColor'),
+ textColor: document.getElementById('widgetTextColor'),
+ borderColor: document.getElementById('widgetBorderColor'),
+ radius: document.getElementById('widgetRadius'),
+ fontSize: document.getElementById('widgetFontSize'),
+ opacity: document.getElementById('widgetOpacity')
+ };
+ let widgets = loadWidgets();
+
+ function refresh() {
+ list.innerHTML = widgets.map(w => `
+
+
+
${w.title || w.type}
+
${w.type} • ${w.width}x${w.height} • ${(w.shape||'rounded')}
+
+
+
+
+
+
`).join('') || 'No widgets yet.
';
+ saveWidgets(widgets);
+ }
+
+ list.addEventListener('click', e => {
+ const btn = e.target.closest('button[data-act]');
+ if (!btn) return;
+ const { act, id } = btn.dataset;
+ if (act === 'del') widgets = widgets.filter(w => w.id !== id);
+ if (act === 'dup') {
+ const src = widgets.find(w => w.id === id);
+ if (src) widgets.push({ ...src, id: uid(), title: `${src.title || src.type} Copy`, x: (src.x || 20) + 20, y: (src.y || 20) + 20 });
+ }
+ refresh();
+ });
+
+ form.addEventListener('submit', e => {
+ e.preventDefault();
+ widgets.push({
+ id: uid(),
+ type: fields.type.value,
+ title: fields.title.value.trim() || TYPES[fields.type.value].label,
+ width: clamp(Number(fields.width.value) || 220, 140, 600),
+ height: clamp(Number(fields.height.value) || 130, 80, 500),
+ text: fields.text.value.trim(),
+ unit: fields.unit.value,
+ timezone: fields.timezone.value.trim(),
+ locale: fields.locale.value.trim(),
+ shape: fields.shape.value,
+ bgColor: fields.bgColor.value,
+ headerColor: fields.headerColor.value,
+ textColor: fields.textColor.value,
+ borderColor: fields.borderColor.value,
+ radius: clamp(Number(fields.radius.value) || 12, 0, 48),
+ fontSize: clamp(Number(fields.fontSize.value) || 18, 10, 48),
+ opacity: clamp(Number(fields.opacity.value) || 0.85, 0.2, 1),
+ x: 20,
+ y: 20
+ });
+ form.reset();
+ refresh();
+ });
+ refresh();
+ }
+
+ document.addEventListener('DOMContentLoaded', () => {
+ startIndexMode();
+ startSettingsMode();
+ });
+})();
diff --git a/index.html b/index.html
index 8859f0c..eff112e 100644
--- a/index.html
+++ b/index.html
@@ -148,6 +148,11 @@
}
.bob-track.on { background: var(--a); }
.bob-track.on::before { transform: translateX(16px); }
+ #widgetBoard { position: fixed; inset: 0; pointer-events: none; z-index: 5; }
+ .home-widget { position: absolute; pointer-events: auto; border:1px solid var(--br); background: rgba(15,23,42,.6); color: #fff; border-radius: 12px; overflow: hidden; backdrop-filter: blur(8px); }
+ .home-widget-header { padding: 8px 10px; font-size: 12px; font-weight: 700; cursor: grab; background: rgba(59,130,246,.35); user-select:none; }
+ .home-widget-body { padding: 10px; font-size: 18px; }
+
@@ -270,6 +275,9 @@ Bob
+ Advanced
+ 🧩 Widget Settings
+
Legal
🔒 Privacy Policy
📜 Terms of Service
@@ -301,6 +309,7 @@ Account
+
--:--
@@ -336,6 +345,8 @@
Account
+
+
diff --git a/settings.html b/settings.html
index 2bfb1bc..d017eae 100644
--- a/settings.html
+++ b/settings.html
@@ -654,6 +654,44 @@
+
+
+
Widgets
+
+
+
Tip: drag widgets around on the homepage after adding them.
+
+
About 360
@@ -731,6 +769,8 @@
+
+