diff --git a/Dockerfile b/Dockerfile index 193b26c..f813495 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,34 +1,26 @@ -# === Étape 1 : Build === -FROM node:20-alpine AS builder - +FROM node:22-alpine AS base WORKDIR /app +ENV NEXT_TELEMETRY_DISABLED=1 -# Copier les fichiers de dépendances pour profiter du cache Docker -COPY package*.json ./ - -# Installer uniquement ce qu'il faut pour le build +FROM base AS deps +COPY package.json package-lock.json ./ RUN npm ci -# Copier tout le code +FROM deps AS dev COPY . . +EXPOSE 3000 +CMD ["npm", "run", "dev", "--", "--hostname", "0.0.0.0", "--port", "3000"] -# Build Next.js pour la production +FROM deps AS builder +COPY . . RUN npm run build -# === Étape 2 : Runner léger === -FROM node:20-alpine AS runner - -WORKDIR /app - -# Copier uniquement ce qui est nécessaire pour la prod -COPY --from=builder /app/package*.json ./ -COPY --from=builder /app/node_modules ./node_modules -COPY --from=builder /app/.next ./.next -COPY --from=builder /app/public ./public - -# Mode production +FROM base AS runner ENV NODE_ENV=production +ENV HOSTNAME=0.0.0.0 +ENV PORT=3000 +COPY --from=builder /app/.next/standalone ./ +COPY --from=builder /app/.next/static ./.next/static +COPY --from=builder /app/public ./public EXPOSE 3000 - -# Lancer le serveur Next.js -CMD ["npm", "start"] \ No newline at end of file +CMD ["node", "server.js"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e73322f --- /dev/null +++ b/LICENSE @@ -0,0 +1,25 @@ +Copyright (c) 2026 Arthur + +Licence pour tous les projets Arthur + +1. Définition +Cette licence définit les droits et obligations concernant l'utilisation, la modification et la redistribution du code fourni par l'auteur. + +2. Autorisation d'utilisation +Vous êtes libre d'utiliser ce code pour vos projets personnels ou commerciaux. L'utilisation doit inclure une mention de l'auteur d’une manière libre (ex: "inspiré de ArthurP"). + +3. Modification +Vous pouvez modifier, adapter ou améliorer le code pour vos besoins. Les modifications doivent être identifiées comme telles et ne doivent pas être présentées comme l'original. + +4. Redistribution +- Le code original **ne peut pas être redistribué tel quel**. +- Les versions modifiées peuvent être partagées, sous réserve de mentionner l'auteur original. + +5. Usage commercial +L’usage commercial des versions modifiées est autorisé. Vous pouvez générer des revenus avec votre version modifiée. + +6. Attribution +L'auteur original doit être cité d’une manière libre, mais visible, sur tout projet utilisant ce code ou ses dérivés. + +7. Responsabilité +Le code est fourni "tel quel", sans garantie d’aucune sorte. L’auteur décline toute responsabilité pour tout dommage direct ou indirect résultant de l’utilisation du code. \ No newline at end of file diff --git a/README.md b/README.md index 3e1d4c4..ff458a0 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,11 @@ # BlocNote -Bloc-notes Markdown en ligne, rapide et sans inscription. +Bloc-notes Markdown en ligne, rapide, sans inscription et pensé pour l'auto-hébergement. -## Site public +## Site - Production: https://blocnote.arthurp.fr +- Contact: https://contact.arthurp.fr ou contact@arthurp.fr ## Stack technique @@ -13,24 +14,24 @@ Bloc-notes Markdown en ligne, rapide et sans inscription. - TypeScript - ESLint -## Fonctionnalites +## Fonctionnalités -- Edition Markdown avec apercu en temps reel +- Édition Markdown avec aperçu en temps réel - Sauvegarde automatique des notes - Recherche et tri des notes -- Notes epinglees +- Notes épinglées - Export d'une note - Mode clair / sombre - Raccourcis clavier ## Lancement local -Pre-requis: +Prérequis: - Node.js 22+ - npm -Installation et demarrage: +Installation et démarrage: ```bash npm ci @@ -39,25 +40,18 @@ npm run dev Application disponible sur http://localhost:3000 -## Scripts +## Docker + +Deux services sont fournis via [docker-compose.yml](docker-compose.yml): + +- blocnote-dev pour le développement sur le port 3000 +- blocnote-prod pour l'exécution de production sur le port 3016 ```bash -npm run dev # mode developpement -npm run lint # verification lint -npm run build # build production -npm run start # lancer le build production +docker compose --profile dev up +docker compose --profile prod up -d ``` ## Variables d'environnement -Le projet peut utiliser: - - NEXT_PUBLIC_SITE_URL (exemple: https://blocnote.arthurp.fr) - -## Docker - -Un environnement Docker est disponible via [docker-compose.yml](docker-compose.yml). - -```bash -docker compose up -d -``` diff --git a/app/components/SiteFooter.tsx b/app/components/SiteFooter.tsx new file mode 100644 index 0000000..414afb2 --- /dev/null +++ b/app/components/SiteFooter.tsx @@ -0,0 +1,93 @@ +import Link from "next/link"; + +const navigationLinks = [ + { href: "/", label: "Accueil" }, + { href: "/mentions-legales", label: "Mentions légales" }, + { href: "/confidentialite", label: "Confidentialité" }, +]; + +const contactLinks = [ + { href: "https://contact.arthurp.fr", label: "contact.arthurp.fr", external: true }, + { href: "mailto:contact@arthurp.fr", label: "contact@arthurp.fr", external: true }, +]; + +export default function SiteFooter() { + const year = new Date().getFullYear(); + + return ( + + ); +} \ No newline at end of file diff --git a/app/confidentialite/page.tsx b/app/confidentialite/page.tsx new file mode 100644 index 0000000..7cb0c11 --- /dev/null +++ b/app/confidentialite/page.tsx @@ -0,0 +1,56 @@ +import type { Metadata } from "next"; +import Link from "next/link"; + +const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || "https://blocnote.arthurp.fr"; + +export const metadata: Metadata = { + title: "Confidentialité", + description: "Politique de confidentialité de BlocNote.", + alternates: { + canonical: `${siteUrl}/confidentialite`, + }, +}; + +export default function ConfidentialitePage() { + return ( +
+
+ Vie privée +

