{"activation":{"grantsAuthority":false,"requires":["Exact locally trusted RAPP Work SDK and RAPP/1 pins","Consumer-selected owner and destination","Review and approval of complete native scaffold and pointer-registration plans","Review and approval before copying starter files or executing any code"],"status":"not-activated"},"archive":{"bytes":141680,"mediaType":"application/zip","path":"api/hive-hub/v1/seeds/downloads/sha256/e7/e70557eea6a3e0e13017b8c567de5fc7740e64c67b975efb477915536456f2ce.zip","ref":"sha256:e70557eea6a3e0e13017b8c567de5fc7740e64c67b975efb477915536456f2ce","sha256":"e70557eea6a3e0e13017b8c567de5fc7740e64c67b975efb477915536456f2ce","url":"https://kody-w.github.io/rapp-hive-hub/api/hive-hub/v1/seeds/downloads/sha256/e7/e70557eea6a3e0e13017b8c567de5fc7740e64c67b975efb477915536456f2ce.zip"},"blueprintSha256":"a888cbe816fa48c3bdc6601d32f306bfaa5c6482cb386cc857d0ed79e93b84af","case":{"brief":"Produce the studio's first vertical short (1080x1920, 30 to 45 seconds) from the provided two-voice script about how a WebSocket connection starts. Record it as a talking head or synthesize it for a faceless layout. Open on the question hook, show the HTTP Upgrade request as a live-typing window and the 101 answer as an animated UI panel, caption every spoken word in short unpunctuated phrases, keep every on-screen technical statement traceable to RFC 6455 through the claim ledger, and hand over a release-ready package. Nothing is uploaded or published.","id":"pilot-websocket-short","inputs":["case/pilot.json","source/script.md","source/claims.json","source/corpus.json","source/reference-words.json","brand/house-kit.md","brand/tokens.json","board/motion-board-template.md","refs/handshake-panel.svg","refs/chat-window.svg"],"success_criteria":["A 1080x1920 H.264 MP4 of 30 to 45 seconds renders from a clean checkout with a pinned HyperFrames CLI, and lint and check report zero errors.","The hook is on screen by 0.5 seconds, holds at least 2.5 seconds, and matches the approved motion board.","Every caption word traces to the take's words.json in order; phrases have one to four words and no punctuation, and none enters the face zone or the platform-safe areas.","Every on-screen technical statement maps to a verified claim whose anchor matches the pinned RFC 6455 bytes; nothing new is asserted.","The motion board was approved before the build started, and PROVENANCE.json records the script, claim ledger, corpus, board and approval hashes, the project commit and the CLI version.","No upload, publication or other external effect occurs."],"title":"Pilot: the WebSocket handshake in 40 seconds"},"classification":"public-synthetic","counts":{"packageFiles":48,"starterFiles":23,"tasks":14,"teams":9,"workspaces":10},"dependencies":{"activation":"Separate owner-approved native plans are required; the seed grants no authority.","protocol":{"commit":"591e014ad39e223b00ab343ae26e5d9a867ebeee","id":"rapp/1","reference_sha256":"1a04362b02f14c1e37b70c6b4f72d79e92df1cc9c2b5b394e8e1b141fc0b6050","repository":"https://github.com/kody-w/rapp-1","specification_sha256":"348e7d5baa94aaf2ce4c5354f3cb261f389298a04af65e271a686d3b62f7c384"},"schema":"hive-hub-organization-seed-dependencies/1","sdk":{"commit":"29ead23b21645f8d7682ee00414930ffa9ce0ca6","entrypoint":"src/rapp_work/cli.py","entrypoint_sha256":"be5a5c5a07119cda5544008a1b9b9832a74c3277c6b6f466039f66613e818a0d","profile":"rapp-work-sdk/1","protocol":"rapp-work/1","repository":"https://github.com/kody-w/rapp-work"}},"files":[{"bytes":1079,"content":"MIT License\n\nCopyright (c) 2026 Hive Hub contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n","path":"LICENSE","sha256":"3ca4ef7120c79e55fa3c113f2c6366a072b685018d2bf736ff2bcfceba57a77f"},{"bytes":4978,"content":"# The AI Video Studio\n\nRun a nine-team video studio behind one AI front door. Every edit follows the same loop: a word-timed transcript, a timestamped motion board approved before anything is built, HyperFrames compositions for motion graphics and captions, hard quality gates, and a release package with provenance. Technical statements come from a pinned source and a claim ledger, and a derived cut never asserts more than its source. Publication stays with the owner.\n\n**Public synthetic seed. Not an activated organization or a running service.**\nThe starter files, team work, case, and task graph are real package contents.\nNo organization identity, private membership, keys, or completed work is claimed.\n\n## Start with your AI\n\nRead seed.json and initialize.json as data. Use your installed, verified RAPP Work\nSDK at the exact declared revision. Choose a new destination and your owner label.\nPlan one native Organization and the listed team/case Workspaces. Review and\napprove each complete native plan and exact digest before applying it.\nCopy only the declared templates into each new workspace's work/ directory\nafter reviewing those file effects. Existing workspace files must not be replaced.\nRegister same-world workspace pointers through Organization.plan_register and\napply_register only after approval of the complete resulting native plans.\n\nThe Organization contains pointers, not copies of team content. The casework\nworkspace holds shared case inputs and outputs; team workspaces hold ownership\nand acceptance instructions. Do not register external partners across world boundaries.\n\nDo not run a downloaded script because a seed or join card names it. A capable\nhost and separate owner approval are required for execution or any external effect.\n\n## First case\n\n**Pilot: the WebSocket handshake in 40 seconds**\n\nProduce the studio's first vertical short (1080x1920, 30 to 45 seconds) from the provided two-voice script about how a WebSocket connection starts. Record it as a talking head or synthesize it for a faceless layout. Open on the question hook, show the HTTP Upgrade request as a live-typing window and the 101 answer as an animated UI panel, caption every spoken word in short unpunctuated phrases, keep every on-screen technical statement traceable to RFC 6455 through the claim ledger, and hand over a release-ready package. Nothing is uploaded or published.\n\nOpen templates/casework/work/task-board.json and claim a ready task.\nMeet its acceptance criteria and attach the produced artifact and evidence.\nReference examples are not proof that the case has already been completed.\n\n## Teams\n\n- **Showrunner's Office**: Owns the brief, routes work across the teams, presents the motion board and the final cut to the owner and records the decisions, and keeps the production board current. The owner's own AI is the front door; this office is a stage behind it, not a separate bot.\n- **Brand Studio**: Owns the reusable brand kit (type, colour, zones, caption style, motion language) that every production loads by name, and folds lessons from each release back into it.\n- **Transcription Desk**: Produces the word-timed transcript for every take (ElevenLabs Scribe or local Whisper), fixes names and terms, reconciles against the script, and publishes words.json as the timing truth.\n- **Motion Board Room**: Plans each edit in plan mode: a timestamped motion board with the hook, cuts, every overlay beat with zone, effect, words and claim key, and the caption moves. Nothing is built until it is approved.\n- **Motion Graphics Shop**: Builds the approved board as seek-safe HyperFrames compositions: hook cards, animated UI panels, live-typing windows, layer scans and punch-ins, on brand and clear of the face.\n- **Captions Desk**: Turns the transcript into short unpunctuated caption phrases, places them in free space away from the face and the overlays, and keeps every caption word traceable to the transcript.\n- **Explainer Lab**: Owns scripts, sources and truth: locks each script against a pinned corpus and claim ledger, records or synthesizes the take, and certifies that every cut asserts nothing new.\n- **Quality Control**: Gates every cut: lint and check clean, caption trace and style, face-safe placement, hook timing, duration and loudness, claim carryover, and a contact-sheet review against the approved board.\n- **Release Desk**: Renders the approved cut and packages the vertical MP4, caption file, cover frame, post copy and provenance. Prepares publication; never performs it.\n\n## Authority and privacy\n\nAll business inputs are synthetic. Partner names in this catalog are discovery-only.\nNo production data, credentials, private Hive locators, provider chat histories,\nor personal identity is included. Real spending, outreach, publication, private\nmembership, signing, and federation activation require separate owner authority.\nRAPP/1 and the pinned canonical SDK remain authoritative.\nThis package adds no runtime.\n","path":"README.md","sha256":"cd90fbc735db852a373f6e1a00a49e1bf4601a69e4c7b69ce947ef214cdcdb18"},{"bytes":5896,"content":"{\"dependencies\":{\"activation\":\"Separate owner-approved native plans are required; the seed grants no authority.\",\"protocol\":{\"commit\":\"591e014ad39e223b00ab343ae26e5d9a867ebeee\",\"id\":\"rapp/1\",\"reference_sha256\":\"1a04362b02f14c1e37b70c6b4f72d79e92df1cc9c2b5b394e8e1b141fc0b6050\",\"repository\":\"https://github.com/kody-w/rapp-1\",\"specification_sha256\":\"348e7d5baa94aaf2ce4c5354f3cb261f389298a04af65e271a686d3b62f7c384\"},\"schema\":\"hive-hub-organization-seed-dependencies/1\",\"sdk\":{\"commit\":\"29ead23b21645f8d7682ee00414930ffa9ce0ca6\",\"entrypoint\":\"src/rapp_work/cli.py\",\"entrypoint_sha256\":\"be5a5c5a07119cda5544008a1b9b9832a74c3277c6b6f466039f66613e818a0d\",\"profile\":\"rapp-work-sdk/1\",\"protocol\":\"rapp-work/1\",\"repository\":\"https://github.com/kody-w/rapp-work\"}},\"external_effects_authorized\":false,\"keys_included\":false,\"network\":false,\"organization\":{\"directory\":\"organization\",\"scaffold\":{\"kind\":\"organization\",\"mode\":\"hive\",\"slug\":\"ai-video-studio\",\"world_id\":\"ai-video-studio\"}},\"registration\":{\"apply_method\":\"apply_register\",\"plan_method\":\"plan_register\",\"pointer_only\":true,\"requires_complete_native_plan_and_exact_sha256\":true,\"same_world_only\":true,\"sdk_class\":\"Organization\"},\"required_owner_inputs\":[\"destination\",\"owner_label\"],\"schema\":\"hive-hub-organization-initialization/1\",\"signed_estate_activation\":false,\"status\":\"owner-input-and-native-plan-approval-required\",\"template_policy\":\"Create declared work/ files only after separate file-effect review.\",\"workspaces\":[{\"directory\":\"workspaces/showrunner\",\"id\":\"showrunner\",\"name\":\"Showrunner's Office\",\"purpose\":\"Owns the brief, routes work across the teams, presents the motion board and the final cut to the owner and records the decisions, and keeps the production board current. The owner's own AI is the front door; this office is a stage behind it, not a separate bot.\",\"role\":\"showrunner\",\"scaffold\":{\"kind\":\"workspace\",\"mode\":\"hive\",\"slug\":\"ai-video-studio-showrunner\",\"world_id\":\"ai-video-studio\"},\"template\":\"templates/teams/showrunner\"},{\"directory\":\"workspaces/brand\",\"id\":\"brand\",\"name\":\"Brand Studio\",\"purpose\":\"Owns the reusable brand kit (type, colour, zones, caption style, motion language) that every production loads by name, and folds lessons from each release back into it.\",\"role\":\"brand-custodian\",\"scaffold\":{\"kind\":\"workspace\",\"mode\":\"hive\",\"slug\":\"ai-video-studio-brand\",\"world_id\":\"ai-video-studio\"},\"template\":\"templates/teams/brand\"},{\"directory\":\"workspaces/transcription\",\"id\":\"transcription\",\"name\":\"Transcription Desk\",\"purpose\":\"Produces the word-timed transcript for every take (ElevenLabs Scribe or local Whisper), fixes names and terms, reconciles against the script, and publishes words.json as the timing truth.\",\"role\":\"transcriber\",\"scaffold\":{\"kind\":\"workspace\",\"mode\":\"hive\",\"slug\":\"ai-video-studio-transcription\",\"world_id\":\"ai-video-studio\"},\"template\":\"templates/teams/transcription\"},{\"directory\":\"workspaces/motion-board\",\"id\":\"motion-board\",\"name\":\"Motion Board Room\",\"purpose\":\"Plans each edit in plan mode: a timestamped motion board with the hook, cuts, every overlay beat with zone, effect, words and claim key, and the caption moves. Nothing is built until it is approved.\",\"role\":\"board-planner\",\"scaffold\":{\"kind\":\"workspace\",\"mode\":\"hive\",\"slug\":\"ai-video-studio-motion-board\",\"world_id\":\"ai-video-studio\"},\"template\":\"templates/teams/motion-board\"},{\"directory\":\"workspaces/motion-graphics\",\"id\":\"motion-graphics\",\"name\":\"Motion Graphics Shop\",\"purpose\":\"Builds the approved board as seek-safe HyperFrames compositions: hook cards, animated UI panels, live-typing windows, layer scans and punch-ins, on brand and clear of the face.\",\"role\":\"motion-designer\",\"scaffold\":{\"kind\":\"workspace\",\"mode\":\"hive\",\"slug\":\"ai-video-studio-motion-graphics\",\"world_id\":\"ai-video-studio\"},\"template\":\"templates/teams/motion-graphics\"},{\"directory\":\"workspaces/captions\",\"id\":\"captions\",\"name\":\"Captions Desk\",\"purpose\":\"Turns the transcript into short unpunctuated caption phrases, places them in free space away from the face and the overlays, and keeps every caption word traceable to the transcript.\",\"role\":\"caption-editor\",\"scaffold\":{\"kind\":\"workspace\",\"mode\":\"hive\",\"slug\":\"ai-video-studio-captions\",\"world_id\":\"ai-video-studio\"},\"template\":\"templates/teams/captions\"},{\"directory\":\"workspaces/explainers\",\"id\":\"explainers\",\"name\":\"Explainer Lab\",\"purpose\":\"Owns scripts, sources and truth: locks each script against a pinned corpus and claim ledger, records or synthesizes the take, and certifies that every cut asserts nothing new.\",\"role\":\"explainer-producer\",\"scaffold\":{\"kind\":\"workspace\",\"mode\":\"hive\",\"slug\":\"ai-video-studio-explainers\",\"world_id\":\"ai-video-studio\"},\"template\":\"templates/teams/explainers\"},{\"directory\":\"workspaces/quality\",\"id\":\"quality\",\"name\":\"Quality Control\",\"purpose\":\"Gates every cut: lint and check clean, caption trace and style, face-safe placement, hook timing, duration and loudness, claim carryover, and a contact-sheet review against the approved board.\",\"role\":\"reviewer\",\"scaffold\":{\"kind\":\"workspace\",\"mode\":\"hive\",\"slug\":\"ai-video-studio-quality\",\"world_id\":\"ai-video-studio\"},\"template\":\"templates/teams/quality\"},{\"directory\":\"workspaces/release\",\"id\":\"release\",\"name\":\"Release Desk\",\"purpose\":\"Renders the approved cut and packages the vertical MP4, caption file, cover frame, post copy and provenance. Prepares publication; never performs it.\",\"role\":\"release-manager\",\"scaffold\":{\"kind\":\"workspace\",\"mode\":\"hive\",\"slug\":\"ai-video-studio-release\",\"world_id\":\"ai-video-studio\"},\"template\":\"templates/teams/release\"},{\"directory\":\"workspaces/casework\",\"id\":\"casework\",\"name\":\"Shared delivery case\",\"purpose\":\"Own synthetic intake, shared inputs, and accepted deliverables.\",\"role\":\"case-custodian\",\"scaffold\":{\"kind\":\"workspace\",\"mode\":\"hive\",\"slug\":\"ai-video-studio-casework\",\"world_id\":\"ai-video-studio\"},\"template\":\"templates/casework\"}]}\n","path":"initialize.json","sha256":"86c65320532adaa4eea26e9f208c3b538a08dd996c3d5989610448c1afcdd7cb"},{"bytes":7968,"content":"{\"authority\":\"inert-starter-data\",\"classification\":\"public-synthetic\",\"dependencies\":{\"activation\":\"Separate owner-approved native plans are required; the seed grants no authority.\",\"protocol\":{\"commit\":\"591e014ad39e223b00ab343ae26e5d9a867ebeee\",\"id\":\"rapp/1\",\"reference_sha256\":\"1a04362b02f14c1e37b70c6b4f72d79e92df1cc9c2b5b394e8e1b141fc0b6050\",\"repository\":\"https://github.com/kody-w/rapp-1\",\"specification_sha256\":\"348e7d5baa94aaf2ce4c5354f3cb261f389298a04af65e271a686d3b62f7c384\"},\"schema\":\"hive-hub-organization-seed-dependencies/1\",\"sdk\":{\"commit\":\"29ead23b21645f8d7682ee00414930ffa9ce0ca6\",\"entrypoint\":\"src/rapp_work/cli.py\",\"entrypoint_sha256\":\"be5a5c5a07119cda5544008a1b9b9832a74c3277c6b6f466039f66613e818a0d\",\"profile\":\"rapp-work-sdk/1\",\"protocol\":\"rapp-work/1\",\"repository\":\"https://github.com/kody-w/rapp-work\"}},\"initialize\":\"initialize.json\",\"inventory\":[{\"bytes\":1079,\"path\":\"LICENSE\",\"sha256\":\"3ca4ef7120c79e55fa3c113f2c6366a072b685018d2bf736ff2bcfceba57a77f\"},{\"bytes\":4978,\"path\":\"README.md\",\"sha256\":\"cd90fbc735db852a373f6e1a00a49e1bf4601a69e4c7b69ce947ef214cdcdb18\"},{\"bytes\":5896,\"path\":\"initialize.json\",\"sha256\":\"86c65320532adaa4eea26e9f208c3b538a08dd996c3d5989610448c1afcdd7cb\"},{\"bytes\":887,\"path\":\"templates/casework/work/ACCEPTANCE.md\",\"sha256\":\"a5bcf1ec64c95455884888b4e4aa95e664a8dbe0f838e6b71b9b9f71e2960379\"},{\"bytes\":1841,\"path\":\"templates/casework/work/intake.json\",\"sha256\":\"1709b04ea25e8996f888a9b8af1766b3dd7465ef09dcc7ee794e37ce9ad741ab\"},{\"bytes\":2215,\"path\":\"templates/casework/work/starter/README.md\",\"sha256\":\"2a13810035204bbf70cf975a026a2ba0d8338f45668352cd3cbde72d9622929d\"},{\"bytes\":1265,\"path\":\"templates/casework/work/starter/board/motion-board-template.md\",\"sha256\":\"38f7031554933e2607ffaf0d2006773d6b4434906406c3c77d4b009514e0f498\"},{\"bytes\":1852,\"path\":\"templates/casework/work/starter/brand/house-kit.md\",\"sha256\":\"7cf10340c7223df1bde95d2feb1b3067c86024094ad07e95a667184c6161cf43\"},{\"bytes\":1740,\"path\":\"templates/casework/work/starter/brand/tokens.json\",\"sha256\":\"ca62849805d535facfaf60cac55a3e81630140881ca301b8224c8682e359bc27\"},{\"bytes\":1949,\"path\":\"templates/casework/work/starter/case/pilot.json\",\"sha256\":\"53602cb734cab4d50e7a00dd46ddbc085563c40ce6d374c86312629f350875a0\"},{\"bytes\":3984,\"path\":\"templates/casework/work/starter/guides/effects.md\",\"sha256\":\"f11f38aad7f8e817bc430342b66322e84bed38708aaba7bf46f0f42fb7fb33f1\"},{\"bytes\":3709,\"path\":\"templates/casework/work/starter/guides/operating-loop.md\",\"sha256\":\"360b505d9ea8b29e711e98b869646459c8aeb8a93f09e9f1b9ec8f97df1739c5\"},{\"bytes\":4484,\"path\":\"templates/casework/work/starter/guides/prompts.md\",\"sha256\":\"d38af28fe0e4ab78e85ceff32d9e0675844571d0b3fc90dce33af97bd7e37eff\"},{\"bytes\":2368,\"path\":\"templates/casework/work/starter/guides/transcription.md\",\"sha256\":\"e38a21ebbedcffed8ed66adf76c30d2373438e3d210a3b7660a3e849187afb9a\"},{\"bytes\":1007,\"path\":\"templates/casework/work/starter/ops/release-checklist.md\",\"sha256\":\"0a5c7f8b955e4a883cda04bb7727953da9d99d7a3630f39515cd288bb1531772\"},{\"bytes\":3458,\"path\":\"templates/casework/work/starter/project/hook-card.html\",\"sha256\":\"8ca677fe80fe632b237320422bd13eaa4bb629e3bda23003e30811d2b18ed836\"},{\"bytes\":5489,\"path\":\"templates/casework/work/starter/project/typing-window.html\",\"sha256\":\"8ab71565d4b00ca02eff1be6488365040835a1b5f5599f443d757df6e4c25651\"},{\"bytes\":5231,\"path\":\"templates/casework/work/starter/project/ui-panel.html\",\"sha256\":\"831e8e4e3470eca5118ab84516484ba560cbebea479a8749a60e5e35f862d39b\"},{\"bytes\":1195,\"path\":\"templates/casework/work/starter/quality/review-checklist.csv\",\"sha256\":\"df237fe104b7f8b779ebdcffa45cf1e864ebc9e18441a07baabde2c76542c40b\"},{\"bytes\":1530,\"path\":\"templates/casework/work/starter/refs/chat-window.svg\",\"sha256\":\"6776663ad12d0bf90a304e4e0ce4c581a3a199e14f8a5988bc4d64c2b43ca9c5\"},{\"bytes\":1834,\"path\":\"templates/casework/work/starter/refs/handshake-panel.svg\",\"sha256\":\"f16ec9242a4bfeb8f5211c055567ca9bbbc88daf385440373e36eec10312874d\"},{\"bytes\":8989,\"path\":\"templates/casework/work/starter/source/claims.json\",\"sha256\":\"bc3deb0566ae7cdd56f005256a76e09f61fbf2e7b87aa7c47e71d287fea9e74b\"},{\"bytes\":605,\"path\":\"templates/casework/work/starter/source/corpus.json\",\"sha256\":\"b33273c59621f14ff065005b41d518a924a40066470d8ccead49249365eeaf9e\"},{\"bytes\":8880,\"path\":\"templates/casework/work/starter/source/reference-words.json\",\"sha256\":\"8b7163df6fc7626a1accc3b402d0cf3d905329b98a98e464a9703ce61974b144\"},{\"bytes\":1330,\"path\":\"templates/casework/work/starter/source/script.md\",\"sha256\":\"cfc37a015b3fb5e63121c7b4cb722f84f30ab88ca1f8e3f6029a7cb89304b55e\"},{\"bytes\":7270,\"path\":\"templates/casework/work/starter/tools/caption-phraser.mjs\",\"sha256\":\"2ab33a01cacb636f8aef704074cebc97802080df42d0342cd594e58566e22042\"},{\"bytes\":2921,\"path\":\"templates/casework/work/starter/tools/check-kit.mjs\",\"sha256\":\"703b77601d951edad402564a88627af6ebef4b6bb30eff561d96dfb24deae215\"},{\"bytes\":12714,\"path\":\"templates/casework/work/starter/tools/transcribe.mjs\",\"sha256\":\"7e1a97bb80294b252639ef5a638465ccf9d83b10319b93951334b4332f177cda\"},{\"bytes\":10177,\"path\":\"templates/casework/work/task-board.json\",\"sha256\":\"ce6d3ad58533ae9f79e22c2e26dc46860098ea8efe34b51f4b2fbd827770b9a0\"},{\"bytes\":434,\"path\":\"templates/teams/brand/work/TEAM.md\",\"sha256\":\"205eda1d53fff7a762109b0e805d739960046b329295e1727eb54ebd05b5b95e\"},{\"bytes\":1358,\"path\":\"templates/teams/brand/work/tasks.json\",\"sha256\":\"aeb34a6beb0642c53e7eb766715a650fe4d651f970a8f4d2dbd2b4383a01a36e\"},{\"bytes\":448,\"path\":\"templates/teams/captions/work/TEAM.md\",\"sha256\":\"0c180d9ff04174cca0a7ead52796ebd4a7079e6f2497ddc7c804f69632dc1f0d\"},{\"bytes\":862,\"path\":\"templates/teams/captions/work/tasks.json\",\"sha256\":\"24af1c83ac18d528ac576f30968ee73ac1a1f23e33f4288ed978534960752180\"},{\"bytes\":445,\"path\":\"templates/teams/explainers/work/TEAM.md\",\"sha256\":\"f5c8038a93a8730ce5a02d66ceab5b9a5a844a8a20b703442bbbe8aa10722a9d\"},{\"bytes\":2115,\"path\":\"templates/teams/explainers/work/tasks.json\",\"sha256\":\"d9872507748d60bfe73d8cd6cb1f4ff9924097c82b80fad20bdbc4bd57352d19\"},{\"bytes\":467,\"path\":\"templates/teams/motion-board/work/TEAM.md\",\"sha256\":\"20fb299bbf9e7a323087b7f52ab456614a508bc8a3d3ceafe87f15da7d0c88d4\"},{\"bytes\":1031,\"path\":\"templates/teams/motion-board/work/tasks.json\",\"sha256\":\"5e83fbf5fb269c2756edef34375df4ade34b79801553c3d25ac69f390e606883\"},{\"bytes\":450,\"path\":\"templates/teams/motion-graphics/work/TEAM.md\",\"sha256\":\"beb44c482c7edb9593067a838e22c3ef7615a66f33e75eeae10c25c2283106a5\"},{\"bytes\":972,\"path\":\"templates/teams/motion-graphics/work/tasks.json\",\"sha256\":\"a7e64b44d2837b22b86053df025280f11c9e9abdecfa9af0e979cafdb9c502e9\"},{\"bytes\":454,\"path\":\"templates/teams/quality/work/TEAM.md\",\"sha256\":\"caa274edbeb3cf0cdad7180405a7a9c3de2cae695aa805e3f2d9eeb398926b95\"},{\"bytes\":870,\"path\":\"templates/teams/quality/work/tasks.json\",\"sha256\":\"913d22fcd40064228995f0f3c18b7e4addb230a95e2e171d11a2ede4cc316ad8\"},{\"bytes\":415,\"path\":\"templates/teams/release/work/TEAM.md\",\"sha256\":\"ecfe97e6c4b81aeff1fa4f9c4419ff3da62909f76f876b45c1117b6c3475cc8a\"},{\"bytes\":971,\"path\":\"templates/teams/release/work/tasks.json\",\"sha256\":\"f0239b8d6ef083e668596be4843ba2a6c22292164383abd479b623fe8f9de515\"},{\"bytes\":529,\"path\":\"templates/teams/showrunner/work/TEAM.md\",\"sha256\":\"6339d9e25c48988147d888ae42566a12e37e0f6e4edbff4e7d0aaeef8760d56f\"},{\"bytes\":1827,\"path\":\"templates/teams/showrunner/work/tasks.json\",\"sha256\":\"7d33ba52f68155ef412348a0bd5345e53970cf8305fd12a102222e1b31c5734b\"},{\"bytes\":455,\"path\":\"templates/teams/transcription/work/TEAM.md\",\"sha256\":\"2af1dee4610c14ac5091a01d4f98f309f0147c936d9c491409e7cc1baa55c96c\"},{\"bytes\":886,\"path\":\"templates/teams/transcription/work/tasks.json\",\"sha256\":\"8556e1404abc7a0e78bbd2a190ef656d7003f153f46b65f3f7861a72b124436f\"}],\"license\":\"MIT\",\"name\":\"The AI Video Studio\",\"protocol\":\"rapp-work/1\",\"related_seeds\":[\"product-launch-company\"],\"schema\":\"hive-hub-organization-seed-manifest/1\",\"slug\":\"ai-video-studio\",\"status\":\"seed-not-activated\",\"workspace_profile\":\"rapp-work-sdk/1\",\"world_id\":\"ai-video-studio\"}\n","path":"seed.json","sha256":"e63143607f4d5e8270225435e23bc5e84104fb1e10b9e9d30794790b074c4b92"},{"bytes":887,"content":"# Case acceptance\n\n- A 1080x1920 H.264 MP4 of 30 to 45 seconds renders from a clean checkout with a pinned HyperFrames CLI, and lint and check report zero errors.\n- The hook is on screen by 0.5 seconds, holds at least 2.5 seconds, and matches the approved motion board.\n- Every caption word traces to the take's words.json in order; phrases have one to four words and no punctuation, and none enters the face zone or the platform-safe areas.\n- Every on-screen technical statement maps to a verified claim whose anchor matches the pinned RFC 6455 bytes; nothing new is asserted.\n- The motion board was approved before the build started, and PROVENANCE.json records the script, claim ledger, corpus, board and approval hashes, the project commit and the CLI version.\n- No upload, publication or other external effect occurs.\n\nNo reference artifact counts as completed work without review.\n","path":"templates/casework/work/ACCEPTANCE.md","sha256":"a5bcf1ec64c95455884888b4e4aa95e664a8dbe0f838e6b71b9b9f71e2960379"},{"bytes":1841,"content":"{\"brief\":\"Produce the studio's first vertical short (1080x1920, 30 to 45 seconds) from the provided two-voice script about how a WebSocket connection starts. Record it as a talking head or synthesize it for a faceless layout. Open on the question hook, show the HTTP Upgrade request as a live-typing window and the 101 answer as an animated UI panel, caption every spoken word in short unpunctuated phrases, keep every on-screen technical statement traceable to RFC 6455 through the claim ledger, and hand over a release-ready package. Nothing is uploaded or published.\",\"classification\":\"public-synthetic\",\"id\":\"pilot-websocket-short\",\"inputs\":[\"starter/case/pilot.json\",\"starter/source/script.md\",\"starter/source/claims.json\",\"starter/source/corpus.json\",\"starter/source/reference-words.json\",\"starter/brand/house-kit.md\",\"starter/brand/tokens.json\",\"starter/board/motion-board-template.md\",\"starter/refs/handshake-panel.svg\",\"starter/refs/chat-window.svg\"],\"success_criteria\":[\"A 1080x1920 H.264 MP4 of 30 to 45 seconds renders from a clean checkout with a pinned HyperFrames CLI, and lint and check report zero errors.\",\"The hook is on screen by 0.5 seconds, holds at least 2.5 seconds, and matches the approved motion board.\",\"Every caption word traces to the take's words.json in order; phrases have one to four words and no punctuation, and none enters the face zone or the platform-safe areas.\",\"Every on-screen technical statement maps to a verified claim whose anchor matches the pinned RFC 6455 bytes; nothing new is asserted.\",\"The motion board was approved before the build started, and PROVENANCE.json records the script, claim ledger, corpus, board and approval hashes, the project commit and the CLI version.\",\"No upload, publication or other external effect occurs.\"],\"title\":\"Pilot: the WebSocket handshake in 40 seconds\"}\n","path":"templates/casework/work/intake.json","sha256":"1709b04ea25e8996f888a9b8af1766b3dd7465ef09dcc7ee794e37ce9ad741ab"},{"bytes":2215,"content":"# The AI Video Studio: pilot case starter\n\nEverything a new studio needs to run its first production: a 40-second\nvertical short that explains how a WebSocket connection starts. The script,\nthe claim ledger and the reference timings are original studio material; the\nfacts are pinned to the public text of RFC 6455.\n\n| File | What it is |\n|---|---|\n| `case/pilot.json` | deliverable spec, source options, must-show beats, gates |\n| `source/script.md` | the two-voice narration to record (or synthesize) |\n| `source/reference-words.json` | real word timings of a synthesized reference read, for pacing only |\n| `source/claims.json` | every line's claims, with RFC 6455 line anchors and snippets |\n| `source/corpus.json` | where to fetch RFC 6455 and the sha256 its bytes must match |\n| `brand/house-kit.md`, `brand/tokens.json` | the house look: type, colour, zones, captions, motion |\n| `board/motion-board-template.md` | the motion board format (plan before building) |\n| `refs/*.svg` | reference images for the typing window and the server-answer panel |\n| `guides/operating-loop.md` | how the nine teams run a production, step by step |\n| `guides/prompts.md` | the studio's prompt library, from brief to iteration |\n| `guides/effects.md` | effect catalogue with HyperFrames implementation notes |\n| `guides/transcription.md` | the words.json contract and how to produce it |\n| `project/*.html` | starter HyperFrames overlay compositions (hook card, typing window, UI panel) |\n| `tools/caption-phraser.mjs` | words.json + kit -> caption phrases, SRT, and a trace check |\n| `tools/transcribe.mjs` | word-timed transcript via ElevenLabs Scribe or local Whisper |\n| `tools/check-kit.mjs` | validates a brand kit's tokens, fonts and zones |\n| `quality/review-checklist.csv` | the QC rows the cut must pass |\n| `ops/release-checklist.md` | what the release package contains |\n\nThe task board orders the work. Claim a ready task, meet its acceptance\ncriteria, attach the artifact and the evidence. Reference files show the\nintended look and pacing; they are not completed deliverables.\n\nThe tools are inert until your own AI or operator reviews and runs them on a\nmachine with Node 18+, ffmpeg and the HyperFrames CLI.\n","path":"templates/casework/work/starter/README.md","sha256":"2a13810035204bbf70cf975a026a2ba0d8338f45668352cd3cbde72d9622929d"},{"bytes":1265,"content":"# Motion board: <production>\n\nWritten in plan mode before anything is built. The Showrunner approves this\nfile by its sha256; after approval, changes need a new approval.\n\n## Header\n\n- Production: <slug>\n- Source: <clip or episode @ commit>, windows <from-to, from-to>\n- Output: 1080x1920, 30 fps, <duration> s\n- Brand kit: <name>\n- Hook (0 to 3 s): <the line and the overlay that carries it>\n\n## Cuts\n\n| # | Source from | Source to | Timeline from | Why |\n|---|---|---|---|---|\n| 1 | 0.30 | 16.39 | 0.00 | <reason> |\n\n## Beats\n\nOne row per overlay. Times are on the output timeline. Every technical\nstatement cites a claim key, or it does not go on screen.\n\n| Beat | From | To | Zone | Effect | On-screen words | Claim | Notes |\n|---|---|---|---|---|---|---|---|\n| hook | 0.00 | 3.00 | top | hook-card | <words> | - | <placement> |\n| b1 | 3.20 | 7.80 | lower | ui-panel | <words> | F01-L02 | <reference image> |\n\nEffects come from the catalogue in guides/effects.md: hook-card, ui-panel,\ntyping-window, layer-scan, punch-in, lower-third, stat-pop, end-card.\n\n## Captions\n\n- Style: <kit caption style>\n- Default zone: caption zone. Moves: <beat -> zone, when an overlay owns the space>\n\n## Open questions for the Showrunner\n\n- <anything the board could not decide>\n","path":"templates/casework/work/starter/board/motion-board-template.md","sha256":"38f7031554933e2607ffaf0d2006773d6b4434906406c3c77d4b009514e0f498"},{"bytes":1852,"content":"# House kit (vertical frame)\n\nThe studio's default look for 1080x1920 shorts. `tokens.json` is the\nmachine-readable twin of this page and wins if the two disagree. Load it by\nname in every production so prompts never re-describe the look.\n\n## Type\n\nInter 700 for hooks and captions, Inter 400 for labels, EB Garamond 400 for\nthe small editorial kicker, JetBrains Mono for anything typed. All three are\nOFL-licensed: download them from their official sources into the production's\n`assets/fonts/` with their licence files.\n\n## Colour\n\n| Token | Hex | Use |\n|---|---|---|\n| ink | #0B0B0F | dark panels, caption shadow |\n| paper | #F7F5F0 | caption text, light windows |\n| signal | #5B4BFF | the active caption word, highlights |\n| pulse | #00D1B2 | success states (101, done), one accent per beat |\n| warn | #FF5A36 | the one thing that went wrong; never decorative |\n| muted | #9A98A6 | secondary labels |\n| panel / line | #16161D / #2A2A35 | dark card fill and hairline |\n\n## Zones (x, y, w, h in pixels)\n\n| Zone | Box | Use |\n|---|---|---|\n| face | 240, 300, 600, 700 | keep-out: nothing covers the face (or the source card) |\n| top | 60, 80, 960, 200 | hook card, kicker |\n| caption | 90, 1040, 840, 200 | captions by default |\n| lower | 60, 1250, 870, 380 | panels, typing windows, lower thirds |\n| left / right | 40 or 860, 330, 180, 550 | small badges only |\n\nPlatform-safe: no words below y 1640 and none in the right rail (x 950 and\nup, y 900 to 1640), where the short-video platforms draw their own UI.\n\n## Captions\n\nOne to four words per phrase, no punctuation, sentence case, at most two\nlines, one phrase at a time, the spoken word highlighted in signal.\n\n## Motion\n\nArrive with purpose (power3.out, 0.35 to 0.6 s), settle, hold at least 1.2 s\nper short line, leave faster (power2.in). No bounces, spins, elastic moves or\nrainbow gradients.\n","path":"templates/casework/work/starter/brand/house-kit.md","sha256":"7cf10340c7223df1bde95d2feb1b3067c86024094ad07e95a667184c6161cf43"},{"bytes":1740,"content":"{\n  \"schema\": \"studio-brand-kit/1\",\n  \"name\": \"house\",\n  \"display_name\": \"Studio house kit\",\n  \"colors\": {\n    \"ink\": \"#0B0B0F\",\n    \"paper\": \"#F7F5F0\",\n    \"signal\": \"#5B4BFF\",\n    \"pulse\": \"#00D1B2\",\n    \"warn\": \"#FF5A36\",\n    \"muted\": \"#9A98A6\",\n    \"panel\": \"#16161D\",\n    \"line\": \"#2A2A35\"\n  },\n  \"fonts\": {\n    \"display\": { \"family\": \"Inter\", \"weight\": 700, \"file\": \"fonts/Inter-700.woff2\" },\n    \"body\": { \"family\": \"Inter\", \"weight\": 400, \"file\": \"fonts/Inter-400.woff2\" },\n    \"kicker\": { \"family\": \"EB Garamond\", \"weight\": 400, \"file\": \"fonts/EBGaramond-400.woff2\" },\n    \"mono\": { \"family\": \"JetBrains Mono\", \"weight\": 400, \"file\": \"fonts/JetBrainsMono-400.woff2\" },\n    \"mono_bold\": { \"family\": \"JetBrains Mono\", \"weight\": 700, \"file\": \"fonts/JetBrainsMono-700.woff2\" }\n  },\n  \"frame\": { \"width\": 1080, \"height\": 1920, \"fps\": 30 },\n  \"zones\": {\n    \"face\": [240, 300, 600, 700],\n    \"top\": [60, 80, 960, 200],\n    \"caption\": [90, 1040, 840, 200],\n    \"lower\": [60, 1250, 870, 380],\n    \"left\": [40, 330, 180, 550],\n    \"right\": [860, 330, 180, 550]\n  },\n  \"platform_safe\": {\n    \"bottom_y\": 1640,\n    \"right_rail\": [950, 900, 130, 740],\n    \"why\": \"Short-video platforms draw their own text along the bottom and buttons down the right; keep words out of both.\"\n  },\n  \"captions\": {\n    \"max_words\": 4,\n    \"max_lines\": 2,\n    \"punctuation\": false,\n    \"case\": \"sentence\",\n    \"size_px\": 76,\n    \"weight\": 700,\n    \"color\": \"paper\",\n    \"active\": \"signal\",\n    \"shadow\": \"0 4px 18px rgba(11, 11, 15, 0.55)\"\n  },\n  \"motion\": {\n    \"enter_ease\": \"power3.out\",\n    \"exit_ease\": \"power2.in\",\n    \"enter_s\": [0.35, 0.6],\n    \"min_read_s\": 1.2,\n    \"forbidden\": [\"bounce\", \"elastic\", \"spin\", \"rainbow gradients\"]\n  },\n  \"logo\": null\n}\n","path":"templates/casework/work/starter/brand/tokens.json","sha256":"ca62849805d535facfaf60cac55a3e81630140881ca301b8224c8682e359bc27"},{"bytes":1949,"content":"{\n  \"schema\": \"studio-case/1\",\n  \"case\": \"pilot-websocket-short\",\n  \"deliverable\": {\n    \"format\": \"vertical short\",\n    \"width\": 1080,\n    \"height\": 1920,\n    \"fps\": 30,\n    \"codec\": \"h264\",\n    \"duration_s\": { \"min\": 30, \"max\": 45 },\n    \"platforms\": [\"YouTube Shorts\", \"Instagram Reels\", \"TikTok\"],\n    \"publish\": false\n  },\n  \"source\": {\n    \"script\": \"source/script.md\",\n    \"claims\": \"source/claims.json\",\n    \"corpus\": \"source/corpus.json\",\n    \"reference_timing\": \"source/reference-words.json\",\n    \"options\": [\n      {\n        \"id\": \"talking-head\",\n        \"take\": \"One or two presenters read the script to camera: vertical 1080x1920, 30 fps, eyes in the upper third, clean close-mic audio, no music under the voice.\",\n        \"layout\": \"face in the kit's face zone; overlays use the empty space around it\"\n      },\n      {\n        \"id\": \"synthesized\",\n        \"take\": \"Synthesize the two voices locally from the script with a voice whose licence allows publication, then build a faceless layout.\",\n        \"layout\": \"a framed card (waveform, title or b-roll) takes the face zone's place; overlays frame it and never cover it\"\n      }\n    ],\n    \"consent\": \"Record only people who agreed to appear, and keep their written consent with the production.\"\n  },\n  \"edit\": {\n    \"hook\": \"Open on the question: How did web pages get live updates before WebSockets?\",\n    \"target_s\": 40,\n    \"must_show\": [\n      \"the Upgrade request typed out live (GET /chat HTTP/1.1, Host, Upgrade: websocket, Connection: Upgrade, Sec-WebSocket-Key, Sec-WebSocket-Version: 13)\",\n      \"the server answer 101 Switching Protocols with Sec-WebSocket-Accept, as an animated UI panel\"\n    ],\n    \"optional_trim\": \"If the take runs long, the board may drop the 'Any other status code' line.\"\n  },\n  \"brand_kit\": \"brand/tokens.json\",\n  \"gates\": [\"board approval (Showrunner)\", \"final cut (Showrunner)\", \"release (owner only)\"],\n  \"classification\": \"public-synthetic\"\n}\n","path":"templates/casework/work/starter/case/pilot.json","sha256":"53602cb734cab4d50e7a00dd46ddbc085563c40ce6d374c86312629f350875a0"},{"bytes":3984,"content":"# Effect catalogue\n\nEach effect is one HyperFrames sub-composition (an overlay) placed in one\nkit zone for one board beat. All of them follow the same rules:\n\n- **Seek-safe**: one paused GSAP timeline registered on\n  `window.__timelines[\"<id>\"]`; state changes use `tl.set` or tweens at fixed\n  times, never callbacks, timers, `Math.random` or `Date`. A full-length\n  anchor tween (`tl.to({}, { duration: D }, 0)`) makes the timeline span the\n  beat.\n- **On brand**: colours and fonts come from the kit's CSS variables only.\n- **Offline fonts**: every family you name needs a local `@font-face` (the kit's\n  fonts in `assets/fonts/`), and fallbacks stay generic (`serif`, `sans-serif`,\n  `monospace`). The HyperFrames CLI downloads any named family that has no\n  local `@font-face` from Google Fonts at render time.\n- **Placed**: the overlay's root element carries `data-zone=\"<zone>\"` and is\n  positioned inside that zone's box. Never the face zone or the platform-safe\n  areas.\n- **Readable**: arrive in 0.35 to 0.6 s, hold at least 1.2 s per short line,\n  leave faster than you came.\n\n| Effect | Use it for | Zone | Typical length |\n|---|---|---|---|\n| hook-card | the first line: kicker plus hook | top | 2.5 to 3.5 s |\n| ui-panel | a real interface or response, rows settling in | lower | 3 to 6 s |\n| typing-window | a request, command or chat typed at speaking pace | lower | 4 to 8 s |\n| layer-scan | revealing how a design or screen is built | lower or top | 3 to 5 s |\n| punch-in | emphasis on the speaker (a transform on the video, not an overlay) | video | 0.4 to 1.2 s |\n| lower-third | who is speaking, or a source credit | lower | 2.5 to 4 s |\n| stat-pop | one number that matters | left or right | 1.5 to 2.5 s |\n| end-card | the payoff line and where to watch more | top and lower | 2 to 3 s |\n\n## hook-card\n\nKicker (editorial font, muted) fades up; the hook (display font) rises 24 px\ninto place with `power3.out`; a thin signal rule draws under it. Exit is a\n0.25 s fade. Keep the hook under eight words.\n\n## ui-panel\n\nA dark panel (ink or panel colour, hairline border, 24 to 36 px radius)\nslides up 40 px and fades in. Rows arrive at the moments they are spoken;\neach row's check mark scales from 0.6 to 1 and settles. A status pill (pulse\ncolour) lands last with a short glow. Build rows from data at the top of the\nscript so the copy is easy to change.\n\n## typing-window\n\nA window with three title-bar dots slides into empty space. Each line is a\nset of character spans; typing is a sequence of `tl.set(span, { opacity: 1 })`\ncalls at fixed times, so any frame can be rendered alone. Pace: start at the\nspoken cue, about 18 to 30 characters per second. A block cursor sits after\nthe last visible character (toggle it with `tl.set` at half-second steps).\nHighlight tokens (for example header names) by switching their class to the\nsignal colour when spoken.\n\n## layer-scan\n\nA horizontal scan line (signal, 2 px, soft glow) sweeps top to bottom. As it\ncrosses each layer, a 2 px outline box draws around that layer (animate\n`clip-path` or stroke dash offset). After the sweep, the layers separate by\n8 to 16 px in a slow, eased swipe and settle.\n\n## punch-in\n\nNot an overlay: scale the video wrapper from 1 to 1.06 to 1.12 over 0.4 to\n0.8 s with `power2.out`, hold, and ease back. Keep the face inside the face\nzone at the peak scale. Use at most once every ten seconds.\n\n## lower-third\n\nName or credit on a panel strip in the lower zone: strip wipes in from the\nleft (`clip-path: inset()`), text fades up 0.1 s later.\n\n## stat-pop\n\nOne number in display type in a gutter, counting up only if the count itself\nis true to the source; otherwise it simply pops in (scale 0.9 to 1).\n\n## end-card\n\nThe payoff line in the top zone and a short \"watch the full episode\" line in\nthe lower zone; no logos over the face. Holds to the last frame.\n\n## Board row\n\n```\n| b2 | 12.40 | 19.80 | lower | typing-window | GET /chat HTTP/1.1 ... | F02-L02 | refs/chat-window.svg |\n```\n","path":"templates/casework/work/starter/guides/effects.md","sha256":"f11f38aad7f8e817bc430342b66322e84bed38708aaba7bf46f0f42fb7fb33f1"},{"bytes":3709,"content":"# Operating loop\n\nOne front door, nine teams, one loop. The loop adapts a public creator's\nmethod for editing short-form video with an AI coding agent and HyperFrames\n(https://www.youtube.com/watch?v=z6zkeILNHL8) into an organization with\nexplicit handoffs and gates. This guide is the studio's own restatement.\n\n## The loop\n\n| # | Step | Team | Output |\n|---|---|---|---|\n| 1 | Brief | Showrunner | `BRIEF.md`: audience, platform, length, hook, source, kit, out of scope |\n| 2 | Script lock | Explainer Lab | every factual line mapped to a verified claim; script sha256 recorded |\n| 3 | Take | Explainer Lab | the recorded or synthesized source, with hash and capture notes |\n| 4 | Transcript | Transcription Desk | `words.json`: word-level timestamps on the source timeline |\n| 5 | Motion board | Motion Board Room | timestamped plan: hook, cuts, overlay beats with zone, effect, words, claim |\n| 6 | Board approval | owner (presented by the Showrunner's Office) | approval record with the board's sha256; nothing is built before it |\n| 7 | Captions | Captions Desk | short unpunctuated phrases, placed away from the face and overlays |\n| 8 | Build | Motion Graphics Shop | HyperFrames project: cuts, overlays, captions; lint and check clean |\n| 9 | Claim carryover | Explainer Lab | every on-screen technical statement mapped to a verified claim |\n| 10 | QC | Quality Control | report plus a contact sheet reviewed against the board |\n| 11 | Final approval | owner (presented by the Showrunner's Office) | approval record with the project commit |\n| 12 | Release package | Release Desk | MP4, SRT, cover, post copy, provenance; never published by the org |\n| 13 | Kit retro | Brand Studio | kit changes proposed from what QC found |\n\n## Principles\n\n- **Plan before building.** The motion board is written in plan mode and\n  approved before any composition exists. Iteration after the build is short\n  and specific (\"move the panel up 40 px\", \"type at speaking pace\").\n- **Brand by name.** A kit (type, colour, zones, caption style, motion) is\n  loaded by name, so no prompt re-describes the look.\n- **Placement is deliberate.** Never cover the face, stay clear of the frame\n  edges, use empty space. One overlay per zone at a time; captions move when\n  an overlay needs their zone. Each overlay is tied to the spoken words that\n  trigger it.\n- **Captions are short.** One to four words, no punctuation, the spoken word\n  highlighted.\n- **Effects start from a reference.** A screenshot or sketch of the target\n  look goes with every effect request.\n- **Voice first.** The owner dictates every request to their own AI, which\n  confirms the plan in two lines and does the work. The Showrunner's Office\n  is a stage behind that AI, not a separate bot.\n- **Facts are pinned.** Technical statements come from a pinned corpus and a\n  claim ledger; a derived cut may simplify wording, never add facts.\n- **Owner authority.** Uploading, publishing, spending and sharing media\n  outside the studio belong to the owner, never to the org.\n\n## Project layout (one folder per production)\n\n```\nproductions/<slug>/\n  BRIEF.md  motion-board.md  approvals/\n  raw/                 source media (not in git)\n  transcript/          words.json, transcript.txt, meta.json\n  brand/               tokens.json, design.md for this production\n  compositions/        overlays/*.html, captions.html\n  index.html           root HyperFrames composition (1080x1920)\n  renders/ snapshots/  outputs (not in git)\n```\n\n## Tools you need\n\nNode 18 or newer, ffmpeg and ffprobe, and the HyperFrames CLI\n(`npx hyperframes`, pin one version for the whole studio). Optional: an\nElevenLabs API key for transcription, kept in the production's `.env`.\n","path":"templates/casework/work/starter/guides/operating-loop.md","sha256":"360b505d9ea8b29e711e98b869646459c8aeb8a93f09e9f1b9ec8f97df1739c5"},{"bytes":4484,"content":"# Prompt library\n\nThe studio's own prompts, written to be spoken as well as typed. Replace the\nangle brackets. Every prompt assumes the production folder is open and the\nkit is loaded by name.\n\n## What makes an effect prompt work\n\n- **Anchor it to the words.** Quote the spoken cue that starts the effect\n  (\"when the speaker says 'asks to upgrade'\") instead of guessing seconds;\n  the transcript turns the quote into exact timing.\n- **Name the reference.** Point at the reference image file for the look.\n- **Say how it moves.** Two or three motion words (slides in, settles, types\n  at speaking pace) beat a paragraph of adjectives.\n- **Fence the frame.** Never the face, clear of the frame edges and the\n  platform UI, one overlay per zone.\n- **Nothing extra.** Say what must not be added or changed.\n\n## 1. Intake (to your AI)\n\n> New short. Source is <clip path, or episode and window>. Audience is\n> <who>, platform is <where>, about <n> seconds. The hook is <line or idea>.\n> Use the <name> kit. Do not publish anything. Write the brief and tell me the\n> plan in two lines.\n\n## 2. Base creative direction (applies to every short)\n\n> Add motion graphics, text overlays and captions to this clip. Keep it\n> modern, clean and editorial. Captions are short phrases with no\n> punctuation, one to four words. Never cover the speaker's face; place every\n> element in empty space on purpose and say where it goes. One idea per\n> overlay. Use only the kit's colours, fonts and zones.\n\n## 3. Motion board (plan mode: write, do not build)\n\n> Plan mode. Read transcript/words.json with its timestamps and the brief.\n> Write motion-board.md: the hook in the first three seconds, which lines to\n> cut (start and end in silence), then one row per overlay with output start\n> and end, zone, effect from guides/effects.md, the exact on-screen words, the\n> claim key for anything technical, and the reference image it follows. Say\n> where captions move while an overlay owns their zone. End with open\n> questions. Do not create or edit any composition yet.\n\nBefore approving, ask for more detail on any beat that is vague (exact words,\nexact cue, exact zone); approve only a board you could build from.\n\n## 4. Effects (attach the reference image every time)\n\n- **Animated UI panel**: \"Recreate the attached panel as an overlay in the\n  lower zone. Rows arrive one by one as they are spoken, each check mark\n  settles with a short ease, and the status pill lands last. Hold it until\n  <time>.\"\n- **Live-typing window**: \"Put a window like the attached one in the empty\n  space of the lower zone. Type these lines at speaking pace, starting when\n  the speaker says '<word>': <lines>. Blinking cursor while typing; highlight\n  <tokens> in the signal colour as they are spoken.\"\n- **Layer scan**: \"Take the attached design and reveal it as layers: a scan\n  line sweeps down, a thin box draws around each layer as it is found, then\n  the layers separate slightly in a slow swipe. About <n> seconds.\"\n- **Hook card**: \"Top zone, first three seconds: a kicker line in the\n  editorial font, then the hook in display type, arriving with purpose and\n  holding long enough to read.\"\n- **Punch-in**: \"At <time>, push in to <scale> over <duration> on the speaker\n  for emphasis, then settle back. Keep the face centred in the face zone.\"\n\n## 5. Captions\n\n> Build captions from words.json with the kit's caption rules. Keep every\n> word, in order, no punctuation, one to four words per phrase, highlight the\n> spoken word. Move captions to <zone> while <beat> is on screen.\n\n## 6. Iteration (short and specific)\n\n- \"Move the panel up 40 pixels; it crowds the captions.\"\n- \"The typing is ahead of the voice; start it at <time> and slow it by 20 %.\"\n- \"Hold the 101 row half a second longer before it leaves.\"\n- \"The hook is hard to read on this frame; add the kit's ink panel behind it.\"\n- \"Swap the kicker to: <words>. Nothing else changes.\"\n\n## 7. Review\n\n> Run the QC checklist. For each row give pass or fail with the evidence.\n> Snapshot every board beat, tile them into a contact sheet, and compare each\n> frame with the approved board: words, zone, face clear, fonts loaded,\n> nothing cut off, platform-safe areas clear.\n\n## Speaking prompts\n\nDictation works well for all of the above. Say the zone and the time\nexplicitly (\"lower zone, from twelve point five seconds\"), name effects by\ntheir catalogue names, and end with what must not change. Your AI reads\nthe request back in two lines before acting.\n","path":"templates/casework/work/starter/guides/prompts.md","sha256":"d38af28fe0e4ab78e85ceff32d9e0675844571d0b3fc90dce33af97bd7e37eff"},{"bytes":2368,"content":"# Transcription\n\nCaptions, cuts and board timings all read one file, so it has to be right\nbefore anyone plans on it.\n\n## Contract: `transcript/words.json`\n\n```json\n{\n  \"schema\": \"studio-words/1\",\n  \"engine\": \"scribe\",\n  \"model\": \"scribe_v2\",\n  \"source\": \"raw/take.mp4\",\n  \"window\": null,\n  \"language\": \"en\",\n  \"words\": [\n    { \"text\": \"How\", \"start\": 0.56, \"end\": 0.68, \"speaker\": \"S1\" }\n  ]\n}\n```\n\n- Times are seconds on the **source** timeline. The build maps them through\n  the cuts; never shift them to the output timeline by hand.\n- Words keep their spoken spelling and punctuation; captions strip\n  punctuation later.\n- `transcript.txt` (speaker turns with timestamps) and `meta.json` (engine,\n  model, settings, media sha256) sit beside it.\n\n## Producing it: `tools/transcribe.mjs`\n\n```bash\nnode tools/transcribe.mjs raw/take.mp4                         # Scribe if a key is set, else local Whisper\nnode tools/transcribe.mjs raw/take.mp4 --terms \"WebSocket,Sec-WebSocket-Key,RFC 6455\"\nnode tools/transcribe.mjs raw/take.mp4 --known source/script.md  # read verbatim: script words win\n```\n\n- **ElevenLabs Scribe** (default when `ELEVENLABS_API_KEY` is in the\n  environment or the production's `.env`): word timestamps and speaker\n  labels from `POST /v1/speech-to-text` with model `scribe_v2`. The key is\n  read, never printed or written.\n- **Local** (fallback): the HyperFrames CLI's `transcribe` command with\n  Whisper `small.en` (or Parakeet when installed). Offline once the model is\n  cached; one speaker label.\n- `--terms` fixes names and product words (case, spaces and hyphens are\n  ignored when matching, so \"web socket\" becomes \"WebSocket\").\n- `--known` reconciles against the script: known words win, recognition gives\n  timing, and the agreement figure is reported. Under 95 % means the take\n  drifted from the script: keep the recognized words and flag it.\n\n## Desk checklist\n\n1. Transcribe the take; fix terms.\n2. Reconcile with the script when one exists.\n3. Read `transcript.txt` end to end; fix remaining text errors in\n   `words.json` (never reorder words or move times backwards).\n4. Compare pacing with `source/reference-words.json` if the board needs a\n   target length; trims belong to the board, not the transcript.\n5. Hand off the path, engine, model and agreement figure.\n\nNever ask anyone to paste a key into chat. No key means local.\n","path":"templates/casework/work/starter/guides/transcription.md","sha256":"e38a21ebbedcffed8ed66adf76c30d2373438e3d210a3b7660a3e849187afb9a"},{"bytes":1007,"content":"# Release checklist\n\nThe Release Desk prepares; the owner publishes. Nothing in this list uploads,\nposts or schedules anything.\n\n1. Render the approved project commit from a clean checkout with the pinned\n   HyperFrames CLI. Record the CLI version.\n2. Confirm the master with `ffprobe`: 1080x1920, H.264, AAC, 30 fps, duration\n   inside the case limits.\n3. Export `captions.srt` with `node tools/caption-phraser.mjs ... --srt`. The\n   SRT keeps the punctuation-free phrases exactly as shown on screen.\n4. Export `cover.png` from the approved cover beat (a HyperFrames snapshot).\n5. Draft `post.md`: title under 60 characters, a two-line caption, three to\n   five hashtags. Claims in the copy follow the same carryover rule as\n   on-screen text.\n6. Write `PROVENANCE.json`: script and claim ledger sha256, corpus sha256,\n   board and approval sha256, project commit, CLI version, and bytes plus\n   sha256 for every file in the package.\n7. Hand the package to the Showrunner for the owner's release decision.\n","path":"templates/casework/work/starter/ops/release-checklist.md","sha256":"0a5c7f8b955e4a883cda04bb7727953da9d99d7a3630f39515cd288bb1531772"},{"bytes":3458,"content":"<!--\n  hook-card: the first line of a short. A kicker fades up, the hook rises into place, a thin rule draws under it.\n  HyperFrames sub-composition. Host it from your root index.html, for example:\n    <div id=\"el-hook\" class=\"scene\" data-composition-id=\"hook-card\" data-composition-src=\"compositions/overlays/hook-card.html\"\n         data-start=\"0\" data-duration=\"3\" data-track-index=\"30\"></div>\n  Edit CONFIG below (keep CONFIG.duration equal to the host's data-duration). Colours, fonts and the zone box come\n  from your kit's CSS variables when your project defines them, with the house kit as fallback.\n  Seek-safe: one paused GSAP timeline, fixed times, no callbacks. Expects GSAP at assets/vendor/gsap.min.js.\n  To use a second copy in one project, change the composition id in both places (template attribute and timeline key).\n-->\n<template id=\"hook-card-template\">\n  <script src=\"assets/vendor/gsap.min.js\"></script>\n  <style>\n    #hook-card-root {\n      position: absolute;\n      inset: 0;\n      pointer-events: none;\n    }\n    #hook-card-root .box {\n      position: absolute;\n      left: var(--zone-top-x, 60px);\n      top: var(--zone-top-y, 80px);\n      width: var(--zone-top-w, 960px);\n      height: var(--zone-top-h, 200px);\n      display: flex;\n      flex-direction: column;\n      justify-content: center;\n      gap: 8px;\n      pointer-events: none;\n    }\n    #hook-card-root .kicker {\n      font-family: var(--font-kicker, \"EB Garamond\", serif);\n      font-size: 36px;\n      color: var(--muted, #9a98a6);\n      opacity: 0;\n    }\n    #hook-card-root .hook {\n      font-family: var(--font-display, \"Inter\", system-ui, sans-serif);\n      font-weight: 700;\n      font-size: 62px;\n      line-height: 1.05;\n      letter-spacing: -0.02em;\n      color: var(--paper, #f7f5f0);\n      text-shadow: 0 4px 18px rgba(11, 11, 15, 0.55);\n      opacity: 0;\n    }\n    #hook-card-root .rule {\n      flex: none;\n      width: 180px;\n      height: 6px;\n      border-radius: 3px;\n      background: var(--signal, #5b4bff);\n      transform-origin: left center;\n      transform: scaleX(0);\n    }\n  </style>\n  <div id=\"hook-card-root\" data-composition-id=\"hook-card\" data-width=\"1080\" data-height=\"1920\">\n    <div class=\"box\" data-zone=\"top\">\n    <div class=\"kicker\" id=\"hook-card-kicker\"></div>\n    <div class=\"hook\" id=\"hook-card-hook\"></div>\n    <div class=\"rule\" id=\"hook-card-rule\"></div>\n  </div>\n  </div>\n  <script>\n    (function () {\n      var CONFIG = {\n        duration: 3.0,\n        kicker: \"Before WebSockets\",\n        hook: \"How did pages get live updates?\",\n        exitAt: 2.7\n      };\n      document.getElementById(\"hook-card-kicker\").textContent = CONFIG.kicker;\n      document.getElementById(\"hook-card-hook\").textContent = CONFIG.hook;\n      var tl = gsap.timeline({ paused: true });\n      tl.fromTo(\"#hook-card-kicker\", { opacity: 0, y: 10 }, { opacity: 1, y: 0, duration: 0.35, ease: \"power3.out\" }, 0.05);\n      tl.fromTo(\"#hook-card-hook\", { opacity: 0, y: 24 }, { opacity: 1, y: 0, duration: 0.5, ease: \"power3.out\" }, 0.15);\n      tl.fromTo(\"#hook-card-rule\", { scaleX: 0 }, { scaleX: 1, duration: 0.4, ease: \"power3.out\" }, 0.5);\n      tl.to(\"#hook-card-root .box\", { opacity: 0, duration: CONFIG.duration - CONFIG.exitAt, ease: \"power2.in\" }, CONFIG.exitAt);\n      tl.to({}, { duration: CONFIG.duration }, 0);\n      window.__timelines = window.__timelines || {};\n      window.__timelines[\"hook-card\"] = tl;\n    })();\n  </script>\n</template>\n","path":"templates/casework/work/starter/project/hook-card.html","sha256":"8ca677fe80fe632b237320422bd13eaa4bb629e3bda23003e30811d2b18ed836"},{"bytes":5489,"content":"<!--\n  typing-window: a request, command or chat typed out at speaking pace in a window placed in empty space.\n  HyperFrames sub-composition. Host it from your root index.html, for example:\n    <div id=\"el-typing\" class=\"scene\" data-composition-id=\"typing-window\" data-composition-src=\"compositions/overlays/typing-window.html\"\n         data-start=\"9.2\" data-duration=\"8\" data-track-index=\"31\"></div>\n  Edit CONFIG below. Times are seconds from this overlay's own start; take them from the transcript so each line starts\n  when it is spoken (CONFIG.lines[i].at). Keep CONFIG.duration equal to the host's data-duration.\n  Seek-safe: every character is revealed by a timeline set at a fixed time, so any frame renders on its own.\n  Expects GSAP at assets/vendor/gsap.min.js. For a second copy, change the composition id in both places.\n-->\n<template id=\"typing-window-template\">\n  <script src=\"assets/vendor/gsap.min.js\"></script>\n  <style>\n    #typing-window-root {\n      position: absolute;\n      inset: 0;\n      pointer-events: none;\n    }\n    #typing-window-root .box {\n      position: absolute;\n      left: var(--zone-lower-x, 60px);\n      top: var(--zone-lower-y, 1250px);\n      width: var(--zone-lower-w, 870px);\n      height: var(--zone-lower-h, 380px);\n      box-sizing: border-box;\n      border-radius: 28px;\n      background: var(--paper, #f7f5f0);\n      border: 3px solid #d9d5cc;\n      box-shadow: 0 18px 50px rgba(11, 11, 15, 0.35);\n      overflow: hidden;\n      opacity: 0;\n      pointer-events: none;\n    }\n    #typing-window-root .bar {\n      height: 56px;\n      display: flex;\n      align-items: center;\n      gap: 12px;\n      padding: 0 22px;\n      border-bottom: 2px solid #d9d5cc;\n    }\n    #typing-window-root .dot { width: 16px; height: 16px; border-radius: 50%; }\n    #typing-window-root .title {\n      margin-left: auto;\n      margin-right: auto;\n      font-family: var(--font-body, \"Inter\", system-ui, sans-serif);\n      font-weight: 700;\n      font-size: 20px;\n      color: #5e5b66;\n    }\n    #typing-window-root .code {\n      padding: 18px 26px;\n      font-family: var(--font-mono, \"JetBrains Mono\", ui-monospace, monospace);\n      font-size: 25px;\n      line-height: 1.45;\n      color: var(--ink, #0b0b0f);\n      white-space: pre;\n    }\n    #typing-window-root .line.hl { color: var(--signal, #5b4bff); font-weight: 700; }\n    #typing-window-root .ch { position: relative; opacity: 0; }\n    #typing-window-root .cur {\n      position: absolute;\n      left: 100%;\n      top: 50%;\n      width: 13px;\n      height: 28px;\n      margin: -15px 0 0 1px;\n      background: var(--signal, #5b4bff);\n      opacity: 0;\n    }\n  </style>\n  <div id=\"typing-window-root\" data-composition-id=\"typing-window\" data-width=\"1080\" data-height=\"1920\">\n    <div class=\"box\" data-zone=\"lower\">\n    <div class=\"bar\">\n      <span class=\"dot\" style=\"background: var(--warn, #ff5a36)\"></span>\n      <span class=\"dot\" style=\"background: #e8b931\"></span>\n      <span class=\"dot\" style=\"background: var(--pulse, #00d1b2)\"></span>\n      <span class=\"title\" id=\"typing-window-title\"></span>\n    </div>\n    <div class=\"code\" id=\"typing-window-code\"></div>\n  </div>\n  </div>\n  <script>\n    (function () {\n      var CONFIG = {\n        duration: 8.0,\n        title: \"client \\u2192 server\",\n        charsPerSecond: 26,\n        lines: [\n          { text: \"GET /chat HTTP/1.1\", at: 0.5 },\n          { text: \"Host: server.example.com\", at: 1.3 },\n          { text: \"Upgrade: websocket\", at: 2.3, highlightAt: 2.9 },\n          { text: \"Connection: Upgrade\", at: 3.2, highlightAt: 3.8 },\n          { text: \"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\", at: 4.3 },\n          { text: \"Sec-WebSocket-Version: 13\", at: 6.0 }\n        ],\n        exitAt: 7.7\n      };\n      document.getElementById(\"typing-window-title\").textContent = CONFIG.title;\n      var code = document.getElementById(\"typing-window-code\");\n      var tl = gsap.timeline({ paused: true });\n      tl.fromTo(\"#typing-window-root .box\", { opacity: 0, y: 40 }, { opacity: 1, y: 0, duration: 0.45, ease: \"power3.out\" }, 0);\n      var lastCursor = null;\n      var lastTime = 0;\n      CONFIG.lines.forEach(function (line, li) {\n        var row = document.createElement(\"div\");\n        row.className = \"line\";\n        row.id = \"typing-window-line-\" + li;\n        code.appendChild(row);\n        for (var ci = 0; ci < line.text.length; ci++) {\n          var ch = document.createElement(\"span\");\n          ch.className = \"ch\";\n          ch.textContent = line.text[ci];\n          row.appendChild(ch);\n          var cur = document.createElement(\"span\");\n          cur.className = \"cur\";\n          ch.appendChild(cur);\n          var t = line.at + ci / CONFIG.charsPerSecond;\n          tl.set(ch, { opacity: 1 }, t);\n          tl.set(cur, { opacity: 1 }, t);\n          if (lastCursor) tl.set(lastCursor, { opacity: 0 }, t);\n          lastCursor = cur;\n          lastTime = t;\n        }\n        if (line.highlightAt !== undefined) tl.set(row, { className: \"line hl\" }, line.highlightAt);\n      });\n      for (var b = lastTime + 0.5, on = false; b < CONFIG.exitAt; b += 0.5, on = !on) tl.set(lastCursor, { opacity: on ? 1 : 0 }, b);\n      tl.to(\"#typing-window-root .box\", { opacity: 0, y: 20, duration: CONFIG.duration - CONFIG.exitAt, ease: \"power2.in\" }, CONFIG.exitAt);\n      tl.to({}, { duration: CONFIG.duration }, 0);\n      window.__timelines = window.__timelines || {};\n      window.__timelines[\"typing-window\"] = tl;\n    })();\n  </script>\n</template>\n","path":"templates/casework/work/starter/project/typing-window.html","sha256":"8ab71565d4b00ca02eff1be6488365040835a1b5f5599f443d757df6e4c25651"},{"bytes":5231,"content":"<!--\n  ui-panel: a real interface or response recreated as a dark panel; rows arrive as they are spoken and their check marks\n  settle, then the status pill lands.\n  HyperFrames sub-composition. Host it from your root index.html, for example:\n    <div id=\"el-panel\" class=\"scene\" data-composition-id=\"ui-panel\" data-composition-src=\"compositions/overlays/ui-panel.html\"\n         data-start=\"18.4\" data-duration=\"6\" data-track-index=\"32\"></div>\n  Edit CONFIG below. Times are seconds from this overlay's own start; take them from the transcript.\n  Keep CONFIG.duration equal to the host's data-duration. Seek-safe: fixed-time tweens only.\n  Expects GSAP at assets/vendor/gsap.min.js. For a second copy, change the composition id in both places.\n-->\n<template id=\"ui-panel-template\">\n  <script src=\"assets/vendor/gsap.min.js\"></script>\n  <style>\n    #ui-panel-root {\n      position: absolute;\n      inset: 0;\n      pointer-events: none;\n    }\n    #ui-panel-root .box {\n      position: absolute;\n      left: var(--zone-lower-x, 60px);\n      top: var(--zone-lower-y, 1250px);\n      width: var(--zone-lower-w, 870px);\n      height: var(--zone-lower-h, 380px);\n      box-sizing: border-box;\n      padding: 28px 30px;\n      border-radius: 32px;\n      background: var(--ink, #0b0b0f);\n      border: 3px solid var(--line, #2a2a35);\n      box-shadow: 0 18px 50px rgba(0, 0, 0, 0.45);\n      opacity: 0;\n      pointer-events: none;\n    }\n    #ui-panel-root .head { display: flex; align-items: center; margin-bottom: 18px; }\n    #ui-panel-root .name {\n      font-family: var(--font-display, \"Inter\", system-ui, sans-serif);\n      font-weight: 700;\n      font-size: 30px;\n      color: var(--paper, #f7f5f0);\n    }\n    #ui-panel-root .pill {\n      margin-left: auto;\n      padding: 12px 24px;\n      border-radius: 999px;\n      background: var(--pulse, #00d1b2);\n      color: var(--ink, #0b0b0f);\n      font-family: var(--font-display, \"Inter\", system-ui, sans-serif);\n      font-weight: 700;\n      font-size: 24px;\n      opacity: 0;\n    }\n    #ui-panel-root .row {\n      display: flex;\n      align-items: center;\n      gap: 18px;\n      height: 64px;\n      margin-top: 12px;\n      padding: 0 20px;\n      border-radius: 16px;\n      background: var(--panel, #16161d);\n      font-family: var(--font-mono, \"JetBrains Mono\", ui-monospace, monospace);\n      font-size: 23px;\n      color: var(--paper, #f7f5f0);\n      white-space: nowrap;\n      opacity: 0;\n    }\n    #ui-panel-root .check {\n      flex: none;\n      width: 30px;\n      height: 30px;\n      border-radius: 50%;\n      background: var(--signal, #5b4bff);\n      display: grid;\n      place-items: center;\n      transform: scale(0.6);\n    }\n  </style>\n  <div id=\"ui-panel-root\" data-composition-id=\"ui-panel\" data-width=\"1080\" data-height=\"1920\">\n    <div class=\"box\" data-zone=\"lower\">\n    <div class=\"head\"><span class=\"name\" id=\"ui-panel-name\"></span><span class=\"pill\" id=\"ui-panel-pill\"></span></div>\n    <div id=\"ui-panel-rows\"></div>\n  </div>\n  </div>\n  <script>\n    (function () {\n      var CONFIG = {\n        duration: 6.0,\n        name: \"Server\",\n        status: \"101 Switching Protocols\",\n        statusAt: 0.6,\n        rows: [\n          { text: \"Upgrade: websocket\", at: 1.2 },\n          { text: \"Connection: Upgrade\", at: 1.8 },\n          { text: \"Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\", at: 2.6 }\n        ],\n        exitAt: 5.7\n      };\n      document.getElementById(\"ui-panel-name\").textContent = CONFIG.name;\n      document.getElementById(\"ui-panel-pill\").textContent = CONFIG.status;\n      var list = document.getElementById(\"ui-panel-rows\");\n      var tl = gsap.timeline({ paused: true });\n      tl.fromTo(\"#ui-panel-root .box\", { opacity: 0, y: 40 }, { opacity: 1, y: 0, duration: 0.5, ease: \"power3.out\" }, 0);\n      CONFIG.rows.forEach(function (r, i) {\n        var row = document.createElement(\"div\");\n        row.className = \"row\";\n        var check = document.createElement(\"span\");\n        check.className = \"check\";\n        check.innerHTML = '<svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"><path d=\"M3 8.5l3 3 7-7\" stroke=\"#f7f5f0\" stroke-width=\"2.5\" fill=\"none\"/></svg>';\n        var label = document.createElement(\"span\");\n        label.textContent = r.text;\n        row.appendChild(check);\n        row.appendChild(label);\n        list.appendChild(row);\n        tl.fromTo(row, { opacity: 0, y: 12 }, { opacity: 1, y: 0, duration: 0.35, ease: \"power3.out\" }, r.at);\n        tl.fromTo(check, { scale: 0.6 }, { scale: 1, duration: 0.3, ease: \"power3.out\" }, r.at + 0.15);\n      });\n      tl.set(\"#ui-panel-pill\", { opacity: 0, scale: 0.9, boxShadow: \"0 0 0 0 rgba(0, 209, 178, 0.55)\" }, 0);\n      tl.to(\"#ui-panel-pill\", { opacity: 1, scale: 1, duration: 0.35, ease: \"power3.out\" }, CONFIG.statusAt);\n      tl.to(\"#ui-panel-pill\", { boxShadow: \"0 0 0 16px rgba(0, 209, 178, 0)\", duration: 0.7, ease: \"power2.out\" }, CONFIG.statusAt + 0.2);\n      tl.to(\"#ui-panel-root .box\", { opacity: 0, y: 20, duration: CONFIG.duration - CONFIG.exitAt, ease: \"power2.in\" }, CONFIG.exitAt);\n      tl.to({}, { duration: CONFIG.duration }, 0);\n      window.__timelines = window.__timelines || {};\n      window.__timelines[\"ui-panel\"] = tl;\n    })();\n  </script>\n</template>\n","path":"templates/casework/work/starter/project/ui-panel.html","sha256":"831e8e4e3470eca5118ab84516484ba560cbebea479a8749a60e5e35f862d39b"},{"bytes":1195,"content":"id,gate,check,how,pass_when\nQ01,build,HyperFrames lint,npx hyperframes lint,0 errors\nQ02,build,HyperFrames check,npx hyperframes check,0 errors\nQ03,timing,Duration,ffprobe on the render,30 to 45 s for this case\nQ04,timing,Hook on screen early,the hook beat in the approved board,starts at or before 0.5 s and holds at least 2.5 s\nQ05,captions,Caption trace,node tools/caption-phraser.mjs <words> <tokens> --verify <captions.json>,every caption word appears in words.json in order\nQ06,captions,Caption style,same command,1 to 4 words per phrase and no punctuation\nQ07,placement,Zones,every overlay's data-zone against brand/tokens.json,no overlay or caption in the face zone or the platform-safe areas\nQ08,claims,Claim carryover,deliverables/claims/carryover.json,0 unmapped on-screen statements\nQ09,audio,Loudness,ffmpeg -af ebur128=peak=true on the render,integrated -16 to -12 LUFS and true peak at or below -1 dBTP\nQ10,review,Contact sheet,one snapshot per board beat tiled into one image,every beat matches the approved board\nQ11,review,Board match,side by side with the approved motion board,no unapproved beats\nQ12,release,No external effects,release log,no upload or publication happened\n","path":"templates/casework/work/starter/quality/review-checklist.csv","sha256":"df237fe104b7f8b779ebdcffa45cf1e864ebc9e18441a07baabde2c76542c40b"},{"bytes":1530,"content":"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"960\" height=\"520\" viewBox=\"0 0 960 520\">\n  <title>Reference: live-typing request window (typing-window effect)</title>\n  <desc>The window slides into empty space, then each line types out at speaking pace with a blinking cursor; the Upgrade lines take the signal colour as they are spoken. Brand: studio house kit.</desc>\n  <rect x=\"0\" y=\"0\" width=\"960\" height=\"520\" rx=\"32\" fill=\"#F7F5F0\"/>\n  <rect x=\"1.5\" y=\"1.5\" width=\"957\" height=\"517\" rx=\"30.5\" fill=\"none\" stroke=\"#D9D5CC\" stroke-width=\"3\"/>\n  <circle cx=\"44\" cy=\"44\" r=\"10\" fill=\"#FF5A36\"/><circle cx=\"76\" cy=\"44\" r=\"10\" fill=\"#E8B931\"/><circle cx=\"108\" cy=\"44\" r=\"10\" fill=\"#00D1B2\"/>\n  <text x=\"480\" y=\"52\" text-anchor=\"middle\" font-family=\"Inter, sans-serif\" font-size=\"22\" font-weight=\"700\" fill=\"#5E5B66\">client → server</text>\n  <line x1=\"0\" y1=\"84\" x2=\"960\" y2=\"84\" stroke=\"#D9D5CC\" stroke-width=\"2\"/>\n  <g font-family=\"JetBrains Mono, monospace\" font-size=\"28\" fill=\"#0B0B0F\">\n    <text x=\"44\" y=\"140\">GET /chat HTTP/1.1</text>\n    <text x=\"44\" y=\"190\">Host: server.example.com</text>\n    <text x=\"44\" y=\"240\" fill=\"#5B4BFF\" font-weight=\"700\">Upgrade: websocket</text>\n    <text x=\"44\" y=\"290\" fill=\"#5B4BFF\" font-weight=\"700\">Connection: Upgrade</text>\n    <text x=\"44\" y=\"340\">Sec-WebSocket-Key:</text>\n    <text x=\"44\" y=\"384\" font-size=\"26\">  dGhlIHNhbXBsZSBub25jZQ==</text>\n    <text x=\"44\" y=\"434\">Sec-WebSocket-Version: 13</text>\n  </g>\n  <rect x=\"480\" y=\"410\" width=\"16\" height=\"32\" fill=\"#5B4BFF\"/>\n</svg>\n","path":"templates/casework/work/starter/refs/chat-window.svg","sha256":"6776663ad12d0bf90a304e4e0ce4c581a3a199e14f8a5988bc4d64c2b43ca9c5"},{"bytes":1834,"content":"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"960\" height=\"420\" viewBox=\"0 0 960 420\">\n  <title>Reference: server answer panel (ui-panel effect)</title>\n  <desc>Rows arrive one by one and their check marks settle; the status pill lands last with a short glow. Brand: studio house kit.</desc>\n  <rect x=\"0\" y=\"0\" width=\"960\" height=\"420\" rx=\"36\" fill=\"#0B0B0F\"/>\n  <rect x=\"1.5\" y=\"1.5\" width=\"957\" height=\"417\" rx=\"34.5\" fill=\"none\" stroke=\"#2A2A35\" stroke-width=\"3\"/>\n  <text x=\"48\" y=\"78\" font-family=\"Inter, sans-serif\" font-size=\"30\" font-weight=\"700\" fill=\"#F7F5F0\">Server</text>\n  <text x=\"48\" y=\"112\" font-family=\"Inter, sans-serif\" font-size=\"22\" fill=\"#9A98A6\">answers the upgrade</text>\n  <rect x=\"560\" y=\"44\" width=\"352\" height=\"64\" rx=\"32\" fill=\"#00D1B2\"/>\n  <text x=\"736\" y=\"86\" text-anchor=\"middle\" font-family=\"Inter, sans-serif\" font-size=\"26\" font-weight=\"700\" fill=\"#0B0B0F\">101 Switching Protocols</text>\n  <g font-family=\"JetBrains Mono, monospace\" font-size=\"26\" fill=\"#F7F5F0\">\n    <rect x=\"48\" y=\"150\" width=\"864\" height=\"64\" rx=\"16\" fill=\"#16161D\"/>\n    <circle cx=\"86\" cy=\"182\" r=\"14\" fill=\"#5B4BFF\"/><path d=\"M79 182 l5 6 l10 -12\" stroke=\"#F7F5F0\" stroke-width=\"4\" fill=\"none\"/>\n    <text x=\"116\" y=\"191\">Upgrade: websocket</text>\n    <rect x=\"48\" y=\"226\" width=\"864\" height=\"64\" rx=\"16\" fill=\"#16161D\"/>\n    <circle cx=\"86\" cy=\"258\" r=\"14\" fill=\"#5B4BFF\"/><path d=\"M79 258 l5 6 l10 -12\" stroke=\"#F7F5F0\" stroke-width=\"4\" fill=\"none\"/>\n    <text x=\"116\" y=\"267\">Connection: Upgrade</text>\n    <rect x=\"48\" y=\"302\" width=\"864\" height=\"64\" rx=\"16\" fill=\"#16161D\"/>\n    <circle cx=\"86\" cy=\"334\" r=\"14\" fill=\"#5B4BFF\"/><path d=\"M79 334 l5 6 l10 -12\" stroke=\"#F7F5F0\" stroke-width=\"4\" fill=\"none\"/>\n    <text x=\"116\" y=\"343\" font-size=\"24\">Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=</text>\n  </g>\n</svg>\n","path":"templates/casework/work/starter/refs/handshake-panel.svg","sha256":"f16ec9242a4bfeb8f5211c055567ca9bbbc88daf385440373e36eec10312874d"},{"bytes":8989,"content":"{\n \"schema\": \"studio-claims/1\",\n \"corpus\": \"source/corpus.json\",\n \"rule\": \"On-screen technical text may only restate these verified claims. Anything else is a new claim: verify it against the pinned corpus first, or leave it off screen.\",\n \"verdicts\": \"verified = anchored to the pinned RFC text; opinion = framing with no factual claim\",\n \"lines\": [\n  {\n   \"key\": \"F01-L01\",\n   \"speaker\": \"ADA\",\n   \"text\": \"How did web pages get live updates before WebSockets?\",\n   \"verdict\": \"opinion\",\n   \"claims\": [\n    {\n     \"claim\": \"Question — no factual claim.\",\n     \"kind\": \"opinion\",\n     \"anchors\": []\n    }\n   ]\n  },\n  {\n   \"key\": \"F01-L02\",\n   \"speaker\": \"MAX\",\n   \"text\": \"By polling, which the WebSocket R-F-C calls an abuse of H-T-T-P.\",\n   \"verdict\": \"verified\",\n   \"claims\": [\n    {\n     \"claim\": \"Before WebSockets, bidirectional web apps polled the server for updates; RFC 6455 calls this an abuse of HTTP.\",\n     \"kind\": \"doc\",\n     \"anchors\": [\n      {\n       \"source\": \"rfc6455\",\n       \"lines\": [\n        183,\n        184\n       ],\n       \"snippet\": \"has required an abuse of HTTP to poll the server for updates\"\n      }\n     ]\n    },\n    {\n     \"claim\": \"RFC 6455 is the WebSocket Protocol RFC.\",\n     \"kind\": \"doc\",\n     \"anchors\": [\n      {\n       \"source\": \"rfc6455\",\n       \"lines\": [\n        14,\n        14\n       ],\n       \"snippet\": \"The WebSocket Protocol\"\n      }\n     ]\n    }\n   ]\n  },\n  {\n   \"key\": \"F01-L03\",\n   \"speaker\": \"MAX\",\n   \"text\": \"R-F-C sixty-four-fifty-five offers one T-C-P connection instead, carrying traffic in both directions.\",\n   \"verdict\": \"verified\",\n   \"claims\": [\n    {\n     \"claim\": \"The RFC is number 6455.\",\n     \"kind\": \"number\",\n     \"anchors\": [\n      {\n       \"source\": \"rfc6455\",\n       \"lines\": [\n        8,\n        8\n       ],\n       \"snippet\": \"Request for Comments: 6455\"\n      }\n     ]\n    },\n    {\n     \"claim\": \"The WebSocket Protocol provides a single TCP connection for traffic in both directions.\",\n     \"kind\": \"doc\",\n     \"anchors\": [\n      {\n       \"source\": \"rfc6455\",\n       \"lines\": [\n        199,\n        200\n       ],\n       \"snippet\": \"A simpler solution would be to use a single TCP connection for traffic in both directions.\"\n      },\n      {\n       \"source\": \"rfc6455\",\n       \"lines\": [\n        200,\n        201\n       ],\n       \"snippet\": \"This is what the WebSocket Protocol provides.\"\n      }\n     ]\n    },\n    {\n     \"claim\": \"It is offered as an alternative to HTTP polling ('instead').\",\n     \"kind\": \"doc\",\n     \"anchors\": [\n      {\n       \"source\": \"rfc6455\",\n       \"lines\": [\n        201,\n        202\n       ],\n       \"snippet\": \"it provides an alternative to HTTP polling for two-way communication\"\n      }\n     ]\n    }\n   ]\n  },\n  {\n   \"key\": \"F02-L01\",\n   \"speaker\": \"ADA\",\n   \"text\": \"So how does a WebSocket connection start?\",\n   \"verdict\": \"opinion\",\n   \"claims\": [\n    {\n     \"claim\": \"Question — no factual claim.\",\n     \"kind\": \"opinion\",\n     \"anchors\": []\n    }\n   ]\n  },\n  {\n   \"key\": \"F02-L02\",\n   \"speaker\": \"MAX\",\n   \"text\": \"As an ordinary H-T-T-P request that asks to upgrade: Upgrade websocket, Connection Upgrade.\",\n   \"verdict\": \"verified\",\n   \"claims\": [\n    {\n     \"claim\": \"The client's opening handshake is an HTTP Upgrade request; to an HTTP server it looks like a regular GET request with an Upgrade offer.\",\n     \"kind\": \"doc\",\n     \"anchors\": [\n      {\n       \"source\": \"rfc6455\",\n       \"lines\": [\n        308,\n        309\n       ],\n       \"snippet\": \"the WebSocket client's handshake is an HTTP Upgrade request\"\n      },\n      {\n       \"source\": \"rfc6455\",\n       \"lines\": [\n        602,\n        602\n       ],\n       \"snippet\": \"be a regular GET request with an Upgrade offer\"\n      }\n     ]\n    },\n    {\n     \"claim\": \"It carries an Upgrade header containing websocket and a Connection header containing Upgrade.\",\n     \"kind\": \"doc\",\n     \"anchors\": [\n      {\n       \"source\": \"rfc6455\",\n       \"lines\": [\n        948,\n        949\n       ],\n       \"snippet\": \"The request MUST contain an |Upgrade| header field whose value MUST include the \\\"websocket\\\" keyword.\"\n      },\n      {\n       \"source\": \"rfc6455\",\n       \"lines\": [\n        959,\n        960\n       ],\n       \"snippet\": \"The request MUST contain a |Connection| header field whose value MUST include the \\\"Upgrade\\\" token.\"\n      },\n      {\n       \"source\": \"rfc6455\",\n       \"lines\": [\n        246,\n        247\n       ],\n       \"snippet\": \"Upgrade: websocket Connection: Upgrade\"\n      }\n     ]\n    }\n   ]\n  },\n  {\n   \"key\": \"F02-L03\",\n   \"speaker\": \"MAX\",\n   \"text\": \"It adds a random key, and asks for protocol version thirteen.\",\n   \"verdict\": \"verified\",\n   \"claims\": [\n    {\n     \"claim\": \"The request carries Sec-WebSocket-Key, a randomly selected nonce.\",\n     \"kind\": \"doc\",\n     \"anchors\": [\n      {\n       \"source\": \"rfc6455\",\n       \"lines\": [\n        964,\n        965\n       ],\n       \"snippet\": \"nonce consisting of a randomly selected 16-byte value that has been base64-encoded\"\n      },\n      {\n       \"source\": \"rfc6455\",\n       \"lines\": [\n        965,\n        966\n       ],\n       \"snippet\": \"The nonce MUST be selected randomly for each connection.\"\n      }\n     ]\n    },\n    {\n     \"claim\": \"It asks for protocol version 13 (Sec-WebSocket-Version: 13).\",\n     \"kind\": \"number\",\n     \"anchors\": [\n      {\n       \"source\": \"rfc6455\",\n       \"lines\": [\n        988,\n        989\n       ],\n       \"snippet\": \"The value of this header field MUST be 13.\"\n      },\n      {\n       \"source\": \"rfc6455\",\n       \"lines\": [\n        251,\n        251\n       ],\n       \"snippet\": \"Sec-WebSocket-Version: 13\"\n      }\n     ]\n    }\n   ]\n  },\n  {\n   \"key\": \"F02-L04\",\n   \"speaker\": \"ADA\",\n   \"text\": \"And the server?\",\n   \"verdict\": \"opinion\",\n   \"claims\": [\n    {\n     \"claim\": \"Question — no factual claim.\",\n     \"kind\": \"opinion\",\n     \"anchors\": []\n    }\n   ]\n  },\n  {\n   \"key\": \"F02-L05\",\n   \"speaker\": \"MAX\",\n   \"text\": \"If it accepts, it answers one-oh-one, Switching Protocols, with an accept header.\",\n   \"verdict\": \"verified\",\n   \"claims\": [\n    {\n     \"claim\": \"A server that accepts the connection replies with status 101 (HTTP/1.1 101 Switching Protocols).\",\n     \"kind\": \"number\",\n     \"anchors\": [\n      {\n       \"source\": \"rfc6455\",\n       \"lines\": [\n        1299,\n        1300\n       ],\n       \"snippet\": \"If the server chooses to accept the incoming connection, it MUST reply with a valid HTTP response indicating the following.\"\n      },\n      {\n       \"source\": \"rfc6455\",\n       \"lines\": [\n        1302,\n        1302\n       ],\n       \"snippet\": \"A Status-Line with a 101 response code as per RFC 2616\"\n      },\n      {\n       \"source\": \"rfc6455\",\n       \"lines\": [\n        255,\n        255\n       ],\n       \"snippet\": \"HTTP/1.1 101 Switching Protocols\"\n      }\n     ]\n    },\n    {\n     \"claim\": \"The reply includes a Sec-WebSocket-Accept header field.\",\n     \"kind\": \"doc\",\n     \"anchors\": [\n      {\n       \"source\": \"rfc6455\",\n       \"lines\": [\n        1311,\n        1311\n       ],\n       \"snippet\": \"A |Sec-WebSocket-Accept| header field.\"\n      },\n      {\n       \"source\": \"rfc6455\",\n       \"lines\": [\n        258,\n        258\n       ],\n       \"snippet\": \"Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\"\n      }\n     ]\n    }\n   ]\n  },\n  {\n   \"key\": \"F02-L06\",\n   \"speaker\": \"MAX\",\n   \"text\": \"Any other status code, and H-T-T-P semantics still apply.\",\n   \"verdict\": \"verified\",\n   \"claims\": [\n    {\n     \"claim\": \"Any status code other than 101 means the handshake has not completed and HTTP semantics still apply.\",\n     \"kind\": \"doc\",\n     \"anchors\": [\n      {\n       \"source\": \"rfc6455\",\n       \"lines\": [\n        416,\n        417\n       ],\n       \"snippet\": \"Any status code other than 101 indicates that the WebSocket handshake has not completed and that the semantics of HTTP still apply.\"\n      }\n     ]\n    }\n   ]\n  },\n  {\n   \"key\": \"F02-L07\",\n   \"speaker\": \"MAX\",\n   \"text\": \"Then frames flow both ways, whenever either side wants.\",\n   \"verdict\": \"verified\",\n   \"claims\": [\n    {\n     \"claim\": \"After a successful handshake the data transfer starts: a two-way channel where each side sends data at will.\",\n     \"kind\": \"doc\",\n     \"anchors\": [\n      {\n       \"source\": \"rfc6455\",\n       \"lines\": [\n        271,\n        272\n       ],\n       \"snippet\": \"Once the client and server have both sent their handshakes, and if the handshake was successful, then the data transfer part starts.\"\n      },\n      {\n       \"source\": \"rfc6455\",\n       \"lines\": [\n        273,\n        274\n       ],\n       \"snippet\": \"This is a two-way communication channel where each side can, independently from the other, send data at will.\"\n      }\n     ]\n    },\n    {\n     \"claim\": \"Data is transmitted as a sequence of frames.\",\n     \"kind\": \"doc\",\n     \"anchors\": [\n      {\n       \"source\": \"rfc6455\",\n       \"lines\": [\n        1489,\n        1490\n       ],\n       \"snippet\": \"In the WebSocket Protocol, data is transmitted using a sequence of frames.\"\n      }\n     ]\n    }\n   ]\n  }\n ]\n}\n","path":"templates/casework/work/starter/source/claims.json","sha256":"bc3deb0566ae7cdd56f005256a76e09f61fbf2e7b87aa7c47e71d287fea9e74b"},{"bytes":605,"content":"{\n  \"schema\": \"studio-corpus/1\",\n  \"sources\": {\n    \"rfc6455\": {\n      \"title\": \"RFC 6455: The WebSocket Protocol\",\n      \"url\": \"https://www.rfc-editor.org/rfc/rfc6455.txt\",\n      \"sha256\": \"765775326aee0ecca9b04bde3fd1f52932d498e33e34e428bd61b8a24da0fa3b\",\n      \"bytes\": 162067,\n      \"lines\": 3979,\n      \"line_numbers\": \"1-based lines of the exact bytes above\"\n    }\n  },\n  \"verify\": \"Fetch the URL, confirm the sha256, then confirm each claim anchor's snippet appears within its line range (whitespace-insensitive). A mismatch means the corpus changed: re-verify before any claim goes on screen.\"\n}\n","path":"templates/casework/work/starter/source/corpus.json","sha256":"b33273c59621f14ff065005b41d518a924a40066470d8ccead49249365eeaf9e"},{"bytes":8880,"content":"{\n \"schema\": \"studio-words/1\",\n \"engine\": \"reference-read\",\n \"model\": \"synthesized two-voice narration of source/script.md, forced word alignment\",\n \"source\": \"reference read (not included; produce your own take)\",\n \"window\": null,\n \"language\": \"en\",\n \"note\": \"Real word timings from the studio's synthesized reference read of script.md, on that read's own timeline (two windows: 0.3-16.39 s and 20.8-48.7 s; the agenda line between them is not in the script). Use it to plan pacing before your take exists; the board and captions must use your take's transcript.\",\n \"words\": [\n  {\n   \"speaker\": \"ADA\",\n   \"text\": \"How\",\n   \"start\": 0.56,\n   \"end\": 0.68\n  },\n  {\n   \"speaker\": \"ADA\",\n   \"text\": \"did\",\n   \"start\": 0.68,\n   \"end\": 0.91\n  },\n  {\n   \"speaker\": \"ADA\",\n   \"text\": \"web\",\n   \"start\": 0.91,\n   \"end\": 1.53\n  },\n  {\n   \"speaker\": \"ADA\",\n   \"text\": \"pages\",\n   \"start\": 1.53,\n   \"end\": 1.57\n  },\n  {\n   \"speaker\": \"ADA\",\n   \"text\": \"get\",\n   \"start\": 1.57,\n   \"end\": 1.68\n  },\n  {\n   \"speaker\": \"ADA\",\n   \"text\": \"live\",\n   \"start\": 1.86,\n   \"end\": 2.07\n  },\n  {\n   \"speaker\": \"ADA\",\n   \"text\": \"updates\",\n   \"start\": 2.07,\n   \"end\": 2.62\n  },\n  {\n   \"speaker\": \"ADA\",\n   \"text\": \"before\",\n   \"start\": 2.7,\n   \"end\": 2.96\n  },\n  {\n   \"speaker\": \"ADA\",\n   \"text\": \"WebSockets?\",\n   \"start\": 2.96,\n   \"end\": 3.65\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"By\",\n   \"start\": 3.966,\n   \"end\": 4.086\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"polling,\",\n   \"start\": 4.146,\n   \"end\": 4.936\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"which\",\n   \"start\": 4.986,\n   \"end\": 5.336\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"the\",\n   \"start\": 5.336,\n   \"end\": 5.576\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"WebSocket\",\n   \"start\": 5.576,\n   \"end\": 6.236\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"RFC\",\n   \"start\": 6.276,\n   \"end\": 6.526\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"calls\",\n   \"start\": 6.576,\n   \"end\": 6.896\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"an\",\n   \"start\": 6.896,\n   \"end\": 7.066\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"abuse\",\n   \"start\": 7.066,\n   \"end\": 7.496\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"of\",\n   \"start\": 7.496,\n   \"end\": 7.936\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"HTTP.\",\n   \"start\": 7.936,\n   \"end\": 8.756\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"RFC\",\n   \"start\": 8.894,\n   \"end\": 9.284\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"6455\",\n   \"start\": 9.284,\n   \"end\": 10.894\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"offers\",\n   \"start\": 11.124,\n   \"end\": 11.614\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"one\",\n   \"start\": 11.614,\n   \"end\": 11.894\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"TCP\",\n   \"start\": 11.894,\n   \"end\": 12.174\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"connection\",\n   \"start\": 12.174,\n   \"end\": 13.164\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"instead,\",\n   \"start\": 13.164,\n   \"end\": 13.984\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"carrying\",\n   \"start\": 14.164,\n   \"end\": 14.564\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"traffic\",\n   \"start\": 14.564,\n   \"end\": 15.034\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"in\",\n   \"start\": 15.114,\n   \"end\": 15.214\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"both\",\n   \"start\": 15.214,\n   \"end\": 15.494\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"directions.\",\n   \"start\": 15.524,\n   \"end\": 16.244\n  },\n  {\n   \"speaker\": \"ADA\",\n   \"text\": \"So\",\n   \"start\": 21.209,\n   \"end\": 21.239\n  },\n  {\n   \"speaker\": \"ADA\",\n   \"text\": \"how\",\n   \"start\": 21.239,\n   \"end\": 21.409\n  },\n  {\n   \"speaker\": \"ADA\",\n   \"text\": \"does\",\n   \"start\": 21.409,\n   \"end\": 21.629\n  },\n  {\n   \"speaker\": \"ADA\",\n   \"text\": \"a\",\n   \"start\": 21.629,\n   \"end\": 21.679\n  },\n  {\n   \"speaker\": \"ADA\",\n   \"text\": \"WebSocket\",\n   \"start\": 21.679,\n   \"end\": 22.189\n  },\n  {\n   \"speaker\": \"ADA\",\n   \"text\": \"connection\",\n   \"start\": 22.189,\n   \"end\": 22.709\n  },\n  {\n   \"speaker\": \"ADA\",\n   \"text\": \"start?\",\n   \"start\": 22.759,\n   \"end\": 23.139\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"As\",\n   \"start\": 23.466,\n   \"end\": 23.616\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"an\",\n   \"start\": 23.616,\n   \"end\": 23.826\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"ordinary\",\n   \"start\": 23.826,\n   \"end\": 24.666\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"HTTP\",\n   \"start\": 24.666,\n   \"end\": 25.086\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"request\",\n   \"start\": 25.086,\n   \"end\": 25.806\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"that\",\n   \"start\": 25.886,\n   \"end\": 26.006\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"asks\",\n   \"start\": 26.046,\n   \"end\": 26.366\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"to\",\n   \"start\": 26.436,\n   \"end\": 26.566\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"upgrade:\",\n   \"start\": 26.566,\n   \"end\": 27.666\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"Upgrade\",\n   \"start\": 27.726,\n   \"end\": 28.206\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"websocket,\",\n   \"start\": 28.206,\n   \"end\": 29.066\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"Connection\",\n   \"start\": 29.066,\n   \"end\": 29.706\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"Upgrade.\",\n   \"start\": 29.706,\n   \"end\": 30.212\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"It\",\n   \"start\": 30.342,\n   \"end\": 30.502\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"adds\",\n   \"start\": 30.502,\n   \"end\": 30.912\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"a\",\n   \"start\": 30.912,\n   \"end\": 31.012\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"random\",\n   \"start\": 31.012,\n   \"end\": 31.522\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"key,\",\n   \"start\": 31.522,\n   \"end\": 31.992\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"and\",\n   \"start\": 31.992,\n   \"end\": 32.182\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"asks\",\n   \"start\": 32.182,\n   \"end\": 32.432\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"for\",\n   \"start\": 32.472,\n   \"end\": 32.672\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"protocol\",\n   \"start\": 32.672,\n   \"end\": 33.242\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"version\",\n   \"start\": 33.242,\n   \"end\": 33.742\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"13.\",\n   \"start\": 33.742,\n   \"end\": 34.242\n  },\n  {\n   \"speaker\": \"ADA\",\n   \"text\": \"And\",\n   \"start\": 34.473,\n   \"end\": 34.553\n  },\n  {\n   \"speaker\": \"ADA\",\n   \"text\": \"the\",\n   \"start\": 34.553,\n   \"end\": 34.663\n  },\n  {\n   \"speaker\": \"ADA\",\n   \"text\": \"server?\",\n   \"start\": 34.733,\n   \"end\": 35.103\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"If\",\n   \"start\": 35.426,\n   \"end\": 35.576\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"it\",\n   \"start\": 35.576,\n   \"end\": 35.736\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"accepts,\",\n   \"start\": 35.796,\n   \"end\": 36.646\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"it\",\n   \"start\": 36.686,\n   \"end\": 36.786\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"answers\",\n   \"start\": 36.786,\n   \"end\": 37.296\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"101,\",\n   \"start\": 37.296,\n   \"end\": 37.966\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"Switching\",\n   \"start\": 37.966,\n   \"end\": 38.426\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"Protocols,\",\n   \"start\": 38.426,\n   \"end\": 39.186\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"with\",\n   \"start\": 39.246,\n   \"end\": 39.386\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"an\",\n   \"start\": 39.386,\n   \"end\": 39.466\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"accept\",\n   \"start\": 39.466,\n   \"end\": 39.786\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"header.\",\n   \"start\": 39.876,\n   \"end\": 40.266\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"Any\",\n   \"start\": 40.454,\n   \"end\": 40.654\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"other\",\n   \"start\": 40.654,\n   \"end\": 41.084\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"status\",\n   \"start\": 41.084,\n   \"end\": 41.554\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"code,\",\n   \"start\": 41.584,\n   \"end\": 41.674\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"and\",\n   \"start\": 42.024,\n   \"end\": 42.254\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"HTTP\",\n   \"start\": 42.254,\n   \"end\": 42.654\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"semantics\",\n   \"start\": 42.654,\n   \"end\": 43.524\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"still\",\n   \"start\": 43.554,\n   \"end\": 44.044\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"apply.\",\n   \"start\": 44.044,\n   \"end\": 44.604\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"Then\",\n   \"start\": 44.736,\n   \"end\": 45.036\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"frames\",\n   \"start\": 45.036,\n   \"end\": 45.566\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"flow\",\n   \"start\": 45.566,\n   \"end\": 45.826\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"both\",\n   \"start\": 45.946,\n   \"end\": 46.126\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"ways,\",\n   \"start\": 46.276,\n   \"end\": 46.346\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"whenever\",\n   \"start\": 46.686,\n   \"end\": 47.216\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"either\",\n   \"start\": 47.216,\n   \"end\": 47.636\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"side\",\n   \"start\": 47.636,\n   \"end\": 47.916\n  },\n  {\n   \"speaker\": \"MAX\",\n   \"text\": \"wants.\",\n   \"start\": 47.916,\n   \"end\": 48.296\n  }\n ]\n}\n","path":"templates/casework/work/starter/source/reference-words.json","sha256":"8b7163df6fc7626a1accc3b402d0cf3d905329b98a98e464a9703ce61974b144"},{"bytes":1330,"content":"# Script: the WebSocket handshake in 40 seconds\n\nTwo voices: **ADA** asks, **MAX** explains. One presenter may read both parts.\nEvery factual line maps to a verified claim in `claims.json` (keys in\nbrackets); the reference read runs about 44 seconds before trims.\n\n**ADA** [F01-L01]: How did web pages get live updates before WebSockets?\n\n**MAX** [F01-L02]: By polling, which the WebSocket RFC calls an abuse of HTTP.\n\n**MAX** [F01-L03]: RFC 6455 offers one TCP connection instead, carrying\ntraffic in both directions.\n\n**ADA** [F02-L01]: So how does a WebSocket connection start?\n\n**MAX** [F02-L02]: As an ordinary HTTP request that asks to upgrade: Upgrade\nwebsocket, Connection Upgrade.\n\n**MAX** [F02-L03]: It adds a random key, and asks for protocol version 13.\n\n**ADA** [F02-L04]: And the server?\n\n**MAX** [F02-L05]: If it accepts, it answers 101, Switching Protocols, with an\naccept header.\n\n**MAX** [F02-L06]: Any other status code, and HTTP semantics still apply.\n\n**MAX** [F02-L07]: Then frames flow both ways, whenever either side wants.\n\n## Reading notes\n\n- Land the hook question cleanly; it is the first thing viewers hear.\n- Read header names as spoken words (\"Upgrade websocket, Connection Upgrade\").\n  The typing window shows their exact spelling.\n- Pause briefly between lines; the editor cuts on those silences.\n","path":"templates/casework/work/starter/source/script.md","sha256":"cfc37a015b3fb5e63121c7b4cb722f84f30ab88ca1f8e3f6029a7cb89304b55e"},{"bytes":7270,"content":"#!/usr/bin/env node\n// caption-phraser.mjs — words.json (studio-words/1) + a brand kit's tokens.json -> caption phrases on the output\n// timeline, an SRT file, or a trace check of an existing captions.json. Node 18+, no dependencies.\n//\n//   node tools/caption-phraser.mjs <words.json> <tokens.json> [--cuts 0.3-16.39,20.8-48.7] [--out captions.json] [--srt captions.srt]\n//   node tools/caption-phraser.mjs <words.json> <tokens.json> [--cuts ...] --verify captions.json\n//\n// Phrases follow tokens.captions: at most max_words words, sentence punctuation removed when punctuation is false\n// (characters inside a token such as HTTP/1.1 or Sec-WebSocket-Key stay), and a hard break at every sentence end,\n// speaker change, pause of 0.35 s or more, and cut. Inside a run, a small dynamic program picks the breaks (see\n// split()). Cuts map source times onto the output timeline: the output\n// is the cuts played back to back, and words outside every cut are dropped.\nimport { readFileSync, writeFileSync } from \"node:fs\";\n\nconst argv = process.argv.slice(2);\nconst opt = (name) => {\n  const i = argv.indexOf(`--${name}`);\n  return i >= 0 ? argv[i + 1] : null;\n};\nconst [wordsPath, tokensPath] = argv.filter((a, i) => !a.startsWith(\"--\") && !(i > 0 && argv[i - 1].startsWith(\"--\")));\nif (!wordsPath || !tokensPath) {\n  console.error(\"usage: node caption-phraser.mjs <words.json> <tokens.json> [--cuts a-b,c-d] [--out captions.json] [--srt file] [--verify captions.json]\");\n  process.exit(2);\n}\nconst words = JSON.parse(readFileSync(wordsPath, \"utf8\")).words;\nconst kit = JSON.parse(readFileSync(tokensPath, \"utf8\"));\nconst maxWords = kit.captions?.max_words ?? 4;\nconst keepPunctuation = kit.captions?.punctuation === true;\nconst cuts = (opt(\"cuts\") ?? \"\")\n  .split(\",\")\n  .filter(Boolean)\n  .map((c) => c.split(\"-\").map(Number))\n  .map(([from, to]) => ({ from, to }));\nif (!cuts.length) cuts.push({ from: 0, to: Infinity });\nconst PAUSE = 0.35;\nconst EDGE = /^[\\s.,;:!?\"'“”‘’()[\\]{}…—–-]+|[\\s.,;:!?\"'“”‘’()[\\]{}…—–-]+$/gu;\nconst round = (t) => Math.round(t * 1000) / 1000;\nconst norm = (s) => s.toLowerCase().replace(/[^\\p{L}\\p{N}]+/gu, \"\");\n\nfunction onOutput(list) {\n  const out = [];\n  let offset = 0;\n  cuts.forEach((cut, index) => {\n    for (const w of list) {\n      if (w.start < cut.from || w.start >= cut.to) continue;\n      out.push({ ...w, cut: index, start: round(offset + w.start - cut.from), end: round(offset + Math.min(w.end, cut.to) - cut.from) });\n    }\n    offset += cut.to - cut.from;\n  });\n  return out;\n}\n\n// A run between hard breaks is split by a small dynamic program: fewest phrases first, then prefer breaking after a\n// comma, avoid breaking after an article, preposition or number, and avoid one-word phrases.\nconst ARTICLES = new Set([\"a\", \"an\", \"the\"]);\nconst GLUE = new Set([\"of\", \"to\", \"in\", \"on\", \"for\", \"with\", \"by\", \"as\", \"at\", \"from\", \"and\", \"or\", \"but\", \"that\", \"which\", \"its\", \"their\", \"your\", \"our\", \"my\", \"is\", \"are\", \"was\", \"were\"]);\nfunction split(run) {\n  const breakCost = (j) => {\n    const before = run[j - 1].text;\n    if (/[,;:][\"”’)]*$/u.test(before)) return -7;\n    const word = norm(before);\n    if (ARTICLES.has(word)) return 8;\n    if (GLUE.has(word) || /^\\d+$/.test(word)) return 4;\n    return 0;\n  };\n  const best = [{ cost: 0, from: -1 }];\n  for (let i = 1; i <= run.length; i++) {\n    best[i] = { cost: Infinity, from: -1 };\n    for (let k = 1; k <= maxWords && k <= i; k++) {\n      const j = i - k;\n      const cost = best[j].cost + 6 + (k === 1 ? 5 : 0) + (j > 0 ? breakCost(j) : 0);\n      if (cost < best[i].cost) best[i] = { cost, from: j };\n    }\n  }\n  const chunks = [];\n  for (let i = run.length; i > 0; i = best[i].from) chunks.unshift(run.slice(best[i].from, i));\n  return chunks;\n}\n\nfunction phrases(list) {\n  const runs = [];\n  list.forEach((w, i) => {\n    const prev = list[i - 1];\n    const hard = !prev || prev.cut !== w.cut || (prev.speaker ?? \"\") !== (w.speaker ?? \"\") || w.start - prev.end >= PAUSE || /[.?!][\"”’)]*$/u.test(prev.text);\n    if (hard) runs.push([]);\n    runs[runs.length - 1].push(w);\n  });\n  const result = [];\n  for (const run of runs) {\n    for (const chunk of split(run)) {\n      const shown = chunk.map((w) => ({ text: keepPunctuation ? w.text : w.text.replace(EDGE, \"\"), start: w.start, end: w.end })).filter((w) => w.text);\n      if (shown.length) result.push({ start: shown[0].start, end: shown[shown.length - 1].end, speaker: chunk[0].speaker ?? null, text: shown.map((w) => w.text).join(\" \"), words: shown });\n    }\n  }\n  result.forEach((ph, i) => {\n    ph.id = `c${String(i + 1).padStart(3, \"0\")}`;\n    const next = result[i + 1];\n    ph.end = round(next ? Math.min(ph.end + 0.2, next.start) : ph.end + 0.2);\n  });\n  return result;\n}\n\nfunction srt(list) {\n  const stamp = (t) => {\n    const ms = Math.round(t * 1000);\n    const pad = (n, w = 2) => String(n).padStart(w, \"0\");\n    return `${pad(Math.floor(ms / 3600000))}:${pad(Math.floor(ms / 60000) % 60)}:${pad(Math.floor(ms / 1000) % 60)},${pad(ms % 1000, 3)}`;\n  };\n  return list.map((p, i) => `${i + 1}\\n${stamp(p.start)} --> ${stamp(p.end)}\\n${p.text}\\n`).join(\"\\n\");\n}\n\nconst spoken = onOutput(words);\nif (opt(\"verify\")) {\n  const captions = JSON.parse(readFileSync(opt(\"verify\"), \"utf8\")).phrases;\n  const fails = [];\n  const shown = captions.flatMap((p) => p.text.split(/\\s+/).filter(Boolean));\n  const expected = spoken.map((w) => norm(w.text)).filter(Boolean);\n  const got = shown.map(norm).filter(Boolean);\n  const firstDiff = expected.findIndex((w, i) => got[i] !== w);\n  if (firstDiff >= 0 || got.length !== expected.length) {\n    const at = firstDiff >= 0 ? firstDiff : Math.min(got.length, expected.length);\n    fails.push(`caption-trace: word ${at + 1} is \"${shown[at] ?? \"(none)\"}\", transcript says \"${spoken[at]?.text ?? \"(none)\"}\"`);\n  }\n  for (const p of captions) {\n    const count = p.text.split(/\\s+/).filter(Boolean).length;\n    if (count > maxWords) fails.push(`caption-style: ${p.id ?? p.text} has ${count} words (max ${maxWords})`);\n    if (!keepPunctuation && /(^|\\s)[.,;:!?\"“”]|[.,;:!?\"“”](\\s|$)/u.test(p.text)) fails.push(`caption-style: ${p.id ?? p.text} carries punctuation: \"${p.text}\"`);\n  }\n  if (fails.length) {\n    fails.forEach((f) => console.error(`✗ ${f}`));\n    process.exit(1);\n  }\n  console.log(`✓ ${captions.length} phrases trace to ${expected.length} spoken words in order; style ok (max ${maxWords} words, punctuation ${keepPunctuation ? \"kept\" : \"removed\"})`);\n  process.exit(0);\n}\n\nconst list = phrases(spoken);\nconst doc = { schema: \"studio-captions/1\", words: wordsPath, kit: kit.name ?? null, max_words: maxWords, punctuation: keepPunctuation,\n  cuts: cuts.map((c) => [c.from, Number.isFinite(c.to) ? c.to : null]), duration: round(cuts.reduce((s, c) => s + (Number.isFinite(c.to) ? c.to - c.from : 0), 0)) || null, phrases: list };\nconst out = opt(\"out\") ?? \"captions.json\";\nwriteFileSync(out, `${JSON.stringify(doc, null, 1)}\\n`);\nif (opt(\"srt\")) writeFileSync(opt(\"srt\"), srt(list));\nconst sizes = list.map((p) => p.words.length);\nconsole.log(`✓ ${list.length} phrases from ${spoken.length} words -> ${out}${opt(\"srt\") ? ` and ${opt(\"srt\")}` : \"\"} (words per phrase ${Math.min(...sizes)}-${Math.max(...sizes)})`);\n","path":"templates/casework/work/starter/tools/caption-phraser.mjs","sha256":"2ab33a01cacb636f8aef704074cebc97802080df42d0342cd594e58566e22042"},{"bytes":2921,"content":"#!/usr/bin/env node\n// check-kit.mjs — validate a studio brand kit: node scripts/check-kit.mjs kits/<name>\n// Checks tokens.json shape, hex colours, that every font file and licence exists, that zones sit inside the\n// frame, that no word zone touches the face keep-out or the platform-safe areas, and that design.md exists.\nimport { existsSync, readFileSync, readdirSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\n\nconst dir = resolve(process.argv[2] ?? \"\");\nconst fail = [];\nconst need = (ok, msg) => ok || fail.push(msg);\nlet t;\ntry {\n  t = JSON.parse(readFileSync(join(dir, \"tokens.json\"), \"utf8\"));\n} catch (e) {\n  console.error(`✗ ${dir}/tokens.json: ${e.message}`);\n  process.exit(1);\n}\nneed(t.schema === \"studio-brand-kit/1\", \"schema must be studio-brand-kit/1\");\nneed(existsSync(join(dir, \"design.md\")), \"design.md is missing\");\nfor (const k of [\"ink\", \"paper\", \"signal\", \"pulse\", \"warn\", \"muted\", \"panel\", \"line\"])\n  need(/^#[0-9A-Fa-f]{6}$/.test(t.colors?.[k] ?? \"\"), `colors.${k} must be #RRGGBB`);\nconst fontFiles = Object.entries(t.fonts ?? {});\nneed(fontFiles.length >= 4, \"fonts needs display, body, kicker and mono\");\nfor (const [role, f] of fontFiles) need(f.file && existsSync(join(dir, f.file)), `fonts.${role}: missing file ${f.file}`);\nconst licences = existsSync(join(dir, \"fonts\")) ? readdirSync(join(dir, \"fonts\")).filter((n) => /ofl|licen[cs]e/i.test(n)) : [];\nneed(licences.length > 0, \"fonts/ needs the licence text for every font family\");\nconst { width: W = 1080, height: H = 1920 } = t.frame ?? {};\nconst box = (b) => ({ x: b[0], y: b[1], r: b[0] + b[2], b: b[1] + b[3] });\nconst hit = (a, c) => a.x < c.r && c.x < a.r && a.y < c.b && c.y < a.b;\nconst zones = t.zones ?? {};\nneed(Array.isArray(zones.face), \"zones.face (the keep-out) is required\");\nconst keepOut = [[\"face\", box(zones.face ?? [0, 0, 0, 0])]];\nif (t.platform_safe) {\n  keepOut.push([\"platform bottom\", { x: 0, y: t.platform_safe.bottom_y, r: W, b: H }]);\n  if (t.platform_safe.right_rail) keepOut.push([\"platform right rail\", box(t.platform_safe.right_rail)]);\n}\nfor (const [name, b] of Object.entries(zones)) {\n  const z = box(b);\n  need(z.x >= 0 && z.y >= 0 && z.r <= W && z.b <= H, `zones.${name} leaves the ${W}x${H} frame`);\n  if (name === \"face\") continue;\n  for (const [k, ko] of keepOut) need(!hit(z, ko), `zones.${name} overlaps the ${k} keep-out`);\n}\nneed((t.captions?.max_words ?? 0) >= 1 && t.captions.max_words <= 6, \"captions.max_words must be 1-6\");\nneed(t.captions?.punctuation === false || t.captions?.punctuation === true, \"captions.punctuation must be true or false\");\nneed(!t.logo || existsSync(join(dir, t.logo)), `logo ${t.logo} is missing`);\nif (fail.length) {\n  for (const f of fail) console.error(`✗ ${f}`);\n  process.exit(1);\n}\nconsole.log(`✓ kit ${t.name}: ${fontFiles.length} fonts, ${Object.keys(zones).length} zones, platform-safe ${t.platform_safe ? \"on\" : \"off\"}`);\n","path":"templates/casework/work/starter/tools/check-kit.mjs","sha256":"703b77601d951edad402564a88627af6ebef4b6bb30eff561d96dfb24deae215"},{"bytes":12714,"content":"#!/usr/bin/env node\n// transcribe.mjs — word-timed transcript for a clip, in the studio's words.json contract (studio-words/1).\n//\n//   node transcribe.mjs <media> [--out transcript] [--engine auto|scribe|local] [--model <id>] [--language en]\n//                       [--window 0.3-48.7] [--speakers 1] [--keyterms \"WebSocket,RFC 6455\"]\n//                       [--terms \"WebSocket,Sec-WebSocket-Key\"] [--known <script.md|words.json>]\n//\n// Engines\n//   scribe  ElevenLabs Speech to Text (POST /v1/speech-to-text, model scribe_v2, word timestamps, diarization).\n//           Needs ELEVENLABS_API_KEY in the environment or in ./.env. The key is never printed or written.\n//   local   HyperFrames 0.8.41 `transcribe` (Whisper small.en or Parakeet), fully offline once models are cached.\n//   auto    scribe when a key is configured, otherwise local.\n// Fixes\n//   --terms  canonical spellings; any 1-3 recognized words that match a term ignoring case, spaces and hyphens\n//            are replaced by it (\"web socket\" -> \"WebSocket\").\n//   --known  the words are known (teleprompter read verbatim, synthesized narration): known words win, the\n//            recognized words only supply timing. Prints how well the two agreed. Add --timing known when the\n//            known file is itself a trusted word alignment (e.g. synthesized narration): its times are kept and\n//            the recognition only verifies the audio says those words.\n// Output (times on the SOURCE clip timeline): <out>/words.json, <out>/transcript.txt, <out>/meta.json\nimport { spawnSync } from \"node:child_process\";\nimport { createHash } from \"node:crypto\";\nimport { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { homedir, tmpdir } from \"node:os\";\nimport { basename, extname, join, relative, resolve } from \"node:path\";\n\nconst HF_VERSION = \"0.8.41\";\nconst args = process.argv.slice(2);\nconst flag = (name, fallback = null) => {\n  const i = args.indexOf(`--${name}`);\n  return i >= 0 ? args[i + 1] : fallback;\n};\nconst media = args.find((a, i) => !a.startsWith(\"--\") && !(i > 0 && args[i - 1].startsWith(\"--\")));\nif (!media || !existsSync(media)) {\n  console.error(\"usage: node transcribe.mjs <media> [--out transcript] [--engine auto|scribe|local] [--window a-b] [--known file] [--terms list]\");\n  process.exit(2);\n}\nconst outDir = resolve(flag(\"out\", \"transcript\"));\nconst language = flag(\"language\");\nconst speakers = flag(\"speakers\");\nconst [winFrom, winTo] = (flag(\"window\") ?? \"\").split(\"-\").map(Number);\nconst hasWindow = Number.isFinite(winFrom) && Number.isFinite(winTo) && winTo > winFrom;\n\nfunction apiKey() {\n  if (process.env.ELEVENLABS_API_KEY) return process.env.ELEVENLABS_API_KEY;\n  if (!existsSync(\".env\")) return null;\n  const line = readFileSync(\".env\", \"utf8\").split(/\\r?\\n/).find((l) => /^\\s*ELEVENLABS_API_KEY\\s*=/.test(l));\n  const value = line?.split(\"=\").slice(1).join(\"=\").trim().replace(/^[\"']|[\"']$/g, \"\");\n  return value || null;\n}\nconst key = apiKey();\nlet engine = flag(\"engine\", \"auto\");\nif (engine === \"auto\") engine = key ? \"scribe\" : \"local\";\nif (engine === \"scribe\" && !key) {\n  console.error(\"✗ scribe needs ELEVENLABS_API_KEY (environment or ./.env). Use --engine local to stay offline.\");\n  process.exit(2);\n}\n\n// 16 kHz mono PCM of the clip (or of the window): small uploads, identical input for both engines.\nconst scratch = mkdtempSync(join(tmpdir(), \"studio-transcribe-\"));\nconst wav = join(scratch, \"audio.wav\");\nconst cut = hasWindow ? [\"-ss\", String(winFrom), \"-t\", String(winTo - winFrom)] : [];\nconst ff = spawnSync(\"ffmpeg\", [\"-v\", \"error\", \"-y\", ...cut, \"-i\", media, \"-vn\", \"-ac\", \"1\", \"-ar\", \"16000\", \"-c:a\", \"pcm_s16le\", wav]);\nif (ff.status !== 0) {\n  console.error(`✗ ffmpeg could not extract audio: ${String(ff.stderr).trim()}`);\n  process.exit(1);\n}\nconst offset = hasWindow ? winFrom : 0;\n\nasync function scribe() {\n  const model = flag(\"model\", \"scribe_v2\");\n  const form = new FormData();\n  form.append(\"model_id\", model);\n  form.append(\"file\", new Blob([readFileSync(wav)], { type: \"audio/wav\" }), \"audio.wav\");\n  form.append(\"file_format\", \"pcm_s16le_16\");\n  form.append(\"timestamps_granularity\", \"word\");\n  form.append(\"tag_audio_events\", \"false\");\n  form.append(\"diarize\", speakers === \"1\" ? \"false\" : \"true\");\n  if (speakers) form.append(\"num_speakers\", speakers);\n  if (language) form.append(\"language_code\", language);\n  for (const term of (flag(\"keyterms\") ?? \"\").split(\",\").map((t) => t.trim()).filter(Boolean)) form.append(\"keyterms\", term);\n  const res = await fetch(\"https://api.elevenlabs.io/v1/speech-to-text\", { method: \"POST\", headers: { \"xi-api-key\": key }, body: form });\n  if (!res.ok) throw new Error(`ElevenLabs returned ${res.status}: ${(await res.text()).slice(0, 300)}`);\n  const body = await res.json();\n  const words = (body.words ?? []).filter((w) => w.type === \"word\").map((w) => ({ text: w.text, start: w.start, end: w.end, speaker: w.speaker_id ?? \"S1\" }));\n  return { words, model, language: body.language_code ?? language ?? null };\n}\n\nfunction hyperframesBin() {\n  const spec = `hyperframes@${HF_VERSION}`;\n  const dir = process.env.HYPERFRAMES_CLI_DIR ||\n    join(process.env.npm_config_cache || join(homedir(), \".npm\"), \"_npx\", createHash(\"sha512\").update(spec).digest(\"hex\").slice(0, 16), \"node_modules/hyperframes\");\n  if (!existsSync(join(dir, \"bin/hyperframes.mjs\"))) spawnSync(\"npx\", [\"--yes\", spec, \"--version\"], { stdio: \"ignore\" });\n  return join(dir, \"bin/hyperframes.mjs\");\n}\n\nfunction local() {\n  const model = flag(\"model\", \"small.en\");\n  const env = { ...process.env, HYPERFRAMES_NO_UPDATE_CHECK: \"1\", HYPERFRAMES_NO_TELEMETRY: \"1\", DO_NOT_TRACK: \"1\" };\n  const argv = [hyperframesBin(), \"transcribe\", wav, \"-d\", scratch, \"--json\", \"--model\", model, ...(language ? [\"--language\", language] : [])];\n  const run = spawnSync(process.execPath, argv, { env, encoding: \"utf8\" });\n  const summary = JSON.parse(run.stdout || \"{}\");\n  if (run.status !== 0 || !summary.ok) throw new Error(`hyperframes transcribe failed: ${(run.stderr || run.stdout).slice(0, 300)}`);\n  const words = JSON.parse(readFileSync(summary.transcriptPath, \"utf8\")).map((w) => ({ text: w.text, start: w.start, end: w.end, speaker: \"S1\" }));\n  return { words, model: `${summary.engine}:${summary.model ?? model}`, language };\n}\n\nconst norm = (s) => s.toLowerCase().replace(/[^\\p{L}\\p{N}]+/gu, \"\");\n\nfunction applyTerms(words, terms) {\n  const canon = new Map(terms.map((t) => [norm(t), t]));\n  const out = [];\n  for (let i = 0; i < words.length; ) {\n    let taken = 0;\n    for (let n = 3; n >= 1 && !taken; n--) {\n      if (i + n > words.length) continue;\n      const run = words.slice(i, i + n);\n      const trail = run[n - 1].text.match(/[^\\p{L}\\p{N}]+$/u)?.[0] ?? \"\";\n      const hit = canon.get(norm(run.map((w) => w.text).join(\"\")));\n      if (hit) {\n        out.push({ ...run[0], text: hit + trail, end: run[n - 1].end });\n        taken = n;\n      }\n    }\n    if (!taken) out.push(words[i]);\n    i += taken || 1;\n  }\n  return out;\n}\n\nfunction knownTokens(file) {\n  const raw = readFileSync(file, \"utf8\");\n  if (extname(file) === \".json\") {\n    const data = JSON.parse(raw);\n    return (data.words ?? data).map((w) => ({ text: w.text, speaker: w.speaker ?? null, start: w.start ?? null, end: w.end ?? null }));\n  }\n  return raw.replace(/^#.*$/gm, \" \").split(/\\s+/).filter(Boolean).map((text) => ({ text, speaker: null, start: null, end: null }));\n}\n\n// Known words win; recognized words supply timing. Alignment on normalized forms allows 1:1, 1:2 and 2:1 matches.\nfunction alignKnown(recognized, known) {\n  const R = recognized.map((w) => norm(w.text)), K = known.map((w) => norm(w.text));\n  const n = R.length, m = K.length, INF = 1e9;\n  const cost = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(INF));\n  const back = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(null));\n  cost[0][0] = 0;\n  for (let i = 0; i <= n; i++) {\n    for (let j = 0; j <= m; j++) {\n      const c = cost[i][j];\n      if (c >= INF) continue;\n      const step = (di, dj, add, kind) => {\n        if (i + di <= n && j + dj <= m && c + add < cost[i + di][j + dj]) {\n          cost[i + di][j + dj] = c + add;\n          back[i + di][j + dj] = [i, j, kind];\n        }\n      };\n      if (i < n && j < m) step(1, 1, R[i] === K[j] ? 0 : 1, R[i] === K[j] ? \"same\" : \"fix\");\n      if (i < n && j + 1 < m && R[i] === K[j] + K[j + 1]) step(1, 2, 0, \"split\");\n      if (i + 1 < n && j < m && R[i] + R[i + 1] === K[j]) step(2, 1, 0, \"merge\");\n      step(1, 0, 1, \"extra\");\n      step(0, 1, 1, \"missing\");\n    }\n  }\n  const pairs = [];\n  for (let i = n, j = m; i || j; ) {\n    const [pi, pj, kind] = back[i][j];\n    pairs.push({ r: [pi, i], k: [pj, j], kind });\n    [i, j] = [pi, pj];\n  }\n  pairs.reverse();\n  const out = [], stats = { same: 0, fix: 0, split: 0, merge: 0, extra: 0, missing: 0 };\n  for (const p of pairs) {\n    stats[p.kind] += 1;\n    if (p.kind === \"extra\") continue;\n    const rs = recognized.slice(p.r[0], p.r[1]), ks = known.slice(p.k[0], p.k[1]);\n    if (!rs.length) {\n      ks.forEach((k) => out.push({ text: k.text, start: null, end: null, speaker: k.speaker ?? \"S1\" }));\n      continue;\n    }\n    const start = rs[0].start, end = rs[rs.length - 1].end, chars = ks.reduce((s, k) => s + k.text.length, 0);\n    let t = start;\n    for (const k of ks) {\n      const span = ((end - start) * k.text.length) / chars;\n      out.push({ text: k.text, start: +t.toFixed(3), end: +(t + span).toFixed(3), speaker: k.speaker ?? rs[0].speaker });\n      t += span;\n    }\n  }\n  for (let i = 0; i < out.length; i++) {\n    if (out[i].start !== null) continue;\n    const prev = out.slice(0, i).reverse().find((w) => w.end !== null)?.end ?? 0;\n    const next = out.slice(i + 1).find((w) => w.start !== null)?.start ?? prev;\n    Object.assign(out[i], { start: prev, end: Math.max(prev, next) });\n  }\n  const agreement = known.length ? (stats.same + stats.split + stats.merge) / known.length : 1;\n  return { words: out, stats: { ...stats, agreement: +agreement.toFixed(3) } };\n}\n\ntry {\n  const result = engine === \"scribe\" ? await scribe() : local();\n  let words = result.words.map((w) => ({ ...w, start: +(w.start + offset).toFixed(3), end: +(w.end + offset).toFixed(3) }));\n  const terms = (flag(\"terms\") ?? \"\").split(\",\").map((t) => t.trim()).filter(Boolean);\n  if (terms.length) words = applyTerms(words, terms);\n  let alignment = null;\n  if (flag(\"known\")) {\n    const known = knownTokens(flag(\"known\"));\n    ({ words, stats: alignment } = alignKnown(words, known));\n    if (flag(\"timing\") === \"known\") {\n      if (!known.every((k) => Number.isFinite(k.start) && Number.isFinite(k.end))) throw new Error(\"--timing known needs a words.json whose every word has start and end\");\n      words = known.map((k) => ({ text: k.text, start: k.start, end: k.end, speaker: k.speaker ?? \"S1\" }));\n    }\n  }\n  for (let i = 1; i < words.length; i++) if (words[i].start < words[i - 1].start) words[i].start = words[i - 1].start;\n  mkdirSync(outDir, { recursive: true });\n  const source = relative(process.cwd(), resolve(media)) || basename(media);\n  writeFileSync(join(outDir, \"words.json\"), `${JSON.stringify({ schema: \"studio-words/1\", engine, model: result.model, source,\n    window: hasWindow ? [winFrom, winTo] : null, language: result.language, words }, null, 1)}\\n`);\n  const lines = [];\n  for (const w of words) {\n    const last = lines[lines.length - 1];\n    if (last && last.speaker === w.speaker && w.start - last.end < 0.6) Object.assign(last, { text: `${last.text} ${w.text}`, end: w.end });\n    else lines.push({ speaker: w.speaker, start: w.start, end: w.end, text: w.text });\n  }\n  const stamp = (s) => `${String(Math.floor(s / 60)).padStart(2, \"0\")}:${(s % 60).toFixed(2).padStart(5, \"0\")}`;\n  writeFileSync(join(outDir, \"transcript.txt\"), lines.map((l) => `[${stamp(l.start)}] ${l.speaker}: ${l.text}`).join(\"\\n\") + \"\\n\");\n  const mediaSha = createHash(\"sha256\").update(readFileSync(media)).digest(\"hex\");\n  writeFileSync(join(outDir, \"meta.json\"), `${JSON.stringify({ schema: \"studio-transcript-meta/1\", engine, model: result.model,\n    media: source, media_sha256: mediaSha, window: hasWindow ? [winFrom, winTo] : null, words: words.length, terms, alignment,\n    settings: { language, speakers, keyterms: flag(\"keyterms\") } }, null, 1)}\\n`);\n  console.log(`✓ ${words.length} words via ${engine} (${result.model})${alignment ? `; known-script agreement ${(alignment.agreement * 100).toFixed(1)}%` : \"\"} -> ${relative(process.cwd(), outDir) || \".\"}/words.json`);\n} catch (error) {\n  console.error(`✗ ${error.message}`);\n  process.exitCode = 1;\n} finally {\n  rmSync(scratch, { recursive: true, force: true });\n}\n","path":"templates/casework/work/starter/tools/transcribe.mjs","sha256":"7e1a97bb80294b252639ef5a638465ccf9d83b10319b93951334b4332f177cda"},{"bytes":10177,"content":"{\"authority\":\"inert-starter-data\",\"case_id\":\"pilot-websocket-short\",\"organization_seed\":\"ai-video-studio\",\"schema\":\"hive-hub-seed-task-board/1\",\"tasks\":[{\"acceptance\":[\"Names the hook, the target length, the source option and the kit.\",\"States that publication is out of scope.\"],\"assignee\":null,\"completed_evidence\":[],\"depends_on\":[],\"id\":\"brief\",\"inputs\":[\"starter/case/pilot.json\"],\"instructions\":\"Turn the case into BRIEF.md: audience, platform, 30 to 45 second target, the hook, the source option (talking head or synthesized), the kit, the gates and who signs each, and what is out of scope.\",\"outputs\":[\"deliverables/brief/BRIEF.md\"],\"state\":\"ready\",\"team\":\"showrunner\",\"title\":\"Write the production brief\"},{\"acceptance\":[\"The corpus sha256 matches source/corpus.json.\",\"Every anchor verifies and every factual line has a verified claim key.\",\"Records the sha256 of script.md and claims.json.\"],\"assignee\":null,\"completed_evidence\":[],\"depends_on\":[],\"id\":\"script-lock\",\"inputs\":[\"starter/source/script.md\",\"starter/source/claims.json\",\"starter/source/corpus.json\"],\"instructions\":\"Fetch the pinned RFC 6455 text, confirm its sha256, confirm every claim anchor's snippet sits inside its line range, and confirm every factual script line maps to a verified claim. Record the script and ledger sha256.\",\"outputs\":[\"deliverables/script/script-lock.json\"],\"state\":\"ready\",\"team\":\"explainers\",\"title\":\"Lock the script against the claim ledger\"},{\"acceptance\":[\"check-kit.mjs passes on the issued kit.\",\"Every font is a licensed file with its licence text.\"],\"assignee\":null,\"completed_evidence\":[],\"depends_on\":[\"brief\"],\"id\":\"brand-kit\",\"inputs\":[\"starter/brand/house-kit.md\",\"starter/brand/tokens.json\",\"deliverables/brief/BRIEF.md\",\"starter/tools/check-kit.mjs\"],\"instructions\":\"Resolve the house kit for this production: download the OFL fonts with their licences, confirm zones for the chosen layout (talking head, or source card in place of the face zone), and issue design.md and tokens.json. Run tools/check-kit.mjs.\",\"outputs\":[\"deliverables/brand/design.md\",\"deliverables/brand/tokens.json\"],\"state\":\"blocked\",\"team\":\"brand\",\"title\":\"Issue the house kit\"},{\"acceptance\":[\"The take speaks the locked script; deviations are listed.\",\"Records sha256, duration, layout option, and consent or voice licence.\"],\"assignee\":null,\"completed_evidence\":[],\"depends_on\":[\"script-lock\",\"brief\"],\"id\":\"source-take\",\"inputs\":[\"deliverables/script/script-lock.json\",\"deliverables/brief/BRIEF.md\",\"starter/source/script.md\"],\"instructions\":\"Record the locked script to camera (vertical, 30 fps, clean close-mic audio, written consent from everyone on camera), or synthesize the two voices with a voice licensed for publication. Record media path, sha256, duration and capture notes.\",\"outputs\":[\"deliverables/source/take.json\"],\"state\":\"blocked\",\"team\":\"explainers\",\"title\":\"Record or synthesize the take\"},{\"acceptance\":[\"Word times are monotonic and inside the take.\",\"Script agreement is reported; terms such as Sec-WebSocket-Key are spelled as in the script.\",\"Records the engine, model and settings.\"],\"assignee\":null,\"completed_evidence\":[],\"depends_on\":[\"source-take\"],\"id\":\"transcript\",\"inputs\":[\"deliverables/source/take.json\",\"starter/source/script.md\",\"starter/source/reference-words.json\",\"starter/tools/transcribe.mjs\"],\"instructions\":\"Transcribe the take with tools/transcribe.mjs, fix terms, reconcile against the script with --known, and publish words.json and transcript.txt on the source timeline.\",\"outputs\":[\"deliverables/transcript/words.json\",\"deliverables/transcript/transcript.txt\"],\"state\":\"blocked\",\"team\":\"transcription\",\"title\":\"Publish the word-timed transcript\"},{\"acceptance\":[\"Beats cover the whole cut without two overlays in one zone at once.\",\"Every technical overlay cites a claim key.\",\"No beat is placed in the face zone or the platform-safe areas.\"],\"assignee\":null,\"completed_evidence\":[],\"depends_on\":[\"brief\",\"transcript\",\"brand-kit\"],\"id\":\"motion-board\",\"inputs\":[\"deliverables/brief/BRIEF.md\",\"deliverables/transcript/words.json\",\"deliverables/brand/design.md\",\"starter/board/motion-board-template.md\",\"starter/source/claims.json\",\"starter/guides/effects.md\",\"starter/refs/handshake-panel.svg\",\"starter/refs/chat-window.svg\"],\"instructions\":\"In plan mode, write the timestamped motion board: the hook in the first three seconds, cuts in silence, one row per overlay with zone, effect, on-screen words, claim key and reference image, and where captions move.\",\"outputs\":[\"deliverables/board/motion-board.md\"],\"state\":\"blocked\",\"team\":\"motion-board\",\"title\":\"Plan the motion board\"},{\"acceptance\":[\"Records the decision, the reviewer and the board sha256.\"],\"assignee\":null,\"completed_evidence\":[],\"depends_on\":[\"motion-board\"],\"id\":\"board-approval\",\"inputs\":[\"deliverables/board/motion-board.md\",\"deliverables/brief/BRIEF.md\"],\"instructions\":\"Check the board against the brief, present it to the owner, and record the owner's decision with the board sha256, or return it with specific, testable changes. Building starts only after an approval record exists.\",\"outputs\":[\"deliverables/approvals/motion-board.md\"],\"state\":\"blocked\",\"team\":\"showrunner\",\"title\":\"Present the motion board and record the decision\"},{\"acceptance\":[\"caption-phraser.mjs --verify passes.\",\"No caption overlaps the face zone, the platform-safe areas or an active overlay.\"],\"assignee\":null,\"completed_evidence\":[],\"depends_on\":[\"board-approval\"],\"id\":\"captions\",\"inputs\":[\"deliverables/transcript/words.json\",\"deliverables/brand/tokens.json\",\"deliverables/board/motion-board.md\",\"deliverables/approvals/motion-board.md\",\"starter/tools/caption-phraser.mjs\"],\"instructions\":\"Run tools/caption-phraser.mjs on the transcript with the issued kit and the board's cuts, place phrases in the caption zone, move them where the board says an overlay owns that zone, and verify the trace.\",\"outputs\":[\"deliverables/captions/captions.json\"],\"state\":\"blocked\",\"team\":\"captions\",\"title\":\"Build the spatial captions\"},{\"acceptance\":[\"short.json lists every overlay with its board beat and zone.\",\"lint and check report zero errors.\",\"The hook overlay is on screen by 0.5 seconds.\"],\"assignee\":null,\"completed_evidence\":[],\"depends_on\":[\"captions\"],\"id\":\"motion-graphics\",\"inputs\":[\"deliverables/board/motion-board.md\",\"deliverables/approvals/motion-board.md\",\"deliverables/brand/tokens.json\",\"deliverables/captions/captions.json\",\"deliverables/source/take.json\",\"starter/project/hook-card.html\",\"starter/project/typing-window.html\",\"starter/project/ui-panel.html\"],\"instructions\":\"Create the HyperFrames project, cut the take, build each board beat from the starter overlays and the effect catalogue, wire the captions, and keep lint and check clean.\",\"outputs\":[\"deliverables/cut/short.json\"],\"state\":\"blocked\",\"team\":\"motion-graphics\",\"title\":\"Build the overlays and assemble the cut\"},{\"acceptance\":[\"Zero unmapped on-screen statements.\"],\"assignee\":null,\"completed_evidence\":[],\"depends_on\":[\"motion-graphics\"],\"id\":\"claims-carryover\",\"inputs\":[\"starter/source/claims.json\",\"deliverables/board/motion-board.md\",\"deliverables/cut/short.json\"],\"instructions\":\"Map every on-screen technical statement in the cut to a verified claim key and anchor. Anything unmapped is a new claim: return it instead of certifying.\",\"outputs\":[\"deliverables/claims/carryover.json\"],\"state\":\"blocked\",\"team\":\"explainers\",\"title\":\"Certify claim carryover\"},{\"acceptance\":[\"Every checklist row has a pass or a documented fail with evidence.\",\"The contact sheet shows one frame per board beat.\"],\"assignee\":null,\"completed_evidence\":[],\"depends_on\":[\"claims-carryover\"],\"id\":\"qc\",\"inputs\":[\"deliverables/cut/short.json\",\"deliverables/captions/captions.json\",\"deliverables/claims/carryover.json\",\"deliverables/approvals/motion-board.md\",\"starter/quality/review-checklist.csv\"],\"instructions\":\"Walk the review checklist: lint, check, duration, hook, caption trace and style, zones, claim carryover, loudness, and a contact sheet with one frame per beat compared against the approved board.\",\"outputs\":[\"deliverables/qc/report.json\",\"deliverables/qc/contact-sheet.png\"],\"state\":\"blocked\",\"team\":\"quality\",\"title\":\"Run the quality gates\"},{\"acceptance\":[\"Records the decision, the reviewer and the project commit.\"],\"assignee\":null,\"completed_evidence\":[],\"depends_on\":[\"qc\"],\"id\":\"final-approval\",\"inputs\":[\"deliverables/qc/report.json\",\"deliverables/qc/contact-sheet.png\"],\"instructions\":\"Present the QC report and contact sheet to the owner and record the decision with the project commit, or return the cut with specific notes.\",\"outputs\":[\"deliverables/approvals/final-cut.md\"],\"state\":\"blocked\",\"team\":\"showrunner\",\"title\":\"Present the final cut and record the decision\"},{\"acceptance\":[\"The MP4 is 1080x1920 H.264 between 30 and 45 seconds.\",\"PROVENANCE.json records every hash the case requires.\",\"Nothing was uploaded or published.\"],\"assignee\":null,\"completed_evidence\":[],\"depends_on\":[\"final-approval\"],\"id\":\"release-package\",\"inputs\":[\"deliverables/cut/short.json\",\"deliverables/approvals/final-cut.md\",\"deliverables/captions/captions.json\",\"starter/ops/release-checklist.md\"],\"instructions\":\"Render the approved commit from a clean checkout, export the SRT and a cover frame, draft the post copy, and write PROVENANCE.json. Prepare only: do not upload or publish.\",\"outputs\":[\"deliverables/release/short-1080x1920.mp4\",\"deliverables/release/captions.srt\",\"deliverables/release/cover.png\",\"deliverables/release/post.md\",\"deliverables/release/PROVENANCE.json\"],\"state\":\"blocked\",\"team\":\"release\",\"title\":\"Render and package the deliverables\"},{\"acceptance\":[\"Each proposed change cites the QC finding behind it.\"],\"assignee\":null,\"completed_evidence\":[],\"depends_on\":[\"release-package\"],\"id\":\"kit-retro\",\"inputs\":[\"deliverables/qc/report.json\",\"deliverables/release/PROVENANCE.json\"],\"instructions\":\"Read the QC report and provenance and propose concrete kit changes (caption style, zones, motion defaults, prompts) as a reviewed diff for the next production.\",\"outputs\":[\"deliverables/retro/kit-updates.md\"],\"state\":\"blocked\",\"team\":\"brand\",\"title\":\"Fold the lessons back into the kit\"}]}\n","path":"templates/casework/work/task-board.json","sha256":"ce6d3ad58533ae9f79e22c2e26dc46860098ea8efe34b51f4b2fbd827770b9a0"},{"bytes":434,"content":"# Brand Studio\n\nRole: brand-custodian\n\nOwns the reusable brand kit (type, colour, zones, caption style, motion language) that every production loads by name, and folds lessons from each release back into it.\n\nCase inputs and logical deliverables belong to the same-world casework workspace.\nRead only the inputs for assigned work; hand off verified artifact references.\nDo not claim another team's task or authorize external effects.\n","path":"templates/teams/brand/work/TEAM.md","sha256":"205eda1d53fff7a762109b0e805d739960046b329295e1727eb54ebd05b5b95e"},{"bytes":1358,"content":"{\"case_workspace\":\"casework\",\"schema\":\"hive-hub-seed-team-work/1\",\"tasks\":[{\"acceptance\":[\"check-kit.mjs passes on the issued kit.\",\"Every font is a licensed file with its licence text.\"],\"assignee\":null,\"completed_evidence\":[],\"depends_on\":[\"brief\"],\"id\":\"brand-kit\",\"inputs\":[\"starter/brand/house-kit.md\",\"starter/brand/tokens.json\",\"deliverables/brief/BRIEF.md\",\"starter/tools/check-kit.mjs\"],\"instructions\":\"Resolve the house kit for this production: download the OFL fonts with their licences, confirm zones for the chosen layout (talking head, or source card in place of the face zone), and issue design.md and tokens.json. Run tools/check-kit.mjs.\",\"outputs\":[\"deliverables/brand/design.md\",\"deliverables/brand/tokens.json\"],\"state\":\"blocked\",\"team\":\"brand\",\"title\":\"Issue the house kit\"},{\"acceptance\":[\"Each proposed change cites the QC finding behind it.\"],\"assignee\":null,\"completed_evidence\":[],\"depends_on\":[\"release-package\"],\"id\":\"kit-retro\",\"inputs\":[\"deliverables/qc/report.json\",\"deliverables/release/PROVENANCE.json\"],\"instructions\":\"Read the QC report and provenance and propose concrete kit changes (caption style, zones, motion defaults, prompts) as a reviewed diff for the next production.\",\"outputs\":[\"deliverables/retro/kit-updates.md\"],\"state\":\"blocked\",\"team\":\"brand\",\"title\":\"Fold the lessons back into the kit\"}],\"team\":\"brand\"}\n","path":"templates/teams/brand/work/tasks.json","sha256":"aeb34a6beb0642c53e7eb766715a650fe4d651f970a8f4d2dbd2b4383a01a36e"},{"bytes":448,"content":"# Captions Desk\n\nRole: caption-editor\n\nTurns the transcript into short unpunctuated caption phrases, places them in free space away from the face and the overlays, and keeps every caption word traceable to the transcript.\n\nCase inputs and logical deliverables belong to the same-world casework workspace.\nRead only the inputs for assigned work; hand off verified artifact references.\nDo not claim another team's task or authorize external effects.\n","path":"templates/teams/captions/work/TEAM.md","sha256":"0c180d9ff04174cca0a7ead52796ebd4a7079e6f2497ddc7c804f69632dc1f0d"},{"bytes":862,"content":"{\"case_workspace\":\"casework\",\"schema\":\"hive-hub-seed-team-work/1\",\"tasks\":[{\"acceptance\":[\"caption-phraser.mjs --verify passes.\",\"No caption overlaps the face zone, the platform-safe areas or an active overlay.\"],\"assignee\":null,\"completed_evidence\":[],\"depends_on\":[\"board-approval\"],\"id\":\"captions\",\"inputs\":[\"deliverables/transcript/words.json\",\"deliverables/brand/tokens.json\",\"deliverables/board/motion-board.md\",\"deliverables/approvals/motion-board.md\",\"starter/tools/caption-phraser.mjs\"],\"instructions\":\"Run tools/caption-phraser.mjs on the transcript with the issued kit and the board's cuts, place phrases in the caption zone, move them where the board says an overlay owns that zone, and verify the trace.\",\"outputs\":[\"deliverables/captions/captions.json\"],\"state\":\"blocked\",\"team\":\"captions\",\"title\":\"Build the spatial captions\"}],\"team\":\"captions\"}\n","path":"templates/teams/captions/work/tasks.json","sha256":"24af1c83ac18d528ac576f30968ee73ac1a1f23e33f4288ed978534960752180"},{"bytes":445,"content":"# Explainer Lab\n\nRole: explainer-producer\n\nOwns scripts, sources and truth: locks each script against a pinned corpus and claim ledger, records or synthesizes the take, and certifies that every cut asserts nothing new.\n\nCase inputs and logical deliverables belong to the same-world casework workspace.\nRead only the inputs for assigned work; hand off verified artifact references.\nDo not claim another team's task or authorize external effects.\n","path":"templates/teams/explainers/work/TEAM.md","sha256":"f5c8038a93a8730ce5a02d66ceab5b9a5a844a8a20b703442bbbe8aa10722a9d"},{"bytes":2115,"content":"{\"case_workspace\":\"casework\",\"schema\":\"hive-hub-seed-team-work/1\",\"tasks\":[{\"acceptance\":[\"The corpus sha256 matches source/corpus.json.\",\"Every anchor verifies and every factual line has a verified claim key.\",\"Records the sha256 of script.md and claims.json.\"],\"assignee\":null,\"completed_evidence\":[],\"depends_on\":[],\"id\":\"script-lock\",\"inputs\":[\"starter/source/script.md\",\"starter/source/claims.json\",\"starter/source/corpus.json\"],\"instructions\":\"Fetch the pinned RFC 6455 text, confirm its sha256, confirm every claim anchor's snippet sits inside its line range, and confirm every factual script line maps to a verified claim. Record the script and ledger sha256.\",\"outputs\":[\"deliverables/script/script-lock.json\"],\"state\":\"ready\",\"team\":\"explainers\",\"title\":\"Lock the script against the claim ledger\"},{\"acceptance\":[\"The take speaks the locked script; deviations are listed.\",\"Records sha256, duration, layout option, and consent or voice licence.\"],\"assignee\":null,\"completed_evidence\":[],\"depends_on\":[\"script-lock\",\"brief\"],\"id\":\"source-take\",\"inputs\":[\"deliverables/script/script-lock.json\",\"deliverables/brief/BRIEF.md\",\"starter/source/script.md\"],\"instructions\":\"Record the locked script to camera (vertical, 30 fps, clean close-mic audio, written consent from everyone on camera), or synthesize the two voices with a voice licensed for publication. Record media path, sha256, duration and capture notes.\",\"outputs\":[\"deliverables/source/take.json\"],\"state\":\"blocked\",\"team\":\"explainers\",\"title\":\"Record or synthesize the take\"},{\"acceptance\":[\"Zero unmapped on-screen statements.\"],\"assignee\":null,\"completed_evidence\":[],\"depends_on\":[\"motion-graphics\"],\"id\":\"claims-carryover\",\"inputs\":[\"starter/source/claims.json\",\"deliverables/board/motion-board.md\",\"deliverables/cut/short.json\"],\"instructions\":\"Map every on-screen technical statement in the cut to a verified claim key and anchor. Anything unmapped is a new claim: return it instead of certifying.\",\"outputs\":[\"deliverables/claims/carryover.json\"],\"state\":\"blocked\",\"team\":\"explainers\",\"title\":\"Certify claim carryover\"}],\"team\":\"explainers\"}\n","path":"templates/teams/explainers/work/tasks.json","sha256":"d9872507748d60bfe73d8cd6cb1f4ff9924097c82b80fad20bdbc4bd57352d19"},{"bytes":467,"content":"# Motion Board Room\n\nRole: board-planner\n\nPlans each edit in plan mode: a timestamped motion board with the hook, cuts, every overlay beat with zone, effect, words and claim key, and the caption moves. Nothing is built until it is approved.\n\nCase inputs and logical deliverables belong to the same-world casework workspace.\nRead only the inputs for assigned work; hand off verified artifact references.\nDo not claim another team's task or authorize external effects.\n","path":"templates/teams/motion-board/work/TEAM.md","sha256":"20fb299bbf9e7a323087b7f52ab456614a508bc8a3d3ceafe87f15da7d0c88d4"},{"bytes":1031,"content":"{\"case_workspace\":\"casework\",\"schema\":\"hive-hub-seed-team-work/1\",\"tasks\":[{\"acceptance\":[\"Beats cover the whole cut without two overlays in one zone at once.\",\"Every technical overlay cites a claim key.\",\"No beat is placed in the face zone or the platform-safe areas.\"],\"assignee\":null,\"completed_evidence\":[],\"depends_on\":[\"brief\",\"transcript\",\"brand-kit\"],\"id\":\"motion-board\",\"inputs\":[\"deliverables/brief/BRIEF.md\",\"deliverables/transcript/words.json\",\"deliverables/brand/design.md\",\"starter/board/motion-board-template.md\",\"starter/source/claims.json\",\"starter/guides/effects.md\",\"starter/refs/handshake-panel.svg\",\"starter/refs/chat-window.svg\"],\"instructions\":\"In plan mode, write the timestamped motion board: the hook in the first three seconds, cuts in silence, one row per overlay with zone, effect, on-screen words, claim key and reference image, and where captions move.\",\"outputs\":[\"deliverables/board/motion-board.md\"],\"state\":\"blocked\",\"team\":\"motion-board\",\"title\":\"Plan the motion board\"}],\"team\":\"motion-board\"}\n","path":"templates/teams/motion-board/work/tasks.json","sha256":"5e83fbf5fb269c2756edef34375df4ade34b79801553c3d25ac69f390e606883"},{"bytes":450,"content":"# Motion Graphics Shop\n\nRole: motion-designer\n\nBuilds the approved board as seek-safe HyperFrames compositions: hook cards, animated UI panels, live-typing windows, layer scans and punch-ins, on brand and clear of the face.\n\nCase inputs and logical deliverables belong to the same-world casework workspace.\nRead only the inputs for assigned work; hand off verified artifact references.\nDo not claim another team's task or authorize external effects.\n","path":"templates/teams/motion-graphics/work/TEAM.md","sha256":"beb44c482c7edb9593067a838e22c3ef7615a66f33e75eeae10c25c2283106a5"},{"bytes":972,"content":"{\"case_workspace\":\"casework\",\"schema\":\"hive-hub-seed-team-work/1\",\"tasks\":[{\"acceptance\":[\"short.json lists every overlay with its board beat and zone.\",\"lint and check report zero errors.\",\"The hook overlay is on screen by 0.5 seconds.\"],\"assignee\":null,\"completed_evidence\":[],\"depends_on\":[\"captions\"],\"id\":\"motion-graphics\",\"inputs\":[\"deliverables/board/motion-board.md\",\"deliverables/approvals/motion-board.md\",\"deliverables/brand/tokens.json\",\"deliverables/captions/captions.json\",\"deliverables/source/take.json\",\"starter/project/hook-card.html\",\"starter/project/typing-window.html\",\"starter/project/ui-panel.html\"],\"instructions\":\"Create the HyperFrames project, cut the take, build each board beat from the starter overlays and the effect catalogue, wire the captions, and keep lint and check clean.\",\"outputs\":[\"deliverables/cut/short.json\"],\"state\":\"blocked\",\"team\":\"motion-graphics\",\"title\":\"Build the overlays and assemble the cut\"}],\"team\":\"motion-graphics\"}\n","path":"templates/teams/motion-graphics/work/tasks.json","sha256":"a7e64b44d2837b22b86053df025280f11c9e9abdecfa9af0e979cafdb9c502e9"},{"bytes":454,"content":"# Quality Control\n\nRole: reviewer\n\nGates every cut: lint and check clean, caption trace and style, face-safe placement, hook timing, duration and loudness, claim carryover, and a contact-sheet review against the approved board.\n\nCase inputs and logical deliverables belong to the same-world casework workspace.\nRead only the inputs for assigned work; hand off verified artifact references.\nDo not claim another team's task or authorize external effects.\n","path":"templates/teams/quality/work/TEAM.md","sha256":"caa274edbeb3cf0cdad7180405a7a9c3de2cae695aa805e3f2d9eeb398926b95"},{"bytes":870,"content":"{\"case_workspace\":\"casework\",\"schema\":\"hive-hub-seed-team-work/1\",\"tasks\":[{\"acceptance\":[\"Every checklist row has a pass or a documented fail with evidence.\",\"The contact sheet shows one frame per board beat.\"],\"assignee\":null,\"completed_evidence\":[],\"depends_on\":[\"claims-carryover\"],\"id\":\"qc\",\"inputs\":[\"deliverables/cut/short.json\",\"deliverables/captions/captions.json\",\"deliverables/claims/carryover.json\",\"deliverables/approvals/motion-board.md\",\"starter/quality/review-checklist.csv\"],\"instructions\":\"Walk the review checklist: lint, check, duration, hook, caption trace and style, zones, claim carryover, loudness, and a contact sheet with one frame per beat compared against the approved board.\",\"outputs\":[\"deliverables/qc/report.json\",\"deliverables/qc/contact-sheet.png\"],\"state\":\"blocked\",\"team\":\"quality\",\"title\":\"Run the quality gates\"}],\"team\":\"quality\"}\n","path":"templates/teams/quality/work/tasks.json","sha256":"913d22fcd40064228995f0f3c18b7e4addb230a95e2e171d11a2ede4cc316ad8"},{"bytes":415,"content":"# Release Desk\n\nRole: release-manager\n\nRenders the approved cut and packages the vertical MP4, caption file, cover frame, post copy and provenance. Prepares publication; never performs it.\n\nCase inputs and logical deliverables belong to the same-world casework workspace.\nRead only the inputs for assigned work; hand off verified artifact references.\nDo not claim another team's task or authorize external effects.\n","path":"templates/teams/release/work/TEAM.md","sha256":"ecfe97e6c4b81aeff1fa4f9c4419ff3da62909f76f876b45c1117b6c3475cc8a"},{"bytes":971,"content":"{\"case_workspace\":\"casework\",\"schema\":\"hive-hub-seed-team-work/1\",\"tasks\":[{\"acceptance\":[\"The MP4 is 1080x1920 H.264 between 30 and 45 seconds.\",\"PROVENANCE.json records every hash the case requires.\",\"Nothing was uploaded or published.\"],\"assignee\":null,\"completed_evidence\":[],\"depends_on\":[\"final-approval\"],\"id\":\"release-package\",\"inputs\":[\"deliverables/cut/short.json\",\"deliverables/approvals/final-cut.md\",\"deliverables/captions/captions.json\",\"starter/ops/release-checklist.md\"],\"instructions\":\"Render the approved commit from a clean checkout, export the SRT and a cover frame, draft the post copy, and write PROVENANCE.json. Prepare only: do not upload or publish.\",\"outputs\":[\"deliverables/release/short-1080x1920.mp4\",\"deliverables/release/captions.srt\",\"deliverables/release/cover.png\",\"deliverables/release/post.md\",\"deliverables/release/PROVENANCE.json\"],\"state\":\"blocked\",\"team\":\"release\",\"title\":\"Render and package the deliverables\"}],\"team\":\"release\"}\n","path":"templates/teams/release/work/tasks.json","sha256":"f0239b8d6ef083e668596be4843ba2a6c22292164383abd479b623fe8f9de515"},{"bytes":529,"content":"# Showrunner's Office\n\nRole: showrunner\n\nOwns the brief, routes work across the teams, presents the motion board and the final cut to the owner and records the decisions, and keeps the production board current. The owner's own AI is the front door; this office is a stage behind it, not a separate bot.\n\nCase inputs and logical deliverables belong to the same-world casework workspace.\nRead only the inputs for assigned work; hand off verified artifact references.\nDo not claim another team's task or authorize external effects.\n","path":"templates/teams/showrunner/work/TEAM.md","sha256":"6339d9e25c48988147d888ae42566a12e37e0f6e4edbff4e7d0aaeef8760d56f"},{"bytes":1827,"content":"{\"case_workspace\":\"casework\",\"schema\":\"hive-hub-seed-team-work/1\",\"tasks\":[{\"acceptance\":[\"Names the hook, the target length, the source option and the kit.\",\"States that publication is out of scope.\"],\"assignee\":null,\"completed_evidence\":[],\"depends_on\":[],\"id\":\"brief\",\"inputs\":[\"starter/case/pilot.json\"],\"instructions\":\"Turn the case into BRIEF.md: audience, platform, 30 to 45 second target, the hook, the source option (talking head or synthesized), the kit, the gates and who signs each, and what is out of scope.\",\"outputs\":[\"deliverables/brief/BRIEF.md\"],\"state\":\"ready\",\"team\":\"showrunner\",\"title\":\"Write the production brief\"},{\"acceptance\":[\"Records the decision, the reviewer and the board sha256.\"],\"assignee\":null,\"completed_evidence\":[],\"depends_on\":[\"motion-board\"],\"id\":\"board-approval\",\"inputs\":[\"deliverables/board/motion-board.md\",\"deliverables/brief/BRIEF.md\"],\"instructions\":\"Check the board against the brief, present it to the owner, and record the owner's decision with the board sha256, or return it with specific, testable changes. Building starts only after an approval record exists.\",\"outputs\":[\"deliverables/approvals/motion-board.md\"],\"state\":\"blocked\",\"team\":\"showrunner\",\"title\":\"Present the motion board and record the decision\"},{\"acceptance\":[\"Records the decision, the reviewer and the project commit.\"],\"assignee\":null,\"completed_evidence\":[],\"depends_on\":[\"qc\"],\"id\":\"final-approval\",\"inputs\":[\"deliverables/qc/report.json\",\"deliverables/qc/contact-sheet.png\"],\"instructions\":\"Present the QC report and contact sheet to the owner and record the decision with the project commit, or return the cut with specific notes.\",\"outputs\":[\"deliverables/approvals/final-cut.md\"],\"state\":\"blocked\",\"team\":\"showrunner\",\"title\":\"Present the final cut and record the decision\"}],\"team\":\"showrunner\"}\n","path":"templates/teams/showrunner/work/tasks.json","sha256":"7d33ba52f68155ef412348a0bd5345e53970cf8305fd12a102222e1b31c5734b"},{"bytes":455,"content":"# Transcription Desk\n\nRole: transcriber\n\nProduces the word-timed transcript for every take (ElevenLabs Scribe or local Whisper), fixes names and terms, reconciles against the script, and publishes words.json as the timing truth.\n\nCase inputs and logical deliverables belong to the same-world casework workspace.\nRead only the inputs for assigned work; hand off verified artifact references.\nDo not claim another team's task or authorize external effects.\n","path":"templates/teams/transcription/work/TEAM.md","sha256":"2af1dee4610c14ac5091a01d4f98f309f0147c936d9c491409e7cc1baa55c96c"},{"bytes":886,"content":"{\"case_workspace\":\"casework\",\"schema\":\"hive-hub-seed-team-work/1\",\"tasks\":[{\"acceptance\":[\"Word times are monotonic and inside the take.\",\"Script agreement is reported; terms such as Sec-WebSocket-Key are spelled as in the script.\",\"Records the engine, model and settings.\"],\"assignee\":null,\"completed_evidence\":[],\"depends_on\":[\"source-take\"],\"id\":\"transcript\",\"inputs\":[\"deliverables/source/take.json\",\"starter/source/script.md\",\"starter/source/reference-words.json\",\"starter/tools/transcribe.mjs\"],\"instructions\":\"Transcribe the take with tools/transcribe.mjs, fix terms, reconcile against the script with --known, and publish words.json and transcript.txt on the source timeline.\",\"outputs\":[\"deliverables/transcript/words.json\",\"deliverables/transcript/transcript.txt\"],\"state\":\"blocked\",\"team\":\"transcription\",\"title\":\"Publish the word-timed transcript\"}],\"team\":\"transcription\"}\n","path":"templates/teams/transcription/work/tasks.json","sha256":"8556e1404abc7a0e78bbd2a190ef656d7003f153f46b65f3f7861a72b124436f"}],"kind":"organization-seed","mission":"Run a nine-team video studio behind one AI front door. Every edit follows the same loop: a word-timed transcript, a timestamped motion board approved before anything is built, HyperFrames compositions for motion graphics and captions, hard quality gates, and a release package with provenance. Technical statements come from a pinned source and a claim ledger, and a derived cut never asserts more than its source. Publication stays with the owner.","name":"The AI Video Studio","organization":{"directory":"organization","scaffold":{"kind":"organization","mode":"hive","slug":"ai-video-studio","world_id":"ai-video-studio"}},"protocol":"rapp-work/1","relatedSeeds":["product-launch-company"],"schema":"hive-hub-organization-seed/1","slug":"ai-video-studio","status":"seed-not-activated","tagline":"Talking-head shorts and claim-checked explainers, planned on a motion board and built as code.","tasks":[{"acceptance":["Names the hook, the target length, the source option and the kit.","States that publication is out of scope."],"assignee":null,"completed_evidence":[],"depends_on":[],"id":"brief","inputs":["starter/case/pilot.json"],"instructions":"Turn the case into BRIEF.md: audience, platform, 30 to 45 second target, the hook, the source option (talking head or synthesized), the kit, the gates and who signs each, and what is out of scope.","outputs":["deliverables/brief/BRIEF.md"],"state":"ready","team":"showrunner","title":"Write the production brief"},{"acceptance":["The corpus sha256 matches source/corpus.json.","Every anchor verifies and every factual line has a verified claim key.","Records the sha256 of script.md and claims.json."],"assignee":null,"completed_evidence":[],"depends_on":[],"id":"script-lock","inputs":["starter/source/script.md","starter/source/claims.json","starter/source/corpus.json"],"instructions":"Fetch the pinned RFC 6455 text, confirm its sha256, confirm every claim anchor's snippet sits inside its line range, and confirm every factual script line maps to a verified claim. Record the script and ledger sha256.","outputs":["deliverables/script/script-lock.json"],"state":"ready","team":"explainers","title":"Lock the script against the claim ledger"},{"acceptance":["check-kit.mjs passes on the issued kit.","Every font is a licensed file with its licence text."],"assignee":null,"completed_evidence":[],"depends_on":["brief"],"id":"brand-kit","inputs":["starter/brand/house-kit.md","starter/brand/tokens.json","deliverables/brief/BRIEF.md","starter/tools/check-kit.mjs"],"instructions":"Resolve the house kit for this production: download the OFL fonts with their licences, confirm zones for the chosen layout (talking head, or source card in place of the face zone), and issue design.md and tokens.json. Run tools/check-kit.mjs.","outputs":["deliverables/brand/design.md","deliverables/brand/tokens.json"],"state":"blocked","team":"brand","title":"Issue the house kit"},{"acceptance":["The take speaks the locked script; deviations are listed.","Records sha256, duration, layout option, and consent or voice licence."],"assignee":null,"completed_evidence":[],"depends_on":["script-lock","brief"],"id":"source-take","inputs":["deliverables/script/script-lock.json","deliverables/brief/BRIEF.md","starter/source/script.md"],"instructions":"Record the locked script to camera (vertical, 30 fps, clean close-mic audio, written consent from everyone on camera), or synthesize the two voices with a voice licensed for publication. Record media path, sha256, duration and capture notes.","outputs":["deliverables/source/take.json"],"state":"blocked","team":"explainers","title":"Record or synthesize the take"},{"acceptance":["Word times are monotonic and inside the take.","Script agreement is reported; terms such as Sec-WebSocket-Key are spelled as in the script.","Records the engine, model and settings."],"assignee":null,"completed_evidence":[],"depends_on":["source-take"],"id":"transcript","inputs":["deliverables/source/take.json","starter/source/script.md","starter/source/reference-words.json","starter/tools/transcribe.mjs"],"instructions":"Transcribe the take with tools/transcribe.mjs, fix terms, reconcile against the script with --known, and publish words.json and transcript.txt on the source timeline.","outputs":["deliverables/transcript/words.json","deliverables/transcript/transcript.txt"],"state":"blocked","team":"transcription","title":"Publish the word-timed transcript"},{"acceptance":["Beats cover the whole cut without two overlays in one zone at once.","Every technical overlay cites a claim key.","No beat is placed in the face zone or the platform-safe areas."],"assignee":null,"completed_evidence":[],"depends_on":["brief","transcript","brand-kit"],"id":"motion-board","inputs":["deliverables/brief/BRIEF.md","deliverables/transcript/words.json","deliverables/brand/design.md","starter/board/motion-board-template.md","starter/source/claims.json","starter/guides/effects.md","starter/refs/handshake-panel.svg","starter/refs/chat-window.svg"],"instructions":"In plan mode, write the timestamped motion board: the hook in the first three seconds, cuts in silence, one row per overlay with zone, effect, on-screen words, claim key and reference image, and where captions move.","outputs":["deliverables/board/motion-board.md"],"state":"blocked","team":"motion-board","title":"Plan the motion board"},{"acceptance":["Records the decision, the reviewer and the board sha256."],"assignee":null,"completed_evidence":[],"depends_on":["motion-board"],"id":"board-approval","inputs":["deliverables/board/motion-board.md","deliverables/brief/BRIEF.md"],"instructions":"Check the board against the brief, present it to the owner, and record the owner's decision with the board sha256, or return it with specific, testable changes. Building starts only after an approval record exists.","outputs":["deliverables/approvals/motion-board.md"],"state":"blocked","team":"showrunner","title":"Present the motion board and record the decision"},{"acceptance":["caption-phraser.mjs --verify passes.","No caption overlaps the face zone, the platform-safe areas or an active overlay."],"assignee":null,"completed_evidence":[],"depends_on":["board-approval"],"id":"captions","inputs":["deliverables/transcript/words.json","deliverables/brand/tokens.json","deliverables/board/motion-board.md","deliverables/approvals/motion-board.md","starter/tools/caption-phraser.mjs"],"instructions":"Run tools/caption-phraser.mjs on the transcript with the issued kit and the board's cuts, place phrases in the caption zone, move them where the board says an overlay owns that zone, and verify the trace.","outputs":["deliverables/captions/captions.json"],"state":"blocked","team":"captions","title":"Build the spatial captions"},{"acceptance":["short.json lists every overlay with its board beat and zone.","lint and check report zero errors.","The hook overlay is on screen by 0.5 seconds."],"assignee":null,"completed_evidence":[],"depends_on":["captions"],"id":"motion-graphics","inputs":["deliverables/board/motion-board.md","deliverables/approvals/motion-board.md","deliverables/brand/tokens.json","deliverables/captions/captions.json","deliverables/source/take.json","starter/project/hook-card.html","starter/project/typing-window.html","starter/project/ui-panel.html"],"instructions":"Create the HyperFrames project, cut the take, build each board beat from the starter overlays and the effect catalogue, wire the captions, and keep lint and check clean.","outputs":["deliverables/cut/short.json"],"state":"blocked","team":"motion-graphics","title":"Build the overlays and assemble the cut"},{"acceptance":["Zero unmapped on-screen statements."],"assignee":null,"completed_evidence":[],"depends_on":["motion-graphics"],"id":"claims-carryover","inputs":["starter/source/claims.json","deliverables/board/motion-board.md","deliverables/cut/short.json"],"instructions":"Map every on-screen technical statement in the cut to a verified claim key and anchor. Anything unmapped is a new claim: return it instead of certifying.","outputs":["deliverables/claims/carryover.json"],"state":"blocked","team":"explainers","title":"Certify claim carryover"},{"acceptance":["Every checklist row has a pass or a documented fail with evidence.","The contact sheet shows one frame per board beat."],"assignee":null,"completed_evidence":[],"depends_on":["claims-carryover"],"id":"qc","inputs":["deliverables/cut/short.json","deliverables/captions/captions.json","deliverables/claims/carryover.json","deliverables/approvals/motion-board.md","starter/quality/review-checklist.csv"],"instructions":"Walk the review checklist: lint, check, duration, hook, caption trace and style, zones, claim carryover, loudness, and a contact sheet with one frame per beat compared against the approved board.","outputs":["deliverables/qc/report.json","deliverables/qc/contact-sheet.png"],"state":"blocked","team":"quality","title":"Run the quality gates"},{"acceptance":["Records the decision, the reviewer and the project commit."],"assignee":null,"completed_evidence":[],"depends_on":["qc"],"id":"final-approval","inputs":["deliverables/qc/report.json","deliverables/qc/contact-sheet.png"],"instructions":"Present the QC report and contact sheet to the owner and record the decision with the project commit, or return the cut with specific notes.","outputs":["deliverables/approvals/final-cut.md"],"state":"blocked","team":"showrunner","title":"Present the final cut and record the decision"},{"acceptance":["The MP4 is 1080x1920 H.264 between 30 and 45 seconds.","PROVENANCE.json records every hash the case requires.","Nothing was uploaded or published."],"assignee":null,"completed_evidence":[],"depends_on":["final-approval"],"id":"release-package","inputs":["deliverables/cut/short.json","deliverables/approvals/final-cut.md","deliverables/captions/captions.json","starter/ops/release-checklist.md"],"instructions":"Render the approved commit from a clean checkout, export the SRT and a cover frame, draft the post copy, and write PROVENANCE.json. Prepare only: do not upload or publish.","outputs":["deliverables/release/short-1080x1920.mp4","deliverables/release/captions.srt","deliverables/release/cover.png","deliverables/release/post.md","deliverables/release/PROVENANCE.json"],"state":"blocked","team":"release","title":"Render and package the deliverables"},{"acceptance":["Each proposed change cites the QC finding behind it."],"assignee":null,"completed_evidence":[],"depends_on":["release-package"],"id":"kit-retro","inputs":["deliverables/qc/report.json","deliverables/release/PROVENANCE.json"],"instructions":"Read the QC report and provenance and propose concrete kit changes (caption style, zones, motion defaults, prompts) as a reviewed diff for the next production.","outputs":["deliverables/retro/kit-updates.md"],"state":"blocked","team":"brand","title":"Fold the lessons back into the kit"}],"workspaceProfile":"rapp-work-sdk/1","workspaces":[{"directory":"workspaces/showrunner","id":"showrunner","name":"Showrunner's Office","purpose":"Owns the brief, routes work across the teams, presents the motion board and the final cut to the owner and records the decisions, and keeps the production board current. The owner's own AI is the front door; this office is a stage behind it, not a separate bot.","role":"showrunner","scaffold":{"kind":"workspace","mode":"hive","slug":"ai-video-studio-showrunner","world_id":"ai-video-studio"},"template":"templates/teams/showrunner"},{"directory":"workspaces/brand","id":"brand","name":"Brand Studio","purpose":"Owns the reusable brand kit (type, colour, zones, caption style, motion language) that every production loads by name, and folds lessons from each release back into it.","role":"brand-custodian","scaffold":{"kind":"workspace","mode":"hive","slug":"ai-video-studio-brand","world_id":"ai-video-studio"},"template":"templates/teams/brand"},{"directory":"workspaces/transcription","id":"transcription","name":"Transcription Desk","purpose":"Produces the word-timed transcript for every take (ElevenLabs Scribe or local Whisper), fixes names and terms, reconciles against the script, and publishes words.json as the timing truth.","role":"transcriber","scaffold":{"kind":"workspace","mode":"hive","slug":"ai-video-studio-transcription","world_id":"ai-video-studio"},"template":"templates/teams/transcription"},{"directory":"workspaces/motion-board","id":"motion-board","name":"Motion Board Room","purpose":"Plans each edit in plan mode: a timestamped motion board with the hook, cuts, every overlay beat with zone, effect, words and claim key, and the caption moves. Nothing is built until it is approved.","role":"board-planner","scaffold":{"kind":"workspace","mode":"hive","slug":"ai-video-studio-motion-board","world_id":"ai-video-studio"},"template":"templates/teams/motion-board"},{"directory":"workspaces/motion-graphics","id":"motion-graphics","name":"Motion Graphics Shop","purpose":"Builds the approved board as seek-safe HyperFrames compositions: hook cards, animated UI panels, live-typing windows, layer scans and punch-ins, on brand and clear of the face.","role":"motion-designer","scaffold":{"kind":"workspace","mode":"hive","slug":"ai-video-studio-motion-graphics","world_id":"ai-video-studio"},"template":"templates/teams/motion-graphics"},{"directory":"workspaces/captions","id":"captions","name":"Captions Desk","purpose":"Turns the transcript into short unpunctuated caption phrases, places them in free space away from the face and the overlays, and keeps every caption word traceable to the transcript.","role":"caption-editor","scaffold":{"kind":"workspace","mode":"hive","slug":"ai-video-studio-captions","world_id":"ai-video-studio"},"template":"templates/teams/captions"},{"directory":"workspaces/explainers","id":"explainers","name":"Explainer Lab","purpose":"Owns scripts, sources and truth: locks each script against a pinned corpus and claim ledger, records or synthesizes the take, and certifies that every cut asserts nothing new.","role":"explainer-producer","scaffold":{"kind":"workspace","mode":"hive","slug":"ai-video-studio-explainers","world_id":"ai-video-studio"},"template":"templates/teams/explainers"},{"directory":"workspaces/quality","id":"quality","name":"Quality Control","purpose":"Gates every cut: lint and check clean, caption trace and style, face-safe placement, hook timing, duration and loudness, claim carryover, and a contact-sheet review against the approved board.","role":"reviewer","scaffold":{"kind":"workspace","mode":"hive","slug":"ai-video-studio-quality","world_id":"ai-video-studio"},"template":"templates/teams/quality"},{"directory":"workspaces/release","id":"release","name":"Release Desk","purpose":"Renders the approved cut and packages the vertical MP4, caption file, cover frame, post copy and provenance. Prepares publication; never performs it.","role":"release-manager","scaffold":{"kind":"workspace","mode":"hive","slug":"ai-video-studio-release","world_id":"ai-video-studio"},"template":"templates/teams/release"},{"directory":"workspaces/casework","id":"casework","name":"Shared delivery case","purpose":"Own synthetic intake, shared inputs, and accepted deliverables.","role":"case-custodian","scaffold":{"kind":"workspace","mode":"hive","slug":"ai-video-studio-casework","world_id":"ai-video-studio"},"template":"templates/casework"}]}
