create-rasti 0.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.
Files changed (75) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +96 -0
  3. package/bin/create-rasti.js +4 -0
  4. package/extras/cn/README.md +57 -0
  5. package/extras/cn/package.json +9 -0
  6. package/extras/cn/src/index.js +37 -0
  7. package/extras/micro-router/README.md +78 -0
  8. package/extras/micro-router/package-lock.json +26 -0
  9. package/extras/micro-router/package.json +12 -0
  10. package/extras/micro-router/src/index.js +192 -0
  11. package/extras/rasti-icons/README.md +65 -0
  12. package/extras/rasti-icons/bin/rasti-icons.js +84 -0
  13. package/extras/rasti-icons/package.json +11 -0
  14. package/extras/rasti-icons/src/generate.js +119 -0
  15. package/extras/rasti-icons/src/presets.js +57 -0
  16. package/package.json +54 -0
  17. package/src/apply/base.js +29 -0
  18. package/src/apply/cssfun.js +38 -0
  19. package/src/apply/description.js +56 -0
  20. package/src/apply/featuresInclude.js +75 -0
  21. package/src/apply/icons.js +21 -0
  22. package/src/apply/index.js +134 -0
  23. package/src/apply/router.js +50 -0
  24. package/src/apply/ssr.js +29 -0
  25. package/src/apply/static.js +46 -0
  26. package/src/apply/tailwind.js +33 -0
  27. package/src/args.js +55 -0
  28. package/src/cli.js +91 -0
  29. package/src/plan.js +33 -0
  30. package/src/prompts.js +116 -0
  31. package/src/utils/copy.js +21 -0
  32. package/src/utils/exec.js +83 -0
  33. package/src/utils/logger.js +79 -0
  34. package/src/utils/pkg.js +87 -0
  35. package/src/utils/template.js +205 -0
  36. package/src/validate.js +48 -0
  37. package/src/versions.js +17 -0
  38. package/templates/AGENTS.md +48 -0
  39. package/templates/_base/App-cssfun.js +88 -0
  40. package/templates/_base/App-tailwind.js +58 -0
  41. package/templates/_base/App.js +58 -0
  42. package/templates/_base/components/Button-cssfun.js +51 -0
  43. package/templates/_base/components/Button-tailwind.js +52 -0
  44. package/templates/_base/components/Button.js +22 -0
  45. package/templates/_base/components/Header-cssfun.js +69 -0
  46. package/templates/_base/components/Header-tailwind.js +17 -0
  47. package/templates/_base/components/Header.js +17 -0
  48. package/templates/_base/components/Home-cssfun.js +98 -0
  49. package/templates/_base/components/Home-tailwind.js +35 -0
  50. package/templates/_base/components/Home.js +35 -0
  51. package/templates/_base/style.css +170 -0
  52. package/templates/_extras/router/components/About-cssfun.js +43 -0
  53. package/templates/_extras/router/components/About-tailwind.js +14 -0
  54. package/templates/_extras/router/components/About.js +16 -0
  55. package/templates/_extras/router/router-setup.js +60 -0
  56. package/templates/_features/cssfun/index.html +14 -0
  57. package/templates/_features/cssfun/theme.js +60 -0
  58. package/templates/_features/tailwind/style.css +26 -0
  59. package/templates/_features/tailwind/vite.config.js +8 -0
  60. package/templates/spa/index.html +14 -0
  61. package/templates/spa/package.json +17 -0
  62. package/templates/spa/public/.gitkeep +0 -0
  63. package/templates/spa/src/main.js +15 -0
  64. package/templates/spa/vite.config.js +6 -0
  65. package/templates/ssr/app.js +71 -0
  66. package/templates/ssr/index.html +16 -0
  67. package/templates/ssr/package.json +23 -0
  68. package/templates/ssr/public/.gitkeep +0 -0
  69. package/templates/ssr/server.js +7 -0
  70. package/templates/ssr/src/entry-client.js +15 -0
  71. package/templates/ssr/src/entry-server.js +49 -0
  72. package/templates/ssr/vite.config.js +6 -0
  73. package/templates/static/scripts/build-static.js +161 -0
  74. package/templates/static/scripts/serve-static.js +19 -0
  75. package/templates/static/static.config.js +14 -0
