@almadar/std 16.187.0 → 16.189.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/behaviors/lolo/ui/core/.hand-authored.json +4 -4
- package/behaviors/lolo/ui/core/atoms/std-board.lolo +138 -4
- package/behaviors/lolo/ui/core/atoms/std-browse.lolo +148 -8
- package/behaviors/lolo/ui/core/atoms/std-calendar.lolo +161 -11
- package/behaviors/lolo/ui/core/atoms/std-event-log.lolo +255 -2
- package/behaviors/lolo/ui/core/atoms/std-gallery.lolo +148 -1
- package/behaviors/lolo/ui/core/atoms/std-list.lolo +193 -1
- package/behaviors/lolo/ui/core/atoms/std-record-detail.lolo +731 -0
- package/behaviors/lolo/ui/core/atoms/std-related.lolo +5 -1
- package/behaviors/lolo/ui/core/atoms/std-selection.lolo +182 -1
- package/behaviors/lolo/ui/core/atoms/std-tree.lolo +113 -3
- package/behaviors/lolo/ui/core/atoms/std-version-history.lolo +1 -1
- package/behaviors/lolo/ui/core/molecules/ui-document-details.lolo +80 -0
- package/behaviors/lolo/ui/core/molecules/ui-document-panel.lolo +213 -0
- package/behaviors/lolo/ui/core/molecules/{ui-rich-block-editor.lolo → ui-rich-text-editor.lolo} +25 -25
- package/behaviors/lolo/ui/game/atoms/std-area-fill.lolo +93 -0
- package/behaviors/lolo/ui/game/atoms/std-choice-gate.lolo +100 -0
- package/behaviors/lolo/ui/game/atoms/std-coef-pad.lolo +114 -0
- package/behaviors/lolo/ui/game/atoms/std-curve-fog.lolo +50 -0
- package/behaviors/lolo/ui/game/atoms/std-loop-lock.lolo +79 -0
- package/behaviors/lolo/ui/game/atoms/std-pad-lock.lolo +97 -0
- package/behaviors/lolo/ui/game/atoms/std-patrol-hazard.lolo +14 -7
- package/behaviors/lolo/ui/game/atoms/std-platformer-body.lolo +48 -5
- package/behaviors/lolo/ui/game/atoms/std-velocity-gate.lolo +101 -0
- package/behaviors/lolo/ui/game/atoms/std-zone-card.lolo +95 -0
- package/behaviors/lolo/ui/game/organisms/std-platformer-board-2d.lolo +9 -0
- package/behaviors/registry/infra/atoms/std-approval-gate.orb +16 -8
- package/behaviors/registry/infra/atoms/std-cache-aside.orb +62 -31
- package/behaviors/registry/infra/atoms/std-circuit-breaker.orb +32 -16
- package/behaviors/registry/infra/atoms/std-data-erasure.orb +16 -8
- package/behaviors/registry/infra/atoms/std-mod-queue.orb +16 -8
- package/behaviors/registry/infra/atoms/std-rate-limiter.orb +28 -14
- package/behaviors/registry/infra/organisms/std-status-lifecycle-control.orb +2 -1
- package/behaviors/registry/ml/organisms/std-escalating-decision.orb +10 -5
- package/behaviors/registry/ml/organisms/std-knowledge-tracing.orb +4 -2
- package/behaviors/registry/ui/core/atoms/std-billable-hour.orb +2 -1
- package/behaviors/registry/ui/core/atoms/std-board.orb +355 -2
- package/behaviors/registry/ui/core/atoms/std-browse.orb +304 -31
- package/behaviors/registry/ui/core/atoms/std-calendar.orb +393 -25
- package/behaviors/registry/ui/core/atoms/std-dashboard-grid-embedded.orb +2 -1
- package/behaviors/registry/ui/core/atoms/std-esign-flow.orb +4 -2
- package/behaviors/registry/ui/core/atoms/std-esign-request.orb +6 -3
- package/behaviors/registry/ui/core/atoms/std-event-log.orb +773 -0
- package/behaviors/registry/ui/core/atoms/std-export.orb +4 -2
- package/behaviors/registry/ui/core/atoms/std-gallery.orb +390 -0
- package/behaviors/registry/ui/core/atoms/std-import.orb +4 -2
- package/behaviors/registry/ui/core/atoms/std-list.orb +451 -0
- package/behaviors/registry/ui/core/atoms/std-record-detail.orb +3360 -0
- package/behaviors/registry/ui/core/atoms/std-related.orb +23 -1
- package/behaviors/registry/ui/core/atoms/std-selection.orb +436 -0
- package/behaviors/registry/ui/core/atoms/std-tree.orb +292 -1
- package/behaviors/registry/ui/core/atoms/std-version-history.orb +1 -1
- package/behaviors/registry/ui/core/molecules/ui-document-details.orb +342 -0
- package/behaviors/registry/ui/core/molecules/ui-document-panel.orb +655 -0
- package/behaviors/registry/ui/core/molecules/{ui-rich-block-editor.orb → ui-rich-text-editor.orb} +84 -97
- package/behaviors/registry/ui/game/atoms/std-area-fill.orb +955 -0
- package/behaviors/registry/ui/game/atoms/std-choice-gate.orb +855 -0
- package/behaviors/registry/ui/game/atoms/std-coef-pad.orb +928 -0
- package/behaviors/registry/ui/game/atoms/std-curve-fog.orb +337 -0
- package/behaviors/registry/ui/game/atoms/std-fx-trail.orb +1 -1
- package/behaviors/registry/ui/game/atoms/std-gate-lock.orb +1 -1
- package/behaviors/registry/ui/game/atoms/std-lives.orb +1 -1
- package/behaviors/registry/ui/game/atoms/std-loop-lock.orb +785 -0
- package/behaviors/registry/ui/game/atoms/std-pad-lock.orb +933 -0
- package/behaviors/registry/ui/game/atoms/std-patrol-hazard.orb +190 -44
- package/behaviors/registry/ui/game/atoms/std-platformer-body.orb +1330 -653
- package/behaviors/registry/ui/game/atoms/std-velocity-gate.orb +1040 -0
- package/behaviors/registry/ui/game/atoms/std-zone-card.orb +642 -0
- package/behaviors/registry/ui/game/organisms/std-arcade-board-3d.orb +8 -4
- package/behaviors/registry/ui/game/organisms/std-crew-board-2d.orb +6 -3
- package/behaviors/registry/ui/game/organisms/std-dungeon-board-2d.orb +10 -5
- package/behaviors/registry/ui/game/organisms/std-market-board-3d.orb +8 -4
- package/behaviors/registry/ui/game/organisms/std-objective-board-3d.orb +8 -4
- package/behaviors/registry/ui/game/organisms/std-platformer-board-2d.orb +36 -1
- package/behaviors/registry/ui/game/organisms/std-survival-board-3d.orb +8 -4
- package/behaviors/registry/ui/game/organisms/std-tactics-board-2d.orb +12 -6
- package/behaviors/registry/ui/game/organisms/std-tactics-board-3d.orb +8 -4
- package/behaviors/registry/ui/game/organisms/std-worldmap-board-2d.orb +6 -3
- package/behaviors/registry/ui/learning/atoms/std-learn-slope-probe.orb +1 -1
- package/behaviors/registry/ui/marketing/atoms/std-marketing-article.orb +2 -1
- package/behaviors/registry/ui/marketing/atoms/std-marketing-footer.orb +2 -1
- package/behaviors/registry/ui/marketing/atoms/std-marketing-split.orb +2 -1
- package/behaviors/registry/ui/marketing/atoms/std-marketing-stats.orb +2 -1
- package/behaviors/registry/ui/marketing/atoms/std-marketing-steps.orb +2 -1
- package/behaviors/registry/ui/marketing/atoms/std-marketing-team.orb +2 -1
- package/dist/behaviors/exports-reader.js +10971 -6255
- package/dist/behaviors/functions/index.d.ts +1205 -256
- package/dist/behaviors/functions/index.js +10932 -6245
- package/dist/behaviors/index.d.ts +1 -1
- package/dist/behaviors/index.js +10972 -6256
- package/dist/behaviors/query.js +10971 -6255
- package/dist/behaviors-embeddings.hash.json +3 -3
- package/dist/behaviors-embeddings.json +485 -469
- package/dist/behaviors-registry.json +6125 -2940
- package/dist/index.d.ts +1 -1
- package/dist/index.js +10972 -6256
- package/dist/knob-embeddings.hash.json +3 -3
- package/dist/knob-embeddings.json +1 -1
- package/dist/registry/factory-signatures.json +1 -1
- package/package.json +4 -4
- package/behaviors/lolo/ui/core/atoms/std-rich-editor.lolo +0 -790
- package/behaviors/registry/ui/core/atoms/std-rich-editor.orb +0 -2225
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
app ui-document-panel "1.0.0"
|
|
2
|
+
"DocumentPanel — a record rendered as one continuous document: editable title over the rich-text body, editing controls tucked into the header corner. — UI factory (1:1 with the @almadar/ui document-panel pattern)."
|
|
3
|
+
;; @capabilities "document page, note editor, wiki page, article editor, writing surface, rich text document, content editing canvas"
|
|
4
|
+
orbital DocumentPanelOrbital {
|
|
5
|
+
type DocumentPanelLoaded = Event { data : [DocumentPanelItem] } "DocumentPanel rows finished loading; payload.data holds the collection"
|
|
6
|
+
type DocumentPanelActionsItem = {
|
|
7
|
+
label : string!
|
|
8
|
+
event : string
|
|
9
|
+
icon : icon
|
|
10
|
+
variant : "primary" | "secondary" | "ghost" | "danger"
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
entity DocumentPanelItem [runtime] {
|
|
14
|
+
id : string!
|
|
15
|
+
title : string
|
|
16
|
+
value : string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
trait DocumentPanelRender -> @rebindable DocumentPanelItem [interaction, instance] {
|
|
20
|
+
initial: idle
|
|
21
|
+
|
|
22
|
+
state idle {
|
|
23
|
+
INIT -> idle
|
|
24
|
+
(fetch DocumentPanelItem { emit: { success: DocumentPanelLoaded } })
|
|
25
|
+
(set @entity.title @config.title)
|
|
26
|
+
(set @entity.value @config.value)
|
|
27
|
+
(render-ui main {
|
|
28
|
+
entity: @entity
|
|
29
|
+
title: @entity.title
|
|
30
|
+
subtitle: @config.subtitle
|
|
31
|
+
value: @entity.value
|
|
32
|
+
actions: @config.actions
|
|
33
|
+
maxInlineActions: @config.maxInlineActions
|
|
34
|
+
editing: @config.editing
|
|
35
|
+
titleCommitEvent: @config.titleCommitEvent
|
|
36
|
+
contentChangeEvent: @config.contentChangeEvent
|
|
37
|
+
editEvent: @config.editEvent
|
|
38
|
+
doneEvent: @config.doneEvent
|
|
39
|
+
editLabel: @config.editLabel
|
|
40
|
+
doneLabel: @config.doneLabel
|
|
41
|
+
autosaveHint: @config.autosaveHint
|
|
42
|
+
placeholder: @config.placeholder
|
|
43
|
+
className: @config.className
|
|
44
|
+
type: document-panel
|
|
45
|
+
})
|
|
46
|
+
DocumentPanelLoaded -> idle
|
|
47
|
+
(render-ui main {
|
|
48
|
+
entity: @payload.data
|
|
49
|
+
title: @entity.title
|
|
50
|
+
subtitle: @config.subtitle
|
|
51
|
+
value: @entity.value
|
|
52
|
+
actions: @config.actions
|
|
53
|
+
maxInlineActions: @config.maxInlineActions
|
|
54
|
+
editing: @config.editing
|
|
55
|
+
titleCommitEvent: @config.titleCommitEvent
|
|
56
|
+
contentChangeEvent: @config.contentChangeEvent
|
|
57
|
+
editEvent: @config.editEvent
|
|
58
|
+
doneEvent: @config.doneEvent
|
|
59
|
+
editLabel: @config.editLabel
|
|
60
|
+
doneLabel: @config.doneLabel
|
|
61
|
+
autosaveHint: @config.autosaveHint
|
|
62
|
+
placeholder: @config.placeholder
|
|
63
|
+
className: @config.className
|
|
64
|
+
type: document-panel
|
|
65
|
+
})
|
|
66
|
+
TITLE_COMMIT -> idle
|
|
67
|
+
(set @entity.title ?title)
|
|
68
|
+
(render-ui main {
|
|
69
|
+
entity: @entity
|
|
70
|
+
title: @entity.title
|
|
71
|
+
subtitle: @config.subtitle
|
|
72
|
+
value: @entity.value
|
|
73
|
+
actions: @config.actions
|
|
74
|
+
maxInlineActions: @config.maxInlineActions
|
|
75
|
+
editing: @config.editing
|
|
76
|
+
titleCommitEvent: @config.titleCommitEvent
|
|
77
|
+
contentChangeEvent: @config.contentChangeEvent
|
|
78
|
+
editEvent: @config.editEvent
|
|
79
|
+
doneEvent: @config.doneEvent
|
|
80
|
+
editLabel: @config.editLabel
|
|
81
|
+
doneLabel: @config.doneLabel
|
|
82
|
+
autosaveHint: @config.autosaveHint
|
|
83
|
+
placeholder: @config.placeholder
|
|
84
|
+
className: @config.className
|
|
85
|
+
type: document-panel
|
|
86
|
+
})
|
|
87
|
+
CONTENT_CHANGE -> idle
|
|
88
|
+
(set @entity.value ?value)
|
|
89
|
+
(render-ui main {
|
|
90
|
+
entity: @entity
|
|
91
|
+
title: @entity.title
|
|
92
|
+
subtitle: @config.subtitle
|
|
93
|
+
value: @entity.value
|
|
94
|
+
actions: @config.actions
|
|
95
|
+
maxInlineActions: @config.maxInlineActions
|
|
96
|
+
editing: @config.editing
|
|
97
|
+
titleCommitEvent: @config.titleCommitEvent
|
|
98
|
+
contentChangeEvent: @config.contentChangeEvent
|
|
99
|
+
editEvent: @config.editEvent
|
|
100
|
+
doneEvent: @config.doneEvent
|
|
101
|
+
editLabel: @config.editLabel
|
|
102
|
+
doneLabel: @config.doneLabel
|
|
103
|
+
autosaveHint: @config.autosaveHint
|
|
104
|
+
placeholder: @config.placeholder
|
|
105
|
+
className: @config.className
|
|
106
|
+
type: document-panel
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
emits {
|
|
111
|
+
@config.titleCommitEvent -> external {
|
|
112
|
+
title : string!
|
|
113
|
+
id : string!
|
|
114
|
+
}
|
|
115
|
+
@description "Emitted when the title commits: { title, id }"
|
|
116
|
+
@tier "essential"
|
|
117
|
+
@config.contentChangeEvent -> external {
|
|
118
|
+
value : string!
|
|
119
|
+
}
|
|
120
|
+
@description "Forwarded to the embedded RichTextEditor's autosave stream: { value }"
|
|
121
|
+
@tier "essential"
|
|
122
|
+
@config.editEvent -> external {
|
|
123
|
+
id : string!
|
|
124
|
+
}
|
|
125
|
+
@description "Emitted by the header Edit affordance and a click on the read body: { id }"
|
|
126
|
+
@tier "essential"
|
|
127
|
+
@config.doneEvent -> external {
|
|
128
|
+
id : string!
|
|
129
|
+
}
|
|
130
|
+
@description "Emitted by the Done button that ends the editing session: { id }"
|
|
131
|
+
@tier "essential"
|
|
132
|
+
DocumentPanelLoaded
|
|
133
|
+
@description "DocumentPanel rows finished loading; payload.data holds the collection."
|
|
134
|
+
@synonyms "loaded, fetched, retrieved"
|
|
135
|
+
@tier "essential"
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
listens {
|
|
139
|
+
TITLE_COMMIT {
|
|
140
|
+
title : string!
|
|
141
|
+
id : string!
|
|
142
|
+
}
|
|
143
|
+
CONTENT_CHANGE {
|
|
144
|
+
value : string!
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
config {
|
|
149
|
+
title : string
|
|
150
|
+
@label "Title"
|
|
151
|
+
@description "Document title (falls back to 'Untitled' for a blank draft)"
|
|
152
|
+
@tier "presentation"
|
|
153
|
+
subtitle : string
|
|
154
|
+
@label "Subtitle"
|
|
155
|
+
@description "Muted byline under the title; hidden when empty"
|
|
156
|
+
@tier "presentation"
|
|
157
|
+
value : string
|
|
158
|
+
@label "Value"
|
|
159
|
+
@description "The document body as rich-text HTML"
|
|
160
|
+
@tier "presentation"
|
|
161
|
+
actions : [DocumentPanelActionsItem] = []
|
|
162
|
+
@label "Actions"
|
|
163
|
+
@description "Header actions (settings, history, …). At most one explicit primary stays inline; the rest live under a single '⋯' menu — the title owns its row."
|
|
164
|
+
@tier "presentation"
|
|
165
|
+
maxInlineActions : number
|
|
166
|
+
@label "Max Inline Actions"
|
|
167
|
+
@description "0 collapses even the primary action into the menu"
|
|
168
|
+
@tier "presentation"
|
|
169
|
+
editing : boolean = false
|
|
170
|
+
@label "Editing"
|
|
171
|
+
@description "false = read surface with click-to-edit body; true = live editor"
|
|
172
|
+
@tier "presentation"
|
|
173
|
+
titleCommitEvent : string = "TITLE_COMMIT"
|
|
174
|
+
@label "Title Commit Event"
|
|
175
|
+
@description "Emitted when the title commits: { title, id }"
|
|
176
|
+
@tier "presentation"
|
|
177
|
+
contentChangeEvent : string = "CONTENT_CHANGE"
|
|
178
|
+
@label "Content Change Event"
|
|
179
|
+
@description "Forwarded to the embedded RichTextEditor's autosave stream: { value }"
|
|
180
|
+
@tier "presentation"
|
|
181
|
+
editEvent : string = "EDIT"
|
|
182
|
+
@label "Edit Event"
|
|
183
|
+
@description "Emitted by the header Edit affordance and a click on the read body: { id }"
|
|
184
|
+
@tier "presentation"
|
|
185
|
+
doneEvent : string = "DONE"
|
|
186
|
+
@label "Done Event"
|
|
187
|
+
@description "Emitted by the Done button that ends the editing session: { id }"
|
|
188
|
+
@tier "presentation"
|
|
189
|
+
editLabel : string
|
|
190
|
+
@label "Edit Label"
|
|
191
|
+
@description "editLabel prop"
|
|
192
|
+
@tier "presentation"
|
|
193
|
+
doneLabel : string
|
|
194
|
+
@label "Done Label"
|
|
195
|
+
@description "doneLabel prop"
|
|
196
|
+
@tier "presentation"
|
|
197
|
+
autosaveHint : string
|
|
198
|
+
@label "Autosave Hint"
|
|
199
|
+
@description "Muted caption beside Done telling the author work persists on its own"
|
|
200
|
+
@tier "presentation"
|
|
201
|
+
placeholder : string
|
|
202
|
+
@label "Placeholder"
|
|
203
|
+
@description "Placeholder for an empty body"
|
|
204
|
+
@tier "presentation"
|
|
205
|
+
className : string
|
|
206
|
+
@label "Class Name"
|
|
207
|
+
@description "className prop"
|
|
208
|
+
@tier "presentation"
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
page "/document-panel" as DocumentPanelPage -> DocumentPanelRender
|
|
213
|
+
}
|
package/behaviors/lolo/ui/core/molecules/{ui-rich-block-editor.lolo → ui-rich-text-editor.lolo}
RENAMED
|
@@ -1,56 +1,60 @@
|
|
|
1
|
-
app ui-rich-
|
|
2
|
-
"
|
|
3
|
-
orbital
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
entity RichBlockEditorItem [runtime] {
|
|
1
|
+
app ui-rich-text-editor "1.0.0"
|
|
2
|
+
"RichTextEditor — UI factory (1:1 with the @almadar/ui rich-text-editor pattern)."
|
|
3
|
+
orbital RichTextEditorOrbital {
|
|
4
|
+
entity RichTextEditorItem [runtime] {
|
|
7
5
|
id : string!
|
|
6
|
+
value : string
|
|
8
7
|
}
|
|
9
8
|
|
|
10
|
-
trait
|
|
9
|
+
trait RichTextEditorRender -> @rebindable RichTextEditorItem [interaction, instance] {
|
|
11
10
|
initial: idle
|
|
12
11
|
|
|
13
12
|
state idle {
|
|
14
13
|
INIT -> idle
|
|
15
|
-
(
|
|
14
|
+
(set @entity.value @config.value)
|
|
16
15
|
(render-ui main {
|
|
17
|
-
|
|
16
|
+
value: @entity.value
|
|
18
17
|
onChange: @config.onChange
|
|
19
18
|
changeEvent: @config.changeEvent
|
|
20
19
|
readOnly: @config.readOnly
|
|
21
20
|
placeholder: @config.placeholder
|
|
22
|
-
enableBlocks: @config.enableBlocks
|
|
23
21
|
showToolbar: @config.showToolbar
|
|
24
22
|
className: @config.className
|
|
25
|
-
type: rich-
|
|
23
|
+
type: rich-text-editor
|
|
26
24
|
})
|
|
27
|
-
|
|
25
|
+
CHANGE -> idle
|
|
26
|
+
(set @entity.value ?value)
|
|
28
27
|
(render-ui main {
|
|
29
|
-
|
|
28
|
+
value: @entity.value
|
|
30
29
|
onChange: @config.onChange
|
|
31
30
|
changeEvent: @config.changeEvent
|
|
32
31
|
readOnly: @config.readOnly
|
|
33
32
|
placeholder: @config.placeholder
|
|
34
|
-
enableBlocks: @config.enableBlocks
|
|
35
33
|
showToolbar: @config.showToolbar
|
|
36
34
|
className: @config.className
|
|
37
|
-
type: rich-
|
|
35
|
+
type: rich-text-editor
|
|
38
36
|
})
|
|
39
37
|
}
|
|
40
38
|
|
|
41
39
|
emits {
|
|
42
40
|
@config.onChange -> external {
|
|
43
|
-
|
|
41
|
+
value : string
|
|
44
42
|
}
|
|
45
43
|
@description "onChange prop"
|
|
46
44
|
@tier "essential"
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
listens {
|
|
48
|
+
CHANGE {
|
|
49
|
+
value : string
|
|
50
|
+
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
config {
|
|
54
|
+
value : string
|
|
55
|
+
@label "Value"
|
|
56
|
+
@description "The rich text content as HTML. Sanitized before rendering and on every change."
|
|
57
|
+
@tier "presentation"
|
|
54
58
|
onChange : string = "CHANGE"
|
|
55
59
|
@label "On Change"
|
|
56
60
|
@description "onChange prop"
|
|
@@ -67,10 +71,6 @@ orbital RichBlockEditorOrbital {
|
|
|
67
71
|
@label "Placeholder"
|
|
68
72
|
@description "placeholder prop"
|
|
69
73
|
@tier "presentation"
|
|
70
|
-
enableBlocks : boolean = true
|
|
71
|
-
@label "Enable Blocks"
|
|
72
|
-
@description "Opt-in to the Notion-style block authoring chrome (insert toolbar, per-row +/menu gutter, turn-into menu). Off by default: the editor renders as a plain rich text surface that edits block content inline without any add-block affordances."
|
|
73
|
-
@tier "presentation"
|
|
74
74
|
showToolbar : boolean = true
|
|
75
75
|
@label "Show Toolbar"
|
|
76
76
|
@description "showToolbar prop"
|
|
@@ -82,5 +82,5 @@ orbital RichBlockEditorOrbital {
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
page "/rich-
|
|
85
|
+
page "/rich-text-editor" as RichTextEditorPage -> RichTextEditorRender
|
|
86
86
|
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
app std-area-fill "1.0.0"
|
|
2
|
+
"Headless area-fill mechanic: the player paints area under a curve by moving through a fill region. Listens BODY_MOVED and, per fill, accumulates height(x) × |dx| while the player is inside the fill's x-range (the per-tick step is clamped to 0.5 world units — the physical per-tick maximum — so teleports and respawns paint nothing) — height is linear interpolation of the config curve samples above the fill's baseline (the platformer body's leftSamp/rightSamp idiom). rateMode picks the rate: \"area\" (height × |dx|, default — fills fastest where the curve is tallest), \"speed\" (|dx| only, flat rate), or \"ride\" (|dx| only while the move reports skating — walking the same distance paints nothing). When a fill's filled reaches its target it flips reported and emits AREA_FILLED once (the shared entity carries the rest); each fill also stamps a frontier (x0 + span × min(1, filled/target)) so boards can shade the painted region left-to-right as the gauge grows. RESTART resets every fill. Renders nothing; boards shade the painted region and read filled/frontier from the shared entity (the render authority)."
|
|
3
|
+
orbital AreaFillOrbital {
|
|
4
|
+
type AreaFillSample = {
|
|
5
|
+
x : number!
|
|
6
|
+
y : number!
|
|
7
|
+
}
|
|
8
|
+
type AreaFill = {
|
|
9
|
+
id : string!
|
|
10
|
+
x0 : number!
|
|
11
|
+
x1 : number!
|
|
12
|
+
baseline : number!
|
|
13
|
+
target : number!
|
|
14
|
+
rateMode : string
|
|
15
|
+
hint : string
|
|
16
|
+
samples : [AreaFillSample]!
|
|
17
|
+
filled : number
|
|
18
|
+
frontier : number
|
|
19
|
+
reported : boolean
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
entity AreaFillData [runtime, shared] {
|
|
23
|
+
id : string!
|
|
24
|
+
fills : [AreaFill] = []
|
|
25
|
+
lastX : number = 0
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
trait AreaFill -> @rebindable AreaFillData [lifecycle] {
|
|
29
|
+
initial: active
|
|
30
|
+
|
|
31
|
+
state active {
|
|
32
|
+
INIT -> active
|
|
33
|
+
(set @entity.fills (array/map @config.fills (fn f (object/merge @f { filled: 0, frontier: (object/get @f x0), reported: false }))))
|
|
34
|
+
(set @entity.lastX 0)
|
|
35
|
+
|
|
36
|
+
BODY_MOVED -> active when @config.running
|
|
37
|
+
(let ((adx (math/abs (- @payload.x @entity.lastX))))
|
|
38
|
+
(set @entity.fills (array/map @entity.fills (fn f
|
|
39
|
+
(let ((inside (and (>= @payload.x (object/get @f x0)) (<= @payload.x (object/get @f x1)))))
|
|
40
|
+
(let ((leftSamp (array/last (array/filter (object/get @f samples) (fn s (<= (object/get @s x) @payload.x))))))
|
|
41
|
+
(let ((rightSamp (array/first (array/filter (object/get @f samples) (fn s (> (object/get @s x) @payload.x))))))
|
|
42
|
+
(let ((h (if (or (== @leftSamp null) (== @rightSamp null)) 0
|
|
43
|
+
(- (+ (object/get @leftSamp y) (* (/ (- (object/get @rightSamp y) (object/get @leftSamp y)) (- (object/get @rightSamp x) (object/get @leftSamp x))) (- @payload.x (object/get @leftSamp x)))) (object/get @f baseline)))))
|
|
44
|
+
(let ((step (math/min @adx 0.5)))
|
|
45
|
+
(let ((rate (if @inside
|
|
46
|
+
(if (== (object/get @f rateMode) "ride") (if @payload.skating @step 0)
|
|
47
|
+
(if (== (object/get @f rateMode) "speed") @step
|
|
48
|
+
(* @step @h)))
|
|
49
|
+
0)))
|
|
50
|
+
(let ((newFilled (+ (object/get @f filled) @rate)))
|
|
51
|
+
(object/merge @f { filled: @newFilled, frontier: (+ (object/get @f x0) (* (- (object/get @f x1) (object/get @f x0)) (math/min 1 (/ @newFilled (object/get @f target))))) }))))))))))))
|
|
52
|
+
(set @entity.lastX @payload.x)
|
|
53
|
+
(let ((newly (array/filter @entity.fills (fn f (and (>= (object/get @f filled) (object/get @f target)) (== (object/get @f reported) false))))))
|
|
54
|
+
(if (> (array/len @newly) 0)
|
|
55
|
+
(do
|
|
56
|
+
(set @entity.fills (array/map @entity.fills (fn f (if (== (object/get @f id) (object/get (array/first @newly) id)) (object/merge @f { reported: true }) @f))))
|
|
57
|
+
(emit AREA_FILLED { id: (object/get (array/first @newly) id), x: @payload.x, y: @payload.y }))
|
|
58
|
+
null))
|
|
59
|
+
|
|
60
|
+
RESTART -> active
|
|
61
|
+
(set @entity.fills (array/map @config.fills (fn f (object/merge @f { filled: 0, frontier: (object/get @f x0), reported: false }))))
|
|
62
|
+
(set @entity.lastX 0)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
emits {
|
|
66
|
+
AREA_FILLED -> external {
|
|
67
|
+
id : string!
|
|
68
|
+
x : number!
|
|
69
|
+
y : number!
|
|
70
|
+
}
|
|
71
|
+
@description "A fill's painted area reached its target (x, y = the player's position at completion, for burst placement). When several fills complete on one move, the first reports and the shared entity carries the rest."
|
|
72
|
+
@tier "domain"
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
listens {
|
|
76
|
+
BODY_MOVED { x : number!, y : number!, vx : number!, vy : number!, grounded : boolean!, skating : boolean!, speed : number! }
|
|
77
|
+
RESTART { id : string }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
config {
|
|
81
|
+
running : boolean = true
|
|
82
|
+
@label "Running"
|
|
83
|
+
@description "When true, BODY_MOVED paints every fill the player moves through; toggle to freeze every fill as-is."
|
|
84
|
+
@tier "domain"
|
|
85
|
+
fills : [AreaFill] = []
|
|
86
|
+
@label "Fills"
|
|
87
|
+
@description "One row per fill region: id, x-range (x0..x1 in world units), baseline the height is measured from, target area, rateMode (area | speed | ride — default area), optional hint, and the curve samples to interpolate. filled/frontier/reported are stamped at runtime."
|
|
88
|
+
@tier "domain"
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
page "/area-fill" as AreaFillPage -> AreaFill
|
|
93
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
app std-choice-gate "1.0.0"
|
|
2
|
+
"Headless choice-gate mechanic for side-view boards: a gate of N labeled option platforms where progress stops until the player commits to an answer. Listens BODY_MOVED and judges a gate when the player stands grounded on one of its option platforms (|dx| <= reachX of the option center, |dy| <= reachY of the gate's option plane — the pad-lock touch pattern): the correct option stamps the gate solved + chosen on the shared entity and emits CHOICE_CORRECT once (a solved gate never judges again, so the path stays open); a wrong option emits CHOICE_WRONG carrying the gate id, the chosen optionId, and the option's world position, so the board can bounce the player back and float the gate's hint (the board owns the bounce — the atom only reports). RESTART unsolves every gate. Renders nothing; boards draw the option platforms and labels from the shared entity (the render authority)."
|
|
3
|
+
orbital ChoiceGateOrbital {
|
|
4
|
+
type ChoiceOption = {
|
|
5
|
+
id : string!
|
|
6
|
+
dx : number!
|
|
7
|
+
label : string!
|
|
8
|
+
correct : boolean!
|
|
9
|
+
}
|
|
10
|
+
type ChoiceGate = {
|
|
11
|
+
id : string!
|
|
12
|
+
x : number!
|
|
13
|
+
y : number!
|
|
14
|
+
hint : string
|
|
15
|
+
options : [ChoiceOption]!
|
|
16
|
+
solved : boolean
|
|
17
|
+
chosen : string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
entity ChoiceGateData [runtime, shared] {
|
|
21
|
+
id : string!
|
|
22
|
+
choices : [ChoiceGate] = []
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
trait ChoiceGate -> @rebindable ChoiceGateData [lifecycle] {
|
|
26
|
+
initial: active
|
|
27
|
+
|
|
28
|
+
state active {
|
|
29
|
+
INIT -> active
|
|
30
|
+
(set @entity.choices (array/map @config.gates (fn g (object/merge @g { solved: false, chosen: "" }))))
|
|
31
|
+
|
|
32
|
+
BODY_MOVED -> active when @config.running
|
|
33
|
+
(let ((touch (array/filter @entity.choices (fn g
|
|
34
|
+
(and (== (object/get @g solved) false)
|
|
35
|
+
(> (array/len (array/filter (object/get @g options) (fn o
|
|
36
|
+
(and (== @payload.grounded true)
|
|
37
|
+
(and (<= (math/abs (- @payload.x (+ (object/get @g x) (object/get @o dx)))) @config.reachX)
|
|
38
|
+
(<= (math/abs (- @payload.y (object/get @g y))) @config.reachY)))))) 0))))))
|
|
39
|
+
(if (> (array/len @touch) 0)
|
|
40
|
+
(let ((g (array/first @touch)))
|
|
41
|
+
(let ((o (array/first (array/filter (object/get @g options) (fn oo
|
|
42
|
+
(and (== @payload.grounded true)
|
|
43
|
+
(and (<= (math/abs (- @payload.x (+ (object/get @g x) (object/get @oo dx)))) @config.reachX)
|
|
44
|
+
(<= (math/abs (- @payload.y (object/get @g y))) @config.reachY))))))))
|
|
45
|
+
(if (== (object/get @o correct) true)
|
|
46
|
+
(do
|
|
47
|
+
(set @entity.choices (array/map @entity.choices (fn c (if (== (object/get @c id) (object/get @g id)) (object/merge @c { solved: true, chosen: (object/get @o id) }) @c))))
|
|
48
|
+
(emit CHOICE_CORRECT { id: (object/get @g id), x: (object/get @g x), y: (object/get @g y) }))
|
|
49
|
+
(emit CHOICE_WRONG { id: (object/get @g id), optionId: (object/get @o id), x: (+ (object/get @g x) (object/get @o dx)), y: (object/get @g y) }))))
|
|
50
|
+
null))
|
|
51
|
+
|
|
52
|
+
RESTART -> active
|
|
53
|
+
(set @entity.choices (array/map @config.gates (fn g (object/merge @g { solved: false, chosen: "" }))))
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
emits {
|
|
57
|
+
CHOICE_CORRECT -> external {
|
|
58
|
+
id : string!
|
|
59
|
+
x : number!
|
|
60
|
+
y : number!
|
|
61
|
+
}
|
|
62
|
+
@description "The player landed grounded on the gate's correct option; fires once per gate (solved gates stop judging). Carries the gate center so boards can burst the unlock there."
|
|
63
|
+
@tier "domain"
|
|
64
|
+
CHOICE_WRONG -> external {
|
|
65
|
+
id : string!
|
|
66
|
+
optionId : string!
|
|
67
|
+
x : number!
|
|
68
|
+
y : number!
|
|
69
|
+
}
|
|
70
|
+
@description "The player landed grounded on a wrong option; carries the gate id, the chosen optionId, and the option's world position so boards can bounce the player back to the approach and float the gate's hint."
|
|
71
|
+
@tier "domain"
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
listens {
|
|
75
|
+
BODY_MOVED { x : number!, y : number!, vx : number!, vy : number!, grounded : boolean!, skating : boolean!, speed : number! }
|
|
76
|
+
RESTART { id : string }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
config {
|
|
80
|
+
running : boolean = true
|
|
81
|
+
@label "Running"
|
|
82
|
+
@description "When true, BODY_MOVED judges option landings; toggle to freeze every gate as-is."
|
|
83
|
+
@tier "domain"
|
|
84
|
+
gates : [ChoiceGate] = []
|
|
85
|
+
@label "Choice Gates"
|
|
86
|
+
@description "One row per gate: id, gate center (x, y — y is the option-platform surface the judge tests against), the hint floated on a wrong answer, and the options: id, dx offset from the gate center, the label drawn above the platform, and correct. solved/chosen are stamped at runtime."
|
|
87
|
+
@tier "domain"
|
|
88
|
+
reachX : number = 0.7
|
|
89
|
+
@label "Reach X"
|
|
90
|
+
@description "Horizontal tolerance (world units) around an option platform's center when testing a landing (approximates the body's half-width plus a step)."
|
|
91
|
+
@tier "domain"
|
|
92
|
+
reachY : number = 1.2
|
|
93
|
+
@label "Reach Y"
|
|
94
|
+
@description "Vertical tolerance (world units) around the gate's option plane when testing a landing (covers small hops onto the platform surface)."
|
|
95
|
+
@tier "domain"
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
page "/choice-gate" as ChoiceGatePage -> ChoiceGate
|
|
100
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
app std-coef-pad "1.0.0"
|
|
2
|
+
"Headless coefficient-pad mechanic for graph-riding boards: floor pads that reshape the road the player is riding (y = a·f(x−h) + k). Listens BODY_MOVED and, when the player stands grounded on an unapplied pad (|dx| <= reachX, |dy| <= reachY of the pad center — the pad-lock touch pattern), stamps the pad applied on the shared entity and emits COEF_PAD_APPLIED carrying the pad position AND its mod (x0, x1, a, h, k), so the board can append the mod to the body authority's curveMods (std-platformer-body with curveModsEnabled) and hop the player onto the reshaped surface. When every pad is applied it emits COEF_PAD_SET_COMPLETED once. RESTART un-applies every pad (the board clears curveMods alongside — the pad reports, the board owns the road). Renders nothing; boards draw the pads and read applied from the shared entity (the render authority)."
|
|
3
|
+
orbital CoefPadOrbital {
|
|
4
|
+
type CoefMod = {
|
|
5
|
+
x0 : number!
|
|
6
|
+
x1 : number!
|
|
7
|
+
a : number!
|
|
8
|
+
h : number!
|
|
9
|
+
k : number!
|
|
10
|
+
}
|
|
11
|
+
type CoefModSample = {
|
|
12
|
+
x : number!
|
|
13
|
+
y : number!
|
|
14
|
+
}
|
|
15
|
+
type CoefPad = {
|
|
16
|
+
id : string!
|
|
17
|
+
x : number!
|
|
18
|
+
y : number!
|
|
19
|
+
mod : CoefMod!
|
|
20
|
+
glyph : string
|
|
21
|
+
hint : string
|
|
22
|
+
hop : boolean
|
|
23
|
+
samples : [CoefModSample]
|
|
24
|
+
applied : boolean
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
entity CoefPadData [runtime, shared] {
|
|
28
|
+
id : string!
|
|
29
|
+
coefPads : [CoefPad] = []
|
|
30
|
+
coefCompleted : boolean = false
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
trait CoefPadLock -> @rebindable CoefPadData [lifecycle] {
|
|
34
|
+
initial: active
|
|
35
|
+
|
|
36
|
+
state active {
|
|
37
|
+
INIT -> active
|
|
38
|
+
(set @entity.coefPads (array/map @config.coefPads (fn p (object/merge @p { applied: false }))))
|
|
39
|
+
(set @entity.coefCompleted false)
|
|
40
|
+
|
|
41
|
+
BODY_MOVED -> active when @config.running
|
|
42
|
+
(let ((touch (array/filter @entity.coefPads (fn p
|
|
43
|
+
(and (== (object/get @p applied) false)
|
|
44
|
+
(and (== @payload.grounded true)
|
|
45
|
+
(and (<= (math/abs (- @payload.x (object/get @p x))) @config.reachX)
|
|
46
|
+
(<= (math/abs (- @payload.y (object/get @p y))) @config.reachY))))))))
|
|
47
|
+
(if (> (array/len @touch) 0)
|
|
48
|
+
(let ((p (array/first @touch)))
|
|
49
|
+
(let ((m (object/get @p mod)))
|
|
50
|
+
(let ((remaining (array/len (array/filter @entity.coefPads (fn c (and (== (object/get @c applied) false) (!= (object/get @c id) (object/get @p id))))))))
|
|
51
|
+
(do
|
|
52
|
+
(set @entity.coefPads (array/map @entity.coefPads (fn c (if (== (object/get @c id) (object/get @p id)) (object/merge @c { applied: true }) @c))))
|
|
53
|
+
(emit COEF_PAD_APPLIED { id: (object/get @p id), x: (object/get @p x), y: (object/get @p y), x0: (object/get @m x0), x1: (object/get @m x1), a: (object/get @m a), h: (object/get @m h), k: (object/get @m k), hint: (object/get @p hint), hop: (object/get @p hop), samples: (object/get @p samples) })
|
|
54
|
+
(if (and (== @entity.coefCompleted false) (== @remaining 0))
|
|
55
|
+
(do
|
|
56
|
+
(set @entity.coefCompleted true)
|
|
57
|
+
(emit COEF_PAD_SET_COMPLETED {}))
|
|
58
|
+
null)))))
|
|
59
|
+
null))
|
|
60
|
+
|
|
61
|
+
RESTART -> active
|
|
62
|
+
(set @entity.coefPads (array/map @config.coefPads (fn p (object/merge @p { applied: false }))))
|
|
63
|
+
(set @entity.coefCompleted false)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
emits {
|
|
67
|
+
COEF_PAD_APPLIED -> external {
|
|
68
|
+
id : string!
|
|
69
|
+
x : number!
|
|
70
|
+
y : number!
|
|
71
|
+
x0 : number!
|
|
72
|
+
x1 : number!
|
|
73
|
+
a : number!
|
|
74
|
+
h : number!
|
|
75
|
+
k : number!
|
|
76
|
+
hint : string
|
|
77
|
+
hop : boolean
|
|
78
|
+
samples : [CoefModSample]
|
|
79
|
+
}
|
|
80
|
+
@description "The player stood on an unapplied coefficient pad; carries the pad position and the full mod (x0, x1, a, h, k) so the board appends it to the body authority's curveMods and hops the player onto the reshaped road. Fires once per pad per run."
|
|
81
|
+
@tier "domain"
|
|
82
|
+
COEF_PAD_SET_COMPLETED -> external {
|
|
83
|
+
}
|
|
84
|
+
@description "Every coefficient pad in the set is applied; fires once per run. Boards layer the completion fx and dismiss the zone card."
|
|
85
|
+
@tier "domain"
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
listens {
|
|
89
|
+
BODY_MOVED { x : number!, y : number!, vx : number!, vy : number!, grounded : boolean!, skating : boolean!, speed : number! }
|
|
90
|
+
RESTART { id : string }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
config {
|
|
94
|
+
running : boolean = true
|
|
95
|
+
@label "Running"
|
|
96
|
+
@description "When true, BODY_MOVED applies touched pads; toggle to freeze every pad as-is."
|
|
97
|
+
@tier "domain"
|
|
98
|
+
coefPads : [CoefPad] = []
|
|
99
|
+
@label "Coefficient Pads"
|
|
100
|
+
@description "One row per pad: id, center (x, y in world units), the mod applied to the road while inside x0..x1 (a scales slope+height, h shifts horizontally, k lifts), and the hint floated on apply, and hop (true = the board teleports the player onto the reshaped surface — use when the mod steps the road far from the pad; false = the skate snap carries the player, preserving speed). applied is stamped at runtime."
|
|
101
|
+
@tier "domain"
|
|
102
|
+
reachX : number = 0.7
|
|
103
|
+
@label "Reach X"
|
|
104
|
+
@description "Horizontal tolerance (world units) around a pad's center when testing player contact (approximates the body's half-width plus a step)."
|
|
105
|
+
@tier "domain"
|
|
106
|
+
reachY : number = 1.2
|
|
107
|
+
@label "Reach Y"
|
|
108
|
+
@description "Vertical tolerance (world units) around a pad's center when testing player contact (covers riding the surface the pad sits on)."
|
|
109
|
+
@tier "domain"
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
page "/coef-pad" as CoefPadPage -> CoefPadLock
|
|
114
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
app std-curve-fog "1.0.0"
|
|
2
|
+
"Headless prediction-fog mechanic for graph-riding boards: in configured world regions the road ahead is only partially REVEALED — the atom listens BODY_MOVED and stamps fogX (the reveal horizon = player x + the region's lookAhead) onto the shared entity, and the board's render layer clips or ghosts every road sample past it, so the player must commit to jumps before the landing is visible and internalize where the function GOES (blowup, flattening, slow growth, divergence). Outside every region fogX sits at a large sentinel, i.e. fully revealed. Physics is untouched: the collider keeps the whole curve — the fog is purely visual. Renders nothing; boards clip their own curve/region samples against @entity.fogX (the render authority)."
|
|
3
|
+
orbital CurveFogOrbital {
|
|
4
|
+
type CurveFogRegion = {
|
|
5
|
+
x0 : number!
|
|
6
|
+
x1 : number!
|
|
7
|
+
lookAhead : number!
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
entity CurveFogData [runtime, shared] {
|
|
11
|
+
id : string!
|
|
12
|
+
fogX : number = 99999
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
trait CurveFog -> @rebindable CurveFogData [lifecycle] {
|
|
16
|
+
initial: active
|
|
17
|
+
|
|
18
|
+
state active {
|
|
19
|
+
INIT -> active
|
|
20
|
+
(set @entity.fogX 99999)
|
|
21
|
+
|
|
22
|
+
BODY_MOVED -> active when @config.running
|
|
23
|
+
(let ((rmatch (array/filter @config.regions (fn r (and (>= @payload.x (object/get @r x0)) (< @payload.x (object/get @r x1)))))))
|
|
24
|
+
(if (> (array/len @rmatch) 0)
|
|
25
|
+
(set @entity.fogX (+ @payload.x (object/get (array/first @rmatch) lookAhead)))
|
|
26
|
+
(set @entity.fogX 99999)))
|
|
27
|
+
|
|
28
|
+
RESTART -> active
|
|
29
|
+
(set @entity.fogX 99999)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
listens {
|
|
33
|
+
BODY_MOVED { x : number!, y : number!, vx : number!, vy : number!, grounded : boolean!, skating : boolean!, speed : number! }
|
|
34
|
+
RESTART { id : string }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
config {
|
|
38
|
+
running : boolean = true
|
|
39
|
+
@label "Running"
|
|
40
|
+
@description "When true, BODY_MOVED tracks the reveal horizon; toggle to freeze the fog where it is."
|
|
41
|
+
@tier "domain"
|
|
42
|
+
regions : [CurveFogRegion] = []
|
|
43
|
+
@label "Fog Regions"
|
|
44
|
+
@description "One row per fogged world span: x-range (x0..x1) and lookAhead — how far past the player the road stays revealed while inside. Small values force prediction (blowups), large values suit slow tail lessons. Empty list = no fog anywhere."
|
|
45
|
+
@tier "domain"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
page "/curve-fog" as CurveFogPage -> CurveFog
|
|
50
|
+
}
|