From 634028d508bac3e614624d4da43263ebb78a1037 Mon Sep 17 00:00:00 2001 From: Eduardo David Paredes Vara Date: Thu, 8 Oct 2026 00:59:42 +0000 Subject: [PATCH] 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 --- DEPRECATED-kasm/README.md | 38 - DEPRECATED-kasm/docker-compose.yml | 226 - DEPRECATED-kasm/stack.env | 10 - README.md | 25 +- backups/kopia/README.md | 6 +- backups/kopia/backup-paths.txt | 23 +- backups/kopia/excludes.txt | 10 - backups/kopia/pre-backup-dumps.sh | 5 - beszel/README.md | 55 - beszel/docker-compose.yml | 49 - beszel/stack.env | 16 - bookstack/README.md | 41 - bookstack/docker-compose.yml | 66 - bookstack/stack.env | 29 - changedetection/README.md | 30 - changedetection/docker-compose.yml | 45 - crowdsec/README.md | 56 +- crowdsec/docker-compose.yml | 84 - crowdsec/metrics-export.sh | 55 - crowdsec/setup.sh | 2 - crowdsec/stack.env | 21 - dozzle/README.md | 42 - dozzle/docker-compose.yml | 30 - dozzle/stack.env | 13 - finance/README.md | 28 - finance/docker-compose.yml | 31 - ghost-me/.env.example | 19 - ghost-me/README.md | 29 - ghost-me/docker-compose.yml | 91 - ghost-me/routes.yaml | 18 - ghost-me/theme-source-personal/LICENSE | 22 - ghost-me/theme-source-personal/README.md | 68 - .../assets/built/screen.css | 1 - .../assets/built/screen.css.map | 1 - .../assets/built/source.js | 1 - .../assets/built/source.js.map | 1 - .../assets/css/personal.css | 861 --- .../assets/css/screen.css | 3388 --------- .../assets/fonts/eb-garamond-italic.woff2 | Bin 41180 -> 0 bytes .../assets/fonts/eb-garamond-roman.woff2 | Bin 40064 -> 0 bytes .../assets/fonts/inter-roman.woff2 | Bin 37780 -> 0 bytes .../assets/fonts/jetbrains-mono-italic.woff2 | Bin 42436 -> 0 bytes .../assets/fonts/jetbrains-mono-roman.woff2 | Bin 39888 -> 0 bytes .../assets/images/default-skin.png | Bin 547 -> 0 bytes .../assets/images/default-skin.svg | 1 - .../assets/images/preloader.gif | Bin 866 -> 0 bytes .../assets/js/dropdown.js | 93 - .../assets/js/lib/imagesloaded.pkgd.min.js | 7 - .../js/lib/photoswipe-ui-default.min.js | 4 - .../assets/js/lib/photoswipe.min.js | 4 - .../assets/js/lib/reframe.min.js | 1 - .../assets/js/lightbox.js | 103 - .../theme-source-personal/assets/js/main.js | 60 - .../assets/js/pagination.js | 95 - ghost-me/theme-source-personal/author.hbs | 64 - ghost-me/theme-source-personal/default.hbs | 74 - ghost-me/theme-source-personal/gulpfile.js | 177 - ghost-me/theme-source-personal/home.hbs | 12 - ghost-me/theme-source-personal/index.hbs | 6 - .../locales/context.json | 121 - .../theme-source-personal/locales/de-CH.json | 121 - .../theme-source-personal/locales/de.json | 121 - .../theme-source-personal/locales/en.json | 121 - .../theme-source-personal/locales/fr.json | 121 - .../theme-source-personal/locales/ga.json | 121 - .../theme-source-personal/locales/gd.json | 121 - .../theme-source-personal/locales/it.json | 121 - .../theme-source-personal/locales/nl.json | 121 - .../theme-source-personal/locales/pt-BR.json | 121 - .../theme-source-personal/locales/sv.json | 121 - .../theme-source-personal/locales/tr.json | 121 - .../theme-source-personal/locales/uk.json | 121 - .../theme-source-personal/locales/vi.json | 121 - .../locales/zh-Hant.json | 121 - .../theme-source-personal/locales/zh.json | 121 - ghost-me/theme-source-personal/package.json | 214 - .../theme-source-personal/page-inicio.hbs | 60 - ghost-me/theme-source-personal/page.hbs | 26 - .../partials/components/cta.hbs | 1 - .../partials/components/featured.hbs | 14 - .../partials/components/footer.hbs | 25 - .../partials/components/header-content.hbs | 80 - .../partials/components/header.hbs | 11 - .../partials/components/navigation.hbs | 23 - .../partials/components/post-list.hbs | 112 - .../partials/email-subscription.hbs | 9 - .../partials/feature-image.hbs | 17 - .../partials/icons/arrow.hbs | 1 - .../partials/icons/avatar.hbs | 1 - .../partials/icons/bluesky.hbs | 3 - .../partials/icons/burger.hbs | 1 - .../partials/icons/checkmark.hbs | 24 - .../partials/icons/close.hbs | 1 - .../partials/icons/facebook.hbs | 1 - .../partials/icons/fire.hbs | 3 - .../partials/icons/instagram.hbs | 5 - .../partials/icons/linkedin.hbs | 3 - .../partials/icons/loader.hbs | 17 - .../partials/icons/lock.hbs | 1 - .../partials/icons/mastodon.hbs | 3 - .../partials/icons/rss.hbs | 1 - .../partials/icons/search.hbs | 1 - .../partials/icons/threads.hbs | 3 - .../partials/icons/tiktok.hbs | 3 - .../partials/icons/x.hbs | 1 - .../partials/icons/youtube.hbs | 3 - .../partials/lightbox.hbs | 41 - .../partials/post-card.hbs | 47 - .../partials/search-toggle.hbs | 3 - .../partials/typography/fonts.hbs | 15 - .../partials/typography/mono.hbs | 21 - .../partials/typography/sans.hbs | 11 - .../partials/typography/serif.hbs | 21 - ghost-me/theme-source-personal/post.hbs | 85 - ghost-me/theme-source-personal/projects.hbs | 80 - ghost-me/theme-source-personal/renovate.json | 6 - ghost-me/theme-source-personal/tag.hbs | 22 - ghost-me/theme-source-personal/yarn.lock | 6232 ----------------- ghost/.env.example | 21 - ghost/README.md | 48 - ghost/docker-compose.yml | 108 - ghost/ops-gate/ops_gate.py | 113 - ghost/theme-overrides/headline/default.hbs | 140 - ghost/theme-overrides/headline/ops.hbs | 31 - .../headline/partials/loop-grid.hbs | 89 - .../headline/partials/loop-minimal.hbs | 15 - ghost/theme-overrides/headline/post.hbs | 83 - it/Dockerfile | 8 - it/README.md | 27 - it/docker-compose.yml | 31 - memos/README.md | 42 - memos/docker-compose.yml | 68 - memos/proxy/server.js | 81 - memos/stack.env | 10 - n8n-observability/scripts/ops-report | 7 - onetimesecret/README.md | 41 - onetimesecret/docker-compose.yml | 65 - onetimesecret/stack.env | 21 - pdf/README.md | 30 - pdf/docker-compose.yml | 36 - vikunja/README.md | 42 - vikunja/docker-compose.yml | 69 - vikunja/stack.env | 17 - 143 files changed, 19 insertions(+), 16649 deletions(-) delete mode 100644 DEPRECATED-kasm/README.md delete mode 100644 DEPRECATED-kasm/docker-compose.yml delete mode 100644 DEPRECATED-kasm/stack.env delete mode 100644 beszel/README.md delete mode 100644 beszel/docker-compose.yml delete mode 100644 beszel/stack.env delete mode 100644 bookstack/README.md delete mode 100644 bookstack/docker-compose.yml delete mode 100644 bookstack/stack.env delete mode 100644 changedetection/README.md delete mode 100644 changedetection/docker-compose.yml delete mode 100755 crowdsec/metrics-export.sh delete mode 100644 dozzle/README.md delete mode 100644 dozzle/docker-compose.yml delete mode 100644 dozzle/stack.env delete mode 100644 finance/README.md delete mode 100644 finance/docker-compose.yml delete mode 100644 ghost-me/.env.example delete mode 100644 ghost-me/README.md delete mode 100644 ghost-me/docker-compose.yml delete mode 100644 ghost-me/routes.yaml delete mode 100644 ghost-me/theme-source-personal/LICENSE delete mode 100644 ghost-me/theme-source-personal/README.md delete mode 100644 ghost-me/theme-source-personal/assets/built/screen.css delete mode 100644 ghost-me/theme-source-personal/assets/built/screen.css.map delete mode 100644 ghost-me/theme-source-personal/assets/built/source.js delete mode 100644 ghost-me/theme-source-personal/assets/built/source.js.map delete mode 100644 ghost-me/theme-source-personal/assets/css/personal.css delete mode 100644 ghost-me/theme-source-personal/assets/css/screen.css delete mode 100644 ghost-me/theme-source-personal/assets/fonts/eb-garamond-italic.woff2 delete mode 100644 ghost-me/theme-source-personal/assets/fonts/eb-garamond-roman.woff2 delete mode 100644 ghost-me/theme-source-personal/assets/fonts/inter-roman.woff2 delete mode 100644 ghost-me/theme-source-personal/assets/fonts/jetbrains-mono-italic.woff2 delete mode 100644 ghost-me/theme-source-personal/assets/fonts/jetbrains-mono-roman.woff2 delete mode 100644 ghost-me/theme-source-personal/assets/images/default-skin.png delete mode 100644 ghost-me/theme-source-personal/assets/images/default-skin.svg delete mode 100644 ghost-me/theme-source-personal/assets/images/preloader.gif delete mode 100644 ghost-me/theme-source-personal/assets/js/dropdown.js delete mode 100644 ghost-me/theme-source-personal/assets/js/lib/imagesloaded.pkgd.min.js delete mode 100644 ghost-me/theme-source-personal/assets/js/lib/photoswipe-ui-default.min.js delete mode 100644 ghost-me/theme-source-personal/assets/js/lib/photoswipe.min.js delete mode 100644 ghost-me/theme-source-personal/assets/js/lib/reframe.min.js delete mode 100644 ghost-me/theme-source-personal/assets/js/lightbox.js delete mode 100644 ghost-me/theme-source-personal/assets/js/main.js delete mode 100644 ghost-me/theme-source-personal/assets/js/pagination.js delete mode 100644 ghost-me/theme-source-personal/author.hbs delete mode 100644 ghost-me/theme-source-personal/default.hbs delete mode 100644 ghost-me/theme-source-personal/gulpfile.js delete mode 100644 ghost-me/theme-source-personal/home.hbs delete mode 100644 ghost-me/theme-source-personal/index.hbs delete mode 100644 ghost-me/theme-source-personal/locales/context.json delete mode 100644 ghost-me/theme-source-personal/locales/de-CH.json delete mode 100644 ghost-me/theme-source-personal/locales/de.json delete mode 100644 ghost-me/theme-source-personal/locales/en.json delete mode 100644 ghost-me/theme-source-personal/locales/fr.json delete mode 100644 ghost-me/theme-source-personal/locales/ga.json delete mode 100644 ghost-me/theme-source-personal/locales/gd.json delete mode 100644 ghost-me/theme-source-personal/locales/it.json delete mode 100644 ghost-me/theme-source-personal/locales/nl.json delete mode 100644 ghost-me/theme-source-personal/locales/pt-BR.json delete mode 100644 ghost-me/theme-source-personal/locales/sv.json delete mode 100644 ghost-me/theme-source-personal/locales/tr.json delete mode 100644 ghost-me/theme-source-personal/locales/uk.json delete mode 100644 ghost-me/theme-source-personal/locales/vi.json delete mode 100644 ghost-me/theme-source-personal/locales/zh-Hant.json delete mode 100644 ghost-me/theme-source-personal/locales/zh.json delete mode 100644 ghost-me/theme-source-personal/package.json delete mode 100644 ghost-me/theme-source-personal/page-inicio.hbs delete mode 100644 ghost-me/theme-source-personal/page.hbs delete mode 100644 ghost-me/theme-source-personal/partials/components/cta.hbs delete mode 100644 ghost-me/theme-source-personal/partials/components/featured.hbs delete mode 100644 ghost-me/theme-source-personal/partials/components/footer.hbs delete mode 100644 ghost-me/theme-source-personal/partials/components/header-content.hbs delete mode 100644 ghost-me/theme-source-personal/partials/components/header.hbs delete mode 100644 ghost-me/theme-source-personal/partials/components/navigation.hbs delete mode 100644 ghost-me/theme-source-personal/partials/components/post-list.hbs delete mode 100644 ghost-me/theme-source-personal/partials/email-subscription.hbs delete mode 100644 ghost-me/theme-source-personal/partials/feature-image.hbs delete mode 100644 ghost-me/theme-source-personal/partials/icons/arrow.hbs delete mode 100644 ghost-me/theme-source-personal/partials/icons/avatar.hbs delete mode 100644 ghost-me/theme-source-personal/partials/icons/bluesky.hbs delete mode 100644 ghost-me/theme-source-personal/partials/icons/burger.hbs delete mode 100644 ghost-me/theme-source-personal/partials/icons/checkmark.hbs delete mode 100644 ghost-me/theme-source-personal/partials/icons/close.hbs delete mode 100644 ghost-me/theme-source-personal/partials/icons/facebook.hbs delete mode 100644 ghost-me/theme-source-personal/partials/icons/fire.hbs delete mode 100644 ghost-me/theme-source-personal/partials/icons/instagram.hbs delete mode 100644 ghost-me/theme-source-personal/partials/icons/linkedin.hbs delete mode 100644 ghost-me/theme-source-personal/partials/icons/loader.hbs delete mode 100644 ghost-me/theme-source-personal/partials/icons/lock.hbs delete mode 100644 ghost-me/theme-source-personal/partials/icons/mastodon.hbs delete mode 100644 ghost-me/theme-source-personal/partials/icons/rss.hbs delete mode 100644 ghost-me/theme-source-personal/partials/icons/search.hbs delete mode 100644 ghost-me/theme-source-personal/partials/icons/threads.hbs delete mode 100644 ghost-me/theme-source-personal/partials/icons/tiktok.hbs delete mode 100644 ghost-me/theme-source-personal/partials/icons/x.hbs delete mode 100644 ghost-me/theme-source-personal/partials/icons/youtube.hbs delete mode 100644 ghost-me/theme-source-personal/partials/lightbox.hbs delete mode 100644 ghost-me/theme-source-personal/partials/post-card.hbs delete mode 100644 ghost-me/theme-source-personal/partials/search-toggle.hbs delete mode 100644 ghost-me/theme-source-personal/partials/typography/fonts.hbs delete mode 100644 ghost-me/theme-source-personal/partials/typography/mono.hbs delete mode 100644 ghost-me/theme-source-personal/partials/typography/sans.hbs delete mode 100644 ghost-me/theme-source-personal/partials/typography/serif.hbs delete mode 100644 ghost-me/theme-source-personal/post.hbs delete mode 100644 ghost-me/theme-source-personal/projects.hbs delete mode 100644 ghost-me/theme-source-personal/renovate.json delete mode 100644 ghost-me/theme-source-personal/tag.hbs delete mode 100644 ghost-me/theme-source-personal/yarn.lock delete mode 100644 ghost/.env.example delete mode 100644 ghost/README.md delete mode 100644 ghost/docker-compose.yml delete mode 100644 ghost/ops-gate/ops_gate.py delete mode 100644 ghost/theme-overrides/headline/default.hbs delete mode 100644 ghost/theme-overrides/headline/ops.hbs delete mode 100644 ghost/theme-overrides/headline/partials/loop-grid.hbs delete mode 100644 ghost/theme-overrides/headline/partials/loop-minimal.hbs delete mode 100644 ghost/theme-overrides/headline/post.hbs delete mode 100644 it/Dockerfile delete mode 100644 it/README.md delete mode 100644 it/docker-compose.yml delete mode 100644 memos/README.md delete mode 100644 memos/docker-compose.yml delete mode 100644 memos/proxy/server.js delete mode 100644 memos/stack.env delete mode 100644 onetimesecret/README.md delete mode 100644 onetimesecret/docker-compose.yml delete mode 100644 onetimesecret/stack.env delete mode 100644 pdf/README.md delete mode 100644 pdf/docker-compose.yml delete mode 100644 vikunja/README.md delete mode 100644 vikunja/docker-compose.yml delete mode 100644 vikunja/stack.env diff --git a/DEPRECATED-kasm/README.md b/DEPRECATED-kasm/README.md deleted file mode 100644 index 9295679..0000000 --- a/DEPRECATED-kasm/README.md +++ /dev/null @@ -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**. diff --git a/DEPRECATED-kasm/docker-compose.yml b/DEPRECATED-kasm/docker-compose.yml deleted file mode 100644 index 4da1d89..0000000 --- a/DEPRECATED-kasm/docker-compose.yml +++ /dev/null @@ -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 diff --git a/DEPRECATED-kasm/stack.env b/DEPRECATED-kasm/stack.env deleted file mode 100644 index 4b58f52..0000000 --- a/DEPRECATED-kasm/stack.env +++ /dev/null @@ -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 diff --git a/README.md b/README.md index 1188074..39d0302 100644 --- a/README.md +++ b/README.md @@ -82,10 +82,8 @@ Usa `docker compose --env-file .env -f /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=`). -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 DEPRECATED-` (preserva historial). +1. `docker compose --env-file .env down` desde la carpeta del stack. +2. Backup en `/opt/backups/removal--/` (`chmod 600`): datos de + `/opt/`, 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--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 `. +6. Quitar referencias en `backups/kopia/` y `n8n-observability/scripts/ops-report`. +7. Borrar los datos en `/opt/` y `git rm -r `. diff --git a/backups/kopia/README.md b/backups/kopia/README.md index eb22d56..68735f5 100644 --- a/backups/kopia/README.md +++ b/backups/kopia/README.md @@ -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 diff --git a/backups/kopia/backup-paths.txt b/backups/kopia/backup-paths.txt index 12ce41b..51ba920 100644 --- a/backups/kopia/backup-paths.txt +++ b/backups/kopia/backup-paths.txt @@ -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 diff --git a/backups/kopia/excludes.txt b/backups/kopia/excludes.txt index 9e9905a..cd6bb8d 100644 --- a/backups/kopia/excludes.txt +++ b/backups/kopia/excludes.txt @@ -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 diff --git a/backups/kopia/pre-backup-dumps.sh b/backups/kopia/pre-backup-dumps.sh index c3e9b94..d35e09f 100755 --- a/backups/kopia/pre-backup-dumps.sh +++ b/backups/kopia/pre-backup-dumps.sh @@ -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" diff --git a/beszel/README.md b/beszel/README.md deleted file mode 100644 index 5779b07..0000000 --- a/beszel/README.md +++ /dev/null @@ -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`. diff --git a/beszel/docker-compose.yml b/beszel/docker-compose.yml deleted file mode 100644 index 87c71b6..0000000 --- a/beszel/docker-compose.yml +++ /dev/null @@ -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 diff --git a/beszel/stack.env b/beszel/stack.env deleted file mode 100644 index 7929da7..0000000 --- a/beszel/stack.env +++ /dev/null @@ -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 diff --git a/bookstack/README.md b/bookstack/README.md deleted file mode 100644 index ee60a38..0000000 --- a/bookstack/README.md +++ /dev/null @@ -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 -``` diff --git a/bookstack/docker-compose.yml b/bookstack/docker-compose.yml deleted file mode 100644 index e6ce863..0000000 --- a/bookstack/docker-compose.yml +++ /dev/null @@ -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 diff --git a/bookstack/stack.env b/bookstack/stack.env deleted file mode 100644 index 6935805..0000000 --- a/bookstack/stack.env +++ /dev/null @@ -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 diff --git a/changedetection/README.md b/changedetection/README.md deleted file mode 100644 index 51d5c5d..0000000 --- a/changedetection/README.md +++ /dev/null @@ -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` diff --git a/changedetection/docker-compose.yml b/changedetection/docker-compose.yml deleted file mode 100644 index bd81e00..0000000 --- a/changedetection/docker-compose.yml +++ /dev/null @@ -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 diff --git a/crowdsec/README.md b/crowdsec/README.md index 685c969..84948b5 100644 --- a/crowdsec/README.md +++ b/crowdsec/README.md @@ -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) diff --git a/crowdsec/docker-compose.yml b/crowdsec/docker-compose.yml index 7e926b5..f349a14 100644 --- a/crowdsec/docker-compose.yml +++ b/crowdsec/docker-compose.yml @@ -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 diff --git a/crowdsec/metrics-export.sh b/crowdsec/metrics-export.sh deleted file mode 100755 index 14555ce..0000000 --- a/crowdsec/metrics-export.sh +++ /dev/null @@ -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 diff --git a/crowdsec/setup.sh b/crowdsec/setup.sh index d677aa3..3ba9f54 100755 --- a/crowdsec/setup.sh +++ b/crowdsec/setup.sh @@ -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..middlewares: "crowdsec-bouncer@file"' -echo "" -echo "Dashboard Grafana (requiere Authentik): https://${CROWDSEC_GRAFANA_DOMAIN}" diff --git a/crowdsec/stack.env b/crowdsec/stack.env index 8fc51bf..a1e8dc6 100644 --- a/crowdsec/stack.env +++ b/crowdsec/stack.env @@ -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 diff --git a/dozzle/README.md b/dozzle/README.md deleted file mode 100644 index 886dd1c..0000000 --- a/dozzle/README.md +++ /dev/null @@ -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. diff --git a/dozzle/docker-compose.yml b/dozzle/docker-compose.yml deleted file mode 100644 index 0d3d41e..0000000 --- a/dozzle/docker-compose.yml +++ /dev/null @@ -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 diff --git a/dozzle/stack.env b/dozzle/stack.env deleted file mode 100644 index 13bdf09..0000000 --- a/dozzle/stack.env +++ /dev/null @@ -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 diff --git a/finance/README.md b/finance/README.md deleted file mode 100644 index a1901c2..0000000 --- a/finance/README.md +++ /dev/null @@ -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` diff --git a/finance/docker-compose.yml b/finance/docker-compose.yml deleted file mode 100644 index ccd3f80..0000000 --- a/finance/docker-compose.yml +++ /dev/null @@ -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 diff --git a/ghost-me/.env.example b/ghost-me/.env.example deleted file mode 100644 index 549b125..0000000 --- a/ghost-me/.env.example +++ /dev/null @@ -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 " - -TRAEFIK_DOCKER_NETWORK=proxy -TRAEFIK_ENTRYPOINT_SECURE=websecure -TRAEFIK_CERTRESOLVER=letsencrypt diff --git a/ghost-me/README.md b/ghost-me/README.md deleted file mode 100644 index 9cd01b9..0000000 --- a/ghost-me/README.md +++ /dev/null @@ -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`. diff --git a/ghost-me/docker-compose.yml b/ghost-me/docker-compose.yml deleted file mode 100644 index 7896644..0000000 --- a/ghost-me/docker-compose.yml +++ /dev/null @@ -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 diff --git a/ghost-me/routes.yaml b/ghost-me/routes.yaml deleted file mode 100644 index d66d00f..0000000 --- a/ghost-me/routes.yaml +++ /dev/null @@ -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}/ diff --git a/ghost-me/theme-source-personal/LICENSE b/ghost-me/theme-source-personal/LICENSE deleted file mode 100644 index d24c76d..0000000 --- a/ghost-me/theme-source-personal/LICENSE +++ /dev/null @@ -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. diff --git a/ghost-me/theme-source-personal/README.md b/ghost-me/theme-source-personal/README.md deleted file mode 100644 index 05eb458..0000000 --- a/ghost-me/theme-source-personal/README.md +++ /dev/null @@ -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. - -  - -# 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/.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). diff --git a/ghost-me/theme-source-personal/assets/built/screen.css b/ghost-me/theme-source-personal/assets/built/screen.css deleted file mode 100644 index 3e06df5..0000000 --- a/ghost-me/theme-source-personal/assets/built/screen.css +++ /dev/null @@ -1 +0,0 @@ -:root{--color-white:#fff;--color-lighter-gray:rgba(0,0,0,.05);--color-light-gray:#e6e6e6;--color-mid-gray:#ccc;--color-dark-gray:#444;--color-darker-gray:#15171a;--color-black:#000;--color-primary-text:var(--color-darker-gray);--color-secondary-text:rgba(0,0,0,.55);--color-border:rgba(0,0,0,.08);--color-dark-border:rgba(0,0,0,.55);--font-sans:Inter,-apple-system,BlinkMacSystemFont,"Segoe UI","Roboto","Oxygen","Ubuntu","Cantarell","Fira Sans","Droid Sans","Helvetica Neue",sans-serif;--font-serif:"EB Garamond",Georgia,Times,serif;--font-serif-alt:Georgia,Times,serif;--font-mono:"JetBrains Mono",Menlo,Consolas,Monaco,"Liberation Mono","Lucida Console",monospace;--container-width:1320px;--container-gap:clamp(24px,1.7032rem + 1.9355vw,48px);--grid-gap:42px}:is(.gh-navigation,.gh-footer).has-accent-color,:root.has-light-text{--color-lighter-gray:hsla(0,0%,100%,.1);--color-darker-gray:#fff;--color-secondary-text:hsla(0,0%,100%,.64);--color-border:hsla(0,0%,100%,.15);--color-dark-border:hsla(0,0%,100%,.5)}*,:after,:before{box-sizing:border-box}*{margin:0}html{font-size:62.5%}body{line-height:1.6;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}canvas,img,picture,svg,video{display:block;height:auto;max-width:100%}iframe{display:block}button,input,select,textarea{font:inherit}h1,h2,h3,h4,h5,h6,p{overflow-wrap:break-word}h1,h2,h3,h4,h5,h6{line-height:1.2}sub,sup{line-height:1}body{background-color:var(--background-color);color:var(--color-primary-text);font-family:var(--gh-font-body,var(--font-sans));font-size:1.6rem}h1,h2,h3,h4,h5,h6{font-family:var(--gh-font-heading,var(--font-sans))}a{color:var(--color-darker-gray);text-decoration:none}a:hover{opacity:.8}.gh-button{align-items:center;background-color:var(--ghost-accent-color);border:0;border-radius:100px;color:var(--color-white);cursor:pointer;display:inline-flex;font-size:1.5rem;font-weight:600;gap:.4em;justify-content:center;letter-spacing:-.004em;line-height:1;padding:.8em 1.4em}.gh-button:hover{opacity:.95}.gh-icon-button{align-items:center;background-color:transparent;border:0;color:var(--color-darker-gray);cursor:pointer;display:inline-flex;height:32px;justify-content:center;outline:none;padding:0;width:32px}.gh-icon-button:hover :is(svg,span){opacity:.8}.gh-icon-button svg{height:20px;width:20px}.gh-form{align-items:center;background-color:var(--color-lighter-gray);border-radius:40px;display:flex;font-size:1.7rem;font-weight:450;height:56px;letter-spacing:-.008em;max-width:560px;position:relative;transition:background-color .2s ease;width:100%}.gh-form.success{pointer-events:none}.gh-form.error{box-shadow:0 0 0 1px red}.gh-form:hover{background-color:rgba(0,0,0,.065)}.gh-footer.has-accent-color .gh-form:hover,.has-light-text .gh-form:hover{background-color:hsla(0,0%,100%,.15)}.gh-form-input{background-color:transparent;border:0;border-radius:40px;font-size:inherit;height:100%;inset:0;letter-spacing:inherit;line-height:1.1;outline:none;padding-inline:26px;position:absolute;transition:.3s ease-in-out;width:100%}.gh-form-input::-moz-placeholder{color:rgba(0,0,0,.3)}.gh-form-input::placeholder,button.gh-form-input{color:rgba(0,0,0,.3)}:is(.has-serif-title,.has-mono-title):not([class*=" gh-font-heading"]):not([class^=gh-font-heading]) .gh-form-input,body[class*=" gh-font-heading"]:not(.gh-font-heading-fira-sans):not(.gh-font-heading-inter):not(.gh-font-heading-manrope):not(.gh-font-heading-noto-sans):not(.gh-font-heading-nunito):not(.gh-font-heading-poppins):not(.gh-font-heading-roboto):not(.gh-font-heading-space-grotesk) .gh-form-input,body[class^=gh-font-heading]:not(.gh-font-heading-fira-sans):not(.gh-font-heading-inter):not(.gh-font-heading-manrope):not(.gh-font-heading-noto-sans):not(.gh-font-heading-nunito):not(.gh-font-heading-poppins):not(.gh-font-heading-roboto):not(.gh-font-heading-space-grotesk) .gh-form-input{padding-inline:20px}.gh-form.gh-form.success .gh-form-input{opacity:.5}.gh-footer.has-accent-color .gh-form-input,.has-light-text .gh-form-input{color:#fff}.gh-footer.has-accent-color .gh-form-input::-moz-placeholder,.has-light-text .gh-form-input::-moz-placeholder{color:hsla(0,0%,100%,.55)}.gh-footer.has-accent-color .gh-form-input::placeholder,.has-light-text .gh-form-input::placeholder,.has-light-text button.gh-form-input{color:hsla(0,0%,100%,.55)}.gh-header.is-classic.has-image .gh-form-input{color:#15171a}.gh-header.is-classic.has-image .gh-form-input::-moz-placeholder{color:rgba(0,0,0,.5)}.gh-header.is-classic.has-image .gh-form-input::placeholder,.gh-header.is-classic.has-image .gh-form>svg,.gh-header.is-classic.has-image button.gh-form-input{color:rgba(0,0,0,.5)}button.gh-form-input{color:var(--color-secondary-text);cursor:pointer;padding-inline-start:56px;text-align:left}:is(.has-serif-title,.has-mono-title):not([class*=" gh-font-heading"]):not([class^=gh-font-heading]) button.gh-form-input,body[class*=" gh-font-heading"]:not(.gh-font-heading-fira-sans):not(.gh-font-heading-inter):not(.gh-font-heading-manrope):not(.gh-font-heading-noto-sans):not(.gh-font-heading-nunito):not(.gh-font-heading-poppins):not(.gh-font-heading-roboto):not(.gh-font-heading-space-grotesk) button.gh-form-input,body[class^=gh-font-heading]:not(.gh-font-heading-fira-sans):not(.gh-font-heading-inter):not(.gh-font-heading-manrope):not(.gh-font-heading-noto-sans):not(.gh-font-heading-nunito):not(.gh-font-heading-poppins):not(.gh-font-heading-roboto):not(.gh-font-heading-space-grotesk) button.gh-form-input{padding-inline-start:50px}.gh-form .gh-button{font-size:inherit;height:44px;min-width:132px;padding-inline:24px;position:absolute;right:6px}.gh-form>svg{color:var(--color-secondary-text);height:20px;left:22px;position:relative;width:20px}:is(.has-serif-title,.has-mono-title):not([class*=" gh-font-heading"]):not([class^=gh-font-heading]) .gh-form>svg{left:16px}.gh-form .gh-button svg{display:none;margin-top:1px;position:absolute}.gh-form .gh-button>span:last-child{display:none}.gh-form:is(.loading,.success) .gh-button>span:first-child{visibility:hidden}.gh-form.loading .gh-button svg:first-of-type{display:block}.gh-form.success .gh-button>span:first-child{display:none}.gh-form.success .gh-button>span:last-child{display:inline}.gh-form [data-members-error]{font-size:1.4rem;line-height:1.4;margin-top:10px;position:absolute;top:100%;width:100%}@media (max-width:576px){.gh-form{font-size:1.6rem}.gh-form .gh-button{min-width:104px;padding-inline:12px}}.gh-viewport{display:flex;flex-direction:column;min-height:100vh;position:relative}.gh-outer{padding:0 max(4vmin,20px)}.gh-outer .gh-outer{padding:0}.gh-inner{margin:0 auto;max-width:var(--container-width);width:100%}.gh-main{flex-grow:1}.gh-canvas,.kg-width-full.kg-content-wide{--main:min(var(--content-width,720px),100% - var(--container-gap) * 2);--wide:minmax(0,calc((var(--container-width, 1200px) - var(--content-width, 720px))/2));--full:minmax(var(--container-gap),1fr);display:grid;grid-template-columns:[full-start] var(--full) [wide-start] var(--wide) [main-start] var(--main) [main-end] var(--wide) [wide-end] var(--full) [full-end]}.gh-canvas>*{grid-column:main}.kg-content-wide>div,.kg-width-wide{grid-column:wide}.kg-width-full{grid-column:full}.gh-navigation{background-color:var(--background-color);color:var(--color-darker-gray);font-size:1.5rem;font-weight:550;height:100px}.gh-navigation :is(.gh-navigation-logo,a:not(.gh-button),.gh-icon-button){color:inherit}.gh-navigation-inner{align-items:center;-moz-column-gap:24px;column-gap:24px;display:grid;grid-auto-flow:row dense;height:100%}.gh-navigation-brand{line-height:1}.gh-navigation-logo{font-family:var(--gh-font-heading,var(--font-sans));font-size:calc(2.4rem*var(--factor, 1));font-weight:725;letter-spacing:-.015em;position:relative;white-space:nowrap}.gh-navigation-logo img{max-height:40px}.gh-navigation-menu{align-items:center;display:flex;gap:24px}.gh-navigation .nav{align-items:center;display:inline-flex;gap:28px;list-style:none;margin:0;padding:0;white-space:nowrap}.gh-navigation .gh-more-toggle{font-size:inherit;margin:0 -6px;position:relative;text-transform:inherit}.gh-navigation .gh-more-toggle svg{height:24px;width:24px}.gh-navigation-actions{align-items:center;background-color:var(--background-color);display:flex;gap:24px;justify-content:flex-end}.gh-navigation.has-accent-color .gh-navigation-actions{background-color:var(--ghost-accent-color)}.gh-navigation-members{align-items:center;display:flex;gap:20px;white-space:nowrap}.gh-navigation-members .gh-button{font-size:inherit;font-weight:600}.gh-search{margin-left:-2px;margin-right:-2px}@media (max-width:767px){.gh-navigation-logo{white-space:normal}.gh-navigation-members{flex-direction:column-reverse;gap:16px;width:100%}.gh-navigation-actions .gh-search{display:none}}@media (min-width:768px){.gh-navigation-brand .gh-search{display:none}.gh-navigation:not(.is-dropdown-loaded) .gh-navigation-menu .nav>li{opacity:0}}.gh-navigation.has-accent-color{background-color:var(--ghost-accent-color)}.gh-navigation.has-accent-color .gh-button{background-color:#fff;color:#15171a}.gh-navigation.is-left-logo .gh-navigation-inner{grid-template-columns:auto 1fr auto}@media (min-width:768px){.gh-navigation.is-left-logo .gh-navigation-logo:not(:has(img)){top:-2px}}@media (min-width:992px){.gh-navigation.is-left-logo .gh-navigation-menu{margin-left:16px;margin-right:100px}}.gh-navigation.is-middle-logo .gh-navigation-inner{grid-template-columns:1fr auto 1fr}.gh-navigation.is-middle-logo .gh-navigation-brand{grid-column-start:2}.gh-navigation.is-middle-logo .gh-navigation-actions{gap:28px}@media (min-width:992px){.gh-navigation.is-middle-logo .gh-navigation-menu{margin-right:64px}}.gh-navigation.is-stacked{height:auto;position:relative}.gh-navigation.is-stacked .gh-navigation-inner{grid-template-columns:1fr auto 1fr}.gh-navigation.is-stacked .gh-navigation-brand{align-items:center;display:flex;grid-column-start:2;grid-row-start:1;min-height:80px}@media (max-width:767px){.gh-navigation.is-stacked .gh-navigation-brand{min-height:unset}}@media (min-width:992px){.gh-navigation.is-stacked .gh-navigation-inner{padding:0}.gh-navigation.is-stacked .gh-navigation-brand{align-items:center;display:flex;height:80px}.gh-navigation.is-stacked .gh-navigation-menu{grid-column:1/4;grid-row-start:2;height:60px;justify-content:center;margin:0 48px}.gh-navigation.is-stacked .gh-navigation-menu:after,.gh-navigation.is-stacked .gh-navigation-menu:before{background-color:var(--color-border);content:"";height:1px;left:0;position:absolute;top:80px;width:100%}.gh-navigation.is-stacked .gh-navigation-menu:after{top:140px}.gh-navigation.is-stacked .gh-navigation-actions{grid-column:1/4;grid-row-start:1;justify-content:space-between}.gh-navigation.is-stacked .gh-navigation-actions .gh-search{display:flex;gap:10px;width:auto}}.gh-dropdown{background-color:#fff;border-radius:5px;box-shadow:0 0 0 1px rgba(0,0,0,.04),0 7px 20px -5px rgba(0,0,0,.15);margin-top:24px;opacity:0;padding:12px 0;position:absolute;right:-16px;text-align:left;top:100%;transform:translate3d(0,6px,0);transition:opacity .3s,transform .2s;visibility:hidden;width:200px;z-index:90}.gh-dropdown.is-left{left:-16px;right:auto}.is-dropdown-mega .gh-dropdown{-moz-column-gap:40px;column-gap:40px;display:grid;grid-auto-flow:column;grid-template-columns:1fr 1fr;padding:20px 32px;width:auto}.is-dropdown-open .gh-dropdown{opacity:1;transform:translateY(0);visibility:visible}.gh-dropdown li a{color:#15171a!important;display:block;line-height:1.5;padding:7px 20px;white-space:normal}.is-dropdown-mega .gh-dropdown li a{padding:8px 0}.gh-burger{align-items:center;display:flex;display:none;justify-content:center;margin-left:4px;margin-right:-7px;position:relative}.gh-burger svg{height:24px;width:24px}.gh-burger svg:last-child,.is-open .gh-burger svg:first-child{display:none}.is-open .gh-burger svg:last-child{display:block}@media (max-width:767px){.gh-burger{display:flex}#gh-navigation{height:64px}#gh-navigation .gh-navigation-inner{gap:48px;grid-template-columns:1fr;grid-template-rows:auto 1fr auto}#gh-navigation .gh-navigation-brand{align-items:center;display:grid;grid-column-start:1;grid-template-columns:1fr auto auto;height:64px}#gh-navigation .gh-navigation-logo{font-size:2.2rem}#gh-navigation .gh-navigation-actions,#gh-navigation .gh-navigation-menu{justify-content:center;opacity:0;position:fixed;visibility:hidden}#gh-navigation .gh-navigation-menu{transform:translateY(0);transition:none}#gh-navigation .nav{align-items:center;gap:20px;line-height:1.4}#gh-navigation .nav a{font-size:1.75rem;font-weight:600;text-transform:none}#gh-navigation .nav li{opacity:0;transform:translateY(-4px)}#gh-navigation .gh-navigation-actions{text-align:center}#gh-navigation :is(.gh-button,a[data-portal=signin]){opacity:0;transform:translateY(8px)}#gh-navigation .gh-button{font-size:1.75rem;text-transform:none;width:100%}#gh-navigation a[data-portal=signin]{font-size:1.75rem}#gh-main{transition:opacity .4s}.is-open#gh-navigation{height:100%;inset:0;overflow-y:scroll;position:fixed;z-index:3999999;-webkit-overflow-scrolling:touch}.is-open#gh-navigation .gh-navigation-actions,.is-open#gh-navigation .gh-navigation-menu{opacity:1;position:static;visibility:visible}.is-open#gh-navigation .nav{display:flex;flex-direction:column}.is-open#gh-navigation .nav li{opacity:1;transform:translateY(0);transition:transform .2s,opacity .2s}.is-open#gh-navigation .gh-navigation-actions{align-items:center;bottom:0;display:inline-flex;flex-direction:column;gap:12px;left:0;padding:var(--container-gap) 0;position:sticky;right:0}.is-open#gh-navigation :is(.gh-button,a[data-portal=signin]){opacity:1;transform:translateY(0);transition:transform .4s,opacity .4s;transition-delay:.2s}.is-open#gh-navigation a[data-portal=signin]{transition-delay:.4s}.is-open#gh-main{opacity:0}}.gh-card{position:relative}.gh-card-link{display:flex;flex-direction:column;gap:20px}.gh-card-link:hover{opacity:1}.gh-card-image{aspect-ratio:16/9;flex-shrink:0;position:relative}.gh-card-image img{height:100%;inset:0;-o-object-fit:cover;object-fit:cover;position:absolute;width:100%}.gh-card-wrapper{flex-grow:1}.gh-card-tag{display:none;font-size:1.2rem;font-weight:500;letter-spacing:.01em;margin-bottom:4px;text-transform:uppercase}.gh-card-title{font-size:calc(1.9rem*var(--factor, 1));font-weight:725;letter-spacing:-.014em;line-height:1.3}.gh-card-link:hover .gh-card-title{opacity:.8}.gh-card-excerpt{display:-webkit-box;font-size:1.45rem;-webkit-line-clamp:2;line-height:1.4;margin-top:8px;overflow-y:hidden;-webkit-box-orient:vertical}.gh-card-meta{align-items:center;color:var(--color-secondary-text);font-size:1.25rem;font-weight:500;letter-spacing:-.004em;line-height:1.4;padding-bottom:1px}.gh-card-meta svg{display:inline-block;height:12px;margin-right:2px;position:relative;top:1px;width:12px}.gh-card-meta:not(:empty){margin-top:8px}.gh-card-author+.gh-card-date:before{content:"—";margin-right:4px}.gh-header{margin-top:40px;position:relative}.gh-header-inner{overflow:hidden;position:relative}.gh-header.is-magazine .gh-header-inner{display:grid;gap:var(--grid-gap);grid-template-columns:repeat(16,1fr)}.gh-header.is-magazine .gh-header-inner>div{display:flex;flex-direction:column;gap:var(--grid-gap);grid-row:1}.gh-header.is-magazine .gh-header-left{grid-column:1/span 4}.gh-header.is-magazine .gh-header-inner>.gh-card{grid-column:5/span 8;position:relative}.gh-header.is-magazine .gh-header-right{grid-column:13/-1}.gh-header.is-magazine .gh-header-inner>.gh-card .gh-card-link{gap:28px}.gh-header.is-magazine .gh-header-inner>.gh-card .gh-card-image{aspect-ratio:1.618033}.gh-header.is-magazine .gh-header-inner>.gh-card .gh-card-tag{display:block;margin-bottom:12px}.gh-header.is-magazine .gh-header-inner>.gh-card .gh-card-title{font-size:clamp(2.8rem,1.36vw + 2.25rem,4rem);font-weight:700;letter-spacing:-.022em;line-height:1.1}.gh-header.is-magazine .gh-header-inner>.gh-card .gh-card-excerpt{font-size:1.8rem;letter-spacing:-.02em;margin-top:clamp(12px,.45vw + 10.18px,16px);max-width:90%}.gh-header.is-magazine .gh-header-inner>.gh-card .gh-card-meta:not(:empty){margin-top:14px}.gh-header.is-magazine :is(.gh-header-left,.gh-header-right) .gh-card:last-child .gh-card-image{display:none}.gh-header.is-magazine .gh-header-inner>div .gh-card-excerpt{display:none}.gh-header.is-magazine .gh-header-inner>.gh-card:after,.gh-header.is-magazine .gh-header-inner>.gh-card:before{background-color:var(--color-border);content:"";height:100%;left:calc(var(--grid-gap)/-2);position:absolute;top:0;width:1px}.gh-header.is-magazine .gh-header-inner>.gh-card:after{left:auto;right:calc(var(--grid-gap)/-2)}.gh-header.is-magazine .gh-header-inner>div .gh-card+.gh-card:before{background-color:var(--color-border);content:"";height:1px;left:0;position:absolute;top:calc(var(--grid-gap)/-2);width:100%}@media (max-width:1199px){.gh-header.is-magazine .gh-header-inner{grid-template-columns:repeat(12,1fr)}.gh-header.is-magazine .gh-header-inner>.gh-card{grid-column:1/span 8}.gh-header.is-magazine .gh-header-left{grid-column:9/-1}.gh-header.is-magazine .gh-header-inner>div.gh-header-right{flex-direction:row;grid-column:1/-1;grid-row:2}.gh-header.is-magazine .gh-header-right .gh-card{flex:1}.gh-header.is-magazine .gh-header-right .gh-card:last-child .gh-card-image{display:block}.gh-header.is-magazine .gh-header-right{position:relative}.gh-header.is-magazine .gh-header-right:before{background-color:var(--color-border);content:"";height:1px;left:0;position:absolute;top:calc(var(--grid-gap)/-2);width:100%}.gh-header.is-magazine .gh-header-right .gh-card:before{display:none}.gh-header.is-magazine .gh-header-right .gh-card:after{background-color:var(--color-border);content:"";height:100%;position:absolute;right:calc(var(--grid-gap)/-2);top:0;width:1px}}@media (max-width:991px){.gh-header.is-magazine .gh-header-left .gh-card:nth-child(2) .gh-card-image{display:none}}@media (max-width:767px){.gh-header.is-magazine .gh-header-inner{display:flex;flex-direction:column;gap:var(--grid-gap)}.gh-header.is-magazine .gh-header-inner>div.gh-header-right{flex-direction:column}.gh-header.is-magazine .gh-card-image{display:block!important}.gh-header.is-magazine .gh-card:before{background-color:var(--color-border);content:"";display:block!important;height:1px;left:0;position:absolute;top:calc(var(--grid-gap)/-2);width:100%}}.gh-header.is-highlight .gh-header-inner{display:grid;gap:var(--grid-gap);grid-template-columns:repeat(16,1fr)}.gh-header.is-highlight .gh-header-left{grid-column:span 8;position:relative}.gh-header.is-highlight .gh-header-left .gh-card{grid-column:span 8;grid-row:span 3}.gh-header.is-highlight .gh-header-left .gh-card .gh-card-link{gap:28px}.gh-header.is-highlight .gh-header-left .gh-card .gh-card-image{aspect-ratio:3/2}.gh-header.is-highlight .gh-header-left .gh-card .gh-card-tag{display:block;margin-bottom:12px}.gh-header.is-highlight .gh-header-left .gh-card .gh-card-title{font-size:clamp(2.8rem,1.36vw + 2.25rem,4rem);font-weight:700;letter-spacing:-.022em;line-height:1.1}.gh-header.is-highlight .gh-header-left .gh-card .gh-card-excerpt{font-size:1.8rem;margin-top:clamp(12px,.45vw + 10.18px,16px)}.gh-header.is-highlight .gh-header-left .gh-card .gh-card-meta:not(:empty){margin-top:12px}.gh-header.is-highlight .gh-header-middle{display:flex;flex-direction:column;gap:var(--grid-gap);grid-column:9/span 4;position:relative}.gh-header.is-highlight .gh-header-middle .gh-card .gh-card-excerpt,.gh-header.is-highlight .gh-header-middle .gh-card:last-child .gh-card-image{display:none}.gh-header.is-highlight .gh-header-right{grid-column:13/-1}.gh-header.is-highlight .gh-featured{margin-top:0;padding:0}.gh-header.is-highlight .gh-featured-feed{display:flex;flex-direction:column}.gh-header.is-highlight .gh-featured-feed .gh-card-title{font-size:clamp(1.4rem,.23vw + 1.31rem,1.6rem)}.gh-header.is-highlight .gh-header-left:after,.gh-header.is-highlight .gh-header-middle:after{background-color:var(--color-border);content:"";height:100%;position:absolute;right:calc(var(--grid-gap)/-2);top:0;width:1px}.gh-header.is-highlight .gh-card:before{background-color:var(--color-border);content:"";height:1px;left:0;position:absolute;top:calc(var(--grid-gap)/-2);width:100%}.gh-header.is-highlight .gh-featured .gh-card:first-child:before{display:none}@media (max-width:1199px){.gh-header.is-highlight .gh-header-inner{grid-template-columns:repeat(9,1fr)}.gh-header.is-highlight .gh-header-left{grid-column:span 6}.gh-header.is-highlight .gh-header-middle{grid-column:7/-1}.gh-header.is-highlight .gh-header-right{grid-column:1/-1}.gh-header.is-highlight .gh-featured-feed{display:grid;grid-template-columns:repeat(3,1fr)}.gh-header.is-highlight .gh-featured-feed .gh-card:before{width:calc(100% + var(--grid-gap))}.gh-header.is-highlight .gh-featured-feed .gh-card:after{background-color:var(--color-border);content:"";height:100%;left:calc(var(--grid-gap)/-2);position:absolute;top:0;width:1px}}@media (max-width:991px){.gh-header.is-highlight .gh-header-middle .gh-card:nth-child(2) .gh-card-image{display:none}}@media (max-width:767px){.gh-header.is-highlight .gh-header-inner{display:flex;flex-direction:column}.gh-header.is-highlight .gh-featured-feed{display:flex}.gh-header.is-highlight .gh-card-image{display:block!important}.gh-header.is-highlight .gh-header-middle .gh-card .gh-card-excerpt{display:-webkit-box}}.gh-header.is-classic{display:flex;margin-top:0;padding-block:160px}.gh-header.is-classic .gh-header-inner{align-items:center;display:flex;flex-direction:column;gap:48px;margin:auto;max-width:1020px;overflow:unset;text-align:center}.gh-header.is-classic .gh-header-title{font-size:calc(clamp(3rem, 1.82vw + 2.27rem, 4.6rem)*var(--factor, 1));letter-spacing:-.028em;line-height:1.1}.gh-header.is-classic.has-image{margin-top:0}.gh-header.is-classic.has-image:before{background-color:var(--color-black);content:"";inset:0;opacity:.3;position:absolute;transition:opacity 1.5s ease}.gh-header.is-classic.has-image .gh-header-inner{color:#fff}.gh-header.is-classic.has-image .gh-header-image{height:100%;inset:0;-o-object-fit:cover;object-fit:cover;position:absolute;width:100%;z-index:-1}.gh-header.is-classic.has-image .gh-form{background-color:#fff}.gh-cta{display:none;margin-top:max(4vw,40px)}.gh-header:is(.is-highlight,.is-magazine)+.gh-cta{display:block}.gh-cta-inner{align-items:center;background-color:var(--color-lighter-gray);display:flex;flex-direction:column;gap:36px;overflow:hidden;padding:clamp(48px,3.64vw + 33.45px,80px) clamp(40px,2.73vw + 29.09px,64px);position:relative;text-align:center}.gh-cta-content{max-width:640px}.gh-cta-title{font-size:clamp(2.8rem,1.36vw + 2.25rem,4rem);font-weight:700;letter-spacing:-.021em;line-height:1.1}.gh-cta-description{font-size:1.8rem;letter-spacing:-.015em;line-height:1.4;margin-top:12px}.gh-featured{margin-top:100px}.gh-navigation+.gh-featured{margin-top:64px}.gh-header.is-classic:not(.has-image)+.gh-featured{margin-top:0}.gh-featured-inner{overflow:hidden}.gh-featured-title{border-bottom:1px solid var(--color-border);font-size:1.2rem;font-weight:550;letter-spacing:.025em;margin-bottom:20px;padding-bottom:12px;text-transform:uppercase}.gh-featured-feed{display:grid;gap:var(--grid-gap);grid-template-columns:repeat(4,1fr)}.gh-featured-feed .gh-card{container-name:featured-card;container-type:inline-size}.gh-featured-feed .gh-card-link{align-items:flex-start;display:flex;flex-direction:row-reverse}.gh-featured-feed .gh-card-image{aspect-ratio:1;width:72px}@container featured-card (width < 240px){.gh-featured-feed .gh-card-image{display:none}}@container featured-card (240px <= width <= 270px){.gh-featured-feed .gh-card-image{width:64px}}.gh-featured-feed .gh-card-wrapper{container-name:featured-card-wrapper;container-type:inline-size}.gh-featured-feed .gh-card-title{font-size:1.6rem;font-weight:650;letter-spacing:-.011em}@container featured-card-wrapper (width < 170px){.gh-featured-feed .gh-card-title{font-size:1.6rem}}.gh-featured-feed .gh-card-excerpt{display:none}.gh-featured-feed .gh-card-meta:not(:empty){margin-top:8px}.gh-featured-feed .gh-card:before{background-color:var(--color-border);content:"";height:100%;left:calc(var(--grid-gap)/-2);position:absolute;top:0;width:1px}@media (max-width:1199px){.gh-viewport>.gh-featured .gh-featured-feed{grid-template-columns:repeat(3,1fr)}.gh-viewport>.gh-featured .gh-featured-feed .gh-card:nth-child(4){display:none}}@media (max-width:767px){.gh-viewport>.gh-featured .gh-featured-feed{display:flex;flex-direction:column}}.gh-container{flex-grow:1;margin-top:64px}.gh-container-inner{-moz-column-gap:var(--grid-gap);column-gap:var(--grid-gap);display:grid;grid-template-columns:repeat(16,1fr)}:is(.gh-featured,.gh-cta)+.gh-container{margin-top:max(4vw,40px)}.gh-header.is-classic:not(.has-image)+.gh-container{margin-top:0}.gh-navigation+.gh-container .gh-container-title,:is(.paged,.tag-template,.author-template) .gh-container:not(.has-sidebar) .gh-container-title{display:none}.gh-more{display:none;font-size:calc(1.9rem*var(--factor, 1));font-weight:725;grid-column:1/-1;letter-spacing:-.014em;margin-top:48px}.gh-container.has-sidebar .gh-more{grid-column:span 12}.home-template .gh-feed:has(>:nth-child(12):last-child)~.gh-more{display:block}.gh-more a{align-items:center;display:flex;gap:4px}.gh-more svg{height:18px;margin-top:-1px;width:18px}.has-serif-title:not([class*=" gh-font-heading"]):not([class^=gh-font-heading]) .gh-more svg{margin-top:0}.gh-container.has-sidebar .gh-main{grid-column:1/span 12;position:relative}.gh-container.has-sidebar .gh-sidebar{grid-column:13/-1}.gh-container.has-sidebar .gh-main:after{background-color:var(--color-border);content:"";height:100%;position:absolute;right:calc(var(--grid-gap)/-2);top:0;width:1px}.gh-container:not(.has-sidebar) :is(.gh-container-title,.gh-main,.gh-more){grid-column:3/span 12}.gh-container.is-list.no-image:not(.has-sidebar) :is(.gh-container-title,.gh-main,.gh-more){grid-column:4/span 10}.gh-header:is(.is-highlight,.is-magazine)~.gh-container.is-grid:not(.has-sidebar) :is(.gh-container-title,.gh-main,.gh-more){grid-column:1/-1}@media (max-width:1199px){.gh-container-inner{display:block;overflow:hidden}.gh-container.has-sidebar .gh-sidebar{display:none}}.gh-container-title{border-bottom:1px solid var(--color-border);font-size:1.2rem;font-weight:550;grid-column:1/-1;letter-spacing:.025em;margin-bottom:calc(var(--grid-gap)/2);padding-bottom:12px;text-transform:uppercase}.gh-container:not(:has(.gh-card)) .gh-container-title{display:none}.gh-container .gh-feed{gap:var(--grid-gap)}.gh-container .gh-card-meta:not(:empty){margin-top:12px}.gh-container.is-list .gh-feed{container-name:list-feed;container-type:inline-size;display:flex;flex-direction:column}.gh-container.is-list .gh-card-link{align-items:center;flex-direction:row;gap:24px}.gh-container.is-list .no-image .gh-card-link{padding-block:20px}.gh-container.is-list .gh-card-image{aspect-ratio:1.618033;flex-shrink:0;width:220px}@container list-feed (width < 600px){.gh-container.is-list .gh-card-image{width:160px}}.gh-container.is-list .gh-card:not(.no-image) .gh-card-wrapper{max-width:600px}.gh-container.is-list .gh-card-title{--factor:1.05}.gh-container.is-list .no-image .gh-card-title{--factor:1.2}.gh-container.is-list .gh-card-excerpt{margin-top:6px}.gh-container.is-list .gh-card+.gh-card:before{background-color:var(--color-border);content:"";height:1px;left:0;position:absolute;top:calc(var(--grid-gap)/-2);width:100%}.home-template .gh-container.is-list .gh-card:first-child:before{display:none}@media (max-width:767px){.gh-container.is-list .gh-card-link{align-items:flex-start;flex-direction:column}.gh-container.is-list .gh-card-image{width:100%}}.gh-container.is-grid .gh-feed{display:grid;grid-template-columns:repeat(auto-fit,minmax(248px,1fr));overflow:hidden;row-gap:calc(var(--grid-gap)*1.5)}.gh-container.is-grid .gh-card:before{height:1px;right:calc(var(--grid-gap)/-2);top:calc(var(--grid-gap)/-2)}.gh-container.is-grid .gh-card:after,.gh-container.is-grid .gh-card:before{background-color:var(--color-border);content:"";left:calc(var(--grid-gap)/-2);position:absolute}.gh-container.is-grid .gh-card:after{height:100%;top:0;width:1px}@media (max-width:767px){.gh-container.is-grid .gh-feed{grid-template-columns:repeat(2,1fr)}}@media (max-width:576px){.gh-container.is-grid .gh-feed{display:flex;flex-direction:column}}.gh-container.is-list.no-image .gh-card-image{display:none}.gh-sidebar-inner{position:sticky;top:calc(var(--grid-gap)/2)}.gh-sidebar-title{border-bottom:1px solid var(--color-border);font-size:1.2rem;font-weight:550;grid-column:1/-1;letter-spacing:.025em;margin-bottom:calc(var(--grid-gap)/2);padding-bottom:12px;text-transform:uppercase}.gh-about{align-items:center;background-color:var(--color-lighter-gray);display:flex;flex-direction:column;justify-content:center;min-height:380px;padding:48px 24px;text-align:center}.gh-about-icon{border-radius:50%;height:60px;margin-bottom:24px;width:60px}.gh-about-title{font-size:calc(2.4rem*var(--factor, 1));font-weight:700;letter-spacing:-.019em}.gh-about-description{font-size:1.45rem;line-height:1.4;margin-top:12px}.gh-about .gh-button{margin-top:32px}.gh-recommendations{margin-top:48px}.gh-recommendations .recommendations{display:flex;flex-direction:column;gap:26px;list-style-type:none;margin:0;padding:0}.gh-recommendations .recommendation:first-child{margin-top:4px}.gh-recommendations .recommendation a{display:grid;gap:4px 12px;grid-template-columns:24px auto}.gh-recommendations .recommendation a:hover{opacity:1}.gh-recommendations .recommendation-favicon{border-radius:4px;grid-row:span 2;width:100%}.gh-recommendations .recommendation-title{font-size:1.5rem;font-weight:650;letter-spacing:-.009em;margin-top:-2px}.gh-recommendations .recommendation a:hover .recommendation-title{opacity:.8}.gh-recommendations .recommendation-url{color:var(--color-secondary-text);font-size:1.4rem;line-height:1.25;order:1;overflow:hidden;text-overflow:ellipsis}.gh-recommendations .recommendation-description{color:var(--color-secondary-text);display:-webkit-box;display:none;font-size:1.4rem;grid-column:2;-webkit-line-clamp:2;line-height:1.4;overflow-y:hidden;-webkit-box-orient:vertical}.gh-recommendations button{align-items:center;background-color:transparent;border:0;color:var(--color-darker-gray);cursor:pointer;display:inline-flex;font-size:1.2rem;font-weight:550;gap:2px;letter-spacing:.025em;margin-top:24px;padding:0;text-transform:uppercase}.gh-recommendations button svg{margin-top:-1px;width:12px}.gh-recommendations button:hover{opacity:.8}.gh-article{--container-width:1120px;word-break:break-word}.gh-article-header{margin:clamp(40px,3.64vw + 25.45px,72px) 0 40px}.gh-article-tag{color:var(--ghost-accent-color);font-size:1.3rem;font-weight:500;letter-spacing:.01em;margin-bottom:12px;text-transform:uppercase}.gh-article-title{font-size:calc(clamp(3.4rem, 1.36vw + 2.85rem, 4.6rem)*var(--factor, 1));letter-spacing:-.022em;line-height:1.1}.gh-article-excerpt{font-size:clamp(1.7rem,.23vw + 1.61rem,1.9rem);letter-spacing:-.018em;line-height:1.45;margin-top:clamp(12px,.45vw + 10.18px,16px);max-width:720px}.gh-article-meta{align-items:center;display:flex;gap:8px;margin-left:6px}.gh-article-author-image{display:flex;margin-right:8px}.gh-article-author-image a{background-color:var(--color-light-gray);border:3px solid var(--background-color);border-radius:50%;height:56px;margin:0 -8px;overflow:hidden;position:relative;width:56px}.gh-article-author-image a:first-child{z-index:10}.gh-article-author-image a:nth-child(2){z-index:9}.gh-article-author-image a:nth-child(3){z-index:8}.gh-article-author-image :is(img,svg){height:100%;inset:0;-o-object-fit:cover;object-fit:cover;position:absolute;width:100%}.gh-article-meta-wrapper{display:flex;flex-direction:column;gap:4px}.gh-article-author-name{font-size:1.6rem;font-weight:650;letter-spacing:-.013em}.gh-article-meta-content{color:var(--color-secondary-text);font-size:1.35rem;font-weight:500;letter-spacing:-.006em;line-height:1.2}.gh-article-image{grid-column:wide;margin-top:40px}.gh-article-image img{width:100%}.page-template .gh-article-header{margin-block:72px 32px}.page-template .gh-content:only-child>:first-child:not(.kg-width-full){margin-top:64px}.page-template .gh-content>:last-child:not(.kg-width-full){margin-bottom:6vw}.page-template .gh-footer{margin-top:0}.gh-content{font-size:var(--content-font-size,1.7rem);letter-spacing:-.01em}.gh-content>*+*{margin-bottom:0;margin-top:calc(28px*var(--content-spacing-factor, 1))}.gh-content>.kg-width-full+.kg-width-full:not(.kg-width-full.kg-card-hascaption+.kg-width-full){margin-top:0}.gh-content>[id]:not(:first-child){margin-top:calc(56px*var(--content-spacing-factor, 1))}.post-template .gh-content.drop-cap>p:first-of-type:first-letter{float:left;font-size:3.1em;font-weight:700;line-height:1;margin:0 .2em 0 -1px}.has-serif-body.post-template:not([class*=" gh-font-body"]):not([class^=gh-font-body]) .gh-content.drop-cap>p:first-of-type:first-letter{font-size:3.2em}.gh-content>[id]+p{margin-top:calc(12px*var(--content-spacing-factor, 1))}.gh-content>:is(hr,blockquote,iframe){margin-top:calc(48px*var(--content-spacing-factor, 1))!important;position:relative}.gh-content>:is(hr,blockquote,iframe)+*{margin-top:calc(48px*var(--content-spacing-factor, 1))!important}.gh-content h1{font-size:calc(2.2em*var(--factor, 1));letter-spacing:-.02em}.gh-content h2{font-size:calc(1.6em*var(--factor, 1));letter-spacing:-.02em}.gh-content h3{font-size:calc(1.3em*var(--factor, 1));letter-spacing:-.017em}.gh-content a{color:var(--ghost-accent-color);text-decoration:underline}.gh-content .kg-callout-card .kg-callout-text,.gh-content .kg-toggle-card .kg-toggle-content>:is(ul,ol,p){font-size:.95em}.has-serif-body:not([class*=" gh-font-body"]):not([class^=gh-font-body]) .gh-content .kg-callout-text,.has-serif-body:not([class*=" gh-font-body"]):not([class^=gh-font-body]) .gh-content .kg-toggle-content>ol,.has-serif-body:not([class*=" gh-font-body"]):not([class^=gh-font-body]) .gh-content .kg-toggle-content>p,.has-serif-body:not([class*=" gh-font-body"]):not([class^=gh-font-body]) .gh-content .kg-toggle-content>ul,.has-serif-body:not([class*=" gh-font-body"]):not([class^=gh-font-body]) .gh-content>blockquote,.has-serif-body:not([class*=" gh-font-body"]):not([class^=gh-font-body]) .gh-content>dl,.has-serif-body:not([class*=" gh-font-body"]):not([class^=gh-font-body]) .gh-content>ol,.has-serif-body:not([class*=" gh-font-body"]):not([class^=gh-font-body]) .gh-content>p,.has-serif-body:not([class*=" gh-font-body"]):not([class^=gh-font-body]) .gh-content>ul{font-family:var(--font-serif-alt)}.gh-content :is(ul,ol){padding-left:28px}.gh-content :is(li+li,li :is(ul,ol)){margin-top:8px}.gh-content ol ol li{list-style-type:lower-alpha}.gh-content ol ol ol li{list-style-type:lower-roman}.gh-content hr{background-color:var(--color-border);border:0;height:1px;width:100%}.gh-content .gh-table{overflow-x:scroll;-webkit-overflow-scrolling:touch}.gh-content .gh-table table{border-collapse:collapse;border-spacing:0;font-family:var(--font-sans);font-size:1.5rem;vertical-align:top;white-space:nowrap;width:100%}.gh-content .gh-table table th{color:var(--color-darkgrey);font-size:1.2rem;font-weight:700;letter-spacing:.2px;text-align:left;text-transform:uppercase}.gh-content .gh-table table :is(th,td),.gh-content .gh-table table td{border-bottom:1px solid var(--color-border);padding:6px 12px}.gh-content .gh-table table :is(th,td):first-child{padding-left:0}.gh-content .gh-table table :is(th,td):last-child{padding-right:0}.gh-content pre{border-radius:6px;font-size:1.5rem;line-height:1.5em;overflow:auto;padding:16px}.gh-content :not(pre)>code,.gh-content pre{background:var(--color-lighter-gray);font-family:var(--font-mono)}.gh-content :not(pre)>code{border-radius:.25em;font-size:.95em;font-weight:400;line-height:1em;padding:.15em .4em;vertical-align:baseline}.gh-content :not(.kg-card):not(table):not([id])+:is(.kg-card,table){margin-top:calc(48px*var(--content-spacing-factor, 1))}.gh-content :is(.kg-card,table)+:not(.kg-card):not(table):not([id]){margin-top:calc(48px*var(--content-spacing-factor, 1))}.gh-content .kg-card.kg-width-full+:not(.kg-card):not([id]),.gh-content :not(.kg-card):not([id])+.kg-card.kg-width-full{margin-top:calc(68px*var(--content-spacing-factor, 1))}.kg-image{margin-left:auto;margin-right:auto}.kg-embed-card{align-items:center;display:flex;flex-direction:column;width:100%}.kg-gallery-image,.kg-image[width][height]{cursor:pointer}.kg-gallery-image a:hover,.kg-image-card a:hover{opacity:1!important}.kg-card.kg-toggle-card .kg-toggle-heading-text{font-size:2rem;font-weight:700}.has-serif-title:not([class*=" gh-font-heading"]):not([class^=gh-font-heading]) .kg-toggle-card .kg-toggle-heading-text{font-family:var(--font-serif)}.has-mono-title:not([class*=" gh-font-heading"]):not([class^=gh-font-heading]) .kg-toggle-card .kg-toggle-heading-text{font-family:var(--font-mono)}.kg-callout-card.kg-card{border-radius:.25em}.kg-callout-card-accent a{text-decoration:underline}blockquote:not([class]){border-left:4px solid var(--ghost-accent-color);padding-left:2rem}blockquote.kg-blockquote-alt{color:var(--color-secondary-text);font-style:normal;font-weight:400}.kg-card.kg-button-card .kg-btn{font-size:1em;height:unset;line-height:1.2em;padding:.6em 1.2em;text-align:center}.has-serif-title:not([class*=" gh-font-heading"]):not([class^=gh-font-heading]) .kg-card.kg-header-card h2.kg-header-card-header{font-family:var(--font-serif)}.has-mono-title:not([class*=" gh-font-heading"]):not([class^=gh-font-heading]) .kg-card.kg-header-card h2.kg-header-card-header{font-family:var(--font-mono)}.has-serif-body:not([class*=" gh-font-body"]):not([class^=gh-font-body]) .kg-header-card h3.kg-header-card-subheader{font-family:var(--font-serif)}.kg-bookmark-card .kg-bookmark-container{border-radius:.25em!important}.kg-bookmark-card .kg-bookmark-container:hover{opacity:1}.kg-bookmark-card a.kg-bookmark-container,.kg-bookmark-card a.kg-bookmark-container:hover{background:var(--background-color)!important;color:var(--color-darker-gray)!important}.kg-bookmark-card .kg-bookmark-content{padding:1.15em}.kg-bookmark-card .kg-bookmark-title{font-size:.9em}.kg-bookmark-card .kg-bookmark-description{font-size:.8em;margin-top:.3em;max-height:none}.kg-bookmark-card .kg-bookmark-metadata{font-size:.8em}.kg-bookmark-card .kg-bookmark-thumbnail img{border-radius:0 .2em .2em 0}.kg-product-card.kg-card .kg-product-card-image{margin-bottom:12px}.kg-product-card.kg-card a.kg-product-card-button{height:2.8em;margin-top:12px}.has-serif-title:not([class*=" gh-font-heading"]):not([class^=gh-font-heading]) .kg-product-card.kg-card .kg-product-card-title{font-family:var(--font-serif)}.has-mono-title:not([class*=" gh-font-heading"]):not([class^=gh-font-heading]) .kg-product-card.kg-card .kg-product-card-title{font-family:var(--font-mono)}.has-serif-body:not([class*=" gh-font-body"]):not([class^=gh-font-body]) .kg-product-card-description :is(p,ul,ol){font-family:var(--font-serif-alt)}.kg-file-card.kg-card .kg-file-card-container{padding:.6em}.kg-file-card.kg-card .kg-file-card-contents{margin:4px 8px 6px}.kg-file-card.kg-card .kg-file-card-metadata{font-size:.9em}.kg-file-card.kg-card .kg-file-card-filesize:before{margin-left:6px;margin-right:6px}figcaption{font-size:1.4rem;margin-top:12px;text-align:center}.kg-card.kg-width-full figcaption{padding:0 16px}figcaption a{color:var(--ghost-accent-color);text-decoration:underline}.gh-comments{margin-top:48px}.post-template .gh-container{margin-top:120px}.post-template .gh-container-inner{display:block}.post-template .gh-container.is-grid .gh-feed{grid-template-columns:repeat(4,1fr)}.post-template .gh-container .gh-container-title{display:block}.gh-archive{display:grid;gap:var(--grid-gap);grid-template-columns:repeat(16,1fr);margin-block:80px 24px}.gh-archive.has-image{margin-top:48px}.gh-archive-inner{border-bottom:1px solid var(--color-border);display:grid;gap:var(--grid-gap);grid-column:1/-1;grid-template-columns:1fr 1fr;padding-bottom:clamp(40px,2.73vw + 29.09px,64px)}.gh-archive.has-image .gh-archive-inner{align-items:center;grid-column:1/-1}.gh-archive:not(.has-sidebar):not(.has-image) .gh-archive-inner{grid-column:3/span 12}.gh-archive .gh-article-image{grid-column:auto;margin-top:0}:is(.tag-template,.author-template) .gh-container{margin-top:0}.author-template .gh-archive-inner{display:flex;flex-direction:row-reverse;gap:24px;justify-content:flex-end}.author-template .gh-article-image{border-radius:50%;height:120px;margin-top:0;-o-object-fit:cover;object-fit:cover;width:120px}.author-template .gh-article-title{font-size:3.6rem}.gh-author-meta{color:var(--color-secondary-text);display:flex;font-size:1.5rem;font-weight:550;gap:10px;margin-top:14px}.gh-author-meta a{color:inherit}.gh-author-social{align-items:center;display:flex;gap:16px;padding-left:3px}.gh-author-social svg{height:20px;width:20px}@media (max-width:1199px){.gh-archive{display:block}}@media (max-width:767px){.gh-archive-inner{align-items:flex-start;display:flex}.author-template .gh-archive-inner,.gh-archive-inner{flex-direction:column-reverse}}.has-serif-title:not([class*=" gh-font-heading"]):not([class^=gh-font-heading]){--factor:1.15}.has-mono-title:not([class*=" gh-font-heading"]):not([class^=gh-font-heading]){--factor:1.1}.has-sans-title:not([class*=" gh-font-heading"]):not([class^=gh-font-heading]) :is(.is-title,.gh-content :is(h2,h3)){font-family:var(--gh-font-heading,var(--font-sans))}.has-serif-title:not([class*=" gh-font-heading"]):not([class^=gh-font-heading]) :is(.is-title,.gh-content :is(h2,h3)){font-family:var(--gh-font-heading,var(--font-serif));font-weight:550}.has-mono-title:not([class*=" gh-font-heading"]):not([class^=gh-font-heading]) :is(.is-title,.gh-content :is(h2,h3)){font-family:var(--gh-font-heading,var(--font-mono))}.has-sans-body:not([class*=" gh-font-body"]):not([class^=gh-font-body]) .is-body{font-family:var(--gh-font-body,var(--font-sans))}.has-serif-body:not([class*=" gh-font-body"]):not([class^=gh-font-body]) .is-body{font-family:var(--gh-font-body,var(--font-serif-alt))}.has-serif-title:not([class*=" gh-font-heading"]):not([class^=gh-font-heading]) .gh-header.is-classic .gh-header-title{font-weight:550;letter-spacing:-.015em}.has-mono-title:not([class*=" gh-font-heading"]):not([class^=gh-font-heading]) .gh-header.is-classic .gh-header-title{letter-spacing:-.01em}.has-serif-title:not([class*=" gh-font-heading"]):not([class^=gh-font-heading]) .gh-form{border-radius:0}.has-serif-title:not([class*=" gh-font-heading"]):not([class^=gh-font-heading]) .gh-card-title{font-size:calc(2rem*var(--factor, 1));letter-spacing:-.006em;line-height:1.15}.has-serif-title:not([class*=" gh-font-heading"]):not([class^=gh-font-heading]) .gh-featured-feed .gh-card-title{font-size:calc(1.6rem*var(--factor, 1))}.has-mono-title:not([class*=" gh-font-heading"]):not([class^=gh-font-heading]) .gh-featured-feed .gh-card-title{font-size:calc(1.5rem*var(--factor, 1));letter-spacing:0}.has-serif-title:not([class*=" gh-font-heading"]):not([class^=gh-font-heading]) .gh-header.is-highlight .gh-featured-feed .gh-card-title{font-size:clamp(1.6rem,.23vw + 1.51rem,1.8rem)}.has-mono-title:not([class*=" gh-font-heading"]):not([class^=gh-font-heading]) .gh-card-title{font-size:calc(1.8rem*var(--factor, 1));line-height:1.2}.has-serif-title:not([class*=" gh-font-heading"]):not([class^=gh-font-heading]) .gh-about-title{letter-spacing:-.009em}.has-serif-title:not([class*=" gh-font-heading"]):not([class^=gh-font-heading]) .gh-article-title,.has-serif-title:not([class*=" gh-font-heading"]):not([class^=gh-font-heading]) .gh-footer-signup-header{letter-spacing:-.019em}.has-serif-body:not([class*=" gh-font-body"]):not([class^=gh-font-body]){--content-font-size:1.9rem}.has-serif-body:not([class*=" gh-font-body"]):not([class^=gh-font-body]) .gh-card-excerpt{font-size:1.65rem;letter-spacing:.0005em;line-height:1.4}.has-serif-body:not([class*=" gh-font-body"]):not([class^=gh-font-body]) .gh-header.is-highlight .gh-card:first-child .gh-card-excerpt,.has-serif-body:not([class*=" gh-font-body"]):not([class^=gh-font-body]) .gh-header.is-magazine .gh-header-inner>.gh-card .gh-card-excerpt{font-size:1.8rem;letter-spacing:-.001em}.has-serif-title:not([class*=" gh-font-heading"]):not([class^=gh-font-heading]) .gh-header.is-highlight .gh-header-left .gh-card-title,.has-serif-title:not([class*=" gh-font-heading"]):not([class^=gh-font-heading]) .gh-header.is-magazine .gh-header-inner>.gh-card .gh-card-title{font-size:clamp(3.2rem,1.82vw + 2.47rem,4.9rem);font-weight:550}.has-serif-body:not([class*=" gh-font-body"]):not([class^=gh-font-body]) .gh-about-description{font-size:1.6rem}.has-serif-body:not([class*=" gh-font-body"]):not([class^=gh-font-body]) .gh-article-excerpt,.has-serif-body:not([class*=" gh-font-body"]):not([class^=gh-font-body]) .gh-footer-signup-subhead{letter-spacing:0}:is(.has-serif-title,.has-mono-title):not([class*=" gh-font-heading"]):not([class^=gh-font-heading]) :is(.gh-button,.gh-form,.gh-form-input),body[class*=" gh-font-heading"]:not(.gh-font-heading-fira-sans):not(.gh-font-heading-inter):not(.gh-font-heading-manrope):not(.gh-font-heading-noto-sans):not(.gh-font-heading-nunito):not(.gh-font-heading-poppins):not(.gh-font-heading-roboto):not(.gh-font-heading-space-grotesk) :is(.gh-button,.gh-form,.gh-form-input),body[class^=gh-font-heading]:not(.gh-font-heading-fira-sans):not(.gh-font-heading-inter):not(.gh-font-heading-manrope):not(.gh-font-heading-noto-sans):not(.gh-font-heading-nunito):not(.gh-font-heading-poppins):not(.gh-font-heading-roboto):not(.gh-font-heading-space-grotesk) :is(.gh-button,.gh-form,.gh-form-input){border-radius:0}.has-serif-title:not([class*=" gh-font-heading"]):not([class^=gh-font-heading]) .gh-cta-title{font-size:4.8rem}.gh-footer{color:var(--color-darker-gray);font-size:1.5rem;margin-top:120px}.gh-footer a:not(.gh-button){color:inherit}.gh-footer.has-accent-color{background-color:var(--ghost-accent-color)}.gh-footer.has-accent-color .gh-footer-bar{border-top:0}.gh-footer.has-accent-color .gh-button{background-color:#fff;color:#15171a}.gh-footer-bar{align-items:center;border-block:1px solid var(--color-border);-moz-column-gap:32px;column-gap:32px;display:grid;font-weight:550;grid-template-columns:1fr auto 1fr;margin-bottom:120px;padding-block:28px}.gh-footer-logo{font-family:var(--gh-font-heading,var(--font-sans));font-size:calc(2.4rem*var(--factor, 1));font-weight:725;letter-spacing:-.015em;position:relative;white-space:nowrap}.gh-footer-logo img{max-height:40px}.gh-footer-menu .nav{display:flex;flex-wrap:wrap;gap:8px 28px;justify-content:center;list-style-type:none;margin:0;padding:0}.gh-footer-copyright{text-align:right;white-space:nowrap}.gh-footer-copyright a{text-decoration:underline}.gh-footer-signup{align-items:center;display:flex;flex-direction:column;margin-bottom:120px;text-align:center}.gh-footer-signup-header{font-size:calc(clamp(2.8rem,1.36vw + 2.25rem,4rem)*var(--factor, 1));font-weight:700;letter-spacing:-.03em}.gh-footer-signup-subhead{font-size:1.8rem;font-weight:450;letter-spacing:-.014em;line-height:1.4;margin-top:12px;max-width:640px;opacity:.75}.gh-footer-signup .gh-form{margin-top:40px}@media (max-width:991px){.gh-footer-bar{display:flex;flex-direction:column;gap:20px}}.gh-social-links{display:flex;gap:20px;justify-content:center;margin-bottom:32px}.gh-social-links a{will-change:opacity}.gh-social-links svg{height:20px;width:20px}.pswp{backface-visibility:hidden;display:none;height:100%;left:0;outline:none;overflow:hidden;position:absolute;top:0;touch-action:none;width:100%;z-index:3999999;-webkit-text-size-adjust:100%}.pswp img{max-width:none}.pswp--animate_opacity{opacity:.001;transition:opacity 333ms cubic-bezier(.4,0,.22,1);will-change:opacity}.pswp--open{display:block}.pswp--zoom-allowed .pswp__img{cursor:zoom-in}.pswp--zoomed-in .pswp__img{cursor:grab}.pswp--dragging .pswp__img{cursor:grabbing}.pswp__bg{backface-visibility:hidden;background-color:rgba(0,0,0,.85);opacity:0;transform:translateZ(0);transition:opacity 333ms cubic-bezier(.4,0,.22,1);will-change:opacity}.pswp__bg,.pswp__scroll-wrap{height:100%;left:0;position:absolute;top:0;width:100%}.pswp__scroll-wrap{overflow:hidden}.pswp__container,.pswp__zoom-wrap{backface-visibility:hidden;bottom:0;left:0;position:absolute;right:0;top:0;touch-action:none}.pswp__container,.pswp__img{-webkit-user-select:none;-moz-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;-webkit-touch-callout:none}.pswp__zoom-wrap{position:absolute;transform-origin:left top;transition:transform 333ms cubic-bezier(.4,0,.22,1);width:100%}.pswp--animated-in .pswp__bg,.pswp--animated-in .pswp__zoom-wrap{transition:none}.pswp__item{bottom:0;overflow:hidden;right:0}.pswp__img,.pswp__item{left:0;position:absolute;top:0}.pswp__img{height:auto;width:auto}.pswp__img--placeholder{backface-visibility:hidden}.pswp__img--placeholder--blank{background:var(--color-black)}.pswp--ie .pswp__img{height:auto!important;left:0;top:0;width:100%!important}.pswp__error-msg{color:var(--color-secondary-text);font-size:14px;left:0;line-height:16px;margin-top:-8px;position:absolute;text-align:center;top:50%;width:100%}.pswp__error-msg a{color:var(--color-secondary-text);text-decoration:underline}.pswp__button{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;box-shadow:none;cursor:pointer;display:block;float:right;height:44px;margin:0;overflow:visible;padding:0;position:relative;transition:opacity .2s;width:44px}.pswp__button:focus,.pswp__button:hover{opacity:1}.pswp__button:active{opacity:.9;outline:none}.pswp__button::-moz-focus-inner{border:0;padding:0}.pswp__ui--over-close .pswp__button--close{opacity:1}.pswp__button,.pswp__button--arrow--left:before,.pswp__button--arrow--right:before{background:url(../images/default-skin.png) 0 0 no-repeat;background-size:264px 88px;height:44px;width:44px}@media (min-resolution:1.1dppx),(min-resolution:105dpi){.pswp--svg .pswp__button,.pswp--svg .pswp__button--arrow--left:before,.pswp--svg .pswp__button--arrow--right:before{background-image:url(../images/default-skin.svg)}.pswp--svg .pswp__button--arrow--left,.pswp--svg .pswp__button--arrow--right{background:none}}.pswp__button--close{background-position:0 -44px}.pswp__button--share{background-position:-44px -44px}.pswp__button--fs{display:none}.pswp--supports-fs .pswp__button--fs{display:block}.pswp--fs .pswp__button--fs{background-position:-44px 0}.pswp__button--zoom{background-position:-88px 0;display:none}.pswp--zoom-allowed .pswp__button--zoom{display:block}.pswp--zoomed-in .pswp__button--zoom{background-position:-132px 0}.pswp--touch .pswp__button--arrow--left,.pswp--touch .pswp__button--arrow--right{visibility:hidden}.pswp__button--arrow--left,.pswp__button--arrow--right{background:none;height:100px;margin-top:-50px;position:absolute;top:50%;width:70px}.pswp__button--arrow--left{left:0}.pswp__button--arrow--right{right:0}.pswp__button--arrow--left:before,.pswp__button--arrow--right:before{content:"";height:30px;position:absolute;top:35px;width:32px}.pswp__button--arrow--left:before{background-position:-138px -44px;left:6px}.pswp__button--arrow--right:before{background-position:-94px -44px;right:6px}.pswp__counter{color:var(--color-white);font-size:11px;font-weight:700;height:44px;left:0;line-height:44px;padding:0 15px;position:absolute;top:0;-webkit-user-select:none;-moz-user-select:none;user-select:none}.pswp__caption{bottom:0;left:0;min-height:44px;position:absolute;width:100%}.pswp__caption__center{color:var(--color-white);font-size:11px;line-height:1.6;margin:0 auto;max-width:420px;padding:25px 15px 30px;text-align:center}.pswp__caption__center .post-caption-title{font-size:15px;font-weight:500;margin-bottom:7px;text-transform:uppercase}.pswp__caption__center .post-caption-meta-item+.post-caption-meta-item:before{content:"\02022";padding:0 4px}.pswp__caption--empty{display:none}.pswp__caption--fake{visibility:hidden}.pswp__preloader{direction:ltr;height:44px;left:50%;margin-left:-22px;opacity:0;position:absolute;top:0;transition:opacity .25s ease-out;width:44px;will-change:opacity}.pswp__preloader__icn{height:20px;margin:12px;width:20px}.pswp__preloader--active{opacity:1}.pswp__preloader--active .pswp__preloader__icn{background:url(../images/preloader.gif) 0 0 no-repeat}.pswp--css_animation .pswp__preloader--active{opacity:1}.pswp--css_animation .pswp__preloader--active .pswp__preloader__icn{animation:clockwise .5s linear infinite}.pswp--css_animation .pswp__preloader--active .pswp__preloader__donut{animation:donut-rotate 1s cubic-bezier(.4,0,.22,1) infinite}.pswp--css_animation .pswp__preloader__icn{background:none;height:14px;left:15px;margin:0;opacity:.75;position:absolute;top:15px;width:14px}.pswp--css_animation .pswp__preloader__cut{height:14px;overflow:hidden;position:relative;width:7px}.pswp--css_animation .pswp__preloader__donut{background:none;border-bottom:2px solid transparent;border-left:2px solid transparent;border-radius:50%;border-right:2px solid var(--color-white);border-top:2px solid var(--color-white);box-sizing:border-box;height:14px;left:0;margin:0;position:absolute;top:0;width:14px}@media screen and (max-width:1024px){.pswp__preloader{float:right;left:auto;margin:0;position:relative;top:auto}}@keyframes clockwise{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}@keyframes donut-rotate{0%{transform:rotate(0)}50%{transform:rotate(-140deg)}to{transform:rotate(0)}}.pswp__ui{opacity:1;visibility:visible;z-index:1550;-webkit-font-smoothing:auto}.pswp__top-bar{height:44px;left:0;position:absolute;top:0;width:100%}.pswp--has_mouse .pswp__button--arrow--left,.pswp--has_mouse .pswp__button--arrow--right,.pswp__caption,.pswp__top-bar{backface-visibility:hidden;transition:opacity 333ms cubic-bezier(.4,0,.22,1);will-change:opacity}.pswp--has_mouse .pswp__button--arrow--left,.pswp--has_mouse .pswp__button--arrow--right{visibility:visible}.pswp__ui--idle .pswp__button--arrow--left,.pswp__ui--idle .pswp__button--arrow--right,.pswp__ui--idle .pswp__top-bar{opacity:0}.pswp__ui--hidden .pswp__button--arrow--left,.pswp__ui--hidden .pswp__button--arrow--right,.pswp__ui--hidden .pswp__caption,.pswp__ui--hidden .pswp__top-bar{opacity:.001}.pswp__ui--one-slide .pswp__button--arrow--left,.pswp__ui--one-slide .pswp__button--arrow--right,.pswp__ui--one-slide .pswp__counter{display:none}.pswp__element--disabled{display:none!important}.pswp--minimal--dark .pswp__top-bar{background:none}.gh-meta-share{align-items:center;display:flex;justify-content:space-between;margin-top:20px}.has-light-text .gh-button-share{border-color:hsla(0,0%,100%,.25);color:hsla(0,0%,100%,.85)}.has-light-text .gh-button-share:hover{border-color:hsla(0,0%,100%,.85)}.gh-button-share{align-items:center;background:transparent;border:1px solid rgba(0,0,0,.15);border-radius:999px;color:rgba(0,0,0,.85);cursor:pointer;display:inline-flex;transition:all .4s ease}.gh-button-share:hover{border-color:rgba(0,0,0,.5)}/*# sourceMappingURL=screen.css.map */ diff --git a/ghost-me/theme-source-personal/assets/built/screen.css.map b/ghost-me/theme-source-personal/assets/built/screen.css.map deleted file mode 100644 index 7ec5cee..0000000 --- a/ghost-me/theme-source-personal/assets/built/screen.css.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["screen.css"],"names":[],"mappings":"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","file":"screen.css","sourcesContent":["/* Table of contents\n/* ------------------------------------------------------------\n\nThis is a development CSS file which is built to a minified\nproduction stylesheet in assets/built/screen.css\n\n1. Variables\n2. Fonts\n3. Resets\n4. Globals\n5. Layout\n6. Navigation\n 6.1. Navigation styles\n 6.2. Navigation layouts\n 6.3. Dropdown menu\n 6.4. Mobile menu\n7. Card\n8. Header\n 8.1. Magazine layout\n 8.2. Highlight layout\n 8.3. Classic layout\n9. CTA\n10. Featured posts\n11. Container\n 11.1. With sidebar\n 11.2. Without sidebar\n12. Post list\n 12.1. List style\n 12.2. Grid style\n 12.3. No image list\n13. Sidebar\n14. Post/page\n 14.1. Article\n 14.2. Page template\n 14.3. Page without header\n15. Content\n16. Cards\n17. Comments\n18. Recent posts\n19. Archive\n20. Design settings\n21. Footer\n 21.1. Footer styles\n 21.2. Footer bar\n 21.3. Footer signup\n 21.4. Social links\n22. Lightbox\n\n*/\n\n/* 1. Variables\n/* ---------------------------------------------------------- */\n\n:root {\n --color-white: #fff;\n --color-lighter-gray: rgb(0 0 0 / 0.05);\n --color-light-gray: #e6e6e6;\n --color-mid-gray: #ccc;\n --color-dark-gray: #444;\n --color-darker-gray: #15171a;\n --color-black: #000;\n --color-primary-text: var(--color-darker-gray);\n --color-secondary-text: rgb(0 0 0 / 0.55);\n --color-border: rgb(0 0 0 / 0.08);\n --color-dark-border: rgb(0 0 0 / 0.55);\n --font-sans: Inter, -apple-system, BlinkMacSystemFont, \"Segoe UI\", \"Roboto\", \"Oxygen\", \"Ubuntu\", \"Cantarell\", \"Fira Sans\", \"Droid Sans\", \"Helvetica Neue\", sans-serif;\n --font-serif: \"EB Garamond\", Georgia, Times, serif;\n --font-serif-alt: Georgia, Times, serif;\n --font-mono: \"JetBrains Mono\", Menlo, Consolas, Monaco, \"Liberation Mono\", \"Lucida Console\", monospace;\n --container-width: 1320px;\n --container-gap: clamp(24px, 1.7032rem + 1.9355vw, 48px);\n --grid-gap: 42px;\n}\n\n:root.has-light-text,\n:is(.gh-navigation, .gh-footer).has-accent-color {\n --color-lighter-gray: rgb(255 255 255 / 0.1);\n --color-darker-gray: #fff;\n --color-secondary-text: rgb(255 255 255 / 0.64);\n --color-border: rgb(255 255 255 / 0.15);\n --color-dark-border: rgb(255 255 255 / 0.5);\n}\n\n/* 2. Fonts\n/* ---------------------------------------------------------- */\n\n/* Fonts are preloaded and defined in default.hbs to avoid layout shift */\n\n/* 3. Resets\n/* ---------------------------------------------------------- */\n\n*, *::before, *::after {\n box-sizing: border-box;\n}\n\n* {\n margin: 0;\n}\n\nhtml {\n font-size: 62.5%;\n}\n\nbody {\n line-height: 1.6;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\nimg, picture, video, canvas, svg {\n display: block;\n height: auto;\n max-width: 100%;\n}\n\niframe {\n display: block;\n}\n\ninput, button, textarea, select {\n font: inherit;\n}\n\np, h1, h2, h3, h4, h5, h6 {\n overflow-wrap: break-word;\n}\n\nh1, h2, h3, h4, h5, h6 {\n line-height: 1.2;\n}\n\nsub, sup {\n line-height: 1;\n}\n\n/* 4. Globals\n/* ---------------------------------------------------------- */\n\nbody {\n font-family: var(--gh-font-body, var(--font-sans));\n font-size: 1.6rem;\n background-color: var(--background-color);\n color: var(--color-primary-text);\n}\n\nh1, h2, h3, h4, h5, h6 {\n font-family: var(--gh-font-heading, var(--font-sans));\n}\n\na {\n color: var(--color-darker-gray);\n text-decoration: none;\n}\n\na:hover {\n opacity: 0.8;\n}\n\n.gh-button {\n display: inline-flex;\n gap: 0.4em;\n align-items: center;\n justify-content: center;\n padding: 0.8em 1.4em;\n font-size: 1.5rem;\n font-weight: 600;\n letter-spacing: -0.004em;\n line-height: 1;\n color: var(--color-white);\n cursor: pointer;\n background-color: var(--ghost-accent-color);\n border: 0;\n border-radius: 100px;\n}\n\n.gh-button:hover {\n opacity: 0.95;\n}\n\n.gh-icon-button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n padding: 0;\n color: var(--color-darker-gray);\n cursor: pointer;\n background-color: transparent;\n border: 0;\n outline: none;\n}\n\n.gh-icon-button:hover :is(svg, span) {\n opacity: 0.8;\n}\n\n.gh-icon-button svg {\n width: 20px;\n height: 20px;\n}\n\n.gh-form {\n display: flex;\n align-items: center;\n position: relative;\n max-width: 560px;\n width: 100%;\n height: 56px;\n font-size: 1.7rem;\n font-weight: 450;\n letter-spacing: -0.008em;\n border-radius: 40px;\n background-color: var(--color-lighter-gray);\n transition: background-color 0.2s ease;\n}\n\n.gh-form.success {\n pointer-events: none;\n}\n\n.gh-form.error {\n box-shadow: 0 0 0 1px red;\n}\n\n.gh-form:hover {\n background-color: rgb(0 0 0 / 0.065);\n}\n\n.has-light-text .gh-form:hover,\n.gh-footer.has-accent-color .gh-form:hover {\n background-color: rgb(255 255 255 / 0.15);\n}\n\n.gh-form-input {\n position: absolute;\n inset: 0;\n padding-inline: 26px;\n width: 100%;\n height: 100%;\n font-size: inherit;\n letter-spacing: inherit;\n line-height: 1.1;\n border: 0;\n border-radius: 40px;\n background-color: transparent;\n outline: none;\n transition: 0.3s ease-in-out;\n}\n\n.gh-form-input::placeholder,\nbutton.gh-form-input {\n color: rgb(0 0 0 / 0.3);\n}\n\n:is(.has-serif-title, .has-mono-title):not([class*=\" gh-font-heading\"]):not([class^=\"gh-font-heading\"]) .gh-form-input,\nbody[class*=\" gh-font-heading\"]:not(.gh-font-heading-fira-sans):not(.gh-font-heading-inter):not(.gh-font-heading-manrope):not(.gh-font-heading-noto-sans):not(.gh-font-heading-nunito):not(.gh-font-heading-poppins):not(.gh-font-heading-roboto):not(.gh-font-heading-space-grotesk) .gh-form-input,\nbody[class^=\"gh-font-heading\"]:not(.gh-font-heading-fira-sans):not(.gh-font-heading-inter):not(.gh-font-heading-manrope):not(.gh-font-heading-noto-sans):not(.gh-font-heading-nunito):not(.gh-font-heading-poppins):not(.gh-font-heading-roboto):not(.gh-font-heading-space-grotesk) .gh-form-input {\n padding-inline: 20px;\n}\n\n.gh-form.gh-form.success .gh-form-input {\n opacity: 0.5;\n}\n\n.has-light-text .gh-form-input,\n.gh-footer.has-accent-color .gh-form-input {\n color: #fff;\n}\n\n.has-light-text .gh-form-input::placeholder,\n.has-light-text button.gh-form-input,\n.gh-footer.has-accent-color .gh-form-input::placeholder {\n color: rgb(255 255 255 / 0.55);\n}\n\n.gh-header.is-classic.has-image .gh-form-input {\n color: #15171a;\n}\n\n.gh-header.is-classic.has-image .gh-form-input::placeholder,\n.gh-header.is-classic.has-image button.gh-form-input,\n.gh-header.is-classic.has-image .gh-form > svg {\n color: rgb(0 0 0 / 0.5);\n}\n\nbutton.gh-form-input {\n padding-inline-start: 56px;\n text-align: left;\n color: var(--color-secondary-text);\n cursor: pointer;\n}\n\n:is(.has-serif-title,.has-mono-title):not([class*=\" gh-font-heading\"]):not([class^=\"gh-font-heading\"]) button.gh-form-input,\nbody[class*=\" gh-font-heading\"]:not(.gh-font-heading-fira-sans):not(.gh-font-heading-inter):not(.gh-font-heading-manrope):not(.gh-font-heading-noto-sans):not(.gh-font-heading-nunito):not(.gh-font-heading-poppins):not(.gh-font-heading-roboto):not(.gh-font-heading-space-grotesk) button.gh-form-input,\nbody[class^=\"gh-font-heading\"]:not(.gh-font-heading-fira-sans):not(.gh-font-heading-inter):not(.gh-font-heading-manrope):not(.gh-font-heading-noto-sans):not(.gh-font-heading-nunito):not(.gh-font-heading-poppins):not(.gh-font-heading-roboto):not(.gh-font-heading-space-grotesk) button.gh-form-input {\n padding-inline-start: 50px;\n}\n\n.gh-form .gh-button {\n position: absolute;\n right: 6px;\n min-width: 132px;\n padding-inline: 24px;\n height: 44px;\n font-size: inherit;\n}\n\n.gh-form > svg {\n position: relative;\n left: 22px;\n width: 20px;\n height: 20px;\n color: var(--color-secondary-text);\n}\n\n:is(.has-serif-title,.has-mono-title):not([class*=\" gh-font-heading\"]):not([class^=\"gh-font-heading\"]) .gh-form > svg {\n left: 16px;\n}\n\n.gh-form .gh-button svg {\n display: none;\n position: absolute;\n margin-top: 1px;\n}\n\n.gh-form .gh-button > span:last-child {\n display: none;\n}\n\n.gh-form:is(.loading, .success) .gh-button > span:first-child {\n visibility: hidden;\n}\n\n.gh-form.loading .gh-button svg:first-of-type {\n display: block;\n}\n\n.gh-form.success .gh-button > span:first-child {\n display: none;\n}\n\n.gh-form.success .gh-button > span:last-child {\n display: inline;\n}\n\n.gh-form [data-members-error] {\n position: absolute;\n top: 100%;\n margin-top: 10px;\n width: 100%;\n font-size: 1.4rem;\n line-height: 1.4;\n}\n\n@media (max-width: 576px) {\n .gh-form {\n font-size: 1.6rem;\n }\n\n .gh-form .gh-button {\n min-width: 104px;\n padding-inline: 12px;\n }\n}\n\n/* 5. Layout\n/* ---------------------------------------------------------- */\n\n.gh-viewport {\n position: relative;\n display: flex;\n flex-direction: column;\n min-height: 100vh;\n}\n\n.gh-outer {\n padding: 0 max(4vmin, 20px);\n}\n\n.gh-outer .gh-outer {\n padding: 0;\n}\n\n.gh-inner {\n margin: 0 auto;\n max-width: var(--container-width);\n width: 100%;\n}\n\n.gh-main {\n flex-grow: 1;\n}\n\n.gh-canvas,\n.kg-width-full.kg-content-wide {\n --main: min(var(--content-width, 720px), 100% - var(--container-gap) * 2);\n --wide: minmax(0, calc((var(--container-width, 1200px) - var(--content-width, 720px)) / 2));\n --full: minmax(var(--container-gap), 1fr);\n\n display: grid;\n grid-template-columns:\n [full-start] var(--full)\n [wide-start] var(--wide)\n [main-start] var(--main) [main-end]\n var(--wide) [wide-end]\n var(--full) [full-end];\n}\n\n.gh-canvas > * {\n grid-column: main;\n}\n\n.kg-width-wide,\n.kg-content-wide > div {\n grid-column: wide;\n}\n\n.kg-width-full {\n grid-column: full;\n}\n\n/* 6. Navigation\n/* ---------------------------------------------------------- */\n\n.gh-navigation {\n height: 100px;\n font-size: 1.5rem;\n font-weight: 550;\n background-color: var(--background-color);\n color: var(--color-darker-gray);\n}\n\n.gh-navigation :is(.gh-navigation-logo, a:not(.gh-button), .gh-icon-button) {\n color: inherit;\n}\n\n.gh-navigation-inner {\n display: grid;\n grid-auto-flow: row dense;\n column-gap: 24px;\n align-items: center;\n height: 100%;\n}\n\n.gh-navigation-brand {\n line-height: 1;\n}\n\n.gh-navigation-logo {\n position: relative;\n font-family: var(--gh-font-heading, var(--font-sans));\n font-size: calc(2.4rem * var(--factor, 1));\n font-weight: 725;\n letter-spacing: -0.015em;\n white-space: nowrap;\n}\n\n.gh-navigation-logo img {\n max-height: 40px;\n}\n\n.gh-navigation-menu {\n display: flex;\n gap: 24px;\n align-items: center;\n}\n\n.gh-navigation .nav {\n display: inline-flex;\n gap: 28px;\n align-items: center;\n padding: 0;\n margin: 0;\n white-space: nowrap;\n list-style: none;\n}\n\n.gh-navigation .gh-more-toggle {\n position: relative;\n margin: 0 -6px;\n font-size: inherit;\n text-transform: inherit;\n}\n\n.gh-navigation .gh-more-toggle svg {\n width: 24px;\n height: 24px;\n}\n\n.gh-navigation-actions {\n display: flex;\n gap: 24px;\n align-items: center;\n justify-content: flex-end;\n background-color: var(--background-color);\n}\n\n.gh-navigation.has-accent-color .gh-navigation-actions {\n background-color: var(--ghost-accent-color);\n}\n\n.gh-navigation-members {\n display: flex;\n gap: 20px;\n align-items: center;\n white-space: nowrap;\n}\n\n.gh-navigation-members .gh-button {\n font-size: inherit;\n font-weight: 600;\n}\n\n.gh-search {\n margin-right: -2px;\n margin-left: -2px;\n}\n\n@media (max-width: 767px) {\n .gh-navigation-logo {\n white-space: normal;\n }\n\n .gh-navigation-members {\n flex-direction: column-reverse;\n gap: 16px;\n width: 100%;\n }\n\n .gh-navigation-actions .gh-search {\n display: none;\n }\n}\n\n@media (min-width: 768px) {\n .gh-navigation-brand .gh-search {\n display: none;\n }\n\n .gh-navigation:not(.is-dropdown-loaded) .gh-navigation-menu .nav > li {\n opacity: 0;\n }\n}\n\n/* 6.1. Navigation styles */\n\n.gh-navigation.has-accent-color {\n background-color: var(--ghost-accent-color);\n}\n\n.gh-navigation.has-accent-color .gh-button {\n background-color: #fff;\n color: #15171a;\n}\n\n/* 6.2. Navigation layouts */\n\n/*\n======================================================================\nLOGO Home About Collection Author Portal Login Subscribe\n======================================================================\n*/\n\n.gh-navigation.is-left-logo .gh-navigation-inner {\n grid-template-columns: auto 1fr auto;\n}\n\n@media (min-width: 768px) {\n .gh-navigation.is-left-logo .gh-navigation-logo:not(:has(img)) {\n top: -2px;\n }\n}\n\n@media (min-width: 992px) {\n .gh-navigation.is-left-logo .gh-navigation-menu {\n margin-right: 100px;\n margin-left: 16px;\n }\n}\n\n/*\n======================================================================\nHome About Collection LOGO Login Subscribe\n======================================================================\n*/\n\n.gh-navigation.is-middle-logo .gh-navigation-inner {\n grid-template-columns: 1fr auto 1fr;\n}\n\n.gh-navigation.is-middle-logo .gh-navigation-brand {\n grid-column-start: 2;\n}\n\n.gh-navigation.is-middle-logo .gh-navigation-actions {\n gap: 28px;\n}\n\n@media (min-width: 992px) {\n .gh-navigation.is-middle-logo .gh-navigation-menu {\n margin-right: 64px;\n }\n}\n\n/*\n======================================================================\nSearch LOGO Login Subscribe\n Home About Collection Author Portal\n======================================================================\n*/\n\n.gh-navigation.is-stacked {\n position: relative;\n height: auto;\n}\n\n.gh-navigation.is-stacked .gh-navigation-inner {\n grid-template-columns: 1fr auto 1fr;\n}\n\n.gh-navigation.is-stacked .gh-navigation-brand {\n display: flex;\n align-items: center;\n grid-row-start: 1;\n grid-column-start: 2;\n min-height: 80px;\n}\n\n@media (max-width: 767px) {\n .gh-navigation.is-stacked .gh-navigation-brand {\n min-height: unset;\n }\n}\n\n@media (min-width: 992px) {\n .gh-navigation.is-stacked .gh-navigation-inner {\n padding: 0;\n }\n\n .gh-navigation.is-stacked .gh-navigation-brand {\n display: flex;\n align-items: center;\n height: 80px;\n }\n\n .gh-navigation.is-stacked .gh-navigation-menu {\n grid-row-start: 2;\n grid-column: 1 / 4;\n justify-content: center;\n height: 60px;\n margin: 0 48px;\n }\n\n .gh-navigation.is-stacked .gh-navigation-menu::before,\n .gh-navigation.is-stacked .gh-navigation-menu::after {\n position: absolute;\n top: 80px;\n left: 0;\n width: 100%;\n height: 1px;\n content: \"\";\n background-color: var(--color-border);\n }\n\n .gh-navigation.is-stacked .gh-navigation-menu::after {\n top: 140px;\n }\n\n .gh-navigation.is-stacked .gh-navigation-actions {\n grid-row-start: 1;\n grid-column: 1 / 4;\n justify-content: space-between;\n }\n\n .gh-navigation.is-stacked .gh-navigation-actions .gh-search {\n display: flex;\n gap: 10px;\n width: auto;\n }\n}\n\n/* 6.3. Dropdown menu */\n\n.gh-dropdown {\n position: absolute;\n top: 100%;\n right: -16px;\n z-index: 90;\n width: 200px;\n padding: 12px 0;\n margin-top: 24px;\n text-align: left;\n visibility: hidden;\n background-color: #fff;\n border-radius: 5px;\n box-shadow: 0 0 0 1px rgb(0 0 0 / 0.04), 0 7px 20px -5px rgb(0 0 0 / 0.15);\n opacity: 0;\n transition: opacity 0.3s, transform 0.2s;\n transform: translate3d(0, 6px, 0);\n}\n\n.gh-dropdown.is-left {\n right: auto;\n left: -16px;\n}\n\n.is-dropdown-mega .gh-dropdown {\n display: grid;\n grid-template-columns: 1fr 1fr;\n grid-auto-flow: column;\n column-gap: 40px;\n width: auto;\n padding: 20px 32px;\n}\n\n.is-dropdown-open .gh-dropdown {\n visibility: visible;\n opacity: 1;\n transform: translateY(0);\n}\n\n.gh-dropdown li a {\n display: block;\n padding: 7px 20px;\n line-height: 1.5;\n white-space: normal;\n color: #15171a !important;\n}\n\n.is-dropdown-mega .gh-dropdown li a {\n padding: 8px 0;\n}\n\n/* 6.4. Mobile menu */\n\n.gh-burger {\n display: flex;\n justify-content: center;\n align-items: center;\n position: relative;\n display: none;\n margin-right: -7px;\n margin-left: 4px;\n}\n\n.gh-burger svg {\n width: 24px;\n height: 24px;\n}\n\n.gh-burger svg:last-child {\n display: none;\n}\n\n.is-open .gh-burger svg:first-child {\n display: none;\n}\n\n.is-open .gh-burger svg:last-child {\n display: block;\n}\n\n@media (max-width: 767px) {\n .gh-burger {\n display: flex;\n }\n\n #gh-navigation {\n height: 64px;\n }\n\n #gh-navigation .gh-navigation-inner {\n grid-template-rows: auto 1fr auto;\n grid-template-columns: 1fr;\n gap: 48px;\n }\n\n #gh-navigation .gh-navigation-brand {\n display: grid;\n grid-template-columns: 1fr auto auto;\n grid-column-start: 1;\n align-items: center;\n height: 64px;\n }\n\n #gh-navigation .gh-navigation-logo {\n font-size: 2.2rem;\n }\n\n #gh-navigation .gh-navigation-menu,\n #gh-navigation .gh-navigation-actions {\n position: fixed;\n justify-content: center;\n visibility: hidden;\n opacity: 0;\n }\n\n #gh-navigation .gh-navigation-menu {\n transition: none;\n transform: translateY(0);\n }\n\n #gh-navigation .nav {\n gap: 20px;\n align-items: center;\n line-height: 1.4;\n }\n\n #gh-navigation .nav a {\n font-size: 1.75rem;\n font-weight: 600;\n text-transform: none;\n }\n\n #gh-navigation .nav li {\n opacity: 0;\n transform: translateY(-4px);\n }\n\n #gh-navigation .gh-navigation-actions {\n text-align: center;\n }\n\n #gh-navigation :is(.gh-button, a[data-portal=\"signin\"]) {\n opacity: 0;\n transform: translateY(8px);\n }\n\n #gh-navigation .gh-button {\n width: 100%;\n font-size: 1.75rem;\n text-transform: none;\n }\n\n #gh-navigation a[data-portal=\"signin\"] {\n font-size: 1.75rem;\n }\n\n #gh-main {\n transition: opacity 0.4s;\n }\n\n .is-open#gh-navigation {\n position: fixed;\n inset: 0;\n z-index: 3999999;\n height: 100%;\n overflow-y: scroll;\n -webkit-overflow-scrolling: touch;\n }\n\n .is-open#gh-navigation .gh-navigation-menu,\n .is-open#gh-navigation .gh-navigation-actions {\n position: static;\n visibility: visible;\n opacity: 1;\n }\n\n .is-open#gh-navigation .nav {\n display: flex;\n flex-direction: column;\n }\n\n .is-open#gh-navigation .nav li {\n opacity: 1;\n transition: transform 0.2s, opacity 0.2s;\n transform: translateY(0);\n }\n\n .is-open#gh-navigation .gh-navigation-actions {\n position: sticky;\n right: 0;\n bottom: 0;\n left: 0;\n display: inline-flex;\n flex-direction: column;\n gap: 12px;\n align-items: center;\n padding: var(--container-gap) 0;\n }\n\n .is-open#gh-navigation :is(.gh-button, a[data-portal=\"signin\"]) {\n opacity: 1;\n transition: transform 0.4s, opacity 0.4s;\n transition-delay: 0.2s;\n transform: translateY(0);\n }\n\n .is-open#gh-navigation a[data-portal=\"signin\"] {\n transition-delay: 0.4s;\n }\n\n .is-open#gh-main {\n opacity: 0;\n }\n}\n\n/* 7. Card\n/* ---------------------------------------------------------- */\n\n.gh-card {\n position: relative;\n}\n\n.gh-card-link {\n display: flex;\n flex-direction: column;\n gap: 20px;\n}\n\n.gh-card-link:hover {\n opacity: 1;\n}\n\n.gh-card-image {\n position: relative;\n flex-shrink: 0;\n aspect-ratio: 16 / 9;\n}\n\n.gh-card-image img {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n object-fit: cover;\n}\n\n.gh-card-wrapper {\n flex-grow: 1;\n}\n\n.gh-card-tag {\n display: none;\n margin-bottom: 4px;\n font-size: 1.2rem;\n font-weight: 500;\n letter-spacing: 0.01em;\n text-transform: uppercase;\n}\n\n.gh-card-title {\n font-size: calc(1.9rem * var(--factor, 1));\n font-weight: 725;\n letter-spacing: -0.014em;\n line-height: 1.3;\n}\n\n.gh-card-link:hover .gh-card-title {\n opacity: 0.8;\n}\n\n.gh-card-excerpt {\n display: -webkit-box;\n overflow-y: hidden;\n margin-top: 8px;\n font-size: 1.45rem;\n line-height: 1.4;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n}\n\n.gh-card-meta {\n align-items: center;\n padding-bottom: 1px;\n font-size: 1.25rem;\n font-weight: 500;\n line-height: 1.4;\n letter-spacing: -0.004em;\n color: var(--color-secondary-text);\n}\n\n.gh-card-meta svg {\n position: relative;\n top: 1px;\n display: inline-block;\n width: 12px;\n height: 12px;\n margin-right: 2px;\n}\n\n.gh-card-meta:not(:empty) {\n margin-top: 8px;\n}\n\n.gh-card-author + .gh-card-date::before {\n content: \"—\";\n margin-right: 4px;\n}\n\n/* 8. Header\n/* ---------------------------------------------------------- */\n\n.gh-header {\n position: relative;\n margin-top: 40px;\n}\n\n.gh-header-inner {\n position: relative;\n overflow: hidden;\n}\n\n/* 8.1. Magazine layout */\n\n.gh-header.is-magazine .gh-header-inner {\n display: grid;\n grid-template-columns: repeat(16, 1fr);\n gap: var(--grid-gap);\n}\n\n.gh-header.is-magazine .gh-header-inner > div {\n display: flex;\n flex-direction: column;\n gap: var(--grid-gap);\n grid-row: 1;\n}\n\n.gh-header.is-magazine .gh-header-left {\n grid-column: 1 / span 4;\n}\n\n.gh-header.is-magazine .gh-header-inner > .gh-card {\n position: relative;\n grid-column: 5 / span 8;\n}\n\n.gh-header.is-magazine .gh-header-right {\n grid-column: 13 / -1;\n}\n\n.gh-header.is-magazine .gh-header-inner > .gh-card .gh-card-link {\n gap: 28px;\n}\n\n.gh-header.is-magazine .gh-header-inner > .gh-card .gh-card-image {\n aspect-ratio: 1.618033;\n}\n\n.gh-header.is-magazine .gh-header-inner > .gh-card .gh-card-tag {\n display: block;\n margin-bottom: 12px;\n}\n\n.gh-header.is-magazine .gh-header-inner > .gh-card .gh-card-title {\n font-size: clamp(2.8rem, 1.36vw + 2.25rem, 4rem);\n font-weight: 700;\n line-height: 1.1;\n letter-spacing: -0.022em;\n}\n\n.gh-header.is-magazine .gh-header-inner > .gh-card .gh-card-excerpt {\n margin-top: clamp(12px, 0.45vw + 10.18px, 16px);\n font-size: 1.8rem;\n letter-spacing: -0.02em;\n max-width: 90%;\n}\n\n.gh-header.is-magazine .gh-header-inner > .gh-card .gh-card-meta:not(:empty) {\n margin-top: 14px;\n}\n\n.gh-header.is-magazine :is(.gh-header-left, .gh-header-right) .gh-card:last-child .gh-card-image {\n display: none;\n}\n\n.gh-header.is-magazine .gh-header-inner > div .gh-card-excerpt {\n display: none;\n}\n\n.gh-header.is-magazine .gh-header-inner > .gh-card::before,\n.gh-header.is-magazine .gh-header-inner > .gh-card::after {\n position: absolute;\n top: 0;\n left: calc(var(--grid-gap) / -2);\n content: \"\";\n width: 1px;\n height: 100%;\n background-color: var(--color-border);\n}\n\n.gh-header.is-magazine .gh-header-inner > .gh-card::after {\n right: calc(var(--grid-gap) / -2);\n left: auto;\n}\n\n.gh-header.is-magazine .gh-header-inner > div .gh-card + .gh-card::before {\n position: absolute;\n top: calc(var(--grid-gap) / -2);\n left: 0;\n content: \"\";\n width: 100%;\n height: 1px;\n background-color: var(--color-border);\n}\n\n@media (max-width: 1199px) {\n .gh-header.is-magazine .gh-header-inner {\n grid-template-columns: repeat(12, 1fr);\n }\n\n .gh-header.is-magazine .gh-header-inner > .gh-card {\n grid-column: 1 / span 8;\n }\n\n .gh-header.is-magazine .gh-header-left {\n grid-column: 9 / -1;\n }\n\n .gh-header.is-magazine .gh-header-inner > div.gh-header-right {\n grid-column: 1 / -1;\n grid-row: 2;\n flex-direction: row;\n }\n\n .gh-header.is-magazine .gh-header-right .gh-card {\n flex: 1;\n }\n\n .gh-header.is-magazine .gh-header-right .gh-card:last-child .gh-card-image {\n display: block;\n }\n\n .gh-header.is-magazine .gh-header-right {\n position: relative;\n }\n\n .gh-header.is-magazine .gh-header-right::before {\n position: absolute;\n top: calc(var(--grid-gap) / -2);\n left: 0;\n content: \"\";\n width: 100%;\n height: 1px;\n background-color: var(--color-border);\n }\n\n .gh-header.is-magazine .gh-header-right .gh-card::before {\n display: none;\n }\n\n .gh-header.is-magazine .gh-header-right .gh-card::after {\n position: absolute;\n top: 0;\n right: calc(var(--grid-gap) / -2);\n content: \"\";\n width: 1px;\n height: 100%;\n background-color: var(--color-border);\n }\n}\n\n@media (max-width: 991px) {\n .gh-header.is-magazine .gh-header-left .gh-card:nth-child(2) .gh-card-image {\n display: none;\n }\n}\n\n@media (max-width: 767px) {\n .gh-header.is-magazine .gh-header-inner {\n display: flex;\n flex-direction: column;\n gap: var(--grid-gap);\n }\n\n .gh-header.is-magazine .gh-header-inner > div.gh-header-right {\n flex-direction: column;\n }\n\n .gh-header.is-magazine .gh-card-image {\n display: block !important;\n }\n\n .gh-header.is-magazine .gh-card::before {\n display: block !important;\n position: absolute;\n top: calc(var(--grid-gap) / -2);\n left: 0;\n content: \"\";\n width: 100%;\n height: 1px;\n background-color: var(--color-border);\n }\n}\n\n/* 8.2. Highlight layout */\n\n.gh-header.is-highlight .gh-header-inner {\n display: grid;\n grid-template-columns: repeat(16, 1fr);\n gap: var(--grid-gap);\n}\n\n.gh-header.is-highlight .gh-header-left {\n position: relative;\n grid-column: span 8;\n}\n\n.gh-header.is-highlight .gh-header-left .gh-card {\n grid-column: span 8;\n grid-row: span 3;\n}\n\n.gh-header.is-highlight .gh-header-left .gh-card .gh-card-link {\n gap: 28px;\n}\n\n.gh-header.is-highlight .gh-header-left .gh-card .gh-card-image {\n aspect-ratio: 3 / 2;\n}\n\n.gh-header.is-highlight .gh-header-left .gh-card .gh-card-tag {\n display: block;\n margin-bottom: 12px;\n}\n\n.gh-header.is-highlight .gh-header-left .gh-card .gh-card-title {\n font-size: clamp(2.8rem, 1.36vw + 2.25rem, 4rem);\n font-weight: 700;\n line-height: 1.1;\n letter-spacing: -0.022em;\n}\n\n.gh-header.is-highlight .gh-header-left .gh-card .gh-card-excerpt {\n margin-top: clamp(12px, 0.45vw + 10.18px, 16px);\n font-size: 1.8rem;\n}\n\n.gh-header.is-highlight .gh-header-left .gh-card .gh-card-meta:not(:empty) {\n margin-top: 12px;\n}\n\n.gh-header.is-highlight .gh-header-middle {\n position: relative;\n display: flex;\n flex-direction: column;\n gap: var(--grid-gap);\n grid-column: 9 / span 4;\n}\n\n.gh-header.is-highlight .gh-header-middle .gh-card:last-child .gh-card-image {\n display: none;\n}\n\n.gh-header.is-highlight .gh-header-middle .gh-card .gh-card-excerpt {\n display: none;\n}\n\n.gh-header.is-highlight .gh-header-right {\n grid-column: 13 / -1;\n}\n\n.gh-header.is-highlight .gh-featured {\n margin-top: 0;\n padding: 0;\n}\n\n.gh-header.is-highlight .gh-featured-feed {\n display: flex;\n flex-direction: column;\n}\n\n.gh-header.is-highlight .gh-featured-feed .gh-card-title {\n font-size: clamp(1.4rem, 0.23vw + 1.31rem, 1.6rem);\n}\n\n.gh-header.is-highlight .gh-header-left::after,\n.gh-header.is-highlight .gh-header-middle::after {\n position: absolute;\n top: 0;\n right: calc(var(--grid-gap) / -2);\n content: \"\";\n width: 1px;\n height: 100%;\n background-color: var(--color-border);\n}\n\n.gh-header.is-highlight .gh-card::before {\n position: absolute;\n top: calc(var(--grid-gap) / -2);\n left: 0;\n content: \"\";\n width: 100%;\n height: 1px;\n background-color: var(--color-border);\n}\n\n.gh-header.is-highlight .gh-featured .gh-card:first-child::before {\n display: none;\n}\n\n@media (max-width: 1199px) {\n .gh-header.is-highlight .gh-header-inner {\n grid-template-columns: repeat(9, 1fr);\n }\n\n .gh-header.is-highlight .gh-header-left {\n grid-column: span 6;\n }\n\n .gh-header.is-highlight .gh-header-middle {\n grid-column: 7 / -1;\n }\n\n .gh-header.is-highlight .gh-header-right {\n grid-column: 1 / -1;\n }\n\n .gh-header.is-highlight .gh-featured-feed {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n }\n\n .gh-header.is-highlight .gh-featured-feed .gh-card:before {\n width: calc(100% + var(--grid-gap));\n }\n\n .gh-header.is-highlight .gh-featured-feed .gh-card::after {\n position: absolute;\n top: 0;\n left: calc(var(--grid-gap) / -2);\n content: \"\";\n width: 1px;\n height: 100%;\n background-color: var(--color-border);\n }\n}\n\n@media (max-width: 991px) {\n .gh-header.is-highlight .gh-header-middle .gh-card:nth-child(2) .gh-card-image {\n display: none;\n }\n}\n\n@media (max-width: 767px) {\n .gh-header.is-highlight .gh-header-inner {\n display: flex;\n flex-direction: column;\n }\n\n .gh-header.is-highlight .gh-featured-feed {\n display: flex;\n /* gap: var(--grid-gap); */\n }\n\n .gh-header.is-highlight .gh-card-image {\n display: block !important;\n }\n\n .gh-header.is-highlight .gh-header-middle .gh-card .gh-card-excerpt {\n display: -webkit-box;\n }\n}\n\n/* 8.3. Classic layout */\n\n.gh-header.is-classic {\n display: flex;\n margin-top: 0;\n padding-block: 160px;\n}\n\n.gh-header.is-classic .gh-header-inner {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 48px;\n overflow: unset;\n margin: auto;\n max-width: 1020px;\n text-align: center;\n}\n\n.gh-header.is-classic .gh-header-title {\n font-size: calc(clamp(3rem, 1.82vw + 2.27rem, 4.6rem) * var(--factor, 1));\n line-height: 1.1;\n letter-spacing: -0.028em;\n}\n\n.gh-header.is-classic.has-image {\n margin-top: 0;\n}\n\n.gh-header.is-classic.has-image::before {\n position: absolute;\n inset: 0;\n content: \"\";\n opacity: 0.3;\n background-color: var(--color-black);\n transition: opacity 1.5s ease;\n}\n\n.gh-header.is-classic.has-image .gh-header-inner {\n color: #fff;\n}\n\n.gh-header.is-classic.has-image .gh-header-image {\n position: absolute;\n inset: 0;\n z-index: -1;\n width: 100%;\n height: 100%;\n object-fit: cover;\n}\n\n.gh-header.is-classic.has-image .gh-form {\n background-color: #fff;\n}\n\n/* 9. CTA\n/* ---------------------------------------------------------- */\n\n.gh-cta {\n display: none;\n margin-top: max(4vw, 40px);\n}\n\n.gh-header:is(.is-highlight, .is-magazine) + .gh-cta {\n display: block;\n}\n\n.gh-cta-inner {\n position: relative;\n overflow: hidden;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 36px;\n padding: clamp(48px, 3.64vw + 33.45px, 80px) clamp(40px, 2.73vw + 29.09px, 64px);\n text-align: center;\n background-color: var(--color-lighter-gray);\n}\n\n.gh-cta-content {\n max-width: 640px;\n}\n\n.gh-cta-title {\n font-size: clamp(2.8rem,1.36vw + 2.25rem,4rem);\n font-weight: 700;\n line-height: 1.1;\n letter-spacing: -0.021em;\n}\n\n.gh-cta-description {\n margin-top: 12px;\n font-size: 1.8rem;\n line-height: 1.4;\n letter-spacing: -0.015em;\n}\n\n/* 10. Featured posts\n/* ---------------------------------------------------------- */\n\n.gh-featured {\n margin-top: 100px;\n}\n\n.gh-navigation + .gh-featured {\n margin-top: 64px;\n}\n\n.gh-header.is-classic:not(.has-image) + .gh-featured {\n margin-top: 0;\n}\n\n.gh-featured-inner {\n overflow: hidden;\n}\n\n.gh-featured-title {\n margin-bottom: 20px;\n padding-bottom: 12px;\n font-size: 1.2rem;\n font-weight: 550;\n letter-spacing: 0.025em;\n text-transform: uppercase;\n border-bottom: 1px solid var(--color-border);\n}\n\n.gh-featured-feed {\n display: grid;\n grid-template-columns: repeat(4, 1fr);\n gap: var(--grid-gap);\n}\n\n.gh-featured-feed .gh-card {\n container-type: inline-size;\n container-name: featured-card;\n}\n\n.gh-featured-feed .gh-card-link {\n display: flex;\n flex-direction: row-reverse;\n align-items: flex-start;\n}\n\n.gh-featured-feed .gh-card-image {\n width: 72px;\n aspect-ratio: 1;\n}\n\n@container featured-card (width < 240px) {\n .gh-featured-feed .gh-card-image {\n display: none;\n }\n}\n\n@container featured-card (240px <= width <= 270px) {\n .gh-featured-feed .gh-card-image {\n width: 64px;\n }\n}\n\n.gh-featured-feed .gh-card-wrapper {\n container-type: inline-size;\n container-name: featured-card-wrapper;\n}\n\n.gh-featured-feed .gh-card-title {\n font-size: 1.6rem;\n font-weight: 650;\n letter-spacing: -0.011em;\n}\n\n@container featured-card-wrapper (width < 170px) {\n .gh-featured-feed .gh-card-title {\n font-size: 1.6rem;\n }\n}\n\n.gh-featured-feed .gh-card-excerpt {\n display: none;\n}\n\n.gh-featured-feed .gh-card-meta:not(:empty) {\n margin-top: 8px;\n}\n\n.gh-featured-feed .gh-card::before {\n position: absolute;\n top: 0;\n left: calc(var(--grid-gap) / -2);\n content: \"\";\n width: 1px;\n height: 100%;\n background-color: var(--color-border);\n}\n\n@media (max-width: 1199px) {\n .gh-viewport > .gh-featured .gh-featured-feed {\n grid-template-columns: repeat(3, 1fr);\n }\n\n .gh-viewport > .gh-featured .gh-featured-feed .gh-card:nth-child(4) {\n display: none;\n }\n}\n\n@media (max-width: 767px) {\n .gh-viewport > .gh-featured .gh-featured-feed {\n display: flex;\n flex-direction: column;\n }\n}\n\n/* 11. Container\n/* ---------------------------------------------------------- */\n\n.gh-container {\n flex-grow: 1;\n margin-top: 64px;\n}\n\n.gh-container-inner {\n display: grid;\n grid-template-columns: repeat(16, 1fr);\n column-gap: var(--grid-gap);\n}\n\n:is(.gh-featured, .gh-cta) + .gh-container {\n margin-top: max(4vw, 40px);\n}\n\n.gh-header.is-classic:not(.has-image) + .gh-container {\n margin-top: 0;\n}\n\n.gh-navigation + .gh-container .gh-container-title,\n:is(.paged, .tag-template, .author-template) .gh-container:not(.has-sidebar) .gh-container-title {\n display: none;\n}\n\n.gh-more {\n display: none;\n grid-column: 1 / -1;\n margin-top: 48px;\n font-size: calc(1.9rem * var(--factor, 1));\n font-weight: 725;\n letter-spacing: -0.014em;\n}\n\n.gh-container.has-sidebar .gh-more {\n grid-column: span 12;\n}\n\n.home-template .gh-feed:has(> :nth-child(12):last-child) ~ .gh-more {\n display: block;\n}\n\n.gh-more a {\n display: flex;\n align-items: center;\n gap: 4px;\n}\n\n.gh-more svg {\n margin-top: -1px;\n width: 18px;\n height: 18px;\n}\n\n.has-serif-title:not([class*=\" gh-font-heading\"]):not([class^=\"gh-font-heading\"]) .gh-more svg {\n margin-top: 0;\n}\n\n/* 11.1. With sidebar */\n\n.gh-container.has-sidebar .gh-main {\n grid-column: 1 / span 12;\n position: relative;\n}\n\n.gh-container.has-sidebar .gh-sidebar {\n grid-column: 13 / -1;\n}\n\n.gh-container.has-sidebar .gh-main::after {\n position: absolute;\n top: 0;\n right: calc(var(--grid-gap) / -2);\n content: \"\";\n width: 1px;\n height: 100%;\n background-color: var(--color-border);\n}\n\n/* 11.2. Without sidebar */\n\n.gh-container:not(.has-sidebar) :is(.gh-container-title, .gh-main, .gh-more) {\n grid-column: 3 / span 12;\n}\n\n.gh-container.is-list.no-image:not(.has-sidebar) :is(.gh-container-title, .gh-main, .gh-more) {\n grid-column: 4 / span 10;\n}\n\n.gh-header:is(.is-highlight, .is-magazine) ~ .gh-container.is-grid:not(.has-sidebar) :is(.gh-container-title, .gh-main, .gh-more) {\n grid-column: 1 / -1;\n}\n\n@media (max-width: 1199px) {\n .gh-container-inner {\n display: block;\n overflow: hidden;\n }\n\n .gh-container.has-sidebar .gh-sidebar {\n display: none;\n }\n}\n\n/* 12. Post list\n/* ---------------------------------------------------------- */\n\n.gh-container-title {\n grid-column: 1 / -1;\n margin-bottom: calc(var(--grid-gap) / 2);\n padding-bottom: 12px;\n font-size: 1.2rem;\n font-weight: 550;\n letter-spacing: 0.025em;\n text-transform: uppercase;\n border-bottom: 1px solid var(--color-border);\n}\n\n.gh-container:not(:has(.gh-card)) .gh-container-title {\n display: none;\n}\n\n.gh-container .gh-feed {\n gap: var(--grid-gap);\n}\n\n.gh-container .gh-card-meta:not(:empty) {\n margin-top: 12px;\n}\n\n/* 12.1. List style */\n\n.gh-container.is-list .gh-feed {\n display: flex;\n flex-direction: column;\n container-type: inline-size;\n container-name: list-feed;\n}\n\n.gh-container.is-list .gh-card-link {\n flex-direction: row;\n align-items: center;\n gap: 24px;\n}\n\n.gh-container.is-list .no-image .gh-card-link {\n padding-block: 20px;\n}\n\n.gh-container.is-list .gh-card-image {\n flex-shrink: 0;\n width: 220px;\n aspect-ratio: 1.618033;\n}\n\n@container list-feed (width < 600px) {\n .gh-container.is-list .gh-card-image {\n width: 160px;\n }\n}\n\n.gh-container.is-list .gh-card:not(.no-image) .gh-card-wrapper {\n max-width: 600px;\n}\n\n.gh-container.is-list .gh-card-title {\n --factor: 1.05;\n}\n\n.gh-container.is-list .no-image .gh-card-title {\n --factor: 1.2;\n}\n\n.gh-container.is-list .gh-card-excerpt {\n margin-top: 6px;\n}\n\n.gh-container.is-list .gh-card + .gh-card::before {\n position: absolute;\n top: calc(var(--grid-gap) / -2);\n left: 0;\n content: \"\";\n width: 100%;\n height: 1px;\n background-color: var(--color-border);\n}\n\n.home-template .gh-container.is-list .gh-card:first-child:before {\n display: none;\n}\n\n@media (max-width: 767px) {\n .gh-container.is-list .gh-card-link {\n flex-direction: column;\n align-items: flex-start;\n }\n\n .gh-container.is-list .gh-card-image {\n width: 100%;\n }\n}\n\n/* 12.2. Grid style */\n\n.gh-container.is-grid .gh-feed {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(248px, 1fr));\n row-gap: calc(var(--grid-gap) * 1.5);\n overflow: hidden;\n}\n\n.gh-container.is-grid .gh-card::before {\n position: absolute;\n top: calc(var(--grid-gap) / -2);\n right: calc(var(--grid-gap) / -2);\n left: calc(var(--grid-gap) / -2);\n content: \"\";\n height: 1px;\n background-color: var(--color-border);\n}\n\n.gh-container.is-grid .gh-card::after {\n position: absolute;\n top: 0;\n left: calc(var(--grid-gap) / -2);\n content: \"\";\n width: 1px;\n height: 100%;\n background-color: var(--color-border);\n}\n\n@media (max-width: 767px) {\n .gh-container.is-grid .gh-feed {\n grid-template-columns: repeat(2, 1fr);\n }\n}\n\n@media (max-width: 576px) {\n .gh-container.is-grid .gh-feed {\n display: flex;\n flex-direction: column;\n }\n}\n\n/* 12.3. No image list */\n\n.gh-container.is-list.no-image .gh-card-image {\n display: none;\n}\n\n/* 13. Sidebar\n/* ---------------------------------------------------------- */\n\n.gh-sidebar-inner {\n position: sticky;\n top: calc(var(--grid-gap) / 2);\n}\n\n.gh-sidebar-title {\n grid-column: 1 / -1;\n margin-bottom: calc(var(--grid-gap) / 2);\n padding-bottom: 12px;\n font-size: 1.2rem;\n font-weight: 550;\n letter-spacing: 0.025em;\n text-transform: uppercase;\n border-bottom: 1px solid var(--color-border);\n}\n\n.gh-about {\n display: flex;\n flex-direction: column;\n justify-content: center;\n align-items: center;\n padding: 48px 24px;\n min-height: 380px;\n text-align: center;\n background-color: var(--color-lighter-gray);\n}\n\n.gh-about-icon {\n margin-bottom: 24px;\n width: 60px;\n height: 60px;\n border-radius: 50%;\n}\n\n.gh-about-title {\n font-size: calc(2.4rem * var(--factor, 1));\n font-weight: 700;\n letter-spacing: -0.019em;\n}\n\n.gh-about-description {\n margin-top: 12px;\n font-size: 1.45rem;\n line-height: 1.4;\n}\n\n.gh-about .gh-button {\n margin-top: 32px;\n}\n\n.gh-recommendations {\n margin-top: 48px;\n}\n\n.gh-recommendations .recommendations {\n display: flex;\n flex-direction: column;\n gap: 26px;\n margin: 0;\n padding: 0;\n list-style-type: none;\n}\n\n.gh-recommendations .recommendation:first-child {\n margin-top: 4px;\n}\n\n.gh-recommendations .recommendation a {\n display: grid;\n grid-template-columns: 24px auto;\n gap: 4px 12px;\n}\n\n.gh-recommendations .recommendation a:hover {\n opacity: 1;\n}\n\n.gh-recommendations .recommendation-favicon {\n grid-row: span 2;\n width: 100%;\n border-radius: 4px;\n}\n\n.gh-recommendations .recommendation-title {\n margin-top: -2px;\n font-size: 1.5rem;\n font-weight: 650;\n letter-spacing: -0.009em;\n}\n\n.gh-recommendations .recommendation a:hover .recommendation-title {\n opacity: 0.8;\n}\n\n.gh-recommendations .recommendation-url {\n order: 1;\n overflow: hidden;\n font-size: 1.4rem;\n line-height: 1.25;\n color: var(--color-secondary-text);\n text-overflow: ellipsis;\n}\n\n.gh-recommendations .recommendation-description {\n display: -webkit-box;\n display: none;\n overflow-y: hidden;\n grid-column: 2;\n font-size: 1.4rem;\n line-height: 1.4;\n color: var(--color-secondary-text);\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n}\n\n.gh-recommendations button {\n display: inline-flex;\n align-items: center;\n gap: 2px;\n margin-top: 24px;\n padding: 0;\n cursor: pointer;\n font-size: 1.2rem;\n font-weight: 550;\n letter-spacing: 0.025em;\n text-transform: uppercase;\n background-color: transparent;\n border: 0;\n color: var(--color-darker-gray);\n}\n\n.gh-recommendations button svg {\n margin-top: -1px;\n width: 12px;\n}\n\n.gh-recommendations button:hover {\n opacity: 0.8;\n}\n\n/* 14. Post/page\n/* ---------------------------------------------------------- */\n\n/* 14.1. Article */\n\n.gh-article {\n --container-width: 1120px;\n\n word-break: break-word;\n}\n\n.gh-article-header {\n margin: clamp(40px, 3.64vw + 25.45px, 72px) 0 40px;\n}\n\n.gh-article-tag {\n margin-bottom: 12px;\n font-size: 1.3rem;\n font-weight: 500;\n letter-spacing: 0.01em;\n text-transform: uppercase;\n color: var(--ghost-accent-color);\n}\n\n.gh-article-title {\n font-size: calc(clamp(3.4rem, 1.36vw + 2.85rem, 4.6rem) * var(--factor, 1));\n line-height: 1.1;\n letter-spacing: -0.022em;\n}\n\n.gh-article-excerpt {\n margin-top: clamp(12px, 0.45vw + 10.18px, 16px);\n max-width: 720px;\n font-size: clamp(1.7rem, 0.23vw + 1.61rem, 1.9rem);\n line-height: 1.45;\n letter-spacing: -0.018em;\n}\n\n.gh-article-meta {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-left: 6px;\n}\n\n.gh-article-author-image {\n display: flex;\n margin-right: 8px;\n}\n\n.gh-article-author-image a {\n position: relative;\n margin: 0 -8px;\n width: 56px;\n height: 56px;\n overflow: hidden;\n background-color: var(--color-light-gray);\n border-radius: 50%;\n border: 3px solid var(--background-color);\n}\n\n.gh-article-author-image a:first-child {\n z-index: 10;\n}\n\n.gh-article-author-image a:nth-child(2) {\n z-index: 9;\n}\n\n.gh-article-author-image a:nth-child(3) {\n z-index: 8;\n}\n\n.gh-article-author-image :is(img, svg) {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n object-fit: cover;\n}\n\n.gh-article-meta-wrapper {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.gh-article-author-name {\n font-size: 1.6rem;\n font-weight: 650;\n letter-spacing: -0.013em;\n}\n\n.gh-article-meta-content {\n font-size: 1.35rem;\n font-weight: 500;\n line-height: 1.2;\n letter-spacing: -0.006em;\n color: var(--color-secondary-text);\n}\n\n.gh-article-image {\n grid-column: wide;\n margin-top: 40px;\n}\n\n.gh-article-image img {\n width: 100%;\n}\n\n/* 14.2. Page template */\n\n.page-template .gh-article-header {\n margin-block: 72px 32px;\n}\n\n/* 14.3. Page without header */\n\n.page-template .gh-content:only-child > *:first-child:not(.kg-width-full) {\n margin-top: 64px;\n}\n\n.page-template .gh-content > *:last-child:not(.kg-width-full) {\n margin-bottom: 6vw;\n}\n\n.page-template .gh-footer {\n margin-top: 0;\n}\n\n/* 15. Content\n/* ---------------------------------------------------------- */\n\n/* Content refers to styling all page and post content that is\ncreated within the Ghost editor. The main content handles\nheadings, text, images and lists. We deal with cards lower down. */\n\n.gh-content {\n font-size: var(--content-font-size, 1.7rem);\n letter-spacing: -0.01em;\n}\n\n/* Default vertical spacing */\n.gh-content > * + * {\n margin-top: calc(28px * var(--content-spacing-factor, 1));\n margin-bottom: 0;\n}\n\n/* Remove space between full-width cards */\n.gh-content > .kg-width-full + .kg-width-full:not(.kg-width-full.kg-card-hascaption + .kg-width-full) {\n margin-top: 0;\n}\n\n/* Add back a top margin to all headings,\nunless a heading is the very first element in the post content */\n.gh-content > [id]:not(:first-child) {\n margin-top: calc(56px * var(--content-spacing-factor, 1));\n}\n\n/* Add drop cap setting */\n.post-template .gh-content.drop-cap > p:first-of-type:first-letter {\n margin :0 0.2em 0em 0;\n font-size: 3.1em;\n float:left;\n line-height: 1;\n margin-left: -1px;\n font-weight: 700;\n}\n\n.has-serif-body.post-template:not([class*=\" gh-font-body\"]):not([class^=\"gh-font-body\"]) .gh-content.drop-cap > p:first-of-type:first-letter {\n font-size: 3.2em;\n}\n\n/* Add a small margin between a heading and paragraph after it */\n.gh-content > [id] + p {\n margin-top: calc(12px * var(--content-spacing-factor, 1));\n}\n\n/* A larger margin before/after dividers, blockquotes and embeds */\n.gh-content > :is(hr, blockquote, iframe) {\n position: relative;\n margin-top: calc(48px * var(--content-spacing-factor, 1)) !important;\n}\n\n.gh-content > :is(hr, blockquote, iframe) + * {\n margin-top: calc(48px * var(--content-spacing-factor, 1)) !important;\n}\n\n/* Now the content typography styles */\n.gh-content h1 {\n font-size: calc(2.2em * var(--factor, 1));\n letter-spacing: -0.02em;\n}\n\n.gh-content h2 {\n font-size: calc(1.6em * var(--factor, 1));\n letter-spacing: -0.02em;\n}\n\n.gh-content h3 {\n font-size: calc(1.3em * var(--factor, 1));\n letter-spacing: -0.017em;\n}\n\n.gh-content a {\n color: var(--ghost-accent-color);\n text-decoration: underline;\n}\n\n.gh-content .kg-callout-card .kg-callout-text,\n.gh-content .kg-toggle-card .kg-toggle-content > :is(ul, ol, p) {\n font-size: 0.95em;\n}\n\n.has-serif-body:not([class*=\" gh-font-body\"]):not([class^=\"gh-font-body\"]) .gh-content > blockquote,\n.has-serif-body:not([class*=\" gh-font-body\"]):not([class^=\"gh-font-body\"]) .gh-content > ol,\n.has-serif-body:not([class*=\" gh-font-body\"]):not([class^=\"gh-font-body\"]) .gh-content > ul,\n.has-serif-body:not([class*=\" gh-font-body\"]):not([class^=\"gh-font-body\"]) .gh-content > dl,\n.has-serif-body:not([class*=\" gh-font-body\"]):not([class^=\"gh-font-body\"]) .gh-content > p,\n.has-serif-body:not([class*=\" gh-font-body\"]):not([class^=\"gh-font-body\"]) .gh-content .kg-callout-text,\n.has-serif-body:not([class*=\" gh-font-body\"]):not([class^=\"gh-font-body\"]) .gh-content .kg-toggle-content > ol,\n.has-serif-body:not([class*=\" gh-font-body\"]):not([class^=\"gh-font-body\"]) .gh-content .kg-toggle-content > ul,\n.has-serif-body:not([class*=\" gh-font-body\"]):not([class^=\"gh-font-body\"]) .gh-content .kg-toggle-content > p {\n font-family: var(--font-serif-alt);\n}\n\n.gh-content :is(ul, ol) {\n padding-left: 28px;\n}\n\n.gh-content :is(li + li, li :is(ul, ol)) {\n margin-top: 8px;\n}\n\n.gh-content ol ol li {\n list-style-type: lower-alpha;\n}\n\n.gh-content ol ol ol li {\n list-style-type: lower-roman;\n}\n\n.gh-content hr {\n width: 100%;\n height: 1px;\n background-color: var(--color-border);\n border: 0;\n}\n\n.gh-content .gh-table {\n overflow-x: scroll;\n -webkit-overflow-scrolling: touch;\n}\n\n.gh-content .gh-table table {\n width: 100%;\n font-family: var(--font-sans);\n font-size: 1.5rem;\n white-space: nowrap;\n vertical-align: top;\n border-spacing: 0;\n border-collapse: collapse;\n}\n\n.gh-content .gh-table table th {\n font-size: 1.2rem;\n font-weight: 700;\n color: var(--color-darkgrey);\n text-align: left;\n text-transform: uppercase;\n letter-spacing: 0.2px;\n}\n\n.gh-content .gh-table table :is(th, td),\n.gh-content .gh-table table td {\n padding: 6px 12px;\n border-bottom: 1px solid var(--color-border);\n}\n\n.gh-content .gh-table table :is(th, td):first-child {\n padding-left: 0;\n}\n\n.gh-content .gh-table table :is(th, td):last-child {\n padding-right: 0;\n}\n\n.gh-content pre {\n overflow: auto;\n padding: 16px;\n font-size: 1.5rem;\n line-height: 1.5em;\n background: var(--color-lighter-gray);\n border-radius: 6px;\n font-family: var(--font-mono);\n}\n\n.gh-content :not(pre) > code {\n vertical-align: baseline;\n padding: 0.15em 0.4em;\n font-weight: 400;\n font-size: 0.95em;\n line-height: 1em;\n background: var(--color-lighter-gray);\n border-radius: 0.25em;\n font-family: var(--font-mono);\n}\n\n/* 16. Cards\n/* ---------------------------------------------------------- */\n\n/* Add extra margin before/after any cards, except for when immediately preceeded by a heading */\n\n.gh-content :not(.kg-card):not(table):not([id]) + :is(.kg-card, table) {\n margin-top: calc(48px * var(--content-spacing-factor, 1));\n}\n\n.gh-content :is(.kg-card, table) + :not(.kg-card):not(table):not([id]) {\n margin-top: calc(48px * var(--content-spacing-factor, 1));\n}\n\n.gh-content :not(.kg-card):not([id]) + .kg-card.kg-width-full {\n margin-top: calc(68px * var(--content-spacing-factor, 1));\n}\n\n.gh-content .kg-card.kg-width-full + :not(.kg-card):not([id]) {\n margin-top: calc(68px * var(--content-spacing-factor, 1));\n}\n\n/* Image */\n\n.kg-image {\n margin-right: auto;\n margin-left: auto;\n}\n\n/* Embed */\n\n.kg-embed-card {\n display: flex;\n flex-direction: column;\n align-items: center;\n width: 100%;\n}\n\n/* Gallery */\n\n.kg-image[width][height],\n.kg-gallery-image {\n cursor: pointer;\n}\n\n.kg-image-card a:hover,\n.kg-gallery-image a:hover {\n opacity: 1 !important;\n}\n\n/* Toggle */\n\n.kg-card.kg-toggle-card .kg-toggle-heading-text {\n font-size: 2rem;\n font-weight: 700;\n}\n\n.has-serif-title:not([class*=\" gh-font-heading\"]):not([class^=\"gh-font-heading\"]) .kg-toggle-card .kg-toggle-heading-text {\n font-family: var(--font-serif);\n}\n\n.has-mono-title:not([class*=\" gh-font-heading\"]):not([class^=\"gh-font-heading\"]) .kg-toggle-card .kg-toggle-heading-text {\n font-family: var(--font-mono);\n}\n\n/* Callout */\n\n.kg-callout-card.kg-card {\n border-radius: 0.25em;\n}\n\n.kg-callout-card-accent a {\n text-decoration: underline;\n}\n\n/* Blockquote */\n\nblockquote:not([class]) {\n padding-left: 2rem;\n border-left: 4px solid var(--ghost-accent-color);\n}\n\nblockquote.kg-blockquote-alt {\n font-style: normal;\n font-weight: 400;\n color: var(--color-secondary-text);\n}\n\n/* Button */\n\n.kg-card.kg-button-card .kg-btn {\n height: unset;\n padding: .6em 1.2em;\n text-align: center;\n font-size: 1em;\n line-height: 1.2em;\n}\n\n/* Header */\n\n.has-serif-title:not([class*=\" gh-font-heading\"]):not([class^=\"gh-font-heading\"]) .kg-card.kg-header-card h2.kg-header-card-header {\n font-family: var(--font-serif);\n}\n\n.has-mono-title:not([class*=\" gh-font-heading\"]):not([class^=\"gh-font-heading\"]) .kg-card.kg-header-card h2.kg-header-card-header {\n font-family: var(--font-mono);\n}\n\n.has-serif-body:not([class*=\" gh-font-body\"]):not([class^=\"gh-font-body\"]) .kg-header-card h3.kg-header-card-subheader {\n font-family: var(--font-serif);\n}\n\n/* Bookmark */\n\n.kg-bookmark-card .kg-bookmark-container {\n border-radius: 0.25em !important;\n}\n\n.kg-bookmark-card .kg-bookmark-container:hover {\n opacity: 1;\n}\n\n.kg-bookmark-card a.kg-bookmark-container,\n.kg-bookmark-card a.kg-bookmark-container:hover {\n background: var(--background-color) !important;\n color: var(--color-darker-gray) !important;\n}\n\n.kg-bookmark-card .kg-bookmark-content {\n padding: 1.15em;\n}\n\n.kg-bookmark-card .kg-bookmark-title {\n font-size: 0.9em;\n}\n\n.kg-bookmark-card .kg-bookmark-description {\n max-height: none;\n margin-top: 0.3em;\n font-size: 0.8em;\n}\n\n.kg-bookmark-card .kg-bookmark-metadata {\n font-size: 0.8em;\n}\n\n.kg-bookmark-card .kg-bookmark-thumbnail img {\n border-radius: 0 0.2em 0.2em 0;\n}\n\n/* Product */\n\n.kg-product-card.kg-card .kg-product-card-image {\n margin-bottom: 12px;\n}\n\n.kg-product-card.kg-card a.kg-product-card-button {\n height: 2.8em;\n margin-top: 12px;\n}\n\n.has-serif-title:not([class*=\" gh-font-heading\"]):not([class^=\"gh-font-heading\"]) .kg-product-card.kg-card .kg-product-card-title {\n font-family: var(--font-serif);\n}\n\n.has-mono-title:not([class*=\" gh-font-heading\"]):not([class^=\"gh-font-heading\"]) .kg-product-card.kg-card .kg-product-card-title {\n font-family: var(--font-mono);\n}\n\n.has-serif-body:not([class*=\" gh-font-body\"]):not([class^=\"gh-font-body\"]) .kg-product-card-description :is(p, ul, ol) {\n font-family: var(--font-serif-alt);\n}\n\n/* File */\n\n.kg-file-card.kg-card .kg-file-card-container {\n padding: 0.6em;\n}\n\n.kg-file-card.kg-card .kg-file-card-contents {\n margin: 4px 8px 6px;\n}\n\n.kg-file-card.kg-card .kg-file-card-metadata {\n font-size: 0.9em;\n}\n\n.kg-file-card.kg-card .kg-file-card-filesize::before {\n margin-right: 6px;\n margin-left: 6px;\n}\n\n/* Caption */\n\nfigcaption {\n margin-top: 12px;\n font-size: 1.4rem;\n text-align: center;\n}\n\n.kg-card.kg-width-full figcaption {\n padding: 0 16px;\n}\n\nfigcaption a {\n color: var(--ghost-accent-color);\n text-decoration: underline;\n}\n\n/* 17. Comments\n/* ---------------------------------------------------------- */\n\n.gh-comments {\n margin-top: 48px;\n}\n\n/* 18. Recent posts\n/* ---------------------------------------------------------- */\n\n.post-template .gh-container {\n margin-top: 120px;\n}\n\n.post-template .gh-container-inner {\n display: block;\n}\n\n.post-template .gh-container.is-grid .gh-feed {\n grid-template-columns: repeat(4, 1fr);\n}\n\n.post-template .gh-container .gh-container-title {\n display: block;\n}\n\n/* 19. Archive\n/* ---------------------------------------------------------- */\n\n.gh-archive {\n display: grid;\n grid-template-columns: repeat(16, 1fr);\n gap: var(--grid-gap);\n margin-block: 80px 24px;\n}\n\n.gh-archive.has-image {\n margin-top: 48px;\n}\n\n.gh-archive-inner {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: var(--grid-gap);\n grid-column: 1 / -1;\n padding-bottom: clamp(40px, 2.73vw + 29.09px, 64px);\n border-bottom: 1px solid var(--color-border);\n}\n\n.gh-archive.has-image .gh-archive-inner {\n align-items: center;\n grid-column: 1 / -1;\n}\n\n.gh-archive:not(.has-sidebar):not(.has-image) .gh-archive-inner {\n grid-column: 3 / span 12;\n}\n\n.gh-archive .gh-article-image {\n grid-column: auto;\n margin-top: 0;\n}\n\n:is(.tag-template, .author-template) .gh-container {\n margin-top: 0;\n}\n\n.author-template .gh-archive-inner {\n display: flex;\n flex-direction: row-reverse;\n justify-content: flex-end;\n gap: 24px;\n}\n\n.author-template .gh-article-image {\n margin-top: 0;\n width: 120px;\n height: 120px;\n border-radius: 50%;\n object-fit: cover;\n}\n\n.author-template .gh-article-title {\n font-size: 3.6rem;\n}\n\n.gh-author-meta {\n display: flex;\n gap: 10px;\n margin-top: 14px;\n font-size: 1.5rem;\n font-weight: 550;\n color: var(--color-secondary-text);\n}\n\n.gh-author-meta a {\n color: inherit;\n}\n\n.gh-author-social {\n display: flex;\n gap: 16px;\n align-items: center;\n padding-left: 3px;\n}\n\n.gh-author-social svg {\n width: 20px;\n height: 20px;\n}\n\n@media (max-width: 1199px) {\n .gh-archive {\n display: block;\n }\n}\n\n@media (max-width: 767px) {\n .gh-archive-inner {\n display: flex;\n flex-direction: column-reverse;\n align-items: flex-start;\n }\n\n .author-template .gh-archive-inner {\n flex-direction: column-reverse;\n }\n}\n\n/* 20. Design settings\n/* ---------------------------------------------------------- */\n\n.has-serif-title:not([class*=\" gh-font-heading\"]):not([class^=\"gh-font-heading\"]) {\n --factor: 1.15;\n}\n\n.has-mono-title:not([class*=\" gh-font-heading\"]):not([class^=\"gh-font-heading\"]) {\n --factor: 1.1;\n}\n\n.has-sans-title:not([class*=\" gh-font-heading\"]):not([class^=\"gh-font-heading\"]) :is(.is-title, .gh-content :is(h2, h3)) {\n font-family: var(--gh-font-heading, var(--font-sans));\n}\n\n.has-serif-title:not([class*=\" gh-font-heading\"]):not([class^=\"gh-font-heading\"]) :is(.is-title, .gh-content :is(h2, h3)) {\n font-family: var(--gh-font-heading, var(--font-serif));\n font-weight: 550;\n}\n\n.has-mono-title:not([class*=\" gh-font-heading\"]):not([class^=\"gh-font-heading\"]) :is(.is-title, .gh-content :is(h2, h3)) {\n font-family: var(--gh-font-heading, var(--font-mono));\n}\n\n.has-sans-body:not([class*=\" gh-font-body\"]):not([class^=\"gh-font-body\"]) .is-body {\n font-family: var(--gh-font-body, var(--font-sans));\n}\n\n.has-serif-body:not([class*=\" gh-font-body\"]):not([class^=\"gh-font-body\"]) .is-body {\n font-family: var(--gh-font-body, var(--font-serif-alt));\n}\n\n.has-serif-title:not([class*=\" gh-font-heading\"]):not([class^=\"gh-font-heading\"]) .gh-header.is-classic .gh-header-title {\n font-weight: 550;\n letter-spacing: -0.015em;\n}\n\n.has-mono-title:not([class*=\" gh-font-heading\"]):not([class^=\"gh-font-heading\"]) .gh-header.is-classic .gh-header-title {\n letter-spacing: -0.01em;\n}\n\n.has-serif-title:not([class*=\" gh-font-heading\"]):not([class^=\"gh-font-heading\"]) .gh-form {\n border-radius: 0;\n}\n\n.has-serif-title:not([class*=\" gh-font-heading\"]):not([class^=\"gh-font-heading\"]) .gh-card-title {\n line-height: 1.15;\n letter-spacing: -0.006em;\n font-size: calc(2.0rem*var(--factor, 1))\n}\n\n.has-serif-title:not([class*=\" gh-font-heading\"]):not([class^=\"gh-font-heading\"]) .gh-featured-feed .gh-card-title {\n font-size: calc(1.6rem*var(--factor, 1))\n}\n\n.has-mono-title:not([class*=\" gh-font-heading\"]):not([class^=\"gh-font-heading\"]) .gh-featured-feed .gh-card-title {\n font-size: calc(1.5rem*var(--factor, 1));\n letter-spacing: 0;\n}\n\n.has-serif-title:not([class*=\" gh-font-heading\"]):not([class^=\"gh-font-heading\"]) .gh-header.is-highlight .gh-featured-feed .gh-card-title {\n font-size: clamp(1.6rem, 0.23vw + 1.51rem, 1.8rem);\n}\n\n.has-mono-title:not([class*=\" gh-font-heading\"]):not([class^=\"gh-font-heading\"]) .gh-card-title {\n font-size: calc(1.8rem*var(--factor, 1));\n line-height: 1.2;\n}\n\n.has-serif-title:not([class*=\" gh-font-heading\"]):not([class^=\"gh-font-heading\"]) .gh-about-title {\n letter-spacing: -0.009em;\n}\n\n.has-serif-title:not([class*=\" gh-font-heading\"]):not([class^=\"gh-font-heading\"]) .gh-footer-signup-header {\n letter-spacing: -0.019em;\n}\n\n.has-serif-title:not([class*=\" gh-font-heading\"]):not([class^=\"gh-font-heading\"]) .gh-article-title {\n letter-spacing: -0.019em;\n}\n\n.has-serif-body:not([class*=\" gh-font-body\"]):not([class^=\"gh-font-body\"]) {\n --content-font-size: 1.9rem;\n}\n\n.has-serif-body:not([class*=\" gh-font-body\"]):not([class^=\"gh-font-body\"]) .gh-card-excerpt {\n font-size: 1.65rem;\n line-height: 1.4;\n letter-spacing: 0.0005em;\n}\n\n.has-serif-body:not([class*=\" gh-font-body\"]):not([class^=\"gh-font-body\"]) .gh-header.is-magazine .gh-header-inner > .gh-card .gh-card-excerpt,\n.has-serif-body:not([class*=\" gh-font-body\"]):not([class^=\"gh-font-body\"]) .gh-header.is-highlight .gh-card:first-child .gh-card-excerpt {\n font-size: 1.8rem;\n letter-spacing: -0.001em;\n}\n\n.has-serif-title:not([class*=\" gh-font-heading\"]):not([class^=\"gh-font-heading\"]) .gh-header.is-magazine .gh-header-inner>.gh-card .gh-card-title,\n.has-serif-title:not([class*=\" gh-font-heading\"]):not([class^=\"gh-font-heading\"]) .gh-header.is-highlight .gh-header-left .gh-card-title {\n font-weight: 550;\n font-size: clamp(3.2rem,1.82vw + 2.47rem,4.9rem)\n}\n\n.has-serif-body:not([class*=\" gh-font-body\"]):not([class^=\"gh-font-body\"]) .gh-about-description {\n font-size: 1.6rem;\n}\n\n.has-serif-body:not([class*=\" gh-font-body\"]):not([class^=\"gh-font-body\"]) .gh-article-excerpt {\n letter-spacing: 0;\n}\n\n.has-serif-body:not([class*=\" gh-font-body\"]):not([class^=\"gh-font-body\"]) .gh-footer-signup-subhead {\n letter-spacing: 0;\n}\n\n:is(.has-serif-title,.has-mono-title):not([class*=\" gh-font-heading\"]):not([class^=\"gh-font-heading\"]) :is(.gh-button, .gh-form, .gh-form-input),\nbody[class*=\" gh-font-heading\"]:not(.gh-font-heading-fira-sans):not(.gh-font-heading-inter):not(.gh-font-heading-manrope):not(.gh-font-heading-noto-sans):not(.gh-font-heading-nunito):not(.gh-font-heading-poppins):not(.gh-font-heading-roboto):not(.gh-font-heading-space-grotesk) :is(.gh-button, .gh-form, .gh-form-input),\nbody[class^=\"gh-font-heading\"]:not(.gh-font-heading-fira-sans):not(.gh-font-heading-inter):not(.gh-font-heading-manrope):not(.gh-font-heading-noto-sans):not(.gh-font-heading-nunito):not(.gh-font-heading-poppins):not(.gh-font-heading-roboto):not(.gh-font-heading-space-grotesk) :is(.gh-button, .gh-form, .gh-form-input) {\n border-radius: 0;\n}\n\n.has-serif-title:not([class*=\" gh-font-heading\"]):not([class^=\"gh-font-heading\"]) .gh-cta-title {\n font-size: 4.8rem;\n}\n\n/* 21. Footer\n/* ---------------------------------------------------------- */\n\n.gh-footer {\n margin-top: 120px;\n font-size: 1.5rem;\n color: var(--color-darker-gray);\n}\n\n.gh-footer a:not(.gh-button) {\n color: inherit;\n}\n\n/* 21.1 Footer styles */\n\n.gh-footer.has-accent-color {\n background-color: var(--ghost-accent-color);\n}\n\n.gh-footer.has-accent-color .gh-footer-bar {\n border-top: 0;\n}\n\n.gh-footer.has-accent-color .gh-button {\n background-color: #fff;\n color: #15171a;\n}\n\n/* 21.2. Footer bar */\n\n.gh-footer-bar {\n display: grid;\n grid-template-columns: 1fr auto 1fr;\n align-items: center;\n column-gap: 32px;\n margin-bottom: 120px;\n padding-block: 28px;\n font-weight: 550;\n border-block: 1px solid var(--color-border);\n}\n\n.gh-footer-logo {\n position: relative;\n font-family: var(--gh-font-heading, var(--font-sans));\n font-size: calc(2.4rem * var(--factor, 1));\n font-weight: 725;\n letter-spacing: -0.015em;\n white-space: nowrap;\n}\n\n.gh-footer-logo img {\n max-height: 40px;\n}\n\n.gh-footer-menu .nav {\n display: flex;\n justify-content: center;\n gap: 8px 28px;\n flex-wrap: wrap;\n margin: 0;\n padding: 0;\n list-style-type: none;\n}\n\n.gh-footer-copyright {\n text-align: right;\n white-space: nowrap;\n}\n\n.gh-footer-copyright a {\n text-decoration: underline;\n}\n\n/* 21.3. Footer signup */\n\n.gh-footer-signup {\n display: flex;\n flex-direction: column;\n align-items: center;\n margin-bottom: 120px;\n text-align: center;\n}\n\n.gh-footer-signup-header {\n font-size: calc(clamp(2.8rem,1.36vw + 2.25rem,4rem) * var(--factor, 1));\n font-weight: 700;\n letter-spacing: -0.03em;\n}\n\n.gh-footer-signup-subhead {\n margin-top: 12px;\n max-width: 640px;\n font-size: 1.8rem;\n font-weight: 450;\n line-height: 1.4;\n letter-spacing: -0.014em;\n opacity: 0.75;\n}\n\n.gh-footer-signup .gh-form {\n margin-top: 40px;\n}\n\n@media (max-width: 991px) {\n .gh-footer-bar {\n display: flex;\n flex-direction: column;\n gap: 20px;\n }\n}\n\n/* 21.4. Social links */\n\n.gh-social-links {\n display: flex;\n justify-content: center;\n gap: 20px;\n margin-bottom: 32px;\n}\n\n.gh-social-links a {\n will-change: opacity;\n}\n\n.gh-social-links svg {\n width: 20px;\n height: 20px;\n}\n\n/* 22. Lightbox\n/* ---------------------------------------------------------- */\n\n.pswp {\n position: absolute;\n top: 0;\n left: 0;\n z-index: 3999999;\n display: none;\n width: 100%;\n height: 100%;\n overflow: hidden;\n -ms-touch-action: none;\n touch-action: none;\n outline: none;\n backface-visibility: hidden;\n -webkit-text-size-adjust: 100%;\n}\n\n.pswp img {\n max-width: none;\n}\n\n.pswp--animate_opacity {\n opacity: 0.001;\n transition: opacity 333ms cubic-bezier(0.4, 0, 0.22, 1);\n will-change: opacity;\n}\n\n.pswp--open {\n display: block;\n}\n\n.pswp--zoom-allowed .pswp__img {\n cursor: zoom-in;\n}\n\n.pswp--zoomed-in .pswp__img {\n cursor: grab;\n}\n\n.pswp--dragging .pswp__img {\n cursor: grabbing;\n}\n\n.pswp__bg {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background-color: rgba(0, 0, 0, 0.85);\n opacity: 0;\n transition: opacity 333ms cubic-bezier(0.4, 0, 0.22, 1);\n transform: translateZ(0);\n backface-visibility: hidden;\n will-change: opacity;\n}\n\n.pswp__scroll-wrap {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n overflow: hidden;\n}\n\n.pswp__container,\n.pswp__zoom-wrap {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n touch-action: none;\n backface-visibility: hidden;\n}\n\n.pswp__container,\n.pswp__img {\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n -webkit-touch-callout: none;\n}\n\n.pswp__zoom-wrap {\n position: absolute;\n width: 100%;\n transition: transform 333ms cubic-bezier(0.4, 0, 0.22, 1);\n transform-origin: left top;\n}\n\n.pswp--animated-in .pswp__bg,\n.pswp--animated-in .pswp__zoom-wrap {\n transition: none;\n}\n\n.pswp__item {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n overflow: hidden;\n}\n\n.pswp__img {\n position: absolute;\n top: 0;\n left: 0;\n width: auto;\n height: auto;\n}\n\n.pswp__img--placeholder {\n backface-visibility: hidden;\n}\n\n.pswp__img--placeholder--blank {\n background: var(--color-black);\n}\n\n.pswp--ie .pswp__img {\n top: 0;\n left: 0;\n width: 100% !important;\n height: auto !important;\n}\n\n.pswp__error-msg {\n position: absolute;\n top: 50%;\n left: 0;\n width: 100%;\n margin-top: -8px;\n font-size: 14px;\n line-height: 16px;\n color: var(--color-secondary-text);\n text-align: center;\n}\n\n.pswp__error-msg a {\n color: var(--color-secondary-text);\n text-decoration: underline;\n}\n\n.pswp__button {\n position: relative;\n display: block;\n float: right;\n width: 44px;\n height: 44px;\n padding: 0;\n margin: 0;\n overflow: visible;\n appearance: none;\n cursor: pointer;\n background: none;\n border: 0;\n box-shadow: none;\n transition: opacity 0.2s;\n}\n\n.pswp__button:focus,\n.pswp__button:hover {\n opacity: 1;\n}\n\n.pswp__button:active {\n outline: none;\n opacity: 0.9;\n}\n\n.pswp__button::-moz-focus-inner {\n padding: 0;\n border: 0;\n}\n\n.pswp__ui--over-close .pswp__button--close {\n opacity: 1;\n}\n\n.pswp__button,\n.pswp__button--arrow--left::before,\n.pswp__button--arrow--right::before {\n width: 44px;\n height: 44px;\n background: url(\"../images/default-skin.png\") 0 0 no-repeat;\n background-size: 264px 88px;\n}\n\n@media (-webkit-min-device-pixel-ratio: 1.1), (-webkit-min-device-pixel-ratio: 1.09375), (min-resolution: 105dpi), (min-resolution: 1.1dppx) {\n .pswp--svg .pswp__button,\n .pswp--svg .pswp__button--arrow--left::before,\n .pswp--svg .pswp__button--arrow--right::before {\n background-image: url(\"../images/default-skin.svg\");\n }\n\n .pswp--svg .pswp__button--arrow--left,\n .pswp--svg .pswp__button--arrow--right {\n background: none;\n }\n}\n\n.pswp__button--close {\n background-position: 0 -44px;\n}\n\n.pswp__button--share {\n background-position: -44px -44px;\n}\n\n.pswp__button--fs {\n display: none;\n}\n\n.pswp--supports-fs .pswp__button--fs {\n display: block;\n}\n\n.pswp--fs .pswp__button--fs {\n background-position: -44px 0;\n}\n\n.pswp__button--zoom {\n display: none;\n background-position: -88px 0;\n}\n\n.pswp--zoom-allowed .pswp__button--zoom {\n display: block;\n}\n\n.pswp--zoomed-in .pswp__button--zoom {\n background-position: -132px 0;\n}\n\n.pswp--touch .pswp__button--arrow--left,\n.pswp--touch .pswp__button--arrow--right {\n visibility: hidden;\n}\n\n.pswp__button--arrow--left,\n.pswp__button--arrow--right {\n position: absolute;\n top: 50%;\n width: 70px;\n height: 100px;\n margin-top: -50px;\n background: none;\n}\n\n.pswp__button--arrow--left {\n left: 0;\n}\n\n.pswp__button--arrow--right {\n right: 0;\n}\n\n.pswp__button--arrow--left::before,\n.pswp__button--arrow--right::before {\n position: absolute;\n top: 35px;\n width: 32px;\n height: 30px;\n content: \"\";\n}\n\n.pswp__button--arrow--left::before {\n left: 6px;\n background-position: -138px -44px;\n}\n\n.pswp__button--arrow--right::before {\n right: 6px;\n background-position: -94px -44px;\n}\n\n.pswp__counter {\n position: absolute;\n top: 0;\n left: 0;\n height: 44px;\n padding: 0 15px;\n font-size: 11px;\n font-weight: 700;\n line-height: 44px;\n color: var(--color-white);\n user-select: none;\n}\n\n.pswp__caption {\n position: absolute;\n bottom: 0;\n left: 0;\n width: 100%;\n min-height: 44px;\n}\n\n.pswp__caption__center {\n max-width: 420px;\n padding: 25px 15px 30px;\n margin: 0 auto;\n font-size: 11px;\n line-height: 1.6;\n color: var(--color-white);\n text-align: center;\n}\n\n.pswp__caption__center .post-caption-title {\n margin-bottom: 7px;\n font-size: 15px;\n font-weight: 500;\n text-transform: uppercase;\n}\n\n.pswp__caption__center .post-caption-meta-item + .post-caption-meta-item::before {\n padding: 0 4px;\n content: \"\\02022\";\n}\n\n.pswp__caption--empty {\n display: none;\n}\n\n.pswp__caption--fake {\n visibility: hidden;\n}\n\n.pswp__preloader {\n position: absolute;\n top: 0;\n left: 50%;\n width: 44px;\n height: 44px;\n margin-left: -22px;\n opacity: 0;\n transition: opacity 0.25s ease-out;\n direction: ltr;\n will-change: opacity;\n}\n\n.pswp__preloader__icn {\n width: 20px;\n height: 20px;\n margin: 12px;\n}\n\n.pswp__preloader--active {\n opacity: 1;\n}\n\n.pswp__preloader--active .pswp__preloader__icn {\n background: url(\"../images/preloader.gif\") 0 0 no-repeat;\n}\n\n.pswp--css_animation .pswp__preloader--active {\n opacity: 1;\n}\n\n.pswp--css_animation .pswp__preloader--active .pswp__preloader__icn {\n animation: clockwise 500ms linear infinite;\n}\n\n.pswp--css_animation .pswp__preloader--active .pswp__preloader__donut {\n animation: donut-rotate 1000ms cubic-bezier(0.4, 0, 0.22, 1) infinite;\n}\n\n.pswp--css_animation .pswp__preloader__icn {\n position: absolute;\n top: 15px;\n left: 15px;\n width: 14px;\n height: 14px;\n margin: 0;\n background: none;\n opacity: 0.75;\n}\n\n.pswp--css_animation .pswp__preloader__cut {\n position: relative;\n width: 7px;\n height: 14px;\n overflow: hidden;\n}\n\n.pswp--css_animation .pswp__preloader__donut {\n position: absolute;\n top: 0;\n left: 0;\n box-sizing: border-box;\n width: 14px;\n height: 14px;\n margin: 0;\n background: none;\n border: 2px solid var(--color-white);\n border-bottom-color: transparent;\n border-left-color: transparent;\n border-radius: 50%;\n}\n\n@media screen and (max-width: 1024px) {\n .pswp__preloader {\n position: relative;\n top: auto;\n left: auto;\n float: right;\n margin: 0;\n }\n}\n\n@keyframes clockwise {\n 0% {\n transform: rotate(0deg);\n }\n\n 100% {\n transform: rotate(360deg);\n }\n}\n\n@keyframes donut-rotate {\n 0% {\n transform: rotate(0);\n }\n\n 50% {\n transform: rotate(-140deg);\n }\n\n 100% {\n transform: rotate(0);\n }\n}\n\n.pswp__ui {\n z-index: 1550;\n visibility: visible;\n opacity: 1;\n -webkit-font-smoothing: auto;\n}\n\n.pswp__top-bar {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 44px;\n}\n\n.pswp__caption,\n.pswp__top-bar,\n.pswp--has_mouse .pswp__button--arrow--left,\n.pswp--has_mouse .pswp__button--arrow--right {\n backface-visibility: hidden;\n transition: opacity 333ms cubic-bezier(0.4, 0, 0.22, 1);\n will-change: opacity;\n}\n\n.pswp--has_mouse .pswp__button--arrow--left,\n.pswp--has_mouse .pswp__button--arrow--right {\n visibility: visible;\n}\n\n.pswp__ui--idle .pswp__top-bar {\n opacity: 0;\n}\n\n.pswp__ui--idle .pswp__button--arrow--left,\n.pswp__ui--idle .pswp__button--arrow--right {\n opacity: 0;\n}\n\n.pswp__ui--hidden .pswp__top-bar,\n.pswp__ui--hidden .pswp__caption,\n.pswp__ui--hidden .pswp__button--arrow--left,\n.pswp__ui--hidden .pswp__button--arrow--right {\n opacity: 0.001;\n}\n\n.pswp__ui--one-slide .pswp__button--arrow--left,\n.pswp__ui--one-slide .pswp__button--arrow--right,\n.pswp__ui--one-slide .pswp__counter {\n display: none;\n}\n\n.pswp__element--disabled {\n display: none !important;\n}\n\n.pswp--minimal--dark .pswp__top-bar {\n background: none;\n}\n\n/* SHARE */\n.gh-meta-share {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-top: 20px;\n}\n\n.has-light-text .gh-button-share {\n border-color: rgba(255, 255, 255, 0.25);\n color: rgba(255, 255, 255, 0.85);\n}\n\n.has-light-text .gh-button-share:hover {\n border-color: rgba(255, 255, 255, 0.85);\n}\n\n.gh-button-share {\n display: inline-flex;\n align-items: center;\n border: 1px solid rgba(0, 0, 0, 0.15);\n border-radius: 999px;\n color: rgba(0, 0, 0, 0.85);\n background: transparent;\n cursor: pointer;\n transition: all 0.4s ease;\n}\n\n.gh-button-share:hover {\n border-color: rgba(0, 0, 0, 0.5);\n}\n"]} \ No newline at end of file diff --git a/ghost-me/theme-source-personal/assets/built/source.js b/ghost-me/theme-source-personal/assets/built/source.js deleted file mode 100644 index 1b94003..0000000 --- a/ghost-me/theme-source-personal/assets/built/source.js +++ /dev/null @@ -1 +0,0 @@ -function dropdown(){let r=window.matchMedia("(max-width: 767px)"),a=document.querySelector(".gh-navigation"),s=a.querySelector(".gh-navigation-menu"),l=s?.querySelector(".nav");if(l){let e=document.querySelector(".gh-navigation-logo"),t=l.innerHTML;r.matches&&l.querySelectorAll("li").forEach(function(e,t){e.style.transitionDelay=.03*(t+1)+"s"});let n=function(){if(!r.matches){for(var e=[];l.offsetWidth+64>s.offsetWidth&&l.lastElementChild;)e.unshift(l.lastElementChild),l.lastElementChild.remove();if(e.length){let t=document.createElement("button"),n=(t.setAttribute("class","gh-more-toggle gh-icon-button"),t.setAttribute("aria-label","More"),t.innerHTML='',document.createElement("div"));n.setAttribute("class","gh-dropdown"),10<=e.length?(a.classList.add("is-dropdown-mega"),n.style.gridTemplateRows=`repeat(${Math.ceil(e.length/2)}, 1fr)`):a.classList.remove("is-dropdown-mega"),e.forEach(function(e){n.appendChild(e)}),t.appendChild(n),l.appendChild(t);var o=t.getBoundingClientRect(),i=window.innerWidth/2;o.left{l.innerHTML=t,n()},1)})}}function lightbox(e){document.querySelectorAll(e).forEach(function(e){e.addEventListener("click",function(e){var t=e;t.preventDefault();for(var n,o=[],i=0,r=t.target.closest(".kg-card").previousElementSibling;r&&(r.classList.contains("kg-image-card")||r.classList.contains("kg-gallery-card"));){var a=[];r.querySelectorAll("img").forEach(function(e){a.push({src:e.getAttribute("src"),msrc:e.getAttribute("src"),w:e.getAttribute("width"),h:e.getAttribute("height"),el:e}),i+=1}),r=r.previousElementSibling,o=a.concat(o)}t.target.classList.contains("kg-image")?o.push({src:t.target.getAttribute("src"),msrc:t.target.getAttribute("src"),w:t.target.getAttribute("width"),h:t.target.getAttribute("height"),el:t.target}):(n=!1,t.target.closest(".kg-gallery-card").querySelectorAll("img").forEach(function(e){o.push({src:e.getAttribute("src"),msrc:e.getAttribute("src"),w:e.getAttribute("width"),h:e.getAttribute("height"),el:e}),n||e===t.target?n=!0:i+=1}));for(var s=t.target.closest(".kg-card").nextElementSibling;s&&(s.classList.contains("kg-image-card")||s.classList.contains("kg-gallery-card"));)s.querySelectorAll("img").forEach(function(e){o.push({src:e.getAttribute("src"),msrc:e.getAttribute("src"),w:e.getAttribute("width"),h:e.getAttribute("height"),el:e})}),s=s.nextElementSibling;e=document.querySelectorAll(".pswp")[0];new PhotoSwipe(e,PhotoSwipeUI_Default,o,{bgOpacity:.9,closeOnScroll:!0,fullscreenEl:!1,history:!1,index:i,shareEl:!1,zoomEl:!1,getThumbBoundsFn:function(e){var e=o[e].el,t=window.pageYOffset||document.documentElement.scrollTop,e=e.getBoundingClientRect();return{x:e.left,y:e.top+t,w:e.width}}}).init()})})}function pagination(e=!0,s,l=!1){let c=document.querySelector(".gh-feed");if(!c)return;let t=!1,n=document.querySelector(".gh-footer"),u=document.querySelector(".gh-loadmore"),o=(!document.querySelector("link[rel=next]")&&u&&u.remove(),async function(){var o=document.querySelector("link[rel=next]");if(o)try{var e=await(await fetch(o.href)).text(),i=(new DOMParser).parseFromString(e,"text/html"),r=i.querySelectorAll(".gh-feed:not(.gh-featured):not(.gh-related) > *");let t=document.createDocumentFragment(),n=[];r.forEach(function(e){e=document.importNode(e,!0);l&&(e.style.visibility="hidden"),t.appendChild(e),n.push(e)}),c.appendChild(t),s&&s(n,d);var a=i.querySelector("link[rel=next]");a&&a.href?o.href=a.href:(o.remove(),u&&u.remove())}catch(e){throw o.remove(),e}}),d=async function(){n.getBoundingClientRect().top<=window.innerHeight&&document.querySelector("link[rel=next]")&&await o()};let i=new IntersectionObserver(async function(e){if(!t){if(t=!0,e[0].isIntersecting)if(l)await o();else for(;n.getBoundingClientRect().top<=window.innerHeight&&document.querySelector("link[rel=next]");)await o();t=!1,document.querySelector("link[rel=next]")||i.disconnect()}});e?i.observe(n):u.addEventListener("click",o)}((e,t)=>{"function"==typeof define&&define.amd?define("ev-emitter/ev-emitter",t):"object"==typeof module&&module.exports?module.exports=t():e.EvEmitter=t()})("undefined"!=typeof window?window:this,function(){function e(){}var t=e.prototype;return t.on=function(e,t){var n;if(e&&t)return-1==(n=(n=this._events=this._events||{})[e]=n[e]||[]).indexOf(t)&&n.push(t),this},t.once=function(e,t){var n;if(e&&t)return this.on(e,t),((n=this._onceEvents=this._onceEvents||{})[e]=n[e]||{})[t]=!0,this},t.off=function(e,t){e=this._events&&this._events[e];if(e&&e.length)return-1!=(t=e.indexOf(t))&&e.splice(t,1),this},t.emitEvent=function(e,t){var n=this._events&&this._events[e];if(n&&n.length){n=n.slice(0),t=t||[];for(var o=this._onceEvents&&this._onceEvents[e],i=0;i{"function"==typeof define&&define.amd?define(["ev-emitter/ev-emitter"],function(e){return n(t,e)}):"object"==typeof module&&module.exports?module.exports=n(t,require("ev-emitter")):t.imagesLoaded=n(t,t.EvEmitter)})("undefined"!=typeof window?window:this,function(t,e){function r(e,t){for(var n in t)e[n]=t[n];return e}function a(e,t,n){var o,i;return this instanceof a?(o="string"==typeof(o=e)?document.querySelectorAll(e):o)?(this.elements=(i=o,Array.isArray(i)?i:"object"==typeof i&&"number"==typeof i.length?c.call(i):[i]),this.options=r({},this.options),"function"==typeof t?n=t:r(this.options,t),n&&this.on("always",n),this.getImages(),s&&(this.jqDeferred=new s.Deferred),void setTimeout(this.check.bind(this))):void l.error("Bad element for imagesLoaded "+(o||e)):new a(e,t,n)}function n(e){this.img=e}function o(e,t){this.url=e,this.element=t,this.img=new Image}var s=t.jQuery,l=t.console,c=Array.prototype.slice,u=((a.prototype=Object.create(e.prototype)).options={},a.prototype.getImages=function(){this.images=[],this.elements.forEach(this.addElementImages,this)},a.prototype.addElementImages=function(e){"IMG"==e.nodeName&&this.addImage(e),!0===this.options.background&&this.addElementBackgroundImages(e);var t=e.nodeType;if(t&&u[t]){for(var n=e.querySelectorAll("img"),o=0;o{"function"==typeof define&&define.amd?define(t):"object"==typeof exports?module.exports=t():e.PhotoSwipeUI_Default=t()})(this,function(){return function(o,s){function e(e){if(T)return!0;e=e||window.event,C.timeToIdle&&C.mouseUsed&&!y&&a();for(var t,n,o=(e.target||e.srcElement).getAttribute("class")||"",i=0;i