menu sin JS, iconos propios en vez de emoji, chats y tribus mas usables, README al dia

La 1.3.5 manda script-src 'none': fuera mobile_menu.js y mobile_pub.js. La hoja del
menu se abre con el ancla #oasis-menu y :target, y el gesto atras la cierra sin
cambiar de pantalla. La barra del pub solo sale si hay algo que contar.

- iconos de linea propios (images/mobile-ico-*.svg, con mask) en los avisos de
  version y de sugerencia, las fichas de visibilidad y Compartir, en vez de emoji
- avisos de arriba en una linea cada uno
- chats: Responder es un boton con su nombre y Enviar ocupa el ancho; lo mismo en la
  respuesta del foro
- tribus: ancla en las pestañas, todas en mayusculas, etiquetas y votos del foro de 44px
- comentarios abiertos con la opcion del dev (setCommentsOpen)
- Publicar al primer sitio de escribir encendido
- buscador con "mas opciones" de la 1.3.5 tambien en form.filter-box
- zonas tactiles y letra que quedaban cortas en fichas, pager, hora de comentario
- README con capturas nuevas: actividad y menu de serie, perfil Activista, tribu,
  blog y ajustes; portada con los colores de serie
- guia: sin JS desde la 1.3.5, la hoja con :target y los iconos mobile-*.svg
This commit is contained in:
hacklab 2026-10-23 20:08:45 +02:00
parent 389c84b93d
commit 6f0e45ed1d
46 changed files with 417 additions and 265 deletions

View file

@ -46,7 +46,7 @@ Dónde va cada cosa:
|---|---|
| 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` |
| La barra de unirse a un pub | `views/mobile_pubbar.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` |
@ -81,7 +81,8 @@ Cómo seguir modificando, paso a paso:
es una regla del modelo, así que se explica en pantalla y punto.
- `src/server/` (arranque de SSB, `ssb_config.js`, `package.json`).
- `src/backend/request_guards.js`, que lleva la CSP. Nada de `style=""` ni de
`<script>` en la página.
`<script>` en la página. Desde la 1.3.5 es `script-src 'none'`: tampoco valen
ficheros `.js` propios. Lo que haga falta, con enlaces, formularios y CSS.
- Las traducciones del dev, `translations/oasis_*.js`. Se reescriben casi en cada
versión.
- `styles/style.css` y `styles/mobile.css`.
@ -131,9 +132,10 @@ tiene o lo está haciendo.
## Navegación
Al abrir se ve la actividad. Los módulos están en la quinta pestaña de abajo, Menú,
que abre una hoja con el lanzador (`hive_views.js`: una casilla y CSS). Un script
mínimo, `public/js/mobile_menu.js`, hace que el botón atrás de Android cierre la hoja;
sin él el menú funciona igual. La barra tiene cuatro atajos más Menú. Cada perfil trae los suyos (`BARS` en
que abre una hoja con el lanzador (`hive_views.js`). La abre el ancla `#oasis-menu`
y la pinta `:target`: abrirla deja una entrada en el historial, así que el gesto atrás
la cierra sin cambiar de pantalla. La X, tocar fuera o volver a tocar Menú van a
`#menu-cerrado`, un ancla que no existe, para que la página no salte arriba. La barra tiene cuatro atajos más Menú. Cada perfil trae los suyos (`BARS` en
`fork_workflows.js`) y quien la cambia desde «Tu barra» la cambia solo para ese perfil
(`bottomBarBySkin` en la config; sin perfil reconocido, `bottomBarPins`). Por qué
así: `docs/ESTUDIO_NAVEGACION.md`. Si se cambia, que sea con una prueba que lo
@ -152,9 +154,9 @@ Aquí se puede hacer lo que haga falta.
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
- `src/views/mobile_pubbar.js`: la barra fija de unirse a un pub (y de restaurar una
copia), encima de la barra de abajo. Solo sale si hay algo que contar y se pone al
día al cambiar de pantalla (sin JS); 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.
@ -169,7 +171,9 @@ Aquí se puede hacer lo que haga falta.
- `src/views/mobile_icons.js`: los iconos.
- `src/backend/fork_routes.js`: nuestro router. Va justo delante del del dev.
- `src/backend/mobile_layer.js`: lo que nuestra interfaz necesita del backend
(avatar, filtro y lanzador de la barra de arriba). La monta `fork_routes.js`.
(avatar, filtro y lanzador de la barra de arriba). La monta `fork_routes.js`. También
deja abiertos los comentarios de las fichas con la opción del dev
(`comments_view.setCommentsOpen`), salvo en el parlamento.
- `src/models/fork_workflows.js`: nuestros workflows, que en el móvil hacen de skins:
cada uno enciende unos módulos y trae su barra de abajo. Activista (acción directa,
6 módulos) es el de serie, después Acampada (16) y los del dev; en Ajustes y en la
@ -179,6 +183,13 @@ Aquí se puede hacer lo que haga falta.
puesto (le pasa a Movimiento, el `activists` del dev, y a Por defecto).
- `src/client/assets/translations/i18n_fork.js`: nuestros textos.
- `src/client/assets/images/ux-*.svg`: nuestras imágenes.
- `src/client/assets/images/mobile-*.svg`: iconos de línea que `mobile_ux.css` pinta
con `mask` en sitios del dev que llevan emoji o un carácter (avisos de arriba, fichas
de visibilidad, Compartir, estado del teléfono, responder y enviar en chats). Se
esconde el carácter con `font-size: 0` y el icono toma el color del texto. Mismo
trazo para todos: 24x24, línea de 2, puntas redondas. Los del menú y la barra de
abajo están en `views/mobile_icons.js` y no se tocan desde aquí. Nada de emoji ni de
logotipos (tampoco el robot de las sugerencias del dev).
- `android/scripts/sobre_oficial.sh`: monta la APK sobre la oficial del dev. El resto
de `android/` (el proyecto Gradle propio) queda como estaba, sin uso.
- `android/icono/`: el icono adaptativo (la isla del logo de Oasis sobre su fondo).

View file

