Compare commits

...
Sign in to create a new pull request.

7 commits

Author SHA1 Message Date
s1to
9eb5123c03 Load Oasis-Mobile-UX theme directly on mobile
Drops the conditional/selectable-theme loading that broke the CSS;
matches the known-working build.
2026-07-26 14:18:42 +02:00
s1to
8aaba6585a Add Oasis-Mobile-UX theme with hexagon navigation
- Move mobile styles into a selectable Oasis-Mobile-UX theme; mobile.css kept intact
- Hexagon category launcher on home (10 categories with expandable modules)
- Customizable bottom bar (up to 4 shortcuts, Peers/Invites fixed)
- Gold-outlined hexagons
2026-07-17 12:12:55 +02:00
s1to
6c816bab94 Fix vertical scroll on mobile 2026-07-11 15:09:23 +02:00
s1to
c7419a44d5 Bottom-bar module picker routes
- Add GET/POST /settings/bottombar to choose the pinned module.
2026-07-09 16:44:17 +02:00
s1to
bc0f55e914 Empty states and collapsible activity filters
- Activity and search show an empty state with a call to action.
- Selecting an activity type collapses the filters and adds a back arrow to change it.
2026-07-09 16:44:17 +02:00
s1to
9c100ed773 Configurable bottom bar and empty-state helper
- Bottom bar: replace Activity with a + slot to pin a module; hide slots for disabled modules.
- Add a reusable empty-state helper and the inbox empty state.
- Mobile styles and translations for the above.
2026-07-09 16:44:17 +02:00
s1to
851042e576 keylock: password-based at-rest encryption
- Pre-boot password gate (main.js) with language selector; backend starts only after unlock.
- keylock.js: scrypt KEK + AES-256-GCM wrap/unwrap of the SSB secret and keyrings.
- crypto.js: keyrings encrypted/decrypted with the KEK.
- ssb_config.js: decrypted keys in memory (no plaintext secret on disk).
2026-07-09 09:56:30 +02:00
22 changed files with 1480 additions and 663 deletions

148
main.js
View file

@ -67,9 +67,153 @@ console.log('[Oasis] Launching Oasis Social Network Utopia...');
console.log('[Oasis] Backend path:', backendPath);
console.log('[Oasis] HOME:', process.env.HOME);
try {
const http = require('http');
const keylock = require('./src/models/keylock');
const configPath = path.join(process.env.HOME, '.ssb');
let backendStarted = false;
function startBackend() {
if (backendStarted) return;
backendStarted = true;
try {
require(backendPath);
console.log('[Oasis] Backend started on port 3000');
} catch (error) {
} catch (error) {
console.error('[Oasis] Error starting backend:', error);
}
}
function unlockAndStart(keys) {
global.__OASIS_KEYS__ = keys;
for (const s of sockets) { try { s.destroy(); } catch (_) {} }
try { lockServer.close(() => startBackend()); } catch (_) { startBackend(); }
}
const MODE = keylock.isConfigured(configPath) ? 'unlock' : 'setup';
const LOCK_LANGS = ['en','es','fr','eu','de','it','pt','zh','ar','hi','ru'];
const LOCK_I18N = {
en: { name:"English", t_setup:"Create your password", t_unlock:"Unlock Oasis", s_setup:"It encrypts your identity and keys on this device. <b>If you forget it, you lose the account</b> (no recovery).", s_unlock:"Enter your password to decrypt your keys and start Oasis.", ph:"Password", ph2:"Repeat password", b_setup:"Create and enter", b_unlock:"Enter", e_min:"Minimum 6 characters", e_mism:"Passwords do not match", e_wrong:"Incorrect password", e_pre:"Error: ", m_unlock:"Unlocking…" },
es: { name:"Español", t_setup:"Crea tu contraseña", t_unlock:"Desbloquea Oasis", s_setup:"Cifrará tu identidad y tus claves en este dispositivo. <b>Si la olvidas, pierdes la cuenta</b> (no hay recuperación).", s_unlock:"Introduce tu contraseña para descifrar tus claves y arrancar Oasis.", ph:"Contraseña", ph2:"Repite la contraseña", b_setup:"Crear y entrar", b_unlock:"Entrar", e_min:"Mínimo 6 caracteres", e_mism:"Las contraseñas no coinciden", e_wrong:"Contraseña incorrecta", e_pre:"Error: ", m_unlock:"Desbloqueando…" },
fr: { name:"Français", t_setup:"Créez votre mot de passe", t_unlock:"Déverrouiller Oasis", s_setup:"Il chiffre votre identité et vos clés sur cet appareil. <b>Si vous l'oubliez, vous perdez le compte</b> (aucune récupération).", s_unlock:"Saisissez votre mot de passe pour déchiffrer vos clés et démarrer Oasis.", ph:"Mot de passe", ph2:"Répétez le mot de passe", b_setup:"Créer et entrer", b_unlock:"Entrer", e_min:"6 caractères minimum", e_mism:"Les mots de passe ne correspondent pas", e_wrong:"Mot de passe incorrect", e_pre:"Erreur : ", m_unlock:"Déverrouillage…" },
eu: { name:"Euskara", t_setup:"Sortu zure pasahitza", t_unlock:"Desblokeatu Oasis", s_setup:"Zure identitatea eta gakoak gailu honetan zifratzen ditu. <b>Ahazten baduzu, kontua galtzen duzu</b> (ez dago berreskuratzerik).", s_unlock:"Sartu zure pasahitza gakoak deszifratu eta Oasis abiarazteko.", ph:"Pasahitza", ph2:"Errepikatu pasahitza", b_setup:"Sortu eta sartu", b_unlock:"Sartu", e_min:"Gutxienez 6 karaktere", e_mism:"Pasahitzak ez datoz bat", e_wrong:"Pasahitz okerra", e_pre:"Errorea: ", m_unlock:"Desblokeatzen…" },
de: { name:"Deutsch", t_setup:"Erstelle dein Passwort", t_unlock:"Oasis entsperren", s_setup:"Es verschlüsselt deine Identität und Schlüssel auf diesem Gerät. <b>Wenn du es vergisst, verlierst du das Konto</b> (keine Wiederherstellung).", s_unlock:"Gib dein Passwort ein, um deine Schlüssel zu entschlüsseln und Oasis zu starten.", ph:"Passwort", ph2:"Passwort wiederholen", b_setup:"Erstellen und eintreten", b_unlock:"Eintreten", e_min:"Mindestens 6 Zeichen", e_mism:"Passwörter stimmen nicht überein", e_wrong:"Falsches Passwort", e_pre:"Fehler: ", m_unlock:"Entsperren…" },
it: { name:"Italiano", t_setup:"Crea la tua password", t_unlock:"Sblocca Oasis", s_setup:"Cifra la tua identità e le tue chiavi su questo dispositivo. <b>Se la dimentichi, perdi l'account</b> (nessun recupero).", s_unlock:"Inserisci la password per decifrare le chiavi e avviare Oasis.", ph:"Password", ph2:"Ripeti la password", b_setup:"Crea ed entra", b_unlock:"Entra", e_min:"Minimo 6 caratteri", e_mism:"Le password non coincidono", e_wrong:"Password errata", e_pre:"Errore: ", m_unlock:"Sblocco…" },
pt: { name:"Português", t_setup:"Crie a sua palavra-passe", t_unlock:"Desbloquear o Oasis", s_setup:"Cifra a sua identidade e chaves neste dispositivo. <b>Se a esquecer, perde a conta</b> (sem recuperação).", s_unlock:"Introduza a sua palavra-passe para decifrar as chaves e iniciar o Oasis.", ph:"Palavra-passe", ph2:"Repita a palavra-passe", b_setup:"Criar e entrar", b_unlock:"Entrar", e_min:"Mínimo 6 caracteres", e_mism:"As palavras-passe não coincidem", e_wrong:"Palavra-passe incorreta", e_pre:"Erro: ", m_unlock:"A desbloquear…" },
zh: { name:"中文", t_setup:"创建你的密码", t_unlock:"解锁 Oasis", s_setup:"它会在此设备上加密你的身份和密钥。<b>如果忘记,你将失去账户</b>(无法恢复)。", s_unlock:"输入密码以解密你的密钥并启动 Oasis。", ph:"密码", ph2:"重复密码", b_setup:"创建并进入", b_unlock:"进入", e_min:"至少 6 个字符", e_mism:"密码不一致", e_wrong:"密码错误", e_pre:"错误:", m_unlock:"正在解锁…" },
ar: { name:"العربية", t_setup:"أنشئ كلمة المرور", t_unlock:"فتح Oasis", s_setup:"يشفّر هويتك ومفاتيحك على هذا الجهاز. <b>إذا نسيتها، تفقد الحساب</b> (لا استعادة).", s_unlock:"أدخل كلمة المرور لفك تشفير مفاتيحك وتشغيل Oasis.", ph:"كلمة المرور", ph2:"أعد كلمة المرور", b_setup:"إنشاء ودخول", b_unlock:"دخول", e_min:"6 أحرف على الأقل", e_mism:"كلمتا المرور غير متطابقتين", e_wrong:"كلمة مرور غير صحيحة", e_pre:"خطأ: ", m_unlock:"جارٍ الفتح…" },
hi: { name:"हिन्दी", t_setup:"अपना पासवर्ड बनाएं", t_unlock:"Oasis अनलॉक करें", s_setup:"यह इस डिवाइस पर आपकी पहचान और कुंजियाँ एन्क्रिप्ट करता है। <b>भूल गए तो खाता खो देंगे</b> (कोई रिकवरी नहीं)।", s_unlock:"अपनी कुंजियाँ डिक्रिप्ट करने और Oasis शुरू करने के लिए पासवर्ड दर्ज करें।", ph:"पासवर्ड", ph2:"पासवर्ड दोहराएँ", b_setup:"बनाएँ और प्रवेश करें", b_unlock:"प्रवेश करें", e_min:"कम से कम 6 अक्षर", e_mism:"पासवर्ड मेल नहीं खाते", e_wrong:"गलत पासवर्ड", e_pre:"त्रुटि: ", m_unlock:"अनलॉक हो रहा है…" },
ru: { name:"Русский", t_setup:"Создайте пароль", t_unlock:"Разблокировать Oasis", s_setup:"Он шифрует вашу личность и ключи на этом устройстве. <b>Если забудете, потеряете аккаунт</b> (без восстановления).", s_unlock:"Введите пароль, чтобы расшифровать ключи и запустить Oasis.", ph:"Пароль", ph2:"Повторите пароль", b_setup:"Создать и войти", b_unlock:"Войти", e_min:"Минимум 6 символов", e_mism:"Пароли не совпадают", e_wrong:"Неверный пароль", e_pre:"Ошибка: ", m_unlock:"Разблокировка…" },
};
function lockPage(mode) {
const confirmField = mode === 'setup'
? '<input id="c" type="password" inputmode="text" autocomplete="new-password" />'
: '';
return '<!doctype html><!-- OASIS_LOCK_MARKER --><html lang="en"><head><meta charset="utf-8">'
+ '<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">'
+ '<title>Oasis</title><style>'
+ '*{box-sizing:border-box}body{margin:0;background:#121212;color:#eee;font-family:system-ui,sans-serif;'
+ 'min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px}'
+ '.card{width:100%;max-width:360px;text-align:center}h1{color:#FFD700;font-size:22px;margin:0 0 8px}'
+ 'p{color:#aaa;font-size:14px;line-height:1.4;margin:0 0 20px}'
+ 'input{width:100%;padding:14px;margin:8px 0;border-radius:10px;border:1px solid #333;'
+ 'background:#1c1c1c;color:#eee;font-size:16px}'
+ 'button[type=submit]{width:100%;padding:14px;margin-top:12px;border:0;border-radius:10px;background:#FFD700;'
+ 'color:#111;font-size:16px;font-weight:600}button[type=submit]:disabled{opacity:.5}'
+ '#err{color:#ff6b6b;font-size:13px;min-height:18px;margin-top:8px}#msg{color:#FFD700;font-size:14px}'
+ '#langbtn{position:fixed;top:calc(env(safe-area-inset-top,0px) + 12px);right:12px;width:42px;height:42px;'
+ 'border-radius:50%;border:1px solid #333;background:#1c1c1c;color:#FFD700;font-size:13px;font-weight:700;'
+ 'padding:0;z-index:10;cursor:pointer;display:flex;align-items:center;justify-content:center}'
+ '.langmenu{position:fixed;top:calc(env(safe-area-inset-top,0px) + 60px);right:12px;background:#1c1c1c;'
+ 'border:1px solid #333;border-radius:12px;padding:6px;z-index:11;max-height:70vh;overflow:auto;min-width:150px;'
+ 'box-shadow:0 6px 20px rgba(0,0,0,.5)}'
+ '.langmenu button{display:block;width:100%;text-align:left;background:transparent;color:#eee;border:0;'
+ 'border-radius:8px;padding:11px 12px;font-size:15px;margin:0;font-weight:400;cursor:pointer}'
+ '.langmenu button.cur{color:#FFD700;font-weight:700;background:#262626}'
+ '[dir=rtl] #langbtn,[dir=rtl] .langmenu{right:auto;left:12px}[dir=rtl] .langmenu button{text-align:right}'
+ '</style></head><body>'
+ '<button id="langbtn" type="button" aria-label="Language">EN</button>'
+ '<div id="langmenu" class="langmenu" hidden></div>'
+ '<div class="card"><h1 id="title"></h1><p id="sub"></p>'
+ '<form id="f"><input id="p" type="password" inputmode="text" autocomplete="' + (mode === 'setup' ? 'new-password' : 'current-password') + '" autofocus />'
+ confirmField
+ '<button type="submit" id="btn"></button>'
+ '<div id="err"></div><div id="msg"></div></form></div>'
+ '<script>'
+ 'var MODE="' + mode + '";var I18N=' + JSON.stringify(LOCK_I18N) + ';var LANGS=' + JSON.stringify(LOCK_LANGS) + ';'
+ 'var $=function(id){return document.getElementById(id);};'
+ 'function getLang(){try{var s=localStorage.getItem("oasisLockLang");if(s&&I18N[s])return s;}catch(e){}return "en";}'
+ 'var LANG=getLang();'
+ 'function setCookie(l){try{document.cookie="language="+l+";path=/;max-age=31536000";}catch(e){}}'
+ 'try{if(localStorage.getItem("oasisLockLang"))setCookie(LANG);}catch(e){}'
+ 'function apply(){var t=I18N[LANG]||I18N.en;'
+ 'document.documentElement.lang=LANG;document.documentElement.dir=(LANG==="ar")?"rtl":"ltr";'
+ '$("title").innerHTML=(MODE==="setup")?t.t_setup:t.t_unlock;'
+ '$("sub").innerHTML=(MODE==="setup")?t.s_setup:t.s_unlock;'
+ '$("p").placeholder=t.ph;var c=$("c");if(c)c.placeholder=t.ph2;'
+ '$("btn").textContent=(MODE==="setup")?t.b_setup:t.b_unlock;'
+ '$("langbtn").textContent=LANG.toUpperCase();}'
+ 'function buildMenu(){var m=$("langmenu");m.innerHTML="";LANGS.forEach(function(l){'
+ 'var b=document.createElement("button");b.type="button";b.textContent=(I18N[l]&&I18N[l].name)||l;'
+ 'if(l===LANG)b.className="cur";b.onclick=function(){LANG=l;try{localStorage.setItem("oasisLockLang",l);}catch(e){}'
+ 'setCookie(l);apply();buildMenu();m.hidden=true;};m.appendChild(b);});}'
+ '$("langbtn").onclick=function(){var m=$("langmenu");m.hidden=!m.hidden;};'
+ 'document.addEventListener("click",function(e){var m=$("langmenu");if(!m.hidden&&e.target!==$("langbtn")&&!m.contains(e.target))m.hidden=true;});'
+ 'buildMenu();apply();'
+ 'var f=$("f"),err=$("err"),msg=$("msg");'
+ 'f.addEventListener("submit",async function(e){e.preventDefault();var t=I18N[LANG]||I18N.en;err.textContent="";'
+ 'var p=$("p").value,c=$("c");'
+ 'if(MODE==="setup"){if(p.length<6){err.textContent=t.e_min;return;}if(c&&c.value!==p){err.textContent=t.e_mism;return;}}'
+ 'var btn=$("btn");btn.disabled=true;'
+ 'try{var r=await fetch(MODE==="setup"?"/__keylock/setup":"/__keylock/unlock",{method:"POST",'
+ 'headers:{"Content-Type":"application/json"},body:JSON.stringify({password:p})});'
+ 'if(r.ok){msg.textContent=t.m_unlock;waitReload();}else{err.textContent=t.e_wrong;btn.disabled=false;}}'
+ 'catch(e2){err.textContent=t.e_pre+e2.message;btn.disabled=false;}});'
+ 'async function waitReload(){for(var i=0;i<120;i++){try{var r=await fetch("/?_k="+Date.now(),{cache:"no-store"});'
+ 'var t=await r.text();if(t.indexOf("OASIS_LOCK_MARKER")===-1){location.reload();return;}}catch(e){}'
+ 'await new Promise(function(s){setTimeout(s,500);});}location.reload();}'
+ '</script></body></html>';
}
function readBody(req) {
return new Promise((resolve) => {
let b = '';
req.on('data', (d) => { b += d; if (b.length > 1e5) req.destroy(); });
req.on('end', () => { try { resolve(JSON.parse(b || '{}')); } catch (_) { resolve({}); } });
req.on('error', () => resolve({}));
});
}
const sockets = new Set();
const lockServer = http.createServer(async (req, res) => {
try {
if (req.method === 'POST' && req.url === '/__keylock/setup') {
const { password } = await readBody(req);
if (!password || String(password).length < 6) { res.writeHead(400, { 'Connection': 'close' }); return res.end('{"ok":false}'); }
keylock.setPassword(configPath, password);
const keys = keylock.loadOrCreateSecret(configPath);
res.writeHead(200, { 'Content-Type': 'application/json', 'Connection': 'close' });
res.end('{"ok":true}');
return unlockAndStart(keys);
}
if (req.method === 'POST' && req.url === '/__keylock/unlock') {
const { password } = await readBody(req);
const kek = password ? keylock.verifyPassword(configPath, password) : null;
if (!kek) { res.writeHead(401, { 'Connection': 'close' }); return res.end('{"ok":false}'); }
const keys = keylock.loadOrCreateSecret(configPath);
res.writeHead(200, { 'Content-Type': 'application/json', 'Connection': 'close' });
res.end('{"ok":true}');
return unlockAndStart(keys);
}
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'Connection': 'close' });
res.end(lockPage(MODE));
} catch (e) {
res.writeHead(500, { 'Connection': 'close' });
res.end('{"ok":false,"error":"' + String(e && e.message).replace(/"/g, '') + '"}');
}
});
lockServer.on('connection', (s) => { sockets.add(s); s.on('close', () => sockets.delete(s)); });
lockServer.listen(3000, '127.0.0.1', () => {
console.log('[Oasis] keylock gate escuchando en 127.0.0.1:3000 (modo: ' + MODE + ')');
});

