kiwi/frontend/src/components/HallOfChaosView.vue
pyr0ball 9246935fd7 feat: Hall of Chaos easter egg -- HallOfChaosView + long-press trigger
Adds the Hall of Chaos overlay component (recipe blooper gallery with
static CSS tilts, chaos level counter, panel-local overlay) and wires
the 800ms long-press trigger on the Bloopers filter tab in
CommunityFeedPanel. Pairs with the backend /community/hall-of-chaos
endpoint and test added in Task 10.
2026-04-13 12:30:48 -07:00

182 lines
4.4 KiB
Vue

<template>
<div class="hall-of-chaos-overlay" role="dialog" aria-modal="true" aria-label="Hall of Chaos">
<!-- Header -->
<div class="chaos-header">
<h2 class="chaos-title">HALL OF CHAOS</h2>
<p class="chaos-subtitle text-sm">
Chaos Level: <span class="chaos-level">{{ chaosLevel }}</span>
</p>
<button
class="btn btn-secondary chaos-exit-btn"
aria-label="Exit Hall of Chaos"
@click="$emit('close')"
>
Escape the chaos
</button>
</div>
<!-- Loading -->
<div v-if="loading" class="chaos-loading text-center text-secondary" aria-busy="true">
Assembling the chaos...
</div>
<!-- Error -->
<div v-else-if="error" class="chaos-empty text-center text-secondary" role="alert">
The chaos is temporarily indisposed.
</div>
<!-- Empty -->
<div v-else-if="posts.length === 0" class="chaos-empty text-center text-secondary">
<p>No bloopers yet. Be the first to make a glorious mistake.</p>
</div>
<!-- Blooper cards -->
<div v-else class="chaos-grid" aria-label="Blooper posts">
<article
v-for="(post, index) in posts"
:key="post.slug"
class="chaos-card"
:class="`chaos-card--tilt-${(index % 5) + 1}`"
:style="{ '--chaos-border-color': borderColors[index % borderColors.length] }"
>
<p class="chaos-card-author text-xs text-muted">{{ post.pseudonym }}</p>
<h3 class="chaos-card-title text-base font-semibold">{{ post.title }}</h3>
<p v-if="post.outcome_notes" class="chaos-card-notes text-sm text-secondary">
{{ post.outcome_notes }}
</p>
<p v-if="post.recipe_name" class="chaos-card-recipe text-xs text-muted mt-xs">
Recipe: {{ post.recipe_name }}
</p>
</article>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import api from '../services/api'
import type { CommunityPost } from '../stores/community'
defineEmits<{ close: [] }>()
const posts = ref<CommunityPost[]>([])
const chaosLevel = ref(0)
const loading = ref(true)
const error = ref(false)
// CSS custom property strings -- no hardcoded hex
const borderColors = [
'var(--color-warning)',
'var(--color-info)',
'var(--color-success)',
'var(--color-error)',
'var(--color-warning)',
]
onMounted(async () => {
try {
const response = await api.get<{ posts: CommunityPost[]; chaos_level: number }>(
'/community/hall-of-chaos'
)
posts.value = response.data.posts
chaosLevel.value = response.data.chaos_level
} catch {
error.value = true
} finally {
loading.value = false
}
})
</script>
<style scoped>
.hall-of-chaos-overlay {
position: absolute;
inset: 0;
z-index: 200;
background: var(--color-bg-primary);
overflow-y: auto;
padding: var(--spacing-md);
border-radius: var(--radius-lg);
}
.chaos-header {
text-align: center;
margin-bottom: var(--spacing-lg);
}
.chaos-title {
font-size: 2rem;
font-weight: 900;
letter-spacing: 0.12em;
color: var(--color-warning);
margin: 0 0 var(--spacing-xs);
text-transform: uppercase;
}
.chaos-subtitle {
color: var(--color-text-secondary);
margin: 0 0 var(--spacing-sm);
}
.chaos-level {
font-weight: 700;
color: var(--color-warning);
}
.chaos-exit-btn {
font-size: var(--font-size-xs);
}
.chaos-loading,
.chaos-empty {
padding: var(--spacing-xl);
}
.chaos-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: var(--spacing-md);
padding-bottom: var(--spacing-lg);
}
/* Static tilts applied once at render -- not animations, no reduced-motion concern */
.chaos-card {
background: var(--color-bg-card);
border: 2px solid var(--chaos-border-color, var(--color-border));
border-radius: var(--radius-lg);
padding: var(--spacing-md);
}
.chaos-card--tilt-1 { transform: rotate(-3deg); }
.chaos-card--tilt-2 { transform: rotate(2deg); }
.chaos-card--tilt-3 { transform: rotate(-1.5deg); }
.chaos-card--tilt-4 { transform: rotate(4deg); }
.chaos-card--tilt-5 { transform: rotate(-4.5deg); }
.chaos-card-title {
margin: var(--spacing-xs) 0;
color: var(--color-text-primary);
}
.chaos-card-author,
.chaos-card-notes,
.chaos-card-recipe {
margin: 0;
}
@media (max-width: 480px) {
.chaos-grid {
grid-template-columns: 1fr;
}
.chaos-card--tilt-1,
.chaos-card--tilt-2,
.chaos-card--tilt-3,
.chaos-card--tilt-4,
.chaos-card--tilt-5 {
transform: none;
}
}
</style>