Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/app/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
<UiToaster />
</template>
148 changes: 120 additions & 28 deletions apps/app/assets/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,119 @@
* Design tokens — source unique de vérité (Tailwind v4 @theme).
* Règle d'équipe : AUCUNE valeur en dur dans les composants.
* On consomme ces tokens via les classes générées : bg-brand-600, text-ink-700,
* rounded-card, font-sans, etc. Le design system détaillé viendra avec l'UX/UI.
* rounded-card, font-sans, shadow-card, etc.
*
* Direction "Chaleur professionnelle" (réfs Resume.io/Teal/Notion) :
* neutres chauds (stone), accent indigo profond, radius généreux, ombres douces.
*
* Exceptions admises à la règle tokens-only (décision revue 2026-06) :
* - `white`/`black` Tailwind : constantes universelles, autorisées sur fonds
* sombres ou brand (text-white sur bg-brand-600, etc.).
* - Modificateurs d'opacité `/N` sur tokens (bg-brand-100/60…) : mécanisme
* standard Tailwind v4 (color-mix), pas une valeur en dur.
* - Valeurs arbitraires ([…]) uniquement quand intrinsèques au composant
* (ex. tailles texte du mini-mockup CvMockup) — jamais pour contourner
* un token existant.
*/
@theme {
/* Couleur de marque (placeholder neutre tant que le branding n'est pas tranché —
le naming/branding final viendra du CMO via THI-121 → ajuster ici uniquement). */
--color-brand-50: #eff6ff;
--color-brand-100: #dbeafe;
--color-brand-400: #60a5fa;
--color-brand-500: #3b82f6;
--color-brand-600: #2563eb;
--color-brand-700: #1d4ed8;

/* Texte & neutres */
--color-ink-100: #f3f4f6;
--color-ink-300: #d1d5db;
--color-ink-500: #6b7280;
--color-ink-700: #374151;
--color-ink-900: #111827;

/* États sémantiques */
--color-success-500: #16a34a;
--color-warning-500: #d97706;
--color-danger-500: #dc2626;
/* Marque — indigo profond (crédibilité) */
--color-brand-50: #eef2ff;
--color-brand-100: #e0e7ff;
--color-brand-200: #c7d2fe;
--color-brand-300: #a5b4fc;
--color-brand-400: #818cf8;
--color-brand-500: #6366f1;
--color-brand-600: #4f46e5;
--color-brand-700: #4338ca;
--color-brand-800: #3730a3;
--color-brand-900: #312e81;
--color-brand-950: #1e1b4b;

/* Texte & neutres — stone (chaleur) */
--color-ink-50: #fafaf9;
--color-ink-100: #f5f5f4;
--color-ink-200: #e7e5e4;
--color-ink-300: #d6d3d1;
--color-ink-400: #a8a29e;
--color-ink-500: #78716c;
--color-ink-600: #57534e;
--color-ink-700: #44403c;
--color-ink-800: #292524;
--color-ink-900: #1c1917;
--color-ink-950: #0c0a09;

/* États sémantiques — échelles fond (50/100), bordure (200), texte/action (600/700) */
--color-success-50: #f0fdf4;
--color-success-100: #dcfce7;
--color-success-200: #bbf7d0;
--color-success-500: #22c55e;
--color-success-600: #16a34a;
--color-success-700: #15803d;

--color-warning-50: #fffbeb;
--color-warning-100: #fef3c7;
--color-warning-200: #fde68a;
--color-warning-500: #f59e0b;
--color-warning-600: #d97706;
--color-warning-700: #b45309;

--color-danger-50: #fef2f2;
--color-danger-100: #fee2e2;
--color-danger-200: #fecaca;
--color-danger-500: #ef4444;
--color-danger-600: #dc2626;
--color-danger-700: #b91c1c;

/* Surfaces & bordures */
--color-surface: #ffffff;
--color-surface-muted: #f9fafb;
--color-border: #e5e7eb;
--color-surface-muted: #fafaf9;
--color-surface-warm: #f5f5f4;
--color-border: #e7e5e4;
--color-border-strong: #d6d3d1;

/* Typographie */
--font-sans: 'Inter', ui-sans-serif, system-ui, -apple-system, sans-serif;
/* Typographie — chargée réellement via @nuxt/fonts (nuxt.config.ts) */
--font-sans:
'Plus Jakarta Sans', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;

/* Rayons nommés */
--radius-card: 0.75rem;
--radius-control: 0.5rem;
/* Rayons nommés — généreux (direction chaleur pro) */
--radius-card: 1rem;
--radius-control: 0.625rem;

/* Ombres multi-couches très douces (style Stripe), teinte stone */
--shadow-xs: 0 1px 2px 0 rgb(28 25 23 / 0.04);
--shadow-sm: 0 1px 2px 0 rgb(28 25 23 / 0.04), 0 1px 3px 0 rgb(28 25 23 / 0.06);
--shadow-card: 0 1px 2px 0 rgb(28 25 23 / 0.04), 0 2px 8px -2px rgb(28 25 23 / 0.08);
--shadow-card-hover:
0 2px 4px -1px rgb(28 25 23 / 0.06), 0 8px 24px -4px rgb(28 25 23 / 0.12);
--shadow-pop:
0 4px 8px -2px rgb(28 25 23 / 0.08), 0 16px 40px -8px rgb(28 25 23 / 0.16);
--shadow-brand: 0 1px 2px 0 rgb(67 56 202 / 0.12), 0 4px 12px -2px rgb(79 70 229 / 0.25);

/* Animations utilitaires (toasts, entrées de sections) */
--animate-toast-in: toast-in 0.3s cubic-bezier(0.16, 1, 0.3, 1);
--animate-fade-up: fade-up 0.5s cubic-bezier(0.16, 1, 0.3, 1) both;

@keyframes toast-in {
from {
opacity: 0;
transform: translateY(0.75rem) scale(0.97);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}

@keyframes fade-up {
from {
opacity: 0;
transform: translateY(0.5rem);
}
to {
opacity: 1;
transform: translateY(0);
}
}
}

html,
Expand All @@ -47,6 +125,20 @@ body,
height: 100%;
}

