ci: replace OVH FTP deploy with Cloudflare tunnel deploy via Photon
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
name: Deploy to Cloudflare (via Photon)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: deploy-production
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
PUBLIC_SITE_URL: https://www.lelectronrare.fr/
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build static site
|
||||
run: npm run build
|
||||
|
||||
- name: Deploy to Photon
|
||||
uses: appleboy/scp-action@v0.1.7
|
||||
with:
|
||||
host: ${{ secrets.PHOTON_HOST }}
|
||||
username: root
|
||||
key: ${{ secrets.PHOTON_SSH_KEY }}
|
||||
source: "dist/"
|
||||
target: "/tmp/site-dist/"
|
||||
overwrite: true
|
||||
rm: true
|
||||
|
||||
- name: Restart site container
|
||||
uses: appleboy/ssh-action@v1.0.3
|
||||
with:
|
||||
host: ${{ secrets.PHOTON_HOST }}
|
||||
username: root
|
||||
key: ${{ secrets.PHOTON_SSH_KEY }}
|
||||
script: |
|
||||
docker rm -f electron-rare-site 2>/dev/null || true
|
||||
docker run -d \
|
||||
--name electron-rare-site \
|
||||
--network zacus-stack_default \
|
||||
-v /tmp/site-dist/dist:/app/dist \
|
||||
-p 127.0.0.1:4321:4321 \
|
||||
--restart unless-stopped \
|
||||
node:22-alpine \
|
||||
sh -c 'npm install -g serve 2>/dev/null && serve /app/dist -l 4321 -s'
|
||||
|
||||
- name: Verify deployment
|
||||
uses: appleboy/ssh-action@v1.0.3
|
||||
with:
|
||||
host: ${{ secrets.PHOTON_HOST }}
|
||||
username: root
|
||||
key: ${{ secrets.PHOTON_SSH_KEY }}
|
||||
script: |
|
||||
sleep 8
|
||||
STATUS=$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:4321/)
|
||||
if [ "$STATUS" != "200" ]; then
|
||||
echo "Site returned $STATUS instead of 200"
|
||||
exit 1
|
||||
fi
|
||||
echo "Site is live (HTTP 200)"
|
||||
@@ -1,64 +0,0 @@
|
||||
name: Deploy External Site to OVH FTP
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
target:
|
||||
description: Deployment target
|
||||
required: true
|
||||
default: preview
|
||||
type: choice
|
||||
options:
|
||||
- preview
|
||||
- production
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ovh-ftp-${{ inputs.target }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
FTP_HOST: ${{ secrets.OVH_FTP_HOST }}
|
||||
FTP_USER: ${{ secrets.OVH_FTP_USER }}
|
||||
FTP_PASS: ${{ secrets.OVH_FTP_PASS }}
|
||||
FTP_PORT: ${{ secrets.OVH_FTP_PORT }}
|
||||
FTP_REMOTE_DIR: ${{ secrets.OVH_FTP_REMOTE_DIR }}
|
||||
PUBLIC_SITE_URL_PROD: ${{ vars.PUBLIC_SITE_URL_PROD }}
|
||||
PUBLIC_SITE_URL_PREVIEW: ${{ vars.PUBLIC_SITE_URL_PREVIEW }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
cache-dependency-path: |
|
||||
package-lock.json
|
||||
apps/lab-interactif/package-lock.json
|
||||
|
||||
- name: Install root dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Install lab dependencies
|
||||
run: npm ci --prefix apps/lab-interactif
|
||||
|
||||
- name: Install lftp
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y lftp
|
||||
|
||||
- name: Deploy target
|
||||
env:
|
||||
DEPLOY_TARGET_SKIP_BUILD: "0"
|
||||
run: bash scripts/deploy-ovh-ftp-target.sh "${{ inputs.target }}"
|
||||
|
||||
- name: Verify public target
|
||||
run: node scripts/verify-public-target.mjs "${{ inputs.target }}"
|
||||
@@ -1,76 +0,0 @@
|
||||
name: Deploy Static Site + Lab to GitHub Pages
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
concurrency:
|
||||
group: pages
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
cache-dependency-path: |
|
||||
package-lock.json
|
||||
apps/lab-interactif/package-lock.json
|
||||
|
||||
- name: Install root dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Install lab dependencies
|
||||
run: npm ci --prefix apps/lab-interactif
|
||||
|
||||
- name: Build lab module
|
||||
run: npm run lab:build
|
||||
|
||||
- name: Prepare pages artifact
|
||||
run: |
|
||||
mkdir -p .pages-dist
|
||||
cp index.html .pages-dist/
|
||||
cp styles.css .pages-dist/
|
||||
cp script.js .pages-dist/
|
||||
cp robots.txt .pages-dist/
|
||||
cp sitemap.xml .pages-dist/
|
||||
cp favicon.svg .pages-dist/
|
||||
cp favicon.ico .pages-dist/
|
||||
cp favicon-32x32.png .pages-dist/
|
||||
cp favicon-16x16.png .pages-dist/
|
||||
cp apple-touch-icon.png .pages-dist/
|
||||
cp -R assets .pages-dist/assets
|
||||
cp -R lab .pages-dist/lab
|
||||
if [ -f CNAME ]; then cp CNAME .pages-dist/; fi
|
||||
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v5
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: ./.pages-dist
|
||||
|
||||
deploy:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
FROM node:22-alpine AS base
|
||||
WORKDIR /app
|
||||
|
||||
FROM base AS deps
|
||||
COPY package.json package-lock.json ./
|
||||
COPY apps/lab-interactif/package.json apps/lab-interactif/package-lock.json ./apps/lab-interactif/
|
||||
RUN npm ci && npm ci --prefix apps/lab-interactif
|
||||
|
||||
FROM base AS build
|
||||
ARG PUBLIC_SITE_URL=https://www.lelectronrare.fr
|
||||
ENV PUBLIC_SITE_URL=${PUBLIC_SITE_URL}
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY --from=deps /app/apps/lab-interactif/node_modules ./apps/lab-interactif/node_modules
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
FROM node:22-alpine AS runtime
|
||||
WORKDIR /app
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY --from=build /app/dist ./dist
|
||||
ENV HOST=0.0.0.0
|
||||
ENV PORT=4321
|
||||
EXPOSE 4321
|
||||
CMD ["node", "dist/server/entry.mjs"]
|
||||
@@ -1,3 +1,4 @@
|
||||
import node from '@astrojs/node';
|
||||
import { defineConfig } from 'astro/config';
|
||||
import react from '@astrojs/react';
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
@@ -11,6 +12,8 @@ const basePath = sitePathname === '/' ? '/' : sitePathname.replace(/\/$/, '');
|
||||
export default defineConfig({
|
||||
site: siteUrl,
|
||||
base: basePath,
|
||||
output: 'server',
|
||||
adapter: node({ mode: 'standalone' }),
|
||||
integrations: [react()],
|
||||
vite: {
|
||||
plugins: [tailwindcss()]
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
PUBLIC_SITE_URL=https://www.lelectronrare.fr
|
||||
FRAPPE_URL=https://tower.saillant.cc
|
||||
FRAPPE_API_KEY=5603aaa29126619
|
||||
FRAPPE_API_SECRET=21e51a4eb6d5882
|
||||
CRM_FALLBACK_URL=https://electron.saillant.cc
|
||||
@@ -0,0 +1,37 @@
|
||||
services:
|
||||
electron-rare-site:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
FRAPPE_URL: ${FRAPPE_URL:?FRAPPE_URL is required}
|
||||
FRAPPE_API_KEY: ${FRAPPE_API_KEY:?FRAPPE_API_KEY is required}
|
||||
FRAPPE_API_SECRET: ${FRAPPE_API_SECRET:?FRAPPE_API_SECRET is required}
|
||||
CRM_FALLBACK_URL: ${CRM_FALLBACK_URL:-}
|
||||
container_name: electron-rare-site
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
HOST: 0.0.0.0
|
||||
PORT: 4321
|
||||
PUBLIC_SITE_URL: ${PUBLIC_SITE_URL:-https://www.lelectronrare.fr}
|
||||
FRAPPE_URL: ${FRAPPE_URL}
|
||||
FRAPPE_API_KEY: ${FRAPPE_API_KEY}
|
||||
FRAPPE_API_SECRET: ${FRAPPE_API_SECRET}
|
||||
CRM_FALLBACK_URL: ${CRM_FALLBACK_URL:-}
|
||||
NODE_TLS_REJECT_UNAUTHORIZED: "0"
|
||||
extra_hosts:
|
||||
- "tower.saillant.cc:192.168.0.120"
|
||||
ports:
|
||||
- "127.0.0.1:4321:4321"
|
||||
networks:
|
||||
- zacus-stack
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "5"
|
||||
|
||||
networks:
|
||||
zacus-stack:
|
||||
external: true
|
||||
name: zacus-stack_default
|
||||
Generated
+197
@@ -8,6 +8,7 @@
|
||||
"name": "electron-rare-studio",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@astrojs/node": "^9.4.5",
|
||||
"@astrojs/react": "^4.4.1",
|
||||
"@radix-ui/react-slot": "^1.2.3",
|
||||
"astro": "^5.13.8",
|
||||
@@ -179,6 +180,26 @@
|
||||
"vfile": "^6.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@astrojs/node": {
|
||||
"version": "9.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@astrojs/node/-/node-9.4.5.tgz",
|
||||
"integrity": "sha512-rTfrcZsjZKJUhB8AlWJUa8amEy8I3CZePSjYn0D9+YTn27CBB3v5jHDOuUJQVRc8fm8oEi2uprF79agTGexwcA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@astrojs/internal-helpers": "0.7.4",
|
||||
"send": "^1.2.0",
|
||||
"server-destroy": "^1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"astro": "^5.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@astrojs/node/node_modules/@astrojs/internal-helpers": {
|
||||
"version": "0.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.7.4.tgz",
|
||||
"integrity": "sha512-lDA9MqE8WGi7T/t2BMi+EAXhs4Vcvr94Gqx3q15cFEz8oFZMO4/SFBqYr/UcmNlvW+35alowkVj+w9VhLvs5Cw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@astrojs/prism": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-3.3.0.tgz",
|
||||
@@ -5055,6 +5076,15 @@
|
||||
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/depd": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/dequal": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
|
||||
@@ -5230,6 +5260,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.302",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.302.tgz",
|
||||
@@ -5259,6 +5295,15 @@
|
||||
"integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/encodeurl": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
|
||||
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/enhanced-resolve": {
|
||||
"version": "5.20.0",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz",
|
||||
@@ -5354,6 +5399,12 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/escape-string-regexp": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
|
||||
@@ -5396,6 +5447,15 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/etag": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/eventemitter3": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz",
|
||||
@@ -5541,6 +5601,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/fresh": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
|
||||
"integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
@@ -5860,6 +5929,26 @@
|
||||
"integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==",
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/http-errors": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
|
||||
"integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"depd": "~2.0.0",
|
||||
"inherits": "~2.0.4",
|
||||
"setprototypeof": "~1.2.0",
|
||||
"statuses": "~2.0.2",
|
||||
"toidentifier": "~1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/import-meta-resolve": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz",
|
||||
@@ -5880,6 +5969,12 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/iron-webcrypto": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz",
|
||||
@@ -7199,6 +7294,31 @@
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.54.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
|
||||
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
|
||||
"integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "^1.54.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/min-indent": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
|
||||
@@ -7404,6 +7524,18 @@
|
||||
"integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/on-finished": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
||||
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ee-first": "1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/oniguruma-parser": {
|
||||
"version": "0.12.1",
|
||||
"resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.1.tgz",
|
||||
@@ -7776,6 +7908,15 @@
|
||||
"integrity": "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/range-parser": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "19.2.4",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
|
||||
@@ -8246,6 +8387,44 @@
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/send": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
|
||||
"integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "^4.4.3",
|
||||
"encodeurl": "^2.0.0",
|
||||
"escape-html": "^1.0.3",
|
||||
"etag": "^1.8.1",
|
||||
"fresh": "^2.0.0",
|
||||
"http-errors": "^2.0.1",
|
||||
"mime-types": "^3.0.2",
|
||||
"ms": "^2.1.3",
|
||||
"on-finished": "^2.4.1",
|
||||
"range-parser": "^1.2.1",
|
||||
"statuses": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/server-destroy": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/server-destroy/-/server-destroy-1.0.1.tgz",
|
||||
"integrity": "sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/setprototypeof": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
||||
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/sharp": {
|
||||
"version": "0.34.5",
|
||||
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
|
||||
@@ -8403,6 +8582,15 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/statuses": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
|
||||
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/storybook": {
|
||||
"version": "9.1.19",
|
||||
"resolved": "https://registry.npmjs.org/storybook/-/storybook-9.1.19.tgz",
|
||||
@@ -9194,6 +9382,15 @@
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/toidentifier": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/trim-lines": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz",
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
"deploy:web:sftp": "npm run build:external && bash scripts/deploy-ovh-sftp.sh dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/node": "^9.4.5",
|
||||
"@astrojs/react": "^4.4.1",
|
||||
"@radix-ui/react-slot": "^1.2.3",
|
||||
"astro": "^5.13.8",
|
||||
|
||||
+198
-137
@@ -1,146 +1,207 @@
|
||||
---
|
||||
import BaseLayout from '@/layouts/BaseLayout.astro';
|
||||
import '@/styles/global.css';
|
||||
import '@/styles/home-workbench.css';
|
||||
import SiteHeader from '@/components/SiteHeader.astro';
|
||||
import { Hero } from '@/components/sections/Hero';
|
||||
import { About } from '@/components/sections/About';
|
||||
import { CaseStudies } from '@/components/sections/CaseStudies';
|
||||
import { GraphicSprints } from '@/components/sections/GraphicSprints';
|
||||
import Contact from '@/components/sections/Contact.astro';
|
||||
import Faq from '@/components/sections/Faq.astro';
|
||||
import { withSiteBase } from '@/lib/site';
|
||||
import { TRACK_EVENTS, trackAttrs } from '@/lib/tracking';
|
||||
|
||||
const formationHref = withSiteBase('/formation/');
|
||||
const mentionsHref = withSiteBase('/mentions-legales/');
|
||||
const headerItems = [
|
||||
{ href: '#a-propos', label: 'Approche' },
|
||||
{ href: '#graphic-sprints-title', label: 'Missions' },
|
||||
{ href: formationHref, label: 'Formation' },
|
||||
{ href: '#faq', label: 'FAQ' },
|
||||
{ href: '#contact', label: 'Contact', desktopOnly: true }
|
||||
];
|
||||
---
|
||||
|
||||
<BaseLayout>
|
||||
<SiteHeader brandHref="#top" items={headerItems} quickHref="#contact" />
|
||||
<BaseLayout title="L'Electron Rare — Bientôt" description="L'Electron Rare arrive. Systèmes électroniques spécifiques, formations, prototypage.">
|
||||
<main class="countdown-page">
|
||||
<svg class="atom" viewBox="0 0 300 300" xmlns="http://www.w3.org/2000/svg">
|
||||
<ellipse cx="150" cy="150" rx="120" ry="45" fill="none" stroke="var(--trace-cyan-soft, #5bd1d866)" stroke-width="0.8" transform="rotate(-30 150 150)"/>
|
||||
<ellipse cx="150" cy="150" rx="120" ry="45" fill="none" stroke="var(--trace-amber-soft, #f1c27a66)" stroke-width="0.8" transform="rotate(30 150 150)"/>
|
||||
<ellipse cx="150" cy="150" rx="120" ry="45" fill="none" stroke="var(--trace-green-soft, #b6d18f44)" stroke-width="0.8" transform="rotate(90 150 150)"/>
|
||||
<circle cx="150" cy="150" r="18" fill="url(#nucleusGlow)"/>
|
||||
<circle cx="150" cy="150" r="8" fill="var(--accent, #f1c27a)"/>
|
||||
<circle r="5" fill="var(--electric, #5bd1d8)">
|
||||
<animateMotion dur="3s" repeatCount="indefinite"><mpath href="#o1"/></animateMotion>
|
||||
</circle>
|
||||
<circle r="10" fill="var(--electric, #5bd1d8)" opacity="0.3">
|
||||
<animateMotion dur="3s" repeatCount="indefinite"><mpath href="#o1"/></animateMotion>
|
||||
</circle>
|
||||
<circle r="4" fill="var(--accent, #f1c27a)">
|
||||
<animateMotion dur="4.2s" repeatCount="indefinite"><mpath href="#o2"/></animateMotion>
|
||||
</circle>
|
||||
<circle r="9" fill="var(--accent, #f1c27a)" opacity="0.25">
|
||||
<animateMotion dur="4.2s" repeatCount="indefinite"><mpath href="#o2"/></animateMotion>
|
||||
</circle>
|
||||
<circle r="3" fill="var(--trace-green, #b6d18f)">
|
||||
<animateMotion dur="5.8s" repeatCount="indefinite"><mpath href="#o3"/></animateMotion>
|
||||
</circle>
|
||||
<circle r="8" fill="var(--trace-green, #b6d18f)" opacity="0.2">
|
||||
<animateMotion dur="5.8s" repeatCount="indefinite"><mpath href="#o3"/></animateMotion>
|
||||
</circle>
|
||||
<defs>
|
||||
<ellipse id="o1" cx="150" cy="150" rx="120" ry="45" transform="rotate(-30 150 150)" fill="none"/>
|
||||
<ellipse id="o2" cx="150" cy="150" rx="120" ry="45" transform="rotate(30 150 150)" fill="none"/>
|
||||
<ellipse id="o3" cx="150" cy="150" rx="120" ry="45" transform="rotate(90 150 150)" fill="none"/>
|
||||
<radialGradient id="nucleusGlow">
|
||||
<stop offset="0%" stop-color="var(--accent, #f1c27a)" stop-opacity="0.6"/>
|
||||
<stop offset="100%" stop-color="var(--accent, #f1c27a)" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
<main id="main-content" class="site-shell studio-structure pb-12 pt-3">
|
||||
<section class="structure-grid structure-grid--hero" data-reveal>
|
||||
<div class="structure-cell structure-cell--hero">
|
||||
<Hero />
|
||||
</div>
|
||||
</section>
|
||||
<h1 class="title">L'<span class="electric">é</span>lectron rare</h1>
|
||||
|
||||
<section class="structure-grid structure-grid--systems" data-reveal>
|
||||
<div class="structure-cell structure-cell--about">
|
||||
<About />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="structure-grid structure-grid--cases" data-reveal>
|
||||
<div class="structure-cell structure-cell--cases">
|
||||
<CaseStudies />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="photo-strip" aria-label="Photos de terrain" data-reveal>
|
||||
<div class="photo-strip-track">
|
||||
<img src={withSiteBase('/assets/photos/pcb-teensy-led-kxkm.webp')} alt="PCB custom Teensy LED — projet spectacle vivant KXKM" loading="lazy" decoding="async" width="280" height="190" class="photo-strip-img" />
|
||||
<img src={withSiteBase('/assets/photos/platine-automate-cablage.webp')} alt="Câblage automate industriel — intervention terrain" loading="lazy" decoding="async" width="280" height="190" class="photo-strip-img" />
|
||||
<img src={withSiteBase('/assets/photos/bench-bms-batteries-xt60.webp')} alt="Banc de test BMS batteries — prototypage" loading="lazy" decoding="async" width="280" height="190" class="photo-strip-img" />
|
||||
<img src={withSiteBase('/assets/photos/automate-siemens-s7.webp')} alt="Automate Siemens S7 — diagnostic industriel" loading="lazy" decoding="async" width="280" height="190" class="photo-strip-img" />
|
||||
<img src={withSiteBase('/assets/photos/soudure-pcb-composants.webp')} alt="Soudure PCB — formation et prototypage" loading="lazy" decoding="async" width="280" height="190" class="photo-strip-img" />
|
||||
<img src={withSiteBase('/assets/photos/oscilloscope-philips-vintage.webp')} alt="Instrumentation et mesure — oscilloscope" loading="lazy" decoding="async" width="280" height="190" class="photo-strip-img" />
|
||||
<img src={withSiteBase('/assets/photos/rack-cbtc-lon-connecteurs.webp')} alt="Rack CBTC transport — connectique industrielle" loading="lazy" decoding="async" width="280" height="190" class="photo-strip-img" />
|
||||
<img src={withSiteBase('/assets/photos/pcb-embarquee-boitier-test.webp')} alt="Carte embarquée en test — validation prototype" loading="lazy" decoding="async" width="280" height="190" class="photo-strip-img" />
|
||||
<img src={withSiteBase('/assets/photos/armoire-automate-schneider.webp')} alt="Armoire Schneider — automatisme industriel" loading="lazy" decoding="async" width="280" height="190" class="photo-strip-img" />
|
||||
<img src={withSiteBase('/assets/photos/outillage-atelier-pro.webp')} alt="Atelier professionnel — outillage organisé" loading="lazy" decoding="async" width="280" height="190" class="photo-strip-img" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="video-strip" aria-label="Vidéos de terrain" data-reveal>
|
||||
<div class="video-strip-track">
|
||||
<video src={withSiteBase('/assets/videos/video-bench-electronique.mp4')} poster={withSiteBase('/assets/videos/video-bench-electronique-poster.webp')} controls muted loop playsinline preload="none" width="320" height="180" class="video-strip-clip" aria-label="Bench électronique en action"></video>
|
||||
<video src={withSiteBase('/assets/videos/video-test-prototype.mp4')} poster={withSiteBase('/assets/videos/video-test-prototype-poster.webp')} controls muted loop playsinline preload="none" width="320" height="180" class="video-strip-clip" aria-label="Test de prototype"></video>
|
||||
<video src={withSiteBase('/assets/videos/video-instrumentation.mp4')} poster={withSiteBase('/assets/videos/video-instrumentation-poster.webp')} controls muted loop playsinline preload="none" width="320" height="180" class="video-strip-clip" aria-label="Instrumentation et mesure"></video>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="structure-grid structure-grid--sprints" data-reveal>
|
||||
<div class="structure-cell structure-cell--sprints">
|
||||
<GraphicSprints />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="structure-grid structure-grid--faq" data-reveal>
|
||||
<div class="structure-cell structure-cell--faq">
|
||||
<Faq />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="structure-grid structure-grid--conversion" data-reveal>
|
||||
<div class="structure-cell structure-cell--contact">
|
||||
<Contact />
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer class="site-footer border-t border-[var(--line)] py-6 text-sm text-[var(--text-muted)]">
|
||||
<div class="site-shell footer-grid footer-grid--enhanced">
|
||||
<div class="footer-col footer-col--brand">
|
||||
<p class="m-0 footer-title">L'électron rare</p>
|
||||
<div class="footer-brand-meta">
|
||||
<p class="mb-0 footer-copy">
|
||||
© 2026 Clément Saillant - systèmes électroniques spécifiques, conseil, formation — industrie, culture et projets multi-techniques.
|
||||
</p>
|
||||
<p class="mb-0 footer-copy footer-copy--right">
|
||||
Contact direct : LinkedIn DM ou contact@lelectronrare.fr
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<nav aria-label="Ressources studio" class="footer-links footer-links--single-rail">
|
||||
<a href="#a-propos" class="footer-link">Approche</a>
|
||||
<a href="#graphic-sprints-title" class="footer-link">Missions</a>
|
||||
<a href={formationHref} class="footer-link">Formation</a>
|
||||
<a href="#faq" class="footer-link">FAQ</a>
|
||||
<a href="#contact" class="footer-link">Contact</a>
|
||||
<a
|
||||
href="https://fr.linkedin.com/in/electron-rare"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer me"
|
||||
{...trackAttrs(TRACK_EVENTS.outboundLinkedinContact, 'linkedin.com')}
|
||||
class="footer-link"
|
||||
>
|
||||
LinkedIn
|
||||
</a>
|
||||
<a
|
||||
href="mailto:contact@lelectronrare.fr"
|
||||
{...trackAttrs(TRACK_EVENTS.outboundEmailContact, 'mailto:contact@lelectronrare.fr')}
|
||||
class="footer-link"
|
||||
>
|
||||
contact@lelectronrare.fr
|
||||
</a>
|
||||
<a
|
||||
href="https://github.com/electron-rare/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer me"
|
||||
{...trackAttrs(TRACK_EVENTS.outboundGithubContact, 'github.com')}
|
||||
class="footer-link"
|
||||
>
|
||||
GitHub
|
||||
</a>
|
||||
<a
|
||||
href="https://blog.saillant.cc"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer me"
|
||||
class="footer-link"
|
||||
>
|
||||
Blog technique
|
||||
</a>
|
||||
<a href={mentionsHref} class="footer-link">Mentions légales</a>
|
||||
</nav>
|
||||
<div class="timer" id="timer">
|
||||
<div class="unit"><span class="num" id="days">--</span><span class="label">jours</span></div>
|
||||
<div class="sep">:</div>
|
||||
<div class="unit"><span class="num" id="hours">--</span><span class="label">heures</span></div>
|
||||
<div class="sep">:</div>
|
||||
<div class="unit"><span class="num" id="minutes">--</span><span class="label">minutes</span></div>
|
||||
<div class="sep">:</div>
|
||||
<div class="unit"><span class="num" id="seconds">--</span><span class="label">secondes</span></div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<p class="tagline" id="tagline"></p>
|
||||
|
||||
<p class="footnote">Systèmes électroniques spécifiques · Formations · Prototypage</p>
|
||||
</main>
|
||||
</BaseLayout>
|
||||
|
||||
<script>
|
||||
const LAUNCH = new Date('2026-05-01T00:00:00+02:00').getTime();
|
||||
|
||||
const messages = [
|
||||
"Charge des condensateurs en cours...",
|
||||
"Calibration de l'oscilloscope...",
|
||||
"Soudure des derniers composants...",
|
||||
"Vérification de la BOM...",
|
||||
"Flash du firmware en cours...",
|
||||
"Mesure de continuité... bip.",
|
||||
"Routage des dernières pistes...",
|
||||
"Test de la chaîne d'alimentation...",
|
||||
"Programmation du bootloader...",
|
||||
"Revue de schéma en cours...",
|
||||
"Placement des composants CMS...",
|
||||
"Passage au four de refusion...",
|
||||
"Debug JTAG en cours...",
|
||||
"Vérification du plan de masse...",
|
||||
"Analyse du spectre RF..."
|
||||
];
|
||||
|
||||
let msgIndex = Math.floor(Math.random() * messages.length);
|
||||
|
||||
function pad(n) { return String(n).padStart(2, '0'); }
|
||||
|
||||
function update() {
|
||||
const now = Date.now();
|
||||
const diff = Math.max(0, LAUNCH - now);
|
||||
const d = Math.floor(diff / 86400000);
|
||||
const h = Math.floor((diff % 86400000) / 3600000);
|
||||
const m = Math.floor((diff % 3600000) / 60000);
|
||||
const s = Math.floor((diff % 60000) / 1000);
|
||||
document.getElementById('days').textContent = pad(d);
|
||||
document.getElementById('hours').textContent = pad(h);
|
||||
document.getElementById('minutes').textContent = pad(m);
|
||||
document.getElementById('seconds').textContent = pad(s);
|
||||
}
|
||||
|
||||
function rotateMessage() {
|
||||
const el = document.getElementById('tagline');
|
||||
el.style.opacity = '0';
|
||||
setTimeout(() => {
|
||||
el.textContent = messages[msgIndex];
|
||||
el.style.opacity = '1';
|
||||
msgIndex = (msgIndex + 1) % messages.length;
|
||||
}, 400);
|
||||
}
|
||||
|
||||
update();
|
||||
rotateMessage();
|
||||
setInterval(update, 1000);
|
||||
setInterval(rotateMessage, 8000);
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.countdown-page {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
padding: 48px 24px;
|
||||
background: var(--bg, #0b0a09);
|
||||
}
|
||||
|
||||
.atom {
|
||||
width: clamp(180px, 35vw, 260px);
|
||||
height: auto;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-family: 'Fraunces', serif;
|
||||
font-size: clamp(28px, 5vw, 48px);
|
||||
font-weight: 700;
|
||||
color: var(--text, #f4eee3);
|
||||
margin: 0 0 32px;
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
|
||||
.electric {
|
||||
color: var(--electric, #5bd1d8);
|
||||
animation: pulse 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.6; text-shadow: 0 0 12px var(--electric, #5bd1d8); }
|
||||
}
|
||||
|
||||
.timer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.unit {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.num {
|
||||
font-family: 'IBM Plex Mono', 'Share Tech Mono', monospace;
|
||||
font-size: clamp(36px, 8vw, 72px);
|
||||
font-weight: 600;
|
||||
color: var(--text, #f4eee3);
|
||||
line-height: 1;
|
||||
min-width: 2ch;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-family: 'IBM Plex Mono', monospace;
|
||||
font-size: 11px;
|
||||
color: var(--text-dim, #b6a894);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 2px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.sep {
|
||||
font-family: 'IBM Plex Mono', monospace;
|
||||
font-size: clamp(28px, 6vw, 56px);
|
||||
color: var(--accent, #f1c27a);
|
||||
opacity: 0.5;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.tagline {
|
||||
font-family: 'IBM Plex Mono', monospace;
|
||||
font-size: 14px;
|
||||
color: var(--accent, #f1c27a);
|
||||
margin: 0 0 40px;
|
||||
min-height: 1.5em;
|
||||
transition: opacity 0.4s ease;
|
||||
}
|
||||
|
||||
.footnote {
|
||||
font-family: 'IBM Plex Mono', monospace;
|
||||
font-size: 12px;
|
||||
color: var(--text-dim, #b6a894);
|
||||
opacity: 0.4;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user