Aller au contenu

Visual Design System — Decision Record

Date: 2026-03-27 Status: Locked Depends on: Content editing decision (content_editing.md) Blocks: B3 (design system), B4 (page templates), A1 (ContentBlock types)


1. Research Basis

Sites Analyzed (22 sites across 5 tiers)

Tier 1 — Best-in-class reference (custom builds, 8.5-9.5/10): - elone-clinic.com — Warm taupe #C5A37F, Raleway, patient-centric nav, extreme minimalism - selarl-gadcabinetdentaire.chirurgiens-dentistes.fr — Dark theme, glassmorphism, video hero, floating pill nav - dentistes-bastia-amurza.fr (Mediweb Premium) — Fahkwang + Montserrat, muted gold #a09d7d, 64.45% container

Tier 2 — Strong design (7-8/10): - oralpes.com — 4-font system, teal + orange, dark teal text #094251, square buttons - stelladentalsuite.co.uk — Jost + Cabin, warm beige #e8d4c6, sharp-cornered buttons, fixed mobile dock - ardentis.ch — NPS score display, price transparency, 17-clinic network

Tier 3 — Award-winning international (researched via web): - Grand Street Dental (NYC) — Serif/sans pairing, teal + yellow, editorial feel - Zen Dental Studio (SF) — Charcoal + sand + gold at partial opacity, Tailwind-based - Dentologie (Atlanta) — Playfair Display + Inter, HSL warm cream, no pure white anywhere - Beehive Dental (Canada) — Poppins, warm tan #FFCFAE, full-screen video hero, 0px radius buttons - Smile Lounge (Brooklyn) — Moneta serif + Montserrat, earth tones only

Tier 4 — French dental standard (5-6/10): - dr-addi-laurence, docteurs-nakkache-soyer, dr-balazuc-armbruster-marine - Mediweb standard sites (Lumiere, Victoire, Archident, Rosalba, Mourlaas) - dentelia.fr, minttandartsen.be

Tier 5 — Low-end baseline (2-3/10): - thomas-fortin.fr (SoLocal), Denti.site sites

Design Systems Researched

  • shadcn/ui (CSS variable theming with OKLCH)
  • Radix Themes (12-step functional color scale)
  • Open Props (spacing/sizing scales)
  • Tailwind CSS v4 (@theme inline + CSS custom properties)

Visual Inspection Notes (Chrome, 2026-03-27)

Sites visually inspected in-browser to validate CSS/HTML analysis:

  • Elone Clinic — Confirmed: extreme minimalism, fashion-level photography (couple smiling outdoors, close-up portrait), full-bleed sections, centered text on dark backgrounds. The hero is lifestyle, not clinical. Each service is a full-viewport image tile. No visible nav — just hamburger + search.
  • GAD Cabinet — Confirmed: full dark theme, video hero of modern office interior, thin-bordered rounded cards for values ("ECOUTE", "EXCELLENCE"), geometric logo, "RDV" dropdown CTA always visible.
  • Oralpes — Confirmed: teal logo, office interior video hero with warm wood tones, bold orange CTA on peach referral banner, accessibility widget bottom-right.
  • Stella Dental UK — Confirmed: scrolling black promo bar marquee, full-bleed team photo in plant-filled boutique interior (feels like hotel, not clinic), branded merchandise visible, warm beige treatment cards in 2-col grid with "LEARN MORE" outline buttons.
  • Amurza (Mediweb Premium) — Confirmed: muted gold/olive hero background, Fahkwang uppercase headings with wide letter-spacing, outline "PRENDRE RDV" button, phone number prominent.

Critical visual takeaway: Photography quality and style is the single most impactful visual decision — more than any CSS token. The gap between Elone/Stella (lifestyle/boutique photography) and standard sites (stock or generic office photos) is immediately visible and dramatic. Real team photos in warm, well-lit environments make or break the design.

Key Finding

The single biggest differentiator between premium and average dental sites is warmth over clinical. Replacing cold blue-and-white with warm tones (beige, tan, teal, gold) through background tints, serif headings, generous whitespace, and real photography is what separates premium dental web design from template sites.

Dimension Average French Practice Premium International
Color Blue + white, clinical Warm earth tones, curated
Typography Single sans-serif Serif/sans pairing, fluid sizing
Hero Small banner ~400px Full-viewport video/slideshow
Whitespace Cramped, dense sections Generous (10-15vh section padding)
Photography Stock photos Custom, 20+ original images
CTAs Blue button, one style Accent-colored, dual CTA, micro-interactions
Navigation 8+ items, dropdowns 4-6 items, prominent booking CTA
Text color Pure black #000 Dark teal/charcoal #094251 or #1a1b18
Backgrounds Pure white #FFF Warm off-white #FAFAF8, alternating tinted sections