body {
@apply bg-surface text-ink-800 antialiased;
}

/* Respecte la préférence utilisateur de mouvement réduit. */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}

/* ─── Print / PDF (cible : Chromium headless, PR 3) ─────────────────────────
* Règles appliquées aussi bien au Ctrl+P du navigateur qu'au rendu Playwright.
* Objectif : margins A4, pas d'éléments d'interface, couleurs préservées.
Expand Down
149 changes: 149 additions & 0 deletions apps/app/components/landing/CvMockup.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
<script setup lang="ts">
import { useLanding } from '~/composables/useLanding'

/**
* Visuel hero : pile « avant / après » de deux mini-CV.
* - Derrière : le CV générique (lignes neutres, estompé).
* - Devant : le CV adapté à l'offre, avec les éléments réalignés surlignés
* (bg-brand-100) et un badge « Match 87 % ».
*
* Le contenu du CV est un pseudo-contenu FICTIF purement décoratif
* (aria-hidden) — seuls les libellés (badge, avant/après) viennent de l'i18n.
*/
const { m } = useLanding()

interface MockJob {
role: string
period: string
bullets: { text: string; highlight: boolean }[]
}

const JOBS: readonly MockJob[] = [
{
role: 'Cheffe de projets digitaux · Lumea',
period: '2021 – 2025',
bullets: [
{ text: 'Pilotage de 12 campagnes multicanales (budget 150 k€)', highlight: true },
{ text: '+40 % d’abonnés à la newsletter en un an', highlight: false },
],
},
{
role: 'Chargée de communication · Atelier Verde',
period: '2018 – 2021',
bullets: [{ text: 'Refonte du site vitrine et de la stratégie de contenus', highlight: false }],
},
]

const SKILLS: readonly { label: string; highlight: boolean }[] = [
{ label: 'Gestion de projet', highlight: true },
{ label: 'SEO', highlight: true },
{ label: 'HubSpot', highlight: false },
{ label: 'Analyse de données', highlight: false },
{ label: 'Brief créatif', highlight: false },
]
</script>

