rip-lang 3.7.4 → 3.8.9
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/CHANGELOG.md +106 -1
- package/README.md +42 -34
- package/docs/RIP-INTERNALS.md +2 -4
- package/docs/RIP-LANG.md +150 -3
- package/docs/RIP-TYPES.md +1 -2
- package/docs/demo.html +342 -0
- package/docs/dist/rip-ui.min.js +514 -0
- package/docs/dist/rip-ui.min.js.br +0 -0
- package/docs/dist/rip.browser.js +327 -478
- package/docs/dist/rip.browser.min.js +168 -208
- package/docs/dist/rip.browser.min.js.br +0 -0
- package/docs/dist/ui.js +962 -0
- package/docs/dist/ui.min.js +2 -0
- package/docs/dist/ui.min.js.br +0 -0
- package/docs/dist/ui.rip +957 -0
- package/docs/dist/ui.rip.br +0 -0
- package/docs/examples.rip +180 -0
- package/docs/index.html +3 -1599
- package/docs/playground-app.html +1022 -0
- package/docs/playground-js.html +1645 -0
- package/docs/playground-rip-ui.html +1419 -0
- package/docs/playground-rip.html +1450 -0
- package/docs/rip-fav.svg +5 -0
- package/package.json +3 -3
- package/scripts/serve.js +3 -2
- package/src/browser.js +21 -5
- package/src/compiler.js +191 -292
- package/src/components.js +100 -95
- package/src/grammar/README.md +234 -0
- package/src/grammar/grammar.rip +5 -5
- package/src/grammar/lunar.rip +2412 -0
- package/src/grammar/solar.rip +18 -4
- package/src/lexer.js +53 -24
- package/src/parser-rd.js +3242 -0
- package/src/parser.js +10 -9
- package/src/repl.js +24 -5
- package/docs/NOTES.md +0 -93
- package/docs/RIP-GUIDE.md +0 -698
- package/docs/RIP-REACTIVITY.md +0 -311
package/docs/demo.html
ADDED
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Rip UI Demo</title>
|
|
7
|
+
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg width='420' height='420' viewBox='0 0 420 420' fill='none' xmlns='http://www.w3.org/2000/svg'><circle cx='210' cy='210' r='210' fill='white'/><path d='M114.5 263C79.772 263 45.7872 275.051 34.271 283.579C33.4994 284.151 34.012 285.229 34.9656 285.117C73.1916 280.629 115.309 292.74 146.5 304C178.5 315.552 221 336 269 336C314.651 336 372.074 310.499 386.401 293.944C387.038 293.208 386.301 292.353 385.435 292.799C376.614 297.341 364.243 306.018 316.073 306.948C265.273 307.928 159 263 114.5 263Z' fill='%230389FF'/><path d='M223.46 84C239.53 84 253.592 86.9253 265.645 92.7754C277.697 98.6254 287.071 107.048 293.767 118.043C300.462 129.038 303.811 142.219 303.811 157.584C303.811 173.09 300.357 186.165 293.449 196.808C287.068 206.742 278.259 214.402 267.026 219.792L309.603 297.958C297.885 297.851 283.094 295.413 266.52 291.62C257.58 289.574 248.214 287.157 238.645 284.547L209.127 229.054H188.782V270.333C176.996 266.968 165.515 263.788 154.823 261.15C146.038 258.984 137.665 257.152 130 255.885V84H223.46ZM188.782 183.381H209.505C216.412 183.381 222.297 182.535 227.16 180.844C232.094 179.082 235.865 176.297 238.473 172.491C241.151 168.685 242.49 163.716 242.49 157.584C242.49 151.382 241.151 146.342 238.473 142.466C235.865 138.519 232.094 135.628 227.16 133.796C222.297 131.893 216.412 130.941 209.505 130.941H188.782V183.381Z' fill='%23BB0000'/></svg>">
|
|
8
|
+
<style>
|
|
9
|
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Rip UI Demo — Self-contained styles
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/* Design Tokens */
|
|
16
|
+
:root {
|
|
17
|
+
--font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
18
|
+
--gray-50: #f9fafb; --gray-100: #f3f4f6; --gray-200: #e5e7eb;
|
|
19
|
+
--gray-300: #d1d5db; --gray-400: #9ca3af; --gray-500: #6b7280;
|
|
20
|
+
--gray-600: #4b5563; --gray-700: #374151; --gray-800: #1f2937; --gray-900: #111827;
|
|
21
|
+
--blue-50: #eff6ff; --blue-100: #dbeafe; --blue-200: #bfdbfe; --blue-500: #3b82f6;
|
|
22
|
+
--blue-600: #2563eb; --blue-700: #1d4ed8;
|
|
23
|
+
--green-50: #f0fdf4; --green-500: #22c55e; --green-600: #16a34a;
|
|
24
|
+
--red-50: #fef2f2; --red-500: #ef4444;
|
|
25
|
+
--surface: var(--gray-50); --card: #ffffff;
|
|
26
|
+
--text: var(--gray-900); --text-secondary: var(--gray-500); --text-muted: var(--gray-400);
|
|
27
|
+
--border: var(--gray-200); --accent: var(--blue-600); --accent-hover: var(--blue-700);
|
|
28
|
+
--radius: 0.75rem; --radius-lg: 1rem;
|
|
29
|
+
--shadow: 0 1px 3px rgb(0 0 0 / 0.1), 0 1px 2px rgb(0 0 0 / 0.06);
|
|
30
|
+
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
|
31
|
+
--transition: 150ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* Reset */
|
|
35
|
+
*, *::before, *::after { box-sizing: border-box; }
|
|
36
|
+
* { margin: 0; }
|
|
37
|
+
html { -webkit-text-size-adjust: 100%; }
|
|
38
|
+
body { font-family: var(--font); line-height: 1.6; color: var(--text); background: var(--surface); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
|
|
39
|
+
a { color: inherit; text-decoration: none; }
|
|
40
|
+
img, svg { display: block; max-width: 100%; }
|
|
41
|
+
input, button, textarea, select { font: inherit; }
|
|
42
|
+
#app { max-width: 52rem; margin: 0 auto; padding: 2rem; }
|
|
43
|
+
|
|
44
|
+
/* Layout Shell */
|
|
45
|
+
.app-layout { min-height: 100vh; display: flex; flex-direction: column; }
|
|
46
|
+
.app-nav { background: var(--card); border-bottom: 1px solid var(--border); border-radius: var(--radius); padding: 0 1.5rem; position: sticky; top: 0; z-index: 100; }
|
|
47
|
+
.nav-inner { max-width: 48rem; margin: 0 auto; display: flex; align-items: center; justify-content: space-between; height: 3.5rem; }
|
|
48
|
+
.nav-brand { display: flex; align-items: center; gap: 0.5rem; font-weight: 600; font-size: 0.9375rem; letter-spacing: -0.02em; }
|
|
49
|
+
.nav-links { display: flex; align-items: center; gap: 0.25rem; }
|
|
50
|
+
.nav-link { padding: 0.375rem 0.75rem; border-radius: 0.5rem; color: var(--text-secondary); font-size: 0.8125rem; font-weight: 500; transition: color var(--transition), background-color var(--transition); }
|
|
51
|
+
.nav-link:hover { color: var(--text); background-color: var(--gray-100); }
|
|
52
|
+
.nav-link:active { background-color: var(--gray-200); }
|
|
53
|
+
.nav-link.active { color: var(--accent); background-color: var(--blue-50); }
|
|
54
|
+
.nav-link.active:hover { color: var(--accent-hover); background-color: var(--blue-100); }
|
|
55
|
+
.nav-link.active:active { background-color: var(--blue-200); }
|
|
56
|
+
.app-main { flex: 1; max-width: 48rem; width: 100%; margin: 0 auto; padding: 2rem 1.5rem; }
|
|
57
|
+
|
|
58
|
+
/* Page Sections */
|
|
59
|
+
.page-header { margin-bottom: 2rem; }
|
|
60
|
+
.page-title { font-size: 1.5rem; font-weight: 700; letter-spacing: -0.03em; line-height: 1.2; color: var(--text); }
|
|
61
|
+
.page-subtitle { font-size: 0.875rem; color: var(--text-secondary); margin-top: 0.25rem; }
|
|
62
|
+
|
|
63
|
+
/* Card */
|
|
64
|
+
.card { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-lg); box-shadow: var(--shadow); padding: 1.5rem; }
|
|
65
|
+
.card + .card { margin-top: 1rem; }
|
|
66
|
+
.card h3 { font-size: 0.8125rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-secondary); margin-bottom: 1rem; }
|
|
67
|
+
.card-title { font-size: 0.8125rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-secondary); margin-bottom: 1rem; }
|
|
68
|
+
|
|
69
|
+
/* Button */
|
|
70
|
+
.btn { display: inline-flex; align-items: center; justify-content: center; gap: 0.5rem; padding: 0.5rem 1rem; border: 1px solid var(--border); border-radius: 0.5rem; background-color: var(--card); color: var(--text); font-size: 0.8125rem; font-weight: 500; cursor: pointer; transition: color var(--transition), background-color var(--transition), border-color var(--transition), transform var(--transition); user-select: none; }
|
|
71
|
+
.btn:hover { background-color: var(--gray-50); border-color: var(--gray-300); }
|
|
72
|
+
.btn:active { transform: scale(0.98); }
|
|
73
|
+
.btn-primary { background: var(--accent); border-color: var(--accent); color: white; }
|
|
74
|
+
.btn-primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
|
|
75
|
+
.btn-danger { color: var(--red-500); border-color: var(--red-500); }
|
|
76
|
+
.btn-danger:hover { background: var(--red-50); }
|
|
77
|
+
.btn-sm { padding: 0.25rem 0.5rem; font-size: 0.75rem; }
|
|
78
|
+
.btn-group { display: flex; gap: 0.5rem; flex-wrap: wrap; }
|
|
79
|
+
|
|
80
|
+
/* Counter */
|
|
81
|
+
.counter-display { font-size: 4rem; font-weight: 700; letter-spacing: -0.04em; text-align: center; padding: 2rem 0; font-variant-numeric: tabular-nums; color: var(--text); transition: color var(--transition); }
|
|
82
|
+
.counter-display.positive { color: var(--green-600); }
|
|
83
|
+
.counter-display.negative { color: var(--red-500); }
|
|
84
|
+
.counter-controls { display: flex; justify-content: center; gap: 0.5rem; }
|
|
85
|
+
|
|
86
|
+
/* Input */
|
|
87
|
+
.input { width: 100%; padding: 0.5rem 0.75rem; border: 1px solid var(--border); border-radius: 0.5rem; background-color: var(--card); color: var(--text); font-size: 0.875rem; transition: border-color var(--transition), box-shadow var(--transition); }
|
|
88
|
+
.input::placeholder { color: var(--text-muted); }
|
|
89
|
+
.input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px var(--blue-100); }
|
|
90
|
+
|
|
91
|
+
/* Todo List */
|
|
92
|
+
.todo-input-row { display: flex; gap: 0.5rem; margin-bottom: 1rem; }
|
|
93
|
+
.todo-input-row .input { flex: 1; }
|
|
94
|
+
.todo-list { list-style: none; padding: 0; }
|
|
95
|
+
.todo-item { display: flex; align-items: center; gap: 0.75rem; padding: 0.75rem 0; border-bottom: 1px solid var(--gray-100); transition: opacity var(--transition); }
|
|
96
|
+
.todo-item:last-child { border-bottom: none; }
|
|
97
|
+
.todo-text { flex: 1; font-size: 0.875rem; color: var(--text); }
|
|
98
|
+
.todo-text.done { text-decoration: line-through; color: var(--text-muted); }
|
|
99
|
+
.todo-delete { opacity: 0; padding: 0.125rem 0.375rem; border: none; background: none; color: var(--text-muted); cursor: pointer; border-radius: 0.25rem; font-size: 0.75rem; transition: opacity var(--transition), color var(--transition), background-color var(--transition); }
|
|
100
|
+
.todo-item:hover .todo-delete { opacity: 1; }
|
|
101
|
+
.todo-delete:hover { color: var(--red-500); background: var(--red-50); }
|
|
102
|
+
.todo-stats { display: flex; justify-content: space-between; padding-top: 0.75rem; border-top: 1px solid var(--gray-100); font-size: 0.75rem; color: var(--text-secondary); }
|
|
103
|
+
|
|
104
|
+
/* Feature Grid */
|
|
105
|
+
.feature-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1rem; }
|
|
106
|
+
@media (max-width: 640px) { .feature-grid { grid-template-columns: 1fr; } }
|
|
107
|
+
.feature-card { background-color: var(--card); border: 1px solid var(--border); border-radius: var(--radius); padding: 1.25rem; transition: border-color var(--transition), box-shadow var(--transition); }
|
|
108
|
+
.feature-card:hover { border-color: var(--gray-300); box-shadow: var(--shadow); }
|
|
109
|
+
.feature-icon { font-size: 1.5rem; margin-bottom: 0.75rem; }
|
|
110
|
+
.feature-name { font-size: 0.875rem; font-weight: 600; color: var(--text); margin-bottom: 0.25rem; }
|
|
111
|
+
.feature-desc { font-size: 0.8125rem; color: var(--text-secondary); line-height: 1.5; }
|
|
112
|
+
|
|
113
|
+
/* Stat badge */
|
|
114
|
+
.stat { display: inline-flex; align-items: center; gap: 0.375rem; padding: 0.375rem 0.75rem; background: var(--gray-100); border-radius: var(--radius); font-size: 0.8125rem; font-weight: 500; color: var(--text-secondary); }
|
|
115
|
+
.stat-value { font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums; }
|
|
116
|
+
|
|
117
|
+
/* Error banner */
|
|
118
|
+
.error-banner { background: var(--red-50); border: 1px solid var(--red-500); border-radius: var(--radius); padding: 1rem; margin-bottom: 1rem; display: flex; align-items: center; justify-content: space-between; }
|
|
119
|
+
</style>
|
|
120
|
+
<script type="module" src="dist/rip-ui.min.js"></script>
|
|
121
|
+
</head>
|
|
122
|
+
<body>
|
|
123
|
+
<div id="app"></div>
|
|
124
|
+
|
|
125
|
+
<script type="text/rip">
|
|
126
|
+
{ launch } = importRip!('dist/ui.rip')
|
|
127
|
+
|
|
128
|
+
launch window.location.pathname,
|
|
129
|
+
target: '#app'
|
|
130
|
+
persist: true
|
|
131
|
+
hash: true
|
|
132
|
+
bundle:
|
|
133
|
+
components:
|
|
134
|
+
'components/_layout.rip': '''
|
|
135
|
+
# Root Layout (with error boundary and navigation indicator)
|
|
136
|
+
|
|
137
|
+
export Layout = component
|
|
138
|
+
errorMsg := null
|
|
139
|
+
|
|
140
|
+
onError: (err) ->
|
|
141
|
+
errorMsg = err.message
|
|
142
|
+
|
|
143
|
+
render
|
|
144
|
+
.app-layout
|
|
145
|
+
nav.app-nav
|
|
146
|
+
.nav-inner
|
|
147
|
+
a.nav-brand "Rip UI"
|
|
148
|
+
.nav-links
|
|
149
|
+
a.('nav-link', @router.path is '/' and 'active') href: "#", "Home"
|
|
150
|
+
a.('nav-link', @router.path is '/counter' and 'active') href: "#counter", "Counter"
|
|
151
|
+
a.('nav-link', @router.path is '/todos' and 'active') href: "#todos", "Todos"
|
|
152
|
+
a.('nav-link', @router.path is '/about' and 'active') href: "#about", "About"
|
|
153
|
+
if @router.navigating
|
|
154
|
+
span.nav-loading "Loading..."
|
|
155
|
+
main.app-main
|
|
156
|
+
if errorMsg
|
|
157
|
+
.error-banner
|
|
158
|
+
p "Something went wrong: #{errorMsg}"
|
|
159
|
+
button.btn @click: (-> errorMsg = null), "Dismiss"
|
|
160
|
+
#content
|
|
161
|
+
'''
|
|
162
|
+
|
|
163
|
+
'components/index.rip': '''
|
|
164
|
+
# Home Page
|
|
165
|
+
|
|
166
|
+
export Home = component
|
|
167
|
+
|
|
168
|
+
render
|
|
169
|
+
div
|
|
170
|
+
.page-header
|
|
171
|
+
h1.page-title "Rip UI Framework!"
|
|
172
|
+
p.page-subtitle "Zero-build reactive web apps"
|
|
173
|
+
|
|
174
|
+
.feature-grid
|
|
175
|
+
.feature-card
|
|
176
|
+
.feature-icon "🗂"
|
|
177
|
+
.feature-name "Component Store"
|
|
178
|
+
.feature-desc "Component source files loaded on demand. Compiled in the browser."
|
|
179
|
+
|
|
180
|
+
.feature-card
|
|
181
|
+
.feature-icon "🔀"
|
|
182
|
+
.feature-name "File-Based Router"
|
|
183
|
+
.feature-desc "URLs map to components. Dynamic segments and nested layouts."
|
|
184
|
+
|
|
185
|
+
.feature-card
|
|
186
|
+
.feature-icon "⚡"
|
|
187
|
+
.feature-name "Reactive Stash"
|
|
188
|
+
.feature-desc "Deep state tree with path navigation. Every property tracked."
|
|
189
|
+
|
|
190
|
+
.feature-card
|
|
191
|
+
.feature-icon "🎯"
|
|
192
|
+
.feature-name "Fine-Grained Rendering"
|
|
193
|
+
.feature-desc "No virtual DOM. Direct DOM updates via reactive effects."
|
|
194
|
+
'''
|
|
195
|
+
|
|
196
|
+
'components/counter.rip': '''
|
|
197
|
+
# Counter Page
|
|
198
|
+
|
|
199
|
+
export Counter = component
|
|
200
|
+
count := @app.data.count or 0
|
|
201
|
+
step := 1
|
|
202
|
+
|
|
203
|
+
~> @app.data.count = count
|
|
204
|
+
|
|
205
|
+
increment: -> count += step
|
|
206
|
+
decrement: -> count -= step
|
|
207
|
+
reset: -> count = 0
|
|
208
|
+
|
|
209
|
+
render
|
|
210
|
+
div
|
|
211
|
+
.page-header
|
|
212
|
+
h1.page-title "Counter"
|
|
213
|
+
p.page-subtitle "Reactive state — persists across reload"
|
|
214
|
+
|
|
215
|
+
.card
|
|
216
|
+
.counter-display "#{count}"
|
|
217
|
+
|
|
218
|
+
.counter-controls
|
|
219
|
+
button.btn @click: @decrement, "- #{step}"
|
|
220
|
+
button.btn @click: @reset, "Reset"
|
|
221
|
+
button.btn.btn-primary @click: @increment, "+ #{step}"
|
|
222
|
+
|
|
223
|
+
.card
|
|
224
|
+
.card-title "Step Size"
|
|
225
|
+
.btn-group
|
|
226
|
+
button.btn @click: (-> step = 1), "1"
|
|
227
|
+
button.btn @click: (-> step = 5), "5"
|
|
228
|
+
button.btn @click: (-> step = 10), "10"
|
|
229
|
+
button.btn @click: (-> step = 100), "100"
|
|
230
|
+
'''
|
|
231
|
+
|
|
232
|
+
'components/todos.rip': '''
|
|
233
|
+
# Todos Page
|
|
234
|
+
|
|
235
|
+
export Todos = component
|
|
236
|
+
newTodo := ''
|
|
237
|
+
todos := @app.data.todos or []
|
|
238
|
+
nextId := (@app.data.nextId or 1)
|
|
239
|
+
|
|
240
|
+
~> @app.data.todos = todos
|
|
241
|
+
~> @app.data.nextId = nextId
|
|
242
|
+
|
|
243
|
+
remaining ~= todos.filter((t) -> not t.done).length
|
|
244
|
+
|
|
245
|
+
addTodo: ->
|
|
246
|
+
if newTodo.trim()
|
|
247
|
+
todos = [...todos, { id: nextId, text: newTodo.trim(), done: false }]
|
|
248
|
+
nextId++
|
|
249
|
+
newTodo = ''
|
|
250
|
+
|
|
251
|
+
deleteTodo: (id) ->
|
|
252
|
+
todos = todos.filter (t) -> t.id isnt id
|
|
253
|
+
|
|
254
|
+
clearAll: ->
|
|
255
|
+
todos = []
|
|
256
|
+
|
|
257
|
+
handleKey: (e) ->
|
|
258
|
+
if e.key is 'Enter'
|
|
259
|
+
@addTodo()
|
|
260
|
+
|
|
261
|
+
render
|
|
262
|
+
div
|
|
263
|
+
.page-header
|
|
264
|
+
h1.page-title "Todos"
|
|
265
|
+
p.page-subtitle "List rendering and state management"
|
|
266
|
+
|
|
267
|
+
.card
|
|
268
|
+
.todo-input-row
|
|
269
|
+
input.input type: "text", value: newTodo, placeholder: "What needs to be done?", @keydown: @handleKey
|
|
270
|
+
button.btn.btn-primary @click: @addTodo, "Add"
|
|
271
|
+
|
|
272
|
+
ul.todo-list
|
|
273
|
+
for todo in todos
|
|
274
|
+
li.todo-item
|
|
275
|
+
span.todo-text todo.text
|
|
276
|
+
button.todo-delete @click: (=> @deleteTodo(todo.id)), "x"
|
|
277
|
+
|
|
278
|
+
.todo-stats
|
|
279
|
+
span "#{remaining} remaining"
|
|
280
|
+
button.btn.btn-sm @click: @clearAll, "Clear all"
|
|
281
|
+
'''
|
|
282
|
+
|
|
283
|
+
'components/about.rip': '''
|
|
284
|
+
# About Page — uses the Card component for content sections
|
|
285
|
+
|
|
286
|
+
export About = component
|
|
287
|
+
|
|
288
|
+
render
|
|
289
|
+
div
|
|
290
|
+
.page-header
|
|
291
|
+
h1.page-title "About Rip UI"
|
|
292
|
+
p.page-subtitle "Zero-build reactive web apps!"
|
|
293
|
+
|
|
294
|
+
Card title: "The Idea"
|
|
295
|
+
p "Traditional frameworks build and bundle on the server, then ship static artifacts. Rip UI ships the compiler to the browser instead — under 55KB on the wire. Components arrive as .rip source files, are compiled on demand, and render with fine-grained reactivity. No build step, no bundler, no configuration files."
|
|
296
|
+
|
|
297
|
+
Card title: "Two Keywords"
|
|
298
|
+
p "The component model adds exactly two keywords to the Rip language: component and render. Everything else — reactive state (:=), computed values (~=), effects (~>), methods, lifecycle hooks — is standard Rip syntax that already exists."
|
|
299
|
+
|
|
300
|
+
Card title: "Architecture"
|
|
301
|
+
p "Components are stored as source files and compiled on demand. The Router maps URLs to components using file-based conventions (like Next.js). When you navigate, the Renderer compiles the matching .rip file and mounts the resulting component. Each reactive binding creates a direct DOM effect — no virtual DOM diffing."
|
|
302
|
+
|
|
303
|
+
Card title: "Stack"
|
|
304
|
+
.btn-group
|
|
305
|
+
.stat
|
|
306
|
+
span "Compiler "
|
|
307
|
+
span.stat-value "47KB"
|
|
308
|
+
.stat
|
|
309
|
+
span "Framework "
|
|
310
|
+
span.stat-value "8KB"
|
|
311
|
+
.stat
|
|
312
|
+
span "Combined "
|
|
313
|
+
span.stat-value "52KB"
|
|
314
|
+
.stat
|
|
315
|
+
span "Build step "
|
|
316
|
+
span.stat-value "None"
|
|
317
|
+
.stat
|
|
318
|
+
span "Dependencies "
|
|
319
|
+
span.stat-value "Zero"
|
|
320
|
+
|
|
321
|
+
Card title: "Modules"
|
|
322
|
+
p "The framework lives in one file: ui.rip. It uses Rip's built-in reactive primitives directly — the same signals that power := and ~= in components. Deep reactive stash with path navigation. Component store for source management. File-based router mapping URLs to components. Renderer that compiles and mounts components with layout support."
|
|
323
|
+
'''
|
|
324
|
+
|
|
325
|
+
'components/card.rip': '''
|
|
326
|
+
# Card — reusable content card component
|
|
327
|
+
|
|
328
|
+
export Card = component
|
|
329
|
+
title =! ""
|
|
330
|
+
|
|
331
|
+
render
|
|
332
|
+
div.card
|
|
333
|
+
if title
|
|
334
|
+
h3 "#{title}"
|
|
335
|
+
@children
|
|
336
|
+
'''
|
|
337
|
+
|
|
338
|
+
data:
|
|
339
|
+
title: 'Rip UI Demo'
|
|
340
|
+
</script>
|
|
341
|
+
</body>
|
|
342
|
+
</html>
|