use charte from www

This commit is contained in:
Christophe Robillard 2026-09-29 06:34:43 +02:00
parent b5a18ab6fd
commit ad251e7558
52 changed files with 2203 additions and 116 deletions

97
app/assets/fonts/OFL.txt Normal file
View file

@ -0,0 +1,97 @@
Young Serif
Copyright 2023 The Young Serif Project Authors (https://github.com/noirblancrouge/YoungSerif)
Atkinson Hyperlegible Next
Copyright 2020-2024 The Atkinson Hyperlegible Next Project Authors (https://github.com/googlefonts/atkinson-hyperlegible-next)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path d="M4 4 L7 24 Q7.5 28 11.5 28 L19.5 28 Q23.5 28 24 24 L27 4 Z" fill="#5F9B59" stroke="#2E2A26" stroke-width="2.5" stroke-linejoin="round"/><path d="M26.5 9 C32 9 32 20 25 21" fill="none" stroke="#2E2A26" stroke-width="3" stroke-linecap="round"/></svg>

After

Width:  |  Height:  |  Size: 318 B

View file

@ -0,0 +1,48 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 580 214" role="img" aria-labelledby="t">
<title id="t">Trois tasses de couleur alignées sur la table, et une quatrième posée sur la Kluk, fumante.</title>
<style>
.table { fill: #D9BC94; }
.table-fil, .tasse-corps, .tasse-anse, .kluk-socle, .vapeur path {
stroke: #2E2A26; stroke-width: 3; stroke-linecap: round; stroke-linejoin: round;
}
.tasse-anse { fill: none; stroke-width: 6; }
.tasse-corps { fill: currentColor; }
.tasse-reflet { fill: none; stroke: #fff; stroke-opacity: 0.45; stroke-width: 4; stroke-linecap: round; }
.kluk-socle { fill: #B48D5F; }
.kluk-voyant { fill: #F5F6F0; }
.vapeur path { fill: none; stroke-width: 2.5; stroke: #5A534C; }
.humeur-gris { color: #8D97A8; }
.humeur-orange { color: #EDA33D; }
.humeur-rouge { color: #D8573F; }
.humeur-vert { color: #5F9B59; }
@media (prefers-reduced-motion: no-preference) {
.vapeur path { animation: vapeur 5s ease-in-out infinite; }
.vapeur path + path { animation-delay: 2.5s; }
}
@keyframes vapeur {
0%, 100% { opacity: 0.25; transform: translateY(6px); }
50% { opacity: 0.9; transform: translateY(-6px); }
}
</style>
<defs>
<g id="tasse">
<path class="tasse-anse" d="M63 22 C86 20 88 60 60 62" />
<path class="tasse-corps" d="M3 6 L10 78 Q11 86 20 86 L48 86 Q57 86 58 78 L65 6 Z" />
<path class="tasse-reflet" d="M13 18 L17 62" />
</g>
</defs>
<rect class="table" x="0" y="200" width="580" height="14" rx="3" />
<path class="table-fil" d="M0 200 H580" />
<use href="#tasse" x="34" y="114" class="humeur-gris" />
<use href="#tasse" x="132" y="114" class="humeur-orange" />
<use href="#tasse" x="230" y="114" class="humeur-rouge" />
<g transform="translate(410 98)">
<g class="vapeur">
<path d="M22 -6 c-9 -11 9 -20 0 -31 c-9 -11 9 -20 0 -31" />
<path d="M44 -10 c-9 -11 9 -20 0 -31 c-9 -11 9 -20 0 -31" />
</g>
<use href="#tasse" x="0" y="0" class="humeur-vert" />
<rect class="kluk-socle" x="-22" y="86" width="112" height="16" rx="8" />
<circle class="kluk-voyant" cx="34" cy="94" r="2.6" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-2 -2 44 36"><path d="M3 3 L6 25 Q6.5 29 10.5 29 L24.5 29 Q28.5 29 29 25 L32 3 Z M31.5 8 C39 8 39.5 21 30 22" fill="none" stroke="#B48D5F" stroke-width="2.5" stroke-linejoin="round" stroke-linecap="round"/></svg>

After

Width:  |  Height:  |  Size: 262 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-3 -3 95 95"><path d="M63 22 C86 20 88 60 60 62" fill="none" stroke="#2E2A26" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/><path d="M3 6 L10 78 Q11 86 20 86 L48 86 Q57 86 58 78 L65 6 Z" fill="#8D97A8" stroke="#2E2A26" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/><path d="M13 18 L17 62" fill="none" stroke="#fff" stroke-opacity="0.45" stroke-width="4" stroke-linecap="round"/></svg>

After

Width:  |  Height:  |  Size: 472 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-3 -3 95 95"><path d="M63 22 C86 20 88 60 60 62" fill="none" stroke="#2E2A26" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/><path d="M3 6 L10 78 Q11 86 20 86 L48 86 Q57 86 58 78 L65 6 Z" fill="#EDA33D" stroke="#2E2A26" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/><path d="M13 18 L17 62" fill="none" stroke="#fff" stroke-opacity="0.45" stroke-width="4" stroke-linecap="round"/></svg>

After

Width:  |  Height:  |  Size: 472 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-3 -3 95 95"><path d="M63 22 C86 20 88 60 60 62" fill="none" stroke="#2E2A26" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/><path d="M3 6 L10 78 Q11 86 20 86 L48 86 Q57 86 58 78 L65 6 Z" fill="#D8573F" stroke="#2E2A26" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/><path d="M13 18 L17 62" fill="none" stroke="#fff" stroke-opacity="0.45" stroke-width="4" stroke-linecap="round"/></svg>

After

Width:  |  Height:  |  Size: 472 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-3 -3 95 95"><path d="M63 22 C86 20 88 60 60 62" fill="none" stroke="#2E2A26" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/><path d="M3 6 L10 78 Q11 86 20 86 L48 86 Q57 86 58 78 L65 6 Z" fill="#5F9B59" stroke="#2E2A26" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/><path d="M13 18 L17 62" fill="none" stroke="#fff" stroke-opacity="0.45" stroke-width="4" stroke-linecap="round"/></svg>

After

Width:  |  Height:  |  Size: 472 B

View file

@ -0,0 +1,62 @@
// Raccords entre Bulma et la charte : ce que ni l'un ni l'autre ne règle seul.
// Les titres Bulma suivent ceux de la charte : Young Serif en 400.
.title,
.subtitle {
font-family: var(--serif);
font-weight: 400;
letter-spacing: -0.005em;
}
// La charte souligne tous les liens ; pas les liens habillés en bouton ou en étiquette.
a.button,
a.tag,
a.navbar-item,
a.dropdown-item {
text-decoration: none;
}
// La charte ne prépare les <button class="bouton"> que dans un .formulaire :
// on les prépare partout (button_to, f.submit…).
button.bouton,
input.bouton {
border: 0;
font: inherit;
font-weight: 700;
cursor: pointer;
}
button.bouton:disabled,
input.bouton:disabled {
opacity: 0.6;
cursor: progress;
}
// button_to enveloppe son bouton dans un formulaire : le garder en ligne dans la navigation.
.nav form.button_to {
display: inline;
}
// La charte ne vise que les champs email et les zones de texte : on étend aux autres types.
.formulaire {
input[type="text"],
input[type="password"],
input[type="date"],
input[type="number"],
select {
display: block;
width: 100%;
padding: 0.7rem 0.85rem;
border: 2px solid var(--bordure-champ);
border-radius: var(--arrondi-petit);
background: var(--blanc);
color: var(--encre);
font: inherit;
line-height: 1.4;
&:focus-visible {
outline: 3px solid var(--vert);
outline-offset: 1px;
border-color: var(--encre);
}
}
}

View file

@ -1,41 +1,29 @@
// @charset "utf-8"; // Bulma, habillé aux couleurs et typographies de la charte Kluk (charte.css).
// Les valeurs reprennent les variables de charte.css : garder les deux en phase.
// Version sans mode sombre : la charte n'en a pas, et Bulma basculerait sinon
// champs, titres et fonds en sombre quand le système le demande.
@use 'bulma/versions/bulma-no-dark-mode' with (
$family-primary: ("Atkinson Hyperlegible Next", "Atkinson Hyperlegible", system-ui, sans-serif),
$family-secondary: ("Young Serif", Georgia, serif),
$primary: #2E2A26, // --encre : les boutons de la charte sont à l'encre
$link: #3F7A3B, // --vert-lien
$success: #5F9B59, // --vert
$warning: #EDA33D, // --orange
$danger: #D8573F, // --rouge
$info: #8D97A8, // --gris
$text: #2E2A26, // --encre
$text-strong: #2E2A26,
$text-weak: #5A534C, // --encre-douce
$scheme-main: #F5F6F0, // --porcelaine
$background: #E9ECE2, // --porcelaine-fonce
$border: #8A8279, // --bordure-champ
$radius: 0.5rem, // --arrondi-petit
$radius-large: 0.75rem, // --arrondi
);
// Import a Google Font // La charte vient après Bulma pour que ses règles de base l'emportent.
// @import url('https://fonts.googleapis.com/css?family=Nunito:400,700'); @use 'charte';
@use 'charte-bulma';
// Import only what you need from Bulma // Styles propres à l'application
// @import "bulma/sass/utilities/_all.sass"; @use 'kluk';
// @import "bulma/sass/base/_all.sass";
// @import "bulma/sass/elements/button.sass";
// @import "bulma/sass/elements/container.sass";
// @import "bulma/sass/elements/title.sass";
// @import "bulma/sass/form/_all.sass";
// @import "bulma/sass/components/navbar.sass";
// @import "bulma/sass/layout/hero.sass";
// @import "bulma/sass/layout/section.sass";
@use 'bulma/bulma';
@use 'bulma/sass/utilities/_index.scss' as v;
@use './kluk';
// Set your brand colors
// $purple: #8A4D76;
// $pink: #FA7C91;
// $brown: #757763;
// $beige-light: #D0D1CD;
// $beige-lighter: #EFF0EB;
// Update Bulma's global variables
// $family-sans-serif: "Nunito", sans-serif;
// $grey-dark: $brown;
// $grey-light: $beige-light;
// $primary: $purple;
// $link: $pink;
// $widescreen-enabled: false;
// $fullhd-enabled: false;
// Update some of Bulma's component variables
// $body-background-color: $beige-lighter;
// $control-border-width: 2px;
// $input-border-color: transparent;
// $input-shadow: none;

View file

@ -0,0 +1,531 @@
/*
* Kluk — charte graphique, feuille de style réutilisable.
* Extraite de assets/css/style.scss (site kluk.fr), en CSS pur, sans Sass.
* Copie de charte/css/kluk.css : seuls les chemins des polices changent, ils
* pointent vers app/assets/fonts/ (Propshaft les réécrit avec l'empreinte).
* Ne pas modifier ici : les ajustements pour Bulma sont dans _charte-bulma.scss.
*
* Palette : la porcelaine du matin, l'encre du thé, le chêne de la table,
* et les quatre couleurs des tasses en accent.
*
* Sommaire
* 1. Polices
* 2. Variables (couleurs, typographies, mise en page)
* 3. Base
* 4. Liens, focus, lien d'évitement
* 5. Boutons
* 6. En-tête et navigation
* 7. Colonne de texte et titres
* 8. Illustration « tasses »
* 9. Calendrier d'humeurs
* 10. Photos
* 11. Formulaire
* 12. Pied de page
* 13. Impression
*/
/* ---------- 1. Polices ---------- */
@font-face {
font-family: "Young Serif";
src: url("/young-serif-400.woff2") format("woff2");
font-weight: 400;
font-style: normal;
font-display: swap;
}
/* Fichier variable : couvre toutes les graisses de 200 à 800 */
@font-face {
font-family: "Atkinson Hyperlegible Next";
src: url("/atkinson-hyperlegible-next-400.woff2") format("woff2");
font-weight: 200 800;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Atkinson Hyperlegible Next";
src: url("/atkinson-hyperlegible-next-400-italic.woff2") format("woff2");
font-weight: 400;
font-style: italic;
font-display: swap;
}
/* ---------- 2. Variables ---------- */
:root {
/* Fonds et texte */
--porcelaine: #F5F6F0;
--porcelaine-fonce: #E9ECE2;
--encre: #2E2A26;
--encre-douce: #5A534C;
--chene: #D9BC94;
--chene-fonce: #B48D5F;
/* Humeurs (les quatre tasses) */
--gris: #8D97A8;
--orange: #EDA33D;
--vert: #5F9B59;
--rouge: #D8573F;
--vert-lien: #3F7A3B;
/* Teintes utilitaires (écrites en dur dans le site d'origine) */
--encre-survol: #45403A;
--surface: #FBFCF8;
--bordure-champ: #8A8279;
--erreur-fond: #FBE9E5;
--blanc: #fff;
/* Typographies */
--serif: "Young Serif", Georgia, serif;
--sans: "Atkinson Hyperlegible Next", "Atkinson Hyperlegible", system-ui, sans-serif;
/* Mise en page */
--colonne: 36rem;
--gouttiere: 1.25rem;
--largeur-entete: 64rem;
/* Arrondis */
--arrondi-petit: 0.5rem;
--arrondi: 0.75rem;
--arrondi-pilule: 999px;
}
/* ---------- 3. Base ---------- */
*,
*::before,
*::after { box-sizing: border-box; }
html {
-webkit-text-size-adjust: 100%;
text-size-adjust: 100%;
}
body {
margin: 0;
min-height: 100vh;
display: flex;
flex-direction: column;
background: var(--porcelaine);
color: var(--encre);
font-family: var(--sans);
font-size: 1.1875rem;
line-height: 1.65;
font-kerning: normal;
hyphens: manual;
}
main { flex: 1; outline: none; }
img, svg { display: block; max-width: 100%; }
/* ---------- 4. Liens, focus, lien d'évitement ---------- */
a {
color: var(--encre);
text-decoration-line: underline;
text-decoration-color: var(--vert);
text-decoration-thickness: 0.12em;
text-underline-offset: 0.2em;
}
a:hover { text-decoration-color: var(--encre); }
:focus-visible {
outline: 3px solid var(--encre);
outline-offset: 3px;
border-radius: 4px;
}
.evitement {
position: absolute;
left: var(--gouttiere);
top: -4rem;
padding: 0.5rem 1rem;
background: var(--encre);
color: var(--porcelaine);
z-index: 10;
}
.evitement:focus { top: 0.75rem; }
/* ---------- 5. Boutons ---------- */
.bouton {
display: inline-block;
padding: 0.8rem 1.6rem;
border-radius: var(--arrondi-pilule);
background: var(--encre);
color: var(--porcelaine);
font-weight: 700;
line-height: 1.3;
text-decoration: none;
transition: background-color 0.15s ease;
}
.bouton:hover { background: var(--encre-survol); color: var(--porcelaine); }
.bouton:focus-visible { outline-offset: 4px; border-radius: var(--arrondi-pilule); }
.bouton-petit {
padding: 0.45rem 1rem;
font-size: 1rem;
}
/* Un bouton seul dans son paragraphe respire davantage */
p:has(> .bouton:only-child) { margin: 2rem 0; }
/* ---------- 6. En-tête et navigation ---------- */
.entete {
max-width: var(--largeur-entete);
margin: 0 auto;
padding: 1.25rem var(--gouttiere);
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 1rem 2rem;
width: 100%;
}
.logo {
display: inline-block;
padding: 0.05rem 0.55rem 0.15rem;
border: 2.5px solid var(--encre);
border-radius: var(--arrondi-petit);
font-family: var(--sans);
font-weight: 800;
font-size: 1.35rem;
line-height: 1.2;
letter-spacing: 0.01em;
text-decoration: none;
}
.nav ul {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.35rem 1.35rem;
font-size: 1rem;
}
.nav a:not(.bouton) {
text-decoration-color: transparent;
}
.nav a:not(.bouton):hover,
.nav a[aria-current="page"]:not(.bouton) {
text-decoration-color: var(--vert);
}
.nav a.bouton[aria-current="page"] {
background: var(--porcelaine);
color: var(--encre);
box-shadow: inset 0 0 0 2px var(--encre);
}
/* ---------- 7. Colonne de texte et titres ---------- */
.texte {
max-width: calc(var(--colonne) + 2 * var(--gouttiere));
margin: 0 auto;
padding: 2rem var(--gouttiere) 5rem;
}
h1, h2, h3 {
font-family: var(--serif);
font-weight: 400;
line-height: 1.15;
letter-spacing: -0.005em;
text-wrap: balance;
}
h1 {
font-size: clamp(2.35rem, 1.6rem + 3.6vw, 3.75rem);
margin: 1.5rem 0 1.5rem;
}
h2 {
font-size: clamp(1.6rem, 1.35rem + 1vw, 2rem);
margin: 4.5rem 0 1.25rem;
}
p { margin: 0 0 1.15rem; }
/* Le chapeau : paragraphe entièrement en italique juste sous le titre */
h1 + p:has(> em:only-child) {
font-size: 1.3rem;
line-height: 1.55;
color: var(--encre-douce);
}
.note {
margin-top: 2.5rem;
color: var(--encre-douce);
font-size: 1.0625rem;
}
.exergue {
margin: 2.25rem 0;
padding-left: 1.25rem;
border-left: 4px solid var(--orange);
font-family: var(--serif);
font-size: 1.45rem;
line-height: 1.4;
}
code {
font-size: 0.9em;
background: var(--porcelaine-fonce);
padding: 0.05em 0.3em;
border-radius: 3px;
}
/* ---------- 8. Illustration « tasses » ---------- */
.tasses {
margin: 1.5rem 0 0;
}
/* L'illustration déborde légèrement de la colonne sur grand écran */
@media (min-width: 48rem) {
.tasses { margin-inline: -4rem; }
}
.tasses svg { width: 100%; height: auto; overflow: visible; }
.table { fill: var(--chene); }
.table-fil,
.tasse-corps,
.tasse-anse,
.kluk-socle,
.vapeur path {
stroke: var(--encre);
stroke-width: 3;
stroke-linecap: round;
stroke-linejoin: round;
}
.tasse-anse { fill: none; stroke-width: 6; }
.tasse-corps { fill: currentColor; }
.tasse-reflet { fill: none; stroke: var(--blanc); stroke-opacity: 0.45; stroke-width: 4; stroke-linecap: round; }
.kluk-socle { fill: var(--chene-fonce); }
.kluk-voyant { fill: var(--porcelaine); }
.vapeur path { fill: none; stroke-width: 2.5; stroke: var(--encre-douce); }
.humeur-gris { color: var(--gris); }
.humeur-orange { color: var(--orange); }
.humeur-rouge { color: var(--rouge); }
.humeur-vert { color: var(--vert); }
@media (prefers-reduced-motion: no-preference) {
.vapeur path {
animation: vapeur 5s ease-in-out infinite;
}
.vapeur path + path { animation-delay: 2.5s; }
}
@keyframes vapeur {
0%, 100% { opacity: 0.25; transform: translateY(6px); }
50% { opacity: 0.9; transform: translateY(-6px); }
}
/* ---------- 9. Calendrier d'humeurs (décoratif) ---------- */
.calendrier {
display: grid;
grid-template-rows: repeat(7, auto);
grid-auto-flow: column;
grid-auto-columns: 1fr;
gap: 4px;
margin: 2.25rem 0 1rem;
padding: 1.25rem;
background: var(--surface);
border-radius: var(--arrondi);
box-shadow: inset 0 0 0 1px var(--porcelaine-fonce);
}
.jour {
aspect-ratio: 1;
border-radius: 2px;
}
.jour-g { background: var(--gris); }
.jour-o { background: var(--orange); }
.jour-v { background: var(--vert); }
.jour-r { background: var(--rouge); }
@media (max-width: 30rem) {
.calendrier { gap: 3px; padding: 0.9rem; }
}
/* ---------- 10. Photos ---------- */
.photo {
margin: 2rem 0;
}
.photo img {
width: 100%;
height: auto;
border-radius: var(--arrondi);
}
.photo figcaption {
margin-top: 0.6rem;
font-size: 0.95rem;
color: var(--encre-douce);
}
.photo-attente {
display: flex;
align-items: center;
gap: 1.25rem;
aspect-ratio: 3 / 2;
padding: 1.5rem;
border: 2px dashed var(--chene-fonce);
border-radius: var(--arrondi);
background: var(--porcelaine-fonce);
color: var(--encre-douce);
font-size: 1rem;
line-height: 1.5;
}
.photo-attente svg {
flex: none;
width: 2.75rem;
fill: none;
stroke: var(--chene-fonce);
stroke-width: 2.5;
stroke-linejoin: round;
stroke-linecap: round;
}
.photo-attente p { margin: 0; }
.photo-attente strong { color: var(--encre); }
/* ---------- 11. Formulaire ---------- */
.formulaire {
margin: 2.5rem 0 1rem;
padding: 1.75rem 1.25rem;
background: var(--surface);
border-radius: var(--arrondi);
box-shadow: inset 0 0 0 1px var(--porcelaine-fonce);
}
@media (min-width: 40rem) {
.formulaire { padding: 2.25rem 2.5rem; }
}
.champ {
margin: 0 0 1.75rem;
padding: 0;
border: 0;
min-width: 0;
}
.champ > label,
.champ > legend {
display: block;
padding: 0;
margin-bottom: 0.5rem;
font-weight: 700;
}
.precision {
font-weight: 400;
color: var(--encre-douce);
}
.formulaire input[type="email"],
.formulaire textarea {
display: block;
width: 100%;
padding: 0.7rem 0.85rem;
border: 2px solid var(--bordure-champ);
border-radius: var(--arrondi-petit);
background: var(--blanc);
color: var(--encre);
font: inherit;
line-height: 1.4;
}
.formulaire textarea { resize: vertical; min-height: 8rem; }
.formulaire input[type="email"]:focus-visible,
.formulaire textarea:focus-visible {
outline: 3px solid var(--vert);
outline-offset: 1px;
border-color: var(--encre);
}
.choix {
list-style: none;
margin: 0;
padding: 0;
}
.choix li {
display: flex;
align-items: flex-start;
gap: 0.75rem;
padding: 0.35rem 0;
}
.choix input {
flex: none;
width: 1.35rem;
height: 1.35rem;
margin: 0.2rem 0 0;
accent-color: var(--vert-lien);
cursor: pointer;
}
.choix label { cursor: pointer; }
/* Champ piège anti-robots, invisible */
.piege {
position: absolute;
left: -9999px;
width: 1px;
height: 1px;
overflow: hidden;
}
.formulaire-erreur {
padding: 0.75rem 1rem;
border-left: 4px solid var(--rouge);
background: var(--erreur-fond);
font-size: 1rem;
}
.formulaire .bouton {
border: 0;
font: inherit;
font-weight: 700;
cursor: pointer;
}
.formulaire .bouton:disabled { opacity: 0.6; cursor: progress; }
.formulaire-secours {
margin: 1rem 0 0;
font-size: 1rem;
color: var(--encre-douce);
}
/* ---------- 12. Pied de page : la table en bois ---------- */
.pied {
background: var(--chene);
border-top: 3px solid var(--encre);
font-size: 1rem;
}
.pied-interieur {
max-width: calc(var(--colonne) + 2 * var(--gouttiere));
margin: 0 auto;
padding: 2rem var(--gouttiere) 2.5rem;
}
.pied-phrase {
font-family: var(--serif);
font-size: 1.15rem;
margin: 0 0 1rem;
}
.pied-liens {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-wrap: wrap;
gap: 0.5rem 2rem;
}
.pied a { text-decoration-color: var(--encre); text-decoration-thickness: 1px; }
.pied a:hover { text-decoration-thickness: 2px; }
/* ---------- 13. Impression ---------- */
@media print {
.entete .nav, .pied, .tasses, .calendrier { display: none; }
body { background: var(--blanc); }
}

View file

@ -1,31 +1,8 @@
/* /*
* This is a manifest file that'll be compiled into application.css. * Styles propres à l'application (tableau de bord, journal).
* * La charte graphique est dans charte.css, les raccords avec Bulma dans _charte-bulma.scss.
* With Propshaft, assets are served efficiently without preprocessing steps. You can still include
* application-wide styles in this file, but keep in mind that CSS precedence will follow the standard
* cascading order, meaning styles declared later in the document or manifest will override earlier ones,
* depending on specificity.
*
* Consider organizing styles into separate files for maintainability.
*/ */
body {
margin: 0px;
font-family: "Sour Gummy", sans-serif;
font-weight: 350;
font-style: normal;
}
main {
height: 100vh;
}
#navbar-top {
position: sticky;
top: 0;
}
.tracker { .tracker {
align-content: center; align-content: center;
} }
@ -36,9 +13,9 @@ main {
} }
.current-day { .current-day {
background-color: white; background-color: var(--porcelaine);
position: sticky; position: sticky;
top: 64px; top: 0;
align-content: center; align-content: center;
} }
@ -71,14 +48,14 @@ main {
.selected-day { .selected-day {
border-radius: 50%; border-radius: 50%;
box-shadow: 0 0 0 3px white, 0 0 0 5px black; box-shadow: 0 0 0 3px var(--porcelaine), 0 0 0 5px var(--encre);
margin: 4px; margin: 4px;
width: 15px; width: 15px;
height: 15px; height: 15px;
} }
.empty-day { .empty-day {
border: 1px dashed #aaa; border: 1px dashed var(--bordure-champ);
margin: 4px; margin: 4px;
width: 15px; width: 15px;
height: 15px; height: 15px;

View file

@ -0,0 +1,7 @@
# Page de démonstration des composants de la charte graphique (développement seulement).
class CharteController < ApplicationController
allow_unauthenticated_access
def show
end
end

View file

@ -1,2 +1,8 @@
module ApplicationHelper module ApplicationHelper
# Lien de navigation de l'en-tête : marqué aria-current="page" sur la page courante,
# ce qui déclenche le soulignement vert (ou l'inversion du bouton) de la charte.
def lien_nav(nom, chemin, **options)
options[:"aria-current"] = "page" if current_page?(chemin)
link_to nom, chemin, **options
end
end end

View file

@ -0,0 +1,13 @@
<header class="entete">
<%= link_to "kluk", root_path, class: "logo", "aria-label": "Kluk, retour à l'accueil" %>
<nav class="nav" aria-label="Navigation principale">
<ul>
<% if authenticated? %>
<li><%= lien_nav "Mon tableau de bord", dashboard_path %></li>
<li><%= button_to "Se déconnecter", session_path, method: :delete, class: "bouton bouton-petit" %></li>
<% else %>
<li><%= lien_nav "Se connecter", new_session_path, class: "bouton bouton-petit" %></li>
<% end %>
</ul>
</nav>
</header>

View file

@ -0,0 +1,9 @@
<footer class="pied">
<div class="pied-interieur">
<p class="pied-phrase">Comment ça Kluk aujourd’hui&#8239;?</p>
<ul class="pied-liens">
<li><%= link_to "Découvrir la Kluk", "https://kluk.fr/" %></li>
<li><%= link_to "Mentions légales", "https://kluk.fr/mentions-legales/" %></li>
</ul>
</div>
</footer>

View file

@ -0,0 +1,5 @@
<%# Bouton de la charte (pilule encre). Pour un bouton d'envoi : f.submit "…", class: "bouton".
render "charte/bouton", texte: "Je veux ma Kluk", href: dashboard_path
render "charte/bouton", texte: "Voir", href: "#", petit: true, data: { turbo_frame: "x" } %>
<%# locals: (texte:, href:, petit: false, **attributs) -%>
<%= link_to texte, href, class: ["bouton", ("bouton-petit" if petit)], **attributs %>

View file

@ -0,0 +1,10 @@
<%# Calendrier d'humeurs décoratif : une case par jour, colonnes de 7 jours (une semaine par colonne).
jours : suite de lettres g (gris), o (orange), v (vert), r (rouge), en chaîne ou en tableau.
render "charte/calendrier", jours: "vvovvvg vvvvovv ovvrvvv" %>
<%# locals: (jours:) -%>
<% jours = jours.is_a?(String) ? jours.delete(" ").chars : Array(jours) %>
<div class="calendrier" aria-hidden="true">
<% jours.each do |jour| %>
<span class="jour jour-<%= jour %>"></span>
<% end %>
</div>

View file

@ -0,0 +1,12 @@
<%# Champ de formulaire de la charte (dans un form de classe "formulaire") :
libellé en gras au-dessus, précision facultative en encre douce. Le bloc donne la saisie.
render("charte/champ", form: f, attribut: :email_address, libelle: "Votre adresse mail", precision: "(obligatoire)") { f.email_field :email_address, required: true }
(en ERB, avec « do » / « end » et la saisie dans le bloc) %>
<%# locals: (form:, attribut:, libelle:, precision: nil) -%>
<div class="champ">
<%= form.label attribut do %>
<%= libelle %>
<% if precision %><span class="precision"><%= precision %></span><% end %>
<% end %>
<%= yield %>
</div>

View file

@ -0,0 +1,4 @@
<%# Phrase mise en valeur, en serif, avec un trait orange.
render "charte/exergue", texte: "Une phrase à mettre en valeur." %>
<%# locals: (texte:) -%>
<%= tag.p texte, class: "exergue" %>

View file

@ -0,0 +1,8 @@
<%# Message d'erreur de formulaire (trait rouge sur fond rosé). Rien n'est affiché sans message.
render "charte/formulaire_erreur", messages: @day_log.errors.full_messages
render "charte/formulaire_erreur", messages: flash[:alert] %>
<%# locals: (messages:) -%>
<% messages = Array(messages).compact_blank %>
<% if messages.any? %>
<p class="formulaire-erreur" role="alert"><%= safe_join(messages, tag.br) %></p>
<% end %>

View file

@ -0,0 +1,4 @@
<%# Précision discrète, en fin de page.
render "charte/note", texte: "Une précision." %>
<%# locals: (texte:) -%>
<%= tag.p texte, class: "note" %>

View file

@ -0,0 +1,10 @@
<%# Photo arrondie avec légende facultative. Toujours un alt descriptif, et width/height
pour éviter les sauts de mise en page (format du site : 1200 × 900).
render "charte/photo", src: "croisiere.jpg", alt: "…", width: 1200, height: 900, legende: "…" %>
<%# locals: (src:, alt:, width: nil, height: nil, legende: nil) -%>
<figure class="photo">
<%= image_tag src, alt: alt, width: width, height: height, loading: "lazy", decoding: "async" %>
<% if legende %>
<figcaption><%= legende %></figcaption>
<% end %>
</figure>

View file

@ -0,0 +1,12 @@
<%# Emplacement de photo, en attendant l'image : cadre 3:2 en pointillés chêne.
render "charte/photo_attente", description: "La Kluk posée sur la table du petit-déjeuner." %>
<%# locals: (description:, titre: nil, legende: nil) -%>
<figure class="photo">
<div class="photo-attente">
<svg viewBox="0 0 40 32" aria-hidden="true" focusable="false"><path d="M3 3 L6 25 Q6.5 29 10.5 29 L24.5 29 Q28.5 29 29 25 L32 3 Z M31.5 8 C39 8 39.5 21 30 22" /></svg>
<p><strong><%= titre || "Photo à venir" %></strong><br><%= description %></p>
</div>
<% if legende %>
<figcaption><%= legende %></figcaption>
<% end %>
</figure>

View file

@ -0,0 +1,30 @@
<%# Illustration « table et tasses », en SVG dans la page pour suivre les variables CSS.
Une seule par page (identifiants #tasse et #tasses-titre).
render "charte/tasses" %>
<%# locals: () -%>
<figure class="tasses">
<svg viewBox="0 0 580 214" role="img" aria-labelledby="tasses-titre">
<title id="tasses-titre">Illustration : trois tasses de couleur alignées sur la table, et une quatrième posée sur la Kluk, fumante.</title>
<defs>
<g id="tasse">
<path class="tasse-anse" d="M63 22 C86 20 88 60 60 62" />
<path class="tasse-corps" d="M3 6 L10 78 Q11 86 20 86 L48 86 Q57 86 58 78 L65 6 Z" />
<path class="tasse-reflet" d="M13 18 L17 62" />
</g>
</defs>
<rect class="table" x="0" y="200" width="580" height="14" rx="3" />
<path class="table-fil" d="M0 200 H580" />
<use href="#tasse" x="34" y="114" class="humeur-gris" />
<use href="#tasse" x="132" y="114" class="humeur-orange" />
<use href="#tasse" x="230" y="114" class="humeur-rouge" />
<g class="kluk-illu" transform="translate(410 98)">
<g class="vapeur" aria-hidden="true">
<path d="M22 -6 c-9 -11 9 -20 0 -31 c-9 -11 9 -20 0 -31" />
<path d="M44 -10 c-9 -11 9 -20 0 -31 c-9 -11 9 -20 0 -31" />
</g>
<use href="#tasse" x="0" y="0" class="humeur-vert" />
<rect class="kluk-socle" x="-22" y="86" width="112" height="16" rx="8" />
<circle class="kluk-voyant" cx="34" cy="94" r="2.6" />
</g>
</svg>
</figure>

View file

@ -0,0 +1,47 @@
<% content_for :title, "Charte Kluk" %>
<article class="texte">
<h1>Charte graphique Kluk</h1>
<p><em>Les composants de la charte, rendus par les partials de app/views/charte.</em></p>
<p>Texte courant en Atkinson Hyperlegible Next, avec <a href="#">un lien souligné de vert</a>, du <strong>gras</strong>, de l’<em>italique</em> et du <code>code</code>.</p>
<p><%= render "charte/bouton", texte: "Bouton principal", href: "#" %></p>
<p><%= render "charte/bouton", texte: "Petit bouton", href: "#", petit: true %></p>
<%= render "charte/tasses" %>
<h2>Intertitre de section</h2>
<p>Les intertitres ménagent un grand espace au-dessus d’eux pour découper la page en respirations.</p>
<%= render "charte/exergue", texte: "Une phrase mise en exergue, en serif, soulignée d’un trait orange." %>
<%= render "charte/photo", src: "croisiere.jpg", alt: "Photo d’exemple", legende: "Légende facultative sous la photo." %>
<%= render "charte/photo_attente", description: "Description de la photo attendue." %>
<%= render "charte/calendrier", jours: "vvovvvg vvvvovv ovvrvvv vvgvvvo ovvvvvv rrovrrr rorrvvo gvggggg gggvggg gogggvg ggoggvg vvgvvvv ovvvovv vrvvvov vvvvovv ovvgvvv vvovvrv vvvovvv vgvvvvo vvvvovv" %>
<%= form_with url: "#", class: "formulaire", data: { turbo: false } do |f| %>
<%= render "charte/champ", form: f, attribut: :email, libelle: "Votre adresse mail", precision: "(obligatoire)" do %>
<%= f.email_field :email, autocomplete: "email" %>
<% end %>
<%= render "charte/champ", form: f, attribut: :mot_de_passe, libelle: "Mot de passe" do %>
<%= f.password_field :mot_de_passe %>
<% end %>
<%= render "charte/champ", form: f, attribut: :message, libelle: "Un mot, une idée, une question ?", precision: "(facultatif)" do %>
<%= f.text_area :message, rows: 5 %>
<% end %>
<%= render "charte/formulaire_erreur", messages: ["Message d’erreur du formulaire."] %>
<%= f.submit "Envoyer", class: "bouton" %>
<% end %>
<h2>Bulma aux couleurs de la charte</h2>
<div class="buttons">
<a class="button is-primary" href="#">is-primary</a>
<a class="button is-link" href="#">is-link</a>
<a class="button is-light" href="#">is-light</a>
</div>
<p class="title is-4">Titre Bulma</p>
<div class="notification is-danger">Notification is-danger</div>
<%= render "charte/note", texte: "Une note discrète en fin de page." %>
</article>

View file

@ -1,4 +1,4 @@
%main.columns.m-auto .columns.m-auto
.current-day.column.is-hidden-mobile .current-day.column.is-hidden-mobile
%section.m-4 %section.m-4
%figure.image.has-ratio %figure.image.has-ratio

View file

@ -1 +1,2 @@
%h1 Comment ça KLUK ? %article.texte
%h1 Comment ça KLUK ?

View file

@ -1,24 +1,26 @@
<div> <article class="texte">
<h1>Set up your password</h1> <div>
<p>Please set your password to activate your account.</p> <h1>Set up your password</h1>
<p>Please set your password to activate your account.</p>
<%= form_with model: @user, url: invitation_path(token: params[:token]), method: :patch do |form| %> <%= form_with model: @user, url: invitation_path(token: params[:token]), method: :patch do |form| %>
<div> <div>
<%= form.password_field :password, <%= form.password_field :password,
required: true, required: true,
autocomplete: "new-password", autocomplete: "new-password",
placeholder: "Enter new password" %> placeholder: "Enter new password" %>
</div> </div>
<div> <div>
<%= form.password_field :password_confirmation, <%= form.password_field :password_confirmation,
required: true, required: true,
autocomplete: "new-password", autocomplete: "new-password",
placeholder: "Repeat new password" %> placeholder: "Repeat new password" %>
</div> </div>
<div> <div>
<%= form.submit "Activate Account" %> <%= form.submit "Activate Account" %>
</div> </div>
<% end %> <% end %>
</div> </div>
</article>

View file

@ -1,9 +1,10 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html lang="fr">
<head> <head>
<title><%= content_for(:title) || "Moodie" %></title> <title><%= content_for(:title) || "Kluk" %></title>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1"> <meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="theme-color" content="#F5F6F0">
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes"> <meta name="mobile-web-app-capable" content="yes">
<%= csrf_meta_tags %> <%= csrf_meta_tags %>
@ -14,13 +15,11 @@
<%# Enable PWA manifest for installable apps (make sure to enable in config/routes.rb too!) %> <%# Enable PWA manifest for installable apps (make sure to enable in config/routes.rb too!) %>
<%#= tag.link rel: "manifest", href: pwa_manifest_path(format: :json) %> <%#= tag.link rel: "manifest", href: pwa_manifest_path(format: :json) %>
<link rel="icon" href="/icon.png" type="image/png"> <%= favicon_link_tag "charte/favicon.svg", type: "image/svg+xml" %>
<link rel="icon" href="/icon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/icon.png"> <link rel="apple-touch-icon" href="/icon.png">
<link rel="preconnect" href="https://fonts.googleapis.com"> <%= preload_link_tag "young-serif-400.woff2" %>
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <%= preload_link_tag "atkinson-hyperlegible-next-400.woff2" %>
<link href="https://fonts.googleapis.com/css2?family=Delius&family=Sour+Gummy:ital,wght@0,100..900;1,100..900&display=swap" rel="stylesheet">
<script src="https://kit.fontawesome.com/079b06296f.js" crossorigin="anonymous"></script> <script src="https://kit.fontawesome.com/079b06296f.js" crossorigin="anonymous"></script>
@ -30,7 +29,11 @@
</head> </head>
<body> <body>
<%= render 'menu' %> <a class="evitement" href="#contenu">Aller au contenu</a>
<%= yield %> <%= render "entete" %>
<main id="contenu" tabindex="-1">
<%= yield %>
</main>
<%= render "pied" %>
</body> </body>
</html> </html>

View file

@ -1,4 +1,4 @@
<main data-controller="mood" class="columns m-auto"> <div data-controller="mood" class="columns m-auto">
<div class="current-day column is-hidden-mobile"> <div class="current-day column is-hidden-mobile">
<section class="m-4"> <section class="m-4">
<figure class="image has-ratio"> <figure class="image has-ratio">
@ -60,4 +60,4 @@
</div> </div>
</div> </div>
</section> </section>
</main> </div>

View file

@ -1,9 +1,11 @@
<h1>Update your password</h1> <article class="texte">
<h1>Update your password</h1>
<%= tag.div(flash[:alert], style: "color:red") if flash[:alert] %> <%= tag.div(flash[:alert], style: "color:red") if flash[:alert] %>
<%= form_with url: password_path(params[:token]), method: :put do |form| %> <%= form_with url: password_path(params[:token]), method: :put do |form| %>
<%= form.password_field :password, required: true, autocomplete: "new-password", placeholder: "Enter new password", maxlength: 72 %><br> <%= form.password_field :password, required: true, autocomplete: "new-password", placeholder: "Enter new password", maxlength: 72 %><br>
<%= form.password_field :password_confirmation, required: true, autocomplete: "new-password", placeholder: "Repeat new password", maxlength: 72 %><br> <%= form.password_field :password_confirmation, required: true, autocomplete: "new-password", placeholder: "Repeat new password", maxlength: 72 %><br>
<%= form.submit "Save" %> <%= form.submit "Save" %>
<% end %> <% end %>
</article>

View file

@ -1,8 +1,10 @@
<h1>Forgot your password?</h1> <article class="texte">
<h1>Forgot your password?</h1>
<%= tag.div(flash[:alert], style: "color:red") if flash[:alert] %> <%= tag.div(flash[:alert], style: "color:red") if flash[:alert] %>
<%= form_with url: passwords_path do |form| %> <%= form_with url: passwords_path do |form| %>
<%= form.email_field :email_address, required: true, autofocus: true, autocomplete: "username", placeholder: "Enter your email address", value: params[:email_address] %><br> <%= form.email_field :email_address, required: true, autofocus: true, autocomplete: "username", placeholder: "Enter your email address", value: params[:email_address] %><br>
<%= form.submit "Email reset instructions" %> <%= form.submit "Email reset instructions" %>
<% end %> <% end %>
</article>

View file

@ -1,4 +1,4 @@
%main.columns.m-auto{"data-controller" => "mood"} .columns.m-auto{"data-controller" => "mood"}
.column.is-hidden-mobile .column.is-hidden-mobile
%section.m-4 %section.m-4
%figure.image.has-ratio %figure.image.has-ratio

389
charte-graphique.md Normal file
View file

@ -0,0 +1,389 @@
# Charte graphique Kluk
Charte extraite du site kluk.fr (`assets/css/style.scss`, `_layouts/`, `_includes/`).
Tout ce qu'il faut pour la réutiliser ailleurs se trouve dans le dossier [`charte/`](charte/) :
```
charte/
├── css/kluk.css Feuille de style complète, en CSS pur (sans Sass)
├── fonts/ Young Serif, Atkinson Hyperlegible Next (woff2) + licence OFL
├── img/
│ ├── favicon.svg Icône du site (tasse verte)
│ ├── illustration-tasses.svg Illustration « table et tasses », autonome (styles intégrés)
│ ├── tasse-{gris,orange,vert,rouge}.svg Une tasse seule, dans chaque couleur d'humeur
│ └── photo-attente.svg Pictogramme de l'emplacement de photo
└── exemple.html Page de démonstration de tous les composants
```
Pour démarrer : copier `charte/` dans le projet, puis dans le `<head>` :
```html
<meta name="theme-color" content="#F5F6F0">
<link rel="preload" href="charte/fonts/young-serif-400.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="charte/fonts/atkinson-hyperlegible-next-400.woff2" as="font" type="font/woff2" crossorigin>
<link rel="stylesheet" href="charte/css/kluk.css">
<link rel="icon" href="charte/img/favicon.svg" type="image/svg+xml">
```
Les chemins des polices dans `kluk.css` sont relatifs (`../fonts/`) : garder la structure `css/` + `fonts/` côte à côte.
---
## 1. Esprit
Un univers de petit-déjeuner, calme et chaleureux : **la porcelaine du matin** (fond), **l'encre du thé** (texte), **le chêne de la table** (pied de page, illustration), et **les quatre couleurs des tasses** (gris, orange, vert, rouge) comme accents, qui sont aussi les quatre « humeurs ».
Principes :
- une seule colonne de lecture étroite (36 rem), beaucoup d'air ;
- des titres en serif ronde et amicale, un texte courant très lisible ;
- des formes arrondies (boutons en pilule, cartes arrondies), des traits d'encre épais façon dessin ;
- l'accessibilité d'abord : contrastes élevés, focus très visible, lien d'évitement, animations coupées si l'utilisateur le demande.
---
## 2. Couleurs
Toutes les couleurs sont des variables CSS déclarées sur `:root`.
### Fonds et texte
| Variable | Valeur | Rôle |
|---|---|---|
| `--porcelaine` | `#F5F6F0` | Fond de page, texte sur fond encre, `theme-color` |
| `--porcelaine-fonce` | `#E9ECE2` | Fond de `code`, emplacement de photo, liseré des cartes |
| `--encre` | `#2E2A26` | Texte, bordures, fond des boutons |
| `--encre-douce` | `#5A534C` | Texte secondaire : chapeau, notes, légendes, précisions |
| `--chene` | `#D9BC94` | Pied de page, plateau de la table (illustration) |
| `--chene-fonce` | `#B48D5F` | Socle de la Kluk, pointillés et picto de l'emplacement photo |
### Humeurs (accents)
| Variable | Valeur | Rôle |
|---|---|---|
| `--gris` | `#8D97A8` | Humeur grise |
| `--orange` | `#EDA33D` | Humeur orange, trait de l'exergue |
| `--vert` | `#5F9B59` | Humeur verte, soulignement des liens, focus des champs, favicon |
| `--rouge` | `#D8573F` | Humeur rouge, trait des messages d'erreur |
| `--vert-lien` | `#3F7A3B` | Vert foncé pour les cases à cocher (`accent-color`) |
### Teintes utilitaires
Écrites en dur dans le site d'origine ; `kluk.css` les expose en variables, avec les mêmes valeurs.
| Variable | Valeur | Rôle |
|---|---|---|
| `--encre-survol` | `#45403A` | Fond des boutons au survol |
| `--surface` | `#FBFCF8` | Fond des cartes (formulaire, calendrier) |
| `--bordure-champ` | `#8A8279` | Bordure des champs de saisie |
| `--erreur-fond` | `#FBE9E5` | Fond des messages d'erreur |
| `--blanc` | `#fff` | Fond des champs, reflet des tasses, fond à l'impression |
### Contrastes (WCAG)
| Couple | Ratio | Usage autorisé |
|---|---|---|
| encre / porcelaine | 13,1 : 1 | tout texte |
| encre-douce / porcelaine | 7,0 : 1 | tout texte |
| encre / chêne | 7,8 : 1 | tout texte (pied de page) |
| porcelaine / encre-survol | 9,4 : 1 | texte des boutons au survol |
| vert-lien / porcelaine | 4,8 : 1 | texte possible |
| vert / porcelaine | 3,1 : 1 | éléments graphiques seulement (soulignés, contours) |
| rouge / porcelaine | 3,6 : 1 | éléments graphiques seulement |
| gris / porcelaine | 2,7 : 1 | décoratif seulement |
| orange / porcelaine | 2,0 : 1 | décoratif seulement |
Règle : **le texte est toujours en encre ou encre douce**. Les couleurs d'humeur ne servent qu'aux traits, soulignés, pastilles et illustrations, jamais au texte.
---
## 3. Typographies
| Variable | Pile | Usage |
|---|---|---|
| `--serif` | `"Young Serif", Georgia, serif` | Titres `h1`–`h3`, exergue, phrase du pied de page |
| `--sans` | `"Atkinson Hyperlegible Next", "Atkinson Hyperlegible", system-ui, sans-serif` | Tout le reste, y compris le logo |
Fichiers fournis (`charte/fonts/`, licence SIL OFL 1.1 dans `OFL.txt`) :
- `young-serif-400.woff2` : Young Serif, une seule graisse (400) ;
- `atkinson-hyperlegible-next-400.woff2` : Atkinson Hyperlegible Next **variable**, graisses 200 à 800 ;
- `atkinson-hyperlegible-next-400-italic.woff2` : italique 400.
Graisses utilisées : 400 (texte), 700 (boutons, libellés, `strong`), 800 (logo). Les titres restent en 400 : c'est la serif qui porte le caractère.
### Échelle
| Élément | Taille | Interlignage | Remarques |
|---|---|---|---|
| Corps (`body`) | `1.1875rem` (19 px) | 1.65 | `font-kerning: normal` |
| `h1` | `clamp(2.35rem, 1.6rem + 3.6vw, 3.75rem)` | 1.15 | serif, `text-wrap: balance` |
| `h2` | `clamp(1.6rem, 1.35rem + 1vw, 2rem)` | 1.15 | serif |
| Chapeau | `1.3rem` | 1.55 | italique, encre douce |
| Exergue | `1.45rem` | 1.4 | serif |
| Logo | `1.35rem` | 1.2 | sans 800, `letter-spacing: 0.01em` |
| Phrase du pied | `1.15rem` | — | serif |
| Note | `1.0625rem` | — | encre douce |
| Navigation, petit bouton, pied, formulaire (erreur, secours), emplacement photo | `1rem` | — | |
| Légende de photo | `0.95rem` | — | encre douce |
| `code` | `0.9em` | — | |
Titres : `letter-spacing: -0.005em`.
### Typographie française
Le site applique automatiquement (plugin Jekyll `_plugins/typographie.rb`) une espace fine insécable avant `; ! ?` et une espace insécable avant `:` et `»` et après `«`. Dans un autre projet, taper ces espaces directement (`&#8239;` fine, `&nbsp;` insécable) ou reprendre ce plugin. Apostrophe typographique `’` et guillemets `« »`.
---
## 4. Espacements et mise en page
| Variable | Valeur | Rôle |
|---|---|---|
| `--colonne` | `36rem` | Largeur de la colonne de texte (~ 65 caractères) |
| `--gouttiere` | `1.25rem` | Marge latérale sur mobile, padding horizontal des conteneurs |
| `--largeur-entete` | `64rem` | Largeur maximale de l'en-tête |
| `--arrondi-petit` | `0.5rem` | Logo, champs de saisie |
| `--arrondi` | `0.75rem` | Photos, cartes (formulaire, calendrier, emplacement photo) |
| `--arrondi-pilule` | `999px` | Boutons |
Rythme vertical (marges) :
| Élément | Marge |
|---|---|
| Paragraphe | `0 0 1.15rem` |
| `h1` | `1.5rem 0` |
| `h2` | `4.5rem 0 1.25rem` (grande respiration avant chaque section) |
| Paragraphe contenant seulement un bouton | `2rem 0` |
| Photo | `2rem 0` |
| Exergue, calendrier | `2.25rem 0` (calendrier : `2.25rem 0 1rem`) |
| Formulaire | `2.5rem 0 1rem` |
| Note | `margin-top: 2.5rem` |
| Colonne `.texte` | padding `2rem` en haut, `5rem` en bas |
Points de rupture : `30rem` (calendrier resserré), `40rem` (formulaire plus aéré), `48rem` (l'illustration déborde de 4 rem de chaque côté de la colonne).
Structure de page : `body` en flex colonne avec `min-height: 100vh`, `main` en `flex: 1`, pour que le pied de page reste en bas.
Traits : encre, 2 à 3 px (boutons actifs 2 px, logo 2,5 px, pied de page 3 px, illustration 3 px, anses 6 px). Trait latéral de 4 px pour l'exergue (orange) et les erreurs (rouge).
---
## 5. Composants
La page [`charte/exemple.html`](charte/exemple.html) les montre tous.
### Squelette de page
```html
<body>
<a class="evitement" href="#contenu">Aller au contenu</a>
<header class="entete">…</header>
<main id="contenu" tabindex="-1">
<article class="texte">
…contenu…
</article>
</main>
<footer class="pied">…</footer>
</body>
```
`.evitement` est caché au-dessus de l'écran et n'apparaît qu'au focus clavier.
### En-tête, logo et navigation
```html
<header class="entete">
<a class="logo" href="/" aria-label="Kluk, retour à l'accueil">kluk</a>
<nav class="nav" aria-label="Navigation principale">
<ul>
<li><a href="/" aria-current="page">Accueil</a></li>
<li><a href="/histoire/">Comment est née l'idée</a></li>
<li><a href="/je-veux-ma-kluk/" class="bouton bouton-petit">Je veux ma Kluk</a></li>
</ul>
</nav>
</header>
```
- Le logo est un mot-symbole en minuscules, en Atkinson Hyperlegible Next graisse 800, encadré d'un trait d'encre arrondi.
- Les liens de navigation ne sont pas soulignés ; le soulignement vert apparaît au survol et sur la page courante (`aria-current="page"`).
- Une seule entrée de navigation est mise en valeur en bouton. Quand c'est la page courante, le bouton s'inverse (fond porcelaine, contour encre).
### Liens
Texte en encre, soulignement vert épais (`0.12em`, décalé de `0.2em`) qui passe à l'encre au survol. Dans le pied de page, soulignement encre fin (1 px, 2 px au survol).
### Focus
Contour encre de 3 px, décalé de 3 px, coins arrondis à 4 px, sur tous les éléments (`:focus-visible`). Les champs de formulaire utilisent un contour vert.
### Bouton
```html
<p><a class="bouton" href="/je-veux-ma-kluk/">Je veux ma Kluk</a></p>
<a class="bouton bouton-petit" href="…">Petit bouton</a>
<button class="bouton" type="submit">Envoyer</button>
```
Pilule encre, texte porcelaine en 700. Un bouton seul dans son paragraphe prend une marge de 2 rem. Dans un `.formulaire`, un `<button class="bouton">` perd sa bordure native et passe à `opacity: 0.6` quand il est `disabled`.
### Titres, chapeau, texte
```html
<h1>Titre de la page</h1>
<p><em>Le chapeau : une phrase d'accroche en italique.</em></p>
<p>Texte courant…</p>
<h2>Intertitre</h2>
```
Le chapeau n'a pas de classe : c'est **le paragraphe qui suit directement le `h1` et ne contient qu'un `<em>`** (sélecteur `h1 + p:has(> em:only-child)`).
### Exergue
```html
<p class="exergue">Une phrase à mettre en valeur.</p>
```
### Note
```html
<p class="note">Une précision discrète, en fin de page.</p>
```
### Code en ligne
```html
<code>exemple</code>
```
### Photo
```html
<figure class="photo">
<img src="photo.jpg" alt="Description de la photo" width="1200" height="900" loading="lazy" decoding="async">
<figcaption>Légende facultative.</figcaption>
</figure>
```
Toujours un `alt` descriptif, et `width`/`height` pour éviter les sauts de mise en page. Format des photos du site : 1200 × 900 (4:3) ; image de partage : 1200 × 630.
### Emplacement de photo (en attendant l'image)
```html
<figure class="photo">
<div class="photo-attente">
<svg viewBox="0 0 40 32" aria-hidden="true" focusable="false"><path d="M3 3 L6 25 Q6.5 29 10.5 29 L24.5 29 Q28.5 29 29 25 L32 3 Z M31.5 8 C39 8 39.5 21 30 22" /></svg>
<p><strong>Photo à venir</strong><br>Description de la photo attendue.</p>
</div>
</figure>
```
Cadre 3:2 en pointillés chêne foncé. Le pictogramme est aussi fourni seul : `img/photo-attente.svg`.
### Illustration « table et tasses »
Deux façons de l'utiliser :
1. **En image**, le plus simple (couleurs et animation intégrées au fichier) :
```html
<figure class="tasses">
<img src="charte/img/illustration-tasses.svg" alt="Trois tasses de couleur alignées sur la table, et une quatrième posée sur la Kluk, fumante.">
</figure>
```
2. **En SVG dans la page**, pour qu'elle suive les variables CSS : copier le bloc `<figure class="tasses">` de `charte/exemple.html`. La couleur de chaque tasse vient de sa classe d'humeur (`humeur-gris`, `humeur-orange`, `humeur-rouge`, `humeur-vert`) via `currentColor`. On peut donc réutiliser le symbole `#tasse` :
```html
<use href="#tasse" x="34" y="114" class="humeur-orange" />
```
La vapeur ondule doucement (5 s, deux volutes décalées de 2,5 s), seulement si `prefers-reduced-motion` ne le refuse pas. À partir de 48 rem, la figure déborde de 4 rem de part et d'autre de la colonne.
Tasses seules : `img/tasse-gris.svg`, `tasse-orange.svg`, `tasse-vert.svg`, `tasse-rouge.svg`.
### Calendrier d'humeurs (décoratif)
```html
<div class="calendrier" aria-hidden="true">
<span class="jour jour-v"></span>
<span class="jour jour-o"></span>
<span class="jour jour-g"></span>
<span class="jour jour-r"></span>
<!-- … une case par jour ; la grille remplit des colonnes de 7 jours (une semaine par colonne) -->
</div>
```
`jour-g` gris, `jour-o` orange, `jour-v` vert, `jour-r` rouge. Carte de fond `--surface` avec liseré intérieur `--porcelaine-fonce`.
### Formulaire
```html
<form class="formulaire">
<div class="champ">
<label for="champ-email">Votre adresse mail <span class="precision">(obligatoire)</span></label>
<input id="champ-email" name="Email" type="email" required autocomplete="email">
</div>
<fieldset class="champ">
<legend>Ce que vous aimeriez faire <span class="precision">(cochez tout ce qui vous ressemble)</span></legend>
<ul class="choix">
<li>
<input id="choix-1" type="checkbox" name="choix[]" value="1">
<label for="choix-1">Premier choix</label>
</li>
</ul>
</fieldset>
<div class="champ">
<label for="champ-message">Un mot, une idée, une question ? <span class="precision">(facultatif)</span></label>
<textarea id="champ-message" name="Message" rows="5"></textarea>
</div>
<!-- Champ piège anti-robots : invisible, laissé vide par les humains -->
<div class="piege" aria-hidden="true">
<label for="champ-site">Ne pas remplir</label>
<input id="champ-site" name="_site" type="text" tabindex="-1" autocomplete="off">
</div>
<p class="formulaire-erreur" role="alert" hidden></p>
<button class="bouton" type="submit">Envoyer</button>
<noscript><p class="formulaire-secours">Ce formulaire a besoin de JavaScript.</p></noscript>
</form>
```
- Libellés en 700 au-dessus des champs, précisions en 400 encre douce entre parenthèses.
- Champs : fond blanc, bordure 2 px `--bordure-champ`, arrondi 0,5 rem ; au focus, contour vert 3 px et bordure encre.
- Les styles ne visent que `input[type="email"]` et `textarea` : ajouter les autres types au sélecteur si besoin.
- Cases à cocher agrandies (1,35 rem), teintées `--vert-lien`.
- Message d'erreur : trait rouge à gauche sur fond rosé.
### Pied de page
```html
<footer class="pied">
<div class="pied-interieur">
<p class="pied-phrase">Une petite phrase facultative.</p>
<ul class="pied-liens">
<li><a href="/je-veux-ma-kluk/">Je veux ma Kluk</a></li>
<li><a href="/mentions-legales/">Mentions légales</a></li>
</ul>
</div>
</footer>
```
C'est « la table en bois » : fond chêne, trait d'encre de 3 px en haut, comme le plateau de l'illustration.
---
## 6. Impression
La navigation, le pied de page, l'illustration et le calendrier sont masqués ; le fond devient blanc.
## 7. Icône
`img/favicon.svg` : une tasse verte (`--vert`) cernée d'encre. `theme-color` : `#F5F6F0`.

528
charte/css/kluk.css Normal file
View file

@ -0,0 +1,528 @@
/*
* Kluk — charte graphique, feuille de style réutilisable.
* Extraite de assets/css/style.scss (site kluk.fr), en CSS pur, sans Sass.
*
* Palette : la porcelaine du matin, l'encre du thé, le chêne de la table,
* et les quatre couleurs des tasses en accent.
*
* Sommaire
* 1. Polices
* 2. Variables (couleurs, typographies, mise en page)
* 3. Base
* 4. Liens, focus, lien d'évitement
* 5. Boutons
* 6. En-tête et navigation
* 7. Colonne de texte et titres
* 8. Illustration « tasses »
* 9. Calendrier d'humeurs
* 10. Photos
* 11. Formulaire
* 12. Pied de page
* 13. Impression
*/
/* ---------- 1. Polices ---------- */
@font-face {
font-family: "Young Serif";
src: url("../fonts/young-serif-400.woff2") format("woff2");
font-weight: 400;
font-style: normal;
font-display: swap;
}
/* Fichier variable : couvre toutes les graisses de 200 à 800 */
@font-face {
font-family: "Atkinson Hyperlegible Next";
src: url("../fonts/atkinson-hyperlegible-next-400.woff2") format("woff2");
font-weight: 200 800;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Atkinson Hyperlegible Next";
src: url("../fonts/atkinson-hyperlegible-next-400-italic.woff2") format("woff2");
font-weight: 400;
font-style: italic;
font-display: swap;
}
/* ---------- 2. Variables ---------- */
:root {
/* Fonds et texte */
--porcelaine: #F5F6F0;
--porcelaine-fonce: #E9ECE2;
--encre: #2E2A26;
--encre-douce: #5A534C;
--chene: #D9BC94;
--chene-fonce: #B48D5F;
/* Humeurs (les quatre tasses) */
--gris: #8D97A8;
--orange: #EDA33D;
--vert: #5F9B59;
--rouge: #D8573F;
--vert-lien: #3F7A3B;
/* Teintes utilitaires (écrites en dur dans le site d'origine) */
--encre-survol: #45403A;
--surface: #FBFCF8;
--bordure-champ: #8A8279;
--erreur-fond: #FBE9E5;
--blanc: #fff;
/* Typographies */
--serif: "Young Serif", Georgia, serif;
--sans: "Atkinson Hyperlegible Next", "Atkinson Hyperlegible", system-ui, sans-serif;
/* Mise en page */
--colonne: 36rem;
--gouttiere: 1.25rem;
--largeur-entete: 64rem;
/* Arrondis */
--arrondi-petit: 0.5rem;
--arrondi: 0.75rem;
--arrondi-pilule: 999px;
}
/* ---------- 3. Base ---------- */
*,
*::before,
*::after { box-sizing: border-box; }
html {
-webkit-text-size-adjust: 100%;
text-size-adjust: 100%;
}
body {
margin: 0;
min-height: 100vh;
display: flex;
flex-direction: column;
background: var(--porcelaine);
color: var(--encre);
font-family: var(--sans);
font-size: 1.1875rem;
line-height: 1.65;
font-kerning: normal;
hyphens: manual;
}
main { flex: 1; outline: none; }
img, svg { display: block; max-width: 100%; }
/* ---------- 4. Liens, focus, lien d'évitement ---------- */
a {
color: var(--encre);
text-decoration-line: underline;
text-decoration-color: var(--vert);
text-decoration-thickness: 0.12em;
text-underline-offset: 0.2em;
}
a:hover { text-decoration-color: var(--encre); }
:focus-visible {
outline: 3px solid var(--encre);
outline-offset: 3px;
border-radius: 4px;
}
.evitement {
position: absolute;
left: var(--gouttiere);
top: -4rem;
padding: 0.5rem 1rem;
background: var(--encre);
color: var(--porcelaine);
z-index: 10;
}
.evitement:focus { top: 0.75rem; }
/* ---------- 5. Boutons ---------- */
.bouton {
display: inline-block;
padding: 0.8rem 1.6rem;
border-radius: var(--arrondi-pilule);
background: var(--encre);
color: var(--porcelaine);
font-weight: 700;
line-height: 1.3;
text-decoration: none;
transition: background-color 0.15s ease;
}
.bouton:hover { background: var(--encre-survol); color: var(--porcelaine); }
.bouton:focus-visible { outline-offset: 4px; border-radius: var(--arrondi-pilule); }
.bouton-petit {
padding: 0.45rem 1rem;
font-size: 1rem;
}
/* Un bouton seul dans son paragraphe respire davantage */
p:has(> .bouton:only-child) { margin: 2rem 0; }
/* ---------- 6. En-tête et navigation ---------- */
.entete {
max-width: var(--largeur-entete);
margin: 0 auto;
padding: 1.25rem var(--gouttiere);
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 1rem 2rem;
width: 100%;
}
.logo {
display: inline-block;
padding: 0.05rem 0.55rem 0.15rem;
border: 2.5px solid var(--encre);
border-radius: var(--arrondi-petit);
font-family: var(--sans);
font-weight: 800;
font-size: 1.35rem;
line-height: 1.2;
letter-spacing: 0.01em;
text-decoration: none;
}
.nav ul {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.35rem 1.35rem;
font-size: 1rem;
}
.nav a:not(.bouton) {
text-decoration-color: transparent;
}
.nav a:not(.bouton):hover,
.nav a[aria-current="page"]:not(.bouton) {
text-decoration-color: var(--vert);
}
.nav a.bouton[aria-current="page"] {
background: var(--porcelaine);
color: var(--encre);
box-shadow: inset 0 0 0 2px var(--encre);
}
/* ---------- 7. Colonne de texte et titres ---------- */
.texte {
max-width: calc(var(--colonne) + 2 * var(--gouttiere));
margin: 0 auto;
padding: 2rem var(--gouttiere) 5rem;
}
h1, h2, h3 {
font-family: var(--serif);
font-weight: 400;
line-height: 1.15;
letter-spacing: -0.005em;
text-wrap: balance;
}
h1 {
font-size: clamp(2.35rem, 1.6rem + 3.6vw, 3.75rem);
margin: 1.5rem 0 1.5rem;
}
h2 {
font-size: clamp(1.6rem, 1.35rem + 1vw, 2rem);
margin: 4.5rem 0 1.25rem;
}
p { margin: 0 0 1.15rem; }
/* Le chapeau : paragraphe entièrement en italique juste sous le titre */
h1 + p:has(> em:only-child) {
font-size: 1.3rem;
line-height: 1.55;
color: var(--encre-douce);
}
.note {
margin-top: 2.5rem;
color: var(--encre-douce);
font-size: 1.0625rem;
}
.exergue {
margin: 2.25rem 0;
padding-left: 1.25rem;
border-left: 4px solid var(--orange);
font-family: var(--serif);
font-size: 1.45rem;
line-height: 1.4;
}
code {
font-size: 0.9em;
background: var(--porcelaine-fonce);
padding: 0.05em 0.3em;
border-radius: 3px;
}
/* ---------- 8. Illustration « tasses » ---------- */
.tasses {
margin: 1.5rem 0 0;
}
/* L'illustration déborde légèrement de la colonne sur grand écran */
@media (min-width: 48rem) {
.tasses { margin-inline: -4rem; }
}
.tasses svg { width: 100%; height: auto; overflow: visible; }
.table { fill: var(--chene); }
.table-fil,
.tasse-corps,
.tasse-anse,
.kluk-socle,
.vapeur path {
stroke: var(--encre);
stroke-width: 3;
stroke-linecap: round;
stroke-linejoin: round;
}
.tasse-anse { fill: none; stroke-width: 6; }
.tasse-corps { fill: currentColor; }
.tasse-reflet { fill: none; stroke: var(--blanc); stroke-opacity: 0.45; stroke-width: 4; stroke-linecap: round; }
.kluk-socle { fill: var(--chene-fonce); }
.kluk-voyant { fill: var(--porcelaine); }
.vapeur path { fill: none; stroke-width: 2.5; stroke: var(--encre-douce); }
.humeur-gris { color: var(--gris); }
.humeur-orange { color: var(--orange); }
.humeur-rouge { color: var(--rouge); }
.humeur-vert { color: var(--vert); }
@media (prefers-reduced-motion: no-preference) {
.vapeur path {
animation: vapeur 5s ease-in-out infinite;
}
.vapeur path + path { animation-delay: 2.5s; }
}
@keyframes vapeur {
0%, 100% { opacity: 0.25; transform: translateY(6px); }
50% { opacity: 0.9; transform: translateY(-6px); }
}
/* ---------- 9. Calendrier d'humeurs (décoratif) ---------- */
.calendrier {
display: grid;
grid-template-rows: repeat(7, auto);
grid-auto-flow: column;
grid-auto-columns: 1fr;
gap: 4px;
margin: 2.25rem 0 1rem;
padding: 1.25rem;
background: var(--surface);
border-radius: var(--arrondi);
box-shadow: inset 0 0 0 1px var(--porcelaine-fonce);
}
.jour {
aspect-ratio: 1;
border-radius: 2px;
}
.jour-g { background: var(--gris); }
.jour-o { background: var(--orange); }
.jour-v { background: var(--vert); }
.jour-r { background: var(--rouge); }
@media (max-width: 30rem) {
.calendrier { gap: 3px; padding: 0.9rem; }
}
/* ---------- 10. Photos ---------- */
.photo {
margin: 2rem 0;
}
.photo img {
width: 100%;
height: auto;
border-radius: var(--arrondi);
}
.photo figcaption {
margin-top: 0.6rem;
font-size: 0.95rem;
color: var(--encre-douce);
}
.photo-attente {
display: flex;
align-items: center;
gap: 1.25rem;
aspect-ratio: 3 / 2;
padding: 1.5rem;
border: 2px dashed var(--chene-fonce);
border-radius: var(--arrondi);
background: var(--porcelaine-fonce);
color: var(--encre-douce);
font-size: 1rem;
line-height: 1.5;
}
.photo-attente svg {
flex: none;
width: 2.75rem;
fill: none;
stroke: var(--chene-fonce);
stroke-width: 2.5;
stroke-linejoin: round;
stroke-linecap: round;
}
.photo-attente p { margin: 0; }
.photo-attente strong { color: var(--encre); }
/* ---------- 11. Formulaire ---------- */
.formulaire {
margin: 2.5rem 0 1rem;
padding: 1.75rem 1.25rem;
background: var(--surface);
border-radius: var(--arrondi);
box-shadow: inset 0 0 0 1px var(--porcelaine-fonce);
}
@media (min-width: 40rem) {
.formulaire { padding: 2.25rem 2.5rem; }
}
.champ {
margin: 0 0 1.75rem;
padding: 0;
border: 0;
min-width: 0;
}
.champ > label,
.champ > legend {
display: block;
padding: 0;
margin-bottom: 0.5rem;
font-weight: 700;
}
.precision {
font-weight: 400;
color: var(--encre-douce);
}
.formulaire input[type="email"],
.formulaire textarea {
display: block;
width: 100%;
padding: 0.7rem 0.85rem;
border: 2px solid var(--bordure-champ);
border-radius: var(--arrondi-petit);
background: var(--blanc);
color: var(--encre);
font: inherit;
line-height: 1.4;
}
.formulaire textarea { resize: vertical; min-height: 8rem; }
.formulaire input[type="email"]:focus-visible,
.formulaire textarea:focus-visible {
outline: 3px solid var(--vert);
outline-offset: 1px;
border-color: var(--encre);
}
.choix {
list-style: none;
margin: 0;
padding: 0;
}
.choix li {
display: flex;
align-items: flex-start;
gap: 0.75rem;
padding: 0.35rem 0;
}
.choix input {
flex: none;
width: 1.35rem;
height: 1.35rem;
margin: 0.2rem 0 0;
accent-color: var(--vert-lien);
cursor: pointer;
}
.choix label { cursor: pointer; }
/* Champ piège anti-robots, invisible */
.piege {
position: absolute;
left: -9999px;
width: 1px;
height: 1px;
overflow: hidden;
}
.formulaire-erreur {
padding: 0.75rem 1rem;
border-left: 4px solid var(--rouge);
background: var(--erreur-fond);
font-size: 1rem;
}
.formulaire .bouton {
border: 0;
font: inherit;
font-weight: 700;
cursor: pointer;
}
.formulaire .bouton:disabled { opacity: 0.6; cursor: progress; }
.formulaire-secours {
margin: 1rem 0 0;
font-size: 1rem;
color: var(--encre-douce);
}
/* ---------- 12. Pied de page : la table en bois ---------- */
.pied {
background: var(--chene);
border-top: 3px solid var(--encre);
font-size: 1rem;
}
.pied-interieur {
max-width: calc(var(--colonne) + 2 * var(--gouttiere));
margin: 0 auto;
padding: 2rem var(--gouttiere) 2.5rem;
}
.pied-phrase {
font-family: var(--serif);
font-size: 1.15rem;
margin: 0 0 1rem;
}
.pied-liens {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-wrap: wrap;
gap: 0.5rem 2rem;
}
.pied a { text-decoration-color: var(--encre); text-decoration-thickness: 1px; }
.pied a:hover { text-decoration-thickness: 2px; }
/* ---------- 13. Impression ---------- */
@media print {
.entete .nav, .pied, .tasses, .calendrier { display: none; }
body { background: var(--blanc); }
}

122
charte/exemple.html Normal file
View file

@ -0,0 +1,122 @@
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#F5F6F0">
<title>Charte Kluk — exemple</title>
<link rel="preload" href="fonts/young-serif-400.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="fonts/atkinson-hyperlegible-next-400.woff2" as="font" type="font/woff2" crossorigin>
<link rel="stylesheet" href="css/kluk.css">
<link rel="icon" href="img/favicon.svg" type="image/svg+xml">
</head>
<body>
<a class="evitement" href="#contenu">Aller au contenu</a>
<header class="entete">
<a class="logo" href="#" aria-label="Kluk, retour à l'accueil">kluk</a>
<nav class="nav" aria-label="Navigation principale">
<ul>
<li><a href="#" aria-current="page">Accueil</a></li>
<li><a href="#">Une autre page</a></li>
<li><a href="#" class="bouton bouton-petit">Appel à l’action</a></li>
</ul>
</nav>
</header>
<main id="contenu" tabindex="-1">
<article class="texte">
<h1>Titre de page en Young&nbsp;Serif</h1>
<p><em>Le chapeau : un paragraphe entièrement en italique, juste sous le titre.</em></p>
<p>Texte courant en Atkinson Hyperlegible Next, avec <a href="#">un lien souligné de vert</a>, du <strong>gras</strong>, de l’<em>italique</em> et du <code>code</code>.</p>
<p><a class="bouton" href="#">Bouton principal</a></p>
<figure class="tasses">
<svg viewBox="0 0 580 214" role="img" aria-labelledby="tasses-titre">
<title id="tasses-titre">Illustration : trois tasses de couleur alignées sur la table, et une quatrième posée sur la Kluk, fumante.</title>
<defs>
<g id="tasse">
<path class="tasse-anse" d="M63 22 C86 20 88 60 60 62" />
<path class="tasse-corps" d="M3 6 L10 78 Q11 86 20 86 L48 86 Q57 86 58 78 L65 6 Z" />
<path class="tasse-reflet" d="M13 18 L17 62" />
</g>
</defs>
<rect class="table" x="0" y="200" width="580" height="14" rx="3" />
<path class="table-fil" d="M0 200 H580" />
<use href="#tasse" x="34" y="114" class="humeur-gris" />
<use href="#tasse" x="132" y="114" class="humeur-orange" />
<use href="#tasse" x="230" y="114" class="humeur-rouge" />
<g class="kluk-illu" transform="translate(410 98)">
<g class="vapeur" aria-hidden="true">
<path d="M22 -6 c-9 -11 9 -20 0 -31 c-9 -11 9 -20 0 -31" />
<path d="M44 -10 c-9 -11 9 -20 0 -31 c-9 -11 9 -20 0 -31" />
</g>
<use href="#tasse" x="0" y="0" class="humeur-vert" />
<rect class="kluk-socle" x="-22" y="86" width="112" height="16" rx="8" />
<circle class="kluk-voyant" cx="34" cy="94" r="2.6" />
</g>
</svg>
</figure>
<h2>Intertitre de section</h2>
<p>Les intertitres ménagent un grand espace au-dessus d’eux (4,5&nbsp;rem) pour découper la page en respirations.</p>
<p class="exergue">Une phrase mise en exergue, en serif, soulignée d’un trait orange.</p>
<figure class="photo">
<div class="photo-attente">
<svg viewBox="0 0 40 32" aria-hidden="true" focusable="false"><path d="M3 3 L6 25 Q6.5 29 10.5 29 L24.5 29 Q28.5 29 29 25 L32 3 Z M31.5 8 C39 8 39.5 21 30 22" /></svg>
<p><strong>Photo à venir</strong><br>Description de la photo attendue.</p>
</div>
<figcaption>Légende facultative sous la photo.</figcaption>
</figure>
<div class="calendrier" aria-hidden="true" id="calendrier"></div>
<form class="formulaire" onsubmit="return false">
<div class="champ">
<label for="champ-email">Votre adresse mail <span class="precision">(obligatoire)</span></label>
<input id="champ-email" name="Email" type="email" required autocomplete="email">
</div>
<fieldset class="champ">
<legend>Ce que vous aimeriez faire <span class="precision">(cochez tout ce qui vous ressemble)</span></legend>
<ul class="choix">
<li><input id="choix-1" type="checkbox" name="choix[]" value="1" checked><label for="choix-1">Premier choix</label></li>
<li><input id="choix-2" type="checkbox" name="choix[]" value="2"><label for="choix-2">Deuxième choix</label></li>
</ul>
</fieldset>
<div class="champ">
<label for="champ-message">Un mot, une idée, une question ? <span class="precision">(facultatif)</span></label>
<textarea id="champ-message" name="Message" rows="5"></textarea>
</div>
<p class="formulaire-erreur" role="alert">Message d’erreur (masqué par défaut avec l’attribut <code>hidden</code>).</p>
<button class="bouton" type="submit">Envoyer</button>
<p class="formulaire-secours">Texte de secours, affiché dans un <code>&lt;noscript&gt;</code>.</p>
</form>
<p class="note">Une note discrète en fin de page.</p>
</article>
</main>
<footer class="pied">
<div class="pied-interieur">
<p class="pied-phrase">Une petite phrase en serif, facultative.</p>
<ul class="pied-liens">
<li><a href="#">Premier lien</a></li>
<li><a href="#">Mentions légales</a></li>
</ul>
</div>
</footer>
<script>
// Remplit le calendrier de démonstration : chaque groupe de 7 lettres est une semaine.
// g = gris, o = orange, v = vert, r = rouge.
var semaines = "vvovvvg vvvvovv ovvrvvv vvgvvvo ovvvvvv rrovrrr rorrvvo gvggggg gggvggg gogggvg ggoggvg vvgvvvv ovvvovv vrvvvov vvvvovv ovvgvvv vvovvrv vvvovvv vgvvvvo vvvvovv";
document.getElementById("calendrier").innerHTML = semaines.replace(/ /g, "").split("").map(function (j) {
return '<span class="jour jour-' + j + '"></span>';
}).join("");
</script>
</body>
</html>

97
charte/fonts/OFL.txt Normal file
View file

@ -0,0 +1,97 @@
Young Serif
Copyright 2023 The Young Serif Project Authors (https://github.com/noirblancrouge/YoungSerif)
Atkinson Hyperlegible Next
Copyright 2020-2024 The Atkinson Hyperlegible Next Project Authors (https://github.com/googlefonts/atkinson-hyperlegible-next)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

Binary file not shown.

1
charte/img/favicon.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path d="M4 4 L7 24 Q7.5 28 11.5 28 L19.5 28 Q23.5 28 24 24 L27 4 Z" fill="#5F9B59" stroke="#2E2A26" stroke-width="2.5" stroke-linejoin="round"/><path d="M26.5 9 C32 9 32 20 25 21" fill="none" stroke="#2E2A26" stroke-width="3" stroke-linecap="round"/></svg>

After

Width:  |  Height:  |  Size: 318 B

View file

@ -0,0 +1,48 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 580 214" role="img" aria-labelledby="t">
<title id="t">Trois tasses de couleur alignées sur la table, et une quatrième posée sur la Kluk, fumante.</title>
<style>
.table { fill: #D9BC94; }
.table-fil, .tasse-corps, .tasse-anse, .kluk-socle, .vapeur path {
stroke: #2E2A26; stroke-width: 3; stroke-linecap: round; stroke-linejoin: round;
}
.tasse-anse { fill: none; stroke-width: 6; }
.tasse-corps { fill: currentColor; }
.tasse-reflet { fill: none; stroke: #fff; stroke-opacity: 0.45; stroke-width: 4; stroke-linecap: round; }
.kluk-socle { fill: #B48D5F; }
.kluk-voyant { fill: #F5F6F0; }
.vapeur path { fill: none; stroke-width: 2.5; stroke: #5A534C; }
.humeur-gris { color: #8D97A8; }
.humeur-orange { color: #EDA33D; }
.humeur-rouge { color: #D8573F; }
.humeur-vert { color: #5F9B59; }
@media (prefers-reduced-motion: no-preference) {
.vapeur path { animation: vapeur 5s ease-in-out infinite; }
.vapeur path + path { animation-delay: 2.5s; }
}
@keyframes vapeur {
0%, 100% { opacity: 0.25; transform: translateY(6px); }
50% { opacity: 0.9; transform: translateY(-6px); }
}
</style>
<defs>
<g id="tasse">
<path class="tasse-anse" d="M63 22 C86 20 88 60 60 62" />
<path class="tasse-corps" d="M3 6 L10 78 Q11 86 20 86 L48 86 Q57 86 58 78 L65 6 Z" />
<path class="tasse-reflet" d="M13 18 L17 62" />
</g>
</defs>
<rect class="table" x="0" y="200" width="580" height="14" rx="3" />
<path class="table-fil" d="M0 200 H580" />
<use href="#tasse" x="34" y="114" class="humeur-gris" />
<use href="#tasse" x="132" y="114" class="humeur-orange" />
<use href="#tasse" x="230" y="114" class="humeur-rouge" />
<g transform="translate(410 98)">
<g class="vapeur">
<path d="M22 -6 c-9 -11 9 -20 0 -31 c-9 -11 9 -20 0 -31" />
<path d="M44 -10 c-9 -11 9 -20 0 -31 c-9 -11 9 -20 0 -31" />
</g>
<use href="#tasse" x="0" y="0" class="humeur-vert" />
<rect class="kluk-socle" x="-22" y="86" width="112" height="16" rx="8" />
<circle class="kluk-voyant" cx="34" cy="94" r="2.6" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-2 -2 44 36"><path d="M3 3 L6 25 Q6.5 29 10.5 29 L24.5 29 Q28.5 29 29 25 L32 3 Z M31.5 8 C39 8 39.5 21 30 22" fill="none" stroke="#B48D5F" stroke-width="2.5" stroke-linejoin="round" stroke-linecap="round"/></svg>

After

Width:  |  Height:  |  Size: 262 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-3 -3 95 95"><path d="M63 22 C86 20 88 60 60 62" fill="none" stroke="#2E2A26" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/><path d="M3 6 L10 78 Q11 86 20 86 L48 86 Q57 86 58 78 L65 6 Z" fill="#8D97A8" stroke="#2E2A26" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/><path d="M13 18 L17 62" fill="none" stroke="#fff" stroke-opacity="0.45" stroke-width="4" stroke-linecap="round"/></svg>

After

Width:  |  Height:  |  Size: 472 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-3 -3 95 95"><path d="M63 22 C86 20 88 60 60 62" fill="none" stroke="#2E2A26" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/><path d="M3 6 L10 78 Q11 86 20 86 L48 86 Q57 86 58 78 L65 6 Z" fill="#EDA33D" stroke="#2E2A26" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/><path d="M13 18 L17 62" fill="none" stroke="#fff" stroke-opacity="0.45" stroke-width="4" stroke-linecap="round"/></svg>

After

Width:  |  Height:  |  Size: 472 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-3 -3 95 95"><path d="M63 22 C86 20 88 60 60 62" fill="none" stroke="#2E2A26" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/><path d="M3 6 L10 78 Q11 86 20 86 L48 86 Q57 86 58 78 L65 6 Z" fill="#D8573F" stroke="#2E2A26" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/><path d="M13 18 L17 62" fill="none" stroke="#fff" stroke-opacity="0.45" stroke-width="4" stroke-linecap="round"/></svg>

After

Width:  |  Height:  |  Size: 472 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-3 -3 95 95"><path d="M63 22 C86 20 88 60 60 62" fill="none" stroke="#2E2A26" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/><path d="M3 6 L10 78 Q11 86 20 86 L48 86 Q57 86 58 78 L65 6 Z" fill="#5F9B59" stroke="#2E2A26" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/><path d="M13 18 L17 62" fill="none" stroke="#fff" stroke-opacity="0.45" stroke-width="4" stroke-linecap="round"/></svg>

After

Width:  |  Height:  |  Size: 472 B

View file

@ -13,6 +13,7 @@ Rails.application.routes.draw do
# get "service-worker" => "rails/pwa#service_worker", as: :pwa_service_worker # get "service-worker" => "rails/pwa#service_worker", as: :pwa_service_worker
mount LetterOpenerWeb::Engine, at: "/letter_opener" if Rails.env.development? mount LetterOpenerWeb::Engine, at: "/letter_opener" if Rails.env.development?
get "/charte", to: "charte#show" if Rails.env.development?
# Defines the root path route ("/") # Defines the root path route ("/")
root "home#index" root "home#index"