chore init
This commit is contained in:
1 parent
1b7406b1d3
commit
a0d312a884
60 files changed
+5046
-125
No files matched your search
@@ -0,0 +1,115 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Documentation — CV-Maker",
|
||||
description: "Guide de prise en main de CV-Maker et syntaxe Markdown supportée.",
|
||||
};
|
||||
|
||||
export default function DocumentationPage() {
|
||||
return (
|
||||
<main className="mx-auto w-full max-w-3xl flex-1 px-6 py-10">
|
||||
<Link
|
||||
href="/"
|
||||
className="text-sm font-medium text-muted underline-offset-2 hover:text-accent hover:underline"
|
||||
>
|
||||
← Retour à l'éditeur
|
||||
</Link>
|
||||
|
||||
<h1 className="mt-4 text-2xl font-bold text-ink">Documentation</h1>
|
||||
|
||||
<section className="mt-6">
|
||||
<h2 className="text-xl font-semibold text-ink">Prise en main</h2>
|
||||
<ol className="mt-3 list-decimal space-y-2 pl-5 text-sm leading-relaxed text-ink">
|
||||
<li>
|
||||
<strong>Onglets 1 à 5</strong> — sélectionnez une zone de saisie (accroche,
|
||||
état civil + contacts, formations, soft skills, centres d'intérêt).
|
||||
Un seul onglet est actif à la fois ; chaque zone conserve son texte.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Saisie Markdown</strong> — le texte est écrit en Markdown dans le
|
||||
panneau gauche et rendu en HTML en temps réel dans la feuille A4 à droite.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Expériences professionnelles</strong> — bouton « Ajouter » pour créer
|
||||
une expérience (titre + contenu). La liste est numérotée ; chaque entrée peut
|
||||
être modifiée ou supprimée. L'ordre de saisie détermine l'affichage : la
|
||||
première expérience ajoutée apparaît en haut de la section « Expériences
|
||||
professionnelles » de la feuille, les suivantes en dessous.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Sauvegarde automatique</strong> — chaque frappe est enregistrée dans le
|
||||
navigateur (localStorage, clé <code>cv-maker:v1</code>). Rechargez la page :
|
||||
vos données sont restaurées. Aucune donnée n'est envoyée sur Internet.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Impression / PDF</strong> — le bouton « Imprimer / PDF » ouvre
|
||||
l'aperçu d'impression du navigateur. Enregistrez en PDF : seule la
|
||||
feuille A4 est exportée, sans les panneaux d'édition.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Export / import JSON</strong> — le bouton « Exporter JSON »
|
||||
télécharge votre CV au format JSON (fichier <code>cv-maker.json</code>).
|
||||
Sur un autre appareil, « Importer JSON » charge ce fichier et remplace
|
||||
le contenu actuel (après confirmation). C'est le moyen de partager ou
|
||||
de sauvegarder un CV entre plusieurs navigateurs.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Réinitialiser</strong> — le bouton « Réinitialiser » (après
|
||||
confirmation) vide toutes les zones et la liste des expériences.
|
||||
</li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<section className="mt-8">
|
||||
<h2 className="text-xl font-semibold text-ink">Syntaxe Markdown supportée</h2>
|
||||
<p className="mt-2 text-sm leading-relaxed text-muted">
|
||||
Les 5 zones et le contenu des expériences sont rendus en Markdown (GFM).
|
||||
</p>
|
||||
<div className="mt-4 overflow-x-auto rounded-md border border-border bg-white">
|
||||
<table className="w-full text-sm text-ink">
|
||||
<thead>
|
||||
<tr className="border-b border-border bg-paper text-left">
|
||||
<th className="px-3 py-2 font-semibold">Élément</th>
|
||||
<th className="px-3 py-2 font-semibold">Syntaxe</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-border">
|
||||
{[
|
||||
["Titre niveau 3", "### Développeur Full-Stack"],
|
||||
["Gras", "**Jean Dupont**"],
|
||||
["Italique", "*Contact*"],
|
||||
["Liste à puces", "- Compétence 1\n- Compétence 2"],
|
||||
["Liste numérotée", "1. Diplôme 1\n2. Diplôme 2"],
|
||||
["Lien", "[GitHub](https://github.com/user)"],
|
||||
["Lien brut", "<https://example.com>"],
|
||||
["Code en ligne", "`npm run dev`"],
|
||||
["Bloc de code", "```\nconst x = 42;\n```"],
|
||||
["Citation", "> Citation d'accroche"],
|
||||
["Séparateur", "---"],
|
||||
].map(([label, syntax]) => (
|
||||
<tr key={label}>
|
||||
<td className="px-3 py-2 font-medium">{label}</td>
|
||||
<td className="px-3 py-2 font-mono text-xs whitespace-pre">
|
||||
{syntax}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mt-8 rounded-md border border-border bg-white p-4">
|
||||
<h2 className="text-sm font-semibold text-ink">Conseil : structurer son CV</h2>
|
||||
<p className="mt-2 text-sm leading-relaxed text-ink">
|
||||
Utilisez l'onglet 1 pour le nom et le poste visé, l'onglet 2 pour vos
|
||||
coordonnées (e-mail, téléphone, site web, GitHub), l'onglet 3 pour vos
|
||||
formations, l'onglet 4 pour vos soft skills et l'onglet 5 pour vos
|
||||
centres d'intérêt. Complétez avec vos expériences professionnelles, de la plus
|
||||
récente à la plus ancienne.
|
||||
</p>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
+130
-20
@@ -1,26 +1,136 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
:root {
|
||||
--background: #ffffff;
|
||||
--foreground: #171717;
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--font-sans: var(--font-geist-sans);
|
||||
--font-mono: var(--font-geist-mono);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background: #0a0a0a;
|
||||
--foreground: #ededed;
|
||||
}
|
||||
@theme {
|
||||
--color-surface: #ffffff;
|
||||
--color-paper: #f1f5f9;
|
||||
--color-ink: #0f172a;
|
||||
--color-muted: #64748b;
|
||||
--color-accent: #2563eb;
|
||||
--color-danger: #dc2626;
|
||||
--color-border: #e2e8f0;
|
||||
--font-sans: var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif;
|
||||
--font-mono: var(--font-geist-mono), ui-monospace, monospace;
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
background: var(--color-paper);
|
||||
color: var(--color-ink);
|
||||
}
|
||||
|
||||
/* Tailwind v4 ne force plus cursor:pointer sur les boutons ; on le restaure */
|
||||
/* (le curseur « not-allowed » des boutons désactivés reste prioritaire) */
|
||||
button:not(:disabled),
|
||||
[role="button"]:not(:disabled) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* ── Feuille A4 (preview) ─────────────────────────────────────── */
|
||||
.cv-sheet {
|
||||
width: 210mm;
|
||||
max-width: 100%;
|
||||
min-height: 297mm;
|
||||
background: var(--color-surface);
|
||||
box-shadow: 0 10px 30px rgb(15 23 42 / 0.15);
|
||||
border: 1px solid var(--color-border);
|
||||
padding: 16mm;
|
||||
}
|
||||
|
||||
.cv-sheet-body {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 2fr;
|
||||
gap: 12mm;
|
||||
}
|
||||
|
||||
/* ── Rendu Markdown du CV ─────────────────────────────────────── */
|
||||
.cv-md {
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.cv-md p {
|
||||
margin: 0.25em 0;
|
||||
}
|
||||
|
||||
.cv-md h1,
|
||||
.cv-md h2,
|
||||
.cv-md h3,
|
||||
.cv-md h4 {
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
margin: 0.35em 0 0.25em;
|
||||
}
|
||||
|
||||
.cv-md h1 {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
.cv-md h2 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
.cv-md h3 {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.cv-md h4 {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.cv-md ul,
|
||||
.cv-md ol {
|
||||
margin: 0.25em 0;
|
||||
padding-left: 1.2em;
|
||||
}
|
||||
|
||||
.cv-md li {
|
||||
margin: 0.1em 0;
|
||||
}
|
||||
|
||||
.cv-md a {
|
||||
color: var(--color-accent);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.cv-md code {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.9em;
|
||||
background: var(--color-paper);
|
||||
padding: 0.1em 0.3em;
|
||||
border-radius: 0.25em;
|
||||
}
|
||||
|
||||
.cv-md pre {
|
||||
background: var(--color-paper);
|
||||
padding: 0.6em;
|
||||
border-radius: 0.375em;
|
||||
overflow-x: auto;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.cv-md blockquote {
|
||||
border-left: 3px solid var(--color-border);
|
||||
padding-left: 0.75em;
|
||||
margin: 0.25em 0;
|
||||
color: var(--color-muted);
|
||||
}
|
||||
|
||||
/* ── Impression PDF ───────────────────────────────────────────── */
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@media print {
|
||||
body {
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.print-hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.cv-sheet {
|
||||
box-shadow: none;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
width: auto;
|
||||
height: auto;
|
||||
padding: 16mm;
|
||||
}
|
||||
}
|
||||
+23
-6
@@ -1,5 +1,7 @@
|
||||
import type { Metadata } from "next";
|
||||
import type { Metadata, Viewport } from "next";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import { SerwistProvider } from "@serwist/next/react";
|
||||
import { Footer } from "@/components/ui/Footer";
|
||||
import "./globals.css";
|
||||
|
||||
const geistSans = Geist({
|
||||
@@ -13,17 +15,32 @@ const geistMono = Geist_Mono({
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Next App",
|
||||
description: "Generated by create next app",
|
||||
title: "CV-Maker — Créez votre CV",
|
||||
description:
|
||||
"Créez votre CV en Markdown avec aperçu temps réel au format A4, 100 % local dans votre navigateur.",
|
||||
};
|
||||
|
||||
export const viewport: Viewport = {
|
||||
themeColor: "#2563eb",
|
||||
};
|
||||
|
||||
export default function RootLayout({ children }: LayoutProps<"/">) {
|
||||
return (
|
||||
<html
|
||||
lang="en"
|
||||
lang="fr"
|
||||
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
|
||||
>
|
||||
<body className="min-h-full flex flex-col">{children}</body>
|
||||
<body className="min-h-full flex flex-col bg-paper text-ink">
|
||||
<SerwistProvider
|
||||
swUrl="/sw.js"
|
||||
disable={process.env.NODE_ENV === "development"}
|
||||
>
|
||||
<div className="flex flex-1 flex-col">
|
||||
{children}
|
||||
<Footer />
|
||||
</div>
|
||||
</SerwistProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { MetadataRoute } from "next";
|
||||
|
||||
export default function manifest(): MetadataRoute.Manifest {
|
||||
return {
|
||||
name: "CV-Maker",
|
||||
short_name: "CV-Maker",
|
||||
description: "Créez votre CV en Markdown avec aperçu temps réel au format A4.",
|
||||
start_url: "/",
|
||||
display: "standalone",
|
||||
background_color: "#f1f5f9",
|
||||
theme_color: "#2563eb",
|
||||
icons: [
|
||||
{
|
||||
src: "/icons/icon-192.png",
|
||||
sizes: "192x192",
|
||||
type: "image/png",
|
||||
},
|
||||
{
|
||||
src: "/icons/icon-512.png",
|
||||
sizes: "512x512",
|
||||
type: "image/png",
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Mentions légales — CV-Maker",
|
||||
description:
|
||||
"Mentions légales de l'application CV-Maker : éditeur, données traitées, stockage et droits.",
|
||||
};
|
||||
|
||||
export default function MentionsLegalesPage() {
|
||||
return (
|
||||
<main className="mx-auto w-full max-w-3xl flex-1 px-6 py-10">
|
||||
<Link
|
||||
href="/"
|
||||
className="text-sm font-medium text-muted underline-offset-2 hover:text-accent hover:underline"
|
||||
>
|
||||
← Retour à l'éditeur
|
||||
</Link>
|
||||
|
||||
<h1 className="mt-4 text-2xl font-bold text-ink">Mentions légales</h1>
|
||||
|
||||
<div className="mt-6 space-y-6 text-sm leading-relaxed text-ink">
|
||||
<section>
|
||||
<h2 className="text-base font-semibold text-ink">Éditeur</h2>
|
||||
<p className="mt-2">
|
||||
L'utilisateur de l'application est le seul responsable et
|
||||
destinataire des données saisies. Aucune entité tiers ne traite ces
|
||||
données.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className="text-base font-semibold text-ink">Données traitées</h2>
|
||||
<p className="mt-2">
|
||||
Les zones de saisie (message d'accroche, état civil, contacts,
|
||||
formations, soft skills, centres d'intérêt) et les expériences
|
||||
professionnelles contiennent des informations que vous choisissez de
|
||||
saisir : nom, prénom, adresse, e-mail, téléphone, site web, GitHub,
|
||||
parcours professionnel et formations.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className="text-base font-semibold text-ink">Finalité</h2>
|
||||
<p className="mt-2">
|
||||
Les données sont utilisées exclusivement à la production d'un CV,
|
||||
destiné à être imprimé ou exporté en PDF par l'utilisateur.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className="text-base font-semibold text-ink">Stockage</h2>
|
||||
<p className="mt-2">
|
||||
Les données sont stockées dans le{" "}
|
||||
<code className="rounded bg-paper px-1.5 py-0.5 font-mono text-xs">
|
||||
localStorage
|
||||
</code>{" "}
|
||||
du navigateur, sous la clé{" "}
|
||||
<code className="rounded bg-paper px-1.5 py-0.5 font-mono text-xs">
|
||||
cv-maker:v1
|
||||
</code>
|
||||
. Aucune donnée n'est envoyée à un serveur. Le stockage est
|
||||
entièrement local à votre navigateur.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className="text-base font-semibold text-ink">Durée de conservation</h2>
|
||||
<p className="mt-2">
|
||||
Les données sont conservées jusqu'à leur effacement par
|
||||
l'utilisateur (via le bouton « Réinitialiser » dans
|
||||
l'application, ou en vidant le cache de votre navigateur).
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className="text-base font-semibold text-ink">
|
||||
Droits de la personne concernée
|
||||
</h2>
|
||||
<p className="mt-2">
|
||||
En tant que seul responsable des données, vous disposez d'un accès
|
||||
complet à l'ensemble des informations saisies. Vous pouvez les
|
||||
modifier à tout moment depuis l'éditeur, ou les supprimer en
|
||||
utilisant :
|
||||
</p>
|
||||
<ul className="mt-2 list-disc space-y-1 pl-5">
|
||||
<li>
|
||||
Le bouton <strong>« Réinitialiser »</strong> dans la barre d'outils
|
||||
(après confirmation).
|
||||
</li>
|
||||
<li>
|
||||
Les outils de votre navigateur : paramètres → vie privée → vider les
|
||||
données de ce site pour cette application.
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className="text-base font-semibold text-ink">Sécurité</h2>
|
||||
<p className="mt-2">
|
||||
Le contenu Markdown saisi est assaini avant rendu via{" "}
|
||||
<strong>DOMPurify</strong> pour neutraliser tout script ou contenu
|
||||
dangereux (protection contre les attaques XSS). Aucune donnée
|
||||
n'est transmise sur le réseau ; l'application fonctionne
|
||||
entièrement hors ligne une fois chargée.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<p className="text-xs text-muted">
|
||||
Dernière mise à jour : 16 septembre 2026
|
||||
</p>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
+4
-68
@@ -1,69 +1,5 @@
|
||||
import Image from "next/image";
|
||||
import { CvWorkspace } from "@/components/cv/CvWorkspace";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="flex flex-col flex-1 items-center justify-center bg-zinc-50 font-sans dark:bg-black">
|
||||
<main className="flex flex-1 w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
|
||||
<Image
|
||||
className="dark:invert h-5 w-[100px]"
|
||||
src="/next.svg"
|
||||
alt="Next.js logo"
|
||||
width={100}
|
||||
height={20}
|
||||
priority
|
||||
/>
|
||||
<div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
|
||||
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
|
||||
To get started, edit the{" "}
|
||||
<code className="rounded bg-black/[.06] px-1.5 py-0.5 font-mono text-[0.9em] dark:bg-white/[.08]">
|
||||
page.tsx
|
||||
</code>{" "}
|
||||
file.
|
||||
</h1>
|
||||
<p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
|
||||
Looking for a starting point or more instructions? Head over to{" "}
|
||||
<a
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
||||
>
|
||||
Templates
|
||||
</a>{" "}
|
||||
or the{" "}
|
||||
<a
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
||||
>
|
||||
Learning
|
||||
</a>{" "}
|
||||
center.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
|
||||
<a
|
||||
className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
className="dark:invert h-[14px] w-4"
|
||||
src="/vercel.svg"
|
||||
alt="Vercel logomark"
|
||||
width={16}
|
||||
height={14}
|
||||
/>
|
||||
Deploy Now
|
||||
</a>
|
||||
<a
|
||||
className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Documentation
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default function HomePage() {
|
||||
return <CvWorkspace />;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/// <reference lib="esnext" />
|
||||
/// <reference lib="webworker" />
|
||||
|
||||
import { Serwist } from "serwist";
|
||||
import type { PrecacheEntry, SerwistGlobalConfig } from "serwist";
|
||||
import { defaultCache } from "@serwist/next/worker";
|
||||
|
||||
declare global {
|
||||
interface WorkerGlobalScope extends SerwistGlobalConfig {
|
||||
__SW_MANIFEST: (PrecacheEntry | string)[] | undefined;
|
||||
}
|
||||
}
|
||||
|
||||
declare const self: ServiceWorkerGlobalScope;
|
||||
|
||||
const serwist = new Serwist({
|
||||
precacheEntries: self.__SW_MANIFEST,
|
||||
skipWaiting: true,
|
||||
clientsClaim: true,
|
||||
navigationPreload: true,
|
||||
runtimeCaching: defaultCache,
|
||||
});
|
||||
|
||||
serwist.addEventListeners();
|
||||
Reference in new issue
Block a user