skin Activista, colores en todo y barra de unirse a un pub

Skin: uno solo en el movil, Activista, lo mas simple posible: letra del sistema,
titulos en negrita sin mayusculas, bordes de 1px, sin sombras ni botones que se
hunden; el principal con el color de resalte. Sale de Acampada (themes/Activista.css
y styles/activista.css); fuera Acampada y OasisMobile del selector, la fuente Anton y
las miniaturas. Quien tuviera otro skin pasa a Activista al abrir. Los colores se
aplican siempre; rojo de texto que se lee en las paletas oscuras.

Perfil de uso: Activismo por defecto en la config de serie.

Pub: barra fija encima de la barra de abajo mientras se une y descarga la red, con
la fase, el porcentaje y, si falla, por que (sin red o pub caido, pub sin copia, otro
corte). Se pone al dia sin recargar (public/js/mobile_pub.js y /mobile/pub-status);
sale al tocar unirse y, si no arranca nada, explica las causas habituales. Fuera la
recarga cada 5s de la bienvenida y de Copias en el movil. backend.js pasa
backupModel a fork_routes.js en la linea que lo monta.

Actividad: fichas de filtro en rejilla de tres y botones de cada tarjeta en una fila.
Enlaces web de 24px.
This commit is contained in:
hacklab 2026-10-20 14:23:26 +02:00
parent b277f8e1cf
commit b84a8856dc
25 changed files with 702 additions and 371 deletions

View file

@ -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.

View file

@ -242,7 +242,7 @@ table.tight td { padding: 1.1mm 2mm; }
<rect x="0" y="0" width="330" height="34" rx="6" fill="#f1f4f5" stroke="#0b0b0b"/><text x="12" y="22">1 · style.css y mobile.css (tuyas)</text>
<rect x="0" y="40" width="330" height="34" rx="6" fill="#e2fbff" stroke="#0b0b0b"/><text x="12" y="62">2 · mobile_base.css: maqueta de la rama</text>
<rect x="0" y="80" width="330" height="34" rx="6" fill="#fff" stroke="#0b0b0b"/><text x="12" y="102">3 · skin: Acampada.css o tu OasisMobile.css</text>
<rect x="0" y="120" width="330" height="34" rx="6" fill="#e2fbff" stroke="#0b0b0b"/><text x="12" y="142">4 · hive_acampada.css (solo con Acampada)</text>
<rect x="0" y="120" width="330" height="34" rx="6" fill="#e2fbff" stroke="#0b0b0b"/><text x="12" y="142">4 · activista.css (solo con Acampada)</text>
<rect x="0" y="160" width="330" height="34" rx="6" fill="#0b0b0b"/><text x="12" y="182" fill="#fff">5 · mobile_ux.css: piezas comunes, la última</text>
</g>
</svg>

View file

@ -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;

View file

@ -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';

View file

@ -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)

View file

@ -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.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

View file

@ -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; }

View file

@ -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;

View file

@ -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; }

View file

@ -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 <link> 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; }
}

View file

@ -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: "Лес",

View file

@ -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);
});
})();

View file

@ -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": {

View file

@ -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']

View file

@ -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);

View file

@ -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('</head>', '<meta http-equiv="refresh" content="5"></head>') : 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('</head>', '<meta http-equiv="refresh" content="5"></head>') : html;
};
exports.renderRestoreStatus = renderRestoreStatus;

View file

@ -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' })
);
};

View file

@ -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,

120
src/views/mobile_pubbar.js Normal file
View file

@ -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 };

View file

@ -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; };

View file

@ -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();

View file

@ -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('</head>', '<meta http-equiv="refresh" content="5"></head>') : 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('</head>', '<meta http-equiv="refresh" content="5"></head>') : html
}