Merge chore/tour-residuals into develop
This commit is contained in:
@@ -92,3 +92,12 @@ container is redeployed: merge → VPS `git pull && docker compose up -d
|
||||
|
||||
Commits: 4 (fix/clean/docs + this report). BREAKING: 1 (SEC-1, container
|
||||
port — compose covered). Branch left UNMERGED — `gitflow finish` on GO.
|
||||
|
||||
## Follow-up 2026-07-05 — residuals closed (chore/tour-residuals, user GO)
|
||||
|
||||
| ID | Resolution |
|
||||
|----|-----------|
|
||||
| CLN-3 | Card CSS deduplicated via grouped selectors (shared chrome/hover/head/title/tag blocks + per-class specifics), zero HTML change, cascade-order verified (no interfering same-specificity rules between shared and specific blocks), braces 195/195. Honest correction: the audited "~421 redundant lines" was overstated — real net dedup ≈ 60 lines. |
|
||||
| CLN-4 | Norm aligned with reality: the 8 functional neutrals (inks, rule/tag, 2 green intermediates) are now DOCUMENTED as allowed in CLAUDE.md (+ README pointer). "Any color outside the two lists is a violation" keeps the norm enforceable. |
|
||||
| SEC-7 | script-src hardened: `unsafe-inline` replaced by the sha256 hash of the single inline script (index has zero style/script attributes). style-src keeps `unsafe-inline` (CV carries 2 style attributes + single-file convention) — documented. NEW INVARIANT in CLAUDE.md: recompute the hash after any inline-JS edit (stale hash = JS silently blocked in prod). |
|
||||
| INF-2 | CORRECTION: false positive in the 2026-07-05 report-only run — `.gitignore` exists (549B) and covers the expected classes. No action was ever needed. |
|
||||
|
||||
@@ -66,7 +66,7 @@ The PDF must match the latest HTML before pushing or sending.
|
||||
|
||||
## Design system (non-negotiable)
|
||||
|
||||
Palette — exact hex:
|
||||
Palette — exact hex (brand colors):
|
||||
- `#0d1b12` — dark forest (nav, dark sections, footer)
|
||||
- `#1b5e3b` — green primary (links, section titles on light bg)
|
||||
- `#2d7a4f` — green accent (borders, dots, separators)
|
||||
@@ -74,6 +74,13 @@ Palette — exact hex:
|
||||
- `#dff0e7` — green tint (pill bg)
|
||||
- `#f5f3ec` — parchment (page bg)
|
||||
|
||||
Functional neutrals (allowed, intentional — layering + text, NOT brand):
|
||||
- `#183325` (`--dark-mid`), `#0e3320` (`--g900`), `#eef7f1` (`--g050`) —
|
||||
green-scale intermediates for dark layering and light block bg
|
||||
- `#111111` / `#1e1e1e` / `#636363` (`--ink-1/2/3`) — text hierarchy
|
||||
- `#d8d4c8` (`--rule`), `#e6e2d8` (`--tag`) — separators, generic tags
|
||||
Any color outside these two lists is a violation.
|
||||
|
||||
Typography:
|
||||
- `Fraunces` (serif) — display: hero name, section titles, role headings
|
||||
- `JetBrains Mono` (mono) — eyebrows, badges, tech pills, nav, contact rows
|
||||
@@ -124,6 +131,12 @@ None — global rules apply.
|
||||
- Edits to `index.html` or `CV_Bastien_Chanot.html` must preserve the
|
||||
palette + typography + structure unless explicitly asked to change them.
|
||||
- After editing `CV_Bastien_Chanot.html`, regenerate the PDF.
|
||||
- After editing index.html's inline `<script>`, recompute the CSP hash and
|
||||
update `nginx-security-headers.conf` (script-src is hash-pinned — a stale
|
||||
hash silently disables the JS in prod):
|
||||
```bash
|
||||
python3 -c "import hashlib,base64,re;h=base64.b64encode(hashlib.sha256(re.search(r'<script>(.*?)</script>',open('index.html',encoding='utf-8').read(),re.S).group(1).encode()).digest()).decode();print('sha256-'+h)"
|
||||
```
|
||||
- Never add external dependencies beyond Google Fonts.
|
||||
- Never add tracking, analytics, cookie banners or third-party scripts.
|
||||
- Always test in mobile width (375px) and desktop (1440px) before claiming done.
|
||||
|
||||
@@ -73,6 +73,9 @@ Strict palette (non-negotiable):
|
||||
| `#dff0e7` | Green tint — pill background |
|
||||
| `#f5f3ec` | Parchment — page background |
|
||||
|
||||
Plus a documented set of functional neutrals (text inks, rules/tags, two
|
||||
green-scale intermediates) — the exhaustive list lives in `CLAUDE.md`.
|
||||
|
||||
Typography:
|
||||
- `Fraunces` — display (names, titles)
|
||||
- `JetBrains Mono` — technical labels, badges, pills, nav, contact
|
||||
|
||||
+19
-64
@@ -419,39 +419,49 @@
|
||||
gap: 20px;
|
||||
margin-top: 40px;
|
||||
}
|
||||
.stack-card {
|
||||
/* ── Shared card chrome (stack / project / theme cards + méthode items).
|
||||
Per-class blocks below keep only their specifics; the cascade resolves
|
||||
identically to the previous duplicated declarations. ── */
|
||||
.stack-card, .project-card, .theme-card, .methode-item {
|
||||
background: var(--page);
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: var(--r-md);
|
||||
padding: 24px;
|
||||
transition: border-color .25s ease, transform .25s ease, box-shadow .25s ease;
|
||||
}
|
||||
.stack-card:hover {
|
||||
.stack-card:hover, .project-card:hover, .theme-card:hover, .methode-item:hover {
|
||||
border-color: var(--g300);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
.stack-card-head {
|
||||
.stack-card-head, .project-card-head, .theme-card-head {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px dashed var(--rule);
|
||||
}
|
||||
.stack-card h3 {
|
||||
.stack-card h3, .project-card h3, .theme-card h4, .methode-body h3 {
|
||||
font-family: var(--serif);
|
||||
font-weight: 600;
|
||||
font-size: 19px;
|
||||
color: var(--ink-1);
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
.stack-card-tag {
|
||||
.stack-card-tag, .project-card-tag, .theme-card-tag {
|
||||
font-family: var(--mono);
|
||||
font-size: 11px;
|
||||
color: var(--g500);
|
||||
letter-spacing: 0.1em;
|
||||
}
|
||||
|
||||
.stack-card {
|
||||
padding: 24px;
|
||||
}
|
||||
.stack-card-head {
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
.stack-card h3 {
|
||||
font-size: 19px;
|
||||
}
|
||||
.pills {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@@ -641,40 +651,19 @@
|
||||
}
|
||||
@media (min-width: 768px) { .projects-grid { grid-template-columns: repeat(2, 1fr); } }
|
||||
.project-card {
|
||||
background: var(--page);
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: var(--r-md);
|
||||
padding: 24px;
|
||||
transition: border-color .25s ease, transform .25s ease, box-shadow .25s ease;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.project-card:hover {
|
||||
border-color: var(--g300);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
.project-card-head {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px dashed var(--rule);
|
||||
}
|
||||
.project-card h3 {
|
||||
font-family: var(--serif);
|
||||
font-weight: 600;
|
||||
font-size: 20px;
|
||||
color: var(--ink-1);
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
.project-card-tag {
|
||||
font-family: var(--mono);
|
||||
font-size: 11px;
|
||||
color: var(--g500);
|
||||
letter-spacing: 0.1em;
|
||||
flex-shrink: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -732,41 +721,20 @@
|
||||
@media (min-width: 1200px) { .formation-themes { grid-template-columns: repeat(3, 1fr); } }
|
||||
|
||||
.theme-card {
|
||||
background: var(--page);
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: var(--r-md);
|
||||
padding: 22px;
|
||||
transition: border-color .25s ease, transform .25s ease, box-shadow .25s ease;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.theme-card:hover {
|
||||
border-color: var(--g300);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
.theme-card-head {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px dashed var(--rule);
|
||||
}
|
||||
.theme-card h4 {
|
||||
font-family: var(--serif);
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
color: var(--ink-1);
|
||||
letter-spacing: -0.01em;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.theme-card-tag {
|
||||
font-family: var(--mono);
|
||||
font-size: 11px;
|
||||
color: var(--g500);
|
||||
letter-spacing: 0.1em;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.theme-quote {
|
||||
@@ -861,16 +829,7 @@
|
||||
grid-template-columns: 56px 1fr;
|
||||
gap: 20px;
|
||||
align-items: start;
|
||||
background: var(--page);
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: var(--r-md);
|
||||
padding: 22px 24px;
|
||||
transition: border-color .25s ease, transform .25s ease, box-shadow .25s ease;
|
||||
}
|
||||
.methode-item:hover {
|
||||
border-color: var(--g300);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
.methode-num {
|
||||
font-family: var(--mono);
|
||||
@@ -882,11 +841,7 @@
|
||||
padding-top: 4px;
|
||||
}
|
||||
.methode-body h3 {
|
||||
font-family: var(--serif);
|
||||
font-weight: 600;
|
||||
font-size: 19px;
|
||||
color: var(--ink-1);
|
||||
letter-spacing: -0.01em;
|
||||
margin-bottom: 6px;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
@@ -8,5 +8,8 @@ add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||
add_header Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=()" always;
|
||||
# CSP: inline CSS + JS are allowed (project convention), fonts from Google.
|
||||
add_header Content-Security-Policy "default-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; script-src 'self' 'unsafe-inline'; img-src 'self' data:; base-uri 'self'; form-action 'self'; frame-ancestors 'self'" always;
|
||||
# CSP: inline CSS allowed (style attributes in the CV + single-file convention);
|
||||
# the inline script is HASH-pinned (no script unsafe-inline). INVARIANT: after
|
||||
# ANY edit to index.html's inline <script>, recompute the hash (command in
|
||||
# CLAUDE.md) and update it here — a stale hash silently disables the JS.
|
||||
add_header Content-Security-Policy "default-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; script-src 'self' 'sha256-Al1M34KxI6Ye5Viu6aO//7CYyaLzqtpG9GX95FFlSOY='; img-src 'self' data:; base-uri 'self'; form-action 'self'; frame-ancestors 'self'" always;
|
||||
|
||||
Reference in New Issue
Block a user