generated from nhcarrigan/template
feat: initial prototype
Security Scan and Upload / Security & DefectDojo Upload (push) Successful in 47s
Security Scan and Upload / Security & DefectDojo Upload (push) Successful in 47s
This commit is contained in:
@@ -0,0 +1,214 @@
|
||||
<script lang="ts">
|
||||
import { characterState, characterInfo } from "$lib/stores/character";
|
||||
import type { CharacterState, CharacterStateInfo } from "$lib/types/states";
|
||||
|
||||
let currentState: CharacterState = $state("idle");
|
||||
let info: CharacterStateInfo = $state({
|
||||
state: "idle",
|
||||
label: "Ready",
|
||||
description: "Waiting for your command~",
|
||||
spriteFile: "idle.png",
|
||||
});
|
||||
|
||||
characterState.subscribe((state) => {
|
||||
currentState = state;
|
||||
});
|
||||
|
||||
characterInfo.subscribe((i) => {
|
||||
info = i;
|
||||
});
|
||||
|
||||
function getAnimationClass(): string {
|
||||
switch (currentState) {
|
||||
case "thinking":
|
||||
return "animate-thinking";
|
||||
case "typing":
|
||||
return "animate-typing";
|
||||
case "searching":
|
||||
return "animate-searching";
|
||||
case "success":
|
||||
return "animate-celebrate";
|
||||
case "error":
|
||||
return "animate-shake";
|
||||
default:
|
||||
return "animate-idle";
|
||||
}
|
||||
}
|
||||
|
||||
function getBackgroundGlow(): string {
|
||||
switch (currentState) {
|
||||
case "thinking":
|
||||
return "shadow-thinking";
|
||||
case "typing":
|
||||
return "shadow-typing";
|
||||
case "searching":
|
||||
return "shadow-searching";
|
||||
case "coding":
|
||||
return "shadow-coding";
|
||||
case "mcp":
|
||||
return "shadow-mcp";
|
||||
case "success":
|
||||
return "shadow-success";
|
||||
case "error":
|
||||
return "shadow-error";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="anime-girl-container flex flex-col items-center justify-end h-full p-4">
|
||||
<div class="character-frame relative {getBackgroundGlow()} w-full max-w-md">
|
||||
<div class="sprite-container {getAnimationClass()}">
|
||||
<img
|
||||
src="/sprites/{info.spriteFile}"
|
||||
alt="Hikari - {info.label}"
|
||||
class="character-sprite w-full h-auto object-contain"
|
||||
onerror={(e) => {
|
||||
const target = e.currentTarget as HTMLImageElement;
|
||||
target.src = "/sprites/placeholder.svg";
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="state-indicator absolute -bottom-2 left-1/2 transform -translate-x-1/2">
|
||||
<div
|
||||
class="px-3 py-1 rounded-full text-xs font-medium bg-[var(--bg-secondary)] border border-[var(--border-color)] text-[var(--accent-primary)]"
|
||||
>
|
||||
{info.label}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="speech-bubble mt-4 max-w-xs">
|
||||
<div class="relative bg-[var(--bg-secondary)] rounded-lg px-4 py-2 border border-[var(--border-color)]">
|
||||
<div class="absolute -top-2 left-1/2 transform -translate-x-1/2 w-0 h-0 border-l-8 border-r-8 border-b-8 border-transparent border-b-[var(--bg-secondary)]"></div>
|
||||
<p class="text-sm text-gray-300 text-center italic">{info.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.character-frame {
|
||||
border-radius: 50%;
|
||||
transition: box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.shadow-thinking {
|
||||
box-shadow: 0 0 30px rgba(147, 51, 234, 0.5);
|
||||
}
|
||||
|
||||
.shadow-typing {
|
||||
box-shadow: 0 0 30px rgba(59, 130, 246, 0.5);
|
||||
}
|
||||
|
||||
.shadow-searching {
|
||||
box-shadow: 0 0 30px rgba(234, 179, 8, 0.5);
|
||||
}
|
||||
|
||||
.shadow-coding {
|
||||
box-shadow: 0 0 30px rgba(34, 197, 94, 0.5);
|
||||
}
|
||||
|
||||
.shadow-mcp {
|
||||
box-shadow: 0 0 30px rgba(236, 72, 153, 0.5);
|
||||
}
|
||||
|
||||
.shadow-success {
|
||||
box-shadow: 0 0 30px rgba(16, 185, 129, 0.5);
|
||||
}
|
||||
|
||||
.shadow-error {
|
||||
box-shadow: 0 0 30px rgba(239, 68, 68, 0.5);
|
||||
}
|
||||
|
||||
@keyframes idle-bob {
|
||||
0%, 100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes thinking-sway {
|
||||
0%, 100% {
|
||||
transform: rotate(-2deg);
|
||||
}
|
||||
50% {
|
||||
transform: rotate(2deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes typing-bounce {
|
||||
0%, 100% {
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-3px) scale(1.02);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes searching-look {
|
||||
0%, 100% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
25% {
|
||||
transform: translateX(-5px);
|
||||
}
|
||||
75% {
|
||||
transform: translateX(5px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes celebrate {
|
||||
0%, 100% {
|
||||
transform: scale(1) rotate(0deg);
|
||||
}
|
||||
25% {
|
||||
transform: scale(1.1) rotate(-5deg);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.1) rotate(5deg);
|
||||
}
|
||||
75% {
|
||||
transform: scale(1.05) rotate(-3deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes shake {
|
||||
0%, 100% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
10%, 30%, 50%, 70%, 90% {
|
||||
transform: translateX(-5px);
|
||||
}
|
||||
20%, 40%, 60%, 80% {
|
||||
transform: translateX(5px);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-idle {
|
||||
animation: idle-bob 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.animate-thinking {
|
||||
animation: thinking-sway 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.animate-typing {
|
||||
animation: typing-bounce 0.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.animate-searching {
|
||||
animation: searching-look 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.animate-celebrate {
|
||||
animation: celebrate 0.8s ease-in-out;
|
||||
}
|
||||
|
||||
.animate-shake {
|
||||
animation: shake 0.5s ease-in-out;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user