diff --git a/GUIA_DE_DESARROLLO.md b/GUIA_DE_DESARROLLO.md index 132b8ad..392cc6c 100644 --- a/GUIA_DE_DESARROLLO.md +++ b/GUIA_DE_DESARROLLO.md @@ -44,8 +44,9 @@ Dónde va cada cosa: | Quiero... | Va en | |---|---| -| Cambiar colores, tamaños, márgenes | `styles/mobile_ux.css` (los dos skins) o `hive_acampada.css` (solo Acampada) | -| Cambiar la paleta de Acampada | Variables `--a26-*` de `themes/Acampada.css`; las elegibles, en `mobile_settings.js` | +| Cambiar colores, tamaños, márgenes | `styles/mobile_ux.css` (piezas) o `styles/activista.css` (lanzador y barras) | +| Cambiar los colores de serie o las paletas | Variables `--a26-*` de `themes/Activista.css`; las paletas, en `mobile_settings.js` | +| La barra de unirse a un pub | `views/mobile_pubbar.js` y `public/js/mobile_pub.js` | | Otro HTML en una pantalla del dev | Función en `views/mobile_ui.js` + una línea en su vista | | Una pantalla nueva | Vista propia en `views/mobile_*.js` + ruta en `fork_routes.js` | | Algo en Ajustes del móvil | `views/mobile_settings.js` | @@ -144,13 +145,20 @@ Aquí se puede hacer lo que haga falta. tarjetas, formularios). Se carga siempre en el móvil, antes del skin. - `src/client/assets/styles/mobile_ux.css`: márgenes, letra, botones y todas las piezas nuevas. Vale para los dos skins de móvil. -- `src/client/assets/themes/Acampada.css` y `styles/hive_acampada.css`: el skin - Acampada. +- `src/client/assets/themes/Activista.css` y `styles/activista.css`: el skin + Activista, el único del móvil (Acampada y el OasisMobile del dev se quitaron del + selector el 20/10/2026). Lo más simple posible: letra del sistema, bordes finos y + sin sombras. El skin quita lo que sobra; los colores los pone cada cual. +- `src/views/mobile_pubbar.js` y `public/js/mobile_pub.js`: la barra fija de unirse a + un pub (y de restaurar una copia), encima de la barra de abajo. Se pone al día sin + recargar, preguntando a `/mobile/pub-status`; el estado sale de + `backupModel.restoreStatus()`, que `backend.js` le pasa a `fork_routes.js` en la + línea que lo monta. - `src/views/mobile_ui.js`: lo que se enchufa en vistas del dev. - `src/views/mobile_settings.js`: los seis accesos de arriba de Ajustes (identidad, perfil, portada, colores, comunidad y saludo) y los colores. Los colores se guardan en la config (`mobileColors`) y salen en `/mobile-colors.css`, la última hoja; son - las variables `--a26-*` de Acampada, así que solo valen con ese skin. + las variables `--a26-*` del skin Activista. - `src/views/mobile_modules_view.js`, `identities_view.js`: pantallas propias. - `src/views/hive_views.js`: lanzador y barras de arriba y abajo. - `src/views/mobile_icons.js`: los iconos. @@ -172,10 +180,10 @@ Aquí se puede hacer lo que haga falta. Cambiar cómo se ve algo. Se busca la clase en el HTML (servidor local e inspector) y se escribe la regla en `mobile_ux.css`. Para los colores usad las variables `--ux-*` (`--ux-bg`, `--ux-fg`, `--ux-accent`, `--ux-muted`, `--ux-line`, `--ux-danger`), que -tiran de Acampada si está y si no de la paleta de OasisMobile. Si la regla no gana, -es que `mobile.css` o `Acampada.css` mandan con `!important` y selectores largos: +tiran de las `--a26-*` del skin. Si la regla no gana, +es que `mobile.css` o `Activista.css` mandan con `!important` y selectores largos: hay que subir la especificidad y poner `!important` también. Ojo con una trampa: -`Acampada.css` quita el borde a todos los `div`, así que para ponérselo a uno hace +`Activista.css` quita fondo, borde y sombra a todos los `div`, así que para ponérselo a uno hace falta `.main-column div.la-clase`. Huecos de las barras del sistema: nada de `env(safe-area-inset-*)`. El contenedor de @@ -305,7 +313,7 @@ líneas (20/10/2026, contra el `nodejs-project` de la APK 1.3.3). Por tamaño: - `backend/blobHandler.js` (190): limpiar los metadatos de las fotos también en Android. Es una función nuestra metida en su fichero; habría que proponérsela al dev. - `configs/oasis-config.json` (87) y `configs/config-manager.js` (18): la config de - serie del móvil (tema Acampada, workflow acampada, barra de abajo). + serie del móvil (skin Activista, perfil Activismo, barra de abajo). - `views/main_views.js` (64), `settings_view.js` (33), `welcome_view.js` (20), `tribes_view.js` (9), `vote_view.js` (7): enganches a `mobile_ui.js` y el orden de carga de las hojas. Es lo que la guía permite, pero hay que revisarlo en cada versión. diff --git a/docs/guia_pdf/guia.html b/docs/guia_pdf/guia.html index bfbce40..fd1d932 100644 --- a/docs/guia_pdf/guia.html +++ b/docs/guia_pdf/guia.html @@ -242,7 +242,7 @@ table.tight td { padding: 1.1mm 2mm; } 1 · style.css y mobile.css (tuyas) 2 · mobile_base.css: maqueta de la rama 3 · skin: Acampada.css o tu OasisMobile.css - 4 · hive_acampada.css (solo con Acampada) + 4 · activista.css (solo con Acampada) 5 · mobile_ux.css: piezas comunes, la última diff --git a/src/backend/backend.js b/src/backend/backend.js index 39ef244..335bd71 100644 --- a/src/backend/backend.js +++ b/src/backend/backend.js @@ -15576,7 +15576,7 @@ const middleware = [ await next(); }, // la rama movil: su router y su capa de backend (ver GUIA_DE_DESARROLLO.md) - require('./fork_routes')({ checkMod, getViewerId, getConfig, saveConfig, isLoopbackRequest, sendErrorPage, about }), + require('./fork_routes')({ checkMod, getViewerId, getConfig, saveConfig, isLoopbackRequest, sendErrorPage, about, backupModel }), routes, async (ctx) => { if (ctx.body != null || (ctx.method !== 'GET' && ctx.method !== 'HEAD')) return; diff --git a/src/backend/fork_routes.js b/src/backend/fork_routes.js index 208febb..af05afc 100644 --- a/src/backend/fork_routes.js +++ b/src/backend/fork_routes.js @@ -20,7 +20,9 @@ require("../models/fork_workflows").register(); const isMobile = () => process.env.OASIS_MOBILE === '1'; -module.exports = ({ checkMod, getViewerId, getConfig, saveConfig, isLoopbackRequest, sendErrorPage, about }) => { +module.exports = ({ checkMod, getViewerId, getConfig, saveConfig, isLoopbackRequest, sendErrorPage, about, backupModel }) => { +const pubBar = require('../views/mobile_pubbar'); +pubBar.setSource(backupModel); const mobileLayer = require('./mobile_layer')({ getViewerId, about }); const forkRouter = new koaRouter(); @@ -38,21 +40,29 @@ const mobileLayer = require('./mobile_layer')({ getViewerId, about }); ctx.cookies.set('theme', theme, { httpOnly: true, sameSite: 'strict', secure: ctx.secure }); ctx.redirect('/settings#theme'); }) - // colores del movil (mobile_settings.js): se guardan en la config y el aspecto pasa - // a Acampada, que es el tema que lee esas variables + // colores del movil (mobile_settings.js): se guardan en la config .post('/settings/colors', koaBody(), async (ctx, next) => { if (!isMobile()) return next(); const body = ctx.request.body || {}; const cfg = getConfig(); cfg.mobileColors = require('../views/mobile_settings').colorsFromBody(body); - if ((cfg.themes || {}).current !== 'Acampada') { - cfg.themes = { ...(cfg.themes || {}), current: 'Acampada' }; - ctx.cookies.set('theme', 'Acampada', { httpOnly: true, sameSite: 'strict', secure: ctx.secure }); - } saveConfig(cfg); const back = String(body.returnTo || ''); ctx.redirect(/^\/(settings|welcome)(#[a-z-]+)?$/.test(back) ? back : '/settings#colors'); }) + // barra de unirse a un pub (mobile_pubbar.js): el estado para el script, sin + // recargar la pagina, y cerrar el aviso de terminado + .get('/mobile/pub-status', async (ctx, next) => { + if (!isMobile()) return next(); + ctx.set('Cache-Control', 'no-store'); + ctx.body = pubBar.status() || { kind: 'none', running: false }; + }) + .post('/mobile/pub-status/dismiss', koaBody(), async (ctx, next) => { + if (!isMobile()) return next(); + pubBar.dismiss(); + const back = String(ctx.get('referer') || '').replace(/^https?:\/\/[^/]+/, ''); + ctx.redirect(/^\/[\w\-/?=&%.#]*$/.test(back) ? back : '/activity'); + }) .get('/mobile-colors.css', async (ctx, next) => { if (!isMobile()) return next(); ctx.type = 'text/css'; diff --git a/src/backend/mobile_layer.js b/src/backend/mobile_layer.js index e33da94..6b4aac7 100644 --- a/src/backend/mobile_layer.js +++ b/src/backend/mobile_layer.js @@ -31,8 +31,22 @@ module.exports = ({ getViewerId, about }) => { } catch (_) {} }; + // un solo skin en el movil desde el 20/10/2026: quien venga de Acampada u + // OasisMobile pasa a Activista (su config se conserva al actualizar la APK) + let skinChecked = false; + const checkSkin = () => { + if (skinChecked) return; + skinChecked = true; + try { + const { getConfig, saveConfig } = require('../configs/config-manager.js'); + const cfg = getConfig(); + if ((cfg.themes || {}).current !== 'Activista') { cfg.themes = { ...(cfg.themes || {}), current: 'Activista' }; saveConfig(cfg); } + } catch (_) {} + }; + return async (ctx, next) => { if (!isMobile() || !isPage(ctx.path)) return next(); + checkSkin(); global.__OASIS_HIVE_FILTER__ = (ctx.path === '/activity' && (ctx.query.hive === 'personal' || ctx.query.hive === 'community')) ? ctx.query.hive : ''; // el lanzador ya no va en la portada: la portada es la actividad y los modulos // estan en la hoja de la pestaña Menu (docs/ESTUDIO_NAVEGACION.md) diff --git a/src/client/assets/fonts/Anton-Regular.ttf b/src/client/assets/fonts/Anton-Regular.ttf deleted file mode 100644 index 4d65707..0000000 Binary files a/src/client/assets/fonts/Anton-Regular.ttf and /dev/null differ diff --git a/src/client/assets/fonts/OFL.txt b/src/client/assets/fonts/OFL.txt deleted file mode 100644 index 93fedde..0000000 --- a/src/client/assets/fonts/OFL.txt +++ /dev/null @@ -1,93 +0,0 @@ -Copyright 2020 The Anton Project Authors (https://github.com/googlefonts/AntonFont.git) - -This Font Software is licensed under the SIL Open Font License, Version 1.1. -This license is copied below, and is also available with a FAQ at: -http://scripts.sil.org/OFL - - ------------------------------------------------------------ -SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ------------------------------------------------------------ - -PREAMBLE -The goals of the Open Font License (OFL) are to stimulate worldwide -development of collaborative font projects, to support the font creation -efforts of academic and linguistic communities, and to provide a free and -open framework in which fonts may be shared and improved in partnership -with others. - -The OFL allows the licensed fonts to be used, studied, modified and -redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, -redistributed and/or sold with any software provided that any reserved -names are not used by derivative works. The fonts and derivatives, -however, cannot be released under any other type of license. The -requirement for fonts to remain under this license does not apply -to any document created using the fonts or their derivatives. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright -Holder(s) under this license and clearly marked as such. This may -include source files, build scripts and documentation. - -"Reserved Font Name" refers to any names specified as such after the -copyright statement(s). - -"Original Version" refers to the collection of Font Software components as -distributed by the Copyright Holder(s). - -"Modified Version" refers to any derivative made by adding to, deleting, -or substituting -- in part or in whole -- any of the components of the -Original Version, by changing formats or by porting the Font Software to a -new environment. - -"Author" refers to any designer, engineer, programmer, technical -writer or other person who contributed to the Font Software. - -PERMISSION & CONDITIONS -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Font Software, to use, study, copy, merge, embed, modify, -redistribute, and sell modified and unmodified copies of the Font -Software, subject to the following conditions: - -1) Neither the Font Software nor any of its individual components, -in Original or Modified Versions, may be sold by itself. - -2) Original or Modified Versions of the Font Software may be bundled, -redistributed and/or sold with any software, provided that each copy -contains the above copyright notice and this license. These can be -included either as stand-alone text files, human-readable headers or -in the appropriate machine-readable metadata fields within text or -binary files as long as those fields can be easily viewed by the user. - -3) No Modified Version of the Font Software may use the Reserved Font -Name(s) unless explicit written permission is granted by the corresponding -Copyright Holder. This restriction only applies to the primary font name as -presented to the users. - -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font -Software shall not be used to promote, endorse or advertise any -Modified Version, except to acknowledge the contribution(s) of the -Copyright Holder(s) and the Author(s) or with their explicit written -permission. - -5) The Font Software, modified or unmodified, in part or in whole, -must be distributed entirely under this license, and must not be -distributed under any other license. The requirement for fonts to -remain under this license does not apply to any document created -using the Font Software. - -TERMINATION -This license becomes null and void if any of the above conditions are -not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM -OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/src/client/assets/images/theme-acampada.png b/src/client/assets/images/theme-acampada.png deleted file mode 100644 index 2546f50..0000000 Binary files a/src/client/assets/images/theme-acampada.png and /dev/null differ diff --git a/src/client/assets/images/theme-oasismobile.png b/src/client/assets/images/theme-oasismobile.png deleted file mode 100644 index 668009e..0000000 Binary files a/src/client/assets/images/theme-oasismobile.png and /dev/null differ diff --git a/src/client/assets/styles/hive_acampada.css b/src/client/assets/styles/activista.css similarity index 91% rename from src/client/assets/styles/hive_acampada.css rename to src/client/assets/styles/activista.css index 05e415f..40e1151 100644 --- a/src/client/assets/styles/hive_acampada.css +++ b/src/client/assets/styles/activista.css @@ -1,9 +1,8 @@ -/* Lanzador, barras, tarjetas e identidades con el tema Acampada (rama movil). +/* Lanzador, barras, tarjetas e identidades del skin Activista (rama movil). * - * OasisMobile.css (cargado antes que themes/Acampada.css) esta pensado para fondo - * oscuro; esta hoja va detras del tema, solo con Acampada, y cambia lo que alli - * es amarillo sobre negro por negro sobre blanco con el cian neon de - * acampada26s.net. Las variables --a26-* las define themes/Acampada.css. */ + * Va detras de themes/Activista.css y pinta las piezas propias de la rama con las + * variables --a26-* (las de serie estan en el tema; Colores las cambia). Hasta el + * 20/10/2026 era hive_acampada.css, del skin Acampada. */ body { background: var(--a26-bg) !important; } @@ -18,7 +17,7 @@ body { background: var(--a26-bg) !important; } .oasis-mobile-topbar .omt-menus .oasis-menu-btn { font-family: var(--a26-tit) !important; font-weight: 400 !important; - text-transform: uppercase; + text-transform: none; font-size: 20px !important; letter-spacing: .02em; min-height: 44px !important; @@ -27,11 +26,11 @@ body { background: var(--a26-bg) !important; } color: var(--a26-fg) !important; border: var(--a26-bd) !important; border-radius: var(--a26-radius) !important; - box-shadow: 0 4px 0 var(--a26-fg) !important; + box-shadow: none !important; } .oasis-mobile-topbar .omt-menus .oasis-menu-btn span { color: var(--a26-fg) !important; } .oasis-mobile-topbar .omt-menus .oasis-menu-btn.active { background: var(--a26-neon) !important; color: var(--a26-on-neon) !important; } -.oasis-mobile-topbar .omt-menus .oasis-menu-btn:active { transform: translateY(3px); box-shadow: 0 1px 0 var(--a26-fg) !important; } +.oasis-mobile-topbar .omt-menus .oasis-menu-btn:active { box-shadow: none !important; } .oasis-mobile-topbar .omt-logo img, .oasis-mobile-topbar .omt-avatar img { border: var(--a26-bd) !important; border-radius: 50%; @@ -62,15 +61,14 @@ body { background: var(--a26-bg) !important; } color: var(--a26-fg) !important; border: var(--a26-bd) !important; border-radius: var(--a26-radius) !important; - box-shadow: 0 4px 0 var(--a26-fg) !important; + box-shadow: none !important; font-size: 24px !important; transition: transform .12s ease-out, box-shadow .12s ease-out; } .ln-app:active .ln-ico, .ln-app:focus-visible .ln-ico { - transform: translateY(3px); - box-shadow: 0 1px 0 var(--a26-fg) !important; + box-shadow: none !important; background: var(--a26-neon) !important; - border-color: var(--a26-fg) !important; color: var(--a26-on-neon) !important; + border-color: var(--a26-edge) !important; color: var(--a26-on-neon) !important; } .ln-app:hover, .ln-app:focus-visible { background: transparent !important; } .ln-lbl { color: var(--a26-fg) !important; font-size: 12px !important; font-weight: 600 !important; } @@ -95,7 +93,7 @@ body { background: var(--a26-bg) !important; } } .oasis-bottombar a.bb-item.current .bb-ico { background: var(--a26-neon) !important; color: var(--a26-on-neon) !important; } .oasis-bottombar a.bb-item.current .bb-lbl { color: var(--a26-fg) !important; } -.oasis-bottombar .bb-badge { background: var(--a26-neon) !important; color: var(--a26-on-neon) !important; border: 2px solid var(--a26-fg) !important; } +.oasis-bottombar .bb-badge { background: var(--a26-neon) !important; color: var(--a26-on-neon) !important; border: 1px solid var(--a26-edge) !important; } /* Nodos e Invitaciones: juntos y estrechos { background: var(--a26-bg) !important; border: var(--a26-bd-fino) !important; @@ -110,7 +108,7 @@ body { background: var(--a26-bg) !important; } color: var(--a26-fg) !important; border: var(--a26-bd) !important; border-radius: var(--a26-radius) !important; - box-shadow: 0 4px 0 var(--a26-fg) !important; + box-shadow: none !important; font-family: var(--a26-txt) !important; text-transform: none; } @@ -125,28 +123,28 @@ body { background: var(--a26-bg) !important; } .oasis-nav-list li a.current { background: var(--a26-neon) !important; color: var(--a26-on-neon) !important; } /* ---------- foro y chat: los colores de hive.css pensados para fondo negro ---------- */ -.forum-comment.level-1, .forum-comment.level-2, .forum-comment.level-3, .forum-comment.level-4 { border-left: 3px solid var(--a26-fg) !important; } +.forum-comment.level-1, .forum-comment.level-2, .forum-comment.level-3, .forum-comment.level-4 { border-left: 1px solid var(--a26-edge) !important; } .forum-comment.highlighted-reply { background: var(--a26-tint) !important; border-left: 5px solid var(--a26-neon) !important; } .chat-message-form .filter-btn::before, .forum-send-btn::before { color: var(--a26-fg) !important; } /* ---------- lo que mobile.css de upstream pinta en amarillo con !important ---------- */ /* mobile.css va detras del tema, asi que estas reglas tienen que ir aqui */ .create-button, button.create-button, a.create-button { - background-color: var(--a26-fg) !important; - border-color: var(--a26-fg) !important; - color: var(--a26-bg) !important; + background-color: var(--a26-neon) !important; + border-color: var(--a26-neon) !important; + color: var(--a26-on-neon) !important; } -.filter-btn.active { background-color: var(--a26-neon) !important; border-color: var(--a26-fg) !important; color: var(--a26-on-neon) !important; } +.filter-btn.active { background-color: var(--a26-neon) !important; border-color: var(--a26-neon) !important; color: var(--a26-on-neon) !important; } .peers-conn-actions button, .filter-box__button { background-color: var(--a26-bg) !important; color: var(--a26-fg) !important; } .oasis-id-copy { background: var(--a26-soft) !important; } .gov-overview tbody tr { border: var(--a26-bd-fino) !important; } -button, input[type="submit"], input[type="button"], .filter-btn, .create-button { min-height: 48px !important; font-size: 18px !important; } +button, input[type="submit"], input[type="button"], .filter-btn, .create-button { min-height: 44px !important; font-size: 16px !important; text-transform: none !important; } -/* aviso de bienvenida: el boton de empezar en negro y la X sin caja */ +/* aviso de bienvenida: el boton de empezar con el resalte y la X sin caja */ .welcome-banner .welcome-banner-start { - background: var(--a26-fg) !important; - color: var(--a26-bg) !important; - box-shadow: 0 4px 0 var(--a26-bg) !important; + background: var(--a26-neon) !important; + color: var(--a26-on-neon) !important; + box-shadow: none !important; border: var(--a26-bd) !important; } .welcome-banner .welcome-banner-close-btn { @@ -213,10 +211,10 @@ button:not([class]), input[type="submit"]:not([class]), input[type="button"]:not .identities .identity-actions button { width: 100%; } .identities .identity-legacy { text-align: center; text-decoration: underline; } .identities .identity-row { border: var(--a26-bd) !important; border-radius: var(--a26-radius) !important; background: var(--a26-bg) !important; } -.identities .identity-name { font-family: var(--a26-tit) !important; font-weight: 400 !important; text-transform: uppercase; font-size: 22px !important; } +.identities .identity-name { font-family: var(--a26-tit) !important; font-weight: 400 !important; text-transform: none; font-size: 22px !important; } .identities .identity-feed { color: var(--a26-muted) !important; } .identities .identity-active { background: var(--a26-neon) !important; border: var(--a26-bd-fino) !important; color: var(--a26-on-neon) !important; } -.identities .identity-warn { color: #c4003b !important; } +.identities .identity-warn { color: var(--a26-danger-text) !important; } /* las barras de filtros de cada modulo: upstream las encoge (.75em) y les pone borde amarillo con selectores compuestos; aqui, del mismo tamaño que el resto */ @@ -224,10 +222,10 @@ button:not([class]), input[type="submit"]:not([class]), input[type="button"]:not .mode-buttons-row .create-button, .filters .create-button, .filter-group .create-button, .activity-sub-filter .create-button, .activity-filter-grid .create-button { border: var(--a26-bd) !important; - border-color: var(--a26-fg) !important; - background: var(--a26-fg) !important; - color: var(--a26-bg) !important; - font-size: 18px !important; + border-color: var(--a26-neon) !important; + background: var(--a26-neon) !important; + color: var(--a26-on-neon) !important; + font-size: 16px !important; padding: 8px 16px !important; min-height: 48px !important; border-radius: var(--a26-radius) !important; @@ -259,7 +257,7 @@ button:not([class]), input[type="submit"]:not([class]), input[type="button"]:not background: var(--a26-bg) !important; color: var(--a26-fg) !important; border: var(--a26-bd-fino) !important; border-radius: 50% !important; box-shadow: none !important; } -.btn-singleview.btn-delete { color: #c4003b !important; border-color: #c4003b !important; } +.btn-singleview.btn-delete { color: var(--a26-danger-text) !important; border-color: var(--a26-danger-text) !important; } /* barras de filtros: chips en fila que se reparten el ancho, no un boton por linea */ .mode-buttons, .mode-buttons-row, .mode-buttons-cols, .tribe-mode-buttons, .ui-toolbar--filters, .filters { @@ -275,7 +273,7 @@ button:not([class]), input[type="submit"]:not([class]), input[type="button"]:not width: auto !important; flex: 0 0 auto !important; min-height: 38px !important; height: auto !important; padding: 4px 12px !important; margin: 0 !important; - font-size: 15px !important; box-shadow: 0 3px 0 var(--a26-fg) !important; + font-size: 15px !important; box-shadow: none !important; border: var(--a26-bd-fino) !important; } .mode-buttons .create-button, .mode-buttons-row .create-button, .tribe-mode-buttons .create-button, .filters .create-button { @@ -330,7 +328,7 @@ a.user-link { font-weight: 700 !important; text-decoration: underline !important } .pm-exposition-chip *, .card-chips-row > span * { color: var(--a26-bg) !important; } .pm-exposition-text { font-family: var(--a26-tit) !important; letter-spacing: .04em; font-size: 13px !important; } -.content-actions .btn-singleview.btn-content, .content-actions a.btn-content { border-color: var(--a26-fg) !important; color: var(--a26-fg) !important; } +.content-actions .btn-singleview.btn-content, .content-actions a.btn-content { border-color: var(--a26-edge) !important; color: var(--a26-fg) !important; } /* opiniones y votos */ .opinions-summary, .vote-btn, .score-btn { @@ -342,13 +340,13 @@ a.user-link { font-weight: 700 !important; text-decoration: underline !important /* tablas de detalle (fecha, estado, lugar...): filas claras, la etiqueta en gris */ .main-column table, .main-column table tr, .main-column table td, .main-column table th, .main-column table tbody { - background: var(--a26-bg) !important; color: var(--a26-fg) !important; border-color: var(--a26-fg) !important; + background: var(--a26-bg) !important; color: var(--a26-fg) !important; border-color: var(--a26-edge) !important; } .main-column table { border: var(--a26-bd-fino) !important; border-radius: 10px !important; } .main-column table tr + tr td { border-top: 1px solid var(--a26-line) !important; } .main-column table td:first-child, .main-column table th, .main-column [class*="-info-label"] { color: var(--a26-muted) !important; font-weight: 700 !important; font-size: 13px !important; - text-transform: uppercase; letter-spacing: .03em; + text-transform: none; letter-spacing: .03em; } /* perfil: los datos del habitante como lineas claras, sin cajas negras */ @@ -382,7 +380,7 @@ a.user-link { font-weight: 700 !important; text-decoration: underline !important padding: 4px 10px !important; font-weight: 700 !important; text-decoration: none !important; } .activity-chip.active { background: var(--a26-neon) !important; color: var(--a26-on-neon) !important; } -.activity-chip:hover { color: var(--a26-fg) !important; border-color: var(--a26-fg) !important; } +.activity-chip:hover { color: var(--a26-fg) !important; border-color: var(--a26-edge) !important; } /* ---------- fichas de filtro de 1.3.3 (OasisMobile.css las pinta en amarillo y gris) ---------- */ /* mismo selector que alli: gana por ir detras */ @@ -402,11 +400,11 @@ a.user-link { font-weight: 700 !important; text-decoration: underline !important border-radius: var(--a26-radius) !important; background: var(--a26-bg) !important; color: var(--a26-fg) !important; - box-shadow: 0 3px 0 var(--a26-fg) !important; + box-shadow: none !important; } .main-column :is(.tribe-mode-buttons, .mode-buttons, .mode-buttons-row, .filter-group) .filter-btn.active:not(.create-button) { background: var(--a26-neon) !important; - border-color: var(--a26-fg) !important; + border-color: var(--a26-edge) !important; color: var(--a26-on-neon) !important; } .main-column :is(.tribe-mode-buttons, .mode-buttons, .mode-buttons-row, .filter-group) .create-button { min-height: 48px !important; } @@ -442,7 +440,7 @@ img.inhabitant-photo-details { border: var(--a26-bd) !important; } .welcome-action { color: var(--a26-fg) !important; } label.welcome-ux-option { background: var(--a26-bg) !important; border: var(--a26-bd-fino) !important; - border-radius: var(--a26-radius) !important; box-shadow: 0 4px 0 var(--a26-fg) !important; + border-radius: var(--a26-radius) !important; box-shadow: none !important; } label.welcome-ux-option:has(input:checked) { background: var(--a26-neon) !important; color: var(--a26-on-neon) !important; } @@ -464,7 +462,7 @@ label.welcome-ux-option:has(input:checked) { background: var(--a26-neon) !import font-family: "Noto Color Emoji", "Apple Color Emoji", "Segoe UI Emoji", sans-serif !important; text-transform: none !important; letter-spacing: 0 !important; background: var(--a26-bg) !important; color: var(--a26-fg) !important; - border: 1px solid var(--a26-fg) !important; border-radius: 10px !important; box-shadow: none !important; + border: 1px solid var(--a26-edge) !important; border-radius: 10px !important; box-shadow: none !important; } /* el 📌 va en la esquina: el texto deja su hueco (el padding general de la burbuja lo pisaba) */ .chat-bubble-row .chat-message.chat-has-pin-toggle { position: relative !important; padding-right: 44px !important; } @@ -476,15 +474,15 @@ label.welcome-ux-option:has(input:checked) { background: var(--a26-neon) !import .skin-card { background: var(--a26-bg) !important; color: var(--a26-fg) !important; border: var(--a26-bd) !important; border-radius: var(--a26-radius) !important; - box-shadow: 0 5px 0 var(--a26-fg) !important; + box-shadow: none !important; } .skin-card .skin-shot { border: var(--a26-bd-fino); } .skin-card .skin-name { font-family: var(--a26-tit) !important; font-weight: 400 !important; - text-transform: uppercase; letter-spacing: .02em; font-size: 21px !important; + text-transform: none; letter-spacing: .02em; font-size: 21px !important; } .skin-card.is-current { background: var(--a26-neon) !important; color: var(--a26-on-neon) !important; border: var(--a26-bd) !important; } -.skin-card:active { transform: translateY(4px); box-shadow: 0 1px 0 var(--a26-fg) !important; background: var(--a26-neon) !important; color: var(--a26-on-neon) !important; } +.skin-card:active { box-shadow: none !important; background: var(--a26-neon) !important; color: var(--a26-on-neon) !important; } /* ---------- letra y botones mas grandes, para leer sin gafas ---------- */ body { font-size: 18px; } @@ -518,3 +516,6 @@ input, textarea, select { font-size: 18px !important; min-height: 50px; } /* el plegable de mensajes anteriores del buzon: gris claro sobre blanco */ summary.pm-thread-toggle { color: var(--a26-muted) !important; } .pm-thread-icon { font-size: 12px !important; } + +/* fichas de la actividad y de filtros: mobile.css del dev las pone en mayusculas */ +.activity-chip, a.activity-chip, .filter-btn, a.filter-btn { text-transform: none !important; } diff --git a/src/client/assets/styles/mobile_base.css b/src/client/assets/styles/mobile_base.css index f3e3e36..b49222e 100644 --- a/src/client/assets/styles/mobile_base.css +++ b/src/client/assets/styles/mobile_base.css @@ -157,7 +157,7 @@ body { padding-top: 74px; background: #121212; } min-height: 48px; padding: 0 var(--space-3); cursor: pointer; color: #FFD700; font-weight: bold; - text-transform: uppercase; letter-spacing: .04em; + text-transform: none; letter-spacing: .04em; font-size: var(--fs-caption); } .oasis-nav-header .nav-text { flex: 1 1 auto; } @@ -218,7 +218,7 @@ body { overscroll-behavior: none; } .bb-manage { margin: 8px 0 16px; } .bb-manage h3, .bb-manage-add h3 { - font-size: 13px; text-transform: uppercase; letter-spacing: .05em; + font-size: 13px; text-transform: none; letter-spacing: .05em; color: #FFD700; margin: 14px 0 8px; } .bb-manage-list { display: flex; flex-direction: column; gap: 8px; } @@ -246,7 +246,7 @@ body { overscroll-behavior: none; } .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; + font-size: 11px; font-weight: 600; letter-spacing: .08em; text-transform: none; color: var(--c-muted); } .ln-grid { @@ -377,7 +377,7 @@ button, input[type="submit"], input[type="button"], color: var(--c-muted, #8c8c8c); font-size: 11px; font-weight: 700; - text-transform: uppercase; + text-transform: none; letter-spacing: .03em; margin-bottom: 2px; padding-bottom: 0; @@ -395,7 +395,7 @@ button, input[type="submit"], input[type="button"], color: var(--c-muted, #8c8c8c); font-size: 10px; font-weight: 700; - text-transform: uppercase; + text-transform: none; letter-spacing: .03em; margin-bottom: 1px; white-space: nowrap; diff --git a/src/client/assets/styles/mobile_ux.css b/src/client/assets/styles/mobile_ux.css index 06ce6c1..430bc19 100644 --- a/src/client/assets/styles/mobile_ux.css +++ b/src/client/assets/styles/mobile_ux.css @@ -1,15 +1,12 @@ -/* Piezas de usabilidad de la rama movil, para los dos skins (Acampada y OasisMobile). +/* Piezas de usabilidad de la rama movil. * - * Va la ultima, detras del skin. Los colores salen de las variables de - * Acampada (--a26-*) si el tema las define y, si no, de la paleta oscura con oro - * de OasisMobile; asi cada pieza nueva se ve bien en los dos sin duplicarla. + * Va detras del skin (Activista, el unico del movil desde el 20/10/2026) y antes de + * /mobile-colors.css. Los colores salen de las variables --a26-* a traves de las + * --ux-*; los valores de reserva son los del OasisMobile.css del dev, por si algun + * dia se carga sin skin. * - * Letra: Anton (--ux-title) solo para titulos y para el boton principal de cada - * pantalla; todo lo demas (fichas, botones secundarios, etiquetas) en la letra del - * sistema en negrita. Antes habia Anton en botones, fichas, cabeceras de tabla y - * pestañas, y la pantalla parecia un cartel. - * - * Espacios: rejilla de 4/8. Zonas tactiles de 44px como minimo. */ + * Letra del sistema en todo; los titulos en negrita. Espacios: rejilla de 4/8. + * Zonas tactiles de 44px como minimo; enlaces dentro del texto, 24px. */ :root { --ux-bg: var(--a26-bg, #121212); @@ -18,7 +15,7 @@ --ux-on-accent: var(--a26-on-neon, #000); --ux-soft: var(--a26-soft, #1e1e1e); --ux-muted: var(--a26-muted, #a8a8a8); - --ux-line: var(--a26-fg, #3a3a3a); + --ux-line: var(--a26-edge, #3a3a3a); --ux-radius: var(--a26-radius, 12px); --ux-title: var(--a26-tit, inherit); --ux-text: var(--a26-txt, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif); @@ -39,7 +36,7 @@ } .main-column :is(.tribe-mode-buttons, .mode-buttons, .mode-buttons-row, .filter-group, .filters) .filter-btn:not(.create-button) { font-size: 15px !important; - text-transform: uppercase; + text-transform: none; } /* los formularios de cada modulo (crear tribu, votacion, sala...): etiquetas como texto, no como titulos; el h2 de la pregunta de una votacion era Anton de 28px */ @@ -144,19 +141,19 @@ .main-column a.tribe-row { display: flex !important; flex-wrap: nowrap !important; align-items: center; gap: 12px; width: auto !important; padding: 10px 12px !important; min-height: 72px; - border: 2px solid var(--ux-line) !important; border-radius: var(--ux-radius) !important; + border: 1px solid var(--ux-line) !important; border-radius: var(--ux-radius) !important; background: var(--ux-bg) !important; color: var(--ux-fg) !important; text-decoration: none !important; } .main-column a.tribe-row:active { background: var(--ux-accent) !important; color: var(--ux-on-accent) !important; } .tribe-row .tribe-row-thumb { flex: 0 0 52px; width: 52px !important; height: 52px !important; border-radius: 10px !important; - object-fit: cover; border: 2px solid var(--ux-line) !important; margin: 0 !important; + object-fit: cover; border: 1px solid var(--ux-line) !important; margin: 0 !important; } .tribe-row .tribe-row-icon { padding: 11px; background: var(--ux-soft); } .tribe-row .tribe-row-text { flex: 1 1 0; min-width: 0; overflow: hidden; display: flex; flex-direction: column; gap: 2px; } .tribe-row .tribe-row-text > span { display: block; max-width: 100%; margin: 0 !important; padding: 0 !important; line-height: 1.3; } .tribe-row .tribe-row-title { - font-family: var(--ux-title) !important; font-size: 21px; line-height: 1.1; text-transform: uppercase; + font-family: var(--ux-title) !important; font-size: 21px; line-height: 1.1; text-transform: none; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .tribe-row .tribe-row-facts { font-size: 13px; font-weight: 700; color: var(--ux-muted); } @@ -170,7 +167,7 @@ .vote-bar-count { font-weight: 700; font-size: 15px; min-width: 2ch; text-align: right; } .vote-bar-meter { grid-column: 1 / -1; width: 100%; height: 12px; appearance: none; -webkit-appearance: none; - border: 2px solid var(--ux-line); border-radius: 8px; overflow: hidden; background: var(--ux-soft); + border: 1px solid var(--ux-line); border-radius: 8px; overflow: hidden; background: var(--ux-soft); } .vote-bar-meter::-webkit-progress-bar { background: var(--ux-soft); } .vote-bar-meter::-webkit-progress-value { background: var(--ux-fg); } @@ -189,15 +186,15 @@ width: 100% !important; min-height: 52px !important; margin: 0 !important; font-family: var(--ux-text) !important; font-weight: 700 !important; font-size: 16px !important; } -.main-column .vote-buttons-row-single button[value="YES"], .main-column .vote-buttons-row-single button[value="GREEN"] { border-color: var(--ux-ok) !important; box-shadow: 0 4px 0 var(--ux-ok) !important; } -.main-column .vote-buttons-row-single button[value="NO"], .main-column .vote-buttons-row-single button[value="RED"] { border-color: var(--ux-danger) !important; box-shadow: 0 4px 0 var(--ux-danger) !important; } -.main-column .vote-buttons-row-single button[value="YELLOW"] { border-color: var(--ux-warn) !important; box-shadow: 0 4px 0 var(--ux-warn) !important; } +.main-column .vote-buttons-row-single button[value="YES"], .main-column .vote-buttons-row-single button[value="GREEN"] { border-color: var(--ux-ok) !important; box-shadow: none !important; } +.main-column .vote-buttons-row-single button[value="NO"], .main-column .vote-buttons-row-single button[value="RED"] { border-color: var(--ux-danger) !important; box-shadow: none !important; } +.main-column .vote-buttons-row-single button[value="YELLOW"] { border-color: var(--ux-warn) !important; box-shadow: none !important; } /* tipo de votacion: tarjetas de radio */ .vote-kind { display: flex; flex-direction: column; gap: 10px; margin: 4px 0 18px; } .vote-kind-title { font-weight: 700; font-size: 15px; } .vote-kind-option { display: grid !important; grid-template-columns: auto 1fr; gap: 2px 12px; align-items: center; - padding: 12px 14px; border: 2px solid var(--ux-line); border-radius: var(--ux-radius); cursor: pointer; + padding: 12px 14px; border: 1px solid var(--ux-line); border-radius: var(--ux-radius); cursor: pointer; } .main-column .vote-kind-option input[type="radio"] { grid-row: 1 / span 2; width: 22px !important; height: 22px !important; min-height: 0 !important; margin: 0 !important; accent-color: var(--ux-fg); } .vote-kind-option:has(input:checked) { background: var(--ux-accent); color: var(--ux-on-accent); } @@ -210,12 +207,12 @@ .module-group { display: flex; flex-direction: column; gap: 8px; } .module-group-title { font-family: var(--ux-title) !important; font-weight: 400 !important; font-size: 20px !important; - text-transform: uppercase; margin: 0 0 4px !important; padding-bottom: 4px; border-bottom: 3px solid var(--ux-line); + text-transform: none; margin: 0 0 4px !important; padding-bottom: 4px; border-bottom: 1px solid var(--ux-line); } .main-column label.module-switch { display: grid !important; grid-template-columns: 40px minmax(0, 1fr) 52px; align-items: center; gap: 12px; padding: 10px 12px; min-height: 64px; cursor: pointer; - border: 2px solid var(--ux-line); border-radius: var(--ux-radius); + border: 1px solid var(--ux-line); border-radius: var(--ux-radius); font-family: var(--ux-text) !important; text-transform: none !important; letter-spacing: 0 !important; } .module-switch-ico { width: 40px; height: 40px; padding: 8px; border-radius: 10px; background: var(--ux-soft); color: var(--ux-fg); } @@ -227,7 +224,7 @@ .main-column label.module-switch input.switch-input[type="checkbox"] { appearance: none; -webkit-appearance: none; flex: 0 0 auto; width: 52px !important; height: 30px !important; min-height: 0 !important; margin: 0 !important; padding: 0 !important; - border: 2px solid var(--ux-line) !important; border-radius: 16px !important; background: var(--ux-soft) !important; + border: 1px solid var(--ux-line) !important; border-radius: 16px !important; background: var(--ux-soft) !important; position: relative; cursor: pointer; transition: background .15s ease-out; } .main-column label.module-switch input.switch-input[type="checkbox"]::after { @@ -248,7 +245,7 @@ .main-column .lang-pick .lang-chip, .lang-pick .lang-chip { width: 100% !important; min-height: 48px !important; margin: 0 !important; padding: 6px 10px !important; font-size: 16px !important; text-transform: none !important; box-shadow: none !important; - border: 2px solid var(--ux-line) !important; background: var(--ux-bg) !important; color: var(--ux-fg) !important; + border: 1px solid var(--ux-line) !important; background: var(--ux-bg) !important; color: var(--ux-fg) !important; } .main-column .lang-pick .lang-chip.is-current, .lang-pick .lang-chip.is-current { background: var(--ux-accent) !important; color: var(--ux-on-accent) !important; } @@ -341,10 +338,10 @@ body .main-content > .main-column { padding-left: 0 !important; padding-right: 0 .tribe-tabs-strip::-webkit-scrollbar { display: none; } .main-column a.tribe-tab, .tribe-tabs-more > summary { flex: 0 0 auto; scroll-snap-align: start; display: inline-flex; align-items: center; justify-content: center; - min-height: 40px; padding: 4px 14px; border: 2px solid var(--ux-line) !important; border-radius: 20px !important; + min-height: 40px; padding: 4px 14px; border: 1px solid var(--ux-line) !important; border-radius: 20px !important; background: var(--ux-bg) !important; color: var(--ux-fg) !important; text-decoration: none !important; font-family: var(--ux-text) !important; font-weight: 700 !important; font-size: 14px !important; white-space: nowrap; - text-transform: uppercase; letter-spacing: .02em; + text-transform: none; letter-spacing: .02em; } .main-column a.tribe-tab.is-current { background: var(--ux-accent) !important; color: var(--ux-on-accent) !important; } .tribe-tabs-more > summary { list-style: none; cursor: pointer; margin-top: 2px; } @@ -393,7 +390,7 @@ body .main-content > .main-column { padding-left: 0 !important; padding-right: 0 .main-column :is(.tribe-mode-buttons, .mode-buttons, .mode-buttons-row, .filters, .forum-create-col) .create-button.create-button { width: 100% !important; min-height: 52px !important; margin: 4px 0 0 !important; padding: 8px 16px !important; font-family: var(--ux-title) !important; font-weight: 400 !important; font-size: 20px !important; - letter-spacing: .02em !important; text-transform: uppercase; + letter-spacing: .02em !important; text-transform: none; } /* ---------- estados vacios: icono, texto centrado, sin cajas ---------- */ @@ -414,14 +411,14 @@ body .main-content > .main-column { padding-left: 0 !important; padding-right: 0 /* ---------- el pie abajo del todo aunque la pagina sea corta ---------- */ body .main-content { min-height: calc(100dvh - 300px); } .main-column .tribe-grid:has(> p:only-child), .main-column .filters, .main-column .tribe-mode-buttons { padding-left: 0 !important; padding-right: 0 !important; margin-left: 0 !important; margin-right: 0 !important; } -.main-column :is(.tribe-mode-buttons, .mode-buttons, .mode-buttons-row, .filters, .forum-create-col) .create-button.create-button { box-shadow: 0 5px 0 var(--ux-accent) !important; } +.main-column :is(.tribe-mode-buttons, .mode-buttons, .mode-buttons-row, .filters, .forum-create-col) .create-button.create-button { box-shadow: none !important; } /* ---------- buzon: cada mensaje en pocas lineas ---------- */ /* era una tabla de seis filas (De, Fecha, Para, Asunto, Tasa ECO, Cifrado) dentro de dos cajas: un aviso ocupaba una pantalla. Para es siempre una misma, el asunto ya es el titulo, la tasa ECO no se entiende y el cifrado es igual en todos */ .main-column .pm-thread { border: 0 !important; padding: 0 !important; margin: 0 0 10px !important; background: transparent !important; } -.main-column div.pm-card { padding: 12px !important; margin: 0 !important; border: 2px solid var(--ux-line) !important; border-radius: var(--ux-radius) !important; } +.main-column div.pm-card { padding: 12px !important; margin: 0 !important; border: 1px solid var(--ux-line) !important; border-radius: var(--ux-radius) !important; } .main-column .pm-card .pm-info-table { display: flex !important; flex-wrap: wrap; align-items: center; gap: 4px 10px; border: 0 !important; margin: 0 0 6px !important; width: 100%; } .main-column .pm-card .pm-info-table tbody { display: contents !important; } .main-column .pm-card .pm-info-table tr { display: none !important; } @@ -481,7 +478,7 @@ form.oasis-phone-state .oasis-phone-state-btn { .main-column .vote-bar > * { margin: 0 !important; } .main-column .vote-bar-meter { height: 12px !important; min-height: 0 !important; } .main-column .vote-bar-label { text-transform: lowercase; } -.main-column .vote-bar-label::first-letter { text-transform: uppercase; } +.main-column .vote-bar-label::first-letter { text-transform: none; } /* personalizar la barra de abajo: una fila por atajo, como el resto de listas */ .main-column .bb-manage-list, .main-column .bb-pick-grid { display: flex !important; flex-direction: column !important; gap: 8px !important; align-items: stretch !important; } @@ -520,7 +517,7 @@ body > .welcome-banner { order: -1; margin-top: 10px !important; } margin: 0 !important; padding: 6px 8px !important; box-sizing: border-box; border: var(--a26-bd-fino, 2px solid var(--ux-line)) !important; border-radius: var(--ux-radius) !important; background: var(--ux-bg) !important; color: var(--ux-fg) !important; box-shadow: none !important; - font-family: var(--ux-text) !important; font-weight: 700 !important; font-size: 15px !important; text-transform: uppercase; text-decoration: none !important; + font-family: var(--ux-text) !important; font-weight: 700 !important; font-size: 15px !important; text-transform: none; text-decoration: none !important; } /* ---------- pestaña Menu y su hoja con todos los modulos ---------- */ @@ -538,34 +535,34 @@ body > .welcome-banner { order: -1; margin-top: 10px !important; } bottom: 0; max-height: 82vh; max-height: 82dvh; overflow-y: auto; overscroll-behavior: contain; padding: 8px 14px calc(18px + var(--inset-bottom)); background: var(--ux-bg); color: var(--ux-fg); - border-top: 3px solid var(--ux-line); border-radius: 22px 22px 0 0; + border-top: 1px solid var(--ux-line); border-radius: 22px 22px 0 0; box-shadow: 0 -6px 24px rgba(0, 0, 0, .25); } .menu-sheet-head { position: sticky; top: -8px; z-index: 1; display: flex; align-items: center; justify-content: space-between; - margin: 0 -14px 8px; padding: 12px 14px 10px; background: var(--ux-bg); border-bottom: 2px solid var(--ux-line); + margin: 0 -14px 8px; padding: 12px 14px 10px; background: var(--ux-bg); border-bottom: 1px solid var(--ux-line); } .menu-sheet-head::before { content: ""; position: absolute; top: 4px; left: 50%; width: 44px; height: 5px; margin-left: -22px; border-radius: 3px; background: var(--ux-muted); } -.menu-sheet-title { font-family: var(--ux-title); font-size: 26px; text-transform: uppercase; line-height: 1; } +.menu-sheet-title { font-family: var(--ux-title); font-size: 26px; text-transform: none; line-height: 1; } .menu-sheet-close { display: inline-flex; align-items: center; justify-content: center; width: 44px; height: 44px; cursor: pointer; - border: 2px solid var(--ux-line); border-radius: 50%; font-size: 20px; background: var(--ux-bg); + border: 1px solid var(--ux-line); border-radius: 50%; font-size: 20px; background: var(--ux-bg); } .menu-sheet .launcher { margin: 0 !important; padding: 0 !important; } /* cinco pestañas: que quepan a 360px */ .oasis-bottombar .bb-item { flex: 1 1 0 !important; min-width: 0 !important; } @media (max-width: 380px) { .oasis-bottombar .bb-lbl { font-size: 11px !important; } } -.oasis-bottombar .menu-toggle:checked ~ div.menu-sheet { background: var(--ux-bg) !important; bottom: calc(66px + var(--inset-bottom)); border-radius: 22px 22px 0 0 !important; border: 0 !important; border-top: 3px solid var(--ux-line) !important; } -.oasis-bottombar div.menu-sheet-head { background: var(--ux-bg) !important; border: 0 !important; border-bottom: 2px solid var(--ux-line) !important; } +.oasis-bottombar .menu-toggle:checked ~ div.menu-sheet { background: var(--ux-bg) !important; bottom: calc(66px + var(--inset-bottom)); border-radius: 22px 22px 0 0 !important; border: 0 !important; border-top: 1px solid var(--ux-line) !important; } +.oasis-bottombar div.menu-sheet-head { background: var(--ux-bg) !important; border: 0 !important; border-bottom: 1px solid var(--ux-line) !important; } .oasis-bottombar .menu-toggle:checked ~ .menu-sheet-backdrop { bottom: calc(66px + var(--inset-bottom)); } /* en la hoja todo tiene que caber sin deslizar: cinco iconos por fila y mas pequeños (con 20 modulos la rejilla del lanzador medía 1.200px para 690 de hoja) */ .menu-sheet .launcher .ln-grid { display: grid !important; grid-template-columns: repeat(5, 1fr) !important; gap: 8px 2px !important; } .menu-sheet .launcher .ln-app { min-width: 0 !important; padding: 0 !important; gap: 4px !important; } -.menu-sheet .launcher .ln-ico.has-svg { width: 48px !important; height: 48px !important; padding: 11px !important; box-shadow: 0 3px 0 var(--ux-line) !important; } +.menu-sheet .launcher .ln-ico.has-svg { width: 48px !important; height: 48px !important; padding: 11px !important; box-shadow: none !important; } .menu-sheet .launcher .ln-lbl { font-size: 11px !important; line-height: 1.1 !important; } .menu-sheet .launcher .ln-title { font-size: 15px !important; margin: 0 0 6px !important; padding: 0 0 2px !important; border-bottom-width: 2px !important; } .menu-sheet .launcher .ln-group + .ln-group { margin-top: 10px !important; } @@ -594,13 +591,13 @@ body:has(#oasis-menu-toggle:checked) form.oasis-phone-state { display: none !imp .main-column button.ux-pick-card { display: flex !important; flex-direction: row; align-items: center; text-align: left; width: 100% !important; min-width: 0 !important; min-height: 64px !important; height: auto !important; margin: 0 0 10px !important; padding: 8px 12px 8px 8px !important; - background: var(--ux-bg) !important; color: var(--ux-fg) !important; border: 2px solid var(--ux-line) !important; border-radius: 14px !important; - box-shadow: 0 3px 0 var(--ux-line) !important; text-transform: none !important; font-family: inherit !important; cursor: pointer; + background: var(--ux-bg) !important; color: var(--ux-fg) !important; border: 1px solid var(--ux-line) !important; border-radius: 14px !important; + box-shadow: none !important; text-transform: none !important; font-family: inherit !important; cursor: pointer; } .main-column button.ux-pick-card.is-current { background: var(--ux-accent) !important; color: var(--ux-on-accent) !important; } .main-column .ux-pick-shot { display: block; flex: 0 0 76px; width: 76px; height: auto; border-radius: 8px; margin: 0 12px 0 0; border: 1px solid rgba(0, 0, 0, .2); } .main-column .ux-pick-label { flex: 1 1 auto; min-width: 0; text-align: left; font-size: 16px; font-weight: 700; line-height: 1.2; } -.main-column .ux-pick-now { flex: 0 0 auto; margin-left: 8px; font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; } +.main-column .ux-pick-now { flex: 0 0 auto; margin-left: 8px; font-size: 12px; font-weight: 700; text-transform: none; letter-spacing: .04em; } /* perfil de uso: sin miniatura, nombre y una linea de para que sirve */ .main-column .ux-pick-text { flex: 1 1 auto; min-width: 0; display: block; text-align: left; } .main-column .ux-pick-text .ux-pick-label { display: block; } @@ -624,9 +621,9 @@ body:has(#oasis-menu-toggle:checked) form.oasis-phone-state { display: none !imp display: flex !important; flex-direction: column; align-items: center; justify-content: flex-start; text-align: center; min-height: 104px; min-width: 0; padding: 12px 6px 10px !important; box-sizing: border-box; text-decoration: none !important; background: var(--ux-bg) !important; color: var(--ux-fg) !important; - border: 2px solid var(--ux-line) !important; border-radius: 14px !important; box-shadow: 0 3px 0 var(--ux-line) !important; + border: 1px solid var(--ux-line) !important; border-radius: 14px !important; box-shadow: none !important; } -.main-column a.quick-tile:active { transform: translateY(3px); box-shadow: 0 0 0 var(--ux-line) !important; background: var(--ux-accent) !important; color: var(--ux-on-accent) !important; } +.main-column a.quick-tile:active { box-shadow: 0 0 0 var(--ux-line) !important; background: var(--ux-accent) !important; color: var(--ux-on-accent) !important; } .main-column .quick-tile .quick-ico.has-svg { display: block; width: 30px; height: 30px; margin: 0 0 6px; } .main-column .quick-tile .quick-ico svg { width: 100%; height: 100%; } .main-column .quick-tile .quick-name { display: block; font-size: 14px; font-weight: 700; line-height: 1.15; } @@ -635,7 +632,7 @@ body:has(#oasis-menu-toggle:checked) form.oasis-phone-state { display: none !imp .main-column div.quick-links { display: block !important; margin: 0 !important; padding: 0 !important; } .main-column div.quick-links a { display: block !important; text-align: center; margin: 0 0 10px !important; } .main-column div.quick-identity { display: flex !important; align-items: center; margin: 0 0 14px !important; padding: 0 !important; } -.main-column .quick-identity-avatar { width: 56px; height: 56px; border-radius: 50%; margin-right: 12px; border: 2px solid var(--ux-line); object-fit: cover; } +.main-column .quick-identity-avatar { width: 56px; height: 56px; border-radius: 50%; margin-right: 12px; border: 1px solid var(--ux-line); object-fit: cover; } .main-column .quick-identity-name { font-size: 18px; font-weight: 700; } .main-column section#identity a.create-button, .main-column section#greeting a.create-button { display: block !important; text-align: center; } @@ -647,27 +644,80 @@ body:has(#oasis-menu-toggle:checked) form.oasis-phone-state { display: none !imp .main-column button.pal-card { display: flex !important; flex-direction: row !important; flex-wrap: nowrap !important; align-items: center; width: 100% !important; min-width: 0 !important; min-height: 56px !important; height: auto !important; margin: 0 !important; padding: 8px 10px !important; text-align: left; text-transform: none !important; font-family: var(--ux-text) !important; - background: var(--ux-bg) !important; color: var(--ux-fg) !important; border: 2px solid var(--ux-line) !important; border-radius: 14px !important; box-shadow: 0 3px 0 var(--ux-line) !important; + background: var(--ux-bg) !important; color: var(--ux-fg) !important; border: 1px solid var(--ux-line) !important; border-radius: 14px !important; box-shadow: none !important; } -.main-column button.pal-card.is-current { border-width: 3px !important; box-shadow: 0 0 0 3px var(--ux-accent), 0 3px 0 var(--ux-line) !important; } +.main-column button.pal-card.is-current { border-width: 3px !important; box-shadow: 0 0 0 3px var(--ux-accent) !important; } .main-column button.pal-card { justify-content: flex-start !important; } .main-column .pal-card .pal-name { flex: 1 1 auto; min-width: 0; text-align: left; font-size: 15px; font-weight: 700; line-height: 1.15; } .main-column .pal-card .ux-pick-now { display: none; } -.pal-swatch { display: inline-flex; flex: 0 0 auto; width: 42px; height: 30px; margin-right: 10px; border-radius: 8px; overflow: hidden; border: 2px solid var(--ux-line); } +.pal-swatch { display: inline-flex; flex: 0 0 auto; width: 42px; height: 30px; margin-right: 10px; border-radius: 8px; overflow: hidden; border: 1px solid var(--ux-line); } .pal-swatch .pal-bg { flex: 2 1 0; } .pal-swatch .pal-fg { flex: 1 1 0; } .pal-swatch .pal-ac { flex: 1 1 0; } -.main-column details.color-custom { margin: 4px 0 0 !important; padding: 0 !important; border: 2px solid var(--ux-line) !important; border-radius: 14px !important; background: var(--ux-bg) !important; } +.main-column details.color-custom { margin: 4px 0 0 !important; padding: 0 !important; border: 1px solid var(--ux-line) !important; border-radius: 14px !important; background: var(--ux-bg) !important; } .main-column details.color-custom > summary { display: flex; align-items: center; min-height: 52px; padding: 8px 12px; font-weight: 700; cursor: pointer; list-style: none; } .main-column details.color-custom > summary::-webkit-details-marker { display: none; } .main-column details.color-custom > summary::after { content: "\25BE"; margin-left: auto; font-size: 18px; } .main-column details.color-custom[open] > summary::after { content: "\25B4"; } .main-column form.color-custom-form { padding: 4px 12px 14px !important; margin: 0 !important; border: 0 !important; background: transparent !important; box-shadow: none !important; } .main-column form.color-custom-form label.color-field { display: flex !important; align-items: center; justify-content: space-between; min-height: 52px; margin: 0 0 8px !important; } -.main-column label.color-field input[type="color"] { width: 64px !important; height: 44px !important; padding: 2px !important; border: 2px solid var(--ux-line) !important; border-radius: 10px !important; background: var(--ux-bg) !important; } +.main-column label.color-field input[type="color"] { width: 64px !important; height: 44px !important; padding: 2px !important; border: 1px solid var(--ux-line) !important; border-radius: 10px !important; background: var(--ux-bg) !important; } .main-column form.color-custom-form .create-button { width: 100% !important; margin-top: 6px !important; } .main-column .color-pick .welcome-action { margin-top: 4px !important; } +/* ---------- actividad (la portada): fichas en rejilla y tarjetas ordenadas ---------- */ +/* fichas de filtro: tres columnas iguales, texto centrado (antes cada una de su ancho y + el texto pegado arriba) */ +.main-column div.activity-filter-chips:not(.activity-toolbar-row) { + display: grid !important; grid-template-columns: repeat(3, minmax(0, 1fr)); grid-gap: 8px; + margin: 0 0 12px !important; padding: 0 !important; +} +.main-column div.activity-filter-chips:not(.activity-toolbar-row) a.activity-chip { + display: flex !important; align-items: center; justify-content: center; text-align: center; + width: auto !important; min-width: 0; min-height: 44px !important; margin: 0 !important; padding: 4px 6px !important; + font-size: 14px !important; line-height: 1.15 !important; overflow-wrap: anywhere; +} +/* cabecera de cada tarjeta: los botones en una fila arriba a la derecha y la etiqueta + (FEEDS, SALAS...) debajo a la izquierda; antes compartian fila y la X caia sola */ +.main-column div.card-header.activity-card-header { display: flex !important; flex-direction: column !important; align-items: stretch !important; padding: 0 !important; margin: 0 0 8px !important; } +.main-column div.activity-card-header > div.content-actions { order: 1; display: flex !important; justify-content: flex-end !important; align-items: center; gap: 8px; width: 100% !important; margin: 0 0 8px !important; padding: 0 !important; } +.main-column div.activity-card-header > div.card-chips-row { order: 2; display: flex !important; justify-content: flex-start !important; margin: 0 !important; padding: 0 !important; } +/* la X de borrar, cerrada, mide lo que su boton (algo le daba todo el ancho) */ +.main-column div.content-actions > details.content-action-confirm:not([open]) { width: auto !important; flex: 0 0 auto !important; margin: 0 !important; } +@supports not (inset: 0) { + .main-column div.activity-card-header > div.content-actions > * { margin-left: 8px !important; } +} + +/* ---------- barra de unirse a un pub (mobile_pubbar.js) ---------- */ +/* fija encima de la barra de abajo, en todas las pantallas; el color de la franja de + la izquierda dice el estado: resalte mientras va, verde al terminar, ambar si se + unio pero sin descarga y rojo si fallo */ +/* el skin quita fondo, borde y sombra a todos los div: aqui con especificidad alta */ +nav.oasis-bottombar div.pub-bar, body div.pub-bar { + position: fixed !important; left: 8px; right: 8px; bottom: calc(80px + var(--inset-bottom)); z-index: 90; + display: flex; align-items: flex-start; padding: 10px 6px 10px 14px !important; margin: 0 !important; border-radius: 12px !important; + background: var(--ux-bg) !important; color: var(--ux-fg) !important; + border: 1px solid var(--ux-line) !important; border-left: 6px solid var(--ux-accent) !important; + box-shadow: 0 4px 16px rgba(0, 0, 0, .22) !important; +} +nav.oasis-bottombar div.pub-bar[hidden], body div.pub-bar[hidden] { display: none !important; } +nav.oasis-bottombar div.pub-bar.is-done { border-left-color: var(--ux-ok) !important; } +nav.oasis-bottombar div.pub-bar.is-warn { border-left-color: var(--ux-warn) !important; } +nav.oasis-bottombar div.pub-bar.is-error { border-left-color: var(--ux-danger) !important; } +nav.oasis-bottombar div.pub-bar div.pub-bar-body { flex: 1 1 auto; min-width: 0; padding: 0 !important; margin: 0 !important; background: transparent !important; box-shadow: none !important; border: 0 !important; } +.pub-bar .pub-bar-text { margin: 0 !important; font-size: 15px; font-weight: 700; line-height: 1.35; } +.pub-bar .pub-bar-hint { margin: 2px 0 0 !important; font-size: 13px; color: var(--ux-muted); } +.pub-bar .pub-bar-hint:empty { display: none; } +.pub-bar .pub-bar-meter { display: block; width: 100%; height: 6px; margin-top: 8px; accent-color: var(--ux-accent); } +.pub-bar.is-done .pub-bar-meter, .pub-bar.is-warn .pub-bar-meter, .pub-bar.is-error .pub-bar-meter { display: none; } +.pub-bar .pub-bar-close { margin: 0 !important; padding: 0 !important; border: 0 !important; background: none !important; } +.pub-bar .pub-bar-close button { min-width: 44px; min-height: 44px !important; padding: 0 !important; margin: 0 !important; border: 0 !important; background: transparent !important; color: var(--ux-fg) !important; font-size: 18px !important; } +.pub-bar[data-running="1"] .pub-bar-close { display: none; } +/* que la barra no tape lo ultimo de la pagina */ +body.has-pub-bar { padding-bottom: calc(190px + var(--inset-bottom)) !important; } +/* con el menu abierto, la barra se va */ +body:has(#oasis-menu-toggle:checked) .pub-bar { display: none !important; } + /* ---------- zonas tactiles (pruebas de usabilidad del 20/10) ---------- */ /* en el Pixel 9 se midieron por debajo de 44px: logo y avatar de la barra de arriba (42), fichas de filtro (38), pasos de la bienvenida (33) y casillas del dev (16) */ @@ -687,6 +737,7 @@ body .oasis-mobile-topbar .omt-logo img, body .oasis-mobile-topbar .omt-avatar i .main-column .tribe-card-title > a { display: inline-block; padding: 6px 0; min-height: 44px; box-sizing: border-box; } .main-column a.user-link, .main-column a.tag-link { display: inline-block; min-height: 24px; line-height: 24px; } .main-column .message-text a { display: inline-block; min-height: 24px; line-height: 24px; } +.main-column a[href^="http"] { display: inline-block; min-height: 24px; line-height: 24px; } .main-column summary.pm-thread-toggle { min-height: 44px; display: flex; align-items: center; } .main-column form label:has(> input[type="radio"]) { min-height: 44px; display: flex !important; align-items: center; } .main-column input[type="checkbox"]:not(.switch-input):not(.pref-pill-input), .main-column input[type="radio"] { width: 24px !important; height: 24px !important; } diff --git a/src/client/assets/themes/Acampada.css b/src/client/assets/themes/Activista.css similarity index 59% rename from src/client/assets/themes/Acampada.css rename to src/client/assets/themes/Activista.css index 3bd4c88..caea6f4 100644 --- a/src/client/assets/themes/Acampada.css +++ b/src/client/assets/themes/Activista.css @@ -1,32 +1,31 @@ -/* Tema Acampada (rama movil): blanco, negro y cian neon, titulos en Anton. +/* Skin Activista (rama movil): el unico del movil, lo mas simple posible. * - * Los colores y las formas salen de acampada26s.net: bordes negros gruesos, - * botones con sombra solida que se hunden al pulsar y titulos en mayusculas. + * Letra del sistema, titulos en negrita sin mayusculas, bordes de 1px, sin sombras y + * el boton principal relleno con el color de resalte. Los colores salen de las + * variables --a26-* (el nombre viene del skin Acampada, que se quito el 20/10/2026): + * aqui van los de serie y Colores en Ajustes los cambia (mobile_settings.js, hoja + * /mobile-colors.css, que va la ultima). * - * La maqueta de movil es styles/mobile_base.css, que main_views.js carga justo antes - * con su propio versionado (no con @import: se quedaba en la cache del - * WebView); aqui solo se cambian colores, letra y formas encima. Por eso casi todo lleva - * !important: la maqueta tambien lo usa. Las piezas propias de la rama - * (panal, barras) se ajustan en styles/hive_acampada.css. */ -@font-face { - font-family: "Anton"; - src: url("../fonts/Anton-Regular.ttf") format("truetype"); - font-display: swap; -} + * La maqueta de movil es styles/mobile_base.css, que main_views.js carga justo antes; + * aqui solo se cambian colores, letra y formas encima. Por eso casi todo lleva + * !important: la maqueta tambien lo usa. Las piezas propias de la rama (lanzador, + * barras) se ajustan en styles/activista.css. */ :root { --a26-bg: #fff; - --a26-fg: #000; + --a26-fg: #111; --a26-neon: #00e5ff; --a26-soft: #f2f2f2; --a26-muted: #555; --a26-on-neon: #000; --a26-tint: #e5fcff; - --a26-line: #ccc; - --a26-bd: 3px solid var(--a26-fg); - --a26-bd-fino: 2px solid var(--a26-fg); - --a26-radius: 14px; - --a26-tit: "Anton", Impact, "Arial Narrow", sans-serif; + --a26-line: #d6d6d6; + --a26-edge: #8a8a8a; + --a26-danger-text: #c4003b; + --a26-bd: 1px solid var(--a26-edge); + --a26-bd-fino: 1px solid var(--a26-edge); + --a26-radius: 12px; + --a26-tit: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; --a26-txt: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; color-scheme: light; } @@ -43,7 +42,7 @@ header, footer, .header, .main-content, .main-column, .sidebar-left, .sidebar-ri section, article, div, table, .profile { background-color: transparent !important; color: var(--a26-fg) !important; - border-color: var(--a26-fg) !important; + border-color: var(--a26-line) !important; } div { border: 0 !important; box-shadow: none !important; } @@ -52,25 +51,25 @@ span, p, li, td, th, label, small, strong, em, h4, h5, h6 { color: inherit !impo a { color: var(--a26-fg) !important; } a:hover, a:focus-visible { color: var(--a26-on-neon) !important; background-color: var(--a26-neon); } -/* --- titulos: Anton en mayusculas --- */ +/* --- titulos: negrita, sin mayusculas --- */ h1, h2, h3, .card-label, .tags-header h2, .oasis-nav-header { font-family: var(--a26-tit) !important; - font-weight: 400 !important; - text-transform: uppercase; - letter-spacing: .01em; - line-height: 1.05 !important; + font-weight: 700 !important; + text-transform: none !important; + letter-spacing: 0; + line-height: 1.2 !important; color: var(--a26-fg) !important; } -h1 { font-size: 34px !important; } -h2 { font-size: 28px !important; } -h3 { font-size: 21px !important; } -.card-label { font-size: 15px !important; } +h1 { font-size: 26px !important; } +h2 { font-size: 22px !important; } +h3 { font-size: 18px !important; } +.card-label { font-size: 14px !important; } -/* cabecera de cada modulo: titulo con raya gorda debajo, como los h2 de la web */ +/* cabecera de cada modulo: titulo y una raya fina debajo */ .tags-header { background: transparent !important; border: 0 !important; - border-bottom: var(--a26-bd) !important; + border-bottom: 1px solid var(--a26-line) !important; border-radius: 0 !important; padding: 0 0 8px !important; margin: 0 0 16px !important; @@ -78,64 +77,61 @@ h3 { font-size: 21px !important; } } .tags-header p { color: var(--a26-muted) !important; font-size: 15px !important; margin: 4px 0 0 !important; } -/* --- tarjetas: recuadro negro, sin sombras difusas --- */ +/* --- tarjetas: borde fino, sin sombra --- */ .main-column article, .main-column [class$="-card"], .main-column [class*="-card "], .main-column .card, .forum-comment, .feed-row { background: var(--a26-bg) !important; - border: var(--a26-bd-fino) !important; + border: 1px solid var(--a26-line) !important; border-radius: var(--a26-radius) !important; box-shadow: none !important; } .main-column > section { border: 0 !important; padding: 0 !important; } -/* --- botones: los .bt de acampada --- */ +/* --- botones: contorno fino; el elegido y el principal, rellenos --- */ button, input[type="submit"], input[type="button"], .filter-btn, .create-button, .buy-btn, .delete-btn, a.user-link, button.user-link, .date-link, .welcome-banner-start, .update-download-link { - font-family: var(--a26-tit) !important; - font-weight: 400 !important; - text-transform: uppercase; - letter-spacing: .02em; + font-family: var(--a26-txt) !important; + font-weight: 600 !important; + text-transform: none !important; + letter-spacing: 0; background: var(--a26-bg) !important; color: var(--a26-fg) !important; border: var(--a26-bd) !important; border-radius: var(--a26-radius) !important; - box-shadow: 0 5px 0 var(--a26-fg) !important; + box-shadow: none !important; text-decoration: none !important; - transition: transform .12s ease-out, box-shadow .12s ease-out; } button, input[type="submit"], input[type="button"], .filter-btn, .create-button { - min-height: 48px; + min-height: 44px; padding: 8px 16px !important; - font-size: 18px !important; + font-size: 16px !important; margin-bottom: 6px; } button:active, input[type="submit"]:active, input[type="button"]:active, -.filter-btn:active, .create-button:active, a.user-link:active { - transform: translateY(4px); - box-shadow: 0 1px 0 var(--a26-fg) !important; - background: var(--a26-neon) !important; color: var(--a26-on-neon) !important; +.filter-btn:active, a.user-link:active { + background: var(--a26-soft) !important; } -/* el boton elegido (filtros, pestañas): relleno cian */ .filter-btn.active, .mode-buttons .filter-btn.active, .mode-buttons-row .filter-btn.active, .tribe-mode-buttons .filter-btn.active, .filters .filter-btn.active, .activity-filter-grid .filter-btn.active, .activity-sub-filter .filter-btn.active, .bank-range-tabs .filter-btn.active { background: var(--a26-neon) !important; - border-color: var(--a26-fg) !important; + border-color: var(--a26-neon) !important; color: var(--a26-on-neon) !important; } -/* el boton de crear: negro, para que se vea primero */ +/* el principal de cada pantalla (crear, escribir): relleno con el resalte */ .create-button, .mode-buttons .create-button, .mode-buttons-row .create-button, .tribe-mode-buttons .create-button, .filters .create-button { - background: var(--a26-fg) !important; - color: var(--a26-bg) !important; - box-shadow: 0 5px 0 var(--a26-neon) !important; - font-size: 18px !important; + background: var(--a26-neon) !important; + border-color: var(--a26-neon) !important; + color: var(--a26-on-neon) !important; + font-size: 16px !important; padding: 8px 16px !important; min-height: 48px !important; } -.delete-btn { box-shadow: 0 5px 0 #e0004d !important; } +.create-button:active { filter: brightness(.92); } +.delete-btn { border-color: var(--a26-danger-text) !important; color: var(--a26-danger-text) !important; } /* --- formularios --- */ input, textarea, select { @@ -148,33 +144,34 @@ input, textarea, select { min-height: 44px; } input:focus, textarea:focus, select:focus { - outline: 3px solid var(--a26-neon) !important; + outline: 2px solid var(--a26-neon) !important; outline-offset: 1px; } -input[type="checkbox"], input[type="radio"] { min-height: 0; accent-color: var(--a26-fg); } +input[type="checkbox"], input[type="radio"] { min-height: 0; accent-color: var(--a26-neon); } /* --- tablas --- */ -table { border: var(--a26-bd-fino) !important; border-radius: 10px; overflow: hidden; } -table th { background: var(--a26-fg) !important; color: var(--a26-bg) !important; } -table td { border-color: var(--a26-fg) !important; } +table { border: 1px solid var(--a26-line) !important; border-radius: 10px; overflow: hidden; } +table th { background: var(--a26-soft) !important; color: var(--a26-fg) !important; } +table td { border-color: var(--a26-line) !important; } /* --- avisos de upstream (bienvenida, actualizaciones) --- */ .update-banner, .welcome-banner { - background: var(--a26-neon) !important; - color: var(--a26-on-neon) !important; - border: var(--a26-bd) !important; - border-radius: var(--a26-radius) !important; + background: var(--a26-tint) !important; + color: var(--a26-fg) !important; + border: 0 !important; + border-left: 4px solid var(--a26-neon) !important; + border-radius: 10px !important; margin: 8px 10px !important; } -.update-banner-link, .update-banner-text { color: var(--a26-on-neon) !important; } +.update-banner-link, .update-banner-text { color: var(--a26-fg) !important; } .welcome-banner-close-btn { - background: var(--a26-bg) !important; - box-shadow: none !important; - min-height: 40px; width: 40px; padding: 0 !important; + background: transparent !important; + border: 0 !important; + min-height: 44px; width: 44px; padding: 0 !important; } /* --- colores sueltos de OasisMobile que se quedaban en amarillo o naranja --- */ -.avatar, .article img, .post-preview img { border-color: var(--a26-fg) !important; } +.avatar, .article img, .post-preview img { border-color: var(--a26-line) !important; } .profile .name, .snh-invite-code, .stats-kpi-label, .stats-kpi-value, .carbon-bar-formula, a.hashtag, .chat-topic-title, .chat-topic-active .chat-topic-title, .chat-topic-meta, .chat-topic-last-author, .chat-workspace-details > .tribe-side .tribe-card-members { @@ -186,13 +183,8 @@ a.hashtag { text-decoration: underline !important; } /* --- pie --- */ .oasis-footer, .oasis-footer * { color: var(--a26-muted) !important; background: transparent !important; } -.oasis-footer { border-top: var(--a26-bd) !important; margin-top: 32px; } +.oasis-footer { border-top: 1px solid var(--a26-line) !important; margin-top: 32px; } .oasis-footer a { text-decoration: underline !important; } -::-webkit-scrollbar-thumb { background-color: var(--a26-fg) !important; } +::-webkit-scrollbar-thumb { background-color: var(--a26-edge) !important; } ::-webkit-scrollbar-track { background-color: var(--a26-soft) !important; } - -@media (prefers-reduced-motion: reduce) { - button, .filter-btn, .create-button { transition: none; } - button:active, .filter-btn:active, .create-button:active { transform: none; } -} diff --git a/src/client/assets/translations/i18n_fork.js b/src/client/assets/translations/i18n_fork.js index 4378fd5..8b1d33e 100644 --- a/src/client/assets/translations/i18n_fork.js +++ b/src/client/assets/translations/i18n_fork.js @@ -22,6 +22,21 @@ module.exports = { bbMenuClose: "Close", uxPickHint: "Tap one and it is saved. You can change it any time in Settings.", uxPickCurrent: "In use", + pubBarJoining: "Joining the pub… it can take up to a minute. Keep the app open.", + pubBarJoinFailed: "If you did not get into the pub, the usual reasons are: no connection (wifi or data), the pub is down, or the invite was already used. Try another pub from the list, or later.", + pubBarDownload: "Downloading the network from {pub} ({tier})…", + pubBarTierRecent: "the latest", + pubBarTierFull: "everything before", + pubBarRestore: "Saving to your phone: {pct}% · {n} messages", + pubBarHint: "You can keep using the app meanwhile.", + pubBarDone: "You are in {pub}: {n} messages downloaded.", + pubBarErrNoSnap: "You joined {pub}, but it offers no copy to download at once. Posts will arrive little by little while the app is open.", + pubBarErrNet: "Cannot reach {pub}. Check that you have Internet (wifi or data) and try again later; if it keeps failing, the pub may be down.", + pubBarErrOther: "The download from {pub} stopped ({error}). You are in; the rest will arrive little by little.", + pubBarClose: "Close", + pubBarBackupRestore: "Restoring your backup: {pct}% · {n} messages", + pubBarBackupDone: "Backup restored: {n} messages.", + pubBarBackupErr: "The backup could not be restored: {error}. Check the file and the password.", quickTitle: "The basics", quickHint: "What you chose when you started, one tap away.", quick_identity: "Identity", @@ -37,7 +52,6 @@ module.exports = { quickWriteGreeting: "Write a greeting", welcomeHintColors: "Background, text and highlight colour of the app.", colorsHint: "Tap a palette and it is applied.", - colorsSkinNote: "Colours go with the Acampada look; choosing one switches to it.", colorsCustom: "Your own", colorsBg: "Background", colorsFg: "Text", @@ -45,7 +59,7 @@ module.exports = { colorsApply: "Apply", colorsFixed: "The text could not be read on that background, so it was set to black or white.", colorsKeep: "Keep these", - palette_acampada: "Acampada", + palette_acampada: "Light", palette_noche: "Night", palette_oro: "Gold", palette_bosque: "Forest", @@ -144,6 +158,21 @@ module.exports = { bbMenuClose: "Cerrar", uxPickHint: "Toca uno y queda guardado. Puedes cambiarlo cuando quieras en Ajustes.", uxPickCurrent: "En uso", + pubBarJoining: "Uniéndote al pub… puede tardar hasta un minuto. No cierres la app.", + pubBarJoinFailed: "Si no has entrado en el pub, lo más habitual es: no hay conexión (wifi o datos), el pub está caído o la invitación ya se ha usado. Prueba con otro pub de la lista o más tarde.", + pubBarDownload: "Descargando la red de {pub} ({tier})…", + pubBarTierRecent: "lo más reciente", + pubBarTierFull: "todo lo anterior", + pubBarRestore: "Guardando en tu móvil: {pct} % · {n} mensajes", + pubBarHint: "Puedes seguir usando la app mientras tanto.", + pubBarDone: "Ya estás en {pub}: {n} mensajes descargados.", + pubBarErrNoSnap: "Te has unido a {pub}, pero no ofrece una copia para descargar de golpe. Lo que publique la gente irá llegando poco a poco mientras la app esté abierta.", + pubBarErrNet: "No se puede conectar con {pub}. Comprueba que tienes Internet (wifi o datos) y vuelve a probar en un rato; si sigue igual, el pub puede estar caído.", + pubBarErrOther: "La descarga desde {pub} se ha cortado ({error}). Ya estás dentro; lo demás llegará poco a poco.", + pubBarClose: "Cerrar", + pubBarBackupRestore: "Restaurando tu copia: {pct} % · {n} mensajes", + pubBarBackupDone: "Copia restaurada: {n} mensajes.", + pubBarBackupErr: "No se ha podido restaurar la copia: {error}. Revisa el archivo y la contraseña.", quickTitle: "Lo básico", quickHint: "Lo que elegiste al empezar, a un toque.", quick_identity: "Identidad", @@ -159,7 +188,6 @@ module.exports = { quickWriteGreeting: "Escribir el saludo", welcomeHintColors: "Fondo, texto y color de resalte de la app.", colorsHint: "Toca una paleta y se aplica.", - colorsSkinNote: "Los colores van con el aspecto Acampada; al elegir uno se pasa a ese aspecto.", colorsCustom: "A tu gusto", colorsBg: "Fondo", colorsFg: "Texto", @@ -167,7 +195,7 @@ module.exports = { colorsApply: "Aplicar", colorsFixed: "El texto no se leía sobre ese fondo, así que se ha puesto en negro o blanco.", colorsKeep: "Dejar estos", - palette_acampada: "Acampada", + palette_acampada: "Claro", palette_noche: "Noche", palette_oro: "Oro", palette_bosque: "Bosque", @@ -272,6 +300,21 @@ module.exports = { bbMenuClose: "Fermer", uxPickHint: "Touchez-en un et c'est enregistré. Vous pouvez le changer à tout moment dans les Paramètres.", uxPickCurrent: "Actuel", + pubBarJoining: "Connexion au pub… cela peut prendre jusqu'à une minute. Gardez l'app ouverte.", + pubBarJoinFailed: "Si vous n'êtes pas entré dans le pub, c'est souvent : pas de connexion (wifi ou données), pub hors service, ou invitation déjà utilisée. Essayez un autre pub de la liste, ou plus tard.", + pubBarDownload: "Téléchargement du réseau depuis {pub} ({tier})…", + pubBarTierRecent: "le plus récent", + pubBarTierFull: "tout le reste", + pubBarRestore: "Enregistrement sur votre téléphone : {pct} % · {n} messages", + pubBarHint: "Vous pouvez continuer à utiliser l'app.", + pubBarDone: "Vous êtes dans {pub} : {n} messages téléchargés.", + pubBarErrNoSnap: "Vous avez rejoint {pub}, mais il ne propose pas de copie à télécharger d'un coup. Les publications arriveront peu à peu tant que l'app est ouverte.", + pubBarErrNet: "Impossible de joindre {pub}. Vérifiez votre connexion (wifi ou données) et réessayez plus tard ; si cela continue, le pub est peut-être hors service.", + pubBarErrOther: "Le téléchargement depuis {pub} s'est interrompu ({error}). Vous êtes dedans ; le reste arrivera peu à peu.", + pubBarClose: "Fermer", + pubBarBackupRestore: "Restauration de votre sauvegarde : {pct} % · {n} messages", + pubBarBackupDone: "Sauvegarde restaurée : {n} messages.", + pubBarBackupErr: "Impossible de restaurer la sauvegarde : {error}. Vérifiez le fichier et le mot de passe.", quickTitle: "L'essentiel", quickHint: "Ce que vous avez choisi au début, à portée de doigt.", quick_identity: "Identité", @@ -287,7 +330,6 @@ module.exports = { quickWriteGreeting: "Écrire un salut", welcomeHintColors: "Fond, texte et couleur d'accent de l'application.", colorsHint: "Touchez une palette et elle s'applique.", - colorsSkinNote: "Les couleurs vont avec l'aspect Acampada ; en choisir une passe à cet aspect.", colorsCustom: "À votre goût", colorsBg: "Fond", colorsFg: "Texte", @@ -295,7 +337,7 @@ module.exports = { colorsApply: "Appliquer", colorsFixed: "Le texte n'était pas lisible sur ce fond, il a été mis en noir ou en blanc.", colorsKeep: "Garder celles-ci", - palette_acampada: "Acampada", + palette_acampada: "Clair", palette_noche: "Nuit", palette_oro: "Or", palette_bosque: "Forêt", @@ -394,6 +436,21 @@ module.exports = { bbMenuClose: "Itxi", uxPickHint: "Bat ukitu eta gordeta geratzen da. Nahi duzunean alda dezakezu Ezarpenetan.", uxPickCurrent: "Erabiltzen", + pubBarJoining: "Pubera sartzen… minutu bat arte iraun dezake. Ez itxi aplikazioa.", + pubBarJoinFailed: "Pubean sartu ez bazara, ohikoena hau da: ez dago konexiorik (wifia edo datuak), puba eroria dago edo gonbidapena erabilita dago. Saiatu zerrendako beste pub batekin edo geroago.", + pubBarDownload: "{pub}-ren sarea deskargatzen ({tier})…", + pubBarTierRecent: "berriena", + pubBarTierFull: "aurreko guztia", + pubBarRestore: "Mugikorrean gordetzen: % {pct} · {n} mezu", + pubBarHint: "Bitartean aplikazioa erabiltzen jarrai dezakezu.", + pubBarDone: "{pub}-n zaude: {n} mezu deskargatuta.", + pubBarErrNoSnap: "{pub}-ra batu zara, baina ez du kopiarik eskaintzen behin betiko deskargatzeko. Jendeak argitaratzen duena pixkanaka iritsiko da aplikazioa irekita dagoen bitartean.", + pubBarErrNet: "Ezin da {pub}-rekin konektatu. Egiaztatu Interneta duzula (wifia edo datuak) eta saiatu berriro geroago; berdin jarraitzen badu, puba eroria egon daiteke.", + pubBarErrOther: "{pub}-tik deskarga eten da ({error}). Barruan zaude; gainerakoa pixkanaka iritsiko da.", + pubBarClose: "Itxi", + pubBarBackupRestore: "Zure kopia leheneratzen: % {pct} · {n} mezu", + pubBarBackupDone: "Kopia leheneratuta: {n} mezu.", + pubBarBackupErr: "Ezin izan da kopia leheneratu: {error}. Begiratu fitxategia eta pasahitza.", quickTitle: "Oinarrizkoa", quickHint: "Hasieran aukeratu zenuena, ukitu batera.", quick_identity: "Nortasuna", @@ -409,7 +466,6 @@ module.exports = { quickWriteGreeting: "Agurra idatzi", welcomeHintColors: "Aplikazioaren hondoa, testua eta nabarmentze-kolorea.", colorsHint: "Ukitu paleta bat eta aplikatzen da.", - colorsSkinNote: "Koloreak Acampada itxurarekin doaz; bat aukeratzean itxura horretara pasatzen da.", colorsCustom: "Zure gustura", colorsBg: "Hondoa", colorsFg: "Testua", @@ -417,7 +473,7 @@ module.exports = { colorsApply: "Aplikatu", colorsFixed: "Testua ez zen irakurtzen hondo horretan, beltzez edo zuriz jarri da.", colorsKeep: "Hauek utzi", - palette_acampada: "Acampada", + palette_acampada: "Argia", palette_noche: "Gaua", palette_oro: "Urrea", palette_bosque: "Basoa", @@ -516,6 +572,21 @@ module.exports = { bbMenuClose: "Schließen", uxPickHint: "Tippe eins an und es ist gespeichert. Du kannst es jederzeit in den Einstellungen ändern.", uxPickCurrent: "Aktiv", + pubBarJoining: "Beitritt zum Pub… das kann bis zu einer Minute dauern. Lass die App offen.", + pubBarJoinFailed: "Wenn du nicht in den Pub gekommen bist, liegt es meist daran: keine Verbindung (WLAN oder Daten), der Pub ist down oder die Einladung wurde schon benutzt. Probier einen anderen Pub aus der Liste oder später.", + pubBarDownload: "Netzwerk wird von {pub} geladen ({tier})…", + pubBarTierRecent: "das Neueste", + pubBarTierFull: "alles davor", + pubBarRestore: "Wird auf dein Handy gespeichert: {pct} % · {n} Nachrichten", + pubBarHint: "Du kannst die App solange weiter benutzen.", + pubBarDone: "Du bist in {pub}: {n} Nachrichten geladen.", + pubBarErrNoSnap: "Du bist {pub} beigetreten, aber es bietet keine Kopie zum Herunterladen. Beiträge kommen nach und nach, solange die App offen ist.", + pubBarErrNet: "{pub} ist nicht erreichbar. Prüfe deine Internetverbindung (WLAN oder Daten) und versuch es später; wenn es so bleibt, ist der Pub vielleicht down.", + pubBarErrOther: "Der Download von {pub} wurde unterbrochen ({error}). Du bist drin; der Rest kommt nach und nach.", + pubBarClose: "Schließen", + pubBarBackupRestore: "Sicherung wird wiederhergestellt: {pct} % · {n} Nachrichten", + pubBarBackupDone: "Sicherung wiederhergestellt: {n} Nachrichten.", + pubBarBackupErr: "Die Sicherung konnte nicht wiederhergestellt werden: {error}. Prüfe Datei und Passwort.", quickTitle: "Das Wichtigste", quickHint: "Was du am Anfang gewählt hast, mit einem Tipp.", quick_identity: "Identität", @@ -531,7 +602,6 @@ module.exports = { quickWriteGreeting: "Gruß schreiben", welcomeHintColors: "Hintergrund, Text und Akzentfarbe der App.", colorsHint: "Tippe auf eine Palette und sie wird angewendet.", - colorsSkinNote: "Die Farben gehören zum Acampada-Design; mit einer Auswahl wechselst du dorthin.", colorsCustom: "Eigene", colorsBg: "Hintergrund", colorsFg: "Text", @@ -539,7 +609,7 @@ module.exports = { colorsApply: "Anwenden", colorsFixed: "Der Text war auf diesem Hintergrund nicht lesbar, daher wurde er schwarz oder weiß gesetzt.", colorsKeep: "Diese behalten", - palette_acampada: "Acampada", + palette_acampada: "Hell", palette_noche: "Nacht", palette_oro: "Gold", palette_bosque: "Wald", @@ -638,6 +708,21 @@ module.exports = { bbMenuClose: "Chiudi", uxPickHint: "Toccane uno ed è salvato. Puoi cambiarlo quando vuoi nelle Impostazioni.", uxPickCurrent: "In uso", + pubBarJoining: "Ti stai unendo al pub… può volerci fino a un minuto. Tieni l'app aperta.", + pubBarJoinFailed: "Se non sei entrato nel pub, di solito è perché: non c'è connessione (wifi o dati), il pub è giù o l'invito è già stato usato. Prova un altro pub della lista, o più tardi.", + pubBarDownload: "Scaricamento della rete da {pub} ({tier})…", + pubBarTierRecent: "il più recente", + pubBarTierFull: "tutto il resto", + pubBarRestore: "Salvataggio sul telefono: {pct} % · {n} messaggi", + pubBarHint: "Puoi continuare a usare l'app nel frattempo.", + pubBarDone: "Sei in {pub}: {n} messaggi scaricati.", + pubBarErrNoSnap: "Ti sei unito a {pub}, ma non offre una copia da scaricare in un colpo. I post arriveranno poco a poco mentre l'app è aperta.", + pubBarErrNet: "Impossibile raggiungere {pub}. Controlla di avere Internet (wifi o dati) e riprova più tardi; se continua, il pub potrebbe essere giù.", + pubBarErrOther: "Lo scaricamento da {pub} si è interrotto ({error}). Sei dentro; il resto arriverà poco a poco.", + pubBarClose: "Chiudi", + pubBarBackupRestore: "Ripristino del backup: {pct} % · {n} messaggi", + pubBarBackupDone: "Backup ripristinato: {n} messaggi.", + pubBarBackupErr: "Impossibile ripristinare il backup: {error}. Controlla file e password.", quickTitle: "L'essenziale", quickHint: "Quello che hai scelto all'inizio, a portata di tocco.", quick_identity: "Identità", @@ -653,7 +738,6 @@ module.exports = { quickWriteGreeting: "Scrivi un saluto", welcomeHintColors: "Sfondo, testo e colore di risalto dell'app.", colorsHint: "Tocca una tavolozza e viene applicata.", - colorsSkinNote: "I colori vanno con l'aspetto Acampada; sceglierne uno passa a quell'aspetto.", colorsCustom: "A modo tuo", colorsBg: "Sfondo", colorsFg: "Testo", @@ -661,7 +745,7 @@ module.exports = { colorsApply: "Applica", colorsFixed: "Il testo non si leggeva su quello sfondo, quindi è stato messo nero o bianco.", colorsKeep: "Tieni questi", - palette_acampada: "Acampada", + palette_acampada: "Chiaro", palette_noche: "Notte", palette_oro: "Oro", palette_bosque: "Bosco", @@ -760,6 +844,21 @@ module.exports = { bbMenuClose: "Fechar", uxPickHint: "Toque num e fica guardado. Pode mudá-lo quando quiser nas Definições.", uxPickCurrent: "Em uso", + pubBarJoining: "A entrar no pub… pode demorar até um minuto. Não feche a app.", + pubBarJoinFailed: "Se não entrou no pub, o mais habitual é: não há ligação (wifi ou dados), o pub está em baixo ou o convite já foi usado. Tente outro pub da lista, ou mais tarde.", + pubBarDownload: "A descarregar a rede de {pub} ({tier})…", + pubBarTierRecent: "o mais recente", + pubBarTierFull: "tudo o anterior", + pubBarRestore: "A guardar no telemóvel: {pct} % · {n} mensagens", + pubBarHint: "Pode continuar a usar a app entretanto.", + pubBarDone: "Está em {pub}: {n} mensagens descarregadas.", + pubBarErrNoSnap: "Entrou em {pub}, mas não oferece uma cópia para descarregar de uma vez. As publicações vão chegando aos poucos enquanto a app estiver aberta.", + pubBarErrNet: "Não é possível ligar a {pub}. Verifique se tem Internet (wifi ou dados) e tente mais tarde; se continuar, o pub pode estar em baixo.", + pubBarErrOther: "A descarga de {pub} parou ({error}). Já está dentro; o resto chegará aos poucos.", + pubBarClose: "Fechar", + pubBarBackupRestore: "A restaurar a cópia: {pct} % · {n} mensagens", + pubBarBackupDone: "Cópia restaurada: {n} mensagens.", + pubBarBackupErr: "Não foi possível restaurar a cópia: {error}. Verifique o ficheiro e a palavra-passe.", quickTitle: "O essencial", quickHint: "O que escolheu ao começar, a um toque.", quick_identity: "Identidade", @@ -775,7 +874,6 @@ module.exports = { quickWriteGreeting: "Escrever a saudação", welcomeHintColors: "Fundo, texto e cor de destaque da app.", colorsHint: "Toque numa paleta e ela aplica-se.", - colorsSkinNote: "As cores vão com o aspeto Acampada; escolher uma passa para esse aspeto.", colorsCustom: "À sua maneira", colorsBg: "Fundo", colorsFg: "Texto", @@ -783,7 +881,7 @@ module.exports = { colorsApply: "Aplicar", colorsFixed: "O texto não se lia nesse fundo, por isso ficou preto ou branco.", colorsKeep: "Manter estas", - palette_acampada: "Acampada", + palette_acampada: "Claro", palette_noche: "Noite", palette_oro: "Ouro", palette_bosque: "Floresta", @@ -882,6 +980,21 @@ module.exports = { bbMenuClose: "关闭", uxPickHint: "点一下即保存。随时可以在设置中更改。", uxPickCurrent: "使用中", + pubBarJoining: "正在加入 pub……最多可能需要一分钟。请不要关闭应用。", + pubBarJoinFailed: "如果没有进入 pub,常见原因是:没有网络(wifi 或流量)、pub 已停止服务,或邀请已被使用。请尝试列表中的其他 pub,或稍后再试。", + pubBarDownload: "正在从 {pub} 下载网络({tier})……", + pubBarTierRecent: "最新内容", + pubBarTierFull: "之前的全部", + pubBarRestore: "正在保存到手机:{pct}% · {n} 条消息", + pubBarHint: "在此期间你可以继续使用应用。", + pubBarDone: "你已加入 {pub}:已下载 {n} 条消息。", + pubBarErrNoSnap: "你已加入 {pub},但它不提供可一次下载的副本。应用打开时,内容会陆续到达。", + pubBarErrNet: "无法连接 {pub}。请检查网络(wifi 或流量),稍后再试;如果仍然失败,pub 可能已停止服务。", + pubBarErrOther: "从 {pub} 的下载中断了({error})。你已加入,其余内容会陆续到达。", + pubBarClose: "关闭", + pubBarBackupRestore: "正在恢复备份:{pct}% · {n} 条消息", + pubBarBackupDone: "备份已恢复:{n} 条消息。", + pubBarBackupErr: "无法恢复备份:{error}。请检查文件和密码。", quickTitle: "基本设置", quickHint: "开始时的选择,一点即达。", quick_identity: "身份", @@ -897,7 +1010,6 @@ module.exports = { quickWriteGreeting: "写问候", welcomeHintColors: "应用的背景、文字和强调色。", colorsHint: "点一个配色即可应用。", - colorsSkinNote: "颜色配合 Acampada 外观;选择后会切换到该外观。", colorsCustom: "自定义", colorsBg: "背景", colorsFg: "文字", @@ -905,7 +1017,7 @@ module.exports = { colorsApply: "应用", colorsFixed: "文字在该背景上看不清,已改为黑色或白色。", colorsKeep: "保留这些", - palette_acampada: "Acampada", + palette_acampada: "浅色", palette_noche: "夜晚", palette_oro: "金色", palette_bosque: "森林", @@ -1004,6 +1116,21 @@ module.exports = { bbMenuClose: "إغلاق", uxPickHint: "المس واحدًا ويُحفظ. يمكنك تغييره في أي وقت من الإعدادات.", uxPickCurrent: "قيد الاستخدام", + pubBarJoining: "جارٍ الانضمام إلى الـ pub… قد يستغرق ذلك دقيقة. أبقِ التطبيق مفتوحًا.", + pubBarJoinFailed: "إن لم تدخل إلى الـ pub، فالأسباب المعتادة: لا يوجد اتصال (واي فاي أو بيانات)، أو الـ pub متوقف، أو الدعوة استُخدمت من قبل. جرّب pub آخر من القائمة أو لاحقًا.", + pubBarDownload: "جارٍ تنزيل الشبكة من {pub} ({tier})…", + pubBarTierRecent: "الأحدث", + pubBarTierFull: "كل ما سبق", + pubBarRestore: "جارٍ الحفظ على هاتفك: {pct}% · {n} رسالة", + pubBarHint: "يمكنك متابعة استخدام التطبيق في هذه الأثناء.", + pubBarDone: "أنت في {pub}: تم تنزيل {n} رسالة.", + pubBarErrNoSnap: "انضممت إلى {pub}، لكنه لا يقدم نسخة للتنزيل دفعة واحدة. ستصل المنشورات تدريجيًا ما دام التطبيق مفتوحًا.", + pubBarErrNet: "تعذّر الاتصال بـ {pub}. تحقق من اتصالك بالإنترنت (واي فاي أو بيانات) وحاول لاحقًا؛ إن استمر الأمر فقد يكون الـ pub متوقفًا.", + pubBarErrOther: "توقف التنزيل من {pub} ({error}). أنت منضم؛ وسيصل الباقي تدريجيًا.", + pubBarClose: "إغلاق", + pubBarBackupRestore: "جارٍ استعادة النسخة الاحتياطية: {pct}% · {n} رسالة", + pubBarBackupDone: "تمت استعادة النسخة: {n} رسالة.", + pubBarBackupErr: "تعذّرت استعادة النسخة: {error}. تحقق من الملف وكلمة المرور.", quickTitle: "الأساسيات", quickHint: "ما اخترته في البداية، بلمسة واحدة.", quick_identity: "الهوية", @@ -1019,7 +1146,6 @@ module.exports = { quickWriteGreeting: "كتابة التحية", welcomeHintColors: "لون الخلفية والنص والتمييز في التطبيق.", colorsHint: "المس لوحة ألوان فتُطبَّق.", - colorsSkinNote: "الألوان تأتي مع مظهر Acampada؛ اختيار لون ينقلك إليه.", colorsCustom: "على ذوقك", colorsBg: "الخلفية", colorsFg: "النص", @@ -1027,7 +1153,7 @@ module.exports = { colorsApply: "تطبيق", colorsFixed: "لم يكن النص مقروءًا على تلك الخلفية، فجُعل أسود أو أبيض.", colorsKeep: "الإبقاء على هذه", - palette_acampada: "Acampada", + palette_acampada: "فاتح", palette_noche: "ليل", palette_oro: "ذهب", palette_bosque: "غابة", @@ -1126,6 +1252,21 @@ module.exports = { bbMenuClose: "बंद करें", uxPickHint: "एक पर टैप करें और वह सेव हो जाएगा। सेटिंग्स में इसे कभी भी बदल सकते हैं।", uxPickCurrent: "चालू", + pubBarJoining: "pub से जुड़ रहे हैं… इसमें एक मिनट तक लग सकता है। ऐप खुला रखें।", + pubBarJoinFailed: "अगर आप pub में नहीं जा पाए, तो आम वजहें हैं: कनेक्शन नहीं है (वाई-फ़ाई या डेटा), pub बंद है, या निमंत्रण पहले ही इस्तेमाल हो चुका है। सूची से कोई दूसरा pub आज़माएँ, या बाद में।", + pubBarDownload: "{pub} से नेटवर्क डाउनलोड हो रहा है ({tier})…", + pubBarTierRecent: "सबसे नया", + pubBarTierFull: "पहले का सब कुछ", + pubBarRestore: "आपके फ़ोन में सहेजा जा रहा है: {pct}% · {n} संदेश", + pubBarHint: "इस बीच आप ऐप का उपयोग जारी रख सकते हैं।", + pubBarDone: "आप {pub} में हैं: {n} संदेश डाउनलोड हुए।", + pubBarErrNoSnap: "आप {pub} से जुड़ गए, पर यह एक बार में डाउनलोड करने के लिए कॉपी नहीं देता। ऐप खुला रहने पर पोस्ट धीरे-धीरे आएँगी।", + pubBarErrNet: "{pub} से जुड़ नहीं पा रहे। जाँचें कि इंटरनेट (वाई-फ़ाई या डेटा) है और बाद में फिर कोशिश करें; अगर ऐसा ही रहे तो pub बंद हो सकता है।", + pubBarErrOther: "{pub} से डाउनलोड रुक गया ({error})। आप जुड़ चुके हैं; बाकी धीरे-धीरे आएगा।", + pubBarClose: "बंद करें", + pubBarBackupRestore: "आपका बैकअप बहाल हो रहा है: {pct}% · {n} संदेश", + pubBarBackupDone: "बैकअप बहाल हुआ: {n} संदेश।", + pubBarBackupErr: "बैकअप बहाल नहीं हो सका: {error}। फ़ाइल और पासवर्ड जाँचें।", quickTitle: "मूल बातें", quickHint: "शुरुआत में जो चुना था, एक टैप पर।", quick_identity: "पहचान", @@ -1141,7 +1282,6 @@ module.exports = { quickWriteGreeting: "अभिवादन लिखें", welcomeHintColors: "ऐप का पृष्ठभूमि, टेक्स्ट और हाइलाइट रंग।", colorsHint: "किसी पैलेट पर टैप करें और वह लागू हो जाएगा।", - colorsSkinNote: "रंग Acampada रूप के साथ चलते हैं; कोई चुनने पर वही रूप लग जाता है।", colorsCustom: "अपनी पसंद", colorsBg: "पृष्ठभूमि", colorsFg: "टेक्स्ट", @@ -1149,7 +1289,7 @@ module.exports = { colorsApply: "लागू करें", colorsFixed: "उस पृष्ठभूमि पर टेक्स्ट पढ़ा नहीं जा रहा था, इसलिए काला या सफ़ेद कर दिया गया।", colorsKeep: "इन्हें रखें", - palette_acampada: "Acampada", + palette_acampada: "हल्का", palette_noche: "रात", palette_oro: "सोना", palette_bosque: "जंगल", @@ -1248,6 +1388,21 @@ module.exports = { bbMenuClose: "Закрыть", uxPickHint: "Нажмите на вариант, и он сохранится. Изменить можно в любой момент в Настройках.", uxPickCurrent: "Сейчас", + pubBarJoining: "Подключение к пабу… это может занять до минуты. Не закрывайте приложение.", + pubBarJoinFailed: "Если не удалось войти в паб, обычно причина такая: нет связи (wifi или мобильные данные), паб недоступен или приглашение уже использовано. Попробуйте другой паб из списка или позже.", + pubBarDownload: "Загрузка сети с {pub} ({tier})…", + pubBarTierRecent: "самое новое", + pubBarTierFull: "всё остальное", + pubBarRestore: "Сохранение на телефон: {pct} % · {n} сообщений", + pubBarHint: "Пока можно пользоваться приложением.", + pubBarDone: "Вы в {pub}: загружено {n} сообщений.", + pubBarErrNoSnap: "Вы присоединились к {pub}, но он не даёт копию для загрузки целиком. Публикации будут приходить постепенно, пока приложение открыто.", + pubBarErrNet: "Не удаётся связаться с {pub}. Проверьте интернет (wifi или мобильные данные) и попробуйте позже; если не получится, паб может быть недоступен.", + pubBarErrOther: "Загрузка с {pub} прервалась ({error}). Вы уже внутри; остальное придёт постепенно.", + pubBarClose: "Закрыть", + pubBarBackupRestore: "Восстановление резервной копии: {pct} % · {n} сообщений", + pubBarBackupDone: "Копия восстановлена: {n} сообщений.", + pubBarBackupErr: "Не удалось восстановить копию: {error}. Проверьте файл и пароль.", quickTitle: "Основное", quickHint: "То, что вы выбрали в начале, в одно касание.", quick_identity: "Личность", @@ -1263,7 +1418,6 @@ module.exports = { quickWriteGreeting: "Написать приветствие", welcomeHintColors: "Фон, текст и цвет выделения в приложении.", colorsHint: "Нажмите на палитру, и она применится.", - colorsSkinNote: "Цвета идут с оформлением Acampada; при выборе включается оно.", colorsCustom: "Свои", colorsBg: "Фон", colorsFg: "Текст", @@ -1271,7 +1425,7 @@ module.exports = { colorsApply: "Применить", colorsFixed: "Текст не читался на этом фоне, поэтому он стал чёрным или белым.", colorsKeep: "Оставить эти", - palette_acampada: "Acampada", + palette_acampada: "Светлая", palette_noche: "Ночь", palette_oro: "Золото", palette_bosque: "Лес", diff --git a/src/client/public/js/mobile_pub.js b/src/client/public/js/mobile_pub.js new file mode 100644 index 0000000..b016884 --- /dev/null +++ b/src/client/public/js/mobile_pub.js @@ -0,0 +1,72 @@ +// Barra de unirse a un pub (ver views/mobile_pubbar.js). Sin este script la barra se +// ve igual al cargar cada pagina; con el, se pone al dia sola y sale nada mas tocar +// "unirse", sin recargar la pagina. +(function () { + var bar = document.getElementById('pub-bar'); + if (!bar || !window.fetch) return; + var text = bar.querySelector('.pub-bar-text'); + var hint = bar.querySelector('.pub-bar-hint'); + var meter = bar.querySelector('.pub-bar-meter'); + var timer = null; + + function show(s) { + if (!s || s.kind === 'none') { bar.hidden = true; document.body.classList.remove('has-pub-bar'); return; } + bar.hidden = false; + document.body.classList.add('has-pub-bar'); + bar.className = 'pub-bar is-' + s.kind; + bar.setAttribute('data-running', s.running ? '1' : '0'); + text.textContent = s.text || ''; + hint.textContent = s.hint || ''; + if (s.pct == null) meter.removeAttribute('value'); else meter.value = s.pct; + } + + function poll() { + fetch('/mobile/pub-status', { credentials: 'same-origin', cache: 'no-store' }) + .then(function (r) { return r.json(); }) + .then(function (s) { + show(s); + clearTimeout(timer); + if (s && s.running) timer = setTimeout(poll, 2000); + }) + .catch(function () { clearTimeout(timer); timer = setTimeout(poll, 5000); }); + } + + if (!bar.hidden) document.body.classList.add('has-pub-bar'); + if (bar.getAttribute('data-running') === '1') timer = setTimeout(poll, 2000); + + // despues de intentar unirse: si no ha arrancado ninguna descarga, explicar las + // causas habituales (el aviso del dev a veces dice "codigo no valido" cuando lo + // que pasa es que no hay red). Solo se mira nuestro estado, no la pagina del dev + var tried = 0; + try { tried = Number(sessionStorage.getItem('oasisPubJoin')) || 0; sessionStorage.removeItem('oasisPubJoin'); } catch (_) {} + if (tried && Date.now() - tried < 3 * 60 * 1000 && bar.hidden) { + fetch('/mobile/pub-status', { credentials: 'same-origin', cache: 'no-store' }) + .then(function (r) { return r.json(); }) + .then(function (s) { + if (s && s.kind !== 'none') { show(s); if (s.running) timer = setTimeout(poll, 2000); return; } + show({ kind: 'error', running: false, pct: null, text: bar.getAttribute('data-failed'), hint: '' }); + }) + .catch(function () {}); + } + + // al tocar unirse (Invitaciones, bienvenida, Ajustes, importar pubs): la barra sale ya. El + // dev tarda en contestar mientras conecta con el pub y la pantalla se quedaba quieta + document.addEventListener('submit', function (e) { + var f = e.target; + var action = (f && f.getAttribute('action')) || ''; + var by = e.submitter && e.submitter.value; + if (!/^\/(invites\/join|invites\/import-pubs|settings\/invite\/accept|welcome\/community)$/.test(action) || by === 'skip') return; + // en la bienvenida, solo si hay algun pub marcado (sin e.submitter en WebView viejos) + if (action === '/welcome/community' && !f.querySelector('input[name="pub"]:checked')) return; + try { sessionStorage.setItem('oasisPubJoin', String(Date.now())); } catch (_) {} + show({ kind: 'joining', running: true, pct: null, text: bar.getAttribute('data-joining'), hint: '' }); + }); + + // cerrar sin cambiar de pagina + var close = bar.querySelector('.pub-bar-close'); + if (close) close.addEventListener('submit', function (e) { + e.preventDefault(); + fetch(close.getAttribute('action'), { method: 'POST', credentials: 'same-origin' }).catch(function () {}); + show(null); + }); +})(); diff --git a/src/configs/oasis-config.json b/src/configs/oasis-config.json index 2ee9846..a0514d2 100644 --- a/src/configs/oasis-config.json +++ b/src/configs/oasis-config.json @@ -1,39 +1,39 @@ { "themes": { - "current": "Acampada" + "current": "Activista" }, "ux": { "current": "blocks" }, "modules": { "blogsMod": "on", - "pollsMod": "off", - "fediverseMod": "off", + "pollsMod": "on", + "fediverseMod": "on", "invitesMod": "on", "walletMod": "off", "devMod": "off", - "cipherMod": "off", - "bookmarksMod": "off", + "cipherMod": "on", + "bookmarksMod": "on", "videosMod": "on", - "docsMod": "off", - "audiosMod": "off", - "tagsMod": "off", + "docsMod": "on", + "audiosMod": "on", + "tagsMod": "on", "imagesMod": "on", - "trendingMod": "off", + "trendingMod": "on", "eventsMod": "on", - "tasksMod": "off", + "tasksMod": "on", "marketMod": "off", "votesMod": "on", "tribesMod": "on", - "reportsMod": "off", - "opinionsMod": "off", + "reportsMod": "on", + "opinionsMod": "on", "padsMod": "on", - "calendarsMod": "off", + "calendarsMod": "on", "transfersMod": "off", "feedMod": "on", "pixeliaMod": "off", - "melodyMod": "off", - "agendaMod": "off", + "melodyMod": "on", + "agendaMod": "on", "aiMod": "off", "aiNavMod": "off", "forumMod": "on", @@ -41,28 +41,28 @@ "housingMod": "off", "jobsMod": "off", "shopsMod": "off", - "projectsMod": "off", + "projectsMod": "on", "industryMod": "off", "bankingMod": "off", - "schoolMod": "off", + "schoolMod": "on", "parliamentMod": "on", - "courtsMod": "off", - "favoritesMod": "off", - "logsMod": "off", - "mapsMod": "off", + "courtsMod": "on", + "favoritesMod": "on", + "logsMod": "on", + "mapsMod": "on", "chatsMod": "on", - "torrentsMod": "off", - "filesMod": "off", + "torrentsMod": "on", + "filesMod": "on", "phoneMod": "on", "roomsMod": "on", - "graphosMod": "off", - "larpMod": "off", + "graphosMod": "on", + "larpMod": "on", "backupMod": "on", - "campaignsMod": "off", - "emergenciesMod": "off", - "logisticsMod": "off", - "mailingMod": "off", - "podcastsMod": "off", + "campaignsMod": "on", + "emergenciesMod": "on", + "logisticsMod": "on", + "mailingMod": "on", + "podcastsMod": "on", "wikiMod": "on" }, "wallet": { diff --git a/src/models/fork_workflows.js b/src/models/fork_workflows.js index 4bc97ec..eb99b79 100644 --- a/src/models/fork_workflows.js +++ b/src/models/fork_workflows.js @@ -14,7 +14,7 @@ const FORK_WORKFLOWS = [ // una acampada: hablar (chats, telefono y salas de voz del dev), escribir juntos, // compartir lo que pasa y decidir (tribus = comisiones, parlamento y votaciones) key: 'acampada', - theme: 'Acampada', + theme: 'Activista', homePage: 'activity', modules: ['chats', 'phone', 'rooms', 'pads', 'forum', 'images', 'videos', 'wiki', 'blogs', 'feed', 'invites', 'backup', 'tribes', 'parliament', 'votes', 'events'] diff --git a/src/models/workflows_model.js b/src/models/workflows_model.js index 81ba66f..fe390ed 100644 --- a/src/models/workflows_model.js +++ b/src/models/workflows_model.js @@ -82,7 +82,7 @@ const WORKFLOWS = [ }, { key: 'mobile', - theme: 'Acampada', + theme: 'OasisMobile', homePage: 'activity', modules: MOBILE_MODULES } @@ -91,13 +91,9 @@ const WORKFLOWS = [ const WORKFLOW_KEYS = WORKFLOWS.map(w => w.key); const isMobile = () => process.env.OASIS_MOBILE === '1'; -// en el movil el workflow no toca el skin: se queda el que haya elegido cada cual -// (Acampada u OasisMobile); si no hay ninguno de movil, Acampada -const MOBILE_THEMES = ['Acampada', 'OasisMobile']; -const mobileSkin = (cfg) => { - const cur = cfg && cfg.themes && cfg.themes.current; - return MOBILE_THEMES.includes(cur) ? cur : 'Acampada'; -}; +// en el movil el workflow no toca el skin: hay uno solo, Activista +const MOBILE_THEMES = ['Activista']; +const mobileSkin = () => 'Activista'; const themeOf = (workflow, cfg) => (isMobile() ? mobileSkin(cfg) : workflow.theme); const sameTheme = (theme, workflow) => (isMobile() ? MOBILE_THEMES.includes(theme) : theme === workflow.theme); diff --git a/src/views/backup_view.js b/src/views/backup_view.js index f3e401c..08399ed 100644 --- a/src/views/backup_view.js +++ b/src/views/backup_view.js @@ -146,7 +146,8 @@ exports.backupView = async ({ type = "RECOVERY", options = null, restoreJob = nu div({ class: "div-center backup-wrap" }, body) ) ); - return restoreJob && restoreJob.running ? html.replace('', '') : html; + // movil: sin recargar cada 5s; la barra del pub (mobile_pubbar.js) se pone al dia sola + return restoreJob && restoreJob.running && !require('./mobile_ui').isMobile() ? html.replace('', '') : html; }; exports.renderRestoreStatus = renderRestoreStatus; diff --git a/src/views/hive_views.js b/src/views/hive_views.js index 6a5421e..651f3d0 100644 --- a/src/views/hive_views.js +++ b/src/views/hive_views.js @@ -132,7 +132,10 @@ module.exports = ({ i18n }) => { renderHiveNav() ), // atras cierra la hoja (sin este script el menu funciona igual) - script({ src: '/js/mobile_menu.js', defer: 'defer' }) + script({ src: '/js/mobile_menu.js', defer: 'defer' }), + // unirse a un pub: barra fija encima de esta, al dia sin recargar (mobile_pubbar.js) + require('./mobile_pubbar').pubBar(), + script({ src: '/js/mobile_pub.js', defer: 'defer' }) ); }; diff --git a/src/views/main_views.js b/src/views/main_views.js index d70acf4..82ff836 100644 --- a/src/views/main_views.js +++ b/src/views/main_views.js @@ -441,17 +441,18 @@ exports.CONTENT_FAV_KIND = CONTENT_FAV_KIND; exports.CONTENT_SPREADABLE = CONTENT_SPREADABLE; exports.CONTENT_TORRENTABLE = CONTENT_TORRENTABLE; exports.contentDeleteAction = contentDeleteAction; -exports.THEMES = [['Acampada', 'Acampada'], ['Dark-SNH', 'Dark-SNH'], ['Clear-SNH', 'Clear-SNH'], ['Purple-SNH', 'Purple-SNH'], ['Matrix-SNH', 'Matrix-SNH'], ['OasisMobile', 'Oasis-Mobile']]; -// los dos skins de movil; los de escritorio no estan pensados para la maqueta del movil -exports.MOBILE_THEMES = [['Acampada', 'Acampada'], ['OasisMobile', 'Oasis Mobile']]; +exports.THEMES = [['Activista', 'Activista'], ['Dark-SNH', 'Dark-SNH'], ['Clear-SNH', 'Clear-SNH'], ['Purple-SNH', 'Purple-SNH'], ['Matrix-SNH', 'Matrix-SNH'], ['OasisMobile', 'Oasis-Mobile']]; +// el skin del movil (uno solo desde el 20/10/2026); los de escritorio no estan pensados +// para la maqueta del movil +exports.MOBILE_THEMES = [['Activista', 'Activista']]; exports.WIZARD_THEMES = process.env.OASIS_MOBILE === '1' ? exports.MOBILE_THEMES - : exports.THEMES.filter(([value]) => value !== 'OasisMobile' && value !== 'Acampada'); -// en el movil la maqueta de la rama (styles/mobile_base.css) va antes de cualquier skin; -// encima, Acampada.css o el OasisMobile.css del dev. hive_acampada.css va detras del -// tema Acampada: pinta lanzador, barras y tarjetas. -const acampadaBase = () => process.env.OASIS_MOBILE === '1' ? link({ rel: "stylesheet", href: `/assets/styles/mobile_base.css?v=${assetVersion()}` }) : null; -const acampadaLink = (theme) => theme === 'Acampada' ? link({ rel: "stylesheet", href: `/assets/styles/hive_acampada.css?v=${assetVersion()}` }) : null; + : exports.THEMES.filter(([value]) => value !== 'OasisMobile' && value !== 'Activista'); +// en el movil la maqueta de la rama (styles/mobile_base.css) va antes del skin; +// encima, themes/Activista.css y detras styles/activista.css, que pinta lanzador, +// barras y tarjetas. +const mobileBase = () => process.env.OASIS_MOBILE === '1' ? link({ rel: "stylesheet", href: `/assets/styles/mobile_base.css?v=${assetVersion()}` }) : null; +const skinLink = (theme) => theme === 'Activista' ? link({ rel: "stylesheet", href: `/assets/styles/activista.css?v=${assetVersion()}` }) : null; const renderDocumentActions = (kind, id, leadingNodes = []) => { const base = id ? `/${kind}/${encodeURIComponent(id)}` : `/${kind}`; @@ -2007,7 +2008,7 @@ const template = (titlePrefix, ...elements) => { // movil: el tema va DESPUES de mobile.css para ganar por cascada sin !important; // escritorio: orden original de epsylon ...(process.env.OASIS_MOBILE === '1' - ? [ link({ rel: "stylesheet", href: `/assets/styles/mobile.css?v=${assetVersion()}` }), acampadaBase(theme), themeLink, acampadaLink(theme) ] + ? [ link({ rel: "stylesheet", href: `/assets/styles/mobile.css?v=${assetVersion()}` }), mobileBase(theme), themeLink, skinLink(theme) ] : [ themeLink, link({ rel: "stylesheet", href: `/assets/styles/mobile.css?v=${assetVersion()}`, media: "(max-width: 768px)" }) ]), process.env.OASIS_MOBILE === '1' ? link({ rel: "stylesheet", href: `/assets/styles/mobile_ux.css?v=${assetVersion()}` }) : null, process.env.OASIS_MOBILE === '1' ? require('./mobile_ui').colorsLink() : null, @@ -2396,7 +2397,7 @@ exports.ainavHomeView = ({ recentTags = [] } = {}) => { // movil: el tema va DESPUES de mobile.css para ganar por cascada sin !important; // escritorio: orden original de epsylon ...(process.env.OASIS_MOBILE === '1' - ? [ link({ rel: "stylesheet", href: `/assets/styles/mobile.css?v=${assetVersion()}` }), acampadaBase(theme), link({ rel: "stylesheet", href: `/assets/themes/${theme}.css?v=${assetVersion()}` }), acampadaLink(theme) ] + ? [ link({ rel: "stylesheet", href: `/assets/styles/mobile.css?v=${assetVersion()}` }), mobileBase(theme), link({ rel: "stylesheet", href: `/assets/themes/${theme}.css?v=${assetVersion()}` }), skinLink(theme) ] : [ link({ rel: "stylesheet", href: `/assets/themes/${theme}.css?v=${assetVersion()}` }), link({ rel: "stylesheet", href: `/assets/styles/mobile.css?v=${assetVersion()}`, media: "(max-width: 768px)" }) ]), process.env.OASIS_MOBILE === '1' ? link({ rel: "stylesheet", href: `/assets/styles/mobile_ux.css?v=${assetVersion()}` }) : null, process.env.OASIS_MOBILE === '1' ? require('./mobile_ui').colorsLink() : null, diff --git a/src/views/mobile_pubbar.js b/src/views/mobile_pubbar.js new file mode 100644 index 0000000..141b1c6 --- /dev/null +++ b/src/views/mobile_pubbar.js @@ -0,0 +1,120 @@ +"use strict"; +// Barra de "uniendote a un pub" (rama movil). +// +// Al unirse a un pub, el dev acepta la invitacion y arranca una descarga de la red +// desde el pub (backupModel.startBootstrap: bajar la copia y guardarla en el movil). +// Su pantalla de bienvenida lo enseñaba recargandose entera cada 5 segundos y, si +// fallaba, solo decia el error en ingles. Aqui va una barra fija encima de la barra +// de abajo, en todas las pantallas, que dice en claro que esta pasando y por que +// falla. La pinta hive_views.js (la barra de abajo es nuestra) y la mantiene al dia +// public/js/mobile_pub.js preguntando a /mobile/pub-status, sin recargar. +// +// El estado lo lee de backupModel.restoreStatus(), que le pasa fork_routes.js al +// montarse (es una funcion del dev; no se cambia nada suyo). + +const { button, div, form, p, progress, span } = require("../server/node_modules/hyperaxe"); + +const t = () => require("./main_views").i18n; + +let source = null; +const setSource = (backupModel) => { source = backupModel; }; + +// lo terminado se enseña un rato y se puede cerrar +const SHOW_DONE_MS = 15 * 60 * 1000; +let dismissedAt = null; +const dismiss = () => { const j = source && source.restoreStatus(); dismissedAt = j && j.finishedAt; }; + +// "net:pub.example.org:8008~shs:..." -> "pub.example.org" +const hostOf = (address) => { + const m = String(address || "").match(/^[a-z]+:([^:~]+)(?::\d+)?/i); + return m ? m[1] : String(address || "").slice(0, 40); +}; + +const errorKind = (err) => { + const e = String(err || ""); + if (/snapshot not available/i.test(e)) return "nosnap"; + if (/ECONNREFUSED|ETIMEDOUT|ENOTFOUND|EHOSTUNREACH|ENETUNREACH|EAI_AGAIN|timed? ?out|socket|connect/i.test(e)) return "net"; + return "other"; +}; + +const fill = (s, vars) => String(s || "").replace(/\{(\w+)\}/g, (_, k) => (vars[k] != null ? String(vars[k]) : "")); +const num = (n) => Number(n || 0).toLocaleString("es-ES"); + +// el estado, ya en palabras: { kind, text, hint, pct, running } +const status = () => { + const job = source && typeof source.restoreStatus === "function" ? source.restoreStatus() : null; + if (!job) return null; + const i18n = t(); + if (job.source !== "snapshot") return backupStatus(job, i18n); + const pub = hostOf(job.address); + const pr = job.progress || {}; + if (job.running) { + const pct = Math.max(0, Math.min(100, Number(pr.percent) || 0)); + if (job.phase === "restore") { + return { kind: "restore", running: true, pct, + text: fill(i18n.pubBarRestore || "Saving to your phone: {pct}% · {n} messages", { pct: Math.round(pct), n: num(pr.messages) }), + hint: i18n.pubBarHint || "You can keep using the app meanwhile." }; + } + const tier = job.tier === "full" ? (i18n.pubBarTierFull || "everything before") : (i18n.pubBarTierRecent || "the latest"); + return { kind: "download", running: true, pct: null, + text: fill(i18n.pubBarDownload || "Downloading the network from {pub} ({tier})…", { pub, tier }), + hint: i18n.pubBarHint || "You can keep using the app meanwhile." }; + } + if (job.finishedAt && job.finishedAt === dismissedAt) return null; + if (job.finishedAt && Date.now() - Date.parse(job.finishedAt) > SHOW_DONE_MS) return null; + if (job.error) { + const k = errorKind(job.error); + const key = { nosnap: "pubBarErrNoSnap", net: "pubBarErrNet", other: "pubBarErrOther" }[k]; + const fallback = { + nosnap: "You joined {pub}, but it offers no copy to download at once. Posts will arrive little by little while the app is open.", + net: "Cannot reach {pub}. Check that you have Internet (wifi or data) and try again later; if it keeps failing, the pub may be down.", + other: "The download from {pub} stopped ({error}). You are in; the rest will arrive little by little." + }[k]; + return { kind: k === "nosnap" ? "warn" : "error", running: false, pct: null, + text: fill(i18n[key] || fallback, { pub, error: String(job.error).slice(0, 80) }), hint: "" }; + } + const r = job.result || {}; + return { kind: "done", running: false, pct: 100, + text: fill(i18n.pubBarDone || "You are in {pub}: {n} messages downloaded.", { pub, n: num(r.messages) }), hint: "" }; +}; + +// restaurar una copia desde archivo (pantalla de Copias): la misma barra +const backupStatus = (job, i18n) => { + const pr = job.progress || {}; + if (job.running) { + const pct = Math.max(0, Math.min(100, Number(pr.percent) || 0)); + return { kind: "restore", running: true, pct, + text: fill(i18n.pubBarBackupRestore || "Restoring your backup: {pct}% · {n} messages", { pct: Math.round(pct), n: num(pr.messages) }), + hint: i18n.pubBarHint || "You can keep using the app meanwhile." }; + } + if (job.finishedAt && job.finishedAt === dismissedAt) return null; + if (job.finishedAt && Date.now() - Date.parse(job.finishedAt) > SHOW_DONE_MS) return null; + if (job.error) return { kind: "error", running: false, pct: null, hint: "", + text: fill(i18n.pubBarBackupErr || "The backup could not be restored: {error}. Check the file and the password.", { error: String(job.error).slice(0, 80) }) }; + return { kind: "done", running: false, pct: 100, hint: "", + text: fill(i18n.pubBarBackupDone || "Backup restored: {n} messages.", { n: num((job.result || {}).messages) }) }; +}; + +// la barra; siempre se pinta (oculta si no hay nada) para que el script la enseñe +// al tocar "unirse", antes de que conteste el dev +const pubBar = () => { + const i18n = t(); + const s = status(); + return div({ + id: "pub-bar", class: `pub-bar${s ? ` is-${s.kind}` : ""}`, role: "status", "aria-live": "polite", + "data-running": s && s.running ? "1" : "0", + "data-joining": i18n.pubBarJoining || "Joining the pub… it can take up to a minute. Keep the app open.", + "data-failed": i18n.pubBarJoinFailed || "If you did not get into the pub, the usual reasons are: no connection (wifi or data), the pub is down, or the invite was already used. Try another pub from the list, or later.", + ...(s ? {} : { hidden: true }) + }, + div({ class: "pub-bar-body" }, + p({ class: "pub-bar-text" }, s ? s.text : ""), + p({ class: "pub-bar-hint" }, s ? s.hint : ""), + progress({ class: "pub-bar-meter", max: "100", ...(s && s.pct != null ? { value: String(s.pct) } : {}) }) + ), + form({ method: "POST", action: "/mobile/pub-status/dismiss", class: "pub-bar-close" }, + button({ type: "submit", "aria-label": i18n.pubBarClose || "Close" }, "✕")) + ); +}; + +module.exports = { setSource, status, dismiss, pubBar, errorKind, hostOf }; diff --git a/src/views/mobile_settings.js b/src/views/mobile_settings.js index b15ccca..d5864e3 100644 --- a/src/views/mobile_settings.js +++ b/src/views/mobile_settings.js @@ -10,8 +10,8 @@ // /feed/create): no se reenvia nada a sus rutas ni se toca lo que contestan. Colores // es propio: /settings/colors y la hoja /mobile-colors.css, que va la ultima. // -// Los colores son las variables --a26-* del tema Acampada; por eso al elegir colores -// el aspecto pasa a Acampada (OasisMobile.css es del dev y lleva los suyos fijos). +// Los colores son las variables --a26-* del skin Activista, el unico del movil: el +// skin quita lo que sobra y los colores lo dejan a gusto de cada cual. const crypto = require("crypto"); const { a, button, details, div, form, img, input, label, p, section, span, summary, h2 } = require("../server/node_modules/hyperaxe"); @@ -22,7 +22,7 @@ const cfgNow = () => { try { return require("../configs/config-manager.js").getC // ---------- colores ---------- -// [clave, fondo, texto, acento]; la primera es la de Acampada tal cual +// [clave, fondo, texto, acento]; la primera es la de serie const PALETTES = [ ["acampada", "#ffffff", "#000000", "#00e5ff"], ["noche", "#101114", "#f2f2f2", "#00e5ff"], @@ -47,7 +47,7 @@ const contrast = (x, y) => { const p = lum(x), q = lum(y); return (Math.max(p, q // negro o blanco, el que mas se lea encima const readableOn = (hex) => contrast(hex, "#000000") >= contrast(hex, "#ffffff") ? "#000000" : "#ffffff"; -// lo guardado, ya comprobado; sin nada guardado, Acampada +// lo guardado, ya comprobado; sin nada guardado, la de serie const colorState = (cfg) => { const c = (cfg || cfgNow()).mobileColors; if (!c || typeof c !== "object" || ![c.bg, c.fg, c.accent].every(v => HEX.test(String(v || "")))) { @@ -79,15 +79,16 @@ const PREVIEW_CSS = PALETTES.map(([k, bg, fg, ac]) => // muestras de las paletas (la CSP no deja estilos en linea) const colorsCss = (cfg) => { const c = colorState(cfg); - // solo con Acampada: OasisMobile.css es del dev y lleva su paleta fija - const onAcampada = ((cfg || cfgNow()).themes || {}).current === "Acampada"; - const vars = c.chosen && onAcampada ? [ + const vars = c.chosen ? [ `--a26-bg:${c.bg}`, `--a26-fg:${c.fg}`, `--a26-neon:${c.accent}`, `--a26-on-neon:${readableOn(c.accent)}`, `--a26-soft:${mix(c.bg, c.fg, 0.06)}`, `--a26-muted:${mix(c.fg, c.bg, 0.35)}`, `--a26-tint:${mix(c.bg, c.accent, 0.2)}`, - `--a26-line:${mix(c.bg, c.fg, 0.25)}`, + `--a26-line:${mix(c.bg, c.fg, 0.18)}`, + `--a26-edge:${mix(c.bg, c.fg, 0.5)}`, + // rojo de texto (borrar): el de serie si se lee sobre el fondo; si no, uno claro + `--a26-danger-text:${contrast(c.bg, "#c4003b") >= 4.5 ? "#c4003b" : "#ff6b86"}`, `color-scheme:${lum(c.bg) < 0.2 ? "dark" : "light"}` ] : []; const custom = `.pal-custom .pal-bg{background:${c.bg}!important}.pal-custom .pal-fg{background:${c.fg}!important}.pal-custom .pal-ac{background:${c.accent}!important}`; @@ -104,12 +105,10 @@ const colorsPick = (returnTo, withKeep) => { const i18n = t(); const c = colorState(); const cur = c.chosen ? c.preset : DEFAULT[0]; - const theme = (cfgNow().themes || {}).current; return div({ class: "color-pick" }, form({ method: "POST", action: "/settings/colors", class: "ux-pick" }, input({ type: "hidden", name: "returnTo", value: returnTo }), p({ class: "ux-pick-hint" }, i18n.colorsHint || "Tap a palette and it is applied."), - theme && theme !== "Acampada" ? p({ class: "form-hint" }, i18n.colorsSkinNote || "Colours go with the Acampada look; choosing one switches to it.") : null, div({ class: "pal-grid" }, ...PALETTES.map(([k]) => button(Object.assign({ type: "submit", name: "preset", value: k, class: k === cur ? "pal-card is-current" : "pal-card" }, k === cur ? { "aria-current": "true" } : {}), swatch(k), @@ -220,7 +219,7 @@ const greetingSection = () => { // recibe las secciones de settings_view (pestaña Oasis) y devuelve la lista del movil: // la rejilla arriba, los bloques nuevos metidos en su sitio y el orden de los accesos -const ORDER = ["quick", "update", "language", "identity", "workflows", "ux", "colors", "theme", "community", "greeting", "home-page"]; +const ORDER = ["quick", "update", "language", "identity", "workflows", "ux", "colors", "community", "greeting", "home-page"]; const withQuick = (parts) => { const cfg = cfgNow(); const mods = cfg.modules || {}; @@ -230,7 +229,8 @@ const withQuick = (parts) => { mods.invitesMod !== "off" ? communitySection() : null, mods.feedMod !== "off" ? greetingSection() : null ].filter(Boolean); - const list = parts.filter(Boolean).concat(extra); + // el selector de skin no va: en el movil hay uno solo (Activista) + const list = parts.filter(el => el && el.id !== "theme").concat(extra); const ids = list.map(el => el && el.id); list.push(quickGrid(cfg, ids)); const rank = (el) => { const i = ORDER.indexOf(el && el.id); return i === -1 ? ORDER.length : i; }; diff --git a/src/views/mobile_ui.js b/src/views/mobile_ui.js index 777669d..fdd6373 100644 --- a/src/views/mobile_ui.js +++ b/src/views/mobile_ui.js @@ -153,7 +153,7 @@ const uxPick = (action, cfg, withHint) => { }; // perfil de uso (workflow): igual, un boton por opcion con una linea de para que -// sirve. Activismo y Acampada primero; el resto en el orden del dev +// sirve. Activismo (el de serie) y Acampada primero; el resto en el orden del dev const WORKFLOW_FIRST = ['activists', 'acampada']; const workflowPick = (action, current, keys, withHint) => { const i18n = t(); diff --git a/src/views/welcome_view.js b/src/views/welcome_view.js index 808fa10..32ba75d 100644 --- a/src/views/welcome_view.js +++ b/src/views/welcome_view.js @@ -149,5 +149,6 @@ exports.welcomeView = async (status, profile = {}) => { button({ type: "submit", class: "welcome-skip-btn" }, i18n.welcomeSkip))) : null ) - return status.bootstrap && status.bootstrap.running ? html.replace('', '') : html + // movil: sin recargar cada 5s; la barra del pub (mobile_pubbar.js) se pone al dia sola + return status.bootstrap && status.bootstrap.running && !mobileUi.isMobile() ? html.replace('', '') : html }