이름 오버레이 슬라이드와 로컬 메일 인증 우회 추가

캐릭터 이름 표시가 긴 항목은 카드 하단 텍스트를 자동 무한 슬라이드로 보여주고, 넘치지 않는 이름은 기존 고정 표시를 유지하도록 정리했다. 개발 환경에서는 SKIP_EMAIL_VERIFICATION=true 설정으로 가입 시 메일 인증 단계를 건너뛸 수 있게 보강했다.

Made-with: Cursor
This commit is contained in:
2026-04-27 11:10:59 +09:00
committed by DUCK JIN
parent 2559605318
commit 069eda3544
8 changed files with 105 additions and 11 deletions

View File

@@ -204,6 +204,47 @@ function normalizeSearchText(text) {
.toLowerCase()
}
function updateNameMarquee(el) {
if (!el) return
const textEl = el.querySelector('.itemNameOverlay__text')
if (!textEl) return
const content = (textEl.textContent || '').trim()
textEl.setAttribute('data-text', content)
if (!content) {
el.classList.remove('itemNameOverlay--marquee')
el.style.removeProperty('--marquee-distance')
el.style.removeProperty('--marquee-duration')
return
}
const overflowPx = Math.ceil(textEl.scrollWidth - textEl.clientWidth)
const needsMarquee = overflowPx > 0
if (!needsMarquee) {
el.classList.remove('itemNameOverlay--marquee')
el.style.removeProperty('--marquee-distance')
el.style.removeProperty('--marquee-duration')
return
}
const distancePx = overflowPx + 20
const duration = Math.min(24, Math.max(8, distancePx / 10))
el.classList.add('itemNameOverlay--marquee')
el.style.setProperty('--marquee-distance', `${distancePx}px`)
el.style.setProperty('--marquee-duration', `${duration}s`)
}
const vNameMarquee = {
mounted(el) {
updateNameMarquee(el)
},
updated(el) {
updateNameMarquee(el)
},
}
function createAutoTierListTitle() {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
const pick = (size) => Array.from({ length: size }, () => chars[Math.floor(Math.random() * chars.length)]).join('')
@@ -1520,7 +1561,9 @@ onUnmounted(() => {
<div v-if="columns.length > 1" class="previewOnly__columnBadge">{{ column.name || '열 ' + (columnIndex + 1) }}</div>
<div v-for="id in getGroupCellIds(g, columnIndex)" :key="id" class="previewOnly__cell">
<img :src="resolveItemSrc(itemsById[id])" class="thumb" :alt="itemsById[id]?.label || id" />
<div v-if="showCharacterNames" class="itemNameOverlay">{{ itemsById[id]?.label || id }}</div>
<div v-if="showCharacterNames" v-name-marquee class="itemNameOverlay">
<span class="itemNameOverlay__text">{{ itemsById[id]?.label || id }}</span>
</div>
</div>
</div>
</div>
@@ -1832,7 +1875,9 @@ onUnmounted(() => {
:alt="itemsById[id]?.label || id"
draggable="false"
/>
<div v-if="showCharacterNames" class="itemNameOverlay">{{ itemsById[id]?.label || id }}</div>
<div v-if="showCharacterNames" v-name-marquee class="itemNameOverlay">
<span class="itemNameOverlay__text">{{ itemsById[id]?.label || id }}</span>
</div>
<button
v-if="canEdit && !isExporting"
class="cellRemoveBtn"
@@ -2981,14 +3026,38 @@ onUnmounted(() => {
font-size: 11px;
line-height: 1.2;
font-weight: 800;
text-align: center;
display: flex;
align-items: flex-end;
justify-content: flex-start;
pointer-events: none;
overflow: hidden;
}
.itemNameOverlay__text {
display: inline-block;
max-width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: flex;
align-items: flex-end;
justify-content: center;
pointer-events: none;
transform: translateX(0);
}
.itemNameOverlay--marquee .itemNameOverlay__text {
overflow: visible;
text-overflow: clip;
padding-right: 20px;
animation: itemNameMarquee var(--marquee-duration, 12s) linear infinite;
}
.itemNameOverlay--marquee .itemNameOverlay__text::after {
content: attr(data-text);
display: inline-block;
padding-left: 20px;
}
@keyframes itemNameMarquee {
0% {
transform: translateX(0);
}
100% {
transform: translateX(calc(var(--marquee-distance, 0px) * -1));
}
}
.cellRemoveBtn {
position: absolute;