diff --git a/src/client/assets/themes/OasisMobile.css b/src/client/assets/themes/OasisMobile.css index 4e93351..a3c03d2 100644 --- a/src/client/assets/themes/OasisMobile.css +++ b/src/client/assets/themes/OasisMobile.css @@ -42,7 +42,7 @@ body { overflow-x: clip; overflow-y: visible; } body { padding-bottom: calc(64px + env(safe-area-inset-bottom, 0px)); } -/* topbar FIJA arriba: [logo] [Personal] [Community] [avatar]; el hive scrollea por debajo */ +/* topbar FIJA arriba: [logo] [Personal] [Community] [avatar]; el lanzador scrollea por debajo */ .oasis-mobile-topbar { position: fixed; top: 0; left: 0; right: 0; z-index: 100; display: flex; align-items: center; gap: 8px; @@ -80,8 +80,8 @@ body { padding-top: 84px; background: #121212; } /* ANULAR el "card" global de style.css `div{background:#222;border-radius:10px;box-shadow;padding:20px;margin-bottom:20px}` en nuestro chrome móvil propio: era el 2º color (caja mas clara) detras de los botones, - la caja redondeada de la topbar y el fondo #222 detras de los hexagonos. */ -.header, .hive-nav, .hive-grid, .hive-cats, .hive-row, + la caja redondeada de la topbar y el fondo #222 detras del lanzador. */ +.header, .launcher, .ln-group, .ln-grid, .oasis-mobile-topbar, .oasis-mobile-topbar .omt-menus { background-color: transparent; box-shadow: none; border-radius: 0; } @@ -209,7 +209,7 @@ body { overscroll-behavior: none; } .main-content, .main-column, .header { overflow: visible; } -/* === Usabilidad 0.9.1: Personal/Community filtran el hive server-side (enlaces ?hive=) + desplegable FILTER === */ +/* === Usabilidad 0.9.1: Personal/Community filtran el lanzador server-side (enlaces ?hive=) + desplegable FILTER === */ /* desplegable FILTER de la ristra de filtros de Activity (oculto por defecto) */ .activity-filter-toggle { position: absolute; opacity: 0; width: 1px; height: 1px; pointer-events: none; margin: 0; } @@ -252,77 +252,36 @@ body { overscroll-behavior: none; } margin-top: 16px; min-height: 48px; line-height: 48px; } -.hive-nav { --hx: min(64px, calc((100vw - 40px) / 4)); --hxm: min(58px, calc((100vw - 36px) / 5)); margin: 8px 0 16px; overflow: visible; background: transparent; padding: 0; border: 0; box-shadow: none; } -.hive-radio { position: absolute; opacity: 0; width: 0; height: 0; pointer-events: none; } -.hive-grid { display: flex; flex-direction: column; align-items: center; overflow: visible; background: transparent; padding: 0; margin: 0; box-shadow: none; } -.hive-row { display: flex; justify-content: center; gap: 3px; overflow: visible; background: transparent; padding: 0; margin: 0; box-shadow: none; } -.hive-cats .hive-row + .hive-row { margin-top: calc(var(--hx) * -0.30); } -.hex-cat, .hex-mod { - position: relative; - -webkit-clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%); - clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%); - display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px; - text-decoration: none; cursor: pointer; box-sizing: border-box; - background: #FFD700; - filter: drop-shadow(0 1px 2px rgba(0,0,0,.6)); +/* Lanzador de modulos de la home (sustituye a los hexagonos): secciones con un + rotulo discreto y una rejilla de baldosas, como el cajon de apps del movil */ +.launcher { margin: 6px 0 20px; padding: 0; border: 0; } +.ln-group { margin: 0; padding: 0; border: 0; } +.ln-group + .ln-group { margin-top: 20px; } +.ln-title { + margin: 0 2px 10px; padding: 0; border: 0; + font-size: 11px; font-weight: 600; letter-spacing: .08em; text-transform: uppercase; + color: var(--c-muted); } -.hex-cat::before, .hex-mod::before { - content: ''; position: absolute; inset: 1.5px; z-index: 0; - -webkit-clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%); - clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%); - background: #161616; +.ln-grid { + display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 14px 8px; margin: 0; padding: 0; +} +.ln-app { + display: flex; flex-direction: column; align-items: center; gap: 6px; + min-width: 0; text-decoration: none; -webkit-tap-highlight-color: transparent; +} +.ln-ico { + width: 54px; height: 54px; box-sizing: border-box; + display: flex; align-items: center; justify-content: center; + border-radius: 16px; background: #1c1c1c; border: 1px solid #333; + color: #FFD700; font-size: 22px; line-height: 1; + transition: background-color .12s, border-color .12s; +} +.ln-app:active .ln-ico, .ln-app:focus-visible .ln-ico { background: #2a2412; border-color: #FFD700; } +.ln-lbl { + max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; + font-size: 11px; line-height: 1.2; text-align: center; color: #e6e6e6; } -.hex-cat { width: var(--hx); height: calc(var(--hx) * 1.15); } -.hex-mod { width: var(--hxm); height: calc(var(--hxm) * 1.15); } -.hex-cat .hx-ico, .hex-mod .hx-ico { color: #FFD700; line-height: 1; position: relative; z-index: 1; } -.hex-cat .hx-ico { font-size: 17px; } -.hex-mod .hx-ico { font-size: 16px; } -.hex-cat .hx-lbl, .hex-mod .hx-lbl { color: #FFD700; text-align: center; line-height: 1; font-weight: bold; overflow: hidden; position: relative; z-index: 1; } -.hex-cat .hx-lbl { font-size: 8px; max-width: 88%; } -.hex-mod .hx-lbl { font-size: 8px; max-width: 92%; } -.hex-cat:active::before, .hex-mod:active::before { background: #242424; } -#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: #FFD700; } -#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: #FFD700; } -#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; } -.hive-panel { display: none; margin: 12px auto 0; max-width: 340px; background: #101010; border: 1px solid #242424; border-radius: 16px; padding: 12px 8px; overflow: visible; box-shadow: none; } -#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; } -.hive-mods { display: flex; flex-wrap: wrap; justify-content: center; gap: 4px; margin: 0; padding: 0; background: transparent; overflow: visible; } .header .top-bar-left nav, .header .top-bar-right nav { display: none; } @@ -909,33 +868,6 @@ button, input[type="submit"], input[type="button"], padding: 8px 12px; font-size: 13px; margin: 0 0 6px; } -/* --- #3: al desplegar una categoría, sus módulos son BOTONES CUADRADOS redondeados (no hexágonos), - en rejilla 2×2 (2 y 2) y grandes: cada cuadrado llena su columna --- */ -.hive-mods { - display: grid; grid-template-columns: repeat(2, 1fr); - justify-items: stretch; align-content: center; - gap: 12px; padding: 8px 4px; -} -.hex-mod { - clip-path: none; -webkit-clip-path: none; - width: 100%; height: auto; aspect-ratio: 1 / 1; min-height: 120px; - border-radius: 18px; padding: 8px; gap: 7px; -} -.hex-mod::before { clip-path: none; -webkit-clip-path: none; inset: 2px; border-radius: 16px; } -.hex-mod .hx-ico { font-size: 36px; } -.hex-mod .hx-lbl { font-size: 12.5px; max-width: 94%; } -/* si el nº de módulos es IMPAR, el último (solo en su fila) se CENTRA en vez de pegarse a la izquierda; - abarca las 2 columnas pero conserva el ancho de una sola (gap 12 → mitad 6) */ -.hex-mod:last-child:nth-child(odd) { - grid-column: 1 / -1; justify-self: center; width: calc(50% - 6px); -} - -/* --- #2: el hexágono "Personal" muestra tu avatar (diferencia del filtro "Personal" del header) --- */ -.hex-cat .hx-avatar { - width: 58%; height: 58%; border-radius: 50%; object-fit: cover; - position: relative; z-index: 1; border: 1px solid rgba(255,215,0,.6); -} - /* --- #1: chat estilo Signal — compose ABAJO + mensajes arriba (el reply deja de "saltar") --- */ .tribe-main.chat-full-width { display: flex; flex-direction: column-reverse; } #chatbox { scroll-margin-top: 8px; scroll-margin-bottom: 8px; } @@ -1032,17 +964,17 @@ button, input[type="submit"], input[type="button"], ========================================================================== */ /* --- fuente única en todo el UI móvil (quitar el lío monospace/sans) --- */ -.oasis-mobile-topbar, .oasis-mobile-menus, .hive-nav, .oasis-bottombar, .main-column, +.oasis-mobile-topbar, .oasis-mobile-menus, .launcher, .oasis-bottombar, .main-column, .oasis-mobile-topbar input, .main-column input, .main-column textarea, .main-column select, button { font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; } /* (topbar sin barra de búsqueda: vuelve el título "OASIS" — CSS .omt-title más arriba) */ -/* --- hive ahora global en el header (debajo de los 2 botones) --- */ -.header .hive-nav { margin: 34px auto 10px; } +/* --- el lanzador va en el header (debajo de los 2 botones), solo en la home --- */ +.header .launcher { width: 100%; max-width: 440px; box-sizing: border-box; margin: 34px auto 10px; padding: 0 14px; } -/* holgura bajo la topbar fija (el hive va en el header, solo en la home) */ +/* holgura bajo la topbar fija */ body { padding-top: 84px; } /* ========================================================================== @@ -1113,3 +1045,22 @@ body { padding-top: 84px; } .kc-btn-start { font-weight: 600; } .kc-btn-stop { background: #241414; border-color: #5a2a2a; } .kc-status { font-size: 12px; color: #FFDD44; margin-top: 6px; min-height: 14px; } + +/* ========================================================================== + 1.3.x: upstream trae en mobile.css su propio chrome movil (cabecera fija, cajones + laterales y botones Personal/Community que los abren). La rama ya tiene el suyo + (topbar, lanzador y barra inferior), asi que aqui se apaga el de upstream sin + tocar mobile.css. Va con !important porque las reglas que anula lo llevan. + ========================================================================== */ +.oasis-nav-side-toggle ~ .header { + position: static !important; + display: flex !important; flex-direction: column !important; flex-wrap: nowrap !important; + align-items: stretch !important; + min-height: 0 !important; padding: 0 !important; gap: 0 !important; + z-index: auto !important; +} +body:has(.oasis-nav-side-toggle) { padding-top: 84px !important; } +.oasis-nav-side-toggle ~ .header .oasis-mobile-menus, +.oasis-nav-side-toggle ~ .header .top-bar-left nav, +.oasis-nav-side-toggle ~ .header .top-bar-right nav, +.oasis-nav-side-toggle ~ .header .top-bar-left a.logo-icon { display: none !important; } diff --git a/src/views/hive_views.js b/src/views/hive_views.js index ea30059..54a5bf7 100644 --- a/src/views/hive_views.js +++ b/src/views/hive_views.js @@ -1,6 +1,6 @@ "use strict"; // Navegacion propia de la rama experimental para movil: topbar, barra inferior -// personalizable y rejilla de hexagonos. +// personalizable y lanzador de modulos. // // Vive fuera de main_views.js a proposito: upstream reescribe ese fichero en cada // release (de 0.9.1 a 0.9.5 movio +256/-502 lineas) y esta interfaz se perdia en @@ -12,7 +12,7 @@ const { getConfig } = require("../configs/config-manager.js"); const sharedState = require("../configs/shared-state"); module.exports = ({ i18n }) => { - // === Oasis UX (portado de 0.8.7_UX): topbar + bottombar + hive === + // === Oasis UX (portado de 0.8.7_UX): topbar + bottombar + lanzador === // topbar fija arriba: [logo] [Personal] [Community] [avatar] en una línea (sin título OASIS) const renderMobileTopbar = () => { // solo en movil: en escritorio manda la cabecera de upstream @@ -39,6 +39,8 @@ module.exports = ({ i18n }) => { { key: 'tribes', href: '/tribes', glyph: 'ꖥ', labelKey: 'tribesTitle', mod: 'tribesMod' }, { key: 'chats', href: '/chats', glyph: 'ꖒ', labelKey: 'chatsTitle', mod: 'chatsMod' }, { key: 'karvan', href: '/karvan', glyph: '⧗', labelKey: 'karvanTitle', mod: 'karvanMod' }, + { key: 'rooms', href: '/rooms', glyph: 'ꕢ', labelKey: 'roomsTitle', mod: 'roomsMod' }, + { key: 'wiki', href: '/wiki', glyph: 'ꖿ', labelKey: 'wikiTitle', mod: 'wikiMod' }, { key: 'polls', href: '/polls', glyph: 'ꖙ', labelKey: 'pollsTitle', mod: 'pollsMod' }, { key: 'blogs', href: '/blogs', glyph: 'ꔫ', labelKey: 'blogTitle', mod: 'blogsMod' }, { key: 'data', href: '/data', glyph: 'ꘑ', labelKey: 'dataTitle', mod: 'dataMod' }, @@ -119,71 +121,104 @@ module.exports = ({ i18n }) => { ); }; + // Lanzador de modulos de la home: secciones con una rejilla de iconos, como el + // cajon de apps del movil. Solo salen los modulos activos y las secciones que + // tienen alguno, asi que con un workflow corto (activistas) cabe en una pantalla. + const GROUPS = [ + { id: 'community', titleKey: 'menuNetwork', side: 'community', mods: [ + { href: '/activity', glyph: 'ꔙ', labelKey: 'activityTitle', mod: null }, + { href: '/feed', glyph: 'ꕿ', labelKey: 'feedTitle', mod: 'feedMod' }, + { href: '/chats', glyph: 'ꖒ', labelKey: 'chatsTitle', mod: 'chatsMod' }, + { href: '/karvan', glyph: '⧗', labelKey: 'karvanTitle', mod: 'karvanMod' }, + { href: '/rooms', glyph: 'ꕢ', labelKey: 'roomsTitle', mod: 'roomsMod' }, + { href: '/forum', glyph: 'ꕒ', labelKey: 'forumTitle', mod: 'forumMod' }, + { href: '/pads', glyph: 'ꔗ', labelKey: 'padsTitle', mod: 'padsMod' }, + { href: '/wiki', glyph: 'ꖿ', labelKey: 'wikiTitle', mod: 'wikiMod' }, + { href: '/blogs', glyph: 'ꔫ', labelKey: 'blogTitle', mod: 'blogsMod' }, + { href: '/opinions', glyph: 'ꔍ', labelKey: 'opinionsTitle', mod: 'opinionsMod' }, + { href: '/mentions', glyph: '✺', labelKey: 'mentions', mod: null }, + { href: '/tags', glyph: 'ꖶ', labelKey: 'tagsLabel', mod: 'tagsMod' }, + { href: '/trending', glyph: 'ꗝ', labelKey: 'trendingLabel', mod: 'trendingMod' }, + { href: '/maps', glyph: 'ꔌ', labelKey: 'mapsLabel', mod: 'mapsMod' }, + { href: '/graphos', glyph: '⌬', labelKey: 'modulesGraphosLabel', mod: 'graphosMod' }, + { href: '/inhabitants', glyph: 'ꖘ', labelKey: 'inhabitantsLabel', mod: null }, + { href: '/fediverse', glyph: 'ꗵ', labelKey: 'fediverseTimeline', mod: 'fediverseMod' } ] }, + { id: 'media', titleKey: 'menuMedia', side: 'community', mods: [ + { href: '/images', glyph: 'ꕥ', labelKey: 'imagesLabel', mod: 'imagesMod' }, + { href: '/videos', glyph: 'ꗟ', labelKey: 'videosLabel', mod: 'videosMod' }, + { href: '/audios', glyph: 'ꔿ', labelKey: 'audiosLabel', mod: 'audiosMod' }, + { href: '/podcasts', glyph: '◉', labelKey: 'podcastsTitle', mod: 'podcastsMod' }, + { href: '/documents', glyph: 'ꕨ', labelKey: 'docsLabel', mod: 'docsMod' }, + { href: '/files', glyph: 'ꖃ', labelKey: 'filesTitle', mod: 'filesMod' }, + { href: '/bookmarks', glyph: 'ꔪ', labelKey: 'bookmarksLabel', mod: 'bookmarksMod' }, + { href: '/torrents', glyph: 'ꖅ', labelKey: 'torrentsLabel', mod: 'torrentsMod' } ] }, + { id: 'creative', titleKey: 'menuCreative', side: 'community', mods: [ + { href: '/games', glyph: 'ꕇ', labelKey: 'gamesTitle', mod: 'gamesMod' }, + { href: '/pixelia', glyph: 'ꔘ', labelKey: 'pixeliaTitle', mod: 'pixeliaMod' }, + { href: '/melody', glyph: '♪', labelKey: 'melodyTitle', mod: 'melodyMod' } ] }, + { id: 'office', titleKey: 'menuOffice', side: 'personal', mods: [ + { href: '/events', glyph: 'ꕆ', labelKey: 'eventsLabel', mod: 'eventsMod' }, + { href: '/calendars', glyph: 'ꖯ', labelKey: 'calendarsTitle', mod: 'calendarsMod' }, + { href: '/tasks', glyph: 'ꖧ', labelKey: 'tasksTitle', mod: 'tasksMod' }, + { href: '/campaigns', glyph: '⚑', labelKey: 'campaignsTitle', mod: 'campaignsMod' }, + { href: '/mailing', glyph: '✉', labelKey: 'mailingTitle', mod: 'mailingMod' }, + { href: '/votes', glyph: 'ꔰ', labelKey: 'votationsTitle', mod: 'votesMod' }, + { href: '/polls', glyph: 'ꖙ', labelKey: 'pollsTitle', mod: 'pollsMod' }, + { href: '/reports', glyph: 'ꕤ', labelKey: 'reportsTitle', mod: 'reportsMod' } ] }, + { id: 'governance', titleKey: 'menuGovernance', side: 'personal', mods: [ + { href: '/tribes', glyph: 'ꖥ', labelKey: 'tribesTitle', mod: 'tribesMod' }, + { href: '/emergencies', glyph: '✚', labelKey: 'emergenciesTitle', mod: 'emergenciesMod' }, + { href: '/school', glyph: 'ꗳ', labelKey: 'schoolTitle', mod: 'schoolMod' }, + { href: '/larp', glyph: '✦', labelKey: 'larpTitle', mod: 'larpMod' }, + { href: '/parliament', glyph: 'ꗞ', labelKey: 'parliamentTitle', mod: 'parliamentMod' }, + { href: '/courts', glyph: 'ꖻ', labelKey: 'courtsTitle', mod: 'courtsMod' } ] }, + { id: 'economy', titleKey: 'menuEconomy', side: 'personal', mods: [ + { href: '/market', glyph: 'ꕻ', labelKey: 'marketTitle', mod: 'marketMod' }, + { href: '/shops', glyph: 'ꔜ', labelKey: 'shopsTitle', mod: 'shopsMod' }, + { href: '/jobs', glyph: 'ꗒ', labelKey: 'jobsTitle', mod: 'jobsMod' }, + { href: '/projects', glyph: 'ꕧ', labelKey: 'projectsTitle', mod: 'projectsMod' }, + { href: '/housing', glyph: '⌂', labelKey: 'housingTitle', mod: 'housingMod' }, + { href: '/logistics', glyph: '⇄', labelKey: 'logisticsTitle', mod: 'logisticsMod' }, + { href: '/banking', glyph: 'ꗴ', labelKey: 'bankingTitle', mod: 'bankingMod' }, + { href: '/transfers', glyph: 'ꘉ', labelKey: 'transfersTitle', mod: 'transfersMod' } ] }, + { id: 'personal', titleKey: 'menuPersonal', side: 'personal', mods: [ + { href: '/profile', glyph: '⚉', labelKey: 'profile', mod: null }, + { href: '/agenda', glyph: 'ꗤ', labelKey: 'agendaTitle', mod: 'agendaMod' }, + { href: '/favorites', glyph: 'ꘝ', labelKey: 'favoritesTitle', mod: 'favoritesMod' }, + { href: '/phone', glyph: '☏', labelKey: 'phoneTitle', mod: 'phoneMod' }, + { href: '/cv', glyph: 'ꕛ', labelKey: 'cvTitle', mod: null }, + { href: '/wallet', glyph: '❄', labelKey: 'wallet', mod: 'walletMod' }, + { href: '/logs', glyph: 'ꗯ', labelKey: 'logsTitle', mod: 'logsMod' } ] }, + { id: 'tools', titleKey: 'menuTools', side: 'personal', mods: [ + { href: '/invites', glyph: 'ꔹ', labelKey: 'invites', mod: 'invitesMod' }, + { href: '/backup', glyph: 'ꖤ', labelKey: 'backupTitle', mod: 'backupMod' }, + { href: '/cipher', glyph: 'ꗄ', labelKey: 'cipher', mod: 'cipherMod' }, + { href: '/ai', glyph: 'ꘜ', labelKey: 'ai', mod: 'aiMod' }, + { href: '/stats', glyph: 'ꕷ', labelKey: 'statistics', mod: null }, + { href: '/blockexplorer', glyph: 'ꖸ', labelKey: 'blockchain', mod: null }, + { href: '/modules', glyph: 'ꗣ', labelKey: 'modules', mod: null }, + { href: '/settings', glyph: '⚙', labelKey: 'settings', mod: null } ] }, + ]; + 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: '/polls', glyph: 'ꖙ', labelKey: 'pollsTitle', mod: 'pollsMod' }, { 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 }, - { href: '/data', glyph: 'ꘑ', labelKey: 'dataTitle', mod: 'dataMod' } ] }, - { 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' }, - { href: '/karvan', glyph: '⧗', labelKey: 'karvanTitle', mod: 'karvanMod' } ] }, - { id: 'blogs', emoji: '✦', title: i18n.menuBlogs, mods: [ - { href: '/blogs', glyph: 'ꔫ', labelKey: 'blogTitle', mod: 'blogsMod' }, { 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' } ] }, - ]; - // filtro server-side (lo pone el middleware desde ?hive=): personal = CATS[0..4], community = CATS[5..9], vacío = las 10 + // Personal/Social de la topbar (lo pone el middleware desde ?hive=) parte el lanzador en dos const hiveFilter = (typeof global !== "undefined" && global.__OASIS_HIVE_FILTER__) || ""; - const shown = hiveFilter === "personal" ? CATS.slice(0, 5) : hiveFilter === "community" ? CATS.slice(5) : CATS; - const rowSizes = shown.length <= 5 ? [3, 2] : [3, 4, 3]; - 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 = shown.map(c => input({ type: 'radio', name: 'hive-cat', id: `hive-${c.id}`, class: 'hive-radio' })); - const catRows = []; - let idx = 0; - for (const size of rowSizes) { catRows.push(div({ class: 'hive-row' }, ...shown.slice(idx, idx + size).map(catHex))); idx += size; } - const grid = div({ class: 'hive-grid hive-cats' }, ...catRows); - const panels = shown.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); + const app = (m) => a({ class: 'ln-app', href: m.href }, + span({ class: 'ln-ico', 'aria-hidden': 'true' }, m.glyph), + span({ class: 'ln-lbl' }, i18n[m.labelKey] || m.href.slice(1))); + const groups = GROUPS + .filter(g => !hiveFilter || g.side === hiveFilter) + .map(g => ({ g, mods: g.mods.filter(m => modOn(m.mod)) })) + .filter(x => x.mods.length); + return nav({ class: 'launcher', 'aria-label': i18n.modules || 'Modules' }, + ...groups.map(({ g, mods }) => div({ class: 'ln-group' }, + h3({ class: 'ln-title' }, i18n[g.titleKey] || g.id), + div({ class: 'ln-grid' }, ...mods.map(app)) + )) + ); };