@@ -0,0 +1,58 @@
1
+ import { Component, Model } from 'rasti';
2
+ import Header from './components/Header.js';
3
+ import Home from './components/Home.js';
4
+ {{#if ROUTER}}
5
+ import About from './components/About.js';
6
+ import { createAppRouter } from './router-setup.js';
7
+ {{#endif}}
8
+
9
+ /**
10
+ * @typedef {Object} AppState
11
+ * @property {number} count
12
+ {{#if ROUTER}}
13
+ * @property {import('./router-setup.js').Location | null} location
14
+ {{#endif}}
15
+ */
16
+
17
+ /** Root application component.
18
+ * @type {typeof import('rasti').Component<{}, AppState>}
19
+ */
20
+ const App = Component.create`
21
+ <div class="font-sans flex min-h-dvh flex-col items-center gap-6 md:gap-10 max-w-[1000px] mx-auto p-5 md:p-12 box-border text-white">
22
+ <${Header} />
23
+ {{#if ROUTER}}
24
+ ${({ state, partial }) => state.location?.test('/') ?
25
+ partial`<${Home} count="${({ state }) => state.count}" handleIncrement="${({ state }) => () => { state.count++; }}" />` :
26
+ partial`<${About} />`}
27
+ {{#else}}
28
+ <${Home} count="${({ state }) => state.count}" handleIncrement="${({ state }) => () => { state.count++; }}" />
29
+ {{#endif}}
30
+ </div>
31
+ {{#if ROUTER}}
32
+ `.extend({
33
+ /**
34
+ * @param {Object} [options]
35
+ * @param {string} [options.url] - Initial URL for server-side routing.
36
+ */
37
+ onCreate(options = {}) {
38
+ this.state = new Model({ location : null, count : 0 });
39
+ this.router = createAppRouter(this.state);
40
+ const url = options.url ?? (typeof window !== 'undefined' ? window.location.pathname + window.location.search : '/');
41
+ this.router.navigate(url, { addToHistory : false });
42
+ },
43
+ onHydrate() {
44
+ this.destroyQueue.push(
45
+ this.router.delegateNavigation(this.el),
46
+ this.router.bindHistory()
47
+ );
48
+ }
49
+ });
50
+ {{#else}}
51
+ `.extend({
52
+ onCreate() {
53
+ this.state = new Model({ count : 0 });
54
+ }
55
+ });
56
+ {{#endif}}
57
+
58
+ export default App;
@@ -0,0 +1,58 @@
1
+ import { Component, Model } from 'rasti';
2
+ import Header from './components/Header.js';
3
+ import Home from './components/Home.js';
4
+ {{#if ROUTER}}
5
+ import About from './components/About.js';
6
+ import { createAppRouter } from './router-setup.js';
7
+ {{#endif}}
8
+
9
+ /**
10
+ * @typedef {Object} AppState
11
+ * @property {number} count
12
+ {{#if ROUTER}}
13
+ * @property {import('./router-setup.js').Location | null} location
14
+ {{#endif}}
15
+ */
16
+
17
+ /** Root application component.
18
+ * @type {typeof import('rasti').Component<{}, AppState>}
19
+ */
20
+ const App = Component.create`
21
+ <div class="app">
22
+ <${Header} />
23
+ {{#if ROUTER}}
24
+ ${({ state, partial }) => state.location?.test('/') ?
25
+ partial`<${Home} count="${({ state }) => state.count}" handleIncrement="${({ state }) => () => { state.count++; }}" />` :
26
+ partial`<${About} />`}
27
+ {{#else}}
28
+ <${Home} count="${({ state }) => state.count}" handleIncrement="${({ state }) => () => { state.count++; }}" />
29
+ {{#endif}}
30
+ </div>
31
+ {{#if ROUTER}}
32
+ `.extend({
33
+ /**
34
+ * @param {Object} [options]
35
+ * @param {string} [options.url] - Initial URL for server-side routing.
36
+ */
37
+ onCreate(options = {}) {
38
+ this.state = new Model({ location : null, count : 0 });
39
+ this.router = createAppRouter(this.state);
40
+ const url = options.url ?? (typeof window !== 'undefined' ? window.location.pathname + window.location.search : '/');
41
+ this.router.navigate(url, { addToHistory : false });
42
+ },
43
+ onHydrate() {
44
+ this.destroyQueue.push(
45
+ this.router.delegateNavigation(this.el),
46
+ this.router.bindHistory()
47
+ );
48
+ }
49
+ });
50
+ {{#else}}
51
+ `.extend({
52
+ onCreate() {
53
+ this.state = new Model({ count : 0 });
54
+ }
55
+ });
56
+ {{#endif}}
57
+
58
+ export default App;
@@ -0,0 +1,51 @@
1
+ import { Component } from 'rasti';
2
+ import { css } from 'cssfun';
3
+ import cn from '../lib/cn.js';
4
+
5
+ const { classes } = css({
6
+ root : {
7
+ display : 'inline-flex',
8
+ alignItems : 'center',
9
+ justifyContent : 'center',
10
+ minHeight : '52px',
11
+ minWidth : '200px',
12
+ padding : '0 32px',
13
+ fontSize : '20px',
14
+ fontWeight : '650',
15
+ color : 'inherit',
16
+ background : 'var(--fun-buttonBg)',
17
+ border : '1px solid var(--fun-buttonBorder)',
18
+ borderRadius : '999px',
19
+ cursor : 'pointer',
20
+ boxShadow : 'inset 0 1px 0 rgba(255, 255, 255, 0.1), var(--fun-buttonShadow)',
21
+ backdropFilter : 'blur(16px)',
22
+ transition : 'border-color 0.2s ease, background 0.2s ease, transform 0.15s ease',
23
+ '&:hover' : {
24
+ borderColor : 'var(--fun-buttonHoverBorder)',
25
+ background : 'var(--fun-buttonHoverBg)'
26
+ },
27
+ '&:active' : {
28
+ transform : 'scale(0.985)'
29
+ },
30
+ }
31
+ });
32
+
33
+ /**
34
+ * @typedef {Object} ButtonProps
35
+ * @property {Function} handleClick - Click handler.
36
+ * @property {string} [className] - Additional CSS classes.
37
+ */
38
+
39
+ /** Reusable styled button. Renders children via props.renderChildren().
40
+ * @type {typeof import('rasti').Component<ButtonProps>}
41
+ */
42
+ const Button = Component.create`
43
+ <button
44
+ class="${({ props }) => cn(classes.root, props.className)}"
45
+ onClick="${({ props }) => props.handleClick}"
46
+ >
47
+ ${({ props }) => props.renderChildren()}
48
+ </button>
49
+ `;
50
+
51
+ export default Button;
@@ -0,0 +1,52 @@
1
+ import { Component } from 'rasti';
2
+ import cn from '../lib/cn.js';
3
+
4
+ /**
5
+ * The base classes for the button.
6
+ */
7
+ const BASE_CLASSES = [
8
+ 'inline-flex',
9
+ 'items-center',
10
+ 'justify-center',
11
+ 'rounded-full',
12
+ 'border',
13
+ 'border-white/12',
14
+ 'bg-linear-to-b',
15
+ 'from-[#5ee9df]/20',
16
+ 'to-white/6',
17
+ 'text-white',
18
+ 'shadow-[inset_0_1px_0_rgba(255,255,255,0.1),0_6px_20px_rgba(0,0,0,0.18)]',
19
+ 'backdrop-blur-md',
20
+ 'transition-[border-color,background,transform]',
21
+ 'duration-200',
22
+ 'hover:border-[#5ee9df]/40',
23
+ 'hover:from-[#5ee9df]/28',
24
+ 'hover:to-white/10',
25
+ 'active:scale-[0.985]',
26
+ 'active:duration-150',
27
+ 'min-h-13',
28
+ 'min-w-[200px]',
29
+ 'px-8',
30
+ 'text-xl',
31
+ 'font-[650]'
32
+ ];
33
+
34
+ /**
35
+ * @typedef {Object} ButtonProps
36
+ * @property {Function} handleClick - Click handler.
37
+ * @property {string} [className] - Additional CSS classes.
38
+ */
39
+
40
+ /** Reusable styled button. Renders children via props.renderChildren().
41
+ * @type {typeof import('rasti').Component<ButtonProps>}
42
+ */
43
+ const Button = Component.create`
44
+ <button
45
+ class="${({ props }) => cn(BASE_CLASSES, props.className)}"
46
+ onClick="${({ props }) => props.handleClick}"
47
+ >
48
+ ${({ props }) => props.renderChildren()}
49
+ </button>
50
+ `;
51
+
52
+ export default Button;
@@ -0,0 +1,22 @@
1
+ import { Component } from 'rasti';
2
+ import cn from '../lib/cn.js';
3
+
4
+ /**
5
+ * @typedef {Object} ButtonProps
6
+ * @property {Function} handleClick - Click handler.
7
+ * @property {string} [className] - Additional CSS classes.
8
+ */
9
+
10
+ /** Reusable styled button. Renders children via props.renderChildren().
11
+ * @type {typeof import('rasti').Component<ButtonProps>}
12
+ */
13
+ const Button = Component.create`
14
+ <button
15
+ class="${({ props }) => cn('button', props.className)}"
16
+ onClick="${({ props }) => props.handleClick}"
17
+ >
18
+ ${({ props }) => props.renderChildren()}
19
+ </button>
20
+ `;
21
+
22
+ export default Button;
@@ -0,0 +1,69 @@
1
+ import { Component } from 'rasti';
2
+ import { css } from 'cssfun';
3
+ import cn from '../lib/cn.js';
4
+ import Button from './Button.js';
5
+
6
+ const { classes } = css({
7
+ root : {
8
+ display : 'flex',
9
+ alignItems : 'center',
10
+ width : '100%'
11
+ },
12
+ nav : {
13
+ display : 'flex',
14
+ gap : '16px'
15
+ },
16
+ actions : {
17
+ display : 'flex',
18
+ gap : '10px',
19
+ marginLeft : 'auto'
20
+ },
21
+ showWhenLight : {
22
+ display : 'var(--fun-showWhenLight)'
23
+ },
24
+ showWhenDark : {
25
+ display : 'var(--fun-showWhenDark)'
26
+ },
27
+ buttonSm : {
28
+ minHeight : '40px',
29
+ minWidth : 'auto',
30
+ padding : '0 16px',
31
+ fontSize : '13px',
32
+ fontWeight : '600',
33
+ letterSpacing : '0.01em'
34
+ }
35
+ });
36
+
37
+ function setColorScheme(mode) {
38
+ if (typeof document !== 'undefined') document.documentElement.setAttribute('data-color-scheme', mode);
39
+ }
40
+
41
+ /**
42
+ * App header (cssfun): light/dark theme toggle, and navigation when router is enabled.
43
+ */
44
+ const Header = Component.create`
45
+ <header class="${classes.root}">
46
+ {{#if ROUTER}}
47
+ <nav class="${classes.nav}">
48
+ <a data-router href="/">Home</a>
49
+ <a data-router href="{{ROUTE_ABOUT}}">About</a>
50
+ </nav>
51
+ {{#endif}}
52
+ <div class="${classes.actions}">
53
+ <${Button}
54
+ className=${cn(classes.buttonSm, classes.showWhenDark)}
55
+ handleClick=${() => setColorScheme('light')}
56
+ >
57
+ Light
58
+ </${Button}>
59
+ <${Button}
60
+ className=${cn(classes.buttonSm, classes.showWhenLight)}
61
+ handleClick=${() => setColorScheme('dark')}
62
+ >
63
+ Dark
64
+ </${Button}>
65
+ </div>
66
+ </header>
67
+ `;
68
+
69
+ export default Header;
@@ -0,0 +1,17 @@
1
+ import { Component } from 'rasti';
2
+
3
+ /**
4
+ * App header: navigation (when router is enabled).
5
+ */
6
+ const Header = Component.create`
7
+ <header class="flex w-full items-center">
8
+ {{#if ROUTER}}
9
+ <nav class="flex gap-6">
10
+ <a data-router href="/">Home</a>
11
+ <a data-router href="{{ROUTE_ABOUT}}">About</a>
12
+ </nav>
13
+ {{#endif}}
14
+ </header>
15
+ `;
16
+
17
+ export default Header;
@@ -0,0 +1,17 @@
1
+ import { Component } from 'rasti';
2
+
3
+ /**
4
+ * App header: navigation (when router is enabled).
5
+ */
6
+ const Header = Component.create`
7
+ <header class="header">
8
+ {{#if ROUTER}}
9
+ <nav class="nav">
10
+ <a data-router href="/">Home</a>
11
+ <a data-router href="{{ROUTE_ABOUT}}">About</a>
12
+ </nav>
13
+ {{#endif}}
14
+ </header>
15
+ `;
16
+
17
+ export default Header;
@@ -0,0 +1,98 @@
1
+ import { Component } from 'rasti';
2
+ import { css } from 'cssfun';
3
+ import Button from './Button.js';
4
+
5
+ const { classes } = css({
6
+ root : {
7
+ width : '100%',
8
+ display : 'flex',
9
+ flexDirection : 'column',
10
+ alignItems : 'center',
11
+ gap : 'clamp(24px, 6vw, 40px)'
12
+ },
13
+ title : { display : 'flex', alignItems : 'center', justifyContent : 'center', gap : '16px', fontSize : '36px', fontWeight : '700', margin : 0 },
14
+ logoLink : { display : 'inline-block' },
15
+ logoLight : { height : 'clamp(56px, 15vw, 96px)', display : 'var(--fun-showWhenLight)' },
16
+ logoDark : { height : 'clamp(56px, 15vw, 96px)', display : 'var(--fun-showWhenDark)' },
17
+ counter : {
18
+ display : 'flex',
19
+ alignItems : 'center',
20
+ justifyContent : 'center'
21
+ },
22
+ info : {
23
+ width : 'min(100%, 720px)',
24
+ padding : 'clamp(20px, 4vw, 30px)',
25
+ background : 'var(--fun-cardBg)',
26
+ borderRadius : '24px',
27
+ border : '1px solid var(--fun-cardBorder)',
28
+ boxShadow : 'var(--fun-cardInset), var(--fun-cardShadow)',
29
+ backdropFilter : 'blur(18px)',
30
+ '& code' : {
31
+ backgroundColor : 'var(--fun-codeBg)',
32
+ padding : '3px 10px',
33
+ borderRadius : '999px',
34
+ fontSize : '13px',
35
+ fontFamily : 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
36
+ fontWeight : '600',
37
+ border : '1px solid var(--fun-codeBorder)'
38
+ }
39
+ },
40
+ infoStack : {
41
+ display : 'grid',
42
+ gap : '16px'
43
+ },
44
+ infoDescription : {
45
+ margin : 0,
46
+ lineHeight : '1.7',
47
+ fontSize : '15px'
48
+ },
49
+ infoIncludes : {
50
+ padding : '16px 20px',
51
+ borderRadius : '16px',
52
+ background : 'var(--fun-metaBg)',
53
+ border : '1px solid var(--fun-metaBorder)',
54
+ '& p' : {
55
+ margin : 0,
56
+ lineHeight : '1.7'
57
+ },
58
+ '& p + p' : {
59
+ marginTop : '8px'
60
+ }
61
+ },
62
+ infoHint : {
63
+ margin : 0,
64
+ lineHeight : '1.7',
65
+ color : 'var(--fun-mutedText)'
66
+ }
67
+ });
68
+
69
+ /**
70
+ * @typedef {Object} HomeProps
71
+ * @property {number} count - Current counter value.
72
+ * @property {Function} handleIncrement - Increments the counter.
73
+ */
74
+
75
+ /** Home view: logo, counter and project info.
76
+ * @type {typeof import('rasti').Component<HomeProps>}
77
+ */
78
+ const Home = Component.create`
79
+ <div class="${classes.root}">
80
+ <h1 class="${classes.title}">
81
+ <a class="${classes.logoLink}" href="https://rasti.js.org" target="_blank"><img class="${classes.logoLight}" src="{{LOGO_LIGHT_SRC}}" alt="{{NAME}}" /><img class="${classes.logoDark}" src="{{LOGO_SRC}}" alt="{{NAME}}" /></a>
82
+ </h1>
83
+ <div class="${classes.counter}">
84
+ <${Button} handleClick="${({ props }) => props.handleIncrement}">
85
+ count is ${({ props }) => props.count}
86
+ </${Button}>
87
+ </div>
88
+ <div class="${classes.info}">
89
+ <div class="${classes.infoStack}">
90
+ <p class="${classes.infoDescription}">{{DESCRIPTION}}</p>
91
+ <div class="${classes.infoIncludes}">{{FEATURES_INCLUDE}}</div>
92
+ <p class="${classes.infoHint}">Edit <code>src/App.js</code> and save to test HMR.</p>
93
+ </div>
94
+ </div>
95
+ </div>
96
+ `;
97
+
98
+ export default Home;
@@ -0,0 +1,35 @@
1
+ import { Component } from 'rasti';
2
+ import Button from './Button.js';
3
+
4
+ /**
5
+ * @typedef {Object} HomeProps
6
+ * @property {number} count - Current counter value.
7
+ * @property {Function} handleIncrement - Increments the counter.
8
+ */
9
+
10
+ /** Home view: logo, counter and project info.
11
+ * @type {typeof import('rasti').Component<HomeProps>}
12
+ */
13
+ const Home = Component.create`
14
+ <div class="flex w-full flex-col items-center gap-6 md:gap-10">
15
+ <h1 class="flex items-center justify-center gap-4 text-4xl font-bold m-0">
16
+ <a class="inline-block" href="https://rasti.js.org" target="_blank">
17
+ <img class="h-14 md:h-24 block" src="{{LOGO_SRC}}" alt="{{NAME}}" />
18
+ </a>
19
+ </h1>
20
+ <div class="flex items-center">
21
+ <${Button} handleClick="${({ props }) => props.handleIncrement}">
22
+ count is ${({ props }) => props.count}
23
+ </${Button}>
24
+ </div>
25
+ <div class="w-full max-w-[720px] rounded-3xl border border-white/12 bg-linear-to-b from-[rgba(20,24,28,0.92)] to-[rgba(10,10,10,0.82)] p-5 md:p-[30px] shadow-[inset_0_1px_0_rgba(255,255,255,0.06),0_12px_40px_rgba(0,0,0,0.2)] backdrop-blur-[18px]">
26
+ <div class="grid gap-4">
27
+ <p class="m-0 text-[15px] leading-[1.7] text-white/88">{{DESCRIPTION}}</p>
28
+ <div class="rounded-2xl border border-white/6 bg-white/4 px-5 py-4 leading-[1.7] text-white/88 [&_p+p]:mt-2 [&_p]:m-0">{{FEATURES_INCLUDE}}</div>
29
+ <p class="m-0 leading-[1.7] text-white/58">Edit <code class="rounded-full border border-white/10 bg-white/10 px-2.5 py-0.5 text-[13px] font-semibold font-mono">src/App.js</code> and save to test HMR.</p>
30
+ </div>
31
+ </div>
32
+ </div>
33
+ `;
34
+
35
+ export default Home;
@@ -0,0 +1,35 @@
1
+ import { Component } from 'rasti';
2
+ import Button from './Button.js';
3
+
4
+ /**
5
+ * @typedef {Object} HomeProps
6
+ * @property {number} count - Current counter value.
7
+ * @property {Function} handleIncrement - Increments the counter.
8
+ */
9
+
10
+ /** Home view: logo, counter and project info.
11
+ * @type {typeof import('rasti').Component<HomeProps>}
12
+ */
13
+ const Home = Component.create`
14
+ <div class="home">
15
+ <h1 class="title">
16
+ <a class="logo-link" href="https://rasti.js.org" target="_blank">
17
+ <img class="logo" src="{{LOGO_SRC}}" alt="{{NAME}}" />
18
+ </a>
19
+ </h1>
20
+ <div class="counter">
21
+ <${Button} handleClick="${({ props }) => props.handleIncrement}">
22
+ count is ${({ props }) => props.count}
23
+ </${Button}>
24
+ </div>
25
+ <div class="info">
26
+ <div class="info-stack">
27
+ <p class="info-description">{{DESCRIPTION}}</p>
28
+ <div class="info-includes">{{FEATURES_INCLUDE}}</div>
29
+ <p class="info-hint">Edit <code>src/App.js</code> and save to test HMR.</p>
30
+ </div>
31
+ </div>
32
+ </div>
33
+ `;
34
+
35
+ export default Home;
@@ -0,0 +1,170 @@
1
+ :root {
2
+ --app-link: #5ee9df;
3
+ --app-link-hover: #93fdf5;
4
+ --app-accent: #5ee9df;
5
+ --app-border: rgba(255, 255, 255, 0.12);
6
+ --app-border-hover: rgba(94, 233, 223, 0.4);
7
+ --app-muted: rgba(255, 255, 255, 0.58);
8
+ }
9
+
10
+ * {
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ background: #0a0a0a;
16
+ color: #ffffff;
17
+ margin: 0;
18
+ padding: 0;
19
+ }
20
+
21
+ a {
22
+ color: var(--app-link);
23
+ text-decoration: none;
24
+ transition: color 0.2s ease;
25
+ }
26
+
27
+ a:hover {
28
+ color: var(--app-link-hover);
29
+ text-decoration: underline;
30
+ }
31
+
32
+ .app {
33
+ font-family: system-ui, sans-serif;
34
+ min-height: 100dvh;
35
+ display: flex;
36
+ flex-direction: column;
37
+ align-items: center;
38
+ gap: clamp(24px, 5vw, 40px);
39
+ max-width: 1000px;
40
+ margin: 0 auto;
41
+ padding: clamp(20px, 5vw, 48px);
42
+ }
43
+
44
+ .header {
45
+ display: flex;
46
+ align-items: center;
47
+ width: 100%;
48
+ }
49
+
50
+ .logo-link {
51
+ display: inline-block;
52
+ }
53
+
54
+ .logo {
55
+ height: clamp(56px, 15vw, 96px);
56
+ display: block;
57
+ }
58
+
59
+ .home {
60
+ width: 100%;
61
+ display: flex;
62
+ flex-direction: column;
63
+ align-items: center;
64
+ gap: clamp(24px, 6vw, 40px);
65
+ }
66
+
67
+ .page {
68
+ width: 100%;
69
+ display: flex;
70
+ justify-content: center;
71
+ }
72
+
73
+ .title {
74
+ display: flex;
75
+ align-items: center;
76
+ justify-content: center;
77
+ gap: 16px;
78
+ font-size: 36px;
79
+ font-weight: 700;
80
+ margin: 0;
81
+ }
82
+
83
+ .counter {
84
+ display: flex;
85
+ align-items: center;
86
+ justify-content: center;
87
+ }
88
+
89
+ .button {
90
+ display: inline-flex;
91
+ align-items: center;
92
+ justify-content: center;
93
+ min-height: 52px;
94
+ min-width: 200px;
95
+ padding: 0 32px;
96
+ font-size: 20px;
97
+ font-weight: 650;
98
+ color: #ffffff;
99
+ background: linear-gradient(180deg, rgba(94, 233, 223, 0.2) 0%, rgba(255, 255, 255, 0.06) 100%);
100
+ border: 1px solid var(--app-border);
101
+ border-radius: 999px;
102
+ cursor: pointer;
103
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 6px 20px rgba(0, 0, 0, 0.18);
104
+ backdrop-filter: blur(16px);
105
+ transition: border-color 0.2s ease, background 0.2s ease, transform 0.15s ease;
106
+ }
107
+
108
+ .button:hover {
109
+ border-color: var(--app-border-hover);
110
+ background: linear-gradient(180deg, rgba(94, 233, 223, 0.28) 0%, rgba(255, 255, 255, 0.1) 100%);
111
+ }
112
+
113
+ .button:active {
114
+ transform: scale(0.985);
115
+ }
116
+
117
+ .info {
118
+ width: min(100%, 720px);
119
+ padding: clamp(20px, 4vw, 30px);
120
+ background: linear-gradient(180deg, rgba(20, 24, 28, 0.92) 0%, rgba(10, 10, 10, 0.82) 100%);
121
+ border-radius: 24px;
122
+ border: 1px solid var(--app-border);
123
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06), 0 12px 40px rgba(0, 0, 0, 0.2);
124
+ backdrop-filter: blur(18px);
125
+ }
126
+
127
+ .info-stack {
128
+ display: grid;
129
+ gap: 16px;
130
+ }
131
+
132
+ .info-description {
133
+ margin: 0;
134
+ line-height: 1.7;
135
+ font-size: 15px;
136
+ color: rgba(255, 255, 255, 0.88);
137
+ }
138
+
139
+ .info-includes {
140
+ padding: 16px 20px;
141
+ border-radius: 16px;
142
+ background: rgba(255, 255, 255, 0.04);
143
+ border: 1px solid rgba(255, 255, 255, 0.06);
144
+ }
145
+
146
+ .info-includes p {
147
+ margin: 0;
148
+ line-height: 1.7;
149
+ color: rgba(255, 255, 255, 0.88);
150
+ }
151
+
152
+ .info-includes p + p {
153
+ margin-top: 8px;
154
+ }
155
+
156
+ .info-hint {
157
+ margin: 0;
158
+ line-height: 1.7;
159
+ color: var(--app-muted);
160
+ }
161
+
162
+ .info code {
163
+ background-color: rgba(255, 255, 255, 0.1);
164
+ padding: 3px 10px;
165
+ border-radius: 999px;
166
+ font-size: 13px;
167
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
168
+ font-weight: 600;
169
+ border: 1px solid rgba(255, 255, 255, 0.1);
170
+ }