diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 6db8a04..72eed24 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -5,6 +5,7 @@ on: branches: [master] paths: - "site/**" + - "docs/demo/risk-gate-demo-*" - ".github/workflows/pages.yml" workflow_dispatch: diff --git a/.gitignore b/.gitignore index 020fd10..e5b0fc1 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,10 @@ /coverage/ *.profraw *.profdata + +# Agent and tool state +.ai-jail +.claude-flow/ +.swarm/ +ruvector.db +__pycache__/ diff --git a/README.md b/README.md index df1861a..f1867d7 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,17 @@ idempotent report comment. It does not replace technical review. It makes likely impact explicit and applies a policy proportional to the affected area's criticality. +## Demo + +[](docs/demo/risk-gate-demo-en.mp4) + +Watch the 64-second demo ([MP4](docs/demo/risk-gate-demo-en.mp4)): configure +`.risk-gate.yaml`, add the Action, open a pull request, and follow the report +from cascade impact to merge. Also available in +[Português (Brasil)](docs/demo/risk-gate-demo-pt-BR.mp4) and +[Español](docs/demo/risk-gate-demo-es.mp4). See [docs/demo](docs/demo/README.md) +for how the video was made and how to re-render it. + ## How the analysis works For a PR execution, Risk Gate: diff --git a/docs/README.md b/docs/README.md index 98bceae..5f5664f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -73,4 +73,5 @@ cargo run -- --ast-impact --root /path/to/repository + --repository owner/repos ~~~ The implementation plan is in implementation-plan.md. Quality commands and -coverage/mutation requirements are in quality.md. +coverage/mutation requirements are in quality.md. Demo videos in English, +Portuguese, and Spanish, with their HyperFrames source, are in demo/. diff --git a/docs/demo/README.md b/docs/demo/README.md new file mode 100644 index 0000000..c7821f1 --- /dev/null +++ b/docs/demo/README.md @@ -0,0 +1,65 @@ +# Risk Gate demo videos + +A 64-second walkthrough of Risk Gate running as a GitHub Actions step: +versioning `.risk-gate.yaml`, adding the workflow, opening a pull request, +watching the gate fail on a cascade into payments, reading the report, and +merging after a fix. + +| Language | Video | Poster | +| --- | --- | --- | +| English | [risk-gate-demo-en.mp4](risk-gate-demo-en.mp4) | [risk-gate-demo-en.jpg](risk-gate-demo-en.jpg) | +| Português (Brasil) | [risk-gate-demo-pt-BR.mp4](risk-gate-demo-pt-BR.mp4) | [risk-gate-demo-pt-BR.jpg](risk-gate-demo-pt-BR.jpg) | +| Español | [risk-gate-demo-es.mp4](risk-gate-demo-es.mp4) | [risk-gate-demo-es.jpg](risk-gate-demo-es.jpg) | + +Only the captions, intro, and closing card are translated. The GitHub +screens, code, logs, and report stay in English because they reproduce what +users see. + +## What is real and what is staged + +- The report comment, the Actions log, and the cascade diagram come from a + real run of the `risk-gate` v0.1.0 release binary (`--base main --head HEAD`) + on a fixture repository (`acme/storefront`, 40 files) whose PR changes + `src/api/checkout.ts`, which imports `src/payment/charge.ts`. The captured + Markdown is `video/assets/data/report-failed.md`. Validation timings are + those of the fixture's scripts. +- The `.risk-gate.yaml` and workflow shown in the video are the fixture's + files and match the configuration contract in `specs/configuration.md`. +- The GitHub interface is rebuilt in HTML for the video; it is not a screen + recording. The merge box assumes branch protection that requires the Risk + Gate check, which Risk Gate itself does not configure. +- After the fix commit the video shows only the passing check and the merge + box. In the fixture the re-run reports `Status: PARTIAL` with + `Merge blocked: false`, because non-code files such as `.risk-gate.yaml` and + shell scripts are listed as unanalyzed; the updated comment is not shown. +- Music and sound effects are synthesized by `video/scripts/make_audio.py` + with a fixed seed, so they carry no third-party licence. + +## Rebuilding + +The source is a [HyperFrames](https://hyperframes.heygen.com) project in +`video/`: + +- `index.html` places the scenes, captions, and audio on the timeline. +- `compositions/` holds one sub-composition per scene plus the caption track. +- `assets/kit.js` is the shared seek-safe engine (cursor, camera zoom, + typing); `assets/kit.css` rebuilds the GitHub surfaces. +- `assets/i18n.js` holds every translated string. + +Preview and edit in HyperFrames Studio: + +~~~bash +cd docs/demo/video +npx --yes hyperframes@0.8.155 preview +~~~ + +Render all languages (Node.js 22+, FFmpeg, Python 3 with NumPy; run outside +sandboxes that block Chrome): + +~~~bash +docs/demo/video/scripts/render.sh # en, pt-BR, es +docs/demo/video/scripts/render.sh pt-BR # a single language +~~~ + +To add a language, add a table to `assets/i18n.js`, add it to the `lang` +options in `index.html`, and pass it to `render.sh`. diff --git a/docs/demo/risk-gate-demo-en.jpg b/docs/demo/risk-gate-demo-en.jpg new file mode 100644 index 0000000..5f05f36 Binary files /dev/null and b/docs/demo/risk-gate-demo-en.jpg differ diff --git a/docs/demo/risk-gate-demo-en.mp4 b/docs/demo/risk-gate-demo-en.mp4 new file mode 100644 index 0000000..bc8f62f Binary files /dev/null and b/docs/demo/risk-gate-demo-en.mp4 differ diff --git a/docs/demo/risk-gate-demo-es.jpg b/docs/demo/risk-gate-demo-es.jpg new file mode 100644 index 0000000..b57258f Binary files /dev/null and b/docs/demo/risk-gate-demo-es.jpg differ diff --git a/docs/demo/risk-gate-demo-es.mp4 b/docs/demo/risk-gate-demo-es.mp4 new file mode 100644 index 0000000..bf5aa5a Binary files /dev/null and b/docs/demo/risk-gate-demo-es.mp4 differ diff --git a/docs/demo/risk-gate-demo-pt-BR.jpg b/docs/demo/risk-gate-demo-pt-BR.jpg new file mode 100644 index 0000000..2f05cfd Binary files /dev/null and b/docs/demo/risk-gate-demo-pt-BR.jpg differ diff --git a/docs/demo/risk-gate-demo-pt-BR.mp4 b/docs/demo/risk-gate-demo-pt-BR.mp4 new file mode 100644 index 0000000..0078df0 Binary files /dev/null and b/docs/demo/risk-gate-demo-pt-BR.mp4 differ diff --git a/docs/demo/video/.gitignore b/docs/demo/video/.gitignore new file mode 100644 index 0000000..00cdda8 --- /dev/null +++ b/docs/demo/video/.gitignore @@ -0,0 +1,11 @@ +# HyperFrames and tool caches +.hyperframes/ +.waveform-cache/ +renders/ +node_modules/ +# Agent tooling state +.claude-flow/ +.swarm/ +ruvector.db +# Intermediate audio; the encoded .m4a files are committed +assets/audio/*.wav diff --git a/docs/demo/video/assets/audio/music.m4a b/docs/demo/video/assets/audio/music.m4a new file mode 100644 index 0000000..febedf7 Binary files /dev/null and b/docs/demo/video/assets/audio/music.m4a differ diff --git a/docs/demo/video/assets/audio/sfx.m4a b/docs/demo/video/assets/audio/sfx.m4a new file mode 100644 index 0000000..f990232 Binary files /dev/null and b/docs/demo/video/assets/audio/sfx.m4a differ diff --git a/docs/demo/video/assets/data/report-failed.md b/docs/demo/video/assets/data/report-failed.md new file mode 100644 index 0000000..641557f --- /dev/null +++ b/docs/demo/video/assets/data/report-failed.md @@ -0,0 +1,49 @@ + +## Risk Gate + +- **Risk:** `danger` +- **Status:** `FAILED` +- **Merge blocked:** `true` +- **Version:** `0.1.0` + +### Why this risk + +- `src/api/checkout.ts` → **danger** (rule `src/api/`); modules: `chargeCard`, `checkout`, `items`, `token`, `total`, `items`, `sum`, `item`, `sum`, `chargeCard`, `total` + +### Cascade impact + +- `src/api/checkout.ts` is connected to `src/payment/charge.ts` → **danger** (rule `src/payment/`): Payments and authorization; chain: `src/api/checkout.ts` → `src/payment/charge.ts` +- `src/api/checkout.ts` is connected to `tests/checkout.test.ts` → **minimal** (rule `tests/`); chain: `src/api/checkout.ts` → `tests/checkout.test.ts` +- `src/api/checkout.ts` is connected to `src/payment/fraud.ts` → **danger** (rule `src/payment/`): Payments and authorization; chain: `src/api/checkout.ts` → `src/payment/charge.ts` → `src/payment/fraud.ts` + +```mermaid +flowchart LR + impact_0["src/api/checkout.ts"] + impact_1["src/payment/charge.ts"] + impact_2["tests/checkout.test.ts"] + impact_3["src/payment/fraud.ts"] + impact_0 -->|"step 1"| impact_1 + impact_0 -->|"step 1"| impact_2 + impact_1 -->|"step 2"| impact_3 +``` + +### AST analysis + +Parsed files: `40`; incomplete files: `4`. + +The following files require attention: + +- `.github/workflows/risk-gate.yml`: unsupported language +- `.risk-gate.yaml`: unsupported language +- `scripts/mutation-test.sh`: unsupported language +- `scripts/test-e2e.sh`: unsupported language + +### Validations + +- `e2e`: `SUCCESS` (exit: `0`; 4048 ms) + - stdout: `12 scenarios passed` +- `mutationTesting`: `FAILED` (exit: `1`; 6019 ms) + - stderr: `3 mutants survived in src/payment/charge.ts` + +_Generated by risk-gate._ + diff --git a/docs/demo/video/assets/data/report.js b/docs/demo/video/assets/data/report.js new file mode 100644 index 0000000..4f4ce9a --- /dev/null +++ b/docs/demo/video/assets/data/report.js @@ -0,0 +1,4 @@ +/* Generated by scripts/report-data.mjs from assets/data/report-failed.md. */ +window.RG_REPORT = { + "failed": "\n## Risk Gate\n\n- **Risk:** `danger`\n- **Status:** `FAILED`\n- **Merge blocked:** `true`\n- **Version:** `0.1.0`\n\n### Why this risk\n\n- `src/api/checkout.ts` → **danger** (rule `src/api/`); modules: `chargeCard`, `checkout`, `items`, `token`, `total`, `items`, `sum`, `item`, `sum`, `chargeCard`, `total`\n\n### Cascade impact\n\n- `src/api/checkout.ts` is connected to `src/payment/charge.ts` → **danger** (rule `src/payment/`): Payments and authorization; chain: `src/api/checkout.ts` → `src/payment/charge.ts`\n- `src/api/checkout.ts` is connected to `tests/checkout.test.ts` → **minimal** (rule `tests/`); chain: `src/api/checkout.ts` → `tests/checkout.test.ts`\n- `src/api/checkout.ts` is connected to `src/payment/fraud.ts` → **danger** (rule `src/payment/`): Payments and authorization; chain: `src/api/checkout.ts` → `src/payment/charge.ts` → `src/payment/fraud.ts`\n\n```mermaid\nflowchart LR\n impact_0[\"src/api/checkout.ts\"]\n impact_1[\"src/payment/charge.ts\"]\n impact_2[\"tests/checkout.test.ts\"]\n impact_3[\"src/payment/fraud.ts\"]\n impact_0 -->|\"step 1\"| impact_1\n impact_0 -->|\"step 1\"| impact_2\n impact_1 -->|\"step 2\"| impact_3\n```\n\n### AST analysis\n\nParsed files: `40`; incomplete files: `4`.\n\nThe following files require attention:\n\n- `.github/workflows/risk-gate.yml`: unsupported language\n- `.risk-gate.yaml`: unsupported language\n- `scripts/mutation-test.sh`: unsupported language\n- `scripts/test-e2e.sh`: unsupported language\n\n### Validations\n\n- `e2e`: `SUCCESS` (exit: `0`; 4048 ms)\n - stdout: `12 scenarios passed`\n- `mutationTesting`: `FAILED` (exit: `1`; 6019 ms)\n - stderr: `3 mutants survived in src/payment/charge.ts`\n\n_Generated by risk-gate._" +}; diff --git a/docs/demo/video/assets/fonts/LICENSE-Inter.txt b/docs/demo/video/assets/fonts/LICENSE-Inter.txt new file mode 100644 index 0000000..40589da --- /dev/null +++ b/docs/demo/video/assets/fonts/LICENSE-Inter.txt @@ -0,0 +1,93 @@ +Copyright 2016 The Inter Project Authors (https://github.com/rsms/inter) Inter-Italic[opsz,wght].ttf: Copyright 2016 The Inter Project Authors (https://github.com/rsms/inter) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/docs/demo/video/assets/fonts/LICENSE-JetBrainsMono.txt b/docs/demo/video/assets/fonts/LICENSE-JetBrainsMono.txt new file mode 100644 index 0000000..8f7ed67 --- /dev/null +++ b/docs/demo/video/assets/fonts/LICENSE-JetBrainsMono.txt @@ -0,0 +1,93 @@ +Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono) JetBrainsMono-Italic[wght].ttf: Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/docs/demo/video/assets/fonts/inter-latin-400-normal.woff2 b/docs/demo/video/assets/fonts/inter-latin-400-normal.woff2 new file mode 100644 index 0000000..f15b025 Binary files /dev/null and b/docs/demo/video/assets/fonts/inter-latin-400-normal.woff2 differ diff --git a/docs/demo/video/assets/fonts/inter-latin-500-normal.woff2 b/docs/demo/video/assets/fonts/inter-latin-500-normal.woff2 new file mode 100644 index 0000000..54f0a59 Binary files /dev/null and b/docs/demo/video/assets/fonts/inter-latin-500-normal.woff2 differ diff --git a/docs/demo/video/assets/fonts/inter-latin-600-normal.woff2 b/docs/demo/video/assets/fonts/inter-latin-600-normal.woff2 new file mode 100644 index 0000000..d189794 Binary files /dev/null and b/docs/demo/video/assets/fonts/inter-latin-600-normal.woff2 differ diff --git a/docs/demo/video/assets/fonts/inter-latin-700-normal.woff2 b/docs/demo/video/assets/fonts/inter-latin-700-normal.woff2 new file mode 100644 index 0000000..a68fb10 Binary files /dev/null and b/docs/demo/video/assets/fonts/inter-latin-700-normal.woff2 differ diff --git a/docs/demo/video/assets/fonts/jetbrains-mono-latin-400-normal.woff2 b/docs/demo/video/assets/fonts/jetbrains-mono-latin-400-normal.woff2 new file mode 100644 index 0000000..5858873 Binary files /dev/null and b/docs/demo/video/assets/fonts/jetbrains-mono-latin-400-normal.woff2 differ diff --git a/docs/demo/video/assets/fonts/jetbrains-mono-latin-500-normal.woff2 b/docs/demo/video/assets/fonts/jetbrains-mono-latin-500-normal.woff2 new file mode 100644 index 0000000..be878e6 Binary files /dev/null and b/docs/demo/video/assets/fonts/jetbrains-mono-latin-500-normal.woff2 differ diff --git a/docs/demo/video/assets/fonts/jetbrains-mono-latin-700-normal.woff2 b/docs/demo/video/assets/fonts/jetbrains-mono-latin-700-normal.woff2 new file mode 100644 index 0000000..3a4e333 Binary files /dev/null and b/docs/demo/video/assets/fonts/jetbrains-mono-latin-700-normal.woff2 differ diff --git a/docs/demo/video/assets/i18n.js b/docs/demo/video/assets/i18n.js new file mode 100644 index 0000000..dd954a4 --- /dev/null +++ b/docs/demo/video/assets/i18n.js @@ -0,0 +1,66 @@ +/* On-screen copy for every language the demo ships in. + The GitHub UI, code, and the Risk Gate report stay in English because they + reproduce real product output; only the narration layer is translated. + Select a language with: hyperframes render --variables '{"lang":"pt-BR"}' */ +window.RG_I18N = { + en: { + intro_tag: "Merge risk, made explicit.", + intro_sub: "A GitHub Action that matches validations to the risk of every pull request.", + chip_action: "GitHub Action", + chip_rust: "Written in Rust", + chip_langs: "Python · JavaScript · TypeScript · Elixir", + c1: "Version your risk policy with your code", + c2: "Map critical paths to the validations they require", + c3: "Add one step to your GitHub Actions workflow", + c4: "Open a pull request as usual", + c5: "Risk Gate reads the whole project and follows its dependency graph", + c6: "A failed validation never counts as success", + c7: "Every pull request gets one auditable report", + c8: "Cascade impact: an API change that reaches payments", + c9: "Validation results with exit codes and timings", + c10: "Merging waits until the gate passes", + c11: "Push a fix and the gate runs again automatically", + outro_tag: "Deterministic. Explainable. Auditable.", + outro_cta: "Add it to your workflow", + }, + "pt-BR": { + intro_tag: "Risco de merge, explícito.", + intro_sub: "Uma GitHub Action que ajusta as validações ao risco de cada pull request.", + chip_action: "GitHub Action", + chip_rust: "Feito em Rust", + chip_langs: "Python · JavaScript · TypeScript · Elixir", + c1: "Versione sua política de risco junto com o código", + c2: "Mapeie áreas críticas e as validações que elas exigem", + c3: "Adicione um único passo ao workflow do GitHub Actions", + c4: "Abra um pull request como sempre", + c5: "O Risk Gate lê o projeto inteiro e segue o grafo de dependências", + c6: "Validação que falhou nunca conta como sucesso", + c7: "Cada pull request ganha um relatório auditável", + c8: "Impacto em cascata: uma mudança na API que chega em pagamentos", + c9: "Resultados das validações com exit codes e tempos", + c10: "O merge espera até o gate passar", + c11: "Envie a correção e o gate roda de novo automaticamente", + outro_tag: "Determinístico. Explicável. Auditável.", + outro_cta: "Adicione ao seu workflow", + }, + es: { + intro_tag: "El riesgo del merge, explícito.", + intro_sub: "Una GitHub Action que ajusta las validaciones al riesgo de cada pull request.", + chip_action: "GitHub Action", + chip_rust: "Hecho en Rust", + chip_langs: "Python · JavaScript · TypeScript · Elixir", + c1: "Versiona tu política de riesgo junto con el código", + c2: "Mapea las rutas críticas y las validaciones que requieren", + c3: "Añade un solo paso a tu workflow de GitHub Actions", + c4: "Abre un pull request como siempre", + c5: "Risk Gate lee todo el proyecto y sigue el grafo de dependencias", + c6: "Una validación fallida nunca cuenta como éxito", + c7: "Cada pull request recibe un informe auditable", + c8: "Impacto en cascada: un cambio en la API que llega a pagos", + c9: "Resultados de las validaciones con códigos de salida y tiempos", + c10: "El merge espera hasta que el gate pase", + c11: "Sube la corrección y el gate se vuelve a ejecutar solo", + outro_tag: "Determinista. Explicable. Auditable.", + outro_cta: "Agrégalo a tu workflow", + }, +}; diff --git a/docs/demo/video/assets/kit.css b/docs/demo/video/assets/kit.css new file mode 100644 index 0000000..c24cbf8 --- /dev/null +++ b/docs/demo/video/assets/kit.css @@ -0,0 +1,675 @@ +/* Shared styles for the Risk Gate demo film. + Loaded once by index.html, so every mounted scene shares one design system. + The GitHub surfaces are rebuilt at 1.35x of the web UI so they stay legible + in a 1920x1080 frame. */ + +@font-face { + font-family: "Inter"; + src: url("fonts/inter-latin-400-normal.woff2") format("woff2"); + font-weight: 400; +} +@font-face { + font-family: "Inter"; + src: url("fonts/inter-latin-500-normal.woff2") format("woff2"); + font-weight: 500; +} +@font-face { + font-family: "Inter"; + src: url("fonts/inter-latin-600-normal.woff2") format("woff2"); + font-weight: 600; +} +@font-face { + font-family: "Inter"; + src: url("fonts/inter-latin-700-normal.woff2") format("woff2"); + font-weight: 700; +} +@font-face { + font-family: "JetBrains Mono"; + src: url("fonts/jetbrains-mono-latin-400-normal.woff2") format("woff2"); + font-weight: 400; +} +@font-face { + font-family: "JetBrains Mono"; + src: url("fonts/jetbrains-mono-latin-500-normal.woff2") format("woff2"); + font-weight: 500; +} +@font-face { + font-family: "JetBrains Mono"; + src: url("fonts/jetbrains-mono-latin-700-normal.woff2") format("woff2"); + font-weight: 700; +} + +:root { + --fg: #1f2328; + --muted: #59636e; + --border: #d1d9e0; + --border-muted: #e3e8ee; + --subtle: #f6f8fa; + --accent: #0969da; + --success: #1a7f37; + --success-btn: #1f883d; + --danger: #d1242f; + --attention: #9a6700; + --done: #8250df; + --ink: #0f172a; + --risk-minimal: #14b8a6; + --risk-low: #3b82f6; + --risk-normal: #f59e0b; + --risk-high: #f97316; + --risk-danger: #f43f5e; + --mono: "JetBrains Mono", ui-monospace, monospace; + --sans: "Inter", ui-sans-serif, system-ui, sans-serif; +} + +/* ---------- Stage, camera, cursor ---------- */ + +.rg-stage { + position: absolute; + inset: 0; + overflow: hidden; + font-variant-ligatures: none; + font-family: var(--sans); + color: var(--fg); +} +.rg-cam { + position: absolute; + left: 0; + top: 0; + width: 1920px; + height: 1080px; + transform-origin: 0 0; +} +.rg-cursor { + position: absolute; + left: 0; + top: 0; + width: 46px; + height: 46px; + margin-left: -9.6px; + margin-top: -5.8px; + transform-origin: 9.6px 5.8px; + filter: drop-shadow(0 4px 6px rgba(15, 23, 42, 0.28)); + z-index: 50; +} +.rg-cursor path { + fill: #ffffff; + stroke: #111827; + stroke-width: 1.4; + stroke-linejoin: round; +} +.rg-ripple { + position: absolute; + left: 0; + top: 0; + width: 10px; + height: 10px; + border-radius: 50%; + border: 3px solid rgba(9, 105, 218, 0.85); + background: rgba(9, 105, 218, 0.12); + opacity: 0; + z-index: 49; +} +.rg-focus { + position: absolute; + border-radius: 10px; + border: 3px solid #f59e0b; + background: rgba(253, 224, 71, 0.18); + box-shadow: 0 0 0 6px rgba(245, 158, 11, 0.16); + opacity: 0; + z-index: 40; +} + +/* ---------- Browser window ---------- */ + +.win { + position: absolute; + left: 150px; + top: 64px; + width: 1620px; + height: 828px; + border-radius: 18px; + background: #ffffff; + overflow: hidden; + box-shadow: + 0 40px 80px -24px rgba(15, 23, 42, 0.28), + 0 12px 28px -8px rgba(15, 23, 42, 0.14), + 0 0 0 1px rgba(15, 23, 42, 0.08); +} +.win-bar { + position: absolute; + left: 0; + top: 0; + right: 0; + height: 50px; + background: #eceff3; + border-bottom: 1px solid #dde2e8; + display: flex; + align-items: center; + gap: 18px; + padding: 0 20px; +} +.win-dots { + display: flex; + gap: 9px; +} +.win-dots span { + width: 14px; + height: 14px; + border-radius: 50%; + display: block; +} +.win-dots span:nth-child(1) { + background: #ff5f57; +} +.win-dots span:nth-child(2) { + background: #febc2e; +} +.win-dots span:nth-child(3) { + background: #28c840; +} +.win-url { + margin-left: 70px; + width: 760px; + height: 32px; + border-radius: 16px; + background: #ffffff; + border: 1px solid #dde2e8; + display: flex; + align-items: center; + gap: 10px; + padding: 0 16px; + font-size: 16px; + color: #475569; +} +.win-url svg { + width: 14px; + height: 14px; + flex: none; +} +.win-url b { + color: #0f172a; + font-weight: 500; +} + +.gh-header { + position: absolute; + left: 0; + right: 0; + top: 50px; + height: 62px; + background: var(--subtle); + display: flex; + align-items: center; + gap: 16px; + padding: 0 28px; + font-size: 19px; +} +.gh-mark { + width: 38px; + height: 38px; + flex: none; +} +.gh-crumb { + display: flex; + gap: 8px; + color: var(--fg); +} +.gh-crumb .owner { + font-weight: 400; +} +.gh-crumb .sep { + color: var(--muted); +} +.gh-crumb .repo { + font-weight: 600; +} +.gh-header .spacer { + flex: 1; +} +.gh-search { + width: 320px; + height: 38px; + border: 1px solid var(--border); + border-radius: 8px; + background: #fff; + display: flex; + align-items: center; + padding: 0 12px; + gap: 10px; + color: var(--muted); + font-size: 17px; +} +.gh-avatar { + width: 38px; + height: 38px; + border-radius: 50%; + flex: none; + display: grid; + place-items: center; + color: #fff; + font-weight: 600; + font-size: 16px; +} +.av-dana { + background: linear-gradient(135deg, #8b5cf6, #ec4899); +} +.av-bot { + background: #24292f; +} +.av-alice { + background: linear-gradient(135deg, #0ea5e9, #22c55e); +} + +.gh-nav { + position: absolute; + left: 0; + right: 0; + top: 112px; + height: 50px; + background: var(--subtle); + border-bottom: 1px solid var(--border); + display: flex; + align-items: flex-end; + gap: 6px; + padding: 0 22px; + font-size: 18px; +} +.gh-nav .tab { + display: flex; + align-items: center; + gap: 9px; + padding: 0 12px 12px; + color: var(--fg); + position: relative; +} +.gh-nav .tab svg { + width: 18px; + height: 18px; + color: var(--muted); +} +.gh-nav .tab.on { + font-weight: 600; +} +.gh-nav .tab.on::after { + content: ""; + position: absolute; + left: 4px; + right: 4px; + bottom: -1px; + height: 3px; + border-radius: 3px; + background: #fd8c73; +} +.gh-count { + min-width: 24px; + padding: 0 8px; + height: 24px; + border-radius: 12px; + background: rgba(175, 184, 193, 0.35); + font-size: 15px; + display: inline-grid; + place-items: center; + font-weight: 500; +} + +.gh-page { + position: absolute; + left: 0; + right: 0; + top: 162px; + bottom: 0; + overflow: hidden; + background: #ffffff; +} +.gh-scroll { + position: absolute; + left: 0; + right: 0; + top: 0; +} + +/* ---------- Buttons, chips, icons ---------- */ + +.btn { + height: 44px; + padding: 0 18px; + border-radius: 8px; + border: 1px solid var(--border); + background: var(--subtle); + font-size: 18px; + font-weight: 500; + display: inline-flex; + align-items: center; + gap: 8px; + color: var(--fg); + white-space: nowrap; +} +.btn.primary { + background: var(--success-btn); + border-color: rgba(31, 35, 40, 0.15); + color: #ffffff; + font-weight: 600; +} +.btn.disabled { + background: #f3f5f7; + color: #8c959f; +} +.ico { + width: 20px; + height: 20px; + flex: none; +} +code, +.code { + font-family: var(--mono); +} +.md code { + font-size: 0.86em; + padding: 2px 7px; + border-radius: 6px; + background: rgba(175, 184, 193, 0.28); +} + +/* ---------- Editor (create new file) ---------- */ + +.ed-top { + position: absolute; + left: 40px; + right: 40px; + top: 26px; + height: 48px; + display: flex; + align-items: center; + gap: 10px; + font-size: 19px; +} +.ed-top .repo { + color: var(--accent); + font-weight: 600; +} +.ed-file { + height: 44px; + min-width: 420px; + border: 1px solid var(--border); + border-radius: 8px; + padding: 0 14px; + display: flex; + align-items: center; + font-family: var(--mono); + font-size: 18px; + background: #fff; + position: relative; +} +.ed-file.focus { + border-color: var(--accent); + box-shadow: 0 0 0 3px rgba(9, 105, 218, 0.2); +} +.ed-file .ph { + color: #8c959f; + font-family: var(--sans); + position: absolute; + left: 14px; +} +.ed-top .in { + color: var(--muted); +} +.ed-branch { + font-family: var(--mono); + font-size: 16px; + padding: 3px 10px; + border-radius: 6px; + background: #ddf4ff; + color: var(--accent); +} +.ed-top .spacer { + flex: 1; +} +.ed-box { + position: absolute; + left: 40px; + right: 40px; + top: 96px; + bottom: 28px; + border: 1px solid var(--border); + border-radius: 10px; + overflow: hidden; +} +.ed-tabs { + height: 52px; + background: var(--subtle); + border-bottom: 1px solid var(--border); + display: flex; + align-items: flex-end; + padding: 0 10px; + gap: 4px; + font-size: 17px; +} +.ed-tabs .t { + height: 42px; + padding: 0 18px; + display: flex; + align-items: center; + border-radius: 8px 8px 0 0; + color: var(--muted); +} +.ed-tabs .t.on { + background: #fff; + border: 1px solid var(--border); + border-bottom-color: #fff; + margin-bottom: -1px; + color: var(--fg); + font-weight: 500; +} +.ed-tabs .spacer { + flex: 1; +} +.ed-tabs .opt { + align-self: center; + color: var(--muted); + font-size: 15px; + padding: 0 10px; +} +.ed-body { + position: absolute; + left: 0; + right: 0; + top: 52px; + bottom: 0; + overflow: hidden; +} +.ed-lines { + position: absolute; + left: 0; + top: 10px; + right: 0; + font-family: var(--mono); + font-size: 16px; + line-height: 20px; +} +.ed-line { + display: flex; + height: 20px; +} +.ed-line .n { + width: 62px; + flex: none; + text-align: right; + padding-right: 20px; + color: #6e7781; +} +.ed-line .c { + white-space: pre; +} +.tk-key { + color: #116329; +} +.tk-str { + color: #0a3069; +} +.tk-num { + color: #0550ae; +} +.tk-punc { + color: #1f2328; +} +.tk-com { + color: #6e7781; +} +.tk-var { + color: #953800; +} +.caret { + position: absolute; + left: 0; + top: 0; + width: 2px; + height: 20px; + background: #1f2328; + z-index: 5; +} + +/* ---------- Pull request ---------- */ + +.pr-head { + padding: 26px 44px 0; +} +.pr-title { + font-size: 34px; + font-weight: 400; + display: flex; + align-items: baseline; + gap: 12px; +} +.pr-title .num { + color: var(--muted); + font-weight: 300; +} +.pr-meta { + margin-top: 14px; + display: flex; + align-items: center; + gap: 12px; + font-size: 18px; + color: var(--muted); +} +.state { + height: 38px; + padding: 0 16px; + border-radius: 19px; + display: inline-flex; + align-items: center; + gap: 8px; + color: #fff; + font-weight: 500; + font-size: 17px; +} +.state.open { + background: var(--success-btn); +} +.state.merged { + background: var(--done); +} +.branch { + font-family: var(--mono); + font-size: 15px; + padding: 2px 8px; + border-radius: 6px; + background: #ddf4ff; + color: var(--accent); +} +.pr-tabs { + margin-top: 22px; + border-bottom: 1px solid var(--border); + display: flex; + gap: 6px; + padding: 0 34px; + font-size: 18px; +} +.pr-tabs .tab { + display: flex; + align-items: center; + gap: 9px; + padding: 0 12px 14px; + position: relative; + color: var(--muted); +} +.pr-tabs .tab.on { + color: var(--fg); + font-weight: 600; +} +.pr-tabs .tab.on::after { + content: ""; + position: absolute; + left: 4px; + right: 4px; + bottom: -1px; + height: 3px; + border-radius: 3px; + background: #fd8c73; +} + +/* ---------- Actions log ---------- */ + +.log { + background: #0d1117; + color: #e6edf3; + font-family: var(--mono); + font-size: 16px; + line-height: 25px; +} +.log .ln { + display: flex; + white-space: pre; +} +.log .ln .n { + width: 54px; + flex: none; + text-align: right; + padding-right: 18px; + color: #848d97; +} +.log .ln.err { + color: #ff7b72; +} +.log .ln.cmd { + color: #d2a8ff; +} +.log .ln.ok { + color: #7ee787; +} +.log .ln.dim { + color: #8b949e; +} + +/* ---------- Markdown comment ---------- */ + +.md { + font-size: 19px; + line-height: 1.55; + color: var(--fg); +} +.md h2 { + font-size: 28px; + font-weight: 600; + padding-bottom: 8px; + border-bottom: 1px solid var(--border-muted); + margin: 0 0 16px; +} +.md h3 { + font-size: 22px; + font-weight: 600; + margin: 22px 0 10px; +} +.md ul { + margin: 0; + padding-left: 30px; +} +.md li { + margin: 4px 0; +} +.md p { + margin: 0 0 10px; +} +.md em { + color: var(--muted); +} diff --git a/docs/demo/video/assets/kit.js b/docs/demo/video/assets/kit.js new file mode 100644 index 0000000..9daf291 --- /dev/null +++ b/docs/demo/video/assets/kit.js @@ -0,0 +1,595 @@ +/* Shared, seek-safe scene engine for the Risk Gate demo film. + + Every scene describes its cursor path, clicks, camera poses, and typing as + data. RG.scene() turns that data into one pure render(t) function and one + paused GSAP timeline that calls it, so any frame can be seeked in any order + and always paints the same pixels. Geometry is measured from layout offsets, + which ignore the camera transform and any player scaling. */ +(function () { + "use strict"; + + var W = 1920; + var H = 1080; + + function clamp01(u) { + return u < 0 ? 0 : u > 1 ? 1 : u; + } + function mix(a, b, u) { + return a + (b - a) * u; + } + function ease(name) { + return gsap.parseEase(name || "power2.inOut"); + } + function seg(t, at, dur, easing) { + var u = clamp01((t - at) / Math.max(dur, 0.0001)); + return easing ? ease(easing)(u) : u; + } + // Clamp v into [lo, hi]; when the view is wider than the bounds, centre it. + // The clamp fades in with zoom so a zoom-out lands exactly on the rest frame. + function clampRange(v, lo, hi, weight) { + var target = lo > hi ? (lo + hi) / 2 : Math.max(lo, Math.min(hi, v)); + return v + (target - v) * (weight == null ? 1 : weight); + } + function val(v, t) { + return typeof v === "function" ? v(t) : v; + } + + /* ---------- language ---------- */ + + function lang() { + // assets/lang.js (written by scripts/render.sh) wins over the composition + // variable, so a render never depends on how variables reach sub-scenes. + var vars = window.__hfVariables || {}; + var chosen = window.RG_LANG || vars.lang || "en"; + return window.RG_I18N && window.RG_I18N[chosen] ? chosen : "en"; + } + function tr(key) { + var table = window.RG_I18N || {}; + var current = table[lang()] || {}; + if (current[key] != null) return current[key]; + return (table.en || {})[key] != null ? table.en[key] : key; + } + function applyText(root) { + root.querySelectorAll("[data-i18n]").forEach(function (el) { + el.textContent = tr(el.getAttribute("data-i18n")); + }); + } + + /* ---------- geometry ---------- */ + + // Position of el's box relative to stage, from offset chains (transform-free). + function box(el, stage) { + var x = 0; + var y = 0; + var node = el; + while (node && node !== stage) { + x += node.offsetLeft; + y += node.offsetTop; + node = node.offsetParent; + } + return { x: x, y: y, w: el.offsetWidth, h: el.offsetHeight }; + } + function anchor(el, stage, ax, ay) { + var b = box(el, stage); + return { x: b.x + b.w * (ax == null ? 0.5 : ax), y: b.y + b.h * (ay == null ? 0.5 : ay) }; + } + + /* ---------- syntax highlighting (YAML) ---------- */ + + function esc(s) { + return s.replace(/&/g, "&").replace(//g, ">"); + } + function yamlValue(v) { + if (!v) return ""; + var lead = v.match(/^\s*/)[0]; + var body = v.slice(lead.length); + var cls = "tk-str"; + if (/^(true|false|null|-?\d+(\.\d+)?)$/.test(body)) cls = "tk-num"; + else if (/^\[/.test(body)) cls = "tk-punc"; + else if (/^\$\{\{/.test(body)) cls = "tk-var"; + else if (/^"/.test(body)) cls = "tk-str"; + else cls = "tk-str"; + return esc(lead) + '' + esc(body) + ""; + } + function yamlLine(line) { + var m = line.match(/^(\s*)(- )?([A-Za-z_][\w-]*)(:)(.*)$/); + if (m) { + return ( + esc(m[1]) + + (m[2] ? '- ' : "") + + '' + + esc(m[3]) + + ':' + + yamlValue(m[5]) + ); + } + var item = line.match(/^(\s*)(- )(.*)$/); + if (item) return esc(item[1]) + '- ' + yamlValue(item[3]); + return esc(line); + } + + // Fill an editor element with numbered, highlighted lines. + function editorLines(container, text) { + var html = text + .split("\n") + .map(function (line, i) { + return ( + '
" + esc(part) + "";
+ var t = esc(part).replace(/\*\*([^*]+)\*\*/g, "$1");
+ return t;
+ })
+ .join("");
+ }
+
+ // Mermaid flowchart LR -> SVG using Mermaid's default (light) theme colours.
+ function flowchart(src) {
+ var nodes = {};
+ var order = [];
+ var edges = [];
+ src.split("\n").forEach(function (line) {
+ var n = line.match(/^\s*(\w+)\["([^"]*)"\]\s*$/);
+ if (n) {
+ nodes[n[1]] = { id: n[1], label: n[2].replace(/"/g, '"').replace(/&/g, "&") };
+ order.push(n[1]);
+ return;
+ }
+ var e = line.match(/^\s*(\w+)\s*-->\|"([^"]*)"\|\s*(\w+)\s*$/);
+ if (e) edges.push({ from: e[1], label: e[2], to: e[3] });
+ });
+ // Rank = longest path from a root; rows keep declaration order within a rank.
+ var rank = {};
+ order.forEach(function (id) {
+ rank[id] = 0;
+ });
+ for (var pass = 0; pass < order.length; pass++) {
+ edges.forEach(function (e) {
+ if (rank[e.to] < rank[e.from] + 1) rank[e.to] = rank[e.from] + 1;
+ });
+ }
+ var cols = {};
+ order.forEach(function (id) {
+ (cols[rank[id]] = cols[rank[id]] || []).push(id);
+ });
+ var NW = 250;
+ var NH = 58;
+ var GAPX = 150;
+ var GAPY = 44;
+ var maxRows = 0;
+ Object.keys(cols).forEach(function (c) {
+ maxRows = Math.max(maxRows, cols[c].length);
+ });
+ var height = maxRows * NH + (maxRows - 1) * GAPY + 20;
+ var ncols = Object.keys(cols).length;
+ var width = ncols * NW + (ncols - 1) * GAPX + 20;
+ Object.keys(cols).forEach(function (c) {
+ var ids = cols[c];
+ var colH = ids.length * NH + (ids.length - 1) * GAPY;
+ ids.forEach(function (id, i) {
+ nodes[id].x = 10 + Number(c) * (NW + GAPX);
+ nodes[id].y = (height - colH) / 2 + i * (NH + GAPY);
+ });
+ });
+ // A child of a single-parent chain sits on its parent's row.
+ edges.forEach(function (e) {
+ var siblings = cols[rank[e.to]];
+ if (siblings.length === 1) nodes[e.to].y = nodes[e.from].y;
+ });
+ var svg =
+ '";
+ }
+
+ function render(md) {
+ var lines = md.split("\n");
+ var out = [];
+ var inList = false;
+ var inSub = false;
+ var fence = null;
+ function closeLists() {
+ if (inSub) out.push("");
+ else if (inList) out.push("");
+ if (inList) out.push("");
+ inList = false;
+ inSub = false;
+ }
+ for (var i = 0; i < lines.length; i++) {
+ var line = lines[i];
+ if (fence !== null) {
+ if (/^```/.test(line)) {
+ out.push('" + (em ? "" + inline(em[1]) + "" : inline(line)) + "
"); + } + closeLists(); + return out.join(""); + } + + window.RGMD = { render: render }; +})(); diff --git a/docs/demo/video/assets/vendor/gsap.min.js b/docs/demo/video/assets/vendor/gsap.min.js new file mode 100644 index 0000000..fde57af --- /dev/null +++ b/docs/demo/video/assets/vendor/gsap.min.js @@ -0,0 +1,11 @@ +/*! + * GSAP 3.14.2 + * https://gsap.com + * + * @license Copyright 2025, GreenSock. All rights reserved. + * Subject to the terms at https://gsap.com/standard-license. + * @author: Jack Doyle, jack@greensock.com + */ + +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t=t||self).window=t.window||{})}(this,function(e){"use strict";function _inheritsLoose(t,e){t.prototype=Object.create(e.prototype),(t.prototype.constructor=t).__proto__=e}function _assertThisInitialized(t){if(void 0===t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return t}function r(t){return"string"==typeof t}function s(t){return"function"==typeof t}function t(t){return"number"==typeof t}function u(t){return void 0===t}function v(t){return"object"==typeof t}function w(t){return!1!==t}function x(){return"undefined"!=typeof window}function y(t){return s(t)||r(t)}function R(t){return(i=bt(t,ht))&&Fe}function S(t,e){return console.warn("Invalid property",t,"set to",e,"Missing plugin? gsap.registerPlugin()")}function T(t,e){return!e&&console.warn(t)}function U(t,e){return t&&(ht[t]=e)&&i&&(i[t]=e)||ht}function V(){return 0}function ga(t){var e,r,i=t[0];if(v(i)||s(i)||(t=[t]),!(e=(i._gsap||{}).harness)){for(r=yt.length;r--&&!yt[r].targetTest(i););e=yt[r]}for(r=t.length;r--;)t[r]&&(t[r]._gsap||(t[r]._gsap=new Xt(t[r],e)))||t.splice(r,1);return t}function ha(t){return t._gsap||ga(Pt(t))[0]._gsap}function ia(t,e,r){return(r=t[e])&&s(r)?t[e]():u(r)&&t.getAttribute&&t.getAttribute(e)||r}function ja(t,e){return(t=t.split(",")).forEach(e)||t}function ka(t){return Math.round(1e5*t)/1e5||0}function la(t){return Math.round(1e7*t)/1e7||0}function ma(t,e){var r=e.charAt(0),i=parseFloat(e.substr(2));return t=parseFloat(t),"+"===r?t+i:"-"===r?t-i:"*"===r?t*i:t/i}function na(t,e){for(var r=e.length,i=0;t.indexOf(e[i])<0&&++i