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 @@ + +