odoro 0.3.4 → 1.0.1
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/dist/cli.js +3 -3
- package/dist/{commands-PT3NS65O.js → commands-AHMXBWQQ.js} +25 -5
- package/dist/{create-IOFAPDBO.js → create-LTT3SAWT.js} +11 -7
- package/dist/{package-Q2FV4K3H.js → package-LK46RV2I.js} +1 -1
- package/package.json +1 -1
- package/templates/react-ts/_variantes/avec-moteur/src/sections/Fond.tsx +118 -0
- package/templates/react-ts/_variantes/sans-libs/src/App.tsx +22 -51
- package/templates/react-ts/_variantes/sans-libs/src/composants/Marque.tsx +37 -0
- package/templates/react-ts/_variantes/sans-libs/src/sections/Cloture.tsx +60 -0
- package/templates/react-ts/_variantes/sans-libs/src/sections/Fond.tsx +23 -0
- package/templates/react-ts/_variantes/sans-libs/src/sections/Hero.tsx +92 -0
- package/templates/react-ts/_variantes/sans-libs/src/sections/Piliers.tsx +44 -0
- package/templates/react-ts/_variantes/sans-libs/src/styles.css +322 -40
- package/templates/react-ts/_variantes/sans-routeur/src/App.tsx +35 -77
- package/templates/react-ts/src/App.tsx +56 -29
- package/templates/react-ts/src/composants/Marque.tsx +50 -0
- package/templates/react-ts/src/routes/About.tsx +38 -14
- package/templates/react-ts/src/routes/Home.tsx +21 -73
- package/templates/react-ts/src/sections/Cloture.tsx +69 -0
- package/templates/react-ts/src/sections/Fond.tsx +42 -0
- package/templates/react-ts/src/sections/Hero.tsx +119 -0
- package/templates/react-ts/src/sections/Piliers.tsx +68 -0
- package/templates/react-ts/src/styles.css +4 -5
- package/templates/react-ts-server/_variantes/avec-moteur/client/src/sections/Fond.tsx +118 -0
- package/templates/react-ts-server/_variantes/sans-libs/client/src/App.tsx +22 -51
- package/templates/react-ts-server/_variantes/sans-libs/client/src/composants/Marque.tsx +37 -0
- package/templates/react-ts-server/_variantes/sans-libs/client/src/sections/Cloture.tsx +60 -0
- package/templates/react-ts-server/_variantes/sans-libs/client/src/sections/Fond.tsx +23 -0
- package/templates/react-ts-server/_variantes/sans-libs/client/src/sections/Hero.tsx +92 -0
- package/templates/react-ts-server/_variantes/sans-libs/client/src/sections/Piliers.tsx +44 -0
- package/templates/react-ts-server/_variantes/sans-libs/client/src/styles.css +322 -40
- package/templates/react-ts-server/_variantes/sans-routeur/client/src/App.tsx +35 -77
- package/templates/react-ts-server/client/src/App.tsx +56 -29
- package/templates/react-ts-server/client/src/composants/Marque.tsx +50 -0
- package/templates/react-ts-server/client/src/routes/About.tsx +38 -14
- package/templates/react-ts-server/client/src/routes/Home.tsx +18 -138
- package/templates/react-ts-server/client/src/sections/Cloture.tsx +69 -0
- package/templates/react-ts-server/client/src/sections/Fond.tsx +42 -0
- package/templates/react-ts-server/client/src/sections/Hero.tsx +119 -0
- package/templates/react-ts-server/client/src/sections/Piliers.tsx +68 -0
- package/templates/react-ts-server/client/src/styles.css +4 -5
package/dist/cli.js
CHANGED
|
@@ -131,7 +131,7 @@ function rootFrom(flags, positional) {
|
|
|
131
131
|
async function run(argv) {
|
|
132
132
|
const { command, positional, flags } = parseArgs(argv);
|
|
133
133
|
if (flags["version"] === true) {
|
|
134
|
-
const manifest = await import('./package-
|
|
134
|
+
const manifest = await import('./package-LK46RV2I.js');
|
|
135
135
|
console.log(manifest.default.version);
|
|
136
136
|
return 0;
|
|
137
137
|
}
|
|
@@ -142,7 +142,7 @@ async function run(argv) {
|
|
|
142
142
|
switch (command) {
|
|
143
143
|
case "create":
|
|
144
144
|
case "new": {
|
|
145
|
-
const { createCommand } = await import('./create-
|
|
145
|
+
const { createCommand } = await import('./create-LTT3SAWT.js');
|
|
146
146
|
const options = {};
|
|
147
147
|
if (positional[0] !== void 0) options.name = positional[0];
|
|
148
148
|
if (typeof flags["template"] === "string") options.template = flags["template"];
|
|
@@ -180,7 +180,7 @@ async function run(argv) {
|
|
|
180
180
|
case "list":
|
|
181
181
|
case "diff":
|
|
182
182
|
case "doctor": {
|
|
183
|
-
const registry = await import('./commands-
|
|
183
|
+
const registry = await import('./commands-AHMXBWQQ.js');
|
|
184
184
|
const options = {
|
|
185
185
|
root: typeof flags["root"] === "string" ? flags["root"] : process.cwd(),
|
|
186
186
|
registry: typeof flags["registry"] === "string" ? flags["registry"] : void 0,
|
|
@@ -89,9 +89,29 @@ async function saveProject(root, config) {
|
|
|
89
89
|
|
|
90
90
|
// src/add/rewrite.ts
|
|
91
91
|
var REGISTRY_TOKEN = "@registre";
|
|
92
|
-
function
|
|
92
|
+
function estUnAlias(prefix) {
|
|
93
|
+
return /^[@~#]/.test(prefix);
|
|
94
|
+
}
|
|
95
|
+
function cheminRelatif(depuis, vers) {
|
|
96
|
+
const segmentsDepuis = depuis.split("/").slice(0, -1);
|
|
97
|
+
const segmentsVers = vers.split("/");
|
|
98
|
+
let commun = 0;
|
|
99
|
+
while (commun < segmentsDepuis.length && commun < segmentsVers.length - 1 && segmentsDepuis[commun] === segmentsVers[commun]) {
|
|
100
|
+
commun += 1;
|
|
101
|
+
}
|
|
102
|
+
const montees = segmentsDepuis.length - commun;
|
|
103
|
+
const descente = segmentsVers.slice(commun).join("/");
|
|
104
|
+
return montees === 0 ? `./${descente}` : `${"../".repeat(montees)}${descente}`;
|
|
105
|
+
}
|
|
106
|
+
function rewriteImports(source, importPrefix, target) {
|
|
93
107
|
const prefix = importPrefix.replace(/\/$/, "");
|
|
94
|
-
|
|
108
|
+
if (estUnAlias(prefix) || target === void 0) {
|
|
109
|
+
return source.split(`${REGISTRY_TOKEN}/`).join(`${prefix}/`);
|
|
110
|
+
}
|
|
111
|
+
return source.replaceAll(
|
|
112
|
+
new RegExp(`${REGISTRY_TOKEN}/([^'"\\s]+)`, "g"),
|
|
113
|
+
(_tout, chemin) => cheminRelatif(target, chemin)
|
|
114
|
+
);
|
|
95
115
|
}
|
|
96
116
|
function usedTokens(source) {
|
|
97
117
|
const pattern = new RegExp(`${REGISTRY_TOKEN}/([\\w./-]+)`, "g");
|
|
@@ -134,7 +154,7 @@ async function inspectEntry(root, config, id, upstream) {
|
|
|
134
154
|
if (source !== void 0) {
|
|
135
155
|
served.set(
|
|
136
156
|
join(config.aliases.directory, file.target).replaceAll("\\", "/"),
|
|
137
|
-
rewriteImports(source, config.aliases.import)
|
|
157
|
+
rewriteImports(source, config.aliases.import, file.target)
|
|
138
158
|
);
|
|
139
159
|
}
|
|
140
160
|
}
|
|
@@ -401,7 +421,7 @@ async function planInstall(root, config, entries) {
|
|
|
401
421
|
await planWrite(
|
|
402
422
|
root,
|
|
403
423
|
targetPath(config, file.target),
|
|
404
|
-
rewriteImports(source, config.aliases.import),
|
|
424
|
+
rewriteImports(source, config.aliases.import, file.target),
|
|
405
425
|
entry.id
|
|
406
426
|
)
|
|
407
427
|
);
|
|
@@ -505,7 +525,7 @@ async function initCommand(options) {
|
|
|
505
525
|
const suggested = defaultAliases(guess);
|
|
506
526
|
if (guess === null) {
|
|
507
527
|
warn(
|
|
508
|
-
`Aucun alias trouve dans tsconfig.json : les
|
|
528
|
+
`Aucun alias trouve dans tsconfig.json : les composants iront dans ${colors.cyan(suggested.directory)}/ et s importeront entre eux en relatif.`
|
|
509
529
|
);
|
|
510
530
|
} else {
|
|
511
531
|
info(
|
|
@@ -71,11 +71,13 @@ function paquetsDe(modules) {
|
|
|
71
71
|
(m) => m.paquet
|
|
72
72
|
);
|
|
73
73
|
}
|
|
74
|
-
function
|
|
74
|
+
function variantesDe(modules) {
|
|
75
75
|
const choisis = new Set(modules);
|
|
76
|
-
|
|
77
|
-
if (!choisis.has("
|
|
78
|
-
|
|
76
|
+
const variantes = [];
|
|
77
|
+
if (!choisis.has("libs")) variantes.push("sans-libs");
|
|
78
|
+
else if (!choisis.has("router")) variantes.push("sans-routeur");
|
|
79
|
+
if (choisis.has("engine")) variantes.push("avec-moteur");
|
|
80
|
+
return variantes;
|
|
79
81
|
}
|
|
80
82
|
function gardeLesRoutes(modules) {
|
|
81
83
|
return modules.includes("router");
|
|
@@ -234,8 +236,10 @@ async function scaffold(options) {
|
|
|
234
236
|
await rm(join(options.target, dossier), { recursive: true, force: true });
|
|
235
237
|
}
|
|
236
238
|
}
|
|
237
|
-
const
|
|
238
|
-
const
|
|
239
|
+
const poses = [];
|
|
240
|
+
for (const variante of variantesDe(modules)) {
|
|
241
|
+
poses.push(...await poserVariante(source, options.target, variante));
|
|
242
|
+
}
|
|
239
243
|
await renamePackage(
|
|
240
244
|
options.target,
|
|
241
245
|
options.packageName,
|
|
@@ -447,7 +451,7 @@ async function createCommand(options) {
|
|
|
447
451
|
}
|
|
448
452
|
if (database !== void 0) prompts.log.info(database.note);
|
|
449
453
|
if (modules.includes("registre")) {
|
|
450
|
-
const { initCommand } = await import('./commands-
|
|
454
|
+
const { initCommand } = await import('./commands-AHMXBWQQ.js');
|
|
451
455
|
const code = await initCommand({ root: target, yes: true });
|
|
452
456
|
if (code !== 0) {
|
|
453
457
|
prompts.log.warn(
|
package/package.json
CHANGED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Le fond decoratif, en surface WebGL.
|
|
3
|
+
*
|
|
4
|
+
* Cette version remplace le degre statique parce que le moteur a ete retenu a
|
|
5
|
+
* la creation. Le reste de la page est inchange : elle place `<Fond />` et ne
|
|
6
|
+
* sait pas ce qu'il y a derriere.
|
|
7
|
+
*
|
|
8
|
+
* ## Ce que le moteur apporte, et qu'un degrade ne peut pas
|
|
9
|
+
*
|
|
10
|
+
* **Un arbitre.** La surface est demandee au gestionnaire du moteur, qui la
|
|
11
|
+
* refuse si le navigateur n'a pas WebGL, si trop de contextes sont deja
|
|
12
|
+
* ouverts, ou si l'appareil ne suivrait pas. Le refus est une valeur, pas une
|
|
13
|
+
* exception : on affiche alors le degrade, et la page tient.
|
|
14
|
+
*
|
|
15
|
+
* **Le mouvement reduit.** Sous cette preference, rien n'est monte du tout :
|
|
16
|
+
* le fond n'apportait que son mouvement, et la carte graphique n'a pas a
|
|
17
|
+
* s'allumer pour un visiteur qui a demande le calme.
|
|
18
|
+
*
|
|
19
|
+
* **Les couleurs du theme.** Les teintes sont lues dans les jetons, pas
|
|
20
|
+
* ecrites dans le nuanceur. Une bascule clair / sombre les relit, et changer
|
|
21
|
+
* `--o-palette-brand-500` repeint le fond sans toucher a ce fichier.
|
|
22
|
+
*
|
|
23
|
+
* ## Pourquoi des styles en ligne
|
|
24
|
+
*
|
|
25
|
+
* Ce fichier est pose quel que soit le reste : un projet qui a decoche les
|
|
26
|
+
* bibliotheques n'a pas les classes `o-*`, et une classe absente ne peint
|
|
27
|
+
* rien — le fond aurait alors une hauteur nulle, sans que rien ne le signale.
|
|
28
|
+
* Les quelques regles de placement sont donc ecrites ou elles sont sures.
|
|
29
|
+
*
|
|
30
|
+
* @module
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import {
|
|
34
|
+
AURORA_FRAGMENT,
|
|
35
|
+
readTokenColour,
|
|
36
|
+
useMotionState,
|
|
37
|
+
useShaderSurface,
|
|
38
|
+
type ShaderColour,
|
|
39
|
+
} from '@odoro-cli/engine'
|
|
40
|
+
import { useEffect, useMemo, useState, type ReactElement } from 'react'
|
|
41
|
+
|
|
42
|
+
/** Les jetons lus pour teinter la nappe. */
|
|
43
|
+
const TEINTES = ['--o-palette-brand-500', '--o-palette-brand-300', '--o-theme-bg'] as const
|
|
44
|
+
|
|
45
|
+
/** Le degrade servi tant que la surface n'est pas la — ou pour toujours. */
|
|
46
|
+
function Degrade(): ReactElement {
|
|
47
|
+
return (
|
|
48
|
+
<div
|
|
49
|
+
style={{
|
|
50
|
+
position: 'absolute',
|
|
51
|
+
inset: 0,
|
|
52
|
+
opacity: 0.28,
|
|
53
|
+
filter: 'blur(64px)',
|
|
54
|
+
background:
|
|
55
|
+
'radial-gradient(60% 60% at 50% 0%, var(--o-palette-brand-500, #f97316), transparent)',
|
|
56
|
+
}}
|
|
57
|
+
/>
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Une nappe animee derriere le haut de la page. */
|
|
62
|
+
export function Fond(): ReactElement {
|
|
63
|
+
const { quality, reduced } = useMotionState()
|
|
64
|
+
const [hote, setHote] = useState<HTMLElement | null>(null)
|
|
65
|
+
const [teintes, setTeintes] = useState<readonly ShaderColour[]>([])
|
|
66
|
+
|
|
67
|
+
// Relues quand l'hote apparait et quand le theme bascule — ce dernier cas
|
|
68
|
+
// passe par la politique de mouvement, qui se renouvelle a chaque etat.
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
if (hote === null) return
|
|
71
|
+
setTeintes(TEINTES.map((jeton) => readTokenColour(jeton, hote)))
|
|
72
|
+
}, [hote, reduced, quality])
|
|
73
|
+
|
|
74
|
+
const uniforms = useMemo(() => {
|
|
75
|
+
const [a, b, c] = teintes
|
|
76
|
+
if (a === undefined || b === undefined || c === undefined) return undefined
|
|
77
|
+
// Le fond du theme domine, la marque n'est qu'un reflet : ces trois
|
|
78
|
+
// couleurs vont dans un melange, et deux teintes de marque sur trois
|
|
79
|
+
// donneraient une nappe orange en pleine page plutot qu'un halo.
|
|
80
|
+
return { uColorA: c, uColorB: c, uColorC: a, uSpeed: 0.1, uScale: 1.4, uOctaves: 3 }
|
|
81
|
+
}, [teintes])
|
|
82
|
+
|
|
83
|
+
const { ref, ready, refused } = useShaderSurface<HTMLDivElement>({
|
|
84
|
+
fragment: AURORA_FRAGMENT,
|
|
85
|
+
uniforms: uniforms ?? {},
|
|
86
|
+
name: 'fond-accueil',
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
const repli = !ready || refused !== undefined || uniforms === undefined
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<div
|
|
93
|
+
aria-hidden="true"
|
|
94
|
+
ref={(element) => {
|
|
95
|
+
setHote(element)
|
|
96
|
+
ref.current = element
|
|
97
|
+
}}
|
|
98
|
+
style={{
|
|
99
|
+
position: 'absolute',
|
|
100
|
+
insetInline: 0,
|
|
101
|
+
top: 0,
|
|
102
|
+
height: '42rem',
|
|
103
|
+
overflow: 'hidden',
|
|
104
|
+
pointerEvents: 'none',
|
|
105
|
+
// Un fond decoratif se remarque quand on le cherche, pas avant. A
|
|
106
|
+
// pleine intensite la nappe passe devant le titre qu'elle est censee
|
|
107
|
+
// porter ; a un cinquieme, elle le souligne.
|
|
108
|
+
opacity: 0.2,
|
|
109
|
+
// Le bas se dissout dans la page : une surface qui s'arrete net
|
|
110
|
+
// dessine une ligne horizontale que rien ne justifie.
|
|
111
|
+
maskImage: 'linear-gradient(to bottom, black 45%, transparent)',
|
|
112
|
+
WebkitMaskImage: 'linear-gradient(to bottom, black 45%, transparent)',
|
|
113
|
+
}}
|
|
114
|
+
>
|
|
115
|
+
{repli ? <Degrade /> : null}
|
|
116
|
+
</div>
|
|
117
|
+
)
|
|
118
|
+
}
|
|
@@ -1,71 +1,42 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { ReactElement } from 'react'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
},
|
|
9
|
-
{
|
|
10
|
-
title: 'TypeScript',
|
|
11
|
-
body: 'Types stricts, alias de chemins, et verification sans emission.',
|
|
12
|
-
},
|
|
13
|
-
{
|
|
14
|
-
title: 'A vous de jouer',
|
|
15
|
-
body: 'Aucun systeme de style impose : le votre prend toute la place.',
|
|
16
|
-
},
|
|
17
|
-
]
|
|
3
|
+
import { Marque } from '@/composants/Marque'
|
|
4
|
+
import { Cloture } from '@/sections/Cloture'
|
|
5
|
+
import { Fond } from '@/sections/Fond'
|
|
6
|
+
import { Hero } from '@/sections/Hero'
|
|
7
|
+
import { Piliers } from '@/sections/Piliers'
|
|
18
8
|
|
|
19
9
|
/**
|
|
20
10
|
* Racine de l'application.
|
|
21
11
|
*
|
|
22
12
|
* Les bibliotheques Odoro n'ont pas ete retenues a la creation : pas de
|
|
23
|
-
* classes `o-*`, pas de jetons, pas de routeur.
|
|
24
|
-
*
|
|
13
|
+
* classes `o-*`, pas de jetons, pas de routeur. Le dessin est celui de la
|
|
14
|
+
* version complete, porte par `styles.css` en CSS ordinaire.
|
|
25
15
|
*
|
|
26
16
|
* Pour les ajouter plus tard : `npm i @odoro-cli/libs`, puis importer
|
|
27
17
|
* `@odoro-cli/libs/styles.css` dans `main.tsx`.
|
|
28
18
|
*/
|
|
29
|
-
export function App() {
|
|
30
|
-
const [count, setCount] = useState(0)
|
|
31
|
-
|
|
19
|
+
export function App(): ReactElement {
|
|
32
20
|
return (
|
|
33
21
|
<div className="app-shell">
|
|
34
22
|
<nav className="nav">
|
|
35
|
-
<
|
|
23
|
+
<Marque />
|
|
24
|
+
<a href="https://odoro.dev" target="_blank" rel="noreferrer" className="nav-lien">
|
|
25
|
+
odoro.dev
|
|
26
|
+
</a>
|
|
36
27
|
</nav>
|
|
37
28
|
|
|
38
|
-
<main className="
|
|
39
|
-
<
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
</p>
|
|
44
|
-
|
|
45
|
-
<div className="grille">
|
|
46
|
-
{FEATURES.map((feature) => (
|
|
47
|
-
<article key={feature.title} className="carte">
|
|
48
|
-
<h2>{feature.title}</h2>
|
|
49
|
-
<p>{feature.body}</p>
|
|
50
|
-
</article>
|
|
51
|
-
))}
|
|
52
|
-
</div>
|
|
53
|
-
|
|
54
|
-
<div className="ligne">
|
|
55
|
-
<button
|
|
56
|
-
type="button"
|
|
57
|
-
className="bouton"
|
|
58
|
-
onClick={() => {
|
|
59
|
-
setCount((value) => value + 1)
|
|
60
|
-
}}
|
|
61
|
-
>
|
|
62
|
-
Compter
|
|
63
|
-
</button>
|
|
64
|
-
<span className="compteur">{count}</span>
|
|
65
|
-
</div>
|
|
29
|
+
<main className="principal">
|
|
30
|
+
<Fond />
|
|
31
|
+
<Hero />
|
|
32
|
+
<Piliers />
|
|
33
|
+
<Cloture />
|
|
66
34
|
</main>
|
|
67
35
|
|
|
68
|
-
<footer className="pied">
|
|
36
|
+
<footer className="pied">
|
|
37
|
+
<Marque />
|
|
38
|
+
<span>Construit avec Odoro.</span>
|
|
39
|
+
</footer>
|
|
69
40
|
</div>
|
|
70
41
|
)
|
|
71
42
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* La marque : le signe et le mot.
|
|
3
|
+
*
|
|
4
|
+
* Meme signe que dans la version complete, dessine en SVG avec `currentColor`
|
|
5
|
+
* pour prendre la couleur de son parent. Les bibliotheques n'ayant pas ete
|
|
6
|
+
* retenues, la teinte vient d'une variable declaree dans `styles.css`.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { ReactElement } from 'react'
|
|
12
|
+
|
|
13
|
+
/** Le signe seul, a la taille de la police qui l'entoure. */
|
|
14
|
+
export function Signe({ className = '' }: { readonly className?: string }): ReactElement {
|
|
15
|
+
return (
|
|
16
|
+
<svg viewBox="0 0 100 100" fill="none" aria-hidden="true" className={`signe ${className}`}>
|
|
17
|
+
<path
|
|
18
|
+
d="M20.25 20.25H50a29.75 29.75 0 1 1-29.75 29.75Z"
|
|
19
|
+
stroke="currentColor"
|
|
20
|
+
strokeWidth="10.5"
|
|
21
|
+
strokeLinejoin="miter"
|
|
22
|
+
/>
|
|
23
|
+
</svg>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Le signe et le mot. */
|
|
28
|
+
export function Marque(): ReactElement {
|
|
29
|
+
return (
|
|
30
|
+
<span className="marque">
|
|
31
|
+
<span className="marque-signe">
|
|
32
|
+
<Signe />
|
|
33
|
+
</span>
|
|
34
|
+
Odoro
|
|
35
|
+
</span>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* La cloture : une commande, et la porte de sortie vers la documentation.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useState, type ReactElement } from 'react'
|
|
8
|
+
|
|
9
|
+
/** La commande que l'on copie le plus souvent. */
|
|
10
|
+
const COMMANDE = 'npm i @odoro-cli/libs'
|
|
11
|
+
|
|
12
|
+
/** La section de cloture. */
|
|
13
|
+
export function Cloture(): ReactElement {
|
|
14
|
+
const [copie, setCopie] = useState(false)
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<section className="section section-centre">
|
|
18
|
+
<div className="contenu-etroit">
|
|
19
|
+
<h2 className="cloture-titre">Ajoutez le systeme de style quand vous voudrez.</h2>
|
|
20
|
+
<p className="cloture-chapeau">
|
|
21
|
+
Les jetons, les utilitaires et les composants vivent dans un paquet a part.
|
|
22
|
+
Ce projet tourne sans eux, et les accueille sans rien casser.
|
|
23
|
+
</p>
|
|
24
|
+
|
|
25
|
+
<div className="commande">
|
|
26
|
+
<code>{COMMANDE}</code>
|
|
27
|
+
<button
|
|
28
|
+
type="button"
|
|
29
|
+
className="bouton bouton-fantome"
|
|
30
|
+
onClick={() => {
|
|
31
|
+
// Le presse-papiers peut etre refuse — hors contexte sur, ou
|
|
32
|
+
// permission retiree. L'echec ne casse pas la page : le libelle
|
|
33
|
+
// ne change simplement pas.
|
|
34
|
+
void navigator.clipboard
|
|
35
|
+
?.writeText(COMMANDE)
|
|
36
|
+
.then(() => {
|
|
37
|
+
setCopie(true)
|
|
38
|
+
setTimeout(() => {
|
|
39
|
+
setCopie(false)
|
|
40
|
+
}, 1600)
|
|
41
|
+
})
|
|
42
|
+
.catch(() => undefined)
|
|
43
|
+
}}
|
|
44
|
+
>
|
|
45
|
+
{copie ? 'Copie' : 'Copier'}
|
|
46
|
+
</button>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<a
|
|
50
|
+
href="https://odoro.dev/docs/installation"
|
|
51
|
+
target="_blank"
|
|
52
|
+
rel="noreferrer"
|
|
53
|
+
className="lien-discret"
|
|
54
|
+
>
|
|
55
|
+
Lire la marche a suivre
|
|
56
|
+
</a>
|
|
57
|
+
</div>
|
|
58
|
+
</section>
|
|
59
|
+
)
|
|
60
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Le fond decoratif de la page.
|
|
3
|
+
*
|
|
4
|
+
* Deux nappes de couleur de marque, tres diluees. Rien ne bouge : c'est un
|
|
5
|
+
* degrade, pas une animation.
|
|
6
|
+
*
|
|
7
|
+
* Si le moteur a ete retenu, ce fichier est remplace par une version qui ouvre
|
|
8
|
+
* une surface WebGL animee.
|
|
9
|
+
*
|
|
10
|
+
* @module
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { ReactElement } from 'react'
|
|
14
|
+
|
|
15
|
+
/** Deux nappes de marque derriere le haut de la page. */
|
|
16
|
+
export function Fond(): ReactElement {
|
|
17
|
+
return (
|
|
18
|
+
<div className="fond" aria-hidden="true">
|
|
19
|
+
<div className="fond-nappe fond-nappe-haute" />
|
|
20
|
+
<div className="fond-nappe fond-nappe-droite" />
|
|
21
|
+
</div>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Le hero : la promesse, et la fenetre du produit.
|
|
3
|
+
*
|
|
4
|
+
* Meme dessin que la version avec bibliotheques — titre serre, pastilles de
|
|
5
|
+
* faits, fenetre de navigateur — en CSS ordinaire. Ce qui manque ici est
|
|
6
|
+
* l'animation d'entree : elle venait de `@odoro-cli/libs/motion`, et il n'y a
|
|
7
|
+
* pas lieu de la reecrire a la main dans un projet de depart.
|
|
8
|
+
*
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { ReactElement } from 'react'
|
|
13
|
+
|
|
14
|
+
import { Signe } from '@/composants/Marque'
|
|
15
|
+
|
|
16
|
+
/** Trois faits, pas trois slogans. */
|
|
17
|
+
const FAITS = ['Zero configuration', 'TypeScript strict', 'Rechargement a chaud']
|
|
18
|
+
|
|
19
|
+
/** Ce que la fenetre montre. */
|
|
20
|
+
const EXTRAIT = `export function App() {
|
|
21
|
+
return (
|
|
22
|
+
<h1 className="titre">
|
|
23
|
+
Votre premiere page.
|
|
24
|
+
</h1>
|
|
25
|
+
)
|
|
26
|
+
}`
|
|
27
|
+
|
|
28
|
+
/** Une fenetre de navigateur miniature. */
|
|
29
|
+
function Fenetre(): ReactElement {
|
|
30
|
+
return (
|
|
31
|
+
<div className="fenetre">
|
|
32
|
+
<div className="fenetre-barre">
|
|
33
|
+
<span className="fenetre-pastilles" aria-hidden="true">
|
|
34
|
+
<span />
|
|
35
|
+
<span />
|
|
36
|
+
<span />
|
|
37
|
+
</span>
|
|
38
|
+
<span className="fenetre-chemin">src/App.tsx</span>
|
|
39
|
+
</div>
|
|
40
|
+
<pre className="fenetre-code">{EXTRAIT}</pre>
|
|
41
|
+
</div>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Le hero de la page d'accueil. */
|
|
46
|
+
export function Hero(): ReactElement {
|
|
47
|
+
return (
|
|
48
|
+
<section className="hero">
|
|
49
|
+
<div className="contenu">
|
|
50
|
+
<span className="pastille-marque">
|
|
51
|
+
<Signe />
|
|
52
|
+
Genere par odoro create
|
|
53
|
+
</span>
|
|
54
|
+
|
|
55
|
+
<h1 className="hero-titre">
|
|
56
|
+
Votre premiere page, <span className="accent">deja vivante</span>.
|
|
57
|
+
</h1>
|
|
58
|
+
|
|
59
|
+
<p className="hero-chapeau">
|
|
60
|
+
Le serveur de developpement, la compilation et le rechargement a chaud
|
|
61
|
+
viennent de <code>odoro</code>. Le systeme de style est le votre.
|
|
62
|
+
</p>
|
|
63
|
+
|
|
64
|
+
<div className="hero-actions">
|
|
65
|
+
<a href="#piliers" className="bouton bouton-primaire">
|
|
66
|
+
Voir ce qui est inclus
|
|
67
|
+
</a>
|
|
68
|
+
<a
|
|
69
|
+
href="https://odoro.dev/docs"
|
|
70
|
+
target="_blank"
|
|
71
|
+
rel="noreferrer"
|
|
72
|
+
className="bouton bouton-secondaire"
|
|
73
|
+
>
|
|
74
|
+
Documentation
|
|
75
|
+
</a>
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<div className="faits">
|
|
79
|
+
{FAITS.map((fait) => (
|
|
80
|
+
<span key={fait} className="fait">
|
|
81
|
+
{fait}
|
|
82
|
+
</span>
|
|
83
|
+
))}
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<div className="hero-fenetre">
|
|
87
|
+
<Fenetre />
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
</section>
|
|
91
|
+
)
|
|
92
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trois piliers : ce que le projet a deja sous la main.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ReactElement } from 'react'
|
|
8
|
+
|
|
9
|
+
/** Un pilier : un titre, une phrase. */
|
|
10
|
+
const PILIERS: readonly { readonly titre: string; readonly texte: string }[] = [
|
|
11
|
+
{
|
|
12
|
+
titre: 'Compilation',
|
|
13
|
+
texte:
|
|
14
|
+
'Un serveur de developpement a rechargement a chaud, et une compilation de production.',
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
titre: 'TypeScript',
|
|
18
|
+
texte: 'Types stricts, alias de chemins, et verification sans emission.',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
titre: 'A vous de jouer',
|
|
22
|
+
texte: 'Aucun systeme de style impose : le votre prend toute la place.',
|
|
23
|
+
},
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
/** La section des piliers. */
|
|
27
|
+
export function Piliers(): ReactElement {
|
|
28
|
+
return (
|
|
29
|
+
<section id="piliers" className="section">
|
|
30
|
+
<div className="contenu">
|
|
31
|
+
<h2 className="sur-titre">Ce qui est deja la</h2>
|
|
32
|
+
<div className="grille">
|
|
33
|
+
{PILIERS.map((pilier, index) => (
|
|
34
|
+
<article key={pilier.titre} className="carte">
|
|
35
|
+
<span className="carte-numero">{String(index + 1).padStart(2, '0')}</span>
|
|
36
|
+
<h3>{pilier.titre}</h3>
|
|
37
|
+
<p>{pilier.texte}</p>
|
|
38
|
+
</article>
|
|
39
|
+
))}
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
</section>
|
|
43
|
+
)
|
|
44
|
+
}
|