feat: add OVH FTP preview deployment

This commit is contained in:
Clément SAILLANT
2026-03-14 14:29:27 +00:00
parent 49b0f6ae23
commit 3c641ba79a
17 changed files with 329 additions and 104 deletions
+4 -1
View File
@@ -3,4 +3,7 @@ FTP_USER=ecobsoleiq
FTP_PORT=21 FTP_PORT=21
FTP_REMOTE_DIR=/www FTP_REMOTE_DIR=/www
FTP_PASS=your-ftp-password FTP_PASS=your-ftp-password
PUBLIC_SITE_URL=https://ton-domaine.fr/ PUBLIC_SITE_URL_PROD=https://www.lelectronrare.fr/
PUBLIC_SITE_URL_PREVIEW=https://www.lelectronrare.fr/preview/
FTP_DNS_RETRIES=5
FTP_DNS_DELAY=2
+61
View File
@@ -0,0 +1,61 @@
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 }}"
+1 -1
View File
@@ -10,6 +10,6 @@
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
<script type="module" src="/src/main.jsx"></script> <script type="module" src="./src/main.jsx"></script>
</body> </body>
</html> </html>
+2 -1
View File
@@ -2,13 +2,14 @@ import { NavLink, Route, Routes } from 'react-router-dom';
import { LabHomePage } from './pages/LabHomePage'; import { LabHomePage } from './pages/LabHomePage';
import { SignalsPage } from './pages/SignalsPage'; import { SignalsPage } from './pages/SignalsPage';
import { PrototypesPage } from './pages/PrototypesPage'; import { PrototypesPage } from './pages/PrototypesPage';
import { withSiteBase } from './lib/paths';
export function App() { export function App() {
return ( return (
<div className="lab-app"> <div className="lab-app">
<header className="lab-header"> <header className="lab-header">
<div className="lab-shell"> <div className="lab-shell">
<a className="lab-brand" href="/"> <a className="lab-brand" href={withSiteBase('/')}>
Retour site principal Retour site principal
</a> </a>
<nav aria-label="Navigation lab"> <nav aria-label="Navigation lab">
+29
View File
@@ -0,0 +1,29 @@
const normalizeBaseUrl = (value) => (value.endsWith('/') ? value : `${value}/`);
export const LAB_BASE_URL = normalizeBaseUrl(import.meta.env.BASE_URL || '/lab/');
export const SITE_BASE_URL = LAB_BASE_URL.replace(/lab\/$/, '');
const isExternalUrl = (value) =>
/^(?:[a-z]+:)?\/\//i.test(value) || value.startsWith('mailto:') || value.startsWith('tel:');
export function withSiteBase(value) {
if (!value || isExternalUrl(value)) {
return value;
}
if (value.startsWith('/#')) {
return `${SITE_BASE_URL}#${value.slice(2)}`;
}
if (value.startsWith('#') || value.startsWith('?')) {
return value;
}
const normalizedPath = value.startsWith('/') ? value.slice(1) : value;
return `${SITE_BASE_URL}${normalizedPath}`;
}
export function labHashHref(pathname = '/') {
const normalizedPath = pathname === '/' ? '/' : pathname.startsWith('/') ? pathname : `/${pathname}`;
return `${LAB_BASE_URL}#${normalizedPath}`;
}
@@ -1,4 +1,8 @@
import { withSiteBase } from '../lib/paths';
export function LabHomePage() { export function LabHomePage() {
const labPublicPath = `${withSiteBase('/lab/')}`;
return ( return (
<section className="lab-panel"> <section className="lab-panel">
<p className="lab-kicker">Module C</p> <p className="lab-kicker">Module C</p>
@@ -18,7 +22,7 @@ export function LabHomePage() {
<article> <article>
<h2>Contrat d'integration</h2> <h2>Contrat d'integration</h2>
<p> <p>
Chemin public: <code>/lab/</code>. Deploiement via build Vite, independent du template statique. Chemin public: <code>{labPublicPath}</code>. Deploiement via build Vite, independent du template statique.
</p> </p>
</article> </article>
</div> </div>
+7 -1
View File
@@ -1,9 +1,15 @@
import { defineConfig } from 'vite'; import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react'; import react from '@vitejs/plugin-react';
const normalizeUrl = (value) => (value.endsWith('/') ? value : `${value}/`);
const rawSiteUrl = process.env.PUBLIC_SITE_URL || process.env.EXTERNAL_SITE_URL || 'https://electron-rare.github.io/';
const parsedSiteUrl = new URL(normalizeUrl(rawSiteUrl));
const siteBasePath = parsedSiteUrl.pathname === '/' ? '' : parsedSiteUrl.pathname.replace(/\/$/, '');
const labBase = `${siteBasePath}/lab/`;
export default defineConfig({ export default defineConfig({
plugins: [react()], plugins: [react()],
base: '/lab/', base: labBase,
build: { build: {
outDir: '../../lab', outDir: '../../lab',
emptyOutDir: true emptyOutDir: true
+6 -1
View File
@@ -2,10 +2,15 @@ import { defineConfig } from 'astro/config';
import react from '@astrojs/react'; import react from '@astrojs/react';
import tailwindcss from '@tailwindcss/vite'; import tailwindcss from '@tailwindcss/vite';
const siteUrl = process.env.PUBLIC_SITE_URL || 'https://electron-rare.github.io/'; const normalizeUrl = (value) => (value.endsWith('/') ? value : `${value}/`);
const rawSiteUrl = process.env.PUBLIC_SITE_URL || process.env.EXTERNAL_SITE_URL || 'https://electron-rare.github.io/';
const siteUrl = normalizeUrl(rawSiteUrl);
const sitePathname = new URL(siteUrl).pathname;
const basePath = sitePathname === '/' ? '/' : sitePathname.replace(/\/$/, '');
export default defineConfig({ export default defineConfig({
site: siteUrl, site: siteUrl,
base: basePath,
integrations: [react()], integrations: [react()],
vite: { vite: {
plugins: [tailwindcss()] plugins: [tailwindcss()]
@@ -0,0 +1,50 @@
# OVH FTP Preview Deploy
## Pourquoi
Le deploy FTP direct depuis la machine locale bloque sur le canal de donnees FTP OVH.
Le build local fonctionne, mais l'upload reste instable.
## Solution retenue
Deployer vers OVH FTP depuis un runner GitHub Actions public.
Le runner :
- build le site avec la bonne `PUBLIC_SITE_URL`
- choisit la cible `preview` ou `production`
- synchronise `dist/` via `lftp`
## Secrets GitHub requis
- `OVH_FTP_HOST`
- `OVH_FTP_USER`
- `OVH_FTP_PASS`
- `OVH_FTP_PORT`
- `OVH_FTP_REMOTE_DIR`
## Variables GitHub requises
- `PUBLIC_SITE_URL_PROD=https://www.lelectronrare.fr/`
- `PUBLIC_SITE_URL_PREVIEW=https://www.lelectronrare.fr/preview/`
## Workflow
Fichier :
- `.github/workflows/deploy-ovh-ftp.yml`
Script cible :
- `scripts/deploy-ovh-ftp-target.sh`
## Usage
### Preview
```bash
gh workflow run deploy-ovh-ftp.yml --ref <branche> -f target=preview
```
### Production
```bash
gh workflow run deploy-ovh-ftp.yml --ref <branche> -f target=production
```
+1 -1
View File
@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1400 700" role="img" aria-label="Electron rare logo lockup"><image href="/assets/brand/logo-lockup.png" width="1400" height="700" preserveAspectRatio="xMidYMid meet"/></svg> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1400 700" role="img" aria-label="Electron rare logo lockup"><image href="logo-lockup.png" width="1400" height="700" preserveAspectRatio="xMidYMid meet"/></svg>

Before

Width:  |  Height:  |  Size: 227 B

After

Width:  |  Height:  |  Size: 213 B

+1 -1
View File
@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" role="img" aria-label="Electron rare logo mark"><image href="/assets/brand/logo-mark.png" width="1024" height="1024" preserveAspectRatio="xMidYMid meet"/></svg> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" role="img" aria-label="Electron rare logo mark"><image href="logo-mark.png" width="1024" height="1024" preserveAspectRatio="xMidYMid meet"/></svg>

Before

Width:  |  Height:  |  Size: 225 B

After

Width:  |  Height:  |  Size: 211 B

+1 -1
View File
@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" role="img" aria-label="Electron rare favicon"><image href="/assets/brand/logo-mark.png" width="1024" height="1024" preserveAspectRatio="xMidYMid meet"/></svg> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" role="img" aria-label="Electron rare favicon"><image href="assets/brand/logo-mark.png" width="1024" height="1024" preserveAspectRatio="xMidYMid meet"/></svg>

Before

Width:  |  Height:  |  Size: 223 B

After

Width:  |  Height:  |  Size: 222 B

+69
View File
@@ -0,0 +1,69 @@
#!/usr/bin/env bash
set -euo pipefail
TARGET="${1:-preview}"
LOCAL_DIR="${2:-dist}"
FTP_ENV_FILE="${FTP_ENV_FILE:-.env.ftp}"
load_env_file() {
if [[ ! -f "$FTP_ENV_FILE" ]]; then
return
fi
set -a
# shellcheck disable=SC1090
source "$FTP_ENV_FILE"
set +a
}
resolve_base_remote_dir() {
if [[ -n "${FTP_REMOTE_DIR:-}" ]]; then
printf '%s\n' "${FTP_REMOTE_DIR%/}"
return
fi
if [[ -n "${FTP_URL:-}" && "$FTP_URL" =~ ^ftp://[^@:/]+@[^:/]+(:([0-9]+))?(/.*)?$ ]]; then
printf '%s\n' "${BASH_REMATCH[3]:-/www}"
return
fi
printf '%s\n' "/www"
}
set_target_env() {
local base_remote_dir
base_remote_dir="$(resolve_base_remote_dir)"
case "$TARGET" in
preview)
export PUBLIC_SITE_URL="${PUBLIC_SITE_URL_PREVIEW:-https://www.lelectronrare.fr/preview/}"
export FTP_REMOTE_DIR="${base_remote_dir}/preview"
;;
production|prod)
export PUBLIC_SITE_URL="${PUBLIC_SITE_URL_PROD:-${PUBLIC_SITE_URL:-https://www.lelectronrare.fr/}}"
export FTP_REMOTE_DIR="${base_remote_dir}"
;;
*)
echo "Unknown target: ${TARGET}. Use 'preview' or 'production'."
exit 1
;;
esac
unset FTP_URL
}
main() {
load_env_file
set_target_env
if [[ "${DEPLOY_TARGET_SKIP_BUILD:-0}" != "1" ]]; then
npm run build:external
else
echo "[deploy-ovh-ftp-target] Skipping build:external (already built)."
fi
bash scripts/deploy-ovh-ftp.sh "$LOCAL_DIR"
}
main "$@"
+14 -12
View File
@@ -1,5 +1,6 @@
import * as React from 'react'; import * as React from 'react';
import type { CtaLink } from '@/lib/types'; import type { CtaLink } from '@/lib/types';
import { withSiteBase } from '@/lib/site';
import { TRACK_EVENTS, trackAttrs } from '@/lib/tracking'; import { TRACK_EVENTS, trackAttrs } from '@/lib/tracking';
const heroCtas: CtaLink[] = [ const heroCtas: CtaLink[] = [
@@ -24,6 +25,7 @@ const heroCtas: CtaLink[] = [
]; ];
const HERO_TITLE_WORDS = ['Conception', 'electronique', 'sur mesure']; const HERO_TITLE_WORDS = ['Conception', 'electronique', 'sur mesure'];
const assetPath = (value: string) => withSiteBase(value);
const HERO_VARIANT_ASSETS: Record< const HERO_VARIANT_ASSETS: Record<
string, string,
@@ -45,18 +47,18 @@ const HERO_VARIANT_ASSETS: Record<
} }
> = { > = {
v12: { v12: {
routingDesktopWebp: '/assets/da/openai/hero-pcb-routing-map-v2.webp', routingDesktopWebp: assetPath('/assets/da/openai/hero-pcb-routing-map-v2.webp'),
routingMobileWebp: '/assets/da/openai/hero-pcb-routing-map-mobile-low-noise-v2.webp', routingMobileWebp: assetPath('/assets/da/openai/hero-pcb-routing-map-mobile-low-noise-v2.webp'),
routingDesktop: '/assets/da/openai/hero-pcb-routing-map-v2.png', routingDesktop: assetPath('/assets/da/openai/hero-pcb-routing-map-v2.png'),
routingMobile: '/assets/da/openai/hero-pcb-routing-map-mobile-low-noise-v2.png', routingMobile: assetPath('/assets/da/openai/hero-pcb-routing-map-mobile-low-noise-v2.png'),
instrumentDesktopWebp: '/assets/da/openai/hero-instrument-panel-v2.webp', instrumentDesktopWebp: assetPath('/assets/da/openai/hero-instrument-panel-v2.webp'),
instrumentMobileWebp: '/assets/da/openai/hero-instrument-panel-mobile-low-noise-v2.webp', instrumentMobileWebp: assetPath('/assets/da/openai/hero-instrument-panel-mobile-low-noise-v2.webp'),
instrumentDesktop: '/assets/da/openai/hero-instrument-panel-v2.png', instrumentDesktop: assetPath('/assets/da/openai/hero-instrument-panel-v2.png'),
instrumentMobile: '/assets/da/openai/hero-instrument-panel-mobile-low-noise-v2.png', instrumentMobile: assetPath('/assets/da/openai/hero-instrument-panel-mobile-low-noise-v2.png'),
notebookDesktopWebp: '/assets/da/openai/hero-carnet-labo-open-v2.webp', notebookDesktopWebp: assetPath('/assets/da/openai/hero-carnet-labo-open-v2.webp'),
notebookMobileWebp: '/assets/da/openai/carnet-labo-ouvert.webp', notebookMobileWebp: assetPath('/assets/da/openai/carnet-labo-ouvert.webp'),
notebookDesktop: '/assets/da/openai/hero-carnet-labo-open-v2.png', notebookDesktop: assetPath('/assets/da/openai/hero-carnet-labo-open-v2.png'),
notebookMobile: '/assets/da/openai/carnet-labo-ouvert.png', notebookMobile: assetPath('/assets/da/openai/carnet-labo-ouvert.png'),
routingCaption: 'Architecture pcb', routingCaption: 'Architecture pcb',
instrumentCaption: 'Appareil de mesure (asset)' instrumentCaption: 'Appareil de mesure (asset)'
} }
+29 -73
View File
@@ -1,5 +1,14 @@
--- ---
import { GTM_CONTAINER_ID_DEFAULT, SITE_META, SITE_OG_IMAGE_URL, SITE_URL } from '@/lib/site'; import {
CANONICAL_URL,
GTM_CONTAINER_ID_DEFAULT,
PUBLIC_SITE_ROOT_URL,
SITE_IS_SUBPATH_BUILD,
SITE_META,
SITE_OG_IMAGE_URL,
stripSiteBase,
withSiteBase
} from '@/lib/site';
interface Props { interface Props {
title?: string; title?: string;
@@ -11,25 +20,28 @@ const {
description = SITE_META.description description = SITE_META.description
} = Astro.props; } = Astro.props;
const canonical = SITE_URL; const pagePath = stripSiteBase(Astro.url.pathname);
const canonical = new URL(pagePath, CANONICAL_URL).href;
const pageUrl = new URL(pagePath, PUBLIC_SITE_ROOT_URL).href;
const robotsContent = SITE_IS_SUBPATH_BUILD ? 'noindex,nofollow,noarchive' : 'index,follow,max-image-preview:large';
const gtmId = import.meta.env.PUBLIC_GTM_CONTAINER_ID || GTM_CONTAINER_ID_DEFAULT; const gtmId = import.meta.env.PUBLIC_GTM_CONTAINER_ID || GTM_CONTAINER_ID_DEFAULT;
const structuredDataJsonLd = JSON.stringify({ const structuredDataJsonLd = JSON.stringify({
'@context': 'https://schema.org', '@context': 'https://schema.org',
'@graph': [ '@graph': [
{ {
'@type': 'WebSite', '@type': 'WebSite',
'@id': `${canonical}#website`, '@id': `${CANONICAL_URL}#website`,
url: canonical, url: CANONICAL_URL,
name: "L'électron rare", name: "L'électron rare",
inLanguage: 'fr-FR', inLanguage: 'fr-FR',
description: SITE_META.description description: SITE_META.description
}, },
{ {
'@type': 'Organization', '@type': 'Organization',
'@id': `${canonical}#organization`, '@id': `${CANONICAL_URL}#organization`,
name: "L'électron rare", name: "L'électron rare",
url: canonical, url: CANONICAL_URL,
logo: `${canonical}assets/brand/logo-mark.png`, logo: new URL('assets/brand/logo-mark.png', CANONICAL_URL).href,
sameAs: [ sameAs: [
'https://fr.linkedin.com/in/electron-rare', 'https://fr.linkedin.com/in/electron-rare',
'https://www.malt.com/profile/clementsaillant', 'https://www.malt.com/profile/clementsaillant',
@@ -38,15 +50,15 @@ const structuredDataJsonLd = JSON.stringify({
}, },
{ {
'@type': 'Person', '@type': 'Person',
'@id': `${canonical}#person`, '@id': `${CANONICAL_URL}#person`,
name: 'Clément Saillant', name: 'Clément Saillant',
alternateName: "L'électron rare", alternateName: "L'électron rare",
url: canonical, url: CANONICAL_URL,
jobTitle: 'Concepteur en création électronique, design produit et systèmes expérimentaux', jobTitle: 'Concepteur en création électronique, design produit et systèmes expérimentaux',
description: description:
"Codeur créatif, itérateur IA et inventeur de systèmes : création électronique, performances audiovisuelles et collaborations studio sous l'identité L'électron rare.", "Codeur créatif, itérateur IA et inventeur de systèmes : création électronique, performances audiovisuelles et collaborations studio sous l'identité L'électron rare.",
worksFor: { worksFor: {
'@id': `${canonical}#organization` '@id': `${CANONICAL_URL}#organization`
}, },
sameAs: [ sameAs: [
'https://fr.linkedin.com/in/electron-rare', 'https://fr.linkedin.com/in/electron-rare',
@@ -66,20 +78,20 @@ const structuredDataJsonLd = JSON.stringify({
<title>{title}</title> <title>{title}</title>
<meta name="theme-color" content={SITE_META.themeColor} /> <meta name="theme-color" content={SITE_META.themeColor} />
<meta name="description" content={description} /> <meta name="description" content={description} />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="icon" type="image/svg+xml" href={withSiteBase('/favicon.svg')} />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" /> <link rel="icon" type="image/png" sizes="32x32" href={withSiteBase('/favicon-32x32.png')} />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" /> <link rel="icon" type="image/png" sizes="16x16" href={withSiteBase('/favicon-16x16.png')} />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" /> <link rel="apple-touch-icon" sizes="180x180" href={withSiteBase('/apple-touch-icon.png')} />
<link rel="shortcut icon" href="/favicon.ico" /> <link rel="shortcut icon" href={withSiteBase('/favicon.ico')} />
<link rel="canonical" href={canonical} /> <link rel="canonical" href={canonical} />
<meta name="robots" content="index,follow,max-image-preview:large" /> <meta name="robots" content={robotsContent} />
<meta property="og:type" content="website" /> <meta property="og:type" content="website" />
<meta property="og:locale" content="fr_FR" /> <meta property="og:locale" content="fr_FR" />
<meta property="og:site_name" content="L'électron rare" /> <meta property="og:site_name" content="L'électron rare" />
<meta property="og:title" content={title} /> <meta property="og:title" content={title} />
<meta property="og:url" content={canonical} /> <meta property="og:url" content={pageUrl} />
<meta property="og:description" content={SITE_META.ogDescription} /> <meta property="og:description" content={SITE_META.ogDescription} />
<meta property="og:image" content={SITE_OG_IMAGE_URL} /> <meta property="og:image" content={SITE_OG_IMAGE_URL} />
<meta property="og:image:width" content="1200" /> <meta property="og:image:width" content="1200" />
@@ -413,62 +425,6 @@ const structuredDataJsonLd = JSON.stringify({
})(); })();
</script> </script>
<script is:inline>
(function () {
var root = document.documentElement;
var key = "lelectron-theme";
var defaultTheme = "default";
var lightTheme = 'light';
var highContrastTheme = 'high-contrast';
var themes = [defaultTheme, lightTheme, highContrastTheme];
// Prefer the atelier (light) theme by default; allow cycling to standard + high contrast.
var preferred = window.matchMedia && window.matchMedia('(prefers-contrast: more)').matches ? highContrastTheme : lightTheme;
var labelForTheme = function (theme) {
if (theme === lightTheme) {
return 'Mode contraste';
}
if (theme === highContrastTheme) {
return 'Mode standard';
}
return 'Mode clair';
};
var applyTheme = function (theme) {
root.setAttribute('data-theme', theme);
window.localStorage.setItem(key, theme);
var button = document.querySelector('[data-theme-toggle]');
if (!button) {
return;
}
button.textContent = labelForTheme(theme);
button.setAttribute('aria-pressed', theme === highContrastTheme ? 'true' : 'false');
};
var toggleTheme = function () {
var current = root.getAttribute('data-theme') || defaultTheme;
var index = themes.indexOf(current);
var nextTheme = themes[(index + 1) % themes.length];
applyTheme(nextTheme);
};
var stored = window.localStorage.getItem(key);
if (stored === defaultTheme || stored === lightTheme || stored === highContrastTheme) {
root.setAttribute('data-theme', stored);
} else {
root.setAttribute('data-theme', preferred);
}
var button = document.querySelector('[data-theme-toggle]');
if (button) {
button.addEventListener('click', toggleTheme);
}
applyTheme(root.getAttribute('data-theme') || defaultTheme);
})();
</script>
<script is:inline> <script is:inline>
(function () { (function () {
var nav = document.querySelector('.site-nav'); var nav = document.querySelector('.site-nav');
+49 -7
View File
@@ -1,6 +1,53 @@
const normalizeUrl = (value: string) => (value.endsWith('/') ? value : `${value}/`); const normalizeUrl = (value: string) => (value.endsWith('/') ? value : `${value}/`);
export const SITE_URL = normalizeUrl(import.meta.env.PUBLIC_SITE_URL || 'https://electron-rare.github.io/'); const resolvedSiteUrl = import.meta.env.PUBLIC_SITE_URL || 'https://electron-rare.github.io/';
const parsedSiteUrl = new URL(normalizeUrl(resolvedSiteUrl));
const resolvedBasePath = parsedSiteUrl.pathname === '/' ? '' : parsedSiteUrl.pathname.replace(/\/$/, '');
export const SITE_URL = parsedSiteUrl.href;
export const SITE_BASE_PATH = resolvedBasePath;
export const SITE_IS_SUBPATH_BUILD = SITE_BASE_PATH !== '';
export const CANONICAL_URL = 'https://www.lelectronrare.fr/';
export const PUBLIC_SITE_ROOT_URL = SITE_IS_SUBPATH_BUILD ? SITE_URL : CANONICAL_URL;
export function withSiteBase(value: string) {
if (!value) {
return SITE_BASE_PATH || '/';
}
if (/^(?:[a-z]+:)?\/\//i.test(value) || value.startsWith('mailto:') || value.startsWith('tel:')) {
return value;
}
if (value.startsWith('#') || value.startsWith('?')) {
return value;
}
const normalizedPath = value.startsWith('/') ? value : `/${value}`;
return SITE_BASE_PATH ? `${SITE_BASE_PATH}${normalizedPath}` : normalizedPath;
}
export function stripSiteBase(pathname: string) {
if (!pathname) {
return '/';
}
if (!SITE_BASE_PATH) {
return pathname;
}
if (pathname === SITE_BASE_PATH) {
return '/';
}
if (pathname.startsWith(`${SITE_BASE_PATH}/`)) {
return pathname.slice(SITE_BASE_PATH.length) || '/';
}
return pathname;
}
export const SITE_META = { export const SITE_META = {
title: "Clément Saillant — L'électron rare", title: "Clément Saillant — L'électron rare",
@@ -14,11 +61,6 @@ export const SITE_META = {
themeColor: '#f4eee3' themeColor: '#f4eee3'
} as const; } as const;
export const SITE_OG_IMAGE_URL = `${SITE_URL}${SITE_META.ogImagePath}`; export const SITE_OG_IMAGE_URL = new URL(SITE_META.ogImagePath, PUBLIC_SITE_ROOT_URL).href;
export const GTM_CONTAINER_ID_DEFAULT = 'GTM-5SLM67QF'; export const GTM_CONTAINER_ID_DEFAULT = 'GTM-5SLM67QF';
export const BRAND_ASSETS = {
markPath: 'assets/brand/logo-mark.png',
lockupPath: 'assets/brand/logo-lockup.png'
} as const;
-3
View File
@@ -26,9 +26,6 @@ import { TRACK_EVENTS, trackAttrs } from '@/lib/tracking';
<li><a href="#contact" class="figma-lab-nav-link site-navlink">#contact</a></li> <li><a href="#contact" class="figma-lab-nav-link site-navlink">#contact</a></li>
</ul> </ul>
</nav> </nav>
<button class="site-contrast-toggle" data-theme-toggle type="button" aria-pressed="false">
Mode contraste
</button>
</div> </div>
</header> </header>