f8b918b9cd
Replace the previous split-screen with a brand panel that hosts an animated mascot (eyeballs whose pupils track the focused form field and hide while the password is typed) and decorative grid/circles, plus a mobile brand mark and footer links. Swap Ant Design form controls for native inputs styled to match. The mascot is composed from new AnimatedCharacters / EyeBall / Pupil components. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
39 lines
1.3 KiB
Vue
39 lines
1.3 KiB
Vue
<template>
|
|
<div ref="pupilRef" class="pupil" :style="{
|
|
width: size + 'px', height: size + 'px',
|
|
backgroundColor: pupilColor,
|
|
transform: `translate(${pos.x}px, ${pos.y}px)`,
|
|
}" />
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
|
|
|
const props = withDefaults(defineProps<{
|
|
size?: number; maxDistance?: number; pupilColor?: string
|
|
forceLookX?: number; forceLookY?: number
|
|
}>(), { size: 12, maxDistance: 5, pupilColor: 'black' })
|
|
|
|
const mx = ref(0), my = ref(0)
|
|
const pupilRef = ref<HTMLDivElement>()
|
|
const onMove = (e: MouseEvent) => { mx.value = e.clientX; my.value = e.clientY }
|
|
onMounted(() => window.addEventListener('mousemove', onMove))
|
|
onUnmounted(() => window.removeEventListener('mousemove', onMove))
|
|
|
|
const pos = computed(() => {
|
|
if (!pupilRef.value) return { x: 0, y: 0 }
|
|
if (props.forceLookX !== undefined && props.forceLookY !== undefined)
|
|
return { x: props.forceLookX, y: props.forceLookY }
|
|
const r = pupilRef.value.getBoundingClientRect()
|
|
const dx = mx.value - (r.left + r.width / 2)
|
|
const dy = my.value - (r.top + r.height / 2)
|
|
const d = Math.min(Math.sqrt(dx ** 2 + dy ** 2), props.maxDistance)
|
|
const a = Math.atan2(dy, dx)
|
|
return { x: Math.cos(a) * d, y: Math.sin(a) * d }
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
.pupil { border-radius: 50%; transition: transform 0.1s ease-out; }
|
|
</style>
|