<template>
<div class="relative mx-auto w-full max-w-sm" aria-hidden="true">
<!-- CV générique (derrière, estompé) -->
<div
class="absolute inset-0 -translate-x-4 translate-y-4 -rotate-3 rounded-card bg-surface p-5 opacity-80 shadow-card ring-1 ring-border"
>
<span
class="inline-flex rounded-full bg-ink-100 px-2.5 py-0.5 text-[10px] font-semibold uppercase tracking-widest text-ink-500"
>
{{ m.hero.mockup.beforeLabel }}
</span>
<div class="mt-4 flex items-center gap-3">
<div class="h-9 w-9 rounded-full bg-ink-200"></div>
<div class="space-y-1.5">
<div class="h-2.5 w-28 rounded-full bg-ink-200"></div>
<div class="h-2 w-36 rounded-full bg-ink-100"></div>
</div>
</div>
<div class="mt-5 space-y-2">
<div class="h-2 w-full rounded-full bg-ink-100"></div>
<div class="h-2 w-5/6 rounded-full bg-ink-100"></div>
<div class="h-2 w-3/4 rounded-full bg-ink-100"></div>
<div class="mt-4 h-2 w-1/3 rounded-full bg-ink-200"></div>
<div class="h-2 w-full rounded-full bg-ink-100"></div>
<div class="h-2 w-4/6 rounded-full bg-ink-100"></div>
</div>
</div>

<!-- CV adapté à l'offre (devant) -->
<div class="relative rotate-1 rounded-card bg-surface p-5 shadow-pop ring-1 ring-border">
<span class="absolute -top-3 right-4 drop-shadow-sm">
<UiBadge variant="success">{{ m.hero.mockup.badge }}</UiBadge>
</span>

<span
class="inline-flex rounded-full bg-brand-50 px-2.5 py-0.5 text-[10px] font-semibold uppercase tracking-widest text-brand-700 ring-1 ring-brand-100"
>
{{ m.hero.mockup.afterLabel }}
</span>

<!-- Identité -->
<div class="mt-4 flex items-center gap-3">
<div
class="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-brand-600 text-xs font-bold text-white"
>
CR
</div>
<div class="min-w-0">
<p class="text-sm font-bold text-ink-900">Camille Roussel</p>
<p class="truncate text-[11px] text-ink-600">Cheffe de projets marketing · Nantes</p>
</div>
</div>

<!-- Accroche -->
<p class="mt-3 text-[10px] leading-relaxed text-ink-700">
<span class="rounded bg-brand-100 px-1 py-px font-medium text-brand-900">
6 ans de gestion de projets digitaux
</span>
au service de marques grand public, de la stratégie au déploiement.
</p>

<!-- Expérience -->
<p
class="mt-4 border-b border-border pb-1 text-[9px] font-semibold uppercase tracking-widest text-brand-700"
>
Expérience
</p>
<div v-for="job in JOBS" :key="job.role" class="mt-2.5">
<div class="flex items-baseline justify-between gap-2">
<p class="truncate text-[10px] font-semibold text-ink-900">{{ job.role }}</p>
<p class="shrink-0 text-[9px] text-ink-500">{{ job.period }}</p>
</div>
<ul class="mt-1 list-disc space-y-0.5 pl-3.5 marker:text-brand-400">
<li v-for="bullet in job.bullets" :key="bullet.text" class="text-[10px] text-ink-700">
<span
v-if="bullet.highlight"
class="rounded bg-brand-100 px-1 py-px font-medium text-brand-900"
>
{{ bullet.text }}
</span>
<template v-else>{{ bullet.text }}</template>
</li>
</ul>
</div>

<!-- Compétences -->
<p
class="mt-4 border-b border-border pb-1 text-[9px] font-semibold uppercase tracking-widest text-brand-700"
>
Compétences
</p>
<div class="mt-2 flex flex-wrap gap-1">
<span
v-for="skill in SKILLS"
:key="skill.label"
class="rounded-full px-2 py-0.5 text-[9px] font-medium"
:class="skill.highlight ? 'bg-brand-100 text-brand-800' : 'bg-ink-100 text-ink-700'"
>
{{ skill.label }}
</span>
</div>
</div>
</div>
</template>
Loading
Loading