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
}