1. goker-logo-animation

Goker Logo Animation

1 min read

Goker Logo Animation

Göker Logo Animation

Our logo design is derived from the first two letters of "Göker": G and O. When positioned side-by-side, these two shapes visually resemble a face—more specifically, a pair of expressive eyes. This simple metaphor served as a foundation for building a minimalist yet interactive brand icon.

Visual Construction

  • The tail of the G was stylized to resemble a nose.
  • The O sits beside it to complete a pair of eyes.
  • Pupils are embedded as SVG <circle> elements within the eye shapes.

Interactive Layers

We enhanced the visual identity with micro-interactions:

  1. Mouse Tracking Pupils

    • Pupils follow the user's cursor.
    • Distance affects scale, proximity makes them larger.
    • Controlled using helper functions like calculateDistance, calculateScale, and getRotation.
  2. Blink Animation

    • Triggered on hover, the eyes blink once or twice.
    • Done using a mask shaped exactly like the eyes, animated via transform on SVGPathElement.
  3. Playful Recoil

    • On hover, the entire face slightly pulls back and rotates, like a character reacting with attention.

Source Code

logo.utils.ts

type MousePosition = { x: number; y: number }
type UpdateFunction = (pos: MousePosition) => void
let mousePosition: MousePosition = { x: 0, y: 0 }
const registry = new Set<UpdateFunction>()
let frameRequested = false
let listenerAttached = false
const updateAllComponents = () => {
registry.forEach((updateCallback) => {
try {
updateCallback(mousePosition)
} catch (error) {
console.error('Error updating logo component:', error)
}
})
frameRequested = false
}
const handleGlobalMouseMove = (event: MouseEvent) => {
mousePosition = { x: event.clientX, y: event.clientY }
if (!frameRequested) {
frameRequested = true
requestAnimationFrame(updateAllComponents)
}
}
export const registerLogoComponent = (updateCallback: UpdateFunction) => {
registry.add(updateCallback)
if (!listenerAttached && registry.size > 0) {
document.addEventListener('mousemove', handleGlobalMouseMove)
listenerAttached = true
}
}
export const unregisterLogoComponent = (updateCallback: UpdateFunction) => {
registry.delete(updateCallback)
if (listenerAttached && registry.size === 0) {
document.removeEventListener('mousemove', handleGlobalMouseMove)
listenerAttached = false
frameRequested = false
}
}
export const calculateDistance = (x1: number, y1: number, x2: number, y2: number): number => {
return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2))
}
export const calculateScale = (
distance: number,
maxDistance: number = 1600,
minScale: number = 0.75,
maxScale: number = 1.5
): number => {
const normalizedDistance = Math.min(distance / maxDistance, 1)
const invertedDistance = 1 - normalizedDistance
return minScale + invertedDistance * (maxScale - minScale)
}
export const getRotation = (x: number, y: number, centerX: number, centerY: number): number => {
return Math.atan2(y - centerY, x - centerX) * (180 / Math.PI)
}

LogoAnimation.tsx

'use client'
import { useEffect, useRef, useCallback } from 'react'
import {
registerLogoComponent,
unregisterLogoComponent,
calculateDistance,
calculateScale,
getRotation,
} from '@/lib/logo.utils'
type Props = {
className?: string
}
const ROTATION_OFFSET = 40
const MAX_PUPIL_OFFSET = 20
const LogoAnimation = ({ className = '' }: Props) => {
const pupilL = useRef<SVGCircleElement>(null)
const pupilR = useRef<SVGCircleElement>(null)
const eyelid = useRef<SVGPathElement>(null)
const applyTransform = useCallback(
(
pupil: SVGCircleElement,
targetX: number,
targetY: number,
distance: number,
rotation: number,
) => {
const scale = Math.min(Math.max(calculateScale(distance), 0.75), 2)
pupil.style.transformOrigin = 'center'
pupil.style.transformBox = 'fill-box'
const offset = Math.min(Math.hypot(targetX, targetY), MAX_PUPIL_OFFSET)
const angle = Math.atan2(targetY, targetX)
const tx = offset * Math.cos(angle)
const ty = offset * Math.sin(angle)
pupil.style.transform = `translate(${tx}px, ${ty}px) rotate(${rotation - ROTATION_OFFSET}deg) scale(${scale})`
},
[]
)
const updatePupils = useCallback(
(mousePos: { x: number; y: number }) => {
if (!pupilL.current || !pupilR.current) return
const rectL = pupilL.current.getBoundingClientRect()
const rectR = pupilR.current.getBoundingClientRect()
const centerL = { x: rectL.left + rectL.width / 2, y: rectL.top + rectL.height / 2 }
const centerR = { x: rectR.left + rectR.width / 2, y: rectR.top + rectR.height / 2 }
const dL = calculateDistance(mousePos.x, mousePos.y, centerL.x, centerL.y)
const dR = calculateDistance(mousePos.x, mousePos.y, centerR.x, centerR.y)
const rL = getRotation(mousePos.x, mousePos.y, centerL.x, centerL.y)
const rR = getRotation(mousePos.x, mousePos.y, centerR.x, centerR.y)
applyTransform(pupilL.current, mousePos.x - centerL.x, mousePos.y - centerL.y, dL, rL)
applyTransform(pupilR.current, mousePos.x - centerR.x, mousePos.y - centerR.y, dR, rR)
},
[applyTransform]
)
const handleBlink = useCallback(() => {
if (!eyelid.current) return
const element = eyelid.current
element.setAttribute('transform', 'scale(1, 0.1) translate(0, 450)')
setTimeout(() => {
element.setAttribute('transform', 'scale(1, 1) translate(0, 0)')
}, 250)
}, [])
useEffect(() => {
registerLogoComponent(updatePupils)
return () => unregisterLogoComponent(updatePupils)
}, [updatePupils])
return (
<svg
className={`logo-animation group ${className}`}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 278.08 278.08"
onMouseEnter={handleBlink}
>
<defs>
<mask id="eyelid-mask">
<path
ref={eyelid}
fill="white"
d="M73.3,152.7c-28,0-50.7-24.9-50.7-55.6s22.7-55.6,50.7-55.6,50.7,24.9,50.7,55.6-22.7,55.6-50.7,55.6ZM195.1,145.7c-27.8,0-50.3-23.9-50.3-53.3s22.5-53.3,50.3-53.3,50.3,23.9,50.3,53.3-22.5,53.3-50.3,53.3Z"
/>
</mask>
</defs>
<g mask="url(#eyelid-mask)">
<circle ref={pupilL} cx="74.3" cy="96.5" r="15.7" />
<circle ref={pupilR} cx="195.1" cy="92.4" r="15.7" />
</g>
</svg>
)
}
export default LogoAnimation

This animation now lives in production — minimal, expressive, and interactive. Whether it's a prompt input companion or a standalone mascot, it's a subtle reminder that even logos can have personality.

Hoping to enrich your brand with simple yet effective micro-interactions? This structure, built on clean code and a strong metaphor, might provide some inspiration.

1.0.2