Added About Page

This commit is contained in:
2025-09-17 23:34:37 +10:00
parent 2f6006626d
commit b9e1f6827a
30 changed files with 1270 additions and 529 deletions

View File

@@ -0,0 +1,242 @@
// directives/hoverEffects.ts
import { Directive } from 'vue'
export interface HoverOptions {
type?: 'lift' | 'scale' | 'tilt' | 'glow' | 'rotate' | 'slide-up' | 'slide-down' | 'bounce' | 'shake'
duration?: number | string
easing?: string
intensity?: number // Scale factor for the effect (0.1 = subtle, 1.0 = normal, 2.0 = dramatic)
transform?: string // Custom transform for hover state
boxShadow?: string // Custom box shadow for hover state
backgroundColor?: string // Custom background color for hover state
borderColor?: string // Custom border color for hover state
color?: string // Custom text color for hover state
scale?: number // Custom scale value
translateY?: number // Custom Y translation in pixels
translateX?: number // Custom X translation in pixels
rotateZ?: number // Custom rotation in degrees
disabled?: boolean // Disable hover effects
}
export type HoverValue = string | HoverOptions
interface HoverElement extends HTMLElement {
_hoverOptions?: HoverOptions
_originalStyles?: {
transition?: string
transform?: string
boxShadow?: string
backgroundColor?: string
borderColor?: string
color?: string
}
_mouseEnterHandler?: (e: MouseEvent) => void
_mouseLeaveHandler?: (e: MouseEvent) => void
}
function setupHoverEffects(el: HoverElement, binding: any) {
// Clean up existing handlers
if (el._mouseEnterHandler) {
el.removeEventListener('mouseenter', el._mouseEnterHandler)
el.removeEventListener('mouseleave', el._mouseLeaveHandler!)
}
let options: HoverOptions = {}
if (typeof binding.value === 'string') {
options = { type: binding.value }
} else if (typeof binding.value === 'object') {
options = { ...binding.value }
}
const {
type = 'lift',
duration = '0.3s',
easing = 'ease',
intensity = 1.0,
transform,
boxShadow,
backgroundColor,
borderColor,
color,
scale,
translateY,
translateX,
rotateZ,
disabled = false
} = options
el._hoverOptions = options
if (disabled) return
// Store original styles
const computedStyle = window.getComputedStyle(el)
el._originalStyles = {
transition: computedStyle.transition,
transform: computedStyle.transform,
boxShadow: computedStyle.boxShadow,
backgroundColor: computedStyle.backgroundColor,
borderColor: computedStyle.borderColor,
color: computedStyle.color
}
// Set base transition
el.style.transition = `all ${duration} ${easing}`
// Get hover effects based on type
const hoverEffects = getHoverEffects(type, intensity, {
transform,
boxShadow,
backgroundColor,
borderColor,
color,
scale,
translateY,
translateX,
rotateZ
})
// Mouse enter handler
el._mouseEnterHandler = (e: MouseEvent) => {
Object.assign(el.style, hoverEffects.enter)
}
// Mouse leave handler
el._mouseLeaveHandler = (e: MouseEvent) => {
Object.assign(el.style, hoverEffects.leave)
}
el.addEventListener('mouseenter', el._mouseEnterHandler)
el.addEventListener('mouseleave', el._mouseLeaveHandler)
}
function getHoverEffects(
type: string,
intensity: number,
customValues: Partial<HoverOptions>
) {
const effects = {
enter: {} as any,
leave: {} as any
}
// Custom values take precedence
if (customValues.transform) {
effects.enter.transform = customValues.transform
effects.leave.transform = 'none'
return effects
}
// Preset effects
switch (type) {
case 'lift':
effects.enter.transform = `translateY(${customValues.translateY || -8 * intensity}px) scale(${customValues.scale || 1 + 0.02 * intensity})`
effects.enter.boxShadow = customValues.boxShadow || `0 ${10 * intensity}px ${25 * intensity}px rgba(0,0,0,0.15)`
break
case 'scale':
effects.enter.transform = `scale(${customValues.scale || 1 + 0.05 * intensity})`
break
case 'tilt':
effects.enter.transform = `perspective(1000px) rotateY(${customValues.rotateZ || 5 * intensity}deg) scale(${1 + 0.02 * intensity})`
break
case 'glow':
effects.enter.boxShadow = customValues.boxShadow || `0 0 ${20 * intensity}px rgba(59, 130, 246, 0.5)`
effects.enter.transform = `scale(${1 + 0.02 * intensity})`
break
case 'rotate':
effects.enter.transform = `rotate(${customValues.rotateZ || 5 * intensity}deg) scale(${1 + 0.02 * intensity})`
break
case 'slide-up':
effects.enter.transform = `translateY(${customValues.translateY || -5 * intensity}px)`
break
case 'slide-down':
effects.enter.transform = `translateY(${customValues.translateY || 5 * intensity}px)`
break
case 'bounce':
effects.enter.transform = `translateY(${-3 * intensity}px) scale(${1 + 0.05 * intensity})`
effects.enter.transition = `all 0.2s cubic-bezier(0.68, -0.55, 0.265, 1.55)`
break
case 'shake':
effects.enter.animation = `shake-${intensity} 0.5s ease-in-out`
break
}
// Add custom color effects
if (customValues.backgroundColor) {
effects.enter.backgroundColor = customValues.backgroundColor
}
if (customValues.borderColor) {
effects.enter.borderColor = customValues.borderColor
}
if (customValues.color) {
effects.enter.color = customValues.color
}
// Leave effects (return to original)
effects.leave.transform = 'none'
effects.leave.boxShadow = ''
effects.leave.backgroundColor = ''
effects.leave.borderColor = ''
effects.leave.color = ''
effects.leave.animation = ''
return effects
}
export const hoverEffects: Directive = {
mounted(el: HoverElement, binding) {
setupHoverEffects(el, binding)
},
updated(el: HoverElement, binding) {
if (JSON.stringify(binding.value) !== JSON.stringify(binding.oldValue)) {
setupHoverEffects(el, binding)
}
},
unmounted(el: HoverElement) {
if (el._mouseEnterHandler) {
el.removeEventListener('mouseenter', el._mouseEnterHandler)
el.removeEventListener('mouseleave', el._mouseLeaveHandler!)
}
}
}
// Add shake keyframes to document if not already added
if (typeof window !== 'undefined') {
const addShakeKeyframes = () => {
if (document.querySelector('#hover-directive-styles')) return
const style = document.createElement('style')
style.id = 'hover-directive-styles'
style.innerHTML = `
@keyframes shake-1 {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-2px); }
75% { transform: translateX(2px); }
}
@keyframes shake-2 {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-5px); }
75% { transform: translateX(5px); }
}
`
document.head.appendChild(style)
}
// Add styles when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', addShakeKeyframes)
} else {
addShakeKeyframes()
}
}

