frameyard 0.8.0 → 0.9.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.
Files changed (285) hide show
  1. package/CHANGELOG.md +110 -14
  2. package/GLOSSARY.md +112 -2
  3. package/README.md +302 -265
  4. package/dist/assets/integrations.d.ts +8 -4
  5. package/dist/assets/integrations.d.ts.map +1 -1
  6. package/dist/assets/integrations.js +8 -4
  7. package/dist/assets/integrations.js.map +1 -1
  8. package/dist/assets/provenance.d.ts +2 -2
  9. package/dist/assets/provenance.d.ts.map +1 -1
  10. package/dist/assets/provenance.js +5 -3
  11. package/dist/assets/provenance.js.map +1 -1
  12. package/dist/assets/tts-supertonic.d.ts +28 -0
  13. package/dist/assets/tts-supertonic.d.ts.map +1 -0
  14. package/dist/assets/tts-supertonic.js +156 -0
  15. package/dist/assets/tts-supertonic.js.map +1 -0
  16. package/dist/assets/tts.d.ts +18 -6
  17. package/dist/assets/tts.d.ts.map +1 -1
  18. package/dist/assets/tts.js +219 -97
  19. package/dist/assets/tts.js.map +1 -1
  20. package/dist/cli.js +1685 -93
  21. package/dist/cli.js.map +1 -1
  22. package/dist/composition/format.d.ts +1 -1
  23. package/dist/composition/format.d.ts.map +1 -1
  24. package/dist/composition/format.js +1 -1
  25. package/dist/composition/format.js.map +1 -1
  26. package/dist/context.d.ts +82 -0
  27. package/dist/context.d.ts.map +1 -0
  28. package/dist/context.js +277 -0
  29. package/dist/context.js.map +1 -0
  30. package/dist/contracts.d.ts +25 -0
  31. package/dist/contracts.d.ts.map +1 -1
  32. package/dist/contracts.js.map +1 -1
  33. package/dist/doctor.d.ts +14 -1
  34. package/dist/doctor.d.ts.map +1 -1
  35. package/dist/doctor.js +166 -7
  36. package/dist/doctor.js.map +1 -1
  37. package/dist/errors.d.ts +1 -1
  38. package/dist/errors.d.ts.map +1 -1
  39. package/dist/errors.js.map +1 -1
  40. package/dist/fonts/add.d.ts +41 -0
  41. package/dist/fonts/add.d.ts.map +1 -0
  42. package/dist/fonts/add.js +259 -0
  43. package/dist/fonts/add.js.map +1 -0
  44. package/dist/fonts/binary.d.ts +4 -0
  45. package/dist/fonts/binary.d.ts.map +1 -0
  46. package/dist/fonts/binary.js +21 -0
  47. package/dist/fonts/binary.js.map +1 -0
  48. package/dist/fonts/css.d.ts +4 -0
  49. package/dist/fonts/css.d.ts.map +1 -0
  50. package/dist/fonts/css.js +50 -0
  51. package/dist/fonts/css.js.map +1 -0
  52. package/dist/fonts/fontsource.d.ts +107 -0
  53. package/dist/fonts/fontsource.d.ts.map +1 -0
  54. package/dist/fonts/fontsource.js +565 -0
  55. package/dist/fonts/fontsource.js.map +1 -0
  56. package/dist/fonts/index.d.ts +11 -0
  57. package/dist/fonts/index.d.ts.map +1 -0
  58. package/dist/fonts/index.js +11 -0
  59. package/dist/fonts/index.js.map +1 -0
  60. package/dist/fonts/install.d.ts +24 -0
  61. package/dist/fonts/install.d.ts.map +1 -0
  62. package/dist/fonts/install.js +145 -0
  63. package/dist/fonts/install.js.map +1 -0
  64. package/dist/fonts/manifest.d.ts +21 -0
  65. package/dist/fonts/manifest.d.ts.map +1 -0
  66. package/dist/fonts/manifest.js +96 -0
  67. package/dist/fonts/manifest.js.map +1 -0
  68. package/dist/fonts/options.d.ts +9 -0
  69. package/dist/fonts/options.d.ts.map +1 -0
  70. package/dist/fonts/options.js +66 -0
  71. package/dist/fonts/options.js.map +1 -0
  72. package/dist/fonts/schema.d.ts +4 -0
  73. package/dist/fonts/schema.d.ts.map +1 -0
  74. package/dist/fonts/schema.js +316 -0
  75. package/dist/fonts/schema.js.map +1 -0
  76. package/dist/fonts/summary.d.ts +16 -0
  77. package/dist/fonts/summary.d.ts.map +1 -0
  78. package/dist/fonts/summary.js +22 -0
  79. package/dist/fonts/summary.js.map +1 -0
  80. package/dist/fonts/types.d.ts +56 -0
  81. package/dist/fonts/types.d.ts.map +1 -0
  82. package/dist/fonts/types.js +8 -0
  83. package/dist/fonts/types.js.map +1 -0
  84. package/dist/fonts/verify.d.ts +33 -0
  85. package/dist/fonts/verify.d.ts.map +1 -0
  86. package/dist/fonts/verify.js +200 -0
  87. package/dist/fonts/verify.js.map +1 -0
  88. package/dist/html/validate.d.ts.map +1 -1
  89. package/dist/html/validate.js +59 -0
  90. package/dist/html/validate.js.map +1 -1
  91. package/dist/index.d.ts +3 -3
  92. package/dist/index.d.ts.map +1 -1
  93. package/dist/index.js +1 -1
  94. package/dist/index.js.map +1 -1
  95. package/dist/inspect.d.ts +61 -0
  96. package/dist/inspect.d.ts.map +1 -0
  97. package/dist/inspect.js +325 -0
  98. package/dist/inspect.js.map +1 -0
  99. package/dist/lint.d.ts +63 -0
  100. package/dist/lint.d.ts.map +1 -0
  101. package/dist/lint.js +265 -0
  102. package/dist/lint.js.map +1 -0
  103. package/dist/policy.d.ts +69 -0
  104. package/dist/policy.d.ts.map +1 -0
  105. package/dist/policy.js +361 -0
  106. package/dist/policy.js.map +1 -0
  107. package/dist/preview/timeline-data.d.ts +17 -69
  108. package/dist/preview/timeline-data.d.ts.map +1 -1
  109. package/dist/preview/timeline-host.d.ts.map +1 -1
  110. package/dist/preview/timeline-host.js +70 -45
  111. package/dist/preview/timeline-host.js.map +1 -1
  112. package/dist/preview/timeline-main.d.ts.map +1 -1
  113. package/dist/preview/timeline-main.js +47 -4
  114. package/dist/preview/timeline-main.js.map +1 -1
  115. package/dist/project.d.ts +10 -3
  116. package/dist/project.d.ts.map +1 -1
  117. package/dist/project.js +20 -17
  118. package/dist/project.js.map +1 -1
  119. package/dist/renderer/ffmpeg.d.ts +42 -2
  120. package/dist/renderer/ffmpeg.d.ts.map +1 -1
  121. package/dist/renderer/ffmpeg.js +254 -22
  122. package/dist/renderer/ffmpeg.js.map +1 -1
  123. package/dist/renderer/index.js +1 -1
  124. package/dist/renderer/index.js.map +1 -1
  125. package/dist/renderer/keyframe-expressions.d.ts +39 -0
  126. package/dist/renderer/keyframe-expressions.d.ts.map +1 -0
  127. package/dist/renderer/keyframe-expressions.js +180 -0
  128. package/dist/renderer/keyframe-expressions.js.map +1 -0
  129. package/dist/renderer/timeline.d.ts +2 -0
  130. package/dist/renderer/timeline.d.ts.map +1 -1
  131. package/dist/renderer/timeline.js +89 -116
  132. package/dist/renderer/timeline.js.map +1 -1
  133. package/dist/runtime/host.d.ts.map +1 -1
  134. package/dist/runtime/host.js +4 -0
  135. package/dist/runtime/host.js.map +1 -1
  136. package/dist/scaffold.d.ts +3 -0
  137. package/dist/scaffold.d.ts.map +1 -1
  138. package/dist/scaffold.js +48 -320
  139. package/dist/scaffold.js.map +1 -1
  140. package/dist/server/composition-html.d.ts +2 -0
  141. package/dist/server/composition-html.d.ts.map +1 -1
  142. package/dist/server/composition-html.js +7 -0
  143. package/dist/server/composition-html.js.map +1 -1
  144. package/dist/server/server.d.ts.map +1 -1
  145. package/dist/server/server.js +31 -1
  146. package/dist/server/server.js.map +1 -1
  147. package/dist/server/timeline-preview-data.d.ts.map +1 -1
  148. package/dist/server/timeline-preview-data.js +12 -34
  149. package/dist/server/timeline-preview-data.js.map +1 -1
  150. package/dist/server/timeline-preview-html.d.ts.map +1 -1
  151. package/dist/server/timeline-preview-html.js +18 -2
  152. package/dist/server/timeline-preview-html.js.map +1 -1
  153. package/dist/templates.d.ts +16 -0
  154. package/dist/templates.d.ts.map +1 -0
  155. package/dist/templates.js +186 -0
  156. package/dist/templates.js.map +1 -0
  157. package/dist/timeline/easing.d.ts +36 -0
  158. package/dist/timeline/easing.d.ts.map +1 -0
  159. package/dist/timeline/easing.js +225 -0
  160. package/dist/timeline/easing.js.map +1 -0
  161. package/dist/timeline/keyframes.d.ts +23 -0
  162. package/dist/timeline/keyframes.d.ts.map +1 -0
  163. package/dist/timeline/keyframes.js +95 -0
  164. package/dist/timeline/keyframes.js.map +1 -0
  165. package/dist/timeline/plan.d.ts +6 -12
  166. package/dist/timeline/plan.d.ts.map +1 -1
  167. package/dist/timeline/plan.js +17 -30
  168. package/dist/timeline/plan.js.map +1 -1
  169. package/dist/timeline/resolve.d.ts +12 -0
  170. package/dist/timeline/resolve.d.ts.map +1 -1
  171. package/dist/timeline/resolve.js +105 -9
  172. package/dist/timeline/resolve.js.map +1 -1
  173. package/dist/timeline/scene.d.ts +126 -0
  174. package/dist/timeline/scene.d.ts.map +1 -0
  175. package/dist/timeline/scene.js +109 -0
  176. package/dist/timeline/scene.js.map +1 -0
  177. package/dist/timeline/schema.d.ts +31 -0
  178. package/dist/timeline/schema.d.ts.map +1 -1
  179. package/dist/timeline/schema.js +240 -9
  180. package/dist/timeline/schema.js.map +1 -1
  181. package/dist/timeline/transitions.d.ts +21 -0
  182. package/dist/timeline/transitions.d.ts.map +1 -0
  183. package/dist/timeline/transitions.js +23 -0
  184. package/dist/timeline/transitions.js.map +1 -0
  185. package/dist/version.d.ts +1 -1
  186. package/dist/version.js +1 -1
  187. package/docs/adr/0007-make-html-documents-the-composition-unit.md +1 -1
  188. package/docs/adr/0010-make-timelines-the-render-unit.md +3 -2
  189. package/docs/adr/0014-compose-timeline-clips-with-css-blend-modes.md +2 -1
  190. package/docs/adr/0015-inherit-composition-format-from-the-project.md +2 -2
  191. package/docs/adr/0016-author-compositions-as-html-fragments.md +1 -1
  192. package/docs/adr/0018-animate-timeline-clips-with-keyframes.md +35 -0
  193. package/docs/adr/0019-make-same-track-overlap-the-transition-window.md +36 -0
  194. package/docs/adr/0020-crossfade-audio-clips-on-audio-tracks.md +27 -0
  195. package/docs/adr/0021-project-timeline-scenes-once.md +32 -0
  196. package/docs/adr/0022-gate-heavy-operations-with-a-workspace-resource-policy.md +35 -0
  197. package/docs/adr/0023-pin-fonts-as-project-assets.md +98 -0
  198. package/docs/migration-0.4.md +2 -2
  199. package/docs/preview-testing.md +19 -6
  200. package/docs/{v0.1-design.md → v0.x-design.md} +55 -30
  201. package/examples/blend/README.md +3 -3
  202. package/examples/demo/README.md +2 -2
  203. package/examples/intro/README.md +71 -0
  204. package/examples/intro/assets/fonts/figtree/LICENSE.txt +93 -0
  205. package/examples/intro/assets/fonts/figtree/figtree-latin-wght-normal.woff2 +0 -0
  206. package/examples/intro/assets/fonts/instrument-serif/LICENSE.txt +93 -0
  207. package/examples/intro/assets/fonts/instrument-serif/instrument-serif-latin-400-italic.woff2 +0 -0
  208. package/examples/intro/assets/fonts/space-mono/LICENSE.txt +93 -0
  209. package/examples/intro/assets/fonts/space-mono/space-mono-latin-400-normal.woff2 +0 -0
  210. package/examples/intro/assets/fonts/space-mono/space-mono-latin-700-normal.woff2 +0 -0
  211. package/examples/intro/assets/grain.png +0 -0
  212. package/examples/intro/assets/score.ogg +0 -0
  213. package/examples/intro/compositions/brand.html +412 -0
  214. package/examples/intro/compositions/chrome.controller.js +63 -0
  215. package/examples/intro/compositions/chrome.html +256 -0
  216. package/examples/intro/compositions/lattice.controller.js +148 -0
  217. package/examples/intro/compositions/lattice.html +308 -0
  218. package/examples/intro/compositions/opening.html +504 -0
  219. package/examples/intro/compositions/studio.html +930 -0
  220. package/examples/intro/compositions/theme.css +218 -0
  221. package/examples/intro/fy.config.js +13 -0
  222. package/examples/intro/fy.fonts.json +100 -0
  223. package/examples/intro/scripts/make-grain.mjs +79 -0
  224. package/examples/intro/scripts/make-score.mjs +702 -0
  225. package/examples/intro/timelines/intro.timeline.json +60 -0
  226. package/examples/keyframes/README.md +76 -0
  227. package/examples/keyframes/compositions/backdrop.html +60 -0
  228. package/examples/keyframes/compositions/card.html +99 -0
  229. package/examples/keyframes/fy.config.js +10 -0
  230. package/examples/keyframes/timelines/keyframes.timeline.json +60 -0
  231. package/examples/nested/README.md +112 -0
  232. package/examples/nested/assets/pulse.wav +0 -0
  233. package/examples/nested/compositions/backdrop.html +84 -0
  234. package/examples/nested/compositions/card.html +275 -0
  235. package/examples/nested/compositions/overlay.html +307 -0
  236. package/examples/nested/fy.config.js +13 -0
  237. package/examples/nested/scripts/make-pulse.sh +20 -0
  238. package/examples/nested/timelines/chapter-a.timeline.json +39 -0
  239. package/examples/nested/timelines/chapter-b.timeline.json +39 -0
  240. package/examples/nested/timelines/nested-reel.timeline.json +52 -0
  241. package/examples/static/README.md +2 -2
  242. package/examples/transitions/README.md +128 -0
  243. package/examples/transitions/assets/alpha.wav +0 -0
  244. package/examples/transitions/assets/beta.wav +0 -0
  245. package/examples/transitions/assets/gamma.wav +0 -0
  246. package/examples/transitions/compositions/alpha.html +56 -0
  247. package/examples/transitions/compositions/beta.html +56 -0
  248. package/examples/transitions/compositions/gamma.html +56 -0
  249. package/examples/transitions/fy.config.js +12 -0
  250. package/examples/transitions/timelines/audio-crossfade.timeline.json +44 -0
  251. package/examples/transitions/timelines/transitions.timeline.json +31 -0
  252. package/examples/video/README.md +4 -4
  253. package/examples/webgl/README.md +1 -1
  254. package/package.json +12 -5
  255. package/scaffolds/init/AGENTS.md +131 -0
  256. package/scaffolds/init/README.md +56 -0
  257. package/scaffolds/init/compositions/hello.html +79 -0
  258. package/scaffolds/init/fy.config.js +9 -0
  259. package/scaffolds/init/gitignore.template +1 -0
  260. package/scaffolds/init/package.json +11 -0
  261. package/scaffolds/init/props/hello.json +4 -0
  262. package/scaffolds/init/props/main.json +6 -0
  263. package/scaffolds/init/timelines/main.timeline.json +20 -0
  264. package/scaffolds/local-models/LOCAL-MODELS.md +77 -0
  265. package/scaffolds/local-models/fy.policy.json +5 -0
  266. package/scaffolds/local-models/opencode/opencode.jsonc +17 -0
  267. package/scaffolds/local-models/pi/APPEND_SYSTEM.md +21 -0
  268. package/scaffolds/local-models/pi/sandbox.json +15 -0
  269. package/scaffolds/local-models/pi/settings.json +15 -0
  270. package/skills/frameyard/README.md +1 -0
  271. package/skills/frameyard/SKILL.md +59 -61
  272. package/skills/frameyard/references/audio-assets.md +32 -3
  273. package/skills/frameyard/references/common-mistakes.md +53 -2
  274. package/skills/frameyard/references/deterministic-authoring.md +10 -3
  275. package/skills/frameyard/references/diagnostics.md +31 -1
  276. package/skills/frameyard/references/failure-recovery.md +32 -6
  277. package/skills/frameyard/references/fonts.md +132 -0
  278. package/skills/frameyard/references/json-results.md +119 -6
  279. package/skills/frameyard/references/quick-reference.md +222 -0
  280. package/skills/frameyard/references/timelines.md +97 -7
  281. /package/examples/blend/{video.config.js → fy.config.js} +0 -0
  282. /package/examples/demo/{video.config.js → fy.config.js} +0 -0
  283. /package/examples/static/{video.config.js → fy.config.js} +0 -0
  284. /package/examples/video/{video.config.js → fy.config.js} +0 -0
  285. /package/examples/webgl/{video.config.js → fy.config.js} +0 -0
