feat: voice mic, pixel life layer, enhanced transcript-debug UX
VoiceMicButton component, PixelLife aquatic layer, improved UserMessageBubble with voice display, AgentBadge terminal switcher, ChatContainer voice integration, FloatingTranscriptDebug ocean life enhancements, and terminal registry support. Remove traefik config.
This commit is contained in:
@@ -0,0 +1,356 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { useAquaticState } from '../useAquaticState'
|
||||
|
||||
const { activeEventModifiers } = useAquaticState()
|
||||
|
||||
const pixelSeeds = Array.from({ length: 15 }, () => Math.random())
|
||||
|
||||
// All swimming creatures are rare — only stationary ones always visible
|
||||
const showSeahorse = ref(false)
|
||||
const showOctopus = ref(false)
|
||||
const showCrab = ref(false)
|
||||
const showEel = ref(false)
|
||||
const showPufferfish = ref(false)
|
||||
const showShrimp = ref(false)
|
||||
const showNautilus = ref(false)
|
||||
const showSwordfish = ref(false)
|
||||
const showStarfish = ref(false)
|
||||
const showAnchor = ref(false)
|
||||
const showSubmarine = ref(false)
|
||||
const showDiver = ref(false)
|
||||
|
||||
let tickTimer: ReturnType<typeof setInterval> | null = null
|
||||
|
||||
function tickPixelLife() {
|
||||
// Common creatures — low chance each tick
|
||||
showSeahorse.value = Math.random() < 0.06
|
||||
showOctopus.value = Math.random() < 0.05
|
||||
showCrab.value = Math.random() < 0.06
|
||||
showEel.value = Math.random() < 0.04
|
||||
showPufferfish.value = Math.random() < 0.05
|
||||
showShrimp.value = Math.random() < 0.06
|
||||
showNautilus.value = Math.random() < 0.04
|
||||
showSwordfish.value = Math.random() < 0.03
|
||||
showStarfish.value = Math.random() < 0.05
|
||||
|
||||
// Very rare creatures
|
||||
showAnchor.value = Math.random() < 0.01
|
||||
showSubmarine.value = Math.random() < 0.008
|
||||
showDiver.value = Math.random() < 0.015
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
tickPixelLife()
|
||||
tickTimer = setInterval(tickPixelLife, 60000)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (tickTimer) clearInterval(tickTimer)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="pixel-life">
|
||||
<!-- Swimming creatures (all rare) -->
|
||||
<i v-if="showSeahorse" class="px seahorse" :style="{ animationDelay: `-${pixelSeeds[0] * 160}s` }"></i>
|
||||
<i v-if="showOctopus" class="px octopus" :style="{ animationDelay: `-${pixelSeeds[1] * 200}s` }"></i>
|
||||
<i v-if="showCrab" class="px crab" :style="{ animationDelay: `-${pixelSeeds[2] * 120}s` }"></i>
|
||||
<i v-if="showEel" class="px eel" :style="{ animationDelay: `-${pixelSeeds[3] * 70}s` }"></i>
|
||||
<i v-if="showPufferfish" class="px pufferfish" :style="{ animationDelay: `-${pixelSeeds[4] * 140}s` }"></i>
|
||||
<i v-if="showShrimp" class="px shrimp" :style="{ animationDelay: `-${pixelSeeds[5] * 50}s` }"></i>
|
||||
<i v-if="showNautilus" class="px nautilus" :style="{ animationDelay: `-${pixelSeeds[6] * 240}s` }"></i>
|
||||
<i v-if="showSwordfish" class="px swordfish" :style="{ animationDelay: `-${pixelSeeds[7] * 25}s` }"></i>
|
||||
<i v-if="showStarfish" class="px starfish-walk" :style="{ animationDelay: `-${pixelSeeds[8] * 360}s` }"></i>
|
||||
|
||||
<!-- Stationary creatures (always visible) -->
|
||||
<i class="px treasure"></i>
|
||||
<i class="px sea-anemone"></i>
|
||||
<i class="px clam"></i>
|
||||
|
||||
<!-- Very rare creatures -->
|
||||
<i v-if="showAnchor" class="px anchor"></i>
|
||||
<i v-if="showSubmarine" class="px submarine" :style="{ animationDelay: `-${pixelSeeds[11] * 320}s` }"></i>
|
||||
<i v-if="showDiver" class="px diver" :style="{ animationDelay: `-${pixelSeeds[12] * 180}s` }"></i>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* ══════════════════════════════════════════════════════════════════════════
|
||||
Pixel Life — box-shadow pixel art creatures for the main background
|
||||
══════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
.pixel-life {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
z-index: 8;
|
||||
}
|
||||
|
||||
.px {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
background: transparent;
|
||||
font-style: normal;
|
||||
transform: scale(3);
|
||||
transform-origin: center;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
/* ── Keyframes ── */
|
||||
|
||||
@keyframes px-swim-left {
|
||||
from { left: calc(100% + 20px); }
|
||||
to { left: -20px; }
|
||||
}
|
||||
|
||||
@keyframes px-swim-right {
|
||||
from { left: -20px; }
|
||||
to { left: calc(100% + 20px); }
|
||||
}
|
||||
|
||||
@keyframes px-bob {
|
||||
0%, 100% { transform: scale(3) translateY(0); }
|
||||
50% { transform: scale(3) translateY(-4px); }
|
||||
}
|
||||
|
||||
@keyframes px-sine {
|
||||
0% { transform: scale(3) translateY(0); }
|
||||
25% { transform: scale(3) translateY(-6px); }
|
||||
50% { transform: scale(3) translateY(0); }
|
||||
75% { transform: scale(3) translateY(6px); }
|
||||
100% { transform: scale(3) translateY(0); }
|
||||
}
|
||||
|
||||
@keyframes px-sink {
|
||||
from { top: -10px; }
|
||||
to { top: calc(100% + 10px); }
|
||||
}
|
||||
|
||||
@keyframes px-puff {
|
||||
0%, 100% { transform: scale(3); }
|
||||
50% { transform: scale(4.5); }
|
||||
}
|
||||
|
||||
@keyframes px-pulse-glow {
|
||||
0%, 100% { opacity: 0.3; }
|
||||
50% { opacity: 0.7; }
|
||||
}
|
||||
|
||||
@keyframes px-tentacle {
|
||||
0%, 100% { transform: scale(3) translateY(0) skewX(0deg); }
|
||||
25% { transform: scale(3) translateY(-2px) skewX(2deg); }
|
||||
75% { transform: scale(3) translateY(2px) skewX(-2deg); }
|
||||
}
|
||||
|
||||
@keyframes px-clam-open {
|
||||
0%, 100% { transform: scale(3) scaleY(1); }
|
||||
50% { transform: scale(3) scaleY(0.5); }
|
||||
}
|
||||
|
||||
/* ── 1. Seahorse — yellow-orange, gentle bob, swims left ── */
|
||||
.seahorse {
|
||||
top: 40%;
|
||||
animation:
|
||||
px-swim-left 160s linear infinite,
|
||||
px-bob 8s ease-in-out infinite;
|
||||
box-shadow:
|
||||
1px -2px #fbbf24, 2px -2px #fbbf24,
|
||||
0 -1px #f59e0b, 1px -1px #f59e0b, 2px -1px #f59e0b,
|
||||
0 0 #f59e0b, 1px 0 #d97706,
|
||||
0 1px #d97706, 1px 1px #d97706,
|
||||
0 2px #b45309, 1px 2px #b45309,
|
||||
-1px 3px #b45309, 0 3px #92400e,
|
||||
-1px 4px #92400e;
|
||||
}
|
||||
|
||||
/* ── 2. Octopus — purple-red, 4 trailing tentacles, swims right ── */
|
||||
.octopus {
|
||||
top: 55%;
|
||||
animation: px-swim-right 200s linear infinite;
|
||||
box-shadow:
|
||||
1px -1px #a855f7, 2px -1px #a855f7,
|
||||
0 0 #9333ea, 1px 0 #9333ea, 2px 0 #9333ea, 3px 0 #a855f7,
|
||||
0 1px #7c3aed, 1px 1px #7c3aed, 2px 1px #7c3aed, 3px 1px #7c3aed,
|
||||
-1px 2px #6d28d9, 0 2px #7c3aed, 2px 2px #7c3aed, 3px 2px #6d28d9,
|
||||
-1px 3px #5b21b6, 1px 3px #6d28d9, 3px 3px #5b21b6,
|
||||
-2px 4px #4c1d95, 0 4px #5b21b6, 2px 4px #5b21b6, 4px 4px #4c1d95;
|
||||
}
|
||||
|
||||
/* ── 3. Crab — red-orange, walking along bottom ── */
|
||||
.crab {
|
||||
top: 85%;
|
||||
animation: px-swim-right 120s linear infinite;
|
||||
box-shadow:
|
||||
-2px -1px #dc2626, 4px -1px #dc2626,
|
||||
-2px 0 #ef4444, -1px 0 #ef4444, 4px 0 #ef4444, 3px 0 #ef4444,
|
||||
0 0 #b91c1c, 1px 0 #dc2626, 2px 0 #dc2626,
|
||||
0 1px #991b1b, 1px 1px #b91c1c, 2px 1px #b91c1c,
|
||||
-1px 2px #7f1d1d, 0 2px #991b1b, 2px 2px #991b1b, 3px 2px #7f1d1d;
|
||||
}
|
||||
|
||||
/* ── 4. Eel — green-dark, sinuous, swims left ── */
|
||||
.eel {
|
||||
top: 45%;
|
||||
animation:
|
||||
px-swim-left 70s linear infinite,
|
||||
px-sine 4s ease-in-out infinite;
|
||||
box-shadow:
|
||||
0 0 #16a34a, 1px 0 #15803d, 2px 0 #166534, 3px 0 #14532d,
|
||||
4px 0 #166534, 5px 0 #15803d, 6px 0 #16a34a, 7px 0 #15803d,
|
||||
1px 1px #22c55e, 2px 1px #22c55e, 3px 1px #22c55e, 5px 1px #22c55e, 6px 1px #22c55e,
|
||||
0 -1px #fbbf24;
|
||||
}
|
||||
|
||||
/* ── 5. Pufferfish — yellow, inflates via scale pulse ── */
|
||||
.pufferfish {
|
||||
top: 35%;
|
||||
animation:
|
||||
px-swim-left 140s linear infinite,
|
||||
px-puff 10s ease-in-out infinite;
|
||||
box-shadow:
|
||||
0 -1px #fbbf24, 1px -1px #fbbf24,
|
||||
-1px 0 #f59e0b, 0 0 #eab308, 1px 0 #eab308, 2px 0 #f59e0b,
|
||||
-1px 1px #f59e0b, 0 1px #eab308, 1px 1px #eab308, 2px 1px #f59e0b,
|
||||
0 2px #d97706, 1px 2px #d97706,
|
||||
-2px 0 #ca8a04, 3px 0 #ca8a04, 0 -2px #ca8a04, 1px -2px #ca8a04,
|
||||
0 0 #1e293b;
|
||||
}
|
||||
|
||||
/* ── 6. Shrimp — pink-red, dart near bottom ── */
|
||||
.shrimp {
|
||||
top: 80%;
|
||||
animation: px-swim-right 50s linear infinite;
|
||||
box-shadow:
|
||||
0 0 #fb7185, 1px 0 #f43f5e, 2px 0 #e11d48, 3px 0 #be123c,
|
||||
0 1px #fda4af, 1px 1px #fb7185, 2px 1px #f43f5e,
|
||||
-1px -1px rgba(251,113,133,0.6), -2px -2px rgba(251,113,133,0.3),
|
||||
4px 0 #9f1239, 5px -1px #881337;
|
||||
}
|
||||
|
||||
/* ── 7. Nautilus — cream-brown spiral shell, swims left ── */
|
||||
.nautilus {
|
||||
top: 50%;
|
||||
animation:
|
||||
px-swim-left 240s linear infinite,
|
||||
px-bob 12s ease-in-out infinite;
|
||||
box-shadow:
|
||||
0 -1px #d4a574, 1px -1px #c2956a,
|
||||
-1px 0 #d4a574, 0 0 #b08050, 1px 0 #a0714a, 2px 0 #c2956a,
|
||||
-1px 1px #c2956a, 0 1px #8b6040, 1px 1px #a0714a, 2px 1px #b08050,
|
||||
0 2px #c2956a, 1px 2px #d4a574,
|
||||
-2px 1px #fef3c7, -2px 2px #fde68a;
|
||||
}
|
||||
|
||||
/* ── 8. Swordfish — silver-blue, fast streak ── */
|
||||
.swordfish {
|
||||
top: 25%;
|
||||
animation: px-swim-left 25s linear infinite;
|
||||
box-shadow:
|
||||
-3px 0 #94a3b8, -2px 0 #94a3b8,
|
||||
-1px 0 #64748b, 0 0 #475569, 1px 0 #475569,
|
||||
2px 0 #334155, 3px 0 #334155, 4px 0 #475569, 5px 0 #64748b,
|
||||
1px 1px #94a3b8, 2px 1px #94a3b8, 3px 1px #94a3b8, 4px 1px #94a3b8,
|
||||
2px -1px #334155, 3px -1px #334155,
|
||||
6px -1px #475569, 6px 1px #475569;
|
||||
}
|
||||
|
||||
/* ── 9. Starfish — orange, very slow crawl along floor ── */
|
||||
.starfish-walk {
|
||||
top: 88%;
|
||||
animation: px-swim-right 360s linear infinite;
|
||||
box-shadow:
|
||||
0 0 #f97316, 1px 0 #ea580c,
|
||||
0 -2px #fb923c, 1px -2px #fb923c,
|
||||
-2px 0 #fb923c, 3px 0 #fb923c,
|
||||
-1px 2px #fb923c, 2px 2px #fb923c,
|
||||
0 -1px #f97316, 1px -1px #f97316,
|
||||
-1px 0 #f97316, 2px 0 #f97316,
|
||||
0 1px #ea580c, 1px 1px #ea580c,
|
||||
-1px 1px #f97316, 2px 1px #f97316;
|
||||
}
|
||||
|
||||
/* ── 10. Anchor — grey iron, sinks slowly (very rare) ── */
|
||||
.anchor {
|
||||
left: 70%;
|
||||
animation: px-sink 100s linear forwards;
|
||||
box-shadow:
|
||||
0 -3px #6b7280, 1px -3px #6b7280,
|
||||
-1px -2px #6b7280, 2px -2px #6b7280,
|
||||
0 -2px #9ca3af, 1px -2px #9ca3af,
|
||||
0 -1px #4b5563, 1px -1px #4b5563,
|
||||
0 0 #4b5563, 1px 0 #4b5563,
|
||||
0 1px #4b5563, 1px 1px #4b5563,
|
||||
0 2px #374151, 1px 2px #374151,
|
||||
-2px 2px #6b7280, -1px 2px #4b5563, 2px 2px #4b5563, 3px 2px #6b7280,
|
||||
-2px 3px #374151, 3px 3px #374151,
|
||||
-1px 3px #4b5563, 2px 3px #4b5563;
|
||||
}
|
||||
|
||||
/* ── 11. Treasure — brown chest with gold gleam (stationary) ── */
|
||||
.treasure {
|
||||
top: 90%;
|
||||
left: 25%;
|
||||
animation: px-pulse-glow 10s ease-in-out infinite;
|
||||
box-shadow:
|
||||
0 -1px #92400e, 1px -1px #92400e, 2px -1px #92400e, 3px -1px #92400e,
|
||||
-1px 0 #78350f, 0 0 #78350f, 1px 0 #92400e, 2px 0 #92400e, 3px 0 #78350f, 4px 0 #78350f,
|
||||
-1px 1px #78350f, 0 1px #78350f, 1px 1px #92400e, 2px 1px #92400e, 3px 1px #78350f, 4px 1px #78350f,
|
||||
1px 0 #fbbf24, 2px 0 #fbbf24,
|
||||
1px -1px #fde68a;
|
||||
}
|
||||
|
||||
/* ── 12. Submarine — grey with yellow light (very rare), swims right ── */
|
||||
.submarine {
|
||||
top: 30%;
|
||||
animation: px-swim-right 320s linear infinite;
|
||||
box-shadow:
|
||||
3px -2px #6b7280, 3px -1px #6b7280,
|
||||
0 0 #4b5563, 1px 0 #4b5563, 2px 0 #4b5563, 3px 0 #4b5563, 4px 0 #4b5563, 5px 0 #4b5563,
|
||||
-1px 1px #374151, 0 1px #374151, 1px 1px #374151, 2px 1px #374151, 3px 1px #374151, 4px 1px #374151, 5px 1px #374151, 6px 1px #374151,
|
||||
0 2px #4b5563, 1px 2px #4b5563, 2px 2px #4b5563, 3px 2px #4b5563, 4px 2px #4b5563, 5px 2px #4b5563,
|
||||
1px 1px #fbbf24, 4px 1px #fbbf24,
|
||||
-2px 0 #9ca3af, -2px 1px #9ca3af, -2px 2px #9ca3af;
|
||||
}
|
||||
|
||||
/* ── 13. Diver — black suit, white mask, bubble trail (very rare) ── */
|
||||
.diver {
|
||||
top: 40%;
|
||||
animation: px-swim-left 180s linear infinite;
|
||||
box-shadow:
|
||||
0 -1px #f8fafc, 1px -1px #f8fafc,
|
||||
0 0 #1e293b, 1px 0 #1e293b,
|
||||
0 1px #1e293b, 1px 1px #1e293b,
|
||||
0 2px #0f172a, 1px 2px #0f172a,
|
||||
-1px 3px #1e293b, 2px 3px #1e293b,
|
||||
2px 0 #475569, 2px 1px #475569,
|
||||
-1px -2px rgba(255,255,255,0.5), -2px -3px rgba(255,255,255,0.3), -3px -4px rgba(255,255,255,0.15);
|
||||
}
|
||||
|
||||
/* ── 14. Sea Anemone — pink-purple, tentacle wave (stationary) ── */
|
||||
.sea-anemone {
|
||||
top: 92%;
|
||||
left: 60%;
|
||||
animation: px-tentacle 12s ease-in-out infinite;
|
||||
box-shadow:
|
||||
-1px -2px #e879f9, 0 -2px #d946ef, 1px -2px #c026d3, 2px -2px #e879f9,
|
||||
-1px -1px #d946ef, 0 -1px #c026d3, 1px -1px #d946ef, 2px -1px #c026d3,
|
||||
0 0 #a21caf, 1px 0 #a21caf,
|
||||
0 1px #86198f, 1px 1px #86198f;
|
||||
}
|
||||
|
||||
/* ── 15. Clam — grey shell, pearl gleam inside (stationary) ── */
|
||||
.clam {
|
||||
top: 90%;
|
||||
left: 82%;
|
||||
animation: px-clam-open 16s ease-in-out infinite;
|
||||
box-shadow:
|
||||
0 -1px #9ca3af, 1px -1px #9ca3af, 2px -1px #9ca3af,
|
||||
-1px 0 #6b7280, 0 0 #6b7280, 1px 0 #6b7280, 2px 0 #6b7280, 3px 0 #6b7280,
|
||||
-1px 1px #4b5563, 0 1px #4b5563, 1px 1px #4b5563, 2px 1px #4b5563, 3px 1px #4b5563,
|
||||
0 2px #6b7280, 1px 2px #6b7280, 2px 2px #6b7280,
|
||||
1px 0 #fef3c7;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user