2. Typography

Font Pairing: DM Serif Display + DM Sans

Role Font Weight(s) Fallback
Headings (h1-h4) DM Serif Display 400 (regular only) Georgia, serif
Body, UI, nav DM Sans 400, 500, 600 system-ui, sans-serif

Why this pairing: - DM Serif Display — High-contrast serif with elegant curves. Designed for display/heading use. Signals authority and sophistication without being overly formal. Free (Google Fonts). Full Latin Extended support for French (e, e, e, c, oe, etc.). The serif heading is the single strongest "premium" signal found across all award-winning dental sites (Dentologie, Grand Street, Smile Lounge, Beehive all use serif headings). - DM Sans — Same design family as DM Serif Display (both from Colophon Foundry), guaranteeing automatic visual harmony. Low-contrast geometric sans-serif optimized for small-size legibility. Variable font (reduces HTTP requests). Full Latin Extended support for French. More distinctive than Inter (which is ubiquitous in AI-generated and template designs) while being equally readable.

Alternatives per practice personality (if a practice wants a different feel):

Personality Heading Body
Warm family practice Lora Nunito Sans
Modern editorial DM Serif Display Instrument Sans
Clean geometric Plus Jakarta Sans Satoshi
Clinical premium Outfit General Sans

Type Scale (Minor Third ratio, 1.2)

Base: 16px (1rem). Line-height: 1.625 body, 1.2 headings.

Token Size rem Use
text-xs 11px 0.694rem Fine print, legal, copyright
text-sm 13px 0.833rem Captions, meta, breadcrumbs
text-base 16px 1rem Body text
text-lg 19px 1.2rem Lead paragraphs, card descriptions
text-xl 23px 1.44rem H4, subheadings
text-2xl 28px 1.728rem H3
text-3xl 33px 2.074rem H2
text-4xl 40px 2.488rem H1
text-5xl 48px 2.986rem Hero display

Fluid hero sizing for mobile adaptation:

--text-hero: clamp(2.074rem, 1.5rem + 2.5vw, 2.986rem);

Font weights: 400 (body), 500 (emphasis/nav), 600 (subheadings/buttons). Headings use DM Serif Display at 400 (its only weight — the serif contrast provides visual weight naturally). DM Sans supports the full 100-1000 variable weight range.

Letter-spacing: 0.01em on body (slight), 0.03em on uppercase nav items, -0.02em on large headings (tightened).


3. Color Token System

Architecture: OKLCH + CSS Custom Properties + Tailwind v4

Why OKLCH: Rotating the hue channel produces visually consistent results across all 9 practice themes. The lightness channel directly correlates to perceived brightness, so WCAG contrast ratios remain predictable when only hue changes. This is the format used by shadcn/ui and recommended for Tailwind v4.

Why CSS custom properties: Per-practice theming via a class on <html> (e.g., class="theme-aubagne"). Each practice overrides 2-3 variables (primarily hue rotation). No build-time theme generation needed.

Semantic Token Set

Following the shadcn/ui pattern, adapted for dental/medical context:

:root {
  /* === Surface / Text pairs === */
  --background:            oklch(0.995 0.003 80);    /* warm off-white, never pure white */
  --foreground:            oklch(0.20  0.02  230);   /* dark blue-gray, never pure black */

  --card:                  oklch(1.0   0     0);     /* white cards on off-white bg */
  --card-foreground:       oklch(0.20  0.02  230);

  --muted:                 oklch(0.96  0.008 80);    /* warm light bg for alternating sections */
  --muted-foreground:      oklch(0.45  0.02  230);   /* secondary text */

  /* === Brand colors (per-practice customizable) === */
  --primary:               oklch(0.55  0.12  195);   /* default: warm teal */
  --primary-foreground:    oklch(0.98  0     0);     /* white on primary */

  --secondary:             oklch(0.92  0.02  195);   /* tinted light surface */
  --secondary-foreground:  oklch(0.30  0.03  195);

  --accent:                oklch(0.70  0.14  70);    /* warm amber for CTAs (backgrounds/buttons only) */
  --accent-foreground:     oklch(0.15  0.02  70);    /* dark on accent */
  --accent-text:           oklch(0.50  0.14  70);    /* amber for inline text/links on white — passes WCAG AA */

  /* === Functional === */
  --destructive:           oklch(0.55  0.20  25);    /* errors, warnings */
  --destructive-foreground: oklch(0.98 0     0);

  --border:                oklch(0.91  0.005 80);    /* warm subtle borders */
  --input:                 oklch(0.91  0.005 80);    /* form input borders */
  --ring:                  oklch(0.55  0.12  195);   /* focus ring = primary */

  /* === Radius === */
  --radius:                0.625rem;                  /* 10px base — modern, not childish */

  /* === Transitions === */
  --transition-base:       0.3s ease;
}

