Nemůžete vyplnit toto pole
doporučujeme
produktové fotky BOLD 47
ZDARMAZDARMA
Doprava zdarma
Týdenní přehled VERTICAL STANDALONE | BOLD
✨ Týdny s prostorem pro měsíční moduly – vertikální uspořádání s flexibilitou Co dostaneš: Týdny ledna až června 2026 rozdělené po měsících Vertikální...
150 Kč
 
Objednáno
Dárek zdarma
Dárek zdarma
Ke každé objednávce
Garance doručení
nepoškozeného zboží
Přes 3000 výdejních míst
po celé ČR
Doručení do druhého dne
na jakékoliv místo

Vítejte v našem světě kreativního plánování

 

Krásné, praktické a hlavně funkční náplně do diářů - to je keikaku.

 

Máte rádi bullet journaling, ale nemáte na vykreslování čas? Nudí Vás nudné diáře, kterých je všude plno? 

 

...pak jste tu správně :) 

'use client'; import React, { useEffect, useRef } from 'react'; interface BubbleCursorProps { wrapperElement?: HTMLElement; } class Particle { lifeSpan: number; initialLifeSpan: number; velocity: { x: number; y: number }; position: { x: number; y: number }; baseDimension: number; constructor(x: number, y: number) { this.initialLifeSpan = Math.floor(Math.random() * 60 + 60); this.lifeSpan = this.initialLifeSpan; this.velocity = { x: (Math.random() < 0.5 ? -1 : 1) * (Math.random() / 10), y: -0.4 + Math.random() * -1, }; this.position = { x, y }; this.baseDimension = 4; } update(context: CanvasRenderingContext2D) { this.position.x += this.velocity.x; this.position.y += this.velocity.y; this.velocity.x += ((Math.random() < 0.5 ? -1 : 1) * 2) / 75; this.velocity.y -= Math.random() / 600; this.lifeSpan--; const scale = 0.2 + (this.initialLifeSpan - this.lifeSpan) / this.initialLifeSpan; context.fillStyle = '#e6f1f7'; context.strokeStyle = '#3a92c5'; context.beginPath(); context.arc( this.position.x - (this.baseDimension / 2) * scale, this.position.y - this.baseDimension / 2, this.baseDimension * scale, 0, 2 * Math.PI ); context.stroke(); context.fill(); context.closePath(); } } const BubbleCursor: React.FC = ({ wrapperElement }) => { const canvasRef = useRef(null); const particlesRef = useRef([]); const cursorRef = useRef({ x: 0, y: 0 }); const animationFrameRef = useRef(null); useEffect(() => { const prefersReducedMotion = window.matchMedia( '(prefers-reduced-motion: reduce)' ); let canvas: HTMLCanvasElement | null = null; let context: CanvasRenderingContext2D | null = null; let width = window.innerWidth; let height = window.innerHeight; const init = () => { if (prefersReducedMotion.matches) { console.log( 'This browser has prefers reduced motion turned on, so the cursor did not init' ); return false; } canvas = canvasRef.current; if (!canvas) return; context = canvas.getContext('2d'); if (!context) return; canvas.style.top = '0px'; canvas.style.left = '0px'; canvas.style.pointerEvents = 'none'; if (wrapperElement) { canvas.style.position = 'absolute'; wrapperElement.appendChild(canvas); canvas.width = wrapperElement.clientWidth; canvas.height = wrapperElement.clientHeight; } else { canvas.style.position = 'fixed'; document.body.appendChild(canvas); canvas.width = width; canvas.height = height; } bindEvents(); loop(); }; const bindEvents = () => { const element = wrapperElement || document.body; element.addEventListener('mousemove', onMouseMove); element.addEventListener('touchmove', onTouchMove, { passive: true }); element.addEventListener('touchstart', onTouchMove, { passive: true }); window.addEventListener('resize', onWindowResize); }; const onWindowResize = () => { width = window.innerWidth; height = window.innerHeight; if (!canvasRef.current) return; if (wrapperElement) { canvasRef.current.width = wrapperElement.clientWidth; canvasRef.current.height = wrapperElement.clientHeight; } else { canvasRef.current.width = width; canvasRef.current.height = height; } }; const onTouchMove = (e: TouchEvent) => { if (e.touches.length > 0) { for (let i = 0; i < e.touches.length; i++) { addParticle(e.touches[i].clientX, e.touches[i].clientY); } } }; const onMouseMove = (e: MouseEvent) => { if (wrapperElement) { const boundingRect = wrapperElement.getBoundingClientRect(); cursorRef.current.x = e.clientX - boundingRect.left; cursorRef.current.y = e.clientY - boundingRect.top; } else { cursorRef.current.x = e.clientX; cursorRef.current.y = e.clientY; } addParticle(cursorRef.current.x, cursorRef.current.y); }; const addParticle = (x: number, y: number) => { particlesRef.current.push(new Particle(x, y)); }; const updateParticles = () => { if (!canvas || !context) return; if (particlesRef.current.length === 0) { return; } context.clearRect(0, 0, canvas.width, canvas.height); // Update for (let i = 0; i < particlesRef.current.length; i++) { particlesRef.current[i].update(context); } // Remove dead particles for (let i = particlesRef.current.length - 1; i >= 0; i--) { if (particlesRef.current[i].lifeSpan < 0) { particlesRef.current.splice(i, 1); } } if (particlesRef.current.length === 0) { context.clearRect(0, 0, canvas.width, canvas.height); } }; const loop = () => { updateParticles(); animationFrameRef.current = requestAnimationFrame(loop); }; init(); return () => { if (canvas) { canvas.remove(); } if (animationFrameRef.current) { cancelAnimationFrame(animationFrameRef.current); } const element = wrapperElement || document.body; element.removeEventListener('mousemove', onMouseMove); element.removeEventListener('touchmove', onTouchMove); element.removeEventListener('touchstart', onTouchMove); window.removeEventListener('resize', onWindowResize); }; }, [wrapperElement]); return ; }; export default BubbleCursor;