chore(frontend): introduce prettier + eslint and prune unused code
- Add Prettier 3 with prettier-plugin-organize-imports (sorts/removes unused imports) - Add ESLint 10 flat config with typescript-eslint + eslint-plugin-vue + eslint-config-prettier - Add root scripts: format, format:check, lint, lint:fix - Reformat 257 files across admin-web, ops-web, desktop-client, packages - Remove unused locals/exports flagged by --noUnusedLocals/--noUnusedParameters - Fix duplicate localTabLabel key, surrogate-pair regex u-flag, NBSP literal in regex - Skip server/ (Go) and apps/browser-extension/ (deprecated per ADR) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,31 +3,63 @@
|
||||
<!-- Purple -->
|
||||
<div ref="purpleRef" class="char purple" :style="purpleStyle">
|
||||
<div class="eyes" :style="purpleEyesStyle">
|
||||
<EyeBall v-for="i in 2" :key="'p'+i" :size="18" :pupil-size="7" :max-distance="5"
|
||||
eye-color="white" pupil-color="#2D2D2D" :is-blinking="isPurpleBlinking"
|
||||
:force-look-x="purpleLookX" :force-look-y="purpleLookY" />
|
||||
<EyeBall
|
||||
v-for="i in 2"
|
||||
:key="'p' + i"
|
||||
:size="18"
|
||||
:pupil-size="7"
|
||||
:max-distance="5"
|
||||
eye-color="white"
|
||||
pupil-color="#2D2D2D"
|
||||
:is-blinking="isPurpleBlinking"
|
||||
:force-look-x="purpleLookX"
|
||||
:force-look-y="purpleLookY"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Black -->
|
||||
<div ref="blackRef" class="char black" :style="blackStyle">
|
||||
<div class="eyes" :style="blackEyesStyle">
|
||||
<EyeBall v-for="i in 2" :key="'b'+i" :size="16" :pupil-size="6" :max-distance="4"
|
||||
eye-color="white" pupil-color="#2D2D2D" :is-blinking="isBlackBlinking"
|
||||
:force-look-x="blackLookX" :force-look-y="blackLookY" />
|
||||
<EyeBall
|
||||
v-for="i in 2"
|
||||
:key="'b' + i"
|
||||
:size="16"
|
||||
:pupil-size="6"
|
||||
:max-distance="4"
|
||||
eye-color="white"
|
||||
pupil-color="#2D2D2D"
|
||||
:is-blinking="isBlackBlinking"
|
||||
:force-look-x="blackLookX"
|
||||
:force-look-y="blackLookY"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Orange -->
|
||||
<div ref="orangeRef" class="char orange" :style="orangeStyle">
|
||||
<div class="eyes" :style="orangeEyesStyle">
|
||||
<Pupil v-for="i in 2" :key="'o'+i" :size="12" :max-distance="5" pupil-color="#2D2D2D"
|
||||
:force-look-x="hiding ? -5 : undefined" :force-look-y="hiding ? -4 : undefined" />
|
||||
<Pupil
|
||||
v-for="i in 2"
|
||||
:key="'o' + i"
|
||||
:size="12"
|
||||
:max-distance="5"
|
||||
pupil-color="#2D2D2D"
|
||||
:force-look-x="hiding ? -5 : undefined"
|
||||
:force-look-y="hiding ? -4 : undefined"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Yellow -->
|
||||
<div ref="yellowRef" class="char yellow" :style="yellowStyle">
|
||||
<div class="eyes" :style="yellowEyesStyle">
|
||||
<Pupil v-for="i in 2" :key="'y'+i" :size="12" :max-distance="5" pupil-color="#2D2D2D"
|
||||
:force-look-x="hiding ? -5 : undefined" :force-look-y="hiding ? -4 : undefined" />
|
||||
<Pupil
|
||||
v-for="i in 2"
|
||||
:key="'y' + i"
|
||||
:size="12"
|
||||
:max-distance="5"
|
||||
pupil-color="#2D2D2D"
|
||||
:force-look-x="hiding ? -5 : undefined"
|
||||
:force-look-y="hiding ? -4 : undefined"
|
||||
/>
|
||||
</div>
|
||||
<div class="mouth" :style="yellowMouthStyle" />
|
||||
</div>
|
||||
@@ -35,22 +67,25 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, watch, onMounted, onUnmounted } from 'vue'
|
||||
import { computed, onMounted, onUnmounted, reactive, ref, watch } from 'vue'
|
||||
import EyeBall from './EyeBall.vue'
|
||||
import Pupil from './Pupil.vue'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
/** Whether the user is currently typing in an input */
|
||||
isTyping?: boolean
|
||||
/** Whether a secret field has content */
|
||||
hasSecret?: boolean
|
||||
/** Whether the secret is currently visible (e.g. password shown) */
|
||||
secretVisible?: boolean
|
||||
}>(), {
|
||||
isTyping: false,
|
||||
hasSecret: false,
|
||||
secretVisible: false,
|
||||
})
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
/** Whether the user is currently typing in an input */
|
||||
isTyping?: boolean
|
||||
/** Whether a secret field has content */
|
||||
hasSecret?: boolean
|
||||
/** Whether the secret is currently visible (e.g. password shown) */
|
||||
secretVisible?: boolean
|
||||
}>(),
|
||||
{
|
||||
isTyping: false,
|
||||
hasSecret: false,
|
||||
secretVisible: false,
|
||||
},
|
||||
)
|
||||
|
||||
const mouseX = ref(0)
|
||||
const mouseY = ref(0)
|
||||
@@ -73,7 +108,10 @@ const orangePos = reactive({ faceX: 0, faceY: 0, bodySkew: 0 })
|
||||
const hiding = computed(() => props.hasSecret && props.secretVisible)
|
||||
const leaning = computed(() => props.isTyping || (props.hasSecret && !props.secretVisible))
|
||||
|
||||
function calcPos(el: HTMLDivElement | null, target: { faceX: number; faceY: number; bodySkew: number }) {
|
||||
function calcPos(
|
||||
el: HTMLDivElement | null,
|
||||
target: { faceX: number; faceY: number; bodySkew: number },
|
||||
) {
|
||||
if (!el) return
|
||||
const r = el.getBoundingClientRect()
|
||||
const dx = mouseX.value - (r.left + r.width / 2)
|
||||
@@ -92,15 +130,24 @@ function tick() {
|
||||
rafId = requestAnimationFrame(tick)
|
||||
}
|
||||
|
||||
function onMouseMove(e: MouseEvent) { mouseX.value = e.clientX; mouseY.value = e.clientY }
|
||||
function onMouseMove(e: MouseEvent) {
|
||||
mouseX.value = e.clientX
|
||||
mouseY.value = e.clientY
|
||||
}
|
||||
|
||||
function setupBlink(target: { value: boolean }) {
|
||||
let t: number
|
||||
const go = () => {
|
||||
t = window.setTimeout(() => {
|
||||
target.value = true
|
||||
window.setTimeout(() => { target.value = false; go() }, 150)
|
||||
}, Math.random() * 4000 + 3000)
|
||||
t = window.setTimeout(
|
||||
() => {
|
||||
target.value = true
|
||||
window.setTimeout(() => {
|
||||
target.value = false
|
||||
go()
|
||||
}, 150)
|
||||
},
|
||||
Math.random() * 4000 + 3000,
|
||||
)
|
||||
}
|
||||
go()
|
||||
return () => clearTimeout(t)
|
||||
@@ -118,28 +165,39 @@ onMounted(() => {
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('mousemove', onMouseMove)
|
||||
cancelAnimationFrame(rafId)
|
||||
stopP?.(); stopB?.()
|
||||
stopP?.()
|
||||
stopB?.()
|
||||
})
|
||||
|
||||
// Look at each other when typing starts
|
||||
watch(() => props.isTyping, (v) => {
|
||||
if (v) {
|
||||
isLookingAtEachOther.value = true
|
||||
setTimeout(() => { isLookingAtEachOther.value = false }, 800)
|
||||
} else {
|
||||
isLookingAtEachOther.value = false
|
||||
}
|
||||
})
|
||||
watch(
|
||||
() => props.isTyping,
|
||||
(v) => {
|
||||
if (v) {
|
||||
isLookingAtEachOther.value = true
|
||||
setTimeout(() => {
|
||||
isLookingAtEachOther.value = false
|
||||
}, 800)
|
||||
} else {
|
||||
isLookingAtEachOther.value = false
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
// Purple peeks when secret is visible
|
||||
let peekT: number | undefined
|
||||
watch([() => props.hasSecret, () => props.secretVisible, isPurplePeeking], () => {
|
||||
clearTimeout(peekT)
|
||||
if (props.hasSecret && props.secretVisible) {
|
||||
peekT = window.setTimeout(() => {
|
||||
isPurplePeeking.value = true
|
||||
setTimeout(() => { isPurplePeeking.value = false }, 800)
|
||||
}, Math.random() * 3000 + 2000)
|
||||
peekT = window.setTimeout(
|
||||
() => {
|
||||
isPurplePeeking.value = true
|
||||
setTimeout(() => {
|
||||
isPurplePeeking.value = false
|
||||
}, 800)
|
||||
},
|
||||
Math.random() * 3000 + 2000,
|
||||
)
|
||||
} else {
|
||||
isPurplePeeking.value = false
|
||||
}
|
||||
@@ -148,31 +206,40 @@ watch([() => props.hasSecret, () => props.secretVisible, isPurplePeeking], () =>
|
||||
// Computed styles
|
||||
const purpleStyle = computed(() => ({
|
||||
height: leaning.value ? '440px' : '400px',
|
||||
transform: hiding.value ? 'skewX(0deg)'
|
||||
: leaning.value ? `skewX(${purplePos.bodySkew - 12}deg) translateX(40px)`
|
||||
: `skewX(${purplePos.bodySkew}deg)`,
|
||||
transform: hiding.value
|
||||
? 'skewX(0deg)'
|
||||
: leaning.value
|
||||
? `skewX(${purplePos.bodySkew - 12}deg) translateX(40px)`
|
||||
: `skewX(${purplePos.bodySkew}deg)`,
|
||||
}))
|
||||
const purpleEyesStyle = computed(() => ({
|
||||
left: hiding.value ? '20px' : isLookingAtEachOther.value ? '55px' : `${45 + purplePos.faceX}px`,
|
||||
top: hiding.value ? '35px' : isLookingAtEachOther.value ? '65px' : `${40 + purplePos.faceY}px`,
|
||||
gap: '32px',
|
||||
}))
|
||||
const purpleLookX = computed(() => hiding.value ? (isPurplePeeking.value ? 4 : -4) : isLookingAtEachOther.value ? 3 : undefined)
|
||||
const purpleLookY = computed(() => hiding.value ? (isPurplePeeking.value ? 5 : -4) : isLookingAtEachOther.value ? 4 : undefined)
|
||||
const purpleLookX = computed(() =>
|
||||
hiding.value ? (isPurplePeeking.value ? 4 : -4) : isLookingAtEachOther.value ? 3 : undefined,
|
||||
)
|
||||
const purpleLookY = computed(() =>
|
||||
hiding.value ? (isPurplePeeking.value ? 5 : -4) : isLookingAtEachOther.value ? 4 : undefined,
|
||||
)
|
||||
|
||||
const blackStyle = computed(() => ({
|
||||
transform: hiding.value ? 'skewX(0deg)'
|
||||
: isLookingAtEachOther.value ? `skewX(${blackPos.bodySkew * 1.5 + 10}deg) translateX(20px)`
|
||||
: leaning.value ? `skewX(${blackPos.bodySkew * 1.5}deg)`
|
||||
: `skewX(${blackPos.bodySkew}deg)`,
|
||||
transform: hiding.value
|
||||
? 'skewX(0deg)'
|
||||
: isLookingAtEachOther.value
|
||||
? `skewX(${blackPos.bodySkew * 1.5 + 10}deg) translateX(20px)`
|
||||
: leaning.value
|
||||
? `skewX(${blackPos.bodySkew * 1.5}deg)`
|
||||
: `skewX(${blackPos.bodySkew}deg)`,
|
||||
}))
|
||||
const blackEyesStyle = computed(() => ({
|
||||
left: hiding.value ? '10px' : isLookingAtEachOther.value ? '32px' : `${26 + blackPos.faceX}px`,
|
||||
top: hiding.value ? '28px' : isLookingAtEachOther.value ? '12px' : `${32 + blackPos.faceY}px`,
|
||||
gap: '24px',
|
||||
}))
|
||||
const blackLookX = computed(() => hiding.value ? -4 : isLookingAtEachOther.value ? 0 : undefined)
|
||||
const blackLookY = computed(() => hiding.value ? -4 : isLookingAtEachOther.value ? -4 : undefined)
|
||||
const blackLookX = computed(() => (hiding.value ? -4 : isLookingAtEachOther.value ? 0 : undefined))
|
||||
const blackLookY = computed(() => (hiding.value ? -4 : isLookingAtEachOther.value ? -4 : undefined))
|
||||
|
||||
const orangeStyle = computed(() => ({
|
||||
transform: hiding.value ? 'skewX(0deg)' : `skewX(${orangePos.bodySkew}deg)`,
|
||||
@@ -198,12 +265,59 @@ const yellowMouthStyle = computed(() => ({
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.characters-container { position: relative; width: 550px; height: 400px; }
|
||||
.char { position: absolute; bottom: 0; transition: all 0.7s ease-in-out; transform-origin: bottom center; }
|
||||
.purple { left: 70px; width: 180px; background: #6C3FF5; border-radius: 10px 10px 0 0; z-index: 1; }
|
||||
.black { left: 240px; width: 120px; height: 310px; background: #2D2D2D; border-radius: 8px 8px 0 0; z-index: 2; }
|
||||
.orange { left: 0; width: 240px; height: 200px; background: #FF9B6B; border-radius: 120px 120px 0 0; z-index: 3; }
|
||||
.yellow { left: 310px; width: 140px; height: 230px; background: #E8D754; border-radius: 70px 70px 0 0; z-index: 4; }
|
||||
.eyes { position: absolute; display: flex; transition: all 0.7s ease-in-out; }
|
||||
.mouth { position: absolute; width: 80px; height: 4px; background: #2D2D2D; border-radius: 4px; transition: all 0.2s ease-out; }
|
||||
.characters-container {
|
||||
position: relative;
|
||||
width: 550px;
|
||||
height: 400px;
|
||||
}
|
||||
.char {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
transition: all 0.7s ease-in-out;
|
||||
transform-origin: bottom center;
|
||||
}
|
||||
.purple {
|
||||
left: 70px;
|
||||
width: 180px;
|
||||
background: #6c3ff5;
|
||||
border-radius: 10px 10px 0 0;
|
||||
z-index: 1;
|
||||
}
|
||||
.black {
|
||||
left: 240px;
|
||||
width: 120px;
|
||||
height: 310px;
|
||||
background: #2d2d2d;
|
||||
border-radius: 8px 8px 0 0;
|
||||
z-index: 2;
|
||||
}
|
||||
.orange {
|
||||
left: 0;
|
||||
width: 240px;
|
||||
height: 200px;
|
||||
background: #ff9b6b;
|
||||
border-radius: 120px 120px 0 0;
|
||||
z-index: 3;
|
||||
}
|
||||
.yellow {
|
||||
left: 310px;
|
||||
width: 140px;
|
||||
height: 230px;
|
||||
background: #e8d754;
|
||||
border-radius: 70px 70px 0 0;
|
||||
z-index: 4;
|
||||
}
|
||||
.eyes {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
transition: all 0.7s ease-in-out;
|
||||
}
|
||||
.mouth {
|
||||
position: absolute;
|
||||
width: 80px;
|
||||
height: 4px;
|
||||
background: #2d2d2d;
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s ease-out;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,28 +1,57 @@
|
||||
<template>
|
||||
<div ref="eyeRef" class="eyeball" :style="{
|
||||
width: size + 'px', height: isBlinking ? '2px' : size + 'px',
|
||||
backgroundColor: eyeColor,
|
||||
}">
|
||||
<div v-if="!isBlinking" class="pupil-inner" :style="{
|
||||
width: pupilSize + 'px', height: pupilSize + 'px',
|
||||
backgroundColor: pupilColor,
|
||||
transform: `translate(${pos.x}px, ${pos.y}px)`,
|
||||
}" />
|
||||
<div
|
||||
ref="eyeRef"
|
||||
class="eyeball"
|
||||
:style="{
|
||||
width: size + 'px',
|
||||
height: isBlinking ? '2px' : size + 'px',
|
||||
backgroundColor: eyeColor,
|
||||
}"
|
||||
>
|
||||
<div
|
||||
v-if="!isBlinking"
|
||||
class="pupil-inner"
|
||||
:style="{
|
||||
width: pupilSize + 'px',
|
||||
height: pupilSize + 'px',
|
||||
backgroundColor: pupilColor,
|
||||
transform: `translate(${pos.x}px, ${pos.y}px)`,
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
size?: number; pupilSize?: number; maxDistance?: number
|
||||
eyeColor?: string; pupilColor?: string; isBlinking?: boolean
|
||||
forceLookX?: number; forceLookY?: number
|
||||
}>(), { size: 48, pupilSize: 16, maxDistance: 10, eyeColor: 'white', pupilColor: 'black', isBlinking: false })
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
size?: number
|
||||
pupilSize?: number
|
||||
maxDistance?: number
|
||||
eyeColor?: string
|
||||
pupilColor?: string
|
||||
isBlinking?: boolean
|
||||
forceLookX?: number
|
||||
forceLookY?: number
|
||||
}>(),
|
||||
{
|
||||
size: 48,
|
||||
pupilSize: 16,
|
||||
maxDistance: 10,
|
||||
eyeColor: 'white',
|
||||
pupilColor: 'black',
|
||||
isBlinking: false,
|
||||
},
|
||||
)
|
||||
|
||||
const mx = ref(0), my = ref(0)
|
||||
const mx = ref(0),
|
||||
my = ref(0)
|
||||
const eyeRef = ref<HTMLDivElement>()
|
||||
const onMove = (e: MouseEvent) => { mx.value = e.clientX; my.value = e.clientY }
|
||||
const onMove = (e: MouseEvent) => {
|
||||
mx.value = e.clientX
|
||||
my.value = e.clientY
|
||||
}
|
||||
onMounted(() => window.addEventListener('mousemove', onMove))
|
||||
onUnmounted(() => window.removeEventListener('mousemove', onMove))
|
||||
|
||||
|
||||
@@ -1,22 +1,37 @@
|
||||
<template>
|
||||
<div ref="pupilRef" class="pupil" :style="{
|
||||
width: size + 'px', height: size + 'px',
|
||||
backgroundColor: pupilColor,
|
||||
transform: `translate(${pos.x}px, ${pos.y}px)`,
|
||||
}" />
|
||||
<div
|
||||
ref="pupilRef"
|
||||
class="pupil"
|
||||
:style="{
|
||||
width: size + 'px',
|
||||
height: size + 'px',
|
||||
backgroundColor: pupilColor,
|
||||
transform: `translate(${pos.x}px, ${pos.y}px)`,
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
size?: number; maxDistance?: number; pupilColor?: string
|
||||
forceLookX?: number; forceLookY?: number
|
||||
}>(), { size: 12, maxDistance: 5, pupilColor: 'black' })
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
size?: number
|
||||
maxDistance?: number
|
||||
pupilColor?: string
|
||||
forceLookX?: number
|
||||
forceLookY?: number
|
||||
}>(),
|
||||
{ size: 12, maxDistance: 5, pupilColor: 'black' },
|
||||
)
|
||||
|
||||
const mx = ref(0), my = ref(0)
|
||||
const mx = ref(0),
|
||||
my = ref(0)
|
||||
const pupilRef = ref<HTMLDivElement>()
|
||||
const onMove = (e: MouseEvent) => { mx.value = e.clientX; my.value = e.clientY }
|
||||
const onMove = (e: MouseEvent) => {
|
||||
mx.value = e.clientX
|
||||
my.value = e.clientY
|
||||
}
|
||||
onMounted(() => window.addEventListener('mousemove', onMove))
|
||||
onUnmounted(() => window.removeEventListener('mousemove', onMove))
|
||||
|
||||
@@ -34,5 +49,8 @@ const pos = computed(() => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.pupil { border-radius: 50%; transition: transform 0.1s ease-out; }
|
||||
.pupil {
|
||||
border-radius: 50%;
|
||||
transition: transform 0.1s ease-out;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user