Per-Practice Theming

Each practice only overrides the hue channel (and optionally chroma) of --primary and --accent. Lightness stays constant to preserve contrast ratios.

/* Aubagne — warm teal (default) */
.theme-aubagne {
  --primary:             oklch(0.55  0.12  195);
  --accent:              oklch(0.70  0.14  70);
}

/* Le Canet — deeper blue */
.theme-le-canet {
  --primary:             oklch(0.55  0.14  235);
  --accent:              oklch(0.70  0.14  55);
}

/* Bodin Boulogne — sage/olive (premium feel) */
.theme-bodin {
  --primary:             oklch(0.55  0.10  145);
  --accent:              oklch(0.70  0.12  80);
}

/* David Simon Thiais — warm taupe (Elone-inspired) */
.theme-david-simon {
  --primary:             oklch(0.55  0.08  60);
  --accent:              oklch(0.70  0.14  35);
}

Rule: Each practice gets a primary hue (from SiteConfig in Aletheia) + an auto-derived accent hue (complementary or split-complementary). The --secondary, --muted, and --border tokens are derived from --primary's hue by adjusting lightness and chroma. This means a single hue value in SiteConfig generates the entire practice palette.

Tailwind v4 Integration

@theme inline {
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --color-card: var(--card);
  --color-card-foreground: var(--card-foreground);
  --color-primary: var(--primary);
  --color-primary-foreground: var(--primary-foreground);
  --color-secondary: var(--secondary);
  --color-secondary-foreground: var(--secondary-foreground);
  --color-muted: var(--muted);
  --color-muted-foreground: var(--muted-foreground);
  --color-accent: var(--accent);
  --color-accent-foreground: var(--accent-foreground);
  --color-accent-text: var(--accent-text);
  --color-destructive: var(--destructive);
  --color-border: var(--border);
  --color-input: var(--input);
  --color-ring: var(--ring);
  --radius-sm: calc(var(--radius) * 0.6);
  --radius-md: var(--radius);
  --radius-lg: calc(var(--radius) * 1.6);
  --radius-xl: calc(var(--radius) * 2.4);
}

Usage in components: bg-primary text-primary-foreground, bg-muted, border-border, rounded-md.

Contrast Compliance

With --foreground at lightness 0.20 on --background at 0.995: contrast ratio > 12:1 (exceeds WCAG AAA). With --primary at lightness 0.55 on white: contrast ratio ~5:1 (passes WCAG AA for large text, borderline for small text — use --foreground for small body text, --primary for headings and interactive elements). With --accent at lightness 0.70 on white: fails for text — use only for backgrounds/buttons with --accent-foreground (lightness 0.15) on top. With --accent-text at lightness 0.50 on white: contrast ratio ~5.5:1 (passes WCAG AA). Use for inline accent-colored text, links, and small interactive labels.


4. Spacing & Sizing Scale

Base Grid: 4px (0.25rem)

Aligns with Tailwind's default spacing scale. All spacing values are multiples of 4px.

Token Value px Primary use
space-0.5 0.125rem 2px Hairline gaps, icon padding
space-1 0.25rem 4px Tight inline gaps
space-1.5 0.375rem 6px Small internal padding
space-2 0.5rem 8px Compact element spacing, badge padding
space-3 0.75rem 12px Button horizontal padding, input padding
space-4 1rem 16px Standard gap between elements
space-5 1.25rem 20px Card internal padding (mobile)
space-6 1.5rem 24px Card internal padding (desktop)
space-8 2rem 32px Between component groups
space-10 2.5rem 40px Section title to content
space-12 3rem 48px Between sections (mobile)
space-16 4rem 64px Between sections (desktop)
space-20 5rem 80px Major section breaks
space-24 6rem 96px Page-level vertical rhythm

Section Padding (Viewport-Relative)

Premium dental sites use viewport-relative padding for proportional scaling. Inspired by GAD Cabinet's 2vw-11vw approach:

--section-padding-y: clamp(3rem, 8vh, 6rem);   /* vertical: 48px → 96px */
--section-padding-x: clamp(1rem, 5vw, 4rem);   /* horizontal: 16px → 64px */

Container

--container-max: 1280px;          /* card grids, multi-column layouts */
--container-content: 900px;       /* body text sections — optimal 60-75 chars/line at 16px DM Sans */
--container-narrow: 768px;        /* blog posts, legal pages */
--container-wide: 1440px;         /* full-bleed sections with internal container */

Border Radius Scale

Base: --radius: 0.625rem (10px). Derived:

Token Value Use
rounded-sm 6px Small chips, badges
rounded-md 10px Buttons, inputs, cards
rounded-lg 16px Large cards, modals
rounded-xl 24px Image containers, hero overlays

Not using: 9999px pill radius (marks site as template/WordPress per research). Not using 0px sharp corners (requires extremely strong photography to not look unfinished).


5. Component Visual Language

5.1 Navigation

Pattern: Sticky header, minimal items, prominent booking CTA.

[Logo]  Le Cabinet  L'Equipe  Votre Besoin v  Nos Soins v  Resultats  Contact  [Prendre RDV]
  • Desktop: Fixed top, height: 70px. White background with backdrop-filter: blur(12px) on scroll (frosted glass, as seen in GAD, Dentologie, Amurza). box-shadow: 0 1px 0 var(--border) at rest, subtle shadow on scroll.
  • Mobile (< 768px): Hamburger menu. Never hide the booking CTA (anti-pattern from Balazuc). Sticky bottom bar: [Appeler] [Prendre RDV] — inspired by Stella Dental's fixed mobile dock.
  • Items: 6 nav items max + CTA button. Dropdown on "Votre Besoin" and "Nos Soins".
  • Typography: DM Sans 500, 14px, uppercase with letter-spacing: 0.03em.
  • CTA button: bg-accent text-accent-foreground rounded-md, padding 10px 20px.

5.2 Hero

Pattern: Full-viewport with high-quality image or video + overlay text + dual CTA.

  • Height: min-height: 80vh (desktop), min-height: 60vh (mobile). Not 100vh (avoids content being completely hidden below fold).
  • Image hero: WebP, object-fit: cover, object-position: center. Responsive srcset with 3 breakpoints.
  • Video hero: When video_url is provided, renders as background <video> instead of <img>:
  • autoplay muted loop playsinline — plays silently on load like GAD, Amurza, Oralpes.
  • poster attribute = the image field (required even when video is set — serves as fallback + LQIP while video loads).
  • object-fit: cover on the <video> element, same sizing as image hero.
  • Mobile: serve video only on viewports >= 768px. Below 768px, show the poster image instead (saves bandwidth, avoids battery drain). Use a <picture>/<source media> pattern or JS check.
  • prefers-reduced-motion: if user prefers reduced motion, show poster image instead of video.
  • Format: MP4 (H.264) as primary, WebM (VP9) as progressive enhancement <source>. Max file size: 8MB (aim for 15-30s loop at 1080p). Served via Cloudflare CDN.
  • No sound toggle: unlike Amurza's approach, our videos are always muted (simpler, no consent issue). Sound-enabled videos use the video ContentBlock type instead.
  • Overlay: linear-gradient(to right, oklch(0.15 0.02 230 / 0.6), transparent 70%) — dark on left for text readability. Same overlay for both image and video heroes.
  • Content: Left-aligned. H1 in DM Serif Display at text-5xl (hero size). Tagline in DM Sans at text-lg. Two CTAs:
  • Primary: "Prendre rendez-vous" — bg-accent text-accent-foreground (warm amber)
  • Secondary: "01 XX XX XX XX" — border-primary-foreground bg-transparent text-primary-foreground (outline white)
  • Animation: Content fades in with 0.6s stagger (heading, tagline, CTAs). Respect prefers-reduced-motion.

5.3 Cards

Three card variants used across the platform:

Service Card (homepage + service hub):

[Image 16:10 ratio, rounded-xl top corners]
[Category tag — text-sm, text-primary, uppercase]
[Title — DM Serif Display, text-xl]
[Excerpt — DM Sans 400, text-base, text-muted-foreground, 2-line clamp]
[Arrow icon →]
- bg-card border border-border rounded-lg. Hover: shadow-md translate-y-[-2px] with transition: var(--transition-base).

Team Card (team page):

[Photo 3:4 ratio, rounded-lg, object-fit cover]
[Name — DM Serif Display, text-lg]
[Specialty — DM Sans 400, text-sm, text-primary]
[CTA link — "Prendre RDV" text-sm]
- No border, no background. Clean and open.

Stat Card (trust bar):

[Number — DM Serif Display, text-4xl, text-primary]
[Label — DM Sans 500, text-sm, text-muted-foreground, uppercase]
- Inline flex, centered. Used in trust/stats bar section.

5.4 Buttons

Two variants + sizes:

Primary (main CTAs): bg-accent text-accent-foreground rounded-md. Hover: brightness(1.05), translateY(-1px), shadow-sm. Secondary (supporting actions): border border-primary text-primary bg-transparent rounded-md. Hover: bg-primary text-primary-foreground. Ghost (tertiary, in-page links): text-primary underline-offset-4 hover:underline.