@@ -0,0 +1,256 @@
1
+ <link rel="stylesheet" href="./theme.css" />
2
+ <style>
3
+ @keyframes hud-life {
4
+ 0% {
5
+ opacity: 0;
6
+ }
7
+ 4%,
8
+ 93% {
9
+ opacity: 1;
10
+ }
11
+ 100% {
12
+ opacity: 0;
13
+ }
14
+ }
15
+ .chrome {
16
+ position: relative;
17
+ width: 100%;
18
+ height: 100%;
19
+ overflow: hidden;
20
+ pointer-events: none;
21
+ }
22
+ .texture {
23
+ position: absolute;
24
+ inset: 0;
25
+ background-image: url("../assets/grain.png");
26
+ background-size: 256px 256px;
27
+ mix-blend-mode: soft-light;
28
+ opacity: 0.17;
29
+ }
30
+ .vignette {
31
+ position: absolute;
32
+ inset: 0;
33
+ background:
34
+ radial-gradient(ellipse at 50% 46%, transparent 46%, rgba(0, 0, 0, 0.34) 100%),
35
+ linear-gradient(180deg, rgba(0, 0, 0, 0.22), transparent 14%, transparent 86%, rgba(0, 0, 0, 0.28));
36
+ }
37
+ .hud {
38
+ position: absolute;
39
+ inset: 0;
40
+ animation: hud-life 1s linear both;
41
+ }
42
+ .mark {
43
+ position: absolute;
44
+ left: 96px;
45
+ top: 50px;
46
+ display: flex;
47
+ align-items: center;
48
+ gap: 18px;
49
+ color: var(--paper);
50
+ text-shadow: 0 1px 10px rgba(0, 0, 0, 0.75);
51
+ }
52
+ .mark-glyph {
53
+ display: grid;
54
+ grid-template-columns: repeat(2, 10px);
55
+ grid-template-rows: repeat(2, 10px);
56
+ gap: 2px;
57
+ }
58
+ .mark-glyph i {
59
+ display: block;
60
+ border: 1px solid rgba(242, 245, 238, 0.54);
61
+ }
62
+ .mark-glyph i:nth-child(2) {
63
+ background: var(--accent);
64
+ border-color: var(--accent);
65
+ box-shadow: 0 0 12px color-mix(in srgb, var(--accent) 60%, transparent);
66
+ }
67
+ .mark-word {
68
+ font-size: 16px;
69
+ font-weight: 600;
70
+ letter-spacing: 0.26em;
71
+ text-transform: uppercase;
72
+ }
73
+ .mark-slash {
74
+ color: var(--muted);
75
+ font-size: 15px;
76
+ letter-spacing: 0.16em;
77
+ }
78
+ .top-meta {
79
+ position: absolute;
80
+ right: 96px;
81
+ top: 46px;
82
+ text-align: right;
83
+ font-size: 14px;
84
+ line-height: 1.8;
85
+ letter-spacing: 0.14em;
86
+ color: var(--muted);
87
+ text-transform: uppercase;
88
+ text-shadow: 0 1px 10px rgba(0, 0, 0, 0.85);
89
+ }
90
+ .top-meta b {
91
+ color: var(--muted-strong);
92
+ font-weight: 500;
93
+ }
94
+ .bracket {
95
+ position: absolute;
96
+ width: 26px;
97
+ height: 26px;
98
+ border-color: rgba(242, 245, 238, 0.24);
99
+ border-style: solid;
100
+ }
101
+ .bracket.tl {
102
+ left: 38px;
103
+ top: 38px;
104
+ border-width: 1px 0 0 1px;
105
+ }
106
+ .bracket.tr {
107
+ right: 38px;
108
+ top: 38px;
109
+ border-width: 1px 1px 0 0;
110
+ }
111
+ .bracket.bl {
112
+ left: 38px;
113
+ bottom: 38px;
114
+ border-width: 0 0 1px 1px;
115
+ }
116
+ .bracket.br {
117
+ right: 38px;
118
+ bottom: 38px;
119
+ border-width: 0 1px 1px 0;
120
+ }
121
+ .foot {
122
+ position: absolute;
123
+ left: 96px;
124
+ right: 96px;
125
+ bottom: 52px;
126
+ display: flex;
127
+ align-items: flex-end;
128
+ justify-content: space-between;
129
+ gap: 36px;
130
+ font-size: 14px;
131
+ letter-spacing: 0.12em;
132
+ color: var(--muted);
133
+ text-transform: uppercase;
134
+ text-shadow: 0 1px 10px rgba(0, 0, 0, 0.85);
135
+ }
136
+ .foot b {
137
+ color: var(--paper);
138
+ font-weight: 500;
139
+ }
140
+ .frame-read {
141
+ display: flex;
142
+ align-items: baseline;
143
+ gap: 12px;
144
+ }
145
+ .frame-read strong {
146
+ color: var(--accent);
147
+ font-size: 20px;
148
+ font-weight: 500;
149
+ letter-spacing: 0.06em;
150
+ }
151
+ .chapter-stack {
152
+ position: absolute;
153
+ left: 50%;
154
+ bottom: 52px;
155
+ width: 360px;
156
+ height: 16px;
157
+ margin-left: -180px;
158
+ text-align: center;
159
+ font-size: 13px;
160
+ letter-spacing: 0.18em;
161
+ text-transform: uppercase;
162
+ color: var(--muted-strong);
163
+ }
164
+ .chapter {
165
+ position: absolute;
166
+ inset: 0;
167
+ display: flex;
168
+ align-items: center;
169
+ justify-content: center;
170
+ gap: 12px;
171
+ white-space: nowrap;
172
+ text-shadow: 0 1px 10px rgba(0, 0, 0, 0.85);
173
+ }
174
+ .chapter::before {
175
+ content: "";
176
+ width: 4px;
177
+ height: 4px;
178
+ background: var(--accent);
179
+ box-shadow: 0 0 10px var(--accent);
180
+ }
181
+ .progress {
182
+ position: absolute;
183
+ left: 96px;
184
+ right: 96px;
185
+ bottom: 32px;
186
+ height: 1px;
187
+ background: rgba(238, 245, 235, 0.12);
188
+ }
189
+ .progress .fill {
190
+ width: calc(var(--fy-progress) * 100%);
191
+ height: 100%;
192
+ background: var(--accent);
193
+ box-shadow: 0 0 12px var(--accent);
194
+ }
195
+ .progress .ticks {
196
+ position: absolute;
197
+ inset: -3px 0 auto;
198
+ height: 7px;
199
+ background-image: linear-gradient(90deg, rgba(238, 245, 235, 0.28) 0 1px, transparent 1px);
200
+ background-size: 10% 100%;
201
+ opacity: 0.5;
202
+ }
203
+ .right-meta {
204
+ display: flex;
205
+ align-items: baseline;
206
+ gap: 22px;
207
+ }
208
+ </style>
209
+ <fy-composition duration-in-frames="1200">
210
+ <div class="chrome">
211
+ <div class="texture"></div>
212
+ <div class="vignette"></div>
213
+ <div class="hud" data-fy-anim-start="0" data-fy-anim-duration="1200" data-fy-anim-easing="linear">
214
+ <div class="bracket tl"></div>
215
+ <div class="bracket tr"></div>
216
+ <div class="bracket bl"></div>
217
+ <div class="bracket br"></div>
218
+
219
+ <div class="mark">
220
+ <span class="mark-glyph"><i></i><i></i><i></i><i></i></span>
221
+ <span class="mark-word mono">Frameyard</span>
222
+ <span class="mark-slash mono">/</span>
223
+ <span class="mark-slash mono">intro film</span>
224
+ </div>
225
+ <div class="top-meta mono">
226
+ <div><b>1920 × 1080</b> / 60 FPS</div>
227
+ <div>20.00 S / 1200 FRAMES</div>
228
+ </div>
229
+
230
+ <div class="foot mono">
231
+ <div class="frame-read">
232
+ <span>frame</span>
233
+ <strong id="frame">0000</strong>
234
+ <span>/ <span id="total">1199</span></span>
235
+ <span class="dim">·</span>
236
+ <span id="time">00.00 S</span>
237
+ </div>
238
+ <div class="chapter-stack">
239
+ <span class="chapter" id="chapter">01 / author</span>
240
+ </div>
241
+ <div class="right-meta">
242
+ <span>local-first</span>
243
+ <span>html + json</span>
244
+ <span><b>frame-exact</b></span>
245
+ </div>
246
+ </div>
247
+
248
+ <div class="progress">
249
+ <div class="ticks"></div>
250
+ <div class="fill"></div>
251
+ </div>
252
+ </div>
253
+ </div>
254
+ </fy-composition>
255
+
256
+ <script type="text/fy-controller" src="./chrome.controller.js"></script>
@@ -0,0 +1,148 @@
1
+ const COLS = 7;
2
+ const ROWS = 5;
3
+ const LAYERS = 14;
4
+ const SPACING_X = 1.95;
5
+ const SPACING_Y = 1.15;
6
+ const SPACING_Z = 3.3;
7
+ const SPAN = SPACING_Z * LAYERS;
8
+ const FOCAL = 1480;
9
+ const CAMERA = 7.2;
10
+
11
+ let canvas;
12
+ let ctx;
13
+ let fps = 60;
14
+ let width = 1920;
15
+ let height = 1080;
16
+
17
+ const nodes = [];
18
+ const paper = [242, 245, 238];
19
+ const accent = [184, 243, 107];
20
+
21
+ function smoothstep(edge0, edge1, value) {
22
+ const t = Math.min(1, Math.max(0, (value - edge0) / (edge1 - edge0)));
23
+ return t * t * (3 - 2 * t);
24
+ }
25
+
26
+ function rgba(color, alpha) {
27
+ return `rgba(${color[0]}, ${color[1]}, ${color[2]}, ${alpha.toFixed(3)})`;
28
+ }
29
+
30
+ function clearNodes() {
31
+ nodes.length = 0;
32
+ }
33
+
34
+ function draw(frame) {
35
+ if (!ctx) {
36
+ return;
37
+ }
38
+
39
+ const time = frame / fps;
40
+ const travel = time * 2.05;
41
+ const yaw = -0.075 + Math.sin(time * 0.29) * 0.075;
42
+ const pitch = Math.sin(time * 0.22 + 0.8) * 0.032;
43
+ const cosYaw = Math.cos(yaw);
44
+ const sinYaw = Math.sin(yaw);
45
+ const cosPitch = Math.cos(pitch);
46
+ const sinPitch = Math.sin(pitch);
47
+ const centerX = 1214;
48
+ const centerY = 524;
49
+
50
+ ctx.clearRect(0, 0, width, height);
51
+ clearNodes();
52
+
53
+ for (let k = 0; k < LAYERS; k += 1) {
54
+ const depth = (((k * SPACING_Z - travel) % SPAN) + SPAN) % SPAN;
55
+
56
+ for (let i = 0; i < COLS; i += 1) {
57
+ const worldX = (i - (COLS - 1) / 2) * SPACING_X;
58
+
59
+ for (let j = 0; j < ROWS; j += 1) {
60
+ const worldY = (j - (ROWS - 1) / 2) * SPACING_Y;
61
+ const x1 = worldX * cosYaw - depth * sinYaw;
62
+ const z1 = worldX * sinYaw + depth * cosYaw;
63
+ const y1 = worldY * cosPitch - z1 * sinPitch;
64
+ const z2 = worldY * sinPitch + z1 * cosPitch;
65
+ const perspective = FOCAL / (z2 + CAMERA);
66
+
67
+ if (perspective <= 0) {
68
+ continue;
69
+ }
70
+
71
+ const halfW = 0.9 * perspective;
72
+ const halfH = 0.58 * perspective;
73
+ const x = centerX + x1 * perspective;
74
+ const y = centerY + y1 * perspective;
75
+
76
+ if (x + halfW < -80 || x - halfW > width + 80 || y + halfH < -80 || y - halfH > height + 80) {
77
+ continue;
78
+ }
79
+
80
+ const reveal = smoothstep(0, 2.1, depth);
81
+ const near = 1 - depth / SPAN;
82
+ const alpha = reveal * (0.06 + 0.5 * near * near);
83
+ const signal = Math.sin(time * 1.12 + i * 0.92 + j * 1.37 + k * 0.47 - 0.6);
84
+ const selected = signal > 0.86 || (i * 11 + j * 7 + k * 5) % 31 === 0;
85
+
86
+ nodes.push({
87
+ x,
88
+ y,
89
+ halfW,
90
+ halfH,
91
+ z: z2,
92
+ alpha,
93
+ selected,
94
+ lineWidth: Math.max(0.55, Math.min(1.9, 0.35 + perspective * 0.01)),
95
+ });
96
+ }
97
+ }
98
+ }
99
+
100
+ nodes.sort((a, b) => b.z - a.z);
101
+
102
+ for (const node of nodes) {
103
+ const color = node.selected ? accent : paper;
104
+ const strength = node.selected ? Math.min(1, node.alpha * 1.9 + 0.16) : node.alpha;
105
+ ctx.strokeStyle = rgba(color, strength);
106
+ ctx.lineWidth = node.selected ? node.lineWidth * 1.12 : node.lineWidth;
107
+
108
+ if (node.selected && node.z < 18) {
109
+ ctx.shadowColor = rgba(accent, 0.7);
110
+ ctx.shadowBlur = Math.min(22, 8 + node.halfW * 0.08);
111
+ }
112
+
113
+ ctx.strokeRect(node.x - node.halfW, node.y - node.halfH, node.halfW * 2, node.halfH * 2);
114
+ ctx.shadowBlur = 0;
115
+
116
+ if (node.selected && node.halfW > 38) {
117
+ ctx.fillStyle = rgba(accent, Math.min(0.9, node.alpha * 2.4));
118
+ ctx.fillRect(node.x - 1.5, node.y - 1.5, 3, 3);
119
+ }
120
+ }
121
+ }
122
+
123
+ export default {
124
+ mount({ root }) {
125
+ canvas = root.querySelector("#lattice");
126
+ if (!(canvas instanceof HTMLCanvasElement)) {
127
+ throw new Error("lattice composition requires a #lattice canvas");
128
+ }
129
+ ctx = canvas.getContext("2d", { alpha: true });
130
+ fps = Number(root.getAttribute("fps") ?? 60);
131
+ width = canvas.width;
132
+ height = canvas.height;
133
+ draw(0);
134
+ },
135
+
136
+ update({ frame }) {
137
+ draw(frame);
138
+ },
139
+
140
+ dispose() {
141
+ clearNodes();
142
+ canvas = undefined;
143
+ ctx = undefined;
144
+ fps = 60;
145
+ width = 1920;
146
+ height = 1080;
147
+ },
148
+ };
@@ -0,0 +1,308 @@
1
+ <link rel="stylesheet" href="./theme.css" />
2
+ <script type="application/json" data-fy="props">
3
+ {
4
+ "eyebrow": "RENDER / 03",
5
+ "lineOne": "THE FRAME",
6
+ "lineTwo": "is the truth.",
7
+ "note": "One integer clock. No wall time. The same input yields the same pixels.",
8
+ "badge": "deterministic by construction",
9
+ "accent": "#b8f36b"
10
+ }
11
+ </script>
12
+ <style>
13
+ @keyframes stage-in {
14
+ from {
15
+ opacity: 0;
16
+ transform: scale(1.035);
17
+ }
18
+ to {
19
+ opacity: 1;
20
+ transform: scale(1);
21
+ }
22
+ }
23
+ @keyframes spin {
24
+ to {
25
+ transform: rotate(360deg);
26
+ }
27
+ }
28
+ @keyframes spin-reverse {
29
+ to {
30
+ transform: rotate(-360deg);
31
+ }
32
+ }
33
+ .scene {
34
+ background: radial-gradient(circle at 63% 48%, rgba(184, 243, 107, 0.055), transparent 32%), #06080b;
35
+ }
36
+ .stage {
37
+ position: absolute;
38
+ inset: 0;
39
+ animation: stage-in 1s cubic-bezier(0.16, 1, 0.3, 1) both;
40
+ }
41
+ .halo {
42
+ position: absolute;
43
+ left: 744px;
44
+ top: 54px;
45
+ width: 940px;
46
+ height: 940px;
47
+ border-radius: 50%;
48
+ background: radial-gradient(circle, rgba(184, 243, 107, 0.14) 0%, rgba(184, 243, 107, 0.035) 42%, transparent 68%);
49
+ filter: blur(16px);
50
+ animation: glow-pulse 7s ease-in-out infinite;
51
+ }
52
+ .orbit {
53
+ position: absolute;
54
+ left: 874px;
55
+ top: 184px;
56
+ width: 680px;
57
+ height: 680px;
58
+ border: 1px dashed rgba(184, 243, 107, 0.18);
59
+ border-radius: 50%;
60
+ animation: spin 72s linear infinite;
61
+ }
62
+ .orbit::before,
63
+ .orbit::after {
64
+ content: "";
65
+ position: absolute;
66
+ width: 5px;
67
+ height: 5px;
68
+ background: var(--accent);
69
+ box-shadow: 0 0 12px var(--accent);
70
+ }
71
+ .orbit::before {
72
+ left: 50%;
73
+ top: -3px;
74
+ }
75
+ .orbit::after {
76
+ left: 50%;
77
+ bottom: -3px;
78
+ }
79
+ .orbit-outer {
80
+ position: absolute;
81
+ left: 764px;
82
+ top: 74px;
83
+ width: 900px;
84
+ height: 900px;
85
+ border: 1px solid rgba(242, 245, 238, 0.05);
86
+ border-radius: 50%;
87
+ animation: spin-reverse 110s linear infinite;
88
+ }
89
+ #lattice {
90
+ position: absolute;
91
+ inset: 0;
92
+ width: 100%;
93
+ height: 100%;
94
+ }
95
+ .scrim {
96
+ position: absolute;
97
+ inset: 0;
98
+ background:
99
+ radial-gradient(ellipse at 88% 4%, rgba(6, 8, 11, 0.45), transparent 32%),
100
+ linear-gradient(
101
+ 90deg,
102
+ #06080b 0%,
103
+ #06080b 30%,
104
+ rgba(6, 8, 11, 0.97) 38%,
105
+ rgba(6, 8, 11, 0.82) 48%,
106
+ rgba(6, 8, 11, 0.42) 58%,
107
+ transparent 70%
108
+ ),
109
+ linear-gradient(180deg, rgba(6, 8, 11, 0.55), transparent 18%, transparent 74%, rgba(6, 8, 11, 0.86));
110
+ }
111
+ .content {
112
+ position: absolute;
113
+ inset: 0;
114
+ animation: scene-out 1s linear both;
115
+ }
116
+ .hero {
117
+ position: absolute;
118
+ left: 148px;
119
+ top: 246px;
120
+ width: 820px;
121
+ }
122
+ .eyebrow {
123
+ display: flex;
124
+ align-items: center;
125
+ gap: 18px;
126
+ margin-bottom: 52px;
127
+ color: var(--accent);
128
+ font-size: 19px;
129
+ font-weight: 600;
130
+ letter-spacing: 0.22em;
131
+ text-transform: uppercase;
132
+ animation: rise-small 1s ease-out both;
133
+ }
134
+ .eyebrow::before {
135
+ content: "";
136
+ width: 36px;
137
+ height: 1px;
138
+ background: var(--accent);
139
+ box-shadow: 0 0 12px var(--accent);
140
+ }
141
+ .headline {
142
+ margin: 0;
143
+ color: var(--paper);
144
+ }
145
+ .line {
146
+ display: block;
147
+ overflow: hidden;
148
+ padding-bottom: 0.04em;
149
+ }
150
+ .line-one {
151
+ font-size: 108px;
152
+ line-height: 0.92;
153
+ font-weight: 660;
154
+ letter-spacing: -0.055em;
155
+ }
156
+ .line-one span {
157
+ display: inline-block;
158
+ animation: line-up 1s cubic-bezier(0.16, 1, 0.3, 1) both;
159
+ }
160
+ .line-two {
161
+ margin-top: 4px;
162
+ font-size: 174px;
163
+ line-height: 0.86;
164
+ letter-spacing: -0.02em;
165
+ }
166
+ .line-two span {
167
+ display: inline-block;
168
+ animation: line-up 1s cubic-bezier(0.16, 1, 0.3, 1) both;
169
+ }
170
+ .note {
171
+ margin: 46px 0 0;
172
+ max-width: 680px;
173
+ color: #9ca8a1;
174
+ font-size: 31px;
175
+ line-height: 1.5;
176
+ letter-spacing: -0.01em;
177
+ animation: rise-in 1s ease-out both;
178
+ }
179
+ .badge {
180
+ display: inline-flex;
181
+ align-items: center;
182
+ gap: 12px;
183
+ margin-top: 44px;
184
+ padding: 12px 16px;
185
+ border: 1px solid rgba(184, 243, 107, 0.24);
186
+ border-radius: 2px;
187
+ color: var(--accent);
188
+ font-size: 14px;
189
+ letter-spacing: 0.14em;
190
+ text-transform: uppercase;
191
+ animation: rise-small 1s ease-out both;
192
+ }
193
+ .badge::before {
194
+ content: "";
195
+ width: 6px;
196
+ height: 6px;
197
+ background: var(--accent);
198
+ box-shadow: 0 0 10px var(--accent);
199
+ }
200
+ .spec {
201
+ position: absolute;
202
+ left: 148px;
203
+ right: 148px;
204
+ bottom: 142px;
205
+ display: flex;
206
+ align-items: flex-end;
207
+ justify-content: space-between;
208
+ gap: 48px;
209
+ color: var(--muted-strong);
210
+ font-size: 15px;
211
+ letter-spacing: 0.14em;
212
+ text-transform: uppercase;
213
+ text-shadow: 0 1px 12px rgba(0, 0, 0, 0.9);
214
+ animation: rise-small 1s ease-out both;
215
+ }
216
+ .spec::before {
217
+ content: "";
218
+ position: absolute;
219
+ left: 0;
220
+ right: 0;
221
+ top: -24px;
222
+ height: 1px;
223
+ background: linear-gradient(90deg, rgba(242, 245, 238, 0.24), rgba(242, 245, 238, 0.06) 68%, transparent);
224
+ }
225
+ .spec b {
226
+ color: var(--paper);
227
+ font-weight: 500;
228
+ }
229
+ .spec-tags {
230
+ display: flex;
231
+ align-items: center;
232
+ gap: 30px;
233
+ }
234
+ .spec-tags span {
235
+ display: flex;
236
+ align-items: center;
237
+ gap: 10px;
238
+ }
239
+ .spec-tags span::before {
240
+ content: "";
241
+ width: 5px;
242
+ height: 5px;
243
+ background: var(--accent);
244
+ }
245
+ </style>
246
+ <fy-composition duration-in-frames="330">
247
+ <div class="scene">
248
+ <div
249
+ class="stage"
250
+ data-fy-anim-start="0"
251
+ data-fy-anim-duration="80"
252
+ data-fy-anim-easing="cubic-bezier(0.16, 1, 0.3, 1)"
253
+ >
254
+ <div class="halo"></div>
255
+ <div class="orbit-outer"></div>
256
+ <div class="orbit"></div>
257
+ <canvas id="lattice" width="1920" height="1080"></canvas>
258
+ </div>
259
+ <div class="scrim"></div>
260
+ <div class="content" data-fy-anim-start="288" data-fy-anim-duration="30" data-fy-anim-easing="linear">
261
+ <div class="hero">
262
+ <div class="eyebrow mono" data-fy-anim-start="28" data-fy-anim-duration="44" data-fy-anim-easing="ease-out">
263
+ <span data-fy-text="eyebrow"></span>
264
+ </div>
265
+ <h1 class="headline">
266
+ <span class="line line-one">
267
+ <span
268
+ data-fy-text="lineOne"
269
+ data-fy-anim-start="40"
270
+ data-fy-anim-duration="64"
271
+ data-fy-anim-easing="cubic-bezier(0.16, 1, 0.3, 1)"
272
+ ></span>
273
+ </span>
274
+ <span class="line line-two">
275
+ <span
276
+ class="serif accent"
277
+ data-fy-text="lineTwo"
278
+ data-fy-anim-start="60"
279
+ data-fy-anim-duration="76"
280
+ data-fy-anim-easing="cubic-bezier(0.16, 1, 0.3, 1)"
281
+ ></span>
282
+ </span>
283
+ </h1>
284
+ <p
285
+ class="note"
286
+ data-fy-text="note"
287
+ data-fy-anim-start="92"
288
+ data-fy-anim-duration="56"
289
+ data-fy-anim-easing="ease-out"
290
+ ></p>
291
+ <div class="badge mono" data-fy-anim-start="120" data-fy-anim-duration="44" data-fy-anim-easing="ease-out">
292
+ <span data-fy-text="badge"></span>
293
+ </div>
294
+ </div>
295
+
296
+ <div class="spec mono" data-fy-anim-start="128" data-fy-anim-duration="44" data-fy-anim-easing="ease-out">
297
+ <div><b>f(frame) → pixels</b> &nbsp;·&nbsp; no drift, no guesswork</div>
298
+ <div class="spec-tags">
299
+ <span>Canvas 2D</span>
300
+ <span>Seek-safe</span>
301
+ <span>± 0 px</span>
302
+ </div>
303
+ </div>
304
+ </div>
305
+ </div>
306
+ </fy-composition>
307
+
308
+ <script type="text/fy-controller" src="./lattice.controller.js"></script>