odoro 1.0.1 → 1.0.3
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 +73 -0
- package/dist/build-KI7IEVRJ.js +4 -0
- package/dist/{chunk-R6PMAMXA.js → chunk-2EE5QUCW.js} +16 -3
- package/dist/{chunk-CJIUP7A3.js → chunk-FJYUQALD.js} +1 -1
- package/dist/cli.js +4 -4
- package/dist/{create-LTT3SAWT.js → create-VUSKHXBN.js} +109 -65
- package/dist/index.js +2 -2
- package/dist/{package-LK46RV2I.js → package-2JOBLFXE.js} +3 -2
- package/dist/registry/index.d.ts +2 -2
- package/dist/{server-ATGFRVSW.js → server-DXOQAPVX.js} +1 -1
- package/package.json +3 -2
- package/templates/react-ts/_variantes/avec-moteur/src/{sections/Fond.tsx → fond.tsx} +13 -13
- package/templates/react-ts/_variantes/sans-libs/src/App.tsx +363 -23
- package/templates/{react-ts-server/_variantes/sans-libs/client/src/sections/Fond.tsx → react-ts/_variantes/sans-libs/src/fond.tsx} +1 -1
- package/templates/react-ts/_variantes/sans-libs/src/styles.css +115 -38
- package/templates/react-ts/_variantes/sans-routeur/src/App.tsx +427 -22
- package/templates/react-ts/index.html +11 -0
- package/templates/react-ts/public/favicon.svg +2 -3
- package/templates/react-ts/src/App.tsx +585 -57
- package/templates/{react-ts-server/client/src/sections/Fond.tsx → react-ts/src/fond.tsx} +9 -9
- package/templates/react-ts/src/router.tsx +72 -0
- package/templates/react-ts-server/_variantes/avec-moteur/client/src/{sections/Fond.tsx → fond.tsx} +13 -13
- package/templates/react-ts-server/_variantes/sans-libs/client/src/App.tsx +363 -23
- package/templates/{react-ts/_variantes/sans-libs/src/sections/Fond.tsx → react-ts-server/_variantes/sans-libs/client/src/fond.tsx} +1 -1
- package/templates/react-ts-server/_variantes/sans-libs/client/src/styles.css +115 -38
- package/templates/react-ts-server/_variantes/sans-routeur/client/src/App.tsx +427 -22
- package/templates/react-ts-server/client/index.html +11 -0
- package/templates/react-ts-server/client/public/favicon.svg +2 -3
- package/templates/react-ts-server/client/src/App.tsx +585 -57
- package/templates/{react-ts/src/sections/Fond.tsx → react-ts-server/client/src/fond.tsx} +9 -9
- package/templates/react-ts-server/client/src/router.tsx +72 -0
- package/dist/build-GIPMPYJ3.js +0 -4
- package/templates/react-ts/_variantes/sans-libs/src/composants/Marque.tsx +0 -37
- package/templates/react-ts/_variantes/sans-libs/src/sections/Cloture.tsx +0 -60
- package/templates/react-ts/_variantes/sans-libs/src/sections/Hero.tsx +0 -92
- package/templates/react-ts/_variantes/sans-libs/src/sections/Piliers.tsx +0 -44
- package/templates/react-ts/src/composants/Marque.tsx +0 -50
- package/templates/react-ts/src/routes/About.tsx +0 -43
- package/templates/react-ts/src/routes/Home.tsx +0 -28
- package/templates/react-ts/src/routes/NotFound.tsx +0 -14
- package/templates/react-ts/src/sections/Cloture.tsx +0 -69
- package/templates/react-ts/src/sections/Hero.tsx +0 -119
- package/templates/react-ts/src/sections/Piliers.tsx +0 -68
- package/templates/react-ts-server/_variantes/sans-libs/client/src/composants/Marque.tsx +0 -37
- package/templates/react-ts-server/_variantes/sans-libs/client/src/sections/Cloture.tsx +0 -60
- package/templates/react-ts-server/_variantes/sans-libs/client/src/sections/Hero.tsx +0 -92
- package/templates/react-ts-server/_variantes/sans-libs/client/src/sections/Piliers.tsx +0 -44
- package/templates/react-ts-server/client/src/composants/Marque.tsx +0 -50
- package/templates/react-ts-server/client/src/routes/About.tsx +0 -43
- package/templates/react-ts-server/client/src/routes/Home.tsx +0 -28
- package/templates/react-ts-server/client/src/routes/NotFound.tsx +0 -14
- package/templates/react-ts-server/client/src/sections/Cloture.tsx +0 -69
- package/templates/react-ts-server/client/src/sections/Hero.tsx +0 -119
- package/templates/react-ts-server/client/src/sections/Piliers.tsx +0 -68
|
@@ -1,48 +1,453 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Toute la page, en un fichier.
|
|
3
|
+
*
|
|
4
|
+
* ## Une seule page
|
|
5
|
+
*
|
|
6
|
+
* Le routeur n'a pas ete retenu a la creation : il n'y a donc ni `router.tsx`
|
|
7
|
+
* ni navigation entre pages, et les sections se suivent. Le dessin est le meme
|
|
8
|
+
* que dans la version routee.
|
|
9
|
+
*
|
|
10
|
+
* Pour en ajouter un plus tard, `@odoro-cli/libs/router` est deja installe : il
|
|
11
|
+
* vient avec les bibliotheques.
|
|
12
|
+
*
|
|
13
|
+
* ## La barre de navigation
|
|
14
|
+
*
|
|
15
|
+
* Elle ne peint pas de bandeau : ce sont des gelules posees sur ce qu'il y a
|
|
16
|
+
* derriere, avec un flou d'arriere-plan. Le fond decoratif continue donc de
|
|
17
|
+
* passer dessous, au lieu d'etre coupe par une bande opaque.
|
|
18
|
+
*
|
|
19
|
+
* @module
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { Reveal, Stagger } from '@odoro-cli/libs/motion'
|
|
23
|
+
import { buttonClasses } from '@odoro-cli/libs/ui'
|
|
24
|
+
import { useEffect, useState, type CSSProperties, type ReactElement } from 'react'
|
|
25
|
+
|
|
26
|
+
import { Fond } from '@/fond'
|
|
2
27
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
import { Hero } from '@/sections/Hero'
|
|
7
|
-
import { Piliers } from '@/sections/Piliers'
|
|
28
|
+
/* -------------------------------------------------------------------------- */
|
|
29
|
+
/* Marque */
|
|
30
|
+
/* -------------------------------------------------------------------------- */
|
|
8
31
|
|
|
9
32
|
/**
|
|
10
|
-
*
|
|
33
|
+
* Le signe de la marque.
|
|
11
34
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
35
|
+
* La taille est en style et non en classe : une classe utilitaire a valeur
|
|
36
|
+
* arbitraire n'est emise que si le compilateur l'a vue passer, et une classe
|
|
37
|
+
* absente ne peint rien — le signe serait sans dimensions, donc invisible.
|
|
38
|
+
*/
|
|
39
|
+
function Signe({ taille = '1.75rem' }: { readonly taille?: string }): ReactElement {
|
|
40
|
+
return (
|
|
41
|
+
<svg
|
|
42
|
+
viewBox="0 0 100 100"
|
|
43
|
+
fill="none"
|
|
44
|
+
aria-hidden="true"
|
|
45
|
+
style={{ width: taille, height: taille, flexShrink: 0 }}
|
|
46
|
+
>
|
|
47
|
+
<path
|
|
48
|
+
d="M20.25 20.25H50a29.75 29.75 0 1 1-29.75 29.75Z"
|
|
49
|
+
stroke="currentColor"
|
|
50
|
+
strokeWidth="10.5"
|
|
51
|
+
strokeLinejoin="miter"
|
|
52
|
+
/>
|
|
53
|
+
</svg>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* -------------------------------------------------------------------------- */
|
|
58
|
+
/* Barre de navigation */
|
|
59
|
+
/* -------------------------------------------------------------------------- */
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* La surface d'une gelule.
|
|
15
63
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
64
|
+
* Le systeme decline les echelles de noir et de blanc, mais pas leurs variantes
|
|
65
|
+
* sombres en alpha. Le melange se fait donc en style, ou il est exact et suit
|
|
66
|
+
* le theme sans qu'on ait a redire chaque couleur.
|
|
18
67
|
*/
|
|
19
|
-
|
|
68
|
+
const GELULE: CSSProperties = {
|
|
69
|
+
backgroundColor: 'color-mix(in oklab, var(--o-theme-bg) 72%, transparent)',
|
|
70
|
+
borderColor: 'color-mix(in oklab, var(--o-theme-fg) 12%, transparent)',
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** La barre : la marque, et le lien vers le site. */
|
|
74
|
+
function Barre(): ReactElement {
|
|
20
75
|
return (
|
|
21
|
-
<
|
|
22
|
-
<
|
|
23
|
-
<
|
|
24
|
-
|
|
76
|
+
<header className="o-sticky o-top-0 o-z-20 o-px-5 md:o-px-8">
|
|
77
|
+
<div className="o-mx-auto o-flex o-h-20 o-w-full o-max-w-5xl o-items-center o-gap-3">
|
|
78
|
+
<span
|
|
79
|
+
className="o-inline-flex o-h-14 o-shrink-0 o-items-center o-gap-2.5 o-rounded-full o-border-w-1 o-pl-5 o-pr-6 o-backdrop-blur-xl"
|
|
80
|
+
style={GELULE}
|
|
81
|
+
>
|
|
82
|
+
<span className="o-text-brand-500">
|
|
83
|
+
<Signe />
|
|
84
|
+
</span>
|
|
85
|
+
<span className="o-text-lg o-font-bold o-tracking-wide o-text-zinc-950 dark:o-text-white">
|
|
86
|
+
ODORO
|
|
87
|
+
</span>
|
|
88
|
+
</span>
|
|
89
|
+
|
|
90
|
+
<div className="o-ml-auto o-flex o-items-center o-gap-3">
|
|
25
91
|
<a
|
|
26
92
|
href="https://odoro.dev"
|
|
27
93
|
target="_blank"
|
|
28
94
|
rel="noreferrer"
|
|
29
|
-
className="o-
|
|
95
|
+
className="o-inline-flex o-h-14 o-shrink-0 o-items-center o-rounded-full o-border-w-1 o-px-5 o-text-sm o-no-underline o-backdrop-blur-xl o-text-zinc-600 dark:o-text-zinc-300 hover:o-text-zinc-950 dark:hover:o-text-white"
|
|
96
|
+
style={GELULE}
|
|
30
97
|
>
|
|
31
98
|
odoro.dev
|
|
32
99
|
</a>
|
|
100
|
+
<BasculeTheme />
|
|
33
101
|
</div>
|
|
34
|
-
</
|
|
102
|
+
</div>
|
|
103
|
+
</header>
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/* -------------------------------------------------------------------------- */
|
|
108
|
+
/* Theme */
|
|
109
|
+
/* -------------------------------------------------------------------------- */
|
|
110
|
+
|
|
111
|
+
/** Les trois etats de la bascule. */
|
|
112
|
+
type Theme = 'systeme' | 'clair' | 'sombre'
|
|
113
|
+
|
|
114
|
+
/** Ou le choix est memorise. Le script de `index.html` lit la meme cle. */
|
|
115
|
+
const CLE_THEME = 'odoro-theme'
|
|
116
|
+
|
|
117
|
+
/** L'ordre du cycle, au clic. */
|
|
118
|
+
const CYCLE: readonly Theme[] = ['systeme', 'clair', 'sombre']
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Pose le theme sur la racine du document.
|
|
122
|
+
*
|
|
123
|
+
* `systeme` retire l'attribut plutot que d'en poser un troisieme : la feuille
|
|
124
|
+
* de style ecoute `data-theme`, et son absence rend la main a la preference du
|
|
125
|
+
* navigateur. C'est ce qui permet a une page laissee en « systeme » de suivre
|
|
126
|
+
* le passage en nuit sans etre rouverte.
|
|
127
|
+
*/
|
|
128
|
+
function poserTheme(theme: Theme): void {
|
|
129
|
+
if (theme === 'systeme') delete document.documentElement.dataset.theme
|
|
130
|
+
else document.documentElement.dataset.theme = theme === 'clair' ? 'light' : 'dark'
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Le theme memorise, ou `systeme`. */
|
|
134
|
+
function themeMemorise(): Theme {
|
|
135
|
+
try {
|
|
136
|
+
const valeur = localStorage.getItem(CLE_THEME)
|
|
137
|
+
if (valeur === 'light') return 'clair'
|
|
138
|
+
if (valeur === 'dark') return 'sombre'
|
|
139
|
+
return 'systeme'
|
|
140
|
+
} catch {
|
|
141
|
+
// Stockage refuse — fenetre privee, cookies bloques. Le theme du systeme
|
|
142
|
+
// reste une reponse valable ; echouer ici priverait la page de son rendu.
|
|
143
|
+
return 'systeme'
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Les trois pictogrammes, dessines plutot qu'importes : les icones sont optionnelles. */
|
|
148
|
+
function Pictogramme({ theme }: { readonly theme: Theme }): ReactElement {
|
|
149
|
+
const commun = {
|
|
150
|
+
viewBox: '0 0 24 24',
|
|
151
|
+
fill: 'none',
|
|
152
|
+
stroke: 'currentColor',
|
|
153
|
+
strokeWidth: 1.8,
|
|
154
|
+
strokeLinecap: 'round' as const,
|
|
155
|
+
strokeLinejoin: 'round' as const,
|
|
156
|
+
'aria-hidden': true,
|
|
157
|
+
style: { width: '1.15rem', height: '1.15rem' },
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (theme === 'clair') {
|
|
161
|
+
return (
|
|
162
|
+
<svg {...commun}>
|
|
163
|
+
<circle cx="12" cy="12" r="4" />
|
|
164
|
+
<path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4" />
|
|
165
|
+
</svg>
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (theme === 'sombre') {
|
|
170
|
+
return (
|
|
171
|
+
<svg {...commun}>
|
|
172
|
+
<path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8Z" />
|
|
173
|
+
</svg>
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return (
|
|
178
|
+
<svg {...commun}>
|
|
179
|
+
<rect x="2" y="4" width="20" height="13" rx="2" />
|
|
180
|
+
<path d="M8 21h8M12 17v4" />
|
|
181
|
+
</svg>
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* La bascule de theme.
|
|
187
|
+
*
|
|
188
|
+
* Trois etats et non deux : « systeme » est un choix a part entiere, et le
|
|
189
|
+
* retirer obligerait un visiteur a re-choisir a chaque changement de sa
|
|
190
|
+
* preference.
|
|
191
|
+
*/
|
|
192
|
+
function BasculeTheme(): ReactElement {
|
|
193
|
+
const [theme, setTheme] = useState<Theme>(() => themeMemorise())
|
|
194
|
+
|
|
195
|
+
useEffect(() => {
|
|
196
|
+
poserTheme(theme)
|
|
197
|
+
try {
|
|
198
|
+
if (theme === 'systeme') localStorage.removeItem(CLE_THEME)
|
|
199
|
+
else localStorage.setItem(CLE_THEME, theme === 'clair' ? 'light' : 'dark')
|
|
200
|
+
} catch {
|
|
201
|
+
// Le theme reste applique pour la session, meme sans stockage.
|
|
202
|
+
}
|
|
203
|
+
}, [theme])
|
|
204
|
+
|
|
205
|
+
return (
|
|
206
|
+
<button
|
|
207
|
+
type="button"
|
|
208
|
+
aria-label={`Theme : ${theme}`}
|
|
209
|
+
title={`Theme : ${theme}`}
|
|
210
|
+
onClick={() => {
|
|
211
|
+
setTheme((actuel) => CYCLE[(CYCLE.indexOf(actuel) + 1) % CYCLE.length] ?? 'systeme')
|
|
212
|
+
}}
|
|
213
|
+
className="o-inline-flex o-size-14 o-shrink-0 o-cursor-pointer o-items-center o-justify-center o-rounded-full o-border-w-1 o-backdrop-blur-xl o-text-zinc-600 dark:o-text-zinc-300 hover:o-text-zinc-950 dark:hover:o-text-white"
|
|
214
|
+
style={GELULE}
|
|
215
|
+
>
|
|
216
|
+
<Pictogramme theme={theme} />
|
|
217
|
+
</button>
|
|
218
|
+
)
|
|
219
|
+
}
|
|
35
220
|
|
|
221
|
+
/* -------------------------------------------------------------------------- */
|
|
222
|
+
/* Sections */
|
|
223
|
+
/* -------------------------------------------------------------------------- */
|
|
224
|
+
|
|
225
|
+
/** Trois faits, pas trois slogans. */
|
|
226
|
+
const FAITS = ['Zero configuration', 'Theme clair et sombre', 'Mouvement reduit respecte']
|
|
227
|
+
|
|
228
|
+
/** Ce que la fenetre du produit montre. */
|
|
229
|
+
const EXTRAIT = `export function App() {
|
|
230
|
+
return (
|
|
231
|
+
<Reveal>
|
|
232
|
+
<h1 className="o-text-6xl o-font-bold">
|
|
233
|
+
Votre premiere page.
|
|
234
|
+
</h1>
|
|
235
|
+
</Reveal>
|
|
236
|
+
)
|
|
237
|
+
}`
|
|
238
|
+
|
|
239
|
+
/** Une fenetre de navigateur miniature. */
|
|
240
|
+
function Fenetre(): ReactElement {
|
|
241
|
+
return (
|
|
242
|
+
<div className="o-overflow-hidden o-rounded-2xl o-border-w-1 o-border-zinc-200 dark:o-border-zinc-800 o-bg-zinc-50 dark:o-bg-zinc-900 o-shadow-2xl">
|
|
243
|
+
<div className="o-flex o-items-center o-gap-2 o-border-b o-border-zinc-200 dark:o-border-zinc-800 o-px-4 o-py-2.5">
|
|
244
|
+
<span className="o-flex o-gap-1.5" aria-hidden="true">
|
|
245
|
+
<span className="o-size-2.5 o-rounded-full o-bg-zinc-300 dark:o-bg-zinc-700" />
|
|
246
|
+
<span className="o-size-2.5 o-rounded-full o-bg-zinc-300 dark:o-bg-zinc-700" />
|
|
247
|
+
<span className="o-size-2.5 o-rounded-full o-bg-zinc-300 dark:o-bg-zinc-700" />
|
|
248
|
+
</span>
|
|
249
|
+
<span className="o-ml-2 o-font-mono o-text-xs o-text-zinc-400">src/App.tsx</span>
|
|
250
|
+
</div>
|
|
251
|
+
<pre className="o-m-0 o-overflow-x-auto o-p-5 o-font-mono o-text-xs o-leading-relaxed o-text-zinc-600 dark:o-text-zinc-300">
|
|
252
|
+
{EXTRAIT}
|
|
253
|
+
</pre>
|
|
254
|
+
</div>
|
|
255
|
+
)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/** Le hero : la promesse, les actions, et la fenetre du produit. */
|
|
259
|
+
function Hero(): ReactElement {
|
|
260
|
+
return (
|
|
261
|
+
<section className="o-relative o-px-6 o-pb-20 o-pt-16 md:o-pt-24">
|
|
262
|
+
<div className="o-mx-auto o-w-full o-max-w-5xl">
|
|
263
|
+
<Reveal>
|
|
264
|
+
<span className="o-inline-flex o-items-center o-gap-2 o-whitespace-nowrap o-rounded-full o-border-w-1 o-border-brand-500/30 o-bg-brand-500/10 o-px-3 o-py-1 o-text-xs o-font-medium o-text-brand-600 dark:o-text-brand-400">
|
|
265
|
+
<Signe taille="0.9rem" />
|
|
266
|
+
Genere par odoro create
|
|
267
|
+
</span>
|
|
268
|
+
|
|
269
|
+
<h1 className="o-mt-6 o-max-w-3xl o-text-5xl o-font-bold o-tracking-tight md:o-text-6xl">
|
|
270
|
+
Votre premiere page, <span className="o-text-brand-500">deja vivante</span>.
|
|
271
|
+
</h1>
|
|
272
|
+
|
|
273
|
+
<p className="o-mt-5 o-max-w-xl o-text-lg o-text-zinc-500 dark:o-text-zinc-400">
|
|
274
|
+
Le moteur d animation et le systeme de style viennent de la meme librairie.
|
|
275
|
+
Modifiez cette page : elle se recharge sans perdre son etat.
|
|
276
|
+
</p>
|
|
277
|
+
</Reveal>
|
|
278
|
+
|
|
279
|
+
<Stagger step={70} className="o-mt-8 o-flex o-flex-wrap o-items-center o-gap-3">
|
|
280
|
+
<a
|
|
281
|
+
href="#piliers"
|
|
282
|
+
className={`o-no-underline ${buttonClasses({ tone: 'primary' })}`}
|
|
283
|
+
>
|
|
284
|
+
Voir ce qui est inclus
|
|
285
|
+
</a>
|
|
286
|
+
<a
|
|
287
|
+
href="https://odoro.dev/docs"
|
|
288
|
+
target="_blank"
|
|
289
|
+
rel="noreferrer"
|
|
290
|
+
className={`o-no-underline ${buttonClasses({ tone: 'secondary' })}`}
|
|
291
|
+
>
|
|
292
|
+
Documentation
|
|
293
|
+
</a>
|
|
294
|
+
</Stagger>
|
|
295
|
+
|
|
296
|
+
<Stagger step={60} className="o-mt-6 o-flex o-flex-wrap o-gap-2">
|
|
297
|
+
{FAITS.map((fait) => (
|
|
298
|
+
<span
|
|
299
|
+
key={fait}
|
|
300
|
+
className="o-rounded-full o-border-w-1 o-border-zinc-200 dark:o-border-zinc-800 o-px-3 o-py-1 o-text-xs o-text-zinc-500 dark:o-text-zinc-400"
|
|
301
|
+
>
|
|
302
|
+
{fait}
|
|
303
|
+
</span>
|
|
304
|
+
))}
|
|
305
|
+
</Stagger>
|
|
306
|
+
|
|
307
|
+
<Reveal>
|
|
308
|
+
<div className="o-mt-14">
|
|
309
|
+
<Fenetre />
|
|
310
|
+
</div>
|
|
311
|
+
</Reveal>
|
|
312
|
+
</div>
|
|
313
|
+
</section>
|
|
314
|
+
)
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/** Ce que le projet a deja sous la main. */
|
|
318
|
+
const PILIERS = [
|
|
319
|
+
{
|
|
320
|
+
titre: 'Animations',
|
|
321
|
+
texte:
|
|
322
|
+
"Une couche mince sur le moteur du navigateur. Le mouvement reduit est respecte d'office.",
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
titre: 'Styles',
|
|
326
|
+
texte: 'Des jetons en source de verite, une feuille statique, aucun scan a l execution.',
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
titre: 'Interface',
|
|
330
|
+
texte: 'Des composants qui lisent les memes jetons que votre code : les rethemer suffit.',
|
|
331
|
+
},
|
|
332
|
+
]
|
|
333
|
+
|
|
334
|
+
/** La section des piliers. */
|
|
335
|
+
function Piliers(): ReactElement {
|
|
336
|
+
return (
|
|
337
|
+
<section
|
|
338
|
+
id="piliers"
|
|
339
|
+
className="o-border-t o-border-zinc-200 dark:o-border-zinc-800 o-px-6 o-py-20"
|
|
340
|
+
>
|
|
341
|
+
<div className="o-mx-auto o-w-full o-max-w-5xl">
|
|
342
|
+
<h2 className="o-text-sm o-font-semibold o-uppercase o-tracking-widest o-text-zinc-400">
|
|
343
|
+
Ce qui est deja la
|
|
344
|
+
</h2>
|
|
345
|
+
|
|
346
|
+
<Stagger step={90} className="o-mt-8 o-grid o-gap-4 md:o-grid-cols-3">
|
|
347
|
+
{PILIERS.map((pilier, index) => (
|
|
348
|
+
<article
|
|
349
|
+
key={pilier.titre}
|
|
350
|
+
className="o-rounded-xl o-border-w-1 o-border-zinc-200 dark:o-border-zinc-800 o-bg-white dark:o-bg-zinc-900 o-p-6"
|
|
351
|
+
>
|
|
352
|
+
<span className="o-font-mono o-text-xs o-text-brand-500">
|
|
353
|
+
{String(index + 1).padStart(2, '0')}
|
|
354
|
+
</span>
|
|
355
|
+
<h3 className="o-mt-3 o-text-lg o-font-semibold o-tracking-tight">
|
|
356
|
+
{pilier.titre}
|
|
357
|
+
</h3>
|
|
358
|
+
<p className="o-mt-2 o-text-sm o-text-zinc-500 dark:o-text-zinc-400">
|
|
359
|
+
{pilier.texte}
|
|
360
|
+
</p>
|
|
361
|
+
</article>
|
|
362
|
+
))}
|
|
363
|
+
</Stagger>
|
|
364
|
+
</div>
|
|
365
|
+
</section>
|
|
366
|
+
)
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/** La commande que l'on copie le plus souvent. */
|
|
370
|
+
const COMMANDE = 'odoro add text/count-up'
|
|
371
|
+
|
|
372
|
+
/** La cloture : une commande, et la porte vers le catalogue. */
|
|
373
|
+
function Cloture(): ReactElement {
|
|
374
|
+
const [copie, setCopie] = useState(false)
|
|
375
|
+
|
|
376
|
+
return (
|
|
377
|
+
<section className="o-border-t o-border-zinc-200 dark:o-border-zinc-800 o-px-6 o-py-24">
|
|
378
|
+
<div className="o-mx-auto o-w-full o-max-w-3xl o-text-center">
|
|
379
|
+
<Reveal>
|
|
380
|
+
<h2 className="o-text-3xl o-font-bold o-tracking-tight md:o-text-4xl">
|
|
381
|
+
Ajoutez un composant, sans quitter le terminal.
|
|
382
|
+
</h2>
|
|
383
|
+
<p className="o-mx-auto o-mt-4 o-max-w-prose o-text-zinc-500 dark:o-text-zinc-400">
|
|
384
|
+
Le registre copie le code dans votre projet. Il vous appartient : on le lit,
|
|
385
|
+
on le modifie, il ne se met pas a jour dans votre dos.
|
|
386
|
+
</p>
|
|
387
|
+
|
|
388
|
+
<div className="o-mx-auto o-mt-8 o-flex o-max-w-md o-items-center o-gap-2 o-rounded-xl o-border-w-1 o-border-zinc-200 dark:o-border-zinc-800 o-bg-zinc-50 dark:o-bg-zinc-900 o-p-2 o-pl-4">
|
|
389
|
+
<code className="o-flex-1 o-text-left o-font-mono o-text-sm">{COMMANDE}</code>
|
|
390
|
+
<button
|
|
391
|
+
type="button"
|
|
392
|
+
className={buttonClasses({ tone: 'ghost', size: 'sm' })}
|
|
393
|
+
onClick={() => {
|
|
394
|
+
// Le presse-papiers peut etre refuse — hors contexte sur, ou
|
|
395
|
+
// permission retiree. L'echec ne casse pas la page.
|
|
396
|
+
void navigator.clipboard
|
|
397
|
+
?.writeText(COMMANDE)
|
|
398
|
+
.then(() => {
|
|
399
|
+
setCopie(true)
|
|
400
|
+
setTimeout(() => {
|
|
401
|
+
setCopie(false)
|
|
402
|
+
}, 1600)
|
|
403
|
+
})
|
|
404
|
+
.catch(() => undefined)
|
|
405
|
+
}}
|
|
406
|
+
>
|
|
407
|
+
{copie ? 'Copie' : 'Copier'}
|
|
408
|
+
</button>
|
|
409
|
+
</div>
|
|
410
|
+
|
|
411
|
+
<a
|
|
412
|
+
href="https://odoro.dev/docs/registry"
|
|
413
|
+
target="_blank"
|
|
414
|
+
rel="noreferrer"
|
|
415
|
+
className="o-mt-6 o-inline-block o-text-sm o-text-zinc-500 dark:o-text-zinc-400 o-no-underline hover:o-text-brand-500"
|
|
416
|
+
>
|
|
417
|
+
Parcourir le catalogue
|
|
418
|
+
</a>
|
|
419
|
+
</Reveal>
|
|
420
|
+
</div>
|
|
421
|
+
</section>
|
|
422
|
+
)
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/* -------------------------------------------------------------------------- */
|
|
426
|
+
/* Racine */
|
|
427
|
+
/* -------------------------------------------------------------------------- */
|
|
428
|
+
|
|
429
|
+
/** Racine de l'application. */
|
|
430
|
+
export function App(): ReactElement {
|
|
431
|
+
return (
|
|
432
|
+
// `relative` porte le fond, pose ici et non dans le contenu : ancre plus
|
|
433
|
+
// bas, il commencait sous la barre et laissait une bande plus sombre
|
|
434
|
+
// derriere elle en haut de page.
|
|
435
|
+
<div className="app-shell o-relative">
|
|
436
|
+
<Fond />
|
|
437
|
+
<Barre />
|
|
36
438
|
<main className="o-relative">
|
|
37
|
-
<Fond />
|
|
38
439
|
<Hero />
|
|
39
440
|
<Piliers />
|
|
40
441
|
<Cloture />
|
|
41
442
|
</main>
|
|
42
|
-
|
|
43
443
|
<footer className="o-border-t o-border-zinc-200 dark:o-border-zinc-800 o-px-6 o-py-8">
|
|
44
444
|
<div className="o-mx-auto o-flex o-w-full o-max-w-5xl o-items-center o-justify-between o-gap-4 o-text-sm o-text-zinc-500 dark:o-text-zinc-400">
|
|
45
|
-
<
|
|
445
|
+
<span className="o-inline-flex o-items-center o-gap-2">
|
|
446
|
+
<span className="o-text-brand-500">
|
|
447
|
+
<Signe taille="1.1rem" />
|
|
448
|
+
</span>
|
|
449
|
+
<span className="o-font-semibold o-tracking-wide">ODORO</span>
|
|
450
|
+
</span>
|
|
46
451
|
<span>Construit avec Odoro.</span>
|
|
47
452
|
</div>
|
|
48
453
|
</footer>
|
|
@@ -4,7 +4,18 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
7
|
+
<meta name="theme-color" content="#3b82f6" />
|
|
7
8
|
<title>Odoro</title>
|
|
9
|
+
<script>
|
|
10
|
+
// Applique le theme memorise avant la premiere peinture. Le faire depuis
|
|
11
|
+
// React arriverait trop tard : un visiteur en theme force verrait un
|
|
12
|
+
// eclair de l'autre theme, le temps que le script se charge.
|
|
13
|
+
try {
|
|
14
|
+
var theme = localStorage.getItem('odoro-theme')
|
|
15
|
+
if (theme === 'light' || theme === 'dark')
|
|
16
|
+
document.documentElement.dataset.theme = theme
|
|
17
|
+
} catch (error) {}
|
|
18
|
+
</script>
|
|
8
19
|
</head>
|
|
9
20
|
<body>
|
|
10
21
|
<div id="root"></div>
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0
|
|
2
|
-
<
|
|
3
|
-
<circle cx="16" cy="16" r="7" fill="none" stroke="#fff" stroke-width="3" />
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" fill="none">
|
|
2
|
+
<path d="M20.25 20.25H50a29.75 29.75 0 1 1-29.75 29.75Z" stroke="#3b82f6" stroke-width="10.5" stroke-linejoin="miter"/>
|
|
4
3
|
</svg>
|