Clean white-contrast rollout and sync tracking checks

This commit is contained in:
Clément SAILLANT
2026-03-14 17:59:43 +00:00
parent 58f8011657
commit 9d83265731
4 changed files with 97 additions and 185 deletions
+5 -4
View File
@@ -94,18 +94,19 @@ Consolider la DA actuelle en version claire, contraste blanc, sans reintroduire
- contact direct
## Backlog P0
- [ ] Forcer la cascade sur la DA blanche dans `BaseLayout.astro`, `site.ts`, `global.css`, `home-workbench.css`.
- [ ] Relever les derniers blocs sombres restants: `hero-bg-photo`, `photo-strip`, `video-strip`, `contact-minitel`, FAQ focus.
- [x] Forcer la cascade sur la DA blanche dans `BaseLayout.astro`, `site.ts`, `global.css`, `home-workbench.css`.
- [x] Relever les derniers blocs sombres restants: `hero-bg-photo`, `photo-strip`, `video-strip`, `contact-minitel`, FAQ focus.
- [ ] Verifier la lisibilite des CTA sur blanc.
- [ ] Verifier que la home et `formation` partagent bien le meme socle visuel.
- [ ] Verifier que la home et `formation` partagent bien le meme socle visuel en preview live.
## Backlog P1
- [ ] Supprimer les references documentaires a `#projets`, Malt, GitHub Pages static prod, dual-rail hero, contrast toggle.
- [ ] Verifier les events `data-track` sur les CTA actifs uniquement.
- [x] Recaler `tracking:check` sur le contrat Astro actif pour sortir des faux positifs legacy.
- [ ] Valider preview OVH puis prod.
## Backlog P2
- [ ] Supprimer les reliquats `.site-contrast-toggle` du CSS global.
- [x] Supprimer les reliquats `.site-contrast-toggle` du CSS global.
- [ ] Evaluer si un vrai theme secondaire doit exister plus tard ou si le mode blanc reste la seule version publique.
- [ ] Consolider une archive explicite des anciens artefacts DA pour eviter toute reintroduction.
+5 -2
View File
@@ -22,11 +22,14 @@ Source of truth: this file for execution priorities, plus the current code in `s
- [x] Ancienne variante `studio/figma` sortie du runtime actif.
- [x] Bouton contraste retire du markup.
- [x] Theme blanc contraste force par defaut dans le layout source.
- [x] Reliquats CSS `.site-contrast-toggle` supprimes du source actif.
- [x] Build preview local `PUBLIC_SITE_URL=/preview/` repasse propre.
- [x] Validation tracking recalee sur le contrat Astro actif.
- [ ] White-contrast pass verifiee visuellement sur preview.
- [ ] White-contrast pass verifiee visuellement sur production.
## P0 - Bloc de sortie immediate
- [ ] Builder le preview avec le theme blanc contraste (`PUBLIC_SITE_URL=https://www.lelectronrare.fr/preview/ npm run build:external`).
- [x] Builder le preview avec le theme blanc contraste (`PUBLIC_SITE_URL=https://www.lelectronrare.fr/preview/ npm run build:external`).
- [ ] Verifier le rendu de la home: nav, hero, approche, cas clients, missions, FAQ, footer.
- [ ] Verifier les surfaces medias: hero photo, photo strip, video strip.
- [ ] Verifier la section contact en version claire: tabs, textarea, formulaire, focus clavier, messages d'etat.
@@ -55,7 +58,7 @@ Source of truth: this file for execution priorities, plus the current code in `s
- [ ] Soumettre ou recontroler Search Console sur la base de la prod actuelle OVH.
## P3 - Nettoyage et consolidation
- [ ] Supprimer les blocs CSS morts `.site-contrast-toggle` encore presents dans `src/styles/global.css`.
- [x] Supprimer les blocs CSS morts `.site-contrast-toggle` encore presents dans `src/styles/global.css`.
- [ ] Nettoyer les docs qui parlent encore de GitHub Pages static root, `#projets`, Malt, ou du toggle contraste.
- [ ] Mesurer LCP et budget media de la variante blanche en conditions reelles.
- [ ] Archiver les plans anterieurs devenus purement historiques.
+85 -34
View File
@@ -1,45 +1,96 @@
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const htmlPath = new URL('../index.html', import.meta.url);
const html = fs.readFileSync(htmlPath, 'utf8');
const expectedPrimary = [
'cta_hero_projets',
'cta_hero_contact',
'cta_hero_profile',
'outbound_linkedin_contact',
'outbound_malt_contact',
'outbound_bandcamp_contact',
'outbound_github_contact',
'outbound_github_project_rtc_bl_phone',
'outbound_github_project_zacus',
'outbound_github_project_site',
'outbound_github_lab_more',
'cta_lab_interactif_open'
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const repoRoot = path.resolve(__dirname, '..');
const trackingFile = path.join(repoRoot, 'src/lib/tracking.ts');
const sourceRoots = [
path.join(repoRoot, 'src/pages'),
path.join(repoRoot, 'src/components'),
path.join(repoRoot, 'src/content')
];
const expectedLegacy = ['outbound_github_project', 'outbound_github_contact'];
function walk(dir) {
return fs.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
return walk(fullPath);
}
if (/\.(astro|tsx|ts|jsx|js)$/.test(entry.name)) {
return [fullPath];
}
return [];
});
}
const dataTrackMatches = [...html.matchAll(/data-track="([^"]+)"/g)].map((m) => m[1]);
const dataLegacyMatches = [...html.matchAll(/data-track-legacy="([^"]+)"/g)].map((m) => m[1]);
const uniqueDataTrack = [...new Set(dataTrackMatches)];
const uniqueLegacyTrack = [...new Set(dataLegacyMatches)];
const missingPrimary = expectedPrimary.filter((eventName) => !uniqueDataTrack.includes(eventName));
const missingLegacy = expectedLegacy.filter((eventName) => !uniqueLegacyTrack.includes(eventName));
if (missingPrimary.length || missingLegacy.length) {
console.error('Tracking contract validation failed.');
if (missingPrimary.length) {
console.error(`Missing primary events: ${missingPrimary.join(', ')}`);
function readTrackEvents() {
const source = fs.readFileSync(trackingFile, 'utf8');
const objectMatch = source.match(/TRACK_EVENTS\s*=\s*\{([\s\S]*?)\}\s*as const/);
if (!objectMatch) {
throw new Error('Unable to parse TRACK_EVENTS from src/lib/tracking.ts');
}
if (missingLegacy.length) {
console.error(`Missing legacy events: ${missingLegacy.join(', ')}`);
const entries = [...objectMatch[1].matchAll(/(\w+)\s*:\s*'([^']+)'/g)].map((match) => ({
key: match[1],
value: match[2]
}));
return new Map(entries.map(({ key, value }) => [key, value]));
}
const knownEvents = readTrackEvents();
const usedEventKeys = new Set();
const usedRawEvents = new Set();
const legacyAttrs = [];
for (const sourceRoot of sourceRoots) {
for (const filePath of walk(sourceRoot)) {
const content = fs.readFileSync(filePath, 'utf8');
for (const match of content.matchAll(/TRACK_EVENTS\.(\w+)/g)) {
usedEventKeys.add(match[1]);
}
for (const match of content.matchAll(/data-track="([^"]+)"/g)) {
usedRawEvents.add(match[1]);
}
if (content.includes('data-track-legacy=')) {
legacyAttrs.push(path.relative(repoRoot, filePath));
}
}
}
const usedEvents = new Set(
[...usedEventKeys]
.filter((key) => knownEvents.has(key))
.map((key) => knownEvents.get(key))
.concat([...usedRawEvents])
);
const missingEvents = [...knownEvents.entries()]
.filter(([_, value]) => !usedEvents.has(value))
.map(([key, value]) => `${key}=${value}`);
const unexpectedEvents = [...usedEvents].filter(
(value) => ![...knownEvents.values()].includes(value)
);
if (legacyAttrs.length || missingEvents.length || unexpectedEvents.length) {
console.error('Tracking contract validation failed.');
if (legacyAttrs.length) {
console.error(`Legacy tracking attrs found in: ${legacyAttrs.join(', ')}`);
}
if (missingEvents.length) {
console.error(`Known events missing from active source: ${missingEvents.join(', ')}`);
}
if (unexpectedEvents.length) {
console.error(`Unknown tracking events found in active source: ${unexpectedEvents.join(', ')}`);
}
process.exit(1);
}
console.log('Tracking contract validation passed.');
console.log(`Primary events found (${uniqueDataTrack.length}): ${uniqueDataTrack.join(', ')}`);
console.log(`Legacy events found (${uniqueLegacyTrack.length}): ${uniqueLegacyTrack.join(', ')}`);
console.log(`Known events (${knownEvents.size}): ${[...knownEvents.values()].join(', ')}`);
console.log(`Active events used (${usedEvents.size}): ${[...usedEvents].join(', ')}`);
+2 -145
View File
@@ -786,32 +786,6 @@ h3 {
color: color-mix(in oklab, var(--electric) 82%, var(--text) 18%);
}
.site-contrast-toggle {
border: 1px solid color-mix(in oklab, var(--trace-cyan) 48%, transparent);
}
.site-contrast-toggle {
background: color-mix(in oklab, var(--surface-soft) 74%, transparent);
}
.site-contrast-toggle {
color: color-mix(in oklab, var(--electric) 90%, var(--text) 10%);
}
.site-contrast-toggle:hover,
.site-contrast-toggle:focus-visible {
border-color: color-mix(in oklab, var(--electric) 70%, transparent);
}
.site-contrast-toggle:hover,
.site-contrast-toggle:focus-visible {
background: color-mix(in oklab, var(--electric) 12%, transparent);
}
.site-contrast-toggle:before {
border: 1px solid color-mix(in oklab, var(--text) 70%, transparent);
}
.projects-timeline-item:before {
border: 1px solid color-mix(in oklab, var(--trace-cyan) 44%, transparent);
}
@@ -999,57 +973,6 @@ h3 {
.studio-muted {
color: var(--text-muted);
}
.site-contrast-toggle {
border: 1px solid var(--trace-cyan);
border-radius: 999px;
align-items: center;
gap: 0.45rem;
min-height: 2rem;
padding: 0 0.88rem;
display: inline-flex;
}
.site-contrast-toggle {
background: var(--surface-soft);
}
.site-contrast-toggle {
color: var(--electric);
}
.site-contrast-toggle {
letter-spacing: 0.04em;
white-space: nowrap;
cursor: pointer;
font-size: 0.78rem;
font-weight: 600;
transition:
background 0.2s,
border-color 0.2s,
transform 0.2s;
}
.site-contrast-toggle:hover,
.site-contrast-toggle:focus-visible {
border-color: var(--electric);
}
.site-contrast-toggle:hover,
.site-contrast-toggle:focus-visible {
background: var(--electric);
}
.site-contrast-toggle:active {
transform: translateY(1px);
}
.site-contrast-toggle:before {
content: "";
border: 1px solid var(--text);
border-radius: 999px;
width: 0.55rem;
height: 0.55rem;
}
.site-contrast-toggle:before {
background: radial-gradient(
circle at 35% 35%,
var(--trace-cyan),
transparent 58%
);
}
.projects-timeline-item {
grid-template-columns: 4.5rem 1fr;
gap: 0.32rem;
@@ -1224,11 +1147,6 @@ button:focus-visible {
flex-wrap: wrap;
justify-content: center;
}
.site-contrast-toggle {
justify-content: center;
width: 100%;
max-width: 14rem;
}
.site-shell-title {
max-width: 12ch;
}
@@ -1426,7 +1344,6 @@ li {
sans-serif;
}
.site-navlink,
.site-contrast-toggle,
.lab-kicker,
.lab-pill,
.studio-chip {
@@ -1572,16 +1489,6 @@ li {
border-radius: 0.3rem;
font-size: 0.61rem;
}
.site-contrast-toggle {
letter-spacing: 0.1em;
text-transform: uppercase;
border-radius: 0.28rem;
min-height: 1.95rem;
font-size: 0.66rem;
}
.site-contrast-toggle:before {
border-radius: 1px;
}
.circuit-board,
.lab-panel,
.studio-panel {
@@ -3178,25 +3085,6 @@ footer:before {
color: #3a2e22;
}
/* HIGH-CONTRAST: contrast toggle */
:root[data-da-variant="v12"][data-theme="high-contrast"] .site-contrast-toggle {
background: #ffffff;
border-color: #333;
color: #1a1a1a;
box-shadow: none;
}
:root[data-da-variant="v12"][data-theme="high-contrast"] .site-contrast-toggle:hover,
:root[data-da-variant="v12"][data-theme="high-contrast"] .site-contrast-toggle:focus-visible {
background: #f0e8dd;
border-color: #1a1a1a;
box-shadow: 0 0 0 2px #1a1a1a;
}
:root[data-da-variant="v12"][data-theme="high-contrast"] .site-contrast-toggle::before {
background: none;
}
/* HIGH-CONTRAST: pills / CTAs */
:root[data-da-variant="v12"][data-theme="high-contrast"] .figma-lab-pill--secondary,
:root[data-da-variant="v12"][data-theme="high-contrast"] .graphic-sprints-cta {
@@ -3481,25 +3369,6 @@ footer:before {
display: none;
}
:root[data-da-variant="v12"] .site-contrast-toggle {
min-height: 2.5rem;
border-color: rgba(120, 94, 73, 0.18);
background: rgba(255, 250, 243, 0.86);
color: #4d3a30;
box-shadow: 0 8px 20px rgba(99, 74, 54, 0.08);
}
:root[data-da-variant="v12"] .site-contrast-toggle:hover,
:root[data-da-variant="v12"] .site-contrast-toggle:focus-visible {
background: rgba(255, 253, 249, 0.98);
border-color: rgba(120, 94, 73, 0.28);
box-shadow: 0 12px 24px rgba(99, 74, 54, 0.12);
}
:root[data-da-variant="v12"] .site-contrast-toggle::before {
background: linear-gradient(90deg, rgba(183, 86, 44, 0.18), rgba(47, 104, 118, 0.18));
}
:root[data-da-variant="v12"] .figma-lab-nav-row {
min-height: 4.25rem;
gap: 1.2rem;
@@ -4351,8 +4220,7 @@ details[open] > .faq-question .faq-chevron::after {
gap: 0.5rem;
}
:root[data-da-variant="v12"] .figma-lab-nav-link,
:root[data-da-variant="v12"] .site-contrast-toggle {
:root[data-da-variant="v12"] .figma-lab-nav-link {
min-height: 2.2rem;
display: inline-flex;
align-items: center;
@@ -4376,9 +4244,7 @@ details[open] > .faq-question .faq-chevron::after {
:root[data-da-variant="v12"] .figma-lab-nav-link:hover,
:root[data-da-variant="v12"] .figma-lab-nav-link:focus-visible,
:root[data-da-variant="v12"] .figma-lab-nav-link.is-active,
:root[data-da-variant="v12"] .figma-lab-nav-link[aria-current="location"],
:root[data-da-variant="v12"] .site-contrast-toggle:hover,
:root[data-da-variant="v12"] .site-contrast-toggle:focus-visible {
:root[data-da-variant="v12"] .figma-lab-nav-link[aria-current="location"] {
color: #cbffd0;
border-color: rgba(51, 255, 255, 0.3);
background:
@@ -4390,10 +4256,6 @@ details[open] > .faq-question .faq-chevron::after {
text-shadow: 0 0 10px rgba(51, 255, 51, 0.22);
}
:root[data-da-variant="v12"] .site-contrast-toggle::before {
background: linear-gradient(90deg, rgba(51, 255, 255, 0.16), rgba(51, 255, 51, 0.16));
}
:root[data-da-variant="v12"] .figma-lab-left,
:root[data-da-variant="v12"] .figma-lab-right,
:root[data-da-variant="v12"] .circuit-board,
@@ -4737,7 +4599,6 @@ details[open] > .faq-question .faq-chevron::after {
@media (max-width: 767px) {
:root[data-da-variant="v12"] .figma-lab-nav-link,
:root[data-da-variant="v12"] .site-contrast-toggle,
:root[data-da-variant="v12"] .figma-lab-pill,
:root[data-da-variant="v12"] .graphic-sprints-cta {
width: 100%;
@@ -4758,10 +4619,6 @@ details[open] > .faq-question .faq-chevron::after {
align-items: center;
}
.site-contrast-toggle {
min-height: 2.75rem; /* 44px */
}
/* About section photo integration */
.about-intro {
display: flex;