mirror of
https://github.com/DavidHDev/vue-bits.git
synced 2026-03-07 06:29:30 -07:00
Add prettier config, format codebase
This commit is contained in:
84
.github/ISSUE_TEMPLATE/1-bug-report.yml
vendored
84
.github/ISSUE_TEMPLATE/1-bug-report.yml
vendored
@@ -1,47 +1,47 @@
|
|||||||
name: 🐞 Bug report
|
name: 🐞 Bug report
|
||||||
description: Help improve Vue Bits.
|
description: Help improve Vue Bits.
|
||||||
labels: ["bug"]
|
labels: ['bug']
|
||||||
title: "[BUG]: "
|
title: '[BUG]: '
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
## Thanks for trying to improve Vue Bits!
|
## Thanks for trying to improve Vue Bits!
|
||||||
Before continuing make sure you have checked other issues to see if your issue has already been reported / addressed.
|
Before continuing make sure you have checked other issues to see if your issue has already been reported / addressed.
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: desc
|
id: desc
|
||||||
attributes:
|
attributes:
|
||||||
label: Describe the issue
|
label: Describe the issue
|
||||||
description: What is happening right now? What is supposed to happen?
|
description: What is happening right now? What is supposed to happen?
|
||||||
placeholder: When I do ..., it does ... but it should do ...
|
placeholder: When I do ..., it does ... but it should do ...
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
## Reproduction
|
## Reproduction
|
||||||
|
|
||||||
Please provide code snippets/screenshots and, if possible/needed, a live environment where your bug can be reproduced.
|
Please provide code snippets/screenshots and, if possible/needed, a live environment where your bug can be reproduced.
|
||||||
- type: input
|
- type: input
|
||||||
id: reproduction-link
|
id: reproduction-link
|
||||||
attributes:
|
attributes:
|
||||||
label: Reproduction Link
|
label: Reproduction Link
|
||||||
description: Link a live environment you used to reproduce.
|
description: Link a live environment you used to reproduce.
|
||||||
placeholder: https://github.com/DavidHDev/vue-bits
|
placeholder: https://github.com/DavidHDev/vue-bits
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: repro-steps
|
id: repro-steps
|
||||||
attributes:
|
attributes:
|
||||||
label: Steps to reproduce
|
label: Steps to reproduce
|
||||||
description: What steps should be taken to reproduce your issue.
|
description: What steps should be taken to reproduce your issue.
|
||||||
validations:
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: checkboxes
|
||||||
|
id: terms
|
||||||
|
attributes:
|
||||||
|
label: Validations
|
||||||
|
description: Please make sure you have checked all of the following.
|
||||||
|
options:
|
||||||
|
- label: I have checked other issues to see if my issue was already reported or addressed
|
||||||
required: true
|
required: true
|
||||||
- type: checkboxes
|
|
||||||
id: terms
|
|
||||||
attributes:
|
|
||||||
label: Validations
|
|
||||||
description: Please make sure you have checked all of the following.
|
|
||||||
options:
|
|
||||||
- label: I have checked other issues to see if my issue was already reported or addressed
|
|
||||||
required: true
|
|
||||||
44
.github/ISSUE_TEMPLATE/2-feature-request.yml
vendored
44
.github/ISSUE_TEMPLATE/2-feature-request.yml
vendored
@@ -1,26 +1,26 @@
|
|||||||
name: 💡 Feature Request
|
name: 💡 Feature Request
|
||||||
description: Suggest something for Vue Bits.
|
description: Suggest something for Vue Bits.
|
||||||
labels: ["enhancement"]
|
labels: ['enhancement']
|
||||||
title: "[FEAT]: "
|
title: '[FEAT]: '
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
## Thanks for trying to improve Vue Bits!
|
## Thanks for trying to improve Vue Bits!
|
||||||
Before continuing make sure you have checked other issues to see if your idea has already been discussed / addressed.
|
Before continuing make sure you have checked other issues to see if your idea has already been discussed / addressed.
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: desc
|
id: desc
|
||||||
attributes:
|
attributes:
|
||||||
label: Share your suggestion
|
label: Share your suggestion
|
||||||
description: What would you like to see in Vue Bits?
|
description: What would you like to see in Vue Bits?
|
||||||
placeholder: I want flying pigs in a component please
|
placeholder: I want flying pigs in a component please
|
||||||
validations:
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: checkboxes
|
||||||
|
id: terms
|
||||||
|
attributes:
|
||||||
|
label: Validations
|
||||||
|
description: Please make sure you have checked all of the following.
|
||||||
|
options:
|
||||||
|
- label: I have checked other issues to see if my issue was already discussed or addressed
|
||||||
required: true
|
required: true
|
||||||
- type: checkboxes
|
|
||||||
id: terms
|
|
||||||
attributes:
|
|
||||||
label: Validations
|
|
||||||
description: Please make sure you have checked all of the following.
|
|
||||||
options:
|
|
||||||
- label: I have checked other issues to see if my issue was already discussed or addressed
|
|
||||||
required: true
|
|
||||||
34
.prettierignore
Normal file
34
.prettierignore
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Dependencies
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Build outputs
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
public/ui/
|
||||||
|
|
||||||
|
# OS files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# IDE files
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Cache
|
||||||
|
.cache/
|
||||||
|
.parcel-cache/
|
||||||
|
.nuxt/
|
||||||
|
.next/
|
||||||
|
.vite/
|
||||||
|
|
||||||
|
# Environment files
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
coverage/
|
||||||
|
*.tsbuildinfo
|
||||||
14
.prettierrc
Normal file
14
.prettierrc
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"printWidth": 120,
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"arrowParens": "avoid",
|
||||||
|
"endOfLine": "lf",
|
||||||
|
"vueIndentScriptAndStyle": false,
|
||||||
|
"htmlWhitespaceSensitivity": "ignore",
|
||||||
|
"bracketSameLine": false,
|
||||||
|
"singleAttributePerLine": false
|
||||||
|
}
|
||||||
@@ -28,9 +28,9 @@ Go to [vue-bits.dev](https://vue-bits.dev/) to view the documentation.
|
|||||||
|
|
||||||
## About
|
## About
|
||||||
|
|
||||||
This is the official Vue port of [React Bits](https://reactbits.dev)!
|
This is the official Vue port of [React Bits](https://reactbits.dev)!
|
||||||
|
|
||||||
Vue Bits is a large collection of animated Vue UI components made to spice up your web creations. We've got animations, components, backgrounds, and awesome stuff that you won't be able to find anywhere else - all free for you to use!
|
Vue Bits is a large collection of animated Vue UI components made to spice up your web creations. We've got animations, components, backgrounds, and awesome stuff that you won't be able to find anywhere else - all free for you to use!
|
||||||
|
|
||||||
These components are all enhanced with customization options as props, to make it easy for you to get exactly what you need.
|
These components are all enhanced with customization options as props, to make it easy for you to get exactly what you need.
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ Please review the [Contribution Guide](https://github.com/DavidHDev/vue-bits/blo
|
|||||||
|
|
||||||
## Stats
|
## Stats
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Sponsorship
|
## Sponsorship
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { globalIgnores } from 'eslint/config'
|
import { globalIgnores } from 'eslint/config';
|
||||||
import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript'
|
import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript';
|
||||||
import pluginVue from 'eslint-plugin-vue'
|
import pluginVue from 'eslint-plugin-vue';
|
||||||
|
|
||||||
export default defineConfigWithVueTs(
|
export default defineConfigWithVueTs(
|
||||||
{
|
{
|
||||||
name: 'app/files-to-lint',
|
name: 'app/files-to-lint',
|
||||||
files: ['**/*.{ts,mts,tsx,vue}'],
|
files: ['**/*.{ts,mts,tsx,vue}']
|
||||||
},
|
},
|
||||||
|
|
||||||
globalIgnores(['**/dist/**', '**/dist-ssr/**', '**/coverage/**']),
|
globalIgnores(['**/dist/**', '**/dist-ssr/**', '**/coverage/**']),
|
||||||
@@ -18,7 +18,7 @@ export default defineConfigWithVueTs(
|
|||||||
files: ['**/*.vue'],
|
files: ['**/*.vue'],
|
||||||
rules: {
|
rules: {
|
||||||
'vue/multi-word-component-names': 'off',
|
'vue/multi-word-component-names': 'off',
|
||||||
'vue/no-reserved-component-names': 'off',
|
'vue/no-reserved-component-names': 'off'
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
)
|
);
|
||||||
|
|||||||
186
index.html
186
index.html
@@ -1,99 +1,107 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en" style="background: #0b0b0b;">
|
<html lang="en" style="background: #0b0b0b">
|
||||||
|
<head>
|
||||||
|
<!-- Basic Meta Tags -->
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta name="theme-color" content="#0b0b0b" />
|
||||||
|
<meta name="author" content="David Haz" />
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="An open source collection of high quality, animated, interactive & fully customizable Vue components for building stunning, memorable user interfaces."
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
name="keywords"
|
||||||
|
content="Vue, Vue tutorials, Vue tips, Vue best practices, Vue development, Vue guides, Vue articles, Vue ecosystem, JavaScript, frontend development, VueJS, UI Library, Component Library, Vue Components, Vue Animations"
|
||||||
|
/>
|
||||||
|
|
||||||
<head>
|
<!-- Font -->
|
||||||
<!-- Basic Meta Tags -->
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
<meta charset="UTF-8" />
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<link
|
||||||
<meta name="theme-color" content="#0b0b0b" />
|
href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&display=swap"
|
||||||
<meta name="author" content="David Haz">
|
rel="stylesheet"
|
||||||
<meta name="description"
|
/>
|
||||||
content="An open source collection of high quality, animated, interactive & fully customizable Vue components for building stunning, memorable user interfaces." />
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
<meta name="keywords"
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
content="Vue, Vue tutorials, Vue tips, Vue best practices, Vue development, Vue guides, Vue articles, Vue ecosystem, JavaScript, frontend development, VueJS, UI Library, Component Library, Vue Components, Vue Animations" />
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,200..800&family=Figtree:ital,wght@0,300..900;1,300..900&family=Figtree:wght@200..800&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Gochi+Hand&display=swap" rel="stylesheet" />
|
||||||
|
|
||||||
<!-- Font -->
|
<!-- Icons -->
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="icon" type="image/svg+xml" sizes="16x16 32x32" href="favicon.ico" />
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="icon" type="image/png" href="favicon-96x96.png" sizes="96x96" />
|
||||||
<link
|
<link rel="shortcut icon" href="favicon.ico" />
|
||||||
href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&display=swap"
|
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||||
rel="stylesheet">
|
<meta name="apple-mobile-web-app-title" content="Vue Bits" />
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="manifest" href="/site.webmanifest" />
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
||||||
<link
|
|
||||||
href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,200..800&family=Figtree:ital,wght@0,300..900;1,300..900&family=Figtree:wght@200..800&display=swap"
|
|
||||||
rel="stylesheet">
|
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Gochi+Hand&display=swap" rel="stylesheet">
|
|
||||||
|
|
||||||
<!-- Icons -->
|
<!-- Open Graph (OG) - Facebook, LinkedIn, etc. -->
|
||||||
<link rel="icon" type="image/svg+xml" sizes="16x16 32x32" href="favicon.ico" />
|
<meta property="og:type" content="website" />
|
||||||
<link rel="icon" type="image/png" href="favicon-96x96.png" sizes="96x96" />
|
<meta property="og:title" content="Vue Bits" />
|
||||||
<link rel="shortcut icon" href="favicon.ico" />
|
<meta
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
property="og:description"
|
||||||
<meta name="apple-mobile-web-app-title" content="Vue Bits" />
|
content="An open source collection of high quality, animated, interactive & fully customizable Vue components for building stunning, memorable user interfaces."
|
||||||
<link rel="manifest" href="/site.webmanifest" />
|
/>
|
||||||
|
<meta property="og:image" content="https://vue-bits.dev/og-pic.png" />
|
||||||
|
<meta property="og:image:alt" content="The Vue Bits landing page design, showcasing the logo and a subtitle!" />
|
||||||
|
<meta property="og:image:width" content="1200" />
|
||||||
|
<meta property="og:image:height" content="630" />
|
||||||
|
<meta property="og:url" content="https://vue-bits.dev" />
|
||||||
|
<meta property="og:site_name" content="Vue Bits" />
|
||||||
|
<meta property="og:locale" content="en_US" />
|
||||||
|
|
||||||
<!-- Open Graph (OG) - Facebook, LinkedIn, etc. -->
|
<!-- Twitter Card - Twitter Sharing -->
|
||||||
<meta property="og:type" content="website">
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
<meta property="og:title" content="Vue Bits">
|
<meta name="twitter:title" content="Vue Bits" />
|
||||||
<meta property="og:description"
|
<meta
|
||||||
content="An open source collection of high quality, animated, interactive & fully customizable Vue components for building stunning, memorable user interfaces.">
|
name="twitter:description"
|
||||||
<meta property="og:image" content="https://vue-bits.dev/og-pic.png">
|
content="An open source collection of high quality, animated, interactive & fully customizable Vue components for building stunning, memorable user interfaces."
|
||||||
<meta property="og:image:alt" content="The Vue Bits landing page design, showcasing the logo and a subtitle!">
|
/>
|
||||||
<meta property="og:image:width" content="1200">
|
<meta name="twitter:image" content="https://vue-bits.dev/og-pic.jpg" />
|
||||||
<meta property="og:image:height" content="630">
|
<meta name="twitter:image:alt" content="The Vue Bits landing page design, showcasing the logo and a subtitle!" />
|
||||||
<meta property="og:url" content="https://vue-bits.dev">
|
|
||||||
<meta property="og:site_name" content="Vue Bits">
|
|
||||||
<meta property="og:locale" content="en_US">
|
|
||||||
|
|
||||||
<!-- Twitter Card - Twitter Sharing -->
|
<!-- Favicon & Apple Touch Icons -->
|
||||||
<meta name="twitter:card" content="summary_large_image">
|
<link rel="icon" href="/favicon.ico" />
|
||||||
<meta name="twitter:title" content="Vue Bits">
|
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||||
<meta name="twitter:description"
|
<link rel="manifest" href="/site.webmanifest" />
|
||||||
content="An open source collection of high quality, animated, interactive & fully customizable Vue components for building stunning, memorable user interfaces.">
|
|
||||||
<meta name="twitter:image" content="https://vue-bits.dev/og-pic.jpg">
|
|
||||||
<meta name="twitter:image:alt" content="The Vue Bits landing page design, showcasing the logo and a subtitle!">
|
|
||||||
|
|
||||||
<!-- Favicon & Apple Touch Icons -->
|
<!-- Canonical & Robots Meta Tags -->
|
||||||
<link rel="icon" href="/favicon.ico">
|
<link rel="canonical" href="https://vue-bits.dev" />
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
|
<meta name="robots" content="index, follow" />
|
||||||
<link rel="manifest" href="/site.webmanifest">
|
|
||||||
|
|
||||||
<!-- Canonical & Robots Meta Tags -->
|
<!-- Structured Data (JSON-LD for SEO) -->
|
||||||
<link rel="canonical" href="https://vue-bits.dev">
|
<script type="application/ld+json">
|
||||||
<meta name="robots" content="index, follow">
|
{
|
||||||
|
"@context": "https://schema.org",
|
||||||
<!-- Structured Data (JSON-LD for SEO) -->
|
"@type": "WebPage",
|
||||||
<script type="application/ld+json">
|
"name": "Vue Bits",
|
||||||
{
|
"url": "https://vue-bits.dev",
|
||||||
"@context": "https://schema.org",
|
"description": "An open source collection of high quality, animated, interactive & fully customizable Vue components for building stunning, memorable user interfaces.",
|
||||||
"@type": "WebPage",
|
"image": "https://vue-bits.dev/og-pic.jpg",
|
||||||
"name": "Vue Bits",
|
"author": {
|
||||||
"url": "https://vue-bits.dev",
|
"@type": "Person",
|
||||||
"description": "An open source collection of high quality, animated, interactive & fully customizable Vue components for building stunning, memorable user interfaces.",
|
"name": "David Haz"
|
||||||
"image": "https://vue-bits.dev/og-pic.jpg",
|
},
|
||||||
"author": {
|
"publisher": {
|
||||||
"@type": "Person",
|
"@type": "Person",
|
||||||
"name": "David Haz"
|
"name": "David Haz",
|
||||||
},
|
"logo": {
|
||||||
"publisher": {
|
"@type": "ImageObject",
|
||||||
"@type": "Person",
|
"url": "https://davidhaz.com"
|
||||||
"name": "David Haz",
|
}
|
||||||
"logo": {
|
}
|
||||||
"@type": "ImageObject",
|
|
||||||
"url": "https://davidhaz.com"
|
|
||||||
}
|
}
|
||||||
}
|
</script>
|
||||||
}
|
<title>Vue Bits - Animated UI Components For Vue</title>
|
||||||
</script>
|
</head>
|
||||||
<title>Vue Bits - Animated UI Components For Vue</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<script type="module" src="/src/main.ts"></script>
|
<script type="module" src="/src/main.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
|
</html>
|
||||||
</html>
|
|
||||||
|
|||||||
@@ -1,37 +1,37 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://unpkg.com/jsrepo@1.30.1/schemas/registry-config.json",
|
"$schema": "https://unpkg.com/jsrepo@1.30.1/schemas/registry-config.json",
|
||||||
"meta": {
|
"meta": {
|
||||||
"authors": ["David Haz"],
|
"authors": ["David Haz"],
|
||||||
"description": "An open source collection of animated, interactive & fully customizable Vue components for building stunning, memorable user interfaces.",
|
"description": "An open source collection of animated, interactive & fully customizable Vue components for building stunning, memorable user interfaces.",
|
||||||
"bugs": "https://github.com/DavidHDev/vue-bits/issues",
|
"bugs": "https://github.com/DavidHDev/vue-bits/issues",
|
||||||
"homepage": "https://vue-bits.dev",
|
"homepage": "https://vue-bits.dev",
|
||||||
"repository": "https://github.com/DavidHDev/vue-bits",
|
"repository": "https://github.com/DavidHDev/vue-bits",
|
||||||
"tags": [
|
"tags": [
|
||||||
"vue",
|
"vue",
|
||||||
"javascript",
|
"javascript",
|
||||||
"components",
|
"components",
|
||||||
"web",
|
"web",
|
||||||
"vuejs",
|
"vuejs",
|
||||||
"css-animations",
|
"css-animations",
|
||||||
"component-library",
|
"component-library",
|
||||||
"ui-components",
|
"ui-components",
|
||||||
"3d",
|
"3d",
|
||||||
"ui-library",
|
"ui-library",
|
||||||
"tailwind",
|
"tailwind",
|
||||||
"tailwindcss",
|
"tailwindcss",
|
||||||
"components",
|
"components",
|
||||||
"components-library"
|
"components-library"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dirs": [],
|
"dirs": [],
|
||||||
"doNotListBlocks": [],
|
"doNotListBlocks": [],
|
||||||
"doNotListCategories": [],
|
"doNotListCategories": [],
|
||||||
"listBlocks": [],
|
"listBlocks": [],
|
||||||
"listCategories": [],
|
"listCategories": [],
|
||||||
"excludeDeps": ["vue"],
|
"excludeDeps": ["vue"],
|
||||||
"includeBlocks": [],
|
"includeBlocks": [],
|
||||||
"includeCategories": [],
|
"includeCategories": [],
|
||||||
"excludeBlocks": [],
|
"excludeBlocks": [],
|
||||||
"excludeCategories": [],
|
"excludeCategories": [],
|
||||||
"preview": true
|
"preview": true
|
||||||
}
|
}
|
||||||
|
|||||||
1
package-lock.json
generated
1
package-lock.json
generated
@@ -41,6 +41,7 @@
|
|||||||
"jsrepo": "^1.30.1",
|
"jsrepo": "^1.30.1",
|
||||||
"npm-run-all2": "^8.0.4",
|
"npm-run-all2": "^8.0.4",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
|
"prettier": "^3.6.2",
|
||||||
"tailwindcss": "^4.1.11",
|
"tailwindcss": "^4.1.11",
|
||||||
"typescript": "~5.8.0",
|
"typescript": "~5.8.0",
|
||||||
"vite": "^7.0.0",
|
"vite": "^7.0.0",
|
||||||
|
|||||||
@@ -11,6 +11,8 @@
|
|||||||
"build-only": "vite build",
|
"build-only": "vite build",
|
||||||
"type-check": "vue-tsc --build",
|
"type-check": "vue-tsc --build",
|
||||||
"lint": "eslint . --fix",
|
"lint": "eslint . --fix",
|
||||||
|
"format": "prettier --write .",
|
||||||
|
"format:check": "prettier --check .",
|
||||||
"new:component": "node scripts/generateComponent.js"
|
"new:component": "node scripts/generateComponent.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -47,6 +49,7 @@
|
|||||||
"jsrepo": "^1.30.1",
|
"jsrepo": "^1.30.1",
|
||||||
"npm-run-all2": "^8.0.4",
|
"npm-run-all2": "^8.0.4",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
|
"prettier": "^3.6.2",
|
||||||
"tailwindcss": "^4.1.11",
|
"tailwindcss": "^4.1.11",
|
||||||
"typescript": "~5.8.0",
|
"typescript": "~5.8.0",
|
||||||
"vite": "^7.0.0",
|
"vite": "^7.0.0",
|
||||||
|
|||||||
@@ -1 +1,11 @@
|
|||||||
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
|
{
|
||||||
|
"name": "",
|
||||||
|
"short_name": "",
|
||||||
|
"icons": [
|
||||||
|
{ "src": "/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" },
|
||||||
|
{ "src": "/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" }
|
||||||
|
],
|
||||||
|
"theme_color": "#ffffff",
|
||||||
|
"background_color": "#ffffff",
|
||||||
|
"display": "standalone"
|
||||||
|
}
|
||||||
|
|||||||
0
scripts/cleanEmptyLines.js
Normal file
0
scripts/cleanEmptyLines.js
Normal file
@@ -1,15 +1,15 @@
|
|||||||
import fs from "fs";
|
import fs from 'fs';
|
||||||
import path from "path";
|
import path from 'path';
|
||||||
import process from "process";
|
import process from 'process';
|
||||||
|
|
||||||
import { fileURLToPath } from "url";
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
const args = process.argv.slice(2);
|
const args = process.argv.slice(2);
|
||||||
if (args.length < 2) {
|
if (args.length < 2) {
|
||||||
console.error("Usage: npm run generate:component <ComponentType> <ComponentName>");
|
console.error('Usage: npm run generate:component <ComponentType> <ComponentName>');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17,12 +17,12 @@ const [componentType, componentName] = args;
|
|||||||
const componentNameLower = componentName.charAt(0).toLowerCase() + componentName.slice(1);
|
const componentNameLower = componentName.charAt(0).toLowerCase() + componentName.slice(1);
|
||||||
|
|
||||||
const paths = {
|
const paths = {
|
||||||
content: path.join(__dirname, "../src/content", componentType, componentName),
|
content: path.join(__dirname, '../src/content', componentType, componentName),
|
||||||
demo: path.join(__dirname, "../src/demo", componentType),
|
demo: path.join(__dirname, '../src/demo', componentType),
|
||||||
constants: path.join(__dirname, "../src/constants/code", componentType),
|
constants: path.join(__dirname, '../src/constants/code', componentType)
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.values(paths).forEach((dir) => {
|
Object.values(paths).forEach(dir => {
|
||||||
if (!fs.existsSync(dir)) {
|
if (!fs.existsSync(dir)) {
|
||||||
fs.mkdirSync(dir, { recursive: true });
|
fs.mkdirSync(dir, { recursive: true });
|
||||||
}
|
}
|
||||||
@@ -31,12 +31,12 @@ Object.values(paths).forEach((dir) => {
|
|||||||
const files = [
|
const files = [
|
||||||
path.join(paths.content, `${componentName}.vue`),
|
path.join(paths.content, `${componentName}.vue`),
|
||||||
path.join(paths.demo, `${componentName}Demo.vue`),
|
path.join(paths.demo, `${componentName}Demo.vue`),
|
||||||
path.join(paths.constants, `${componentNameLower}Code.ts`),
|
path.join(paths.constants, `${componentNameLower}Code.ts`)
|
||||||
];
|
];
|
||||||
|
|
||||||
files.forEach((file) => {
|
files.forEach(file => {
|
||||||
if (!fs.existsSync(file)) {
|
if (!fs.existsSync(file)) {
|
||||||
fs.writeFileSync(file, "");
|
fs.writeFileSync(file, '');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
25
src/App.vue
25
src/App.vue
@@ -1,27 +1,24 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<DisplayHeader
|
<DisplayHeader v-if="!isCategoryPage" :activeItem="activeItem" />
|
||||||
v-if="!isCategoryPage"
|
|
||||||
:activeItem="activeItem"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<router-view />
|
<router-view />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue';
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router';
|
||||||
import DisplayHeader from '@/components/landing/DisplayHeader/DisplayHeader.vue'
|
import DisplayHeader from '@/components/landing/DisplayHeader/DisplayHeader.vue';
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute();
|
||||||
|
|
||||||
const activeItem = computed(() => {
|
const activeItem = computed(() => {
|
||||||
if (route.path === '/') return 'home'
|
if (route.path === '/') return 'home';
|
||||||
return null
|
return null;
|
||||||
})
|
});
|
||||||
|
|
||||||
const isCategoryPage = computed(() => {
|
const isCategoryPage = computed(() => {
|
||||||
return /^\/[^/]+\/[^/]+$/.test(route.path)
|
return /^\/[^/]+\/[^/]+$/.test(route.path);
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -2,12 +2,21 @@
|
|||||||
<div class="cli-installation">
|
<div class="cli-installation">
|
||||||
<h2 class="demo-title">One-Time Installation</h2>
|
<h2 class="demo-title">One-Time Installation</h2>
|
||||||
|
|
||||||
<VCodeBlock v-if="command" :code="command" :persistent-copy-button="true" highlightjs lang="bash" theme="nord"
|
<VCodeBlock
|
||||||
:copy-button="true" class="code-block" />
|
v-if="command"
|
||||||
|
:code="command"
|
||||||
|
:persistent-copy-button="true"
|
||||||
|
highlightjs
|
||||||
|
lang="bash"
|
||||||
|
theme="nord"
|
||||||
|
:copy-button="true"
|
||||||
|
class="code-block"
|
||||||
|
/>
|
||||||
|
|
||||||
<div class="cli-divider"></div>
|
<div class="cli-divider"></div>
|
||||||
|
|
||||||
<h2 class="demo-title">Full CLI Setup</h2>
|
<h2 class="demo-title">Full CLI Setup</h2>
|
||||||
|
|
||||||
<p class="jsrepo-info">
|
<p class="jsrepo-info">
|
||||||
Vue Bits uses
|
Vue Bits uses
|
||||||
<a href="https://jsrepo.dev/" target="_blank" rel="noreferrer">jsrepo</a>
|
<a href="https://jsrepo.dev/" target="_blank" rel="noreferrer">jsrepo</a>
|
||||||
@@ -17,23 +26,57 @@
|
|||||||
<Accordion expandIcon="pi pi-chevron-right" collapseIcon="pi pi-chevron-down">
|
<Accordion expandIcon="pi pi-chevron-right" collapseIcon="pi pi-chevron-down">
|
||||||
<AccordionPanel value="setup">
|
<AccordionPanel value="setup">
|
||||||
<AccordionHeader>Setup Steps</AccordionHeader>
|
<AccordionHeader>Setup Steps</AccordionHeader>
|
||||||
|
|
||||||
<AccordionContent
|
<AccordionContent
|
||||||
:pt="{ transition: { enterFromClass: '', enterActiveClass: '', enterToClass: '', leaveFromClass: '', leaveActiveClass: '', leaveToClass: '' } }">
|
:pt="{
|
||||||
|
transition: {
|
||||||
|
enterFromClass: '',
|
||||||
|
enterActiveClass: '',
|
||||||
|
enterToClass: '',
|
||||||
|
leaveFromClass: '',
|
||||||
|
leaveActiveClass: '',
|
||||||
|
leaveToClass: ''
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
>
|
||||||
<div class="setup-content">
|
<div class="setup-content">
|
||||||
<p class="demo-extra-info">1. Initialize a config file for your project</p>
|
<p class="demo-extra-info">1. Initialize a config file for your project</p>
|
||||||
|
|
||||||
<div class="setup-option">
|
<div class="setup-option">
|
||||||
<VCodeBlock :persistent-copy-button="true" code="npx jsrepo init https://vue-bits.dev/ui" highlightjs
|
<VCodeBlock
|
||||||
lang="bash" theme="nord" :copy-button="true" class="code-block" />
|
:persistent-copy-button="true"
|
||||||
|
code="npx jsrepo init https://vue-bits.dev/ui"
|
||||||
|
highlightjs
|
||||||
|
lang="bash"
|
||||||
|
theme="nord"
|
||||||
|
:copy-button="true"
|
||||||
|
class="code-block"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="demo-extra-info">2. Browse & add components from the list</p>
|
<p class="demo-extra-info">2. Browse & add components from the list</p>
|
||||||
<VCodeBlock :persistent-copy-button="true" code="npx jsrepo add" highlightjs lang="bash" theme="nord"
|
|
||||||
:copy-button="true" class="code-block" />
|
<VCodeBlock
|
||||||
|
:persistent-copy-button="true"
|
||||||
|
code="npx jsrepo add"
|
||||||
|
highlightjs
|
||||||
|
lang="bash"
|
||||||
|
theme="nord"
|
||||||
|
:copy-button="true"
|
||||||
|
class="code-block"
|
||||||
|
/>
|
||||||
|
|
||||||
<p class="demo-extra-info">3. Or just add a specific component</p>
|
<p class="demo-extra-info">3. Or just add a specific component</p>
|
||||||
<VCodeBlock :persistent-copy-button="true" code="npx jsrepo add Animations/AnimatedContainer" highlightjs
|
|
||||||
lang="bash" theme="nord" :copy-button="true" class="code-block" />
|
<VCodeBlock
|
||||||
|
:persistent-copy-button="true"
|
||||||
|
code="npx jsrepo add Animations/AnimatedContainer"
|
||||||
|
highlightjs
|
||||||
|
lang="bash"
|
||||||
|
theme="nord"
|
||||||
|
:copy-button="true"
|
||||||
|
class="code-block"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</AccordionContent>
|
</AccordionContent>
|
||||||
</AccordionPanel>
|
</AccordionPanel>
|
||||||
@@ -42,15 +85,15 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { VCodeBlock } from '@wdns/vue-code-block'
|
import { VCodeBlock } from '@wdns/vue-code-block';
|
||||||
import Accordion from 'primevue/accordion'
|
import Accordion from 'primevue/accordion';
|
||||||
import AccordionPanel from 'primevue/accordionpanel'
|
import AccordionPanel from 'primevue/accordionpanel';
|
||||||
import AccordionHeader from 'primevue/accordionheader'
|
import AccordionHeader from 'primevue/accordionheader';
|
||||||
import AccordionContent from 'primevue/accordioncontent'
|
import AccordionContent from 'primevue/accordioncontent';
|
||||||
|
|
||||||
const { command } = defineProps<{
|
const { command } = defineProps<{
|
||||||
command?: string
|
command?: string;
|
||||||
}>()
|
}>();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -74,7 +117,7 @@ const { command } = defineProps<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
.jsrepo-info a {
|
.jsrepo-info a {
|
||||||
color: #27FF64;
|
color: #27ff64;
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,4 +209,4 @@ const { command } = defineProps<{
|
|||||||
:deep(.v-code-block--me-1) {
|
:deep(.v-code-block--me-1) {
|
||||||
margin-right: 0 !important;
|
margin-right: 0 !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -4,14 +4,25 @@
|
|||||||
<h2 class="demo-title">{{ getDisplayName(name) }}</h2>
|
<h2 class="demo-title">{{ getDisplayName(name) }}</h2>
|
||||||
|
|
||||||
<div v-if="snippet" class="code-container">
|
<div v-if="snippet" class="code-container">
|
||||||
<div class="code-wrapper" :class="{ 'collapsed': shouldCollapse(snippet) && !isExpanded(name) }">
|
<div class="code-wrapper" :class="{ collapsed: shouldCollapse(snippet) && !isExpanded(name) }">
|
||||||
<VCodeBlock :code="snippet" highlightjs :lang="getLanguage(name)" theme="nord" :copy-button="true"
|
<VCodeBlock
|
||||||
:persistent-copy-button="true" class="code-block" />
|
:code="snippet"
|
||||||
|
highlightjs
|
||||||
|
:lang="getLanguage(name)"
|
||||||
|
theme="nord"
|
||||||
|
:copy-button="true"
|
||||||
|
:persistent-copy-button="true"
|
||||||
|
class="code-block"
|
||||||
|
/>
|
||||||
|
|
||||||
<div v-if="shouldCollapse(snippet) && !isExpanded(name)" class="fade-overlay" />
|
<div v-if="shouldCollapse(snippet) && !isExpanded(name)" class="fade-overlay" />
|
||||||
|
|
||||||
<button v-if="shouldCollapse(snippet)" class="expand-button" :class="{ 'expanded': isExpanded(name) }"
|
<button
|
||||||
@click="toggleExpanded(name)">
|
v-if="shouldCollapse(snippet)"
|
||||||
|
class="expand-button"
|
||||||
|
:class="{ expanded: isExpanded(name) }"
|
||||||
|
@click="toggleExpanded(name)"
|
||||||
|
>
|
||||||
{{ isExpanded(name) ? 'Collapse Snippet' : 'See Full Snippet' }}
|
{{ isExpanded(name) ? 'Collapse Snippet' : 'See Full Snippet' }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -19,6 +30,7 @@
|
|||||||
|
|
||||||
<div v-if="!snippet" class="no-code">
|
<div v-if="!snippet" class="no-code">
|
||||||
<span>Nothing here yet!</span>
|
<span>Nothing here yet!</span>
|
||||||
|
|
||||||
<i class="pi pi-face-sad"></i>
|
<i class="pi pi-face-sad"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -26,58 +38,55 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue';
|
||||||
import { VCodeBlock } from '@wdns/vue-code-block'
|
import { VCodeBlock } from '@wdns/vue-code-block';
|
||||||
import type { CodeObject } from '../../types/code'
|
import type { CodeObject } from '../../types/code';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
codeObject: CodeObject
|
codeObject: CodeObject;
|
||||||
}>()
|
}>();
|
||||||
|
|
||||||
const skipKeys = [
|
const skipKeys = ['cli'];
|
||||||
'cli'
|
|
||||||
]
|
|
||||||
|
|
||||||
const expandedSections = ref<Set<string>>(new Set())
|
const expandedSections = ref<Set<string>>(new Set());
|
||||||
|
|
||||||
const codeEntries = computed(() => {
|
const codeEntries = computed(() => {
|
||||||
return Object.entries(props.codeObject).filter(([name]) => !skipKeys.includes(name))
|
return Object.entries(props.codeObject).filter(([name]) => !skipKeys.includes(name));
|
||||||
})
|
});
|
||||||
|
|
||||||
|
|
||||||
const shouldCollapse = (snippet: string) => {
|
const shouldCollapse = (snippet: string) => {
|
||||||
const codeLines = snippet?.split('\n').length || 0
|
const codeLines = snippet?.split('\n').length || 0;
|
||||||
return codeLines > 35
|
return codeLines > 35;
|
||||||
}
|
};
|
||||||
|
|
||||||
const isExpanded = (name: string) => {
|
const isExpanded = (name: string) => {
|
||||||
return expandedSections.value.has(name)
|
return expandedSections.value.has(name);
|
||||||
}
|
};
|
||||||
|
|
||||||
const toggleExpanded = (name: string) => {
|
const toggleExpanded = (name: string) => {
|
||||||
if (expandedSections.value.has(name)) {
|
if (expandedSections.value.has(name)) {
|
||||||
expandedSections.value.delete(name)
|
expandedSections.value.delete(name);
|
||||||
} else {
|
} else {
|
||||||
expandedSections.value.add(name)
|
expandedSections.value.add(name);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const getDisplayName = (name: string) => {
|
const getDisplayName = (name: string) => {
|
||||||
if (name === 'code') return 'Code'
|
if (name === 'code') return 'Code';
|
||||||
if (name === 'cli') return 'CLI Command'
|
if (name === 'cli') return 'CLI Command';
|
||||||
if (name === 'utility') return 'Utility'
|
if (name === 'utility') return 'Utility';
|
||||||
if (name === 'usage') return 'Usage'
|
if (name === 'usage') return 'Usage';
|
||||||
if (name === 'installation') return 'Installation'
|
if (name === 'installation') return 'Installation';
|
||||||
return name.charAt(0).toUpperCase() + name.slice(1)
|
return name.charAt(0).toUpperCase() + name.slice(1);
|
||||||
}
|
};
|
||||||
|
|
||||||
const getLanguage = (name: string) => {
|
const getLanguage = (name: string) => {
|
||||||
if (name === 'cli') return 'bash'
|
if (name === 'cli') return 'bash';
|
||||||
if (name === 'code') return 'html'
|
if (name === 'code') return 'html';
|
||||||
if (name === 'usage') return 'html'
|
if (name === 'usage') return 'html';
|
||||||
if (name === 'installation') return 'bash'
|
if (name === 'installation') return 'bash';
|
||||||
return 'javascript'
|
return 'javascript';
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -1,18 +1,17 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="dependencies-container">
|
<div class="dependencies-container">
|
||||||
<h2 class="demo-title">Dependencies</h2>
|
<h2 class="demo-title">Dependencies</h2>
|
||||||
|
|
||||||
<div class="demo-details">
|
<div class="demo-details">
|
||||||
<span v-for="dep in dependencyList" :key="dep" class="dependency-tag">
|
<span v-for="dep in dependencyList" :key="dep" class="dependency-tag">{{ dep }}</span>
|
||||||
{{ dep }}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
interface Props {
|
interface Props {
|
||||||
dependencyList: string[]
|
dependencyList: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
defineProps<Props>()
|
defineProps<Props>();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,29 +1,33 @@
|
|||||||
<template>
|
<template>
|
||||||
<Button :style="{
|
<Button
|
||||||
fontWeight: 500,
|
:style="{
|
||||||
borderRadius: '0.75rem',
|
fontWeight: 500,
|
||||||
border: '1px solid #142216',
|
borderRadius: '0.75rem',
|
||||||
padding: '1rem',
|
border: '1px solid #142216',
|
||||||
position: 'fixed',
|
padding: '1rem',
|
||||||
right: '2.3em',
|
position: 'fixed',
|
||||||
zIndex: 98,
|
right: '2.3em',
|
||||||
boxShadow: '10px 0 25px rgba(0, 0, 0, 0.2)',
|
zIndex: 98,
|
||||||
transition: '0.3s ease',
|
boxShadow: '10px 0 25px rgba(0, 0, 0, 0.2)',
|
||||||
opacity: visible ? 1 : 0,
|
transition: '0.3s ease',
|
||||||
bottom: visible ? '2.5em' : '1em',
|
opacity: visible ? 1 : 0,
|
||||||
cursor: visible ? 'pointer' : 'default'
|
bottom: visible ? '2.5em' : '1em',
|
||||||
}" class="back-to-top" @click="visible && scrollToTop()">
|
cursor: visible ? 'pointer' : 'default'
|
||||||
<i class="pi pi-arrow-up" style="color: #fff; font-size: 1rem;"></i>
|
}"
|
||||||
|
class="back-to-top"
|
||||||
|
@click="visible && scrollToTop()"
|
||||||
|
>
|
||||||
|
<i class="pi pi-arrow-up" style="color: #fff; font-size: 1rem"></i>
|
||||||
</Button>
|
</Button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onUnmounted } from 'vue'
|
import { ref, onMounted, onUnmounted } from 'vue';
|
||||||
import { useToast } from 'primevue/usetoast'
|
import { useToast } from 'primevue/usetoast';
|
||||||
import Button from 'primevue/button'
|
import Button from 'primevue/button';
|
||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast();
|
||||||
const visible = ref(false)
|
const visible = ref(false);
|
||||||
|
|
||||||
const messages = [
|
const messages = [
|
||||||
'🐴 Country roads, take me home!',
|
'🐴 Country roads, take me home!',
|
||||||
@@ -33,34 +37,33 @@ const messages = [
|
|||||||
'🐉 Fus Ro Dah!',
|
'🐉 Fus Ro Dah!',
|
||||||
'🍄 The princess is in another castle!',
|
'🍄 The princess is in another castle!',
|
||||||
'🦸♂️ Avengers, assemble!',
|
'🦸♂️ Avengers, assemble!',
|
||||||
'🗡️ It\'s dangerous to go alone! Take this.',
|
"🗡️ It's dangerous to go alone! Take this.",
|
||||||
'📜 A wizard is never late.',
|
'📜 A wizard is never late.',
|
||||||
'💍 Foul Tarnished, in search of the Elden Ring!',
|
'💍 Foul Tarnished, in search of the Elden Ring!',
|
||||||
'🐊 See you later, alligator.',
|
'🐊 See you later, alligator.',
|
||||||
'🔥 Dracarys!'
|
'🔥 Dracarys!'
|
||||||
]
|
];
|
||||||
|
|
||||||
const getRandomMessage = (messages: string[]) =>
|
const getRandomMessage = (messages: string[]) => messages[Math.floor(Math.random() * messages.length)];
|
||||||
messages[Math.floor(Math.random() * messages.length)]
|
|
||||||
|
|
||||||
const scrollToTop = () => {
|
const scrollToTop = () => {
|
||||||
window.scrollTo(0, 0)
|
window.scrollTo(0, 0);
|
||||||
toast.add({
|
toast.add({
|
||||||
severity: 'secondary',
|
severity: 'secondary',
|
||||||
summary: getRandomMessage(messages),
|
summary: getRandomMessage(messages),
|
||||||
life: 3000
|
life: 3000
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
const onScroll = () => {
|
const onScroll = () => {
|
||||||
visible.value = window.scrollY > 500
|
visible.value = window.scrollY > 500;
|
||||||
}
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
window.addEventListener('scroll', onScroll)
|
window.addEventListener('scroll', onScroll);
|
||||||
})
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
window.removeEventListener('scroll', onScroll)
|
window.removeEventListener('scroll', onScroll);
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,24 +1,19 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="contribute-container">
|
<div class="contribute-container">
|
||||||
<h2 class="demo-title-contribute">Help improve this component!</h2>
|
<h2 class="demo-title-contribute">Help improve this component!</h2>
|
||||||
|
|
||||||
<div class="contribute-buttons">
|
<div class="contribute-buttons">
|
||||||
<a
|
<a :href="bugReportUrl" target="_blank" rel="noreferrer" class="contribute-button">
|
||||||
:href="bugReportUrl"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
class="contribute-button"
|
|
||||||
>
|
|
||||||
<i class="pi pi-exclamation-triangle"></i>
|
<i class="pi pi-exclamation-triangle"></i>
|
||||||
|
|
||||||
<span>Report an issue</span>
|
<span>Report an issue</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<span class="contribute-separator">or</span>
|
<span class="contribute-separator">or</span>
|
||||||
<a
|
|
||||||
:href="featureRequestUrl"
|
<a :href="featureRequestUrl" target="_blank" rel="noreferrer" class="contribute-button">
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
class="contribute-button"
|
|
||||||
>
|
|
||||||
<i class="pi pi-lightbulb"></i>
|
<i class="pi pi-lightbulb"></i>
|
||||||
|
|
||||||
<span>Request a feature</span>
|
<span>Request a feature</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -26,22 +21,22 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue';
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router';
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute();
|
||||||
|
|
||||||
const bugReportUrl = computed(() => {
|
const bugReportUrl = computed(() => {
|
||||||
const category = route.params.category
|
const category = route.params.category;
|
||||||
const subcategory = route.params.subcategory
|
const subcategory = route.params.subcategory;
|
||||||
const title = encodeURIComponent(`[BUG]: ${category}/${subcategory}`)
|
const title = encodeURIComponent(`[BUG]: ${category}/${subcategory}`);
|
||||||
return `https://github.com/DavidHDev/vue-bits/issues/new?template=1-bug-report.yml&title=${title}&labels=bug`
|
return `https://github.com/DavidHDev/vue-bits/issues/new?template=1-bug-report.yml&title=${title}&labels=bug`;
|
||||||
})
|
});
|
||||||
|
|
||||||
const featureRequestUrl = computed(() => {
|
const featureRequestUrl = computed(() => {
|
||||||
const category = route.params.category
|
const category = route.params.category;
|
||||||
const subcategory = route.params.subcategory
|
const subcategory = route.params.subcategory;
|
||||||
const title = encodeURIComponent(`[FEAT]: ${category}/${subcategory}`)
|
const title = encodeURIComponent(`[FEAT]: ${category}/${subcategory}`);
|
||||||
return `https://github.com/DavidHDev/vue-bits/issues/new?template=2-feature-request.yml&title=${title}&labels=enhancement`
|
return `https://github.com/DavidHDev/vue-bits/issues/new?template=2-feature-request.yml&title=${title}&labels=enhancement`;
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<h2 class="demo-title">Customize</h2>
|
<h2 class="demo-title">Customize</h2>
|
||||||
|
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,28 +1,45 @@
|
|||||||
<template>
|
<template>
|
||||||
<svg width="141" height="30" viewBox="0 0 193 41" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="141" height="30" viewBox="0 0 193 41" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M66.4663 34.2676L56.3843 7.12372H60.5722L68.7929 30.2348L77.0912 7.12372H81.2015L71.1195 34.2676H66.4663Z"
|
<path
|
||||||
fill="white" />
|
d="M66.4663 34.2676L56.3843 7.12372H60.5722L68.7929 30.2348L77.0912 7.12372H81.2015L71.1195 34.2676H66.4663Z"
|
||||||
|
fill="white"
|
||||||
|
/>
|
||||||
|
|
||||||
<path
|
<path
|
||||||
d="M88.3915 34.7329C86.8662 34.7329 85.5349 34.4227 84.3974 33.8023C83.2858 33.1818 82.4198 32.2512 81.7994 31.0103C81.2048 29.7695 80.9075 28.2055 80.9075 26.3183V14.724H84.7852V25.8918C84.7852 27.7272 85.1859 29.1103 85.9873 30.0409C86.7887 30.9716 87.9391 31.4369 89.4384 31.4369C90.4466 31.4369 91.3514 31.1913 92.1528 30.7001C92.9801 30.2089 93.6264 29.498 94.0917 28.5674C94.557 27.6367 94.7897 26.4993 94.7897 25.155V14.724H98.6674V34.2676H95.2162L94.9448 30.9328C94.3502 32.1219 93.4842 33.0526 92.3467 33.7247C91.2092 34.3969 89.8908 34.7329 88.3915 34.7329Z"
|
d="M88.3915 34.7329C86.8662 34.7329 85.5349 34.4227 84.3974 33.8023C83.2858 33.1818 82.4198 32.2512 81.7994 31.0103C81.2048 29.7695 80.9075 28.2055 80.9075 26.3183V14.724H84.7852V25.8918C84.7852 27.7272 85.1859 29.1103 85.9873 30.0409C86.7887 30.9716 87.9391 31.4369 89.4384 31.4369C90.4466 31.4369 91.3514 31.1913 92.1528 30.7001C92.9801 30.2089 93.6264 29.498 94.0917 28.5674C94.557 27.6367 94.7897 26.4993 94.7897 25.155V14.724H98.6674V34.2676H95.2162L94.9448 30.9328C94.3502 32.1219 93.4842 33.0526 92.3467 33.7247C91.2092 34.3969 89.8908 34.7329 88.3915 34.7329Z"
|
||||||
fill="white" />
|
fill="white"
|
||||||
|
/>
|
||||||
|
|
||||||
<path
|
<path
|
||||||
d="M110.648 34.7329C108.787 34.7329 107.133 34.3064 105.685 33.4533C104.237 32.6002 103.1 31.411 102.273 29.8858C101.471 28.3606 101.071 26.5898 101.071 24.5734C101.071 22.5053 101.471 20.7086 102.273 19.1834C103.1 17.6323 104.237 16.4302 105.685 15.5771C107.133 14.6982 108.813 14.2587 110.726 14.2587C112.639 14.2587 114.281 14.6852 115.651 15.5383C117.021 16.3914 118.081 17.5289 118.83 18.9507C119.58 20.3467 119.955 21.8977 119.955 23.6039C119.955 23.8624 119.942 24.1468 119.916 24.457C119.916 24.7414 119.903 25.0645 119.877 25.4265H103.901V22.6733H116.077C116 21.0447 115.457 19.7779 114.449 18.8731C113.44 17.9425 112.187 17.4772 110.687 17.4772C109.627 17.4772 108.658 17.7228 107.779 18.2139C106.9 18.6793 106.189 19.3772 105.646 20.3079C105.129 21.2127 104.871 22.3631 104.871 23.759V24.8448C104.871 26.2925 105.129 27.5204 105.646 28.5286C106.189 29.511 106.9 30.2606 107.779 30.7777C108.658 31.2689 109.614 31.5144 110.648 31.5144C111.889 31.5144 112.91 31.243 113.712 30.7001C114.513 30.1572 115.108 29.4205 115.496 28.4898H119.373C119.037 29.679 118.468 30.7518 117.667 31.7083C116.866 32.639 115.87 33.3757 114.681 33.9186C113.518 34.4615 112.174 34.7329 110.648 34.7329Z"
|
d="M110.648 34.7329C108.787 34.7329 107.133 34.3064 105.685 33.4533C104.237 32.6002 103.1 31.411 102.273 29.8858C101.471 28.3606 101.071 26.5898 101.071 24.5734C101.071 22.5053 101.471 20.7086 102.273 19.1834C103.1 17.6323 104.237 16.4302 105.685 15.5771C107.133 14.6982 108.813 14.2587 110.726 14.2587C112.639 14.2587 114.281 14.6852 115.651 15.5383C117.021 16.3914 118.081 17.5289 118.83 18.9507C119.58 20.3467 119.955 21.8977 119.955 23.6039C119.955 23.8624 119.942 24.1468 119.916 24.457C119.916 24.7414 119.903 25.0645 119.877 25.4265H103.901V22.6733H116.077C116 21.0447 115.457 19.7779 114.449 18.8731C113.44 17.9425 112.187 17.4772 110.687 17.4772C109.627 17.4772 108.658 17.7228 107.779 18.2139C106.9 18.6793 106.189 19.3772 105.646 20.3079C105.129 21.2127 104.871 22.3631 104.871 23.759V24.8448C104.871 26.2925 105.129 27.5204 105.646 28.5286C106.189 29.511 106.9 30.2606 107.779 30.7777C108.658 31.2689 109.614 31.5144 110.648 31.5144C111.889 31.5144 112.91 31.243 113.712 30.7001C114.513 30.1572 115.108 29.4205 115.496 28.4898H119.373C119.037 29.679 118.468 30.7518 117.667 31.7083C116.866 32.639 115.87 33.3757 114.681 33.9186C113.518 34.4615 112.174 34.7329 110.648 34.7329Z"
|
||||||
fill="white" />
|
fill="white"
|
||||||
|
/>
|
||||||
|
|
||||||
<path
|
<path
|
||||||
d="M130.615 34.2676V7.12372H140.658C142.545 7.12372 144.122 7.43394 145.389 8.05437C146.656 8.64895 147.599 9.47619 148.22 10.5361C148.866 11.5701 149.189 12.7464 149.189 14.0648C149.189 15.4349 148.892 16.5853 148.297 17.5159C147.703 18.4466 146.914 19.1704 145.932 19.6875C144.975 20.1786 143.941 20.463 142.83 20.5406L143.373 20.1528C144.562 20.1786 145.648 20.5018 146.63 21.1222C147.612 21.7168 148.388 22.5182 148.956 23.5264C149.525 24.5346 149.81 25.6462 149.81 26.8612C149.81 28.2572 149.474 29.5239 148.801 30.6613C148.129 31.773 147.134 32.6519 145.816 33.2982C144.497 33.9445 142.881 34.2676 140.968 34.2676H130.615ZM134.493 31.0491H140.464C142.171 31.0491 143.489 30.6613 144.42 29.8858C145.376 29.0844 145.854 27.9599 145.854 26.5122C145.854 25.0904 145.363 23.9529 144.381 23.0998C143.424 22.2467 142.093 21.8202 140.387 21.8202H134.493V31.0491ZM134.493 18.8344H140.232C141.86 18.8344 143.101 18.4595 143.954 17.7098C144.807 16.9343 145.234 15.8744 145.234 14.5301C145.234 13.2376 144.807 12.2164 143.954 11.4667C143.101 10.6912 141.822 10.3034 140.115 10.3034H134.493V18.8344Z"
|
d="M130.615 34.2676V7.12372H140.658C142.545 7.12372 144.122 7.43394 145.389 8.05437C146.656 8.64895 147.599 9.47619 148.22 10.5361C148.866 11.5701 149.189 12.7464 149.189 14.0648C149.189 15.4349 148.892 16.5853 148.297 17.5159C147.703 18.4466 146.914 19.1704 145.932 19.6875C144.975 20.1786 143.941 20.463 142.83 20.5406L143.373 20.1528C144.562 20.1786 145.648 20.5018 146.63 21.1222C147.612 21.7168 148.388 22.5182 148.956 23.5264C149.525 24.5346 149.81 25.6462 149.81 26.8612C149.81 28.2572 149.474 29.5239 148.801 30.6613C148.129 31.773 147.134 32.6519 145.816 33.2982C144.497 33.9445 142.881 34.2676 140.968 34.2676H130.615ZM134.493 31.0491H140.464C142.171 31.0491 143.489 30.6613 144.42 29.8858C145.376 29.0844 145.854 27.9599 145.854 26.5122C145.854 25.0904 145.363 23.9529 144.381 23.0998C143.424 22.2467 142.093 21.8202 140.387 21.8202H134.493V31.0491ZM134.493 18.8344H140.232C141.86 18.8344 143.101 18.4595 143.954 17.7098C144.807 16.9343 145.234 15.8744 145.234 14.5301C145.234 13.2376 144.807 12.2164 143.954 11.4667C143.101 10.6912 141.822 10.3034 140.115 10.3034H134.493V18.8344Z"
|
||||||
fill="white" />
|
fill="white"
|
||||||
|
/>
|
||||||
|
|
||||||
<path
|
<path
|
||||||
d="M153.736 34.2676V14.724H157.614V34.2676H153.736ZM155.714 11.0402C154.964 11.0402 154.344 10.8075 153.852 10.3422C153.387 9.87689 153.154 9.2823 153.154 8.55847C153.154 7.86048 153.387 7.29175 153.852 6.85228C154.344 6.38696 154.964 6.1543 155.714 6.1543C156.438 6.1543 157.045 6.38696 157.536 6.85228C158.027 7.29175 158.273 7.86048 158.273 8.55847C158.273 9.2823 158.027 9.87689 157.536 10.3422C157.045 10.8075 156.438 11.0402 155.714 11.0402Z"
|
d="M153.736 34.2676V14.724H157.614V34.2676H153.736ZM155.714 11.0402C154.964 11.0402 154.344 10.8075 153.852 10.3422C153.387 9.87689 153.154 9.2823 153.154 8.55847C153.154 7.86048 153.387 7.29175 153.852 6.85228C154.344 6.38696 154.964 6.1543 155.714 6.1543C156.438 6.1543 157.045 6.38696 157.536 6.85228C158.027 7.29175 158.273 7.86048 158.273 8.55847C158.273 9.2823 158.027 9.87689 157.536 10.3422C157.045 10.8075 156.438 11.0402 155.714 11.0402Z"
|
||||||
fill="white" />
|
fill="white"
|
||||||
|
/>
|
||||||
|
|
||||||
<path
|
<path
|
||||||
d="M170.449 34.2676C169.208 34.2676 168.135 34.0737 167.23 33.6859C166.326 33.2982 165.628 32.6519 165.136 31.7471C164.645 30.8423 164.4 29.6144 164.4 28.0633V18.02H161.026V14.724H164.4L164.865 9.83811H168.277V14.724H173.823V18.02H168.277V28.1021C168.277 29.2137 168.51 29.9763 168.975 30.3899C169.441 30.7777 170.242 30.9716 171.38 30.9716H173.629V34.2676H170.449Z"
|
d="M170.449 34.2676C169.208 34.2676 168.135 34.0737 167.23 33.6859C166.326 33.2982 165.628 32.6519 165.136 31.7471C164.645 30.8423 164.4 29.6144 164.4 28.0633V18.02H161.026V14.724H164.4L164.865 9.83811H168.277V14.724H173.823V18.02H168.277V28.1021C168.277 29.2137 168.51 29.9763 168.975 30.3899C169.441 30.7777 170.242 30.9716 171.38 30.9716H173.629V34.2676H170.449Z"
|
||||||
fill="white" />
|
fill="white"
|
||||||
|
/>
|
||||||
|
|
||||||
<path
|
<path
|
||||||
d="M184.885 34.7329C183.23 34.7329 181.782 34.4615 180.542 33.9186C179.301 33.3757 178.318 32.6131 177.595 31.6308C176.871 30.6484 176.431 29.498 176.276 28.1796H180.231C180.361 28.8 180.606 29.3688 180.968 29.8858C181.356 30.4028 181.873 30.8165 182.519 31.1267C183.191 31.4369 183.98 31.592 184.885 31.592C185.738 31.592 186.436 31.4757 186.979 31.243C187.547 30.9845 187.961 30.6484 188.219 30.2348C188.478 29.7953 188.607 29.33 188.607 28.8388C188.607 28.115 188.426 27.5721 188.064 27.2102C187.728 26.8224 187.211 26.5251 186.513 26.3183C185.841 26.0857 185.027 25.8789 184.07 25.6979C183.165 25.5428 182.287 25.336 181.433 25.0775C180.606 24.7931 179.856 24.4441 179.184 24.0305C178.538 23.6169 178.021 23.0998 177.633 22.4794C177.246 21.8331 177.052 21.0447 177.052 20.114C177.052 19.0024 177.349 18.0071 177.943 17.1282C178.538 16.2234 179.378 15.5254 180.464 15.0342C181.576 14.5172 182.881 14.2587 184.38 14.2587C186.552 14.2587 188.297 14.7757 189.615 15.8098C190.934 16.8438 191.709 18.3044 191.942 20.1916H188.181C188.077 19.3126 187.689 18.6405 187.017 18.1752C186.345 17.684 185.453 17.4384 184.342 17.4384C183.23 17.4384 182.377 17.6581 181.782 18.0976C181.188 18.5371 180.891 19.1187 180.891 19.8426C180.891 20.3079 181.059 20.7215 181.395 21.0834C181.731 21.4453 182.222 21.7556 182.868 22.0141C183.54 22.2467 184.355 22.4665 185.311 22.6733C186.681 22.9318 187.909 23.2549 188.995 23.6427C190.081 24.0305 190.947 24.5992 191.593 25.3489C192.239 26.0986 192.562 27.1714 192.562 28.5674C192.588 29.7824 192.278 30.8552 191.632 31.7859C191.011 32.7165 190.119 33.4404 188.956 33.9574C187.819 34.4744 186.462 34.7329 184.885 34.7329Z"
|
d="M184.885 34.7329C183.23 34.7329 181.782 34.4615 180.542 33.9186C179.301 33.3757 178.318 32.6131 177.595 31.6308C176.871 30.6484 176.431 29.498 176.276 28.1796H180.231C180.361 28.8 180.606 29.3688 180.968 29.8858C181.356 30.4028 181.873 30.8165 182.519 31.1267C183.191 31.4369 183.98 31.592 184.885 31.592C185.738 31.592 186.436 31.4757 186.979 31.243C187.547 30.9845 187.961 30.6484 188.219 30.2348C188.478 29.7953 188.607 29.33 188.607 28.8388C188.607 28.115 188.426 27.5721 188.064 27.2102C187.728 26.8224 187.211 26.5251 186.513 26.3183C185.841 26.0857 185.027 25.8789 184.07 25.6979C183.165 25.5428 182.287 25.336 181.433 25.0775C180.606 24.7931 179.856 24.4441 179.184 24.0305C178.538 23.6169 178.021 23.0998 177.633 22.4794C177.246 21.8331 177.052 21.0447 177.052 20.114C177.052 19.0024 177.349 18.0071 177.943 17.1282C178.538 16.2234 179.378 15.5254 180.464 15.0342C181.576 14.5172 182.881 14.2587 184.38 14.2587C186.552 14.2587 188.297 14.7757 189.615 15.8098C190.934 16.8438 191.709 18.3044 191.942 20.1916H188.181C188.077 19.3126 187.689 18.6405 187.017 18.1752C186.345 17.684 185.453 17.4384 184.342 17.4384C183.23 17.4384 182.377 17.6581 181.782 18.0976C181.188 18.5371 180.891 19.1187 180.891 19.8426C180.891 20.3079 181.059 20.7215 181.395 21.0834C181.731 21.4453 182.222 21.7556 182.868 22.0141C183.54 22.2467 184.355 22.4665 185.311 22.6733C186.681 22.9318 187.909 23.2549 188.995 23.6427C190.081 24.0305 190.947 24.5992 191.593 25.3489C192.239 26.0986 192.562 27.1714 192.562 28.5674C192.588 29.7824 192.278 30.8552 191.632 31.7859C191.011 32.7165 190.119 33.4404 188.956 33.9574C187.819 34.4744 186.462 34.7329 184.885 34.7329Z"
|
||||||
fill="white" />
|
fill="white"
|
||||||
|
/>
|
||||||
|
|
||||||
<path
|
<path
|
||||||
d="M0.345215 0.450195H15.6363C15.6363 0.450195 22.0583 12.3486 26.6885 19.6614C26.8836 19.9694 26.9797 20.151 27.1894 20.4493C27.3852 20.7278 27.4954 20.8853 27.7214 21.1399C27.9339 21.3795 27.8963 21.356 28.1397 21.6249C28.2726 21.7719 28.3875 21.8829 28.6738 22.068C28.933 22.2355 29.377 22.3481 29.7138 22.2345C29.9746 22.1465 30.0451 22.1136 30.2668 21.9894C31.043 21.5543 31.8402 19.9698 31.8402 19.9698L40.7527 3.71523H32.9372V0.450859L46.3206 0.450195L35.594 19.0675C35.594 19.0675 34.5027 20.8176 33.7209 22.1763C33.5935 22.3977 33.0493 23.1986 32.8374 23.4686C32.4369 23.9791 32.2132 24.2841 31.8402 24.6523C31.4828 25.005 31.1302 25.1777 30.8799 25.2783C30.4928 25.4337 30.0788 25.5121 29.663 25.546C29.3087 25.5749 29.0702 25.5934 28.7179 25.546C28.2786 25.4868 28.2472 25.4717 27.7214 25.2783C27.5003 25.197 27.131 24.9776 26.783 24.7266C26.3964 24.4478 26.1328 24.1992 25.6981 23.7395C25.3155 23.3347 25.1467 23.0947 24.8373 22.6316C24.5125 22.1455 24.2598 21.8766 23.953 21.379C19.7839 14.6168 13.8639 3.71523 13.8639 3.71523H5.99112L23.3635 33.9098L26.6885 28.1777C26.6885 28.1777 27.1444 28.7994 28.7438 28.7475C30.2137 28.6998 30.5672 28.1777 30.5672 28.1777L23.3635 40.437L3.53103 6.05657L2.19899 3.71523L0.345215 0.450195Z"
|
d="M0.345215 0.450195H15.6363C15.6363 0.450195 22.0583 12.3486 26.6885 19.6614C26.8836 19.9694 26.9797 20.151 27.1894 20.4493C27.3852 20.7278 27.4954 20.8853 27.7214 21.1399C27.9339 21.3795 27.8963 21.356 28.1397 21.6249C28.2726 21.7719 28.3875 21.8829 28.6738 22.068C28.933 22.2355 29.377 22.3481 29.7138 22.2345C29.9746 22.1465 30.0451 22.1136 30.2668 21.9894C31.043 21.5543 31.8402 19.9698 31.8402 19.9698L40.7527 3.71523H32.9372V0.450859L46.3206 0.450195L35.594 19.0675C35.594 19.0675 34.5027 20.8176 33.7209 22.1763C33.5935 22.3977 33.0493 23.1986 32.8374 23.4686C32.4369 23.9791 32.2132 24.2841 31.8402 24.6523C31.4828 25.005 31.1302 25.1777 30.8799 25.2783C30.4928 25.4337 30.0788 25.5121 29.663 25.546C29.3087 25.5749 29.0702 25.5934 28.7179 25.546C28.2786 25.4868 28.2472 25.4717 27.7214 25.2783C27.5003 25.197 27.131 24.9776 26.783 24.7266C26.3964 24.4478 26.1328 24.1992 25.6981 23.7395C25.3155 23.3347 25.1467 23.0947 24.8373 22.6316C24.5125 22.1455 24.2598 21.8766 23.953 21.379C19.7839 14.6168 13.8639 3.71523 13.8639 3.71523H5.99112L23.3635 33.9098L26.6885 28.1777C26.6885 28.1777 27.1444 28.7994 28.7438 28.7475C30.2137 28.6998 30.5672 28.1777 30.5672 28.1777L23.3635 40.437L3.53103 6.05657L2.19899 3.71523L0.345215 0.450195Z"
|
||||||
fill="white" />
|
fill="white"
|
||||||
|
/>
|
||||||
|
|
||||||
<circle cx="29.3282" cy="10.6089" r="3.34281" fill="white" />
|
<circle cx="29.3282" cy="10.6089" r="3.34281" fill="white" />
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
@@ -30,5 +47,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'VueBitsLogo'
|
name: 'VueBitsLogo'
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,25 +1,26 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="preview-color">
|
<div class="preview-color">
|
||||||
<span class="color-label">{{ title }}</span>
|
<span class="color-label">{{ title }}</span>
|
||||||
|
|
||||||
<input :value="modelValue" @input="handleColorChange" type="color" :disabled="disabled" class="color-input" />
|
<input :value="modelValue" @input="handleColorChange" type="color" :disabled="disabled" class="color-input" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
defineProps<{
|
defineProps<{
|
||||||
title: string
|
title: string;
|
||||||
modelValue: string
|
modelValue: string;
|
||||||
disabled?: boolean
|
disabled?: boolean;
|
||||||
}>()
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
'update:modelValue': [value: string]
|
'update:modelValue': [value: string];
|
||||||
}>()
|
}>();
|
||||||
|
|
||||||
const handleColorChange = (event: Event) => {
|
const handleColorChange = (event: Event) => {
|
||||||
const target = event.target as HTMLInputElement
|
const target = event.target as HTMLInputElement;
|
||||||
emit('update:modelValue', target.value)
|
emit('update:modelValue', target.value);
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="preview-select">
|
<div class="preview-select">
|
||||||
<span class="select-label">{{ title }}</span>
|
<span class="select-label">{{ title }}</span>
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
:model-value="modelValue"
|
:model-value="modelValue"
|
||||||
@update:model-value="handleSelectChange"
|
@update:model-value="handleSelectChange"
|
||||||
@@ -14,45 +15,45 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue';
|
||||||
import Select from 'primevue/select'
|
import Select from 'primevue/select';
|
||||||
|
|
||||||
interface Option {
|
interface Option {
|
||||||
label: string
|
label: string;
|
||||||
value: string | number
|
value: string | number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
title: string
|
title: string;
|
||||||
modelValue: string | number
|
modelValue: string | number;
|
||||||
options: Option[] | string[] | number[]
|
options: Option[] | string[] | number[];
|
||||||
optionLabel?: string
|
optionLabel?: string;
|
||||||
optionValue?: string
|
optionValue?: string;
|
||||||
placeholder?: string
|
placeholder?: string;
|
||||||
disabled?: boolean
|
disabled?: boolean;
|
||||||
}>()
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
'update:modelValue': [value: string | number]
|
'update:modelValue': [value: string | number];
|
||||||
}>()
|
}>();
|
||||||
|
|
||||||
const handleSelectChange = (value: string | number) => {
|
const handleSelectChange = (value: string | number) => {
|
||||||
emit('update:modelValue', value)
|
emit('update:modelValue', value);
|
||||||
}
|
};
|
||||||
|
|
||||||
const isObjectArray = computed(() => {
|
const isObjectArray = computed(() => {
|
||||||
return props.options.length > 0 && typeof props.options[0] === 'object'
|
return props.options.length > 0 && typeof props.options[0] === 'object';
|
||||||
})
|
});
|
||||||
|
|
||||||
const selectAttributes = computed(() => {
|
const selectAttributes = computed(() => {
|
||||||
if (isObjectArray.value) {
|
if (isObjectArray.value) {
|
||||||
return {
|
return {
|
||||||
optionLabel: props.optionLabel || 'label',
|
optionLabel: props.optionLabel || 'label',
|
||||||
optionValue: props.optionValue || 'value'
|
optionValue: props.optionValue || 'value'
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
return {}
|
return {};
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="preview-slider">
|
<div class="preview-slider">
|
||||||
<span class="slider-label">{{ title }}</span>
|
<span class="slider-label">{{ title }}</span>
|
||||||
|
|
||||||
<Slider
|
<Slider
|
||||||
:model-value="modelValue"
|
:model-value="modelValue"
|
||||||
@update:model-value="handleSliderChange"
|
@update:model-value="handleSliderChange"
|
||||||
@@ -10,31 +11,32 @@
|
|||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
class="custom-slider"
|
class="custom-slider"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<span class="slider-value">{{ modelValue }}{{ valueUnit }}</span>
|
<span class="slider-value">{{ modelValue }}{{ valueUnit }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Slider from 'primevue/slider'
|
import Slider from 'primevue/slider';
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
title: string
|
title: string;
|
||||||
modelValue: number
|
modelValue: number;
|
||||||
min?: number
|
min?: number;
|
||||||
max?: number
|
max?: number;
|
||||||
step?: number
|
step?: number;
|
||||||
valueUnit?: string
|
valueUnit?: string;
|
||||||
disabled?: boolean
|
disabled?: boolean;
|
||||||
}>()
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
'update:modelValue': [value: number]
|
'update:modelValue': [value: number];
|
||||||
}>()
|
}>();
|
||||||
|
|
||||||
const handleSliderChange = (value: number | number[]) => {
|
const handleSliderChange = (value: number | number[]) => {
|
||||||
const numValue = Array.isArray(value) ? value[0] : value
|
const numValue = Array.isArray(value) ? value[0] : value;
|
||||||
emit('update:modelValue', numValue)
|
emit('update:modelValue', numValue);
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="preview-switch">
|
<div class="preview-switch">
|
||||||
<span class="switch-label">{{ title }}</span>
|
<span class="switch-label">{{ title }}</span>
|
||||||
|
|
||||||
<ToggleSwitch
|
<ToggleSwitch
|
||||||
:model-value="modelValue"
|
:model-value="modelValue"
|
||||||
@update:model-value="$emit('update:modelValue', $event)"
|
@update:model-value="$emit('update:modelValue', $event)"
|
||||||
@@ -10,17 +11,17 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import ToggleSwitch from 'primevue/toggleswitch'
|
import ToggleSwitch from 'primevue/toggleswitch';
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
title: string
|
title: string;
|
||||||
modelValue: boolean
|
modelValue: boolean;
|
||||||
disabled?: boolean
|
disabled?: boolean;
|
||||||
}>()
|
}>();
|
||||||
|
|
||||||
defineEmits<{
|
defineEmits<{
|
||||||
'update:modelValue': [value: boolean]
|
'update:modelValue': [value: boolean];
|
||||||
}>()
|
}>();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="prop-table-container">
|
<div class="prop-table-container">
|
||||||
<h2 class="demo-title">Props</h2>
|
<h2 class="demo-title">Props</h2>
|
||||||
|
|
||||||
<div class="table-wrapper">
|
<div class="table-wrapper">
|
||||||
<DataTable :value="data" class="props-table">
|
<DataTable :value="data" class="props-table">
|
||||||
<Column field="name" header="Property">
|
<Column field="name" header="Property">
|
||||||
@@ -8,16 +9,19 @@
|
|||||||
<div class="code-cell">{{ data.name }}</div>
|
<div class="code-cell">{{ data.name }}</div>
|
||||||
</template>
|
</template>
|
||||||
</Column>
|
</Column>
|
||||||
|
|
||||||
<Column field="type" header="Type">
|
<Column field="type" header="Type">
|
||||||
<template #body="{ data }">
|
<template #body="{ data }">
|
||||||
<span class="type-text">{{ data.type }}</span>
|
<span class="type-text">{{ data.type }}</span>
|
||||||
</template>
|
</template>
|
||||||
</Column>
|
</Column>
|
||||||
|
|
||||||
<Column field="default" header="Default">
|
<Column field="default" header="Default">
|
||||||
<template #body="{ data }">
|
<template #body="{ data }">
|
||||||
<div class="code-cell">{{ data.default || '—' }}</div>
|
<div class="code-cell">{{ data.default || '—' }}</div>
|
||||||
</template>
|
</template>
|
||||||
</Column>
|
</Column>
|
||||||
|
|
||||||
<Column field="description" header="Description">
|
<Column field="description" header="Description">
|
||||||
<template #body="{ data }">
|
<template #body="{ data }">
|
||||||
<div class="description-text">{{ data.description }}</div>
|
<div class="description-text">{{ data.description }}</div>
|
||||||
@@ -29,19 +33,19 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import DataTable from 'primevue/datatable'
|
import DataTable from 'primevue/datatable';
|
||||||
import Column from 'primevue/column'
|
import Column from 'primevue/column';
|
||||||
|
|
||||||
interface PropData {
|
interface PropData {
|
||||||
name: string
|
name: string;
|
||||||
type: string
|
type: string;
|
||||||
default: string
|
default: string;
|
||||||
description: string
|
description: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
data: PropData[]
|
data: PropData[];
|
||||||
}>()
|
}>();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -5,11 +5,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Button from 'primevue/button'
|
import Button from 'primevue/button';
|
||||||
|
|
||||||
defineEmits<{
|
defineEmits<{
|
||||||
refresh: []
|
refresh: [];
|
||||||
}>()
|
}>();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -5,24 +5,31 @@
|
|||||||
<Tab value="0">
|
<Tab value="0">
|
||||||
<div class="tab-header">
|
<div class="tab-header">
|
||||||
<i class="pi pi-eye"></i>
|
<i class="pi pi-eye"></i>
|
||||||
|
|
||||||
<span>Preview</span>
|
<span>Preview</span>
|
||||||
</div>
|
</div>
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
||||||
<Tab value="1">
|
<Tab value="1">
|
||||||
<div class="tab-header">
|
<div class="tab-header">
|
||||||
<i class="pi pi-code"></i>
|
<i class="pi pi-code"></i>
|
||||||
|
|
||||||
<span>Code</span>
|
<span>Code</span>
|
||||||
</div>
|
</div>
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
||||||
<Tab value="2">
|
<Tab value="2">
|
||||||
<div class="tab-header">
|
<div class="tab-header">
|
||||||
<i class="pi pi-box"></i>
|
<i class="pi pi-box"></i>
|
||||||
|
|
||||||
<span>CLI</span>
|
<span>CLI</span>
|
||||||
</div>
|
</div>
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
||||||
<Tab value="3">
|
<Tab value="3">
|
||||||
<div class="tab-header">
|
<div class="tab-header">
|
||||||
<i class="pi pi-heart"></i>
|
<i class="pi pi-heart"></i>
|
||||||
|
|
||||||
<span>Contribute</span>
|
<span>Contribute</span>
|
||||||
</div>
|
</div>
|
||||||
</Tab>
|
</Tab>
|
||||||
@@ -32,12 +39,15 @@
|
|||||||
<TabPanel value="0">
|
<TabPanel value="0">
|
||||||
<slot name="preview" />
|
<slot name="preview" />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|
||||||
<TabPanel value="1">
|
<TabPanel value="1">
|
||||||
<slot name="code" />
|
<slot name="code" />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|
||||||
<TabPanel value="2">
|
<TabPanel value="2">
|
||||||
<slot name="cli" />
|
<slot name="cli" />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|
||||||
<TabPanel value="3">
|
<TabPanel value="3">
|
||||||
<ContributionSection />
|
<ContributionSection />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
@@ -47,12 +57,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Tabs from 'primevue/tabs'
|
import Tabs from 'primevue/tabs';
|
||||||
import TabList from 'primevue/tablist'
|
import TabList from 'primevue/tablist';
|
||||||
import Tab from 'primevue/tab'
|
import Tab from 'primevue/tab';
|
||||||
import TabPanels from 'primevue/tabpanels'
|
import TabPanels from 'primevue/tabpanels';
|
||||||
import TabPanel from 'primevue/tabpanel'
|
import TabPanel from 'primevue/tabpanel';
|
||||||
import ContributionSection from './ContributionSection.vue'
|
import ContributionSection from './ContributionSection.vue';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -121,8 +131,8 @@ import ContributionSection from './ContributionSection.vue'
|
|||||||
:deep(.p-tab-indicator),
|
:deep(.p-tab-indicator),
|
||||||
:deep(.p-tab::before),
|
:deep(.p-tab::before),
|
||||||
:deep(.p-tab::after),
|
:deep(.p-tab::after),
|
||||||
:deep(.p-tab[aria-selected="true"]::before),
|
:deep(.p-tab[aria-selected='true']::before),
|
||||||
:deep(.p-tab[aria-selected="true"]::after),
|
:deep(.p-tab[aria-selected='true']::after),
|
||||||
:deep(.p-tablist::after),
|
:deep(.p-tablist::after),
|
||||||
:deep(.p-tablist-tab-list::before),
|
:deep(.p-tablist-tab-list::before),
|
||||||
:deep(.p-tablist-tab-list::after),
|
:deep(.p-tablist-tab-list::after),
|
||||||
@@ -131,14 +141,14 @@ import ContributionSection from './ContributionSection.vue'
|
|||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.p-tab[aria-selected="true"]) {
|
:deep(.p-tab[aria-selected='true']) {
|
||||||
background: transparent !important;
|
background: transparent !important;
|
||||||
border-bottom: none !important;
|
border-bottom: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.p-tab[aria-selected="true"] .tab-header) {
|
:deep(.p-tab[aria-selected='true'] .tab-header) {
|
||||||
background: #333333;
|
background: #333333;
|
||||||
color: #A7EF9E;
|
color: #a7ef9e;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.p-tabpanels) {
|
:deep(.p-tabpanels) {
|
||||||
|
|||||||
@@ -77,7 +77,9 @@
|
|||||||
color: inherit;
|
color: inherit;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
transition: opacity 0.3s ease, transform 0.2s ease;
|
transition:
|
||||||
|
opacity 0.3s ease,
|
||||||
|
transform 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-link:hover {
|
.nav-link:hover {
|
||||||
@@ -106,9 +108,7 @@
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
padding: 0 0 0 1.4rem;
|
padding: 0 0 0 1.4rem;
|
||||||
height: calc(60px - 2px);
|
height: calc(60px - 2px);
|
||||||
background: linear-gradient(135deg,
|
background: linear-gradient(135deg, rgb(30, 160, 63), rgba(24, 47, 255, 0.6));
|
||||||
rgb(30, 160, 63),
|
|
||||||
rgba(24, 47, 255, 0.6));
|
|
||||||
background-size: 200% 200%;
|
background-size: 200% 200%;
|
||||||
backdrop-filter: blur(25px);
|
backdrop-filter: blur(25px);
|
||||||
-webkit-backdrop-filter: blur(25px);
|
-webkit-backdrop-filter: blur(25px);
|
||||||
@@ -121,14 +121,14 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
transition: .3s ease;
|
transition: 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cta-button span {
|
.cta-button span {
|
||||||
background-color: #0b0b0b;
|
background-color: #0b0b0b;
|
||||||
margin-left: 1em;
|
margin-left: 1em;
|
||||||
margin-right: calc(1em - 8px);
|
margin-right: calc(1em - 8px);
|
||||||
padding-top: .1em;
|
padding-top: 0.1em;
|
||||||
height: 45px;
|
height: 45px;
|
||||||
border-radius: 50px;
|
border-radius: 50px;
|
||||||
width: 100px;
|
width: 100px;
|
||||||
@@ -143,16 +143,16 @@
|
|||||||
margin-right: 6px;
|
margin-right: 6px;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
transition: .3s ease;
|
transition: 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cta-button:hover {
|
.cta-button:hover {
|
||||||
transition: .3s ease;
|
transition: 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cta-button:hover span img {
|
.cta-button:hover span img {
|
||||||
transform: scale(1.2);
|
transform: scale(1.2);
|
||||||
transition: .3s ease;
|
transition: 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 900px) {
|
@media (max-width: 900px) {
|
||||||
@@ -307,4 +307,4 @@
|
|||||||
.nav-cta-group {
|
.nav-cta-group {
|
||||||
gap: 2rem;
|
gap: 2rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,22 +7,12 @@
|
|||||||
|
|
||||||
<div class="nav-cta-group">
|
<div class="nav-cta-group">
|
||||||
<nav class="landing-nav-items" ref="navRef">
|
<nav class="landing-nav-items" ref="navRef">
|
||||||
<router-link
|
<router-link class="nav-link" :class="{ 'active-link': activeItem === 'home' }" to="/">Home</router-link>
|
||||||
class="nav-link"
|
|
||||||
:class="{ 'active-link': activeItem === 'home' }"
|
<router-link class="nav-link" to="/text-animations/split-text">Docs</router-link>
|
||||||
to="/"
|
|
||||||
>
|
|
||||||
Home
|
|
||||||
</router-link>
|
|
||||||
<router-link class="nav-link" to="/text-animations/split-text">
|
|
||||||
Docs
|
|
||||||
</router-link>
|
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<button
|
<button class="cta-button" @click="openGitHub">
|
||||||
class="cta-button"
|
|
||||||
@click="openGitHub"
|
|
||||||
>
|
|
||||||
Star On GitHub
|
Star On GitHub
|
||||||
<span ref="starCountRef" :style="{ opacity: 0 }">
|
<span ref="starCountRef" :style="{ opacity: 0 }">
|
||||||
<img :src="starIcon" alt="Star Icon" />
|
<img :src="starIcon" alt="Star Icon" />
|
||||||
@@ -35,43 +25,48 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch } from 'vue'
|
import { ref, watch } from 'vue';
|
||||||
import { gsap } from 'gsap'
|
import { gsap } from 'gsap';
|
||||||
import VueBitsLogo from '@/components/common/Logo.vue'
|
import VueBitsLogo from '@/components/common/Logo.vue';
|
||||||
import { useStars } from '@/composables/useStars'
|
import { useStars } from '@/composables/useStars';
|
||||||
import starIcon from '@/assets/common/star.svg'
|
import starIcon from '@/assets/common/star.svg';
|
||||||
import './DisplayHeader.css'
|
import './DisplayHeader.css';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
activeItem?: string | null;
|
activeItem?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
defineProps<Props>()
|
defineProps<Props>();
|
||||||
|
|
||||||
const navRef = ref<HTMLElement | null>(null)
|
const navRef = ref<HTMLElement | null>(null);
|
||||||
const starCountRef = ref<HTMLElement | null>(null)
|
const starCountRef = ref<HTMLElement | null>(null);
|
||||||
const stars = useStars()
|
const stars = useStars();
|
||||||
|
|
||||||
const openGitHub = () => {
|
const openGitHub = () => {
|
||||||
window.open('https://github.com/DavidHDev/vue-bits', '_blank')
|
window.open('https://github.com/DavidHDev/vue-bits', '_blank');
|
||||||
}
|
};
|
||||||
|
|
||||||
watch(stars, (newStars) => {
|
watch(
|
||||||
if (newStars && starCountRef.value) {
|
stars,
|
||||||
gsap.fromTo(starCountRef.value,
|
newStars => {
|
||||||
{
|
if (newStars && starCountRef.value) {
|
||||||
scale: 0,
|
gsap.fromTo(
|
||||||
width: 0,
|
starCountRef.value,
|
||||||
opacity: 0
|
{
|
||||||
},
|
scale: 0,
|
||||||
{
|
width: 0,
|
||||||
scale: 1,
|
opacity: 0
|
||||||
width: "100px",
|
},
|
||||||
opacity: 1,
|
{
|
||||||
duration: 0.8,
|
scale: 1,
|
||||||
ease: "back.out(1)"
|
width: '100px',
|
||||||
}
|
opacity: 1,
|
||||||
)
|
duration: 0.8,
|
||||||
}
|
ease: 'back.out(1)'
|
||||||
}, { immediate: true })
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
letter-spacing: -2px;
|
letter-spacing: -2px;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
margin-bottom: .2rem;
|
margin-bottom: 0.2rem;
|
||||||
background: linear-gradient(135deg, #fff 0%, #60fa89 20%, #55f788 40%, #00ff62 60%, #55f799 80%, #fff 100%);
|
background: linear-gradient(135deg, #fff 0%, #60fa89 20%, #55f788 40%, #00ff62 60%, #55f799 80%, #fff 100%);
|
||||||
background-size: 200% 200%;
|
background-size: 200% 200%;
|
||||||
-webkit-background-clip: text;
|
-webkit-background-clip: text;
|
||||||
@@ -44,7 +44,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@keyframes gradientShift {
|
@keyframes gradientShift {
|
||||||
|
|
||||||
0%,
|
0%,
|
||||||
100% {
|
100% {
|
||||||
background-position: 0% 50%;
|
background-position: 0% 50%;
|
||||||
@@ -120,8 +119,6 @@
|
|||||||
grid-column: 2 / 3;
|
grid-column: 2 / 3;
|
||||||
grid-row: 2 / 3;
|
grid-row: 2 / 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 50rem) {
|
@media (min-width: 50rem) {
|
||||||
@@ -145,8 +142,6 @@
|
|||||||
grid-column: 1 / 3;
|
grid-column: 1 / 3;
|
||||||
grid-row: 2 / 3;
|
grid-row: 2 / 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 768px) and (max-width: 49.99rem) {
|
@media (min-width: 768px) and (max-width: 49.99rem) {
|
||||||
@@ -169,8 +164,6 @@
|
|||||||
grid-column: 1 / 2;
|
grid-column: 1 / 2;
|
||||||
grid-row: 2 / 3;
|
grid-row: 2 / 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.feature-card {
|
.feature-card {
|
||||||
@@ -212,14 +205,20 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
padding: 1px;
|
padding: 1px;
|
||||||
background: radial-gradient(200px circle at var(--glow-x) var(--glow-y),
|
background: radial-gradient(
|
||||||
rgba(132, 0, 255, calc(var(--glow-intensity) * 0.8)) 0%,
|
200px circle at var(--glow-x) var(--glow-y),
|
||||||
rgba(132, 0, 255, calc(var(--glow-intensity) * 0.4)) 30%,
|
rgba(132, 0, 255, calc(var(--glow-intensity) * 0.8)) 0%,
|
||||||
transparent 60%);
|
rgba(132, 0, 255, calc(var(--glow-intensity) * 0.4)) 30%,
|
||||||
|
transparent 60%
|
||||||
|
);
|
||||||
border-radius: inherit;
|
border-radius: inherit;
|
||||||
mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
|
mask:
|
||||||
|
linear-gradient(#fff 0 0) content-box,
|
||||||
|
linear-gradient(#fff 0 0);
|
||||||
mask-composite: subtract;
|
mask-composite: subtract;
|
||||||
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
|
-webkit-mask:
|
||||||
|
linear-gradient(#fff 0 0) content-box,
|
||||||
|
linear-gradient(#fff 0 0);
|
||||||
-webkit-mask-composite: xor;
|
-webkit-mask-composite: xor;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
transition: opacity 0.3s ease;
|
transition: opacity 0.3s ease;
|
||||||
@@ -230,8 +229,6 @@
|
|||||||
background: #07160a;
|
background: #07160a;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.feature-card:hover::before {
|
.feature-card:hover::before {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
@@ -314,7 +311,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.feature-card.particle-container:hover {
|
.feature-card.particle-container:hover {
|
||||||
box-shadow: 0 4px 20px rgba(24, 78, 42, 0.4), 0 0 30px rgba(0, 255, 76, 0.2);
|
box-shadow:
|
||||||
|
0 4px 20px rgba(24, 78, 42, 0.4),
|
||||||
|
0 0 30px rgba(0, 255, 76, 0.2);
|
||||||
background: #07160b;
|
background: #07160b;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -481,8 +480,6 @@
|
|||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@media (max-width: 479px) {
|
@media (max-width: 479px) {
|
||||||
.features-section {
|
.features-section {
|
||||||
padding: 4rem 1rem 2rem;
|
padding: 4rem 1rem 2rem;
|
||||||
@@ -510,8 +507,6 @@
|
|||||||
font-size: 4rem;
|
font-size: 4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.feature-card h3 {
|
.feature-card h3 {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
@@ -553,8 +548,6 @@
|
|||||||
font-size: 4rem;
|
font-size: 4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.feature-card h3 {
|
.feature-card h3 {
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
margin-bottom: 0.4rem;
|
margin-bottom: 0.4rem;
|
||||||
@@ -659,4 +652,4 @@
|
|||||||
.feature-card p {
|
.feature-card p {
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
<div class="features-container">
|
<div class="features-container">
|
||||||
<div class="features-header">
|
<div class="features-header">
|
||||||
<h3 class="features-title">Zero cost, all the cool.</h3>
|
<h3 class="features-title">Zero cost, all the cool.</h3>
|
||||||
|
|
||||||
<p class="features-subtitle">Everything you need to add flair to your websites</p>
|
<p class="features-subtitle">Everything you need to add flair to your websites</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -13,11 +14,16 @@
|
|||||||
<div className="messages-gif-wrapper">
|
<div className="messages-gif-wrapper">
|
||||||
<img src="/assets/messages.gif" alt="Messages animation" className="messages-gif" />
|
<img src="/assets/messages.gif" alt="Messages animation" className="messages-gif" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2>
|
<h2>
|
||||||
<template v-if="isMobile">100</template>
|
<template v-if="isMobile">100</template>
|
||||||
<CountUp v-else :to="100" />%
|
|
||||||
|
<CountUp v-else :to="100" />
|
||||||
|
%
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<h3>Free & Open Source</h3>
|
<h3>Free & Open Source</h3>
|
||||||
|
|
||||||
<p>Loved by developers around the world</p>
|
<p>Loved by developers around the world</p>
|
||||||
</ParticleCard>
|
</ParticleCard>
|
||||||
|
|
||||||
@@ -25,19 +31,24 @@
|
|||||||
<div className="components-gif-wrapper">
|
<div className="components-gif-wrapper">
|
||||||
<img src="/assets/components.gif" alt="Components animation" className="components-gif" />
|
<img src="/assets/components.gif" alt="Components animation" className="components-gif" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2>
|
<h2>
|
||||||
<template v-if="isMobile">40</template>
|
<template v-if="isMobile">40</template>
|
||||||
<CountUp v-else :to="40" />+
|
|
||||||
|
<CountUp v-else :to="40" />
|
||||||
|
+
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<h3>Curated Components</h3>
|
<h3>Curated Components</h3>
|
||||||
|
|
||||||
<p>Growing weekly & only getting better</p>
|
<p>Growing weekly & only getting better</p>
|
||||||
</ParticleCard>
|
</ParticleCard>
|
||||||
|
|
||||||
<ParticleCard class="feature-card card4" :disable-animations="isMobile">
|
<ParticleCard class="feature-card card4" :disable-animations="isMobile">
|
||||||
<h2>
|
<h2>Modern</h2>
|
||||||
Modern
|
|
||||||
</h2>
|
|
||||||
<h3>Technologies</h3>
|
<h3>Technologies</h3>
|
||||||
|
|
||||||
<p>TypeScript + Tailwind, ready to ship</p>
|
<p>TypeScript + Tailwind, ready to ship</p>
|
||||||
</ParticleCard>
|
</ParticleCard>
|
||||||
</div>
|
</div>
|
||||||
@@ -46,26 +57,26 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onUnmounted, defineComponent, h } from 'vue'
|
import { ref, onMounted, onUnmounted, defineComponent, h } from 'vue';
|
||||||
import { gsap } from 'gsap'
|
import { gsap } from 'gsap';
|
||||||
import CountUp from '../../../content/Animations/CountUp/CountUp.vue'
|
import CountUp from '../../../content/Animations/CountUp/CountUp.vue';
|
||||||
import './FeatureCards.css'
|
import './FeatureCards.css';
|
||||||
|
|
||||||
const isMobile = ref(false)
|
const isMobile = ref(false);
|
||||||
const gridRef = ref<HTMLDivElement | null>(null)
|
const gridRef = ref<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
const checkIsMobile = () => {
|
const checkIsMobile = () => {
|
||||||
isMobile.value = window.innerWidth <= 768
|
isMobile.value = window.innerWidth <= 768;
|
||||||
}
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
checkIsMobile()
|
checkIsMobile();
|
||||||
window.addEventListener('resize', checkIsMobile)
|
window.addEventListener('resize', checkIsMobile);
|
||||||
})
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
window.removeEventListener('resize', checkIsMobile)
|
window.removeEventListener('resize', checkIsMobile);
|
||||||
})
|
});
|
||||||
|
|
||||||
const ParticleCard = defineComponent({
|
const ParticleCard = defineComponent({
|
||||||
name: 'ParticleCard',
|
name: 'ParticleCard',
|
||||||
@@ -76,114 +87,119 @@ const ParticleCard = defineComponent({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
setup(props, { slots }) {
|
setup(props, { slots }) {
|
||||||
const cardRef = ref<HTMLDivElement | null>(null)
|
const cardRef = ref<HTMLDivElement | null>(null);
|
||||||
const particlesRef = ref<HTMLDivElement[]>([])
|
const particlesRef = ref<HTMLDivElement[]>([]);
|
||||||
const timeoutsRef = ref<number[]>([])
|
const timeoutsRef = ref<number[]>([]);
|
||||||
const isHoveredRef = ref(false)
|
const isHoveredRef = ref(false);
|
||||||
const memoizedParticles = ref<HTMLDivElement[]>([])
|
const memoizedParticles = ref<HTMLDivElement[]>([]);
|
||||||
const particlesInit = ref(false)
|
const particlesInit = ref(false);
|
||||||
|
|
||||||
const createParticle = (x: number, y: number): HTMLDivElement => {
|
const createParticle = (x: number, y: number): HTMLDivElement => {
|
||||||
const el = document.createElement('div')
|
const el = document.createElement('div');
|
||||||
el.className = 'particle'
|
el.className = 'particle';
|
||||||
el.style.cssText = `
|
el.style.cssText = `
|
||||||
position:absolute;width:4px;height:4px;border-radius:50%;
|
position:absolute;width:4px;height:4px;border-radius:50%;
|
||||||
background:rgba(132,0,255,1);box-shadow:0 0 6px rgba(132,0,255,.6);
|
background:rgba(132,0,255,1);box-shadow:0 0 6px rgba(132,0,255,.6);
|
||||||
pointer-events:none;z-index:100;left:${x}px;top:${y}px;
|
pointer-events:none;z-index:100;left:${x}px;top:${y}px;
|
||||||
`
|
`;
|
||||||
return el
|
return el;
|
||||||
}
|
};
|
||||||
|
|
||||||
const memoizeParticles = () => {
|
const memoizeParticles = () => {
|
||||||
if (particlesInit.value || !cardRef.value) return
|
if (particlesInit.value || !cardRef.value) return;
|
||||||
const { width, height } = cardRef.value.getBoundingClientRect()
|
const { width, height } = cardRef.value.getBoundingClientRect();
|
||||||
Array.from({ length: 12 }).forEach(() => {
|
Array.from({ length: 12 }).forEach(() => {
|
||||||
memoizedParticles.value.push(createParticle(Math.random() * width, Math.random() * height))
|
memoizedParticles.value.push(createParticle(Math.random() * width, Math.random() * height));
|
||||||
})
|
});
|
||||||
particlesInit.value = true
|
particlesInit.value = true;
|
||||||
}
|
};
|
||||||
|
|
||||||
const clearParticles = () => {
|
const clearParticles = () => {
|
||||||
timeoutsRef.value.forEach(clearTimeout)
|
timeoutsRef.value.forEach(clearTimeout);
|
||||||
timeoutsRef.value = []
|
timeoutsRef.value = [];
|
||||||
particlesRef.value.forEach(p =>
|
particlesRef.value.forEach(p =>
|
||||||
gsap.to(p, {
|
gsap.to(p, {
|
||||||
scale: 0,
|
scale: 0,
|
||||||
opacity: 0,
|
opacity: 0,
|
||||||
duration: 0.3,
|
duration: 0.3,
|
||||||
ease: "back.in(1.7)",
|
ease: 'back.in(1.7)',
|
||||||
onComplete: () => {
|
onComplete: () => {
|
||||||
if (p.parentNode) {
|
if (p.parentNode) {
|
||||||
p.parentNode.removeChild(p)
|
p.parentNode.removeChild(p);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
)
|
);
|
||||||
particlesRef.value = []
|
particlesRef.value = [];
|
||||||
}
|
};
|
||||||
|
|
||||||
const animateParticles = () => {
|
const animateParticles = () => {
|
||||||
if (!cardRef.value || !isHoveredRef.value) return
|
if (!cardRef.value || !isHoveredRef.value) return;
|
||||||
if (!particlesInit.value) memoizeParticles()
|
if (!particlesInit.value) memoizeParticles();
|
||||||
|
|
||||||
memoizedParticles.value.forEach((particle, i) => {
|
memoizedParticles.value.forEach((particle, i) => {
|
||||||
const id = setTimeout(() => {
|
const id = setTimeout(() => {
|
||||||
if (!isHoveredRef.value || !cardRef.value) return
|
if (!isHoveredRef.value || !cardRef.value) return;
|
||||||
const clone = particle.cloneNode(true) as HTMLDivElement
|
const clone = particle.cloneNode(true) as HTMLDivElement;
|
||||||
cardRef.value.appendChild(clone)
|
cardRef.value.appendChild(clone);
|
||||||
particlesRef.value.push(clone)
|
particlesRef.value.push(clone);
|
||||||
|
|
||||||
gsap.set(clone, { scale: 0, opacity: 0 })
|
gsap.set(clone, { scale: 0, opacity: 0 });
|
||||||
gsap.to(clone, { scale: 1, opacity: 1, duration: 0.3, ease: "back.out(1.7)" })
|
gsap.to(clone, { scale: 1, opacity: 1, duration: 0.3, ease: 'back.out(1.7)' });
|
||||||
gsap.to(clone, {
|
gsap.to(clone, {
|
||||||
x: (Math.random() - 0.5) * 100,
|
x: (Math.random() - 0.5) * 100,
|
||||||
y: (Math.random() - 0.5) * 100,
|
y: (Math.random() - 0.5) * 100,
|
||||||
rotation: Math.random() * 360,
|
rotation: Math.random() * 360,
|
||||||
duration: 2 + Math.random() * 2,
|
duration: 2 + Math.random() * 2,
|
||||||
ease: "none",
|
ease: 'none',
|
||||||
repeat: -1,
|
repeat: -1,
|
||||||
yoyo: true,
|
yoyo: true
|
||||||
})
|
});
|
||||||
gsap.to(clone, { opacity: 0.3, duration: 1.5, ease: "power2.inOut", repeat: -1, yoyo: true })
|
gsap.to(clone, { opacity: 0.3, duration: 1.5, ease: 'power2.inOut', repeat: -1, yoyo: true });
|
||||||
}, i * 100)
|
}, i * 100);
|
||||||
timeoutsRef.value.push(id)
|
timeoutsRef.value.push(id);
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleMouseEnter = () => {
|
const handleMouseEnter = () => {
|
||||||
isHoveredRef.value = true
|
isHoveredRef.value = true;
|
||||||
animateParticles()
|
animateParticles();
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleMouseLeave = () => {
|
const handleMouseLeave = () => {
|
||||||
isHoveredRef.value = false
|
isHoveredRef.value = false;
|
||||||
clearParticles()
|
clearParticles();
|
||||||
}
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (props.disableAnimations || !cardRef.value) return
|
if (props.disableAnimations || !cardRef.value) return;
|
||||||
|
|
||||||
const node = cardRef.value
|
const node = cardRef.value;
|
||||||
node.addEventListener('mouseenter', handleMouseEnter)
|
node.addEventListener('mouseenter', handleMouseEnter);
|
||||||
node.addEventListener('mouseleave', handleMouseLeave)
|
node.addEventListener('mouseleave', handleMouseLeave);
|
||||||
})
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
if (cardRef.value) {
|
if (cardRef.value) {
|
||||||
cardRef.value.removeEventListener('mouseenter', handleMouseEnter)
|
cardRef.value.removeEventListener('mouseenter', handleMouseEnter);
|
||||||
cardRef.value.removeEventListener('mouseleave', handleMouseLeave)
|
cardRef.value.removeEventListener('mouseleave', handleMouseLeave);
|
||||||
}
|
}
|
||||||
isHoveredRef.value = false
|
isHoveredRef.value = false;
|
||||||
clearParticles()
|
clearParticles();
|
||||||
})
|
});
|
||||||
|
|
||||||
return () => h('div', {
|
return () =>
|
||||||
ref: cardRef,
|
h(
|
||||||
class: 'particle-container',
|
'div',
|
||||||
style: { position: 'relative', overflow: 'hidden' }
|
{
|
||||||
}, slots.default?.())
|
ref: cardRef,
|
||||||
|
class: 'particle-container',
|
||||||
|
style: { position: 'relative', overflow: 'hidden' }
|
||||||
|
},
|
||||||
|
slots.default?.()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
const GlobalSpotlight = defineComponent({
|
const GlobalSpotlight = defineComponent({
|
||||||
name: 'GlobalSpotlight',
|
name: 'GlobalSpotlight',
|
||||||
@@ -198,89 +214,88 @@ const GlobalSpotlight = defineComponent({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const spotlightRef = ref<HTMLDivElement | null>(null)
|
const spotlightRef = ref<HTMLDivElement | null>(null);
|
||||||
const isInsideSectionRef = ref(false)
|
const isInsideSectionRef = ref(false);
|
||||||
|
|
||||||
const handleMouseMove = (e: MouseEvent) => {
|
const handleMouseMove = (e: MouseEvent) => {
|
||||||
if (!spotlightRef.value || !props.gridRef.value) return
|
if (!spotlightRef.value || !props.gridRef.value) return;
|
||||||
const section = props.gridRef.value.closest('.features-section')
|
const section = props.gridRef.value.closest('.features-section');
|
||||||
const rect = section?.getBoundingClientRect()
|
const rect = section?.getBoundingClientRect();
|
||||||
const inside =
|
const inside =
|
||||||
rect &&
|
rect && e.clientX >= rect.left && e.clientX <= rect.right && e.clientY >= rect.top && e.clientY <= rect.bottom;
|
||||||
e.clientX >= rect.left && e.clientX <= rect.right &&
|
|
||||||
e.clientY >= rect.top && e.clientY <= rect.bottom
|
|
||||||
|
|
||||||
isInsideSectionRef.value = inside
|
isInsideSectionRef.value = inside;
|
||||||
const cards = props.gridRef.value.querySelectorAll('.feature-card')
|
const cards = props.gridRef.value.querySelectorAll('.feature-card');
|
||||||
|
|
||||||
if (!inside) {
|
if (!inside) {
|
||||||
gsap.to(spotlightRef.value, { opacity: 0, duration: 0.3, ease: "power2.out" })
|
gsap.to(spotlightRef.value, { opacity: 0, duration: 0.3, ease: 'power2.out' });
|
||||||
cards.forEach((card: HTMLElement) => card.style.setProperty('--glow-intensity', '0'))
|
cards.forEach((card: HTMLElement) => card.style.setProperty('--glow-intensity', '0'));
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let minDist = Infinity
|
let minDist = Infinity;
|
||||||
const prox = 100, fade = 150
|
const prox = 100,
|
||||||
|
fade = 150;
|
||||||
cards.forEach((card: HTMLElement) => {
|
cards.forEach((card: HTMLElement) => {
|
||||||
const r = card.getBoundingClientRect()
|
const r = card.getBoundingClientRect();
|
||||||
const cx = r.left + r.width / 2
|
const cx = r.left + r.width / 2;
|
||||||
const cy = r.top + r.height / 2
|
const cy = r.top + r.height / 2;
|
||||||
const d = Math.hypot(e.clientX - cx, e.clientY - cy) - Math.max(r.width, r.height) / 2
|
const d = Math.hypot(e.clientX - cx, e.clientY - cy) - Math.max(r.width, r.height) / 2;
|
||||||
const ed = Math.max(0, d)
|
const ed = Math.max(0, d);
|
||||||
minDist = Math.min(minDist, ed)
|
minDist = Math.min(minDist, ed);
|
||||||
|
|
||||||
const rx = ((e.clientX - r.left) / r.width) * 100
|
const rx = ((e.clientX - r.left) / r.width) * 100;
|
||||||
const ry = ((e.clientY - r.top) / r.height) * 100
|
const ry = ((e.clientY - r.top) / r.height) * 100;
|
||||||
let glow = 0
|
let glow = 0;
|
||||||
if (ed <= prox) glow = 1
|
if (ed <= prox) glow = 1;
|
||||||
else if (ed <= fade) glow = (fade - ed) / (fade - prox)
|
else if (ed <= fade) glow = (fade - ed) / (fade - prox);
|
||||||
card.style.setProperty('--glow-x', `${rx}%`)
|
card.style.setProperty('--glow-x', `${rx}%`);
|
||||||
card.style.setProperty('--glow-y', `${ry}%`)
|
card.style.setProperty('--glow-y', `${ry}%`);
|
||||||
card.style.setProperty('--glow-intensity', String(glow))
|
card.style.setProperty('--glow-intensity', String(glow));
|
||||||
})
|
});
|
||||||
|
|
||||||
gsap.to(spotlightRef.value, { left: e.clientX, top: e.clientY, duration: 0.1, ease: "power2.out" })
|
gsap.to(spotlightRef.value, { left: e.clientX, top: e.clientY, duration: 0.1, ease: 'power2.out' });
|
||||||
const target = minDist <= prox ? 0.8 : minDist <= fade ? ((fade - minDist) / (fade - prox)) * 0.8 : 0
|
const target = minDist <= prox ? 0.8 : minDist <= fade ? ((fade - minDist) / (fade - prox)) * 0.8 : 0;
|
||||||
gsap.to(spotlightRef.value, { opacity: target, duration: target > 0 ? 0.2 : 0.5, ease: "power2.out" })
|
gsap.to(spotlightRef.value, { opacity: target, duration: target > 0 ? 0.2 : 0.5, ease: 'power2.out' });
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleMouseLeave = () => {
|
const handleMouseLeave = () => {
|
||||||
isInsideSectionRef.value = false
|
isInsideSectionRef.value = false;
|
||||||
props.gridRef.value
|
props.gridRef.value
|
||||||
?.querySelectorAll('.feature-card')
|
?.querySelectorAll('.feature-card')
|
||||||
.forEach((card: HTMLElement) => card.style.setProperty('--glow-intensity', '0'))
|
.forEach((card: HTMLElement) => card.style.setProperty('--glow-intensity', '0'));
|
||||||
if (spotlightRef.value) {
|
if (spotlightRef.value) {
|
||||||
gsap.to(spotlightRef.value, { opacity: 0, duration: 0.3, ease: "power2.out" })
|
gsap.to(spotlightRef.value, { opacity: 0, duration: 0.3, ease: 'power2.out' });
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (props.disableAnimations || !props.gridRef?.value) return
|
if (props.disableAnimations || !props.gridRef?.value) return;
|
||||||
|
|
||||||
const spotlight = document.createElement('div')
|
const spotlight = document.createElement('div');
|
||||||
spotlight.className = 'global-spotlight'
|
spotlight.className = 'global-spotlight';
|
||||||
spotlight.style.cssText = `
|
spotlight.style.cssText = `
|
||||||
position:fixed;width:800px;height:800px;border-radius:50%;pointer-events:none;
|
position:fixed;width:800px;height:800px;border-radius:50%;pointer-events:none;
|
||||||
background:radial-gradient(circle,rgba(132,0,255,.15) 0%,rgba(132,0,255,.08) 15%,
|
background:radial-gradient(circle,rgba(132,0,255,.15) 0%,rgba(132,0,255,.08) 15%,
|
||||||
rgba(132,0,255,.04) 25%,rgba(132,0,255,.02) 40%,rgba(132,0,255,.01) 65%,transparent 70%);
|
rgba(132,0,255,.04) 25%,rgba(132,0,255,.02) 40%,rgba(132,0,255,.01) 65%,transparent 70%);
|
||||||
z-index:200;opacity:0;transform:translate(-50%,-50%);mix-blend-mode:screen;
|
z-index:200;opacity:0;transform:translate(-50%,-50%);mix-blend-mode:screen;
|
||||||
`
|
`;
|
||||||
document.body.appendChild(spotlight)
|
document.body.appendChild(spotlight);
|
||||||
spotlightRef.value = spotlight
|
spotlightRef.value = spotlight;
|
||||||
|
|
||||||
document.addEventListener('mousemove', handleMouseMove)
|
document.addEventListener('mousemove', handleMouseMove);
|
||||||
document.addEventListener('mouseleave', handleMouseLeave)
|
document.addEventListener('mouseleave', handleMouseLeave);
|
||||||
})
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
document.removeEventListener('mousemove', handleMouseMove)
|
document.removeEventListener('mousemove', handleMouseMove);
|
||||||
document.removeEventListener('mouseleave', handleMouseLeave)
|
document.removeEventListener('mouseleave', handleMouseLeave);
|
||||||
if (spotlightRef.value?.parentNode) {
|
if (spotlightRef.value?.parentNode) {
|
||||||
spotlightRef.value.parentNode.removeChild(spotlightRef.value)
|
spotlightRef.value.parentNode.removeChild(spotlightRef.value);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
return () => null
|
return () => null;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -48,13 +48,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.footer-heart {
|
.footer-heart {
|
||||||
color: #27FF64;
|
color: #27ff64;
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer-creator-link {
|
.footer-creator-link {
|
||||||
color: #27FF64;
|
color: #27ff64;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
transition: color 0.2s ease;
|
transition: color 0.2s ease;
|
||||||
}
|
}
|
||||||
@@ -152,4 +152,4 @@
|
|||||||
padding: 0.5rem 0.75rem;
|
padding: 0.5rem 0.75rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,14 @@
|
|||||||
<div class="footer-content">
|
<div class="footer-content">
|
||||||
<div class="footer-left">
|
<div class="footer-left">
|
||||||
<img :src="vueBitsLogo" alt="Vue Bits" class="footer-logo" />
|
<img :src="vueBitsLogo" alt="Vue Bits" class="footer-logo" />
|
||||||
|
|
||||||
<p class="footer-description">
|
<p class="footer-description">
|
||||||
A library created with <i class="pi pi-heart-fill footer-heart"></i> by
|
A library created with
|
||||||
|
<i class="pi pi-heart-fill footer-heart"></i>
|
||||||
|
by
|
||||||
<a href="https://davidhaz.com/" target="_blank" class="footer-creator-link">this guy</a>
|
<a href="https://davidhaz.com/" target="_blank" class="footer-creator-link">this guy</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p class="footer-copyright">© {{ currentYear }} Vue Bits</p>
|
<p class="footer-copyright">© {{ currentYear }} Vue Bits</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -15,15 +19,12 @@
|
|||||||
<a href="https://github.com/DavidHDev/vue-bits" target="_blank" rel="noopener noreferrer" class="footer-link">
|
<a href="https://github.com/DavidHDev/vue-bits" target="_blank" rel="noopener noreferrer" class="footer-link">
|
||||||
GitHub
|
GitHub
|
||||||
</a>
|
</a>
|
||||||
<router-link to="/text-animations/split-text" class="footer-link">
|
|
||||||
Docs
|
<router-link to="/text-animations/split-text" class="footer-link">Docs</router-link>
|
||||||
</router-link>
|
|
||||||
<a href="https://www.jsrepo.com/" target="_blank" class="footer-link">
|
<a href="https://www.jsrepo.com/" target="_blank" class="footer-link">CLI</a>
|
||||||
CLI
|
|
||||||
</a>
|
<a href="https://reactbits.dev/" target="_blank" class="footer-link">React Bits</a>
|
||||||
<a href="https://reactbits.dev/" target="_blank" class="footer-link">
|
|
||||||
React Bits
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
@@ -31,10 +32,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue';
|
||||||
import vueBitsLogo from '../../../assets/logos/vue-bits-logo.svg'
|
import vueBitsLogo from '../../../assets/logos/vue-bits-logo.svg';
|
||||||
import FadeContent from '@/content/Animations/FadeContent/FadeContent.vue'
|
import FadeContent from '@/content/Animations/FadeContent/FadeContent.vue';
|
||||||
import './Footer.css'
|
import './Footer.css';
|
||||||
|
|
||||||
const currentYear = computed(() => new Date().getFullYear())
|
const currentYear = computed(() => new Date().getFullYear());
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -2,18 +2,41 @@
|
|||||||
<div class="landing-content">
|
<div class="landing-content">
|
||||||
<div class="hero-main-content">
|
<div class="hero-main-content">
|
||||||
<h1 class="landing-title">
|
<h1 class="landing-title">
|
||||||
<ResponsiveSplitText :is-mobile="isMobile" text="Animated Vue components" class-name="hero-split"
|
<ResponsiveSplitText
|
||||||
split-type="chars" :delay="30" :duration="2" ease="elastic.out(0.5, 0.3)" />
|
:is-mobile="isMobile"
|
||||||
|
text="Animated Vue components"
|
||||||
|
class-name="hero-split"
|
||||||
|
split-type="chars"
|
||||||
|
:delay="30"
|
||||||
|
:duration="2"
|
||||||
|
ease="elastic.out(0.5, 0.3)"
|
||||||
|
/>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
<ResponsiveSplitText :is-mobile="isMobile" text="for creative developers" class-name="hero-split"
|
|
||||||
split-type="chars" :delay="30" :duration="2" ease="elastic.out(0.5, 0.3)" />
|
<ResponsiveSplitText
|
||||||
|
:is-mobile="isMobile"
|
||||||
|
text="for creative developers"
|
||||||
|
class-name="hero-split"
|
||||||
|
split-type="chars"
|
||||||
|
:delay="30"
|
||||||
|
:duration="2"
|
||||||
|
ease="elastic.out(0.5, 0.3)"
|
||||||
|
/>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<ResponsiveSplitText :is-mobile="isMobile" class-name="landing-subtitle" split-type="words" :delay="10"
|
<ResponsiveSplitText
|
||||||
:duration="1" text="Eighty-plus snippets, ready to be dropped into your Vue projects" />
|
:is-mobile="isMobile"
|
||||||
|
class-name="landing-subtitle"
|
||||||
|
split-type="words"
|
||||||
|
:delay="10"
|
||||||
|
:duration="1"
|
||||||
|
text="Eighty-plus snippets, ready to be dropped into your Vue projects"
|
||||||
|
/>
|
||||||
|
|
||||||
<router-link to="/text-animations/split-text" class="landing-button">
|
<router-link to="/text-animations/split-text" class="landing-button">
|
||||||
<span>Browse Components</span>
|
<span>Browse Components</span>
|
||||||
|
|
||||||
<div class="button-arrow-circle">
|
<div class="button-arrow-circle">
|
||||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="#ffffff" xmlns="http://www.w3.org/2000/svg">
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="#ffffff" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M6 12L10 8L6 4" stroke="#0b0b0b" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
<path d="M6 12L10 8L6 4" stroke="#0b0b0b" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
||||||
@@ -25,8 +48,14 @@
|
|||||||
<div v-if="!isMobile" class="hero-cards-container">
|
<div v-if="!isMobile" class="hero-cards-container">
|
||||||
<div class="hero-card hero-card-1" @click="openUrl('https://vue-bits.dev/backgrounds/dot-grid')">
|
<div class="hero-card hero-card-1" @click="openUrl('https://vue-bits.dev/backgrounds/dot-grid')">
|
||||||
<div class="w-full h-full relative hero-dot-grid">
|
<div class="w-full h-full relative hero-dot-grid">
|
||||||
<DotGrid base-color="#ffffff" active-color="rgba(138, 43, 226, 0.9)" :dot-size="8" :gap="16"
|
<DotGrid
|
||||||
:proximity="50" />
|
base-color="#ffffff"
|
||||||
|
active-color="rgba(138, 43, 226, 0.9)"
|
||||||
|
:dot-size="8"
|
||||||
|
:gap="16"
|
||||||
|
:proximity="50"
|
||||||
|
/>
|
||||||
|
|
||||||
<div class="placeholder-card"></div>
|
<div class="placeholder-card"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -34,11 +63,13 @@
|
|||||||
<div class="hero-cards-row">
|
<div class="hero-cards-row">
|
||||||
<div class="hero-card hero-card-2" @click="openUrl('https://vue-bits.dev/backgrounds/letter-glitch')">
|
<div class="hero-card hero-card-2" @click="openUrl('https://vue-bits.dev/backgrounds/letter-glitch')">
|
||||||
<LetterGlitch class-name="hero-glitch" :glitch-colors="['#ffffff', '#999999', '#333333']" />
|
<LetterGlitch class-name="hero-glitch" :glitch-colors="['#ffffff', '#999999', '#333333']" />
|
||||||
|
|
||||||
<div class="placeholder-card"></div>
|
<div class="placeholder-card"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="hero-card hero-card-3" @click="openUrl('https://vue-bits.dev/backgrounds/squares')">
|
<div class="hero-card hero-card-3" @click="openUrl('https://vue-bits.dev/backgrounds/squares')">
|
||||||
<Squares border-color="#fff" :speed="0.2" direction="diagonal" hover-fill-color="#fff" />
|
<Squares border-color="#fff" :speed="0.2" direction="diagonal" hover-fill-color="#fff" />
|
||||||
|
|
||||||
<div class="placeholder-card"></div>
|
<div class="placeholder-card"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -47,11 +78,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onUnmounted, h, defineComponent } from 'vue'
|
import { ref, onMounted, onUnmounted, h, defineComponent } from 'vue';
|
||||||
import DotGrid from '@/content/Backgrounds/DotGrid/DotGrid.vue'
|
import DotGrid from '@/content/Backgrounds/DotGrid/DotGrid.vue';
|
||||||
import SplitText from '@/content/TextAnimations/SplitText/SplitText.vue'
|
import SplitText from '@/content/TextAnimations/SplitText/SplitText.vue';
|
||||||
import LetterGlitch from '@/content/Backgrounds/LetterGlitch/LetterGlitch.vue'
|
import LetterGlitch from '@/content/Backgrounds/LetterGlitch/LetterGlitch.vue';
|
||||||
import Squares from '@/content/Backgrounds/Squares/Squares.vue'
|
import Squares from '@/content/Backgrounds/Squares/Squares.vue';
|
||||||
|
|
||||||
const ResponsiveSplitText = defineComponent({
|
const ResponsiveSplitText = defineComponent({
|
||||||
props: {
|
props: {
|
||||||
@@ -71,7 +102,7 @@ const ResponsiveSplitText = defineComponent({
|
|||||||
},
|
},
|
||||||
render() {
|
render() {
|
||||||
if (this.isMobile) {
|
if (this.isMobile) {
|
||||||
return h('span', { class: this.className }, this.text)
|
return h('span', { class: this.className }, this.text);
|
||||||
} else {
|
} else {
|
||||||
return h(SplitText, {
|
return h(SplitText, {
|
||||||
text: this.text,
|
text: this.text,
|
||||||
@@ -86,29 +117,29 @@ const ResponsiveSplitText = defineComponent({
|
|||||||
rootMargin: this.rootMargin,
|
rootMargin: this.rootMargin,
|
||||||
textAlign: this.textAlign,
|
textAlign: this.textAlign,
|
||||||
onLetterAnimationComplete: this.onLetterAnimationComplete as (() => void) | undefined
|
onLetterAnimationComplete: this.onLetterAnimationComplete as (() => void) | undefined
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
const openUrl = (url: string) => {
|
const openUrl = (url: string) => {
|
||||||
window.open(url)
|
window.open(url);
|
||||||
}
|
};
|
||||||
|
|
||||||
const isMobile = ref(false)
|
const isMobile = ref(false);
|
||||||
|
|
||||||
const checkIsMobile = () => {
|
const checkIsMobile = () => {
|
||||||
isMobile.value = window.innerWidth <= 768
|
isMobile.value = window.innerWidth <= 768;
|
||||||
}
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
checkIsMobile()
|
checkIsMobile();
|
||||||
window.addEventListener('resize', checkIsMobile)
|
window.addEventListener('resize', checkIsMobile);
|
||||||
})
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
window.removeEventListener('resize', checkIsMobile)
|
window.removeEventListener('resize', checkIsMobile);
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -119,4 +150,4 @@ onUnmounted(() => {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,29 +1,32 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="!isMobile" ref="containerRef" :style="{
|
<div
|
||||||
position: 'absolute',
|
v-if="!isMobile"
|
||||||
inset: 0,
|
ref="containerRef"
|
||||||
overflow: 'hidden',
|
:style="{
|
||||||
width: '100vw',
|
position: 'absolute',
|
||||||
height: '100vh'
|
inset: 0,
|
||||||
}">
|
overflow: 'hidden',
|
||||||
</div>
|
width: '100vw',
|
||||||
|
height: '100vh'
|
||||||
|
}"
|
||||||
|
></div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onUnmounted, watch } from 'vue'
|
import { ref, onMounted, onUnmounted, watch } from 'vue';
|
||||||
import { Renderer, Camera, Transform, Program, Mesh, Geometry } from 'ogl'
|
import { Renderer, Camera, Transform, Program, Mesh, Geometry } from 'ogl';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
xOffset?: number
|
xOffset?: number;
|
||||||
yOffset?: number
|
yOffset?: number;
|
||||||
rotationDeg?: number
|
rotationDeg?: number;
|
||||||
focalLength?: number
|
focalLength?: number;
|
||||||
speed1?: number
|
speed1?: number;
|
||||||
speed2?: number
|
speed2?: number;
|
||||||
dir2?: number
|
dir2?: number;
|
||||||
bend1?: number
|
bend1?: number;
|
||||||
bend2?: number
|
bend2?: number;
|
||||||
fadeInDuration?: number
|
fadeInDuration?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
@@ -37,7 +40,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
bend1: 0.9,
|
bend1: 0.9,
|
||||||
bend2: 0.6,
|
bend2: 0.6,
|
||||||
fadeInDuration: 2000
|
fadeInDuration: 2000
|
||||||
})
|
});
|
||||||
|
|
||||||
const vertex = /* glsl */ `
|
const vertex = /* glsl */ `
|
||||||
attribute vec2 position;
|
attribute vec2 position;
|
||||||
@@ -46,7 +49,7 @@ void main() {
|
|||||||
vUv = position * 0.5 + 0.5;
|
vUv = position * 0.5 + 0.5;
|
||||||
gl_Position = vec4(position, 0.0, 1.0);
|
gl_Position = vec4(position, 0.0, 1.0);
|
||||||
}
|
}
|
||||||
`
|
`;
|
||||||
|
|
||||||
const fragment = /* glsl */ `
|
const fragment = /* glsl */ `
|
||||||
precision mediump float;
|
precision mediump float;
|
||||||
@@ -143,40 +146,40 @@ void main() {
|
|||||||
mainImage(color, coord);
|
mainImage(color, coord);
|
||||||
gl_FragColor = color;
|
gl_FragColor = color;
|
||||||
}
|
}
|
||||||
`
|
`;
|
||||||
|
|
||||||
const isMobile = ref(false)
|
const isMobile = ref(false);
|
||||||
const isVisible = ref(true)
|
const isVisible = ref(true);
|
||||||
const containerRef = ref<HTMLDivElement | null>(null)
|
const containerRef = ref<HTMLDivElement | null>(null);
|
||||||
const uniformOffset = ref(new Float32Array([props.xOffset, props.yOffset]))
|
const uniformOffset = ref(new Float32Array([props.xOffset, props.yOffset]));
|
||||||
const uniformResolution = ref(new Float32Array([1, 1]))
|
const uniformResolution = ref(new Float32Array([1, 1]));
|
||||||
const rendererRef = ref<Renderer | null>(null)
|
const rendererRef = ref<Renderer | null>(null);
|
||||||
const fadeStartTime = ref<number | null>(null)
|
const fadeStartTime = ref<number | null>(null);
|
||||||
const lastTimeRef = ref(0)
|
const lastTimeRef = ref(0);
|
||||||
const pausedTimeRef = ref(0)
|
const pausedTimeRef = ref(0);
|
||||||
const rafId = ref<number | null>(null)
|
const rafId = ref<number | null>(null);
|
||||||
const resizeObserver = ref<ResizeObserver | null>(null)
|
const resizeObserver = ref<ResizeObserver | null>(null);
|
||||||
const intersectionObserver = ref<IntersectionObserver | null>(null)
|
const intersectionObserver = ref<IntersectionObserver | null>(null);
|
||||||
|
|
||||||
const checkIsMobile = () => {
|
const checkIsMobile = () => {
|
||||||
isMobile.value = window.innerWidth <= 768
|
isMobile.value = window.innerWidth <= 768;
|
||||||
}
|
};
|
||||||
|
|
||||||
const resize = () => {
|
const resize = () => {
|
||||||
if (!containerRef.value || !rendererRef.value) return
|
if (!containerRef.value || !rendererRef.value) return;
|
||||||
|
|
||||||
const { width, height } = containerRef.value.getBoundingClientRect()
|
const { width, height } = containerRef.value.getBoundingClientRect();
|
||||||
rendererRef.value.setSize(width, height)
|
rendererRef.value.setSize(width, height);
|
||||||
uniformResolution.value[0] = width * rendererRef.value.dpr
|
uniformResolution.value[0] = width * rendererRef.value.dpr;
|
||||||
uniformResolution.value[1] = height * rendererRef.value.dpr
|
uniformResolution.value[1] = height * rendererRef.value.dpr;
|
||||||
|
|
||||||
const gl = rendererRef.value.gl
|
const gl = rendererRef.value.gl;
|
||||||
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight)
|
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
|
||||||
gl.clear(gl.COLOR_BUFFER_BIT)
|
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||||
}
|
};
|
||||||
|
|
||||||
const initWebGL = () => {
|
const initWebGL = () => {
|
||||||
if (isMobile.value || !containerRef.value) return
|
if (isMobile.value || !containerRef.value) return;
|
||||||
|
|
||||||
const renderer = new Renderer({
|
const renderer = new Renderer({
|
||||||
alpha: true,
|
alpha: true,
|
||||||
@@ -184,20 +187,20 @@ const initWebGL = () => {
|
|||||||
antialias: false,
|
antialias: false,
|
||||||
depth: false,
|
depth: false,
|
||||||
stencil: false,
|
stencil: false,
|
||||||
powerPreference: 'high-performance',
|
powerPreference: 'high-performance'
|
||||||
})
|
});
|
||||||
rendererRef.value = renderer
|
rendererRef.value = renderer;
|
||||||
|
|
||||||
const gl = renderer.gl
|
const gl = renderer.gl;
|
||||||
gl.clearColor(0, 0, 0, 0)
|
gl.clearColor(0, 0, 0, 0);
|
||||||
containerRef.value.appendChild(gl.canvas)
|
containerRef.value.appendChild(gl.canvas);
|
||||||
|
|
||||||
const camera = new Camera(gl)
|
const camera = new Camera(gl);
|
||||||
const scene = new Transform()
|
const scene = new Transform();
|
||||||
|
|
||||||
const geometry = new Geometry(gl, {
|
const geometry = new Geometry(gl, {
|
||||||
position: { size: 2, data: new Float32Array([-1, -1, 3, -1, -1, 3]) },
|
position: { size: 2, data: new Float32Array([-1, -1, 3, -1, -1, 3]) }
|
||||||
})
|
});
|
||||||
|
|
||||||
const program = new Program(gl, {
|
const program = new Program(gl, {
|
||||||
vertex,
|
vertex,
|
||||||
@@ -215,117 +218,117 @@ const initWebGL = () => {
|
|||||||
bend2: { value: props.bend2 },
|
bend2: { value: props.bend2 },
|
||||||
bendAdj1: { value: 0 },
|
bendAdj1: { value: 0 },
|
||||||
bendAdj2: { value: 0 },
|
bendAdj2: { value: 0 },
|
||||||
uOpacity: { value: 0 },
|
uOpacity: { value: 0 }
|
||||||
},
|
}
|
||||||
})
|
});
|
||||||
new Mesh(gl, { geometry, program }).setParent(scene)
|
new Mesh(gl, { geometry, program }).setParent(scene);
|
||||||
|
|
||||||
resize()
|
resize();
|
||||||
|
|
||||||
resizeObserver.value = new ResizeObserver(resize)
|
resizeObserver.value = new ResizeObserver(resize);
|
||||||
resizeObserver.value.observe(containerRef.value)
|
resizeObserver.value.observe(containerRef.value);
|
||||||
|
|
||||||
const loop = (now: number) => {
|
const loop = (now: number) => {
|
||||||
if (isVisible.value) {
|
if (isVisible.value) {
|
||||||
if (lastTimeRef.value === 0) {
|
if (lastTimeRef.value === 0) {
|
||||||
lastTimeRef.value = now - pausedTimeRef.value
|
lastTimeRef.value = now - pausedTimeRef.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
const t = (now - lastTimeRef.value) * 0.001
|
const t = (now - lastTimeRef.value) * 0.001;
|
||||||
|
|
||||||
if (fadeStartTime.value === null && t > 0.1) {
|
if (fadeStartTime.value === null && t > 0.1) {
|
||||||
fadeStartTime.value = now
|
fadeStartTime.value = now;
|
||||||
}
|
}
|
||||||
|
|
||||||
let opacity = 0
|
let opacity = 0;
|
||||||
if (fadeStartTime.value !== null) {
|
if (fadeStartTime.value !== null) {
|
||||||
const fadeElapsed = now - fadeStartTime.value
|
const fadeElapsed = now - fadeStartTime.value;
|
||||||
opacity = Math.min(fadeElapsed / props.fadeInDuration, 1)
|
opacity = Math.min(fadeElapsed / props.fadeInDuration, 1);
|
||||||
opacity = 1 - Math.pow(1 - opacity, 3)
|
opacity = 1 - Math.pow(1 - opacity, 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
uniformOffset.value[0] = props.xOffset
|
uniformOffset.value[0] = props.xOffset;
|
||||||
uniformOffset.value[1] = props.yOffset
|
uniformOffset.value[1] = props.yOffset;
|
||||||
|
|
||||||
program.uniforms.iTime.value = t
|
program.uniforms.iTime.value = t;
|
||||||
program.uniforms.uRotation.value = props.rotationDeg * Math.PI / 180
|
program.uniforms.uRotation.value = (props.rotationDeg * Math.PI) / 180;
|
||||||
program.uniforms.focalLength.value = props.focalLength
|
program.uniforms.focalLength.value = props.focalLength;
|
||||||
program.uniforms.uOpacity.value = opacity
|
program.uniforms.uOpacity.value = opacity;
|
||||||
|
|
||||||
renderer.render({ scene, camera })
|
renderer.render({ scene, camera });
|
||||||
} else {
|
} else {
|
||||||
if (lastTimeRef.value !== 0) {
|
if (lastTimeRef.value !== 0) {
|
||||||
pausedTimeRef.value = now - lastTimeRef.value
|
pausedTimeRef.value = now - lastTimeRef.value;
|
||||||
lastTimeRef.value = 0
|
lastTimeRef.value = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rafId.value = requestAnimationFrame(loop)
|
rafId.value = requestAnimationFrame(loop);
|
||||||
}
|
};
|
||||||
|
|
||||||
rafId.value = requestAnimationFrame(loop)
|
rafId.value = requestAnimationFrame(loop);
|
||||||
}
|
};
|
||||||
|
|
||||||
const setupIntersectionObserver = () => {
|
const setupIntersectionObserver = () => {
|
||||||
if (!containerRef.value || isMobile.value) return
|
if (!containerRef.value || isMobile.value) return;
|
||||||
|
|
||||||
intersectionObserver.value = new IntersectionObserver(
|
intersectionObserver.value = new IntersectionObserver(
|
||||||
([entry]) => {
|
([entry]) => {
|
||||||
isVisible.value = entry.isIntersecting
|
isVisible.value = entry.isIntersecting;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
rootMargin: '50px',
|
rootMargin: '50px',
|
||||||
threshold: 0.1,
|
threshold: 0.1
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
|
|
||||||
intersectionObserver.value.observe(containerRef.value)
|
intersectionObserver.value.observe(containerRef.value);
|
||||||
}
|
};
|
||||||
|
|
||||||
const cleanup = () => {
|
const cleanup = () => {
|
||||||
if (rafId.value) {
|
if (rafId.value) {
|
||||||
cancelAnimationFrame(rafId.value)
|
cancelAnimationFrame(rafId.value);
|
||||||
rafId.value = null
|
rafId.value = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resizeObserver.value) {
|
if (resizeObserver.value) {
|
||||||
resizeObserver.value.disconnect()
|
resizeObserver.value.disconnect();
|
||||||
resizeObserver.value = null
|
resizeObserver.value = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (intersectionObserver.value) {
|
if (intersectionObserver.value) {
|
||||||
intersectionObserver.value.disconnect()
|
intersectionObserver.value.disconnect();
|
||||||
intersectionObserver.value = null
|
intersectionObserver.value = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rendererRef.value) {
|
if (rendererRef.value) {
|
||||||
rendererRef.value.gl.canvas.remove()
|
rendererRef.value.gl.canvas.remove();
|
||||||
rendererRef.value = null
|
rendererRef.value = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.removeEventListener('resize', checkIsMobile)
|
window.removeEventListener('resize', checkIsMobile);
|
||||||
}
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
checkIsMobile()
|
checkIsMobile();
|
||||||
window.addEventListener('resize', checkIsMobile)
|
window.addEventListener('resize', checkIsMobile);
|
||||||
|
|
||||||
if (!isMobile.value) {
|
if (!isMobile.value) {
|
||||||
initWebGL()
|
initWebGL();
|
||||||
setupIntersectionObserver()
|
setupIntersectionObserver();
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
cleanup()
|
cleanup();
|
||||||
})
|
});
|
||||||
|
|
||||||
watch(isMobile, (newIsMobile) => {
|
watch(isMobile, newIsMobile => {
|
||||||
if (newIsMobile) {
|
if (newIsMobile) {
|
||||||
cleanup()
|
cleanup();
|
||||||
} else {
|
} else {
|
||||||
initWebGL()
|
initWebGL();
|
||||||
setupIntersectionObserver()
|
setupIntersectionObserver();
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -17,9 +17,7 @@
|
|||||||
max-width: 1200px;
|
max-width: 1200px;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
background: linear-gradient(135deg,
|
background: linear-gradient(135deg, #3aed6d, rgba(24, 255, 93, 0.6));
|
||||||
#3aed6d,
|
|
||||||
rgba(24, 255, 93, 0.6));
|
|
||||||
background-size: 200% 200%;
|
background-size: 200% 200%;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
padding: 4rem 3rem;
|
padding: 4rem 3rem;
|
||||||
@@ -78,7 +76,7 @@
|
|||||||
background: transparent;
|
background: transparent;
|
||||||
color: #0b0b0b;
|
color: #0b0b0b;
|
||||||
border: 2px solid #0b0b0b;
|
border: 2px solid #0b0b0b;
|
||||||
padding: .6rem 1.6rem;
|
padding: 0.6rem 1.6rem;
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
border-radius: 50px;
|
border-radius: 50px;
|
||||||
@@ -88,7 +86,7 @@
|
|||||||
|
|
||||||
.start-building-button:hover {
|
.start-building-button:hover {
|
||||||
background: #0b0b0b;
|
background: #0b0b0b;
|
||||||
color: #27FF64;
|
color: #27ff64;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1280px) {
|
@media (max-width: 1280px) {
|
||||||
@@ -159,4 +157,4 @@
|
|||||||
padding: 0.75rem 1.5rem;
|
padding: 0.75rem 1.5rem;
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,16 +3,15 @@
|
|||||||
<div class="start-building-container">
|
<div class="start-building-container">
|
||||||
<div class="start-building-card">
|
<div class="start-building-card">
|
||||||
<h2 class="start-building-title">Start exploring Vue Bits</h2>
|
<h2 class="start-building-title">Start exploring Vue Bits</h2>
|
||||||
|
|
||||||
<p class="start-building-subtitle">Animations, components, backgrounds - it's all here</p>
|
<p class="start-building-subtitle">Animations, components, backgrounds - it's all here</p>
|
||||||
|
|
||||||
<router-link to="/text-animations/split-text" class="start-building-button">
|
<router-link to="/text-animations/split-text" class="start-building-button">Browse Components</router-link>
|
||||||
Browse Components
|
|
||||||
</router-link>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import './StartBuilding.css'
|
import './StartBuilding.css';
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,20 +1,24 @@
|
|||||||
<template>
|
<template>
|
||||||
<main class="app-container">
|
<main class="app-container">
|
||||||
<Header />
|
<Header />
|
||||||
|
|
||||||
<section class="category-wrapper">
|
<section class="category-wrapper">
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
|
|
||||||
<div class="category-page">
|
<div class="category-page">
|
||||||
<router-view />
|
<router-view />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<Toast position="bottom-right"
|
|
||||||
|
<Toast
|
||||||
|
position="bottom-right"
|
||||||
:closeButtonProps="{ style: { right: '0', margin: '0', outline: 'none', border: 'none' } }"
|
:closeButtonProps="{ style: { right: '0', margin: '0', outline: 'none', border: 'none' } }"
|
||||||
:pt="{
|
:pt="{
|
||||||
message: {
|
message: {
|
||||||
style: {
|
style: {
|
||||||
borderRadius: '10px',
|
borderRadius: '10px',
|
||||||
border: '1px solid #142216',
|
border: '1px solid #142216',
|
||||||
backgroundColor: '#0b0b0b',
|
backgroundColor: '#0b0b0b'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
messageContent: {
|
messageContent: {
|
||||||
@@ -27,11 +31,12 @@
|
|||||||
display: 'none'
|
display: 'none'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}" />
|
}"
|
||||||
|
/>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Header from '../navs/Header.vue';
|
import Header from '../navs/Header.vue';
|
||||||
import Sidebar from '../navs/Sidebar.vue';
|
import Sidebar from '../navs/Sidebar.vue';
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
<div class="drawer-content" @click.stop>
|
<div class="drawer-content" @click.stop>
|
||||||
<div class="drawer-header">
|
<div class="drawer-header">
|
||||||
<img :src="Logo" alt="Logo" class="drawer-logo" />
|
<img :src="Logo" alt="Logo" class="drawer-logo" />
|
||||||
|
|
||||||
<button class="close-button" aria-label="Close Menu" @click="closeDrawer">
|
<button class="close-button" aria-label="Close Menu" @click="closeDrawer">
|
||||||
<i class="pi pi-times"></i>
|
<i class="pi pi-times"></i>
|
||||||
</button>
|
</button>
|
||||||
@@ -35,12 +36,13 @@
|
|||||||
|
|
||||||
<div class="drawer-body">
|
<div class="drawer-body">
|
||||||
<!-- Navigation Categories -->
|
<!-- Navigation Categories -->
|
||||||
|
|
||||||
<div class="drawer-navigation">
|
<div class="drawer-navigation">
|
||||||
<div class="categories-container">
|
<div class="categories-container">
|
||||||
<Category
|
<Category
|
||||||
v-for="cat in CATEGORIES"
|
v-for="cat in CATEGORIES"
|
||||||
:key="cat.name"
|
:key="cat.name"
|
||||||
:category="cat"
|
:category="cat"
|
||||||
:location="route"
|
:location="route"
|
||||||
:handle-click="onNavClick"
|
:handle-click="onNavClick"
|
||||||
:handle-transition-navigation="handleMobileTransitionNavigation"
|
:handle-transition-navigation="handleMobileTransitionNavigation"
|
||||||
@@ -55,9 +57,9 @@
|
|||||||
|
|
||||||
<div class="drawer-section">
|
<div class="drawer-section">
|
||||||
<p class="section-title">Useful Links</p>
|
<p class="section-title">Useful Links</p>
|
||||||
<router-link to="/text-animations/split-text" @click="closeDrawer" class="drawer-link">
|
|
||||||
Docs
|
<router-link to="/text-animations/split-text" @click="closeDrawer" class="drawer-link">Docs</router-link>
|
||||||
</router-link>
|
|
||||||
<a href="https://github.com/DavidHDev/vue-bits" target="_blank" @click="closeDrawer" class="drawer-link">
|
<a href="https://github.com/DavidHDev/vue-bits" target="_blank" @click="closeDrawer" class="drawer-link">
|
||||||
GitHub
|
GitHub
|
||||||
<i class="pi pi-arrow-up-right arrow-icon"></i>
|
<i class="pi pi-arrow-up-right arrow-icon"></i>
|
||||||
@@ -68,6 +70,7 @@
|
|||||||
|
|
||||||
<div class="drawer-section">
|
<div class="drawer-section">
|
||||||
<p class="section-title">Other</p>
|
<p class="section-title">Other</p>
|
||||||
|
|
||||||
<a href="https://davidhaz.com/" target="_blank" @click="closeDrawer" class="drawer-link">
|
<a href="https://davidhaz.com/" target="_blank" @click="closeDrawer" class="drawer-link">
|
||||||
Who made this?
|
Who made this?
|
||||||
<i class="pi pi-arrow-up-right arrow-icon"></i>
|
<i class="pi pi-arrow-up-right arrow-icon"></i>
|
||||||
@@ -80,58 +83,58 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onUnmounted, computed, defineComponent, h } from 'vue'
|
import { ref, onMounted, onUnmounted, computed, defineComponent, h } from 'vue';
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
import { useStars } from '../../composables/useStars'
|
import { useStars } from '../../composables/useStars';
|
||||||
import { CATEGORIES, NEW, UPDATED } from '../../constants/Categories'
|
import { CATEGORIES, NEW, UPDATED } from '../../constants/Categories';
|
||||||
import FadeContent from '../../content/Animations/FadeContent/FadeContent.vue'
|
import FadeContent from '../../content/Animations/FadeContent/FadeContent.vue';
|
||||||
import Logo from '../../assets/logos/vue-bits-logo.svg'
|
import Logo from '../../assets/logos/vue-bits-logo.svg';
|
||||||
import Star from '../../assets/common/star.svg'
|
import Star from '../../assets/common/star.svg';
|
||||||
|
|
||||||
const isDrawerOpen = ref(false)
|
const isDrawerOpen = ref(false);
|
||||||
const isTransitioning = ref(false)
|
const isTransitioning = ref(false);
|
||||||
const stars = useStars()
|
const stars = useStars();
|
||||||
const route = useRoute()
|
const route = useRoute();
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
|
|
||||||
const slug = (str: string) => str.replace(/\s+/g, "-").toLowerCase()
|
const slug = (str: string) => str.replace(/\s+/g, '-').toLowerCase();
|
||||||
|
|
||||||
const toggleDrawer = () => {
|
const toggleDrawer = () => {
|
||||||
isDrawerOpen.value = !isDrawerOpen.value
|
isDrawerOpen.value = !isDrawerOpen.value;
|
||||||
}
|
};
|
||||||
|
|
||||||
const closeDrawer = () => {
|
const closeDrawer = () => {
|
||||||
isDrawerOpen.value = false
|
isDrawerOpen.value = false;
|
||||||
}
|
};
|
||||||
|
|
||||||
const openGitHub = () => {
|
const openGitHub = () => {
|
||||||
window.open('https://github.com/DavidHDev/vue-bits', '_blank')
|
window.open('https://github.com/DavidHDev/vue-bits', '_blank');
|
||||||
}
|
};
|
||||||
|
|
||||||
const onNavClick = () => {
|
const onNavClick = () => {
|
||||||
closeDrawer()
|
closeDrawer();
|
||||||
window.scrollTo(0, 0)
|
window.scrollTo(0, 0);
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleMobileTransitionNavigation = async (path: string) => {
|
const handleMobileTransitionNavigation = async (path: string) => {
|
||||||
if (isTransitioning.value || route.path === path) return
|
if (isTransitioning.value || route.path === path) return;
|
||||||
|
|
||||||
closeDrawer()
|
closeDrawer();
|
||||||
isTransitioning.value = true
|
isTransitioning.value = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await router.push(path)
|
await router.push(path);
|
||||||
window.scrollTo(0, 0)
|
window.scrollTo(0, 0);
|
||||||
} finally {
|
} finally {
|
||||||
isTransitioning.value = false
|
isTransitioning.value = false;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleKeyDown = (e: KeyboardEvent) => {
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
if (e.key === 'Escape' && isDrawerOpen.value) {
|
if (e.key === 'Escape' && isDrawerOpen.value) {
|
||||||
closeDrawer()
|
closeDrawer();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const Category = defineComponent({
|
const Category = defineComponent({
|
||||||
name: 'Category',
|
name: 'Category',
|
||||||
@@ -167,63 +170,67 @@ const Category = defineComponent({
|
|||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
interface ItemType {
|
interface ItemType {
|
||||||
sub: string
|
sub: string;
|
||||||
path: string
|
path: string;
|
||||||
isActive: boolean
|
isActive: boolean;
|
||||||
isNew: boolean
|
isNew: boolean;
|
||||||
isUpdated: boolean
|
isUpdated: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const items = computed(() =>
|
const items = computed(() =>
|
||||||
props.category.subcategories.map((sub: string): ItemType => {
|
props.category.subcategories.map((sub: string): ItemType => {
|
||||||
const path = `/${slug(props.category.name)}/${slug(sub)}`
|
const path = `/${slug(props.category.name)}/${slug(sub)}`;
|
||||||
const activePath = props.location.path
|
const activePath = props.location.path;
|
||||||
return {
|
return {
|
||||||
sub,
|
sub,
|
||||||
path,
|
path,
|
||||||
isActive: activePath === path,
|
isActive: activePath === path,
|
||||||
isNew: (NEW as string[]).includes(sub),
|
isNew: (NEW as string[]).includes(sub),
|
||||||
isUpdated: (UPDATED as string[]).includes(sub),
|
isUpdated: (UPDATED as string[]).includes(sub)
|
||||||
}
|
};
|
||||||
})
|
})
|
||||||
)
|
);
|
||||||
|
|
||||||
return () => h('div', { class: 'category' }, [
|
return () =>
|
||||||
h('p', { class: 'category-name' }, props.category.name),
|
h('div', { class: 'category' }, [
|
||||||
h('div', { class: 'category-items' },
|
h('p', { class: 'category-name' }, props.category.name),
|
||||||
items.value.map(({ sub, path, isActive, isNew, isUpdated }: ItemType) => {
|
h(
|
||||||
return h('router-link', {
|
'div',
|
||||||
key: path,
|
{ class: 'category-items' },
|
||||||
to: path,
|
items.value.map(({ sub, path, isActive, isNew, isUpdated }: ItemType) => {
|
||||||
class: [
|
return h(
|
||||||
'sidebar-item',
|
'router-link',
|
||||||
{ 'active-sidebar-item': isActive },
|
{
|
||||||
{ 'transitioning': props.isTransitioning }
|
key: path,
|
||||||
],
|
to: path,
|
||||||
onClick: (e: Event) => {
|
class: ['sidebar-item', { 'active-sidebar-item': isActive }, { transitioning: props.isTransitioning }],
|
||||||
e.preventDefault()
|
onClick: (e: Event) => {
|
||||||
props.handleTransitionNavigation(path)
|
e.preventDefault();
|
||||||
},
|
props.handleTransitionNavigation(path);
|
||||||
onMouseenter: (e: Event) => props.onItemMouseEnter(path, e),
|
},
|
||||||
onMouseleave: props.onItemMouseLeave
|
onMouseenter: (e: Event) => props.onItemMouseEnter(path, e),
|
||||||
}, {
|
onMouseleave: props.onItemMouseLeave
|
||||||
default: () => [
|
},
|
||||||
sub,
|
{
|
||||||
isNew ? h('span', { class: 'new-tag' }, 'New') : null,
|
default: () =>
|
||||||
isUpdated ? h('span', { class: 'updated-tag' }, 'Updated') : null
|
[
|
||||||
].filter(Boolean)
|
sub,
|
||||||
|
isNew ? h('span', { class: 'new-tag' }, 'New') : null,
|
||||||
|
isUpdated ? h('span', { class: 'updated-tag' }, 'Updated') : null
|
||||||
|
].filter(Boolean)
|
||||||
|
}
|
||||||
|
);
|
||||||
})
|
})
|
||||||
})
|
)
|
||||||
)
|
]);
|
||||||
])
|
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
document.addEventListener('keydown', handleKeyDown)
|
document.addEventListener('keydown', handleKeyDown);
|
||||||
})
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
document.removeEventListener('keydown', handleKeyDown)
|
document.removeEventListener('keydown', handleKeyDown);
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<!-- Mobile Drawer -->
|
<!-- Mobile Drawer -->
|
||||||
|
|
||||||
<div v-if="isDrawerOpen" class="drawer-overlay" @click="closeDrawer">
|
<div v-if="isDrawerOpen" class="drawer-overlay" @click="closeDrawer">
|
||||||
<div class="drawer-content" :class="{ 'drawer-open': isDrawerOpen }" @click.stop>
|
<div class="drawer-content" :class="{ 'drawer-open': isDrawerOpen }" @click.stop>
|
||||||
<div class="drawer-header sidebar-logo">
|
<div class="drawer-header sidebar-logo">
|
||||||
@@ -7,6 +8,7 @@
|
|||||||
<router-link to="/" @click="closeDrawer">
|
<router-link to="/" @click="closeDrawer">
|
||||||
<img :src="Logo" alt="Logo" class="drawer-logo" />
|
<img :src="Logo" alt="Logo" class="drawer-logo" />
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
<button class="icon-button" aria-label="Close" @click="closeDrawer">
|
<button class="icon-button" aria-label="Close" @click="closeDrawer">
|
||||||
<i class="pi pi-times"></i>
|
<i class="pi pi-times"></i>
|
||||||
</button>
|
</button>
|
||||||
@@ -15,26 +17,41 @@
|
|||||||
|
|
||||||
<div class="drawer-body">
|
<div class="drawer-body">
|
||||||
<div class="categories-container">
|
<div class="categories-container">
|
||||||
<Category v-for="cat in CATEGORIES" :key="cat.name" :category="cat" :location="route"
|
<Category
|
||||||
:pending-active-path="pendingActivePath ?? undefined" :handle-click="onNavClick"
|
v-for="cat in CATEGORIES"
|
||||||
:handle-transition-navigation="handleMobileTransitionNavigation" :on-item-mouse-enter="() => { }"
|
:key="cat.name"
|
||||||
:on-item-mouse-leave="() => { }" :is-transitioning="isTransitioning" />
|
:category="cat"
|
||||||
|
:location="route"
|
||||||
|
:pending-active-path="pendingActivePath ?? undefined"
|
||||||
|
:handle-click="onNavClick"
|
||||||
|
:handle-transition-navigation="handleMobileTransitionNavigation"
|
||||||
|
:on-item-mouse-enter="() => {}"
|
||||||
|
:on-item-mouse-leave="() => {}"
|
||||||
|
:is-transitioning="isTransitioning"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="separator"></div>
|
<div class="separator"></div>
|
||||||
|
|
||||||
<div class="useful-links">
|
<div class="useful-links">
|
||||||
<p class="useful-links-title">Useful Links</p>
|
<p class="useful-links-title">Useful Links</p>
|
||||||
|
|
||||||
<div class="links-container">
|
<div class="links-container">
|
||||||
<a href="https://github.com/DavidHDev/vue-bits" target="_blank" @click="closeDrawer" class="useful-link">
|
<a href="https://github.com/DavidHDev/vue-bits" target="_blank" @click="closeDrawer" class="useful-link">
|
||||||
<span>GitHub</span>
|
<span>GitHub</span>
|
||||||
|
|
||||||
<i class="pi pi-arrow-up-right arrow-icon"></i>
|
<i class="pi pi-arrow-up-right arrow-icon"></i>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<router-link to="/text-animations/split-text" @click="closeDrawer" class="useful-link">
|
<router-link to="/text-animations/split-text" @click="closeDrawer" class="useful-link">
|
||||||
<span>Docs</span>
|
<span>Docs</span>
|
||||||
|
|
||||||
<i class="pi pi-arrow-up-right arrow-icon"></i>
|
<i class="pi pi-arrow-up-right arrow-icon"></i>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
<a href="https://davidhaz.com/" target="_blank" @click="closeDrawer" class="useful-link">
|
<a href="https://davidhaz.com/" target="_blank" @click="closeDrawer" class="useful-link">
|
||||||
<span>Who made this?</span>
|
<span>Who made this?</span>
|
||||||
|
|
||||||
<i class="pi pi-arrow-up-right arrow-icon"></i>
|
<i class="pi pi-arrow-up-right arrow-icon"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -44,174 +61,192 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Desktop Sidebar -->
|
<!-- Desktop Sidebar -->
|
||||||
<nav ref="sidebarContainerRef" class="sidebar" :class="{ 'sidebar-no-fade': isScrolledToBottom }"
|
|
||||||
@scroll="handleScroll">
|
<nav
|
||||||
|
ref="sidebarContainerRef"
|
||||||
|
class="sidebar"
|
||||||
|
:class="{ 'sidebar-no-fade': isScrolledToBottom }"
|
||||||
|
@scroll="handleScroll"
|
||||||
|
>
|
||||||
<div ref="sidebarRef" class="sidebar-content">
|
<div ref="sidebarRef" class="sidebar-content">
|
||||||
<!-- Active line indicator -->
|
<!-- Active line indicator -->
|
||||||
<div class="active-line" :style="{
|
|
||||||
transform: isLineVisible && linePosition !== null
|
<div
|
||||||
? `translateY(${linePosition - 8}px)`
|
class="active-line"
|
||||||
: 'translateY(-100px)',
|
:style="{
|
||||||
opacity: isLineVisible ? 1 : 0
|
transform:
|
||||||
}"></div>
|
isLineVisible && linePosition !== null ? `translateY(${linePosition - 8}px)` : 'translateY(-100px)',
|
||||||
|
opacity: isLineVisible ? 1 : 0
|
||||||
|
}"
|
||||||
|
></div>
|
||||||
|
|
||||||
<!-- Hover line indicator -->
|
<!-- Hover line indicator -->
|
||||||
<div class="hover-line" :style="{
|
|
||||||
transform: hoverLinePosition !== null
|
<div
|
||||||
? `translateY(${hoverLinePosition - 8}px)`
|
class="hover-line"
|
||||||
: 'translateY(-100px)',
|
:style="{
|
||||||
opacity: isHoverLineVisible ? 1 : 0
|
transform: hoverLinePosition !== null ? `translateY(${hoverLinePosition - 8}px)` : 'translateY(-100px)',
|
||||||
}"></div>
|
opacity: isHoverLineVisible ? 1 : 0
|
||||||
|
}"
|
||||||
|
></div>
|
||||||
|
|
||||||
<div class="categories-list">
|
<div class="categories-list">
|
||||||
<Category v-for="cat in CATEGORIES" :key="cat.name" :category="cat" :location="route"
|
<Category
|
||||||
:pending-active-path="pendingActivePath ?? undefined" :handle-click="scrollToTop"
|
v-for="cat in CATEGORIES"
|
||||||
:handle-transition-navigation="handleTransitionNavigation" :on-item-mouse-enter="onItemEnter"
|
:key="cat.name"
|
||||||
:on-item-mouse-leave="onItemLeave" :is-transitioning="isTransitioning" />
|
:category="cat"
|
||||||
|
:location="route"
|
||||||
|
:pending-active-path="pendingActivePath ?? undefined"
|
||||||
|
:handle-click="scrollToTop"
|
||||||
|
:handle-transition-navigation="handleTransitionNavigation"
|
||||||
|
:on-item-mouse-enter="onItemEnter"
|
||||||
|
:on-item-mouse-leave="onItemLeave"
|
||||||
|
:is-transitioning="isTransitioning"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onUnmounted, nextTick, watch, defineComponent, h, computed } from 'vue'
|
import { ref, onMounted, onUnmounted, nextTick, watch, defineComponent, h, computed } from 'vue';
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
import { CATEGORIES, NEW, UPDATED } from '../../constants/Categories'
|
import { CATEGORIES, NEW, UPDATED } from '../../constants/Categories';
|
||||||
import Logo from '../../assets/logos/vue-bits-logo.svg'
|
import Logo from '../../assets/logos/vue-bits-logo.svg';
|
||||||
import '../../css/sidebar.css'
|
import '../../css/sidebar.css';
|
||||||
|
|
||||||
const HOVER_TIMEOUT_DELAY = 150
|
const HOVER_TIMEOUT_DELAY = 150;
|
||||||
|
|
||||||
const isDrawerOpen = ref(false)
|
const isDrawerOpen = ref(false);
|
||||||
const linePosition = ref<number | null>(null)
|
const linePosition = ref<number | null>(null);
|
||||||
const isLineVisible = ref(false)
|
const isLineVisible = ref(false);
|
||||||
const hoverLinePosition = ref<number | null>(null)
|
const hoverLinePosition = ref<number | null>(null);
|
||||||
const isHoverLineVisible = ref(false)
|
const isHoverLineVisible = ref(false);
|
||||||
const pendingActivePath = ref<string | null>(null)
|
const pendingActivePath = ref<string | null>(null);
|
||||||
const isScrolledToBottom = ref(false)
|
const isScrolledToBottom = ref(false);
|
||||||
const isTransitioning = ref(false)
|
const isTransitioning = ref(false);
|
||||||
|
|
||||||
const sidebarRef = ref<HTMLDivElement>()
|
const sidebarRef = ref<HTMLDivElement>();
|
||||||
const sidebarContainerRef = ref<HTMLDivElement>()
|
const sidebarContainerRef = ref<HTMLDivElement>();
|
||||||
|
|
||||||
let hoverTimeoutRef: number | null = null
|
let hoverTimeoutRef: number | null = null;
|
||||||
let hoverDelayTimeoutRef: number | null = null
|
let hoverDelayTimeoutRef: number | null = null;
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute();
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
|
|
||||||
const scrollToTop = () => window.scrollTo(0, 0)
|
const scrollToTop = () => window.scrollTo(0, 0);
|
||||||
const slug = (str: string) => str.replace(/\s+/g, "-").toLowerCase()
|
const slug = (str: string) => str.replace(/\s+/g, '-').toLowerCase();
|
||||||
|
|
||||||
const findActiveElement = () => {
|
const findActiveElement = () => {
|
||||||
const activePath = pendingActivePath.value || route.path
|
const activePath = pendingActivePath.value || route.path;
|
||||||
|
|
||||||
for (const category of CATEGORIES) {
|
for (const category of CATEGORIES) {
|
||||||
const activeItem = category.subcategories.find((sub: string) => {
|
const activeItem = category.subcategories.find((sub: string) => {
|
||||||
const expectedPath = `/${slug(category.name)}/${slug(sub)}`
|
const expectedPath = `/${slug(category.name)}/${slug(sub)}`;
|
||||||
return activePath === expectedPath
|
return activePath === expectedPath;
|
||||||
})
|
});
|
||||||
if (activeItem) {
|
if (activeItem) {
|
||||||
const selector = `.sidebar a[href="${activePath}"]`
|
const selector = `.sidebar a[href="${activePath}"]`;
|
||||||
const element = document.querySelector(selector) as HTMLElement
|
const element = document.querySelector(selector) as HTMLElement;
|
||||||
return element
|
return element;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null
|
return null;
|
||||||
}
|
};
|
||||||
|
|
||||||
const updateLinePosition = (el: HTMLElement | null) => {
|
const updateLinePosition = (el: HTMLElement | null) => {
|
||||||
if (!el || !sidebarRef.value || !sidebarRef.value.offsetParent) return null
|
if (!el || !sidebarRef.value || !sidebarRef.value.offsetParent) return null;
|
||||||
const sidebarRect = sidebarRef.value.getBoundingClientRect()
|
const sidebarRect = sidebarRef.value.getBoundingClientRect();
|
||||||
const elRect = el.getBoundingClientRect()
|
const elRect = el.getBoundingClientRect();
|
||||||
return elRect.top - sidebarRect.top + elRect.height / 2
|
return elRect.top - sidebarRect.top + elRect.height / 2;
|
||||||
}
|
};
|
||||||
|
|
||||||
const closeDrawer = () => {
|
const closeDrawer = () => {
|
||||||
isDrawerOpen.value = false
|
isDrawerOpen.value = false;
|
||||||
}
|
};
|
||||||
|
|
||||||
const onNavClick = () => {
|
const onNavClick = () => {
|
||||||
closeDrawer()
|
closeDrawer();
|
||||||
scrollToTop()
|
scrollToTop();
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleTransitionNavigation = async (path: string) => {
|
const handleTransitionNavigation = async (path: string) => {
|
||||||
if (isTransitioning.value || route.path === path) return
|
if (isTransitioning.value || route.path === path) return;
|
||||||
|
|
||||||
pendingActivePath.value = path
|
pendingActivePath.value = path;
|
||||||
|
|
||||||
// TODO: Implement transition when available
|
// TODO: Implement transition when available
|
||||||
await router.push(path)
|
await router.push(path);
|
||||||
scrollToTop()
|
scrollToTop();
|
||||||
pendingActivePath.value = null
|
pendingActivePath.value = null;
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleMobileTransitionNavigation = async (path: string) => {
|
const handleMobileTransitionNavigation = async (path: string) => {
|
||||||
if (isTransitioning.value || route.path === path) return
|
if (isTransitioning.value || route.path === path) return;
|
||||||
|
|
||||||
closeDrawer()
|
closeDrawer();
|
||||||
pendingActivePath.value = path
|
pendingActivePath.value = path;
|
||||||
|
|
||||||
// TODO: Implement transition when available
|
// TODO: Implement transition when available
|
||||||
await router.push(path)
|
await router.push(path);
|
||||||
scrollToTop()
|
scrollToTop();
|
||||||
pendingActivePath.value = null
|
pendingActivePath.value = null;
|
||||||
}
|
};
|
||||||
|
|
||||||
const onItemEnter = (path: string, e: Event) => {
|
const onItemEnter = (path: string, e: Event) => {
|
||||||
if (hoverTimeoutRef) clearTimeout(hoverTimeoutRef)
|
if (hoverTimeoutRef) clearTimeout(hoverTimeoutRef);
|
||||||
if (hoverDelayTimeoutRef) clearTimeout(hoverDelayTimeoutRef)
|
if (hoverDelayTimeoutRef) clearTimeout(hoverDelayTimeoutRef);
|
||||||
|
|
||||||
const targetElement = e.currentTarget as HTMLElement
|
const targetElement = e.currentTarget as HTMLElement;
|
||||||
const pos = updateLinePosition(targetElement)
|
const pos = updateLinePosition(targetElement);
|
||||||
|
|
||||||
if (pos !== null) {
|
if (pos !== null) {
|
||||||
hoverLinePosition.value = pos
|
hoverLinePosition.value = pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
hoverDelayTimeoutRef = setTimeout(() => {
|
hoverDelayTimeoutRef = setTimeout(() => {
|
||||||
isHoverLineVisible.value = true
|
isHoverLineVisible.value = true;
|
||||||
}, 200)
|
}, 200);
|
||||||
}
|
};
|
||||||
|
|
||||||
const onItemLeave = () => {
|
const onItemLeave = () => {
|
||||||
if (hoverDelayTimeoutRef) clearTimeout(hoverDelayTimeoutRef)
|
if (hoverDelayTimeoutRef) clearTimeout(hoverDelayTimeoutRef);
|
||||||
|
|
||||||
hoverTimeoutRef = setTimeout(() => {
|
hoverTimeoutRef = setTimeout(() => {
|
||||||
isHoverLineVisible.value = false
|
isHoverLineVisible.value = false;
|
||||||
}, HOVER_TIMEOUT_DELAY)
|
}, HOVER_TIMEOUT_DELAY);
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleScroll = () => {
|
const handleScroll = () => {
|
||||||
const sidebarElement = sidebarContainerRef.value
|
const sidebarElement = sidebarContainerRef.value;
|
||||||
if (!sidebarElement) return
|
if (!sidebarElement) return;
|
||||||
|
|
||||||
const { scrollTop, scrollHeight, clientHeight } = sidebarElement
|
const { scrollTop, scrollHeight, clientHeight } = sidebarElement;
|
||||||
const isAtBottom = scrollTop + clientHeight >= scrollHeight - 10
|
const isAtBottom = scrollTop + clientHeight >= scrollHeight - 10;
|
||||||
isScrolledToBottom.value = isAtBottom
|
isScrolledToBottom.value = isAtBottom;
|
||||||
}
|
};
|
||||||
|
|
||||||
const updateActiveLine = async () => {
|
const updateActiveLine = async () => {
|
||||||
await nextTick()
|
await nextTick();
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const activeEl = findActiveElement()
|
const activeEl = findActiveElement();
|
||||||
|
|
||||||
if (!activeEl) {
|
if (!activeEl) {
|
||||||
isLineVisible.value = false
|
isLineVisible.value = false;
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pos = updateLinePosition(activeEl)
|
const pos = updateLinePosition(activeEl);
|
||||||
if (pos !== null) {
|
if (pos !== null) {
|
||||||
linePosition.value = pos
|
linePosition.value = pos;
|
||||||
isLineVisible.value = true
|
isLineVisible.value = true;
|
||||||
} else {
|
} else {
|
||||||
isLineVisible.value = false
|
isLineVisible.value = false;
|
||||||
}
|
}
|
||||||
}, 100)
|
}, 100);
|
||||||
}
|
};
|
||||||
|
|
||||||
const Category = defineComponent({
|
const Category = defineComponent({
|
||||||
name: 'Category',
|
name: 'Category',
|
||||||
@@ -251,74 +286,78 @@ const Category = defineComponent({
|
|||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
interface ItemType {
|
interface ItemType {
|
||||||
sub: string
|
sub: string;
|
||||||
path: string
|
path: string;
|
||||||
isActive: boolean
|
isActive: boolean;
|
||||||
isNew: boolean
|
isNew: boolean;
|
||||||
isUpdated: boolean
|
isUpdated: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const items = computed(() =>
|
const items = computed(() =>
|
||||||
props.category.subcategories.map((sub: string): ItemType => {
|
props.category.subcategories.map((sub: string): ItemType => {
|
||||||
const path = `/${slug(props.category.name)}/${slug(sub)}`
|
const path = `/${slug(props.category.name)}/${slug(sub)}`;
|
||||||
const activePath = props.pendingActivePath || props.location.path
|
const activePath = props.pendingActivePath || props.location.path;
|
||||||
return {
|
return {
|
||||||
sub,
|
sub,
|
||||||
path,
|
path,
|
||||||
isActive: activePath === path,
|
isActive: activePath === path,
|
||||||
isNew: (NEW as string[]).includes(sub),
|
isNew: (NEW as string[]).includes(sub),
|
||||||
isUpdated: (UPDATED as string[]).includes(sub),
|
isUpdated: (UPDATED as string[]).includes(sub)
|
||||||
}
|
};
|
||||||
})
|
})
|
||||||
)
|
);
|
||||||
|
|
||||||
return () => h('div', { class: 'category' }, [
|
return () =>
|
||||||
h('p', { class: 'category-name' }, props.category.name),
|
h('div', { class: 'category' }, [
|
||||||
h('div', { class: 'category-items' },
|
h('p', { class: 'category-name' }, props.category.name),
|
||||||
items.value.map(({ sub, path, isActive, isNew, isUpdated }: ItemType) => {
|
h(
|
||||||
return h('router-link', {
|
'div',
|
||||||
key: path,
|
{ class: 'category-items' },
|
||||||
to: path,
|
items.value.map(({ sub, path, isActive, isNew, isUpdated }: ItemType) => {
|
||||||
class: [
|
return h(
|
||||||
'sidebar-item',
|
'router-link',
|
||||||
{ 'active-sidebar-item': isActive },
|
{
|
||||||
{ 'transitioning': props.isTransitioning }
|
key: path,
|
||||||
],
|
to: path,
|
||||||
onClick: (e: Event) => {
|
class: ['sidebar-item', { 'active-sidebar-item': isActive }, { transitioning: props.isTransitioning }],
|
||||||
e.preventDefault()
|
onClick: (e: Event) => {
|
||||||
props.handleTransitionNavigation(path)
|
e.preventDefault();
|
||||||
},
|
props.handleTransitionNavigation(path);
|
||||||
onMouseenter: (e: Event) => props.onItemMouseEnter(path, e),
|
},
|
||||||
onMouseleave: props.onItemMouseLeave
|
onMouseenter: (e: Event) => props.onItemMouseEnter(path, e),
|
||||||
}, {
|
onMouseleave: props.onItemMouseLeave
|
||||||
default: () => [
|
},
|
||||||
sub,
|
{
|
||||||
isNew ? h('span', { class: 'new-tag' }, 'New') : null,
|
default: () =>
|
||||||
isUpdated ? h('span', { class: 'updated-tag' }, 'Updated') : null
|
[
|
||||||
].filter(Boolean)
|
sub,
|
||||||
|
isNew ? h('span', { class: 'new-tag' }, 'New') : null,
|
||||||
|
isUpdated ? h('span', { class: 'updated-tag' }, 'Updated') : null
|
||||||
|
].filter(Boolean)
|
||||||
|
}
|
||||||
|
);
|
||||||
})
|
})
|
||||||
})
|
)
|
||||||
)
|
]);
|
||||||
])
|
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
watch(() => route.path, updateActiveLine)
|
watch(() => route.path, updateActiveLine);
|
||||||
watch(pendingActivePath, updateActiveLine)
|
watch(pendingActivePath, updateActiveLine);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
updateActiveLine()
|
updateActiveLine();
|
||||||
if (sidebarContainerRef.value) {
|
if (sidebarContainerRef.value) {
|
||||||
sidebarContainerRef.value.addEventListener('scroll', handleScroll)
|
sidebarContainerRef.value.addEventListener('scroll', handleScroll);
|
||||||
handleScroll()
|
handleScroll();
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
if (hoverTimeoutRef) clearTimeout(hoverTimeoutRef)
|
if (hoverTimeoutRef) clearTimeout(hoverTimeoutRef);
|
||||||
if (hoverDelayTimeoutRef) clearTimeout(hoverDelayTimeoutRef)
|
if (hoverDelayTimeoutRef) clearTimeout(hoverDelayTimeoutRef);
|
||||||
if (sidebarContainerRef.value) {
|
if (sidebarContainerRef.value) {
|
||||||
sidebarContainerRef.value.removeEventListener('scroll', handleScroll)
|
sidebarContainerRef.value.removeEventListener('scroll', handleScroll);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import { ref } from 'vue'
|
import { ref } from 'vue';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Composable for force re-rendering components
|
* Composable for force re-rendering components
|
||||||
* Useful for demo components that need to restart animations or reset state
|
* Useful for demo components that need to restart animations or reset state
|
||||||
*/
|
*/
|
||||||
export function useForceRerender() {
|
export function useForceRerender() {
|
||||||
const rerenderKey = ref(0)
|
const rerenderKey = ref(0);
|
||||||
|
|
||||||
const forceRerender = () => {
|
const forceRerender = () => {
|
||||||
rerenderKey.value++
|
rerenderKey.value++;
|
||||||
}
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
rerenderKey,
|
rerenderKey,
|
||||||
forceRerender
|
forceRerender
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,48 +1,51 @@
|
|||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue';
|
||||||
import { getStarsCount } from '@/utils/utils'
|
import { getStarsCount } from '@/utils/utils';
|
||||||
|
|
||||||
const CACHE_KEY = 'github_stars_cache'
|
const CACHE_KEY = 'github_stars_cache';
|
||||||
const CACHE_DURATION = 24 * 60 * 60 * 1000
|
const CACHE_DURATION = 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
export function useStars() {
|
export function useStars() {
|
||||||
const stars = ref<number>(0)
|
const stars = ref<number>(0);
|
||||||
|
|
||||||
const fetchStars = async () => {
|
const fetchStars = async () => {
|
||||||
try {
|
try {
|
||||||
const cachedData = localStorage.getItem(CACHE_KEY)
|
const cachedData = localStorage.getItem(CACHE_KEY);
|
||||||
|
|
||||||
if (cachedData) {
|
if (cachedData) {
|
||||||
const { count, timestamp } = JSON.parse(cachedData)
|
const { count, timestamp } = JSON.parse(cachedData);
|
||||||
const now = Date.now()
|
const now = Date.now();
|
||||||
|
|
||||||
if (now - timestamp < CACHE_DURATION) {
|
if (now - timestamp < CACHE_DURATION) {
|
||||||
stars.value = count
|
stars.value = count;
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const count = await getStarsCount()
|
const count = await getStarsCount();
|
||||||
|
|
||||||
localStorage.setItem(CACHE_KEY, JSON.stringify({
|
localStorage.setItem(
|
||||||
count,
|
CACHE_KEY,
|
||||||
timestamp: Date.now()
|
JSON.stringify({
|
||||||
}))
|
count,
|
||||||
|
timestamp: Date.now()
|
||||||
stars.value = count
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
stars.value = count;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching stars:', error)
|
console.error('Error fetching stars:', error);
|
||||||
|
|
||||||
const cachedData = localStorage.getItem(CACHE_KEY)
|
const cachedData = localStorage.getItem(CACHE_KEY);
|
||||||
if (cachedData) {
|
if (cachedData) {
|
||||||
const { count } = JSON.parse(cachedData)
|
const { count } = JSON.parse(cachedData);
|
||||||
stars.value = count
|
stars.value = count;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchStars()
|
fetchStars();
|
||||||
})
|
});
|
||||||
|
|
||||||
return stars
|
return stars;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export const CATEGORIES = [
|
|||||||
'Text Cursor',
|
'Text Cursor',
|
||||||
'Decrypted Text',
|
'Decrypted Text',
|
||||||
'True Focus',
|
'True Focus',
|
||||||
'Scroll Float',
|
'Scroll Float'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -34,7 +34,7 @@ export const CATEGORIES = [
|
|||||||
'Count Up',
|
'Count Up',
|
||||||
'Click Spark',
|
'Click Spark',
|
||||||
'Magnet',
|
'Magnet',
|
||||||
'Cubes',
|
'Cubes'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -55,8 +55,8 @@ export const CATEGORIES = [
|
|||||||
'Glass Icons',
|
'Glass Icons',
|
||||||
'Decay Card',
|
'Decay Card',
|
||||||
'Flowing Menu',
|
'Flowing Menu',
|
||||||
'Elastic Slider',
|
'Elastic Slider'
|
||||||
],
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Backgrounds',
|
name: 'Backgrounds',
|
||||||
@@ -73,6 +73,6 @@ export const CATEGORIES = [
|
|||||||
'Iridescence',
|
'Iridescence',
|
||||||
'Threads',
|
'Threads',
|
||||||
'Grid Motion'
|
'Grid Motion'
|
||||||
],
|
]
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,69 +1,69 @@
|
|||||||
const animations = {
|
const animations = {
|
||||||
'fade-content': () => import("../demo/Animations/FadeContentDemo.vue"),
|
'fade-content': () => import('../demo/Animations/FadeContentDemo.vue'),
|
||||||
'animated-content': () => import("../demo/Animations/AnimatedContentDemo.vue"),
|
'animated-content': () => import('../demo/Animations/AnimatedContentDemo.vue'),
|
||||||
'pixel-transition': () => import("../demo/Animations/PixelTransitionDemo.vue"),
|
'pixel-transition': () => import('../demo/Animations/PixelTransitionDemo.vue'),
|
||||||
'glare-hover': () => import("../demo/Animations/GlareHoverDemo.vue"),
|
'glare-hover': () => import('../demo/Animations/GlareHoverDemo.vue'),
|
||||||
'magnet-lines': () => import("../demo/Animations/MagnetLinesDemo.vue"),
|
'magnet-lines': () => import('../demo/Animations/MagnetLinesDemo.vue'),
|
||||||
'click-spark': () => import("../demo/Animations/ClickSparkDemo.vue"),
|
'click-spark': () => import('../demo/Animations/ClickSparkDemo.vue'),
|
||||||
'magnet': () => import("../demo/Animations/MagnetDemo.vue"),
|
magnet: () => import('../demo/Animations/MagnetDemo.vue'),
|
||||||
'cubes': () => import("../demo/Animations/CubesDemo.vue"),
|
cubes: () => import('../demo/Animations/CubesDemo.vue'),
|
||||||
'count-up': () => import("../demo/Animations/CountUpDemo.vue"),
|
'count-up': () => import('../demo/Animations/CountUpDemo.vue')
|
||||||
};
|
};
|
||||||
|
|
||||||
const textAnimations = {
|
const textAnimations = {
|
||||||
'split-text': () => import("../demo/TextAnimations/SplitTextDemo.vue"),
|
'split-text': () => import('../demo/TextAnimations/SplitTextDemo.vue'),
|
||||||
'blur-text': () => import("../demo/TextAnimations/BlurTextDemo.vue"),
|
'blur-text': () => import('../demo/TextAnimations/BlurTextDemo.vue'),
|
||||||
'circular-text': () => import("../demo/TextAnimations/CircularTextDemo.vue"),
|
'circular-text': () => import('../demo/TextAnimations/CircularTextDemo.vue'),
|
||||||
'shiny-text': () => import("../demo/TextAnimations/ShinyTextDemo.vue"),
|
'shiny-text': () => import('../demo/TextAnimations/ShinyTextDemo.vue'),
|
||||||
'text-pressure': () => import("../demo/TextAnimations/TextPressureDemo.vue"),
|
'text-pressure': () => import('../demo/TextAnimations/TextPressureDemo.vue'),
|
||||||
'curved-loop': () => import("../demo/TextAnimations/CurvedLoopDemo.vue"),
|
'curved-loop': () => import('../demo/TextAnimations/CurvedLoopDemo.vue'),
|
||||||
'fuzzy-text': () => import("../demo/TextAnimations/FuzzyTextDemo.vue"),
|
'fuzzy-text': () => import('../demo/TextAnimations/FuzzyTextDemo.vue'),
|
||||||
'gradient-text': () => import("../demo/TextAnimations/GradientTextDemo.vue"),
|
'gradient-text': () => import('../demo/TextAnimations/GradientTextDemo.vue'),
|
||||||
'text-trail': () => import("../demo/TextAnimations/TextTrailDemo.vue"),
|
'text-trail': () => import('../demo/TextAnimations/TextTrailDemo.vue'),
|
||||||
'falling-text': () => import("../demo/TextAnimations/FallingTextDemo.vue"),
|
'falling-text': () => import('../demo/TextAnimations/FallingTextDemo.vue'),
|
||||||
'text-cursor': () => import("../demo/TextAnimations/TextCursorDemo.vue"),
|
'text-cursor': () => import('../demo/TextAnimations/TextCursorDemo.vue'),
|
||||||
'decrypted-text': () => import("../demo/TextAnimations/DecryptedTextDemo.vue"),
|
'decrypted-text': () => import('../demo/TextAnimations/DecryptedTextDemo.vue'),
|
||||||
'true-focus': () => import("../demo/TextAnimations/TrueFocusDemo.vue"),
|
'true-focus': () => import('../demo/TextAnimations/TrueFocusDemo.vue'),
|
||||||
'scroll-float': () => import("../demo/TextAnimations/ScrollFloatDemo.vue"),
|
'scroll-float': () => import('../demo/TextAnimations/ScrollFloatDemo.vue')
|
||||||
};
|
};
|
||||||
|
|
||||||
const components = {
|
const components = {
|
||||||
'masonry': () => import("../demo/Components/MasonryDemo.vue"),
|
masonry: () => import('../demo/Components/MasonryDemo.vue'),
|
||||||
'profile-card': () => import("../demo/Components/ProfileCardDemo.vue"),
|
'profile-card': () => import('../demo/Components/ProfileCardDemo.vue'),
|
||||||
'dock': () => import("../demo/Components/DockDemo.vue"),
|
dock: () => import('../demo/Components/DockDemo.vue'),
|
||||||
'gooey-nav': () => import("../demo/Components/GooeyNavDemo.vue"),
|
'gooey-nav': () => import('../demo/Components/GooeyNavDemo.vue'),
|
||||||
'pixel-card': () => import("../demo/Components/PixelCardDemo.vue"),
|
'pixel-card': () => import('../demo/Components/PixelCardDemo.vue'),
|
||||||
'carousel': () => import("../demo/Components/CarouselDemo.vue"),
|
carousel: () => import('../demo/Components/CarouselDemo.vue'),
|
||||||
'spotlight-card': () => import("../demo/Components/SpotlightCardDemo.vue"),
|
'spotlight-card': () => import('../demo/Components/SpotlightCardDemo.vue'),
|
||||||
'circular-gallery': () => import("../demo/Components/CircularGalleryDemo.vue"),
|
'circular-gallery': () => import('../demo/Components/CircularGalleryDemo.vue'),
|
||||||
'flying-posters': () => import("../demo/Components/FlyingPostersDemo.vue"),
|
'flying-posters': () => import('../demo/Components/FlyingPostersDemo.vue'),
|
||||||
'card-swap': () => import("../demo/Components/CardSwapDemo.vue"),
|
'card-swap': () => import('../demo/Components/CardSwapDemo.vue'),
|
||||||
'infinite-scroll': () => import("../demo/Components/InfiniteScrollDemo.vue"),
|
'infinite-scroll': () => import('../demo/Components/InfiniteScrollDemo.vue'),
|
||||||
'glass-icons': () => import("../demo/Components/GlassIconsDemo.vue"),
|
'glass-icons': () => import('../demo/Components/GlassIconsDemo.vue'),
|
||||||
'decay-card': () => import("../demo/Components/DecayCardDemo.vue"),
|
'decay-card': () => import('../demo/Components/DecayCardDemo.vue'),
|
||||||
'flowing-menu': () => import("../demo/Components/FlowingMenuDemo.vue"),
|
'flowing-menu': () => import('../demo/Components/FlowingMenuDemo.vue'),
|
||||||
'elastic-slider': () => import("../demo/Components/ElasticSliderDemo.vue"),
|
'elastic-slider': () => import('../demo/Components/ElasticSliderDemo.vue'),
|
||||||
'tilted-card': () => import("../demo/Components/TiltedCardDemo.vue"),
|
'tilted-card': () => import('../demo/Components/TiltedCardDemo.vue')
|
||||||
};
|
};
|
||||||
|
|
||||||
const backgrounds = {
|
const backgrounds = {
|
||||||
'dot-grid': () => import("../demo/Backgrounds/DotGridDemo.vue"),
|
'dot-grid': () => import('../demo/Backgrounds/DotGridDemo.vue'),
|
||||||
'silk': () => import("../demo/Backgrounds/SilkDemo.vue"),
|
silk: () => import('../demo/Backgrounds/SilkDemo.vue'),
|
||||||
'lightning': () => import("../demo/Backgrounds/LightningDemo.vue"),
|
lightning: () => import('../demo/Backgrounds/LightningDemo.vue'),
|
||||||
'letter-glitch': () => import("../demo/Backgrounds/LetterGlitchDemo.vue"),
|
'letter-glitch': () => import('../demo/Backgrounds/LetterGlitchDemo.vue'),
|
||||||
'particles': () => import("../demo/Backgrounds/ParticlesDemo.vue"),
|
particles: () => import('../demo/Backgrounds/ParticlesDemo.vue'),
|
||||||
'waves': () => import("../demo/Backgrounds/WavesDemo.vue"),
|
waves: () => import('../demo/Backgrounds/WavesDemo.vue'),
|
||||||
'squares': () => import("../demo/Backgrounds/SquaresDemo.vue"),
|
squares: () => import('../demo/Backgrounds/SquaresDemo.vue'),
|
||||||
'iridescence': () => import("../demo/Backgrounds/IridescenceDemo.vue"),
|
iridescence: () => import('../demo/Backgrounds/IridescenceDemo.vue'),
|
||||||
'threads': () => import("../demo/Backgrounds/ThreadsDemo.vue"),
|
threads: () => import('../demo/Backgrounds/ThreadsDemo.vue'),
|
||||||
'aurora': () => import("../demo/Backgrounds/AuroraDemo.vue"),
|
aurora: () => import('../demo/Backgrounds/AuroraDemo.vue'),
|
||||||
'beams': () => import("../demo/Backgrounds/BeamsDemo.vue"),
|
beams: () => import('../demo/Backgrounds/BeamsDemo.vue'),
|
||||||
'grid-motion': () => import("../demo/Backgrounds/GridMotionDemo.vue"),
|
'grid-motion': () => import('../demo/Backgrounds/GridMotionDemo.vue')
|
||||||
};
|
};
|
||||||
|
|
||||||
export const componentMap = {
|
export const componentMap = {
|
||||||
...animations,
|
...animations,
|
||||||
...textAnimations,
|
...textAnimations,
|
||||||
...components,
|
...components,
|
||||||
...backgrounds,
|
...backgrounds
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@/content/Animations/AnimatedContent/AnimatedContent.vue?raw'
|
import code from '@/content/Animations/AnimatedContent/AnimatedContent.vue?raw';
|
||||||
import type { CodeObject } from '@/types/code'
|
import type { CodeObject } from '@/types/code';
|
||||||
|
|
||||||
export const animatedContent: CodeObject = {
|
export const animatedContent: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Animations/AnimatedContent`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/Animations/AnimatedContent`,
|
||||||
@@ -32,4 +32,4 @@ export const animatedContent: CodeObject = {
|
|||||||
};
|
};
|
||||||
</script>`,
|
</script>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@content/Animations/ClickSpark/ClickSpark.vue?raw'
|
import code from '@content/Animations/ClickSpark/ClickSpark.vue?raw';
|
||||||
import type { CodeObject } from '../../../types/code'
|
import type { CodeObject } from '../../../types/code';
|
||||||
|
|
||||||
export const clickSpark: CodeObject = {
|
export const clickSpark: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Animations/ClickSpark`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/Animations/ClickSpark`,
|
||||||
@@ -44,4 +44,4 @@ import ClickSpark from '@/content/Animations/ClickSpark/ClickSpark.vue'
|
|||||||
}
|
}
|
||||||
</style>`,
|
</style>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@/content/Animations/CountUp/CountUp.vue?raw'
|
import code from '@/content/Animations/CountUp/CountUp.vue?raw';
|
||||||
import type { CodeObject } from '@/types/code'
|
import type { CodeObject } from '@/types/code';
|
||||||
|
|
||||||
export const countup: CodeObject = {
|
export const countup: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Animations/CountUp`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/Animations/CountUp`,
|
||||||
@@ -30,4 +30,4 @@ export const countup: CodeObject = {
|
|||||||
};
|
};
|
||||||
</script>`,
|
</script>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@content/Animations/Cubes/Cubes.vue?raw'
|
import code from '@content/Animations/Cubes/Cubes.vue?raw';
|
||||||
import type { CodeObject } from '../../../types/code'
|
import type { CodeObject } from '../../../types/code';
|
||||||
|
|
||||||
export const cubes: CodeObject = {
|
export const cubes: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Animations/Cubes`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/Animations/Cubes`,
|
||||||
@@ -29,4 +29,4 @@ export const cubes: CodeObject = {
|
|||||||
import Cubes from "./Cubes.vue";
|
import Cubes from "./Cubes.vue";
|
||||||
</script>`,
|
</script>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@content/Animations/FadeContent/FadeContent.vue?raw'
|
import code from '@content/Animations/FadeContent/FadeContent.vue?raw';
|
||||||
import type { CodeObject } from '../../../types/code'
|
import type { CodeObject } from '../../../types/code';
|
||||||
|
|
||||||
export const fadeContent: CodeObject = {
|
export const fadeContent: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Animations/FadeContent`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/Animations/FadeContent`,
|
||||||
@@ -24,4 +24,4 @@ export const fadeContent: CodeObject = {
|
|||||||
import FadeContent from "./FadeContent.vue";
|
import FadeContent from "./FadeContent.vue";
|
||||||
</script>`,
|
</script>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@/content/Animations/GlareHover/GlareHover.vue?raw'
|
import code from '@/content/Animations/GlareHover/GlareHover.vue?raw';
|
||||||
import type { CodeObject } from '@/types/code'
|
import type { CodeObject } from '@/types/code';
|
||||||
|
|
||||||
export const glareHover: CodeObject = {
|
export const glareHover: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Animations/GlareHover`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/Animations/GlareHover`,
|
||||||
@@ -26,4 +26,4 @@ export const glareHover: CodeObject = {
|
|||||||
import GlareHover from "./GlareHover.vue";
|
import GlareHover from "./GlareHover.vue";
|
||||||
</script>`,
|
</script>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@content/Animations/Magnet/Magnet.vue?raw'
|
import code from '@content/Animations/Magnet/Magnet.vue?raw';
|
||||||
import type { CodeObject } from '../../../types/code'
|
import type { CodeObject } from '../../../types/code';
|
||||||
|
|
||||||
export const magnet: CodeObject = {
|
export const magnet: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Animations/Magnet`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/Animations/Magnet`,
|
||||||
@@ -45,4 +45,4 @@ import Magnet from '@/content/Animations/Magnet/Magnet.vue'
|
|||||||
}
|
}
|
||||||
</style>`,
|
</style>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@/content/Animations/MagnetLines/MagnetLines.vue?raw'
|
import code from '@/content/Animations/MagnetLines/MagnetLines.vue?raw';
|
||||||
import type { CodeObject } from '@/types/code'
|
import type { CodeObject } from '@/types/code';
|
||||||
|
|
||||||
export const magnetLines: CodeObject = {
|
export const magnetLines: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Animations/MagnetLines`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/Animations/MagnetLines`,
|
||||||
@@ -19,4 +19,4 @@ export const magnetLines: CodeObject = {
|
|||||||
import MagnetLines from "./MagnetLines.vue";
|
import MagnetLines from "./MagnetLines.vue";
|
||||||
</script>`,
|
</script>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@/content/Animations/PixelTransition/PixelTransition.vue?raw'
|
import code from '@/content/Animations/PixelTransition/PixelTransition.vue?raw';
|
||||||
import type { CodeObject } from '@/types/code'
|
import type { CodeObject } from '@/types/code';
|
||||||
|
|
||||||
export const pixelTransition: CodeObject = {
|
export const pixelTransition: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Animations/PixelTransition`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/Animations/PixelTransition`,
|
||||||
@@ -26,4 +26,4 @@ export const pixelTransition: CodeObject = {
|
|||||||
import PixelTransition from './PixelTransition.vue';
|
import PixelTransition from './PixelTransition.vue';
|
||||||
</script>`,
|
</script>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@content/Backgrounds/Aurora/Aurora.vue?raw'
|
import code from '@content/Backgrounds/Aurora/Aurora.vue?raw';
|
||||||
import type { CodeObject } from '../../../types/code'
|
import type { CodeObject } from '../../../types/code';
|
||||||
|
|
||||||
export const aurora: CodeObject = {
|
export const aurora: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Backgrounds/Aurora`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/Backgrounds/Aurora`,
|
||||||
@@ -30,4 +30,4 @@ export const aurora: CodeObject = {
|
|||||||
}
|
}
|
||||||
</style>`,
|
</style>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@content/Backgrounds/Beams/Beams.vue?raw'
|
import code from '@content/Backgrounds/Beams/Beams.vue?raw';
|
||||||
import type { CodeObject } from '../../../types/code'
|
import type { CodeObject } from '../../../types/code';
|
||||||
|
|
||||||
export const beams: CodeObject = {
|
export const beams: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Backgrounds/Beams`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/Backgrounds/Beams`,
|
||||||
@@ -33,4 +33,4 @@ export const beams: CodeObject = {
|
|||||||
}
|
}
|
||||||
</style>`,
|
</style>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@content/Backgrounds/DotGrid/DotGrid.vue?raw'
|
import code from '@content/Backgrounds/DotGrid/DotGrid.vue?raw';
|
||||||
import type { CodeObject } from '../../../types/code'
|
import type { CodeObject } from '../../../types/code';
|
||||||
|
|
||||||
export const dotGrid: CodeObject = {
|
export const dotGrid: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Backgrounds/DotGrid`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/Backgrounds/DotGrid`,
|
||||||
@@ -36,4 +36,4 @@ export const dotGrid: CodeObject = {
|
|||||||
}
|
}
|
||||||
</style>`,
|
</style>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@content/Backgrounds/Iridescence/Iridescence.vue?raw'
|
import code from '@content/Backgrounds/Iridescence/Iridescence.vue?raw';
|
||||||
import type { CodeObject } from '../../../types/code'
|
import type { CodeObject } from '../../../types/code';
|
||||||
|
|
||||||
export const iridescence: CodeObject = {
|
export const iridescence: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Backgrounds/Iridescence`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/Backgrounds/Iridescence`,
|
||||||
@@ -19,4 +19,4 @@ export const iridescence: CodeObject = {
|
|||||||
import Iridescence from "./Iridescence.vue";
|
import Iridescence from "./Iridescence.vue";
|
||||||
</script>`,
|
</script>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@content/Backgrounds/LetterGlitch/LetterGlitch.vue?raw'
|
import code from '@content/Backgrounds/LetterGlitch/LetterGlitch.vue?raw';
|
||||||
import type { CodeObject } from '../../../types/code'
|
import type { CodeObject } from '../../../types/code';
|
||||||
|
|
||||||
export const letterGlitch: CodeObject = {
|
export const letterGlitch: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Backgrounds/LetterGlitch`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/Backgrounds/LetterGlitch`,
|
||||||
@@ -29,4 +29,4 @@ export const letterGlitch: CodeObject = {
|
|||||||
}
|
}
|
||||||
</style>`,
|
</style>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@content/Backgrounds/Lightning/Lightning.vue?raw'
|
import code from '@content/Backgrounds/Lightning/Lightning.vue?raw';
|
||||||
import type { CodeObject } from '../../../types/code'
|
import type { CodeObject } from '../../../types/code';
|
||||||
|
|
||||||
export const lightning: CodeObject = {
|
export const lightning: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Backgrounds/Lightning`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/Backgrounds/Lightning`,
|
||||||
@@ -30,4 +30,4 @@ export const lightning: CodeObject = {
|
|||||||
}
|
}
|
||||||
</style>`,
|
</style>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@content/Backgrounds/Particles/Particles.vue?raw'
|
import code from '@content/Backgrounds/Particles/Particles.vue?raw';
|
||||||
import type { CodeObject } from '../../../types/code'
|
import type { CodeObject } from '../../../types/code';
|
||||||
|
|
||||||
export const particles: CodeObject = {
|
export const particles: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Backgrounds/Particles`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/Backgrounds/Particles`,
|
||||||
@@ -36,4 +36,4 @@ export const particles: CodeObject = {
|
|||||||
}
|
}
|
||||||
</style>`,
|
</style>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@content/Backgrounds/Silk/Silk.vue?raw'
|
import code from '@content/Backgrounds/Silk/Silk.vue?raw';
|
||||||
import type { CodeObject } from '../../../types/code'
|
import type { CodeObject } from '../../../types/code';
|
||||||
|
|
||||||
export const silk: CodeObject = {
|
export const silk: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Backgrounds/Silk`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/Backgrounds/Silk`,
|
||||||
@@ -30,4 +30,4 @@ export const silk: CodeObject = {
|
|||||||
}
|
}
|
||||||
</style>`,
|
</style>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@content/Backgrounds/Squares/Squares.vue?raw'
|
import code from '@content/Backgrounds/Squares/Squares.vue?raw';
|
||||||
import type { CodeObject } from '../../../types/code'
|
import type { CodeObject } from '../../../types/code';
|
||||||
|
|
||||||
export const squares: CodeObject = {
|
export const squares: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Backgrounds/Squares`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/Backgrounds/Squares`,
|
||||||
@@ -19,4 +19,4 @@ export const squares: CodeObject = {
|
|||||||
import Squares from "./Squares.vue";
|
import Squares from "./Squares.vue";
|
||||||
</script>`,
|
</script>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@content/Backgrounds/Threads/Threads.vue?raw'
|
import code from '@content/Backgrounds/Threads/Threads.vue?raw';
|
||||||
import type { CodeObject } from '../../../types/code'
|
import type { CodeObject } from '../../../types/code';
|
||||||
|
|
||||||
export const threads: CodeObject = {
|
export const threads: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Backgrounds/Threads`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/Backgrounds/Threads`,
|
||||||
@@ -19,4 +19,4 @@ export const threads: CodeObject = {
|
|||||||
import Threads from "./Threads.vue";
|
import Threads from "./Threads.vue";
|
||||||
</script>`,
|
</script>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@content/Backgrounds/Waves/Waves.vue?raw'
|
import code from '@content/Backgrounds/Waves/Waves.vue?raw';
|
||||||
import type { CodeObject } from '../../../types/code'
|
import type { CodeObject } from '../../../types/code';
|
||||||
|
|
||||||
export const waves: CodeObject = {
|
export const waves: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Backgrounds/Waves`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/Backgrounds/Waves`,
|
||||||
@@ -35,4 +35,4 @@ export const waves: CodeObject = {
|
|||||||
}
|
}
|
||||||
</style>`,
|
</style>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@content/Components/CardSwap/CardSwap.vue?raw'
|
import code from '@content/Components/CardSwap/CardSwap.vue?raw';
|
||||||
import type { CodeObject } from '../../../types/code'
|
import type { CodeObject } from '../../../types/code';
|
||||||
|
|
||||||
export const cardSwap: CodeObject = {
|
export const cardSwap: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/CardSwap`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/CardSwap`,
|
||||||
@@ -51,4 +51,4 @@ export const cardSwap: CodeObject = {
|
|||||||
};
|
};
|
||||||
</script>`,
|
</script>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@content/Components/Carousel/Carousel.vue?raw'
|
import code from '@content/Components/Carousel/Carousel.vue?raw';
|
||||||
import type { CodeObject } from '../../../types/code'
|
import type { CodeObject } from '../../../types/code';
|
||||||
|
|
||||||
export const carousel: CodeObject = {
|
export const carousel: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/Carousel`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/Carousel`,
|
||||||
@@ -37,4 +37,4 @@ export const carousel: CodeObject = {
|
|||||||
];
|
];
|
||||||
</script>`,
|
</script>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@content/Components/CircularGallery/CircularGallery.vue?raw'
|
import code from '@content/Components/CircularGallery/CircularGallery.vue?raw';
|
||||||
import type { CodeObject } from '../../../types/code'
|
import type { CodeObject } from '../../../types/code';
|
||||||
|
|
||||||
export const circularGallery: CodeObject = {
|
export const circularGallery: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/CircularGallery`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/CircularGallery`,
|
||||||
@@ -24,4 +24,4 @@ export const circularGallery: CodeObject = {
|
|||||||
import CircularGallery from "./CircularGallery.vue";
|
import CircularGallery from "./CircularGallery.vue";
|
||||||
</script>`,
|
</script>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@content/Components/DecayCard/DecayCard.vue?raw'
|
import code from '@content/Components/DecayCard/DecayCard.vue?raw';
|
||||||
import type { CodeObject } from '../../../types/code'
|
import type { CodeObject } from '../../../types/code';
|
||||||
|
|
||||||
export const decayCard: CodeObject = {
|
export const decayCard: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/DecayCard`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/DecayCard`,
|
||||||
@@ -20,4 +20,4 @@ export const decayCard: CodeObject = {
|
|||||||
import DecayCard from "./DecayCard.vue";
|
import DecayCard from "./DecayCard.vue";
|
||||||
</script>`,
|
</script>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@content/Components/Dock/Dock.vue?raw'
|
import code from '@content/Components/Dock/Dock.vue?raw';
|
||||||
import type { CodeObject } from '../../../types/code'
|
import type { CodeObject } from '../../../types/code';
|
||||||
|
|
||||||
export const dock: CodeObject = {
|
export const dock: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/Dock`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/Dock`,
|
||||||
@@ -44,4 +44,4 @@ export const dock: CodeObject = {
|
|||||||
];
|
];
|
||||||
</script>`,
|
</script>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@content/Components/ElasticSlider/ElasticSlider.vue?raw'
|
import code from '@content/Components/ElasticSlider/ElasticSlider.vue?raw';
|
||||||
import type { CodeObject } from '../../../types/code'
|
import type { CodeObject } from '../../../types/code';
|
||||||
|
|
||||||
export const elasticSlider: CodeObject = {
|
export const elasticSlider: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/ElasticSlider`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/ElasticSlider`,
|
||||||
@@ -27,4 +27,4 @@ export const elasticSlider: CodeObject = {
|
|||||||
import ElasticSlider from "./ElasticSlider.vue";
|
import ElasticSlider from "./ElasticSlider.vue";
|
||||||
</script>`,
|
</script>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@content/Components/FlowingMenu/FlowingMenu.vue?raw'
|
import code from '@content/Components/FlowingMenu/FlowingMenu.vue?raw';
|
||||||
import type { CodeObject } from '../../../types/code'
|
import type { CodeObject } from '../../../types/code';
|
||||||
|
|
||||||
export const flowingMenu: CodeObject = {
|
export const flowingMenu: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/FlowingMenu`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/FlowingMenu`,
|
||||||
@@ -19,4 +19,4 @@ export const flowingMenu: CodeObject = {
|
|||||||
];
|
];
|
||||||
</script>`,
|
</script>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@content/Components/FlyingPosters/FlyingPosters.vue?raw'
|
import code from '@content/Components/FlyingPosters/FlyingPosters.vue?raw';
|
||||||
import type { CodeObject } from '../../../types/code'
|
import type { CodeObject } from '../../../types/code';
|
||||||
|
|
||||||
export const flyingPosters: CodeObject = {
|
export const flyingPosters: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/FlyingPosters`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/FlyingPosters`,
|
||||||
@@ -34,4 +34,4 @@ export const flyingPosters: CodeObject = {
|
|||||||
];
|
];
|
||||||
</script>`,
|
</script>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@content/Components/GlassIcons/GlassIcons.vue?raw'
|
import code from '@content/Components/GlassIcons/GlassIcons.vue?raw';
|
||||||
import type { CodeObject } from '../../../types/code'
|
import type { CodeObject } from '../../../types/code';
|
||||||
|
|
||||||
export const glassIcons: CodeObject = {
|
export const glassIcons: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/GlassIcons`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/GlassIcons`,
|
||||||
@@ -20,4 +20,4 @@ export const glassIcons: CodeObject = {
|
|||||||
];
|
];
|
||||||
</script>`,
|
</script>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@content/Components/GooeyNav/GooeyNav.vue?raw'
|
import code from '@content/Components/GooeyNav/GooeyNav.vue?raw';
|
||||||
import type { CodeObject } from '../../../types/code'
|
import type { CodeObject } from '../../../types/code';
|
||||||
|
|
||||||
export const gooeyNav: CodeObject = {
|
export const gooeyNav: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/GooeyNav`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/GooeyNav`,
|
||||||
@@ -37,4 +37,4 @@ export const gooeyNav: CodeObject = {
|
|||||||
}
|
}
|
||||||
</style>`,
|
</style>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@content/Components/InfiniteScroll/InfiniteScroll.vue?raw'
|
import code from '@content/Components/InfiniteScroll/InfiniteScroll.vue?raw';
|
||||||
import type { CodeObject } from '../../../types/code'
|
import type { CodeObject } from '../../../types/code';
|
||||||
|
|
||||||
export const infiniteScroll: CodeObject = {
|
export const infiniteScroll: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/InfiniteScroll`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/InfiniteScroll`,
|
||||||
@@ -31,4 +31,4 @@ export const infiniteScroll: CodeObject = {
|
|||||||
];
|
];
|
||||||
</script>`,
|
</script>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@content/Components/Masonry/Masonry.vue?raw'
|
import code from '@content/Components/Masonry/Masonry.vue?raw';
|
||||||
import type { CodeObject } from '../../../types/code'
|
import type { CodeObject } from '../../../types/code';
|
||||||
|
|
||||||
export const masonry: CodeObject = {
|
export const masonry: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/Masonry`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/Masonry`,
|
||||||
@@ -29,4 +29,4 @@ const items = ref([
|
|||||||
])
|
])
|
||||||
</script>`,
|
</script>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@content/Components/PixelCard/PixelCard.vue?raw'
|
import code from '@content/Components/PixelCard/PixelCard.vue?raw';
|
||||||
import type { CodeObject } from '../../../types/code'
|
import type { CodeObject } from '../../../types/code';
|
||||||
|
|
||||||
export const pixelCard: CodeObject = {
|
export const pixelCard: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/PixelCard`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/PixelCard`,
|
||||||
@@ -18,4 +18,4 @@ export const pixelCard: CodeObject = {
|
|||||||
import PixelCard from "./PixelCard.vue";
|
import PixelCard from "./PixelCard.vue";
|
||||||
</script>`,
|
</script>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@content/Components/ProfileCard/ProfileCard.vue?raw'
|
import code from '@content/Components/ProfileCard/ProfileCard.vue?raw';
|
||||||
import type { CodeObject } from '../../../types/code'
|
import type { CodeObject } from '../../../types/code';
|
||||||
|
|
||||||
export const profileCard: CodeObject = {
|
export const profileCard: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/ProfileCard`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/ProfileCard`,
|
||||||
@@ -28,4 +28,4 @@ export const profileCard: CodeObject = {
|
|||||||
};
|
};
|
||||||
</script>`,
|
</script>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@content/Components/SpotlightCard/SpotlightCard.vue?raw'
|
import code from '@content/Components/SpotlightCard/SpotlightCard.vue?raw';
|
||||||
import type { CodeObject } from '../../../types/code'
|
import type { CodeObject } from '../../../types/code';
|
||||||
|
|
||||||
export const spotlightCard: CodeObject = {
|
export const spotlightCard: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/SpotlightCard`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/SpotlightCard`,
|
||||||
@@ -16,4 +16,4 @@ export const spotlightCard: CodeObject = {
|
|||||||
import SpotlightCard from "./SpotlightCard.vue";
|
import SpotlightCard from "./SpotlightCard.vue";
|
||||||
</script>`,
|
</script>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@content/Components/TiltedCard/TiltedCard.vue?raw'
|
import code from '@content/Components/TiltedCard/TiltedCard.vue?raw';
|
||||||
import type { CodeObject } from '../../../types/code'
|
import type { CodeObject } from '../../../types/code';
|
||||||
|
|
||||||
export const tiltedCard: CodeObject = {
|
export const tiltedCard: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/TiltedCard`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/TiltedCard`,
|
||||||
@@ -31,4 +31,4 @@ export const tiltedCard: CodeObject = {
|
|||||||
import TiltedCard from "./TiltedCard.vue";
|
import TiltedCard from "./TiltedCard.vue";
|
||||||
</script>`,
|
</script>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@content/TextAnimations/BlurText/BlurText.vue?raw'
|
import code from '@content/TextAnimations/BlurText/BlurText.vue?raw';
|
||||||
import type { CodeObject } from '../../../types/code'
|
import type { CodeObject } from '../../../types/code';
|
||||||
|
|
||||||
export const blurText: CodeObject = {
|
export const blurText: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/BlurText`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/BlurText`,
|
||||||
@@ -26,4 +26,4 @@ export const blurText: CodeObject = {
|
|||||||
};
|
};
|
||||||
</script>`,
|
</script>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@content/TextAnimations/CircularText/CircularText.vue?raw'
|
import code from '@content/TextAnimations/CircularText/CircularText.vue?raw';
|
||||||
import type { CodeObject } from '../../../types/code'
|
import type { CodeObject } from '../../../types/code';
|
||||||
|
|
||||||
export const circularText: CodeObject = {
|
export const circularText: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/CircularText`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/CircularText`,
|
||||||
@@ -17,4 +17,4 @@ export const circularText: CodeObject = {
|
|||||||
import CircularText from "./CircularText.vue";
|
import CircularText from "./CircularText.vue";
|
||||||
</script>`,
|
</script>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@content/TextAnimations/CurvedLoop/CurvedLoop.vue?raw'
|
import code from '@content/TextAnimations/CurvedLoop/CurvedLoop.vue?raw';
|
||||||
import type { CodeObject } from '../../../types/code'
|
import type { CodeObject } from '../../../types/code';
|
||||||
|
|
||||||
export const curvedLoop: CodeObject = {
|
export const curvedLoop: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/CurvedLoop`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/CurvedLoop`,
|
||||||
@@ -17,4 +17,4 @@ export const curvedLoop: CodeObject = {
|
|||||||
import CurvedLoop from "./CurvedLoop.vue";
|
import CurvedLoop from "./CurvedLoop.vue";
|
||||||
</script>`,
|
</script>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@/content/TextAnimations/DecryptedText/DecryptedText.vue?raw'
|
import code from '@/content/TextAnimations/DecryptedText/DecryptedText.vue?raw';
|
||||||
import type { CodeObject } from '@/types/code'
|
import type { CodeObject } from '@/types/code';
|
||||||
|
|
||||||
export const decryptedText: CodeObject = {
|
export const decryptedText: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/DecryptedText`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/DecryptedText`,
|
||||||
@@ -21,4 +21,4 @@ export const decryptedText: CodeObject = {
|
|||||||
import DecryptedText from "./DecryptedText.vue";
|
import DecryptedText from "./DecryptedText.vue";
|
||||||
</script>`,
|
</script>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@/content/TextAnimations/FallingText/FallingText.vue?raw'
|
import code from '@/content/TextAnimations/FallingText/FallingText.vue?raw';
|
||||||
import type { CodeObject } from '@/types/code'
|
import type { CodeObject } from '@/types/code';
|
||||||
|
|
||||||
export const fallingText: CodeObject = {
|
export const fallingText: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/FallingText`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/FallingText`,
|
||||||
@@ -19,4 +19,4 @@ export const fallingText: CodeObject = {
|
|||||||
import FallingText from "./FallingText.vue";
|
import FallingText from "./FallingText.vue";
|
||||||
</script>`,
|
</script>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@content/TextAnimations/FuzzyText/FuzzyText.vue?raw'
|
import code from '@content/TextAnimations/FuzzyText/FuzzyText.vue?raw';
|
||||||
import type { CodeObject } from '../../../types/code'
|
import type { CodeObject } from '../../../types/code';
|
||||||
|
|
||||||
export const fuzzyText: CodeObject = {
|
export const fuzzyText: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/FuzzyText`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/FuzzyText`,
|
||||||
@@ -19,4 +19,4 @@ export const fuzzyText: CodeObject = {
|
|||||||
import FuzzyText from "./FuzzyText.vue";
|
import FuzzyText from "./FuzzyText.vue";
|
||||||
</script>`,
|
</script>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@content/TextAnimations/GradientText/GradientText.vue?raw'
|
import code from '@content/TextAnimations/GradientText/GradientText.vue?raw';
|
||||||
import type { CodeObject } from '../../../types/code'
|
import type { CodeObject } from '../../../types/code';
|
||||||
|
|
||||||
export const gradientText: CodeObject = {
|
export const gradientText: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/GradientText`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/GradientText`,
|
||||||
@@ -17,4 +17,4 @@ export const gradientText: CodeObject = {
|
|||||||
import GradientText from "./GradientText.vue";
|
import GradientText from "./GradientText.vue";
|
||||||
</script>`,
|
</script>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@content/TextAnimations/ScrollFloat/ScrollFloat.vue?raw'
|
import code from '@content/TextAnimations/ScrollFloat/ScrollFloat.vue?raw';
|
||||||
import type { CodeObject } from '../../../types/code'
|
import type { CodeObject } from '../../../types/code';
|
||||||
|
|
||||||
export const scrollFloatCode: CodeObject = {
|
export const scrollFloatCode: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/ScrollFloat`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/ScrollFloat`,
|
||||||
@@ -22,4 +22,4 @@ export const scrollFloatCode: CodeObject = {
|
|||||||
import ScrollFloat from "./ScrollFloat.vue";
|
import ScrollFloat from "./ScrollFloat.vue";
|
||||||
</script>`,
|
</script>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@content/TextAnimations/ShinyText/ShinyText.vue?raw'
|
import code from '@content/TextAnimations/ShinyText/ShinyText.vue?raw';
|
||||||
import type { CodeObject } from '../../../types/code'
|
import type { CodeObject } from '../../../types/code';
|
||||||
|
|
||||||
export const shinyText: CodeObject = {
|
export const shinyText: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/ShinyText`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/ShinyText`,
|
||||||
@@ -16,4 +16,4 @@ export const shinyText: CodeObject = {
|
|||||||
import ShinyText from "./ShinyText.vue";
|
import ShinyText from "./ShinyText.vue";
|
||||||
</script>`,
|
</script>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Fun fact: this is the first component ever made for Vue Bits!
|
// Fun fact: this is the first component ever made for Vue Bits!
|
||||||
import code from '@content/TextAnimations/SplitText/SplitText.vue?raw'
|
import code from '@content/TextAnimations/SplitText/SplitText.vue?raw';
|
||||||
import type { CodeObject } from '../../../types/code'
|
import type { CodeObject } from '../../../types/code';
|
||||||
|
|
||||||
export const splitText: CodeObject = {
|
export const splitText: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/SplitText`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/SplitText`,
|
||||||
@@ -30,4 +30,4 @@ export const splitText: CodeObject = {
|
|||||||
};
|
};
|
||||||
</script>`,
|
</script>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@/content/TextAnimations/TextCursor/TextCursor.vue?raw'
|
import code from '@/content/TextAnimations/TextCursor/TextCursor.vue?raw';
|
||||||
import type { CodeObject } from '@/types/code'
|
import type { CodeObject } from '@/types/code';
|
||||||
|
|
||||||
export const textCursor: CodeObject = {
|
export const textCursor: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/TextCursor`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/TextCursor`,
|
||||||
@@ -21,4 +21,4 @@ export const textCursor: CodeObject = {
|
|||||||
import TextCursor from "./TextCursor.vue";
|
import TextCursor from "./TextCursor.vue";
|
||||||
</script>`,
|
</script>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@content/TextAnimations/TextPressure/TextPressure.vue?raw'
|
import code from '@content/TextAnimations/TextPressure/TextPressure.vue?raw';
|
||||||
import type { CodeObject } from '../../../types/code'
|
import type { CodeObject } from '../../../types/code';
|
||||||
|
|
||||||
export const textPressure: CodeObject = {
|
export const textPressure: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/TextPressure`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/TextPressure`,
|
||||||
@@ -22,4 +22,4 @@ export const textPressure: CodeObject = {
|
|||||||
import TextPressure from "./TextPressure.vue";
|
import TextPressure from "./TextPressure.vue";
|
||||||
</script>`,
|
</script>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@/content/TextAnimations/TextTrail/TextTrail.vue?raw'
|
import code from '@/content/TextAnimations/TextTrail/TextTrail.vue?raw';
|
||||||
import type { CodeObject } from '@/types/code'
|
import type { CodeObject } from '@/types/code';
|
||||||
|
|
||||||
export const textTrail: CodeObject = {
|
export const textTrail: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/TextTrail`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/TextTrail`,
|
||||||
@@ -20,4 +20,4 @@ export const textTrail: CodeObject = {
|
|||||||
import TextTrail from "./TextTrail.vue";
|
import TextTrail from "./TextTrail.vue";
|
||||||
</script>`,
|
</script>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from "@/content/TextAnimations/TrueFocus/TrueFocus.vue?raw";
|
import code from '@/content/TextAnimations/TrueFocus/TrueFocus.vue?raw';
|
||||||
import type { CodeObject } from "../../../types/code";
|
import type { CodeObject } from '../../../types/code';
|
||||||
|
|
||||||
export const trueFocus: CodeObject = {
|
export const trueFocus: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/TrueFocus`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/TrueFocus`,
|
||||||
@@ -18,5 +18,5 @@ export const trueFocus: CodeObject = {
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import TrueFocus from "./TrueFocus.vue";
|
import TrueFocus from "./TrueFocus.vue";
|
||||||
</script>`,
|
</script>`,
|
||||||
code,
|
code
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onUnmounted, watch } from 'vue'
|
import { ref, onMounted, onUnmounted, watch } from 'vue';
|
||||||
import { gsap } from 'gsap'
|
import { gsap } from 'gsap';
|
||||||
import { ScrollTrigger } from 'gsap/ScrollTrigger'
|
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
||||||
|
|
||||||
gsap.registerPlugin(ScrollTrigger)
|
gsap.registerPlugin(ScrollTrigger);
|
||||||
|
|
||||||
interface AnimatedContentProps {
|
interface AnimatedContentProps {
|
||||||
distance?: number
|
distance?: number;
|
||||||
direction?: 'vertical' | 'horizontal'
|
direction?: 'vertical' | 'horizontal';
|
||||||
reverse?: boolean
|
reverse?: boolean;
|
||||||
duration?: number
|
duration?: number;
|
||||||
ease?: string | ((progress: number) => number)
|
ease?: string | ((progress: number) => number);
|
||||||
initialOpacity?: number
|
initialOpacity?: number;
|
||||||
animateOpacity?: boolean
|
animateOpacity?: boolean;
|
||||||
scale?: number
|
scale?: number;
|
||||||
threshold?: number
|
threshold?: number;
|
||||||
delay?: number
|
delay?: number;
|
||||||
className?: string
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<AnimatedContentProps>(), {
|
const props = withDefaults(defineProps<AnimatedContentProps>(), {
|
||||||
@@ -31,27 +31,27 @@ const props = withDefaults(defineProps<AnimatedContentProps>(), {
|
|||||||
threshold: 0.1,
|
threshold: 0.1,
|
||||||
delay: 0,
|
delay: 0,
|
||||||
className: ''
|
className: ''
|
||||||
})
|
});
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
complete: []
|
complete: [];
|
||||||
}>()
|
}>();
|
||||||
|
|
||||||
const containerRef = ref<HTMLDivElement>()
|
const containerRef = ref<HTMLDivElement>();
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const el = containerRef.value
|
const el = containerRef.value;
|
||||||
if (!el) return
|
if (!el) return;
|
||||||
|
|
||||||
const axis = props.direction === 'horizontal' ? 'x' : 'y'
|
const axis = props.direction === 'horizontal' ? 'x' : 'y';
|
||||||
const offset = props.reverse ? -props.distance : props.distance
|
const offset = props.reverse ? -props.distance : props.distance;
|
||||||
const startPct = (1 - props.threshold) * 100
|
const startPct = (1 - props.threshold) * 100;
|
||||||
|
|
||||||
gsap.set(el, {
|
gsap.set(el, {
|
||||||
[axis]: offset,
|
[axis]: offset,
|
||||||
scale: props.scale,
|
scale: props.scale,
|
||||||
opacity: props.animateOpacity ? props.initialOpacity : 1,
|
opacity: props.animateOpacity ? props.initialOpacity : 1
|
||||||
})
|
});
|
||||||
|
|
||||||
gsap.to(el, {
|
gsap.to(el, {
|
||||||
[axis]: 0,
|
[axis]: 0,
|
||||||
@@ -65,10 +65,10 @@ onMounted(() => {
|
|||||||
trigger: el,
|
trigger: el,
|
||||||
start: `top ${startPct}%`,
|
start: `top ${startPct}%`,
|
||||||
toggleActions: 'play none none none',
|
toggleActions: 'play none none none',
|
||||||
once: true,
|
once: true
|
||||||
},
|
}
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => [
|
() => [
|
||||||
@@ -81,24 +81,24 @@ watch(
|
|||||||
props.animateOpacity,
|
props.animateOpacity,
|
||||||
props.scale,
|
props.scale,
|
||||||
props.threshold,
|
props.threshold,
|
||||||
props.delay,
|
props.delay
|
||||||
],
|
],
|
||||||
() => {
|
() => {
|
||||||
const el = containerRef.value
|
const el = containerRef.value;
|
||||||
if (!el) return
|
if (!el) return;
|
||||||
|
|
||||||
ScrollTrigger.getAll().forEach((t) => t.kill())
|
ScrollTrigger.getAll().forEach(t => t.kill());
|
||||||
gsap.killTweensOf(el)
|
gsap.killTweensOf(el);
|
||||||
|
|
||||||
const axis = props.direction === 'horizontal' ? 'x' : 'y'
|
const axis = props.direction === 'horizontal' ? 'x' : 'y';
|
||||||
const offset = props.reverse ? -props.distance : props.distance
|
const offset = props.reverse ? -props.distance : props.distance;
|
||||||
const startPct = (1 - props.threshold) * 100
|
const startPct = (1 - props.threshold) * 100;
|
||||||
|
|
||||||
gsap.set(el, {
|
gsap.set(el, {
|
||||||
[axis]: offset,
|
[axis]: offset,
|
||||||
scale: props.scale,
|
scale: props.scale,
|
||||||
opacity: props.animateOpacity ? props.initialOpacity : 1,
|
opacity: props.animateOpacity ? props.initialOpacity : 1
|
||||||
})
|
});
|
||||||
|
|
||||||
gsap.to(el, {
|
gsap.to(el, {
|
||||||
[axis]: 0,
|
[axis]: 0,
|
||||||
@@ -112,27 +112,24 @@ watch(
|
|||||||
trigger: el,
|
trigger: el,
|
||||||
start: `top ${startPct}%`,
|
start: `top ${startPct}%`,
|
||||||
toggleActions: 'play none none none',
|
toggleActions: 'play none none none',
|
||||||
once: true,
|
once: true
|
||||||
},
|
}
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
{ deep: true }
|
{ deep: true }
|
||||||
)
|
);
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
const el = containerRef.value
|
const el = containerRef.value;
|
||||||
if (el) {
|
if (el) {
|
||||||
ScrollTrigger.getAll().forEach((t) => t.kill())
|
ScrollTrigger.getAll().forEach(t => t.kill());
|
||||||
gsap.killTweensOf(el)
|
gsap.killTweensOf(el);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div ref="containerRef" :class="`animated-content ${props.className}`">
|
||||||
ref="containerRef"
|
|
||||||
:class="`animated-content ${props.className}`"
|
|
||||||
>
|
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,35 +1,29 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div ref="containerRef" class="relative w-full h-full" @click="handleClick">
|
||||||
ref="containerRef"
|
<canvas ref="canvasRef" class="absolute inset-0 pointer-events-none" />
|
||||||
class="relative w-full h-full"
|
|
||||||
@click="handleClick"
|
|
||||||
>
|
|
||||||
<canvas
|
|
||||||
ref="canvasRef"
|
|
||||||
class="absolute inset-0 pointer-events-none"
|
|
||||||
/>
|
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onUnmounted, computed, watch } from 'vue'
|
import { ref, onMounted, onUnmounted, computed, watch } from 'vue';
|
||||||
|
|
||||||
interface Spark {
|
interface Spark {
|
||||||
x: number
|
x: number;
|
||||||
y: number
|
y: number;
|
||||||
angle: number
|
angle: number;
|
||||||
startTime: number
|
startTime: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
sparkColor?: string
|
sparkColor?: string;
|
||||||
sparkSize?: number
|
sparkSize?: number;
|
||||||
sparkRadius?: number
|
sparkRadius?: number;
|
||||||
sparkCount?: number
|
sparkCount?: number;
|
||||||
duration?: number
|
duration?: number;
|
||||||
easing?: "linear" | "ease-in" | "ease-out" | "ease-in-out"
|
easing?: 'linear' | 'ease-in' | 'ease-out' | 'ease-in-out';
|
||||||
extraScale?: number
|
extraScale?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
@@ -40,149 +34,152 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
duration: 400,
|
duration: 400,
|
||||||
easing: 'ease-out',
|
easing: 'ease-out',
|
||||||
extraScale: 1.0
|
extraScale: 1.0
|
||||||
})
|
});
|
||||||
|
|
||||||
const containerRef = ref<HTMLDivElement | null>(null)
|
const containerRef = ref<HTMLDivElement | null>(null);
|
||||||
const canvasRef = ref<HTMLCanvasElement | null>(null)
|
const canvasRef = ref<HTMLCanvasElement | null>(null);
|
||||||
const sparks = ref<Spark[]>([])
|
const sparks = ref<Spark[]>([]);
|
||||||
const startTimeRef = ref<number | null>(null)
|
const startTimeRef = ref<number | null>(null);
|
||||||
const animationId = ref<number | null>(null)
|
const animationId = ref<number | null>(null);
|
||||||
|
|
||||||
const easeFunc = computed(() => {
|
const easeFunc = computed(() => {
|
||||||
return (t: number) => {
|
return (t: number) => {
|
||||||
switch (props.easing) {
|
switch (props.easing) {
|
||||||
case "linear":
|
case 'linear':
|
||||||
return t
|
return t;
|
||||||
case "ease-in":
|
case 'ease-in':
|
||||||
return t * t
|
return t * t;
|
||||||
case "ease-in-out":
|
case 'ease-in-out':
|
||||||
return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t
|
return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
|
||||||
default:
|
default:
|
||||||
return t * (2 - t)
|
return t * (2 - t);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
|
|
||||||
const handleClick = (e: MouseEvent) => {
|
const handleClick = (e: MouseEvent) => {
|
||||||
const canvas = canvasRef.value
|
const canvas = canvasRef.value;
|
||||||
if (!canvas) return
|
if (!canvas) return;
|
||||||
const rect = canvas.getBoundingClientRect()
|
const rect = canvas.getBoundingClientRect();
|
||||||
const x = e.clientX - rect.left
|
const x = e.clientX - rect.left;
|
||||||
const y = e.clientY - rect.top
|
const y = e.clientY - rect.top;
|
||||||
|
|
||||||
const now = performance.now()
|
const now = performance.now();
|
||||||
const newSparks: Spark[] = Array.from({ length: props.sparkCount }, (_, i) => ({
|
const newSparks: Spark[] = Array.from({ length: props.sparkCount }, (_, i) => ({
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
angle: (2 * Math.PI * i) / props.sparkCount,
|
angle: (2 * Math.PI * i) / props.sparkCount,
|
||||||
startTime: now,
|
startTime: now
|
||||||
}))
|
}));
|
||||||
|
|
||||||
sparks.value.push(...newSparks)
|
sparks.value.push(...newSparks);
|
||||||
}
|
};
|
||||||
|
|
||||||
const draw = (timestamp: number) => {
|
const draw = (timestamp: number) => {
|
||||||
if (!startTimeRef.value) {
|
if (!startTimeRef.value) {
|
||||||
startTimeRef.value = timestamp
|
startTimeRef.value = timestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
const canvas = canvasRef.value
|
const canvas = canvasRef.value;
|
||||||
const ctx = canvas?.getContext('2d')
|
const ctx = canvas?.getContext('2d');
|
||||||
if (!ctx || !canvas) return
|
if (!ctx || !canvas) return;
|
||||||
|
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
sparks.value = sparks.value.filter((spark: Spark) => {
|
sparks.value = sparks.value.filter((spark: Spark) => {
|
||||||
const elapsed = timestamp - spark.startTime
|
const elapsed = timestamp - spark.startTime;
|
||||||
if (elapsed >= props.duration) {
|
if (elapsed >= props.duration) {
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const progress = elapsed / props.duration
|
const progress = elapsed / props.duration;
|
||||||
const eased = easeFunc.value(progress)
|
const eased = easeFunc.value(progress);
|
||||||
|
|
||||||
const distance = eased * props.sparkRadius * props.extraScale
|
const distance = eased * props.sparkRadius * props.extraScale;
|
||||||
const lineLength = props.sparkSize * (1 - eased)
|
const lineLength = props.sparkSize * (1 - eased);
|
||||||
|
|
||||||
const x1 = spark.x + distance * Math.cos(spark.angle)
|
const x1 = spark.x + distance * Math.cos(spark.angle);
|
||||||
const y1 = spark.y + distance * Math.sin(spark.angle)
|
const y1 = spark.y + distance * Math.sin(spark.angle);
|
||||||
const x2 = spark.x + (distance + lineLength) * Math.cos(spark.angle)
|
const x2 = spark.x + (distance + lineLength) * Math.cos(spark.angle);
|
||||||
const y2 = spark.y + (distance + lineLength) * Math.sin(spark.angle)
|
const y2 = spark.y + (distance + lineLength) * Math.sin(spark.angle);
|
||||||
|
|
||||||
ctx.strokeStyle = props.sparkColor
|
ctx.strokeStyle = props.sparkColor;
|
||||||
ctx.lineWidth = 2
|
ctx.lineWidth = 2;
|
||||||
ctx.beginPath()
|
ctx.beginPath();
|
||||||
ctx.moveTo(x1, y1)
|
ctx.moveTo(x1, y1);
|
||||||
ctx.lineTo(x2, y2)
|
ctx.lineTo(x2, y2);
|
||||||
ctx.stroke()
|
ctx.stroke();
|
||||||
|
|
||||||
return true
|
return true;
|
||||||
})
|
});
|
||||||
|
|
||||||
animationId.value = requestAnimationFrame(draw)
|
animationId.value = requestAnimationFrame(draw);
|
||||||
}
|
};
|
||||||
|
|
||||||
const resizeCanvas = () => {
|
const resizeCanvas = () => {
|
||||||
const canvas = canvasRef.value
|
const canvas = canvasRef.value;
|
||||||
if (!canvas) return
|
if (!canvas) return;
|
||||||
|
|
||||||
const parent = canvas.parentElement
|
const parent = canvas.parentElement;
|
||||||
if (!parent) return
|
if (!parent) return;
|
||||||
|
|
||||||
const { width, height } = parent.getBoundingClientRect()
|
const { width, height } = parent.getBoundingClientRect();
|
||||||
if (canvas.width !== width || canvas.height !== height) {
|
if (canvas.width !== width || canvas.height !== height) {
|
||||||
canvas.width = width
|
canvas.width = width;
|
||||||
canvas.height = height
|
canvas.height = height;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
let resizeTimeout: number
|
let resizeTimeout: number;
|
||||||
|
|
||||||
const handleResize = () => {
|
const handleResize = () => {
|
||||||
clearTimeout(resizeTimeout)
|
clearTimeout(resizeTimeout);
|
||||||
resizeTimeout = setTimeout(resizeCanvas, 100)
|
resizeTimeout = setTimeout(resizeCanvas, 100);
|
||||||
}
|
};
|
||||||
|
|
||||||
let resizeObserver: ResizeObserver | null = null
|
let resizeObserver: ResizeObserver | null = null;
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const canvas = canvasRef.value
|
const canvas = canvasRef.value;
|
||||||
if (!canvas) return
|
if (!canvas) return;
|
||||||
|
|
||||||
const parent = canvas.parentElement
|
const parent = canvas.parentElement;
|
||||||
if (!parent) return
|
if (!parent) return;
|
||||||
|
|
||||||
resizeObserver = new ResizeObserver(handleResize)
|
resizeObserver = new ResizeObserver(handleResize);
|
||||||
resizeObserver.observe(parent)
|
resizeObserver.observe(parent);
|
||||||
|
|
||||||
resizeCanvas()
|
resizeCanvas();
|
||||||
|
|
||||||
animationId.value = requestAnimationFrame(draw)
|
animationId.value = requestAnimationFrame(draw);
|
||||||
})
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
if (resizeObserver) {
|
if (resizeObserver) {
|
||||||
resizeObserver.disconnect()
|
resizeObserver.disconnect();
|
||||||
}
|
}
|
||||||
clearTimeout(resizeTimeout)
|
clearTimeout(resizeTimeout);
|
||||||
|
|
||||||
if (animationId.value) {
|
|
||||||
cancelAnimationFrame(animationId.value)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
watch([
|
|
||||||
() => props.sparkColor,
|
|
||||||
() => props.sparkSize,
|
|
||||||
() => props.sparkRadius,
|
|
||||||
() => props.sparkCount,
|
|
||||||
() => props.duration,
|
|
||||||
easeFunc,
|
|
||||||
() => props.extraScale
|
|
||||||
], () => {
|
|
||||||
if (animationId.value) {
|
if (animationId.value) {
|
||||||
cancelAnimationFrame(animationId.value)
|
cancelAnimationFrame(animationId.value);
|
||||||
}
|
}
|
||||||
animationId.value = requestAnimationFrame(draw)
|
});
|
||||||
})
|
|
||||||
|
watch(
|
||||||
|
[
|
||||||
|
() => props.sparkColor,
|
||||||
|
() => props.sparkSize,
|
||||||
|
() => props.sparkRadius,
|
||||||
|
() => props.sparkCount,
|
||||||
|
() => props.duration,
|
||||||
|
easeFunc,
|
||||||
|
() => props.extraScale
|
||||||
|
],
|
||||||
|
() => {
|
||||||
|
if (animationId.value) {
|
||||||
|
cancelAnimationFrame(animationId.value);
|
||||||
|
}
|
||||||
|
animationId.value = requestAnimationFrame(draw);
|
||||||
|
}
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -3,161 +3,164 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onUnmounted, watch, computed } from 'vue'
|
import { ref, onMounted, onUnmounted, watch, computed } from 'vue';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
to: number
|
to: number;
|
||||||
from?: number
|
from?: number;
|
||||||
direction?: "up" | "down"
|
direction?: 'up' | 'down';
|
||||||
delay?: number
|
delay?: number;
|
||||||
duration?: number
|
duration?: number;
|
||||||
className?: string
|
className?: string;
|
||||||
startWhen?: boolean
|
startWhen?: boolean;
|
||||||
separator?: string
|
separator?: string;
|
||||||
onStart?: () => void
|
onStart?: () => void;
|
||||||
onEnd?: () => void
|
onEnd?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
from: 0,
|
from: 0,
|
||||||
direction: "up",
|
direction: 'up',
|
||||||
delay: 0,
|
delay: 0,
|
||||||
duration: 2,
|
duration: 2,
|
||||||
className: "",
|
className: '',
|
||||||
startWhen: true,
|
startWhen: true,
|
||||||
separator: ""
|
separator: ''
|
||||||
})
|
});
|
||||||
|
|
||||||
const elementRef = ref<HTMLSpanElement | null>(null)
|
const elementRef = ref<HTMLSpanElement | null>(null);
|
||||||
const currentValue = ref(props.direction === "down" ? props.to : props.from)
|
const currentValue = ref(props.direction === 'down' ? props.to : props.from);
|
||||||
const isInView = ref(false)
|
const isInView = ref(false);
|
||||||
const animationId = ref<number | null>(null)
|
const animationId = ref<number | null>(null);
|
||||||
const hasStarted = ref(false)
|
const hasStarted = ref(false);
|
||||||
|
|
||||||
let intersectionObserver: IntersectionObserver | null = null
|
let intersectionObserver: IntersectionObserver | null = null;
|
||||||
|
|
||||||
const damping = computed(() => 20 + 40 * (1 / props.duration))
|
const damping = computed(() => 20 + 40 * (1 / props.duration));
|
||||||
const stiffness = computed(() => 100 * (1 / props.duration))
|
const stiffness = computed(() => 100 * (1 / props.duration));
|
||||||
|
|
||||||
let velocity = 0
|
let velocity = 0;
|
||||||
let startTime = 0
|
let startTime = 0;
|
||||||
|
|
||||||
const formatNumber = (value: number) => {
|
const formatNumber = (value: number) => {
|
||||||
const options = {
|
const options = {
|
||||||
useGrouping: !!props.separator,
|
useGrouping: !!props.separator,
|
||||||
minimumFractionDigits: 0,
|
minimumFractionDigits: 0,
|
||||||
maximumFractionDigits: 0,
|
maximumFractionDigits: 0
|
||||||
}
|
};
|
||||||
|
|
||||||
const formattedNumber = Intl.NumberFormat("en-US", options).format(
|
const formattedNumber = Intl.NumberFormat('en-US', options).format(Number(value.toFixed(0)));
|
||||||
Number(value.toFixed(0))
|
|
||||||
)
|
|
||||||
|
|
||||||
return props.separator
|
return props.separator ? formattedNumber.replace(/,/g, props.separator) : formattedNumber;
|
||||||
? formattedNumber.replace(/,/g, props.separator)
|
};
|
||||||
: formattedNumber
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateDisplay = () => {
|
const updateDisplay = () => {
|
||||||
if (elementRef.value) {
|
if (elementRef.value) {
|
||||||
elementRef.value.textContent = formatNumber(currentValue.value)
|
elementRef.value.textContent = formatNumber(currentValue.value);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const springAnimation = (timestamp: number) => {
|
const springAnimation = (timestamp: number) => {
|
||||||
if (!startTime) startTime = timestamp
|
if (!startTime) startTime = timestamp;
|
||||||
|
|
||||||
const target = props.direction === "down" ? props.from : props.to
|
const target = props.direction === 'down' ? props.from : props.to;
|
||||||
const current = currentValue.value
|
const current = currentValue.value;
|
||||||
|
|
||||||
const displacement = target - current
|
const displacement = target - current;
|
||||||
const springForce = displacement * stiffness.value
|
const springForce = displacement * stiffness.value;
|
||||||
const dampingForce = velocity * damping.value
|
const dampingForce = velocity * damping.value;
|
||||||
const acceleration = springForce - dampingForce
|
const acceleration = springForce - dampingForce;
|
||||||
|
|
||||||
velocity += acceleration * 0.016 // Assuming 60fps
|
velocity += acceleration * 0.016; // Assuming 60fps
|
||||||
currentValue.value += velocity * 0.016
|
currentValue.value += velocity * 0.016;
|
||||||
|
|
||||||
updateDisplay()
|
updateDisplay();
|
||||||
|
|
||||||
if (Math.abs(displacement) > 0.01 || Math.abs(velocity) > 0.01) {
|
if (Math.abs(displacement) > 0.01 || Math.abs(velocity) > 0.01) {
|
||||||
animationId.value = requestAnimationFrame(springAnimation)
|
animationId.value = requestAnimationFrame(springAnimation);
|
||||||
} else {
|
} else {
|
||||||
currentValue.value = target
|
currentValue.value = target;
|
||||||
updateDisplay()
|
updateDisplay();
|
||||||
animationId.value = null
|
animationId.value = null;
|
||||||
|
|
||||||
if (props.onEnd) {
|
if (props.onEnd) {
|
||||||
props.onEnd()
|
props.onEnd();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const startAnimation = () => {
|
const startAnimation = () => {
|
||||||
if (hasStarted.value || !isInView.value || !props.startWhen) return
|
if (hasStarted.value || !isInView.value || !props.startWhen) return;
|
||||||
|
|
||||||
hasStarted.value = true
|
hasStarted.value = true;
|
||||||
|
|
||||||
if (props.onStart) {
|
if (props.onStart) {
|
||||||
props.onStart()
|
props.onStart();
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
startTime = 0
|
startTime = 0;
|
||||||
velocity = 0
|
velocity = 0;
|
||||||
animationId.value = requestAnimationFrame(springAnimation)
|
animationId.value = requestAnimationFrame(springAnimation);
|
||||||
}, props.delay * 1000)
|
}, props.delay * 1000);
|
||||||
}
|
};
|
||||||
|
|
||||||
const setupIntersectionObserver = () => {
|
const setupIntersectionObserver = () => {
|
||||||
if (!elementRef.value) return
|
if (!elementRef.value) return;
|
||||||
|
|
||||||
intersectionObserver = new IntersectionObserver(
|
intersectionObserver = new IntersectionObserver(
|
||||||
([entry]) => {
|
([entry]) => {
|
||||||
if (entry.isIntersecting && !isInView.value) {
|
if (entry.isIntersecting && !isInView.value) {
|
||||||
isInView.value = true
|
isInView.value = true;
|
||||||
startAnimation()
|
startAnimation();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
threshold: 0,
|
threshold: 0,
|
||||||
rootMargin: "0px"
|
rootMargin: '0px'
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
|
|
||||||
intersectionObserver.observe(elementRef.value)
|
intersectionObserver.observe(elementRef.value);
|
||||||
}
|
};
|
||||||
|
|
||||||
const cleanup = () => {
|
const cleanup = () => {
|
||||||
if (animationId.value) {
|
if (animationId.value) {
|
||||||
cancelAnimationFrame(animationId.value)
|
cancelAnimationFrame(animationId.value);
|
||||||
animationId.value = null
|
animationId.value = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (intersectionObserver) {
|
if (intersectionObserver) {
|
||||||
intersectionObserver.disconnect()
|
intersectionObserver.disconnect();
|
||||||
intersectionObserver = null
|
intersectionObserver = null;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
watch([() => props.from, () => props.to, () => props.direction], () => {
|
watch(
|
||||||
currentValue.value = props.direction === "down" ? props.to : props.from
|
[() => props.from, () => props.to, () => props.direction],
|
||||||
updateDisplay()
|
() => {
|
||||||
hasStarted.value = false
|
currentValue.value = props.direction === 'down' ? props.to : props.from;
|
||||||
}, { immediate: true })
|
updateDisplay();
|
||||||
|
hasStarted.value = false;
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
watch(() => props.startWhen, () => {
|
watch(
|
||||||
if (props.startWhen && isInView.value && !hasStarted.value) {
|
() => props.startWhen,
|
||||||
startAnimation()
|
() => {
|
||||||
|
if (props.startWhen && isInView.value && !hasStarted.value) {
|
||||||
|
startAnimation();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
updateDisplay()
|
updateDisplay();
|
||||||
setupIntersectionObserver()
|
setupIntersectionObserver();
|
||||||
})
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
cleanup()
|
cleanup();
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -2,46 +2,74 @@
|
|||||||
<div class="relative w-1/2 max-md:w-11/12 aspect-square" :style="wrapperStyle">
|
<div class="relative w-1/2 max-md:w-11/12 aspect-square" :style="wrapperStyle">
|
||||||
<div ref="sceneRef" class="grid w-full h-full" :style="sceneStyle">
|
<div ref="sceneRef" class="grid w-full h-full" :style="sceneStyle">
|
||||||
<template v-for="(_, r) in cells" :key="`row-${r}`">
|
<template v-for="(_, r) in cells" :key="`row-${r}`">
|
||||||
<div v-for="(__, c) in cells" :key="`${r}-${c}`"
|
<div
|
||||||
class="cube relative w-full h-full aspect-square [transform-style:preserve-3d]" :data-row="r" :data-col="c">
|
v-for="(__, c) in cells"
|
||||||
|
:key="`${r}-${c}`"
|
||||||
|
class="cube relative w-full h-full aspect-square [transform-style:preserve-3d]"
|
||||||
|
:data-row="r"
|
||||||
|
:data-col="c"
|
||||||
|
>
|
||||||
<span class="absolute pointer-events-none -inset-9" />
|
<span class="absolute pointer-events-none -inset-9" />
|
||||||
|
|
||||||
<div class="cube-face absolute inset-0 flex items-center justify-center" :style="{
|
<div
|
||||||
background: 'var(--cube-face-bg)',
|
class="cube-face absolute inset-0 flex items-center justify-center"
|
||||||
border: 'var(--cube-face-border)',
|
:style="{
|
||||||
boxShadow: 'var(--cube-face-shadow)',
|
background: 'var(--cube-face-bg)',
|
||||||
transform: 'translateY(-50%) rotateX(90deg)',
|
border: 'var(--cube-face-border)',
|
||||||
}" />
|
boxShadow: 'var(--cube-face-shadow)',
|
||||||
<div class="cube-face absolute inset-0 flex items-center justify-center" :style="{
|
transform: 'translateY(-50%) rotateX(90deg)'
|
||||||
background: 'var(--cube-face-bg)',
|
}"
|
||||||
border: 'var(--cube-face-border)',
|
/>
|
||||||
boxShadow: 'var(--cube-face-shadow)',
|
|
||||||
transform: 'translateY(50%) rotateX(-90deg)',
|
<div
|
||||||
}" />
|
class="cube-face absolute inset-0 flex items-center justify-center"
|
||||||
<div class="cube-face absolute inset-0 flex items-center justify-center" :style="{
|
:style="{
|
||||||
background: 'var(--cube-face-bg)',
|
background: 'var(--cube-face-bg)',
|
||||||
border: 'var(--cube-face-border)',
|
border: 'var(--cube-face-border)',
|
||||||
boxShadow: 'var(--cube-face-shadow)',
|
boxShadow: 'var(--cube-face-shadow)',
|
||||||
transform: 'translateX(-50%) rotateY(-90deg)',
|
transform: 'translateY(50%) rotateX(-90deg)'
|
||||||
}" />
|
}"
|
||||||
<div class="cube-face absolute inset-0 flex items-center justify-center" :style="{
|
/>
|
||||||
background: 'var(--cube-face-bg)',
|
|
||||||
border: 'var(--cube-face-border)',
|
<div
|
||||||
boxShadow: 'var(--cube-face-shadow)',
|
class="cube-face absolute inset-0 flex items-center justify-center"
|
||||||
transform: 'translateX(50%) rotateY(90deg)',
|
:style="{
|
||||||
}" />
|
background: 'var(--cube-face-bg)',
|
||||||
<div class="cube-face absolute inset-0 flex items-center justify-center" :style="{
|
border: 'var(--cube-face-border)',
|
||||||
background: 'var(--cube-face-bg)',
|
boxShadow: 'var(--cube-face-shadow)',
|
||||||
border: 'var(--cube-face-border)',
|
transform: 'translateX(-50%) rotateY(-90deg)'
|
||||||
boxShadow: 'var(--cube-face-shadow)',
|
}"
|
||||||
transform: 'rotateY(-90deg) translateX(50%) rotateY(90deg)',
|
/>
|
||||||
}" />
|
|
||||||
<div class="cube-face absolute inset-0 flex items-center justify-center" :style="{
|
<div
|
||||||
background: 'var(--cube-face-bg)',
|
class="cube-face absolute inset-0 flex items-center justify-center"
|
||||||
border: 'var(--cube-face-border)',
|
:style="{
|
||||||
boxShadow: 'var(--cube-face-shadow)',
|
background: 'var(--cube-face-bg)',
|
||||||
transform: 'rotateY(90deg) translateX(-50%) rotateY(-90deg)',
|
border: 'var(--cube-face-border)',
|
||||||
}" />
|
boxShadow: 'var(--cube-face-shadow)',
|
||||||
|
transform: 'translateX(50%) rotateY(90deg)'
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="cube-face absolute inset-0 flex items-center justify-center"
|
||||||
|
:style="{
|
||||||
|
background: 'var(--cube-face-bg)',
|
||||||
|
border: 'var(--cube-face-border)',
|
||||||
|
boxShadow: 'var(--cube-face-shadow)',
|
||||||
|
transform: 'rotateY(-90deg) translateX(50%) rotateY(90deg)'
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="cube-face absolute inset-0 flex items-center justify-center"
|
||||||
|
:style="{
|
||||||
|
background: 'var(--cube-face-bg)',
|
||||||
|
border: 'var(--cube-face-border)',
|
||||||
|
boxShadow: 'var(--cube-face-shadow)',
|
||||||
|
transform: 'rotateY(90deg) translateX(-50%) rotateY(-90deg)'
|
||||||
|
}"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@@ -49,34 +77,34 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted, onUnmounted, withDefaults } from 'vue'
|
import { ref, computed, onMounted, onUnmounted, withDefaults } from 'vue';
|
||||||
import gsap from 'gsap'
|
import gsap from 'gsap';
|
||||||
|
|
||||||
interface Gap {
|
interface Gap {
|
||||||
row: number
|
row: number;
|
||||||
col: number
|
col: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Duration {
|
interface Duration {
|
||||||
enter: number
|
enter: number;
|
||||||
leave: number
|
leave: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
gridSize?: number
|
gridSize?: number;
|
||||||
cubeSize?: number
|
cubeSize?: number;
|
||||||
maxAngle?: number
|
maxAngle?: number;
|
||||||
radius?: number
|
radius?: number;
|
||||||
easing?: gsap.EaseString
|
easing?: gsap.EaseString;
|
||||||
duration?: Duration
|
duration?: Duration;
|
||||||
cellGap?: number | Gap
|
cellGap?: number | Gap;
|
||||||
borderStyle?: string
|
borderStyle?: string;
|
||||||
faceColor?: string
|
faceColor?: string;
|
||||||
shadow?: boolean | string
|
shadow?: boolean | string;
|
||||||
autoAnimate?: boolean
|
autoAnimate?: boolean;
|
||||||
rippleOnClick?: boolean
|
rippleOnClick?: boolean;
|
||||||
rippleColor?: string
|
rippleColor?: string;
|
||||||
rippleSpeed?: number
|
rippleSpeed?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
@@ -91,37 +119,37 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
autoAnimate: true,
|
autoAnimate: true,
|
||||||
rippleOnClick: true,
|
rippleOnClick: true,
|
||||||
rippleColor: '#fff',
|
rippleColor: '#fff',
|
||||||
rippleSpeed: 2,
|
rippleSpeed: 2
|
||||||
})
|
});
|
||||||
|
|
||||||
const sceneRef = ref<HTMLDivElement | null>(null)
|
const sceneRef = ref<HTMLDivElement | null>(null);
|
||||||
const rafRef = ref<number | null>(null)
|
const rafRef = ref<number | null>(null);
|
||||||
const idleTimerRef = ref<number | null>(null)
|
const idleTimerRef = ref<number | null>(null);
|
||||||
const userActiveRef = ref(false)
|
const userActiveRef = ref(false);
|
||||||
const simPosRef = ref<{ x: number; y: number }>({ x: 0, y: 0 })
|
const simPosRef = ref<{ x: number; y: number }>({ x: 0, y: 0 });
|
||||||
const simTargetRef = ref<{ x: number; y: number }>({ x: 0, y: 0 })
|
const simTargetRef = ref<{ x: number; y: number }>({ x: 0, y: 0 });
|
||||||
const simRAFRef = ref<number | null>(null)
|
const simRAFRef = ref<number | null>(null);
|
||||||
|
|
||||||
const colGap = computed(() => {
|
const colGap = computed(() => {
|
||||||
return typeof props.cellGap === 'number'
|
return typeof props.cellGap === 'number'
|
||||||
? `${props.cellGap}px`
|
? `${props.cellGap}px`
|
||||||
: (props.cellGap as Gap)?.col !== undefined
|
: (props.cellGap as Gap)?.col !== undefined
|
||||||
? `${(props.cellGap as Gap).col}px`
|
? `${(props.cellGap as Gap).col}px`
|
||||||
: '5%'
|
: '5%';
|
||||||
})
|
});
|
||||||
|
|
||||||
const rowGap = computed(() => {
|
const rowGap = computed(() => {
|
||||||
return typeof props.cellGap === 'number'
|
return typeof props.cellGap === 'number'
|
||||||
? `${props.cellGap}px`
|
? `${props.cellGap}px`
|
||||||
: (props.cellGap as Gap)?.row !== undefined
|
: (props.cellGap as Gap)?.row !== undefined
|
||||||
? `${(props.cellGap as Gap).row}px`
|
? `${(props.cellGap as Gap).row}px`
|
||||||
: '5%'
|
: '5%';
|
||||||
})
|
});
|
||||||
|
|
||||||
const enterDur = computed(() => props.duration.enter)
|
const enterDur = computed(() => props.duration.enter);
|
||||||
const leaveDur = computed(() => props.duration.leave)
|
const leaveDur = computed(() => props.duration.leave);
|
||||||
|
|
||||||
const cells = computed(() => Array.from({ length: props.gridSize }))
|
const cells = computed(() => Array.from({ length: props.gridSize }));
|
||||||
|
|
||||||
const sceneStyle = computed(() => ({
|
const sceneStyle = computed(() => ({
|
||||||
gridTemplateColumns: props.cubeSize
|
gridTemplateColumns: props.cubeSize
|
||||||
@@ -133,189 +161,184 @@ const sceneStyle = computed(() => ({
|
|||||||
columnGap: colGap.value,
|
columnGap: colGap.value,
|
||||||
rowGap: rowGap.value,
|
rowGap: rowGap.value,
|
||||||
perspective: '99999999px',
|
perspective: '99999999px',
|
||||||
gridAutoRows: '1fr',
|
gridAutoRows: '1fr'
|
||||||
}))
|
}));
|
||||||
|
|
||||||
const wrapperStyle = computed(() => ({
|
const wrapperStyle = computed(() => ({
|
||||||
'--cube-face-border': props.borderStyle,
|
'--cube-face-border': props.borderStyle,
|
||||||
'--cube-face-bg': props.faceColor,
|
'--cube-face-bg': props.faceColor,
|
||||||
'--cube-face-shadow':
|
'--cube-face-shadow': props.shadow === true ? '0 0 6px rgba(0,0,0,.5)' : props.shadow || 'none',
|
||||||
props.shadow === true ? '0 0 6px rgba(0,0,0,.5)' : props.shadow || 'none',
|
|
||||||
...(props.cubeSize
|
...(props.cubeSize
|
||||||
? {
|
? {
|
||||||
width: `${props.gridSize * props.cubeSize}px`,
|
width: `${props.gridSize * props.cubeSize}px`,
|
||||||
height: `${props.gridSize * props.cubeSize}px`,
|
height: `${props.gridSize * props.cubeSize}px`
|
||||||
}
|
}
|
||||||
: {}),
|
: {})
|
||||||
}))
|
}));
|
||||||
|
|
||||||
const tiltAt = (rowCenter: number, colCenter: number) => {
|
const tiltAt = (rowCenter: number, colCenter: number) => {
|
||||||
if (!sceneRef.value) return
|
if (!sceneRef.value) return;
|
||||||
|
|
||||||
sceneRef.value.querySelectorAll<HTMLDivElement>('.cube').forEach((cube) => {
|
sceneRef.value.querySelectorAll<HTMLDivElement>('.cube').forEach(cube => {
|
||||||
const r = +(cube.dataset.row!)
|
const r = +cube.dataset.row!;
|
||||||
const c = +(cube.dataset.col!)
|
const c = +cube.dataset.col!;
|
||||||
const dist = Math.hypot(r - rowCenter, c - colCenter)
|
const dist = Math.hypot(r - rowCenter, c - colCenter);
|
||||||
|
|
||||||
if (dist <= props.radius) {
|
if (dist <= props.radius) {
|
||||||
const pct = 1 - dist / props.radius
|
const pct = 1 - dist / props.radius;
|
||||||
const angle = pct * props.maxAngle
|
const angle = pct * props.maxAngle;
|
||||||
gsap.to(cube, {
|
gsap.to(cube, {
|
||||||
duration: enterDur.value,
|
duration: enterDur.value,
|
||||||
ease: props.easing,
|
ease: props.easing,
|
||||||
overwrite: true,
|
overwrite: true,
|
||||||
rotateX: -angle,
|
rotateX: -angle,
|
||||||
rotateY: angle,
|
rotateY: angle
|
||||||
})
|
});
|
||||||
} else {
|
} else {
|
||||||
gsap.to(cube, {
|
gsap.to(cube, {
|
||||||
duration: leaveDur.value,
|
duration: leaveDur.value,
|
||||||
ease: 'power3.out',
|
ease: 'power3.out',
|
||||||
overwrite: true,
|
overwrite: true,
|
||||||
rotateX: 0,
|
rotateX: 0,
|
||||||
rotateY: 0,
|
rotateY: 0
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
const onPointerMove = (e: PointerEvent) => {
|
const onPointerMove = (e: PointerEvent) => {
|
||||||
userActiveRef.value = true
|
userActiveRef.value = true;
|
||||||
if (idleTimerRef.value) clearTimeout(idleTimerRef.value)
|
if (idleTimerRef.value) clearTimeout(idleTimerRef.value);
|
||||||
|
|
||||||
const rect = sceneRef.value!.getBoundingClientRect()
|
const rect = sceneRef.value!.getBoundingClientRect();
|
||||||
const cellW = rect.width / props.gridSize
|
const cellW = rect.width / props.gridSize;
|
||||||
const cellH = rect.height / props.gridSize
|
const cellH = rect.height / props.gridSize;
|
||||||
const colCenter = (e.clientX - rect.left) / cellW
|
const colCenter = (e.clientX - rect.left) / cellW;
|
||||||
const rowCenter = (e.clientY - rect.top) / cellH
|
const rowCenter = (e.clientY - rect.top) / cellH;
|
||||||
|
|
||||||
if (rafRef.value) cancelAnimationFrame(rafRef.value)
|
if (rafRef.value) cancelAnimationFrame(rafRef.value);
|
||||||
rafRef.value = requestAnimationFrame(() =>
|
rafRef.value = requestAnimationFrame(() => tiltAt(rowCenter, colCenter));
|
||||||
tiltAt(rowCenter, colCenter)
|
|
||||||
)
|
|
||||||
|
|
||||||
idleTimerRef.value = setTimeout(() => {
|
idleTimerRef.value = setTimeout(() => {
|
||||||
userActiveRef.value = false
|
userActiveRef.value = false;
|
||||||
}, 3000)
|
}, 3000);
|
||||||
}
|
};
|
||||||
|
|
||||||
const resetAll = () => {
|
const resetAll = () => {
|
||||||
if (!sceneRef.value) return
|
if (!sceneRef.value) return;
|
||||||
sceneRef.value.querySelectorAll<HTMLDivElement>('.cube').forEach((cube) =>
|
sceneRef.value.querySelectorAll<HTMLDivElement>('.cube').forEach(cube =>
|
||||||
gsap.to(cube, {
|
gsap.to(cube, {
|
||||||
duration: leaveDur.value,
|
duration: leaveDur.value,
|
||||||
rotateX: 0,
|
rotateX: 0,
|
||||||
rotateY: 0,
|
rotateY: 0,
|
||||||
ease: 'power3.out',
|
ease: 'power3.out'
|
||||||
})
|
})
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
const onClick = (e: MouseEvent) => {
|
const onClick = (e: MouseEvent) => {
|
||||||
if (!props.rippleOnClick || !sceneRef.value) return
|
if (!props.rippleOnClick || !sceneRef.value) return;
|
||||||
|
|
||||||
const rect = sceneRef.value.getBoundingClientRect()
|
const rect = sceneRef.value.getBoundingClientRect();
|
||||||
const cellW = rect.width / props.gridSize
|
const cellW = rect.width / props.gridSize;
|
||||||
const cellH = rect.height / props.gridSize
|
const cellH = rect.height / props.gridSize;
|
||||||
const colHit = Math.floor((e.clientX - rect.left) / cellW)
|
const colHit = Math.floor((e.clientX - rect.left) / cellW);
|
||||||
const rowHit = Math.floor((e.clientY - rect.top) / cellH)
|
const rowHit = Math.floor((e.clientY - rect.top) / cellH);
|
||||||
|
|
||||||
const baseRingDelay = 0.15
|
const baseRingDelay = 0.15;
|
||||||
const baseAnimDur = 0.3
|
const baseAnimDur = 0.3;
|
||||||
const baseHold = 0.6
|
const baseHold = 0.6;
|
||||||
|
|
||||||
const spreadDelay = baseRingDelay / props.rippleSpeed
|
const spreadDelay = baseRingDelay / props.rippleSpeed;
|
||||||
const animDuration = baseAnimDur / props.rippleSpeed
|
const animDuration = baseAnimDur / props.rippleSpeed;
|
||||||
const holdTime = baseHold / props.rippleSpeed
|
const holdTime = baseHold / props.rippleSpeed;
|
||||||
|
|
||||||
const rings: Record<number, HTMLDivElement[]> = {}
|
const rings: Record<number, HTMLDivElement[]> = {};
|
||||||
sceneRef.value.querySelectorAll<HTMLDivElement>('.cube').forEach((cube) => {
|
sceneRef.value.querySelectorAll<HTMLDivElement>('.cube').forEach(cube => {
|
||||||
const r = +(cube.dataset.row!)
|
const r = +cube.dataset.row!;
|
||||||
const c = +(cube.dataset.col!)
|
const c = +cube.dataset.col!;
|
||||||
const dist = Math.hypot(r - rowHit, c - colHit)
|
const dist = Math.hypot(r - rowHit, c - colHit);
|
||||||
const ring = Math.round(dist)
|
const ring = Math.round(dist);
|
||||||
if (!rings[ring]) rings[ring] = []
|
if (!rings[ring]) rings[ring] = [];
|
||||||
rings[ring].push(cube)
|
rings[ring].push(cube);
|
||||||
})
|
});
|
||||||
|
|
||||||
Object.keys(rings)
|
Object.keys(rings)
|
||||||
.map(Number)
|
.map(Number)
|
||||||
.sort((a, b) => a - b)
|
.sort((a, b) => a - b)
|
||||||
.forEach((ring) => {
|
.forEach(ring => {
|
||||||
const delay = ring * spreadDelay
|
const delay = ring * spreadDelay;
|
||||||
const faces = rings[ring].flatMap((cube) =>
|
const faces = rings[ring].flatMap(cube => Array.from(cube.querySelectorAll<HTMLElement>('.cube-face')));
|
||||||
Array.from(cube.querySelectorAll<HTMLElement>('.cube-face'))
|
|
||||||
)
|
|
||||||
|
|
||||||
gsap.to(faces, {
|
gsap.to(faces, {
|
||||||
backgroundColor: props.rippleColor,
|
backgroundColor: props.rippleColor,
|
||||||
duration: animDuration,
|
duration: animDuration,
|
||||||
delay,
|
delay,
|
||||||
ease: 'power3.out',
|
ease: 'power3.out'
|
||||||
})
|
});
|
||||||
gsap.to(faces, {
|
gsap.to(faces, {
|
||||||
backgroundColor: props.faceColor,
|
backgroundColor: props.faceColor,
|
||||||
duration: animDuration,
|
duration: animDuration,
|
||||||
delay: delay + animDuration + holdTime,
|
delay: delay + animDuration + holdTime,
|
||||||
ease: 'power3.out',
|
ease: 'power3.out'
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
const startAutoAnimation = () => {
|
const startAutoAnimation = () => {
|
||||||
if (!props.autoAnimate || !sceneRef.value) return
|
if (!props.autoAnimate || !sceneRef.value) return;
|
||||||
|
|
||||||
simPosRef.value = {
|
simPosRef.value = {
|
||||||
x: Math.random() * props.gridSize,
|
x: Math.random() * props.gridSize,
|
||||||
y: Math.random() * props.gridSize,
|
y: Math.random() * props.gridSize
|
||||||
}
|
};
|
||||||
simTargetRef.value = {
|
simTargetRef.value = {
|
||||||
x: Math.random() * props.gridSize,
|
x: Math.random() * props.gridSize,
|
||||||
y: Math.random() * props.gridSize,
|
y: Math.random() * props.gridSize
|
||||||
}
|
};
|
||||||
|
|
||||||
const speed = 0.02
|
const speed = 0.02;
|
||||||
const loop = () => {
|
const loop = () => {
|
||||||
if (!userActiveRef.value) {
|
if (!userActiveRef.value) {
|
||||||
const pos = simPosRef.value
|
const pos = simPosRef.value;
|
||||||
const tgt = simTargetRef.value
|
const tgt = simTargetRef.value;
|
||||||
pos.x += (tgt.x - pos.x) * speed
|
pos.x += (tgt.x - pos.x) * speed;
|
||||||
pos.y += (tgt.y - pos.y) * speed
|
pos.y += (tgt.y - pos.y) * speed;
|
||||||
tiltAt(pos.y, pos.x)
|
tiltAt(pos.y, pos.x);
|
||||||
|
|
||||||
if (Math.hypot(pos.x - tgt.x, pos.y - tgt.y) < 0.1) {
|
if (Math.hypot(pos.x - tgt.x, pos.y - tgt.y) < 0.1) {
|
||||||
simTargetRef.value = {
|
simTargetRef.value = {
|
||||||
x: Math.random() * props.gridSize,
|
x: Math.random() * props.gridSize,
|
||||||
y: Math.random() * props.gridSize,
|
y: Math.random() * props.gridSize
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
simRAFRef.value = requestAnimationFrame(loop)
|
simRAFRef.value = requestAnimationFrame(loop);
|
||||||
}
|
};
|
||||||
simRAFRef.value = requestAnimationFrame(loop)
|
simRAFRef.value = requestAnimationFrame(loop);
|
||||||
}
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const el = sceneRef.value
|
const el = sceneRef.value;
|
||||||
if (!el) return
|
if (!el) return;
|
||||||
|
|
||||||
el.addEventListener('pointermove', onPointerMove)
|
el.addEventListener('pointermove', onPointerMove);
|
||||||
el.addEventListener('pointerleave', resetAll)
|
el.addEventListener('pointerleave', resetAll);
|
||||||
el.addEventListener('click', onClick)
|
el.addEventListener('click', onClick);
|
||||||
|
|
||||||
startAutoAnimation()
|
startAutoAnimation();
|
||||||
})
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
const el = sceneRef.value
|
const el = sceneRef.value;
|
||||||
if (el) {
|
if (el) {
|
||||||
el.removeEventListener('pointermove', onPointerMove)
|
el.removeEventListener('pointermove', onPointerMove);
|
||||||
el.removeEventListener('pointerleave', resetAll)
|
el.removeEventListener('pointerleave', resetAll);
|
||||||
el.removeEventListener('click', onClick)
|
el.removeEventListener('click', onClick);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rafRef.value !== null) cancelAnimationFrame(rafRef.value)
|
if (rafRef.value !== null) cancelAnimationFrame(rafRef.value);
|
||||||
if (idleTimerRef.value !== null) clearTimeout(idleTimerRef.value)
|
if (idleTimerRef.value !== null) clearTimeout(idleTimerRef.value);
|
||||||
if (simRAFRef.value !== null) cancelAnimationFrame(simRAFRef.value)
|
if (simRAFRef.value !== null) cancelAnimationFrame(simRAFRef.value);
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
:style="{
|
:style="{
|
||||||
opacity: inView ? 1 : initialOpacity,
|
opacity: inView ? 1 : initialOpacity,
|
||||||
transition: `opacity ${duration}ms ${easing}, filter ${duration}ms ${easing}`,
|
transition: `opacity ${duration}ms ${easing}, filter ${duration}ms ${easing}`,
|
||||||
filter: blur ? (inView ? 'blur(0px)' : 'blur(10px)') : 'none',
|
filter: blur ? (inView ? 'blur(0px)' : 'blur(10px)') : 'none'
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
@@ -13,16 +13,16 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onUnmounted } from 'vue'
|
import { ref, onMounted, onUnmounted } from 'vue';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
blur?: boolean
|
blur?: boolean;
|
||||||
duration?: number
|
duration?: number;
|
||||||
easing?: string
|
easing?: string;
|
||||||
delay?: number
|
delay?: number;
|
||||||
threshold?: number
|
threshold?: number;
|
||||||
initialOpacity?: number
|
initialOpacity?: number;
|
||||||
className?: string
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
@@ -33,34 +33,34 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
threshold: 0.1,
|
threshold: 0.1,
|
||||||
initialOpacity: 0,
|
initialOpacity: 0,
|
||||||
className: ''
|
className: ''
|
||||||
})
|
});
|
||||||
|
|
||||||
const inView = ref(false)
|
const inView = ref(false);
|
||||||
const elementRef = ref<HTMLDivElement | null>(null)
|
const elementRef = ref<HTMLDivElement | null>(null);
|
||||||
let observer: IntersectionObserver | null = null
|
let observer: IntersectionObserver | null = null;
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const element = elementRef.value
|
const element = elementRef.value;
|
||||||
if (!element) return
|
if (!element) return;
|
||||||
|
|
||||||
observer = new IntersectionObserver(
|
observer = new IntersectionObserver(
|
||||||
([entry]) => {
|
([entry]) => {
|
||||||
if (entry.isIntersecting) {
|
if (entry.isIntersecting) {
|
||||||
observer?.unobserve(element)
|
observer?.unobserve(element);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
inView.value = true
|
inView.value = true;
|
||||||
}, props.delay)
|
}, props.delay);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ threshold: props.threshold }
|
{ threshold: props.threshold }
|
||||||
)
|
);
|
||||||
|
|
||||||
observer.observe(element)
|
observer.observe(element);
|
||||||
})
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
if (observer) {
|
if (observer) {
|
||||||
observer.disconnect()
|
observer.disconnect();
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user