Sizes: - sm: h-9 px-3 text-sm (nav, inline) - md: h-11 px-5 text-base (default) - lg: h-13 px-8 text-lg (hero, standalone CTAs)

Font: DM Sans 600, no uppercase on buttons (uppercase reserved for nav/labels).

5.5 Section Rhythm

Pages alternate between white and muted backgrounds to create visual rhythm:

[Hero — full-width image/video]
[Section A — bg-background (warm off-white)]
[Section B — bg-muted (warm tinted)]
[Section A — bg-background]
[Section B — bg-muted]
[CTA Section — bg-primary text-primary-foreground]
[Footer — bg-foreground text-background (dark)]

Each section: padding: var(--section-padding-y) var(--section-padding-x). Content constrained to --container-max.

4-column layout:

Col 1: Logo + practice description (2-3 lines) + social icons
Col 2: Navigation links (Le Cabinet, L'Equipe, Nos Soins...)
Col 3: Horaires (table: Lun-Ven + Samedi)
Col 4: Contact (address, phone, email) + mini map
---
Bottom bar: (c) 2026 [Practice Name] | Mentions legales | Politique de confidentialite | Accessibilite
  • bg-foreground text-background (dark footer). Links: text-muted-foreground hover:text-background.
  • Social icons: 24px, text-muted-foreground hover:text-accent.

5.7 Breadcrumbs

On all inner pages. text-sm, text-muted-foreground. Arrow separator >. Current page in text-foreground (not linked). Schema.org BreadcrumbList markup.

Accueil > Nos Soins > Implantologie Aubagne > Chirurgie guidee

Placement: Above the hero, below the nav. Sits inside a bg-background strip with padding: space-3 0 between the sticky nav and the hero/content area. Not on the homepage (root page has no breadcrumb). On service detail (L2) pages with compact hero (40vh), the breadcrumb appears between the nav and the hero image.

5.8 FAQ Accordion

  • Question: DM Sans 600, text-lg. Chevron icon rotates on open.
  • Answer: DM Sans 400, text-base, text-muted-foreground. Max-width: --container-narrow.
  • Border between items: border-b border-border.
  • Schema.org FAQPage markup on every FAQ block.
  • Animation: smooth height transition, respect prefers-reduced-motion.

5.9 Before/After

Image comparison slider component: - Two images side by side with draggable divider. - Labels: "Avant" / "Apres" in text-sm badges. - rounded-lg overflow-hidden. - Caption below: treatment type + link to service page (cross-linking).

5.10 CTA Block

Full-width call-to-action section: - bg-primary text-primary-foreground, padding: var(--section-padding-y). - Centered heading (DM Serif Display, text-3xl): "Prenez rendez-vous" - Subtext: practice phone + opening hours summary. - Two buttons (Primary amber + Secondary outline white). - Appears at the bottom of every content page.


6. ContentBlock Types

These are the block_type values for the ContentBlock model's JSONField in Aletheia. Each type has a defined JSON schema for its content field.

block_type Description JSON content fields
hero Full-width hero with image/video + heading + CTAs heading, tagline, image, video_url, cta_primary_text, cta_primary_url, cta_secondary_text, cta_secondary_url, overlay_position
text Markdown body text body (Markdown string)
text_media 50/50 text + image/video block heading, body, media_file, media_position (left/right), media_type (image/video)
cards_grid Grid of cards (services, features) heading, cards[] (each: title, excerpt, image, url, icon)
cta Call-to-action banner heading, subtext, cta_primary_text, cta_primary_url, cta_secondary_text, cta_secondary_url, style (primary/accent)
faq Accordion FAQ with schema.org heading, items[] (each: question, answer)
testimonials Patient testimonial carousel heading, testimonials[] (each: quote, author, rating, treatment_type)
before_after Before/after image comparison heading, cases[] (each: before_image, after_image, caption, treatment_url)
stats Trust metrics bar stats[] (each: value, label, icon)
team_grid Practitioner cards heading, subtext, max_display (int), show_cta (bool) — data from Aletheia Dentist model, not duplicated
gallery Image gallery with lightbox heading, images[] (each: image, caption, category)
map Embedded map + practice info heading, show_hours (bool), show_parking (bool) — data from Aletheia Practice model
related_services "Voir aussi" cross-links heading, services[] (each: title, url, icon) — or auto-generated from Page.related_services
video Embedded video player heading, video_url, poster_image, caption
quote Pull quote / highlight quote, author, role

Validation: Each block_type has a JSON Schema stored in the codebase (not in DB). Aletheia validates content against the schema on save. Invalid content blocks cannot be published.


7. Homepage Specification

Section Order (default template)

1. [Hero]              — block_type: hero
                         Full-viewport, practice team photo or video
                         H1: "[Votre cabinet dentaire a] [Ville]"
                         Dual CTA: "Prendre rendez-vous" + phone number

2. [Votre Besoin]      — block_type: cards_grid
                         "Comment pouvons-nous vous aider ?"
                         5 patient-need cards (icons + titles)
                         Links to /votre-besoin/ pages

3. [Team Preview]      — block_type: team_grid
                         "Notre equipe"
                         3-4 practitioner cards (photo + name + specialty + CTA)
                         "Voir toute l'equipe" link

4. [Services]          — block_type: cards_grid
                         "Nos soins"
                         3-4 service category cards (Implantologie, Esthetique, Parodontologie...)
                         Image + title + excerpt

5. [Trust Bar]         — block_type: stats
                         4 metrics inline:
                         "[X] ans d'experience" | "[X] praticiens" | "[X] patients/an" | "NPS [X]"
                         bg-muted section

6. [Before/After]      — block_type: before_after (optional)
                         "Resultats"
                         2-3 case previews + "Voir tous nos cas cliniques"

7. [Testimonials]      — block_type: testimonials (optional)
                         "Ils nous font confiance"
                         3-card carousel with ratings

8. [CTA]               — block_type: cta
                         bg-primary full-width
                         "Prenez rendez-vous" + phone + hours summary

9. [Map]               — block_type: map
                         Embedded map + address + hours + parking info

Visual Rhythm

hero         → full-bleed image/video
votre besoin → bg-background (off-white)
team         → bg-muted (warm tinted)
services     → bg-background
trust bar    → bg-muted
before/after → bg-background
testimonials → bg-muted
cta          → bg-primary (brand color, full-bleed)
map          → bg-background
footer       → bg-foreground (dark)

8. Service Page Specifications

Service Hub (L1) — e.g., /implant-dentaire-aubagne/

1. [Breadcrumb]        Accueil > Implantologie Aubagne

2. [Hero]              block_type: hero
                       H1: "Implantologie a [Ville]"
                       Hero image (treatment-specific)
                       No CTA in hero (too early for service pages)

3. [Introduction]      block_type: text
                       200-word intro. Lead paragraph in text-lg.

4. [Sub-services]      block_type: cards_grid
                       "Nos traitements en implantologie"
                       Grid of clickable cards (Remplacer 1 dent, All-on-4, Chirurgie guidee...)

5. [Trust/Why Us]      block_type: text_media
                       "Pourquoi nous choisir"
                       50/50: text + cabinet/equipment photo

6. [FAQ]               block_type: faq
                       2-3 most common questions
                       Schema.org FAQPage

7. [CTA]               block_type: cta
                       "Prendre rendez-vous" + phone + Doctolib

Service Detail (L2) — e.g., /implant-dentaire-aubagne/chirurgie-guidee/

1. [Breadcrumb]        Accueil > Implantologie Aubagne > Chirurgie guidee

2. [Hero]              block_type: hero (compact — 40vh, not full-screen)
                       H1: "Chirurgie guidee a [Ville]"

3. [Content]           block_type: text
                       400-600 words. Treatment description, procedure, benefits.
                       Includes inline images via Markdown.

4. [Before/After]      block_type: before_after
                       Linked cases for this specific treatment

5. [Related]           block_type: related_services
                       "Voir aussi" — 2-3 related treatments
                       (e.g., Chirurgie guidee → Mise en charge immediate, All-on-4)

6. [FAQ]               block_type: faq
                       Treatment-specific FAQ (schema.org)

7. [CTA]               block_type: cta
                       "Prendre rendez-vous pour une chirurgie guidee"

9. Accessibility (RGAA 4.1.2 / WCAG 2.1 AA)

Compliance Target

WCAG 2.1 AA (baseline requirement at launch). RGAA 4.1.2 (aspirational, progressive post-launch compliance). RGAA exceeds legal requirements for most individual practices (<10 employees) but: - The platform serves 9+ practices — cumulative scope matters - Accessibility is a competitive advantage (0/60 benchmarked sites are RGAA-compliant) - Required deliverables (RGAA): accessibility declaration in footer, compliance percentage, feedback mechanism

Key Technical Requirements Built Into Design System

Requirement Implementation
Color contrast All text/bg pairs designed for > 4.5:1 (AA). --foreground on --background > 12:1. Primary used only for large text and interactive elements.
Focus indicators outline: 2px solid var(--ring); outline-offset: 2px on all interactive elements. Never outline: none.
Touch targets Minimum 48x48px on mobile (buttons, links, nav items).
Keyboard navigation All interactive components operable via Tab, Enter, Space, Escape, Arrow keys.
Skip link "Aller au contenu principal" as first focusable element, hidden until focused.
Landmarks <header>, <nav>, <main>, <footer>, <aside> on every page.
Heading hierarchy Single <h1> per page. Sequential h1-h6, no skipping levels.
Language <html lang="fr">. lang attribute on any EN/JP content blocks.
Images Alt text on all informative images. alt="" on decorative images.
Forms Explicit <label> elements. <fieldset>/<legend> for groups. Linked error messages. Required field indicators.
Motion Respect prefers-reduced-motion: disable fade-in animations, parallax, auto-play carousels.
Links Descriptive text (no "cliquez ici"). Visually distinct from body text (underline or color + underline).

10. Image Strategy

Formats & Sizes

Context Format Sizes (srcset) Aspect Ratio
Hero WebP (AVIF if supported) 640w, 1280w, 1920w 16:9 (desktop), 4:3 (mobile crop)
Service card WebP 400w, 800w 16:10
Team portrait WebP 300w, 600w 3:4
Before/after WebP 400w, 800w 4:3
Gallery WebP 400w, 800w, 1200w Varied (preserve original)
Logo SVG n/a n/a
Icons SVG (inline) n/a 1:1

Processing Pipeline (Aletheia — django-imagekit)

Upload original → auto-generate: 1. WebP at 3 responsive sizes 2. AVIF at 3 responsive sizes (bonus, if Pillow supports) 3. Blur placeholder (20px wide, base64-inlined for LQIP)

All images served via Cloudflare CDN with Cache-Control: public, max-age=31536000, immutable.

Video Strategy

Videos are supported in hero, text_media, and video block types.

Context Behavior Format Max size
Hero background Autoplay, muted, loop, no controls MP4 (H.264) + WebM (VP9) 8MB, 15-30s loop, 1080p
text_media inline Autoplay muted loop (same as hero) when in view MP4 + WebM 8MB
video standalone Play on click, with controls, poster image MP4 + WebM 50MB (longer format, e.g., practice tour)

Storage: Videos uploaded to Aletheia MediaFile model with media_type=video. Stored on local disk (same as images). Served via Cloudflare CDN.

No server-side transcoding at v1. Practices upload MP4 directly. Aletheia validates file size and format on upload. Future: add ffmpeg transcoding to Celery pipeline if needed.

Poster images: Required for all videos. Auto-extracted from first frame if not provided (future Celery task), or manually uploaded.

Photography Direction

Based on research, premium dental sites use: - Real team and office photography — never stock. This is the single most impactful visual investment. - Lifestyle over clinical — patients smiling, warm lighting, modern interiors. Not close-ups of dental instruments. - Consistent color grading — warm tones, slightly desaturated, matching the site palette. - Minimum per practice: 5 team portraits, 3 office interior shots, 1 hero image (team group shot or office exterior).


11. Animation & Interaction

Principles

  • Subtle, purposeful, never gratuitous.
  • All animations respect prefers-reduced-motion.
  • Default transition: var(--transition-base) = 0.3s ease.

Specific Animations

Element Animation Duration
Page sections Fade-in-up on scroll (AOS-style) 0.6s, staggered 100ms
Hero content Sequential fade-in (heading → tagline → CTAs) 0.6s, stagger 200ms
Cards hover translateY(-2px) + shadow-md 0.3s
Buttons hover translateY(-1px) + brightness(1.05) 0.2s
Nav on scroll Background opacity 0 → 1 + shadow 0.3s
FAQ accordion Smooth height transition 0.3s
Mobile menu Slide-in from right 0.3s
Breadcrumb separator None (static)

What We Don't Do

  • No parallax scrolling (performance cost, accessibility issue, feels dated)
  • No auto-playing video with sound (consent required)
  • No infinite scroll (bad for SEO, confusing for patients)
  • No decorative SVG blob animations (GAD does this well, but complexity/maintenance cost is not worth it for multi-tenant)

12. Empty States

Optional homepage sections (before/after, testimonials) and optional page blocks must degrade gracefully when a practice has no content for them.

Rule: Hide, Don't Show Empty

If a ContentBlock section has no data (e.g., zero testimonials, zero case studies), the section is not rendered at all — no heading, no empty container, no "coming soon" placeholder. The visual rhythm adjusts automatically (next section takes over the background alternation).

Implementation

// Pseudo-logic in Next.js page template
{testimonials.length > 0 && <TestimonialsSection data={testimonials} />}
{cases.length > 0 && <BeforeAfterSection data={cases} />}

Per-Block Minimums

Block type Minimum to render Behavior below minimum
testimonials 1 testimonial Hide entire section
before_after 1 case Hide entire section
team_grid 1 practitioner Hide entire section
stats 2 stats Hide entire section (1 stat alone looks odd)
gallery 3 images Hide entire section (1-2 images look incomplete)
cards_grid 1 card Hide entire section
faq 1 item Hide entire section
related_services 1 service Hide entire section

Homepage Guaranteed Sections

These sections must always have content (enforced by SiteConfig validation in Aletheia):

  1. Hero — at minimum: H1 + practice name (no image = solid bg-primary with white text)
  2. Services — at minimum: the practice's enabled service categories
  3. CTA — always rendered (phone number from Practice model, always available)
  4. Map — always rendered (address from Practice model, always available)

Hero Fallback

If a practice has no hero image uploaded yet: - Desktop: bg-primary solid background with text-primary-foreground. H1 + tagline + dual CTA centered. - No gradient overlay (no image to overlay on). - This ensures a practice can go live before professional photography is done.


13. Error Pages

404 — Page Not Found

Simple, on-brand, helpful. No illustration or gimmick.

[Nav — standard sticky header]

[Centered content, max-width: --container-content]
  H1 (DM Serif Display, text-4xl): "Page introuvable"
  Body (DM Sans 400, text-lg, text-muted-foreground):
    "La page que vous cherchez n'existe pas ou a ete deplacee."

  [Two buttons, centered, gap-4]
    Primary: "Retour a l'accueil" → /
    Secondary: "Nous contacter" → /contact/

[Footer — standard]
  • Background: bg-background (warm off-white). No muted/alternating sections.
  • Centered vertically with min-height: 60vh on the content area.
  • Breadcrumb: hidden (no meaningful path to show).
  • The 404 page uses the practice's theme (resolved from the Host header, same as any other page).

500 — Server Error

Minimal static page (no API calls, no dynamic content — it must render even when the backend is down):

[Minimal header — logo only, no nav links]

[Centered content]
  H1: "Une erreur est survenue"
  Body: "Nous travaillons a resoudre le probleme. Veuillez reessayer dans quelques instants."

  [Single button]
    Primary: "Recharger la page" → javascript:location.reload()

  [Phone number — hardcoded fallback from build-time env]
    "Besoin d'aide ? Appelez le [phone]"
  • Must be a static HTML file (not server-rendered) so it works when Next.js itself is down.
  • Uses inline styles (not Tailwind classes) to avoid CSS loading dependency.
  • Practice phone number injected at build time from environment variables.

Summary of Locked Decisions

Decision Choice Rationale
Heading font DM Serif Display Serif = premium signal. Free, full French support.
Body font DM Sans Same family as DM Serif Display, distinctive, variable font, full French support.
Type scale Minor Third (1.2), 16px base Balanced for medical content (lots of body text).
Color system OKLCH + CSS custom properties Per-practice theming via hue rotation. Predictable contrast.
Default palette Warm teal primary + amber accent Warm over clinical. Teal = trust + health. Amber CTAs = visual pop.
Backgrounds Warm off-white, never pure white Premium feel (Dentologie, Zen Dental, all Tier 1 sites).
Text color Dark blue-gray, never pure black Softer, more refined (Oralpes, Amurza, all premium sites).
Border radius 10px base Modern without being childish (9999px) or harsh (0px).
Spacing 4px grid, Tailwind-aligned Industry standard, framework-native.
Section padding Viewport-relative clamp() Proportional scaling (GAD, premium international pattern).
Container 1280px max + 900px content 1280px for grids, 900px for body text (optimal line length).
Nav Sticky, 6 items max, frosted glass on scroll Minimal, premium, always-accessible booking CTA.
Hero 80vh, image/video, dual CTA Full-viewport impact without hiding all content.
Buttons Accent-colored primary, outline secondary Warm CTA color (not primary brand color) for conversion.
Cards 3 variants (service, team, stat) Covers all template needs with consistent language.
ContentBlock types 16 types Covers all page templates. JSON-validated in Aletheia.
Accessibility RGAA 4.1.2 full compliance Competitive advantage + legal compliance.
Images WebP + AVIF, 3 sizes, LQIP blur placeholder Performance (LCP < 2.5s target).
Animations Fade-in-up on scroll, hover micro-interactions Subtle, accessible, performant.
Empty states Hide sections with no data, hero fallback to solid bg-primary Graceful degradation, no "coming soon" placeholders.
Error pages 404 on-brand with nav; 500 static HTML with inline styles 404 uses practice theme; 500 works when backend is down.