View file

@ -7887,6 +7887,28 @@ router
saveConfig(cfg);
ctx.redirect(`/modules`);
})
.get("/settings/bottombar", async (ctx) => {
ctx.body = require("../views/main_views").bottomBarPickerView();
})
.post("/settings/bottombar", koaBody(), async (ctx) => {
const cfg = getConfig();
const MAX = 4;
const body = ctx.request.body || {};
const action = String(body.action || '').trim();
const key = String(body.key || '').trim();
const catalog = require("../views/main_views").PINNABLE_MODULES || [];
let pins = Array.isArray(cfg.bottomBarPins) ? cfg.bottomBarPins.slice() : [];
if (action === 'add') {
if (key && catalog.some(m => m.key === key) && !pins.includes(key) && pins.length < MAX) pins.push(key);
} else if (action === 'remove') {
pins = pins.filter(k => k !== key);
} else if (action === 'clear') {
pins = [];
}
cfg.bottomBarPins = pins.slice(0, MAX);
saveConfig(cfg);
ctx.redirect('/settings/bottombar');
})
.post("/settings/ai", koaBody(), async (ctx) => {
const aiPrompt = String(ctx.request.body.ai_prompt || "").trim();
if (aiPrompt.length > 128) { sendErrorPage(ctx, "Prompt too long. Must be 128 characters or fewer.", { status: 400 }); return; }

View file

@ -914,620 +914,3 @@ a.create-button {
.peers-technical-block .block-info-table td[data-label=""]::before { content: none !important; }
.peers-list .block-info-table td .peer-key,
.peers-technical-block .block-info-table td .peer-key { text-align: right !important; }
.oasis-bottombar {
display: flex !important;
position: fixed !important;
left: 0 !important; right: 0 !important; bottom: 0 !important;
z-index: 1000 !important;
justify-content: space-around !important;
align-items: center !important;
gap: 4px !important;
background: #1F1F1F !important;
border-top: 1px solid #333 !important;
padding: 8px 6px calc(8px + env(safe-area-inset-bottom, 0px)) !important;
}
.oasis-bottombar .bb-item {
position: relative !important;
display: flex !important;
flex-direction: column !important;
align-items: center !important;
gap: 3px !important;
flex: 1 1 0 !important;
min-width: 0 !important;
padding: 2px 0 !important;
text-decoration: none !important;
}
.oasis-bottombar .bb-ico { font-size: 18px !important; line-height: 1 !important; color: #FFA500 !important; }
.oasis-bottombar .bb-lbl { font-size: 9px !important; color: #ccc !important; white-space: nowrap !important; }
.oasis-bottombar .bb-badge {
position: absolute !important;
top: -4px !important;
left: calc(50% + 6px) !important;
min-width: 15px !important; height: 15px !important;
padding: 0 3px !important;
border-radius: 8px !important;
background: #FFA500 !important;
color: #121212 !important;
font-size: 9px !important; font-weight: bold !important;
display: flex !important; align-items: center !important; justify-content: center !important;
}
body { padding-bottom: 64px !important; }
.oasis-mobile-topbar {
display: flex !important;
align-items: center !important;
justify-content: space-between !important;
width: 100% !important;
padding: 8px 12px !important;
background: transparent !important;
box-shadow: none !important; border: 0 !important;
}
.oasis-mobile-topbar .omt-logo img,
.oasis-mobile-topbar .omt-avatar img {
width: 32px !important; height: 32px !important;
border-radius: 50% !important;
border: 2px solid #FFA500 !important;
object-fit: cover !important; display: block !important;
}
.oasis-mobile-topbar .omt-title {
flex: 1 1 auto !important;
text-align: center !important;
font-size: 26px !important; font-weight: bold !important;
letter-spacing: 10px !important; text-indent: 10px !important;
color: #FFD700 !important; text-decoration: none !important;
white-space: nowrap !important;
background: transparent !important; border: 0 !important; box-shadow: none !important; padding: 0 !important;
}
.header .top-bar-left > a.logo-icon { display: none !important; }
.oasis-mobile-menus { display: flex !important; gap: 8px !important; padding: 0 10px 6px !important; }
.oasis-mobile-menus .oasis-menu-ico { display: none !important; }
.oasis-mobile-menus .oasis-menu-btn {
flex: 1 1 0 !important;
display: flex !important; align-items: center !important; justify-content: center !important;
padding: 12px !important;
border: 1.5px solid #333 !important; border-radius: 10px !important;
background: #121212 !important; color: #ccc !important;
font-weight: bold !important; letter-spacing: 1px !important; cursor: pointer !important;
}
#nav-left-toggle:checked ~ .header .oasis-menu-btn[for="nav-left-toggle"] {
background: #FFA500 !important; color: #121212 !important; border-color: #FFA500 !important;
}
#nav-right-toggle:checked ~ .header .oasis-menu-btn[for="nav-right-toggle"] {
background: #FFD700 !important; color: #121212 !important; border-color: #FFD700 !important;
}
:root {
--space-1: 4px; --space-2: 8px; --space-3: 16px; --space-4: 24px; --space-5: 32px;
--fs-caption: 12px; --fs-body: 14px; --fs-body-lg: 16px; --fs-title: 20px;
--lh-body: 1.5; --measure: 66ch;
--c-muted: #8c8c8c; --radius-md: 12px;
}
.oasis-bottombar .bb-fab .bb-ico {
width: 46px !important; height: 46px !important;
margin-top: -22px !important;
border-radius: 50% !important;
background: #FFA500 !important; color: #121212 !important;
display: flex !important; align-items: center !important; justify-content: center !important;
font-size: 22px !important;
box-shadow: 0 2px 8px rgba(0,0,0,.4) !important;
}
.oasis-bottombar .bb-fab .bb-lbl { color: #FFA500 !important; font-weight: bold !important; }
.main-column, .main-column p, .main-column li, .main-column a,
.main-column span, .main-column td, .main-column h1, .main-column h2, .main-column h3 {
overflow-wrap: anywhere !important;
word-break: break-word !important;
}
.main-column { padding: var(--space-3) !important; }
.main-column p { line-height: var(--lh-body) !important; }
.main-column > * + * { margin-top: var(--space-3) !important; }
.main-column img, .main-column video, .main-column table, .main-column pre, .main-column iframe {
max-width: 100% !important; height: auto !important;
}
.main-column pre { white-space: pre-wrap !important; }
.oasis-nav-group {
list-style: none !important;
background: #1c1c1c !important;
border: 1px solid #333 !important;
border-radius: var(--radius-md) !important;
overflow: hidden !important;
margin-bottom: var(--space-2) !important;
}
.oasis-nav-header {
display: flex !important; align-items: center !important; gap: var(--space-2) !important;
min-height: 48px !important; padding: 0 var(--space-3) !important;
cursor: pointer !important;
color: #FFD700 !important; font-weight: bold !important;
text-transform: uppercase !important; letter-spacing: .04em !important;
font-size: var(--fs-caption) !important;
}
.oasis-nav-header .nav-text { flex: 1 1 auto !important; }
.oasis-nav-header .oasis-nav-arrow { color: var(--c-muted) !important; transition: transform 150ms ease !important; }
.oasis-nav-toggle:checked + .oasis-nav-header .oasis-nav-arrow { transform: rotate(180deg) !important; }
.oasis-nav-header:active { background: #262626 !important; }
.oasis-nav-list { list-style: none !important; padding: 0 !important; margin: 0 !important; }
.oasis-nav-list li a {
display: flex !important; align-items: center !important; gap: var(--space-3) !important;
min-height: 48px !important; padding: 0 var(--space-3) !important;
color: #e8e8e8 !important; text-decoration: none !important;
border-top: 1px solid #262626 !important;
}
.oasis-nav-list li a .emoji { flex: 0 0 auto !important; width: 22px !important; text-align: center !important; color: #FFA500 !important; }
.oasis-nav-list li a .nav-text { font-size: var(--fs-body) !important; }
.oasis-nav-list li a:active { background: #262626 !important; }
.oasis-nav-list li a.current { color: #FFA500 !important; font-weight: bold !important; }
.main-column h1 { font-size: var(--fs-title) !important; font-weight: 700 !important; line-height: 1.25 !important; margin: 0 0 var(--space-2) !important; }
.main-column h2 { font-size: 18px !important; font-weight: 700 !important; line-height: 1.3 !important; margin: 0 0 var(--space-2) !important; }
.main-column h3 { font-size: var(--fs-body-lg) !important; font-weight: 600 !important; margin: 0 0 var(--space-1) !important; }
.main-column p { font-size: var(--fs-body-lg) !important; }
.main-column .created-at, .main-column .small, .main-column .time,
.main-column .meta, .main-column .author-meta, .main-column .subtitle {
color: var(--c-muted) !important; font-size: var(--fs-caption) !important;
}
.main-column article,
.main-column [class*="-card"] {
background: #1c1c1c !important;
border: 1px solid #333 !important;
border-radius: var(--radius-md) !important;
padding: var(--space-3) !important;
}
.main-column article:active,
.main-column [class*="-card"]:active { background: #202020 !important; }
.btn, button, input[type="submit"], input[type="button"], .submit-button,
.filter-btn, .create-button, .edit-btn, .delete-btn, .join-btn, .leave-btn, .buy-btn {
min-height: 48px !important;
}
button:active, input[type="submit"]:active, .submit-button:active,
.filter-btn:active, .create-button:active, .edit-btn:active,
.delete-btn:active, .join-btn:active, .leave-btn:active, .buy-btn:active {
filter: brightness(.85) !important;
}
.main-column a:active { opacity: .6 !important; }
* { -webkit-tap-highlight-color: transparent !important; }
a, button, label, .tap, .oasis-menu-btn, .bb-item, .oasis-nav-header, .oasis-nav-list li a {
touch-action: manipulation !important;
}
body { overscroll-behavior: none !important; }
.header .top-bar-left nav,
.header .top-bar-right nav { display: none !important; }
.oasis-mobile-topbar { padding-top: calc(env(safe-area-inset-top, 0px) + 10px) !important; }
.oasis-bottombar .bb-pair {
display: flex !important; align-items: center !important;
flex: 0 0 auto !important;
gap: 2px !important; padding: 3px !important;
background: #262626 !important;
border: 1px solid #3a3a3a !important;
border-radius: 999px !important;
}
.oasis-bottombar .bb-pair .bb-item { flex: 0 0 auto !important; padding: 2px 8px !important; }
.oasis-bottombar .bb-pair .bb-lbl { font-size: 8px !important; }
.oasis-bottombar a.bb-item.current .bb-ico {
background: #FFA500 !important; color: #121212 !important;
border-radius: 999px !important; padding: 4px 12px !important;
}
.oasis-bottombar a.bb-item.current .bb-lbl { color: #FFA500 !important; font-weight: bold !important; }
.oasis-bottombar a.bb-item.current .bb-badge { background: #121212 !important; color: #FFA500 !important; }
.main-column, .main-column p, .main-column li, .main-column a,
.main-column span, .main-column td, .main-column th, .main-column label,
.main-column h1, .main-column h2, .main-column h3, .main-column h4,
.main-column strong, .main-column em, .main-column small,
.created-at, .post-meta, .feed-post-meta,
.feed-row .small, .feed-row .time, .feed-row .created-at,
.profile-sensors-box > *,
.card-field .card-value, .card-field .card-label {
overflow-wrap: break-word !important;
word-break: normal !important;
-webkit-hyphens: none !important;
hyphens: none !important;
}
.main-column pre, .main-column code, kbd, samp,
.peer-key, .user-link.peer-key, .oasis-id-copy, .search-oasis-id,
.pm-sent-key-value, .kpi__value,
.block-info-table td .peer-key, .block-info-table td .user-link,
.pm-info-table td .user-link,
.main-column a[href*="ed25519"], .main-column a[href^="ssb:"],
.main-column a[href^="/author/"] {
overflow-wrap: anywhere !important;
word-break: break-all !important;
}
button, input[type="submit"], input[type="button"],
.filter-btn, a.filter-btn, .create-button, .edit-btn, .delete-btn,
.join-btn, .leave-btn, .buy-btn, .submit-button, .btn,
.conn-actions button, .peers-conn-actions button,
.mode-buttons button, .mode-buttons .filter-btn,
.filter-group button, .filter-group .filter-btn,
.main-column button, .main-column input[type="submit"] {
white-space: normal !important;
word-break: normal !important;
overflow-wrap: break-word !important;
-webkit-hyphens: none !important;
hyphens: none !important;
}
.conn-actions, .peers-conn-actions {
display: flex !important;
flex-direction: row !important;
flex-wrap: wrap !important;
justify-content: center !important;
align-items: stretch !important;
gap: 8px !important;
width: 100% !important;
max-width: 100% !important;
overflow-x: hidden !important;
}
.conn-actions > form, .peers-conn-actions > form {
flex: 0 1 auto !important;
width: auto !important;
min-width: 0 !important;
margin: 0 !important;
display: flex !important;
}
.pm-info-table, .pm-info-table tbody,
.block-info-table, .block-info-table tbody {
display: block !important;
width: 100% !important;
max-width: 100% !important;
border: 0 !important;
background: transparent !important;
box-shadow: none !important;
table-layout: auto !important;
}
.pm-info-table thead, .block-info-table thead { display: none !important; }
.pm-info-table tr, .block-info-table tr {
display: block !important;
width: 100% !important;
box-sizing: border-box !important;
border: 0 !important;
border-bottom: 1px solid rgba(255,165,0,0.18) !important;
border-radius: 0 !important;
margin: 0 0 8px 0 !important;
padding: 6px 0 !important;
background: transparent !important;
}
.pm-info-table td, .block-info-table td {
display: block !important;
width: 100% !important;
max-width: 100% !important;
box-sizing: border-box !important;
border: 0 !important;
background: transparent !important;
padding: 1px 0 !important;
text-align: left !important;
overflow-wrap: break-word !important;
word-break: normal !important;
}
.pm-info-table td.card-label, .block-info-table td.card-label {
color: var(--c-muted, #8c8c8c) !important;
font-size: 11px !important;
font-weight: 700 !important;
text-transform: uppercase !important;
letter-spacing: .03em !important;
margin-bottom: 2px !important;
padding-bottom: 0 !important;
}
.pm-info-table td.card-value, .block-info-table td.card-value {
font-size: 15px !important;
line-height: 1.5 !important;
margin-bottom: 6px !important;
}
.block-info-table tr:first-child:has(> td.card-label) { display: none !important; }
.block-info-table td[data-label]:not([data-label=""])::before {
content: attr(data-label);
display: block !important;
color: var(--c-muted, #8c8c8c) !important;
font-size: 10px !important;
font-weight: 700 !important;
text-transform: uppercase !important;
letter-spacing: .03em !important;
margin-bottom: 1px !important;
white-space: nowrap !important;
word-break: normal !important;
}
.block-info-table td[data-label=""]::before { content: none !important; }
.peers-list .block-info-table td,
.peers-technical-block .block-info-table td,
.gov-overview tbody td {
word-break: normal !important;
overflow-wrap: break-word !important;
}
.pm-info-table td .user-link, .pm-info-table td .peer-key, .pm-info-table td a[href],
.block-info-table td .peer-key, .block-info-table td .user-link, .block-info-table td a[href] {
display: inline-block !important;
max-width: 100% !important;
overflow-wrap: anywhere !important;
word-break: break-all !important;
}
.pm-info-table td form, .block-info-table td form,
.pm-info-table td button, .block-info-table td button {
width: 100% !important;
margin: 0 !important;
}
.main-content { width: 100% !important; max-width: 100% !important; }
.main-column {
width: 100% !important; max-width: 100% !important;
padding: 16px !important; box-sizing: border-box !important;
}
.main-column .card-field {
flex-direction: column !important;
align-items: stretch !important;
gap: 2px !important;
}
.main-column .card-field .card-value {
width: 100% !important; max-width: 100% !important; font-size: 15px !important;
}
.main-column .card-field .card-label {
color: var(--c-muted, #8c8c8c) !important; font-size: 11px !important;
}
.main-column img, .main-column video, .main-column iframe, .main-column canvas {
max-width: 100% !important; height: auto !important;
}
.main-column hr {
width: 100% !important; border: 0 !important;
border-top: 1px solid #333 !important; margin: 16px 0 !important;
}
.activity-filter-acc { width: 100% !important; margin-bottom: 8px !important; }
.activity-quick-modes {
display: flex !important; flex-wrap: nowrap !important; gap: 6px !important; margin-bottom: 8px !important;
}
.activity-quick-modes form { flex: 1 1 0 !important; min-width: 0 !important; margin: 0 !important; display: flex !important; }
.activity-quick-modes .filter-btn {
flex: 1 1 0 !important; width: 100% !important; min-height: 44px !important;
white-space: nowrap !important; word-break: keep-all !important; overflow-wrap: normal !important;
padding-left: 6px !important; padding-right: 6px !important; font-size: 14px !important;
}
.activity-groups { list-style: none !important; padding: 0 !important; margin: 0 !important; }
.activity-groups .oasis-nav-header .emoji { display: none !important; }
.activity-groups .oasis-nav-header .nav-text { flex: 1 1 auto !important; }
.activity-groups .act-count {
flex: 0 0 auto !important; min-width: 20px !important; text-align: center !important;
font-size: 11px !important; font-weight: 700 !important; color: #121212 !important;
background: #FFA500 !important; border-radius: 999px !important; padding: 1px 7px !important;
margin: 0 8px 0 0 !important;
}
.activity-groups .oasis-nav-list li { list-style: none !important; margin: 0 !important; padding: 0 !important; }
.activity-groups .oasis-nav-list form { margin: 0 !important; display: block !important; width: 100% !important; }
.activity-groups .oasis-nav-list .filter-btn {
display: block !important; width: 100% !important; text-align: left !important;
min-height: 44px !important; margin: 2px 0 !important; box-sizing: border-box !important;
}
.activity-groups .oasis-nav-list .filter-btn.active {
outline: 2px solid #FFA500 !important; outline-offset: -2px !important;
}
.message-list { display: flex !important; flex-direction: column !important; gap: 4px !important; width: 100% !important; }
.message-list .pm-thread { display: flex !important; flex-direction: column !important; width: 100% !important; align-self: stretch !important; }
.pm-thread-replies { display: flex !important; flex-direction: column !important; gap: 4px !important; }
.pm-card.pm-bubble {
max-width: 82% !important; width: fit-content !important; min-width: 0 !important;
box-sizing: border-box !important; padding: 8px 12px !important; margin: 2px 0 !important;
border-radius: 16px !important; border: 1px solid #333 !important;
background: #1e1e1e !important; box-shadow: none !important; cursor: default !important;
}
.pm-card.pm-bubble.pm-recv {
align-self: flex-start !important; margin-left: 0 !important; margin-right: auto !important;
border-bottom-left-radius: 4px !important; background: #1e1e1e !important; border-color: #343434 !important;
}
.pm-card.pm-bubble.pm-sent {
align-self: flex-end !important; margin-left: auto !important; margin-right: 0 !important;
border-bottom-right-radius: 4px !important; background: #2a2413 !important; border-color: #5c4c1f !important;
}
.pm-bubble .pm-bubble-sender { font-size: 11px !important; font-weight: 700 !important; color: #FFA500 !important; margin-bottom: 3px !important; }
.pm-bubble .pm-bubble-sender a { color: #FFA500 !important; }
.pm-bubble .message-text { font-size: 15px !important; line-height: 1.42 !important; color: #ececec !important; margin: 0 !important; }
.pm-bubble .message-text a { color: #FFD700 !important; }
.pm-bubble .pm-bubble-meta {
display: flex !important; flex-wrap: wrap !important; align-items: center !important;
gap: 6px !important; margin-top: 4px !important; font-size: 10px !important; color: var(--c-muted, #8c8c8c) !important;
}
.pm-card.pm-sent .pm-bubble-meta { justify-content: flex-end !important; }
.pm-card.pm-recv .pm-bubble-meta { justify-content: flex-start !important; }
.pm-bubble .pm-bubble-subject { font-style: italic !important; opacity: .75 !important; max-width: 50% !important; overflow: hidden !important; text-overflow: ellipsis !important; white-space: nowrap !important; }
.pm-bubble .pm-bubble-lock { opacity: .85 !important; }
.pm-bubble .pm-bubble-eco { font-size: 9px !important; opacity: .9 !important; }
.pm-bubble .pm-bubble-eco * { font-size: 9px !important; }
.pm-bubble .pm-actions { margin-top: 6px !important; display: flex !important; gap: 6px !important; }
.pm-card.pm-sent .pm-actions { justify-content: flex-end !important; }
.pm-bubble .pm-actions .pm-btn { font-size: 10px !important; padding: 3px 8px !important; min-height: 28px !important; }
.pm-bubble .pm-actions form { margin: 0 !important; }
.pm-bubble .pm-info-table { display: none !important; }
.pm-thread .pm-thread-details { width: 100% !important; }
.pm-thread .pm-thread-toggle { font-size: 12px !important; color: var(--c-muted, #8c8c8c) !important; padding: 4px 0 !important; cursor: pointer !important; }
.main-column h1, .main-column h2, .main-column h3, .main-column h4,
.tags-header h2, .tags-header h3, .title-with-chip h2, .pm-title, h2.pm-title,
.cn-name, h1.cn-name, .cn-section, h2.cn-section, .profile-side-name, h2.profile-side-name {
text-wrap: balance !important;
overflow-wrap: break-word !important;
word-break: keep-all !important;
-webkit-hyphens: none !important; hyphens: none !important;
text-align: left !important;
}
.main-column h1, .tags-header h2, .cn-name {
font-size: var(--fs-title, 20px) !important; font-weight: 700 !important;
line-height: 1.22 !important; letter-spacing: -0.01em !important;
margin: 0 0 var(--space-2, 8px) !important;
}
.main-column h2, .pm-title, .title-with-chip h2, .cn-section {
font-size: 18px !important; font-weight: 700 !important;
line-height: 1.28 !important; margin: var(--space-1, 4px) 0 var(--space-2, 8px) !important;
}
.main-column h3 {
font-size: var(--fs-body-lg, 16px) !important; font-weight: 600 !important;
line-height: 1.35 !important; margin: var(--space-1, 4px) 0 !important;
}
.main-column h1, .main-column h2, .main-column h3,
.tags-header h2, .pm-title, .cn-name, .cn-section {
max-width: 40ch !important; width: auto !important; min-width: 0 !important;
}
.main-column h1 code, .main-column h2 code, .main-column h3 code,
.tags-header h2 code, .pm-title code,
.main-column h1 a[href*="ed25519"], .main-column h2 a[href*="ed25519"],
.main-column h1 a[href^="ssb:"], .main-column h2 a[href^="ssb:"] {
word-break: break-all !important; overflow-wrap: anywhere !important;
}
.main-column .tags-header {
display: flex !important; flex-direction: column !important; align-items: flex-start !important;
gap: var(--space-1, 4px) !important; width: 100% !important; max-width: 100% !important;
margin: 0 0 var(--space-2, 8px) !important;
}
.main-column .tags-header h2 { margin: 0 !important; }
.main-column .title-with-chip {
display: flex !important; flex-wrap: wrap !important; align-items: center !important;
gap: var(--space-2, 8px) !important; width: 100% !important; max-width: 100% !important;
}
.main-column .title-with-chip h2 { flex: 1 1 auto !important; min-width: 0 !important; margin: 0 !important; max-width: none !important; }
.main-column .title-with-chip > :not(h2) { flex: 0 0 auto !important; }
.main-column p, .main-column li,
.tags-header p, .tags-header .prefs-help, .indexing-note, .indexing-percent,
.error-page-message, .welcome-text, .message-text, .pm-bubble .message-text {
text-wrap: pretty !important;
line-height: 1.55 !important;
max-width: min(66ch, 100%) !important;
overflow-wrap: anywhere !important;
word-break: normal !important;
min-width: 0 !important;
-webkit-hyphens: none !important; hyphens: none !important;
}
.main-column p + p { margin-top: var(--space-3, 16px) !important; }
.tags-header p, .tags-header .prefs-help, .indexing-note { line-height: 1.5 !important; margin: 4px 0 0 0 !important; }
.pm-bubble .message-text { line-height: 1.45 !important; max-width: 100% !important; }
[style*="auto-fill"],
[style*="minmax(240px"], [style*="minmax(280px"], [style*="minmax(300px"],
[style*="minmax( 240px"], [style*="minmax( 280px"], [style*="minmax( 300px"],
.trending-container, .cn-hub-grid, .cn-products {
display: grid !important; grid-template-columns: 1fr !important; gap: 12px !important;
width: 100% !important; max-width: 100% !important;
}
.trending-container > *, .games-grid > *, .market-grid > *, .tribe-grid > *,
.feed-container, .feed-card, .trending-card,
.main-column article, .main-column [class*="-card"], .main-column [class*="-item"],
.main-column [class*="-entry"], .main-column .card {
width: 100% !important; max-width: 100% !important; min-width: 0 !important;
box-sizing: border-box !important; margin-left: 0 !important; margin-right: 0 !important;
}
.feed-card .feed-text, .trending-card .feed-text,
.main-column article p, .main-column [class*="-card"] p { max-width: var(--measure, 66ch) !important; }
.main-column form, .comment-form, .comment-form-wrapper, .market-form,
.new-message-form, .project-control-form, .filter-box {
display: block !important; width: 100% !important; max-width: 100% !important;
min-width: 0 !important; box-sizing: border-box !important;
}
.main-column input:not([type="checkbox"]):not([type="radio"]):not(.oasis-nav-toggle):not(.oasis-nav-side-toggle),
.main-column textarea, .main-column select {
width: 100% !important; max-width: 100% !important; min-width: 0 !important; box-sizing: border-box !important;
}
.main-column form > button:only-of-type, .main-column form > .create-button,
.comment-form button[type="submit"], .market-form button[type="submit"],
.new-message-form button[type="submit"] {
display: block !important; width: 100% !important; max-width: 100% !important;
box-sizing: border-box !important; margin-top: 8px !important;
}
[style*="width:33%"], [style*="width: 33%"], [style*="width:25%"], [style*="width: 25%"],
[style*="width:60%"], [style*="width: 60%"], [style*="width:40%"], [style*="width: 40%"] {
width: 100% !important; max-width: 100% !important;
}
.main-column [style*="max-width: 800px"], .main-column [style*="max-width:800px"],
.ai-response, .ai-train-bar {
max-width: 100% !important; width: 100% !important; margin-left: 0 !important; margin-right: 0 !important; box-sizing: border-box !important;
}
.message-list, .pm-thread, .pm-thread-replies { width: 100% !important; max-width: 100% !important; box-sizing: border-box !important; }
.main-column p .user-link, .main-column p a[href^="/author/"],
.main-column li .user-link, .main-column span .user-link,
.main-column .message-text .user-link, .main-column .message-text a[href^="/author/"],
.pm-bubble .message-text .user-link, .pm-bubble .message-text a[href^="/author/"],
.pm-quote a[href^="/author/"] {
display: inline !important; white-space: normal !important;
overflow-wrap: anywhere !important; word-break: keep-all !important;
-webkit-hyphens: none !important; hyphens: none !important;
}
.peer-key, .user-link.peer-key, .block-info-table td .peer-key, .pm-info-table td .peer-key,
.pm-info-table td .user-link, .block-info-table td .user-link,
.search-oasis-id, .pm-sent-key-value, .kpi__value {
display: inline-block !important; max-width: 100% !important;
overflow-wrap: anywhere !important; word-break: break-all !important;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace !important;
font-variant-numeric: tabular-nums !important; line-height: 1.45 !important;
}
.oasis-id-copy {
display: block !important; width: 100% !important; white-space: nowrap !important;
overflow: hidden !important; text-overflow: ellipsis !important;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace !important;
}
.invites-list .user-link.peer-key, .peers-list .block-info-table td .peer-key {
display: block !important; max-width: 100% !important; white-space: nowrap !important;
overflow: hidden !important; text-overflow: ellipsis !important; word-break: normal !important;
}
.main-column .message-text a[href^="http"], .main-column p a[href^="http"],
.pm-bubble .message-text a[href^="http"], .main-column .message-text a.oasis-path-link,
.main-column .message-text a.job-link, .main-column .message-text a.project-link,
.main-column .message-text a.market-link {
overflow-wrap: anywhere !important; word-break: keep-all !important;
-webkit-hyphens: none !important; hyphens: none !important;
}
.main-column code:not(pre code), .pm-bubble .message-text code {
display: inline !important; max-width: 100% !important;
overflow-wrap: anywhere !important; word-break: break-word !important; white-space: pre-wrap !important;
}
.main-column .tags-header { margin-bottom: 6px !important; gap: 2px !important; }
.main-column .tags-header h2, .tags-header h2 {
font-size: 17px !important; line-height: 1.2 !important; letter-spacing: 0 !important;
}
.main-column .tags-header p { font-size: 13px !important; line-height: 1.35 !important; margin: 0 !important; }
.main-column { padding: 10px 12px 16px 12px !important; }
.activity-filter-acc { margin-bottom: 4px !important; }

View file

@ -0,0 +1,777 @@
html { overflow-x: clip !important; overflow-y: visible !important; height: auto !important; }
body { overflow-x: clip !important; overflow-y: visible !important; }
.oasis-bottombar {
display: flex !important;
position: fixed !important;
left: 0 !important; right: 0 !important; bottom: 0 !important;
z-index: 1000 !important;
justify-content: space-around !important;
align-items: center !important;
gap: 4px !important;
background: #1F1F1F !important;
border-top: 1px solid #333 !important;
padding: 8px 6px calc(8px + env(safe-area-inset-bottom, 0px)) !important;
}
.oasis-bottombar .bb-item {
position: relative !important;
display: flex !important;
flex-direction: column !important;
align-items: center !important;
gap: 3px !important;
flex: 1 1 0 !important;
min-width: 0 !important;
padding: 2px 0 !important;
text-decoration: none !important;
}
.oasis-bottombar .bb-ico { font-size: 18px !important; line-height: 1 !important; color: #FFA500 !important; }
.oasis-bottombar .bb-lbl { font-size: 9px !important; color: #ccc !important; white-space: nowrap !important; }
.oasis-bottombar .bb-badge {
position: absolute !important;
top: -4px !important;
left: calc(50% + 6px) !important;
min-width: 15px !important; height: 15px !important;
padding: 0 3px !important;
border-radius: 8px !important;
background: #FFA500 !important;
color: #121212 !important;
font-size: 9px !important; font-weight: bold !important;
display: flex !important; align-items: center !important; justify-content: center !important;
}
body { padding-bottom: calc(64px + env(safe-area-inset-bottom, 0px)) !important; }
.oasis-mobile-topbar {
display: flex !important;
align-items: center !important;
justify-content: space-between !important;
width: 100% !important;
padding: 8px 12px !important;
background: transparent !important;
box-shadow: none !important; border: 0 !important;
}
.oasis-mobile-topbar .omt-logo img,
.oasis-mobile-topbar .omt-avatar img {
width: 32px !important; height: 32px !important;
border-radius: 50% !important;
border: 2px solid #FFA500 !important;
object-fit: cover !important; display: block !important;
}
.oasis-mobile-topbar .omt-title {
flex: 1 1 auto !important;
text-align: center !important;
font-size: 26px !important; font-weight: bold !important;
letter-spacing: 10px !important; text-indent: 10px !important;
color: #FFD700 !important; text-decoration: none !important;
white-space: nowrap !important;
background: transparent !important; border: 0 !important; box-shadow: none !important; padding: 0 !important;
}
.header .top-bar-left > a.logo-icon { display: none !important; }
.oasis-mobile-menus { display: flex !important; gap: 8px !important; padding: 0 10px 6px !important; }
.oasis-mobile-menus .oasis-menu-ico { display: none !important; }
.oasis-mobile-menus .oasis-menu-btn {
flex: 1 1 0 !important;
display: flex !important; align-items: center !important; justify-content: center !important;
padding: 12px !important;
border: 1.5px solid #333 !important; border-radius: 10px !important;
background: #121212 !important; color: #ccc !important;
font-weight: bold !important; letter-spacing: 1px !important; cursor: pointer !important;
}
#nav-left-toggle:checked ~ .header .oasis-menu-btn[for="nav-left-toggle"] {
background: #FFA500 !important; color: #121212 !important; border-color: #FFA500 !important;
}
#nav-right-toggle:checked ~ .header .oasis-menu-btn[for="nav-right-toggle"] {
background: #FFD700 !important; color: #121212 !important; border-color: #FFD700 !important;
}
:root {
--space-1: 4px; --space-2: 8px; --space-3: 16px; --space-4: 24px; --space-5: 32px;
--fs-caption: 12px; --fs-body: 14px; --fs-body-lg: 16px; --fs-title: 20px;
--lh-body: 1.5; --measure: 66ch;
--c-muted: #8c8c8c; --radius-md: 12px;
}
.oasis-bottombar .bb-fab .bb-ico {
width: 46px !important; height: 46px !important;
margin-top: -22px !important;
border-radius: 50% !important;
background: #FFA500 !important; color: #121212 !important;
display: flex !important; align-items: center !important; justify-content: center !important;
font-size: 22px !important;
box-shadow: 0 2px 8px rgba(0,0,0,.4) !important;
}
.oasis-bottombar .bb-fab .bb-lbl { color: #FFA500 !important; font-weight: bold !important; }
.main-column, .main-column p, .main-column li, .main-column a,
.main-column span, .main-column td, .main-column h1, .main-column h2, .main-column h3 {
overflow-wrap: anywhere !important;
word-break: break-word !important;
}
.main-column { padding: var(--space-3) !important; }
.main-column p { line-height: var(--lh-body) !important; }
.main-column > * + * { margin-top: var(--space-3) !important; }
.main-column img, .main-column video, .main-column table, .main-column pre, .main-column iframe {
max-width: 100% !important; height: auto !important;
}
.main-column pre { white-space: pre-wrap !important; }
.oasis-nav-group {
list-style: none !important;
background: #1c1c1c !important;
border: 1px solid #333 !important;
border-radius: var(--radius-md) !important;
overflow: hidden !important;
margin-bottom: var(--space-2) !important;
}
.oasis-nav-header {
display: flex !important; align-items: center !important; gap: var(--space-2) !important;
min-height: 48px !important; padding: 0 var(--space-3) !important;
cursor: pointer !important;
color: #FFD700 !important; font-weight: bold !important;
text-transform: uppercase !important; letter-spacing: .04em !important;
font-size: var(--fs-caption) !important;
}
.oasis-nav-header .nav-text { flex: 1 1 auto !important; }
.oasis-nav-header .oasis-nav-arrow { color: var(--c-muted) !important; transition: transform 150ms ease !important; }
.oasis-nav-toggle:checked + .oasis-nav-header .oasis-nav-arrow { transform: rotate(180deg) !important; }
.oasis-nav-header:active { background: #262626 !important; }
.oasis-nav-list { list-style: none !important; padding: 0 !important; margin: 0 !important; }
.oasis-nav-list li a {
display: flex !important; align-items: center !important; gap: var(--space-3) !important;
min-height: 48px !important; padding: 0 var(--space-3) !important;
color: #e8e8e8 !important; text-decoration: none !important;
border-top: 1px solid #262626 !important;
}
.oasis-nav-list li a .emoji { flex: 0 0 auto !important; width: 22px !important; text-align: center !important; color: #FFA500 !important; }
.oasis-nav-list li a .nav-text { font-size: var(--fs-body) !important; }
.oasis-nav-list li a:active { background: #262626 !important; }
.oasis-nav-list li a.current { color: #FFA500 !important; font-weight: bold !important; }
.main-column h1 { font-size: var(--fs-title) !important; font-weight: 700 !important; line-height: 1.25 !important; margin: 0 0 var(--space-2) !important; }
.main-column h2 { font-size: 18px !important; font-weight: 700 !important; line-height: 1.3 !important; margin: 0 0 var(--space-2) !important; }
.main-column h3 { font-size: var(--fs-body-lg) !important; font-weight: 600 !important; margin: 0 0 var(--space-1) !important; }
.main-column p { font-size: var(--fs-body-lg) !important; }
.main-column .created-at, .main-column .small, .main-column .time,
.main-column .meta, .main-column .author-meta, .main-column .subtitle {
color: var(--c-muted) !important; font-size: var(--fs-caption) !important;
}
.main-column article,
.main-column [class*="-card"] {
background: #1c1c1c !important;
border: 1px solid #333 !important;
border-radius: var(--radius-md) !important;
padding: var(--space-3) !important;
}
.main-column article:active,
.main-column [class*="-card"]:active { background: #202020 !important; }
.btn, button, input[type="submit"], input[type="button"], .submit-button,
.filter-btn, .create-button, .edit-btn, .delete-btn, .join-btn, .leave-btn, .buy-btn {
min-height: 48px !important;
}
button:active, input[type="submit"]:active, .submit-button:active,
.filter-btn:active, .create-button:active, .edit-btn:active,
.delete-btn:active, .join-btn:active, .leave-btn:active, .buy-btn:active {
filter: brightness(.85) !important;
}
.main-column a:active { opacity: .6 !important; }
* { -webkit-tap-highlight-color: transparent !important; }
a, button, label, .tap, .oasis-menu-btn, .bb-item, .oasis-nav-header, .oasis-nav-list li a {
touch-action: manipulation !important;
}
body { overscroll-behavior: none !important; }
.main-content, .main-column, .header,
.sidebar-left, .sidebar-right { overflow: visible !important; }
.sidebar-left, .sidebar-right { display: none !important; }
#nav-left-toggle:checked ~ .main-content .sidebar-left { display: block !important; }
#nav-right-toggle:checked ~ .main-content .sidebar-right { display: block !important; }
.bb-manage { margin: 8px 0 16px !important; }
.bb-manage h3, .bb-manage-add h3 {
font-size: 13px !important; text-transform: uppercase !important; letter-spacing: .05em !important;
color: #FFA500 !important; margin: 14px 0 8px !important;
}
.bb-manage-list { display: flex !important; flex-direction: column !important; gap: 8px !important; }
.bb-manage-form { margin: 0 !important; width: 100% !important; }
.bb-manage-btn {
width: 100% !important;
display: flex !important; align-items: center !important; gap: 12px !important;
min-height: 52px !important; padding: 0 14px !important;
background: #1c1c1c !important; border: 1px solid #333 !important; border-radius: 10px !important;
color: #eee !important; cursor: pointer !important; text-align: left !important;
}
.bb-manage-ico { font-size: 20px !important; color: #FFA500 !important; width: 26px !important; text-align: center !important; }
.bb-manage-lbl { flex: 1 1 auto !important; font-size: 15px !important; }
.bb-manage-x { font-size: 16px !important; color: #ff6b6b !important; font-weight: bold !important; }
.bb-manage-empty, .bb-manage-full { color: #999 !important; font-size: 14px !important; padding: 8px 2px !important; }
.bb-manage-done {
display: block !important; text-align: center !important;
margin-top: 16px !important; min-height: 48px !important; line-height: 48px !important;
}
.hive-nav { --hx: min(64px, calc((100vw - 40px) / 4)); --hxm: min(58px, calc((100vw - 36px) / 5)); margin: 8px 0 16px !important; overflow: visible !important; background: transparent !important; padding: 0 !important; border: 0 !important; box-shadow: none !important; }
.hive-radio { position: absolute !important; opacity: 0 !important; width: 0 !important; height: 0 !important; pointer-events: none !important; }
.hive-grid { display: flex !important; flex-direction: column !important; align-items: center !important; overflow: visible !important; background: transparent !important; padding: 0 !important; margin: 0 !important; box-shadow: none !important; }
.hive-row { display: flex !important; justify-content: center !important; gap: 3px !important; overflow: visible !important; background: transparent !important; padding: 0 !important; margin: 0 !important; box-shadow: none !important; }
.hive-cats .hive-row + .hive-row { margin-top: calc(var(--hx) * -0.30) !important; }
.hex-cat, .hex-mod {
position: relative !important;
-webkit-clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%) !important;
clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%) !important;
display: flex !important; flex-direction: column !important; align-items: center !important; justify-content: center !important; gap: 2px !important;
text-decoration: none !important; cursor: pointer !important; box-sizing: border-box !important;
background: #D9A400 !important;
filter: drop-shadow(0 1px 2px rgba(0,0,0,.6)) !important;
}
.hex-cat::before, .hex-mod::before {
content: '' !important; position: absolute !important; inset: 1.5px !important; z-index: 0 !important;
-webkit-clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%) !important;
clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%) !important;
background: #161616 !important;
}
.hex-cat { width: var(--hx) !important; height: calc(var(--hx) * 1.15) !important; }
.hex-mod { width: var(--hxm) !important; height: calc(var(--hxm) * 1.15) !important; }
.hex-cat .hx-ico, .hex-mod .hx-ico { color: #FFA500 !important; line-height: 1 !important; position: relative !important; z-index: 1 !important; }
.hex-cat .hx-ico { font-size: 17px !important; }
.hex-mod .hx-ico { font-size: 16px !important; }
.hex-cat .hx-lbl, .hex-mod .hx-lbl { color: #d8d8d8 !important; text-align: center !important; line-height: 1 !important; font-weight: bold !important; overflow: hidden !important; position: relative !important; z-index: 1 !important; }
.hex-cat .hx-lbl { font-size: 8px !important; max-width: 88% !important; }
.hex-mod .hx-lbl { font-size: 8px !important; max-width: 92% !important; }
.hex-cat:active::before, .hex-mod:active::before { background: #242424 !important; }
#hive-personal:checked ~ .hive-grid .hex-cat[for="hive-personal"],
#hive-governance:checked ~ .hive-grid .hex-cat[for="hive-governance"],
#hive-office:checked ~ .hive-grid .hex-cat[for="hive-office"],
#hive-economy:checked ~ .hive-grid .hex-cat[for="hive-economy"],
#hive-tools:checked ~ .hive-grid .hex-cat[for="hive-tools"],
#hive-network:checked ~ .hive-grid .hex-cat[for="hive-network"],
#hive-blogs:checked ~ .hive-grid .hex-cat[for="hive-blogs"],
#hive-creative:checked ~ .hive-grid .hex-cat[for="hive-creative"],
#hive-media:checked ~ .hive-grid .hex-cat[for="hive-media"],
#hive-fediverse:checked ~ .hive-grid .hex-cat[for="hive-fediverse"] { background: #FFA500 !important; }
#hive-personal:checked ~ .hive-grid .hex-cat[for="hive-personal"]::before,
#hive-governance:checked ~ .hive-grid .hex-cat[for="hive-governance"]::before,
#hive-office:checked ~ .hive-grid .hex-cat[for="hive-office"]::before,
#hive-economy:checked ~ .hive-grid .hex-cat[for="hive-economy"]::before,
#hive-tools:checked ~ .hive-grid .hex-cat[for="hive-tools"]::before,
#hive-network:checked ~ .hive-grid .hex-cat[for="hive-network"]::before,
#hive-blogs:checked ~ .hive-grid .hex-cat[for="hive-blogs"]::before,
#hive-creative:checked ~ .hive-grid .hex-cat[for="hive-creative"]::before,
#hive-media:checked ~ .hive-grid .hex-cat[for="hive-media"]::before,
#hive-fediverse:checked ~ .hive-grid .hex-cat[for="hive-fediverse"]::before { background: #FFA500 !important; }
#hive-personal:checked ~ .hive-grid .hex-cat[for="hive-personal"] :is(.hx-ico,.hx-lbl),
#hive-governance:checked ~ .hive-grid .hex-cat[for="hive-governance"] :is(.hx-ico,.hx-lbl),
#hive-office:checked ~ .hive-grid .hex-cat[for="hive-office"] :is(.hx-ico,.hx-lbl),
#hive-economy:checked ~ .hive-grid .hex-cat[for="hive-economy"] :is(.hx-ico,.hx-lbl),
#hive-tools:checked ~ .hive-grid .hex-cat[for="hive-tools"] :is(.hx-ico,.hx-lbl),
#hive-network:checked ~ .hive-grid .hex-cat[for="hive-network"] :is(.hx-ico,.hx-lbl),
#hive-blogs:checked ~ .hive-grid .hex-cat[for="hive-blogs"] :is(.hx-ico,.hx-lbl),
#hive-creative:checked ~ .hive-grid .hex-cat[for="hive-creative"] :is(.hx-ico,.hx-lbl),
#hive-media:checked ~ .hive-grid .hex-cat[for="hive-media"] :is(.hx-ico,.hx-lbl),
#hive-fediverse:checked ~ .hive-grid .hex-cat[for="hive-fediverse"] :is(.hx-ico,.hx-lbl) { color: #121212 !important; }
.hive-panel { display: none !important; margin: 12px auto 0 !important; max-width: 340px !important; background: #101010 !important; border: 1px solid #242424 !important; border-radius: 16px !important; padding: 12px 8px !important; overflow: visible !important; box-shadow: none !important; }
#hive-personal:checked ~ #hive-panel-personal,
#hive-governance:checked ~ #hive-panel-governance,
#hive-office:checked ~ #hive-panel-office,
#hive-economy:checked ~ #hive-panel-economy,
#hive-tools:checked ~ #hive-panel-tools,
#hive-network:checked ~ #hive-panel-network,
#hive-blogs:checked ~ #hive-panel-blogs,
#hive-creative:checked ~ #hive-panel-creative,
#hive-media:checked ~ #hive-panel-media,
#hive-fediverse:checked ~ #hive-panel-fediverse { display: block !important; }
.hive-mods { display: flex !important; flex-wrap: wrap !important; justify-content: center !important; gap: 4px !important; margin: 0 !important; padding: 0 !important; background: transparent !important; overflow: visible !important; }
.header .top-bar-left nav,
.header .top-bar-right nav { display: none !important; }
.oasis-mobile-topbar { padding-top: calc(env(safe-area-inset-top, 0px) + 10px) !important; }
.oasis-bottombar .bb-pair {
display: flex !important; align-items: center !important;
flex: 0 0 auto !important;
gap: 2px !important; padding: 3px !important;
background: #262626 !important;
border: 1px solid #3a3a3a !important;
border-radius: 999px !important;
}
.oasis-bottombar .bb-pair .bb-item { flex: 0 0 auto !important; padding: 2px 8px !important; }
.oasis-bottombar .bb-pair .bb-lbl { font-size: 8px !important; }
.oasis-bottombar a.bb-item.current .bb-ico {
background: #FFA500 !important; color: #121212 !important;
border-radius: 999px !important; padding: 4px 12px !important;
}
.oasis-bottombar a.bb-item.current .bb-lbl { color: #FFA500 !important; font-weight: bold !important; }
.oasis-bottombar a.bb-item.current .bb-badge { background: #121212 !important; color: #FFA500 !important; }
.main-column, .main-column p, .main-column li, .main-column a,
.main-column span, .main-column td, .main-column th, .main-column label,
.main-column h1, .main-column h2, .main-column h3, .main-column h4,
.main-column strong, .main-column em, .main-column small,
.created-at, .post-meta, .feed-post-meta,
.feed-row .small, .feed-row .time, .feed-row .created-at,
.profile-sensors-box > *,
.card-field .card-value, .card-field .card-label {
overflow-wrap: break-word !important;
word-break: normal !important;
-webkit-hyphens: none !important;
hyphens: none !important;
}
.main-column pre, .main-column code, kbd, samp,
.peer-key, .user-link.peer-key, .oasis-id-copy, .search-oasis-id,
.pm-sent-key-value, .kpi__value,
.block-info-table td .peer-key, .block-info-table td .user-link,
.pm-info-table td .user-link,
.main-column a[href*="ed25519"], .main-column a[href^="ssb:"],
.main-column a[href^="/author/"] {
overflow-wrap: anywhere !important;
word-break: break-all !important;
}
button, input[type="submit"], input[type="button"],
.filter-btn, a.filter-btn, .create-button, .edit-btn, .delete-btn,
.join-btn, .leave-btn, .buy-btn, .submit-button, .btn,
.conn-actions button, .peers-conn-actions button,
.mode-buttons button, .mode-buttons .filter-btn,
.filter-group button, .filter-group .filter-btn,
.main-column button, .main-column input[type="submit"] {
white-space: normal !important;
word-break: normal !important;
overflow-wrap: break-word !important;
-webkit-hyphens: none !important;
hyphens: none !important;
}
.conn-actions, .peers-conn-actions {
display: flex !important;
flex-direction: row !important;
flex-wrap: wrap !important;
justify-content: center !important;
align-items: stretch !important;
gap: 8px !important;
width: 100% !important;
max-width: 100% !important;
overflow-x: hidden !important;
}
.conn-actions > form, .peers-conn-actions > form {
flex: 0 1 auto !important;
width: auto !important;
min-width: 0 !important;
margin: 0 !important;
display: flex !important;
}
.pm-info-table, .pm-info-table tbody,
.block-info-table, .block-info-table tbody {
display: block !important;
width: 100% !important;
max-width: 100% !important;
border: 0 !important;
background: transparent !important;
box-shadow: none !important;
table-layout: auto !important;
}
.pm-info-table thead, .block-info-table thead { display: none !important; }
.pm-info-table tr, .block-info-table tr {
display: block !important;
width: 100% !important;
box-sizing: border-box !important;
border: 0 !important;
border-bottom: 1px solid rgba(255,165,0,0.18) !important;
border-radius: 0 !important;
margin: 0 0 8px 0 !important;
padding: 6px 0 !important;
background: transparent !important;
}
.pm-info-table td, .block-info-table td {
display: block !important;
width: 100% !important;
max-width: 100% !important;
box-sizing: border-box !important;
border: 0 !important;
background: transparent !important;
padding: 1px 0 !important;
text-align: left !important;
overflow-wrap: break-word !important;
word-break: normal !important;
}
.pm-info-table td.card-label, .block-info-table td.card-label {
color: var(--c-muted, #8c8c8c) !important;
font-size: 11px !important;
font-weight: 700 !important;
text-transform: uppercase !important;
letter-spacing: .03em !important;
margin-bottom: 2px !important;
padding-bottom: 0 !important;
}
.pm-info-table td.card-value, .block-info-table td.card-value {
font-size: 15px !important;
line-height: 1.5 !important;
margin-bottom: 6px !important;
}
.block-info-table tr:first-child:has(> td.card-label) { display: none !important; }
.block-info-table td[data-label]:not([data-label=""])::before {
content: attr(data-label);
display: block !important;
color: var(--c-muted, #8c8c8c) !important;
font-size: 10px !important;
font-weight: 700 !important;
text-transform: uppercase !important;
letter-spacing: .03em !important;
margin-bottom: 1px !important;
white-space: nowrap !important;
word-break: normal !important;
}
.block-info-table td[data-label=""]::before { content: none !important; }
.peers-list .block-info-table td,
.peers-technical-block .block-info-table td,
.gov-overview tbody td {
word-break: normal !important;
overflow-wrap: break-word !important;
}
.pm-info-table td .user-link, .pm-info-table td .peer-key, .pm-info-table td a[href],
.block-info-table td .peer-key, .block-info-table td .user-link, .block-info-table td a[href] {
display: inline-block !important;
max-width: 100% !important;
overflow-wrap: anywhere !important;
word-break: break-all !important;
}
.pm-info-table td form, .block-info-table td form,
.pm-info-table td button, .block-info-table td button {
width: 100% !important;
margin: 0 !important;
}
.main-content { width: 100% !important; max-width: 100% !important; }
.main-column {
width: 100% !important; max-width: 100% !important;
padding: 16px !important; box-sizing: border-box !important;
}
.main-column .card-field {
flex-direction: column !important;
align-items: stretch !important;
gap: 2px !important;
}
.main-column .card-field .card-value {
width: 100% !important; max-width: 100% !important; font-size: 15px !important;
}
.main-column .card-field .card-label {
color: var(--c-muted, #8c8c8c) !important; font-size: 11px !important;
}
.main-column img, .main-column video, .main-column iframe, .main-column canvas {
max-width: 100% !important; height: auto !important;
}
.main-column hr {
width: 100% !important; border: 0 !important;
border-top: 1px solid #333 !important; margin: 16px 0 !important;
}
.activity-filter-acc { width: 100% !important; margin-bottom: 8px !important; }
.activity-quick-modes {
display: flex !important; flex-wrap: nowrap !important; gap: 6px !important; margin-bottom: 8px !important;
}
.activity-quick-modes form { flex: 1 1 0 !important; min-width: 0 !important; margin: 0 !important; display: flex !important; }
.activity-quick-modes .filter-btn {
flex: 1 1 0 !important; width: 100% !important; min-height: 44px !important;
white-space: nowrap !important; word-break: keep-all !important; overflow-wrap: normal !important;
padding-left: 6px !important; padding-right: 6px !important; font-size: 14px !important;
}
.activity-groups { list-style: none !important; padding: 0 !important; margin: 0 !important; }
.activity-groups .oasis-nav-header .emoji { display: none !important; }
.activity-groups .oasis-nav-header .nav-text { flex: 1 1 auto !important; }
.activity-groups .act-count {
flex: 0 0 auto !important; min-width: 20px !important; text-align: center !important;
font-size: 11px !important; font-weight: 700 !important; color: #121212 !important;
background: #FFA500 !important; border-radius: 999px !important; padding: 1px 7px !important;
margin: 0 8px 0 0 !important;
}
.activity-groups .oasis-nav-list li { list-style: none !important; margin: 0 !important; padding: 0 !important; }
.activity-groups .oasis-nav-list form { margin: 0 !important; display: block !important; width: 100% !important; }
.activity-groups .oasis-nav-list .filter-btn {
display: block !important; width: 100% !important; text-align: left !important;
min-height: 44px !important; margin: 2px 0 !important; box-sizing: border-box !important;
}
.activity-groups .oasis-nav-list .filter-btn.active {
outline: 2px solid #FFA500 !important; outline-offset: -2px !important;
}
.message-list { display: flex !important; flex-direction: column !important; gap: 4px !important; width: 100% !important; }
.message-list .pm-thread { display: flex !important; flex-direction: column !important; width: 100% !important; align-self: stretch !important; }
.pm-thread-replies { display: flex !important; flex-direction: column !important; gap: 4px !important; }
.pm-card.pm-bubble {
max-width: 82% !important; width: fit-content !important; min-width: 0 !important;
box-sizing: border-box !important; padding: 8px 12px !important; margin: 2px 0 !important;
border-radius: 16px !important; border: 1px solid #333 !important;
background: #1e1e1e !important; box-shadow: none !important; cursor: default !important;
}
.pm-card.pm-bubble.pm-recv {
align-self: flex-start !important; margin-left: 0 !important; margin-right: auto !important;
border-bottom-left-radius: 4px !important; background: #1e1e1e !important; border-color: #343434 !important;
}
.pm-card.pm-bubble.pm-sent {
align-self: flex-end !important; margin-left: auto !important; margin-right: 0 !important;
border-bottom-right-radius: 4px !important; background: #2a2413 !important; border-color: #5c4c1f !important;
}
.pm-bubble .pm-bubble-sender { font-size: 11px !important; font-weight: 700 !important; color: #FFA500 !important; margin-bottom: 3px !important; }
.pm-bubble .pm-bubble-sender a { color: #FFA500 !important; }
.pm-bubble .message-text { font-size: 15px !important; line-height: 1.42 !important; color: #ececec !important; margin: 0 !important; }
.pm-bubble .message-text a { color: #FFD700 !important; }
.pm-bubble .pm-bubble-meta {
display: flex !important; flex-wrap: wrap !important; align-items: center !important;
gap: 6px !important; margin-top: 4px !important; font-size: 10px !important; color: var(--c-muted, #8c8c8c) !important;
}
.pm-card.pm-sent .pm-bubble-meta { justify-content: flex-end !important; }
.pm-card.pm-recv .pm-bubble-meta { justify-content: flex-start !important; }
.pm-bubble .pm-bubble-subject { font-style: italic !important; opacity: .75 !important; max-width: 50% !important; overflow: hidden !important; text-overflow: ellipsis !important; white-space: nowrap !important; }
.pm-bubble .pm-bubble-lock { opacity: .85 !important; }
.pm-bubble .pm-bubble-eco { font-size: 9px !important; opacity: .9 !important; }
.pm-bubble .pm-bubble-eco * { font-size: 9px !important; }
.pm-bubble .pm-actions { margin-top: 6px !important; display: flex !important; gap: 6px !important; }
.pm-card.pm-sent .pm-actions { justify-content: flex-end !important; }
.pm-bubble .pm-actions .pm-btn { font-size: 10px !important; padding: 3px 8px !important; min-height: 28px !important; }
.pm-bubble .pm-actions form { margin: 0 !important; }
.pm-bubble .pm-info-table { display: none !important; }
.pm-thread .pm-thread-details { width: 100% !important; }
.pm-thread .pm-thread-toggle { font-size: 12px !important; color: var(--c-muted, #8c8c8c) !important; padding: 4px 0 !important; cursor: pointer !important; }
.main-column h1, .main-column h2, .main-column h3, .main-column h4,
.tags-header h2, .tags-header h3, .title-with-chip h2, .pm-title, h2.pm-title,
.cn-name, h1.cn-name, .cn-section, h2.cn-section, .profile-side-name, h2.profile-side-name {
text-wrap: balance !important;
overflow-wrap: break-word !important;
word-break: keep-all !important;
-webkit-hyphens: none !important; hyphens: none !important;
text-align: left !important;
}
.main-column h1, .tags-header h2, .cn-name {
font-size: var(--fs-title, 20px) !important; font-weight: 700 !important;
line-height: 1.22 !important; letter-spacing: -0.01em !important;
margin: 0 0 var(--space-2, 8px) !important;
}
.main-column h2, .pm-title, .title-with-chip h2, .cn-section {
font-size: 18px !important; font-weight: 700 !important;
line-height: 1.28 !important; margin: var(--space-1, 4px) 0 var(--space-2, 8px) !important;
}
.main-column h3 {
font-size: var(--fs-body-lg, 16px) !important; font-weight: 600 !important;
line-height: 1.35 !important; margin: var(--space-1, 4px) 0 !important;
}
.main-column h1, .main-column h2, .main-column h3,
.tags-header h2, .pm-title, .cn-name, .cn-section {
max-width: 40ch !important; width: auto !important; min-width: 0 !important;
}
.main-column h1 code, .main-column h2 code, .main-column h3 code,
.tags-header h2 code, .pm-title code,
.main-column h1 a[href*="ed25519"], .main-column h2 a[href*="ed25519"],
.main-column h1 a[href^="ssb:"], .main-column h2 a[href^="ssb:"] {
word-break: break-all !important; overflow-wrap: anywhere !important;
}
.main-column .tags-header {
display: flex !important; flex-direction: column !important; align-items: flex-start !important;
gap: var(--space-1, 4px) !important; width: 100% !important; max-width: 100% !important;
margin: 0 0 var(--space-2, 8px) !important;
}
.main-column .tags-header h2 { margin: 0 !important; }
.main-column .title-with-chip {
display: flex !important; flex-wrap: wrap !important; align-items: center !important;
gap: var(--space-2, 8px) !important; width: 100% !important; max-width: 100% !important;
}
.main-column .title-with-chip h2 { flex: 1 1 auto !important; min-width: 0 !important; margin: 0 !important; max-width: none !important; }
.main-column .title-with-chip > :not(h2) { flex: 0 0 auto !important; }
.main-column p, .main-column li,
.tags-header p, .tags-header .prefs-help, .indexing-note, .indexing-percent,
.error-page-message, .welcome-text, .message-text, .pm-bubble .message-text {
text-wrap: pretty !important;
line-height: 1.55 !important;
max-width: min(66ch, 100%) !important;
overflow-wrap: anywhere !important;
word-break: normal !important;
min-width: 0 !important;
-webkit-hyphens: none !important; hyphens: none !important;
}
.main-column p + p { margin-top: var(--space-3, 16px) !important; }
.tags-header p, .tags-header .prefs-help, .indexing-note { line-height: 1.5 !important; margin: 4px 0 0 0 !important; }
.pm-bubble .message-text { line-height: 1.45 !important; max-width: 100% !important; }
[style*="auto-fill"],
[style*="minmax(240px"], [style*="minmax(280px"], [style*="minmax(300px"],
[style*="minmax( 240px"], [style*="minmax( 280px"], [style*="minmax( 300px"],
.trending-container, .cn-hub-grid, .cn-products {
display: grid !important; grid-template-columns: 1fr !important; gap: 12px !important;
width: 100% !important; max-width: 100% !important;
}
.trending-container > *, .games-grid > *, .market-grid > *, .tribe-grid > *,
.feed-container, .feed-card, .trending-card,
.main-column article, .main-column [class*="-card"], .main-column [class*="-item"],
.main-column [class*="-entry"], .main-column .card {
width: 100% !important; max-width: 100% !important; min-width: 0 !important;
box-sizing: border-box !important; margin-left: 0 !important; margin-right: 0 !important;
}
.feed-card .feed-text, .trending-card .feed-text,
.main-column article p, .main-column [class*="-card"] p { max-width: var(--measure, 66ch) !important; }
.main-column form, .comment-form, .comment-form-wrapper, .market-form,
.new-message-form, .project-control-form, .filter-box {
display: block !important; width: 100% !important; max-width: 100% !important;
min-width: 0 !important; box-sizing: border-box !important;
}
.main-column input:not([type="checkbox"]):not([type="radio"]):not(.oasis-nav-toggle):not(.oasis-nav-side-toggle),
.main-column textarea, .main-column select {
width: 100% !important; max-width: 100% !important; min-width: 0 !important; box-sizing: border-box !important;
}
.main-column form > button:only-of-type, .main-column form > .create-button,
.comment-form button[type="submit"], .market-form button[type="submit"],
.new-message-form button[type="submit"] {
display: block !important; width: 100% !important; max-width: 100% !important;
box-sizing: border-box !important; margin-top: 8px !important;
}
[style*="width:33%"], [style*="width: 33%"], [style*="width:25%"], [style*="width: 25%"],
[style*="width:60%"], [style*="width: 60%"], [style*="width:40%"], [style*="width: 40%"] {
width: 100% !important; max-width: 100% !important;
}
.main-column [style*="max-width: 800px"], .main-column [style*="max-width:800px"],
.ai-response, .ai-train-bar {
max-width: 100% !important; width: 100% !important; margin-left: 0 !important; margin-right: 0 !important; box-sizing: border-box !important;
}
.message-list, .pm-thread, .pm-thread-replies { width: 100% !important; max-width: 100% !important; box-sizing: border-box !important; }
.main-column p .user-link, .main-column p a[href^="/author/"],
.main-column li .user-link, .main-column span .user-link,
.main-column .message-text .user-link, .main-column .message-text a[href^="/author/"],
.pm-bubble .message-text .user-link, .pm-bubble .message-text a[href^="/author/"],
.pm-quote a[href^="/author/"] {
display: inline !important; white-space: normal !important;
overflow-wrap: anywhere !important; word-break: keep-all !important;
-webkit-hyphens: none !important; hyphens: none !important;
}
.peer-key, .user-link.peer-key, .block-info-table td .peer-key, .pm-info-table td .peer-key,
.pm-info-table td .user-link, .block-info-table td .user-link,
.search-oasis-id, .pm-sent-key-value, .kpi__value {
display: inline-block !important; max-width: 100% !important;
overflow-wrap: anywhere !important; word-break: break-all !important;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace !important;
font-variant-numeric: tabular-nums !important; line-height: 1.45 !important;
}
.oasis-id-copy {
display: block !important; width: 100% !important; white-space: nowrap !important;
overflow: hidden !important; text-overflow: ellipsis !important;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace !important;
}
.invites-list .user-link.peer-key, .peers-list .block-info-table td .peer-key {
display: block !important; max-width: 100% !important; white-space: nowrap !important;
overflow: hidden !important; text-overflow: ellipsis !important; word-break: normal !important;
}
.main-column .message-text a[href^="http"], .main-column p a[href^="http"],
.pm-bubble .message-text a[href^="http"], .main-column .message-text a.oasis-path-link,
.main-column .message-text a.job-link, .main-column .message-text a.project-link,
.main-column .message-text a.market-link {
overflow-wrap: anywhere !important; word-break: keep-all !important;
-webkit-hyphens: none !important; hyphens: none !important;
}
.main-column code:not(pre code), .pm-bubble .message-text code {
display: inline !important; max-width: 100% !important;
overflow-wrap: anywhere !important; word-break: break-word !important; white-space: pre-wrap !important;
}
.main-column .tags-header { margin-bottom: 6px !important; gap: 2px !important; }
.main-column .tags-header h2, .tags-header h2 {
font-size: 17px !important; line-height: 1.2 !important; letter-spacing: 0 !important;
}
.main-column .tags-header p { font-size: 13px !important; line-height: 1.35 !important; margin: 0 !important; }
.main-column { padding: 10px 12px 16px 12px !important; }
.activity-filter-acc { margin-bottom: 4px !important; }
.empty-state {
display: flex !important; flex-direction: column !important; align-items: center !important;
justify-content: center !important; text-align: center !important;
padding: 40px 16px !important; gap: 8px !important; width: 100% !important; box-sizing: border-box !important;
}
.empty-state-icon { font-size: 44px !important; line-height: 1 !important; opacity: 0.55 !important; }
.empty-state-title { font-size: 16px !important; font-weight: 700 !important; margin: 4px 0 0 0 !important; color: #eee !important; }
.empty-state-text { color: var(--c-muted, #8c8c8c) !important; font-size: 14px !important; line-height: 1.4 !important; max-width: 40ch !important; margin: 0 !important; }
.empty-state-cta { margin-top: 10px !important; min-height: 48px !important; display: inline-flex !important; align-items: center !important; }
.activity-filter-collapsed {
display: flex !important; align-items: center !important; gap: 10px !important;
flex-wrap: wrap !important; margin-bottom: 8px !important; width: 100% !important;
}
.activity-back {
display: inline-flex !important; align-items: center !important; gap: 4px !important;
min-height: 44px !important; padding: 6px 14px 6px 10px !important; flex: 0 0 auto !important;
}
.activity-back-arrow { font-size: 22px !important; line-height: 1 !important; font-weight: bold !important; }
.activity-active-filter {
font-weight: 700 !important; color: #FFA500 !important; text-transform: uppercase !important;
font-size: 13px !important; letter-spacing: .03em !important;
}
.oasis-bottombar .bb-item { min-height: 48px !important; }
.oasis-bottombar .bb-pair .bb-item { min-height: 44px !important; padding: 4px 10px !important; }
.oasis-mobile-menus .oasis-menu-btn { min-height: 48px !important; }
.pm-bubble .pm-actions .pm-btn { min-height: 40px !important; padding: 6px 12px !important; }
.score-btn, .vote-btn, .feed-action-btn {
min-height: 44px !important; min-width: 44px !important;
display: inline-flex !important; align-items: center !important; justify-content: center !important;
}
.score-btn:active, .vote-btn:active, .feed-action-btn:active { filter: brightness(.85) !important; }
.oasis-bottombar .bb-add .bb-ico {
background: #FFA500 !important; color: #121212 !important; border-radius: 999px !important;
width: 30px !important; height: 30px !important; display: inline-flex !important;
align-items: center !important; justify-content: center !important;
font-weight: bold !important; font-size: 20px !important; line-height: 1 !important;
}
.bb-pick-grid {
display: grid !important; grid-template-columns: repeat(3, 1fr) !important;
gap: 8px !important; width: 100% !important; margin-top: 8px !important;
}
.bb-pick-form { margin: 0 !important; display: block !important; }
.bb-pick-btn {
display: flex !important; flex-direction: column !important; align-items: center !important;
justify-content: center !important; gap: 6px !important; width: 100% !important;
min-height: 76px !important; padding: 10px 6px !important; box-sizing: border-box !important;
background: #1c1c1c !important; border: 1px solid #333 !important; border-radius: 12px !important; color: #eee !important;
}
.bb-pick-btn.active { border-color: #FFA500 !important; background: #2a2412 !important; }
.bb-pick-ico { font-size: 26px !important; color: #FFA500 !important; line-height: 1 !important; }
.bb-pick-lbl { font-size: 11px !important; text-align: center !important; line-height: 1.2 !important; }

View file

@ -1,6 +1,20 @@
const { a, em, strong } = require('../../../server/node_modules/hyperaxe');
module.exports = {
ar: {
bbAdd: "إضافة",
bbQuickActions: "إجراءات سريعة",
bbPickTitle: "تثبيت وحدة",
bbPickDesc: "اختر وحدة لخانة + في الشريط السفلي.",
bbPickNone: "لا شيء",
bbManage: "تعديل",
bbYourBar: "شريطك",
bbRemove: "إزالة",
bbFull: "تم بلوغ الحد الأقصى",
bbDone: "تم",
activityChangeFilter: "تغيير التصفية",
emptyConnectHint: "اتصل بـ pub للمزامنة مع الشبكة.",
emptyConnectCta: "الاتصال بـ pub",
menuCommunity: "المجتمع",
activityGroupCommunity: "المجتمع والحوكمة",
activityGroupOrg: "التنظيم",
activityGroupComm: "التواصل",

View file

@ -1,6 +1,20 @@
const { a, em, strong } = require('../../../server/node_modules/hyperaxe');
module.exports = {
de: {
bbAdd: "Hinzufügen",
bbQuickActions: "Schnellaktionen",
bbPickTitle: "Modul anheften",
bbPickDesc: "Wähle ein Modul für den +-Platz in der unteren Leiste.",
bbPickNone: "Keins",
bbManage: "Bearbeiten",
bbYourBar: "Deine Leiste",
bbRemove: "Entfernen",
bbFull: "Maximum erreicht",
bbDone: "Fertig",
activityChangeFilter: "Filter ändern",
emptyConnectHint: "Verbinde dich mit einem Pub, um dich mit dem Netzwerk zu synchronisieren.",
emptyConnectCta: "Mit einem Pub verbinden",
menuCommunity: "Gemeinschaft",
activityGroupCommunity: "Gemeinschaft & Governance",
activityGroupOrg: "Organisation",
activityGroupComm: "Kommunikation",

View file

@ -1,6 +1,20 @@
const { a, em, strong } = require('../../../server/node_modules/hyperaxe');
module.exports = {
en: {
bbAdd: "Add",
bbQuickActions: "Quick actions",
bbPickTitle: "Pin a module",
bbPickDesc: "Choose a module for the + slot in the bottom bar.",
bbPickNone: "None",
bbManage: "Edit",
bbYourBar: "Your bar",
bbRemove: "Remove",
bbFull: "Maximum reached",
bbDone: "Done",
activityChangeFilter: "Change filter",
emptyConnectHint: "Connect to a pub to sync with the network.",
emptyConnectCta: "Connect to a pub",
menuCommunity: "Community",
activityGroupCommunity: "Community & Governance",
activityGroupOrg: "Organization",
activityGroupComm: "Communication",

View file

@ -1,6 +1,20 @@
const { a, em, strong } = require('../../../server/node_modules/hyperaxe');
module.exports = {
es: {
bbAdd: "Añadir",
bbQuickActions: "Acciones rápidas",
bbPickTitle: "Fijar un módulo",
bbPickDesc: "Elige un módulo para el hueco + de la barra inferior.",
bbPickNone: "Ninguno",
bbManage: "Editar",
bbYourBar: "Tu barra",
bbRemove: "Quitar",
bbFull: "Máximo alcanzado",
bbDone: "Hecho",
activityChangeFilter: "Cambiar filtro",
emptyConnectHint: "Conéctate a un pub para sincronizar con la red.",
emptyConnectCta: "Conéctate a un pub",
menuCommunity: "Comunidad",
activityGroupCommunity: "Comunidad y Gobernanza",
activityGroupOrg: "Organización",
activityGroupComm: "Comunicación",

View file

@ -1,6 +1,20 @@
const { a, em, strong } = require('../../../server/node_modules/hyperaxe');
module.exports = {
eu: {
bbAdd: "Gehitu",
bbQuickActions: "Ekintza azkarrak",
bbPickTitle: "Ainguratu modulu bat",
bbPickDesc: "Aukeratu modulu bat beheko barrako + hutsunerako.",
bbPickNone: "Bat ere ez",
bbManage: "Editatu",
bbYourBar: "Zure barra",
bbRemove: "Kendu",
bbFull: "Gehienekora iritsi da",
bbDone: "Eginda",
activityChangeFilter: "Aldatu iragazkia",
emptyConnectHint: "Konektatu pub batera sarearekin sinkronizatzeko.",
emptyConnectCta: "Konektatu pub batera",
menuCommunity: "Komunitatea",
activityGroupCommunity: "Komunitatea eta gobernantza",
activityGroupOrg: "Antolakuntza",
activityGroupComm: "Komunikazioa",

View file

@ -1,6 +1,20 @@
const { a, em, strong } = require('../../../server/node_modules/hyperaxe');
module.exports = {
fr: {
bbAdd: "Ajouter",
bbQuickActions: "Actions rapides",
bbPickTitle: "Épingler un module",
bbPickDesc: "Choisis un module pour l'emplacement + de la barre inférieure.",
bbPickNone: "Aucun",
bbManage: "Modifier",
bbYourBar: "Ta barre",
bbRemove: "Retirer",
bbFull: "Maximum atteint",
bbDone: "Terminé",
activityChangeFilter: "Changer de filtre",
emptyConnectHint: "Connecte-toi à un pub pour synchroniser avec le réseau.",
emptyConnectCta: "Se connecter à un pub",
menuCommunity: "Communauté",
activityGroupCommunity: "Communauté et gouvernance",
activityGroupOrg: "Organisation",
activityGroupComm: "Communication",

View file

@ -1,6 +1,20 @@
const { a, em, strong } = require('../../../server/node_modules/hyperaxe');
module.exports = {
hi: {
bbAdd: "जोड़ें",
bbQuickActions: "त्वरित क्रियाएँ",
bbPickTitle: "एक मॉड्यूल पिन करें",
bbPickDesc: "नीचे की बार में + स्लॉट के लिए एक मॉड्यूल चुनें।",
bbPickNone: "कोई नहीं",
bbManage: "संपादित करें",
bbYourBar: "आपकी बार",
bbRemove: "हटाएँ",
bbFull: "अधिकतम पहुँच गया",
bbDone: "हो गया",
activityChangeFilter: "फ़िल्टर बदलें",
emptyConnectHint: "नेटवर्क से सिंक करने के लिए किसी pub से जुड़ें।",
emptyConnectCta: "pub से जुड़ें",
menuCommunity: "समुदाय",
activityGroupCommunity: "समुदाय और शासन",
activityGroupOrg: "संगठन",
activityGroupComm: "संचार",

View file

@ -1,6 +1,20 @@
const { a, em, strong } = require('../../../server/node_modules/hyperaxe');
module.exports = {
it: {
bbAdd: "Aggiungi",
bbQuickActions: "Azioni rapide",
bbPickTitle: "Fissa un modulo",
bbPickDesc: "Scegli un modulo per lo spazio + nella barra inferiore.",
bbPickNone: "Nessuno",
bbManage: "Modifica",
bbYourBar: "La tua barra",
bbRemove: "Rimuovi",
bbFull: "Massimo raggiunto",
bbDone: "Fatto",
activityChangeFilter: "Cambia filtro",
emptyConnectHint: "Connettiti a un pub per sincronizzarti con la rete.",
emptyConnectCta: "Connettiti a un pub",
menuCommunity: "Comunità",
activityGroupCommunity: "Comunità e governance",
activityGroupOrg: "Organizzazione",
activityGroupComm: "Comunicazione",

View file

@ -1,6 +1,20 @@
const { a, em, strong } = require('../../../server/node_modules/hyperaxe');
module.exports = {
pt: {
bbAdd: "Adicionar",
bbQuickActions: "Ações rápidas",
bbPickTitle: "Fixar um módulo",
bbPickDesc: "Escolhe um módulo para o espaço + da barra inferior.",
bbPickNone: "Nenhum",
bbManage: "Editar",
bbYourBar: "Sua barra",
bbRemove: "Remover",
bbFull: "Máximo atingido",
bbDone: "Concluído",
activityChangeFilter: "Mudar filtro",
emptyConnectHint: "Liga-te a um pub para sincronizar com a rede.",
emptyConnectCta: "Ligar a um pub",
menuCommunity: "Comunidade",
activityGroupCommunity: "Comunidade e governança",
activityGroupOrg: "Organização",
activityGroupComm: "Comunicação",

View file

@ -1,6 +1,20 @@
const { a, em, strong } = require('../../../server/node_modules/hyperaxe');
module.exports = {
ru: {
bbAdd: "Добавить",
bbQuickActions: "Быстрые действия",
bbPickTitle: "Закрепить модуль",
bbPickDesc: "Выберите модуль для слота + в нижней панели.",
bbPickNone: "Нет",
bbManage: "Изменить",
bbYourBar: "Ваша панель",
bbRemove: "Убрать",
bbFull: "Достигнут максимум",
bbDone: "Готово",
activityChangeFilter: "Сменить фильтр",
emptyConnectHint: "Подключитесь к пабу для синхронизации с сетью.",
emptyConnectCta: "Подключиться к пабу",
menuCommunity: "Сообщество",
activityGroupCommunity: "Сообщество и управление",
activityGroupOrg: "Организация",
activityGroupComm: "Коммуникация",

View file

@ -1,6 +1,20 @@
const { a, em, strong } = require('../../../server/node_modules/hyperaxe');
module.exports = {
zh: {
bbAdd: "添加",
bbQuickActions: "快捷操作",
bbPickTitle: "固定一个模块",
bbPickDesc: "为底部栏的 + 位置选择一个模块。",
bbPickNone: "无",
bbManage: "编辑",
bbYourBar: "你的工具栏",
bbRemove: "移除",
bbFull: "已达上限",
bbDone: "完成",
activityChangeFilter: "更改筛选",
emptyConnectHint: "连接到一个 pub 以与网络同步。",
emptyConnectCta: "连接到 pub",
menuCommunity: "社区",
activityGroupCommunity: "社区与治理",
activityGroupOrg: "组织",
activityGroupComm: "沟通",

View file

@ -78,6 +78,7 @@ if (!fs.existsSync(configFilePath)) {
"limit": 2000
},
"homePage": "activity",
"bottomBarPins": ["search", "inbox", "publish"],
"language": "en",
"wish": "whole",
"pmVisibility": "whole",
@ -96,6 +97,15 @@ const getConfig = () => {
if (cfg.ux.current === 'menus') cfg.ux.current = 'blocks';
if (cfg.ux.current !== 'blocks' && cfg.ux.current !== 'ainav') cfg.ux.current = 'blocks';
if (cfg.ux.current === 'ainav' && cfg.modules && cfg.modules.aiNavMod !== 'on') cfg.ux.current = 'blocks';
let pins = Array.isArray(cfg.bottomBarPins)
? cfg.bottomBarPins
: (typeof cfg.bottomBarPin === 'string'
? (cfg.bottomBarPin ? [cfg.bottomBarPin] : [])
: ['search', 'inbox', 'publish']);
pins = pins.filter(k => typeof k === 'string' && k);
pins = pins.filter((k, i) => pins.indexOf(k) === i).slice(0, 4);
cfg.bottomBarPins = pins;
delete cfg.bottomBarPin;
return cfg;
};

View file

@ -1,6 +1,7 @@
const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
const keylock = require('./keylock');
const SENSITIVE_FIELDS = [
'title', 'description', 'location', 'price', 'salary', 'options', 'votes',
@ -45,7 +46,14 @@ module.exports = (configPath, namespace = 'tribes') => {
const loadKeyring = () => {
try {
keyring = JSON.parse(fs.readFileSync(keyringPath, 'utf8'));
const raw = fs.readFileSync(keyringPath);
if (keylock.isWrapped(raw)) {
if (!keylock.getKEK()) throw new Error('keyring cifrado pero keylock bloqueado');
keyring = JSON.parse(keylock.unwrap(raw).toString('utf8'));
} else {
keyring = JSON.parse(raw.toString('utf8'));
if (keylock.getKEK()) { try { saveKeyring(); } catch (_) {} }
}
try { fs.chmodSync(keyringPath, 0o600); } catch (_) {}
} catch (e) {
if (e.code !== 'ENOENT') throw e;
@ -55,8 +63,11 @@ module.exports = (configPath, namespace = 'tribes') => {
};
const saveKeyring = () => {
const json = JSON.stringify(keyring, null, 2);
const kek = keylock.getKEK();
const out = kek ? keylock.wrap(Buffer.from(json, 'utf8'), kek) : Buffer.from(json, 'utf8');
const tmp = keyringPath + '.tmp.' + process.pid + '.' + Date.now();
fs.writeFileSync(tmp, JSON.stringify(keyring, null, 2), { encoding: 'utf8', mode: 0o600 });
fs.writeFileSync(tmp, out, { mode: 0o600 });
fs.renameSync(tmp, keyringPath);
try { fs.chmodSync(keyringPath, 0o600); } catch (_) {}
};

129
src/models/keylock.js Normal file
View file

@ -0,0 +1,129 @@
const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
const ssbKeys = require('../server/node_modules/ssb-keys');
const MAGIC = Buffer.from('OAES1\n', 'utf8');
const KEYLEN = 32;
const DEFAULT_SCRYPT = { N: 16384, r: 8, p: 1 };
const VERIFIER_PLAINTEXT = Buffer.from('oasis-keylock-v1', 'utf8');
let KEK = null; // SOLO en memoria
const maxmemFor = (s) => Math.max(64 * 1024 * 1024, 256 * s.N * s.r);
const deriveKEK = (password, salt, scrypt = DEFAULT_SCRYPT) =>
crypto.scryptSync(Buffer.from(String(password), 'utf8'), salt, KEYLEN,
{ N: scrypt.N, r: scrypt.r, p: scrypt.p, maxmem: maxmemFor(scrypt) });
const isWrapped = (buf) =>
Buffer.isBuffer(buf) && buf.length >= MAGIC.length && buf.subarray(0, MAGIC.length).equals(MAGIC);
const wrap = (buf, kek = KEK) => {
if (!kek) throw new Error('keylock: locked (no KEK)');
const iv = crypto.randomBytes(12);
const c = crypto.createCipheriv('aes-256-gcm', kek, iv);
const ct = Buffer.concat([c.update(buf), c.final()]);
const tag = c.getAuthTag();
return Buffer.concat([MAGIC, iv, tag, ct]);
};
const unwrap = (blob, kek = KEK) => {
if (!kek) throw new Error('keylock: locked (no KEK)');
if (!isWrapped(blob)) throw new Error('keylock: not a wrapped blob');
let o = MAGIC.length;
const iv = blob.subarray(o, o += 12);
const tag = blob.subarray(o, o += 16);
const ct = blob.subarray(o);
const d = crypto.createDecipheriv('aes-256-gcm', kek, iv);
d.setAuthTag(tag);
return Buffer.concat([d.update(ct), d.final()]);
};
const lockPath = (configPath) => path.join(configPath, 'keylock.json');
const isConfigured = (configPath) => {
try { return fs.existsSync(lockPath(configPath)); } catch (_) { return false; }
};
const setPassword = (configPath, password) => {
const salt = crypto.randomBytes(16);
const scrypt = DEFAULT_SCRYPT;
const kek = deriveKEK(password, salt, scrypt);
const verifier = wrap(VERIFIER_PLAINTEXT, kek);
const data = {
v: 1, kdf: 'scrypt', N: scrypt.N, r: scrypt.r, p: scrypt.p,
salt: salt.toString('hex'), verifier: verifier.toString('base64'),
};
fs.mkdirSync(configPath, { recursive: true, mode: 0o700 });
const tmp = lockPath(configPath) + '.tmp';
fs.writeFileSync(tmp, JSON.stringify(data), { mode: 0o600 });
fs.renameSync(tmp, lockPath(configPath));
KEK = kek;
return kek;
};
const verifyPassword = (configPath, password) => {
let data;
try { data = JSON.parse(fs.readFileSync(lockPath(configPath), 'utf8')); } catch (_) { return null; }
const salt = Buffer.from(data.salt, 'hex');
const scrypt = { N: data.N || DEFAULT_SCRYPT.N, r: data.r || DEFAULT_SCRYPT.r, p: data.p || DEFAULT_SCRYPT.p };
const kek = deriveKEK(password, salt, scrypt);
try {
if (unwrap(Buffer.from(data.verifier, 'base64'), kek).equals(VERIFIER_PLAINTEXT)) {
KEK = kek;
return kek;
}
} catch (_) {}
return null;
};
const getKEK = () => KEK;
const setKEK = (k) => { KEK = k; };
const isUnlocked = () => KEK != null;
const lock = () => { try { if (KEK) KEK.fill(0); } catch (_) {} KEK = null; };
const secretEncPath = (configPath) => path.join(configPath, 'secret.enc');
const legacySecretPath = (configPath) => path.join(configPath, 'secret');
const shred = (p) => {
try { fs.writeFileSync(p, crypto.randomBytes(Math.max(1, fs.statSync(p).size))); } catch (_) {}
try { fs.unlinkSync(p); } catch (_) {}
};
const writeSecret = (configPath, keys) => {
const blob = wrap(Buffer.from(JSON.stringify(keys), 'utf8'));
fs.mkdirSync(configPath, { recursive: true, mode: 0o700 });
const tmp = secretEncPath(configPath) + '.tmp';
fs.writeFileSync(tmp, blob, { mode: 0o600 });
fs.renameSync(tmp, secretEncPath(configPath));
};
const loadOrCreateSecret = (configPath) => {
if (!KEK) throw new Error('keylock: locked');
fs.mkdirSync(configPath, { recursive: true, mode: 0o700 });
const encP = secretEncPath(configPath);
if (fs.existsSync(encP)) {
return JSON.parse(unwrap(fs.readFileSync(encP)).toString('utf8'));
}
const legP = legacySecretPath(configPath);
if (fs.existsSync(legP)) {
const keys = ssbKeys.loadSync(legP);
writeSecret(configPath, keys);
shred(legP);
return keys;
}
const keys = ssbKeys.generate('ed25519'); // primer arranque
writeSecret(configPath, keys);
return keys;
};
const hasIdentity = (configPath) =>
fs.existsSync(secretEncPath(configPath)) || fs.existsSync(legacySecretPath(configPath));
module.exports = {
MAGIC, deriveKEK, wrap, unwrap, isWrapped,
isConfigured, setPassword, verifyPassword,
getKEK, setKEK, isUnlocked, lock,
loadOrCreateSecret, writeSecret, hasIdentity,
};

View file

@ -11,9 +11,14 @@ const i = argv.indexOf('--');
const conf = argv.slice(i + 1);
const cliArgs = ~i ? argv.slice(0, i) : argv;
let config = Config('ssb', minimist(conf));
const override = minimist(conf);
if (global.__OASIS_KEYS__) override.keys = global.__OASIS_KEYS__;
let config = Config('ssb', override);
config = { ...config, ...configData };
if (global.__OASIS_KEYS__) config.keys = global.__OASIS_KEYS__;
const debug = process.argv.includes('--debug') || process.env.OASIS_DEBUG === '1' || process.env.OASIS_DEBUG === 'true';
if (debug) {
config.logging = { ...(config.logging || {}), level: 'debug' };

View file

@ -1,5 +1,5 @@
const { div, h2, p, section, button, form, a, input, img, textarea, br, span, video: videoHyperaxe, audio: audioHyperaxe, table, tr, td, th, details, summary, ul, li, label } = require("../server/node_modules/hyperaxe");
const { template, i18n, userLink, userLinkLabel, renderSpreadButton, renderContentActions } = require('./main_views');
const { template, i18n, userLink, userLinkLabel, renderSpreadButton, renderContentActions, emptyState, renderHiveNav } = require('./main_views');
const opinionCategories = require('../backend/opinion_categories');
const OPINION_TYPES = new Set(['bookmark','votes','feed','image','audio','video','document','torrent','transfer']);
@ -285,7 +285,7 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map()) {
: validActions;
if (!deduped.length) {
return div({ class: "no-actions" }, p(i18n.noActions));
return emptyState({ icon: "📭", title: i18n.noActions, text: i18n.emptyConnectHint, ctaHref: "/invites", ctaLabel: i18n.emptyConnectCta });
}
const seenDocumentTitles = new Set();
@ -1596,7 +1596,7 @@ function renderActionCards(actions, userId, allActions, spreadMap = new Map()) {
const filteredCards = cards.filter(Boolean);
if (!filteredCards.length) {
return div({ class: "no-actions" }, p(i18n.noActions));
return emptyState({ icon: "📭", title: i18n.noActions, text: i18n.emptyConnectHint, ctaHref: "/invites", ctaLabel: i18n.emptyConnectCta });
}
return filteredCards;
}
@ -1841,6 +1841,7 @@ exports.activityView = (actions, filter, userId, q = '', extras = {}) => {
h2(i18n.activityList),
p(desc)
),
renderHiveNav(),
(() => {
const labelOf = {};
activityTypes.forEach(t => { labelOf[t.type] = t.label; });
@ -1849,33 +1850,18 @@ exports.activityView = (actions, filter, userId, q = '', extras = {}) => {
button({ type: 'submit', class: filter === type ? 'filter-btn active' : 'filter-btn' }, labelOf[type] || type)
);
const QUICK = ['recent', 'all', 'mine'];
const GROUPS = [
{ id: 'act-comunidad', emoji: '👥', title: i18n.activityGroupCommunity || 'Community & Governance', types: ['inhabitants', 'tribe', 'larp', 'parliament', 'courts', 'votes', 'report'] },
{ id: 'act-organizacion', emoji: '🗓', title: i18n.activityGroupOrg || 'Organization', types: ['event', 'calendar', 'task', 'project', 'job'] },
{ id: 'act-comunicacion', emoji: '💬', title: i18n.activityGroupComm || 'Communication', types: ['post', 'feed', 'chat', 'pad', 'forum'] },
{ id: 'act-economia', emoji: '💰', title: i18n.activityGroupEconomy || 'Economy', types: ['banking', 'market', 'shop', 'transfer'] },
{ id: 'act-media', emoji: '🎬', title: i18n.activityGroupMedia || 'Content & Media', types: ['audio', 'bookmark', 'document', 'image', 'torrent', 'video'] },
{ id: 'act-otros', emoji: '⋯', title: i18n.activityGroupOther || 'Others', types: ['map', 'curriculum'] },
];
const isTypeFilter = filter && !QUICK.includes(filter) && labelOf[filter];
if (isTypeFilter) {
return div({ class: 'activity-filter-collapsed' },
a({ class: 'activity-back filter-btn', href: '/activity' },
span({ class: 'activity-back-arrow' }, ''),
span(i18n.activityChangeFilter || 'Change filter')
),
span({ class: 'activity-active-filter' }, labelOf[filter] || filter)
);
}
return div({ class: 'activity-filter-acc' },
div({ class: 'activity-quick-modes' }, QUICK.map(mkBtn)),
ul({ class: 'oasis-nav-groups activity-groups' },
GROUPS.map(g => {
const openByFilter = g.types.includes(filter);
return li({ class: 'oasis-nav-group' },
input({ type: 'checkbox', id: `oasis-nav-group-${g.id}`, class: 'oasis-nav-toggle', ...(openByFilter ? { checked: true } : {}) }),
label({ for: `oasis-nav-group-${g.id}`, class: 'oasis-nav-header' },
span({ class: 'emoji' }, g.emoji),
span({ class: 'nav-text' }, g.title),
span({ class: 'act-count' }, String(g.types.length)),
span({ class: 'oasis-nav-arrow' }, '▾')
),
ul({ class: 'oasis-nav-list' },
g.types.map(t => li(mkBtn(t)))
)
);
})
)
div({ class: 'activity-quick-modes' }, QUICK.map(mkBtn))
);
})(),
sub

View file

@ -112,6 +112,67 @@ const renderContentActions = (msgId, viewHref) => {
};
exports.renderContentActions = renderContentActions;
const emptyState = ({ icon, title, text, ctaHref, ctaLabel }) => div(
{ class: "empty-state" },
icon ? span({ class: "empty-state-icon" }, icon) : "",
title ? p({ class: "empty-state-title" }, title) : "",
text ? p({ class: "empty-state-text" }, text) : "",
(ctaHref && ctaLabel) ? a({ class: "empty-state-cta filter-btn", href: ctaHref }, ctaLabel) : ""
);
exports.emptyState = emptyState;
exports.bottomBarPickerView = () => {
const cfg = getConfig();
const modOn = (m) => !m || cfg.modules[m] === 'on' || cfg.modules[m] === undefined;
const MAX = 4;
const pinnedKeys = cfg.bottomBarPins || [];
const pinned = pinnedKeys
.map(key => exports.PINNABLE_MODULES.find(m => m.key === key))
.filter(Boolean);
const full = pinned.length >= MAX;
const actionForm = (action, key, cls, node) => form({ method: 'POST', action: '/settings/bottombar', class: cls },
input({ type: 'hidden', name: 'action', value: action }),
input({ type: 'hidden', name: 'key', value: key }),
node
);
const pinnedRow = (m) => actionForm('remove', m.key, 'bb-manage-form',
button({ type: 'submit', class: 'bb-manage-btn', 'aria-label': i18n.bbRemove || 'Remove' },
span({ class: 'bb-manage-ico' }, m.glyph),
span({ class: 'bb-manage-lbl' }, i18n[m.labelKey] || m.key),
span({ class: 'bb-manage-x' }, '✕')
)
);
const addBtn = (m) => actionForm('add', m.key, 'bb-pick-form',
button({ type: 'submit', class: 'bb-pick-btn' },
span({ class: 'bb-pick-ico' }, m.glyph),
span({ class: 'bb-pick-lbl' }, i18n[m.labelKey] || m.key)
)
);
const addable = exports.PINNABLE_MODULES.filter(m => modOn(m.mod) && !pinnedKeys.includes(m.key));
return template(
i18n.bbManage || 'Edit bar',
section(
div({ class: 'tags-header' },
h2(i18n.bbPickTitle || 'Customize the bar'),
p(i18n.bbPickDesc || 'Choose up to 4 shortcuts for the bottom bar.')
),
div({ class: 'bb-manage' },
h3(`${i18n.bbYourBar || 'Your bar'} (${pinned.length}/${MAX})`),
pinned.length
? div({ class: 'bb-manage-list' }, ...pinned.map(pinnedRow))
: p({ class: 'bb-manage-empty' }, i18n.bbPickNone || 'None')
),
div({ class: 'bb-manage-add' },
h3(i18n.bbAdd || 'Add'),
full
? p({ class: 'bb-manage-full' }, i18n.bbFull || 'Maximum reached')
: div({ class: 'bb-pick-grid' }, ...addable.map(addBtn))
),
a({ class: 'filter-btn bb-manage-done', href: '/activity' }, i18n.bbDone || 'Done')
)
);
};
exports.renderStateChip = renderStateChip;
exports.renderOpenClosedChip = renderOpenClosedChip;
exports.renderVisibilityChip = renderVisibilityChip;
@ -1080,27 +1141,160 @@ const renderMobileTopbar = () => div(
a({ class: "omt-avatar", href: "/profile" }, img({ src: (typeof global !== "undefined" && global.__OASIS_SELF_AVATAR__) || "/assets/images/default-avatar.png", alt: i18n.profile || "Profile" }))
);
const PINNABLE_MODULES = [
{ key: 'search', href: '/search', glyph: 'ꔅ', labelKey: 'searchTitle', mod: null },
{ key: 'inbox', href: '/inbox', glyph: '☂', labelKey: 'inbox', mod: null },
{ key: 'publish', href: '/publish', glyph: '❂', labelKey: 'publish', mod: null },
{ key: 'activity', href: '/activity', glyph: 'ꔙ', labelKey: 'activityTitle', mod: null },
{ key: 'tribes', href: '/tribes', glyph: 'ꖥ', labelKey: 'tribesTitle', mod: 'tribesMod' },
{ key: 'chats', href: '/chats', glyph: 'ꖒ', labelKey: 'chatsTitle', mod: 'chatsMod' },
{ key: 'market', href: '/market', glyph: 'ꕻ', labelKey: 'marketTitle', mod: 'marketMod' },
{ key: 'events', href: '/events', glyph: 'ꕆ', labelKey: 'eventsLabel', mod: 'eventsMod' },
{ key: 'calendars', href: '/calendars', glyph: 'ꖯ', labelKey: 'calendarsTitle', mod: 'calendarsMod' },
{ key: 'banking', href: '/banking', glyph: 'ꗴ', labelKey: 'bankingTitle', mod: 'bankingMod' },
{ key: 'forum', href: '/forum', glyph: 'ꕒ', labelKey: 'forumTitle', mod: 'forumMod' },
{ key: 'videos', href: '/videos', glyph: 'ꗟ', labelKey: 'videosLabel', mod: 'videosMod' },
{ key: 'images', href: '/images', glyph: 'ꕥ', labelKey: 'imagesLabel', mod: 'imagesMod' },
{ key: 'audios', href: '/audios', glyph: 'ꔿ', labelKey: 'audiosLabel', mod: 'audiosMod' },
{ key: 'documents', href: '/documents', glyph: 'ꕨ', labelKey: 'docsLabel', mod: 'docsMod' },
{ key: 'bookmarks', href: '/bookmarks', glyph: 'ꔪ', labelKey: 'bookmarksLabel', mod: 'bookmarksMod' },
{ key: 'maps', href: '/maps', glyph: 'ꔌ', labelKey: 'mapsLabel', mod: 'mapsMod' },
{ key: 'trending', href: '/trending', glyph: 'ꗝ', labelKey: 'trendingLabel', mod: 'trendingMod' },
{ key: 'jobs', href: '/jobs', glyph: 'ꗒ', labelKey: 'jobsTitle', mod: 'jobsMod' },
{ key: 'agenda', href: '/agenda', glyph: 'ꗤ', labelKey: 'agendaTitle', mod: 'agendaMod' },
{ key: 'wallet', href: '/wallet', glyph: '❄', labelKey: 'wallet', mod: 'walletMod' },
{ key: 'tasks', href: '/tasks', glyph: 'ꖧ', labelKey: 'tasksTitle', mod: 'tasksMod' },
{ key: 'torrents', href: '/torrents', glyph: 'ꖅ', labelKey: 'torrentsLabel', mod: 'torrentsMod' },
{ key: 'tags', href: '/tags', glyph: 'ꖶ', labelKey: 'tagsLabel', mod: 'tagsMod' },
{ key: 'pads', href: '/pads', glyph: 'ꔗ', labelKey: 'padsTitle', mod: 'padsMod' },
{ key: 'projects', href: '/projects', glyph: 'ꕧ', labelKey: 'projectsTitle', mod: 'projectsMod' },
{ key: 'shops', href: '/shops', glyph: 'ꔜ', labelKey: 'shopsTitle', mod: 'shopsMod' },
{ key: 'transfers', href: '/transfers', glyph: 'ꘉ', labelKey: 'transfersTitle', mod: 'transfersMod' },
{ key: 'feed', href: '/feed', glyph: 'ꕿ', labelKey: 'feedTitle', mod: 'feedMod' },
{ key: 'pixelia', href: '/pixelia', glyph: 'ꔘ', labelKey: 'pixeliaTitle', mod: 'pixeliaMod' },
{ key: 'melody', href: '/melody', glyph: '♪', labelKey: 'melodyTitle', mod: 'melodyMod' },
{ key: 'games', href: '/games', glyph: 'ꕇ', labelKey: 'gamesTitle', mod: 'gamesMod' },
{ key: 'votes', href: '/votes', glyph: 'ꔰ', labelKey: 'votationsTitle', mod: 'votesMod' },
{ key: 'reports', href: '/reports', glyph: 'ꕥ', labelKey: 'reportsTitle', mod: 'reportsMod' },
{ key: 'opinions', href: '/opinions', glyph: 'ꔍ', labelKey: 'opinionsTitle', mod: 'opinionsMod' },
{ key: 'parliament',href: '/parliament',glyph: 'ꗞ', labelKey: 'parliamentTitle',mod: 'parliamentMod' },
{ key: 'courts', href: '/courts', glyph: 'ꖻ', labelKey: 'courtsTitle', mod: 'courtsMod' },
{ key: 'larp', href: '/larp', glyph: '✦', labelKey: 'larpTitle', mod: 'larpMod' },
{ key: 'ai', href: '/ai', glyph: 'ꘜ', labelKey: 'ai', mod: 'aiMod' },
{ key: 'cipher', href: '/cipher', glyph: 'ꗄ', labelKey: 'cipher', mod: 'cipherMod' },
{ key: 'fediverse', href: '/fediverse', glyph: 'ꗵ', labelKey: 'fediverseTimeline', mod: 'fediverseMod' },
{ key: 'inhabitants', href: '/inhabitants', glyph: 'ꖘ', labelKey: 'inhabitantsLabel', mod: null },
{ key: 'cv', href: '/cv', glyph: 'ꕛ', labelKey: 'cvTitle', mod: null },
{ key: 'stats', href: '/stats', glyph: 'ꕷ', labelKey: 'statistics', mod: null },
{ key: 'blockexplorer', href: '/blockexplorer', glyph: 'ꖸ', labelKey: 'blockchain', mod: null },
];
exports.PINNABLE_MODULES = PINNABLE_MODULES;
const renderBottomBar = () => {
const inboxN = sharedState.getInboxCount();
const cfg = getConfig();
const modOn = (m) => !m || cfg.modules[m] === 'on' || cfg.modules[m] === undefined;
const bbLink = (href, ico, label, badge) => a(
{ class: "bb-item", href },
span({ class: "bb-ico" }, ico),
(badge && badge > 0) ? span({ class: "bb-badge" }, String(badge)) : "",
span({ class: "bb-lbl" }, label)
);
const MAX = 4;
const pins = (cfg.bottomBarPins || [])
.map(key => PINNABLE_MODULES.find(m => m.key === key))
.filter(m => m && modOn(m.mod))
.slice(0, MAX);
const slots = pins.map(m => bbLink(
m.href, m.glyph, i18n[m.labelKey] || m.key, m.key === 'inbox' ? inboxN : 0
));
const editSlot = a(
{ class: "bb-item bb-add", href: "/settings/bottombar" },
span({ class: "bb-ico" }, pins.length < MAX ? "" : "✎"),
span({ class: "bb-lbl" }, pins.length < MAX ? (i18n.bbAdd || "Add") : (i18n.bbManage || "Edit"))
);
const pair = [ bbLink("/peers", "⧖", i18n.peers || "Peers") ];
if (modOn('invitesMod')) pair.push(bbLink("/invites", "ꔮ", i18n.invites || "Invites"));
return nav(
{ class: "oasis-bottombar", style: "display:none", "aria-label": "Quick actions" },
bbLink("/search", "ꔅ", i18n.searchTitle || "Buscar"),
bbLink("/inbox", "☂", i18n.inbox || "Inbox", inboxN),
bbLink("/publish", "❂", i18n.publish || "Write"),
bbLink("/activity", "ꔙ", i18n.activityTitle || "Activity"),
div({ class: "bb-pair" },
bbLink("/peers", "⧖", i18n.peers || "Peers"),
bbLink("/invites", "ꔮ", i18n.invites || "Invites")
)
{ class: "oasis-bottombar", style: "display:none", "aria-label": i18n.bbQuickActions || "Quick actions" },
...slots,
editSlot,
div({ class: "bb-pair" }, ...pair)
);
};
const RADIAL_ITEMS = [
{ key: 'search', href: '/search', glyph: 'ꔅ', labelKey: 'searchTitle', mod: null },
{ key: 'chats', href: '/chats', glyph: 'ꖒ', labelKey: 'chatsTitle', mod: 'chatsMod' },
{ key: 'tribes', href: '/tribes', glyph: 'ꖥ', labelKey: 'tribesTitle', mod: 'tribesMod' },
{ key: 'market', href: '/market', glyph: 'ꕻ', labelKey: 'marketTitle', mod: 'marketMod' },
{ key: 'events', href: '/events', glyph: 'ꕆ', labelKey: 'eventsLabel', mod: 'eventsMod' },
{ key: 'forum', href: '/forum', glyph: 'ꕒ', labelKey: 'forumTitle', mod: 'forumMod' },
{ key: 'agenda', href: '/agenda', glyph: 'ꗤ', labelKey: 'agendaTitle', mod: 'agendaMod' },
];
const renderHiveNav = () => {
if (process.env.OASIS_MOBILE !== '1') return "";
const cfg = getConfig();
const modOn = (m) => !m || cfg.modules[m] === 'on' || cfg.modules[m] === undefined;
const CATS = [
{ id: 'personal', emoji: '⚉', title: i18n.menuPersonal, mods: [
{ href: '/profile', glyph: '⚉', labelKey: 'profile', mod: null }, { href: '/cv', glyph: 'ꕛ', labelKey: 'cvTitle', mod: null },
{ href: '/agenda', glyph: 'ꗤ', labelKey: 'agendaTitle', mod: 'agendaMod' }, { href: '/favorites', glyph: 'ꘝ', labelKey: 'favoritesTitle', mod: 'favoritesMod' },
{ href: '/logs', glyph: 'ꗯ', labelKey: 'logsTitle', mod: 'logsMod' }, { href: '/wallet', glyph: '❄', labelKey: 'wallet', mod: 'walletMod' },
{ href: '/modules', glyph: 'ꗣ', labelKey: 'modules', mod: null }, { href: '/settings', glyph: '⚙', labelKey: 'settings', mod: null } ] },
{ id: 'governance', emoji: '⚖', title: i18n.menuGovernance, mods: [
{ href: '/inhabitants', glyph: 'ꖘ', labelKey: 'inhabitantsLabel', mod: null }, { href: '/tribes', glyph: 'ꖥ', labelKey: 'tribesTitle', mod: 'tribesMod' },
{ href: '/larp', glyph: '✦', labelKey: 'larpTitle', mod: 'larpMod' }, { href: '/parliament', glyph: 'ꗞ', labelKey: 'parliamentTitle', mod: 'parliamentMod' },
{ href: '/courts', glyph: 'ꖻ', labelKey: 'courtsTitle', mod: 'courtsMod' } ] },
{ id: 'office', emoji: '⌂', title: i18n.menuOffice, mods: [
{ href: '/votes', glyph: 'ꔰ', labelKey: 'votationsTitle', mod: 'votesMod' }, { href: '/events', glyph: 'ꕆ', labelKey: 'eventsLabel', mod: 'eventsMod' },
{ href: '/calendars', glyph: 'ꖯ', labelKey: 'calendarsTitle', mod: 'calendarsMod' }, { href: '/tasks', glyph: 'ꖧ', labelKey: 'tasksTitle', mod: 'tasksMod' },
{ href: '/reports', glyph: 'ꕥ', labelKey: 'reportsTitle', mod: 'reportsMod' } ] },
{ id: 'economy', emoji: '¤', title: i18n.menuEconomy, mods: [
{ href: '/banking', glyph: 'ꗴ', labelKey: 'bankingTitle', mod: 'bankingMod' }, { href: '/market', glyph: 'ꕻ', labelKey: 'marketTitle', mod: 'marketMod' },
{ href: '/projects', glyph: 'ꕧ', labelKey: 'projectsTitle', mod: 'projectsMod' }, { href: '/jobs', glyph: 'ꗒ', labelKey: 'jobsTitle', mod: 'jobsMod' },
{ href: '/shops', glyph: 'ꔜ', labelKey: 'shopsTitle', mod: 'shopsMod' }, { href: '/transfers', glyph: 'ꘉ', labelKey: 'transfersTitle', mod: 'transfersMod' } ] },
{ id: 'tools', emoji: '⚒', title: i18n.menuTools, mods: [
{ href: '/ai', glyph: 'ꘜ', labelKey: 'ai', mod: 'aiMod' }, { href: '/blockexplorer', glyph: 'ꖸ', labelKey: 'blockchain', mod: null },
{ href: '/cipher', glyph: 'ꗄ', labelKey: 'cipher', mod: 'cipherMod' }, { href: '/invites', glyph: 'ꔹ', labelKey: 'invites', mod: 'invitesMod' },
{ href: '/legacy', glyph: 'ꖤ', labelKey: 'legacy', mod: 'legacyMod' }, { href: '/stats', glyph: 'ꕷ', labelKey: 'statistics', mod: null } ] },
{ id: 'network', emoji: '☍', title: i18n.menuNetwork, mods: [
{ href: '/activity', glyph: 'ꔙ', labelKey: 'activityTitle', mod: null }, { href: '/tags', glyph: 'ꖶ', labelKey: 'tagsLabel', mod: 'tagsMod' },
{ href: '/trending', glyph: 'ꗝ', labelKey: 'trendingLabel', mod: 'trendingMod' }, { href: '/opinions', glyph: 'ꔍ', labelKey: 'opinionsTitle', mod: 'opinionsMod' },
{ href: '/pads', glyph: 'ꔗ', labelKey: 'padsTitle', mod: 'padsMod' }, { href: '/forum', glyph: 'ꕒ', labelKey: 'forumTitle', mod: 'forumMod' },
{ href: '/maps', glyph: 'ꔌ', labelKey: 'mapsLabel', mod: 'mapsMod' }, { href: '/chats', glyph: 'ꖒ', labelKey: 'chatsTitle', mod: 'chatsMod' } ] },
{ id: 'blogs', emoji: '✦', title: i18n.menuBlogs, mods: [
{ href: '/mentions', glyph: '✺', labelKey: 'mentions', mod: null }, { href: '/public/latest', glyph: '☄', labelKey: 'latest', mod: 'latestMod' },
{ href: '/public/latest/threads', glyph: '♺', labelKey: 'threads', mod: 'threadsMod' }, { href: '/public/latest/topics', glyph: 'ϟ', labelKey: 'topics', mod: 'topicsMod' },
{ href: '/public/latest/summaries', glyph: '※', labelKey: 'summaries', mod: 'summariesMod' }, { href: '/public/popular/day', glyph: '⌘', labelKey: 'popular', mod: 'popularMod' },
{ href: '/public/latest/extended', glyph: '∞', labelKey: 'multiverse', mod: 'multiverseMod' } ] },
{ id: 'creative', emoji: '✎', title: i18n.menuCreative, mods: [
{ href: '/feed', glyph: 'ꕿ', labelKey: 'feedTitle', mod: 'feedMod' }, { href: '/games', glyph: 'ꕇ', labelKey: 'gamesTitle', mod: 'gamesMod' },
{ href: '/pixelia', glyph: 'ꔘ', labelKey: 'pixeliaTitle', mod: 'pixeliaMod' }, { href: '/melody', glyph: '♪', labelKey: 'melodyTitle', mod: 'melodyMod' } ] },
{ id: 'media', emoji: '▤', title: i18n.menuMedia, mods: [
{ href: '/audios', glyph: 'ꔿ', labelKey: 'audiosLabel', mod: 'audiosMod' }, { href: '/bookmarks', glyph: 'ꔪ', labelKey: 'bookmarksLabel', mod: 'bookmarksMod' },
{ href: '/documents', glyph: 'ꕨ', labelKey: 'docsLabel', mod: 'docsMod' }, { href: '/images', glyph: 'ꕥ', labelKey: 'imagesLabel', mod: 'imagesMod' },
{ href: '/torrents', glyph: 'ꖅ', labelKey: 'torrentsLabel', mod: 'torrentsMod' }, { href: '/videos', glyph: 'ꗟ', labelKey: 'videosLabel', mod: 'videosMod' } ] },
{ id: 'fediverse', emoji: '⌬', title: i18n.fediverse, mods: [
{ href: '/fediverse', glyph: 'ꗵ', labelKey: 'fediverseTimeline', mod: 'fediverseMod' } ] },
];
const catHex = (c) => label({ for: `hive-${c.id}`, class: 'hex-cat' },
span({ class: 'hx-ico' }, c.emoji), span({ class: 'hx-lbl' }, c.title));
const modHex = (m) => a({ class: 'hex-mod', href: m.href },
span({ class: 'hx-ico' }, m.glyph), span({ class: 'hx-lbl' }, i18n[m.labelKey] || ''));
const radios = CATS.map(c => input({ type: 'radio', name: 'hive-cat', id: `hive-${c.id}`, class: 'hive-radio' }));
const catRows = [];
let idx = 0;
for (const size of [3, 4, 3]) { catRows.push(div({ class: 'hive-row' }, ...CATS.slice(idx, idx + size).map(catHex))); idx += size; }
const grid = div({ class: 'hive-grid hive-cats' }, ...catRows);
const panels = CATS.map(c => div({ class: 'hive-panel', id: `hive-panel-${c.id}` },
div({ class: 'hive-mods' }, ...c.mods.filter(m => modOn(m.mod)).map(modHex))
));
return div({ class: 'hive-nav' }, ...radios, grid, ...panels);
};
exports.renderHiveNav = renderHiveNav;
const template = (titlePrefix, ...elements) => {
const currentConfig = getConfig();
const theme = currentConfig.themes.current || "Dark-SNH";
@ -1116,6 +1310,7 @@ const template = (titlePrefix, ...elements) => {
link({ rel: "stylesheet", href: "/assets/styles/style.css" }),
themeLink,
link({ rel: "stylesheet", href: "/assets/styles/mobile.css", ...(process.env.OASIS_MOBILE === '1' ? {} : { media: "(max-width: 768px)" }) }),
link({ rel: "stylesheet", href: "/assets/themes/OasisMobileUX.css", ...(process.env.OASIS_MOBILE === '1' ? {} : { media: "(max-width: 768px)" }) }),
link({ rel: "icon", href: "/assets/images/favicon.svg" }),
meta({ charset: "utf-8" }),
meta({ name: "description", content: i18n.oasisDescription }),
@ -1439,6 +1634,7 @@ exports.ainavHomeView = ({ recentTags = [] } = {}) => {
link({ rel: "stylesheet", href: "/assets/styles/style.css" }),
link({ rel: "stylesheet", href: `/assets/themes/${theme}.css` }),
link({ rel: "stylesheet", href: "/assets/styles/mobile.css", ...(process.env.OASIS_MOBILE === '1' ? {} : { media: "(max-width: 768px)" }) }),
link({ rel: "stylesheet", href: "/assets/themes/OasisMobileUX.css", ...(process.env.OASIS_MOBILE === '1' ? {} : { media: "(max-width: 768px)" }) }),
link({ rel: "icon", href: "/assets/images/favicon.svg" }),
meta({ charset: "utf-8" }),
meta({ name: "description", content: i18n.oasisDescription }),
@ -3402,7 +3598,7 @@ exports.privateView = async (messagesInput, filter, decrypted = null) => {
threadGroups[tid].push(msg)
}
if (!threadOrder.length) return p({ class: 'empty' }, i18n.noPrivateMessages)
if (!threadOrder.length) return emptyState({ icon: "✉", title: i18n.noPrivateMessages, text: i18n.emptyConnectHint, ctaHref: "/invites", ctaLabel: i18n.emptyConnectCta })
return threadOrder.map(tid => {
const msgs = threadGroups[tid]

View file

@ -1,5 +1,5 @@
const { form, button, div, h2, p, section, input, label, select, option, img, audio: audioHyperaxe, video: videoHyperaxe, table, hr, hd, br, td, tr, th, a, span } = require("../server/node_modules/hyperaxe");
const { template, i18n, userLink, renderContentActions } = require('./main_views');
const { template, i18n, userLink, renderContentActions, emptyState } = require('./main_views');
const moment = require("../server/node_modules/moment");
const { renderTextWithStyles } = require('../backend/renderTextWithStyles');
const { renderUrl } = require('../backend/renderUrl');
@ -601,7 +601,7 @@ const searchView = ({ messages = [], blobs = {}, query = "", type = "", types =
})
)
)
: div({ class: 'no-results' }, p(i18n.noResultsFound));
: emptyState({ icon: "🔍", title: i18n.noResultsFound });
let html = template(
hashtag ? `#${hashtag}` : i18n.searchTitle,