View File

@@ -0,0 +1,122 @@
// directives/intersectionTransition.ts
import { Directive } from 'vue'
interface TransitionOptions {
type?: string
duration?: number | string
easing?: string
delay?: number | string
opacity?: number | string
transform?: string
threshold?: number
}
interface IntersectionElement extends HTMLElement {
_observer?: IntersectionObserver
_options?: TransitionOptions
}
function setupTransition(el: IntersectionElement, binding: any) {
// Handle different binding value types
let options: TransitionOptions = {}
if (typeof binding.value === 'string') {
// Simple string usage: v-show-on-intersect="'fade-up'"
options = { type: binding.value }
} else if (typeof binding.value === 'object') {
// Object usage: v-show-on-intersect="{ type: 'fade-up', duration: '2s', transform: 'translateX(-100px)' }"
options = { ...binding.value }
}
// Set defaults
const {
type = 'fade-up',
duration = '1s',
easing = 'cubic-bezier(0.4, 0, 0.2, 1)',
delay = '0s',
opacity = '0',
transform,
threshold = 0.1
} = options
el._options = options
// Get initial transform (custom or preset)
const initialTransform = transform || getInitialTransform(type)
// Initially hide the element
el.style.opacity = String(opacity)
el.style.transform = initialTransform
el.style.transition = `all ${duration} ${easing} ${delay}`
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
// Show element
el.style.opacity = '1'
el.style.transform = 'none'
} else {
// Hide element
el.style.opacity = String(opacity)
el.style.transform = initialTransform
}
})
}, {
threshold: threshold as number
})
el._observer = observer
observer.observe(el)
}
export const intersectionTransition: Directive = {
mounted(el: IntersectionElement, binding) {
setupTransition(el, binding)
},
updated(el: IntersectionElement, binding) {
// Handle dynamic updates to the directive value
if (JSON.stringify(binding.value) !== JSON.stringify(binding.oldValue)) {
// Disconnect old observer and setup with new options
if (el._observer) {
el._observer.disconnect()
}
setupTransition(el, binding)
}
},
unmounted(el: IntersectionElement) {
if (el._observer) {
el._observer.disconnect()
}
}
}
function getInitialTransform(transitionType: string): string {
switch (transitionType) {
case 'fade-up':
return 'translateY(5em)'
case 'fade-down':
return 'translateY(-5em)'
case 'slide-left':
return 'translateX(5em)'
case 'slide-right':
return 'translateX(-5em)'
case 'scale':
return 'scale(0.8)'
case 'scale-up':
return 'scale(1.2)'
case 'rotate':
return 'rotate(180deg)'
case 'rotate-left':
return 'translateY(100px) rotate(12deg) scale(0.9)'
case 'rotate-right':
return 'translateY(100px) rotate(-12deg) scale(0.9)'
case 'flip-x':
return 'rotateX(90deg)'
case 'flip-y':
return 'rotateY(90deg)'
default:
return ''
}
}