@lystech/sections 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +109 -55
- package/dist/SectionsProvider.d.ts +78 -0
- package/dist/SignatureLystech.d.ts +9 -0
- package/dist/editeur/Connexion.d.ts +6 -0
- package/dist/editeur/Editeur.d.ts +6 -0
- package/dist/editeur/TexteEnPlace.d.ts +7 -0
- package/dist/editeur/api.d.ts +39 -0
- package/dist/editeur/champs.d.ts +9 -0
- package/dist/editeur/index.d.ts +20 -0
- package/dist/editeur/pont.d.ts +28 -0
- package/dist/editeur/styles.d.ts +2 -0
- package/dist/editeur.js +1208 -0
- package/dist/index.d.ts +18 -108
- package/dist/index.js +259 -82
- package/dist/morceaux/sourceTexte-BMPMpNg9.js +71 -0
- package/dist/sourceTexte.d.ts +34 -0
- package/dist/types.d.ts +41 -0
- package/dist/useSection.d.ts +50 -0
- package/package.json +14 -6
- package/dist/index.umd.cjs +0 -1
package/README.md
CHANGED
|
@@ -1,55 +1,109 @@
|
|
|
1
|
-
# @lystech/sections
|
|
2
|
-
|
|
3
|
-
Le contenu d'un site, modifiable par son propriétaire — sans reconstruction ni
|
|
4
|
-
déploiement.
|
|
5
|
-
|
|
6
|
-
## Pourquoi un paquet à part de `@lystech/core`
|
|
7
|
-
|
|
8
|
-
`core` exige React 18 et embarque antd, Mantine et React Query : 2,4 Mo de
|
|
9
|
-
JavaScript et 1,2 Mo de CSS. Une clinique qui veut changer trois textes n'a
|
|
10
|
-
aucune raison de charger un magasin, un panier et PayPal — et beaucoup de sites
|
|
11
|
-
clients sont en React 19.
|
|
12
|
-
|
|
13
|
-
Ce paquet pèse **
|
|
14
|
-
qu'une boutique qui l'a déjà n'ait rien de
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
`
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
une
|
|
1
|
+
# @lystech/sections
|
|
2
|
+
|
|
3
|
+
Le contenu d'un site, modifiable par son propriétaire — sans reconstruction ni
|
|
4
|
+
déploiement.
|
|
5
|
+
|
|
6
|
+
## Pourquoi un paquet à part de `@lystech/core`
|
|
7
|
+
|
|
8
|
+
`core` exige React 18 et embarque antd, Mantine et React Query : 2,4 Mo de
|
|
9
|
+
JavaScript et 1,2 Mo de CSS. Une clinique qui veut changer trois textes n'a
|
|
10
|
+
aucune raison de charger un magasin, un panier et PayPal — et beaucoup de sites
|
|
11
|
+
clients sont en React 19.
|
|
12
|
+
|
|
13
|
+
Ce paquet pèse **4,7 Ko** en lecture (1,9 Ko compressé) et ne dépend que de
|
|
14
|
+
React. `core` le réexporte, pour qu'une boutique qui l'a déjà n'ait rien de
|
|
15
|
+
plus à installer.
|
|
16
|
+
|
|
17
|
+
## La plateforme fixe l'alphabet, pas les mots
|
|
18
|
+
|
|
19
|
+
Le serveur ne connaît aucun type de bloc. « plat-du-jour », « praticien »,
|
|
20
|
+
« tarifs » appartiennent au site, qui les déclare. Ce que la plateforme
|
|
21
|
+
connaît, ce sont les types de champs — `text`, `richText`, `image`, `number`,
|
|
22
|
+
`boolean`, `url`, `date`, `choice`, `list` — vrais dans tous les métiers, et
|
|
23
|
+
suffisants pour fabriquer un formulaire pour un bloc jamais vu.
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
import { SectionsProvider, useSection } from "@lystech/sections";
|
|
29
|
+
|
|
30
|
+
<SectionsProvider site="armoise" page="massage" langue={i18n.language}>
|
|
31
|
+
<MaPage />
|
|
32
|
+
</SectionsProvider>;
|
|
33
|
+
|
|
34
|
+
// Dans un composant déjà écrit :
|
|
35
|
+
const { contenu: tarifs } = useSection("tarifs-massotherapie", {
|
|
36
|
+
defaut: SITE.tarifs.massotherapie, // ce que le site affichait déjà
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Le repli n'est pas une option
|
|
41
|
+
|
|
42
|
+
`defaut` porte les valeurs actuelles du site. Tant que rien n'est publié — ou
|
|
43
|
+
si l'API ne répond pas — ce sont elles qui s'affichent. Brancher un site
|
|
44
|
+
existant ne peut donc rien casser : au pire il continue d'afficher ce qu'il
|
|
45
|
+
affichait hier.
|
|
46
|
+
|
|
47
|
+
### Publié ≠ brouillon
|
|
48
|
+
|
|
49
|
+
Le site public ne lit QUE le contenu publié. Un brouillon enregistré ne change
|
|
50
|
+
rien pour les visiteurs tant que personne n'a cliqué « Publier ».
|
|
51
|
+
|
|
52
|
+
## Champs traduits
|
|
53
|
+
|
|
54
|
+
Un champ déclaré `localized: true` stocke `{ fr: …, en: … }` ; le paquet rend
|
|
55
|
+
la langue courante, avec repli sur une autre langue plutôt que sur du vide —
|
|
56
|
+
une version anglaise oubliée vaut mieux affichée en français qu'absente.
|
|
57
|
+
|
|
58
|
+
## L'éditeur en place
|
|
59
|
+
|
|
60
|
+
La personne qui édite ouvre SON site avec un jeton dans l'adresse. Le paquet le
|
|
61
|
+
détecte, le retire aussitôt de la barre d'adresse, et charge l'interface
|
|
62
|
+
d'édition — 22 Ko qu'aucun visiteur ne télécharge jamais, parce que le
|
|
63
|
+
`import()` qui y mène n'est atteint que s'il y a un jeton.
|
|
64
|
+
|
|
65
|
+
Elle voit alors son site, ses polices, sa mise en page, avec chaque bloc
|
|
66
|
+
modifiable encadré. Ce qu'elle tape apparaît dedans à mesure qu'elle le tape.
|
|
67
|
+
|
|
68
|
+
### Marquer les blocs dans la page
|
|
69
|
+
|
|
70
|
+
L'éditeur doit savoir quel rectangle de la page correspond à quel bloc. Aucun
|
|
71
|
+
moyen fiable de le deviner : le site le dit.
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
const { contenu, ancrage } = useSection("tarifs", { defaut });
|
|
75
|
+
return <section {...ancrage}>…</section>;
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Hors édition, c'est un attribut `data-` inerte : aucun poids, aucun effet.
|
|
79
|
+
|
|
80
|
+
### Trois états, jamais confondus
|
|
81
|
+
|
|
82
|
+
| | Qui le voit |
|
|
83
|
+
|---|---|
|
|
84
|
+
| **Aperçu** | la personne qui édite, dans son navigateur |
|
|
85
|
+
| **Brouillon** | elle seule, enregistré sur le serveur |
|
|
86
|
+
| **Publié** | les visiteurs |
|
|
87
|
+
|
|
88
|
+
### Le jeton
|
|
89
|
+
|
|
90
|
+
Délivré par `POST /site-sections/edit-token` depuis la console Lystech. Il vaut
|
|
91
|
+
quelques heures, pour UNE organisation, et il est **plafonné à l'éditeur** même
|
|
92
|
+
si la personne est administratrice : un lien qui fuite ne peut pas ouvrir la
|
|
93
|
+
console. Le droit est relu dans les rôles à chaque requête, donc retirer un
|
|
94
|
+
accès ferme les jetons déjà émis sur-le-champ.
|
|
95
|
+
|
|
96
|
+
## Sites prérendus
|
|
97
|
+
|
|
98
|
+
Un site généré au build affiche les replis du code dans son HTML, puis bascule
|
|
99
|
+
sur le contenu publié une fois le JavaScript chargé — l'ancien prix clignote
|
|
100
|
+
vers le nouveau, et les moteurs de recherche n'indexent que l'ancien.
|
|
101
|
+
|
|
102
|
+
Passez le contenu au premier rendu pour supprimer les deux problèmes :
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
<SectionsProvider site="armoise" page="massage" sectionsInitiales={dejaLu}>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
où `dejaLu` vient d'un appel, dans le script de prérendu, à
|
|
109
|
+
`GET /site-sections/public/<site>/<page>`.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { SourceTexte } from './sourceTexte';
|
|
3
|
+
import { SectionPubliee } from './types';
|
|
4
|
+
type Etat = "chargement" | "pret" | "erreur";
|
|
5
|
+
type Contexte = {
|
|
6
|
+
/** Sous-domaine de l'organisation dans Lystech. */
|
|
7
|
+
site: string;
|
|
8
|
+
/** La page courante — l'éditeur en a besoin pour créer une section. */
|
|
9
|
+
page: string;
|
|
10
|
+
/** Langue courante — décide quelle version d'un champ traduit est rendue. */
|
|
11
|
+
langue: string;
|
|
12
|
+
apiBaseUrl: string;
|
|
13
|
+
sections: SectionPubliee[];
|
|
14
|
+
etat: Etat;
|
|
15
|
+
recharger: () => void;
|
|
16
|
+
/**
|
|
17
|
+
* L'APERÇU : ce que l'éditeur est en train d'écrire, avant publication.
|
|
18
|
+
*
|
|
19
|
+
* Il prime sur le contenu publié, par type de bloc. C'est ce qui rend
|
|
20
|
+
* l'édition « en place » : la cliente voit ses mots dans SA mise en page,
|
|
21
|
+
* avec SES polices et SES couleurs, au lieu d'un formulaire posé à côté
|
|
22
|
+
* d'un aperçu qui ne lui ressemble jamais tout à fait.
|
|
23
|
+
*/
|
|
24
|
+
apercu: Record<string, Record<string, unknown> | null>;
|
|
25
|
+
poserApercu: (blockType: string, data: Record<string, unknown> | null) => void;
|
|
26
|
+
/** Vrai quand un jeton d'édition est actif. Faux pour tout visiteur. */
|
|
27
|
+
modeEdition: boolean;
|
|
28
|
+
};
|
|
29
|
+
export declare function SectionsProvider({ site, page, langue, apiBaseUrl, sectionsInitiales, sourceTexte, pagesPartagees, children, }: {
|
|
30
|
+
/** Sous-domaine de l'organisation (ex. « armoise »). */
|
|
31
|
+
site: string;
|
|
32
|
+
/** La page dont on veut les sections (ex. « accueil »). */
|
|
33
|
+
page: string;
|
|
34
|
+
langue?: string | undefined;
|
|
35
|
+
apiBaseUrl?: string | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* LE CONTENU DÉJÀ CONNU AU PREMIER RENDU — pour un site prérendu.
|
|
38
|
+
*
|
|
39
|
+
* Sans lui, une page générée au build affiche les REPLIS du code, puis
|
|
40
|
+
* bascule sur le contenu publié une fois le JavaScript chargé. Le visiteur
|
|
41
|
+
* voit l'ancien prix clignoter vers le nouveau, et les moteurs de recherche
|
|
42
|
+
* n'indexent jamais que l'ancien. Un script de prérendu qui lit l'API et
|
|
43
|
+
* passe le résultat ici supprime les deux problèmes.
|
|
44
|
+
*/
|
|
45
|
+
sectionsInitiales?: SectionPubliee[] | undefined;
|
|
46
|
+
/**
|
|
47
|
+
* D'OU VIENNENT LES MOTS DU SITE.
|
|
48
|
+
*
|
|
49
|
+
* Fournie, le contenu publie est verse dedans : chaque texte du site —
|
|
50
|
+
* y compris le titre de l'onglet et la description des resultats de
|
|
51
|
+
* recherche — devient modifiable, sans marquer une seule section a la
|
|
52
|
+
* main. `adaptateurI18next(i18n)` la construit en une ligne.
|
|
53
|
+
*/
|
|
54
|
+
sourceTexte?: SourceTexte | undefined;
|
|
55
|
+
/**
|
|
56
|
+
* Des pages LUES EN PLUS de la page courante.
|
|
57
|
+
*
|
|
58
|
+
* Tout site a des textes partages — pied de page, navigation,
|
|
59
|
+
* coordonnees. Les ranger dans une page « commun » lue partout evite de
|
|
60
|
+
* les recopier sur chaque page, ou de forcer quelqu'un a revenir a
|
|
61
|
+
* l'accueil pour corriger une adresse affichee en bas de toutes.
|
|
62
|
+
*
|
|
63
|
+
* Elles ne changent PAS `page` : une nouvelle section se cree toujours
|
|
64
|
+
* sur la page courante, jamais dans le commun par accident.
|
|
65
|
+
*/
|
|
66
|
+
pagesPartagees?: string[] | undefined;
|
|
67
|
+
children: ReactNode;
|
|
68
|
+
}): import("react").JSX.Element;
|
|
69
|
+
/**
|
|
70
|
+
* Le contexte, ou `null` HORS d'un fournisseur.
|
|
71
|
+
*
|
|
72
|
+
* On ne lève pas : un composant branché sur les sections doit rester
|
|
73
|
+
* utilisable dans une page qui n'en a pas (une maquette, un test, une page
|
|
74
|
+
* d'erreur). Il affichera son repli, ce qui est exactement le bon
|
|
75
|
+
* comportement.
|
|
76
|
+
*/
|
|
77
|
+
export declare function useSectionsContext(): Contexte | null;
|
|
78
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function SignatureLystech({ site, apiBaseUrl, className, mention, connexion, }: {
|
|
2
|
+
/** Sous-domaine de l'organisation. Pris du fournisseur si omis. */
|
|
3
|
+
site?: string | undefined;
|
|
4
|
+
apiBaseUrl?: string | undefined;
|
|
5
|
+
className?: string | undefined;
|
|
6
|
+
/** Les mots de la mention, si le site en préfère d'autres. */
|
|
7
|
+
mention?: string | undefined;
|
|
8
|
+
connexion?: string | undefined;
|
|
9
|
+
}): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { SourceTexte } from '../sourceTexte';
|
|
2
|
+
export declare function TexteEnPlace({ source, actif, surModification, }: {
|
|
3
|
+
source: SourceTexte;
|
|
4
|
+
/** Faux pendant qu'un panneau est ouvert : on ne veut pas deux modes. */
|
|
5
|
+
actif: boolean;
|
|
6
|
+
surModification: (ns: string, chemin: string, langue: string, valeur: string) => void | Promise<void>;
|
|
7
|
+
}): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { BlocDeclare } from '../types';
|
|
2
|
+
/** Une section telle que l'éditeur la voit — brouillon compris. */
|
|
3
|
+
export type SectionEditeur = {
|
|
4
|
+
UID: string;
|
|
5
|
+
page: string;
|
|
6
|
+
blockType: string;
|
|
7
|
+
position: number;
|
|
8
|
+
enabled: boolean;
|
|
9
|
+
data: Record<string, unknown> | null;
|
|
10
|
+
draft: Record<string, unknown> | null;
|
|
11
|
+
publishedOn: string | null;
|
|
12
|
+
};
|
|
13
|
+
export type Api = ReturnType<typeof creerApi>;
|
|
14
|
+
export declare function creerApi({ jeton, site, apiBaseUrl, surJetonRefuse, }: {
|
|
15
|
+
jeton: string;
|
|
16
|
+
site: string;
|
|
17
|
+
apiBaseUrl: string;
|
|
18
|
+
/**
|
|
19
|
+
* Appele quand le serveur refuse le jeton.
|
|
20
|
+
*
|
|
21
|
+
* UN JETON EXPIRE NE DOIT PAS LAISSER UNE BARRE D'EDITION A L'ECRAN. Sans
|
|
22
|
+
* ce rappel, la personne verrait « Mode edition », ouvrirait un panneau, y
|
|
23
|
+
* ecrirait, cliquerait « Publier » — et n'obtiendrait qu'une erreur, sans
|
|
24
|
+
* comprendre qu'il suffisait de se reconnecter.
|
|
25
|
+
*/
|
|
26
|
+
surJetonRefuse: () => void;
|
|
27
|
+
}): {
|
|
28
|
+
/** Les blocs que le site a déclaré savoir afficher. Route publique. */
|
|
29
|
+
schemas: () => Promise<BlocDeclare[]>;
|
|
30
|
+
/** Ce qui existe sur une page, brouillons compris. */
|
|
31
|
+
pourPage: (page: string) => Promise<SectionEditeur[]>;
|
|
32
|
+
creer: (page: string, blockType: string, draft: Record<string, unknown>) => Promise<{
|
|
33
|
+
UID: string;
|
|
34
|
+
}>;
|
|
35
|
+
enregistrerBrouillon: (UID: string, draft: Record<string, unknown>) => Promise<any>;
|
|
36
|
+
publier: (UID: string) => Promise<any>;
|
|
37
|
+
/** Ramène le brouillon à ce qui est en ligne. */
|
|
38
|
+
annuler: (UID: string) => Promise<any>;
|
|
39
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ChampDeclare } from '../types';
|
|
2
|
+
type Props = {
|
|
3
|
+
champ: ChampDeclare;
|
|
4
|
+
valeur: unknown;
|
|
5
|
+
changer: (v: unknown) => void;
|
|
6
|
+
langue: string;
|
|
7
|
+
};
|
|
8
|
+
export declare function Champ({ champ, valeur, changer, langue }: Props): import("react").JSX.Element;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { PontEdition } from './pont';
|
|
2
|
+
export type { PontEdition } from './pont';
|
|
3
|
+
/**
|
|
4
|
+
* LE PANNEAU DE CONNEXION, sur le site lui-meme.
|
|
5
|
+
*
|
|
6
|
+
* Charge au clic, jamais avant : le lien du pied de page est un simple bouton
|
|
7
|
+
* tant que personne ne le presse. Un visiteur ne telecharge donc rien de tout
|
|
8
|
+
* ceci.
|
|
9
|
+
*/
|
|
10
|
+
export declare function monterConnexion({ site, apiBaseUrl, surJeton, }: {
|
|
11
|
+
site: string;
|
|
12
|
+
apiBaseUrl: string;
|
|
13
|
+
surJeton: (jeton: string) => void;
|
|
14
|
+
}): () => void;
|
|
15
|
+
export declare function monterEditeur({ jeton, site, apiBaseUrl, pont, }: {
|
|
16
|
+
jeton: string;
|
|
17
|
+
site: string;
|
|
18
|
+
apiBaseUrl: string;
|
|
19
|
+
pont: PontEdition;
|
|
20
|
+
}): () => void;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { SourceTexte } from '../sourceTexte';
|
|
2
|
+
export type PontEdition = {
|
|
3
|
+
/**
|
|
4
|
+
* D'où viennent les mots du site — et comment les remplacer.
|
|
5
|
+
*
|
|
6
|
+
* C'est elle qui permet a l'editeur de reconnaitre un texte affiche dans
|
|
7
|
+
* la page et de remonter a son identite. Sans elle, il ne reste que les
|
|
8
|
+
* blocs marques a la main.
|
|
9
|
+
*/
|
|
10
|
+
sourceTexte?: SourceTexte | undefined;
|
|
11
|
+
/** Sous-domaine de l'organisation — affiché, jamais envoyé. */
|
|
12
|
+
site: string;
|
|
13
|
+
/** La page courante. Mise à jour par le fournisseur. */
|
|
14
|
+
page: string;
|
|
15
|
+
/** La langue affichée par le site en ce moment. */
|
|
16
|
+
langue: string;
|
|
17
|
+
/** Afficher un contenu non publié dans la page, ou `null` pour l'enlever. */
|
|
18
|
+
poserApercu: (blockType: string, data: Record<string, unknown> | null) => void;
|
|
19
|
+
/** Relire le contenu publié depuis le serveur. */
|
|
20
|
+
recharger: () => void;
|
|
21
|
+
/**
|
|
22
|
+
* Posé par l'ÉDITEUR, appelé par le FOURNISSEUR quand la page change.
|
|
23
|
+
*
|
|
24
|
+
* Sans cet avis, un site à page unique qui change d'adresse sans rien
|
|
25
|
+
* recharger laisserait l'éditeur sur les sections de la page précédente.
|
|
26
|
+
*/
|
|
27
|
+
surChangementDePage?: (page: string) => void;
|
|
28
|
+
};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const CSS = "\n:host {\n all: initial;\n --fond: #16181d;\n --fond-clair: #21242b;\n --bord: #333844;\n --texte: #f2f3f5;\n --texte-doux: #a3a9b6;\n --accent: #3ba58d;\n --accent-sombre: #2d8570;\n --danger: #d9776a;\n font-family: ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", Roboto,\n \"Helvetica Neue\", Arial, sans-serif;\n font-size: 14px;\n line-height: 1.45;\n color: var(--texte);\n}\n\n* { box-sizing: border-box; }\n\n/* LE CALQUE LAISSE PASSER LES CLICS. Un conteneur fixe en plein \u00E9cran qui\n intercepte tout rendrait le site inutilisable derri\u00E8re lui : on ne pourrait\n plus naviguer, ni m\u00EAme faire d\u00E9filer correctement. Seuls ses ENFANTS\n reprennent les clics. */\n.calque {\n position: fixed;\n inset: 0;\n pointer-events: none;\n z-index: 2147483000;\n}\n.calque > * { pointer-events: auto; }\n\n/* ---------------------------------------------------------- CADRES ------ */\n\n.cadre {\n position: fixed;\n border: 2px dashed rgba(59, 165, 141, 0.55);\n border-radius: 6px;\n pointer-events: none;\n transition: border-color 120ms ease;\n}\n.cadre--actif { border-color: var(--accent); border-style: solid; }\n\n.pastille {\n position: fixed;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 5px 10px;\n border: 0;\n border-radius: 999px;\n background: var(--accent);\n color: #06120e;\n font-size: 12px;\n font-weight: 650;\n letter-spacing: 0.01em;\n cursor: pointer;\n box-shadow: 0 2px 10px rgba(0, 0, 0, 0.35);\n}\n.pastille:hover { background: #45bda2; }\n.pastille:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }\n\n/* ------------------------------------------------------------ BARRE ----- */\n\n.barre {\n position: fixed;\n left: 50%;\n bottom: 18px;\n transform: translateX(-50%);\n display: flex;\n align-items: center;\n gap: 14px;\n padding: 9px 14px;\n border: 1px solid var(--bord);\n border-radius: 999px;\n background: var(--fond);\n box-shadow: 0 8px 30px rgba(0, 0, 0, 0.45);\n max-width: calc(100vw - 24px);\n}\n.barre__point {\n width: 8px; height: 8px; border-radius: 50%;\n background: var(--accent);\n flex: none;\n}\n.barre__texte { font-size: 13px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.barre__texte b { font-weight: 650; }\n.barre__compte { color: var(--texte-doux); font-size: 12px; white-space: nowrap; }\n\n/* ----------------------------------------------------------- PANNEAU ---- */\n\n.panneau {\n position: fixed;\n top: 0; right: 0; bottom: 0;\n width: min(420px, 100vw);\n display: flex;\n flex-direction: column;\n background: var(--fond);\n border-left: 1px solid var(--bord);\n box-shadow: -12px 0 40px rgba(0, 0, 0, 0.4);\n}\n@media (max-width: 520px) {\n .panneau { top: auto; height: 78vh; border-left: 0; border-top: 1px solid var(--bord); }\n}\n\n.panneau__tete {\n display: flex; align-items: flex-start; gap: 10px;\n padding: 16px 16px 12px;\n border-bottom: 1px solid var(--bord);\n}\n.panneau__titre { font-size: 15px; font-weight: 650; margin: 0; }\n.panneau__sous { margin: 3px 0 0; font-size: 12px; color: var(--texte-doux); }\n.panneau__corps { flex: 1; overflow-y: auto; padding: 14px 16px 18px; }\n.panneau__pied {\n display: flex; gap: 8px; align-items: center;\n padding: 12px 16px;\n border-top: 1px solid var(--bord);\n background: var(--fond-clair);\n}\n.panneau__pied .espace { flex: 1; }\n\n/* --------------------------------------------------- CONNEXION ---------- */\n\n/* Le voile PREND les clics, lui : une boite de dialogue qui demande un mot de\n passe ne doit pas laisser cliquer la page derriere elle. C'est l'exception\n a la regle du calque, et elle est volontaire. */\n.voile {\n position: fixed;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 16px;\n background: rgba(8, 10, 14, 0.62);\n backdrop-filter: blur(2px);\n}\n\n.fenetre {\n width: min(420px, 100%);\n max-height: 90vh;\n overflow-y: auto;\n border: 1px solid var(--bord);\n border-radius: 12px;\n background: var(--fond);\n box-shadow: 0 24px 60px rgba(0, 0, 0, 0.5);\n}\n\n/* ------------------------------------------------------------ CHAMPS ---- */\n\n.champ { margin-bottom: 16px; }\n.champ__etiquette {\n display: block; margin-bottom: 5px;\n font-size: 12px; font-weight: 600; color: var(--texte);\n}\n.champ__aide { margin: 4px 0 0; font-size: 11.5px; color: var(--texte-doux); }\n.champ__langue {\n display: inline-block; margin: 0 0 4px;\n font-size: 10.5px; font-weight: 700; letter-spacing: 0.06em;\n text-transform: uppercase; color: var(--texte-doux);\n}\n\ninput[type=\"text\"], input[type=\"url\"], input[type=\"number\"], input[type=\"date\"],\nselect, textarea {\n width: 100%;\n padding: 8px 10px;\n border: 1px solid var(--bord);\n border-radius: 6px;\n background: var(--fond-clair);\n color: var(--texte);\n font: inherit;\n font-size: 13px;\n}\ntextarea { min-height: 92px; resize: vertical; line-height: 1.5; }\ninput:focus, select:focus, textarea:focus {\n outline: none; border-color: var(--accent);\n box-shadow: 0 0 0 3px rgba(59, 165, 141, 0.18);\n}\n\n.bascule { display: flex; align-items: center; gap: 8px; }\n.bascule input { width: 16px; height: 16px; accent-color: var(--accent); }\n\n.vignette {\n margin-top: 8px; max-height: 110px; border-radius: 6px;\n border: 1px solid var(--bord); display: block;\n}\n\n/* --------------------------------------------------------- LISTES ------- */\n\n.liste__item {\n border: 1px solid var(--bord); border-radius: 8px;\n padding: 12px; margin-bottom: 10px; background: var(--fond-clair);\n}\n.liste__tete {\n display: flex; align-items: center; justify-content: space-between;\n margin-bottom: 8px;\n}\n.liste__rang { font-size: 11px; font-weight: 700; color: var(--texte-doux); }\n\n/* --------------------------------------------------------- BOUTONS ------ */\n\n.bouton {\n display: inline-flex; align-items: center; justify-content: center; gap: 6px;\n padding: 8px 14px; border-radius: 7px; border: 1px solid transparent;\n font: inherit; font-size: 13px; font-weight: 600; cursor: pointer;\n background: var(--fond-clair); color: var(--texte); border-color: var(--bord);\n}\n.bouton:hover { background: #2a2e37; }\n.bouton:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }\n.bouton--principal { background: var(--accent); border-color: var(--accent); color: #06120e; }\n.bouton--principal:hover { background: #45bda2; }\n.bouton--discret { background: transparent; border-color: transparent; color: var(--texte-doux); }\n.bouton--discret:hover { background: var(--fond-clair); color: var(--texte); }\n.bouton[disabled] { opacity: 0.5; cursor: default; }\n.bouton--icone { padding: 6px 9px; }\n\n.message {\n margin: 0 0 12px; padding: 9px 11px; border-radius: 7px;\n font-size: 12.5px; border: 1px solid;\n}\n.message--erreur { background: rgba(217, 119, 106, 0.12); border-color: rgba(217, 119, 106, 0.4); color: #f0b3a9; }\n.message--ok { background: rgba(59, 165, 141, 0.12); border-color: rgba(59, 165, 141, 0.4); color: #8fd9c6; }\n.message--info { background: var(--fond-clair); border-color: var(--bord); color: var(--texte-doux); }\n\n@media (prefers-reduced-motion: reduce) {\n * { transition: none !important; animation: none !important; }\n}\n";
|
|
2
|
+
export declare const CSS_EN_PLACE = "\n/* --------------------------------------------------------- CHOIX -------- */\n\n/* Quand un meme texte vient de plusieurs cles, on demande laquelle plutot que\n de deviner \u2014 et de modifier la mauvaise sans que personne le voie. */\n.choix {\n position: fixed;\n width: 260px;\n padding: 12px;\n border: 1px solid var(--bord);\n border-radius: 10px;\n background: var(--fond);\n box-shadow: 0 14px 40px rgba(0, 0, 0, 0.45);\n}\n.choix__titre { margin: 0 0 2px; font-size: 12.5px; font-weight: 650; }\n.choix__aide { margin: 0 0 10px; font-size: 11.5px; color: var(--texte-doux); }\n.choix__option {\n display: flex; flex-direction: column; align-items: flex-start; gap: 2px;\n width: 100%; margin-bottom: 6px; padding: 7px 9px;\n border: 1px solid var(--bord); border-radius: 7px;\n background: var(--fond-clair); color: var(--texte);\n font: inherit; text-align: left; cursor: pointer;\n}\n.choix__option:hover { background: #2a2e37; border-color: var(--accent); }\n.choix__chemin { font-size: 12px; font-weight: 600; }\n.choix__ns { font-size: 10.5px; color: var(--texte-doux); }\n\n/* ------------------------------------------------------- RANGEES -------- */\n\n.rangee {\n display: flex; align-items: center; justify-content: space-between; gap: 10px;\n width: 100%; margin-bottom: 6px; padding: 10px 12px;\n border: 1px solid var(--bord); border-radius: 8px;\n background: var(--fond-clair); color: var(--texte);\n font: inherit; text-align: left; cursor: pointer;\n}\n.rangee:hover { background: #2a2e37; border-color: var(--accent); }\n.rangee__nom { font-size: 13px; font-weight: 600; }\n.rangee__info { font-size: 11.5px; color: var(--texte-doux); white-space: nowrap; }\n\n/* ------------------------------------------------------ FLOTTANT -------- */\n\n/* Un message au-dessus de la barre, pour confirmer sans ouvrir de panneau. */\n.flottant {\n position: fixed;\n left: 50%; bottom: 76px;\n transform: translateX(-50%);\n max-width: min(460px, calc(100vw - 24px));\n}\n.flottant .message { margin: 0; box-shadow: 0 8px 26px rgba(0,0,0,.4); }\n";
|