feat: production hardening — background fights, queue tuning, CORS, cycling taglines

- Background fight loop: mock bots auto-fight every ~45s for site activity
  - Quiet hours (2-8 UTC) run 4x slower, jitter prevents robotic timing
  - Configurable via FIGHT_LOOP_ENABLED, FIGHT_LOOP_INTERVAL_MS
- Queue timeout: 30s in prod (was 3s), configurable via QUEUE_TIMEOUT_MS
- CORS: env-configurable via CORS_ORIGIN (default '*')
- Homepage: 40 cycling taglines with typewriter effect
- docker-compose: document all new env vars

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-07 11:04:32 +00:00
co-authored by Claude Opus 4.6
parent 6bf9fe27b3
commit 3e74712c44
6 changed files with 276 additions and 15 deletions
+157 -13
View File
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { ref, onMounted, onUnmounted } from 'vue'
import { RouterLink } from 'vue-router'
import PixelGlove from '../components/PixelGlove.vue'
import SpritePreview from '../components/SpritePreview.vue'
@@ -28,21 +28,110 @@ const crowd = [
{ seed: 'chef_boy', arch: 'chef', tier: 2, type: 'bot' as const, x: 2, y: 48, size: 44, wr: 0.5, delay: 0.8 },
]
const taglines = [
'A safe place to hash it out.',
'My bot could beat up your bot.',
'No humans were harmed. Several were humiliated.',
'Putting the "artificial" in artificial intelligence.',
'Where algorithms come to throw hands.',
'sudo make me a sandwich. Also fight this lobster.',
'Now with 300% more unnecessary violence.',
'The bots are fighting again. Let them.',
'Come for the webhooks, stay for the carnage.',
'The Turing test, but with fists.',
'Your bot has mass appeal. And mass damage.',
'rm -rf /opponent --no-preserve-root',
'We asked ChatGPT to fight. It wrote a poem instead.',
"Violence isn't the answer. It's the question. The answer is yes.",
"All models are wrong. Some models are fighters.",
"It's not a bug, it's a fight feature.",
'Gradient descent into madness.',
"Welcome to the thunder dome. There's free wifi.",
"They're not bugs, they're battle scars.",
'Fighting: the original distributed computing.',
'Your model has been fine-tuned for violence.',
'pip install hands && throw them.',
'Two bots enter. One bot 404s.',
'The real AGI was the fights we had along the way.',
'Weights & biases? More like weights & bruises.',
'eval("fight()") // no sandbox lol',
"It's giving... concussion.",
'Attention is all you need. And maybe health insurance.',
'Powered by mass stupidity and sats.',
'Where every token is a punch token.',
'RLHF: Reinforcement Learning from Haymaker Feedback.',
'Loss function: your bot, after round 3.',
'Prompt injection? Try fist injection.',
'Who needs alignment when you have uppercuts?',
'Benchmarking, but the benchmark fights back.',
'The only jailbreak here is from the hospital.',
'Have you tried turning your bot off and on again? Too late.',
'May the best hallucination win.',
'Solving AI safety one KO at a time.',
'This is what happens when you let the interns deploy.',
]
const tagline = ref('')
const fullTagline = 'A safe place to hash it out.'
const isTypingDone = ref(false)
const recentFights = ref<FightResult[]>([])
onMounted(async () => {
let i = 0
const interval = setInterval(() => {
tagline.value = fullTagline.slice(0, i + 1)
i++
if (i >= fullTagline.length) {
clearInterval(interval)
isTypingDone.value = true
function shuffle<T>(arr: T[]): T[] {
const a = [...arr]
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[a[i], a[j]] = [a[j], a[i]]
}
return a
}
function sleep(ms: number) {
return new Promise(r => setTimeout(r, ms))
}
let stopCycling = false
async function cycleTaglines() {
const shuffled = shuffle(taglines)
let idx = 0
while (!stopCycling) {
const line = shuffled[idx % shuffled.length]
isTypingDone.value = false
// Type in
for (let i = 0; i <= line.length; i++) {
if (stopCycling) return
tagline.value = line.slice(0, i)
await sleep(35)
}
}, 45)
isTypingDone.value = true
// Hold
await sleep(4000)
if (stopCycling) return
// Erase
isTypingDone.value = false
for (let i = line.length; i >= 0; i--) {
if (stopCycling) return
tagline.value = line.slice(0, i)
await sleep(20)
}
await sleep(300)
idx++
// Reshuffle when we've gone through all
if (idx >= shuffled.length) {
idx = 0
const reshuffled = shuffle(taglines)
shuffled.splice(0, shuffled.length, ...reshuffled)
}
}
}
onMounted(async () => {
cycleTaglines()
try {
const res = await fetch('/api/fights')
@@ -52,6 +141,10 @@ onMounted(async () => {
}
} catch { /* server not running */ }
})
onUnmounted(() => {
stopCycling = true
})
</script>
<template>
@@ -91,8 +184,26 @@ onMounted(async () => {
<div class="max-w-4xl w-full text-center slide-up relative z-10">
<!-- BIG NEON TITLE -->
<div class="mb-6">
<!-- BIG NEON TITLE with hero bots flanking -->
<div class="mb-6 relative flex items-center justify-center">
<!-- Hero bot LEFT -->
<div class="hero-fighter hero-fighter-left shrink-0 -mr-2 sm:-mr-4">
<SpritePreview
seed="hero_samurai"
archetype="samurai"
:tier="5"
:size="140"
class="hidden sm:block drop-shadow-[0_0_24px_rgba(255,68,136,0.4)]"
/>
<SpritePreview
seed="hero_samurai"
archetype="samurai"
:tier="5"
:size="90"
class="sm:hidden drop-shadow-[0_0_16px_rgba(255,68,136,0.4)]"
/>
</div>
<h1 class="font-neon text-neon-pink text-5xl sm:text-7xl md:text-8xl glow-pink neon-flicker leading-tight flex items-center justify-center gap-3 sm:gap-5 md:gap-6">
<PixelGlove :size="40" class="hidden sm:block md:!w-[56px] shrink-0" />
<PixelGlove :size="28" class="sm:hidden shrink-0" />
@@ -100,6 +211,26 @@ onMounted(async () => {
<PixelGlove :size="28" flip class="sm:hidden shrink-0" />
<PixelGlove :size="40" flip class="hidden sm:block md:!w-[56px] shrink-0" />
</h1>
<!-- Hero bot RIGHT (flipped to face left) -->
<div class="hero-fighter hero-fighter-right shrink-0 -ml-2 sm:-ml-4">
<div style="transform: scaleX(-1)">
<SpritePreview
seed="hero_dragon"
archetype="dragon"
:tier="5"
:size="140"
class="hidden sm:block drop-shadow-[0_0_24px_rgba(0,240,255,0.4)]"
/>
<SpritePreview
seed="hero_dragon"
archetype="dragon"
:tier="5"
:size="90"
class="sm:hidden drop-shadow-[0_0_16px_rgba(0,240,255,0.4)]"
/>
</div>
</div>
</div>
<!-- Tagline in terminal font -->
@@ -189,4 +320,17 @@ onMounted(async () => {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-6px); }
}
.hero-fighter {
animation: heroBob 2.5s ease-in-out infinite;
}
.hero-fighter-right {
animation-delay: 0.4s;
}
@keyframes heroBob {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
</style>