@ -18,24 +18,35 @@ está qué se puede tocar y qué no. En corto: el backend, el middleware, los mo
las traducciones son de epsylon y no se tocan; lo nuestro va en hojas, vistas y rutas
propias.
Versión base: Oasis 1.3.3 · Rama: `PRUEBAS` ·
Versión base: Oasis 1.3.5 · Rama: `PRUEBAS` ·
[Documentación completa](https://gitea.laenre.net/hacklab/OASIS_MOBILE/wiki) ·
[Versión de escritorio](https://gitea.laenre.net/hacklab/OASIS_LINUX)
![Oasis Mobile: lanzador, filtros y barra de atajos](docs/capturas/00-portada.png)
![Oasis Mobile: actividad, menú con los perfiles Mínimo y Activista, y una tribu](docs/capturas/00-portada.png)
[⬇ Descargar la APK](https://gitea.laenre.net/hacklab/OASIS_MOBILE/releases) ·
Android 7 o superior, 64 bits · se instala junto al Oasis oficial sin pisarlo
### Novedades de esta versión
- Al día con Oasis 1.3.3: karma en el perfil, salas con directo, fotos selladas en
chats privados y tribus, visibilidad de menciones y las traducciones nuevas.
- Arranca en modo acampada: chats, teléfono, salas, pads, foro, wiki, tribus,
parlamento, votaciones y eventos. El resto se enciende en *Módulos* o en
*Ajustes → Workflows*.
- Las llamadas son las de Oasis. Fuera Karvan, el módulo propio de salas: ahora se usan
el Teléfono y las Salas del dev, con su levantar la mano, grabación y emisión.
- Al día con Oasis 1.3.5: cabecera por módulo, buscador con más opciones, avisos de
versión y sugerencias, y su CSP sin JavaScript en la página.
- Arranca con el perfil Mínimo: chats, teléfono, eventos, emergencias, invitaciones y
copia de seguridad. Encima van Acampada (16 módulos), Activista (24) y los del dev,
de menos a más, cada uno con su barra de abajo. Se cambia en *Ajustes → Perfil*.
- Un solo skin, Activista: letra del sistema, bordes finos y sin sombras. Los colores
de serie son los de la APK oficial (negro y oro) y se cambian en *Ajustes → Colores*.
- Menú sin JavaScript: la hoja se abre con un ancla y el gesto atrás la cierra sin
cambiar de pantalla.
- Tribus: las pestañas se quedan a la vista al cambiar de sección, todas en el mismo
estilo, y los botones de la columna (unirse, invitar, suscribirse, etiquetas) de
44 px.
- Responder en los chats es un botón con su nombre, no una flecha de 27 px junto a la
hora; Enviar ocupa el ancho. Los comentarios salen abiertos, con la caja a la vista.
- Iconos de línea propios en vez de los emoji del dev en los avisos, las fichas de
visibilidad (pública, mutuos, cifrada, licencia, tribu) y Compartir.
- Publicar lleva al primer sitio de escribir que esté encendido (blog, feed, foro o
wiki); antes, sin blogs, acababa en Módulos.
- La APK se monta sobre la oficial: se coge la del dev, se le cambia el backend por el
de la rama y se le da otro nombre de paquete. Así trae todo lo nativo del dev (el
puente de audio y cámara, las notificaciones de llamada) y convive con la oficial.
@ -64,15 +75,15 @@ Android 7 o superior, 64 bits · se instala junto al Oasis oficial sin pisarlo
| | Oasis oficial | Esta rama |
|---|---|---|
| Llamadas y salas | Teléfono y Salas, con micrófono y cámara | Los mismos, tal cual |
| Interfaz de móvil | El menú de escritorio en una pantalla pequeña | Tema Acampada, lanzador con iconos, fichas, barra de cuatro atajos |
| Primer arranque | Casi sesenta módulos encendidos | Modo acampada: lo justo para organizarse |
| Interfaz de móvil | El menú de escritorio en una pantalla pequeña | Skin Activista, actividad al abrir, hoja de Menú con iconos, barra de cuatro atajos |
| Primer arranque | Casi sesenta módulos encendidos | Perfil Mínimo (6 módulos) y una escalera de perfiles hasta los del dev |
| Votaciones | Seis opciones fijas en una tabla | Sí/No, semáforo o termómetro, con barras |
| Varias identidades | Una por instalación | Varias en el mismo móvil, con aviso si dos comparten clave |
| App Android | La del dev | La del dev, con el backend de la rama y paquete propio (`net.laenre.oasis`) |
| Metadatos de las fotos | Se quitan solo si está `sharp`; en Android nunca | Se quitan siempre: GPS, marca, modelo, número de serie |
Los nombres de las categorías, los módulos y el resto de la aplicación son los de
epsylon, sin tocar. Con el tema OasisMobile se ve el del propio dev.
epsylon, sin tocar.
---
@ -127,20 +138,20 @@ En la barra de arriba, junto al avatar, está el botón de *No molestar* del Tel
![La interfaz por dentro](docs/capturas/esquema-interfaz.png)
<p align="center">
<img src="docs/capturas/01-portada.png" width="200">
<img src="docs/capturas/02-tribus.png" width="200">
<img src="docs/capturas/03-votacion.png" width="200">
<img src="docs/capturas/04-modulos.png" width="200">
<img src="docs/capturas/01-actividad.png" width="200">
<img src="docs/capturas/02-menu.png" width="200">
<img src="docs/capturas/03-menu-activista.png" width="200">
</p>
<p align="center">
<img src="docs/capturas/05-ajustes.png" width="200">
<img src="docs/capturas/06-barra.png" width="200">
<img src="docs/capturas/08-salas.png" width="200">
<img src="docs/capturas/07-oasismobile.png" width="200">
<img src="docs/capturas/04-tribu.png" width="200">
<img src="docs/capturas/05-blog.png" width="200">
<img src="docs/capturas/06-ajustes.png" width="200">
</p>
La actividad al abrir, tribus, una votación de semáforo, módulos, ajustes, la barra de
atajos, las salas del dev y el menú con el tema OasisMobile del dev.
Lo que sale al instalarla: la actividad al abrir y la hoja de Menú con el perfil
Mínimo. Después, con el perfil Activista: el menú completo, una tribu, un blog y
Ajustes. Capturas a 412 px, en español, con los colores de serie. Cómo rehacerlas:
`docs/capturas/generar_portada.py`.
Casi sesenta módulos no caben en la pantalla de un teléfono. Al abrir la app se ve la
actividad, lo que ha pasado en la red. Abajo hay cuatro atajos que eliges tú y una
@ -148,9 +159,9 @@ quinta pestaña fija, Menú, que abre desde abajo una hoja con todos los módulo
secciones, como el cajón de apps del móvil. Está en todas las pantallas. Por qué así,
y no un menú arriba o una hamburguesa: [docs/ESTUDIO_NAVEGACION.md](docs/ESTUDIO_NAVEGACION.md).
Solo aparecen los módulos activos. En *Ajustes → Workflows* se eligen de golpe: el de
acampada deja lo justo para organizarse. En el móvil un workflow cambia los módulos,
no el aspecto: el aspecto se elige en *Ajustes → Tema* (Acampada u OasisMobile).
Solo aparecen los módulos activos. En *Ajustes → Perfil* se eligen de golpe, de
menos a más: Mínimo, Acampada, Activista y los del dev. Un perfil cambia los módulos y
la barra de abajo, no el aspecto: los colores van en *Ajustes → Colores*.
---

View file

@ -58,8 +58,10 @@ mismo lanzador de ahora). Se cierra tocando fuera, con la X o volviendo a tocar
Es la idea de B, un botón que despliega las opciones, pero puesto donde llega el
pulgar y donde está el resto de la navegación. Funciona igual en todas las pantallas.
Está hecho con una casilla y CSS; un script mínimo hace que el botón atrás de Android
cierre la hoja en vez de cambiar de página.
Está hecho con un ancla y CSS (`:target`), sin JS: la CSP de la 1.3.5 no deja ninguno.
Abrir la hoja deja una entrada en el historial y atrás la cierra sin cambiar de
página. Al volver atrás desde un módulo abierto desde la hoja, la hoja sale abierta:
se vuelve a donde se estaba.
## Lo que salió en la prueba (emulador, 19/10/2026)

View file

@ -19,8 +19,8 @@ nuestros. Después, APK nueva con `sobre_oficial.sh` y repetir las pruebas.
Menú y perfiles:
- Publicar va siempre a `/blogs`; en Empleo y Negocio, que no tienen blogs, acaba en
`/modules`. Llevarlo a crear según los módulos encendidos.
- Hecho (23/10): Publicar va al primer sitio de escribir encendido (blog, feed, foro,
wiki).
Medidas:
@ -33,17 +33,13 @@ Medidas:
Tribus:
- Al tocar una pestaña se recarga arriba y su contenido queda bajo la cabecera. Hace
falta un ancla en el enlace: pedírsela al dev o meterla con el enganche de
`tribes_view.js`.
- Pestañas de 40 px, mayúsculas mezcladas ("Habitantes" y "GOBIERNO") y la fila se
corta con un "Más" debajo. Imagen enorme en Gobierno.
- Hecho (23/10): ancla `#tribe-tabs` en las pestañas (desde `mobile_ui.tribeTabs`) y
todas en mayúsculas. Queda: imagen enorme en Gobierno.
Blogs:
- La caja de comentar va tras un desplegable que solo dice "✑(0)": ponerle texto.
- El botón de enviar comentario queda 161 px bajo la barra de abajo.
- Los comentarios salen plegados: quien comenta no ve el suyo al volver.
- Hecho (23/10): comentarios abiertos con la opción del dev
(`comments_view.setCommentsOpen`, desde `mobile_layer.js`), salvo en el parlamento.
- Input de 32 px en el formulario de crear.
Wiki y pads:
@ -118,3 +114,29 @@ Todas las listas:
grande y el nombre después.
- Rehacer la APK con `sobre_oficial.sh` y pasar `TESTS/perfiles` (ahora hay 10
perfiles y la barra cambia con cada uno).
## Tras las pruebas de tribus y mensajería (23/10)
`TESTS/tribus/` (local): dos identidades de usar y tirar, B ve desde fuera la tribu de
A, se une, recorre las 24 pestañas, escribe en el feed y el foro, y comenta un blog.
Pasa entero salvo lo de abajo.
Arreglado en la capa: la hoja del menú sin JS (`:target`), iconos propios en vez de
emoji en avisos, fichas de visibilidad y Compartir, avisos de arriba en una línea,
responder y enviar en los chats, buscador con "más opciones" de la 1.3.5 en
`form.filter-box`, botones de voto del foro, etiquetas de la tribu, hora de un
comentario, ficha PÚBLICO y etiquetas del menú que se salían.
Pedirle al dev:
- **Dejar una tribu no pide confirmación.** El botón DEJAR va directo a
`/tribes/leave/:id`. Lo mismo con la ✕ de borrar un comentario. Él ya tiene
`confirmPage` (lo usa para unirse con invitación abierta).
- **Enlace a un hilo que no existe.** En Actividad, la ficha de un comentario a un
evento lleva a `/thread/<id>` y sale "No se ha encontrado".
- **Emoji metidos en el texto**: "👥: 1 · 💬 3" en foros, listas y actividad, y
"🔔 SUSCRIBIRSE". Con un `span` alrededor del emoji, como ya hace en las fichas de
visibilidad, se podrían cambiar por iconos desde CSS.
- **Etiquetas de la barra de abajo**: "Votaciones" no cabe con cinco pestañas a 412 px
y sale "Votacion…".

Binary file not shown.

Before

Width:  |  Height:  |  Size: 305 KiB

After

Width:  |  Height:  |  Size: 141 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

BIN
docs/capturas/02-menu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

BIN
docs/capturas/04-tribu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

BIN
docs/capturas/05-blog.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

View file

@ -1,9 +1,11 @@
#!/usr/bin/env python3
"""Portada y esquemas del README, con la paleta del tema Acampada.
"""Portada y esquemas del README.
Las capturas de movil (01..08) se hacen aparte, con la app arrancada en un nodo de
pruebas a 412x915 y escala 2; este script compone 00-portada.png a partir de ellas y
dibuja esquema-interfaz.png, esquema-rama.png y esquema-llamadas.png.
Las capturas de movil (01..06) se hacen aparte, con la app arrancada en un nodo de
pruebas a 412x915 y escala 2, en español y con lo que trae de serie (perfil Minimo y
colores del dev; 03 a 06 con el perfil Activista). Este script compone 00-portada.png
a partir de ellas, con los colores de serie, y dibuja esquema-interfaz.png,
esquema-rama.png y esquema-llamadas.png (estos aun con la paleta de Acampada).
El texto vive aqui: se cambia y se relanza, nunca se retocan los PNG a mano.
python3 docs/capturas/generar_portada.py
@ -17,22 +19,25 @@ ANTON = HERE.parents[1] / "src/client/assets/fonts/Anton-Regular.ttf"
SANS = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"
BG, FG, NEON, MUTED = "#ffffff", "#000000", "#00e5ff", "#555555"
# colores de serie del movil (los de la APK oficial del dev)
DEV_BG, DEV_CARD, DEV_FG, DEV_ACCENT, DEV_MUTED = "#121212", "#1e1e1e", "#ffd700", "#ffa300", "#bbbbbb"
SANS_BOLD = "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf"
def font(path, size):
return ImageFont.truetype(str(path), size)
def phone(shot, width):
"""Una captura dentro de un marco negro grueso con la sombra cian de los botones."""
def phone(shot, width, fg=FG, neon=NEON):
"""Una captura dentro de un marco grueso con una sombra de color debajo."""
im = Image.open(HERE / shot).convert("RGB")
h = round(im.height * width / im.width)
im = im.resize((width, h), Image.LANCZOS)
pad, radius, shadow = 10, 34, 10
frame = Image.new("RGBA", (width + 2 * pad, h + 2 * pad + shadow), (0, 0, 0, 0))
d = ImageDraw.Draw(frame)
d.rounded_rectangle((0, shadow, width + 2 * pad - 1, h + 2 * pad + shadow - 1), radius, fill=NEON)
d.rounded_rectangle((0, 0, width + 2 * pad - 1, h + 2 * pad - 1), radius, fill=FG)
d.rounded_rectangle((0, shadow, width + 2 * pad - 1, h + 2 * pad + shadow - 1), radius, fill=neon)
d.rounded_rectangle((0, 0, width + 2 * pad - 1, h + 2 * pad - 1), radius, fill=fg)
mask = Image.new("L", im.size, 0)
ImageDraw.Draw(mask).rounded_rectangle((0, 0, width - 1, h - 1), radius - pad, fill=255)
frame.paste(im, (pad, pad), mask)
@ -41,23 +46,23 @@ def phone(shot, width):
def portada():
W, H = 1600, 1100
out = Image.new("RGB", (W, H), BG)
out = Image.new("RGB", (W, H), DEV_BG)
d = ImageDraw.Draw(out)
d.text((80, 40), "OASIS MOBILE", font=font(ANTON, 92), fill=FG)
bottom = d.textbbox((80, 40), "OASIS MOBILE", font=font(ANTON, 92))[3]
d.rectangle((80, bottom + 12, 420, bottom + 20), fill=FG)
d.text((80, 196), "Rama PRUEBAS del hacklab · base Oasis 1.3.3 · tema Acampada, pensado para el pulgar",
font=font(SANS, 27), fill=MUTED)
shots = [("01-portada.png", "Actividad al abrir"), ("02-tribus.png", "Tribus en filas"),
("03-votacion.png", "Votar en semáforo"), ("07-oasismobile.png", "El menú, tema del dev")]
pw, gap, top = 320, 50, 270
d.text((80, 40), "OASIS MOBILE", font=font(SANS_BOLD, 84), fill=DEV_FG)
bottom = d.textbbox((80, 40), "OASIS MOBILE", font=font(SANS_BOLD, 84))[3]
d.rectangle((80, bottom + 12, 420, bottom + 18), fill=DEV_ACCENT)
d.text((80, 186), "Rama PRUEBAS del hacklab · base Oasis 1.3.5 · lo que sale al instalarla",
font=font(SANS, 27), fill=DEV_MUTED)
shots = [("01-actividad.png", "Actividad al abrir"), ("02-menu.png", "Menú, perfil Mínimo"),
("03-menu-activista.png", "Menú, perfil Activista"), ("04-tribu.png", "Una tribu")]
pw, gap, top = 320, 50, 260
x = (W - (4 * (pw + 20) + 3 * gap)) // 2
cap = font(ANTON, 34)
cap = font(SANS_BOLD, 26)
for shot, label in shots:
fr = phone(shot, pw)
fr = phone(shot, pw, fg="#000000", neon=DEV_ACCENT)
out.paste(fr, (x, top), fr)
tw = d.textlength(label.upper(), font=cap)
d.text((x + (fr.width - tw) / 2, top + fr.height + 18), label.upper(), font=cap, fill=FG)
tw = d.textlength(label, font=cap)
d.text((x + (fr.width - tw) / 2, top + fr.height + 18), label, font=cap, fill=DEV_FG)
x += fr.width + gap
out.save(HERE / "00-portada.png", optimize=True)
@ -202,7 +207,7 @@ def esquema_llamadas():
if __name__ == "__main__":
portada()
esquema()
esquema_rama()
esquema_llamadas()
import sys
# sin argumentos, todo; con nombres, solo esos (p. ej. "portada")
for name in sys.argv[1:] or ["portada", "esquema", "esquema_rama", "esquema_llamadas"]:
globals()[name]()

View file

@ -50,13 +50,7 @@ const mobileLayer = require('./mobile_layer')({ getViewerId, about });
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 };
})
// barra de unirse a un pub (mobile_pubbar.js): cerrar el aviso de terminado
.post('/mobile/pub-status/dismiss', koaBody(), async (ctx, next) => {
if (!isMobile()) return next();
pubBar.dismiss();

View file

@ -51,9 +51,16 @@ module.exports = ({ getViewerId, about }) => {
// 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)
global.__OASIS_SHOW_HIVE__ = false;
// en Ajustes el nombre y el avatar se leen al momento (se acaban de cambiar en
// /profile/edit, la pantalla del dev); en el resto, como mucho cada minuto
if (!avatarAt || ctx.path === '/settings') await refreshAvatar();
// comentarios abiertos en las fichas: plegados, la caja de comentar iba tras un
// "✑(0)" sin texto y quien comentaba no veia el suyo al volver. Es la opcion del dev
// para ?comments=open (comments_view.setCommentsOpen). En el parlamento hay un bloque
// por propuesta en la lista: ahi se quedan plegados
if (ctx.method === 'GET' && !ctx.path.startsWith('/parliament')) {
try { require('../views/comments_view').setCommentsOpen(true); } catch (_) {}
}
// en Ajustes y en el perfil (adonde vuelve el dev tras /profile/edit) el nombre y el
// avatar se leen al momento; en el resto, como mucho cada minuto
if (!avatarAt || ctx.path === '/settings' || ctx.path === '/profile') await refreshAvatar();
else if (Date.now() - avatarAt > 60000) refreshAvatar();
return next();
};

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v12"/><path d="M7 10l5 5 5-5"/><path d="M4 17v3h16v-3"/></svg>

After

Width:  |  Height:  |  Size: 227 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="11" width="14" height="10" rx="2"/><path d="M8 11V8a4 4 0 0 1 8 0v3"/></svg>

After

Width:  |  Height:  |  Size: 242 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 15V3"/><path d="M8 7l4-4 4 4"/><path d="M5 12v8h14v-8"/></svg>

After

Width:  |  Height:  |  Size: 226 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12l16-8-6 16-3-7z"/></svg>

After

Width:  |  Height:  |  Size: 189 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 3h8l4 4v14H6z"/><path d="M14 3v4h4"/><path d="M9 12h6M9 16h6"/></svg>

After

Width:  |  Height:  |  Size: 232 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="8" r="3"/><path d="M3 20a6 6 0 0 1 12 0"/><circle cx="17" cy="9" r="2.5"/><path d="M16 14a5 5 0 0 1 5 5"/></svg>

After

Width:  |  Height:  |  Size: 282 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M3 12h18"/><path d="M12 3a14 14 0 0 1 0 18a14 14 0 0 1 0-18z"/></svg>

After

Width:  |  Height:  |  Size: 260 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="6" cy="12" r="2.5"/><circle cx="18" cy="6" r="2.5"/><circle cx="18" cy="18" r="2.5"/><path d="M8.3 11l7.4-3.8M8.3 13l7.4 3.8"/></svg>

After

Width:  |  Height:  |  Size: 296 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 7L4 12l5 5"/><path d="M4 12h10a6 6 0 0 1 6 6v1"/></svg>

After

Width:  |  Height:  |  Size: 218 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M10 8l4 4-4 4"/></svg>

After

Width:  |  Height:  |  Size: 213 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg>

After

Width:  |  Height:  |  Size: 211 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 4L3 20h18z"/><path d="M12 4v16"/><path d="M9 20l3-5 3 5"/></svg>

After

Width:  |  Height:  |  Size: 228 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 3h4l2 5-2.5 1.5a11 11 0 0 0 6 6L16 13l5 2v4a2 2 0 0 1-2 2A17 17 0 0 1 3 5a2 2 0 0 1 2-2z"/><path d="M22 2L2 22"/></svg>

After

Width:  |  Height:  |  Size: 282 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 3h4l2 5-2.5 1.5a11 11 0 0 0 6 6L16 13l5 2v4a2 2 0 0 1-2 2A17 17 0 0 1 3 5a2 2 0 0 1 2-2z"/></svg>

After

Width:  |  Height:  |  Size: 260 B

View file

@ -799,7 +799,7 @@ body { padding-top: 74px; } /* espejo { display: contents; }
min-height: 0 !important; padding: 0 !important; gap: 0 !important;
z-index: auto !important;
}
body:has(.oasis-nav-side-toggle) { padding-top: 84px !important; }
body:has(.oasis-nav-side-toggle) { padding-top: 74px !important; }
.oasis-nav-side-toggle ~ .header .oasis-mobile-menus,
.oasis-nav-side-toggle ~ .header .top-bar-left nav,
.oasis-nav-side-toggle ~ .header .top-bar-right nav,

View file

@ -107,7 +107,7 @@
.welcome-banner .welcome-banner-close { position: absolute !important; top: 0 !important; right: 0 !important; margin: 0 !important; }
.welcome-banner .welcome-banner-close-btn {
min-height: 44px !important; height: 44px !important; width: 44px !important; padding: 0 !important; margin: 0 !important;
font-size: 16px !important; color: var(--ux-muted) !important;
font-size: 16px !important; color: var(--ux-fg) !important; /* el gris sobre el tinte del banner no llegaba a 4,5 */
}
/* ---------- pie de una linea ---------- */
@ -322,6 +322,10 @@ a:focus-visible, button:focus-visible, summary:focus-visible, label.module-switc
por lado) y el texto de una votacion o una tribu se quedaba en 264 de 412px */
body .main-content { padding-left: 14px !important; padding-right: 14px !important; }
body .main-content > .main-column { padding-left: 0 !important; padding-right: 0 !important; }
/* y arriba: la barra fija ya deja su holgura (body 74px); con los 20+10 de contenido y
columna habia 67px vacios antes del titulo de cada pantalla */
body .main-content { padding-top: 6px !important; }
body .main-content > .main-column { padding-top: 0 !important; }
.main-column :is(.tribe-details, .tribe-side, .tribe-main, .job-section, .vote-form, .tribe-content) {
padding-left: 0 !important; padding-right: 0 !important; margin-left: 0 !important; margin-right: 0 !important;
width: 100% !important; max-width: 100% !important; box-sizing: border-box;
@ -479,13 +483,33 @@ form.oasis-phone-state {
/* mobile.css lo fija con .oasis-nav-side-toggle ~ .header ...: hace falta mas especificidad */
form.oasis-phone-state .oasis-phone-state-btn,
body .oasis-nav-side-toggle ~ .header form.oasis-phone-state .oasis-phone-state-btn,
body form.oasis-phone-state button.oasis-phone-state-btn.pm-exposition-chip {
body form.oasis-phone-state button.oasis-phone-state-btn.pm-exposition-chip[type] {
min-width: 44px !important; max-width: 44px !important; max-height: 44px !important;
width: 44px !important; height: 44px !important; min-height: 44px !important; padding: 0 !important; margin: 0 !important;
display: inline-flex !important; align-items: center; justify-content: center;
border-radius: 50% !important; box-shadow: none !important; font-size: 20px !important;
}
.oasis-mobile-topbar .omt-menus { padding-right: 46px; }
.oasis-mobile-topbar .omt-menus { padding-right: 180px; }
/* el boton era un disco amarillo con un ✆ de fuente: parecia "llamar" y es el estado
del telefono (si saltan las llamadas o no). Pastilla con borde, icono dibujado y el
nombre del estado, que es el title del dev ya traducido. En No molestar (la clase
pm-exposition-hidden), rellena y con el telefono tachado. Solo CSS */
body form.oasis-phone-state button.oasis-phone-state-btn.pm-exposition-chip[type] {
width: auto !important; max-width: 180px !important; min-width: 44px !important; padding: 0 12px 0 10px !important;
border-radius: 22px !important; border: 1px solid var(--ux-line) !important;
background: var(--ux-bg) !important; color: var(--ux-fg) !important;
font-family: var(--ux-text) !important; font-size: 13px !important; font-weight: 600 !important; letter-spacing: 0 !important; text-transform: none !important;
}
body form.oasis-phone-state button.oasis-phone-state-btn > .pm-exposition-icon { display: none !important; }
body form.oasis-phone-state button.oasis-phone-state-btn::before {
content: ""; flex: 0 0 auto; width: 18px; height: 18px; margin-right: 6px; background-color: currentColor;
-webkit-mask: url(/assets/images/mobile-phone.svg) center / contain no-repeat; mask: url(/assets/images/mobile-phone.svg) center / contain no-repeat;
}
body form.oasis-phone-state button.oasis-phone-state-btn[title]::after { content: attr(title); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
body form.oasis-phone-state button.oasis-phone-state-btn.pm-exposition-chip.pm-exposition-hidden[type] { background: var(--ux-accent) !important; color: var(--ux-on-accent) !important; border-color: var(--ux-accent) !important; }
body form.oasis-phone-state button.oasis-phone-state-btn.pm-exposition-hidden::before {
-webkit-mask-image: url(/assets/images/mobile-phone-dnd.svg); mask-image: url(/assets/images/mobile-phone-dnd.svg);
}
/* barras de la votacion: juntas, y las etiquetas del dev (CONFUNDIDO, SEGUIR
MAYORIA) en el mismo tipo que las nuestras (Si, No) */
@ -537,15 +561,16 @@ body > .welcome-banner { order: -1; margin-top: 10px !important; }
/* ---------- pestaña Menu y su hoja con todos los modulos ---------- */
/* la portada es la actividad; los modulos se abren desde abajo, como el "Mas" de
Android (docs/ESTUDIO_NAVEGACION.md). Una casilla oculta abre y cierra la hoja */
.oasis-bottombar input.menu-toggle { position: absolute; opacity: 0; width: 0 !important; height: 0 !important; min-height: 0 !important; pointer-events: none; }
.oasis-bottombar label.bb-menu { cursor: pointer; }
.oasis-bottombar .menu-toggle:checked ~ label.bb-menu .bb-ico.has-svg { background: var(--ux-accent) !important; color: var(--ux-on-accent) !important; }
Android (docs/ESTUDIO_NAVEGACION.md). La abre el ancla #oasis-menu (:target) y la
cierra cualquier otra; con la hoja abierta, la pestaña Menu es la de cerrar */
.oasis-bottombar a.bb-menu-close, .oasis-bottombar:target a.bb-menu:not(.bb-menu-close) { display: none !important; }
.oasis-bottombar:target a.bb-menu-close { display: flex !important; }
.oasis-bottombar:target a.bb-menu-close .bb-ico.has-svg { background: var(--ux-accent) !important; color: var(--ux-on-accent) !important; }
.oasis-bottombar .menu-sheet, .oasis-bottombar .menu-sheet-backdrop { display: none; }
.oasis-bottombar .menu-toggle:checked ~ .menu-sheet-backdrop {
.oasis-bottombar:target .menu-sheet-backdrop {
display: block; position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 1002; background: rgba(0, 0, 0, .45);
}
.oasis-bottombar .menu-toggle:checked ~ .menu-sheet {
.oasis-bottombar:target .menu-sheet {
display: block; position: fixed; left: 0; right: 0; z-index: 1003;
bottom: 0; max-height: 82vh; max-height: 82dvh; overflow-y: auto; overscroll-behavior: contain;
padding: 8px 14px calc(18px + var(--inset-bottom));
@ -570,9 +595,9 @@ body > .welcome-banner { order: -1; margin-top: 10px !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: 1px solid var(--ux-line) !important; }
.oasis-bottombar:target 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)); }
.oasis-bottombar:target .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; }
@ -582,7 +607,7 @@ body > .welcome-banner { order: -1; margin-top: 10px !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; }
/* con el menu abierto, solo Menu lleva la pastilla */
.oasis-bottombar .menu-toggle:checked ~ a.bb-item.current .bb-ico.has-svg { background: transparent !important; color: var(--ux-fg) !important; }
.oasis-bottombar:target a.bb-item.current .bb-ico.has-svg { background: transparent !important; color: var(--ux-fg) !important; }
/* los grupos de uno o dos modulos (Biblioteca, Oficina, Gobernanza...) van de dos en
dos, uno al lado del otro; los grandes ocupan el ancho. Hace falta :has(): en los
WebView viejos (Android 9-11 sin actualizar) cada grupo va a lo ancho */
@ -594,7 +619,7 @@ body > .welcome-banner { order: -1; margin-top: 10px !important; }
.menu-sheet .launcher .ln-group:not(:has(.ln-app:nth-child(3))) .ln-grid { grid-template-columns: 1fr 1fr !important; }
}
/* con el menu abierto, el boton de No molestar no se monta encima de la hoja */
body:has(#oasis-menu-toggle:checked) form.oasis-phone-state { display: none !important; }
body:has(#oasis-menu:target) form.oasis-phone-state { display: none !important; }
/* ---------- tipo de portada: un toque y guardado ---------- */
/* bienvenida y Ajustes: cada opcion es un boton. En el movil, una fila por opcion con
@ -635,7 +660,10 @@ body:has(#oasis-menu-toggle:checked) form.oasis-phone-state { display: none !imp
/* ---------- Ajustes: los seis accesos de arriba (mobile_settings.js) ---------- */
/* rejilla de 3x2 a 360px: icono, nombre y lo que hay puesto. Cada acceso baja a su
bloque, que se para debajo de la barra de arriba (67px) */
.main-column section[id] { scroll-margin-top: calc(76px + var(--inset-top)); }
.main-column section[id], #tribe-tabs, #comments, #comments-latest { scroll-margin-top: calc(76px + var(--inset-top)); }
/* pestañas de tribu: el dev mezcla "Habitantes" y "GOBIERNO"; todas en mayusculas,
como las fichas de filtro (::first-letter no va en un inline-flex) */
.main-column a.tribe-tab, .main-column details.tribe-tabs-more > summary { text-transform: uppercase !important; font-size: 13px !important; letter-spacing: 0 !important; padding: 4px 6px !important; }
.main-column div.quick-grid { display: grid !important; grid-template-columns: repeat(3, minmax(0, 1fr)); grid-gap: 10px; margin: 0 0 6px !important; padding: 0 !important; }
.main-column a.quick-tile {
display: flex !important; flex-direction: column; align-items: center; justify-content: flex-start; text-align: center;
@ -715,7 +743,7 @@ body:has(#oasis-menu-toggle:checked) form.oasis-phone-state { display: none !imp
}
.main-column .mobile-filter > label.mobile-filter-btn::after { content: "\25BE"; margin-left: 8px; font-size: 16px; }
.main-column .mobile-filter > input:checked + label.mobile-filter-btn::after { content: "\25B4"; }
.main-column .mobile-filter.is-filtered > label.mobile-filter-btn { background: var(--ux-accent); color: var(--ux-on-accent); border-color: var(--ux-accent); }
.main-column .mobile-filter.is-filtered > label.mobile-filter-btn { background: var(--ux-accent) !important; color: var(--ux-on-accent) !important; border-color: var(--ux-accent) !important; }
.main-column .mobile-filter .activity-filter-now { margin-left: 8px; font-weight: 400; text-transform: none; letter-spacing: 0; }
.main-column .mobile-filter .activity-filter-now::before { content: "\00B7"; margin-right: 8px; }
.main-column .mobile-filter > div.mobile-filter-chips { display: none !important; }
@ -765,9 +793,9 @@ nav.oasis-bottombar div.pub-bar div.pub-bar-body { flex: 1 1 auto; min-width: 0;
.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; }
body:has(div.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; }
body:has(#oasis-menu:target) .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
@ -802,7 +830,7 @@ body .oasis-mobile-topbar .omt-logo img, body .oasis-mobile-topbar .omt-avatar i
@media (min-width: 700px) {
body .main-content { max-width: 720px !important; margin-left: auto !important; margin-right: auto !important; width: 100% !important; box-sizing: border-box; }
body > .welcome-banner { width: calc(100% - 28px) !important; max-width: 692px !important; margin-left: auto !important; margin-right: auto !important; box-sizing: border-box; }
.oasis-bottombar .menu-toggle:checked ~ div.menu-sheet { left: 50%; right: auto; width: 640px; margin-left: -320px; }
.oasis-bottombar:target div.menu-sheet { left: 50%; right: auto; width: 640px; margin-left: -320px; }
}
/* ---------- WebView viejos ---------- */
@ -841,7 +869,7 @@ body .oasis-mobile-topbar .omt-logo img, body .oasis-mobile-topbar .omt-avatar i
.main-column div.apk-modbar .apk-pin { margin: 0 0 0 10px !important; width: auto !important; flex: 0 0 auto; }
/* las fichas de la cabecera en rejilla de tres columnas, sin deslizar de lado */
.main-column div.apk-modbar > .apk-strip:not(.mobile-filter) {
display: grid !important; grid-template-columns: repeat(3, minmax(0, 1fr)); grid-gap: 8px;
display: grid !important; grid-template-columns: repeat(3, minmax(0, 1fr)) !important; grid-gap: 8px;
overflow: visible !important; margin: 0 0 10px !important; padding: 0 !important; white-space: normal !important;
}
.main-column div.apk-modbar > .apk-strip:not(.mobile-filter) > *,
@ -859,6 +887,14 @@ body .oasis-mobile-topbar .omt-logo img, body .oasis-mobile-topbar .omt-avatar i
/* la seccion que se queda vacia cuando la cabecera se lleva las fichas y el buscador */
.main-column > section:empty, .main-column div.activity-toolbar-row:empty { display: none !important; }
.main-column > section > div.activity-toolbar-row:only-child:empty { display: none !important; }
.main-column > section:has(> div.activity-toolbar-row:only-child:empty) { display: none !important; }
/* las secciones del dev llevan 16px arriba y 20 abajo; en columna flex no se juntan */
.main-column > section { margin-top: 0 !important; }
/* cuatro fichas (todas, recientes, mias, top): en una fila, no tres y una suelta.
Sin :has (WebView viejo) se quedan en tres y una */
.main-column div.apk-modbar > .apk-strip:not(.mobile-filter) > form:only-child:has(> button:first-child:nth-last-child(4)) { grid-template-columns: repeat(4, minmax(0, 1fr)) !important; }
.main-column div.apk-modbar > .apk-strip:not(.mobile-filter) > form:only-child > button:first-child:nth-last-child(4),
.main-column div.apk-modbar > .apk-strip:not(.mobile-filter) > form:only-child > button:first-child:nth-last-child(4) ~ button { padding: 4px 2px !important; font-size: 13px !important; }
/* cabecera de tarjeta de la 1.3.5: autoria a la izquierda (nombre cortado con ...) y
el menu ⋯ y la flecha juntos a la derecha. Pisa lo de la 1.3.3 (columna) */
@ -878,7 +914,7 @@ body .oasis-mobile-topbar .omt-logo img, body .oasis-mobile-topbar .omt-avatar i
.main-column div.card-chips-row > * { margin: 0 6px 6px 0 !important; }
/* algunas tiras son un solo formulario con todos los botones (Eventos): la rejilla va dentro */
.main-column div.apk-modbar > .apk-strip:not(.mobile-filter) > form:only-child {
grid-column: 1 / -1; display: grid !important; grid-template-columns: repeat(3, minmax(0, 1fr)); grid-gap: 8px;
grid-column: 1 / -1; display: grid !important; grid-template-columns: repeat(3, minmax(0, 1fr)) !important; grid-gap: 8px;
min-height: 0 !important; padding: 0 !important;
}
.main-column div.apk-modbar > .apk-strip:not(.mobile-filter) > form:only-child > button { min-height: 44px !important; margin: 0 !important; }
@ -943,3 +979,183 @@ html[lang="ar"] .main-column input, html[lang="ar"] .main-column textarea { dire
}
.main-column div.card-header.card-top > div.content-actions { padding-bottom: 32px !important; }
.main-column div.content-actions > * + * { margin-left: 28px !important; }
/* ---------- zonas tactiles y letra que quedaban cortas (pruebas del 20/10) ---------- */
.main-column a.card-byline-avatar { display: inline-flex !important; align-items: center; justify-content: center; min-width: 44px !important; min-height: 44px !important; }
.main-column a.share-summary-link, .main-column summary.comments-summary, .main-column summary.opinions-summary,
.main-column summary.tribe-members-count, .main-column a.larp-my-house-link, .main-column a.wallet-chip-link, .main-column a.graphos-zoom-btn, .main-column a.update-download-link {
display: inline-flex !important; align-items: center; min-height: 44px !important; box-sizing: border-box;
}
/* enlace de wiki a una pagina que no existe: va en el texto, 24px como los demas */
.main-column a.wiki-link { display: inline-block; min-height: 24px; line-height: 24px; }
/* etiquetas de 10-11px del dev (melodia, parlamento y gobierno de tribu, perfil) */
.main-column .melody-note-type, .main-column .kpi__label, .main-column .inhabitant-stat-label { font-size: 12px !important; }
/* casilla dentro de su etiqueta (permitir comentarios): se toca la etiqueta entera */
.main-column form label:not([class]) { display: inline-flex; align-items: center; min-height: 44px; }
/* el nombre en la ficha de habitante (mobile_base lo deja en linea, 18px) */
.main-column p.inhabitant-left-name > a.user-link { display: inline-block; min-height: 24px; line-height: 24px; }
.main-column .profile-side-mention > a { display: inline-flex; align-items: center; min-height: 44px; max-width: 100%; }
.main-column .profile-side-mention > a > strong { min-width: 0; overflow-wrap: anywhere; word-break: break-all; }
/* colores fijos de style.css que no siguen la paleta: con Papel, Lila o Bosque el nombre
de la tarjeta (#FFD700), el telefono (fondo #1A1A1A) y el chip "Tu" (#2a2a2a) quedaban
a contraste 1,0-1,4 */
.main-column .card-byline a.user-link { color: var(--ux-fg) !important; }
.main-column div.wallet-line { background: var(--ux-bg) !important; color: var(--ux-fg) !important; }
.main-column div.wallet-line .wallet-address { color: var(--ux-fg) !important; }
.main-column span.status.you { background: var(--ux-accent) !important; color: var(--ux-on-accent) !important; }
/* Actividad con un modulo filtrado: las fichas que llevan a ese modulo (todas,
recientes, mias, top) en una fila, no una por fila. La flecha sobra */
.main-column div.activity-toolbar-row { display: flex !important; flex-direction: row !important; flex-wrap: wrap !important; align-items: center !important; gap: 0 !important; width: auto !important; margin: 0 -4px 8px !important; padding: 0 !important; }
.main-column div.activity-toolbar-row > span.activity-subchip-label { display: none !important; }
.main-column div.activity-toolbar-row > a.activity-chip {
display: inline-flex !important; flex: 1 1 0 !important; width: auto !important; min-width: 0 !important; justify-content: center; align-items: center;
min-height: 44px !important; margin: 0 4px 8px !important; padding: 4px 2px !important; font-size: 13px !important;
}
.main-column div.activity-toolbar-row > :not(a):not(span.activity-subchip-label) { flex: 1 1 100%; margin-left: 4px !important; margin-right: 4px !important; }
/* Habitantes: la ficha del dev va dentro de otra tarjeta; la de dentro sin marco */
.main-column div.trending-card div.inhabitant-card { background: transparent !important; border: 0 !important; box-shadow: none !important; padding: 0 !important; }
.main-column div.trending-card div.inhabitant-card::before, .main-column div.trending-card div.inhabitant-card::after { display: none !important; }
/* ---------- buscador de /search de la 1.3.5 (apk-searchform) ----------
su CSS esta en OasisMobile.css, que con Activista no carga: el campo se quedaba en
26px y los campos de "mas" siempre a la vista. Casilla + etiqueta, sin JS */
.main-column form.apk-searchform { display: flex !important; flex-wrap: wrap !important; align-items: center !important; position: relative; }
.main-column form.apk-searchform > input.filter-box__input { flex: 1 1 0 !important; min-width: 0 !important; width: auto !important; min-height: 48px !important; }
.main-column form.apk-searchform > div.filter-box__controls { display: flex !important; flex: 0 0 auto !important; align-items: center; width: auto !important; margin: 0 0 0 8px !important; padding: 0 !important; }
.main-column form.apk-searchform label.apk-more-btn {
display: inline-flex !important; align-items: center; justify-content: center; min-width: 44px; min-height: 44px; margin: 0 8px 0 0 !important;
border: 1px solid var(--ux-line) !important; border-radius: 12px !important; cursor: pointer; color: var(--ux-fg) !important;
}
.main-column form.apk-searchform label.apk-more-btn::after { content: "\2295"; font-size: 22px; line-height: 1; }
.main-column form.apk-searchform > input.apk-more-toggle { position: absolute !important; opacity: 0 !important; width: 1px !important; height: 1px !important; margin: 0 !important; pointer-events: none; }
.main-column form.apk-searchform > div.apk-more-fields { display: none !important; flex: 1 1 100% !important; margin: 10px 0 0 !important; padding: 0 !important; }
.main-column form.apk-searchform > input.apk-more-toggle:checked ~ div.apk-more-fields { display: grid !important; grid-template-columns: repeat(2, minmax(0, 1fr)); grid-gap: 10px; }
.main-column form.apk-searchform div.apk-more-field { display: flex !important; flex-direction: column !important; min-width: 0; margin: 0 !important; padding: 0 !important; }
.main-column form.apk-searchform span.apk-more-label { font-size: 13px !important; color: var(--ux-muted) !important; margin: 0 0 4px !important; }
.main-column form.apk-searchform div.apk-more-field > input, .main-column form.apk-searchform div.apk-more-field > select { width: 100% !important; min-width: 0 !important; min-height: 44px !important; box-sizing: border-box; margin: 0 !important; }
/* aviso de sugerencia (robot) del dev: el enlace era una linea de 23px cortada a "Orden ..." */
.main-column div.update-banner.ai-suggestion-banner, div.update-banner.ai-suggestion-banner { display: flex !important; align-items: center !important; flex-wrap: nowrap !important; }
div.update-banner.ai-suggestion-banner > .update-banner-icon { display: block !important; flex: 0 0 auto; margin: 0 8px 0 0 !important; position: static !important; }
div.update-banner.ai-suggestion-banner > a.update-banner-link {
flex: 1 1 auto !important; min-width: 0 !important; max-width: none !important; display: block !important; min-height: 44px; line-height: 44px !important;
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
div.update-banner.ai-suggestion-banner > form.welcome-banner-close { position: static !important; flex: 0 0 auto !important; width: auto !important; margin: 0 0 0 8px !important; padding: 0 !important; }
div.update-banner.ai-suggestion-banner > form > button.welcome-banner-close-btn { width: 44px !important; min-width: 44px !important; max-width: 44px !important; min-height: 44px !important; padding: 0 !important; color: var(--ux-fg) !important; }
/* "EN RESPUESTA A" en las respuestas de Actividad: 10,7px */
.main-column .reply-context-label, .main-column .reply-context-label > a.tag-link { font-size: 12px !important; }
/* ocultar un comentario (ojo): el dev lo pone a left:-12px y se montaba sobre el borde
de la ficha y el nombre. Arriba a la derecha, dentro */
.main-column div.votations-comment-card { padding-right: 56px !important; }
.main-column div.votations-comment-card > form.comment-delete-form { left: auto !important; right: 6px !important; top: 6px !important; width: auto !important; }
.main-column div.votations-comment-card > form.comment-delete-form > button { min-width: 44px !important; min-height: 44px !important; }
/* paginacion del dev (10 / 50 / 100 / todos): fondo #1C1C1C fijo, con paletas claras
el texto quedaba a contraste 1,0-1,1 */
.main-column a.oasis-pager-btn { display: inline-flex !important; align-items: center; min-height: 44px; background: var(--ux-bg) !important; color: var(--ux-fg) !important; border: 1px solid var(--ux-line) !important; }
.main-column a.oasis-pager-btn.active { background: var(--ux-accent) !important; color: var(--ux-on-accent) !important; border-color: var(--ux-accent) !important; }
.main-column .oasis-pager-info { color: var(--ux-muted) !important; font-size: 13px !important; }
/* ---------- tribus y foro (TESTS/tribus, 23/10) ---------- */
/* votar un hilo del foro (▲ ▼): 30px */
.main-column button.score-btn { min-width: 44px !important; min-height: 44px !important; padding: 0 !important; }
/* etiquetas de la columna de la tribu: se tocan como fichas, no van en el texto */
.main-column .tribe-side-tags a.tag-link { display: inline-flex !important; align-items: center; min-height: 44px !important; box-sizing: border-box; }
/* la hora de un comentario lleva al hilo: era una linea de 34px */
.main-column .created-at a:not(.user-link) { display: inline-flex !important; align-items: center; min-height: 44px; }
/* ficha de con quien se comparte (PUBLICO, el nombre de la tribu): 22px. Bordes
transparentes arriba y abajo para llegar a 44 sin que la ficha se vea mas alta */
.main-column a.pm-exposition-chip-link { display: inline-flex !important; align-items: center; min-height: 44px; box-sizing: border-box; border-top: 11px solid transparent !important; border-bottom: 11px solid transparent !important; background-clip: padding-box !important; }
/* correos y enlaces sueltos en el texto de una publicacion: 16px de alto */
.main-column a[href^="mailto:"] { display: inline-block; min-height: 24px; line-height: 24px; }
.main-column span.internal-link-kind { font-size: 12px !important; }
/* titulo de una tarjeta que lleva a su ficha (Actividad): 26px */
.main-column div.card-field > a.card-value { display: inline-flex !important; align-items: center; min-height: 44px; }
/* ---------- iconos propios en vez de los emoji del dev (23/10) ----------
Los avisos, las fichas de con quien se comparte y Compartir llevaban emoji (el robot,
la llave inglesa, el globo, el candado). Se esconde el caracter y se dibuja un icono
de linea, del color del texto. Lo que escribe la gente no se toca */
.update-banner > .update-banner-icon, .main-column span.pm-exposition-icon, .main-column span.share-summary-icon {
font-size: 0 !important; display: inline-block !important; flex: 0 0 auto;
width: 18px !important; height: 18px !important; margin: 0 6px 0 0 !important; vertical-align: -3px;
background-color: currentColor; -webkit-mask: url(/assets/images/mobile-ico-publico.svg) center / contain no-repeat; mask: url(/assets/images/mobile-ico-publico.svg) center / contain no-repeat;
}
.main-column .pm-exposition-chip span.pm-exposition-icon, .update-banner > .update-banner-icon { background-color: currentColor !important; color: inherit !important; font-size: 0 !important; }
.update-banner > .update-banner-icon { width: 22px !important; height: 22px !important; -webkit-mask-image: url(/assets/images/mobile-ico-actualizar.svg); mask-image: url(/assets/images/mobile-ico-actualizar.svg); }
.update-banner.ai-suggestion-banner > .update-banner-icon { -webkit-mask-image: url(/assets/images/mobile-ico-sugerencia.svg); mask-image: url(/assets/images/mobile-ico-sugerencia.svg); }
.main-column span.share-summary-icon { -webkit-mask-image: url(/assets/images/mobile-ico-compartir.svg); mask-image: url(/assets/images/mobile-ico-compartir.svg); }
.main-column .pm-exposition-mutuals > span.pm-exposition-icon { -webkit-mask-image: url(/assets/images/mobile-ico-mutuos.svg); mask-image: url(/assets/images/mobile-ico-mutuos.svg); }
.main-column .pm-exposition-encrypted > span.pm-exposition-icon { -webkit-mask-image: url(/assets/images/mobile-ico-cifrado.svg); mask-image: url(/assets/images/mobile-ico-cifrado.svg); }
.main-column .pm-exposition-license > span.pm-exposition-icon { -webkit-mask-image: url(/assets/images/mobile-ico-licencia.svg); mask-image: url(/assets/images/mobile-ico-licencia.svg); }
.main-column .pm-exposition-tribe > span.pm-exposition-icon { -webkit-mask-image: url(/assets/images/mobile-ico-tribu.svg); mask-image: url(/assets/images/mobile-ico-tribu.svg); }
.main-column .pm-exposition-transport > span.pm-exposition-icon, .main-column .pm-exposition-fediverse > span.pm-exposition-icon { -webkit-mask-image: url(/assets/images/mobile-ico-red.svg); mask-image: url(/assets/images/mobile-ico-red.svg); }
.main-column [class*="pm-exposition-lifespan"] > span.pm-exposition-icon { -webkit-mask-image: url(/assets/images/mobile-ico-tiempo.svg); mask-image: url(/assets/images/mobile-ico-tiempo.svg); }
.main-column .pm-exposition-chip span.pm-exposition-icon { width: 14px !important; height: 14px !important; margin-right: 4px !important; vertical-align: -2px; }
/* los dos avisos de arriba (version nueva y sugerencia) se comian 150px en cada
pantalla: una linea cada uno, juntos */
.main-column .apk-banners { margin: 0 0 8px !important; }
.main-column .apk-banners > div.update-banner, body div.update-banner { min-height: 0 !important; padding: 4px 4px 4px 12px !important; margin: 0 0 6px !important; align-items: center !important; }
.main-column .apk-banners > div.update-banner > .update-banner-text { flex: 1 1 auto; min-width: 0; font-size: 13px !important; line-height: 1.3 !important; }
.main-column .apk-banners > div.update-banner > a.update-banner-link { flex: 0 0 auto; display: inline-flex !important; align-items: center; min-height: 44px; margin-left: 8px !important; font-size: 13px !important; }
.main-column .apk-banners > div.update-banner.ai-suggestion-banner > a.update-banner-link { flex: 1 1 auto !important; margin-left: 0 !important; }
/* buscador con "mas opciones" de la 1.3.5 tambien en form.filter-box (Imagenes y
otros): la casilla salia suelta y Buscar caia a otra linea */
.main-column input.apk-more-toggle { position: absolute !important; opacity: 0 !important; width: 1px !important; height: 1px !important; margin: 0 !important; pointer-events: none; }
.main-column form.filter-box { display: flex !important; flex-wrap: wrap !important; align-items: center !important; position: relative; }
/* con la casilla de "mas" el select va dentro del desplegable: fila, no columna */
.main-column form.filter-box.filter-box:has(> input.apk-more-toggle) { flex-direction: row !important; }
.main-column form.filter-box > input.filter-box__input { flex: 1 1 0 !important; min-width: 0 !important; width: auto !important; }
.main-column form.filter-box > div.filter-box__controls { display: flex !important; flex: 0 0 auto !important; align-items: center; width: auto !important; margin: 0 0 0 8px !important; padding: 0 !important; }
.main-column form.filter-box label.apk-more-btn {
display: inline-flex !important; align-items: center; justify-content: center; min-width: 44px; min-height: 44px; margin: 0 8px 0 0 !important;
border: 1px solid var(--ux-line) !important; border-radius: 12px !important; cursor: pointer; color: var(--ux-fg) !important;
}
.main-column form.filter-box label.apk-more-btn::after { content: "\2295"; font-size: 22px; line-height: 1; }
.main-column form.filter-box > div.apk-more-fields { display: none !important; flex: 1 1 100% !important; margin: 10px 0 0 !important; padding: 0 !important; }
.main-column form.filter-box > input.apk-more-toggle:checked ~ div.apk-more-fields { display: grid !important; grid-template-columns: repeat(2, minmax(0, 1fr)); grid-gap: 10px; }
.main-column form.filter-box div.apk-more-fields > select, .main-column form.filter-box div.apk-more-fields > input, .main-column form.filter-box div.apk-more-fields > div { width: 100% !important; min-width: 0 !important; min-height: 44px !important; box-sizing: border-box; margin: 0 !important; }
/* ---------- chats: responder, reacciones y enviar (23/10) ----------
Responder a un mensaje era un "↩" de 27x20 junto a la hora: ni se veia ni se
acertaba. Pastilla con icono y la palabra (el title del dev, ya traducido) */
.main-column .chat-message-meta a.chat-reply-btn, .main-column .chat-msg-actions a.chat-reply-btn {
display: inline-flex !important; align-items: center; min-height: 44px; padding: 0 12px 0 10px !important; margin-left: auto;
border: 1px solid var(--ux-line) !important; border-radius: 22px !important; opacity: 1 !important;
font-size: 0 !important; color: var(--ux-fg) !important; text-decoration: none !important;
}
.main-column .chat-message-meta a.chat-reply-btn::before, .main-column .chat-msg-actions a.chat-reply-btn::before {
content: ""; width: 18px; height: 18px; margin-right: 6px; background-color: currentColor;
-webkit-mask: url(/assets/images/mobile-ico-responder.svg) center / contain no-repeat; mask: url(/assets/images/mobile-ico-responder.svg) center / contain no-repeat;
}
.main-column .chat-message-meta a.chat-reply-btn[title]::after, .main-column .chat-msg-actions a.chat-reply-btn[title]::after { content: attr(title); font-size: 13px; font-weight: 600; }
.main-column button.chat-react-btn { min-height: 44px !important; min-width: 44px !important; }
/* enviar (chat y respuesta del foro): un disco con ➤ o ↩ y nada mas. Ancho entero,
icono y la palabra */
body .main-column .chat-message-form button.filter-btn, body .main-column button.forum-send-btn {
display: flex !important; line-height: 1 !important; padding: 0 16px !important; align-items: center; justify-content: center; width: 100% !important; max-width: none !important;
min-height: 48px !important; height: auto !important; border-radius: 12px !important; font-size: 15px !important; font-weight: 700 !important;
background: var(--ux-accent) !important; color: var(--ux-on-accent) !important; border: 0 !important;
}
body .main-column .chat-message-form button.filter-btn::before, body .main-column button.forum-send-btn::before {
content: "" !important; width: 18px; height: 18px; margin-right: 8px; background-color: currentColor !important;
-webkit-mask: url(/assets/images/mobile-ico-enviar.svg) center / contain no-repeat; mask: url(/assets/images/mobile-ico-enviar.svg) center / contain no-repeat;
}
/* hoja del menu: "Configuracion" y "Copia de seguridad" se salian de su casilla a 412px.
Solo la etiqueta; los iconos quedan como estan */
.menu-sheet .launcher .ln-lbl { max-width: 100%; white-space: normal !important; overflow-wrap: anywhere; -webkit-hyphens: auto; hyphens: auto; text-align: center; }
/* Ajustes, Actualizacion: "Version instalada" se cortaba en su celda */
.main-column table.torrent-file-info td.tribe-info-label { white-space: normal !important; overflow: visible !important; text-overflow: clip !important; }
/* botones de icono del dev en las fichas de Ficheros y Torrents: 34px */
.main-column button.btn-singleview { min-width: 44px !important; min-height: 44px !important; }
/* titulos de tarjeta (videos, audios) y categorias de opinion (Amor, Interesante): 26px y 10,5px */
.main-column a.card-title-link { display: inline-flex !important; align-items: center; min-height: 44px; }
.main-column span.vote-category { font-size: 12px !important; }

View file

@ -22,8 +22,6 @@ 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",
@ -165,8 +163,6 @@ 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",
@ -313,8 +309,6 @@ module.exports = {
bbMenuClose: "Tanca",
uxPickHint: "Toca'n un i queda desat. El pots canviar quan vulguis a Configuració.",
uxPickCurrent: "En ús",
pubBarJoining: "T'estàs unint al pub… pot trigar fins a un minut. No tanquis l'app.",
pubBarJoinFailed: "Si no has entrat al pub, el més habitual és: no hi ha connexió (wifi o dades), el pub ha caigut o la invitació ja s'ha fet servir. Prova amb un altre pub de la llista o més tard.",
pubBarDownload: "Descarregant la xarxa de {pub} ({tier})…",
pubBarTierRecent: "el més recent",
pubBarTierFull: "tot l'anterior",
@ -451,8 +445,6 @@ module.exports = {
bbMenuClose: "Pechar",
uxPickHint: "Toca un e queda gardado. Podes cambialo cando queiras en Axustes.",
uxPickCurrent: "En uso",
pubBarJoining: "Uníndote ao pub… pode tardar ata un minuto. Non peches a app.",
pubBarJoinFailed: "Se non entraches no pub, o máis habitual é: non hai conexión (wifi ou datos), o pub está caído ou o convite xa se usou. Proba con outro pub da lista ou máis tarde.",
pubBarDownload: "Descargando a rede de {pub} ({tier})…",
pubBarTierRecent: "o máis recente",
pubBarTierFull: "todo o anterior",
@ -590,8 +582,6 @@ 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",
@ -733,8 +723,6 @@ 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",
@ -876,8 +864,6 @@ 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",
@ -1019,8 +1005,6 @@ 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",
@ -1162,8 +1146,6 @@ 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",
@ -1305,8 +1287,6 @@ module.exports = {
bbMenuClose: "关闭",
uxPickHint: "点一下即保存。随时可以在设置中更改。",
uxPickCurrent: "使用中",
pubBarJoining: "正在加入 pub……最多可能需要一分钟。请不要关闭应用。",
pubBarJoinFailed: "如果没有进入 pub,常见原因是:没有网络(wifi 或流量)、pub 已停止服务,或邀请已被使用。请尝试列表中的其他 pub,或稍后再试。",
pubBarDownload: "正在从 {pub} 下载网络({tier})……",
pubBarTierRecent: "最新内容",
pubBarTierFull: "之前的全部",
@ -1448,8 +1428,6 @@ module.exports = {
bbMenuClose: "إغلاق",
uxPickHint: "المس واحدًا ويُحفظ. يمكنك تغييره في أي وقت من الإعدادات.",
uxPickCurrent: "قيد الاستخدام",
pubBarJoining: "جارٍ الانضمام إلى الـ pub… قد يستغرق ذلك دقيقة. أبقِ التطبيق مفتوحًا.",
pubBarJoinFailed: "إن لم تدخل إلى الـ pub، فالأسباب المعتادة: لا يوجد اتصال (واي فاي أو بيانات)، أو الـ pub متوقف، أو الدعوة استُخدمت من قبل. جرّب pub آخر من القائمة أو لاحقًا.",
pubBarDownload: "جارٍ تنزيل الشبكة من {pub} ({tier})…",
pubBarTierRecent: "الأحدث",
pubBarTierFull: "كل ما سبق",
@ -1591,8 +1569,6 @@ module.exports = {
bbMenuClose: "बंद करें",
uxPickHint: "एक पर टैप करें और वह सेव हो जाएगा। सेटिंग्स में इसे कभी भी बदल सकते हैं।",
uxPickCurrent: "चालू",
pubBarJoining: "pub से जुड़ रहे हैं… इसमें एक मिनट तक लग सकता है। ऐप खुला रखें।",
pubBarJoinFailed: "अगर आप pub में नहीं जा पाए, तो आम वजहें हैं: कनेक्शन नहीं है (वाई-फ़ाई या डेटा), pub बंद है, या निमंत्रण पहले ही इस्तेमाल हो चुका है। सूची से कोई दूसरा pub आज़माएँ, या बाद में।",
pubBarDownload: "{pub} से नेटवर्क डाउनलोड हो रहा है ({tier})…",
pubBarTierRecent: "सबसे नया",
pubBarTierFull: "पहले का सब कुछ",
@ -1734,8 +1710,6 @@ module.exports = {
bbMenuClose: "Закрыть",
uxPickHint: "Нажмите на вариант, и он сохранится. Изменить можно в любой момент в Настройках.",
uxPickCurrent: "Сейчас",
pubBarJoining: "Подключение к пабу… это может занять до минуты. Не закрывайте приложение.",
pubBarJoinFailed: "Если не удалось войти в паб, обычно причина такая: нет связи (wifi или мобильные данные), паб недоступен или приглашение уже использовано. Попробуйте другой паб из списка или позже.",
pubBarDownload: "Загрузка сети с {pub} ({tier})…",
pubBarTierRecent: "самое новое",
pubBarTierFull: "всё остальное",

View file

@ -1,31 +0,0 @@
// Hoja del menu (pestaña Menu de la barra de abajo). Abre y cierra con una casilla
// y CSS; esto solo hace que el boton atras de Android cierre la hoja en vez de
// cambiar de pagina. Ver docs/ESTUDIO_NAVEGACION.md.
(function () {
var box = document.getElementById('oasis-menu-toggle');
if (!box || !window.history || !history.pushState) return;
var sheet = document.querySelector('.menu-sheet');
var pending = null;
if (history.state && history.state.oasisMenu) box.checked = true;
box.addEventListener('change', function () {
if (box.checked) { history.pushState({ oasisMenu: true }, ''); }
else if (history.state && history.state.oasisMenu) { history.back(); }
});
window.addEventListener('popstate', function () {
if (box.checked) box.checked = false;
// se navega despues de que la vuelta atras termine (en WebView 69, si no, se
// perdia la entrada nueva)
if (pending) setTimeout(follow, 50);
});
function follow() { if (pending) { var go = pending; pending = null; location.href = go; } }
// al ir a un modulo desde la hoja, primero se quita la entrada de la hoja: si no,
// al volver atras desde el modulo la portada aparecia con la hoja abierta
if (sheet) sheet.addEventListener('click', function (e) {
var a = e.target.closest ? e.target.closest('a[href]') : null;
if (!a || !(history.state && history.state.oasisMenu)) return;
e.preventDefault();
pending = a.href;
history.back();
setTimeout(follow, 1000);
});
})();

View file

@ -1,72 +0,0 @@
// 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

@ -146,8 +146,7 @@ exports.backupView = async ({ type = "RECOVERY", options = null, restoreJob = nu
div({ class: "div-center backup-wrap" }, body)
)
);
// 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;
return restoreJob && restoreJob.running ? html.replace('</head>', '<meta http-equiv="refresh" content="5"></head>') : html;
};
exports.renderRestoreStatus = renderRestoreStatus;

View file

@ -7,7 +7,7 @@
// cada integracion. Aqui solo depende de i18n, que se inyecta porque main_views
// lo muta en cada peticion sin reasignarlo.
const { a, button, div, form, h2, h3, img, input, label, nav, p, script, section, span } = require("../server/node_modules/hyperaxe");
const { a, button, div, form, h2, h3, img, input, nav, p, section, span } = require("../server/node_modules/hyperaxe");
const { getConfig } = require("../configs/config-manager.js");
const sharedState = require("../configs/shared-state");
const { iconNode, iconKeyOf } = require("./mobile_icons");
@ -109,34 +109,41 @@ module.exports = ({ i18n }) => {
.map(key => PINNABLE_MODULES.find(m => m.key === key))
.filter(m => m && modOn(m.mod))
.slice(0, MAX);
// Publicar: el /publish del dev va siempre a crear un blog, y sin blogs (Empleo,
// Negocio) acababa en /modules. Aqui, al primer sitio de escribir que este encendido
const WRITE_TO = [['blogsMod', '/blogs?filter=CREATE'], ['feedMod', '/feed/create'], ['forumMod', '/forum?filter=create'], ['wikiMod', '/wiki?filter=create']];
const writeTo = (WRITE_TO.find(([mod]) => modOn(mod)) || [null, '/publish'])[1];
const slots = pins.map(m => bbLink(
m.href, m.glyph, i18n[m.labelKey] || m.key, m.key === 'inbox' ? inboxN : 0
m.key === 'publish' ? writeTo : m.href, m.glyph, i18n[m.labelKey] || m.key, m.key === 'inbox' ? inboxN : 0
));
// quinta pestaña fija: Menu abre desde abajo una hoja con todos los modulos (el
// lanzador). Sin JS: una casilla y CSS. autocomplete=off para que al volver atras
// el navegador no la deje abierta. Ver docs/ESTUDIO_NAVEGACION.md
// lanzador). Sin JS (la CSP de la 1.3.5 no deja ninguno): un ancla #oasis-menu y
// :target. Abrir deja una entrada en el historial, asi que atras cierra la hoja.
// Cerrar va a un ancla que no existe para que la pagina no salte arriba.
// Ver docs/ESTUDIO_NAVEGACION.md
const menuLabel = i18n.bbMenu || 'Menu';
const closeLabel = i18n.bbMenuClose || 'Close';
return nav(
{ class: "oasis-bottombar", attrs: { "aria-label": i18n.bbQuickActions || "Quick actions" } },
input({ type: 'checkbox', id: 'oasis-menu-toggle', class: 'menu-toggle', autocomplete: 'off', 'aria-label': menuLabel }),
{ id: 'oasis-menu', class: "oasis-bottombar", attrs: { "aria-label": i18n.bbQuickActions || "Quick actions" } },
...slots,
label({ class: 'bb-item bb-menu', for: 'oasis-menu-toggle' },
a({ class: 'bb-item bb-menu', href: '#oasis-menu' },
iconNode('bb-ico', 'modules', '▦'),
span({ class: 'bb-lbl' }, menuLabel)
),
label({ class: 'menu-sheet-backdrop', for: 'oasis-menu-toggle', 'aria-hidden': 'true' }),
a({ class: 'bb-item bb-menu bb-menu-close', href: '#menu-cerrado', 'aria-label': closeLabel },
iconNode('bb-ico', 'modules', '▦'),
span({ class: 'bb-lbl' }, menuLabel)
),
a({ class: 'menu-sheet-backdrop', href: '#menu-cerrado', 'aria-hidden': 'true', tabindex: '-1' }),
div({ class: 'menu-sheet', role: 'dialog', 'aria-label': menuLabel },
div({ class: 'menu-sheet-head' },
span({ class: 'menu-sheet-title' }, menuLabel),
label({ class: 'menu-sheet-close', for: 'oasis-menu-toggle', 'aria-label': i18n.bbMenuClose || 'Close' }, '✕')
a({ class: 'menu-sheet-close', href: '#menu-cerrado', 'aria-label': closeLabel }, '✕')
),
renderHiveNav()
),
// atras cierra la hoja (sin este script el menu funciona igual)
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' })
// unirse a un pub: barra fija encima de esta (mobile_pubbar.js)
require('./mobile_pubbar').pubBar()
);
};

View file

@ -6,8 +6,7 @@
// 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.
// falla. La pinta hive_views.js (la barra de abajo es nuestra) en cada pantalla.
//
// 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).
@ -95,22 +94,17 @@ const backupStatus = (job, i18n) => {
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
// la barra, solo si hay algo que contar. Sin JS (la CSP de la 1.3.5 no deja ninguno)
// se pone al dia al cambiar de pantalla
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 })
},
if (!s) return null;
return div({ id: "pub-bar", class: `pub-bar is-${s.kind}`, role: "status", "data-running": s.running ? "1" : "0" },
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) } : {}) })
p({ class: "pub-bar-text" }, s.text),
s.hint ? p({ class: "pub-bar-hint" }, s.hint) : null,
progress({ class: "pub-bar-meter", max: "100", ...(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" }, "✕"))

View file

@ -92,11 +92,11 @@ const TRIBE_MAIN_TABS = ['activity', 'inhabitants', 'governance', 'votations', '
const tribeTabs = (tribe, section, sections) => {
const i18n = t();
const all = sections.flatMap(g => g.items);
const tab = (s) => a({ href: `/tribe/${encodeURIComponent(tribe.id)}?section=${encodeURIComponent(s.key)}`, class: section === s.key ? 'tribe-tab is-current' : 'tribe-tab', ...(section === s.key ? { 'aria-current': 'page' } : {}) }, s.label);
const tab = (s) => a({ href: `/tribe/${encodeURIComponent(tribe.id)}?section=${encodeURIComponent(s.key)}#tribe-tabs`, class: section === s.key ? 'tribe-tab is-current' : 'tribe-tab', ...(section === s.key ? { 'aria-current': 'page' } : {}) }, s.label);
const main = all.filter(s => TRIBE_MAIN_TABS.includes(s.key));
const rest = all.filter(s => !TRIBE_MAIN_TABS.includes(s.key));
const currentRest = rest.find(s => s.key === section);
return nav({ class: 'tribe-tabs', 'aria-label': i18n.tribeSectionsLabel || 'Sections' },
return nav({ id: 'tribe-tabs', class: 'tribe-tabs', 'aria-label': i18n.tribeSectionsLabel || 'Sections' },
div({ class: 'tribe-tabs-strip' }, ...(currentRest ? [tab(currentRest)] : []), ...main.map(tab)),
details({ class: 'tribe-tabs-more' },
summary(i18n.tribeSectionsMore || 'More'),

View file

@ -149,6 +149,5 @@ exports.welcomeView = async (status, profile = {}) => {
button({ type: "submit", class: "welcome-skip-btn" }, i18n.welcomeSkip)))
: null
)
// 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
return status.bootstrap && status.bootstrap.running ? html.replace('</head>', '<meta http-equiv="refresh" content="5"></head>') : html
}