chore: retire unused stacks and CrowdSec dashboards

Remove stacks no longer in use: ghost, ghost-me, changedetection, pdf
(Stirling PDF), it (IT-Tools), finance (Actual Budget), bookstack, dozzle,
beszel, memos, vikunja, onetimesecret and DEPRECATED-kasm.

CrowdSec keeps only the engine (the Traefik bouncer is unchanged). Drop
Grafana, Prometheus, crowdsec-postgres and metrics-export.sh, which only
fed the local dashboard; CrowdSec itself uses SQLite.

Also drop references to these services, THSCom, Scheduler and the
SpinTools OpenProject from Kopia paths/dumps and ops-report endpoints,
and replace the DEPRECATED- convention in the README with the removal
procedure used here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Eduardo David Paredes Vara
2026-10-08 00:59:42 +00:00
parent 382253d41c
commit 634028d508
143 changed files with 19 additions and 16649 deletions
-38
View File
@@ -1,38 +0,0 @@
# Kasm
Stack para desplegar [Kasm](https://www.kasmweb.com/) en `kasm.sherlockhomeless.net` detrás de Traefik y Authentik.
## 🚀 Despliegue con Docker Compose
Asegurate de tener `kasm/.env` configurado y despliega:
```bash
cd kasm
docker compose --env-file .env up -d
```
## ⚙️ Variables importantes
Edita `kasm/.env`:
```env
KASM_DOMAIN=kasm.sherlockhomeless.net
KASM_OPT_PATH=/opt/kasm/opt
TRAEFIK_AUTH_MIDDLEWARE=ths-authentik@docker
```
## 📁 Preparación del host
Antes del primer arranque:
```bash
mkdir -p /opt/kasm/opt
```
## 🔐 Autenticación y acceso
Este stack publica Kasm por Traefik con HTTPS y lo protege con `ths-authentik@docker`.
## 🏠 Integración con Homepage
El servicio publica labels `homepage.*` para aparecer automáticamente en Homepage dentro del grupo **Infraestructura**.
-226
View File
@@ -1,226 +0,0 @@
services:
db:
image: kasmweb/postgres:${KASM_VERSION}
container_name: kasm_db
restart: unless-stopped
stop_signal: SIGTERM
stop_grace_period: 60s
environment:
POSTGRES_PASSWORD: ${KASM_DB_PASSWORD}
POSTGRES_USER: kasmapp
POSTGRES_DB: kasm
command: postgres -c ssl=on -c ssl_cert_file=/etc/ssl/certs/db_server.crt -c ssl_key_file=/etc/ssl/certs/db_server.key -c config_file=/var/lib/postgresql/conf/postgresql.conf -c hba_file=/var/lib/postgresql/conf/pg_hba.conf
healthcheck:
test: "pg_isready --username=$$POSTGRES_USER --dbname=$$POSTGRES_DB && cat /proc/1/cmdline | grep -q '^postgres'"
timeout: 5s
retries: 20
volumes:
- ${KASM_DB_INIT_SQL}:/docker-entrypoint-initdb.d/current.sql:ro,Z
- ${KASM_ROOT_PATH}/conf/database/pg_hba.conf:/var/lib/postgresql/conf/pg_hba.conf:ro,Z
- ${KASM_ROOT_PATH}/conf/database/postgresql.conf:/var/lib/postgresql/conf/postgresql.conf:ro,Z
- ${KASM_ROOT_PATH}/certs/db_server.crt:/etc/ssl/certs/db_server.crt:ro,Z
- ${KASM_ROOT_PATH}/certs/db_server.key:/etc/ssl/certs/db_server.key:ro,Z
- ${KASM_ROOT_PATH}/log/postgres:/var/log/postgres:Z
- ${KASM_ROOT_PATH}/tmp/kasm_db:/tmp:Z
- ${KASM_DB_DATA_PATH}:/var/lib/postgresql/data:Z
networks:
- kasm_default_network
labels:
prune.protect: "true"
kasm_api:
image: kasmweb/api:${KASM_VERSION}
container_name: kasm_api
user: "${KASM_UID}:${KASM_GID}"
read_only: true
restart: unless-stopped
depends_on:
db:
condition: service_healthy
healthcheck:
test: "/usr/bin/healthcheck.sh http://localhost:8080/api/__healthcheck \"\" 5 60"
timeout: 30s
interval: 10s
retries: 3
start_period: 60s
volumes:
- ${KASM_ROOT_PATH}:/opt/kasm/current:Z
- ${KASM_ROOT_PATH}/healthcheck/kasm_api:/healthcheck:Z
- ${KASM_ROOT_PATH}/tmp/api:/tmp:Z
networks:
- kasm_default_network
labels:
prune.protect: "true"
kasm_manager:
image: kasmweb/manager:${KASM_VERSION}
container_name: kasm_manager
user: "${KASM_UID}:${KASM_GID}"
read_only: true
restart: unless-stopped
depends_on:
db:
condition: service_healthy
healthcheck:
test: "/usr/bin/healthcheck.sh http://localhost:8181/__healthcheck \"\" 5 60"
timeout: 30s
interval: 10s
retries: 3
volumes:
- ${KASM_ROOT_PATH}:/opt/kasm/current:Z
- ${KASM_ROOT_PATH}/healthcheck/kasm_manager:/healthcheck:Z
- ${KASM_ROOT_PATH}/tmp/manager:/tmp:Z
networks:
- kasm_default_network
labels:
prune.protect: "true"
kasm_agent:
image: kasmweb/agent:${KASM_VERSION}
container_name: kasm_agent
user: root
read_only: true
restart: unless-stopped
depends_on:
- kasm_manager
healthcheck:
test: "/usr/bin/healthcheck.sh http://localhost:4444/__healthcheck \"\" 30 90 15"
timeout: 30s
interval: 30s
retries: 3
volumes:
- ${KASM_ROOT_PATH}:/opt/kasm/current:Z
- ${KASM_ROOT_PATH}/healthcheck/kasm_agent:/healthcheck:Z
- /var/run/docker.sock:/var/run/docker.sock
- ${KASM_ROOT_PATH}/conf/nginx:/etc/nginx/conf.d:Z
networks:
- kasm_default_network
labels:
prune.protect: "true"
kasm_guac:
image: kasmweb/kasm-guac:${KASM_VERSION}
container_name: kasm_guac
user: "${KASM_UID}:${KASM_GID}"
read_only: true
init: true
restart: unless-stopped
healthcheck:
test: "/usr/bin/healthcheck.sh http://localhost:3000/__healthcheck \"\" 30 90"
interval: 60s
timeout: 30s
retries: 3
volumes:
- ${KASM_ROOT_PATH}/log:/opt/kasm/current/log:Z
- ${KASM_ROOT_PATH}/tmp/guac:/opt/kasm/current/tmp/guac:Z
- ${KASM_ROOT_PATH}/conf/app/guac:/opt/kasm/current/conf/app/guac:Z
- ${KASM_ROOT_PATH}/tmp/guac:/tmp:Z
- ${KASM_ROOT_PATH}/healthcheck/kasm_guac:/healthcheck:Z
networks:
- kasm_default_network
labels:
prune.protect: "true"
rdp_gateway:
image: kasmweb/rdp-gateway:${KASM_VERSION}
container_name: kasm_rdp_gateway
user: "${KASM_UID}:${KASM_GID}"
read_only: true
restart: unless-stopped
depends_on:
- kasm_api
healthcheck:
test: "/usr/bin/healthcheck.sh http://localhost:5555/__healthcheck 3389 1 30"
interval: 60s
timeout: 30s
start_period: 30s
volumes:
- ${KASM_ROOT_PATH}/log:/opt/kasm/current/log:Z
- ${KASM_ROOT_PATH}/conf/app/rdp_gateway:/usr/local/etc/rdpproxy/conf:Z
- ${KASM_ROOT_PATH}/certs/kasm_nginx.key:/usr/local/etc/rdpproxy/rdpproxy.key:ro,Z
- ${KASM_ROOT_PATH}/certs/kasm_nginx.crt:/usr/local/etc/rdpproxy/rdpproxy.crt:ro,Z
- ${KASM_ROOT_PATH}/tmp/rdpgw/var:/usr/local/etc/rdpproxy/var:Z
- ${KASM_ROOT_PATH}/tmp/rdpgw/tmp:/tmp:Z
- ${KASM_ROOT_PATH}/healthcheck/rdp_gateway:/healthcheck:Z
networks:
- kasm_default_network
labels:
prune.protect: "true"
kasm_rdp_https_gateway:
image: kasmweb/rdp-https-gateway:${KASM_VERSION}
container_name: kasm_rdp_https_gateway
user: "${KASM_UID}:${KASM_GID}"
read_only: true
restart: unless-stopped
depends_on:
proxy:
condition: service_started
healthcheck:
test: "/usr/bin/healthcheck.sh https://localhost:9443/rdp-https-gateway/__healthcheck \"\" 1 30"
interval: 60s
timeout: 30s
start_period: 30s
volumes:
- ${KASM_ROOT_PATH}/log:/opt/kasm/current/log:Z
- ${KASM_ROOT_PATH}/conf/app/rdp_https_gateway:/opt/rdpgw/conf:Z
- ${KASM_ROOT_PATH}/certs/kasm_nginx.key:/opt/rdpgw/key.pem:ro,Z
- ${KASM_ROOT_PATH}/certs/kasm_nginx.crt:/opt/rdpgw/server.pem:ro,Z
- ${KASM_ROOT_PATH}/healthcheck/rdp_https_gateway:/healthcheck:Z
networks:
- kasm_default_network
labels:
prune.protect: "true"
proxy:
image: kasmweb/proxy:${KASM_VERSION}
container_name: kasm_proxy
restart: unless-stopped
depends_on:
kasm_manager:
condition: service_healthy
kasm_api:
condition: service_healthy
kasm_agent:
condition: service_started
kasm_guac:
condition: service_started
volumes:
- ${KASM_ROOT_PATH}/conf/nginx:/etc/nginx/conf.d:ro,Z
- ${KASM_ROOT_PATH}/certs/kasm_nginx.key:/etc/ssl/private/kasm_nginx.key:ro,Z
- ${KASM_ROOT_PATH}/certs/kasm_nginx.crt:/etc/ssl/certs/kasm_nginx.crt:ro,Z
- ${KASM_ROOT_PATH}/log/nginx:/var/log/external/nginx:Z
- ${KASM_ROOT_PATH}/log/logrotate:/var/log/external/logrotate:Z
networks:
- kasm_default_network
- kasm_sidecar_network
- proxy
labels:
prune.protect: "true"
traefik.enable: "true"
traefik.docker.network: "${TRAEFIK_DOCKER_NETWORK}"
traefik.http.routers.kasm.rule: "Host(`${KASM_DOMAIN}`)"
traefik.http.routers.kasm.entrypoints: "${TRAEFIK_ENTRYPOINT_SECURE}"
traefik.http.routers.kasm.tls: "true"
traefik.http.routers.kasm.tls.certresolver: "${TRAEFIK_CERTRESOLVER}"
traefik.http.routers.kasm.middlewares: "crowdsec-bouncer@file,${TRAEFIK_AUTH_MIDDLEWARE}"
traefik.http.services.kasm.loadbalancer.server.port: "3000"
traefik.http.services.kasm.loadbalancer.server.scheme: "https"
traefik.http.services.kasm.loadbalancer.passhostheader: "true"
homepage.group: "Infraestructura"
homepage.name: "Kasm"
homepage.icon: "https://www.kasmweb.com/favicon.ico"
homepage.description: "Workspaces en navegador"
homepage.href: "https://${KASM_DOMAIN}"
homepage.siteMonitor: "https://kasm_proxy:3000/"
homepage.weight: "350"
networks:
kasm_default_network:
name: kasm_default_network
driver: bridge
kasm_sidecar_network:
name: kasm_sidecar_network
driver: bridge
proxy:
external: true
-10
View File
@@ -1,10 +0,0 @@
TZ=Europe/Madrid
KASM_IMAGE=lscr.io/linuxserver/kasm:latest
KASM_DOMAIN=app.example.com
KASM_OPT_PATH=/var/lib/kasm
TRAEFIK_DOCKER_NETWORK=proxy
TRAEFIK_ENTRYPOINT_SECURE=websecure
TRAEFIK_CERTRESOLVER=letsencrypt
TRAEFIK_AUTH_MIDDLEWARE=ths-authentik@docker
SERVICE_URL_KASM=https://app.example.com
SERVICE_FQDN_KASM=app.example.com
+14 -11
View File
@@ -82,10 +82,8 @@ Usa `docker compose --env-file .env -f <ruta>/docker-compose.yml up -d` si prefi
|---|---|---|---|
| Traefik | Reverse proxy con TLS automatico | `Traefik/` | [README](Traefik/README.md) |
| Authentik | SSO y Forward Auth | `authentik/` | [README](authentik/README.md) |
| CrowdSec | IPS/IDS, bouncer y Grafana | `crowdsec/` | [README](crowdsec/README.md) |
| CrowdSec | IPS/IDS y bouncer | `crowdsec/` | [README](crowdsec/README.md) |
| Homepage | Dashboard principal | `homepage/` | [README](homepage/README.md) |
| Dozzle | Visor de logs Docker | `dozzle/` | [README](dozzle/README.md) |
| Beszel | Monitorizacion ligera | `beszel/` | [README](beszel/README.md) |
| Gitea | Git autoalojado | `gitea/` | [README](gitea/README.md) |
| n8n | Automatizacion de workflows | `n8n/` | [README](n8n/README.md) |
| Mail Relay | Relay SMTP interno | `mail-relay/` | [README](mail-relay/README.md) |
@@ -171,13 +169,18 @@ Si un `.env` no se aplica:
- CrowdSec debe ir antes de Authentik en la cadena de middlewares cuando ambos se usen.
- Las instrucciones antiguas de Portainer se consideran obsoletas.
## Stacks deprecados
## Retirar un stack
Los stacks descartados de la infra (ya no se despliegan) se prefijan con
`DEPRECATED-` (ej. `DEPRECATED-kasm/`) en vez de borrarse, para conservar
la config por si hace falta consultarla. Antes de renombrar:
Los stacks descartados se borran del repo (el historial de git conserva la
config). Antes de borrar:
1. Confirmar que no queden contenedores corriendo (`docker ps -a --filter name=<stack>`).
2. Quitar/inhabilitar su ruta en `/opt/traefik/dynamic/` si tenía una.
3. Quitar su entrada de Homepage (labels en el compose o `services.yaml`).
4. `git mv <stack> DEPRECATED-<stack>` (preserva historial).
1. `docker compose --env-file .env down` desde la carpeta del stack.
2. Backup en `/opt/backups/removal-<fecha>-<stack>/` (`chmod 600`): datos de
`/opt/<stack>`, la carpeta del repo con su `.env` y los ficheros de
`/opt/traefik/dynamic/` que se toquen.
3. Quitar su ruta en `/opt/traefik/dynamic/` (renombrar a `.bak-<ts>-removed`)
y su middleware en `sablier.yml` si lo tenía.
4. Quitar su entrada de Homepage (labels en el compose o `services.yaml`).
5. Borrar sus registros DNS con `cloudflared/cloudflare_dns.py delete <nombre>`.
6. Quitar referencias en `backups/kopia/` y `n8n-observability/scripts/ops-report`.
7. Borrar los datos en `/opt/<stack>` y `git rm -r <stack>`.
+3 -3
View File
@@ -33,10 +33,10 @@ The snapshot paths are listed in `backup-paths.txt`. The important choices are:
- Gitea: `/opt/gitea/data` and runner data.
- Nextcloud: config, themes and a MariaDB dump only. `/opt/nextcloud/data`, `/opt/nextcloud/custom_apps` and `/opt/nextcloud/html` are excluded.
- Databases: PostgreSQL and MariaDB dumps are written to `/opt/kopia/staging/dumps/latest`.
- Service configuration: deployment repo, Traefik, Authentik, n8n, Memos, Vikunja, Trilium, Karakeep, AdGuard, WireGuard, mail relay, Beszel, Homepage, Dozzle, Kasm and OpenCode.
- Local active deployments: Scheduler, Spintools, THSCOM, OpenClaw restore state and OpenCode runtime state.
- Service configuration: deployment repo, Traefik, Authentik, n8n, Trilium, Karakeep, AdGuard, WireGuard, mail relay, Homepage and OpenCode.
- Local active deployments: Spintools, OpenClaw restore state and OpenCode runtime state.
Live database directories are excluded when a logical dump exists. Kasm's PostgreSQL database is backed up with `pg_dump`; Docker image/layer storage under `/opt/kasm/opt/docker` is intentionally excluded because it is rebuildable and large. OpenProject's embedded PostgreSQL data is not dumped by this first version; treat its restore as best-effort unless a service-specific dump command is added.
Live database directories are excluded when a logical dump exists.
## Install Kopia
+1 -22
View File
@@ -21,8 +21,6 @@
# App configuration and small persistent data.
/opt/n8n/data
/opt/authentik/redis
/opt/memos/data
/opt/vikunja/files
/opt/trilium/data
/opt/karakeep/data
/opt/karakeep/meili_data
@@ -34,22 +32,9 @@
/opt/traefik/certs
/opt/mail-relay/opendkim
/opt/mail-relay/secrets
/opt/beszel/data
/opt/beszel/agent
/opt/homepage/config
/opt/dozzle/data
/opt/guacamole/admin-credentials.env
# Kasm Workspaces: DB dump plus install/config state required for restore.
# Docker image/layer storage under /opt/kasm/opt/docker is intentionally omitted.
/opt/kasm/1.18.1/bin
/opt/kasm/1.18.1/certs
/opt/kasm/1.18.1/conf
/opt/kasm/1.18.1/docker
/opt/kasm/1.18.1/file_mappings
/opt/kasm/1.18.1/licenses
/opt/kasm/1.18.1/plugins
# OpenCode Web UI service configuration and runtime state.
/etc/opencode-web.env
/home/felidae/.config/opencode
@@ -66,16 +51,10 @@
/opt/onlyoffice/lib
/opt/onlyoffice/postgresql
# Scheduler: keep PostgreSQL data volume and logical dump, but not source tree.
/var/lib/docker/volumes/scheduler_app_postgres_data/_data
# Spintools / THSCOM.
# Spintools.
/opt/spintools/prod
/opt/spintools/openproject/assets
/opt/spintools/openproject-token
/opt/spintools/authentik/redis
/var/lib/docker/volumes/spintools_ruleta_static/_data
/var/lib/docker/volumes/thscom-backend-media/_data
# OpenClaw runtime state, excluding workspaces via exclude file.
/home/felidae/.openclaw
-10
View File
@@ -7,32 +7,22 @@
/opt/paperless/media/.thumbnails
# Live database directories are handled by logical dumps when possible.
/var/lib/docker/volumes/kasm_db_1.18.1
/opt/kasm/opt/docker
/opt/kasm/1.18.1/log
/opt/kasm/1.18.1/tmp
/opt/paperless/pgdata
/opt/gitea/postgres
/opt/n8n/postgres
/opt/authentik/postgres
/opt/memos/postgres
/opt/vikunja/postgres
/opt/nextcloud/db
/opt/spintools/authentik/postgres
/opt/spintools/openproject/pgdata
# Rebuildable caches and dependencies.
**/node_modules
node_modules
frontend/*/node_modules
/home/felidae/scheduler_app/frontend/*/node_modules
**/.next/cache
frontend/*/.next
/home/felidae/scheduler_app/frontend/*/.next
**/__pycache__
**/.pytest_cache
**/.mypy_cache
/home/felidae/scheduler_app/.mypy_cache
**/.ruff_cache
**/.cache
**/cache
-5
View File
@@ -73,13 +73,8 @@ dump_mariadb() {
dump_postgres "paperless-db" "paperless" "paperless"
dump_postgres "gitea-postgres" "gitea" "gitea"
dump_postgres "n8n-pg" "n8n" "n8n"
dump_postgres "vikunja-db" "vikunja" "vikunja"
dump_postgres "memos-db" "memos" "memos"
dump_postgres "ths-authentik-postgres" "authentik" "authentik"
dump_postgres "scheduler-db" "scheduler" "scheduler"
dump_postgres "guacamole-postgres" "guacamole" "guacamole"
dump_postgres "kasm_db" "kasmapp" "kasm"
dump_postgres "thscom-db" "postgres" "postgres"
dump_postgres "spintools-db" "postgres" "postgres"
dump_postgres "spintools-authentik-postgres" "authentik" "authentik"
dump_postgres "charge-card-lab-postgres" "charge_card_lab" "charge_card_lab"
-55
View File
@@ -1,55 +0,0 @@
# Beszel
Stack para desplegar [Beszel](https://beszel.dev/) en `beszel.sherlockhomeless.net`, con el **hub** publicado por Traefik y un **agent** local para monitorizar este mismo host Docker.
## 🚀 Despliegue con Docker Compose
Asegurate de tener `beszel/.env` configurado y despliega:
```bash
cd beszel
docker compose --env-file .env up -d
```
## ⚙️ Variables importantes
Edita `beszel/.env`:
```env
BESZEL_DOMAIN=beszel.sherlockhomeless.net
BESZEL_APP_URL=https://beszel.sherlockhomeless.net
BESZEL_DATA_PATH=/opt/beszel/data
BESZEL_SOCKET_PATH=/opt/beszel/socket
BESZEL_AGENT_DATA_PATH=/opt/beszel/agent
BESZEL_AGENT_TOKEN=pega-aqui-tu-token
BESZEL_AGENT_KEY="ssh-ed25519 AAAA..."
```
## 📁 Preparación del host
Crea los directorios persistentes antes del primer arranque:
```bash
mkdir -p /opt/beszel/data /opt/beszel/socket /opt/beszel/agent
```
## 🔐 Autenticación y acceso
Beszel sí incluye autenticación propia, soporte multiusuario y OIDC, así que el router de Traefik no añade `ths-authentik@docker` por defecto para evitar doble login.
El acceso externo queda expuesto por HTTPS en `https://beszel.sherlockhomeless.net`, pero la autenticación se resuelve dentro del propio hub de Beszel. Si más adelante quieres federarlo con Authentik, lo natural es configurar OIDC dentro de Beszel en lugar de meter un forward auth delante.
## 🔌 Alta del agente local
Este stack monta un socket Unix compartido entre hub y agent, que es la forma recomendada por Beszel para monitorizar el mismo host sin depender de IPs internas.
Después de crear tu usuario administrador en la UI:
1. Ve a **Add System** y copia la clave pública y el token.
2. Pégalos en `BESZEL_AGENT_KEY` y `BESZEL_AGENT_TOKEN`.
3. Actualiza el stack.
4. En el campo **Host / IP** del sistema usa `/beszel_socket/beszel.sock`.
## 🏠 Integración con Homepage
El hub publica labels `homepage.*`, así que Homepage puede mostrarlo automáticamente dentro del grupo **Observabilidad** apuntando a `https://beszel.sherlockhomeless.net`.
-49
View File
@@ -1,49 +0,0 @@
services:
beszel:
image: ${BESZEL_IMAGE}
container_name: beszel
restart: unless-stopped
environment:
TZ: ${TZ}
APP_URL: ${BESZEL_APP_URL}
volumes:
- ${BESZEL_DATA_PATH}:/beszel_data:Z
- ${BESZEL_SOCKET_PATH}:/beszel_socket:Z
networks:
- proxy
labels:
traefik.enable: "true"
traefik.docker.network: "${TRAEFIK_DOCKER_NETWORK}"
traefik.http.routers.beszel.rule: "Host(`${BESZEL_DOMAIN}`)"
traefik.http.routers.beszel.entrypoints: "${TRAEFIK_ENTRYPOINT_SECURE}"
traefik.http.routers.beszel.tls: "true"
traefik.http.routers.beszel.tls.certresolver: "${TRAEFIK_CERTRESOLVER}"
traefik.http.services.beszel.loadbalancer.server.port: "${BESZEL_HTTP_PORT}"
homepage.group: "Observabilidad"
homepage.name: "Beszel"
homepage.icon: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/beszel.svg"
homepage.description: "Monitorización ligera de host y contenedores"
homepage.href: "https://${BESZEL_DOMAIN}"
homepage.siteMonitor: "http://beszel:8090/"
homepage.showStats: "false"
homepage.weight: "200"
beszel-agent:
image: ${BESZEL_AGENT_IMAGE}
container_name: beszel-agent
restart: unless-stopped
network_mode: host
volumes:
- ${BESZEL_AGENT_DATA_PATH}:/var/lib/beszel-agent:Z
- ${BESZEL_SOCKET_PATH}:/beszel_socket:Z
- /var/run/docker.sock:/var/run/docker.sock:ro
environment:
TZ: ${TZ}
LISTEN: /beszel_socket/beszel.sock
HUB_URL: http://localhost:${BESZEL_HTTP_PORT}
TOKEN: ${BESZEL_AGENT_TOKEN}
KEY: ${BESZEL_AGENT_KEY}
networks:
proxy:
external: true
-16
View File
@@ -1,16 +0,0 @@
TZ=Europe/Madrid
BESZEL_IMAGE=henrygd/beszel:latest
BESZEL_AGENT_IMAGE=henrygd/beszel-agent:latest
BESZEL_DOMAIN=beszel.sherlockhomeless.net
BESZEL_APP_URL=https://beszel.sherlockhomeless.net
BESZEL_HTTP_PORT=8090
BESZEL_DATA_PATH=/opt/beszel/data
BESZEL_SOCKET_PATH=/opt/beszel/socket
BESZEL_AGENT_DATA_PATH=/opt/beszel/agent
BESZEL_AGENT_TOKEN=CAMBIAME_token_generado_en_beszel
BESZEL_AGENT_KEY=ssh-ed25519 CAMBIAME_clave_publica_generada_en_beszel
TRAEFIK_DOCKER_NETWORK=proxy
TRAEFIK_ENTRYPOINT_SECURE=websecure
TRAEFIK_CERTRESOLVER=letsencrypt
SERVICE_URL_BESZEL=https://beszel.sherlockhomeless.net
SERVICE_FQDN_BESZEL=beszel.sherlockhomeless.net
-41
View File
@@ -1,41 +0,0 @@
# BookStack
Base de conocimiento y documentación desplegada con MariaDB, acceso por Traefik y envío de correo mediante `mail-relay`.
## Arquitectura y propósito
`bookstack` sirve la aplicación y guarda adjuntos/configuración en `BOOKSTACK_DATA_PATH`; `bookstack-db` conserva páginas, permisos y estructura en MariaDB. La base sólo usa la red interna `bookstack`. La aplicación se añade a `proxy` para Traefik y a `mail_internal` para enviar correo sin publicar la base de datos ni el relay.
## Configuración
- `BOOKSTACK_DOMAIN` y `APP_URL` deben coincidir para que enlaces, cookies y redirecciones HTTPS sean válidos.
- `APP_KEY` cifra datos de aplicación: genérala una vez, mantenla estable y respáldala como secreto.
- Las variables `BOOKSTACK_DB_*` conectan ambos contenedores; las contraseñas deben existir sólo en `.env`.
- `BOOKSTACK_PUID` y `BOOKSTACK_PGID` deben tener acceso a `BOOKSTACK_DATA_PATH`.
- `BOOKSTACK_LANG` y `BOOKSTACK_DARK_MODE` fijan los valores iniciales de interfaz.
- `BOOKSTACK_MAIL_FROM*` identifica el remitente; el transporte usa el servicio interno `mail-relay`.
Separar datos y base permite actualizar la imagen sin perder contenido y respaldar cada componente con el método adecuado.
## Requisitos
- Docker Compose
- Redes externas `proxy` y `mail_internal`
- Un archivo `.env` local con las imágenes, dominio, rutas, credenciales de base de datos y `APP_KEY`
## Despliegue
```bash
cd bookstack
docker compose --env-file .env config
docker compose --env-file .env up -d
```
Los datos de BookStack y MariaDB se conservan en las rutas indicadas por `BOOKSTACK_DATA_PATH` y `BOOKSTACK_DB_PATH`.
## Operación
```bash
docker compose --env-file .env ps
docker compose --env-file .env logs -f bookstack
```
-66
View File
@@ -1,66 +0,0 @@
services:
bookstack-db:
image: ${BOOKSTACK_DB_IMAGE}
container_name: bookstack-db
restart: unless-stopped
labels:
prune.protect: "true"
environment:
MYSQL_ROOT_PASSWORD: ${BOOKSTACK_DB_ROOT_PASSWORD}
MYSQL_DATABASE: ${BOOKSTACK_DB_NAME}
MYSQL_USER: ${BOOKSTACK_DB_USER}
MYSQL_PASSWORD: ${BOOKSTACK_DB_PASSWORD}
TZ: ${TZ}
volumes:
- ${BOOKSTACK_DB_PATH}:/var/lib/mysql:Z
networks:
- bookstack
bookstack:
image: ${BOOKSTACK_IMAGE}
container_name: bookstack
restart: unless-stopped
depends_on:
- bookstack-db
environment:
PUID: ${BOOKSTACK_PUID}
PGID: ${BOOKSTACK_PGID}
TZ: ${TZ}
APP_KEY: ${APP_KEY}
APP_URL: https://${BOOKSTACK_DOMAIN}
DB_HOST: bookstack-db
DB_PORT: 3306
DB_DATABASE: ${BOOKSTACK_DB_NAME}
DB_USERNAME: ${BOOKSTACK_DB_USER}
DB_PASSWORD: ${BOOKSTACK_DB_PASSWORD}
APP_LANG: ${BOOKSTACK_LANG}
APP_DEFAULT_DARK_MODE: ${BOOKSTACK_DARK_MODE}
MAIL_DRIVER: smtp
MAIL_HOST: mail-relay
MAIL_PORT: 587
MAIL_FROM: ${BOOKSTACK_MAIL_FROM}
MAIL_FROM_NAME: ${BOOKSTACK_MAIL_FROM_NAME}
volumes:
- ${BOOKSTACK_DATA_PATH}:/config:Z
networks:
- bookstack
- proxy
- mail_internal
labels:
prune.protect: "true"
traefik.enable: "true"
traefik.docker.network: "${TRAEFIK_DOCKER_NETWORK}"
homepage.group: "Productividad"
homepage.name: "BookStack"
homepage.icon: "bookstack.png"
homepage.href: "https://${BOOKSTACK_DOMAIN}/"
homepage.description: "Base de conocimiento y documentacion"
homepage.siteMonitor: "https://${BOOKSTACK_DOMAIN}/"
networks:
bookstack:
driver: bridge
mail_internal:
external: true
proxy:
external: true
-29
View File
@@ -1,29 +0,0 @@
##### MariaDB BookStack #####
BOOKSTACK_DB_IMAGE=mariadb:11
BOOKSTACK_DB_ROOT_PASSWORD=change-me-root
BOOKSTACK_DB_NAME=bookstack
BOOKSTACK_DB_USER=bookstack
BOOKSTACK_DB_PASSWORD=change-me
BOOKSTACK_DB_PATH=/opt/bookstack/db
##### BookStack #####
BOOKSTACK_IMAGE=lscr.io/linuxserver/bookstack:latest
BOOKSTACK_PUID=1000
BOOKSTACK_PGID=1000
TZ=Europe/Madrid
BOOKSTACK_DOMAIN=wiki.example.com
APP_KEY=
BOOKSTACK_LANG=es
BOOKSTACK_DARK_MODE=true
BOOKSTACK_DATA_PATH=/opt/bookstack/data
##### Correo #####
BOOKSTACK_MAIL_FROM=bookstack@example.com
BOOKSTACK_MAIL_FROM_NAME=BookStack
##### Traefik #####
TRAEFIK_DOCKER_NETWORK=proxy
TRAEFIK_ENTRYPOINT_SECURE=websecure
TRAEFIK_CERTRESOLVER=letsencrypt
TRAEFIK_AUTH_MIDDLEWARE=ths-authentik@docker
-30
View File
@@ -1,30 +0,0 @@
# changedetection.io
changedetection.io published at `https://changedetection.sherlockhomeless.net`.
## Qué hace y cómo funciona
Supervisa páginas y avisa cuando cambia su contenido. `changedetection` guarda reglas e histórico en `CHANGEDETECTION_DATA_PATH`; `changedetection-browser` renderiza sitios con JavaScript mediante Playwright. El navegador sólo está en `changedetection_internal`, porque su puerto de depuración no debe exponerse.
## Configuración
- `CHANGEDETECTION_DOMAIN` forma `BASE_URL` y debe coincidir con Traefik.
- `CHANGEDETECTION_IMAGE` y `CHANGEDETECTION_BROWSER_IMAGE` fijan las versiones de aplicación y navegador.
- `CHANGEDETECTION_DATA_PATH` contiene toda la configuración que debe respaldarse.
- `PLAYWRIGHT_DRIVER_URL` ya apunta al sidecar; elige el fetcher Playwright dentro de la UI sólo para páginas que lo necesiten, ya que consume más recursos.
- El router dinámico combina CrowdSec, Authentik y Sablier: seguridad, control de identidad y arranque bajo demanda, respectivamente.
## Despliegue
```bash
docker compose --env-file .env config
docker compose --env-file .env up -d
```
The stack includes a Browserless Chrome sidecar for JavaScript-heavy pages.
The public route is defined in Traefik dynamic config and protected by:
- `crowdsec-bouncer@file`
- `ths-authentik@docker`
- `sablier-changedetection@file`
-45
View File
@@ -1,45 +0,0 @@
services:
changedetection:
image: ${CHANGEDETECTION_IMAGE}
container_name: changedetection
restart: unless-stopped
environment:
TZ: ${TZ}
BASE_URL: https://${CHANGEDETECTION_DOMAIN}
PLAYWRIGHT_DRIVER_URL: ws://changedetection-browser:3000
volumes:
- ${CHANGEDETECTION_DATA_PATH}:/datastore:Z
depends_on:
- changedetection-browser
networks:
- changedetection_internal
- proxy
labels:
prune.protect: "true"
traefik.enable: "true"
traefik.docker.network: "${TRAEFIK_DOCKER_NETWORK}"
homepage.group: "Utilidades"
homepage.name: "ChangeDetection"
homepage.icon: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/changedetection.svg"
homepage.description: "Monitor de cambios web"
homepage.href: "https://${CHANGEDETECTION_DOMAIN}/"
homepage.siteMonitor: "http://changedetection:5000/"
homepage.weight: "530"
changedetection-browser:
image: ${CHANGEDETECTION_BROWSER_IMAGE}
container_name: changedetection-browser
restart: unless-stopped
shm_size: "2gb"
networks:
- changedetection_internal
labels:
prune.protect: "true"
networks:
changedetection_internal:
driver: bridge
labels:
prune.protect: "true"
proxy:
external: true
+1 -55
View File
@@ -8,7 +8,6 @@ en tiempo real. Usa inteligencia comunitaria para compartir reputación de IPs e
Este stack despliega:
- **CrowdSec Engine**: Motor de detección que analiza logs de Traefik y otros contenedores
- **CrowdSec Bouncer (Traefik Plugin)**: Middleware que bloquea IPs maliciosas a nivel HTTP (L7)
- **Grafana**: Dashboard web para visualizar alertas, decisiones y métricas (protegido con Authentik)
- Baneos automáticos contra ataques de fuerza bruta, escaneos, CVEs conocidos y más
## 🚀 Despliegue
@@ -20,10 +19,7 @@ Este stack despliega:
docker network create proxy
```
2. **Registro DNS**: Configura el registro A para el dashboard
- `grafana.tudominio.com` → IP del servidor
3. **Directorios**: Créalos automáticamente con el setup:
2. **Directorios**: Créalos automáticamente con el setup:
```bash
cd crowdsec && ./setup.sh
```
@@ -42,9 +38,6 @@ Variables críticas:
# Clave API del bouncer (generada automáticamente por setup.sh o pon una manual)
CROWDSEC_BOUNCER_API_KEY=tu-key-de-64-caracteres
# Dominio del dashboard Grafana
CROWDSEC_GRAFANA_DOMAIN=grafana.tudominio.com
# Colecciones a instalar
CROWDSEC_COLLECTIONS=crowdsecurity/traefik
@@ -105,35 +98,6 @@ docker exec crowdsec cscli metrics
docker exec crowdsec cscli bouncers list
```
## 🔐 Configurar Authentik para el Dashboard (Grafana)
El dashboard de Grafana (`grafana.tudominio.com`) está protegido con `ths-authentik@docker`. Para que funcione:
### En el panel de Authentik:
1. **Crear Proxy Provider**:
- Ve a **Applications** → **Providers** → **Create**
- Tipo: **Proxy Provider**
- Name: `CrowdSec Grafana`
- External host: `https://grafana.tudominio.com`
- Authentication flow: `default-authentication-flow`
- Authorization flow: `default-provider-authorization-implicit-consent`
- Mode: `Forward auth (single application)`
2. **Crear Application**:
- Ve a **Applications** → **Create**
- Name: `CrowdSec Grafana`
- Slug: `grafana`
- Provider: selecciona el creado en paso 1
- UI Settings: opcional (icono, descripción)
3. **Asociar al Embedded Outpost**:
- Ve a **Outposts** → `authentik Embedded Outpost` → **Edit**
- Asegúrate de que la aplicación `CrowdSec Grafana` está seleccionada
- Haz clic en **Update**
> El middleware `ths-authentik@docker` ya está configurado en las labels del servicio `crowdsec-grafana`. Si otros stacks también lo usan, comparte la misma configuración de Authentik.
## 🛡️ Proteger servicios con CrowdSec
### Método 1: Middleware global (recomendado)
@@ -354,7 +318,6 @@ Requiere que el bot se ejecute en el mismo host que el contenedor `crowdsec` (ac
- **API local solo en loopback**: La API de CrowdSec (`cscli lapi`) solo escucha en `127.0.0.1:8080`
- **Bouncer integrado en Traefik**: No expone puertos adicionales, todo el filtrado ocurre dentro de Traefik
- **Dashboard protegido por Authentik**: Grafana requiere autenticación SSO previa
- **Motor con capacidades mínimas**: Solo `NET_BIND_SERVICE`, sin `CAP_SYS_ADMIN` ni privilegios elevados
- **SELinux**: Volúmenes con contexto `:Z` para entornos con SELinux habilitado
- **Key del bouncer**: Generada aleatoriamente y almacenada solo en `.env` (gitignorado)
@@ -407,21 +370,6 @@ Requiere que el bot se ejecute en el mismo host que el contenedor `crowdsec` (ac
docker exec crowdsec cscli decisions list
```
### El dashboard pide login pero Authentik no redirige
1. Verifica que el middleware está correctamente referenciado en las labels:
```bash
docker inspect crowdsec-grafana | grep -A 20 Labels
```
2. Revisa que la aplicación existe en Authentik y está asociada al Embedded Outpost
3. Comprueba los logs de Authentik:
```bash
docker logs ths-authentik-server
```
## 💾 Backups
Incluye en tus backups de Kopia (ya configurados en `backups/kopia/`):
@@ -442,7 +390,6 @@ Para actualizar CrowdSec:
1. Edita `.env` y actualiza la versión si es necesario:
```env
CROWDSEC_IMAGE=crowdsecurity/crowdsec:latest
GRAFANA_IMAGE=grafana/grafana:latest
```
2. Redespiega:
```bash
@@ -458,5 +405,4 @@ Para actualizar CrowdSec:
- [Documentación oficial de CrowdSec](https://docs.crowdsec.net/)
- [CrowdSec + Traefik Plugin](https://plugins.traefik.io/plugins/6335349fe93a526d1ec2aed6/crowdsec-bouncer)
- [CrowdSec Hub (colecciones)](https://hub.crowdsec.net/)
- [Grafana Docs](https://grafana.com/docs/grafana/latest/)
- [CrowdSec Discord](https://discord.gg/crowdsec)
-84
View File
@@ -14,11 +14,6 @@ services:
BOUNCER_KEY_TRAEFIK: ${CROWDSEC_BOUNCER_API_KEY}
LEVEL: ${CROWDSEC_LOG_LEVEL}
TZ: ${TZ:-America/Mexico_City}
POSTGRES_HOST: crowdsec-postgres
POSTGRES_PORT: "5432"
POSTGRES_DB: ${CROWDSEC_POSTGRES_DB}
POSTGRES_USER: ${CROWDSEC_POSTGRES_USER}
POSTGRES_PASSWORD: ${CROWDSEC_POSTGRES_PASSWORD}
volumes:
- ${CROWDSEC_CONFIG_PATH}:/etc/crowdsec:Z
- crowdsec_lib:/var/lib/crowdsec
@@ -31,7 +26,6 @@ services:
ports:
- "127.0.0.1:8080:8080"
networks:
- crowdsec_internal
- proxy
healthcheck:
test: ["CMD", "cscli", "lapi", "status"]
@@ -39,85 +33,7 @@ services:
timeout: 5s
retries: 3
crowdsec-postgres:
image: ${CROWDSEC_POSTGRES_IMAGE}
container_name: crowdsec-postgres
restart: unless-stopped
environment:
POSTGRES_USER: ${CROWDSEC_POSTGRES_USER}
POSTGRES_PASSWORD: ${CROWDSEC_POSTGRES_PASSWORD}
POSTGRES_DB: ${CROWDSEC_POSTGRES_DB}
volumes:
- ${CROWDSEC_POSTGRES_PATH}:/var/lib/postgresql/data:Z
networks:
- crowdsec_internal
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${CROWDSEC_POSTGRES_USER} -d ${CROWDSEC_POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 5
crowdsec-prometheus:
image: prom/prometheus@sha256:e4254400b85610324913f0dc4acf92603d9984e7519414c5a12811aa6146acc3
container_name: crowdsec-prometheus
restart: unless-stopped
command:
- "--config.file=/etc/prometheus/prometheus.yml"
- "--storage.tsdb.path=/prometheus"
- "--storage.tsdb.retention.time=7d"
- "--web.enable-lifecycle"
volumes:
- ${CROWDSEC_PROMETHEUS_CONFIG}/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- ${CROWDSEC_PROMETHEUS_PATH}:/prometheus:Z
networks:
- crowdsec_internal
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:9090/-/healthy"]
interval: 30s
timeout: 5s
retries: 3
crowdsec-grafana:
image: ${GRAFANA_IMAGE}
container_name: crowdsec-grafana
restart: unless-stopped
environment:
GF_SERVER_ROOT_URL: https://${CROWDSEC_GRAFANA_DOMAIN}
GF_AUTH_PROXY_ENABLED: "true"
GF_AUTH_PROXY_HEADER_NAME: X-Authentik-Username
GF_AUTH_PROXY_HEADER_EMAIL: X-Authentik-Email
GF_AUTH_PROXY_HEADER_GROUPS: X-Authentik-Groups
GF_AUTH_PROXY_AUTO_SIGN_UP: "true"
GF_AUTH_PROXY_ENABLE_LOGIN_TOKEN: "false"
GF_AUTH_DISABLE_LOGIN_FORM: "true"
GF_AUTH_SIGNOUT_REDIRECT: https://${AUTHENTIK_DOMAIN:-auth.sherlockhomeless.net}/application/o/${CROWDSEC_GRAFANA_SLUG:-grafana}/end-session/
volumes:
- ${CROWDSEC_GRAFANA_PATH}:/var/lib/grafana:Z
- ${CROWDSEC_GRAFANA_PROVISIONING}:/etc/grafana/provisioning:ro
- ${CROWDSEC_GRAFANA_DASHBOARDS}:/var/lib/grafana/dashboards:ro
networks:
- crowdsec_internal
- proxy
labels:
traefik.enable: "true"
traefik.docker.network: "${TRAEFIK_DOCKER_NETWORK}"
traefik.http.routers.crowdsec-grafana.rule: "Host(`${CROWDSEC_GRAFANA_DOMAIN}`)"
traefik.http.routers.crowdsec-grafana.entrypoints: "${TRAEFIK_ENTRYPOINT_SECURE}"
traefik.http.routers.crowdsec-grafana.tls: "true"
traefik.http.routers.crowdsec-grafana.tls.certresolver: "${TRAEFIK_CERTRESOLVER}"
traefik.http.routers.crowdsec-grafana.service: "crowdsec-grafana"
traefik.http.routers.crowdsec-grafana.middlewares: "crowdsec-bouncer@file,${TRAEFIK_AUTH_MIDDLEWARE}"
traefik.http.services.crowdsec-grafana.loadbalancer.server.port: "3000"
homepage.group: "Observabilidad"
homepage.name: "Grafana"
homepage.href: "https://${CROWDSEC_GRAFANA_DOMAIN}"
homepage.description: "Dashboards CrowdSec y monitorizacion"
homepage.icon: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/grafana.svg"
homepage.siteMonitor: "https://${CROWDSEC_GRAFANA_DOMAIN}"
networks:
crowdsec_internal:
driver: bridge
proxy:
external: true
-55
View File
@@ -1,55 +0,0 @@
#!/usr/bin/env bash
# ============================================================
# crowdsec-metrics-export.sh
# Exporta métricas de CrowdSec a PostgreSQL para Grafana.
# Ejecutar cada 5 minutos via cron.
# ============================================================
set -euo pipefail
PG_DSN="postgresql://crowdsec:crowdsec_metrics_2026@crowdsec-postgres:5432/crowdsec"
# -----------------------------------------------------------
# 1. Decisiones activas (IPs bloqueadas)
# -----------------------------------------------------------
docker exec crowdsec cscli decisions list -o json 2>/dev/null | \
python3 -c "
import sys, json
data = json.load(sys.stdin)
for entry in data:
for d in entry.get('decisions', []):
ip = d.get('value','').replace(chr(39),chr(39)+chr(39))
scenario = d.get('scenario','').replace(chr(39),chr(39)+chr(39))
dtype = d.get('type','').replace(chr(39),chr(39)+chr(39))
origin = d.get('origin','').replace(chr(39),chr(39)+chr(39))
scope = d.get('scope','').replace(chr(39),chr(39)+chr(39))
dur = d.get('duration','').replace(chr(39),chr(39)+chr(39))
print(f'{ip}|{scenario}|{dtype}|{origin}|{scope}|{dur}')
" 2>/dev/null | sort -u > /tmp/cs_decisions.txt
# Limpiar tabla y re-insertar
docker exec crowdsec-postgres psql "$PG_DSN" -c "DELETE FROM decisions" 2>/dev/null
DECISION_COUNT=0
while IFS='|' read -r ip reason action origin scope dur; do
[ -z "$ip" ] && continue
docker exec crowdsec-postgres psql "$PG_DSN" -c "
INSERT INTO decisions (ip, reason, action, origin, scope, duration)
VALUES ('$ip', '$reason', '$action', '$origin', '$scope', '$dur')
" 2>/dev/null || true
DECISION_COUNT=$((DECISION_COUNT + 1))
done < /tmp/cs_decisions.txt
# -----------------------------------------------------------
# 2. Métricas agregadas
# -----------------------------------------------------------
ALERTS=$(docker exec crowdsec cscli alerts list -o json 2>/dev/null | python3 -c "import sys,json; print(len(json.load(sys.stdin)))" 2>/dev/null || echo 0)
docker exec crowdsec-postgres psql "$PG_DSN" -c "
INSERT INTO metrics (metric_name, metric_value, labels) VALUES
('active_decisions', $DECISION_COUNT, '{}'::jsonb),
('active_alerts', $ALERTS, '{}'::jsonb)
" 2>/dev/null || true
echo "$(date '+%Y-%m-%d %H:%M:%S') crowdsec-metrics: $DECISION_COUNT decisions, $ALERTS alerts"
rm -f /tmp/cs_decisions.txt
-2
View File
@@ -92,5 +92,3 @@ echo " 4. El middleware se usa asi: crowdsec-bouncer@file"
echo ""
echo "Para proteger un servicio, agrega el middleware en sus labels de Traefik:"
echo ' traefik.http.routers.<svc>.middlewares: "crowdsec-bouncer@file"'
echo ""
echo "Dashboard Grafana (requiere Authentik): https://${CROWDSEC_GRAFANA_DOMAIN}"
-21
View File
@@ -1,7 +1,5 @@
##### Imágenes #####
CROWDSEC_IMAGE=crowdsecurity/crowdsec:latest
CROWDSEC_POSTGRES_IMAGE=docker.io/library/postgres:16-alpine
GRAFANA_IMAGE=grafana/grafana:latest
##### CrowdSec Engine #####
CROWDSEC_BOUNCER_API_KEY=change-me-to-a-random-64-char-hex
@@ -9,26 +7,7 @@ CROWDSEC_FW_BOUNCER_KEY=change-me-to-another-random-64-char-hex
CROWDSEC_LOG_LEVEL=info
CROWDSEC_LOCAL_API_PORT=8080
##### Base de datos PostgreSQL (métricas para Grafana) #####
CROWDSEC_POSTGRES_DB=crowdsec
CROWDSEC_POSTGRES_USER=crowdsec
CROWDSEC_POSTGRES_PASSWORD=change-me
CROWDSEC_POSTGRES_PATH=/opt/crowdsec/postgres
##### Directorios de datos #####
CROWDSEC_CONFIG_PATH=/opt/crowdsec/config
CROWDSEC_DATA_PATH=/opt/crowdsec/data
CROWDSEC_PROMETHEUS_CONFIG=/opt/crowdsec/prometheus
CROWDSEC_PROMETHEUS_PATH=/opt/crowdsec/prometheus-data
CROWDSEC_GRAFANA_PATH=/opt/crowdsec/grafana
CROWDSEC_GRAFANA_PROVISIONING=/opt/crowdsec/grafana/provisioning
CROWDSEC_GRAFANA_DASHBOARDS=/opt/crowdsec/grafana/dashboards
TRAEFIK_LOG_PATH=/opt/traefik/logs
##### Traefik / dominios #####
TRAEFIK_DOCKER_NETWORK=proxy
CROWDSEC_GRAFANA_DOMAIN=grafana.sherlockhomeless.net
TRAEFIK_ENTRYPOINT_SECURE=websecure
TRAEFIK_CERTRESOLVER=letsencrypt
TRAEFIK_AUTH_MIDDLEWARE=ths-authentik@docker
TRAEFIK_CROWDSEC_MIDDLEWARE=crowdsec-bouncer@file
-42
View File
@@ -1,42 +0,0 @@
# Dozzle
Stack para desplegar [Dozzle](https://dozzle.dev/) como visor ligero de logs Docker en `dozzle.sherlockhomeless.net`.
## 🚀 Despliegue con Docker Compose
Asegurate de tener `dozzle/.env` configurado y despliega:
```bash
cd dozzle
docker compose --env-file .env up -d
```
## ⚙️ Variables importantes
Edita `dozzle/.env`:
```env
DOZZLE_DOMAIN=dozzle.sherlockhomeless.net
DOZZLE_DATA_PATH=/opt/dozzle/data
TRAEFIK_AUTH_MIDDLEWARE=ths-authentik@docker
DOZZLE_ENABLE_ACTIONS=false
DOZZLE_ENABLE_SHELL=false
```
## 📁 Preparación del host
Crea el directorio persistente antes del primer arranque:
```bash
mkdir -p /opt/dozzle/data
```
## 🔐 Autenticación y acceso
Dozzle no trae autenticación suficiente para exponerse tal cual a Internet, así que este stack lo publica detrás de Traefik y fuerza el middleware compartido `ths-authentik@docker`.
La autenticación se resuelve por completo en Traefik + Authentik antes de llegar al contenedor. Dozzle no expone puertos directos al host, así que no queda accesible sin pasar por ese middleware. Se mantienen desactivadas las acciones y el acceso shell para no dar más privilegios de los necesarios sobre `docker.sock`.
## 🏠 Integración con Homepage
El servicio publica labels `homepage.*`, así que puede aparecer automáticamente en Homepage dentro del grupo **Observabilidad** como enlace al dominio configurado. Homepage no dispone de un widget nativo para Dozzle, así que aquí solo se expone la tarjeta del servicio.
-30
View File
@@ -1,30 +0,0 @@
services:
dozzle:
image: ${DOZZLE_IMAGE}
container_name: dozzle
restart: unless-stopped
environment:
TZ: ${TZ}
DOZZLE_NO_ANALYTICS: ${DOZZLE_NO_ANALYTICS}
DOZZLE_ENABLE_ACTIONS: ${DOZZLE_ENABLE_ACTIONS}
DOZZLE_ENABLE_SHELL: ${DOZZLE_ENABLE_SHELL}
volumes:
- ${DOZZLE_DATA_PATH}:/data:Z
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- proxy
labels:
prune.protect: "true"
traefik.enable: "true"
traefik.docker.network: "${TRAEFIK_DOCKER_NETWORK}"
homepage.group: "Observabilidad"
homepage.name: "Dozzle"
homepage.icon: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/dozzle.svg"
homepage.description: "Logs en tiempo real de contenedores Docker"
homepage.href: "https://${DOZZLE_DOMAIN}"
homepage.siteMonitor: "http://dozzle:8080/"
homepage.weight: "100"
networks:
proxy:
external: true
-13
View File
@@ -1,13 +0,0 @@
TZ=Europe/Madrid
DOZZLE_IMAGE=amir20/dozzle:latest
DOZZLE_DOMAIN=dozzle.sherlockhomeless.net
DOZZLE_DATA_PATH=/opt/dozzle/data
DOZZLE_NO_ANALYTICS=true
DOZZLE_ENABLE_ACTIONS=false
DOZZLE_ENABLE_SHELL=false
TRAEFIK_DOCKER_NETWORK=proxy
TRAEFIK_ENTRYPOINT_SECURE=websecure
TRAEFIK_CERTRESOLVER=letsencrypt
TRAEFIK_AUTH_MIDDLEWARE=ths-authentik@docker
SERVICE_URL_DOZZLE=https://dozzle.sherlockhomeless.net
SERVICE_FQDN_DOZZLE=dozzle.sherlockhomeless.net
-28
View File
@@ -1,28 +0,0 @@
# Actual Budget
Actual Budget published at `https://finance.sherlockhomeless.net`.
## Qué hace y cómo funciona
Actual Budget permite administrar presupuesto personal con un modelo local-first y sincronización mediante el servidor. Los datos del servidor se guardan en `ACTUALBUDGET_DATA_PATH`; no hay base de datos separada.
## Configuración
- `ACTUALBUDGET_DOMAIN` define el dominio publicado.
- `ACTUALBUDGET_IMAGE` controla la versión que se despliega.
- `ACTUALBUDGET_DATA_PATH` debe incluirse en backups y tener permisos de escritura.
- La red interna aísla el servicio y `proxy` permite a Traefik alcanzarlo.
- El router externo aplica CrowdSec, Authentik y Sablier. Authentik protege el acceso al servidor, pero no sustituye las credenciales/cifrado que configure Actual Budget.
## Despliegue
```bash
docker compose --env-file .env config
docker compose --env-file .env up -d
```
The public route is defined in Traefik dynamic config and protected by:
- `crowdsec-bouncer@file`
- `ths-authentik@docker`
- `sablier-actualbudget@file`
-31
View File
@@ -1,31 +0,0 @@
services:
actualbudget:
image: ${ACTUALBUDGET_IMAGE}
container_name: actualbudget
restart: unless-stopped
environment:
TZ: ${TZ}
volumes:
- ${ACTUALBUDGET_DATA_PATH}:/data:Z
networks:
- finance_internal
- proxy
labels:
prune.protect: "true"
traefik.enable: "true"
traefik.docker.network: "${TRAEFIK_DOCKER_NETWORK}"
homepage.group: "Personal"
homepage.name: "Finance"
homepage.icon: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/actual-budget.svg"
homepage.description: "Presupuesto personal"
homepage.href: "https://${ACTUALBUDGET_DOMAIN}/"
homepage.siteMonitor: "http://actualbudget:5006/"
homepage.weight: "520"
networks:
finance_internal:
driver: bridge
labels:
prune.protect: "true"
proxy:
external: true
-19
View File
@@ -1,19 +0,0 @@
TZ=Europe/Madrid
GHOST_ME_IMAGE=ghost:6-alpine
GHOST_ME_DOMAIN=me.thehomelesssherlock.com
GHOST_ME_CONTENT_PATH=/opt/ghost-me/content
GHOST_ME_ROUTES_PATH=./routes.yaml
GHOST_ME_DB_IMAGE=mysql:8.4
GHOST_ME_DB_DATA_PATH=/opt/ghost-me/mysql
GHOST_ME_DB_NAME=ghost_me
GHOST_ME_DB_USER=ghost_me
GHOST_ME_DB_PASSWORD=change-me
GHOST_ME_DB_ROOT_PASSWORD=change-me
GHOST_ME_MAIL_FROM="Eduardo Paredes <noreply@thehomelesssherlock.com>"
TRAEFIK_DOCKER_NETWORK=proxy
TRAEFIK_ENTRYPOINT_SECURE=websecure
TRAEFIK_CERTRESOLVER=letsencrypt
-29
View File
@@ -1,29 +0,0 @@
# Ghost Me
Ghost publication for the personal site currently exposed as:
- `https://me.thehomelesssherlock.com`
## Qué hace y arquitectura
Es el sitio personal y portfolio. `ghost-me-db` conserva contenido en MySQL y `ghost-me` sirve Ghost con el tema local `theme-source-personal`. `routes.yaml` separa inicio, blog y proyectos mediante colecciones y etiquetas.
## Configuración
- `GHOST_ME_DOMAIN` es el dominio canónico; `GHOST_ME_DOMAIN_ALT` redirige permanentemente al principal.
- `GHOST_ME_DB_*` configura MySQL y debe permanecer sólo en `.env`.
- `GHOST_ME_CONTENT_PATH` conserva uploads y configuración de Ghost.
- `GHOST_ME_ROUTES_PATH` monta las rutas de contenido y debe apuntar a `./routes.yaml`.
- `GHOST_ME_MAIL_FROM` y `mail_internal` permiten correo mediante `mail-relay`.
- El tema se monta desde el repositorio para poder versionarlo; valida sus cambios con `npm test` dentro de `theme-source-personal` antes de actualizarlo.
La base queda aislada en `ghost_me_internal`; únicamente Ghost se conecta a `proxy` y `mail_internal`.
## Despliegue
```bash
docker compose --env-file .env config
docker compose --env-file .env up -d
```
Copy `.env.example` to `.env` and keep real secrets only in `.env`.
-91
View File
@@ -1,91 +0,0 @@
services:
ghost-me-db:
image: ${GHOST_ME_DB_IMAGE}
container_name: ghost-me-db
restart: unless-stopped
labels:
prune.protect: "true"
environment:
MYSQL_ROOT_PASSWORD: ${GHOST_ME_DB_ROOT_PASSWORD}
MYSQL_DATABASE: ${GHOST_ME_DB_NAME}
MYSQL_USER: ${GHOST_ME_DB_USER}
MYSQL_PASSWORD: ${GHOST_ME_DB_PASSWORD}
TZ: ${TZ}
volumes:
- ${GHOST_ME_DB_DATA_PATH}:/var/lib/mysql:Z
healthcheck:
test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -u${GHOST_ME_DB_USER} -p${GHOST_ME_DB_PASSWORD} --silent"]
interval: 10s
timeout: 5s
retries: 20
start_period: 30s
networks:
- ghost_me_internal
ghost-me:
image: ${GHOST_ME_IMAGE}
container_name: ghost-me
restart: unless-stopped
depends_on:
ghost-me-db:
condition: service_healthy
environment:
NODE_ENV: production
TZ: ${TZ}
url: https://${GHOST_ME_DOMAIN}
database__client: mysql
database__connection__host: ghost-me-db
database__connection__user: ${GHOST_ME_DB_USER}
database__connection__password: ${GHOST_ME_DB_PASSWORD}
database__connection__database: ${GHOST_ME_DB_NAME}
mail__transport: SMTP
mail__from: ${GHOST_ME_MAIL_FROM}
mail__options__host: mail-relay
mail__options__port: 587
mail__options__secure: "false"
mail__options__ignoreTLS: "true"
volumes:
- ${GHOST_ME_CONTENT_PATH}:/var/lib/ghost/content:Z
- ${GHOST_ME_ROUTES_PATH}:/var/lib/ghost/content/settings/routes.yaml:Z
- ./theme-source-personal:/var/lib/ghost/content/themes/source-personal:Z
networks:
- ghost_me_internal
- proxy
- mail_internal
labels:
prune.protect: "true"
traefik.enable: "true"
traefik.docker.network: "${TRAEFIK_DOCKER_NETWORK}"
traefik.http.routers.ghost-me.rule: "Host(`${GHOST_ME_DOMAIN}`)"
traefik.http.routers.ghost-me.entrypoints: "${TRAEFIK_ENTRYPOINT_SECURE}"
traefik.http.routers.ghost-me.tls: "true"
traefik.http.routers.ghost-me.tls.certresolver: "${TRAEFIK_CERTRESOLVER}"
traefik.http.routers.ghost-me.service: "ghost-me"
traefik.http.routers.ghost-me.middlewares: "crowdsec-bouncer@file"
traefik.http.services.ghost-me.loadbalancer.server.port: "2368"
traefik.http.routers.ghost-me-alt.rule: "Host(`${GHOST_ME_DOMAIN_ALT}`)"
traefik.http.routers.ghost-me-alt.entrypoints: "${TRAEFIK_ENTRYPOINT_SECURE}"
traefik.http.routers.ghost-me-alt.tls: "true"
traefik.http.routers.ghost-me-alt.tls.certresolver: "${TRAEFIK_CERTRESOLVER}"
traefik.http.routers.ghost-me-alt.service: "ghost-me"
traefik.http.routers.ghost-me-alt.middlewares: "crowdsec-bouncer@file,ghost-me-alt-redirect"
traefik.http.middlewares.ghost-me-alt-redirect.redirectregex.permanent: "true"
traefik.http.middlewares.ghost-me-alt-redirect.redirectregex.regex: "^https://${GHOST_ME_DOMAIN_ALT}(:443)?/(.*)"
traefik.http.middlewares.ghost-me-alt-redirect.redirectregex.replacement: "https://${GHOST_ME_DOMAIN}/$${2}"
homepage.group: "Personal"
homepage.name: "Me"
homepage.icon: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/ghost.svg"
homepage.description: "Sitio personal y CV de Eduardo Paredes"
homepage.href: "https://${GHOST_ME_DOMAIN}/"
homepage.siteMonitor: "http://ghost-me:2368/"
homepage.weight: "140"
networks:
ghost_me_internal:
driver: bridge
labels:
prune.protect: "true"
mail_internal:
external: true
proxy:
external: true
-18
View File
@@ -1,18 +0,0 @@
routes:
/:
data: page.inicio
template: page-inicio
collections:
/blog/:
permalink: /blog/{slug}/
template: index
filter: tag:-hash-project
/proyectos/:
permalink: /proyectos/{slug}/
template: projects
filter: tag:hash-project
taxonomies:
tag: /tag/{slug}/
author: /author/{slug}/
-22
View File
@@ -1,22 +0,0 @@
Copyright (c) 2013-2026 Ghost Foundation
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
-68
View File
@@ -1,68 +0,0 @@
# Source
The default theme for [Ghost](http://github.com/tryghost/ghost/). This is the latest development version of Source! If you're just looking to download the latest release, head over to the [releases](https://github.com/TryGhost/Source/releases) page.
&nbsp;
# First time using a Ghost theme?
Ghost uses a simple templating language called [Handlebars](http://handlebarsjs.com/) for its themes.
This theme has lots of code comments to help explain what's going on just by reading the code. Once you feel comfortable with how everything works, we also have full [theme API documentation](https://ghost.org/docs/themes/) which explains every possible Handlebars helper and template.
**The main files are:**
- `default.hbs` - The parent template file, which includes your global header/footer
- `home.hbs` - The homepage
- `index.hbs` - The main template to generate a list of posts
- `post.hbs` - The template used to render individual posts
- `page.hbs` - Used for individual pages
- `tag.hbs` - Used for tag archives, eg. "all posts tagged with `news`"
- `author.hbs` - Used for author archives, eg. "all posts written by Jamie"
One neat trick is that you can also create custom one-off templates by adding the slug of a page to a template file. For example:
- `page-about.hbs` - Custom template for an `/about/` page
- `tag-news.hbs` - Custom template for `/tag/news/` archive
- `author-ali.hbs` - Custom template for `/author/ali/` archive
# Development
Source styles are compiled using Gulp/PostCSS to polyfill future CSS spec. You'll need [Node](https://nodejs.org/), [Yarn](https://yarnpkg.com/) and [Gulp](https://gulpjs.com) installed globally. After that, from the theme's root directory:
```bash
# install dependencies
yarn install
# run development server
yarn dev
```
Now you can edit `/assets/css/` files, which will be compiled to `/assets/built/` automatically.
The `zip` Gulp task packages the theme files into `dist/<theme-name>.zip`, which you can then upload to your site.
```bash
# create .zip file
yarn zip
```
# PostCSS Features Used
- Autoprefixer - Don't worry about writing browser prefixes of any kind, it's all done automatically with support for the latest 2 major versions of every browser.
# SVG Icons
Source uses inline SVG icons, included via Handlebars partials. You can find all icons inside `/partials/icons`. To use an icon just include the name of the relevant file, eg. To include the SVG icon in `/partials/icons/rss.hbs` - use `{{> "icons/rss"}}`.
You can add your own SVG icons in the same manner.
# Translations
Please see [@TryGhost/Themes/theme-translations/README.md](https://github.com/TryGhost/Themes/blob/main/packages/theme-translations/README.md) for how to build, edit, or contribute translations.
# Copyright & License
Copyright (c) 2013-2026 Ghost Foundation - Released under the [MIT license](LICENSE).
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,861 +0,0 @@
/*
Personal portfolio layer for me.thehomelesssherlock.com.
Keeps Source structure, but moves the visual language closer to the
previous custom site: dark technical UI, blue accents, compact cards.
*/
:root {
--background-color: #0f0f0f !important;
--ghost-accent-color: #2563eb;
--color-white: #ffffff;
--color-lighter-gray: rgb(255 255 255 / 0.06);
--color-light-gray: rgb(255 255 255 / 0.12);
--color-mid-gray: #6b6560;
--color-dark-gray: #9ca3af;
--color-darker-gray: #ffffff;
--color-primary-text: #ffffff;
--color-secondary-text: #d1d5db;
--color-border: #333333;
--color-dark-border: rgb(59 130 246 / 0.35);
--ths-bg-primary: #0f0f0f;
--ths-bg-secondary: #1a1a1a;
--ths-bg-tertiary: #252525;
--ths-bg-surface: #1e1e1e;
--ths-accent: #3b82f6;
--ths-accent-strong: #2563eb;
--ths-accent-soft: rgb(59 130 246 / 0.16);
--ths-text-primary: #ffffff;
--ths-text-secondary: #d1d5db;
--ths-text-muted: #9ca3af;
--container-width: 1040px;
--container-gap: clamp(20px, 5vw, 40px);
--grid-gap: 18px;
}
html,
body {
background: var(--ths-bg-primary);
}
body {
color: var(--ths-text-secondary);
}
.gh-viewport {
min-height: 100vh;
background:
radial-gradient(circle at 50% 0, rgb(37 99 235 / 0.1), transparent 34rem),
var(--ths-bg-primary);
}
a {
color: var(--ths-text-primary);
}
a:hover {
color: #93c5fd;
opacity: 1;
}
.gh-navigation {
position: sticky;
top: 0;
z-index: 20;
height: 40px;
background: rgb(15 15 15 / 0.8);
border-bottom: 1px solid var(--color-border);
backdrop-filter: blur(14px);
}
.gh-navigation-inner {
display: flex;
justify-content: center;
align-items: center;
height: 40px;
min-height: 40px;
max-width: 1040px;
padding-inline: 16px;
}
.gh-navigation-brand {
display: none;
}
.gh-navigation-menu {
display: flex;
justify-content: center;
width: 100%;
}
.gh-navigation-menu .nav {
display: flex;
align-items: center;
justify-content: center;
gap: 2.4rem;
flex-wrap: wrap;
}
.gh-navigation-logo {
display: none;
width: 42px;
height: 42px;
justify-content: center;
color: var(--ths-text-primary);
font-family: var(--font-mono);
font-size: 0;
font-weight: 800;
letter-spacing: 0;
background: linear-gradient(135deg, rgb(37 99 235 / 0.95), rgb(14 165 233 / 0.82));
border: 1px solid rgb(147 197 253 / 0.26);
border-radius: 10px;
box-shadow: 0 14px 38px rgb(37 99 235 / 0.22);
}
.gh-navigation-logo::before {
content: "EP";
font-size: 1.8rem;
}
.gh-navigation-logo:has(img)::before {
content: none;
}
.gh-navigation-logo img {
max-height: 30px;
}
.gh-navigation-menu a,
.gh-footer-menu a {
color: var(--ths-text-muted);
font-size: 1.25rem;
font-weight: 500;
transition: color 0.2s ease;
}
.gh-navigation-menu a:hover,
.gh-footer-menu a:hover {
color: var(--ths-text-primary);
}
.gh-navigation-menu .nav-contacto a {
padding: 0.55rem 1.35rem;
color: #fff;
font-weight: 600;
background: linear-gradient(135deg, var(--ths-accent-strong), var(--ths-accent));
border-radius: 8px;
box-shadow: 0 8px 20px rgb(37 99 235 / 0.25);
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.gh-navigation-menu .nav-contacto a:hover {
color: #fff;
transform: translateY(-1px);
box-shadow: 0 10px 26px rgb(37 99 235 / 0.35);
}
.gh-search,
.gh-icon-button {
color: var(--ths-accent);
}
.gh-button,
.gh-content .kg-button-card a,
.gh-content .kg-button-card .kg-btn {
color: #fff !important;
background: linear-gradient(135deg, var(--ths-accent-strong), #1d4ed8);
border: 1px solid rgb(147 197 253 / 0.24);
border-radius: 8px;
box-shadow: 0 12px 32px rgb(37 99 235 / 0.22);
}
.gh-button:hover,
.gh-content .kg-button-card a:hover,
.gh-content .kg-button-card .kg-btn:hover {
transform: translateY(-1px);
box-shadow: 0 16px 38px rgb(37 99 235 / 0.32);
}
.gh-main,
.gh-outer {
background: transparent;
}
.gh-header {
min-height: 386px;
background: transparent;
}
.gh-header-inner {
padding-top: clamp(76px, 12vw, 132px);
padding-bottom: clamp(56px, 10vw, 108px);
text-align: center;
}
.gh-header-title,
.gh-article-title {
max-width: 900px;
color: var(--ths-text-primary);
font-weight: 800;
letter-spacing: 0;
}
.gh-header-title {
margin-inline: auto;
font-size: clamp(4.4rem, 8vw, 7.8rem);
}
.gh-article-title {
font-size: clamp(3.6rem, 6vw, 6.6rem);
}
.gh-article-excerpt,
.gh-header-description,
.gh-content > p:first-child {
color: var(--ths-text-muted);
}
.gh-featured,
.gh-container,
.gh-article,
.gh-archive {
background: transparent;
}
.gh-post-card,
.gh-card,
.gh-about,
.gh-recommendations,
.gh-content :is(.kg-callout-card, .kg-bookmark-card, .kg-product-card) {
background: var(--ths-bg-tertiary);
border: 1px solid var(--color-border);
border-radius: 10px;
box-shadow: 0 18px 50px rgb(0 0 0 / 0.18);
}
.gh-post-card {
padding: 18px;
transition: transform 0.18s ease, border-color 0.18s ease, box-shadow 0.18s ease;
}
.gh-post-card:hover {
transform: translateY(-2px);
border-color: rgb(59 130 246 / 0.45);
box-shadow: 0 20px 60px rgb(37 99 235 / 0.13);
}
.gh-post-card-title,
.gh-card-title,
.gh-about-title,
.gh-sidebar-title {
color: var(--ths-text-primary);
}
.gh-post-card-excerpt,
.gh-card-excerpt,
.gh-about-description,
.gh-post-card-meta,
.gh-article-meta {
color: var(--ths-text-muted);
}
.gh-post-card-tags a,
.gh-content a {
color: #93c5fd;
}
.gh-content {
color: var(--ths-text-secondary);
}
.gh-content h1,
.gh-content h2,
.gh-content h3,
.gh-content h4 {
color: var(--ths-text-primary);
letter-spacing: 0;
}
.gh-content h2::after {
content: "";
display: block;
width: 64px;
height: 2px;
margin-top: 12px;
background: linear-gradient(90deg, var(--ths-accent), rgb(56 189 248 / 0.6));
border-radius: 999px;
}
.gh-content code,
.gh-content pre {
font-family: var(--font-mono);
}
.gh-content code {
color: #93c5fd;
background: var(--ths-bg-surface);
border: 1px solid var(--color-border);
border-radius: 5px;
}
.gh-content pre {
background: var(--ths-bg-surface);
border: 1px solid var(--color-border);
border-radius: 10px;
}
.gh-content blockquote {
color: var(--ths-text-secondary);
border-left-color: var(--ths-accent);
}
.gh-content hr {
background: var(--color-border);
}
.gh-content :is(table, th, td) {
border-color: var(--color-border);
}
.gh-content th {
color: var(--ths-text-primary);
background: var(--ths-bg-secondary);
}
.gh-content td {
background: rgb(37 37 37 / 0.48);
}
.gh-footer {
margin-top: 56px;
color: var(--ths-text-muted);
background: var(--ths-bg-primary);
border-top: 1px solid var(--color-border);
}
.gh-footer-bar {
border-top-color: var(--color-border);
}
.gh-footer-logo {
color: var(--ths-text-primary);
}
.gh-footer-copyright,
.gh-footer-copyright a {
color: var(--ths-text-muted);
}
/* Homepage: hero + latest posts (page-inicio.hbs) */
.ths-hero {
box-sizing: border-box;
min-height: 386px;
padding: clamp(72px, 9vw, 86px) 0 clamp(56px, 7vw, 78px);
background:
radial-gradient(circle at 50% 0, rgb(37 99 235 / 0.12), transparent 32rem),
var(--ths-bg-primary);
text-align: center;
}
.ths-hero-inner {
max-width: 780px;
margin-inline: auto;
}
.ths-hero-title {
margin: 0 0 18px;
color: var(--ths-text-primary);
font-size: clamp(4.2rem, 5.4vw, 5.4rem);
font-weight: 800;
line-height: 1.06;
letter-spacing: -0.01em;
}
.ths-hero-subtitle {
margin: 0 auto 20px;
max-width: 560px;
color: var(--ths-text-secondary);
font-size: clamp(1.55rem, 2vw, 1.8rem);
line-height: 1.45;
}
.ths-hero-kicker {
margin: 0 auto 34px;
color: var(--ths-text-muted);
font-size: clamp(1.25rem, 1.6vw, 1.45rem);
line-height: 1.45;
}
.ths-hero-actions {
display: flex;
justify-content: center;
gap: 16px;
flex-wrap: wrap;
}
.ths-btn {
min-width: 108px;
padding: 11px 26px;
font-weight: 600;
font-size: 1.25rem;
border-radius: 8px;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.ths-btn-primary {
color: #fff;
background: linear-gradient(135deg, var(--ths-accent-strong), var(--ths-accent));
box-shadow: 0 12px 30px rgb(37 99 235 / 0.28);
}
.ths-btn-secondary {
color: var(--ths-text-primary);
background: var(--ths-bg-tertiary);
border: 1px solid var(--color-border);
}
.ths-btn:hover {
transform: translateY(-2px);
}
.ths-btn-primary:hover {
box-shadow: 0 16px 38px rgb(37 99 235 / 0.4);
}
.ths-section {
padding: clamp(38px, 5vw, 58px) 0;
}
.ths-about-section {
background: var(--ths-bg-secondary);
}
.ths-about-inner {
display: block;
width: min(calc(100% - 40px), 1040px);
max-width: none;
margin-inline: auto;
}
.ths-about-inner p {
max-width: 1040px;
margin: 0;
color: var(--ths-text-secondary);
font-size: 1.35rem;
line-height: 1.58;
}
.ths-section-title {
position: relative;
margin: 0 0 32px;
padding-bottom: 14px;
color: var(--ths-text-primary);
font-size: clamp(1.6rem, 3vw, 2.2rem);
font-weight: 800;
}
.ths-section-title::after {
content: "";
position: absolute;
left: 0;
bottom: 0;
width: 56px;
height: 3px;
background: linear-gradient(90deg, var(--ths-accent), rgb(56 189 248 / 0.6));
border-radius: 999px;
}
.ths-posts-section {
background: var(--ths-bg-primary);
padding-top: clamp(40px, 5vw, 64px);
}
.ths-posts-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 16px;
}
.ths-post-card {
display: block;
padding: 20px;
color: inherit;
background: var(--ths-bg-tertiary);
border: 1px solid var(--color-border);
border-radius: 10px;
transition: transform 0.18s ease, border-color 0.18s ease, box-shadow 0.18s ease;
}
.ths-post-card:hover {
transform: translateY(-3px);
border-color: rgb(59 130 246 / 0.45);
box-shadow: 0 16px 40px rgb(37 99 235 / 0.14);
opacity: 1;
}
.ths-post-card-title {
margin: 0 0 10px;
color: var(--ths-text-primary);
font-size: 1.05rem;
font-weight: 700;
}
.ths-post-card-meta {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 10px;
color: var(--ths-text-muted);
font-size: 0.85rem;
}
.ths-post-card-excerpt {
margin: 0 0 14px;
color: var(--ths-text-secondary);
font-size: 0.9rem;
line-height: 1.5;
}
.ths-post-card-tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.ths-post-card-tags span {
padding: 4px 10px;
color: #93c5fd;
font-size: 0.75rem;
font-weight: 600;
background: rgb(59 130 246 / 0.12);
border: 1px solid rgb(59 130 246 / 0.3);
border-radius: 6px;
}
.ths-posts-more {
margin-top: 24px;
text-align: center;
}
.ths-posts-more a {
color: var(--ths-accent);
font-weight: 600;
}
.ths-footer-inner {
width: min(calc(100% - 40px), 1040px);
margin-inline: auto;
}
.ths-contact-section {
color: var(--ths-text-secondary);
text-align: center;
background: var(--ths-bg-secondary);
border-top: 1px solid rgb(255 255 255 / 0.04);
}
.ths-contact-inner {
width: min(calc(100% - 40px), 680px);
margin-inline: auto;
}
.ths-contact-inner h2 {
margin: 0 0 14px;
color: var(--ths-text-primary);
font-size: clamp(2.4rem, 3vw, 3rem);
font-weight: 800;
}
.ths-contact-inner p {
margin: 0 0 26px;
font-size: 1.35rem;
line-height: 1.55;
}
.ths-footer {
color: var(--ths-text-muted);
background: var(--ths-bg-primary);
border-top: 1px solid var(--color-border);
}
.ths-footer-inner {
padding-block: 34px 22px;
}
.ths-footer-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 48px;
padding-bottom: 30px;
}
.ths-footer-grid nav {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 8px;
}
.ths-footer-grid h2 {
margin: 0 0 4px;
color: var(--ths-text-primary);
font-size: 1.2rem;
font-weight: 700;
}
.ths-footer-grid a {
color: var(--ths-text-muted);
font-size: 1.15rem;
}
.ths-footer-grid a:hover {
color: var(--ths-text-primary);
}
.ths-footer-copyright {
padding-top: 22px;
text-align: center;
font-size: 1.05rem;
border-top: 1px solid var(--color-border);
}
/* Projects collection */
.ths-projects-archive {
min-height: calc(100vh - 40px);
padding: clamp(54px, 7vw, 76px) 0 clamp(64px, 8vw, 90px);
background: var(--ths-bg-primary);
}
.ths-projects-inner {
width: min(calc(100% - 40px), 1040px);
margin-inline: auto;
}
.ths-projects-header {
max-width: 720px;
margin-bottom: 30px;
}
.ths-projects-header h1 {
margin: 0 0 5px;
color: var(--ths-text-primary);
font-size: clamp(2.8rem, 4vw, 3.8rem);
font-weight: 800;
line-height: 1.15;
}
.ths-projects-subtitle {
margin: 0;
color: var(--ths-text-muted);
font-size: 1.3rem;
}
.ths-heading-rule {
display: block;
width: 56px;
height: 3px;
margin: 14px 0 30px;
background: linear-gradient(90deg, var(--ths-accent), rgb(56 189 248 / 0.6));
border-radius: 999px;
}
.ths-projects-intro {
margin: 0;
color: var(--ths-text-secondary);
font-size: 1.35rem;
line-height: 1.55;
}
.ths-project-filters {
padding-bottom: 25px;
border-bottom: 1px solid var(--color-border);
}
.ths-project-filters > span {
display: block;
margin-bottom: 10px;
color: var(--ths-text-muted);
font-size: 1rem;
font-weight: 700;
text-transform: uppercase;
}
.ths-project-filters > div {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.ths-project-filters button {
padding: 6px 12px;
color: var(--ths-text-secondary);
font: inherit;
font-size: 1.05rem;
font-weight: 600;
background: var(--ths-bg-tertiary);
border: 1px solid var(--color-border);
border-radius: 6px;
cursor: pointer;
}
.ths-project-filters button:hover,
.ths-project-filters button.is-active {
color: #fff;
background: var(--ths-accent);
border-color: var(--ths-accent);
}
.ths-project-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 16px;
margin-top: 30px;
}
.ths-project-card {
min-width: 0;
background: var(--ths-bg-tertiary);
border: 1px solid var(--color-border);
border-radius: 7px;
transition: transform 0.18s ease, border-color 0.18s ease, box-shadow 0.18s ease;
}
.ths-project-card:hover {
transform: translateY(-2px);
border-color: rgb(59 130 246 / 0.45);
box-shadow: 0 16px 40px rgb(37 99 235 / 0.13);
}
.ths-project-card > a {
display: flex;
flex-direction: column;
height: 100%;
padding: 17px;
color: inherit;
}
.ths-project-card-heading {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
margin-bottom: 10px;
}
.ths-project-card h2 {
margin: 0;
color: var(--ths-text-primary);
font-size: 1.3rem;
font-weight: 750;
line-height: 1.3;
}
.ths-project-status {
flex: none;
padding: 3px 7px;
color: #fff7ed;
font-size: 0.9rem;
font-weight: 700;
background: #f59e0b;
border: 1px solid #fbbf24;
border-radius: 5px;
}
.ths-project-card p {
margin: 0 0 14px;
color: var(--ths-text-secondary);
font-size: 1.1rem;
line-height: 1.48;
}
.ths-project-tags {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-top: auto;
}
.ths-project-tags span {
padding: 4px 9px;
color: #93c5fd;
font-size: 0.9rem;
font-weight: 600;
background: rgb(59 130 246 / 0.1);
border: 1px solid rgb(59 130 246 / 0.5);
border-radius: 5px;
}
.ths-project-empty {
margin: 30px 0 0;
color: var(--ths-text-muted);
text-align: center;
}
.ths-projects-page .pagination {
margin-top: 30px;
}
@media (min-width: 768px) and (max-width: 1023px) {
.ths-posts-grid,
.ths-project-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 767px) {
.gh-navigation-logo {
width: auto;
min-width: 42px;
padding: 0 10px;
}
.gh-header {
min-height: 340px;
}
.gh-navigation {
height: auto;
min-height: 46px;
}
.gh-navigation-inner {
height: auto;
min-height: 46px;
}
.gh-navigation-menu .nav {
gap: 1.4rem;
row-gap: 0.8rem;
padding-block: 12px;
}
.gh-navigation-menu a,
.gh-footer-menu a {
font-size: 1.2rem;
}
.ths-hero {
box-sizing: border-box;
min-height: 340px;
padding-top: 58px;
}
.ths-hero-title {
font-size: clamp(3.8rem, 13vw, 4.8rem);
}
.gh-post-card {
padding: 16px;
}
.ths-posts-grid,
.ths-project-grid {
grid-template-columns: 1fr;
}
.ths-footer-grid {
grid-template-columns: 1fr;
gap: 26px;
}
}
File diff suppressed because it is too large Load Diff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 547 B

@@ -1 +0,0 @@
<svg width="264" height="88" viewBox="0 0 264 88" xmlns="http://www.w3.org/2000/svg"><title>default-skin 2</title><g fill="none" fill-rule="evenodd"><g><path d="M67.002 59.5v3.768c-6.307.84-9.184 5.75-10.002 9.732 2.22-2.83 5.564-5.098 10.002-5.098V71.5L73 65.585 67.002 59.5z" id="Shape" fill="#fff"/><g fill="#fff"><path d="M13 29v-5h2v3h3v2h-5zM13 15h5v2h-3v3h-2v-5zM31 15v5h-2v-3h-3v-2h5zM31 29h-5v-2h3v-3h2v5z" id="Shape"/></g><g fill="#fff"><path d="M62 24v5h-2v-3h-3v-2h5zM62 20h-5v-2h3v-3h2v5zM70 20v-5h2v3h3v2h-5zM70 24h5v2h-3v3h-2v-5z"/></g><path d="M20.586 66l-5.656-5.656 1.414-1.414L22 64.586l5.656-5.656 1.414 1.414L23.414 66l5.656 5.656-1.414 1.414L22 67.414l-5.656 5.656-1.414-1.414L20.586 66z" fill="#fff"/><path d="M111.785 65.03L110 63.5l3-3.5h-10v-2h10l-3-3.5 1.785-1.468L117 59l-5.215 6.03z" fill="#fff"/><path d="M152.215 65.03L154 63.5l-3-3.5h10v-2h-10l3-3.5-1.785-1.468L147 59l5.215 6.03z" fill="#fff"/><g><path id="Rectangle-11" fill="#fff" d="M160.957 28.543l-3.25-3.25-1.413 1.414 3.25 3.25z"/><path d="M152.5 27c3.038 0 5.5-2.462 5.5-5.5s-2.462-5.5-5.5-5.5-5.5 2.462-5.5 5.5 2.462 5.5 5.5 5.5z" id="Oval-1" stroke="#fff" stroke-width="1.5"/><path fill="#fff" d="M150 21h5v1h-5z"/></g><g><path d="M116.957 28.543l-1.414 1.414-3.25-3.25 1.414-1.414 3.25 3.25z" fill="#fff"/><path d="M108.5 27c3.038 0 5.5-2.462 5.5-5.5s-2.462-5.5-5.5-5.5-5.5 2.462-5.5 5.5 2.462 5.5 5.5 5.5z" stroke="#fff" stroke-width="1.5"/><path fill="#fff" d="M106 21h5v1h-5z"/><path fill="#fff" d="M109.043 19.008l-.085 5-1-.017.085-5z"/></g></g></g></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 866 B

@@ -1,93 +0,0 @@
function dropdown() {
const mediaQuery = window.matchMedia('(max-width: 767px)');
const head = document.querySelector('.gh-navigation');
const menu = head.querySelector('.gh-navigation-menu');
const nav = menu?.querySelector('.nav');
if (!nav) return;
const logo = document.querySelector('.gh-navigation-logo');
const navHTML = nav.innerHTML;
if (mediaQuery.matches) {
const items = nav.querySelectorAll('li');
items.forEach(function (item, index) {
item.style.transitionDelay = `${0.03 * (index + 1)}s`;
});
}
const makeDropdown = function () {
if (mediaQuery.matches) return;
const submenuItems = [];
while ((nav.offsetWidth + 64) > menu.offsetWidth) {
if (nav.lastElementChild) {
submenuItems.unshift(nav.lastElementChild);
nav.lastElementChild.remove();
} else {
break;
}
}
if (!submenuItems.length) {
head.classList.add('is-dropdown-loaded');
return;
}
const toggle = document.createElement('button');
toggle.setAttribute('class', 'gh-more-toggle gh-icon-button');
toggle.setAttribute('aria-label', 'More');
toggle.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="currentColor"><path d="M21.333 16c0-1.473 1.194-2.667 2.667-2.667v0c1.473 0 2.667 1.194 2.667 2.667v0c0 1.473-1.194 2.667-2.667 2.667v0c-1.473 0-2.667-1.194-2.667-2.667v0zM13.333 16c0-1.473 1.194-2.667 2.667-2.667v0c1.473 0 2.667 1.194 2.667 2.667v0c0 1.473-1.194 2.667-2.667 2.667v0c-1.473 0-2.667-1.194-2.667-2.667v0zM5.333 16c0-1.473 1.194-2.667 2.667-2.667v0c1.473 0 2.667 1.194 2.667 2.667v0c0 1.473-1.194 2.667-2.667 2.667v0c-1.473 0-2.667-1.194-2.667-2.667v0z"></path></svg>';
const wrapper = document.createElement('div');
wrapper.setAttribute('class', 'gh-dropdown');
if (submenuItems.length >= 10) {
head.classList.add('is-dropdown-mega');
wrapper.style.gridTemplateRows = `repeat(${Math.ceil(submenuItems.length / 2)}, 1fr)`;
} else {
head.classList.remove('is-dropdown-mega');
}
submenuItems.forEach(function (child) {
wrapper.appendChild(child);
});
toggle.appendChild(wrapper);
nav.appendChild(toggle);
const toggleRect = toggle.getBoundingClientRect();
const documentCenter = window.innerWidth / 2;
if (toggleRect.left < documentCenter) {
wrapper.classList.add('is-left');
}
head.classList.add('is-dropdown-loaded');
window.addEventListener('click', function (e) {
if (head.classList.contains('is-dropdown-open')) {
head.classList.remove('is-dropdown-open');
} else if (toggle.contains(e.target)) {
head.classList.add('is-dropdown-open');
}
});
}
imagesLoaded(logo, function () {
makeDropdown();
});
window.addEventListener('load', function () {
if (!logo) {
makeDropdown();
}
});
window.addEventListener('resize', function () {
setTimeout(() => {
nav.innerHTML = navHTML;
makeDropdown();
}, 1);
});
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).reframe=t()}(this,function(){"use strict";function t(){for(var e=0,t=0,n=arguments.length;t<n;t++)e+=arguments[t].length;for(var i=Array(e),o=0,t=0;t<n;t++)for(var r=arguments[t],f=0,d=r.length;f<d;f++,o++)i[o]=r[f];return i}return function(e,s){return void 0===s&&(s="js-reframe"),("string"==typeof e?t(document.querySelectorAll(e)):"length"in e?t(e):[e]).forEach(function(e){var t,n,i,o,r,f,d,l;-1!==e.className.split(" ").indexOf(s)||-1<e.style.width.indexOf("%")||(i=e.getAttribute("height")||e.offsetHeight,o=e.getAttribute("width")||e.offsetWidth,r=("string"==typeof i?parseInt(i):i)/("string"==typeof o?parseInt(o):o)*100,(f=document.createElement("div")).className=s,(d=f.style).position="relative",d.width="100%",d.paddingTop=r+"%",(l=e.style).position="absolute",l.width="100%",l.height="100%",l.left="0",l.top="0",null!==(t=e.parentNode)&&void 0!==t&&t.insertBefore(f,e),null!==(n=e.parentNode)&&void 0!==n&&n.removeChild(e),f.appendChild(e))})}});
@@ -1,103 +0,0 @@
function lightbox(trigger) {
var onThumbnailsClick = function (e) {
e.preventDefault();
var items = [];
var index = 0;
var prevSibling = e.target.closest('.kg-card').previousElementSibling;
while (prevSibling && (prevSibling.classList.contains('kg-image-card') || prevSibling.classList.contains('kg-gallery-card'))) {
var prevItems = [];
prevSibling.querySelectorAll('img').forEach(function (item) {
prevItems.push({
src: item.getAttribute('src'),
msrc: item.getAttribute('src'),
w: item.getAttribute('width'),
h: item.getAttribute('height'),
el: item,
})
index += 1;
});
prevSibling = prevSibling.previousElementSibling;
items = prevItems.concat(items);
}
if (e.target.classList.contains('kg-image')) {
items.push({
src: e.target.getAttribute('src'),
msrc: e.target.getAttribute('src'),
w: e.target.getAttribute('width'),
h: e.target.getAttribute('height'),
el: e.target,
});
} else {
var reachedCurrentItem = false;
e.target.closest('.kg-gallery-card').querySelectorAll('img').forEach(function (item) {
items.push({
src: item.getAttribute('src'),
msrc: item.getAttribute('src'),
w: item.getAttribute('width'),
h: item.getAttribute('height'),
el: item,
});
if (!reachedCurrentItem && item !== e.target) {
index += 1;
} else {
reachedCurrentItem = true;
}
});
}
var nextSibling = e.target.closest('.kg-card').nextElementSibling;
while (nextSibling && (nextSibling.classList.contains('kg-image-card') || nextSibling.classList.contains('kg-gallery-card'))) {
nextSibling.querySelectorAll('img').forEach(function (item) {
items.push({
src: item.getAttribute('src'),
msrc: item.getAttribute('src'),
w: item.getAttribute('width'),
h: item.getAttribute('height'),
el: item,
})
});
nextSibling = nextSibling.nextElementSibling;
}
var pswpElement = document.querySelectorAll('.pswp')[0];
var options = {
bgOpacity: 0.9,
closeOnScroll: true,
fullscreenEl: false,
history: false,
index: index,
shareEl: false,
zoomEl: false,
getThumbBoundsFn: function(index) {
var thumbnail = items[index].el,
pageYScroll = window.pageYOffset || document.documentElement.scrollTop,
rect = thumbnail.getBoundingClientRect();
return {x:rect.left, y:rect.top + pageYScroll, w:rect.width};
}
}
var gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, options);
gallery.init();
return false;
};
var triggers = document.querySelectorAll(trigger);
triggers.forEach(function (trig) {
trig.addEventListener('click', function (e) {
onThumbnailsClick(e);
});
});
}
@@ -1,60 +0,0 @@
/* Mobile menu burger toggle */
(function () {
const navigation = document.querySelector('.gh-navigation');
const burger = navigation.querySelector('.gh-burger');
if (!burger) return;
burger.addEventListener('click', function () {
if (!navigation.classList.contains('is-open')) {
navigation.classList.add('is-open');
document.documentElement.style.overflowY = 'hidden';
} else {
navigation.classList.remove('is-open');
document.documentElement.style.overflowY = null;
}
});
})();
/* Add lightbox to gallery images */
(function () {
lightbox(
'.kg-image-card > .kg-image[width][height], .kg-gallery-image > img'
);
})();
/* Responsive video in post content */
(function () {
const sources = [
'.gh-content iframe[src*="youtube.com"]',
'.gh-content iframe[src*="youtube-nocookie.com"]',
'.gh-content iframe[src*="player.vimeo.com"]',
'.gh-content iframe[src*="kickstarter.com"][src*="video.html"]',
'.gh-content object',
'.gh-content embed',
];
reframe(document.querySelectorAll(sources.join(',')));
})();
/* Turn the main nav into dropdown menu when there are more than 5 menu items */
(function () {
dropdown();
})();
/* Infinite scroll pagination */
(function () {
if (!document.body.classList.contains('home-template') && !document.body.classList.contains('post-template')) {
pagination();
}
})();
/* Responsive HTML table */
(function () {
const tables = document.querySelectorAll('.gh-content > table:not(.gist table)');
tables.forEach(function (table) {
const wrapper = document.createElement('div');
wrapper.className = 'gh-table';
table.parentNode.insertBefore(wrapper, table);
wrapper.appendChild(table);
});
})();
@@ -1,95 +0,0 @@
function pagination(isInfinite = true, done, isMasonry = false) {
const feedElement = document.querySelector('.gh-feed');
if (!feedElement) return;
let loading = false;
const target = document.querySelector('.gh-footer');
const buttonElement = document.querySelector('.gh-loadmore');
if (!document.querySelector('link[rel=next]') && buttonElement) {
buttonElement.remove();
}
const loadNextPage = async function () {
const nextElement = document.querySelector('link[rel=next]');
if (!nextElement) return;
try {
const res = await fetch(nextElement.href);
const html = await res.text();
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const postElements = doc.querySelectorAll('.gh-feed:not(.gh-featured):not(.gh-related) > *');
const fragment = document.createDocumentFragment();
const elems = [];
postElements.forEach(function (post) {
var clonedItem = document.importNode(post, true);
if (isMasonry) {
clonedItem.style.visibility = 'hidden';
}
fragment.appendChild(clonedItem);
elems.push(clonedItem);
});
feedElement.appendChild(fragment);
if (done) {
done(elems, loadNextWithCheck);
}
const resNextElement = doc.querySelector('link[rel=next]');
if (resNextElement && resNextElement.href) {
nextElement.href = resNextElement.href;
} else {
nextElement.remove();
if (buttonElement) {
buttonElement.remove();
}
}
} catch (e) {
nextElement.remove();
throw e;
}
};
const loadNextWithCheck = async function () {
if (target.getBoundingClientRect().top <= window.innerHeight && document.querySelector('link[rel=next]')) {
await loadNextPage();
}
}
const callback = async function (entries) {
if (loading) return;
loading = true;
if (entries[0].isIntersecting) {
// keep loading next page until target is out of the viewport or we've loaded the last page
if (!isMasonry) {
while (target.getBoundingClientRect().top <= window.innerHeight && document.querySelector('link[rel=next]')) {
await loadNextPage();
}
} else {
await loadNextPage();
}
}
loading = false;
if (!document.querySelector('link[rel=next]')) {
observer.disconnect();
}
};
const observer = new IntersectionObserver(callback);
if (isInfinite) {
observer.observe(target);
} else {
buttonElement.addEventListener('click', loadNextPage);
}
}
-64
View File
@@ -1,64 +0,0 @@
{{!< default}}
{{!-- The tag above means: insert everything in this file into the body of the default.hbs template --}}
<main class="gh-main gh-outer">
{{#author}}
<section class="gh-archive{{#if @custom.show_publication_info_sidebar}} has-sidebar{{/if}} gh-inner">
<div class="gh-archive-inner">
<div class="gh-archive-wrapper">
<h1 class="gh-article-title is-title">
{{#if website}}
<a class="gh-author-social-link" href="{{website}}" target="_blank" rel="noopener">{{name}}</a>
{{else}}
{{name}}
{{/if}}
</h1>
{{#if bio}}
<p class="gh-article-excerpt">{{bio}}</p>
{{/if}}
<footer class="gh-author-meta">
<div class="gh-author-social">
{{#if twitter}}
<a class="gh-author-social-link" href="{{social_url type="twitter"}}" target="_blank" rel="noopener">{{> "icons/x"}}</a>
{{/if}}
{{#if facebook}}
<a class="gh-author-social-link" href="{{social_url type="facebook"}}" target="_blank" rel="noopener">{{> "icons/facebook"}}</a>
{{/if}}
{{#if linkedin}}
<a class="gh-author-social-link" href="{{social_url type="linkedin"}}" target="_blank" rel="noopener">{{> "icons/linkedin"}}</a>
{{/if}}
{{#if bluesky}}
<a class="gh-author-social-link" href="{{social_url type="bluesky"}}" target="_blank" rel="noopener">{{> "icons/bluesky"}}</a>
{{/if}}
{{#if threads}}
<a class="gh-author-social-link" href="{{social_url type="threads"}}" target="_blank" rel="noopener">{{> "icons/threads"}}</a>
{{/if}}
{{#if mastodon}}
<a class="gh-author-social-link" href="{{social_url type="mastodon"}}" target="_blank" rel="noopener">{{> "icons/mastodon"}}</a>
{{/if}}
{{#if tiktok}}
<a class="gh-author-social-link" href="{{social_url type="tiktok"}}" target="_blank" rel="noopener">{{> "icons/tiktok"}}</a>
{{/if}}
{{#if youtube}}
<a class="gh-author-social-link" href="{{social_url type="youtube"}}" target="_blank" rel="noopener">{{> "icons/youtube"}}</a>
{{/if}}
{{#if instagram}}
<a class="gh-author-social-link" href="{{social_url type="instagram"}}" target="_blank" rel="noopener">{{> "icons/instagram"}}</a>
{{/if}}
</div>
{{#if location}}
<div class="gh-author-location">{{location}}</div>
{{/if}}
</footer>
</div>
{{#if profile_image}}
<img class="gh-article-image" src="{{img_url profile_image size="s"}}" alt="{{name}}">
{{/if}}
</div>
</section>
{{/author}}
{{> "components/post-list" feed="archive" postFeedStyle=@custom.post_feed_style showTitle=false showSidebar=@custom.show_publication_info_sidebar}}
</main>
@@ -1,74 +0,0 @@
<!DOCTYPE html>
<html lang="{{@site.locale}}">
<head>
{{!-- Basic meta - advanced meta is output with {{ghost_head}} below --}}
<title>{{meta_title page=(t " (Page %)")}}</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{{!-- Preload main styles and scripts for better performance --}}
<link rel="preload" as="style" href="{{asset "built/screen.css"}}">
<link rel="preload" as="script" href="{{asset "built/source.js"}}">
{{!-- Fonts are preloaded and defined in the default template to avoid layout shift --}}
{{> "typography/fonts"}}
{{!-- Theme assets - use the {{asset}} helper to reference styles & scripts, this will take care of caching and cache-busting automatically --}}
<link rel="stylesheet" type="text/css" href="{{asset "built/screen.css"}}">
<link rel="stylesheet" type="text/css" href="{{asset "css/personal.css"}}">
{{!-- Custom background color --}}
<style>
:root {
--background-color: {{@custom.site_background_color}}
}
</style>
<script>
/* The script for calculating the color contrast has been taken from
https://gomakethings.com/dynamically-changing-the-text-color-based-on-background-color-contrast-with-vanilla-js/ */
var accentColor = getComputedStyle(document.documentElement).getPropertyValue('--background-color');
accentColor = accentColor.trim().slice(1);
if (accentColor.length === 3) {
accentColor = accentColor[0] + accentColor[0] + accentColor[1] + accentColor[1] + accentColor[2] + accentColor[2];
}
var r = parseInt(accentColor.substr(0, 2), 16);
var g = parseInt(accentColor.substr(2, 2), 16);
var b = parseInt(accentColor.substr(4, 2), 16);
var yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000;
var textColor = (yiq >= 128) ? 'dark' : 'light';
document.documentElement.className = `has-${textColor}-text`;
</script>
{{!-- This tag outputs all your advanced SEO meta, structured data, and other important settings, it should always be the last tag before the closing head tag --}}
{{ghost_head}}
</head>
<body class="{{body_class}} has-{{#match @custom.title_font "Elegant serif"}}serif{{else match @custom.title_font "Consistent mono"}}mono{{else}}sans{{/match}}-title has-{{#match @custom.body_font "Elegant serif"}}serif{{else}}sans{{/match}}-body">
<div class="gh-viewport">
{{> "components/navigation" navigationLayout=@custom.navigation_layout}}
{{{body}}}
{{> "components/footer"}}
</div>
{{#is "post, page"}}
{{> "lightbox"}}
{{/is}}
{{!-- Scripts - handle responsive videos, infinite scroll, and navigation dropdowns --}}
<script src="{{asset "built/source.js"}}"></script>
{{!-- Ghost outputs required functional scripts with this tag, it should always be the last thing before the closing body tag --}}
{{ghost_foot}}
</body>
</html>
-177
View File
@@ -1,177 +0,0 @@
const {series, watch, src, dest, parallel} = require('gulp');
const pump = require('pump');
const path = require('path');
const releaseUtils = require('@tryghost/release-utils');
const inquirer = require('inquirer');
const {mergeLocales} = require('@tryghost/theme-translations/build');
// gulp plugins and utils
const livereload = require('gulp-livereload');
const postcss = require('gulp-postcss');
const zip = require('gulp-zip').default;
const concat = require('gulp-concat');
const uglify = require('gulp-uglify');
const beeper = require('beeper');
const fs = require('fs');
// postcss plugins
const autoprefixer = require('autoprefixer');
const cssnano = require('cssnano');
const easyimport = require('postcss-easy-import');
const REPO = 'TryGhost/Source';
const REPO_READONLY = 'TryGhost/Source';
const CHANGELOG_PATH = path.join(process.cwd(), '.', 'changelog.md');
function serve(done) {
livereload.listen();
done();
}
const handleError = (done) => {
return function (err) {
if (err) {
beeper();
}
return done(err);
};
};
function hbs(done) {
pump([
src(['*.hbs', 'partials/**/*.hbs']),
livereload()
], handleError(done));
}
function css(done) {
pump([
src('assets/css/screen.css', {sourcemaps: true}),
postcss([
easyimport,
autoprefixer(),
cssnano()
]),
dest('assets/built/', {sourcemaps: '.'}),
livereload()
], handleError(done));
}
function js(done) {
pump([
src([
// pull in lib files first so our own code can depend on it
'assets/js/lib/*.js',
'assets/js/*.js'
], {sourcemaps: true}),
concat('source.js'),
uglify(),
dest('assets/built/', {sourcemaps: '.'}),
livereload()
], handleError(done));
}
function zipper(done) {
const filename = require('./package.json').name + '.zip';
pump([
src([
'**',
'!node_modules', '!node_modules/**',
'!dist', '!dist/**',
'!yarn-error.log',
'!yarn.lock',
'!gulpfile.js'
]),
zip(filename),
dest('dist/')
], handleError(done));
}
const cssWatcher = () => watch('assets/css/**', css);
const jsWatcher = () => watch('assets/js/**', js);
const hbsWatcher = () => watch(['*.hbs', 'partials/**/*.hbs'], hbs);
const localesWatcher = () => watch('./locales-local/**/*.json', mergeLocales());
const watcher = parallel(cssWatcher, jsWatcher, hbsWatcher, localesWatcher);
const build = series(css, js, mergeLocales());
exports.build = build;
exports.zip = series(build, zipper);
exports.default = series(build, serve, watcher);
exports.release = async () => {
// @NOTE: https://yarnpkg.com/lang/en/docs/cli/version/
// require(./package.json) can run into caching issues, this re-reads from file everytime on release
let packageJSON = JSON.parse(fs.readFileSync('./package.json'));
const newVersion = packageJSON.version;
if (!newVersion || newVersion === '') {
console.log(`Invalid version: ${newVersion}`);
return;
}
console.log(`\nCreating release for ${newVersion}...`);
const githubToken = process.env.GST_TOKEN;
if (!githubToken) {
console.log('Please configure your environment with a GitHub token located in GST_TOKEN');
return;
}
try {
const prompt = inquirer.createPromptModule();
const result = await prompt([{
type: 'input',
name: 'compatibleWithGhost',
message: 'Which version of Ghost is it compatible with?',
default: '5.67.0'
}]);
const compatibleWithGhost = result.compatibleWithGhost;
const releasesResponse = await releaseUtils.releases.get({
userAgent: 'Source',
uri: `https://api.github.com/repos/${REPO_READONLY}/releases`
});
if (!releasesResponse || !releasesResponse) {
console.log('No releases found. Skipping...');
return;
}
let previousVersion = releasesResponse[0].tag_name || releasesResponse[0].name;
console.log(`Previous version: ${previousVersion}`);
const changelog = new releaseUtils.Changelog({
changelogPath: CHANGELOG_PATH,
folder: path.join(process.cwd(), '.')
});
changelog
.write({
githubRepoPath: `https://github.com/${REPO}`,
lastVersion: previousVersion
})
.sort()
.clean();
const newReleaseResponse = await releaseUtils.releases.create({
draft: true,
preRelease: false,
tagName: 'v' + newVersion,
releaseName: newVersion,
userAgent: 'Source',
uri: `https://api.github.com/repos/${REPO}/releases`,
github: {
token: githubToken
},
content: [`**Compatible with Ghost ≥ ${compatibleWithGhost}**\n\n`],
changelogPath: CHANGELOG_PATH
});
console.log(`\nRelease draft generated: ${newReleaseResponse.releaseUrl}\n`);
} catch (err) {
console.error(err);
process.exit(1);
}
};
-12
View File
@@ -1,12 +0,0 @@
{{!< default}}
{{!-- The tag above means: insert everything in this file into the body of the default.hbs template --}}
{{> "components/header" headerStyle=@custom.header_style}}
{{#match @custom.header_style "!=" "Highlight"}}
{{> "components/featured" showFeatured=@custom.show_featured_posts limit=4}}
{{/match}}
{{> "components/cta"}}
{{> "components/post-list" feed="home" postFeedStyle=@custom.post_feed_style showTitle=true showSidebar=@custom.show_publication_info_sidebar}}
-6
View File
@@ -1,6 +0,0 @@
{{!< default}}
{{!-- The tag above means: insert everything in this file into the body of the default.hbs template --}}
<main class="gh-main">
{{> "components/post-list" feed="index" postFeedStyle=@custom.post_feed_style showTitle=true showSidebar=@custom.show_publication_info_sidebar}}
</main>
@@ -1,121 +0,0 @@
{
" (Page %)": "alto/default.hbs, bulletin/default.hbs, dawn/default.hbs, digest/default.hbs, dope/default.hbs, ease/default.hbs, edge/default.hbs, edition/default.hbs, episode/default.hbs, headline/default.hbs, journal/default.hbs, london/default.hbs, ruby/default.hbs, solo/default.hbs, source/default.hbs, taste/default.hbs, wave/default.hbs",
" and ": "ghost-tpl/content-cta.hbs",
"% issues": "journal/index.hbs",
"% min": "headline/partials/post-meta.hbs",
"% min read": "_shared/article.hbs, alto/partials/loop.hbs, alto/post.hbs, dawn/partials/content.hbs, dawn/partials/loop.hbs, episode/partials/components/article.hbs, journal/partials/loop.hbs, solo/partials/loop.hbs, source/post.hbs, taste/partials/components/article.hbs",
"0 issues": "journal/index.hbs",
"1 issue": "journal/index.hbs",
"1 min": "headline/partials/post-meta.hbs",
"1 min read": "_shared/article.hbs, alto/partials/loop.hbs, alto/post.hbs, dawn/partials/content.hbs, dawn/partials/loop.hbs, episode/partials/components/article.hbs, journal/partials/loop.hbs, solo/partials/loop.hbs, source/post.hbs, taste/partials/components/article.hbs",
"A collection of 0 issues": "bulletin/index.hbs, bulletin/tag.hbs, digest/index.hbs, digest/tag.hbs, journal/tag.hbs",
"A collection of 0 posts": "london/tag.hbs",
"A collection of 1 issue": "bulletin/index.hbs, bulletin/tag.hbs, digest/index.hbs, digest/tag.hbs, journal/tag.hbs",
"A collection of 1 post": "london/tag.hbs",
"A collection of {numberOfIssues} issues": "bulletin/index.hbs, bulletin/tag.hbs, digest/index.hbs, digest/tag.hbs, journal/tag.hbs",
"A collection of {numberOfPosts} posts": "london/tag.hbs",
"About": "journal/index.hbs",
"Access code": "ghost-private/private.hbs",
"Account": "_shared/navbar.hbs, alto/default.hbs, bulletin/default.hbs, dawn/default.hbs, digest/default.hbs, ease/default.hbs, edge/default.hbs, edition/default.hbs, episode/partials/components/navbar.hbs, headline/default.hbs, journal/default.hbs, london/default.hbs, ruby/default.hbs, solo/default.hbs, source/partials/components/navigation.hbs, taste/partials/components/navbar.hbs, wave/default.hbs",
"Additional issues will be published soon.": "journal/index.hbs",
"All episodes": "episode/index.hbs",
"All episodes →": "episode/home.hbs",
"Already have an account?": "bulletin/partials/content-cta.hbs, dawn/partials/content-cta.hbs, digest/partials/content-cta.hbs, edition/partials/content-cta.hbs, ghost-tpl/content-cta.hbs, journal/partials/content-cta.hbs, solo/partials/content-cta.hbs",
"Archive": "bulletin/index.hbs, digest/index.hbs",
"Browse all issues": "digest/partials/content.hbs",
"Browse archive": "bulletin/partials/article.hbs",
"By {authors}": "bulletin/partials/article.hbs, ruby/post.hbs",
"By {primaryAuthor}": "bulletin/home.hbs",
"Close": "ghost-private/private.hbs",
"Close (Esc)": "alto/partials/pswp.hbs, bulletin/partials/pswp.hbs, dawn/partials/pswp.hbs, digest/partials/pswp.hbs, dope/partials/pswp.hbs, ease/partials/pswp.hbs, edge/partials/pswp.hbs, edition/partials/pswp.hbs, episode/partials/pswp.hbs, headline/partials/pswp.hbs, journal/partials/pswp.hbs, london/partials/pswp.hbs, ruby/partials/pswp.hbs, solo/partials/pswp.hbs, source/partials/lightbox.hbs, taste/partials/pswp.hbs",
"Comments": "alto/post.hbs, headline/partials/comments.hbs",
"Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.": "bulletin/home.hbs, journal/default.hbs",
"Email sent": "source/partials/email-subscription.hbs",
"Enter": "ghost-private/private.hbs",
"Enter access code": "ghost-private/private.hbs",
"Enter your email": "casper/post.hbs",
"Featured": "digest/index.hbs, journal/index.hbs, source/partials/components/featured.hbs",
"Go to the front page →": "casper/error-404.hbs, casper/error.hbs",
"Latest": "_shared/list.hbs, digest/index.hbs, episode/partials/components/list.hbs, headline/home.hbs, headline/index.hbs, journal/index.hbs, source/partials/components/post-list.hbs, taste/partials/components/list.hbs",
"Latest episodes": "episode/home.hbs",
"Latest issue": "bulletin/home.hbs, digest/partials/content.hbs",
"Learn more": "_shared/header-content.hbs, episode/partials/components/header-content.hbs, taste/partials/components/header-content.hbs",
"Listen on": "episode/partials/podcast-services.hbs",
"Load more issues": "journal/author.hbs, journal/index.hbs, journal/tag.hbs",
"Login": "dawn/partials/cover.hbs",
"Member discussion:": "wave/post.hbs",
"Members": "solo/partials/loop.hbs",
"Members only": "casper/partials/post-card.hbs",
"Menu": "source/partials/components/navigation.hbs",
"More": "headline/partials/topic-minimal.hbs",
"More issues": "journal/index.hbs",
"More links": "dope/partials/side-menu.hbs",
"Newer Posts": "ghost-tpl/pagination.hbs",
"Next": "alto/partials/pagination.hbs, digest/partials/content.hbs, ruby/partials/post-navigation.hbs",
"Next (arrow right)": "alto/partials/pswp.hbs, bulletin/partials/pswp.hbs, dawn/partials/pswp.hbs, digest/partials/pswp.hbs, dope/partials/pswp.hbs, ease/partials/pswp.hbs, edge/partials/pswp.hbs, edition/partials/pswp.hbs, episode/partials/pswp.hbs, headline/partials/pswp.hbs, journal/partials/pswp.hbs, london/partials/pswp.hbs, ruby/partials/pswp.hbs, solo/partials/pswp.hbs, source/partials/lightbox.hbs, taste/partials/pswp.hbs",
"Next issue": "bulletin/partials/article.hbs, digest/partials/content.hbs",
"Next post": "dawn/partials/content.hbs",
"Next →": "solo/post.hbs",
"Older Posts": "ghost-tpl/pagination.hbs",
"Page {page} of {totalPages}": "alto/partials/pagination.hbs, ghost-tpl/pagination.hbs",
"Paid": "solo/partials/loop.hbs",
"Paid-members only": "casper/partials/post-card.hbs",
"Please enter a valid email address": "ghost-private/private.hbs",
"Powered by Ghost": "casper/default.hbs",
"Powered by {ghostlink}": "_shared/footer.hbs, alto/default.hbs, bulletin/default.hbs, dawn/default.hbs, digest/default.hbs, dope/default.hbs, ease/default.hbs, edge/default.hbs, edition/default.hbs, episode/partials/components/footer.hbs, headline/default.hbs, journal/default.hbs, london/default.hbs, ruby/default.hbs, solo/default.hbs, source/partials/components/footer.hbs, taste/partials/components/footer.hbs, wave/default.hbs",
"Previous": "alto/partials/pagination.hbs, digest/partials/content.hbs, ruby/partials/post-navigation.hbs",
"Previous (arrow left)": "alto/partials/pswp.hbs, bulletin/partials/pswp.hbs, dawn/partials/pswp.hbs, digest/partials/pswp.hbs, dope/partials/pswp.hbs, ease/partials/pswp.hbs, edge/partials/pswp.hbs, edition/partials/pswp.hbs, episode/partials/pswp.hbs, headline/partials/pswp.hbs, journal/partials/pswp.hbs, london/partials/pswp.hbs, ruby/partials/pswp.hbs, solo/partials/pswp.hbs, source/partials/lightbox.hbs, taste/partials/pswp.hbs",
"Previous issue": "bulletin/partials/article.hbs, digest/partials/content.hbs",
"Previous post": "dawn/partials/content.hbs",
"Published by:": "dawn/partials/content.hbs",
"RSS": "london/author.hbs",
"Read latest issue": "bulletin/home.hbs",
"Read more": "ruby/partials/loop.hbs, source/post.hbs",
"Read next": "headline/partials/related-posts.hbs",
"Recent posts": "dope/partials/side-menu.hbs",
"Recommendations": "dope/partials/side-menu.hbs, source/partials/components/post-list.hbs",
"Search posts, tags and authors": "_shared/header-content.hbs, ease/default.hbs, episode/partials/components/header-content.hbs, source/partials/components/header-content.hbs, taste/partials/components/header-content.hbs",
"Search this site": "_shared/navbar.hbs, alto/default.hbs, bulletin/default.hbs, bulletin/home.hbs, dawn/default.hbs, digest/default.hbs, dope/partials/side-menu.hbs, ease/default.hbs, edge/default.hbs, edition/default.hbs, episode/partials/components/navbar.hbs, headline/default.hbs, journal/default.hbs, london/default.hbs, ruby/default.hbs, solo/default.hbs, source/partials/search-toggle.hbs, taste/partials/components/navbar.hbs, wave/default.hbs",
"See all": "dope/partials/side-menu.hbs, source/partials/components/post-list.hbs",
"Share": "alto/partials/pswp.hbs, bulletin/partials/pswp.hbs, dawn/partials/pswp.hbs, digest/partials/pswp.hbs, dope/partials/pswp.hbs, ease/partials/pswp.hbs, edge/partials/pswp.hbs, edition/partials/pswp.hbs, episode/partials/pswp.hbs, headline/partials/pswp.hbs, journal/partials/pswp.hbs, london/partials/pswp.hbs, ruby/partials/pswp.hbs, solo/partials/pswp.hbs, source/partials/lightbox.hbs, taste/partials/pswp.hbs",
"Show more": "headline/home.hbs, headline/partials/topic-grid.hbs",
"Sign in": "_shared/navbar.hbs, alto/default.hbs, bulletin/default.hbs, bulletin/partials/content-cta.hbs, dawn/default.hbs, dawn/partials/content-cta.hbs, digest/default.hbs, ease/default.hbs, edge/default.hbs, edition/default.hbs, edition/partials/content-cta.hbs, episode/partials/components/navbar.hbs, ghost-tpl/content-cta.hbs, headline/default.hbs, journal/default.hbs, london/default.hbs, ruby/default.hbs, solo/default.hbs, source/partials/components/navigation.hbs, taste/partials/components/navbar.hbs, wave/default.hbs",
"Sign in.": "digest/partials/content-cta.hbs, journal/partials/content-cta.hbs, solo/partials/content-cta.hbs",
"Sign up now to get access to the library of members-only issues.": "digest/default.hbs, journal/index.hbs",
"Signups from this email domain are currently restricted.": "ghost-private/private.hbs",
"Site owner login": "ghost-private/private.hbs",
"Something went wrong, please try again.": "ghost-private/private.hbs",
"Stay tuned": "bulletin/home.hbs",
"Subscribe": "_shared/cta.hbs, _shared/header-content.hbs, _shared/navbar.hbs, alto/default.hbs, bulletin/default.hbs, dawn/default.hbs, digest/default.hbs, ease/default.hbs, edge/default.hbs, edition/default.hbs, episode/partials/components/cta.hbs, episode/partials/components/header-content.hbs, episode/partials/components/navbar.hbs, headline/default.hbs, journal/default.hbs, journal/index.hbs, london/default.hbs, ruby/default.hbs, solo/default.hbs, solo/index.hbs, source/partials/components/navigation.hbs, source/partials/components/post-list.hbs, source/partials/email-subscription.hbs, taste/partials/components/cta.hbs, taste/partials/components/header-content.hbs, taste/partials/components/navbar.hbs, wave/default.hbs",
"Subscribe now": "bulletin/partials/content-cta.hbs, dawn/partials/content-cta.hbs, dawn/partials/cover.hbs, digest/partials/content-cta.hbs, edition/partials/content-cta.hbs, ghost-tpl/content-cta.hbs, headline/default.hbs, journal/partials/content-cta.hbs, solo/partials/content-cta.hbs",
"Subscribe to {sitetitle}": "digest/default.hbs, headline/default.hbs, journal/default.hbs",
"Subscription confirmed!": "ghost-private/private.hbs",
"Thanks! Now check your email to confirm.": "ghost-private/private.hbs",
"Theme errors": "casper/error.hbs",
"This page is for paying subscribers only": "ghost-tpl/content-cta.hbs",
"This page is for subscribers on the {tiers} only": "ghost-tpl/content-cta.hbs",
"This page is for subscribers only": "ghost-tpl/content-cta.hbs",
"This post is for paying subscribers only": "bulletin/partials/content-cta.hbs, dawn/partials/content-cta.hbs, digest/partials/content-cta.hbs, edition/partials/content-cta.hbs, ghost-tpl/content-cta.hbs, journal/partials/content-cta.hbs, solo/partials/content-cta.hbs",
"This post is for subscribers on the {tiers} only": "bulletin/partials/content-cta.hbs, dawn/partials/content-cta.hbs, digest/partials/content-cta.hbs, edition/partials/content-cta.hbs, ghost-tpl/content-cta.hbs, journal/partials/content-cta.hbs, solo/partials/content-cta.hbs",
"This post is for subscribers only": "bulletin/partials/content-cta.hbs, dawn/partials/content-cta.hbs, digest/partials/content-cta.hbs, edition/partials/content-cta.hbs, ghost-tpl/content-cta.hbs, journal/partials/content-cta.hbs, solo/partials/content-cta.hbs",
"Toggle fullscreen": "alto/partials/pswp.hbs, bulletin/partials/pswp.hbs, dawn/partials/pswp.hbs, digest/partials/pswp.hbs, dope/partials/pswp.hbs, ease/partials/pswp.hbs, edge/partials/pswp.hbs, edition/partials/pswp.hbs, episode/partials/pswp.hbs, headline/partials/pswp.hbs, journal/partials/pswp.hbs, london/partials/pswp.hbs, ruby/partials/pswp.hbs, solo/partials/pswp.hbs, source/partials/lightbox.hbs, taste/partials/pswp.hbs",
"Toggle menu": "_shared/navbar.hbs, alto/default.hbs, bulletin/default.hbs, dawn/default.hbs, digest/default.hbs, ease/default.hbs, edge/default.hbs, edition/default.hbs, episode/partials/components/navbar.hbs, headline/default.hbs, journal/default.hbs, london/default.hbs, ruby/default.hbs, solo/default.hbs, taste/partials/components/navbar.hbs, wave/default.hbs",
"Too many sign-up attempts, try again later": "ghost-private/private.hbs",
"Topic": "journal/tag.hbs",
"Topics": "journal/index.hbs",
"Upgrade": "source/partials/components/post-list.hbs",
"Upgrade now": "bulletin/partials/content-cta.hbs, dawn/partials/content-cta.hbs, digest/partials/content-cta.hbs, edition/partials/content-cta.hbs, journal/index.hbs, journal/partials/content-cta.hbs, solo/partials/content-cta.hbs",
"Upgrade to a paid account to get full access.": "journal/index.hbs",
"Upgrade your account": "ghost-tpl/content-cta.hbs",
"View all →": "_shared/list.hbs, episode/partials/components/list.hbs, taste/partials/components/list.hbs",
"View project": "edge/partials/loop.hbs",
"Website": "london/author.hbs",
"You might also like...": "alto/partials/related.hbs, dawn/partials/related-posts.hbs, ruby/partials/related-posts.hbs",
"Your email address": "ghost-private/private.hbs",
"Zoom in/out": "alto/partials/pswp.hbs, bulletin/partials/pswp.hbs, dawn/partials/pswp.hbs, digest/partials/pswp.hbs, dope/partials/pswp.hbs, ease/partials/pswp.hbs, edge/partials/pswp.hbs, edition/partials/pswp.hbs, episode/partials/pswp.hbs, headline/partials/pswp.hbs, journal/partials/pswp.hbs, london/partials/pswp.hbs, ruby/partials/pswp.hbs, solo/partials/pswp.hbs, source/partials/lightbox.hbs, taste/partials/pswp.hbs",
"comment": "_shared/article.hbs, episode/partials/components/article.hbs, solo/post.hbs, taste/partials/components/article.hbs",
"comments": "_shared/article.hbs, episode/partials/components/article.hbs, solo/post.hbs, taste/partials/components/article.hbs",
"in {primaryTag}": "bulletin/partials/article.hbs, ruby/post.hbs",
"jamie@example.com": "_shared/cta.hbs, _shared/header-content.hbs, bulletin/home.hbs, digest/default.hbs, episode/partials/components/cta.hbs, episode/partials/components/header-content.hbs, journal/default.hbs, journal/index.hbs, source/partials/email-subscription.hbs, taste/partials/components/cta.hbs, taste/partials/components/header-content.hbs",
"← Previous": "solo/post.hbs"
}
@@ -1,121 +0,0 @@
{
" (Page %)": "",
" and ": "",
"% issues": "",
"% min": "",
"% min read": "Lesezeit: % Minuten",
"0 issues": "",
"1 issue": "",
"1 min": "",
"1 min read": "Lesezeit: 1 Minute",
"A collection of 0 issues": "",
"A collection of 0 posts": "",
"A collection of 1 issue": "",
"A collection of 1 post": "",
"A collection of {numberOfIssues} issues": "",
"A collection of {numberOfPosts} posts": "",
"About": "",
"Access code": "",
"Account": "Konto",
"Additional issues will be published soon.": "",
"All episodes": "",
"All episodes →": "",
"Already have an account?": "",
"Archive": "",
"Browse all issues": "",
"Browse archive": "",
"By {authors}": "",
"By {primaryAuthor}": "",
"Close": "",
"Close (Esc)": "Schliessen (ESC)",
"Comments": "",
"Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.": "",
"Email sent": "",
"Enter": "",
"Enter access code": "",
"Enter your email": "",
"Featured": "Hervorgehoben",
"Go to the front page →": "",
"Latest": "Neu",
"Latest episodes": "",
"Latest issue": "",
"Learn more": "",
"Listen on": "",
"Load more issues": "",
"Login": "",
"Member discussion:": "",
"Members": "",
"Members only": "",
"Menu": "",
"More": "",
"More issues": "",
"More links": "",
"Newer Posts": "",
"Next": "",
"Next (arrow right)": "Nächstes Bild (Rechte Pfeiltaste)",
"Next issue": "",
"Next post": "",
"Next →": "",
"Older Posts": "",
"Page {page} of {totalPages}": "",
"Paid": "",
"Paid-members only": "",
"Please enter a valid email address": "",
"Powered by Ghost": "",
"Powered by {ghostlink}": "Läuft mit {ghostlink}",
"Previous": "",
"Previous (arrow left)": "Vorheriges Bild (Linke Pfeiltaste)",
"Previous issue": "",
"Previous post": "",
"Published by:": "",
"RSS": "",
"Read latest issue": "",
"Read more": "Lies mehr",
"Read next": "",
"Recent posts": "",
"Recommendations": "Empfehlungen",
"Search posts, tags and authors": "Suche nach Beiträgen, Schlagwörtern und Autoren",
"Search this site": "Suche auf dieser Webseite",
"See all": "Alles ansehen",
"Share": "Teilen",
"Show more": "",
"Sign in": "Einloggen",
"Sign in.": "",
"Sign up now to get access to the library of members-only issues.": "",
"Signups from this email domain are currently restricted.": "",
"Site owner login": "",
"Something went wrong, please try again.": "",
"Stay tuned": "",
"Subscribe": "Abonnieren",
"Subscribe now": "",
"Subscribe to {sitetitle}": "",
"Subscription confirmed!": "",
"Thanks! Now check your email to confirm.": "",
"Theme errors": "",
"This page is for paying subscribers only": "",
"This page is for subscribers on the {tiers} only": "",
"This page is for subscribers only": "",
"This post is for paying subscribers only": "",
"This post is for subscribers on the {tiers} only": "",
"This post is for subscribers only": "",
"Toggle fullscreen": "Vollbildanzeige",
"Toggle menu": "",
"Too many sign-up attempts, try again later": "",
"Topic": "",
"Topics": "",
"Upgrade": "Upgrade",
"Upgrade now": "",
"Upgrade to a paid account to get full access.": "",
"Upgrade your account": "Mitgliedschafts-Stufe anpassen",
"View all →": "",
"View project": "",
"Website": "",
"You might also like...": "",
"Your email address": "",
"Zoom in/out": "Zoomen",
"comment": "",
"comments": "",
"in {primaryTag}": "",
"jamie@example.com": "hans.muster@beispiel.com",
"← Previous": ""
}
@@ -1,121 +0,0 @@
{
" (Page %)": " (Seite %)",
" and ": " und ",
"% issues": "% Ausgaben",
"% min": "% Min.",
"% min read": "% Min. Lesezeit",
"0 issues": "0 Ausgaben",
"1 issue": "1 Ausgabe",
"1 min": "1 Min.",
"1 min read": "1 Min. Lesezeit",
"A collection of 0 issues": "Eine Sammlung von 0 Ausgaben",
"A collection of 0 posts": "Eine Sammlung von 0 Beiträgen",
"A collection of 1 issue": "Eine Sammlung von einer Ausgabe",
"A collection of 1 post": "Eine Sammlung von einem Beitrag",
"A collection of {numberOfIssues} issues": "Eine Sammlung von {numberOfIssues} Ausgaben",
"A collection of {numberOfPosts} posts": "Eine Sammlung von {numberOfPosts} Beiträgen",
"About": "Über",
"Access code": "",
"Account": "Konto",
"Additional issues will be published soon.": "Weitere Ausgaben werden in Kürze veröffentlicht.",
"All episodes": "Alle Episoden",
"All episodes →": "Alle Episoden →",
"Already have an account?": "Bereits ein Konto?",
"Archive": "Archiv",
"Browse all issues": "Alle Ausgaben durchsuchen",
"Browse archive": "Archiv durchsuchen",
"By {authors}": "Von {authors}",
"By {primaryAuthor}": "Von {primaryAuthor}",
"Close": "",
"Close (Esc)": "Schließen (Esc)",
"Comments": "Kommentare",
"Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.": "",
"Email sent": "E-Mail gesendet",
"Enter": "",
"Enter access code": "",
"Enter your email": "E-Mail-Adresse eingeben",
"Featured": "Empfohlen",
"Go to the front page →": "Zur Startseite →",
"Latest": "Neueste",
"Latest episodes": "Neueste Episoden",
"Latest issue": "Neueste Ausgabe",
"Learn more": "Mehr erfahren",
"Listen on": "Verfügbar auf",
"Load more issues": "Weitere Ausgaben laden",
"Login": "Anmelden",
"Member discussion:": "Mitgliederdiskussion:",
"Members": "Mitglieder",
"Members only": "Nur für Mitglieder",
"Menu": "Menü",
"More": "Mehr",
"More issues": "Weitere Ausgaben",
"More links": "Weitere Links",
"Newer Posts": "Neuere Beiträge",
"Next": "Weiter",
"Next (arrow right)": "Weiter (Pfeiltaste rechts)",
"Next issue": "Nächste Ausgabe",
"Next post": "Nächster Beitrag",
"Next →": "Weiter →",
"Older Posts": "Ältere Beiträge",
"Page {page} of {totalPages}": "Seite {page} von {totalPages}",
"Paid": "Kostenpflichtig",
"Paid-members only": "Nur für zahlende Mitglieder",
"Please enter a valid email address": "",
"Powered by Ghost": "Erstellt mit Ghost",
"Powered by {ghostlink}": "Erstellt mit {ghostlink}",
"Previous": "Zurück",
"Previous (arrow left)": "Zurück (Pfeiltaste links)",
"Previous issue": "Vorherige Ausgabe",
"Previous post": "Vorheriger Beitrag",
"Published by:": "Veröffentlicht von:",
"RSS": "RSS",
"Read latest issue": "Neueste Ausgabe lesen",
"Read more": "Weiterlesen",
"Read next": "Lies auch",
"Recent posts": "Aktuelle Beiträge",
"Recommendations": "Empfehlungen",
"Search posts, tags and authors": "Beiträge, Tags und Autoren durchsuchen",
"Search this site": "Diese Seite durchsuchen",
"See all": "Alle anzeigen",
"Share": "Teilen",
"Show more": "Mehr anzeigen",
"Sign in": "Anmelden",
"Sign in.": "Anmelden.",
"Sign up now to get access to the library of members-only issues.": "Jetzt anmelden und alle Mitglieder-Ausgaben lesen.",
"Signups from this email domain are currently restricted.": "",
"Site owner login": "",
"Something went wrong, please try again.": "",
"Stay tuned": "Bleib auf dem Laufenden",
"Subscribe": "Abonnieren",
"Subscribe now": "Jetzt abonnieren",
"Subscribe to {sitetitle}": "{sitetitle} abonnieren",
"Subscription confirmed!": "",
"Thanks! Now check your email to confirm.": "",
"Theme errors": "Theme-Fehler",
"This page is for paying subscribers only": "Diese Seite ist nur für zahlende Abonnenten",
"This page is for subscribers on the {tiers} only": "Diese Seite ist nur für Abonnenten der Tarife {tiers}",
"This page is for subscribers only": "Diese Seite ist nur für Abonnenten",
"This post is for paying subscribers only": "Dieser Beitrag ist nur für zahlende Abonnenten",
"This post is for subscribers on the {tiers} only": "Dieser Beitrag ist nur für Abonnenten der Tarife {tiers}",
"This post is for subscribers only": "Dieser Beitrag ist nur für Abonnenten",
"Toggle fullscreen": "Vollbild umschalten",
"Toggle menu": "Menü umschalten",
"Too many sign-up attempts, try again later": "",
"Topic": "Thema",
"Topics": "Themen",
"Upgrade": "Upgraden",
"Upgrade now": "Jetzt upgraden",
"Upgrade to a paid account to get full access.": "Wechsle zum kostenpflichtigen Konto, um vollen Zugang zu erhalten.",
"Upgrade your account": "Konto upgraden",
"View all →": "Alle anzeigen →",
"View project": "Projekt ansehen",
"Website": "Website",
"You might also like...": "Das könnte dich auch interessieren …",
"Your email address": "",
"Zoom in/out": "Vergrößern/Verkleinern",
"comment": "Kommentar",
"comments": "Kommentare",
"in {primaryTag}": "in {primaryTag}",
"jamie@example.com": "jamie@example.com",
"← Previous": "← Zurück"
}
@@ -1,121 +0,0 @@
{
" (Page %)": "",
" and ": "",
"% issues": "",
"% min": "",
"% min read": "",
"0 issues": "",
"1 issue": "",
"1 min": "",
"1 min read": "",
"A collection of 0 issues": "",
"A collection of 0 posts": "",
"A collection of 1 issue": "",
"A collection of 1 post": "",
"A collection of {numberOfIssues} issues": "",
"A collection of {numberOfPosts} posts": "",
"About": "",
"Access code": "",
"Account": "",
"Additional issues will be published soon.": "",
"All episodes": "",
"All episodes →": "",
"Already have an account?": "",
"Archive": "",
"Browse all issues": "",
"Browse archive": "",
"By {authors}": "",
"By {primaryAuthor}": "",
"Close": "",
"Close (Esc)": "",
"Comments": "",
"Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.": "",
"Email sent": "",
"Enter": "",
"Enter access code": "",
"Enter your email": "",
"Featured": "",
"Go to the front page →": "",
"Latest": "",
"Latest episodes": "",
"Latest issue": "",
"Learn more": "",
"Listen on": "",
"Load more issues": "",
"Login": "",
"Member discussion:": "",
"Members": "",
"Members only": "",
"Menu": "",
"More": "",
"More issues": "",
"More links": "",
"Newer Posts": "",
"Next": "",
"Next (arrow right)": "",
"Next issue": "",
"Next post": "",
"Next →": "",
"Older Posts": "",
"Page {page} of {totalPages}": "",
"Paid": "",
"Paid-members only": "",
"Please enter a valid email address": "",
"Powered by Ghost": "",
"Powered by {ghostlink}": "",
"Previous": "",
"Previous (arrow left)": "",
"Previous issue": "",
"Previous post": "",
"Published by:": "",
"RSS": "",
"Read latest issue": "",
"Read more": "",
"Read next": "",
"Recent posts": "",
"Recommendations": "",
"Search posts, tags and authors": "",
"Search this site": "",
"See all": "",
"Share": "",
"Show more": "",
"Sign in": "",
"Sign in.": "",
"Sign up now to get access to the library of members-only issues.": "",
"Signups from this email domain are currently restricted.": "",
"Site owner login": "",
"Something went wrong, please try again.": "",
"Stay tuned": "",
"Subscribe": "",
"Subscribe now": "",
"Subscribe to {sitetitle}": "",
"Subscription confirmed!": "",
"Thanks! Now check your email to confirm.": "",
"Theme errors": "",
"This page is for paying subscribers only": "",
"This page is for subscribers on the {tiers} only": "",
"This page is for subscribers only": "",
"This post is for paying subscribers only": "",
"This post is for subscribers on the {tiers} only": "",
"This post is for subscribers only": "",
"Toggle fullscreen": "",
"Toggle menu": "",
"Too many sign-up attempts, try again later": "",
"Topic": "",
"Topics": "",
"Upgrade": "",
"Upgrade now": "",
"Upgrade to a paid account to get full access.": "",
"Upgrade your account": "",
"View all →": "",
"View project": "",
"Website": "",
"You might also like...": "",
"Your email address": "",
"Zoom in/out": "",
"comment": "",
"comments": "",
"in {primaryTag}": "",
"jamie@example.com": "",
"← Previous": ""
}
@@ -1,121 +0,0 @@
{
" (Page %)": " (Page %)",
" and ": " et ",
"% issues": "% éditions",
"% min": "% min",
"% min read": "% min de lecture",
"0 issues": "0 édition",
"1 issue": "1 édition",
"1 min": "1 min",
"1 min read": "1 min de lecture",
"A collection of 0 issues": "Une collection de 0 édition",
"A collection of 0 posts": "Une collection de 0 publication",
"A collection of 1 issue": "Une collection de 1 édition",
"A collection of 1 post": "Une collection de 1 publication",
"A collection of {numberOfIssues} issues": "Une collection de {numberOfIssues} éditions",
"A collection of {numberOfPosts} posts": "Une collection de {numberOfPosts} publications",
"About": "À propos",
"Access code": "",
"Account": "Mon compte",
"Additional issues will be published soon.": "De nouvelles éditions seront publiées prochainement.",
"All episodes": "Tous les épisodes",
"All episodes →": "Tous les épisodes →",
"Already have an account?": "Vous avez déjà un compte ?",
"Archive": "Archives",
"Browse all issues": "Parcourir toutes les éditions",
"Browse archive": "Parcourir les archives",
"By {authors}": "Par {authors}",
"By {primaryAuthor}": "Par {primaryAuthor}",
"Close": "",
"Close (Esc)": "Fermer (Esc)",
"Comments": "Commentaires",
"Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.": "Ne manquez aucune édition. Inscrivez-vous pour accéder à l'ensemble des éditions réservées aux abonnés.",
"Email sent": "E-mail envoyé",
"Enter": "",
"Enter access code": "",
"Enter your email": "Saisissez votre e-mail",
"Featured": "À la une",
"Go to the front page →": "Aller à la page d'accueil →",
"Latest": "Nouveautés",
"Latest episodes": "Derniers épisodes",
"Latest issue": "Dernière édition",
"Learn more": "En savoir plus",
"Listen on": "Écouter sur",
"Load more issues": "Charger plus d'éditions",
"Login": "Connexion",
"Member discussion:": "Discussion des abonnés :",
"Members": "Abonnés",
"Members only": "Réservé aux abonnés",
"Menu": "Menu",
"More": "Plus",
"More issues": "Plus d'éditions",
"More links": "Plus de liens",
"Newer Posts": "Publications plus récentes",
"Next": "Suivant",
"Next (arrow right)": "Suivant (flèche droite)",
"Next issue": "Édition suivante",
"Next post": "Publication suivante",
"Next →": "Suivant →",
"Older Posts": "Publications plus anciennes",
"Page {page} of {totalPages}": "Page {page} sur {totalPages}",
"Paid": "Payant",
"Paid-members only": "Réservé aux abonnés payants",
"Please enter a valid email address": "",
"Powered by Ghost": "Propulsé par Ghost",
"Powered by {ghostlink}": "Propulsé par {ghostlink}",
"Previous": "Précédent",
"Previous (arrow left)": "Précédent (flèche gauche)",
"Previous issue": "Édition précédente",
"Previous post": "Publication précédente",
"Published by:": "Publié par :",
"RSS": "RSS",
"Read latest issue": "Lire la dernière édition",
"Read more": "Lire la suite",
"Read next": "À lire ensuite",
"Recent posts": "Publications récentes",
"Recommendations": "Recommandations",
"Search posts, tags and authors": "Rechercher parmi les publications, tags et auteurs",
"Search this site": "Rechercher sur ce site",
"See all": "Tout voir",
"Share": "Partager",
"Show more": "Voir plus",
"Sign in": "Se connecter",
"Sign in.": "Se connecter.",
"Sign up now to get access to the library of members-only issues.": "Inscrivez-vous pour accéder à l'ensemble des éditions réservées aux abonnés.",
"Signups from this email domain are currently restricted.": "",
"Site owner login": "",
"Something went wrong, please try again.": "",
"Stay tuned": "Restez à l'écoute",
"Subscribe": "S'abonner",
"Subscribe now": "S'abonner maintenant",
"Subscribe to {sitetitle}": "S'abonner à {sitetitle}",
"Subscription confirmed!": "",
"Thanks! Now check your email to confirm.": "",
"Theme errors": "Erreurs de thème",
"This page is for paying subscribers only": "Cette page est réservée aux abonnés payants",
"This page is for subscribers on the {tiers} only": "Cette page est réservée aux abonnés des formules {tiers}",
"This page is for subscribers only": "Cette page est réservée aux abonnés",
"This post is for paying subscribers only": "Cette publication est réservée aux abonnés payants",
"This post is for subscribers on the {tiers} only": "Cette publication est réservée aux abonnés des formules {tiers}",
"This post is for subscribers only": "Cette publication est réservée aux abonnés",
"Toggle fullscreen": "Basculer en plein écran",
"Toggle menu": "Basculer le menu",
"Too many sign-up attempts, try again later": "",
"Topic": "Sujet",
"Topics": "Sujets",
"Upgrade": "Mise à niveau",
"Upgrade now": "Mettre à niveau maintenant",
"Upgrade to a paid account to get full access.": "Passez à un abonnement payant pour un accès complet.",
"Upgrade your account": "Surclasser votre compte",
"View all →": "Tout voir →",
"View project": "Voir le projet",
"Website": "Site web",
"You might also like...": "Vous aimerez peut-être aussi…",
"Your email address": "",
"Zoom in/out": "Zoom avant/arrière",
"comment": "commentaire",
"comments": "commentaires",
"in {primaryTag}": "dans {primaryTag}",
"jamie@example.com": "jean.martin@exemple.com",
"← Previous": "← Précédent"
}
@@ -1,121 +0,0 @@
{
" (Page %)": "",
" and ": "",
"% issues": "",
"% min": "",
"% min read": "Am léamha: %m",
"0 issues": "",
"1 issue": "",
"1 min": "",
"1 min read": "Am léamha: 1m",
"A collection of 0 issues": "",
"A collection of 0 posts": "",
"A collection of 1 issue": "",
"A collection of 1 post": "",
"A collection of {numberOfIssues} issues": "",
"A collection of {numberOfPosts} posts": "",
"About": "",
"Access code": "",
"Account": "Cuntas",
"Additional issues will be published soon.": "",
"All episodes": "",
"All episodes →": "",
"Already have an account?": "",
"Archive": "",
"Browse all issues": "",
"Browse archive": "",
"By {authors}": "",
"By {primaryAuthor}": "",
"Close": "",
"Close (Esc)": "Dún (Esc)",
"Comments": "",
"Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.": "",
"Email sent": "",
"Enter": "",
"Enter access code": "",
"Enter your email": "",
"Featured": "Aibhsithe",
"Go to the front page →": "",
"Latest": "Is déanaí",
"Latest episodes": "",
"Latest issue": "",
"Learn more": "",
"Listen on": "",
"Load more issues": "",
"Login": "",
"Member discussion:": "",
"Members": "",
"Members only": "",
"Menu": "",
"More": "",
"More issues": "",
"More links": "",
"Newer Posts": "",
"Next": "",
"Next (arrow right)": "Ar aghaidh (arrow right)",
"Next issue": "",
"Next post": "",
"Next →": "",
"Older Posts": "",
"Page {page} of {totalPages}": "",
"Paid": "",
"Paid-members only": "",
"Please enter a valid email address": "",
"Powered by Ghost": "",
"Powered by {ghostlink}": "Le cumhacht {ghostlink}",
"Previous": "",
"Previous (arrow left)": "Ar ais (arrow left)",
"Previous issue": "",
"Previous post": "",
"Published by:": "",
"RSS": "",
"Read latest issue": "",
"Read more": "Tuilleadh léitheoireachta",
"Read next": "",
"Recent posts": "",
"Recommendations": "Moltaí",
"Search posts, tags and authors": "Cuardaigh i bpostálacha, clibeanna agus scríbhneoirí",
"Search this site": "Cuardaigh an suíomh gréasáin seo",
"See all": "Taispeáin iad uile",
"Share": "Comhroinn",
"Show more": "",
"Sign in": "Logáil isteach",
"Sign in.": "",
"Sign up now to get access to the library of members-only issues.": "",
"Signups from this email domain are currently restricted.": "",
"Site owner login": "",
"Something went wrong, please try again.": "",
"Stay tuned": "",
"Subscribe": "Liostáil",
"Subscribe now": "",
"Subscribe to {sitetitle}": "",
"Subscription confirmed!": "",
"Thanks! Now check your email to confirm.": "",
"Theme errors": "",
"This page is for paying subscribers only": "",
"This page is for subscribers on the {tiers} only": "",
"This page is for subscribers only": "",
"This post is for paying subscribers only": "",
"This post is for subscribers on the {tiers} only": "",
"This post is for subscribers only": "",
"Toggle fullscreen": "Scoránaigh an módh lánscáileáin",
"Toggle menu": "",
"Too many sign-up attempts, try again later": "",
"Topic": "",
"Topics": "",
"Upgrade": "Uasghrádaigh",
"Upgrade now": "",
"Upgrade to a paid account to get full access.": "",
"Upgrade your account": "Uasghrádaigh leibhéal do chuntais",
"View all →": "",
"View project": "",
"Website": "",
"You might also like...": "",
"Your email address": "",
"Zoom in/out": "Zúmáil isteach/amach",
"comment": "",
"comments": "",
"in {primaryTag}": "",
"jamie@example.com": "sean.oceallaigh@sampla.com",
"← Previous": ""
}
@@ -1,121 +0,0 @@
{
" (Page %)": "",
" and ": "",
"% issues": "",
"% min": "",
"% min read": "Àm leughaidh: %m",
"0 issues": "",
"1 issue": "",
"1 min": "",
"1 min read": "Àm leughaidh: 1m",
"A collection of 0 issues": "",
"A collection of 0 posts": "",
"A collection of 1 issue": "",
"A collection of 1 post": "",
"A collection of {numberOfIssues} issues": "",
"A collection of {numberOfPosts} posts": "",
"About": "",
"Access code": "",
"Account": "Cunntas",
"Additional issues will be published soon.": "",
"All episodes": "",
"All episodes →": "",
"Already have an account?": "",
"Archive": "",
"Browse all issues": "",
"Browse archive": "",
"By {authors}": "",
"By {primaryAuthor}": "",
"Close": "",
"Close (Esc)": "Dùin (Esc)",
"Comments": "",
"Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.": "",
"Email sent": "",
"Enter": "",
"Enter access code": "",
"Enter your email": "",
"Featured": "Brosnaichte",
"Go to the front page →": "",
"Latest": "As ùire",
"Latest episodes": "",
"Latest issue": "",
"Learn more": "",
"Listen on": "",
"Load more issues": "",
"Login": "",
"Member discussion:": "",
"Members": "",
"Members only": "",
"Menu": "",
"More": "",
"More issues": "",
"More links": "",
"Newer Posts": "",
"Next": "",
"Next (arrow right)": "Air adhart (arrow right)",
"Next issue": "",
"Next post": "",
"Next →": "",
"Older Posts": "",
"Page {page} of {totalPages}": "",
"Paid": "",
"Paid-members only": "",
"Please enter a valid email address": "",
"Powered by Ghost": "",
"Powered by {ghostlink}": "Le cumhachd {ghostlink}",
"Previous": "",
"Previous (arrow left)": "Air ais (arrow left)",
"Previous issue": "",
"Previous post": "",
"Published by:": "",
"RSS": "",
"Read latest issue": "",
"Read more": "Barrachd leughaidh",
"Read next": "",
"Recent posts": "",
"Recommendations": "Molaidhean",
"Search posts, tags and authors": "Lorg anns na postaichean, tagaichean agus sgrìobhadairean",
"Search this site": "Rannsaich an làrach-lìn seo",
"See all": "Seall na h-uile",
"Share": "Co-roinn",
"Show more": "",
"Sign in": "Clàraich a-steach",
"Sign in.": "",
"Sign up now to get access to the library of members-only issues.": "",
"Signups from this email domain are currently restricted.": "",
"Site owner login": "",
"Something went wrong, please try again.": "",
"Stay tuned": "",
"Subscribe": "Fo-sgrìobh",
"Subscribe now": "",
"Subscribe to {sitetitle}": "",
"Subscription confirmed!": "",
"Thanks! Now check your email to confirm.": "",
"Theme errors": "",
"This page is for paying subscribers only": "",
"This page is for subscribers on the {tiers} only": "",
"This page is for subscribers only": "",
"This post is for paying subscribers only": "",
"This post is for subscribers on the {tiers} only": "",
"This post is for subscribers only": "",
"Toggle fullscreen": "Toglaich am modh làn-sgrìn",
"Toggle menu": "",
"Too many sign-up attempts, try again later": "",
"Topic": "",
"Topics": "",
"Upgrade": "Àrdaich",
"Upgrade now": "",
"Upgrade to a paid account to get full access.": "",
"Upgrade your account": "Àrdaich ìre a' chunntais agad",
"View all →": "",
"View project": "",
"Website": "",
"You might also like...": "",
"Your email address": "",
"Zoom in/out": "Leudaich/Lùghdaich",
"comment": "",
"comments": "",
"in {primaryTag}": "",
"jamie@example.com": "alasdair.macdonald@eisimpleir.com",
"← Previous": ""
}
@@ -1,121 +0,0 @@
{
" (Page %)": " (Pagina %)",
" and ": " e ",
"% issues": "% edizioni",
"% min": "% min",
"% min read": "% min di lettura",
"0 issues": "0 edizioni",
"1 issue": "1 edizione",
"1 min": "1 min",
"1 min read": "1 min di lettura",
"A collection of 0 issues": "Una raccolta di 0 edizioni",
"A collection of 0 posts": "Una raccolta di 0 post",
"A collection of 1 issue": "Una raccolta di 1 edizione",
"A collection of 1 post": "Una raccolta di 1 post",
"A collection of {numberOfIssues} issues": "Una raccolta di {numberOfIssues} edizioni",
"A collection of {numberOfPosts} posts": "Una raccolta di {numberOfPosts} post",
"About": "Chi siamo",
"Access code": "Codice di accesso",
"Account": "Account",
"Additional issues will be published soon.": "Ulteriori edizioni saranno pubblicate presto.",
"All episodes": "Tutti gli episodi",
"All episodes →": "Tutti gli episodi →",
"Already have an account?": "Hai già un Account?",
"Archive": "Archivio",
"Browse all issues": "Sfoglia tutte le edizioni",
"Browse archive": "Esplora archivio",
"By {authors}": "Da {authors}",
"By {primaryAuthor}": "Da {primaryAuthor}",
"Close": "Chiudi",
"Close (Esc)": "Chiudi (Esc)",
"Comments": "Commenti",
"Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.": "Non perderti le ultime edizioni. Iscriviti ora per accedere alla libreria delle edizioni riservate ai membri.",
"Email sent": "E-Mail inviata",
"Enter": "Invio",
"Enter access code": "Inserisci codice di accesso",
"Enter your email": "Inserisci la tua mail",
"Featured": "In evidenza",
"Go to the front page →": "Vai alla Homepage →",
"Latest": "Ultimi",
"Latest episodes": "Ultimi episodi",
"Latest issue": "Ultima edizione",
"Learn more": "Scopri di più",
"Listen on": "Ascolta su",
"Load more issues": "Carica altre edizioni",
"Login": "Accedi",
"Member discussion:": "Discussione per i membri:",
"Members": "Membri",
"Members only": "Solo per i membri",
"Menu": "Menu",
"More": "Altro",
"More issues": "Altre edizioni",
"More links": "Altri link",
"Newer Posts": "Post più recenti",
"Next": "Successivo",
"Next (arrow right)": "Successivo (freccia destra)",
"Next issue": "Prossima edizione",
"Next post": "Prossimo post",
"Next →": "Successivo →",
"Older Posts": "Post più vecchi",
"Page {page} of {totalPages}": "Pagina {page} di {totalPages}",
"Paid": "A pagamento",
"Paid-members only": "Solo per membri abbonati",
"Please enter a valid email address": "Inserisci un indirizzo email valido",
"Powered by Ghost": "Realizzato con Ghost",
"Powered by {ghostlink}": "Realizzato con {ghostlink}",
"Previous": "Precedente",
"Previous (arrow left)": "Precedente (freccia sinistra)",
"Previous issue": "Edizione precedente",
"Previous post": "Post precedente",
"Published by:": "Pubblicato da:",
"RSS": "RSS",
"Read latest issue": "Leggi l'ultima edizione",
"Read more": "Leggi di più",
"Read next": "Continua a leggere",
"Recent posts": "Post recenti",
"Recommendations": "Consigliati",
"Search posts, tags and authors": "Cerca post, tag e autori",
"Search this site": "Cerca in questo sito",
"See all": "Vedi tutti",
"Share": "Condividi",
"Show more": "Mostra altro",
"Sign in": "Accedi",
"Sign in.": "Accedi.",
"Sign up now to get access to the library of members-only issues.": "Iscriviti ora per accedere alla libreria delle edizioni riservate ai membri.",
"Signups from this email domain are currently restricted.": "Le iscrizioni da questo dominio email sono attualmente limitate.",
"Site owner login": "Accesso proprietario sito",
"Something went wrong, please try again.": "Qualcosa è andato storto, per favore riprova.",
"Stay tuned": "Resta sintonizzato",
"Subscribe": "Iscriviti",
"Subscribe now": "Iscriviti ora",
"Subscribe to {sitetitle}": "Iscriviti a {sitetitle}",
"Subscription confirmed!": "Iscrizione confermata!",
"Thanks! Now check your email to confirm.": "Grazie! Ora controlla la tua email per confermare.",
"Theme errors": "Errori del tema",
"This page is for paying subscribers only": "Questa pagina è riservata agli abbonati paganti",
"This page is for subscribers on the {tiers} only": "Questa pagina è riservata agli abbonati ai piani {tiers}",
"This page is for subscribers only": "Questa pagina è riservata agli abbonati",
"This post is for paying subscribers only": "Questo post è riservato agli abbonati paganti",
"This post is for subscribers on the {tiers} only": "Questo post è riservato agli abbonati ai piani {tiers}",
"This post is for subscribers only": "Questo post è riservato agli abbonati",
"Toggle fullscreen": "Attiva/disattiva schermo intero",
"Toggle menu": "Attiva/disattiva menu",
"Too many sign-up attempts, try again later": "Troppi tentativi di registrazione, riprova più tardi",
"Topic": "Argomento",
"Topics": "Argomenti",
"Upgrade": "Upgrade",
"Upgrade now": "Esegui l'upgrade ora",
"Upgrade to a paid account to get full access.": "Esegui l'upgrade a un account a pagamento per ottenere l'accesso completo.",
"Upgrade your account": "Esegui l'upgrade del tuo account",
"View all →": "Vedi tutti →",
"View project": "Visualizza progetto",
"Website": "Sito web",
"You might also like...": "Potrebbe interessarti anche...",
"Your email address": "Il tuo indirizzo email",
"Zoom in/out": "Aumenta/riduci zoom",
"comment": "commento",
"comments": "commenti",
"in {primaryTag}": "in {primaryTag}",
"jamie@example.com": "jamie@example.com",
"← Previous": "← Indietro"
}
@@ -1,121 +0,0 @@
{
" (Page %)": "",
" and ": "",
"% issues": "",
"% min": "",
"% min read": "% min leestijd",
"0 issues": "",
"1 issue": "",
"1 min": "",
"1 min read": "1 min leestijd",
"A collection of 0 issues": "",
"A collection of 0 posts": "",
"A collection of 1 issue": "",
"A collection of 1 post": "",
"A collection of {numberOfIssues} issues": "",
"A collection of {numberOfPosts} posts": "",
"About": "",
"Access code": "",
"Account": "Account",
"Additional issues will be published soon.": "",
"All episodes": "",
"All episodes →": "",
"Already have an account?": "",
"Archive": "",
"Browse all issues": "",
"Browse archive": "",
"By {authors}": "",
"By {primaryAuthor}": "",
"Close": "",
"Close (Esc)": "Sluit (Esc)",
"Comments": "",
"Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.": "",
"Email sent": "",
"Enter": "",
"Enter access code": "",
"Enter your email": "",
"Featured": "Uitgelicht",
"Go to the front page →": "",
"Latest": "Nieuw",
"Latest episodes": "",
"Latest issue": "",
"Learn more": "",
"Listen on": "",
"Load more issues": "",
"Login": "",
"Member discussion:": "",
"Members": "",
"Members only": "",
"Menu": "",
"More": "",
"More issues": "",
"More links": "",
"Newer Posts": "",
"Next": "",
"Next (arrow right)": "Volgende (pijl recht)",
"Next issue": "",
"Next post": "",
"Next →": "",
"Older Posts": "",
"Page {page} of {totalPages}": "",
"Paid": "",
"Paid-members only": "",
"Please enter a valid email address": "",
"Powered by Ghost": "",
"Powered by {ghostlink}": "Werkt op {ghostlink}",
"Previous": "",
"Previous (arrow left)": "Vorige (pijl links)",
"Previous issue": "",
"Previous post": "",
"Published by:": "",
"RSS": "",
"Read latest issue": "",
"Read more": "Lees meer",
"Read next": "",
"Recent posts": "",
"Recommendations": "Aanbevelingen",
"Search posts, tags and authors": "Zoek artikelen, tags en auteurs",
"Search this site": "Zoek op de site",
"See all": "Bekijk alles",
"Share": "Deel",
"Show more": "",
"Sign in": "Log in",
"Sign in.": "",
"Sign up now to get access to the library of members-only issues.": "",
"Signups from this email domain are currently restricted.": "",
"Site owner login": "",
"Something went wrong, please try again.": "",
"Stay tuned": "",
"Subscribe": "Aanmelden",
"Subscribe now": "",
"Subscribe to {sitetitle}": "",
"Subscription confirmed!": "",
"Thanks! Now check your email to confirm.": "",
"Theme errors": "",
"This page is for paying subscribers only": "",
"This page is for subscribers on the {tiers} only": "",
"This page is for subscribers only": "",
"This post is for paying subscribers only": "",
"This post is for subscribers on the {tiers} only": "",
"This post is for subscribers only": "",
"Toggle fullscreen": "Volledig scherm",
"Toggle menu": "",
"Too many sign-up attempts, try again later": "",
"Topic": "",
"Topics": "",
"Upgrade": "Upgrade",
"Upgrade now": "",
"Upgrade to a paid account to get full access.": "",
"Upgrade your account": "Account upgraden",
"View all →": "",
"View project": "",
"Website": "",
"You might also like...": "",
"Your email address": "",
"Zoom in/out": "Zoom in/uit",
"comment": "",
"comments": "",
"in {primaryTag}": "",
"jamie@example.com": "jan.jansen@voorbeeld.com",
"← Previous": ""
}
@@ -1,121 +0,0 @@
{
" (Page %)": "",
" and ": "",
"% issues": "",
"% min": "",
"% min read": "% min de leitura",
"0 issues": "",
"1 issue": "",
"1 min": "",
"1 min read": "1 min de leitura",
"A collection of 0 issues": "",
"A collection of 0 posts": "",
"A collection of 1 issue": "",
"A collection of 1 post": "",
"A collection of {numberOfIssues} issues": "",
"A collection of {numberOfPosts} posts": "",
"About": "",
"Access code": "",
"Account": "Conta",
"Additional issues will be published soon.": "",
"All episodes": "",
"All episodes →": "",
"Already have an account?": "",
"Archive": "",
"Browse all issues": "",
"Browse archive": "",
"By {authors}": "",
"By {primaryAuthor}": "",
"Close": "",
"Close (Esc)": "Fechar (Esc)",
"Comments": "",
"Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.": "",
"Email sent": "",
"Enter": "",
"Enter access code": "",
"Enter your email": "",
"Featured": "Em destaque",
"Go to the front page →": "",
"Latest": "Recente",
"Latest episodes": "",
"Latest issue": "",
"Learn more": "",
"Listen on": "",
"Load more issues": "",
"Login": "",
"Member discussion:": "",
"Members": "",
"Members only": "",
"Menu": "",
"More": "",
"More issues": "",
"More links": "",
"Newer Posts": "",
"Next": "",
"Next (arrow right)": "Próximo (botão direito)",
"Next issue": "",
"Next post": "",
"Next →": "",
"Older Posts": "",
"Page {page} of {totalPages}": "",
"Paid": "",
"Paid-members only": "",
"Please enter a valid email address": "",
"Powered by Ghost": "",
"Powered by {ghostlink}": "Alimentado por {ghostlink}",
"Previous": "",
"Previous (arrow left)": "Anterior (botão esquerdo)",
"Previous issue": "",
"Previous post": "",
"Published by:": "",
"RSS": "",
"Read latest issue": "",
"Read more": "Veja mais ",
"Read next": "",
"Recent posts": "",
"Recommendations": "Recomendações",
"Search posts, tags and authors": "Encontrar publicações, tópicos e autores",
"Search this site": "Encontrar neste site",
"See all": "Veja todos",
"Share": "Compartilhar",
"Show more": "",
"Sign in": "Login",
"Sign in.": "",
"Sign up now to get access to the library of members-only issues.": "",
"Signups from this email domain are currently restricted.": "",
"Site owner login": "",
"Something went wrong, please try again.": "",
"Stay tuned": "",
"Subscribe": "Inscreva-se",
"Subscribe now": "",
"Subscribe to {sitetitle}": "",
"Subscription confirmed!": "",
"Thanks! Now check your email to confirm.": "",
"Theme errors": "",
"This page is for paying subscribers only": "",
"This page is for subscribers on the {tiers} only": "",
"This page is for subscribers only": "",
"This post is for paying subscribers only": "",
"This post is for subscribers on the {tiers} only": "",
"This post is for subscribers only": "",
"Toggle fullscreen": "Alternar para tela cheia",
"Toggle menu": "",
"Too many sign-up attempts, try again later": "",
"Topic": "",
"Topics": "",
"Upgrade": "Atualizar",
"Upgrade now": "",
"Upgrade to a paid account to get full access.": "",
"Upgrade your account": "Atualize sua conta",
"View all →": "",
"View project": "",
"Website": "",
"You might also like...": "",
"Your email address": "",
"Zoom in/out": "Aumentar/Diminuir zoom",
"comment": "",
"comments": "",
"in {primaryTag}": "",
"jamie@example.com": "joao.silva@exemplo.com",
"← Previous": ""
}
@@ -1,121 +0,0 @@
{
" (Page %)": "",
" and ": "",
"% issues": "",
"% min": "",
"% min read": "% min läsning",
"0 issues": "",
"1 issue": "",
"1 min": "",
"1 min read": "1 min läsning",
"A collection of 0 issues": "",
"A collection of 0 posts": "",
"A collection of 1 issue": "",
"A collection of 1 post": "",
"A collection of {numberOfIssues} issues": "",
"A collection of {numberOfPosts} posts": "",
"About": "",
"Access code": "",
"Account": "Konto",
"Additional issues will be published soon.": "",
"All episodes": "",
"All episodes →": "",
"Already have an account?": "",
"Archive": "",
"Browse all issues": "",
"Browse archive": "",
"By {authors}": "",
"By {primaryAuthor}": "",
"Close": "",
"Close (Esc)": "Stäng (Esc)",
"Comments": "",
"Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.": "",
"Email sent": "",
"Enter": "",
"Enter access code": "",
"Enter your email": "",
"Featured": "Utvalt",
"Go to the front page →": "",
"Latest": "Senaste",
"Latest episodes": "",
"Latest issue": "",
"Learn more": "",
"Listen on": "",
"Load more issues": "",
"Login": "",
"Member discussion:": "",
"Members": "",
"Members only": "",
"Menu": "",
"More": "",
"More issues": "",
"More links": "",
"Newer Posts": "",
"Next": "",
"Next (arrow right)": "Nästa (arrow right)",
"Next issue": "",
"Next post": "",
"Next →": "",
"Older Posts": "",
"Page {page} of {totalPages}": "",
"Paid": "",
"Paid-members only": "",
"Please enter a valid email address": "",
"Powered by Ghost": "",
"Powered by {ghostlink}": "Drivs av {ghostlink}",
"Previous": "",
"Previous (arrow left)": "Föregående (arrow left)",
"Previous issue": "",
"Previous post": "",
"Published by:": "",
"RSS": "",
"Read latest issue": "",
"Read more": "Läs mer",
"Read next": "",
"Recent posts": "",
"Recommendations": "Rekommendationer",
"Search posts, tags and authors": "Sök inlägg, taggar och författare",
"Search this site": "Sök på webbplatsen",
"See all": "Visa alla",
"Share": "Dela",
"Show more": "",
"Sign in": "Logga in",
"Sign in.": "",
"Sign up now to get access to the library of members-only issues.": "",
"Signups from this email domain are currently restricted.": "",
"Site owner login": "",
"Something went wrong, please try again.": "",
"Stay tuned": "",
"Subscribe": "Prenumerera",
"Subscribe now": "",
"Subscribe to {sitetitle}": "",
"Subscription confirmed!": "",
"Thanks! Now check your email to confirm.": "",
"Theme errors": "",
"This page is for paying subscribers only": "",
"This page is for subscribers on the {tiers} only": "",
"This page is for subscribers only": "",
"This post is for paying subscribers only": "",
"This post is for subscribers on the {tiers} only": "",
"This post is for subscribers only": "",
"Toggle fullscreen": "Växla fullskärm",
"Toggle menu": "",
"Too many sign-up attempts, try again later": "",
"Topic": "",
"Topics": "",
"Upgrade": "Uppgradera",
"Upgrade now": "",
"Upgrade to a paid account to get full access.": "",
"Upgrade your account": "Uppgradera ditt konto",
"View all →": "",
"View project": "",
"Website": "",
"You might also like...": "",
"Your email address": "",
"Zoom in/out": "Zooma in/ut",
"comment": "",
"comments": "",
"in {primaryTag}": "",
"jamie@example.com": "karl.johansson@exempel.com",
"← Previous": ""
}
@@ -1,121 +0,0 @@
{
" (Page %)": " (Sayfa %)",
" and ": " ve ",
"% issues": "% sayı",
"% min": "% dk",
"% min read": "% dk okuma",
"0 issues": "0 sayı",
"1 issue": "1 sayı",
"1 min": "1 dk",
"1 min read": "1 dk okuma",
"A collection of 0 issues": "0 sayılık bir koleksiyon",
"A collection of 0 posts": "0 yazılık bir koleksiyon",
"A collection of 1 issue": "1 sayılık bir koleksiyon",
"A collection of 1 post": "1 yazılık bir koleksiyon",
"A collection of {numberOfIssues} issues": "{numberOfIssues} sayılık bir koleksiyon",
"A collection of {numberOfPosts} posts": "{numberOfPosts} yazılık bir koleksiyon",
"About": "Hakkında",
"Access code": "Erişim kodu",
"Account": "Hesap",
"Additional issues will be published soon.": "Ek sayılar yakında yayınlanacak.",
"All episodes": "Tüm bölümler",
"All episodes →": "Tüm bölümler →",
"Already have an account?": "Zaten bir hesabınız var mı?",
"Archive": "Arşiv",
"Browse all issues": "Tüm sayılara göz at",
"Browse archive": "Arşive göz at",
"By {authors}": "{authors} tarafından",
"By {primaryAuthor}": "{primaryAuthor} tarafından",
"Close": "Kapat",
"Close (Esc)": "Kapat (Esc)",
"Comments": "Yorumlar",
"Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.": "En son sayıları kaçırmayın. Sadece üyelere özel sayıların kütüphanesine erişim sağlamak için şimdi kaydolun.",
"Email sent": "E-posta gönderildi",
"Enter": "Gir",
"Enter access code": "Erişim kodunu girin",
"Enter your email": "E-postanızı girin",
"Featured": "Öne çıkanlar",
"Go to the front page →": "Ana sayfaya git →",
"Latest": "En son",
"Latest episodes": "Son bölümler",
"Latest issue": "Son sayı",
"Learn more": "Daha fazla bilgi",
"Listen on": "Şurada dinle:",
"Load more issues": "Daha fazla sayı yükle",
"Login": "Giriş yap",
"Member discussion:": "Üye tartışması:",
"Members": "Üyeler",
"Members only": "Üyelere özel",
"Menu": "Menü",
"More": "Daha fazla",
"More issues": "Daha fazla sayı",
"More links": "Daha fazla bağlantı",
"Newer Posts": "Daha Yeni Yazılar",
"Next": "Sonraki",
"Next (arrow right)": "Sonraki (sağ ok)",
"Next issue": "Sonraki sayı",
"Next post": "Sonraki yazı",
"Next →": "Sonraki →",
"Older Posts": "Daha Eski Yazılar",
"Page {page} of {totalPages}": "Sayfa {page} / {totalPages}",
"Paid": "Ücretli",
"Paid-members only": "Ücretli üyelere özel",
"Please enter a valid email address": "Lütfen geçerli bir e-posta adresi girin",
"Powered by Ghost": "Ghost ile yapıldı",
"Powered by {ghostlink}": "{ghostlink} ile yapıldı",
"Previous": "Önceki",
"Previous (arrow left)": "Önceki (sol ok)",
"Previous issue": "Önceki sayı",
"Previous post": "Önceki yazı",
"Published by:": "Yayınlayan:",
"RSS": "RSS",
"Read latest issue": "Son sayıyı oku",
"Read more": "Devamını oku",
"Read next": "Sıradakini oku",
"Recent posts": "Son yazılar",
"Recommendations": "Öneriler",
"Search posts, tags and authors": "Yazı, etiket ve yazar ara",
"Search this site": "Bu sitede ara",
"See all": "Tümünü gör",
"Share": "Paylaş",
"Show more": "Daha fazla göster",
"Sign in": "Giriş yap",
"Sign in.": "Giriş yap.",
"Sign up now to get access to the library of members-only issues.": "Sadece üyelere özel sayıların kütüphanesine erişim sağlamak için şimdi kaydolun.",
"Signups from this email domain are currently restricted.": "Bu e-posta alan adıyla abonelikler şu an için sınırlandırıldı.",
"Site owner login": "Site sahibi girişi",
"Something went wrong, please try again.": "Bir sorun oluştu, lütfen tekrar deneyin.",
"Stay tuned": "Takipte kalın",
"Subscribe": "Abone ol",
"Subscribe now": "Şimdi abone ol",
"Subscribe to {sitetitle}": "{sitetitle} abonesi ol",
"Subscription confirmed!": "Abonelik onaylandı!",
"Thanks! Now check your email to confirm.": "Teşekkürler! Şimdi onay için e-postanızı kontrol edin.",
"Theme errors": "Tema hataları",
"This page is for paying subscribers only": "Bu sayfa sadece ücretli aboneler içindir",
"This page is for subscribers on the {tiers} only": "Bu sayfa sadece {tiers} aboneleri içindir",
"This page is for subscribers only": "Bu sayfa sadece aboneler içindir",
"This post is for paying subscribers only": "Bu yazı sadece ücretli aboneler içindir",
"This post is for subscribers on the {tiers} only": "Bu yazı sadece {tiers} aboneleri içindir",
"This post is for subscribers only": "Bu yazı sadece aboneler içindir",
"Toggle fullscreen": "Tam ekran aç/kapat",
"Toggle menu": "Menüyü aç/kapat",
"Too many sign-up attempts, try again later": "Çok fazla abonelik girişimi, bir süre sonra tekrar deneyin",
"Topic": "Konu",
"Topics": "Konular",
"Upgrade": "Yükselt",
"Upgrade now": "Şimdi yükselt",
"Upgrade to a paid account to get full access.": "Tam erişim elde etmek için ücretli bir hesaba yükseltin.",
"Upgrade your account": "Hesabınızı yükseltin",
"View all →": "Tümünü gör →",
"View project": "Projeyi görüntüle",
"Website": "Web sitesi",
"You might also like...": "Bunlar da hoşunuza gidebilir...",
"Your email address": "E-posta adresiniz",
"Zoom in/out": "Büyüt/küçült",
"comment": "yorum",
"comments": "yorumlar",
"in {primaryTag}": "{primaryTag} içinde",
"jamie@example.com": "bilge@ornek.com",
"← Previous": "← Önceki"
}
@@ -1,121 +0,0 @@
{
" (Page %)": " (Сторінка %)",
" and ": " та ",
"% issues": "% випусків",
"% min": "% хв",
"% min read": "Читати % хв",
"0 issues": "0 випусків",
"1 issue": "1 випуск",
"1 min": "1 хв",
"1 min read": "Читати 1 хв",
"A collection of 0 issues": "Добірка з 0 випусків",
"A collection of 0 posts": "Добірка з 0 дописів",
"A collection of 1 issue": "Добірка з 1 випуску",
"A collection of 1 post": "Добірка з 1 допису",
"A collection of {numberOfIssues} issues": "Добірка з {numberOfIssues} випусків",
"A collection of {numberOfPosts} posts": "Добірка з {numberOfPosts} дописів",
"About": "Про нас",
"Access code": "",
"Account": "Обліковий запис",
"Additional issues will be published soon.": "Готуємо для вас нові випуски.",
"All episodes": "Всі епізоди",
"All episodes →": "Всі епізоди →",
"Already have an account?": "Вже є обліковий запис?",
"Archive": "Архів",
"Browse all issues": "Переглянути всі випуски",
"Browse archive": "Переглянути архів",
"By {authors}": "Від {authors}",
"By {primaryAuthor}": "Від {primaryAuthor}",
"Close": "",
"Close (Esc)": "Закрити (Esc)",
"Comments": "Коментарі",
"Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.": "Хочете читати першими? Приєднуйтеся до нашої спільноти підписників і отримайте ексклюзивний доступ до випусків.",
"Email sent": "Лист надіслано",
"Enter": "",
"Enter access code": "",
"Enter your email": "Введіть електронну адресу",
"Featured": "Обрані дописи",
"Go to the front page →": "Перейти на головну сторінку →",
"Latest": "Останні",
"Latest episodes": "Останні епізоди",
"Latest issue": "Останній випуск",
"Learn more": "Дізнатися більше",
"Listen on": "Слухати на",
"Load more issues": "Завантажити більше випусків",
"Login": "Вхід",
"Member discussion:": "Обговорення серед спільноти:",
"Members": "Підписники",
"Members only": "Тільки для підписників",
"Menu": "Меню",
"More": "Більше",
"More issues": "Більше випусків",
"More links": "Більше посилань",
"Newer Posts": "Новіші дописи",
"Next": "Далі",
"Next (arrow right)": "Далі (стрілка вправо)",
"Next issue": "Наступний випуск",
"Next post": "Наступний допис",
"Next →": "Далі →",
"Older Posts": "Старіші дописи",
"Page {page} of {totalPages}": "Сторінка {page} з {totalPages}",
"Paid": "Платний",
"Paid-members only": "Тільки за платною підпискою",
"Please enter a valid email address": "",
"Powered by Ghost": "Створено на платформі Ghost",
"Powered by {ghostlink}": "За підтримки {ghostlink}",
"Previous": "Назад",
"Previous (arrow left)": "Назад (стрілка вліво)",
"Previous issue": "Попередній випуск",
"Previous post": "Попередній допис",
"Published by:": "Опубліковано автором:",
"RSS": "RSS",
"Read latest issue": "Читати останній випуск",
"Read more": "Читати далі",
"Read next": "Читати наступне",
"Recent posts": "Нещодавні дописи",
"Recommendations": "Рекомендації",
"Search posts, tags and authors": "Пошук дописів, теґів та авторів",
"Search this site": "Пошук на сайті",
"See all": "Переглянути всі",
"Share": "Поділитися",
"Show more": "Показати більше",
"Sign in": "Увійти",
"Sign in.": "Увійти.",
"Sign up now to get access to the library of members-only issues.": "Приєднуйтеся до нашої спільноти підписників і отримайте ексклюзивний доступ до випусків.",
"Signups from this email domain are currently restricted.": "",
"Site owner login": "",
"Something went wrong, please try again.": "",
"Stay tuned": "Слідкувати за оновленнями",
"Subscribe": "Підписатися",
"Subscribe now": "Підписатися зараз",
"Subscribe to {sitetitle}": "Підписатися на {sitetitle}",
"Subscription confirmed!": "",
"Thanks! Now check your email to confirm.": "",
"Theme errors": "Помилки теми",
"This page is for paying subscribers only": "Ця сторінка доступна лише для платних підписників",
"This page is for subscribers on the {tiers} only": "Ця сторінка доступна лише для підписників рівня {tiers}",
"This page is for subscribers only": "Ця сторінка доступна лише для підписників",
"This post is for paying subscribers only": "Цей допис доступний лише для платних підписників",
"This post is for subscribers on the {tiers} only": "Цей допис доступний лише для підписників рівня {tiers}",
"This post is for subscribers only": "Цей допис доступний лише для підписників",
"Toggle fullscreen": "Повноекранний режим",
"Toggle menu": "Перемкнути меню",
"Too many sign-up attempts, try again later": "",
"Topic": "Тема",
"Topics": "Теми",
"Upgrade": "Покращити підписку",
"Upgrade now": "Покращити підписку зараз",
"Upgrade to a paid account to get full access.": "Оформіть платну підписку, щоб відкрити повний доступ.",
"Upgrade your account": "Підвищити рівень своєї підписки",
"View all →": "Переглянути всі →",
"View project": "Переглянути проєкт",
"Website": "Вебсайт",
"You might also like...": "Вам також може сподобатися...",
"Your email address": "",
"Zoom in/out": "Збільшити/зменшити",
"comment": "коментар",
"comments": "коментарі",
"in {primaryTag}": "в {primaryTag}",
"jamie@example.com": "ivan.koval@pryklad.com",
"← Previous": "← Назад"
}
@@ -1,121 +0,0 @@
{
" (Page %)": " (Trang %)",
" and ": " và ",
"% issues": "% ấn bản",
"% min": "% phút",
"% min read": "% phút đọc",
"0 issues": "0 ấn bản",
"1 issue": "1 ấn bản",
"1 min": "1 phút",
"1 min read": "1 phút đọc",
"A collection of 0 issues": "Tuyển tập có 0 ấn bản",
"A collection of 0 posts": "Tuyển tập có 0 bài viết",
"A collection of 1 issue": "Tuyển tập có 1 ấn bản",
"A collection of 1 post": "Tuyển tập có 1 bài viết",
"A collection of {numberOfIssues} issues": "Tuyển tập gồm {numberOfIssues} ấn bản",
"A collection of {numberOfPosts} posts": "Tuyển tập gồm {numberOfPosts} bài viết",
"About": "Giới thiệu",
"Access code": "Mã truy cập",
"Account": "Tài khoản",
"Additional issues will be published soon.": "Các ấn bản bổ sung sẽ sớm được xuất bản.",
"All episodes": "Tất cả tập",
"All episodes →": "Tất cả tập →",
"Already have an account?": "Đã có tài khoản?",
"Archive": "Kho lưu trữ",
"Browse all issues": "Xem tất cả ấn bản",
"Browse archive": "Duyệt kho lưu trữ",
"By {authors}": "Bởi {authors}",
"By {primaryAuthor}": "Bởi {primaryAuthor}",
"Close": "Đóng",
"Close (Esc)": "Đóng (Esc)",
"Comments": "Bình luận",
"Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.": "Đừng bỏ lỡ các ấn bản mới nhất. Đăng ký ngay để truy cập thư viện dành riêng cho thành viên.",
"Email sent": "Email đã được gửi",
"Enter": "Nhập",
"Enter access code": "Nhập mã truy cập",
"Enter your email": "Nhập email của bạn",
"Featured": "Nổi bật",
"Go to the front page →": "Đi đến trang chủ →",
"Latest": "Mới nhất",
"Latest episodes": "Tập mới nhất",
"Latest issue": "Ấn bản mới nhất",
"Learn more": "Tìm hiểu thêm",
"Listen on": "Nghe trên",
"Load more issues": "Tải thêm ấn bản",
"Login": "Đăng nhập",
"Member discussion:": "Thảo luận của thành viên:",
"Members": "Thành viên",
"Members only": "Chỉ dành cho thành viên",
"Menu": "Menu",
"More": "Thêm",
"More issues": "Thêm ấn bản",
"More links": "Liên kết khác",
"Newer Posts": "Bài viết mới hơn",
"Next": "Tiếp theo",
"Next (arrow right)": "Tiếp theo (mũi tên phải)",
"Next issue": "Ấn bản kế tiếp",
"Next post": "Bài viết kế tiếp",
"Next →": "Tiếp theo →",
"Older Posts": "Bài viết cũ hơn",
"Page {page} of {totalPages}": "Trang {page} trong {totalPages}",
"Paid": "Trả phí",
"Paid-members only": "Chỉ dành cho thành viên trả phí",
"Please enter a valid email address": "Vui lòng nhập địa chỉ email hợp lệ",
"Powered by Ghost": "Cung cấp bởi Ghost",
"Powered by {ghostlink}": "Cung cấp bởi {ghostlink}",
"Previous": "Trước đó",
"Previous (arrow left)": "Trước đó (mũi tên trái)",
"Previous issue": "Ấn bản trước",
"Previous post": "Bài viết trước",
"Published by:": "Xuất bản bởi:",
"RSS": "RSS",
"Read latest issue": "Đọc ấn bản mới nhất",
"Read more": "Đọc thêm",
"Read next": "Đọc tiếp",
"Recent posts": "Bài viết gần đây",
"Recommendations": "Đề xuất",
"Search posts, tags and authors": "Tìm kiếm bài viết, thẻ và tác giả",
"Search this site": "Tìm kiếm trên trang này",
"See all": "Xem tất cả",
"Share": "Chia sẻ",
"Show more": "Xem thêm",
"Sign in": "Đăng nhập",
"Sign in.": "Đăng nhập.",
"Sign up now to get access to the library of members-only issues.": "Đăng ký ngay để truy cập thư viện ấn bản dành riêng cho thành viên.",
"Signups from this email domain are currently restricted.": "Việc đăng ký từ tên miền email này hiện đang bị hạn chế.",
"Site owner login": "Đăng nhập cho chủ sở hữu trang web",
"Something went wrong, please try again.": "Đã có lỗi xảy ra, vui lòng thử lại.",
"Stay tuned": "Hãy chú ý theo dõi",
"Subscribe": "Đăng ký đọc",
"Subscribe now": "Đăng ký đọc ngay",
"Subscribe to {sitetitle}": "Đăng ký nhận bài viết mới từ {sitetitle}",
"Subscription confirmed!": "Đã xác nhận đăng ký!",
"Thanks! Now check your email to confirm.": "Cảm ơn! Bây giờ hãy kiểm tra email của bạn để xác nhận.",
"Theme errors": "Lỗi giao diện",
"This page is for paying subscribers only": "Trang này chỉ dành cho thành viên trả phí",
"This page is for subscribers on the {tiers} only": "Trang này chỉ dành cho thành viên ở gói {tiers}",
"This page is for subscribers only": "Trang này chỉ dành cho thành viên",
"This post is for paying subscribers only": "Bài viết này chỉ dành cho thành viên trả phí",
"This post is for subscribers on the {tiers} only": "Bài viết này chỉ dành cho thành viên ở gói {tiers}",
"This post is for subscribers only": "Bài viết này chỉ dành cho thành viên",
"Toggle fullscreen": "Bật/tắt toàn màn hình",
"Toggle menu": "Bật/tắt menu",
"Too many sign-up attempts, try again later": "Quá nhiều lần thử đăng ký, vui lòng thử lại sau",
"Topic": "Chủ đề",
"Topics": "Chủ đề",
"Upgrade": "Nâng cấp",
"Upgrade now": "Nâng cấp ngay",
"Upgrade to a paid account to get full access.": "Nâng cấp lên tài khoản trả phí để có quyền truy cập đầy đủ.",
"Upgrade your account": "Nâng cấp tài khoản của bạn",
"View all →": "Xem tất cả →",
"View project": "Xem dự án",
"Website": "Website",
"You might also like...": "Có thể bạn cũng thích...",
"Your email address": "Địa chỉ email của bạn",
"Zoom in/out": "Phóng to/thu nhỏ",
"comment": "bình luận",
"comments": "bình luận",
"in {primaryTag}": "trong {primaryTag}",
"jamie@example.com": "jamie@example.com",
"← Previous": "← Trước"
}
@@ -1,121 +0,0 @@
{
" (Page %)": "",
" and ": "",
"% issues": "",
"% min": "",
"% min read": "閱讀時間 % 分鐘",
"0 issues": "",
"1 issue": "",
"1 min": "",
"1 min read": "閱讀時間 1 分鐘",
"A collection of 0 issues": "",
"A collection of 0 posts": "",
"A collection of 1 issue": "",
"A collection of 1 post": "",
"A collection of {numberOfIssues} issues": "",
"A collection of {numberOfPosts} posts": "",
"About": "",
"Access code": "",
"Account": "我的帳號",
"Additional issues will be published soon.": "",
"All episodes": "",
"All episodes →": "",
"Already have an account?": "已經有帳號?",
"Archive": "",
"Browse all issues": "",
"Browse archive": "",
"By {authors}": "",
"By {primaryAuthor}": "",
"Close": "",
"Close (Esc)": "關閉(Esc)",
"Comments": "",
"Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.": "",
"Email sent": "",
"Enter": "",
"Enter access code": "",
"Enter your email": "",
"Featured": "精選",
"Go to the front page →": "",
"Latest": "最新",
"Latest episodes": "",
"Latest issue": "",
"Learn more": "",
"Listen on": "",
"Load more issues": "",
"Login": "登入",
"Member discussion:": "讀者交流:",
"Members": "",
"Members only": "",
"Menu": "",
"More": "",
"More issues": "",
"More links": "",
"Newer Posts": "",
"Next": "",
"Next (arrow right)": "",
"Next issue": "",
"Next post": "",
"Next →": "",
"Older Posts": "",
"Page {page} of {totalPages}": "",
"Paid": "",
"Paid-members only": "付費會員限定",
"Please enter a valid email address": "",
"Powered by Ghost": "",
"Powered by {ghostlink}": "",
"Previous": "",
"Previous (arrow left)": "",
"Previous issue": "",
"Previous post": "",
"Published by:": "作者:",
"RSS": "",
"Read latest issue": "",
"Read more": "",
"Read next": "",
"Recent posts": "近期文章",
"Recommendations": "推薦",
"Search posts, tags and authors": "搜尋文章、標籤和作者",
"Search this site": "搜尋本站內容",
"See all": "查看全部",
"Share": "分享",
"Show more": "顯示更多",
"Sign in": "登入",
"Sign in.": "登入。",
"Sign up now to get access to the library of members-only issues.": "",
"Signups from this email domain are currently restricted.": "",
"Site owner login": "",
"Something went wrong, please try again.": "",
"Stay tuned": "",
"Subscribe": "訂閱",
"Subscribe now": "現在訂閱",
"Subscribe to {sitetitle}": "",
"Subscription confirmed!": "",
"Thanks! Now check your email to confirm.": "",
"Theme errors": "",
"This page is for paying subscribers only": "",
"This page is for subscribers on the {tiers} only": "",
"This page is for subscribers only": "",
"This post is for paying subscribers only": "本篇為付費會員專屬內容",
"This post is for subscribers on the {tiers} only": "本篇為層級 {tiers} 訂閱者專屬內容",
"This post is for subscribers only": "本篇為訂閱者專屬內容",
"Toggle fullscreen": "切換全螢幕",
"Toggle menu": "",
"Too many sign-up attempts, try again later": "",
"Topic": "",
"Topics": "",
"Upgrade": "",
"Upgrade now": "",
"Upgrade to a paid account to get full access.": "升級付費帳號,解鎖完整內容。",
"Upgrade your account": "",
"View all →": "",
"View project": "",
"Website": "網站",
"You might also like...": "您可能也會感興趣...",
"Your email address": "",
"Zoom in/out": "",
"comment": "",
"comments": "",
"in {primaryTag}": "",
"jamie@example.com": "",
"← Previous": ""
}
@@ -1,121 +0,0 @@
{
" (Page %)": "",
" and ": "",
"% issues": "",
"% min": "",
"% min read": "阅读时间 % 分钟",
"0 issues": "",
"1 issue": "",
"1 min": "",
"1 min read": "阅读时间 1 分钟",
"A collection of 0 issues": "",
"A collection of 0 posts": "",
"A collection of 1 issue": "",
"A collection of 1 post": "",
"A collection of {numberOfIssues} issues": "",
"A collection of {numberOfPosts} posts": "",
"About": "",
"Access code": "",
"Account": "账户",
"Additional issues will be published soon.": "",
"All episodes": "",
"All episodes →": "",
"Already have an account?": "",
"Archive": "",
"Browse all issues": "",
"Browse archive": "",
"By {authors}": "",
"By {primaryAuthor}": "",
"Close": "",
"Close (Esc)": "关闭 (Esc)",
"Comments": "",
"Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.": "",
"Email sent": "",
"Enter": "",
"Enter access code": "",
"Enter your email": "",
"Featured": "精选",
"Go to the front page →": "",
"Latest": "最新",
"Latest episodes": "",
"Latest issue": "",
"Learn more": "",
"Listen on": "",
"Load more issues": "",
"Login": "",
"Member discussion:": "",
"Members": "",
"Members only": "",
"Menu": "",
"More": "",
"More issues": "",
"More links": "",
"Newer Posts": "",
"Next": "",
"Next (arrow right)": "下一个 (右箭头)",
"Next issue": "",
"Next post": "",
"Next →": "",
"Older Posts": "",
"Page {page} of {totalPages}": "",
"Paid": "",
"Paid-members only": "",
"Please enter a valid email address": "",
"Powered by Ghost": "",
"Powered by {ghostlink}": "由 {ghostlink} 强力驱动",
"Previous": "",
"Previous (arrow left)": "上一个 (左箭头)",
"Previous issue": "",
"Previous post": "",
"Published by:": "",
"RSS": "",
"Read latest issue": "",
"Read more": "阅读更多",
"Read next": "",
"Recent posts": "",
"Recommendations": "推荐",
"Search posts, tags and authors": "搜索文章、标签和作者",
"Search this site": "搜索本站内容",
"See all": "查看全部",
"Share": "分享",
"Show more": "",
"Sign in": "登录",
"Sign in.": "",
"Sign up now to get access to the library of members-only issues.": "",
"Signups from this email domain are currently restricted.": "",
"Site owner login": "",
"Something went wrong, please try again.": "",
"Stay tuned": "",
"Subscribe": "订阅",
"Subscribe now": "",
"Subscribe to {sitetitle}": "",
"Subscription confirmed!": "",
"Thanks! Now check your email to confirm.": "",
"Theme errors": "",
"This page is for paying subscribers only": "",
"This page is for subscribers on the {tiers} only": "",
"This page is for subscribers only": "",
"This post is for paying subscribers only": "",
"This post is for subscribers on the {tiers} only": "",
"This post is for subscribers only": "",
"Toggle fullscreen": "切换全屏",
"Toggle menu": "",
"Too many sign-up attempts, try again later": "",
"Topic": "",
"Topics": "",
"Upgrade": "升级",
"Upgrade now": "",
"Upgrade to a paid account to get full access.": "",
"Upgrade your account": "升级你的账户",
"View all →": "",
"View project": "",
"Website": "",
"You might also like...": "",
"Your email address": "",
"Zoom in/out": "放大/缩小",
"comment": "",
"comments": "",
"in {primaryTag}": "",
"jamie@example.com": "张伟@示例.com",
"← Previous": ""
}
-214
View File
@@ -1,214 +0,0 @@
{
"name": "source-personal",
"description": "Source theme adapted for a personal portfolio without public subscription UI",
"demo": "https://source.ghost.io",
"version": "1.7.1",
"engines": {
"ghost": ">=5.0.0",
"node": ">=22.12.0"
},
"license": "MIT",
"scripts": {
"dev": "gulp",
"zip": "gulp zip",
"test": "gscan .",
"test:ci": "gscan --fatal --verbose .",
"pretest": "gulp build",
"preship": "yarn test",
"ship": "STATUS=$(git status --porcelain); echo $STATUS; if [ -z \"$STATUS\" ]; then yarn version && git push --follow-tags; else echo \"Uncomitted changes found.\" && exit 1; fi",
"postship": "git fetch && gulp release"
},
"author": {
"name": "Ghost Foundation",
"email": "hello@ghost.org",
"url": "https://ghost.org/"
},
"gpm": {
"type": "theme",
"categories": [
"Minimal",
"Magazine"
]
},
"keywords": [
"ghost",
"theme",
"ghost-theme"
],
"repository": {
"type": "git",
"url": "https://github.com/TryGhost/Source.git"
},
"bugs": "https://github.com/TryGhost/Source/issues",
"contributors": "https://github.com/TryGhost/Source/graphs/contributors",
"devDependencies": {
"@tryghost/release-utils": "0.8.1",
"@tryghost/theme-translations": "0.0.21",
"autoprefixer": "10.4.24",
"beeper": "2.1.0",
"cssnano": "7.1.2",
"gscan": "6.2.1",
"gulp": "5.0.1",
"gulp-concat": "2.6.1",
"gulp-livereload": "4.0.2",
"gulp-postcss": "10.0.0",
"gulp-uglify": "3.0.2",
"gulp-zip": "6.1.0",
"inquirer": "14.0.2",
"postcss": "8.5.10",
"postcss-easy-import": "4.0.0",
"pump": "3.0.4"
},
"browserslist": [
"defaults"
],
"config": {
"posts_per_page": 12,
"image_sizes": {
"xs": {
"width": 160
},
"s": {
"width": 320
},
"m": {
"width": 600
},
"l": {
"width": 960
},
"xl": {
"width": 1200
},
"xxl": {
"width": 2000
}
},
"card_assets": true,
"custom": {
"navigation_layout": {
"type": "select",
"options": [
"Logo in the middle",
"Logo on the left",
"Stacked"
],
"default": "Logo in the middle"
},
"site_background_color": {
"type": "color",
"default": "#ffffff"
},
"header_and_footer_color": {
"type": "select",
"options": [
"Background color",
"Accent color"
],
"default": "Background color"
},
"title_font": {
"type": "select",
"options": [
"Modern sans-serif",
"Elegant serif",
"Consistent mono"
],
"default": "Modern sans-serif"
},
"body_font": {
"type": "select",
"options": [
"Modern sans-serif",
"Elegant serif"
],
"default": "Modern sans-serif"
},
"signup_heading": {
"type": "text",
"description": "Used in your footer across your theme, defaults to site title when empty"
},
"signup_subheading": {
"type": "text",
"description": "Defaults to site description when empty"
},
"header_style": {
"type": "select",
"options": [
"Landing",
"Highlight",
"Magazine",
"Search",
"Off"
],
"description": "Landing is recommended for all sites, Highlight & Magazine for those with more content",
"default": "Landing",
"group": "homepage"
},
"header_text": {
"type": "text",
"group": "homepage",
"description": "Defaults to site description when empty",
"visibility": "header_style:[Landing, Search]"
},
"background_image": {
"type": "boolean",
"default": true,
"description": "Use the publication cover set on the Brand tab as your background",
"group": "homepage",
"visibility": "header_style:[Landing, Search]"
},
"show_featured_posts": {
"type": "boolean",
"default": false,
"group": "homepage",
"visibility": "header_style:[Highlight, Magazine]"
},
"post_feed_style": {
"type": "select",
"options": [
"List",
"Grid"
],
"default": "List",
"group": "homepage"
},
"show_images_in_feed": {
"type": "boolean",
"default": true,
"group": "homepage",
"visibility": "post_feed_style:List"
},
"show_author": {
"type": "boolean",
"default": true,
"group": "homepage"
},
"show_publish_date": {
"type": "boolean",
"default": true,
"group": "homepage"
},
"show_publication_info_sidebar": {
"type": "boolean",
"default": false,
"group": "homepage"
},
"show_post_metadata": {
"type": "boolean",
"default": true,
"group": "post"
},
"enable_drop_caps_on_posts": {
"type": "boolean",
"default": false,
"group": "post"
},
"show_related_articles": {
"type": "boolean",
"default": true,
"group": "post"
}
}
}
}
@@ -1,60 +0,0 @@
{{!< default}}
{{!-- Dedicated homepage template: hero + latest posts, kept separate from the generic page.hbs used by other static pages --}}
{{#post}}
<main class="gh-main ths-home">
<section class="ths-hero">
<div class="ths-hero-inner gh-canvas">
<h1 class="ths-hero-title is-title">{{title}}</h1>
{{#if custom_excerpt}}
<p class="ths-hero-subtitle">{{custom_excerpt}}</p>
{{/if}}
<p class="ths-hero-kicker">Amante de las ciencias, tecnologias, y la filosofia</p>
<div class="ths-hero-actions">
<a class="ths-btn ths-btn-primary" href="/proyectos/">Ver proyectos</a>
</div>
</div>
</section>
<section class="ths-section ths-about-section">
<div class="ths-about-inner gh-canvas">
<p>
Construyo sistemas y automatizaciones para que las cosas funcionen sin depender de la suerte. Mi foco esta en DevOps/SRE, seguridad y despliegues: Linux, contenedores, Kubernetes, Ansible, redes, proxies, certificados y pipelines. Me engancha convertir un "esto es un lio" en una arquitectura clara, documentada y repetible. Aqui comparto proyectos, aprendizajes y herramientas que voy afinando.
</p>
</div>
</section>
{{#get "posts" filter="tag:hash-project" limit="3" include="tags" as |featuredProjects|}}
{{#if featuredProjects}}
<section class="ths-section ths-posts-section">
<div class="gh-canvas">
<h2 class="ths-section-title">Proyectos Destacados</h2>
<div class="ths-posts-grid">
{{#foreach featuredProjects}}
<a class="ths-post-card" href="{{url}}">
<h3 class="ths-post-card-title">{{title}}</h3>
<p class="ths-post-card-excerpt">{{excerpt words="20"}}</p>
</a>
{{/foreach}}
</div>
<div class="ths-posts-more">
<a href="/proyectos/">Ver todos los proyectos &rarr;</a>
</div>
</div>
</section>
{{/if}}
{{/get}}
<section class="ths-section ths-contact-section">
<div class="ths-contact-inner">
<h2>¿Hablamos?</h2>
<p>Estoy abierto a conversaciones sobre DevOps, proyectos interesantes y oportunidades de colaboración.</p>
<a class="ths-btn ths-btn-primary" href="/contacto/">Contactarme</a>
</div>
</section>
</main>
{{/post}}
-26
View File
@@ -1,26 +0,0 @@
{{!< default}}
{{!-- The tag above means: insert everything in this file into the body of the default.hbs template --}}
{{#post}}
<main class="gh-main">
<article class="gh-article {{post_class}}">
{{#match @page.show_title_and_feature_image}}
<header class="gh-article-header gh-canvas">
<h1 class="gh-article-title is-title">{{title}}</h1>
{{#if custom_excerpt}}
<p class="gh-article-excerpt is-body">{{custom_excerpt}}</p>
{{/if}}
{{> "feature-image"}}
</header>
{{/match}}
<section class="gh-content gh-canvas is-body">
{{content}}
</section>
</article>
</main>
{{/post}}
@@ -1 +0,0 @@
{{!-- Personal portfolio: no public subscription CTA. --}}
@@ -1,14 +0,0 @@
{{#if showFeatured}}
{{#get "posts" filter="featured:true" include="authors" limit=limit as |featured|}}
<section class="gh-featured gh-outer">
<div class="gh-featured-inner gh-inner">
<h2 class="gh-featured-title">{{t "Featured"}}</h2>
<div class="gh-featured-feed">
{{#foreach featured}}
{{> "post-card" imageSizes="80px"}}
{{/foreach}}
</div>
</div>
</section>
{{/get}}
{{/if}}
@@ -1,25 +0,0 @@
<footer class="ths-footer">
<div class="ths-footer-inner">
<div class="ths-footer-grid">
<nav aria-label="Navegación">
<h2>Navegación</h2>
<a href="/proyectos/">Proyectos</a>
<a href="https://news.sherlockhomeless.net/">Blog</a>
<a href="/lab/">Lab</a>
</nav>
<nav aria-label="Conecta">
<h2>Conecta</h2>
<a href="https://github.com/epava1516" target="_blank" rel="noopener">GitHub</a>
<a href="https://t.me/SherlockHomeless1516" target="_blank" rel="noopener">Telegram</a>
</nav>
<nav aria-label="Más">
<h2>Más</h2>
<a href="/sobre-mi/">Sobre mí</a>
<a href="/contacto/">Contacto</a>
</nav>
</div>
<div class="ths-footer-copyright">
&copy; {{date format="YYYY"}} Eduardo David Paredes Vara. All rights reserved.
</div>
</div>
</footer>
@@ -1,80 +0,0 @@
<section class="gh-header is-{{#match headerStyle "Magazine"}}magazine{{else match headerStyle "Highlight"}}highlight{{else}}classic{{/match}}{{#if @custom.background_image}}{{#if @site.cover_image}} has-image{{/if}}{{/if}} gh-outer">
{{!-- Background image --}}
{{#if @custom.background_image}}
{{#match headerStyle "!=" "Magazine"}}
{{#match headerStyle "!=" "Highlight"}}
{{#if @site.cover_image}}
<img class="gh-header-image" src="{{@site.cover_image}}" alt="{{@site.title}}">
{{/if}}
{{/match}}
{{/match}}
{{/if}}
<div class="gh-header-inner gh-inner">
{{!-- Highlight layout --}}
{{#match headerStyle "Highlight"}}
<div class="gh-header-left">
{{#foreach posts limit="1"}}
{{> "post-card" imageSizes="(max-width: 767px) calc(100vw - max(8vmin, 40px)), 640px"}}
{{/foreach}}
</div>
<div class="gh-header-middle">
{{#foreach posts from="2" limit="3"}}
{{> "post-card"}}
{{/foreach}}
</div>
<div class="gh-header-right">
{{#if @custom.show_featured_posts}}
{{> "components/featured" showFeatured=@custom.show_featured_posts limit=6}}
{{else}}
<div class="gh-featured-feed">
{{#foreach posts from="5" limit="6"}}
{{> "post-card" imageSizes="80px"}}
{{/foreach}}
</div>
{{/if}}
</div>
{{/match}}
{{!-- Magazine layout --}}
{{#match headerStyle "Magazine"}}
{{#foreach posts limit="7"}}
{{#match @number 2}}
<div class="gh-header-left">
{{/match}}
{{#match @number 5}}
<div class="gh-header-right">
{{/match}}
{{#if @first}}
{{> "post-card" imageSizes="640px"}}
{{else}}
{{> "post-card"}}
{{/if}}
{{#match @number 4}}
</div>
{{/match}}
{{#match @number 7}}
</div>
{{/match}}
{{/foreach}}
{{/match}}
{{!-- Landing layout --}}
{{#match headerStyle "Landing"}}
<h1 class="gh-header-title is-title">{{#if @custom.header_text}}{{@custom.header_text}}{{else}}{{@site.description}}{{/if}}</h1>
{{/match}}
{{!-- Search layout --}}
{{#match headerStyle "Search"}}
<h1 class="gh-header-title is-title">{{#if @custom.header_text}}{{@custom.header_text}}{{else}}{{@site.description}}{{/if}}</h1>
<form class="gh-form">
{{> "icons/search"}}
<button class="gh-form-input" data-ghost-search>{{t "Search posts, tags and authors"}}</button>
</form>
{{/match}}
</div>
</section>
@@ -1,11 +0,0 @@
{{#match headerStyle "!=" "Off"}}
{{#match headerStyle "Highlight"}}
{{> "components/header-content"}}
{{else match headerStyle "Magazine"}}
{{> "components/header-content"}}
{{else}}
{{> "components/header-content"}}
{{/match}}
{{/match}}
@@ -1,23 +0,0 @@
<header id="gh-navigation" class="gh-navigation is-{{#match navigationLayout "Logo on the left"}}left-logo{{else match navigationLayout "Stacked"}}stacked{{else}}middle-logo{{/match}}{{#match @custom.header_and_footer_color "Accent color"}} has-accent-color{{/match}} gh-outer">
<div class="gh-navigation-inner gh-inner">
<div class="gh-navigation-brand">
<a class="gh-navigation-logo is-title" href="{{@site.url}}">
{{#if @site.logo}}
<img src="{{@site.logo}}" alt="{{@site.title}}">
{{else}}
{{@site.title}}
{{/if}}
</a>
<button class="gh-burger gh-icon-button" aria-label="{{t "Menu"}}">
{{> "icons/burger"}}
{{> "icons/close"}}
</button>
</div>
<nav class="gh-navigation-menu">
{{navigation}}
</nav>
</div>
</header>
@@ -1,112 +0,0 @@
{{!--
Parameters:
* feed (index, home, archive, recent)
* postFeedStyle (list, grid)
* showTitle (true, false)
* showSidebar (true, false)
--}}
<section class="gh-container is-{{#match postFeedStyle "List"}}list{{else}}grid{{/match}}{{#if showSidebar}} has-sidebar{{/if}}{{#unless @custom.show_images_in_feed}} no-image{{/unless}} gh-outer">
<div class="gh-container-inner gh-inner">
{{#if showTitle}}
<h2 class="gh-container-title">
{{#unless title}}{{t "Latest"}}{{else}}{{title}}{{/unless}}
</h2>
{{/if}}
<main class="gh-main">
<div class="gh-feed">
{{!-- Homepage --}}
{{#match feed "home"}}
{{#match @custom.header_style "Highlight"}}
{{#if @custom.show_featured_posts}}
{{#match posts.length ">=" 4}}
{{#get "posts" include="authors" limit="16"}}
{{#foreach posts from="5"}}
{{> "post-card" lazyLoad=true}}
{{/foreach}}
{{/get}}
{{/match}}
{{else}}
{{#match posts.length ">=" 10}}
{{#get "posts" include="authors" limit="22"}}
{{#foreach posts from="11"}}
{{> "post-card" lazyLoad=true}}
{{/foreach}}
{{/get}}
{{/match}}
{{/if}}
{{else match @custom.header_style "Magazine"}}
{{#match posts.length ">=" 7}}
{{#get "posts" include="authors" limit="19"}}
{{#foreach posts from="8"}}
{{> "post-card" lazyLoad=true}}
{{/foreach}}
{{/get}}
{{/match}}
{{else}}
{{#get "posts" include="authors" limit=@config.posts_per_page}}
{{#foreach posts}}
{{> "post-card" lazyLoad=true}}
{{/foreach}}
{{/get}}
{{/match}}
{{/match}}
{{!-- All posts --}}
{{#match feed "index"}}
{{#match pagination.page 2}}
{{#get "posts" include="authors" limit=@config.posts_per_page as |recent|}}
{{#foreach recent}}
{{> "post-card"}}
{{/foreach}}
{{/get}}
{{/match}}
{{#foreach posts}}
{{> "post-card" lazyLoad=true}}
{{/foreach}}
{{/match}}
{{!-- Tag and author pages --}}
{{#match feed "archive"}}
{{#foreach posts}}
{{> "post-card" lazyLoad=true}}
{{/foreach}}
{{/match}}
</div>
{{#match pagination.pages ">" 1}}
<div class="gh-more is-title">
<a href="{{@site.url}}/page/2/">{{t "See all"}} {{> "icons/arrow"}}</a>
</div>
{{/match}}
</main>
{{#if showSidebar}}
<aside class="gh-sidebar">
<div class="gh-sidebar-inner">
<section class="gh-about">
{{#if @site.icon}}
<img class="gh-about-icon" src="{{@site.icon}}" alt="{{@site.title}}" loading="lazy">
{{/if}}
<h3 class="gh-about-title is-title">{{@site.title}}</h3>
{{#if @site.description}}
<p class="gh-about-description is-body">{{@site.description}}</p>
{{/if}}
</section>
{{#if @site.recommendations_enabled}}
<section class="gh-recommendations">
<h4 class="gh-sidebar-title">{{t "Recommendations"}}</h4>
{{recommendations}}
<button data-portal="recommendations">{{t "See all"}} {{> "icons/arrow"}}</button>
</section>
{{/if}}
</div>
</aside>
{{/if}}
</div>
</section>
@@ -1,9 +0,0 @@
<form class="gh-form" data-members-form>
<input class="gh-form-input" id="{{email_field_id}}" name="email" type="email" placeholder="{{t "jamie@example.com"}}" required data-members-email>
<button class="gh-button" type="submit" aria-label="{{t "Subscribe"}}">
<span><span>{{t "Subscribe"}}</span> {{> "icons/arrow"}}</span>
{{> "icons/loader"}}
<span>{{t "Email sent"}}</span>
</button>
<p data-members-error></p>
</form>
@@ -1,17 +0,0 @@
{{#if feature_image}}
<figure class="gh-article-image">
<img
srcset="{{img_url feature_image size="s"}} 320w,
{{img_url feature_image size="m"}} 600w,
{{img_url feature_image size="l"}} 960w,
{{img_url feature_image size="xl"}} 1200w,
{{img_url feature_image size="xxl"}} 2000w"
sizes="(max-width: 1200px) 100vw, 1120px"
src="{{img_url feature_image size="xl"}}"
alt="{{#if feature_image_alt}}{{feature_image_alt}}{{else}}{{title}}{{/if}}"
>
{{#if feature_image_caption}}
<figcaption>{{feature_image_caption}}</figcaption>
{{/if}}
</figure>
{{/if}}
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="currentColor" viewBox="0 0 256 256"><path d="M224.49,136.49l-72,72a12,12,0,0,1-17-17L187,140H40a12,12,0,0,1,0-24H187L135.51,64.48a12,12,0,0,1,17-17l72,72A12,12,0,0,1,224.49,136.49Z"></path></svg>

Before

Width:  |  Height:  |  Size: 264 B

@@ -1 +0,0 @@
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path d="M3.513 18.998C4.749 15.504 8.082 13 12 13s7.251 2.504 8.487 5.998C18.47 21.442 15.417 23 12 23s-6.47-1.558-8.487-4.002zM12 12c2.21 0 4-2.79 4-5s-1.79-4-4-4-4 1.79-4 4 1.79 5 4 5z" fill="#FFF"/></g></svg>

Before

Width:  |  Height:  |  Size: 308 B

@@ -1,3 +0,0 @@
<svg class="icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.8868 11.3624C10.8107 9.1288 7.87874 4.96656 5.15331 2.91348C3.18679 1.43245 0 0.28612 0 3.93314C0 4.66121 0.416033 10.0519 0.660462 10.927C1.50889 13.9693 4.60131 14.7453 7.352 14.2758C2.54378 15.0967 1.32092 17.8158 3.9624 20.535C8.97901 25.6994 11.1728 19.2393 11.7346 17.584C11.8385 17.2784 11.8868 17.1365 11.8868 17.262C11.8868 17.1365 11.9351 17.2784 12.0391 17.584C12.6008 19.2393 14.7946 25.6994 19.8113 20.535C22.4527 17.8158 21.2298 15.0967 16.4217 14.2758C19.1723 14.7453 22.2648 13.9693 23.1132 10.927C23.3576 10.0519 23.7736 4.66121 23.7736 3.93314C23.7736 0.28612 20.5872 1.43245 18.6204 2.91348C15.8949 4.96656 12.963 9.1288 11.8868 11.3624Z" fill="currentColor"/>
</svg>

Before

Width:  |  Height:  |  Size: 789 B

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M224,128a8,8,0,0,1-8,8H40a8,8,0,0,1,0-16H216A8,8,0,0,1,224,128ZM40,72H216a8,8,0,0,0,0-16H40a8,8,0,0,0,0,16ZM216,184H40a8,8,0,0,0,0,16H216a8,8,0,0,0,0-16Z"></path></svg>

Before

Width:  |  Height:  |  Size: 282 B

@@ -1,24 +0,0 @@
<svg class="checkmark" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52">
<path class="checkmark__check" fill="none" d="M14.1 27.2l7.1 7.2 16.7-16.8"/>
<style>
.checkmark {
width: 40px;
height: 40px;
display: block;
stroke-width: 2.5;
stroke: currentColor;
stroke-miterlimit: 10;
}
.checkmark__check {
transform-origin: 50% 50%;
stroke-dasharray: 48;
stroke-dashoffset: 48;
animation: stroke .3s cubic-bezier(0.650, 0.000, 0.450, 1.000) forwards;
}
@keyframes stroke {
100% { stroke-dashoffset: 0; }
}
</style>
</svg>

Before

Width:  |  Height:  |  Size: 716 B

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M205.66,194.34a8,8,0,0,1-11.32,11.32L128,139.31,61.66,205.66a8,8,0,0,1-11.32-11.32L116.69,128,50.34,61.66A8,8,0,0,1,61.66,50.34L128,116.69l66.34-66.35a8,8,0,0,1,11.32,11.32L139.31,128Z"></path></svg>

Before

Width:  |  Height:  |  Size: 313 B

@@ -1 +0,0 @@
<svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="currentColor"><path d="M23.9981 11.9991C23.9981 5.37216 18.626 0 11.9991 0C5.37216 0 0 5.37216 0 11.9991C0 17.9882 4.38789 22.9522 10.1242 23.8524V15.4676H7.07758V11.9991H10.1242V9.35553C10.1242 6.34826 11.9156 4.68714 14.6564 4.68714C15.9692 4.68714 17.3424 4.92149 17.3424 4.92149V7.87439H15.8294C14.3388 7.87439 13.8739 8.79933 13.8739 9.74824V11.9991H17.2018L16.6698 15.4676H13.8739V23.8524C19.6103 22.9522 23.9981 17.9882 23.9981 11.9991Z"/></svg>

Before

Width:  |  Height:  |  Size: 531 B

@@ -1,3 +0,0 @@
<svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.49365 4.58752C3.53115 6.03752 2.74365 7.70002 2.74365 9.25002C2.74365 10.6424 3.29678 11.9778 4.28134 12.9623C5.26591 13.9469 6.60127 14.5 7.99365 14.5C9.38604 14.5 10.7214 13.9469 11.706 12.9623C12.6905 11.9778 13.2437 10.6424 13.2437 9.25002C13.2437 6.00002 10.9937 3.50002 9.16865 1.68127L6.99365 6.25002L4.49365 4.58752Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
</svg>

Before

Width:  |  Height:  |  Size: 538 B

@@ -1,5 +0,0 @@
<svg class="icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 2.982C14.937 2.982 15.285 2.993 16.445 3.046C17.1424 3.05412 17.8331 3.18233 18.487 3.425C18.965 3.60111 19.3973 3.88237 19.752 4.248C20.1176 4.60269 20.3989 5.035 20.575 5.513C20.8177 6.16685 20.9459 6.85762 20.954 7.555C21.007 8.715 21.018 9.063 21.018 12C21.018 14.937 21.007 15.285 20.954 16.445C20.9459 17.1424 20.8177 17.8331 20.575 18.487C20.3919 18.9615 20.1116 19.3924 19.752 19.752C19.3924 20.1116 18.9615 20.3919 18.487 20.575C17.8331 20.8177 17.1424 20.9459 16.445 20.954C15.285 21.007 14.937 21.018 12 21.018C9.063 21.018 8.715 21.007 7.555 20.954C6.85762 20.9459 6.16685 20.8177 5.513 20.575C5.035 20.3989 4.60269 20.1176 4.248 19.752C3.88237 19.3973 3.60111 18.965 3.425 18.487C3.18233 17.8331 3.05412 17.1424 3.046 16.445C2.993 15.285 2.982 14.937 2.982 12C2.982 9.063 2.993 8.715 3.046 7.555C3.05412 6.85762 3.18233 6.16685 3.425 5.513C3.60111 5.035 3.88237 4.60269 4.248 4.248C4.60269 3.88237 5.035 3.60111 5.513 3.425C6.16685 3.18233 6.85762 3.05412 7.555 3.046C8.715 2.993 9.063 2.982 12 2.982ZM12 1C9.013 1 8.638 1.013 7.465 1.066C6.55258 1.08486 5.6499 1.25762 4.795 1.577C4.06355 1.86017 3.3994 2.29319 2.84521 2.84824C2.29102 3.40329 1.85904 4.06811 1.577 4.8C1.25762 5.6549 1.08486 6.55758 1.066 7.47C1.013 8.638 1 9.013 1 12C1 14.987 1.013 15.362 1.066 16.535C1.08486 17.4474 1.25762 18.3501 1.577 19.205C1.86017 19.9365 2.29319 20.6006 2.84824 21.1548C3.40329 21.709 4.06811 22.141 4.8 22.423C5.6549 22.7424 6.55758 22.9151 7.47 22.934C8.638 22.987 9.013 23 12 23C14.987 23 15.362 22.987 16.535 22.934C17.4474 22.9151 18.3501 22.7424 19.205 22.423C19.9365 22.1398 20.6006 21.7068 21.1548 21.1518C21.709 20.5967 22.141 19.9319 22.423 19.2C22.7424 18.3451 22.9151 17.4424 22.934 16.53C22.987 15.362 23 14.987 23 12C23 9.013 22.987 8.638 22.934 7.465C22.9151 6.55258 22.7424 5.6499 22.423 4.795C22.1398 4.06355 21.7068 3.3994 21.1518 2.84521C20.5967 2.29102 19.9319 1.85904 19.2 1.577C18.3451 1.25762 17.4424 1.08486 16.53 1.066C15.362 1.013 14.987 1 12 1Z" fill="currentColor"/>
<path d="M11.9996 6.35107C10.8823 6.35107 9.79015 6.68238 8.86117 7.3031C7.9322 7.92382 7.20815 8.80608 6.78059 9.8383C6.35303 10.8705 6.24116 12.0063 6.45913 13.1021C6.6771 14.1979 7.21512 15.2045 8.00514 15.9945C8.79517 16.7845 9.80172 17.3226 10.8975 17.5405C11.9933 17.7585 13.1291 17.6466 14.1614 17.2191C15.1936 16.7915 16.0758 16.0675 16.6966 15.1385C17.3173 14.2095 17.6486 13.1173 17.6486 12.0001C17.6486 10.5019 17.0534 9.06502 15.994 8.00563C14.9346 6.94624 13.4978 6.35107 11.9996 6.35107ZM11.9996 15.6671C11.2743 15.6671 10.5653 15.452 9.96231 15.0491C9.35928 14.6461 8.88927 14.0734 8.61172 13.4034C8.33418 12.7333 8.26156 11.996 8.40305 11.2847C8.54454 10.5734 8.89379 9.91995 9.40663 9.40711C9.91947 8.89427 10.5729 8.54503 11.2842 8.40353C11.9955 8.26204 12.7328 8.33466 13.4029 8.61221C14.0729 8.88975 14.6457 9.35976 15.0486 9.9628C15.4515 10.5658 15.6666 11.2748 15.6666 12.0001C15.6666 12.9726 15.2802 13.9053 14.5925 14.593C13.9049 15.2807 12.9721 15.6671 11.9996 15.6671Z" fill="currentColor"/>
<path d="M17.8718 7.44811C18.6008 7.44811 19.1918 6.85712 19.1918 6.12811C19.1918 5.39909 18.6008 4.80811 17.8718 4.80811C17.1427 4.80811 16.5518 5.39909 16.5518 6.12811C16.5518 6.85712 17.1427 7.44811 17.8718 7.44811Z" fill="currentColor"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.3 KiB

@@ -1,3 +0,0 @@
<svg class="icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M22.2 0H1.8C1.32261 0 0.864773 0.18964 0.527213 0.527213C0.18964 0.864773 0 1.32261 0 1.8V22.2C0 22.6773 0.18964 23.1352 0.527213 23.4728C0.864773 23.8104 1.32261 24 1.8 24H22.2C22.6773 24 23.1352 23.8104 23.4728 23.4728C23.8104 23.1352 24 22.6773 24 22.2V1.8C24 1.32261 23.8104 0.864773 23.4728 0.527213C23.1352 0.18964 22.6773 0 22.2 0ZM7.2 20.4H3.6V9.6H7.2V20.4ZM5.4 7.5C4.98741 7.48821 4.58747 7.35509 4.25011 7.11729C3.91275 6.87949 3.65293 6.54755 3.50316 6.16293C3.35337 5.77832 3.32025 5.35809 3.40793 4.95476C3.4956 4.55144 3.7002 4.18288 3.99613 3.89517C4.29208 3.60745 4.66624 3.41332 5.07188 3.33704C5.47752 3.26075 5.89664 3.30569 6.27688 3.46625C6.65712 3.6268 6.98163 3.89585 7.20983 4.23977C7.43804 4.58371 7.55983 4.98725 7.56 5.4C7.55052 5.96441 7.318 6.50213 6.91327 6.89564C6.50852 7.28913 5.96447 7.50643 5.4 7.5ZM20.4 20.4H16.8V14.712C16.8 13.008 16.08 12.396 15.144 12.396C14.8696 12.4143 14.6015 12.4865 14.3551 12.6088C14.1087 12.7309 13.8888 12.9007 13.7081 13.108C13.5276 13.3155 13.3896 13.5565 13.3024 13.8173C13.2152 14.0781 13.1804 14.3536 13.2 14.628C13.194 14.6839 13.194 14.7401 13.2 14.796V20.4H9.6V9.6H13.08V11.16C13.4311 10.626 13.9133 10.1911 14.4807 9.89693C15.048 9.6028 15.6813 9.4592 16.32 9.48C18.18 9.48 20.352 10.512 20.352 13.872L20.4 20.4Z" fill="currentColor"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

@@ -1,17 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" width="24" viewBox="0 0 24 24">
<g stroke-linecap="round" stroke-width="2" fill="currentColor" stroke="none" stroke-linejoin="round" class="nc-icon-wrapper">
<g class="nc-loop-dots-4-24-icon-o">
<circle cx="4" cy="12" r="3"></circle>
<circle cx="12" cy="12" r="3"></circle>
<circle cx="20" cy="12" r="3"></circle>
</g>
<style data-cap="butt">
.nc-loop-dots-4-24-icon-o{--animation-duration:0.8s}
.nc-loop-dots-4-24-icon-o *{opacity:.4;transform:scale(.75);animation:nc-loop-dots-4-anim var(--animation-duration) infinite}
.nc-loop-dots-4-24-icon-o :nth-child(1){transform-origin:4px 12px;animation-delay:-.3s;animation-delay:calc(var(--animation-duration)/-2.666)}
.nc-loop-dots-4-24-icon-o :nth-child(2){transform-origin:12px 12px;animation-delay:-.15s;animation-delay:calc(var(--animation-duration)/-5.333)}
.nc-loop-dots-4-24-icon-o :nth-child(3){transform-origin:20px 12px}
@keyframes nc-loop-dots-4-anim{0%,100%{opacity:.4;transform:scale(.75)}50%{opacity:1;transform:scale(1)}}
</style>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" height="20" width="20" id="Lock-1--Streamline-Ultimate"><defs></defs><title>lock-1</title><path d="M4.375 8.125h11.25s1.25 0 1.25 1.25v8.75s0 1.25 -1.25 1.25H4.375s-1.25 0 -1.25 -1.25v-8.75s0 -1.25 1.25 -1.25" fill="none" stroke="currentcolor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="M5.625 8.125V5a4.375 4.375 0 0 1 8.75 0v3.125" fill="none" stroke="currentcolor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="m10 12.5 0 2.5" fill="none" stroke="currentcolor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path></svg>

Before

Width:  |  Height:  |  Size: 678 B

@@ -1,3 +0,0 @@
<svg class="icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.68 18.2926C19.6352 17.9336 22.2083 16.0807 22.5317 14.3878C23.0412 11.7208 22.9992 7.87958 22.9992 7.87958C22.9992 2.67325 19.647 1.14719 19.647 1.14719C17.9569 0.357269 15.0545 0.0250852 12.0393 0H11.9652C8.94993 0.0250852 6.04953 0.357269 4.35923 1.14719C4.35923 1.14719 1.00697 2.67325 1.00697 7.87958C1.00697 8.19521 1.00537 8.52719 1.0037 8.87279C0.999066 9.83259 0.993924 10.8974 1.02121 12.0089C1.14294 17.1004 1.93853 22.1184 6.56468 23.3644C8.69769 23.9389 10.529 24.0591 12.004 23.9766C14.6787 23.8257 16.1801 23.0053 16.1801 23.0053L16.092 21.0305C16.092 21.0305 14.1806 21.6438 12.0339 21.5691C9.90716 21.4949 7.66194 21.3357 7.31796 18.6786C7.28619 18.4452 7.2703 18.1957 7.2703 17.9336C7.2703 17.9336 9.35808 18.4528 12.004 18.5762C13.6218 18.6517 15.1391 18.4797 16.68 18.2926ZM19.045 14.5875V8.28346C19.045 6.99507 18.7227 5.97123 18.0752 5.21376C17.4074 4.45629 16.5328 4.06799 15.4474 4.06799C14.1913 4.06799 13.2402 4.55922 12.6114 5.54181L12 6.58465L11.3886 5.54181C10.7598 4.55922 9.80863 4.06799 8.55266 4.06799C7.46709 4.06799 6.59259 4.45629 5.92483 5.21376C5.27728 5.97123 4.9549 6.99507 4.9549 8.28346V14.5875H7.40928V8.46875C7.40928 7.17894 7.94259 6.52427 9.00936 6.52427C10.1888 6.52427 10.7801 7.30087 10.7801 8.8365V12.1856H13.22V8.8365C13.22 7.30087 13.8111 6.52427 14.9906 6.52427C16.0573 6.52427 16.5907 7.17894 16.5907 8.46875V14.5875H19.045Z" fill="currentColor"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

Some files were not shown because too many files have changed in this diff Show More