reborn-ui 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -0
- package/dist/index.js +871 -0
- package/dist/index.js.map +1 -0
- package/package.json +40 -0
- package/registry/.gitkeep +2 -0
- package/registry/components/animate-grid.json +18 -0
- package/registry/components/animated-beam.json +16 -0
- package/registry/components/animated-circular-progressbar.json +16 -0
- package/registry/components/animated-list.json +22 -0
- package/registry/components/animated-testimonials.json +18 -0
- package/registry/components/animated-tooltip.json +18 -0
- package/registry/components/apple-card-carousel.json +35 -0
- package/registry/components/aurora-background.json +16 -0
- package/registry/components/balance-slider.json +16 -0
- package/registry/components/bending-gallery.json +18 -0
- package/registry/components/bento-grid.json +24 -0
- package/registry/components/bg-black-hole.json +14 -0
- package/registry/components/bg-bubbles.json +18 -0
- package/registry/components/bg-falling-stars.json +16 -0
- package/registry/components/bg-neural.json +14 -0
- package/registry/components/bg-particle-whirlpool.json +18 -0
- package/registry/components/bg-silk.json +16 -0
- package/registry/components/bg-stars.json +18 -0
- package/registry/components/bg-stractium.json +16 -0
- package/registry/components/blur-reveal.json +18 -0
- package/registry/components/book.json +28 -0
- package/registry/components/border-beam.json +16 -0
- package/registry/components/box-reveal.json +18 -0
- package/registry/components/card-3d.json +24 -0
- package/registry/components/card-spotlight.json +16 -0
- package/registry/components/carousel-3d.json +15 -0
- package/registry/components/color-picker.json +26 -0
- package/registry/components/colourful-text.json +18 -0
- package/registry/components/compare.json +22 -0
- package/registry/components/confetti.json +22 -0
- package/registry/components/container-scroll.json +26 -0
- package/registry/components/container-text-flip.json +19 -0
- package/registry/components/cosmic-portal.json +18 -0
- package/registry/components/direction-aware-hover.json +16 -0
- package/registry/components/dock.json +32 -0
- package/registry/components/expandable-gallery.json +16 -0
- package/registry/components/file-tree.json +28 -0
- package/registry/components/file-upload.json +22 -0
- package/registry/components/flickering-grid.json +16 -0
- package/registry/components/flip-card.json +16 -0
- package/registry/components/flip-words.json +16 -0
- package/registry/components/fluid-cursor.json +16 -0
- package/registry/components/focus.json +16 -0
- package/registry/components/github-globe.json +23 -0
- package/registry/components/glare-card.json +18 -0
- package/registry/components/globe.json +19 -0
- package/registry/components/glow-border.json +16 -0
- package/registry/components/glowing-effect.json +18 -0
- package/registry/components/gradient-button.json +16 -0
- package/registry/components/halo-search.json +16 -0
- package/registry/components/hyper-text.json +19 -0
- package/registry/components/icon-cloud.json +16 -0
- package/registry/components/image-trail-cursor.json +22 -0
- package/registry/components/images-slider.json +18 -0
- package/registry/components/infinite-grid.json +47 -0
- package/registry/components/input.json +18 -0
- package/registry/components/interactive-grid-pattern.json +16 -0
- package/registry/components/interactive-hover-button.json +16 -0
- package/registry/components/iphone-mockup.json +16 -0
- package/registry/components/lamp-effect.json +16 -0
- package/registry/components/lens.json +18 -0
- package/registry/components/letter-pullup.json +18 -0
- package/registry/components/light-speed.json +31 -0
- package/registry/components/line-shadow-text.json +16 -0
- package/registry/components/link-preview.json +16 -0
- package/registry/components/liquid-background.json +18 -0
- package/registry/components/liquid-glass.json +16 -0
- package/registry/components/liquid-logo.json +24 -0
- package/registry/components/logo-cloud.json +24 -0
- package/registry/components/logo-origami.json +22 -0
- package/registry/components/marquee.json +20 -0
- package/registry/components/meteors.json +16 -0
- package/registry/components/morphing-tabs.json +16 -0
- package/registry/components/morphing-text.json +16 -0
- package/registry/components/multi-step-loader.json +16 -0
- package/registry/components/neon-border.json +16 -0
- package/registry/components/number-ticker.json +18 -0
- package/registry/components/orbit.json +16 -0
- package/registry/components/particle-image.json +24 -0
- package/registry/components/particles-bg.json +18 -0
- package/registry/components/pattern-background.json +18 -0
- package/registry/components/photo-gallery.json +16 -0
- package/registry/components/radiant-text.json +16 -0
- package/registry/components/rainbow-button.json +16 -0
- package/registry/components/ripple-button.json +16 -0
- package/registry/components/ripple.json +24 -0
- package/registry/components/safari-mockup.json +16 -0
- package/registry/components/scratch-to-reveal.json +18 -0
- package/registry/components/scroll-island.json +20 -0
- package/registry/components/shader-toy.json +22 -0
- package/registry/components/shimmer-button.json +16 -0
- package/registry/components/sleek-line-cursor.json +12 -0
- package/registry/components/smooth-cursor.json +23 -0
- package/registry/components/snowfall-bg.json +18 -0
- package/registry/components/sparkles-text.json +18 -0
- package/registry/components/sparkles.json +18 -0
- package/registry/components/spinning-text.json +18 -0
- package/registry/components/spline.json +23 -0
- package/registry/components/spring-calendar.json +22 -0
- package/registry/components/svg-mask.json +16 -0
- package/registry/components/tailed-cursor.json +14 -0
- package/registry/components/testimonial-slider.json +16 -0
- package/registry/components/tetris.json +19 -0
- package/registry/components/text-3d.json +16 -0
- package/registry/components/text-generate-effect.json +16 -0
- package/registry/components/text-glitch.json +12 -0
- package/registry/components/text-highlight.json +16 -0
- package/registry/components/text-hover-effect.json +16 -0
- package/registry/components/text-reveal-card.json +20 -0
- package/registry/components/text-reveal.json +18 -0
- package/registry/components/text-scroll-reveal.json +20 -0
- package/registry/components/timeline.json +18 -0
- package/registry/components/tracing-beam.json +19 -0
- package/registry/components/vanishing-input.json +18 -0
- package/registry/components/video-text.json +16 -0
- package/registry/components/vortex.json +19 -0
- package/registry/components/warp-background.json +22 -0
- package/registry/components/wavy-background.json +19 -0
- package/registry/components/world-map.json +19 -0
- package/registry/registry.json +2007 -0
- package/templates/composables/useMouseState.ts +21 -0
- package/templates/lib/utils.ts +13 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "particle-image",
|
|
3
|
+
"dependencies": [],
|
|
4
|
+
"files": [
|
|
5
|
+
{
|
|
6
|
+
"path": "index.ts",
|
|
7
|
+
"content": "export { default as ParticleImage } from \"./ParticleImage.vue\";\r\n"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"path": "inspiraImageParticles.d.ts",
|
|
11
|
+
"content": "/**\r\n * Type definitions for inspiraImageParticles\r\n */\r\n\r\n/**\r\n * Options for initializing the InspiraImageParticle class.\r\n */\r\ninterface InspiraImageParticleOptions {\r\n width?: number;\r\n height?: number;\r\n maxWidth?: number;\r\n maxHeight?: number;\r\n minWidth?: number;\r\n minHeight?: number;\r\n gravity?: number;\r\n particleGap?: number;\r\n particleSize?: number;\r\n layerCount?: number;\r\n depth?: number;\r\n rotationDuration?: number;\r\n growDuration?: number;\r\n waitDuration?: number;\r\n shrinkDuration?: number;\r\n shrinkDistance?: number;\r\n threeDimensional?: boolean | string;\r\n lifeCycle?: boolean | string;\r\n layerDistance?: number;\r\n initPosition?: \"random\" | \"top\" | \"left\" | \"bottom\" | \"right\" | \"misplaced\" | \"none\";\r\n initDirection?: \"random\" | \"top\" | \"left\" | \"bottom\" | \"right\" | \"none\";\r\n fadePosition?: \"explode\" | \"top\" | \"left\" | \"bottom\" | \"right\" | \"random\" | \"none\";\r\n fadeDirection?: \"random\" | \"top\" | \"left\" | \"bottom\" | \"right\" | \"none\";\r\n noise?: number;\r\n disableInteraction?: boolean;\r\n mouseForce?: number;\r\n clickStrength?: number;\r\n color?: string;\r\n colorArr?: number[];\r\n image?: HTMLImageElement;\r\n imageId?: string;\r\n imageUrl?: string;\r\n wrapperElement?: HTMLElement;\r\n canvas?: HTMLCanvasElement;\r\n context?: CanvasRenderingContext2D | WebGL2RenderingContext | WebGLRenderingContext;\r\n renderer?: \"default\" | \"webgl\";\r\n addTimestamp?: boolean;\r\n responsiveWidth?: boolean;\r\n}\r\n\r\n/**\r\n * Interface for event handling methods.\r\n */\r\nexport interface EventHandlers {\r\n on(event: string, callback: (params?: unknow) => void): void;\r\n emit(event: string, params?: unknow): void;\r\n}\r\n\r\n/**\r\n * Interface containing all public property definitions for InspiraImageParticle.\r\n */\r\nexport interface InspiraImageParticleProps {\r\n // Public Properties\r\n state: string;\r\n touches: Array<{ x: number; y: number; z: number; force: number }>;\r\n colorArr: number[];\r\n vertices: Float32Array | false;\r\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\r\n events: { [key: string]: Function[] };\r\n canvas?: HTMLCanvasElement;\r\n context?: CanvasRenderingContext2D | WebGLRenderingContext;\r\n particles: unknow[];\r\n origins: unknow[];\r\n width: number;\r\n height: number;\r\n maxWidth?: number;\r\n maxHeight?: number;\r\n minWidth?: number;\r\n minHeight?: number;\r\n alphaFade: number;\r\n gravity: number;\r\n particleGap: number;\r\n particleSize: number;\r\n layerCount: number;\r\n depth: number;\r\n rotationDuration: number;\r\n growDuration: number;\r\n waitDuration: number;\r\n shrinkDuration: number;\r\n shrinkDistance: number;\r\n threeDimensional: boolean;\r\n lifeCycle: boolean;\r\n layerDistance: number;\r\n initPosition: string;\r\n initDirection: string;\r\n fadePosition: string;\r\n fadeDirection: string;\r\n noise: number;\r\n disableInteraction: boolean;\r\n mouseForce: number;\r\n clickStrength: number;\r\n imageWidth: number;\r\n imageHeight: number;\r\n imageRatio: number;\r\n srcImage?: HTMLImageElement;\r\n image?: HTMLImageElement;\r\n imageUrl: string;\r\n wrapperElement?: HTMLElement;\r\n responsiveWidth: boolean;\r\n renderWidth: number;\r\n renderHeight: number;\r\n offsetX: number;\r\n offsetY: number;\r\n speed: number;\r\n gravityFactor: number;\r\n renderer: \"default\" | \"webgl\";\r\n color: string;\r\n}\r\n\r\n/**\r\n * InspiraImageParticle class for creating particle animations from images.\r\n */\r\nexport declare class InspiraImageParticle implements InspiraImageParticleProps, EventHandlers {\r\n constructor(options?: InspiraImageParticleOptions);\r\n\r\n // Public Methods\r\n on(event: string, fn: (params?: unknow) => void): void;\r\n emit(event: string, params?: unknow): void;\r\n\r\n start(options?: Partial<InspiraImageParticleOptions>): void;\r\n stop(options?: Partial<InspiraImageParticleOptions>): void;\r\n\r\n // Private Methods\r\n private _animate(): void;\r\n private _onImageLoaded(options: InspiraImageParticleOptions): void;\r\n private _initImage(options: InspiraImageParticleOptions): void;\r\n private _initContext(options: InspiraImageParticleOptions): void;\r\n private _defaultInitContext(options: InspiraImageParticleOptions): void;\r\n private _webglInitContext(): void;\r\n private _webglSetAttributes(): void;\r\n private _updateRotation(): void;\r\n private _defaultRenderer(): void;\r\n private _webglRenderer(): void;\r\n private _calculate(): void;\r\n private _fade(): void;\r\n private _initParticles(): void;\r\n private _initOrigins(): void;\r\n private _initParticlePosition(origin: unknow, particle: unknow): void;\r\n private _initParticleDirection(particle: unknow): void;\r\n private _fadeOriginPosition(origin: unknow): void;\r\n private _fadeOriginDirection(particle: unknow): void;\r\n private _parseColor(str: string): number[] | undefined;\r\n\r\n // Private Getters\r\n private get _mouseHandler(): (e: MouseEvent) => void;\r\n private get _clickHandler(): (e: MouseEvent) => void;\r\n private get _touchHandler(): (e: TouchEvent) => void;\r\n private get _clearTouches(): (e: Event) => void;\r\n}\r\n\r\n/**\r\n * Initializes the InspiraImageParticle module and returns the class.\r\n */\r\nexport declare const inspiraImageParticles: () => {\r\n InspiraImageParticle: typeof InspiraImageParticle;\r\n};\r\n"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"path": "inspiraImageParticles.js",
|
|
15
|
+
"content": "/* eslint-disable */\r\nexport const inspiraImageParticles = () => {\r\n \"use strict\";\r\n\r\n const _requestAnimationFrame =\r\n window.requestAnimationFrame ||\r\n window.webkitRequestAnimationFrame ||\r\n window.mozRequestAnimationFrame ||\r\n function (callback) {\r\n setTimeout(callback, 10);\r\n };\r\n const PI2 = Math.PI * 2;\r\n let imageData, renderCount;\r\n let index, startIndex, layerIndex;\r\n let origin, particle, touch, touchIndex, rect;\r\n let x, y, z, dX, dY, dZ, distance;\r\n let force, angle, intensity, vertices;\r\n let canvas, context, data, r, g, b, a;\r\n let tick;\r\n let rotationX = 0;\r\n let rotationY = 0;\r\n\r\n class InspiraImageParticle {\r\n constructor(optionsParam) {\r\n let options = {};\r\n if (optionsParam) {\r\n if (optionsParam.nodeName) {\r\n options = JSON.parse(JSON.stringify(optionsParam.dataset));\r\n if (optionsParam.nodeName === \"IMG\") {\r\n options.image = optionsParam;\r\n } else {\r\n options.wrapperElement = optionsParam;\r\n }\r\n } else {\r\n options = optionsParam;\r\n }\r\n }\r\n this.state = \"stopped\";\r\n this.touches = [];\r\n this.on(\"imageLoaded\", this._onImageLoaded);\r\n this._initImage(options);\r\n }\r\n\r\n on(event, fn) {\r\n this.events = this.events || {};\r\n this.events[event] = this.events[event] || [];\r\n this.events[event].push(fn);\r\n }\r\n\r\n emit(event, params) {\r\n const events = this.events[event];\r\n if (events && events.length) {\r\n for (let eventIndex = 0; eventIndex < events.length; eventIndex++) {\r\n const cb = events[eventIndex];\r\n cb.call(this, params);\r\n }\r\n }\r\n }\r\n\r\n get renderer() {\r\n return this._renderer;\r\n }\r\n\r\n set renderer(value) {\r\n this._renderer = value;\r\n this._draw = this[\"_\" + value + \"Renderer\"];\r\n try {\r\n this[\"_\" + value + \"InitContext\"]();\r\n } catch (e) {\r\n console.log(e);\r\n if (value !== \"default\") {\r\n this.renderer = \"default\";\r\n }\r\n }\r\n }\r\n\r\n set color(value) {\r\n this.colorArr = this._parseColor(value);\r\n if (this.colorArr) {\r\n if (isNaN(this.colorArr[3])) {\r\n this.colorArr[3] = 255;\r\n }\r\n if (0 < this.colorArr[3] && this.colorArr[3] <= 1) {\r\n this.colorArr[3] *= 255;\r\n }\r\n }\r\n }\r\n\r\n start(optionsParam) {\r\n const options = optionsParam || {};\r\n this.initPosition = options.initPosition || this.initPosition;\r\n this.initDirection = options.initDirection || this.initDirection;\r\n if (this.canvas) {\r\n this.canvas.width = this.width;\r\n this.canvas.height = this.height;\r\n this.canvas.style.display = \"\";\r\n }\r\n this._initOrigins();\r\n this._initParticles();\r\n this._webglSetAttributes();\r\n if (this.state !== \"running\") {\r\n this.state = \"running\";\r\n if (!this.disableInteraction) {\r\n if (\"ontouchstart\" in window || window.navigator.msPointerEnabled) {\r\n document.body.addEventListener(\"touchstart\", this._touchHandler);\r\n document.body.addEventListener(\"touchmove\", this._touchHandler);\r\n document.body.addEventListener(\"touchend\", this._clearTouches);\r\n document.body.addEventListener(\"touchcancel\", this._clearTouches);\r\n } else {\r\n this.canvas.addEventListener(\"mousemove\", this._mouseHandler);\r\n this.canvas.addEventListener(\"mouseout\", this._clearTouches);\r\n this.canvas.addEventListener(\"click\", this._clickHandler);\r\n }\r\n }\r\n this._animate();\r\n }\r\n }\r\n\r\n stop(optionsParam) {\r\n const options = optionsParam || {};\r\n this.fadePosition = options.fadePosition || this.fadePosition;\r\n this.fadeDirection = options.fadeDirection || this.fadeDirection;\r\n this._fade();\r\n document.body.removeEventListener(\"touchstart\", this._touchHandler);\r\n document.body.removeEventListener(\"touchmove\", this._touchHandler);\r\n document.body.removeEventListener(\"touchend\", this._clearTouches);\r\n document.body.removeEventListener(\"touchcancel\", this._clearTouches);\r\n if (this.canvas) {\r\n this.canvas.removeEventListener(\"mousemove\", this._mouseHandler);\r\n this.canvas.removeEventListener(\"mouseout\", this._clearTouches);\r\n this.canvas.removeEventListener(\"click\", this._clickHandler);\r\n }\r\n }\r\n\r\n _animate() {\r\n if (this.state !== \"stopped\") {\r\n this._calculate();\r\n this._draw();\r\n _requestAnimationFrame(() => this._animate());\r\n } else {\r\n this.emit(\"stopped\");\r\n }\r\n }\r\n\r\n get _mouseHandler() {\r\n return (e) => {\r\n this.touches = [\r\n {\r\n x: e.offsetX,\r\n y: e.offsetY,\r\n z: 49 + (this.layerCount - 1) * this.layerDistance,\r\n force: 1,\r\n },\r\n ];\r\n };\r\n }\r\n\r\n get _clickHandler() {\r\n return (e) => {\r\n const strength = this.clickStrength;\r\n this.origins.map((o) => (o.z -= strength));\r\n setTimeout(() => {\r\n this.origins.map((o) => (o.z += strength));\r\n }, 100);\r\n };\r\n }\r\n\r\n get _touchHandler() {\r\n return (e) => {\r\n this.touches = [];\r\n rect = this.canvas.getBoundingClientRect();\r\n for (touchIndex = 0; touchIndex < e.changedTouches.length; touchIndex++) {\r\n touch = e.changedTouches[touchIndex];\r\n if (touch.target === this.canvas) {\r\n this.touches.push({\r\n x: touch.pageX - rect.left,\r\n y: touch.pageY - rect.top,\r\n z: 49 + (this.layerCount - 1) * this.layerDistance,\r\n force: touch.force || 1,\r\n });\r\n e.preventDefault();\r\n }\r\n }\r\n };\r\n }\r\n\r\n get _clearTouches() {\r\n return (e) => {\r\n this.touches = [];\r\n };\r\n }\r\n\r\n _onImageLoaded(options) {\r\n this.imageWidth = this.image.naturalWidth || this.image.width;\r\n this.imageHeight = this.image.naturalHeight || this.image.height;\r\n this.imageRatio = this.imageWidth / this.imageHeight;\r\n this.width = this.width || this.imageWidth;\r\n this.height = this.height || this.imageHeight;\r\n this.renderSize = (this.width + this.height) / 4;\r\n if (this.srcImage) {\r\n this.srcImage.style.display = \"none\";\r\n }\r\n this._initSettings(options);\r\n this._initContext(options);\r\n this._initResponsive(options);\r\n this.start();\r\n }\r\n\r\n _initImage(options) {\r\n this.srcImage = options.image;\r\n if (!this.srcImage && options.imageId) {\r\n this.srcImage = document.getElementById(options.imageId);\r\n }\r\n this.imageUrl = options.imageUrl || this.srcImage.src;\r\n this.image = document.createElement(\"img\");\r\n this.wrapperElement = options.wrapperElement || this.srcImage.parentElement;\r\n this.image.onload = () => this.emit(\"imageLoaded\", options);\r\n this.image.crossOrigin = \"Anonymous\";\r\n if (options.addTimestamp) {\r\n if (/\\?/.test(this.imageUrl)) {\r\n this.imageUrl += \"&d=\" + Date.now();\r\n } else {\r\n this.imageUrl += \"?d=\" + Date.now();\r\n }\r\n }\r\n this.image.src = this.imageUrl;\r\n }\r\n\r\n _initContext(options) {\r\n this.canvas = options.canvas;\r\n if (!this.canvas && !this.context && this.wrapperElement) {\r\n this.canvas = document.createElement(\"canvas\");\r\n this.wrapperElement.appendChild(this.canvas);\r\n }\r\n if (this.convas) {\r\n this.convas.style.display = \"none\";\r\n }\r\n this.context = options.context;\r\n this.renderer = options.renderer || \"default\";\r\n }\r\n\r\n _defaultInitContext(options) {\r\n this.context = this.context || this.canvas.getContext(\"2d\");\r\n }\r\n\r\n _webglInitContext() {\r\n this.context =\r\n this.context ||\r\n this.canvas.getContext(\"webgl2\") ||\r\n this.canvas.getContext(\"experimental-webgl\");\r\n this.fragmentShaderScript = `#version 300 es\r\n \r\n precision highp float;\r\n \r\n in vec4 vColor;\r\n out vec4 fragColor;\r\n \r\n void main(void) {\r\n // fragColor = vec4(1, 1, 1, 0.1);\r\n fragColor = vColor;\r\n }\r\n `;\r\n\r\n this.vertexShaderScript = `#version 300 es\r\n \r\n precision highp float;\r\n \r\n in vec3 vertexPosition;\r\n in vec4 vertexColor;\r\n uniform vec3 vertexOffset;\r\n uniform float pointSize;\r\n uniform float depth;\r\n vec3 mirror = vec3(1, -1, 1);\r\n \r\n uniform mat4 modelViewMatrix;\r\n uniform mat4 perspectiveMatrix;\r\n uniform mat4 rotationMatrix;\r\n \r\n out vec4 vColor;\r\n \r\n void main(void) {\r\n gl_Position = rotationMatrix * perspectiveMatrix * modelViewMatrix * vec4(mirror * vertexPosition + vertexOffset, vertexPosition);\r\n gl_PointSize = pointSize + max((log(vertexPosition.z) - 3.91) * depth, -pointSize + 1.0);\r\n vColor = vertexColor;\r\n }\r\n `;\r\n this.context.viewport(0, 0, this.width, this.height);\r\n const vertexShader = this.context.createShader(this.context.VERTEX_SHADER);\r\n this.context.shaderSource(vertexShader, this.vertexShaderScript);\r\n this.context.compileShader(vertexShader);\r\n if (!this.context.getShaderParameter(vertexShader, this.context.COMPILE_STATUS)) {\r\n console.log(this.context.getShaderInfoLog(vertexShader));\r\n }\r\n const fragmentShader = this.context.createShader(this.context.FRAGMENT_SHADER);\r\n this.context.shaderSource(fragmentShader, this.fragmentShaderScript);\r\n this.context.compileShader(fragmentShader);\r\n if (!this.context.getShaderParameter(fragmentShader, this.context.COMPILE_STATUS)) {\r\n console.log(this.context.getShaderInfoLog(fragmentShader));\r\n }\r\n this.program = this.context.createProgram();\r\n this.context.attachShader(this.program, vertexShader);\r\n this.context.attachShader(this.program, fragmentShader);\r\n this.context.linkProgram(this.program);\r\n this.context.useProgram(this.program);\r\n this.vertexPosition = this.context.getAttribLocation(this.program, \"vertexPosition\");\r\n this.context.enableVertexAttribArray(this.vertexPosition);\r\n this.vertexColor = this.context.getAttribLocation(this.program, \"vertexColor\");\r\n this.context.enableVertexAttribArray(this.vertexColor);\r\n this.context.clearColor(0.0, 0.0, 0.0, 0.0);\r\n this.context.enable(this.context.BLEND);\r\n this.context.disable(this.context.DEPTH_TEST);\r\n this.context.blendFunc(this.context.SRC_ALPHA, this.context.ONE);\r\n this.vertexBuffer = this.context.createBuffer();\r\n this.context.bindBuffer(this.context.ARRAY_BUFFER, this.vertexBuffer);\r\n this.context.clear(this.context.COLOR_BUFFER_BIT | this.context.DEPTH_BUFFER_BIT);\r\n this.vertexOffset = this.context.getUniformLocation(this.program, \"vertexOffset\");\r\n this.context.uniform3f(this.vertexOffset, 0, 0, 1000);\r\n this.context.vertexAttribPointer(this.vertexPosition, 3.0, this.context.FLOAT, false, 28, 0);\r\n this.context.vertexAttribPointer(this.vertexColor, 4.0, this.context.FLOAT, false, 28, 12);\r\n this.uModelViewMatrix = this.context.getUniformLocation(this.program, \"modelViewMatrix\");\r\n this.uPerspectiveMatrix = this.context.getUniformLocation(this.program, \"perspectiveMatrix\");\r\n this.uRotationMatrix = this.context.getUniformLocation(this.program, \"rotationMatrix\");\r\n this.uPointSize = this.context.getUniformLocation(this.program, \"pointSize\");\r\n this.uDepth = this.context.getUniformLocation(this.program, \"depth\");\r\n // this.uVertexColors = this.context.getUniformLocation(this.program, 'vertexColors');\r\n // this.uVertexIndex = this.context.getUniformLocation(this.program, 'vertexIndex');\r\n\r\n this._webglSetAttributes();\r\n }\r\n\r\n _webglSetAttributes() {\r\n if (this.renderer === \"webgl\") {\r\n var fieldOfView = 1;\r\n var aspectRatio = this.canvas.width / this.canvas.height;\r\n var nearPlane = 10;\r\n var farPlane = 100;\r\n var top = nearPlane * Math.tan((fieldOfView * Math.PI) / 360.0);\r\n var bottom = -top;\r\n var right = top * aspectRatio;\r\n var left = -right;\r\n var a = (right + left) / (right - left);\r\n var b = (top + bottom) / (top - bottom);\r\n var c = (farPlane + nearPlane) / (farPlane - nearPlane);\r\n var d = (2 * farPlane * nearPlane) / (farPlane - nearPlane);\r\n var x = (2 * nearPlane) / (right - left);\r\n var y = (2 * nearPlane) / (top - bottom);\r\n\r\n var perspectiveMatrix = [x, 0, a, 0, 0, y, b, 0, 0, 0, c, d, 0, 0, -1, 0];\r\n var modelViewMatrix = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];\r\n this.context.viewport(0, 0, this.width, this.height);\r\n this.context.uniformMatrix4fv(\r\n this.uModelViewMatrix,\r\n false,\r\n new Float32Array(perspectiveMatrix),\r\n );\r\n this.context.uniformMatrix4fv(\r\n this.uPerspectiveMatrix,\r\n false,\r\n new Float32Array(modelViewMatrix),\r\n );\r\n this.context.uniform1f(this.uPointSize, this.particleSize);\r\n this.context.uniform1f(this.uDepth, this.depth);\r\n // this.context.uniform4fv(this.uVertexColors, new Float32Array(this.vertexColors));\r\n // this.context.uniform1f(this.uVertexIndex, 0);\r\n this._updateRotation();\r\n }\r\n }\r\n\r\n _updateRotation() {\r\n const a = Math.cos(rotationX);\r\n const b = Math.sin(rotationX);\r\n const c = Math.cos(rotationY);\r\n const d = Math.sin(rotationY);\r\n var rotationMatrix = [c, 0, d, 0, 0, a, -b, 0, -c, b, a, 0, 0, 0, 0, 1];\r\n this.context.uniformMatrix4fv(this.uRotationMatrix, false, new Float32Array(rotationMatrix));\r\n }\r\n\r\n _webglRenderer() {\r\n vertices = new Float32Array(this.vertices);\r\n this.context.bufferData(this.context.ARRAY_BUFFER, vertices, this.context.STATIC_DRAW);\r\n this.context.clear(this.context.COLOR_BUFFER_BIT | this.context.DEPTH_BUFFER_BIT);\r\n this.context.drawArrays(this.context.POINTS, 0, this.particles.length);\r\n this.context.flush();\r\n }\r\n\r\n _initSettings(options) {\r\n this.width = options.width * 1 || this.width;\r\n this.height = options.height * 1 || this.height;\r\n this.maxWidth = options.maxWidth;\r\n this.maxHeight = options.maxHeight;\r\n this.minWidth = options.minWidth;\r\n this.minHeight = options.minHeight;\r\n if (this.maxWidth) {\r\n if (/%$/.test(this.maxWidth)) {\r\n this.maxWidth = (this.width * this.maxWidth.replace(\"%\", \"\")) / 100;\r\n } else {\r\n this.maxWidth *= 1;\r\n }\r\n }\r\n if (this.maxHeight) {\r\n if (/%$/.test(this.maxHeight)) {\r\n this.maxHeight = (this.height * this.maxHeight.replace(\"%\", \"\")) / 100;\r\n } else {\r\n this.maxHeight *= 1;\r\n }\r\n }\r\n if (this.minWidth) {\r\n if (/%$/.test(this.minWidth)) {\r\n this.minWidth = (this.width * this.minWidth.replace(\"%\", \"\")) / 100;\r\n } else {\r\n this.minWidth *= 1;\r\n }\r\n }\r\n if (this.minHeight) {\r\n if (/%$/.test(this.minHeight)) {\r\n this.minHeight = (this.height * this.minHeight.replace(\"%\", \"\")) / 100;\r\n } else {\r\n this.minHeight *= 1;\r\n }\r\n }\r\n this.alphaFade = 0.4;\r\n this.gravity = options.gravity * 1 || 0.08;\r\n this.particleGap = options.particleGap * 1 || 3;\r\n this.particleSize = options.particleSize * 1 || 1;\r\n this.layerCount = options.layerCount * 1 || 1;\r\n this.depth = options.depth * 1 || 1;\r\n this.rotationDuration = options.rotationDuration * 1 || 0;\r\n this.growDuration = options.growDuration * 1 || 200;\r\n this.waitDuration = options.waitDuration * 1 || 200;\r\n this.shrinkDuration = options.shrinkDuration * 1 || 200;\r\n this.shrinkDistance = options.shrinkDistance * 1 || 50;\r\n this.threeDimensional =\r\n options.threeDimensional !== undefined && options.threeDimensional !== \"false\"\r\n ? !!options.threeDimensional\r\n : false;\r\n this.lifeCycle =\r\n options.lifeCycle !== undefined && options.lifeCycle !== \"false\"\r\n ? !!options.lifeCycle\r\n : false;\r\n this.layerDistance = options.layerDistance || this.particleGap;\r\n this.initPosition = options.initPosition || \"random\";\r\n this.initDirection = options.initDirection || \"random\";\r\n this.fadePosition = options.fadePosition || \"none\";\r\n this.fadeDirection = options.fadeDirection || \"none\";\r\n this.noise = isNaN(options.noise * 1) ? 10 : options.noise * 1;\r\n this.disableInteraction = options.disableInteraction;\r\n this.mouseForce = options.mouseForce * 1 || 30;\r\n this.clickStrength = options.clickStrength * 1 || 0;\r\n this.color = options.color;\r\n this.colorArr = options.colorArr || this.colorArr;\r\n }\r\n\r\n _initResponsive(options) {\r\n this.responsiveWidth = this.wrapperElement && options.responsiveWidth;\r\n if (this.responsiveWidth) {\r\n this.on(\"stopped\", () => {\r\n this.width = this.wrapperElement.clientWidth;\r\n this.start();\r\n });\r\n this.wrapperElement.addEventListener(\"resize\", () => {\r\n if (this.width !== this.wrapperElement.clientWidth) {\r\n this.stop();\r\n }\r\n });\r\n this.width = this.wrapperElement.clientWidth;\r\n }\r\n }\r\n\r\n _calculate() {\r\n this.vertices = this.renderer === \"webgl\" ? [] : false;\r\n\r\n renderCount = 0;\r\n for (index = 0; index < this.particles.length; index++) {\r\n origin = this.origins[index];\r\n particle = this.particles[index];\r\n dX = origin.x - particle.x + (Math.random() - 0.5) * this.noise;\r\n dY = origin.y - particle.y + (Math.random() - 0.5) * this.noise;\r\n dZ = origin.z - particle.z + ((Math.random() - 0.5) * this.noise) / 1000;\r\n distance = Math.sqrt(dX * dX + dY * dY + dZ * dZ);\r\n force = distance * 0.01;\r\n particle.vx += force * (dX / distance) * this.speed;\r\n particle.vy += force * (dY / distance) * this.speed;\r\n particle.vz += force * (dZ / distance) * this.speed;\r\n for (touchIndex = 0; touchIndex < this.touches.length; touchIndex++) {\r\n touch = this.touches[touchIndex];\r\n dX = particle.x - touch.x;\r\n dY = particle.y - touch.y;\r\n dZ = particle.z - touch.z;\r\n distance = Math.sqrt(dX * dX + dY * dY + dZ * dZ);\r\n force = (this.mouseForce * touch.force) / distance;\r\n particle.vx += force * (dX / distance) * this.speed;\r\n particle.vy += force * (dY / distance) * this.speed;\r\n particle.vz += force * (dZ / distance) * this.speed;\r\n }\r\n particle.vx *= this.gravityFactor;\r\n particle.vy *= this.gravityFactor;\r\n particle.vz *= this.gravityFactor;\r\n particle.x += particle.vx;\r\n particle.y += particle.vy;\r\n particle.z += particle.vz;\r\n if (\r\n 0 > particle.x ||\r\n particle.x >= this.width ||\r\n 0 > particle.y ||\r\n particle.y >= this.height\r\n ) {\r\n particle.isHidden = true;\r\n if (this.state === \"stopping\") {\r\n particle.isDead = true;\r\n }\r\n } else {\r\n if (this.state === \"stopping\" && !particle.isDead) {\r\n renderCount++;\r\n }\r\n particle.isHidden = false;\r\n }\r\n if (this.vertices) {\r\n x = particle.x - this.width / 2;\r\n y = particle.y - this.height / 2;\r\n z = particle.z;\r\n a = origin.vertexColors[3];\r\n if (this.lifeCycle) {\r\n origin.tick += 1;\r\n if (origin.tick >= 0) {\r\n if (origin.tick < this.growDuration) {\r\n a = a * (origin.tick / this.growDuration);\r\n // z -= 50 * (tick / this.shrinkDuration);\r\n } else {\r\n tick = origin.tick - this.growDuration - this.waitDuration;\r\n if (tick >= 0 && tick <= this.shrinkDuration) {\r\n touch = this.touches[touchIndex];\r\n // rotationX = Math.PI / 2 + Math.cos(dX * Math.PI / 2) * dX * Math.PI * 0.1;\r\n // rotationY = Math.PI / 2 + Math.cos(dY * Math.PI / 2) * dY * Math.PI * 0.1;\r\n distance = Math.sqrt(x * x + y * y + (z - 50) * (z - 50));\r\n // distance = Math.sqrt(x * x + y * y);\r\n force = tick / this.shrinkDuration;\r\n x += this.shrinkDistance * (x / distance) * force;\r\n y += this.shrinkDistance * (y / distance) * force;\r\n z += this.shrinkDistance * ((z - 50) / distance) * force;\r\n a *= 1 - force;\r\n if (tick === this.shrinkDuration) {\r\n origin.tick = 0;\r\n }\r\n }\r\n }\r\n } else {\r\n a = 0;\r\n }\r\n }\r\n this.vertices.push(\r\n x,\r\n y,\r\n z,\r\n origin.vertexColors[0],\r\n origin.vertexColors[1],\r\n origin.vertexColors[2],\r\n a,\r\n );\r\n }\r\n }\r\n if (this.state === \"stopping\" && renderCount === 0) {\r\n this.state = \"stopped\";\r\n }\r\n }\r\n\r\n _defaultRenderer() {\r\n this.depth = Math.max((this.layerDistance * this.layerCount) / 2, this.mouseForce);\r\n this.minZ = -this.depth;\r\n this.maxZ = this.depth;\r\n imageData = this.context.createImageData(this.width, this.height);\r\n\r\n for (index = 0; index < this.origins.length; index++) {\r\n origin = this.origins[index];\r\n particle = this.particles[index];\r\n if (!particle.isDead && !particle.isHidden) {\r\n x = ~~particle.x;\r\n y = ~~particle.y;\r\n a = origin.color[3];\r\n if (this.alphaFade > 0 && this.layerCount > 1) {\r\n z = Math.max(Math.min(particle.z, this.maxZ), this.minZ) - this.minZ;\r\n a = a * (1 - this.alphaFade) + a * this.alphaFade * (z / (this.maxZ - this.minZ));\r\n a = Math.max(Math.min(~~a, 255), 0);\r\n }\r\n startIndex = (x + y * this.width) * 4;\r\n imageData.data[startIndex + 0] = origin.color[0];\r\n imageData.data[startIndex + 1] = origin.color[1];\r\n imageData.data[startIndex + 2] = origin.color[2];\r\n imageData.data[startIndex + 3] = a;\r\n }\r\n }\r\n this.context.putImageData(imageData, 0, 0);\r\n }\r\n\r\n _initParticles() {\r\n this.particles = undefined;\r\n this.particles = [];\r\n for (index = 0; index < this.origins.length; index++) {\r\n origin = this.origins[index];\r\n particle = {};\r\n this._initParticlePosition(origin, particle);\r\n this._initParticleDirection(particle);\r\n this.particles.push(particle);\r\n }\r\n }\r\n\r\n _initParticlePosition(origin, particle) {\r\n particle.z = 0;\r\n switch (this.initPosition) {\r\n case \"random\": {\r\n particle.x = Math.random() * this.width;\r\n particle.y = Math.random() * this.height;\r\n break;\r\n }\r\n case \"top\": {\r\n particle.x = Math.random() * this.width * 3 - this.width;\r\n particle.y = -Math.random() * this.height;\r\n break;\r\n }\r\n case \"left\": {\r\n particle.x = -Math.random() * this.width;\r\n particle.y = Math.random() * this.height * 3 - this.height;\r\n break;\r\n }\r\n case \"bottom\": {\r\n particle.x = Math.random() * this.width * 3 - this.width;\r\n particle.y = this.height + Math.random() * this.height;\r\n break;\r\n }\r\n case \"right\": {\r\n particle.x = this.width + Math.random() * this.width;\r\n particle.y = Math.random() * this.height * 3 - this.height;\r\n break;\r\n }\r\n case \"misplaced\": {\r\n particle.x = origin.x + Math.random() * this.width * 0.3 - this.width * 0.1;\r\n particle.y = origin.y + Math.random() * this.height * 0.3 - this.height * 0.1;\r\n break;\r\n }\r\n default: {\r\n particle.x = origin.x;\r\n particle.y = origin.y;\r\n }\r\n }\r\n }\r\n\r\n _fade() {\r\n if (\r\n this.fadePosition === \"explode\" ||\r\n this.fadePosition === \"top\" ||\r\n this.fadePosition === \"left\" ||\r\n this.fadePosition === \"bottom\" ||\r\n this.fadePosition === \"right\"\r\n ) {\r\n this.state = \"stopping\";\r\n } else {\r\n this.state = \"stopped\";\r\n }\r\n if (this.origins) {\r\n for (index = 0; index < this.origins.length; index++) {\r\n this._fadeOriginPosition(this.origins[index]);\r\n this._fadeOriginDirection(this.particles[index]);\r\n }\r\n }\r\n }\r\n\r\n _fadeOriginPosition(origin) {\r\n switch (this.fadePosition) {\r\n case \"random\": {\r\n origin.x = Math.random() * this.width * 2 - this.width;\r\n origin.y = Math.random() * this.height * 2 - this.height;\r\n if (origin.x > 0) origin.x += this.width;\r\n if (origin.y > 0) origin.y += this.height;\r\n break;\r\n }\r\n case \"top\": {\r\n origin.x = Math.random() * this.width * 3 - this.width;\r\n origin.y = -Math.random() * this.height;\r\n break;\r\n }\r\n case \"left\": {\r\n origin.x = -Math.random() * this.width;\r\n origin.y = Math.random() * this.height * 3 - this.height;\r\n break;\r\n }\r\n case \"bottom\": {\r\n origin.x = Math.random() * this.width * 3 - this.width;\r\n origin.y = this.height + Math.random() * this.height;\r\n break;\r\n }\r\n case \"right\": {\r\n origin.x = this.width + Math.random() * this.width;\r\n origin.y = Math.random() * this.height * 3 - this.height;\r\n break;\r\n }\r\n default: {\r\n // Stay in place\r\n }\r\n }\r\n }\r\n\r\n _initParticleDirection(particle) {\r\n particle.vz = 0;\r\n switch (this.initDirection) {\r\n case \"random\": {\r\n angle = Math.random() * Math.PI * 2;\r\n intensity = Math.random();\r\n particle.vx = this.width * intensity * Math.sin(angle) * 0.1;\r\n particle.vy = this.height * intensity * Math.cos(angle) * 0.1;\r\n break;\r\n }\r\n case \"top\": {\r\n angle = Math.random() * Math.PI - Math.PI / 2;\r\n intensity = Math.random();\r\n particle.vx = this.width * intensity * Math.sin(angle) * 0.1;\r\n particle.vy = this.height * intensity * Math.cos(angle) * 0.1;\r\n break;\r\n }\r\n case \"left\": {\r\n angle = Math.random() * Math.PI + Math.PI;\r\n intensity = Math.random();\r\n particle.vx = this.width * intensity * Math.sin(angle) * 0.1;\r\n particle.vy = this.height * intensity * Math.cos(angle) * 0.1;\r\n break;\r\n }\r\n case \"bottom\": {\r\n angle = Math.random() * Math.PI + Math.PI / 2;\r\n intensity = Math.random();\r\n particle.vx = this.width * intensity * Math.sin(angle) * 0.1;\r\n particle.vy = this.height * intensity * Math.cos(angle) * 0.1;\r\n break;\r\n }\r\n case \"right\": {\r\n angle = Math.random() * Math.PI;\r\n intensity = Math.random();\r\n particle.vx = this.width * intensity * Math.sin(angle) * 0.1;\r\n particle.vy = this.height * intensity * Math.cos(angle) * 0.1;\r\n break;\r\n }\r\n default: {\r\n particle.vx = 0;\r\n particle.vy = 0;\r\n }\r\n }\r\n }\r\n\r\n _fadeOriginDirection(particle) {\r\n switch (this.fadeDirection) {\r\n case \"random\": {\r\n angle = Math.random() * Math.PI * 2;\r\n intensity = Math.random();\r\n particle.vx += this.width * intensity * Math.sin(angle) * 0.1;\r\n particle.vy += this.height * intensity * Math.cos(angle) * 0.1;\r\n break;\r\n }\r\n case \"top\": {\r\n angle = Math.random() * Math.PI - Math.PI / 2;\r\n intensity = Math.random();\r\n particle.vx += this.width * intensity * Math.sin(angle) * 0.1;\r\n particle.vy += this.height * intensity * Math.cos(angle) * 0.1;\r\n break;\r\n }\r\n case \"left\": {\r\n angle = Math.random() * Math.PI + Math.PI;\r\n intensity = Math.random();\r\n particle.vx += this.width * intensity * Math.sin(angle) * 0.1;\r\n particle.vy += this.height * intensity * Math.cos(angle) * 0.1;\r\n break;\r\n }\r\n case \"bottom\": {\r\n angle = Math.random() * Math.PI + Math.PI / 2;\r\n intensity = Math.random();\r\n particle.vx += this.width * intensity * Math.sin(angle) * 0.1;\r\n particle.vy += this.height * intensity * Math.cos(angle) * 0.1;\r\n break;\r\n }\r\n case \"right\": {\r\n angle = Math.random() * Math.PI;\r\n intensity = Math.random();\r\n particle.vx += this.width * intensity * Math.sin(angle) * 0.1;\r\n particle.vy += this.height * intensity * Math.cos(angle) * 0.1;\r\n break;\r\n }\r\n default: {\r\n particle.vx = 0;\r\n particle.vy = 0;\r\n }\r\n }\r\n }\r\n\r\n _initOrigins() {\r\n canvas = document.createElement(\"canvas\");\r\n if (this.responsiveWidth) {\r\n this.width = this.wrapperElement.clientWidth;\r\n }\r\n this.ratio =\r\n Math.min(this.width, this.maxWidth || Number.POSITIVE_INFINITY) /\r\n Math.min(this.height, this.maxHeight || Number.POSITIVE_INFINITY);\r\n if (this.ratio < this.imageRatio) {\r\n this.renderWidth = ~~Math.min(\r\n this.width || Number.POSITIVE_INFINITY,\r\n this.minWidth || this.imageWidth || Number.POSITIVE_INFINITY,\r\n this.maxWidth || Number.POSITIVE_INFINITY,\r\n );\r\n this.renderHeight = ~~(this.renderWidth / this.imageRatio);\r\n } else {\r\n this.renderHeight = ~~Math.min(\r\n this.height || Number.POSITIVE_INFINITY,\r\n this.minHeight || this.imageHeight || Number.POSITIVE_INFINITY,\r\n this.maxHeight || Number.POSITIVE_INFINITY,\r\n );\r\n this.renderWidth = ~~(this.renderHeight * this.imageRatio);\r\n }\r\n this.offsetX = ~~((this.width - this.renderWidth) / 2);\r\n this.offsetY = ~~((this.height - this.renderHeight) / 2);\r\n canvas.width = this.renderWidth;\r\n canvas.height = this.renderHeight;\r\n context = canvas.getContext(\"2d\");\r\n context.drawImage(this.image, 0, 0, this.renderWidth, this.renderHeight);\r\n data = context.getImageData(0, 0, this.renderWidth, this.renderHeight).data;\r\n this.origins = undefined;\r\n this.origins = [];\r\n const duration = this.growDuration + this.waitDuration + this.shrinkDuration;\r\n for (x = 0; x < this.renderWidth; x += this.particleGap) {\r\n for (y = 0; y < this.renderHeight; y += this.particleGap) {\r\n index = (x + y * this.renderWidth) * 4;\r\n a = data[index + 3];\r\n if (a > 0) {\r\n const seed = Math.random();\r\n tick = -Math.floor(seed * duration);\r\n if (this.colorArr) {\r\n for (layerIndex = 0; layerIndex < this.layerCount; layerIndex++) {\r\n this.origins.push({\r\n x: this.offsetX + x,\r\n y: this.offsetY + y,\r\n z: layerIndex * this.layerDistance + 50,\r\n color: this.colorArr,\r\n tick,\r\n seed,\r\n vertexColors: this.colorArr.map((c) => c / 255),\r\n });\r\n }\r\n } else {\r\n r = data[index];\r\n g = data[index + 1];\r\n b = data[index + 2];\r\n for (layerIndex = 0; layerIndex < this.layerCount; layerIndex++) {\r\n this.origins.push({\r\n x: this.offsetX + x,\r\n y: this.offsetY + y,\r\n z: layerIndex * this.layerDistance + 50,\r\n color: [r, g, b, a],\r\n tick,\r\n seed,\r\n vertexColors: [r / 255, g / 255, b / 255, a / 255],\r\n });\r\n }\r\n }\r\n }\r\n }\r\n }\r\n this.speed = Math.log(this.origins.length) / 10;\r\n this.gravityFactor = 1 - this.gravity * this.speed;\r\n }\r\n\r\n _parseColor(strParam) {\r\n let color;\r\n if (typeof strParam !== \"string\") {\r\n return undefined;\r\n }\r\n const str = strParam.replace(\" \", \"\");\r\n\r\n if ((color = /^#([\\da-fA-F]{2})([\\da-fA-F]{2})([\\da-fA-F]{2})/.exec(str))) {\r\n color = [parseInt(color[1], 16), parseInt(color[2], 16), parseInt(color[3], 16)];\r\n } else if ((color = /^#([\\da-fA-F])([\\da-fA-F])([\\da-fA-F])/.exec(str))) {\r\n color = [\r\n parseInt(color[1], 16) * 17,\r\n parseInt(color[2], 16) * 17,\r\n parseInt(color[3], 16) * 17,\r\n ];\r\n } else if ((color = /^rgba\\(([\\d]+),([\\d]+),([\\d]+),([\\d]+|[\\d]*.[\\d]+)\\)/.exec(str))) {\r\n color = [+color[1], +color[2], +color[3], +color[4]];\r\n } else if ((color = /^rgb\\(([\\d]+),([\\d]+),([\\d]+)\\)/.exec(str))) {\r\n color = [+color[1], +color[2], +color[3]];\r\n } else return undefined;\r\n\r\n return color;\r\n }\r\n }\r\n\r\n return {\r\n InspiraImageParticle,\r\n };\r\n};\r\n"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"path": "ParticleImage.vue",
|
|
19
|
+
"content": "<template>\r\n <img\r\n ref=\"imageParticleRef\"\r\n :src=\"imageSrc\"\r\n :class=\"cn('hidden w-32 h-32', $props.class)\"\r\n :data-particle-gap=\"particleGap\"\r\n :data-width=\"canvasWidth\"\r\n :data-height=\"canvasHeight\"\r\n :data-gravity=\"gravity\"\r\n :data-particle-size=\"particleSize\"\r\n :data-mouse-force=\"mouseForce\"\r\n :data-renderer=\"renderer\"\r\n :data-color=\"color\"\r\n :data-color-arr=\"colorArr\"\r\n :data-init-position=\"initPosition\"\r\n :data-init-direction=\"initDirection\"\r\n :data-fade-position=\"fadePosition\"\r\n :data-fade-direction=\"fadeDirection\"\r\n :data-noise=\"noise\"\r\n :data-responsive-width=\"responsiveWidth\"\r\n />\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport { cn } from \"@/lib/utils\";\r\nimport {\r\n inspiraImageParticles,\r\n type InspiraImageParticle as ImageParticle,\r\n} from \"./inspiraImageParticles\";\r\nimport { ref, onMounted } from \"vue\";\r\n\r\ntype ParticleImageProps = {\r\n imageSrc: string;\r\n class?: string;\r\n canvasWidth?: string;\r\n canvasHeight?: string;\r\n gravity?: string;\r\n particleSize?: string;\r\n particleGap?: string;\r\n mouseForce?: string;\r\n renderer?: \"default\" | \"webgl\";\r\n color?: string;\r\n colorArr?: number[];\r\n initPosition?: \"random\" | \"top\" | \"left\" | \"bottom\" | \"right\" | \"misplaced\" | \"none\";\r\n initDirection?: \"random\" | \"top\" | \"left\" | \"bottom\" | \"right\" | \"none\";\r\n fadePosition?: \"explode\" | \"top\" | \"left\" | \"bottom\" | \"right\" | \"random\" | \"none\";\r\n fadeDirection?: \"random\" | \"top\" | \"left\" | \"bottom\" | \"right\" | \"none\";\r\n noise?: number;\r\n responsiveWidth?: boolean;\r\n};\r\n\r\ndefineProps<ParticleImageProps>();\r\n\r\nlet particles: ImageParticle;\r\nconst imageParticleRef = ref<HTMLImageElement>();\r\n\r\nonMounted(() => {\r\n const { InspiraImageParticle } = inspiraImageParticles();\r\n particles = new InspiraImageParticle(imageParticleRef.value);\r\n});\r\n</script>\r\n"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"fileCount": 4,
|
|
23
|
+
"contentHash": "da8ef51dbc8835ae1249e80e69170cf29b49bf94"
|
|
24
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "particles-bg",
|
|
3
|
+
"dependencies": [
|
|
4
|
+
"@vueuse/core"
|
|
5
|
+
],
|
|
6
|
+
"files": [
|
|
7
|
+
{
|
|
8
|
+
"path": "index.ts",
|
|
9
|
+
"content": "export { default as ParticlesBg } from \"./ParticlesBg.vue\";\r\n"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"path": "ParticlesBg.vue",
|
|
13
|
+
"content": "<template>\r\n <div\r\n ref=\"canvasContainerRef\"\r\n :class=\"$props.class\"\r\n aria-hidden=\"true\"\r\n >\r\n <canvas ref=\"canvasRef\"></canvas>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { useMouse, useDevicePixelRatio } from \"@vueuse/core\";\r\nimport { ref, onMounted, onBeforeUnmount, watch, computed, reactive } from \"vue\";\r\n\r\ntype Circle = {\r\n x: number;\r\n y: number;\r\n translateX: number;\r\n translateY: number;\r\n size: number;\r\n alpha: number;\r\n targetAlpha: number;\r\n dx: number;\r\n dy: number;\r\n magnetism: number;\r\n};\r\n\r\ntype Props = {\r\n color?: string;\r\n quantity?: number;\r\n staticity?: number;\r\n ease?: number;\r\n class?: string;\r\n};\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n color: \"#FFF\",\r\n quantity: 100,\r\n staticity: 50,\r\n ease: 50,\r\n class: \"\",\r\n});\r\n\r\nconst canvasRef = ref<HTMLCanvasElement | null>(null);\r\nconst canvasContainerRef = ref<HTMLDivElement | null>(null);\r\nconst context = ref<CanvasRenderingContext2D | null>(null);\r\nconst circles = ref<Circle[]>([]);\r\nconst mouse = reactive<{ x: number; y: number }>({ x: 0, y: 0 });\r\nconst canvasSize = reactive<{ w: number; h: number }>({ w: 0, h: 0 });\r\nconst { x: mouseX, y: mouseY } = useMouse();\r\nconst { pixelRatio } = useDevicePixelRatio();\r\n\r\nconst color = computed(() => {\r\n // Remove the leading '#' if it's present\r\n let hex = props.color.replace(/^#/, \"\");\r\n\r\n // If the hex code is 3 characters, expand it to 6 characters\r\n if (hex.length === 3) {\r\n hex = hex\r\n .split(\"\")\r\n .map((char) => char + char)\r\n .join(\"\");\r\n }\r\n\r\n // Parse the r, g, b values from the hex string\r\n const bigint = parseInt(hex, 16);\r\n const r = (bigint >> 16) & 255; // Extract the red component\r\n const g = (bigint >> 8) & 255; // Extract the green component\r\n const b = bigint & 255; // Extract the blue component\r\n\r\n // Return the RGB values as a string separated by spaces\r\n return `${r} ${g} ${b}`;\r\n});\r\n\r\nonMounted(() => {\r\n if (canvasRef.value) {\r\n context.value = canvasRef.value.getContext(\"2d\");\r\n }\r\n\r\n initCanvas();\r\n animate();\r\n window.addEventListener(\"resize\", initCanvas);\r\n});\r\n\r\nonBeforeUnmount(() => {\r\n window.removeEventListener(\"resize\", initCanvas);\r\n});\r\n\r\nwatch([mouseX, mouseY], () => {\r\n onMouseMove();\r\n});\r\n\r\nfunction initCanvas() {\r\n resizeCanvas();\r\n drawParticles();\r\n}\r\n\r\nfunction onMouseMove() {\r\n if (canvasRef.value) {\r\n const rect = canvasRef.value.getBoundingClientRect();\r\n const { w, h } = canvasSize;\r\n const x = mouseX.value - rect.left - w / 2;\r\n const y = mouseY.value - rect.top - h / 2;\r\n\r\n const inside = x < w / 2 && x > -w / 2 && y < h / 2 && y > -h / 2;\r\n if (inside) {\r\n mouse.x = x;\r\n mouse.y = y;\r\n }\r\n }\r\n}\r\n\r\nfunction resizeCanvas() {\r\n if (canvasContainerRef.value && canvasRef.value && context.value) {\r\n circles.value.length = 0;\r\n canvasSize.w = canvasContainerRef.value.offsetWidth;\r\n canvasSize.h = canvasContainerRef.value.offsetHeight;\r\n canvasRef.value.width = canvasSize.w * pixelRatio.value;\r\n canvasRef.value.height = canvasSize.h * pixelRatio.value;\r\n canvasRef.value.style.width = canvasSize.w + \"px\";\r\n canvasRef.value.style.height = canvasSize.h + \"px\";\r\n context.value.scale(pixelRatio.value, pixelRatio.value);\r\n }\r\n}\r\n\r\nfunction circleParams(): Circle {\r\n const x = Math.floor(Math.random() * canvasSize.w);\r\n const y = Math.floor(Math.random() * canvasSize.h);\r\n const translateX = 0;\r\n const translateY = 0;\r\n const size = Math.floor(Math.random() * 2) + 1;\r\n const alpha = 0;\r\n const targetAlpha = parseFloat((Math.random() * 0.6 + 0.1).toFixed(1));\r\n const dx = (Math.random() - 0.5) * 0.2;\r\n const dy = (Math.random() - 0.5) * 0.2;\r\n const magnetism = 0.1 + Math.random() * 4;\r\n return {\r\n x,\r\n y,\r\n translateX,\r\n translateY,\r\n size,\r\n alpha,\r\n targetAlpha,\r\n dx,\r\n dy,\r\n magnetism,\r\n };\r\n}\r\n\r\nfunction drawCircle(circle: Circle, update = false) {\r\n if (context.value) {\r\n const { x, y, translateX, translateY, size, alpha } = circle;\r\n context.value.translate(translateX, translateY);\r\n context.value.beginPath();\r\n context.value.arc(x, y, size, 0, 2 * Math.PI);\r\n context.value.fillStyle = `rgba(${color.value.split(\" \").join(\", \")}, ${alpha})`;\r\n context.value.fill();\r\n context.value.setTransform(pixelRatio.value, 0, 0, pixelRatio.value, 0, 0);\r\n\r\n if (!update) {\r\n circles.value.push(circle);\r\n }\r\n }\r\n}\r\n\r\nfunction clearContext() {\r\n if (context.value) {\r\n context.value.clearRect(0, 0, canvasSize.w, canvasSize.h);\r\n }\r\n}\r\n\r\nfunction drawParticles() {\r\n clearContext();\r\n const particleCount = props.quantity;\r\n for (let i = 0; i < particleCount; i++) {\r\n const circle = circleParams();\r\n drawCircle(circle);\r\n }\r\n}\r\n\r\nfunction remapValue(\r\n value: number,\r\n start1: number,\r\n end1: number,\r\n start2: number,\r\n end2: number,\r\n): number {\r\n const remapped = ((value - start1) * (end2 - start2)) / (end1 - start1) + start2;\r\n return remapped > 0 ? remapped : 0;\r\n}\r\n\r\nfunction animate() {\r\n clearContext();\r\n circles.value.forEach((circle, i) => {\r\n // Handle the alpha value\r\n const edge = [\r\n circle.x + circle.translateX - circle.size, // distance from left edge\r\n canvasSize.w - circle.x - circle.translateX - circle.size, // distance from right edge\r\n circle.y + circle.translateY - circle.size, // distance from top edge\r\n canvasSize.h - circle.y - circle.translateY - circle.size, // distance from bottom edge\r\n ];\r\n\r\n const closestEdge = edge.reduce((a, b) => Math.min(a, b));\r\n const remapClosestEdge = parseFloat(remapValue(closestEdge, 0, 20, 0, 1).toFixed(2));\r\n\r\n if (remapClosestEdge > 1) {\r\n circle.alpha += 0.02;\r\n if (circle.alpha > circle.targetAlpha) circle.alpha = circle.targetAlpha;\r\n } else {\r\n circle.alpha = circle.targetAlpha * remapClosestEdge;\r\n }\r\n\r\n circle.x += circle.dx;\r\n circle.y += circle.dy;\r\n circle.translateX +=\r\n (mouse.x / (props.staticity / circle.magnetism) - circle.translateX) / props.ease;\r\n circle.translateY +=\r\n (mouse.y / (props.staticity / circle.magnetism) - circle.translateY) / props.ease;\r\n\r\n // circle gets out of the canvas\r\n if (\r\n circle.x < -circle.size ||\r\n circle.x > canvasSize.w + circle.size ||\r\n circle.y < -circle.size ||\r\n circle.y > canvasSize.h + circle.size\r\n ) {\r\n // remove the circle from the array\r\n circles.value.splice(i, 1);\r\n // create a new circle\r\n const newCircle = circleParams();\r\n drawCircle(newCircle);\r\n // update the circle position\r\n } else {\r\n drawCircle(\r\n {\r\n ...circle,\r\n x: circle.x,\r\n y: circle.y,\r\n translateX: circle.translateX,\r\n translateY: circle.translateY,\r\n alpha: circle.alpha,\r\n },\r\n true,\r\n );\r\n }\r\n });\r\n window.requestAnimationFrame(animate);\r\n}\r\n</script>\r\n"
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"fileCount": 2,
|
|
17
|
+
"contentHash": "dedb8d4e5d3d6191bc7464fb48bc94491e93f744"
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pattern-background",
|
|
3
|
+
"dependencies": [
|
|
4
|
+
"class-variance-authority"
|
|
5
|
+
],
|
|
6
|
+
"files": [
|
|
7
|
+
{
|
|
8
|
+
"path": "index.ts",
|
|
9
|
+
"content": "import { cva, type VariantProps } from \"class-variance-authority\";\r\nimport type { HTMLAttributes } from \"vue\";\r\n\r\ntype ObjectValues<T> = T[keyof T];\r\n\r\nexport const PATTERN_BACKGROUND_DIRECTION = {\r\n Top: \"top\",\r\n Bottom: \"bottom\",\r\n Left: \"left\",\r\n Right: \"right\",\r\n TopLeft: \"top-left\",\r\n TopRight: \"top-right\",\r\n BottomLeft: \"bottom-left\",\r\n BottomRight: \"bottom-right\",\r\n} as const;\r\n\r\nexport type PatternBackgroundDirection = ObjectValues<typeof PATTERN_BACKGROUND_DIRECTION>;\r\n\r\nexport interface BaseProps {\r\n class?: HTMLAttributes[\"class\"];\r\n animate?: boolean;\r\n direction?: PatternBackgroundDirection;\r\n variant?: PatternBackgroundVariants[\"variant\"];\r\n size?: PatternBackgroundVariants[\"size\"];\r\n mask?: PatternBackgroundMaskVariants[\"mask\"];\r\n speed?: ObjectValues<typeof PATTERN_BACKGROUND_SPEED>;\r\n}\r\n\r\nexport const PATTERN_BACKGROUND_VARIANT = {\r\n Grid: \"grid\",\r\n Dot: \"dot\",\r\n BigDot: \"big-dot\",\r\n} as const;\r\n\r\nexport const PATTERN_BACKGROUND_SPEED = {\r\n Default: 10000,\r\n Slow: 25000,\r\n Fast: 5000,\r\n} as const;\r\n\r\nexport const patternBackgroundVariants = cva(\"relative text-clip\", {\r\n variants: {\r\n variant: {\r\n [PATTERN_BACKGROUND_VARIANT.Grid]:\r\n \"bg-[linear-gradient(to_right,hsl(var(--foreground)/0.3)_1px,transparent_1px),linear-gradient(to_bottom,hsl(var(--foreground)/0.3)_1px,transparent_1px)]\",\r\n [PATTERN_BACKGROUND_VARIANT.Dot]:\r\n \"bg-[radial-gradient(hsl(var(--foreground)/0.3)_1px,transparent_1px)]\",\r\n [PATTERN_BACKGROUND_VARIANT.BigDot]:\r\n \"bg-[radial-gradient(hsl(var(--foreground)/0.3)_3px,transparent_3px)]\",\r\n },\r\n size: {\r\n xs: \"bg-[size:8px_8px]\",\r\n sm: \"bg-[size:16px_16px]\",\r\n md: \"bg-[size:24px_24px]\",\r\n lg: \"bg-[size:32px_32px]\",\r\n },\r\n },\r\n defaultVariants: {\r\n variant: \"grid\",\r\n size: \"md\",\r\n },\r\n});\r\n\r\nexport type PatternBackgroundVariants = VariantProps<typeof patternBackgroundVariants>;\r\n\r\nexport const PATTERN_BACKGROUND_MASK = {\r\n Ellipse: \"ellipse\",\r\n EllipseTop: \"ellipse-top\",\r\n} as const;\r\n\r\nexport const patternBackgroundMaskVariants = cva(\"bg-background\", {\r\n variants: {\r\n mask: {\r\n [PATTERN_BACKGROUND_MASK.Ellipse]:\r\n \"[mask-image:radial-gradient(ellipse_at_center,transparent,black_80%)]\",\r\n [PATTERN_BACKGROUND_MASK.EllipseTop]:\r\n \"[mask-image:radial-gradient(ellipse_at_top,transparent,black_80%)]\",\r\n },\r\n },\r\n defaultVariants: {\r\n mask: \"ellipse\",\r\n },\r\n});\r\n\r\nexport type PatternBackgroundMaskVariants = VariantProps<typeof patternBackgroundMaskVariants>;\r\n\r\nexport { default as PatternBackground } from \"./PatternBackground.vue\";\r\n"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"path": "PatternBackground.vue",
|
|
13
|
+
"content": "<template>\r\n <div\r\n :class=\"\r\n cn(\r\n patternBackgroundVariants({ variant, size }),\r\n ` ${animate ? 'move move-' + direction : ''} `,\r\n props.class,\r\n )\r\n \"\r\n >\r\n <div\r\n :class=\"\r\n cn(\r\n 'absolute pointer-events-none inset-0 flex items-center justify-center',\r\n patternBackgroundMaskVariants({ mask }),\r\n )\r\n \"\r\n ></div>\r\n <slot />\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { cn } from \"@/lib/utils\";\r\nimport type { BaseProps as Props } from \".\";\r\nimport {\r\n PATTERN_BACKGROUND_DIRECTION,\r\n PATTERN_BACKGROUND_SPEED,\r\n PATTERN_BACKGROUND_VARIANT,\r\n patternBackgroundMaskVariants,\r\n patternBackgroundVariants,\r\n} from \".\";\r\nimport { computed } from \"vue\";\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n direction: () => PATTERN_BACKGROUND_DIRECTION.Top,\r\n variant: () => PATTERN_BACKGROUND_VARIANT.Grid,\r\n speed: () => PATTERN_BACKGROUND_SPEED.Default,\r\n size: undefined,\r\n mask: undefined,\r\n});\r\n\r\nconst durationFormSpeed = computed(() => `${props.speed}ms`);\r\n</script>\r\n\r\n<style scoped>\r\n@keyframes to-top {\r\n 0% {\r\n background-position: 0 100%;\r\n }\r\n 100% {\r\n background-position: 0 0;\r\n }\r\n}\r\n@keyframes to-bottom {\r\n 0% {\r\n background-position: 0 0;\r\n }\r\n 100% {\r\n background-position: 0 100%;\r\n }\r\n}\r\n@keyframes to-right {\r\n 0% {\r\n background-position: 0 0;\r\n }\r\n 100% {\r\n background-position: 100% 0;\r\n }\r\n}\r\n@keyframes to-left {\r\n 0% {\r\n background-position: 100% 0;\r\n }\r\n 100% {\r\n background-position: 0 0;\r\n }\r\n}\r\n@keyframes to-top-right {\r\n 0% {\r\n background-position: 0 100%;\r\n }\r\n 100% {\r\n background-position: 100% 0;\r\n }\r\n}\r\n@keyframes to-top-left {\r\n 0% {\r\n background-position: 100% 100%;\r\n }\r\n 100% {\r\n background-position: 0 0;\r\n }\r\n}\r\n@keyframes to-bottom-right {\r\n 0% {\r\n background-position: 0 0;\r\n }\r\n 100% {\r\n background-position: 100% 100%;\r\n }\r\n}\r\n@keyframes to-bottom-left {\r\n 0% {\r\n background-position: 100% 0;\r\n }\r\n 100% {\r\n background-position: 0 100%;\r\n }\r\n}\r\n\r\n.move {\r\n animation-duration: v-bind(durationFormSpeed);\r\n animation-timing-function: linear;\r\n animation-iteration-count: infinite;\r\n}\r\n\r\n.move-top {\r\n animation-name: to-top;\r\n}\r\n.move-bottom {\r\n animation-name: to-bottom;\r\n}\r\n.move-right {\r\n animation-name: to-right;\r\n}\r\n.move-left {\r\n animation-name: to-left;\r\n}\r\n.move-top-right {\r\n animation-name: to-top-right;\r\n}\r\n.move-top-left {\r\n animation-name: to-top-left;\r\n}\r\n.move-bottom-right {\r\n animation-name: to-bottom-right;\r\n}\r\n.move-bottom-left {\r\n animation-name: to-bottom-left;\r\n}\r\n</style>\r\n"
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"fileCount": 2,
|
|
17
|
+
"contentHash": "b933c111061d519d4128d2d7b6f0cc73ca012f40"
|
|
18
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "photo-gallery",
|
|
3
|
+
"dependencies": [],
|
|
4
|
+
"files": [
|
|
5
|
+
{
|
|
6
|
+
"path": "index.ts",
|
|
7
|
+
"content": "export { default as PhotoGallery } from \"./PhotoGallery.vue\";\r\n"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"path": "PhotoGallery.vue",
|
|
11
|
+
"content": "<template>\r\n <div\r\n class=\"gallery\"\r\n :class=\"cn('mb-[var(--size)] grid grid-cols-6 gap-1', props.containerClass)\"\r\n >\r\n <img\r\n v-for=\"(image, index) in props.items\"\r\n :key=\"index\"\r\n :src=\"image.src\"\r\n :alt=\"`image+${index}`\"\r\n class=\"gallery-img\"\r\n :class=\"\r\n cn(\r\n 'size-[calc(var(--size)*2)] rounded object-cover transition-[clip-path,filter] duration-75',\r\n props.class,\r\n )\r\n \"\r\n />\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { cn } from \"@/lib/utils\";\r\n\r\ninterface Props {\r\n containerClass?: string;\r\n class?: string;\r\n items: {\r\n src: string;\r\n }[];\r\n}\r\nconst props = defineProps<Props>();\r\n</script>\r\n\r\n<style scoped>\r\n.gallery {\r\n --size: 100px;\r\n grid-auto-rows: var(--size);\r\n\r\n &:has(:hover) img:not(:hover),\r\n &:has(:focus) img:not(:focus) {\r\n filter: brightness(0.5) contrast(0.5);\r\n }\r\n\r\n img {\r\n clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);\r\n grid-column: auto / span 2;\r\n\r\n &:nth-child(5n-1) {\r\n grid-column: 2 / span 2;\r\n }\r\n\r\n &:hover,\r\n &:focus {\r\n clip-path: polygon(100% 0, 100% 100%, 0 100%, 0 0);\r\n z-index: 1;\r\n transition:\r\n clip-path 0.25s,\r\n filter 0.25s;\r\n filter: saturate(150%);\r\n }\r\n\r\n &:focus {\r\n outline: 10px dashed black;\r\n outline-offset: -5px;\r\n }\r\n }\r\n}\r\n</style>\r\n"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"fileCount": 2,
|
|
15
|
+
"contentHash": "2f50dacd9eed88162bb5d468efc7893775700f67"
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "radiant-text",
|
|
3
|
+
"dependencies": [],
|
|
4
|
+
"files": [
|
|
5
|
+
{
|
|
6
|
+
"path": "index.ts",
|
|
7
|
+
"content": "export { default as RadiantText } from \"./RadiantText.vue\";\r\n"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"path": "RadiantText.vue",
|
|
11
|
+
"content": "<template>\r\n <p\r\n :style=\"styleVar\"\r\n :class=\"\r\n cn(\r\n 'mx-auto max-w-md text-neutral-600/70 dark:text-neutral-400/70',\r\n // Radiant effect\r\n 'radiant-animation bg-clip-text bg-no-repeat [background-position:0_0] [background-size:var(--radiant-width)_100%] [transition:background-position_1s_cubic-bezier(.6,.6,0,1)_infinite]',\r\n // Radiant gradient\r\n 'bg-gradient-to-r from-transparent via-black via-50% to-transparent dark:via-white',\r\n $props.class,\r\n )\r\n \"\r\n >\r\n <slot />\r\n </p>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport { cn } from \"@/lib/utils\";\r\nimport { computed } from \"vue\";\r\n\r\nconst props = defineProps({\r\n duration: {\r\n type: Number,\r\n default: 10,\r\n },\r\n radiantWidth: {\r\n type: Number,\r\n default: 100,\r\n },\r\n class: String,\r\n});\r\n\r\nconst styleVar = computed(() => {\r\n return {\r\n \"--radiant-anim-duration\": `${props.duration}s`,\r\n \"--radiant-width\": `${props.radiantWidth}px`,\r\n };\r\n});\r\n</script>\r\n\r\n<style scoped>\r\n@keyframes radiant {\r\n 0%,\r\n 90%,\r\n 100% {\r\n background-position: calc(-100% - var(--radiant-width)) 0;\r\n }\r\n 30%,\r\n 60% {\r\n background-position: calc(100% + var(--radiant-width)) 0;\r\n }\r\n}\r\n\r\n.radiant-animation {\r\n animation: radiant var(--radiant-anim-duration) infinite;\r\n}\r\n</style>\r\n"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"fileCount": 2,
|
|
15
|
+
"contentHash": "d76ece0d49783de2bf549f49b3999dbcf429414f"
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "rainbow-button",
|
|
3
|
+
"dependencies": [],
|
|
4
|
+
"files": [
|
|
5
|
+
{
|
|
6
|
+
"path": "index.ts",
|
|
7
|
+
"content": "export { default as RainbowButton } from \"./RainbowButton.vue\";\r\n"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"path": "RainbowButton.vue",
|
|
11
|
+
"content": "<template>\r\n <component\r\n :is=\"is\"\r\n :class=\"\r\n cn(\r\n 'rainbow-button',\r\n 'group relative inline-flex h-11 cursor-pointer items-center justify-center rounded-xl border-0 bg-[length:200%] px-8 py-2 font-medium text-primary-foreground transition-colors [background-clip:padding-box,border-box,border-box] [background-origin:border-box] [border:calc(0.08*1rem)_solid_transparent] focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50',\r\n 'before:absolute before:bottom-[-20%] before:left-1/2 before:z-0 before:h-1/5 before:w-3/5 before:-translate-x-1/2 before:bg-[linear-gradient(90deg,var(--color-1),var(--color-5),var(--color-3),var(--color-4),var(--color-2))] before:bg-[length:200%] before:[filter:blur(calc(0.8*1rem))]',\r\n 'bg-[linear-gradient(#121213,#121213),linear-gradient(#121213_50%,rgba(18,18,19,0.6)_80%,rgba(18,18,19,0)),linear-gradient(90deg,var(--color-1),var(--color-5),var(--color-3),var(--color-4),var(--color-2))]',\r\n 'dark:bg-[linear-gradient(#fff,#fff),linear-gradient(#fff_50%,rgba(255,255,255,0.6)_80%,rgba(0,0,0,0)),linear-gradient(90deg,var(--color-1),var(--color-5),var(--color-3),var(--color-4),var(--color-2))]',\r\n props.class,\r\n )\r\n \"\r\n >\r\n <slot />\r\n </component>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { cn } from \"@/lib/utils\";\r\nimport { computed } from \"vue\";\r\n\r\ninterface RainbowButtonProps {\r\n class?: string;\r\n is?: string;\r\n speed?: number;\r\n}\r\n\r\nconst props = withDefaults(defineProps<RainbowButtonProps>(), {\r\n speed: 2,\r\n is: \"button\",\r\n});\r\n\r\nconst speedInSeconds = computed(() => `${props.speed}s`);\r\n</script>\r\n\r\n<style scoped>\r\n.rainbow-button {\r\n --color-1: hsl(0 100% 63%);\r\n --color-2: hsl(270 100% 63%);\r\n --color-3: hsl(210 100% 63%);\r\n --color-4: hsl(195 100% 63%);\r\n --color-5: hsl(90 100% 63%);\r\n --speed: v-bind(speedInSeconds);\r\n animation: rainbow var(--speed) infinite linear;\r\n}\r\n\r\n.rainbow-button:before {\r\n animation: rainbow var(--speed) infinite linear;\r\n}\r\n\r\n@keyframes rainbow {\r\n 0% {\r\n background-position: 0;\r\n }\r\n 100% {\r\n background-position: 200%;\r\n }\r\n}\r\n</style>\r\n"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"fileCount": 2,
|
|
15
|
+
"contentHash": "7db4596cc2c3ddd0001d54ed5669ec54190e3a12"
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ripple-button",
|
|
3
|
+
"dependencies": [],
|
|
4
|
+
"files": [
|
|
5
|
+
{
|
|
6
|
+
"path": "index.ts",
|
|
7
|
+
"content": "export { default as RippleButton } from \"./RippleButton.vue\";\r\n"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"path": "RippleButton.vue",
|
|
11
|
+
"content": "<template>\r\n <button\r\n ref=\"rippleButtonRef\"\r\n :class=\"\r\n cn(\r\n 'relative flex cursor-pointer items-center justify-center overflow-hidden',\r\n 'rounded-lg border-2 bg-background px-4 py-2 text-center text-primary',\r\n $props.class,\r\n )\r\n \"\r\n :style=\"{ '--duration': $props.duration + 'ms' }\"\r\n @click=\"handleClick\"\r\n >\r\n <div class=\"relative z-10\">\r\n <slot />\r\n </div>\r\n\r\n <span class=\"pointer-events-none absolute inset-0\">\r\n <span\r\n v-for=\"ripple in buttonRipples\"\r\n :key=\"ripple.key\"\r\n class=\"ripple-animation absolute rounded-full bg-background opacity-30\"\r\n :style=\"{\r\n width: ripple.size + 'px',\r\n height: ripple.size + 'px',\r\n top: ripple.y + 'px',\r\n left: ripple.x + 'px',\r\n backgroundColor: $props.rippleColor,\r\n transform: 'scale(0)',\r\n animationDuration: $props.duration + 'ms',\r\n }\"\r\n />\r\n </span>\r\n </button>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport { ref, watchEffect, type HTMLAttributes } from \"vue\";\r\nimport { cn } from \"@/lib/utils\";\r\n\r\ninterface RippleButtonProps {\r\n class?: HTMLAttributes[\"class\"];\r\n rippleColor?: string;\r\n duration?: number;\r\n}\r\n\r\nconst props = withDefaults(defineProps<RippleButtonProps>(), {\r\n rippleColor: \"#ADD8E6\",\r\n duration: 600,\r\n});\r\n\r\nconst emit = defineEmits<{\r\n (e: \"click\", event: MouseEvent): void;\r\n}>();\r\n\r\nconst rippleButtonRef = ref<HTMLButtonElement | null>(null);\r\nconst buttonRipples = ref<Array<{ x: number; y: number; size: number; key: number }>>([]);\r\n\r\nfunction handleClick(event: MouseEvent) {\r\n createRipple(event);\r\n emit(\"click\", event);\r\n}\r\n\r\nfunction createRipple(event: MouseEvent) {\r\n const button = rippleButtonRef.value;\r\n if (!button) return;\r\n\r\n const rect = button.getBoundingClientRect();\r\n const size = Math.max(rect.width, rect.height);\r\n const x = event.clientX - rect.left - size / 2;\r\n const y = event.clientY - rect.top - size / 2;\r\n\r\n const newRipple = { x, y, size, key: Date.now() };\r\n buttonRipples.value.push(newRipple);\r\n}\r\n\r\nwatchEffect(() => {\r\n if (buttonRipples.value.length > 0) {\r\n const lastRipple = buttonRipples.value[buttonRipples.value.length - 1];\r\n setTimeout(() => {\r\n buttonRipples.value = buttonRipples.value.filter((ripple) => ripple.key !== lastRipple.key);\r\n }, props.duration);\r\n }\r\n});\r\n</script>\r\n\r\n<style scoped>\r\n@keyframes rippling {\r\n 0% {\r\n opacity: 1;\r\n }\r\n 100% {\r\n transform: scale(2);\r\n opacity: 0;\r\n }\r\n}\r\n\r\n.ripple-animation {\r\n animation: rippling var(--duration) ease-out;\r\n}\r\n</style>\r\n"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"fileCount": 2,
|
|
15
|
+
"contentHash": "7aa38484c00405d6f3bfea25933e89a419e4b39c"
|
|
16
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ripple",
|
|
3
|
+
"dependencies": [],
|
|
4
|
+
"files": [
|
|
5
|
+
{
|
|
6
|
+
"path": "index.ts",
|
|
7
|
+
"content": "export { default as Ripple } from \"./Ripple.vue\";\r\n"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"path": "Ripple.vue",
|
|
11
|
+
"content": "<template>\r\n <div class=\"absolute inset-0\">\r\n <RippleCircle\r\n v-for=\"index in numberOfCircles\"\r\n :key=\"index\"\r\n :opacity=\"baseCircleOpacity - index * circleOpacityDowngradeRatio\"\r\n :size=\"baseCircleSize + index * spaceBetweenCircle\"\r\n :animation-delay=\"index * waveSpeed\"\r\n :border-style=\"index === numberOfCircles - 1 ? 'dashed' : 'solid'\"\r\n :class=\"circleClass\"\r\n />\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\ninterface Props {\r\n baseCircleSize?: number;\r\n baseCircleOpacity?: number;\r\n spaceBetweenCircle?: number;\r\n circleOpacityDowngradeRatio?: number;\r\n circleClass?: string;\r\n waveSpeed?: number;\r\n numberOfCircles?: number;\r\n}\r\n\r\nwithDefaults(defineProps<Props>(), {\r\n baseCircleSize: 210,\r\n baseCircleOpacity: 0.24,\r\n circleOpacityDowngradeRatio: 0.03,\r\n waveSpeed: 80,\r\n spaceBetweenCircle: 70,\r\n numberOfCircles: 7,\r\n});\r\n</script>\r\n"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"path": "RippleCircle.vue",
|
|
15
|
+
"content": "<template>\r\n <div :class=\"cn('absolute shadow-xl', 'animate-ripple-circle', props.class)\" />\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { cn } from \"@/lib/utils\";\r\n\r\ninterface Props {\r\n size?: number;\r\n class?: string;\r\n opacity?: number;\r\n animationDelay?: number;\r\n borderStyle?: string;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n size: 210,\r\n opacity: 0.24,\r\n});\r\n</script>\r\n\r\n<style scoped>\r\n.animate-ripple-circle {\r\n animation: ripple-effect var(--duration, 2s) ease-in-out calc(var(--i, 0) * 0.2s) infinite;\r\n border-width: 1px;\r\n top: 50%;\r\n left: 50%;\r\n width: v-bind('props.size + \"px\"');\r\n height: v-bind('props.size + \"px\"');\r\n animation-delay: v-bind('props.animationDelay + \"ms\"');\r\n opacity: v-bind(\"props.opacity\");\r\n transform: translate(-50%, -50%) scale(1);\r\n border-style: v-bind(\"props.borderStyle\");\r\n}\r\n\r\n@keyframes ripple-effect {\r\n 0%,\r\n 100% {\r\n transform: translate(-50%, -50%) scale(1);\r\n }\r\n\r\n 50% {\r\n transform: translate(-50%, -50%) scale(0.9);\r\n }\r\n}\r\n</style>\r\n"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"path": "RippleContainer.vue",
|
|
19
|
+
"content": "<template>\r\n <div class=\"relative\">\r\n <slot />\r\n <Ripple />\r\n </div>\r\n</template>\r\n"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"fileCount": 4,
|
|
23
|
+
"contentHash": "c543641c11a497804ce0f6e787b1adc58e0e2afc"
|
|
24
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "safari-mockup",
|
|
3
|
+
"dependencies": [],
|
|
4
|
+
"files": [
|
|
5
|
+
{
|
|
6
|
+
"path": "index.ts",
|
|
7
|
+
"content": "export { default as SafariMockup } from \"./SafariMockup.vue\";\r\n"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"path": "SafariMockup.vue",
|
|
11
|
+
"content": "<template>\r\n <svg\r\n fill=\"none\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n :width=\"width\"\r\n :height=\"height\"\r\n :viewBox=\"`0 0 ${width} ${height}`\"\r\n >\r\n <g clipPath=\"url(#path0)\">\r\n <path\r\n d=\"M0 52H1202V741C1202 747.627 1196.63 753 1190 753H12C5.37258 753 0 747.627 0 741V52Z\"\r\n class=\"fill-[#E5E5E5] dark:fill-[#404040]\"\r\n ></path>\r\n <path\r\n fillRule=\"evenodd\"\r\n clipRule=\"evenodd\"\r\n d=\"M0 12C0 5.37258 5.37258 0 12 0H1190C1196.63 0 1202 5.37258 1202 12V52H0L0 12Z\"\r\n class=\"fill-[#E5E5E5] dark:fill-[#404040]\"\r\n ></path>\r\n <path\r\n fillRule=\"evenodd\"\r\n clipRule=\"evenodd\"\r\n d=\"M1.06738 12C1.06738 5.92487 5.99225 1 12.0674 1H1189.93C1196.01 1 1200.93 5.92487 1200.93 12V51H1.06738V12Z\"\r\n class=\"fill-white dark:fill-[#262626]\"\r\n ></path>\r\n <circle\r\n cx=\"27\"\r\n cy=\"25\"\r\n r=\"6\"\r\n class=\"fill-[#E5E5E5] dark:fill-[#404040]\"\r\n ></circle>\r\n <circle\r\n cx=\"47\"\r\n cy=\"25\"\r\n r=\"6\"\r\n class=\"fill-[#E5E5E5] dark:fill-[#404040]\"\r\n ></circle>\r\n <circle\r\n cx=\"67\"\r\n cy=\"25\"\r\n r=\"6\"\r\n class=\"fill-[#E5E5E5] dark:fill-[#404040]\"\r\n ></circle>\r\n <path\r\n d=\"M286 17C286 13.6863 288.686 11 292 11H946C949.314 11 952 13.6863 952 17V35C952 38.3137 949.314 41 946 41H292C288.686 41 286 38.3137 286 35V17Z\"\r\n class=\"fill-[#E5E5E5] dark:fill-[#404040]\"\r\n ></path>\r\n <g class=\"mix-blend-luminosity\">\r\n <path\r\n d=\"M566.269 32.0852H572.426C573.277 32.0852 573.696 31.6663 573.696 30.7395V25.9851C573.696 25.1472 573.353 24.7219 572.642 24.6521V23.0842C572.642 20.6721 571.036 19.5105 569.348 19.5105C567.659 19.5105 566.053 20.6721 566.053 23.0842V24.6711C565.393 24.7727 565 25.1917 565 25.9851V30.7395C565 31.6663 565.418 32.0852 566.269 32.0852ZM567.272 22.97C567.272 21.491 568.211 20.6785 569.348 20.6785C570.478 20.6785 571.423 21.491 571.423 22.97V24.6394L567.272 24.6458V22.97Z\"\r\n fill=\"#A3A3A3\"\r\n ></path>\r\n </g>\r\n <g class=\"mix-blend-luminosity\">\r\n <text\r\n x=\"580\"\r\n y=\"30\"\r\n fill=\"#A3A3A3\"\r\n fontSize=\"12\"\r\n fontFamily=\"Arial, sans-serif\"\r\n >\r\n {{ url }}\r\n </text>\r\n </g>\r\n <g class=\"mix-blend-luminosity\">\r\n <path\r\n d=\"M265.5 33.8984C265.641 33.8984 265.852 33.8516 266.047 33.7422C270.547 31.2969 272.109 30.1641 272.109 27.3203V21.4219C272.109 20.4844 271.742 20.1484 270.961 19.8125C270.094 19.4453 267.18 18.4297 266.328 18.1406C266.07 18.0547 265.766 18 265.5 18C265.234 18 264.93 18.0703 264.672 18.1406C263.82 18.3828 260.906 19.4531 260.039 19.8125C259.258 20.1406 258.891 20.4844 258.891 21.4219V27.3203C258.891 30.1641 260.461 31.2812 264.945 33.7422C265.148 33.8516 265.359 33.8984 265.5 33.8984ZM265.922 19.5781C266.945 19.9766 269.172 20.7656 270.344 21.1875C270.562 21.2656 270.617 21.3828 270.617 21.6641V27.0234C270.617 29.3125 269.469 29.9375 265.945 32.0625C265.727 32.1875 265.617 32.2344 265.508 32.2344V19.4844C265.617 19.4844 265.734 19.5156 265.922 19.5781Z\"\r\n fill=\"#A3A3A3\"\r\n ></path>\r\n </g>\r\n <g class=\"mix-blend-luminosity\">\r\n <path\r\n d=\"M936.273 24.9766C936.5 24.9766 936.68 24.9062 936.82 24.7578L940.023 21.5312C940.195 21.3594 940.273 21.1719 940.273 20.9531C940.273 20.7422 940.188 20.5391 940.023 20.3828L936.82 17.125C936.68 16.9688 936.5 16.8906 936.273 16.8906C935.852 16.8906 935.516 17.2422 935.516 17.6719C935.516 17.8828 935.594 18.0547 935.727 18.2031L937.594 20.0312C937.227 19.9766 936.852 19.9453 936.477 19.9453C932.609 19.9453 929.516 23.0391 929.516 26.9141C929.516 30.7891 932.633 33.9062 936.5 33.9062C940.375 33.9062 943.484 30.7891 943.484 26.9141C943.484 26.4453 943.156 26.1094 942.688 26.1094C942.234 26.1094 941.93 26.4453 941.93 26.9141C941.93 29.9297 939.516 32.3516 936.5 32.3516C933.492 32.3516 931.07 29.9297 931.07 26.9141C931.07 23.875 933.469 21.4688 936.477 21.4688C936.984 21.4688 937.453 21.5078 937.867 21.5781L935.734 23.6875C935.594 23.8281 935.516 24 935.516 24.2109C935.516 24.6406 935.852 24.9766 936.273 24.9766Z\"\r\n fill=\"#A3A3A3\"\r\n ></path>\r\n </g>\r\n <g class=\"mix-blend-luminosity\">\r\n <path\r\n d=\"M1134 33.0156C1134.49 33.0156 1134.89 32.6094 1134.89 32.1484V27.2578H1139.66C1140.13 27.2578 1140.54 26.8594 1140.54 26.3672C1140.54 25.8828 1140.13 25.4766 1139.66 25.4766H1134.89V20.5859C1134.89 20.1172 1134.49 19.7188 1134 19.7188C1133.52 19.7188 1133.11 20.1172 1133.11 20.5859V25.4766H1128.34C1127.88 25.4766 1127.46 25.8828 1127.46 26.3672C1127.46 26.8594 1127.88 27.2578 1128.34 27.2578H1133.11V32.1484C1133.11 32.6094 1133.52 33.0156 1134 33.0156Z\"\r\n fill=\"#A3A3A3\"\r\n ></path>\r\n </g>\r\n <g class=\"mix-blend-luminosity\">\r\n <path\r\n d=\"M1161.8 31.0703H1163.23V32.375C1163.23 34.0547 1164.12 34.9219 1165.81 34.9219H1174.2C1175.89 34.9219 1176.77 34.0547 1176.77 32.3828V24.0469C1176.77 22.375 1175.89 21.5 1174.2 21.5H1172.77V20.2578C1172.77 18.5859 1171.88 17.7109 1170.19 17.7109H1161.8C1160.1 17.7109 1159.23 18.5781 1159.23 20.2578V28.5234C1159.23 30.1953 1160.1 31.0703 1161.8 31.0703ZM1161.9 29.5078C1161.18 29.5078 1160.78 29.1328 1160.78 28.3828V20.3984C1160.78 19.6406 1161.18 19.2656 1161.9 19.2656H1170.09C1170.8 19.2656 1171.2 19.6406 1171.2 20.3984V21.5H1165.81C1164.12 21.5 1163.23 22.375 1163.23 24.0469V29.5078H1161.9ZM1165.91 33.3672C1165.19 33.3672 1164.8 32.9922 1164.8 32.2422V24.1875C1164.8 23.4297 1165.19 23.0625 1165.91 23.0625H1174.1C1174.81 23.0625 1175.21 23.4297 1175.21 24.1875V32.2422C1175.21 32.9922 1174.81 33.3672 1174.1 33.3672H1165.91Z\"\r\n fill=\"#A3A3A3\"\r\n ></path>\r\n </g>\r\n <g class=\"mix-blend-luminosity\">\r\n <path\r\n d=\"M1099.51 28.4141C1099.91 28.4141 1100.24 28.0859 1100.24 27.6953V19.8359L1100.18 18.6797L1100.66 19.25L1101.75 20.4141C1101.88 20.5547 1102.06 20.625 1102.24 20.625C1102.6 20.625 1102.9 20.3672 1102.9 20C1102.9 19.8047 1102.82 19.6641 1102.69 19.5312L1100.06 17.0078C1099.88 16.8203 1099.7 16.7578 1099.51 16.7578C1099.32 16.7578 1099.14 16.8203 1098.95 17.0078L1096.33 19.5312C1096.2 19.6641 1096.12 19.8047 1096.12 20C1096.12 20.3672 1096.41 20.625 1096.77 20.625C1096.95 20.625 1097.14 20.5547 1097.27 20.4141L1098.35 19.25L1098.84 18.6719L1098.78 19.8359V27.6953C1098.78 28.0859 1099.11 28.4141 1099.51 28.4141ZM1095 34.6562H1104C1105.7 34.6562 1106.57 33.7812 1106.57 32.1094V24.4297C1106.57 22.7578 1105.7 21.8828 1104 21.8828H1101.89V23.4375H1103.9C1104.61 23.4375 1105.02 23.8125 1105.02 24.5625V31.9688C1105.02 32.7188 1104.61 33.0938 1103.9 33.0938H1095.1C1094.38 33.0938 1093.98 32.7188 1093.98 31.9688V24.5625C1093.98 23.8125 1094.38 23.4375 1095.1 23.4375H1097.13V21.8828H1095C1093.31 21.8828 1092.43 22.75 1092.43 24.4297V32.1094C1092.43 33.7812 1093.31 34.6562 1095 34.6562Z\"\r\n fill=\"#A3A3A3\"\r\n ></path>\r\n </g>\r\n <g class=\"mix-blend-luminosity\">\r\n <path\r\n d=\"M99.5703 33.6016H112.938C114.633 33.6016 115.516 32.7266 115.516 31.0547V21.5469C115.516 19.875 114.633 19 112.938 19H99.5703C97.8828 19 97 19.8672 97 21.5469V31.0547C97 32.7266 97.8828 33.6016 99.5703 33.6016ZM99.6719 32.0469C98.9531 32.0469 98.5547 31.6719 98.5547 30.9141V21.6875C98.5547 20.9297 98.9531 20.5547 99.6719 20.5547H103.234V32.0469H99.6719ZM112.836 20.5547C113.555 20.5547 113.953 20.9297 113.953 21.6875V30.9141C113.953 31.6719 113.555 32.0469 112.836 32.0469H104.711V20.5547H112.836ZM101.703 23.4141C101.984 23.4141 102.219 23.1719 102.219 22.9062C102.219 22.6406 101.984 22.4062 101.703 22.4062H100.102C99.8203 22.4062 99.5859 22.6406 99.5859 22.9062C99.5859 23.1719 99.8203 23.4141 100.102 23.4141H101.703ZM101.703 25.5156C101.984 25.5156 102.219 25.2812 102.219 25.0078C102.219 24.7422 101.984 24.5078 101.703 24.5078H100.102C99.8203 24.5078 99.5859 24.7422 99.5859 25.0078C99.5859 25.2812 99.8203 25.5156 100.102 25.5156H101.703ZM101.703 27.6094C101.984 27.6094 102.219 27.3828 102.219 27.1094C102.219 26.8438 101.984 26.6172 101.703 26.6172H100.102C99.8203 26.6172 99.5859 26.8438 99.5859 27.1094C99.5859 27.3828 99.8203 27.6094 100.102 27.6094H101.703Z\"\r\n fill=\"#A3A3A3\"\r\n ></path>\r\n </g>\r\n <g class=\"mix-blend-luminosity\">\r\n <path\r\n d=\"M143.914 32.5938C144.094 32.7656 144.312 32.8594 144.562 32.8594C145.086 32.8594 145.492 32.4531 145.492 31.9375C145.492 31.6797 145.391 31.4453 145.211 31.2656L139.742 25.9219L145.211 20.5938C145.391 20.4141 145.492 20.1719 145.492 19.9219C145.492 19.4062 145.086 19 144.562 19C144.312 19 144.094 19.0938 143.922 19.2656L137.844 25.2031C137.625 25.4062 137.516 25.6562 137.516 25.9297C137.516 26.2031 137.625 26.4375 137.836 26.6484L143.914 32.5938Z\"\r\n fill=\"#A3A3A3\"\r\n ></path>\r\n </g>\r\n <g class=\"mix-blend-luminosity\">\r\n <path\r\n d=\"M168.422 32.8594C168.68 32.8594 168.891 32.7656 169.07 32.5938L175.148 26.6562C175.359 26.4375 175.469 26.2109 175.469 25.9297C175.469 25.6562 175.367 25.4141 175.148 25.2109L169.07 19.2656C168.891 19.0938 168.68 19 168.422 19C167.898 19 167.492 19.4062 167.492 19.9219C167.492 20.1719 167.602 20.4141 167.773 20.5938L173.25 25.9375L167.773 31.2656C167.594 31.4531 167.492 31.6797 167.492 31.9375C167.492 32.4531 167.898 32.8594 168.422 32.8594Z\"\r\n fill=\"#A3A3A3\"\r\n ></path>\r\n </g>\r\n <image\r\n width=\"1200\"\r\n height=\"700\"\r\n x=\"1\"\r\n y=\"52\"\r\n preserveAspectRatio=\"xMidYMid slice\"\r\n :href=\"src\"\r\n style=\"clip-path: url(#roundedBottom)\"\r\n ></image>\r\n </g>\r\n <defs>\r\n <clipPath id=\"path0\">\r\n <rect\r\n fill=\"white\"\r\n :width=\"width\"\r\n :height=\"height\"\r\n ></rect>\r\n </clipPath>\r\n <clipPath id=\"roundedBottom\">\r\n <path\r\n d=\"M1 52H1201V741C1201 747.075 1196.08 752 1190 752H12C5.92486 752 1 747.075 1 741V52Z\"\r\n fill=\"white\"\r\n ></path>\r\n </clipPath>\r\n </defs>\r\n </svg>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\ntype SafariMockupProps = {\r\n url?: string;\r\n src?: string;\r\n width?: number;\r\n height?: number;\r\n};\r\n\r\nwithDefaults(defineProps<SafariMockupProps>(), {\r\n width: 1203,\r\n height: 753,\r\n});\r\n</script>\r\n"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"fileCount": 2,
|
|
15
|
+
"contentHash": "8f93100531674d063b6ae9665f1546b41e453e83"
|
|
16
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "scratch-to-reveal",
|
|
3
|
+
"dependencies": [
|
|
4
|
+
"motion-v"
|
|
5
|
+
],
|
|
6
|
+
"files": [
|
|
7
|
+
{
|
|
8
|
+
"path": "index.ts",
|
|
9
|
+
"content": "export { default as ScratchToReveal } from \"./ScratchToReveal.vue\";\r\n"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"path": "ScratchToReveal.vue",
|
|
13
|
+
"content": "<template>\r\n <Motion\r\n ref=\"containerRef\"\r\n :class=\"cn('relative select-none', props.class)\"\r\n :style=\"{\r\n width: containerWidth,\r\n height: containerHeight,\r\n cursor: cursorImg,\r\n }\"\r\n :initial=\"{\r\n scale: 1,\r\n rotate: [0, 10, -10, 10, -10, 0],\r\n }\"\r\n :transition=\"{ duration: 0.5 }\"\r\n >\r\n <canvas\r\n ref=\"canvasRef\"\r\n :width=\"width\"\r\n :height=\"height\"\r\n class=\"absolute left-0 top-0\"\r\n @mousedown=\"handleMouseDown\"\r\n @touchstart=\"handleTouchStart\"\r\n />\r\n\r\n <slot />\r\n </Motion>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport { cn } from \"@/lib/utils\";\r\nimport { Motion, useAnimate } from \"motion-v\";\r\nimport { ref, computed, onMounted, onUnmounted, type Ref } from \"vue\";\r\n\r\nconst cursorImg =\r\n \"url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMiIgaGVpZ2h0PSIzMiIgdmlld0JveD0iMCAwIDMyIDMyIj4KICA8Y2lyY2xlIGN4PSIxNiIgY3k9IjE2IiByPSIxNSIgc3R5bGU9ImZpbGw6I2ZmZjtzdHJva2U6IzAwMDtzdHJva2Utd2lkdGg6MXB4OyIgLz4KPC9zdmc+'), auto\";\r\n\r\ninterface Props {\r\n class?: string;\r\n width: number;\r\n height: number;\r\n minScratchPercentage?: number;\r\n gradientColors?: [string, string, string];\r\n}\r\n\r\nconst canvasRef = ref<HTMLCanvasElement>();\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n gradientColors: () => [\"#A97CF8\", \"#F38CB8\", \"#FDCC92\"],\r\n minScratchPercentage: 50,\r\n});\r\n\r\nconst containerWidth = computed(() => props.width + \"px\");\r\nconst containerHeight = computed(() => props.height + \"px\");\r\n\r\nconst context = ref<CanvasRenderingContext2D>();\r\n\r\nconst emit = defineEmits<{\r\n complete: [];\r\n}>();\r\n\r\nconst isScratching = ref(false);\r\nconst isComplete = ref(false);\r\n\r\nfunction handleMouseDown() {\r\n isScratching.value = true;\r\n}\r\nfunction handleTouchStart() {\r\n isScratching.value = true;\r\n}\r\n\r\nconst canvasWidth = computed(() => canvasRef.value?.width || props.width);\r\nconst canvasHeight = computed(() => canvasRef.value?.height || props.height);\r\n\r\nfunction drawCanvas(canvasRef: Ref<HTMLCanvasElement>) {\r\n context.value = canvasRef.value.getContext(\"2d\")!;\r\n context.value.fillStyle = \"#ccc\";\r\n context.value.fillRect(0, 0, canvasWidth.value, canvasHeight.value);\r\n const gradient = context.value.createLinearGradient(0, 0, canvasWidth.value, canvasHeight.value);\r\n gradient.addColorStop(0, props.gradientColors[0]);\r\n gradient.addColorStop(0.5, props.gradientColors[1]);\r\n gradient.addColorStop(1, props.gradientColors[2]);\r\n context.value.fillStyle = gradient;\r\n context.value.fillRect(0, 0, canvasWidth.value, canvasHeight.value);\r\n}\r\n\r\nfunction scratch(clientX: number, clientY: number) {\r\n if (canvasRef.value && context.value) {\r\n const rect = canvasRef.value.getBoundingClientRect();\r\n const x = clientX - rect.left + 16;\r\n const y = clientY - rect.top + 16;\r\n\r\n context.value.globalCompositeOperation = \"destination-out\";\r\n context.value.beginPath();\r\n context.value.arc(x, y, 30, 0, Math.PI * 2);\r\n context.value.fill();\r\n }\r\n}\r\n\r\nfunction handleDocumentMouseMove(event: MouseEvent) {\r\n if (!isScratching.value) return;\r\n scratch(event.clientX, event.clientY);\r\n}\r\n\r\nfunction handleDocumentTouchMove(event: TouchEvent) {\r\n if (!isScratching.value) return;\r\n const touch = event.touches[0];\r\n scratch(touch.clientX, touch.clientY);\r\n}\r\n\r\nfunction handleDocumentMouseUp() {\r\n isScratching.value = false;\r\n checkCompletion();\r\n}\r\nfunction handleDocumentTouchEnd() {\r\n isScratching.value = false;\r\n checkCompletion();\r\n}\r\n\r\nfunction addEventListeners() {\r\n document.addEventListener(\"mousedown\", handleDocumentMouseMove);\r\n document.addEventListener(\"mousemove\", handleDocumentMouseMove);\r\n document.addEventListener(\"touchstart\", handleDocumentTouchMove);\r\n document.addEventListener(\"touchmove\", handleDocumentTouchMove);\r\n document.addEventListener(\"mouseup\", handleDocumentMouseUp);\r\n document.addEventListener(\"touchend\", handleDocumentTouchEnd);\r\n document.addEventListener(\"touchcancel\", handleDocumentTouchEnd);\r\n}\r\n\r\nfunction checkCompletion() {\r\n if (isComplete.value) return;\r\n\r\n if (canvasRef.value && context.value) {\r\n const imageData = context.value.getImageData(0, 0, canvasWidth.value, canvasHeight.value);\r\n const pixels = imageData.data;\r\n const totalPixels = pixels.length / 4;\r\n let clearPixels = 0;\r\n\r\n for (let i = 3; i < pixels.length; i += 4) {\r\n if (pixels[i] === 0) {\r\n clearPixels++;\r\n }\r\n }\r\n\r\n const percentage = (clearPixels / totalPixels) * 100;\r\n\r\n if (percentage >= props.minScratchPercentage) {\r\n isComplete.value = true;\r\n context.value.clearRect(0, 0, canvasWidth.value, canvasHeight.value);\r\n\r\n startAnimation();\r\n } else {\r\n isScratching.value = false;\r\n }\r\n }\r\n}\r\n\r\nconst [containerRef, animate] = useAnimate();\r\nasync function startAnimation() {\r\n if (!containerRef.value) return;\r\n animate(containerRef.value, {\r\n scale: 1,\r\n rotate: [0, 10, -10, 10, -10, 0],\r\n });\r\n\r\n emit(\"complete\");\r\n}\r\n\r\nonMounted(() => {\r\n if (!canvasRef.value) return;\r\n\r\n drawCanvas(canvasRef as Ref<HTMLCanvasElement>);\r\n\r\n addEventListeners();\r\n});\r\n\r\nfunction removeEventListeners() {\r\n document.removeEventListener(\"mousedown\", handleDocumentMouseMove);\r\n document.removeEventListener(\"mousemove\", handleDocumentMouseMove);\r\n document.removeEventListener(\"touchstart\", handleDocumentTouchMove);\r\n document.removeEventListener(\"touchmove\", handleDocumentTouchMove);\r\n document.removeEventListener(\"mouseup\", handleDocumentMouseUp);\r\n document.removeEventListener(\"touchend\", handleDocumentTouchEnd);\r\n document.removeEventListener(\"touchcancel\", handleDocumentTouchEnd);\r\n}\r\nonUnmounted(() => {\r\n removeEventListeners();\r\n});\r\n</script>\r\n\r\n<style></style>\r\n"
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"fileCount": 2,
|
|
17
|
+
"contentHash": "d73468bc57fbf2557c8a7dc2baccebcb13d46f12"
|
|
18
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "scroll-island",
|
|
3
|
+
"dependencies": [
|
|
4
|
+
"@number-flow/vue",
|
|
5
|
+
"@vueuse/core",
|
|
6
|
+
"motion-v"
|
|
7
|
+
],
|
|
8
|
+
"files": [
|
|
9
|
+
{
|
|
10
|
+
"path": "index.ts",
|
|
11
|
+
"content": "export { default as ScrollIsland } from \"./ScrollIsland.vue\";\r\n"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"path": "ScrollIsland.vue",
|
|
15
|
+
"content": "<template>\r\n <MotionConfig\r\n :transition=\"{\r\n duration: 0.7,\r\n type: 'spring',\r\n bounce: 0.5,\r\n }\"\r\n >\r\n <div\r\n :class=\"\r\n cn(\r\n 'fixed left-1/2 top-12 z-[999] -translate-x-1/2 bg-primary/90 backdrop-blur-lg border-radius',\r\n $props.class,\r\n )\r\n \"\r\n @click=\"() => (open = !open)\"\r\n >\r\n <motion.div\r\n id=\"motion-id\"\r\n layout\r\n :initial=\"{\r\n height: props.height,\r\n width: 0,\r\n }\"\r\n :animate=\"{\r\n height: open && isSlotAvailable ? 'auto' : props.height,\r\n width: open && isSlotAvailable ? 320 : 260,\r\n }\"\r\n class=\"bg-natural-900 relative cursor-pointer overflow-hidden text-secondary\"\r\n >\r\n <header class=\"gray- flex h-11 cursor-pointer items-center gap-2 px-4\">\r\n <AnimatedCircularProgressBar\r\n :value=\"scrollPercentage * 100\"\r\n :min=\"0\"\r\n :max=\"100\"\r\n :circle-stroke-width=\"10\"\r\n class=\"w-6\"\r\n :show-percentage=\"false\"\r\n :duration=\"0.3\"\r\n :gauge-secondary-color=\"isDark ? '#6b728055' : '#6b728099'\"\r\n :gauge-primary-color=\"isDark ? 'black' : 'white'\"\r\n />\r\n <h1 class=\"grow text-center font-bold\">{{ title }}</h1>\r\n <NumberFlow\r\n :value=\"scrollPercentage\"\r\n :format=\"{ style: 'percent' }\"\r\n locales=\"en-US\"\r\n />\r\n </header>\r\n <motion.div\r\n v-if=\"isSlotAvailable\"\r\n class=\"mb-2 flex h-full max-h-60 flex-col gap-1 overflow-y-auto px-4 text-sm\"\r\n >\r\n <slot />\r\n </motion.div>\r\n </motion.div>\r\n </div>\r\n </MotionConfig>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport { cn } from \"@/lib/utils\";\r\nimport NumberFlow from \"@number-flow/vue\";\r\nimport { useColorMode } from \"@vueuse/core\";\r\nimport { motion, MotionConfig } from \"motion-v\";\r\nimport { computed, onMounted, onUnmounted, ref, useSlots } from \"vue\";\r\n\r\ninterface Props {\r\n class?: string;\r\n title?: string;\r\n height?: number;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n class: \"\",\r\n title: \"Progress\",\r\n height: 44,\r\n});\r\n\r\nconst open = ref(false);\r\nconst slots = useSlots();\r\n\r\nconst scrollPercentage = ref(0);\r\n\r\nconst isDark = computed(() => useColorMode().value == \"dark\");\r\nconst isSlotAvailable = computed(() => !!slots.default);\r\nconst borderRadius = computed(() => `${props.height / 2}px`);\r\n\r\nonMounted(() => {\r\n if (window === undefined) return;\r\n\r\n window.addEventListener(\"scroll\", updatePageScroll);\r\n updatePageScroll();\r\n});\r\n\r\nfunction updatePageScroll() {\r\n scrollPercentage.value = window.scrollY / (document.body.scrollHeight - window.innerHeight);\r\n}\r\n\r\nonUnmounted(() => {\r\n window.removeEventListener(\"scroll\", updatePageScroll);\r\n});\r\n</script>\r\n\r\n<style scoped>\r\n.border-radius {\r\n border-radius: v-bind(borderRadius);\r\n}\r\n</style>\r\n"
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"fileCount": 2,
|
|
19
|
+
"contentHash": "4f97735aff0c3428314b1ddc395fd39156e512d1"
|
|
20
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "shader-toy",
|
|
3
|
+
"dependencies": [
|
|
4
|
+
"ogl"
|
|
5
|
+
],
|
|
6
|
+
"files": [
|
|
7
|
+
{
|
|
8
|
+
"path": "index.ts",
|
|
9
|
+
"content": "export { default as ShaderToy } from \"./ShaderToy.vue\";\r\nexport * from \"./InspiraShaderToy\";\r\n"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"path": "InspiraShaderToy.ts",
|
|
13
|
+
"content": "import { Renderer, Camera, Transform, Geometry, Program, Mesh } from \"ogl\";\r\n\r\nexport interface ShaderConfig {\r\n source: string;\r\n}\r\n\r\nexport interface MouseState {\r\n x: number;\r\n y: number;\r\n clickX: number;\r\n clickY: number;\r\n}\r\n\r\nexport interface HSVControls {\r\n hue: number; // 0-360\r\n saturation: number; // 0-1\r\n brightness: number; // 0-1\r\n}\r\n\r\nexport type MouseMode = \"click\" | \"hover\";\r\n\r\nexport class InspiraShaderToy {\r\n private renderer: Renderer;\r\n private camera: Camera;\r\n private scene: Transform;\r\n private geometry: Geometry;\r\n private program: Program | null = null;\r\n private mesh: Mesh | null = null;\r\n\r\n // Timing\r\n private isPlaying: boolean = false;\r\n private firstDrawTime: number = 0;\r\n private prevDrawTime: number = 0;\r\n private targetFPS: number = 60;\r\n private frameInterval: number = 1000 / 60;\r\n private lastFrameTime: number = 0;\r\n\r\n // Callback\r\n private onDrawCallback?: () => void;\r\n\r\n // Uniforms\r\n private iFrame: number = 0;\r\n private iMouse: MouseState = { x: 0, y: 0, clickX: 0, clickY: 0 };\r\n private hsv: HSVControls = { hue: 0, saturation: 1, brightness: 1 };\r\n private _mouseMode: MouseMode = \"click\";\r\n private _mouseSensitivity: number = 1.0; // New: mouse sensitivity multiplier\r\n private _mouseDamping: number = 0.9; // New: mouse movement damping factor (0-1)\r\n\r\n private _speed: number = 1; // Speed multiplier\r\n\r\n // Shader source\r\n private shaderSource: string = \"\";\r\n\r\n private readonly vertexShader = `#version 300 es\r\n #ifdef GL_ES\r\n precision highp float;\r\n precision highp int;\r\n #endif\r\n in vec2 position;\r\n void main() {\r\n gl_Position = vec4(position, 0.0, 1.0);\r\n }\r\n `;\r\n\r\n private readonly fragmentShaderHeader = `#version 300 es\r\n #ifdef GL_ES\r\n precision highp float;\r\n precision highp int;\r\n #endif\r\n \r\n uniform vec3 iResolution; // viewport resolution (in pixels)\r\n uniform float iTime; // shader playback time (in seconds)\r\n uniform float iTimeDelta; // render time (in seconds)\r\n uniform float iFrameRate; // shader frame rate\r\n uniform int iFrame; // shader playback frame\r\n uniform vec4 iMouse; // mouse pixel coords. xy: current, zw: click\r\n uniform vec4 iDate; // (year, month, day, unixtime in seconds)\r\n uniform vec3 iHSV; // HSV controls (hue, saturation, brightness)\r\n uniform float iSpeed; // speed multiplier\r\n \r\n out vec4 fragColor;\r\n \r\n // HSV to RGB conversion\r\n vec3 hsv2rgb(vec3 c) {\r\n vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);\r\n vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);\r\n return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);\r\n }\r\n \r\n // RGB to HSV conversion\r\n vec3 rgb2hsv(vec3 c) {\r\n vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);\r\n vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));\r\n vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));\r\n float d = q.x - min(q.w, q.y);\r\n float e = 1.0e-10;\r\n return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);\r\n }\r\n \r\n // Apply HSV adjustments\r\n vec3 applyHSV(vec3 color, vec3 hsvAdjust) {\r\n vec3 hsv = rgb2hsv(color);\r\n hsv.x = fract(hsv.x + hsvAdjust.x / 360.0);\r\n hsv.y = clamp(hsv.y * hsvAdjust.y, 0.0, 1.0);\r\n hsv.z = clamp(hsv.z * hsvAdjust.z, 0.0, 1.0);\r\n return hsv2rgb(hsv);\r\n }\r\n \r\n void mainImage(out vec4 c, in vec2 f);\r\n \r\n void main() {\r\n vec4 color = vec4(0.0, 0.0, 0.0, 1.0);\r\n mainImage(color, gl_FragCoord.xy);\r\n \r\n // Apply HSV adjustments if not default\r\n if (iHSV.x != 0.0 || iHSV.y != 1.0 || iHSV.z != 1.0) {\r\n color.rgb = applyHSV(color.rgb, iHSV);\r\n }\r\n \r\n fragColor = color;\r\n }\r\n `;\r\n\r\n constructor(\r\n private container: HTMLElement,\r\n mouseMode?: MouseMode,\r\n fps?: number,\r\n ) {\r\n if (mouseMode) {\r\n this._mouseMode = mouseMode;\r\n }\r\n if (fps) {\r\n this.setFrameRate(fps);\r\n }\r\n\r\n // Create renderer with WebGL 2 context\r\n this.renderer = new Renderer({\r\n width: this.container.clientWidth,\r\n height: this.container.clientHeight,\r\n dpr: window.devicePixelRatio,\r\n alpha: true,\r\n depth: false,\r\n stencil: false,\r\n antialias: true,\r\n powerPreference: \"high-performance\",\r\n });\r\n\r\n // Ensure WebGL 2 context\r\n if (!this.renderer.gl || !(this.renderer.gl instanceof WebGL2RenderingContext)) {\r\n throw new Error(\"WebGL 2 not supported\");\r\n }\r\n\r\n // Append canvas to container\r\n this.container.appendChild(this.renderer.gl.canvas);\r\n\r\n // Setup camera (orthographic for full-screen quad)\r\n this.camera = new Camera(this.renderer.gl);\r\n this.camera.position.z = 1;\r\n\r\n // Setup scene\r\n this.scene = new Transform();\r\n\r\n // Setup geometry (full-screen quad)\r\n this.geometry = new Geometry(this.renderer.gl, {\r\n position: {\r\n size: 2,\r\n data: new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1, -1, 1, 1, -1]),\r\n },\r\n });\r\n\r\n this.setup();\r\n }\r\n\r\n private setup(): void {\r\n this.setupMouseEvents();\r\n this.setupResizeHandler();\r\n }\r\n\r\n private setupMouseEvents(): void {\r\n const canvas = this.renderer.gl.canvas;\r\n let isMouseDown = false;\r\n\r\n const getScaledMousePos = (event: MouseEvent | Touch) => {\r\n const rect = canvas.getBoundingClientRect();\r\n const dpr = window.devicePixelRatio;\r\n\r\n // Get mouse position relative to canvas\r\n const x = event.clientX - rect.left;\r\n const y = event.clientY - rect.top;\r\n\r\n // Scale by DPR, apply sensitivity, and flip Y-axis\r\n return {\r\n x: x * dpr * this._mouseSensitivity,\r\n y: (canvas.height - y * dpr) * this._mouseSensitivity, // Flip Y to match GLSL coordinates\r\n };\r\n };\r\n\r\n canvas.addEventListener(\"mousemove\", (event: MouseEvent) => {\r\n const { x: newX, y: newY } = getScaledMousePos(event);\r\n\r\n // Apply damping with configurable factor\r\n this.iMouse.x = this.iMouse.x * this._mouseDamping + newX * (1 - this._mouseDamping);\r\n this.iMouse.y = this.iMouse.y * this._mouseDamping + newY * (1 - this._mouseDamping);\r\n\r\n // Handle click coordinates based on mode\r\n if (this._mouseMode === \"hover\" && !isMouseDown) {\r\n this.iMouse.clickX = this.iMouse.x;\r\n this.iMouse.clickY = this.iMouse.y;\r\n } else if (isMouseDown) {\r\n this.iMouse.clickX = newX;\r\n this.iMouse.clickY = newY;\r\n }\r\n });\r\n\r\n canvas.addEventListener(\"mousedown\", (event: MouseEvent) => {\r\n isMouseDown = true;\r\n const { x: clickX, y: clickY } = getScaledMousePos(event);\r\n\r\n if (this._mouseMode === \"click\") {\r\n this.iMouse.clickX = clickX;\r\n this.iMouse.clickY = clickY;\r\n }\r\n });\r\n\r\n canvas.addEventListener(\"mouseup\", () => {\r\n isMouseDown = false;\r\n });\r\n\r\n // Handle touch events for mobile\r\n canvas.addEventListener(\"touchmove\", (event: TouchEvent) => {\r\n event.preventDefault();\r\n const touch = event.touches[0];\r\n const { x: newX, y: newY } = getScaledMousePos(touch);\r\n\r\n this.iMouse.x = newX;\r\n this.iMouse.y = newY;\r\n\r\n if (this._mouseMode === \"hover\") {\r\n this.iMouse.clickX = newX;\r\n this.iMouse.clickY = newY;\r\n }\r\n });\r\n\r\n canvas.addEventListener(\"touchstart\", (event: TouchEvent) => {\r\n event.preventDefault();\r\n isMouseDown = true;\r\n const touch = event.touches[0];\r\n const { x: clickX, y: clickY } = getScaledMousePos(touch);\r\n\r\n if (this._mouseMode === \"click\") {\r\n this.iMouse.clickX = clickX;\r\n this.iMouse.clickY = clickY;\r\n }\r\n });\r\n\r\n canvas.addEventListener(\"touchend\", () => {\r\n isMouseDown = false;\r\n });\r\n }\r\n\r\n private setupResizeHandler(): void {\r\n const resizeObserver = new ResizeObserver(() => {\r\n const width = this.container.clientWidth;\r\n const height = this.container.clientHeight;\r\n\r\n // Update renderer size\r\n this.renderer.setSize(width, height);\r\n\r\n // Update viewport\r\n this.renderer.gl.viewport(\r\n 0,\r\n 0,\r\n width * window.devicePixelRatio,\r\n height * window.devicePixelRatio,\r\n );\r\n\r\n // Update resolution uniform if program exists\r\n if (this.program) {\r\n this.program.uniforms.iResolution.value = [\r\n width * window.devicePixelRatio,\r\n height * window.devicePixelRatio,\r\n window.devicePixelRatio,\r\n ];\r\n }\r\n });\r\n\r\n resizeObserver.observe(this.container);\r\n }\r\n\r\n private compileProgram(): boolean {\r\n if (!this.shaderSource) return false;\r\n\r\n const fullFragmentShader = this.fragmentShaderHeader + this.shaderSource;\r\n\r\n try {\r\n const program = new Program(this.renderer.gl, {\r\n vertex: this.vertexShader,\r\n fragment: fullFragmentShader,\r\n uniforms: {\r\n iResolution: {\r\n value: [\r\n this.container.clientWidth * window.devicePixelRatio,\r\n this.container.clientHeight * window.devicePixelRatio,\r\n window.devicePixelRatio,\r\n ],\r\n },\r\n iTime: { value: 0 },\r\n iTimeDelta: { value: 0 },\r\n iFrameRate: { value: this.targetFPS },\r\n iFrame: { value: 0 },\r\n iMouse: { value: [0, 0, 0, 0] },\r\n iDate: { value: [0, 0, 0, 0] },\r\n iHSV: { value: [this.hsv.hue, this.hsv.saturation, this.hsv.brightness] },\r\n iSpeed: { value: this._speed },\r\n },\r\n });\r\n\r\n this.program = program;\r\n this.mesh = new Mesh(this.renderer.gl, {\r\n geometry: this.geometry,\r\n program,\r\n });\r\n\r\n return true;\r\n } catch (error) {\r\n console.error(\"Failed to compile shader:\", error);\r\n return false;\r\n }\r\n }\r\n\r\n private draw(): void {\r\n if (!this.program || !this.mesh) {\r\n console.warn(\"Program or mesh not initialized\");\r\n return;\r\n }\r\n\r\n const now = this.isPlaying ? Date.now() : this.prevDrawTime;\r\n\r\n // Frame rate limiting\r\n if (this.isPlaying && this.targetFPS < 60) {\r\n const elapsed = now - this.lastFrameTime;\r\n if (elapsed < this.frameInterval) {\r\n requestAnimationFrame(() => this.animate());\r\n return;\r\n }\r\n this.lastFrameTime = now - (elapsed % this.frameInterval);\r\n }\r\n\r\n const date = new Date(now);\r\n\r\n if (this.firstDrawTime === 0) {\r\n this.firstDrawTime = now;\r\n }\r\n\r\n if (this.onDrawCallback) {\r\n this.onDrawCallback();\r\n }\r\n\r\n const iTimeDelta = (now - this.prevDrawTime) * 0.001 * this._speed;\r\n const iTime = (now - this.firstDrawTime) * 0.001 * this._speed;\r\n const iDate = [date.getFullYear(), date.getMonth(), date.getDate(), date.getTime() * 0.001];\r\n\r\n if (this.program && this.mesh) {\r\n // Update uniforms\r\n this.program.uniforms.iResolution.value = [\r\n this.container.clientWidth * window.devicePixelRatio,\r\n this.container.clientHeight * window.devicePixelRatio,\r\n window.devicePixelRatio,\r\n ];\r\n this.program.uniforms.iTime.value = iTime;\r\n this.program.uniforms.iTimeDelta.value = iTimeDelta;\r\n this.program.uniforms.iFrameRate.value = this.targetFPS;\r\n this.program.uniforms.iFrame.value = this.iFrame;\r\n this.program.uniforms.iMouse.value = [\r\n this.iMouse.x,\r\n this.iMouse.y,\r\n this.iMouse.clickX,\r\n this.iMouse.clickY,\r\n ];\r\n this.program.uniforms.iDate.value = iDate;\r\n this.program.uniforms.iHSV.value = [this.hsv.hue, this.hsv.saturation, this.hsv.brightness];\r\n this.program.uniforms.iSpeed.value = this._speed;\r\n\r\n // Render\r\n this.renderer.render({ scene: this.mesh, camera: this.camera });\r\n }\r\n\r\n this.prevDrawTime = now;\r\n this.iFrame++;\r\n }\r\n\r\n private animate = (): void => {\r\n if (this.isPlaying) {\r\n this.draw();\r\n requestAnimationFrame(this.animate);\r\n }\r\n };\r\n\r\n // Public methods\r\n public setShader(config: ShaderConfig): boolean {\r\n this.shaderSource = config.source;\r\n const success = this.compileProgram();\r\n\r\n // If playing, trigger a redraw\r\n if (success && this.isPlaying) {\r\n this.draw();\r\n }\r\n\r\n return success;\r\n }\r\n\r\n public setHSV(hsv: Partial<HSVControls>): void {\r\n if (hsv.hue !== undefined) this.hsv.hue = hsv.hue;\r\n if (hsv.saturation !== undefined) this.hsv.saturation = hsv.saturation;\r\n if (hsv.brightness !== undefined) this.hsv.brightness = hsv.brightness;\r\n\r\n // Update immediately if not playing\r\n if (!this.isPlaying && this.program && this.mesh) {\r\n this.draw();\r\n }\r\n }\r\n\r\n public setHue(val: number) {\r\n this.hsv.hue = val;\r\n\r\n // Update immediately if not playing\r\n if (!this.isPlaying && this.program && this.mesh) {\r\n this.draw();\r\n }\r\n }\r\n\r\n public setSaturation(val: number) {\r\n this.hsv.saturation = val;\r\n\r\n // Update immediately if not playing\r\n if (!this.isPlaying && this.program && this.mesh) {\r\n this.draw();\r\n }\r\n }\r\n\r\n public setBrightness(val: number) {\r\n this.hsv.brightness = val;\r\n\r\n // Update immediately if not playing\r\n if (!this.isPlaying && this.program && this.mesh) {\r\n this.draw();\r\n }\r\n }\r\n\r\n public getHSV(): HSVControls {\r\n return { ...this.hsv };\r\n }\r\n // New speed methods\r\n public setSpeed(val: number): void {\r\n this._speed = Math.max(0, val);\r\n\r\n // Update immediately if not playing\r\n if (!this.isPlaying && this.program && this.mesh) {\r\n this.draw();\r\n }\r\n }\r\n\r\n public getSpeed(): number {\r\n return this._speed;\r\n }\r\n\r\n public setFrameRate(fps: number): void {\r\n this.targetFPS = Math.max(1, Math.min(60, fps));\r\n this.frameInterval = 1000 / this.targetFPS;\r\n }\r\n\r\n public getFrameRate(): number {\r\n return this.targetFPS;\r\n }\r\n\r\n public setOnDraw(callback: () => void): void {\r\n this.onDrawCallback = callback;\r\n }\r\n\r\n public time(): number {\r\n return (this.prevDrawTime - this.firstDrawTime) * 0.001 * this._speed;\r\n }\r\n\r\n public isPlayingState(): boolean {\r\n return this.isPlaying;\r\n }\r\n\r\n public reset(): void {\r\n const now = Date.now();\r\n this.firstDrawTime = now;\r\n this.prevDrawTime = now;\r\n this.lastFrameTime = now;\r\n this.iFrame = 0;\r\n this.draw();\r\n }\r\n\r\n public pause(): void {\r\n this.isPlaying = false;\r\n }\r\n\r\n public play(): void {\r\n if (!this.isPlaying) {\r\n this.isPlaying = true;\r\n const now = Date.now();\r\n const elapsed = this.prevDrawTime - this.firstDrawTime;\r\n this.firstDrawTime = now - elapsed;\r\n this.prevDrawTime = now;\r\n this.lastFrameTime = now;\r\n this.animate();\r\n }\r\n }\r\n\r\n public dispose(): void {\r\n this.pause();\r\n if (this.renderer.gl.canvas.parentElement) {\r\n this.renderer.gl.canvas.parentElement.removeChild(this.renderer.gl.canvas);\r\n }\r\n }\r\n\r\n // Getters and Setters\r\n public get mouseMode(): MouseMode {\r\n return this._mouseMode;\r\n }\r\n\r\n public set mouseMode(val: MouseMode) {\r\n this._mouseMode = val;\r\n }\r\n public get speed(): number {\r\n return this._speed;\r\n }\r\n\r\n public set speed(val: number) {\r\n this.setSpeed(val);\r\n }\r\n\r\n // New mouse sensitivity methods\r\n public setMouseSensitivity(sensitivity: number): void {\r\n this._mouseSensitivity = Math.max(0.1, Math.min(5.0, sensitivity)); // Clamp between 0.1 and 5.0\r\n }\r\n\r\n public getMouseSensitivity(): number {\r\n return this._mouseSensitivity;\r\n }\r\n\r\n // New mouse damping methods\r\n public setMouseDamping(damping: number): void {\r\n this._mouseDamping = Math.max(0, Math.min(0.99, damping)); // Clamp between 0 and 0.99\r\n }\r\n\r\n public getMouseDamping(): number {\r\n return this._mouseDamping;\r\n }\r\n}\r\n"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"path": "ShaderToy.vue",
|
|
17
|
+
"content": "<template>\r\n <div\r\n ref=\"containerRef\"\r\n :class=\"['shadertoy-container', props.class]\"\r\n />\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, onMounted, onUnmounted, watch, type HTMLAttributes } from \"vue\";\r\nimport { InspiraShaderToy, type MouseMode } from \"./InspiraShaderToy\";\r\n\r\ninterface Props {\r\n mouseMode?: MouseMode;\r\n class?: HTMLAttributes[\"class\"];\r\n shaderCode: string;\r\n hue?: number;\r\n saturation?: number;\r\n brightness?: number;\r\n speed?: number;\r\n mouseSensitivity?: number;\r\n damping?: number;\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n mouseMode: \"click\",\r\n hue: 0,\r\n saturation: 1,\r\n brightness: 1,\r\n speed: 1,\r\n mouseSensitivity: 1,\r\n damping: 0,\r\n});\r\n\r\nconst containerRef = ref<HTMLElement>();\r\nlet shader: InspiraShaderToy | undefined = undefined;\r\n\r\nonMounted(() => {\r\n if (!containerRef.value) return;\r\n\r\n shader = new InspiraShaderToy(containerRef.value, props.mouseMode);\r\n\r\n const success = shader.setShader({\r\n source: props.shaderCode,\r\n });\r\n\r\n if (!success) {\r\n console.error(\"Failed to compile shader\");\r\n return;\r\n }\r\n\r\n shader.setHSV({\r\n hue: props.hue,\r\n saturation: props.saturation,\r\n brightness: props.brightness,\r\n });\r\n\r\n shader.setSpeed(props.speed);\r\n\r\n shader.setMouseSensitivity(props.mouseSensitivity);\r\n shader.setMouseDamping(props.damping);\r\n\r\n shader.play();\r\n});\r\n\r\nonUnmounted(() => {\r\n shader?.dispose();\r\n});\r\n\r\nwatch(\r\n () => props.hue,\r\n (v) => {\r\n if (v !== undefined && shader) {\r\n shader.setHue(v);\r\n }\r\n },\r\n);\r\n\r\nwatch(\r\n () => props.saturation,\r\n (v) => {\r\n if (v !== undefined && shader) {\r\n shader.setSaturation(v);\r\n }\r\n },\r\n);\r\n\r\nwatch(\r\n () => props.brightness,\r\n (v) => {\r\n if (v !== undefined && shader) {\r\n shader.setBrightness(v);\r\n }\r\n },\r\n);\r\n\r\nwatch(\r\n () => props.speed,\r\n (v) => {\r\n if (v !== undefined && shader) {\r\n shader.setSpeed(v);\r\n }\r\n },\r\n);\r\n\r\nwatch(\r\n () => props.mouseSensitivity,\r\n (v) => {\r\n if (v !== undefined && shader) {\r\n shader.setMouseSensitivity(v);\r\n }\r\n },\r\n);\r\n\r\nwatch(\r\n () => props.damping,\r\n (v) => {\r\n if (v !== undefined && shader) {\r\n shader.setMouseDamping(v);\r\n }\r\n },\r\n);\r\n</script>\r\n\r\n<style scoped>\r\n.shadertoy-container {\r\n display: block;\r\n position: relative;\r\n height: 100%;\r\n width: 100%;\r\n}\r\n\r\n.shadertoy-container canvas {\r\n display: block;\r\n max-width: 100%;\r\n width: 100%;\r\n height: 100%;\r\n cursor: pointer;\r\n}\r\n</style>\r\n"
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"fileCount": 3,
|
|
21
|
+
"contentHash": "ac23fae2605bb74de98c31d1358edf85ea1bf9ef"
|
|
22
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "shimmer-button",
|
|
3
|
+
"dependencies": [],
|
|
4
|
+
"files": [
|
|
5
|
+
{
|
|
6
|
+
"path": "index.ts",
|
|
7
|
+
"content": "export { default as ShimmerButton } from \"./ShimmerButton.vue\";\r\n"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"path": "ShimmerButton.vue",
|
|
11
|
+
"content": "<template>\r\n <button\r\n :class=\"\r\n cn(\r\n 'group relative z-0 flex cursor-pointer items-center justify-center overflow-hidden whitespace-nowrap border border-white/10 px-6 py-3 text-white [background:var(--bg)] [border-radius:var(--radius)] dark:text-black',\r\n 'transform-gpu transition-transform duration-300 ease-in-out active:translate-y-px',\r\n $props.class,\r\n )\r\n \"\r\n :style=\"{\r\n '--spread': '90deg',\r\n '--shimmer-color': shimmerColor,\r\n '--radius': borderRadius,\r\n '--speed': shimmerDuration,\r\n '--cut': shimmerSize,\r\n '--bg': background,\r\n }\"\r\n >\r\n <div :class=\"cn('-z-30 blur-[2px]', 'absolute inset-0 overflow-visible [container-type:size]')\">\r\n <div\r\n class=\"animate-shimmer-btn-shimmer-slide absolute inset-0 h-[100cqh] [aspect-ratio:1] [border-radius:0] [mask:none]\"\r\n >\r\n <div\r\n class=\"animate-shimmer-btn-spin-around absolute -inset-full w-auto rotate-0 [background:conic-gradient(from_calc(270deg-(var(--spread)*0.5)),transparent_0,var(--shimmer-color)_var(--spread),transparent_var(--spread))] [translate:0_0]\"\r\n />\r\n </div>\r\n </div>\r\n <slot />\r\n\r\n <div\r\n :class=\"\r\n cn(\r\n 'insert-0 absolute size-full',\r\n\r\n 'rounded-2xl px-4 py-1.5 text-sm font-medium shadow-[inset_0_-8px_10px_#ffffff1f]',\r\n\r\n // transition\r\n 'transform-gpu transition-all duration-300 ease-in-out',\r\n\r\n // on hover\r\n 'group-hover:shadow-[inset_0_-6px_10px_#ffffff3f]',\r\n\r\n // on click\r\n 'group-active:shadow-[inset_0_-10px_10px_#ffffff3f]',\r\n )\r\n \"\r\n />\r\n\r\n <div\r\n class=\"absolute -z-20 [background:var(--bg)] [border-radius:var(--radius)] [inset:var(--cut)]\"\r\n />\r\n </button>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport { cn } from \"@/lib/utils\";\r\n\r\ntype ShimmerButtonProps = {\r\n shimmerColor?: string;\r\n shimmerSize?: string;\r\n borderRadius?: string;\r\n shimmerDuration?: string;\r\n background?: string;\r\n class?: string;\r\n};\r\n\r\nwithDefaults(defineProps<ShimmerButtonProps>(), {\r\n shimmerColor: \"#ffffff\",\r\n shimmerSize: \"0.05em\",\r\n shimmerDuration: \"3s\",\r\n borderRadius: \"100px\",\r\n background: \"rgba(0, 0, 0, 1)\",\r\n});\r\n</script>\r\n\r\n<style scoped>\r\n@keyframes shimmer-btn-shimmer-slide {\r\n to {\r\n transform: translate(calc(100cqw - 100%), 0);\r\n }\r\n}\r\n\r\n@keyframes shimmer-btn-spin-around {\r\n 0% {\r\n transform: translateZ(0) rotate(0);\r\n }\r\n 15%,\r\n 35% {\r\n transform: translateZ(0) rotate(90deg);\r\n }\r\n 65%,\r\n 85% {\r\n transform: translateZ(0) rotate(270deg);\r\n }\r\n 100% {\r\n transform: translateZ(0) rotate(360deg);\r\n }\r\n}\r\n\r\n.animate-shimmer-btn-shimmer-slide {\r\n animation: shimmer-btn-shimmer-slide var(--speed) ease-in-out infinite alternate;\r\n}\r\n\r\n.animate-shimmer-btn-spin-around {\r\n animation: shimmer-btn-spin-around calc(var(--speed) * 2) infinite linear;\r\n}\r\n</style>\r\n"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"fileCount": 2,
|
|
15
|
+
"contentHash": "1ae683af5e3de36673b49613023b0ba71f75f7bf"
|
|
16
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sleek-line-cursor",
|
|
3
|
+
"dependencies": [],
|
|
4
|
+
"files": [
|
|
5
|
+
{
|
|
6
|
+
"path": "SleekLineCursor.vue",
|
|
7
|
+
"content": "<template>\r\n <canvas\r\n id=\"canvas\"\r\n ref=\"canvasRef\"\r\n :class=\"cn('pointer-events-none fixed inset-0 z-50', props.class)\"\r\n />\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { onMounted, onUnmounted, ref, type HTMLAttributes } from \"vue\";\r\nimport { cn } from \"~/lib/utils\";\r\n\r\ninterface Props {\r\n friction?: number;\r\n trails?: number;\r\n size?: number;\r\n dampening?: number;\r\n tension?: number;\r\n class?: HTMLAttributes[\"class\"];\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n friction: 0.5,\r\n trails: 20,\r\n size: 50,\r\n dampening: 0.25,\r\n tension: 0.98,\r\n});\r\n\r\nconst canvasRef = ref<HTMLCanvasElement | null>(null);\r\n\r\ninterface NodeType {\r\n x: number;\r\n y: number;\r\n vx: number;\r\n vy: number;\r\n}\r\n\r\ninterface WaveOptions {\r\n phase?: number;\r\n offset?: number;\r\n frequency?: number;\r\n amplitude?: number;\r\n}\r\n\r\ninterface LineOptions {\r\n spring: number;\r\n}\r\n\r\nclass Wave {\r\n phase: number = 0;\r\n offset: number = 0;\r\n frequency: number = 0.001;\r\n amplitude: number = 1;\r\n private e: number = 0;\r\n\r\n constructor(options: WaveOptions = {}) {\r\n this.init(options);\r\n }\r\n\r\n init(options: WaveOptions): void {\r\n this.phase = options.phase || 0;\r\n this.offset = options.offset || 0;\r\n this.frequency = options.frequency || 0.001;\r\n this.amplitude = options.amplitude || 1;\r\n }\r\n\r\n update(): number {\r\n this.phase += this.frequency;\r\n this.e = this.offset + Math.sin(this.phase) * this.amplitude;\r\n return this.e;\r\n }\r\n\r\n value(): number {\r\n return this.e;\r\n }\r\n}\r\n\r\nclass Node implements NodeType {\r\n x: number = 0;\r\n y: number = 0;\r\n vx: number = 0;\r\n vy: number = 0;\r\n}\r\n\r\nclass Line {\r\n spring: number = 0;\r\n friction: number = 0;\r\n nodes: NodeType[] = [];\r\n\r\n constructor(options: LineOptions) {\r\n this.init(options);\r\n }\r\n\r\n init(options: LineOptions): void {\r\n this.spring = options.spring + 0.1 * Math.random() - 0.02;\r\n this.friction = E.friction + 0.01 * Math.random() - 0.002;\r\n this.nodes = [];\r\n\r\n for (let n = 0; n < E.size; n++) {\r\n const t = new Node();\r\n t.x = pos.x;\r\n t.y = pos.y;\r\n this.nodes.push(t);\r\n }\r\n }\r\n\r\n update(): void {\r\n let e = this.spring;\r\n let t = this.nodes[0];\r\n\r\n t.vx += (pos.x - t.x) * e;\r\n t.vy += (pos.y - t.y) * e;\r\n\r\n for (let i = 0, a = this.nodes.length; i < a; i++) {\r\n t = this.nodes[i];\r\n\r\n if (i > 0) {\r\n const n = this.nodes[i - 1];\r\n t.vx += (n.x - t.x) * e;\r\n t.vy += (n.y - t.y) * e;\r\n t.vx += n.vx * E.dampening;\r\n t.vy += n.vy * E.dampening;\r\n }\r\n\r\n t.vx *= this.friction;\r\n t.vy *= this.friction;\r\n t.x += t.vx;\r\n t.y += t.vy;\r\n e *= E.tension;\r\n }\r\n }\r\n\r\n draw(ctx: CanvasRenderingContext2D): void {\r\n let e: NodeType, t: NodeType;\r\n let n = this.nodes[0].x;\r\n let i = this.nodes[0].y;\r\n\r\n ctx.beginPath();\r\n ctx.moveTo(n, i);\r\n\r\n for (let a = 1, o = this.nodes.length - 2; a < o; a++) {\r\n e = this.nodes[a];\r\n t = this.nodes[a + 1];\r\n n = 0.5 * (e.x + t.x);\r\n i = 0.5 * (e.y + t.y);\r\n ctx.quadraticCurveTo(e.x, e.y, n, i);\r\n }\r\n\r\n e = this.nodes[this.nodes.length - 2];\r\n t = this.nodes[this.nodes.length - 1];\r\n ctx.quadraticCurveTo(e.x, e.y, t.x, t.y);\r\n ctx.stroke();\r\n ctx.closePath();\r\n }\r\n}\r\n\r\nlet ctx: CanvasRenderingContext2D & { running?: boolean; frame?: number };\r\nlet f: Wave;\r\nlet pos = { x: 0, y: 0 };\r\nlet lines: Line[] = [];\r\n\r\nconst E = {\r\n debug: true,\r\n friction: props.friction,\r\n trails: props.trails,\r\n size: props.size,\r\n dampening: props.dampening,\r\n tension: props.tension,\r\n};\r\n\r\nfunction createLines(): void {\r\n lines = [];\r\n for (let e = 0; e < E.trails; e++) {\r\n lines.push(new Line({ spring: 0.4 + (e / E.trails) * 0.025 }));\r\n }\r\n}\r\n\r\nfunction updatePosition(e: MouseEvent | TouchEvent): void {\r\n if (\"touches\" in e) {\r\n pos.x = e.touches[0].pageX;\r\n pos.y = e.touches[0].pageY;\r\n } else {\r\n pos.x = e.clientX;\r\n pos.y = e.clientY;\r\n }\r\n e.preventDefault();\r\n}\r\n\r\nfunction handleTouchMove(e: TouchEvent): void {\r\n if (e.touches.length === 1) {\r\n pos.x = e.touches[0].pageX;\r\n pos.y = e.touches[0].pageY;\r\n }\r\n}\r\n\r\nfunction render(): void {\r\n if (ctx.running) {\r\n ctx.globalCompositeOperation = \"source-over\";\r\n ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);\r\n ctx.globalCompositeOperation = \"lighter\";\r\n ctx.strokeStyle = `hsla(${Math.round(f.update())},50%,50%,0.2)`;\r\n ctx.lineWidth = 1;\r\n\r\n for (let t = 0; t < E.trails; t++) {\r\n const e = lines[t];\r\n e.update();\r\n e.draw(ctx);\r\n }\r\n\r\n ctx.frame = (ctx.frame || 0) + 1;\r\n window.requestAnimationFrame(render);\r\n }\r\n}\r\n\r\nfunction resizeCanvas(): void {\r\n if (ctx && ctx.canvas) {\r\n ctx.canvas.width = window.innerWidth - 20;\r\n ctx.canvas.height = window.innerHeight;\r\n }\r\n}\r\n\r\nfunction onMouseMove(e: MouseEvent | TouchEvent): void {\r\n document.removeEventListener(\"mousemove\", onMouseMove);\r\n document.removeEventListener(\"touchstart\", onMouseMove);\r\n document.addEventListener(\"mousemove\", updatePosition);\r\n document.addEventListener(\"touchmove\", updatePosition);\r\n document.addEventListener(\"touchstart\", handleTouchMove);\r\n updatePosition(e);\r\n createLines();\r\n render();\r\n}\r\n\r\nfunction handleFocus(): void {\r\n if (!ctx.running) {\r\n ctx.running = true;\r\n render();\r\n }\r\n}\r\n\r\nfunction handleBlur(): void {\r\n ctx.running = true;\r\n}\r\n\r\nfunction initCanvas(): void {\r\n const canvas = canvasRef.value;\r\n if (!canvas) return;\r\n\r\n ctx = canvas.getContext(\"2d\") as CanvasRenderingContext2D & {\r\n running?: boolean;\r\n frame?: number;\r\n };\r\n\r\n ctx.running = true;\r\n ctx.frame = 1;\r\n\r\n f = new Wave({\r\n phase: Math.random() * 2 * Math.PI,\r\n amplitude: 85,\r\n frequency: 0.0015,\r\n offset: 285,\r\n });\r\n\r\n document.addEventListener(\"mousemove\", onMouseMove);\r\n document.addEventListener(\"touchstart\", onMouseMove);\r\n document.body.addEventListener(\"orientationchange\", resizeCanvas);\r\n window.addEventListener(\"resize\", resizeCanvas);\r\n window.addEventListener(\"focus\", handleFocus);\r\n window.addEventListener(\"blur\", handleBlur);\r\n\r\n resizeCanvas();\r\n}\r\n\r\nfunction cleanup(): void {\r\n if (ctx) {\r\n ctx.running = false;\r\n }\r\n\r\n document.removeEventListener(\"mousemove\", onMouseMove);\r\n document.removeEventListener(\"mousemove\", updatePosition);\r\n document.removeEventListener(\"touchstart\", onMouseMove);\r\n document.removeEventListener(\"touchstart\", handleTouchMove);\r\n document.removeEventListener(\"touchmove\", updatePosition);\r\n document.body.removeEventListener(\"orientationchange\", resizeCanvas);\r\n window.removeEventListener(\"resize\", resizeCanvas);\r\n window.removeEventListener(\"focus\", handleFocus);\r\n window.removeEventListener(\"blur\", handleBlur);\r\n}\r\n\r\nonMounted(() => {\r\n initCanvas();\r\n});\r\n\r\nonUnmounted(() => {\r\n cleanup();\r\n});\r\n</script>\r\n\r\n<style scoped>\r\n/* Tailwind classes are applied directly to the template */\r\n</style>\r\n"
|
|
8
|
+
}
|
|
9
|
+
],
|
|
10
|
+
"fileCount": 1,
|
|
11
|
+
"contentHash": "d4ed2755e71527f515823fc53be50d51e99a7d9a"
|
|
12
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "smooth-cursor",
|
|
3
|
+
"dependencies": [
|
|
4
|
+
"@vueuse/core",
|
|
5
|
+
"motion-v"
|
|
6
|
+
],
|
|
7
|
+
"files": [
|
|
8
|
+
{
|
|
9
|
+
"path": "DefaultCursor.vue",
|
|
10
|
+
"content": "<template>\r\n <svg\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n width=\"32\"\r\n height=\"32\"\r\n viewBox=\"0 0 32 32\"\r\n >\r\n <path\r\n fill=\"currentColor\"\r\n d=\"M9.391 2.32C8.42 1.56 7 2.253 7 3.486V28.41c0 1.538 1.966 2.18 2.874.938l6.225-8.523a2 2 0 0 1 1.615-.82h9.69c1.512 0 2.17-1.912.978-2.844z\"\r\n />\r\n </svg>\r\n</template>\r\n"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"path": "index.ts",
|
|
14
|
+
"content": "export { default as SmoothCursor } from \"./SmoothCursor.vue\";\r\n"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"path": "SmoothCursor.vue",
|
|
18
|
+
"content": "<template>\r\n <Motion\r\n as=\"div\"\r\n :style=\"{\r\n position: 'fixed',\r\n left: cursorX,\r\n top: cursorY,\r\n translateX: '-50%',\r\n translateY: '-50%',\r\n rotate: rotation,\r\n scale: scale,\r\n zIndex: 100,\r\n pointerEvents: 'none',\r\n willChange: 'transform',\r\n }\"\r\n :initial=\"{ scale: 0 }\"\r\n :animate=\"{ scale: 1 }\"\r\n :transition=\"{\r\n type: 'spring',\r\n stiffness: 400,\r\n damping: 30,\r\n }\"\r\n >\r\n <component :is=\"props.cursor\" />\r\n </Motion>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport type { Component } from \"vue\";\r\nimport DefaultCursor from \"./DefaultCursor.vue\";\r\nimport { useSpring, Motion } from \"motion-v\";\r\nimport { useEventListener, useTimeout } from \"@vueuse/core\";\r\n\r\ninterface Position {\r\n x: number;\r\n y: number;\r\n}\r\ninterface SmoothCursorProps {\r\n cursor?: Component;\r\n springConfig?: {\r\n damping: number;\r\n stiffness: number;\r\n mass: number;\r\n restDelta: number;\r\n };\r\n}\r\n\r\nconst props = withDefaults(defineProps<SmoothCursorProps>(), {\r\n cursor: () => DefaultCursor,\r\n springConfig: () => ({\r\n damping: 45,\r\n stiffness: 400,\r\n mass: 1,\r\n restDelta: 0.001,\r\n }),\r\n});\r\n\r\nconst isMoving = ref(false);\r\nconst lastMousePos = ref<Position>({ x: 0, y: 0 });\r\nconst velocity = ref<Position>({ x: 0, y: 0 });\r\nconst lastUpdateTime = ref(Date.now());\r\nconst previousAngle = ref(0);\r\nconst accumulatedRotation = ref(0);\r\n\r\nconst cursorX = useSpring(0, props.springConfig);\r\nconst cursorY = useSpring(0, props.springConfig);\r\nconst rotation = useSpring(0, {\r\n ...props.springConfig,\r\n damping: 60,\r\n stiffness: 300,\r\n});\r\nconst scale = useSpring(1, {\r\n ...props.springConfig,\r\n stiffness: 500,\r\n damping: 35,\r\n});\r\n\r\nfunction updateVelocity(currentPos: Position) {\r\n const currentTime = Date.now();\r\n const deltaTime = currentTime - lastUpdateTime.value;\r\n\r\n if (deltaTime > 0) {\r\n velocity.value = {\r\n x: (currentPos.x - lastMousePos.value.x) / deltaTime,\r\n y: (currentPos.y - lastMousePos.value.y) / deltaTime,\r\n };\r\n }\r\n\r\n lastUpdateTime.value = currentTime;\r\n lastMousePos.value = currentPos;\r\n}\r\n\r\nfunction smoothMouseMove(e: MouseEvent) {\r\n const currentPos = { x: e.clientX, y: e.clientY };\r\n updateVelocity(currentPos);\r\n\r\n const speed = Math.sqrt(Math.pow(velocity.value.x, 2) + Math.pow(velocity.value.y, 2));\r\n\r\n cursorX.set(currentPos.x);\r\n cursorY.set(currentPos.y);\r\n\r\n if (speed > 0.1) {\r\n const currentAngle = Math.atan2(velocity.value.y, velocity.value.x) * (180 / Math.PI) + 90;\r\n\r\n let angleDiff = currentAngle - previousAngle.value;\r\n if (angleDiff > 180) angleDiff -= 360;\r\n if (angleDiff < -180) angleDiff += 360;\r\n accumulatedRotation.value += angleDiff;\r\n rotation.set(accumulatedRotation.value);\r\n previousAngle.value = currentAngle;\r\n\r\n scale.set(0.95);\r\n isMoving.value = true;\r\n\r\n useTimeout(150, {\r\n callback: () => {\r\n scale.set(1);\r\n isMoving.value = false;\r\n },\r\n });\r\n }\r\n}\r\n\r\nlet rafId: number;\r\n\r\nfunction throttledMouseMove(e: MouseEvent) {\r\n if (rafId) return;\r\n\r\n rafId = requestAnimationFrame(() => {\r\n smoothMouseMove(e);\r\n rafId = 0;\r\n });\r\n}\r\n\r\ndocument.body.style.cursor = \"none\";\r\nuseEventListener(window, \"mousemove\", throttledMouseMove);\r\n\r\nonUnmounted(() => {\r\n if (rafId) cancelAnimationFrame(rafId);\r\n document.body.style.cursor = \"default\";\r\n});\r\n</script>\r\n<style scoped></style>\r\n"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"fileCount": 3,
|
|
22
|
+
"contentHash": "27cf030e0499af7da467680730d8b3f947b0f9e8"
|
|
23
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "snowfall-bg",
|
|
3
|
+
"dependencies": [
|
|
4
|
+
"@vueuse/core"
|
|
5
|
+
],
|
|
6
|
+
"files": [
|
|
7
|
+
{
|
|
8
|
+
"path": "index.ts",
|
|
9
|
+
"content": "export { default as SnowfallBg } from \"./SnowfallBg.vue\";\r\n"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"path": "SnowfallBg.vue",
|
|
13
|
+
"content": "<template>\r\n <div\r\n ref=\"canvasContainerRef\"\r\n :class=\"$props.class\"\r\n aria-hidden=\"true\"\r\n >\r\n <canvas ref=\"canvasRef\"></canvas>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { onMounted, onBeforeUnmount, ref, reactive, computed } from \"vue\";\r\nimport { useDevicePixelRatio } from \"@vueuse/core\";\r\n\r\ntype Snowflake = {\r\n x: number;\r\n y: number;\r\n size: number;\r\n alpha: number;\r\n dx: number; // Horizontal drift\r\n dy: number; // Vertical fall speed\r\n};\r\n\r\ntype Props = {\r\n color?: string;\r\n quantity?: number;\r\n speed?: number;\r\n maxRadius?: number;\r\n minRadius?: number;\r\n class?: string;\r\n};\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n color: \"#FFF\",\r\n quantity: 100,\r\n speed: 1, // Controls how fast the snowflakes fall\r\n maxRadius: 3, // Default max radius\r\n minRadius: 1, // Default min radius\r\n class: \"\",\r\n});\r\n\r\nconst canvasRef = ref<HTMLCanvasElement | null>(null);\r\nconst canvasContainerRef = ref<HTMLDivElement | null>(null);\r\nconst context = ref<CanvasRenderingContext2D | null>(null);\r\nconst snowflakes = ref<Snowflake[]>([]);\r\nconst canvasSize = reactive<{ w: number; h: number }>({ w: 0, h: 0 });\r\nconst { pixelRatio } = useDevicePixelRatio();\r\n\r\nconst color = computed(() => {\r\n const hex = props.color.replace(/^#/, \"\").padStart(6, \"0\");\r\n const bigint = parseInt(hex, 16);\r\n const r = (bigint >> 16) & 255;\r\n const g = (bigint >> 8) & 255;\r\n const b = bigint & 255;\r\n return `${r} ${g} ${b}`;\r\n});\r\n\r\nonMounted(() => {\r\n if (canvasRef.value) {\r\n context.value = canvasRef.value.getContext(\"2d\");\r\n }\r\n initCanvas();\r\n animate();\r\n window.addEventListener(\"resize\", initCanvas);\r\n});\r\n\r\nonBeforeUnmount(() => {\r\n window.removeEventListener(\"resize\", initCanvas);\r\n});\r\n\r\nfunction initCanvas() {\r\n resizeCanvas();\r\n createSnowflakes();\r\n}\r\n\r\nfunction resizeCanvas() {\r\n if (canvasContainerRef.value && canvasRef.value && context.value) {\r\n snowflakes.value.length = 0;\r\n canvasSize.w = canvasContainerRef.value.offsetWidth;\r\n canvasSize.h = canvasContainerRef.value.offsetHeight;\r\n canvasRef.value.width = canvasSize.w * pixelRatio.value;\r\n canvasRef.value.height = canvasSize.h * pixelRatio.value;\r\n canvasRef.value.style.width = `${canvasSize.w}px`;\r\n canvasRef.value.style.height = `${canvasSize.h}px`;\r\n context.value.scale(pixelRatio.value, pixelRatio.value);\r\n }\r\n}\r\n\r\nfunction createSnowflakes() {\r\n for (let i = 0; i < props.quantity; i++) {\r\n const snowflake = createSnowflake();\r\n snowflakes.value.push(snowflake);\r\n }\r\n}\r\n\r\nfunction createSnowflake(): Snowflake {\r\n const x = Math.random() * canvasSize.w;\r\n const y = Math.random() * canvasSize.h;\r\n const size = Math.random() * (props.maxRadius! - props.minRadius!) + props.minRadius!; // Random size between min and max radius\r\n const alpha = Math.random() * 0.5 + 0.5; // Opacity between 0.5 and 1\r\n const dx = (Math.random() - 0.5) * 0.5; // Slight horizontal drift\r\n const dy = Math.random() * 0.25 + props.speed; // Falling speed\r\n\r\n return { x, y, size, alpha, dx, dy };\r\n}\r\n\r\nfunction drawSnowflake(snowflake: Snowflake) {\r\n if (context.value) {\r\n const { x, y, size, alpha } = snowflake;\r\n context.value.beginPath();\r\n context.value.arc(x, y, size, 0, Math.PI * 2);\r\n context.value.fillStyle = `rgba(${color.value.split(\" \").join(\", \")}, ${alpha})`;\r\n context.value.fill();\r\n }\r\n}\r\n\r\nfunction animate() {\r\n if (context.value) {\r\n context.value.clearRect(0, 0, canvasSize.w, canvasSize.h);\r\n }\r\n\r\n snowflakes.value.forEach((snowflake) => {\r\n snowflake.x += snowflake.dx; // Drift horizontally\r\n snowflake.y += snowflake.dy; // Fall down\r\n\r\n // Reset snowflake when it moves out of the canvas\r\n if (snowflake.y > canvasSize.h) {\r\n snowflake.y = -snowflake.size; // Reset to the top\r\n snowflake.x = Math.random() * canvasSize.w; // Random horizontal position\r\n }\r\n\r\n drawSnowflake(snowflake);\r\n });\r\n\r\n requestAnimationFrame(animate);\r\n}\r\n</script>\r\n"
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"fileCount": 2,
|
|
17
|
+
"contentHash": "a014b39dd7310e0fecf1bff4046680b4d1fc3ef4"
|
|
18
|
+
}
|