commit 965e537497084dd07c619fecaf8f7331f94205cd Author: Cédric Date: Fri Oct 2 18:16:20 2026 +0000 Stack docker-compose Roundcube 1.7.4 (nginx, php-fpm, MariaDB) avec skin vBlog.io Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01Sa4nUG3GtbdEaPS6fA8eq1 diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..78bde26 --- /dev/null +++ b/.env.example @@ -0,0 +1,49 @@ +# Copier en .env puis adapter : cp .env.example .env +# Pas de commentaire en fin de ligne : Compose peut l'intégrer à la valeur. + +COMPOSE_PROJECT_NAME=roundcube + +# --- Versions (images épinglées) --- +# Tag roundcube/roundcubemail:-fpm-alpine +RC_VERSION=1.7.4 +# MariaDB LTS / nginx branche stable +MARIADB_VERSION=11.8 +NGINX_VERSION=1.30-alpine + +# --- Exposition HTTP (prévue derrière un reverse proxy TLS) --- +# 0.0.0.0 pour écouter sur toutes les interfaces +HTTP_BIND=127.0.0.1 +HTTP_PORT=8080 + +# --- Base de données --- +# Générer avec : openssl rand -base64 24 +DB_NAME=roundcubemail +DB_USER=roundcube +DB_PASSWORD=change-me +DB_ROOT_PASSWORD=change-me + +# --- Clé de chiffrement Roundcube (des_key) --- +# 24 caractères exactement ; ne plus la changer ensuite. openssl rand -base64 18 +RC_DES_KEY=change-me-24-characters! + +# --- Serveurs de messagerie --- +# Préfixes : ssl:// (TLS implicite, 993/465), tls:// (STARTTLS, 143/587), aucun = clair +IMAP_HOST=ssl://mail.example.org +IMAP_PORT=993 +SMTP_HOST=tls://mail.example.org +SMTP_PORT=587 +# Domaine ajouté aux identifiants sans @ (ex. example.org), vide = aucun +USERNAME_DOMAIN= + +# --- Plugins --- +# Plugins fournis avec Roundcube, à activer +RC_PLUGINS=archive,zipdownload,managesieve,markasjunk +# Paquets composer installés au démarrage (ex. johndoh/contextmenu), à activer aussi dans RC_PLUGINS +RC_COMPOSER_PLUGINS= + +# --- Divers --- +# Taille max des pièces jointes : PHP (upload/post) et nginx (client_max_body_size) +UPLOAD_MAX_FILESIZE=25M +# Dictionnaires aspell intégrés à l'image (build), séparés par des espaces +ASPELL_DICTS=fr en +TZ=Europe/Paris diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4c49bd7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.env diff --git a/README.md b/README.md new file mode 100644 index 0000000..a1b3901 --- /dev/null +++ b/README.md @@ -0,0 +1,107 @@ +# Roundcube Webmail en docker-compose + +Stack Roundcube **1.7.4** prête à l'emploi : nginx + php-fpm + MariaDB, skin Elastic recompilé avec la charte vBlog.io (surcharges LESS dans `theme/`). + +``` + :8080 (127.0.0.1 par défaut, derrière un reverse proxy TLS) + │ + ┌─────────────▼─────────────┐ réseau frontend (sortie IMAP/SMTP) + │ nginx 1.30-alpine │ sert le statique depuis le volume www (ro) + └─────────────┬─────────────┘ + │ FastCGI :9000 + ┌─────────────▼─────────────┐ + │ roundcube (php-fpm 8.4) │ image locale = roundcube/roundcubemail:1.7.4-fpm-alpine + │ │ + skin Elastic recompilé + aspell + mailcap + └─────────────┬─────────────┘ + │ réseau backend (internal: pas d'accès sortant) + ┌─────────────▼─────────────┐ + │ mariadb 11.8 (LTS) │ + └───────────────────────────┘ +``` + +![Login](docs/apercu-login.png) + +## Démarrage + +```sh +cp .env.example .env # renseigner mots de passe, RC_DES_KEY (24 car.), IMAP/SMTP +docker compose up -d --build +docker compose ps # les 3 services doivent passer "healthy" +``` + +Puis `http://127.0.0.1:8080/`. Le premier démarrage copie les sources dans le volume `www` et crée le schéma SQL (`bin/initdb.sh`), compter ~30 s. + +## Arborescence + +| Chemin | Rôle | +|---|---| +| `compose.yaml` | Les 3 services, volumes, réseaux, healthchecks | +| `.env.example` | Toutes les variables (versions, BDD, IMAP/SMTP, plugins...) | +| `roundcube/Dockerfile` | Build multi-étage : compilation LESS (node) puis image finale | +| `roundcube/zz-healthcheck.conf` | `ping.path` php-fpm pour le healthcheck | +| `nginx/templates/default.conf.template` | vhost, rendu par l'entrypoint nginx (envsubst `NGINX_*`) | +| `config/*.php` | Config Roundcube additionnelle, montée sur `/var/roundcube/config` | +| `theme/` | Surcharges du skin Elastic (`_variables.less`, `_styles.less`, `fonts/`, `images/`) | +| `scripts/sync-theme.sh` | Réimporte une customisation (défaut `../roundcube-custom/vblog`) | + +## Choix techniques + +- **Image officielle `fpm-alpine`** ([roundcube/roundcubemail-docker](https://github.com/roundcube/roundcubemail-docker)) : son entrypoint génère `config.docker.inc.php` depuis les `ROUNDCUBEMAIL_*`, inclut `/var/roundcube/config/*.php`, initialise/migre la base. Pas de réimplémentation. +- **Volume `www` partagé** (rw côté Roundcube, ro côté nginx) : pattern documenté upstream pour la variante FPM. À chaque démarrage, l'entrypoint resynchronise `/usr/src/roundcubemail` vers le volume (`bin/installto.sh`) : une image reconstruite (version ou thème) est donc propagée au simple `up -d`. +- **nginx** : depuis 1.7, seul `public_html/` est le docroot et les ressources sont appelées en `/static.php/`. Le vhost les sert directement depuis le volume (préfixes et extensions alignés sur `ALLOWED_PATHS`/`SUPPORTED_TYPES` de `static.php`, cache 30 j, les URL portent `?s=`). Seuls `index.php` et `static.php` atteignent PHP ; `installer.php` et le reste renvoient 404. +- **Thème compilé au build** : Elastic est livré en CSS minifié ; `_variables.less`/`_styles.less` ne sont pris en compte qu'à la compilation (`@import (optional)` dans `styles/variables.less` et `styles/styles.less`). Un étage `node:22-alpine` recompile `styles`, `print` et `embed` avec `lessc` (même commande que `skins/elastic/Makefile`). Aucune dépendance Node dans l'image finale. +- **Dictionnaires aspell et `mailcap` intégrés au build** plutôt qu'installés à chaque démarrage (`ROUNDCUBEMAIL_ASPELL_DICTS` fait un `apk add` au runtime). `mailcap` fournit `/etc/mime.types`, absent de l'image upstream (sinon warning « Mimetype to file extension mapping doesn't work properly »). +- **`des_key` imposée par `.env`** : sinon l'entrypoint la génère aléatoirement dans `config.inc.php`, perdue si le volume `www` est recréé. +- **Healthchecks** : MariaDB `healthcheck.sh --connect --innodb_initialized` ; Roundcube : port FPM 9000 ; nginx : `GET /fpm-ping` relayé à php-fpm (chaîne nginx → FPM validée, URL limitée à 127.0.0.1). Ordre de démarrage piloté par `depends_on: condition: service_healthy`. +- **Réseau `backend` en `internal: true`** : MariaDB n'a ni port publié ni accès sortant. + +## Thème vBlog.io + +Les fichiers de `theme/` et `config/20-vblog.inc.php` sont une copie de `roundcube-custom/vblog/`. Pour resynchroniser après modification : + +```sh +sh scripts/sync-theme.sh [dossier_source] +docker compose up -d --build +``` + +Pour revenir à l'Elastic d'origine : vider `theme/`, supprimer `config/20-vblog.inc.php`, rebuild. + +## Configuration + +- **Variables** : voir `.env.example` (commentaires sur leur propre ligne : Compose peut intégrer un commentaire de fin de ligne à la valeur). +- **Options Roundcube** : déposer des `*.php` dans `config/` (inclus par ordre alphabétique), puis `docker compose restart roundcube`. Référence : `config/defaults.inc.php` dans le conteneur. `config/10-base.inc.php` fixe langue, `proxy_whitelist`, durée de session, rate-limit de login. +- **Plugins** : intégrés → `RC_PLUGINS` ; tiers → `RC_COMPOSER_PLUGINS` (installés par composer au démarrage, nécessite l'accès à packagist) + ajout dans `RC_PLUGINS`. +- **Taille des pièces jointes** : `UPLOAD_MAX_FILESIZE` applique la même limite à PHP et nginx. Le message encodé en base64 pèse ~1,37× la pièce jointe. + +## Reverse proxy TLS + +La stack écoute en HTTP sur `127.0.0.1:8080`. Le proxy frontal (Traefik, HAProxy, nginx...) doit transmettre `X-Forwarded-For` et `X-Forwarded-Proto: https`. Restreindre `proxy_whitelist` (`config/10-base.inc.php`) à l'IP du proxy en production. Si Roundcube est publié sous un sous-chemin, régler `ROUNDCUBEMAIL_REQUEST_PATH`. + +## Exploitation + +```sh +docker compose logs -f roundcube # logs Roundcube et php-fpm (log_driver = stdout) + +# Sauvegarde : base + volume enigma si le plugin est utilisé +docker compose exec -T mariadb sh -c 'mariadb-dump -uroot -p"$MARIADB_ROOT_PASSWORD" --single-transaction "$MARIADB_DATABASE"' > roundcube-$(date +%F).sql +``` + +**Mise à jour Roundcube** : modifier `RC_VERSION` dans `.env` puis `docker compose up -d --build`. L'entrypoint exécute `installto.sh` puis `initdb.sh --update` (migrations SQL). Sauvegarder la base avant. + +**Proxy TLS d'entreprise pendant le build** (npm, apk) : fournir sa CA en secret BuildKit, optionnel : + +```sh +docker build -f roundcube/Dockerfile --secret id=ca,src=/chemin/ca.pem -t local/roundcube:1.7.4 . +docker compose up -d --no-build +``` + +## Validation effectuée + +Stack construite et démarrée avec Docker 29.6 / Compose 5.3 : 3 services `healthy`, schéma SQL créé, login IMAP réussi contre un Dovecot 2.4.5 de test (utilisateur enregistré en base), charte vBlog appliquée (captures `docs/`), `installer.php` et `static.php/config/...` en 404, statique servi par nginx avec cache. Non testé dans l'environnement de validation (dépôt Alpine bloqué) : l'étape `apk add` des dictionnaires/mailcap, et l'envoi SMTP. + +## Sources + +- Image et entrypoint : https://github.com/roundcube/roundcubemail-docker et https://hub.docker.com/r/roundcube/roundcubemail +- Options de config : https://github.com/roundcube/roundcubemail/blob/master/config/defaults.inc.php +- Skin Elastic, points d'extension LESS : https://github.com/roundcube/roundcubemail/tree/master/skins/elastic +- Image nginx (templates envsubst) : https://hub.docker.com/_/nginx ; image MariaDB (`healthcheck.sh`) : https://hub.docker.com/_/mariadb diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..16faa01 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,105 @@ +name: ${COMPOSE_PROJECT_NAME:-roundcube} + +# Roundcube Webmail : nginx (frontal) -> php-fpm (Roundcube) -> MariaDB +# Variables : voir .env.example + +services: + mariadb: + image: mariadb:${MARIADB_VERSION:-11.8} + restart: unless-stopped + command: + - --character-set-server=utf8mb4 + - --collation-server=utf8mb4_unicode_ci + environment: + MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:?DB_ROOT_PASSWORD manquant dans .env} + MARIADB_DATABASE: ${DB_NAME:-roundcubemail} + MARIADB_USER: ${DB_USER:-roundcube} + MARIADB_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD manquant dans .env} + MARIADB_AUTO_UPGRADE: "1" + volumes: + - db:/var/lib/mysql + networks: [backend] + healthcheck: + test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] + interval: 10s + timeout: 5s + retries: 10 + start_period: 30s + + roundcube: + build: + context: . + dockerfile: roundcube/Dockerfile + args: + RC_VERSION: ${RC_VERSION:-1.7.4} + ASPELL_DICTS: ${ASPELL_DICTS:-fr en} + image: ${RC_IMAGE:-local/roundcube}:${RC_VERSION:-1.7.4} + restart: unless-stopped + depends_on: + mariadb: + condition: service_healthy + environment: + ROUNDCUBEMAIL_DB_TYPE: mysql + ROUNDCUBEMAIL_DB_HOST: mariadb + ROUNDCUBEMAIL_DB_PORT: 3306 + ROUNDCUBEMAIL_DB_NAME: ${DB_NAME:-roundcubemail} + ROUNDCUBEMAIL_DB_USER: ${DB_USER:-roundcube} + ROUNDCUBEMAIL_DB_PASSWORD: ${DB_PASSWORD} + ROUNDCUBEMAIL_DES_KEY: ${RC_DES_KEY:?RC_DES_KEY manquant dans .env (24 caractères)} + ROUNDCUBEMAIL_DEFAULT_HOST: ${IMAP_HOST:?IMAP_HOST manquant dans .env} + ROUNDCUBEMAIL_DEFAULT_PORT: ${IMAP_PORT:-993} + ROUNDCUBEMAIL_SMTP_SERVER: ${SMTP_HOST:?SMTP_HOST manquant dans .env} + ROUNDCUBEMAIL_SMTP_PORT: ${SMTP_PORT:-587} + ROUNDCUBEMAIL_USERNAME_DOMAIN: ${USERNAME_DOMAIN:-} + ROUNDCUBEMAIL_PLUGINS: ${RC_PLUGINS:-archive,zipdownload,managesieve,markasjunk} + ROUNDCUBEMAIL_COMPOSER_PLUGINS: ${RC_COMPOSER_PLUGINS:-} + ROUNDCUBEMAIL_SKIN: elastic + ROUNDCUBEMAIL_UPLOAD_MAX_FILESIZE: ${UPLOAD_MAX_FILESIZE:-25M} + TZ: ${TZ:-Europe/Paris} + volumes: + - www:/var/www/html # sources Roundcube (partagées avec nginx) + - ./config:/var/roundcube/config:ro # surcharges PHP (*.php inclus dans l'ordre alpha) + - temp:/tmp/roundcube-temp + - enigma:/var/roundcube/enigma # clés OpenPGP si plugin enigma + networks: [backend, frontend] # frontend = sortie vers IMAP/SMTP + healthcheck: + test: ["CMD", "php", "-r", "exit(@fsockopen('127.0.0.1', 9000) ? 0 : 1);"] + interval: 10s + timeout: 5s + retries: 6 + start_period: 120s # 1er démarrage : copie des sources + initdb + + nginx: + image: nginx:${NGINX_VERSION:-1.30-alpine} + restart: unless-stopped + depends_on: + roundcube: + condition: service_healthy + environment: + NGINX_PHP_FPM: roundcube:9000 + NGINX_CLIENT_MAX_BODY_SIZE: ${UPLOAD_MAX_FILESIZE:-25M} + NGINX_ENVSUBST_FILTER: ^NGINX_ # n'interpole que nos variables + TZ: ${TZ:-Europe/Paris} + volumes: + - www:/var/www/html:ro + - ./nginx/templates:/etc/nginx/templates:ro + ports: + - "${HTTP_BIND:-127.0.0.1}:${HTTP_PORT:-8080}:80" + networks: [frontend] + healthcheck: + test: ["CMD-SHELL", "wget -q -O - http://127.0.0.1/fpm-ping | grep -q pong"] + interval: 15s + timeout: 5s + retries: 3 + start_period: 10s + +volumes: + db: + www: + temp: + enigma: + +networks: + backend: + internal: true # MariaDB sans accès sortant ni exposition + frontend: diff --git a/config/10-base.inc.php b/config/10-base.inc.php new file mode 100644 index 0000000..cc008c7 --- /dev/null +++ b/config/10-base.inc.php @@ -0,0 +1,21 @@ + ['verify_peer' => false, 'verify_peer_name' => false]]; +// $config['smtp_conn_options'] = ['ssl' => ['verify_peer' => false, 'verify_peer_name' => false]]; diff --git a/config/20-vblog.inc.php b/config/20-vblog.inc.php new file mode 100644 index 0000000..d698f69 --- /dev/null +++ b/config/20-vblog.inc.php @@ -0,0 +1,20 @@ + '/images/vblog-logo.svg', + 'elastic:login[dark]' => '/images/vblog-logo-white.svg', + 'elastic:*' => '/images/vblog-logo-white.svg', + 'elastic:*[dark]' => '/images/vblog-logo-white.svg', + 'elastic:*[link]' => 'https://vblog.io', +]; + +// Lien "Aide" vers le blog (optionnel) +// $config['support_url'] = 'https://vblog.io'; diff --git a/docs/apercu-boite.png b/docs/apercu-boite.png new file mode 100644 index 0000000..ec8e442 Binary files /dev/null and b/docs/apercu-boite.png differ diff --git a/docs/apercu-login.png b/docs/apercu-login.png new file mode 100644 index 0000000..a3d6a2d Binary files /dev/null and b/docs/apercu-login.png differ diff --git a/nginx/templates/default.conf.template b/nginx/templates/default.conf.template new file mode 100644 index 0000000..8ad2f46 --- /dev/null +++ b/nginx/templates/default.conf.template @@ -0,0 +1,65 @@ +# Roundcube 1.7 : seul public_html/ est exposé (index.php, static.php). +# Les ressources statiques sont référencées en /static.php/ ; nginx les +# sert directement depuis le volume partagé et ne délègue à PHP que le reste. + +upstream roundcube_fpm { + server ${NGINX_PHP_FPM}; +} + +server { + listen 80 default_server; + server_name _; + + root /var/www/html/public_html; + index index.php; + + client_max_body_size ${NGINX_CLIENT_MAX_BODY_SIZE}; + server_tokens off; + + add_header X-Content-Type-Options "nosniff" always; + add_header Referrer-Policy "same-origin" always; + + # Statique servi par nginx (même liste de préfixes que ALLOWED_PATHS de static.php) + location ~ ^/static\.php/((?:skins|plugins|program/js|program/resources)/.+\.(?:css|js|map|png|gif|jpe?g|svg|ico|webp|avif|woff2?|ttf|eot|html|json))$ { + alias /var/www/html/$1; + expires 30d; + add_header X-Content-Type-Options "nosniff" always; + access_log off; + } + + location = /favicon.ico { + alias /var/www/html/skins/elastic/images/favicon.ico; + access_log off; + } + + # Points d'entrée PHP autorisés (installer.php volontairement exclu) + location ~ ^/(?:index|static)\.php(?:/|$) { + fastcgi_split_path_info ^(.+?\.php)(/.*)$; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param PATH_INFO $fastcgi_path_info; + fastcgi_param HTTP_PROXY ""; + fastcgi_pass roundcube_fpm; + fastcgi_read_timeout 300s; + } + + # Healthcheck : ping du pool php-fpm, local uniquement + location = /fpm-ping { + allow 127.0.0.1; + deny all; + access_log off; + include fastcgi_params; + fastcgi_param SCRIPT_NAME /fpm-ping; + fastcgi_param SCRIPT_FILENAME /fpm-ping; + fastcgi_pass roundcube_fpm; + } + + location = / { + rewrite ^ /index.php last; + } + + # Tout le reste (installer.php, fichiers cachés...) : refusé + location / { + return 404; + } +} diff --git a/roundcube/Dockerfile b/roundcube/Dockerfile new file mode 100644 index 0000000..9f7b995 --- /dev/null +++ b/roundcube/Dockerfile @@ -0,0 +1,52 @@ +# syntax=docker/dockerfile:1 +# Image Roundcube (php-fpm) + skin Elastic recompilé avec les surcharges de theme/ +# +# Le skin Elastic est livré compilé (styles.min.css). Ses points d'extension +# _variables.less / _styles.less ne sont pris en compte qu'à la compilation LESS : +# on recompile donc ici, dans un étage Node jetable, puis on recopie le skin +# dans /usr/src/roundcubemail (source que l'entrypoint synchronise vers /var/www/html). + +ARG RC_VERSION=1.7.4 + +FROM roundcube/roundcubemail:${RC_VERSION}-fpm-alpine AS upstream + +# --- Étage 1 : compilation LESS du skin Elastic --- +FROM node:22-alpine AS theme +ARG LESS_VERSION=4.9.1 +ARG LESS_CLEANCSS_VERSION=1.6.0 +# Secret optionnel "ca" : CA d'un proxy TLS d'entreprise (docker build --secret id=ca,src=ca.pem) +RUN --mount=type=secret,id=ca,required=false \ + NODE_EXTRA_CA_CERTS=/run/secrets/ca npm install -g --no-fund --no-audit \ + less@${LESS_VERSION} less-plugin-clean-css@${LESS_CLEANCSS_VERSION} + +COPY --from=upstream /usr/src/roundcubemail/skins/elastic /build/elastic +COPY theme/ /build/theme/ + +WORKDIR /build/elastic +# Surcharges optionnelles : un theme/ vide redonne l'Elastic d'origine +RUN set -eu; \ + for f in _variables.less _styles.less; do \ + if [ -f /build/theme/$f ]; then cp /build/theme/$f styles/; echo "surcharge: $f"; fi; \ + done; \ + if [ -d /build/theme/fonts ]; then cp -r /build/theme/fonts/. fonts/; fi; \ + if [ -d /build/theme/images ]; then cp -r /build/theme/images/. images/; fi; \ + for css in styles print embed; do \ + lessc --clean-css="--s1 --advanced" styles/$css.less > styles/$css.min.css; \ + done; \ + ls -l styles/*.min.css + +# --- Étage 2 : image finale --- +FROM upstream +ARG ASPELL_DICTS="fr en" +# Dictionnaires installés au build (plutôt qu'à chaque démarrage via ROUNDCUBEMAIL_ASPELL_DICTS) +# mailcap fournit /etc/mime.types, utilisé par Roundcube pour le mapping type MIME <-> extension +RUN --mount=type=secret,id=ca,required=false \ + set -eu; \ + if [ -s /run/secrets/ca ]; then export SSL_CERT_FILE=/run/secrets/ca; fi; \ + pkgs="mailcap"; \ + for d in $(echo "$ASPELL_DICTS" | tr ',' ' '); do pkgs="$pkgs aspell-$d"; done; \ + apk add --no-cache $pkgs +COPY --from=theme /build/elastic/ /usr/src/roundcubemail/skins/elastic/ + +# Healthcheck php-fpm : réponse au ping FastCGI via le pool +COPY roundcube/zz-healthcheck.conf /usr/local/etc/php-fpm.d/zz-healthcheck.conf diff --git a/roundcube/zz-healthcheck.conf b/roundcube/zz-healthcheck.conf new file mode 100644 index 0000000..977c59e --- /dev/null +++ b/roundcube/zz-healthcheck.conf @@ -0,0 +1,5 @@ +; Expose /fpm-ping sur le pool www, interrogé par nginx (healthcheck de bout en bout) +[www] +ping.path = /fpm-ping +ping.response = pong +access.suppress_path[] = /fpm-ping diff --git a/scripts/sync-theme.sh b/scripts/sync-theme.sh new file mode 100644 index 0000000..58099c5 --- /dev/null +++ b/scripts/sync-theme.sh @@ -0,0 +1,19 @@ +#!/bin/sh +# Importe une customisation Elastic (format roundcube-custom/vblog) dans theme/ et config/ +# Usage : scripts/sync-theme.sh [dossier_source] (défaut : ../roundcube-custom/vblog) +set -eu +cd "$(dirname "$0")/.." +SRC=${1:-../roundcube-custom/vblog} +[ -d "$SRC" ] || { echo "Source introuvable : $SRC" >&2; exit 1; } + +rm -rf theme && mkdir -p theme +for f in _variables.less _styles.less; do + if [ -f "$SRC/$f" ]; then cp "$SRC/$f" theme/; fi +done +for d in fonts images; do + if [ -d "$SRC/$d" ]; then cp -r "$SRC/$d" theme/; fi +done +if [ -f "$SRC/config-vblog.inc.php" ]; then + cp "$SRC/config-vblog.inc.php" config/20-vblog.inc.php +fi +echo "Thème importé depuis $SRC. Appliquer : docker compose up -d --build" diff --git a/theme/_styles.less b/theme/_styles.less new file mode 100644 index 0000000..9fc9797 --- /dev/null +++ b/theme/_styles.less @@ -0,0 +1,75 @@ +// Compléments CSS charte vBlog.io pour le skin Elastic (Roundcube) +// Emplacement : skins/elastic/styles/_styles.less + +// --- Police Poppins auto-hébergée (pas d'appel à Google Fonts) --- +// Fichiers à copier dans skins/elastic/fonts/ (source : paquet npm @fontsource/poppins, licence OFL) +@font-face { + font-family: 'Poppins'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url('../fonts/poppins-latin-400-normal.woff2') format('woff2'); +} +@font-face { + font-family: 'Poppins'; + font-style: italic; + font-weight: 400; + font-display: swap; + src: url('../fonts/poppins-latin-400-italic.woff2') format('woff2'); +} +@font-face { + font-family: 'Poppins'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url('../fonts/poppins-latin-700-normal.woff2') format('woff2'); +} +@font-face { + font-family: 'Poppins'; + font-style: italic; + font-weight: 700; + font-display: swap; + src: url('../fonts/poppins-latin-700-italic.woff2') format('woff2'); +} + +body, button, input, optgroup, select, textarea, .popover { + font-family: Poppins, Roboto, sans-serif; +} + +// --- Bouton principal de la page de login en orange (CTA du blog) --- +#login-form .btn-primary { + background-color: @vblog-accent; + border-color: @vblog-accent; + + &:hover, &:focus { + background-color: darken(@vblog-accent, 8%); + border-color: darken(@vblog-accent, 8%); + } +} + +// --- Mode sombre : liens lisibles sur fond sombre, bouton "Écrire" en orange --- +// :where() ramène la spécificité à celle de la règle de base "a { color: @color-link }" +// de global.less : seuls les liens "nus" changent, pas les listes ni les boutons. +:where(html.dark-mode) a { + color: lighten(@vblog-primary, 40%); + + &:hover { + color: lighten(@vblog-primary, 50%); + } +} + +html.dark-mode { + #taskmenu .action-buttons a { + color: @vblog-accent; + } + + // Elastic assombrit le logo en mode sombre (opacity .8) : on le laisse net + #logo { + opacity: 1; + } +} + +// --- Logo du menu latéral : l'emblème est presque carré, on lui laisse de l'air --- +#layout-menu .popover-header img { + padding: .6rem; +} diff --git a/theme/_variables.less b/theme/_variables.less new file mode 100644 index 0000000..3b052aa --- /dev/null +++ b/theme/_variables.less @@ -0,0 +1,31 @@ +// Charte vBlog.io pour le skin Elastic (Roundcube) +// Emplacement : skins/elastic/styles/_variables.less +// Source des couleurs : kit Elementor de vblog.io +// https://vblog.io/wp-content/uploads/elementor/css/post-6.css (.elementor-kit-6) +// primary #314977 · secondary #000000 · text #7A7A7A · accent #FF7B10 · police Poppins + +@vblog-primary: #314977; // bleu marine +@vblog-accent: #FF7B10; // orange + +// --- Couleur principale (boutons, sélection, focus, cases à cocher, quota...) --- +@color-main: @vblog-primary; +@color-link: @vblog-primary; +@color-link-hover: darken(@color-link, 12%); + +// --- Menu latéral gauche : fond marine, action "Écrire" en orange --- +@color-taskmenu-background: @vblog-primary; +@color-taskmenu-button-selected-background: darken(@vblog-primary, 8%); +@color-taskmenu-button-background-hover: darken(@vblog-primary, 8%); +@color-taskmenu-button-action: @vblog-accent; +@color-taskmenu-button-action-hover: lighten(@vblog-accent, 10%); + +// --- Accent orange sur les badges des messages récents --- +@color-list-recent-badge-background: @vblog-accent; +@color-list-badge-background: @vblog-primary; + +// --- Mode sombre : le marine est trop sombre sur fond sombre, on l'éclaircit --- +@color-dark-list-selected: lighten(@vblog-primary, 40%); +@color-dark-input-border-focus: lighten(@vblog-primary, 30%); +@color-dark-information: @vblog-primary; +@color-dark-scrollbar-thumb: lighten(@vblog-primary, 10%); +@color-dark-blockquote-0: lighten(@vblog-primary, 35%); diff --git a/theme/fonts/OFL-Poppins.txt b/theme/fonts/OFL-Poppins.txt new file mode 100644 index 0000000..3c49f41 --- /dev/null +++ b/theme/fonts/OFL-Poppins.txt @@ -0,0 +1,93 @@ +Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-ThinItalic.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-ExtraLight.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-ExtraLightItalic.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-Light.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-LightItalic.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-Regular.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-Italic.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-Medium.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-MediumItalic.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-SemiBold.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-SemiBoldItalic.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-Bold.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-BoldItalic.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-ExtraBold.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-ExtraBoldItalic.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-Black.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-BlackItalic.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/theme/fonts/poppins-latin-400-italic.woff2 b/theme/fonts/poppins-latin-400-italic.woff2 new file mode 100644 index 0000000..1112336 Binary files /dev/null and b/theme/fonts/poppins-latin-400-italic.woff2 differ diff --git a/theme/fonts/poppins-latin-400-normal.woff2 b/theme/fonts/poppins-latin-400-normal.woff2 new file mode 100644 index 0000000..b69e009 Binary files /dev/null and b/theme/fonts/poppins-latin-400-normal.woff2 differ diff --git a/theme/fonts/poppins-latin-700-italic.woff2 b/theme/fonts/poppins-latin-700-italic.woff2 new file mode 100644 index 0000000..70974d8 Binary files /dev/null and b/theme/fonts/poppins-latin-700-italic.woff2 differ diff --git a/theme/fonts/poppins-latin-700-normal.woff2 b/theme/fonts/poppins-latin-700-normal.woff2 new file mode 100644 index 0000000..bf022fc Binary files /dev/null and b/theme/fonts/poppins-latin-700-normal.woff2 differ diff --git a/theme/images/vblog-logo-white.svg b/theme/images/vblog-logo-white.svg new file mode 100644 index 0000000..a7790d1 --- /dev/null +++ b/theme/images/vblog-logo-white.svg @@ -0,0 +1,13 @@ + +AAAWgmp1bWIAAAAeanVtZGMycGEAEQAQgAAAqgA4m3EDYzJwYQAAABZcanVtYgAAAEdqdW1kYzJtYQARABCAAACqADibcQN1cm46YzJwYTpkYTI3NDZjNi1mNzFiLTRmYTktODExZS05M2NkYWY4NjhhNzQAAAADl2p1bWIAAAApanVtZGMyYXMAEQAQgAAAqgA4m3EDYzJwYS5hc3NlcnRpb25zAAAAALxqdW1iAAAARGp1bWRjYm9yABEAEIAAAKoAOJtxE2MycGEuaW5ncmVkaWVudC52MwAAAAAYYzJzaA91XmY9vYc/lQWs5mc/nSgAAABwY2JvcqNpZGM6Zm9ybWF0bWltYWdlL3N2Zyt4bWxqaW5zdGFuY2VJRHgseG1wOmlpZDo5NDg4N2FjYy0zZWRlLTRkNTEtYjEwNC1jNzI4ODJlYzJjODVscmVsYXRpb25zaGlwaHBhcmVudE9mAAAB4mp1bWIAAABBanVtZGNib3IAEQAQgAAAqgA4m3ETYzJwYS5hY3Rpb25zLnYyAAAAABhjMnNo0cSopkBTFmCEdCRaDm4D2QAAAZljYm9yomdhY3Rpb25zgqJmYWN0aW9ua2MycGEub3BlbmVkanBhcmFtZXRlcnOha2luZ3JlZGllbnRzgaJjdXJseC1zZWxmI2p1bWJmPWMycGEuYXNzZXJ0aW9ucy9jMnBhLmluZ3JlZGllbnQudjNkaGFzaFggHLDHh6IB6u0VT/kooPSrah5drQE82Y5rJoe/qvpz5TqkZmFjdGlvbngdY29tLmFudGhyb3BpYy5jbGF1ZGUucHJvdmlkZWRqcGFyYW1ldGVyc6F4H2NvbS5hbnRocm9waWMub3JpZ2luLWNvbmZpZGVuY2VndW5rbm93bmtkZXNjcmlwdGlvbnhmQ2xhdWRlIHByb3ZpZGVkIHRoaXMgZmlsZSBhdCB0aGUgcmVxdWVzdCBvZiBhIHVzZXIgYW5kIG1heSBoYXZlIGNyZWF0ZWQgb3IgbW9kaWZpZWQgdGhlIGZpbGUgY29udGVudHMubXNvZnR3YXJlQWdlbnShZG5hbWVmQ2xhdWRlcmFsbEFjdGlvbnNJbmNsdWRlZPUAAADIanVtYgAAAEBqdW1kY2JvcgARABCAAACqADibcRNjMnBhLmhhc2guZGF0YQAAAAAYYzJzaMasuQR/8QV+DkHEEuN8x9AAAACAY2JvcqVjYWxnZnNoYTI1NmNwYWRNAAAAAAAAAAAAAAAAAGRoYXNoWCAT0buA0oWePCeILrY7wuF+ctrG5lgvGRcyRirNXcHZLGRuYW1lbmp1bWJmIG1hbmlmZXN0amV4Y2x1c2lvbnOBomVzdGFydBjNZmxlbmd0aBkeBAAAAj5qdW1iAAAAJ2p1bWRjMmNsABEAEIAAAKoAOJtxA2MycGEuY2xhaW0udjIAAAACD2Nib3KlY2FsZ2ZzaGEyNTZpc2lnbmF0dXJleE1zZWxmI2p1bWJmPS9jMnBhL3VybjpjMnBhOmRhMjc0NmM2LWY3MWItNGZhOS04MTFlLTkzY2RhZjg2OGE3NC9jMnBhLnNpZ25hdHVyZWppbnN0YW5jZUlEeCx4bXA6aWlkOmYwZWJlY2QzLWY1ZWItNGU1OS1hYzAwLWMyYWQ1OTM4MzAxZXJjcmVhdGVkX2Fzc2VydGlvbnODomN1cmx4LXNlbGYjanVtYmY9YzJwYS5hc3NlcnRpb25zL2MycGEuaW5ncmVkaWVudC52M2RoYXNoWCAcsMeHogHq7RVP+Sig9KtqHl2tATzZjmsmh7+q+nPlOqJjdXJseCpzZWxmI2p1bWJmPWMycGEuYXNzZXJ0aW9ucy9jMnBhLmFjdGlvbnMudjJkaGFzaFggANyWZH5B4dxS/hb406toS2r01DxDX6NJMAeOnX/BNKeiY3VybHgpc2VsZiNqdW1iZj1jMnBhLmFzc2VydGlvbnMvYzJwYS5oYXNoLmRhdGFkaGFzaFggmc3m2Mzqiz5Id6nTzYaBsHtDc0/mJtVh/TILchty2U90Y2xhaW1fZ2VuZXJhdG9yX2luZm+jZG5hbWVvQW50aHJvcGljIEZpbGVzZ3ZlcnNpb25lMS4wLjBrc3BlY1ZlcnNpb25lMi40LjAAABA4anVtYgAAAChqdW1kYzJjcwARABCAAACqADibcQNjMnBhLnNpZ25hdHVyZQAAABAIY2JvctKEWQISogEmGCFZAgowggIGMIIBjaADAgECAhRA5aAK7sI50L64g/oGQgU9Z1UTADAKBggqhkjOPQQDAzBJMRcwFQYDVQQKEw5BbnRocm9waWMsIFBCQzEuMCwGA1UEAxMlQW50aHJvcGljIENvbnRlbnQgQ3JlZGVudGlhbHMgUm9vdCBDQTAeFw0yNjA4MDcxODQzNTZaFw0yODA4MDYxOTQzNTZaMEQxFzAVBgNVBAoTDkFudGhyb3BpYywgUEJDMSkwJwYDVQQDEyBBbnRocm9waWMgQ2xhdWRlIENvbnRlbnQgU2lnbmluZzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJh6CmvLUBgFFNU0vUKlOVtE6djd17L5SuwX0LemFisBM3dkd/3cyjxFA3Qo5S46fX0/ihY0VZ7mfb9KF703t5OjWDBWMA4GA1UdDwEB/wQEAwIHgDAVBgNVHSUEDjAMBgorBgEEAYPoXgIBMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUzlHiBIFOZFsj+OPEz5o+nMHXXMIwCgYIKoZIzj0EAwMDZwAwZAIwMXMdFJ4BetLLVY7ORuE9noqbbAZOZn/aArXyTwFAZfKrPzxF2vPoJNf1+UCdg1XGAjBwX1zd9WGqYkqmL5SFqw1QySjr1zJfpJM9+1rdDwSPLMOPOjKuiXjoU/pUUeG9RwmhY3BhZFkNngAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPZYQG/ItNK4qa/jOO1TsdhbZG15OJxWuALP6h98NAXAYU/cseE7va6a4qIxjlo4kDLeSCGKoPAqw1Pa3A89GxJqwkI= + + + + + + + \ No newline at end of file diff --git a/theme/images/vblog-logo.svg b/theme/images/vblog-logo.svg new file mode 100644 index 0000000..0cc08fe --- /dev/null +++ b/theme/images/vblog-logo.svg @@ -0,0 +1,13 @@ + +AAAWgmp1bWIAAAAeanVtZGMycGEAEQAQgAAAqgA4m3EDYzJwYQAAABZcanVtYgAAAEdqdW1kYzJtYQARABCAAACqADibcQN1cm46YzJwYTpmYTA2ZmM0My0yMGFkLTRmYzMtOTc4Zi00NDdmNWE1ZDNiNmEAAAADl2p1bWIAAAApanVtZGMyYXMAEQAQgAAAqgA4m3EDYzJwYS5hc3NlcnRpb25zAAAAALxqdW1iAAAARGp1bWRjYm9yABEAEIAAAKoAOJtxE2MycGEuaW5ncmVkaWVudC52MwAAAAAYYzJzaFN7GjaL+DvcZ7qDIvssT+QAAABwY2JvcqNpZGM6Zm9ybWF0bWltYWdlL3N2Zyt4bWxqaW5zdGFuY2VJRHgseG1wOmlpZDoyMTQxNjZhMS01OWQ5LTQ1MTAtYjdlNi1mNjc1NjM0YjNiYjhscmVsYXRpb25zaGlwaHBhcmVudE9mAAAB4mp1bWIAAABBanVtZGNib3IAEQAQgAAAqgA4m3ETYzJwYS5hY3Rpb25zLnYyAAAAABhjMnNotc0ool/uwYHS0xMCXfhROQAAAZljYm9yomdhY3Rpb25zgqJmYWN0aW9ua2MycGEub3BlbmVkanBhcmFtZXRlcnOha2luZ3JlZGllbnRzgaJjdXJseC1zZWxmI2p1bWJmPWMycGEuYXNzZXJ0aW9ucy9jMnBhLmluZ3JlZGllbnQudjNkaGFzaFgg0gLQY2DQpyuLaKHGYmxL9pIGWRXgVZQndUlPC3BBZ/GkZmFjdGlvbngdY29tLmFudGhyb3BpYy5jbGF1ZGUucHJvdmlkZWRqcGFyYW1ldGVyc6F4H2NvbS5hbnRocm9waWMub3JpZ2luLWNvbmZpZGVuY2VndW5rbm93bmtkZXNjcmlwdGlvbnhmQ2xhdWRlIHByb3ZpZGVkIHRoaXMgZmlsZSBhdCB0aGUgcmVxdWVzdCBvZiBhIHVzZXIgYW5kIG1heSBoYXZlIGNyZWF0ZWQgb3IgbW9kaWZpZWQgdGhlIGZpbGUgY29udGVudHMubXNvZnR3YXJlQWdlbnShZG5hbWVmQ2xhdWRlcmFsbEFjdGlvbnNJbmNsdWRlZPUAAADIanVtYgAAAEBqdW1kY2JvcgARABCAAACqADibcRNjMnBhLmhhc2guZGF0YQAAAAAYYzJzaCHV0xW1SMJqd+3ehqhKsZUAAACAY2JvcqVjYWxnZnNoYTI1NmNwYWRNAAAAAAAAAAAAAAAAAGRoYXNoWCCef7I8bNY42SEO0ST7/4olLBw27oaPO19GdQejLNz5HWRuYW1lbmp1bWJmIG1hbmlmZXN0amV4Y2x1c2lvbnOBomVzdGFydBjNZmxlbmd0aBkeBAAAAj5qdW1iAAAAJ2p1bWRjMmNsABEAEIAAAKoAOJtxA2MycGEuY2xhaW0udjIAAAACD2Nib3KlY2FsZ2ZzaGEyNTZpc2lnbmF0dXJleE1zZWxmI2p1bWJmPS9jMnBhL3VybjpjMnBhOmZhMDZmYzQzLTIwYWQtNGZjMy05NzhmLTQ0N2Y1YTVkM2I2YS9jMnBhLnNpZ25hdHVyZWppbnN0YW5jZUlEeCx4bXA6aWlkOjI4ZGFhZTE0LWNiNDgtNDNhOC05M2NkLTZjYjliN2ZjZjhjY3JjcmVhdGVkX2Fzc2VydGlvbnODomN1cmx4LXNlbGYjanVtYmY9YzJwYS5hc3NlcnRpb25zL2MycGEuaW5ncmVkaWVudC52M2RoYXNoWCDSAtBjYNCnK4toocZibEv2kgZZFeBVlCd1SU8LcEFn8aJjdXJseCpzZWxmI2p1bWJmPWMycGEuYXNzZXJ0aW9ucy9jMnBhLmFjdGlvbnMudjJkaGFzaFgg7mshbHpJrugvO3izq6qTUyqOAO23FFoU7v69VayZMmOiY3VybHgpc2VsZiNqdW1iZj1jMnBhLmFzc2VydGlvbnMvYzJwYS5oYXNoLmRhdGFkaGFzaFggFBFyHyxF4DRsGQWlQaYvja1e/2VaVq3EX3/njPu9PpV0Y2xhaW1fZ2VuZXJhdG9yX2luZm+jZG5hbWVvQW50aHJvcGljIEZpbGVzZ3ZlcnNpb25lMS4wLjBrc3BlY1ZlcnNpb25lMi40LjAAABA4anVtYgAAAChqdW1kYzJjcwARABCAAACqADibcQNjMnBhLnNpZ25hdHVyZQAAABAIY2JvctKEWQISogEmGCFZAgowggIGMIIBjaADAgECAhRA5aAK7sI50L64g/oGQgU9Z1UTADAKBggqhkjOPQQDAzBJMRcwFQYDVQQKEw5BbnRocm9waWMsIFBCQzEuMCwGA1UEAxMlQW50aHJvcGljIENvbnRlbnQgQ3JlZGVudGlhbHMgUm9vdCBDQTAeFw0yNjA4MDcxODQzNTZaFw0yODA4MDYxOTQzNTZaMEQxFzAVBgNVBAoTDkFudGhyb3BpYywgUEJDMSkwJwYDVQQDEyBBbnRocm9waWMgQ2xhdWRlIENvbnRlbnQgU2lnbmluZzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJh6CmvLUBgFFNU0vUKlOVtE6djd17L5SuwX0LemFisBM3dkd/3cyjxFA3Qo5S46fX0/ihY0VZ7mfb9KF703t5OjWDBWMA4GA1UdDwEB/wQEAwIHgDAVBgNVHSUEDjAMBgorBgEEAYPoXgIBMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUzlHiBIFOZFsj+OPEz5o+nMHXXMIwCgYIKoZIzj0EAwMDZwAwZAIwMXMdFJ4BetLLVY7ORuE9noqbbAZOZn/aArXyTwFAZfKrPzxF2vPoJNf1+UCdg1XGAjBwX1zd9WGqYkqmL5SFqw1QySjr1zJfpJM9+1rdDwSPLMOPOjKuiXjoU/pUUeG9RwmhY3BhZFkNngAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPZYQO/FLaxreCU8sCz3hCIpHvCog/3EMQ1B9bv18Iy3DhHvMqvObe196WwqVzIN7+bg5yRPTBGdX8D+FDTVGCglxYA= + + + + + + + \ No newline at end of file