Confidentialité

+

+ BlocNote est conçu pour garder les notes localement dans le navigateur et éviter la collecte de données inutile. +

+
+ +
+
+

Stockage des notes

+

+ Les notes sont enregistrées dans le navigateur via localStorage. Elles ne sont pas envoyées à un serveur BlocNote pour la synchronisation. +

+
+ +
+

Données collectées

+

+ L'application n'exige pas de compte utilisateur. Les contenus créés restent sous le contrôle direct de la personne qui utilise le site. +

+
+ +
+

Partage et support

+

+ Si vous souhaitez signaler un souci ou poser une question liée à la confidentialité, contactez contact.arthurp.fr ou contact@arthurp.fr. +

+
+ +
+

Évolution

+

+ Cette page peut évoluer si le fonctionnement technique du service change. La version la plus récente reste celle publiée sur le site. +

+
+
+
+ ); +} \ No newline at end of file diff --git a/app/globals.css b/app/globals.css index 455e9f9..f7cc705 100644 --- a/app/globals.css +++ b/app/globals.css @@ -64,17 +64,25 @@ } html, body { - height: 100%; - overflow: hidden; + min-height: 100%; } body { font-family: var(--font-sans); background: var(--bg-primary); color: var(--text-primary); + overflow-x: hidden; + overflow-y: auto; transition: background var(--transition), color var(--transition); } +.site-shell { + display: flex; + flex-direction: column; + min-height: 100vh; + width: 100%; +} + /* ===== Layout ===== */ .app-container { display: flex; @@ -82,6 +90,200 @@ body { overflow: hidden; } +/* ===== Site Footer ===== */ +.site-footer { + border-top: 1px solid var(--border-color); + background: + radial-gradient(circle at top left, rgba(59, 130, 246, 0.14), transparent 34%), + linear-gradient(180deg, var(--bg-secondary), var(--bg-primary)); + padding: 40px 24px 28px; +} + +.site-footer-inner { + max-width: 1200px; + margin: 0 auto; + display: grid; + grid-template-columns: minmax(0, 1.5fr) repeat(3, minmax(0, 1fr)); + gap: 28px; +} + +.site-footer-brand { + display: flex; + flex-direction: column; + gap: 16px; +} + +.site-footer-logo { + display: inline-flex; + align-items: center; + gap: 10px; + font-size: 22px; + font-weight: 700; + letter-spacing: -0.4px; +} + +.site-footer-mark { + display: block; + width: 34px; + height: 34px; + border-radius: 10px; +} + +.site-footer-description { + max-width: 34rem; + font-size: 15px; + line-height: 1.7; + color: var(--text-secondary); +} + +.site-footer-badges { + display: flex; + flex-wrap: wrap; + gap: 10px; +} + +.site-footer-badge { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 8px 12px; + border: 1px solid var(--border-color); + border-radius: 999px; + background: var(--bg-primary); + color: var(--text-secondary); + font-size: 12px; +} + +.site-footer-column { + display: flex; + flex-direction: column; + gap: 14px; +} + +.site-footer-heading { + font-size: 13px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--text-tertiary); +} + +.site-footer-links { + display: flex; + flex-direction: column; + gap: 10px; +} + +.site-footer-link { + color: var(--text-primary); + text-decoration: none; + font-size: 15px; + line-height: 1.5; + transition: color var(--transition); +} + +.site-footer-link:hover { + color: var(--accent); +} + +.site-footer-link-muted { + color: var(--text-secondary); +} + +.site-footer-bottom { + max-width: 1200px; + margin: 28px auto 0; + padding-top: 20px; + border-top: 1px solid var(--border-light); + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + flex-wrap: wrap; + color: var(--text-tertiary); + font-size: 13px; +} + +.site-footer-bottom strong { + color: var(--text-primary); +} + +.legal-page { + flex: 1; + width: 100%; + max-width: 1100px; + margin: 0 auto; + padding: 80px 24px 96px; +} + +.legal-page-hero { + display: flex; + flex-direction: column; + gap: 14px; + max-width: 46rem; + margin-bottom: 32px; +} + +.legal-page-kicker { + font-size: 12px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.12em; + color: var(--accent); +} + +.legal-page-title { + font-size: clamp(2rem, 4vw, 3.2rem); + line-height: 1.05; + letter-spacing: -0.05em; +} + +.legal-page-intro { + font-size: 18px; + line-height: 1.7; + color: var(--text-secondary); +} + +.legal-page-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 18px; +} + +.legal-card { + padding: 24px; + border: 1px solid var(--border-color); + border-radius: var(--radius-lg); + background: linear-gradient(180deg, var(--bg-secondary), var(--bg-primary)); + box-shadow: var(--shadow-sm); +} + +.legal-card h2 { + margin-bottom: 12px; + font-size: 20px; + letter-spacing: -0.02em; +} + +.legal-card p, +.legal-card li { + font-size: 15px; + line-height: 1.7; + color: var(--text-secondary); +} + +.legal-card ul { + padding-left: 18px; +} + +.legal-page-link { + color: var(--accent); + text-decoration: none; +} + +.legal-page-link:hover { + text-decoration: underline; +} + /* ===== Loading ===== */ .loading-screen { display: flex; @@ -898,6 +1100,26 @@ body { .preview-pane { padding: 14px 16px; } + + .site-footer { + padding: 32px 20px 24px; + } + + .site-footer-inner { + grid-template-columns: 1fr 1fr; + } + + .site-footer-brand { + grid-column: 1 / -1; + } + + .legal-page { + padding: 64px 20px 88px; + } + + .legal-page-grid { + grid-template-columns: 1fr; + } } @media (max-width: 480px) { @@ -913,6 +1135,14 @@ body { width: 48px; min-width: 48px; } + + .site-footer-inner { + grid-template-columns: 1fr; + } + + .site-footer-bottom { + align-items: flex-start; + } } /* ===== Accessibility ===== */ diff --git a/app/layout.tsx b/app/layout.tsx index 57b01b2..4357485 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,6 +1,7 @@ import type { Metadata, Viewport } from "next"; import { Geist, Geist_Mono } from "next/font/google"; import "./globals.css"; +import SiteFooter from "./components/SiteFooter"; const geistSans = Geist({ variable: "--font-geist-sans", @@ -95,6 +96,7 @@ export const metadata: Metadata = { }, icons: { icon: [ + { url: "/icon.svg", type: "image/svg+xml" }, { url: "/favicon.ico", sizes: "any" }, { url: "/icon-192.png", sizes: "192x192", type: "image/png" }, { url: "/icon-512.png", sizes: "512x512", type: "image/png" }, @@ -151,7 +153,10 @@ export default function RootLayout({ className={`${geistSans.variable} ${geistMono.variable} antialiased`} > Aller au contenu principal - {children} +
+ {children} + +
); diff --git a/app/mentions-legales/page.tsx b/app/mentions-legales/page.tsx new file mode 100644 index 0000000..89f9e1c --- /dev/null +++ b/app/mentions-legales/page.tsx @@ -0,0 +1,56 @@ +import type { Metadata } from "next"; +import Link from "next/link"; + +const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || "https://blocnote.arthurp.fr"; + +export const metadata: Metadata = { + title: "Mentions légales", + description: "Mentions légales du site BlocNote.", + alternates: { + canonical: `${siteUrl}/mentions-legales`, + }, +}; + +export default function MentionsLegalesPage() { + return ( +
+
+ Informations légales +

Mentions légales

+

+ Les informations ci-dessous résument l'éditeur du site, les contacts utiles et le cadre technique de BlocNote. +

+
+ +
+
+

Éditeur du site

+

+ BlocNote est édité par Arthur P. Le site est accessible à l'adresse blocnote.arthurp.fr. +

+
+ +
+

Contact

+

+ Pour toute demande, utilisez contact.arthurp.fr ou écrivez à contact@arthurp.fr. +

+
+ +
+

Hébergement

+

+ Le site est auto-hébergé sur Proxmox, avec une mise en production Docker pour servir l'application Next.js. +

+
+ +
+

Propriété intellectuelle

+

+ Le contenu, l'identité visuelle et le code source de BlocNote restent protégés par le droit d'auteur. Toute réutilisation doit respecter les droits du titulaire. +

+
+
+
+ ); +} \ No newline at end of file diff --git a/app/sitemap.ts b/app/sitemap.ts index 6864cc5..c14a3ed 100644 --- a/app/sitemap.ts +++ b/app/sitemap.ts @@ -9,5 +9,17 @@ export default function sitemap(): MetadataRoute.Sitemap { changeFrequency: "weekly", priority: 1.0, }, + { + url: `${siteUrl}/mentions-legales`, + lastModified: new Date(), + changeFrequency: "yearly", + priority: 0.3, + }, + { + url: `${siteUrl}/confidentialite`, + lastModified: new Date(), + changeFrequency: "yearly", + priority: 0.3, + }, ]; } diff --git a/docker-compose.yml b/docker-compose.yml index a47d63b..1840044 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,11 +1,31 @@ services: - web: - build: . + blocnote-dev: + profiles: ["dev"] + build: + context: . + target: dev + container_name: blocnote-dev ports: - "3000:3000" volumes: - ./:/app - /app/node_modules + - /app/.next environment: - - NODE_ENV=development - command: npm run dev \ No newline at end of file + NEXT_TELEMETRY_DISABLED: "1" + NODE_ENV: development + + blocnote-prod: + profiles: ["prod"] + build: + context: . + target: runner + container_name: blocnote-prod + restart: unless-stopped + ports: + - "3016:3000" + env_file: + - .env + environment: + NEXT_TELEMETRY_DISABLED: "1" + NODE_ENV: production diff --git a/next.config.ts b/next.config.ts index 077057c..dcc58ba 100644 --- a/next.config.ts +++ b/next.config.ts @@ -3,6 +3,7 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { compress: true, poweredByHeader: false, + output: "standalone", async headers() { return [ {