@cardstack/boxel-cli 0.9.0 → 0.10.0-unstable.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 (995) hide show
  1. package/README.md +12 -0
  2. package/bundled-test-harness/assets/account-switch-failed-GCSVbJML.js +23 -0
  3. package/bundled-test-harness/assets/account-switch-failed-GsWni_Gk.css +18 -0
  4. package/bundled-test-harness/assets/account-switch-failed-test-CdZWNths.js +45 -0
  5. package/bundled-test-harness/assets/add-workspace-c7L50Gx8.js +141 -0
  6. package/bundled-test-harness/assets/add-workspace-test-BZxHa6Pe.js +72 -0
  7. package/bundled-test-harness/assets/advanced-color-picker-0pGF-3WG.js +504 -0
  8. package/bundled-test-harness/assets/advanced-color-picker-pbO4HWc_.js +2 -0
  9. package/bundled-test-harness/assets/ai-assistant-test-Dnm4o9vK.js +2211 -0
  10. package/bundled-test-harness/assets/ai-module-creation-test-C6253CMt.js +151 -0
  11. package/bundled-test-harness/assets/aibot-message-miKyGeKk.js +278 -0
  12. package/bundled-test-harness/assets/app-i8pGdQkw.js +22855 -0
  13. package/bundled-test-harness/assets/app-zC7o6PyQ.css +2718 -0
  14. package/bundled-test-harness/assets/audio-file-def-DHnydP0t.js +133 -0
  15. package/bundled-test-harness/assets/auth-container-BP2gSoo4.css +51 -0
  16. package/bundled-test-harness/assets/auth-container-DN94iVfX.js +24 -0
  17. package/bundled-test-harness/assets/avif-meta-extractor-Bo5zRX1V.js +162 -0
  18. package/bundled-test-harness/assets/base64-image-B0RYxzHT.js +2 -0
  19. package/bundled-test-harness/assets/base64-image-Bxjqdwew.js +262 -0
  20. package/bundled-test-harness/assets/big-integer-7RLj5_7H.js +89 -0
  21. package/bundled-test-harness/assets/big-integer-C9sJxKVy.js +2 -0
  22. package/bundled-test-harness/assets/binary-upload-dedupe-test-DZo3EaV0.js +308 -0
  23. package/bundled-test-harness/assets/boolean-CcKPYUph.js +94 -0
  24. package/bundled-test-harness/assets/boolean-DvsSmtM7.js +2 -0
  25. package/bundled-test-harness/assets/brand-functional-palette-KpVSY2Sa.js +137 -0
  26. package/bundled-test-harness/assets/brand-logo-DDiOec0t.js +295 -0
  27. package/bundled-test-harness/assets/broken-link-template-test-DAngUbFJ.js +192 -0
  28. package/bundled-test-harness/assets/bundled-base-C_lumba6.js +254 -0
  29. package/bundled-test-harness/assets/bundled-base-modules-test-CSF7Rl4Z.js +54 -0
  30. package/bundled-test-harness/assets/buried-stack-item-scroll-test-B0J_EP88.js +236 -0
  31. package/bundled-test-harness/assets/bxl-expression-test-Dej7WgI6.js +250 -0
  32. package/bundled-test-harness/assets/can-invoke-test-elyUcOKT.js +401 -0
  33. package/bundled-test-harness/assets/card-api-CjWXCkqh.js +8 -0
  34. package/bundled-test-harness/assets/card-api-Dzje0smT.js +4393 -0
  35. package/bundled-test-harness/assets/card-api-test-DA8d4Ooc.js +198 -0
  36. package/bundled-test-harness/assets/card-basics-test-mp1Odo-l.js +5364 -0
  37. package/bundled-test-harness/assets/card-chooser-paging-test-y8OfboYd.js +235 -0
  38. package/bundled-test-harness/assets/card-chooser-test-nmg2zkDj.js +393 -0
  39. package/bundled-test-harness/assets/card-collection-BXiO3i7R.js +118 -0
  40. package/bundled-test-harness/assets/card-context-search-results-Dbhn7lvW.js +30 -0
  41. package/bundled-test-harness/assets/card-context-search-results-test-DVXT-YC5.js +184 -0
  42. package/bundled-test-harness/assets/card-def-field-def-relationships-test-mX4rxrtO.js +562 -0
  43. package/bundled-test-harness/assets/card-delete-test-BxFsqFnX.js +466 -0
  44. package/bundled-test-harness/assets/card-error-C-7hjxu9.js +72 -0
  45. package/bundled-test-harness/assets/card-error-awaiting-index-test-BvfY2eaW.js +53 -0
  46. package/bundled-test-harness/assets/card-error-detail-nMwopDir.js +271 -0
  47. package/bundled-test-harness/assets/card-info-CTFxdohf.js +2 -0
  48. package/bundled-test-harness/assets/card-list-B9QrkJ97.js +2 -0
  49. package/bundled-test-harness/assets/card-list-XbwLCXZS.js +202 -0
  50. package/bundled-test-harness/assets/card-resource-JGLjQUy6.js +86 -0
  51. package/bundled-test-harness/assets/cards-grid-CWG41hZE.js +344 -0
  52. package/bundled-test-harness/assets/cards-grid-DSpX99p6.js +2 -0
  53. package/bundled-test-harness/assets/cards-grid-layout-Bht0LwZy.js +2 -0
  54. package/bundled-test-harness/assets/cards-grid-layout-CdpKlHUb.js +111 -0
  55. package/bundled-test-harness/assets/check-correctness-test-C4BrUeKA.js +554 -0
  56. package/bundled-test-harness/assets/clip-BEVvLrXx.js +2 -0
  57. package/bundled-test-harness/assets/clip-CoB714qX.js +92 -0
  58. package/bundled-test-harness/assets/code-ref-C2uguisI.js +2 -0
  59. package/bundled-test-harness/assets/code-ref-Cy7sV5a4.js +138 -0
  60. package/bundled-test-harness/assets/code-submode-availability-test-BsA_Ul01.js +198 -0
  61. package/bundled-test-harness/assets/code-submode-test-DkBhpEPB.js +1839 -0
  62. package/bundled-test-harness/assets/codeblocks-test-B1eZ2_JF.js +418 -0
  63. package/bundled-test-harness/assets/codemirror-editor-C6pxSm5y.js +2 -0
  64. package/bundled-test-harness/assets/codemirror-editor-DzM46Cd8.js +701 -0
  65. package/bundled-test-harness/assets/codemirror-embed-toolbar-test-BemP-ZdG.js +366 -0
  66. package/bundled-test-harness/assets/color-C-jGNFWZ.js +2 -0
  67. package/bundled-test-harness/assets/color-field-test-DPIRaBec.js +212 -0
  68. package/bundled-test-harness/assets/color-picker-field-Bv4IjS-d.js +139 -0
  69. package/bundled-test-harness/assets/color-picker-field-D1bYCQIg.js +2 -0
  70. package/bundled-test-harness/assets/color-wheel-picker-IIaCQplL.js +283 -0
  71. package/bundled-test-harness/assets/color-wheel-picker-Kw-UN3kV.js +2 -0
  72. package/bundled-test-harness/assets/color-zzCBneih.js +65 -0
  73. package/bundled-test-harness/assets/command-data-BMBtjT1E.js +95 -0
  74. package/bundled-test-harness/assets/command-data-BXC5Z0oA.js +2 -0
  75. package/bundled-test-harness/assets/compaction-test-CxUKnxlQ.js +150 -0
  76. package/bundled-test-harness/assets/components-Ct_agx0G.js +14294 -0
  77. package/bundled-test-harness/assets/computed-test-C-lpGQQ3.js +1320 -0
  78. package/bundled-test-harness/assets/container-BdPu8rvc.css +5565 -0
  79. package/bundled-test-harness/assets/container-Dj-5prh9.js +11288 -0
  80. package/bundled-test-harness/assets/contains-many-component-CAWqzX8W.js +2 -0
  81. package/bundled-test-harness/assets/contrast-checker-addon-BTLD7oMx.js +2 -0
  82. package/bundled-test-harness/assets/contrast-checker-addon-DBzizR04.js +71 -0
  83. package/bundled-test-harness/assets/convert-to-class-name-test-zZuKxYvF.js +17 -0
  84. package/bundled-test-harness/assets/coordinate-Bw8va6tO.js +48 -0
  85. package/bundled-test-harness/assets/copy-file-to-realm-test-DtCKPzqj.js +148 -0
  86. package/bundled-test-harness/assets/country-B_tWpLlO.js +199 -0
  87. package/bundled-test-harness/assets/create-file-modal-D-VPyEYp.js +570 -0
  88. package/bundled-test-harness/assets/create-listing-modal-test-hyLkQY0u.js +273 -0
  89. package/bundled-test-harness/assets/create-listing-pr-request-test-rjWFA_5S.js +117 -0
  90. package/bundled-test-harness/assets/create-spec-test-HKIxZGxK.js +256 -0
  91. package/bundled-test-harness/assets/create-specs-9-ELVyJP.js +215 -0
  92. package/bundled-test-harness/assets/create-submission-workflow-test-DNZrbRX8.js +132 -0
  93. package/bundled-test-harness/assets/credential-fields-test-xPEdA911.js +157 -0
  94. package/bundled-test-harness/assets/css-value-Du3HL39X.js +48 -0
  95. package/bundled-test-harness/assets/csv-file-def-EHRmilrp.js +277 -0
  96. package/bundled-test-harness/assets/currency-y6ZCHIqG.js +134 -0
  97. package/bundled-test-harness/assets/date-B27PrSFT.js +183 -0
  98. package/bundled-test-harness/assets/date-CiP5Eb9i.js +2 -0
  99. package/bundled-test-harness/assets/date-time-fields-test-Clf5vxRW.js +359 -0
  100. package/bundled-test-harness/assets/datetime-BEbR9YgU.js +201 -0
  101. package/bundled-test-harness/assets/datetime-DPnrt1wl.js +2 -0
  102. package/bundled-test-harness/assets/datetime-stamp-AuT7F7qn.js +2 -0
  103. package/bundled-test-harness/assets/datetime-stamp-CGwP2P3f.js +81 -0
  104. package/bundled-test-harness/assets/day-BFLxZ9bQ.js +2 -0
  105. package/bundled-test-harness/assets/day-CQoEkqgE.js +113 -0
  106. package/bundled-test-harness/assets/debug-message-test-CzgOpfcI.js +117 -0
  107. package/bundled-test-harness/assets/dist-B-fYvxfL.js +389 -0
  108. package/bundled-test-harness/assets/dist-C2IXvIKX.js +323 -0
  109. package/bundled-test-harness/assets/dist-CdNZu43l.js +1710 -0
  110. package/bundled-test-harness/assets/docx-file-def-BQMw460_.js +60 -0
  111. package/bundled-test-harness/assets/docx-meta-extractor-DBlmH_kV.js +60 -0
  112. package/bundled-test-harness/assets/docx-meta-extractor-Dw4gZ0ae.js +2 -0
  113. package/bundled-test-harness/assets/duration-B0SnEiw1.js +184 -0
  114. package/bundled-test-harness/assets/duration-DbjJTFeA.js +2 -0
  115. package/bundled-test-harness/assets/email-C-8cyWiQ.js +80 -0
  116. package/bundled-test-harness/assets/email-field-test-BOBmvQqx.js +75 -0
  117. package/bundled-test-harness/assets/email-xUgCst-p.js +2 -0
  118. package/bundled-test-harness/assets/enum-BWiCyJlu.js +167 -0
  119. package/bundled-test-harness/assets/enum-CzYjivhO.js +2 -0
  120. package/bundled-test-harness/assets/enum-field-test-Bea9EsQR.js +953 -0
  121. package/bundled-test-harness/assets/error-display-test-Cbl2D_0c.js +343 -0
  122. package/bundled-test-harness/assets/ethereum-address-Bw_J05bD.js +78 -0
  123. package/bundled-test-harness/assets/ethereum-address-C9wHMBt3.js +2 -0
  124. package/bundled-test-harness/assets/expiration-warning-BHcJEjZt.js +90 -0
  125. package/bundled-test-harness/assets/expiration-warning-_G8SKZd0.js +2 -0
  126. package/bundled-test-harness/assets/externals-CKblKBo1.js +4393 -0
  127. package/bundled-test-harness/assets/fallback-banner-BqCrvTDt.js +24 -0
  128. package/bundled-test-harness/assets/fallback-banner-live-delete-test-D_AYMvf6.js +207 -0
  129. package/bundled-test-harness/assets/fallback-banner-test-B8Ne9a5g.js +225 -0
  130. package/bundled-test-harness/assets/fallback-banner-test-C7oaSxd1.js +53 -0
  131. package/bundled-test-harness/assets/fallback-models-test-DY-PvU9k.js +161 -0
  132. package/bundled-test-harness/assets/field-component-Bj8YthPE.js +2 -0
  133. package/bundled-test-harness/assets/field-configuration-test-B5LztQ47.js +548 -0
  134. package/bundled-test-harness/assets/field-edit-Cx_haspD.js +36 -0
  135. package/bundled-test-harness/assets/field-edit-DQZWUz3F.js +2 -0
  136. package/bundled-test-harness/assets/field-markdown-domain-test-DnqK3CrE.js +616 -0
  137. package/bundled-test-harness/assets/field-markdown-oversize-test-BEpbXIvl.js +88 -0
  138. package/bundled-test-harness/assets/field-markdown-primitives-test-BDORWoOk.js +365 -0
  139. package/bundled-test-harness/assets/field-markdown-specialized-test-BmIzadYq.js +534 -0
  140. package/bundled-test-harness/assets/field-test-helpers-CRF9-jFw.js +45 -0
  141. package/bundled-test-harness/assets/file-api-DPMJghBY.js +7 -0
  142. package/bundled-test-harness/assets/file-attachment-test-DFLXg3xV.js +796 -0
  143. package/bundled-test-harness/assets/file-chooser-panel-test-B4DzCWkt.js +153 -0
  144. package/bundled-test-harness/assets/file-chooser-test-DpjOWgpB.js +504 -0
  145. package/bundled-test-harness/assets/file-def-edit-Bstl7Kkl.js +2 -0
  146. package/bundled-test-harness/assets/file-def-edit-C1dzJxmQ.js +26 -0
  147. package/bundled-test-harness/assets/file-def-embedded-CUy4G7Vc.js +2 -0
  148. package/bundled-test-harness/assets/file-def-embedded-DJs6P5t0.js +38 -0
  149. package/bundled-test-harness/assets/file-def-fitted-BlrBFfLh.js +38 -0
  150. package/bundled-test-harness/assets/file-def-fitted-Bo5Qfkyf.js +2 -0
  151. package/bundled-test-harness/assets/file-def-isolated-BsHydanv.js +38 -0
  152. package/bundled-test-harness/assets/file-def-isolated-Dp-Lc99-.js +2 -0
  153. package/bundled-test-harness/assets/file-def-serialization-test-CISKpt_o.js +89 -0
  154. package/bundled-test-harness/assets/file-menu-items-7_ltrFZq.js +117 -0
  155. package/bundled-test-harness/assets/file-menu-items-BnRG9ydE.js +2 -0
  156. package/bundled-test-harness/assets/file-preview-stage-CV3qZpiK.js +72 -0
  157. package/bundled-test-harness/assets/file-preview-stage-DTA5X8rX.js +2 -0
  158. package/bundled-test-harness/assets/file-shell-embedded-BpmiVzIG.js +2 -0
  159. package/bundled-test-harness/assets/file-shell-embedded-CEv0-6OE.js +68 -0
  160. package/bundled-test-harness/assets/file-shell-fitted-C6k3GnMc.js +2 -0
  161. package/bundled-test-harness/assets/file-shell-fitted-iSTg3PJa.js +98 -0
  162. package/bundled-test-harness/assets/file-shell-isolated-C-pEod2J.js +195 -0
  163. package/bundled-test-harness/assets/file-shell-isolated-DI9oU7ix.js +2 -0
  164. package/bundled-test-harness/assets/file-tree-from-index-BtOOMqnb.js +151 -0
  165. package/bundled-test-harness/assets/file-tree-from-index-test-T8w3hPw_.js +96 -0
  166. package/bundled-test-harness/assets/film-5n9_gRqZ.js +4074 -0
  167. package/bundled-test-harness/assets/film-Ca2Vzlfu.js +13 -0
  168. package/bundled-test-harness/assets/film-DEvhBbBu.js +2 -0
  169. package/bundled-test-harness/assets/fitted-6OGuHgoU.js +2 -0
  170. package/bundled-test-harness/assets/fitted-B2l1jlSp.js +34 -0
  171. package/bundled-test-harness/assets/flac-meta-extractor-BoJqvmDC.js +99 -0
  172. package/bundled-test-harness/assets/font-file-def-Czzt5paT.js +64 -0
  173. package/bundled-test-harness/assets/font-meta-extractor-DQYT_e5b.js +2 -0
  174. package/bundled-test-harness/assets/font-meta-extractor-IHD-pG8V.js +303 -0
  175. package/bundled-test-harness/assets/formatted-aibot-message-test-Djsl8RzE.js +109 -0
  176. package/bundled-test-harness/assets/from-elsewhere-BXcLYxxu.js +131 -0
  177. package/bundled-test-harness/assets/frontmatter-field-CciolgL3.js +41 -0
  178. package/bundled-test-harness/assets/garbage-collection-test-BTOLAfB6.js +403 -0
  179. package/bundled-test-harness/assets/gauge-C1loqGq3.js +2 -0
  180. package/bundled-test-harness/assets/gauge-u8dxl90d.js +106 -0
  181. package/bundled-test-harness/assets/general-test-Cd5WtVvd.js +926 -0
  182. package/bundled-test-harness/assets/get-relationship-membership-state-test-DJAuhOVM.js +1308 -0
  183. package/bundled-test-harness/assets/gif-meta-extractor-tSaPtuYW.js +114 -0
  184. package/bundled-test-harness/assets/glimmer-motion-and-choreo-in-cards-test-DNSy5l59.js +160 -0
  185. package/bundled-test-harness/assets/glimmer-motion-and-choreo-test-B6BbQxYq.js +74 -0
  186. package/bundled-test-harness/assets/helper-m6jA5HmU.js +478 -0
  187. package/bundled-test-harness/assets/host-submode-test-CsgdksBi.js +1243 -0
  188. package/bundled-test-harness/assets/html-file-def-yKtdMSa-.js +82 -0
  189. package/bundled-test-harness/assets/hydratable-card-CxNfbqly.js +147 -0
  190. package/bundled-test-harness/assets/hydratable-card-test-NJ4CI4ey.js +426 -0
  191. package/bundled-test-harness/assets/icons-B3SAtRoj.js +894 -0
  192. package/bundled-test-harness/assets/image-file-def-B0rpS3Yy.js +3 -0
  193. package/bundled-test-harness/assets/image-file-def-DUFmjNL8.js +65 -0
  194. package/bundled-test-harness/assets/image-olOUqgMQ.js +47 -0
  195. package/bundled-test-harness/assets/index-changes-test-D7OE-99k.js +367 -0
  196. package/bundled-test-harness/assets/index-query-engine-test-BBKqgSmg.js +2593 -0
  197. package/bundled-test-harness/assets/inspector-test-DV6Ms2uz.js +1930 -0
  198. package/bundled-test-harness/assets/instance-filter-matcher-test-Bs4kf7E8.js +788 -0
  199. package/bundled-test-harness/assets/interact-submode-creation-and-permissions-test-DIfgRhIN.js +475 -0
  200. package/bundled-test-harness/assets/interact-submode-setup-D4M_e580.js +566 -0
  201. package/bundled-test-harness/assets/is-component-declaration-test-jPGRTVh1.js +50 -0
  202. package/bundled-test-harness/assets/iso-bmff-DHtkSbQM.js +205 -0
  203. package/bundled-test-harness/assets/iso-bmff-zmOHoSVg.js +2 -0
  204. package/bundled-test-harness/assets/isolated-and-edit-BAnwcK1T.js +2 -0
  205. package/bundled-test-harness/assets/job-priority-header-test-7j2f3Dtl.js +81 -0
  206. package/bundled-test-harness/assets/job-scoped-instance-residency-test-DPzVQUL3.js +103 -0
  207. package/bundled-test-harness/assets/join-the-community-Ft5fjeIA.js +86 -0
  208. package/bundled-test-harness/assets/jpg-meta-extractor-Dsq6rZ8F.js +88 -0
  209. package/bundled-test-harness/assets/json-field-CxOZvp3k.js +19 -0
  210. package/bundled-test-harness/assets/json-field-ojdwGD6T.js +2 -0
  211. package/bundled-test-harness/assets/json-file-def-Da_5-zSM.js +298 -0
  212. package/bundled-test-harness/assets/last-modified-date-BnyJsc04.js +43 -0
  213. package/bundled-test-harness/assets/last-modified-date-test-DFl2mo1B.js +28 -0
  214. package/bundled-test-harness/assets/links-to-editor-DO24-71w.js +2 -0
  215. package/bundled-test-harness/assets/links-to-many-component-C7DabwAY.js +2 -0
  216. package/bundled-test-harness/assets/linksto-broken-link-placeholder-test-D4lDhvnQ.js +398 -0
  217. package/bundled-test-harness/assets/linksto-error-sentinel-test-oWgDz00K.js +238 -0
  218. package/bundled-test-harness/assets/linksto-many-broken-link-placeholder-test-CpSl7zJ7.js +354 -0
  219. package/bundled-test-harness/assets/linksto-many-per-slot-access-test-DJY3J8Xl.js +303 -0
  220. package/bundled-test-harness/assets/linksto-sentinel-reload-test-BxzfoONe.js +197 -0
  221. package/bundled-test-harness/assets/linksto-sentinel-roundtrip-test-DyE99km_.js +260 -0
  222. package/bundled-test-harness/assets/linksto-singular-js-access-contract-test-dcWAAgxt.js +196 -0
  223. package/bundled-test-harness/assets/llm-model-DrswiczZ.js +64 -0
  224. package/bundled-test-harness/assets/login-C-EIG0-Q.css +543 -0
  225. package/bundled-test-harness/assets/login-CNeL-9IC.js +408 -0
  226. package/bundled-test-harness/assets/login-error-text-test-CEH2tjNP.js +51 -0
  227. package/bundled-test-harness/assets/login-test-BpMx7qwV.js +246 -0
  228. package/bundled-test-harness/assets/m4a-meta-extractor-BJkMHUIK.js +204 -0
  229. package/bundled-test-harness/assets/main-BaUgfpPE.js +5 -0
  230. package/bundled-test-harness/assets/markdown-ByEGbwov.js +7 -0
  231. package/bundled-test-harness/assets/markdown-KJzgTg-A.js +2 -0
  232. package/bundled-test-harness/assets/markdown-editor-mode-select-BUGPXaSE.js +2 -0
  233. package/bundled-test-harness/assets/markdown-editor-mode-select-DBk8dG0O.js +54 -0
  234. package/bundled-test-harness/assets/markdown-embed-chooser-modal-test-BIlEmna9.js +470 -0
  235. package/bundled-test-harness/assets/markdown-embed-preview-pane-test-B6rscKpk.js +447 -0
  236. package/bundled-test-harness/assets/markdown-embed-preview-test-CTQw1wq-.js +368 -0
  237. package/bundled-test-harness/assets/markdown-fallback-BVsco6gJ.js +2 -0
  238. package/bundled-test-harness/assets/markdown-fallback-test-C_CIFEzH.js +316 -0
  239. package/bundled-test-harness/assets/markdown-highlighting-test-D6YvywF5.js +244 -0
  240. package/bundled-test-harness/assets/markdown-preview-1TSMSUv0.js +64 -0
  241. package/bundled-test-harness/assets/markdown-preview-jbuUjLoD.js +72 -0
  242. package/bundled-test-harness/assets/markdown-preview-test-zuU5uoA_.js +238 -0
  243. package/bundled-test-harness/assets/markdown-skill-frontmatter-test-CoicGRkb.js +146 -0
  244. package/bundled-test-harness/assets/menu-items-DZ_uOoYu.js +2 -0
  245. package/bundled-test-harness/assets/message-tool-test-K6LgTq5d.js +33 -0
  246. package/bundled-test-harness/assets/metadata-fields-WiOuUCDl.js +1415 -0
  247. package/bundled-test-harness/assets/metadata-fields-iu5AfQp3.js +2 -0
  248. package/bundled-test-harness/assets/midi-audio-def-CD2-4Wjx.js +52 -0
  249. package/bundled-test-harness/assets/midi-meta-extractor-CE3sLiEV.js +284 -0
  250. package/bundled-test-harness/assets/midi-meta-extractor-O7c8LHYV.js +2 -0
  251. package/bundled-test-harness/assets/migrate-skill-test-Beh-AeSN.js +161 -0
  252. package/bundled-test-harness/assets/mini-BNXINKKF.js +85 -0
  253. package/bundled-test-harness/assets/mini-C43Q4BWp.js +1519 -0
  254. package/bundled-test-harness/assets/mini-card-chooser-test-B1GMp-31.js +272 -0
  255. package/bundled-test-harness/assets/mini-file-chooser-test-CbI1vrQn.js +201 -0
  256. package/bundled-test-harness/assets/missing-template-BPhJRbDs.js +48 -0
  257. package/bundled-test-harness/assets/missing-template-DP-Pi-HQ.js +2 -0
  258. package/bundled-test-harness/assets/modal-KpPz2f8x.js +322 -0
  259. package/bundled-test-harness/assets/modal-container-0Efz_h2z.js +40 -0
  260. package/bundled-test-harness/assets/model-cost-tier-test-D-5VSjh6.js +173 -0
  261. package/bundled-test-harness/assets/module-contents-service-BRkTETb6.js +256 -0
  262. package/bundled-test-harness/assets/moment-Cln59YUK.js +3474 -0
  263. package/bundled-test-harness/assets/monaco-AbHCoDJY.js +143 -0
  264. package/bundled-test-harness/assets/monaco-test-DCPi18sy.js +100 -0
  265. package/bundled-test-harness/assets/monaco-test-waiter-lvFEYpFj.js +190 -0
  266. package/bundled-test-harness/assets/month-1eUIsvWN.js +2 -0
  267. package/bundled-test-harness/assets/month-BeUdWMWQ.js +123 -0
  268. package/bundled-test-harness/assets/month-day-Bbsd8YbR.js +192 -0
  269. package/bundled-test-harness/assets/month-day-Dks5STJu.js +2 -0
  270. package/bundled-test-harness/assets/month-year-pL_rpUhX.js +2 -0
  271. package/bundled-test-harness/assets/month-year-wH_YnYgL.js +93 -0
  272. package/bundled-test-harness/assets/mp3-meta-extractor-C3u34UHM.js +476 -0
  273. package/bundled-test-harness/assets/mp4-meta-extractor-Bj3F7dxs.js +188 -0
  274. package/bundled-test-harness/assets/multiple-stacks-test-mkNdTUMI.js +208 -0
  275. package/bundled-test-harness/assets/nested-query-field-render-test-BQLn3wg9.js +249 -0
  276. package/bundled-test-harness/assets/no-stacks-test-DpwZQAJ-.js +48 -0
  277. package/bundled-test-harness/assets/number-DZa-fHpu.js +151 -0
  278. package/bundled-test-harness/assets/number-Dvua76YP.js +3 -0
  279. package/bundled-test-harness/assets/number-field-test-BiaH2eaB.js +184 -0
  280. package/bundled-test-harness/assets/number-input-D5nDYDkK.js +82 -0
  281. package/bundled-test-harness/assets/office-extract-JzYva33g.js +27 -0
  282. package/bundled-test-harness/assets/office-extract-XMpsyYRh.js +2 -0
  283. package/bundled-test-harness/assets/ogg-meta-extractor-DJosZ9LA.js +225 -0
  284. package/bundled-test-harness/assets/on-key-BBVkS8Bi.js +472 -0
  285. package/bundled-test-harness/assets/ooxml-BlAE2w4d.js +172 -0
  286. package/bundled-test-harness/assets/ooxml-D-p8OmuM.js +2 -0
  287. package/bundled-test-harness/assets/open-image-in-new-tab-test-DWBU5pw9.js +52 -0
  288. package/bundled-test-harness/assets/operation-lowering-test-nzqprueU.js +2812 -0
  289. package/bundled-test-harness/assets/operations-CPvfTuY3.js +766 -0
  290. package/bundled-test-harness/assets/operations-invocation-test-C75T6zn3.js +523 -0
  291. package/bundled-test-harness/assets/operations-query-test-BoCszQEW.js +459 -0
  292. package/bundled-test-harness/assets/operations-test-79ipSnpM.js +2518 -0
  293. package/bundled-test-harness/assets/operations-x3Aw2Nky.js +3 -0
  294. package/bundled-test-harness/assets/operator-mode-acceptance-test-jjsYtt96.js +1188 -0
  295. package/bundled-test-harness/assets/operator-mode-card-chooser-test-hZP9gEQK.js +1036 -0
  296. package/bundled-test-harness/assets/operator-mode-links-test-DN16SuIx.js +793 -0
  297. package/bundled-test-harness/assets/operator-mode-state-service-gwiTSiWX.js +1275 -0
  298. package/bundled-test-harness/assets/operator-mode-state-service-test-Dt2qnKKP.js +206 -0
  299. package/bundled-test-harness/assets/operator-mode-test-BZnnxO9q.js +683 -0
  300. package/bundled-test-harness/assets/operator-mode-ui-test-O--qEo-y.js +1027 -0
  301. package/bundled-test-harness/assets/overlay-menu-items-test-Bl2k0aAa.js +317 -0
  302. package/bundled-test-harness/assets/overlays-CdN_JokA.js +270 -0
  303. package/bundled-test-harness/assets/overlays-clip-test-DpbR1ZTz.js +91 -0
  304. package/bundled-test-harness/assets/overlays-detached-element-test-cLgEGvMs.js +56 -0
  305. package/bundled-test-harness/assets/pane-PYdezM7g.js +330 -0
  306. package/bundled-test-harness/assets/panel-7FDqkxrr.js +474 -0
  307. package/bundled-test-harness/assets/panel-C3E0s49q.js +3540 -0
  308. package/bundled-test-harness/assets/past-sessions-test-BsjEeoze.js +321 -0
  309. package/bundled-test-harness/assets/patch-fields-test-CsixJmSr.js +1014 -0
  310. package/bundled-test-harness/assets/patch-instance-test-CBDgTi0L.js +348 -0
  311. package/bundled-test-harness/assets/pdf-file-def-9VNjnqDa.js +76 -0
  312. package/bundled-test-harness/assets/pdf-meta-extractor-BF4e5kMR.js +156 -0
  313. package/bundled-test-harness/assets/pdf-meta-extractor-D8Bxh2cc.js +2 -0
  314. package/bundled-test-harness/assets/pdf-viewer-Bu413sN8.js +112 -0
  315. package/bundled-test-harness/assets/pdf-viewer-GKDi_xmf.js +2 -0
  316. package/bundled-test-harness/assets/percentage-TOr6qK2T.js +84 -0
  317. package/bundled-test-harness/assets/permission-checking-realm-test-BRNVYszm.js +306 -0
  318. package/bundled-test-harness/assets/phone-number-D5SR8nWu.js +236 -0
  319. package/bundled-test-harness/assets/phone-number-field-test-DffwtqGd.js +73 -0
  320. package/bundled-test-harness/assets/phone-number-hf_xIVGQ.js +2 -0
  321. package/bundled-test-harness/assets/plate-Vojc88hM.js +41 -0
  322. package/bundled-test-harness/assets/plate-dTUzEMj5.js +2 -0
  323. package/bundled-test-harness/assets/player-CmRw0uM5.js +82 -0
  324. package/bundled-test-harness/assets/player-Dt85lPZl.js +2 -0
  325. package/bundled-test-harness/assets/plural-field-components-test-DNpGuH1D.js +198 -0
  326. package/bundled-test-harness/assets/png-image-def-GY7gjqya.js +48 -0
  327. package/bundled-test-harness/assets/png-meta-extractor-4pI611re.js +2 -0
  328. package/bundled-test-harness/assets/png-meta-extractor-CHp_rpAi.js +111 -0
  329. package/bundled-test-harness/assets/policy-scoped-search-test-CipPu7tY.js +339 -0
  330. package/bundled-test-harness/assets/pptx-file-def-lBBiebvn.js +60 -0
  331. package/bundled-test-harness/assets/pptx-meta-extractor-BjJYWiOJ.js +77 -0
  332. package/bundled-test-harness/assets/pptx-meta-extractor-DbT-dPRg.js +2 -0
  333. package/bundled-test-harness/assets/prerender-persistence-block-test-ClHMVvJt.js +124 -0
  334. package/bundled-test-harness/assets/prerender-serialize-cycle-test-LdqaMTK8.js +134 -0
  335. package/bundled-test-harness/assets/preview-CYlam_aW.js +74 -0
  336. package/bundled-test-harness/assets/preview-format-CiSo4kEa.js +41 -0
  337. package/bundled-test-harness/assets/preview-format-test-MsB1TOLB.js +103 -0
  338. package/bundled-test-harness/assets/process-card-B3xBGkv1.js +2 -0
  339. package/bundled-test-harness/assets/process-card-DumVcx9E.js +114 -0
  340. package/bundled-test-harness/assets/process-card-test-C44H8yVy.js +94 -0
  341. package/bundled-test-harness/assets/profile-settings-modal-ipYzrVTz.js +708 -0
  342. package/bundled-test-harness/assets/progress-circle-DJ6WSO2E.js +95 -0
  343. package/bundled-test-harness/assets/progress-circle-q358_9Sn.js +2 -0
  344. package/bundled-test-harness/assets/quarter-BjO5DlU2.js +2 -0
  345. package/bundled-test-harness/assets/quarter-_D72FwfS.js +168 -0
  346. package/bundled-test-harness/assets/query-field-membership-status-test-CzDyfK49.js +323 -0
  347. package/bundled-test-harness/assets/query-field-page-optin-test-B-x3vsmC.js +126 -0
  348. package/bundled-test-harness/assets/query-field-schema-test-DFw6KuYv.js +231 -0
  349. package/bundled-test-harness/assets/query-field-stale-prerender-test-DSQ-cv_O.js +152 -0
  350. package/bundled-test-harness/assets/query-fields-test-DQiMFRCe.js +535 -0
  351. package/bundled-test-harness/assets/rail-CNspDFyn.js +44 -0
  352. package/bundled-test-harness/assets/rail-D2YcQ0VP.js +2 -0
  353. package/bundled-test-harness/assets/realm-82Z5RUM_.js +95 -0
  354. package/bundled-test-harness/assets/realm-config-SgrwqjRu.js +844 -0
  355. package/bundled-test-harness/assets/realm-config-policy-standing-test-C6sQNk2u.js +290 -0
  356. package/bundled-test-harness/assets/realm-config-routing-rule-edit-test-DcYXBlvi.js +274 -0
  357. package/bundled-test-harness/assets/realm-config-settings-test-DblJv06X.js +187 -0
  358. package/bundled-test-harness/assets/realm-dropdown-CDvL8k3B.js +560 -0
  359. package/bundled-test-harness/assets/realm-et890flh.js +2 -0
  360. package/bundled-test-harness/assets/realm-field-test-pBK_x-Xw.js +100 -0
  361. package/bundled-test-harness/assets/realm-indexing-test-DSFn2F8F.js +4380 -0
  362. package/bundled-test-harness/assets/realm-policy-test-DYnLqzTS.js +1141 -0
  363. package/bundled-test-harness/assets/realm-test-DOZT2EG5.js +2363 -0
  364. package/bundled-test-harness/assets/realm-url-tooltip-qaXTjRb-.js +56 -0
  365. package/bundled-test-harness/assets/realm-url-tooltip-test-DGTEeLH9.js +78 -0
  366. package/bundled-test-harness/assets/reasoning-test-BRkhvYjr.js +234 -0
  367. package/bundled-test-harness/assets/recent-colors-addon-BreXcpg8.js +2 -0
  368. package/bundled-test-harness/assets/recent-colors-addon-CmCkbJ2_.js +61 -0
  369. package/bundled-test-harness/assets/relationship-loading-state-test-B6UGYKzG.js +290 -0
  370. package/bundled-test-harness/assets/relative-time-BjHvL023.js +2 -0
  371. package/bundled-test-harness/assets/relative-time-ChYKOMIM.js +129 -0
  372. package/bundled-test-harness/assets/remix-card-test-CpV0kKfq.js +58 -0
  373. package/bundled-test-harness/assets/rendered-markdown-CJa7IzCC.js +263 -0
  374. package/bundled-test-harness/assets/rendered-markdown-test-CVKsHS4P.js +587 -0
  375. package/bundled-test-harness/assets/resident-instance-reuse-test-jQfW1NMn.js +82 -0
  376. package/bundled-test-harness/assets/response-field-B2-daCl2.js +38 -0
  377. package/bundled-test-harness/assets/result-tile-BSg4mzbc.js +140 -0
  378. package/bundled-test-harness/assets/rich-markdown-BtP1yamM.js +2 -0
  379. package/bundled-test-harness/assets/rich-markdown-C6rQUngb.js +218 -0
  380. package/bundled-test-harness/assets/rich-markdown-field-test-9yB6ykCe.js +1647 -0
  381. package/bundled-test-harness/assets/room-C7tKd02X.js +1008 -0
  382. package/bundled-test-harness/assets/room-message-test-DsMmRYpV.js +445 -0
  383. package/bundled-test-harness/assets/room-test-golI1H61.js +95 -0
  384. package/bundled-test-harness/assets/run-realm-code-CbXk6bnK.js +1600 -0
  385. package/bundled-test-harness/assets/run-realm-code-test-B9JeesaW.js +2468 -0
  386. package/bundled-test-harness/assets/schema-generation-test-uLLGm5Vh.js +28 -0
  387. package/bundled-test-harness/assets/score-BQhCQdUP.js +2 -0
  388. package/bundled-test-harness/assets/score-CoWEzENH.js +100 -0
  389. package/bundled-test-harness/assets/scrolling-test-WHWEkKeZ.js +384 -0
  390. package/bundled-test-harness/assets/search-BkQYpQDY.js +605 -0
  391. package/bundled-test-harness/assets/search-adorn-test-CW92JPOk.js +136 -0
  392. package/bundled-test-harness/assets/search-entries-BU4w6Ymc.js +472 -0
  393. package/bundled-test-harness/assets/search-entries-result-test-9eydsDtD.js +234 -0
  394. package/bundled-test-harness/assets/search-entries-test-ByUREx0G.js +1486 -0
  395. package/bundled-test-harness/assets/search-result-header-UFkdo0Bx.js +128 -0
  396. package/bundled-test-harness/assets/search-results-DrHTuVRZ.js +266 -0
  397. package/bundled-test-harness/assets/search-results-test-krtQQM57.js +578 -0
  398. package/bundled-test-harness/assets/search-selection-menu-test-DoqSbez7.js +108 -0
  399. package/bundled-test-harness/assets/search-sheet-persistence-test-4ZPaEY-y.js +131 -0
  400. package/bundled-test-harness/assets/search-test-BYpq32RM.js +2241 -0
  401. package/bundled-test-harness/assets/searchable-BXw4T11q.js +192 -0
  402. package/bundled-test-harness/assets/searchable-CVvPDA3Q.js +2 -0
  403. package/bundled-test-harness/assets/searchable-option-test-BhLFI4ei.js +322 -0
  404. package/bundled-test-harness/assets/searchable-search-doc-test-DrNLCF73.js +1271 -0
  405. package/bundled-test-harness/assets/secure-sign-in-VNQhe0ae.js +130 -0
  406. package/bundled-test-harness/assets/secure-sign-in-Za8PX9cD.css +24 -0
  407. package/bundled-test-harness/assets/secure-sign-in-aunWHCrB.js +73 -0
  408. package/bundled-test-harness/assets/secure-sign-in-test-BcY1TcNd.js +32 -0
  409. package/bundled-test-harness/assets/secure-sign-in-test-DRdz89BT.js +245 -0
  410. package/bundled-test-harness/assets/sending-test-mbHpGZUu.js +576 -0
  411. package/bundled-test-harness/assets/serialization-rri-form-audit-test-nypV9J82.js +136 -0
  412. package/bundled-test-harness/assets/serialization-test-aLwrFv4d.js +8758 -0
  413. package/bundled-test-harness/assets/setup-Dgn0s6Zk.js +772 -0
  414. package/bundled-test-harness/assets/shared-link-target-reuse-test-D9MR2UNc.js +207 -0
  415. package/bundled-test-harness/assets/show-card-Dwfk332M.js +67 -0
  416. package/bundled-test-harness/assets/show-card-test-CRPwwJvc.js +365 -0
  417. package/bundled-test-harness/assets/show-file-DLdFPHMR.js +34 -0
  418. package/bundled-test-harness/assets/signed-capture-DUR9PohQ.js +204 -0
  419. package/bundled-test-harness/assets/signed-capture-link-test-DfTAB6OT.js +132 -0
  420. package/bundled-test-harness/assets/single-stack-test-Crr_8nti.js +652 -0
  421. package/bundled-test-harness/assets/skill-DQ00VonZ.js +3 -0
  422. package/bundled-test-harness/assets/skill-reference-LEitQV9i.js +112 -0
  423. package/bundled-test-harness/assets/skill-yZXS1VHQ.js +76 -0
  424. package/bundled-test-harness/assets/skills-test-D3-C-Dbm.js +648 -0
  425. package/bundled-test-harness/assets/slider-picker-B6AxKL2j.js +245 -0
  426. package/bundled-test-harness/assets/slider-picker-C4Tc5o-s.js +2 -0
  427. package/bundled-test-harness/assets/spec-Cl_LB6nC.js +602 -0
  428. package/bundled-test-harness/assets/spinner-BHFM7zcR.js +39 -0
  429. package/bundled-test-harness/assets/spinner-DvAvsyDT.css +21 -0
  430. package/bundled-test-harness/assets/store-BV6WX8oV.js +2205 -0
  431. package/bundled-test-harness/assets/store-module-rebuild-test-HyphFJiF.js +454 -0
  432. package/bundled-test-harness/assets/store-search-test-nUeJ5opb.js +175 -0
  433. package/bundled-test-harness/assets/store-test-BebUIJAg.js +3460 -0
  434. package/bundled-test-harness/assets/string-BHZZwNlZ.js +9 -0
  435. package/bundled-test-harness/assets/submode-switcher-D-U9cXo0.js +169 -0
  436. package/bundled-test-harness/assets/svg-meta-extractor-BsG4w2nX.js +42 -0
  437. package/bundled-test-harness/assets/swatches-picker-4vFztxHo.js +33 -0
  438. package/bundled-test-harness/assets/swatches-picker-C7RE7uJ8.js +2 -0
  439. package/bundled-test-harness/assets/switch-submode-CuA0Zfjn.js +79 -0
  440. package/bundled-test-harness/assets/switch-submode-test-i1Qic_Cp.js +168 -0
  441. package/bundled-test-harness/assets/tag-CZNs3DPN.js +105 -0
  442. package/bundled-test-harness/assets/tests-BneuP252.js +3523 -0
  443. package/bundled-test-harness/assets/text-area-DAShQQBe.js +9 -0
  444. package/bundled-test-harness/assets/text-file-def-D2Rs6nS1.js +151 -0
  445. package/bundled-test-harness/assets/text-input-validator-test-C9IG-1GZ.js +156 -0
  446. package/bundled-test-harness/assets/theme-Dm8LWaRK.js +9 -0
  447. package/bundled-test-harness/assets/theme-card-test-Cbb6tAap.js +621 -0
  448. package/bundled-test-harness/assets/theme-scheme-island-test-DOVLakx3.js +264 -0
  449. package/bundled-test-harness/assets/three-d-model-def-DWSPQGgD.js +70 -0
  450. package/bundled-test-harness/assets/time--_Txqya1.js +126 -0
  451. package/bundled-test-harness/assets/time-DKD7Bdjd.js +2 -0
  452. package/bundled-test-harness/assets/time-slots-BZaugjq_.js +2 -0
  453. package/bundled-test-harness/assets/time-slots-C85M0rWl.js +85 -0
  454. package/bundled-test-harness/assets/timeline-B3KGDm3M.js +2 -0
  455. package/bundled-test-harness/assets/timeline-BMwgaZZu.js +59 -0
  456. package/bundled-test-harness/assets/to-device-streaming-test-CMp5bFiz.js +163 -0
  457. package/bundled-test-harness/assets/token-usage-test-CXA0b8Ep.js +166 -0
  458. package/bundled-test-harness/assets/tool-field-BGLelnsr.js +94 -0
  459. package/bundled-test-harness/assets/tool-field-BOFuDDt3.js +2 -0
  460. package/bundled-test-harness/assets/tools-CTjy7Aub.js +725 -0
  461. package/bundled-test-harness/assets/tools-calling-test--JR-7AK7.js +200 -0
  462. package/bundled-test-harness/assets/tools-test-6yAW3W5I.js +2820 -0
  463. package/bundled-test-harness/assets/tools-test-DhpMUQkz.js +1624 -0
  464. package/bundled-test-harness/assets/ts-file-def-CDbf8aw2.js +159 -0
  465. package/bundled-test-harness/assets/update-room-skills-test-DoKsS_1C.js +240 -0
  466. package/bundled-test-harness/assets/url-D0ooeJxC.js +2 -0
  467. package/bundled-test-harness/assets/url-mFskVPSX.js +96 -0
  468. package/bundled-test-harness/assets/video-file-def-AcmWqzev.js +76 -0
  469. package/bundled-test-harness/assets/wav-meta-extractor-CbTLruQf.js +368 -0
  470. package/bundled-test-harness/assets/webm-meta-extractor-C9JH-CMX.js +178 -0
  471. package/bundled-test-harness/assets/webp-meta-extractor-C6sHm4J7.js +98 -0
  472. package/bundled-test-harness/assets/website-Bxuk_OmR.js +92 -0
  473. package/bundled-test-harness/assets/week-Bxssxce7.js +2 -0
  474. package/bundled-test-harness/assets/week-DQLj9974.js +111 -0
  475. package/bundled-test-harness/assets/welcome-to-boxel-BH82RsW5.js +172 -0
  476. package/bundled-test-harness/assets/workspace-DJ_s2x4g.js +614 -0
  477. package/bundled-test-harness/assets/workspace-a11y-test-BJbZNgoP.js +197 -0
  478. package/bundled-test-harness/assets/workspace-behavior-test-BLBARTfb.js +200 -0
  479. package/bundled-test-harness/assets/workspace-chooser-BpmlVrVn.js +2219 -0
  480. package/bundled-test-harness/assets/workspace-chooser-CqLR2Dzh.css +1296 -0
  481. package/bundled-test-harness/assets/workspace-chooser-catalog-order-test-CmAVNAWC.js +36 -0
  482. package/bundled-test-harness/assets/workspace-chooser-unreachable-notice-test-DEfG7Uh9.js +60 -0
  483. package/bundled-test-harness/assets/workspace-delete-multiple-test-DE7Z8ooj.js +212 -0
  484. package/bundled-test-harness/assets/workspace-delete-summary-test-CwtG_zLH.js +63 -0
  485. package/bundled-test-harness/assets/workspace-timestamp-labels-test-DFAaRmJW.js +58 -0
  486. package/bundled-test-harness/assets/xlsx-file-def-DcvaRYnZ.js +60 -0
  487. package/bundled-test-harness/assets/xlsx-meta-extractor-0RlViVaf.js +101 -0
  488. package/bundled-test-harness/assets/xlsx-meta-extractor-BH30Vuem.js +2 -0
  489. package/bundled-test-harness/assets/year-B_MBk_hE.js +78 -0
  490. package/bundled-test-harness/assets/year-hSpccI9L.js +2 -0
  491. package/bundled-test-harness/assets/zip-file-def-c84iddi9.js +288 -0
  492. package/bundled-test-harness/index.html +78 -68
  493. package/bundled-test-harness/tests/index.html +78 -68
  494. package/bundled-types/host-app/components/matrix/account-switch-failed.gts +2 -2
  495. package/bundled-types/host-app/components/matrix/auth.gts +2 -2
  496. package/bundled-types/host-app/components/matrix/cli-auth.gts +7 -6
  497. package/bundled-types/host-app/components/matrix/forgot-password.gts +43 -129
  498. package/bundled-types/host-app/components/matrix/login.gts +15 -118
  499. package/bundled-types/host-app/components/matrix/register-user.gts +45 -226
  500. package/bundled-types/host-app/components/matrix/secure-sign-in.gts +235 -0
  501. package/bundled-types/host-app/components/operator-mode/container.gts +13 -1
  502. package/bundled-types/host-app/components/operator-mode/profile-info-popover.gts +1 -1
  503. package/bundled-types/host-app/config/environment.ts +1 -1
  504. package/bundled-types/host-app/lib/secure-sign-in.ts +130 -0
  505. package/bundled-types/host-app/services/matrix-service.ts +46 -0
  506. package/bundled-types/host-app/services/monaco-service.ts +0 -5
  507. package/bundled-types/host-app/utils/login-token.ts +23 -10
  508. package/dist/api.js +1 -1
  509. package/dist/index.js +289 -285
  510. package/package.json +1 -1
  511. package/src/build-program.ts +6 -2
  512. package/src/commands/profile.ts +72 -3
  513. package/src/lib/profile-manager.ts +5 -1
  514. package/src/lib/secure-session.ts +29 -0
  515. package/src/lib/sso-login.ts +94 -15
  516. package/bundled-test-harness/assets/account-switch-failed-B3pr_iC3.js +0 -32
  517. package/bundled-test-harness/assets/account-switch-failed-DTGZZo-G.css +0 -61
  518. package/bundled-test-harness/assets/account-switch-failed-test-9ENVgvlx.js +0 -45
  519. package/bundled-test-harness/assets/add-workspace-BAbk0250.js +0 -141
  520. package/bundled-test-harness/assets/add-workspace-test-ChAja_t-.js +0 -72
  521. package/bundled-test-harness/assets/advanced-color-picker-C-xBFJ2S.js +0 -504
  522. package/bundled-test-harness/assets/advanced-color-picker-bWAmCT-8.js +0 -2
  523. package/bundled-test-harness/assets/ai-assistant-test-BJ9yetuC.js +0 -2211
  524. package/bundled-test-harness/assets/ai-module-creation-test-Bw3TKS9d.js +0 -151
  525. package/bundled-test-harness/assets/aibot-message-DkgHT6eJ.js +0 -465
  526. package/bundled-test-harness/assets/app-DW5QjtRp.js +0 -22816
  527. package/bundled-test-harness/assets/app-yCDjO4mk.css +0 -2718
  528. package/bundled-test-harness/assets/audio-file-def-bJ3cXVmC.js +0 -133
  529. package/bundled-test-harness/assets/avif-meta-extractor-CrSDfYEt.js +0 -162
  530. package/bundled-test-harness/assets/base64-image-C7UBjK5W.js +0 -262
  531. package/bundled-test-harness/assets/base64-image-CjWpxROT.js +0 -2
  532. package/bundled-test-harness/assets/big-integer-BBql7oGZ.js +0 -89
  533. package/bundled-test-harness/assets/big-integer-B_TrzRy_.js +0 -2
  534. package/bundled-test-harness/assets/binary-upload-dedupe-test-BiX8kS7D.js +0 -308
  535. package/bundled-test-harness/assets/boolean-9LNUUTfw.js +0 -2
  536. package/bundled-test-harness/assets/boolean-BAmK01TF.js +0 -94
  537. package/bundled-test-harness/assets/brand-functional-palette-CFuoS-LS.js +0 -137
  538. package/bundled-test-harness/assets/brand-logo-By89eknI.js +0 -295
  539. package/bundled-test-harness/assets/broken-link-template-test-odBriDSP.js +0 -192
  540. package/bundled-test-harness/assets/bundled-base-CLQkkmN4.js +0 -254
  541. package/bundled-test-harness/assets/bundled-base-modules-test-BYrTaTwa.js +0 -54
  542. package/bundled-test-harness/assets/buried-stack-item-scroll-test-DU4bM5nd.js +0 -236
  543. package/bundled-test-harness/assets/bxl-expression-test-Cs9JaZbC.js +0 -250
  544. package/bundled-test-harness/assets/can-invoke-test-ccMc06BU.js +0 -401
  545. package/bundled-test-harness/assets/card-api-Bn65NUT0.js +0 -8
  546. package/bundled-test-harness/assets/card-api-test-COrFXu2i.js +0 -198
  547. package/bundled-test-harness/assets/card-api-u1pOrwS6.js +0 -4393
  548. package/bundled-test-harness/assets/card-basics-test-D4rn3iN-.js +0 -5364
  549. package/bundled-test-harness/assets/card-chooser-paging-test-bZhdrUXt.js +0 -235
  550. package/bundled-test-harness/assets/card-chooser-test-D9w1duob.js +0 -393
  551. package/bundled-test-harness/assets/card-collection-ClCV-__1.js +0 -118
  552. package/bundled-test-harness/assets/card-context-search-results-H_rN3Upz.js +0 -30
  553. package/bundled-test-harness/assets/card-context-search-results-test-jciyaouV.js +0 -184
  554. package/bundled-test-harness/assets/card-def-field-def-relationships-test-BVZoQzM8.js +0 -562
  555. package/bundled-test-harness/assets/card-delete-test-CuLcZFnY.js +0 -466
  556. package/bundled-test-harness/assets/card-error-DIOivSkD.js +0 -72
  557. package/bundled-test-harness/assets/card-error-awaiting-index-test-JMwcRONq.js +0 -53
  558. package/bundled-test-harness/assets/card-error-detail-96jWBx6B.js +0 -271
  559. package/bundled-test-harness/assets/card-info-BxPtwG1h.js +0 -2
  560. package/bundled-test-harness/assets/card-list-B34qY3io.js +0 -2
  561. package/bundled-test-harness/assets/card-list-BpCaKkZJ.js +0 -202
  562. package/bundled-test-harness/assets/card-resource-DcPbIwH9.js +0 -86
  563. package/bundled-test-harness/assets/cards-grid-CTN8nBT_.js +0 -344
  564. package/bundled-test-harness/assets/cards-grid-Cx44vL-M.js +0 -2
  565. package/bundled-test-harness/assets/cards-grid-layout-BdL0Uzxl.js +0 -111
  566. package/bundled-test-harness/assets/cards-grid-layout-Dn8Yb4ma.js +0 -2
  567. package/bundled-test-harness/assets/check-correctness-test-BfMV-MC0.js +0 -554
  568. package/bundled-test-harness/assets/clip-CK6mVuyN.js +0 -92
  569. package/bundled-test-harness/assets/clip-CfXD1JTr.js +0 -2
  570. package/bundled-test-harness/assets/code-ref-85Bih6Zl.js +0 -138
  571. package/bundled-test-harness/assets/code-ref-DATHaoyt.js +0 -2
  572. package/bundled-test-harness/assets/code-submode-availability-test-DLYlx0wo.js +0 -198
  573. package/bundled-test-harness/assets/code-submode-test-CDIi7MjD.js +0 -1824
  574. package/bundled-test-harness/assets/codeblocks-test-CNPyTvuv.js +0 -418
  575. package/bundled-test-harness/assets/codemirror-editor-BPOPTR-f.js +0 -2
  576. package/bundled-test-harness/assets/codemirror-editor-DPhG0pec.js +0 -701
  577. package/bundled-test-harness/assets/codemirror-embed-toolbar-test-Dk5lFOUq.js +0 -366
  578. package/bundled-test-harness/assets/color-BxN5gRSg.js +0 -65
  579. package/bundled-test-harness/assets/color-CrunRcis.js +0 -2
  580. package/bundled-test-harness/assets/color-field-test-COjFIpXV.js +0 -212
  581. package/bundled-test-harness/assets/color-picker-field-C-GS1CIw.js +0 -139
  582. package/bundled-test-harness/assets/color-picker-field-D3RXhbCK.js +0 -2
  583. package/bundled-test-harness/assets/color-wheel-picker-Bz8cBmf4.js +0 -283
  584. package/bundled-test-harness/assets/color-wheel-picker-DrwPrB8B.js +0 -2
  585. package/bundled-test-harness/assets/command-data-BxXgtNBf.js +0 -2
  586. package/bundled-test-harness/assets/command-data-ZdkcpEgG.js +0 -95
  587. package/bundled-test-harness/assets/compaction-test-DS7DTGT_.js +0 -150
  588. package/bundled-test-harness/assets/components-CbCTWAON.js +0 -17777
  589. package/bundled-test-harness/assets/computed-test-D_OrXDtR.js +0 -1320
  590. package/bundled-test-harness/assets/container-DDolu1gk.js +0 -11109
  591. package/bundled-test-harness/assets/container-DndcCIMv.css +0 -5169
  592. package/bundled-test-harness/assets/contains-many-component-BtgnAXih.js +0 -2
  593. package/bundled-test-harness/assets/contrast-checker-addon-kwtLKWdL.js +0 -2
  594. package/bundled-test-harness/assets/contrast-checker-addon-zFY5cen1.js +0 -71
  595. package/bundled-test-harness/assets/convert-to-class-name-test-BcGWl2K1.js +0 -17
  596. package/bundled-test-harness/assets/coordinate-D72EF1Pl.js +0 -48
  597. package/bundled-test-harness/assets/copy-file-to-realm-test-CYoDX4YV.js +0 -148
  598. package/bundled-test-harness/assets/country-B-iIe5v8.js +0 -199
  599. package/bundled-test-harness/assets/create-file-modal-PpvMksjR.js +0 -570
  600. package/bundled-test-harness/assets/create-listing-modal-test-CQtEf_UD.js +0 -273
  601. package/bundled-test-harness/assets/create-listing-pr-request-test-CDtKWyfk.js +0 -117
  602. package/bundled-test-harness/assets/create-spec-test-Cbqk8I9T.js +0 -256
  603. package/bundled-test-harness/assets/create-specs-CZg2cpnh.js +0 -215
  604. package/bundled-test-harness/assets/create-submission-workflow-test-CYrxF7bg.js +0 -132
  605. package/bundled-test-harness/assets/credential-fields-test-Di4jCJgU.js +0 -157
  606. package/bundled-test-harness/assets/css-value-CceJyI5o.js +0 -48
  607. package/bundled-test-harness/assets/csv-file-def-DJL2aZI9.js +0 -277
  608. package/bundled-test-harness/assets/currency-Bn5WIqzF.js +0 -134
  609. package/bundled-test-harness/assets/date-XI2CVYA2.js +0 -2
  610. package/bundled-test-harness/assets/date-h83UJcFR.js +0 -183
  611. package/bundled-test-harness/assets/date-time-fields-test-Cterr2mj.js +0 -359
  612. package/bundled-test-harness/assets/datetime-DadI80tC.js +0 -201
  613. package/bundled-test-harness/assets/datetime-Dpb_Sejj.js +0 -2
  614. package/bundled-test-harness/assets/datetime-stamp-C4vv397u.js +0 -81
  615. package/bundled-test-harness/assets/datetime-stamp-DIsJdQux.js +0 -2
  616. package/bundled-test-harness/assets/day-C4pZoGNM.js +0 -113
  617. package/bundled-test-harness/assets/day-D0goBhKb.js +0 -2
  618. package/bundled-test-harness/assets/debug-message-test-CV0sj-fl.js +0 -117
  619. package/bundled-test-harness/assets/dist-CAIttNZm.js +0 -1710
  620. package/bundled-test-harness/assets/dist-CLbqnmnm.js +0 -389
  621. package/bundled-test-harness/assets/dist-qkBOmIQy.js +0 -323
  622. package/bundled-test-harness/assets/docx-file-def-hz2tVQKf.js +0 -60
  623. package/bundled-test-harness/assets/docx-meta-extractor-CeRshlxL.js +0 -60
  624. package/bundled-test-harness/assets/docx-meta-extractor-muK5CKF3.js +0 -2
  625. package/bundled-test-harness/assets/duration-BYx1dLfm.js +0 -2
  626. package/bundled-test-harness/assets/duration-FAmTFeUa.js +0 -184
  627. package/bundled-test-harness/assets/email-D-bFy_C1.js +0 -80
  628. package/bundled-test-harness/assets/email-field-test-CY9K2zur.js +0 -75
  629. package/bundled-test-harness/assets/email-j4DZNH2-.js +0 -2
  630. package/bundled-test-harness/assets/enum-B7_baAn_.js +0 -167
  631. package/bundled-test-harness/assets/enum-BQqNzVQS.js +0 -2
  632. package/bundled-test-harness/assets/enum-field-test-BJ9Epz15.js +0 -953
  633. package/bundled-test-harness/assets/error-display-test-CbDWjJ2F.js +0 -343
  634. package/bundled-test-harness/assets/ethereum-address-BOLEnKsm.js +0 -78
  635. package/bundled-test-harness/assets/ethereum-address-s3Kvb2wG.js +0 -2
  636. package/bundled-test-harness/assets/expiration-warning-BaIP1MOZ.js +0 -2
  637. package/bundled-test-harness/assets/expiration-warning-gjEtxPk0.js +0 -90
  638. package/bundled-test-harness/assets/externals-Bih8ejPS.js +0 -4393
  639. package/bundled-test-harness/assets/fallback-banner-MlImHjSD.js +0 -24
  640. package/bundled-test-harness/assets/fallback-banner-live-delete-test-Bm7IKXPf.js +0 -207
  641. package/bundled-test-harness/assets/fallback-banner-test-C7TGA0Ob.js +0 -53
  642. package/bundled-test-harness/assets/fallback-banner-test-lvuaVRXu.js +0 -225
  643. package/bundled-test-harness/assets/fallback-models-test-Dy7iIKUS.js +0 -161
  644. package/bundled-test-harness/assets/field-component-DBoOxTke.js +0 -2
  645. package/bundled-test-harness/assets/field-configuration-test-DnAnzyC-.js +0 -548
  646. package/bundled-test-harness/assets/field-edit-DdJ8Akso.js +0 -2
  647. package/bundled-test-harness/assets/field-edit-XAop92l8.js +0 -36
  648. package/bundled-test-harness/assets/field-markdown-domain-test-cZCqbcjj.js +0 -616
  649. package/bundled-test-harness/assets/field-markdown-oversize-test-BuATetVz.js +0 -88
  650. package/bundled-test-harness/assets/field-markdown-primitives-test-BsubQfhv.js +0 -365
  651. package/bundled-test-harness/assets/field-markdown-specialized-test-4eNLiYpF.js +0 -534
  652. package/bundled-test-harness/assets/field-test-helpers-9I7cHnaU.js +0 -45
  653. package/bundled-test-harness/assets/file-api-CC86I7Er.js +0 -7
  654. package/bundled-test-harness/assets/file-attachment-test-o9191GMb.js +0 -796
  655. package/bundled-test-harness/assets/file-chooser-panel-test-C8aZuJlk.js +0 -153
  656. package/bundled-test-harness/assets/file-chooser-test-B0Tn8CCR.js +0 -504
  657. package/bundled-test-harness/assets/file-def-edit-BSiF2Lje.js +0 -2
  658. package/bundled-test-harness/assets/file-def-edit-C5TYBp_q.js +0 -26
  659. package/bundled-test-harness/assets/file-def-embedded-BLVTCiVB.js +0 -2
  660. package/bundled-test-harness/assets/file-def-embedded-Bep4iD6s.js +0 -38
  661. package/bundled-test-harness/assets/file-def-fitted-BTs-AS78.js +0 -2
  662. package/bundled-test-harness/assets/file-def-fitted-DcPitdYu.js +0 -38
  663. package/bundled-test-harness/assets/file-def-isolated-Kg1YVhm-.js +0 -38
  664. package/bundled-test-harness/assets/file-def-isolated-tK5JWCam.js +0 -2
  665. package/bundled-test-harness/assets/file-def-serialization-test-D4wnd5XK.js +0 -89
  666. package/bundled-test-harness/assets/file-menu-items-CH4-naSK.js +0 -117
  667. package/bundled-test-harness/assets/file-menu-items-emUxx3ZI.js +0 -2
  668. package/bundled-test-harness/assets/file-preview-stage-B1McIWwu.js +0 -2
  669. package/bundled-test-harness/assets/file-preview-stage-BgZLNMN4.js +0 -72
  670. package/bundled-test-harness/assets/file-shell-embedded-BmfuwWfU.js +0 -2
  671. package/bundled-test-harness/assets/file-shell-embedded-zh8CJanA.js +0 -68
  672. package/bundled-test-harness/assets/file-shell-fitted-DSFUNRGJ.js +0 -2
  673. package/bundled-test-harness/assets/file-shell-fitted-DXKDH0fn.js +0 -98
  674. package/bundled-test-harness/assets/file-shell-isolated-0F1MFv4q.js +0 -2
  675. package/bundled-test-harness/assets/file-shell-isolated-DduZZzmi.js +0 -195
  676. package/bundled-test-harness/assets/file-tree-from-index-BzDvvUHy.js +0 -151
  677. package/bundled-test-harness/assets/file-tree-from-index-test-D0hfI4a1.js +0 -96
  678. package/bundled-test-harness/assets/film-BN_HVXBU.js +0 -13
  679. package/bundled-test-harness/assets/film-CKkbUi0L.js +0 -2
  680. package/bundled-test-harness/assets/film-CMNJITZ-.js +0 -4074
  681. package/bundled-test-harness/assets/fitted-CAvdcKpu.js +0 -34
  682. package/bundled-test-harness/assets/fitted-CbKOwd1u.js +0 -2
  683. package/bundled-test-harness/assets/flac-meta-extractor-e8qajPSl.js +0 -99
  684. package/bundled-test-harness/assets/font-file-def-9atde5W8.js +0 -64
  685. package/bundled-test-harness/assets/font-meta-extractor-IralDC8M.js +0 -303
  686. package/bundled-test-harness/assets/font-meta-extractor-zPBVOfoR.js +0 -2
  687. package/bundled-test-harness/assets/formatted-aibot-message-test-CWOiZUng.js +0 -109
  688. package/bundled-test-harness/assets/from-elsewhere-ChxbtsBa.js +0 -131
  689. package/bundled-test-harness/assets/frontmatter-field-cdig7KR9.js +0 -41
  690. package/bundled-test-harness/assets/garbage-collection-test-Dg-ww_Sb.js +0 -403
  691. package/bundled-test-harness/assets/gauge-B45nLOGe.js +0 -106
  692. package/bundled-test-harness/assets/gauge-BK-768BM.js +0 -2
  693. package/bundled-test-harness/assets/general-test-D7cn7PUr.js +0 -926
  694. package/bundled-test-harness/assets/get-relationship-membership-state-test-Skmj9V6y.js +0 -1308
  695. package/bundled-test-harness/assets/gif-meta-extractor-BychhK9q.js +0 -114
  696. package/bundled-test-harness/assets/glimmer-motion-and-choreo-in-cards-test-C09YUmk4.js +0 -160
  697. package/bundled-test-harness/assets/glimmer-motion-and-choreo-test-DYskx_Kk.js +0 -74
  698. package/bundled-test-harness/assets/helper-BHX0HLF2.js +0 -478
  699. package/bundled-test-harness/assets/host-submode-test-Clw_QnvF.js +0 -1243
  700. package/bundled-test-harness/assets/html-file-def-C0P9giwU.js +0 -82
  701. package/bundled-test-harness/assets/hydratable-card-DnjrxKsT.js +0 -147
  702. package/bundled-test-harness/assets/hydratable-card-test-BAmWHfv1.js +0 -426
  703. package/bundled-test-harness/assets/icons-DBXBZgLe.js +0 -894
  704. package/bundled-test-harness/assets/image-Cip6ojDd.js +0 -47
  705. package/bundled-test-harness/assets/image-file-def-BdQxjZcN.js +0 -65
  706. package/bundled-test-harness/assets/image-file-def-DP4aqWcS.js +0 -3
  707. package/bundled-test-harness/assets/index-changes-test-D8b_56Ke.js +0 -367
  708. package/bundled-test-harness/assets/index-query-engine-test-CCyaUwMX.js +0 -2593
  709. package/bundled-test-harness/assets/inspector-test-BJTVGdOO.js +0 -1930
  710. package/bundled-test-harness/assets/instance-filter-matcher-test-DjsPrXL7.js +0 -788
  711. package/bundled-test-harness/assets/interact-submode-creation-and-permissions-test-DSqM0vxh.js +0 -475
  712. package/bundled-test-harness/assets/interact-submode-setup-DF31UMX-.js +0 -566
  713. package/bundled-test-harness/assets/is-component-declaration-test-Did47eG9.js +0 -50
  714. package/bundled-test-harness/assets/iso-bmff-LuWkGL__.js +0 -205
  715. package/bundled-test-harness/assets/iso-bmff-_flO6c20.js +0 -2
  716. package/bundled-test-harness/assets/isolated-and-edit-frocLHin.js +0 -2
  717. package/bundled-test-harness/assets/job-priority-header-test-DHLAXfp7.js +0 -81
  718. package/bundled-test-harness/assets/job-scoped-instance-residency-test-C1Bt5j98.js +0 -103
  719. package/bundled-test-harness/assets/join-the-community-BBUSO2cs.js +0 -86
  720. package/bundled-test-harness/assets/jpg-meta-extractor-CoybO4Hq.js +0 -88
  721. package/bundled-test-harness/assets/json-field-8sdH5RQb.js +0 -2
  722. package/bundled-test-harness/assets/json-field-BDD3iTUh.js +0 -19
  723. package/bundled-test-harness/assets/json-file-def-BKibsfc6.js +0 -298
  724. package/bundled-test-harness/assets/last-modified-date-CR5jru2m.js +0 -43
  725. package/bundled-test-harness/assets/last-modified-date-test-jJ00YdTm.js +0 -28
  726. package/bundled-test-harness/assets/links-to-editor-rDoCYaN-.js +0 -2
  727. package/bundled-test-harness/assets/links-to-many-component-BqZT88Fb.js +0 -2
  728. package/bundled-test-harness/assets/linksto-broken-link-placeholder-test-Cp1Eb_4U.js +0 -398
  729. package/bundled-test-harness/assets/linksto-error-sentinel-test-Bddavnh_.js +0 -238
  730. package/bundled-test-harness/assets/linksto-many-broken-link-placeholder-test-c5WegXnR.js +0 -354
  731. package/bundled-test-harness/assets/linksto-many-per-slot-access-test-CWskzfGW.js +0 -303
  732. package/bundled-test-harness/assets/linksto-sentinel-reload-test-ByeVNhr8.js +0 -197
  733. package/bundled-test-harness/assets/linksto-sentinel-roundtrip-test-DP26i6Gb.js +0 -260
  734. package/bundled-test-harness/assets/linksto-singular-js-access-contract-test-fDUXx8tO.js +0 -196
  735. package/bundled-test-harness/assets/llm-model-BcLEkTV7.js +0 -64
  736. package/bundled-test-harness/assets/login-CeuuNVvU.css +0 -139
  737. package/bundled-test-harness/assets/login-DV0WlJv-.js +0 -348
  738. package/bundled-test-harness/assets/login-error-text-test-BMGY_9gW.js +0 -51
  739. package/bundled-test-harness/assets/login-test-CL9YpMH5.js +0 -246
  740. package/bundled-test-harness/assets/m4a-meta-extractor-CHO1JQtb.js +0 -204
  741. package/bundled-test-harness/assets/main-CPLT8JNu.js +0 -5
  742. package/bundled-test-harness/assets/markdown-BvBALLGh.js +0 -7
  743. package/bundled-test-harness/assets/markdown-DFezmrFD.js +0 -2
  744. package/bundled-test-harness/assets/markdown-editor-mode-select-Au64bcIO.js +0 -2
  745. package/bundled-test-harness/assets/markdown-editor-mode-select-rK9PP8rv.js +0 -54
  746. package/bundled-test-harness/assets/markdown-embed-chooser-modal-test-CIxizf5k.js +0 -470
  747. package/bundled-test-harness/assets/markdown-embed-preview-pane-test-C7GoNN7l.js +0 -447
  748. package/bundled-test-harness/assets/markdown-embed-preview-test-CDnj4uhd.js +0 -368
  749. package/bundled-test-harness/assets/markdown-fallback-n7PsLNmO.js +0 -2
  750. package/bundled-test-harness/assets/markdown-fallback-test-DTHT6OwQ.js +0 -316
  751. package/bundled-test-harness/assets/markdown-highlighting-test-DJ-4WSE_.js +0 -244
  752. package/bundled-test-harness/assets/markdown-preview-Ccelx5W-.js +0 -64
  753. package/bundled-test-harness/assets/markdown-preview-JXuMC_ve.js +0 -72
  754. package/bundled-test-harness/assets/markdown-preview-test-ByZqrpPk.js +0 -238
  755. package/bundled-test-harness/assets/markdown-skill-frontmatter-test-DAHGeL4K.js +0 -146
  756. package/bundled-test-harness/assets/menu-items-DzJm9H_H.js +0 -2
  757. package/bundled-test-harness/assets/message-tool-test-lmdrIEdY.js +0 -33
  758. package/bundled-test-harness/assets/metadata-fields-BZZr4Qe5.js +0 -2
  759. package/bundled-test-harness/assets/metadata-fields-BtdjGsKE.js +0 -1415
  760. package/bundled-test-harness/assets/midi-audio-def-uYbYFmFi.js +0 -52
  761. package/bundled-test-harness/assets/midi-meta-extractor-DNI5gc1q.js +0 -284
  762. package/bundled-test-harness/assets/midi-meta-extractor-DbKZ4fMG.js +0 -2
  763. package/bundled-test-harness/assets/migrate-skill-test-DI1xceiL.js +0 -161
  764. package/bundled-test-harness/assets/mini-CNoDyHqG.js +0 -1519
  765. package/bundled-test-harness/assets/mini-DexlIeKK.js +0 -85
  766. package/bundled-test-harness/assets/mini-card-chooser-test-DT5eIf1_.js +0 -272
  767. package/bundled-test-harness/assets/mini-file-chooser-test-DTQnKTZJ.js +0 -201
  768. package/bundled-test-harness/assets/missing-template-DL2mwddM.js +0 -2
  769. package/bundled-test-harness/assets/missing-template-ltqlJ4JC.js +0 -48
  770. package/bundled-test-harness/assets/modal-container-DRnt95-S.js +0 -40
  771. package/bundled-test-harness/assets/modal-zE95eEFp.js +0 -322
  772. package/bundled-test-harness/assets/model-cost-tier-test-CcFz7hcv.js +0 -173
  773. package/bundled-test-harness/assets/module-contents-service-Dk_zWt-V.js +0 -256
  774. package/bundled-test-harness/assets/month-BqReBYWo.js +0 -2
  775. package/bundled-test-harness/assets/month-RhvmIp40.js +0 -123
  776. package/bundled-test-harness/assets/month-day-XFAQA72z.js +0 -2
  777. package/bundled-test-harness/assets/month-day-XzKpk6nz.js +0 -192
  778. package/bundled-test-harness/assets/month-year-BeuNr2Cl.js +0 -2
  779. package/bundled-test-harness/assets/month-year-DhU7hATV.js +0 -93
  780. package/bundled-test-harness/assets/mp3-meta-extractor-Dx2RnJc5.js +0 -476
  781. package/bundled-test-harness/assets/mp4-meta-extractor--k0m8KsT.js +0 -188
  782. package/bundled-test-harness/assets/multiple-stacks-test-bC24IWcJ.js +0 -208
  783. package/bundled-test-harness/assets/nested-query-field-render-test-qxElXx1e.js +0 -249
  784. package/bundled-test-harness/assets/no-stacks-test-Br7IGAgV.js +0 -48
  785. package/bundled-test-harness/assets/number-73SsCMEF.js +0 -3
  786. package/bundled-test-harness/assets/number-CicTBWQd.js +0 -151
  787. package/bundled-test-harness/assets/number-field-test-kV5zXo1J.js +0 -184
  788. package/bundled-test-harness/assets/number-input-CQy4xUeA.js +0 -82
  789. package/bundled-test-harness/assets/office-extract-Bo8sbNkK.js +0 -27
  790. package/bundled-test-harness/assets/office-extract-CfBaoRMD.js +0 -2
  791. package/bundled-test-harness/assets/ogg-meta-extractor-aIf5q_Rq.js +0 -225
  792. package/bundled-test-harness/assets/on-key-C8acaldQ.js +0 -472
  793. package/bundled-test-harness/assets/ooxml-Bu3iZezv.js +0 -172
  794. package/bundled-test-harness/assets/ooxml-jQPBQK25.js +0 -2
  795. package/bundled-test-harness/assets/open-image-in-new-tab-test-CP7q3kMm.js +0 -52
  796. package/bundled-test-harness/assets/operation-lowering-test-9Mdr0hRT.js +0 -2812
  797. package/bundled-test-harness/assets/operations-R9b661Qn.js +0 -3
  798. package/bundled-test-harness/assets/operations-Zv_V3Api.js +0 -766
  799. package/bundled-test-harness/assets/operations-invocation-test-BJPpWb2H.js +0 -523
  800. package/bundled-test-harness/assets/operations-query-test-DufdmPQF.js +0 -459
  801. package/bundled-test-harness/assets/operations-test-CzT051YH.js +0 -2518
  802. package/bundled-test-harness/assets/operator-mode-acceptance-test-ByOIOcD0.js +0 -1188
  803. package/bundled-test-harness/assets/operator-mode-card-chooser-test-CNhR9mvv.js +0 -1036
  804. package/bundled-test-harness/assets/operator-mode-links-test-CuxSVTPR.js +0 -793
  805. package/bundled-test-harness/assets/operator-mode-state-service-CCeHt5SH.js +0 -1275
  806. package/bundled-test-harness/assets/operator-mode-state-service-test-C8TI9WWg.js +0 -206
  807. package/bundled-test-harness/assets/operator-mode-test-DG-mG8Tn.js +0 -683
  808. package/bundled-test-harness/assets/operator-mode-ui-test-DB7xhjS0.js +0 -1027
  809. package/bundled-test-harness/assets/overlay-menu-items-test-C6rcdF_S.js +0 -317
  810. package/bundled-test-harness/assets/overlays-D2zmPxzK.js +0 -270
  811. package/bundled-test-harness/assets/overlays-clip-test-Clpq3JzQ.js +0 -91
  812. package/bundled-test-harness/assets/overlays-detached-element-test-BtsTeJch.js +0 -56
  813. package/bundled-test-harness/assets/pane-f-IYhcWT.js +0 -330
  814. package/bundled-test-harness/assets/panel-B3PCSBbf.js +0 -3540
  815. package/bundled-test-harness/assets/panel-Ban7zCBP.js +0 -474
  816. package/bundled-test-harness/assets/past-sessions-test-uM2m-oKV.js +0 -321
  817. package/bundled-test-harness/assets/patch-fields-test-COPjDhnt.js +0 -1014
  818. package/bundled-test-harness/assets/patch-instance-test-CRb3dfby.js +0 -348
  819. package/bundled-test-harness/assets/pdf-file-def-J_yOUU8_.js +0 -76
  820. package/bundled-test-harness/assets/pdf-meta-extractor-83T5Bdo7.js +0 -156
  821. package/bundled-test-harness/assets/pdf-meta-extractor-INFIklMy.js +0 -2
  822. package/bundled-test-harness/assets/pdf-viewer-B4rRluWA.js +0 -2
  823. package/bundled-test-harness/assets/pdf-viewer-vYem8ozk.js +0 -112
  824. package/bundled-test-harness/assets/percentage-ChagfNRG.js +0 -84
  825. package/bundled-test-harness/assets/permission-checking-realm-test-Dmb2a428.js +0 -306
  826. package/bundled-test-harness/assets/phone-number-BiD2eaSd.js +0 -236
  827. package/bundled-test-harness/assets/phone-number-C-wt4_UK.js +0 -2
  828. package/bundled-test-harness/assets/phone-number-field-test-BHzAmuBP.js +0 -73
  829. package/bundled-test-harness/assets/plate-CqN2nM6P.js +0 -41
  830. package/bundled-test-harness/assets/plate-DaIfpORN.js +0 -2
  831. package/bundled-test-harness/assets/player-CGt5fTd5.js +0 -2
  832. package/bundled-test-harness/assets/player-CN0l44LK.js +0 -82
  833. package/bundled-test-harness/assets/plural-field-components-test-CO2g2opW.js +0 -198
  834. package/bundled-test-harness/assets/png-image-def-B_oou2wS.js +0 -48
  835. package/bundled-test-harness/assets/png-meta-extractor-BfPz1qP_.js +0 -2
  836. package/bundled-test-harness/assets/png-meta-extractor-C8ai8sPw.js +0 -111
  837. package/bundled-test-harness/assets/policy-scoped-search-test-BRRp2a1A.js +0 -339
  838. package/bundled-test-harness/assets/pptx-file-def-C3qYEHDM.js +0 -60
  839. package/bundled-test-harness/assets/pptx-meta-extractor-8IuoBq4I.js +0 -77
  840. package/bundled-test-harness/assets/pptx-meta-extractor-BPhy9RMM.js +0 -2
  841. package/bundled-test-harness/assets/prerender-persistence-block-test-Cteb_Rs_.js +0 -124
  842. package/bundled-test-harness/assets/prerender-serialize-cycle-test-CH7b6Ra9.js +0 -134
  843. package/bundled-test-harness/assets/preview-CmERZpI6.js +0 -74
  844. package/bundled-test-harness/assets/preview-format-BPMlCSbb.js +0 -41
  845. package/bundled-test-harness/assets/preview-format-test-B7sA8fbZ.js +0 -103
  846. package/bundled-test-harness/assets/process-card-CtDzDB-_.js +0 -2
  847. package/bundled-test-harness/assets/process-card-erbWKY5h.js +0 -114
  848. package/bundled-test-harness/assets/process-card-test-DXOt1jDW.js +0 -94
  849. package/bundled-test-harness/assets/profile-settings-modal-11BrR058.js +0 -708
  850. package/bundled-test-harness/assets/progress-circle-Cd79Zqh2.js +0 -2
  851. package/bundled-test-harness/assets/progress-circle-sc-W5aUl.js +0 -95
  852. package/bundled-test-harness/assets/quarter-B61kCpcD.js +0 -168
  853. package/bundled-test-harness/assets/quarter-d5jkTzcz.js +0 -2
  854. package/bundled-test-harness/assets/query-field-membership-status-test-BmpVfohw.js +0 -323
  855. package/bundled-test-harness/assets/query-field-page-optin-test-9_Cce-OJ.js +0 -126
  856. package/bundled-test-harness/assets/query-field-schema-test-CwV7xanX.js +0 -231
  857. package/bundled-test-harness/assets/query-field-stale-prerender-test-RkdnZ-VV.js +0 -152
  858. package/bundled-test-harness/assets/query-fields-test-BLxYkTAM.js +0 -535
  859. package/bundled-test-harness/assets/rail-CFPU0tFe.js +0 -2
  860. package/bundled-test-harness/assets/rail-CQ53Dakr.js +0 -44
  861. package/bundled-test-harness/assets/realm-DdVH9lSZ.js +0 -95
  862. package/bundled-test-harness/assets/realm-DdsFw3b-.js +0 -2
  863. package/bundled-test-harness/assets/realm-config-DIslxoB7.js +0 -844
  864. package/bundled-test-harness/assets/realm-config-policy-standing-test-b4WxNDor.js +0 -290
  865. package/bundled-test-harness/assets/realm-config-routing-rule-edit-test-C-rcvkkD.js +0 -274
  866. package/bundled-test-harness/assets/realm-config-settings-test-DDUkM3zw.js +0 -187
  867. package/bundled-test-harness/assets/realm-dropdown-1j55Dy-1.js +0 -560
  868. package/bundled-test-harness/assets/realm-field-test-D-uEtFKO.js +0 -100
  869. package/bundled-test-harness/assets/realm-indexing-test-U9m0epR-.js +0 -4380
  870. package/bundled-test-harness/assets/realm-policy-test-DhKabhlk.js +0 -1141
  871. package/bundled-test-harness/assets/realm-test-BX6C9SS5.js +0 -2363
  872. package/bundled-test-harness/assets/realm-url-tooltip-BjjcKqBx.js +0 -56
  873. package/bundled-test-harness/assets/realm-url-tooltip-test-DW0ZwEEE.js +0 -78
  874. package/bundled-test-harness/assets/reasoning-test-BWT94KXG.js +0 -234
  875. package/bundled-test-harness/assets/recent-colors-addon-CfgmgilF.js +0 -61
  876. package/bundled-test-harness/assets/recent-colors-addon-D--Ry3Pf.js +0 -2
  877. package/bundled-test-harness/assets/relationship-loading-state-test-C5R6jao_.js +0 -290
  878. package/bundled-test-harness/assets/relative-time-Bq7pqums.js +0 -2
  879. package/bundled-test-harness/assets/relative-time-D0QyW4ig.js +0 -129
  880. package/bundled-test-harness/assets/remix-card-test-Cy0h3Xdf.js +0 -58
  881. package/bundled-test-harness/assets/rendered-markdown-Hw_EF8SO.js +0 -263
  882. package/bundled-test-harness/assets/rendered-markdown-test-CqeF-UZd.js +0 -587
  883. package/bundled-test-harness/assets/resident-instance-reuse-test-BiI6L40n.js +0 -82
  884. package/bundled-test-harness/assets/response-field-BCxG5phG.js +0 -38
  885. package/bundled-test-harness/assets/result-tile-DMzRnTP8.js +0 -140
  886. package/bundled-test-harness/assets/rich-markdown-C4RUl7L0.js +0 -2
  887. package/bundled-test-harness/assets/rich-markdown-DxQSCg-o.js +0 -218
  888. package/bundled-test-harness/assets/rich-markdown-field-test-B7wpedKs.js +0 -1647
  889. package/bundled-test-harness/assets/room-D0M0Zwrs.js +0 -1008
  890. package/bundled-test-harness/assets/room-message-test-BWWd4xC8.js +0 -445
  891. package/bundled-test-harness/assets/room-test-DFH-nOwi.js +0 -95
  892. package/bundled-test-harness/assets/run-realm-code-DyVyEDBl.js +0 -1600
  893. package/bundled-test-harness/assets/run-realm-code-test-BpxF-tOz.js +0 -2468
  894. package/bundled-test-harness/assets/schema-generation-test-DjaJqpYP.js +0 -28
  895. package/bundled-test-harness/assets/score-Cfp34BE7.js +0 -2
  896. package/bundled-test-harness/assets/score-DIWWpeyf.js +0 -100
  897. package/bundled-test-harness/assets/scrolling-test-hA_uBwBg.js +0 -384
  898. package/bundled-test-harness/assets/search-C0-b7dJI.js +0 -605
  899. package/bundled-test-harness/assets/search-adorn-test-C6KP3AwA.js +0 -136
  900. package/bundled-test-harness/assets/search-entries-BfPq5sPS.js +0 -472
  901. package/bundled-test-harness/assets/search-entries-result-test-Bl9u_BOD.js +0 -234
  902. package/bundled-test-harness/assets/search-entries-test-BDC_lS43.js +0 -1486
  903. package/bundled-test-harness/assets/search-result-header-DCAze_cg.js +0 -128
  904. package/bundled-test-harness/assets/search-results-Dd0enVun.js +0 -266
  905. package/bundled-test-harness/assets/search-results-test-T5KEEII4.js +0 -578
  906. package/bundled-test-harness/assets/search-selection-menu-test-CCssnBq1.js +0 -108
  907. package/bundled-test-harness/assets/search-sheet-persistence-test-BzQiO-FK.js +0 -131
  908. package/bundled-test-harness/assets/search-test-fyCWb24m.js +0 -2241
  909. package/bundled-test-harness/assets/searchable-BkgeKQMl.js +0 -2
  910. package/bundled-test-harness/assets/searchable-D_6p6fyb.js +0 -192
  911. package/bundled-test-harness/assets/searchable-option-test-CnCUYGaq.js +0 -322
  912. package/bundled-test-harness/assets/searchable-search-doc-test-B8Q0D98v.js +0 -1271
  913. package/bundled-test-harness/assets/sending-test-D5PGBDys.js +0 -576
  914. package/bundled-test-harness/assets/serialization-rri-form-audit-test-BRJ1cJ31.js +0 -136
  915. package/bundled-test-harness/assets/serialization-test-DM6hK258.js +0 -8758
  916. package/bundled-test-harness/assets/setup-C_GgVgzM.js +0 -772
  917. package/bundled-test-harness/assets/shared-link-target-reuse-test-Be-lSKib.js +0 -207
  918. package/bundled-test-harness/assets/show-card-BFG6KR1m.js +0 -67
  919. package/bundled-test-harness/assets/show-card-test-l0wMrfZq.js +0 -365
  920. package/bundled-test-harness/assets/show-file-B8-FRVHj.js +0 -34
  921. package/bundled-test-harness/assets/signed-capture-D_Q44TQ7.js +0 -204
  922. package/bundled-test-harness/assets/signed-capture-link-test-DOS5xpYv.js +0 -132
  923. package/bundled-test-harness/assets/single-stack-test-BIEtzHVS.js +0 -652
  924. package/bundled-test-harness/assets/skill-BUTGmSsm.js +0 -76
  925. package/bundled-test-harness/assets/skill-Bo7R_2Ud.js +0 -3
  926. package/bundled-test-harness/assets/skill-reference-Cs64py3k.js +0 -112
  927. package/bundled-test-harness/assets/skills-test-CXxSIkYa.js +0 -648
  928. package/bundled-test-harness/assets/slider-picker-C81errGI.js +0 -245
  929. package/bundled-test-harness/assets/slider-picker-mvKsy6cR.js +0 -2
  930. package/bundled-test-harness/assets/spec-B55lJurX.js +0 -602
  931. package/bundled-test-harness/assets/store-DyQ0KiyI.js +0 -2205
  932. package/bundled-test-harness/assets/store-module-rebuild-test-C7532_bS.js +0 -454
  933. package/bundled-test-harness/assets/store-search-test-80NMJuoW.js +0 -175
  934. package/bundled-test-harness/assets/store-test-dHiWXXMs.js +0 -3460
  935. package/bundled-test-harness/assets/string-DHlQEHYm.js +0 -9
  936. package/bundled-test-harness/assets/submode-switcher-DQv-CD6G.js +0 -169
  937. package/bundled-test-harness/assets/svg-meta-extractor-Uw_EJytF.js +0 -42
  938. package/bundled-test-harness/assets/swatches-picker-CaEoibN1.js +0 -33
  939. package/bundled-test-harness/assets/swatches-picker-gPl50JbP.js +0 -2
  940. package/bundled-test-harness/assets/switch-submode-BKDnJd4m.js +0 -79
  941. package/bundled-test-harness/assets/switch-submode-test-_E2mVk4z.js +0 -168
  942. package/bundled-test-harness/assets/tag-B60lDr-j.js +0 -105
  943. package/bundled-test-harness/assets/tests-BBBgPBB6.js +0 -3520
  944. package/bundled-test-harness/assets/text-area-CJjWmwpi.js +0 -9
  945. package/bundled-test-harness/assets/text-file-def-u_dl0i2z.js +0 -151
  946. package/bundled-test-harness/assets/text-input-validator-test-BTwnkSS2.js +0 -156
  947. package/bundled-test-harness/assets/theme-BK-fAHVn.js +0 -9
  948. package/bundled-test-harness/assets/theme-card-test-BQmT7rqr.js +0 -621
  949. package/bundled-test-harness/assets/theme-scheme-island-test-Oi3aeyp1.js +0 -264
  950. package/bundled-test-harness/assets/three-d-model-def-B4KckPVC.js +0 -70
  951. package/bundled-test-harness/assets/time-BcuaSkHg.js +0 -126
  952. package/bundled-test-harness/assets/time-f7Irg7Z7.js +0 -2
  953. package/bundled-test-harness/assets/time-slots-ClYIZN53.js +0 -2
  954. package/bundled-test-harness/assets/time-slots-TI1aaqRN.js +0 -85
  955. package/bundled-test-harness/assets/timeline-Cof9eyq0.js +0 -59
  956. package/bundled-test-harness/assets/timeline-DFWQtCWE.js +0 -2
  957. package/bundled-test-harness/assets/to-device-streaming-test-BDrUUlOt.js +0 -163
  958. package/bundled-test-harness/assets/token-usage-test-CiZo03fY.js +0 -166
  959. package/bundled-test-harness/assets/tool-field-BXHr9r0w.js +0 -94
  960. package/bundled-test-harness/assets/tool-field-CoRO5C4R.js +0 -2
  961. package/bundled-test-harness/assets/tools-CmVaYDIS.js +0 -725
  962. package/bundled-test-harness/assets/tools-calling-test-B454__XF.js +0 -200
  963. package/bundled-test-harness/assets/tools-test-CLu51dJO.js +0 -1624
  964. package/bundled-test-harness/assets/tools-test-XGRAvVug.js +0 -2820
  965. package/bundled-test-harness/assets/ts-file-def-C05BDwdg.js +0 -159
  966. package/bundled-test-harness/assets/update-room-skills-test-jOvP14JX.js +0 -240
  967. package/bundled-test-harness/assets/url-BFf2CNo-.js +0 -2
  968. package/bundled-test-harness/assets/url-CYm-CeXp.js +0 -96
  969. package/bundled-test-harness/assets/video-file-def-B2UIrOFS.js +0 -76
  970. package/bundled-test-harness/assets/wav-meta-extractor-BNkv7e2f.js +0 -368
  971. package/bundled-test-harness/assets/webm-meta-extractor-C933kYyc.js +0 -178
  972. package/bundled-test-harness/assets/webp-meta-extractor-BLz0z1pZ.js +0 -98
  973. package/bundled-test-harness/assets/website-BuOMUW1v.js +0 -92
  974. package/bundled-test-harness/assets/week-Bo0666ji.js +0 -111
  975. package/bundled-test-harness/assets/week-CaVFA8Vc.js +0 -2
  976. package/bundled-test-harness/assets/welcome-to-boxel-PWard7GF.js +0 -172
  977. package/bundled-test-harness/assets/workspace-KXfFTQNV.js +0 -614
  978. package/bundled-test-harness/assets/workspace-a11y-test-Buwb-rfO.js +0 -197
  979. package/bundled-test-harness/assets/workspace-behavior-test-CbgFUOOo.js +0 -200
  980. package/bundled-test-harness/assets/workspace-chooser-BmQvhhrl.js +0 -2248
  981. package/bundled-test-harness/assets/workspace-chooser-Cu84Gd_E.css +0 -1317
  982. package/bundled-test-harness/assets/workspace-chooser-catalog-order-test-rcRw6sni.js +0 -36
  983. package/bundled-test-harness/assets/workspace-chooser-unreachable-notice-test-DUXnBIdY.js +0 -60
  984. package/bundled-test-harness/assets/workspace-delete-multiple-test-DjRKhZ2g.js +0 -212
  985. package/bundled-test-harness/assets/workspace-delete-summary-test-CvkLVYyN.js +0 -63
  986. package/bundled-test-harness/assets/workspace-timestamp-labels-test-AYJT4inv.js +0 -58
  987. package/bundled-test-harness/assets/xlsx-file-def-DuKdbGrJ.js +0 -60
  988. package/bundled-test-harness/assets/xlsx-meta-extractor-Bz-C2JWP.js +0 -101
  989. package/bundled-test-harness/assets/xlsx-meta-extractor-ClwwgABg.js +0 -2
  990. package/bundled-test-harness/assets/year-B6ctHikl.js +0 -2
  991. package/bundled-test-harness/assets/year-C11uAQE6.js +0 -78
  992. package/bundled-test-harness/assets/zip-file-def-B8XN-OS_.js +0 -288
  993. package/bundled-types/host-app/components/matrix/auth-button.gts +0 -39
  994. package/bundled-types/host-app/components/matrix/auth-container.gts +0 -69
  995. package/bundled-types/host-app/components/matrix/auth-form-field.gts +0 -43
@@ -1,4074 +0,0 @@
1
- import "./chunk-Rp0pCe6z.js";
2
- import { r as initializeDeferredDecorator, t as decorateFieldV2 } from "./runtime--fcdnjmJ-BoCBNIsz.js";
3
- import { et as tracked, wt as setComponentTemplate } from "./cache-DtDz7X5V-Wn8jRDfL.js";
4
- import { H as templateFactory, S as on } from "./index-DaCZcoCY-CgzbilMy.js";
5
- import "./tracking-CNiq91Pk.js";
6
- import "./modifier-D0EP-FQ7.js";
7
- import "./component-BJO47HTh.js";
8
- import { t as Component } from "./dist-CkA1rgqO.js";
9
- import "./template-factory-ds9s0u8l.js";
10
- import { r as modifier } from "./dist-nMt-N14r.js";
11
- import "./src-Bq3lg4hB.js";
12
- import { t as MotionModifier } from "./motion-l7EBYFii.js";
13
- import { t as Choreo } from "./choreo-CJDBg03r.js";
14
- import { E as at } from "./steps-D0xobKkB.js";
15
- import { t as Clip } from "./clip-CK6mVuyN.js";
16
- import { n as VOCABULARY, t as FilmGraph } from "./host-KVk6VIHI.js";
17
- import { h as seamShape, p as inGlass, s as Joins, u as PRESENTATIONS } from "./joins-D_xzMJAJ.js";
18
- import { i as Track, n as Insert, r as Stamp, t as Captions } from "./overlays-C6PM7oTd.js";
19
- import { t as Plate } from "./plate-CqN2nM6P.js";
20
- import { n as Menu, r as Player, t as Burst } from "./player-CN0l44LK.js";
21
- import { t as Rail } from "./rail-CQ53Dakr.js";
22
- import { n as Gate, t as EndCard } from "./titles-BE_XnCe4.js";
23
- //#region ../choreo/src/film/clips.ts
24
- /**
25
- * CLIPS — a source-time window over the film's clock.
26
- *
27
- * A beat is a shot of the picture. A clip is something else laid over it
28
- * for a while: a video, a still, or a freeze of the picture itself. Its
29
- * arithmetic is the compositor's (packages/choreo-gallery/realm/design/choreo-composition.md, Phase C3),
30
- * lifted whole because it was already right:
31
- *
32
- * source = in + (film − start) × rate
33
- * window = for, or (out − in) / rate, or the rest of the beat
34
- *
35
- * Before `start` the clip is absent; inside its window it is active and
36
- * its source time is the number above; past it the end policy applies —
37
- * `remove` (the default) takes it off, `hold` keeps the last sample on
38
- * screen, `freeze` keeps it on screen and writes nothing further. Every
39
- * state is re-derived from the film time on every frame, never discovered
40
- * by playing from zero — which is what lets an exact film scrub into the
41
- * middle of a clip and find it where it should be.
42
- *
43
- * A clip may outlive its beat: the window is measured on the film's
44
- * clock, so a still that starts late in one shot can cross the seam into
45
- * the next. That is the clip overlap an editor means.
46
- */
47
- /**
48
- * A LOOK HELD ON A CLIP — the first adjustment that belongs to something
49
- * other than the picture.
50
- *
51
- * `f.picture.Look` reaches the WebGL page's own grade; a clip is a DOM
52
- * element and has never had one, so a video inset over a graded picture
53
- * was ungraded and there was no way to say otherwise. These are the
54
- * operations a browser can do to an element without a texture, composed
55
- * in the order the film's own shader composes them (saturate, contrast,
56
- * brightness, then sepia and hue) — because filter order is not
57
- * commutative and the two should agree.
58
- *
59
- * What this is NOT is the whole filter stack: curves, secondaries and a
60
- * LUT need the pixels, which needs the clip through a texture. This is
61
- * the half a browser gives for free.
62
- */
63
- /** how long a clip's window is, in film seconds */
64
- function clipWindow(spec, beatStart, nextStart) {
65
- if (spec.for !== void 0) return spec.for;
66
- if (spec.out !== void 0) return (spec.out - (spec.in ?? 0)) / (spec.rate ?? 1);
67
- return Math.max(0, nextStart - beatStart - (spec.at ?? 0));
68
- }
69
- /**
70
- * The clip on screen at a film time, if any: the most recent beat at or
71
- * before `index` whose clip's window holds the time, or whose end policy
72
- * keeps it on screen past its window. Older clips are never reached past
73
- * a newer one — a clip that was removed is gone, and one that is held is
74
- * the one on screen.
75
- */
76
- function resolveClip(beats, beatStart, film, index, total, lane = 0) {
77
- for (let i = Math.min(index, beats.length - 1); i >= 0; i -= 1) {
78
- const spec = laneOf(beats[i], lane);
79
- if (!spec) continue;
80
- const start = beatStart(i) + (spec.at ?? 0);
81
- if (film < start) continue;
82
- const next = i + 1 < beats.length ? beatStart(i + 1) : total;
83
- const window = clipWindow(spec, beatStart(i), next);
84
- const since = film - start;
85
- const rate = spec.rate ?? 1;
86
- const inPoint = spec.in ?? 0;
87
- if (since < window) return {
88
- index: i,
89
- since,
90
- source: inPoint + since * rate,
91
- spec,
92
- state: "active"
93
- };
94
- const end = spec.end ?? "remove";
95
- if (end === "remove") return null;
96
- return {
97
- index: i,
98
- since,
99
- source: end === "hold" ? inPoint + window * rate : null,
100
- spec,
101
- state: end === "hold" ? "held" : "frozen"
102
- };
103
- }
104
- return null;
105
- }
106
- /** a beat's clip on one lane; a clip with no lane takes its own position */
107
- function laneOf(beat, lane) {
108
- const list = beat.clips;
109
- if (!list?.length) return;
110
- for (let i = 0; i < list.length; i += 1) if ((list[i].lane ?? i) === lane) return list[i];
111
- }
112
- /** every lane any beat up to `index` puts a clip on, in order */
113
- function clipLanes(beats, index) {
114
- const seen = /* @__PURE__ */ new Set();
115
- for (let i = Math.min(index, beats.length - 1); i >= 0; i -= 1) beats[i].clips?.forEach((spec, n) => seen.add(spec.lane ?? n));
116
- return [...seen].sort((a, b) => a - b);
117
- }
118
- /**
119
- * EVERY CLIP ON SCREEN AT A FILM TIME, one per lane, in lane order — which
120
- * is also paint order, so a score decides what sits over what by which
121
- * lane it puts a thing on.
122
- *
123
- * A beat used to carry one clip and a second on the same shot overwrote
124
- * the first without saying so. Lanes are the smallest thing that fixes
125
- * that without inventing a track model: the resolver below is the old
126
- * one, run once per lane.
127
- */
128
- function resolveClips(beats, beatStart, film, index, total) {
129
- const out = [];
130
- for (const lane of clipLanes(beats, index)) {
131
- const found = resolveClip(beats, beatStart, film, index, total, lane);
132
- if (found) out.push(found);
133
- }
134
- return out;
135
- }
136
- //#endregion
137
- //#region ../choreo/src/film/math.ts
138
- /** the small arithmetic every film does: the same four lines, once */
139
- var RAD = Math.PI / 180;
140
- var lerp = (a, b, t) => a + (b - a) * t;
141
- var clamp01 = (v) => Math.max(0, Math.min(1, v));
142
- /** smoothstep on [0,1] */
143
- var smooth = (t) => {
144
- const k = clamp01(t);
145
- return k * k * (3 - 2 * k);
146
- };
147
- var hex = (h) => [
148
- parseInt(h.slice(1, 3), 16) / 255,
149
- parseInt(h.slice(3, 5), 16) / 255,
150
- parseInt(h.slice(5, 7), 16) / 255
151
- ];
152
- /** relative luminance of a #rrggbb, 0..1 */
153
- var luminance = (h) => {
154
- if (!/^#[0-9a-f]{6}$/i.test(h)) return 1;
155
- const [r, g, b] = hex(h);
156
- return .2126 * r + .7152 * g + .0722 * b;
157
- };
158
- /** a #rrggbb at an alpha, as CSS */
159
- var rgba = (h, a) => {
160
- if (!/^#[0-9a-f]{6}$/i.test(h)) return h;
161
- const [r, g, b] = hex(h);
162
- return `rgba(${Math.round(r * 255)}, ${Math.round(g * 255)}, ${Math.round(b * 255)}, ${a})`;
163
- };
164
- var mmss = (s) => {
165
- return `${Math.floor(Math.max(0, s) / 60)}:${String(Math.floor(Math.max(0, s) % 60)).padStart(2, "0")}`;
166
- };
167
- /** the shortest way round a circle, radians */
168
- var wrapAngle = (d) => {
169
- let k = d;
170
- while (k > Math.PI) k -= Math.PI * 2;
171
- while (k < -Math.PI) k += Math.PI * 2;
172
- return k;
173
- };
174
- //#endregion
175
- //#region ../choreo/src/film/schedule.ts
176
- /**
177
- * THE SCHEDULE — everything a film derives from its shot list before a
178
- * single frame is drawn, as pure functions of the table.
179
- *
180
- * A film is its cue table and its camera path: when a beat starts, what
181
- * fires when, where the lens is at every waypoint, how the chapters are
182
- * cut. These were getters on the engine; lifting them here changes no
183
- * number — the engine calls them — and lets the same arithmetic be run
184
- * headless, in Node, to write the golden fixtures the graph refactor is
185
- * measured against (`scripts/film-fixtures.mjs`,
186
- * `tests/unit/film-schedule-test.ts`).
187
- *
188
- * Nothing in here reads the clock, the page or the DOM. Everything is a
189
- * function of `Beat[]` and a few numbers, which is the property the
190
- * fixtures pin.
191
- */
192
- /** one tick of the shot list, in seconds: a beat's `ticks` × this is its length */
193
- var TICK = 2;
194
- /** the whole film, in seconds, leads included */
195
- function totalSecs(beats) {
196
- return beats.reduce((n, b, i) => n + b.ticks + (i === 0 ? 0 : b.lead ?? 0), 0) * 2;
197
- }
198
- /** film seconds before a beat's nominal head, leads included */
199
- function secsBefore(beats, index) {
200
- return beats.slice(0, index).reduce((n, b, i) => n + b.ticks + (i === 0 ? 0 : b.lead ?? 0), 0) * 2;
201
- }
202
- /**
203
- * FILM SECONDS AT WHICH A BEAT'S OWN SHOT BEGINS. The cues fire one tick
204
- * after the beat table says, because the pose-in-force seed occupies the
205
- * spline's first slot — so the windows the exact clock derives carry the
206
- * same offset, and the picture and the type agree with the cut film to
207
- * the frame. This is the single most load-bearing constant in the film.
208
- */
209
- function beatStart(beats, i) {
210
- if (i <= 0) return 0;
211
- return secsBefore(beats, i) + (beats[i].lead ?? 0) * 2 + 2;
212
- }
213
- /** one cue per beat (and an `air` cue ahead of a led beat), as delays into the score */
214
- function cues(beats) {
215
- let t = 0;
216
- const out = [];
217
- beats.forEach((b, i) => {
218
- const lead = i === 0 ? 0 : b.lead ?? 0;
219
- if (lead > 0) out.push({
220
- action: "air",
221
- delay: t + 2,
222
- index: String(i)
223
- });
224
- const at = t + lead * 2;
225
- t = at + b.ticks * 2;
226
- out.push({
227
- action: "beat",
228
- delay: at === 0 ? 0 : at + 2,
229
- index: String(i)
230
- });
231
- });
232
- return out;
233
- }
234
- /** the first beat of each chapter, in whole-film indices */
235
- function chapterHeads(beats) {
236
- const heads = [];
237
- beats.forEach((b, i) => {
238
- if (heads.length === 0 || beats[heads[heads.length - 1]].ch !== b.ch) heads.push(i);
239
- });
240
- return heads;
241
- }
242
- /** the chapter menu's rows: head index, numeral, title, shot count, running time */
243
- function contents(beats, chapters) {
244
- const heads = chapterHeads(beats);
245
- return heads.map((head, i) => {
246
- const nextHead = heads[i + 1] ?? beats.length;
247
- const ch = chapters[beats[head].ch] ?? chapters[0];
248
- return {
249
- head,
250
- n: ch.n,
251
- secs: beats.slice(head, nextHead).reduce((t, b) => t + b.ticks, 0) * 2,
252
- shots: nextHead - head,
253
- title: ch.title
254
- };
255
- });
256
- }
257
- /** the seam a beat is entered through, or none when it neither cuts nor names one */
258
- function joinInto(beats, i, defaultJoin) {
259
- const beat = beats[i];
260
- return beat.cut || beat.join ? beat.join ?? defaultJoin : null;
261
- }
262
- /**
263
- * WHERE A SHOT ENDS — the beat's authored tail, floored so it always reads
264
- * as a move, and clamped so it never travels past the pose the next shot
265
- * begins on. Shared by the path (which lerps its waypoints across it) and
266
- * by the launch: a cut hands the chaser this shot's own speed.
267
- *
268
- * A shot must move on screen, not on paper: frame-differencing a capture
269
- * of the cut showed authored tails of a few degrees reading as stills, so
270
- * a tail is a DIRECTION and a floor, not a distance — what the beat asks
271
- * for is honoured, and anything slower than a real slow move is stretched
272
- * up to one. The floors are per second. And it never travels past where
273
- * the next shot begins: at a cut that jumped BACKWARDS into the successor.
274
- */
275
- function tailFor(beats, bi) {
276
- const b = beats[bi];
277
- const drift = b.toCam ?? {
278
- ...b.cam,
279
- dolly: b.cam.dolly * 1.05,
280
- pitch: b.cam.pitch + 1.4,
281
- yaw: b.cam.yaw + 6
282
- };
283
- const next = beats[bi + 1];
284
- const secs = b.ticks * 2;
285
- const sgn = (d) => d < 0 ? -1 : 1;
286
- const stretch = (key, floor) => {
287
- const head = b.cam[key] ?? 0;
288
- const d = (drift[key] ?? 0) - head;
289
- const nose = next ? (next.cam[key] ?? 0) - head : 0;
290
- const way = d !== 0 ? sgn(d) : nose !== 0 ? sgn(nose) : 1;
291
- const reach = !next ? Infinity : sgn(nose) === way && nose !== 0 ? Math.abs(nose) : 0;
292
- return head + way * Math.min(Math.max(Math.abs(d), floor), Math.max(Math.abs(d), reach));
293
- };
294
- const to = {
295
- ...drift,
296
- dolly: stretch("dolly", b.cam.dolly * Math.min(.22, .022 * secs)),
297
- ox: stretch("ox", Math.min(.07, .008 * secs)),
298
- pitch: stretch("pitch", Math.min(6, .45 * secs)),
299
- yaw: stretch("yaw", Math.min(22, 2 * secs))
300
- };
301
- return {
302
- dolly: to.dolly,
303
- fx: to.fx ?? b.cam.fx ?? 0,
304
- fz: to.fz ?? b.cam.fz ?? 0,
305
- lookY: to.lookY,
306
- ox: to.ox,
307
- pitch: to.pitch,
308
- yaw: to.yaw
309
- };
310
- }
311
- /** one waypoint of the camera spline, in the terms `c.Camera3D @through` takes */
312
- /**
313
- * THE PATH. Every beat contributes `ticks` waypoints, lerped from its
314
- * head to its tail, so waypoint count is screen time; a led beat first
315
- * contributes `lead` waypoints travelling in from the previous tail; a
316
- * `cut` beat splices the spline. `recut` marks the first beat as a splice
317
- * too — a cut film re-cut from a chapter's head has nothing behind it.
318
- */
319
- function waypoints(beats, recut = false) {
320
- const pts = [];
321
- for (const [bi, b] of beats.entries()) {
322
- const splice = b.cut === true || bi === 0 && recut;
323
- const to = tailFor(beats, bi);
324
- const lead = bi === 0 ? 0 : b.lead ?? 0;
325
- const prev = pts[pts.length - 1];
326
- if (lead > 0 && prev) for (let k = 1; k <= lead; k++) {
327
- const f = k / (lead + 1);
328
- pts.push({
329
- dolly: lerp(prev.dolly, b.cam.dolly, f),
330
- look: {
331
- x: lerp(prev.look.x, b.cam.fx ?? 0, f),
332
- y: lerp(prev.look.y, b.cam.lookY, f),
333
- z: lerp(prev.look.z, b.cam.fz ?? 0, f)
334
- },
335
- pitch: lerp(prev.pitch, b.cam.pitch, f),
336
- x: lerp(prev.x, b.cam.ox ?? 0, f),
337
- y: 0,
338
- yaw: lerp(prev.yaw, b.cam.yaw, f)
339
- });
340
- }
341
- for (let k = 0; k < b.ticks; k++) {
342
- const f = b.ticks === 1 ? 0 : k / (b.ticks - 1);
343
- pts.push({
344
- cut: splice && k === 0 ? true : void 0,
345
- dolly: lerp(b.cam.dolly, to.dolly, f),
346
- look: {
347
- x: lerp(b.cam.fx ?? 0, to.fx ?? 0, f),
348
- y: lerp(b.cam.lookY, to.lookY, f),
349
- z: lerp(b.cam.fz ?? 0, to.fz ?? 0, f)
350
- },
351
- pitch: lerp(b.cam.pitch, to.pitch, f),
352
- x: lerp(b.cam.ox ?? 0, to.ox ?? 0, f),
353
- y: 0,
354
- yaw: lerp(b.cam.yaw, to.yaw, f)
355
- });
356
- }
357
- }
358
- return pts;
359
- }
360
- /**
361
- * THE GOLDEN SCHEDULE: one object per film, everything above at once, in
362
- * the shape the fixtures are written in. A refactor of the engine that
363
- * changes any number in here has changed the film.
364
- */
365
- function schedule(beats, chapters, defaultJoin) {
366
- return {
367
- beats: beats.map((b, i) => ({
368
- ch: b.ch,
369
- id: b.id,
370
- join: joinInto(beats, i, defaultJoin),
371
- lead: i === 0 ? 0 : b.lead ?? 0,
372
- secsBefore: secsBefore(beats, i),
373
- start: beatStart(beats, i),
374
- ticks: b.ticks
375
- })),
376
- contents: contents(beats, chapters),
377
- cues: cues(beats),
378
- total: totalSecs(beats),
379
- waypoints: waypoints(beats)
380
- };
381
- }
382
- //#endregion
383
- //#region ../choreo/src/film/seam.ts
384
- /**
385
- * THE SEAM'S GATE: hold the cut until its still can be PAINTED.
386
- *
387
- * Every join that carries the outgoing frame carries it as a JPEG data
388
- * URL in the DOM, and a full-resolution JPEG handed to the DOM is not
389
- * paintable on the frame it is handed over — the browser decodes it off
390
- * the main thread. The lens, meanwhile, moves on that frame. So the seam
391
- * used to play backwards: three frames of the incoming shot at 60 Hz,
392
- * then the still of the outgoing one, then the transition over it.
393
- *
394
- * The gate decodes the frame into the memory cache first and holds the
395
- * whole cut — the lens snap, the overlay, an iris's projection — until
396
- * it is ready; the DOM image then paints from that decode on the frame
397
- * it is inserted. The outgoing shot stays live and moving for those few
398
- * milliseconds, which nobody can see: a cut two frames late reads as a
399
- * cut, a cut that shows the wrong picture does not.
400
- *
401
- * Three rules the film depends on:
402
- *
403
- * - **Nothing to decode, nothing to wait for.** A seam with no still (a
404
- * cut, a flash, a dissolve the page held in its own glass) runs
405
- * SYNCHRONOUSLY, on the caller's stack, exactly as it always did.
406
- * - **One seam at a time.** A cut that arrives while an earlier one is
407
- * still waiting retires it; the earlier one never runs.
408
- * - **No seam waits for ever.** A decode that never comes back (a
409
- * truncated capture, a browser without `HTMLImageElement.decode`) is
410
- * given `cap` milliseconds and then the cut goes anyway.
411
- */
412
- var Seam = class {
413
- /** which cut is in hand; a later one retires an earlier one */
414
- turn = 0;
415
- /**
416
- * Hold `cut` until `still` can be painted, or run it now if there is
417
- * no still. `cap` is the longest the film will wait on a decode.
418
- */
419
- hold(still, cut, cap = 120) {
420
- if (!still) {
421
- cut();
422
- return;
423
- }
424
- const mine = this.turn += 1;
425
- let ran = false;
426
- const go = () => {
427
- if (ran || mine !== this.turn) return;
428
- ran = true;
429
- cut();
430
- };
431
- const pre = new Image();
432
- pre.src = still;
433
- setTimeout(go, cap);
434
- const decoding = pre.decode?.();
435
- if (decoding) decoding.then(go, go);
436
- else go();
437
- }
438
- };
439
- //#endregion
440
- //#region ../choreo/src/film/film.gts
441
- /**
442
- * `<Film>` — a headless cutting room, in which a 3D scene takes the place
443
- * of the video track.
444
- *
445
- * The model is an editor: one clock, a shot list dropped onto it, and
446
- * components that read the clock — the lens, the joins, the type, the
447
- * voice, the timeline. The film is a pure function of that clock, which
448
- * is why a beat can be widened and the type, the voice, the transport
449
- * and the readout all re-time together. The picture is whatever the film
450
- * hands in through the `Picture` port; two reference films hand in a
451
- * WebGL page in an iframe (packages/choreo-gallery/realm/design/film-construct.md).
452
- *
453
- * THE CAMERA. The score authors the shot and the lens CHASES it: one
454
- * critically-damped stage here, the page's own chase as the second, so
455
- * two integrators in series bound the jerk. That is what gives the films
456
- * their hand-held quality, and it is exactly what forfeits seeking — so
457
- * a skip here is an edit, never a seek: the score is re-cut from a
458
- * chapter's head and replays.
459
- *
460
- * WHAT THE POSE ARGS MEAN (an orbit rig):
461
- * yaw → the orbit's azimuth, degrees
462
- * pitch → elevation, degrees
463
- * dolly → magnification: 1 fits the subject, bigger is tighter
464
- * lookY → how far up the subject the lens aims, from the rig's mid-height
465
- * fx, fz → where on the ground the lens aims
466
- * ox → an off-centre frustum, fractions of the frame
467
- */
468
- /**
469
- * ONE TICK IS TWO SECONDS, and every beat is a whole number of them.
470
- * `@through` splits its clock evenly between waypoints, so a beat buys
471
- * the screen time it wants by contributing that many waypoints to the
472
- * path — and a beat that wants to HOLD contributes the same pose several
473
- * times over, which a Catmull-Rom spline comes smoothly to rest on.
474
- */
475
- /**
476
- * `?awake` keeps the loops running in a document that reports itself
477
- * hidden — an embedded preview pane, a headless runner — where the idle
478
- * gate would otherwise (correctly) stop the film dead. Tooling only.
479
- */
480
- var AWAKE = new URLSearchParams(window.location.search).has("awake");
481
- /** how much each mood brightens the frame, for judging the ink against it */
482
- var GRADE_LUM = {
483
- amber: 1.08,
484
- chalk: 1.27,
485
- ink: 1,
486
- iron: .9,
487
- plate: 1.09
488
- };
489
- /** what the blocks are given: the handle, and the vocabulary a film's score is written in */
490
- /** the row a film stands on before it has any: the door, with nothing behind it */ var EMPTY_BEAT = {
491
- cam: {
492
- dolly: 1,
493
- lookY: 0,
494
- pitch: 0,
495
- yaw: 0
496
- },
497
- ch: 0,
498
- id: "",
499
- mode: "clear",
500
- ticks: 1
501
- };
502
- var EMPTY_CHAPTER = {
503
- n: "",
504
- title: ""
505
- };
506
- /**
507
- * How long a render may wait for the scene to arrive and for the score's
508
- * second pass to land (`openTheDoor`). Frames rather than milliseconds,
509
- * because what it is waiting for is a render.
510
- */
511
- var BOOT_FRAMES = 240;
512
- /** one lane's clip, as the template needs it */
513
- /** which lane a resolved clip sits on: its own, or its position in the beat */ function laneOfClip(beat, clip) {
514
- const at = (beat.clips ?? []).indexOf(clip.spec);
515
- return clip.spec.lane ?? (at >= 0 ? at : 0);
516
- }
517
- /** the rows are rebuilt every frame; only a real change should re-render */
518
- function sameRows(a, b) {
519
- if (a.length !== b.length) return false;
520
- return a.every((row, i) => {
521
- const other = b[i];
522
- return row.key === other.key && row.state === other.state && row.freeze === other.freeze && row.src === other.src;
523
- });
524
- }
525
- var Film = class Film extends Component {
526
- static {
527
- decorateFieldV2(this.prototype, "graph", [tracked], function() {
528
- return null;
529
- });
530
- }
531
- #graph = (initializeDeferredDecorator(this, "graph"), void 0);
532
- /**
533
- * THE GRAPH, compiled. A film written as `f.Spine` / `f.Chapter` /
534
- * `f.Shot` in the default block compiles to the same rows a table would
535
- * hand in (`graph/compile.ts`); the table arguments, when given, win.
536
- */
537
- takeGraph = (graph) => {
538
- this.graph = graph;
539
- };
540
- /**
541
- * THE PICTURE, as it registered itself from the `<:picture>` block: the
542
- * page, its files, how it is seated, its rig, its moods. Nothing here
543
- * is about the edit, which is why none of it is an argument any more.
544
- */
545
- static {
546
- decorateFieldV2(this.prototype, "picture", [tracked], function() {
547
- return null;
548
- });
549
- }
550
- #picture = (initializeDeferredDecorator(this, "picture"), void 0);
551
- takePicture = (spec) => {
552
- this.picture = spec;
553
- };
554
- get assets() {
555
- return this.picture?.assets ?? "";
556
- }
557
- get standing() {
558
- return this.picture?.standing ?? 0;
559
- }
560
- get frameTitle() {
561
- return this.picture?.title ?? this.args.name;
562
- }
563
- /**
564
- * THE WHOLE SHOT LIST, and there is only one place it comes from: the
565
- * score in the default block, compiled. The film used to take a
566
- * `Beat[]` as an argument as well, which is how both films were fed
567
- * until Phase 2 migrated them; the table path is gone now that nothing
568
- * hands one in, and the graph is the only way to write a film.
569
- */
570
- get all() {
571
- return this.graph?.beats ?? [];
572
- }
573
- get chapters() {
574
- return this.graph?.chapters ?? [];
575
- }
576
- get voSecs() {
577
- return this.graph?.voSecs ?? {};
578
- }
579
- get voGain() {
580
- return this.args.voGain ?? this.graph?.voGain ?? {};
581
- }
582
- get grades() {
583
- return this.picture?.grades ?? {};
584
- }
585
- get lookFx() {
586
- return this.picture?.lookFx ?? {};
587
- }
588
- get lutAmount() {
589
- return this.picture?.lutAmount ?? .52;
590
- }
591
- get defaultJoin() {
592
- return this.args.join ?? this.graph?.defaultJoin ?? "dip";
593
- }
594
- /**
595
- * WHERE THIS SEAM IS PLAYED. A seam the PICTURE declares by name is
596
- * played in its glass with that name; the three the film knows how to
597
- * mix are played there under the film's own law; everything else is an
598
- * overlay in this document. The name is the only thing that decides,
599
- * which is what makes the vocabulary open: a picture that declares
600
- * `ridged-burn` gets `<f.Join @presentation="ridged-burn" />` for free,
601
- * and the film learns nothing about it but its length.
602
- */
603
- glassFor(join) {
604
- if (!this.film?.seam || !this.film.freeze) return null;
605
- if (this.picture?.seams?.includes(join)) return { name: join };
606
- return inGlass(join) ? {} : null;
607
- }
608
- /** how deep a seam goes when nothing says: the film's own default */
609
- get defaultOver() {
610
- return this.args.over ?? this.graph?.defaultOver ?? "picture";
611
- }
612
- /**
613
- * HOW DEEP THIS BEAT'S SEAM GOES. The row says, or the presentation
614
- * says, or the film does. A seam that covers the furniture is always a
615
- * seam with a still in the DOM: the picture's own glass is inside the
616
- * canvas and cannot cover anything outside it.
617
- */
618
- overOf(beat) {
619
- const join = beat.join ?? this.defaultJoin;
620
- return beat.over ?? this.presentations[join]?.over ?? this.defaultOver;
621
- }
622
- /** every seam the film can play: its own twelve, and any the score brought */
623
- get presentations() {
624
- const brought = this.graph?.presentations ?? {};
625
- const out = { ...PRESENTATIONS };
626
- for (const [name, p] of Object.entries(brought)) out[name] = {
627
- ...out[name],
628
- component: p.component ?? out[name]?.component,
629
- over: p.over ?? out[name]?.over,
630
- secs: p.secs,
631
- still: p.component ? true : out[name]?.still ?? false
632
- };
633
- return out;
634
- }
635
- /** seconds a seam holds the picture; nothing, for one the film does not know */
636
- secsOf(join) {
637
- return join ? this.presentations[join]?.secs ?? 0 : 0;
638
- }
639
- /** does the seam paint a still of the outgoing frame over the incoming one */
640
- holdsStill(join) {
641
- return !!join && !!this.presentations[join]?.still;
642
- }
643
- /** how much of a frosted city a film frame can carry */
644
- get cityGlass() {
645
- return this.picture?.cityGlass ?? .16;
646
- }
647
- /** the rig's mid-height: `lookY` is world height minus this */
648
- get rigMid() {
649
- return this.picture?.rigMid ?? 6.6;
650
- }
651
- /** how much the passing cloud thickens the air */
652
- get cloudHaze() {
653
- return this.picture?.cloudHaze ?? .09;
654
- }
655
- /** the face type in the world is set in; a film that names none of it
656
- * leaves the page's own */
657
- get worldFamily() {
658
- const w = this.picture?.worldType;
659
- return w ? w.family : "Archivo, \"Helvetica Neue\", Helvetica, Arial, sans-serif";
660
- }
661
- get worldWeight() {
662
- const w = this.picture?.worldType;
663
- return w ? w.weight : 800;
664
- }
665
- get worldTrack() {
666
- return this.picture?.worldType?.track ?? .02;
667
- }
668
- /** the editorial accent: the film's own, or the scene's palette by day */
669
- accentFor(dark, scene) {
670
- const a = this.picture?.accent ?? {
671
- dark: "#d9b44a",
672
- light: "#8f6b1f"
673
- };
674
- return dark ? a.dark : a.light ?? scene ?? "#8f6b1f";
675
- }
676
- /** the film's handles, as the door and the card are given them */
677
- get handle() {
678
- return {
679
- ...VOCABULARY,
680
- picture: this.picture?.adjustments ?? VOCABULARY.picture,
681
- beat: this.beat,
682
- begin: this.begin,
683
- chapter: this.chapter,
684
- cutTo: this.pick,
685
- preview: this.preview,
686
- ready: this.ready,
687
- renderAt: this.renderAt,
688
- restart: this.restart,
689
- runtime: this.runtime,
690
- seek: this.seek,
691
- toc: this.toc
692
- };
693
- }
694
- get exact() {
695
- return this.args.seek === "exact";
696
- }
697
- /** the master clock: seconds into the whole film (exact mode) */
698
- t = 0;
699
- /** a seek happened since the last fold: it lands instantly, seam re-made */
700
- jumped = false;
701
- /** the score region's context, read every frame for its current run */
702
- scoreCtx = null;
703
- scoreRun = null;
704
- /** the element the seam overlays live in, so their animations can be seeked */
705
- joinsEl;
706
- /** where the seam on screen began, film seconds, and how long it plays */
707
- seamAt = -1;
708
- seamLen = 0;
709
- /** the lead whose air has been applied ahead of its beat */
710
- airedFor = -1;
711
- /** where the sun was before this beat asked, for a deterministic walk */
712
- sunFrom = null;
713
- /**
714
- * THE CLIPS ON SCREEN, one row per lane, in lane order — which is also
715
- * paint order. There used to be one of these and a beat that declared
716
- * two clips kept the second one only.
717
- */
718
- static {
719
- decorateFieldV2(this.prototype, "clipRows", [tracked], function() {
720
- return [];
721
- });
722
- }
723
- #clipRows = (initializeDeferredDecorator(this, "clipRows"), void 0);
724
- /** the picture read back, per lane, for a freeze */ clipFreezes = /* @__PURE__ */ new Map();
725
- clipNow = /* @__PURE__ */ new Map();
726
- clipMedia = /* @__PURE__ */ new Map();
727
- clipEl = modifier((el, [lane]) => {
728
- this.clipMedia.set(lane, el);
729
- return () => {
730
- if (this.clipMedia.get(lane) === el) this.clipMedia.delete(lane);
731
- };
732
- });
733
- /**
734
- * THE CLIP, EVERY FRAME. Resolve what should be on screen at this film
735
- * time, land the DOM when that changes (a new window is a new element,
736
- * so the entrance replays), and drive the media: a playing video is
737
- * left to run and corrected when it drifts, a paused or scrubbed one
738
- * is seeked, a held one stands at its out point, a frozen one is left
739
- * alone. A freeze reads the picture back when its window opens — or,
740
- * on a seek into it, after the page has been stood at that moment.
741
- */
742
- clips(film, filmTime, jumped) {
743
- const found = resolveClips(this.all, (i) => this.beatStart(i), filmTime, this.absoluteIndex, this.totalSecs);
744
- const lanes = /* @__PURE__ */ new Set();
745
- const rows = [];
746
- for (const clip of found) {
747
- const lane = clip.spec.lane ?? laneOfClip(this.all[clip.index], clip);
748
- lanes.add(lane);
749
- const key = `${this.all[clip.index].id}:${lane}`;
750
- const was = this.clipNow.get(lane);
751
- this.clipNow.set(lane, clip);
752
- if (key !== (was ? `${this.all[was.index].id}:${lane}` : "")) {
753
- this.clipFreezes.delete(lane);
754
- if (clip.spec.kind === "freeze") {
755
- if (jumped || clip.since > .1) this.freezeClipAt(clip, filmTime);
756
- const shot = film.snapshot();
757
- if (shot.length > 64) this.clipFreezes.set(lane, shot);
758
- }
759
- }
760
- rows.push({
761
- freeze: this.clipFreezes.get(lane) ?? "",
762
- key,
763
- lane,
764
- spec: clip.spec,
765
- src: clip.spec.src ? `${this.assets}${clip.spec.src}` : "",
766
- state: clip.state
767
- });
768
- this.driveClip(lane, clip, was, jumped);
769
- }
770
- for (const lane of [...this.clipNow.keys()]) {
771
- if (lanes.has(lane)) continue;
772
- this.clipNow.delete(lane);
773
- this.clipFreezes.delete(lane);
774
- const gone = this.clipMedia.get(lane);
775
- if (gone instanceof HTMLVideoElement && gone.volume > .001) gone.volume = Math.max(0, gone.volume - .12);
776
- }
777
- if (!sameRows(this.clipRows, rows)) this.clipRows = rows;
778
- }
779
- /**
780
- * ONE LANE'S MEDIA, every frame. A playing video is left to run and
781
- * corrected when it drifts, a paused or scrubbed one is seeked, a held
782
- * one stands at its out point, a frozen one is left alone.
783
- *
784
- * A CLIP'S SOUND NEVER STEPS. The element arrives at zero and is eased
785
- * up to its level, and when its window closes it is eased back down
786
- * before the exit fade takes the element away — a source that starts or
787
- * stops at level is a click, whatever it is playing.
788
- */
789
- driveClip(lane, found, was, jumped) {
790
- const v = this.clipMedia.get(lane);
791
- if (found.spec.kind !== "video") {
792
- if (v instanceof HTMLVideoElement && v.volume > .001) v.volume = Math.max(0, v.volume - .12);
793
- return;
794
- }
795
- if (!(v instanceof HTMLVideoElement) || found.source === null) return;
796
- const level = Math.max(0, Math.min(1, found.spec.volume ?? 0));
797
- if (was?.index !== found.index) v.volume = 0;
798
- const closing = found.state !== "active" ? 0 : level;
799
- if (Math.abs(v.volume - closing) > .002) v.volume = v.volume + (closing - v.volume) * .28;
800
- const rate = found.spec.rate ?? 1;
801
- if (found.state === "active" && this.playing && this.scrubAt === null && !jumped) {
802
- if (v.paused) {
803
- v.currentTime = found.source;
804
- v.playbackRate = rate;
805
- v.play().catch(() => {});
806
- } else if (Math.abs(v.currentTime - found.source) > .2) v.currentTime = found.source;
807
- } else {
808
- if (!v.paused) v.pause();
809
- if (Math.abs(v.currentTime - found.source) > .04) v.currentTime = found.source;
810
- }
811
- }
812
- /**
813
- * THE PICTURE AT A MOMENT THAT HAS PASSED. A freeze seeked into needs
814
- * the frame from its window's head: the score is stood there for one
815
- * evaluation (the camera step reports the pose), the page is posed and
816
- * clocked to that moment, and the fold's own time is restored after
817
- * the read-back.
818
- */
819
- freezeClipAt(clip, filmTime) {
820
- const film = this.film;
821
- const run = this.scoreCtx?.run ?? null;
822
- if (!film || !run) return;
823
- const head = filmTime - clip.since;
824
- const b = this.all[clip.index];
825
- const local = Math.min(1, Math.max(0, (head - this.beatStart(clip.index)) / (b.ticks * 2)));
826
- run.pause();
827
- run.time = head;
828
- const g = this.goal;
829
- film.pose({
830
- az: g.yaw * RAD,
831
- el: g.pitch * RAD,
832
- fov: null,
833
- fx: g.fx ?? 0,
834
- fz: g.fz ?? 0,
835
- lookY: g.lookY,
836
- near: null,
837
- ox: g.ox ?? 0,
838
- snap: true,
839
- zoom: g.dolly
840
- });
841
- if (Array.isArray(b.build)) film.time(lerp(b.build[0], b.build[1], smooth(Math.min(1, local / (b.buildBy ?? 1)))));
842
- run.time = this.exact ? this.t : run.time;
843
- }
844
- /**
845
- * FILM SECONDS AT WHICH A BEAT'S OWN SHOT BEGINS. The cues fire one
846
- * tick after the beat table says, because the pose-in-force seed
847
- * occupies the spline's first slot — so the windows the exact clock
848
- * derives carry the same offset, and the picture and the type agree
849
- * with the cut film to the frame.
850
- */
851
- beatStart(i) {
852
- return beatStart(this.all, i);
853
- }
854
- /** the beat in force at a film time */
855
- indexAt(t) {
856
- let i = 0;
857
- while (i + 1 < this.all.length && this.beatStart(i + 1) <= t) i += 1;
858
- return i;
859
- }
860
- /** the score's region, held so its run can be driven by the clock */
861
- grabScore = modifier((_el, [ctx]) => {
862
- this.scoreCtx = ctx;
863
- return () => {
864
- if (this.scoreCtx === ctx) this.scoreCtx = null;
865
- };
866
- });
867
- joinsWrap = modifier((el) => {
868
- this.joinsEl = el;
869
- return () => {
870
- if (this.joinsEl === el) this.joinsEl = void 0;
871
- };
872
- });
873
- /** the score must stand even while paused: a paused exact film is a still */
874
- get scoreOn() {
875
- return this.playing || this.exact;
876
- }
877
- /**
878
- * SEEK. In exact mode the clock is set and the next fold lands there;
879
- * in cut mode the nearest shot's head is re-cut to, which is the only
880
- * honest seek a chased lens has.
881
- */
882
- seek = (seconds) => {
883
- if (!this.exact) {
884
- let i = 0;
885
- while (i + 1 < this.all.length && this.secsBefore(i + 1) <= seconds) i += 1;
886
- this.cutTo(i);
887
- return;
888
- }
889
- const total = this.totalSecs;
890
- this.t = Math.max(0, Math.min(total, seconds));
891
- this.jumped = true;
892
- this.menu = false;
893
- if (this.ended && this.t < total) {
894
- this.ended = false;
895
- this.idled = false;
896
- this.film?.idle(false);
897
- }
898
- if (this.booted && this.film) this.fold();
899
- };
900
- /**
901
- * A STILL AT A TIME, for a capture worker: pause, seek, run one frame
902
- * of the loop against a zero step, and give the page two frames to
903
- * draw it. The same code path as playing there — that is the claim.
904
- */ /** a frame is being rendered rather than scrubbed to: seams play */
905
- rendering = false;
906
- /**
907
- * A RENDER OPENS THE DOOR ITSELF.
908
- *
909
- * `?from=0` puts the title card up and leaves the film unbooted — and
910
- * an unbooted film HAS NO SCORE, because the whole `<Choreo>` sits
911
- * behind `{{#if this.booted}}` (the rig has to be an insertion before
912
- * the camera step has a subject). So `fold`'s `run.time = t` wrote to
913
- * a null run, `onCamera3D` never fired, `goal` stayed wherever the
914
- * load path's `snap` seated it, and exact mode copied that one pose
915
- * forward on every frame. Measured on towers, a stepped render swept
916
- * half a degree of yaw across 273 seconds where the compiled path
917
- * sweeps 380 — a whole render of a frozen lens, and `film-render.mjs`
918
- * hit it every time, because its default `--from` is 0.
919
- *
920
- * A render is a request for the film, not for the lobby. So it opens
921
- * the door itself, MUTED — nothing was clicked, and no browser hands
922
- * out audio for a gesture nobody made.
923
- */
924
- renderAt = async (seconds) => {
925
- if (!this.exact) {
926
- this.seek(seconds);
927
- return;
928
- }
929
- await this.openTheDoor();
930
- this.playing = false;
931
- this.rendering = true;
932
- try {
933
- this.seek(seconds);
934
- const now = performance.now();
935
- this.lastTick = now;
936
- this.tick(now);
937
- await new Promise((r) => requestAnimationFrame(() => r(void 0)));
938
- await new Promise((r) => requestAnimationFrame(() => r(void 0)));
939
- } finally {
940
- this.rendering = false;
941
- }
942
- };
943
- /**
944
- * THE DOOR, OPENED BY A SCRIPT. Two waits, and neither is optional.
945
- *
946
- * The handle publishes itself from the mount modifier, a frame before
947
- * the iframe's load seats the port — so a renderer that polls for
948
- * `__choreo[name]` can call in before there is a picture to begin
949
- * against, and `begin` would only pocket the choice. And `begin`
950
- * itself DEFERS the lap bump that buys the score region its second
951
- * render pass: until that pass lands the run does not exist, and a
952
- * seek against a missing run is exactly the frozen lens this avoids.
953
- *
954
- * Both waits are bounded. A render against a film that never arrives
955
- * degrades to the frame it would have drawn before, rather than
956
- * hanging the worker on a promise that will not settle.
957
- */
958
- async openTheDoor() {
959
- if (this.booted) return;
960
- const frame = () => new Promise((r) => requestAnimationFrame(() => r(void 0)));
961
- for (let i = 0; i < BOOT_FRAMES && !this.film; i += 1) await frame();
962
- if (!this.film) return;
963
- this.begin(false);
964
- for (let i = 0; i < BOOT_FRAMES && !this.scoreCtx?.run; i += 1) await frame();
965
- }
966
- /**
967
- * THE FOLD (exact mode): the beat in force is the one whose window
968
- * holds the clock; entering it — forward by playing, or by a seek in
969
- * either direction — applies it whole, because every beat asserts its
970
- * complete state and never inherits. A seek that lands inside a seam
971
- * re-makes the outgoing frame first, so the seam plays from the middle
972
- * exactly as it would have played into it.
973
- */
974
- fold() {
975
- if (!this.film) return;
976
- const t = this.t;
977
- const i = this.indexAt(t);
978
- const jumped = this.jumped;
979
- this.jumped = false;
980
- const next = this.all[i + 1];
981
- if (next && (next.lead ?? 0) > 0 && t >= this.secsBefore(i + 1) + 2 && this.airedFor !== i + 1) {
982
- this.airedFor = i + 1;
983
- this.applyAir(next, false);
984
- }
985
- if (i !== this.beatIndex || jumped) {
986
- const beat = this.all[i];
987
- const since = t - this.beatStart(i);
988
- if (jumped) {
989
- this.settleAir(i);
990
- this.airedFor = -1;
991
- this.freeze = "";
992
- }
993
- const join = beat.cut || beat.join ? beat.join ?? this.defaultJoin : null;
994
- const len = this.secsOf(join ?? void 0);
995
- this.seamAt = -1;
996
- this.seamLen = 0;
997
- /**
998
- * A SCRUB IS NOT A RENDER.
999
- *
1000
- * A jump into a seam's window stands at the seam's END: a seam is
1001
- * played forward over a still of the frame that was actually on
1002
- * screen, a scrub has no such frame, and re-making one bought a
1003
- * read-back per drag for a transition nobody asked to watch.
1004
- *
1005
- * But `renderAt` is a jump too, and a film rendered frame by frame
1006
- * is a jump on EVERY frame — so with that rule alone an exact film
1007
- * rendered deterministically had no seams in it at all. Which
1008
- * breaks the promise the mode is named for: the same frame whether
1009
- * you played there or rendered there. Measured on the reel, five of
1010
- * its twelve seams came out as hard cuts in the render and worked
1011
- * when played.
1012
- *
1013
- * So a render says so, and gets its seams.
1014
- */
1015
- const inSeam = !!join && since < len && i > 0 && (!jumped || this.rendering);
1016
- const frozen = false;
1017
- if (inSeam) {
1018
- this.seamAt = this.beatStart(i);
1019
- this.seamLen = len;
1020
- }
1021
- this.beatIndex = i;
1022
- this.applyBeat(beat, jumped, {
1023
- frozen,
1024
- seam: inSeam,
1025
- since
1026
- });
1027
- if (jumped) this.speakAt(beat, since);
1028
- }
1029
- if (this.seamAt >= 0) {
1030
- const ms = Math.max(0, (t - this.seamAt) * 1e3);
1031
- for (const a of this.joinsEl?.getAnimations({ subtree: true }) ?? []) {
1032
- a.pause();
1033
- a.currentTime = ms;
1034
- }
1035
- if (t - this.seamAt > this.seamLen + .3 || t < this.seamAt) {
1036
- this.seamAt = -1;
1037
- this.joinKind = "";
1038
- }
1039
- }
1040
- const run = this.scoreCtx?.run ?? null;
1041
- if (run !== this.scoreRun) {
1042
- this.scoreRun = run;
1043
- run?.pause();
1044
- }
1045
- if (run) run.time = t;
1046
- if (t >= this.totalSecs && !this.ended) this.end();
1047
- }
1048
- /** a line, from the middle: what a seek owes the voice */
1049
- speakAt(beat, since) {
1050
- this.hush();
1051
- if (!beat.vo || !this.sound) {
1052
- this.film?.duck(1);
1053
- return;
1054
- }
1055
- if (since < (this.voSecs[beat.id] ?? beat.ticks * 2 * .66) - .3) {
1056
- this.film?.voice(this.voSrc(beat), this.voGain[beat.id] ?? 1, since);
1057
- this.primeNext(beat);
1058
- } else this.film?.duck(1);
1059
- }
1060
- /**
1061
- * THE SEAM'S GATE (`film/seam.ts`). A join's still is a JPEG the
1062
- * browser has not decoded yet on the frame it is handed over, and the
1063
- * lens moves on that frame — so every cut used to show three frames of
1064
- * the incoming shot before the outgoing one was on screen. The gate
1065
- * decodes first and holds the cut; a seam with no still runs at once.
1066
- */
1067
- seam = new Seam();
1068
- /**
1069
- * THE SEAM, EXACT. Every join that carries the outgoing frame carries it
1070
- * as a still in the DOM — the page's own dissolves run on the page's
1071
- * clock and cannot be stood at a time — and the whip, which is the
1072
- * chaser's, becomes a cut. The overlay is then driven by the fold.
1073
- */
1074
- seamExact(film, beat, join, seam) {
1075
- const wants = seam ? seam.seam : true;
1076
- const glass = wants ? this.glassFor(join) : null;
1077
- if (glass) {
1078
- if (this.glassKind !== join) {
1079
- film.freeze?.(true);
1080
- this.glassKind = join;
1081
- this.glassName = glass.name ?? "";
1082
- this.glassLen = this.secsOf(join);
1083
- this.glassColor = beat.dipTo ?? "#0d0905";
1084
- this.freeze = "";
1085
- this.joinKind = "";
1086
- }
1087
- if (beat.cut) this.snap(beat.cam);
1088
- return;
1089
- }
1090
- const stills = this.holdsStill(join);
1091
- let held = this.freeze;
1092
- if (wants && stills && !seam?.frozen) {
1093
- const shot = film.snapshot();
1094
- held = shot.length > 64 ? shot : "";
1095
- }
1096
- if (!wants) {
1097
- held = "";
1098
- this.freeze = "";
1099
- }
1100
- if (held) film.freeze?.(true);
1101
- this.seam.hold(held, () => {
1102
- film.freeze?.(false);
1103
- this.seamPlay(film, beat, join, wants, held);
1104
- });
1105
- }
1106
- /** the seam itself: the still on screen, the lens on the incoming shot */
1107
- seamPlay(film, beat, join, wants, held) {
1108
- this.freeze = held;
1109
- if (beat.cut || join === "wipe" || join === "whip") this.snap(beat.cam);
1110
- if (!wants) return;
1111
- const deep = this.overOf(beat);
1112
- switch (join) {
1113
- case "cut":
1114
- case "whip": break;
1115
- case "dip":
1116
- this.dipColor = beat.dipTo ?? "#0d0905";
1117
- this.play("dip", deep);
1118
- break;
1119
- case "flash":
1120
- this.play("flash", deep);
1121
- break;
1122
- case "sweep":
1123
- this.lightSweep(film, beat);
1124
- break;
1125
- case "defocus":
1126
- if (this.freeze) this.play("defocus", deep);
1127
- break;
1128
- case "iris": {
1129
- if (!this.freeze) break;
1130
- const v = film.view();
1131
- const p = beat.to ? film.project(...beat.to) : void 0;
1132
- const cx = p ? Math.max(12, Math.min(88, p.x / v.w * 100)) : 50;
1133
- const cy = p ? Math.max(12, Math.min(88, p.y / v.h * 100)) : 50;
1134
- this.irisAt = `--ix:${cx.toFixed(1)}%;--iy:${cy.toFixed(1)}%`;
1135
- this.play("iris", deep);
1136
- break;
1137
- }
1138
- default: if (this.freeze) this.play(join, deep);
1139
- }
1140
- }
1141
- /** the last cue has run: hold the pose, settle the music, offer the card */
1142
- end() {
1143
- this.playing = false;
1144
- this.ended = true;
1145
- this.film?.voiceStop(1e3);
1146
- if (this.film?.outro) this.film.outro(4200);
1147
- else this.film?.duck(.35);
1148
- }
1149
- /** the beat on screen; the score names it, the front layer renders it */
1150
- static {
1151
- decorateFieldV2(this.prototype, "beatIndex", [tracked], function() {
1152
- return 0;
1153
- });
1154
- }
1155
- #beatIndex = (initializeDeferredDecorator(this, "beatIndex"), void 0);
1156
- static {
1157
- decorateFieldV2(this.prototype, "playing", [tracked], function() {
1158
- return true;
1159
- });
1160
- }
1161
- #playing = (initializeDeferredDecorator(this, "playing"), void 0);
1162
- static {
1163
- decorateFieldV2(this.prototype, "lap", [tracked], function() {
1164
- return 0;
1165
- });
1166
- }
1167
- #lap = (initializeDeferredDecorator(this, "lap"), void 0);
1168
- /** bumping this edits the score, which is how a finite film loops */
1169
- static {
1170
- decorateFieldV2(this.prototype, "booted", [tracked], function() {
1171
- return false;
1172
- });
1173
- }
1174
- #booted = (initializeDeferredDecorator(this, "booted"), void 0);
1175
- static {
1176
- decorateFieldV2(this.prototype, "joinKind", [tracked], function() {
1177
- return "";
1178
- });
1179
- }
1180
- #joinKind = (initializeDeferredDecorator(this, "joinKind"), void 0);
1181
- /** which seam is playing, and a key that replays it: bumped on every cut */
1182
- static {
1183
- decorateFieldV2(this.prototype, "joinStamp", [tracked], function() {
1184
- return 0;
1185
- });
1186
- }
1187
- #joinStamp = (initializeDeferredDecorator(this, "joinStamp"), void 0);
1188
- static {
1189
- decorateFieldV2(this.prototype, "joinOver", [tracked], function() {
1190
- return false;
1191
- });
1192
- }
1193
- #joinOver = (initializeDeferredDecorator(this, "joinOver"), void 0);
1194
- /** the seam on screen covers the furniture, not only the picture */
1195
- overTimer = 0;
1196
- /**
1197
- * THE SEAM IN THE GLASS. Its kind, how long it has and the colour it
1198
- * passes through — and `reveal`, how much of the incoming frame is NOT
1199
- * covered by it, which is the film's other half: the type, the stamp
1200
- * and the inserts wear it, so a caption is uncovered by the same
1201
- * gesture that uncovers the picture.
1202
- */
1203
- glassKind = "";
1204
- /** the picture's own name for it, when the picture declared this seam */
1205
- glassName = "";
1206
- glassLen = 0;
1207
- glassColor = "";
1208
- reveal = 1;
1209
- /** the captured outgoing frame every freeze-based join plays with */
1210
- static {
1211
- decorateFieldV2(this.prototype, "freeze", [tracked], function() {
1212
- return "";
1213
- });
1214
- }
1215
- #freeze = (initializeDeferredDecorator(this, "freeze"), void 0);
1216
- static {
1217
- decorateFieldV2(this.prototype, "dipColor", [tracked], function() {
1218
- return "#0d0905";
1219
- });
1220
- }
1221
- #dipColor = (initializeDeferredDecorator(this, "dipColor"), void 0);
1222
- /** the 'dip' join's veil colour */
1223
- static {
1224
- decorateFieldV2(this.prototype, "irisAt", [tracked], function() {
1225
- return "";
1226
- });
1227
- }
1228
- #irisAt = (initializeDeferredDecorator(this, "irisAt"), void 0);
1229
- /** where the iris closes to, as inline custom properties */
1230
- static {
1231
- decorateFieldV2(this.prototype, "fault", [tracked], function() {
1232
- return "";
1233
- });
1234
- }
1235
- #fault = (initializeDeferredDecorator(this, "fault"), void 0);
1236
- /** dev beacon: the first uncaught error, worn on the sleeve */
1237
- /**
1238
- * THE SEAM, DRIVEN. Half of it is the picture, which is handed two
1239
- * numbers and composites them in light; half is the furniture, which
1240
- * wears what is left over. Both come from one progress, and that
1241
- * progress is a function of the clock — so a seek through a seam needs
1242
- * nothing stood at a time, which is what the DOM overlay always did.
1243
- */ /** take a driven seam off: the picture back to live, the furniture whole */
1244
- endSeam() {
1245
- if (!this.glassKind) return;
1246
- this.glassKind = "";
1247
- this.glassName = "";
1248
- this.reveal = 1;
1249
- this.film?.seam?.(null);
1250
- }
1251
- driveSeam(film, t) {
1252
- if (!this.glassKind) return;
1253
- const len = Math.max(.001, this.glassLen);
1254
- const p = this.exact ? this.seamAt >= 0 ? (t - this.seamAt) / len : 1 : (performance.now() - this.beatAt) / 1e3 / len;
1255
- if (p >= 1) {
1256
- this.endSeam();
1257
- return;
1258
- }
1259
- if (this.glassName) {
1260
- film.seam?.({
1261
- mix: 0,
1262
- name: this.glassName,
1263
- p,
1264
- veil: 0
1265
- });
1266
- this.reveal = p;
1267
- return;
1268
- }
1269
- const { mix, veil } = seamShape(this.glassKind, p);
1270
- film.seam?.({
1271
- color: this.glassColor,
1272
- mix,
1273
- p,
1274
- veil
1275
- });
1276
- this.reveal = 1 - Math.max(mix, veil);
1277
- }
1278
- /**
1279
- * Run a seam's overlay: a new element per cut, from its own first
1280
- * frame. A seam played `everything` is lifted above the type, the
1281
- * insert and the clip for as long as it runs, and put back after — an
1282
- * empty layer left standing over the furniture would swallow the next
1283
- * shot's pointer events, and there is no event for a CSS animation the
1284
- * film did not start.
1285
- */
1286
- play(kind, over = "picture") {
1287
- this.joinKind = kind;
1288
- this.joinStamp += 1;
1289
- this.joinOver = over === "everything";
1290
- window.clearTimeout(this.overTimer);
1291
- if (this.joinOver) {
1292
- const stamp = this.joinStamp;
1293
- this.overTimer = window.setTimeout(() => {
1294
- if (this.joinStamp === stamp) this.joinOver = false;
1295
- }, this.secsOf(kind) * 1e3 + 80);
1296
- }
1297
- }
1298
- /**
1299
- * THE ENDING. A film that laps back to its own first frame has no
1300
- * ending, and the coda earns one — so when the last cue has run, the
1301
- * run simply stops: the final pose holds, the music settles down a
1302
- * step, and a card offers the way back in. Replay is a choice the
1303
- * viewer makes, never something the clock does to them.
1304
- */
1305
- static {
1306
- decorateFieldV2(this.prototype, "ended", [tracked], function() {
1307
- return false;
1308
- });
1309
- }
1310
- #ended = (initializeDeferredDecorator(this, "ended"), void 0);
1311
- static {
1312
- decorateFieldV2(this.prototype, "yearNow", [tracked], function() {
1313
- return 1882;
1314
- });
1315
- }
1316
- #yearNow = (initializeDeferredDecorator(this, "yearNow"), void 0);
1317
- /**
1318
- * THE YEAR IS THE SUBJECT. A film whose whole argument is that one
1319
- * building took a hundred and forty-four years cannot leave the audience
1320
- * to infer the date from the state of the scaffolding: the year is on
1321
- * screen, and it is on a line from the first stone to the present, so
1322
- * 1954 is not a number but a POSITION — two thirds along a rule that
1323
- * still has a third to run.
1324
- */
1325
- yearSent = 0;
1326
- /** whether the announced year is currently seated in the scene */
1327
- stampOn = false;
1328
- /** the page has been told the picture is static — see the frame loop */
1329
- idled = false;
1330
- gradeSent = "";
1331
- rakeSent = NaN;
1332
- rakeDeg = 35;
1333
- /** which beat's lightning cue has fired (reset on every beat change) */
1334
- struck = "";
1335
- /** which of the beat's hours (Beat.hours) is in force; -1 between beats */
1336
- hourStep = -1;
1337
- /** the next grade goes to the glass without its ease (a still-join) */
1338
- gradeSnap = false;
1339
- /** the last stock sent to the glass, by name; undefined before the first */
1340
- lutSent = void 0;
1341
- /** the last hour a beat named, so the night can be known without naming it again */
1342
- hourNow = 0;
1343
- /** the scrub track's width in px, kept by a ResizeObserver */
1344
- trackW = 0;
1345
- trackRO;
1346
- /** the chapter fractions the bar is drawn with, computed once */
1347
- segFr;
1348
- trackWrap = modifier((el) => {
1349
- this.trackW = el.clientWidth;
1350
- this.trackRO = new ResizeObserver(() => {
1351
- this.trackW = el.clientWidth;
1352
- });
1353
- this.trackRO.observe(el);
1354
- return () => {
1355
- this.trackRO?.disconnect();
1356
- this.trackRO = void 0;
1357
- };
1358
- });
1359
- /**
1360
- * THE KNOB IS THE FILL'S END. The bar is chapters with a gap between
1361
- * them, so the playhead's position is not linear in the playhead's
1362
- * value — a second formula for the knob drifts from the fill inside
1363
- * every segment. This is the one formula: the same fractions the fill
1364
- * uses, over the measured track, with the gaps added back.
1365
- */
1366
- headPx(prog) {
1367
- const segs = this.segFr ??= this.playbar.map((c) => {
1368
- const m = /--s0:([\d.]+);--sl:([\d.]+)/.exec(c.style);
1369
- return {
1370
- s0: +m[1],
1371
- sl: +m[2]
1372
- };
1373
- });
1374
- const gap = 2;
1375
- const usable = this.trackW - gap * (segs.length - 1);
1376
- let i = segs.findIndex((s, n) => prog < s.s0 + s.sl || n === segs.length - 1);
1377
- if (i < 0) i = segs.length - 1;
1378
- const s = segs[i];
1379
- const f = Math.max(0, Math.min(1, (prog - s.s0) / s.sl));
1380
- return (s.s0 + f * s.sl) * usable + i * gap;
1381
- }
1382
- /** which tower the lineup's metronome is on; -1 between lineups */
1383
- static {
1384
- decorateFieldV2(this.prototype, "cycleStep", [tracked], function() {
1385
- return -1;
1386
- });
1387
- }
1388
- #cycleStep = (initializeDeferredDecorator(this, "cycleStep"), void 0);
1389
- film;
1390
- /**
1391
- * THE FRAME'S DRESSING, WHERE THE PICTURE CAN TAKE IT.
1392
- *
1393
- * The wash and the cloud were CSS layers over the canvas, which meant a
1394
- * seam could not touch them: the picture dissolved and the paper behind
1395
- * the type snapped on the cut frame — two edits at once. A transition
1396
- * applies to what its own container renders, so they belong in the
1397
- * container, and the container is the picture's post pass (where the
1398
- * vignette, the grain, the split tone and the grade already live). A
1399
- * picture that offers `wash`/`cloud` gets them and the film stops
1400
- * drawing its own; one that does not keeps the CSS layers.
1401
- */
1402
- static {
1403
- decorateFieldV2(this.prototype, "washInGlass", [tracked], function() {
1404
- return false;
1405
- });
1406
- }
1407
- #washInGlass = (initializeDeferredDecorator(this, "washInGlass"), void 0);
1408
- static {
1409
- decorateFieldV2(this.prototype, "cloudInGlass", [tracked], function() {
1410
- return false;
1411
- });
1412
- }
1413
- #cloudInGlass = (initializeDeferredDecorator(this, "cloudInGlass"), void 0);
1414
- static {
1415
- decorateFieldV2(this.prototype, "dimInGlass", [tracked], function() {
1416
- return false;
1417
- });
1418
- }
1419
- #dimInGlass = (initializeDeferredDecorator(this, "dimInGlass"), void 0);
1420
- /** the palette's paper, as `wear` last read it, for the wash */ paperNow = "";
1421
- /** the last wash the picture was told about: mode and paper, told on change */
1422
- washSent = "";
1423
- frameEl;
1424
- lineEl;
1425
- dotEl;
1426
- tetherEl;
1427
- dim;
1428
- /** the dim's own value, chased rather than cut so it never snaps on */
1429
- dimNow = 0;
1430
- plateEl;
1431
- rowEls = [];
1432
- raf = 0;
1433
- lastTick = 0;
1434
- /** when the current beat was entered, for the clocks it owns */
1435
- beatAt = 0;
1436
- /** where the beat's sky word was planted, in world azimuth */
1437
- skyAz = 0;
1438
- /** the azimuth the mark word is planted on, behind the building */
1439
- markAz = 0;
1440
- /** a sideways nudge in world units, for a word too short to centre */
1441
- markOff = 0;
1442
- /** the height the word rises FROM — set when it actually arrives */
1443
- markFrom = 2.6;
1444
- markSeated = false;
1445
- /** the last pose actually pushed — the final smoothing stage */
1446
- sent;
1447
- /** the light in force, and the light the beat asked for */
1448
- sunNow = null;
1449
- sunGoal = null;
1450
- static {
1451
- decorateFieldV2(this.prototype, "idle", [tracked], function() {
1452
- return false;
1453
- });
1454
- }
1455
- #idle = (initializeDeferredDecorator(this, "idle"), void 0);
1456
- static {
1457
- decorateFieldV2(this.prototype, "scrubAt", [tracked], function() {
1458
- return null;
1459
- });
1460
- }
1461
- #scrubAt = (initializeDeferredDecorator(this, "scrubAt"), void 0);
1462
- /** the fraction the hand is holding while scrubbing, else null */
1463
- static {
1464
- decorateFieldV2(this.prototype, "hoverAt", [tracked], function() {
1465
- return null;
1466
- });
1467
- }
1468
- #hoverAt = (initializeDeferredDecorator(this, "hoverAt"), void 0);
1469
- /** the fraction under a resting pointer on the bar, else null */
1470
- static {
1471
- decorateFieldV2(this.prototype, "vol", [tracked], function() {
1472
- return Film.savedVol();
1473
- });
1474
- }
1475
- #vol = (initializeDeferredDecorator(this, "vol"), void 0);
1476
- /** the master fader, 0..1, remembered across visits */
1477
- static {
1478
- decorateFieldV2(this.prototype, "burst", [tracked], function() {
1479
- return "";
1480
- });
1481
- }
1482
- #burst = (initializeDeferredDecorator(this, "burst"), void 0);
1483
- /** the centre-screen glyph that confirms a play or a pause */
1484
- static {
1485
- decorateFieldV2(this.prototype, "paused", [tracked], function() {
1486
- return false;
1487
- });
1488
- }
1489
- #paused = (initializeDeferredDecorator(this, "paused"), void 0);
1490
- /** the viewer's pause: the run, the page and the beat clock all hold */
1491
- pausedAt = 0;
1492
- /** the lap whose head beat the cut itself already applied */
1493
- primedLap = -1;
1494
- /** the bar's own element, for the slider's live value */
1495
- scrubEl;
1496
- shownPct = -1;
1497
- static {
1498
- decorateFieldV2(this.prototype, "clock", [tracked], function() {
1499
- return "0:00";
1500
- });
1501
- }
1502
- #clock = (initializeDeferredDecorator(this, "clock"), void 0);
1503
- static {
1504
- decorateFieldV2(this.prototype, "full", [tracked], function() {
1505
- return false;
1506
- });
1507
- }
1508
- #full = (initializeDeferredDecorator(this, "full"), void 0);
1509
- idleTimer = 0;
1510
- shownSec = -1;
1511
- /** the whole film, in seconds, leads included */
1512
- get totalSecs() {
1513
- return totalSecs(this.all);
1514
- }
1515
- secsBefore(index) {
1516
- return secsBefore(this.all, index);
1517
- }
1518
- /** one segment per chapter, sized and placed in whole-film fractions */
1519
- get playbar() {
1520
- const total = this.totalSecs;
1521
- return this.chapterHeads.map((head, i) => {
1522
- const end = this.chapterHeads[i + 1] ?? this.all.length;
1523
- const ch = this.chapters[this.all[head].ch] ?? this.chapters[0];
1524
- const s0 = this.secsBefore(head) / total;
1525
- const sl = (this.secsBefore(end) - this.secsBefore(head)) / total;
1526
- return {
1527
- head,
1528
- n: ch.n,
1529
- style: `flex:${sl};--s0:${s0.toFixed(5)};--sl:${sl.toFixed(5)}`,
1530
- title: ch.title
1531
- };
1532
- });
1533
- }
1534
- static mmss(s) {
1535
- return `${Math.floor(Math.max(0, s) / 60)}:${String(Math.floor(Math.max(0, s) % 60)).padStart(2, "0")}`;
1536
- }
1537
- get duration() {
1538
- return Film.mmss(this.totalSecs);
1539
- }
1540
- /**
1541
- * What the hand is pointing at — resting on the bar or dragging it:
1542
- * the chapter under it and the time, on two lines, the way every
1543
- * player's hover bubble does. A drag wins over a hover.
1544
- */
1545
- get tip() {
1546
- const at = this.scrubAt ?? this.hoverAt;
1547
- if (at === null) return null;
1548
- const want = at * this.totalSecs;
1549
- let i = 0;
1550
- while (i + 1 < this.all.length && this.secsBefore(i + 1) <= want) i += 1;
1551
- const ch = this.chapters[this.all[i].ch] ?? this.chapters[0];
1552
- return {
1553
- ch: `${ch.n} ${ch.title}`,
1554
- t: Film.mmss(want)
1555
- };
1556
- }
1557
- /** rolling AND not held by the viewer: what the play button shows */
1558
- get live() {
1559
- return this.playing && !this.paused;
1560
- }
1561
- /** the fader's setting, as it was left last time — one setting for
1562
- * every film, the way a viewer's volume is theirs and not the film's */
1563
- static savedVol() {
1564
- try {
1565
- const v = Number(window.localStorage.getItem("cf-vol"));
1566
- return Number.isFinite(v) && v > 0 && v <= 1 ? v : 1;
1567
- } catch {
1568
- return 1;
1569
- }
1570
- }
1571
- /** sound on, and the fader up: the speaker glyph's three states */
1572
- get loud() {
1573
- if (!this.sound || this.vol <= 0) return "off";
1574
- return this.vol < .5 ? "low" : "high";
1575
- }
1576
- get volStyle() {
1577
- return `--cf-vol:${this.vol.toFixed(2)}`;
1578
- }
1579
- /**
1580
- * THE FADER. Dragging it up while muted unmutes, because that is what
1581
- * the hand means; the value is the page's master gain, under the mix
1582
- * and the mute, and it is remembered for the next visit.
1583
- */
1584
- fade = (e) => {
1585
- const v = Number(e.target.value);
1586
- this.vol = v;
1587
- this.film?.volume?.(v);
1588
- try {
1589
- window.localStorage.setItem("cf-vol", v.toFixed(2));
1590
- } catch {}
1591
- if (v > 0 && !this.sound) this.hear();
1592
- };
1593
- /** the glyph that flashes in the middle of the picture on play/pause */
1594
- pop(kind) {
1595
- if (this.gate || this.ended) return;
1596
- this.burst = "";
1597
- requestAnimationFrame(() => {
1598
- this.burst = kind;
1599
- });
1600
- }
1601
- burstDone = () => {
1602
- this.burst = "";
1603
- };
1604
- /**
1605
- * A tap on the picture is play/pause and two taps are full screen —
1606
- * the grammar of every player since the first one. The controls, the
1607
- * door, the end card, the rail and the menu keep their own clicks.
1608
- */
1609
- static onGlass(e) {
1610
- return !e.target?.closest("button, a, input, .cf-player, .cf-menu, .cf-gate, .cf-rail");
1611
- }
1612
- tap = (e) => {
1613
- if (this.gate || this.ended || this.menu || !Film.onGlass(e)) return;
1614
- this.toggle();
1615
- };
1616
- tapTwice = (e) => {
1617
- if (this.gate || this.ended || this.menu || !Film.onGlass(e)) return;
1618
- this.screen();
1619
- };
1620
- /** the bar's own element, for the slider's live value */
1621
- slider = modifier((el) => {
1622
- this.scrubEl = el;
1623
- return () => {
1624
- this.scrubEl = void 0;
1625
- };
1626
- });
1627
- wake = () => {
1628
- if (this.idle) this.idle = false;
1629
- window.clearTimeout(this.idleTimer);
1630
- this.idleTimer = window.setTimeout(() => {
1631
- if (this.scrubAt === null && !this.menu && this.live) this.idle = true;
1632
- }, 2600);
1633
- };
1634
- scrubFrom(e) {
1635
- const el = e.currentTarget.getBoundingClientRect();
1636
- return Math.max(0, Math.min(1, (e.clientX - el.left) / el.width));
1637
- }
1638
- /**
1639
- * The bubble and the ghost fill follow the pointer, not the playhead:
1640
- * the bubble's x in pixels (held off the edges so it never clips) and
1641
- * the hover fraction, both as properties, both without a re-render.
1642
- */
1643
- point(e) {
1644
- const r = e.currentTarget.getBoundingClientRect();
1645
- const x = Math.max(0, Math.min(r.width, e.clientX - r.left));
1646
- const at = r.width > 0 ? x / r.width : 0;
1647
- const pad = Math.min(56, r.width / 2);
1648
- const st = this.pageEl?.style;
1649
- st?.setProperty("--cf-tipx", Math.max(pad, Math.min(r.width - pad, x)).toFixed(1));
1650
- st?.setProperty("--cf-hov", at.toFixed(5));
1651
- return at;
1652
- }
1653
- scrubDown = (event) => {
1654
- const e = event;
1655
- e.currentTarget.setPointerCapture(e.pointerId);
1656
- this.scrubAt = this.point(e);
1657
- };
1658
- scrubMove = (event) => {
1659
- const e = event;
1660
- const at = this.point(e);
1661
- if (this.scrubAt !== null) {
1662
- this.scrubAt = at;
1663
- if (this.exact) this.seek(this.barSecs(this.scrubAt));
1664
- } else if (e.pointerType !== "touch") this.hoverAt = at;
1665
- };
1666
- scrubLeave = () => {
1667
- this.hoverAt = null;
1668
- this.pageEl?.style.setProperty("--cf-hov", "0");
1669
- };
1670
- /**
1671
- * THE PLAYHEAD SNAPS TO A SHOT, because this film cannot seek: the
1672
- * lens is an integrator and a run dropped into its own middle arrives
1673
- * with the wrong velocity (packages/choreo-gallery/realm/docs/choreo-splices.md). So the drag reads
1674
- * as time, the label names the shot under the hand, and the release
1675
- * RE-CUTS from that shot's head — which is the same edit the chapter
1676
- * buttons and the arrow keys make.
1677
- */ /**
1678
- * A HAND ON THE BAR NEVER LANDS ON THE CARD. The bar's right edge is
1679
- * the film's last second, and a click there — or a drag that ran off
1680
- * the end — seeks to the total, which is the ending itself: the card
1681
- * came up under a hand that wanted the last shot. The bar stops two
1682
- * seconds short; the ending is reached by playing to it.
1683
- */
1684
- barSecs(at) {
1685
- return Math.min(at * this.totalSecs, Math.max(0, this.totalSecs - 2));
1686
- }
1687
- scrubUp = (event) => {
1688
- const e = event;
1689
- const at = this.scrubAt ?? this.scrubFrom(e);
1690
- this.scrubAt = null;
1691
- this.hoverAt = null;
1692
- this.pageEl?.style.setProperty("--cf-hov", "0");
1693
- this.seek(this.barSecs(at));
1694
- };
1695
- screen = () => {
1696
- const el = this.pageEl ?? this.frameEl;
1697
- if (!el) return;
1698
- if (document.fullscreenElement) document.exitFullscreen();
1699
- else el.requestFullscreen?.();
1700
- };
1701
- fullChange = () => {
1702
- this.full = !!document.fullscreenElement;
1703
- };
1704
- /**
1705
- * THE POINTER MOVES THE WORLD A LITTLE.
1706
- *
1707
- * A film that cannot be touched is a video, and the whole argument
1708
- * here is that this is a scene. So the cursor gets a few degrees of
1709
- * lean: the lens takes a fraction of it (real parallax — the building
1710
- * and the hills separate) and the type planes take more of it in the
1711
- * other direction, each layer by its own depth. It is small enough to
1712
- * be felt rather than played with, and damped, so it never fights the
1713
- * shot the score is composing.
1714
- */
1715
- lean = {
1716
- x: 0,
1717
- y: 0
1718
- };
1719
- leanTo = {
1720
- x: 0,
1721
- y: 0
1722
- };
1723
- aim = (e) => {
1724
- const el = this.pageEl ?? this.frameEl;
1725
- if (!el) return;
1726
- const r = el.getBoundingClientRect();
1727
- this.leanTo.x = Math.max(-1, Math.min(1, (e.clientX - r.left) / r.width * 2 - 1));
1728
- this.leanTo.y = Math.max(-1, Math.min(1, (e.clientY - r.top) / r.height * 2 - 1));
1729
- };
1730
- /** the haze the beat asked for — the weather drift breathes around it */
1731
- hazeBase = null;
1732
- goal = { ...(this.all[0] ?? EMPTY_BEAT).cam };
1733
- mid = { ...(this.all[0] ?? EMPTY_BEAT).cam };
1734
- midV = {
1735
- dolly: 0,
1736
- fx: 0,
1737
- fz: 0,
1738
- lookY: 0,
1739
- ox: 0,
1740
- pitch: 0,
1741
- yaw: 0
1742
- };
1743
- now = { ...(this.all[0] ?? EMPTY_BEAT).cam };
1744
- nowV = {
1745
- dolly: 0,
1746
- fx: 0,
1747
- fz: 0,
1748
- lookY: 0,
1749
- ox: 0,
1750
- pitch: 0,
1751
- yaw: 0
1752
- };
1753
- /** the world-layer plane's own fade, so a chapter's type breathes in */
1754
- skyOn = 0;
1755
- /**
1756
- * THE SKY WORD'S OPACITY AS ACTUALLY SENT — not the ramp above.
1757
- *
1758
- * `skyOn` only says a word is WANTED; what the word is actually wearing
1759
- * is that ramp times the beat's own in and out, and the out takes it to
1760
- * nothing by 68% of the beat. When the next beat had no word the film
1761
- * used to hand the picture `skyOn` to fade from — a number still sitting
1762
- * near 1 — so a word that had been gone for seconds snapped back to 92%
1763
- * on the frame after the cut and faded out a second time, across the
1764
- * seam. Fade from what the word is wearing instead.
1765
- */
1766
- skyA = 0;
1767
- /** the time of day the furniture is currently dressed for */
1768
- wearing = "";
1769
- /** the cast-shadow offset the type is currently wearing, px */
1770
- shadow = {
1771
- x: 0,
1772
- y: 0
1773
- };
1774
- pageEl;
1775
- /**
1776
- * WHERE THE CUT STARTS — the arrow keys' one piece of state.
1777
- *
1778
- * A film this long is unwatchable without chapter skip, and seeking a run
1779
- * that carries an integrator is not honest (the chaser's pose depends on
1780
- * its history, which is the price Drift's doctrine names out loud). So
1781
- * skipping RE-CUTS instead of seeking: the beat list is sliced here, the
1782
- * path and the cues are both derived from that list, and the score the
1783
- * region sees is a different, shorter film. An edited timeline replays
1784
- * from its own head, which is exactly the behaviour wanted — and it is
1785
- * the same move Sylva's lap makes to loop.
1786
- *
1787
- * `?from=N` seeds it, so a shot can be linked to.
1788
- */ /**
1789
- * The deep link's shot, as asked (`?from=N`). Clamped against the
1790
- * rows when READ, never when set: with the score a graph the rows
1791
- * arrive a frame after construction, and clamping here against zero
1792
- * rows once made every deep link −1.
1793
- */
1794
- static {
1795
- decorateFieldV2(this.prototype, "fromRaw", [tracked], function() {
1796
- return (() => {
1797
- const q = new URLSearchParams(window.location.search).get("from");
1798
- const n = q ? Number(q) : 0;
1799
- return Number.isFinite(n) && n > 0 ? n : 0;
1800
- })();
1801
- });
1802
- }
1803
- #fromRaw = (initializeDeferredDecorator(this, "fromRaw"), void 0);
1804
- get from() {
1805
- return Math.min(this.fromRaw, Math.max(0, this.all.length - 1));
1806
- }
1807
- set from(index) {
1808
- this.fromRaw = index;
1809
- }
1810
- /** sound is off until asked for: nobody's first second should be音 */
1811
- static {
1812
- decorateFieldV2(this.prototype, "sound", [tracked], function() {
1813
- return false;
1814
- });
1815
- }
1816
- #sound = (initializeDeferredDecorator(this, "sound"), void 0);
1817
- static {
1818
- decorateFieldV2(this.prototype, "gate", [tracked], function() {
1819
- return false;
1820
- });
1821
- }
1822
- #gate = (initializeDeferredDecorator(this, "gate"), void 0);
1823
- /**
1824
- * THE GATE. This film is narrated, and narration behind a mute button
1825
- * is a film shown with the projector lamp off — so the front door asks.
1826
- * One held frame, one choice, and the choice IS the user gesture that
1827
- * autoplay policy wants anyway: the browser unlocks audio on the same
1828
- * click that starts the picture. Museums have worked this way forever.
1829
- *
1830
- * `?from` skips it (an authoring link wants the shot, not the lobby)
1831
- * and so does embedding.
1832
- */
1833
- static {
1834
- decorateFieldV2(this.prototype, "ready", [tracked], function() {
1835
- return false;
1836
- });
1837
- }
1838
- #ready = (initializeDeferredDecorator(this, "ready"), void 0);
1839
- /** the scene is loaded and seated behind the gate — the door can open */
1840
- static {
1841
- decorateFieldV2(this.prototype, "rolling", [tracked], function() {
1842
- return false;
1843
- });
1844
- }
1845
- #rolling = (initializeDeferredDecorator(this, "rolling"), void 0);
1846
- /**
1847
- * One pass BEHIND `booted`, on purpose. A region's first render
1848
- * collects no score, so anything standing in it on that pass — the
1849
- * first beat's type, a head photo — was never inserted and never
1850
- * animates. Content mounts on this flag instead, which flips a tick
1851
- * later: the same render that gives the score its first real pass
1852
- * hands the front layer a genuine insertion, and the opening beat's
1853
- * type is DELIVERED like every other beat's instead of being found
1854
- * already on the glass.
1855
- */
1856
- get beats() {
1857
- return this.from > 0 && !this.exact ? this.all.slice(this.from) : this.all;
1858
- }
1859
- /** the first beat of each chapter, in whole-film indices */
1860
- get chapterHeads() {
1861
- return chapterHeads(this.all);
1862
- }
1863
- /** where we are in the WHOLE film, not the current cut */
1864
- get absoluteIndex() {
1865
- return this.from + this.beatIndex;
1866
- }
1867
- get beat() {
1868
- return this.beats[this.beatIndex] ?? this.beats[0] ?? EMPTY_BEAT;
1869
- }
1870
- /**
1871
- * CAPTIONS. `?subs` seeds them on; CC toggles them.
1872
- *
1873
- * They are the narration, printed. That makes them two things at once
1874
- * and both are wanted: an accessibility track for anyone who cannot or
1875
- * would rather not hear the voice, and — before any voice exists — the
1876
- * only reliable way to find out that a line is two seconds too long for
1877
- * the shot it is sitting on.
1878
- */
1879
- static {
1880
- decorateFieldV2(this.prototype, "subsOn", [tracked], function() {
1881
- return new URLSearchParams(window.location.search).has("subs");
1882
- });
1883
- }
1884
- #subsOn = (initializeDeferredDecorator(this, "subsOn"), void 0);
1885
- get subs() {
1886
- return this.subsOn;
1887
- }
1888
- cc = () => {
1889
- this.subsOn = !this.subsOn;
1890
- };
1891
- get grade() {
1892
- return this.beat.grade ?? this.chapter.grade ?? "amber";
1893
- }
1894
- get chapter() {
1895
- return this.chapters[this.beat.ch] ?? this.chapters[0] ?? EMPTY_CHAPTER;
1896
- }
1897
- get embed() {
1898
- return (this.args.embed ?? false) || /[?&]embed\b/.test(window.location.search);
1899
- }
1900
- /** `?debug` puts the build stamp in the corner — see `@build` */
1901
- get debug() {
1902
- return /[?&]debug\b/.test(window.location.search);
1903
- }
1904
- get build() {
1905
- return this.args.build ?? "";
1906
- }
1907
- get srcdoc() {
1908
- return this.picture?.srcdoc;
1909
- }
1910
- get src() {
1911
- const src = this.picture?.src;
1912
- return src ? `${src}?host${AWAKE ? "&awake" : ""}` : "";
1913
- }
1914
- get photoSrc() {
1915
- return `${this.assets}${this.beat.photo?.src ?? ""}`;
1916
- }
1917
- /** a photograph that failed to load is no photograph — drop the plate */
1918
- lost = /* @__PURE__ */ new Set();
1919
- static {
1920
- decorateFieldV2(this.prototype, "lostStamp", [tracked], function() {
1921
- return 0;
1922
- });
1923
- }
1924
- #lostStamp = (initializeDeferredDecorator(this, "lostStamp"), void 0);
1925
- get hasPhoto() {
1926
- const p = this.beat.photo;
1927
- return !!p && this.lostStamp >= 0 && !this.lost.has(p.src);
1928
- }
1929
- missing = () => {
1930
- const p = this.beat.photo;
1931
- if (p) {
1932
- this.lost.add(p.src);
1933
- this.lostStamp += 1;
1934
- }
1935
- };
1936
- /** a fresh name per lap: an edited score replays, a restarted one fights */
1937
- get filmName() {
1938
- return `film-${this.lap}`;
1939
- }
1940
- /**
1941
- * HOW MUCH HAND, in seconds — see `@settle`.
1942
- *
1943
- * The default is a QUARTER OF THE GAP BETWEEN WAYPOINTS, because that
1944
- * is the only number that means the same thing to two different films.
1945
- * A window is smoothing relative to the spacing of the kinks it
1946
- * smooths: a quarter takes the curvature step off every pose change
1947
- * and is far too little to stop the lens visiting the poses. Both
1948
- * films that ship here spend two seconds a waypoint, so both get half
1949
- * a second; a score that cuts four times as fast gets a quarter of
1950
- * that without being asked, and half a second imposed on it would have
1951
- * flattened its shots.
1952
- *
1953
- * Capped at half a second so a very slow film does not drift, and
1954
- * floored at nothing: `@settle={{0}}` gives the raw spline.
1955
- */
1956
- get settle() {
1957
- const said = this.args.settle;
1958
- if (said !== void 0) return said;
1959
- const points = this.path.length;
1960
- return points > 0 ? Math.min(.5, this.filmSeconds / points / 4) : 0;
1961
- }
1962
- /**
1963
- * The pose the score OPENS on, declared to the region — without it the
1964
- * first spline segment travels in from the library's default rig, a
1965
- * two-second bounce every cold boot wore before its first real frame.
1966
- */
1967
- get openPose() {
1968
- const c = (this.beats[0] ?? EMPTY_BEAT).cam;
1969
- return {
1970
- dolly: c.dolly,
1971
- look: {
1972
- x: c.fx ?? 0,
1973
- y: c.lookY,
1974
- z: c.fz ?? 0
1975
- },
1976
- pitch: c.pitch,
1977
- x: c.ox ?? 0,
1978
- y: 0,
1979
- yaw: c.yaw
1980
- };
1981
- }
1982
- get filmSeconds() {
1983
- return this.beats.reduce((n, b, i) => n + b.ticks + (i === 0 ? 0 : b.lead ?? 0), 0) * 2;
1984
- }
1985
- /**
1986
- * THE WHOLE FILM IS ONE CAMERA STEP.
1987
- *
1988
- * Every beat contributes `ticks` waypoints: a moving beat is sampled from
1989
- * its head pose to its tail, a holding beat repeats its pose, and the
1990
- * spline runs through the lot on one clock. So the shot list is a path
1991
- * rather than a playlist, the camera crosses each mark with velocity
1992
- * instead of arriving at it, and a hold is genuinely still without
1993
- * anything having stopped.
1994
- */ /**
1995
- * WHERE A SHOT ENDS — the beat's authored tail, floored so it always
1996
- * reads as a move, and clamped so it never travels past the pose the
1997
- * next shot begins on. Shared by the path (which lerps its waypoints
1998
- * across it) and by the LAUNCH: a cut hands the chaser this shot's own
1999
- * speed, so the lens is already travelling when the cut lands rather
2000
- * than easing up from a standstill.
2001
- */
2002
- tailFor(bi) {
2003
- return tailFor(this.beats, bi);
2004
- }
2005
- get path() {
2006
- return waypoints(this.beats, this.from > 0);
2007
- }
2008
- /**
2009
- * Each beat's entrance, as a delay into the one camera step — offset
2010
- * one tick, because the pose-in-force seed occupies the spline's first
2011
- * slot: waypoint k is crossed at (k+1) slots, not k. The old uniform
2012
- * skew was invisible (camera and cues equally late, a constant the
2013
- * chaser's own lag swallowed); a SPLICE made it audible — the snap
2014
- * fired two seconds before the goal stepped, and the chaser spent the
2015
- * gap dragged back toward the old shot. Cue 0 stays at zero: the boot
2016
- * and every re-cut apply their head beat by hand, and its immediate
2017
- * re-fire has always been the first cue's job.
2018
- */
2019
- get cues() {
2020
- return cues(this.beats);
2021
- }
2022
- /**
2023
- * THE ATTACHMENTS: every window the score drives another region over.
2024
- * The plate for every beat, the insert for a beat with a photograph,
2025
- * the clip for a beat with one — each on the score's own clock, so a
2026
- * seek stands all of them where playing there would. The regions are
2027
- * keyed per beat and mounted for their windows; `remove` past a window
2028
- * leaves them to that; a clip may outlive its beat and holds.
2029
- */
2030
- get windows() {
2031
- const beats = this.beats;
2032
- const heads = cues(beats).filter((c) => c.action === "beat");
2033
- const total = totalSecs(beats);
2034
- const out = [];
2035
- heads.forEach((c, k) => {
2036
- const b = beats[k];
2037
- const start = c.delay;
2038
- const next = heads[k + 1]?.delay ?? total;
2039
- out.push({
2040
- end: "remove",
2041
- length: next - start,
2042
- region: "plate",
2043
- start
2044
- });
2045
- if (b.photo) out.push({
2046
- end: "remove",
2047
- length: next - start,
2048
- region: "insert",
2049
- start
2050
- });
2051
- b.clips?.forEach((c) => {
2052
- out.push({
2053
- end: "hold",
2054
- length: clipWindow(c, start, next),
2055
- region: "clip",
2056
- start: start + (c.at ?? 0)
2057
- });
2058
- });
2059
- });
2060
- return out;
2061
- }
2062
- /**
2063
- * THE TRANSPORT — a broadcast bar, not a thermometer. One segment per
2064
- * chapter, sized by the chapter's actual running time, filled by
2065
- * WHOLE-film progress (the old bar measured the current cut, so a
2066
- * skip made it lie), and clickable: the segments are the same re-cut
2067
- * the menu and the arrows perform.
2068
- */ /**
2069
- * ONE TIMELINE, NOT TWO.
2070
- *
2071
- * The corner carried a strip of chapter segments beside a rule of years:
2072
- * two timelines of the same film, at two different scales, in two
2073
- * different shapes, six inches apart. So the chapters are ON the years
2074
- * now — each one a dot at the year it opens, so the gap between THE SITE
2075
- * and SILENCE is fifty-four years wide and the gap between the last two
2076
- * is a decade, which is the fact the film is about. The dots are still
2077
- * the doors into the chapters.
2078
- */
2079
- get transport() {
2080
- const here = this.absoluteIndex;
2081
- const clock = this.args.clock;
2082
- const total = this.totalSecs;
2083
- return this.contents.map((c) => {
2084
- const b = this.all[c.head];
2085
- let at;
2086
- let label;
2087
- if (clock) {
2088
- const y = clock.yearAt(Array.isArray(b.build) ? b.build[0] : b.build ?? 0);
2089
- const [a, z] = clock.span;
2090
- at = Math.max(0, Math.min(1, (y - a) / (z - a)));
2091
- label = String(Math.floor(y));
2092
- } else at = this.secsBefore(c.head) / total;
2093
- return {
2094
- at,
2095
- done: here >= c.head,
2096
- head: c.head,
2097
- here: c.here,
2098
- label,
2099
- n: c.n,
2100
- title: c.title
2101
- };
2102
- });
2103
- }
2104
- /** the ends of the rule, when it is a clock */
2105
- get railLabels() {
2106
- const span = this.args.clock?.span;
2107
- return span ? [String(span[0]), String(span[1])] : void 0;
2108
- }
2109
- /** the rail is on unless the film says otherwise */
2110
- get railOn() {
2111
- return this.args.rail ?? true;
2112
- }
2113
- get railReadout() {
2114
- return this.args.clock ? String(this.yearNow) : "";
2115
- }
2116
- /**
2117
- * WHEN EACH CUE LANDS — paced against the voice, not the clock.
2118
- *
2119
- * The phrases used to arrive on four fixed delays, which meant a
2120
- * fourteen-second beat finished its type with five seconds of dead air
2121
- * and a short one crowded the read. So the cues spread themselves: the
2122
- * first lands early, the last lands as the measured line is finishing
2123
- * (`VO_SECS`), and a beat with no recording paces against two thirds of
2124
- * its own length, which is where a read for it would sit anyway.
2125
- */ /**
2126
- * THE TRANSITION FIRST, THEN THE TYPE. A setting that arrives while the
2127
- * seam is still on screen is read against two pictures at once. The
2128
- * block waits for the incoming join to finish and a breath more, then
2129
- * enters in its order: kicker, glyph, reading — and the lines after.
2130
- */
2131
- get typeAt() {
2132
- if (this.overOf(this.beat) === "everything") return [
2133
- 0,
2134
- .12,
2135
- .36
2136
- ];
2137
- const wait = this.secsOf(this.beat.join ?? this.defaultJoin) + .45;
2138
- return [
2139
- wait,
2140
- wait + .12,
2141
- wait + .36
2142
- ];
2143
- }
2144
- get sayAt() {
2145
- const b = this.beat;
2146
- const n = b.says?.length ?? 0;
2147
- const dur = b.ticks * 2;
2148
- const vo = this.voSecs[b.id];
2149
- const first = this.typeAt[2] + .3;
2150
- const last = Math.max(first + .8, Math.min(vo ? vo - .4 : dur * .66, dur - 1.4));
2151
- return [
2152
- 0,
2153
- 1,
2154
- 2,
2155
- 3
2156
- ].map((i) => n <= 1 ? first : first + Math.min(i, n - 1) * (last - first) / (n - 1));
2157
- }
2158
- /**
2159
- * Thai and Khmer are not kanji: their ascenders, vowel marks and
2160
- * subscripts stand far outside a CJK-tuned glyph box, so at the kanji
2161
- * size they collide with the kicker above and the reading below. Tall
2162
- * scripts take a reduced setting with real leading.
2163
- */ /**
2164
- * HOW MANY CHARACTERS HAVE TO FIT. A vertical setting is as tall as
2165
- * its string, and 一国一城令 is five glyphs — at the plate's own size
2166
- * that column runs off the top of the frame and the film crops its
2167
- * own title. The count goes to CSS, which sizes the column to the
2168
- * height available rather than to a number somebody typed once while
2169
- * looking at a three-glyph word.
2170
- */
2171
- get glyphFit() {
2172
- return `--cf-glyphs:${Math.max(2, [...this.beat.kanji ?? ""].length)}`;
2173
- }
2174
- get glyphTone() {
2175
- const k = this.beat.kanji ?? "";
2176
- if (/[A-Za-zÀ-ÿ]/.test(k)) return "is-latin";
2177
- return /[฀-๿ក-៿]/.test(k) ? "is-tall" : "";
2178
- }
2179
- /** the lineup's current kanji; empty between lineups */
2180
- get stamp() {
2181
- const c = this.beat.cycle;
2182
- return c && this.cycleStep >= 0 ? c[this.cycleStep]?.kanji ?? "" : "";
2183
- }
2184
- /**
2185
- * THE POSTER IS AN EVENING (by default). The film proper opens in the
2186
- * morning, so the door stands in the last of the light with a long hard
2187
- * shadow across the empty half of the frame; pressing it turns the day
2188
- * over. The film may dress its door otherwise (`@poster`).
2189
- */
2190
- dressPoster(film) {
2191
- const p = this.picture?.poster ?? {};
2192
- film.theme(p.theme ?? 2, true);
2193
- const l = p.light ?? {
2194
- az: -105,
2195
- el: 21
2196
- };
2197
- film.light({
2198
- az: l.az * RAD,
2199
- el: l.el * RAD
2200
- });
2201
- }
2202
- announced = false;
2203
- mount = modifier((el) => {
2204
- const rows = this.all.length;
2205
- if (!this.announced) {
2206
- this.announced = true;
2207
- console.info(`film ${this.args.name}: ${this.args.build ?? ""}`);
2208
- }
2209
- this.frameEl = el;
2210
- this.pageEl = el.closest(".cf-page");
2211
- window.addEventListener("keydown", this.key);
2212
- window.addEventListener("pointermove", this.aim);
2213
- window.addEventListener("pointermove", this.wake);
2214
- window.addEventListener("wheel", this.wake, { passive: true });
2215
- document.addEventListener("fullscreenchange", this.fullChange);
2216
- window.setTimeout(() => {
2217
- if (this.booted) this.wake();
2218
- }, 0);
2219
- window.addEventListener("error", this.trip);
2220
- window.addEventListener("unhandledrejection", this.trip);
2221
- const frame = el.querySelector("iframe");
2222
- if (!frame) return;
2223
- document.body.classList.add(this.embed ? "cf-embedded" : "cf-film");
2224
- /**
2225
- * THE FILM, REACHABLE FROM OUTSIDE THE PAGE.
2226
- *
2227
- * An exact film is a pure function of one number, which is the whole
2228
- * claim of `@seek='exact'` — so it should be possible to stand it at
2229
- * a time from a script and read the frame back, rather than recording
2230
- * it in real time and hoping. `renderAt` has always existed on the
2231
- * handle; it was simply not reachable by anything that is not a
2232
- * template. Published under the film's own name so a page may hold
2233
- * more than one.
2234
- */
2235
- const reg = window.__choreo ??= {};
2236
- reg[this.args.name] = {
2237
- exact: this.exact,
2238
- renderAt: this.renderAt,
2239
- runtime: () => this.runtime,
2240
- seconds: () => this.totalSecs,
2241
- seek: this.seek
2242
- };
2243
- this.gate = this.from === 0 && !this.booted;
2244
- const onLoad = () => {
2245
- const w = frame.contentWindow;
2246
- if (!w?.__film) return;
2247
- if (!rows && !this.all.length) return;
2248
- this.film = w.__film;
2249
- this.takeGlass(this.film);
2250
- this.film.volume?.(this.vol);
2251
- if (this.booted) return;
2252
- try {
2253
- w.__film.time(this.standing);
2254
- this.picture?.seat?.(w.__film);
2255
- this.dressPoster(w.__film);
2256
- this.snap((this.beats[0] ?? EMPTY_BEAT).cam);
2257
- } finally {
2258
- this.ready = true;
2259
- }
2260
- if (this.gate) {
2261
- if (this.wanted !== void 0) {
2262
- this.begin(this.wanted);
2263
- return;
2264
- }
2265
- this.posterAt = 0;
2266
- this.posterRaf ??= requestAnimationFrame(this.poster);
2267
- return;
2268
- }
2269
- /**
2270
- * ONLY A DEEP LINK STARTS ITSELF. A door that opens on a fast (or
2271
- * cached) load is a film that started without being asked — which
2272
- * is how the poster's own circuit once got two seconds and then
2273
- * vanished.
2274
- */ if (!this.booted && this.from > 0) this.begin(true);
2275
- };
2276
- frame.addEventListener("load", onLoad);
2277
- let bootTimer = 0;
2278
- if (frame.contentDocument?.readyState === "complete") bootTimer = window.setTimeout(onLoad, 0);
2279
- /**
2280
- * THE LOOP COMES BACK. This modifier reads tracked state (`from`,
2281
- * `booted`), so it re-installs on every re-cut and on the boot
2282
- * itself — and its own cleanup below cancels the frame loop each
2283
- * time. The old code got away with it because the load path
2284
- * re-booted the world wholesale; now that a running film is left
2285
- * alone, the re-install has to hand back the one thing it took.
2286
- */
2287
- if (this.booted) {
2288
- const w = frame.contentWindow;
2289
- if (w?.__film) {
2290
- this.film = w.__film;
2291
- this.takeGlass(this.film);
2292
- this.film.volume?.(this.vol);
2293
- }
2294
- cancelAnimationFrame(this.raf);
2295
- this.lastTick = performance.now();
2296
- this.raf = requestAnimationFrame(this.frame);
2297
- }
2298
- return () => {
2299
- cancelAnimationFrame(this.raf);
2300
- window.clearTimeout(bootTimer);
2301
- window.removeEventListener("keydown", this.key);
2302
- window.removeEventListener("error", this.trip);
2303
- window.removeEventListener("unhandledrejection", this.trip);
2304
- window.removeEventListener("pointermove", this.aim);
2305
- window.removeEventListener("pointermove", this.wake);
2306
- window.removeEventListener("wheel", this.wake);
2307
- document.removeEventListener("fullscreenchange", this.fullChange);
2308
- window.clearTimeout(this.idleTimer);
2309
- frame.removeEventListener("load", onLoad);
2310
- document.body.classList.remove("cf-film", "cf-embedded");
2311
- delete window.__choreo?.[this.args.name];
2312
- this.film = void 0;
2313
- this.takeGlass(void 0);
2314
- };
2315
- });
2316
- trackEls = modifier((el) => {
2317
- this.lineEl = el.querySelector("line.cf-leader");
2318
- this.tetherEl = el.querySelector("line.cf-tether");
2319
- this.dotEl = el.querySelector("circle");
2320
- });
2321
- dimEl = modifier((el) => {
2322
- this.dim = el;
2323
- return () => {
2324
- if (this.dim === el) this.dim = void 0;
2325
- };
2326
- });
2327
- plate = modifier((el) => {
2328
- this.plateEl = el;
2329
- this.rowEls = [...el.children];
2330
- return () => {
2331
- if (this.plateEl === el) {
2332
- this.plateEl = void 0;
2333
- this.rowEls = [];
2334
- }
2335
- };
2336
- });
2337
- snap(c, launch, secs) {
2338
- this.goal = { ...c };
2339
- this.mid = { ...c };
2340
- this.now = { ...c };
2341
- /**
2342
- * A CUT LANDS ON A MOVING CAMERA.
2343
- *
2344
- * Snapping used to zero the chaser's velocity, so every cut arrived
2345
- * at a standstill and eased up into its move — the one thing a cut
2346
- * must never do, because the ease-in is the join announcing itself.
2347
- * An operator does not stop between shots; the next shot is already
2348
- * running when the frame changes. So the chaser is handed the
2349
- * incoming shot's OWN speed (its whole travel over its whole
2350
- * length) and starts at pace.
2351
- */
2352
- for (const k of [
2353
- "dolly",
2354
- "fx",
2355
- "fz",
2356
- "lookY",
2357
- "ox",
2358
- "pitch",
2359
- "yaw"
2360
- ]) {
2361
- const a = c[k] ?? 0;
2362
- const b = launch ? launch[k] ?? 0 : a;
2363
- /**
2364
- * AND IT IS A NUDGE, NOT A LAUNCH.
2365
- *
2366
- * The chaser is handed the incoming shot's own speed so the cut
2367
- * lands on a moving camera rather than a standstill. But the spring
2368
- * is pulling toward the shot's HEAD while that speed points at its
2369
- * TAIL, so the whole of it has to be absorbed and given back — and
2370
- * once the stiffness came down to 3.9 there was not enough spring
2371
- * left to absorb it: the lens ran past its own opening frame and
2372
- * came back, which is the bounce at every cut. Roughly half the
2373
- * speed still reads as an operator already moving, and the return
2374
- * is inside the first breath of the shot instead of on top of it.
2375
- */ this.midV[k] = secs ? (b - a) / secs * .45 : 0;
2376
- this.nowV[k] = this.midV[k];
2377
- }
2378
- this.sent = void 0;
2379
- this.film?.pose({
2380
- az: c.yaw * RAD,
2381
- el: c.pitch * RAD,
2382
- fx: c.fx ?? 0,
2383
- fz: c.fz ?? 0,
2384
- lookY: c.lookY,
2385
- ox: c.ox ?? 0,
2386
- snap: true,
2387
- zoom: c.dolly
2388
- });
2389
- }
2390
- /**
2391
- * THE WHIP. A 'whip' join does not snap: the goal steps across the
2392
- * seam (the splice does that) and the chaser races it on a briefly
2393
- * stiff spring — a fast, smooth tween between the shots that is over
2394
- * in a third of a second and never reads as a glide. While it runs,
2395
- * the spring is ~3× its documentary stiffness; then the hand relaxes.
2396
- */
2397
- whipUntil = 0;
2398
- /**
2399
- * One critically-damped stage. The page's own chase is the second, which
2400
- * is the whole cascade argument getting made for free by the fact that the
2401
- * scene lives in another document.
2402
- */
2403
- chase(dt) {
2404
- const w = this.whipUntil > performance.now() ? 11 : 3.9;
2405
- for (const k of [
2406
- "dolly",
2407
- "fx",
2408
- "fz",
2409
- "lookY",
2410
- "ox",
2411
- "pitch",
2412
- "yaw"
2413
- ]) {
2414
- const g = this.goal[k] ?? 0;
2415
- const m = this.mid[k] ?? 0;
2416
- const v = this.midV[k] + (w * w * (g - m) - 2 * w * this.midV[k]) * dt;
2417
- this.midV[k] = v;
2418
- const next = m + v * dt;
2419
- this.mid[k] = next;
2420
- this.now[k] = next;
2421
- }
2422
- }
2423
- /**
2424
- * THE POSTER TURNS.
2425
- *
2426
- * A still frame behind a title is indistinguishable from a JPEG, and
2427
- * this whole film's argument is that it is not one. So while the door
2428
- * is up the lens makes a slow circuit of the opening pose — a few
2429
- * degrees either side, breathing in and out — which says "live 3D"
2430
- * before a word is read and costs one rAF. It hands over on the
2431
- * click: the film does not snap away from the poster, it flies from
2432
- * wherever the circuit had reached.
2433
- */
2434
- poster = (stamp) => {
2435
- if (!this.gate || this.booted) {
2436
- this.posterRaf = void 0;
2437
- return;
2438
- }
2439
- this.posterRaf = requestAnimationFrame(this.poster);
2440
- const film = this.film;
2441
- if (!film) return;
2442
- if (!this.posterAt) this.posterAt = stamp;
2443
- const t = (stamp - this.posterAt) / 1e3;
2444
- const c = (this.beats[0] ?? EMPTY_BEAT).cam;
2445
- const pose = {
2446
- dolly: c.dolly * (2.25 + .09 * Math.sin(t * .11 + 1.1)),
2447
- lookY: c.lookY + 1.4 + .6 * Math.sin(t * .15),
2448
- ox: (c.ox ?? 0) * .66,
2449
- pitch: c.pitch + 2.4 + 1.4 * Math.sin(t * .13),
2450
- yaw: c.yaw + 34 + 11 * Math.sin(t * .2)
2451
- };
2452
- film.pose({
2453
- az: pose.yaw * RAD,
2454
- el: pose.pitch * RAD,
2455
- fx: c.fx ?? 0,
2456
- fz: c.fz ?? 0,
2457
- lookY: pose.lookY,
2458
- ox: pose.ox,
2459
- zoom: pose.dolly
2460
- });
2461
- this.posterPose = pose;
2462
- };
2463
- posterRaf;
2464
- posterAt = 0;
2465
- /** where the poster's circuit had reached when the door was answered */
2466
- posterPose;
2467
- /**
2468
- * ONE SETTING AT A TIME, AND THIS TIME IT IS GUARANTEED.
2469
- *
2470
- * The hand-off is a leaver and an arriver sharing the corner for half
2471
- * a second, which is the design. But a leaver whose exit never
2472
- * completes just stays — and a block from the first beat was still
2473
- * standing at full strength eight beats later, with two settings of
2474
- * different chapters overprinting each other. Whatever strands it (a
2475
- * removal that waits on a cue the outgoing beat never had), the rule
2476
- * the film states out loud in its own comment is worth enforcing
2477
- * rather than trusting: any block that is not the current one and has
2478
- * outstayed the longest hand-off is taken off the glass.
2479
- */
2480
- sweepBlocks(now) {
2481
- const blocks = (this.pageEl ?? this.frameEl ?? document.body).querySelectorAll(".cf-type .cf-block");
2482
- if (blocks.length < 2) return;
2483
- for (let i = 0; i < blocks.length - 1; i++) {
2484
- const el = blocks[i];
2485
- const seen = Number(el.dataset["cfSeen"] ?? 0);
2486
- if (blocks.length > 2 || seen && now - seen > 900) el.remove();
2487
- else if (!seen) el.dataset["cfSeen"] = String(now);
2488
- }
2489
- }
2490
- frame = (stamp) => {
2491
- this.raf = requestAnimationFrame(this.frame);
2492
- this.tick(stamp);
2493
- };
2494
- tick(stamp) {
2495
- this.sweepBlocks(stamp);
2496
- const film = this.film;
2497
- if (!film) return;
2498
- if (document.hidden && !AWAKE) return;
2499
- if (this.ended) {
2500
- if (!this.idled) {
2501
- film.idle(true);
2502
- this.idled = true;
2503
- }
2504
- return;
2505
- }
2506
- if (this.idled) {
2507
- film.idle(false);
2508
- this.idled = false;
2509
- }
2510
- if (this.paused && !this.exact) {
2511
- this.lastTick = stamp;
2512
- return;
2513
- }
2514
- const dt = Math.min(.08, this.lastTick ? (stamp - this.lastTick) / 1e3 : 0);
2515
- this.lastTick = stamp;
2516
- const jumpedNow = this.jumped;
2517
- if (this.exact) {
2518
- if (this.playing && this.rolling && !this.paused) this.t = Math.min(this.totalSecs, this.t + dt);
2519
- this.fold();
2520
- if (this.ended) return;
2521
- }
2522
- const since = this.exact ? this.t - this.beatStart(this.absoluteIndex) : (stamp - this.beatAt) / 1e3;
2523
- this.driveSeam(film, this.t);
2524
- if (this.exact) this.now = { ...this.goal };
2525
- else this.chase(dt);
2526
- const k = Math.min(1, dt * 1.8);
2527
- this.lean.x += (this.leanTo.x - this.lean.x) * k;
2528
- this.lean.y += (this.leanTo.y - this.lean.y) * k;
2529
- /**
2530
- * EVERY SHOT IS FLOWN. The sway started as a comparison-chapter
2531
- * idea and belongs to the whole film: a camera that holds perfectly
2532
- * still is a render, and a degree of drift costs nothing and says
2533
- * "somebody is holding this" in every shot.
2534
- *
2535
- * It is divided by the lens, though. A degree of tilt at a 4x
2536
- * close-up throws the frame around; the same degree on a wide is a
2537
- * breath. So the amplitude is scaled down as the shot gets tighter
2538
- * and a beat can still ask for more of it (the drone chapter does).
2539
- */
2540
- const air = (this.beat.bob ?? .5) / Math.max(.8, Math.min(3, this.now.dolly));
2541
- const flown = Math.min(1, Math.max(0, since / (this.beat.ticks * 2)));
2542
- const bob = air * Math.sin(flown * Math.PI * 1.6) * smooth(Math.min(1, flown / .18)) * smooth(Math.min(1, (1 - flown) / .18));
2543
- const rise = this.followRise(film, dt);
2544
- const lookY = rise ? rise.lookY : this.beat.hold === "top" ? Math.max(1.4, film.height() * .62) - this.rigMid : this.beat.hold && !this.beat.to ? film.height() * .5 - this.rigMid : this.now.lookY;
2545
- const ox = Math.abs(this.now.ox ?? 0) < 1e-4 ? 0 : this.now.ox;
2546
- /**
2547
- * ONE LAST FILTER BEFORE THE GLASS.
2548
- *
2549
- * Everything upstream is smooth on its own — a spline, a spring,
2550
- * the scene's own chase — but they are SUMMED here, along with the
2551
- * bob and the pointer's lean, and a sum of smooth things is only as
2552
- * smooth as its roughest term. A short one-pole on the pose that
2553
- * actually ships takes the last of it out.
2554
- *
2555
- * It must never soften a CUT, though: a filter that carries sixty
2556
- * milliseconds of the outgoing shot into the incoming one turns
2557
- * every edit into a tiny dissolve. The snap clears it, so the first
2558
- * frame of a new building is exactly the new building.
2559
- */ /**
2560
- * THE HAND ON THE STICKS.
2561
- *
2562
- * Three sines at unrelated rates, summed and scaled by the lens:
2563
- * about a sixth of a degree of wander on a wide shot and a fraction
2564
- * of that on a close-up, plus a breath of it in the zoom. It is
2565
- * deterministic (no RNG, so no frame ever jumps), continuous (no
2566
- * discontinuity to catch the eye), and slower than a shake — this
2567
- * is the drift of somebody holding a machine steady, not a handheld
2568
- * effect. Turn it off by setting HAND to 0.
2569
- */ const clock = this.exact ? this.t : stamp / 1e3;
2570
- const wander = (a, b, c) => Math.sin(clock * a) * .6 + Math.sin(clock * b) * .3 + Math.sin(clock * c) * .1;
2571
- const hand = .17 / Math.max(.8, Math.min(3.4, this.now.dolly));
2572
- const want = {
2573
- az: (this.now.yaw + this.lean.x * .8 + hand * .25 * wander(.37, .93, 2.11)) * RAD,
2574
- el: (this.now.pitch - this.lean.y * .5 + bob + hand * 1.2 * wander(.29, 1.07, 1.83)) * RAD,
2575
- fx: this.now.fx ?? 0,
2576
- fz: this.now.fz ?? 0,
2577
- lookY: lookY + this.lean.y * .3 + (this.beat.hold ? 0 : bob * .3),
2578
- ox: ox - this.lean.x * .012,
2579
- zoom: Math.min(this.now.dolly, rise?.zoom ?? Infinity) * (1 + .0011 * wander(.23, .71, 1.51))
2580
- };
2581
- const g = Math.min(1, dt * 12);
2582
- const sent = this.exact ? { ...want } : this.sent ?? { ...want };
2583
- if (!this.exact) {
2584
- sent.az += (want.az - sent.az) * g;
2585
- sent.el += (want.el - sent.el) * g;
2586
- sent.fx += (want.fx - sent.fx) * g;
2587
- sent.fz += (want.fz - sent.fz) * g;
2588
- sent.lookY += (want.lookY - sent.lookY) * g;
2589
- sent.ox += (want.ox - sent.ox) * g;
2590
- sent.zoom += (want.zoom - sent.zoom) * g;
2591
- }
2592
- this.sent = sent;
2593
- const eye = this.beat.eye;
2594
- const lift = Math.max(0, Math.min(.9, this.beat.lift ?? 0));
2595
- const walk = eye?.to ? smooth(lift > 0 ? Math.min(1, flown / lift) : flown) : 0;
2596
- if (lift > 0 && this.beat.toCam) {
2597
- const from = this.beat.cam;
2598
- const to = this.beat.toCam;
2599
- const tilt = smooth(Math.max(0, Math.min(1, (flown - lift) / Math.max(.1, 1 - lift))));
2600
- sent.lookY = lerp(from.lookY ?? 0, to.lookY ?? 0, tilt) + bob * .3;
2601
- sent.el = lerp(from.pitch, to.pitch, tilt) * RAD + bob * .5 * RAD;
2602
- }
2603
- film.pose({
2604
- ...sent,
2605
- snap: this.exact ? true : void 0,
2606
- fov: eye ? eye.fov : null,
2607
- near: eye ? [
2608
- lerp(eye.at[0], (eye.to ?? eye.at)[0], walk),
2609
- lerp(eye.at[1], (eye.to ?? eye.at)[1], walk),
2610
- lerp(eye.at[2], (eye.to ?? eye.at)[2], walk)
2611
- ] : null
2612
- });
2613
- const mood = this.grade;
2614
- const snap = this.gradeSnap;
2615
- if (mood !== this.gradeSent || this.rakeDeg !== this.rakeSent) {
2616
- const spec = this.grades[mood] ?? this.grades["amber"];
2617
- film.grade({
2618
- ...spec,
2619
- rake: this.rakeDeg * RAD
2620
- }, snap);
2621
- this.gradeSnap = false;
2622
- this.gradeSent = mood;
2623
- this.rakeSent = this.rakeDeg;
2624
- }
2625
- const b = this.beat;
2626
- const chapLut = this.chapters[b.ch ?? 0]?.lut ?? null;
2627
- const lutName = b.lut === null ? null : b.lut ?? b.look ?? chapLut;
2628
- if (lutName !== this.lutSent && film.lut) {
2629
- const fx = this.lookFx[lutName ?? ""];
2630
- film.lut(lutName === null ? null : lutName.includes(".") ? {
2631
- amount: this.lutAmount,
2632
- url: `${this.assets}luts/${lutName}`
2633
- } : {
2634
- ...fx ?? { amount: this.lutAmount },
2635
- look: lutName
2636
- }, snap);
2637
- this.lutSent = lutName;
2638
- }
2639
- const goal = this.sunGoal;
2640
- if (this.exact) {
2641
- if (goal) {
2642
- const from = this.sunFrom ?? goal;
2643
- const k = smooth(Math.min(1, since / 1.5));
2644
- this.sunNow = {
2645
- az: from.az + wrapAngle(goal.az - from.az) * k,
2646
- el: lerp(from.el, goal.el, k)
2647
- };
2648
- film.light(this.sunNow);
2649
- }
2650
- } else if (goal && this.sunNow) {
2651
- /**
2652
- * AT A RATE, not on a time constant. An eased approach takes the
2653
- * same second and a half whether the sun moves two degrees or
2654
- * fifty-five — and fifty-five degrees in a second and a half is
2655
- * the jarring relight between the construction chapter (near
2656
- * overhead) and the detail chapter (raking). Capping the angular
2657
- * speed makes a small change quick and a big one a proper move,
2658
- * which the chapter's own flight has time for.
2659
- */
2660
- const cap = .3 * dt;
2661
- const ease = Math.min(1, dt * 1.6);
2662
- const walk = (from, to) => {
2663
- const d = (to - from) * ease;
2664
- return from + (Math.abs(d) > cap ? Math.sign(d) * cap : d);
2665
- };
2666
- let d = goal.az - this.sunNow.az;
2667
- while (d > Math.PI) d -= Math.PI * 2;
2668
- while (d < -Math.PI) d += Math.PI * 2;
2669
- this.sunNow.az = walk(this.sunNow.az, this.sunNow.az + d);
2670
- this.sunNow.el = walk(this.sunNow.el, goal.el);
2671
- film.light(this.sunNow);
2672
- }
2673
- this.pageEl?.style.setProperty("--cf-lx", this.lean.x.toFixed(3));
2674
- this.pageEl?.style.setProperty("--cf-ly", this.lean.y.toFixed(3));
2675
- const at = this.exact ? this.t : this.secsBefore(this.absoluteIndex) + Math.min(this.beat.ticks * 2, since);
2676
- const total = this.totalSecs;
2677
- const prog = this.scrubAt ?? Math.min(1, at / total);
2678
- this.pageEl?.style.setProperty("--cf-prog", prog.toFixed(5));
2679
- if (!this.args.clock) this.pageEl?.style.setProperty("--cf-head", prog.toFixed(5));
2680
- if (this.trackW > 0) this.pageEl?.style.setProperty("--cf-hpx", this.headPx(prog).toFixed(1));
2681
- const pct = Math.round(prog * 100);
2682
- if (pct !== this.shownPct) {
2683
- this.shownPct = pct;
2684
- this.scrubEl?.setAttribute("aria-valuenow", String(pct));
2685
- }
2686
- const sec = Math.floor(at);
2687
- if (sec !== this.shownSec) {
2688
- this.shownSec = sec;
2689
- this.clock = Film.mmss(at);
2690
- }
2691
- this.clips(film, at, jumpedNow);
2692
- const beat = this.beat;
2693
- /**
2694
- * The beat's own local clock. It is re-based at every beat entrance, so
2695
- * whatever it drifts is bounded by one beat rather than by the film —
2696
- * the same seek-unsafe-but-self-correcting bargain the chaser makes,
2697
- * and for the same reason: these are simulations, not tweens.
2698
- */
2699
- const local = Math.min(1, since / (beat.ticks * 2));
2700
- if (Array.isArray(beat.build) && this.rolling) {
2701
- const settle = beat.settle ?? 0;
2702
- const span0 = beat.ticks * 2;
2703
- const bl = Math.max(0, Math.min(1, (since - settle) / Math.max(.01, span0 - settle)));
2704
- const t = lerp(beat.build[0], beat.build[1], smooth(Math.min(1, bl / (beat.buildBy ?? 1))));
2705
- film.time(t);
2706
- this.showYear(t);
2707
- } else if (beat.build !== void 0) this.showYear(Array.isArray(beat.build) ? beat.build[0] : beat.build);
2708
- /**
2709
- * The corner empties half a second before the beat changes — and
2710
- * "changes" means the next CUE, not the end of this beat's own
2711
- * ticks. A beat followed by a chapter flight is on screen for its
2712
- * own length PLUS that flight's lead, and measuring against the
2713
- * short number faded the type out early and then held it at zero
2714
- * for the rest of the shot.
2715
- */
2716
- const nextLead = this.all[this.absoluteIndex + 1]?.lead ?? 0;
2717
- const span = (beat.ticks + nextLead) * 2;
2718
- const left = Math.max(0, span - since);
2719
- const last = !this.all[this.absoluteIndex + 1];
2720
- const wait = this.typeAt[0] - .05;
2721
- const enter = smooth(Math.max(0, Math.min(1, (since - wait) / .4)));
2722
- const endOff = !this.exact && this.beatIndex === 0 ? 0 : 2;
2723
- const tailA = this.ended ? 0 : last ? smooth(Math.max(0, Math.min(1, (left - endOff - .5) / 2))) : smooth(Math.min(1, left / 1));
2724
- const typeA = Math.min(enter, tailA, this.reveal);
2725
- this.pageEl?.style.setProperty("--cf-type-a", typeA.toFixed(3));
2726
- const titleK = beat.mode === "title" ? smooth(Math.max(0, Math.min(1, (since - 1) / Math.max(.5, span - 1.4)))) : 1;
2727
- this.pageEl?.style.setProperty("--cf-title-k", titleK.toFixed(3));
2728
- /**
2729
- * THE ANNOUNCED YEAR STANDS IN THE PLACE, NOT ON THE GLASS.
2730
- *
2731
- * On the glass it was a caption the size of the frame: nothing could
2732
- * pass in front of it, and it was white whatever the light was doing.
2733
- * As a plane in the scene the building crosses it, the shot moves past
2734
- * it, and it takes its colour from the hour — a warm ink on a bright
2735
- * morning, a pale wash at night — which is the difference between a
2736
- * date that belongs to the picture and one laid over it.
2737
- */
2738
- const stampA = this.stampAt(beat, local) * typeA;
2739
- if (beat.stamp && (stampA > .002 || this.stampOn)) {
2740
- this.stampOn = stampA > .002;
2741
- const night = luminance(film.palette().paper) < .42;
2742
- film.sky("stamp", {
2743
- color: night ? "rgba(247,240,224,0.5)" : "rgba(62,50,26,0.42)",
2744
- family: this.worldFamily,
2745
- lines: [String(beat.stamp.y)],
2746
- shadow: night ? "rgba(0,0,0,0.35)" : "rgba(255,252,244,0.3)",
2747
- shadowBlur: .12,
2748
- size: 5.4,
2749
- track: this.worldTrack * .5,
2750
- weight: this.worldWeight
2751
- }, {
2752
- billboard: true,
2753
- opacity: stampA * .9,
2754
- x: -Math.sin(this.skyAz) * 34,
2755
- y: 6.4,
2756
- z: -Math.cos(this.skyAz) * 34
2757
- });
2758
- }
2759
- if (this.plateEl) this.plateEl.style.opacity = typeA.toFixed(3);
2760
- if (beat.hours?.length && this.rolling) {
2761
- const over = beat.hoursOver ?? 1;
2762
- const hl = Math.min(1, local / over);
2763
- const step = Math.min(beat.hours.length - 1, Math.floor(hl * beat.hours.length));
2764
- if (step !== this.hourStep) {
2765
- this.hourStep = step;
2766
- film.themeDur(beat.ticks * 2 * over / beat.hours.length);
2767
- film.theme(beat.hours[step], false);
2768
- }
2769
- }
2770
- const here = this.absoluteIndex;
2771
- const tail = (this.all[here + 1]?.ch ?? -1) !== beat.ch;
2772
- this.pageEl?.style.setProperty("--cf-ghost-a", ((tail ? 1 - smooth(Math.max(0, (local - .72) / .22)) : 1) * (1 - this.stampAt(beat, local))).toFixed(3));
2773
- if (beat.lightning !== void 0 && this.struck !== beat.id && since >= beat.lightning) {
2774
- this.struck = beat.id;
2775
- film.lightning();
2776
- }
2777
- if (beat.dissolve) film.modelFade(1 - smooth(Math.max(0, (local - .12) / .72)));
2778
- /**
2779
- * WEATHER CROSSES THE SHOT. The scene lights itself beautifully and
2780
- * then holds that light for four minutes, which is the one thing
2781
- * daylight never does. So every beat long enough to notice gets a
2782
- * cloud: the air thickens, a cool shadow passes over the picture,
2783
- * and it clears again before the beat is out. It is one pass, phased
2784
- * off the beat's own place in the script so no two land alike, and
2785
- * it never touches the grade — a chapter's colour is an argument,
2786
- * this is only the sky.
2787
- */
2788
- if (beat.ticks * 2 >= 8) {
2789
- const phase = this.beatIndex * .37 % 1 * .25;
2790
- const w = Math.max(0, Math.min(1, (local - .12 - phase) / .62));
2791
- const cloud = Math.sin(Math.PI * w) ** 2;
2792
- film.haze((this.hazeBase ?? 0) + this.cloudHaze * cloud);
2793
- this.setCloud(film, cloud);
2794
- } else this.setCloud(film, 0);
2795
- this.pushWash(film);
2796
- if (beat.cycle?.length) {
2797
- const step = Math.min(beat.cycle.length - 1, Math.floor(local * beat.cycle.length));
2798
- if (step !== this.cycleStep) {
2799
- this.cycleStep = step;
2800
- const c = beat.cycle[step];
2801
- if (c.style !== void 0 && film.style && film.styleIndex) {
2802
- if (film.styleIndex() !== c.style) film.style(c.style);
2803
- }
2804
- film.time(c.year !== void 0 && this.args.clock ? this.args.clock.tAt(c.year) : this.standing);
2805
- film.ping(step < beat.cycle.length - 1 ? 0 : 99);
2806
- }
2807
- }
2808
- if (beat.sky) {
2809
- const d = beat.sky.dist ?? 30;
2810
- /**
2811
- * PARALLAX. This used to be placed at `view().az + offset`, which
2812
- * pins the word to the LENS: the camera orbits, the glyph orbits
2813
- * with it, and a hundred feet of type sits perfectly still in the
2814
- * frame — the one thing that tells an eye it is looking at a
2815
- * sticker rather than a place. The angle is now taken once, when
2816
- * the beat lands, and the word stays where it was put: the shot
2817
- * moves past it.
2818
- */
2819
- const a = this.skyAz;
2820
- this.skyOn = this.exact ? smooth(Math.min(1, since / .62)) : this.skyOn + (1 - this.skyOn) * Math.min(1, dt * 1.6);
2821
- film.sky("chapter", {
2822
- color: "rgba(252,247,236,0.34)",
2823
- family: this.worldFamily,
2824
- lines: beat.sky.lines,
2825
- shadow: "rgba(40,30,14,0.34)",
2826
- shadowBlur: .16,
2827
- size: beat.sky.size * 3.4,
2828
- weight: this.worldWeight,
2829
- track: beat.sky.track ?? this.worldTrack
2830
- }, {
2831
- billboard: true,
2832
- opacity: this.skyA = this.skyOn * (beat.sky.opacity ?? .4) * .78 * smooth(Math.max(0, Math.min(1, (local - .16) / .16))) * (1 - smooth(Math.max(0, Math.min(1, (local - .52) / .16)))),
2833
- x: -Math.sin(a) * d,
2834
- y: beat.sky.y,
2835
- z: -Math.cos(a) * d
2836
- });
2837
- } else if (this.skyA > .001 || this.skyOn > .001) {
2838
- this.skyOn = this.exact ? 0 : this.skyOn + (0 - this.skyOn) * Math.min(1, dt * 2.4);
2839
- this.skyA = this.exact ? 0 : this.skyA + (0 - this.skyA) * Math.min(1, dt * 2.4);
2840
- film.fade("chapter", this.skyA);
2841
- }
2842
- this.stageMark(film, beat, local, dt);
2843
- this.trackPoint(film, beat, local, typeA);
2844
- this.traceShape(film, beat, local, dt);
2845
- this.parallax(local);
2846
- this.follow(film);
2847
- this.wear(film);
2848
- }
2849
- /**
2850
- * TYPE THROWS ITS SHADOW WHERE THE BUILDING THROWS ITS OWN.
2851
- *
2852
- * The scene lights itself from a key whose position is a property of the
2853
- * hour, and the tower's cast shadow lies along the ground at whatever
2854
- * diagonal that produces. Type set over the frame with a shadow offset
2855
- * down and right — the default of every drop shadow ever shipped — is
2856
- * lit by a different sun than everything behind it, and the eye reads
2857
- * the mismatch long before it can name it. So the key's own screen
2858
- * position comes back from the scene and the offset is simply the
2859
- * direction away from it. At night, or with the sun behind the lens,
2860
- * there is no honest cast shadow and the type wears none.
2861
- */
2862
- follow(film) {
2863
- const el = this.pageEl;
2864
- if (!el) return;
2865
- const s = film.sun();
2866
- const cx = s.w / 2;
2867
- const cy = s.h * .55;
2868
- const dx = cx - s.x;
2869
- const dy = cy - s.y;
2870
- const len = Math.hypot(dx, dy) || 1;
2871
- const throwPx = s.on ? 3.2 : 0;
2872
- const nx = dx / len * throwPx;
2873
- const ny = dy / len * throwPx;
2874
- const q = (v) => Math.round(v * 10) / 10;
2875
- if (q(nx) !== this.shadow.x || q(ny) !== this.shadow.y) {
2876
- this.shadow = {
2877
- x: q(nx),
2878
- y: q(ny)
2879
- };
2880
- el.style.setProperty("--cf-shx", `${this.shadow.x}px`);
2881
- el.style.setProperty("--cf-shy", `${this.shadow.y}px`);
2882
- this.rakeDeg = q(Math.atan2(ny, nx) * 57.2958);
2883
- el.style.setProperty("--cf-rake", `${this.rakeDeg}deg`);
2884
- }
2885
- }
2886
- /** which of the frame's layers this picture draws itself */
2887
- takeGlass(film) {
2888
- this.washInGlass = !!film?.wash;
2889
- this.cloudInGlass = !!film?.cloud;
2890
- this.dimInGlass = !!film?.dim;
2891
- this.washSent = "";
2892
- }
2893
- /**
2894
- * THE WASH, TOLD ON A CHANGE. Two things decide it — the shot's own mode
2895
- * and the palette's paper — and neither changes often, so the picture is
2896
- * told when one does and eases the change itself over the same 700 ms
2897
- * the stylesheet used to.
2898
- */
2899
- pushWash(film) {
2900
- if (!film.wash) return;
2901
- const mode = this.beat.bare ? "" : this.beat.mode ?? "";
2902
- const sig = `${mode}|${this.paperNow}`;
2903
- if (sig === this.washSent) return;
2904
- this.washSent = sig;
2905
- film.wash(mode, this.paperNow);
2906
- }
2907
- /** the cloud: into the glass where the picture takes it, onto a CSS plate where it does not */
2908
- setCloud(film, k) {
2909
- if (film.cloud) {
2910
- film.cloud(k, this.rakeDeg);
2911
- return;
2912
- }
2913
- this.pageEl?.style.setProperty("--cf-cloud", k.toFixed(3));
2914
- this.pageEl?.classList.toggle("is-cloudless", k < .02);
2915
- }
2916
- /**
2917
- * THE FURNITURE WEARS THE SCENE'S PALETTE.
2918
- *
2919
- * The page carries four times of day and swings its whole palette between
2920
- * them, so a scrim and a caption painted in one fixed ochre are correct in
2921
- * exactly one chapter and wrong in the other four — at noon the film's
2922
- * warm paper sits on a cool scene like a sticker. The scene already
2923
- * publishes its ink as CSS variables, so the film simply reads them and
2924
- * dresses itself the same, once per change rather than once per frame.
2925
- */
2926
- wear(film) {
2927
- const p = film.palette();
2928
- if (!p.time) return;
2929
- /**
2930
- * THE INK IS JUDGED AGAINST WHAT IS ACTUALLY ON SCREEN. The paper
2931
- * alone lied: a noon paper under a heavy grade, a dim, and thick
2932
- * haze is a DARK frame wearing a light theme, and dark ink on it
2933
- * disappears. So the measured luminance is discounted by the
2934
- * grade's own brightness, by whether this beat runs the dim, and
2935
- * by its haze — and the type flips to light whenever the frame it
2936
- * sits on has genuinely gone dark, not merely when the clock says
2937
- * night.
2938
- */
2939
- const GB = this.picture?.gradeLum ?? GRADE_LUM;
2940
- const beat = this.beat;
2941
- const dimmed = beat.trace || beat.to ? .74 : 1;
2942
- const dark = luminance(p.paper) * (GB[this.grade] ?? 1) * dimmed * (1 - .25 * (beat.haze ?? 0)) < .42;
2943
- const ch = this.chapters[this.beat.ch ?? 0] ?? this.chapters[0];
2944
- const sig = `${p.time}${dark ? "#d" : "#l"}${ch.n}`;
2945
- if (sig === this.wearing) return;
2946
- this.wearing = sig;
2947
- const el = this.pageEl;
2948
- if (!el) return;
2949
- this.paperNow = p.paper;
2950
- el.style.setProperty("--cf-paper", p.paper);
2951
- el.style.setProperty("--cf-paper-a", rgba(p.paper, .94));
2952
- el.style.setProperty("--cf-paper-b", rgba(p.paper, .76));
2953
- el.style.setProperty("--cf-paper-c", rgba(p.paper, 0));
2954
- /**
2955
- * WHEN THE GROUND GOES DARK, THE TYPE GOES LIGHT.
2956
- *
2957
- * The scrims are mixed from the scene's own paper, so at night they
2958
- * are a dark wash — and dark ink on a dark wash is unreadable however
2959
- * carefully the scrim was tuned. Rather than hand-pick a palette per
2960
- * hour, the ink is DERIVED — from the frame's effective luminance,
2961
- * computed above. One rule, right for all four hours, every grade,
2962
- * and any theme anybody adds later.
2963
- */
2964
- el.classList.toggle("is-dark", dark);
2965
- el.style.setProperty("--cf-ink", dark ? "#f7f0e0" : p.ink);
2966
- el.style.setProperty("--cf-ink2", dark ? "#bdb3a0" : p.ink2);
2967
- el.style.setProperty("--cf-ink3", dark ? "#e6dcc8" : p.ink3);
2968
- el.style.setProperty("--cf-accent", this.accentFor(dark, p.accent));
2969
- el.style.setProperty("--cf-rule", p.rule);
2970
- el.style.setProperty("--cf-chap", (dark ? ch.tintD : ch.tint) ?? this.accentFor(dark, p.accent));
2971
- }
2972
- /**
2973
- * PLANES, MOVING AT THEIR OWN RATES.
2974
- *
2975
- * The world layer parallaxes because it is genuinely out in the scene at
2976
- * different distances. The front layer has no depth to borrow, so it is
2977
- * given one: over the life of a beat each row drifts and scales on its
2978
- * own rate, the big glyph fastest and nearest, the small print slowest
2979
- * and furthest, so the type reads as a set of planes rather than a
2980
- * sheet. It is the oldest trick in motion graphics and it is here for
2981
- * the oldest reason — a still caption over a moving picture looks
2982
- * pasted on, and a caption that moves WITH the picture looks composited
2983
- * into it.
2984
- *
2985
- * It runs on the beat's own clock rather than Motion's, deliberately:
2986
- * the entrances belong to the score and this belongs to the shot, and
2987
- * putting the two on one timeline would mean the drift restarting every
2988
- * time a word did.
2989
- */
2990
- parallax(local) {
2991
- const rows = this.rowEls;
2992
- if (!rows.length) return;
2993
- const t = smooth(Math.min(1, local * 1.6)) * (.35 + .65 * local);
2994
- /**
2995
- * A CLOSE-UP GETS LESS OF THIS. Parallax on the type exists to
2996
- * stop a caption looking pasted onto a moving picture. On a 4× detail
2997
- * shot the picture is barely moving and the caption is the largest
2998
- * thing on screen, so the same amplitude stops reading as depth and
2999
- * starts reading as drift. It has to scale with what the shot behind
3000
- * it is actually doing.
3001
- */
3002
- const amp = this.beat.mode === "point" ? .35 : 1;
3003
- const out = local > .93 ? Math.max(0, (1 - local) / .07) : 1;
3004
- for (const [i, el] of rows.entries()) {
3005
- const depth = 1 - i / Math.max(1, rows.length - 1);
3006
- const rate = 1 + .22 * Math.sin(i * 2.4);
3007
- const dx = -11 * t * amp;
3008
- const dy = -(5 + 13 * depth) * t * amp * rate;
3009
- const sc = 1 + .055 * depth * t * amp;
3010
- el.style.transform = `translate3d(${dx.toFixed(2)}px,${dy.toFixed(2)}px,0) scale(${sc.toFixed(4)})`;
3011
- el.style.opacity = out.toFixed(3);
3012
- }
3013
- }
3014
- /**
3015
- * THE TRACES LIVE IN THE SCENE NOW. They began as SVG projected over
3016
- * the frame — approximately right and one frame behind the lens on
3017
- * every move. As tubes in the page's own scene they are exact: they
3018
- * lie ON the eave because they are geometry at the eave's own
3019
- * coordinates, the building occludes them honestly, and the draw-on
3020
- * is the tube's index buffer revealed in path order. Each line in a
3021
- * set starts a moment after the one before it — a hand annotating,
3022
- * not a diagram switching on.
3023
- */ /** how much of the follow is in force (eased), and its last aim and zoom */
3024
- riseK = 0;
3025
- riseAim = 0;
3026
- riseZoom = 1;
3027
- /**
3028
- * THE LENS FOLLOWS THE WORK. Chris's rule for a building that goes up
3029
- * in the shot: if a spire is rising, ZOOM OUT; as you push in, follow
3030
- * the top that is being built. So while the followed campaign is
3031
- * between its base and its top, the aim rides a little above the
3032
- * middle of what stands and the zoom is capped to keep base and top
3033
- * in the frame; when it finishes, the authored pose takes over again,
3034
- * on a one-pole so the release is a move and not a jump.
3035
- */
3036
- followRise(film, dt) {
3037
- const b = this.beat;
3038
- const want = b.follow !== false && Array.isArray(b.build) ? film.rising?.(typeof b.follow === "string" ? b.follow : void 0) ?? null : null;
3039
- const on = !!want && want.rising;
3040
- if (this.exact) {
3041
- if (!on || !want) return null;
3042
- const span = Math.max(1.6, want.top - want.base + 1);
3043
- const aim = want.base + .58 * (want.top - want.base) - this.rigMid;
3044
- const zoom = Math.min(this.now.dolly, 7 / span);
3045
- const k = smooth(Math.min(1, want.k / .12)) * (1 - smooth(Math.max(0, (want.k - .86) / .14)));
3046
- return {
3047
- lookY: lerp(this.now.lookY, aim, k),
3048
- zoom: lerp(this.now.dolly, zoom, k)
3049
- };
3050
- }
3051
- if (on && want) {
3052
- const span = Math.max(1.6, want.top - want.base + 1);
3053
- const aim = want.base + .58 * (want.top - want.base) - this.rigMid;
3054
- const zoom = Math.min(this.now.dolly, 7 / span);
3055
- const k = Math.min(1, dt / 1.1);
3056
- this.riseAim += (aim - this.riseAim) * (this.riseK < .02 ? 1 : k);
3057
- this.riseZoom += (zoom - this.riseZoom) * (this.riseK < .02 ? 1 : k);
3058
- }
3059
- this.riseK += ((on ? 1 : 0) - this.riseK) * Math.min(1, dt / .9);
3060
- if (this.riseK < .01) return null;
3061
- return {
3062
- lookY: lerp(this.now.lookY, this.riseAim, this.riseK),
3063
- zoom: lerp(this.now.dolly, this.riseZoom, this.riseK)
3064
- };
3065
- }
3066
- /** the beat whose lines are being timed, and the local time they became drawable */
3067
- traceBeat = "";
3068
- traceFrom = -1;
3069
- /** whether the beat's lines may be on the glass right now */
3070
- traceOk = false;
3071
- /**
3072
- * NO LINE ON A THING STILL GOING UP. A trace or a callout is drawn on a
3073
- * finished part: the followed campaign must be done (or, with nothing
3074
- * followed, the built height must have passed the line). The draw-on
3075
- * is timed from the moment that became true, not from the head.
3076
- */
3077
- traceReady(film, beat, local) {
3078
- if (this.traceBeat !== beat.id) {
3079
- this.traceBeat = beat.id;
3080
- this.traceFrom = -1;
3081
- }
3082
- let ready = true;
3083
- if (beat.follow !== false && Array.isArray(beat.build)) {
3084
- const r = film.rising?.(typeof beat.follow === "string" ? beat.follow : void 0);
3085
- if (typeof beat.follow === "string") ready = !!r && r.done;
3086
- else ready = !r || !r.rising;
3087
- }
3088
- if (ready && this.traceFrom < 0) this.traceFrom = local;
3089
- this.traceOk = ready && this.traceFrom >= 0;
3090
- return this.traceOk;
3091
- }
3092
- traceShape(film, beat, local, dt) {
3093
- const specs = beat.trace ?? [];
3094
- let drew = 0;
3095
- const gone = Math.min(1, Math.max(0, (local - .78) / .16));
3096
- const since = this.traceReady(film, beat, local) ? local - this.traceFrom : -1;
3097
- for (const [i] of specs.entries()) {
3098
- const draw = Math.min(1, Math.max(0, (since - .04 - i * .12) / .16));
3099
- const undraw = Math.min(1, Math.max(0, gone * (1 + i * .25)));
3100
- drew = Math.max(drew, draw * (1 - undraw));
3101
- if (undraw > 0 && film.traceUndraw) {
3102
- film.traceUndraw(`t${i}`, undraw);
3103
- film.traceFade(`t${i}`, undraw >= 1 ? 0 : 1);
3104
- } else {
3105
- film.traceDraw(`t${i}`, draw);
3106
- film.traceFade(`t${i}`, 1 - undraw);
3107
- }
3108
- }
3109
- if (beat.to) drew = Math.max(drew, Math.min(1, Math.max(0, (since - .04) / .18)));
3110
- this.dimNow = this.exact ? drew : this.dimNow + (drew - this.dimNow) * Math.min(1, dt * 3.2);
3111
- if (this.dimInGlass) film.dim?.(this.dimNow);
3112
- else if (this.dim) this.dim.style.opacity = this.dimNow.toFixed(3);
3113
- }
3114
- /** the mark's facing, damped behind the camera's own bearing */
3115
- markFace = 0;
3116
- /**
3117
- * Place the stage mark: climb to its height with a settle, hold, and
3118
- * face the camera a beat late.
3119
- */
3120
- stageMark(film, beat, local, dt) {
3121
- const m = beat.mark;
3122
- const tether = this.tetherEl;
3123
- if (!m) {
3124
- film.fade("mark", 0);
3125
- if (tether) tether.style.opacity = "0";
3126
- return;
3127
- }
3128
- /**
3129
- * IT COMES IN BEHIND THE BUILDING, EVERY TIME. The entry height is
3130
- * read off the structure at the moment the word arrives — a little
3131
- * under the top of whatever has been built so far — so the thing on
3132
- * screen always hides the word's first frames and hands it over as
3133
- * it climbs. Taken once per beat, because reading it every frame
3134
- * would make the word ride the build instead of rising past it.
3135
- */
3136
- if (!this.markSeated && local >= (m.at ?? .14)) {
3137
- this.markSeated = true;
3138
- this.markFrom = Math.max(.4, film.height() - 3.4);
3139
- }
3140
- const raw = Math.min(1, Math.max(0, (local - .04) / .2));
3141
- const c1 = .45;
3142
- const p = raw - 1;
3143
- const rise = raw >= 1 ? 1 : 1 + 1.45 * p * p * p + c1 * p * p;
3144
- const y = m.to * rise;
3145
- const a = m.bearing * RAD;
3146
- const v = film.view();
3147
- this.markFace = this.exact ? v.az : this.markFace + wrapAngle(v.az - this.markFace) * Math.min(1, dt * 1.6);
3148
- film.sky("mark", {
3149
- color: "rgba(78,71,56,0.3)",
3150
- family: this.worldFamily,
3151
- lines: m.lines,
3152
- shadow: "rgba(255,252,244,0.28)",
3153
- shadowBlur: .14,
3154
- size: m.size * 2.7,
3155
- track: this.worldTrack,
3156
- weight: this.worldWeight
3157
- }, {
3158
- opacity: .62 * smooth(Math.max(0, Math.min(1, (local - (m.at ?? .14)) / .16))) * (1 - smooth(Math.max(0, Math.min(1, (local - ((m.at ?? .14) + (m.hold ?? .34))) / .2)))),
3159
- ry: this.markFace / RAD % 360,
3160
- /**
3161
- * IT RISES OUT FROM BEHIND THE BUILDING. The word starts low
3162
- * enough that the structure stands in front of it, fades up
3163
- * while it is still half hidden, climbs past the frame the
3164
- * carpenters are raising, and is gone before it reaches the top
3165
- * of the picture — so the BUILDING reveals it, rather than a
3166
- * caption appearing beside the building. Planted on one azimuth
3167
- * so the camera moves PAST it, and nudged back toward the lens
3168
- * if the orbit carries it out of frame (below).
3169
- */
3170
- x: Math.sin(this.markAz) * 26 + Math.cos(this.markAz) * this.markOff,
3171
- y: this.markFrom + Math.max(0, local - (m.at ?? .14)) * 7,
3172
- z: Math.cos(this.markAz) * 26 - Math.sin(this.markAz) * this.markOff
3173
- });
3174
- /**
3175
- * KEEP IT ON SCREEN — WHILE IT IS ON SCREEN. A planted word
3176
- * parallaxes, which is the point, but the shot can orbit far enough
3177
- * to carry it out of the picture, so the anchor is nudged back
3178
- * toward the lens when what it projects to leaves the safe area.
3179
- *
3180
- * Two things this must NOT do. It must not correct a word that has
3181
- * already faded out — the first mark used to slide sideways as it
3182
- * left, because the correction was still hunting a glyph nobody
3183
- * could see. And it must not treat the TOP edge as a failure: this
3184
- * word is supposed to rise out of frame. Only the sides count, and
3185
- * only while it is visible.
3186
- */
3187
- const seen = film.project(Math.sin(this.markAz) * 26 + Math.cos(this.markAz) * this.markOff, this.markFrom + Math.max(0, local - (m.at ?? .14)) * 7, Math.cos(this.markAz) * 26 - Math.sin(this.markAz) * this.markOff);
3188
- if (local > (m.at ?? .14) - .02 && local < (m.at ?? .14) + (m.hold ?? .34) + .02 && (!seen.on || seen.x < v.w * .12 || seen.x > v.w * .88)) {
3189
- let d = v.az + Math.PI - this.markAz;
3190
- while (d > Math.PI) d -= Math.PI * 2;
3191
- while (d < -Math.PI) d += Math.PI * 2;
3192
- this.markAz += d * Math.min(1, dt * .9);
3193
- }
3194
- if (tether && this.frameEl) {
3195
- const host = this.frameEl.getBoundingClientRect();
3196
- const sx = host.width / v.w;
3197
- const sy = host.height / v.h;
3198
- const from = film.project(Math.sin(a) * m.r * .72, y, Math.cos(a) * m.r * .72);
3199
- const to = film.project(0, y, 0);
3200
- tether.setAttribute("x1", String(from.x * sx));
3201
- tether.setAttribute("y1", String(from.y * sy));
3202
- const tk = Math.min(1, Math.max(0, (raw - .4) / .22));
3203
- const fx = Number(tether.getAttribute("x1") ?? 0);
3204
- const fy = Number(tether.getAttribute("y1") ?? 0);
3205
- tether.setAttribute("x2", String(fx + (to.x * sx - fx) * tk));
3206
- tether.setAttribute("y2", String(fy + (to.y * sy - fy) * tk));
3207
- tether.style.opacity = tk > .01 ? "0.55" : "0";
3208
- }
3209
- }
3210
- /**
3211
- * THE TRACKING LINE — the one piece of the front layer that has to know
3212
- * where the camera is pointing this frame. The caption stays where the
3213
- * layout put it and a line reaches from it to the actual point on the
3214
- * building, redrawn every frame, so the label is attached to the thing
3215
- * rather than merely near it.
3216
- */
3217
- trackPoint(film, beat, local, typeA) {
3218
- const line = this.lineEl;
3219
- const dot = this.dotEl;
3220
- if (!line || !dot) return;
3221
- if (!beat.to || !this.frameEl || !this.plateEl) {
3222
- line.style.opacity = "0";
3223
- dot.style.opacity = "0";
3224
- return;
3225
- }
3226
- if (beat.to[1] > film.height() + .15) {
3227
- line.style.opacity = "0";
3228
- dot.style.opacity = "0";
3229
- return;
3230
- }
3231
- const p = film.project(...beat.to);
3232
- const v = film.view();
3233
- const host = this.frameEl.getBoundingClientRect();
3234
- const box = this.plateEl.getBoundingClientRect();
3235
- const sx = host.width / v.w;
3236
- const sy = host.height / v.h;
3237
- const x2 = p.x * sx;
3238
- const y2 = p.y * sy;
3239
- const cx = box.left - host.left + box.width / 2;
3240
- const cy = box.top - host.top + box.height / 2;
3241
- const x1 = x2 > cx ? box.right - host.left : box.left - host.left;
3242
- const y1 = cy;
3243
- /**
3244
- * A RED LINE IS DRAWN, NEVER SWITCHED ON.
3245
- *
3246
- * The traces on the building draw themselves along their own path and
3247
- * lift back off it; the callout — the leader and the ring — was
3248
- * fading in and out beside them, so half the annotation was a gesture
3249
- * and half was a light switch. Same clock, same behaviour: the leader
3250
- * runs out from the caption to the point, the ring is drawn round it,
3251
- * and at the end the leader's tail catches up with its head while the
3252
- * ring un-draws the way it came.
3253
- */
3254
- const gate = p.on && this.traceOk;
3255
- const inK = Math.min(1, Math.max(0, (local - .12) / .16));
3256
- const outK = Math.min(1, Math.max(0, (local - .78) / .16));
3257
- const draw = gate ? inK : 0;
3258
- const lift = gate ? outK : 1;
3259
- const hx = x1 + (x2 - x1) * draw;
3260
- const hy = y1 + (y2 - y1) * draw;
3261
- const tx = x1 + (hx - x1) * lift;
3262
- const ty = y1 + (hy - y1) * lift;
3263
- line.setAttribute("x1", String(tx));
3264
- line.setAttribute("y1", String(ty));
3265
- line.setAttribute("x2", String(hx));
3266
- line.setAttribute("y2", String(hy));
3267
- dot.setAttribute("cx", String(x2));
3268
- dot.setAttribute("cy", String(y2));
3269
- line.style.opacity = draw > .01 && lift < .99 ? (.75 * typeA).toFixed(3) : "0";
3270
- const vis = Math.max(0, draw - lift);
3271
- dot.style.strokeDasharray = `${vis.toFixed(3)} 1`;
3272
- dot.style.strokeDashoffset = String(-lift.toFixed(3));
3273
- dot.style.opacity = vis > .005 ? typeA.toFixed(3) : "0";
3274
- }
3275
- /**
3276
- * THE READOUT AND THE MARKER. The numeral is tracked and written only
3277
- * when the whole year changes, which is a handful of times a run; the
3278
- * marker's position is a custom property written every frame, because a
3279
- * dot that steps once a year along a rule is a dot that looks broken.
3280
- */ /**
3281
- * WHEN THE NUMERAL IS ON. It rises as the voice reaches the year, holds
3282
- * while the phrase finishes, and is gone well before the cut — a date
3283
- * that outstays the sentence that said it becomes a watermark.
3284
- */
3285
- stampAt(beat, local) {
3286
- const st = beat.stamp;
3287
- if (!st) return 0;
3288
- const secs = beat.ticks * 2;
3289
- const t0 = ((this.voSecs[beat.id] ?? secs * .66) * (st.at ?? .06) + this.typeAt[0]) / secs;
3290
- const rise = .9 / secs;
3291
- const hold = 2.6 / secs;
3292
- const fall = 1.1 / secs;
3293
- const inK = Math.max(0, Math.min(1, (local - t0) / rise));
3294
- const outK = Math.max(0, Math.min(1, (local - t0 - rise - hold) / fall));
3295
- return smooth(inK) * (1 - smooth(outK));
3296
- }
3297
- /** the film's clock readout, from page seconds — nothing without a clock */
3298
- showYear(t) {
3299
- const clock = this.args.clock;
3300
- if (!clock) return;
3301
- const y = clock.yearAt(t);
3302
- const n = Math.floor(y);
3303
- if (n !== this.yearSent) {
3304
- this.yearSent = n;
3305
- this.yearNow = n;
3306
- }
3307
- const [a, z] = clock.span;
3308
- const p = Math.max(0, Math.min(1, (y - a) / (z - a)));
3309
- this.pageEl?.style.setProperty("--cf-head", p.toFixed(4));
3310
- }
3311
- /** everything a beat changes in the world, done once on entry */
3312
- applyBeat(beat, instant = false, seam) {
3313
- const film = this.film;
3314
- if (!film) return;
3315
- this.beatAt = performance.now();
3316
- this.cycleStep = -1;
3317
- for (let i = 0; i < 4; i++) film.clearTrace(`t${i}`);
3318
- const mag = (beat.cam.dolly + (beat.toCam?.dolly ?? beat.cam.dolly)) / 2;
3319
- beat.trace?.forEach((spec, i) => {
3320
- const r = Math.max(.03, Math.min(.2, (spec.wide ? .13 : .085) / mag));
3321
- film.trace(`t${i}`, {
3322
- pts: spec.pts,
3323
- r
3324
- });
3325
- });
3326
- /**
3327
- * A JOIN WITHOUT A CUT. Most seams are cuts, but not all of them:
3328
- * the construction chapter FLIES in and then has to clear the
3329
- * standing building off the ground before it can build one, and a
3330
- * tower blinking out of existence mid-sweep is a glitch. So a beat
3331
- * may name a join without being a cut — the overlay plays over the
3332
- * arriving shot and the camera is left alone.
3333
- */
3334
- if (beat.cut || beat.join) {
3335
- const join = beat.join ?? this.defaultJoin;
3336
- if (this.exact) this.seamExact(film, beat, join, seam);
3337
- else if (join === "whip") this.whipUntil = performance.now() + 360;
3338
- else {
3339
- /**
3340
- * Every remaining join works the same way: capture the OUTGOING
3341
- * frame first — the bridge renders one on demand and reads it
3342
- * back synchronously — then snap, then run the join's overlay on
3343
- * the still while the live incoming shot plays underneath.
3344
- * 'wipe' sweeps it off along the sun's diagonal with a feathered
3345
- * edge; 'blend' is a push dissolve (it fades AND travels);
3346
- * 'iris' closes a circle onto the new shot's own subject; 'dip'
3347
- * covers the seam with a colour. A failed capture (zero-sized
3348
- * surface) degrades to a clean cut — a dip still gets its veil.
3349
- */ /**
3350
- * THE FREEZE BELONGS ON THE GPU, NOT IN A JPEG.
3351
- *
3352
- * Every join that is not a blend used to read the canvas back and
3353
- * encode it — `toDataURL` on a full-resolution, device-pixel-ratio
3354
- * canvas, synchronously, on the frame of the cut. That is tens of
3355
- * milliseconds of stall AT EVERY CUT, which is precisely where the
3356
- * film could least afford it, and with the dip now the default
3357
- * join it was happening twenty-odd times a run. The page can hold
3358
- * the outgoing frame in its own render target instead, for free.
3359
- * The still is kept only as the fallback for a join whose overlay
3360
- * genuinely needs pixels in the DOM (the wipe sweeps one, the iris
3361
- * closes over one).
3362
- */
3363
- let held = "";
3364
- const deep = this.overOf(beat);
3365
- /**
3366
- * THE SEAM IN TWO HALVES. A mix seam the picture can hold goes
3367
- * into the glass — where it is composited in LIGHT, which an
3368
- * `<img>` faded by this document can never be — and the incoming
3369
- * furniture is revealed by the same two numbers rather than
3370
- * hidden under a still. No read-back, no JPEG, no decode to wait
3371
- * for, and a seek through it is a function of the clock.
3372
- *
3373
- * A wipe and an iris are shapes rather than mixes: they need
3374
- * pixels to sweep and to clip, so they keep their still.
3375
- */
3376
- const glass = this.glassFor(join);
3377
- if (glass) {
3378
- film.freeze?.(true);
3379
- this.glassKind = join;
3380
- this.glassName = glass.name ?? "";
3381
- this.glassLen = this.secsOf(join);
3382
- this.glassColor = beat.dipTo ?? "#0d0905";
3383
- this.reveal = 0;
3384
- this.freeze = "";
3385
- this.joinKind = "";
3386
- if (beat.cut) this.snap(beat.cam, this.tailFor(this.beatIndex), beat.ticks * 2);
3387
- } else if (deep !== "everything" && (join === "blend" || join === "melt" || join === "dip")) {
3388
- const live = beat.theme === void 0 && beat.wx === void 0 && beat.style === void 0 && beat.winter === void 0 && beat.grade === void 0 && !beat.hours;
3389
- if (!film.dissolve(join, void 0, live)) {
3390
- const shot = film.snapshot();
3391
- held = shot.length > 64 ? shot : "";
3392
- }
3393
- } else if (join !== "cut") {
3394
- const shot = film.snapshot();
3395
- held = shot.length > 64 ? shot : "";
3396
- }
3397
- const index = this.beatIndex;
3398
- if (this.glassKind === join) {} else if (held) film.freeze?.(true);
3399
- this.seam.hold(this.glassKind === join ? "" : held, () => {
3400
- if (this.glassKind === join) return;
3401
- film.freeze?.(false);
3402
- this.freeze = held;
3403
- if (beat.cut || join === "wipe") this.snap(beat.cam, this.tailFor(index), beat.ticks * 2);
3404
- if (join === "dip") {
3405
- this.dipColor = beat.dipTo ?? "#0d0905";
3406
- this.play("dip", deep);
3407
- } else if (join === "flash") this.play("flash", deep);
3408
- else if (join === "sweep") this.lightSweep(film, beat);
3409
- else if (join === "defocus") {
3410
- if (held) this.play("defocus", deep);
3411
- this.liveEl?.animate([{ filter: "blur(9px)" }, { filter: "blur(0px)" }], {
3412
- duration: 700,
3413
- easing: "cubic-bezier(0.3, 0, 0.3, 1)"
3414
- });
3415
- } else if (held) {
3416
- if (join === "iris") {
3417
- const v = film.view();
3418
- const p = beat.to ? film.project(...beat.to) : void 0;
3419
- const cx = p ? Math.max(12, Math.min(88, p.x / v.w * 100)) : 50;
3420
- const cy = p ? Math.max(12, Math.min(88, p.y / v.h * 100)) : 50;
3421
- this.irisAt = `--ix:${cx.toFixed(1)}%;--iy:${cy.toFixed(1)}%`;
3422
- this.play("iris", deep);
3423
- } else if (this.presentations[join]?.component) this.play(join, deep);
3424
- }
3425
- });
3426
- }
3427
- }
3428
- /**
3429
- * WHICH BUILDING THIS BEAT IS ABOUT — asserted, never inherited.
3430
- *
3431
- * This used to run only when a beat NAMED a style, so every beat
3432
- * that did not name one (all of chapters one to four) simply kept
3433
- * whatever tower was standing. Straight through from the top that is
3434
- * invisible, because the film opens on the keep and only the
3435
- * comparison names anything else. Come back the other way — the
3436
- * comparison or the lineup, then a chapter skip back into DETAIL —
3437
- * and the film narrates the Japanese keep over a Chinese pagoda: the
3438
- * shot called "at the foot" frames somebody else's balcony, and the
3439
- * traces, which are computed from the keep's own level table, point
3440
- * at empty air. That is the "off by one section" and the second
3441
- * building in the frame, and it is not a motion problem at all: it
3442
- * is a beat inheriting state it never asked for. The subject of this
3443
- * film is the keep, so a beat that says nothing means style zero.
3444
- */ if (film.style && film.styleIndex) {
3445
- const style = beat.style ?? 0;
3446
- if (film.styleIndex() !== style) {
3447
- film.style(style);
3448
- film.time(Array.isArray(beat.build) ? beat.build[0] : beat.build ?? this.standing);
3449
- }
3450
- }
3451
- if (!beat.dissolve) film.modelFade(1);
3452
- if (typeof beat.build === "number") film.time(beat.build);
3453
- else if (Array.isArray(beat.build)) film.time(beat.build[0]);
3454
- else film.time(this.standing);
3455
- if (beat.sky) {
3456
- const swing = beat.toCam ? (beat.toCam.yaw - beat.cam.yaw) * RAD / 2 : 0;
3457
- this.skyAz = (this.film?.view().az ?? 0) + swing + (beat.sky.az ?? 0) * RAD;
3458
- }
3459
- if (beat.mark) {
3460
- this.markAz = (this.film?.view().az ?? 0) + Math.PI;
3461
- /**
3462
- * A ONE-CHARACTER WORD IS NOT A TWO-CHARACTER WORD. 瓦 centred on
3463
- * the same anchor as 石垣 reads as sitting too far left — an eye
3464
- * centres a line of type on its mass, not on its box. So a lone
3465
- * glyph is nudged toward the right of frame, and which way THAT
3466
- * is depends on where the lens is standing: both candidates are
3467
- * projected and the one further right wins.
3468
- */
3469
- const lone = (beat.mark.lines[0] ?? "").length <= 1;
3470
- this.markOff = 0;
3471
- if (lone && this.film) {
3472
- const a = this.markAz;
3473
- const at = (k) => this.film.project(Math.sin(a) * 26 + Math.cos(a) * k, 9, Math.cos(a) * 26 - Math.sin(a) * k).x;
3474
- this.markOff = at(3.6) > at(-3.6) ? 3.6 : -3.6;
3475
- }
3476
- this.markSeated = false;
3477
- this.markFrom = (beat.mark.to ?? 8) * .34 - 1.6;
3478
- }
3479
- this.applyAir(beat, instant);
3480
- /**
3481
- * THE VOICE CROSSES ON ITS OWN CLOCK. Tracks are linked by the seam
3482
- * but independent across it: a beat with its own line fades the old
3483
- * one under (~120ms) and speaks; a beat with none is an L-CUT — the
3484
- * outgoing sentence finishes over the new shot and the duck lifts
3485
- * when it lands, exactly as an editor would lay it. A designed
3486
- * silence (`hush`) is the exception that asks for quiet.
3487
- */
3488
- if (beat.vo || beat.hush) {
3489
- this.hush();
3490
- this.speak(beat);
3491
- } else if (!this.sound || !this.film?.speaking()) this.film?.duck(1);
3492
- this.args.onBeat?.(beat, film);
3493
- }
3494
- /**
3495
- * THE AIR OF A SHOT: grade, sun, haze, weather. Split out of the beat
3496
- * so a chapter's sky can begin turning while the lens is still flying
3497
- * into it — the `air` cue fires at the head of a lead, the beat's own
3498
- * cue when it lands, and applying it twice is harmless because every
3499
- * one of these is a set, not a step.
3500
- */
3501
- applyAir(beat, instant = false) {
3502
- const hard = instant || !!beat.cut || beat.join !== void 0 && this.holdsStill(beat.join);
3503
- const film = this.film;
3504
- if (!film) return;
3505
- if (beat.theme !== void 0) film.theme(beat.theme, hard);
3506
- /**
3507
- * THE SUN MOVES, it does not switch on. Both sun and haze are shot
3508
- * properties, released when a beat does not ask — otherwise one
3509
- * raking close-up lights the rest of the film. But a beat that DOES
3510
- * ask used to get its light in a single frame, which in the
3511
- * construction chapter (side light at twelve degrees, then sixty,
3512
- * then near-overhead) read as somebody flipping a switch. The goal
3513
- * is set here and the frame loop walks the light to it.
3514
- */
3515
- const want = beat.sun ? {
3516
- az: beat.sun.az * RAD,
3517
- el: beat.sun.el * RAD
3518
- } : null;
3519
- this.sunFrom = instant ? null : this.sunGoal;
3520
- this.sunGoal = want;
3521
- if (hard || !this.sunNow || !want) {
3522
- this.sunNow = want ? { ...want } : null;
3523
- film.light(this.sunNow);
3524
- }
3525
- this.struck = "";
3526
- this.hourStep = -1;
3527
- this.gradeSnap = hard;
3528
- if (!beat.hours) film.themeDur(null);
3529
- this.hazeBase = beat.haze ?? null;
3530
- film.haze(this.hazeBase);
3531
- if (beat.wx !== void 0) film.wx(beat.wx, hard || !!beat.wxCut);
3532
- if (beat.winter !== void 0) film.winter(beat.winter);
3533
- film.mix(beat.mix ?? null);
3534
- film.tag(Array.isArray(beat.build) && beat.build[1] > beat.build[0]);
3535
- film.rain(beat.rain ?? null);
3536
- film.rim(beat.rim ?? null, true);
3537
- film.quality?.(beat.quality ?? null);
3538
- film.grass?.(beat.grass ?? false);
3539
- if (beat.theme !== void 0) this.hourNow = beat.theme;
3540
- film.city?.(beat.city ?? "glass", this.hourNow === 3 ? .08 : this.cityGlass);
3541
- this.args.onAir?.(beat, film, hard);
3542
- }
3543
- /**
3544
- * THE HOUR A CUT INHERITS.
3545
- *
3546
- * Theme and weather are stated where they CHANGE, so a beat halfway
3547
- * through the film usually says nothing about either — which is right
3548
- * for a film played from the top and wrong for one dropped into. A
3549
- * deep link (or a chapter skip) would open in whatever sky was last
3550
- * set, which since the front door became an evening meant the
3551
- * construction chapter got built at dusk. So a cut resolves its own
3552
- * hour first: walk back to the last beat that named one, and take it.
3553
- */
3554
- settleAir(index) {
3555
- const film = this.film;
3556
- if (!film) return;
3557
- for (let i = index; i >= 0; i -= 1) {
3558
- const t = this.all[i]?.theme;
3559
- if (t !== void 0) {
3560
- film.theme(t, true);
3561
- break;
3562
- }
3563
- }
3564
- for (let i = index; i >= 0; i -= 1) {
3565
- const w = this.all[i]?.wx;
3566
- if (w !== void 0) {
3567
- film.wx(w, true);
3568
- break;
3569
- }
3570
- }
3571
- let winter = null;
3572
- for (let i = index; i >= 0; i -= 1) if (this.all[i]?.winter !== void 0) {
3573
- winter = this.all[i].winter;
3574
- break;
3575
- }
3576
- film.winter(winter ?? null);
3577
- }
3578
- shot = (state) => {
3579
- this.goal = {
3580
- dolly: state.dolly,
3581
- fx: state.look?.x ?? 0,
3582
- fz: state.look?.z ?? 0,
3583
- lookY: state.look?.y ?? 0,
3584
- ox: state.x,
3585
- pitch: state.pitch,
3586
- yaw: state.yaw
3587
- };
3588
- };
3589
- dispatch = (command) => {
3590
- if (command.action === "lap") {
3591
- this.end();
3592
- return;
3593
- }
3594
- if (command.action === "air") {
3595
- const i = Number(command.target);
3596
- if (Number.isFinite(i) && this.beats[i]) this.applyAir(this.beats[i]);
3597
- return;
3598
- }
3599
- if (command.action === "beat") {
3600
- const i = Number(command.target);
3601
- if (i === 0 && this.primedLap === this.lap) {
3602
- this.primedLap = -1;
3603
- this.beatIndex = 0;
3604
- return;
3605
- }
3606
- if (Number.isFinite(i) && this.beats[i]) {
3607
- this.beatIndex = i;
3608
- this.applyBeat(this.beats[i]);
3609
- }
3610
- }
3611
- };
3612
- /**
3613
- * PAUSE MEANS EVERYTHING STOPS. The score's run holds where it is (the
3614
- * lens included), the page's own clock holds (grass, weather, the
3615
- * build, the day), the audio graph suspends, and the beat clock is
3616
- * shifted forward by the length of the hold on resume so the hours,
3617
- * the build and the type pick up exactly where they stood. Tearing the
3618
- * Sequence down was the old pause, and it re-ran the chapter on play.
3619
- * An exact film's runs are already standing — its clock is the only
3620
- * thing that has to hold.
3621
- */
3622
- toggle = () => {
3623
- if (!this.playing) return;
3624
- this.paused = !this.paused;
3625
- this.pop(this.paused ? "pause" : "play");
3626
- if (!this.exact) {
3627
- const run = this.scoreCtx?.run ?? null;
3628
- if (this.paused) {
3629
- this.pausedAt = performance.now();
3630
- run?.pause();
3631
- } else {
3632
- this.beatAt += performance.now() - this.pausedAt;
3633
- run?.play();
3634
- }
3635
- }
3636
- this.film?.hold?.(this.paused);
3637
- this.film?.pause(this.paused);
3638
- };
3639
- /** any re-cut or restart clears a hold: the new run starts rolling */
3640
- unhold() {
3641
- if (this.paused) {
3642
- this.paused = false;
3643
- this.film?.hold?.(false);
3644
- this.film?.pause(false);
3645
- }
3646
- }
3647
- /**
3648
- * SKIP A CHAPTER — by re-cutting, never by seeking.
3649
- *
3650
- * `from` moves to a chapter head, which changes the beat list, which
3651
- * changes the path, the cues and the sequence's name all at once. The
3652
- * region sees a different score and plays it from its head. Seeking the
3653
- * existing run would be the obvious alternative and it would be wrong:
3654
- * the lens is an integrator whose pose depends on where it has been, so
3655
- * a run dropped into the middle of itself arrives with the wrong
3656
- * velocity — the same reason `packages/choreo-gallery/realm/design/sylva-one-world.md` calls the chaser
3657
- * seek-unsafe and means it.
3658
- */
3659
- goChapter = (delta) => {
3660
- const heads = this.chapterHeads;
3661
- const here = this.absoluteIndex;
3662
- let i = heads.findIndex((h, n) => here >= h && (heads[n + 1] ?? Infinity) > here);
3663
- if (i < 0) i = 0;
3664
- const next = delta < 0 && here > heads[i] ? i : Math.max(0, Math.min(heads.length - 1, i + delta));
3665
- this.cutTo(heads[next]);
3666
- };
3667
- cutTo(index, hard = false) {
3668
- this.unhold();
3669
- if (this.exact) {
3670
- this.seek(this.beatStart(index));
3671
- return;
3672
- }
3673
- this.from = index;
3674
- this.beatIndex = 0;
3675
- this.ended = false;
3676
- this.playing = true;
3677
- this.lap += 1;
3678
- this.primedLap = this.lap;
3679
- this.settleAir(index);
3680
- this.applyBeat(this.all[index], true);
3681
- this.snap(this.all[index].cam, hard ? void 0 : this.tailFor(0), hard ? void 0 : this.all[index].ticks * 2);
3682
- }
3683
- /** an uncaught error anywhere becomes a visible line — a film that
3684
- * dies silently mid-reel cannot be debugged from a chair */
3685
- trip = (e) => {
3686
- if (this.fault) return;
3687
- const err = e;
3688
- this.fault = String(err.message ?? err.reason ?? "unknown fault").slice(0, 200);
3689
- };
3690
- prev = () => this.goChapter(-1);
3691
- next = () => this.goChapter(1);
3692
- key = (e) => {
3693
- if (e.metaKey || e.ctrlKey || e.altKey) return;
3694
- if (this.gate) {
3695
- if (e.key === " " || e.key === "Enter") {
3696
- e.preventDefault();
3697
- this.begin(true);
3698
- }
3699
- return;
3700
- }
3701
- if (this.ended && (e.key === " " || e.key === "Enter")) {
3702
- e.preventDefault();
3703
- this.replay();
3704
- return;
3705
- }
3706
- if (e.key === "ArrowRight") {
3707
- e.preventDefault();
3708
- this.next();
3709
- } else if (e.key === "ArrowLeft") {
3710
- e.preventDefault();
3711
- this.prev();
3712
- } else if (e.key === " " || e.key === "k" || e.key === "K") {
3713
- e.preventDefault();
3714
- this.toggle();
3715
- } else if (e.key === "f" || e.key === "F") this.screen();
3716
- else if (e.key >= "1" && e.key <= "9") {
3717
- const head = this.chapterHeads[Number(e.key) - 1];
3718
- if (head !== void 0) this.pick(head);
3719
- } else if (e.key === "0" || e.key === "Home") this.restart();
3720
- else if (e.key === "m" || e.key === "M") this.hear();
3721
- else if (e.key === "c" || e.key === "C" || e.key === "Escape") this.toc();
3722
- else if (e.key === "v" || e.key === "V") this.cc();
3723
- };
3724
- /**
3725
- * THE NARRATION, one file per beat.
3726
- *
3727
- * `assets/vo/<id>.mp3`, played on the beat's entrance and stopped
3728
- * when the beat changes. Per beat rather than one long track because
3729
- * chapter skip RE-CUTS the film: a single track would have to be sought,
3730
- * and a sought track against a re-cut score drifts inside a chapter.
3731
- *
3732
- * A missing file is silence, not an error. The film ships before the
3733
- * voice does, and the score has to be right either way. While a line
3734
- * plays the scene's own music ducks under it, which is the one piece of
3735
- * mixing that cannot wait for a mix.
3736
- */
3737
- voSrc(beat) {
3738
- return `${this.assets}vo/${beat.id}.mp3`;
3739
- }
3740
- /** ask the page to fetch and decode the line after this one, now */
3741
- primeNext(beat) {
3742
- const next = this.all[this.all.indexOf(beat) + 1];
3743
- if (next?.vo) this.film?.voicePrime(this.voSrc(next));
3744
- }
3745
- /**
3746
- * A BOUNDARY NEVER CLIPS THE VOICE — and every line has its own
3747
- * throat. One shared element made politeness impossible: however
3748
- * gently the old line was being faded, the new line's src swap
3749
- * guillotined it mid-word. So an interrupted line keeps its OWN
3750
- * element and fades there (~120ms, fast enough to read as a stop,
3751
- * slow enough that no waveform is cut mid-cycle) while the new line
3752
- * starts clean on a fresh one — the game-dialogue barge-in, which is
3753
- * what a chapter skip actually is. ("Stop at the next word" is the
3754
- * refinement this is built to take: an analyser watching for the
3755
- * inter-word trough before the fade — see packages/choreo-gallery/realm/docs/choreo-splices.md.)
3756
- */ /**
3757
- * A JOIN, EXHIBITED. Any seam as a pure overlay on whatever is playing —
3758
- * no beat change, no snap: the transition itself, on the living
3759
- * picture. What a cutting room's wall plate triggers.
3760
- */
3761
- preview = (join) => {
3762
- const film = this.film;
3763
- if (!film || !this.booted) return;
3764
- if ((join === "blend" || join === "melt") && film.dissolve(join)) {
3765
- this.freeze = "";
3766
- return;
3767
- }
3768
- if (join !== "flash" && join !== "sweep" && join !== "whip") {
3769
- const shot = film.snapshot();
3770
- this.freeze = shot.length > 64 ? shot : "";
3771
- if (!this.freeze && join !== "dip") return;
3772
- }
3773
- switch (join) {
3774
- case "defocus":
3775
- this.play("defocus");
3776
- this.liveEl?.animate([{ filter: "blur(9px)" }, { filter: "blur(0px)" }], {
3777
- duration: 700,
3778
- easing: "cubic-bezier(0.3, 0, 0.3, 1)"
3779
- });
3780
- break;
3781
- case "dip":
3782
- this.dipColor = "#0d0905";
3783
- this.play("dip");
3784
- break;
3785
- case "iris": {
3786
- const v = film.view();
3787
- const b = this.beat;
3788
- const pt = b.to ? film.project(...b.to) : void 0;
3789
- const cx = pt ? Math.max(12, Math.min(88, pt.x / v.w * 100)) : 50;
3790
- const cy = pt ? Math.max(12, Math.min(88, pt.y / v.h * 100)) : 46;
3791
- this.irisAt = `--ix:${cx.toFixed(1)}%;--iy:${cy.toFixed(1)}%`;
3792
- this.play("iris");
3793
- break;
3794
- }
3795
- case "sweep":
3796
- this.lightSweep(film, this.beat);
3797
- break;
3798
- case "whip":
3799
- this.whipUntil = performance.now() + 360;
3800
- break;
3801
- case "cut": break;
3802
- default: this.play(join);
3803
- }
3804
- };
3805
- /** the wrapper the live picture sits in, for rack-defocus */
3806
- liveEl;
3807
- liveWrap = modifier((el) => {
3808
- this.liveEl = el;
3809
- return () => {
3810
- if (this.liveEl === el) this.liveEl = void 0;
3811
- };
3812
- });
3813
- /**
3814
- * THE LIGHT SWEEP: the sun itself flares across the seam — swings
3815
- * high and past, then settles back onto whatever light the beat
3816
- * actually asked for. Fire-and-forget; the restore hands the key
3817
- * back to the beat's own sun (or the hour's).
3818
- */
3819
- lightSweep(film, beat) {
3820
- const t0 = performance.now();
3821
- const dur = 950;
3822
- const base = beat.sun ? {
3823
- az: beat.sun.az * RAD,
3824
- el: beat.sun.el * RAD
3825
- } : null;
3826
- const step = () => {
3827
- const f = (performance.now() - t0) / dur;
3828
- if (f >= 1 || !this.film) {
3829
- film.light(base);
3830
- return;
3831
- }
3832
- const swing = Math.sin(f * Math.PI);
3833
- film.light({
3834
- az: (base?.az ?? .4) + (1 - f) * 2.2 - 1.1,
3835
- el: (base?.el ?? .4) + swing * .3
3836
- });
3837
- requestAnimationFrame(step);
3838
- };
3839
- requestAnimationFrame(step);
3840
- }
3841
- /** the scene brought its own score — six of them, one per tower */ /** the line for this beat, at the level the session was mixed to */
3842
- speak(beat) {
3843
- this.film?.voice(this.voSrc(beat), this.voGain[beat.id] ?? 1);
3844
- this.primeNext(beat);
3845
- }
3846
- hush() {
3847
- this.film?.voiceStop();
3848
- }
3849
- hear = () => {
3850
- this.sound = !this.sound;
3851
- this.film?.sound(this.sound);
3852
- if (this.sound) this.speak(this.beat);
3853
- else this.hush();
3854
- };
3855
- /**
3856
- * FROM THE TOP means the front door, not the first beat.
3857
- *
3858
- * A film restarted straight into its opening shot skips the one piece
3859
- * of the picture that says what it is — and the poster is a
3860
- * composition in its own right, with the building turning in the last
3861
- * of the light. So this clears everything the run has done and puts
3862
- * the door back up, with the lens seated where it opens.
3863
- */
3864
- restart = () => {
3865
- const film = this.film;
3866
- this.unhold();
3867
- this.freeze = "";
3868
- this.film?.voiceStop(120);
3869
- this.playing = false;
3870
- this.ended = false;
3871
- this.menu = false;
3872
- this.from = 0;
3873
- this.beatIndex = 0;
3874
- this.markSeated = false;
3875
- this.skyOn = 0;
3876
- this.skyA = 0;
3877
- this.dimNow = 0;
3878
- this.sent = void 0;
3879
- this.t = 0;
3880
- this.jumped = false;
3881
- this.seamAt = -1;
3882
- this.endSeam();
3883
- this.airedFor = -1;
3884
- this.scoreRun = null;
3885
- this.clipNow.clear();
3886
- this.clipRows = [];
3887
- this.clipFreezes.clear();
3888
- if (film) {
3889
- film.modelFade(1);
3890
- film.rain(null);
3891
- film.rim(null);
3892
- film.fade("mark", 0);
3893
- film.fade("chapter", 0);
3894
- for (let i = 0; i < 4; i++) film.clearTrace(`t${i}`);
3895
- film.duck(1);
3896
- film.idle(false);
3897
- this.idled = false;
3898
- film.time(this.standing);
3899
- this.picture?.seat?.(film);
3900
- this.dressPoster(film);
3901
- this.snap((this.all[0] ?? EMPTY_BEAT).cam);
3902
- }
3903
- this.booted = false;
3904
- this.rolling = false;
3905
- this.gate = true;
3906
- this.posterAt = 0;
3907
- this.posterRaf ??= requestAnimationFrame(this.poster);
3908
- };
3909
- /** a choice made at the door before the scene finished loading */
3910
- wanted;
3911
- /** through the gate — on the click the audio policy was waiting for */
3912
- begin = (withSound) => {
3913
- if (this.booted) {
3914
- this.gate = false;
3915
- if (withSound !== this.sound) this.hear();
3916
- return;
3917
- }
3918
- if (!this.film) {
3919
- this.wanted = withSound;
3920
- return;
3921
- }
3922
- this.gate = false;
3923
- if (this.posterRaf !== void 0) {
3924
- cancelAnimationFrame(this.posterRaf);
3925
- this.posterRaf = void 0;
3926
- }
3927
- this.sound = withSound;
3928
- this.film.sound(withSound);
3929
- if (this.exact) {
3930
- const head = this.from;
3931
- this.from = 0;
3932
- this.t = this.beatStart(head);
3933
- this.beatIndex = head;
3934
- this.jumped = false;
3935
- this.seamAt = -1;
3936
- this.endSeam();
3937
- this.airedFor = -1;
3938
- }
3939
- const first = this.exact ? this.all[this.beatIndex] : this.beats[0] ?? EMPTY_BEAT;
3940
- this.settleAir(this.exact ? this.beatIndex : this.from);
3941
- this.applyBeat(first, true);
3942
- this.applyAir(first, false);
3943
- /**
3944
- * THE CLICK IS ANSWERED IN THE SAME FRAME — and answered with the
3945
- * biggest move in the film. The lens does not cut to the opening
3946
- * shot; it FLIES there from wherever the poster's circuit had
3947
- * reached, on the stiff spring, so the first thing the viewer sees
3948
- * after pressing the button is the camera taking off. A door that
3949
- * opens onto a still frame reads as a page that did not hear you.
3950
- */
3951
- if (this.posterPose && !this.exact) {
3952
- this.now = { ...this.posterPose };
3953
- this.mid = { ...this.posterPose };
3954
- this.goal = { ...(this.beats[0] ?? EMPTY_BEAT).cam };
3955
- this.whipUntil = performance.now() + 1200;
3956
- for (const k of [
3957
- "dolly",
3958
- "fx",
3959
- "fz",
3960
- "lookY",
3961
- "ox",
3962
- "pitch",
3963
- "yaw"
3964
- ]) {
3965
- this.midV[k] = 0;
3966
- this.nowV[k] = 0;
3967
- }
3968
- } else this.snap(first.cam, this.tailFor(0), first.ticks * 2);
3969
- this.playing = true;
3970
- this.paused = false;
3971
- this.booted = true;
3972
- this.wake();
3973
- this.lastTick = performance.now();
3974
- this.raf = requestAnimationFrame(this.frame);
3975
- /**
3976
- * ONE MORE PASS, on purpose. A region's first render collects no
3977
- * score — `firstRender` compiles an empty tree — so the run only
3978
- * exists after a SECOND pass, and that pass has to be paid for by a
3979
- * tracked write somewhere. Every cut so far happened to buy one
3980
- * within a second (a cue firing, a cycle stepping); a cut whose head
3981
- * beat writes nothing — one quiet beat, deep-linked — never did, and
3982
- * the film stood at its first pose forever. Bumping the lap edits
3983
- * the sequence's name, which is a real edit the region must collect.
3984
- */
3985
- window.setTimeout(() => {
3986
- this.lap += 1;
3987
- this.primedLap = this.lap;
3988
- this.rolling = true;
3989
- }, 0);
3990
- };
3991
- /** the whole film's running time, said the way a poster says it */
3992
- get runtime() {
3993
- const s = this.all.reduce((n, b) => n + b.ticks, 0) * 2;
3994
- return `${Math.floor(s / 60)} min ${s % 60 ? `${s % 60} s` : ""}`.trim();
3995
- }
3996
- /**
3997
- * BACK IN FROM THE END CARD — a dissolve, not a flight.
3998
- *
3999
- * The film ends three hundred degrees around the building from where
4000
- * it opened, so tweening the lens home spins it like a globe. The
4001
- * ending cross-fades into the opening frame instead: the last frame
4002
- * is held as a still, the camera is PLACED at the opening pose behind
4003
- * it, and the still fades away.
4004
- */ /**
4005
- * BACK IN FROM THE END CARD — a hard reset, not a journey.
4006
- *
4007
- * The film ends three hundred degrees around the building, with the
4008
- * tower dissolved, the sky at golden hour, the rain gone and a chapter
4009
- * numeral on screen. Tweening any of that back to the opening is a
4010
- * confusing ten seconds in which nothing is either the ending or the
4011
- * beginning. So the ending is CLEARED: every overlay, the model, the
4012
- * weather, the world type, the traces and the lens all go back to
4013
- * their opening state in one frame, and the film starts.
4014
- */ /** the end card's WATCH AGAIN is the front door, not the first beat: the
4015
- * choice of sound is made there, every time */
4016
- replay = () => {
4017
- this.restart();
4018
- };
4019
- /** the chapter menu. The film keeps running behind it, blurred: a
4020
- * menu that freezes the picture makes the picture feel like a file, and
4021
- * this one is meant to feel like a broadcast you are stepping around in */
4022
- static {
4023
- decorateFieldV2(this.prototype, "menu", [tracked], function() {
4024
- return false;
4025
- });
4026
- }
4027
- #menu = (initializeDeferredDecorator(this, "menu"), void 0);
4028
- toc = () => {
4029
- this.menu = !this.menu;
4030
- };
4031
- /** every chapter, with the beat it starts at and whether we are in it */
4032
- get contents() {
4033
- const here = this.absoluteIndex;
4034
- const heads = this.chapterHeads;
4035
- return contents(this.all, this.chapters).map((c, i) => ({
4036
- ...c,
4037
- here: here >= c.head && here < (heads[i + 1] ?? this.all.length)
4038
- }));
4039
- }
4040
- pick = (head) => {
4041
- this.menu = false;
4042
- this.cutTo(head);
4043
- };
4044
- static {
4045
- setComponentTemplate(templateFactory({
4046
- "id": "yWP73lx5",
4047
- "block": "[[[10,0],[15,0,[29,[\"cf-page is-\",[30,1],\"\\n \",[52,[30,0,[\"embed\"]],\"is-embed\"],\"\\n \",[52,[30,0,[\"idle\"]],\"is-idle\"]]]],[14,\"data-scopedcss-82d0acbd35-8cfaa93df9\",\"\"],[12],[1,\"\\n\"],[1,\" \"],[11,0],[16,0,[29,[\"cf-stage is-grade-\",[30,0,[\"grade\"]],\" \",[52,[30,0,[\"subsOn\"]],\"has-subs\"]]]],[24,\"data-scopedcss-82d0acbd35-8cfaa93df9\",\"\"],[4,[30,0,[\"mount\"]],null,null],[4,[32,0],[\"click\",[30,0,[\"tap\"]]],null],[4,[32,0],[\"dblclick\",[30,0,[\"tapTwice\"]]],null],[12],[1,\"\\n\"],[1,\" \"],[11,0],[24,0,\"cf-live\"],[24,\"data-scopedcss-82d0acbd35-8cfaa93df9\",\"\"],[4,[30,0,[\"liveWrap\"]],null,null],[12],[1,\"\\n \"],[10,\"iframe\"],[14,0,\"cf-frame\"],[15,\"src\",[30,0,[\"src\"]]],[15,\"srcdoc\",[30,0,[\"srcdoc\"]]],[15,\"title\",[30,0,[\"frameTitle\"]]],[14,\"loading\",\"eager\"],[14,\"data-scopedcss-82d0acbd35-8cfaa93df9\",\"\"],[12],[13],[1,\"\\n \"],[13],[1,\"\\n\\n\"],[41,[51,[30,0,[\"washInGlass\"]]],[[[1,\" \"],[10,0],[15,0,[29,[\"cf-scrim cf-scrim-\",[30,0,[\"beat\",\"mode\"]],\"\\n \",[52,[30,0,[\"beat\",\"bare\"]],\"is-bare\"]]]],[14,\"aria-hidden\",\"true\"],[14,\"data-scopedcss-82d0acbd35-8cfaa93df9\",\"\"],[12],[13],[1,\"\\n\"]],[]],null],[1,\"\\n\"],[1,\" \"],[11,0],[16,0,[29,[\"cf-joins \",[52,[30,0,[\"joinOver\"]],\"is-over\"]]]],[24,\"data-scopedcss-82d0acbd35-8cfaa93df9\",\"\"],[4,[30,0,[\"joinsWrap\"]],null,null],[12],[1,\"\\n \"],[8,[32,1],[[24,\"data-scopedcss-82d0acbd35-8cfaa93df9\",\"\"]],[[\"@kind\",\"@stamp\",\"@freeze\",\"@irisAt\",\"@dipColor\",\"@seekable\",\"@presentations\"],[[30,0,[\"joinKind\"]],[30,0,[\"joinStamp\"]],[30,0,[\"freeze\"]],[30,0,[\"irisAt\"]],[30,0,[\"dipColor\"]],[30,0,[\"exact\"]],[30,0,[\"presentations\"]]]],null],[1,\"\\n \"],[13],[1,\"\\n\"],[41,[51,[30,0,[\"cloudInGlass\"]]],[[[1,\" \"],[10,0],[14,0,\"cf-cloud\"],[14,\"aria-hidden\",\"true\"],[14,\"data-scopedcss-82d0acbd35-8cfaa93df9\",\"\"],[12],[13],[1,\"\\n\"]],[]],null],[1,\"\\n\"],[41,[51,[30,0,[\"dimInGlass\"]]],[[[1,\" \"],[11,0],[24,0,\"cf-dim\"],[24,\"aria-hidden\",\"true\"],[24,\"data-scopedcss-82d0acbd35-8cfaa93df9\",\"\"],[4,[30,0,[\"dimEl\"]],null,null],[12],[13],[1,\"\\n\"]],[]],null],[1,\"\\n\"],[1,\" \"],[8,[32,2],[[24,\"data-scopedcss-82d0acbd35-8cfaa93df9\",\"\"]],[[\"@wire\"],[[30,0,[\"trackEls\"]]]],null],[1,\"\\n\\n\"],[41,[30,0,[\"booted\"]],[[[1,\" \"],[8,[32,3],[[24,\"data-scopedcss-82d0acbd35-8cfaa93df9\",\"\"]],[[\"@beat\",\"@chapterN\",\"@rolling\",\"@typeAt\",\"@sayAt\",\"@glyphFit\",\"@glyphTone\",\"@mount\"],[[30,0,[\"beat\"]],[30,0,[\"chapter\",\"n\"]],[30,0,[\"rolling\"]],[30,0,[\"typeAt\"]],[30,0,[\"sayAt\"]],[30,0,[\"glyphFit\"]],[30,0,[\"glyphTone\"]],[30,0,[\"plate\"]]]],null],[1,\"\\n\\n \"],[8,[32,4],[[24,\"data-scopedcss-82d0acbd35-8cfaa93df9\",\"\"]],[[\"@step\",\"@word\"],[[30,0,[\"cycleStep\"]],[30,0,[\"stamp\"]]]],null],[1,\"\\n\\n\"],[42,[28,[31,3],[[28,[31,3],[[30,0,[\"clipRows\"]]],null]],null],\"lane\",[[[1,\" \"],[8,[32,5],[[24,\"data-scopedcss-82d0acbd35-8cfaa93df9\",\"\"]],[[\"@key\",\"@spec\",\"@src\",\"@freeze\",\"@state\",\"@lane\",\"@mount\"],[[30,2,[\"key\"]],[30,2,[\"spec\"]],[30,2,[\"src\"]],[30,2,[\"freeze\"]],[30,2,[\"state\"]],[30,2,[\"lane\"]],[30,0,[\"clipEl\"]]]],null],[1,\"\\n\"]],[2]],null],[1,\"\\n\"],[41,[52,[30,0,[\"rolling\"]],[30,0,[\"hasPhoto\"]],false],[[[1,\" \"],[8,[32,6],[[24,\"data-scopedcss-82d0acbd35-8cfaa93df9\",\"\"]],[[\"@beat\",\"@src\",\"@missing\"],[[30,0,[\"beat\"]],[30,0,[\"photoSrc\"]],[30,0,[\"missing\"]]]],null],[1,\"\\n\"]],[]],null],[1,\"\\n\"],[41,[30,0,[\"subs\"]],[[[1,\" \"],[8,[32,7],[[24,\"data-scopedcss-82d0acbd35-8cfaa93df9\",\"\"]],[[\"@line\"],[[30,0,[\"beat\",\"vo\"]]]],null],[1,\"\\n\"]],[]],null],[1,\"\\n\"],[41,[30,0,[\"fault\"]],[[[1,\" \"],[10,2],[14,0,\"cf-fault\"],[14,\"data-scopedcss-82d0acbd35-8cfaa93df9\",\"\"],[12],[1,\"⚠ \"],[1,[30,0,[\"fault\"]]],[13],[1,\"\\n\"]],[]],null],[1,\"\\n\"],[41,[30,0,[\"railOn\"]],[[[1,\" \"],[8,[32,8],[[24,\"data-scopedcss-82d0acbd35-8cfaa93df9\",\"\"]],[[\"@chapter\",\"@marks\",\"@labels\",\"@readout\",\"@pick\"],[[30,0,[\"chapter\"]],[30,0,[\"transport\"]],[30,0,[\"railLabels\"]],[30,0,[\"railReadout\"]],[30,0,[\"pick\"]]]],null],[1,\"\\n\"]],[]],null]],[]],null],[1,\"\\n\"],[41,[30,0,[\"booted\"]],[[[1,\" \"],[8,[32,9],[[24,\"data-scopedcss-82d0acbd35-8cfaa93df9\",\"\"]],[[\"@onCamera3D\",\"@onPerform\",\"@camera3dFrom\"],[[30,0,[\"shot\"]],[30,0,[\"dispatch\"]],[30,0,[\"openPose\"]]]],[[\"default\"],[[[[1,\"\\n \"],[11,\"i\"],[24,0,\"cf-rig\"],[24,\"aria-hidden\",\"true\"],[24,\"data-scopedcss-82d0acbd35-8cfaa93df9\",\"\"],[4,[32,10],null,[[\"id\"],[\"rig\"]]],[4,[30,0,[\"grabScore\"]],[[30,3]],null],[12],[13],[1,\"\\n\"],[41,[30,0,[\"scoreOn\"]],[[[1,\" \"],[8,[30,3,[\"Sequence\"]],[[24,\"data-scopedcss-82d0acbd35-8cfaa93df9\",\"\"]],[[\"@name\"],[[30,0,[\"filmName\"]]]],[[\"default\"],[[[[1,\"\\n \"],[8,[30,3,[\"Camera3D\"]],[[24,\"data-scopedcss-82d0acbd35-8cfaa93df9\",\"\"]],[[\"@name\",\"@through\",\"@duration\",\"@ease\",\"@settle\",\"@tension\"],[\"film\",[30,0,[\"path\"]],[30,0,[\"filmSeconds\"]],\"linear\",[30,0,[\"settle\"]],0.34]],null],[1,\"\\n\"],[42,[28,[31,3],[[28,[31,3],[[30,0,[\"windows\"]]],null]],null],null,[[[1,\" \"],[8,[30,3,[\"Attach\"]],[[24,\"data-scopedcss-82d0acbd35-8cfaa93df9\",\"\"]],[[\"@region\",\"@at\",\"@delay\",\"@duration\",\"@end\",\"@exact\"],[[30,4,[\"region\"]],[28,[32,11],[\"film\"],null],[30,4,[\"start\"]],[30,4,[\"length\"]],[30,4,[\"end\"]],[30,0,[\"exact\"]]]],null],[1,\"\\n\"]],[4]],null],[41,[51,[30,0,[\"exact\"]]],[[[42,[28,[31,3],[[28,[31,3],[[30,0,[\"cues\"]]],null]],null],null,[[[1,\" \"],[8,[30,3,[\"Perform\"]],[[24,\"data-scopedcss-82d0acbd35-8cfaa93df9\",\"\"]],[[\"@at\",\"@delay\",\"@action\",\"@target\"],[[28,[32,11],[\"film\"],null],[30,5,[\"delay\"]],[30,5,[\"action\"]],[30,5,[\"index\"]]]],null],[1,\"\\n\"]],[5]],null],[1,\" \"],[8,[30,3,[\"Perform\"]],[[24,\"data-scopedcss-82d0acbd35-8cfaa93df9\",\"\"]],[[\"@action\"],[\"lap\"]],null],[1,\"\\n\"]],[]],null],[1,\" \"]],[]]]]],[1,\"\\n\"]],[]],null],[1,\" \"]],[3]]]]],[1,\"\\n\"]],[]],null],[1,\"\\n\"],[41,[30,0,[\"gate\"]],[[[1,\" \"],[8,[32,12],[[24,\"data-scopedcss-82d0acbd35-8cfaa93df9\",\"\"]],[[\"@film\"],[[30,0,[\"handle\"]]]],[[\"default\"],[[[[1,\"\\n \"],[18,10,[[30,6]]],[1,\"\\n \"]],[6]]]]],[1,\"\\n\"]],[]],null],[1,\"\\n\"],[41,[30,0,[\"ended\"]],[[[1,\" \"],[8,[32,13],[[24,\"data-scopedcss-82d0acbd35-8cfaa93df9\",\"\"]],[[\"@film\"],[[30,0,[\"handle\"]]]],[[\"default\"],[[[[1,\"\\n \"],[18,11,[[30,7]]],[1,\"\\n \"]],[7]]]]],[1,\"\\n\"]],[]],null],[1,\" \"],[13],[1,\"\\n\\n\"],[41,[30,0,[\"menu\"]],[[[1,\" \"],[8,[32,14],[[24,\"data-scopedcss-82d0acbd35-8cfaa93df9\",\"\"]],[[\"@title\",\"@sub\",\"@contents\",\"@pick\"],[[30,8],[52,[30,9],[30,9],\"chapters\"],[30,0,[\"contents\"]],[30,0,[\"pick\"]]]],null],[1,\"\\n\"]],[]],null],[1,\"\\n\"],[41,[30,0,[\"burst\"]],[[[1,\" \"],[8,[32,15],[[24,\"data-scopedcss-82d0acbd35-8cfaa93df9\",\"\"]],[[\"@kind\",\"@done\"],[[30,0,[\"burst\"]],[30,0,[\"burstDone\"]]]],null],[1,\"\\n\"]],[]],null],[1,\"\\n \"],[8,[32,16],[[24,\"data-scopedcss-82d0acbd35-8cfaa93df9\",\"\"]],[[\"@idle\",\"@away\",\"@live\",\"@sound\",\"@loud\",\"@vol\",\"@volStyle\",\"@fade\",\"@full\",\"@clock\",\"@duration\",\"@chapter\",\"@subsOn\",\"@menu\",\"@debug\",\"@build\",\"@playbar\",\"@scrubAt\",\"@tip\",\"@slider\",\"@trackWrap\",\"@scrubDown\",\"@scrubMove\",\"@scrubUp\",\"@scrubLeave\",\"@toggle\",\"@prev\",\"@next\",\"@hear\",\"@cc\",\"@toc\",\"@restart\",\"@screen\"],[[30,0,[\"idle\"]],[30,0,[\"gate\"]],[30,0,[\"live\"]],[30,0,[\"sound\"]],[30,0,[\"loud\"]],[30,0,[\"vol\"]],[30,0,[\"volStyle\"]],[30,0,[\"fade\"]],[30,0,[\"full\"]],[30,0,[\"clock\"]],[30,0,[\"duration\"]],[30,0,[\"chapter\"]],[30,0,[\"subsOn\"]],[30,0,[\"menu\"]],[30,0,[\"debug\"]],[30,0,[\"build\"]],[30,0,[\"playbar\"]],[30,0,[\"scrubAt\"]],[30,0,[\"tip\"]],[30,0,[\"slider\"]],[30,0,[\"trackWrap\"]],[30,0,[\"scrubDown\"]],[30,0,[\"scrubMove\"]],[30,0,[\"scrubUp\"]],[30,0,[\"scrubLeave\"]],[30,0,[\"toggle\"]],[30,0,[\"prev\"]],[30,0,[\"next\"]],[30,0,[\"hear\"]],[30,0,[\"cc\"]],[30,0,[\"toc\"]],[30,0,[\"restart\"]],[30,0,[\"screen\"]]]],null],[1,\"\\n\\n\"],[1,\" \"],[18,12,[[30,0,[\"takePicture\"]]]],[1,\"\\n \"],[8,[32,17],[[24,\"data-scopedcss-82d0acbd35-8cfaa93df9\",\"\"]],[[\"@onCompile\"],[[30,0,[\"takeGraph\"]]]],[[\"default\"],[[[[1,\"\\n \"],[18,13,[[30,0,[\"handle\"]]]],[1,\"\\n \"]],[]]]]],[1,\"\\n\"],[13],[1,\"\\n\\n\"],[10,\"style\"],[12],[1,\"\\n /* THE FLAG, as an accent system. Two colours and nothing else:\\n the red of the hinomaru for the one thing that is happening NOW\\n — the playhead, the current chapter, the seal, the point a line\\n is drawn to — and a full white for type that has to win over a\\n picture. Everything else stays in the scene's own inks, so the\\n red never becomes decoration; it only ever means \\\"here\\\". */\\n .cf-page {\\n --cf-red: #bc002d;\\n --cf-white: #fffdf8;\\n }\\n\\n .cf-page {\\n /* the film's ink, replaced per chapter from the scene's own\\n time-of-day palette by wear — these are the morning values,\\n which is also what the page opens on. --cf-shx/y is the cast\\n shadow's offset, written every frame from where the scene's key\\n light actually is (see follow). */\\n --cf-paper: #ecdcbc;\\n --cf-paper-a: rgba(236, 220, 188, 0.94);\\n --cf-paper-b: rgba(236, 220, 188, 0.76);\\n --cf-paper-c: rgba(236, 220, 188, 0);\\n --cf-ink: #2e2515;\\n --cf-ink2: #8b7c5c;\\n --cf-ink3: #3f3520;\\n --cf-accent: #a8621f;\\n /* THE ANNOTATION RED, and it is a different colour from the\\n editorial accent on purpose. The accent is ink — it belongs to\\n the page and takes the page's palette. This is a marker: it is\\n not in the scene, it never was, and it should look like\\n somebody drew on the photograph. Muted burnt orange over ochre\\n moss reads as part of the picture, which is exactly what an\\n annotation must not do. */\\n --cf-mark: #ff2412;\\n --cf-rule: #c2b18c;\\n /* The sun, in two numbers: cf-rake is the angle its light makes\\n across the frame, cf-shx/cf-shy the direction away from it.\\n They dress the things that are PHYSICALLY on the frame — the\\n photograph's own plate, the rake of a rule — and deliberately\\n not the type. An offset shadow behind a headline is a sticker\\n effect: it claims the letters are objects lying on the picture,\\n which they are not, and getting the angle right does not rescue\\n it. Type here earns its contrast from the scrim. */\\n /* TYPE. The display face is a serif on purpose. This film is set\\n beside mincho kanji and stands in front of a building, and a\\n wide-tracked geometric sans fights both — it is also the first\\n thing every deck reaches for, which is reason enough to leave\\n it alone. A warm old-style serif sits with the kanji instead of\\n arguing with it. The sans is kept for small mechanical labels\\n only, and its tracking is pulled well back from the point where\\n letterspaced caps start reading as a logo. */\\n /* THE INFORMATION LAYER IS SET IN THE BASILICA'S OWN VOICE.\\n The 2026 centenary identity is heavy grotesque capitals in flat\\n colour on stone paper — the programme, the plates, the timeline\\n of momentums are all cut that way, and a film about the same\\n building in the same year has no business inventing a second\\n typography for the same facts. Archivo is the closest open face\\n to it: a grotesque with real weight at 800 and none of the\\n geometric-sans blandness. The old-style serif stays for the\\n quote and the caption band, where a sentence is being spoken\\n rather than displayed. */\\n --cf-display: Archivo, 'Helvetica Neue', Helvetica, Arial, sans-serif;\\n --cf-caps: Archivo, 'Helvetica Neue', Helvetica, Arial, sans-serif;\\n --cf-serif:\\n 'Cormorant Garamond', 'Iowan Old Style', 'Palatino Linotype',\\n Palatino, Georgia, serif;\\n --cf-ui: Archivo, 'Helvetica Neue', Helvetica, Arial, sans-serif;\\n /* the identity's four colours, and the chapter's own (set by wear) */\\n --cf-c-red: #d8232a;\\n --cf-c-blue: #1268c3;\\n --cf-c-green: #0f7b62;\\n --cf-c-pink: #e5308a;\\n --cf-chap: #d8232a;\\n --cf-shx: 2px;\\n --cf-shy: 2px;\\n --cf-rake: 35deg;\\n\\n position: relative;\\n min-height: 100svh;\\n background: var(--cf-paper);\\n display: flex;\\n flex-direction: column;\\n transition: background 900ms ease;\\n /* TYPE IS MEASURED AGAINST THE FILM, NOT THE WINDOW. Every size\\n below was written in vw, which is right only when the film\\n IS the window: put the same film in a 384px frame on a 1440px\\n page and every line is sized for the page it is sitting on.\\n An inline-size container makes cqw mean \\\"a hundredth of this\\n film's own width\\\" wherever it is mounted — the demo well, the\\n theater, a phone — so one set of numbers serves all of them.\\n Inline-size only: the height stays the stage's business, and\\n the stage below is its own container for exactly that. */\\n container-name: film;\\n container-type: inline-size;\\n }\\n\\n /* THE STAGE NEVER GOES TALLER THAN IT IS WIDE, and on a phone that\\n is the whole difference between a film and a mess.\\n\\n Every layout inside this box is written for a frame that is not\\n portrait: the type sets in a column beside the subject, the mark\\n draws a line across to it, the sky word runs off the right edge,\\n the rail is a rule. Handed a 9:16 viewport none of that has\\n anywhere to go — the picture crops to a slot, the column has no\\n room beside anything, and the shot the score composed is not the\\n shot on screen. Square is enough to hold all of it; the film\\n does not need a cinema ratio, it needs to not be a column.\\n\\n The height is therefore capped at the width. That has to come\\n from the viewport rather than a percentage, because a percentage\\n max-height resolves against the parent's HEIGHT. Nothing changes\\n on a landscape screen, where 100cqw is comfortably taller than\\n the viewport and the cap never binds; on a phone the film plays\\n as a square, letterboxed on the film's own paper. */\\n .cf-stage {\\n position: relative;\\n height: 100svh;\\n /* the 1:1 floor, measured against the film's own width rather\\n than the window's — the same number, and right in a frame */\\n max-height: 100cqw;\\n flex: none;\\n overflow: hidden;\\n /* AND THE SHOT IS ITS OWN CONTAINER, on both axes. The rules\\n that compress the gate and stand the rail down were written\\n as max-height media queries, which ask about the WINDOW —\\n and the window is not the film. A square film in an 844px\\n phone is 390px tall and needed every one of them; it got\\n none, and the door's stack ran straight through its own\\n buttons. Size containment is legal here because the height\\n above is definite, and it is what lets a height query mean\\n \\\"this shot is short\\\" rather than \\\"this phone is short\\\". */\\n container-name: shot;\\n container-type: size;\\n }\\n\\n /* embedded there is nothing under the stage to hold it up, so the\\n letterbox is split evenly rather than all falling below */\\n .cf-page.is-embed {\\n justify-content: center;\\n }\\n\\n .cf-live {\\n position: absolute;\\n inset: 0;\\n }\\n\\n /* THE SCENE DOES NOT TAKE THE POINTER. It is a picture: it has no\\n controls of its own in film mode, and while it swallowed events\\n the wheel went to the iframe's document instead of this page —\\n so the film filled the viewport and the wall plate underneath\\n could not be reached. */\\n .cf-frame {\\n pointer-events: none;\\n position: absolute;\\n inset: 0;\\n width: 100%;\\n height: 100%;\\n border: 0;\\n display: block;\\n /* the primary: contrast, saturation and lift, per grade */\\n }\\n\\n /* ---- the grade ------------------------------------------------- *\\n Two passes, the way a colourist works: a primary on the picture\\n itself (above), then a split tone laid over it — warmth into the\\n highlights on one diagonal, coolness into the shadows on the\\n other. Soft-light rather than overlay, because overlay crushes this scene's\\n mid-tones and the whole point is to keep the moss and the plaster\\n legible while moving the mood underneath them.\\n ---------------------------------------------------------------- */\\n\\n /* the five moods. CONTEXT opens warm and open; HISTORY is archival —\\n desaturated, cool, contrastier, the look of a document rather than\\n a day; CONSTRUCTION goes clean and bright, nearly a blueprint;\\n DETAIL is rich and close; COMPARISON is a museum plate, flat and\\n even, because a comparison that flatters one subject is not one. */\\n .is-grade-amber {\\n --cf-lut: saturate(0.9) contrast(0.94) brightness(1.2);\\n --cf-warm: #ffd9a8;\\n --cf-cool: #b9c8e6;\\n --cf-grade-a: 0.3;\\n --cf-vig-a: 0.5;\\n }\\n\\n .is-grade-iron {\\n --cf-lut: saturate(0.72) contrast(0.98) brightness(1.12) sepia(0.1);\\n --cf-warm: #e8d9c2;\\n --cf-cool: #9fb0c8;\\n --cf-grade-a: 0.4;\\n --cf-vig-a: 0.65;\\n }\\n\\n /* MIDDAY, and it is meant to be the brightest thing in the film.\\n The construction chapter is the one that has to read as\\n information — you are watching a building get assembled — so it\\n is pushed up and opened out until it is nearly a working\\n drawing, and it earns its brightness by sitting between two\\n chapters that are deliberately heavier. Contrast between\\n chapters is a bigger effect than contrast inside one. */\\n .is-grade-chalk {\\n --cf-lut: saturate(0.88) contrast(0.96) brightness(1.34);\\n --cf-warm: #fffdf6;\\n --cf-cool: #cfdde8;\\n --cf-grade-a: 0.2;\\n --cf-vig-a: 0.22;\\n }\\n\\n .is-grade-ink {\\n --cf-lut: saturate(0.98) contrast(1) brightness(1.16);\\n --cf-warm: #ffc9a1;\\n --cf-cool: #8fa0c9;\\n --cf-grade-a: 0.32;\\n --cf-vig-a: 0.68;\\n }\\n\\n /* THE NIGHT: the one grade that pulls the picture DOWN. Every other\\n grade lifts, which is right for daylight; a lifted night is dusk. */\\n .is-grade-night {\\n --cf-lut: saturate(0.76) contrast(1.14) brightness(0.76);\\n --cf-warm: #c9c2b4;\\n --cf-cool: #8193b8;\\n --cf-grade-a: 0.5;\\n --cf-vig-a: 0;\\n }\\n\\n /* WET. Rain is not simply the clear grade with drops in it: the\\n light comes from a lid rather than a source, so the picture\\n loses its warmth and most of its contrast, and the corners\\n close in. */\\n .is-grade-wet {\\n --cf-lut: saturate(0.72) contrast(0.92) brightness(0.94)\\n hue-rotate(-4deg);\\n --cf-warm: #cfd6dd;\\n --cf-cool: #7d8b9e;\\n --cf-grade-a: 0.44;\\n --cf-vig-a: 0.8;\\n }\\n\\n .is-grade-plate {\\n --cf-lut: saturate(0.86) contrast(0.95) brightness(1.2);\\n --cf-warm: #f6e8d2;\\n --cf-cool: #c3c8bd;\\n --cf-grade-a: 0.18;\\n --cf-vig-a: 0.36;\\n }\\n\\n /* SIX COUNTRIES, SIX LIGHTS. The comparison holds the framing and\\n the lens still so the BUILDING is the variable — but six towers\\n under one identical sky read as six models on one lawn, which\\n is a diorama, not a comparison. Each gets the tone its own\\n country is remembered in, at a strength you feel and cannot\\n quite name: the plate grade, bent a few degrees. */\\n .is-grade-c-jp {\\n --cf-lut: saturate(0.84) contrast(0.95) brightness(1.22);\\n --cf-warm: #f6e8d2;\\n --cf-cool: #bcc6bb;\\n --cf-grade-a: 0.2;\\n --cf-vig-a: 0.36;\\n }\\n\\n .is-grade-c-cn {\\n --cf-lut: saturate(0.95) contrast(0.97) brightness(1.16) sepia(0.06);\\n --cf-warm: #ffd9a0;\\n --cf-cool: #c0a68e;\\n --cf-grade-a: 0.28;\\n --cf-vig-a: 0.44;\\n }\\n\\n .is-grade-c-vn {\\n --cf-lut: saturate(0.92) contrast(0.94) brightness(1.18)\\n hue-rotate(-6deg);\\n --cf-warm: #eef0cd;\\n --cf-cool: #a7bda8;\\n --cf-grade-a: 0.26;\\n --cf-vig-a: 0.4;\\n }\\n\\n .is-grade-c-th {\\n --cf-lut: saturate(1.02) contrast(0.93) brightness(1.3);\\n --cf-warm: #ffe9ad;\\n --cf-cool: #d3c6a0;\\n --cf-grade-a: 0.24;\\n --cf-vig-a: 0.3;\\n }\\n\\n .is-grade-c-kh {\\n --cf-lut: saturate(0.9) contrast(0.99) brightness(1.14) sepia(0.12);\\n --cf-warm: #f2cfa4;\\n --cf-cool: #b9a184;\\n --cf-grade-a: 0.3;\\n --cf-vig-a: 0.5;\\n }\\n\\n .is-grade-c-tr {\\n --cf-lut: saturate(0.8) contrast(0.96) brightness(1.26);\\n --cf-warm: #fdf3e2;\\n --cf-cool: #a9bacd;\\n --cf-grade-a: 0.22;\\n --cf-vig-a: 0.34;\\n }\\n\\n /* the outgoing frame, swept off along the sun's diagonal behind a\\n FEATHERED edge — a mask, not a clip, because a wipe with a hard\\n edge is a screen transition and a wipe with a soft one is film */\\n /* THE SWEEP IS TWO TRANSFORMS, NOT A MASK MOVING. Sliding\\n mask-position repaints a full-resolution still every frame and\\n the wipe stutters — which is what a wipe must never do. So the\\n mask stays put on a sheet three frames wide, the SHEET slides\\n across the picture, and the still inside slides back by the same\\n amount so it never moves on screen. Both are transforms: the\\n compositor rasterises the masked sheet once and only translates. */\\n /**\\n * THE WIPE, AS A MOVING EDGE RATHER THAN A MOVING BOX.\\n *\\n * It used to be a box three frames wide carrying the gradient, with\\n * the image inside counter-moving so the edge appeared to cross. The\\n * travel was hardcoded up-left while the gradient's angle followed\\n * the SUN, so the two agreed only by luck: projected, the sweep was\\n * 0.92 of its intended distance at one rake, 0.16 at another, 0 at\\n * -27 degrees, and NEGATIVE past 0 — where the edge ran backwards\\n * and the outgoing frame grew back over the incoming one. Sizing the\\n * travel to the angle fixed the projection but not the geometry:\\n * WHICH corner of the box the still starts at also depends on the\\n * angle's quadrant, so at some rakes it began already uncovered.\\n *\\n * So nothing moves now. The element is the frame, the still fills\\n * it, and the two gradient stops are swept from before the frame to\\n * past it by one number. At p = 0 both stops are behind the frame,\\n * so it is wholly opaque; at p = 1 both are past it, so it is wholly\\n * gone. That is true at every angle, which the old one never was.\\n */\\n @property --cf-wipe-p {\\n syntax: '<number>';\\n initial-value: 0;\\n inherits: false;\\n }\\n\\n .cf-swipe {\\n position: absolute;\\n inset: 0;\\n z-index: 0;\\n pointer-events: none;\\n will-change: mask-position, opacity;\\n /* the band is 24% of the gradient's line: soft enough to read as\\n a feathered edge, tight enough to read as an edge */\\n --cf-wipe-a: calc(var(--cf-wipe-p) * 150% - 45%);\\n mask-image: linear-gradient(\\n calc(var(--cf-rake, 35deg) + 72deg),\\n transparent var(--cf-wipe-a),\\n #000 calc(var(--cf-wipe-a) + 24%)\\n );\\n -webkit-mask-image: linear-gradient(\\n calc(var(--cf-rake, 35deg) + 72deg),\\n transparent var(--cf-wipe-a),\\n #000 calc(var(--cf-wipe-a) + 24%)\\n );\\n /* Star Wars speed: a wipe is a STATEMENT, and at two-thirds of a\\n second it reads as a glitch rather than a decision */\\n animation: cf-swipe 1150ms cubic-bezier(0.42, 0, 0.28, 1) forwards;\\n }\\n\\n .cf-swipe img {\\n position: absolute;\\n inset: 0;\\n width: 100%;\\n height: 100%;\\n object-fit: cover;\\n display: block;\\n }\\n\\n @keyframes cf-swipe {\\n 0% {\\n --cf-wipe-p: 0;\\n opacity: 1;\\n }\\n\\n 88% {\\n opacity: 1;\\n }\\n\\n 100% {\\n --cf-wipe-p: 1;\\n opacity: 0;\\n }\\n }\\n\\n .cf-melt {\\n position: absolute;\\n inset: 0;\\n z-index: 0;\\n width: 100%;\\n height: 100%;\\n object-fit: cover;\\n pointer-events: none;\\n animation: cf-melt 1900ms cubic-bezier(0.4, 0, 0.5, 1) forwards;\\n }\\n\\n @keyframes cf-melt {\\n 0% {\\n opacity: 1;\\n }\\n\\n 22% {\\n opacity: 0.92;\\n }\\n\\n 100% {\\n opacity: 0;\\n }\\n }\\n\\n .cf-blend {\\n position: absolute;\\n inset: 0;\\n z-index: 0;\\n width: 100%;\\n height: 100%;\\n object-fit: cover;\\n pointer-events: none;\\n /* the still wears the same primary as the live frame under it,\\n so the dissolve is between two graded pictures — and it is a\\n PUSH dissolve: the old frame travels gently forward as it\\n thins, so the transition has a direction, not just a mix */\\n animation: cf-blend 520ms ease-out forwards;\\n }\\n\\n @keyframes cf-blend {\\n 0% {\\n opacity: 1;\\n transform: scale(1);\\n }\\n\\n 100% {\\n opacity: 0;\\n transform: scale(1.055);\\n }\\n }\\n\\n /* the blur dissolve — and the freeze half of the rack-defocus */\\n .cf-blurout {\\n position: absolute;\\n inset: 0;\\n z-index: 0;\\n width: 100%;\\n height: 100%;\\n object-fit: cover;\\n pointer-events: none;\\n animation: cf-blurout 640ms ease-out forwards;\\n }\\n\\n @keyframes cf-blurout {\\n 0% {\\n opacity: 1;\\n filter: blur(0);\\n }\\n\\n 100% {\\n opacity: 0;\\n filter: blur(13px);\\n }\\n }\\n\\n /* the optical dissolve: lighten blend, so highlights linger */\\n .cf-luma {\\n position: absolute;\\n inset: 0;\\n z-index: 0;\\n width: 100%;\\n height: 100%;\\n object-fit: cover;\\n pointer-events: none;\\n mix-blend-mode: lighten;\\n animation: cf-blend 700ms ease-in forwards;\\n }\\n\\n /* two breaths of white, no freeze — the gun-crack */\\n .cf-flash {\\n position: absolute;\\n inset: 0;\\n z-index: 4;\\n pointer-events: none;\\n background: #fff8ec;\\n animation: cf-flash 300ms ease-out forwards;\\n }\\n\\n @keyframes cf-flash {\\n 0%,\\n 22% {\\n opacity: 0.92;\\n }\\n\\n 100% {\\n opacity: 0;\\n }\\n }\\n\\n /* the old shot closes in a circle onto the new shot's subject */\\n .cf-iris {\\n position: absolute;\\n inset: 0;\\n z-index: 0;\\n width: 100%;\\n height: 100%;\\n object-fit: cover;\\n pointer-events: none;\\n clip-path: circle(150% at var(--ix, 50%) var(--iy, 50%));\\n animation: cf-iris 680ms cubic-bezier(0.45, 0, 0.3, 1) forwards;\\n }\\n\\n @keyframes cf-iris {\\n 0% {\\n clip-path: circle(150% at var(--ix, 50%) var(--iy, 50%));\\n opacity: 1;\\n }\\n\\n 92% {\\n opacity: 1;\\n }\\n\\n 100% {\\n clip-path: circle(0% at var(--ix, 50%) var(--iy, 50%));\\n opacity: 0;\\n }\\n }\\n\\n .cf-dip {\\n position: absolute;\\n inset: 0;\\n z-index: 0;\\n pointer-events: none;\\n }\\n\\n .cf-dip img {\\n position: absolute;\\n inset: 0;\\n width: 100%;\\n height: 100%;\\n object-fit: cover;\\n animation: cf-dip-frame 760ms linear forwards;\\n }\\n\\n /* the freeze holds until the veil has fully closed, then drops\\n under cover — the incoming shot is never seen before the dip */\\n /* the outgoing frame is swapped for the incoming one IN THE DARK,\\n inside the veil's hold, so none of the change is ever on screen */\\n @keyframes cf-dip-frame {\\n 0%,\\n 37% {\\n opacity: 1;\\n }\\n\\n 41%,\\n 100% {\\n opacity: 0;\\n }\\n }\\n\\n .cf-dip i {\\n position: absolute;\\n inset: 0;\\n opacity: 0;\\n /* linear: the curve is in the stops, and it is not an ease */\\n animation: cf-dip-veil 760ms linear forwards;\\n }\\n\\n /**\\n * A DIP CLOSES A SHUTTER; IT DOES NOT RAISE A BLIND.\\n *\\n * The veil is composited in sRGB, so an opacity of a leaves\\n * (1 - a)^2.2 of the LIGHT: a veil eased evenly to black is down to\\n * a fifth of the light by the halfway mark and then crawls, which is\\n * why the dip read as a lurch and then a wait rather than a fade.\\n * What a shutter does is take the LIGHT down smoothly, and the alpha\\n * that produces a smooth fall in light is this — nearly nothing for\\n * the first third, then a rush into black. The stops carry the\\n * curve, so the timing function is linear.\\n *\\n * Close over 34%, hold black for 10% (a dip is punctuation and the\\n * hold is the full stop), open over the remaining 56%: the way out\\n * is longer than the way in, which is the editor's convention and\\n * the reason a dip lands rather than bounces.\\n */\\n @keyframes cf-dip-veil {\\n 0% {\\n opacity: 0;\\n }\\n\\n 6.8% {\\n opacity: 0.049;\\n }\\n\\n 13.6% {\\n opacity: 0.178;\\n }\\n\\n 17% {\\n opacity: 0.27;\\n }\\n\\n 20.4% {\\n opacity: 0.378;\\n }\\n\\n 23.8% {\\n opacity: 0.502;\\n }\\n\\n 27.2% {\\n opacity: 0.643;\\n }\\n\\n 30.6% {\\n opacity: 0.803;\\n }\\n\\n 34%,\\n 44% {\\n opacity: 1;\\n }\\n\\n 49.6% {\\n opacity: 0.803;\\n }\\n\\n 55.2% {\\n opacity: 0.643;\\n }\\n\\n 60.8% {\\n opacity: 0.502;\\n }\\n\\n 66.4% {\\n opacity: 0.378;\\n }\\n\\n 72% {\\n opacity: 0.27;\\n }\\n\\n 77.6% {\\n opacity: 0.178;\\n }\\n\\n 88.8% {\\n opacity: 0.049;\\n }\\n\\n 100% {\\n opacity: 0;\\n }\\n }\\n\\n /* the seams' own layer: no stacking context of its own, so a flash\\n still rides above the scrim and a still stays under it */\\n .cf-joins {\\n position: absolute;\\n inset: 0;\\n pointer-events: none;\\n }\\n\\n /**\\n * A SEAM THAT COVERS THE FURNITURE (`@over=\\\"everything\\\"`). Above the\\n * type, the stamp, the rail, the inserts and the clips (1 to 4);\\n * level with the transport, which is later in the document and so\\n * stays in front of it; below the menu, the captions, the door and\\n * the fault banner (6 and up). A viewer must never lose the\\n * controls to a transition — and the layer takes no pointer events\\n * either way. The class is on only while the seam runs.\\n */\\n .cf-joins.is-over {\\n z-index: 5;\\n }\\n\\n /* ---- the lens ------------------------------------------------- */\\n /* THE CLOUD IS A CAST SHADOW, not a filter over the lens. A band\\n swept across the whole frame reads as somebody dimming the\\n picture; what a cloud actually does is drop a soft, uneven\\n shape onto the land and the building, keep its edges out of\\n focus, and move. Two overlapping blobs on a wide plate,\\n multiplied over the scene and travelling with the sun's rake. */\\n .cf-cloud {\\n position: absolute;\\n /* -35%/-45% was a plate 1.7x the viewport each way, blurred at 26px\\n on every frame the sun moved. The gradients are soft already; the\\n blur added nothing you could see and a full-screen filter you could\\n feel. Smaller, unblurred, and hidden outright when there is no\\n cloud — an opacity of zero still costs a composited layer. */\\n inset: -12% -16%;\\n z-index: 1;\\n pointer-events: none;\\n /* IT WAS BLUE, AND BARCELONA IS NOT. A cloud passing over a warm\\n stone site takes the warmth out of the light; it does not tip the\\n whole frame to slate. These are a warm grey now, and at half the\\n strength — a shadow crossing the picture rather than a filter\\n laid over it. */\\n opacity: calc(var(--cf-cloud, 0) * 0.32);\\n background:\\n radial-gradient(\\n 62% 40% at 32% 44%,\\n rgba(112, 100, 80, 0.4) 0%,\\n rgba(120, 108, 88, 0.24) 46%,\\n rgba(128, 118, 100, 0) 84%\\n ),\\n radial-gradient(\\n 48% 32% at 68% 58%,\\n rgba(106, 96, 78, 0.3) 0%,\\n rgba(122, 110, 90, 0.16) 52%,\\n rgba(128, 118, 100, 0) 90%\\n );\\n mix-blend-mode: multiply;\\n transform: rotate(calc(var(--cf-rake, 35deg) * 0.4))\\n translate3d(\\n calc(var(--cf-cloud, 0) * -16%),\\n calc(var(--cf-cloud, 0) * 7%),\\n 0\\n );\\n }\\n\\n .is-cloudless .cf-cloud {\\n visibility: hidden;\\n }\\n\\n /* ---- scrims: anchored to the caption, never banded across the\\n frame. A band wide enough to carry a lower third also washes out\\n whatever is standing in the middle of the shot — which on this\\n route is the building. ------------------------------------------ */\\n .cf-scrim {\\n position: absolute;\\n inset: 0;\\n z-index: 1;\\n pointer-events: none;\\n transition: background 700ms ease;\\n }\\n\\n /* a bare beat: no scrim at all — under a walk through the hours the\\n scrim's paper colour snaps with each theme, and a dish of colour\\n snapping behind the type is worse than no dish */\\n .cf-scrim.is-bare {\\n display: none;\\n }\\n\\n .cf-scrim-lower {\\n background: radial-gradient(\\n 128% 82% at 0% 112%,\\n var(--cf-paper-a) 0%,\\n var(--cf-paper-b) 36%,\\n var(--cf-paper-c) 72%\\n );\\n }\\n\\n .cf-scrim-title {\\n background: radial-gradient(\\n 112% 108% at 112% 116%,\\n var(--cf-paper-a) 0%,\\n var(--cf-paper-b) 34%,\\n var(--cf-paper-c) 70%\\n );\\n }\\n\\n .cf-scrim-plate {\\n background: radial-gradient(\\n 84% 104% at 110% 50%,\\n var(--cf-paper-a) 0%,\\n var(--cf-paper-b) 34%,\\n var(--cf-paper-c) 72%\\n );\\n }\\n\\n .cf-scrim-point {\\n background: radial-gradient(\\n 88% 92% at -10% 26%,\\n var(--cf-paper-a) 0%,\\n var(--cf-paper-b) 38%,\\n var(--cf-paper-c) 74%\\n );\\n }\\n\\n .cf-dim {\\n position: absolute;\\n inset: 0;\\n z-index: 2;\\n pointer-events: none;\\n opacity: 0;\\n background: radial-gradient(\\n 76% 66% at 50% 48%,\\n rgba(24, 18, 8, 0.16) 0%,\\n rgba(24, 18, 8, 0.34) 100%\\n );\\n }\\n\\n /* ---- traces and callouts -------------------------------------- */\\n .cf-track {\\n position: absolute;\\n inset: 0;\\n width: 100%;\\n height: 100%;\\n pointer-events: none;\\n z-index: 2;\\n }\\n\\n /* the marker glows, and the glow is doing real work: a thin line\\n over moss competes with the moss at exactly its own frequency,\\n and a soft bloom gives the eye a low-frequency edge to catch\\n first. (The traces on the building carry their own glow shell in\\n the scene now; this dresses only what is still on the glass.) */\\n /* THE GLOW COSTS A FRAME IN SAFARI. These two elements move every\\n frame — the leader is re-pointed at the building on each one — and\\n a stacked pair of drop-shadow filters is re-rasterised each time,\\n which WebKit charges far more for than Blink does. One tight\\n shadow gives the eye the same low-frequency edge to catch; the\\n wide one was doing very little over a dimmed plate anyway. */\\n .cf-leader,\\n .cf-track circle {\\n filter: drop-shadow(0 0 3px rgba(255, 36, 18, 0.9));\\n }\\n\\n .cf-tether {\\n stroke: var(--cf-accent);\\n stroke-width: 1.4;\\n stroke-dasharray: 3 5;\\n opacity: 0;\\n transition: opacity 500ms ease;\\n }\\n\\n .cf-leader {\\n stroke: var(--cf-mark);\\n stroke-width: 1.8;\\n stroke-dasharray: 5 4;\\n stroke-linecap: round;\\n opacity: 0;\\n }\\n\\n .cf-track circle {\\n fill: none;\\n stroke: var(--cf-mark);\\n stroke-width: 2.6;\\n stroke-linecap: round;\\n opacity: 0;\\n }\\n\\n /* ================================================================\\n RESPONSIVE. The film had not one media query in it: every size was\\n a viewport fraction, which keeps type in proportion but does not\\n change a LAYOUT. Two columns of an editorial plate do not become\\n a phone layout by getting smaller — they have to become one\\n column, the ghost numeral has to go, and the settings have to\\n stop being a corner of the frame and become its foot.\\n ================================================================ */\\n\\n /* a laptop, or a window shared with something else */\\n\\n /* a tablet, or a phone held the long way */\\n\\n /* a phone */\\n\\n /* a short window: the film is 16:9 in a letterbox and the vertical\\n rhythm, not the width, is what runs out */\\n\\n /* someone who has asked for less movement gets the film without the\\n drift, the sway and the entrances — and still gets the film */\\n @media (prefers-reduced-motion: reduce) {\\n .cf-mg-mark,\\n .cf-mg-glyph,\\n .cf-gate-ghost,\\n .cf-mg-seal,\\n .cf-kicker::after {\\n animation-duration: 1ms !important;\\n }\\n }\\n\\n /* ---- the front layer ------------------------------------------ */\\n /* THE WALL TEXT LEAVES BEFORE THE SEAM. Its own exit animation\\n runs when the next beat replaces it, which means the outgoing\\n setting is still on screen at the instant of the cut — so the\\n edit lands on a frame with two things happening in it. Half a\\n second of fade ahead of the change empties the corner first,\\n and the cut is then only ever about the picture. */\\n /* the ink follows the hour: the palette is swapped per theme by wear,\\n and under a walk through the day the type must not snap from black\\n to white — it turns with the light */\\n .cf-type,\\n .cf-type p,\\n .cf-type span {\\n transition: color 2400ms ease;\\n }\\n\\n .cf-type {\\n position: absolute;\\n inset: 0;\\n z-index: 3;\\n opacity: var(--cf-type-a, 1);\\n pointer-events: none;\\n color: var(--cf-ink);\\n font-family: var(--cf-display);\\n }\\n\\n .cf-block {\\n position: absolute;\\n max-width: min(46cqw, 660px);\\n }\\n\\n /* a kicker rides a rule that draws itself out of the type — the\\n diagonal is the sun's, so the graphic furniture rakes the same\\n way the shadows do */\\n /* the eyebrow never wraps: 'THE TALLEST CHURCH ON EARTH' at this\\n tracking is wider than the block, and a wrapped kicker changes\\n the height of a bottom-anchored plate */\\n .cf-kicker > span {\\n white-space: nowrap;\\n }\\n\\n .cf-kicker {\\n font-family: var(--cf-ui);\\n font-size: clamp(11px, 0.9cqw, 14px);\\n font-weight: 700;\\n letter-spacing: 0.11em;\\n color: var(--cf-chap);\\n margin: 0 0 14px;\\n display: flex;\\n align-items: center;\\n gap: 12px;\\n }\\n\\n /* the rule draws itself out of the kicker. It is a pseudo-element,\\n so Motion cannot own it — but the whole block is rebuilt on every\\n beat, which means a plain CSS animation runs from its first frame\\n each time and needs no retriggering. */\\n .cf-kicker::after {\\n content: '';\\n /* the rule takes the chapter's colour: on the centenary timeline\\n the rule, the dot and the heading are one colour, and that is\\n what makes it read as a system rather than as decoration */\\n background: var(--cf-chap);\\n /* the rule gives way to the words but never vanishes: a rule that\\n collapses to nothing on a long kicker is furniture that blinks\\n in and out with the string length */\\n flex: 1 1 26px;\\n min-width: 26px;\\n height: 2px;\\n transform-origin: left center;\\n animation: cf-rule 760ms 300ms cubic-bezier(0.22, 1, 0.36, 1) both;\\n }\\n\\n .is-title .cf-kicker::after {\\n transform-origin: right center;\\n }\\n\\n @keyframes cf-rule {\\n from {\\n transform: scaleX(0);\\n }\\n\\n to {\\n transform: scaleX(1);\\n }\\n }\\n\\n .is-point .cf-kicker::after,\\n .is-lower .cf-kicker::after {\\n max-width: 120px;\\n }\\n\\n .cf-kanji {\\n font-family:\\n 'Hiragino Mincho ProN', 'Yu Mincho', YuMincho, 'Noto Serif JP',\\n 'Songti SC', serif;\\n font-size: min(132px, 8.4cqw);\\n line-height: 0.94;\\n letter-spacing: 0.04em;\\n margin: 0 0 18px;\\n }\\n\\n .cf-read {\\n margin: 0 0 20px;\\n display: flex;\\n gap: 6px 16px;\\n align-items: baseline;\\n flex-wrap: wrap;\\n }\\n\\n /* a flex child cannot shrink past its own content without this */\\n .cf-romaji,\\n .cf-gloss {\\n min-width: 0;\\n }\\n\\n /* on a marked beat the reading is the largest thing in the block,\\n because the term itself is out in the scene */\\n .cf-block:not(:has(.is-glyph)) .cf-romaji {\\n font-size: clamp(17px, 1.5cqw, 24px);\\n letter-spacing: 0.2em;\\n }\\n\\n .cf-block:not(:has(.is-glyph)) .cf-gloss {\\n font-size: clamp(15px, 1.2cqw, 19px);\\n }\\n\\n .cf-romaji {\\n font-family: var(--cf-ui);\\n font-size: clamp(11px, 0.94cqw, 14px);\\n font-weight: 700;\\n letter-spacing: 0.11em;\\n color: var(--cf-chap);\\n }\\n\\n .cf-gloss {\\n font-family: var(--cf-ui);\\n font-size: clamp(12px, 0.98cqw, 15px);\\n font-weight: 400;\\n letter-spacing: 0.01em;\\n color: var(--cf-ink3);\\n }\\n\\n /* the planes are the host's to move; the type on them is Motion's */\\n .cf-plane {\\n will-change: transform;\\n }\\n\\n /* a phrase is a HEADLINE, not body copy: it is on screen for two\\n seconds and read at a glance, so it is set at the size a glance\\n needs and it never runs past two lines */\\n .cf-say {\\n margin: 0 0 12px;\\n font-family: var(--cf-display);\\n /* set in the identity's capitals, but a cue is still a caption:\\n at 30px in heavy caps three of them fill a frame and the\\n picture becomes the background to a poster */\\n font-size: clamp(14px, 1.28cqw, 22px);\\n font-weight: 700;\\n text-transform: uppercase;\\n letter-spacing: 0.012em;\\n line-height: 1.16;\\n color: var(--cf-ink);\\n /* a cue is a phrase: wide enough that most set on ONE line.\\n 26ch was a CJK measure and these run to 34 characters, so the\\n measure comes from the longest of them — and the break is left\\n to pretty, because balance moves the break around as the\\n string changes, which is the flicker between a one-line and a\\n two-line setting. */\\n max-width: min(38ch, 34cqw);\\n text-wrap: pretty;\\n }\\n\\n .cf-say:last-child {\\n margin-bottom: 0;\\n }\\n\\n /* tall scripts (Thai, Khmer) at kanji size collide with their\\n neighbours; scale the whole set box and give the marks headroom */\\n .cf-kanji.is-tall {\\n zoom: 0.58;\\n line-height: 1.5;\\n }\\n\\n /* ---- lines that DO what they SAY ------------------------------ *\\n Behavior animations on the inner span, addressed per line by the\\n film (`.sx-<beat>-<n>`). They run the length of the beat, so\\n whenever the delivery reveals a line it is already mid-behavior —\\n never waiting, never done. Two gestures ship here: a plumb drop\\n and a heavy settle. */\\n .cf-sayx {\\n display: inline-block;\\n }\\n\\n @keyframes cf-plumb {\\n 0% {\\n transform: translateY(-16px) scaleY(1.18);\\n }\\n\\n 100% {\\n transform: translateY(0) scaleY(1);\\n }\\n }\\n\\n /* \\\"Flaring at the foot\\\" — the line itself flares, tracking wide\\n from its left foot for the whole shot, the batter's own curve */\\n /* (the old per-line behaviours are gone: one of them animated\\n letter-spacing on an inline-block, which re-wrapped the line it\\n was in halfway through a shot) */\\n\\n @keyframes cf-sink {\\n 0% {\\n transform: translateY(-14px);\\n }\\n\\n 34% {\\n transform: translateY(2px);\\n }\\n\\n 100% {\\n transform: translateY(0);\\n }\\n }\\n\\n /* the four ways a beat is set. A title is ranged right against the\\n frame's edge with the tower in the other half; a lower third sits\\n bottom-left; a plate is a slab in the right third; a point is a\\n caption with a leader running out of it. */\\n .cf-block.is-title {\\n right: 6%;\\n bottom: 15%;\\n text-align: right;\\n max-width: min(48cqw, 700px);\\n /* poster size at the start, the house size once the lens has\\n arrived: the same lean as every block, times the title's own\\n scale, from the corner it hangs from */\\n transform-origin: 100% 100%;\\n transform: translate3d(\\n calc(var(--cf-lx, 0) * -9px),\\n calc(var(--cf-ly, 0) * -5px),\\n 0\\n )\\n scale(calc(1.42 - 0.42 * var(--cf-title-k, 1)));\\n }\\n\\n /* the opening's hierarchy: the name, then its reading, then the\\n cues a step back in the ink — a poster, not a paragraph */\\n .is-title .cf-kanji {\\n margin-bottom: 26px;\\n }\\n\\n .is-title .cf-read {\\n justify-content: flex-end;\\n margin-bottom: 28px;\\n gap: 18px;\\n }\\n\\n .is-title .cf-romaji {\\n font-size: clamp(15px, 1.3cqw, 23px);\\n letter-spacing: 0.3em;\\n }\\n\\n .is-title .cf-gloss {\\n font-size: clamp(15px, 1.25cqw, 22px);\\n }\\n\\n .is-title .cf-say {\\n max-width: 30ch;\\n margin-bottom: 6px;\\n color: var(--cf-ink2);\\n font-size: min(34px, 1.95cqw);\\n }\\n\\n .is-title .cf-kicker {\\n flex-direction: row-reverse;\\n }\\n\\n .is-title .cf-line {\\n margin-left: auto;\\n }\\n\\n .is-title .cf-kanji {\\n font-size: min(190px, 11cqw);\\n }\\n\\n .cf-block.is-lower {\\n left: 5.5%;\\n bottom: 11%;\\n }\\n\\n /* captions take the foot of the frame, so the settings that live\\n down there move up out of their way rather than sit under them */\\n .has-subs .cf-block.is-lower {\\n bottom: 21%;\\n }\\n\\n /* the plate steps out of the caption band's way — and stepping is\\n what it should not do, so it moves over a beat, not a frame */\\n .cf-block {\\n transition: bottom 420ms cubic-bezier(0.22, 1, 0.36, 1);\\n }\\n\\n .has-subs .cf-block.is-title {\\n bottom: 24%;\\n }\\n\\n /* the plate is centred on the frame, so a caption band eats its last\\n cue rather than sitting under it */\\n .has-subs .cf-block.is-plate {\\n top: 44%;\\n }\\n\\n /* THE PLATE, which is the one setting that is a designed object\\n rather than a caption. Two columns: the term set VERTICALLY down\\n the right edge, the way it would be on a museum label or a\\n hanging scroll, and the reading and the phrases in a column\\n beside it. A rule between them draws itself down as the plate\\n lands. Ranged type in a corner was never wrong exactly — it was\\n just nothing, and this chapter's beats are the ones that hold\\n longest, so they are the ones that can least afford nothing. */\\n .cf-block.is-plate {\\n right: 5.5%;\\n top: 50%;\\n transform: translateY(-50%);\\n display: grid;\\n /* minmax(0,…) or the cue column cannot shrink and the word wins */\\n grid-template-columns: minmax(0, auto) auto;\\n justify-content: end;\\n column-gap: min(30px, 1.8cqw);\\n align-items: start;\\n /* the word takes a column of its own, so the block has to be wide\\n enough for both it and a cue line that is not two words per row\\n — and no wider: at two thirds of the frame the cue column began\\n over the subject, and a plate is a label beside the thing */\\n max-width: min(50cqw, 760px);\\n }\\n\\n .is-plate .cf-plane {\\n grid-column: 1;\\n }\\n\\n /* THE CUES RANGE AGAINST THE RULE. The block hugs the right edge and\\n everything in it gathers toward the word, so the plate is one\\n object at the side of the frame and the picture keeps its left. */\\n .is-plate .cf-plane:not(.is-glyph) {\\n display: flex;\\n flex-direction: column;\\n align-items: flex-end;\\n text-align: right;\\n }\\n\\n .is-plate .cf-plane:not(.is-glyph) .cf-kicker {\\n align-self: stretch;\\n }\\n\\n .is-plate .cf-read {\\n justify-content: flex-end;\\n }\\n\\n .is-plate .cf-plane.is-glyph {\\n grid-column: 2;\\n grid-row: 1 / -1;\\n border-right: 2px solid var(--cf-accent);\\n padding-right: min(26px, 1.5cqw);\\n transform-origin: top center;\\n animation: cf-rule-down 820ms 240ms cubic-bezier(0.22, 1, 0.36, 1) both;\\n }\\n\\n /* the mask draws the rule DOWN; it must never clip sideways — the\\n glyph plane leans with the pointer, and a mask flush with the box\\n shaved the leaned strokes off the left edge as a hairline */\\n @keyframes cf-rule-down {\\n from {\\n clip-path: inset(0 -40px 100% -40px);\\n }\\n\\n to {\\n clip-path: inset(0 -40px -4% -40px);\\n }\\n }\\n\\n .is-plate .cf-kanji {\\n writing-mode: vertical-rl;\\n margin: 0;\\n /* the column never grows past the frame: whichever is smaller,\\n the designed size or the height each glyph can have and still\\n leave the set inside the picture */\\n font-size: min(min(88px, 5.4cqw), calc(62svh / var(--cf-glyphs, 3)));\\n letter-spacing: 0.1em;\\n line-height: 1;\\n }\\n\\n .is-plate .cf-kicker {\\n margin-bottom: 18px;\\n }\\n\\n /* the ghost is only ever behind a plate — everywhere else the frame\\n is already carrying the building */\\n .cf-ghost {\\n display: none;\\n }\\n\\n /* THE PLANES LEAN. Each layer takes the pointer at its own depth —\\n the ghost furthest, the big glyph next, the reading lines least\\n — so the type stack has air in it rather than being one sheet\\n of glass in front of a picture. */\\n .cf-block {\\n transform: translate3d(\\n calc(var(--cf-lx, 0) * -9px),\\n calc(var(--cf-ly, 0) * -5px),\\n 0\\n );\\n }\\n\\n .cf-plate .cf-ghost {\\n transform: translateY(-50%)\\n translate3d(\\n calc(var(--cf-lx, 0) * -26px),\\n calc(var(--cf-ly, 0) * -14px),\\n 0\\n );\\n }\\n\\n .cf-kanji {\\n transform: translate3d(\\n calc(var(--cf-lx, 0) * -15px),\\n calc(var(--cf-ly, 0) * -8px),\\n 0\\n );\\n }\\n\\n /* the ghost NUMERAL keeps its old treatment: a filled slab of ink\\n at a whisper of opacity, behind the plate. It is a page\\n furniture mark, not type in the world — the hollow treatment\\n belongs to the glyphs that share air with the building. */\\n .cf-plate .cf-ghost {\\n display: block;\\n position: absolute;\\n right: 3%;\\n top: 50%;\\n transform: translateY(-50%);\\n font-family: var(--cf-ui);\\n font-size: min(460px, 30cqw);\\n font-weight: 700;\\n line-height: 0.8;\\n letter-spacing: -0.04em;\\n color: var(--cf-ink);\\n opacity: calc(0.045 * var(--cf-ghost-a, 1));\\n pointer-events: none;\\n z-index: -1;\\n }\\n\\n .cf-block.is-point {\\n left: 5.5%;\\n top: 18%;\\n max-width: min(36cqw, 520px);\\n }\\n\\n .is-point .cf-kanji {\\n font-size: min(104px, 6.6cqw);\\n }\\n\\n /* ---- clips ------------------------------------------------------ *\\n a video, a still or a freeze over the picture: full frame under the\\n scrim and the type, or an inset like the photograph */\\n .cf-clip {\\n position: absolute;\\n pointer-events: none;\\n }\\n\\n .cf-clip.is-cover {\\n inset: 0;\\n z-index: 1;\\n }\\n\\n .cf-clip.is-cover figure {\\n position: absolute;\\n inset: 0;\\n margin: 0;\\n }\\n\\n .cf-clip.is-cover video,\\n .cf-clip.is-cover img {\\n display: block;\\n width: 100%;\\n height: 100%;\\n object-fit: cover;\\n }\\n\\n .cf-clip.is-cover figcaption {\\n position: absolute;\\n left: 5.5%;\\n bottom: 6%;\\n display: flex;\\n flex-direction: column;\\n gap: 2px;\\n font-family: var(--cf-ui);\\n color: #f7f0e0;\\n text-shadow: 0 1px 12px rgba(0, 0, 0, 0.5);\\n }\\n\\n .cf-clip.is-inset {\\n z-index: 4;\\n }\\n\\n /**\\n * A PIP IS A LAYER, not a photograph. No card, no rule, no caption\\n * furniture — the score places it (`left/top/width` in percent of\\n * the frame, written inline) and it carries nothing but its own\\n * media and a corner radius. It sits under the type, so a lower\\n * third still wins over it, and over the picture and its scrim.\\n */\\n .cf-clip.is-pip {\\n z-index: 2;\\n inset: 0;\\n }\\n\\n .cf-clip.is-pip figure {\\n position: absolute;\\n margin: 0;\\n overflow: hidden;\\n line-height: 0;\\n box-shadow: calc(var(--cf-shx) * 3) calc(var(--cf-shy) * 3) 30px\\n rgba(20, 14, 4, 0.34);\\n }\\n\\n .cf-clip.is-pip video,\\n .cf-clip.is-pip img {\\n display: block;\\n width: 100%;\\n height: auto;\\n }\\n\\n .cf-clip.is-inset figure {\\n position: absolute;\\n right: 5.5%;\\n top: 12%;\\n margin: 0;\\n width: min(30cqw, 340px);\\n background: var(--cf-paper);\\n padding: 10px 10px 8px;\\n border: 1px solid var(--cf-rule);\\n box-shadow: calc(var(--cf-shx) * 4) calc(var(--cf-shy) * 4) 26px\\n rgba(38, 28, 10, 0.26);\\n }\\n\\n .cf-clip.is-inset video,\\n .cf-clip.is-inset img {\\n display: block;\\n width: 100%;\\n height: auto;\\n }\\n\\n .cf-clip.is-inset figcaption {\\n display: flex;\\n flex-direction: column;\\n gap: 2px;\\n padding-top: 8px;\\n font-family: var(--cf-ui);\\n }\\n\\n .cf-clip-cap {\\n font-size: 12px;\\n font-weight: 700;\\n letter-spacing: 0.12em;\\n }\\n\\n .cf-clip-cr {\\n font-size: 10px;\\n letter-spacing: 0.06em;\\n opacity: 0.75;\\n }\\n\\n /* ---- the photograph -------------------------------------------- */\\n .cf-photo {\\n position: absolute;\\n z-index: 4;\\n pointer-events: none;\\n }\\n\\n .cf-photo figure.is-lower,\\n .cf-photo figure.is-title,\\n .cf-photo figure.is-point {\\n position: absolute;\\n right: 5.5%;\\n top: 12%;\\n }\\n\\n .cf-photo figure.is-plate {\\n position: absolute;\\n left: 5.5%;\\n top: 12%;\\n }\\n\\n .cf-photo figure {\\n margin: 0;\\n width: min(30cqw, 340px);\\n background: var(--cf-paper);\\n padding: 10px 10px 8px;\\n border: 1px solid var(--cf-rule);\\n box-shadow: calc(var(--cf-shx) * 4) calc(var(--cf-shy) * 4) 26px\\n rgba(38, 28, 10, 0.26);\\n }\\n\\n .cf-photo img {\\n display: block;\\n width: 100%;\\n height: auto;\\n filter: saturate(0.86) contrast(1.04);\\n }\\n\\n .cf-photo figcaption {\\n display: flex;\\n flex-direction: column;\\n gap: 2px;\\n padding-top: 8px;\\n font-family: var(--cf-ui);\\n }\\n\\n .cf-photo-cap {\\n font-size: 12px;\\n font-weight: 700;\\n letter-spacing: 0.12em;\\n color: var(--cf-ink);\\n }\\n\\n .cf-photo-cr {\\n font-size: 10px;\\n letter-spacing: 0.06em;\\n color: var(--cf-ink2);\\n }\\n\\n .is-dark .cf-subs {\\n color: #1a1408;\\n background: rgba(238, 228, 204, 0.82);\\n }\\n\\n .cf-subs {\\n position: absolute;\\n left: 50%;\\n bottom: 3%;\\n transform: translateX(-50%);\\n z-index: 6;\\n transition: bottom 360ms ease;\\n margin: 0;\\n max-width: 60ch;\\n text-align: center;\\n font-family: var(--cf-serif);\\n font-size: 17px;\\n line-height: 1.5;\\n color: #f7efdd;\\n background: rgba(20, 15, 6, 0.66);\\n padding: 9px 18px;\\n }\\n\\n /* while the transport is up the caption sits above it, and drops\\n back to the foot of the frame when the bar leaves */\\n .cf-page:not(.is-idle) .cf-subs {\\n bottom: 104px;\\n }\\n\\n /* ---- the rail -------------------------------------------------- */\\n /* ONE LINE: the numeral, the chapter's name and the rule, left to\\n right — the rule is not a second row under the title */\\n .cf-rail {\\n flex-wrap: nowrap;\\n position: absolute;\\n left: 5.5%;\\n top: 6%;\\n z-index: 3;\\n display: flex;\\n align-items: center;\\n gap: 14px;\\n font-size: 11px;\\n font-weight: 700;\\n letter-spacing: 0.17em;\\n color: var(--cf-ink3);\\n font-family: var(--cf-ui);\\n }\\n\\n .cf-rail-n {\\n font-size: 22px;\\n font-weight: 800;\\n letter-spacing: 0.02em;\\n color: var(--cf-chap);\\n }\\n\\n .cf-rail-segs {\\n display: flex;\\n gap: 5px;\\n width: 240px;\\n }\\n\\n /* ---- the year's own timeline ----------------------------------\\n A rule the width of the chapter strip, the run so far filled in\\n the chapter's colour, a dot on the head and the year riding above\\n it. Tabular figures so the numeral does not jitter as it counts,\\n and the whole thing is one row under the chapter line so the\\n corner stays a single object. */\\n /* THE RULE IS THE FILM'S LENGTH, so it should read as a long\\n distance: 240px made a century and a half look like a widget.\\n It runs to nearly half the frame now, and the chapter dots have\\n room to sit at their own years without touching. */\\n .cf-years {\\n position: relative;\\n flex: none;\\n height: 34px;\\n margin-left: 34px;\\n width: min(620px, 42cqw);\\n }\\n\\n .cf-years-rule,\\n .cf-years-fill {\\n position: absolute;\\n left: 0;\\n top: 14px;\\n height: 2px;\\n background: var(--cf-rule);\\n }\\n\\n .cf-years-rule {\\n right: 0;\\n }\\n\\n .cf-years-fill {\\n width: calc(5px + var(--cf-head, 0) * (100% - 10px));\\n background: var(--cf-chap);\\n transition: width 220ms linear;\\n }\\n\\n .cf-years-tick {\\n position: absolute;\\n top: 11px;\\n width: 1px;\\n height: 8px;\\n background: var(--cf-rule);\\n }\\n\\n /* the chapters, as dots at the years they open. Unbuilt they are the\\n rule's own colour; passed, they take the chapter's. */\\n .cf-years-ch {\\n position: absolute;\\n top: 10px;\\n z-index: 2;\\n appearance: none;\\n border: 0;\\n padding: 0;\\n width: 10px;\\n height: 10px;\\n margin-left: -5px;\\n border-radius: 50%;\\n background: var(--cf-rule);\\n cursor: pointer;\\n pointer-events: auto;\\n transition:\\n background 300ms ease,\\n transform 200ms ease;\\n }\\n\\n .cf-years-ch.is-done {\\n background: var(--cf-chap);\\n }\\n\\n .cf-years-ch.is-here {\\n background: var(--cf-chap);\\n transform: scale(1.34);\\n }\\n\\n .cf-years-ch:hover {\\n transform: scale(1.5);\\n }\\n\\n .cf-years-now {\\n position: absolute;\\n top: 20px;\\n left: calc(5px + var(--cf-head, 0) * (100% - 10px));\\n transform: translateX(-50%);\\n font-family: var(--cf-ui);\\n font-weight: 800;\\n font-size: 15px;\\n letter-spacing: 0.01em;\\n font-variant-numeric: tabular-nums;\\n color: var(--cf-chap);\\n white-space: nowrap;\\n transition: left 220ms linear;\\n }\\n\\n .cf-years-now::after {\\n content: '';\\n position: absolute;\\n left: 50%;\\n /* rule top 14px, rule 2px, dot 10px → -10 puts the dot's centre\\n on the rule's centre exactly */\\n top: -10px;\\n width: 10px;\\n height: 10px;\\n margin-left: -5px;\\n border-radius: 50%;\\n background: var(--cf-chap);\\n }\\n\\n .cf-years-a,\\n .cf-years-b {\\n position: absolute;\\n top: 0;\\n font-family: var(--cf-ui);\\n font-style: normal;\\n font-size: 9px;\\n font-weight: 600;\\n letter-spacing: 0.14em;\\n color: var(--cf-ink2);\\n }\\n\\n .cf-years-a {\\n left: 0;\\n }\\n\\n .cf-years-b {\\n right: 0;\\n }\\n\\n /* each segment is a 13px-tall click target drawing a hairline\\n track with its fill on top; the current chapter's bar thickens */\\n .cf-rail-seg {\\n appearance: none;\\n position: relative;\\n border: 0;\\n height: 13px;\\n padding: 0;\\n background: transparent;\\n cursor: pointer;\\n pointer-events: auto;\\n }\\n\\n .cf-rail-seg::after {\\n content: '';\\n position: absolute;\\n inset: 5px 0 auto;\\n height: 3px;\\n background: var(--cf-rule);\\n }\\n\\n .cf-rail-seg i {\\n position: absolute;\\n left: 0;\\n top: 5px;\\n height: 3px;\\n z-index: 1;\\n background: var(--cf-chap);\\n transition: width 600ms cubic-bezier(0.22, 1, 0.36, 1);\\n }\\n\\n .cf-rail-seg.is-here i {\\n top: 4px;\\n height: 5px;\\n }\\n\\n /* THE DOT ON THE RULE. The centenary programme draws its timeline as\\n a line with a filled dot at each momentum; the film's chapter\\n strip is the same object, so it is drawn the same way. */\\n .cf-rail-seg::before {\\n content: '';\\n position: absolute;\\n left: -1px;\\n top: 2px;\\n z-index: 2;\\n width: 9px;\\n height: 9px;\\n border-radius: 50%;\\n background: var(--cf-rule);\\n transition: background 300ms ease;\\n }\\n\\n .cf-rail-seg.is-here::before {\\n background: var(--cf-chap);\\n }\\n\\n .cf-rail-seg:hover::after {\\n background: var(--cf-ink2);\\n }\\n\\n .cf-fault {\\n position: absolute;\\n left: 5.5%;\\n bottom: 4%;\\n z-index: 9;\\n margin: 0;\\n padding: 8px 12px;\\n background: #2a0a06;\\n color: #ffb4a0;\\n font:\\n 12px/1.4 ui-monospace,\\n monospace;\\n border-radius: 8px;\\n max-width: 80%;\\n }\\n\\n .cf-rig {\\n position: absolute;\\n width: 0;\\n height: 0;\\n }\\n\\n /* ---- the disc menu --------------------------------------------- */\\n /* the sheet belongs to the picture: it scrims the scene and its\\n transport, and leaves the wall plate below the film alone —\\n a menu that dims an article is a modal, not a disc menu */\\n .cf-menu {\\n position: absolute;\\n inset: 0 0 auto 0;\\n height: 100svh;\\n z-index: 6;\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n background: rgba(28, 22, 10, 0.52);\\n backdrop-filter: blur(2px);\\n }\\n\\n .cf-menu-in {\\n background: var(--cf-paper);\\n border: 1px solid var(--cf-rule);\\n padding: 34px 40px 26px;\\n min-width: min(460px, 84cqw);\\n max-width: min(640px, 92cqw);\\n box-shadow: 0 30px 70px rgba(30, 22, 8, 0.34);\\n font-family: var(--cf-ui);\\n }\\n\\n .cf-menu-head {\\n margin: 0;\\n font-size: 13px;\\n font-weight: 800;\\n letter-spacing: 0.42em;\\n color: var(--cf-ink);\\n }\\n\\n .cf-menu-sub {\\n margin: 4px 0 22px;\\n font-size: 12px;\\n color: var(--cf-ink2);\\n letter-spacing: 0.06em;\\n }\\n\\n .cf-menu-list {\\n list-style: none;\\n margin: 0 0 18px;\\n padding: 0;\\n }\\n\\n .cf-menu-item {\\n appearance: none;\\n background: transparent;\\n border: 0;\\n border-top: 1px solid var(--cf-rule);\\n width: 100%;\\n display: flex;\\n align-items: baseline;\\n gap: 16px;\\n padding: 13px 4px;\\n font: inherit;\\n color: var(--cf-ink);\\n cursor: pointer;\\n text-align: left;\\n }\\n\\n .cf-menu-item:hover {\\n color: var(--cf-accent);\\n }\\n\\n .cf-menu-item.is-here {\\n color: var(--cf-accent);\\n font-weight: 700;\\n }\\n\\n .cf-menu-n {\\n font-size: 12px;\\n letter-spacing: 0.16em;\\n color: var(--cf-accent);\\n min-width: 2.4em;\\n }\\n\\n .cf-menu-t {\\n font-size: 17px;\\n letter-spacing: 0.1em;\\n font-weight: 600;\\n flex: 1;\\n min-width: 0;\\n }\\n\\n .cf-menu-d {\\n font-size: 11px;\\n letter-spacing: 0.1em;\\n color: var(--cf-ink2);\\n }\\n\\n .cf-menu-keys {\\n margin: 0;\\n font-size: 11px;\\n letter-spacing: 0.1em;\\n color: var(--cf-ink2);\\n }\\n\\n /* ---- controls -------------------------------------------------- */\\n /* ---- the player ------------------------------------------------ *\\n * A chaptered playhead in the film's own materials: gaps between\\n * the chapters, seal red for what has played, and a head you can\\n * take hold of. It sits ON the picture, above the transport, and\\n * both stand down when the hand goes away.\\n * ------------------------------------------------------------- */\\n /* THE PLAYER FLOATS ON THE PICTURE, the way every player does —\\n a paper bar under the frame costs fifty pixels of film and\\n announces that this is a document with a video in it. */\\n /* THE BAR IS UNDER THE PICTURE, not on it. A player floating over\\n the frame is right for a video in a page and wrong for a film\\n that fills the window: it takes the bottom of every composition\\n and it disappears exactly when you reach for it. Below the\\n picture it costs seventy-six pixels once and is always there. */\\n /* THE TRANSPORT SITS ON THE PICTURE, not under it. The source page\\n runs its track as a hairline over the scene; here the strip was a\\n solid band of paper that took 76px off every frame. It is a fade\\n of the chapter's own paper now, so the film owns the whole\\n viewport and the controls read as a ledge on the glass. */\\n /* ---- the player ------------------------------------------------ */\\n /* fixed, not absolute: the page runs on below the stage, so the\\n page's bottom is not the frame's bottom. A dark scrim, not paper:\\n white type on a gradient into black is the one thing that reads\\n over every grade the film wears, and it is what the eye expects\\n at the bottom of a picture that plays. */\\n .cf-player {\\n position: fixed;\\n left: 0;\\n right: 0;\\n bottom: 0;\\n z-index: 5;\\n padding: 26px 14px 8px;\\n color: #fff;\\n font-family: var(--cf-ui);\\n background: linear-gradient(\\n to top,\\n rgba(12, 9, 4, 0.72),\\n rgba(12, 9, 4, 0.38) 45%,\\n rgba(12, 9, 4, 0)\\n );\\n transition: opacity 360ms ease;\\n }\\n\\n /* nobody is touching it: the apparatus leaves the picture, the way\\n every player made since 2010 does; a hand on the frame brings it\\n back (wake) */\\n .cf-player.is-idle {\\n opacity: 0;\\n pointer-events: none;\\n }\\n\\n /* the transport waits outside while the front door is open */\\n .cf-player.is-away {\\n visibility: hidden;\\n }\\n\\n .cf-scrub {\\n position: relative;\\n height: 22px;\\n display: flex;\\n align-items: center;\\n cursor: pointer;\\n touch-action: none;\\n outline: none;\\n }\\n\\n .cf-scrub-track {\\n position: relative;\\n display: flex;\\n gap: 2px;\\n width: 100%;\\n height: 3px;\\n transition: height 120ms ease;\\n }\\n\\n .cf-scrub:hover .cf-scrub-track,\\n .cf-scrub:focus-visible .cf-scrub-track {\\n height: 5px;\\n }\\n\\n .cf-scrub-ch {\\n position: relative;\\n overflow: hidden;\\n background: rgba(255, 255, 255, 0.28);\\n transition: transform 120ms ease;\\n }\\n\\n /* the chapter under the pointer stands up a little, the way the\\n hovered chapter on every chaptered bar does */\\n .cf-scrub:hover .cf-scrub-ch:hover {\\n transform: scaleY(1.7);\\n }\\n\\n /* the ghost: a pale fill to wherever the pointer is resting, so\\n the eye reads the cut it would make before the hand makes it */\\n .cf-scrub-ghost {\\n position: absolute;\\n inset: 0 auto 0 0;\\n display: block;\\n background: rgba(255, 255, 255, 0.34);\\n width: calc(\\n clamp(0, (var(--cf-hov, 0) - var(--s0)) / var(--sl), 1) * 100%\\n );\\n }\\n\\n /* each chapter fills with the part of the whole-film playhead that\\n falls inside it — one custom property, updated per frame, and no\\n re-render anywhere */\\n .cf-scrub-fill {\\n position: absolute;\\n inset: 0 auto 0 0;\\n display: block;\\n background: var(--cf-red);\\n width: calc(\\n clamp(0, (var(--cf-prog, 0) - var(--s0)) / var(--sl), 1) * 100%\\n );\\n }\\n\\n /* the knob sits on the fill's end: --cf-hpx is that end in pixels,\\n from the same fractions and the measured track (see frame) */\\n .cf-scrub-head {\\n position: absolute;\\n top: 50%;\\n left: calc(var(--cf-hpx, 0) * 1px);\\n width: 13px;\\n height: 13px;\\n margin: -6.5px 0 0 -6.5px;\\n border-radius: 50%;\\n background: var(--cf-red);\\n transform: scale(0);\\n transition: transform 120ms ease;\\n }\\n\\n .cf-scrub:hover .cf-scrub-head,\\n .cf-scrub:focus-visible .cf-scrub-head,\\n .cf-scrub.is-live .cf-scrub-head {\\n transform: scale(1);\\n }\\n\\n .cf-scrub-tip {\\n position: absolute;\\n bottom: 18px;\\n left: calc(var(--cf-tipx, 0) * 1px);\\n transform: translateX(-50%);\\n display: flex;\\n flex-direction: column;\\n align-items: center;\\n gap: 2px;\\n white-space: nowrap;\\n background: rgba(12, 9, 4, 0.92);\\n color: #fff;\\n padding: 7px 11px 6px;\\n border-radius: 4px;\\n pointer-events: none;\\n }\\n\\n .cf-scrub-tip-ch {\\n font-size: 10px;\\n letter-spacing: 0.12em;\\n color: rgba(255, 255, 255, 0.72);\\n }\\n\\n .cf-scrub-tip-t {\\n font-size: 13px;\\n font-weight: 600;\\n font-variant-numeric: tabular-nums;\\n }\\n\\n .cf-controls {\\n display: flex;\\n align-items: center;\\n gap: 2px;\\n height: 40px;\\n }\\n\\n /* the round ghost button: a 40px target, the glyph 22px inside it,\\n a hover that is a wash rather than an outline */\\n .cf-ic {\\n appearance: none;\\n border: 0;\\n background: transparent;\\n color: #fff;\\n width: 40px;\\n height: 40px;\\n padding: 0;\\n border-radius: 50%;\\n cursor: pointer;\\n display: inline-flex;\\n align-items: center;\\n justify-content: center;\\n flex: none;\\n opacity: 0.92;\\n transition:\\n background 120ms ease,\\n opacity 120ms ease;\\n }\\n\\n .cf-ic svg {\\n width: 22px;\\n height: 22px;\\n fill: currentColor;\\n display: block;\\n }\\n\\n .cf-ic:hover,\\n .cf-ic:focus-visible {\\n background: rgba(255, 255, 255, 0.14);\\n opacity: 1;\\n outline: none;\\n }\\n\\n /* the label above a button — the name and its key — in the bar's\\n own bubble rather than the browser's slow yellow one */\\n [data-tip] {\\n position: relative;\\n }\\n\\n [data-tip]::before {\\n content: attr(data-tip);\\n position: absolute;\\n bottom: calc(100% + 10px);\\n left: 50%;\\n transform: translate(-50%, 4px);\\n white-space: nowrap;\\n background: rgba(12, 9, 4, 0.92);\\n color: #fff;\\n font-family: var(--cf-ui);\\n font-size: 11px;\\n font-weight: 500;\\n letter-spacing: 0.06em;\\n text-transform: none;\\n padding: 6px 10px;\\n border-radius: 4px;\\n opacity: 0;\\n pointer-events: none;\\n transition:\\n opacity 120ms ease 200ms,\\n transform 120ms ease 200ms;\\n }\\n\\n [data-tip]:hover::before,\\n [data-tip]:focus-visible::before {\\n opacity: 1;\\n transform: translate(-50%, 0);\\n }\\n\\n /* the last button's bubble hugs the right edge instead of the\\n button's centre, so it never leaves the viewport */\\n .cf-ic-last::before {\\n left: auto;\\n right: 0;\\n transform: translate(0, 4px);\\n }\\n\\n .cf-ic-last:hover::before,\\n .cf-ic-last:focus-visible::before {\\n transform: translate(0, 0);\\n }\\n\\n /* the speaker and its fader: the fader is folded away until the\\n pointer is on the speaker, then slides out beside it */\\n .cf-vol {\\n display: inline-flex;\\n align-items: center;\\n }\\n\\n .cf-vol-range {\\n appearance: none;\\n -webkit-appearance: none;\\n width: 0;\\n height: 3px;\\n margin: 0;\\n opacity: 0;\\n border-radius: 2px;\\n background: linear-gradient(\\n to right,\\n #fff calc(var(--cf-vol, 1) * 100%),\\n rgba(255, 255, 255, 0.3) 0\\n );\\n cursor: pointer;\\n outline: none;\\n transition:\\n width 200ms ease,\\n margin 200ms ease,\\n opacity 200ms ease;\\n }\\n\\n .cf-vol:hover .cf-vol-range,\\n .cf-vol:focus-within .cf-vol-range {\\n width: 64px;\\n margin: 0 12px 0 4px;\\n opacity: 1;\\n }\\n\\n .cf-vol-range::-webkit-slider-thumb {\\n -webkit-appearance: none;\\n appearance: none;\\n width: 12px;\\n height: 12px;\\n border-radius: 50%;\\n background: #fff;\\n border: 0;\\n }\\n\\n .cf-vol-range::-moz-range-thumb {\\n width: 12px;\\n height: 12px;\\n border-radius: 50%;\\n background: #fff;\\n border: 0;\\n }\\n\\n /* the chapter's name, beside the clock, is the way into the list */\\n .cf-chap {\\n appearance: none;\\n border: 0;\\n background: transparent;\\n color: rgba(255, 255, 255, 0.78);\\n font: inherit;\\n font-size: 12.5px;\\n letter-spacing: 0.04em;\\n display: inline-flex;\\n align-items: center;\\n gap: 5px;\\n height: 32px;\\n padding: 0 6px 0 2px;\\n border-radius: 4px;\\n cursor: pointer;\\n white-space: nowrap;\\n max-width: min(34ch, 26cqw);\\n transition: color 120ms ease;\\n }\\n\\n .cf-chap span {\\n opacity: 0.6;\\n padding-right: 1px;\\n }\\n\\n .cf-chap svg {\\n width: 14px;\\n height: 14px;\\n flex: none;\\n fill: currentColor;\\n opacity: 0.7;\\n transition: transform 160ms ease;\\n }\\n\\n .cf-chap:hover,\\n .cf-chap:focus-visible {\\n color: #fff;\\n outline: none;\\n }\\n\\n .cf-chap:hover svg {\\n transform: translateX(2px);\\n }\\n\\n /* a button with its name on it, set like the link beside it */\\n .cf-word {\\n appearance: none;\\n border: 0;\\n background: transparent;\\n color: rgba(255, 255, 255, 0.82);\\n font-family: var(--cf-ui);\\n font-size: 11px;\\n letter-spacing: 0.1em;\\n text-transform: uppercase;\\n display: inline-flex;\\n align-items: center;\\n gap: 7px;\\n height: 32px;\\n padding: 0 12px 0 8px;\\n border-radius: 16px;\\n cursor: pointer;\\n white-space: nowrap;\\n transition:\\n background 120ms ease,\\n color 120ms ease;\\n }\\n\\n .cf-word svg {\\n width: 18px;\\n height: 18px;\\n fill: currentColor;\\n }\\n\\n .cf-word:hover,\\n .cf-word:focus-visible {\\n background: rgba(255, 255, 255, 0.14);\\n color: #fff;\\n outline: none;\\n }\\n\\n /* the confirmation glyph, fixed at the middle of the picture */\\n .cf-burst {\\n position: fixed;\\n left: 50%;\\n top: 50%;\\n width: 88px;\\n height: 88px;\\n margin: -44px 0 0 -44px;\\n border-radius: 50%;\\n background: rgba(12, 9, 4, 0.55);\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n pointer-events: none;\\n z-index: 4;\\n animation: cf-burst 560ms ease-out forwards;\\n }\\n\\n .cf-burst svg {\\n width: 42px;\\n height: 42px;\\n fill: #fff;\\n }\\n\\n @keyframes cf-burst {\\n from {\\n opacity: 1;\\n transform: scale(0.72);\\n }\\n\\n to {\\n opacity: 0;\\n transform: scale(1.5);\\n }\\n }\\n\\n /* the hand that stopped moving takes its pointer with it */\\n .cf-page.is-idle {\\n cursor: none;\\n }\\n\\n /* CC is a word, set as a badge; on, it is underlined in the film's\\n red the way every captions button on earth is */\\n .cf-ic-cc {\\n font-size: 11px;\\n font-weight: 700;\\n letter-spacing: 0.06em;\\n position: relative;\\n }\\n\\n .cf-ic-cc.is-on::after,\\n .cf-ic.is-on::after {\\n content: '';\\n position: absolute;\\n left: 13px;\\n right: 13px;\\n bottom: 7px;\\n height: 2px;\\n background: var(--cf-red);\\n border-radius: 1px;\\n }\\n\\n .cf-ic.is-on {\\n position: relative;\\n }\\n\\n .cf-time {\\n margin-left: 8px;\\n font-size: 12.5px;\\n letter-spacing: 0.02em;\\n font-variant-numeric: tabular-nums;\\n color: rgba(255, 255, 255, 0.82);\\n white-space: nowrap;\\n }\\n\\n .cf-time b {\\n font-weight: 500;\\n color: #fff;\\n }\\n\\n .cf-time i {\\n font-style: normal;\\n padding: 0 4px;\\n opacity: 0.6;\\n }\\n\\n /* the chapter title cannot be allowed to widen the control row */\\n .cf-time em {\\n display: inline-block;\\n max-width: min(28ch, 22cqw);\\n overflow: hidden;\\n text-overflow: ellipsis;\\n vertical-align: bottom;\\n font-style: normal;\\n margin-left: 6px;\\n color: rgba(255, 255, 255, 0.72);\\n }\\n\\n .cf-spacer {\\n flex: 1 1 auto;\\n }\\n\\n .cf-link {\\n color: rgba(255, 255, 255, 0.82);\\n text-decoration: none;\\n font-size: 11px;\\n letter-spacing: 0.1em;\\n text-transform: uppercase;\\n padding: 0 12px;\\n white-space: nowrap;\\n }\\n\\n .cf-link:hover {\\n color: #fff;\\n text-decoration: underline;\\n text-underline-offset: 3px;\\n }\\n\\n /* only ever under ?debug: which cut of the film the browser has */\\n .cf-build {\\n color: rgba(255, 255, 255, 0.55);\\n font:\\n 10px/1 ui-monospace,\\n monospace;\\n letter-spacing: 0.04em;\\n padding: 0 12px;\\n white-space: nowrap;\\n }\\n\\n /* embedded, the iframe wraps the film AND its transport directly;\\n the app's own chrome inside the iframe stands down instead */\\n body.cf-embedded .topbar,\\n body.cf-embedded .footer {\\n display: none;\\n }\\n\\n /* THE FILM ESCAPES THE APP'S PAGE CONTAINER. .page centres a\\n measured column with gutters and a top offset — right for every\\n demo page, and a pink-tinted mat around a film that owns its\\n frame. Both film bodies flatten it; the film's own layout is the\\n page. (This mattered only once cf-page left position: fixed —\\n fixed elements never felt the container.) */\\n body.cf-film .page,\\n body.cf-embedded .page {\\n width: 100%;\\n margin: 0;\\n padding: 0;\\n }\\n\\n /* a clear beat owns nothing on the glass, the numeral included */\\n .cf-clear .cf-ghost {\\n display: none;\\n }\\n\\n /* ---- the gate --------------------------------------------------- */\\n /* the card stands BESIDE the tower, never over it: the boot pose\\n seats the building left of centre, so the door takes the right\\n third — the same real estate the title beat's type owns — and\\n its wash leans that way too instead of dimming the whole frame */\\n /* THE DOOR DOES NOT BLUR THE BUILDING. A poster's job is to make\\n the subject look like the reason to press play; a blur makes it\\n look like a placeholder. The scene stays sharp and keeps\\n turning (see the poster orbit) — the only thing over it is a\\n ground for the type, raked away from the tower. */\\n .cf-gate {\\n position: absolute;\\n inset: 0;\\n z-index: 7;\\n display: flex;\\n align-items: center;\\n justify-content: flex-end;\\n background: linear-gradient(\\n 100deg,\\n rgba(24, 18, 8, 0) 26%,\\n rgba(24, 18, 8, 0.28) 52%,\\n rgba(22, 16, 7, 0.72) 82%\\n );\\n }\\n\\n /* THE DOOR SCALES, IT DOES NOT RE-LAY.\\n \\n A poster is a fixed composition: the wordmark sits hard right of\\n the building, the rule hangs above it, the seal stamps below. At\\n 735 x 551 — the size it was drawn at — every number here is a\\n pixel. Smaller than that, all of them shrink together and the\\n composition survives; a breakpoint would instead have re-laid it\\n at two or three widths and been wrong at every width between.\\n \\n --gu is that one number: 1px at the design size and less below\\n it, driven by whichever of the two axes is scarcer, so a short\\n wide shot compresses exactly as a narrow tall one does. It never\\n exceeds 1px, so the door never grows past the poster it is. */\\n .cf-gate {\\n --gu: min(1px, 0.136cqw, 0.181cqh);\\n }\\n\\n .cf-gate-in {\\n position: relative;\\n text-align: center;\\n font-family: var(--cf-ui);\\n color: #f7f0e0;\\n /* hard right: the poster is a two-column composition — building\\n in one half, wordmark in the other — and the wordmark drifting\\n toward the middle closes the gap that makes it one */\\n margin-right: calc(132 * var(--gu));\\n }\\n\\n /* the museum frame: hairline, inset like a mat, above the scene\\n and under the type */\\n .cf-gate-frame {\\n position: absolute;\\n inset: min(30px, 2.4cqw);\\n border: 1px solid rgba(247, 240, 224, 0.34);\\n pointer-events: none;\\n animation: cf-mg-fade 1200ms ease-out both;\\n }\\n\\n /* the spine: vertical Japanese down the inside of the frame */\\n .cf-gate-vert {\\n position: absolute;\\n top: 50%;\\n left: min(58px, 4.6cqw);\\n transform: translateY(-50%);\\n writing-mode: vertical-rl;\\n white-space: nowrap;\\n font-family: var(--cf-ui);\\n font-weight: 600;\\n font-size: clamp(10px, 0.95cqw, 13px);\\n letter-spacing: 0.3em;\\n color: rgba(247, 240, 224, 0.66);\\n animation: cf-mg-fade 900ms ease-out both;\\n animation-delay: 2050ms;\\n }\\n\\n .cf-gate-k {\\n position: relative;\\n margin: 0;\\n font-family: var(--cf-display);\\n font-weight: 800;\\n font-size: min(calc(260 * var(--gu)), 15cqw);\\n letter-spacing: -0.03em;\\n white-space: nowrap;\\n line-height: 1.02;\\n text-shadow: 0 8px 70px rgba(20, 14, 4, 0.55);\\n }\\n\\n /* the ghost: the title again, enormous and hollow, standing behind\\n itself — the poster's depth comes from one word at two weights */\\n .cf-gate-ghost {\\n position: absolute;\\n left: 50%;\\n top: 44%;\\n transform: translate(-50%, -50%) scale(1.9);\\n font-size: 1em;\\n line-height: 1;\\n color: transparent;\\n -webkit-text-stroke: 1px rgba(247, 240, 224, 0.2);\\n white-space: nowrap;\\n pointer-events: none;\\n animation: cf-mg-ghost 2400ms ease-out both;\\n animation-delay: 700ms;\\n }\\n\\n @keyframes cf-mg-ghost {\\n 0% {\\n opacity: 0;\\n transform: translate(-50%, -50%) scale(2.05);\\n }\\n\\n 100% {\\n opacity: 1;\\n transform: translate(-50%, -50%) scale(1.9);\\n }\\n }\\n\\n /* the index strip along the foot of the frame */\\n .cf-gate-index {\\n position: absolute;\\n left: min(90px, 7cqw);\\n right: min(90px, 7cqw);\\n bottom: clamp(30px, 5.4vh, 56px);\\n display: flex;\\n flex-wrap: wrap;\\n justify-content: center;\\n gap: 6px min(36px, 2.2cqw);\\n margin: 0;\\n font-family: var(--cf-ui);\\n font-size: clamp(9px, 0.8cqw, 11px);\\n font-weight: 700;\\n letter-spacing: 0.22em;\\n color: rgba(247, 240, 224, 0.62);\\n animation: cf-mg-fade 900ms ease-out both;\\n animation-delay: 2500ms;\\n }\\n\\n .cf-gate-index span {\\n white-space: nowrap;\\n }\\n\\n .cf-gate-t {\\n margin: calc(10 * var(--gu)) 0 0;\\n font-size: calc(15 * var(--gu));\\n font-weight: 800;\\n letter-spacing: 0.5em;\\n text-indent: 0.5em;\\n }\\n\\n .cf-gate-s {\\n margin: calc(6 * var(--gu)) 0 calc(30 * var(--gu));\\n font-size: calc(12 * var(--gu));\\n letter-spacing: 0.14em;\\n color: rgba(247, 240, 224, 0.72);\\n }\\n\\n /* the claim under the subtitle: the display serif, italic, a step\\n back in the ink; and under it the four words, tiny and wide */\\n .cf-gate-live {\\n margin: calc(14 * var(--gu)) 0 0;\\n font-family: var(--cf-display);\\n font-style: italic;\\n font-size: calc(16 * var(--gu));\\n line-height: 1.4;\\n letter-spacing: 0.01em;\\n color: rgba(247, 240, 224, 0.78);\\n max-width: 40ch;\\n text-wrap: balance;\\n }\\n\\n .cf-gate-live-k {\\n margin: calc(8 * var(--gu)) 0 calc(22 * var(--gu));\\n font-family: var(--cf-ui);\\n font-size: calc(10 * var(--gu));\\n letter-spacing: 0.22em;\\n text-transform: uppercase;\\n color: rgba(247, 240, 224, 0.5);\\n }\\n\\n .cf-gate-row {\\n display: flex;\\n gap: calc(12 * var(--gu));\\n justify-content: center;\\n }\\n\\n .cf-go {\\n appearance: none;\\n font: inherit;\\n font-family: var(--cf-ui);\\n font-size: calc(13 * var(--gu));\\n font-weight: 600;\\n letter-spacing: 0.14em;\\n cursor: pointer;\\n padding: calc(14 * var(--gu)) calc(30 * var(--gu));\\n border-radius: 999px;\\n border: 1px solid #f2e9d2;\\n background: #f2e9d2;\\n color: #2e2515;\\n box-shadow: 0 10px 34px rgba(20, 14, 4, 0.35);\\n transition:\\n transform 220ms cubic-bezier(0.22, 1, 0.36, 1),\\n box-shadow 220ms ease;\\n }\\n\\n .cf-go:hover {\\n transform: translateY(-2px);\\n box-shadow: 0 16px 44px rgba(20, 14, 4, 0.42);\\n }\\n\\n .cf-go:disabled {\\n opacity: 0.45;\\n cursor: default;\\n }\\n\\n .cf-go.is-quiet {\\n background: transparent;\\n color: #f2e9d2;\\n border-color: rgba(242, 233, 210, 0.5);\\n }\\n\\n .cf-go.is-quiet:hover {\\n border-color: #f2e9d2;\\n }\\n\\n /* ---- the title package ---------------------------------------- *\\n One motion system for front and back matter, staged like a\\n broadcast title: rule, glyphs, mark, sub, seal, controls. All\\n CSS — these screens live outside the score on purpose, since\\n both exist precisely when the film is not running. */\\n .cf-matter {\\n position: relative;\\n }\\n\\n .cf-mg-rule {\\n display: block;\\n width: 1px;\\n height: 56px;\\n margin: 0 auto 18px;\\n background: rgba(247, 240, 224, 0.65);\\n transform-origin: 50% 0;\\n animation: cf-mg-rule 900ms cubic-bezier(0.22, 1, 0.36, 1) both;\\n }\\n\\n @keyframes cf-mg-rule {\\n 0% {\\n transform: scaleY(0);\\n }\\n\\n 100% {\\n transform: scaleY(1);\\n }\\n }\\n\\n .cf-mg-g1,\\n .cf-mg-g2 {\\n display: inline-block;\\n animation: cf-mg-glyph 1300ms cubic-bezier(0.22, 1, 0.36, 1) both;\\n animation-delay: 350ms;\\n }\\n\\n .cf-mg-g2 {\\n animation-delay: 650ms;\\n }\\n\\n @keyframes cf-mg-glyph {\\n 0% {\\n opacity: 0;\\n filter: blur(16px);\\n transform: translateY(10px);\\n }\\n\\n 100% {\\n opacity: 1;\\n filter: blur(0);\\n transform: translateY(0);\\n }\\n }\\n\\n /* the wordmark tracks IN — from letterspaced air to its set width,\\n the oldest move in broadcast titles because nothing else says\\n \\\"this is the name\\\" as quietly */\\n .cf-mg-mark {\\n white-space: nowrap;\\n transform-origin: center;\\n animation: cf-mg-track 1400ms cubic-bezier(0.22, 1, 0.36, 1) both;\\n animation-delay: 1050ms;\\n }\\n\\n /* THE WORDMARK NO LONGER RE-MEASURES ITSELF.\\n Tracking in from 1.1em added about 250px of laid-out width at the\\n first frame — so on anything but a wide screen the mark started\\n wrapped onto two lines and un-wrapped partway through its own\\n animation, taking the card under it with it. The same gesture\\n reads identically as a scale on the X axis, which is a composited\\n transform and reflows nothing. */\\n @keyframes cf-mg-track {\\n 0% {\\n opacity: 0;\\n transform: scaleX(1.16);\\n }\\n\\n 100% {\\n opacity: 1;\\n transform: scaleX(1);\\n }\\n }\\n\\n .cf-mg-sub {\\n animation: cf-mg-fade 800ms ease-out both;\\n animation-delay: 1650ms;\\n }\\n\\n .cf-mg-row {\\n animation: cf-mg-fade 800ms ease-out both;\\n animation-delay: 2250ms;\\n }\\n\\n /* centred as a block AND line by line: the shorthand margin used to\\n cancel the auto inline margins, so the column sat at the card's\\n left edge with its lines centred inside a box narrower than its\\n first line */\\n .cf-mg-credits {\\n display: flex;\\n flex-direction: column;\\n align-items: center;\\n white-space: nowrap;\\n max-width: 88cqw;\\n gap: 4px;\\n margin: 0 auto 22px;\\n font-size: 11px;\\n letter-spacing: 0.14em;\\n color: rgba(247, 240, 224, 0.6);\\n animation: cf-mg-fade 900ms ease-out both;\\n animation-delay: 2050ms;\\n }\\n\\n @keyframes cf-mg-fade {\\n 0% {\\n opacity: 0;\\n transform: translateY(6px);\\n }\\n\\n 100% {\\n opacity: 1;\\n transform: translateY(0);\\n }\\n }\\n\\n /* the seal: the lineup's stamp, miniature — it lands hard and\\n late, canted the way a hand cants it */\\n /* on the end card the seal belongs IN the card: hung off the right\\n edge it floated in the corner of the frame with nothing to sit on */\\n .cf-end-in .cf-mg-seal {\\n position: static;\\n margin: 0 auto 16px;\\n }\\n\\n /* the open end of the date. A dash at the numeral's own weight is a\\n slab as wide as a digit and reads as a fifth figure. */\\n .cf-end-dash {\\n font-style: normal;\\n font-size: 0.4em;\\n font-weight: 600;\\n vertical-align: 0.5em;\\n margin-left: 0.08em;\\n opacity: 0.7;\\n }\\n\\n /* THE SEAL IS A DISC AND THE WORD SITS ON ITS CENTRE.\\n It was a vertical badge with letterspaced Latin running down it,\\n and letter-spacing adds its space AFTER the last letter — so the\\n word was always sitting one space above the middle of its own\\n box, which is exactly the kind of thing you cannot stop seeing.\\n Horizontal, tracked from the centre (text-indent pays the\\n trailing space back), on a round stamp. */\\n /* the stamp is part of the poster, so it scales with the poster —\\n every number here is in the door's own unit (see --gu) */\\n .cf-mg-seal {\\n position: absolute;\\n top: calc(4 * var(--gu, 1px));\\n right: calc(-76 * var(--gu, 1px));\\n display: grid;\\n place-items: center;\\n width: calc(64 * var(--gu, 1px));\\n height: calc(64 * var(--gu, 1px));\\n font-family: var(--cf-ui);\\n font-weight: 700;\\n font-size: calc(15 * var(--gu, 1px));\\n letter-spacing: 0.12em;\\n text-indent: 0.12em;\\n text-transform: uppercase;\\n color: #f7f0e0;\\n background: var(--cf-mark);\\n border-radius: 50%;\\n animation: cf-mg-seal 500ms cubic-bezier(0.16, 1.2, 0.3, 1) both;\\n animation-delay: 1900ms;\\n }\\n\\n @keyframes cf-mg-seal {\\n 0% {\\n opacity: 0;\\n transform: rotate(-4deg) scale(1.6);\\n }\\n\\n 30% {\\n opacity: 1;\\n transform: rotate(-4deg) scale(0.97);\\n }\\n\\n 100% {\\n opacity: 0.94;\\n transform: rotate(-4deg) scale(1);\\n }\\n }\\n\\n /* ---- the end card ---------------------------------------------- */\\n .cf-end {\\n position: absolute;\\n inset: 0;\\n z-index: 6;\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n background: rgba(28, 22, 10, 0.58);\\n backdrop-filter: blur(1.5px);\\n }\\n\\n .cf-end-in {\\n text-align: center;\\n font-family: var(--cf-ui);\\n color: #f7f0e0;\\n }\\n\\n .cf-end-k {\\n margin: 0;\\n font-family: var(--cf-display);\\n font-weight: 800;\\n /* '1882 —' is six characters, not four: at 19cqw it was six times\\n the viewport wide and it set the width of the whole card */\\n font-size: min(150px, min(15vh, 9cqw));\\n letter-spacing: -0.03em;\\n white-space: nowrap;\\n line-height: 1;\\n text-shadow: 0 6px 60px rgba(20, 14, 4, 0.55);\\n }\\n\\n .cf-end-t {\\n margin: 12px 0 0;\\n font-size: 14px;\\n font-weight: 800;\\n letter-spacing: 0.5em;\\n text-indent: 0.5em;\\n }\\n\\n .cf-end-s {\\n margin: 6px 0 26px;\\n font-size: 12px;\\n letter-spacing: 0.14em;\\n color: rgba(247, 240, 224, 0.7);\\n }\\n\\n /* ---- the stamp -------------------------------------------------- *\\n A hanko, not a title: it strikes, settles at once, and is simply\\n replaced by the next. Seal-red on purpose — it shares the\\n annotation's ink because it IS an annotation, pressed over each\\n tower as the lineup calls the roll. */\\n .cf-stamp {\\n position: absolute;\\n inset: 0;\\n z-index: 3;\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n margin: 0;\\n pointer-events: none;\\n font-family: var(--cf-display);\\n font-weight: 700;\\n font-size: min(24vh, 16cqw);\\n letter-spacing: 0.04em;\\n color: var(--cf-mark);\\n mix-blend-mode: multiply;\\n animation: cf-stamp 700ms cubic-bezier(0.16, 1.3, 0.3, 1) both;\\n }\\n\\n @keyframes cf-stamp {\\n 0% {\\n opacity: 0;\\n transform: rotate(-2.5deg) scale(1.5);\\n }\\n\\n 16% {\\n opacity: 0.92;\\n transform: rotate(-2.5deg) scale(0.99);\\n }\\n\\n 100% {\\n opacity: 0.88;\\n transform: rotate(-2.5deg) scale(1);\\n }\\n }\\n\\n /* the app's own chrome, gone: this route is a frame, not a page */\\n body.cf-film .topbar,\\n body.cf-film .footer {\\n display: none;\\n }\\n\\n /* A ROMAN WORD IS NOT TWO GLYPHS.\\n The block was cut for a two-character CJK setting, where the type\\n size and the column width have nothing to do with each other. A\\n Latin word's width IS its letter count, so 'Evangelistes' set at\\n the size that suited 'Obra' runs off the frame — and a word that\\n wraps to two lines lands the whole plate at a different height.\\n So: never wrap, and take the size from the count the plane\\n already carries (--cf-glyphs), capped by the design size. 0.56em\\n is about the average advance of this face. */\\n .cf-kanji.is-latin {\\n font-family: var(--cf-display);\\n font-weight: 800;\\n text-transform: uppercase;\\n color: var(--cf-chap);\\n writing-mode: horizontal-tb;\\n white-space: nowrap;\\n font-size: min(\\n min(104px, 6.2cqw),\\n calc(var(--cf-fit, 42cqw) / (var(--cf-glyphs, 4) * 0.56))\\n );\\n letter-spacing: -0.02em;\\n line-height: 0.92;\\n }\\n .is-title .cf-kanji.is-latin {\\n --cf-fit: 46cqw;\\n font-size: min(\\n min(142px, 8.6cqw),\\n calc(var(--cf-fit) / (var(--cf-glyphs, 4) * 0.56))\\n );\\n }\\n .is-plate .cf-kanji.is-latin {\\n --cf-fit: 24cqw;\\n writing-mode: horizontal-tb;\\n font-size: min(\\n min(76px, 4.8cqw),\\n calc(var(--cf-fit) / (var(--cf-glyphs, 4) * 0.56))\\n );\\n letter-spacing: -0.01em;\\n max-width: 26cqw;\\n }\\n .is-point .cf-kanji.is-latin {\\n --cf-fit: 32cqw;\\n font-size: min(\\n min(82px, 5.2cqw),\\n calc(var(--cf-fit) / (var(--cf-glyphs, 4) * 0.56))\\n );\\n }\\n /* ---- THE RESPONSIVE OVERRIDES, LAST ON PURPOSE ------------- *\\n\\n These are overrides, and an override that appears before the\\n rule it overrides does nothing at equal specificity. They sat\\n a thousand lines ABOVE the gate and the rail they were written\\n to compress, so none of them has ever applied: the door kept\\n its full stack at every width and ran through its own buttons\\n on anything narrow. Moving them to the foot of the sheet is\\n the whole fix — nothing here changed except where it is.\\n\\n They ask the FILM and the SHOT rather than the window, so a\\n film in a small frame gets the same compact layout a phone\\n does. See the container declarations on .cf-page and\\n .cf-stage above. */\\n\\n @container film (max-width: 1180px) {\\n .cf-block {\\n max-width: min(38ch, 56cqw);\\n }\\n\\n .cf-block.is-plate {\\n max-width: min(46ch, 52cqw);\\n }\\n\\n .is-plate .cf-kanji.is-latin {\\n --cf-fit: 28cqw;\\n max-width: 30cqw;\\n }\\n\\n .cf-say {\\n max-width: min(28ch, 42cqw);\\n }\\n }\\n\\n @container film (max-width: 860px) {\\n /* the plate stops being two columns: the word goes over the cues,\\n and the rule that separated them becomes the rule under it */\\n .cf-block.is-plate {\\n top: auto;\\n bottom: 12%;\\n right: 5.5%;\\n left: 5.5%;\\n display: block;\\n transform: none;\\n max-width: none;\\n }\\n\\n .is-plate .cf-plane.is-glyph {\\n border-right: 0;\\n border-bottom: 2px solid var(--cf-chap);\\n padding-right: 0;\\n padding-bottom: 8px;\\n margin-bottom: 14px;\\n }\\n\\n /* stacked under the word, the cues read from the left again */\\n .is-plate .cf-plane:not(.is-glyph) {\\n align-items: flex-start;\\n text-align: left;\\n }\\n\\n .is-plate .cf-read {\\n justify-content: flex-start;\\n }\\n\\n .is-plate .cf-kanji.is-latin {\\n --cf-fit: 84cqw;\\n max-width: none;\\n }\\n\\n .cf-block.is-title,\\n .cf-block.is-lower,\\n .cf-block.is-point {\\n left: 5.5%;\\n right: 5.5%;\\n top: auto;\\n bottom: 13%;\\n max-width: none;\\n text-align: left;\\n }\\n\\n .is-title .cf-read {\\n justify-content: flex-start;\\n }\\n\\n .is-title .cf-kicker {\\n flex-direction: row;\\n }\\n\\n .cf-kanji.is-latin,\\n .is-title .cf-kanji.is-latin,\\n .is-point .cf-kanji.is-latin {\\n --cf-fit: 84cqw;\\n }\\n\\n .cf-say {\\n max-width: min(32ch, 84cqw);\\n }\\n\\n /* a numeral three-quarters of the screen wide behind two columns\\n of type is a texture; behind one column it is a mess */\\n .cf-ghost,\\n .cf-plate .cf-ghost {\\n display: none;\\n }\\n\\n .cf-photo figure {\\n width: min(46cqw, 260px);\\n }\\n\\n /* a narrow frame: the rule drops under the title again */\\n .cf-rail {\\n flex-wrap: wrap;\\n }\\n\\n .cf-years {\\n flex-basis: 100%;\\n width: min(420px, 56cqw);\\n margin-left: 0;\\n margin-top: 8px;\\n }\\n\\n .cf-subs {\\n font-size: 15px;\\n max-width: 88cqw;\\n }\\n }\\n\\n @container film (max-width: 560px) {\\n .cf-kicker {\\n font-size: 10px;\\n letter-spacing: 0.09em;\\n }\\n\\n .cf-say {\\n font-size: 15px;\\n margin-bottom: 8px;\\n }\\n\\n .cf-read {\\n margin-bottom: 12px;\\n }\\n\\n .cf-kanji {\\n margin-bottom: 12px;\\n }\\n\\n .cf-gate-index,\\n .cf-rail {\\n display: none;\\n }\\n\\n .cf-gate-in.cf-matter {\\n margin-right: 0;\\n padding-inline: 20px;\\n }\\n\\n .cf-gate-vert {\\n display: none;\\n }\\n\\n .cf-mg-seal {\\n position: static;\\n margin: 14px auto 0;\\n }\\n\\n .cf-photo {\\n display: none;\\n }\\n\\n .cf-subs {\\n font-size: 14px;\\n bottom: 10%;\\n }\\n }\\n\\n @container shot (max-height: 620px) {\\n .cf-kanji.is-latin {\\n font-size: min(\\n clamp(30px, 8vh, 64px),\\n calc(var(--cf-fit, 42cqw) / (var(--cf-glyphs, 4) * 0.56))\\n );\\n }\\n\\n .cf-say {\\n font-size: clamp(13px, 2.4vh, 18px);\\n margin-bottom: 7px;\\n }\\n\\n .cf-block.is-lower,\\n .cf-block.is-title {\\n bottom: 8%;\\n }\\n\\n .has-subs .cf-block.is-lower,\\n .has-subs .cf-block.is-title {\\n bottom: 17%;\\n }\\n }\\n /* Sagrada: typography belongs to the shot, not the phone viewport.\\n Keep a compact editorial corner in square/portrait containers;\\n never turn the architecture into the backdrop for a full-width card. */\\n @container shot (max-aspect-ratio: 4/3) or ((max-width: 900px) and (max-height: 500px)) {\\n .is-sagrada .cf-type {\\n --cf-lx: 0;\\n --cf-ly: 0;\\n }\\n .is-sagrada .cf-block,\\n .is-sagrada .cf-block.is-title,\\n .is-sagrada .cf-block.is-lower,\\n .is-sagrada .cf-block.is-point,\\n .is-sagrada .cf-block.is-plate {\\n left: 5.5%;\\n right: auto;\\n top: auto;\\n bottom: 12%;\\n width: 48cqw;\\n max-width: 48cqw;\\n display: block;\\n text-align: left;\\n }\\n .is-sagrada .cf-kanji.is-latin,\\n .is-sagrada .is-title .cf-kanji.is-latin,\\n .is-sagrada .is-point .cf-kanji.is-latin,\\n .is-sagrada .is-plate .cf-kanji.is-latin {\\n --cf-fit: 46cqw;\\n font-size: min(\\n 5.6cqw,\\n 6cqh,\\n calc(46cqw / (var(--cf-glyphs, 4) * 0.56))\\n );\\n max-width: 46cqw;\\n margin-bottom: 1.5cqw;\\n }\\n .is-sagrada .cf-kicker {\\n font-size: clamp(7px, 1.65cqw, 12px);\\n gap: 1.5cqw;\\n margin-bottom: 1.5cqw;\\n letter-spacing: 0.07em;\\n }\\n .is-sagrada .cf-kicker > span {\\n white-space: normal;\\n }\\n .is-sagrada .cf-kicker::after {\\n min-width: 2cqw;\\n height: 1px;\\n }\\n .is-sagrada .cf-read {\\n gap: 0.8cqw 1.2cqw;\\n margin-bottom: 2cqw;\\n justify-content: flex-start;\\n }\\n .is-sagrada .cf-romaji,\\n .is-sagrada .cf-block:not(:has(.is-glyph)) .cf-romaji {\\n font-size: clamp(8px, 1.9cqw, 13px);\\n letter-spacing: 0.06em;\\n }\\n .is-sagrada .cf-gloss,\\n .is-sagrada .cf-block:not(:has(.is-glyph)) .cf-gloss {\\n font-size: clamp(8px, 1.9cqw, 13px);\\n }\\n .is-sagrada .cf-say {\\n font-size: clamp(9px, 2.1cqw, 15px);\\n line-height: 1.2;\\n max-width: 48cqw;\\n margin-bottom: 1cqw;\\n }\\n .is-sagrada .is-plate .cf-plane.is-glyph {\\n padding: 0 0 1.2cqw;\\n margin-bottom: 1.5cqw;\\n border-width: 1px;\\n }\\n .is-sagrada .cf-subs {\\n font-size: clamp(10px, 2.4cqw, 15px);\\n max-width: 80cqw;\\n bottom: 5%;\\n }\\n .is-sagrada .cf-ghost {\\n display: none;\\n }\\n }\\n\"],[13]],[\"@name\",\"row\",\"c\",\"w\",\"cue\",\"f\",\"f\",\"@menuTitle\",\"@menuSub\",\"&gate\",\"&end\",\"&picture\",\"&default\"],[\"if\",\"unless\",\"each\",\"-track-array\",\"yield\"]]",
4048
- "moduleName": "/home/runner/work/boxel/boxel/packages/choreo/src/film/film.gts",
4049
- "scope": () => [
4050
- on,
4051
- Joins,
4052
- Track,
4053
- Plate,
4054
- Stamp,
4055
- Clip,
4056
- Insert,
4057
- Captions,
4058
- Rail,
4059
- Choreo,
4060
- MotionModifier,
4061
- at,
4062
- Gate,
4063
- EndCard,
4064
- Menu,
4065
- Burst,
4066
- Player,
4067
- FilmGraph
4068
- ],
4069
- "isStrictMode": true
4070
- }), this);
4071
- }
4072
- };
4073
- //#endregion
4074
- export { clipWindow as C, clipLanes as S, resolveClips as T, lerp as _, chapterHeads as a, rgba as b, joinInto as c, tailFor as d, totalSecs as f, hex as g, clamp01 as h, beatStart as i, schedule as l, RAD as m, Seam as n, contents as o, waypoints as p, TICK as r, cues as s, Film as t, secsBefore as u, luminance as v, resolveClip as w, smooth as x, mmss as y };