[
  {
    "path": ".editorconfig",
    "content": "[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl}]\ncharset = utf-8\nindent_size = 3\nindent_style = tab\ninsert_final_newline = true\ntrim_trailing_whitespace = true\nend_of_line = lf\nmax_line_length = 100\n"
  },
  {
    "path": ".gitattributes",
    "content": "* text=auto eol=lf\nia.txt text eol=lf\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: [ragnarlotus]# Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]\npatreon: # Replace with a single Patreon username\nopen_collective: # Replace with a single Open Collective username\nko_fi: # Replace with a single Ko-fi username\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: # Replace with a single IssueHunt username\notechie: # Replace with a single Otechie username\ncustom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']\n"
  },
  {
    "path": ".gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\n.DS_Store\ndist\ndist-ssr\ncoverage\n*.local\n\n/cypress/videos/\n/cypress/screenshots/\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n\n*.tsbuildinfo\n"
  },
  {
    "path": ".markdownlint.cjs",
    "content": "module.exports = {\r\n\tdefault: true,\r\n\tMD001: false,\r\n\tMD013: false,\r\n\tMD024: false,\r\n\tMD033: false,\r\n\tMD036: false,\r\n\tMD041: false,\r\n};\r\n"
  },
  {
    "path": ".prettierrc.json",
    "content": "{\n\t\"$schema\": \"https://json.schemastore.org/prettierrc\",\n\t\"semi\": true,\n\t\"singleQuote\": true,\n\t\"printWidth\": 100,\n\t\"useTabs\": true,\n\t\"tabWidth\": 3,\n\t\"vueIndentScriptAndStyle\": true\n}\n"
  },
  {
    "path": ".vscode/extensions.json",
    "content": "{\n  \"recommendations\": [\n    \"Vue.volar\",\n    \"vitest.explorer\",\n    \"dbaeumer.vscode-eslint\",\n    \"EditorConfig.EditorConfig\",\n    \"esbenp.prettier-vscode\"\n  ]\n}\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2025\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"
  },
  {
    "path": "README.md",
    "content": "## Documentation and demos\n\n**[Version 5 documentation](https://ragnarlotus.github.io/vue-flux-docs/documentation/v5/overview)**\n\n**[Version 6 documentation](https://ragnarlotus.github.io/vue-flux-docs/documentation/v6/overview)**\n\n**[Version 7 documentation](https://ragnarlotus.github.io/vue-flux-docs/documentation/v7/overview)**\n\n**[Version 7 demos](https://ragnarlotus.github.io/vue-flux-docs/demos/demos)**\n\n# Overview\n\nThis is an image slider developed with [vue](https://vuejs.org/) 3 which comes with 20 cool transitions out of the box.\n\n![npm](https://img.shields.io/npm/v/vue-flux/latest.svg?style=flat-square)\n![npm](https://img.shields.io/npm/dt/vue-flux.svg?style=flat-square)\n![npm bundle size (minified)](https://img.shields.io/bundlephobia/min/vue-flux/latest.svg?style=flat-square)\n![npm bundle size (minified + gzip)](https://img.shields.io/bundlephobia/minzip/vue-flux/latest.svg?style=flat-square)\n![GitHub issues](https://img.shields.io/github/issues-raw/ragnarlotus/vue-flux.svg?style=flat-square)\n![GitHub](https://img.shields.io/github/license/mashape/apistatus.svg?style=flat-square)\n\n## Features\n\n| Feature | Description |\n|---------|-------------|\n| Responsive | The slider and the images are adapted to container to fill it always |\n| Compatibility | Supported by all major browsers |\n| Expandable | You can add your custom transitions very easily |\n| Customization | Total customizable to suit most needs |\n| Gestures | Mobile friendly by gestures |\n| Functionality | You can use arrow keys to navigate. Switch to full screen |\n| Parallax | It includes a parallax component very easy to set up |\n\n## Quick start\n\nInstall and save the package.\n\n``` bash\nnpm install --save vue-flux@latest\n```\n\nAdd component. This one has all the complements, so you can remove the ones you don't want.\n\n``` html\n<script setup>\n   import { ref, shallowReactive } from 'vue';\n   import {\n      VueFlux,\n      FluxCaption,\n      FluxControls,\n      FluxIndex,\n      FluxPagination,\n      FluxPreloader,\n      Img,\n      Book,\n      Zip,\n   } from 'vue-flux';\n   import 'vue-flux/style.css';\n\n   const $vueFlux = ref();\n\n   const vfOptions = shallowReactive({\n      autoplay: true,\n   });\n\n   const vfRscs = shallowReactive([\n      new Img('URL1' 'img 1'),\n      new Img('URL2' 'img 2'),\n      new Img('URL3' 'img 3'),\n   ]);\n\n   const vfTransitions = shallowReactive([Book, Zip]);\n</script>\n\n<template>\n   <VueFlux\n      :options=\"vfOptions\"\n      :rscs=\"vfRscs\"\n      :transitions=\"vfTransitions\"\n      ref=\"$vueFlux\">\n\n      <template #preloader=\"preloaderProps\">\n         <FluxPreloader v-bind=\"preloaderProps\" />\n      </template>\n\n      <template #caption=\"captionProps\">\n         <FluxCaption v-bind=\"captionProps\" />\n      </template>\n\n      <template #controls=\"controlsProps\">\n         <FluxControls v-bind=\"controlsProps\" />\n      </template>\n\n      <template #pagination=\"paginationProps\">\n         <FluxPagination v-bind=\"paginationProps\" />\n      </template>\n\n      <template #index=\"indexProps\">\n         <FluxIndex v-bind=\"indexProps\" />\n      </template>\n   </VueFlux>\n\n   <button @click=\"$vueFlux.show('next')\">NEXT</button>\n</template>\n```\n\n## Performance\n\nWeight is about 60 KB so is pretty light having only the essential CSS. It also does not require a high end computer as animations are performed with CSS3 hardware acceleration.\n\n## Included transitions\n\n#### 2D transitions\n\n* Fade: fades from one image to next.\n* Kenburn: fades, zoom and moves current image to next.\n* Swipe: swipes the image to display next like uncovered with a curtain.\n* Slide: slides the image horizontally revealing the next.\n* Waterfall: divides the image in bars and drops them down in turns.\n* Zip: divides the image in bars and slides them up and down alternately like a zip.\n* Blinds 2D: divides the image in vertical bars that blinds and fades out.\n* Blocks 1: the image is split in blocks that shrink and fade out randomly.\n* Blocks 2: the image is split in blocks that shrink and fade out in wave from a corner to the opposite.\n* Concentric: a concentric effect is performed by rotating the image converted into circles.\n* Warp: a concentric effect is performed by rotating the image converted into circles in alternate direction.\n* Camera: from outside to inside the image is being circled in black like a camera.\n\n#### 3D transitions\n\n* Cube: turns the image to a side like if place in a cube.\n* Book: makes the effect of turning a page to display next image.\n* Fall: the image falls in front displaying next image.\n* Wave: makes the image 3D and divides it in slices that turn vertically to display the next image.\n* Blinds 3D: divides the image in vertical bars that blinds 180 deg to form the next image.\n* Round 1: the image is split in blocks that turn 180 deg horizontally to form next image.\n* Round 2: panels start to round vertically revealing the next image in upper arrow form leaving trail.\n* Explode: the image starts to explode from the center to outside.\n\n## Parallax\n\nAs simple as this.\n\n``` html\n<script setup>\n   import { FluxParallax, Img } from 'vue-flux';\n\n   const rsc = new Img('URL1' 'img 1');\n</script>\n\n<template>\n   <FluxParallax :rsc=\"rsc\" style=\"height: 300px;\">\n      <div>CONTENT</div>\n   </FluxParallax>\n</template>\n```\n\n## Troubleshooting\n\nIf you find yourself running into issues during installation or running the slider, please check our [documentation](https://ragnarlotus.github.io/vue-flux-docs/documentation/v7/overview). If still needs help open an [issue](https://github.com/ragnarlotus/vue-flux/issues/new). I will be happy to discuss how they can be solved.\n\n## Documentation\n\nYou can view the full documentation at the project's [documentation](https://ragnarlotus.github.io/vue-flux-docs/documentation/v7/overview) with examples and detailed information.\n\n## Changelog\n\nCheck the [changelog](https://ragnarlotus.github.io/vue-flux-docs/documentation/v7/changelog) for update info.\n\n## Inspiration\n\nThis slider was inspired by [Flux Slider](http://joelambert.co.uk/flux/).\n\n## Contributing\n\nContributions, questions and comments are all welcome and encouraged.\n\nDo not hesitate to send me your own transitions to add them to the slider.\n"
  },
  {
    "path": "env.d.ts",
    "content": "/// <reference types=\"vite/client\" />\n"
  },
  {
    "path": "eslint.config.ts",
    "content": "import { globalIgnores } from 'eslint/config';\nimport { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript';\nimport pluginVue from 'eslint-plugin-vue';\nimport pluginVitest from '@vitest/eslint-plugin';\nimport skipFormatting from '@vue/eslint-config-prettier/skip-formatting';\n\n// To allow more languages other than `ts` in `.vue` files, uncomment the following lines:\n// import { configureVueProject } from '@vue/eslint-config-typescript'\n// configureVueProject({ scriptLangs: ['ts', 'tsx'] })\n// More info at https://github.com/vuejs/eslint-config-typescript/#advanced-setup\n\nexport default defineConfigWithVueTs(\n\t{\n\t\tname: 'app/files-to-lint',\n\t\tfiles: ['**/*.{ts,mts,tsx,vue}'],\n\t},\n\n\tglobalIgnores(['**/dist/**', '**/dist-ssr/**', '**/coverage/**']),\n\n\tpluginVue.configs['flat/essential'],\n\tvueTsConfigs.recommended,\n\n\t{\n\t\t...pluginVitest.configs.recommended,\n\t\tfiles: ['src/**/__tests__/*'],\n\t},\n\tskipFormatting,\n\t{\n\t\trules: {\n\t\t\t'vue/multi-word-component-names': 'off',\n\t\t},\n\t},\n);\n"
  },
  {
    "path": "ia.txt",
    "content": "=== vue-flux IA bundle ===\nGenerated on: Fri Dec 12 06:10:34     2025\n\n===== FILE: package.json =====\n{\n\t\"name\": \"vue-flux\",\n\t\"version\": \"7.1.3\",\n\t\"type\": \"module\",\n\t\"description\": \"Vue image and other resources slider\",\n\t\"author\": \"ragnar lotus\",\n\t\"repository\": {\n\t\t\"type\": \"git\",\n\t\t\"url\": \"git+https://github.com/ragnarlotus/vue-flux.git\"\n\t},\n\t\"keywords\": [\n\t\t\"vue\",\n\t\t\"image\",\n\t\t\"slider\",\n\t\t\"carousel\",\n\t\t\"parallax\"\n\t],\n\t\"license\": \"MIT\",\n\t\"bugs\": \"https://github.com/ragnarlotus/vue-flux/issues\",\n\t\"homepage\": \"https://ragnarlotus.github.io/vue-flux-docs/\",\n\t\"main\": \"./dist/vue-flux.umd.cjs\",\n\t\"module\": \"./dist/vue-flux.js\",\n\t\"files\": [\n\t\t\"dist\"\n\t],\n\t\"types\": \"./dist/vue-flux.d.ts\",\n\t\"engines\": {\n\t\t\"node\": \"^20.19.0 || >=22.12.0\"\n\t},\n\t\"scripts\": {\n\t\t\"dev\": \"vite\",\n\t\t\"build\": \"run-p type-check \\\"build-only {@}\\\" --\",\n\t\t\"preview\": \"vite preview\",\n\t\t\"test:coverage\": \"vitest run --coverage --watch\",\n\t\t\"test:unit\": \"vitest\",\n\t\t\"build-only\": \"vite build\",\n\t\t\"type-check\": \"vue-tsc --build\",\n\t\t\"lint\": \"eslint . --fix\",\n\t\t\"format\": \"prettier --write src/\"\n\t},\n\t\"exports\": {\n\t\t\".\": {\n\t\t\t\"types\": \"./dist/vue-flux.d.ts\",\n\t\t\t\"import\": \"./dist/vue-flux.js\",\n\t\t\t\"require\": \"./dist/vue-flux.umd.cjs\"\n\t\t},\n\t\t\"./style.css\": \"./dist/vue-flux.css\",\n\t\t\"./complements\": {\n\t\t\t\"types\": \"./dist/complements/index.d.ts\",\n\t\t\t\"import\": \"./dist/complements/index.js\"\n\t\t},\n\t\t\"./transitions\": {\n\t\t\t\"types\": \"./dist/transitions/index.d.ts\",\n\t\t\t\"import\": \"./dist/transitions/index.js\"\n\t\t}\n\t},\n\t\"sideEffects\": [\n\t\t\"*.css\"\n\t],\n\t\"peerDependencies\": {\n\t\t\"vue\": \"^3.5.0\"\n\t},\n\t\"devDependencies\": {\n\t\t\"@tailwindcss/vite\": \"^4.1.17\",\n\t\t\"@tsconfig/node22\": \"^22.0.5\",\n\t\t\"@types/jsdom\": \"^27.0.0\",\n\t\t\"@types/node\": \"^25.0.0\",\n\t\t\"@vitejs/plugin-vue\": \"^6.0.2\",\n\t\t\"@vitest/coverage-v8\": \"^4.0.15\",\n\t\t\"@vitest/eslint-plugin\": \"^1.5.2\",\n\t\t\"@vue/eslint-config-prettier\": \"^10.2.0\",\n\t\t\"@vue/eslint-config-typescript\": \"^14.6.0\",\n\t\t\"@vue/test-utils\": \"^2.4.6\",\n\t\t\"@vue/tsconfig\": \"^0.8.1\",\n\t\t\"eslint\": \"^9.39.1\",\n\t\t\"eslint-plugin-vue\": \"~10.6.2\",\n\t\t\"jiti\": \"^2.6.1\",\n\t\t\"jsdom\": \"^27.3.0\",\n\t\t\"npm-run-all2\": \"^8.0.4\",\n\t\t\"prettier\": \"3.7.4\",\n\t\t\"sass\": \"^1.96.0\",\n\t\t\"tailwindcss\": \"^4.1.17\",\n\t\t\"typescript\": \"~5.9.3\",\n\t\t\"vite\": \"^7.2.7\",\n\t\t\"vite-plugin-dts\": \"^4.5.4\",\n\t\t\"vite-plugin-vue-devtools\": \"^8.0.5\",\n\t\t\"vitest\": \"^4.0.15\",\n\t\t\"vue\": \"^3.5.25\",\n\t\t\"vue-cosk\": \"^1.0.0\",\n\t\t\"vue-tsc\": \"^3.1.8\"\n\t}\n}\n\n\n===== FILE: vite.config.ts =====\nimport { fileURLToPath, URL } from 'node:url';\nimport { resolve } from 'node:path';\n\nimport { defineConfig } from 'vite';\nimport vue from '@vitejs/plugin-vue';\nimport tailwindcss from '@tailwindcss/vite';\nimport vueDevTools from 'vite-plugin-vue-devtools';\nimport dts from 'vite-plugin-dts';\n\n// https://vite.dev/config/\nexport default defineConfig({\n\tplugins: [\n\t\tvue(),\n\t\tvueDevTools(),\n\t\ttailwindcss(),\n\t\tdts({\n\t\t\ttsconfigPath: './tsconfig.build.json',\n\t\t\trollupTypes: true,\n\t\t}),\n\t],\n\tresolve: {\n\t\talias: {\n\t\t\t'@': fileURLToPath(new URL('./src', import.meta.url)),\n\t\t},\n\t},\n\tbuild: {\n\t\tcopyPublicDir: false,\n\t\tlib: {\n\t\t\tentry: resolve(__dirname, 'src/lib.ts'),\n\t\t\tname: 'VueFlux',\n\t\t\tfileName: 'vue-flux',\n\t\t},\n\t\trollupOptions: {\n\t\t\texternal: ['vue'],\n\t\t\toutput: {\n\t\t\t\tglobals: {\n\t\t\t\t\tvue: 'Vue',\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t},\n});\n\n\n===== FILE: vitest.config.ts =====\nimport { fileURLToPath } from 'node:url';\nimport { mergeConfig, defineConfig, configDefaults } from 'vitest/config';\nimport viteConfig from './vite.config';\n\nexport default mergeConfig(\n\tviteConfig,\n\tdefineConfig({\n\t\ttest: {\n\t\t\tglobals: true,\n\t\t\tenvironment: 'jsdom',\n\t\t\texclude: [...configDefaults.exclude, 'e2e/**'],\n\t\t\troot: fileURLToPath(new URL('./', import.meta.url)),\n\t\t},\n\t}),\n);\n\n\n===== FILE: tsconfig.json =====\n{\n\t\"files\": [],\n\t\"references\": [\n\t\t{\n\t\t\t\"path\": \"./tsconfig.node.json\"\n\t\t},\n\t\t{\n\t\t\t\"path\": \"./tsconfig.app.json\"\n\t\t},\n\t\t{\n\t\t\t\"path\": \"./tsconfig.vitest.json\"\n\t\t}\n\t],\n\t\"compilerOptions\": {\n\t\t\"types\": [\"vitest/globals\"]\n\t},\n\t\"exclude\": [\n\t\t\"src/App.vue\",\n\t\t\"src/main.ts\",\n\t\t\"node_modules\",\n\t\t\"dist\",\n\t\t\"src/**/*.test.ts\",\n\t\t\"src/**/*.test.tsx\",\n\t\t\"src/**/*.spec.ts\",\n\t\t\"src/**/*.spec.tsx\"\n\t]\n}\n\n\n===== FILE: src/App.vue =====\n<!-- eslint-disable @typescript-eslint/no-unused-vars -->\n<script setup lang=\"ts\">\n\timport { ref, type Ref } from 'vue';\n\n\t// Playgrounds\n\timport PgFluxImage from './playgrounds/PgFluxImage.vue';\n\timport PgFluxCube from './playgrounds/PgFluxCube.vue';\n\timport PgFluxGrid from './playgrounds/PgFluxGrid.vue';\n\timport PgFluxTransition from './playgrounds/PgFluxTransition.vue';\n\timport PgVueFlux from './playgrounds/PgVueFlux.vue';\n\timport PgFluxParallax from './playgrounds/PgFluxParallax.vue';\n\timport PgFluxParallaxOp from './playgrounds/PgFluxParallaxOp.vue';\n\timport PgFluxCaption from './playgrounds/PgFluxCaption.vue';\n\timport PgFluxControls from './playgrounds/PgFluxControls.vue';\n\timport PgFluxIndex from './playgrounds/PgFluxIndex.vue';\n\timport PgFluxPagination from './playgrounds/PgFluxPagination.vue';\n\timport PgFluxPreloader from './playgrounds/PgFluxPreloader.vue';\n\n\tconst $wrapper: Ref<null | HTMLDivElement> = ref(null);\n</script>\n\n<template>\n\t<main class=\"container mx-auto mb-4\">\n\t\t<div ref=\"$wrapper\" class=\"relative mx-auto\">\n\t\t\t<!-- <PgFluxImage /> -->\n\t\t\t<!-- <PgFluxCube /> -->\n\t\t\t<!-- <PgFluxGrid /> -->\n\t\t\t<!-- <PgFluxTransition /> -->\n\t\t\t<!-- <PgVueFlux /> -->\n\t\t\t<!-- <PgFluxParallax /> -->\n\t\t\t<PgFluxParallaxOp />\n\t\t\t<!-- <PgFluxCaption /> -->\n\t\t\t<!-- <PgFluxControls /> -->\n\t\t\t<!-- <PgFluxIndex /> -->\n\t\t\t<!-- <PgFluxPagination /> -->\n\t\t\t<!-- <PgFluxPreloader /> -->\n\t\t</div>\n\t</main>\n</template>\n\n\n===== FILE: src/assets/css/base.scss =====\nlabel {\n\tmargin-top: 12px;\n\tdisplay: block;\n\n\tspan {\n\t\tmargin-right: 6px;\n\t}\n}\n\n\n===== FILE: src/assets/css/main.css =====\n@import 'tailwindcss';\n@import './base.scss';\n\n\n===== FILE: src/complements/FluxCaption/FluxCaption.test.ts =====\nimport { Player, Timers } from '../../controllers';\nimport { mount } from '@vue/test-utils';\nimport FluxCaption from './FluxCaption.vue';\nimport emit from '../../components/VueFlux/__test__/emit';\nimport {\n\tvueFluxConfig,\n\tsetCurrentResource,\n\tsetCurrentTransition,\n} from '../__test__/PlayerHelper';\n\nvi.mock('../../controllers/Player/Player');\n\nconst defaultCaption = 'the caption';\n\ndescribe('complements: FluxCaption', () => {\n\tconst timers = new Timers();\n\n\tit('should mount properly without slot', () => {\n\t\tconst player = new Player(vueFluxConfig, timers, emit);\n\n\t\texpect(() => {\n\t\t\tmount(FluxCaption, {\n\t\t\t\tprops: {\n\t\t\t\t\tplayer,\n\t\t\t\t},\n\t\t\t});\n\t\t}).not.toThrow();\n\t});\n\n\tit('should not be visible if no caption', () => {\n\t\tconst player = new Player(vueFluxConfig, timers, emit);\n\n\t\tsetCurrentResource(player);\n\n\t\tconst wrapper = mount(FluxCaption, {\n\t\t\tprops: {\n\t\t\t\tplayer,\n\t\t\t},\n\t\t});\n\n\t\texpect(wrapper.html().includes('class=\"flux-caption\"')).toBeTruthy();\n\t});\n\n\tit('should not be visible if caption has no length', () => {\n\t\tconst player = new Player(vueFluxConfig, timers, emit);\n\n\t\tsetCurrentResource(player, '');\n\n\t\tconst wrapper = mount(FluxCaption, {\n\t\t\tprops: {\n\t\t\t\tplayer,\n\t\t\t},\n\t\t});\n\n\t\texpect(wrapper.html().includes('class=\"flux-caption\"')).toBeTruthy();\n\t});\n\n\tit('should not be visible if transition running', () => {\n\t\tconst player = new Player(vueFluxConfig, timers, emit);\n\n\t\tsetCurrentResource(player, defaultCaption);\n\t\tsetCurrentTransition(player);\n\n\t\tconst wrapper = mount(FluxCaption, {\n\t\t\tprops: {\n\t\t\t\tplayer,\n\t\t\t},\n\t\t});\n\n\t\texpect(wrapper.html().includes('class=\"flux-caption\"')).toBeTruthy();\n\t});\n\n\tit('should display the caption', () => {\n\t\tconst player = new Player(vueFluxConfig, timers, emit);\n\n\t\tsetCurrentResource(player, defaultCaption);\n\n\t\tconst wrapper = mount(FluxCaption, {\n\t\t\tprops: {\n\t\t\t\tplayer,\n\t\t\t},\n\t\t});\n\n\t\texpect(\n\t\t\twrapper.html().includes('class=\"flux-caption visible\"')\n\t\t).toBeTruthy();\n\t});\n\n\tit('should mount properly with slot', () => {\n\t\tconst player = new Player(vueFluxConfig, timers, emit);\n\n\t\tsetCurrentResource(player, defaultCaption);\n\n\t\tconst wrapper = mount(FluxCaption, {\n\t\t\tprops: {\n\t\t\t\tplayer,\n\t\t\t},\n\t\t\tslots: {\n\t\t\t\tdefault: `<h1>{{ params.caption }}</h1>`,\n\t\t\t},\n\t\t});\n\n\t\texpect(\n\t\t\twrapper.html().includes(`<h1>${defaultCaption}</h1>`)\n\t\t).toBeTruthy();\n\t});\n});\n\n\n===== FILE: src/complements/FluxCaption/FluxCaption.vue =====\n<script setup lang=\"ts\">\n\timport { computed } from 'vue';\n\timport { Player } from '../../controllers';\n\n\texport interface Props {\n\t\tplayer: Player;\n\t}\n\n\tconst props = defineProps<Props>();\n\n\tconst { resource, transition } = props.player;\n\n\tconst caption = computed<string>(() => {\n\t\tif (resource.current === null || resource.current.rsc.caption === null) {\n\t\t\treturn '&nbsp;';\n\t\t}\n\n\t\treturn resource.current.rsc.caption;\n\t});\n\n\tconst cssClasses = computed<string[]>(() => {\n\t\tconst classes = ['flux-caption'];\n\n\t\tif (\n\t\t\ttransition.current === null &&\n\t\t\tresource.current !== null &&\n\t\t\tresource.current.rsc.caption.length > 0\n\t\t) {\n\t\t\tclasses.push('visible');\n\t\t}\n\n\t\treturn classes;\n\t});\n</script>\n\n<template>\n\t<div :class=\"cssClasses\">\n\t\t<slot :caption=\"caption\">{{ caption }}</slot>\n\t</div>\n</template>\n\n<style lang=\"scss\">\n\t.vue-flux .flux-caption {\n\t\tflex: none;\n\t\twidth: 100%;\n\t\tfont-size: 0.8rem;\n\t\tline-height: 1.1rem;\n\t\tpadding: 6px;\n\t\tbox-sizing: border-box;\n\t\tcolor: white;\n\t\ttext-align: center;\n\t\tbackground-color: rgba(0, 0, 0, 0.65);\n\t\topacity: 0;\n\n\t\t&.visible {\n\t\t\topacity: 1;\n\t\t\ttransition: opacity 0.3s ease-in;\n\t\t}\n\t}\n</style>\n\n\n===== FILE: src/complements/FluxControls/FluxControls.test.ts =====\nimport { ref, type Ref } from 'vue';\nimport { Player, Timers } from '../../controllers';\nimport { Directions, Statuses } from '../../controllers/Player';\nimport * as Buttons from './buttons';\nimport FluxControls from './FluxControls.vue';\nimport { mount } from '@vue/test-utils';\nimport emit from '../../components/VueFlux/__test__/emit';\nimport { vueFluxConfig, setCurrentResource, setCurrentTransition } from '../__test__/PlayerHelper';\n\nvi.mock('../../controllers/Player/Player');\n\ndescribe('complements: FluxControls', () => {\n\tconst timers = new Timers();\n\tconst mouseOver: Ref<boolean> = ref(false);\n\n\tbeforeEach(() => {\n\t\tmouseOver.value = false;\n\t});\n\n\tit('should mount properly without slot', () => {\n\t\tconst player = new Player(vueFluxConfig, timers, emit);\n\n\t\texpect(() => {\n\t\t\tmount(FluxControls, {\n\t\t\t\tprops: {\n\t\t\t\t\tmouseOver,\n\t\t\t\t\tplayer,\n\t\t\t\t},\n\t\t\t});\n\t\t}).not.toThrow();\n\t});\n\n\tit('should not be visible if transition running', () => {\n\t\tconst player = new Player(vueFluxConfig, timers, emit);\n\n\t\tsetCurrentResource(player);\n\t\tsetCurrentTransition(player);\n\n\t\tconst wrapper = mount(FluxControls, {\n\t\t\tprops: {\n\t\t\t\tmouseOver,\n\t\t\t\tplayer,\n\t\t\t},\n\t\t});\n\n\t\texpect(wrapper.html().includes('class=\"flux-controls\"')).toBeFalsy();\n\t});\n\n\tit('should not be visible if transition running and mouse not moving', () => {\n\t\tconst player = new Player(vueFluxConfig, timers, emit);\n\n\t\tsetCurrentResource(player);\n\n\t\tconst wrapper = mount(FluxControls, {\n\t\t\tprops: {\n\t\t\t\tmouseOver,\n\t\t\t\tplayer,\n\t\t\t},\n\t\t});\n\n\t\texpect(wrapper.html().includes('class=\"flux-controls\"')).toBeFalsy();\n\t});\n\n\tit('should be visible if no transition running and mouse moving', () => {\n\t\tconst player = new Player(vueFluxConfig, timers, emit);\n\n\t\tsetCurrentResource(player);\n\n\t\tmouseOver.value = true;\n\n\t\tconst wrapper = mount(FluxControls, {\n\t\t\tprops: {\n\t\t\t\tmouseOver,\n\t\t\t\tplayer,\n\t\t\t},\n\t\t});\n\n\t\texpect(wrapper.html().includes('class=\"flux-controls\"')).toBeTruthy();\n\t});\n\n\tit('should display play button', () => {\n\t\tconst player = new Player(vueFluxConfig, timers, emit);\n\t\tplayer.status.value = Statuses.stopped;\n\n\t\tsetCurrentResource(player);\n\t\tmouseOver.value = true;\n\n\t\tconst wrapper = mount(FluxControls, {\n\t\t\tprops: {\n\t\t\t\tmouseOver,\n\t\t\t\tplayer,\n\t\t\t},\n\t\t});\n\n\t\texpect(() => {\n\t\t\twrapper.getComponent(Buttons.Play);\n\t\t}).not.toThrow();\n\t});\n\n\tit('should play when button pressed', async () => {\n\t\tconst player = new Player(vueFluxConfig, timers, emit);\n\t\tplayer.status.value = Statuses.stopped;\n\n\t\tsetCurrentResource(player);\n\t\tmouseOver.value = true;\n\n\t\tconst wrapper = mount(FluxControls, {\n\t\t\tprops: {\n\t\t\t\tmouseOver,\n\t\t\t\tplayer,\n\t\t\t},\n\t\t});\n\n\t\tawait wrapper.getComponent(Buttons.Play).trigger('click');\n\n\t\texpect(player.play).toHaveBeenCalledWith(Directions.next, expect.any(Number));\n\t});\n\n\tit('should display stop button', () => {\n\t\tconst player = new Player(vueFluxConfig, timers, emit);\n\t\tplayer.status.value = Statuses.playing;\n\n\t\tsetCurrentResource(player);\n\t\tmouseOver.value = true;\n\n\t\tconst wrapper = mount(FluxControls, {\n\t\t\tprops: {\n\t\t\t\tmouseOver,\n\t\t\t\tplayer,\n\t\t\t},\n\t\t});\n\n\t\texpect(() => {\n\t\t\twrapper.getComponent(Buttons.Stop);\n\t\t}).not.toThrow();\n\t});\n\n\tit('should stop when button pressed', async () => {\n\t\tconst player = new Player(vueFluxConfig, timers, emit);\n\t\tplayer.status.value = Statuses.playing;\n\n\t\tsetCurrentResource(player);\n\t\tmouseOver.value = true;\n\n\t\tconst wrapper = mount(FluxControls, {\n\t\t\tprops: {\n\t\t\t\tmouseOver,\n\t\t\t\tplayer,\n\t\t\t},\n\t\t});\n\n\t\tawait wrapper.getComponent(Buttons.Stop).trigger('click');\n\n\t\texpect(player.stop).toHaveBeenCalledOnce();\n\t});\n\n\tit('should display previous resource when button pressed', async () => {\n\t\tconst player = new Player(vueFluxConfig, timers, emit);\n\t\tplayer.status.value = Statuses.playing;\n\n\t\tsetCurrentResource(player);\n\t\tmouseOver.value = true;\n\n\t\tconst wrapper = mount(FluxControls, {\n\t\t\tprops: {\n\t\t\t\tmouseOver,\n\t\t\t\tplayer,\n\t\t\t},\n\t\t});\n\n\t\tawait wrapper.getComponent(Buttons.Prev).trigger('click');\n\n\t\texpect(player.show).toHaveBeenCalledWith(Directions.prev);\n\t});\n\n\tit('should display next resource when button pressed', async () => {\n\t\tconst player = new Player(vueFluxConfig, timers, emit);\n\t\tplayer.status.value = Statuses.playing;\n\n\t\tsetCurrentResource(player);\n\t\tmouseOver.value = true;\n\n\t\tconst wrapper = mount(FluxControls, {\n\t\t\tprops: {\n\t\t\t\tmouseOver,\n\t\t\t\tplayer,\n\t\t\t},\n\t\t});\n\n\t\tawait wrapper.getComponent(Buttons.Next).trigger('click');\n\n\t\texpect(player.show).toHaveBeenCalledWith(Directions.next);\n\t});\n});\n\n\n===== FILE: src/complements/FluxControls/FluxControls.vue =====\n<script setup lang=\"ts\">\n\timport { type Ref, computed, unref } from 'vue';\n\timport { Player, Directions } from '../../controllers/Player';\n\timport * as Buttons from './buttons';\n\timport { default as PlayerStatuses } from '../../controllers/Player/Statuses';\n\n\texport interface Props {\n\t\tmouseOver?: Ref<boolean>;\n\t\tplayer: Player;\n\t}\n\n\tconst props = withDefaults(defineProps<Props>(), {\n\t\tmouseOver: undefined,\n\t});\n\n\tconst visible = computed<boolean>(() => {\n\t\tif (props.player.resource.current === null) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif (props.mouseOver !== undefined && unref(props.mouseOver) === false) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t});\n</script>\n\n<template>\n\t<transition name=\"fade\">\n\t\t<div v-if=\"visible\" class=\"flux-controls\">\n\t\t\t<Buttons.Prev @click=\"player.show(Directions.prev)\" />\n\t\t\t<Buttons.Play\n\t\t\t\tv-if=\"(player.status.value || player.status) === PlayerStatuses.stopped\"\n\t\t\t\t@click=\"player.play(Directions.next, 1)\"\n\t\t\t/>\n\t\t\t<Buttons.Stop\n\t\t\t\tv-if=\"(player.status.value || player.status) === PlayerStatuses.playing\"\n\t\t\t\t@click=\"player.stop()\"\n\t\t\t/>\n\t\t\t<Buttons.Next @click=\"player.show(Directions.next)\" />\n\t\t</div>\n\t</transition>\n</template>\n\n<style lang=\"scss\">\n\t.vue-flux .flux-controls {\n\t\tflex: none;\n\t\tdisplay: flex;\n\t\tjustify-content: space-between;\n\n\t\t&.fade-enter,\n\t\t&.fade-leave-to {\n\t\t\topacity: 0;\n\t\t}\n\n\t\t&.fade-enter-active,\n\t\t&.fade-leave-active {\n\t\t\ttransition: opacity 0.3s ease-in;\n\t\t}\n\n\t\t.prev {\n\t\t\tmargin-left: 4%;\n\t\t}\n\n\t\t.next {\n\t\t\tmargin-right: 4%;\n\t\t}\n\t}\n</style>\n\n\n===== FILE: src/complements/FluxControls/buttons/Next.vue =====\n<script setup lang=\"ts\">\n\timport { FluxButton } from '../../../components';\n</script>\n\n<template>\n\t<FluxButton class=\"next top right\">\n\t\t<polyline points=\"36,18 78,50 36,82\" />\n\t</FluxButton>\n</template>\n\n\n===== FILE: src/complements/FluxControls/buttons/Play.vue =====\n<script setup lang=\"ts\">\n\timport { FluxButton } from '../../../components';\n</script>\n\n<template>\n\t<FluxButton class=\"play\">\n\t\t<polygon points=\"32,12 82,50 32,88\" />\n\t</FluxButton>\n</template>\n\n\n===== FILE: src/complements/FluxControls/buttons/Prev.vue =====\n<script setup lang=\"ts\">\n\timport { FluxButton } from '../../../components';\n</script>\n\n<template>\n\t<FluxButton class=\"prev top left\">\n\t\t<polyline points=\"64,18 22,50 64,82\" />\n\t</FluxButton>\n</template>\n\n\n===== FILE: src/complements/FluxControls/buttons/Stop.vue =====\n<script setup lang=\"ts\">\n\timport { FluxButton } from '../../../components';\n</script>\n\n<template>\n\t<FluxButton class=\"pause\">\n\t\t<line x1=\"32\" y1=\"22\" x2=\"32\" y2=\"78\" />\n\t\t<line x1=\"68\" y1=\"22\" x2=\"68\" y2=\"78\" />\n\t</FluxButton>\n</template>\n\n\n===== FILE: src/complements/FluxControls/buttons/index.ts =====\nexport { default as Prev } from './Prev.vue';\nexport { default as Play } from './Play.vue';\nexport { default as Stop } from './Stop.vue';\nexport { default as Next } from './Next.vue';\n\n\n===== FILE: src/complements/FluxIndex/Button/Button.test.ts =====\nimport { type Ref, ref } from 'vue';\nimport { mount } from '@vue/test-utils';\nimport Button from './Button.vue';\n\ndescribe('complements: FluxIndex Button', () => {\n\tconst mouseOver: Ref<boolean> = ref(false);\n\n\tbeforeEach(() => {\n\t\tmouseOver.value = false;\n\t});\n\n\tit('mounts properly', () => {\n\t\texpect(() => {\n\t\t\tmount(Button, {\n\t\t\t\tprops: {\n\t\t\t\t\tmouseOver,\n\t\t\t\t},\n\t\t\t});\n\t\t}).not.toThrow();\n\t});\n\n\tit('is visible when mouse over', () => {\n\t\tmouseOver.value = true;\n\n\t\tconst wrapper = mount(Button, {\n\t\t\tprops: {\n\t\t\t\tmouseOver,\n\t\t\t},\n\t\t});\n\n\t\texpect(wrapper.html().includes('toggle bottom left')).toBeTruthy();\n\t});\n\n\tit('is NOT visible when mouse NOT over', () => {\n\t\tconst wrapper = mount(Button, {\n\t\t\tprops: {\n\t\t\t\tmouseOver,\n\t\t\t},\n\t\t});\n\n\t\texpect(wrapper.html().includes('toggle bottom left')).toBeFalsy();\n\t});\n});\n\n\n===== FILE: src/complements/FluxIndex/Button/Button.vue =====\n<script setup lang=\"ts\">\n\timport { type Ref, computed, unref } from 'vue';\n\timport { FluxButton } from '../../../components';\n\n\tinterface Props {\n\t\tmouseOver?: Ref<boolean>;\n\t}\n\n\tconst props = withDefaults(defineProps<Props>(), {\n\t\tmouseOver: undefined,\n\t});\n\n\tconst visible = computed<boolean>(() => [true, undefined].includes(unref(props.mouseOver)));\n</script>\n\n<template>\n\t<transition name=\"fade\">\n\t\t<FluxButton v-if=\"visible\" class=\"toggle bottom left\">\n\t\t\t<rect x=\"17.5\" y=\"17.5\" width=\"12px\" height=\"12px\" />\n\t\t\t<rect x=\"17.5\" y=\"43\" width=\"12px\" height=\"12px\" />\n\t\t\t<rect x=\"17.5\" y=\"68.5\" width=\"12px\" height=\"12px\" />\n\t\t\t<rect x=\"43\" y=\"17.5\" width=\"12px\" height=\"12px\" />\n\t\t\t<rect x=\"43\" y=\"43\" width=\"12px\" height=\"12px\" />\n\t\t\t<rect x=\"43\" y=\"68.5\" width=\"12px\" height=\"12px\" />\n\t\t\t<rect x=\"68.5\" y=\"17.5\" width=\"12px\" height=\"12px\" />\n\t\t\t<rect x=\"68.5\" y=\"43\" width=\"12px\" height=\"12px\" />\n\t\t\t<rect x=\"68.5\" y=\"68.5\" width=\"12px\" height=\"12px\" />\n\t\t</FluxButton>\n\t</transition>\n</template>\n\n<style lang=\"scss\">\n\t.vue-flux .flux-index {\n\t\t.fade-enter,\n\t\t.fade-leave-to {\n\t\t\topacity: 0;\n\t\t}\n\n\t\t.fade-enter-active,\n\t\t.fade-leave-active {\n\t\t\ttransition: opacity 0.3s ease-in;\n\t\t}\n\t}\n</style>\n\n\n===== FILE: src/complements/FluxIndex/FluxIndex.vue =====\n<script setup lang=\"ts\">\n\timport { ref, computed, type Ref } from 'vue';\n\timport { Size } from '../../shared';\n\timport { Player } from '../../controllers';\n\timport Button from './Button/Button.vue';\n\timport List from './List/List.vue';\n\n\texport interface Props {\n\t\tmouseOver?: Ref<boolean>;\n\t\tdisplaySize: Size;\n\t\tplayer: Player;\n\t}\n\n\tconst props = withDefaults(defineProps<Props>(), {\n\t\tmouseOver: undefined,\n\t});\n\n\tconst $fluxIndexList: Ref<null | InstanceType<typeof List>> = ref(null);\n\n\tconst visible = computed<boolean>(() => props.player.resources.list.length > 0);\n</script>\n\n<template>\n\t<div v-if=\"visible\" class=\"flux-index\">\n\t\t<Button v-if=\"mouseOver\" :mouse-over=\"mouseOver\" @click=\"$fluxIndexList?.show()\" />\n\n\t\t<List\n\t\t\tref=\"$fluxIndexList\"\n\t\t\t:display-size=\"displaySize\"\n\t\t\t:player=\"player\"\n\t\t\t:mouse-over=\"mouseOver\"\n\t\t/>\n\t</div>\n</template>\n\n<style lang=\"scss\">\n\t.vue-flux .flux-index {\n\t\tflex: none;\n\t\tmargin-bottom: 2%;\n\t\tfont-size: 0;\n\t\ttext-align: center;\n\n\t\tnav {\n\t\t\tposition: absolute;\n\t\t\ttop: 0;\n\t\t\tleft: 0;\n\t\t\tright: 0;\n\t\t\tbottom: 0;\n\t\t\tdisplay: block;\n\t\t\tmargin: 0;\n\t\t\toverflow: hidden;\n\t\t\tvisibility: hidden;\n\t\t}\n\n\t\tnav.visible {\n\t\t\tz-index: 101;\n\t\t\tvisibility: visible;\n\t\t}\n\n\t\tul {\n\t\t\tdisplay: block;\n\t\t\theight: 100%;\n\t\t\tmargin: 0;\n\t\t\tmargin-top: 100%;\n\t\t\tpadding: 24px 0 0 24px;\n\t\t\tlist-style-type: none;\n\t\t\ttext-align: center;\n\t\t\toverflow-y: auto;\n\t\t\tbackground-color: black;\n\t\t\ttransition: all 0.5s linear;\n\t\t\tfont-size: 0;\n\t\t}\n\t}\n</style>\n\n\n===== FILE: src/complements/FluxIndex/List/List.test.ts =====\nimport { type Ref, ref } from 'vue';\nimport { mount } from '@vue/test-utils';\nimport { Player, Timers } from '../../../controllers';\nimport List from './List.vue';\nimport { Size } from '../../../shared';\nimport emit from '../../../components/VueFlux/__test__/emit';\nimport { vueFluxConfig, setCurrentResource } from '../../__test__/PlayerHelper';\nimport Thumb from '../Thumb/Thumb.vue';\nimport ResourceFactory from '../../../resources/__test__/ResourceFactory';\n\nvi.mock('../../../resources/Img/Img');\nvi.mock('../../../shared/ResourceLoader/ResourceLoader');\nvi.mock('../../../controllers/Player/Player');\n\ndescribe('complements: FluxIndex List', () => {\n\tconst timers = new Timers();\n\tconst displaySize: Size = new Size({ width: 640, height: 360 });\n\tconst mouseOver: Ref<boolean> = ref(false);\n\n\tbeforeEach(() => {\n\t\tmouseOver.value = false;\n\t});\n\n\tit('mounts properly', () => {\n\t\tconst player = new Player(vueFluxConfig, timers, emit);\n\n\t\texpect(() => {\n\t\t\tmount(List, {\n\t\t\t\tprops: {\n\t\t\t\t\tdisplaySize,\n\t\t\t\t\tplayer,\n\t\t\t\t\tmouseOver,\n\t\t\t\t},\n\t\t\t});\n\t\t}).not.toThrow();\n\t});\n\n\tit('is not visible by default', async () => {\n\t\tmouseOver.value = true;\n\t\tconst player = new Player(vueFluxConfig, timers, emit);\n\n\t\tconst wrapper = mount(List, {\n\t\t\tprops: {\n\t\t\t\tdisplaySize,\n\t\t\t\tplayer,\n\t\t\t\tmouseOver,\n\t\t\t},\n\t\t});\n\n\t\texpect(wrapper.html().includes('nav class=\"\"')).toBeTruthy();\n\t});\n\n\tit('shows the list when button clicked', async () => {\n\t\tmouseOver.value = true;\n\t\tconst player = new Player(vueFluxConfig, timers, emit);\n\n\t\tconst wrapper = mount(List, {\n\t\t\tprops: {\n\t\t\t\tdisplaySize,\n\t\t\t\tplayer,\n\t\t\t\tmouseOver,\n\t\t\t},\n\t\t});\n\n\t\tawait wrapper.vm.show();\n\n\t\texpect(wrapper.html().includes('nav class=\"visible\"')).toBeTruthy();\n\t});\n\n\tit('does nothing if clicked resource is the same as current resource', async () => {\n\t\tmouseOver.value = true;\n\t\tconst player = new Player(vueFluxConfig, timers, emit);\n\t\tconst resources = ResourceFactory.create(10);\n\t\tawait player.resources.update(resources, 10, displaySize);\n\n\t\tsetCurrentResource(player);\n\n\t\tconst wrapper = mount(List, {\n\t\t\tprops: {\n\t\t\t\tdisplaySize,\n\t\t\t\tplayer,\n\t\t\t\tmouseOver,\n\t\t\t},\n\t\t});\n\n\t\tawait wrapper.find({ ref: '$list' }).findAllComponents(Thumb)[0].trigger('click');\n\n\t\texpect(player.show).not.toHaveBeenCalled();\n\t});\n});\n\n\n===== FILE: src/complements/FluxIndex/List/List.vue =====\n<script setup lang=\"ts\">\n\timport { type Ref, computed, nextTick, ref } from 'vue';\n\timport { Player } from '../../../controllers';\n\timport Thumb from '../Thumb/Thumb.vue';\n\timport { Size } from '../../../shared';\n\timport useThumbs from '../Thumb/useThumbs';\n\n\texport interface Props {\n\t\tdisplaySize: Size;\n\t\tplayer: Player;\n\t\tmouseOver?: Ref<boolean>;\n\t}\n\n\tconst props = withDefaults(defineProps<Props>(), {\n\t\tmouseOver: undefined,\n\t});\n\n\tconst $list: Ref<null | HTMLUListElement> = ref(null);\n\n\tconst animationTime = 500;\n\tconst visible: Ref<boolean> = ref(false);\n\n\tconst listClass = computed<string[]>(() => {\n\t\tconst classes = [];\n\n\t\tif (visible.value) {\n\t\t\tclasses.push('visible');\n\t\t}\n\n\t\treturn classes;\n\t});\n\n\tasync function show() {\n\t\tif ($list.value === null) {\n\t\t\treturn;\n\t\t}\n\n\t\tprops.player.stop();\n\t\tvisible.value = true;\n\n\t\tawait nextTick();\n\n\t\t// eslint-disable-next-line @typescript-eslint/no-unused-expressions\n\t\t$list.value.clientHeight;\n\t\t$list.value.style.marginTop = '0';\n\t}\n\n\tfunction hide(resourceIndex: null | number) {\n\t\tif ($list.value === null) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (props.player.resource.current?.index === resourceIndex) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (props.mouseOver !== undefined) {\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-unused-expressions\n\t\t\t$list.value.clientHeight;\n\t\t\t$list.value.style.marginTop = '100%';\n\t\t}\n\n\t\tsetTimeout(() => {\n\t\t\tvisible.value = false;\n\n\t\t\tif (resourceIndex !== null) {\n\t\t\t\tprops.player.show(resourceIndex);\n\t\t\t}\n\t\t}, animationTime);\n\t}\n\n\tconst thumbs = useThumbs(props.displaySize, props.player);\n\n\tdefineExpose({ show });\n</script>\n\n<template>\n\t<nav :class=\"listClass\" @click=\"hide(null)\">\n\t\t<ul ref=\"$list\">\n\t\t\t<Thumb\n\t\t\t\tv-for=\"(rsc, index) in player.resources!.list\"\n\t\t\t\t:key=\"index\"\n\t\t\t\t:rsc=\"rsc.resource\"\n\t\t\t\t:size=\"thumbs.size\"\n\t\t\t\t:class=\"thumbs.getClass(index)\"\n\t\t\t\t@click=\"hide(index)\"\n\t\t\t/>\n\t\t</ul>\n\t</nav>\n</template>\n\n<style lang=\"scss\">\n\t.vue-flux .flux-index {\n\t\tnav {\n\t\t\tposition: absolute;\n\t\t\ttop: 0;\n\t\t\tleft: 0;\n\t\t\tright: 0;\n\t\t\tbottom: 0;\n\t\t\tdisplay: block;\n\t\t\tmargin: 0;\n\t\t\toverflow: hidden;\n\t\t\tvisibility: hidden;\n\t\t}\n\n\t\tnav.visible {\n\t\t\tz-index: 101;\n\t\t\tvisibility: visible;\n\t\t}\n\n\t\tul {\n\t\t\tdisplay: block;\n\t\t\theight: 100%;\n\t\t\tmargin: 0;\n\t\t\tmargin-top: 100%;\n\t\t\tpadding: 24px 0 0 24px;\n\t\t\tlist-style-type: none;\n\t\t\ttext-align: center;\n\t\t\toverflow-y: auto;\n\t\t\tbackground-color: black;\n\t\t\ttransition: all 0.5s linear;\n\t\t\tfont-size: 0;\n\t\t}\n\t}\n</style>\n\n\n===== FILE: src/complements/FluxIndex/Thumb/Thumb.vue =====\n<script setup lang=\"ts\">\n\timport type { Ref } from 'vue';\n\timport { Resource } from '../../../resources';\n\timport { Size } from '../../../shared';\n\n\texport interface Props {\n\t\trsc: Resource;\n\t\tsize: Ref<Size>;\n\t}\n\n\tdefineProps<Props>();\n</script>\n\n<template>\n\t<li>\n\t\t<component\n\t\t\t:is=\"rsc.transition.component\"\n\t\t\t:rsc=\"rsc\"\n\t\t\t:size=\"size.value\"\n\t\t\t:title=\"rsc.caption\"\n\t\t/>\n\t</li>\n</template>\n\n<style lang=\"scss\">\n\t.vue-flux .flux-index li {\n\t\tposition: relative;\n\t\tdisplay: inline-block;\n\t\tbox-sizing: content-box;\n\t\tmargin: 0 24px 24px 0;\n\t\tcursor: pointer;\n\t\ttransition: all 0.3s ease;\n\n\t\t&:hover {\n\t\t\tbox-shadow: 0px 0px 3px 2px rgba(255, 255, 255, 0.6);\n\t\t}\n\n\t\t&.current {\n\t\t\tcursor: auto;\n\t\t\tborder: 1px solid white;\n\t\t\tbox-shadow: none;\n\t\t}\n\t}\n</style>\n\n\n===== FILE: src/complements/FluxIndex/Thumb/useThumbs.ts =====\nimport { computed } from 'vue';\nimport { Player } from '../../../controllers';\nimport { Size } from '../../../shared';\n\nexport default function useThumbs(displaySize: Size, player: Player) {\n\tconst size = computed<Size>(() => {\n\t\tlet { width, height } = displaySize.toValue();\n\n\t\twidth = width! / 4.2;\n\t\theight = (width * 90) / 160;\n\n\t\tif (width > 160) {\n\t\t\twidth = 160;\n\t\t\theight = 90;\n\t\t}\n\n\t\treturn new Size({\n\t\t\twidth,\n\t\t\theight,\n\t\t});\n\t});\n\n\tfunction getClass(index: number) {\n\t\tconst { current } = player.resource;\n\n\t\tif (current === null) {\n\t\t\treturn '';\n\t\t}\n\n\t\tif (current.index !== index) {\n\t\t\treturn '';\n\t\t}\n\n\t\treturn 'current';\n\t}\n\n\treturn { size, getClass };\n}\n\n\n===== FILE: src/complements/FluxPagination/FluxPagination.vue =====\n<script setup lang=\"ts\">\n\timport { computed } from 'vue';\n\timport type { ResourceWithOptions } from '../../resources';\n\timport { Player } from '../../controllers';\n\n\texport interface Props {\n\t\tplayer: Player;\n\t}\n\n\tconst props = defineProps<Props>();\n\n\tconst {\n\t\tplayer: { resources, resource, transition },\n\t} = props;\n\n\tconst visible = computed<boolean>(() => resources.list.length > 0);\n\n\tconst getTitle = (rsc: ResourceWithOptions) => {\n\t\treturn rsc.resource.caption;\n\t};\n\n\tconst getCssClass = (index: number, itemCLass: string) => {\n\t\tconst classes = [itemCLass];\n\n\t\tlet active = resource.current?.index === index;\n\n\t\tif (transition.current !== null) {\n\t\t\tactive = false;\n\t\t}\n\n\t\tif (active === true) {\n\t\t\tclasses.push('active');\n\t\t}\n\n\t\treturn classes;\n\t};\n</script>\n\n<template>\n\t<nav v-if=\"visible\" class=\"flux-pagination\">\n\t\t<ul>\n\t\t\t<li v-for=\"(rsc, index) in player.resources.list\" :key=\"index\">\n\t\t\t\t<slot\n\t\t\t\t\t:index=\"index\"\n\t\t\t\t\t:rsc=\"rsc\"\n\t\t\t\t\t:title=\"getTitle(rsc)\"\n\t\t\t\t\t:css-class=\"getCssClass(index, 'custom-pagination-item')\"\n\t\t\t\t>\n\t\t\t\t\t<span\n\t\t\t\t\t\t:title=\"getTitle(rsc)\"\n\t\t\t\t\t\t:class=\"getCssClass(index, 'pagination-item')\"\n\t\t\t\t\t\t@click=\"player.show(index)\"\n\t\t\t\t\t/>\n\t\t\t\t</slot>\n\t\t\t</li>\n\t\t</ul>\n\t</nav>\n</template>\n\n<style lang=\"scss\">\n\t.vue-flux .flux-pagination {\n\t\tflex: none;\n\n\t\tul {\n\t\t\tdisplay: flex;\n\t\t\tflex-wrap: wrap;\n\t\t\tjustify-content: center;\n\t\t\tmargin: 0;\n\t\t\tpadding: 0;\n\t\t\tlist-style-type: none;\n\t\t\ttext-align: center;\n\t\t\tposition: relative;\n\t\t}\n\n\t\tli {\n\t\t\tdisplay: block;\n\t\t\tmargin: 0 1% 1.5% 1%;\n\t\t\tcursor: pointer;\n\t\t\twidth: 2%;\n\t\t\theight: 0;\n\t\t\tmin-width: 10px;\n\t\t\tmin-height: 10px;\n\t\t\tpadding-bottom: 2%;\n\t\t\tposition: relative;\n\t\t\tbox-sizing: border-box;\n\t\t}\n\n\t\t.pagination-item {\n\t\t\tposition: absolute;\n\t\t\ttop: 0;\n\t\t\tleft: 0;\n\t\t\tright: 0;\n\t\t\tbottom: 0;\n\t\t\tbox-sizing: border-box;\n\t\t\tborder: 2px solid #fff;\n\t\t\tborder-radius: 50%;\n\t\t\tbackground-color: rgba(0, 0, 0, 0.7);\n\t\t\ttransition:\n\t\t\t\tbackground-color 0.2s ease-in,\n\t\t\t\tborder 0.2s ease-in;\n\n\t\t\t&:hover {\n\t\t\t\tborder-color: black;\n\t\t\t\tbackground-color: white;\n\t\t\t}\n\n\t\t\t&.active {\n\t\t\t\tborder-color: white;\n\t\t\t\tbackground-color: white;\n\t\t\t}\n\t\t}\n\t}\n</style>\n\n\n===== FILE: src/complements/FluxPreloader/FluxPreloader.vue =====\n<script setup lang=\"ts\">\n\timport type { Ref } from 'vue';\n\timport { ResourceLoader } from '../../shared';\n\n\texport interface Props {\n\t\tloader: Ref<null | ResourceLoader>;\n\t}\n\n\tdefineProps<Props>();\n</script>\n\n<template>\n\t<div class=\"preloader\">\n\t\t<slot\n\t\t\t:loader=\"loader\"\n\t\t\t:preloading=\"loader.value?.preLoading.length\"\n\t\t\t:lazyloading=\"loader.value?.lazyLoading.length\"\n\t\t\t:pct=\"loader.value?.progress\"\n\t\t>\n\t\t\t<div v-if=\"loader.value?.preLoading.length\" class=\"spinner\">\n\t\t\t\t<div class=\"pct\">{{ loader.value?.progress }}%</div>\n\t\t\t\t<div class=\"border\" />\n\t\t\t</div>\n\t\t</slot>\n\t</div>\n</template>\n\n<style lang=\"scss\">\n\t.vue-flux .preloader {\n\t\tposition: absolute;\n\t\ttop: 0;\n\t\tright: 0;\n\t\tbottom: 0;\n\t\tleft: 0;\n\t\tz-index: -1;\n\n\t\t.spinner {\n\t\t\tposition: absolute;\n\t\t\ttop: 50%;\n\t\t\tleft: 50%;\n\t\t\tmargin-top: -40px;\n\t\t\tmargin-left: -40px;\n\t\t\twidth: 80px;\n\t\t\theight: 80px;\n\t\t\tz-index: 14;\n\n\t\t\t.pct {\n\t\t\t\tposition: absolute;\n\t\t\t\tright: 0;\n\t\t\t\tleft: 0;\n\t\t\t\theight: 80px;\n\t\t\t\tline-height: 80px;\n\t\t\t\ttext-align: center;\n\t\t\t\tfont-weight: bold;\n\t\t\t\tz-index: 1;\n\t\t\t}\n\n\t\t\t.border {\n\t\t\t\tbox-sizing: border-box;\n\t\t\t\twidth: 100%;\n\t\t\t\theight: 100%;\n\t\t\t\tborder: 14px solid #f3f3f3;\n\t\t\t\tborder-top-color: #3498db;\n\t\t\t\tborder-bottom-color: #3498db;\n\t\t\t\tborder-radius: 50%;\n\t\t\t\tbackground-color: #f3f3f3;\n\t\t\t\tanimation: spin 2s linear infinite;\n\t\t\t}\n\t\t}\n\n\t\t@keyframes spin {\n\t\t\t0% {\n\t\t\t\ttransform: rotate(0deg);\n\t\t\t}\n\t\t\t100% {\n\t\t\t\ttransform: rotate(360deg);\n\t\t\t}\n\t\t}\n\t}\n</style>\n\n\n===== FILE: src/complements/__test__/PlayerHelper.ts =====\nimport type { VueFluxConfig } from '../../components/VueFlux/types';\nimport { Player } from '../../controllers/Player';\nimport type { ResourceIndex } from '../../repositories/Resources/types';\nimport type { TransitionIndex } from '../../repositories/Transitions/types';\nimport { Img } from '../../resources';\nimport { Blinds2D } from '../../transitions';\n\nexport const vueFluxConfig = {\n\tallowFullscreen: false,\n\tallowToSkipTransition: true,\n\taspectRatio: '16:9',\n\tautohideTime: 2500,\n\tautoplay: false,\n\tbindKeys: false,\n\tdelay: 5000,\n\tenableGestures: false,\n\tinfinite: true,\n\tlazyLoad: true,\n\tlazyLoadAfter: 5,\n} as VueFluxConfig;\n\nexport function setCurrentResource(player: Player, caption?: string) {\n\tplayer.resource.current = {\n\t\tindex: 0,\n\t\trsc: new Img('url', caption),\n\t\toptions: {},\n\t} as ResourceIndex;\n}\n\nexport function setCurrentTransition(player: Player) {\n\tplayer.transition.current = {\n\t\tindex: 0,\n\t\tcomponent: Blinds2D,\n\t\toptions: {},\n\t} as TransitionIndex;\n}\n\n\n===== FILE: src/complements/index.ts =====\nexport { default as FluxCaption } from './FluxCaption/FluxCaption.vue';\nexport { default as FluxControls } from './FluxControls/FluxControls.vue';\nexport { default as FluxIndex } from './FluxIndex/FluxIndex.vue';\nexport { default as FluxPagination } from './FluxPagination/FluxPagination.vue';\nexport { default as FluxPreloader } from './FluxPreloader/FluxPreloader.vue';\n\n\n===== FILE: src/components/FluxButton/FluxButton.test.ts =====\nimport FluxButton from './FluxButton.vue';\nimport { mount } from '@vue/test-utils';\n\ndescribe('component: FluxButton', () => {\n\tit('should mount properly', () => {\n\t\tconst nextLine = '<polyline points=\"36,18 78,50 36,82\" />';\n\n\t\tconst wrapper = mount(FluxButton, {\n\t\t\tslots: {\n\t\t\t\tdefault: nextLine,\n\t\t\t},\n\t\t});\n\n\t\texpect(\n\t\t\twrapper.html().includes('<polyline points=\"36,18 78,50 36,82\"')\n\t\t).toBeTruthy();\n\t});\n});\n\n\n===== FILE: src/components/FluxButton/FluxButton.vue =====\n<template>\n\t<button type=\"button\" class=\"flux-button\" style=\"outline: 0\">\n\t\t<svg\n\t\t\tviewBox=\"0 0 100 100\"\n\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\tversion=\"1.1\"\n\t\t>\n\t\t\t<circle cx=\"50\" cy=\"50\" r=\"50\" />\n\t\t\t<svg viewBox=\"-20 -20 140 140\">\n\t\t\t\t<slot />\n\t\t\t</svg>\n\t\t</svg>\n\t</button>\n</template>\n\n<style lang=\"scss\">\n\t.vue-flux .flux-button {\n\t\tpadding: 0;\n\t\twidth: 6%;\n\t\tmin-width: 26px;\n\t\tmin-height: 26px;\n\t\tmax-width: 40px;\n\t\tmax-height: 40px;\n\t}\n\n\t.flux-button {\n\t\tborder: 0;\n\t\tcursor: pointer;\n\t\tbackground-color: transparent;\n\n\t\t&:hover {\n\t\t\t> svg {\n\t\t\t\tline,\n\t\t\t\tpolyline {\n\t\t\t\t\tstroke: yellow;\n\t\t\t\t}\n\n\t\t\t\trect,\n\t\t\t\tpolygon {\n\t\t\t\t\tfill: yellow;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t> svg {\n\t\t\twidth: 100%;\n\n\t\t\t> circle {\n\t\t\t\tfill: rgba(0, 0, 0, 0.7);\n\t\t\t}\n\n\t\t\tline,\n\t\t\tpolyline,\n\t\t\trect,\n\t\t\tpolygon {\n\t\t\t\tstroke-linecap: round;\n\t\t\t\tstroke-linejoin: round;\n\t\t\t\tstroke: white;\n\t\t\t\tstroke-width: 14;\n\t\t\t\tfill: none;\n\t\t\t}\n\n\t\t\trect,\n\t\t\tpolygon {\n\t\t\t\tfill: white;\n\t\t\t\tstroke-width: 0;\n\t\t\t}\n\t\t}\n\t}\n</style>\n\n\n===== FILE: src/components/FluxCube/FluxCube.vue =====\n<script setup lang=\"ts\">\n\timport { ref, reactive, computed, onBeforeUpdate } from 'vue';\n\timport useComponent from '../useComponent';\n\timport type { FluxCubeProps, SidesComponents, Turn } from './types';\n\timport { Size } from '../../shared';\n\timport type { ComponentStyles, FluxComponent } from '../types';\n\timport SideTransformFactory from './factories/SideTransformFactory';\n\timport CubeFactory from './factories/CubeFactory';\n\timport Sides from './Sides';\n\n\tconst props = withDefaults(defineProps<FluxCubeProps>(), {\n\t\trscs: () => ({}),\n\t\tcolors: () => ({}),\n\t\toffsets: () => ({}),\n\t\tdepth: 0,\n\t\tviewSize: () => new Size(),\n\t});\n\n\tconst $el = ref(null);\n\n\tconst transformOrigin = computed(() =>\n\t\tprops.origin !== undefined ? props.origin : `center center -${props.depth / 2}px`,\n\t);\n\n\tconst componentStyles: ComponentStyles = reactive({\n\t\tbase: {\n\t\t\ttransformStyle: 'preserve-3d',\n\t\t\ttransformOrigin: transformOrigin,\n\t\t},\n\t});\n\n\tconst { style, setCss, transform, show, hide } = useComponent($el, props, componentStyles);\n\n\tconst sideTransformFactory = computed(\n\t\t() => new SideTransformFactory(props.depth, props.size, props.viewSize),\n\t);\n\n\tconst sides = computed(() =>\n\t\tCubeFactory.getSidesProps(\n\t\t\tsideTransformFactory.value,\n\t\t\tprops.color,\n\t\t\tprops.colors,\n\t\t\tprops.rsc,\n\t\t\tprops.rscs,\n\t\t\tprops.offset,\n\t\t\tprops.offsets,\n\t\t),\n\t);\n\n\tconst $sides: SidesComponents = reactive({});\n\n\tonBeforeUpdate(() => {\n\t\tObject.assign($sides, {\n\t\t\t[Sides.front]: undefined,\n\t\t\t[Sides.back]: undefined,\n\t\t\t[Sides.left]: undefined,\n\t\t\t[Sides.right]: undefined,\n\t\t\t[Sides.top]: undefined,\n\t\t\t[Sides.bottom]: undefined,\n\t\t});\n\t});\n\n\tconst turn = (turn: Turn) =>\n\t\ttransform({ transform: sideTransformFactory.value.getRotate(turn) });\n\n\tdefineExpose({\n\t\tsetCss,\n\t\ttransform,\n\t\tshow,\n\t\thide,\n\t\tturn,\n\t});\n</script>\n\n<template>\n\t<div ref=\"$el\" class=\"flux-cube\" :style=\"style\">\n\t\t<component\n\t\t\t:is=\"side!.component\"\n\t\t\tv-for=\"side in sides\"\n\t\t\t:ref=\"(el: FluxComponent) => ($sides[side!.name as keyof typeof Sides] = el)\"\n\t\t\t:key=\"side!.name\"\n\t\t\tv-bind=\"side\"\n\t\t/>\n\t</div>\n</template>\n\n\n===== FILE: src/components/FluxCube/Sides.ts =====\nenum Sides {\n\tfront = 'front',\n\tback = 'back',\n\tleft = 'left',\n\tright = 'right',\n\ttop = 'top',\n\tbottom = 'bottom',\n}\n\nexport default Sides;\n\n\n===== FILE: src/components/FluxCube/Turns.ts =====\nenum Turns {\n\tfront = 'front',\n\tback = 'back',\n\tbackr = 'backr',\n\tbackl = 'backl',\n\tleft = 'left',\n\tright = 'right',\n\ttop = 'top',\n\tbottom = 'bottom',\n}\n\nexport default Turns;\n\n\n===== FILE: src/components/FluxCube/__mocks__/FluxCube.vue =====\n<script setup lang=\"ts\">\n\timport { ref, computed } from 'vue';\n\timport { vi } from 'vitest';\n\timport Side from './Side.vue';\n\timport type { FluxCubeProps, Turn } from '../types';\n\timport { Size } from '../../../shared';\n\timport CubeFactory from '../factories/CubeFactory';\n\timport SideTransformFactory from '../factories/SideTransformFactory';\n\n\tconst props = withDefaults(defineProps<FluxCubeProps>(), {\n\t\trscs: () => ({}),\n\t\tcolors: () => ({}),\n\t\toffsets: () => ({}),\n\t\tdepth: 0,\n\t\tviewSize: () => new Size(),\n\t});\n\n\tconst $el = ref(null);\n\n\tconst sideTransformFactory = computed(\n\t\t() => new SideTransformFactory(props.depth, props.size, props.viewSize),\n\t);\n\n\tconst sides = computed(() =>\n\t\tCubeFactory.getSidesProps(\n\t\t\tsideTransformFactory.value,\n\t\t\tprops.color,\n\t\t\tprops.colors,\n\t\t\tprops.rsc,\n\t\t\tprops.rscs,\n\t\t\tprops.offset,\n\t\t\tprops.offsets,\n\t\t),\n\t);\n\n\tconst turn = vi\n\t\t.fn()\n\t\t// eslint-disable-next-line @typescript-eslint/no-unused-vars\n\t\t.mockImplementation((_turn: Turn) => vi.fn());\n\n\tdefineExpose({\n\t\tsetCss: vi.fn(),\n\t\ttransform: vi.fn(),\n\t\tshow: vi.fn(),\n\t\thide: vi.fn(),\n\t\tturn,\n\t});\n</script>\n\n<template>\n\t<div ref=\"$el\" class=\"flux-cube\">\n\t\t<Side :is=\"side!.component\" v-for=\"side in sides\" :key=\"side!.name\" v-bind=\"side\" />\n\t</div>\n</template>\n\n\n===== FILE: src/components/FluxCube/__mocks__/Side.vue =====\n<script setup lang=\"ts\">\n\timport { ref, type Ref } from 'vue';\n\timport { vi } from 'vitest';\n\n\tconst $el: Ref<null | HTMLDivElement> = ref(null);\n\n\tdefineExpose({\n\t\tsetCss: vi.fn(),\n\t\ttransform: vi.fn(),\n\t\tshow: vi.fn(),\n\t\thide: vi.fn(),\n\t});\n</script>\n\n<template>\n\t<div ref=\"$el\" />\n</template>\n\n\n===== FILE: src/components/FluxCube/factories/CubeFactory.test.ts =====\nimport { Img } from '../../../resources';\nimport { Position, Size } from '../../../shared';\nimport { type SideProps } from '../types';\nimport CubeFactory from './CubeFactory';\nimport CubeSideFactory from './CubeSideFactory';\nimport SideTransformFactory from './SideTransformFactory';\n\ndescribe('factory: CubeFactory', () => {\n\tlet rsc, rscs, color, colors, offset, offsets;\n\n\tconst depth = 160;\n\tconst size = new Size({\n\t\twidth: 640,\n\t\theight: 360,\n\t});\n\tconst viewSize = new Size();\n\n\tconst sideTransformFactory = new SideTransformFactory(depth, size, viewSize);\n\n\tvi.spyOn(CubeSideFactory, 'getProps').mockImplementation(() => ({}) as SideProps);\n\n\tbeforeEach(() => {\n\t\tvi.clearAllMocks();\n\t});\n\n\tit('generates a cube using a color', () => {\n\t\tcolor = '#ccc';\n\n\t\tconst cubeProps = CubeFactory.getSidesProps(sideTransformFactory, color);\n\n\t\texpect(CubeSideFactory.getProps).toHaveBeenCalledTimes(6);\n\t\texpect(Object.keys(cubeProps)).toHaveLength(6);\n\t});\n\n\tit('generates a cube using a colors', () => {\n\t\tcolors = {\n\t\t\ttop: '#ccc',\n\t\t\tleft: '#ccc',\n\t\t\tback: '#ccc',\n\t\t};\n\n\t\tconst cubeProps = CubeFactory.getSidesProps(sideTransformFactory, undefined, colors);\n\n\t\texpect(CubeSideFactory.getProps).toHaveBeenCalledTimes(3);\n\t\texpect(Object.keys(cubeProps)).toHaveLength(3);\n\t});\n\n\tit('generates a cube using a rsc', () => {\n\t\trsc = new Img('url', 'caption');\n\n\t\tconst cubeProps = CubeFactory.getSidesProps(sideTransformFactory, undefined, undefined, rsc);\n\n\t\texpect(CubeSideFactory.getProps).toHaveBeenCalledTimes(6);\n\t\texpect(Object.keys(cubeProps)).toHaveLength(6);\n\t});\n\n\tit('generates a cube using a rscs', () => {\n\t\trscs = {\n\t\t\tbottom: new Img('url', 'caption'),\n\t\t\tright: new Img('url', 'caption'),\n\t\t\tfront: new Img('url', 'caption'),\n\t\t};\n\n\t\tconst cubeProps = CubeFactory.getSidesProps(\n\t\t\tsideTransformFactory,\n\t\t\tundefined,\n\t\t\tundefined,\n\t\t\tundefined,\n\t\t\trscs,\n\t\t);\n\n\t\texpect(CubeSideFactory.getProps).toHaveBeenCalledTimes(3);\n\t\texpect(Object.keys(cubeProps)).toHaveLength(3);\n\t});\n\n\tit('generates a cube using a color with offset', () => {\n\t\tcolor = '#ccc';\n\t\toffset = new Position({ top: 160, left: 80 });\n\n\t\tconst cubeProps = CubeFactory.getSidesProps(\n\t\t\tsideTransformFactory,\n\t\t\tcolor,\n\t\t\tundefined,\n\t\t\tundefined,\n\t\t\tundefined,\n\t\t\toffset,\n\t\t);\n\n\t\texpect(CubeSideFactory.getProps).toHaveBeenCalledTimes(6);\n\t\texpect(Object.keys(cubeProps)).toHaveLength(6);\n\t});\n\n\tit('generates a cube using a colors with offsets', () => {\n\t\tcolors = {\n\t\t\ttop: '#ccc',\n\t\t\tleft: '#ccc',\n\t\t\tback: '#ccc',\n\t\t};\n\n\t\toffsets = {\n\t\t\ttop: new Position({ top: 160, left: 80 }),\n\t\t\tleft: new Position({ top: 160, left: 80 }),\n\t\t\tback: new Position({ top: 160, left: 80 }),\n\t\t};\n\n\t\tconst cubeProps = CubeFactory.getSidesProps(\n\t\t\tsideTransformFactory,\n\t\t\tundefined,\n\t\t\tcolors,\n\t\t\tundefined,\n\t\t\tundefined,\n\t\t\tundefined,\n\t\t\toffsets,\n\t\t);\n\n\t\texpect(CubeSideFactory.getProps).toHaveBeenCalledTimes(3);\n\t\texpect(Object.keys(cubeProps)).toHaveLength(3);\n\t});\n});\n\n\n===== FILE: src/components/FluxCube/factories/CubeFactory.ts =====\nimport type { Side, SidesColors, SidesResources, SidesOffsets, SidesProps } from '../types';\nimport CubeSideFactory from './CubeSideFactory';\nimport SideTransformFactory from './SideTransformFactory';\nimport { Position } from '../../../shared';\nimport Sides from '../Sides';\nimport { Resource } from '../../../resources';\nimport type { CSSProperties } from 'vue';\n\nfunction isSideDefined(side: Side, colors?: SidesColors, rscs?: SidesResources) {\n\tif (colors && colors[side]) {\n\t\treturn true;\n\t}\n\n\tif (rscs && rscs[side]) {\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nfunction getDefinedSides(\n\tcolor?: CSSProperties['color'],\n\tcolors?: SidesColors,\n\trsc?: Resource,\n\trscs?: SidesResources,\n) {\n\tconst sides = Object.values(Sides);\n\n\tif (color || rsc) {\n\t\treturn sides;\n\t}\n\n\treturn Object.values(Sides).filter((side) => isSideDefined(side, colors, rscs));\n}\n\nexport default class CubeFactory {\n\tstatic getSidesProps(\n\t\tsideTransformFactory: SideTransformFactory,\n\t\tcolor?: CSSProperties['color'],\n\t\tcolors?: SidesColors,\n\t\trsc?: Resource,\n\t\trscs?: SidesResources,\n\t\toffset?: Position,\n\t\toffsets?: SidesOffsets,\n\t) {\n\t\tconst sides = getDefinedSides(color, colors, rsc, rscs);\n\t\tconst props: SidesProps = {};\n\n\t\tsides.forEach((side: Side) => {\n\t\t\tprops[side] = CubeSideFactory.getProps(\n\t\t\t\tsideTransformFactory,\n\t\t\t\tside,\n\t\t\t\tcolors && colors[side] ? colors[side] : color,\n\t\t\t\trscs && rscs[side] ? rscs[side] : rsc,\n\t\t\t\toffsets && offsets[side] ? offsets[side] : offset,\n\t\t\t);\n\t\t});\n\n\t\treturn props;\n\t}\n}\n\n\n===== FILE: src/components/FluxCube/factories/CubeSideFactory.ts =====\nimport { Position } from '../../../shared';\nimport { Resource } from '../../../resources';\nimport type { Side, SideProps } from '../types';\nimport SideTransformFactory from './SideTransformFactory';\nimport { FluxImage } from '../../';\nimport type { CSSProperties } from 'vue';\n\nexport default class CubeSideFactory {\n\tstatic getProps(\n\t\tsideTransformFactory: SideTransformFactory,\n\t\tside: Side,\n\t\tcolor?: CSSProperties['color'],\n\t\trsc?: Resource,\n\t\toffset?: Position,\n\t) {\n\t\tconst { depth, size, viewSize } = sideTransformFactory;\n\n\t\tconst props: SideProps = {\n\t\t\tname: side,\n\t\t\tcomponent: rsc ? rsc.transition.component : FluxImage,\n\t\t\tcolor: color,\n\t\t\trsc: rsc,\n\t\t\tsize: size.clone(),\n\t\t\tviewSize: viewSize.clone(),\n\t\t\toffset: offset,\n\t\t\tstyle: {\n\t\t\t\tposition: 'absolute',\n\t\t\t\ttransform: sideTransformFactory.getSideCss(side),\n\t\t\t\tbackfaceVisibility: 'hidden',\n\t\t\t},\n\t\t};\n\n\t\tif (['left', 'right'].includes(side)) {\n\t\t\tprops.viewSize.width.value = depth;\n\t\t\tprops.size.width.value = depth;\n\t\t}\n\n\t\tif (['top', 'bottom'].includes(side)) {\n\t\t\tprops.viewSize.height.value = depth;\n\t\t\tprops.size.height.value = depth;\n\t\t}\n\n\t\treturn props;\n\t}\n}\n\n\n===== FILE: src/components/FluxCube/factories/SideTransformFactory.test.ts =====\nimport { Size } from '../../../shared';\nimport Turns from '../Turns';\nimport SideTransformFactory from './SideTransformFactory';\n\ndescribe('factory: SideTransformFactory', () => {\n\tconst depth = 160;\n\tconst size = new Size({\n\t\twidth: 640,\n\t\theight: 360,\n\t});\n\tconst viewSize = new Size();\n\tconst sideTransformFactory = new SideTransformFactory(depth, size, viewSize);\n\n\tit('should get the proper rotate angles', () => {\n\t\tconst expectations = {\n\t\t\tfront: 'rotateX(0deg) rotateY(0deg)',\n\t\t\tright: 'rotateX(0deg) rotateY(90deg)',\n\t\t\tleft: 'rotateX(0deg) rotateY(-90deg)',\n\t\t\ttop: 'rotateX(90deg) rotateY(0deg)',\n\t\t\tbottom: 'rotateX(-90deg) rotateY(0deg)',\n\t\t\tback: 'rotateX(0deg) rotateY(180deg)',\n\t\t\tbackl: 'rotateX(0deg) rotateY(-180deg)',\n\t\t\tbackr: 'rotateX(0deg) rotateY(180deg)',\n\t\t};\n\n\t\tObject.values(Turns).forEach((turn) => {\n\t\t\texpect(sideTransformFactory.getRotate(turn)).toBe(expectations[turn]);\n\t\t});\n\t});\n\n\tit('should get proper translate coordinates', () => {\n\t\tconst expectations = {\n\t\t\tfront: 'translate3d(0%, 0%, 0px)',\n\t\t\tright: 'translate3d(50%, 0%, 560px)',\n\t\t\tleft: 'translate3d(-50%, 0%, 80px)',\n\t\t\ttop: 'translate3d(0%, -50%, 80px)',\n\t\t\tbottom: 'translate3d(0%, 50%, 280px)',\n\t\t\tback: 'translate3d(0%, 0%, 160px)',\n\t\t\tbackl: 'translate3d(0%, 0%, 160px)',\n\t\t\tbackr: 'translate3d(0%, 0%, 160px)',\n\t\t};\n\n\t\tObject.values(Turns).forEach((turn) => {\n\t\t\texpect(sideTransformFactory.getTranslate(turn)).toBe(\n\t\t\t\texpectations[turn]\n\t\t\t);\n\t\t});\n\t});\n\n\tit('should get each side style', () => {\n\t\tconst expectations = {\n\t\t\tfront: 'rotateX(0deg) rotateY(0deg) translate3d(0%, 0%, 0px)',\n\t\t\tright: 'rotateX(0deg) rotateY(90deg) translate3d(50%, 0%, 560px)',\n\t\t\tleft: 'rotateX(0deg) rotateY(-90deg) translate3d(-50%, 0%, 80px)',\n\t\t\ttop: 'rotateX(90deg) rotateY(0deg) translate3d(0%, -50%, 80px)',\n\t\t\tbottom: 'rotateX(-90deg) rotateY(0deg) translate3d(0%, 50%, 280px)',\n\t\t\tback: 'rotateX(0deg) rotateY(180deg) translate3d(0%, 0%, 160px)',\n\t\t\tbackl: 'rotateX(0deg) rotateY(-180deg) translate3d(0%, 0%, 160px)',\n\t\t\tbackr: 'rotateX(0deg) rotateY(180deg) translate3d(0%, 0%, 160px)',\n\t\t};\n\n\t\tObject.values(Turns).forEach((turn) => {\n\t\t\texpect(sideTransformFactory.getSideCss(turn)).toBe(expectations[turn]);\n\t\t});\n\t});\n});\n\n\n===== FILE: src/components/FluxCube/factories/SideTransformFactory.ts =====\nimport { type Ref, computed } from 'vue';\nimport { Size } from '../../../shared';\nimport type { Side, Turn } from '../types';\n\nconst rotate: {\n\tx: {\n\t\t[key: string]: string;\n\t};\n\ty: {\n\t\t[key: string]: string;\n\t};\n} = {\n\tx: {\n\t\ttop: '90',\n\t\tbottom: '-90',\n\t},\n\n\ty: {\n\t\tback: '180',\n\t\tbackr: '180',\n\t\tbackl: '-180',\n\t\tleft: '-90',\n\t\tright: '90',\n\t},\n};\n\nconst translate: {\n\tx: {\n\t\t[key: string]: string;\n\t};\n\ty: {\n\t\t[key: string]: string;\n\t};\n} = {\n\tx: {\n\t\tleft: '-50',\n\t\tright: '50',\n\t},\n\n\ty: {\n\t\ttop: '-50',\n\t\tbottom: '50',\n\t},\n};\n\nexport default class SideTransformFactory {\n\tdepth: number;\n\tsize: Size;\n\tviewSize: Size;\n\ttranslateZ: Ref<{ [key: string]: number }> = computed(() => {\n\t\tconst halfDepth = this.depth / 2;\n\n\t\tconst { width, height } = this.size.toValue();\n\t\tconst { width: viewWidth, height: viewHeight } = this.viewSize.toValue();\n\n\t\treturn {\n\t\t\tfront: 0,\n\t\t\tback: this.depth,\n\t\t\tbackr: this.depth,\n\t\t\tbackl: this.depth,\n\t\t\tleft: halfDepth,\n\t\t\tright: (viewWidth ?? width!) - halfDepth,\n\t\t\ttop: halfDepth,\n\t\t\tbottom: (viewHeight ?? height!) - halfDepth,\n\t\t};\n\t});\n\n\tconstructor(depth: number, size: Size, viewSize: Size) {\n\t\tthis.depth = depth;\n\t\tthis.size = size;\n\t\tthis.viewSize = viewSize;\n\t}\n\n\tpublic getRotate(turn: Side | Turn) {\n\t\tconst rx = rotate.x[turn] ?? '0';\n\t\tconst ry = rotate.y[turn] ?? '0';\n\n\t\treturn `rotateX(${rx}deg) rotateY(${ry}deg)`;\n\t}\n\n\tpublic getTranslate(side: Side | Turn) {\n\t\tconst tx = translate.x[side] ?? '0';\n\t\tconst ty = translate.y[side] ?? '0';\n\t\tconst tz = this.translateZ.value[side]!.toString();\n\n\t\treturn `translate3d(${tx}%, ${ty}%, ${tz}px)`;\n\t}\n\n\tpublic getSideCss(side: Side | Turn) {\n\t\treturn `${this.getRotate(side)} ${this.getTranslate(side)}`;\n\t}\n}\n\n\n===== FILE: src/components/FluxCube/index.ts =====\nexport { default as FluxCube } from './FluxCube.vue';\nexport { default as Sides } from './Sides';\nexport { default as Turns } from './Turns';\n\n\n===== FILE: src/components/FluxCube/types.ts =====\nimport type { CSSProperties, Component } from 'vue';\nimport { Resource } from '../../resources';\nimport { Position, Size } from '../../shared';\nimport type { ComponentProps, FluxComponent } from '../types';\nimport Sides from './Sides';\nimport Turns from './Turns';\n\nexport interface FluxCubeProps extends ComponentProps {\n\tcolors?: SidesColors;\n\trscs?: SidesResources;\n\toffsets?: SidesOffsets;\n\tdepth?: number;\n\torigin?: string;\n}\n\nexport type Side = keyof typeof Sides;\n\nexport type Turn = keyof typeof Turns;\n\nexport interface SidesColors {\n\t[Sides.front]?: string;\n\t[Sides.back]?: string;\n\t[Sides.left]?: string;\n\t[Sides.right]?: string;\n\t[Sides.top]?: string;\n\t[Sides.bottom]?: string;\n}\n\nexport interface SidesResources {\n\t[Sides.front]?: Resource;\n\t[Sides.back]?: Resource;\n\t[Sides.left]?: Resource;\n\t[Sides.right]?: Resource;\n\t[Sides.top]?: Resource;\n\t[Sides.bottom]?: Resource;\n}\n\nexport interface SidesOffsets {\n\t[Sides.front]?: Position;\n\t[Sides.back]?: Position;\n\t[Sides.left]?: Position;\n\t[Sides.right]?: Position;\n\t[Sides.top]?: Position;\n\t[Sides.bottom]?: Position;\n}\n\nexport interface SideProps {\n\tname: Side;\n\tcomponent: Component;\n\trsc?: Resource;\n\tsize: Size;\n\tviewSize: Size;\n\tcolor?: CSSProperties['color'];\n\toffset?: Position;\n\tstyle: CSSProperties;\n}\n\nexport interface SidesProps {\n\t[Sides.front]?: SideProps;\n\t[Sides.back]?: SideProps;\n\t[Sides.left]?: SideProps;\n\t[Sides.right]?: SideProps;\n\t[Sides.top]?: SideProps;\n\t[Sides.bottom]?: SideProps;\n}\n\nexport interface SidesComponents {\n\t[Sides.front]?: FluxComponent;\n\t[Sides.back]?: FluxComponent;\n\t[Sides.left]?: FluxComponent;\n\t[Sides.right]?: FluxComponent;\n\t[Sides.top]?: FluxComponent;\n\t[Sides.bottom]?: FluxComponent;\n}\n\n\n===== FILE: src/components/FluxGrid/FluxGrid.vue =====\n<script setup lang=\"ts\">\n\timport { ref, reactive, computed, type Ref, onBeforeUpdate } from 'vue';\n\timport useComponent from '../useComponent';\n\timport type { FluxGridProps } from './types';\n\timport { FluxCube } from '../';\n\timport type { ComponentStyles, FluxComponent } from '../types';\n\timport { GridFactory, getRowNumber, getColNumber } from './factories';\n\n\tconst props = withDefaults(defineProps<FluxGridProps>(), {\n\t\trows: 1,\n\t\tcols: 1,\n\t\tdepth: 0,\n\t});\n\n\tconst $el: Ref<null | HTMLDivElement> = ref(null);\n\n\tconst componentStyles: ComponentStyles = reactive({\n\t\tbase: {\n\t\t\tposition: 'relative',\n\t\t},\n\t});\n\n\tconst { style, setCss, show, hide } = useComponent($el, props, componentStyles);\n\n\tconst component = computed(() =>\n\t\tprops.rscs !== undefined ? FluxCube : props.rsc!.transition.component,\n\t);\n\n\tconst tiles = computed(() => GridFactory.getTilesProps(props));\n\n\tconst $tiles: Ref<FluxComponent[]> = ref([]);\n\n\tonBeforeUpdate(() => {\n\t\t$tiles.value = [];\n\t});\n\n\tconst transform = <T,>(cb: (tile: T, index: number) => void) => {\n\t\t$tiles.value.forEach((tile: unknown, index: number) => cb(tile as T, index));\n\t};\n\n\tdefineExpose({\n\t\tsetCss,\n\t\ttransform,\n\t\tshow,\n\t\thide,\n\t\tgetRowNumber,\n\t\tgetColNumber,\n\t});\n</script>\n\n<template>\n\t<div ref=\"$el\" class=\"flux-grid\" :style=\"style\">\n\t\t<component\n\t\t\t:is=\"component\"\n\t\t\tv-for=\"(tile, index) in tiles\"\n\t\t\t:ref=\"(el: FluxComponent) => $tiles.push(el)\"\n\t\t\t:key=\"index\"\n\t\t\tv-bind=\"tile\"\n\t\t/>\n\t</div>\n</template>\n\n\n===== FILE: src/components/FluxGrid/__mocks__/FluxGrid.vue =====\n<script setup lang=\"ts\">\n\timport { onBeforeUpdate, ref, type Ref } from 'vue';\n\timport { vi } from 'vitest';\n\timport Tile from './Tile.vue';\n\timport type { FluxGridProps } from '../types';\n\timport { getRowNumber, getColNumber } from '../factories';\n\n\tconst props = withDefaults(defineProps<FluxGridProps>(), {\n\t\trows: 1,\n\t\tcols: 1,\n\t\tdepth: 0,\n\t});\n\n\tconst $el: Ref<null | HTMLDivElement> = ref(null);\n\n\tconst numTiles = props.rows * props.cols;\n\tconst $tiles: Ref<InstanceType<typeof Tile>[]> = ref([]);\n\n\tonBeforeUpdate(() => {\n\t\t$tiles.value = [];\n\t});\n\n\tconst transform = vi\n\t\t.fn()\n\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t\t.mockImplementation((cb: (tile: any, index: number) => void) => {\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t\t\t$tiles.value.forEach((tile: any, index: number) => cb(tile, index));\n\t\t});\n\n\tdefineExpose({\n\t\tsetCss: vi.fn(),\n\t\ttransform,\n\t\tshow: vi.fn(),\n\t\thide: vi.fn(),\n\t\tgetRowNumber: vi\n\t\t\t.fn()\n\t\t\t.mockImplementation((index: number, numCols: number) => getRowNumber(index, numCols)),\n\t\tgetColNumber: vi\n\t\t\t.fn()\n\t\t\t.mockImplementation((index: number, numCols: number) => getColNumber(index, numCols)),\n\t\t$tiles,\n\t});\n</script>\n\n<template>\n\t<div ref=\"$el\" class=\"flux-grid\">\n\t\t<Tile v-for=\"index in numTiles\" :ref=\"(el: any) => $tiles.push(el)\" :key=\"index\" />\n\t</div>\n</template>\n\n\n===== FILE: src/components/FluxGrid/__mocks__/Tile.vue =====\n<script setup lang=\"ts\">\n\timport { ref, type Ref } from 'vue';\n\timport { vi } from 'vitest';\n\n\tconst $el: Ref<null | HTMLDivElement> = ref(null);\n\n\tdefineExpose({\n\t\tturn: vi.fn(),\n\t\tsetCss: vi.fn(),\n\t\ttransform: vi.fn(),\n\t\tshow: vi.fn(),\n\t\thide: vi.fn(),\n\t\tgetRowNumber: vi.fn(),\n\t\tgetColNumber: vi.fn(),\n\t});\n</script>\n\n<template>\n\t<div ref=\"$el\" />\n</template>\n\n\n===== FILE: src/components/FluxGrid/factories/GridFactory.ts =====\nimport { Size } from '../../../shared';\nimport GridTileFactory from './GridTileFactory';\nimport type { FluxGridProps, FluxGridTileProps } from '../types';\n\nexport default class GridFactory {\n\tstatic getTilesProps(props: FluxGridProps) {\n\t\tconst { rows, cols, size, color, colors, rsc, rscs, depth } = props;\n\n\t\tconst numRows = Math.ceil(rows!);\n\t\tconst numCols = Math.ceil(cols!);\n\n\t\tconst grid = {\n\t\t\tnumRows,\n\t\t\tnumCols,\n\t\t\tnumTiles: numRows * numCols,\n\t\t\tsize,\n\t\t\tdepth: depth!,\n\t\t\tcolor,\n\t\t\tcolors,\n\t\t\trsc,\n\t\t\trscs,\n\t\t};\n\n\t\tconst tile = {\n\t\t\tnumber: 0,\n\t\t\tsize: new Size({\n\t\t\t\twidth: Math.floor(size.width.value! / numCols),\n\t\t\t\theight: Math.floor(size.height.value! / numRows),\n\t\t\t}),\n\t\t\tcss: props.tileCss,\n\t\t};\n\n\t\tconst tilesProps: FluxGridTileProps[] = [];\n\n\t\tfor (let tileNumber = 0; tileNumber < grid.numTiles; tileNumber++) {\n\t\t\ttile.number = tileNumber;\n\t\t\ttilesProps.push(GridTileFactory.getProps(grid, tile));\n\t\t}\n\n\t\treturn tilesProps;\n\t}\n}\n\n\n===== FILE: src/components/FluxGrid/factories/GridTileFactory.ts =====\nimport type { CSSProperties } from 'vue';\nimport { Resource } from '../../../resources';\nimport { Size, Position } from '../../../shared';\nimport type { SidesColors, SidesResources } from '../../FluxCube/types';\nimport type { FluxGridTileProps } from '../types';\n\nexport function getRowNumber(tileNumber: number, numCols: number) {\n\treturn Math.floor(tileNumber / numCols);\n}\n\nexport function getColNumber(tileNumber: number, numCols: number) {\n\treturn tileNumber % numCols;\n}\n\nexport default class GridTileFactory {\n\tstatic getProps(\n\t\tgrid: {\n\t\t\tnumRows: number;\n\t\t\tnumCols: number;\n\t\t\tnumTiles: number;\n\t\t\tsize: Size;\n\t\t\tdepth: number;\n\t\t\tcolor?: CSSProperties['color'];\n\t\t\tcolors?: SidesColors;\n\t\t\trsc?: Resource;\n\t\t\trscs?: SidesResources;\n\t\t},\n\t\ttile: {\n\t\t\tnumber: number;\n\t\t\tsize: Size;\n\t\t\tcss?: CSSProperties;\n\t\t},\n\t) {\n\t\tlet { width, height } = tile.size.toValue();\n\n\t\tconst row = getRowNumber(tile.number, grid.numCols);\n\t\tconst col = getColNumber(tile.number, grid.numCols);\n\n\t\tconst props: FluxGridTileProps = {\n\t\t\tcolor: grid.color,\n\t\t\tcolors: grid.colors,\n\t\t\trsc: grid.rsc,\n\t\t\trscs: grid.rscs,\n\t\t\tsize: grid.size,\n\t\t\tdepth: grid.depth,\n\t\t\toffset: new Position({\n\t\t\t\ttop: row * height!,\n\t\t\t\tleft: col * width!,\n\t\t\t}),\n\t\t};\n\n\t\tif (row + 1 === grid.numRows) {\n\t\t\theight = grid.size.height.value! - row * height!;\n\t\t}\n\n\t\tif (col + 1 === grid.numCols) {\n\t\t\twidth = grid.size.width.value! - col * width!;\n\t\t}\n\n\t\tprops.viewSize = new Size({\n\t\t\twidth,\n\t\t\theight,\n\t\t});\n\n\t\tprops.css = {\n\t\t\t...tile.css,\n\t\t\tposition: 'absolute',\n\t\t\t...props.offset.toPx(),\n\t\t\tzIndex:\n\t\t\t\ttile.number + 1 < grid.numTiles / 2 ? tile.number + 1 : grid.numTiles - tile.number,\n\t\t};\n\n\t\treturn props;\n\t}\n}\n\n\n===== FILE: src/components/FluxGrid/factories/index.ts =====\nexport { default as GridFactory } from './GridFactory';\nexport {\n\tdefault as GridTileFactory,\n\tgetRowNumber,\n\tgetColNumber,\n} from './GridTileFactory';\n\n\n===== FILE: src/components/FluxGrid/types.ts =====\nimport type { CSSProperties } from 'vue';\nimport { Position, Size } from '../../shared';\nimport { Resource } from '../../resources';\nimport type { SidesColors, SidesResources } from '../FluxCube/types';\nimport type { ComponentProps } from '../types';\n\nexport interface FluxGridProps extends ComponentProps {\n\tcolors?: SidesColors;\n\trscs?: SidesResources;\n\trows?: number;\n\tcols?: number;\n\tdepth?: number;\n\ttileCss?: CSSProperties;\n}\n\nexport interface FluxGridTileProps {\n\tcolor?: CSSProperties['color'];\n\tcolors?: SidesColors;\n\trsc?: Resource;\n\trscs?: SidesResources;\n\tsize: Size;\n\tdepth: number;\n\toffset: Position;\n\tviewSize?: Size;\n\tcss?: CSSProperties;\n}\n\n\n===== FILE: src/components/FluxImage/FluxImage.vue =====\n<script setup lang=\"ts\">\n\timport { ref, type Ref, reactive, computed, type CSSProperties } from 'vue';\n\timport useComponent from '../useComponent';\n\timport type { FluxImageProps } from './types';\n\timport type { ComponentStyles } from '../types';\n\timport { Statuses } from '../../resources';\n\n\tconst props = defineProps<FluxImageProps>();\n\n\tconst $el: Ref<null | HTMLDivElement> = ref(null);\n\n\tconst componentStyles: ComponentStyles = reactive({\n\t\tbase: {\n\t\t\toverflow: 'hidden',\n\t\t},\n\n\t\tcolor: computed<CSSProperties>(() => {\n\t\t\tconst colorStyle: CSSProperties = {};\n\n\t\t\tif (props.color !== undefined) {\n\t\t\t\tcolorStyle.backgroundColor = props.color;\n\t\t\t}\n\n\t\t\tif (props.rsc?.backgroundColor !== null) {\n\t\t\t\tcolorStyle.backgroundColor = props.rsc?.backgroundColor;\n\t\t\t}\n\n\t\t\treturn colorStyle;\n\t\t}),\n\n\t\trsc: computed<CSSProperties>(() => {\n\t\t\tconst { rsc, size, offset } = props;\n\n\t\t\tif (!rsc) {\n\t\t\t\treturn {};\n\t\t\t}\n\n\t\t\tif (rsc.status.value === Statuses.notLoaded) {\n\t\t\t\trsc.load();\n\t\t\t\treturn {};\n\t\t\t}\n\n\t\t\tif (!rsc.isLoaded() || !size.isValid() || !$el.value) {\n\t\t\t\treturn {};\n\t\t\t}\n\n\t\t\tconst { width, height, top, left } = rsc.getResizeProps(size, offset);\n\n\t\t\treturn {\n\t\t\t\tbackgroundImage: `url(${rsc.src})`,\n\t\t\t\tbackgroundSize: `${width}px ${height}px`,\n\t\t\t\tbackgroundPosition: `${left}px ${top}px`,\n\t\t\t\tbackgroundRepeat: 'no-repeat',\n\t\t\t};\n\t\t}),\n\t});\n\n\tconst { style, setCss, transform, show, hide } = useComponent($el, props, componentStyles);\n\n\tdefineExpose({\n\t\tsetCss,\n\t\ttransform,\n\t\tshow,\n\t\thide,\n\t});\n</script>\n\n<template>\n\t<div ref=\"$el\" class=\"flux-image\" :style=\"style\" />\n</template>\n\n\n===== FILE: src/components/FluxImage/__mocks__/FluxImage.vue =====\n<script setup lang=\"ts\">\n\timport { ref, type Ref } from 'vue';\n\timport { vi } from 'vitest';\n\timport type { FluxImageProps } from '../types';\n\n\tdefineProps<FluxImageProps>();\n\n\tconst $el: Ref<null | HTMLDivElement> = ref(null);\n\n\tdefineExpose({\n\t\tsetCss: vi.fn(),\n\t\ttransform: vi.fn(),\n\t\tshow: vi.fn(),\n\t\thide: vi.fn(),\n\t});\n</script>\n\n<template>\n\t<div ref=\"$el\" class=\"flux-image\" />\n</template>\n\n\n===== FILE: src/components/FluxImage/types.ts =====\nimport type { ComponentProps } from '../types';\n\nexport interface FluxImageProps extends ComponentProps {}\n\n\n===== FILE: src/components/FluxParallax/FluxParallax.vue =====\n<script setup lang=\"ts\">\n\t// holder (window), component, background\n\n\timport {\n\t\tref,\n\t\treactive,\n\t\tcomputed,\n\t\tunref,\n\t\tonMounted,\n\t\tonUnmounted,\n\t\ttype Ref,\n\t\ttype CSSProperties,\n\t} from 'vue';\n\timport { Maths } from '../../shared';\n\timport type { DisplayProps, FluxParallaxProps, FluxParallaxStyles, ViewProps } from './types';\n\n\tconst { aspectRatio } = Maths;\n\n\tconst props = withDefaults(defineProps<FluxParallaxProps>(), {\n\t\tholder: () => window,\n\t\ttype: 'relative',\n\t\toffset: '100%',\n\t});\n\n\tconst $el: Ref<null | HTMLDivElement> = ref(null);\n\n\tconst { holder, rsc } = props;\n\n\tconst style: FluxParallaxStyles = {\n\t\tbase: {\n\t\t\tposition: 'relative',\n\t\t\tbackground: `url(\"${rsc.src}\") no-repeat`,\n\t\t},\n\n\t\tdefined: reactive({}),\n\n\t\tfinal: computed(() => ({\n\t\t\t...style.base,\n\t\t\t...unref(style.defined),\n\t\t})),\n\t};\n\n\tconst isIos =\n\t\t/iPad|iPhone|iPod/.test(navigator.userAgent) ||\n\t\t(navigator.userAgent === 'MacIntel' && navigator.maxTouchPoints > 1);\n\n\tconst display: DisplayProps = reactive({\n\t\twidth: 0,\n\t\theight: 0,\n\t\taspectRatio: computed(() => aspectRatio(display)),\n\t});\n\n\tconst view: ViewProps = reactive({\n\t\ttop: 0,\n\t\twidth: 0,\n\t\theight: 0,\n\t\taspectRatio: computed(() => aspectRatio(view)),\n\t});\n\n\tconst background = reactive({\n\t\ttop: 0,\n\t\tleft: 0,\n\t\twidth: 0,\n\t\theight: 0,\n\t});\n\n\tconst fixedParentStyle: CSSProperties = {\n\t\tposition: 'absolute',\n\t\ttop: 0,\n\t\tleft: 0,\n\t\tbottom: 0,\n\t\tright: 0,\n\t\tclip: 'rect(auto auto auto auto)',\n\t};\n\n\tconst fixedChildStyle = computed<CSSProperties>(() => ({\n\t\tposition: 'absolute',\n\t\ttop: 0,\n\t\tbottom: 0,\n\t\tleft: 0,\n\t\tright: 0,\n\t\tbackground: `url(\"${rsc.src}\") no-repeat center center fixed`,\n\t\tbackgroundSize: `${background.width}px ${background.height}px`,\n\t}));\n\n\tconst offsetHeight = computed(() => {\n\t\tconst { offset } = props;\n\t\tconst offsetValue = parseFloat(offset);\n\n\t\tif (/^[0-9]+px$/.test(offset)) {\n\t\t\treturn {\n\t\t\t\tpx: offsetValue,\n\t\t\t\tpct: (offsetValue * 100) / background.height,\n\t\t\t};\n\t\t}\n\n\t\tif (/^[0-9]+%$/.test(offset)) {\n\t\t\treturn {\n\t\t\t\tpx: Math.ceil((view.height * offsetValue) / 100),\n\t\t\t\tpct: offsetValue,\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tpx: 0,\n\t\t\tpct: 0,\n\t\t};\n\t});\n\n\tconst remainderHeight = computed(() => {\n\t\tconst effectHeight = isIos ? display.height : view.height + offsetHeight.value.px;\n\n\t\treturn background.height - effectHeight;\n\t});\n\n\tonMounted(() => {\n\t\twindow.addEventListener('resize', resize, {\n\t\t\tpassive: true,\n\t\t});\n\n\t\tif (props.type !== 'fixed' || isIos) {\n\t\t\tholder.addEventListener('scroll', onScroll, {\n\t\t\t\tpassive: true,\n\t\t\t});\n\t\t}\n\n\t\trsc.load().then(() => {\n\t\t\tresize();\n\t\t});\n\t});\n\n\tonUnmounted(() => {\n\t\twindow.removeEventListener('resize', resize);\n\t\tholder.removeEventListener('scroll', onScroll);\n\t});\n\n\tconst resize = () => {\n\t\t// @ts-expect-error:next-line\n\t\tdisplay.width = holder.scrollWidth || holder.innerWidth;\n\t\t// @ts-expect-error:next-line\n\t\tdisplay.height = holder.scrollHeight || holder.innerHeight;\n\n\t\tview.width = $el.value!.clientWidth;\n\t\tview.height = $el.value!.clientHeight;\n\t\tview.top = $el.value!.getBoundingClientRect().top + window.scrollY;\n\n\t\trsc.displaySize.update(display);\n\t\tconst fillProps = rsc.resizeProps.value;\n\n\t\tbackground.width = fillProps.width!;\n\t\tbackground.height = fillProps.height!;\n\n\t\tstyle.defined.backgroundSize = `${background.width}px ${background.height}px`;\n\t\tstyle.defined.backgroundPosition = `center 0`;\n\n\t\tonScroll();\n\t};\n\n\tconst moveBackgroundByPct = (pct: number) => {\n\t\tif (remainderHeight.value > 0)\n\t\t\tpct = (pct * offsetHeight.value.pct) / 100 + 50 - offsetHeight.value.pct / 2;\n\n\t\tstyle.defined.backgroundPositionY = pct.toFixed(2) + '%';\n\t};\n\n\tconst onScroll = () => {\n\t\tif (!rsc.isLoaded() || (!isIos && props.type === 'fixed')) {\n\t\t\treturn;\n\t\t}\n\n\t\t// @ts-expect-error:next-line\n\t\tconst scrollTop = holder.scrollY || holder.scrollTop || 0;\n\n\t\tif (holder !== window) {\n\t\t\treturn handle.relative(scrollTop);\n\t\t}\n\n\t\tif (scrollTop + display.height < view.top) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (scrollTop > view.top + view.height) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst positionY = scrollTop - view.top + display.height;\n\n\t\thandle[props.type](positionY);\n\t};\n\n\tconst handle = {\n\t\tvisible: (positionY: number) => {\n\t\t\tlet pct = 0;\n\n\t\t\tif (positionY < view.height) {\n\t\t\t\tpct = 0;\n\t\t\t} else if (positionY > display.height) {\n\t\t\t\tpct = 100;\n\t\t\t} else {\n\t\t\t\tpct = ((positionY - view.height) * 100) / (display.height - view.height);\n\t\t\t}\n\n\t\t\tmoveBackgroundByPct(pct);\n\t\t},\n\n\t\trelative: (positionY: number) => {\n\t\t\tlet pct;\n\n\t\t\tif (holder === window) {\n\t\t\t\tpct = (positionY * 100) / (display.height + view.height);\n\t\t\t} else {\n\t\t\t\t// @ts-expect-error:next-line\n\t\t\t\tpct = (positionY * 100) / (display.height - holder.clientHeight);\n\t\t\t}\n\n\t\t\tmoveBackgroundByPct(pct);\n\t\t},\n\n\t\tfixed: (positionY: number) => {\n\t\t\tstyle.defined.backgroundPositionY = positionY - display.height + 'px';\n\t\t},\n\t};\n\n\tdefineExpose({\n\t\tresize,\n\t});\n</script>\n\n<template>\n\t<div ref=\"$el\" class=\"flux-parallax\" :style=\"style.final.value\">\n\t\t<div v-if=\"props.type === 'fixed' && !isIos\" :style=\"fixedParentStyle\">\n\t\t\t<div class=\"image\" :style=\"fixedChildStyle\" />\n\t\t</div>\n\n\t\t<slot />\n\t</div>\n</template>\n\n\n===== FILE: src/components/FluxParallax/types.ts =====\nimport type { CSSProperties, ComputedRef } from 'vue';\nimport { Resource } from '../../resources';\n\nexport interface FluxParallaxProps {\n\trsc: Resource;\n\tholder?: Window | Element;\n\ttype?: 'visible' | 'relative' | 'fixed';\n\toffset?: string;\n}\n\nexport interface FluxParallaxStyles {\n\tbase: CSSProperties;\n\tdefined: CSSProperties;\n\tfinal: ComputedRef<CSSProperties>;\n}\n\nexport interface DisplayProps {\n\twidth: number;\n\theight: number;\n\taspectRatio: number;\n}\n\nexport interface ViewProps {\n\ttop: number;\n\twidth: number;\n\theight: number;\n\taspectRatio: number;\n}\n\n\n===== FILE: src/components/FluxTransition/FluxTransition.vue =====\n<script setup lang=\"ts\">\n\timport {\n\t\tref,\n\t\treactive,\n\t\tcomputed,\n\t\tonMounted,\n\t\tonUnmounted,\n\t\tnextTick,\n\t\ttype Ref,\n\t\ttype Component,\n\t} from 'vue';\n\timport type { FluxTransitionProps } from './types';\n\timport type { TransitionComponent } from '../../transitions';\n\n\tconst props = withDefaults(defineProps<FluxTransitionProps>(), {\n\t\toptions: () => ({}),\n\t});\n\n\tconst $el: Ref<null | HTMLDivElement> = ref(null);\n\tconst $transition: Ref<null | Component> = ref(null);\n\n\tconst emit = defineEmits(['ready', 'start', 'end']);\n\n\tconst styles = reactive({\n\t\tbase: {\n\t\t\toverflow: 'hidden',\n\t\t\tperspective: 'none',\n\t\t\tzIndex: 3,\n\t\t},\n\t});\n\n\tconst style = computed(() => {\n\t\tconst { width, height } = props.size.toPx();\n\n\t\treturn {\n\t\t\t...styles.base,\n\t\t\twidth,\n\t\t\theight,\n\t\t};\n\t});\n\n\tconst duration = ref(1);\n\n\tonMounted(async () => {\n\t\tawait nextTick();\n\n\t\tif ($transition.value !== null) {\n\t\t\tduration.value = ($transition.value as TransitionComponent).totalDuration;\n\t\t}\n\n\t\temit('ready', {\n\t\t\ttransition: props.transition,\n\t\t\tfrom: props.from,\n\t\t\tto: props.to,\n\t\t\toptions: props.options,\n\t\t\tduration: duration.value,\n\t\t});\n\t});\n\n\tasync function start() {\n\t\temit('start', {\n\t\t\ttransition: props.transition,\n\t\t\tfrom: props.from,\n\t\t\tto: props.to,\n\t\t\toptions: props.options,\n\t\t\tduration: duration.value,\n\t\t});\n\n\t\tawait nextTick();\n\n\t\tif ($transition.value === null) {\n\t\t\tconsole.error('Transition component not available', props.transition);\n\t\t} else {\n\t\t\t($transition.value as TransitionComponent).onPlay();\n\t\t}\n\n\t\tsetTimeout(() => end(), duration.value);\n\t}\n\n\tfunction end() {\n\t\temit('end', {\n\t\t\ttransition: props.transition,\n\t\t\tfrom: props.from,\n\t\t\tto: props.to,\n\t\t\toptions: props.options,\n\t\t\tduration: duration.value,\n\t\t});\n\t}\n\n\tonUnmounted(() => {\n\t\tif (props.displayComponent) {\n\t\t\tprops.displayComponent.show();\n\t\t}\n\t});\n\n\tdefineExpose({ start });\n</script>\n\n<template>\n\t<div ref=\"$el\" class=\"flux-transition\" :style=\"style\">\n\t\t<component\n\t\t\t:is=\"transition\"\n\t\t\tref=\"$transition\"\n\t\t\t:size=\"size\"\n\t\t\t:from=\"from\"\n\t\t\t:to=\"to\"\n\t\t\t:display-component=\"displayComponent\"\n\t\t\t:options=\"options\"\n\t\t\t:mask-style=\"styles.base\"\n\t\t/>\n\t</div>\n</template>\n\n<style lang=\"scss\">\n\t.flux-transition {\n\t\tposition: relative;\n\t}\n</style>\n\n\n===== FILE: src/components/FluxTransition/types.ts =====\nimport { Resource } from '../../resources';\nimport { Size } from '../../shared';\nimport type { FluxComponent } from '../types';\n\nexport interface FluxTransitionProps {\n\tsize: Size;\n\ttransition: object;\n\tfrom: Resource;\n\tto: Resource;\n\tdisplayComponent?: null | FluxComponent;\n\toptions?: object;\n}\n\n\n===== FILE: src/components/FluxVortex/FluxVortex.vue =====\n<script setup lang=\"ts\">\n\timport { ref, reactive, computed, type Ref, onBeforeUpdate } from 'vue';\n\timport useComponent from '../useComponent';\n\timport type { ComponentStyles, FluxComponent } from '../types';\n\timport type { FluxVortexProps } from './types';\n\timport { VortexFactory } from './factories';\n\n\tconst props = withDefaults(defineProps<FluxVortexProps>(), {\n\t\tcircles: 1,\n\t});\n\n\tconst $el: Ref<null | HTMLDivElement> = ref(null);\n\n\tconst componentStyles: ComponentStyles = reactive({\n\t\tbase: {\n\t\t\tposition: 'relative',\n\t\t\toverflow: 'hidden',\n\t\t},\n\t});\n\n\tconst { style, setCss, show, hide } = useComponent($el, props, componentStyles);\n\n\tconst tiles = computed(() => VortexFactory.getCirclesProps(props));\n\n\tconst $tiles: Ref<FluxComponent[]> = ref([]);\n\n\tonBeforeUpdate(() => {\n\t\t$tiles.value = [];\n\t});\n\n\tconst transform = <T,>(cb: (tile: T, index: number) => void) => {\n\t\t$tiles.value.forEach((tile: unknown, index: number) => cb(tile as T, index));\n\t};\n\n\tdefineExpose({\n\t\tsetCss,\n\t\ttransform,\n\t\tshow,\n\t\thide,\n\t});\n</script>\n\n<template>\n\t<div ref=\"$el\" class=\"flux-vortex\" :style=\"style\">\n\t\t<component\n\t\t\t:is=\"rsc.transition.component\"\n\t\t\tv-for=\"(tile, index) in tiles\"\n\t\t\t:ref=\"(el: any) => $tiles.push(el)\"\n\t\t\t:key=\"index\"\n\t\t\t:size=\"size\"\n\t\t\t:rsc=\"rsc\"\n\t\t\t:offset=\"tile.offset\"\n\t\t\t:css=\"tile.css\"\n\t\t/>\n\t</div>\n</template>\n\n\n===== FILE: src/components/FluxVortex/__mocks__/FluxVortex.vue =====\n<script setup lang=\"ts\">\n\timport { ref, type Ref, onBeforeUpdate } from 'vue';\n\timport { vi } from 'vitest';\n\timport Tile from './Tile.vue';\n\timport type { FluxVortexProps } from '../types';\n\n\tconst props = withDefaults(defineProps<FluxVortexProps>(), {\n\t\tcircles: 1,\n\t});\n\n\tconst $el: Ref<null | HTMLDivElement> = ref(null);\n\n\tconst numTiles = props.circles;\n\tconst $tiles: Ref<InstanceType<typeof Tile>[]> = ref([]);\n\n\tonBeforeUpdate(() => {\n\t\t$tiles.value = [];\n\t});\n\n\tconst transform = vi\n\t\t.fn()\n\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t\t.mockImplementation((cb: (tile: any, index: number) => void) => {\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t\t\t$tiles.value.forEach((tile: any, index: number) => cb(tile, index));\n\t\t});\n\n\tdefineExpose({\n\t\tsetCss: vi.fn(),\n\t\ttransform,\n\t\tshow: vi.fn(),\n\t\thide: vi.fn(),\n\t\tgetRowNumber: vi.fn(),\n\t\tgetColNumber: vi.fn(),\n\t\t$tiles,\n\t});\n</script>\n\n<template>\n\t<div ref=\"$el\" class=\"flux-vortex\">\n\t\t<Tile v-for=\"index in numTiles\" :ref=\"(el: any) => $tiles.push(el)\" :key=\"index\" />\n\t</div>\n</template>\n\n\n===== FILE: src/components/FluxVortex/__mocks__/Tile.vue =====\n<script setup lang=\"ts\">\n\timport { ref, type Ref } from 'vue';\n\timport { vi } from 'vitest';\n\n\tconst $el: Ref<null | HTMLDivElement> = ref(null);\n\n\tdefineExpose({\n\t\tturn: vi.fn(),\n\t\tsetCss: vi.fn(),\n\t\ttransform: vi.fn(),\n\t\tshow: vi.fn(),\n\t\thide: vi.fn(),\n\t\tgetRowNumber: vi.fn(),\n\t\tgetColNumber: vi.fn(),\n\t});\n</script>\n\n<template>\n\t<div ref=\"$el\" />\n</template>\n\n\n===== FILE: src/components/FluxVortex/factories/VortexCircleFactory.ts =====\nimport type { CSSProperties } from 'vue';\nimport { Position } from '../../../shared';\nimport type { FluxVortexCirclesProps } from '../types';\n\nexport default class VortexCircleFactory {\n\tstatic getProps(\n\t\tvortex: {\n\t\t\tnumCircles: number;\n\t\t\tdiagonal: number;\n\t\t\tradius: number;\n\t\t\ttopGap: number;\n\t\t\tleftGap: number;\n\t\t},\n\t\tcircleNumber: number,\n\t\tcircleCss?: CSSProperties,\n\t) {\n\t\tconst size = (vortex.numCircles - circleNumber) * vortex.radius * 2;\n\n\t\tconst gap = vortex.radius * circleNumber;\n\n\t\tconst offset = new Position({\n\t\t\ttop: vortex.topGap + gap,\n\t\t\tleft: vortex.leftGap + gap,\n\t\t});\n\n\t\tconst circle: FluxVortexCirclesProps = {\n\t\t\toffset: offset,\n\t\t\tcss: {\n\t\t\t\t...circleCss,\n\t\t\t\t...offset.toPx(),\n\t\t\t\tposition: 'absolute',\n\t\t\t\twidth: size + 'px',\n\t\t\t\theight: size + 'px',\n\t\t\t\tbackgroundRepeat: 'repeat',\n\t\t\t\tborderRadius: '50%',\n\t\t\t\tzIndex: circleNumber,\n\t\t\t},\n\t\t};\n\n\t\treturn circle;\n\t}\n}\n\n\n===== FILE: src/components/FluxVortex/factories/VortexFactory.ts =====\nimport { Maths } from '../../../shared';\nimport type { FluxVortexProps, FluxVortexCirclesProps } from '../types';\nimport VortexCircleFactory from './VortexCircleFactory';\n\nexport default class VortexFactory {\n\tstatic getCirclesProps(props: FluxVortexProps) {\n\t\tconst { width, height } = props.size.toValue();\n\n\t\tconst numCircles = Math.round(props.circles!);\n\t\tconst diagonal = Maths.diag({ width: width!, height: height! });\n\t\tconst radius = Math.ceil(diagonal / 2 / numCircles);\n\t\tconst topGap = Math.ceil(height! / 2 - radius * numCircles);\n\t\tconst leftGap = Math.ceil(width! / 2 - radius * numCircles);\n\n\t\tconst vortex = {\n\t\t\tnumCircles,\n\t\t\tdiagonal,\n\t\t\tradius,\n\t\t\ttopGap,\n\t\t\tleftGap,\n\t\t};\n\n\t\tconst circlesProps: FluxVortexCirclesProps[] = [];\n\n\t\tfor (let circleNumber = 0; circleNumber < numCircles; circleNumber++) {\n\t\t\tcirclesProps.push(VortexCircleFactory.getProps(vortex, circleNumber, props.tileCss));\n\t\t}\n\n\t\treturn circlesProps;\n\t}\n}\n\n\n===== FILE: src/components/FluxVortex/factories/index.ts =====\nexport { default as VortexFactory } from './VortexFactory';\nexport { default as VortexCircleFactory } from './VortexCircleFactory';\n\n\n===== FILE: src/components/FluxVortex/types.ts =====\nimport type { CSSProperties } from 'vue';\nimport { Resource } from '../../resources';\nimport type { ComponentProps } from '../types';\nimport { Position } from '../../shared';\n\nexport interface FluxVortexProps extends ComponentProps {\n\trsc: Resource;\n\tcircles?: number;\n\ttileCss?: CSSProperties;\n}\n\nexport interface FluxVortexCirclesProps {\n\toffset: Position;\n\tcss: CSSProperties;\n}\n\n\n===== FILE: src/components/FluxWrapper/FluxWrapper.vue =====\n<script setup lang=\"ts\">\n\timport { ref, reactive, type Ref } from 'vue';\n\timport useComponent from '../useComponent';\n\timport type { ComponentStyles } from '../types';\n\timport type { FluxWrapperProps } from './types';\n\n\tconst props = defineProps<FluxWrapperProps>();\n\n\tconst $el: Ref<null | HTMLDivElement> = ref(null);\n\n\tconst componentStyles: ComponentStyles = reactive({\n\t\tbase: {\n\t\t\toverflow: 'hidden',\n\t\t},\n\t});\n\n\tconst { style, setCss, transform, show, hide } = useComponent($el, props, componentStyles);\n\n\tdefineExpose({\n\t\tsetCss,\n\t\ttransform,\n\t\tshow,\n\t\thide,\n\t});\n</script>\n\n<template>\n\t<div ref=\"$el\" class=\"flux-wrapper\" :style=\"style\">\n\t\t<slot />\n\t</div>\n</template>\n\n\n===== FILE: src/components/FluxWrapper/__mocks__/FluxWrapper.vue =====\n<script setup lang=\"ts\">\n\timport { ref, type Ref } from 'vue';\n\timport { vi } from 'vitest';\n\timport type { FluxWrapperProps } from '../types';\n\n\tdefineProps<FluxWrapperProps>();\n\n\tconst $el: Ref<null | HTMLDivElement> = ref(null);\n\n\tdefineExpose({\n\t\tsetCss: vi.fn(),\n\t\ttransform: vi.fn(),\n\t\tshow: vi.fn(),\n\t\thide: vi.fn(),\n\t});\n</script>\n\n<template>\n\t<div ref=\"$el\" class=\"flux-wrapper\">\n\t\t<slot />\n\t</div>\n</template>\n\n\n===== FILE: src/components/FluxWrapper/types.ts =====\nimport type { ComponentProps } from '../types';\n\nexport interface FluxWrapperProps extends ComponentProps {}\n\n\n===== FILE: src/components/VueFlux/VueFlux.vue =====\n<script setup lang=\"ts\">\n\timport { onMounted, onUnmounted, ref, reactive, computed, watch, type Ref, toRaw } from 'vue';\n\timport * as Controllers from '../../controllers';\n\timport { type FluxComponent, FluxTransition } from '../';\n\timport type { VueFluxProps, VueFluxEmits, VueFluxConfig } from './types';\n\timport { default as PlayerStatuses } from '../../controllers/Player/Statuses';\n\n\tconst props = withDefaults(defineProps<VueFluxProps>(), {\n\t\toptions: () => ({}),\n\t});\n\n\tconst emit = defineEmits<VueFluxEmits>();\n\n\tconst $el: Ref<null | HTMLDivElement> = ref(null);\n\tconst $transition: Ref<null | InstanceType<typeof FluxTransition>> = ref(null);\n\tconst $displayComponent: Ref<null | FluxComponent> = ref(null);\n\n\tconst config: VueFluxConfig = reactive({\n\t\tallowFullscreen: false,\n\t\tallowToSkipTransition: true,\n\t\taspectRatio: '16:9',\n\t\tautohideTime: 2500,\n\t\tautoplay: false,\n\t\tbindKeys: false,\n\t\tdelay: 5000,\n\t\tenableGestures: false,\n\t\tinfinite: true,\n\t\tlazyLoad: true,\n\t\tlazyLoadAfter: 5,\n\t});\n\n\tconst timers = new Controllers.Timers();\n\tconst player = new Controllers.Player(config, timers, emit);\n\tconst resources = player.resources;\n\tconst transitions = player.transitions;\n\tconst display = new Controllers.Display($el, config, emit);\n\tconst keys = new Controllers.Keys(config, player);\n\tconst mouse = new Controllers.Mouse();\n\tconst touches = new Controllers.Touches();\n\n\tconst setup = () => {\n\t\tObject.assign(config, props.options);\n\t\tmouse.setup(config, timers);\n\t\tkeys.setup();\n\t};\n\n\twatch(props.options, () => {\n\t\tsetup();\n\t\temit('optionsUpdated');\n\t});\n\n\tasync function updateProp(propName: 'rscs' | 'transitions') {\n\t\tconst wasPlaying = player.status.value === PlayerStatuses.playing;\n\n\t\tif (wasPlaying) {\n\t\t\tawait player.stop(true);\n\t\t}\n\n\t\tawait {\n\t\t\trscs: async () => await updateResources(),\n\t\t\ttransitions: () => updateTransitions(),\n\t\t}[propName]();\n\n\t\tif (wasPlaying) {\n\t\t\tplayer.play();\n\t\t}\n\t}\n\n\tasync function updateResources() {\n\t\tplayer.resource.reset();\n\n\t\tconst numToPreload = config.lazyLoad ? config.lazyLoadAfter : props.rscs.length;\n\n\t\ttry {\n\t\t\tawait resources.update(toRaw(props.rscs), numToPreload, display.size);\n\t\t} catch (e) {\n\t\t\tconsole.error(e);\n\t\t}\n\n\t\tif (resources.list.length) {\n\t\t\tplayer.resource.init(resources);\n\t\t}\n\t}\n\n\twatch(\n\t\t() => props.rscs,\n\t\tasync () => {\n\t\t\tawait updateProp('rscs');\n\t\t},\n\t\t{ deep: false },\n\t);\n\n\tfunction updateTransitions() {\n\t\tplayer.transition.reset();\n\n\t\ttransitions.update(toRaw(props.transitions));\n\n\t\tplayer.transition.init(transitions);\n\t}\n\n\twatch(\n\t\tprops.transitions,\n\t\tasync () => {\n\t\t\tawait updateProp('transitions');\n\t\t\temit('transitionsUpdated');\n\t\t},\n\t\t{ deep: false },\n\t);\n\n\tonMounted(async () => {\n\t\tsetup();\n\n\t\tdisplay.addResizeListener();\n\n\t\tplayer.setup($displayComponent);\n\n\t\tupdateTransitions();\n\n\t\tawait updateResources();\n\n\t\tif (config.autoplay === true) {\n\t\t\tplayer.play();\n\t\t}\n\n\t\temit('mounted');\n\t});\n\n\tonUnmounted(() => {\n\t\ttimers.clear();\n\t\tdisplay.removeResizeListener();\n\t\tkeys.removeKeyListener();\n\n\t\temit('unmounted');\n\t});\n\n\tconst style = computed(() => {\n\t\tif (!display.size.isValid()) {\n\t\t\treturn {};\n\t\t}\n\n\t\tif (display.inFullScreen()) {\n\t\t\treturn {\n\t\t\t\twidth: '100% !important',\n\t\t\t\theight: '100% !important',\n\t\t\t};\n\t\t}\n\n\t\treturn display.size.toPx();\n\t});\n\n\tdefineExpose({\n\t\tshow: player.show.bind(player),\n\t\tplay: player.play.bind(player),\n\t\tstop: player.stop.bind(player),\n\t\tgetPlayer: () => player as Controllers.Player,\n\t\tsize: display.size,\n\t});\n\n\temit('created');\n</script>\n\n<template>\n\t<div\n\t\tref=\"$el\"\n\t\tclass=\"vue-flux\"\n\t\t:style=\"style\"\n\t\t@mousemove=\"mouse.toggle(config, timers, true)\"\n\t\t@mouseleave=\"mouse.toggle(config, timers, false)\"\n\t\t@dblclick=\"display.toggleFullScreen()\"\n\t\t@touchstart=\"touches.start($event, config)\"\n\t\t@touchend=\"touches.end($event, config, player, display, timers, mouse)\"\n\t>\n\t\t<FluxTransition\n\t\t\tv-if=\"\n\t\t\t\t/* eslint-disable vue/html-indent */\n\t\t\t\tplayer.transition.current !== null &&\n\t\t\t\tdisplay.size.isValid() &&\n\t\t\t\tplayer.resource.from !== null &&\n\t\t\t\tplayer.resource.to !== null\n\t\t\t\t/* eslint-enable */\n\t\t\t\"\n\t\t\tref=\"$transition\"\n\t\t\t:transition=\"player.transition.current.component\"\n\t\t\t:size=\"display.size\"\n\t\t\t:from=\"player.resource.from.rsc\"\n\t\t\t:to=\"player.resource.to.rsc\"\n\t\t\t:display-component=\"$displayComponent\"\n\t\t\t:options=\"player.transition.current.options\"\n\t\t\t@ready=\"$transition?.start()\"\n\t\t\t@start=\"player.start()\"\n\t\t\t@end=\"player.end()\"\n\t\t/>\n\n\t\t<component\n\t\t\t:is=\"player.resource.current.rsc.display.component\"\n\t\t\tv-if=\"player.resource.current !== null\"\n\t\t\tref=\"$displayComponent\"\n\t\t\t:size=\"display.size\"\n\t\t\t:rsc=\"player.resource.current.rsc\"\n\t\t\tv-bind=\"player.resource.current.rsc.display.props\"\n\t\t/>\n\n\t\t<div v-if=\"display.size.isValid()\" class=\"complements\">\n\t\t\t<slot name=\"preloader\" :loader=\"resources.loader\" />\n\n\t\t\t<slot name=\"caption\" :player=\"player\" />\n\n\t\t\t<div class=\"remainder upper\" />\n\n\t\t\t<slot name=\"controls\" :mouse-over=\"mouse.isOver\" :player=\"player\" />\n\n\t\t\t<div class=\"remainder lower\" />\n\n\t\t\t<slot\n\t\t\t\tname=\"index\"\n\t\t\t\t:mouse-over=\"mouse.isOver\"\n\t\t\t\t:display-size=\"display.size\"\n\t\t\t\t:player=\"player\"\n\t\t\t/>\n\n\t\t\t<slot name=\"pagination\" :player=\"player\" />\n\t\t</div>\n\t</div>\n</template>\n\n<style lang=\"scss\">\n\t.vue-flux {\n\t\tposition: relative;\n\n\t\t.flux-transition {\n\t\t\tposition: absolute;\n\t\t}\n\n\t\t& > .flux-image {\n\t\t\tposition: absolute;\n\t\t\ttop: 0;\n\t\t\tleft: 0;\n\t\t}\n\n\t\t.complements {\n\t\t\tposition: absolute;\n\t\t\ttop: 0;\n\t\t\tleft: 0;\n\t\t\tright: 0;\n\t\t\tbottom: 0;\n\t\t\tdisplay: flex;\n\t\t\tflex-direction: column;\n\t\t\tjustify-content: space-between;\n\t\t\tz-index: 45;\n\n\t\t\t.remainder {\n\t\t\t\tflex-basis: 50%;\n\t\t\t}\n\t\t}\n\t}\n</style>\n\n\n===== FILE: src/components/VueFlux/__test__/emit.ts =====\nimport { vi } from 'vitest';\nimport type { VueFluxEmits } from '../types';\n\nexport default vi.fn() as unknown as VueFluxEmits;\n\n\n===== FILE: src/components/VueFlux/types.ts =====\nimport { Resource, type ResourceWithOptions } from '../../resources';\nimport type { TransitionWithOptions } from '../../transitions';\nimport { type Direction, PlayerResource, PlayerTransition } from '../../controllers/Player';\nimport { type Component } from 'vue';\n\nexport interface VueFluxOptions {\n\tallowFullscreen?: boolean;\n\tallowToSkipTransition?: boolean;\n\taspectRatio?: string;\n\tautohideTime?: number;\n\tautoplay?: boolean;\n\tbindKeys?: boolean;\n\tdelay?: number;\n\tenableGestures?: boolean;\n\tinfinite?: boolean;\n\tlazyLoad?: boolean;\n\tlazyLoadAfter?: number;\n}\n\nexport interface VueFluxProps {\n\toptions?: VueFluxOptions;\n\trscs: (Resource | ResourceWithOptions)[];\n\ttransitions: (Component | TransitionWithOptions)[];\n}\n\nexport interface VueFluxEmits {\n\t(e: 'created'): void;\n\t(e: 'mounted'): void;\n\t(e: 'unmounted'): void;\n\t(e: 'play', resourceIndex: number | Direction, delay?: number): void;\n\t(e: 'stop'): void;\n\t(e: 'show', resource: PlayerResource, transition: PlayerTransition): void;\n\t(e: 'optionsUpdated'): void;\n\t(e: 'transitionsUpdated'): void;\n\t(e: 'resourcesPreloadStart'): void;\n\t(e: 'resourcesPreloadEnd'): void;\n\t(e: 'resourcesLazyloadStart'): void;\n\t(e: 'resourcesLazyloadEnd'): void;\n\t(e: 'fullscreenEnter'): void;\n\t(e: 'fullscreenExit'): void;\n\t(e: 'transitionStart', resource: PlayerResource, transition: PlayerTransition): void;\n\t(e: 'transitionCancel', resource: PlayerResource, transition: PlayerTransition): void;\n\t(e: 'transitionEnd', resource: PlayerResource, transition: PlayerTransition): void;\n}\n\nexport interface VueFluxConfig {\n\tallowFullscreen: boolean;\n\tallowToSkipTransition: boolean;\n\taspectRatio: string;\n\tautohideTime: number;\n\tautoplay: boolean;\n\tbindKeys: boolean;\n\tdelay: number;\n\tenableGestures: boolean;\n\tinfinite: boolean;\n\tlazyLoad: boolean;\n\tlazyLoadAfter: number;\n}\n\n\n===== FILE: src/components/index.ts =====\nexport { default as FluxButton } from './FluxButton/FluxButton.vue';\nexport * from './FluxCube';\nexport { default as FluxGrid } from './FluxGrid/FluxGrid.vue';\nexport { default as FluxImage } from './FluxImage/FluxImage.vue';\nexport { default as FluxParallax } from './FluxParallax/FluxParallax.vue';\nexport { default as FluxTransition } from './FluxTransition/FluxTransition.vue';\nexport { default as FluxVortex } from './FluxVortex/FluxVortex.vue';\nexport { default as FluxWrapper } from './FluxWrapper/FluxWrapper.vue';\nexport { default as VueFlux } from './VueFlux/VueFlux.vue';\n\nexport type * from './VueFlux/types';\nexport type * from './FluxCube/types';\nexport type * from './FluxGrid/types';\nexport type * from './FluxParallax/types';\nexport type * from './FluxTransition/types';\nexport type * from './FluxVortex/types';\nexport type * from './FluxWrapper/types';\nexport type * from './types';\n\n\n===== FILE: src/components/types.ts =====\nimport type { CSSProperties, Component } from 'vue';\nimport { Resource } from '../resources';\nimport { Size, Position } from '../shared';\n\nexport interface ComponentProps {\n\tcolor?: CSSProperties['color'];\n\trsc?: Resource;\n\tsize: Size;\n\tviewSize?: Size;\n\toffset?: Position;\n\tcss?: CSSProperties;\n}\n\nexport interface ComponentStyles {\n\tbase?: CSSProperties;\n\tcolor?: CSSProperties;\n\trsc?: CSSProperties;\n\tsize?: CSSProperties;\n}\n\nexport type FluxComponent = Component & {\n\tsetCss: (s: CSSProperties) => void;\n\ttransform: (s: CSSProperties) => void;\n\tshow: () => void;\n\thide: () => void;\n};\n\n\n===== FILE: src/components/useComponent.ts =====\nimport { computed, type CSSProperties, type Ref, unref } from 'vue';\nimport { Size } from '../shared';\nimport type { ComponentProps, ComponentStyles } from './types';\n\nexport default function useComponent(\n\t$el: Ref<null | HTMLElement>,\n\tprops: ComponentProps,\n\tcss: ComponentStyles,\n) {\n\tif (css.base === undefined) {\n\t\tcss.base = {} as CSSProperties;\n\t}\n\n\tconst size = computed<CSSProperties>(() => {\n\t\tconst { size, viewSize = new Size() } = props;\n\n\t\tconst { width = size.width.value, height = size.height.value } = viewSize.toValue();\n\n\t\tconst finalSize = new Size({ width, height });\n\n\t\tif (!finalSize.isValid()) {\n\t\t\treturn {};\n\t\t}\n\n\t\treturn finalSize.toPx();\n\t});\n\n\tconst style = computed(() => ({\n\t\t...unref(size),\n\t\t...unref(css.color),\n\t\t...unref(css.rsc),\n\t\t...unref(props.css),\n\t\t...unref(css.base),\n\t}));\n\n\tconst setCss = (s: CSSProperties) => {\n\t\tObject.assign(css.base as CSSProperties, s);\n\t};\n\n\tconst transform = (s: CSSProperties) => {\n\t\tif ($el.value === null) {\n\t\t\treturn;\n\t\t}\n\n\t\t// eslint-disable-next-line @typescript-eslint/no-unused-expressions\n\t\t$el.value.clientHeight;\n\t\tsetCss(s);\n\t};\n\n\tconst show = () => {\n\t\tsetCss({\n\t\t\tvisibility: 'visible',\n\t\t});\n\t};\n\n\tconst hide = () => {\n\t\tsetCss({\n\t\t\tvisibility: 'hidden',\n\t\t});\n\t};\n\n\treturn {\n\t\tstyle,\n\t\tsetCss,\n\t\ttransform,\n\t\tshow,\n\t\thide,\n\t};\n}\n\n\n===== FILE: src/controllers/Display/Display.ts =====\nimport { nextTick, type Ref, type Component } from 'vue';\nimport { Size } from '../../shared';\nimport type { VueFluxConfig, VueFluxEmits } from '../../components';\n\nexport default class Display {\n\tnode: Ref<null | HTMLElement | Component>;\n\tconfig: VueFluxConfig | null;\n\temit: null | VueFluxEmits = null;\n\tsize: Size = new Size();\n\n\tprivate readonly onResize = () => {\n\t\tthis.updateSize();\n\t};\n\n\tconstructor(\n\t\tnode: Ref<null | HTMLElement | Component>,\n\t\tconfig: VueFluxConfig | null = null,\n\t\temit: null | VueFluxEmits = null,\n\t) {\n\t\tthis.node = node;\n\t\tthis.config = config;\n\t\tthis.emit = emit;\n\t}\n\n\tstatic async getSize(node: Ref<null | HTMLElement | Component>) {\n\t\tconst display = new Display(node);\n\t\tawait display.updateSize();\n\n\t\treturn display.size;\n\t}\n\n\taddResizeListener() {\n\t\twindow.addEventListener('resize', this.onResize, {\n\t\t\tpassive: true,\n\t\t});\n\n\t\tvoid this.updateSize();\n\t}\n\n\tremoveResizeListener() {\n\t\twindow.removeEventListener('resize', this.onResize);\n\t}\n\n\tgetAspectRatio() {\n\t\tif (this.config !== null) {\n\t\t\tconst [width, height] = this.config.aspectRatio.split(':');\n\n\t\t\treturn [parseFloat(width ?? ''), parseFloat(height ?? '')];\n\t\t}\n\n\t\treturn [16, 9];\n\t}\n\n\tasync updateSize() {\n\t\tthis.size.reset();\n\n\t\tawait nextTick();\n\n\t\tif (this.node.value === null) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst computedStyle = getComputedStyle(this.node.value as HTMLElement);\n\n\t\tconst width = parseFloat(computedStyle.width);\n\t\tlet height = parseFloat(computedStyle.height);\n\n\t\tif (['0px', 'auto', null].includes(computedStyle.height)) {\n\t\t\tconst [arWidth, arHeight] = this.getAspectRatio();\n\n\t\t\tif (arWidth === undefined || arHeight === undefined) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\theight = (width / arWidth) * arHeight;\n\t\t}\n\n\t\tthis.size.update({\n\t\t\twidth,\n\t\t\theight,\n\t\t});\n\t}\n\n\tinFullScreen = () => !!document.fullscreenElement;\n\n\ttoggleFullScreen() {\n\t\t// eslint-disable-next-line @typescript-eslint/no-unused-expressions\n\t\tthis.inFullScreen() ? this.exitFullScreen() : this.enterFullScreen();\n\t}\n\n\tasync enterFullScreen() {\n\t\tif (this.node?.value === null || !this.config?.allowFullscreen) {\n\t\t\treturn;\n\t\t}\n\n\t\tawait (this.node.value as HTMLElement).requestFullscreen();\n\n\t\tif (this.emit !== null) {\n\t\t\tthis.emit('fullscreenEnter');\n\t\t}\n\t}\n\n\tasync exitFullScreen() {\n\t\tawait document.exitFullscreen();\n\n\t\tif (this.emit !== null) {\n\t\t\tthis.emit('fullscreenExit');\n\t\t}\n\t}\n}\n\n\n===== FILE: src/controllers/Keys/Keys.ts =====\nimport type { VueFluxConfig } from '../../components';\nimport { Directions, Player } from '../';\n\nexport default class Keys {\n\tconfig: VueFluxConfig;\n\tplayer: Player;\n\n\tconstructor(config: VueFluxConfig, player: Player) {\n\t\tthis.config = config;\n\t\tthis.player = player;\n\t}\n\n\tsetup() {\n\t\tthis.removeKeyListener();\n\n\t\tif (this.config.bindKeys) {\n\t\t\twindow.addEventListener('keydown', this.keydown);\n\t\t}\n\t}\n\n\tremoveKeyListener() {\n\t\twindow.removeEventListener('keydown', this.keydown);\n\t}\n\n\tkeydown = (event: KeyboardEvent) => {\n\t\tif (['ArrowLeft', 'Left'].includes(event.key)) {\n\t\t\tthis.player.show(Directions.prev);\n\t\t\treturn;\n\t\t}\n\n\t\tif (['ArrowRight', 'Right'].includes(event.key)) {\n\t\t\tthis.player.show(Directions.next);\n\t\t\treturn;\n\t\t}\n\t};\n}\n\n\n===== FILE: src/controllers/Mouse/Mouse.ts =====\nimport { type Ref, ref } from 'vue';\nimport Timers from '../Timers/Timers';\nimport type { VueFluxConfig } from '../../components';\n\nexport default class Mouse {\n\tisOver: Ref<boolean> = ref(false);\n\n\tsetup(config: VueFluxConfig, timers: Timers) {\n\t\ttimers.clear('mouseOver');\n\n\t\tif (config.autohideTime === 0) {\n\t\t\tthis.isOver.value = true;\n\t\t}\n\t}\n\n\ttoggle(config: VueFluxConfig, timers: Timers, over: boolean) {\n\t\tif (config.autohideTime === 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.isOver.value = over;\n\n\t\tthis[over ? 'over' : 'out'](config, timers);\n\t}\n\n\tout(_config: VueFluxConfig, timers: Timers) {\n\t\ttimers.clear('mouseOver');\n\t}\n\n\tover(config: VueFluxConfig, timers: Timers) {\n\t\ttimers.set('mouseOver', config.autohideTime, () => (this.isOver.value = false));\n\t}\n}\n\n\n===== FILE: src/controllers/Player/Directions.ts =====\nenum Directions {\n\tprev = 'prev',\n\tnext = 'next',\n}\n\nexport default Directions;\n\n\n===== FILE: src/controllers/Player/Player.ts =====\nimport { shallowReactive, nextTick, type Ref, ref } from 'vue';\nimport {\n\tResources,\n\tTransitions,\n\ttype ResourceIndex,\n\ttype TransitionIndex,\n} from '../../repositories';\nimport { PlayerResource, PlayerTransition, Directions, type Direction, Statuses } from './';\nimport type { FluxComponent, VueFluxConfig, VueFluxEmits } from '../../components';\nimport { Timers } from '../';\n\nexport default class Player {\n\tresource: PlayerResource;\n\ttransition: PlayerTransition;\n\n\tstatus: Ref<keyof typeof Statuses> = ref(Statuses.stopped);\n\tconfig: VueFluxConfig;\n\ttimers: Timers;\n\n\temit: VueFluxEmits;\n\tresources: Resources;\n\ttransitions: Transitions;\n\t$displayComponent: Ref<null | FluxComponent> = ref(null);\n\n\tconstructor(\n\t\tconfig: VueFluxConfig,\n\t\ttimers: Timers,\n\n\t\temit: VueFluxEmits,\n\t) {\n\t\tthis.config = config;\n\t\tthis.timers = timers;\n\t\tthis.emit = emit;\n\n\t\tthis.resources = new Resources(emit);\n\t\tthis.transitions = new Transitions();\n\t\tthis.resource = shallowReactive(new PlayerResource());\n\t\tthis.transition = shallowReactive(new PlayerTransition());\n\t}\n\n\tsetup($displayComponent: Ref<null | FluxComponent>) {\n\t\tthis.$displayComponent = $displayComponent;\n\t}\n\n\tplay(resourceIndex: number | Direction = Directions.next, delay?: number) {\n\t\tconst { config, timers, resource } = this;\n\n\t\tthis.status.value = Statuses.playing;\n\n\t\tif (this.transition.current !== null) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst rsc = this.resources?.find(resourceIndex, resource.current?.index);\n\n\t\ttimers.set('transition', delay || rsc?.options.delay || config.delay, () => {\n\t\t\tthis.show(resourceIndex);\n\t\t});\n\n\t\tthis.emit('play', resourceIndex, delay);\n\t}\n\n\tasync stop(cancelTransition: boolean = false) {\n\t\tconst { timers } = this;\n\n\t\tthis.status.value = Statuses.stopped;\n\n\t\ttimers.clear('transition');\n\n\t\tif (this.transition.current !== null && cancelTransition === true) {\n\t\t\tawait this.end(cancelTransition);\n\t\t}\n\n\t\tthis.emit('stop');\n\t}\n\n\tisReadyToShow() {\n\t\tif (this.resource.current === null) {\n\t\t\tthrow new ReferenceError('Current resource not set');\n\t\t}\n\n\t\tif (this.resources === null) {\n\t\t\tthrow new ReferenceError('Resources list not set');\n\t\t}\n\n\t\tif (this.resources.list.length === 0) {\n\t\t\tthrow new RangeError('Resources list empty');\n\t\t}\n\n\t\tif (this.transition.last === null) {\n\t\t\tthrow new ReferenceError('Last transition not set');\n\t\t}\n\n\t\tif (this.transitions === null) {\n\t\t\tthrow new ReferenceError('Transitions list not set');\n\t\t}\n\n\t\tif (this.transitions.list.length === 0) {\n\t\t\tthrow new RangeError('Transitions list empty');\n\t\t}\n\n\t\tif (this.$displayComponent.value === null) {\n\t\t\tthrow new ReferenceError('Display component not set');\n\t\t}\n\n\t\treturn true;\n\t}\n\n\tasync show(\n\t\tresourceIndex: number | Direction = Directions.next,\n\t\ttransitionIndex: number | Direction = Directions.next,\n\t) {\n\t\tif (!this.isReadyToShow()) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst { resource, resources, config, transitions } = this;\n\n\t\tif (this.transition.current !== null) {\n\t\t\tif (config.allowToSkipTransition) {\n\t\t\t\tawait this.end(true);\n\n\t\t\t\tthis.show(resourceIndex, transitionIndex);\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\tconst resourceTo: ResourceIndex = resources!.find(resourceIndex, resource.current!.index);\n\n\t\tif (resource.currentSameAs(resourceTo)) {\n\t\t\treturn;\n\t\t}\n\n\t\tresource.prepareTo(resourceTo);\n\n\t\tthis.timers.clear('transition');\n\n\t\tconst transition: TransitionIndex =\n\t\t\ttypeof transitionIndex === 'number'\n\t\t\t\t? transitions!.getByIndex(transitionIndex)\n\t\t\t\t: transitions!.getByOrder(transitionIndex, this.transition.last!.index);\n\n\t\tif (transition.options.direction === undefined) {\n\t\t\tif (typeof resourceIndex !== 'number') {\n\t\t\t\ttransition.options.direction = resourceIndex;\n\t\t\t} else {\n\t\t\t\ttransition.options.direction =\n\t\t\t\t\tthis.resource.from!.index < this.resource.to!.index\n\t\t\t\t\t\t? Directions.next\n\t\t\t\t\t\t: Directions.prev;\n\t\t\t}\n\t\t}\n\n\t\tthis.transition.current = transition;\n\n\t\tthis.emit('show', this.resource, this.transition);\n\t}\n\n\tstart() {\n\t\tthis.resource.current = this.resource.to;\n\t\tthis.emit('transitionStart', this.resource, this.transition);\n\t}\n\n\tasync end(cancel: boolean = false) {\n\t\tconst { config, resource, resources, timers, transition } = this;\n\n\t\tif (resource.current === null || resources === null) {\n\t\t\treturn;\n\t\t}\n\n\t\ttransition.setCurrentFinished();\n\n\t\tawait nextTick();\n\n\t\tif (cancel === true) {\n\t\t\tthis.emit('transitionCancel', this.resource, this.transition);\n\t\t} else {\n\t\t\tthis.emit('transitionEnd', this.resource, this.transition);\n\t\t}\n\n\t\tif (this.shouldStopPlaying(config.infinite, resource.current, resources.list.length - 1)) {\n\t\t\tthis.stop();\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.shouldPlayNext()) {\n\t\t\ttimers.set('transition', resource.current.options.delay || config.delay, () => {\n\t\t\t\tthis.show();\n\t\t\t});\n\t\t}\n\t}\n\n\tprivate shouldStopPlaying(\n\t\tinfinite: boolean,\n\t\tcurrentResource: ResourceIndex,\n\t\ttotalResources: number,\n\t) {\n\t\tif (\n\t\t\tinfinite === false &&\n\t\t\tcurrentResource.index >= totalResources &&\n\t\t\tthis.status.value === Statuses.playing\n\t\t) {\n\t\t\treturn true;\n\t\t}\n\n\t\tif (currentResource.options.stop === true) {\n\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\tprivate shouldPlayNext() {\n\t\tif (this.status.value === Statuses.playing) {\n\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n\t}\n}\n\n\n===== FILE: src/controllers/Player/Resource.ts =====\nimport { Resources, type ResourceIndex } from '../../repositories';\n\nexport default class PlayerResource {\n\tcurrent: ResourceIndex | null = null;\n\tfrom: ResourceIndex | null = null;\n\tto: ResourceIndex | null = null;\n\n\treset() {\n\t\tthis.current = null;\n\t\tthis.from = null;\n\t\tthis.to = null;\n\t}\n\n\tinit(repository: Resources) {\n\t\tthis.current = repository.getFirst();\n\t}\n\n\tcurrentSameAs(resourceTo: ResourceIndex) {\n\t\tif (this.current!.index === resourceTo.index) {\n\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\tprepareTo(resourceTo: ResourceIndex) {\n\t\tthis.from = this.current;\n\t\tthis.to = resourceTo;\n\t}\n}\n\n\n===== FILE: src/controllers/Player/Statuses.ts =====\nenum Statuses {\n\tstopped = 'stopped',\n\tplaying = 'playing',\n}\n\nexport default Statuses;\n\n\n===== FILE: src/controllers/Player/Transition.ts =====\nimport { Transitions, type TransitionIndex } from '../../repositories';\n\nexport default class PlayerTransition {\n\tcurrent: TransitionIndex | null = null;\n\tlast: TransitionIndex | null = null;\n\n\treset() {\n\t\tthis.current = null;\n\t\tthis.last = null;\n\t}\n\n\tinit(transitions: Transitions) {\n\t\tthis.last = transitions.getLast();\n\t}\n\n\tsetCurrentFinished() {\n\t\tthis.last = this.current;\n\t\tthis.current = null;\n\t}\n}\n\n\n===== FILE: src/controllers/Player/__mocks__/Player.ts =====\nimport { vi } from 'vitest';\nimport { type Ref, ref, shallowReactive } from 'vue';\nimport type { VueFluxConfig, VueFluxEmits } from '../../../components/VueFlux/types';\nimport { PlayerResource, PlayerTransition, Statuses, Timers } from '../..';\nimport { Resources, Transitions } from '../../../repositories';\nimport type { FluxComponent } from '../../../components/types';\n\nexport default class Player {\n\tresource: PlayerResource;\n\ttransition: PlayerTransition;\n\n\tstatus: Ref<keyof typeof Statuses> = ref(Statuses.stopped);\n\tconfig: VueFluxConfig;\n\ttimers: Timers;\n\temit: VueFluxEmits;\n\tresources: Resources;\n\ttransitions: Transitions;\n\t$displayComponent: Ref<null | FluxComponent> = ref(null);\n\n\tconstructor(config: VueFluxConfig, timers: Timers, emit: VueFluxEmits) {\n\t\tthis.config = config;\n\t\tthis.timers = timers;\n\t\tthis.emit = emit;\n\n\t\tthis.resources = new Resources(emit);\n\t\tthis.transitions = new Transitions();\n\t\tthis.resource = shallowReactive(new PlayerResource());\n\t\tthis.transition = shallowReactive(new PlayerTransition());\n\t}\n\n\tsetup = vi.fn();\n\tplay = vi.fn();\n\tstop = vi.fn();\n\tshow = vi.fn();\n\tstart = vi.fn();\n\tend = vi.fn();\n}\n\n\n===== FILE: src/controllers/Player/__mocks__/Resource.ts =====\nimport type { ResourceIndex } from '../../../repositories';\nimport { vi } from 'vitest';\n\nexport default class PlayerResource {\n\tcurrent: ResourceIndex | null = null;\n\tfrom: ResourceIndex | null = null;\n\tto: ResourceIndex | null = null;\n\n\treset = vi.fn();\n\tinit = vi.fn();\n\tcurrentSameAs = vi.fn();\n\tprepareTo = vi.fn();\n}\n\n\n===== FILE: src/controllers/Player/__mocks__/Transitions.ts =====\nimport type { TransitionIndex } from '../../../repositories';\nimport { vi } from 'vitest';\n\nexport default class PlayerTransition {\n\tcurrent: TransitionIndex | null = null;\n\tlast: TransitionIndex | null = null;\n\n\treset = vi.fn();\n\tinit = vi.fn();\n\tsetCurrentFinished = vi.fn();\n}\n\n\n===== FILE: src/controllers/Player/index.ts =====\nexport { default as Directions } from './Directions';\nexport { default as Statuses } from './Statuses';\nexport { default as PlayerResource } from './Resource';\nexport { default as PlayerTransition } from './Transition';\nexport { default as Player } from './Player';\n\nexport type * from './types';\n\n\n===== FILE: src/controllers/Player/types.ts =====\nimport Directions from './Directions';\n\nexport type Direction = Directions.prev | Directions.next;\n\n\n===== FILE: src/controllers/Timers/Timers.ts =====\nexport default class Timers {\n\ttimers: {\n\t\t[index: string]: ReturnType<typeof setTimeout>;\n\t} = {};\n\n\tset(index: string, time: number, cb: () => void) {\n\t\tthis.clear(index);\n\t\tthis.timers[index] = setTimeout(cb, time);\n\t}\n\n\tclear(index?: string) {\n\t\tconst keys = index !== undefined ? [index] : Object.keys(this.timers);\n\n\t\tkeys.forEach((key) => {\n\t\t\tclearTimeout(this.timers[key]);\n\t\t\tdelete this.timers[key];\n\t\t});\n\t}\n}\n\n\n===== FILE: src/controllers/Touches/Touches.ts =====\nimport type { VueFluxConfig } from '../../components';\nimport { Directions, Display, Mouse, Player, Timers } from '../';\n\nexport default class Touches {\n\tstartX = 0;\n\tstartY = 0;\n\tstartTime = 0;\n\tendTime = 0;\n\tprevTouchTime = 0;\n\n\t// Max distance in pixels from start until end\n\ttapThreshold = 5;\n\n\t// Max time in ms from first to second tap\n\tdoubleTapThreshold = 200;\n\n\t// Distance in percentage to trigger slide\n\tslideTrigger = 0.3;\n\n\tstart(event: TouchEvent, config: VueFluxConfig) {\n\t\tif (!config.enableGestures) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst touch = event.changedTouches[0];\n\n\t\tif (!touch) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.startTime = Date.now();\n\t\tthis.startX = touch.clientX;\n\t\tthis.startY = touch.clientY;\n\t}\n\n\tend(\n\t\tevent: TouchEvent,\n\t\tconfig: VueFluxConfig,\n\t\tplayer: Player,\n\t\tdisplay: Display,\n\t\ttimers: Timers,\n\t\tmouse: Mouse,\n\t) {\n\t\tthis.prevTouchTime = this.endTime;\n\t\tthis.endTime = Date.now();\n\n\t\tconst touch = event.changedTouches[0];\n\n\t\tif (!touch) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst offsetX = touch.clientX - this.startX;\n\t\tconst offsetY = touch.clientY - this.startY;\n\n\t\tif (this.tap(offsetX, offsetY)) {\n\t\t\tmouse.toggle(config, timers, true);\n\t\t\treturn;\n\t\t}\n\n\t\tif (!config.enableGestures) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.slideRight(offsetX, display)) {\n\t\t\tplayer.show(Directions.prev);\n\t\t} else if (this.slideLeft(offsetX, display)) {\n\t\t\tplayer.show(Directions.next);\n\t\t}\n\t}\n\n\ttap = (offsetX: number, offsetY: number) =>\n\t\tMath.abs(offsetX) < this.tapThreshold && Math.abs(offsetY) < this.tapThreshold;\n\n\tdoubleTap = () => this.endTime - this.prevTouchTime < this.doubleTapThreshold;\n\n\tslideLeft = (offsetX: number, display: Display) =>\n\t\tdisplay.size.isValid() &&\n\t\toffsetX < 0 &&\n\t\toffsetX < -(display.size!.width.value! * this.slideTrigger);\n\n\tslideRight = (offsetX: number, display: Display) =>\n\t\tdisplay.size.isValid() &&\n\t\toffsetX > 0 &&\n\t\toffsetX > display.size.width.value! * this.slideTrigger;\n\n\tslideUp = (offsetY: number, display: Display) =>\n\t\tdisplay.size.isValid() &&\n\t\toffsetY < 0 &&\n\t\toffsetY < -(display.size.height.value! * this.slideTrigger);\n\n\tslideDown = (offsetY: number, display: Display) =>\n\t\tdisplay.size.isValid() &&\n\t\toffsetY > 0 &&\n\t\toffsetY > display.size.height.value! * this.slideTrigger;\n}\n\n\n===== FILE: src/controllers/index.ts =====\nexport * from './Player';\nexport { default as Display } from './Display/Display';\nexport { default as Keys } from './Keys/Keys';\nexport { default as Mouse } from './Mouse/Mouse';\nexport { default as Timers } from './Timers/Timers';\nexport { default as Touches } from './Touches/Touches';\n\n\n===== FILE: src/lib.ts =====\nexport * from './components';\nexport * from './complements';\nexport * from './resources';\nexport * from './transitions';\n\nexport {\n\tPlayer,\n\tDirections,\n\tStatuses,\n\tPlayerResource,\n\tPlayerTransition,\n} from './controllers/Player';\n\nexport type * from './controllers/Player/types';\n\nexport { Size, Position } from './shared';\n\n\n===== FILE: src/main.ts =====\nimport './assets/css/main.css';\n\nimport { createApp } from 'vue';\nimport App from './App.vue';\n\ncreateApp(App).mount('#app');\n\n\n===== FILE: src/module.d.ts =====\ndeclare module '*';\n\n\n===== FILE: src/playgrounds/PgFluxCaption.vue =====\n<script setup lang=\"ts\">\n\timport { ref, shallowReactive, onMounted, type Ref } from 'vue';\n\timport { VcParagraph } from 'vue-cosk';\n\timport { VueFlux } from '../components';\n\timport { FluxCaption } from '../complements';\n\timport { Img } from '../resources';\n\timport { Book, Zip } from '../transitions';\n\timport { Player } from '../controllers';\n\n\tconst $vueFlux = ref();\n\n\tconst options = shallowReactive({\n\t\tautoplay: true,\n\t});\n\n\tconst rscs = shallowReactive([\n\t\tnew Img(`/images/01.jpg`, 'img 01'),\n\t\tnew Img(`/images/02.jpg`, 'img 02'),\n\t\tnew Img(`/images/03.jpg`, 'img 03'),\n\t\tnew Img(`/images/04.jpg`, 'img 04'),\n\t\tnew Img(`/images/05.jpg`, 'img 05'),\n\t\tnew Img(`/images/06.jpg`, 'img 06'),\n\t]);\n\n\tconst transitions = shallowReactive([Book, Zip]);\n\n\tconst player: Ref<null | Player> = ref(null);\n\n\tonMounted(() => {\n\t\tplayer.value = $vueFlux.value.getPlayer();\n\t});\n</script>\n\n<template>\n\t<div>\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\n\t\t<VueFlux :options=\"options\" :rscs=\"rscs\" :transitions=\"transitions\">\n\t\t\t<template #caption=\"captionProps\">\n\t\t\t\t<FluxCaption v-bind=\"captionProps\" />\n\t\t\t</template>\n\t\t</VueFlux>\n\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\n\t\t<VueFlux ref=\"$vueFlux\" :options=\"options\" :rscs=\"rscs\" :transitions=\"transitions\" />\n\n\t\t<FluxCaption v-if=\"player\" :player=\"player\" />\n\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\t</div>\n</template>\n\n\n===== FILE: src/playgrounds/PgFluxControls.vue =====\n<script setup lang=\"ts\">\n\timport { ref, shallowReactive, onMounted } from 'vue';\n\timport { VcParagraph } from 'vue-cosk';\n\timport { VueFlux } from '../components';\n\timport { FluxControls } from '../complements';\n\timport { Img } from '../resources';\n\timport { Book, Zip } from '../transitions';\n\n\tconst $vueFlux = ref();\n\n\tconst options = shallowReactive({\n\t\tautoplay: true,\n\t});\n\n\tconst rscs = shallowReactive([\n\t\tnew Img(`/images/01.jpg`, 'img 01'),\n\t\tnew Img(`/images/02.jpg`, 'img 02'),\n\t\tnew Img(`/images/03.jpg`, 'img 03'),\n\t\tnew Img(`/images/04.jpg`, 'img 04'),\n\t\tnew Img(`/images/05.jpg`, 'img 05'),\n\t\tnew Img(`/images/06.jpg`, 'img 06'),\n\t]);\n\n\tconst transitions = shallowReactive([Book, Zip]);\n\n\tconst mounted = ref(false);\n\n\tonMounted(() => {\n\t\tmounted.value = true;\n\t});\n</script>\n\n<template>\n\t<div>\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\n\t\t<VueFlux :options=\"options\" :rscs=\"rscs\" :transitions=\"transitions\">\n\t\t\t<template #controls=\"controlsProps\">\n\t\t\t\t<FluxControls v-bind=\"controlsProps\" />\n\t\t\t</template>\n\t\t</VueFlux>\n\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\n\t\t<VueFlux\n\t\t\tref=\"$vueFlux\"\n\t\t\t:options=\"options\"\n\t\t\t:rscs=\"rscs\"\n\t\t\t:transitions=\"transitions\"\n\t\t/>\n\n\t\t<FluxControls v-if=\"mounted\" :player=\"$vueFlux.getPlayer()\" />\n\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\t</div>\n</template>\n\n\n===== FILE: src/playgrounds/PgFluxCube.vue =====\n<script setup lang=\"ts\">\n\timport { ref, type Ref } from 'vue';\n\timport { VcParagraph } from 'vue-cosk';\n\timport Img from '../resources/Img/Img';\n\timport Size from '../shared/Size/Size';\n\timport { ResizeTypes } from '../resources';\n\timport { FluxCube, Turns } from '../components';\n\timport PgButton from './components/PgButton.vue';\n\n\tconst $fluxCube: Ref<null | InstanceType<typeof FluxCube>> = ref(null);\n\n\tconst origins = {\n\t\t['auto (undefined)']: undefined,\n\t\t['left top']: 'left top',\n\t\t['left center']: 'left center',\n\t\t['left bottom']: 'left bottom',\n\t\t['center top']: 'center top',\n\t\t['center center']: 'center center',\n\t\t['center bottom']: 'center bottom',\n\t\t['right top']: 'right top',\n\t\t['right center']: 'right center',\n\t\t['right bottom']: 'right bottom',\n\t};\n\n\tconst colors = {\n\t\tfront: '#ccc',\n\t\tleft: '#ccc',\n\t\tright: '#ccc',\n\t\ttop: '#ccc',\n\t\tbottom: '#ccc',\n\t\tback: '#ccc',\n\t};\n\n\tconst rscs = {\n\t\tfront: new Img(`/images/01.jpg`, 'img 01'),\n\t\tleft: new Img(`/images/02.jpg`, 'img 02', ResizeTypes.fit),\n\t\tright: new Img(`/images/03.jpg`, 'img 03', ResizeTypes.fit),\n\t\ttop: new Img(`/images/04.jpg`, 'img 04', ResizeTypes.fit),\n\t\tbottom: new Img(`/images/05.jpg`, 'img 05', ResizeTypes.fit),\n\t\tback: new Img(`/images/06.jpg`, 'img 06', ResizeTypes.fit),\n\t};\n\n\tconst size = new Size({\n\t\twidth: 640,\n\t\theight: 360,\n\t});\n\n\tconst depth: Ref<number> = ref(160);\n\tconst origin: Ref<undefined | string> = ref(undefined);\n\tconst turnTo: Ref<Turns> = ref(Turns.right);\n\n\tfunction turn() {\n\t\t$fluxCube.value?.turn(turnTo.value);\n\t}\n</script>\n\n<template>\n\t<div>\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\n\t\t<div style=\"perspective: 1600px\" class=\"my-20\">\n\t\t\t<FluxCube\n\t\t\t\tref=\"$fluxCube\"\n\t\t\t\t:colors=\"colors\"\n\t\t\t\t:rscs=\"rscs\"\n\t\t\t\t:depth=\"depth\"\n\t\t\t\t:size=\"size\"\n\t\t\t\t:origin=\"origin\"\n\t\t\t\tstyle=\"transition: all 2000ms ease-out 0s\"\n\t\t\t/>\n\t\t</div>\n\n\t\t<label>\n\t\t\t<span>Depth:</span>\n\n\t\t\t<input v-model.number=\"depth\" type=\"number\" style=\"width: 60px\" />\n\t\t</label>\n\n\t\t<label>\n\t\t\t<span>Origin:</span>\n\n\t\t\t<select v-model=\"origin\">\n\t\t\t\t<option v-for=\"(value, key) in origins\" :key=\"key\" :value=\"value\">\n\t\t\t\t\t{{ key }}\n\t\t\t\t</option>\n\t\t\t</select>\n\t\t</label>\n\n\t\t<label>\n\t\t\t<span>Turn:</span>\n\n\t\t\t<select v-model=\"turnTo\">\n\t\t\t\t<option v-for=\"(value, key) in Turns\" :key=\"key\" :value=\"value\">\n\t\t\t\t\t{{ key }}\n\t\t\t\t</option>\n\t\t\t</select>\n\n\t\t\t<PgButton @click=\"turn()\"> Turn </PgButton>\n\t\t</label>\n\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\t</div>\n</template>\n\n\n===== FILE: src/playgrounds/PgFluxGrid.vue =====\n<script setup lang=\"ts\">\n\timport { ref, type Ref } from 'vue';\n\timport { VcParagraph } from 'vue-cosk';\n\timport Img from '../resources/Img/Img';\n\timport Size from '../shared/Size/Size';\n\timport { FluxGrid } from '../components';\n\timport { ResizeTypes } from '../resources';\n\n\tconst $fluxGridImage: Ref<null | InstanceType<typeof FluxGrid>> = ref(null);\n\tconst $fluxGridCube: Ref<null | InstanceType<typeof FluxGrid>> = ref(null);\n\n\tconst rsc = new Img(`/images/01.jpg`, 'img 01', ResizeTypes.fit);\n\n\tconst size = new Size({\n\t\twidth: 640,\n\t\theight: 360,\n\t});\n\n\tconst colors = {\n\t\tfront: '#ccc',\n\t\tleft: '#ccc',\n\t\tright: '#ccc',\n\t\ttop: '#ccc',\n\t\tbottom: '#ccc',\n\t\tback: '#ccc',\n\t};\n\n\tconst rscs = {\n\t\tfront: new Img(`/images/01.jpg`, 'img 01'),\n\t\tleft: new Img(`/images/02.jpg`, 'img 02', ResizeTypes.fit),\n\t\tright: new Img(`/images/03.jpg`, 'img 03', ResizeTypes.fit),\n\t\ttop: new Img(`/images/04.jpg`, 'img 04', ResizeTypes.fit),\n\t\tbottom: new Img(`/images/05.jpg`, 'img 05', ResizeTypes.fit),\n\t\tback: new Img(`/images/06.jpg`, 'img 06', ResizeTypes.fit),\n\t};\n\n\tconst depth: Ref<number> = ref(160);\n</script>\n\n<template>\n\t<div>\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\n\t\t<FluxGrid ref=\"$fluxGridImage\" :rsc=\"rsc\" :size=\"size\" :rows=\"10\" :cols=\"5\" color=\"#ccc\" />\n\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\n\t\t<FluxGrid\n\t\t\tref=\"$fluxGridCube\"\n\t\t\t:rscs=\"rscs\"\n\t\t\t:size=\"size\"\n\t\t\t:rows=\"10\"\n\t\t\t:cols=\"5\"\n\t\t\t:depth=\"depth\"\n\t\t\t:colors=\"colors\"\n\t\t/>\n\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\t</div>\n</template>\n\n\n===== FILE: src/playgrounds/PgFluxImage.vue =====\n<script setup lang=\"ts\">\n\timport { ref, type Ref } from 'vue';\n\timport { VcParagraph } from 'vue-cosk';\n\timport Img from '../resources/Img/Img';\n\timport Size from '../shared/Size/Size';\n\timport { ResizeTypes } from '../resources';\n\timport { FluxImage } from '../components';\n\n\tconst $fluxImage: Ref<null | InstanceType<typeof FluxImage>> = ref(null);\n\n\tconst rscLandscapeFill = new Img(`/images/01.jpg`, 'img 01 fill');\n\n\tconst rscLandscapeFit = new Img(`/images/01.jpg`, 'img 01 fit', ResizeTypes.fit);\n\n\tconst rscPortraitFill = new Img(`/images/00.jpg`, 'img 00 fill');\n\n\tconst rscPortraitFit = new Img(`/images/00.jpg`, 'img 00 fit', ResizeTypes.fit, '#111');\n\n\tconst sizeLandscape = new Size({\n\t\twidth: 640,\n\t\theight: 360,\n\t});\n\n\tconst sizePortrait = new Size({\n\t\twidth: 211,\n\t\theight: 360,\n\t});\n</script>\n\n<template>\n\t<div>\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\n\t\t<FluxImage ref=\"$fluxImage\" :rsc=\"rscLandscapeFill\" :size=\"sizeLandscape\" />\n\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\n\t\t<FluxImage ref=\"$fluxImage\" :rsc=\"rscLandscapeFit\" :size=\"sizeLandscape\" color=\"#ccc\" />\n\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\n\t\t<FluxImage ref=\"$fluxImage\" :rsc=\"rscLandscapeFill\" :size=\"sizePortrait\" />\n\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\n\t\t<FluxImage ref=\"$fluxImage\" :rsc=\"rscLandscapeFit\" :size=\"sizePortrait\" color=\"#ccc\" />\n\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\n\t\t<FluxImage ref=\"$fluxImage\" :rsc=\"rscPortraitFill\" :size=\"sizePortrait\" />\n\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\n\t\t<FluxImage ref=\"$fluxImage\" :rsc=\"rscPortraitFit\" :size=\"sizePortrait\" color=\"#ccc\" />\n\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\n\t\t<FluxImage ref=\"$fluxImage\" :rsc=\"rscPortraitFill\" :size=\"sizeLandscape\" />\n\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\n\t\t<FluxImage ref=\"$fluxImage\" :rsc=\"rscPortraitFit\" :size=\"sizeLandscape\" color=\"#ccc\" />\n\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\t</div>\n</template>\n\n\n===== FILE: src/playgrounds/PgFluxIndex.vue =====\n<script setup lang=\"ts\">\n\timport { ref, shallowReactive, onMounted, type Ref } from 'vue';\n\timport { VcParagraph } from 'vue-cosk';\n\timport { VueFlux } from '../components';\n\timport { FluxIndex } from '../complements';\n\timport { Img } from '../resources';\n\timport { Book, Zip } from '../transitions';\n\timport { Player } from '../controllers';\n\n\tconst $vueFlux = ref();\n\n\tconst options = shallowReactive({\n\t\tautoplay: false,\n\t});\n\n\tconst rscs = shallowReactive([\n\t\tnew Img(`/images/01.jpg`, 'img 01'),\n\t\tnew Img(`/images/02.jpg`, 'img 02'),\n\t\tnew Img(`/images/03.jpg`, 'img 03'),\n\t\tnew Img(`/images/04.jpg`, 'img 04'),\n\t\tnew Img(`/images/05.jpg`, 'img 05'),\n\t\tnew Img(`/images/06.jpg`, 'img 06'),\n\t]);\n\n\tconst transitions = shallowReactive([Book, Zip]);\n\n\tconst player: Ref<null | Player> = ref(null);\n\n\tonMounted(() => {\n\t\tplayer.value = $vueFlux.value.getPlayer();\n\t});\n</script>\n\n<template>\n\t<div>\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\n\t\t<VueFlux :options=\"options\" :rscs=\"rscs\" :transitions=\"transitions\">\n\t\t\t<template #index=\"indexProps\">\n\t\t\t\t<FluxIndex v-bind=\"indexProps\" />\n\t\t\t</template>\n\t\t</VueFlux>\n\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\n\t\t<VueFlux ref=\"$vueFlux\" :options=\"options\" :rscs=\"rscs\" :transitions=\"transitions\" />\n\n\t\t<FluxIndex v-if=\"player\" :display-size=\"$vueFlux.size\" :player=\"player\" />\n\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\t</div>\n</template>\n\n\n===== FILE: src/playgrounds/PgFluxPagination.vue =====\n<script setup lang=\"ts\">\n\timport { ref, shallowReactive, onMounted, type Ref } from 'vue';\n\timport { VcParagraph } from 'vue-cosk';\n\timport { VueFlux } from '../components';\n\timport { FluxPagination } from '../complements';\n\timport { Img } from '../resources';\n\timport { Book, Zip } from '../transitions';\n\timport { Player } from '../controllers';\n\n\tconst $vueFlux = ref();\n\n\tconst options = shallowReactive({\n\t\tautoplay: false,\n\t});\n\n\tconst rscs = shallowReactive([\n\t\tnew Img(`/images/01.jpg`, 'img 01'),\n\t\tnew Img(`/images/02.jpg`, 'img 02'),\n\t\tnew Img(`/images/03.jpg`, 'img 03'),\n\t\tnew Img(`/images/04.jpg`, 'img 04'),\n\t\tnew Img(`/images/05.jpg`, 'img 05'),\n\t\tnew Img(`/images/06.jpg`, 'img 06'),\n\t]);\n\n\tconst transitions = shallowReactive([Book, Zip]);\n\n\tconst player: Ref<null | Player> = ref(null);\n\n\tonMounted(() => {\n\t\tplayer.value = $vueFlux.value.getPlayer();\n\t});\n</script>\n\n<template>\n\t<div>\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\n\t\t<VueFlux :options=\"options\" :rscs=\"rscs\" :transitions=\"transitions\">\n\t\t\t<template #pagination=\"paginationProps\">\n\t\t\t\t<FluxPagination v-bind=\"paginationProps\" />\n\t\t\t</template>\n\t\t</VueFlux>\n\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\n\t\t<VueFlux ref=\"$vueFlux\" :options=\"options\" :rscs=\"rscs\" :transitions=\"transitions\" />\n\n\t\t<FluxPagination v-if=\"player\" :player=\"player\">\n\t\t\t<template #default=\"pageProps\">\n\t\t\t\t<span\n\t\t\t\t\t:title=\"pageProps.title\"\n\t\t\t\t\t:class=\"pageProps.cssClass\"\n\t\t\t\t\t@click=\"player.show(pageProps.index)\"\n\t\t\t\t>\n\t\t\t\t\t{{ pageProps.index }}\n\t\t\t\t</span>\n\t\t\t</template>\n\t\t</FluxPagination>\n\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\t</div>\n</template>\n\n\n===== FILE: src/playgrounds/PgFluxParallax.vue =====\n<script setup lang=\"ts\">\n\timport { VcParagraph } from 'vue-cosk';\n\timport Img from '../resources/Img/Img';\n\timport { FluxParallax } from '../components';\n\n\tconst rsc = new Img(`/images/01.jpg`, 'img 01');\n</script>\n\n<template>\n\t<div>\n\t\t<VcParagraph v-for=\"i of 6\" :key=\"'a' + i\" mode=\"fill\" style=\"margin: 24px 0\" />\n\n\t\t<FluxParallax type=\"fixed\" :rsc=\"rsc\" style=\"height: 200px\" />\n\n\t\t<VcParagraph v-for=\"i of 6\" :key=\"'b' + i\" mode=\"fill\" style=\"margin: 24px 0\" />\n\t</div>\n</template>\n\n\n===== FILE: src/playgrounds/PgFluxParallaxOp.vue =====\n<script lang=\"ts\">\n\timport { defineComponent, markRaw } from 'vue';\n\timport { VcParagraph } from 'vue-cosk';\n\timport Img from '../resources/Img/Img';\n\timport { FluxParallax } from '../components';\n\n\texport default defineComponent({\n\t\tcomponents: {\n\t\t\tVcParagraph,\n\t\t\tFluxParallax,\n\t\t},\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\trsc: markRaw(new Img(`/images/01.jpg`, 'img 01')),\n\t\t\t};\n\t\t},\n\t});\n</script>\n\n<template>\n\t<div>\n\t\t<VcParagraph v-for=\"i of 6\" :key=\"'a' + i\" mode=\"fill\" style=\"margin: 24px 0\" />\n\n\t\t<FluxParallax type=\"fixed\" :rsc=\"rsc\" style=\"height: 200px\" />\n\n\t\t<VcParagraph v-for=\"i of 6\" :key=\"'b' + i\" mode=\"fill\" style=\"margin: 24px 0\" />\n\t</div>\n</template>\n\n\n===== FILE: src/playgrounds/PgFluxPreloader.vue =====\n<script setup lang=\"ts\">\n\timport { shallowReactive } from 'vue';\n\timport { VcParagraph } from 'vue-cosk';\n\timport { VueFlux } from '../components';\n\timport { FluxPreloader } from '../complements';\n\timport { Img } from '../resources';\n\timport { Book, Zip } from '../transitions';\n\n\tconst options = shallowReactive({\n\t\tautoplay: false,\n\t});\n\n\tconst rscs = shallowReactive([\n\t\tnew Img(`/images/01.jpg`, 'img 01'),\n\t\tnew Img(`/images/02.jpg`, 'img 02'),\n\t\tnew Img(`/images/03.jpg`, 'img 03'),\n\t\tnew Img(`/images/04.jpg`, 'img 04'),\n\t\tnew Img(`/images/05.jpg`, 'img 05'),\n\t\tnew Img(`/images/06.jpg`, 'img 06'),\n\t\tnew Img(`/images/07.jpg`, 'img 07'),\n\t\tnew Img(`/images/08.jpg`, 'img 08'),\n\t\tnew Img(`/images/09.jpg`, 'img 09'),\n\t\tnew Img(`/images/10.jpg`, 'img 10'),\n\t\tnew Img(`/images/11.jpg`, 'img 11'),\n\t\tnew Img(`/images/12.jpg`, 'img 12'),\n\t\tnew Img(`/images/13.jpg`, 'img 13'),\n\t\tnew Img(`/images/14.jpg`, 'img 14'),\n\t\tnew Img(`/images/15.jpg`, 'img 15'),\n\t]);\n\n\tconst transitions = shallowReactive([Book, Zip]);\n</script>\n\n<template>\n\t<div>\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\n\t\t<VueFlux :options=\"options\" :rscs=\"rscs\" :transitions=\"transitions\">\n\t\t\t<template #preloader=\"preloaderProps\">\n\t\t\t\t<FluxPreloader v-bind=\"preloaderProps\" />\n\t\t\t</template>\n\t\t</VueFlux>\n\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\n\t\t<VueFlux :options=\"options\" :rscs=\"rscs\" :transitions=\"transitions\">\n\t\t\t<template #preloader=\"preloaderProps\">\n\t\t\t\t<FluxPreloader v-bind=\"preloaderProps\">\n\t\t\t\t\t<template #default=\"props\">\n\t\t\t\t\t\t<div v-if=\"props.preloading\" class=\"custom-spinner\">\n\t\t\t\t\t\t\t{{ props.pct }} %\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</template>\n\t\t\t\t</FluxPreloader>\n\t\t\t</template>\n\t\t</VueFlux>\n\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\t</div>\n</template>\n\n<style lang=\"scss\">\n\t@keyframes spinner {\n\t\tto {\n\t\t\ttransform: rotate(360deg);\n\t\t}\n\t}\n\n\t.custom-spinner {\n\t\tposition: absolute;\n\t\ttop: 50%;\n\t\tleft: 50%;\n\t\ttext-align: center;\n\t\tline-height: 50px;\n\t\tmargin-top: -25px;\n\t\tmargin-left: -25px;\n\t\twidth: 50px;\n\t\theight: 50px;\n\t\tz-index: 14;\n\n\t\t&:before {\n\t\t\tcontent: '';\n\t\t\tbox-sizing: border-box;\n\t\t\tposition: absolute;\n\t\t\ttop: 50%;\n\t\t\tleft: 50%;\n\t\t\twidth: 50px;\n\t\t\theight: 50px;\n\t\t\tmargin-top: -25px;\n\t\t\tmargin-left: -25px;\n\t\t\tborder-radius: 50%;\n\t\t\tborder: 1px solid #ccc;\n\t\t\tborder-top-color: #07d;\n\t\t\tanimation: spinner 0.6s linear infinite;\n\t\t}\n\t}\n</style>\n\n\n===== FILE: src/playgrounds/PgFluxTransition.vue =====\n<script setup lang=\"ts\">\n\timport { nextTick, ref, type Ref, shallowRef } from 'vue';\n\timport { VcParagraph } from 'vue-cosk';\n\timport Img from '../resources/Img/Img';\n\timport Size from '../shared/Size/Size';\n\timport { FluxTransition } from '../components';\n\timport {\n\t\tFade,\n\t\tKenburn,\n\t\tSwipe,\n\t\tSlide,\n\t\tWaterfall,\n\t\tZip,\n\t\tBlinds2D,\n\t\tBlocks1,\n\t\tBlocks2,\n\t\tConcentric,\n\t\tWarp,\n\t\tCamera,\n\t\tCube,\n\t\tBook,\n\t\tFall,\n\t\tWave,\n\t\tBlinds3D,\n\t\tRound1,\n\t\tRound2,\n\t\tExplode,\n\t} from '../transitions';\n\timport PgButton from './components/PgButton.vue';\n\n\tconst $fluxTransition: Ref<null | InstanceType<typeof FluxTransition>> = ref(null);\n\n\tconst transitions = {\n\t\tFade,\n\t\tKenburn,\n\t\tSwipe,\n\t\tSlide,\n\t\tWaterfall,\n\t\tZip,\n\t\tBlinds2D,\n\t\tBlocks1,\n\t\tBlocks2,\n\t\tConcentric,\n\t\tWarp,\n\t\tCamera,\n\t\tCube,\n\t\tBook,\n\t\tFall,\n\t\tWave,\n\t\tBlinds3D,\n\t\tRound1,\n\t\tRound2,\n\t\tExplode,\n\t};\n\n\tconst enabled = ref(true);\n\n\tconst transition = shallowRef(Fade);\n\tconst rscFrom = new Img(`/images/05.jpg`, 'img 05 fill');\n\tconst rscTo = new Img(`/images/06.jpg`, 'img 06 fit');\n\n\tconst size = new Size({\n\t\twidth: 640,\n\t\theight: 360,\n\t});\n\n\tasync function reset() {\n\t\tawait nextTick();\n\n\t\tenabled.value = false;\n\n\t\tawait nextTick();\n\n\t\tenabled.value = true;\n\t}\n</script>\n\n<template>\n\t<div>\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\n\t\t<FluxTransition\n\t\t\tv-if=\"enabled\"\n\t\t\tref=\"$fluxTransition\"\n\t\t\t:size=\"size\"\n\t\t\t:transition=\"transition\"\n\t\t\t:from=\"rscFrom\"\n\t\t\t:to=\"rscTo\"\n\t\t\t@end=\"reset\"\n\t\t/>\n\n\t\t<label v-if=\"$fluxTransition\" class=\"mt-6\">\n\t\t\t<span>Transition</span>\n\n\t\t\t<select v-model=\"transition\">\n\t\t\t\t<option v-for=\"(component, name) in transitions\" :key=\"name\" :value=\"component\">\n\t\t\t\t\t{{ name }}\n\t\t\t\t</option>\n\t\t\t</select>\n\n\t\t\t<PgButton @click=\"$fluxTransition.start()\">Start</PgButton>\n\t\t</label>\n\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\t</div>\n</template>\n\n\n===== FILE: src/playgrounds/PgVueFlux.vue =====\n<script setup lang=\"ts\">\n\timport { ref, type Ref, shallowReactive } from 'vue';\n\timport { VcParagraph } from 'vue-cosk';\n\timport { Img } from '../resources';\n\timport {\n\t\tFade,\n\t\tKenburn,\n\t\tSwipe,\n\t\tSlide,\n\t\tWaterfall,\n\t\tZip,\n\t\tBlinds2D,\n\t\tBlocks1,\n\t\tBlocks2,\n\t\tConcentric,\n\t\tWarp,\n\t\tCamera,\n\t\tCube,\n\t\tBook,\n\t\tFall,\n\t\tWave,\n\t\tBlinds3D,\n\t\tRound1,\n\t\tRound2,\n\t\tExplode,\n\t} from '../transitions';\n\timport * as Complements from '../complements';\n\timport { VueFlux } from '../components';\n\timport PgButton from './components/PgButton.vue';\n\timport ResizeTypes from '../resources/ResizeTypes';\n\timport { Directions } from '../controllers';\n\n\tconst $vueFlux: Ref<null | InstanceType<typeof VueFlux>> = ref(null);\n\n\tconst options = shallowReactive({\n\t\tallowFullscreen: true,\n\t\tautoplay: false,\n\t\tbindKeys: true,\n\t\tinfinite: true,\n\t\tdelay: 5000,\n\t\tlazyLoadAfter: 10,\n\t});\n\n\tconst images = [];\n\tfor (let i = 1; i <= 20; i++) {\n\t\tconst fileName = i.toString().padStart(2, '0');\n\t\tconst image = new Img(\n\t\t\t`/images/${fileName}.jpg`,\n\t\t\t'img ' + i,\n\t\t\tResizeTypes.fill, //i % 2 === 0 ? ResizeTypes.fit : ResizeTypes.fill\n\t\t);\n\t\timages.push(image);\n\t}\n\n\tconst rscs = shallowReactive(images);\n\n\tconst transitions = {\n\t\tBlinds2D,\n\t\tBlinds3D,\n\t\tBlocks1,\n\t\tBlocks2,\n\t\tBook,\n\t\tCamera,\n\t\tConcentric,\n\t\tCube,\n\t\tExplode,\n\t\tFade,\n\t\tFall,\n\t\tKenburn,\n\t\tRound1,\n\t\tRound2,\n\t\tSlide,\n\t\tSwipe,\n\t\tWarp,\n\t\tWaterfall,\n\t\tWave,\n\t\tZip,\n\t};\n\n\tconst transitionComponents = shallowReactive(Object.values(transitions));\n\n\tconst transitionNames = Object.keys(transitions);\n\n\tconst currentTransitionName = ref(null);\n\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\tfunction updateCurrentTransition(_rsc?: any, transition?: any) {\n\t\tif (transition.current !== null) {\n\t\t\tcurrentTransitionName.value = transition.current.component.__name;\n\t\t} else {\n\t\t\tcurrentTransitionName.value = null;\n\t\t}\n\t}\n</script>\n\n<template>\n\t<div>\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0; padding: 0\" />\n\n\t\t<div class=\"block sm:block md:block lg:flex\">\n\t\t\t<div class=\"lg:w-3/4\">\n\t\t\t\t<VueFlux\n\t\t\t\t\tref=\"$vueFlux\"\n\t\t\t\t\t:transitions=\"transitionComponents\"\n\t\t\t\t\t:rscs=\"rscs\"\n\t\t\t\t\t:options=\"options\"\n\t\t\t\t\t@transitionStart=\"updateCurrentTransition\"\n\t\t\t\t\t@transitionEnd=\"updateCurrentTransition\"\n\t\t\t\t>\n\t\t\t\t\t<template #preloader=\"preloaderProps\">\n\t\t\t\t\t\t<Complements.FluxPreloader v-bind=\"preloaderProps\" />\n\t\t\t\t\t</template>\n\n\t\t\t\t\t<template #caption=\"captionProps\">\n\t\t\t\t\t\t<Complements.FluxCaption v-bind=\"captionProps\" />\n\t\t\t\t\t</template>\n\n\t\t\t\t\t<template #controls=\"controlsProps\">\n\t\t\t\t\t\t<Complements.FluxControls v-bind=\"controlsProps\" />\n\t\t\t\t\t</template>\n\n\t\t\t\t\t<template #index=\"indexProps\">\n\t\t\t\t\t\t<Complements.FluxIndex v-bind=\"indexProps\" />\n\t\t\t\t\t</template>\n\n\t\t\t\t\t<template #pagination=\"paginationProps\">\n\t\t\t\t\t\t<Complements.FluxPagination v-bind=\"paginationProps\" />\n\t\t\t\t\t</template>\n\t\t\t\t</VueFlux>\n\t\t\t</div>\n\n\t\t\t<div class=\"lg:w-1/4 lg:ml-4 lg:mt-0 mt-6\">\n\t\t\t\t<ul v-if=\"$vueFlux && $vueFlux.size.isValid()\" class=\"flex flex-wrap\">\n\t\t\t\t\t<li\n\t\t\t\t\t\tv-for=\"(name, index) in transitionNames\"\n\t\t\t\t\t\t:key=\"name\"\n\t\t\t\t\t\tclass=\"odd:pr-4 mb-4 lg:w-1/2 lg:mr-0 mr-4\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<PgButton\n\t\t\t\t\t\t\tclass=\"w-100\"\n\t\t\t\t\t\t\t:active=\"currentTransitionName === name\"\n\t\t\t\t\t\t\t@click=\"$vueFlux.show(Directions.next, index)\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{{ name }}\n\t\t\t\t\t\t</PgButton>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<div v-if=\"$vueFlux\" class=\"mt-6 lg:flex\">\n\t\t\t<PgButton class=\"mr-4 w-1/3\" @click=\"$vueFlux.show()\">Next</PgButton>\n\t\t\t<PgButton class=\"mr-4 w-1/3\" @click=\"$vueFlux.play()\">Play</PgButton>\n\t\t\t<PgButton class=\"w-1/3 mr-0\" @click=\"$vueFlux.stop()\">Stop</PgButton>\n\t\t</div>\n\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0; padding: 0\" />\n\t</div>\n</template>\n\n\n===== FILE: src/playgrounds/components/PgButton.vue =====\n<script setup lang=\"ts\">\n\tconst props = withDefaults(defineProps<{ active?: boolean }>(), {\n\t\tactive: false,\n\t});\n</script>\n\n<template>\n\t<button\n\t\tclass=\"hover:bg-sky-700 text-white rounded px-2 w-full cursor-pointer\"\n\t\t:class=\"props.active ? 'bg-amber-500' : 'bg-sky-500'\"\n\t>\n\t\t<slot />\n\t</button>\n</template>\n\n\n===== FILE: src/repositories/Resources/Resources.test.ts =====\nimport { Directions } from '../../controllers';\nimport { Resource } from '../../resources';\nimport { Size } from '../../shared';\nimport { default as ResourcesRepository } from './Resources';\nimport ResourceFactory from '../../resources/__test__/ResourceFactory';\nimport emit from '../../components/VueFlux/__test__/emit';\n\nvi.mock('../../resources/Img/Img');\nvi.mock('../../shared/ResourceLoader/ResourceLoader');\n\ndescribe('repositories: Resources', () => {\n\tlet repo: ResourcesRepository;\n\tlet resources: Resource[];\n\tconst size: Size = new Size({\n\t\twidth: 640,\n\t\theight: 360,\n\t});\n\n\tdescribe('width preloading', () => {\n\t\tbeforeEach(async () => {\n\t\t\tvi.clearAllMocks();\n\n\t\t\trepo = new ResourcesRepository(emit);\n\n\t\t\tresources = ResourceFactory.create(5);\n\t\t\tawait repo.update(resources, 5, size);\n\t\t});\n\n\t\tit('updates the repository transitions', () => {\n\t\t\texpect(repo.list).toHaveLength(5);\n\t\t});\n\n\t\tit('emits when preload starts', () => {\n\t\t\texpect(emit).toHaveBeenCalledWith('resourcesPreloadStart');\n\t\t});\n\n\t\tit('emits when preload ends', () => {\n\t\t\texpect(emit).toHaveBeenCalledWith('resourcesPreloadEnd');\n\t\t});\n\n\t\tit('gets the first resource', () => {\n\t\t\texpect(repo.getFirst().rsc).toBe(resources[0]);\n\t\t});\n\n\t\tit('gets the last resource', () => {\n\t\t\texpect(repo.getLast().rsc).toBe(resources[4]);\n\t\t});\n\n\t\tit('get resource by index', () => {\n\t\t\texpect(repo.getByIndex(2).rsc).toBe(resources[2]);\n\t\t});\n\n\t\tit('throws error because the requested index does not exist', () => {\n\t\t\tconst index = resources.length + 1;\n\n\t\t\texpect(() => repo.getByIndex(index)).toThrow(\n\t\t\t\t`Resource index ${index} not found`\n\t\t\t);\n\t\t});\n\n\t\tit('get resource by order next', () => {\n\t\t\texpect(\n\t\t\t\trepo.getByOrder(Directions.next, resources.length - 1).rsc\n\t\t\t).toBe(resources[0]);\n\t\t});\n\n\t\tit('get resource by order prev', () => {\n\t\t\texpect(repo.getByOrder(Directions.prev, 0).rsc).toBe(\n\t\t\t\tresources[resources.length - 1]\n\t\t\t);\n\t\t});\n\n\t\tit('throws an error when trying to find a resource by order without passing the current index', () => {\n\t\t\texpect(() => repo.find(Directions.next)).toThrow(\n\t\t\t\t'Missing currentIndex parameter'\n\t\t\t);\n\t\t});\n\t});\n\n\tdescribe('with lazy loading', () => {\n\t\tconst numResources = 10;\n\t\tconst resourcesToPreload = 5;\n\n\t\tbeforeEach(async () => {\n\t\t\tvi.clearAllMocks();\n\n\t\t\trepo = new ResourcesRepository(emit);\n\n\t\t\tresources = ResourceFactory.create(numResources);\n\t\t\tawait repo.update(resources, resourcesToPreload, size);\n\t\t});\n\n\t\tit('emits resourcesLazyloadStart when start lazy loading', () =>\n\t\t\tnew Promise<void>((done) => {\n\t\t\t\texpect(emit).toHaveBeenCalledWith('resourcesLazyloadStart');\n\t\t\t\tdone();\n\t\t\t}));\n\n\t\tit('emits resourcesLazyloadStart when start lazy loading', () =>\n\t\t\tnew Promise<void>((done) => {\n\t\t\t\texpect(repo.list).toHaveLength(numResources);\n\t\t\t\texpect(emit).toHaveBeenCalledWith('resourcesLazyloadEnd');\n\t\t\t\tdone();\n\t\t\t}));\n\t});\n});\n\n\n===== FILE: src/repositories/Resources/Resources.ts =====\nimport { type Ref, ref, shallowReactive } from 'vue';\nimport { Resource, type ResourceWithOptions } from '../../resources';\nimport { Size, ResourceLoader } from '../../shared';\nimport { type Direction, Directions } from '../../controllers/Player';\nimport type { ResourceIndex } from './types';\nimport ResourcesMapper from './ResourcesMapper';\nimport type { VueFluxEmits } from '../../components';\n\nexport default class Resources {\n\tlist: ResourceWithOptions[] = shallowReactive([]);\n\tloader: Ref<ResourceLoader | null> = ref(null);\n\temit: VueFluxEmits;\n\n\tconstructor(emit: VueFluxEmits) {\n\t\tthis.emit = emit;\n\t}\n\n\tprivate getPrev(currentIndex: number) {\n\t\treturn this.getByIndex(currentIndex > 0 ? currentIndex - 1 : this.list.length - 1);\n\t}\n\n\tprivate getNext(currentIndex: number) {\n\t\treturn this.getByIndex(currentIndex === this.list.length - 1 ? 0 : currentIndex + 1);\n\t}\n\n\tgetFirst() {\n\t\treturn this.getByIndex(0);\n\t}\n\n\tgetLast() {\n\t\treturn this.getByOrder(Directions.prev, 0);\n\t}\n\n\tgetByIndex(index: number) {\n\t\tif (this.list[index] === undefined) {\n\t\t\tthrow new ReferenceError(`Resource index ${index} not found`);\n\t\t}\n\n\t\treturn {\n\t\t\tindex,\n\t\t\trsc: this.list[index].resource,\n\t\t\toptions: JSON.parse(JSON.stringify(this.list[index].options)),\n\t\t} as ResourceIndex;\n\t}\n\n\tgetByOrder(order: Direction, currentIndex: number) {\n\t\treturn {\n\t\t\tprev: () => this.getPrev(currentIndex),\n\t\t\tnext: () => this.getNext(currentIndex),\n\t\t}[order]();\n\t}\n\n\tfind(by: number | Direction, currentIndex?: number) {\n\t\tif (typeof by === 'number') {\n\t\t\treturn this.getByIndex(by);\n\t\t}\n\n\t\tif (currentIndex === undefined) {\n\t\t\tthrow new ReferenceError('Missing currentIndex parameter');\n\t\t}\n\n\t\treturn this.getByOrder(by, currentIndex);\n\t}\n\n\tupdate(rscs: (Resource | ResourceWithOptions)[], numToPreload: number, displaySize: Size) {\n\t\tif (this.loader.value?.hasFinished() === false) {\n\t\t\tthis.loader.value?.cancel();\n\t\t}\n\n\t\tthis.list.splice(0);\n\n\t\tconst resources = ResourcesMapper.withOptions(rscs);\n\n\t\tconst updatePromise = new Promise<void>((resolve, reject) => {\n\t\t\tthis.loader.value = new ResourceLoader(\n\t\t\t\tresources,\n\t\t\t\tnumToPreload,\n\t\t\t\tdisplaySize,\n\t\t\t\t() => this.preloadStart(),\n\t\t\t\t(loaded: ResourceWithOptions[]) => this.preloadEnd(loaded, resolve),\n\t\t\t\t() => this.lazyLoadStart(),\n\t\t\t\t(loaded: ResourceWithOptions[]) => this.lazyLoadEnd(loaded),\n\t\t\t\treject,\n\t\t\t);\n\t\t});\n\n\t\treturn updatePromise;\n\t}\n\n\tpreloadStart() {\n\t\tthis.emit('resourcesPreloadStart');\n\t}\n\n\tpreloadEnd(loaded: ResourceWithOptions[], resolve: () => void) {\n\t\tthis.list.push(...loaded);\n\n\t\tthis.emit('resourcesPreloadEnd');\n\n\t\tresolve();\n\t}\n\n\tlazyLoadStart() {\n\t\tthis.emit('resourcesLazyloadStart');\n\t}\n\n\tlazyLoadEnd(loaded: ResourceWithOptions[]) {\n\t\tthis.list.push(...loaded);\n\n\t\tthis.emit('resourcesLazyloadEnd');\n\t}\n}\n\n\n===== FILE: src/repositories/Resources/ResourcesMapper.test.ts =====\nimport { Img, type ResourceWithOptions } from '../../resources';\nimport ResourcesMapper from './ResourcesMapper';\n\ndescribe('repositories: ResourcesMapper', () => {\n\tit('turns all the transitions array as transitions with options', () => {\n\t\tconst resources = [\n\t\t\tnew Img('url1'),\n\t\t\t{\n\t\t\t\tresource: new Img('url2'),\n\t\t\t\toptions: {\n\t\t\t\t\tdelay: 8000,\n\t\t\t\t},\n\t\t\t} as ResourceWithOptions,\n\t\t\tnew Img('url3'),\n\t\t];\n\n\t\tconst resourcesWithOptions = ResourcesMapper.withOptions(resources);\n\n\t\texpect(resourcesWithOptions[0]!.resource).toBe(resources[0]);\n\t\t// @ts-expect-error:next-line\n\t\texpect(resourcesWithOptions[1]!.resource).toBe(resources[1].resource);\n\t\texpect(resourcesWithOptions[1]!.options.delay).toBe(8000);\n\t\texpect(resourcesWithOptions[2]!.resource).toBe(resources[2]);\n\t\texpect(resourcesWithOptions[2]!.options).toStrictEqual({});\n\t});\n});\n\n\n===== FILE: src/repositories/Resources/ResourcesMapper.ts =====\nimport { Resource, type ResourceWithOptions } from '../../resources';\n\nexport default class ResourcesMapper {\n\tstatic withOptions(rscs: (Resource | ResourceWithOptions)[]) {\n\t\treturn rscs.map((rsc) => {\n\t\t\tlet resource = rsc;\n\t\t\tlet options = {};\n\n\t\t\tif ('resource' in rsc) {\n\t\t\t\tresource = rsc.resource as Resource;\n\n\t\t\t\tif ('options' in rsc) {\n\t\t\t\t\toptions = rsc.options as object;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn { resource, options } as ResourceWithOptions;\n\t\t});\n\t}\n}\n\n\n===== FILE: src/repositories/Resources/types.ts =====\nimport Resource from '../../resources/Resource';\n\nexport interface ResourceIndex {\n\tindex: number;\n\trsc: Resource;\n\toptions: {\n\t\tdelay?: number;\n\t\tstop?: boolean;\n\t};\n}\n\n\n===== FILE: src/repositories/Transitions/Transitions.test.ts =====\nimport { Directions } from '../../controllers';\nimport { default as TransitionsRepository } from './Transitions';\n\nfunction transitionsFactory(numTransitions: number) {\n\treturn new Array(numTransitions).fill({});\n}\n\ndescribe('repositories: Transitions', () => {\n\tlet repo: TransitionsRepository;\n\tlet transitions: object[];\n\n\tbeforeEach(() => {\n\t\trepo = new TransitionsRepository();\n\t});\n\n\tit('updates the repository transitions', () => {\n\t\ttransitions = transitionsFactory(5);\n\t\trepo.update(transitions);\n\n\t\texpect(repo.list).toHaveLength(5);\n\t});\n\n\tit('removes the previous transitions on update', () => {\n\t\ttransitions = transitionsFactory(5);\n\t\trepo.update(transitions);\n\n\t\texpect(repo.list).toHaveLength(5);\n\n\t\ttransitions = transitionsFactory(2);\n\t\trepo.update(transitions);\n\t\texpect(repo.list).toHaveLength(2);\n\t});\n\n\tit('gets the first transition', () => {\n\t\ttransitions = transitionsFactory(5);\n\t\trepo.update(transitions);\n\n\t\texpect(repo.getFirst().component).toBe(transitions[0]);\n\t});\n\n\tit('gets the last transition', () => {\n\t\ttransitions = transitionsFactory(5);\n\t\trepo.update(transitions);\n\n\t\texpect(repo.getLast().component).toBe(\n\t\t\ttransitions[transitions.length - 1]\n\t\t);\n\t});\n\n\tit('gets the transition by an index number', () => {\n\t\ttransitions = transitionsFactory(5);\n\t\trepo.update(transitions);\n\n\t\texpect(repo.getByIndex(2).component).toBe(transitions[2]);\n\t});\n\n\tit('gets the transition by order next', () => {\n\t\ttransitions = transitionsFactory(5);\n\t\trepo.update(transitions);\n\n\t\texpect(repo.getByOrder(Directions.next, 2).component).toBe(\n\t\t\ttransitions[3]\n\t\t);\n\t});\n\n\tit('gets fist the transition by order next', () => {\n\t\ttransitions = transitionsFactory(5);\n\t\trepo.update(transitions);\n\n\t\texpect(repo.getByOrder(Directions.next, 4).component).toBe(\n\t\t\ttransitions[3]\n\t\t);\n\t});\n\n\tit('gets the transition by order previous', () => {\n\t\ttransitions = transitionsFactory(5);\n\t\trepo.update(transitions);\n\n\t\texpect(repo.getByOrder(Directions.prev, 2).component).toBe(\n\t\t\ttransitions[1]\n\t\t);\n\t});\n\n\tit('gets the last transition by order previous', () => {\n\t\ttransitions = transitionsFactory(5);\n\t\trepo.update(transitions);\n\n\t\texpect(repo.getByOrder(Directions.prev, 0).component).toBe(\n\t\t\ttransitions[1]\n\t\t);\n\t});\n});\n\n\n===== FILE: src/repositories/Transitions/Transitions.ts =====\nimport { type Component, shallowReactive } from 'vue';\nimport { Directions, type Direction } from '../../controllers/Player';\nimport type { TransitionIndex } from './types';\nimport type { TransitionWithOptions } from '../../transitions/types';\nimport TransitionsMapper from './TransitionsMapper';\n\nexport default class Transitions {\n\tlist: TransitionWithOptions[] = shallowReactive([]);\n\n\tprivate getPrev(lastIndex: number) {\n\t\treturn this.getByIndex(lastIndex > 0 ? lastIndex - 1 : this.list.length - 1);\n\t}\n\n\tprivate getNext(lastIndex: number) {\n\t\treturn this.getByIndex(lastIndex === this.list.length - 1 ? 0 : lastIndex + 1);\n\t}\n\n\tgetFirst() {\n\t\treturn this.getByIndex(0);\n\t}\n\n\tgetLast() {\n\t\treturn this.getByOrder(Directions.prev, 0);\n\t}\n\n\tgetByIndex(index: number) {\n\t\tconst item = this.list[index];\n\n\t\tif (!item) {\n\t\t\tthrow new Error(`Transition index ${index} out of range`);\n\t\t}\n\n\t\treturn {\n\t\t\tindex,\n\t\t\tcomponent: item.component,\n\t\t\toptions: JSON.parse(JSON.stringify(item.options)),\n\t\t} as TransitionIndex;\n\t}\n\n\tgetByOrder(direction: Direction, lastIndex: number) {\n\t\treturn {\n\t\t\tprev: () => this.getPrev(lastIndex),\n\t\t\tnext: () => this.getNext(lastIndex),\n\t\t}[direction]();\n\t}\n\n\tupdate(transitions: (Component | TransitionWithOptions)[]) {\n\t\tthis.list.splice(0);\n\n\t\tconst transitionsWithOptions = TransitionsMapper.withOptions(transitions);\n\n\t\tthis.list.push(...transitionsWithOptions);\n\t}\n}\n\n\n===== FILE: src/repositories/Transitions/TransitionsMapper.test.ts =====\nimport TransitionsMapper from './TransitionsMapper';\nimport { Fade, Kenburn, Swipe, Slide, type TransitionWithOptions } from '../../transitions';\n\ndescribe('repositories: TransitionsMapper', () => {\n\tit('turns all the transitions array as transitions with options', () => {\n\t\tconst transitions = [\n\t\t\tFade,\n\t\t\t{\n\t\t\t\tcomponent: Kenburn,\n\t\t\t\toptions: { totalDuration: 1600 },\n\t\t\t} as TransitionWithOptions,\n\t\t\tSwipe,\n\t\t\t{\n\t\t\t\tcomponent: Slide,\n\t\t\t\toptions: { totalDuration: 4600 },\n\t\t\t} as TransitionWithOptions,\n\t\t];\n\n\t\tconst transitionsWithOptions = TransitionsMapper.withOptions(transitions);\n\n\t\texpect(transitionsWithOptions[0]!.component).toBe(Fade);\n\t\texpect(transitionsWithOptions[1]!.component).toBe(Kenburn);\n\t\t// @ts-expect-error:next-line\n\t\texpect(transitionsWithOptions[1]!.options.totalDuration).toBe(1600);\n\t\texpect(transitionsWithOptions[2]!.component).toBe(Swipe);\n\t\texpect(transitionsWithOptions[3]!.component).toBe(Slide);\n\t\t// @ts-expect-error:next-line\n\t\texpect(transitionsWithOptions[3]!.options.totalDuration).toBe(4600);\n\t});\n});\n\n\n===== FILE: src/repositories/Transitions/TransitionsMapper.ts =====\nimport type { Component } from 'vue';\nimport type { TransitionComponent, TransitionWithOptions } from '../../transitions';\n\nexport default class TransitionsMapper {\n\tstatic withOptions(transitions: (Component | TransitionWithOptions)[]) {\n\t\treturn transitions.map((transition) => {\n\t\t\tlet component = transition;\n\t\t\tlet options = {};\n\n\t\t\tif ('component' in transition) {\n\t\t\t\tcomponent = transition.component as TransitionComponent;\n\n\t\t\t\tif ('options' in transition) {\n\t\t\t\t\toptions = transition.options;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn { component, options } as TransitionWithOptions;\n\t\t});\n\t}\n}\n\n\n===== FILE: src/repositories/Transitions/types.ts =====\nimport type { Component } from 'vue';\nimport type { Direction } from '../../controllers/Player';\n\nexport interface TransitionIndex {\n\tindex: number;\n\tcomponent: Component;\n\toptions: {\n\t\tdirection?: Direction;\n\t};\n}\n\n\n===== FILE: src/repositories/index.ts =====\nexport { default as Resources } from './Resources/Resources';\nexport { default as Transitions } from './Transitions/Transitions';\n\nexport type { ResourceIndex } from './Resources/types';\nexport type { TransitionIndex } from './Transitions/types';\n\n\n===== FILE: src/resources/Img/Img.test.ts =====\nimport { FluxImage } from '../../components';\nimport ResizeTypes from '../ResizeTypes';\nimport Statuses from '../Statuses';\nimport type { DisplayParameter, ResizeType, TransitionParameter } from '../types';\nimport Img from './Img';\n\ndescribe('resources: Img', () => {\n\tlet img,\n\t\tsrc: string,\n\t\tcaption: string,\n\t\tresizeType: ResizeType,\n\t\tbackgroundColor: string,\n\t\tpromise: Promise<void>,\n\t\tresolve: () => void,\n\t\treject: (message: string) => void;\n\n\tbeforeEach(() => {\n\t\tsrc = 'src';\n\t\tcaption = 'caption';\n\t\tresizeType = ResizeTypes.fill;\n\t\tbackgroundColor = '#ccc';\n\t});\n\n\tit('creates the instance properly with default params', () => {\n\t\timg = new Img(src);\n\n\t\texpect(img.src).toBe(src);\n\t\texpect(img.caption).toBe('');\n\t\texpect(img.resizeType).toBe(ResizeTypes.fill);\n\t\texpect(img.backgroundColor).toBeNull();\n\t});\n\n\tit('creates the instance properly with custom params', () => {\n\t\timg = new Img(src, caption, resizeType, backgroundColor);\n\n\t\texpect(img.src).toBe(src);\n\t\texpect(img.caption).toBe(caption);\n\t\texpect(img.resizeType).toBe(resizeType);\n\t\texpect(img.backgroundColor).toBe(backgroundColor);\n\t});\n\n\tit('creates the instance with the required parameters of abstract Resource', () => {\n\t\timg = new Img(src);\n\n\t\texpect(img.display).toStrictEqual({\n\t\t\tcomponent: FluxImage,\n\t\t\tprops: {},\n\t\t} as DisplayParameter);\n\n\t\texpect(img.transition).toStrictEqual({\n\t\t\tcomponent: FluxImage,\n\t\t\tprops: {},\n\t\t} as TransitionParameter);\n\n\t\texpect(img.errorMessage).toBe(`Image ${src} could not be loaded`);\n\t});\n\n\tit('returns a promise and sets it to property loader', () => {\n\t\timg = new Img(src);\n\n\t\tpromise = img.load();\n\t\texpect(promise).toBeTypeOf('object');\n\t\texpect(img.loader).toBe(promise);\n\t});\n\n\tit('changes the status to loading', () => {\n\t\timg = new Img(src);\n\t\timg.load();\n\n\t\texpect(img.status.value).toBe(Statuses.loading);\n\t});\n\n\tit('returns the loader if already request to load', () => {\n\t\timg = new Img(src);\n\t\tpromise = img.load();\n\n\t\texpect(img.load()).toBe(promise);\n\t});\n\n\tit.todo('calls onLoad when load success');\n\n\tit('sets reals size on load', () => {\n\t\tsrc = '/imgs/pixel.png';\n\t\timg = new Img(src);\n\n\t\tconst htmlImage = new Image();\n\t\thtmlImage.width = 640;\n\t\thtmlImage.height = 480;\n\n\t\tresolve = vi.fn();\n\t\timg.onLoad(htmlImage, resolve);\n\n\t\texpect(img.realSize.toValue()).toStrictEqual({ width: 640, height: 480 });\n\t});\n\n\tit('sets status loaded on load', () => {\n\t\tsrc = '/imgs/pixel.png';\n\t\timg = new Img(src);\n\n\t\tconst htmlImage = new Image();\n\t\thtmlImage.width = 640;\n\t\thtmlImage.height = 480;\n\n\t\tresolve = vi.fn();\n\t\timg.onLoad(htmlImage, resolve);\n\n\t\texpect(img.status.value).toBe(Statuses.loaded);\n\t});\n\n\tit('calls promise resolve on load', () => {\n\t\tsrc = '/imgs/pixel.png';\n\t\timg = new Img(src);\n\n\t\tconst htmlImage = new Image();\n\t\thtmlImage.width = 640;\n\t\thtmlImage.height = 480;\n\n\t\tresolve = vi.fn();\n\t\timg.onLoad(htmlImage, resolve);\n\n\t\texpect(resolve).toHaveBeenCalledOnce();\n\t});\n\n\tit.todo('calls onError when load fails');\n\n\tit('sets the status to error on error', () => {\n\t\timg = new Img(src);\n\n\t\treject = vi.fn();\n\t\timg.onError(reject);\n\n\t\texpect(img.status.value).toBe(Statuses.error);\n\t});\n\n\tit('performs promise reject on error', () => {\n\t\timg = new Img(src);\n\n\t\treject = vi.fn();\n\t\timg.onError(reject);\n\n\t\texpect(reject).toHaveBeenCalledWith(img.errorMessage);\n\t});\n});\n\n\n===== FILE: src/resources/Img/Img.ts =====\nimport { FluxImage } from '../../components';\nimport { Resource, Statuses, ResizeTypes } from '../';\nimport { Size } from '../../shared';\nimport type { DisplayParameter, ResizeType, TransitionParameter } from '../types';\n\nexport default class Img extends Resource {\n\tconstructor(\n\t\tsrc: string,\n\t\tcaption: string = '',\n\t\tresizeType: ResizeType = ResizeTypes.fill,\n\t\tbackgroundColor: null | string = null,\n\t) {\n\t\tconst display: DisplayParameter = {\n\t\t\tcomponent: FluxImage,\n\t\t\tprops: {},\n\t\t};\n\n\t\tconst transition: TransitionParameter = {\n\t\t\tcomponent: FluxImage,\n\t\t\tprops: {},\n\t\t};\n\n\t\tconst errorMessage = `Image ${src} could not be loaded`;\n\n\t\tsuper(src, caption, resizeType, backgroundColor, display, transition, errorMessage);\n\t}\n\n\tload() {\n\t\tif (this.loader !== null) {\n\t\t\treturn this.loader;\n\t\t}\n\n\t\tthis.loader = new Promise<void>((resolve, reject) => {\n\t\t\tthis.status.value = Statuses.loading;\n\n\t\t\tconst img = new Image();\n\n\t\t\timg.onload = () => this.onLoad(img, resolve);\n\t\t\timg.onerror = () => this.onError(reject);\n\n\t\t\timg.src = this.src;\n\t\t});\n\n\t\treturn this.loader;\n\t}\n\n\tonLoad(img: HTMLImageElement, resolve: () => void) {\n\t\tthis.realSize = new Size({\n\t\t\twidth: img.naturalWidth || img.width,\n\t\t\theight: img.naturalHeight || img.height,\n\t\t});\n\n\t\tthis.status.value = Statuses.loaded;\n\n\t\tresolve();\n\t}\n\n\tonError(reject: (message: string) => void) {\n\t\tthis.status.value = Statuses.error;\n\n\t\treject(this.errorMessage);\n\t}\n}\n\n\n===== FILE: src/resources/Img/__mocks__/Img.ts =====\nimport { vi } from 'vitest';\nimport { ResizeTypes, Statuses, Resource } from '../../';\nimport { FluxImage } from '../../../components';\n\nexport default class Img extends Resource {\n\tconstructor() {\n\t\tsuper(\n\t\t\t'',\n\t\t\t'',\n\t\t\tResizeTypes.fill,\n\t\t\tnull,\n\t\t\t{ component: FluxImage, props: {} },\n\t\t\t{ component: FluxImage, props: {} },\n\t\t\t''\n\t\t);\n\t}\n\n\tload = vi.fn().mockImplementation(() => {\n\t\treturn new Promise<void>((resolve) => {\n\t\t\tthis.status.value = Statuses.loading;\n\t\t\tthis.onLoad(null, resolve);\n\t\t});\n\t});\n\n\tonLoad = vi\n\t\t.fn()\n\t\t// eslint-disable-next-line @typescript-eslint/no-unused-vars\n\t\t.mockImplementation((_el: unknown, resolve: () => void) => {\n\t\t\tthis.status.value = Statuses.loaded;\n\t\t\tresolve();\n\t\t});\n\n\tonError = vi\n\t\t.fn()\n\t\t// eslint-disable-next-line @typescript-eslint/no-unused-vars\n\t\t.mockImplementation((_reject: (message: string) => void) => {});\n}\n\n\n===== FILE: src/resources/ResizeTypes.ts =====\nexport enum ResizeTypes {\n\tfill = 'fill',\n\tfit = 'fit',\n}\n\nexport default ResizeTypes;\n\n\n===== FILE: src/resources/Resource.ts =====\nimport { computed, ref, type Ref } from 'vue';\nimport { Size, Position, ResizeCalculator } from '../shared';\nimport type { DisplayParameter, ResizedProps, ResizeType, TransitionParameter } from './types';\nimport { Statuses, ResizeTypes } from './';\n\nexport default abstract class Resource {\n\tsrc: string;\n\tloader: Promise<void> | null = null;\n\terrorMessage: string;\n\tstatus: Ref<Statuses> = ref(Statuses.notLoaded);\n\n\trealSize: Size = new Size();\n\tdisplaySize: Size = new Size();\n\tcaption: string = '';\n\tresizeType: ResizeType;\n\tbackgroundColor: null | string = null;\n\tdisplay: DisplayParameter;\n\ttransition: TransitionParameter;\n\n\tconstructor(\n\t\tsrc: string,\n\t\tcaption: string,\n\t\tresizeType: ResizeType = ResizeTypes.fill,\n\t\tbackgroundColor: null | string = null,\n\t\tdisplay: DisplayParameter,\n\t\ttransition: TransitionParameter,\n\t\terrorMessage: string,\n\t) {\n\t\tthis.src = src;\n\t\tthis.caption = caption;\n\t\tthis.resizeType = resizeType;\n\t\tthis.backgroundColor = backgroundColor;\n\t\tthis.display = display;\n\t\tthis.transition = transition;\n\t\tthis.errorMessage = errorMessage;\n\t}\n\n\tisLoading = () => this.status.value === Statuses.loading;\n\n\tisLoaded = () => this.status.value === Statuses.loaded;\n\n\tisError = () => this.status.value === Statuses.error;\n\n\tabstract load(): Promise<void>;\n\n\tabstract onLoad(el: unknown, resolve: () => void): void;\n\n\tabstract onError(reject: (message: string) => void): void;\n\n\tcalcResizeProps(displaySize: Size) {\n\t\tif ([displaySize.isValid(), this.realSize.isValid()].includes(false)) {\n\t\t\treturn {};\n\t\t}\n\n\t\tconst resCalc = new ResizeCalculator(this.realSize);\n\t\tconst { size, position } = resCalc.resizeTo(displaySize, this.resizeType);\n\n\t\treturn {\n\t\t\t...size.toValue(),\n\t\t\t...position.toValue(),\n\t\t};\n\t}\n\n\tresizeProps = computed<{\n\t\ttop?: number;\n\t\tleft?: number;\n\t\twidth?: number;\n\t\theight?: number;\n\t}>(() => this.calcResizeProps(this.displaySize));\n\n\tgetResizeProps(size: Size, offset?: Position) {\n\t\tconst resizedProps: ResizedProps = {\n\t\t\twidth: 0,\n\t\t\theight: 0,\n\t\t\ttop: 0,\n\t\t\tleft: 0,\n\t\t};\n\n\t\tif (!this.displaySize.isValid()) {\n\t\t\tthis.displaySize.update(size.toValue());\n\t\t}\n\n\t\tObject.assign(\n\t\t\tresizedProps,\n\t\t\tsize.equals(this.displaySize) ? this.resizeProps.value : this.calcResizeProps(size),\n\t\t);\n\n\t\tif (offset !== undefined) {\n\t\t\tresizedProps.top -= offset.top.value || 0;\n\t\t\tresizedProps.left -= offset.left.value || 0;\n\t\t}\n\n\t\treturn resizedProps;\n\t}\n}\n\n\n===== FILE: src/resources/Statuses.ts =====\nexport enum Statuses {\n\tnotLoaded = 'notLoaded',\n\tloading = 'loading',\n\tloaded = 'loaded',\n\terror = 'error',\n}\n\nexport default Statuses;\n\n\n===== FILE: src/resources/__test__/ResourceFactory.ts =====\nimport { Img } from '../';\n\nexport default class ResourceFactory {\n\tstatic create(amount: number) {\n\t\treturn new Array(amount).fill(new Img(''));\n\t}\n}\n\n\n===== FILE: src/resources/index.ts =====\nexport { default as Resource } from './Resource';\nexport { default as Img } from './Img/Img';\nexport { default as Statuses } from './Statuses';\nexport { default as ResizeTypes } from './ResizeTypes';\n\nexport type * from './types';\n\n\n===== FILE: src/resources/types.ts =====\nimport type { Component } from 'vue';\nimport { Resource } from '.';\nimport ResizeTypes from './ResizeTypes';\n\nexport type ResizeType = keyof typeof ResizeTypes;\n\nexport interface ResizedProps {\n\twidth: number;\n\theight: number;\n\ttop: number;\n\tleft: number;\n}\n\nexport interface DisplayParameter {\n\tcomponent: Component;\n\tprops: object;\n}\n\nexport interface TransitionParameter {\n\tcomponent: Component;\n\tprops: object;\n}\n\nexport interface ResourceWithOptions {\n\tresource: Resource;\n\toptions: {\n\t\tdelay?: number;\n\t\tstop?: boolean;\n\t};\n}\n\n\n===== FILE: src/shared/Maths/Maths.test.ts =====\nimport * as Maths from './Maths';\n\ndescribe('shared: Maths', () => {\n\tit('calculates the diagonal', () => {\n\t\tconst size = {\n\t\t\twidth: 640,\n\t\t\theight: 360,\n\t\t};\n\n\t\texpect(Maths.diag(size)).toBe(735);\n\t});\n\n\tit('calculates the aspect ratio', () => {\n\t\tconst size = {\n\t\t\twidth: 640,\n\t\t\theight: 320,\n\t\t};\n\n\t\texpect(Maths.aspectRatio(size)).toBe(2);\n\t});\n});\n\n\n===== FILE: src/shared/Maths/Maths.ts =====\nexport const diag = ({ width, height }: { width: number; height: number }) =>\n\tMath.ceil(Math.sqrt(width * width + height * height));\n\nexport const aspectRatio = ({\n\twidth,\n\theight,\n}: {\n\twidth: number;\n\theight: number;\n}) => width / height;\n\n\n===== FILE: src/shared/Position/Position.test.ts =====\nimport Position from './Position';\n\ndescribe('shared: Position', () => {\n\tlet pos: Position;\n\tlet coords: object;\n\n\tit('initializes values to null without parameters', () => {\n\t\tpos = new Position();\n\n\t\texpect(pos.top.value).toBeNull();\n\t\texpect(pos.left.value).toBeNull();\n\t});\n\n\tit('sets param values', () => {\n\t\tpos = new Position({ top: 100 });\n\t\texpect(pos.top.value).toBe(100);\n\n\t\tpos = new Position({ left: 100 });\n\t\texpect(pos.left.value).toBe(100);\n\n\t\tpos = new Position({\n\t\t\ttop: 100,\n\t\t\tleft: 200,\n\t\t});\n\t\texpect(pos.top.value).toBe(100);\n\t\texpect(pos.left.value).toBe(200);\n\t});\n\n\tit('reset values', () => {\n\t\tpos = new Position({\n\t\t\ttop: 100,\n\t\t\tleft: 200,\n\t\t});\n\n\t\tpos.reset();\n\n\t\texpect(pos.top.value).toBeNull();\n\t\texpect(pos.left.value).toBeNull();\n\t});\n\n\tit('is invalid if top or left is null', () => {\n\t\tpos = new Position({ top: 100 });\n\t\texpect(pos.isValid()).toBeFalsy();\n\n\t\tpos = new Position({ left: 100 });\n\t\texpect(pos.isValid()).toBeFalsy();\n\t});\n\n\tit('is valid when top and left have values', () => {\n\t\tpos = new Position({\n\t\t\ttop: 100,\n\t\t\tleft: 200,\n\t\t});\n\n\t\texpect(pos.isValid()).toBeTruthy();\n\t});\n\n\tit('updates the values', () => {\n\t\tpos = new Position({\n\t\t\ttop: 100,\n\t\t\tleft: 200,\n\t\t});\n\n\t\tpos.update({\n\t\t\ttop: 50,\n\t\t});\n\n\t\texpect(pos.top.value).toBe(50);\n\t\texpect(pos.left.value).toBeNull();\n\n\t\tpos.update({\n\t\t\tleft: 100,\n\t\t});\n\n\t\texpect(pos.top.value).toBeNull();\n\t\texpect(pos.left.value).toBe(100);\n\n\t\tpos.update({\n\t\t\ttop: 200,\n\t\t\tleft: 400,\n\t\t});\n\n\t\texpect(pos.top.value).toBe(200);\n\t\texpect(pos.left.value).toBe(400);\n\t});\n\n\tit('returns the values as plain object', () => {\n\t\tcoords = {\n\t\t\ttop: 100,\n\t\t\tleft: 200,\n\t\t};\n\n\t\tpos = new Position(coords);\n\t\texpect(pos.toValue()).toStrictEqual(coords);\n\n\t\tpos = new Position();\n\t\texpect(pos.toValue()).toStrictEqual({\n\t\t\ttop: undefined,\n\t\t\tleft: undefined,\n\t\t});\n\t});\n\n\tit('throws exception when trying to get the values with px suffix', () => {\n\t\tpos = new Position();\n\t\texpect(() => pos.toPx()).toThrow('Invalid position in pixels');\n\t});\n\n\tit('returns the values with px suffix', () => {\n\t\tcoords = {\n\t\t\ttop: 100,\n\t\t\tleft: 200,\n\t\t};\n\n\t\tpos = new Position(coords);\n\n\t\texpect(pos.toPx()).toStrictEqual({\n\t\t\ttop: coords['top' as keyof object] + 'px',\n\t\t\tleft: coords['left' as keyof object] + 'px',\n\t\t});\n\t});\n});\n\n\n===== FILE: src/shared/Position/Position.ts =====\nimport { ref, type Ref } from 'vue';\n\nexport default class Position {\n\ttop: Ref<null | number> = ref(null);\n\tleft: Ref<null | number> = ref(null);\n\n\tconstructor(\n\t\t{ top = null, left = null }: { top?: null | number; left?: null | number } = {\n\t\t\ttop: null,\n\t\t\tleft: null,\n\t\t},\n\t) {\n\t\tthis.update({ top, left });\n\t}\n\n\treset() {\n\t\tthis.top.value = null;\n\t\tthis.left.value = null;\n\t}\n\n\tisValid() {\n\t\treturn ![this.top.value, this.left.value].includes(null);\n\t}\n\n\tupdate({ top, left }: { top?: null | number; left?: null | number }) {\n\t\tthis.top.value = top ?? null;\n\t\tthis.left.value = left ?? null;\n\t}\n\n\ttoValue() {\n\t\tconst rawPosition: {\n\t\t\ttop?: number;\n\t\t\tleft?: number;\n\t\t} = {\n\t\t\ttop: undefined,\n\t\t\tleft: undefined,\n\t\t};\n\n\t\tif (this.top.value !== null) {\n\t\t\trawPosition.top = this.top.value;\n\t\t}\n\n\t\tif (this.left.value !== null) {\n\t\t\trawPosition.left = this.left.value;\n\t\t}\n\n\t\treturn rawPosition;\n\t}\n\n\ttoPx() {\n\t\tif (!this.isValid()) {\n\t\t\tthrow new RangeError('Invalid position in pixels');\n\t\t}\n\n\t\treturn {\n\t\t\ttop: this.top.value!.toString() + 'px',\n\t\t\tleft: this.left.value!.toString() + 'px',\n\t\t};\n\t}\n}\n\n\n===== FILE: src/shared/ResizeCalculator/ResizeCalculator.test.ts =====\nimport { Size } from '../';\nimport { ResizeTypes } from '../../resources';\nimport ResizeCalculator, { Orientations } from './ResizeCalculator';\n\ndescribe('shared: ResizeCalculator', () => {\n\tlet calc: ResizeCalculator;\n\tlet realSize: Size;\n\tlet newSize: Size;\n\n\tbeforeEach(() => {\n\t\trealSize = new Size();\n\t\tnewSize = new Size();\n\t});\n\n\tit('if the size is invalid throws error', () => {\n\t\tvi.spyOn(realSize, 'isValid').mockImplementation(() => false);\n\n\t\texpect(() => {\n\t\t\tcalc = new ResizeCalculator(realSize);\n\t\t}).toThrow('Invalid real size');\n\n\t\texpect(realSize.isValid).toHaveBeenCalledWith();\n\t});\n\n\tit('if the size is valid when creating the calculator', () => {\n\t\tvi.spyOn(realSize, 'isValid').mockImplementation(() => true);\n\n\t\texpect(() => {\n\t\t\tcalc = new ResizeCalculator(realSize);\n\t\t}).not.toThrow();\n\n\t\texpect(realSize.isValid).toHaveBeenCalledWith();\n\t});\n\n\tit('detects the orientation', () => {\n\t\trealSize.update({\n\t\t\twidth: 640,\n\t\t\theight: 360,\n\t\t});\n\n\t\tcalc = new ResizeCalculator(realSize);\n\n\t\texpect(calc.realOrientation).toBe(Orientations.landscape);\n\n\t\trealSize.update({\n\t\t\twidth: 360,\n\t\t\theight: 640,\n\t\t});\n\n\t\tcalc = new ResizeCalculator(realSize);\n\n\t\texpect(calc.realOrientation).toBe(Orientations.portrait);\n\t});\n\n\tit('if the new size is valid', () => {\n\t\tvi.spyOn(newSize, 'isValid').mockImplementation(() => false);\n\n\t\trealSize.update({\n\t\t\twidth: 640,\n\t\t\theight: 360,\n\t\t});\n\n\t\tcalc = new ResizeCalculator(realSize);\n\n\t\texpect(() => {\n\t\t\tcalc.resizeTo(newSize, ResizeTypes.fill);\n\t\t}).toThrow('Invalid size to resize');\n\n\t\texpect(newSize.isValid).toHaveBeenCalledWith();\n\t});\n\n\tit('new size L real size L and newAspectRatio >= realAspectRatio and type fill', () => {\n\t\tnewSize.update({\n\t\t\twidth: 280,\n\t\t\theight: 140,\n\t\t});\n\n\t\trealSize.update({\n\t\t\twidth: 640,\n\t\t\theight: 360,\n\t\t});\n\n\t\tcalc = new ResizeCalculator(realSize);\n\n\t\tconst { size: adaptedSize, position: adaptedPosition } = calc.resizeTo(\n\t\t\tnewSize,\n\t\t\tResizeTypes.fill\n\t\t);\n\n\t\texpect(adaptedSize.toValue()).toStrictEqual({\n\t\t\twidth: 280,\n\t\t\theight: 157.5,\n\t\t});\n\t\texpect(adaptedPosition.toValue()).toStrictEqual({ top: -8.75, left: 0 });\n\t});\n\n\tit('new size L real size L and newAspectRatio < realAspectRatio and type fill', () => {\n\t\tnewSize.update({\n\t\t\twidth: 280,\n\t\t\theight: 180,\n\t\t});\n\n\t\trealSize.update({\n\t\t\twidth: 280,\n\t\t\theight: 140,\n\t\t});\n\n\t\tcalc = new ResizeCalculator(realSize);\n\n\t\tconst { size: adaptedSize, position: adaptedPosition } = calc.resizeTo(\n\t\t\tnewSize,\n\t\t\tResizeTypes.fit\n\t\t);\n\n\t\texpect(adaptedSize.toValue()).toStrictEqual({\n\t\t\twidth: 280,\n\t\t\theight: 140,\n\t\t});\n\t\texpect(adaptedPosition.toValue()).toStrictEqual({ top: 20, left: 0 });\n\t});\n\n\tit('new size L real size L and newAspectRatio >= realAspectRatio and type fit', () => {\n\t\tnewSize.update({\n\t\t\twidth: 280,\n\t\t\theight: 140,\n\t\t});\n\n\t\trealSize.update({\n\t\t\twidth: 280,\n\t\t\theight: 200,\n\t\t});\n\n\t\tcalc = new ResizeCalculator(realSize);\n\n\t\tconst { size: adaptedSize, position: adaptedPosition } = calc.resizeTo(\n\t\t\tnewSize,\n\t\t\tResizeTypes.fit\n\t\t);\n\n\t\texpect(adaptedSize.toValue()).toStrictEqual({ width: 196, height: 140 });\n\t\texpect(adaptedPosition.toValue()).toStrictEqual({ top: 0, left: 42 });\n\t});\n\n\tit('new size L real size L and newAspectRatio < realAspectRatio and type fit', () => {\n\t\tnewSize.update({\n\t\t\twidth: 280,\n\t\t\theight: 180,\n\t\t});\n\n\t\trealSize.update({\n\t\t\twidth: 280,\n\t\t\theight: 140,\n\t\t});\n\n\t\tcalc = new ResizeCalculator(realSize);\n\n\t\tconst { size: adaptedSize, position: adaptedPosition } = calc.resizeTo(\n\t\t\tnewSize,\n\t\t\tResizeTypes.fit\n\t\t);\n\n\t\texpect(adaptedSize.toValue()).toStrictEqual({\n\t\t\twidth: 280,\n\t\t\theight: 140,\n\t\t});\n\t\texpect(adaptedPosition.toValue()).toStrictEqual({ top: 20, left: 0 });\n\t});\n\n\tit('new size L real size P and type fill', () => {\n\t\tnewSize.update({\n\t\t\twidth: 280,\n\t\t\theight: 140,\n\t\t});\n\n\t\trealSize.update({\n\t\t\twidth: 140,\n\t\t\theight: 280,\n\t\t});\n\n\t\tcalc = new ResizeCalculator(realSize);\n\n\t\tconst { size: adaptedSize, position: adaptedPosition } = calc.resizeTo(\n\t\t\tnewSize,\n\t\t\tResizeTypes.fill\n\t\t);\n\n\t\texpect(adaptedSize.toValue()).toStrictEqual({ width: 280, height: 560 });\n\t\texpect(adaptedPosition.toValue()).toStrictEqual({ top: -210, left: 0 });\n\t});\n\n\tit('new size L real size P and type fit', () => {\n\t\tnewSize.update({\n\t\t\twidth: 280,\n\t\t\theight: 140,\n\t\t});\n\n\t\trealSize.update({\n\t\t\twidth: 140,\n\t\t\theight: 280,\n\t\t});\n\n\t\tcalc = new ResizeCalculator(realSize);\n\n\t\tconst { size: adaptedSize, position: adaptedPosition } = calc.resizeTo(\n\t\t\tnewSize,\n\t\t\tResizeTypes.fit\n\t\t);\n\n\t\texpect(adaptedSize.toValue()).toStrictEqual({\n\t\t\twidth: 70,\n\t\t\theight: 140,\n\t\t});\n\t\texpect(adaptedPosition.toValue()).toStrictEqual({ top: 0, left: 105 });\n\t});\n\n\tit('new size P real size L and type fill', () => {\n\t\tnewSize.update({\n\t\t\twidth: 140,\n\t\t\theight: 280,\n\t\t});\n\n\t\trealSize.update({\n\t\t\twidth: 280,\n\t\t\theight: 140,\n\t\t});\n\n\t\tcalc = new ResizeCalculator(realSize);\n\n\t\tconst { size: adaptedSize, position: adaptedPosition } = calc.resizeTo(\n\t\t\tnewSize,\n\t\t\tResizeTypes.fill\n\t\t);\n\n\t\texpect(adaptedSize.toValue()).toStrictEqual({ width: 560, height: 280 });\n\t\texpect(adaptedPosition.toValue()).toStrictEqual({ top: 0, left: -210 });\n\t});\n\n\tit('new size P real size L and type fit', () => {\n\t\tnewSize.update({\n\t\t\twidth: 140,\n\t\t\theight: 280,\n\t\t});\n\n\t\trealSize.update({\n\t\t\twidth: 280,\n\t\t\theight: 140,\n\t\t});\n\n\t\tcalc = new ResizeCalculator(realSize);\n\n\t\tconst { size: adaptedSize, position: adaptedPosition } = calc.resizeTo(\n\t\t\tnewSize,\n\t\t\tResizeTypes.fit\n\t\t);\n\n\t\texpect(adaptedSize.toValue()).toStrictEqual({\n\t\t\twidth: 140,\n\t\t\theight: 70,\n\t\t});\n\t\texpect(adaptedPosition.toValue()).toStrictEqual({\n\t\t\ttop: 105,\n\t\t\tleft: 0,\n\t\t});\n\t});\n\n\tit('new size P real size P and newAspectRatio >= realAspectRatio and type fill', () => {\n\t\tnewSize.update({\n\t\t\twidth: 140,\n\t\t\theight: 280,\n\t\t});\n\n\t\trealSize.update({\n\t\t\twidth: 180,\n\t\t\theight: 280,\n\t\t});\n\n\t\tcalc = new ResizeCalculator(realSize);\n\n\t\tconst { size: adaptedSize, position: adaptedPosition } = calc.resizeTo(\n\t\t\tnewSize,\n\t\t\tResizeTypes.fill\n\t\t);\n\n\t\texpect(adaptedSize.toValue()).toStrictEqual({\n\t\t\twidth: 180,\n\t\t\theight: 280,\n\t\t});\n\t\texpect(adaptedPosition.toValue()).toStrictEqual({ top: 0, left: -20 });\n\t});\n\n\tit('new size P real size P and newAspectRatio < realAspectRatio and type fill', () => {\n\t\tnewSize.update({\n\t\t\twidth: 180,\n\t\t\theight: 280,\n\t\t});\n\n\t\trealSize.update({\n\t\t\twidth: 140,\n\t\t\theight: 280,\n\t\t});\n\n\t\tcalc = new ResizeCalculator(realSize);\n\n\t\tconst { size: adaptedSize, position: adaptedPosition } = calc.resizeTo(\n\t\t\tnewSize,\n\t\t\tResizeTypes.fill\n\t\t);\n\n\t\texpect(adaptedSize.toValue()).toStrictEqual({\n\t\t\twidth: 180,\n\t\t\theight: 360,\n\t\t});\n\t\texpect(adaptedPosition.toValue()).toStrictEqual({ top: -40, left: 0 });\n\t});\n\n\tit('new size P real size P and newAspectRatio >= realAspectRatio and type fit', () => {\n\t\tnewSize.update({\n\t\t\twidth: 140,\n\t\t\theight: 280,\n\t\t});\n\n\t\trealSize.update({\n\t\t\twidth: 200,\n\t\t\theight: 280,\n\t\t});\n\n\t\tcalc = new ResizeCalculator(realSize);\n\n\t\tconst { size: adaptedSize, position: adaptedPosition } = calc.resizeTo(\n\t\t\tnewSize,\n\t\t\tResizeTypes.fit\n\t\t);\n\n\t\texpect(adaptedSize.toValue()).toStrictEqual({ width: 140, height: 196 });\n\t\texpect(adaptedPosition.toValue()).toStrictEqual({ top: 42, left: 0 });\n\t});\n\n\tit('new size P real size P and newAspectRatio < realAspectRatio and type fit', () => {\n\t\tnewSize.update({\n\t\t\twidth: 180,\n\t\t\theight: 280,\n\t\t});\n\n\t\trealSize.update({\n\t\t\twidth: 140,\n\t\t\theight: 280,\n\t\t});\n\n\t\tcalc = new ResizeCalculator(realSize);\n\n\t\tconst { size: adaptedSize, position: adaptedPosition } = calc.resizeTo(\n\t\t\tnewSize,\n\t\t\tResizeTypes.fit\n\t\t);\n\n\t\texpect(adaptedSize.toValue()).toStrictEqual({ width: 140, height: 280 });\n\t\texpect(adaptedPosition.toValue()).toStrictEqual({ top: 0, left: 20 });\n\t});\n});\n\n\n===== FILE: src/shared/ResizeCalculator/ResizeCalculator.ts =====\nimport { type ResizeType, ResizeTypes } from '../../resources';\nimport { Size, Position } from '../';\n\nexport enum Orientations {\n\tlandscape = 'landscape',\n\tportrait = 'portrait',\n}\n\nconst getOrientation = (aspectRatio: number) =>\n\taspectRatio >= 1 ? Orientations.landscape : Orientations.portrait;\n\ntype Orientation = keyof typeof Orientations;\n\nexport default class ResizeCalculator {\n\trealSize: Size;\n\trealAspectRatio: number;\n\trealOrientation: Orientation;\n\n\tconstructor(realSize: Size) {\n\t\tif (realSize.isValid() === false) {\n\t\t\tthrow new RangeError('Invalid real size');\n\t\t}\n\n\t\tthis.realSize = realSize;\n\t\tthis.realAspectRatio = this.realSize.getAspectRatio();\n\t\tthis.realOrientation = getOrientation(this.realAspectRatio);\n\t}\n\n\tpublic resizeTo(resizeSize: Size, resizeType: ResizeType) {\n\t\tif (resizeSize.isValid() === false) {\n\t\t\tthrow new RangeError('Invalid size to resize');\n\t\t}\n\n\t\tconst resizeAspectRatio = resizeSize.getAspectRatio();\n\t\tconst resizeOrientation = getOrientation(resizeAspectRatio);\n\n\t\tconst adaptedSize: Size = this.getAdaptedSize(\n\t\t\tresizeSize,\n\t\t\tresizeAspectRatio,\n\t\t\tresizeOrientation,\n\t\t\tresizeType,\n\t\t);\n\n\t\tconst adaptedPosition: Position = this.getAdaptedPosition(\n\t\t\tresizeSize,\n\t\t\tresizeAspectRatio,\n\t\t\tadaptedSize,\n\t\t\tresizeType,\n\t\t);\n\n\t\treturn {\n\t\t\tsize: adaptedSize,\n\t\t\tposition: adaptedPosition,\n\t\t};\n\t}\n\n\tprivate getAdaptedSize(\n\t\tresizeSize: Size,\n\t\tresizeAspectRatio: number,\n\t\tresizeOrientation: Orientation,\n\t\tresizeType: ResizeType,\n\t) {\n\t\tif (\n\t\t\tresizeOrientation === Orientations.landscape &&\n\t\t\tthis.realOrientation === Orientations.portrait &&\n\t\t\tresizeType === ResizeTypes.fill\n\t\t) {\n\t\t\treturn this.getAdaptedSizeByWith(resizeSize);\n\t\t}\n\n\t\tif (\n\t\t\tresizeOrientation === Orientations.landscape &&\n\t\t\tthis.realOrientation === Orientations.landscape &&\n\t\t\tresizeAspectRatio >= this.realAspectRatio &&\n\t\t\tresizeType === ResizeTypes.fill\n\t\t) {\n\t\t\treturn this.getAdaptedSizeByWith(resizeSize);\n\t\t}\n\n\t\tif (\n\t\t\tresizeOrientation === Orientations.landscape &&\n\t\t\tthis.realOrientation === Orientations.landscape &&\n\t\t\tresizeAspectRatio < this.realAspectRatio &&\n\t\t\tresizeType === ResizeTypes.fit\n\t\t) {\n\t\t\treturn this.getAdaptedSizeByWith(resizeSize);\n\t\t}\n\n\t\tif (\n\t\t\tresizeOrientation === Orientations.portrait &&\n\t\t\tthis.realOrientation === Orientations.landscape &&\n\t\t\tresizeType === ResizeTypes.fit\n\t\t) {\n\t\t\treturn this.getAdaptedSizeByWith(resizeSize);\n\t\t}\n\n\t\tif (\n\t\t\tresizeOrientation === Orientations.portrait &&\n\t\t\tthis.realOrientation === Orientations.portrait &&\n\t\t\tresizeAspectRatio > this.realAspectRatio &&\n\t\t\tresizeType === ResizeTypes.fill\n\t\t) {\n\t\t\treturn this.getAdaptedSizeByWith(resizeSize);\n\t\t}\n\n\t\tif (\n\t\t\tresizeOrientation === Orientations.portrait &&\n\t\t\tthis.realOrientation === Orientations.portrait &&\n\t\t\tresizeAspectRatio <= this.realAspectRatio &&\n\t\t\tresizeType === ResizeTypes.fit\n\t\t) {\n\t\t\treturn this.getAdaptedSizeByWith(resizeSize);\n\t\t}\n\n\t\treturn this.getAdaptedSizeByHeight(resizeSize);\n\t}\n\n\tprivate getAdaptedSizeByWith(resizeSize: Size) {\n\t\treturn new Size({\n\t\t\twidth: resizeSize.width.value,\n\t\t\theight: resizeSize.width.value! / this.realAspectRatio,\n\t\t});\n\t}\n\n\tprivate getAdaptedSizeByHeight(resizeSize: Size) {\n\t\treturn new Size({\n\t\t\twidth: this.realAspectRatio * resizeSize.height.value!,\n\t\t\theight: resizeSize.height.value,\n\t\t});\n\t}\n\n\tprivate getAdaptedPosition(\n\t\tresizeSize: Size,\n\t\tresizeAspectRatio: number,\n\t\tadaptedSize: Size,\n\t\tresizeType: ResizeType,\n\t) {\n\t\tif (this.realAspectRatio <= resizeAspectRatio && resizeType === ResizeTypes.fill) {\n\t\t\treturn this.getAdaptedPositionVertically(resizeSize, adaptedSize);\n\t\t}\n\n\t\tif (this.realAspectRatio > resizeAspectRatio && resizeType === ResizeTypes.fit) {\n\t\t\treturn this.getAdaptedPositionVertically(resizeSize, adaptedSize);\n\t\t}\n\n\t\treturn this.getAdaptedPositionHorizontally(resizeSize, adaptedSize);\n\t}\n\n\tgetAdaptedPositionVertically(resizeSize: Size, adaptedSize: Size) {\n\t\treturn new Position({\n\t\t\ttop: (resizeSize.height.value! - adaptedSize.height.value!) / 2,\n\t\t\tleft: 0,\n\t\t});\n\t}\n\n\tgetAdaptedPositionHorizontally(resizeSize: Size, adaptedSize: Size) {\n\t\treturn new Position({\n\t\t\ttop: 0,\n\t\t\tleft: (resizeSize.width.value! - adaptedSize.width.value!) / 2,\n\t\t});\n\t}\n}\n\n\n===== FILE: src/shared/ResourceLoader/ResourceLoader.test.ts =====\nimport { vi } from 'vitest';\nimport ResourceLoader from './ResourceLoader';\nimport ResourceLoaderFactory from './__test__/ResourceLoaderFactory';\nimport { Statuses } from '../../resources';\n\nvi.mock('../../resources/Img/Img');\n\ndescribe('shared: ResourceLoader', () => {\n\tlet rscLoader: ResourceLoader;\n\n\tbeforeEach(() => {\n\t\tvi.clearAllMocks();\n\t});\n\n\tit('calls onPreloadStart when preload starts', () => {\n\t\trscLoader = ResourceLoaderFactory.create(10, 5);\n\n\t\texpect(rscLoader.onPreloadStart).toHaveBeenCalledOnce();\n\t});\n\n\tit('preloads all resources if num resources less than num to preload', () => {\n\t\trscLoader = ResourceLoaderFactory.create(10, 15);\n\n\t\texpect(rscLoader.toPreload).toBe(10);\n\t});\n\n\tit('start preloading when created', () => {\n\t\trscLoader = ResourceLoaderFactory.create(10, 10);\n\n\t\texpect(rscLoader.onPreloadStart).toHaveBeenCalledOnce();\n\t});\n\n\tit('start preloading the resources', () => {\n\t\trscLoader = ResourceLoaderFactory.create(15, 10);\n\n\t\texpect(\n\t\t\trscLoader.rscs.every((rsc) =>\n\t\t\t\t[Statuses.loading, Statuses.loaded].includes(rsc.resource.status.value),\n\t\t\t),\n\t\t).toBeTruthy();\n\t});\n\n\tit('checks if resources preloaded are less than to preload and preloads the remaining', () => {\n\t\trscLoader = ResourceLoaderFactory.create(15, 6);\n\n\t\trscLoader.counter.success = 4;\n\t\trscLoader.counter.error = 2;\n\t\trscLoader.counter.total = 6;\n\n\t\trscLoader.preloadEnd();\n\n\t\texpect(rscLoader.preLoading).toHaveLength(8);\n\t});\n\n\tit('calls onPreloadEnd when all preloaded', () =>\n\t\tnew Promise<void>((done) => {\n\t\t\trscLoader = ResourceLoaderFactory.create(5, 5, undefined, () => {\n\t\t\t\texpect(rscLoader.onPreloadStart).toHaveBeenCalledOnce();\n\t\t\t\texpect(rscLoader.onPreloadEnd).toHaveBeenCalledWith(expect.any(Array));\n\t\t\t\tdone();\n\t\t\t});\n\t\t}));\n\n\tit('starts lazy loading when preloading finish', () =>\n\t\tnew Promise<void>((done) => {\n\t\t\trscLoader = ResourceLoaderFactory.create(20, 5, undefined, undefined, () => {\n\t\t\t\texpect(rscLoader.onPreloadStart).toHaveBeenCalledOnce();\n\t\t\t\texpect(rscLoader.onPreloadEnd).toHaveBeenCalledOnce();\n\t\t\t\texpect(rscLoader.counter.total).toBe(5);\n\t\t\t\texpect(rscLoader.onLazyLoadStart).toHaveBeenCalledOnce();\n\t\t\t\tdone();\n\t\t\t});\n\t\t}));\n\n\tit('calls onLazyLoadEnd when lazy loading finish', () =>\n\t\tnew Promise<void>((done) => {\n\t\t\trscLoader = ResourceLoaderFactory.create(20, 5, undefined, undefined, undefined, () => {\n\t\t\t\texpect(rscLoader.onPreloadStart).toHaveBeenCalledOnce();\n\t\t\t\texpect(rscLoader.onPreloadEnd).toHaveBeenCalledOnce();\n\t\t\t\texpect(rscLoader.onLazyLoadStart).toHaveBeenCalledOnce();\n\t\t\t\texpect(rscLoader.onLazyLoadEnd).toHaveBeenCalledWith(expect.any(Array));\n\t\t\t\texpect(rscLoader.counter.total).toBe(20);\n\t\t\t\tdone();\n\t\t\t});\n\t\t}));\n\n\tit('does not update display size if cancelled', () => {\n\t\trscLoader = ResourceLoaderFactory.create(10, 5);\n\t\trscLoader.cancel();\n\n\t\tconst rsc = rscLoader.rscs[0];\n\t\tvi.spyOn(rsc!.resource.displaySize, 'update');\n\n\t\trscLoader.loadSuccess(rsc!);\n\n\t\texpect(rsc!.resource.displaySize.update).not.toHaveBeenCalled();\n\t});\n\n\tit('calculates the progress properly', () => {\n\t\trscLoader = ResourceLoaderFactory.create(15, 6);\n\n\t\trscLoader.counter.success = 4;\n\t\trscLoader.counter.error = 2;\n\t\trscLoader.counter.total = 6;\n\n\t\trscLoader.updateProgress();\n\n\t\texpect(rscLoader.progress.value).toBe(67);\n\t});\n});\n\n\n===== FILE: src/shared/ResourceLoader/ResourceLoader.ts =====\nimport { type Ref, ref } from 'vue';\nimport { Size } from '../';\nimport type { ResourceWithOptions } from '../../resources';\n\nexport default class ResourceLoader {\n\trscs: ResourceWithOptions[] = [];\n\tcounter = {\n\t\tsuccess: 0,\n\t\terror: 0,\n\t\ttotal: 0,\n\t};\n\ttoPreload: number;\n\tpreLoading: ResourceWithOptions[] = [];\n\tlazyLoading: ResourceWithOptions[] = [];\n\tprogress: Ref<number> = ref(0);\n\tdisplaySize: Size;\n\tonPreloadStart: () => void;\n\tonPreloadEnd: (loaded: ResourceWithOptions[]) => void;\n\tonLazyLoadStart: () => void;\n\tonLazyLoadEnd: (loaded: ResourceWithOptions[]) => void;\n\tisCancelled: boolean = false;\n\treject: (message: string, rscs: ResourceWithOptions[]) => void;\n\n\tconstructor(\n\t\trscs: ResourceWithOptions[],\n\t\ttoPreload: number,\n\t\tdisplaySize: Size,\n\t\tonPreloadStart: () => void,\n\t\tonPreloadEnd: (loaded: ResourceWithOptions[]) => void,\n\t\tonLazyLoadStart: () => void,\n\t\tonLazyLoadEnd: (loaded: ResourceWithOptions[]) => void,\n\t\treject: (message: string, rscs: ResourceWithOptions[]) => void,\n\t) {\n\t\tthis.rscs = rscs;\n\t\tthis.toPreload = toPreload > rscs.length ? rscs.length : toPreload;\n\t\tthis.displaySize = displaySize;\n\t\tthis.onPreloadStart = onPreloadStart;\n\t\tthis.onPreloadEnd = onPreloadEnd;\n\t\tthis.onLazyLoadStart = onLazyLoadStart;\n\t\tthis.onLazyLoadEnd = onLazyLoadEnd;\n\t\tthis.reject = reject;\n\n\t\tthis.preloadStart();\n\t}\n\n\tpreloadStart() {\n\t\tthis.onPreloadStart();\n\n\t\tconst { counter } = this;\n\n\t\tconst toLoad = this.rscs.slice(\n\t\t\tcounter.total,\n\t\t\tcounter.total + this.toPreload - counter.success,\n\t\t);\n\n\t\tthis.preLoading = this.preLoading.concat(toLoad);\n\n\t\ttoLoad.forEach((rsc) => this.load(rsc));\n\t}\n\n\tpreloadEnd() {\n\t\tconst { counter, toPreload } = this;\n\n\t\tif (counter.success < toPreload && counter.total < this.rscs.length) {\n\t\t\tthis.preloadStart();\n\t\t\treturn;\n\t\t}\n\n\t\tconst preloadedSuccessfully = this.preLoading.filter((rsc) => rsc.resource.isLoaded());\n\n\t\tthis.onPreloadEnd(preloadedSuccessfully);\n\n\t\tthis.preLoading.length = 0;\n\n\t\tif (counter.total < this.rscs.length) {\n\t\t\tthis.lazyLoadStart();\n\t\t}\n\t}\n\n\tlazyLoadStart() {\n\t\tthis.onLazyLoadStart();\n\n\t\tthis.lazyLoading = this.rscs.slice(this.counter.total);\n\n\t\tthis.lazyLoading.forEach((rsc) => this.load(rsc));\n\t}\n\n\tlazyLoadEnd() {\n\t\tconst lazyLoadedSuccessfully = this.lazyLoading.filter((rsc) => rsc.resource.isLoaded());\n\n\t\tthis.onLazyLoadEnd(lazyLoadedSuccessfully);\n\n\t\tthis.lazyLoading.length = 0;\n\t}\n\n\tload(rsc: ResourceWithOptions) {\n\t\trsc.resource\n\t\t\t.load()\n\t\t\t.then(() => {\n\t\t\t\tthis.loadSuccess(rsc);\n\t\t\t})\n\t\t\t.catch((error) => {\n\t\t\t\tthis.loadError(error);\n\t\t\t})\n\t\t\t.finally(() => {\n\t\t\t\tthis.counter.total++;\n\n\t\t\t\tif (this.isCancelled) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (this.preLoading.length !== 0) {\n\t\t\t\t\tthis.updateProgress();\n\t\t\t\t}\n\n\t\t\t\tif (this.counter.total === this.toPreload) {\n\t\t\t\t\tthis.preloadEnd();\n\t\t\t\t} else if (this.counter.total === this.rscs.length) {\n\t\t\t\t\tthis.lazyLoadEnd();\n\t\t\t\t}\n\t\t\t});\n\t}\n\n\tloadSuccess(rsc: ResourceWithOptions) {\n\t\tthis.counter.success++;\n\n\t\tif (this.isCancelled) {\n\t\t\treturn;\n\t\t}\n\n\t\trsc.resource.displaySize.update(this.displaySize.toValue());\n\t}\n\n\tloadError(error: string) {\n\t\tthis.counter.error++;\n\n\t\tif (this.isCancelled) {\n\t\t\treturn;\n\t\t}\n\n\t\tconsole.error(error);\n\t}\n\n\tupdateProgress() {\n\t\tthis.progress.value = Math.ceil((this.counter.success * 100) / this.toPreload);\n\t}\n\n\thasFinished() {\n\t\treturn this.counter.total === this.rscs.length;\n\t}\n\n\tcancel() {\n\t\tthis.isCancelled = true;\n\t\tthis.reject('Resources loading cancelled', this.rscs);\n\t}\n}\n\n\n===== FILE: src/shared/ResourceLoader/__mocks__/ResourceLoader.ts =====\nimport { Size } from '../../';\nimport type { ResourceWithOptions } from '../../../resources';\n\nexport default class ResourceLoader {\n\trscs: ResourceWithOptions[] = [];\n\tcounter = {\n\t\tsuccess: 0,\n\t\terror: 0,\n\t\ttotal: 0,\n\t};\n\ttoPreload: number;\n\tpreLoading: ResourceWithOptions[] = [];\n\tlazyLoading: ResourceWithOptions[] = [];\n\tdisplaySize: Size;\n\tonPreloadStart: () => void;\n\tonPreloadEnd: (loaded: ResourceWithOptions[]) => void;\n\tonLazyLoadStart: () => void;\n\tonLazyLoadEnd: (loaded: ResourceWithOptions[]) => void;\n\treject: (message: string, rscs: ResourceWithOptions[]) => void;\n\n\tconstructor(\n\t\trscs: ResourceWithOptions[],\n\t\ttoPreload: number,\n\t\tdisplaySize: Size,\n\t\tonPreloadStart: () => void,\n\t\tonPreloadEnd: (loaded: ResourceWithOptions[]) => void,\n\t\tonLazyLoadStart: () => void,\n\t\tonLazyLoadEnd: (loaded: ResourceWithOptions[]) => void,\n\t\treject: (message: string, rscs: ResourceWithOptions[]) => void,\n\t) {\n\t\tthis.rscs = rscs;\n\t\tthis.toPreload = toPreload > rscs.length ? rscs.length : toPreload;\n\t\tthis.displaySize = displaySize;\n\t\tthis.onPreloadStart = onPreloadStart;\n\t\tthis.onPreloadEnd = onPreloadEnd;\n\t\tthis.onLazyLoadStart = onLazyLoadStart;\n\t\tthis.onLazyLoadEnd = onLazyLoadEnd;\n\t\tthis.reject = reject;\n\n\t\tthis.preloadStart();\n\t}\n\n\tpreloadStart() {\n\t\tthis.onPreloadStart();\n\n\t\tconst { counter } = this;\n\n\t\tconst toLoad = this.rscs.slice(\n\t\t\tcounter.total,\n\t\t\tcounter.total + this.toPreload - counter.success,\n\t\t);\n\n\t\tthis.preLoading = this.preLoading.concat(toLoad);\n\n\t\ttoLoad.forEach((rsc) => this.load(rsc));\n\t}\n\n\tpreloadEnd() {\n\t\tconst { counter, toPreload } = this;\n\n\t\tif (counter.success < toPreload && counter.total < toPreload) {\n\t\t\tthis.preloadStart();\n\t\t\treturn;\n\t\t}\n\n\t\tconst preloadedSuccessfully = this.preLoading.filter((rsc) => rsc.resource.isLoaded());\n\n\t\tthis.onPreloadEnd(preloadedSuccessfully);\n\n\t\tthis.preLoading.length = 0;\n\n\t\tif (counter.total < this.rscs.length) {\n\t\t\tthis.lazyLoadStart();\n\t\t}\n\t}\n\n\tlazyLoadStart() {\n\t\tthis.onLazyLoadStart();\n\n\t\tthis.lazyLoading = this.rscs.slice(this.counter.total);\n\n\t\tthis.lazyLoading.forEach((rsc) => this.load(rsc));\n\t}\n\n\tlazyLoadEnd() {\n\t\tconst lazyLoadedSuccessfully = this.lazyLoading.filter((rsc) => rsc.resource.isLoaded());\n\n\t\tthis.onLazyLoadEnd(lazyLoadedSuccessfully);\n\n\t\tthis.lazyLoading.length = 0;\n\t}\n\n\tload(rsc: ResourceWithOptions) {\n\t\trsc.resource\n\t\t\t.load()\n\t\t\t.then(() => {\n\t\t\t\tthis.loadSuccess();\n\t\t\t})\n\t\t\t.finally(() => {\n\t\t\t\tthis.counter.total++;\n\n\t\t\t\tif (this.counter.total === this.toPreload) {\n\t\t\t\t\tthis.preloadEnd();\n\t\t\t\t} else if (this.counter.total === this.rscs.length) {\n\t\t\t\t\tthis.lazyLoadEnd();\n\t\t\t\t}\n\t\t\t});\n\t}\n\n\tloadSuccess() {\n\t\tthis.counter.success++;\n\t}\n\n\thasFinished() {\n\t\treturn this.counter.total === this.rscs.length;\n\t}\n}\n\n\n===== FILE: src/shared/ResourceLoader/__test__/ResourceLoaderFactory.ts =====\nimport { vi } from 'vitest';\nimport ResourceFactory from '../../../resources/__test__/ResourceFactory';\nimport ResourceLoader from '../ResourceLoader';\nimport Size from '../../Size/Size';\nimport type { ResourceWithOptions } from '../../../resources/types';\n\nexport default class ResourceLoaderFactory {\n\tstatic create(\n\t\tnumResources: number,\n\t\tnumToPreload: number,\n\t\tpreloadStartMock?: () => void,\n\t\tpreloadEndMock?: () => void,\n\t\tlazyLoadStartMock?: () => void,\n\t\tlazyLoadEndMock?: () => void,\n\t) {\n\t\tconst displaySize = new Size({\n\t\t\twidth: 640,\n\t\t\theight: 360,\n\t\t});\n\n\t\tconst onPreloadStart = vi.fn();\n\n\t\tif (preloadStartMock) {\n\t\t\tonPreloadStart.mockImplementation(preloadStartMock);\n\t\t}\n\n\t\tconst onPreloadEnd = vi.fn();\n\n\t\tif (preloadEndMock) {\n\t\t\tonPreloadEnd.mockImplementation(preloadEndMock);\n\t\t}\n\n\t\tconst onLazyLoadStart = vi.fn();\n\n\t\tif (lazyLoadStartMock) {\n\t\t\tonLazyLoadStart.mockImplementation(lazyLoadStartMock);\n\t\t}\n\n\t\tconst onLazyLoadEnd = vi.fn();\n\n\t\tif (lazyLoadEndMock) {\n\t\t\tonLazyLoadEnd.mockImplementation(lazyLoadEndMock);\n\t\t}\n\n\t\tconst reject = vi.fn();\n\n\t\tconst resources = ResourceFactory.create(numResources).map((resource) => {\n\t\t\treturn {\n\t\t\t\tresource: resource,\n\t\t\t\toptions: {},\n\t\t\t} as ResourceWithOptions;\n\t\t});\n\n\t\treturn new ResourceLoader(\n\t\t\tresources,\n\t\t\tnumToPreload,\n\t\t\tdisplaySize,\n\t\t\tonPreloadStart,\n\t\t\tonPreloadEnd,\n\t\t\tonLazyLoadStart,\n\t\t\tonLazyLoadEnd,\n\t\t\treject,\n\t\t);\n\t}\n}\n\n\n===== FILE: src/shared/Size/Size.test.ts =====\nimport Size from './Size';\n\ndescribe('shared: Size', () => {\n\tlet size: Size;\n\tlet params: object;\n\n\tit('initializes values to null without parameters', () => {\n\t\tsize = new Size();\n\n\t\texpect(size.width.value).toBeNull();\n\t\texpect(size.height.value).toBeNull();\n\t});\n\n\tit('sets param values', () => {\n\t\tsize = new Size({ width: 100 });\n\t\texpect(size.width.value).toBe(100);\n\n\t\tsize = new Size({ height: 100 });\n\t\texpect(size.height.value).toBe(100);\n\n\t\tsize = new Size({\n\t\t\twidth: 100,\n\t\t\theight: 200,\n\t\t});\n\t\texpect(size.width.value).toBe(100);\n\t\texpect(size.height.value).toBe(200);\n\t});\n\n\tit('reset values', () => {\n\t\tsize = new Size({\n\t\t\twidth: 100,\n\t\t\theight: 200,\n\t\t});\n\n\t\tsize.reset();\n\n\t\texpect(size.width.value).toBeNull();\n\t\texpect(size.height.value).toBeNull();\n\t});\n\n\tit('is invalid if width or height is null', () => {\n\t\tsize = new Size({ width: 100 });\n\t\texpect(size.isValid()).toBeFalsy();\n\n\t\tsize = new Size({ height: 100 });\n\t\texpect(size.isValid()).toBeFalsy();\n\t});\n\n\tit('is valid when width and height have values', () => {\n\t\tsize = new Size({\n\t\t\twidth: 100,\n\t\t\theight: 200,\n\t\t});\n\n\t\texpect(size.isValid()).toBeTruthy();\n\t});\n\n\tit('updates the values', () => {\n\t\tsize = new Size({\n\t\t\twidth: 100,\n\t\t\theight: 200,\n\t\t});\n\n\t\tsize.update({\n\t\t\twidth: 50,\n\t\t});\n\n\t\texpect(size.width.value).toBe(50);\n\t\texpect(size.height.value).toBeNull();\n\n\t\tsize.update({\n\t\t\theight: 100,\n\t\t});\n\n\t\texpect(size.width.value).toBeNull();\n\t\texpect(size.height.value).toBe(100);\n\n\t\tsize.update({\n\t\t\twidth: 200,\n\t\t\theight: 400,\n\t\t});\n\n\t\texpect(size.width.value).toBe(200);\n\t\texpect(size.height.value).toBe(400);\n\t});\n\n\tit('throws an exception trying to calc aspect ratio when size is invalid', () => {\n\t\tsize = new Size({ width: 100 });\n\n\t\texpect(() => size.getAspectRatio()).toThrow(\n\t\t\t'Could not get aspect ratio due to invalid size'\n\t\t);\n\t});\n\n\tit('gets the aspect ration when size is valid', () => {\n\t\tsize = new Size({\n\t\t\twidth: 100,\n\t\t\theight: 200,\n\t\t});\n\n\t\texpect(size.getAspectRatio()).toBeTypeOf('number');\n\t});\n\n\tit('clones the size', () => {\n\t\tparams = {\n\t\t\twidth: 100,\n\t\t\theight: 200,\n\t\t};\n\n\t\tsize = new Size({\n\t\t\twidth: 100,\n\t\t\theight: 200,\n\t\t});\n\n\t\texpect(size.clone().toValue()).toStrictEqual(params);\n\t});\n\n\tit('returns false when width does not match other size', () => {\n\t\tsize = new Size({\n\t\t\twidth: 100,\n\t\t\theight: 200,\n\t\t});\n\n\t\texpect(\n\t\t\tsize.equals(\n\t\t\t\tnew Size({\n\t\t\t\t\twidth: 50,\n\t\t\t\t\theight: 200,\n\t\t\t\t})\n\t\t\t)\n\t\t).toBeFalsy();\n\t});\n\n\tit('returns false when height does not match other size', () => {\n\t\tsize = new Size({\n\t\t\twidth: 100,\n\t\t\theight: 200,\n\t\t});\n\n\t\texpect(\n\t\t\tsize.equals(\n\t\t\t\tnew Size({\n\t\t\t\t\twidth: 100,\n\t\t\t\t\theight: 50,\n\t\t\t\t})\n\t\t\t)\n\t\t).toBeFalsy();\n\t});\n\n\tit('returns true when size equals another size', () => {\n\t\tparams = {\n\t\t\twidth: 100,\n\t\t\theight: 200,\n\t\t};\n\n\t\tsize = new Size(params);\n\n\t\texpect(size.equals(new Size(params))).toBeTruthy();\n\t});\n\n\tit('returns the values as plain object', () => {\n\t\tparams = {\n\t\t\twidth: 100,\n\t\t\theight: 200,\n\t\t};\n\n\t\tsize = new Size(params);\n\t\texpect(size.toValue()).toStrictEqual(params);\n\n\t\tsize = new Size();\n\t\texpect(size.toValue()).toStrictEqual({});\n\t});\n\n\tit('throws exception when trying to get the values with px suffix', () => {\n\t\tsize = new Size();\n\t\texpect(() => size.toPx()).toThrow('Invalid size in pixels');\n\t});\n\n\tit('returns the values with px suffix', () => {\n\t\tparams = {\n\t\t\twidth: 100,\n\t\t\theight: 200,\n\t\t};\n\n\t\tsize = new Size(params);\n\n\t\texpect(size.toPx()).toStrictEqual({\n\t\t\twidth: params['width' as keyof object] + 'px',\n\t\t\theight: params['height' as keyof object] + 'px',\n\t\t});\n\t});\n});\n\n\n===== FILE: src/shared/Size/Size.ts =====\nimport { ref, type Ref } from 'vue';\nimport { Maths } from '../';\n\nexport default class Size {\n\twidth: Ref<null | number> = ref(null);\n\theight: Ref<null | number> = ref(null);\n\n\tconstructor(\n\t\t{\n\t\t\twidth = null,\n\t\t\theight = null,\n\t\t}: {\n\t\t\twidth?: null | number;\n\t\t\theight?: null | number;\n\t\t} = { width: null, height: null },\n\t) {\n\t\tthis.update({ width, height });\n\t}\n\n\treset() {\n\t\tthis.width.value = null;\n\t\tthis.height.value = null;\n\t}\n\n\tisValid() {\n\t\treturn ![this.width.value, this.height.value].includes(null);\n\t}\n\n\tupdate({ width, height }: { width?: null | number; height?: null | number }) {\n\t\tthis.width.value = width ?? null;\n\t\tthis.height.value = height ?? null;\n\t}\n\n\tgetAspectRatio() {\n\t\tif (!this.isValid()) {\n\t\t\tthrow new RangeError('Could not get aspect ratio due to invalid size');\n\t\t}\n\n\t\treturn Maths.aspectRatio(this.toValue() as { width: number; height: number });\n\t}\n\n\tclone() {\n\t\treturn new Size(this.toValue());\n\t}\n\n\tequals(otherSize: Size) {\n\t\tif (this.width.value !== otherSize.width.value) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif (this.height.value !== otherSize.height.value) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\ttoValue() {\n\t\tconst rawSize: {\n\t\t\twidth?: number;\n\t\t\theight?: number;\n\t\t} = {};\n\n\t\tif (this.width.value !== null) {\n\t\t\trawSize.width = this.width.value;\n\t\t}\n\n\t\tif (this.height.value !== null) {\n\t\t\trawSize.height = this.height.value;\n\t\t}\n\n\t\treturn rawSize;\n\t}\n\n\ttoPx() {\n\t\tif (!this.isValid()) {\n\t\t\tthrow new RangeError('Invalid size in pixels');\n\t\t}\n\n\t\treturn {\n\t\t\twidth: this.width.value!.toString() + 'px',\n\t\t\theight: this.height.value!.toString() + 'px',\n\t\t};\n\t}\n}\n\n\n===== FILE: src/shared/index.ts =====\nexport * as Maths from './Maths/Maths';\nexport { default as Position } from './Position/Position';\nexport { default as ResizeCalculator } from './ResizeCalculator/ResizeCalculator';\nexport { default as ResourceLoader } from './ResourceLoader/ResourceLoader';\nexport { default as Size } from './Size/Size';\n\n\n===== FILE: src/transitions/Blinds2D/Blinds2D.test.ts =====\nimport Blinds2D from './Blinds2D.vue';\nimport AnimationWrapper from '../__test__/AnimationWrapper';\nimport { Directions } from '../../controllers/Player';\n\nvi.mock('../../components/FluxGrid/FluxGrid.vue');\n\ndescribe('transition: Blinds2D', () => {\n\tit('exposes onPlay and totalDuration', () => {\n\t\tconst wrapper = AnimationWrapper(Blinds2D, {});\n\n\t\tconst { onPlay, totalDuration } = wrapper.vm;\n\n\t\texpect(typeof onPlay).toBe('function');\n\t\texpect(typeof totalDuration).toBe('number');\n\t});\n\n\tit('performs the transition with default options', () => {\n\t\tconst wrapper = AnimationWrapper(Blinds2D, {});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0.1',\n\t\t\ttransform: 'scaleX(0)',\n\t\t\ttransition: 'all 800ms linear 0ms',\n\t\t});\n\n\t\texpect($tiles[9].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0.1',\n\t\t\ttransform: 'scaleX(0)',\n\t\t\ttransition: 'all 800ms linear 900ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1800);\n\t});\n\n\tit('performs the transition with custom options prev', () => {\n\t\tconst wrapper = AnimationWrapper(Blinds2D, {\n\t\t\tdirection: Directions.prev,\n\t\t\tcols: 6,\n\t\t\ttileDuration: 400,\n\t\t\ttileDelay: 60,\n\t\t\teasing: 'ease-out',\n\t\t});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0.1',\n\t\t\ttransform: 'scaleX(0)',\n\t\t\ttransition: 'all 400ms ease-out 300ms',\n\t\t});\n\n\t\texpect($tiles[5].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0.1',\n\t\t\ttransform: 'scaleX(0)',\n\t\t\ttransition: 'all 400ms ease-out 0ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(760);\n\t});\n\n\tit('performs the transition with custom options next', () => {\n\t\tconst wrapper = AnimationWrapper(Blinds2D, {\n\t\t\tdirection: Directions.next,\n\t\t\tcols: 6,\n\t\t\ttileDuration: 700,\n\t\t\ttileDelay: 90,\n\t\t\teasing: 'ease-in',\n\t\t});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0.1',\n\t\t\ttransform: 'scaleX(0)',\n\t\t\ttransition: 'all 700ms ease-in 0ms',\n\t\t});\n\n\t\texpect($tiles[5].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0.1',\n\t\t\ttransform: 'scaleX(0)',\n\t\t\ttransition: 'all 700ms ease-in 450ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1240);\n\t});\n});\n\n\n===== FILE: src/transitions/Blinds2D/Blinds2D.vue =====\n<script setup lang=\"ts\">\n\timport { ref, reactive, type Ref } from 'vue';\n\timport { type FluxComponent, FluxGrid } from '../../components';\n\timport useTransition from '../useTransition';\n\timport type { TransitionBlinds2DProps, TransitionBlinds2DConf } from './types';\n\timport { Sides } from '../../components/FluxCube';\n\n\tconst props = defineProps<TransitionBlinds2DProps>();\n\n\tconst $grid: Ref<null | InstanceType<typeof FluxGrid>> = ref(null);\n\n\tconst conf: TransitionBlinds2DConf = reactive({\n\t\trows: 1,\n\t\tcols: 10,\n\t\ttileDuration: 800,\n\t\ttileDelay: 100,\n\t\teasing: 'linear',\n\t});\n\n\tuseTransition(conf, props.options);\n\n\tconst rscs = {\n\t\t[Sides.front]: props.from,\n\t};\n\n\tconst totalDuration = conf.tileDelay * conf.cols + conf.tileDuration;\n\n\tconst getDelay = {\n\t\tprev: (index: number) => (conf.cols - index - 1) * conf.tileDelay,\n\t\tnext: (index: number) => index * conf.tileDelay,\n\t};\n\n\tconst onPlay = () => {\n\t\t$grid.value!.transform((tile: FluxComponent, index: number) => {\n\t\t\tconst transition = `all ${conf.tileDuration}ms ${\n\t\t\t\tconf.easing\n\t\t\t} ${getDelay[conf.direction!](index)}ms`;\n\n\t\t\ttile.transform({\n\t\t\t\ttransition,\n\t\t\t\topacity: '0.1',\n\t\t\t\ttransform: 'scaleX(0)',\n\t\t\t});\n\t\t});\n\t};\n\n\tdefineExpose({\n\t\tonPlay,\n\t\ttotalDuration,\n\t});\n</script>\n\n<template>\n\t<FluxGrid ref=\"$grid\" :rows=\"conf.rows\" :cols=\"conf.cols\" :size=\"size\" :rscs=\"rscs\" />\n</template>\n\n\n===== FILE: src/transitions/Blinds2D/types.ts =====\nimport type { TransitionConf, TransitionOptions, TransitionProps } from '../types';\n\nexport interface TransitionBlinds2DOptions extends TransitionOptions {\n\tcols?: number;\n\ttileDuration?: number;\n\ttileDelay?: number;\n}\n\nexport interface TransitionBlinds2DProps extends TransitionProps {\n\toptions?: TransitionBlinds2DOptions;\n}\n\nexport interface TransitionBlinds2DConf extends TransitionConf {\n\trows: number;\n\tcols: number;\n\ttileDuration: number;\n\ttileDelay: number;\n}\n\n\n===== FILE: src/transitions/Blinds3D/Blinds3D.test.ts =====\nimport Blinds3D from './Blinds3D.vue';\nimport AnimationWrapper from '../__test__/AnimationWrapper';\nimport { Directions } from '../../controllers';\nimport { Turns } from '../../components/FluxCube';\n\nvi.mock('../../components/FluxGrid/FluxGrid.vue');\n\ndescribe('transition: Blinds3D', () => {\n\tit('exposes onPlay and totalDuration', () => {\n\t\tconst wrapper = AnimationWrapper(Blinds3D, {});\n\n\t\tconst { onPlay, totalDuration } = wrapper.vm;\n\n\t\texpect(typeof onPlay).toBe('function');\n\t\texpect(typeof totalDuration).toBe('number');\n\t});\n\n\tit('expects to set proper CSS styles before animation', () => {\n\t\tconst wrapper = AnimationWrapper(Blinds3D, {});\n\n\t\tconst maskStyle = wrapper.props('maskStyle');\n\t\texpect(maskStyle.overflow).toBe('visible');\n\n\t\tconst gridCss = wrapper\n\t\t\t.getComponent({\n\t\t\t\tref: '$grid',\n\t\t\t})\n\t\t\t.props('css');\n\t\texpect(gridCss.perspective).toBeDefined();\n\t});\n\n\tit('performs the transition with default options', () => {\n\t\tconst wrapper = AnimationWrapper(Blinds3D, {});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].setCss).toHaveBeenCalledWith({\n\t\t\ttransition: 'all 800ms ease-out 0ms',\n\t\t});\n\t\texpect($tiles[0].turn).toHaveBeenCalledWith(Turns.backr);\n\n\t\texpect($tiles[5].setCss).toHaveBeenCalledWith({\n\t\t\ttransition: 'all 800ms ease-out 750ms',\n\t\t});\n\t\texpect($tiles[5].turn).toHaveBeenCalledWith(Turns.backr);\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1700);\n\t});\n\n\tit('performs the transition with custom options prev', () => {\n\t\tconst wrapper = AnimationWrapper(Blinds3D, {\n\t\t\tdirection: Directions.prev,\n\t\t\tcols: 8,\n\t\t\ttileDuration: 400,\n\t\t\ttileDelay: 60,\n\t\t\teasing: 'ease-in',\n\t\t});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].setCss).toHaveBeenCalledWith({\n\t\t\ttransition: 'all 400ms ease-in 420ms',\n\t\t});\n\t\texpect($tiles[0].turn).toHaveBeenCalledWith(Turns.backl);\n\n\t\texpect($tiles[7].setCss).toHaveBeenCalledWith({\n\t\t\ttransition: 'all 400ms ease-in 0ms',\n\t\t});\n\t\texpect($tiles[7].turn).toHaveBeenCalledWith(Turns.backl);\n\n\t\texpect(wrapper.vm.totalDuration).toBe(880);\n\t});\n\n\tit('performs the transition with custom options next', () => {\n\t\tconst wrapper = AnimationWrapper(Blinds3D, {\n\t\t\tdirection: Directions.next,\n\t\t\tcols: 10,\n\t\t\ttileDuration: 400,\n\t\t\ttileDelay: 60,\n\t\t\teasing: 'linear',\n\t\t});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].setCss).toHaveBeenCalledWith({\n\t\t\ttransition: 'all 400ms linear 0ms',\n\t\t});\n\t\texpect($tiles[0].turn).toHaveBeenCalledWith(Turns.backr);\n\n\t\texpect($tiles[9].setCss).toHaveBeenCalledWith({\n\t\t\ttransition: 'all 400ms linear 540ms',\n\t\t});\n\t\texpect($tiles[9].turn).toHaveBeenCalledWith(Turns.backr);\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1000);\n\t});\n});\n\n\n===== FILE: src/transitions/Blinds3D/Blinds3D.vue =====\n<script setup lang=\"ts\">\n\timport { ref, reactive, type Ref, type CSSProperties } from 'vue';\n\timport useTransition from '../useTransition';\n\timport { FluxGrid, Turns, FluxCube } from '../../components';\n\timport type { TransitionBlinds3DProps, TransitionBlinds3DConf } from './types';\n\n\tconst props = defineProps<TransitionBlinds3DProps>();\n\n\tconst $grid: Ref<null | InstanceType<typeof FluxGrid>> = ref(null);\n\n\tconst conf: TransitionBlinds3DConf = reactive({\n\t\trows: 1,\n\t\tcols: 6,\n\t\ttileDuration: 800,\n\t\ttileDelay: 150,\n\t\teasing: 'ease-out',\n\t});\n\n\tuseTransition(conf, props.options);\n\n\tconst gridCss: CSSProperties = {\n\t\tperspective: '800px',\n\t};\n\n\tconst rscs = {\n\t\tfront: props.from,\n\t\tback: props.to,\n\t};\n\n\tconst totalDuration = conf.tileDelay * conf.cols + conf.tileDuration;\n\n\t// eslint-disable-next-line vue/no-mutating-props\n\tprops.maskStyle.overflow = 'visible';\n\n\tconst getDelay = {\n\t\tprev: (index: number) => (conf.cols - index - 1) * conf.tileDelay,\n\t\tnext: (index: number) => index * conf.tileDelay,\n\t};\n\n\tconst turn = {\n\t\tprev: Turns.backl,\n\t\tnext: Turns.backr,\n\t}[conf.direction!];\n\n\tconst onPlay = () => {\n\t\tif (props.displayComponent) {\n\t\t\tprops.displayComponent.hide();\n\t\t}\n\n\t\ttype FluxCubeType = InstanceType<typeof FluxCube>;\n\n\t\t$grid.value!.transform<FluxCubeType>((tile: FluxCubeType, index: number) => {\n\t\t\tconst transition = `all ${conf.tileDuration}ms ${\n\t\t\t\tconf.easing\n\t\t\t} ${getDelay[conf.direction!](index)}ms`;\n\n\t\t\ttile.setCss({\n\t\t\t\ttransition,\n\t\t\t});\n\n\t\t\ttile.turn(turn);\n\t\t});\n\t};\n\n\tdefineExpose({\n\t\tonPlay,\n\t\ttotalDuration,\n\t});\n</script>\n\n<template>\n\t<FluxGrid\n\t\tref=\"$grid\"\n\t\t:rows=\"conf.rows\"\n\t\t:cols=\"conf.cols\"\n\t\t:size=\"size\"\n\t\t:rscs=\"rscs\"\n\t\t:css=\"gridCss\"\n\t/>\n</template>\n\n\n===== FILE: src/transitions/Blinds3D/types.ts =====\nimport type { TransitionConf, TransitionOptions, TransitionProps } from '../types';\n\nexport interface TransitionBlinds3DOptions extends TransitionOptions {\n\tcols?: number;\n\ttileDuration?: number;\n\ttileDelay?: number;\n}\n\nexport interface TransitionBlinds3DProps extends TransitionProps {\n\toptions?: TransitionBlinds3DOptions;\n}\n\nexport interface TransitionBlinds3DConf extends TransitionConf {\n\trows: number;\n\tcols: number;\n\ttileDuration: number;\n\ttileDelay: number;\n}\n\n\n===== FILE: src/transitions/Blocks1/Blocks1.test.ts =====\nimport Blocks1 from './Blocks1.vue';\nimport AnimationWrapper from '../__test__/AnimationWrapper';\nimport { Directions } from '../../controllers/Player';\n\nvi.mock('../../components/FluxGrid/FluxGrid.vue');\n\ndescribe('transition: Blocks1', () => {\n\tit('exposes onPlay and totalDuration', () => {\n\t\tconst wrapper = AnimationWrapper(Blocks1, {});\n\n\t\tconst { onPlay, totalDuration } = wrapper.vm;\n\n\t\texpect(typeof onPlay).toBe('function');\n\t\texpect(typeof totalDuration).toBe('number');\n\t});\n\n\tit('performs the transition with default options', () => {\n\t\tconst wrapper = AnimationWrapper(Blocks1, {});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'scale(0.3, 0.3)',\n\t\t\ttransition: expect.any(String),\n\t\t});\n\n\t\texpect($tiles[31].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'scale(0.3, 0.3)',\n\t\t\ttransition: expect.any(String),\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1300);\n\t});\n\n\tit('performs the transition with custom options prev', () => {\n\t\tconst wrapper = AnimationWrapper(Blocks1, {\n\t\t\tdirection: Directions.prev,\n\t\t\trows: 3,\n\t\t\tcols: 6,\n\t\t\ttileDuration: 400,\n\t\t\ttileDelay: 60,\n\t\t\teasing: 'ease-in-out',\n\t\t});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'scale(0.3, 0.3)',\n\t\t\ttransition: expect.any(String),\n\t\t});\n\n\t\texpect($tiles[17].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'scale(0.3, 0.3)',\n\t\t\ttransition: expect.any(String),\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(460);\n\t});\n\n\tit('performs the transition with custom options next', () => {\n\t\tconst wrapper = AnimationWrapper(Blocks1, {\n\t\t\tdirection: Directions.next,\n\t\t\trows: 4,\n\t\t\tcols: 6,\n\t\t\ttileDuration: 400,\n\t\t\ttileDelay: 60,\n\t\t\teasing: 'ease-in-out',\n\t\t});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'scale(0.3, 0.3)',\n\t\t\ttransition: expect.any(String),\n\t\t});\n\n\t\texpect($tiles[23].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'scale(0.3, 0.3)',\n\t\t\ttransition: expect.any(String),\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(460);\n\t});\n});\n\n\n===== FILE: src/transitions/Blocks1/Blocks1.vue =====\n<script setup lang=\"ts\">\n\timport { ref, reactive, type Ref } from 'vue';\n\timport useTransition from '../useTransition';\n\timport { type FluxComponent, FluxGrid } from '../../components';\n\timport type { TransitionBlocks1Props, TransitionBlocks1Conf } from './types';\n\n\tconst props = defineProps<TransitionBlocks1Props>();\n\n\tconst $grid: Ref<null | InstanceType<typeof FluxGrid>> = ref(null);\n\n\tconst conf: TransitionBlocks1Conf = reactive({\n\t\trows: 8,\n\t\tcols: 8,\n\t\ttileDuration: 300,\n\t\ttileDelay: 1000,\n\t\teasing: 'linear',\n\t});\n\n\tuseTransition(conf, props.options);\n\n\tif (!props.options?.rows) {\n\t\tconst divider = props.size.width.value! / conf.cols;\n\t\tconf.rows = Math.floor(props.size.height.value! / divider);\n\t}\n\n\tconst totalDuration = conf.tileDelay + conf.tileDuration;\n\n\tconst getDelay = () => Math.floor(Math.random() * conf.tileDelay);\n\n\tconst onPlay = () => {\n\t\t$grid.value!.transform((tile: FluxComponent) => {\n\t\t\tconst transition = `all ${conf.tileDuration}ms ${conf.easing} ${getDelay()}ms`;\n\n\t\t\ttile.transform({\n\t\t\t\ttransition,\n\t\t\t\topacity: '0',\n\t\t\t\ttransform: 'scale(0.3, 0.3)',\n\t\t\t});\n\t\t});\n\t};\n\n\tdefineExpose({\n\t\tonPlay,\n\t\ttotalDuration,\n\t});\n</script>\n\n<template>\n\t<FluxGrid ref=\"$grid\" :rows=\"conf.rows\" :cols=\"conf.cols\" :size=\"size\" :rsc=\"from\" />\n</template>\n\n\n===== FILE: src/transitions/Blocks1/types.ts =====\nimport type { TransitionConf, TransitionOptions, TransitionProps } from '../types';\n\nexport interface TransitionBlocks1Options extends TransitionOptions {\n\trows?: number;\n\tcols?: number;\n\ttileDuration?: number;\n\ttileDelay?: number;\n}\n\nexport interface TransitionBlocks1Props extends TransitionProps {\n\toptions?: TransitionBlocks1Options;\n}\n\nexport interface TransitionBlocks1Conf extends TransitionConf {\n\trows: number;\n\tcols: number;\n\ttileDuration: number;\n\ttileDelay: number;\n}\n\n\n===== FILE: src/transitions/Blocks2/Blocks2.test.ts =====\nimport Blocks2 from './Blocks2.vue';\nimport AnimationWrapper from '../__test__/AnimationWrapper';\nimport { Directions } from '../../controllers/Player';\n\nvi.mock('../../components/FluxGrid/FluxGrid.vue');\n\ndescribe('transition: Blocks2', () => {\n\tit('exposes onPlay and totalDuration', () => {\n\t\tconst wrapper = AnimationWrapper(Blocks2, {});\n\n\t\tconst { onPlay, totalDuration } = wrapper.vm;\n\n\t\texpect(typeof onPlay).toBe('function');\n\t\texpect(typeof totalDuration).toBe('number');\n\t});\n\n\tit('performs the transition with default options', () => {\n\t\tconst wrapper = AnimationWrapper(Blocks2, {});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'scale(0.3)',\n\t\t\ttransition: 'all 800ms ease 0ms',\n\t\t});\n\n\t\texpect($tiles[31].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'scale(0.3)',\n\t\t\ttransition: 'all 800ms ease 800ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(2080);\n\t});\n\n\tit('performs the transition with custom options prev', () => {\n\t\tconst wrapper = AnimationWrapper(Blocks2, {\n\t\t\tdirection: Directions.prev,\n\t\t\trows: 3,\n\t\t\tcols: 6,\n\t\t\ttileDuration: 400,\n\t\t\ttileDelay: 60,\n\t\t\teasing: 'ease-in-out',\n\t\t});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\topacity: '1',\n\t\t\ttransform: 'scale(1)',\n\t\t\ttransition: 'all 400ms ease-in-out 480ms',\n\t\t});\n\n\t\texpect($tiles[17].transform).toHaveBeenCalledWith({\n\t\t\topacity: '1',\n\t\t\ttransform: 'scale(1)',\n\t\t\ttransition: 'all 400ms ease-in-out 60ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(940);\n\t});\n\n\tit('performs the transition with custom options next', () => {\n\t\tconst wrapper = AnimationWrapper(Blocks2, {\n\t\t\tdirection: Directions.next,\n\t\t\trows: 4,\n\t\t\tcols: 6,\n\t\t\ttileDuration: 400,\n\t\t\ttileDelay: 60,\n\t\t\teasing: 'ease-in-out',\n\t\t});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'scale(0.3)',\n\t\t\ttransition: 'all 400ms ease-in-out 0ms',\n\t\t});\n\n\t\texpect($tiles[23].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'scale(0.3)',\n\t\t\ttransition: 'all 400ms ease-in-out 480ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1000);\n\t});\n});\n\n\n===== FILE: src/transitions/Blocks2/Blocks2.vue =====\n<script setup lang=\"ts\">\n\timport { ref, reactive, type Ref } from 'vue';\n\timport useTransition from '../useTransition';\n\timport { type FluxComponent, FluxGrid } from '../../components';\n\timport type { TransitionBlocks2Props, TransitionBlocks2Conf, BackgroundProps } from './types';\n\timport { Directions } from '../../controllers/Player';\n\n\tconst props = defineProps<TransitionBlocks2Props>();\n\n\tconst $grid: Ref<null | InstanceType<typeof FluxGrid>> = ref(null);\n\tconst $background: Ref<null | FluxComponent> = ref(null);\n\n\tconst conf: TransitionBlocks2Conf = reactive({\n\t\trows: 8,\n\t\tcols: 8,\n\t\ttileDuration: 800,\n\t\ttileDelay: 80,\n\t\teasing: 'ease',\n\t});\n\n\tuseTransition(conf, props.options);\n\n\tconst totalDuration = conf.tileDelay * (conf.rows + conf.cols) + conf.tileDuration;\n\n\tif (!props.options?.rows) {\n\t\tconst divider = props.size.width.value! / conf.cols;\n\t\tconf.rows = Math.floor(props.size.height.value! / divider);\n\t}\n\n\tconst background: BackgroundProps = {\n\t\trsc: null,\n\t\tcss: {\n\t\t\tposition: 'absolute',\n\t\t\ttop: 0,\n\t\t\tleft: 0,\n\t\t\tzIndex: 1,\n\t\t},\n\t};\n\n\tconst grid = JSON.parse(JSON.stringify(background));\n\tgrid.css.zIndex = 2;\n\n\tlet tileCss = {};\n\n\tconst setup = {\n\t\tprev: () => {\n\t\t\tgrid.rsc = props.to;\n\t\t\tbackground.rsc = props.from;\n\n\t\t\ttileCss = {\n\t\t\t\topacity: '0',\n\t\t\t\ttransform: 'scale(0.3)',\n\t\t\t};\n\t\t},\n\n\t\tnext: () => {\n\t\t\tgrid.rsc = props.from;\n\t\t},\n\t};\n\n\tsetup[conf.direction!]();\n\n\tconst getDelay = (index: number) => {\n\t\tconst row = $grid.value!.getRowNumber(index, conf.cols);\n\t\tconst col = $grid.value!.getColNumber(index, conf.cols);\n\t\tlet delay = col + row;\n\n\t\tif (conf.direction === Directions.prev) {\n\t\t\tdelay = conf.rows + conf.cols - delay - 1;\n\t\t}\n\n\t\treturn delay * conf.tileDelay;\n\t};\n\n\tconst play = {\n\t\tprev: () => {\n\t\t\t$grid.value!.transform((tile: FluxComponent, index: number) => {\n\t\t\t\ttile.transform({\n\t\t\t\t\ttransition: `all ${conf.tileDuration}ms ${conf.easing} ${getDelay(index)}ms`,\n\t\t\t\t\topacity: '1',\n\t\t\t\t\ttransform: 'scale(1)',\n\t\t\t\t});\n\t\t\t});\n\t\t},\n\n\t\tnext: () => {\n\t\t\t$grid.value!.transform((tile: FluxComponent, index: number) => {\n\t\t\t\ttile.transform({\n\t\t\t\t\ttransition: `all ${conf.tileDuration}ms ${conf.easing} ${getDelay(index)}ms`,\n\t\t\t\t\topacity: '0',\n\t\t\t\t\ttransform: 'scale(0.3)',\n\t\t\t\t});\n\t\t\t});\n\t\t},\n\t};\n\n\tconst onPlay = () => {\n\t\tplay[conf.direction!]();\n\t};\n\n\tdefineExpose({\n\t\tonPlay,\n\t\ttotalDuration,\n\t});\n</script>\n\n<template>\n\t<div>\n\t\t<FluxGrid\n\t\t\tref=\"$grid\"\n\t\t\t:rows=\"conf.rows\"\n\t\t\t:cols=\"conf.cols\"\n\t\t\t:size=\"size\"\n\t\t\t:tile-css=\"tileCss\"\n\t\t\tv-bind=\"grid\"\n\t\t/>\n\n\t\t<component\n\t\t\t:is=\"background.rsc.transition.component\"\n\t\t\tv-if=\"background.rsc !== null\"\n\t\t\tref=\"$background\"\n\t\t\t:size=\"size\"\n\t\t\tv-bind=\"background\"\n\t\t/>\n\t</div>\n</template>\n\n\n===== FILE: src/transitions/Blocks2/types.ts =====\nimport type { CSSProperties } from 'vue';\nimport { Resource } from '../../resources';\nimport type { TransitionConf, TransitionOptions, TransitionProps } from '../types';\n\nexport interface TransitionBlocks2Options extends TransitionOptions {\n\trows?: number;\n\tcols?: number;\n\ttileDuration?: number;\n\ttileDelay?: number;\n}\n\nexport interface TransitionBlocks2Props extends TransitionProps {\n\toptions?: TransitionBlocks2Options;\n}\n\nexport interface TransitionBlocks2Conf extends TransitionConf {\n\trows: number;\n\tcols: number;\n\ttileDuration: number;\n\ttileDelay: number;\n}\n\nexport interface BackgroundProps {\n\trsc: null | Resource;\n\tcss: CSSProperties;\n}\n\n\n===== FILE: src/transitions/Book/Book.test.ts =====\nimport Book from './Book.vue';\nimport AnimationWrapper from '../__test__/AnimationWrapper';\nimport { Directions } from '../../controllers/Player';\n\nvi.mock('../../components/FluxImage/FluxImage.vue');\nvi.mock('../../components/FluxCube/FluxCube.vue');\n\ndescribe('transition: Book', () => {\n\tit('exposes onPlay and totalDuration', () => {\n\t\tconst wrapper = AnimationWrapper(Book, {});\n\n\t\tconst { onPlay, totalDuration } = wrapper.vm;\n\n\t\texpect(typeof onPlay).toBe('function');\n\t\texpect(typeof totalDuration).toBe('number');\n\t});\n\n\tit('performs the transition with default options', () => {\n\t\tconst wrapper = AnimationWrapper(Book, {});\n\n\t\tconst size = wrapper.props('size').toValue();\n\n\t\tconst $from = wrapper.getComponent({\n\t\t\tref: '$from',\n\t\t});\n\n\t\tconst viewSize = $from.props('viewSize').toValue();\n\n\t\texpect(viewSize).toStrictEqual({\n\t\t\twidth: Math.ceil(size.width / 2),\n\t\t\theight: size.height,\n\t\t});\n\n\t\texpect($from.props('offset').toValue()).toStrictEqual({\n\t\t\ttop: 0,\n\t\t\tleft: 0,\n\t\t});\n\n\t\texpect($from.props('css')).toStrictEqual({\n\t\t\ttop: 0,\n\t\t\tleft: 0,\n\t\t\tposition: 'absolute',\n\t\t});\n\n\t\tconst $cube = wrapper.getComponent({\n\t\t\tref: '$cube',\n\t\t});\n\n\t\tconst cubeOffsets = $cube.props('offsets');\n\n\t\texpect(cubeOffsets.front.toValue()).toStrictEqual({\n\t\t\ttop: 0,\n\t\t\tleft: 320,\n\t\t});\n\n\t\texpect(cubeOffsets.back.toValue()).toStrictEqual({\n\t\t\ttop: 0,\n\t\t\tleft: 0,\n\t\t});\n\n\t\texpect($cube.props('origin')).toBe('left center');\n\n\t\texpect($cube.props('css')).toStrictEqual({\n\t\t\ttop: 0,\n\t\t\tleft: '320px',\n\t\t\tposition: 'absolute',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($cube.vm.transform).toHaveBeenCalledWith({\n\t\t\ttransform: 'rotateY(-180deg)',\n\t\t\ttransition: 'transform 1200ms ease-out',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1200);\n\t});\n\n\tit('performs the transition with custom options prev', () => {\n\t\tconst wrapper = AnimationWrapper(Book, {\n\t\t\tdirection: Directions.prev,\n\t\t\ttotalDuration: 900,\n\t\t\teasing: 'linear',\n\t\t});\n\n\t\tconst $from = wrapper.getComponent({\n\t\t\tref: '$from',\n\t\t});\n\n\t\texpect($from.props('offset').toValue()).toStrictEqual({\n\t\t\ttop: 0,\n\t\t\tleft: 320,\n\t\t});\n\n\t\texpect($from.props('css')).toStrictEqual({\n\t\t\ttop: 0,\n\t\t\tleft: '320px',\n\t\t\tposition: 'absolute',\n\t\t});\n\n\t\tconst $cube = wrapper.getComponent({\n\t\t\tref: '$cube',\n\t\t});\n\n\t\tconst cubeOffsets = $cube.props('offsets');\n\n\t\texpect(cubeOffsets.front.toValue()).toStrictEqual({\n\t\t\ttop: 0,\n\t\t\tleft: 0,\n\t\t});\n\n\t\texpect(cubeOffsets.back.toValue()).toStrictEqual({\n\t\t\ttop: 0,\n\t\t\tleft: 320,\n\t\t});\n\n\t\texpect($cube.props('origin')).toBe('right center');\n\n\t\texpect($cube.props('css')).toStrictEqual({\n\t\t\ttop: 0,\n\t\t\tleft: 0,\n\t\t\tposition: 'absolute',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($cube.vm.transform).toHaveBeenCalledWith({\n\t\t\ttransform: 'rotateY(180deg)',\n\t\t\ttransition: 'transform 900ms linear',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(900);\n\t});\n\n\tit('performs the transition with custom options next', () => {\n\t\tconst wrapper = AnimationWrapper(Book, {\n\t\t\tdirection: Directions.next,\n\t\t\ttotalDuration: 1000,\n\t\t\teasing: 'ease-out',\n\t\t});\n\n\t\tconst $from = wrapper.getComponent({\n\t\t\tref: '$from',\n\t\t});\n\n\t\texpect($from.props('offset').toValue()).toStrictEqual({\n\t\t\ttop: 0,\n\t\t\tleft: 0,\n\t\t});\n\n\t\texpect($from.props('css')).toStrictEqual({\n\t\t\ttop: 0,\n\t\t\tleft: 0,\n\t\t\tposition: 'absolute',\n\t\t});\n\n\t\tconst $cube = wrapper.getComponent({\n\t\t\tref: '$cube',\n\t\t});\n\n\t\tconst cubeOffsets = $cube.props('offsets');\n\n\t\texpect(cubeOffsets.front.toValue()).toStrictEqual({\n\t\t\ttop: 0,\n\t\t\tleft: 320,\n\t\t});\n\n\t\texpect(cubeOffsets.back.toValue()).toStrictEqual({\n\t\t\ttop: 0,\n\t\t\tleft: 0,\n\t\t});\n\n\t\texpect($cube.props('origin')).toBe('left center');\n\n\t\texpect($cube.props('css')).toStrictEqual({\n\t\t\ttop: 0,\n\t\t\tleft: '320px',\n\t\t\tposition: 'absolute',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($cube.vm.transform).toHaveBeenCalledWith({\n\t\t\ttransform: 'rotateY(-180deg)',\n\t\t\ttransition: 'transform 1000ms ease-out',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1000);\n\t});\n});\n\n\n===== FILE: src/transitions/Book/Book.vue =====\n<script setup lang=\"ts\">\n\timport { ref, reactive, type Ref, type CSSProperties } from 'vue';\n\timport useTransition from '../useTransition';\n\timport { type FluxComponent, FluxCube } from '../../components';\n\timport type { TransitionBookProps, TransitionBookConf } from './types';\n\timport { Position, Size } from '../../shared';\n\timport type { SidesOffsets } from '../../components/FluxCube/types';\n\timport { Directions } from '../../controllers/Player';\n\n\tconst props = defineProps<TransitionBookProps>();\n\n\tconst $from: Ref<null | FluxComponent> = ref(null);\n\tconst $cube: Ref<null | InstanceType<typeof FluxCube>> = ref(null);\n\n\tconst conf: TransitionBookConf = reactive({\n\t\ttotalDuration: 1200,\n\t\teasing: 'ease-out',\n\t});\n\n\tuseTransition(conf, props.options);\n\n\tconst viewSize: Size = new Size({\n\t\twidth: Math.ceil(props.size.width.value! / 2),\n\t\theight: props.size.height.value,\n\t});\n\n\tconst wrapperStyle: CSSProperties = {\n\t\tperspective: '1600px',\n\t\twidth: '100%',\n\t\theight: '100%',\n\t};\n\n\tconst fromOffset = new Position({\n\t\ttop: 0,\n\t\tleft: 0,\n\t});\n\n\tconst fromCss = {\n\t\tposition: 'absolute',\n\t\ttop: 0,\n\t\tleft: 0,\n\t} as CSSProperties;\n\n\tconst cube = {\n\t\trscs: {\n\t\t\tfront: props.from,\n\t\t\tback: props.to,\n\t\t},\n\t\toffsets: {\n\t\t\tfront: new Position({\n\t\t\t\ttop: 0,\n\t\t\t\tleft: 0,\n\t\t\t}),\n\t\t\tback: new Position({\n\t\t\t\ttop: 0,\n\t\t\t\tleft: 0,\n\t\t\t}),\n\t\t} as SidesOffsets,\n\t\torigin: '',\n\t\tcss: {\n\t\t\tposition: 'absolute',\n\t\t\ttop: 0,\n\t\t\tleft: 0,\n\t\t} as CSSProperties,\n\t};\n\n\tconst halfWidth: number = Math.ceil(props.size.width.value! / 2);\n\tconst halfWidthPx: string = halfWidth.toString() + 'px';\n\n\t// eslint-disable-next-line vue/no-mutating-props\n\tprops.maskStyle.overflow = 'visible';\n\n\tconst setup = {\n\t\tprev: () => {\n\t\t\tfromOffset.left.value = halfWidth;\n\t\t\tfromCss.left = halfWidthPx;\n\n\t\t\tcube.offsets.back!.left.value = halfWidth;\n\t\t\tcube.origin = 'right center';\n\t\t\tcube.css = {\n\t\t\t\t...cube.css,\n\t\t\t};\n\t\t},\n\n\t\tnext: () => {\n\t\t\tcube.offsets.front!.left.value = halfWidth;\n\t\t\tcube.origin = 'left center';\n\t\t\tcube.css = {\n\t\t\t\t...cube.css,\n\t\t\t\tleft: halfWidthPx,\n\t\t\t};\n\t\t},\n\t};\n\n\tsetup[conf.direction!]();\n\n\tconst deg = {\n\t\t[Directions.prev]: '180',\n\t\t[Directions.next]: '-180',\n\t}[conf.direction!];\n\n\tconst onPlay = () => {\n\t\t$cube.value!.transform({\n\t\t\ttransition: `transform ${conf.totalDuration}ms ${conf.easing}`,\n\t\t\ttransform: `rotateY(${deg}deg)`,\n\t\t});\n\t};\n\n\tdefineExpose({\n\t\tonPlay,\n\t\ttotalDuration: conf.totalDuration,\n\t});\n</script>\n\n<template>\n\t<div :style=\"wrapperStyle\">\n\t\t<component\n\t\t\t:is=\"from.transition.component\"\n\t\t\tref=\"$from\"\n\t\t\t:rsc=\"from\"\n\t\t\t:size=\"size\"\n\t\t\t:view-size=\"viewSize\"\n\t\t\t:offset=\"fromOffset\"\n\t\t\t:css=\"fromCss\"\n\t\t/>\n\n\t\t<FluxCube\n\t\t\tref=\"$cube\"\n\t\t\t:rscs=\"cube.rscs\"\n\t\t\t:size=\"size\"\n\t\t\t:view-size=\"viewSize\"\n\t\t\t:offsets=\"cube.offsets\"\n\t\t\t:origin=\"cube.origin\"\n\t\t\t:css=\"cube.css\"\n\t\t/>\n\t</div>\n</template>\n\n\n===== FILE: src/transitions/Book/types.ts =====\nimport type { TransitionConf, TransitionOptions, TransitionProps } from '../types';\n\nexport interface TransitionBookOptions extends TransitionOptions {\n\ttotalDuration?: number;\n}\n\nexport interface TransitionBookProps extends TransitionProps {\n\toptions?: TransitionBookOptions;\n}\n\nexport interface TransitionBookConf extends TransitionConf {\n\ttotalDuration: number;\n}\n\n\n===== FILE: src/transitions/Camera/Camera.test.ts =====\nimport Camera from './Camera.vue';\nimport AnimationWrapper from '../__test__/AnimationWrapper';\nimport { Directions } from '../../controllers/Player';\nimport { Maths, Size } from '../../shared';\n\nvi.mock('../../components/FluxWrapper/FluxWrapper.vue');\n\ndescribe('transition: Camera', () => {\n\tit('exposes onPlay and totalDuration', () => {\n\t\tconst wrapper = AnimationWrapper(Camera, {});\n\n\t\tconst { onPlay, totalDuration } = wrapper.vm;\n\n\t\texpect(typeof onPlay).toBe('function');\n\t\texpect(typeof totalDuration).toBe('number');\n\t});\n\n\tit('performs the transition with default options', () => {\n\t\tconst wrapper = AnimationWrapper(Camera, {});\n\n\t\tconst $wrapper = wrapper.getComponent({\n\t\t\tref: '$wrapper',\n\t\t});\n\n\t\tconst size = wrapper.props('size').toValue() as {\n\t\t\twidth: number;\n\t\t\theight: number;\n\t\t};\n\t\tconst diagSize = Maths.diag(size);\n\n\t\texpect($wrapper.props('size')).toStrictEqual(\n\t\t\tnew Size({ width: diagSize, height: diagSize })\n\t\t);\n\n\t\texpect($wrapper.props('css')).toStrictEqual({\n\t\t\tboxSizing: 'border-box',\n\t\t\tposition: 'absolute',\n\t\t\tdisplay: 'flex',\n\t\t\tjustifyContent: 'center',\n\t\t\toverflow: 'hidden',\n\t\t\tborderRadius: '50%',\n\t\t\tborder: '0 solid #111',\n\t\t\ttop: (size.height - diagSize) / 2 + 'px',\n\t\t\tleft: (size.width - diagSize) / 2 + 'px',\n\t\t});\n\n\t\tconst $from = wrapper.getComponent({\n\t\t\tref: '$from',\n\t\t});\n\n\t\texpect($from.props('css')).toStrictEqual({\n\t\t\talignSelf: 'center',\n\t\t\tflex: 'none',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($wrapper.vm.transform).toHaveBeenCalledWith({\n\t\t\tborderWidth: '367.5px',\n\t\t\ttransition: 'all 400ms cubic-bezier(0.385, 0, 0.795, 0.560) 0ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(900);\n\t});\n\n\tit('performs the transition with custom options prev', () => {\n\t\tconst wrapper = AnimationWrapper(Camera, {\n\t\t\tdirection: Directions.prev,\n\t\t\ttotalDuration: 800,\n\t\t\teasing: 'ease-out',\n\t\t});\n\n\t\tconst $wrapper = wrapper.getComponent({\n\t\t\tref: '$wrapper',\n\t\t});\n\n\t\tconst size = wrapper.props('size').toValue() as {\n\t\t\twidth: number;\n\t\t\theight: number;\n\t\t};\n\t\tconst diagSize = Maths.diag(size);\n\n\t\texpect($wrapper.props('size')).toStrictEqual(\n\t\t\tnew Size({ width: diagSize, height: diagSize })\n\t\t);\n\n\t\texpect($wrapper.props('css')).toStrictEqual({\n\t\t\tboxSizing: 'border-box',\n\t\t\tposition: 'absolute',\n\t\t\tdisplay: 'flex',\n\t\t\tjustifyContent: 'center',\n\t\t\toverflow: 'hidden',\n\t\t\tborderRadius: '50%',\n\t\t\tborder: '0 solid #111',\n\t\t\ttop: (size.height - diagSize) / 2 + 'px',\n\t\t\tleft: (size.width - diagSize) / 2 + 'px',\n\t\t});\n\n\t\tconst $from = wrapper.getComponent({\n\t\t\tref: '$from',\n\t\t});\n\n\t\texpect($from.props('css')).toStrictEqual({\n\t\t\talignSelf: 'center',\n\t\t\tflex: 'none',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($wrapper.vm.transform).toHaveBeenCalledWith({\n\t\t\tborderWidth: '367.5px',\n\t\t\ttransition: 'all 350ms ease-out 0ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(800);\n\t});\n\n\tit('performs the transition with custom options next', () => {\n\t\tconst wrapper = AnimationWrapper(Camera, {\n\t\t\tdirection: Directions.next,\n\t\t\ttotalDuration: 1000,\n\t\t\teasing: 'ease-in',\n\t\t});\n\n\t\tconst $wrapper = wrapper.getComponent({\n\t\t\tref: '$wrapper',\n\t\t});\n\n\t\tconst size = wrapper.props('size').toValue() as {\n\t\t\twidth: number;\n\t\t\theight: number;\n\t\t};\n\t\tconst diagSize = Maths.diag(size);\n\n\t\texpect($wrapper.props('size')).toStrictEqual(\n\t\t\tnew Size({ width: diagSize, height: diagSize })\n\t\t);\n\n\t\texpect($wrapper.props('css')).toStrictEqual({\n\t\t\tboxSizing: 'border-box',\n\t\t\tposition: 'absolute',\n\t\t\tdisplay: 'flex',\n\t\t\tjustifyContent: 'center',\n\t\t\toverflow: 'hidden',\n\t\t\tborderRadius: '50%',\n\t\t\tborder: '0 solid #111',\n\t\t\ttop: (size.height - diagSize) / 2 + 'px',\n\t\t\tleft: (size.width - diagSize) / 2 + 'px',\n\t\t});\n\n\t\tconst $from = wrapper.getComponent({\n\t\t\tref: '$from',\n\t\t});\n\n\t\texpect($from.props('css')).toStrictEqual({\n\t\t\talignSelf: 'center',\n\t\t\tflex: 'none',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($wrapper.vm.transform).toHaveBeenCalledWith({\n\t\t\tborderWidth: '367.5px',\n\t\t\ttransition: 'all 450ms ease-in 0ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1000);\n\t});\n});\n\n\n===== FILE: src/transitions/Camera/Camera.vue =====\n<script setup lang=\"ts\">\n\timport { ref, type Ref, reactive, type CSSProperties } from 'vue';\n\timport { Maths } from '../../shared';\n\timport useTransition from '../useTransition';\n\timport { type FluxComponent, FluxWrapper } from '../../components';\n\timport type { TransitionCameraProps, TransitionCameraConf } from './types';\n\timport { Size } from '../../shared';\n\n\tconst props = defineProps<TransitionCameraProps>();\n\n\tconst $wrapper: Ref<null | InstanceType<typeof FluxWrapper>> = ref(null);\n\tconst $from: Ref<null | FluxComponent> = ref(null);\n\n\tconst conf: TransitionCameraConf = reactive({\n\t\ttotalDuration: 900,\n\t\tbackgroundColor: '#111',\n\t\teasing: 'cubic-bezier(0.385, 0, 0.795, 0.560)',\n\t});\n\n\tuseTransition(conf, props.options);\n\n\tconst fromCss: CSSProperties = {\n\t\talignSelf: 'center',\n\t\tflex: 'none',\n\t};\n\n\tconst diagSize = Maths.diag(props.size.toValue() as { width: number; height: number });\n\n\tconst wrapperSize: Size = new Size({ width: diagSize, height: diagSize });\n\n\tconst WrapperCss: CSSProperties = {\n\t\tboxSizing: 'border-box',\n\t\tposition: 'absolute',\n\t\tdisplay: 'flex',\n\t\tjustifyContent: 'center',\n\t\toverflow: 'hidden',\n\t\tborderRadius: '50%',\n\t\tborder: '0 solid ' + conf.backgroundColor,\n\t\ttop: (props.size.height.value! - diagSize) / 2 + 'px',\n\t\tleft: (props.size.width.value! - diagSize) / 2 + 'px',\n\t};\n\n\tconst onPlay = () => {\n\t\t$wrapper.value!.transform({\n\t\t\ttransition: `all ${conf.totalDuration! / 2 - 50}ms ${conf.easing} 0ms`,\n\t\t\tborderWidth: diagSize / 2 + 'px',\n\t\t});\n\n\t\tsetTimeout(\n\t\t\t() => {\n\t\t\t\t$from.value!.hide();\n\n\t\t\t\t$wrapper.value!.transform({\n\t\t\t\t\ttransition: `all ${conf.totalDuration! / 2 - 50}ms ${conf.easing} 0ms`,\n\t\t\t\t\tborderWidth: 0,\n\t\t\t\t});\n\t\t\t},\n\t\t\tconf.totalDuration! / 2 + 50,\n\t\t);\n\t};\n\n\tdefineExpose({\n\t\tonPlay,\n\t\ttotalDuration: conf.totalDuration,\n\t});\n</script>\n\n<template>\n\t<FluxWrapper ref=\"$wrapper\" :size=\"wrapperSize\" :css=\"WrapperCss\">\n\t\t<component\n\t\t\t:is=\"from.transition.component\"\n\t\t\tref=\"$from\"\n\t\t\t:size=\"size\"\n\t\t\t:rsc=\"from\"\n\t\t\t:css=\"fromCss\"\n\t\t/>\n\t</FluxWrapper>\n</template>\n\n\n===== FILE: src/transitions/Camera/types.ts =====\nimport type { CSSProperties } from 'vue';\nimport type { TransitionConf, TransitionOptions, TransitionProps } from '../types';\n\nexport interface TransitionCameraOptions extends TransitionOptions {\n\ttotalDuration?: number;\n\tbackgroundColor?: CSSProperties['color'];\n}\n\nexport interface TransitionCameraProps extends TransitionProps {\n\toptions?: TransitionCameraOptions;\n}\n\nexport interface TransitionCameraConf extends TransitionConf {\n\ttotalDuration: number;\n\tbackgroundColor: CSSProperties['color'];\n}\n\n\n===== FILE: src/transitions/Concentric/Concentric.test.ts =====\nimport Concentric from './Concentric.vue';\nimport AnimationWrapper from '../__test__/AnimationWrapper';\nimport { Directions } from '../../controllers/Player';\n\nvi.mock('../../components/FluxVortex/FluxVortex.vue');\n\ndescribe('transition: Concentric', () => {\n\tit('exposes onPlay and totalDuration', () => {\n\t\tconst wrapper = AnimationWrapper(Concentric, {});\n\n\t\tconst { onPlay, totalDuration } = wrapper.vm;\n\n\t\texpect(typeof onPlay).toBe('function');\n\t\texpect(typeof totalDuration).toBe('number');\n\t});\n\n\tit('performs the transition with default options', () => {\n\t\tconst wrapper = AnimationWrapper(Concentric, {});\n\n\t\tconst $vortex = wrapper.getComponent({\n\t\t\tref: '$vortex',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($vortex.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $vortex.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'rotateZ(90deg)',\n\t\t\ttransition: 'all 800ms linear 0ms',\n\t\t});\n\n\t\texpect($tiles[6].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'rotateZ(90deg)',\n\t\t\ttransition: 'all 800ms linear 900ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1850);\n\t});\n\n\tit('performs the transition with custom options prev', () => {\n\t\tconst wrapper = AnimationWrapper(Concentric, {\n\t\t\tdirection: Directions.prev,\n\t\t\tcircles: 5,\n\t\t\ttileDuration: 400,\n\t\t\ttileDelay: 60,\n\t\t\teasing: 'ease-out',\n\t\t});\n\n\t\tconst $vortex = wrapper.getComponent({\n\t\t\tref: '$vortex',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($vortex.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $vortex.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'rotateZ(-90deg)',\n\t\t\ttransition: 'all 400ms ease-out 0ms',\n\t\t});\n\n\t\texpect($tiles[4].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'rotateZ(-90deg)',\n\t\t\ttransition: 'all 400ms ease-out 240ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(700);\n\t});\n\n\tit('performs the transition with custom options next', () => {\n\t\tconst wrapper = AnimationWrapper(Concentric, {\n\t\t\tdirection: Directions.next,\n\t\t\tcircles: 10,\n\t\t\ttileDuration: 400,\n\t\t\ttileDelay: 60,\n\t\t\teasing: 'ease-out',\n\t\t});\n\n\t\tconst $vortex = wrapper.getComponent({\n\t\t\tref: '$vortex',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($vortex.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $vortex.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'rotateZ(90deg)',\n\t\t\ttransition: 'all 400ms ease-out 0ms',\n\t\t});\n\n\t\texpect($tiles[9].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'rotateZ(90deg)',\n\t\t\ttransition: 'all 400ms ease-out 540ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1000);\n\t});\n});\n\n\n===== FILE: src/transitions/Concentric/Concentric.vue =====\n<script setup lang=\"ts\">\n\timport { ref, reactive, type Ref } from 'vue';\n\timport useTransition from '../useTransition';\n\timport { type FluxComponent, FluxVortex } from '../../components';\n\timport type { TransitionConcentricProps, TransitionConcentricConf } from './types';\n\timport { Directions } from '../../controllers/Player';\n\n\tconst props = defineProps<TransitionConcentricProps>();\n\n\tconst $vortex: Ref<null | InstanceType<typeof FluxVortex>> = ref(null);\n\n\tconst conf: TransitionConcentricConf = reactive({\n\t\tcircles: 7,\n\t\ttileDuration: 800,\n\t\ttileDelay: 150,\n\t\teasing: 'linear',\n\t});\n\n\tuseTransition(conf, props.options);\n\n\tconst totalDuration = conf.tileDelay * conf.circles + conf.tileDuration;\n\n\tconst getDelay = (index: number) => index * conf.tileDelay;\n\n\tconst deg = {\n\t\t[Directions.prev]: '-90',\n\t\t[Directions.next]: '90',\n\t}[conf.direction!];\n\n\tconst onPlay = () => {\n\t\t$vortex.value!.transform((tile: FluxComponent, index: number) => {\n\t\t\tconst transition = `all ${conf.tileDuration}ms ${conf.easing} ${getDelay(index)}ms`;\n\n\t\t\ttile.transform({\n\t\t\t\ttransition,\n\t\t\t\topacity: '0',\n\t\t\t\ttransform: `rotateZ(${deg}deg)`,\n\t\t\t});\n\t\t});\n\t};\n\n\tdefineExpose({\n\t\tonPlay,\n\t\ttotalDuration,\n\t});\n</script>\n\n<template>\n\t<FluxVortex ref=\"$vortex\" :size=\"size\" :circles=\"conf.circles\" :rsc=\"from\" />\n</template>\n\n\n===== FILE: src/transitions/Concentric/types.ts =====\nimport type { TransitionConf, TransitionOptions, TransitionProps } from '../types';\n\nexport interface TransitionConcentricOptions extends TransitionOptions {\n\tcircles?: number;\n\ttileDuration?: number;\n\ttileDelay?: number;\n}\n\nexport interface TransitionConcentricProps extends TransitionProps {\n\toptions?: TransitionConcentricOptions;\n}\n\nexport interface TransitionConcentricConf extends TransitionConf {\n\tcircles: number;\n\ttileDuration: number;\n\ttileDelay: number;\n}\n\n\n===== FILE: src/transitions/Cube/Cube.test.ts =====\nimport Cube from './Cube.vue';\nimport AnimationWrapper from '../__test__/AnimationWrapper';\nimport { Directions } from '../../controllers';\nimport { Turns } from '../../components/FluxCube';\n\nvi.mock('../../components/FluxCube/FluxCube.vue');\n\ndescribe('transition: Cube', () => {\n\tit('exposes onPlay and totalDuration', () => {\n\t\tconst wrapper = AnimationWrapper(Cube, {});\n\n\t\tconst { onPlay, totalDuration } = wrapper.vm;\n\n\t\texpect(typeof onPlay).toBe('function');\n\t\texpect(typeof totalDuration).toBe('number');\n\t});\n\n\tit('expects to set proper CSS styles before animation', () => {\n\t\tconst wrapper = AnimationWrapper(Cube, {});\n\n\t\tconst maskStyle = wrapper.props('maskStyle');\n\t\texpect(maskStyle.overflow).toBe('visible');\n\t\texpect(maskStyle.perspective).toBeDefined();\n\t});\n\n\tit('performs the transition with default options', () => {\n\t\tconst wrapper = AnimationWrapper(Cube, {});\n\n\t\tconst $cube = wrapper.getComponent({\n\t\t\tref: '$cube',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($cube.vm.turn).toHaveBeenCalledWith(Turns.left);\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1400);\n\t});\n\n\tit('performs the transition with custom options prev', () => {\n\t\tconst wrapper = AnimationWrapper(Cube, {\n\t\t\tdirection: Directions.prev,\n\t\t\ttotalDuration: 900,\n\t\t\teasing: 'ease-in',\n\t\t});\n\n\t\tconst $cube = wrapper.getComponent({\n\t\t\tref: '$cube',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($cube.vm.turn).toHaveBeenCalledWith(Turns.right);\n\n\t\texpect(wrapper.vm.totalDuration).toBe(900);\n\t});\n\n\tit('performs the transition with custom options next', () => {\n\t\tconst wrapper = AnimationWrapper(Cube, {\n\t\t\tdirection: Directions.next,\n\t\t\ttotalDuration: 300,\n\t\t\teasing: 'ease-in-out',\n\t\t});\n\n\t\tconst $cube = wrapper.getComponent({\n\t\t\tref: '$cube',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($cube.vm.turn).toHaveBeenCalledWith(Turns.left);\n\n\t\texpect(wrapper.vm.totalDuration).toBe(300);\n\t});\n});\n\n\n===== FILE: src/transitions/Cube/Cube.vue =====\n<script setup lang=\"ts\">\n\timport { ref, reactive, type Ref, type CSSProperties } from 'vue';\n\timport useTransition from '../useTransition';\n\timport { FluxCube } from '../../components';\n\timport type { TransitionCubeProps, TransitionCubeConf } from './types';\n\timport { Turns } from '../../components/FluxCube';\n\timport { Directions } from '../../controllers/Player';\n\n\tconst props = defineProps<TransitionCubeProps>();\n\n\tconst $cube: Ref<null | InstanceType<typeof FluxCube>> = ref(null);\n\n\tconst conf: TransitionCubeConf = reactive({\n\t\ttotalDuration: 1400,\n\t\teasing: 'ease-out',\n\t});\n\n\tuseTransition(conf, props.options);\n\n\t// eslint-disable-next-line vue/no-mutating-props\n\tObject.assign(props.maskStyle, {\n\t\tperspective: '1600px',\n\t\toverflow: 'visible',\n\t});\n\n\tconst rscs = {\n\t\tfront: props.from,\n\t\tleft: props.to,\n\t\tright: props.to,\n\t};\n\n\tconst cubeCss: CSSProperties = {\n\t\ttransition: `all ${conf.totalDuration}ms ${conf.easing}`,\n\t};\n\n\tconst turn = {\n\t\t[Directions.prev]: Turns.right,\n\t\t[Directions.next]: Turns.left,\n\t}[conf.direction!];\n\n\tconst onPlay = () => {\n\t\tif (props.displayComponent !== null) {\n\t\t\tprops.displayComponent.hide();\n\t\t}\n\n\t\t$cube.value!.turn(turn);\n\t};\n\n\tdefineExpose({\n\t\tonPlay,\n\t\ttotalDuration: conf.totalDuration,\n\t});\n</script>\n\n<template>\n\t<FluxCube ref=\"$cube\" :rscs=\"rscs\" :size=\"size\" :depth=\"size.width.value!\" :css=\"cubeCss\" />\n</template>\n\n\n===== FILE: src/transitions/Cube/types.ts =====\nimport type { TransitionConf, TransitionOptions, TransitionProps } from '../types';\n\nexport interface TransitionCubeOptions extends TransitionOptions {\n\ttotalDuration?: number;\n}\n\nexport interface TransitionCubeProps extends TransitionProps {\n\toptions?: TransitionCubeOptions;\n}\n\nexport interface TransitionCubeConf extends TransitionConf {\n\ttotalDuration: number;\n}\n\n\n===== FILE: src/transitions/Explode/Explode.test.ts =====\nimport Explode from './Explode.vue';\nimport AnimationWrapper from '../__test__/AnimationWrapper';\nimport { Directions } from '../../controllers/Player';\n\nvi.mock('../../components/FluxGrid/FluxGrid.vue');\n\ndescribe('transition: Explode', () => {\n\tit('exposes onPlay and totalDuration', () => {\n\t\tconst wrapper = AnimationWrapper(Explode, {});\n\n\t\tconst { onPlay, totalDuration } = wrapper.vm;\n\n\t\texpect(typeof onPlay).toBe('function');\n\t\texpect(typeof totalDuration).toBe('number');\n\t});\n\n\tit('performs the transition with default options', () => {\n\t\tconst wrapper = AnimationWrapper(Explode, {});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\tconst maskStyle = wrapper.props('maskStyle');\n\t\texpect(maskStyle.overflow).toBe('visible');\n\n\t\tconst gridCss = wrapper\n\t\t\t.getComponent({\n\t\t\t\tref: '$grid',\n\t\t\t})\n\t\t\t.props('css');\n\t\texpect(gridCss.overflow).toBe('visible');\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\tborderRadius: '100%',\n\t\t\topacity: '0',\n\t\t\ttransform: 'scale(2)',\n\t\t\ttransition: 'all 300ms linear 500ms',\n\t\t});\n\n\t\texpect($tiles[21].transform).toHaveBeenCalledWith({\n\t\t\tborderRadius: '100%',\n\t\t\topacity: '0',\n\t\t\ttransform: 'scale(2)',\n\t\t\ttransition: 'all 300ms linear 0ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1400);\n\t});\n\n\tit('performs the transition with custom options prev', () => {\n\t\tconst wrapper = AnimationWrapper(Explode, {\n\t\t\tdirection: Directions.prev,\n\t\t\trows: 3,\n\t\t\tcols: 6,\n\t\t\ttileDuration: 400,\n\t\t\ttileDelay: 60,\n\t\t\teasing: 'ease-in-out',\n\t\t});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\tborderRadius: '100%',\n\t\t\topacity: '0',\n\t\t\ttransform: 'scale(2)',\n\t\t\ttransition: 'all 400ms ease-in-out 150ms',\n\t\t});\n\n\t\texpect($tiles[8].transform).toHaveBeenCalledWith({\n\t\t\tborderRadius: '100%',\n\t\t\topacity: '0',\n\t\t\ttransform: 'scale(2)',\n\t\t\ttransition: 'all 400ms ease-in-out -30ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(540);\n\t});\n\n\tit('performs the transition with custom options next', () => {\n\t\tconst wrapper = AnimationWrapper(Explode, {\n\t\t\tdirection: Directions.next,\n\t\t\trows: 4,\n\t\t\tcols: 7,\n\t\t\ttileDuration: 200,\n\t\t\ttileDelay: 80,\n\t\t\teasing: 'ease-in',\n\t\t});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\tborderRadius: '100%',\n\t\t\topacity: '0',\n\t\t\ttransform: 'scale(2)',\n\t\t\ttransition: 'all 200ms ease-in 280ms',\n\t\t});\n\n\t\texpect($tiles[13].transform).toHaveBeenCalledWith({\n\t\t\tborderRadius: '100%',\n\t\t\topacity: '0',\n\t\t\ttransform: 'scale(2)',\n\t\t\ttransition: 'all 200ms ease-in 200ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(880);\n\t});\n});\n\n\n===== FILE: src/transitions/Explode/Explode.vue =====\n<script setup lang=\"ts\">\n\timport { ref, reactive, type Ref, type CSSProperties } from 'vue';\n\timport useTransition from '../useTransition';\n\timport { type FluxComponent, FluxGrid } from '../../components';\n\timport type { TransitionExplodeProps, TransitionExplodeConf } from './types';\n\n\tconst props = defineProps<TransitionExplodeProps>();\n\n\tconst $grid: Ref<null | InstanceType<typeof FluxGrid>> = ref(null);\n\n\tconst conf: TransitionExplodeConf = reactive({\n\t\trows: 9,\n\t\tcols: 9,\n\t\ttileDuration: 300,\n\t\ttileDelay: 100,\n\t\teasing: 'linear',\n\t});\n\n\tuseTransition(conf, props.options);\n\n\tconst cssGrid: CSSProperties = {\n\t\toverflow: 'visible',\n\t};\n\n\t// eslint-disable-next-line vue/no-mutating-props\n\tprops.maskStyle.overflow = 'visible';\n\n\tif (!props.options?.rows) {\n\t\tconst divider = props.size.width.value! / conf.cols;\n\t\tconf.rows = Math.floor(props.size.height.value! / divider);\n\t}\n\n\tconst totalDuration = (conf.cols / 2 + conf.rows / 2) * (conf.tileDelay * 2);\n\n\tconst getDelay = (index: number) => {\n\t\tconst row = $grid.value!.getRowNumber(index, conf.cols);\n\t\tconst col = $grid.value!.getColNumber(index, conf.cols);\n\n\t\tconst rowDelay = Math.abs(conf.rows / 2 - 0.5 - row);\n\t\tconst colDelay = Math.abs(conf.cols / 2 - 0.5 - col);\n\t\tconst delay = rowDelay + colDelay - 1;\n\n\t\treturn delay * conf.tileDelay;\n\t};\n\n\tconst onPlay = () => {\n\t\t$grid.value!.transform((tile: FluxComponent, index: number) => {\n\t\t\tconst transition = `all ${conf.tileDuration}ms ${conf.easing} ${getDelay(index)}ms`;\n\n\t\t\ttile.transform({\n\t\t\t\ttransition,\n\t\t\t\tborderRadius: '100%',\n\t\t\t\topacity: '0',\n\t\t\t\ttransform: 'scale(2)',\n\t\t\t});\n\t\t});\n\t};\n\n\tdefineExpose({\n\t\tonPlay,\n\t\ttotalDuration,\n\t});\n</script>\n\n<template>\n\t<FluxGrid\n\t\tref=\"$grid\"\n\t\t:rows=\"conf.rows\"\n\t\t:cols=\"conf.cols\"\n\t\t:size=\"size\"\n\t\t:rsc=\"from\"\n\t\t:css=\"cssGrid\"\n\t/>\n</template>\n\n\n===== FILE: src/transitions/Explode/types.ts =====\nimport type { TransitionConf, TransitionOptions, TransitionProps } from '../types';\n\nexport interface TransitionExplodeOptions extends TransitionOptions {\n\trows?: number;\n\tcols?: number;\n\ttileDuration?: number;\n\ttileDelay?: number;\n}\n\nexport interface TransitionExplodeProps extends TransitionProps {\n\toptions?: TransitionExplodeOptions;\n}\n\nexport interface TransitionExplodeConf extends TransitionConf {\n\trows: number;\n\tcols: number;\n\ttileDuration: number;\n\ttileDelay: number;\n}\n\n\n===== FILE: src/transitions/Fade/Fade.test.ts =====\nimport Fade from './Fade.vue';\nimport AnimationWrapper from '../__test__/AnimationWrapper';\nimport { Directions } from '../../controllers/Player';\n\nvi.mock('../../components/FluxImage/FluxImage.vue');\n\ndescribe('transition: Fade', () => {\n\tit('exposes onPlay and totalDuration', () => {\n\t\tconst wrapper = AnimationWrapper(Fade, {});\n\n\t\tconst { onPlay, totalDuration } = wrapper.vm;\n\n\t\texpect(typeof onPlay).toBe('function');\n\t\texpect(typeof totalDuration).toBe('number');\n\t});\n\n\tit('performs the transition with default options', () => {\n\t\tconst wrapper = AnimationWrapper(Fade, {});\n\n\t\tconst $from = wrapper.getComponent({\n\t\t\tref: '$from',\n\t\t});\n\n\t\texpect($from.props('css')).toStrictEqual({\n\t\t\tzIndex: 1,\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($from.vm.transform).toHaveBeenCalledWith({\n\t\t\topacity: 0,\n\t\t\ttransition: 'opacity 1200ms ease-in',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1200);\n\t});\n\n\tit('performs the transition with custom options prev', () => {\n\t\tconst wrapper = AnimationWrapper(Fade, {\n\t\t\tdirection: Directions.prev,\n\t\t\ttotalDuration: 1800,\n\t\t\teasing: 'linear',\n\t\t});\n\n\t\tconst $from = wrapper.getComponent({\n\t\t\tref: '$from',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($from.vm.transform).toHaveBeenCalledWith({\n\t\t\topacity: 0,\n\t\t\ttransition: 'opacity 1800ms linear',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1800);\n\t});\n\n\tit('performs the transition with custom options next', () => {\n\t\tconst wrapper = AnimationWrapper(Fade, {\n\t\t\tdirection: Directions.next,\n\t\t\ttotalDuration: 600,\n\t\t\teasing: 'ease-out',\n\t\t});\n\n\t\tconst $from = wrapper.getComponent({\n\t\t\tref: '$from',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($from.vm.transform).toHaveBeenCalledWith({\n\t\t\topacity: 0,\n\t\t\ttransition: 'opacity 600ms ease-out',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(600);\n\t});\n});\n\n\n===== FILE: src/transitions/Fade/Fade.vue =====\n<script setup lang=\"ts\">\n\timport { ref, reactive, type Ref, type CSSProperties } from 'vue';\n\timport useTransition from '../useTransition';\n\timport type { TransitionFadeProps, TransitionFadeConf } from './types';\n\timport type { FluxComponent } from '../../components';\n\n\tconst props = defineProps<TransitionFadeProps>();\n\n\tconst $from: Ref<null | FluxComponent> = ref(null);\n\n\tconst conf: TransitionFadeConf = reactive({\n\t\ttotalDuration: 1200,\n\t\teasing: 'ease-in',\n\t});\n\n\tuseTransition(conf, props.options);\n\n\tconst fromCss: CSSProperties = {\n\t\tzIndex: 1,\n\t};\n\n\tconst onPlay = () => {\n\t\t$from.value!.transform({\n\t\t\ttransition: `opacity ${conf.totalDuration}ms ${conf.easing}`,\n\t\t\topacity: 0,\n\t\t});\n\t};\n\n\tdefineExpose({\n\t\tonPlay,\n\t\ttotalDuration: conf.totalDuration,\n\t});\n</script>\n\n<template>\n\t<component :is=\"from.transition.component\" ref=\"$from\" :rsc=\"from\" :size=\"size\" :css=\"fromCss\" />\n</template>\n\n\n===== FILE: src/transitions/Fade/types.ts =====\nimport type { TransitionConf, TransitionOptions, TransitionProps } from '../types';\n\nexport interface TransitionFadeOptions extends TransitionOptions {\n\ttotalDuration?: number;\n}\n\nexport interface TransitionFadeProps extends TransitionProps {\n\toptions?: TransitionFadeOptions;\n}\n\nexport interface TransitionFadeConf extends TransitionConf {\n\ttotalDuration: number;\n}\n\n\n===== FILE: src/transitions/Fall/Fall.test.ts =====\nimport Fall from './Fall.vue';\nimport AnimationWrapper from '../__test__/AnimationWrapper';\nimport { Directions } from '../../controllers/Player';\n\nvi.mock('../../components/FluxImage/FluxImage.vue');\n\ndescribe('transition: Fall', () => {\n\tit('exposes onPlay and totalDuration', () => {\n\t\tconst wrapper = AnimationWrapper(Fall, {});\n\n\t\tconst { onPlay, totalDuration } = wrapper.vm;\n\n\t\texpect(typeof onPlay).toBe('function');\n\t\texpect(typeof totalDuration).toBe('number');\n\t});\n\n\tit('performs the transition with default options', () => {\n\t\tconst wrapper = AnimationWrapper(Fall, {});\n\n\t\tconst $from = wrapper.getComponent({\n\t\t\tref: '$from',\n\t\t});\n\n\t\tconst maskStyle = wrapper.props('maskStyle');\n\t\texpect(maskStyle.overflow).toBe('visible');\n\t\texpect(maskStyle.perspective).toBeDefined();\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($from.vm.transform).toHaveBeenCalledWith({\n\t\t\ttransform: 'rotateX(-83.6deg)',\n\t\t\ttransition: 'transform 1600ms ease-in',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1600);\n\t});\n\n\tit('performs the transition with custom options prev', () => {\n\t\tconst wrapper = AnimationWrapper(Fall, {\n\t\t\tdirection: Directions.prev,\n\t\t\ttotalDuration: 1200,\n\t\t\teasing: 'linear',\n\t\t});\n\n\t\tconst $from = wrapper.getComponent({\n\t\t\tref: '$from',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($from.vm.transform).toHaveBeenCalledWith({\n\t\t\ttransform: 'rotateX(-83.6deg)',\n\t\t\ttransition: 'transform 1200ms linear',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1200);\n\t});\n\n\tit('performs the transition with custom options next', () => {\n\t\tconst wrapper = AnimationWrapper(Fall, {\n\t\t\tdirection: Directions.next,\n\t\t\ttotalDuration: 1000,\n\t\t\teasing: 'ease-out',\n\t\t});\n\n\t\tconst $from = wrapper.getComponent({\n\t\t\tref: '$from',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($from.vm.transform).toHaveBeenCalledWith({\n\t\t\ttransform: 'rotateX(-83.6deg)',\n\t\t\ttransition: 'transform 1000ms ease-out',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1000);\n\t});\n});\n\n\n===== FILE: src/transitions/Fall/Fall.vue =====\n<script setup lang=\"ts\">\n\timport { ref, reactive, type Ref, type CSSProperties } from 'vue';\n\timport useTransition from '../useTransition';\n\timport type { TransitionFallProps, TransitionFallConf } from './types';\n\timport type { FluxComponent } from '../../components';\n\n\tconst props = defineProps<TransitionFallProps>();\n\n\tconst $from: Ref<null | FluxComponent> = ref(null);\n\n\tconst conf: TransitionFallConf = reactive({\n\t\ttotalDuration: 1600,\n\t\teasing: 'ease-in',\n\t});\n\n\tuseTransition(conf, props.options);\n\n\t// eslint-disable-next-line vue/no-mutating-props\n\tObject.assign(props.maskStyle, {\n\t\tperspective: '1600px',\n\t\toverflow: 'visible',\n\t});\n\n\tconst style: CSSProperties = {\n\t\ttransformOrigin: 'center bottom',\n\t};\n\n\tconst onPlay = () => {\n\t\t$from.value!.transform({\n\t\t\ttransition: `transform ${conf.totalDuration}ms ${conf.easing}`,\n\t\t\ttransform: 'rotateX(-83.6deg)',\n\t\t});\n\t};\n\n\tdefineExpose({\n\t\tonPlay,\n\t\ttotalDuration: conf.totalDuration,\n\t});\n</script>\n\n<template>\n\t<component :is=\"from.transition.component\" ref=\"$from\" :rsc=\"from\" :size=\"size\" :style=\"style\" />\n</template>\n\n\n===== FILE: src/transitions/Fall/types.ts =====\nimport type { TransitionConf, TransitionOptions, TransitionProps } from '../types';\n\nexport interface TransitionFallOptions extends TransitionOptions {\n\ttotalDuration?: number;\n}\n\nexport interface TransitionFallProps extends TransitionProps {\n\toptions?: TransitionFallOptions;\n}\n\nexport interface TransitionFallConf extends TransitionConf {\n\ttotalDuration: number;\n}\n\n\n===== FILE: src/transitions/Kenburn/Kenburn.test.ts =====\nimport Kenburn from './Kenburn.vue';\nimport AnimationWrapper from '../__test__/AnimationWrapper';\nimport { Directions } from '../../controllers/Player';\n\nvi.mock('../../components/FluxImage/FluxImage.vue');\n\ndescribe('transition: Kenburn', () => {\n\tit('exposes onPlay and totalDuration', () => {\n\t\tconst wrapper = AnimationWrapper(Kenburn, {});\n\n\t\tconst { onPlay, totalDuration } = wrapper.vm;\n\n\t\texpect(typeof onPlay).toBe('function');\n\t\texpect(typeof totalDuration).toBe('number');\n\t});\n\n\tit('performs the transition with default options', () => {\n\t\tconst wrapper = AnimationWrapper(Kenburn, {});\n\n\t\tconst $from = wrapper.getComponent({\n\t\t\tref: '$from',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($from.vm.transform).toHaveBeenCalledWith({\n\t\t\topacity: 0,\n\t\t\ttransform: expect.any(String),\n\t\t\ttransition: 'all 1500ms linear',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1500);\n\t});\n\n\tit('performs the transition with custom options prev', () => {\n\t\tconst wrapper = AnimationWrapper(Kenburn, {\n\t\t\tdirection: Directions.prev,\n\t\t\ttotalDuration: 800,\n\t\t\teasing: 'ease-in',\n\t\t});\n\n\t\tconst $from = wrapper.getComponent({\n\t\t\tref: '$from',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($from.vm.transform).toHaveBeenCalledWith({\n\t\t\topacity: 0,\n\t\t\ttransform: expect.any(String),\n\t\t\ttransition: 'all 800ms ease-in',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(800);\n\t});\n\n\tit('performs the transition with custom options next', () => {\n\t\tconst wrapper = AnimationWrapper(Kenburn, {\n\t\t\tdirection: Directions.next,\n\t\t\ttotalDuration: 800,\n\t\t\teasing: 'ease-in',\n\t\t});\n\n\t\tconst $from = wrapper.getComponent({\n\t\t\tref: '$from',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($from.vm.transform).toHaveBeenCalledWith({\n\t\t\topacity: 0,\n\t\t\ttransform: expect.any(String),\n\t\t\ttransition: 'all 800ms ease-in',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(800);\n\t});\n});\n\n\n===== FILE: src/transitions/Kenburn/Kenburn.vue =====\n<script setup lang=\"ts\">\n\timport { ref, reactive, type Ref, type CSSProperties } from 'vue';\n\timport useTransition from '../useTransition';\n\timport type { TransitionKenburnProps, TransitionKenburnConf } from './types';\n\timport type { FluxComponent } from '../../components';\n\n\tconst props = defineProps<TransitionKenburnProps>();\n\n\tconst $from: Ref<null | FluxComponent> = ref(null);\n\n\tconst conf: TransitionKenburnConf = reactive({\n\t\ttotalDuration: 1500,\n\t\teasing: 'linear',\n\t});\n\n\tuseTransition(conf, props.options);\n\n\tconst transforms = [\n\t\t{\n\t\t\tscale: '1.7',\n\t\t\ttranslateX: '-35%',\n\t\t\ttranslateY: '-35%',\n\t\t\toriginX: 'top',\n\t\t\toriginY: 'left',\n\t\t},\n\n\t\t{\n\t\t\tscale: '1.7',\n\t\t\ttranslateX: '35%',\n\t\t\ttranslateY: '-35%',\n\t\t\toriginX: 'top',\n\t\t\toriginY: 'right',\n\t\t},\n\n\t\t{\n\t\t\tscale: '1.7',\n\t\t\ttranslateX: '-35%',\n\t\t\ttranslateY: '35%',\n\t\t\toriginX: 'bottom',\n\t\t\toriginY: 'left',\n\t\t},\n\n\t\t{\n\t\t\tscale: '1.7',\n\t\t\ttranslateX: '35%',\n\t\t\ttranslateY: '35%',\n\t\t\toriginX: 'bottom',\n\t\t\toriginY: 'right',\n\t\t},\n\t];\n\n\tconst transformNumber: number = Math.floor(Math.random() * 4);\n\tconst transform = transforms[transformNumber];\n\n\tconst css: CSSProperties = {\n\t\ttransformOrigin: transform!.originX + ' ' + transform!.originY,\n\t};\n\n\tconst onPlay = () => {\n\t\t$from.value!.transform({\n\t\t\ttransition: `all ${conf.totalDuration}ms ${conf.easing}`,\n\t\t\ttransform: `scale(${transform!.scale}) translate(${transform!.translateX}, ${transform!.translateY})`,\n\t\t\topacity: 0,\n\t\t});\n\t};\n\n\tdefineExpose({\n\t\tonPlay,\n\t\ttotalDuration: conf.totalDuration,\n\t});\n</script>\n\n<template>\n\t<component :is=\"from.transition.component\" ref=\"$from\" :rsc=\"from\" :size=\"size\" :css=\"css\" />\n</template>\n\n\n===== FILE: src/transitions/Kenburn/types.ts =====\nimport type { TransitionConf, TransitionOptions, TransitionProps } from '../types';\n\nexport interface TransitionKenburnOptions extends TransitionOptions {\n\ttotalDuration?: number;\n}\n\nexport interface TransitionKenburnProps extends TransitionProps {\n\toptions?: TransitionKenburnOptions;\n}\n\nexport interface TransitionKenburnConf extends TransitionConf {\n\ttotalDuration: number;\n}\n\n\n===== FILE: src/transitions/Round1/Round1.test.ts =====\nimport Round1 from './Round1.vue';\nimport AnimationWrapper from '../__test__/AnimationWrapper';\nimport { Directions } from '../../controllers/Player';\nimport { Turns } from '../../components/FluxCube';\n\nvi.mock('../../components/FluxGrid/FluxGrid.vue');\n\ndescribe('transition: Round1', () => {\n\tit('exposes onPlay and totalDuration', () => {\n\t\tconst wrapper = AnimationWrapper(Round1, {});\n\n\t\tconst { onPlay, totalDuration } = wrapper.vm;\n\n\t\texpect(typeof onPlay).toBe('function');\n\t\texpect(typeof totalDuration).toBe('number');\n\t});\n\n\tit('performs the transition with default options', () => {\n\t\tconst wrapper = AnimationWrapper(Round1, {});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\tconst maskStyle = wrapper.props('maskStyle');\n\t\texpect(maskStyle.overflow).toBe('visible');\n\n\t\tconst gridCss = wrapper\n\t\t\t.getComponent({\n\t\t\t\tref: '$grid',\n\t\t\t})\n\t\t\t.props('css');\n\t\texpect(gridCss.perspective).toBeDefined();\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].setCss).toHaveBeenCalledWith({\n\t\t\ttransition: 'all 800ms ease-out 0ms',\n\t\t});\n\t\texpect($tiles[0].turn).toHaveBeenCalledWith(Turns.backr);\n\n\t\texpect($tiles[9].setCss).toHaveBeenCalledWith({\n\t\t\ttransition: 'all 800ms ease-out 300ms',\n\t\t});\n\t\texpect($tiles[9].turn).toHaveBeenCalledWith(Turns.backr);\n\n\t\texpect(wrapper.vm.totalDuration).toBe(2400);\n\t});\n\n\tit('performs the transition with custom options prev', () => {\n\t\tconst wrapper = AnimationWrapper(Round1, {\n\t\t\tdirection: Directions.prev,\n\t\t\trows: 3,\n\t\t\tcols: 6,\n\t\t\ttileDuration: 400,\n\t\t\ttileDelay: 60,\n\t\t\teasing: 'ease-in-out',\n\t\t});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].setCss).toHaveBeenCalledWith({\n\t\t\ttransition: 'all 400ms ease-in-out 480ms',\n\t\t});\n\t\texpect($tiles[0].turn).toHaveBeenCalledWith(Turns.backl);\n\n\t\texpect($tiles[17].setCss).toHaveBeenCalledWith({\n\t\t\ttransition: 'all 400ms ease-in-out 60ms',\n\t\t});\n\t\texpect($tiles[17].turn).toHaveBeenCalledWith(Turns.backl);\n\n\t\texpect(wrapper.vm.totalDuration).toBe(720);\n\t});\n\n\tit('performs the transition with custom options next', () => {\n\t\tconst wrapper = AnimationWrapper(Round1, {\n\t\t\tdirection: Directions.next,\n\t\t\trows: 3,\n\t\t\tcols: 6,\n\t\t\ttileDuration: 400,\n\t\t\ttileDelay: 60,\n\t\t\teasing: 'ease-in-out',\n\t\t});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].setCss).toHaveBeenCalledWith({\n\t\t\ttransition: 'all 400ms ease-in-out 0ms',\n\t\t});\n\t\texpect($tiles[0].turn).toHaveBeenCalledWith(Turns.backr);\n\n\t\texpect($tiles[17].setCss).toHaveBeenCalledWith({\n\t\t\ttransition: 'all 400ms ease-in-out 420ms',\n\t\t});\n\t\texpect($tiles[17].turn).toHaveBeenCalledWith(Turns.backr);\n\n\t\texpect(wrapper.vm.totalDuration).toBe(720);\n\t});\n});\n\n\n===== FILE: src/transitions/Round1/Round1.vue =====\n<script setup lang=\"ts\">\n\timport { ref, reactive, type Ref, type CSSProperties } from 'vue';\n\timport useTransition from '../useTransition';\n\timport { FluxGrid, FluxCube } from '../../components';\n\timport type { TransitionRound1Props, TransitionRound1Conf } from './types';\n\timport { Directions } from '../../controllers/Player';\n\timport { Turns } from '../../components';\n\n\tconst props = defineProps<TransitionRound1Props>();\n\n\tconst $grid: Ref<null | InstanceType<typeof FluxGrid>> = ref(null);\n\n\tconst conf: TransitionRound1Conf = reactive({\n\t\trows: 0,\n\t\tcols: 8,\n\t\ttileDuration: 800,\n\t\ttileDelay: 150,\n\t\teasing: 'ease-out',\n\t});\n\n\tuseTransition(conf, props.options);\n\n\tconst rscs = {\n\t\tfront: props.from,\n\t\tback: props.to,\n\t};\n\n\tconst gridCss: CSSProperties = {\n\t\tperspective: '800px',\n\t};\n\n\t// eslint-disable-next-line vue/no-mutating-props\n\tprops.maskStyle.overflow = 'visible';\n\n\tif (!props.options?.rows) {\n\t\tconst divider = props.size.width.value! / conf.cols;\n\t\tconf.rows = Math.floor(props.size.height.value! / divider);\n\t}\n\n\tconst multiplier = conf.rows > conf.cols ? conf.rows : conf.cols;\n\n\tconst totalDuration = conf.tileDelay * multiplier * 2;\n\n\tconst getDelay = (index: number) => {\n\t\tconst row = $grid.value!.getRowNumber(index, conf.cols);\n\t\tconst col = $grid.value!.getColNumber(index, conf.cols);\n\t\tlet delay = col + row;\n\n\t\tif (conf.direction === Directions.prev) {\n\t\t\tdelay = conf.rows! + conf.cols - delay - 1;\n\t\t}\n\n\t\treturn delay * conf.tileDelay;\n\t};\n\n\tconst turn = {\n\t\t[Directions.prev]: Turns.backl,\n\t\t[Directions.next]: Turns.backr,\n\t}[conf.direction!];\n\n\tconst onPlay = () => {\n\t\tif (props.displayComponent) {\n\t\t\tprops.displayComponent.hide();\n\t\t}\n\n\t\ttype FluxCubeType = InstanceType<typeof FluxCube>;\n\n\t\t$grid.value!.transform<FluxCubeType>((tile: FluxCubeType, index: number) => {\n\t\t\tconst transition = `all ${conf.tileDuration}ms ${conf.easing} ${getDelay(index)}ms`;\n\n\t\t\ttile.setCss({\n\t\t\t\ttransition,\n\t\t\t});\n\n\t\t\ttile.turn(turn);\n\t\t});\n\t};\n\n\tdefineExpose({\n\t\tonPlay,\n\t\ttotalDuration,\n\t});\n</script>\n\n<template>\n\t<FluxGrid\n\t\tref=\"$grid\"\n\t\t:rows=\"conf.rows!\"\n\t\t:cols=\"conf.cols\"\n\t\t:size=\"size\"\n\t\t:rscs=\"rscs\"\n\t\t:css=\"gridCss\"\n\t/>\n</template>\n\n\n===== FILE: src/transitions/Round1/types.ts =====\nimport type { TransitionConf, TransitionOptions, TransitionProps } from '../types';\n\nexport interface TransitionRound1Options extends TransitionOptions {\n\trows?: number;\n\tcols?: number;\n\ttileDuration?: number;\n\ttileDelay?: number;\n}\n\nexport interface TransitionRound1Props extends TransitionProps {\n\toptions?: TransitionRound1Options;\n}\n\nexport interface TransitionRound1Conf extends TransitionConf {\n\trows: number;\n\tcols: number;\n\ttileDuration: number;\n\ttileDelay: number;\n}\n\n\n===== FILE: src/transitions/Round2/Round2.test.ts =====\nimport Round2 from './Round2.vue';\nimport AnimationWrapper from '../__test__/AnimationWrapper';\nimport { Directions } from '../../controllers/Player';\n\nvi.mock('../../components/FluxGrid/FluxGrid.vue');\n\ndescribe('transition: Round2', () => {\n\tit('exposes onPlay and totalDuration', () => {\n\t\tconst wrapper = AnimationWrapper(Round2, {});\n\n\t\tconst { onPlay, totalDuration } = wrapper.vm;\n\n\t\texpect(typeof onPlay).toBe('function');\n\t\texpect(typeof totalDuration).toBe('number');\n\t});\n\n\tit('performs the transition with default options', () => {\n\t\tconst wrapper = AnimationWrapper(Round2, {});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\tconst maskStyle = wrapper.props('maskStyle');\n\t\texpect(maskStyle.overflow).toBe('visible');\n\n\t\tconst gridCss = wrapper\n\t\t\t.getComponent({\n\t\t\t\tref: '$grid',\n\t\t\t})\n\t\t\t.props('css');\n\t\texpect(gridCss.perspective).toBeDefined();\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'rotateY(-540deg)',\n\t\t\ttransition: 'all 800ms linear 100ms',\n\t\t});\n\n\t\texpect($tiles[9].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'rotateY(-540deg)',\n\t\t\ttransition: 'all 800ms linear 0ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1900);\n\t});\n\n\tit('performs the transition with custom options prev', () => {\n\t\tconst wrapper = AnimationWrapper(Round2, {\n\t\t\tdirection: Directions.prev,\n\t\t\trows: 3,\n\t\t\tcols: 6,\n\t\t\ttileDuration: 400,\n\t\t\ttileDelay: 60,\n\t\t\trotateX: -310,\n\t\t\teasing: 'ease-out',\n\t\t});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'rotateY(-310deg)',\n\t\t\ttransition: 'all 400ms ease-out 360ms',\n\t\t});\n\n\t\texpect($tiles[17].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'rotateY(-310deg)',\n\t\t\ttransition: 'all 400ms ease-out 60ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(720);\n\t});\n\n\tit('performs the transition with custom options next', () => {\n\t\tconst wrapper = AnimationWrapper(Round2, {\n\t\t\tdirection: Directions.next,\n\t\t\trows: 3,\n\t\t\tcols: 6,\n\t\t\ttileDuration: 400,\n\t\t\ttileDelay: 60,\n\t\t\trotateX: -310,\n\t\t\teasing: 'ease-out',\n\t\t});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'rotateY(-310deg)',\n\t\t\ttransition: 'all 400ms ease-out 0ms',\n\t\t});\n\n\t\texpect($tiles[17].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'rotateY(-310deg)',\n\t\t\ttransition: 'all 400ms ease-out 300ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(720);\n\t});\n});\n\n\n===== FILE: src/transitions/Round2/Round2.vue =====\n<script setup lang=\"ts\">\n\timport { ref, reactive, type Ref, type CSSProperties } from 'vue';\n\timport useTransition from '../useTransition';\n\timport { type FluxComponent, FluxGrid } from '../../components';\n\timport type { TransitionRound2Props, TransitionRound2Conf } from './types';\n\timport { Directions } from '../../controllers/Player';\n\n\tconst props = defineProps<TransitionRound2Props>();\n\n\tconst $grid: Ref<null | InstanceType<typeof FluxGrid>> = ref(null);\n\n\tconst conf: TransitionRound2Conf = reactive({\n\t\trows: 0,\n\t\tcols: 9,\n\t\ttileDuration: 800,\n\t\ttileDelay: 100,\n\t\trotateX: -540,\n\t\teasing: 'linear',\n\t});\n\n\tuseTransition(conf, props.options);\n\n\t// eslint-disable-next-line vue/no-mutating-props\n\tprops.maskStyle.overflow = 'visible';\n\n\tconst gridCss: CSSProperties = {\n\t\tperspective: '1200px',\n\t};\n\n\tconst tileCss: CSSProperties = {\n\t\tbackfaceVisibility: 'hidden',\n\t};\n\n\tif (!props.options?.rows) {\n\t\tconst divider = props.size.width.value! / conf.cols;\n\t\tconf.rows = Math.floor(props.size.height.value! / divider);\n\t}\n\n\tconst totalDuration = (conf.cols / 2 + conf.rows) * (conf.tileDelay * 2);\n\n\tconst getDelay = (index: number) => {\n\t\tconst row = $grid.value!.getRowNumber(index, conf.cols);\n\t\tconst col = $grid.value!.getColNumber(index, conf.cols);\n\n\t\tlet rowDelay, colDelay;\n\n\t\tif (conf.direction === Directions.prev) {\n\t\t\trowDelay = Math.abs(conf.rows! / 2 - 0.5 - row);\n\t\t\tcolDelay = Math.abs(conf.cols - col);\n\t\t} else {\n\t\t\trowDelay = Math.abs(conf.rows! / 2 - 0.5 - row);\n\t\t\tcolDelay = Math.abs(col);\n\t\t}\n\n\t\tconst delay = rowDelay + colDelay - 1;\n\n\t\treturn delay * conf.tileDelay;\n\t};\n\n\tconst onPlay = () => {\n\t\t$grid.value!.transform((tile: FluxComponent, index: number) => {\n\t\t\tconst transition = `all ${conf.tileDuration}ms ${conf.easing} ${getDelay(index)}ms`;\n\n\t\t\ttile.transform({\n\t\t\t\ttransition,\n\t\t\t\topacity: '0',\n\t\t\t\ttransform: `rotateY(${conf.rotateX.toString()}deg)`,\n\t\t\t});\n\t\t});\n\t};\n\n\tdefineExpose({\n\t\tonPlay,\n\t\ttotalDuration,\n\t});\n</script>\n\n<template>\n\t<FluxGrid\n\t\tref=\"$grid\"\n\t\t:rows=\"conf.rows!\"\n\t\t:cols=\"conf.cols\"\n\t\t:size=\"size\"\n\t\t:depth=\"0\"\n\t\t:rsc=\"from\"\n\t\t:css=\"gridCss\"\n\t\t:tile-css=\"tileCss\"\n\t/>\n</template>\n\n\n===== FILE: src/transitions/Round2/types.ts =====\nimport type { TransitionConf, TransitionOptions, TransitionProps } from '../types';\n\nexport interface TransitionRound2Options extends TransitionOptions {\n\trows?: number;\n\tcols?: number;\n\ttileDuration?: number;\n\ttileDelay?: number;\n\trotateX?: number;\n}\n\nexport interface TransitionRound2Props extends TransitionProps {\n\toptions?: TransitionRound2Options;\n}\n\nexport interface TransitionRound2Conf extends TransitionConf {\n\trows: number;\n\tcols: number;\n\ttileDuration: number;\n\ttileDelay: number;\n\trotateX: number;\n}\n\n\n===== FILE: src/transitions/Slide/Slide.test.ts =====\nimport Slide from './Slide.vue';\nimport AnimationWrapper from '../__test__/AnimationWrapper';\nimport { Directions } from '../../controllers/Player';\nimport { Size } from '../../shared';\n\nvi.mock('../../components/FluxWrapper/FluxWrapper.vue');\n\ndescribe('transition: Slide', () => {\n\tit('exposes onPlay and totalDuration', () => {\n\t\tconst wrapper = AnimationWrapper(Slide, {});\n\n\t\tconst { onPlay, totalDuration } = wrapper.vm;\n\n\t\texpect(typeof onPlay).toBe('function');\n\t\texpect(typeof totalDuration).toBe('number');\n\t});\n\n\tit('performs the transition with default options', () => {\n\t\tconst wrapper = AnimationWrapper(Slide, {});\n\n\t\tconst $wrapper = wrapper.getComponent({\n\t\t\tref: '$wrapper',\n\t\t});\n\n\t\texpect($wrapper.props('size')).toStrictEqual(\n\t\t\tnew Size({ width: 1280, height: 360 })\n\t\t);\n\t\texpect($wrapper.props('css')).toStrictEqual({\n\t\t\tdisplay: 'flex',\n\t\t\tflexWrap: 'nowrap',\n\t\t});\n\n\t\tconst $left = wrapper.getComponent({\n\t\t\tref: '$left',\n\t\t});\n\n\t\texpect($left.props('size')).toStrictEqual(wrapper.props('size'));\n\n\t\tconst $right = wrapper.getComponent({\n\t\t\tref: '$right',\n\t\t});\n\n\t\texpect($right.props('size')).toStrictEqual(wrapper.props('size'));\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($wrapper.vm.transform).toHaveBeenCalledWith({\n\t\t\ttransform: 'translateX(-50%)',\n\t\t\ttransition: 'transform 1400ms ease-in-out',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1400);\n\t});\n\n\tit('performs the transition with custom options prev', () => {\n\t\tconst wrapper = AnimationWrapper(Slide, {\n\t\t\tdirection: Directions.prev,\n\t\t\ttotalDuration: 800,\n\t\t\teasing: 'ease-out',\n\t\t});\n\n\t\tconst $wrapper = wrapper.getComponent({\n\t\t\tref: '$wrapper',\n\t\t});\n\n\t\texpect($wrapper.props('size')).toStrictEqual(\n\t\t\tnew Size({ width: 1280, height: 360 })\n\t\t);\n\t\texpect($wrapper.props('css')).toStrictEqual({\n\t\t\tdisplay: 'flex',\n\t\t\tflexWrap: 'nowrap',\n\t\t\ttransform: 'translateX(-50%)',\n\t\t});\n\n\t\tconst $left = wrapper.getComponent({\n\t\t\tref: '$left',\n\t\t});\n\n\t\texpect($left.props('size')).toStrictEqual(wrapper.props('size'));\n\t\texpect($left.props('rsc')).toStrictEqual(wrapper.props('to'));\n\n\t\tconst $right = wrapper.getComponent({\n\t\t\tref: '$right',\n\t\t});\n\n\t\texpect($right.props('size')).toStrictEqual(wrapper.props('size'));\n\t\texpect($right.props('rsc')).toStrictEqual(wrapper.props('from'));\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($wrapper.vm.transform).toHaveBeenCalledWith({\n\t\t\ttransform: 'translateX(0)',\n\t\t\ttransition: 'transform 800ms ease-out',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(800);\n\t});\n\n\tit('performs the transition with custom options next', () => {\n\t\tconst wrapper = AnimationWrapper(Slide, {\n\t\t\tdirection: Directions.next,\n\t\t\ttotalDuration: 800,\n\t\t\teasing: 'ease-out',\n\t\t});\n\n\t\tconst $wrapper = wrapper.getComponent({\n\t\t\tref: '$wrapper',\n\t\t});\n\n\t\texpect($wrapper.props('size')).toStrictEqual(\n\t\t\tnew Size({ width: 1280, height: 360 })\n\t\t);\n\t\texpect($wrapper.props('css')).toStrictEqual({\n\t\t\tdisplay: 'flex',\n\t\t\tflexWrap: 'nowrap',\n\t\t});\n\n\t\tconst $left = wrapper.getComponent({\n\t\t\tref: '$left',\n\t\t});\n\n\t\texpect($left.props('size')).toStrictEqual(wrapper.props('size'));\n\t\texpect($left.props('rsc')).toStrictEqual(wrapper.props('from'));\n\n\t\tconst $right = wrapper.getComponent({\n\t\t\tref: '$right',\n\t\t});\n\n\t\texpect($right.props('size')).toStrictEqual(wrapper.props('size'));\n\t\texpect($right.props('rsc')).toStrictEqual(wrapper.props('to'));\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($wrapper.vm.transform).toHaveBeenCalledWith({\n\t\t\ttransform: 'translateX(-50%)',\n\t\t\ttransition: 'transform 800ms ease-out',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(800);\n\t});\n});\n\n\n===== FILE: src/transitions/Slide/Slide.vue =====\n<script setup lang=\"ts\">\n\timport { ref, reactive, type Ref, type CSSProperties } from 'vue';\n\timport useTransition from '../useTransition';\n\timport { type FluxComponent, FluxWrapper } from '../../components';\n\timport type { TransitionSlideProps, TransitionSlideConf } from './types';\n\timport type { ComponentProps } from '../../components';\n\timport { Size } from '../../shared';\n\timport { Directions } from '../../controllers/Player';\n\timport { Resource } from '../../resources';\n\n\tconst props = defineProps<TransitionSlideProps>();\n\n\tconst $wrapper: Ref<null | InstanceType<typeof FluxWrapper>> = ref(null);\n\tconst $left: Ref<null | FluxComponent> = ref(null);\n\tconst $right: Ref<null | FluxComponent> = ref(null);\n\n\tconst conf: TransitionSlideConf = reactive({\n\t\ttotalDuration: 1400,\n\t\teasing: 'ease-in-out',\n\t});\n\n\tuseTransition(conf, props.options);\n\n\tconst transition = `transform ${conf.totalDuration}ms ${conf.easing}`;\n\n\tconst wrapperProps: ComponentProps = {\n\t\tsize: new Size({\n\t\t\twidth: props.size.width.value! * 2,\n\t\t\theight: props.size.height.value!,\n\t\t}),\n\t\tcss: {\n\t\t\tdisplay: 'flex',\n\t\t\tflexWrap: 'nowrap',\n\t\t} as CSSProperties,\n\t};\n\n\tlet left: Resource;\n\tlet right: Resource;\n\n\tconst setup = {\n\t\t[Directions.prev]: () => {\n\t\t\tleft = props.to!;\n\t\t\tright = props.from;\n\t\t\twrapperProps.css!.transform = 'translateX(-50%)';\n\t\t},\n\n\t\t[Directions.next]: () => {\n\t\t\tleft = props.from;\n\t\t\tright = props.to!;\n\t\t},\n\t};\n\n\tsetup[conf.direction!]();\n\n\tconst play = {\n\t\t[Directions.prev]: () => {\n\t\t\t$wrapper.value!.transform({\n\t\t\t\ttransition: transition,\n\t\t\t\ttransform: 'translateX(0)',\n\t\t\t});\n\t\t},\n\n\t\t[Directions.next]: () => {\n\t\t\t$wrapper.value!.transform({\n\t\t\t\ttransition: transition,\n\t\t\t\ttransform: 'translateX(-50%)',\n\t\t\t});\n\t\t},\n\t};\n\n\tconst onPlay = () => {\n\t\tplay[conf.direction!]();\n\t};\n\n\tdefineExpose({\n\t\tonPlay,\n\t\ttotalDuration: conf.totalDuration,\n\t});\n</script>\n\n<template>\n\t<FluxWrapper ref=\"$wrapper\" v-bind=\"wrapperProps\">\n\t\t<component :is=\"left.transition.component\" ref=\"$left\" :rsc=\"left\" :size=\"size\" />\n\t\t<component :is=\"right.transition.component\" ref=\"$right\" :rsc=\"right\" :size=\"size\" />\n\t</FluxWrapper>\n</template>\n\n\n===== FILE: src/transitions/Slide/types.ts =====\nimport type { TransitionConf, TransitionOptions, TransitionProps } from '../types';\n\nexport interface TransitionSlideOptions extends TransitionOptions {\n\ttotalDuration?: number;\n}\n\nexport interface TransitionSlideProps extends TransitionProps {\n\toptions?: TransitionSlideOptions;\n}\n\nexport interface TransitionSlideConf extends TransitionConf {\n\ttotalDuration: number;\n}\n\n\n===== FILE: src/transitions/Swipe/Swipe.test.ts =====\nimport Swipe from './Swipe.vue';\nimport AnimationWrapper from '../__test__/AnimationWrapper';\nimport { Directions } from '../../controllers/Player';\n\nvi.mock('../../components/FluxWrapper/FluxWrapper.vue');\n\ndescribe('transition: Swipe', () => {\n\tit('exposes onPlay and totalDuration', () => {\n\t\tconst wrapper = AnimationWrapper(Swipe, {});\n\n\t\tconst { onPlay, totalDuration } = wrapper.vm;\n\n\t\texpect(typeof onPlay).toBe('function');\n\t\texpect(typeof totalDuration).toBe('number');\n\t});\n\n\tit('performs the transition with default options', () => {\n\t\tconst wrapper = AnimationWrapper(Swipe, {});\n\n\t\tconst $wrapper = wrapper.getComponent({\n\t\t\tref: '$wrapper',\n\t\t});\n\n\t\texpect($wrapper.props('size')).toStrictEqual(wrapper.props('size'));\n\t\texpect($wrapper.props('css')).toStrictEqual({\n\t\t\tdisplay: 'flex',\n\t\t\tflexWrap: 'nowrap',\n\t\t\tjustifyContent: 'flex-start',\n\t\t\tleft: 0,\n\t\t\tposition: 'absolute',\n\t\t\ttop: 0,\n\t\t});\n\n\t\tconst $from = wrapper.getComponent({\n\t\t\tref: '$from',\n\t\t});\n\n\t\texpect($from.props('css')).toStrictEqual({\n\t\t\tflex: '0 0 auto',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($wrapper.vm.transform).toHaveBeenCalledWith({\n\t\t\ttransition: 'width 1400ms ease-in-out',\n\t\t\twidth: 0,\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1400);\n\t});\n\n\tit('performs the transition with custom options prev', () => {\n\t\tconst wrapper = AnimationWrapper(Swipe, {\n\t\t\tdirection: Directions.prev,\n\t\t\ttotalDuration: 800,\n\t\t\teasing: 'ease-out',\n\t\t});\n\n\t\tconst $wrapper = wrapper.getComponent({\n\t\t\tref: '$wrapper',\n\t\t});\n\n\t\texpect($wrapper.props('size')).toStrictEqual(wrapper.props('size'));\n\t\texpect($wrapper.props('css')).toStrictEqual({\n\t\t\tdisplay: 'flex',\n\t\t\tflexWrap: 'nowrap',\n\t\t\tjustifyContent: 'flex-end',\n\t\t\tright: 0,\n\t\t\tposition: 'absolute',\n\t\t\ttop: 0,\n\t\t});\n\n\t\tconst $from = wrapper.getComponent({\n\t\t\tref: '$from',\n\t\t});\n\n\t\texpect($from.props('css')).toStrictEqual({\n\t\t\tflex: '0 0 auto',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($wrapper.vm.transform).toHaveBeenCalledWith({\n\t\t\ttransition: 'width 800ms ease-out',\n\t\t\twidth: 0,\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(800);\n\t});\n\n\tit('performs the transition with custom options next', () => {\n\t\tconst wrapper = AnimationWrapper(Swipe, {\n\t\t\tdirection: Directions.next,\n\t\t\ttotalDuration: 800,\n\t\t\teasing: 'ease-out',\n\t\t});\n\n\t\tconst $wrapper = wrapper.getComponent({\n\t\t\tref: '$wrapper',\n\t\t});\n\n\t\texpect($wrapper.props('size')).toStrictEqual(wrapper.props('size'));\n\t\texpect($wrapper.props('css')).toStrictEqual({\n\t\t\tdisplay: 'flex',\n\t\t\tflexWrap: 'nowrap',\n\t\t\tjustifyContent: 'flex-start',\n\t\t\tleft: 0,\n\t\t\tposition: 'absolute',\n\t\t\ttop: 0,\n\t\t});\n\n\t\tconst $from = wrapper.getComponent({\n\t\t\tref: '$from',\n\t\t});\n\n\t\texpect($from.props('css')).toStrictEqual({\n\t\t\tflex: '0 0 auto',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($wrapper.vm.transform).toHaveBeenCalledWith({\n\t\t\ttransition: 'width 800ms ease-out',\n\t\t\twidth: 0,\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(800);\n\t});\n});\n\n\n===== FILE: src/transitions/Swipe/Swipe.vue =====\n<script setup lang=\"ts\">\n\timport { ref, reactive, type Ref, type CSSProperties } from 'vue';\n\timport useTransition from '../useTransition';\n\timport { type FluxComponent, FluxWrapper } from '../../components';\n\timport type { TransitionSwipeProps, TransitionSwipeConf } from './types';\n\timport { Directions } from '../../controllers/Player';\n\n\tconst props = defineProps<TransitionSwipeProps>();\n\n\tconst $wrapper: Ref<null | InstanceType<typeof FluxWrapper>> = ref(null);\n\tconst $from: Ref<null | FluxComponent> = ref(null);\n\n\tconst conf: TransitionSwipeConf = reactive({\n\t\ttotalDuration: 1400,\n\t\teasing: 'ease-in-out',\n\t});\n\n\tuseTransition(conf, props.options);\n\n\tconst wrapperCss: CSSProperties = {\n\t\tposition: 'absolute',\n\t\ttop: 0,\n\t\tdisplay: 'flex',\n\t\tflexWrap: 'nowrap',\n\t};\n\n\tconst fromCss: CSSProperties = {\n\t\tflex: '0 0 auto',\n\t};\n\n\tconst setup = {\n\t\t[Directions.prev]: () => {\n\t\t\tObject.assign(wrapperCss, {\n\t\t\t\tright: 0,\n\t\t\t\tjustifyContent: 'flex-end',\n\t\t\t});\n\t\t},\n\n\t\t[Directions.next]: () => {\n\t\t\tObject.assign(wrapperCss, {\n\t\t\t\tleft: 0,\n\t\t\t\tjustifyContent: 'flex-start',\n\t\t\t});\n\t\t},\n\t};\n\n\tsetup[conf.direction!]();\n\n\tconst onPlay = () => {\n\t\t$wrapper.value!.transform({\n\t\t\ttransition: `width ${conf.totalDuration}ms ${conf.easing}`,\n\t\t\twidth: 0,\n\t\t});\n\t};\n\n\tdefineExpose({\n\t\tonPlay,\n\t\ttotalDuration: conf.totalDuration,\n\t});\n</script>\n\n<template>\n\t<FluxWrapper ref=\"$wrapper\" :size=\"size\" :css=\"wrapperCss\">\n\t\t<component\n\t\t\t:is=\"from.transition.component\"\n\t\t\tref=\"$from\"\n\t\t\t:rsc=\"from\"\n\t\t\t:size=\"size\"\n\t\t\t:css=\"fromCss\"\n\t\t/>\n\t</FluxWrapper>\n</template>\n\n\n===== FILE: src/transitions/Swipe/types.ts =====\nimport type { TransitionConf, TransitionOptions, TransitionProps } from '../types';\n\nexport interface TransitionSwipeOptions extends TransitionOptions {\n\ttotalDuration?: number;\n}\n\nexport interface TransitionSwipeProps extends TransitionProps {\n\toptions?: TransitionSwipeOptions;\n}\n\nexport interface TransitionSwipeConf extends TransitionConf {\n\ttotalDuration: number;\n}\n\n\n===== FILE: src/transitions/Warp/Warp.test.ts =====\nimport Warp from './Warp.vue';\nimport AnimationWrapper from '../__test__/AnimationWrapper';\nimport { Directions } from '../../controllers/Player';\n\nvi.mock('../../components/FluxVortex/FluxVortex.vue');\n\ndescribe('transition: Warp', () => {\n\tit('exposes onPlay and totalDuration', () => {\n\t\tconst wrapper = AnimationWrapper(Warp, {});\n\n\t\tconst { onPlay, totalDuration } = wrapper.vm;\n\n\t\texpect(typeof onPlay).toBe('function');\n\t\texpect(typeof totalDuration).toBe('number');\n\t});\n\n\tit('performs the transition with default options', () => {\n\t\tconst wrapper = AnimationWrapper(Warp, {});\n\n\t\tconst $vortex = wrapper.getComponent({\n\t\t\tref: '$vortex',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($vortex.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $vortex.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'rotateZ(-90deg)',\n\t\t\ttransition: 'all 800ms linear 0ms',\n\t\t});\n\n\t\texpect($tiles[6].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'rotateZ(-90deg)',\n\t\t\ttransition: 'all 800ms linear 900ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1850);\n\t});\n\n\tit('performs the transition with custom options prev', () => {\n\t\tconst wrapper = AnimationWrapper(Warp, {\n\t\t\tdirection: Directions.prev,\n\t\t\tcircles: 10,\n\t\t\ttileDuration: 400,\n\t\t\ttileDelay: 60,\n\t\t\teasing: 'ease-out',\n\t\t});\n\n\t\tconst $vortex = wrapper.getComponent({\n\t\t\tref: '$vortex',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($vortex.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $vortex.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'rotateZ(-90deg)',\n\t\t\ttransition: 'all 400ms ease-out 540ms',\n\t\t});\n\n\t\texpect($tiles[6].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'rotateZ(-90deg)',\n\t\t\ttransition: 'all 400ms ease-out 180ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1000);\n\t});\n\n\tit('performs the transition with custom options next', () => {\n\t\tconst wrapper = AnimationWrapper(Warp, {\n\t\t\tdirection: Directions.next,\n\t\t\tcircles: 10,\n\t\t\ttileDuration: 400,\n\t\t\ttileDelay: 60,\n\t\t\teasing: 'ease-out',\n\t\t});\n\n\t\tconst $vortex = wrapper.getComponent({\n\t\t\tref: '$vortex',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($vortex.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $vortex.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'rotateZ(-90deg)',\n\t\t\ttransition: 'all 400ms ease-out 0ms',\n\t\t});\n\n\t\texpect($tiles[6].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'rotateZ(-90deg)',\n\t\t\ttransition: 'all 400ms ease-out 360ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1000);\n\t});\n});\n\n\n===== FILE: src/transitions/Warp/Warp.vue =====\n<script setup lang=\"ts\">\n\timport { ref, reactive, type Ref } from 'vue';\n\timport useTransition from '../useTransition';\n\timport { type FluxComponent, FluxVortex } from '../../components';\n\timport type { TransitionWarpProps, TransitionWarpConf } from './types';\n\timport { Directions } from '../../controllers/Player';\n\n\tconst props = defineProps<TransitionWarpProps>();\n\n\tconst $vortex: Ref<null | InstanceType<typeof FluxVortex>> = ref(null);\n\n\tconst conf: TransitionWarpConf = reactive({\n\t\tcircles: 7,\n\t\ttileDuration: 800,\n\t\ttileDelay: 150,\n\t\teasing: 'linear',\n\t});\n\n\tuseTransition(conf, props.options);\n\n\tconst totalDuration = conf.tileDelay * conf.circles + conf.tileDuration;\n\n\tconst getDelay = {\n\t\t[Directions.prev]: (index: number) => (conf.circles - index - 1) * conf.tileDelay,\n\t\t[Directions.next]: (index: number) => index * conf.tileDelay,\n\t};\n\n\tconst getDeg = (index: number) => (index % 2 === 0 ? '-90' : '90');\n\n\tconst onPlay = () => {\n\t\t$vortex.value!.transform((tile: FluxComponent, index: number) => {\n\t\t\tconst transition = `all ${conf.tileDuration}ms ${\n\t\t\t\tconf.easing\n\t\t\t} ${getDelay[conf.direction!](index)}ms`;\n\n\t\t\ttile.transform({\n\t\t\t\ttransition,\n\t\t\t\topacity: '0',\n\t\t\t\ttransform: `rotateZ(${getDeg(index)}deg)`,\n\t\t\t});\n\t\t});\n\t};\n\n\tdefineExpose({\n\t\tonPlay,\n\t\ttotalDuration,\n\t});\n</script>\n\n<template>\n\t<FluxVortex ref=\"$vortex\" :size=\"size\" :circles=\"conf.circles\" :rsc=\"from\" />\n</template>\n\n\n===== FILE: src/transitions/Warp/types.ts =====\nimport type { TransitionConf, TransitionOptions, TransitionProps } from '../types';\n\nexport interface TransitionWarpOptions extends TransitionOptions {\n\tcircles?: number;\n\ttileDuration?: number;\n\ttileDelay?: number;\n}\n\nexport interface TransitionWarpProps extends TransitionProps {\n\toptions?: TransitionWarpOptions;\n}\n\nexport interface TransitionWarpConf extends TransitionConf {\n\tcircles: number;\n\ttileDuration: number;\n\ttileDelay: number;\n}\n\n\n===== FILE: src/transitions/Waterfall/Waterfall.test.ts =====\nimport Waterfall from './Waterfall.vue';\nimport AnimationWrapper from '../__test__/AnimationWrapper';\nimport { Directions } from '../../controllers/Player';\n\nvi.mock('../../components/FluxGrid/FluxGrid.vue');\n\ndescribe('transition: Waterfall', () => {\n\tit('exposes onPlay and totalDuration', () => {\n\t\tconst wrapper = AnimationWrapper(Waterfall, {});\n\n\t\tconst { onPlay, totalDuration } = wrapper.vm;\n\n\t\texpect(typeof onPlay).toBe('function');\n\t\texpect(typeof totalDuration).toBe('number');\n\t});\n\n\tit('performs the transition with default options', () => {\n\t\tconst wrapper = AnimationWrapper(Waterfall, {});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0.1',\n\t\t\ttransform: 'translateY(100%)',\n\t\t\ttransition: 'all 600ms cubic-bezier(0.55, 0.055, 0.675, 0.19) 0ms',\n\t\t});\n\n\t\texpect($tiles[9].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0.1',\n\t\t\ttransform: 'translateY(100%)',\n\t\t\ttransition: 'all 600ms cubic-bezier(0.55, 0.055, 0.675, 0.19) 810ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1500);\n\t});\n\n\tit('performs the transition with custom options prev', () => {\n\t\tconst wrapper = AnimationWrapper(Waterfall, {\n\t\t\tdirection: Directions.prev,\n\t\t\tcols: 6,\n\t\t\ttileDuration: 400,\n\t\t\ttileDelay: 60,\n\t\t\teasing: 'ease-out',\n\t\t});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0.1',\n\t\t\ttransform: 'translateY(100%)',\n\t\t\ttransition: 'all 400ms ease-out 300ms',\n\t\t});\n\n\t\texpect($tiles[5].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0.1',\n\t\t\ttransform: 'translateY(100%)',\n\t\t\ttransition: 'all 400ms ease-out 0ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(760);\n\t});\n\n\tit('performs the transition with custom options next', () => {\n\t\tconst wrapper = AnimationWrapper(Waterfall, {\n\t\t\tdirection: Directions.next,\n\t\t\tcols: 6,\n\t\t\ttileDuration: 400,\n\t\t\ttileDelay: 60,\n\t\t\teasing: 'ease-out',\n\t\t});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0.1',\n\t\t\ttransform: 'translateY(100%)',\n\t\t\ttransition: 'all 400ms ease-out 0ms',\n\t\t});\n\n\t\texpect($tiles[5].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0.1',\n\t\t\ttransform: 'translateY(100%)',\n\t\t\ttransition: 'all 400ms ease-out 300ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(760);\n\t});\n});\n\n\n===== FILE: src/transitions/Waterfall/Waterfall.vue =====\n<script setup lang=\"ts\">\n\timport { ref, reactive, type Ref } from 'vue';\n\timport useTransition from '../useTransition';\n\timport { type FluxComponent, FluxGrid } from '../../components';\n\timport type { TransitionWaterfallProps, TransitionWaterfallConf } from './types';\n\timport { Directions } from '../../controllers/Player';\n\n\tconst props = defineProps<TransitionWaterfallProps>();\n\n\tconst $grid: Ref<null | InstanceType<typeof FluxGrid>> = ref(null);\n\n\tconst conf: TransitionWaterfallConf = reactive({\n\t\trows: 1,\n\t\tcols: 10,\n\t\ttileDuration: 600,\n\t\ttileDelay: 90,\n\t\teasing: 'cubic-bezier(0.55, 0.055, 0.675, 0.19)',\n\t});\n\n\tuseTransition(conf, props.options);\n\n\tconst totalDuration = conf.tileDelay * conf.cols + conf.tileDuration;\n\n\tconst getDelay = {\n\t\t[Directions.prev]: (index: number) => (conf.cols - index - 1) * conf.tileDelay,\n\t\t[Directions.next]: (index: number) => index * conf.tileDelay,\n\t};\n\n\tconst onPlay = () => {\n\t\t$grid.value!.transform((tile: FluxComponent, index: number) => {\n\t\t\tconst transition = `all ${conf.tileDuration}ms ${\n\t\t\t\tconf.easing\n\t\t\t} ${getDelay[conf.direction!](index)}ms`;\n\n\t\t\ttile.transform({\n\t\t\t\ttransition,\n\t\t\t\topacity: '0.1',\n\t\t\t\ttransform: `translateY(100%)`,\n\t\t\t});\n\t\t});\n\t};\n\n\tdefineExpose({\n\t\tonPlay,\n\t\ttotalDuration,\n\t});\n</script>\n\n<template>\n\t<FluxGrid ref=\"$grid\" :rows=\"conf.rows\" :cols=\"conf.cols\" :size=\"size\" :rsc=\"from\" />\n</template>\n\n\n===== FILE: src/transitions/Waterfall/types.ts =====\nimport type { TransitionConf, TransitionOptions, TransitionProps } from '../types';\n\nexport interface TransitionWaterfallOptions extends TransitionOptions {\n\tcols?: number;\n\ttileDuration?: number;\n\ttileDelay?: number;\n}\n\nexport interface TransitionWaterfallProps extends TransitionProps {\n\toptions?: TransitionWaterfallOptions;\n}\n\nexport interface TransitionWaterfallConf extends TransitionConf {\n\trows: number;\n\tcols: number;\n\ttileDuration: number;\n\ttileDelay: number;\n}\n\n\n===== FILE: src/transitions/Wave/Wave.test.ts =====\nimport Wave from './Wave.vue';\nimport AnimationWrapper from '../__test__/AnimationWrapper';\nimport { Directions } from '../../controllers';\nimport { Turns } from '../../components/FluxCube';\n\nvi.mock('../../components/FluxGrid/FluxGrid.vue');\n\ndescribe('transition: Wave', () => {\n\tit('exposes onPlay and totalDuration', () => {\n\t\tconst wrapper = AnimationWrapper(Wave, {});\n\n\t\tconst { onPlay, totalDuration } = wrapper.vm;\n\n\t\texpect(typeof onPlay).toBe('function');\n\t\texpect(typeof totalDuration).toBe('number');\n\t});\n\n\tit('expects to set proper CSS styles before animation', () => {\n\t\tconst wrapper = AnimationWrapper(Wave, {});\n\n\t\tconst maskStyle = wrapper.props('maskStyle');\n\t\texpect(maskStyle.overflow).toBe('visible');\n\n\t\tconst gridCss = wrapper\n\t\t\t.getComponent({\n\t\t\t\tref: '$grid',\n\t\t\t})\n\t\t\t.props('css');\n\t\texpect(gridCss.perspective).toBeDefined();\n\t});\n\n\tit('performs the transition with default options', () => {\n\t\tconst wrapper = AnimationWrapper(Wave, {});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].setCss).toHaveBeenCalledWith({\n\t\t\ttransition: 'all 900ms cubic-bezier(0.3, -0.3, 0.735, 0.285) 0ms',\n\t\t});\n\t\texpect($tiles[0].turn).toHaveBeenCalledWith(Turns.bottom);\n\n\t\texpect($tiles[7].setCss).toHaveBeenCalledWith({\n\t\t\ttransition: 'all 900ms cubic-bezier(0.3, -0.3, 0.735, 0.285) 770ms',\n\t\t});\n\t\texpect($tiles[7].turn).toHaveBeenCalledWith(Turns.bottom);\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1780);\n\t});\n\n\tit('performs the transition with custom options prev', () => {\n\t\tconst wrapper = AnimationWrapper(Wave, {\n\t\t\tdirection: Directions.prev,\n\t\t\tcols: 6,\n\t\t\ttileDuration: 400,\n\t\t\ttileDelay: 60,\n\t\t\tsideColor: '#999',\n\t\t\teasing: 'ease-out',\n\t\t});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].setCss).toHaveBeenCalledWith({\n\t\t\ttransition: 'all 400ms ease-out 300ms',\n\t\t});\n\t\texpect($tiles[0].turn).toHaveBeenCalledWith(Turns.bottom);\n\n\t\texpect($tiles[5].setCss).toHaveBeenCalledWith({\n\t\t\ttransition: 'all 400ms ease-out 0ms',\n\t\t});\n\t\texpect($tiles[5].turn).toHaveBeenCalledWith(Turns.bottom);\n\n\t\texpect(wrapper.vm.totalDuration).toBe(760);\n\t});\n\n\tit('performs the transition with custom options next', () => {\n\t\tconst wrapper = AnimationWrapper(Wave, {\n\t\t\tdirection: Directions.next,\n\t\t\tcols: 6,\n\t\t\ttileDuration: 400,\n\t\t\ttileDelay: 60,\n\t\t\teasing: 'ease-out',\n\t\t});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].setCss).toHaveBeenCalledWith({\n\t\t\ttransition: 'all 400ms ease-out 0ms',\n\t\t});\n\t\texpect($tiles[0].turn).toHaveBeenCalledWith(Turns.bottom);\n\n\t\texpect($tiles[5].setCss).toHaveBeenCalledWith({\n\t\t\ttransition: 'all 400ms ease-out 300ms',\n\t\t});\n\t\texpect($tiles[5].turn).toHaveBeenCalledWith(Turns.bottom);\n\n\t\texpect(wrapper.vm.totalDuration).toBe(760);\n\t});\n});\n\n\n===== FILE: src/transitions/Wave/Wave.vue =====\n<script setup lang=\"ts\">\n\timport { ref, reactive, type Ref, type CSSProperties } from 'vue';\n\timport useTransition from '../useTransition';\n\timport { FluxGrid, FluxCube } from '../../components';\n\timport type { TransitionWaveProps, TransitionWaveConf } from './types';\n\timport { Directions } from '../../controllers/Player';\n\timport { Turns } from '../../components/FluxCube';\n\n\tconst props = defineProps<TransitionWaveProps>();\n\n\tconst $grid: Ref<null | InstanceType<typeof FluxGrid>> = ref(null);\n\n\tconst conf: TransitionWaveConf = reactive({\n\t\trows: 1,\n\t\tcols: 8,\n\t\ttileDuration: 900,\n\t\ttileDelay: 110,\n\t\tsideColor: '#333',\n\t\teasing: 'cubic-bezier(0.3, -0.3, 0.735, 0.285)',\n\t});\n\n\tuseTransition(conf, props.options);\n\n\t// eslint-disable-next-line vue/no-mutating-props\n\tprops.maskStyle.overflow = 'visible';\n\n\tconst rscs = {\n\t\tfront: props.from,\n\t\ttop: props.to,\n\t};\n\n\tconst colors = {\n\t\tleft: conf.sideColor,\n\t\tright: conf.sideColor,\n\t};\n\n\tconst gridCss: CSSProperties = {\n\t\tperspective: '1200px',\n\t};\n\n\tconst totalDuration = conf.tileDelay * conf.cols + conf.tileDuration;\n\n\tconst getDelay = {\n\t\t[Directions.prev]: (index: number) => (conf.cols - index - 1) * conf.tileDelay,\n\t\t[Directions.next]: (index: number) => index * conf.tileDelay,\n\t};\n\n\tconst onPlay = () => {\n\t\tif (props.displayComponent) {\n\t\t\tprops.displayComponent.hide();\n\t\t}\n\n\t\ttype FluxCubeType = InstanceType<typeof FluxCube>;\n\n\t\t$grid.value!.transform<FluxCubeType>((tile: FluxCubeType, index: number) => {\n\t\t\tconst transition = `all ${conf.tileDuration}ms ${\n\t\t\t\tconf.easing\n\t\t\t} ${getDelay[conf.direction!](index)}ms`;\n\n\t\t\ttile.setCss({\n\t\t\t\ttransition,\n\t\t\t});\n\n\t\t\ttile.turn(Turns.bottom);\n\t\t});\n\t};\n\n\tdefineExpose({\n\t\tonPlay,\n\t\ttotalDuration,\n\t});\n</script>\n\n<template>\n\t<FluxGrid\n\t\tref=\"$grid\"\n\t\t:rows=\"conf.rows\"\n\t\t:cols=\"conf.cols\"\n\t\t:size=\"size\"\n\t\t:rscs=\"rscs\"\n\t\t:colors=\"colors\"\n\t\t:depth=\"size.height.value!\"\n\t\t:css=\"gridCss\"\n\t/>\n</template>\n\n\n===== FILE: src/transitions/Wave/types.ts =====\nimport type { CSSProperties } from 'vue';\nimport type { TransitionConf, TransitionOptions, TransitionProps } from '../types';\n\nexport interface TransitionWaveOptions extends TransitionOptions {\n\tcols?: number;\n\ttileDuration?: number;\n\ttileDelay?: number;\n\tsideColor?: CSSProperties['color'];\n}\n\nexport interface TransitionWaveProps extends TransitionProps {\n\toptions?: TransitionWaveOptions;\n}\n\nexport interface TransitionWaveConf extends TransitionConf {\n\trows: number;\n\tcols: number;\n\ttileDuration: number;\n\ttileDelay: number;\n\tsideColor: CSSProperties['color'];\n}\n\n\n===== FILE: src/transitions/Zip/Zip.test.ts =====\nimport Zip from './Zip.vue';\nimport AnimationWrapper from '../__test__/AnimationWrapper';\nimport { Directions } from '../../controllers/Player';\n\nvi.mock('../../components/FluxGrid/FluxGrid.vue');\n\ndescribe('transition: Zip', () => {\n\tit('exposes onPlay and totalDuration', () => {\n\t\tconst wrapper = AnimationWrapper(Zip, {});\n\n\t\tconst { onPlay, totalDuration } = wrapper.vm;\n\n\t\texpect(typeof onPlay).toBe('function');\n\t\texpect(typeof totalDuration).toBe('number');\n\t});\n\n\tit('performs the transition with default options', () => {\n\t\tconst wrapper = AnimationWrapper(Zip, {});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0.1',\n\t\t\ttransform: 'translateY(100%)',\n\t\t\ttransition: 'all 600ms ease-in 0ms',\n\t\t});\n\n\t\texpect($tiles[9].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0.1',\n\t\t\ttransform: 'translateY(-100%)',\n\t\t\ttransition: 'all 600ms ease-in 720ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1400);\n\t});\n\n\tit('performs the transition with custom options prev', () => {\n\t\tconst wrapper = AnimationWrapper(Zip, {\n\t\t\tdirection: Directions.prev,\n\t\t\tcols: 6,\n\t\t\ttileDuration: 400,\n\t\t\ttileDelay: 60,\n\t\t\teasing: 'ease-out',\n\t\t});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0.1',\n\t\t\ttransform: 'translateY(100%)',\n\t\t\ttransition: 'all 400ms ease-out 300ms',\n\t\t});\n\n\t\texpect($tiles[5].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0.1',\n\t\t\ttransform: 'translateY(-100%)',\n\t\t\ttransition: 'all 400ms ease-out 0ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(760);\n\t});\n\n\tit('performs the transition with custom options next', () => {\n\t\tconst wrapper = AnimationWrapper(Zip, {\n\t\t\tdirection: Directions.next,\n\t\t\tcols: 6,\n\t\t\ttileDuration: 400,\n\t\t\ttileDelay: 60,\n\t\t\teasing: 'ease-out',\n\t\t});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0.1',\n\t\t\ttransform: 'translateY(100%)',\n\t\t\ttransition: 'all 400ms ease-out 0ms',\n\t\t});\n\n\t\texpect($tiles[5].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0.1',\n\t\t\ttransform: 'translateY(-100%)',\n\t\t\ttransition: 'all 400ms ease-out 300ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(760);\n\t});\n});\n\n\n===== FILE: src/transitions/Zip/Zip.vue =====\n<script setup lang=\"ts\">\n\timport { ref, reactive, type Ref } from 'vue';\n\timport useTransition from '../useTransition';\n\timport { type FluxComponent, FluxGrid } from '../../components';\n\timport type { TransitionZipProps, TransitionZipConf } from './types';\n\timport { Directions } from '../../controllers/Player';\n\n\tconst props = defineProps<TransitionZipProps>();\n\n\tconst $grid: Ref<null | InstanceType<typeof FluxGrid>> = ref(null);\n\n\tconst conf: TransitionZipConf = reactive({\n\t\trows: 1,\n\t\tcols: 10,\n\t\ttileDuration: 600,\n\t\ttileDelay: 80,\n\t\teasing: 'ease-in',\n\t});\n\n\tuseTransition(conf, props.options);\n\n\tconst totalDuration = conf.tileDelay * conf.cols + conf.tileDuration;\n\n\tconst getDelay = {\n\t\t[Directions.prev]: (index: number) => (conf.cols - index - 1) * conf.tileDelay,\n\t\t[Directions.next]: (index: number) => index * conf.tileDelay,\n\t};\n\n\tconst onPlay = () => {\n\t\t$grid.value!.transform((tile: FluxComponent, index: number) => {\n\t\t\tconst transition = `all ${conf.tileDuration}ms ${\n\t\t\t\tconf.easing\n\t\t\t} ${getDelay[conf.direction!](index)}ms`;\n\n\t\t\ttile.transform({\n\t\t\t\ttransition,\n\t\t\t\topacity: '0.1',\n\t\t\t\ttransform: `translateY(${index % 2 ? '-' : ''}100%)`,\n\t\t\t});\n\t\t});\n\t};\n\n\tdefineExpose({\n\t\tonPlay,\n\t\ttotalDuration,\n\t});\n</script>\n\n<template>\n\t<FluxGrid ref=\"$grid\" :rows=\"conf.rows\" :cols=\"conf.cols\" :size=\"size\" :rsc=\"from\" />\n</template>\n\n\n===== FILE: src/transitions/Zip/types.ts =====\nimport type { TransitionConf, TransitionOptions, TransitionProps } from '../types';\n\nexport interface TransitionZipOptions extends TransitionOptions {\n\tcols?: number;\n\ttileDuration?: number;\n\ttileDelay?: number;\n}\n\nexport interface TransitionZipProps extends TransitionProps {\n\toptions?: TransitionZipOptions;\n}\n\nexport interface TransitionZipConf extends TransitionConf {\n\trows: number;\n\tcols: number;\n\ttileDuration: number;\n\ttileDelay: number;\n}\n\n\n===== FILE: src/transitions/__test__/AnimationWrapper.ts =====\nimport { mount } from '@vue/test-utils';\nimport { type Component, markRaw } from 'vue';\nimport { type FluxComponent, FluxImage } from '../../components';\nimport { Img } from '../../resources';\nimport { Size } from '../../shared';\n\nconst size = markRaw(\n\tnew Size({\n\t\twidth: 640,\n\t\theight: 360,\n\t}),\n);\n\nconst from = new Img('from');\nconst to = new Img('to');\n\nconst maskStyle = {\n\toverflow: 'hidden',\n\tperspective: 'none',\n\tzIndex: 3,\n};\n\nconst displayComponent = mount(markRaw(FluxImage), {\n\tprops: {\n\t\tcolor: '#ccc',\n\t\tsize: size,\n\t},\n});\n\nexport default (component: Component, options: object = {}) => {\n\treturn mount(component, {\n\t\tprops: {\n\t\t\tsize,\n\t\t\tfrom,\n\t\t\tto,\n\t\t\toptions,\n\t\t\tmaskStyle,\n\t\t\tdisplayComponent: displayComponent.vm as FluxComponent,\n\t\t},\n\t});\n};\n\n\n===== FILE: src/transitions/index.ts =====\nexport { default as Fade } from './Fade/Fade.vue';\nexport { default as Kenburn } from './Kenburn/Kenburn.vue';\nexport { default as Swipe } from './Swipe/Swipe.vue';\nexport { default as Slide } from './Slide/Slide.vue';\nexport { default as Waterfall } from './Waterfall/Waterfall.vue';\nexport { default as Zip } from './Zip/Zip.vue';\nexport { default as Blinds2D } from './Blinds2D/Blinds2D.vue';\nexport { default as Blocks1 } from './Blocks1/Blocks1.vue';\nexport { default as Blocks2 } from './Blocks2/Blocks2.vue';\nexport { default as Concentric } from './Concentric/Concentric.vue';\nexport { default as Warp } from './Warp/Warp.vue';\nexport { default as Camera } from './Camera/Camera.vue';\nexport { default as Cube } from './Cube/Cube.vue';\nexport { default as Book } from './Book/Book.vue';\nexport { default as Fall } from './Fall/Fall.vue';\nexport { default as Wave } from './Wave/Wave.vue';\nexport { default as Blinds3D } from './Blinds3D/Blinds3D.vue';\nexport { default as Round1 } from './Round1/Round1.vue';\nexport { default as Round2 } from './Round2/Round2.vue';\nexport { default as Explode } from './Explode/Explode.vue';\n\nexport { default as useTransition } from './useTransition';\n\nexport type * from './types';\nexport type * from './Blinds2D/types';\nexport type * from './Blinds3D/types';\nexport type * from './Blocks1/types';\nexport type * from './Blocks2/types';\nexport type * from './Book/types';\nexport type * from './Camera/types';\nexport type * from './Concentric/types';\nexport type * from './Cube/types';\nexport type * from './Explode/types';\nexport type * from './Fade/types';\nexport type * from './Fall/types';\nexport type * from './Kenburn/types';\nexport type * from './Round1/types';\nexport type * from './Round2/types';\nexport type * from './Slide/types';\nexport type * from './Swipe/types';\nexport type * from './Warp/types';\nexport type * from './Waterfall/types';\nexport type * from './Wave/types';\nexport type * from './Zip/types';\n\n\n===== FILE: src/transitions/types.ts =====\nimport { Resource } from '../resources';\nimport type { CSSProperties, Component } from 'vue';\nimport type { Direction } from '../controllers/Player';\nimport { Size } from '../shared';\nimport type { FluxComponent } from '../components';\n\nexport interface TransitionProps {\n\tsize: Size;\n\tfrom: Resource;\n\tto?: Resource;\n\toptions?: object;\n\tmaskStyle: CSSProperties;\n\tdisplayComponent: FluxComponent;\n}\n\nexport interface TransitionOptions {\n\tdirection?: Direction;\n\teasing?: CSSProperties['animation-timing-function'];\n}\n\nexport interface TransitionConf {\n\ttotalDuration?: number;\n\tdirection?: Direction;\n\teasing: CSSProperties['animation-timing-function'];\n}\n\nexport type TransitionComponent = Component & {\n\ttotalDuration: number;\n\tonPlay: () => void;\n};\n\nexport interface TransitionWithOptions {\n\tcomponent: Component;\n\toptions: object;\n}\n\n\n===== FILE: src/transitions/useTransition.ts =====\nimport { Directions } from '../controllers/Player';\nimport type { TransitionConf } from './types';\n\nexport default function useTransition(conf: TransitionConf, options?: object) {\n\tObject.assign(conf, { direction: Directions.next }, options);\n}\n\n\n"
  },
  {
    "path": "index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"UTF-8\" />\n\t\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n\t\t<title>Vite App</title>\n\t</head>\n\t<body>\n\t\t<div id=\"app\"></div>\n\t\t<script type=\"module\" src=\"/src/main.ts\"></script>\n\t</body>\n</html>\n"
  },
  {
    "path": "package.json",
    "content": "{\n\t\"name\": \"vue-flux\",\n\t\"version\": \"7.1.3\",\n\t\"type\": \"module\",\n\t\"description\": \"Vue image and other resources slider\",\n\t\"author\": \"ragnar lotus\",\n\t\"repository\": {\n\t\t\"type\": \"git\",\n\t\t\"url\": \"git+https://github.com/ragnarlotus/vue-flux.git\"\n\t},\n\t\"keywords\": [\n\t\t\"vue\",\n\t\t\"image\",\n\t\t\"slider\",\n\t\t\"carousel\",\n\t\t\"parallax\"\n\t],\n\t\"license\": \"MIT\",\n\t\"bugs\": \"https://github.com/ragnarlotus/vue-flux/issues\",\n\t\"homepage\": \"https://ragnarlotus.github.io/vue-flux-docs/\",\n\t\"main\": \"./dist/vue-flux.umd.cjs\",\n\t\"module\": \"./dist/vue-flux.js\",\n\t\"files\": [\n\t\t\"dist\"\n\t],\n\t\"types\": \"./dist/vue-flux.d.ts\",\n\t\"engines\": {\n\t\t\"node\": \"^20.19.0 || >=22.12.0\"\n\t},\n\t\"scripts\": {\n\t\t\"dev\": \"vite\",\n\t\t\"build\": \"run-p type-check \\\"build-only {@}\\\" --\",\n\t\t\"preview\": \"vite preview\",\n\t\t\"test:coverage\": \"vitest run --coverage --watch\",\n\t\t\"test:unit\": \"vitest\",\n\t\t\"build-only\": \"vite build\",\n\t\t\"type-check\": \"vue-tsc --build\",\n\t\t\"lint\": \"eslint . --fix\",\n\t\t\"format\": \"prettier --write src/\"\n\t},\n\t\"exports\": {\n\t\t\".\": {\n\t\t\t\"types\": \"./dist/vue-flux.d.ts\",\n\t\t\t\"import\": \"./dist/vue-flux.js\",\n\t\t\t\"require\": \"./dist/vue-flux.umd.cjs\"\n\t\t},\n\t\t\"./style.css\": \"./dist/vue-flux.css\",\n\t\t\"./complements\": {\n\t\t\t\"types\": \"./dist/complements/index.d.ts\",\n\t\t\t\"import\": \"./dist/complements/index.js\"\n\t\t},\n\t\t\"./transitions\": {\n\t\t\t\"types\": \"./dist/transitions/index.d.ts\",\n\t\t\t\"import\": \"./dist/transitions/index.js\"\n\t\t}\n\t},\n\t\"sideEffects\": [\n\t\t\"*.css\"\n\t],\n\t\"peerDependencies\": {\n\t\t\"vue\": \"^3.5.0\"\n\t},\n\t\"devDependencies\": {\n\t\t\"@tailwindcss/vite\": \"^4.1.17\",\n\t\t\"@tsconfig/node22\": \"^22.0.5\",\n\t\t\"@types/jsdom\": \"^27.0.0\",\n\t\t\"@types/node\": \"^25.0.0\",\n\t\t\"@vitejs/plugin-vue\": \"^6.0.2\",\n\t\t\"@vitest/coverage-v8\": \"^4.0.15\",\n\t\t\"@vitest/eslint-plugin\": \"^1.5.2\",\n\t\t\"@vue/eslint-config-prettier\": \"^10.2.0\",\n\t\t\"@vue/eslint-config-typescript\": \"^14.6.0\",\n\t\t\"@vue/test-utils\": \"^2.4.6\",\n\t\t\"@vue/tsconfig\": \"^0.8.1\",\n\t\t\"eslint\": \"^9.39.1\",\n\t\t\"eslint-plugin-vue\": \"~10.6.2\",\n\t\t\"jiti\": \"^2.6.1\",\n\t\t\"jsdom\": \"^27.3.0\",\n\t\t\"npm-run-all2\": \"^8.0.4\",\n\t\t\"prettier\": \"3.7.4\",\n\t\t\"sass\": \"^1.96.0\",\n\t\t\"tailwindcss\": \"^4.1.17\",\n\t\t\"typescript\": \"~5.9.3\",\n\t\t\"vite\": \"^7.2.7\",\n\t\t\"vite-plugin-dts\": \"^4.5.4\",\n\t\t\"vite-plugin-vue-devtools\": \"^8.0.5\",\n\t\t\"vitest\": \"^4.0.15\",\n\t\t\"vue\": \"^3.5.25\",\n\t\t\"vue-cosk\": \"^1.0.0\",\n\t\t\"vue-tsc\": \"^3.1.8\"\n\t}\n}\n"
  },
  {
    "path": "src/App.vue",
    "content": "<!-- eslint-disable @typescript-eslint/no-unused-vars -->\n<script setup lang=\"ts\">\n\timport { ref, type Ref } from 'vue';\n\n\t// Playgrounds\n\timport PgFluxImage from './playgrounds/PgFluxImage.vue';\n\timport PgFluxCube from './playgrounds/PgFluxCube.vue';\n\timport PgFluxGrid from './playgrounds/PgFluxGrid.vue';\n\timport PgFluxTransition from './playgrounds/PgFluxTransition.vue';\n\timport PgVueFlux from './playgrounds/PgVueFlux.vue';\n\timport PgFluxParallax from './playgrounds/PgFluxParallax.vue';\n\timport PgFluxParallaxOp from './playgrounds/PgFluxParallaxOp.vue';\n\timport PgFluxCaption from './playgrounds/PgFluxCaption.vue';\n\timport PgFluxControls from './playgrounds/PgFluxControls.vue';\n\timport PgFluxIndex from './playgrounds/PgFluxIndex.vue';\n\timport PgFluxPagination from './playgrounds/PgFluxPagination.vue';\n\timport PgFluxPreloader from './playgrounds/PgFluxPreloader.vue';\n\n\tconst $wrapper: Ref<null | HTMLDivElement> = ref(null);\n</script>\n\n<template>\n\t<main class=\"container mx-auto mb-4\">\n\t\t<div ref=\"$wrapper\" class=\"relative mx-auto\">\n\t\t\t<!-- <PgFluxImage /> -->\n\t\t\t<!-- <PgFluxCube /> -->\n\t\t\t<!-- <PgFluxGrid /> -->\n\t\t\t<!-- <PgFluxTransition /> -->\n\t\t\t<!-- <PgVueFlux /> -->\n\t\t\t<!-- <PgFluxParallax /> -->\n\t\t\t<PgFluxParallaxOp />\n\t\t\t<!-- <PgFluxCaption /> -->\n\t\t\t<!-- <PgFluxControls /> -->\n\t\t\t<!-- <PgFluxIndex /> -->\n\t\t\t<!-- <PgFluxPagination /> -->\n\t\t\t<!-- <PgFluxPreloader /> -->\n\t\t</div>\n\t</main>\n</template>\n"
  },
  {
    "path": "src/assets/css/base.scss",
    "content": "label {\r\n\tmargin-top: 12px;\r\n\tdisplay: block;\r\n\r\n\tspan {\r\n\t\tmargin-right: 6px;\r\n\t}\r\n}\r\n"
  },
  {
    "path": "src/assets/css/main.css",
    "content": "@import 'tailwindcss';\n@import './base.scss';\n"
  },
  {
    "path": "src/complements/FluxCaption/FluxCaption.test.ts",
    "content": "import { Player, Timers } from '../../controllers';\nimport { mount } from '@vue/test-utils';\nimport FluxCaption from './FluxCaption.vue';\nimport emit from '../../components/VueFlux/__test__/emit';\nimport {\n\tvueFluxConfig,\n\tsetCurrentResource,\n\tsetCurrentTransition,\n} from '../__test__/PlayerHelper';\n\nvi.mock('../../controllers/Player/Player');\n\nconst defaultCaption = 'the caption';\n\ndescribe('complements: FluxCaption', () => {\n\tconst timers = new Timers();\n\n\tit('should mount properly without slot', () => {\n\t\tconst player = new Player(vueFluxConfig, timers, emit);\n\n\t\texpect(() => {\n\t\t\tmount(FluxCaption, {\n\t\t\t\tprops: {\n\t\t\t\t\tplayer,\n\t\t\t\t},\n\t\t\t});\n\t\t}).not.toThrow();\n\t});\n\n\tit('should not be visible if no caption', () => {\n\t\tconst player = new Player(vueFluxConfig, timers, emit);\n\n\t\tsetCurrentResource(player);\n\n\t\tconst wrapper = mount(FluxCaption, {\n\t\t\tprops: {\n\t\t\t\tplayer,\n\t\t\t},\n\t\t});\n\n\t\texpect(wrapper.html().includes('class=\"flux-caption\"')).toBeTruthy();\n\t});\n\n\tit('should not be visible if caption has no length', () => {\n\t\tconst player = new Player(vueFluxConfig, timers, emit);\n\n\t\tsetCurrentResource(player, '');\n\n\t\tconst wrapper = mount(FluxCaption, {\n\t\t\tprops: {\n\t\t\t\tplayer,\n\t\t\t},\n\t\t});\n\n\t\texpect(wrapper.html().includes('class=\"flux-caption\"')).toBeTruthy();\n\t});\n\n\tit('should not be visible if transition running', () => {\n\t\tconst player = new Player(vueFluxConfig, timers, emit);\n\n\t\tsetCurrentResource(player, defaultCaption);\n\t\tsetCurrentTransition(player);\n\n\t\tconst wrapper = mount(FluxCaption, {\n\t\t\tprops: {\n\t\t\t\tplayer,\n\t\t\t},\n\t\t});\n\n\t\texpect(wrapper.html().includes('class=\"flux-caption\"')).toBeTruthy();\n\t});\n\n\tit('should display the caption', () => {\n\t\tconst player = new Player(vueFluxConfig, timers, emit);\n\n\t\tsetCurrentResource(player, defaultCaption);\n\n\t\tconst wrapper = mount(FluxCaption, {\n\t\t\tprops: {\n\t\t\t\tplayer,\n\t\t\t},\n\t\t});\n\n\t\texpect(\n\t\t\twrapper.html().includes('class=\"flux-caption visible\"')\n\t\t).toBeTruthy();\n\t});\n\n\tit('should mount properly with slot', () => {\n\t\tconst player = new Player(vueFluxConfig, timers, emit);\n\n\t\tsetCurrentResource(player, defaultCaption);\n\n\t\tconst wrapper = mount(FluxCaption, {\n\t\t\tprops: {\n\t\t\t\tplayer,\n\t\t\t},\n\t\t\tslots: {\n\t\t\t\tdefault: `<h1>{{ params.caption }}</h1>`,\n\t\t\t},\n\t\t});\n\n\t\texpect(\n\t\t\twrapper.html().includes(`<h1>${defaultCaption}</h1>`)\n\t\t).toBeTruthy();\n\t});\n});\n"
  },
  {
    "path": "src/complements/FluxCaption/FluxCaption.vue",
    "content": "<script setup lang=\"ts\">\n\timport { computed } from 'vue';\n\timport { Player } from '../../controllers';\n\n\texport interface Props {\n\t\tplayer: Player;\n\t}\n\n\tconst props = defineProps<Props>();\n\n\tconst { resource, transition } = props.player;\n\n\tconst caption = computed<string>(() => {\n\t\tif (resource.current === null || resource.current.rsc.caption === null) {\n\t\t\treturn '&nbsp;';\n\t\t}\n\n\t\treturn resource.current.rsc.caption;\n\t});\n\n\tconst cssClasses = computed<string[]>(() => {\n\t\tconst classes = ['flux-caption'];\n\n\t\tif (\n\t\t\ttransition.current === null &&\n\t\t\tresource.current !== null &&\n\t\t\tresource.current.rsc.caption.length > 0\n\t\t) {\n\t\t\tclasses.push('visible');\n\t\t}\n\n\t\treturn classes;\n\t});\n</script>\n\n<template>\n\t<div :class=\"cssClasses\">\n\t\t<slot :caption=\"caption\">{{ caption }}</slot>\n\t</div>\n</template>\n\n<style lang=\"scss\">\n\t.vue-flux .flux-caption {\n\t\tflex: none;\n\t\twidth: 100%;\n\t\tfont-size: 0.8rem;\n\t\tline-height: 1.1rem;\n\t\tpadding: 6px;\n\t\tbox-sizing: border-box;\n\t\tcolor: white;\n\t\ttext-align: center;\n\t\tbackground-color: rgba(0, 0, 0, 0.65);\n\t\topacity: 0;\n\n\t\t&.visible {\n\t\t\topacity: 1;\n\t\t\ttransition: opacity 0.3s ease-in;\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "src/complements/FluxControls/FluxControls.test.ts",
    "content": "import { ref, type Ref } from 'vue';\nimport { Player, Timers } from '../../controllers';\nimport { Directions, Statuses } from '../../controllers/Player';\nimport * as Buttons from './buttons';\nimport FluxControls from './FluxControls.vue';\nimport { mount } from '@vue/test-utils';\nimport emit from '../../components/VueFlux/__test__/emit';\nimport { vueFluxConfig, setCurrentResource, setCurrentTransition } from '../__test__/PlayerHelper';\n\nvi.mock('../../controllers/Player/Player');\n\ndescribe('complements: FluxControls', () => {\n\tconst timers = new Timers();\n\tconst mouseOver: Ref<boolean> = ref(false);\n\n\tbeforeEach(() => {\n\t\tmouseOver.value = false;\n\t});\n\n\tit('should mount properly without slot', () => {\n\t\tconst player = new Player(vueFluxConfig, timers, emit);\n\n\t\texpect(() => {\n\t\t\tmount(FluxControls, {\n\t\t\t\tprops: {\n\t\t\t\t\tmouseOver,\n\t\t\t\t\tplayer,\n\t\t\t\t},\n\t\t\t});\n\t\t}).not.toThrow();\n\t});\n\n\tit('should not be visible if transition running', () => {\n\t\tconst player = new Player(vueFluxConfig, timers, emit);\n\n\t\tsetCurrentResource(player);\n\t\tsetCurrentTransition(player);\n\n\t\tconst wrapper = mount(FluxControls, {\n\t\t\tprops: {\n\t\t\t\tmouseOver,\n\t\t\t\tplayer,\n\t\t\t},\n\t\t});\n\n\t\texpect(wrapper.html().includes('class=\"flux-controls\"')).toBeFalsy();\n\t});\n\n\tit('should not be visible if transition running and mouse not moving', () => {\n\t\tconst player = new Player(vueFluxConfig, timers, emit);\n\n\t\tsetCurrentResource(player);\n\n\t\tconst wrapper = mount(FluxControls, {\n\t\t\tprops: {\n\t\t\t\tmouseOver,\n\t\t\t\tplayer,\n\t\t\t},\n\t\t});\n\n\t\texpect(wrapper.html().includes('class=\"flux-controls\"')).toBeFalsy();\n\t});\n\n\tit('should be visible if no transition running and mouse moving', () => {\n\t\tconst player = new Player(vueFluxConfig, timers, emit);\n\n\t\tsetCurrentResource(player);\n\n\t\tmouseOver.value = true;\n\n\t\tconst wrapper = mount(FluxControls, {\n\t\t\tprops: {\n\t\t\t\tmouseOver,\n\t\t\t\tplayer,\n\t\t\t},\n\t\t});\n\n\t\texpect(wrapper.html().includes('class=\"flux-controls\"')).toBeTruthy();\n\t});\n\n\tit('should display play button', () => {\n\t\tconst player = new Player(vueFluxConfig, timers, emit);\n\t\tplayer.status.value = Statuses.stopped;\n\n\t\tsetCurrentResource(player);\n\t\tmouseOver.value = true;\n\n\t\tconst wrapper = mount(FluxControls, {\n\t\t\tprops: {\n\t\t\t\tmouseOver,\n\t\t\t\tplayer,\n\t\t\t},\n\t\t});\n\n\t\texpect(() => {\n\t\t\twrapper.getComponent(Buttons.Play);\n\t\t}).not.toThrow();\n\t});\n\n\tit('should play when button pressed', async () => {\n\t\tconst player = new Player(vueFluxConfig, timers, emit);\n\t\tplayer.status.value = Statuses.stopped;\n\n\t\tsetCurrentResource(player);\n\t\tmouseOver.value = true;\n\n\t\tconst wrapper = mount(FluxControls, {\n\t\t\tprops: {\n\t\t\t\tmouseOver,\n\t\t\t\tplayer,\n\t\t\t},\n\t\t});\n\n\t\tawait wrapper.getComponent(Buttons.Play).trigger('click');\n\n\t\texpect(player.play).toHaveBeenCalledWith(Directions.next, expect.any(Number));\n\t});\n\n\tit('should display stop button', () => {\n\t\tconst player = new Player(vueFluxConfig, timers, emit);\n\t\tplayer.status.value = Statuses.playing;\n\n\t\tsetCurrentResource(player);\n\t\tmouseOver.value = true;\n\n\t\tconst wrapper = mount(FluxControls, {\n\t\t\tprops: {\n\t\t\t\tmouseOver,\n\t\t\t\tplayer,\n\t\t\t},\n\t\t});\n\n\t\texpect(() => {\n\t\t\twrapper.getComponent(Buttons.Stop);\n\t\t}).not.toThrow();\n\t});\n\n\tit('should stop when button pressed', async () => {\n\t\tconst player = new Player(vueFluxConfig, timers, emit);\n\t\tplayer.status.value = Statuses.playing;\n\n\t\tsetCurrentResource(player);\n\t\tmouseOver.value = true;\n\n\t\tconst wrapper = mount(FluxControls, {\n\t\t\tprops: {\n\t\t\t\tmouseOver,\n\t\t\t\tplayer,\n\t\t\t},\n\t\t});\n\n\t\tawait wrapper.getComponent(Buttons.Stop).trigger('click');\n\n\t\texpect(player.stop).toHaveBeenCalledOnce();\n\t});\n\n\tit('should display previous resource when button pressed', async () => {\n\t\tconst player = new Player(vueFluxConfig, timers, emit);\n\t\tplayer.status.value = Statuses.playing;\n\n\t\tsetCurrentResource(player);\n\t\tmouseOver.value = true;\n\n\t\tconst wrapper = mount(FluxControls, {\n\t\t\tprops: {\n\t\t\t\tmouseOver,\n\t\t\t\tplayer,\n\t\t\t},\n\t\t});\n\n\t\tawait wrapper.getComponent(Buttons.Prev).trigger('click');\n\n\t\texpect(player.show).toHaveBeenCalledWith(Directions.prev);\n\t});\n\n\tit('should display next resource when button pressed', async () => {\n\t\tconst player = new Player(vueFluxConfig, timers, emit);\n\t\tplayer.status.value = Statuses.playing;\n\n\t\tsetCurrentResource(player);\n\t\tmouseOver.value = true;\n\n\t\tconst wrapper = mount(FluxControls, {\n\t\t\tprops: {\n\t\t\t\tmouseOver,\n\t\t\t\tplayer,\n\t\t\t},\n\t\t});\n\n\t\tawait wrapper.getComponent(Buttons.Next).trigger('click');\n\n\t\texpect(player.show).toHaveBeenCalledWith(Directions.next);\n\t});\n});\n"
  },
  {
    "path": "src/complements/FluxControls/FluxControls.vue",
    "content": "<script setup lang=\"ts\">\n\timport { type Ref, computed, unref } from 'vue';\n\timport { Player, Directions } from '../../controllers/Player';\n\timport * as Buttons from './buttons';\n\timport { default as PlayerStatuses } from '../../controllers/Player/Statuses';\n\n\texport interface Props {\n\t\tmouseOver?: Ref<boolean>;\n\t\tplayer: Player;\n\t}\n\n\tconst props = withDefaults(defineProps<Props>(), {\n\t\tmouseOver: undefined,\n\t});\n\n\tconst visible = computed<boolean>(() => {\n\t\tif (props.player.resource.current === null) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif (props.mouseOver !== undefined && unref(props.mouseOver) === false) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t});\n</script>\n\n<template>\n\t<transition name=\"fade\">\n\t\t<div v-if=\"visible\" class=\"flux-controls\">\n\t\t\t<Buttons.Prev @click=\"player.show(Directions.prev)\" />\n\t\t\t<Buttons.Play\n\t\t\t\tv-if=\"(player.status.value || player.status) === PlayerStatuses.stopped\"\n\t\t\t\t@click=\"player.play(Directions.next, 1)\"\n\t\t\t/>\n\t\t\t<Buttons.Stop\n\t\t\t\tv-if=\"(player.status.value || player.status) === PlayerStatuses.playing\"\n\t\t\t\t@click=\"player.stop()\"\n\t\t\t/>\n\t\t\t<Buttons.Next @click=\"player.show(Directions.next)\" />\n\t\t</div>\n\t</transition>\n</template>\n\n<style lang=\"scss\">\n\t.vue-flux .flux-controls {\n\t\tflex: none;\n\t\tdisplay: flex;\n\t\tjustify-content: space-between;\n\n\t\t&.fade-enter,\n\t\t&.fade-leave-to {\n\t\t\topacity: 0;\n\t\t}\n\n\t\t&.fade-enter-active,\n\t\t&.fade-leave-active {\n\t\t\ttransition: opacity 0.3s ease-in;\n\t\t}\n\n\t\t.prev {\n\t\t\tmargin-left: 4%;\n\t\t}\n\n\t\t.next {\n\t\t\tmargin-right: 4%;\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "src/complements/FluxControls/buttons/Next.vue",
    "content": "<script setup lang=\"ts\">\n\timport { FluxButton } from '../../../components';\n</script>\r\n\r\n<template>\n\t<FluxButton class=\"next top right\">\n\t\t<polyline points=\"36,18 78,50 36,82\" />\n\t</FluxButton>\n</template>\r\n"
  },
  {
    "path": "src/complements/FluxControls/buttons/Play.vue",
    "content": "<script setup lang=\"ts\">\n\timport { FluxButton } from '../../../components';\n</script>\r\n\r\n<template>\n\t<FluxButton class=\"play\">\n\t\t<polygon points=\"32,12 82,50 32,88\" />\n\t</FluxButton>\n</template>\r\n"
  },
  {
    "path": "src/complements/FluxControls/buttons/Prev.vue",
    "content": "<script setup lang=\"ts\">\n\timport { FluxButton } from '../../../components';\n</script>\r\n\r\n<template>\n\t<FluxButton class=\"prev top left\">\n\t\t<polyline points=\"64,18 22,50 64,82\" />\n\t</FluxButton>\n</template>\r\n"
  },
  {
    "path": "src/complements/FluxControls/buttons/Stop.vue",
    "content": "<script setup lang=\"ts\">\n\timport { FluxButton } from '../../../components';\n</script>\r\n\r\n<template>\n\t<FluxButton class=\"pause\">\n\t\t<line x1=\"32\" y1=\"22\" x2=\"32\" y2=\"78\" />\n\t\t<line x1=\"68\" y1=\"22\" x2=\"68\" y2=\"78\" />\n\t</FluxButton>\n</template>\r\n"
  },
  {
    "path": "src/complements/FluxControls/buttons/index.ts",
    "content": "export { default as Prev } from './Prev.vue';\nexport { default as Play } from './Play.vue';\nexport { default as Stop } from './Stop.vue';\nexport { default as Next } from './Next.vue';\n"
  },
  {
    "path": "src/complements/FluxIndex/Button/Button.test.ts",
    "content": "import { type Ref, ref } from 'vue';\nimport { mount } from '@vue/test-utils';\nimport Button from './Button.vue';\n\ndescribe('complements: FluxIndex Button', () => {\n\tconst mouseOver: Ref<boolean> = ref(false);\n\n\tbeforeEach(() => {\n\t\tmouseOver.value = false;\n\t});\n\n\tit('mounts properly', () => {\n\t\texpect(() => {\n\t\t\tmount(Button, {\n\t\t\t\tprops: {\n\t\t\t\t\tmouseOver,\n\t\t\t\t},\n\t\t\t});\n\t\t}).not.toThrow();\n\t});\n\n\tit('is visible when mouse over', () => {\n\t\tmouseOver.value = true;\n\n\t\tconst wrapper = mount(Button, {\n\t\t\tprops: {\n\t\t\t\tmouseOver,\n\t\t\t},\n\t\t});\n\n\t\texpect(wrapper.html().includes('toggle bottom left')).toBeTruthy();\n\t});\n\n\tit('is NOT visible when mouse NOT over', () => {\n\t\tconst wrapper = mount(Button, {\n\t\t\tprops: {\n\t\t\t\tmouseOver,\n\t\t\t},\n\t\t});\n\n\t\texpect(wrapper.html().includes('toggle bottom left')).toBeFalsy();\n\t});\n});\n"
  },
  {
    "path": "src/complements/FluxIndex/Button/Button.vue",
    "content": "<script setup lang=\"ts\">\n\timport { type Ref, computed, unref } from 'vue';\n\timport { FluxButton } from '../../../components';\n\n\tinterface Props {\n\t\tmouseOver?: Ref<boolean>;\n\t}\n\n\tconst props = withDefaults(defineProps<Props>(), {\n\t\tmouseOver: undefined,\n\t});\n\n\tconst visible = computed<boolean>(() => [true, undefined].includes(unref(props.mouseOver)));\n</script>\n\n<template>\n\t<transition name=\"fade\">\n\t\t<FluxButton v-if=\"visible\" class=\"toggle bottom left\">\n\t\t\t<rect x=\"17.5\" y=\"17.5\" width=\"12px\" height=\"12px\" />\n\t\t\t<rect x=\"17.5\" y=\"43\" width=\"12px\" height=\"12px\" />\n\t\t\t<rect x=\"17.5\" y=\"68.5\" width=\"12px\" height=\"12px\" />\n\t\t\t<rect x=\"43\" y=\"17.5\" width=\"12px\" height=\"12px\" />\n\t\t\t<rect x=\"43\" y=\"43\" width=\"12px\" height=\"12px\" />\n\t\t\t<rect x=\"43\" y=\"68.5\" width=\"12px\" height=\"12px\" />\n\t\t\t<rect x=\"68.5\" y=\"17.5\" width=\"12px\" height=\"12px\" />\n\t\t\t<rect x=\"68.5\" y=\"43\" width=\"12px\" height=\"12px\" />\n\t\t\t<rect x=\"68.5\" y=\"68.5\" width=\"12px\" height=\"12px\" />\n\t\t</FluxButton>\n\t</transition>\n</template>\n\n<style lang=\"scss\">\n\t.vue-flux .flux-index {\n\t\t.fade-enter,\n\t\t.fade-leave-to {\n\t\t\topacity: 0;\n\t\t}\n\n\t\t.fade-enter-active,\n\t\t.fade-leave-active {\n\t\t\ttransition: opacity 0.3s ease-in;\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "src/complements/FluxIndex/FluxIndex.vue",
    "content": "<script setup lang=\"ts\">\n\timport { ref, computed, type Ref } from 'vue';\n\timport { Size } from '../../shared';\n\timport { Player } from '../../controllers';\n\timport Button from './Button/Button.vue';\n\timport List from './List/List.vue';\n\n\texport interface Props {\n\t\tmouseOver?: Ref<boolean>;\n\t\tdisplaySize: Size;\n\t\tplayer: Player;\n\t}\n\n\tconst props = withDefaults(defineProps<Props>(), {\n\t\tmouseOver: undefined,\n\t});\n\n\tconst $fluxIndexList: Ref<null | InstanceType<typeof List>> = ref(null);\n\n\tconst visible = computed<boolean>(() => props.player.resources.list.length > 0);\n</script>\n\n<template>\n\t<div v-if=\"visible\" class=\"flux-index\">\n\t\t<Button v-if=\"mouseOver\" :mouse-over=\"mouseOver\" @click=\"$fluxIndexList?.show()\" />\n\n\t\t<List\n\t\t\tref=\"$fluxIndexList\"\n\t\t\t:display-size=\"displaySize\"\n\t\t\t:player=\"player\"\n\t\t\t:mouse-over=\"mouseOver\"\n\t\t/>\n\t</div>\n</template>\n\n<style lang=\"scss\">\n\t.vue-flux .flux-index {\n\t\tflex: none;\n\t\tmargin-bottom: 2%;\n\t\tfont-size: 0;\n\t\ttext-align: center;\n\n\t\tnav {\n\t\t\tposition: absolute;\n\t\t\ttop: 0;\n\t\t\tleft: 0;\n\t\t\tright: 0;\n\t\t\tbottom: 0;\n\t\t\tdisplay: block;\n\t\t\tmargin: 0;\n\t\t\toverflow: hidden;\n\t\t\tvisibility: hidden;\n\t\t}\n\n\t\tnav.visible {\n\t\t\tz-index: 101;\n\t\t\tvisibility: visible;\n\t\t}\n\n\t\tul {\n\t\t\tdisplay: block;\n\t\t\theight: 100%;\n\t\t\tmargin: 0;\n\t\t\tmargin-top: 100%;\n\t\t\tpadding: 24px 0 0 24px;\n\t\t\tlist-style-type: none;\n\t\t\ttext-align: center;\n\t\t\toverflow-y: auto;\n\t\t\tbackground-color: black;\n\t\t\ttransition: all 0.5s linear;\n\t\t\tfont-size: 0;\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "src/complements/FluxIndex/List/List.test.ts",
    "content": "import { type Ref, ref } from 'vue';\nimport { mount } from '@vue/test-utils';\nimport { Player, Timers } from '../../../controllers';\nimport List from './List.vue';\nimport { Size } from '../../../shared';\nimport emit from '../../../components/VueFlux/__test__/emit';\nimport { vueFluxConfig, setCurrentResource } from '../../__test__/PlayerHelper';\nimport Thumb from '../Thumb/Thumb.vue';\nimport ResourceFactory from '../../../resources/__test__/ResourceFactory';\n\nvi.mock('../../../resources/Img/Img');\nvi.mock('../../../shared/ResourceLoader/ResourceLoader');\nvi.mock('../../../controllers/Player/Player');\n\ndescribe('complements: FluxIndex List', () => {\n\tconst timers = new Timers();\n\tconst displaySize: Size = new Size({ width: 640, height: 360 });\n\tconst mouseOver: Ref<boolean> = ref(false);\n\n\tbeforeEach(() => {\n\t\tmouseOver.value = false;\n\t});\n\n\tit('mounts properly', () => {\n\t\tconst player = new Player(vueFluxConfig, timers, emit);\n\n\t\texpect(() => {\n\t\t\tmount(List, {\n\t\t\t\tprops: {\n\t\t\t\t\tdisplaySize,\n\t\t\t\t\tplayer,\n\t\t\t\t\tmouseOver,\n\t\t\t\t},\n\t\t\t});\n\t\t}).not.toThrow();\n\t});\n\n\tit('is not visible by default', async () => {\n\t\tmouseOver.value = true;\n\t\tconst player = new Player(vueFluxConfig, timers, emit);\n\n\t\tconst wrapper = mount(List, {\n\t\t\tprops: {\n\t\t\t\tdisplaySize,\n\t\t\t\tplayer,\n\t\t\t\tmouseOver,\n\t\t\t},\n\t\t});\n\n\t\texpect(wrapper.html().includes('nav class=\"\"')).toBeTruthy();\n\t});\n\n\tit('shows the list when button clicked', async () => {\n\t\tmouseOver.value = true;\n\t\tconst player = new Player(vueFluxConfig, timers, emit);\n\n\t\tconst wrapper = mount(List, {\n\t\t\tprops: {\n\t\t\t\tdisplaySize,\n\t\t\t\tplayer,\n\t\t\t\tmouseOver,\n\t\t\t},\n\t\t});\n\n\t\tawait wrapper.vm.show();\n\n\t\texpect(wrapper.html().includes('nav class=\"visible\"')).toBeTruthy();\n\t});\n\n\tit('does nothing if clicked resource is the same as current resource', async () => {\n\t\tmouseOver.value = true;\n\t\tconst player = new Player(vueFluxConfig, timers, emit);\n\t\tconst resources = ResourceFactory.create(10);\n\t\tawait player.resources.update(resources, 10, displaySize);\n\n\t\tsetCurrentResource(player);\n\n\t\tconst wrapper = mount(List, {\n\t\t\tprops: {\n\t\t\t\tdisplaySize,\n\t\t\t\tplayer,\n\t\t\t\tmouseOver,\n\t\t\t},\n\t\t});\n\n\t\tawait wrapper.find({ ref: '$list' }).findAllComponents(Thumb)[0].trigger('click');\n\n\t\texpect(player.show).not.toHaveBeenCalled();\n\t});\n});\n"
  },
  {
    "path": "src/complements/FluxIndex/List/List.vue",
    "content": "<script setup lang=\"ts\">\n\timport { type Ref, computed, nextTick, ref } from 'vue';\n\timport { Player } from '../../../controllers';\n\timport Thumb from '../Thumb/Thumb.vue';\n\timport { Size } from '../../../shared';\n\timport useThumbs from '../Thumb/useThumbs';\n\n\texport interface Props {\n\t\tdisplaySize: Size;\n\t\tplayer: Player;\n\t\tmouseOver?: Ref<boolean>;\n\t}\n\n\tconst props = withDefaults(defineProps<Props>(), {\n\t\tmouseOver: undefined,\n\t});\n\n\tconst $list: Ref<null | HTMLUListElement> = ref(null);\n\n\tconst animationTime = 500;\n\tconst visible: Ref<boolean> = ref(false);\n\n\tconst listClass = computed<string[]>(() => {\n\t\tconst classes = [];\n\n\t\tif (visible.value) {\n\t\t\tclasses.push('visible');\n\t\t}\n\n\t\treturn classes;\n\t});\n\n\tasync function show() {\n\t\tif ($list.value === null) {\n\t\t\treturn;\n\t\t}\n\n\t\tprops.player.stop();\n\t\tvisible.value = true;\n\n\t\tawait nextTick();\n\n\t\t// eslint-disable-next-line @typescript-eslint/no-unused-expressions\n\t\t$list.value.clientHeight;\n\t\t$list.value.style.marginTop = '0';\n\t}\n\n\tfunction hide(resourceIndex: null | number) {\n\t\tif ($list.value === null) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (props.player.resource.current?.index === resourceIndex) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (props.mouseOver !== undefined) {\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-unused-expressions\n\t\t\t$list.value.clientHeight;\n\t\t\t$list.value.style.marginTop = '100%';\n\t\t}\n\n\t\tsetTimeout(() => {\n\t\t\tvisible.value = false;\n\n\t\t\tif (resourceIndex !== null) {\n\t\t\t\tprops.player.show(resourceIndex);\n\t\t\t}\n\t\t}, animationTime);\n\t}\n\n\tconst thumbs = useThumbs(props.displaySize, props.player);\n\n\tdefineExpose({ show });\n</script>\n\n<template>\n\t<nav :class=\"listClass\" @click=\"hide(null)\">\n\t\t<ul ref=\"$list\">\n\t\t\t<Thumb\n\t\t\t\tv-for=\"(rsc, index) in player.resources!.list\"\n\t\t\t\t:key=\"index\"\n\t\t\t\t:rsc=\"rsc.resource\"\n\t\t\t\t:size=\"thumbs.size\"\n\t\t\t\t:class=\"thumbs.getClass(index)\"\n\t\t\t\t@click=\"hide(index)\"\n\t\t\t/>\n\t\t</ul>\n\t</nav>\n</template>\n\n<style lang=\"scss\">\n\t.vue-flux .flux-index {\n\t\tnav {\n\t\t\tposition: absolute;\n\t\t\ttop: 0;\n\t\t\tleft: 0;\n\t\t\tright: 0;\n\t\t\tbottom: 0;\n\t\t\tdisplay: block;\n\t\t\tmargin: 0;\n\t\t\toverflow: hidden;\n\t\t\tvisibility: hidden;\n\t\t}\n\n\t\tnav.visible {\n\t\t\tz-index: 101;\n\t\t\tvisibility: visible;\n\t\t}\n\n\t\tul {\n\t\t\tdisplay: block;\n\t\t\theight: 100%;\n\t\t\tmargin: 0;\n\t\t\tmargin-top: 100%;\n\t\t\tpadding: 24px 0 0 24px;\n\t\t\tlist-style-type: none;\n\t\t\ttext-align: center;\n\t\t\toverflow-y: auto;\n\t\t\tbackground-color: black;\n\t\t\ttransition: all 0.5s linear;\n\t\t\tfont-size: 0;\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "src/complements/FluxIndex/Thumb/Thumb.vue",
    "content": "<script setup lang=\"ts\">\n\timport type { Ref } from 'vue';\n\timport { Resource } from '../../../resources';\n\timport { Size } from '../../../shared';\n\n\texport interface Props {\n\t\trsc: Resource;\n\t\tsize: Ref<Size>;\n\t}\n\n\tdefineProps<Props>();\n</script>\n\n<template>\n\t<li>\n\t\t<component\n\t\t\t:is=\"rsc.transition.component\"\n\t\t\t:rsc=\"rsc\"\n\t\t\t:size=\"size.value\"\n\t\t\t:title=\"rsc.caption\"\n\t\t/>\n\t</li>\n</template>\n\n<style lang=\"scss\">\n\t.vue-flux .flux-index li {\n\t\tposition: relative;\n\t\tdisplay: inline-block;\n\t\tbox-sizing: content-box;\n\t\tmargin: 0 24px 24px 0;\n\t\tcursor: pointer;\n\t\ttransition: all 0.3s ease;\n\n\t\t&:hover {\n\t\t\tbox-shadow: 0px 0px 3px 2px rgba(255, 255, 255, 0.6);\n\t\t}\n\n\t\t&.current {\n\t\t\tcursor: auto;\n\t\t\tborder: 1px solid white;\n\t\t\tbox-shadow: none;\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "src/complements/FluxIndex/Thumb/useThumbs.ts",
    "content": "import { computed } from 'vue';\nimport { Player } from '../../../controllers';\nimport { Size } from '../../../shared';\n\nexport default function useThumbs(displaySize: Size, player: Player) {\n\tconst size = computed<Size>(() => {\n\t\tlet { width, height } = displaySize.toValue();\n\n\t\twidth = width! / 4.2;\n\t\theight = (width * 90) / 160;\n\n\t\tif (width > 160) {\n\t\t\twidth = 160;\n\t\t\theight = 90;\n\t\t}\n\n\t\treturn new Size({\n\t\t\twidth,\n\t\t\theight,\n\t\t});\n\t});\n\n\tfunction getClass(index: number) {\n\t\tconst { current } = player.resource;\n\n\t\tif (current === null) {\n\t\t\treturn '';\n\t\t}\n\n\t\tif (current.index !== index) {\n\t\t\treturn '';\n\t\t}\n\n\t\treturn 'current';\n\t}\n\n\treturn { size, getClass };\n}\n"
  },
  {
    "path": "src/complements/FluxPagination/FluxPagination.vue",
    "content": "<script setup lang=\"ts\">\n\timport { computed } from 'vue';\n\timport type { ResourceWithOptions } from '../../resources';\n\timport { Player } from '../../controllers';\n\n\texport interface Props {\n\t\tplayer: Player;\n\t}\n\n\tconst props = defineProps<Props>();\n\n\tconst {\n\t\tplayer: { resources, resource, transition },\n\t} = props;\n\n\tconst visible = computed<boolean>(() => resources.list.length > 0);\n\n\tconst getTitle = (rsc: ResourceWithOptions) => {\n\t\treturn rsc.resource.caption;\n\t};\n\n\tconst getCssClass = (index: number, itemCLass: string) => {\n\t\tconst classes = [itemCLass];\n\n\t\tlet active = resource.current?.index === index;\n\n\t\tif (transition.current !== null) {\n\t\t\tactive = false;\n\t\t}\n\n\t\tif (active === true) {\n\t\t\tclasses.push('active');\n\t\t}\n\n\t\treturn classes;\n\t};\n</script>\n\n<template>\n\t<nav v-if=\"visible\" class=\"flux-pagination\">\n\t\t<ul>\n\t\t\t<li v-for=\"(rsc, index) in player.resources.list\" :key=\"index\">\n\t\t\t\t<slot\n\t\t\t\t\t:index=\"index\"\n\t\t\t\t\t:rsc=\"rsc\"\n\t\t\t\t\t:title=\"getTitle(rsc)\"\n\t\t\t\t\t:css-class=\"getCssClass(index, 'custom-pagination-item')\"\n\t\t\t\t>\n\t\t\t\t\t<span\n\t\t\t\t\t\t:title=\"getTitle(rsc)\"\n\t\t\t\t\t\t:class=\"getCssClass(index, 'pagination-item')\"\n\t\t\t\t\t\t@click=\"player.show(index)\"\n\t\t\t\t\t/>\n\t\t\t\t</slot>\n\t\t\t</li>\n\t\t</ul>\n\t</nav>\n</template>\n\n<style lang=\"scss\">\n\t.vue-flux .flux-pagination {\n\t\tflex: none;\n\n\t\tul {\n\t\t\tdisplay: flex;\n\t\t\tflex-wrap: wrap;\n\t\t\tjustify-content: center;\n\t\t\tmargin: 0;\n\t\t\tpadding: 0;\n\t\t\tlist-style-type: none;\n\t\t\ttext-align: center;\n\t\t\tposition: relative;\n\t\t}\n\n\t\tli {\n\t\t\tdisplay: block;\n\t\t\tmargin: 0 1% 1.5% 1%;\n\t\t\tcursor: pointer;\n\t\t\twidth: 2%;\n\t\t\theight: 0;\n\t\t\tmin-width: 10px;\n\t\t\tmin-height: 10px;\n\t\t\tpadding-bottom: 2%;\n\t\t\tposition: relative;\n\t\t\tbox-sizing: border-box;\n\t\t}\n\n\t\t.pagination-item {\n\t\t\tposition: absolute;\n\t\t\ttop: 0;\n\t\t\tleft: 0;\n\t\t\tright: 0;\n\t\t\tbottom: 0;\n\t\t\tbox-sizing: border-box;\n\t\t\tborder: 2px solid #fff;\n\t\t\tborder-radius: 50%;\n\t\t\tbackground-color: rgba(0, 0, 0, 0.7);\n\t\t\ttransition:\n\t\t\t\tbackground-color 0.2s ease-in,\n\t\t\t\tborder 0.2s ease-in;\n\n\t\t\t&:hover {\n\t\t\t\tborder-color: black;\n\t\t\t\tbackground-color: white;\n\t\t\t}\n\n\t\t\t&.active {\n\t\t\t\tborder-color: white;\n\t\t\t\tbackground-color: white;\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "src/complements/FluxPreloader/FluxPreloader.vue",
    "content": "<script setup lang=\"ts\">\n\timport type { Ref } from 'vue';\n\timport { ResourceLoader } from '../../shared';\n\n\texport interface Props {\n\t\tloader: Ref<null | ResourceLoader>;\n\t}\n\n\tdefineProps<Props>();\n</script>\n\n<template>\n\t<div class=\"preloader\">\n\t\t<slot\n\t\t\t:loader=\"loader\"\n\t\t\t:preloading=\"loader.value?.preLoading.length\"\n\t\t\t:lazyloading=\"loader.value?.lazyLoading.length\"\n\t\t\t:pct=\"loader.value?.progress\"\n\t\t>\n\t\t\t<div v-if=\"loader.value?.preLoading.length\" class=\"spinner\">\n\t\t\t\t<div class=\"pct\">{{ loader.value?.progress }}%</div>\n\t\t\t\t<div class=\"border\" />\n\t\t\t</div>\n\t\t</slot>\n\t</div>\n</template>\n\n<style lang=\"scss\">\n\t.vue-flux .preloader {\n\t\tposition: absolute;\n\t\ttop: 0;\n\t\tright: 0;\n\t\tbottom: 0;\n\t\tleft: 0;\n\t\tz-index: -1;\n\n\t\t.spinner {\n\t\t\tposition: absolute;\n\t\t\ttop: 50%;\n\t\t\tleft: 50%;\n\t\t\tmargin-top: -40px;\n\t\t\tmargin-left: -40px;\n\t\t\twidth: 80px;\n\t\t\theight: 80px;\n\t\t\tz-index: 14;\n\n\t\t\t.pct {\n\t\t\t\tposition: absolute;\n\t\t\t\tright: 0;\n\t\t\t\tleft: 0;\n\t\t\t\theight: 80px;\n\t\t\t\tline-height: 80px;\n\t\t\t\ttext-align: center;\n\t\t\t\tfont-weight: bold;\n\t\t\t\tz-index: 1;\n\t\t\t}\n\n\t\t\t.border {\n\t\t\t\tbox-sizing: border-box;\n\t\t\t\twidth: 100%;\n\t\t\t\theight: 100%;\n\t\t\t\tborder: 14px solid #f3f3f3;\n\t\t\t\tborder-top-color: #3498db;\n\t\t\t\tborder-bottom-color: #3498db;\n\t\t\t\tborder-radius: 50%;\n\t\t\t\tbackground-color: #f3f3f3;\n\t\t\t\tanimation: spin 2s linear infinite;\n\t\t\t}\n\t\t}\n\n\t\t@keyframes spin {\n\t\t\t0% {\n\t\t\t\ttransform: rotate(0deg);\n\t\t\t}\n\t\t\t100% {\n\t\t\t\ttransform: rotate(360deg);\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "src/complements/__test__/PlayerHelper.ts",
    "content": "import type { VueFluxConfig } from '../../components/VueFlux/types';\nimport { Player } from '../../controllers/Player';\nimport type { ResourceIndex } from '../../repositories/Resources/types';\nimport type { TransitionIndex } from '../../repositories/Transitions/types';\nimport { Img } from '../../resources';\nimport { Blinds2D } from '../../transitions';\n\nexport const vueFluxConfig = {\n\tallowFullscreen: false,\n\tallowToSkipTransition: true,\n\taspectRatio: '16:9',\n\tautohideTime: 2500,\n\tautoplay: false,\n\tbindKeys: false,\n\tdelay: 5000,\n\tenableGestures: false,\n\tinfinite: true,\n\tlazyLoad: true,\n\tlazyLoadAfter: 5,\n} as VueFluxConfig;\n\nexport function setCurrentResource(player: Player, caption?: string) {\n\tplayer.resource.current = {\n\t\tindex: 0,\n\t\trsc: new Img('url', caption),\n\t\toptions: {},\n\t} as ResourceIndex;\n}\n\nexport function setCurrentTransition(player: Player) {\n\tplayer.transition.current = {\n\t\tindex: 0,\n\t\tcomponent: Blinds2D,\n\t\toptions: {},\n\t} as TransitionIndex;\n}\n"
  },
  {
    "path": "src/complements/index.ts",
    "content": "export { default as FluxCaption } from './FluxCaption/FluxCaption.vue';\nexport { default as FluxControls } from './FluxControls/FluxControls.vue';\nexport { default as FluxIndex } from './FluxIndex/FluxIndex.vue';\nexport { default as FluxPagination } from './FluxPagination/FluxPagination.vue';\nexport { default as FluxPreloader } from './FluxPreloader/FluxPreloader.vue';\n"
  },
  {
    "path": "src/components/FluxButton/FluxButton.test.ts",
    "content": "import FluxButton from './FluxButton.vue';\nimport { mount } from '@vue/test-utils';\n\ndescribe('component: FluxButton', () => {\n\tit('should mount properly', () => {\n\t\tconst nextLine = '<polyline points=\"36,18 78,50 36,82\" />';\n\n\t\tconst wrapper = mount(FluxButton, {\n\t\t\tslots: {\n\t\t\t\tdefault: nextLine,\n\t\t\t},\n\t\t});\n\n\t\texpect(\n\t\t\twrapper.html().includes('<polyline points=\"36,18 78,50 36,82\"')\n\t\t).toBeTruthy();\n\t});\n});\n"
  },
  {
    "path": "src/components/FluxButton/FluxButton.vue",
    "content": "<template>\n\t<button type=\"button\" class=\"flux-button\" style=\"outline: 0\">\n\t\t<svg\n\t\t\tviewBox=\"0 0 100 100\"\n\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\tversion=\"1.1\"\n\t\t>\n\t\t\t<circle cx=\"50\" cy=\"50\" r=\"50\" />\n\t\t\t<svg viewBox=\"-20 -20 140 140\">\n\t\t\t\t<slot />\n\t\t\t</svg>\n\t\t</svg>\n\t</button>\n</template>\n\n<style lang=\"scss\">\n\t.vue-flux .flux-button {\n\t\tpadding: 0;\n\t\twidth: 6%;\n\t\tmin-width: 26px;\n\t\tmin-height: 26px;\n\t\tmax-width: 40px;\n\t\tmax-height: 40px;\n\t}\n\n\t.flux-button {\n\t\tborder: 0;\n\t\tcursor: pointer;\n\t\tbackground-color: transparent;\n\n\t\t&:hover {\n\t\t\t> svg {\n\t\t\t\tline,\n\t\t\t\tpolyline {\n\t\t\t\t\tstroke: yellow;\n\t\t\t\t}\n\n\t\t\t\trect,\n\t\t\t\tpolygon {\n\t\t\t\t\tfill: yellow;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t> svg {\n\t\t\twidth: 100%;\n\n\t\t\t> circle {\n\t\t\t\tfill: rgba(0, 0, 0, 0.7);\n\t\t\t}\n\n\t\t\tline,\n\t\t\tpolyline,\n\t\t\trect,\n\t\t\tpolygon {\n\t\t\t\tstroke-linecap: round;\n\t\t\t\tstroke-linejoin: round;\n\t\t\t\tstroke: white;\n\t\t\t\tstroke-width: 14;\n\t\t\t\tfill: none;\n\t\t\t}\n\n\t\t\trect,\n\t\t\tpolygon {\n\t\t\t\tfill: white;\n\t\t\t\tstroke-width: 0;\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "src/components/FluxCube/FluxCube.vue",
    "content": "<script setup lang=\"ts\">\n\timport { ref, reactive, computed, onBeforeUpdate } from 'vue';\n\timport useComponent from '../useComponent';\n\timport type { FluxCubeProps, SidesComponents, Turn } from './types';\n\timport { Size } from '../../shared';\n\timport type { ComponentStyles, FluxComponent } from '../types';\n\timport SideTransformFactory from './factories/SideTransformFactory';\n\timport CubeFactory from './factories/CubeFactory';\n\timport Sides from './Sides';\n\n\tconst props = withDefaults(defineProps<FluxCubeProps>(), {\n\t\trscs: () => ({}),\n\t\tcolors: () => ({}),\n\t\toffsets: () => ({}),\n\t\tdepth: 0,\n\t\tviewSize: () => new Size(),\n\t});\n\n\tconst $el = ref(null);\n\n\tconst transformOrigin = computed(() =>\n\t\tprops.origin !== undefined ? props.origin : `center center -${props.depth / 2}px`,\n\t);\n\n\tconst componentStyles: ComponentStyles = reactive({\n\t\tbase: {\n\t\t\ttransformStyle: 'preserve-3d',\n\t\t\ttransformOrigin: transformOrigin,\n\t\t},\n\t});\n\n\tconst { style, setCss, transform, show, hide } = useComponent($el, props, componentStyles);\n\n\tconst sideTransformFactory = computed(\n\t\t() => new SideTransformFactory(props.depth, props.size, props.viewSize),\n\t);\n\n\tconst sides = computed(() =>\n\t\tCubeFactory.getSidesProps(\n\t\t\tsideTransformFactory.value,\n\t\t\tprops.color,\n\t\t\tprops.colors,\n\t\t\tprops.rsc,\n\t\t\tprops.rscs,\n\t\t\tprops.offset,\n\t\t\tprops.offsets,\n\t\t),\n\t);\n\n\tconst $sides: SidesComponents = reactive({});\n\n\tonBeforeUpdate(() => {\n\t\tObject.assign($sides, {\n\t\t\t[Sides.front]: undefined,\n\t\t\t[Sides.back]: undefined,\n\t\t\t[Sides.left]: undefined,\n\t\t\t[Sides.right]: undefined,\n\t\t\t[Sides.top]: undefined,\n\t\t\t[Sides.bottom]: undefined,\n\t\t});\n\t});\n\n\tconst turn = (turn: Turn) =>\n\t\ttransform({ transform: sideTransformFactory.value.getRotate(turn) });\n\n\tdefineExpose({\n\t\tsetCss,\n\t\ttransform,\n\t\tshow,\n\t\thide,\n\t\tturn,\n\t});\n</script>\n\n<template>\n\t<div ref=\"$el\" class=\"flux-cube\" :style=\"style\">\n\t\t<component\n\t\t\t:is=\"side!.component\"\n\t\t\tv-for=\"side in sides\"\n\t\t\t:ref=\"(el: FluxComponent) => ($sides[side!.name as keyof typeof Sides] = el)\"\n\t\t\t:key=\"side!.name\"\n\t\t\tv-bind=\"side\"\n\t\t/>\n\t</div>\n</template>\n"
  },
  {
    "path": "src/components/FluxCube/Sides.ts",
    "content": "enum Sides {\n\tfront = 'front',\n\tback = 'back',\n\tleft = 'left',\n\tright = 'right',\n\ttop = 'top',\n\tbottom = 'bottom',\n}\n\nexport default Sides;\n"
  },
  {
    "path": "src/components/FluxCube/Turns.ts",
    "content": "enum Turns {\n\tfront = 'front',\n\tback = 'back',\n\tbackr = 'backr',\n\tbackl = 'backl',\n\tleft = 'left',\n\tright = 'right',\n\ttop = 'top',\n\tbottom = 'bottom',\n}\n\nexport default Turns;\n"
  },
  {
    "path": "src/components/FluxCube/__mocks__/FluxCube.vue",
    "content": "<script setup lang=\"ts\">\n\timport { ref, computed } from 'vue';\n\timport { vi } from 'vitest';\n\timport Side from './Side.vue';\n\timport type { FluxCubeProps, Turn } from '../types';\n\timport { Size } from '../../../shared';\n\timport CubeFactory from '../factories/CubeFactory';\n\timport SideTransformFactory from '../factories/SideTransformFactory';\n\n\tconst props = withDefaults(defineProps<FluxCubeProps>(), {\n\t\trscs: () => ({}),\n\t\tcolors: () => ({}),\n\t\toffsets: () => ({}),\n\t\tdepth: 0,\n\t\tviewSize: () => new Size(),\n\t});\n\n\tconst $el = ref(null);\n\n\tconst sideTransformFactory = computed(\n\t\t() => new SideTransformFactory(props.depth, props.size, props.viewSize),\n\t);\n\n\tconst sides = computed(() =>\n\t\tCubeFactory.getSidesProps(\n\t\t\tsideTransformFactory.value,\n\t\t\tprops.color,\n\t\t\tprops.colors,\n\t\t\tprops.rsc,\n\t\t\tprops.rscs,\n\t\t\tprops.offset,\n\t\t\tprops.offsets,\n\t\t),\n\t);\n\n\tconst turn = vi\n\t\t.fn()\n\t\t// eslint-disable-next-line @typescript-eslint/no-unused-vars\n\t\t.mockImplementation((_turn: Turn) => vi.fn());\n\n\tdefineExpose({\n\t\tsetCss: vi.fn(),\n\t\ttransform: vi.fn(),\n\t\tshow: vi.fn(),\n\t\thide: vi.fn(),\n\t\tturn,\n\t});\n</script>\n\n<template>\n\t<div ref=\"$el\" class=\"flux-cube\">\n\t\t<Side :is=\"side!.component\" v-for=\"side in sides\" :key=\"side!.name\" v-bind=\"side\" />\n\t</div>\n</template>\n"
  },
  {
    "path": "src/components/FluxCube/__mocks__/Side.vue",
    "content": "<script setup lang=\"ts\">\n\timport { ref, type Ref } from 'vue';\n\timport { vi } from 'vitest';\n\n\tconst $el: Ref<null | HTMLDivElement> = ref(null);\n\n\tdefineExpose({\n\t\tsetCss: vi.fn(),\n\t\ttransform: vi.fn(),\n\t\tshow: vi.fn(),\n\t\thide: vi.fn(),\n\t});\n</script>\n\n<template>\n\t<div ref=\"$el\" />\n</template>\n"
  },
  {
    "path": "src/components/FluxCube/factories/CubeFactory.test.ts",
    "content": "import { Img } from '../../../resources';\nimport { Position, Size } from '../../../shared';\nimport { type SideProps } from '../types';\nimport CubeFactory from './CubeFactory';\nimport CubeSideFactory from './CubeSideFactory';\nimport SideTransformFactory from './SideTransformFactory';\n\ndescribe('factory: CubeFactory', () => {\n\tlet rsc, rscs, color, colors, offset, offsets;\n\n\tconst depth = 160;\n\tconst size = new Size({\n\t\twidth: 640,\n\t\theight: 360,\n\t});\n\tconst viewSize = new Size();\n\n\tconst sideTransformFactory = new SideTransformFactory(depth, size, viewSize);\n\n\tvi.spyOn(CubeSideFactory, 'getProps').mockImplementation(() => ({}) as SideProps);\n\n\tbeforeEach(() => {\n\t\tvi.clearAllMocks();\n\t});\n\n\tit('generates a cube using a color', () => {\n\t\tcolor = '#ccc';\n\n\t\tconst cubeProps = CubeFactory.getSidesProps(sideTransformFactory, color);\n\n\t\texpect(CubeSideFactory.getProps).toHaveBeenCalledTimes(6);\n\t\texpect(Object.keys(cubeProps)).toHaveLength(6);\n\t});\n\n\tit('generates a cube using a colors', () => {\n\t\tcolors = {\n\t\t\ttop: '#ccc',\n\t\t\tleft: '#ccc',\n\t\t\tback: '#ccc',\n\t\t};\n\n\t\tconst cubeProps = CubeFactory.getSidesProps(sideTransformFactory, undefined, colors);\n\n\t\texpect(CubeSideFactory.getProps).toHaveBeenCalledTimes(3);\n\t\texpect(Object.keys(cubeProps)).toHaveLength(3);\n\t});\n\n\tit('generates a cube using a rsc', () => {\n\t\trsc = new Img('url', 'caption');\n\n\t\tconst cubeProps = CubeFactory.getSidesProps(sideTransformFactory, undefined, undefined, rsc);\n\n\t\texpect(CubeSideFactory.getProps).toHaveBeenCalledTimes(6);\n\t\texpect(Object.keys(cubeProps)).toHaveLength(6);\n\t});\n\n\tit('generates a cube using a rscs', () => {\n\t\trscs = {\n\t\t\tbottom: new Img('url', 'caption'),\n\t\t\tright: new Img('url', 'caption'),\n\t\t\tfront: new Img('url', 'caption'),\n\t\t};\n\n\t\tconst cubeProps = CubeFactory.getSidesProps(\n\t\t\tsideTransformFactory,\n\t\t\tundefined,\n\t\t\tundefined,\n\t\t\tundefined,\n\t\t\trscs,\n\t\t);\n\n\t\texpect(CubeSideFactory.getProps).toHaveBeenCalledTimes(3);\n\t\texpect(Object.keys(cubeProps)).toHaveLength(3);\n\t});\n\n\tit('generates a cube using a color with offset', () => {\n\t\tcolor = '#ccc';\n\t\toffset = new Position({ top: 160, left: 80 });\n\n\t\tconst cubeProps = CubeFactory.getSidesProps(\n\t\t\tsideTransformFactory,\n\t\t\tcolor,\n\t\t\tundefined,\n\t\t\tundefined,\n\t\t\tundefined,\n\t\t\toffset,\n\t\t);\n\n\t\texpect(CubeSideFactory.getProps).toHaveBeenCalledTimes(6);\n\t\texpect(Object.keys(cubeProps)).toHaveLength(6);\n\t});\n\n\tit('generates a cube using a colors with offsets', () => {\n\t\tcolors = {\n\t\t\ttop: '#ccc',\n\t\t\tleft: '#ccc',\n\t\t\tback: '#ccc',\n\t\t};\n\n\t\toffsets = {\n\t\t\ttop: new Position({ top: 160, left: 80 }),\n\t\t\tleft: new Position({ top: 160, left: 80 }),\n\t\t\tback: new Position({ top: 160, left: 80 }),\n\t\t};\n\n\t\tconst cubeProps = CubeFactory.getSidesProps(\n\t\t\tsideTransformFactory,\n\t\t\tundefined,\n\t\t\tcolors,\n\t\t\tundefined,\n\t\t\tundefined,\n\t\t\tundefined,\n\t\t\toffsets,\n\t\t);\n\n\t\texpect(CubeSideFactory.getProps).toHaveBeenCalledTimes(3);\n\t\texpect(Object.keys(cubeProps)).toHaveLength(3);\n\t});\n});\n"
  },
  {
    "path": "src/components/FluxCube/factories/CubeFactory.ts",
    "content": "import type { Side, SidesColors, SidesResources, SidesOffsets, SidesProps } from '../types';\nimport CubeSideFactory from './CubeSideFactory';\nimport SideTransformFactory from './SideTransformFactory';\nimport { Position } from '../../../shared';\nimport Sides from '../Sides';\nimport { Resource } from '../../../resources';\nimport type { CSSProperties } from 'vue';\n\nfunction isSideDefined(side: Side, colors?: SidesColors, rscs?: SidesResources) {\n\tif (colors && colors[side]) {\n\t\treturn true;\n\t}\n\n\tif (rscs && rscs[side]) {\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nfunction getDefinedSides(\n\tcolor?: CSSProperties['color'],\n\tcolors?: SidesColors,\n\trsc?: Resource,\n\trscs?: SidesResources,\n) {\n\tconst sides = Object.values(Sides);\n\n\tif (color || rsc) {\n\t\treturn sides;\n\t}\n\n\treturn Object.values(Sides).filter((side) => isSideDefined(side, colors, rscs));\n}\n\nexport default class CubeFactory {\n\tstatic getSidesProps(\n\t\tsideTransformFactory: SideTransformFactory,\n\t\tcolor?: CSSProperties['color'],\n\t\tcolors?: SidesColors,\n\t\trsc?: Resource,\n\t\trscs?: SidesResources,\n\t\toffset?: Position,\n\t\toffsets?: SidesOffsets,\n\t) {\n\t\tconst sides = getDefinedSides(color, colors, rsc, rscs);\n\t\tconst props: SidesProps = {};\n\n\t\tsides.forEach((side: Side) => {\n\t\t\tprops[side] = CubeSideFactory.getProps(\n\t\t\t\tsideTransformFactory,\n\t\t\t\tside,\n\t\t\t\tcolors && colors[side] ? colors[side] : color,\n\t\t\t\trscs && rscs[side] ? rscs[side] : rsc,\n\t\t\t\toffsets && offsets[side] ? offsets[side] : offset,\n\t\t\t);\n\t\t});\n\n\t\treturn props;\n\t}\n}\n"
  },
  {
    "path": "src/components/FluxCube/factories/CubeSideFactory.ts",
    "content": "import { Position } from '../../../shared';\nimport { Resource } from '../../../resources';\nimport type { Side, SideProps } from '../types';\nimport SideTransformFactory from './SideTransformFactory';\nimport { FluxImage } from '../../';\nimport type { CSSProperties } from 'vue';\n\nexport default class CubeSideFactory {\n\tstatic getProps(\n\t\tsideTransformFactory: SideTransformFactory,\n\t\tside: Side,\n\t\tcolor?: CSSProperties['color'],\n\t\trsc?: Resource,\n\t\toffset?: Position,\n\t) {\n\t\tconst { depth, size, viewSize } = sideTransformFactory;\n\n\t\tconst props: SideProps = {\n\t\t\tname: side,\n\t\t\tcomponent: rsc ? rsc.transition.component : FluxImage,\n\t\t\tcolor: color,\n\t\t\trsc: rsc,\n\t\t\tsize: size.clone(),\n\t\t\tviewSize: viewSize.clone(),\n\t\t\toffset: offset,\n\t\t\tstyle: {\n\t\t\t\tposition: 'absolute',\n\t\t\t\ttransform: sideTransformFactory.getSideCss(side),\n\t\t\t\tbackfaceVisibility: 'hidden',\n\t\t\t},\n\t\t};\n\n\t\tif (['left', 'right'].includes(side)) {\n\t\t\tprops.viewSize.width.value = depth;\n\t\t\tprops.size.width.value = depth;\n\t\t}\n\n\t\tif (['top', 'bottom'].includes(side)) {\n\t\t\tprops.viewSize.height.value = depth;\n\t\t\tprops.size.height.value = depth;\n\t\t}\n\n\t\treturn props;\n\t}\n}\n"
  },
  {
    "path": "src/components/FluxCube/factories/SideTransformFactory.test.ts",
    "content": "import { Size } from '../../../shared';\nimport Turns from '../Turns';\nimport SideTransformFactory from './SideTransformFactory';\n\ndescribe('factory: SideTransformFactory', () => {\n\tconst depth = 160;\n\tconst size = new Size({\n\t\twidth: 640,\n\t\theight: 360,\n\t});\n\tconst viewSize = new Size();\n\tconst sideTransformFactory = new SideTransformFactory(depth, size, viewSize);\n\n\tit('should get the proper rotate angles', () => {\n\t\tconst expectations = {\n\t\t\tfront: 'rotateX(0deg) rotateY(0deg)',\n\t\t\tright: 'rotateX(0deg) rotateY(90deg)',\n\t\t\tleft: 'rotateX(0deg) rotateY(-90deg)',\n\t\t\ttop: 'rotateX(90deg) rotateY(0deg)',\n\t\t\tbottom: 'rotateX(-90deg) rotateY(0deg)',\n\t\t\tback: 'rotateX(0deg) rotateY(180deg)',\n\t\t\tbackl: 'rotateX(0deg) rotateY(-180deg)',\n\t\t\tbackr: 'rotateX(0deg) rotateY(180deg)',\n\t\t};\n\n\t\tObject.values(Turns).forEach((turn) => {\n\t\t\texpect(sideTransformFactory.getRotate(turn)).toBe(expectations[turn]);\n\t\t});\n\t});\n\n\tit('should get proper translate coordinates', () => {\n\t\tconst expectations = {\n\t\t\tfront: 'translate3d(0%, 0%, 0px)',\n\t\t\tright: 'translate3d(50%, 0%, 560px)',\n\t\t\tleft: 'translate3d(-50%, 0%, 80px)',\n\t\t\ttop: 'translate3d(0%, -50%, 80px)',\n\t\t\tbottom: 'translate3d(0%, 50%, 280px)',\n\t\t\tback: 'translate3d(0%, 0%, 160px)',\n\t\t\tbackl: 'translate3d(0%, 0%, 160px)',\n\t\t\tbackr: 'translate3d(0%, 0%, 160px)',\n\t\t};\n\n\t\tObject.values(Turns).forEach((turn) => {\n\t\t\texpect(sideTransformFactory.getTranslate(turn)).toBe(\n\t\t\t\texpectations[turn]\n\t\t\t);\n\t\t});\n\t});\n\n\tit('should get each side style', () => {\n\t\tconst expectations = {\n\t\t\tfront: 'rotateX(0deg) rotateY(0deg) translate3d(0%, 0%, 0px)',\n\t\t\tright: 'rotateX(0deg) rotateY(90deg) translate3d(50%, 0%, 560px)',\n\t\t\tleft: 'rotateX(0deg) rotateY(-90deg) translate3d(-50%, 0%, 80px)',\n\t\t\ttop: 'rotateX(90deg) rotateY(0deg) translate3d(0%, -50%, 80px)',\n\t\t\tbottom: 'rotateX(-90deg) rotateY(0deg) translate3d(0%, 50%, 280px)',\n\t\t\tback: 'rotateX(0deg) rotateY(180deg) translate3d(0%, 0%, 160px)',\n\t\t\tbackl: 'rotateX(0deg) rotateY(-180deg) translate3d(0%, 0%, 160px)',\n\t\t\tbackr: 'rotateX(0deg) rotateY(180deg) translate3d(0%, 0%, 160px)',\n\t\t};\n\n\t\tObject.values(Turns).forEach((turn) => {\n\t\t\texpect(sideTransformFactory.getSideCss(turn)).toBe(expectations[turn]);\n\t\t});\n\t});\n});\n"
  },
  {
    "path": "src/components/FluxCube/factories/SideTransformFactory.ts",
    "content": "import { type Ref, computed } from 'vue';\nimport { Size } from '../../../shared';\nimport type { Side, Turn } from '../types';\n\nconst rotate: {\n\tx: {\n\t\t[key: string]: string;\n\t};\n\ty: {\n\t\t[key: string]: string;\n\t};\n} = {\n\tx: {\n\t\ttop: '90',\n\t\tbottom: '-90',\n\t},\n\n\ty: {\n\t\tback: '180',\n\t\tbackr: '180',\n\t\tbackl: '-180',\n\t\tleft: '-90',\n\t\tright: '90',\n\t},\n};\n\nconst translate: {\n\tx: {\n\t\t[key: string]: string;\n\t};\n\ty: {\n\t\t[key: string]: string;\n\t};\n} = {\n\tx: {\n\t\tleft: '-50',\n\t\tright: '50',\n\t},\n\n\ty: {\n\t\ttop: '-50',\n\t\tbottom: '50',\n\t},\n};\n\nexport default class SideTransformFactory {\n\tdepth: number;\n\tsize: Size;\n\tviewSize: Size;\n\ttranslateZ: Ref<{ [key: string]: number }> = computed(() => {\n\t\tconst halfDepth = this.depth / 2;\n\n\t\tconst { width, height } = this.size.toValue();\n\t\tconst { width: viewWidth, height: viewHeight } = this.viewSize.toValue();\n\n\t\treturn {\n\t\t\tfront: 0,\n\t\t\tback: this.depth,\n\t\t\tbackr: this.depth,\n\t\t\tbackl: this.depth,\n\t\t\tleft: halfDepth,\n\t\t\tright: (viewWidth ?? width!) - halfDepth,\n\t\t\ttop: halfDepth,\n\t\t\tbottom: (viewHeight ?? height!) - halfDepth,\n\t\t};\n\t});\n\n\tconstructor(depth: number, size: Size, viewSize: Size) {\n\t\tthis.depth = depth;\n\t\tthis.size = size;\n\t\tthis.viewSize = viewSize;\n\t}\n\n\tpublic getRotate(turn: Side | Turn) {\n\t\tconst rx = rotate.x[turn] ?? '0';\n\t\tconst ry = rotate.y[turn] ?? '0';\n\n\t\treturn `rotateX(${rx}deg) rotateY(${ry}deg)`;\n\t}\n\n\tpublic getTranslate(side: Side | Turn) {\n\t\tconst tx = translate.x[side] ?? '0';\n\t\tconst ty = translate.y[side] ?? '0';\n\t\tconst tz = this.translateZ.value[side]!.toString();\n\n\t\treturn `translate3d(${tx}%, ${ty}%, ${tz}px)`;\n\t}\n\n\tpublic getSideCss(side: Side | Turn) {\n\t\treturn `${this.getRotate(side)} ${this.getTranslate(side)}`;\n\t}\n}\n"
  },
  {
    "path": "src/components/FluxCube/index.ts",
    "content": "export { default as FluxCube } from './FluxCube.vue';\nexport { default as Sides } from './Sides';\nexport { default as Turns } from './Turns';\n"
  },
  {
    "path": "src/components/FluxCube/types.ts",
    "content": "import type { CSSProperties, Component } from 'vue';\nimport { Resource } from '../../resources';\nimport { Position, Size } from '../../shared';\nimport type { ComponentProps, FluxComponent } from '../types';\nimport Sides from './Sides';\nimport Turns from './Turns';\n\nexport interface FluxCubeProps extends ComponentProps {\n\tcolors?: SidesColors;\n\trscs?: SidesResources;\n\toffsets?: SidesOffsets;\n\tdepth?: number;\n\torigin?: string;\n}\n\nexport type Side = keyof typeof Sides;\n\nexport type Turn = keyof typeof Turns;\n\nexport interface SidesColors {\n\t[Sides.front]?: string;\n\t[Sides.back]?: string;\n\t[Sides.left]?: string;\n\t[Sides.right]?: string;\n\t[Sides.top]?: string;\n\t[Sides.bottom]?: string;\n}\n\nexport interface SidesResources {\n\t[Sides.front]?: Resource;\n\t[Sides.back]?: Resource;\n\t[Sides.left]?: Resource;\n\t[Sides.right]?: Resource;\n\t[Sides.top]?: Resource;\n\t[Sides.bottom]?: Resource;\n}\n\nexport interface SidesOffsets {\n\t[Sides.front]?: Position;\n\t[Sides.back]?: Position;\n\t[Sides.left]?: Position;\n\t[Sides.right]?: Position;\n\t[Sides.top]?: Position;\n\t[Sides.bottom]?: Position;\n}\n\nexport interface SideProps {\n\tname: Side;\n\tcomponent: Component;\n\trsc?: Resource;\n\tsize: Size;\n\tviewSize: Size;\n\tcolor?: CSSProperties['color'];\n\toffset?: Position;\n\tstyle: CSSProperties;\n}\n\nexport interface SidesProps {\n\t[Sides.front]?: SideProps;\n\t[Sides.back]?: SideProps;\n\t[Sides.left]?: SideProps;\n\t[Sides.right]?: SideProps;\n\t[Sides.top]?: SideProps;\n\t[Sides.bottom]?: SideProps;\n}\n\nexport interface SidesComponents {\n\t[Sides.front]?: FluxComponent;\n\t[Sides.back]?: FluxComponent;\n\t[Sides.left]?: FluxComponent;\n\t[Sides.right]?: FluxComponent;\n\t[Sides.top]?: FluxComponent;\n\t[Sides.bottom]?: FluxComponent;\n}\n"
  },
  {
    "path": "src/components/FluxGrid/FluxGrid.vue",
    "content": "<script setup lang=\"ts\">\n\timport { ref, reactive, computed, type Ref, onBeforeUpdate } from 'vue';\n\timport useComponent from '../useComponent';\n\timport type { FluxGridProps } from './types';\n\timport { FluxCube } from '../';\n\timport type { ComponentStyles, FluxComponent } from '../types';\n\timport { GridFactory, getRowNumber, getColNumber } from './factories';\n\n\tconst props = withDefaults(defineProps<FluxGridProps>(), {\n\t\trows: 1,\n\t\tcols: 1,\n\t\tdepth: 0,\n\t});\n\n\tconst $el: Ref<null | HTMLDivElement> = ref(null);\n\n\tconst componentStyles: ComponentStyles = reactive({\n\t\tbase: {\n\t\t\tposition: 'relative',\n\t\t},\n\t});\n\n\tconst { style, setCss, show, hide } = useComponent($el, props, componentStyles);\n\n\tconst component = computed(() =>\n\t\tprops.rscs !== undefined ? FluxCube : props.rsc!.transition.component,\n\t);\n\n\tconst tiles = computed(() => GridFactory.getTilesProps(props));\n\n\tconst $tiles: Ref<FluxComponent[]> = ref([]);\n\n\tonBeforeUpdate(() => {\n\t\t$tiles.value = [];\n\t});\n\n\tconst transform = <T,>(cb: (tile: T, index: number) => void) => {\n\t\t$tiles.value.forEach((tile: unknown, index: number) => cb(tile as T, index));\n\t};\n\n\tdefineExpose({\n\t\tsetCss,\n\t\ttransform,\n\t\tshow,\n\t\thide,\n\t\tgetRowNumber,\n\t\tgetColNumber,\n\t});\n</script>\n\n<template>\n\t<div ref=\"$el\" class=\"flux-grid\" :style=\"style\">\n\t\t<component\n\t\t\t:is=\"component\"\n\t\t\tv-for=\"(tile, index) in tiles\"\n\t\t\t:ref=\"(el: FluxComponent) => $tiles.push(el)\"\n\t\t\t:key=\"index\"\n\t\t\tv-bind=\"tile\"\n\t\t/>\n\t</div>\n</template>\n"
  },
  {
    "path": "src/components/FluxGrid/__mocks__/FluxGrid.vue",
    "content": "<script setup lang=\"ts\">\n\timport { onBeforeUpdate, ref, type Ref } from 'vue';\n\timport { vi } from 'vitest';\n\timport Tile from './Tile.vue';\n\timport type { FluxGridProps } from '../types';\n\timport { getRowNumber, getColNumber } from '../factories';\n\n\tconst props = withDefaults(defineProps<FluxGridProps>(), {\n\t\trows: 1,\n\t\tcols: 1,\n\t\tdepth: 0,\n\t});\n\n\tconst $el: Ref<null | HTMLDivElement> = ref(null);\n\n\tconst numTiles = props.rows * props.cols;\n\tconst $tiles: Ref<InstanceType<typeof Tile>[]> = ref([]);\n\n\tonBeforeUpdate(() => {\n\t\t$tiles.value = [];\n\t});\n\n\tconst transform = vi\n\t\t.fn()\n\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t\t.mockImplementation((cb: (tile: any, index: number) => void) => {\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t\t\t$tiles.value.forEach((tile: any, index: number) => cb(tile, index));\n\t\t});\n\n\tdefineExpose({\n\t\tsetCss: vi.fn(),\n\t\ttransform,\n\t\tshow: vi.fn(),\n\t\thide: vi.fn(),\n\t\tgetRowNumber: vi\n\t\t\t.fn()\n\t\t\t.mockImplementation((index: number, numCols: number) => getRowNumber(index, numCols)),\n\t\tgetColNumber: vi\n\t\t\t.fn()\n\t\t\t.mockImplementation((index: number, numCols: number) => getColNumber(index, numCols)),\n\t\t$tiles,\n\t});\n</script>\n\n<template>\n\t<div ref=\"$el\" class=\"flux-grid\">\n\t\t<Tile v-for=\"index in numTiles\" :ref=\"(el: any) => $tiles.push(el)\" :key=\"index\" />\n\t</div>\n</template>\n"
  },
  {
    "path": "src/components/FluxGrid/__mocks__/Tile.vue",
    "content": "<script setup lang=\"ts\">\n\timport { ref, type Ref } from 'vue';\n\timport { vi } from 'vitest';\n\n\tconst $el: Ref<null | HTMLDivElement> = ref(null);\n\n\tdefineExpose({\n\t\tturn: vi.fn(),\n\t\tsetCss: vi.fn(),\n\t\ttransform: vi.fn(),\n\t\tshow: vi.fn(),\n\t\thide: vi.fn(),\n\t\tgetRowNumber: vi.fn(),\n\t\tgetColNumber: vi.fn(),\n\t});\n</script>\n\n<template>\n\t<div ref=\"$el\" />\n</template>\n"
  },
  {
    "path": "src/components/FluxGrid/factories/GridFactory.ts",
    "content": "import { Size } from '../../../shared';\nimport GridTileFactory from './GridTileFactory';\nimport type { FluxGridProps, FluxGridTileProps } from '../types';\n\nexport default class GridFactory {\n\tstatic getTilesProps(props: FluxGridProps) {\n\t\tconst { rows, cols, size, color, colors, rsc, rscs, depth } = props;\n\n\t\tconst numRows = Math.ceil(rows!);\n\t\tconst numCols = Math.ceil(cols!);\n\n\t\tconst grid = {\n\t\t\tnumRows,\n\t\t\tnumCols,\n\t\t\tnumTiles: numRows * numCols,\n\t\t\tsize,\n\t\t\tdepth: depth!,\n\t\t\tcolor,\n\t\t\tcolors,\n\t\t\trsc,\n\t\t\trscs,\n\t\t};\n\n\t\tconst tile = {\n\t\t\tnumber: 0,\n\t\t\tsize: new Size({\n\t\t\t\twidth: Math.floor(size.width.value! / numCols),\n\t\t\t\theight: Math.floor(size.height.value! / numRows),\n\t\t\t}),\n\t\t\tcss: props.tileCss,\n\t\t};\n\n\t\tconst tilesProps: FluxGridTileProps[] = [];\n\n\t\tfor (let tileNumber = 0; tileNumber < grid.numTiles; tileNumber++) {\n\t\t\ttile.number = tileNumber;\n\t\t\ttilesProps.push(GridTileFactory.getProps(grid, tile));\n\t\t}\n\n\t\treturn tilesProps;\n\t}\n}\n"
  },
  {
    "path": "src/components/FluxGrid/factories/GridTileFactory.ts",
    "content": "import type { CSSProperties } from 'vue';\nimport { Resource } from '../../../resources';\nimport { Size, Position } from '../../../shared';\nimport type { SidesColors, SidesResources } from '../../FluxCube/types';\nimport type { FluxGridTileProps } from '../types';\n\nexport function getRowNumber(tileNumber: number, numCols: number) {\n\treturn Math.floor(tileNumber / numCols);\n}\n\nexport function getColNumber(tileNumber: number, numCols: number) {\n\treturn tileNumber % numCols;\n}\n\nexport default class GridTileFactory {\n\tstatic getProps(\n\t\tgrid: {\n\t\t\tnumRows: number;\n\t\t\tnumCols: number;\n\t\t\tnumTiles: number;\n\t\t\tsize: Size;\n\t\t\tdepth: number;\n\t\t\tcolor?: CSSProperties['color'];\n\t\t\tcolors?: SidesColors;\n\t\t\trsc?: Resource;\n\t\t\trscs?: SidesResources;\n\t\t},\n\t\ttile: {\n\t\t\tnumber: number;\n\t\t\tsize: Size;\n\t\t\tcss?: CSSProperties;\n\t\t},\n\t) {\n\t\tlet { width, height } = tile.size.toValue();\n\n\t\tconst row = getRowNumber(tile.number, grid.numCols);\n\t\tconst col = getColNumber(tile.number, grid.numCols);\n\n\t\tconst props: FluxGridTileProps = {\n\t\t\tcolor: grid.color,\n\t\t\tcolors: grid.colors,\n\t\t\trsc: grid.rsc,\n\t\t\trscs: grid.rscs,\n\t\t\tsize: grid.size,\n\t\t\tdepth: grid.depth,\n\t\t\toffset: new Position({\n\t\t\t\ttop: row * height!,\n\t\t\t\tleft: col * width!,\n\t\t\t}),\n\t\t};\n\n\t\tif (row + 1 === grid.numRows) {\n\t\t\theight = grid.size.height.value! - row * height!;\n\t\t}\n\n\t\tif (col + 1 === grid.numCols) {\n\t\t\twidth = grid.size.width.value! - col * width!;\n\t\t}\n\n\t\tprops.viewSize = new Size({\n\t\t\twidth,\n\t\t\theight,\n\t\t});\n\n\t\tprops.css = {\n\t\t\t...tile.css,\n\t\t\tposition: 'absolute',\n\t\t\t...props.offset.toPx(),\n\t\t\tzIndex:\n\t\t\t\ttile.number + 1 < grid.numTiles / 2 ? tile.number + 1 : grid.numTiles - tile.number,\n\t\t};\n\n\t\treturn props;\n\t}\n}\n"
  },
  {
    "path": "src/components/FluxGrid/factories/index.ts",
    "content": "export { default as GridFactory } from './GridFactory';\nexport {\n\tdefault as GridTileFactory,\n\tgetRowNumber,\n\tgetColNumber,\n} from './GridTileFactory';\n"
  },
  {
    "path": "src/components/FluxGrid/types.ts",
    "content": "import type { CSSProperties } from 'vue';\nimport { Position, Size } from '../../shared';\nimport { Resource } from '../../resources';\nimport type { SidesColors, SidesResources } from '../FluxCube/types';\nimport type { ComponentProps } from '../types';\n\nexport interface FluxGridProps extends ComponentProps {\n\tcolors?: SidesColors;\n\trscs?: SidesResources;\n\trows?: number;\n\tcols?: number;\n\tdepth?: number;\n\ttileCss?: CSSProperties;\n}\n\nexport interface FluxGridTileProps {\n\tcolor?: CSSProperties['color'];\n\tcolors?: SidesColors;\n\trsc?: Resource;\n\trscs?: SidesResources;\n\tsize: Size;\n\tdepth: number;\n\toffset: Position;\n\tviewSize?: Size;\n\tcss?: CSSProperties;\n}\n"
  },
  {
    "path": "src/components/FluxImage/FluxImage.vue",
    "content": "<script setup lang=\"ts\">\n\timport { ref, type Ref, reactive, computed, type CSSProperties } from 'vue';\n\timport useComponent from '../useComponent';\n\timport type { FluxImageProps } from './types';\n\timport type { ComponentStyles } from '../types';\n\timport { Statuses } from '../../resources';\n\n\tconst props = defineProps<FluxImageProps>();\n\n\tconst $el: Ref<null | HTMLDivElement> = ref(null);\n\n\tconst componentStyles: ComponentStyles = reactive({\n\t\tbase: {\n\t\t\toverflow: 'hidden',\n\t\t},\n\n\t\tcolor: computed<CSSProperties>(() => {\n\t\t\tconst colorStyle: CSSProperties = {};\n\n\t\t\tif (props.color !== undefined) {\n\t\t\t\tcolorStyle.backgroundColor = props.color;\n\t\t\t}\n\n\t\t\tif (props.rsc?.backgroundColor !== null) {\n\t\t\t\tcolorStyle.backgroundColor = props.rsc?.backgroundColor;\n\t\t\t}\n\n\t\t\treturn colorStyle;\n\t\t}),\n\n\t\trsc: computed<CSSProperties>(() => {\n\t\t\tconst { rsc, size, offset } = props;\n\n\t\t\tif (!rsc) {\n\t\t\t\treturn {};\n\t\t\t}\n\n\t\t\tif (rsc.status.value === Statuses.notLoaded) {\n\t\t\t\trsc.load();\n\t\t\t\treturn {};\n\t\t\t}\n\n\t\t\tif (!rsc.isLoaded() || !size.isValid() || !$el.value) {\n\t\t\t\treturn {};\n\t\t\t}\n\n\t\t\tconst { width, height, top, left } = rsc.getResizeProps(size, offset);\n\n\t\t\treturn {\n\t\t\t\tbackgroundImage: `url(${rsc.src})`,\n\t\t\t\tbackgroundSize: `${width}px ${height}px`,\n\t\t\t\tbackgroundPosition: `${left}px ${top}px`,\n\t\t\t\tbackgroundRepeat: 'no-repeat',\n\t\t\t};\n\t\t}),\n\t});\n\n\tconst { style, setCss, transform, show, hide } = useComponent($el, props, componentStyles);\n\n\tdefineExpose({\n\t\tsetCss,\n\t\ttransform,\n\t\tshow,\n\t\thide,\n\t});\n</script>\n\n<template>\n\t<div ref=\"$el\" class=\"flux-image\" :style=\"style\" />\n</template>\n"
  },
  {
    "path": "src/components/FluxImage/__mocks__/FluxImage.vue",
    "content": "<script setup lang=\"ts\">\n\timport { ref, type Ref } from 'vue';\n\timport { vi } from 'vitest';\n\timport type { FluxImageProps } from '../types';\n\n\tdefineProps<FluxImageProps>();\n\n\tconst $el: Ref<null | HTMLDivElement> = ref(null);\n\n\tdefineExpose({\n\t\tsetCss: vi.fn(),\n\t\ttransform: vi.fn(),\n\t\tshow: vi.fn(),\n\t\thide: vi.fn(),\n\t});\n</script>\n\n<template>\n\t<div ref=\"$el\" class=\"flux-image\" />\n</template>\n"
  },
  {
    "path": "src/components/FluxImage/types.ts",
    "content": "import type { ComponentProps } from '../types';\n\nexport interface FluxImageProps extends ComponentProps {}\n"
  },
  {
    "path": "src/components/FluxParallax/FluxParallax.vue",
    "content": "<script setup lang=\"ts\">\n\t// holder (window), component, background\n\n\timport {\n\t\tref,\n\t\treactive,\n\t\tcomputed,\n\t\tunref,\n\t\tonMounted,\n\t\tonUnmounted,\n\t\ttype Ref,\n\t\ttype CSSProperties,\n\t} from 'vue';\n\timport { Maths } from '../../shared';\n\timport type { DisplayProps, FluxParallaxProps, FluxParallaxStyles, ViewProps } from './types';\n\n\tconst { aspectRatio } = Maths;\n\n\tconst props = withDefaults(defineProps<FluxParallaxProps>(), {\n\t\tholder: () => window,\n\t\ttype: 'relative',\n\t\toffset: '100%',\n\t});\n\n\tconst $el: Ref<null | HTMLDivElement> = ref(null);\n\n\tconst { holder, rsc } = props;\n\n\tconst style: FluxParallaxStyles = {\n\t\tbase: {\n\t\t\tposition: 'relative',\n\t\t\tbackground: `url(\"${rsc.src}\") no-repeat`,\n\t\t},\n\n\t\tdefined: reactive({}),\n\n\t\tfinal: computed(() => ({\n\t\t\t...style.base,\n\t\t\t...unref(style.defined),\n\t\t})),\n\t};\n\n\tconst isIos =\n\t\t/iPad|iPhone|iPod/.test(navigator.userAgent) ||\n\t\t(navigator.userAgent === 'MacIntel' && navigator.maxTouchPoints > 1);\n\n\tconst display: DisplayProps = reactive({\n\t\twidth: 0,\n\t\theight: 0,\n\t\taspectRatio: computed(() => aspectRatio(display)),\n\t});\n\n\tconst view: ViewProps = reactive({\n\t\ttop: 0,\n\t\twidth: 0,\n\t\theight: 0,\n\t\taspectRatio: computed(() => aspectRatio(view)),\n\t});\n\n\tconst background = reactive({\n\t\ttop: 0,\n\t\tleft: 0,\n\t\twidth: 0,\n\t\theight: 0,\n\t});\n\n\tconst fixedParentStyle: CSSProperties = {\n\t\tposition: 'absolute',\n\t\ttop: 0,\n\t\tleft: 0,\n\t\tbottom: 0,\n\t\tright: 0,\n\t\tclip: 'rect(auto auto auto auto)',\n\t};\n\n\tconst fixedChildStyle = computed<CSSProperties>(() => ({\n\t\tposition: 'absolute',\n\t\ttop: 0,\n\t\tbottom: 0,\n\t\tleft: 0,\n\t\tright: 0,\n\t\tbackground: `url(\"${rsc.src}\") no-repeat center center fixed`,\n\t\tbackgroundSize: `${background.width}px ${background.height}px`,\n\t}));\n\n\tconst offsetHeight = computed(() => {\n\t\tconst { offset } = props;\n\t\tconst offsetValue = parseFloat(offset);\n\n\t\tif (/^[0-9]+px$/.test(offset)) {\n\t\t\treturn {\n\t\t\t\tpx: offsetValue,\n\t\t\t\tpct: (offsetValue * 100) / background.height,\n\t\t\t};\n\t\t}\n\n\t\tif (/^[0-9]+%$/.test(offset)) {\n\t\t\treturn {\n\t\t\t\tpx: Math.ceil((view.height * offsetValue) / 100),\n\t\t\t\tpct: offsetValue,\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tpx: 0,\n\t\t\tpct: 0,\n\t\t};\n\t});\n\n\tconst remainderHeight = computed(() => {\n\t\tconst effectHeight = isIos ? display.height : view.height + offsetHeight.value.px;\n\n\t\treturn background.height - effectHeight;\n\t});\n\n\tonMounted(() => {\n\t\twindow.addEventListener('resize', resize, {\n\t\t\tpassive: true,\n\t\t});\n\n\t\tif (props.type !== 'fixed' || isIos) {\n\t\t\tholder.addEventListener('scroll', onScroll, {\n\t\t\t\tpassive: true,\n\t\t\t});\n\t\t}\n\n\t\trsc.load().then(() => {\n\t\t\tresize();\n\t\t});\n\t});\n\n\tonUnmounted(() => {\n\t\twindow.removeEventListener('resize', resize);\n\t\tholder.removeEventListener('scroll', onScroll);\n\t});\n\n\tconst resize = () => {\n\t\t// @ts-expect-error:next-line\n\t\tdisplay.width = holder.scrollWidth || holder.innerWidth;\n\t\t// @ts-expect-error:next-line\n\t\tdisplay.height = holder.scrollHeight || holder.innerHeight;\n\n\t\tview.width = $el.value!.clientWidth;\n\t\tview.height = $el.value!.clientHeight;\n\t\tview.top = $el.value!.getBoundingClientRect().top + window.scrollY;\n\n\t\trsc.displaySize.update(display);\n\t\tconst fillProps = rsc.resizeProps.value;\n\n\t\tbackground.width = fillProps.width!;\n\t\tbackground.height = fillProps.height!;\n\n\t\tstyle.defined.backgroundSize = `${background.width}px ${background.height}px`;\n\t\tstyle.defined.backgroundPosition = `center 0`;\n\n\t\tonScroll();\n\t};\n\n\tconst moveBackgroundByPct = (pct: number) => {\n\t\tif (remainderHeight.value > 0)\n\t\t\tpct = (pct * offsetHeight.value.pct) / 100 + 50 - offsetHeight.value.pct / 2;\n\n\t\tstyle.defined.backgroundPositionY = pct.toFixed(2) + '%';\n\t};\n\n\tconst onScroll = () => {\n\t\tif (!rsc.isLoaded() || (!isIos && props.type === 'fixed')) {\n\t\t\treturn;\n\t\t}\n\n\t\t// @ts-expect-error:next-line\n\t\tconst scrollTop = holder.scrollY || holder.scrollTop || 0;\n\n\t\tif (holder !== window) {\n\t\t\treturn handle.relative(scrollTop);\n\t\t}\n\n\t\tif (scrollTop + display.height < view.top) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (scrollTop > view.top + view.height) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst positionY = scrollTop - view.top + display.height;\n\n\t\thandle[props.type](positionY);\n\t};\n\n\tconst handle = {\n\t\tvisible: (positionY: number) => {\n\t\t\tlet pct = 0;\n\n\t\t\tif (positionY < view.height) {\n\t\t\t\tpct = 0;\n\t\t\t} else if (positionY > display.height) {\n\t\t\t\tpct = 100;\n\t\t\t} else {\n\t\t\t\tpct = ((positionY - view.height) * 100) / (display.height - view.height);\n\t\t\t}\n\n\t\t\tmoveBackgroundByPct(pct);\n\t\t},\n\n\t\trelative: (positionY: number) => {\n\t\t\tlet pct;\n\n\t\t\tif (holder === window) {\n\t\t\t\tpct = (positionY * 100) / (display.height + view.height);\n\t\t\t} else {\n\t\t\t\t// @ts-expect-error:next-line\n\t\t\t\tpct = (positionY * 100) / (display.height - holder.clientHeight);\n\t\t\t}\n\n\t\t\tmoveBackgroundByPct(pct);\n\t\t},\n\n\t\tfixed: (positionY: number) => {\n\t\t\tstyle.defined.backgroundPositionY = positionY - display.height + 'px';\n\t\t},\n\t};\n\n\tdefineExpose({\n\t\tresize,\n\t});\n</script>\n\n<template>\n\t<div ref=\"$el\" class=\"flux-parallax\" :style=\"style.final.value\">\n\t\t<div v-if=\"props.type === 'fixed' && !isIos\" :style=\"fixedParentStyle\">\n\t\t\t<div class=\"image\" :style=\"fixedChildStyle\" />\n\t\t</div>\n\n\t\t<slot />\n\t</div>\n</template>\n"
  },
  {
    "path": "src/components/FluxParallax/types.ts",
    "content": "import type { CSSProperties, ComputedRef } from 'vue';\nimport { Resource } from '../../resources';\n\nexport interface FluxParallaxProps {\n\trsc: Resource;\n\tholder?: Window | Element;\n\ttype?: 'visible' | 'relative' | 'fixed';\n\toffset?: string;\n}\n\nexport interface FluxParallaxStyles {\n\tbase: CSSProperties;\n\tdefined: CSSProperties;\n\tfinal: ComputedRef<CSSProperties>;\n}\n\nexport interface DisplayProps {\n\twidth: number;\n\theight: number;\n\taspectRatio: number;\n}\n\nexport interface ViewProps {\n\ttop: number;\n\twidth: number;\n\theight: number;\n\taspectRatio: number;\n}\n"
  },
  {
    "path": "src/components/FluxTransition/FluxTransition.vue",
    "content": "<script setup lang=\"ts\">\n\timport {\n\t\tref,\n\t\treactive,\n\t\tcomputed,\n\t\tonMounted,\n\t\tonUnmounted,\n\t\tnextTick,\n\t\ttype Ref,\n\t\ttype Component,\n\t} from 'vue';\n\timport type { FluxTransitionProps } from './types';\n\timport type { TransitionComponent } from '../../transitions';\n\n\tconst props = withDefaults(defineProps<FluxTransitionProps>(), {\n\t\toptions: () => ({}),\n\t});\n\n\tconst $el: Ref<null | HTMLDivElement> = ref(null);\n\tconst $transition: Ref<null | Component> = ref(null);\n\n\tconst emit = defineEmits(['ready', 'start', 'end']);\n\n\tconst styles = reactive({\n\t\tbase: {\n\t\t\toverflow: 'hidden',\n\t\t\tperspective: 'none',\n\t\t\tzIndex: 3,\n\t\t},\n\t});\n\n\tconst style = computed(() => {\n\t\tconst { width, height } = props.size.toPx();\n\n\t\treturn {\n\t\t\t...styles.base,\n\t\t\twidth,\n\t\t\theight,\n\t\t};\n\t});\n\n\tconst duration = ref(1);\n\n\tonMounted(async () => {\n\t\tawait nextTick();\n\n\t\tif ($transition.value !== null) {\n\t\t\tduration.value = ($transition.value as TransitionComponent).totalDuration;\n\t\t}\n\n\t\temit('ready', {\n\t\t\ttransition: props.transition,\n\t\t\tfrom: props.from,\n\t\t\tto: props.to,\n\t\t\toptions: props.options,\n\t\t\tduration: duration.value,\n\t\t});\n\t});\n\n\tasync function start() {\n\t\temit('start', {\n\t\t\ttransition: props.transition,\n\t\t\tfrom: props.from,\n\t\t\tto: props.to,\n\t\t\toptions: props.options,\n\t\t\tduration: duration.value,\n\t\t});\n\n\t\tawait nextTick();\n\n\t\tif ($transition.value === null) {\n\t\t\tconsole.error('Transition component not available', props.transition);\n\t\t} else {\n\t\t\t($transition.value as TransitionComponent).onPlay();\n\t\t}\n\n\t\tsetTimeout(() => end(), duration.value);\n\t}\n\n\tfunction end() {\n\t\temit('end', {\n\t\t\ttransition: props.transition,\n\t\t\tfrom: props.from,\n\t\t\tto: props.to,\n\t\t\toptions: props.options,\n\t\t\tduration: duration.value,\n\t\t});\n\t}\n\n\tonUnmounted(() => {\n\t\tif (props.displayComponent) {\n\t\t\tprops.displayComponent.show();\n\t\t}\n\t});\n\n\tdefineExpose({ start });\n</script>\n\n<template>\n\t<div ref=\"$el\" class=\"flux-transition\" :style=\"style\">\n\t\t<component\n\t\t\t:is=\"transition\"\n\t\t\tref=\"$transition\"\n\t\t\t:size=\"size\"\n\t\t\t:from=\"from\"\n\t\t\t:to=\"to\"\n\t\t\t:display-component=\"displayComponent\"\n\t\t\t:options=\"options\"\n\t\t\t:mask-style=\"styles.base\"\n\t\t/>\n\t</div>\n</template>\n\n<style lang=\"scss\">\n\t.flux-transition {\n\t\tposition: relative;\n\t}\n</style>\n"
  },
  {
    "path": "src/components/FluxTransition/types.ts",
    "content": "import { Resource } from '../../resources';\nimport { Size } from '../../shared';\nimport type { FluxComponent } from '../types';\n\nexport interface FluxTransitionProps {\n\tsize: Size;\n\ttransition: object;\n\tfrom: Resource;\n\tto: Resource;\n\tdisplayComponent?: null | FluxComponent;\n\toptions?: object;\n}\n"
  },
  {
    "path": "src/components/FluxVortex/FluxVortex.vue",
    "content": "<script setup lang=\"ts\">\n\timport { ref, reactive, computed, type Ref, onBeforeUpdate } from 'vue';\n\timport useComponent from '../useComponent';\n\timport type { ComponentStyles, FluxComponent } from '../types';\n\timport type { FluxVortexProps } from './types';\n\timport { VortexFactory } from './factories';\n\n\tconst props = withDefaults(defineProps<FluxVortexProps>(), {\n\t\tcircles: 1,\n\t});\n\n\tconst $el: Ref<null | HTMLDivElement> = ref(null);\n\n\tconst componentStyles: ComponentStyles = reactive({\n\t\tbase: {\n\t\t\tposition: 'relative',\n\t\t\toverflow: 'hidden',\n\t\t},\n\t});\n\n\tconst { style, setCss, show, hide } = useComponent($el, props, componentStyles);\n\n\tconst tiles = computed(() => VortexFactory.getCirclesProps(props));\n\n\tconst $tiles: Ref<FluxComponent[]> = ref([]);\n\n\tonBeforeUpdate(() => {\n\t\t$tiles.value = [];\n\t});\n\n\tconst transform = <T,>(cb: (tile: T, index: number) => void) => {\n\t\t$tiles.value.forEach((tile: unknown, index: number) => cb(tile as T, index));\n\t};\n\n\tdefineExpose({\n\t\tsetCss,\n\t\ttransform,\n\t\tshow,\n\t\thide,\n\t});\n</script>\n\n<template>\n\t<div ref=\"$el\" class=\"flux-vortex\" :style=\"style\">\n\t\t<component\n\t\t\t:is=\"rsc.transition.component\"\n\t\t\tv-for=\"(tile, index) in tiles\"\n\t\t\t:ref=\"(el: any) => $tiles.push(el)\"\n\t\t\t:key=\"index\"\n\t\t\t:size=\"size\"\n\t\t\t:rsc=\"rsc\"\n\t\t\t:offset=\"tile.offset\"\n\t\t\t:css=\"tile.css\"\n\t\t/>\n\t</div>\n</template>\n"
  },
  {
    "path": "src/components/FluxVortex/__mocks__/FluxVortex.vue",
    "content": "<script setup lang=\"ts\">\n\timport { ref, type Ref, onBeforeUpdate } from 'vue';\n\timport { vi } from 'vitest';\n\timport Tile from './Tile.vue';\n\timport type { FluxVortexProps } from '../types';\n\n\tconst props = withDefaults(defineProps<FluxVortexProps>(), {\n\t\tcircles: 1,\n\t});\n\n\tconst $el: Ref<null | HTMLDivElement> = ref(null);\n\n\tconst numTiles = props.circles;\n\tconst $tiles: Ref<InstanceType<typeof Tile>[]> = ref([]);\n\n\tonBeforeUpdate(() => {\n\t\t$tiles.value = [];\n\t});\n\n\tconst transform = vi\n\t\t.fn()\n\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t\t.mockImplementation((cb: (tile: any, index: number) => void) => {\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t\t\t$tiles.value.forEach((tile: any, index: number) => cb(tile, index));\n\t\t});\n\n\tdefineExpose({\n\t\tsetCss: vi.fn(),\n\t\ttransform,\n\t\tshow: vi.fn(),\n\t\thide: vi.fn(),\n\t\tgetRowNumber: vi.fn(),\n\t\tgetColNumber: vi.fn(),\n\t\t$tiles,\n\t});\n</script>\n\n<template>\n\t<div ref=\"$el\" class=\"flux-vortex\">\n\t\t<Tile v-for=\"index in numTiles\" :ref=\"(el: any) => $tiles.push(el)\" :key=\"index\" />\n\t</div>\n</template>\n"
  },
  {
    "path": "src/components/FluxVortex/__mocks__/Tile.vue",
    "content": "<script setup lang=\"ts\">\n\timport { ref, type Ref } from 'vue';\n\timport { vi } from 'vitest';\n\n\tconst $el: Ref<null | HTMLDivElement> = ref(null);\n\n\tdefineExpose({\n\t\tturn: vi.fn(),\n\t\tsetCss: vi.fn(),\n\t\ttransform: vi.fn(),\n\t\tshow: vi.fn(),\n\t\thide: vi.fn(),\n\t\tgetRowNumber: vi.fn(),\n\t\tgetColNumber: vi.fn(),\n\t});\n</script>\n\n<template>\n\t<div ref=\"$el\" />\n</template>\n"
  },
  {
    "path": "src/components/FluxVortex/factories/VortexCircleFactory.ts",
    "content": "import type { CSSProperties } from 'vue';\nimport { Position } from '../../../shared';\nimport type { FluxVortexCirclesProps } from '../types';\n\nexport default class VortexCircleFactory {\n\tstatic getProps(\n\t\tvortex: {\n\t\t\tnumCircles: number;\n\t\t\tdiagonal: number;\n\t\t\tradius: number;\n\t\t\ttopGap: number;\n\t\t\tleftGap: number;\n\t\t},\n\t\tcircleNumber: number,\n\t\tcircleCss?: CSSProperties,\n\t) {\n\t\tconst size = (vortex.numCircles - circleNumber) * vortex.radius * 2;\n\n\t\tconst gap = vortex.radius * circleNumber;\n\n\t\tconst offset = new Position({\n\t\t\ttop: vortex.topGap + gap,\n\t\t\tleft: vortex.leftGap + gap,\n\t\t});\n\n\t\tconst circle: FluxVortexCirclesProps = {\n\t\t\toffset: offset,\n\t\t\tcss: {\n\t\t\t\t...circleCss,\n\t\t\t\t...offset.toPx(),\n\t\t\t\tposition: 'absolute',\n\t\t\t\twidth: size + 'px',\n\t\t\t\theight: size + 'px',\n\t\t\t\tbackgroundRepeat: 'repeat',\n\t\t\t\tborderRadius: '50%',\n\t\t\t\tzIndex: circleNumber,\n\t\t\t},\n\t\t};\n\n\t\treturn circle;\n\t}\n}\n"
  },
  {
    "path": "src/components/FluxVortex/factories/VortexFactory.ts",
    "content": "import { Maths } from '../../../shared';\nimport type { FluxVortexProps, FluxVortexCirclesProps } from '../types';\nimport VortexCircleFactory from './VortexCircleFactory';\n\nexport default class VortexFactory {\n\tstatic getCirclesProps(props: FluxVortexProps) {\n\t\tconst { width, height } = props.size.toValue();\n\n\t\tconst numCircles = Math.round(props.circles!);\n\t\tconst diagonal = Maths.diag({ width: width!, height: height! });\n\t\tconst radius = Math.ceil(diagonal / 2 / numCircles);\n\t\tconst topGap = Math.ceil(height! / 2 - radius * numCircles);\n\t\tconst leftGap = Math.ceil(width! / 2 - radius * numCircles);\n\n\t\tconst vortex = {\n\t\t\tnumCircles,\n\t\t\tdiagonal,\n\t\t\tradius,\n\t\t\ttopGap,\n\t\t\tleftGap,\n\t\t};\n\n\t\tconst circlesProps: FluxVortexCirclesProps[] = [];\n\n\t\tfor (let circleNumber = 0; circleNumber < numCircles; circleNumber++) {\n\t\t\tcirclesProps.push(VortexCircleFactory.getProps(vortex, circleNumber, props.tileCss));\n\t\t}\n\n\t\treturn circlesProps;\n\t}\n}\n"
  },
  {
    "path": "src/components/FluxVortex/factories/index.ts",
    "content": "export { default as VortexFactory } from './VortexFactory';\nexport { default as VortexCircleFactory } from './VortexCircleFactory';\n"
  },
  {
    "path": "src/components/FluxVortex/types.ts",
    "content": "import type { CSSProperties } from 'vue';\nimport { Resource } from '../../resources';\nimport type { ComponentProps } from '../types';\nimport { Position } from '../../shared';\n\nexport interface FluxVortexProps extends ComponentProps {\n\trsc: Resource;\n\tcircles?: number;\n\ttileCss?: CSSProperties;\n}\n\nexport interface FluxVortexCirclesProps {\n\toffset: Position;\n\tcss: CSSProperties;\n}\n"
  },
  {
    "path": "src/components/FluxWrapper/FluxWrapper.vue",
    "content": "<script setup lang=\"ts\">\n\timport { ref, reactive, type Ref } from 'vue';\n\timport useComponent from '../useComponent';\n\timport type { ComponentStyles } from '../types';\n\timport type { FluxWrapperProps } from './types';\n\n\tconst props = defineProps<FluxWrapperProps>();\n\n\tconst $el: Ref<null | HTMLDivElement> = ref(null);\n\n\tconst componentStyles: ComponentStyles = reactive({\n\t\tbase: {\n\t\t\toverflow: 'hidden',\n\t\t},\n\t});\n\n\tconst { style, setCss, transform, show, hide } = useComponent($el, props, componentStyles);\n\n\tdefineExpose({\n\t\tsetCss,\n\t\ttransform,\n\t\tshow,\n\t\thide,\n\t});\n</script>\n\n<template>\n\t<div ref=\"$el\" class=\"flux-wrapper\" :style=\"style\">\n\t\t<slot />\n\t</div>\n</template>\n"
  },
  {
    "path": "src/components/FluxWrapper/__mocks__/FluxWrapper.vue",
    "content": "<script setup lang=\"ts\">\n\timport { ref, type Ref } from 'vue';\n\timport { vi } from 'vitest';\n\timport type { FluxWrapperProps } from '../types';\n\n\tdefineProps<FluxWrapperProps>();\n\n\tconst $el: Ref<null | HTMLDivElement> = ref(null);\n\n\tdefineExpose({\n\t\tsetCss: vi.fn(),\n\t\ttransform: vi.fn(),\n\t\tshow: vi.fn(),\n\t\thide: vi.fn(),\n\t});\n</script>\n\n<template>\n\t<div ref=\"$el\" class=\"flux-wrapper\">\n\t\t<slot />\n\t</div>\n</template>\n"
  },
  {
    "path": "src/components/FluxWrapper/types.ts",
    "content": "import type { ComponentProps } from '../types';\n\nexport interface FluxWrapperProps extends ComponentProps {}\n"
  },
  {
    "path": "src/components/VueFlux/VueFlux.vue",
    "content": "<script setup lang=\"ts\">\n\timport { onMounted, onUnmounted, ref, reactive, computed, watch, type Ref, toRaw } from 'vue';\n\timport * as Controllers from '../../controllers';\n\timport { type FluxComponent, FluxTransition } from '../';\n\timport type { VueFluxProps, VueFluxEmits, VueFluxConfig } from './types';\n\timport { default as PlayerStatuses } from '../../controllers/Player/Statuses';\n\n\tconst props = withDefaults(defineProps<VueFluxProps>(), {\n\t\toptions: () => ({}),\n\t});\n\n\tconst emit = defineEmits<VueFluxEmits>();\n\n\tconst $el: Ref<null | HTMLDivElement> = ref(null);\n\tconst $transition: Ref<null | InstanceType<typeof FluxTransition>> = ref(null);\n\tconst $displayComponent: Ref<null | FluxComponent> = ref(null);\n\n\tconst config: VueFluxConfig = reactive({\n\t\tallowFullscreen: false,\n\t\tallowToSkipTransition: true,\n\t\taspectRatio: '16:9',\n\t\tautohideTime: 2500,\n\t\tautoplay: false,\n\t\tbindKeys: false,\n\t\tdelay: 5000,\n\t\tenableGestures: false,\n\t\tinfinite: true,\n\t\tlazyLoad: true,\n\t\tlazyLoadAfter: 5,\n\t});\n\n\tconst timers = new Controllers.Timers();\n\tconst player = new Controllers.Player(config, timers, emit);\n\tconst resources = player.resources;\n\tconst transitions = player.transitions;\n\tconst display = new Controllers.Display($el, config, emit);\n\tconst keys = new Controllers.Keys(config, player);\n\tconst mouse = new Controllers.Mouse();\n\tconst touches = new Controllers.Touches();\n\n\tconst setup = () => {\n\t\tObject.assign(config, props.options);\n\t\tmouse.setup(config, timers);\n\t\tkeys.setup();\n\t};\n\n\twatch(props.options, () => {\n\t\tsetup();\n\t\temit('optionsUpdated');\n\t});\n\n\tasync function updateProp(propName: 'rscs' | 'transitions') {\n\t\tconst wasPlaying = player.status.value === PlayerStatuses.playing;\n\n\t\tif (wasPlaying) {\n\t\t\tawait player.stop(true);\n\t\t}\n\n\t\tawait {\n\t\t\trscs: async () => await updateResources(),\n\t\t\ttransitions: () => updateTransitions(),\n\t\t}[propName]();\n\n\t\tif (wasPlaying) {\n\t\t\tplayer.play();\n\t\t}\n\t}\n\n\tasync function updateResources() {\n\t\tplayer.resource.reset();\n\n\t\tconst numToPreload = config.lazyLoad ? config.lazyLoadAfter : props.rscs.length;\n\n\t\ttry {\n\t\t\tawait resources.update(toRaw(props.rscs), numToPreload, display.size);\n\t\t} catch (e) {\n\t\t\tconsole.error(e);\n\t\t}\n\n\t\tif (resources.list.length) {\n\t\t\tplayer.resource.init(resources);\n\t\t}\n\t}\n\n\twatch(\n\t\t() => props.rscs,\n\t\tasync () => {\n\t\t\tawait updateProp('rscs');\n\t\t},\n\t\t{ deep: false },\n\t);\n\n\tfunction updateTransitions() {\n\t\tplayer.transition.reset();\n\n\t\ttransitions.update(toRaw(props.transitions));\n\n\t\tplayer.transition.init(transitions);\n\t}\n\n\twatch(\n\t\tprops.transitions,\n\t\tasync () => {\n\t\t\tawait updateProp('transitions');\n\t\t\temit('transitionsUpdated');\n\t\t},\n\t\t{ deep: false },\n\t);\n\n\tonMounted(async () => {\n\t\tsetup();\n\n\t\tdisplay.addResizeListener();\n\n\t\tplayer.setup($displayComponent);\n\n\t\tupdateTransitions();\n\n\t\tawait updateResources();\n\n\t\tif (config.autoplay === true) {\n\t\t\tplayer.play();\n\t\t}\n\n\t\temit('mounted');\n\t});\n\n\tonUnmounted(() => {\n\t\ttimers.clear();\n\t\tdisplay.removeResizeListener();\n\t\tkeys.removeKeyListener();\n\n\t\temit('unmounted');\n\t});\n\n\tconst style = computed(() => {\n\t\tif (!display.size.isValid()) {\n\t\t\treturn {};\n\t\t}\n\n\t\tif (display.inFullScreen()) {\n\t\t\treturn {\n\t\t\t\twidth: '100% !important',\n\t\t\t\theight: '100% !important',\n\t\t\t};\n\t\t}\n\n\t\treturn display.size.toPx();\n\t});\n\n\tdefineExpose({\n\t\tshow: player.show.bind(player),\n\t\tplay: player.play.bind(player),\n\t\tstop: player.stop.bind(player),\n\t\tgetPlayer: () => player as Controllers.Player,\n\t\tsize: display.size,\n\t});\n\n\temit('created');\n</script>\n\n<template>\n\t<div\n\t\tref=\"$el\"\n\t\tclass=\"vue-flux\"\n\t\t:style=\"style\"\n\t\t@mousemove=\"mouse.toggle(config, timers, true)\"\n\t\t@mouseleave=\"mouse.toggle(config, timers, false)\"\n\t\t@dblclick=\"display.toggleFullScreen()\"\n\t\t@touchstart=\"touches.start($event, config)\"\n\t\t@touchend=\"touches.end($event, config, player, display, timers, mouse)\"\n\t>\n\t\t<FluxTransition\n\t\t\tv-if=\"\n\t\t\t\t/* eslint-disable vue/html-indent */\n\t\t\t\tplayer.transition.current !== null &&\n\t\t\t\tdisplay.size.isValid() &&\n\t\t\t\tplayer.resource.from !== null &&\n\t\t\t\tplayer.resource.to !== null\n\t\t\t\t/* eslint-enable */\n\t\t\t\"\n\t\t\tref=\"$transition\"\n\t\t\t:transition=\"player.transition.current.component\"\n\t\t\t:size=\"display.size\"\n\t\t\t:from=\"player.resource.from.rsc\"\n\t\t\t:to=\"player.resource.to.rsc\"\n\t\t\t:display-component=\"$displayComponent\"\n\t\t\t:options=\"player.transition.current.options\"\n\t\t\t@ready=\"$transition?.start()\"\n\t\t\t@start=\"player.start()\"\n\t\t\t@end=\"player.end()\"\n\t\t/>\n\n\t\t<component\n\t\t\t:is=\"player.resource.current.rsc.display.component\"\n\t\t\tv-if=\"player.resource.current !== null\"\n\t\t\tref=\"$displayComponent\"\n\t\t\t:size=\"display.size\"\n\t\t\t:rsc=\"player.resource.current.rsc\"\n\t\t\tv-bind=\"player.resource.current.rsc.display.props\"\n\t\t/>\n\n\t\t<div v-if=\"display.size.isValid()\" class=\"complements\">\n\t\t\t<slot name=\"preloader\" :loader=\"resources.loader\" />\n\n\t\t\t<slot name=\"caption\" :player=\"player\" />\n\n\t\t\t<div class=\"remainder upper\" />\n\n\t\t\t<slot name=\"controls\" :mouse-over=\"mouse.isOver\" :player=\"player\" />\n\n\t\t\t<div class=\"remainder lower\" />\n\n\t\t\t<slot\n\t\t\t\tname=\"index\"\n\t\t\t\t:mouse-over=\"mouse.isOver\"\n\t\t\t\t:display-size=\"display.size\"\n\t\t\t\t:player=\"player\"\n\t\t\t/>\n\n\t\t\t<slot name=\"pagination\" :player=\"player\" />\n\t\t</div>\n\t</div>\n</template>\n\n<style lang=\"scss\">\n\t.vue-flux {\n\t\tposition: relative;\n\n\t\t.flux-transition {\n\t\t\tposition: absolute;\n\t\t}\n\n\t\t& > .flux-image {\n\t\t\tposition: absolute;\n\t\t\ttop: 0;\n\t\t\tleft: 0;\n\t\t}\n\n\t\t.complements {\n\t\t\tposition: absolute;\n\t\t\ttop: 0;\n\t\t\tleft: 0;\n\t\t\tright: 0;\n\t\t\tbottom: 0;\n\t\t\tdisplay: flex;\n\t\t\tflex-direction: column;\n\t\t\tjustify-content: space-between;\n\t\t\tz-index: 45;\n\n\t\t\t.remainder {\n\t\t\t\tflex-basis: 50%;\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "src/components/VueFlux/__test__/emit.ts",
    "content": "import { vi } from 'vitest';\nimport type { VueFluxEmits } from '../types';\n\nexport default vi.fn() as unknown as VueFluxEmits;\n"
  },
  {
    "path": "src/components/VueFlux/types.ts",
    "content": "import { Resource, type ResourceWithOptions } from '../../resources';\nimport type { TransitionWithOptions } from '../../transitions';\nimport { type Direction, PlayerResource, PlayerTransition } from '../../controllers/Player';\nimport { type Component } from 'vue';\n\nexport interface VueFluxOptions {\n\tallowFullscreen?: boolean;\n\tallowToSkipTransition?: boolean;\n\taspectRatio?: string;\n\tautohideTime?: number;\n\tautoplay?: boolean;\n\tbindKeys?: boolean;\n\tdelay?: number;\n\tenableGestures?: boolean;\n\tinfinite?: boolean;\n\tlazyLoad?: boolean;\n\tlazyLoadAfter?: number;\n}\n\nexport interface VueFluxProps {\n\toptions?: VueFluxOptions;\n\trscs: (Resource | ResourceWithOptions)[];\n\ttransitions: (Component | TransitionWithOptions)[];\n}\n\nexport interface VueFluxEmits {\n\t(e: 'created'): void;\n\t(e: 'mounted'): void;\n\t(e: 'unmounted'): void;\n\t(e: 'play', resourceIndex: number | Direction, delay?: number): void;\n\t(e: 'stop'): void;\n\t(e: 'show', resource: PlayerResource, transition: PlayerTransition): void;\n\t(e: 'optionsUpdated'): void;\n\t(e: 'transitionsUpdated'): void;\n\t(e: 'resourcesPreloadStart'): void;\n\t(e: 'resourcesPreloadEnd'): void;\n\t(e: 'resourcesLazyloadStart'): void;\n\t(e: 'resourcesLazyloadEnd'): void;\n\t(e: 'fullscreenEnter'): void;\n\t(e: 'fullscreenExit'): void;\n\t(e: 'transitionStart', resource: PlayerResource, transition: PlayerTransition): void;\n\t(e: 'transitionCancel', resource: PlayerResource, transition: PlayerTransition): void;\n\t(e: 'transitionEnd', resource: PlayerResource, transition: PlayerTransition): void;\n}\n\nexport interface VueFluxConfig {\n\tallowFullscreen: boolean;\n\tallowToSkipTransition: boolean;\n\taspectRatio: string;\n\tautohideTime: number;\n\tautoplay: boolean;\n\tbindKeys: boolean;\n\tdelay: number;\n\tenableGestures: boolean;\n\tinfinite: boolean;\n\tlazyLoad: boolean;\n\tlazyLoadAfter: number;\n}\n"
  },
  {
    "path": "src/components/index.ts",
    "content": "export { default as FluxButton } from './FluxButton/FluxButton.vue';\nexport * from './FluxCube';\nexport { default as FluxGrid } from './FluxGrid/FluxGrid.vue';\nexport { default as FluxImage } from './FluxImage/FluxImage.vue';\nexport { default as FluxParallax } from './FluxParallax/FluxParallax.vue';\nexport { default as FluxTransition } from './FluxTransition/FluxTransition.vue';\nexport { default as FluxVortex } from './FluxVortex/FluxVortex.vue';\nexport { default as FluxWrapper } from './FluxWrapper/FluxWrapper.vue';\nexport { default as VueFlux } from './VueFlux/VueFlux.vue';\n\nexport type * from './VueFlux/types';\nexport type * from './FluxCube/types';\nexport type * from './FluxGrid/types';\nexport type * from './FluxParallax/types';\nexport type * from './FluxTransition/types';\nexport type * from './FluxVortex/types';\nexport type * from './FluxWrapper/types';\nexport type * from './types';\n"
  },
  {
    "path": "src/components/types.ts",
    "content": "import type { CSSProperties, Component } from 'vue';\nimport { Resource } from '../resources';\nimport { Size, Position } from '../shared';\n\nexport interface ComponentProps {\n\tcolor?: CSSProperties['color'];\n\trsc?: Resource;\n\tsize: Size;\n\tviewSize?: Size;\n\toffset?: Position;\n\tcss?: CSSProperties;\n}\n\nexport interface ComponentStyles {\n\tbase?: CSSProperties;\n\tcolor?: CSSProperties;\n\trsc?: CSSProperties;\n\tsize?: CSSProperties;\n}\n\nexport type FluxComponent = Component & {\n\tsetCss: (s: CSSProperties) => void;\n\ttransform: (s: CSSProperties) => void;\n\tshow: () => void;\n\thide: () => void;\n};\n"
  },
  {
    "path": "src/components/useComponent.ts",
    "content": "import { computed, type CSSProperties, type Ref, unref } from 'vue';\nimport { Size } from '../shared';\nimport type { ComponentProps, ComponentStyles } from './types';\n\nexport default function useComponent(\n\t$el: Ref<null | HTMLElement>,\n\tprops: ComponentProps,\n\tcss: ComponentStyles,\n) {\n\tif (css.base === undefined) {\n\t\tcss.base = {} as CSSProperties;\n\t}\n\n\tconst size = computed<CSSProperties>(() => {\n\t\tconst { size, viewSize = new Size() } = props;\n\n\t\tconst { width = size.width.value, height = size.height.value } = viewSize.toValue();\n\n\t\tconst finalSize = new Size({ width, height });\n\n\t\tif (!finalSize.isValid()) {\n\t\t\treturn {};\n\t\t}\n\n\t\treturn finalSize.toPx();\n\t});\n\n\tconst style = computed(() => ({\n\t\t...unref(size),\n\t\t...unref(css.color),\n\t\t...unref(css.rsc),\n\t\t...unref(props.css),\n\t\t...unref(css.base),\n\t}));\n\n\tconst setCss = (s: CSSProperties) => {\n\t\tObject.assign(css.base as CSSProperties, s);\n\t};\n\n\tconst transform = (s: CSSProperties) => {\n\t\tif ($el.value === null) {\n\t\t\treturn;\n\t\t}\n\n\t\t// eslint-disable-next-line @typescript-eslint/no-unused-expressions\n\t\t$el.value.clientHeight;\n\t\tsetCss(s);\n\t};\n\n\tconst show = () => {\n\t\tsetCss({\n\t\t\tvisibility: 'visible',\n\t\t});\n\t};\n\n\tconst hide = () => {\n\t\tsetCss({\n\t\t\tvisibility: 'hidden',\n\t\t});\n\t};\n\n\treturn {\n\t\tstyle,\n\t\tsetCss,\n\t\ttransform,\n\t\tshow,\n\t\thide,\n\t};\n}\n"
  },
  {
    "path": "src/controllers/Display/Display.ts",
    "content": "import { nextTick, type Ref, type Component } from 'vue';\nimport { Size } from '../../shared';\nimport type { VueFluxConfig, VueFluxEmits } from '../../components';\n\nexport default class Display {\n\tnode: Ref<null | HTMLElement | Component>;\n\tconfig: VueFluxConfig | null;\n\temit: null | VueFluxEmits = null;\n\tsize: Size = new Size();\n\n\tprivate readonly onResize = () => {\n\t\tthis.updateSize();\n\t};\n\n\tconstructor(\n\t\tnode: Ref<null | HTMLElement | Component>,\n\t\tconfig: VueFluxConfig | null = null,\n\t\temit: null | VueFluxEmits = null,\n\t) {\n\t\tthis.node = node;\n\t\tthis.config = config;\n\t\tthis.emit = emit;\n\t}\n\n\tstatic async getSize(node: Ref<null | HTMLElement | Component>) {\n\t\tconst display = new Display(node);\n\t\tawait display.updateSize();\n\n\t\treturn display.size;\n\t}\n\n\taddResizeListener() {\n\t\twindow.addEventListener('resize', this.onResize, {\n\t\t\tpassive: true,\n\t\t});\n\n\t\tvoid this.updateSize();\n\t}\n\n\tremoveResizeListener() {\n\t\twindow.removeEventListener('resize', this.onResize);\n\t}\n\n\tgetAspectRatio() {\n\t\tif (this.config !== null) {\n\t\t\tconst [width, height] = this.config.aspectRatio.split(':');\n\n\t\t\treturn [parseFloat(width ?? ''), parseFloat(height ?? '')];\n\t\t}\n\n\t\treturn [16, 9];\n\t}\n\n\tasync updateSize() {\n\t\tthis.size.reset();\n\n\t\tawait nextTick();\n\n\t\tif (this.node.value === null) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst computedStyle = getComputedStyle(this.node.value as HTMLElement);\n\n\t\tconst width = parseFloat(computedStyle.width);\n\t\tlet height = parseFloat(computedStyle.height);\n\n\t\tif (['0px', 'auto', null].includes(computedStyle.height)) {\n\t\t\tconst [arWidth, arHeight] = this.getAspectRatio();\n\n\t\t\tif (arWidth === undefined || arHeight === undefined) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\theight = (width / arWidth) * arHeight;\n\t\t}\n\n\t\tthis.size.update({\n\t\t\twidth,\n\t\t\theight,\n\t\t});\n\t}\n\n\tinFullScreen = () => !!document.fullscreenElement;\n\n\ttoggleFullScreen() {\n\t\t// eslint-disable-next-line @typescript-eslint/no-unused-expressions\n\t\tthis.inFullScreen() ? this.exitFullScreen() : this.enterFullScreen();\n\t}\n\n\tasync enterFullScreen() {\n\t\tif (this.node?.value === null || !this.config?.allowFullscreen) {\n\t\t\treturn;\n\t\t}\n\n\t\tawait (this.node.value as HTMLElement).requestFullscreen();\n\n\t\tif (this.emit !== null) {\n\t\t\tthis.emit('fullscreenEnter');\n\t\t}\n\t}\n\n\tasync exitFullScreen() {\n\t\tawait document.exitFullscreen();\n\n\t\tif (this.emit !== null) {\n\t\t\tthis.emit('fullscreenExit');\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/controllers/Keys/Keys.ts",
    "content": "import type { VueFluxConfig } from '../../components';\nimport { Directions, Player } from '../';\n\nexport default class Keys {\n\tconfig: VueFluxConfig;\n\tplayer: Player;\n\n\tconstructor(config: VueFluxConfig, player: Player) {\n\t\tthis.config = config;\n\t\tthis.player = player;\n\t}\n\n\tsetup() {\n\t\tthis.removeKeyListener();\n\n\t\tif (this.config.bindKeys) {\n\t\t\twindow.addEventListener('keydown', this.keydown);\n\t\t}\n\t}\n\n\tremoveKeyListener() {\n\t\twindow.removeEventListener('keydown', this.keydown);\n\t}\n\n\tkeydown = (event: KeyboardEvent) => {\n\t\tif (['ArrowLeft', 'Left'].includes(event.key)) {\n\t\t\tthis.player.show(Directions.prev);\n\t\t\treturn;\n\t\t}\n\n\t\tif (['ArrowRight', 'Right'].includes(event.key)) {\n\t\t\tthis.player.show(Directions.next);\n\t\t\treturn;\n\t\t}\n\t};\n}\n"
  },
  {
    "path": "src/controllers/Mouse/Mouse.ts",
    "content": "import { type Ref, ref } from 'vue';\nimport Timers from '../Timers/Timers';\nimport type { VueFluxConfig } from '../../components';\n\nexport default class Mouse {\n\tisOver: Ref<boolean> = ref(false);\n\n\tsetup(config: VueFluxConfig, timers: Timers) {\n\t\ttimers.clear('mouseOver');\n\n\t\tif (config.autohideTime === 0) {\n\t\t\tthis.isOver.value = true;\n\t\t}\n\t}\n\n\ttoggle(config: VueFluxConfig, timers: Timers, over: boolean) {\n\t\tif (config.autohideTime === 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.isOver.value = over;\n\n\t\tthis[over ? 'over' : 'out'](config, timers);\n\t}\n\n\tout(_config: VueFluxConfig, timers: Timers) {\n\t\ttimers.clear('mouseOver');\n\t}\n\n\tover(config: VueFluxConfig, timers: Timers) {\n\t\ttimers.set('mouseOver', config.autohideTime, () => (this.isOver.value = false));\n\t}\n}\n"
  },
  {
    "path": "src/controllers/Player/Directions.ts",
    "content": "enum Directions {\n\tprev = 'prev',\n\tnext = 'next',\n}\n\nexport default Directions;\n"
  },
  {
    "path": "src/controllers/Player/Player.ts",
    "content": "import { shallowReactive, nextTick, type Ref, ref } from 'vue';\nimport {\n\tResources,\n\tTransitions,\n\ttype ResourceIndex,\n\ttype TransitionIndex,\n} from '../../repositories';\nimport { PlayerResource, PlayerTransition, Directions, type Direction, Statuses } from './';\nimport type { FluxComponent, VueFluxConfig, VueFluxEmits } from '../../components';\nimport { Timers } from '../';\n\nexport default class Player {\n\tresource: PlayerResource;\n\ttransition: PlayerTransition;\n\n\tstatus: Ref<keyof typeof Statuses> = ref(Statuses.stopped);\n\tconfig: VueFluxConfig;\n\ttimers: Timers;\n\n\temit: VueFluxEmits;\n\tresources: Resources;\n\ttransitions: Transitions;\n\t$displayComponent: Ref<null | FluxComponent> = ref(null);\n\n\tconstructor(\n\t\tconfig: VueFluxConfig,\n\t\ttimers: Timers,\n\n\t\temit: VueFluxEmits,\n\t) {\n\t\tthis.config = config;\n\t\tthis.timers = timers;\n\t\tthis.emit = emit;\n\n\t\tthis.resources = new Resources(emit);\n\t\tthis.transitions = new Transitions();\n\t\tthis.resource = shallowReactive(new PlayerResource());\n\t\tthis.transition = shallowReactive(new PlayerTransition());\n\t}\n\n\tsetup($displayComponent: Ref<null | FluxComponent>) {\n\t\tthis.$displayComponent = $displayComponent;\n\t}\n\n\tplay(resourceIndex: number | Direction = Directions.next, delay?: number) {\n\t\tconst { config, timers, resource } = this;\n\n\t\tthis.status.value = Statuses.playing;\n\n\t\tif (this.transition.current !== null) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst rsc = this.resources?.find(resourceIndex, resource.current?.index);\n\n\t\ttimers.set('transition', delay || rsc?.options.delay || config.delay, () => {\n\t\t\tthis.show(resourceIndex);\n\t\t});\n\n\t\tthis.emit('play', resourceIndex, delay);\n\t}\n\n\tasync stop(cancelTransition: boolean = false) {\n\t\tconst { timers } = this;\n\n\t\tthis.status.value = Statuses.stopped;\n\n\t\ttimers.clear('transition');\n\n\t\tif (this.transition.current !== null && cancelTransition === true) {\n\t\t\tawait this.end(cancelTransition);\n\t\t}\n\n\t\tthis.emit('stop');\n\t}\n\n\tisReadyToShow() {\n\t\tif (this.resource.current === null) {\n\t\t\tthrow new ReferenceError('Current resource not set');\n\t\t}\n\n\t\tif (this.resources === null) {\n\t\t\tthrow new ReferenceError('Resources list not set');\n\t\t}\n\n\t\tif (this.resources.list.length === 0) {\n\t\t\tthrow new RangeError('Resources list empty');\n\t\t}\n\n\t\tif (this.transition.last === null) {\n\t\t\tthrow new ReferenceError('Last transition not set');\n\t\t}\n\n\t\tif (this.transitions === null) {\n\t\t\tthrow new ReferenceError('Transitions list not set');\n\t\t}\n\n\t\tif (this.transitions.list.length === 0) {\n\t\t\tthrow new RangeError('Transitions list empty');\n\t\t}\n\n\t\tif (this.$displayComponent.value === null) {\n\t\t\tthrow new ReferenceError('Display component not set');\n\t\t}\n\n\t\treturn true;\n\t}\n\n\tasync show(\n\t\tresourceIndex: number | Direction = Directions.next,\n\t\ttransitionIndex: number | Direction = Directions.next,\n\t) {\n\t\tif (!this.isReadyToShow()) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst { resource, resources, config, transitions } = this;\n\n\t\tif (this.transition.current !== null) {\n\t\t\tif (config.allowToSkipTransition) {\n\t\t\t\tawait this.end(true);\n\n\t\t\t\tthis.show(resourceIndex, transitionIndex);\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\tconst resourceTo: ResourceIndex = resources!.find(resourceIndex, resource.current!.index);\n\n\t\tif (resource.currentSameAs(resourceTo)) {\n\t\t\treturn;\n\t\t}\n\n\t\tresource.prepareTo(resourceTo);\n\n\t\tthis.timers.clear('transition');\n\n\t\tconst transition: TransitionIndex =\n\t\t\ttypeof transitionIndex === 'number'\n\t\t\t\t? transitions!.getByIndex(transitionIndex)\n\t\t\t\t: transitions!.getByOrder(transitionIndex, this.transition.last!.index);\n\n\t\tif (transition.options.direction === undefined) {\n\t\t\tif (typeof resourceIndex !== 'number') {\n\t\t\t\ttransition.options.direction = resourceIndex;\n\t\t\t} else {\n\t\t\t\ttransition.options.direction =\n\t\t\t\t\tthis.resource.from!.index < this.resource.to!.index\n\t\t\t\t\t\t? Directions.next\n\t\t\t\t\t\t: Directions.prev;\n\t\t\t}\n\t\t}\n\n\t\tthis.transition.current = transition;\n\n\t\tthis.emit('show', this.resource, this.transition);\n\t}\n\n\tstart() {\n\t\tthis.resource.current = this.resource.to;\n\t\tthis.emit('transitionStart', this.resource, this.transition);\n\t}\n\n\tasync end(cancel: boolean = false) {\n\t\tconst { config, resource, resources, timers, transition } = this;\n\n\t\tif (resource.current === null || resources === null) {\n\t\t\treturn;\n\t\t}\n\n\t\ttransition.setCurrentFinished();\n\n\t\tawait nextTick();\n\n\t\tif (cancel === true) {\n\t\t\tthis.emit('transitionCancel', this.resource, this.transition);\n\t\t} else {\n\t\t\tthis.emit('transitionEnd', this.resource, this.transition);\n\t\t}\n\n\t\tif (this.shouldStopPlaying(config.infinite, resource.current, resources.list.length - 1)) {\n\t\t\tthis.stop();\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.shouldPlayNext()) {\n\t\t\ttimers.set('transition', resource.current.options.delay || config.delay, () => {\n\t\t\t\tthis.show();\n\t\t\t});\n\t\t}\n\t}\n\n\tprivate shouldStopPlaying(\n\t\tinfinite: boolean,\n\t\tcurrentResource: ResourceIndex,\n\t\ttotalResources: number,\n\t) {\n\t\tif (\n\t\t\tinfinite === false &&\n\t\t\tcurrentResource.index >= totalResources &&\n\t\t\tthis.status.value === Statuses.playing\n\t\t) {\n\t\t\treturn true;\n\t\t}\n\n\t\tif (currentResource.options.stop === true) {\n\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\tprivate shouldPlayNext() {\n\t\tif (this.status.value === Statuses.playing) {\n\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n\t}\n}\n"
  },
  {
    "path": "src/controllers/Player/Resource.ts",
    "content": "import { Resources, type ResourceIndex } from '../../repositories';\n\nexport default class PlayerResource {\n\tcurrent: ResourceIndex | null = null;\n\tfrom: ResourceIndex | null = null;\n\tto: ResourceIndex | null = null;\n\n\treset() {\n\t\tthis.current = null;\n\t\tthis.from = null;\n\t\tthis.to = null;\n\t}\n\n\tinit(repository: Resources) {\n\t\tthis.current = repository.getFirst();\n\t}\n\n\tcurrentSameAs(resourceTo: ResourceIndex) {\n\t\tif (this.current!.index === resourceTo.index) {\n\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\tprepareTo(resourceTo: ResourceIndex) {\n\t\tthis.from = this.current;\n\t\tthis.to = resourceTo;\n\t}\n}\n"
  },
  {
    "path": "src/controllers/Player/Statuses.ts",
    "content": "enum Statuses {\n\tstopped = 'stopped',\n\tplaying = 'playing',\n}\n\nexport default Statuses;\n"
  },
  {
    "path": "src/controllers/Player/Transition.ts",
    "content": "import { Transitions, type TransitionIndex } from '../../repositories';\n\nexport default class PlayerTransition {\n\tcurrent: TransitionIndex | null = null;\n\tlast: TransitionIndex | null = null;\n\n\treset() {\n\t\tthis.current = null;\n\t\tthis.last = null;\n\t}\n\n\tinit(transitions: Transitions) {\n\t\tthis.last = transitions.getLast();\n\t}\n\n\tsetCurrentFinished() {\n\t\tthis.last = this.current;\n\t\tthis.current = null;\n\t}\n}\n"
  },
  {
    "path": "src/controllers/Player/__mocks__/Player.ts",
    "content": "import { vi } from 'vitest';\nimport { type Ref, ref, shallowReactive } from 'vue';\nimport type { VueFluxConfig, VueFluxEmits } from '../../../components/VueFlux/types';\nimport { PlayerResource, PlayerTransition, Statuses, Timers } from '../..';\nimport { Resources, Transitions } from '../../../repositories';\nimport type { FluxComponent } from '../../../components/types';\n\nexport default class Player {\n\tresource: PlayerResource;\n\ttransition: PlayerTransition;\n\n\tstatus: Ref<keyof typeof Statuses> = ref(Statuses.stopped);\n\tconfig: VueFluxConfig;\n\ttimers: Timers;\n\temit: VueFluxEmits;\n\tresources: Resources;\n\ttransitions: Transitions;\n\t$displayComponent: Ref<null | FluxComponent> = ref(null);\n\n\tconstructor(config: VueFluxConfig, timers: Timers, emit: VueFluxEmits) {\n\t\tthis.config = config;\n\t\tthis.timers = timers;\n\t\tthis.emit = emit;\n\n\t\tthis.resources = new Resources(emit);\n\t\tthis.transitions = new Transitions();\n\t\tthis.resource = shallowReactive(new PlayerResource());\n\t\tthis.transition = shallowReactive(new PlayerTransition());\n\t}\n\n\tsetup = vi.fn();\n\tplay = vi.fn();\n\tstop = vi.fn();\n\tshow = vi.fn();\n\tstart = vi.fn();\n\tend = vi.fn();\n}\n"
  },
  {
    "path": "src/controllers/Player/__mocks__/Resource.ts",
    "content": "import type { ResourceIndex } from '../../../repositories';\nimport { vi } from 'vitest';\n\nexport default class PlayerResource {\n\tcurrent: ResourceIndex | null = null;\n\tfrom: ResourceIndex | null = null;\n\tto: ResourceIndex | null = null;\n\n\treset = vi.fn();\n\tinit = vi.fn();\n\tcurrentSameAs = vi.fn();\n\tprepareTo = vi.fn();\n}\n"
  },
  {
    "path": "src/controllers/Player/__mocks__/Transitions.ts",
    "content": "import type { TransitionIndex } from '../../../repositories';\nimport { vi } from 'vitest';\n\nexport default class PlayerTransition {\n\tcurrent: TransitionIndex | null = null;\n\tlast: TransitionIndex | null = null;\n\n\treset = vi.fn();\n\tinit = vi.fn();\n\tsetCurrentFinished = vi.fn();\n}\n"
  },
  {
    "path": "src/controllers/Player/index.ts",
    "content": "export { default as Directions } from './Directions';\nexport { default as Statuses } from './Statuses';\nexport { default as PlayerResource } from './Resource';\nexport { default as PlayerTransition } from './Transition';\nexport { default as Player } from './Player';\n\nexport type * from './types';\n"
  },
  {
    "path": "src/controllers/Player/types.ts",
    "content": "import Directions from './Directions';\n\nexport type Direction = Directions.prev | Directions.next;\n"
  },
  {
    "path": "src/controllers/Timers/Timers.ts",
    "content": "export default class Timers {\n\ttimers: {\n\t\t[index: string]: ReturnType<typeof setTimeout>;\n\t} = {};\n\n\tset(index: string, time: number, cb: () => void) {\n\t\tthis.clear(index);\n\t\tthis.timers[index] = setTimeout(cb, time);\n\t}\n\n\tclear(index?: string) {\n\t\tconst keys = index !== undefined ? [index] : Object.keys(this.timers);\n\n\t\tkeys.forEach((key) => {\n\t\t\tclearTimeout(this.timers[key]);\n\t\t\tdelete this.timers[key];\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "src/controllers/Touches/Touches.ts",
    "content": "import type { VueFluxConfig } from '../../components';\nimport { Directions, Display, Mouse, Player, Timers } from '../';\n\nexport default class Touches {\n\tstartX = 0;\n\tstartY = 0;\n\tstartTime = 0;\n\tendTime = 0;\n\tprevTouchTime = 0;\n\n\t// Max distance in pixels from start until end\n\ttapThreshold = 5;\n\n\t// Max time in ms from first to second tap\n\tdoubleTapThreshold = 200;\n\n\t// Distance in percentage to trigger slide\n\tslideTrigger = 0.3;\n\n\tstart(event: TouchEvent, config: VueFluxConfig) {\n\t\tif (!config.enableGestures) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst touch = event.changedTouches[0];\n\n\t\tif (!touch) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.startTime = Date.now();\n\t\tthis.startX = touch.clientX;\n\t\tthis.startY = touch.clientY;\n\t}\n\n\tend(\n\t\tevent: TouchEvent,\n\t\tconfig: VueFluxConfig,\n\t\tplayer: Player,\n\t\tdisplay: Display,\n\t\ttimers: Timers,\n\t\tmouse: Mouse,\n\t) {\n\t\tthis.prevTouchTime = this.endTime;\n\t\tthis.endTime = Date.now();\n\n\t\tconst touch = event.changedTouches[0];\n\n\t\tif (!touch) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst offsetX = touch.clientX - this.startX;\n\t\tconst offsetY = touch.clientY - this.startY;\n\n\t\tif (this.tap(offsetX, offsetY)) {\n\t\t\tmouse.toggle(config, timers, true);\n\t\t\treturn;\n\t\t}\n\n\t\tif (!config.enableGestures) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.slideRight(offsetX, display)) {\n\t\t\tplayer.show(Directions.prev);\n\t\t} else if (this.slideLeft(offsetX, display)) {\n\t\t\tplayer.show(Directions.next);\n\t\t}\n\t}\n\n\ttap = (offsetX: number, offsetY: number) =>\n\t\tMath.abs(offsetX) < this.tapThreshold && Math.abs(offsetY) < this.tapThreshold;\n\n\tdoubleTap = () => this.endTime - this.prevTouchTime < this.doubleTapThreshold;\n\n\tslideLeft = (offsetX: number, display: Display) =>\n\t\tdisplay.size.isValid() &&\n\t\toffsetX < 0 &&\n\t\toffsetX < -(display.size!.width.value! * this.slideTrigger);\n\n\tslideRight = (offsetX: number, display: Display) =>\n\t\tdisplay.size.isValid() &&\n\t\toffsetX > 0 &&\n\t\toffsetX > display.size.width.value! * this.slideTrigger;\n\n\tslideUp = (offsetY: number, display: Display) =>\n\t\tdisplay.size.isValid() &&\n\t\toffsetY < 0 &&\n\t\toffsetY < -(display.size.height.value! * this.slideTrigger);\n\n\tslideDown = (offsetY: number, display: Display) =>\n\t\tdisplay.size.isValid() &&\n\t\toffsetY > 0 &&\n\t\toffsetY > display.size.height.value! * this.slideTrigger;\n}\n"
  },
  {
    "path": "src/controllers/index.ts",
    "content": "export * from './Player';\nexport { default as Display } from './Display/Display';\nexport { default as Keys } from './Keys/Keys';\nexport { default as Mouse } from './Mouse/Mouse';\nexport { default as Timers } from './Timers/Timers';\nexport { default as Touches } from './Touches/Touches';\n"
  },
  {
    "path": "src/lib.ts",
    "content": "export * from './components';\nexport * from './complements';\nexport * from './resources';\nexport * from './transitions';\n\nexport {\n\tPlayer,\n\tDirections,\n\tStatuses,\n\tPlayerResource,\n\tPlayerTransition,\n} from './controllers/Player';\n\nexport type * from './controllers/Player/types';\n\nexport { Size, Position } from './shared';\n"
  },
  {
    "path": "src/main.ts",
    "content": "import './assets/css/main.css';\n\nimport { createApp } from 'vue';\nimport App from './App.vue';\n\ncreateApp(App).mount('#app');\n"
  },
  {
    "path": "src/module.d.ts",
    "content": "declare module '*';\n"
  },
  {
    "path": "src/playgrounds/PgFluxCaption.vue",
    "content": "<script setup lang=\"ts\">\n\timport { ref, shallowReactive, onMounted, type Ref } from 'vue';\n\timport { VcParagraph } from 'vue-cosk';\n\timport { VueFlux } from '../components';\n\timport { FluxCaption } from '../complements';\n\timport { Img } from '../resources';\n\timport { Book, Zip } from '../transitions';\n\timport { Player } from '../controllers';\n\n\tconst $vueFlux = ref();\n\n\tconst options = shallowReactive({\n\t\tautoplay: true,\n\t});\n\n\tconst rscs = shallowReactive([\n\t\tnew Img(`/images/01.jpg`, 'img 01'),\n\t\tnew Img(`/images/02.jpg`, 'img 02'),\n\t\tnew Img(`/images/03.jpg`, 'img 03'),\n\t\tnew Img(`/images/04.jpg`, 'img 04'),\n\t\tnew Img(`/images/05.jpg`, 'img 05'),\n\t\tnew Img(`/images/06.jpg`, 'img 06'),\n\t]);\n\n\tconst transitions = shallowReactive([Book, Zip]);\n\n\tconst player: Ref<null | Player> = ref(null);\n\n\tonMounted(() => {\n\t\tplayer.value = $vueFlux.value.getPlayer();\n\t});\n</script>\n\n<template>\n\t<div>\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\n\t\t<VueFlux :options=\"options\" :rscs=\"rscs\" :transitions=\"transitions\">\n\t\t\t<template #caption=\"captionProps\">\n\t\t\t\t<FluxCaption v-bind=\"captionProps\" />\n\t\t\t</template>\n\t\t</VueFlux>\n\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\n\t\t<VueFlux ref=\"$vueFlux\" :options=\"options\" :rscs=\"rscs\" :transitions=\"transitions\" />\n\n\t\t<FluxCaption v-if=\"player\" :player=\"player\" />\n\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\t</div>\n</template>\n"
  },
  {
    "path": "src/playgrounds/PgFluxControls.vue",
    "content": "<script setup lang=\"ts\">\n\timport { ref, shallowReactive, onMounted } from 'vue';\n\timport { VcParagraph } from 'vue-cosk';\n\timport { VueFlux } from '../components';\n\timport { FluxControls } from '../complements';\n\timport { Img } from '../resources';\n\timport { Book, Zip } from '../transitions';\n\n\tconst $vueFlux = ref();\n\n\tconst options = shallowReactive({\n\t\tautoplay: true,\n\t});\n\n\tconst rscs = shallowReactive([\n\t\tnew Img(`/images/01.jpg`, 'img 01'),\n\t\tnew Img(`/images/02.jpg`, 'img 02'),\n\t\tnew Img(`/images/03.jpg`, 'img 03'),\n\t\tnew Img(`/images/04.jpg`, 'img 04'),\n\t\tnew Img(`/images/05.jpg`, 'img 05'),\n\t\tnew Img(`/images/06.jpg`, 'img 06'),\n\t]);\n\n\tconst transitions = shallowReactive([Book, Zip]);\n\n\tconst mounted = ref(false);\n\n\tonMounted(() => {\n\t\tmounted.value = true;\n\t});\n</script>\r\n\r\n<template>\n\t<div>\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\n\t\t<VueFlux :options=\"options\" :rscs=\"rscs\" :transitions=\"transitions\">\n\t\t\t<template #controls=\"controlsProps\">\n\t\t\t\t<FluxControls v-bind=\"controlsProps\" />\n\t\t\t</template>\n\t\t</VueFlux>\n\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\n\t\t<VueFlux\n\t\t\tref=\"$vueFlux\"\n\t\t\t:options=\"options\"\n\t\t\t:rscs=\"rscs\"\n\t\t\t:transitions=\"transitions\"\n\t\t/>\n\n\t\t<FluxControls v-if=\"mounted\" :player=\"$vueFlux.getPlayer()\" />\n\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\t</div>\n</template>\r\n"
  },
  {
    "path": "src/playgrounds/PgFluxCube.vue",
    "content": "<script setup lang=\"ts\">\n\timport { ref, type Ref } from 'vue';\n\timport { VcParagraph } from 'vue-cosk';\n\timport Img from '../resources/Img/Img';\n\timport Size from '../shared/Size/Size';\n\timport { ResizeTypes } from '../resources';\n\timport { FluxCube, Turns } from '../components';\n\timport PgButton from './components/PgButton.vue';\n\n\tconst $fluxCube: Ref<null | InstanceType<typeof FluxCube>> = ref(null);\n\n\tconst origins = {\n\t\t['auto (undefined)']: undefined,\n\t\t['left top']: 'left top',\n\t\t['left center']: 'left center',\n\t\t['left bottom']: 'left bottom',\n\t\t['center top']: 'center top',\n\t\t['center center']: 'center center',\n\t\t['center bottom']: 'center bottom',\n\t\t['right top']: 'right top',\n\t\t['right center']: 'right center',\n\t\t['right bottom']: 'right bottom',\n\t};\n\n\tconst colors = {\n\t\tfront: '#ccc',\n\t\tleft: '#ccc',\n\t\tright: '#ccc',\n\t\ttop: '#ccc',\n\t\tbottom: '#ccc',\n\t\tback: '#ccc',\n\t};\n\n\tconst rscs = {\n\t\tfront: new Img(`/images/01.jpg`, 'img 01'),\n\t\tleft: new Img(`/images/02.jpg`, 'img 02', ResizeTypes.fit),\n\t\tright: new Img(`/images/03.jpg`, 'img 03', ResizeTypes.fit),\n\t\ttop: new Img(`/images/04.jpg`, 'img 04', ResizeTypes.fit),\n\t\tbottom: new Img(`/images/05.jpg`, 'img 05', ResizeTypes.fit),\n\t\tback: new Img(`/images/06.jpg`, 'img 06', ResizeTypes.fit),\n\t};\n\n\tconst size = new Size({\n\t\twidth: 640,\n\t\theight: 360,\n\t});\n\n\tconst depth: Ref<number> = ref(160);\n\tconst origin: Ref<undefined | string> = ref(undefined);\n\tconst turnTo: Ref<Turns> = ref(Turns.right);\n\n\tfunction turn() {\n\t\t$fluxCube.value?.turn(turnTo.value);\n\t}\n</script>\n\n<template>\n\t<div>\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\n\t\t<div style=\"perspective: 1600px\" class=\"my-20\">\n\t\t\t<FluxCube\n\t\t\t\tref=\"$fluxCube\"\n\t\t\t\t:colors=\"colors\"\n\t\t\t\t:rscs=\"rscs\"\n\t\t\t\t:depth=\"depth\"\n\t\t\t\t:size=\"size\"\n\t\t\t\t:origin=\"origin\"\n\t\t\t\tstyle=\"transition: all 2000ms ease-out 0s\"\n\t\t\t/>\n\t\t</div>\n\n\t\t<label>\n\t\t\t<span>Depth:</span>\n\n\t\t\t<input v-model.number=\"depth\" type=\"number\" style=\"width: 60px\" />\n\t\t</label>\n\n\t\t<label>\n\t\t\t<span>Origin:</span>\n\n\t\t\t<select v-model=\"origin\">\n\t\t\t\t<option v-for=\"(value, key) in origins\" :key=\"key\" :value=\"value\">\n\t\t\t\t\t{{ key }}\n\t\t\t\t</option>\n\t\t\t</select>\n\t\t</label>\n\n\t\t<label>\n\t\t\t<span>Turn:</span>\n\n\t\t\t<select v-model=\"turnTo\">\n\t\t\t\t<option v-for=\"(value, key) in Turns\" :key=\"key\" :value=\"value\">\n\t\t\t\t\t{{ key }}\n\t\t\t\t</option>\n\t\t\t</select>\n\n\t\t\t<PgButton @click=\"turn()\"> Turn </PgButton>\n\t\t</label>\n\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\t</div>\n</template>\n"
  },
  {
    "path": "src/playgrounds/PgFluxGrid.vue",
    "content": "<script setup lang=\"ts\">\n\timport { ref, type Ref } from 'vue';\n\timport { VcParagraph } from 'vue-cosk';\n\timport Img from '../resources/Img/Img';\n\timport Size from '../shared/Size/Size';\n\timport { FluxGrid } from '../components';\n\timport { ResizeTypes } from '../resources';\n\n\tconst $fluxGridImage: Ref<null | InstanceType<typeof FluxGrid>> = ref(null);\n\tconst $fluxGridCube: Ref<null | InstanceType<typeof FluxGrid>> = ref(null);\n\n\tconst rsc = new Img(`/images/01.jpg`, 'img 01', ResizeTypes.fit);\n\n\tconst size = new Size({\n\t\twidth: 640,\n\t\theight: 360,\n\t});\n\n\tconst colors = {\n\t\tfront: '#ccc',\n\t\tleft: '#ccc',\n\t\tright: '#ccc',\n\t\ttop: '#ccc',\n\t\tbottom: '#ccc',\n\t\tback: '#ccc',\n\t};\n\n\tconst rscs = {\n\t\tfront: new Img(`/images/01.jpg`, 'img 01'),\n\t\tleft: new Img(`/images/02.jpg`, 'img 02', ResizeTypes.fit),\n\t\tright: new Img(`/images/03.jpg`, 'img 03', ResizeTypes.fit),\n\t\ttop: new Img(`/images/04.jpg`, 'img 04', ResizeTypes.fit),\n\t\tbottom: new Img(`/images/05.jpg`, 'img 05', ResizeTypes.fit),\n\t\tback: new Img(`/images/06.jpg`, 'img 06', ResizeTypes.fit),\n\t};\n\n\tconst depth: Ref<number> = ref(160);\n</script>\n\n<template>\n\t<div>\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\n\t\t<FluxGrid ref=\"$fluxGridImage\" :rsc=\"rsc\" :size=\"size\" :rows=\"10\" :cols=\"5\" color=\"#ccc\" />\n\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\n\t\t<FluxGrid\n\t\t\tref=\"$fluxGridCube\"\n\t\t\t:rscs=\"rscs\"\n\t\t\t:size=\"size\"\n\t\t\t:rows=\"10\"\n\t\t\t:cols=\"5\"\n\t\t\t:depth=\"depth\"\n\t\t\t:colors=\"colors\"\n\t\t/>\n\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\t</div>\n</template>\n"
  },
  {
    "path": "src/playgrounds/PgFluxImage.vue",
    "content": "<script setup lang=\"ts\">\n\timport { ref, type Ref } from 'vue';\n\timport { VcParagraph } from 'vue-cosk';\n\timport Img from '../resources/Img/Img';\n\timport Size from '../shared/Size/Size';\n\timport { ResizeTypes } from '../resources';\n\timport { FluxImage } from '../components';\n\n\tconst $fluxImage: Ref<null | InstanceType<typeof FluxImage>> = ref(null);\n\n\tconst rscLandscapeFill = new Img(`/images/01.jpg`, 'img 01 fill');\n\n\tconst rscLandscapeFit = new Img(`/images/01.jpg`, 'img 01 fit', ResizeTypes.fit);\n\n\tconst rscPortraitFill = new Img(`/images/00.jpg`, 'img 00 fill');\n\n\tconst rscPortraitFit = new Img(`/images/00.jpg`, 'img 00 fit', ResizeTypes.fit, '#111');\n\n\tconst sizeLandscape = new Size({\n\t\twidth: 640,\n\t\theight: 360,\n\t});\n\n\tconst sizePortrait = new Size({\n\t\twidth: 211,\n\t\theight: 360,\n\t});\n</script>\n\n<template>\n\t<div>\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\n\t\t<FluxImage ref=\"$fluxImage\" :rsc=\"rscLandscapeFill\" :size=\"sizeLandscape\" />\n\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\n\t\t<FluxImage ref=\"$fluxImage\" :rsc=\"rscLandscapeFit\" :size=\"sizeLandscape\" color=\"#ccc\" />\n\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\n\t\t<FluxImage ref=\"$fluxImage\" :rsc=\"rscLandscapeFill\" :size=\"sizePortrait\" />\n\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\n\t\t<FluxImage ref=\"$fluxImage\" :rsc=\"rscLandscapeFit\" :size=\"sizePortrait\" color=\"#ccc\" />\n\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\n\t\t<FluxImage ref=\"$fluxImage\" :rsc=\"rscPortraitFill\" :size=\"sizePortrait\" />\n\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\n\t\t<FluxImage ref=\"$fluxImage\" :rsc=\"rscPortraitFit\" :size=\"sizePortrait\" color=\"#ccc\" />\n\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\n\t\t<FluxImage ref=\"$fluxImage\" :rsc=\"rscPortraitFill\" :size=\"sizeLandscape\" />\n\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\n\t\t<FluxImage ref=\"$fluxImage\" :rsc=\"rscPortraitFit\" :size=\"sizeLandscape\" color=\"#ccc\" />\n\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\t</div>\n</template>\n"
  },
  {
    "path": "src/playgrounds/PgFluxIndex.vue",
    "content": "<script setup lang=\"ts\">\n\timport { ref, shallowReactive, onMounted, type Ref } from 'vue';\n\timport { VcParagraph } from 'vue-cosk';\n\timport { VueFlux } from '../components';\n\timport { FluxIndex } from '../complements';\n\timport { Img } from '../resources';\n\timport { Book, Zip } from '../transitions';\n\timport { Player } from '../controllers';\n\n\tconst $vueFlux = ref();\n\n\tconst options = shallowReactive({\n\t\tautoplay: false,\n\t});\n\n\tconst rscs = shallowReactive([\n\t\tnew Img(`/images/01.jpg`, 'img 01'),\n\t\tnew Img(`/images/02.jpg`, 'img 02'),\n\t\tnew Img(`/images/03.jpg`, 'img 03'),\n\t\tnew Img(`/images/04.jpg`, 'img 04'),\n\t\tnew Img(`/images/05.jpg`, 'img 05'),\n\t\tnew Img(`/images/06.jpg`, 'img 06'),\n\t]);\n\n\tconst transitions = shallowReactive([Book, Zip]);\n\n\tconst player: Ref<null | Player> = ref(null);\n\n\tonMounted(() => {\n\t\tplayer.value = $vueFlux.value.getPlayer();\n\t});\n</script>\n\n<template>\n\t<div>\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\n\t\t<VueFlux :options=\"options\" :rscs=\"rscs\" :transitions=\"transitions\">\n\t\t\t<template #index=\"indexProps\">\n\t\t\t\t<FluxIndex v-bind=\"indexProps\" />\n\t\t\t</template>\n\t\t</VueFlux>\n\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\n\t\t<VueFlux ref=\"$vueFlux\" :options=\"options\" :rscs=\"rscs\" :transitions=\"transitions\" />\n\n\t\t<FluxIndex v-if=\"player\" :display-size=\"$vueFlux.size\" :player=\"player\" />\n\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\t</div>\n</template>\n"
  },
  {
    "path": "src/playgrounds/PgFluxPagination.vue",
    "content": "<script setup lang=\"ts\">\n\timport { ref, shallowReactive, onMounted, type Ref } from 'vue';\n\timport { VcParagraph } from 'vue-cosk';\n\timport { VueFlux } from '../components';\n\timport { FluxPagination } from '../complements';\n\timport { Img } from '../resources';\n\timport { Book, Zip } from '../transitions';\n\timport { Player } from '../controllers';\n\n\tconst $vueFlux = ref();\n\n\tconst options = shallowReactive({\n\t\tautoplay: false,\n\t});\n\n\tconst rscs = shallowReactive([\n\t\tnew Img(`/images/01.jpg`, 'img 01'),\n\t\tnew Img(`/images/02.jpg`, 'img 02'),\n\t\tnew Img(`/images/03.jpg`, 'img 03'),\n\t\tnew Img(`/images/04.jpg`, 'img 04'),\n\t\tnew Img(`/images/05.jpg`, 'img 05'),\n\t\tnew Img(`/images/06.jpg`, 'img 06'),\n\t]);\n\n\tconst transitions = shallowReactive([Book, Zip]);\n\n\tconst player: Ref<null | Player> = ref(null);\n\n\tonMounted(() => {\n\t\tplayer.value = $vueFlux.value.getPlayer();\n\t});\n</script>\n\n<template>\n\t<div>\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\n\t\t<VueFlux :options=\"options\" :rscs=\"rscs\" :transitions=\"transitions\">\n\t\t\t<template #pagination=\"paginationProps\">\n\t\t\t\t<FluxPagination v-bind=\"paginationProps\" />\n\t\t\t</template>\n\t\t</VueFlux>\n\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\n\t\t<VueFlux ref=\"$vueFlux\" :options=\"options\" :rscs=\"rscs\" :transitions=\"transitions\" />\n\n\t\t<FluxPagination v-if=\"player\" :player=\"player\">\n\t\t\t<template #default=\"pageProps\">\n\t\t\t\t<span\n\t\t\t\t\t:title=\"pageProps.title\"\n\t\t\t\t\t:class=\"pageProps.cssClass\"\n\t\t\t\t\t@click=\"player.show(pageProps.index)\"\n\t\t\t\t>\n\t\t\t\t\t{{ pageProps.index }}\n\t\t\t\t</span>\n\t\t\t</template>\n\t\t</FluxPagination>\n\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\t</div>\n</template>\n"
  },
  {
    "path": "src/playgrounds/PgFluxParallax.vue",
    "content": "<script setup lang=\"ts\">\n\timport { VcParagraph } from 'vue-cosk';\n\timport Img from '../resources/Img/Img';\n\timport { FluxParallax } from '../components';\n\n\tconst rsc = new Img(`/images/01.jpg`, 'img 01');\n</script>\n\n<template>\n\t<div>\n\t\t<VcParagraph v-for=\"i of 6\" :key=\"'a' + i\" mode=\"fill\" style=\"margin: 24px 0\" />\n\n\t\t<FluxParallax type=\"fixed\" :rsc=\"rsc\" style=\"height: 200px\" />\n\n\t\t<VcParagraph v-for=\"i of 6\" :key=\"'b' + i\" mode=\"fill\" style=\"margin: 24px 0\" />\n\t</div>\n</template>\n"
  },
  {
    "path": "src/playgrounds/PgFluxParallaxOp.vue",
    "content": "<script lang=\"ts\">\n\timport { defineComponent, markRaw } from 'vue';\n\timport { VcParagraph } from 'vue-cosk';\n\timport Img from '../resources/Img/Img';\n\timport { FluxParallax } from '../components';\n\n\texport default defineComponent({\n\t\tcomponents: {\n\t\t\tVcParagraph,\n\t\t\tFluxParallax,\n\t\t},\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\trsc: markRaw(new Img(`/images/01.jpg`, 'img 01')),\n\t\t\t};\n\t\t},\n\t});\n</script>\n\n<template>\n\t<div>\n\t\t<VcParagraph v-for=\"i of 6\" :key=\"'a' + i\" mode=\"fill\" style=\"margin: 24px 0\" />\n\n\t\t<FluxParallax type=\"fixed\" :rsc=\"rsc\" style=\"height: 200px\" />\n\n\t\t<VcParagraph v-for=\"i of 6\" :key=\"'b' + i\" mode=\"fill\" style=\"margin: 24px 0\" />\n\t</div>\n</template>\n"
  },
  {
    "path": "src/playgrounds/PgFluxPreloader.vue",
    "content": "<script setup lang=\"ts\">\n\timport { shallowReactive } from 'vue';\n\timport { VcParagraph } from 'vue-cosk';\n\timport { VueFlux } from '../components';\n\timport { FluxPreloader } from '../complements';\n\timport { Img } from '../resources';\n\timport { Book, Zip } from '../transitions';\n\n\tconst options = shallowReactive({\n\t\tautoplay: false,\n\t});\n\n\tconst rscs = shallowReactive([\n\t\tnew Img(`/images/01.jpg`, 'img 01'),\n\t\tnew Img(`/images/02.jpg`, 'img 02'),\n\t\tnew Img(`/images/03.jpg`, 'img 03'),\n\t\tnew Img(`/images/04.jpg`, 'img 04'),\n\t\tnew Img(`/images/05.jpg`, 'img 05'),\n\t\tnew Img(`/images/06.jpg`, 'img 06'),\n\t\tnew Img(`/images/07.jpg`, 'img 07'),\n\t\tnew Img(`/images/08.jpg`, 'img 08'),\n\t\tnew Img(`/images/09.jpg`, 'img 09'),\n\t\tnew Img(`/images/10.jpg`, 'img 10'),\n\t\tnew Img(`/images/11.jpg`, 'img 11'),\n\t\tnew Img(`/images/12.jpg`, 'img 12'),\n\t\tnew Img(`/images/13.jpg`, 'img 13'),\n\t\tnew Img(`/images/14.jpg`, 'img 14'),\n\t\tnew Img(`/images/15.jpg`, 'img 15'),\n\t]);\n\n\tconst transitions = shallowReactive([Book, Zip]);\n</script>\r\n\r\n<template>\n\t<div>\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\n\t\t<VueFlux :options=\"options\" :rscs=\"rscs\" :transitions=\"transitions\">\n\t\t\t<template #preloader=\"preloaderProps\">\n\t\t\t\t<FluxPreloader v-bind=\"preloaderProps\" />\n\t\t\t</template>\n\t\t</VueFlux>\n\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\n\t\t<VueFlux :options=\"options\" :rscs=\"rscs\" :transitions=\"transitions\">\n\t\t\t<template #preloader=\"preloaderProps\">\n\t\t\t\t<FluxPreloader v-bind=\"preloaderProps\">\n\t\t\t\t\t<template #default=\"props\">\n\t\t\t\t\t\t<div v-if=\"props.preloading\" class=\"custom-spinner\">\n\t\t\t\t\t\t\t{{ props.pct }} %\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</template>\n\t\t\t\t</FluxPreloader>\n\t\t\t</template>\n\t\t</VueFlux>\n\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\t</div>\n</template>\r\n\r\n<style lang=\"scss\">\n\t@keyframes spinner {\n\t\tto {\n\t\t\ttransform: rotate(360deg);\n\t\t}\n\t}\n\n\t.custom-spinner {\n\t\tposition: absolute;\n\t\ttop: 50%;\n\t\tleft: 50%;\n\t\ttext-align: center;\n\t\tline-height: 50px;\n\t\tmargin-top: -25px;\n\t\tmargin-left: -25px;\n\t\twidth: 50px;\n\t\theight: 50px;\n\t\tz-index: 14;\n\n\t\t&:before {\n\t\t\tcontent: '';\n\t\t\tbox-sizing: border-box;\n\t\t\tposition: absolute;\n\t\t\ttop: 50%;\n\t\t\tleft: 50%;\n\t\t\twidth: 50px;\n\t\t\theight: 50px;\n\t\t\tmargin-top: -25px;\n\t\t\tmargin-left: -25px;\n\t\t\tborder-radius: 50%;\n\t\t\tborder: 1px solid #ccc;\n\t\t\tborder-top-color: #07d;\n\t\t\tanimation: spinner 0.6s linear infinite;\n\t\t}\n\t}\n</style>\r\n"
  },
  {
    "path": "src/playgrounds/PgFluxTransition.vue",
    "content": "<script setup lang=\"ts\">\n\timport { nextTick, ref, type Ref, shallowRef } from 'vue';\n\timport { VcParagraph } from 'vue-cosk';\n\timport Img from '../resources/Img/Img';\n\timport Size from '../shared/Size/Size';\n\timport { FluxTransition } from '../components';\n\timport {\n\t\tFade,\n\t\tKenburn,\n\t\tSwipe,\n\t\tSlide,\n\t\tWaterfall,\n\t\tZip,\n\t\tBlinds2D,\n\t\tBlocks1,\n\t\tBlocks2,\n\t\tConcentric,\n\t\tWarp,\n\t\tCamera,\n\t\tCube,\n\t\tBook,\n\t\tFall,\n\t\tWave,\n\t\tBlinds3D,\n\t\tRound1,\n\t\tRound2,\n\t\tExplode,\n\t} from '../transitions';\n\timport PgButton from './components/PgButton.vue';\n\n\tconst $fluxTransition: Ref<null | InstanceType<typeof FluxTransition>> = ref(null);\n\n\tconst transitions = {\n\t\tFade,\n\t\tKenburn,\n\t\tSwipe,\n\t\tSlide,\n\t\tWaterfall,\n\t\tZip,\n\t\tBlinds2D,\n\t\tBlocks1,\n\t\tBlocks2,\n\t\tConcentric,\n\t\tWarp,\n\t\tCamera,\n\t\tCube,\n\t\tBook,\n\t\tFall,\n\t\tWave,\n\t\tBlinds3D,\n\t\tRound1,\n\t\tRound2,\n\t\tExplode,\n\t};\n\n\tconst enabled = ref(true);\n\n\tconst transition = shallowRef(Fade);\n\tconst rscFrom = new Img(`/images/05.jpg`, 'img 05 fill');\n\tconst rscTo = new Img(`/images/06.jpg`, 'img 06 fit');\n\n\tconst size = new Size({\n\t\twidth: 640,\n\t\theight: 360,\n\t});\n\n\tasync function reset() {\n\t\tawait nextTick();\n\n\t\tenabled.value = false;\n\n\t\tawait nextTick();\n\n\t\tenabled.value = true;\n\t}\n</script>\n\n<template>\n\t<div>\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\n\t\t<FluxTransition\n\t\t\tv-if=\"enabled\"\n\t\t\tref=\"$fluxTransition\"\n\t\t\t:size=\"size\"\n\t\t\t:transition=\"transition\"\n\t\t\t:from=\"rscFrom\"\n\t\t\t:to=\"rscTo\"\n\t\t\t@end=\"reset\"\n\t\t/>\n\n\t\t<label v-if=\"$fluxTransition\" class=\"mt-6\">\n\t\t\t<span>Transition</span>\n\n\t\t\t<select v-model=\"transition\">\n\t\t\t\t<option v-for=\"(component, name) in transitions\" :key=\"name\" :value=\"component\">\n\t\t\t\t\t{{ name }}\n\t\t\t\t</option>\n\t\t\t</select>\n\n\t\t\t<PgButton @click=\"$fluxTransition.start()\">Start</PgButton>\n\t\t</label>\n\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0\" />\n\t</div>\n</template>\n"
  },
  {
    "path": "src/playgrounds/PgVueFlux.vue",
    "content": "<script setup lang=\"ts\">\n\timport { ref, type Ref, shallowReactive } from 'vue';\n\timport { VcParagraph } from 'vue-cosk';\n\timport { Img } from '../resources';\n\timport {\n\t\tFade,\n\t\tKenburn,\n\t\tSwipe,\n\t\tSlide,\n\t\tWaterfall,\n\t\tZip,\n\t\tBlinds2D,\n\t\tBlocks1,\n\t\tBlocks2,\n\t\tConcentric,\n\t\tWarp,\n\t\tCamera,\n\t\tCube,\n\t\tBook,\n\t\tFall,\n\t\tWave,\n\t\tBlinds3D,\n\t\tRound1,\n\t\tRound2,\n\t\tExplode,\n\t} from '../transitions';\n\timport * as Complements from '../complements';\n\timport { VueFlux } from '../components';\n\timport PgButton from './components/PgButton.vue';\n\timport ResizeTypes from '../resources/ResizeTypes';\n\timport { Directions } from '../controllers';\n\n\tconst $vueFlux: Ref<null | InstanceType<typeof VueFlux>> = ref(null);\n\n\tconst options = shallowReactive({\n\t\tallowFullscreen: true,\n\t\tautoplay: false,\n\t\tbindKeys: true,\n\t\tinfinite: true,\n\t\tdelay: 5000,\n\t\tlazyLoadAfter: 10,\n\t});\n\n\tconst images = [];\n\tfor (let i = 1; i <= 20; i++) {\n\t\tconst fileName = i.toString().padStart(2, '0');\n\t\tconst image = new Img(\n\t\t\t`/images/${fileName}.jpg`,\n\t\t\t'img ' + i,\n\t\t\tResizeTypes.fill, //i % 2 === 0 ? ResizeTypes.fit : ResizeTypes.fill\n\t\t);\n\t\timages.push(image);\n\t}\n\n\tconst rscs = shallowReactive(images);\n\n\tconst transitions = {\n\t\tBlinds2D,\n\t\tBlinds3D,\n\t\tBlocks1,\n\t\tBlocks2,\n\t\tBook,\n\t\tCamera,\n\t\tConcentric,\n\t\tCube,\n\t\tExplode,\n\t\tFade,\n\t\tFall,\n\t\tKenburn,\n\t\tRound1,\n\t\tRound2,\n\t\tSlide,\n\t\tSwipe,\n\t\tWarp,\n\t\tWaterfall,\n\t\tWave,\n\t\tZip,\n\t};\n\n\tconst transitionComponents = shallowReactive(Object.values(transitions));\n\n\tconst transitionNames = Object.keys(transitions);\n\n\tconst currentTransitionName = ref(null);\n\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\tfunction updateCurrentTransition(_rsc?: any, transition?: any) {\n\t\tif (transition.current !== null) {\n\t\t\tcurrentTransitionName.value = transition.current.component.__name;\n\t\t} else {\n\t\t\tcurrentTransitionName.value = null;\n\t\t}\n\t}\n</script>\n\n<template>\n\t<div>\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0; padding: 0\" />\n\n\t\t<div class=\"block sm:block md:block lg:flex\">\n\t\t\t<div class=\"lg:w-3/4\">\n\t\t\t\t<VueFlux\n\t\t\t\t\tref=\"$vueFlux\"\n\t\t\t\t\t:transitions=\"transitionComponents\"\n\t\t\t\t\t:rscs=\"rscs\"\n\t\t\t\t\t:options=\"options\"\n\t\t\t\t\t@transitionStart=\"updateCurrentTransition\"\n\t\t\t\t\t@transitionEnd=\"updateCurrentTransition\"\n\t\t\t\t>\n\t\t\t\t\t<template #preloader=\"preloaderProps\">\n\t\t\t\t\t\t<Complements.FluxPreloader v-bind=\"preloaderProps\" />\n\t\t\t\t\t</template>\n\n\t\t\t\t\t<template #caption=\"captionProps\">\n\t\t\t\t\t\t<Complements.FluxCaption v-bind=\"captionProps\" />\n\t\t\t\t\t</template>\n\n\t\t\t\t\t<template #controls=\"controlsProps\">\n\t\t\t\t\t\t<Complements.FluxControls v-bind=\"controlsProps\" />\n\t\t\t\t\t</template>\n\n\t\t\t\t\t<template #index=\"indexProps\">\n\t\t\t\t\t\t<Complements.FluxIndex v-bind=\"indexProps\" />\n\t\t\t\t\t</template>\n\n\t\t\t\t\t<template #pagination=\"paginationProps\">\n\t\t\t\t\t\t<Complements.FluxPagination v-bind=\"paginationProps\" />\n\t\t\t\t\t</template>\n\t\t\t\t</VueFlux>\n\t\t\t</div>\n\n\t\t\t<div class=\"lg:w-1/4 lg:ml-4 lg:mt-0 mt-6\">\n\t\t\t\t<ul v-if=\"$vueFlux && $vueFlux.size.isValid()\" class=\"flex flex-wrap\">\n\t\t\t\t\t<li\n\t\t\t\t\t\tv-for=\"(name, index) in transitionNames\"\n\t\t\t\t\t\t:key=\"name\"\n\t\t\t\t\t\tclass=\"odd:pr-4 mb-4 lg:w-1/2 lg:mr-0 mr-4\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<PgButton\n\t\t\t\t\t\t\tclass=\"w-100\"\n\t\t\t\t\t\t\t:active=\"currentTransitionName === name\"\n\t\t\t\t\t\t\t@click=\"$vueFlux.show(Directions.next, index)\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{{ name }}\n\t\t\t\t\t\t</PgButton>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<div v-if=\"$vueFlux\" class=\"mt-6 lg:flex\">\n\t\t\t<PgButton class=\"mr-4 w-1/3\" @click=\"$vueFlux.show()\">Next</PgButton>\n\t\t\t<PgButton class=\"mr-4 w-1/3\" @click=\"$vueFlux.play()\">Play</PgButton>\n\t\t\t<PgButton class=\"w-1/3 mr-0\" @click=\"$vueFlux.stop()\">Stop</PgButton>\n\t\t</div>\n\n\t\t<VcParagraph mode=\"fill\" style=\"margin: 24px 0; padding: 0\" />\n\t</div>\n</template>\n"
  },
  {
    "path": "src/playgrounds/components/PgButton.vue",
    "content": "<script setup lang=\"ts\">\n\tconst props = withDefaults(defineProps<{ active?: boolean }>(), {\n\t\tactive: false,\n\t});\n</script>\n\n<template>\n\t<button\n\t\tclass=\"hover:bg-sky-700 text-white rounded px-2 w-full cursor-pointer\"\n\t\t:class=\"props.active ? 'bg-amber-500' : 'bg-sky-500'\"\n\t>\n\t\t<slot />\n\t</button>\n</template>\n"
  },
  {
    "path": "src/repositories/Resources/Resources.test.ts",
    "content": "import { Directions } from '../../controllers';\nimport { Resource } from '../../resources';\nimport { Size } from '../../shared';\nimport { default as ResourcesRepository } from './Resources';\nimport ResourceFactory from '../../resources/__test__/ResourceFactory';\nimport emit from '../../components/VueFlux/__test__/emit';\n\nvi.mock('../../resources/Img/Img');\nvi.mock('../../shared/ResourceLoader/ResourceLoader');\n\ndescribe('repositories: Resources', () => {\n\tlet repo: ResourcesRepository;\n\tlet resources: Resource[];\n\tconst size: Size = new Size({\n\t\twidth: 640,\n\t\theight: 360,\n\t});\n\n\tdescribe('width preloading', () => {\n\t\tbeforeEach(async () => {\n\t\t\tvi.clearAllMocks();\n\n\t\t\trepo = new ResourcesRepository(emit);\n\n\t\t\tresources = ResourceFactory.create(5);\n\t\t\tawait repo.update(resources, 5, size);\n\t\t});\n\n\t\tit('updates the repository transitions', () => {\n\t\t\texpect(repo.list).toHaveLength(5);\n\t\t});\n\n\t\tit('emits when preload starts', () => {\n\t\t\texpect(emit).toHaveBeenCalledWith('resourcesPreloadStart');\n\t\t});\n\n\t\tit('emits when preload ends', () => {\n\t\t\texpect(emit).toHaveBeenCalledWith('resourcesPreloadEnd');\n\t\t});\n\n\t\tit('gets the first resource', () => {\n\t\t\texpect(repo.getFirst().rsc).toBe(resources[0]);\n\t\t});\n\n\t\tit('gets the last resource', () => {\n\t\t\texpect(repo.getLast().rsc).toBe(resources[4]);\n\t\t});\n\n\t\tit('get resource by index', () => {\n\t\t\texpect(repo.getByIndex(2).rsc).toBe(resources[2]);\n\t\t});\n\n\t\tit('throws error because the requested index does not exist', () => {\n\t\t\tconst index = resources.length + 1;\n\n\t\t\texpect(() => repo.getByIndex(index)).toThrow(\n\t\t\t\t`Resource index ${index} not found`\n\t\t\t);\n\t\t});\n\n\t\tit('get resource by order next', () => {\n\t\t\texpect(\n\t\t\t\trepo.getByOrder(Directions.next, resources.length - 1).rsc\n\t\t\t).toBe(resources[0]);\n\t\t});\n\n\t\tit('get resource by order prev', () => {\n\t\t\texpect(repo.getByOrder(Directions.prev, 0).rsc).toBe(\n\t\t\t\tresources[resources.length - 1]\n\t\t\t);\n\t\t});\n\n\t\tit('throws an error when trying to find a resource by order without passing the current index', () => {\n\t\t\texpect(() => repo.find(Directions.next)).toThrow(\n\t\t\t\t'Missing currentIndex parameter'\n\t\t\t);\n\t\t});\n\t});\n\n\tdescribe('with lazy loading', () => {\n\t\tconst numResources = 10;\n\t\tconst resourcesToPreload = 5;\n\n\t\tbeforeEach(async () => {\n\t\t\tvi.clearAllMocks();\n\n\t\t\trepo = new ResourcesRepository(emit);\n\n\t\t\tresources = ResourceFactory.create(numResources);\n\t\t\tawait repo.update(resources, resourcesToPreload, size);\n\t\t});\n\n\t\tit('emits resourcesLazyloadStart when start lazy loading', () =>\n\t\t\tnew Promise<void>((done) => {\n\t\t\t\texpect(emit).toHaveBeenCalledWith('resourcesLazyloadStart');\n\t\t\t\tdone();\n\t\t\t}));\n\n\t\tit('emits resourcesLazyloadStart when start lazy loading', () =>\n\t\t\tnew Promise<void>((done) => {\n\t\t\t\texpect(repo.list).toHaveLength(numResources);\n\t\t\t\texpect(emit).toHaveBeenCalledWith('resourcesLazyloadEnd');\n\t\t\t\tdone();\n\t\t\t}));\n\t});\n});\n"
  },
  {
    "path": "src/repositories/Resources/Resources.ts",
    "content": "import { type Ref, ref, shallowReactive } from 'vue';\nimport { Resource, type ResourceWithOptions } from '../../resources';\nimport { Size, ResourceLoader } from '../../shared';\nimport { type Direction, Directions } from '../../controllers/Player';\nimport type { ResourceIndex } from './types';\nimport ResourcesMapper from './ResourcesMapper';\nimport type { VueFluxEmits } from '../../components';\n\nexport default class Resources {\n\tlist: ResourceWithOptions[] = shallowReactive([]);\n\tloader: Ref<ResourceLoader | null> = ref(null);\n\temit: VueFluxEmits;\n\n\tconstructor(emit: VueFluxEmits) {\n\t\tthis.emit = emit;\n\t}\n\n\tprivate getPrev(currentIndex: number) {\n\t\treturn this.getByIndex(currentIndex > 0 ? currentIndex - 1 : this.list.length - 1);\n\t}\n\n\tprivate getNext(currentIndex: number) {\n\t\treturn this.getByIndex(currentIndex === this.list.length - 1 ? 0 : currentIndex + 1);\n\t}\n\n\tgetFirst() {\n\t\treturn this.getByIndex(0);\n\t}\n\n\tgetLast() {\n\t\treturn this.getByOrder(Directions.prev, 0);\n\t}\n\n\tgetByIndex(index: number) {\n\t\tif (this.list[index] === undefined) {\n\t\t\tthrow new ReferenceError(`Resource index ${index} not found`);\n\t\t}\n\n\t\treturn {\n\t\t\tindex,\n\t\t\trsc: this.list[index].resource,\n\t\t\toptions: JSON.parse(JSON.stringify(this.list[index].options)),\n\t\t} as ResourceIndex;\n\t}\n\n\tgetByOrder(order: Direction, currentIndex: number) {\n\t\treturn {\n\t\t\tprev: () => this.getPrev(currentIndex),\n\t\t\tnext: () => this.getNext(currentIndex),\n\t\t}[order]();\n\t}\n\n\tfind(by: number | Direction, currentIndex?: number) {\n\t\tif (typeof by === 'number') {\n\t\t\treturn this.getByIndex(by);\n\t\t}\n\n\t\tif (currentIndex === undefined) {\n\t\t\tthrow new ReferenceError('Missing currentIndex parameter');\n\t\t}\n\n\t\treturn this.getByOrder(by, currentIndex);\n\t}\n\n\tupdate(rscs: (Resource | ResourceWithOptions)[], numToPreload: number, displaySize: Size) {\n\t\tif (this.loader.value?.hasFinished() === false) {\n\t\t\tthis.loader.value?.cancel();\n\t\t}\n\n\t\tthis.list.splice(0);\n\n\t\tconst resources = ResourcesMapper.withOptions(rscs);\n\n\t\tconst updatePromise = new Promise<void>((resolve, reject) => {\n\t\t\tthis.loader.value = new ResourceLoader(\n\t\t\t\tresources,\n\t\t\t\tnumToPreload,\n\t\t\t\tdisplaySize,\n\t\t\t\t() => this.preloadStart(),\n\t\t\t\t(loaded: ResourceWithOptions[]) => this.preloadEnd(loaded, resolve),\n\t\t\t\t() => this.lazyLoadStart(),\n\t\t\t\t(loaded: ResourceWithOptions[]) => this.lazyLoadEnd(loaded),\n\t\t\t\treject,\n\t\t\t);\n\t\t});\n\n\t\treturn updatePromise;\n\t}\n\n\tpreloadStart() {\n\t\tthis.emit('resourcesPreloadStart');\n\t}\n\n\tpreloadEnd(loaded: ResourceWithOptions[], resolve: () => void) {\n\t\tthis.list.push(...loaded);\n\n\t\tthis.emit('resourcesPreloadEnd');\n\n\t\tresolve();\n\t}\n\n\tlazyLoadStart() {\n\t\tthis.emit('resourcesLazyloadStart');\n\t}\n\n\tlazyLoadEnd(loaded: ResourceWithOptions[]) {\n\t\tthis.list.push(...loaded);\n\n\t\tthis.emit('resourcesLazyloadEnd');\n\t}\n}\n"
  },
  {
    "path": "src/repositories/Resources/ResourcesMapper.test.ts",
    "content": "import { Img, type ResourceWithOptions } from '../../resources';\nimport ResourcesMapper from './ResourcesMapper';\n\ndescribe('repositories: ResourcesMapper', () => {\n\tit('turns all the transitions array as transitions with options', () => {\n\t\tconst resources = [\n\t\t\tnew Img('url1'),\n\t\t\t{\n\t\t\t\tresource: new Img('url2'),\n\t\t\t\toptions: {\n\t\t\t\t\tdelay: 8000,\n\t\t\t\t},\n\t\t\t} as ResourceWithOptions,\n\t\t\tnew Img('url3'),\n\t\t];\n\n\t\tconst resourcesWithOptions = ResourcesMapper.withOptions(resources);\n\n\t\texpect(resourcesWithOptions[0]!.resource).toBe(resources[0]);\n\t\t// @ts-expect-error:next-line\n\t\texpect(resourcesWithOptions[1]!.resource).toBe(resources[1].resource);\n\t\texpect(resourcesWithOptions[1]!.options.delay).toBe(8000);\n\t\texpect(resourcesWithOptions[2]!.resource).toBe(resources[2]);\n\t\texpect(resourcesWithOptions[2]!.options).toStrictEqual({});\n\t});\n});\n"
  },
  {
    "path": "src/repositories/Resources/ResourcesMapper.ts",
    "content": "import { Resource, type ResourceWithOptions } from '../../resources';\n\nexport default class ResourcesMapper {\n\tstatic withOptions(rscs: (Resource | ResourceWithOptions)[]) {\n\t\treturn rscs.map((rsc) => {\n\t\t\tlet resource = rsc;\n\t\t\tlet options = {};\n\n\t\t\tif ('resource' in rsc) {\n\t\t\t\tresource = rsc.resource as Resource;\n\n\t\t\t\tif ('options' in rsc) {\n\t\t\t\t\toptions = rsc.options as object;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn { resource, options } as ResourceWithOptions;\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "src/repositories/Resources/types.ts",
    "content": "import Resource from '../../resources/Resource';\n\nexport interface ResourceIndex {\n\tindex: number;\n\trsc: Resource;\n\toptions: {\n\t\tdelay?: number;\n\t\tstop?: boolean;\n\t};\n}\n"
  },
  {
    "path": "src/repositories/Transitions/Transitions.test.ts",
    "content": "import { Directions } from '../../controllers';\nimport { default as TransitionsRepository } from './Transitions';\n\nfunction transitionsFactory(numTransitions: number) {\n\treturn new Array(numTransitions).fill({});\n}\n\ndescribe('repositories: Transitions', () => {\n\tlet repo: TransitionsRepository;\n\tlet transitions: object[];\n\n\tbeforeEach(() => {\n\t\trepo = new TransitionsRepository();\n\t});\n\n\tit('updates the repository transitions', () => {\n\t\ttransitions = transitionsFactory(5);\n\t\trepo.update(transitions);\n\n\t\texpect(repo.list).toHaveLength(5);\n\t});\n\n\tit('removes the previous transitions on update', () => {\n\t\ttransitions = transitionsFactory(5);\n\t\trepo.update(transitions);\n\n\t\texpect(repo.list).toHaveLength(5);\n\n\t\ttransitions = transitionsFactory(2);\n\t\trepo.update(transitions);\n\t\texpect(repo.list).toHaveLength(2);\n\t});\n\n\tit('gets the first transition', () => {\n\t\ttransitions = transitionsFactory(5);\n\t\trepo.update(transitions);\n\n\t\texpect(repo.getFirst().component).toBe(transitions[0]);\n\t});\n\n\tit('gets the last transition', () => {\n\t\ttransitions = transitionsFactory(5);\n\t\trepo.update(transitions);\n\n\t\texpect(repo.getLast().component).toBe(\n\t\t\ttransitions[transitions.length - 1]\n\t\t);\n\t});\n\n\tit('gets the transition by an index number', () => {\n\t\ttransitions = transitionsFactory(5);\n\t\trepo.update(transitions);\n\n\t\texpect(repo.getByIndex(2).component).toBe(transitions[2]);\n\t});\n\n\tit('gets the transition by order next', () => {\n\t\ttransitions = transitionsFactory(5);\n\t\trepo.update(transitions);\n\n\t\texpect(repo.getByOrder(Directions.next, 2).component).toBe(\n\t\t\ttransitions[3]\n\t\t);\n\t});\n\n\tit('gets fist the transition by order next', () => {\n\t\ttransitions = transitionsFactory(5);\n\t\trepo.update(transitions);\n\n\t\texpect(repo.getByOrder(Directions.next, 4).component).toBe(\n\t\t\ttransitions[3]\n\t\t);\n\t});\n\n\tit('gets the transition by order previous', () => {\n\t\ttransitions = transitionsFactory(5);\n\t\trepo.update(transitions);\n\n\t\texpect(repo.getByOrder(Directions.prev, 2).component).toBe(\n\t\t\ttransitions[1]\n\t\t);\n\t});\n\n\tit('gets the last transition by order previous', () => {\n\t\ttransitions = transitionsFactory(5);\n\t\trepo.update(transitions);\n\n\t\texpect(repo.getByOrder(Directions.prev, 0).component).toBe(\n\t\t\ttransitions[1]\n\t\t);\n\t});\n});\n"
  },
  {
    "path": "src/repositories/Transitions/Transitions.ts",
    "content": "import { type Component, shallowReactive } from 'vue';\nimport { Directions, type Direction } from '../../controllers/Player';\nimport type { TransitionIndex } from './types';\nimport type { TransitionWithOptions } from '../../transitions/types';\nimport TransitionsMapper from './TransitionsMapper';\n\nexport default class Transitions {\n\tlist: TransitionWithOptions[] = shallowReactive([]);\n\n\tprivate getPrev(lastIndex: number) {\n\t\treturn this.getByIndex(lastIndex > 0 ? lastIndex - 1 : this.list.length - 1);\n\t}\n\n\tprivate getNext(lastIndex: number) {\n\t\treturn this.getByIndex(lastIndex === this.list.length - 1 ? 0 : lastIndex + 1);\n\t}\n\n\tgetFirst() {\n\t\treturn this.getByIndex(0);\n\t}\n\n\tgetLast() {\n\t\treturn this.getByOrder(Directions.prev, 0);\n\t}\n\n\tgetByIndex(index: number) {\n\t\tconst item = this.list[index];\n\n\t\tif (!item) {\n\t\t\tthrow new Error(`Transition index ${index} out of range`);\n\t\t}\n\n\t\treturn {\n\t\t\tindex,\n\t\t\tcomponent: item.component,\n\t\t\toptions: JSON.parse(JSON.stringify(item.options)),\n\t\t} as TransitionIndex;\n\t}\n\n\tgetByOrder(direction: Direction, lastIndex: number) {\n\t\treturn {\n\t\t\tprev: () => this.getPrev(lastIndex),\n\t\t\tnext: () => this.getNext(lastIndex),\n\t\t}[direction]();\n\t}\n\n\tupdate(transitions: (Component | TransitionWithOptions)[]) {\n\t\tthis.list.splice(0);\n\n\t\tconst transitionsWithOptions = TransitionsMapper.withOptions(transitions);\n\n\t\tthis.list.push(...transitionsWithOptions);\n\t}\n}\n"
  },
  {
    "path": "src/repositories/Transitions/TransitionsMapper.test.ts",
    "content": "import TransitionsMapper from './TransitionsMapper';\nimport { Fade, Kenburn, Swipe, Slide, type TransitionWithOptions } from '../../transitions';\n\ndescribe('repositories: TransitionsMapper', () => {\n\tit('turns all the transitions array as transitions with options', () => {\n\t\tconst transitions = [\n\t\t\tFade,\n\t\t\t{\n\t\t\t\tcomponent: Kenburn,\n\t\t\t\toptions: { totalDuration: 1600 },\n\t\t\t} as TransitionWithOptions,\n\t\t\tSwipe,\n\t\t\t{\n\t\t\t\tcomponent: Slide,\n\t\t\t\toptions: { totalDuration: 4600 },\n\t\t\t} as TransitionWithOptions,\n\t\t];\n\n\t\tconst transitionsWithOptions = TransitionsMapper.withOptions(transitions);\n\n\t\texpect(transitionsWithOptions[0]!.component).toBe(Fade);\n\t\texpect(transitionsWithOptions[1]!.component).toBe(Kenburn);\n\t\t// @ts-expect-error:next-line\n\t\texpect(transitionsWithOptions[1]!.options.totalDuration).toBe(1600);\n\t\texpect(transitionsWithOptions[2]!.component).toBe(Swipe);\n\t\texpect(transitionsWithOptions[3]!.component).toBe(Slide);\n\t\t// @ts-expect-error:next-line\n\t\texpect(transitionsWithOptions[3]!.options.totalDuration).toBe(4600);\n\t});\n});\n"
  },
  {
    "path": "src/repositories/Transitions/TransitionsMapper.ts",
    "content": "import type { Component } from 'vue';\nimport type { TransitionComponent, TransitionWithOptions } from '../../transitions';\n\nexport default class TransitionsMapper {\n\tstatic withOptions(transitions: (Component | TransitionWithOptions)[]) {\n\t\treturn transitions.map((transition) => {\n\t\t\tlet component = transition;\n\t\t\tlet options = {};\n\n\t\t\tif ('component' in transition) {\n\t\t\t\tcomponent = transition.component as TransitionComponent;\n\n\t\t\t\tif ('options' in transition) {\n\t\t\t\t\toptions = transition.options;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn { component, options } as TransitionWithOptions;\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "src/repositories/Transitions/types.ts",
    "content": "import type { Component } from 'vue';\nimport type { Direction } from '../../controllers/Player';\n\nexport interface TransitionIndex {\n\tindex: number;\n\tcomponent: Component;\n\toptions: {\n\t\tdirection?: Direction;\n\t};\n}\n"
  },
  {
    "path": "src/repositories/index.ts",
    "content": "export { default as Resources } from './Resources/Resources';\nexport { default as Transitions } from './Transitions/Transitions';\n\nexport type { ResourceIndex } from './Resources/types';\nexport type { TransitionIndex } from './Transitions/types';\n"
  },
  {
    "path": "src/resources/Img/Img.test.ts",
    "content": "import { FluxImage } from '../../components';\nimport ResizeTypes from '../ResizeTypes';\nimport Statuses from '../Statuses';\nimport type { DisplayParameter, ResizeType, TransitionParameter } from '../types';\nimport Img from './Img';\n\ndescribe('resources: Img', () => {\n\tlet img,\n\t\tsrc: string,\n\t\tcaption: string,\n\t\tresizeType: ResizeType,\n\t\tbackgroundColor: string,\n\t\tpromise: Promise<void>,\n\t\tresolve: () => void,\n\t\treject: (message: string) => void;\n\n\tbeforeEach(() => {\n\t\tsrc = 'src';\n\t\tcaption = 'caption';\n\t\tresizeType = ResizeTypes.fill;\n\t\tbackgroundColor = '#ccc';\n\t});\n\n\tit('creates the instance properly with default params', () => {\n\t\timg = new Img(src);\n\n\t\texpect(img.src).toBe(src);\n\t\texpect(img.caption).toBe('');\n\t\texpect(img.resizeType).toBe(ResizeTypes.fill);\n\t\texpect(img.backgroundColor).toBeNull();\n\t});\n\n\tit('creates the instance properly with custom params', () => {\n\t\timg = new Img(src, caption, resizeType, backgroundColor);\n\n\t\texpect(img.src).toBe(src);\n\t\texpect(img.caption).toBe(caption);\n\t\texpect(img.resizeType).toBe(resizeType);\n\t\texpect(img.backgroundColor).toBe(backgroundColor);\n\t});\n\n\tit('creates the instance with the required parameters of abstract Resource', () => {\n\t\timg = new Img(src);\n\n\t\texpect(img.display).toStrictEqual({\n\t\t\tcomponent: FluxImage,\n\t\t\tprops: {},\n\t\t} as DisplayParameter);\n\n\t\texpect(img.transition).toStrictEqual({\n\t\t\tcomponent: FluxImage,\n\t\t\tprops: {},\n\t\t} as TransitionParameter);\n\n\t\texpect(img.errorMessage).toBe(`Image ${src} could not be loaded`);\n\t});\n\n\tit('returns a promise and sets it to property loader', () => {\n\t\timg = new Img(src);\n\n\t\tpromise = img.load();\n\t\texpect(promise).toBeTypeOf('object');\n\t\texpect(img.loader).toBe(promise);\n\t});\n\n\tit('changes the status to loading', () => {\n\t\timg = new Img(src);\n\t\timg.load();\n\n\t\texpect(img.status.value).toBe(Statuses.loading);\n\t});\n\n\tit('returns the loader if already request to load', () => {\n\t\timg = new Img(src);\n\t\tpromise = img.load();\n\n\t\texpect(img.load()).toBe(promise);\n\t});\n\n\tit.todo('calls onLoad when load success');\n\n\tit('sets reals size on load', () => {\n\t\tsrc = '/imgs/pixel.png';\n\t\timg = new Img(src);\n\n\t\tconst htmlImage = new Image();\n\t\thtmlImage.width = 640;\n\t\thtmlImage.height = 480;\n\n\t\tresolve = vi.fn();\n\t\timg.onLoad(htmlImage, resolve);\n\n\t\texpect(img.realSize.toValue()).toStrictEqual({ width: 640, height: 480 });\n\t});\n\n\tit('sets status loaded on load', () => {\n\t\tsrc = '/imgs/pixel.png';\n\t\timg = new Img(src);\n\n\t\tconst htmlImage = new Image();\n\t\thtmlImage.width = 640;\n\t\thtmlImage.height = 480;\n\n\t\tresolve = vi.fn();\n\t\timg.onLoad(htmlImage, resolve);\n\n\t\texpect(img.status.value).toBe(Statuses.loaded);\n\t});\n\n\tit('calls promise resolve on load', () => {\n\t\tsrc = '/imgs/pixel.png';\n\t\timg = new Img(src);\n\n\t\tconst htmlImage = new Image();\n\t\thtmlImage.width = 640;\n\t\thtmlImage.height = 480;\n\n\t\tresolve = vi.fn();\n\t\timg.onLoad(htmlImage, resolve);\n\n\t\texpect(resolve).toHaveBeenCalledOnce();\n\t});\n\n\tit.todo('calls onError when load fails');\n\n\tit('sets the status to error on error', () => {\n\t\timg = new Img(src);\n\n\t\treject = vi.fn();\n\t\timg.onError(reject);\n\n\t\texpect(img.status.value).toBe(Statuses.error);\n\t});\n\n\tit('performs promise reject on error', () => {\n\t\timg = new Img(src);\n\n\t\treject = vi.fn();\n\t\timg.onError(reject);\n\n\t\texpect(reject).toHaveBeenCalledWith(img.errorMessage);\n\t});\n});\n"
  },
  {
    "path": "src/resources/Img/Img.ts",
    "content": "import { FluxImage } from '../../components';\nimport { Resource, Statuses, ResizeTypes } from '../';\nimport { Size } from '../../shared';\nimport type { DisplayParameter, ResizeType, TransitionParameter } from '../types';\n\nexport default class Img extends Resource {\n\tconstructor(\n\t\tsrc: string,\n\t\tcaption: string = '',\n\t\tresizeType: ResizeType = ResizeTypes.fill,\n\t\tbackgroundColor: null | string = null,\n\t) {\n\t\tconst display: DisplayParameter = {\n\t\t\tcomponent: FluxImage,\n\t\t\tprops: {},\n\t\t};\n\n\t\tconst transition: TransitionParameter = {\n\t\t\tcomponent: FluxImage,\n\t\t\tprops: {},\n\t\t};\n\n\t\tconst errorMessage = `Image ${src} could not be loaded`;\n\n\t\tsuper(src, caption, resizeType, backgroundColor, display, transition, errorMessage);\n\t}\n\n\tload() {\n\t\tif (this.loader !== null) {\n\t\t\treturn this.loader;\n\t\t}\n\n\t\tthis.loader = new Promise<void>((resolve, reject) => {\n\t\t\tthis.status.value = Statuses.loading;\n\n\t\t\tconst img = new Image();\n\n\t\t\timg.onload = () => this.onLoad(img, resolve);\n\t\t\timg.onerror = () => this.onError(reject);\n\n\t\t\timg.src = this.src;\n\t\t});\n\n\t\treturn this.loader;\n\t}\n\n\tonLoad(img: HTMLImageElement, resolve: () => void) {\n\t\tthis.realSize = new Size({\n\t\t\twidth: img.naturalWidth || img.width,\n\t\t\theight: img.naturalHeight || img.height,\n\t\t});\n\n\t\tthis.status.value = Statuses.loaded;\n\n\t\tresolve();\n\t}\n\n\tonError(reject: (message: string) => void) {\n\t\tthis.status.value = Statuses.error;\n\n\t\treject(this.errorMessage);\n\t}\n}\n"
  },
  {
    "path": "src/resources/Img/__mocks__/Img.ts",
    "content": "import { vi } from 'vitest';\nimport { ResizeTypes, Statuses, Resource } from '../../';\nimport { FluxImage } from '../../../components';\n\nexport default class Img extends Resource {\n\tconstructor() {\n\t\tsuper(\n\t\t\t'',\n\t\t\t'',\n\t\t\tResizeTypes.fill,\n\t\t\tnull,\n\t\t\t{ component: FluxImage, props: {} },\n\t\t\t{ component: FluxImage, props: {} },\n\t\t\t''\n\t\t);\n\t}\n\n\tload = vi.fn().mockImplementation(() => {\n\t\treturn new Promise<void>((resolve) => {\n\t\t\tthis.status.value = Statuses.loading;\n\t\t\tthis.onLoad(null, resolve);\n\t\t});\n\t});\n\n\tonLoad = vi\n\t\t.fn()\n\t\t// eslint-disable-next-line @typescript-eslint/no-unused-vars\n\t\t.mockImplementation((_el: unknown, resolve: () => void) => {\n\t\t\tthis.status.value = Statuses.loaded;\n\t\t\tresolve();\n\t\t});\n\n\tonError = vi\n\t\t.fn()\n\t\t// eslint-disable-next-line @typescript-eslint/no-unused-vars\n\t\t.mockImplementation((_reject: (message: string) => void) => {});\n}\n"
  },
  {
    "path": "src/resources/ResizeTypes.ts",
    "content": "export enum ResizeTypes {\n\tfill = 'fill',\n\tfit = 'fit',\n}\n\nexport default ResizeTypes;\n"
  },
  {
    "path": "src/resources/Resource.ts",
    "content": "import { computed, ref, type Ref } from 'vue';\nimport { Size, Position, ResizeCalculator } from '../shared';\nimport type { DisplayParameter, ResizedProps, ResizeType, TransitionParameter } from './types';\nimport { Statuses, ResizeTypes } from './';\n\nexport default abstract class Resource {\n\tsrc: string;\n\tloader: Promise<void> | null = null;\n\terrorMessage: string;\n\tstatus: Ref<Statuses> = ref(Statuses.notLoaded);\n\n\trealSize: Size = new Size();\n\tdisplaySize: Size = new Size();\n\tcaption: string = '';\n\tresizeType: ResizeType;\n\tbackgroundColor: null | string = null;\n\tdisplay: DisplayParameter;\n\ttransition: TransitionParameter;\n\n\tconstructor(\n\t\tsrc: string,\n\t\tcaption: string,\n\t\tresizeType: ResizeType = ResizeTypes.fill,\n\t\tbackgroundColor: null | string = null,\n\t\tdisplay: DisplayParameter,\n\t\ttransition: TransitionParameter,\n\t\terrorMessage: string,\n\t) {\n\t\tthis.src = src;\n\t\tthis.caption = caption;\n\t\tthis.resizeType = resizeType;\n\t\tthis.backgroundColor = backgroundColor;\n\t\tthis.display = display;\n\t\tthis.transition = transition;\n\t\tthis.errorMessage = errorMessage;\n\t}\n\n\tisLoading = () => this.status.value === Statuses.loading;\n\n\tisLoaded = () => this.status.value === Statuses.loaded;\n\n\tisError = () => this.status.value === Statuses.error;\n\n\tabstract load(): Promise<void>;\n\n\tabstract onLoad(el: unknown, resolve: () => void): void;\n\n\tabstract onError(reject: (message: string) => void): void;\n\n\tcalcResizeProps(displaySize: Size) {\n\t\tif ([displaySize.isValid(), this.realSize.isValid()].includes(false)) {\n\t\t\treturn {};\n\t\t}\n\n\t\tconst resCalc = new ResizeCalculator(this.realSize);\n\t\tconst { size, position } = resCalc.resizeTo(displaySize, this.resizeType);\n\n\t\treturn {\n\t\t\t...size.toValue(),\n\t\t\t...position.toValue(),\n\t\t};\n\t}\n\n\tresizeProps = computed<{\n\t\ttop?: number;\n\t\tleft?: number;\n\t\twidth?: number;\n\t\theight?: number;\n\t}>(() => this.calcResizeProps(this.displaySize));\n\n\tgetResizeProps(size: Size, offset?: Position) {\n\t\tconst resizedProps: ResizedProps = {\n\t\t\twidth: 0,\n\t\t\theight: 0,\n\t\t\ttop: 0,\n\t\t\tleft: 0,\n\t\t};\n\n\t\tif (!this.displaySize.isValid()) {\n\t\t\tthis.displaySize.update(size.toValue());\n\t\t}\n\n\t\tObject.assign(\n\t\t\tresizedProps,\n\t\t\tsize.equals(this.displaySize) ? this.resizeProps.value : this.calcResizeProps(size),\n\t\t);\n\n\t\tif (offset !== undefined) {\n\t\t\tresizedProps.top -= offset.top.value || 0;\n\t\t\tresizedProps.left -= offset.left.value || 0;\n\t\t}\n\n\t\treturn resizedProps;\n\t}\n}\n"
  },
  {
    "path": "src/resources/Statuses.ts",
    "content": "export enum Statuses {\n\tnotLoaded = 'notLoaded',\n\tloading = 'loading',\n\tloaded = 'loaded',\n\terror = 'error',\n}\n\nexport default Statuses;\n"
  },
  {
    "path": "src/resources/__test__/ResourceFactory.ts",
    "content": "import { Img } from '../';\n\nexport default class ResourceFactory {\n\tstatic create(amount: number) {\n\t\treturn new Array(amount).fill(new Img(''));\n\t}\n}\n"
  },
  {
    "path": "src/resources/index.ts",
    "content": "export { default as Resource } from './Resource';\nexport { default as Img } from './Img/Img';\nexport { default as Statuses } from './Statuses';\nexport { default as ResizeTypes } from './ResizeTypes';\n\nexport type * from './types';\n"
  },
  {
    "path": "src/resources/types.ts",
    "content": "import type { Component } from 'vue';\nimport { Resource } from '.';\nimport ResizeTypes from './ResizeTypes';\n\nexport type ResizeType = keyof typeof ResizeTypes;\n\nexport interface ResizedProps {\n\twidth: number;\n\theight: number;\n\ttop: number;\n\tleft: number;\n}\n\nexport interface DisplayParameter {\n\tcomponent: Component;\n\tprops: object;\n}\n\nexport interface TransitionParameter {\n\tcomponent: Component;\n\tprops: object;\n}\n\nexport interface ResourceWithOptions {\n\tresource: Resource;\n\toptions: {\n\t\tdelay?: number;\n\t\tstop?: boolean;\n\t};\n}\n"
  },
  {
    "path": "src/shared/Maths/Maths.test.ts",
    "content": "import * as Maths from './Maths';\n\ndescribe('shared: Maths', () => {\n\tit('calculates the diagonal', () => {\n\t\tconst size = {\n\t\t\twidth: 640,\n\t\t\theight: 360,\n\t\t};\n\n\t\texpect(Maths.diag(size)).toBe(735);\n\t});\n\n\tit('calculates the aspect ratio', () => {\n\t\tconst size = {\n\t\t\twidth: 640,\n\t\t\theight: 320,\n\t\t};\n\n\t\texpect(Maths.aspectRatio(size)).toBe(2);\n\t});\n});\n"
  },
  {
    "path": "src/shared/Maths/Maths.ts",
    "content": "export const diag = ({ width, height }: { width: number; height: number }) =>\n\tMath.ceil(Math.sqrt(width * width + height * height));\n\nexport const aspectRatio = ({\n\twidth,\n\theight,\n}: {\n\twidth: number;\n\theight: number;\n}) => width / height;\n"
  },
  {
    "path": "src/shared/Position/Position.test.ts",
    "content": "import Position from './Position';\n\ndescribe('shared: Position', () => {\n\tlet pos: Position;\n\tlet coords: object;\n\n\tit('initializes values to null without parameters', () => {\n\t\tpos = new Position();\n\n\t\texpect(pos.top.value).toBeNull();\n\t\texpect(pos.left.value).toBeNull();\n\t});\n\n\tit('sets param values', () => {\n\t\tpos = new Position({ top: 100 });\n\t\texpect(pos.top.value).toBe(100);\n\n\t\tpos = new Position({ left: 100 });\n\t\texpect(pos.left.value).toBe(100);\n\n\t\tpos = new Position({\n\t\t\ttop: 100,\n\t\t\tleft: 200,\n\t\t});\n\t\texpect(pos.top.value).toBe(100);\n\t\texpect(pos.left.value).toBe(200);\n\t});\n\n\tit('reset values', () => {\n\t\tpos = new Position({\n\t\t\ttop: 100,\n\t\t\tleft: 200,\n\t\t});\n\n\t\tpos.reset();\n\n\t\texpect(pos.top.value).toBeNull();\n\t\texpect(pos.left.value).toBeNull();\n\t});\n\n\tit('is invalid if top or left is null', () => {\n\t\tpos = new Position({ top: 100 });\n\t\texpect(pos.isValid()).toBeFalsy();\n\n\t\tpos = new Position({ left: 100 });\n\t\texpect(pos.isValid()).toBeFalsy();\n\t});\n\n\tit('is valid when top and left have values', () => {\n\t\tpos = new Position({\n\t\t\ttop: 100,\n\t\t\tleft: 200,\n\t\t});\n\n\t\texpect(pos.isValid()).toBeTruthy();\n\t});\n\n\tit('updates the values', () => {\n\t\tpos = new Position({\n\t\t\ttop: 100,\n\t\t\tleft: 200,\n\t\t});\n\n\t\tpos.update({\n\t\t\ttop: 50,\n\t\t});\n\n\t\texpect(pos.top.value).toBe(50);\n\t\texpect(pos.left.value).toBeNull();\n\n\t\tpos.update({\n\t\t\tleft: 100,\n\t\t});\n\n\t\texpect(pos.top.value).toBeNull();\n\t\texpect(pos.left.value).toBe(100);\n\n\t\tpos.update({\n\t\t\ttop: 200,\n\t\t\tleft: 400,\n\t\t});\n\n\t\texpect(pos.top.value).toBe(200);\n\t\texpect(pos.left.value).toBe(400);\n\t});\n\n\tit('returns the values as plain object', () => {\n\t\tcoords = {\n\t\t\ttop: 100,\n\t\t\tleft: 200,\n\t\t};\n\n\t\tpos = new Position(coords);\n\t\texpect(pos.toValue()).toStrictEqual(coords);\n\n\t\tpos = new Position();\n\t\texpect(pos.toValue()).toStrictEqual({\n\t\t\ttop: undefined,\n\t\t\tleft: undefined,\n\t\t});\n\t});\n\n\tit('throws exception when trying to get the values with px suffix', () => {\n\t\tpos = new Position();\n\t\texpect(() => pos.toPx()).toThrow('Invalid position in pixels');\n\t});\n\n\tit('returns the values with px suffix', () => {\n\t\tcoords = {\n\t\t\ttop: 100,\n\t\t\tleft: 200,\n\t\t};\n\n\t\tpos = new Position(coords);\n\n\t\texpect(pos.toPx()).toStrictEqual({\n\t\t\ttop: coords['top' as keyof object] + 'px',\n\t\t\tleft: coords['left' as keyof object] + 'px',\n\t\t});\n\t});\n});\n"
  },
  {
    "path": "src/shared/Position/Position.ts",
    "content": "import { ref, type Ref } from 'vue';\n\nexport default class Position {\n\ttop: Ref<null | number> = ref(null);\n\tleft: Ref<null | number> = ref(null);\n\n\tconstructor(\n\t\t{ top = null, left = null }: { top?: null | number; left?: null | number } = {\n\t\t\ttop: null,\n\t\t\tleft: null,\n\t\t},\n\t) {\n\t\tthis.update({ top, left });\n\t}\n\n\treset() {\n\t\tthis.top.value = null;\n\t\tthis.left.value = null;\n\t}\n\n\tisValid() {\n\t\treturn ![this.top.value, this.left.value].includes(null);\n\t}\n\n\tupdate({ top, left }: { top?: null | number; left?: null | number }) {\n\t\tthis.top.value = top ?? null;\n\t\tthis.left.value = left ?? null;\n\t}\n\n\ttoValue() {\n\t\tconst rawPosition: {\n\t\t\ttop?: number;\n\t\t\tleft?: number;\n\t\t} = {\n\t\t\ttop: undefined,\n\t\t\tleft: undefined,\n\t\t};\n\n\t\tif (this.top.value !== null) {\n\t\t\trawPosition.top = this.top.value;\n\t\t}\n\n\t\tif (this.left.value !== null) {\n\t\t\trawPosition.left = this.left.value;\n\t\t}\n\n\t\treturn rawPosition;\n\t}\n\n\ttoPx() {\n\t\tif (!this.isValid()) {\n\t\t\tthrow new RangeError('Invalid position in pixels');\n\t\t}\n\n\t\treturn {\n\t\t\ttop: this.top.value!.toString() + 'px',\n\t\t\tleft: this.left.value!.toString() + 'px',\n\t\t};\n\t}\n}\n"
  },
  {
    "path": "src/shared/ResizeCalculator/ResizeCalculator.test.ts",
    "content": "import { Size } from '../';\nimport { ResizeTypes } from '../../resources';\nimport ResizeCalculator, { Orientations } from './ResizeCalculator';\n\ndescribe('shared: ResizeCalculator', () => {\n\tlet calc: ResizeCalculator;\n\tlet realSize: Size;\n\tlet newSize: Size;\n\n\tbeforeEach(() => {\n\t\trealSize = new Size();\n\t\tnewSize = new Size();\n\t});\n\n\tit('if the size is invalid throws error', () => {\n\t\tvi.spyOn(realSize, 'isValid').mockImplementation(() => false);\n\n\t\texpect(() => {\n\t\t\tcalc = new ResizeCalculator(realSize);\n\t\t}).toThrow('Invalid real size');\n\n\t\texpect(realSize.isValid).toHaveBeenCalledWith();\n\t});\n\n\tit('if the size is valid when creating the calculator', () => {\n\t\tvi.spyOn(realSize, 'isValid').mockImplementation(() => true);\n\n\t\texpect(() => {\n\t\t\tcalc = new ResizeCalculator(realSize);\n\t\t}).not.toThrow();\n\n\t\texpect(realSize.isValid).toHaveBeenCalledWith();\n\t});\n\n\tit('detects the orientation', () => {\n\t\trealSize.update({\n\t\t\twidth: 640,\n\t\t\theight: 360,\n\t\t});\n\n\t\tcalc = new ResizeCalculator(realSize);\n\n\t\texpect(calc.realOrientation).toBe(Orientations.landscape);\n\n\t\trealSize.update({\n\t\t\twidth: 360,\n\t\t\theight: 640,\n\t\t});\n\n\t\tcalc = new ResizeCalculator(realSize);\n\n\t\texpect(calc.realOrientation).toBe(Orientations.portrait);\n\t});\n\n\tit('if the new size is valid', () => {\n\t\tvi.spyOn(newSize, 'isValid').mockImplementation(() => false);\n\n\t\trealSize.update({\n\t\t\twidth: 640,\n\t\t\theight: 360,\n\t\t});\n\n\t\tcalc = new ResizeCalculator(realSize);\n\n\t\texpect(() => {\n\t\t\tcalc.resizeTo(newSize, ResizeTypes.fill);\n\t\t}).toThrow('Invalid size to resize');\n\n\t\texpect(newSize.isValid).toHaveBeenCalledWith();\n\t});\n\n\tit('new size L real size L and newAspectRatio >= realAspectRatio and type fill', () => {\n\t\tnewSize.update({\n\t\t\twidth: 280,\n\t\t\theight: 140,\n\t\t});\n\n\t\trealSize.update({\n\t\t\twidth: 640,\n\t\t\theight: 360,\n\t\t});\n\n\t\tcalc = new ResizeCalculator(realSize);\n\n\t\tconst { size: adaptedSize, position: adaptedPosition } = calc.resizeTo(\n\t\t\tnewSize,\n\t\t\tResizeTypes.fill\n\t\t);\n\n\t\texpect(adaptedSize.toValue()).toStrictEqual({\n\t\t\twidth: 280,\n\t\t\theight: 157.5,\n\t\t});\n\t\texpect(adaptedPosition.toValue()).toStrictEqual({ top: -8.75, left: 0 });\n\t});\n\n\tit('new size L real size L and newAspectRatio < realAspectRatio and type fill', () => {\n\t\tnewSize.update({\n\t\t\twidth: 280,\n\t\t\theight: 180,\n\t\t});\n\n\t\trealSize.update({\n\t\t\twidth: 280,\n\t\t\theight: 140,\n\t\t});\n\n\t\tcalc = new ResizeCalculator(realSize);\n\n\t\tconst { size: adaptedSize, position: adaptedPosition } = calc.resizeTo(\n\t\t\tnewSize,\n\t\t\tResizeTypes.fit\n\t\t);\n\n\t\texpect(adaptedSize.toValue()).toStrictEqual({\n\t\t\twidth: 280,\n\t\t\theight: 140,\n\t\t});\n\t\texpect(adaptedPosition.toValue()).toStrictEqual({ top: 20, left: 0 });\n\t});\n\n\tit('new size L real size L and newAspectRatio >= realAspectRatio and type fit', () => {\n\t\tnewSize.update({\n\t\t\twidth: 280,\n\t\t\theight: 140,\n\t\t});\n\n\t\trealSize.update({\n\t\t\twidth: 280,\n\t\t\theight: 200,\n\t\t});\n\n\t\tcalc = new ResizeCalculator(realSize);\n\n\t\tconst { size: adaptedSize, position: adaptedPosition } = calc.resizeTo(\n\t\t\tnewSize,\n\t\t\tResizeTypes.fit\n\t\t);\n\n\t\texpect(adaptedSize.toValue()).toStrictEqual({ width: 196, height: 140 });\n\t\texpect(adaptedPosition.toValue()).toStrictEqual({ top: 0, left: 42 });\n\t});\n\n\tit('new size L real size L and newAspectRatio < realAspectRatio and type fit', () => {\n\t\tnewSize.update({\n\t\t\twidth: 280,\n\t\t\theight: 180,\n\t\t});\n\n\t\trealSize.update({\n\t\t\twidth: 280,\n\t\t\theight: 140,\n\t\t});\n\n\t\tcalc = new ResizeCalculator(realSize);\n\n\t\tconst { size: adaptedSize, position: adaptedPosition } = calc.resizeTo(\n\t\t\tnewSize,\n\t\t\tResizeTypes.fit\n\t\t);\n\n\t\texpect(adaptedSize.toValue()).toStrictEqual({\n\t\t\twidth: 280,\n\t\t\theight: 140,\n\t\t});\n\t\texpect(adaptedPosition.toValue()).toStrictEqual({ top: 20, left: 0 });\n\t});\n\n\tit('new size L real size P and type fill', () => {\n\t\tnewSize.update({\n\t\t\twidth: 280,\n\t\t\theight: 140,\n\t\t});\n\n\t\trealSize.update({\n\t\t\twidth: 140,\n\t\t\theight: 280,\n\t\t});\n\n\t\tcalc = new ResizeCalculator(realSize);\n\n\t\tconst { size: adaptedSize, position: adaptedPosition } = calc.resizeTo(\n\t\t\tnewSize,\n\t\t\tResizeTypes.fill\n\t\t);\n\n\t\texpect(adaptedSize.toValue()).toStrictEqual({ width: 280, height: 560 });\n\t\texpect(adaptedPosition.toValue()).toStrictEqual({ top: -210, left: 0 });\n\t});\n\n\tit('new size L real size P and type fit', () => {\n\t\tnewSize.update({\n\t\t\twidth: 280,\n\t\t\theight: 140,\n\t\t});\n\n\t\trealSize.update({\n\t\t\twidth: 140,\n\t\t\theight: 280,\n\t\t});\n\n\t\tcalc = new ResizeCalculator(realSize);\n\n\t\tconst { size: adaptedSize, position: adaptedPosition } = calc.resizeTo(\n\t\t\tnewSize,\n\t\t\tResizeTypes.fit\n\t\t);\n\n\t\texpect(adaptedSize.toValue()).toStrictEqual({\n\t\t\twidth: 70,\n\t\t\theight: 140,\n\t\t});\n\t\texpect(adaptedPosition.toValue()).toStrictEqual({ top: 0, left: 105 });\n\t});\n\n\tit('new size P real size L and type fill', () => {\n\t\tnewSize.update({\n\t\t\twidth: 140,\n\t\t\theight: 280,\n\t\t});\n\n\t\trealSize.update({\n\t\t\twidth: 280,\n\t\t\theight: 140,\n\t\t});\n\n\t\tcalc = new ResizeCalculator(realSize);\n\n\t\tconst { size: adaptedSize, position: adaptedPosition } = calc.resizeTo(\n\t\t\tnewSize,\n\t\t\tResizeTypes.fill\n\t\t);\n\n\t\texpect(adaptedSize.toValue()).toStrictEqual({ width: 560, height: 280 });\n\t\texpect(adaptedPosition.toValue()).toStrictEqual({ top: 0, left: -210 });\n\t});\n\n\tit('new size P real size L and type fit', () => {\n\t\tnewSize.update({\n\t\t\twidth: 140,\n\t\t\theight: 280,\n\t\t});\n\n\t\trealSize.update({\n\t\t\twidth: 280,\n\t\t\theight: 140,\n\t\t});\n\n\t\tcalc = new ResizeCalculator(realSize);\n\n\t\tconst { size: adaptedSize, position: adaptedPosition } = calc.resizeTo(\n\t\t\tnewSize,\n\t\t\tResizeTypes.fit\n\t\t);\n\n\t\texpect(adaptedSize.toValue()).toStrictEqual({\n\t\t\twidth: 140,\n\t\t\theight: 70,\n\t\t});\n\t\texpect(adaptedPosition.toValue()).toStrictEqual({\n\t\t\ttop: 105,\n\t\t\tleft: 0,\n\t\t});\n\t});\n\n\tit('new size P real size P and newAspectRatio >= realAspectRatio and type fill', () => {\n\t\tnewSize.update({\n\t\t\twidth: 140,\n\t\t\theight: 280,\n\t\t});\n\n\t\trealSize.update({\n\t\t\twidth: 180,\n\t\t\theight: 280,\n\t\t});\n\n\t\tcalc = new ResizeCalculator(realSize);\n\n\t\tconst { size: adaptedSize, position: adaptedPosition } = calc.resizeTo(\n\t\t\tnewSize,\n\t\t\tResizeTypes.fill\n\t\t);\n\n\t\texpect(adaptedSize.toValue()).toStrictEqual({\n\t\t\twidth: 180,\n\t\t\theight: 280,\n\t\t});\n\t\texpect(adaptedPosition.toValue()).toStrictEqual({ top: 0, left: -20 });\n\t});\n\n\tit('new size P real size P and newAspectRatio < realAspectRatio and type fill', () => {\n\t\tnewSize.update({\n\t\t\twidth: 180,\n\t\t\theight: 280,\n\t\t});\n\n\t\trealSize.update({\n\t\t\twidth: 140,\n\t\t\theight: 280,\n\t\t});\n\n\t\tcalc = new ResizeCalculator(realSize);\n\n\t\tconst { size: adaptedSize, position: adaptedPosition } = calc.resizeTo(\n\t\t\tnewSize,\n\t\t\tResizeTypes.fill\n\t\t);\n\n\t\texpect(adaptedSize.toValue()).toStrictEqual({\n\t\t\twidth: 180,\n\t\t\theight: 360,\n\t\t});\n\t\texpect(adaptedPosition.toValue()).toStrictEqual({ top: -40, left: 0 });\n\t});\n\n\tit('new size P real size P and newAspectRatio >= realAspectRatio and type fit', () => {\n\t\tnewSize.update({\n\t\t\twidth: 140,\n\t\t\theight: 280,\n\t\t});\n\n\t\trealSize.update({\n\t\t\twidth: 200,\n\t\t\theight: 280,\n\t\t});\n\n\t\tcalc = new ResizeCalculator(realSize);\n\n\t\tconst { size: adaptedSize, position: adaptedPosition } = calc.resizeTo(\n\t\t\tnewSize,\n\t\t\tResizeTypes.fit\n\t\t);\n\n\t\texpect(adaptedSize.toValue()).toStrictEqual({ width: 140, height: 196 });\n\t\texpect(adaptedPosition.toValue()).toStrictEqual({ top: 42, left: 0 });\n\t});\n\n\tit('new size P real size P and newAspectRatio < realAspectRatio and type fit', () => {\n\t\tnewSize.update({\n\t\t\twidth: 180,\n\t\t\theight: 280,\n\t\t});\n\n\t\trealSize.update({\n\t\t\twidth: 140,\n\t\t\theight: 280,\n\t\t});\n\n\t\tcalc = new ResizeCalculator(realSize);\n\n\t\tconst { size: adaptedSize, position: adaptedPosition } = calc.resizeTo(\n\t\t\tnewSize,\n\t\t\tResizeTypes.fit\n\t\t);\n\n\t\texpect(adaptedSize.toValue()).toStrictEqual({ width: 140, height: 280 });\n\t\texpect(adaptedPosition.toValue()).toStrictEqual({ top: 0, left: 20 });\n\t});\n});\n"
  },
  {
    "path": "src/shared/ResizeCalculator/ResizeCalculator.ts",
    "content": "import { type ResizeType, ResizeTypes } from '../../resources';\nimport { Size, Position } from '../';\n\nexport enum Orientations {\n\tlandscape = 'landscape',\n\tportrait = 'portrait',\n}\n\nconst getOrientation = (aspectRatio: number) =>\n\taspectRatio >= 1 ? Orientations.landscape : Orientations.portrait;\n\ntype Orientation = keyof typeof Orientations;\n\nexport default class ResizeCalculator {\n\trealSize: Size;\n\trealAspectRatio: number;\n\trealOrientation: Orientation;\n\n\tconstructor(realSize: Size) {\n\t\tif (realSize.isValid() === false) {\n\t\t\tthrow new RangeError('Invalid real size');\n\t\t}\n\n\t\tthis.realSize = realSize;\n\t\tthis.realAspectRatio = this.realSize.getAspectRatio();\n\t\tthis.realOrientation = getOrientation(this.realAspectRatio);\n\t}\n\n\tpublic resizeTo(resizeSize: Size, resizeType: ResizeType) {\n\t\tif (resizeSize.isValid() === false) {\n\t\t\tthrow new RangeError('Invalid size to resize');\n\t\t}\n\n\t\tconst resizeAspectRatio = resizeSize.getAspectRatio();\n\t\tconst resizeOrientation = getOrientation(resizeAspectRatio);\n\n\t\tconst adaptedSize: Size = this.getAdaptedSize(\n\t\t\tresizeSize,\n\t\t\tresizeAspectRatio,\n\t\t\tresizeOrientation,\n\t\t\tresizeType,\n\t\t);\n\n\t\tconst adaptedPosition: Position = this.getAdaptedPosition(\n\t\t\tresizeSize,\n\t\t\tresizeAspectRatio,\n\t\t\tadaptedSize,\n\t\t\tresizeType,\n\t\t);\n\n\t\treturn {\n\t\t\tsize: adaptedSize,\n\t\t\tposition: adaptedPosition,\n\t\t};\n\t}\n\n\tprivate getAdaptedSize(\n\t\tresizeSize: Size,\n\t\tresizeAspectRatio: number,\n\t\tresizeOrientation: Orientation,\n\t\tresizeType: ResizeType,\n\t) {\n\t\tif (\n\t\t\tresizeOrientation === Orientations.landscape &&\n\t\t\tthis.realOrientation === Orientations.portrait &&\n\t\t\tresizeType === ResizeTypes.fill\n\t\t) {\n\t\t\treturn this.getAdaptedSizeByWith(resizeSize);\n\t\t}\n\n\t\tif (\n\t\t\tresizeOrientation === Orientations.landscape &&\n\t\t\tthis.realOrientation === Orientations.landscape &&\n\t\t\tresizeAspectRatio >= this.realAspectRatio &&\n\t\t\tresizeType === ResizeTypes.fill\n\t\t) {\n\t\t\treturn this.getAdaptedSizeByWith(resizeSize);\n\t\t}\n\n\t\tif (\n\t\t\tresizeOrientation === Orientations.landscape &&\n\t\t\tthis.realOrientation === Orientations.landscape &&\n\t\t\tresizeAspectRatio < this.realAspectRatio &&\n\t\t\tresizeType === ResizeTypes.fit\n\t\t) {\n\t\t\treturn this.getAdaptedSizeByWith(resizeSize);\n\t\t}\n\n\t\tif (\n\t\t\tresizeOrientation === Orientations.portrait &&\n\t\t\tthis.realOrientation === Orientations.landscape &&\n\t\t\tresizeType === ResizeTypes.fit\n\t\t) {\n\t\t\treturn this.getAdaptedSizeByWith(resizeSize);\n\t\t}\n\n\t\tif (\n\t\t\tresizeOrientation === Orientations.portrait &&\n\t\t\tthis.realOrientation === Orientations.portrait &&\n\t\t\tresizeAspectRatio > this.realAspectRatio &&\n\t\t\tresizeType === ResizeTypes.fill\n\t\t) {\n\t\t\treturn this.getAdaptedSizeByWith(resizeSize);\n\t\t}\n\n\t\tif (\n\t\t\tresizeOrientation === Orientations.portrait &&\n\t\t\tthis.realOrientation === Orientations.portrait &&\n\t\t\tresizeAspectRatio <= this.realAspectRatio &&\n\t\t\tresizeType === ResizeTypes.fit\n\t\t) {\n\t\t\treturn this.getAdaptedSizeByWith(resizeSize);\n\t\t}\n\n\t\treturn this.getAdaptedSizeByHeight(resizeSize);\n\t}\n\n\tprivate getAdaptedSizeByWith(resizeSize: Size) {\n\t\treturn new Size({\n\t\t\twidth: resizeSize.width.value,\n\t\t\theight: resizeSize.width.value! / this.realAspectRatio,\n\t\t});\n\t}\n\n\tprivate getAdaptedSizeByHeight(resizeSize: Size) {\n\t\treturn new Size({\n\t\t\twidth: this.realAspectRatio * resizeSize.height.value!,\n\t\t\theight: resizeSize.height.value,\n\t\t});\n\t}\n\n\tprivate getAdaptedPosition(\n\t\tresizeSize: Size,\n\t\tresizeAspectRatio: number,\n\t\tadaptedSize: Size,\n\t\tresizeType: ResizeType,\n\t) {\n\t\tif (this.realAspectRatio <= resizeAspectRatio && resizeType === ResizeTypes.fill) {\n\t\t\treturn this.getAdaptedPositionVertically(resizeSize, adaptedSize);\n\t\t}\n\n\t\tif (this.realAspectRatio > resizeAspectRatio && resizeType === ResizeTypes.fit) {\n\t\t\treturn this.getAdaptedPositionVertically(resizeSize, adaptedSize);\n\t\t}\n\n\t\treturn this.getAdaptedPositionHorizontally(resizeSize, adaptedSize);\n\t}\n\n\tgetAdaptedPositionVertically(resizeSize: Size, adaptedSize: Size) {\n\t\treturn new Position({\n\t\t\ttop: (resizeSize.height.value! - adaptedSize.height.value!) / 2,\n\t\t\tleft: 0,\n\t\t});\n\t}\n\n\tgetAdaptedPositionHorizontally(resizeSize: Size, adaptedSize: Size) {\n\t\treturn new Position({\n\t\t\ttop: 0,\n\t\t\tleft: (resizeSize.width.value! - adaptedSize.width.value!) / 2,\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "src/shared/ResourceLoader/ResourceLoader.test.ts",
    "content": "import { vi } from 'vitest';\nimport ResourceLoader from './ResourceLoader';\nimport ResourceLoaderFactory from './__test__/ResourceLoaderFactory';\nimport { Statuses } from '../../resources';\n\nvi.mock('../../resources/Img/Img');\n\ndescribe('shared: ResourceLoader', () => {\n\tlet rscLoader: ResourceLoader;\n\n\tbeforeEach(() => {\n\t\tvi.clearAllMocks();\n\t});\n\n\tit('calls onPreloadStart when preload starts', () => {\n\t\trscLoader = ResourceLoaderFactory.create(10, 5);\n\n\t\texpect(rscLoader.onPreloadStart).toHaveBeenCalledOnce();\n\t});\n\n\tit('preloads all resources if num resources less than num to preload', () => {\n\t\trscLoader = ResourceLoaderFactory.create(10, 15);\n\n\t\texpect(rscLoader.toPreload).toBe(10);\n\t});\n\n\tit('start preloading when created', () => {\n\t\trscLoader = ResourceLoaderFactory.create(10, 10);\n\n\t\texpect(rscLoader.onPreloadStart).toHaveBeenCalledOnce();\n\t});\n\n\tit('start preloading the resources', () => {\n\t\trscLoader = ResourceLoaderFactory.create(15, 10);\n\n\t\texpect(\n\t\t\trscLoader.rscs.every((rsc) =>\n\t\t\t\t[Statuses.loading, Statuses.loaded].includes(rsc.resource.status.value),\n\t\t\t),\n\t\t).toBeTruthy();\n\t});\n\n\tit('checks if resources preloaded are less than to preload and preloads the remaining', () => {\n\t\trscLoader = ResourceLoaderFactory.create(15, 6);\n\n\t\trscLoader.counter.success = 4;\n\t\trscLoader.counter.error = 2;\n\t\trscLoader.counter.total = 6;\n\n\t\trscLoader.preloadEnd();\n\n\t\texpect(rscLoader.preLoading).toHaveLength(8);\n\t});\n\n\tit('calls onPreloadEnd when all preloaded', () =>\n\t\tnew Promise<void>((done) => {\n\t\t\trscLoader = ResourceLoaderFactory.create(5, 5, undefined, () => {\n\t\t\t\texpect(rscLoader.onPreloadStart).toHaveBeenCalledOnce();\n\t\t\t\texpect(rscLoader.onPreloadEnd).toHaveBeenCalledWith(expect.any(Array));\n\t\t\t\tdone();\n\t\t\t});\n\t\t}));\n\n\tit('starts lazy loading when preloading finish', () =>\n\t\tnew Promise<void>((done) => {\n\t\t\trscLoader = ResourceLoaderFactory.create(20, 5, undefined, undefined, () => {\n\t\t\t\texpect(rscLoader.onPreloadStart).toHaveBeenCalledOnce();\n\t\t\t\texpect(rscLoader.onPreloadEnd).toHaveBeenCalledOnce();\n\t\t\t\texpect(rscLoader.counter.total).toBe(5);\n\t\t\t\texpect(rscLoader.onLazyLoadStart).toHaveBeenCalledOnce();\n\t\t\t\tdone();\n\t\t\t});\n\t\t}));\n\n\tit('calls onLazyLoadEnd when lazy loading finish', () =>\n\t\tnew Promise<void>((done) => {\n\t\t\trscLoader = ResourceLoaderFactory.create(20, 5, undefined, undefined, undefined, () => {\n\t\t\t\texpect(rscLoader.onPreloadStart).toHaveBeenCalledOnce();\n\t\t\t\texpect(rscLoader.onPreloadEnd).toHaveBeenCalledOnce();\n\t\t\t\texpect(rscLoader.onLazyLoadStart).toHaveBeenCalledOnce();\n\t\t\t\texpect(rscLoader.onLazyLoadEnd).toHaveBeenCalledWith(expect.any(Array));\n\t\t\t\texpect(rscLoader.counter.total).toBe(20);\n\t\t\t\tdone();\n\t\t\t});\n\t\t}));\n\n\tit('does not update display size if cancelled', () => {\n\t\trscLoader = ResourceLoaderFactory.create(10, 5);\n\t\trscLoader.cancel();\n\n\t\tconst rsc = rscLoader.rscs[0];\n\t\tvi.spyOn(rsc!.resource.displaySize, 'update');\n\n\t\trscLoader.loadSuccess(rsc!);\n\n\t\texpect(rsc!.resource.displaySize.update).not.toHaveBeenCalled();\n\t});\n\n\tit('calculates the progress properly', () => {\n\t\trscLoader = ResourceLoaderFactory.create(15, 6);\n\n\t\trscLoader.counter.success = 4;\n\t\trscLoader.counter.error = 2;\n\t\trscLoader.counter.total = 6;\n\n\t\trscLoader.updateProgress();\n\n\t\texpect(rscLoader.progress.value).toBe(67);\n\t});\n});\n"
  },
  {
    "path": "src/shared/ResourceLoader/ResourceLoader.ts",
    "content": "import { type Ref, ref } from 'vue';\nimport { Size } from '../';\nimport type { ResourceWithOptions } from '../../resources';\n\nexport default class ResourceLoader {\n\trscs: ResourceWithOptions[] = [];\n\tcounter = {\n\t\tsuccess: 0,\n\t\terror: 0,\n\t\ttotal: 0,\n\t};\n\ttoPreload: number;\n\tpreLoading: ResourceWithOptions[] = [];\n\tlazyLoading: ResourceWithOptions[] = [];\n\tprogress: Ref<number> = ref(0);\n\tdisplaySize: Size;\n\tonPreloadStart: () => void;\n\tonPreloadEnd: (loaded: ResourceWithOptions[]) => void;\n\tonLazyLoadStart: () => void;\n\tonLazyLoadEnd: (loaded: ResourceWithOptions[]) => void;\n\tisCancelled: boolean = false;\n\treject: (message: string, rscs: ResourceWithOptions[]) => void;\n\n\tconstructor(\n\t\trscs: ResourceWithOptions[],\n\t\ttoPreload: number,\n\t\tdisplaySize: Size,\n\t\tonPreloadStart: () => void,\n\t\tonPreloadEnd: (loaded: ResourceWithOptions[]) => void,\n\t\tonLazyLoadStart: () => void,\n\t\tonLazyLoadEnd: (loaded: ResourceWithOptions[]) => void,\n\t\treject: (message: string, rscs: ResourceWithOptions[]) => void,\n\t) {\n\t\tthis.rscs = rscs;\n\t\tthis.toPreload = toPreload > rscs.length ? rscs.length : toPreload;\n\t\tthis.displaySize = displaySize;\n\t\tthis.onPreloadStart = onPreloadStart;\n\t\tthis.onPreloadEnd = onPreloadEnd;\n\t\tthis.onLazyLoadStart = onLazyLoadStart;\n\t\tthis.onLazyLoadEnd = onLazyLoadEnd;\n\t\tthis.reject = reject;\n\n\t\tthis.preloadStart();\n\t}\n\n\tpreloadStart() {\n\t\tthis.onPreloadStart();\n\n\t\tconst { counter } = this;\n\n\t\tconst toLoad = this.rscs.slice(\n\t\t\tcounter.total,\n\t\t\tcounter.total + this.toPreload - counter.success,\n\t\t);\n\n\t\tthis.preLoading = this.preLoading.concat(toLoad);\n\n\t\ttoLoad.forEach((rsc) => this.load(rsc));\n\t}\n\n\tpreloadEnd() {\n\t\tconst { counter, toPreload } = this;\n\n\t\tif (counter.success < toPreload && counter.total < this.rscs.length) {\n\t\t\tthis.preloadStart();\n\t\t\treturn;\n\t\t}\n\n\t\tconst preloadedSuccessfully = this.preLoading.filter((rsc) => rsc.resource.isLoaded());\n\n\t\tthis.onPreloadEnd(preloadedSuccessfully);\n\n\t\tthis.preLoading.length = 0;\n\n\t\tif (counter.total < this.rscs.length) {\n\t\t\tthis.lazyLoadStart();\n\t\t}\n\t}\n\n\tlazyLoadStart() {\n\t\tthis.onLazyLoadStart();\n\n\t\tthis.lazyLoading = this.rscs.slice(this.counter.total);\n\n\t\tthis.lazyLoading.forEach((rsc) => this.load(rsc));\n\t}\n\n\tlazyLoadEnd() {\n\t\tconst lazyLoadedSuccessfully = this.lazyLoading.filter((rsc) => rsc.resource.isLoaded());\n\n\t\tthis.onLazyLoadEnd(lazyLoadedSuccessfully);\n\n\t\tthis.lazyLoading.length = 0;\n\t}\n\n\tload(rsc: ResourceWithOptions) {\n\t\trsc.resource\n\t\t\t.load()\n\t\t\t.then(() => {\n\t\t\t\tthis.loadSuccess(rsc);\n\t\t\t})\n\t\t\t.catch((error) => {\n\t\t\t\tthis.loadError(error);\n\t\t\t})\n\t\t\t.finally(() => {\n\t\t\t\tthis.counter.total++;\n\n\t\t\t\tif (this.isCancelled) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (this.preLoading.length !== 0) {\n\t\t\t\t\tthis.updateProgress();\n\t\t\t\t}\n\n\t\t\t\tif (this.counter.total === this.toPreload) {\n\t\t\t\t\tthis.preloadEnd();\n\t\t\t\t} else if (this.counter.total === this.rscs.length) {\n\t\t\t\t\tthis.lazyLoadEnd();\n\t\t\t\t}\n\t\t\t});\n\t}\n\n\tloadSuccess(rsc: ResourceWithOptions) {\n\t\tthis.counter.success++;\n\n\t\tif (this.isCancelled) {\n\t\t\treturn;\n\t\t}\n\n\t\trsc.resource.displaySize.update(this.displaySize.toValue());\n\t}\n\n\tloadError(error: string) {\n\t\tthis.counter.error++;\n\n\t\tif (this.isCancelled) {\n\t\t\treturn;\n\t\t}\n\n\t\tconsole.error(error);\n\t}\n\n\tupdateProgress() {\n\t\tthis.progress.value = Math.ceil((this.counter.success * 100) / this.toPreload);\n\t}\n\n\thasFinished() {\n\t\treturn this.counter.total === this.rscs.length;\n\t}\n\n\tcancel() {\n\t\tthis.isCancelled = true;\n\t\tthis.reject('Resources loading cancelled', this.rscs);\n\t}\n}\n"
  },
  {
    "path": "src/shared/ResourceLoader/__mocks__/ResourceLoader.ts",
    "content": "import { Size } from '../../';\nimport type { ResourceWithOptions } from '../../../resources';\n\nexport default class ResourceLoader {\n\trscs: ResourceWithOptions[] = [];\n\tcounter = {\n\t\tsuccess: 0,\n\t\terror: 0,\n\t\ttotal: 0,\n\t};\n\ttoPreload: number;\n\tpreLoading: ResourceWithOptions[] = [];\n\tlazyLoading: ResourceWithOptions[] = [];\n\tdisplaySize: Size;\n\tonPreloadStart: () => void;\n\tonPreloadEnd: (loaded: ResourceWithOptions[]) => void;\n\tonLazyLoadStart: () => void;\n\tonLazyLoadEnd: (loaded: ResourceWithOptions[]) => void;\n\treject: (message: string, rscs: ResourceWithOptions[]) => void;\n\n\tconstructor(\n\t\trscs: ResourceWithOptions[],\n\t\ttoPreload: number,\n\t\tdisplaySize: Size,\n\t\tonPreloadStart: () => void,\n\t\tonPreloadEnd: (loaded: ResourceWithOptions[]) => void,\n\t\tonLazyLoadStart: () => void,\n\t\tonLazyLoadEnd: (loaded: ResourceWithOptions[]) => void,\n\t\treject: (message: string, rscs: ResourceWithOptions[]) => void,\n\t) {\n\t\tthis.rscs = rscs;\n\t\tthis.toPreload = toPreload > rscs.length ? rscs.length : toPreload;\n\t\tthis.displaySize = displaySize;\n\t\tthis.onPreloadStart = onPreloadStart;\n\t\tthis.onPreloadEnd = onPreloadEnd;\n\t\tthis.onLazyLoadStart = onLazyLoadStart;\n\t\tthis.onLazyLoadEnd = onLazyLoadEnd;\n\t\tthis.reject = reject;\n\n\t\tthis.preloadStart();\n\t}\n\n\tpreloadStart() {\n\t\tthis.onPreloadStart();\n\n\t\tconst { counter } = this;\n\n\t\tconst toLoad = this.rscs.slice(\n\t\t\tcounter.total,\n\t\t\tcounter.total + this.toPreload - counter.success,\n\t\t);\n\n\t\tthis.preLoading = this.preLoading.concat(toLoad);\n\n\t\ttoLoad.forEach((rsc) => this.load(rsc));\n\t}\n\n\tpreloadEnd() {\n\t\tconst { counter, toPreload } = this;\n\n\t\tif (counter.success < toPreload && counter.total < toPreload) {\n\t\t\tthis.preloadStart();\n\t\t\treturn;\n\t\t}\n\n\t\tconst preloadedSuccessfully = this.preLoading.filter((rsc) => rsc.resource.isLoaded());\n\n\t\tthis.onPreloadEnd(preloadedSuccessfully);\n\n\t\tthis.preLoading.length = 0;\n\n\t\tif (counter.total < this.rscs.length) {\n\t\t\tthis.lazyLoadStart();\n\t\t}\n\t}\n\n\tlazyLoadStart() {\n\t\tthis.onLazyLoadStart();\n\n\t\tthis.lazyLoading = this.rscs.slice(this.counter.total);\n\n\t\tthis.lazyLoading.forEach((rsc) => this.load(rsc));\n\t}\n\n\tlazyLoadEnd() {\n\t\tconst lazyLoadedSuccessfully = this.lazyLoading.filter((rsc) => rsc.resource.isLoaded());\n\n\t\tthis.onLazyLoadEnd(lazyLoadedSuccessfully);\n\n\t\tthis.lazyLoading.length = 0;\n\t}\n\n\tload(rsc: ResourceWithOptions) {\n\t\trsc.resource\n\t\t\t.load()\n\t\t\t.then(() => {\n\t\t\t\tthis.loadSuccess();\n\t\t\t})\n\t\t\t.finally(() => {\n\t\t\t\tthis.counter.total++;\n\n\t\t\t\tif (this.counter.total === this.toPreload) {\n\t\t\t\t\tthis.preloadEnd();\n\t\t\t\t} else if (this.counter.total === this.rscs.length) {\n\t\t\t\t\tthis.lazyLoadEnd();\n\t\t\t\t}\n\t\t\t});\n\t}\n\n\tloadSuccess() {\n\t\tthis.counter.success++;\n\t}\n\n\thasFinished() {\n\t\treturn this.counter.total === this.rscs.length;\n\t}\n}\n"
  },
  {
    "path": "src/shared/ResourceLoader/__test__/ResourceLoaderFactory.ts",
    "content": "import { vi } from 'vitest';\nimport ResourceFactory from '../../../resources/__test__/ResourceFactory';\nimport ResourceLoader from '../ResourceLoader';\nimport Size from '../../Size/Size';\nimport type { ResourceWithOptions } from '../../../resources/types';\n\nexport default class ResourceLoaderFactory {\n\tstatic create(\n\t\tnumResources: number,\n\t\tnumToPreload: number,\n\t\tpreloadStartMock?: () => void,\n\t\tpreloadEndMock?: () => void,\n\t\tlazyLoadStartMock?: () => void,\n\t\tlazyLoadEndMock?: () => void,\n\t) {\n\t\tconst displaySize = new Size({\n\t\t\twidth: 640,\n\t\t\theight: 360,\n\t\t});\n\n\t\tconst onPreloadStart = vi.fn();\n\n\t\tif (preloadStartMock) {\n\t\t\tonPreloadStart.mockImplementation(preloadStartMock);\n\t\t}\n\n\t\tconst onPreloadEnd = vi.fn();\n\n\t\tif (preloadEndMock) {\n\t\t\tonPreloadEnd.mockImplementation(preloadEndMock);\n\t\t}\n\n\t\tconst onLazyLoadStart = vi.fn();\n\n\t\tif (lazyLoadStartMock) {\n\t\t\tonLazyLoadStart.mockImplementation(lazyLoadStartMock);\n\t\t}\n\n\t\tconst onLazyLoadEnd = vi.fn();\n\n\t\tif (lazyLoadEndMock) {\n\t\t\tonLazyLoadEnd.mockImplementation(lazyLoadEndMock);\n\t\t}\n\n\t\tconst reject = vi.fn();\n\n\t\tconst resources = ResourceFactory.create(numResources).map((resource) => {\n\t\t\treturn {\n\t\t\t\tresource: resource,\n\t\t\t\toptions: {},\n\t\t\t} as ResourceWithOptions;\n\t\t});\n\n\t\treturn new ResourceLoader(\n\t\t\tresources,\n\t\t\tnumToPreload,\n\t\t\tdisplaySize,\n\t\t\tonPreloadStart,\n\t\t\tonPreloadEnd,\n\t\t\tonLazyLoadStart,\n\t\t\tonLazyLoadEnd,\n\t\t\treject,\n\t\t);\n\t}\n}\n"
  },
  {
    "path": "src/shared/Size/Size.test.ts",
    "content": "import Size from './Size';\n\ndescribe('shared: Size', () => {\n\tlet size: Size;\n\tlet params: object;\n\n\tit('initializes values to null without parameters', () => {\n\t\tsize = new Size();\n\n\t\texpect(size.width.value).toBeNull();\n\t\texpect(size.height.value).toBeNull();\n\t});\n\n\tit('sets param values', () => {\n\t\tsize = new Size({ width: 100 });\n\t\texpect(size.width.value).toBe(100);\n\n\t\tsize = new Size({ height: 100 });\n\t\texpect(size.height.value).toBe(100);\n\n\t\tsize = new Size({\n\t\t\twidth: 100,\n\t\t\theight: 200,\n\t\t});\n\t\texpect(size.width.value).toBe(100);\n\t\texpect(size.height.value).toBe(200);\n\t});\n\n\tit('reset values', () => {\n\t\tsize = new Size({\n\t\t\twidth: 100,\n\t\t\theight: 200,\n\t\t});\n\n\t\tsize.reset();\n\n\t\texpect(size.width.value).toBeNull();\n\t\texpect(size.height.value).toBeNull();\n\t});\n\n\tit('is invalid if width or height is null', () => {\n\t\tsize = new Size({ width: 100 });\n\t\texpect(size.isValid()).toBeFalsy();\n\n\t\tsize = new Size({ height: 100 });\n\t\texpect(size.isValid()).toBeFalsy();\n\t});\n\n\tit('is valid when width and height have values', () => {\n\t\tsize = new Size({\n\t\t\twidth: 100,\n\t\t\theight: 200,\n\t\t});\n\n\t\texpect(size.isValid()).toBeTruthy();\n\t});\n\n\tit('updates the values', () => {\n\t\tsize = new Size({\n\t\t\twidth: 100,\n\t\t\theight: 200,\n\t\t});\n\n\t\tsize.update({\n\t\t\twidth: 50,\n\t\t});\n\n\t\texpect(size.width.value).toBe(50);\n\t\texpect(size.height.value).toBeNull();\n\n\t\tsize.update({\n\t\t\theight: 100,\n\t\t});\n\n\t\texpect(size.width.value).toBeNull();\n\t\texpect(size.height.value).toBe(100);\n\n\t\tsize.update({\n\t\t\twidth: 200,\n\t\t\theight: 400,\n\t\t});\n\n\t\texpect(size.width.value).toBe(200);\n\t\texpect(size.height.value).toBe(400);\n\t});\n\n\tit('throws an exception trying to calc aspect ratio when size is invalid', () => {\n\t\tsize = new Size({ width: 100 });\n\n\t\texpect(() => size.getAspectRatio()).toThrow(\n\t\t\t'Could not get aspect ratio due to invalid size'\n\t\t);\n\t});\n\n\tit('gets the aspect ration when size is valid', () => {\n\t\tsize = new Size({\n\t\t\twidth: 100,\n\t\t\theight: 200,\n\t\t});\n\n\t\texpect(size.getAspectRatio()).toBeTypeOf('number');\n\t});\n\n\tit('clones the size', () => {\n\t\tparams = {\n\t\t\twidth: 100,\n\t\t\theight: 200,\n\t\t};\n\n\t\tsize = new Size({\n\t\t\twidth: 100,\n\t\t\theight: 200,\n\t\t});\n\n\t\texpect(size.clone().toValue()).toStrictEqual(params);\n\t});\n\n\tit('returns false when width does not match other size', () => {\n\t\tsize = new Size({\n\t\t\twidth: 100,\n\t\t\theight: 200,\n\t\t});\n\n\t\texpect(\n\t\t\tsize.equals(\n\t\t\t\tnew Size({\n\t\t\t\t\twidth: 50,\n\t\t\t\t\theight: 200,\n\t\t\t\t})\n\t\t\t)\n\t\t).toBeFalsy();\n\t});\n\n\tit('returns false when height does not match other size', () => {\n\t\tsize = new Size({\n\t\t\twidth: 100,\n\t\t\theight: 200,\n\t\t});\n\n\t\texpect(\n\t\t\tsize.equals(\n\t\t\t\tnew Size({\n\t\t\t\t\twidth: 100,\n\t\t\t\t\theight: 50,\n\t\t\t\t})\n\t\t\t)\n\t\t).toBeFalsy();\n\t});\n\n\tit('returns true when size equals another size', () => {\n\t\tparams = {\n\t\t\twidth: 100,\n\t\t\theight: 200,\n\t\t};\n\n\t\tsize = new Size(params);\n\n\t\texpect(size.equals(new Size(params))).toBeTruthy();\n\t});\n\n\tit('returns the values as plain object', () => {\n\t\tparams = {\n\t\t\twidth: 100,\n\t\t\theight: 200,\n\t\t};\n\n\t\tsize = new Size(params);\n\t\texpect(size.toValue()).toStrictEqual(params);\n\n\t\tsize = new Size();\n\t\texpect(size.toValue()).toStrictEqual({});\n\t});\n\n\tit('throws exception when trying to get the values with px suffix', () => {\n\t\tsize = new Size();\n\t\texpect(() => size.toPx()).toThrow('Invalid size in pixels');\n\t});\n\n\tit('returns the values with px suffix', () => {\n\t\tparams = {\n\t\t\twidth: 100,\n\t\t\theight: 200,\n\t\t};\n\n\t\tsize = new Size(params);\n\n\t\texpect(size.toPx()).toStrictEqual({\n\t\t\twidth: params['width' as keyof object] + 'px',\n\t\t\theight: params['height' as keyof object] + 'px',\n\t\t});\n\t});\n});\n"
  },
  {
    "path": "src/shared/Size/Size.ts",
    "content": "import { ref, type Ref } from 'vue';\nimport { Maths } from '../';\n\nexport default class Size {\n\twidth: Ref<null | number> = ref(null);\n\theight: Ref<null | number> = ref(null);\n\n\tconstructor(\n\t\t{\n\t\t\twidth = null,\n\t\t\theight = null,\n\t\t}: {\n\t\t\twidth?: null | number;\n\t\t\theight?: null | number;\n\t\t} = { width: null, height: null },\n\t) {\n\t\tthis.update({ width, height });\n\t}\n\n\treset() {\n\t\tthis.width.value = null;\n\t\tthis.height.value = null;\n\t}\n\n\tisValid() {\n\t\treturn ![this.width.value, this.height.value].includes(null);\n\t}\n\n\tupdate({ width, height }: { width?: null | number; height?: null | number }) {\n\t\tthis.width.value = width ?? null;\n\t\tthis.height.value = height ?? null;\n\t}\n\n\tgetAspectRatio() {\n\t\tif (!this.isValid()) {\n\t\t\tthrow new RangeError('Could not get aspect ratio due to invalid size');\n\t\t}\n\n\t\treturn Maths.aspectRatio(this.toValue() as { width: number; height: number });\n\t}\n\n\tclone() {\n\t\treturn new Size(this.toValue());\n\t}\n\n\tequals(otherSize: Size) {\n\t\tif (this.width.value !== otherSize.width.value) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif (this.height.value !== otherSize.height.value) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\ttoValue() {\n\t\tconst rawSize: {\n\t\t\twidth?: number;\n\t\t\theight?: number;\n\t\t} = {};\n\n\t\tif (this.width.value !== null) {\n\t\t\trawSize.width = this.width.value;\n\t\t}\n\n\t\tif (this.height.value !== null) {\n\t\t\trawSize.height = this.height.value;\n\t\t}\n\n\t\treturn rawSize;\n\t}\n\n\ttoPx() {\n\t\tif (!this.isValid()) {\n\t\t\tthrow new RangeError('Invalid size in pixels');\n\t\t}\n\n\t\treturn {\n\t\t\twidth: this.width.value!.toString() + 'px',\n\t\t\theight: this.height.value!.toString() + 'px',\n\t\t};\n\t}\n}\n"
  },
  {
    "path": "src/shared/index.ts",
    "content": "export * as Maths from './Maths/Maths';\nexport { default as Position } from './Position/Position';\nexport { default as ResizeCalculator } from './ResizeCalculator/ResizeCalculator';\nexport { default as ResourceLoader } from './ResourceLoader/ResourceLoader';\nexport { default as Size } from './Size/Size';\n"
  },
  {
    "path": "src/transitions/Blinds2D/Blinds2D.test.ts",
    "content": "import Blinds2D from './Blinds2D.vue';\nimport AnimationWrapper from '../__test__/AnimationWrapper';\nimport { Directions } from '../../controllers/Player';\n\nvi.mock('../../components/FluxGrid/FluxGrid.vue');\n\ndescribe('transition: Blinds2D', () => {\n\tit('exposes onPlay and totalDuration', () => {\n\t\tconst wrapper = AnimationWrapper(Blinds2D, {});\n\n\t\tconst { onPlay, totalDuration } = wrapper.vm;\n\n\t\texpect(typeof onPlay).toBe('function');\n\t\texpect(typeof totalDuration).toBe('number');\n\t});\n\n\tit('performs the transition with default options', () => {\n\t\tconst wrapper = AnimationWrapper(Blinds2D, {});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0.1',\n\t\t\ttransform: 'scaleX(0)',\n\t\t\ttransition: 'all 800ms linear 0ms',\n\t\t});\n\n\t\texpect($tiles[9].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0.1',\n\t\t\ttransform: 'scaleX(0)',\n\t\t\ttransition: 'all 800ms linear 900ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1800);\n\t});\n\n\tit('performs the transition with custom options prev', () => {\n\t\tconst wrapper = AnimationWrapper(Blinds2D, {\n\t\t\tdirection: Directions.prev,\n\t\t\tcols: 6,\n\t\t\ttileDuration: 400,\n\t\t\ttileDelay: 60,\n\t\t\teasing: 'ease-out',\n\t\t});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0.1',\n\t\t\ttransform: 'scaleX(0)',\n\t\t\ttransition: 'all 400ms ease-out 300ms',\n\t\t});\n\n\t\texpect($tiles[5].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0.1',\n\t\t\ttransform: 'scaleX(0)',\n\t\t\ttransition: 'all 400ms ease-out 0ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(760);\n\t});\n\n\tit('performs the transition with custom options next', () => {\n\t\tconst wrapper = AnimationWrapper(Blinds2D, {\n\t\t\tdirection: Directions.next,\n\t\t\tcols: 6,\n\t\t\ttileDuration: 700,\n\t\t\ttileDelay: 90,\n\t\t\teasing: 'ease-in',\n\t\t});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0.1',\n\t\t\ttransform: 'scaleX(0)',\n\t\t\ttransition: 'all 700ms ease-in 0ms',\n\t\t});\n\n\t\texpect($tiles[5].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0.1',\n\t\t\ttransform: 'scaleX(0)',\n\t\t\ttransition: 'all 700ms ease-in 450ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1240);\n\t});\n});\n"
  },
  {
    "path": "src/transitions/Blinds2D/Blinds2D.vue",
    "content": "<script setup lang=\"ts\">\n\timport { ref, reactive, type Ref } from 'vue';\n\timport { type FluxComponent, FluxGrid } from '../../components';\n\timport useTransition from '../useTransition';\n\timport type { TransitionBlinds2DProps, TransitionBlinds2DConf } from './types';\n\timport { Sides } from '../../components/FluxCube';\n\n\tconst props = defineProps<TransitionBlinds2DProps>();\n\n\tconst $grid: Ref<null | InstanceType<typeof FluxGrid>> = ref(null);\n\n\tconst conf: TransitionBlinds2DConf = reactive({\n\t\trows: 1,\n\t\tcols: 10,\n\t\ttileDuration: 800,\n\t\ttileDelay: 100,\n\t\teasing: 'linear',\n\t});\n\n\tuseTransition(conf, props.options);\n\n\tconst rscs = {\n\t\t[Sides.front]: props.from,\n\t};\n\n\tconst totalDuration = conf.tileDelay * conf.cols + conf.tileDuration;\n\n\tconst getDelay = {\n\t\tprev: (index: number) => (conf.cols - index - 1) * conf.tileDelay,\n\t\tnext: (index: number) => index * conf.tileDelay,\n\t};\n\n\tconst onPlay = () => {\n\t\t$grid.value!.transform((tile: FluxComponent, index: number) => {\n\t\t\tconst transition = `all ${conf.tileDuration}ms ${\n\t\t\t\tconf.easing\n\t\t\t} ${getDelay[conf.direction!](index)}ms`;\n\n\t\t\ttile.transform({\n\t\t\t\ttransition,\n\t\t\t\topacity: '0.1',\n\t\t\t\ttransform: 'scaleX(0)',\n\t\t\t});\n\t\t});\n\t};\n\n\tdefineExpose({\n\t\tonPlay,\n\t\ttotalDuration,\n\t});\n</script>\n\n<template>\n\t<FluxGrid ref=\"$grid\" :rows=\"conf.rows\" :cols=\"conf.cols\" :size=\"size\" :rscs=\"rscs\" />\n</template>\n"
  },
  {
    "path": "src/transitions/Blinds2D/types.ts",
    "content": "import type { TransitionConf, TransitionOptions, TransitionProps } from '../types';\n\nexport interface TransitionBlinds2DOptions extends TransitionOptions {\n\tcols?: number;\n\ttileDuration?: number;\n\ttileDelay?: number;\n}\n\nexport interface TransitionBlinds2DProps extends TransitionProps {\n\toptions?: TransitionBlinds2DOptions;\n}\n\nexport interface TransitionBlinds2DConf extends TransitionConf {\n\trows: number;\n\tcols: number;\n\ttileDuration: number;\n\ttileDelay: number;\n}\n"
  },
  {
    "path": "src/transitions/Blinds3D/Blinds3D.test.ts",
    "content": "import Blinds3D from './Blinds3D.vue';\nimport AnimationWrapper from '../__test__/AnimationWrapper';\nimport { Directions } from '../../controllers';\nimport { Turns } from '../../components/FluxCube';\n\nvi.mock('../../components/FluxGrid/FluxGrid.vue');\n\ndescribe('transition: Blinds3D', () => {\n\tit('exposes onPlay and totalDuration', () => {\n\t\tconst wrapper = AnimationWrapper(Blinds3D, {});\n\n\t\tconst { onPlay, totalDuration } = wrapper.vm;\n\n\t\texpect(typeof onPlay).toBe('function');\n\t\texpect(typeof totalDuration).toBe('number');\n\t});\n\n\tit('expects to set proper CSS styles before animation', () => {\n\t\tconst wrapper = AnimationWrapper(Blinds3D, {});\n\n\t\tconst maskStyle = wrapper.props('maskStyle');\n\t\texpect(maskStyle.overflow).toBe('visible');\n\n\t\tconst gridCss = wrapper\n\t\t\t.getComponent({\n\t\t\t\tref: '$grid',\n\t\t\t})\n\t\t\t.props('css');\n\t\texpect(gridCss.perspective).toBeDefined();\n\t});\n\n\tit('performs the transition with default options', () => {\n\t\tconst wrapper = AnimationWrapper(Blinds3D, {});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].setCss).toHaveBeenCalledWith({\n\t\t\ttransition: 'all 800ms ease-out 0ms',\n\t\t});\n\t\texpect($tiles[0].turn).toHaveBeenCalledWith(Turns.backr);\n\n\t\texpect($tiles[5].setCss).toHaveBeenCalledWith({\n\t\t\ttransition: 'all 800ms ease-out 750ms',\n\t\t});\n\t\texpect($tiles[5].turn).toHaveBeenCalledWith(Turns.backr);\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1700);\n\t});\n\n\tit('performs the transition with custom options prev', () => {\n\t\tconst wrapper = AnimationWrapper(Blinds3D, {\n\t\t\tdirection: Directions.prev,\n\t\t\tcols: 8,\n\t\t\ttileDuration: 400,\n\t\t\ttileDelay: 60,\n\t\t\teasing: 'ease-in',\n\t\t});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].setCss).toHaveBeenCalledWith({\n\t\t\ttransition: 'all 400ms ease-in 420ms',\n\t\t});\n\t\texpect($tiles[0].turn).toHaveBeenCalledWith(Turns.backl);\n\n\t\texpect($tiles[7].setCss).toHaveBeenCalledWith({\n\t\t\ttransition: 'all 400ms ease-in 0ms',\n\t\t});\n\t\texpect($tiles[7].turn).toHaveBeenCalledWith(Turns.backl);\n\n\t\texpect(wrapper.vm.totalDuration).toBe(880);\n\t});\n\n\tit('performs the transition with custom options next', () => {\n\t\tconst wrapper = AnimationWrapper(Blinds3D, {\n\t\t\tdirection: Directions.next,\n\t\t\tcols: 10,\n\t\t\ttileDuration: 400,\n\t\t\ttileDelay: 60,\n\t\t\teasing: 'linear',\n\t\t});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].setCss).toHaveBeenCalledWith({\n\t\t\ttransition: 'all 400ms linear 0ms',\n\t\t});\n\t\texpect($tiles[0].turn).toHaveBeenCalledWith(Turns.backr);\n\n\t\texpect($tiles[9].setCss).toHaveBeenCalledWith({\n\t\t\ttransition: 'all 400ms linear 540ms',\n\t\t});\n\t\texpect($tiles[9].turn).toHaveBeenCalledWith(Turns.backr);\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1000);\n\t});\n});\n"
  },
  {
    "path": "src/transitions/Blinds3D/Blinds3D.vue",
    "content": "<script setup lang=\"ts\">\n\timport { ref, reactive, type Ref, type CSSProperties } from 'vue';\n\timport useTransition from '../useTransition';\n\timport { FluxGrid, Turns, FluxCube } from '../../components';\n\timport type { TransitionBlinds3DProps, TransitionBlinds3DConf } from './types';\n\n\tconst props = defineProps<TransitionBlinds3DProps>();\n\n\tconst $grid: Ref<null | InstanceType<typeof FluxGrid>> = ref(null);\n\n\tconst conf: TransitionBlinds3DConf = reactive({\n\t\trows: 1,\n\t\tcols: 6,\n\t\ttileDuration: 800,\n\t\ttileDelay: 150,\n\t\teasing: 'ease-out',\n\t});\n\n\tuseTransition(conf, props.options);\n\n\tconst gridCss: CSSProperties = {\n\t\tperspective: '800px',\n\t};\n\n\tconst rscs = {\n\t\tfront: props.from,\n\t\tback: props.to,\n\t};\n\n\tconst totalDuration = conf.tileDelay * conf.cols + conf.tileDuration;\n\n\t// eslint-disable-next-line vue/no-mutating-props\n\tprops.maskStyle.overflow = 'visible';\n\n\tconst getDelay = {\n\t\tprev: (index: number) => (conf.cols - index - 1) * conf.tileDelay,\n\t\tnext: (index: number) => index * conf.tileDelay,\n\t};\n\n\tconst turn = {\n\t\tprev: Turns.backl,\n\t\tnext: Turns.backr,\n\t}[conf.direction!];\n\n\tconst onPlay = () => {\n\t\tif (props.displayComponent) {\n\t\t\tprops.displayComponent.hide();\n\t\t}\n\n\t\ttype FluxCubeType = InstanceType<typeof FluxCube>;\n\n\t\t$grid.value!.transform<FluxCubeType>((tile: FluxCubeType, index: number) => {\n\t\t\tconst transition = `all ${conf.tileDuration}ms ${\n\t\t\t\tconf.easing\n\t\t\t} ${getDelay[conf.direction!](index)}ms`;\n\n\t\t\ttile.setCss({\n\t\t\t\ttransition,\n\t\t\t});\n\n\t\t\ttile.turn(turn);\n\t\t});\n\t};\n\n\tdefineExpose({\n\t\tonPlay,\n\t\ttotalDuration,\n\t});\n</script>\n\n<template>\n\t<FluxGrid\n\t\tref=\"$grid\"\n\t\t:rows=\"conf.rows\"\n\t\t:cols=\"conf.cols\"\n\t\t:size=\"size\"\n\t\t:rscs=\"rscs\"\n\t\t:css=\"gridCss\"\n\t/>\n</template>\n"
  },
  {
    "path": "src/transitions/Blinds3D/types.ts",
    "content": "import type { TransitionConf, TransitionOptions, TransitionProps } from '../types';\n\nexport interface TransitionBlinds3DOptions extends TransitionOptions {\n\tcols?: number;\n\ttileDuration?: number;\n\ttileDelay?: number;\n}\n\nexport interface TransitionBlinds3DProps extends TransitionProps {\n\toptions?: TransitionBlinds3DOptions;\n}\n\nexport interface TransitionBlinds3DConf extends TransitionConf {\n\trows: number;\n\tcols: number;\n\ttileDuration: number;\n\ttileDelay: number;\n}\n"
  },
  {
    "path": "src/transitions/Blocks1/Blocks1.test.ts",
    "content": "import Blocks1 from './Blocks1.vue';\nimport AnimationWrapper from '../__test__/AnimationWrapper';\nimport { Directions } from '../../controllers/Player';\n\nvi.mock('../../components/FluxGrid/FluxGrid.vue');\n\ndescribe('transition: Blocks1', () => {\n\tit('exposes onPlay and totalDuration', () => {\n\t\tconst wrapper = AnimationWrapper(Blocks1, {});\n\n\t\tconst { onPlay, totalDuration } = wrapper.vm;\n\n\t\texpect(typeof onPlay).toBe('function');\n\t\texpect(typeof totalDuration).toBe('number');\n\t});\n\n\tit('performs the transition with default options', () => {\n\t\tconst wrapper = AnimationWrapper(Blocks1, {});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'scale(0.3, 0.3)',\n\t\t\ttransition: expect.any(String),\n\t\t});\n\n\t\texpect($tiles[31].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'scale(0.3, 0.3)',\n\t\t\ttransition: expect.any(String),\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1300);\n\t});\n\n\tit('performs the transition with custom options prev', () => {\n\t\tconst wrapper = AnimationWrapper(Blocks1, {\n\t\t\tdirection: Directions.prev,\n\t\t\trows: 3,\n\t\t\tcols: 6,\n\t\t\ttileDuration: 400,\n\t\t\ttileDelay: 60,\n\t\t\teasing: 'ease-in-out',\n\t\t});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'scale(0.3, 0.3)',\n\t\t\ttransition: expect.any(String),\n\t\t});\n\n\t\texpect($tiles[17].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'scale(0.3, 0.3)',\n\t\t\ttransition: expect.any(String),\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(460);\n\t});\n\n\tit('performs the transition with custom options next', () => {\n\t\tconst wrapper = AnimationWrapper(Blocks1, {\n\t\t\tdirection: Directions.next,\n\t\t\trows: 4,\n\t\t\tcols: 6,\n\t\t\ttileDuration: 400,\n\t\t\ttileDelay: 60,\n\t\t\teasing: 'ease-in-out',\n\t\t});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'scale(0.3, 0.3)',\n\t\t\ttransition: expect.any(String),\n\t\t});\n\n\t\texpect($tiles[23].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'scale(0.3, 0.3)',\n\t\t\ttransition: expect.any(String),\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(460);\n\t});\n});\n"
  },
  {
    "path": "src/transitions/Blocks1/Blocks1.vue",
    "content": "<script setup lang=\"ts\">\n\timport { ref, reactive, type Ref } from 'vue';\n\timport useTransition from '../useTransition';\n\timport { type FluxComponent, FluxGrid } from '../../components';\n\timport type { TransitionBlocks1Props, TransitionBlocks1Conf } from './types';\n\n\tconst props = defineProps<TransitionBlocks1Props>();\n\n\tconst $grid: Ref<null | InstanceType<typeof FluxGrid>> = ref(null);\n\n\tconst conf: TransitionBlocks1Conf = reactive({\n\t\trows: 8,\n\t\tcols: 8,\n\t\ttileDuration: 300,\n\t\ttileDelay: 1000,\n\t\teasing: 'linear',\n\t});\n\n\tuseTransition(conf, props.options);\n\n\tif (!props.options?.rows) {\n\t\tconst divider = props.size.width.value! / conf.cols;\n\t\tconf.rows = Math.floor(props.size.height.value! / divider);\n\t}\n\n\tconst totalDuration = conf.tileDelay + conf.tileDuration;\n\n\tconst getDelay = () => Math.floor(Math.random() * conf.tileDelay);\n\n\tconst onPlay = () => {\n\t\t$grid.value!.transform((tile: FluxComponent) => {\n\t\t\tconst transition = `all ${conf.tileDuration}ms ${conf.easing} ${getDelay()}ms`;\n\n\t\t\ttile.transform({\n\t\t\t\ttransition,\n\t\t\t\topacity: '0',\n\t\t\t\ttransform: 'scale(0.3, 0.3)',\n\t\t\t});\n\t\t});\n\t};\n\n\tdefineExpose({\n\t\tonPlay,\n\t\ttotalDuration,\n\t});\n</script>\n\n<template>\n\t<FluxGrid ref=\"$grid\" :rows=\"conf.rows\" :cols=\"conf.cols\" :size=\"size\" :rsc=\"from\" />\n</template>\n"
  },
  {
    "path": "src/transitions/Blocks1/types.ts",
    "content": "import type { TransitionConf, TransitionOptions, TransitionProps } from '../types';\n\nexport interface TransitionBlocks1Options extends TransitionOptions {\n\trows?: number;\n\tcols?: number;\n\ttileDuration?: number;\n\ttileDelay?: number;\n}\n\nexport interface TransitionBlocks1Props extends TransitionProps {\n\toptions?: TransitionBlocks1Options;\n}\n\nexport interface TransitionBlocks1Conf extends TransitionConf {\n\trows: number;\n\tcols: number;\n\ttileDuration: number;\n\ttileDelay: number;\n}\n"
  },
  {
    "path": "src/transitions/Blocks2/Blocks2.test.ts",
    "content": "import Blocks2 from './Blocks2.vue';\nimport AnimationWrapper from '../__test__/AnimationWrapper';\nimport { Directions } from '../../controllers/Player';\n\nvi.mock('../../components/FluxGrid/FluxGrid.vue');\n\ndescribe('transition: Blocks2', () => {\n\tit('exposes onPlay and totalDuration', () => {\n\t\tconst wrapper = AnimationWrapper(Blocks2, {});\n\n\t\tconst { onPlay, totalDuration } = wrapper.vm;\n\n\t\texpect(typeof onPlay).toBe('function');\n\t\texpect(typeof totalDuration).toBe('number');\n\t});\n\n\tit('performs the transition with default options', () => {\n\t\tconst wrapper = AnimationWrapper(Blocks2, {});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'scale(0.3)',\n\t\t\ttransition: 'all 800ms ease 0ms',\n\t\t});\n\n\t\texpect($tiles[31].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'scale(0.3)',\n\t\t\ttransition: 'all 800ms ease 800ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(2080);\n\t});\n\n\tit('performs the transition with custom options prev', () => {\n\t\tconst wrapper = AnimationWrapper(Blocks2, {\n\t\t\tdirection: Directions.prev,\n\t\t\trows: 3,\n\t\t\tcols: 6,\n\t\t\ttileDuration: 400,\n\t\t\ttileDelay: 60,\n\t\t\teasing: 'ease-in-out',\n\t\t});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\topacity: '1',\n\t\t\ttransform: 'scale(1)',\n\t\t\ttransition: 'all 400ms ease-in-out 480ms',\n\t\t});\n\n\t\texpect($tiles[17].transform).toHaveBeenCalledWith({\n\t\t\topacity: '1',\n\t\t\ttransform: 'scale(1)',\n\t\t\ttransition: 'all 400ms ease-in-out 60ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(940);\n\t});\n\n\tit('performs the transition with custom options next', () => {\n\t\tconst wrapper = AnimationWrapper(Blocks2, {\n\t\t\tdirection: Directions.next,\n\t\t\trows: 4,\n\t\t\tcols: 6,\n\t\t\ttileDuration: 400,\n\t\t\ttileDelay: 60,\n\t\t\teasing: 'ease-in-out',\n\t\t});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'scale(0.3)',\n\t\t\ttransition: 'all 400ms ease-in-out 0ms',\n\t\t});\n\n\t\texpect($tiles[23].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'scale(0.3)',\n\t\t\ttransition: 'all 400ms ease-in-out 480ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1000);\n\t});\n});\n"
  },
  {
    "path": "src/transitions/Blocks2/Blocks2.vue",
    "content": "<script setup lang=\"ts\">\n\timport { ref, reactive, type Ref } from 'vue';\n\timport useTransition from '../useTransition';\n\timport { type FluxComponent, FluxGrid } from '../../components';\n\timport type { TransitionBlocks2Props, TransitionBlocks2Conf, BackgroundProps } from './types';\n\timport { Directions } from '../../controllers/Player';\n\n\tconst props = defineProps<TransitionBlocks2Props>();\n\n\tconst $grid: Ref<null | InstanceType<typeof FluxGrid>> = ref(null);\n\tconst $background: Ref<null | FluxComponent> = ref(null);\n\n\tconst conf: TransitionBlocks2Conf = reactive({\n\t\trows: 8,\n\t\tcols: 8,\n\t\ttileDuration: 800,\n\t\ttileDelay: 80,\n\t\teasing: 'ease',\n\t});\n\n\tuseTransition(conf, props.options);\n\n\tconst totalDuration = conf.tileDelay * (conf.rows + conf.cols) + conf.tileDuration;\n\n\tif (!props.options?.rows) {\n\t\tconst divider = props.size.width.value! / conf.cols;\n\t\tconf.rows = Math.floor(props.size.height.value! / divider);\n\t}\n\n\tconst background: BackgroundProps = {\n\t\trsc: null,\n\t\tcss: {\n\t\t\tposition: 'absolute',\n\t\t\ttop: 0,\n\t\t\tleft: 0,\n\t\t\tzIndex: 1,\n\t\t},\n\t};\n\n\tconst grid = JSON.parse(JSON.stringify(background));\n\tgrid.css.zIndex = 2;\n\n\tlet tileCss = {};\n\n\tconst setup = {\n\t\tprev: () => {\n\t\t\tgrid.rsc = props.to;\n\t\t\tbackground.rsc = props.from;\n\n\t\t\ttileCss = {\n\t\t\t\topacity: '0',\n\t\t\t\ttransform: 'scale(0.3)',\n\t\t\t};\n\t\t},\n\n\t\tnext: () => {\n\t\t\tgrid.rsc = props.from;\n\t\t},\n\t};\n\n\tsetup[conf.direction!]();\n\n\tconst getDelay = (index: number) => {\n\t\tconst row = $grid.value!.getRowNumber(index, conf.cols);\n\t\tconst col = $grid.value!.getColNumber(index, conf.cols);\n\t\tlet delay = col + row;\n\n\t\tif (conf.direction === Directions.prev) {\n\t\t\tdelay = conf.rows + conf.cols - delay - 1;\n\t\t}\n\n\t\treturn delay * conf.tileDelay;\n\t};\n\n\tconst play = {\n\t\tprev: () => {\n\t\t\t$grid.value!.transform((tile: FluxComponent, index: number) => {\n\t\t\t\ttile.transform({\n\t\t\t\t\ttransition: `all ${conf.tileDuration}ms ${conf.easing} ${getDelay(index)}ms`,\n\t\t\t\t\topacity: '1',\n\t\t\t\t\ttransform: 'scale(1)',\n\t\t\t\t});\n\t\t\t});\n\t\t},\n\n\t\tnext: () => {\n\t\t\t$grid.value!.transform((tile: FluxComponent, index: number) => {\n\t\t\t\ttile.transform({\n\t\t\t\t\ttransition: `all ${conf.tileDuration}ms ${conf.easing} ${getDelay(index)}ms`,\n\t\t\t\t\topacity: '0',\n\t\t\t\t\ttransform: 'scale(0.3)',\n\t\t\t\t});\n\t\t\t});\n\t\t},\n\t};\n\n\tconst onPlay = () => {\n\t\tplay[conf.direction!]();\n\t};\n\n\tdefineExpose({\n\t\tonPlay,\n\t\ttotalDuration,\n\t});\n</script>\n\n<template>\n\t<div>\n\t\t<FluxGrid\n\t\t\tref=\"$grid\"\n\t\t\t:rows=\"conf.rows\"\n\t\t\t:cols=\"conf.cols\"\n\t\t\t:size=\"size\"\n\t\t\t:tile-css=\"tileCss\"\n\t\t\tv-bind=\"grid\"\n\t\t/>\n\n\t\t<component\n\t\t\t:is=\"background.rsc.transition.component\"\n\t\t\tv-if=\"background.rsc !== null\"\n\t\t\tref=\"$background\"\n\t\t\t:size=\"size\"\n\t\t\tv-bind=\"background\"\n\t\t/>\n\t</div>\n</template>\n"
  },
  {
    "path": "src/transitions/Blocks2/types.ts",
    "content": "import type { CSSProperties } from 'vue';\nimport { Resource } from '../../resources';\nimport type { TransitionConf, TransitionOptions, TransitionProps } from '../types';\n\nexport interface TransitionBlocks2Options extends TransitionOptions {\n\trows?: number;\n\tcols?: number;\n\ttileDuration?: number;\n\ttileDelay?: number;\n}\n\nexport interface TransitionBlocks2Props extends TransitionProps {\n\toptions?: TransitionBlocks2Options;\n}\n\nexport interface TransitionBlocks2Conf extends TransitionConf {\n\trows: number;\n\tcols: number;\n\ttileDuration: number;\n\ttileDelay: number;\n}\n\nexport interface BackgroundProps {\n\trsc: null | Resource;\n\tcss: CSSProperties;\n}\n"
  },
  {
    "path": "src/transitions/Book/Book.test.ts",
    "content": "import Book from './Book.vue';\nimport AnimationWrapper from '../__test__/AnimationWrapper';\nimport { Directions } from '../../controllers/Player';\n\nvi.mock('../../components/FluxImage/FluxImage.vue');\nvi.mock('../../components/FluxCube/FluxCube.vue');\n\ndescribe('transition: Book', () => {\n\tit('exposes onPlay and totalDuration', () => {\n\t\tconst wrapper = AnimationWrapper(Book, {});\n\n\t\tconst { onPlay, totalDuration } = wrapper.vm;\n\n\t\texpect(typeof onPlay).toBe('function');\n\t\texpect(typeof totalDuration).toBe('number');\n\t});\n\n\tit('performs the transition with default options', () => {\n\t\tconst wrapper = AnimationWrapper(Book, {});\n\n\t\tconst size = wrapper.props('size').toValue();\n\n\t\tconst $from = wrapper.getComponent({\n\t\t\tref: '$from',\n\t\t});\n\n\t\tconst viewSize = $from.props('viewSize').toValue();\n\n\t\texpect(viewSize).toStrictEqual({\n\t\t\twidth: Math.ceil(size.width / 2),\n\t\t\theight: size.height,\n\t\t});\n\n\t\texpect($from.props('offset').toValue()).toStrictEqual({\n\t\t\ttop: 0,\n\t\t\tleft: 0,\n\t\t});\n\n\t\texpect($from.props('css')).toStrictEqual({\n\t\t\ttop: 0,\n\t\t\tleft: 0,\n\t\t\tposition: 'absolute',\n\t\t});\n\n\t\tconst $cube = wrapper.getComponent({\n\t\t\tref: '$cube',\n\t\t});\n\n\t\tconst cubeOffsets = $cube.props('offsets');\n\n\t\texpect(cubeOffsets.front.toValue()).toStrictEqual({\n\t\t\ttop: 0,\n\t\t\tleft: 320,\n\t\t});\n\n\t\texpect(cubeOffsets.back.toValue()).toStrictEqual({\n\t\t\ttop: 0,\n\t\t\tleft: 0,\n\t\t});\n\n\t\texpect($cube.props('origin')).toBe('left center');\n\n\t\texpect($cube.props('css')).toStrictEqual({\n\t\t\ttop: 0,\n\t\t\tleft: '320px',\n\t\t\tposition: 'absolute',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($cube.vm.transform).toHaveBeenCalledWith({\n\t\t\ttransform: 'rotateY(-180deg)',\n\t\t\ttransition: 'transform 1200ms ease-out',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1200);\n\t});\n\n\tit('performs the transition with custom options prev', () => {\n\t\tconst wrapper = AnimationWrapper(Book, {\n\t\t\tdirection: Directions.prev,\n\t\t\ttotalDuration: 900,\n\t\t\teasing: 'linear',\n\t\t});\n\n\t\tconst $from = wrapper.getComponent({\n\t\t\tref: '$from',\n\t\t});\n\n\t\texpect($from.props('offset').toValue()).toStrictEqual({\n\t\t\ttop: 0,\n\t\t\tleft: 320,\n\t\t});\n\n\t\texpect($from.props('css')).toStrictEqual({\n\t\t\ttop: 0,\n\t\t\tleft: '320px',\n\t\t\tposition: 'absolute',\n\t\t});\n\n\t\tconst $cube = wrapper.getComponent({\n\t\t\tref: '$cube',\n\t\t});\n\n\t\tconst cubeOffsets = $cube.props('offsets');\n\n\t\texpect(cubeOffsets.front.toValue()).toStrictEqual({\n\t\t\ttop: 0,\n\t\t\tleft: 0,\n\t\t});\n\n\t\texpect(cubeOffsets.back.toValue()).toStrictEqual({\n\t\t\ttop: 0,\n\t\t\tleft: 320,\n\t\t});\n\n\t\texpect($cube.props('origin')).toBe('right center');\n\n\t\texpect($cube.props('css')).toStrictEqual({\n\t\t\ttop: 0,\n\t\t\tleft: 0,\n\t\t\tposition: 'absolute',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($cube.vm.transform).toHaveBeenCalledWith({\n\t\t\ttransform: 'rotateY(180deg)',\n\t\t\ttransition: 'transform 900ms linear',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(900);\n\t});\n\n\tit('performs the transition with custom options next', () => {\n\t\tconst wrapper = AnimationWrapper(Book, {\n\t\t\tdirection: Directions.next,\n\t\t\ttotalDuration: 1000,\n\t\t\teasing: 'ease-out',\n\t\t});\n\n\t\tconst $from = wrapper.getComponent({\n\t\t\tref: '$from',\n\t\t});\n\n\t\texpect($from.props('offset').toValue()).toStrictEqual({\n\t\t\ttop: 0,\n\t\t\tleft: 0,\n\t\t});\n\n\t\texpect($from.props('css')).toStrictEqual({\n\t\t\ttop: 0,\n\t\t\tleft: 0,\n\t\t\tposition: 'absolute',\n\t\t});\n\n\t\tconst $cube = wrapper.getComponent({\n\t\t\tref: '$cube',\n\t\t});\n\n\t\tconst cubeOffsets = $cube.props('offsets');\n\n\t\texpect(cubeOffsets.front.toValue()).toStrictEqual({\n\t\t\ttop: 0,\n\t\t\tleft: 320,\n\t\t});\n\n\t\texpect(cubeOffsets.back.toValue()).toStrictEqual({\n\t\t\ttop: 0,\n\t\t\tleft: 0,\n\t\t});\n\n\t\texpect($cube.props('origin')).toBe('left center');\n\n\t\texpect($cube.props('css')).toStrictEqual({\n\t\t\ttop: 0,\n\t\t\tleft: '320px',\n\t\t\tposition: 'absolute',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($cube.vm.transform).toHaveBeenCalledWith({\n\t\t\ttransform: 'rotateY(-180deg)',\n\t\t\ttransition: 'transform 1000ms ease-out',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1000);\n\t});\n});\n"
  },
  {
    "path": "src/transitions/Book/Book.vue",
    "content": "<script setup lang=\"ts\">\n\timport { ref, reactive, type Ref, type CSSProperties } from 'vue';\n\timport useTransition from '../useTransition';\n\timport { type FluxComponent, FluxCube } from '../../components';\n\timport type { TransitionBookProps, TransitionBookConf } from './types';\n\timport { Position, Size } from '../../shared';\n\timport type { SidesOffsets } from '../../components/FluxCube/types';\n\timport { Directions } from '../../controllers/Player';\n\n\tconst props = defineProps<TransitionBookProps>();\n\n\tconst $from: Ref<null | FluxComponent> = ref(null);\n\tconst $cube: Ref<null | InstanceType<typeof FluxCube>> = ref(null);\n\n\tconst conf: TransitionBookConf = reactive({\n\t\ttotalDuration: 1200,\n\t\teasing: 'ease-out',\n\t});\n\n\tuseTransition(conf, props.options);\n\n\tconst viewSize: Size = new Size({\n\t\twidth: Math.ceil(props.size.width.value! / 2),\n\t\theight: props.size.height.value,\n\t});\n\n\tconst wrapperStyle: CSSProperties = {\n\t\tperspective: '1600px',\n\t\twidth: '100%',\n\t\theight: '100%',\n\t};\n\n\tconst fromOffset = new Position({\n\t\ttop: 0,\n\t\tleft: 0,\n\t});\n\n\tconst fromCss = {\n\t\tposition: 'absolute',\n\t\ttop: 0,\n\t\tleft: 0,\n\t} as CSSProperties;\n\n\tconst cube = {\n\t\trscs: {\n\t\t\tfront: props.from,\n\t\t\tback: props.to,\n\t\t},\n\t\toffsets: {\n\t\t\tfront: new Position({\n\t\t\t\ttop: 0,\n\t\t\t\tleft: 0,\n\t\t\t}),\n\t\t\tback: new Position({\n\t\t\t\ttop: 0,\n\t\t\t\tleft: 0,\n\t\t\t}),\n\t\t} as SidesOffsets,\n\t\torigin: '',\n\t\tcss: {\n\t\t\tposition: 'absolute',\n\t\t\ttop: 0,\n\t\t\tleft: 0,\n\t\t} as CSSProperties,\n\t};\n\n\tconst halfWidth: number = Math.ceil(props.size.width.value! / 2);\n\tconst halfWidthPx: string = halfWidth.toString() + 'px';\n\n\t// eslint-disable-next-line vue/no-mutating-props\n\tprops.maskStyle.overflow = 'visible';\n\n\tconst setup = {\n\t\tprev: () => {\n\t\t\tfromOffset.left.value = halfWidth;\n\t\t\tfromCss.left = halfWidthPx;\n\n\t\t\tcube.offsets.back!.left.value = halfWidth;\n\t\t\tcube.origin = 'right center';\n\t\t\tcube.css = {\n\t\t\t\t...cube.css,\n\t\t\t};\n\t\t},\n\n\t\tnext: () => {\n\t\t\tcube.offsets.front!.left.value = halfWidth;\n\t\t\tcube.origin = 'left center';\n\t\t\tcube.css = {\n\t\t\t\t...cube.css,\n\t\t\t\tleft: halfWidthPx,\n\t\t\t};\n\t\t},\n\t};\n\n\tsetup[conf.direction!]();\n\n\tconst deg = {\n\t\t[Directions.prev]: '180',\n\t\t[Directions.next]: '-180',\n\t}[conf.direction!];\n\n\tconst onPlay = () => {\n\t\t$cube.value!.transform({\n\t\t\ttransition: `transform ${conf.totalDuration}ms ${conf.easing}`,\n\t\t\ttransform: `rotateY(${deg}deg)`,\n\t\t});\n\t};\n\n\tdefineExpose({\n\t\tonPlay,\n\t\ttotalDuration: conf.totalDuration,\n\t});\n</script>\n\n<template>\n\t<div :style=\"wrapperStyle\">\n\t\t<component\n\t\t\t:is=\"from.transition.component\"\n\t\t\tref=\"$from\"\n\t\t\t:rsc=\"from\"\n\t\t\t:size=\"size\"\n\t\t\t:view-size=\"viewSize\"\n\t\t\t:offset=\"fromOffset\"\n\t\t\t:css=\"fromCss\"\n\t\t/>\n\n\t\t<FluxCube\n\t\t\tref=\"$cube\"\n\t\t\t:rscs=\"cube.rscs\"\n\t\t\t:size=\"size\"\n\t\t\t:view-size=\"viewSize\"\n\t\t\t:offsets=\"cube.offsets\"\n\t\t\t:origin=\"cube.origin\"\n\t\t\t:css=\"cube.css\"\n\t\t/>\n\t</div>\n</template>\n"
  },
  {
    "path": "src/transitions/Book/types.ts",
    "content": "import type { TransitionConf, TransitionOptions, TransitionProps } from '../types';\n\nexport interface TransitionBookOptions extends TransitionOptions {\n\ttotalDuration?: number;\n}\n\nexport interface TransitionBookProps extends TransitionProps {\n\toptions?: TransitionBookOptions;\n}\n\nexport interface TransitionBookConf extends TransitionConf {\n\ttotalDuration: number;\n}\n"
  },
  {
    "path": "src/transitions/Camera/Camera.test.ts",
    "content": "import Camera from './Camera.vue';\nimport AnimationWrapper from '../__test__/AnimationWrapper';\nimport { Directions } from '../../controllers/Player';\nimport { Maths, Size } from '../../shared';\n\nvi.mock('../../components/FluxWrapper/FluxWrapper.vue');\n\ndescribe('transition: Camera', () => {\n\tit('exposes onPlay and totalDuration', () => {\n\t\tconst wrapper = AnimationWrapper(Camera, {});\n\n\t\tconst { onPlay, totalDuration } = wrapper.vm;\n\n\t\texpect(typeof onPlay).toBe('function');\n\t\texpect(typeof totalDuration).toBe('number');\n\t});\n\n\tit('performs the transition with default options', () => {\n\t\tconst wrapper = AnimationWrapper(Camera, {});\n\n\t\tconst $wrapper = wrapper.getComponent({\n\t\t\tref: '$wrapper',\n\t\t});\n\n\t\tconst size = wrapper.props('size').toValue() as {\n\t\t\twidth: number;\n\t\t\theight: number;\n\t\t};\n\t\tconst diagSize = Maths.diag(size);\n\n\t\texpect($wrapper.props('size')).toStrictEqual(\n\t\t\tnew Size({ width: diagSize, height: diagSize })\n\t\t);\n\n\t\texpect($wrapper.props('css')).toStrictEqual({\n\t\t\tboxSizing: 'border-box',\n\t\t\tposition: 'absolute',\n\t\t\tdisplay: 'flex',\n\t\t\tjustifyContent: 'center',\n\t\t\toverflow: 'hidden',\n\t\t\tborderRadius: '50%',\n\t\t\tborder: '0 solid #111',\n\t\t\ttop: (size.height - diagSize) / 2 + 'px',\n\t\t\tleft: (size.width - diagSize) / 2 + 'px',\n\t\t});\n\n\t\tconst $from = wrapper.getComponent({\n\t\t\tref: '$from',\n\t\t});\n\n\t\texpect($from.props('css')).toStrictEqual({\n\t\t\talignSelf: 'center',\n\t\t\tflex: 'none',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($wrapper.vm.transform).toHaveBeenCalledWith({\n\t\t\tborderWidth: '367.5px',\n\t\t\ttransition: 'all 400ms cubic-bezier(0.385, 0, 0.795, 0.560) 0ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(900);\n\t});\n\n\tit('performs the transition with custom options prev', () => {\n\t\tconst wrapper = AnimationWrapper(Camera, {\n\t\t\tdirection: Directions.prev,\n\t\t\ttotalDuration: 800,\n\t\t\teasing: 'ease-out',\n\t\t});\n\n\t\tconst $wrapper = wrapper.getComponent({\n\t\t\tref: '$wrapper',\n\t\t});\n\n\t\tconst size = wrapper.props('size').toValue() as {\n\t\t\twidth: number;\n\t\t\theight: number;\n\t\t};\n\t\tconst diagSize = Maths.diag(size);\n\n\t\texpect($wrapper.props('size')).toStrictEqual(\n\t\t\tnew Size({ width: diagSize, height: diagSize })\n\t\t);\n\n\t\texpect($wrapper.props('css')).toStrictEqual({\n\t\t\tboxSizing: 'border-box',\n\t\t\tposition: 'absolute',\n\t\t\tdisplay: 'flex',\n\t\t\tjustifyContent: 'center',\n\t\t\toverflow: 'hidden',\n\t\t\tborderRadius: '50%',\n\t\t\tborder: '0 solid #111',\n\t\t\ttop: (size.height - diagSize) / 2 + 'px',\n\t\t\tleft: (size.width - diagSize) / 2 + 'px',\n\t\t});\n\n\t\tconst $from = wrapper.getComponent({\n\t\t\tref: '$from',\n\t\t});\n\n\t\texpect($from.props('css')).toStrictEqual({\n\t\t\talignSelf: 'center',\n\t\t\tflex: 'none',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($wrapper.vm.transform).toHaveBeenCalledWith({\n\t\t\tborderWidth: '367.5px',\n\t\t\ttransition: 'all 350ms ease-out 0ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(800);\n\t});\n\n\tit('performs the transition with custom options next', () => {\n\t\tconst wrapper = AnimationWrapper(Camera, {\n\t\t\tdirection: Directions.next,\n\t\t\ttotalDuration: 1000,\n\t\t\teasing: 'ease-in',\n\t\t});\n\n\t\tconst $wrapper = wrapper.getComponent({\n\t\t\tref: '$wrapper',\n\t\t});\n\n\t\tconst size = wrapper.props('size').toValue() as {\n\t\t\twidth: number;\n\t\t\theight: number;\n\t\t};\n\t\tconst diagSize = Maths.diag(size);\n\n\t\texpect($wrapper.props('size')).toStrictEqual(\n\t\t\tnew Size({ width: diagSize, height: diagSize })\n\t\t);\n\n\t\texpect($wrapper.props('css')).toStrictEqual({\n\t\t\tboxSizing: 'border-box',\n\t\t\tposition: 'absolute',\n\t\t\tdisplay: 'flex',\n\t\t\tjustifyContent: 'center',\n\t\t\toverflow: 'hidden',\n\t\t\tborderRadius: '50%',\n\t\t\tborder: '0 solid #111',\n\t\t\ttop: (size.height - diagSize) / 2 + 'px',\n\t\t\tleft: (size.width - diagSize) / 2 + 'px',\n\t\t});\n\n\t\tconst $from = wrapper.getComponent({\n\t\t\tref: '$from',\n\t\t});\n\n\t\texpect($from.props('css')).toStrictEqual({\n\t\t\talignSelf: 'center',\n\t\t\tflex: 'none',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($wrapper.vm.transform).toHaveBeenCalledWith({\n\t\t\tborderWidth: '367.5px',\n\t\t\ttransition: 'all 450ms ease-in 0ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1000);\n\t});\n});\n"
  },
  {
    "path": "src/transitions/Camera/Camera.vue",
    "content": "<script setup lang=\"ts\">\n\timport { ref, type Ref, reactive, type CSSProperties } from 'vue';\n\timport { Maths } from '../../shared';\n\timport useTransition from '../useTransition';\n\timport { type FluxComponent, FluxWrapper } from '../../components';\n\timport type { TransitionCameraProps, TransitionCameraConf } from './types';\n\timport { Size } from '../../shared';\n\n\tconst props = defineProps<TransitionCameraProps>();\n\n\tconst $wrapper: Ref<null | InstanceType<typeof FluxWrapper>> = ref(null);\n\tconst $from: Ref<null | FluxComponent> = ref(null);\n\n\tconst conf: TransitionCameraConf = reactive({\n\t\ttotalDuration: 900,\n\t\tbackgroundColor: '#111',\n\t\teasing: 'cubic-bezier(0.385, 0, 0.795, 0.560)',\n\t});\n\n\tuseTransition(conf, props.options);\n\n\tconst fromCss: CSSProperties = {\n\t\talignSelf: 'center',\n\t\tflex: 'none',\n\t};\n\n\tconst diagSize = Maths.diag(props.size.toValue() as { width: number; height: number });\n\n\tconst wrapperSize: Size = new Size({ width: diagSize, height: diagSize });\n\n\tconst WrapperCss: CSSProperties = {\n\t\tboxSizing: 'border-box',\n\t\tposition: 'absolute',\n\t\tdisplay: 'flex',\n\t\tjustifyContent: 'center',\n\t\toverflow: 'hidden',\n\t\tborderRadius: '50%',\n\t\tborder: '0 solid ' + conf.backgroundColor,\n\t\ttop: (props.size.height.value! - diagSize) / 2 + 'px',\n\t\tleft: (props.size.width.value! - diagSize) / 2 + 'px',\n\t};\n\n\tconst onPlay = () => {\n\t\t$wrapper.value!.transform({\n\t\t\ttransition: `all ${conf.totalDuration! / 2 - 50}ms ${conf.easing} 0ms`,\n\t\t\tborderWidth: diagSize / 2 + 'px',\n\t\t});\n\n\t\tsetTimeout(\n\t\t\t() => {\n\t\t\t\t$from.value!.hide();\n\n\t\t\t\t$wrapper.value!.transform({\n\t\t\t\t\ttransition: `all ${conf.totalDuration! / 2 - 50}ms ${conf.easing} 0ms`,\n\t\t\t\t\tborderWidth: 0,\n\t\t\t\t});\n\t\t\t},\n\t\t\tconf.totalDuration! / 2 + 50,\n\t\t);\n\t};\n\n\tdefineExpose({\n\t\tonPlay,\n\t\ttotalDuration: conf.totalDuration,\n\t});\n</script>\n\n<template>\n\t<FluxWrapper ref=\"$wrapper\" :size=\"wrapperSize\" :css=\"WrapperCss\">\n\t\t<component\n\t\t\t:is=\"from.transition.component\"\n\t\t\tref=\"$from\"\n\t\t\t:size=\"size\"\n\t\t\t:rsc=\"from\"\n\t\t\t:css=\"fromCss\"\n\t\t/>\n\t</FluxWrapper>\n</template>\n"
  },
  {
    "path": "src/transitions/Camera/types.ts",
    "content": "import type { CSSProperties } from 'vue';\nimport type { TransitionConf, TransitionOptions, TransitionProps } from '../types';\n\nexport interface TransitionCameraOptions extends TransitionOptions {\n\ttotalDuration?: number;\n\tbackgroundColor?: CSSProperties['color'];\n}\n\nexport interface TransitionCameraProps extends TransitionProps {\n\toptions?: TransitionCameraOptions;\n}\n\nexport interface TransitionCameraConf extends TransitionConf {\n\ttotalDuration: number;\n\tbackgroundColor: CSSProperties['color'];\n}\n"
  },
  {
    "path": "src/transitions/Concentric/Concentric.test.ts",
    "content": "import Concentric from './Concentric.vue';\nimport AnimationWrapper from '../__test__/AnimationWrapper';\nimport { Directions } from '../../controllers/Player';\n\nvi.mock('../../components/FluxVortex/FluxVortex.vue');\n\ndescribe('transition: Concentric', () => {\n\tit('exposes onPlay and totalDuration', () => {\n\t\tconst wrapper = AnimationWrapper(Concentric, {});\n\n\t\tconst { onPlay, totalDuration } = wrapper.vm;\n\n\t\texpect(typeof onPlay).toBe('function');\n\t\texpect(typeof totalDuration).toBe('number');\n\t});\n\n\tit('performs the transition with default options', () => {\n\t\tconst wrapper = AnimationWrapper(Concentric, {});\n\n\t\tconst $vortex = wrapper.getComponent({\n\t\t\tref: '$vortex',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($vortex.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $vortex.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'rotateZ(90deg)',\n\t\t\ttransition: 'all 800ms linear 0ms',\n\t\t});\n\n\t\texpect($tiles[6].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'rotateZ(90deg)',\n\t\t\ttransition: 'all 800ms linear 900ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1850);\n\t});\n\n\tit('performs the transition with custom options prev', () => {\n\t\tconst wrapper = AnimationWrapper(Concentric, {\n\t\t\tdirection: Directions.prev,\n\t\t\tcircles: 5,\n\t\t\ttileDuration: 400,\n\t\t\ttileDelay: 60,\n\t\t\teasing: 'ease-out',\n\t\t});\n\n\t\tconst $vortex = wrapper.getComponent({\n\t\t\tref: '$vortex',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($vortex.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $vortex.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'rotateZ(-90deg)',\n\t\t\ttransition: 'all 400ms ease-out 0ms',\n\t\t});\n\n\t\texpect($tiles[4].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'rotateZ(-90deg)',\n\t\t\ttransition: 'all 400ms ease-out 240ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(700);\n\t});\n\n\tit('performs the transition with custom options next', () => {\n\t\tconst wrapper = AnimationWrapper(Concentric, {\n\t\t\tdirection: Directions.next,\n\t\t\tcircles: 10,\n\t\t\ttileDuration: 400,\n\t\t\ttileDelay: 60,\n\t\t\teasing: 'ease-out',\n\t\t});\n\n\t\tconst $vortex = wrapper.getComponent({\n\t\t\tref: '$vortex',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($vortex.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $vortex.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'rotateZ(90deg)',\n\t\t\ttransition: 'all 400ms ease-out 0ms',\n\t\t});\n\n\t\texpect($tiles[9].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'rotateZ(90deg)',\n\t\t\ttransition: 'all 400ms ease-out 540ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1000);\n\t});\n});\n"
  },
  {
    "path": "src/transitions/Concentric/Concentric.vue",
    "content": "<script setup lang=\"ts\">\n\timport { ref, reactive, type Ref } from 'vue';\n\timport useTransition from '../useTransition';\n\timport { type FluxComponent, FluxVortex } from '../../components';\n\timport type { TransitionConcentricProps, TransitionConcentricConf } from './types';\n\timport { Directions } from '../../controllers/Player';\n\n\tconst props = defineProps<TransitionConcentricProps>();\n\n\tconst $vortex: Ref<null | InstanceType<typeof FluxVortex>> = ref(null);\n\n\tconst conf: TransitionConcentricConf = reactive({\n\t\tcircles: 7,\n\t\ttileDuration: 800,\n\t\ttileDelay: 150,\n\t\teasing: 'linear',\n\t});\n\n\tuseTransition(conf, props.options);\n\n\tconst totalDuration = conf.tileDelay * conf.circles + conf.tileDuration;\n\n\tconst getDelay = (index: number) => index * conf.tileDelay;\n\n\tconst deg = {\n\t\t[Directions.prev]: '-90',\n\t\t[Directions.next]: '90',\n\t}[conf.direction!];\n\n\tconst onPlay = () => {\n\t\t$vortex.value!.transform((tile: FluxComponent, index: number) => {\n\t\t\tconst transition = `all ${conf.tileDuration}ms ${conf.easing} ${getDelay(index)}ms`;\n\n\t\t\ttile.transform({\n\t\t\t\ttransition,\n\t\t\t\topacity: '0',\n\t\t\t\ttransform: `rotateZ(${deg}deg)`,\n\t\t\t});\n\t\t});\n\t};\n\n\tdefineExpose({\n\t\tonPlay,\n\t\ttotalDuration,\n\t});\n</script>\n\n<template>\n\t<FluxVortex ref=\"$vortex\" :size=\"size\" :circles=\"conf.circles\" :rsc=\"from\" />\n</template>\n"
  },
  {
    "path": "src/transitions/Concentric/types.ts",
    "content": "import type { TransitionConf, TransitionOptions, TransitionProps } from '../types';\n\nexport interface TransitionConcentricOptions extends TransitionOptions {\n\tcircles?: number;\n\ttileDuration?: number;\n\ttileDelay?: number;\n}\n\nexport interface TransitionConcentricProps extends TransitionProps {\n\toptions?: TransitionConcentricOptions;\n}\n\nexport interface TransitionConcentricConf extends TransitionConf {\n\tcircles: number;\n\ttileDuration: number;\n\ttileDelay: number;\n}\n"
  },
  {
    "path": "src/transitions/Cube/Cube.test.ts",
    "content": "import Cube from './Cube.vue';\nimport AnimationWrapper from '../__test__/AnimationWrapper';\nimport { Directions } from '../../controllers';\nimport { Turns } from '../../components/FluxCube';\n\nvi.mock('../../components/FluxCube/FluxCube.vue');\n\ndescribe('transition: Cube', () => {\n\tit('exposes onPlay and totalDuration', () => {\n\t\tconst wrapper = AnimationWrapper(Cube, {});\n\n\t\tconst { onPlay, totalDuration } = wrapper.vm;\n\n\t\texpect(typeof onPlay).toBe('function');\n\t\texpect(typeof totalDuration).toBe('number');\n\t});\n\n\tit('expects to set proper CSS styles before animation', () => {\n\t\tconst wrapper = AnimationWrapper(Cube, {});\n\n\t\tconst maskStyle = wrapper.props('maskStyle');\n\t\texpect(maskStyle.overflow).toBe('visible');\n\t\texpect(maskStyle.perspective).toBeDefined();\n\t});\n\n\tit('performs the transition with default options', () => {\n\t\tconst wrapper = AnimationWrapper(Cube, {});\n\n\t\tconst $cube = wrapper.getComponent({\n\t\t\tref: '$cube',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($cube.vm.turn).toHaveBeenCalledWith(Turns.left);\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1400);\n\t});\n\n\tit('performs the transition with custom options prev', () => {\n\t\tconst wrapper = AnimationWrapper(Cube, {\n\t\t\tdirection: Directions.prev,\n\t\t\ttotalDuration: 900,\n\t\t\teasing: 'ease-in',\n\t\t});\n\n\t\tconst $cube = wrapper.getComponent({\n\t\t\tref: '$cube',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($cube.vm.turn).toHaveBeenCalledWith(Turns.right);\n\n\t\texpect(wrapper.vm.totalDuration).toBe(900);\n\t});\n\n\tit('performs the transition with custom options next', () => {\n\t\tconst wrapper = AnimationWrapper(Cube, {\n\t\t\tdirection: Directions.next,\n\t\t\ttotalDuration: 300,\n\t\t\teasing: 'ease-in-out',\n\t\t});\n\n\t\tconst $cube = wrapper.getComponent({\n\t\t\tref: '$cube',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($cube.vm.turn).toHaveBeenCalledWith(Turns.left);\n\n\t\texpect(wrapper.vm.totalDuration).toBe(300);\n\t});\n});\n"
  },
  {
    "path": "src/transitions/Cube/Cube.vue",
    "content": "<script setup lang=\"ts\">\n\timport { ref, reactive, type Ref, type CSSProperties } from 'vue';\n\timport useTransition from '../useTransition';\n\timport { FluxCube } from '../../components';\n\timport type { TransitionCubeProps, TransitionCubeConf } from './types';\n\timport { Turns } from '../../components/FluxCube';\n\timport { Directions } from '../../controllers/Player';\n\n\tconst props = defineProps<TransitionCubeProps>();\n\n\tconst $cube: Ref<null | InstanceType<typeof FluxCube>> = ref(null);\n\n\tconst conf: TransitionCubeConf = reactive({\n\t\ttotalDuration: 1400,\n\t\teasing: 'ease-out',\n\t});\n\n\tuseTransition(conf, props.options);\n\n\t// eslint-disable-next-line vue/no-mutating-props\n\tObject.assign(props.maskStyle, {\n\t\tperspective: '1600px',\n\t\toverflow: 'visible',\n\t});\n\n\tconst rscs = {\n\t\tfront: props.from,\n\t\tleft: props.to,\n\t\tright: props.to,\n\t};\n\n\tconst cubeCss: CSSProperties = {\n\t\ttransition: `all ${conf.totalDuration}ms ${conf.easing}`,\n\t};\n\n\tconst turn = {\n\t\t[Directions.prev]: Turns.right,\n\t\t[Directions.next]: Turns.left,\n\t}[conf.direction!];\n\n\tconst onPlay = () => {\n\t\tif (props.displayComponent !== null) {\n\t\t\tprops.displayComponent.hide();\n\t\t}\n\n\t\t$cube.value!.turn(turn);\n\t};\n\n\tdefineExpose({\n\t\tonPlay,\n\t\ttotalDuration: conf.totalDuration,\n\t});\n</script>\n\n<template>\n\t<FluxCube ref=\"$cube\" :rscs=\"rscs\" :size=\"size\" :depth=\"size.width.value!\" :css=\"cubeCss\" />\n</template>\n"
  },
  {
    "path": "src/transitions/Cube/types.ts",
    "content": "import type { TransitionConf, TransitionOptions, TransitionProps } from '../types';\n\nexport interface TransitionCubeOptions extends TransitionOptions {\n\ttotalDuration?: number;\n}\n\nexport interface TransitionCubeProps extends TransitionProps {\n\toptions?: TransitionCubeOptions;\n}\n\nexport interface TransitionCubeConf extends TransitionConf {\n\ttotalDuration: number;\n}\n"
  },
  {
    "path": "src/transitions/Explode/Explode.test.ts",
    "content": "import Explode from './Explode.vue';\nimport AnimationWrapper from '../__test__/AnimationWrapper';\nimport { Directions } from '../../controllers/Player';\n\nvi.mock('../../components/FluxGrid/FluxGrid.vue');\n\ndescribe('transition: Explode', () => {\n\tit('exposes onPlay and totalDuration', () => {\n\t\tconst wrapper = AnimationWrapper(Explode, {});\n\n\t\tconst { onPlay, totalDuration } = wrapper.vm;\n\n\t\texpect(typeof onPlay).toBe('function');\n\t\texpect(typeof totalDuration).toBe('number');\n\t});\n\n\tit('performs the transition with default options', () => {\n\t\tconst wrapper = AnimationWrapper(Explode, {});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\tconst maskStyle = wrapper.props('maskStyle');\n\t\texpect(maskStyle.overflow).toBe('visible');\n\n\t\tconst gridCss = wrapper\n\t\t\t.getComponent({\n\t\t\t\tref: '$grid',\n\t\t\t})\n\t\t\t.props('css');\n\t\texpect(gridCss.overflow).toBe('visible');\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\tborderRadius: '100%',\n\t\t\topacity: '0',\n\t\t\ttransform: 'scale(2)',\n\t\t\ttransition: 'all 300ms linear 500ms',\n\t\t});\n\n\t\texpect($tiles[21].transform).toHaveBeenCalledWith({\n\t\t\tborderRadius: '100%',\n\t\t\topacity: '0',\n\t\t\ttransform: 'scale(2)',\n\t\t\ttransition: 'all 300ms linear 0ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1400);\n\t});\n\n\tit('performs the transition with custom options prev', () => {\n\t\tconst wrapper = AnimationWrapper(Explode, {\n\t\t\tdirection: Directions.prev,\n\t\t\trows: 3,\n\t\t\tcols: 6,\n\t\t\ttileDuration: 400,\n\t\t\ttileDelay: 60,\n\t\t\teasing: 'ease-in-out',\n\t\t});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\tborderRadius: '100%',\n\t\t\topacity: '0',\n\t\t\ttransform: 'scale(2)',\n\t\t\ttransition: 'all 400ms ease-in-out 150ms',\n\t\t});\n\n\t\texpect($tiles[8].transform).toHaveBeenCalledWith({\n\t\t\tborderRadius: '100%',\n\t\t\topacity: '0',\n\t\t\ttransform: 'scale(2)',\n\t\t\ttransition: 'all 400ms ease-in-out -30ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(540);\n\t});\n\n\tit('performs the transition with custom options next', () => {\n\t\tconst wrapper = AnimationWrapper(Explode, {\n\t\t\tdirection: Directions.next,\n\t\t\trows: 4,\n\t\t\tcols: 7,\n\t\t\ttileDuration: 200,\n\t\t\ttileDelay: 80,\n\t\t\teasing: 'ease-in',\n\t\t});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\tborderRadius: '100%',\n\t\t\topacity: '0',\n\t\t\ttransform: 'scale(2)',\n\t\t\ttransition: 'all 200ms ease-in 280ms',\n\t\t});\n\n\t\texpect($tiles[13].transform).toHaveBeenCalledWith({\n\t\t\tborderRadius: '100%',\n\t\t\topacity: '0',\n\t\t\ttransform: 'scale(2)',\n\t\t\ttransition: 'all 200ms ease-in 200ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(880);\n\t});\n});\n"
  },
  {
    "path": "src/transitions/Explode/Explode.vue",
    "content": "<script setup lang=\"ts\">\n\timport { ref, reactive, type Ref, type CSSProperties } from 'vue';\n\timport useTransition from '../useTransition';\n\timport { type FluxComponent, FluxGrid } from '../../components';\n\timport type { TransitionExplodeProps, TransitionExplodeConf } from './types';\n\n\tconst props = defineProps<TransitionExplodeProps>();\n\n\tconst $grid: Ref<null | InstanceType<typeof FluxGrid>> = ref(null);\n\n\tconst conf: TransitionExplodeConf = reactive({\n\t\trows: 9,\n\t\tcols: 9,\n\t\ttileDuration: 300,\n\t\ttileDelay: 100,\n\t\teasing: 'linear',\n\t});\n\n\tuseTransition(conf, props.options);\n\n\tconst cssGrid: CSSProperties = {\n\t\toverflow: 'visible',\n\t};\n\n\t// eslint-disable-next-line vue/no-mutating-props\n\tprops.maskStyle.overflow = 'visible';\n\n\tif (!props.options?.rows) {\n\t\tconst divider = props.size.width.value! / conf.cols;\n\t\tconf.rows = Math.floor(props.size.height.value! / divider);\n\t}\n\n\tconst totalDuration = (conf.cols / 2 + conf.rows / 2) * (conf.tileDelay * 2);\n\n\tconst getDelay = (index: number) => {\n\t\tconst row = $grid.value!.getRowNumber(index, conf.cols);\n\t\tconst col = $grid.value!.getColNumber(index, conf.cols);\n\n\t\tconst rowDelay = Math.abs(conf.rows / 2 - 0.5 - row);\n\t\tconst colDelay = Math.abs(conf.cols / 2 - 0.5 - col);\n\t\tconst delay = rowDelay + colDelay - 1;\n\n\t\treturn delay * conf.tileDelay;\n\t};\n\n\tconst onPlay = () => {\n\t\t$grid.value!.transform((tile: FluxComponent, index: number) => {\n\t\t\tconst transition = `all ${conf.tileDuration}ms ${conf.easing} ${getDelay(index)}ms`;\n\n\t\t\ttile.transform({\n\t\t\t\ttransition,\n\t\t\t\tborderRadius: '100%',\n\t\t\t\topacity: '0',\n\t\t\t\ttransform: 'scale(2)',\n\t\t\t});\n\t\t});\n\t};\n\n\tdefineExpose({\n\t\tonPlay,\n\t\ttotalDuration,\n\t});\n</script>\n\n<template>\n\t<FluxGrid\n\t\tref=\"$grid\"\n\t\t:rows=\"conf.rows\"\n\t\t:cols=\"conf.cols\"\n\t\t:size=\"size\"\n\t\t:rsc=\"from\"\n\t\t:css=\"cssGrid\"\n\t/>\n</template>\n"
  },
  {
    "path": "src/transitions/Explode/types.ts",
    "content": "import type { TransitionConf, TransitionOptions, TransitionProps } from '../types';\n\nexport interface TransitionExplodeOptions extends TransitionOptions {\n\trows?: number;\n\tcols?: number;\n\ttileDuration?: number;\n\ttileDelay?: number;\n}\n\nexport interface TransitionExplodeProps extends TransitionProps {\n\toptions?: TransitionExplodeOptions;\n}\n\nexport interface TransitionExplodeConf extends TransitionConf {\n\trows: number;\n\tcols: number;\n\ttileDuration: number;\n\ttileDelay: number;\n}\n"
  },
  {
    "path": "src/transitions/Fade/Fade.test.ts",
    "content": "import Fade from './Fade.vue';\nimport AnimationWrapper from '../__test__/AnimationWrapper';\nimport { Directions } from '../../controllers/Player';\n\nvi.mock('../../components/FluxImage/FluxImage.vue');\n\ndescribe('transition: Fade', () => {\n\tit('exposes onPlay and totalDuration', () => {\n\t\tconst wrapper = AnimationWrapper(Fade, {});\n\n\t\tconst { onPlay, totalDuration } = wrapper.vm;\n\n\t\texpect(typeof onPlay).toBe('function');\n\t\texpect(typeof totalDuration).toBe('number');\n\t});\n\n\tit('performs the transition with default options', () => {\n\t\tconst wrapper = AnimationWrapper(Fade, {});\n\n\t\tconst $from = wrapper.getComponent({\n\t\t\tref: '$from',\n\t\t});\n\n\t\texpect($from.props('css')).toStrictEqual({\n\t\t\tzIndex: 1,\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($from.vm.transform).toHaveBeenCalledWith({\n\t\t\topacity: 0,\n\t\t\ttransition: 'opacity 1200ms ease-in',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1200);\n\t});\n\n\tit('performs the transition with custom options prev', () => {\n\t\tconst wrapper = AnimationWrapper(Fade, {\n\t\t\tdirection: Directions.prev,\n\t\t\ttotalDuration: 1800,\n\t\t\teasing: 'linear',\n\t\t});\n\n\t\tconst $from = wrapper.getComponent({\n\t\t\tref: '$from',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($from.vm.transform).toHaveBeenCalledWith({\n\t\t\topacity: 0,\n\t\t\ttransition: 'opacity 1800ms linear',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1800);\n\t});\n\n\tit('performs the transition with custom options next', () => {\n\t\tconst wrapper = AnimationWrapper(Fade, {\n\t\t\tdirection: Directions.next,\n\t\t\ttotalDuration: 600,\n\t\t\teasing: 'ease-out',\n\t\t});\n\n\t\tconst $from = wrapper.getComponent({\n\t\t\tref: '$from',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($from.vm.transform).toHaveBeenCalledWith({\n\t\t\topacity: 0,\n\t\t\ttransition: 'opacity 600ms ease-out',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(600);\n\t});\n});\n"
  },
  {
    "path": "src/transitions/Fade/Fade.vue",
    "content": "<script setup lang=\"ts\">\n\timport { ref, reactive, type Ref, type CSSProperties } from 'vue';\n\timport useTransition from '../useTransition';\n\timport type { TransitionFadeProps, TransitionFadeConf } from './types';\n\timport type { FluxComponent } from '../../components';\n\n\tconst props = defineProps<TransitionFadeProps>();\n\n\tconst $from: Ref<null | FluxComponent> = ref(null);\n\n\tconst conf: TransitionFadeConf = reactive({\n\t\ttotalDuration: 1200,\n\t\teasing: 'ease-in',\n\t});\n\n\tuseTransition(conf, props.options);\n\n\tconst fromCss: CSSProperties = {\n\t\tzIndex: 1,\n\t};\n\n\tconst onPlay = () => {\n\t\t$from.value!.transform({\n\t\t\ttransition: `opacity ${conf.totalDuration}ms ${conf.easing}`,\n\t\t\topacity: 0,\n\t\t});\n\t};\n\n\tdefineExpose({\n\t\tonPlay,\n\t\ttotalDuration: conf.totalDuration,\n\t});\n</script>\n\n<template>\n\t<component :is=\"from.transition.component\" ref=\"$from\" :rsc=\"from\" :size=\"size\" :css=\"fromCss\" />\n</template>\n"
  },
  {
    "path": "src/transitions/Fade/types.ts",
    "content": "import type { TransitionConf, TransitionOptions, TransitionProps } from '../types';\n\nexport interface TransitionFadeOptions extends TransitionOptions {\n\ttotalDuration?: number;\n}\n\nexport interface TransitionFadeProps extends TransitionProps {\n\toptions?: TransitionFadeOptions;\n}\n\nexport interface TransitionFadeConf extends TransitionConf {\n\ttotalDuration: number;\n}\n"
  },
  {
    "path": "src/transitions/Fall/Fall.test.ts",
    "content": "import Fall from './Fall.vue';\nimport AnimationWrapper from '../__test__/AnimationWrapper';\nimport { Directions } from '../../controllers/Player';\n\nvi.mock('../../components/FluxImage/FluxImage.vue');\n\ndescribe('transition: Fall', () => {\n\tit('exposes onPlay and totalDuration', () => {\n\t\tconst wrapper = AnimationWrapper(Fall, {});\n\n\t\tconst { onPlay, totalDuration } = wrapper.vm;\n\n\t\texpect(typeof onPlay).toBe('function');\n\t\texpect(typeof totalDuration).toBe('number');\n\t});\n\n\tit('performs the transition with default options', () => {\n\t\tconst wrapper = AnimationWrapper(Fall, {});\n\n\t\tconst $from = wrapper.getComponent({\n\t\t\tref: '$from',\n\t\t});\n\n\t\tconst maskStyle = wrapper.props('maskStyle');\n\t\texpect(maskStyle.overflow).toBe('visible');\n\t\texpect(maskStyle.perspective).toBeDefined();\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($from.vm.transform).toHaveBeenCalledWith({\n\t\t\ttransform: 'rotateX(-83.6deg)',\n\t\t\ttransition: 'transform 1600ms ease-in',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1600);\n\t});\n\n\tit('performs the transition with custom options prev', () => {\n\t\tconst wrapper = AnimationWrapper(Fall, {\n\t\t\tdirection: Directions.prev,\n\t\t\ttotalDuration: 1200,\n\t\t\teasing: 'linear',\n\t\t});\n\n\t\tconst $from = wrapper.getComponent({\n\t\t\tref: '$from',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($from.vm.transform).toHaveBeenCalledWith({\n\t\t\ttransform: 'rotateX(-83.6deg)',\n\t\t\ttransition: 'transform 1200ms linear',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1200);\n\t});\n\n\tit('performs the transition with custom options next', () => {\n\t\tconst wrapper = AnimationWrapper(Fall, {\n\t\t\tdirection: Directions.next,\n\t\t\ttotalDuration: 1000,\n\t\t\teasing: 'ease-out',\n\t\t});\n\n\t\tconst $from = wrapper.getComponent({\n\t\t\tref: '$from',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($from.vm.transform).toHaveBeenCalledWith({\n\t\t\ttransform: 'rotateX(-83.6deg)',\n\t\t\ttransition: 'transform 1000ms ease-out',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1000);\n\t});\n});\n"
  },
  {
    "path": "src/transitions/Fall/Fall.vue",
    "content": "<script setup lang=\"ts\">\n\timport { ref, reactive, type Ref, type CSSProperties } from 'vue';\n\timport useTransition from '../useTransition';\n\timport type { TransitionFallProps, TransitionFallConf } from './types';\n\timport type { FluxComponent } from '../../components';\n\n\tconst props = defineProps<TransitionFallProps>();\n\n\tconst $from: Ref<null | FluxComponent> = ref(null);\n\n\tconst conf: TransitionFallConf = reactive({\n\t\ttotalDuration: 1600,\n\t\teasing: 'ease-in',\n\t});\n\n\tuseTransition(conf, props.options);\n\n\t// eslint-disable-next-line vue/no-mutating-props\n\tObject.assign(props.maskStyle, {\n\t\tperspective: '1600px',\n\t\toverflow: 'visible',\n\t});\n\n\tconst style: CSSProperties = {\n\t\ttransformOrigin: 'center bottom',\n\t};\n\n\tconst onPlay = () => {\n\t\t$from.value!.transform({\n\t\t\ttransition: `transform ${conf.totalDuration}ms ${conf.easing}`,\n\t\t\ttransform: 'rotateX(-83.6deg)',\n\t\t});\n\t};\n\n\tdefineExpose({\n\t\tonPlay,\n\t\ttotalDuration: conf.totalDuration,\n\t});\n</script>\n\n<template>\n\t<component :is=\"from.transition.component\" ref=\"$from\" :rsc=\"from\" :size=\"size\" :style=\"style\" />\n</template>\n"
  },
  {
    "path": "src/transitions/Fall/types.ts",
    "content": "import type { TransitionConf, TransitionOptions, TransitionProps } from '../types';\n\nexport interface TransitionFallOptions extends TransitionOptions {\n\ttotalDuration?: number;\n}\n\nexport interface TransitionFallProps extends TransitionProps {\n\toptions?: TransitionFallOptions;\n}\n\nexport interface TransitionFallConf extends TransitionConf {\n\ttotalDuration: number;\n}\n"
  },
  {
    "path": "src/transitions/Kenburn/Kenburn.test.ts",
    "content": "import Kenburn from './Kenburn.vue';\nimport AnimationWrapper from '../__test__/AnimationWrapper';\nimport { Directions } from '../../controllers/Player';\n\nvi.mock('../../components/FluxImage/FluxImage.vue');\n\ndescribe('transition: Kenburn', () => {\n\tit('exposes onPlay and totalDuration', () => {\n\t\tconst wrapper = AnimationWrapper(Kenburn, {});\n\n\t\tconst { onPlay, totalDuration } = wrapper.vm;\n\n\t\texpect(typeof onPlay).toBe('function');\n\t\texpect(typeof totalDuration).toBe('number');\n\t});\n\n\tit('performs the transition with default options', () => {\n\t\tconst wrapper = AnimationWrapper(Kenburn, {});\n\n\t\tconst $from = wrapper.getComponent({\n\t\t\tref: '$from',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($from.vm.transform).toHaveBeenCalledWith({\n\t\t\topacity: 0,\n\t\t\ttransform: expect.any(String),\n\t\t\ttransition: 'all 1500ms linear',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1500);\n\t});\n\n\tit('performs the transition with custom options prev', () => {\n\t\tconst wrapper = AnimationWrapper(Kenburn, {\n\t\t\tdirection: Directions.prev,\n\t\t\ttotalDuration: 800,\n\t\t\teasing: 'ease-in',\n\t\t});\n\n\t\tconst $from = wrapper.getComponent({\n\t\t\tref: '$from',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($from.vm.transform).toHaveBeenCalledWith({\n\t\t\topacity: 0,\n\t\t\ttransform: expect.any(String),\n\t\t\ttransition: 'all 800ms ease-in',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(800);\n\t});\n\n\tit('performs the transition with custom options next', () => {\n\t\tconst wrapper = AnimationWrapper(Kenburn, {\n\t\t\tdirection: Directions.next,\n\t\t\ttotalDuration: 800,\n\t\t\teasing: 'ease-in',\n\t\t});\n\n\t\tconst $from = wrapper.getComponent({\n\t\t\tref: '$from',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($from.vm.transform).toHaveBeenCalledWith({\n\t\t\topacity: 0,\n\t\t\ttransform: expect.any(String),\n\t\t\ttransition: 'all 800ms ease-in',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(800);\n\t});\n});\n"
  },
  {
    "path": "src/transitions/Kenburn/Kenburn.vue",
    "content": "<script setup lang=\"ts\">\n\timport { ref, reactive, type Ref, type CSSProperties } from 'vue';\n\timport useTransition from '../useTransition';\n\timport type { TransitionKenburnProps, TransitionKenburnConf } from './types';\n\timport type { FluxComponent } from '../../components';\n\n\tconst props = defineProps<TransitionKenburnProps>();\n\n\tconst $from: Ref<null | FluxComponent> = ref(null);\n\n\tconst conf: TransitionKenburnConf = reactive({\n\t\ttotalDuration: 1500,\n\t\teasing: 'linear',\n\t});\n\n\tuseTransition(conf, props.options);\n\n\tconst transforms = [\n\t\t{\n\t\t\tscale: '1.7',\n\t\t\ttranslateX: '-35%',\n\t\t\ttranslateY: '-35%',\n\t\t\toriginX: 'top',\n\t\t\toriginY: 'left',\n\t\t},\n\n\t\t{\n\t\t\tscale: '1.7',\n\t\t\ttranslateX: '35%',\n\t\t\ttranslateY: '-35%',\n\t\t\toriginX: 'top',\n\t\t\toriginY: 'right',\n\t\t},\n\n\t\t{\n\t\t\tscale: '1.7',\n\t\t\ttranslateX: '-35%',\n\t\t\ttranslateY: '35%',\n\t\t\toriginX: 'bottom',\n\t\t\toriginY: 'left',\n\t\t},\n\n\t\t{\n\t\t\tscale: '1.7',\n\t\t\ttranslateX: '35%',\n\t\t\ttranslateY: '35%',\n\t\t\toriginX: 'bottom',\n\t\t\toriginY: 'right',\n\t\t},\n\t];\n\n\tconst transformNumber: number = Math.floor(Math.random() * 4);\n\tconst transform = transforms[transformNumber];\n\n\tconst css: CSSProperties = {\n\t\ttransformOrigin: transform!.originX + ' ' + transform!.originY,\n\t};\n\n\tconst onPlay = () => {\n\t\t$from.value!.transform({\n\t\t\ttransition: `all ${conf.totalDuration}ms ${conf.easing}`,\n\t\t\ttransform: `scale(${transform!.scale}) translate(${transform!.translateX}, ${transform!.translateY})`,\n\t\t\topacity: 0,\n\t\t});\n\t};\n\n\tdefineExpose({\n\t\tonPlay,\n\t\ttotalDuration: conf.totalDuration,\n\t});\n</script>\n\n<template>\n\t<component :is=\"from.transition.component\" ref=\"$from\" :rsc=\"from\" :size=\"size\" :css=\"css\" />\n</template>\n"
  },
  {
    "path": "src/transitions/Kenburn/types.ts",
    "content": "import type { TransitionConf, TransitionOptions, TransitionProps } from '../types';\n\nexport interface TransitionKenburnOptions extends TransitionOptions {\n\ttotalDuration?: number;\n}\n\nexport interface TransitionKenburnProps extends TransitionProps {\n\toptions?: TransitionKenburnOptions;\n}\n\nexport interface TransitionKenburnConf extends TransitionConf {\n\ttotalDuration: number;\n}\n"
  },
  {
    "path": "src/transitions/Round1/Round1.test.ts",
    "content": "import Round1 from './Round1.vue';\nimport AnimationWrapper from '../__test__/AnimationWrapper';\nimport { Directions } from '../../controllers/Player';\nimport { Turns } from '../../components/FluxCube';\n\nvi.mock('../../components/FluxGrid/FluxGrid.vue');\n\ndescribe('transition: Round1', () => {\n\tit('exposes onPlay and totalDuration', () => {\n\t\tconst wrapper = AnimationWrapper(Round1, {});\n\n\t\tconst { onPlay, totalDuration } = wrapper.vm;\n\n\t\texpect(typeof onPlay).toBe('function');\n\t\texpect(typeof totalDuration).toBe('number');\n\t});\n\n\tit('performs the transition with default options', () => {\n\t\tconst wrapper = AnimationWrapper(Round1, {});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\tconst maskStyle = wrapper.props('maskStyle');\n\t\texpect(maskStyle.overflow).toBe('visible');\n\n\t\tconst gridCss = wrapper\n\t\t\t.getComponent({\n\t\t\t\tref: '$grid',\n\t\t\t})\n\t\t\t.props('css');\n\t\texpect(gridCss.perspective).toBeDefined();\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].setCss).toHaveBeenCalledWith({\n\t\t\ttransition: 'all 800ms ease-out 0ms',\n\t\t});\n\t\texpect($tiles[0].turn).toHaveBeenCalledWith(Turns.backr);\n\n\t\texpect($tiles[9].setCss).toHaveBeenCalledWith({\n\t\t\ttransition: 'all 800ms ease-out 300ms',\n\t\t});\n\t\texpect($tiles[9].turn).toHaveBeenCalledWith(Turns.backr);\n\n\t\texpect(wrapper.vm.totalDuration).toBe(2400);\n\t});\n\n\tit('performs the transition with custom options prev', () => {\n\t\tconst wrapper = AnimationWrapper(Round1, {\n\t\t\tdirection: Directions.prev,\n\t\t\trows: 3,\n\t\t\tcols: 6,\n\t\t\ttileDuration: 400,\n\t\t\ttileDelay: 60,\n\t\t\teasing: 'ease-in-out',\n\t\t});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].setCss).toHaveBeenCalledWith({\n\t\t\ttransition: 'all 400ms ease-in-out 480ms',\n\t\t});\n\t\texpect($tiles[0].turn).toHaveBeenCalledWith(Turns.backl);\n\n\t\texpect($tiles[17].setCss).toHaveBeenCalledWith({\n\t\t\ttransition: 'all 400ms ease-in-out 60ms',\n\t\t});\n\t\texpect($tiles[17].turn).toHaveBeenCalledWith(Turns.backl);\n\n\t\texpect(wrapper.vm.totalDuration).toBe(720);\n\t});\n\n\tit('performs the transition with custom options next', () => {\n\t\tconst wrapper = AnimationWrapper(Round1, {\n\t\t\tdirection: Directions.next,\n\t\t\trows: 3,\n\t\t\tcols: 6,\n\t\t\ttileDuration: 400,\n\t\t\ttileDelay: 60,\n\t\t\teasing: 'ease-in-out',\n\t\t});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].setCss).toHaveBeenCalledWith({\n\t\t\ttransition: 'all 400ms ease-in-out 0ms',\n\t\t});\n\t\texpect($tiles[0].turn).toHaveBeenCalledWith(Turns.backr);\n\n\t\texpect($tiles[17].setCss).toHaveBeenCalledWith({\n\t\t\ttransition: 'all 400ms ease-in-out 420ms',\n\t\t});\n\t\texpect($tiles[17].turn).toHaveBeenCalledWith(Turns.backr);\n\n\t\texpect(wrapper.vm.totalDuration).toBe(720);\n\t});\n});\n"
  },
  {
    "path": "src/transitions/Round1/Round1.vue",
    "content": "<script setup lang=\"ts\">\n\timport { ref, reactive, type Ref, type CSSProperties } from 'vue';\n\timport useTransition from '../useTransition';\n\timport { FluxGrid, FluxCube } from '../../components';\n\timport type { TransitionRound1Props, TransitionRound1Conf } from './types';\n\timport { Directions } from '../../controllers/Player';\n\timport { Turns } from '../../components';\n\n\tconst props = defineProps<TransitionRound1Props>();\n\n\tconst $grid: Ref<null | InstanceType<typeof FluxGrid>> = ref(null);\n\n\tconst conf: TransitionRound1Conf = reactive({\n\t\trows: 0,\n\t\tcols: 8,\n\t\ttileDuration: 800,\n\t\ttileDelay: 150,\n\t\teasing: 'ease-out',\n\t});\n\n\tuseTransition(conf, props.options);\n\n\tconst rscs = {\n\t\tfront: props.from,\n\t\tback: props.to,\n\t};\n\n\tconst gridCss: CSSProperties = {\n\t\tperspective: '800px',\n\t};\n\n\t// eslint-disable-next-line vue/no-mutating-props\n\tprops.maskStyle.overflow = 'visible';\n\n\tif (!props.options?.rows) {\n\t\tconst divider = props.size.width.value! / conf.cols;\n\t\tconf.rows = Math.floor(props.size.height.value! / divider);\n\t}\n\n\tconst multiplier = conf.rows > conf.cols ? conf.rows : conf.cols;\n\n\tconst totalDuration = conf.tileDelay * multiplier * 2;\n\n\tconst getDelay = (index: number) => {\n\t\tconst row = $grid.value!.getRowNumber(index, conf.cols);\n\t\tconst col = $grid.value!.getColNumber(index, conf.cols);\n\t\tlet delay = col + row;\n\n\t\tif (conf.direction === Directions.prev) {\n\t\t\tdelay = conf.rows! + conf.cols - delay - 1;\n\t\t}\n\n\t\treturn delay * conf.tileDelay;\n\t};\n\n\tconst turn = {\n\t\t[Directions.prev]: Turns.backl,\n\t\t[Directions.next]: Turns.backr,\n\t}[conf.direction!];\n\n\tconst onPlay = () => {\n\t\tif (props.displayComponent) {\n\t\t\tprops.displayComponent.hide();\n\t\t}\n\n\t\ttype FluxCubeType = InstanceType<typeof FluxCube>;\n\n\t\t$grid.value!.transform<FluxCubeType>((tile: FluxCubeType, index: number) => {\n\t\t\tconst transition = `all ${conf.tileDuration}ms ${conf.easing} ${getDelay(index)}ms`;\n\n\t\t\ttile.setCss({\n\t\t\t\ttransition,\n\t\t\t});\n\n\t\t\ttile.turn(turn);\n\t\t});\n\t};\n\n\tdefineExpose({\n\t\tonPlay,\n\t\ttotalDuration,\n\t});\n</script>\n\n<template>\n\t<FluxGrid\n\t\tref=\"$grid\"\n\t\t:rows=\"conf.rows!\"\n\t\t:cols=\"conf.cols\"\n\t\t:size=\"size\"\n\t\t:rscs=\"rscs\"\n\t\t:css=\"gridCss\"\n\t/>\n</template>\n"
  },
  {
    "path": "src/transitions/Round1/types.ts",
    "content": "import type { TransitionConf, TransitionOptions, TransitionProps } from '../types';\n\nexport interface TransitionRound1Options extends TransitionOptions {\n\trows?: number;\n\tcols?: number;\n\ttileDuration?: number;\n\ttileDelay?: number;\n}\n\nexport interface TransitionRound1Props extends TransitionProps {\n\toptions?: TransitionRound1Options;\n}\n\nexport interface TransitionRound1Conf extends TransitionConf {\n\trows: number;\n\tcols: number;\n\ttileDuration: number;\n\ttileDelay: number;\n}\n"
  },
  {
    "path": "src/transitions/Round2/Round2.test.ts",
    "content": "import Round2 from './Round2.vue';\nimport AnimationWrapper from '../__test__/AnimationWrapper';\nimport { Directions } from '../../controllers/Player';\n\nvi.mock('../../components/FluxGrid/FluxGrid.vue');\n\ndescribe('transition: Round2', () => {\n\tit('exposes onPlay and totalDuration', () => {\n\t\tconst wrapper = AnimationWrapper(Round2, {});\n\n\t\tconst { onPlay, totalDuration } = wrapper.vm;\n\n\t\texpect(typeof onPlay).toBe('function');\n\t\texpect(typeof totalDuration).toBe('number');\n\t});\n\n\tit('performs the transition with default options', () => {\n\t\tconst wrapper = AnimationWrapper(Round2, {});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\tconst maskStyle = wrapper.props('maskStyle');\n\t\texpect(maskStyle.overflow).toBe('visible');\n\n\t\tconst gridCss = wrapper\n\t\t\t.getComponent({\n\t\t\t\tref: '$grid',\n\t\t\t})\n\t\t\t.props('css');\n\t\texpect(gridCss.perspective).toBeDefined();\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'rotateY(-540deg)',\n\t\t\ttransition: 'all 800ms linear 100ms',\n\t\t});\n\n\t\texpect($tiles[9].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'rotateY(-540deg)',\n\t\t\ttransition: 'all 800ms linear 0ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1900);\n\t});\n\n\tit('performs the transition with custom options prev', () => {\n\t\tconst wrapper = AnimationWrapper(Round2, {\n\t\t\tdirection: Directions.prev,\n\t\t\trows: 3,\n\t\t\tcols: 6,\n\t\t\ttileDuration: 400,\n\t\t\ttileDelay: 60,\n\t\t\trotateX: -310,\n\t\t\teasing: 'ease-out',\n\t\t});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'rotateY(-310deg)',\n\t\t\ttransition: 'all 400ms ease-out 360ms',\n\t\t});\n\n\t\texpect($tiles[17].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'rotateY(-310deg)',\n\t\t\ttransition: 'all 400ms ease-out 60ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(720);\n\t});\n\n\tit('performs the transition with custom options next', () => {\n\t\tconst wrapper = AnimationWrapper(Round2, {\n\t\t\tdirection: Directions.next,\n\t\t\trows: 3,\n\t\t\tcols: 6,\n\t\t\ttileDuration: 400,\n\t\t\ttileDelay: 60,\n\t\t\trotateX: -310,\n\t\t\teasing: 'ease-out',\n\t\t});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'rotateY(-310deg)',\n\t\t\ttransition: 'all 400ms ease-out 0ms',\n\t\t});\n\n\t\texpect($tiles[17].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'rotateY(-310deg)',\n\t\t\ttransition: 'all 400ms ease-out 300ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(720);\n\t});\n});\n"
  },
  {
    "path": "src/transitions/Round2/Round2.vue",
    "content": "<script setup lang=\"ts\">\n\timport { ref, reactive, type Ref, type CSSProperties } from 'vue';\n\timport useTransition from '../useTransition';\n\timport { type FluxComponent, FluxGrid } from '../../components';\n\timport type { TransitionRound2Props, TransitionRound2Conf } from './types';\n\timport { Directions } from '../../controllers/Player';\n\n\tconst props = defineProps<TransitionRound2Props>();\n\n\tconst $grid: Ref<null | InstanceType<typeof FluxGrid>> = ref(null);\n\n\tconst conf: TransitionRound2Conf = reactive({\n\t\trows: 0,\n\t\tcols: 9,\n\t\ttileDuration: 800,\n\t\ttileDelay: 100,\n\t\trotateX: -540,\n\t\teasing: 'linear',\n\t});\n\n\tuseTransition(conf, props.options);\n\n\t// eslint-disable-next-line vue/no-mutating-props\n\tprops.maskStyle.overflow = 'visible';\n\n\tconst gridCss: CSSProperties = {\n\t\tperspective: '1200px',\n\t};\n\n\tconst tileCss: CSSProperties = {\n\t\tbackfaceVisibility: 'hidden',\n\t};\n\n\tif (!props.options?.rows) {\n\t\tconst divider = props.size.width.value! / conf.cols;\n\t\tconf.rows = Math.floor(props.size.height.value! / divider);\n\t}\n\n\tconst totalDuration = (conf.cols / 2 + conf.rows) * (conf.tileDelay * 2);\n\n\tconst getDelay = (index: number) => {\n\t\tconst row = $grid.value!.getRowNumber(index, conf.cols);\n\t\tconst col = $grid.value!.getColNumber(index, conf.cols);\n\n\t\tlet rowDelay, colDelay;\n\n\t\tif (conf.direction === Directions.prev) {\n\t\t\trowDelay = Math.abs(conf.rows! / 2 - 0.5 - row);\n\t\t\tcolDelay = Math.abs(conf.cols - col);\n\t\t} else {\n\t\t\trowDelay = Math.abs(conf.rows! / 2 - 0.5 - row);\n\t\t\tcolDelay = Math.abs(col);\n\t\t}\n\n\t\tconst delay = rowDelay + colDelay - 1;\n\n\t\treturn delay * conf.tileDelay;\n\t};\n\n\tconst onPlay = () => {\n\t\t$grid.value!.transform((tile: FluxComponent, index: number) => {\n\t\t\tconst transition = `all ${conf.tileDuration}ms ${conf.easing} ${getDelay(index)}ms`;\n\n\t\t\ttile.transform({\n\t\t\t\ttransition,\n\t\t\t\topacity: '0',\n\t\t\t\ttransform: `rotateY(${conf.rotateX.toString()}deg)`,\n\t\t\t});\n\t\t});\n\t};\n\n\tdefineExpose({\n\t\tonPlay,\n\t\ttotalDuration,\n\t});\n</script>\n\n<template>\n\t<FluxGrid\n\t\tref=\"$grid\"\n\t\t:rows=\"conf.rows!\"\n\t\t:cols=\"conf.cols\"\n\t\t:size=\"size\"\n\t\t:depth=\"0\"\n\t\t:rsc=\"from\"\n\t\t:css=\"gridCss\"\n\t\t:tile-css=\"tileCss\"\n\t/>\n</template>\n"
  },
  {
    "path": "src/transitions/Round2/types.ts",
    "content": "import type { TransitionConf, TransitionOptions, TransitionProps } from '../types';\n\nexport interface TransitionRound2Options extends TransitionOptions {\n\trows?: number;\n\tcols?: number;\n\ttileDuration?: number;\n\ttileDelay?: number;\n\trotateX?: number;\n}\n\nexport interface TransitionRound2Props extends TransitionProps {\n\toptions?: TransitionRound2Options;\n}\n\nexport interface TransitionRound2Conf extends TransitionConf {\n\trows: number;\n\tcols: number;\n\ttileDuration: number;\n\ttileDelay: number;\n\trotateX: number;\n}\n"
  },
  {
    "path": "src/transitions/Slide/Slide.test.ts",
    "content": "import Slide from './Slide.vue';\nimport AnimationWrapper from '../__test__/AnimationWrapper';\nimport { Directions } from '../../controllers/Player';\nimport { Size } from '../../shared';\n\nvi.mock('../../components/FluxWrapper/FluxWrapper.vue');\n\ndescribe('transition: Slide', () => {\n\tit('exposes onPlay and totalDuration', () => {\n\t\tconst wrapper = AnimationWrapper(Slide, {});\n\n\t\tconst { onPlay, totalDuration } = wrapper.vm;\n\n\t\texpect(typeof onPlay).toBe('function');\n\t\texpect(typeof totalDuration).toBe('number');\n\t});\n\n\tit('performs the transition with default options', () => {\n\t\tconst wrapper = AnimationWrapper(Slide, {});\n\n\t\tconst $wrapper = wrapper.getComponent({\n\t\t\tref: '$wrapper',\n\t\t});\n\n\t\texpect($wrapper.props('size')).toStrictEqual(\n\t\t\tnew Size({ width: 1280, height: 360 })\n\t\t);\n\t\texpect($wrapper.props('css')).toStrictEqual({\n\t\t\tdisplay: 'flex',\n\t\t\tflexWrap: 'nowrap',\n\t\t});\n\n\t\tconst $left = wrapper.getComponent({\n\t\t\tref: '$left',\n\t\t});\n\n\t\texpect($left.props('size')).toStrictEqual(wrapper.props('size'));\n\n\t\tconst $right = wrapper.getComponent({\n\t\t\tref: '$right',\n\t\t});\n\n\t\texpect($right.props('size')).toStrictEqual(wrapper.props('size'));\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($wrapper.vm.transform).toHaveBeenCalledWith({\n\t\t\ttransform: 'translateX(-50%)',\n\t\t\ttransition: 'transform 1400ms ease-in-out',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1400);\n\t});\n\n\tit('performs the transition with custom options prev', () => {\n\t\tconst wrapper = AnimationWrapper(Slide, {\n\t\t\tdirection: Directions.prev,\n\t\t\ttotalDuration: 800,\n\t\t\teasing: 'ease-out',\n\t\t});\n\n\t\tconst $wrapper = wrapper.getComponent({\n\t\t\tref: '$wrapper',\n\t\t});\n\n\t\texpect($wrapper.props('size')).toStrictEqual(\n\t\t\tnew Size({ width: 1280, height: 360 })\n\t\t);\n\t\texpect($wrapper.props('css')).toStrictEqual({\n\t\t\tdisplay: 'flex',\n\t\t\tflexWrap: 'nowrap',\n\t\t\ttransform: 'translateX(-50%)',\n\t\t});\n\n\t\tconst $left = wrapper.getComponent({\n\t\t\tref: '$left',\n\t\t});\n\n\t\texpect($left.props('size')).toStrictEqual(wrapper.props('size'));\n\t\texpect($left.props('rsc')).toStrictEqual(wrapper.props('to'));\n\n\t\tconst $right = wrapper.getComponent({\n\t\t\tref: '$right',\n\t\t});\n\n\t\texpect($right.props('size')).toStrictEqual(wrapper.props('size'));\n\t\texpect($right.props('rsc')).toStrictEqual(wrapper.props('from'));\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($wrapper.vm.transform).toHaveBeenCalledWith({\n\t\t\ttransform: 'translateX(0)',\n\t\t\ttransition: 'transform 800ms ease-out',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(800);\n\t});\n\n\tit('performs the transition with custom options next', () => {\n\t\tconst wrapper = AnimationWrapper(Slide, {\n\t\t\tdirection: Directions.next,\n\t\t\ttotalDuration: 800,\n\t\t\teasing: 'ease-out',\n\t\t});\n\n\t\tconst $wrapper = wrapper.getComponent({\n\t\t\tref: '$wrapper',\n\t\t});\n\n\t\texpect($wrapper.props('size')).toStrictEqual(\n\t\t\tnew Size({ width: 1280, height: 360 })\n\t\t);\n\t\texpect($wrapper.props('css')).toStrictEqual({\n\t\t\tdisplay: 'flex',\n\t\t\tflexWrap: 'nowrap',\n\t\t});\n\n\t\tconst $left = wrapper.getComponent({\n\t\t\tref: '$left',\n\t\t});\n\n\t\texpect($left.props('size')).toStrictEqual(wrapper.props('size'));\n\t\texpect($left.props('rsc')).toStrictEqual(wrapper.props('from'));\n\n\t\tconst $right = wrapper.getComponent({\n\t\t\tref: '$right',\n\t\t});\n\n\t\texpect($right.props('size')).toStrictEqual(wrapper.props('size'));\n\t\texpect($right.props('rsc')).toStrictEqual(wrapper.props('to'));\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($wrapper.vm.transform).toHaveBeenCalledWith({\n\t\t\ttransform: 'translateX(-50%)',\n\t\t\ttransition: 'transform 800ms ease-out',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(800);\n\t});\n});\n"
  },
  {
    "path": "src/transitions/Slide/Slide.vue",
    "content": "<script setup lang=\"ts\">\n\timport { ref, reactive, type Ref, type CSSProperties } from 'vue';\n\timport useTransition from '../useTransition';\n\timport { type FluxComponent, FluxWrapper } from '../../components';\n\timport type { TransitionSlideProps, TransitionSlideConf } from './types';\n\timport type { ComponentProps } from '../../components';\n\timport { Size } from '../../shared';\n\timport { Directions } from '../../controllers/Player';\n\timport { Resource } from '../../resources';\n\n\tconst props = defineProps<TransitionSlideProps>();\n\n\tconst $wrapper: Ref<null | InstanceType<typeof FluxWrapper>> = ref(null);\n\tconst $left: Ref<null | FluxComponent> = ref(null);\n\tconst $right: Ref<null | FluxComponent> = ref(null);\n\n\tconst conf: TransitionSlideConf = reactive({\n\t\ttotalDuration: 1400,\n\t\teasing: 'ease-in-out',\n\t});\n\n\tuseTransition(conf, props.options);\n\n\tconst transition = `transform ${conf.totalDuration}ms ${conf.easing}`;\n\n\tconst wrapperProps: ComponentProps = {\n\t\tsize: new Size({\n\t\t\twidth: props.size.width.value! * 2,\n\t\t\theight: props.size.height.value!,\n\t\t}),\n\t\tcss: {\n\t\t\tdisplay: 'flex',\n\t\t\tflexWrap: 'nowrap',\n\t\t} as CSSProperties,\n\t};\n\n\tlet left: Resource;\n\tlet right: Resource;\n\n\tconst setup = {\n\t\t[Directions.prev]: () => {\n\t\t\tleft = props.to!;\n\t\t\tright = props.from;\n\t\t\twrapperProps.css!.transform = 'translateX(-50%)';\n\t\t},\n\n\t\t[Directions.next]: () => {\n\t\t\tleft = props.from;\n\t\t\tright = props.to!;\n\t\t},\n\t};\n\n\tsetup[conf.direction!]();\n\n\tconst play = {\n\t\t[Directions.prev]: () => {\n\t\t\t$wrapper.value!.transform({\n\t\t\t\ttransition: transition,\n\t\t\t\ttransform: 'translateX(0)',\n\t\t\t});\n\t\t},\n\n\t\t[Directions.next]: () => {\n\t\t\t$wrapper.value!.transform({\n\t\t\t\ttransition: transition,\n\t\t\t\ttransform: 'translateX(-50%)',\n\t\t\t});\n\t\t},\n\t};\n\n\tconst onPlay = () => {\n\t\tplay[conf.direction!]();\n\t};\n\n\tdefineExpose({\n\t\tonPlay,\n\t\ttotalDuration: conf.totalDuration,\n\t});\n</script>\n\n<template>\n\t<FluxWrapper ref=\"$wrapper\" v-bind=\"wrapperProps\">\n\t\t<component :is=\"left.transition.component\" ref=\"$left\" :rsc=\"left\" :size=\"size\" />\n\t\t<component :is=\"right.transition.component\" ref=\"$right\" :rsc=\"right\" :size=\"size\" />\n\t</FluxWrapper>\n</template>\n"
  },
  {
    "path": "src/transitions/Slide/types.ts",
    "content": "import type { TransitionConf, TransitionOptions, TransitionProps } from '../types';\n\nexport interface TransitionSlideOptions extends TransitionOptions {\n\ttotalDuration?: number;\n}\n\nexport interface TransitionSlideProps extends TransitionProps {\n\toptions?: TransitionSlideOptions;\n}\n\nexport interface TransitionSlideConf extends TransitionConf {\n\ttotalDuration: number;\n}\n"
  },
  {
    "path": "src/transitions/Swipe/Swipe.test.ts",
    "content": "import Swipe from './Swipe.vue';\nimport AnimationWrapper from '../__test__/AnimationWrapper';\nimport { Directions } from '../../controllers/Player';\n\nvi.mock('../../components/FluxWrapper/FluxWrapper.vue');\n\ndescribe('transition: Swipe', () => {\n\tit('exposes onPlay and totalDuration', () => {\n\t\tconst wrapper = AnimationWrapper(Swipe, {});\n\n\t\tconst { onPlay, totalDuration } = wrapper.vm;\n\n\t\texpect(typeof onPlay).toBe('function');\n\t\texpect(typeof totalDuration).toBe('number');\n\t});\n\n\tit('performs the transition with default options', () => {\n\t\tconst wrapper = AnimationWrapper(Swipe, {});\n\n\t\tconst $wrapper = wrapper.getComponent({\n\t\t\tref: '$wrapper',\n\t\t});\n\n\t\texpect($wrapper.props('size')).toStrictEqual(wrapper.props('size'));\n\t\texpect($wrapper.props('css')).toStrictEqual({\n\t\t\tdisplay: 'flex',\n\t\t\tflexWrap: 'nowrap',\n\t\t\tjustifyContent: 'flex-start',\n\t\t\tleft: 0,\n\t\t\tposition: 'absolute',\n\t\t\ttop: 0,\n\t\t});\n\n\t\tconst $from = wrapper.getComponent({\n\t\t\tref: '$from',\n\t\t});\n\n\t\texpect($from.props('css')).toStrictEqual({\n\t\t\tflex: '0 0 auto',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($wrapper.vm.transform).toHaveBeenCalledWith({\n\t\t\ttransition: 'width 1400ms ease-in-out',\n\t\t\twidth: 0,\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1400);\n\t});\n\n\tit('performs the transition with custom options prev', () => {\n\t\tconst wrapper = AnimationWrapper(Swipe, {\n\t\t\tdirection: Directions.prev,\n\t\t\ttotalDuration: 800,\n\t\t\teasing: 'ease-out',\n\t\t});\n\n\t\tconst $wrapper = wrapper.getComponent({\n\t\t\tref: '$wrapper',\n\t\t});\n\n\t\texpect($wrapper.props('size')).toStrictEqual(wrapper.props('size'));\n\t\texpect($wrapper.props('css')).toStrictEqual({\n\t\t\tdisplay: 'flex',\n\t\t\tflexWrap: 'nowrap',\n\t\t\tjustifyContent: 'flex-end',\n\t\t\tright: 0,\n\t\t\tposition: 'absolute',\n\t\t\ttop: 0,\n\t\t});\n\n\t\tconst $from = wrapper.getComponent({\n\t\t\tref: '$from',\n\t\t});\n\n\t\texpect($from.props('css')).toStrictEqual({\n\t\t\tflex: '0 0 auto',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($wrapper.vm.transform).toHaveBeenCalledWith({\n\t\t\ttransition: 'width 800ms ease-out',\n\t\t\twidth: 0,\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(800);\n\t});\n\n\tit('performs the transition with custom options next', () => {\n\t\tconst wrapper = AnimationWrapper(Swipe, {\n\t\t\tdirection: Directions.next,\n\t\t\ttotalDuration: 800,\n\t\t\teasing: 'ease-out',\n\t\t});\n\n\t\tconst $wrapper = wrapper.getComponent({\n\t\t\tref: '$wrapper',\n\t\t});\n\n\t\texpect($wrapper.props('size')).toStrictEqual(wrapper.props('size'));\n\t\texpect($wrapper.props('css')).toStrictEqual({\n\t\t\tdisplay: 'flex',\n\t\t\tflexWrap: 'nowrap',\n\t\t\tjustifyContent: 'flex-start',\n\t\t\tleft: 0,\n\t\t\tposition: 'absolute',\n\t\t\ttop: 0,\n\t\t});\n\n\t\tconst $from = wrapper.getComponent({\n\t\t\tref: '$from',\n\t\t});\n\n\t\texpect($from.props('css')).toStrictEqual({\n\t\t\tflex: '0 0 auto',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($wrapper.vm.transform).toHaveBeenCalledWith({\n\t\t\ttransition: 'width 800ms ease-out',\n\t\t\twidth: 0,\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(800);\n\t});\n});\n"
  },
  {
    "path": "src/transitions/Swipe/Swipe.vue",
    "content": "<script setup lang=\"ts\">\n\timport { ref, reactive, type Ref, type CSSProperties } from 'vue';\n\timport useTransition from '../useTransition';\n\timport { type FluxComponent, FluxWrapper } from '../../components';\n\timport type { TransitionSwipeProps, TransitionSwipeConf } from './types';\n\timport { Directions } from '../../controllers/Player';\n\n\tconst props = defineProps<TransitionSwipeProps>();\n\n\tconst $wrapper: Ref<null | InstanceType<typeof FluxWrapper>> = ref(null);\n\tconst $from: Ref<null | FluxComponent> = ref(null);\n\n\tconst conf: TransitionSwipeConf = reactive({\n\t\ttotalDuration: 1400,\n\t\teasing: 'ease-in-out',\n\t});\n\n\tuseTransition(conf, props.options);\n\n\tconst wrapperCss: CSSProperties = {\n\t\tposition: 'absolute',\n\t\ttop: 0,\n\t\tdisplay: 'flex',\n\t\tflexWrap: 'nowrap',\n\t};\n\n\tconst fromCss: CSSProperties = {\n\t\tflex: '0 0 auto',\n\t};\n\n\tconst setup = {\n\t\t[Directions.prev]: () => {\n\t\t\tObject.assign(wrapperCss, {\n\t\t\t\tright: 0,\n\t\t\t\tjustifyContent: 'flex-end',\n\t\t\t});\n\t\t},\n\n\t\t[Directions.next]: () => {\n\t\t\tObject.assign(wrapperCss, {\n\t\t\t\tleft: 0,\n\t\t\t\tjustifyContent: 'flex-start',\n\t\t\t});\n\t\t},\n\t};\n\n\tsetup[conf.direction!]();\n\n\tconst onPlay = () => {\n\t\t$wrapper.value!.transform({\n\t\t\ttransition: `width ${conf.totalDuration}ms ${conf.easing}`,\n\t\t\twidth: 0,\n\t\t});\n\t};\n\n\tdefineExpose({\n\t\tonPlay,\n\t\ttotalDuration: conf.totalDuration,\n\t});\n</script>\n\n<template>\n\t<FluxWrapper ref=\"$wrapper\" :size=\"size\" :css=\"wrapperCss\">\n\t\t<component\n\t\t\t:is=\"from.transition.component\"\n\t\t\tref=\"$from\"\n\t\t\t:rsc=\"from\"\n\t\t\t:size=\"size\"\n\t\t\t:css=\"fromCss\"\n\t\t/>\n\t</FluxWrapper>\n</template>\n"
  },
  {
    "path": "src/transitions/Swipe/types.ts",
    "content": "import type { TransitionConf, TransitionOptions, TransitionProps } from '../types';\n\nexport interface TransitionSwipeOptions extends TransitionOptions {\n\ttotalDuration?: number;\n}\n\nexport interface TransitionSwipeProps extends TransitionProps {\n\toptions?: TransitionSwipeOptions;\n}\n\nexport interface TransitionSwipeConf extends TransitionConf {\n\ttotalDuration: number;\n}\n"
  },
  {
    "path": "src/transitions/Warp/Warp.test.ts",
    "content": "import Warp from './Warp.vue';\nimport AnimationWrapper from '../__test__/AnimationWrapper';\nimport { Directions } from '../../controllers/Player';\n\nvi.mock('../../components/FluxVortex/FluxVortex.vue');\n\ndescribe('transition: Warp', () => {\n\tit('exposes onPlay and totalDuration', () => {\n\t\tconst wrapper = AnimationWrapper(Warp, {});\n\n\t\tconst { onPlay, totalDuration } = wrapper.vm;\n\n\t\texpect(typeof onPlay).toBe('function');\n\t\texpect(typeof totalDuration).toBe('number');\n\t});\n\n\tit('performs the transition with default options', () => {\n\t\tconst wrapper = AnimationWrapper(Warp, {});\n\n\t\tconst $vortex = wrapper.getComponent({\n\t\t\tref: '$vortex',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($vortex.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $vortex.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'rotateZ(-90deg)',\n\t\t\ttransition: 'all 800ms linear 0ms',\n\t\t});\n\n\t\texpect($tiles[6].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'rotateZ(-90deg)',\n\t\t\ttransition: 'all 800ms linear 900ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1850);\n\t});\n\n\tit('performs the transition with custom options prev', () => {\n\t\tconst wrapper = AnimationWrapper(Warp, {\n\t\t\tdirection: Directions.prev,\n\t\t\tcircles: 10,\n\t\t\ttileDuration: 400,\n\t\t\ttileDelay: 60,\n\t\t\teasing: 'ease-out',\n\t\t});\n\n\t\tconst $vortex = wrapper.getComponent({\n\t\t\tref: '$vortex',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($vortex.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $vortex.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'rotateZ(-90deg)',\n\t\t\ttransition: 'all 400ms ease-out 540ms',\n\t\t});\n\n\t\texpect($tiles[6].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'rotateZ(-90deg)',\n\t\t\ttransition: 'all 400ms ease-out 180ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1000);\n\t});\n\n\tit('performs the transition with custom options next', () => {\n\t\tconst wrapper = AnimationWrapper(Warp, {\n\t\t\tdirection: Directions.next,\n\t\t\tcircles: 10,\n\t\t\ttileDuration: 400,\n\t\t\ttileDelay: 60,\n\t\t\teasing: 'ease-out',\n\t\t});\n\n\t\tconst $vortex = wrapper.getComponent({\n\t\t\tref: '$vortex',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($vortex.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $vortex.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'rotateZ(-90deg)',\n\t\t\ttransition: 'all 400ms ease-out 0ms',\n\t\t});\n\n\t\texpect($tiles[6].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0',\n\t\t\ttransform: 'rotateZ(-90deg)',\n\t\t\ttransition: 'all 400ms ease-out 360ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1000);\n\t});\n});\n"
  },
  {
    "path": "src/transitions/Warp/Warp.vue",
    "content": "<script setup lang=\"ts\">\n\timport { ref, reactive, type Ref } from 'vue';\n\timport useTransition from '../useTransition';\n\timport { type FluxComponent, FluxVortex } from '../../components';\n\timport type { TransitionWarpProps, TransitionWarpConf } from './types';\n\timport { Directions } from '../../controllers/Player';\n\n\tconst props = defineProps<TransitionWarpProps>();\n\n\tconst $vortex: Ref<null | InstanceType<typeof FluxVortex>> = ref(null);\n\n\tconst conf: TransitionWarpConf = reactive({\n\t\tcircles: 7,\n\t\ttileDuration: 800,\n\t\ttileDelay: 150,\n\t\teasing: 'linear',\n\t});\n\n\tuseTransition(conf, props.options);\n\n\tconst totalDuration = conf.tileDelay * conf.circles + conf.tileDuration;\n\n\tconst getDelay = {\n\t\t[Directions.prev]: (index: number) => (conf.circles - index - 1) * conf.tileDelay,\n\t\t[Directions.next]: (index: number) => index * conf.tileDelay,\n\t};\n\n\tconst getDeg = (index: number) => (index % 2 === 0 ? '-90' : '90');\n\n\tconst onPlay = () => {\n\t\t$vortex.value!.transform((tile: FluxComponent, index: number) => {\n\t\t\tconst transition = `all ${conf.tileDuration}ms ${\n\t\t\t\tconf.easing\n\t\t\t} ${getDelay[conf.direction!](index)}ms`;\n\n\t\t\ttile.transform({\n\t\t\t\ttransition,\n\t\t\t\topacity: '0',\n\t\t\t\ttransform: `rotateZ(${getDeg(index)}deg)`,\n\t\t\t});\n\t\t});\n\t};\n\n\tdefineExpose({\n\t\tonPlay,\n\t\ttotalDuration,\n\t});\n</script>\n\n<template>\n\t<FluxVortex ref=\"$vortex\" :size=\"size\" :circles=\"conf.circles\" :rsc=\"from\" />\n</template>\n"
  },
  {
    "path": "src/transitions/Warp/types.ts",
    "content": "import type { TransitionConf, TransitionOptions, TransitionProps } from '../types';\n\nexport interface TransitionWarpOptions extends TransitionOptions {\n\tcircles?: number;\n\ttileDuration?: number;\n\ttileDelay?: number;\n}\n\nexport interface TransitionWarpProps extends TransitionProps {\n\toptions?: TransitionWarpOptions;\n}\n\nexport interface TransitionWarpConf extends TransitionConf {\n\tcircles: number;\n\ttileDuration: number;\n\ttileDelay: number;\n}\n"
  },
  {
    "path": "src/transitions/Waterfall/Waterfall.test.ts",
    "content": "import Waterfall from './Waterfall.vue';\nimport AnimationWrapper from '../__test__/AnimationWrapper';\nimport { Directions } from '../../controllers/Player';\n\nvi.mock('../../components/FluxGrid/FluxGrid.vue');\n\ndescribe('transition: Waterfall', () => {\n\tit('exposes onPlay and totalDuration', () => {\n\t\tconst wrapper = AnimationWrapper(Waterfall, {});\n\n\t\tconst { onPlay, totalDuration } = wrapper.vm;\n\n\t\texpect(typeof onPlay).toBe('function');\n\t\texpect(typeof totalDuration).toBe('number');\n\t});\n\n\tit('performs the transition with default options', () => {\n\t\tconst wrapper = AnimationWrapper(Waterfall, {});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0.1',\n\t\t\ttransform: 'translateY(100%)',\n\t\t\ttransition: 'all 600ms cubic-bezier(0.55, 0.055, 0.675, 0.19) 0ms',\n\t\t});\n\n\t\texpect($tiles[9].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0.1',\n\t\t\ttransform: 'translateY(100%)',\n\t\t\ttransition: 'all 600ms cubic-bezier(0.55, 0.055, 0.675, 0.19) 810ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1500);\n\t});\n\n\tit('performs the transition with custom options prev', () => {\n\t\tconst wrapper = AnimationWrapper(Waterfall, {\n\t\t\tdirection: Directions.prev,\n\t\t\tcols: 6,\n\t\t\ttileDuration: 400,\n\t\t\ttileDelay: 60,\n\t\t\teasing: 'ease-out',\n\t\t});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0.1',\n\t\t\ttransform: 'translateY(100%)',\n\t\t\ttransition: 'all 400ms ease-out 300ms',\n\t\t});\n\n\t\texpect($tiles[5].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0.1',\n\t\t\ttransform: 'translateY(100%)',\n\t\t\ttransition: 'all 400ms ease-out 0ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(760);\n\t});\n\n\tit('performs the transition with custom options next', () => {\n\t\tconst wrapper = AnimationWrapper(Waterfall, {\n\t\t\tdirection: Directions.next,\n\t\t\tcols: 6,\n\t\t\ttileDuration: 400,\n\t\t\ttileDelay: 60,\n\t\t\teasing: 'ease-out',\n\t\t});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0.1',\n\t\t\ttransform: 'translateY(100%)',\n\t\t\ttransition: 'all 400ms ease-out 0ms',\n\t\t});\n\n\t\texpect($tiles[5].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0.1',\n\t\t\ttransform: 'translateY(100%)',\n\t\t\ttransition: 'all 400ms ease-out 300ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(760);\n\t});\n});\n"
  },
  {
    "path": "src/transitions/Waterfall/Waterfall.vue",
    "content": "<script setup lang=\"ts\">\n\timport { ref, reactive, type Ref } from 'vue';\n\timport useTransition from '../useTransition';\n\timport { type FluxComponent, FluxGrid } from '../../components';\n\timport type { TransitionWaterfallProps, TransitionWaterfallConf } from './types';\n\timport { Directions } from '../../controllers/Player';\n\n\tconst props = defineProps<TransitionWaterfallProps>();\n\n\tconst $grid: Ref<null | InstanceType<typeof FluxGrid>> = ref(null);\n\n\tconst conf: TransitionWaterfallConf = reactive({\n\t\trows: 1,\n\t\tcols: 10,\n\t\ttileDuration: 600,\n\t\ttileDelay: 90,\n\t\teasing: 'cubic-bezier(0.55, 0.055, 0.675, 0.19)',\n\t});\n\n\tuseTransition(conf, props.options);\n\n\tconst totalDuration = conf.tileDelay * conf.cols + conf.tileDuration;\n\n\tconst getDelay = {\n\t\t[Directions.prev]: (index: number) => (conf.cols - index - 1) * conf.tileDelay,\n\t\t[Directions.next]: (index: number) => index * conf.tileDelay,\n\t};\n\n\tconst onPlay = () => {\n\t\t$grid.value!.transform((tile: FluxComponent, index: number) => {\n\t\t\tconst transition = `all ${conf.tileDuration}ms ${\n\t\t\t\tconf.easing\n\t\t\t} ${getDelay[conf.direction!](index)}ms`;\n\n\t\t\ttile.transform({\n\t\t\t\ttransition,\n\t\t\t\topacity: '0.1',\n\t\t\t\ttransform: `translateY(100%)`,\n\t\t\t});\n\t\t});\n\t};\n\n\tdefineExpose({\n\t\tonPlay,\n\t\ttotalDuration,\n\t});\n</script>\n\n<template>\n\t<FluxGrid ref=\"$grid\" :rows=\"conf.rows\" :cols=\"conf.cols\" :size=\"size\" :rsc=\"from\" />\n</template>\n"
  },
  {
    "path": "src/transitions/Waterfall/types.ts",
    "content": "import type { TransitionConf, TransitionOptions, TransitionProps } from '../types';\n\nexport interface TransitionWaterfallOptions extends TransitionOptions {\n\tcols?: number;\n\ttileDuration?: number;\n\ttileDelay?: number;\n}\n\nexport interface TransitionWaterfallProps extends TransitionProps {\n\toptions?: TransitionWaterfallOptions;\n}\n\nexport interface TransitionWaterfallConf extends TransitionConf {\n\trows: number;\n\tcols: number;\n\ttileDuration: number;\n\ttileDelay: number;\n}\n"
  },
  {
    "path": "src/transitions/Wave/Wave.test.ts",
    "content": "import Wave from './Wave.vue';\nimport AnimationWrapper from '../__test__/AnimationWrapper';\nimport { Directions } from '../../controllers';\nimport { Turns } from '../../components/FluxCube';\n\nvi.mock('../../components/FluxGrid/FluxGrid.vue');\n\ndescribe('transition: Wave', () => {\n\tit('exposes onPlay and totalDuration', () => {\n\t\tconst wrapper = AnimationWrapper(Wave, {});\n\n\t\tconst { onPlay, totalDuration } = wrapper.vm;\n\n\t\texpect(typeof onPlay).toBe('function');\n\t\texpect(typeof totalDuration).toBe('number');\n\t});\n\n\tit('expects to set proper CSS styles before animation', () => {\n\t\tconst wrapper = AnimationWrapper(Wave, {});\n\n\t\tconst maskStyle = wrapper.props('maskStyle');\n\t\texpect(maskStyle.overflow).toBe('visible');\n\n\t\tconst gridCss = wrapper\n\t\t\t.getComponent({\n\t\t\t\tref: '$grid',\n\t\t\t})\n\t\t\t.props('css');\n\t\texpect(gridCss.perspective).toBeDefined();\n\t});\n\n\tit('performs the transition with default options', () => {\n\t\tconst wrapper = AnimationWrapper(Wave, {});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].setCss).toHaveBeenCalledWith({\n\t\t\ttransition: 'all 900ms cubic-bezier(0.3, -0.3, 0.735, 0.285) 0ms',\n\t\t});\n\t\texpect($tiles[0].turn).toHaveBeenCalledWith(Turns.bottom);\n\n\t\texpect($tiles[7].setCss).toHaveBeenCalledWith({\n\t\t\ttransition: 'all 900ms cubic-bezier(0.3, -0.3, 0.735, 0.285) 770ms',\n\t\t});\n\t\texpect($tiles[7].turn).toHaveBeenCalledWith(Turns.bottom);\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1780);\n\t});\n\n\tit('performs the transition with custom options prev', () => {\n\t\tconst wrapper = AnimationWrapper(Wave, {\n\t\t\tdirection: Directions.prev,\n\t\t\tcols: 6,\n\t\t\ttileDuration: 400,\n\t\t\ttileDelay: 60,\n\t\t\tsideColor: '#999',\n\t\t\teasing: 'ease-out',\n\t\t});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].setCss).toHaveBeenCalledWith({\n\t\t\ttransition: 'all 400ms ease-out 300ms',\n\t\t});\n\t\texpect($tiles[0].turn).toHaveBeenCalledWith(Turns.bottom);\n\n\t\texpect($tiles[5].setCss).toHaveBeenCalledWith({\n\t\t\ttransition: 'all 400ms ease-out 0ms',\n\t\t});\n\t\texpect($tiles[5].turn).toHaveBeenCalledWith(Turns.bottom);\n\n\t\texpect(wrapper.vm.totalDuration).toBe(760);\n\t});\n\n\tit('performs the transition with custom options next', () => {\n\t\tconst wrapper = AnimationWrapper(Wave, {\n\t\t\tdirection: Directions.next,\n\t\t\tcols: 6,\n\t\t\ttileDuration: 400,\n\t\t\ttileDelay: 60,\n\t\t\teasing: 'ease-out',\n\t\t});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].setCss).toHaveBeenCalledWith({\n\t\t\ttransition: 'all 400ms ease-out 0ms',\n\t\t});\n\t\texpect($tiles[0].turn).toHaveBeenCalledWith(Turns.bottom);\n\n\t\texpect($tiles[5].setCss).toHaveBeenCalledWith({\n\t\t\ttransition: 'all 400ms ease-out 300ms',\n\t\t});\n\t\texpect($tiles[5].turn).toHaveBeenCalledWith(Turns.bottom);\n\n\t\texpect(wrapper.vm.totalDuration).toBe(760);\n\t});\n});\n"
  },
  {
    "path": "src/transitions/Wave/Wave.vue",
    "content": "<script setup lang=\"ts\">\n\timport { ref, reactive, type Ref, type CSSProperties } from 'vue';\n\timport useTransition from '../useTransition';\n\timport { FluxGrid, FluxCube } from '../../components';\n\timport type { TransitionWaveProps, TransitionWaveConf } from './types';\n\timport { Directions } from '../../controllers/Player';\n\timport { Turns } from '../../components/FluxCube';\n\n\tconst props = defineProps<TransitionWaveProps>();\n\n\tconst $grid: Ref<null | InstanceType<typeof FluxGrid>> = ref(null);\n\n\tconst conf: TransitionWaveConf = reactive({\n\t\trows: 1,\n\t\tcols: 8,\n\t\ttileDuration: 900,\n\t\ttileDelay: 110,\n\t\tsideColor: '#333',\n\t\teasing: 'cubic-bezier(0.3, -0.3, 0.735, 0.285)',\n\t});\n\n\tuseTransition(conf, props.options);\n\n\t// eslint-disable-next-line vue/no-mutating-props\n\tprops.maskStyle.overflow = 'visible';\n\n\tconst rscs = {\n\t\tfront: props.from,\n\t\ttop: props.to,\n\t};\n\n\tconst colors = {\n\t\tleft: conf.sideColor,\n\t\tright: conf.sideColor,\n\t};\n\n\tconst gridCss: CSSProperties = {\n\t\tperspective: '1200px',\n\t};\n\n\tconst totalDuration = conf.tileDelay * conf.cols + conf.tileDuration;\n\n\tconst getDelay = {\n\t\t[Directions.prev]: (index: number) => (conf.cols - index - 1) * conf.tileDelay,\n\t\t[Directions.next]: (index: number) => index * conf.tileDelay,\n\t};\n\n\tconst onPlay = () => {\n\t\tif (props.displayComponent) {\n\t\t\tprops.displayComponent.hide();\n\t\t}\n\n\t\ttype FluxCubeType = InstanceType<typeof FluxCube>;\n\n\t\t$grid.value!.transform<FluxCubeType>((tile: FluxCubeType, index: number) => {\n\t\t\tconst transition = `all ${conf.tileDuration}ms ${\n\t\t\t\tconf.easing\n\t\t\t} ${getDelay[conf.direction!](index)}ms`;\n\n\t\t\ttile.setCss({\n\t\t\t\ttransition,\n\t\t\t});\n\n\t\t\ttile.turn(Turns.bottom);\n\t\t});\n\t};\n\n\tdefineExpose({\n\t\tonPlay,\n\t\ttotalDuration,\n\t});\n</script>\n\n<template>\n\t<FluxGrid\n\t\tref=\"$grid\"\n\t\t:rows=\"conf.rows\"\n\t\t:cols=\"conf.cols\"\n\t\t:size=\"size\"\n\t\t:rscs=\"rscs\"\n\t\t:colors=\"colors\"\n\t\t:depth=\"size.height.value!\"\n\t\t:css=\"gridCss\"\n\t/>\n</template>\n"
  },
  {
    "path": "src/transitions/Wave/types.ts",
    "content": "import type { CSSProperties } from 'vue';\nimport type { TransitionConf, TransitionOptions, TransitionProps } from '../types';\n\nexport interface TransitionWaveOptions extends TransitionOptions {\n\tcols?: number;\n\ttileDuration?: number;\n\ttileDelay?: number;\n\tsideColor?: CSSProperties['color'];\n}\n\nexport interface TransitionWaveProps extends TransitionProps {\n\toptions?: TransitionWaveOptions;\n}\n\nexport interface TransitionWaveConf extends TransitionConf {\n\trows: number;\n\tcols: number;\n\ttileDuration: number;\n\ttileDelay: number;\n\tsideColor: CSSProperties['color'];\n}\n"
  },
  {
    "path": "src/transitions/Zip/Zip.test.ts",
    "content": "import Zip from './Zip.vue';\nimport AnimationWrapper from '../__test__/AnimationWrapper';\nimport { Directions } from '../../controllers/Player';\n\nvi.mock('../../components/FluxGrid/FluxGrid.vue');\n\ndescribe('transition: Zip', () => {\n\tit('exposes onPlay and totalDuration', () => {\n\t\tconst wrapper = AnimationWrapper(Zip, {});\n\n\t\tconst { onPlay, totalDuration } = wrapper.vm;\n\n\t\texpect(typeof onPlay).toBe('function');\n\t\texpect(typeof totalDuration).toBe('number');\n\t});\n\n\tit('performs the transition with default options', () => {\n\t\tconst wrapper = AnimationWrapper(Zip, {});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0.1',\n\t\t\ttransform: 'translateY(100%)',\n\t\t\ttransition: 'all 600ms ease-in 0ms',\n\t\t});\n\n\t\texpect($tiles[9].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0.1',\n\t\t\ttransform: 'translateY(-100%)',\n\t\t\ttransition: 'all 600ms ease-in 720ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(1400);\n\t});\n\n\tit('performs the transition with custom options prev', () => {\n\t\tconst wrapper = AnimationWrapper(Zip, {\n\t\t\tdirection: Directions.prev,\n\t\t\tcols: 6,\n\t\t\ttileDuration: 400,\n\t\t\ttileDelay: 60,\n\t\t\teasing: 'ease-out',\n\t\t});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0.1',\n\t\t\ttransform: 'translateY(100%)',\n\t\t\ttransition: 'all 400ms ease-out 300ms',\n\t\t});\n\n\t\texpect($tiles[5].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0.1',\n\t\t\ttransform: 'translateY(-100%)',\n\t\t\ttransition: 'all 400ms ease-out 0ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(760);\n\t});\n\n\tit('performs the transition with custom options next', () => {\n\t\tconst wrapper = AnimationWrapper(Zip, {\n\t\t\tdirection: Directions.next,\n\t\t\tcols: 6,\n\t\t\ttileDuration: 400,\n\t\t\ttileDelay: 60,\n\t\t\teasing: 'ease-out',\n\t\t});\n\n\t\tconst $grid = wrapper.getComponent({\n\t\t\tref: '$grid',\n\t\t});\n\n\t\twrapper.vm.onPlay();\n\n\t\texpect($grid.vm.transform).toHaveBeenCalledOnce();\n\n\t\tconst { $tiles } = $grid.vm;\n\n\t\texpect($tiles[0].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0.1',\n\t\t\ttransform: 'translateY(100%)',\n\t\t\ttransition: 'all 400ms ease-out 0ms',\n\t\t});\n\n\t\texpect($tiles[5].transform).toHaveBeenCalledWith({\n\t\t\topacity: '0.1',\n\t\t\ttransform: 'translateY(-100%)',\n\t\t\ttransition: 'all 400ms ease-out 300ms',\n\t\t});\n\n\t\texpect(wrapper.vm.totalDuration).toBe(760);\n\t});\n});\n"
  },
  {
    "path": "src/transitions/Zip/Zip.vue",
    "content": "<script setup lang=\"ts\">\n\timport { ref, reactive, type Ref } from 'vue';\n\timport useTransition from '../useTransition';\n\timport { type FluxComponent, FluxGrid } from '../../components';\n\timport type { TransitionZipProps, TransitionZipConf } from './types';\n\timport { Directions } from '../../controllers/Player';\n\n\tconst props = defineProps<TransitionZipProps>();\n\n\tconst $grid: Ref<null | InstanceType<typeof FluxGrid>> = ref(null);\n\n\tconst conf: TransitionZipConf = reactive({\n\t\trows: 1,\n\t\tcols: 10,\n\t\ttileDuration: 600,\n\t\ttileDelay: 80,\n\t\teasing: 'ease-in',\n\t});\n\n\tuseTransition(conf, props.options);\n\n\tconst totalDuration = conf.tileDelay * conf.cols + conf.tileDuration;\n\n\tconst getDelay = {\n\t\t[Directions.prev]: (index: number) => (conf.cols - index - 1) * conf.tileDelay,\n\t\t[Directions.next]: (index: number) => index * conf.tileDelay,\n\t};\n\n\tconst onPlay = () => {\n\t\t$grid.value!.transform((tile: FluxComponent, index: number) => {\n\t\t\tconst transition = `all ${conf.tileDuration}ms ${\n\t\t\t\tconf.easing\n\t\t\t} ${getDelay[conf.direction!](index)}ms`;\n\n\t\t\ttile.transform({\n\t\t\t\ttransition,\n\t\t\t\topacity: '0.1',\n\t\t\t\ttransform: `translateY(${index % 2 ? '-' : ''}100%)`,\n\t\t\t});\n\t\t});\n\t};\n\n\tdefineExpose({\n\t\tonPlay,\n\t\ttotalDuration,\n\t});\n</script>\n\n<template>\n\t<FluxGrid ref=\"$grid\" :rows=\"conf.rows\" :cols=\"conf.cols\" :size=\"size\" :rsc=\"from\" />\n</template>\n"
  },
  {
    "path": "src/transitions/Zip/types.ts",
    "content": "import type { TransitionConf, TransitionOptions, TransitionProps } from '../types';\n\nexport interface TransitionZipOptions extends TransitionOptions {\n\tcols?: number;\n\ttileDuration?: number;\n\ttileDelay?: number;\n}\n\nexport interface TransitionZipProps extends TransitionProps {\n\toptions?: TransitionZipOptions;\n}\n\nexport interface TransitionZipConf extends TransitionConf {\n\trows: number;\n\tcols: number;\n\ttileDuration: number;\n\ttileDelay: number;\n}\n"
  },
  {
    "path": "src/transitions/__test__/AnimationWrapper.ts",
    "content": "import { mount } from '@vue/test-utils';\nimport { type Component, markRaw } from 'vue';\nimport { type FluxComponent, FluxImage } from '../../components';\nimport { Img } from '../../resources';\nimport { Size } from '../../shared';\n\nconst size = markRaw(\n\tnew Size({\n\t\twidth: 640,\n\t\theight: 360,\n\t}),\n);\n\nconst from = new Img('from');\nconst to = new Img('to');\n\nconst maskStyle = {\n\toverflow: 'hidden',\n\tperspective: 'none',\n\tzIndex: 3,\n};\n\nconst displayComponent = mount(markRaw(FluxImage), {\n\tprops: {\n\t\tcolor: '#ccc',\n\t\tsize: size,\n\t},\n});\n\nexport default (component: Component, options: object = {}) => {\n\treturn mount(component, {\n\t\tprops: {\n\t\t\tsize,\n\t\t\tfrom,\n\t\t\tto,\n\t\t\toptions,\n\t\t\tmaskStyle,\n\t\t\tdisplayComponent: displayComponent.vm as FluxComponent,\n\t\t},\n\t});\n};\n"
  },
  {
    "path": "src/transitions/index.ts",
    "content": "export { default as Fade } from './Fade/Fade.vue';\nexport { default as Kenburn } from './Kenburn/Kenburn.vue';\nexport { default as Swipe } from './Swipe/Swipe.vue';\nexport { default as Slide } from './Slide/Slide.vue';\nexport { default as Waterfall } from './Waterfall/Waterfall.vue';\nexport { default as Zip } from './Zip/Zip.vue';\nexport { default as Blinds2D } from './Blinds2D/Blinds2D.vue';\nexport { default as Blocks1 } from './Blocks1/Blocks1.vue';\nexport { default as Blocks2 } from './Blocks2/Blocks2.vue';\nexport { default as Concentric } from './Concentric/Concentric.vue';\nexport { default as Warp } from './Warp/Warp.vue';\nexport { default as Camera } from './Camera/Camera.vue';\nexport { default as Cube } from './Cube/Cube.vue';\nexport { default as Book } from './Book/Book.vue';\nexport { default as Fall } from './Fall/Fall.vue';\nexport { default as Wave } from './Wave/Wave.vue';\nexport { default as Blinds3D } from './Blinds3D/Blinds3D.vue';\nexport { default as Round1 } from './Round1/Round1.vue';\nexport { default as Round2 } from './Round2/Round2.vue';\nexport { default as Explode } from './Explode/Explode.vue';\n\nexport { default as useTransition } from './useTransition';\n\nexport type * from './types';\nexport type * from './Blinds2D/types';\nexport type * from './Blinds3D/types';\nexport type * from './Blocks1/types';\nexport type * from './Blocks2/types';\nexport type * from './Book/types';\nexport type * from './Camera/types';\nexport type * from './Concentric/types';\nexport type * from './Cube/types';\nexport type * from './Explode/types';\nexport type * from './Fade/types';\nexport type * from './Fall/types';\nexport type * from './Kenburn/types';\nexport type * from './Round1/types';\nexport type * from './Round2/types';\nexport type * from './Slide/types';\nexport type * from './Swipe/types';\nexport type * from './Warp/types';\nexport type * from './Waterfall/types';\nexport type * from './Wave/types';\nexport type * from './Zip/types';\n"
  },
  {
    "path": "src/transitions/types.ts",
    "content": "import { Resource } from '../resources';\nimport type { CSSProperties, Component } from 'vue';\nimport type { Direction } from '../controllers/Player';\nimport { Size } from '../shared';\nimport type { FluxComponent } from '../components';\n\nexport interface TransitionProps {\n\tsize: Size;\n\tfrom: Resource;\n\tto?: Resource;\n\toptions?: object;\n\tmaskStyle: CSSProperties;\n\tdisplayComponent: FluxComponent;\n}\n\nexport interface TransitionOptions {\n\tdirection?: Direction;\n\teasing?: CSSProperties['animation-timing-function'];\n}\n\nexport interface TransitionConf {\n\ttotalDuration?: number;\n\tdirection?: Direction;\n\teasing: CSSProperties['animation-timing-function'];\n}\n\nexport type TransitionComponent = Component & {\n\ttotalDuration: number;\n\tonPlay: () => void;\n};\n\nexport interface TransitionWithOptions {\n\tcomponent: Component;\n\toptions: object;\n}\n"
  },
  {
    "path": "src/transitions/useTransition.ts",
    "content": "import { Directions } from '../controllers/Player';\nimport type { TransitionConf } from './types';\n\nexport default function useTransition(conf: TransitionConf, options?: object) {\n\tObject.assign(conf, { direction: Directions.next }, options);\n}\n"
  },
  {
    "path": "tsconfig.app.json",
    "content": "{\n\t\"extends\": \"@vue/tsconfig/tsconfig.dom.json\",\n\t\"include\": [\"env.d.ts\", \"src/**/*\", \"src/**/*.vue\"],\n\t\"exclude\": [\n\t\t\"src/**/*.test.ts\",\n\t\t\"src/**/*.test.tsx\",\n\t\t\"src/**/*.spec.ts\",\n\t\t\"src/**/*.spec.tsx\",\n\t\t\"src/**/__tests__/**\"\n\t],\n\t\"compilerOptions\": {\n\t\t\"tsBuildInfoFile\": \"./node_modules/.tmp/tsconfig.app.tsbuildinfo\",\n\t\t\"paths\": {\n\t\t\t\"@/*\": [\"./src/*\"]\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "tsconfig.build.json",
    "content": "{\n\t\"extends\": \"./tsconfig.app.json\",\n\t\"include\": [\"src/**/*.ts\", \"src/**/*.d.ts\", \"src/**/*.vue\"],\n\t\"exclude\": [\"src/**/__tests__/**\", \"src/**/__mocks__/**\", \"src/playgrounds/**\"],\n\t\"compilerOptions\": {\n\t\t\"types\": [\"vitest/globals\", \"node\", \"jsdom\"]\n\t}\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n\t\"files\": [],\n\t\"references\": [\n\t\t{\n\t\t\t\"path\": \"./tsconfig.node.json\"\n\t\t},\n\t\t{\n\t\t\t\"path\": \"./tsconfig.app.json\"\n\t\t},\n\t\t{\n\t\t\t\"path\": \"./tsconfig.vitest.json\"\n\t\t}\n\t],\n\t\"compilerOptions\": {\n\t\t\"types\": [\"vitest/globals\"]\n\t},\n\t\"exclude\": [\n\t\t\"src/App.vue\",\n\t\t\"src/main.ts\",\n\t\t\"node_modules\",\n\t\t\"dist\",\n\t\t\"src/**/*.test.ts\",\n\t\t\"src/**/*.test.tsx\",\n\t\t\"src/**/*.spec.ts\",\n\t\t\"src/**/*.spec.tsx\"\n\t]\n}\n"
  },
  {
    "path": "tsconfig.node.json",
    "content": "{\n  \"extends\": \"@tsconfig/node22/tsconfig.json\",\n  \"include\": [\n    \"vite.config.*\",\n    \"vitest.config.*\",\n    \"cypress.config.*\",\n    \"nightwatch.conf.*\",\n    \"playwright.config.*\",\n    \"eslint.config.*\"\n  ],\n  \"compilerOptions\": {\n    \"noEmit\": true,\n    \"tsBuildInfoFile\": \"./node_modules/.tmp/tsconfig.node.tsbuildinfo\",\n\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"Bundler\",\n    \"types\": [\"node\"]\n  }\n}\n"
  },
  {
    "path": "tsconfig.vitest.json",
    "content": "{\n\t\"extends\": \"./tsconfig.app.json\",\n\t\"include\": [\"src/**/*.test.ts\", \"env.d.ts\"],\n\t\"exclude\": [],\n\t\"compilerOptions\": {\n\t\t\"tsBuildInfoFile\": \"./node_modules/.tmp/tsconfig.vitest.tsbuildinfo\",\n\t\t\"types\": [\"vitest/globals\", \"node\", \"jsdom\"],\n\t\t\"lib\": []\n\t}\n}\n"
  },
  {
    "path": "vite.config.ts",
    "content": "import { fileURLToPath, URL } from 'node:url';\nimport { resolve } from 'node:path';\n\nimport { defineConfig } from 'vite';\nimport vue from '@vitejs/plugin-vue';\nimport tailwindcss from '@tailwindcss/vite';\nimport vueDevTools from 'vite-plugin-vue-devtools';\nimport dts from 'vite-plugin-dts';\n\n// https://vite.dev/config/\nexport default defineConfig({\n\tplugins: [\n\t\tvue(),\n\t\tvueDevTools(),\n\t\ttailwindcss(),\n\t\tdts({\n\t\t\ttsconfigPath: './tsconfig.build.json',\n\t\t\trollupTypes: true,\n\t\t}),\n\t],\n\tresolve: {\n\t\talias: {\n\t\t\t'@': fileURLToPath(new URL('./src', import.meta.url)),\n\t\t},\n\t},\n\tbuild: {\n\t\tcopyPublicDir: false,\n\t\tlib: {\n\t\t\tentry: resolve(__dirname, 'src/lib.ts'),\n\t\t\tname: 'VueFlux',\n\t\t\tfileName: 'vue-flux',\n\t\t},\n\t\trollupOptions: {\n\t\t\texternal: ['vue'],\n\t\t\toutput: {\n\t\t\t\tglobals: {\n\t\t\t\t\tvue: 'Vue',\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t},\n});\n"
  },
  {
    "path": "vitest.config.ts",
    "content": "import { fileURLToPath } from 'node:url';\nimport { mergeConfig, defineConfig, configDefaults } from 'vitest/config';\nimport viteConfig from './vite.config';\n\nexport default mergeConfig(\n\tviteConfig,\n\tdefineConfig({\n\t\ttest: {\n\t\t\tglobals: true,\n\t\t\tenvironment: 'jsdom',\n\t\t\texclude: [...configDefaults.exclude, 'e2e/**'],\n\t\t\troot: fileURLToPath(new URL('./', import.meta.url)),\n\t\t},\n\t}),\n);\n"
  }
]