Skip to content

Enhancement: Rotating Thematic Loading Animations #371

@github-actions

Description

@github-actions

Enhancement: Rotating Thematic Loading Animations

Issue Type: Enhancement
Priority: Low
Status: Future
Component: Frontend UI
Related: Caddy Reload UI Feedback Implementation


📋 Summary

Implement a hybrid approach for loading animations that randomly rotates between multiple thematic variations for both Charon (proxy operations) and Cerberus (security operations) themes. This adds visual variety and reinforces the mythological branding of the application.


🎯 Motivation

Currently, each operation type displays the same loading animation every time. While functional, this creates a repetitive user experience. By rotating between thematically consistent animation variants, we can:

  1. Reduce Visual Fatigue: Users won't see the exact same animation on every operation
  2. Enhance Branding: Multiple mythological references deepen the Charon/Cerberus theme
  3. Maintain Consistency: All variants stay within their respective theme (blue/Charon or red/Cerberus)
  4. Add Delight: Small surprises in UI create more engaging user experience
  5. Educational: Each variant can teach users more about the mythology (e.g., Charon's obol coin)

🎨 Proposed Animation Variants

Charon Theme (Proxy/General Operations)

Color Palette: Blue (#3B82F6, #60A5FA), Slate (#64748B, #475569)

Animation Description Key Message Examples
Boat on Waves (Current) Boat silhouette bobbing on animated waves "Ferrying across the Styx..."
Rowing Oar Animated oar rowing motion in water "Pulling through the mist..." / "The oar dips and rises..."
River Flow Flowing water with current lines "Drifting down the Styx..." / "Waters carry the change..."

Coin Theme (Authentication)

Color Palette: Gold (#F59E0B, #FBBF24), Amber (#D97706, #F59E0B)

Animation Description Key Message Examples
Coin Flip (Current) Spinning obol (ancient Greek coin) on Y-axis "Paying the ferryman..." / "Your obol grants passage"
Coin Drop Coin falling and landing in palm "The coin drops..." / "Payment accepted"
Token Glow Glowing authentication token/key "Token gleams..." / "The key turns..."
Gate Opening Stone gate/door opening animation "Gates part..." / "Passage granted"

Cerberus Theme (Security Operations)

Color Palette: Red (#DC2626, #EF4444), Amber (#F59E0B), Red-900 (#7F1D1D)

Animation Description Key Message Examples
Three Heads Alert (Current) Three heads with glowing eyes and pulsing shield "Guardian stands watch..." / "Three heads turn..."
Shield Pulse Centered shield with pulsing defensive aura "Barriers strengthen..." / "The ward pulses..."
Guardian Stance Simplified Cerberus silhouette in alert pose "Guarding the threshold..." / "Sentinel awakens..."
Chain Links Animated chain links representing binding/security "Chains of protection..." / "Bonds tighten..."

🛠️ Technical Implementation

Architecture

// frontend/src/components/LoadingStates.tsx

type CharonVariant = 'boat' | 'coin' | 'oar' | 'river'
type CerberusVariant = 'heads' | 'shield' | 'stance' | 'chains'

interface LoadingMessages {
  message: string
  submessage: string
}

const CHARON_MESSAGES: Record<CharonVariant, LoadingMessages[]> = {
  boat: [
    { message: "Ferrying across...", submessage: "Charon guides the way" },
    { message: "Crossing the Styx...", submessage: "The journey begins" }
  ],
  coin: [
    { message: "Paying the ferryman...", submessage: "The obol tumbles" },
    { message: "Coin accepted...", submessage: "Passage granted" }
  ],
  oar: [
    { message: "Pulling through the mist...", submessage: "The oar dips and rises" },
    { message: "Rowing steadily...", submessage: "Progress across dark waters" }
  ],
  river: [
    { message: "Drifting down the Styx...", submessage: "Waters carry the change" },
    { message: "Current flows...", submessage: "The river guides all" }
  ]
}

const CERBERUS_MESSAGES: Record<CerberusVariant, LoadingMessages[]> = {
  heads: [
    { message: "Three heads turn...", submessage: "Guardian stands watch" },
    { message: "Cerberus awakens...", submessage: "The gate is guarded" }
  ],
  shield: [
    { message: "Barriers strengthen...", submessage: "The ward pulses" },
    { message: "Defenses activate...", submessage: "Protection grows" }
  ],
  stance: [
    { message: "Guarding the threshold...", submessage: "Sentinel awakens" },
    { message: "Taking position...", submessage: "The guardian stands firm" }
  ],
  chains: [
    { message: "Chains of protection...", submessage: "Bonds tighten" },
    { message: "Links secure...", submessage: "Nothing passes unchecked" }
  ]
}

// Randomly select variant on component mount
export function ConfigReloadOverlay({ type = 'charon', operationType }: Props) {
  const [variant] = useState(() => {
    if (type === 'cerberus') {
      const variants: CerberusVariant[] = ['heads', 'shield', 'stance', 'chains']
      return variants[Math.floor(Math.random() * variants.length)]
    } else {
      const variants: CharonVariant[] = ['boat', 'coin', 'oar', 'river']
      return variants[Math.floor(Math.random() * variants.length)]
    }
  })

  const [messages] = useState(() => {
    const messageSet = type === 'cerberus'
      ? CERBERUS_MESSAGES[variant as CerberusVariant]
      : CHARON_MESSAGES[variant as CharonVariant]
    return messageSet[Math.floor(Math.random() * messageSet.length)]
  })

  // Render appropriate loader component based on variant
  const Loader = getLoaderComponent(type, variant)

  return (
    <div className="fixed inset-0 bg-slate-900/70 backdrop-blur-sm flex items-center justify-center z-50">
      <div className={/* theme styling */}>
        <Loader size="lg" />
        <div className="text-center">
          <p className="text-slate-200 font-medium text-lg">{messages.message}</p>
          <p className="text-slate-400 text-sm mt-2">{messages.submessage}</p>
        </div>
      </div>
    </div>
  )
}

New Loader Components

Each variant needs its own component:

// Charon Variants
export function CharonCoinLoader({ size }: LoaderProps) {
  // Spinning coin with heads/tails alternating
}

export function CharonOarLoader({ size }: LoaderProps) {
  // Rowing oar motion
}

export function CharonRiverLoader({ size }: LoaderProps) {
  // Flowing water lines
}

// Cerberus Variants
export function CerberusShieldLoader({ size }: LoaderProps) {
  // Pulsing shield with defensive aura
}

export function CerberusStanceLoader({ size }: LoaderProps) {
  // Guardian dog in alert pose
}

export function CerberusChainsLoader({ size }: LoaderProps) {
  // Animated chain links
}

📐 Animation Specifications

Charon: Coin Flip

  • Visual: Ancient Greek obol coin spinning on Y-axis
  • Animation: 360° rotation every 2s, slight wobble
  • Colors: Gold (#F59E0B) glint, slate shadow
  • Message Timing: Change text on coin flip (heads vs tails)

Charon: Rowing Oar

  • Visual: Oar blade dipping into water, pulling back
  • Animation: Arc motion, water ripples on dip
  • Colors: Brown (#92400E) oar, blue (#3B82F6) water
  • Timing: 3s cycle (dip 1s, pull 1.5s, lift 0.5s)

Charon: River Flow

  • Visual: Horizontal flowing lines with subtle particle drift
  • Animation: Lines translate-x infinitely, particles bob
  • Colors: Blue gradient (#1E3A8A → #3B82F6)
  • Timing: Continuous flow, particles move slower than lines

Cerberus: Shield Pulse

  • Visual: Shield outline with expanding aura rings
  • Animation: Rings pulse outward and fade (like sonar)
  • Colors: Red (#DC2626) shield, amber (#F59E0B) aura
  • Timing: 2s pulse interval

Cerberus: Guardian Stance

  • Visual: Simplified three-headed dog silhouette, alert posture
  • Animation: Heads swivel slightly, ears perk
  • Colors: Red (#7F1D1D) body, amber (#F59E0B) eyes
  • Timing: 3s head rotation cycle

Cerberus: Chain Links

  • Visual: 4-5 interlocking chain links
  • Animation: Links tighten/loosen (scale transform)
  • Colors: Gray (#475569) chains, red (#DC2626) accents
  • Timing: 2.5s cycle (tighten 1s, loosen 1.5s)

🧪 Testing Strategy

Visual Regression Tests

  • Capture screenshots of each variant at key animation frames
  • Verify animations play smoothly (no janky SVG rendering)
  • Test across browsers (Chrome, Firefox, Safari)

Unit Tests

describe('ConfigReloadOverlay - Variant Selection', () => {
  it('randomly selects Charon variant', () => {
    const variants = new Set()
    for (let i = 0; i < 20; i++) {
      const { container } = render(<ConfigReloadOverlay type="charon" />)
      // Extract which variant was rendered
      variants.add(getRenderedVariant(container))
    }
    expect(variants.size).toBeGreaterThan(1) // Should see variety
  })

  it('randomly selects Cerberus variant', () => {
    const variants = new Set()
    for (let i = 0; i < 20; i++) {
      const { container } = render(<ConfigReloadOverlay type="cerberus" />)
      variants.add(getRenderedVariant(container))
    }
    expect(variants.size).toBeGreaterThan(1)
  })

  it('uses variant-specific messages', () => {
    const { getByText } = render(<ConfigReloadOverlay type="charon" />)
    // Should find ONE of the Charon messages
    const hasCharonMessage =
      getByText(/ferrying/i) ||
      getByText(/coin/i) ||
      getByText(/oar/i) ||
      getByText(/river/i)
    expect(hasCharonMessage).toBeTruthy()
  })
})

Manual Testing

  • Trigger same operation 10 times, verify different animations appear
  • Verify messages match animation theme (e.g., "Coin" messages with coin animation)
  • Check performance (should be smooth at 60fps)
  • Verify accessibility (screen readers announce state)

📦 Implementation Phases

Phase 1: Core Infrastructure (2-3 hours)

  • Create variant selection logic
  • Create message mapping system
  • Update ConfigReloadOverlay to accept variant prop
  • Write unit tests for variant selection

Phase 2: Charon Variants (3-4 hours)

  • Implement CharonOarLoader component
  • Implement CharonRiverLoader component
  • Create messages for each variant
  • Add Tailwind animations

Phase 3: Coin Variants (3-4 hours)

  • Implement CoinDropLoader component
  • Implement TokenGlowLoader component
  • Implement GateOpeningLoader component
  • Create messages for each variant
  • Add Tailwind animations

Phase 4: Cerberus Variants (4-5 hours)

  • Implement CerberusShieldLoader component
  • Implement CerberusStanceLoader component
  • Implement CerberusChainsLoader component
  • Create messages for each variant
  • Add Tailwind animations

Phase 5: Integration & Polish (2-3 hours)

  • Update all usage sites (ProxyHosts, WafConfig, etc.)
  • Visual regression tests
  • Performance profiling
  • Documentation updates

Total Estimated Time: 15-19 hours


🎯 Success Metrics

  • Users see at least 3 different animations within 10 operations
  • Animation performance: 60fps on mid-range devices
  • Zero accessibility regressions (WCAG 2.1 AA)
  • Positive user feedback on visual variety
  • Code coverage: >90% for variant selection logic

🚫 Out of Scope

  • User preference for specific variant (always random)
  • Custom animation timing controls
  • Additional themes beyond Charon/Cerberus
  • Sound effects or haptic feedback
  • Animation of background overlay entrance/exit

📚 Research References


🔗 See Also

  • Main Implementation: docs/plans/current_spec.md
  • Charon Documentation: docs/features.md
  • Cerberus Documentation: docs/cerberus.md

Auto-created from rotating-loading-animations.md

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    Status

    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions