pwa
No files matched your search
@@ -5,6 +5,15 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>gibbz voice — POC</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<link rel="manifest" href="/manifest.webmanifest" />
|
||||
<link rel="icon" href="/favicon.ico" sizes="any" />
|
||||
<link rel="icon" type="image/svg+xml" href="/icons/icon.svg" />
|
||||
<link rel="apple-touch-icon" href="/icons/apple-touch-icon.png" />
|
||||
<meta name="theme-color" content="#1e1f22" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
<meta name="apple-mobile-web-app-title" content="gibbz" />
|
||||
<script src="https://otel.k6n.net/static/otel-rum.js"></script>
|
||||
<script>
|
||||
window.rum = window.OtelRUM ? OtelRUM.init({
|
||||
@@ -20,6 +29,7 @@
|
||||
<!-- mobile top bar: hamburger opens the channel drawer, shows live status -->
|
||||
<header id="mobile-topbar">
|
||||
<button id="btn-menu" title="Channels">☰</button>
|
||||
<img id="mobile-logo" src="/icons/icon.svg" alt="" width="24" height="24" />
|
||||
<span id="mobile-title">gibbz voice</span>
|
||||
<span class="status-row" id="mobile-status-row">
|
||||
<span class="status-indicator" id="mobile-status-dot"></span>
|
||||
@@ -29,16 +39,12 @@
|
||||
<div id="sidebar-backdrop"></div>
|
||||
|
||||
<div id="app">
|
||||
<!-- server rail -->
|
||||
<nav id="rail">
|
||||
<div class="rail-icon home" title="gibbz voice">G</div>
|
||||
<div class="rail-sep"></div>
|
||||
<div class="rail-icon" title="POC server">VP</div>
|
||||
</nav>
|
||||
|
||||
<!-- channel sidebar -->
|
||||
<aside id="sidebar">
|
||||
<header id="server-name">gibbz voice <span class="poc-tag">POC</span></header>
|
||||
<header id="server-name">
|
||||
<img id="server-logo" src="/icons/icon.svg" alt="" width="22" height="22" />
|
||||
gibbz voice <span class="poc-tag">POC</span>
|
||||
</header>
|
||||
<div class="channel-group-label">Text channels</div>
|
||||
<div class="channel text" data-mx-room="gibbz-general">#
|
||||
general <span class="hint">matrix</span></div>
|
||||
@@ -166,5 +172,12 @@
|
||||
<div id="toasts"></div>
|
||||
|
||||
<script type="module" src="/app.js"></script>
|
||||
<script>
|
||||
if ('serviceWorker' in navigator) {
|
||||
window.addEventListener('load', () => {
|
||||
navigator.serviceWorker.register('/sw.js').catch((err) => console.warn('sw registration failed', err));
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 23 KiB |
@@ -0,0 +1,23 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||
<defs>
|
||||
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0" stop-color="#6b74f5"/>
|
||||
<stop offset="1" stop-color="#4752c4"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="512" height="512" fill="url(#bg)"/>
|
||||
<!-- soundwave rings, centered, kept inside the ~80% maskable safe zone -->
|
||||
<g stroke="#ffffff" stroke-linecap="round" fill="none">
|
||||
<path d="M 168 196 A 96 96 0 0 0 168 316" stroke-width="16" opacity="0.55"/>
|
||||
<path d="M 344 196 A 96 96 0 0 1 344 316" stroke-width="16" opacity="0.55"/>
|
||||
<path d="M 200 222 A 60 60 0 0 0 200 290" stroke-width="16" opacity="0.85"/>
|
||||
<path d="M 312 222 A 60 60 0 0 1 312 290" stroke-width="16" opacity="0.85"/>
|
||||
</g>
|
||||
<!-- mic body -->
|
||||
<g fill="#ffffff">
|
||||
<rect x="226" y="150" width="60" height="110" rx="30"/>
|
||||
<path d="M 196 234 v 12 a 60 60 0 0 0 120 0 v -12 h -16 v 12 a 44 44 0 0 1 -88 0 v -12 z" />
|
||||
<rect x="248" y="318" width="16" height="34" rx="4"/>
|
||||
<rect x="220" y="352" width="72" height="14" rx="6"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 23 KiB |
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "gibbz voice",
|
||||
"short_name": "gibbz",
|
||||
"description": "Discord-style voice chat with screen sharing and spatial audio",
|
||||
"start_url": "/",
|
||||
"scope": "/",
|
||||
"display": "standalone",
|
||||
"background_color": "#1e1f22",
|
||||
"theme_color": "#1e1f22",
|
||||
"orientation": "any",
|
||||
"icons": [
|
||||
{ "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any" },
|
||||
{ "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any" },
|
||||
{ "src": "/icons/maskable-192.png", "sizes": "192x192", "type": "image/png", "purpose": "maskable" },
|
||||
{ "src": "/icons/maskable-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
const CACHE = 'gibbz-shell-v1';
|
||||
const SHELL = ['/', '/index.html', '/style.css', '/app.js', '/manifest.webmanifest'];
|
||||
|
||||
self.addEventListener('install', (event) => {
|
||||
event.waitUntil(caches.open(CACHE).then((cache) => cache.addAll(SHELL)));
|
||||
self.skipWaiting();
|
||||
});
|
||||
|
||||
self.addEventListener('activate', (event) => {
|
||||
event.waitUntil(
|
||||
caches.keys().then((keys) => Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k))))
|
||||
);
|
||||
self.clients.claim();
|
||||
});
|
||||
|
||||
// Never touch the signaling socket or live API data — only cache the static
|
||||
// app shell so the UI can still load (offline/flaky network), not room state.
|
||||
self.addEventListener('fetch', (event) => {
|
||||
const { request } = event;
|
||||
const url = new URL(request.url);
|
||||
if (request.method !== 'GET' || url.origin !== self.location.origin) return;
|
||||
if (url.pathname.startsWith('/api/') || url.pathname === '/ws') return;
|
||||
|
||||
event.respondWith(
|
||||
fetch(request)
|
||||
.then((response) => {
|
||||
const copy = response.clone();
|
||||
caches.open(CACHE).then((cache) => cache.put(request, copy));
|
||||
return response;
|
||||
})
|
||||
.catch(() => caches.match(request).then((cached) => cached || caches.match('/index.html')))
|
||||
);
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
/* Discord-ish dark theme (POC) */
|
||||
:root {
|
||||
--bg-0: #1e1f22; /* rail */
|
||||
--bg-0: #1e1f22; /* user panel / topbar */
|
||||
--bg-1: #2b2d31; /* sidebar */
|
||||
--bg-2: #313338; /* main */
|
||||
--bg-3: #383a40; /* hover */
|
||||
@@ -24,19 +24,6 @@ body {
|
||||
}
|
||||
#app { display: flex; height: 100vh; }
|
||||
|
||||
/* rail */
|
||||
#rail {
|
||||
width: 68px; background: var(--bg-0); padding: 12px 0;
|
||||
display: flex; flex-direction: column; align-items: center; gap: 8px; flex-shrink: 0;
|
||||
}
|
||||
.rail-icon {
|
||||
width: 44px; height: 44px; border-radius: 50%; background: var(--bg-3);
|
||||
display: grid; place-items: center; font-weight: 700; color: var(--text-1);
|
||||
cursor: pointer; transition: border-radius .15s, background .15s;
|
||||
}
|
||||
.rail-icon:hover, .rail-icon.home { border-radius: 16px; background: var(--brand); color: #fff; }
|
||||
.rail-sep { width: 28px; height: 2px; background: var(--bg-3); border-radius: 1px; }
|
||||
|
||||
/* sidebar */
|
||||
#sidebar {
|
||||
width: 250px; background: var(--bg-1); display: flex; flex-direction: column; flex-shrink: 0;
|
||||
@@ -45,7 +32,9 @@ body {
|
||||
#server-name {
|
||||
padding: 14px 16px; font-weight: 700; font-size: 15px;
|
||||
border-bottom: 1px solid rgba(0,0,0,.25); box-shadow: 0 1px 0 rgba(0,0,0,.06);
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
}
|
||||
#server-logo, #mobile-logo { border-radius: 6px; flex-shrink: 0; }
|
||||
.poc-tag {
|
||||
font-size: 10px; background: var(--yellow); color: #000; border-radius: 4px;
|
||||
padding: 1px 5px; vertical-align: 2px; margin-left: 6px; font-weight: 800;
|
||||
@@ -422,13 +411,11 @@ code { font-family: ui-monospace, monospace; font-size: 12px; }
|
||||
|
||||
/* ---------------------------------------------------------------------------
|
||||
mobile layout
|
||||
- rail is dropped (decorative only in this POC)
|
||||
- sidebar becomes an off-canvas drawer opened from #mobile-topbar
|
||||
- mic/deafen/share get an always-visible mirror bar pinned to the bottom
|
||||
--------------------------------------------------------------------------- */
|
||||
@media (max-width: 860px) {
|
||||
#app { padding-top: 48px; } /* room for the fixed topbar */
|
||||
#rail { display: none; }
|
||||
|
||||
#mobile-topbar {
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
|
||||