fix: character slot selection glow and empty protagonist
- character grid starts at position 1 (skip protagonist) - add isEmptySelected state for glow on empty slots - empty protagonist shows relief.png background only
This commit is contained in:
parent
18328e2d38
commit
d51fe03905
2 changed files with 56 additions and 9 deletions
|
|
@ -11,6 +11,7 @@
|
||||||
import { openDetailsSidebar } from '$lib/features/details/openDetailsSidebar.svelte'
|
import { openDetailsSidebar } from '$lib/features/details/openDetailsSidebar.svelte'
|
||||||
import { getJobPortraitUrl, Gender } from '$lib/utils/jobUtils'
|
import { getJobPortraitUrl, Gender } from '$lib/utils/jobUtils'
|
||||||
import { sidebar } from '$lib/stores/sidebar.svelte'
|
import { sidebar } from '$lib/stores/sidebar.svelte'
|
||||||
|
import { GridType } from '$lib/types/enums'
|
||||||
import perpetuityFilled from '$src/assets/icons/perpetuity/filled.svg'
|
import perpetuityFilled from '$src/assets/icons/perpetuity/filled.svg'
|
||||||
import perpetuityEmpty from '$src/assets/icons/perpetuity/empty.svg'
|
import perpetuityEmpty from '$src/assets/icons/perpetuity/empty.svg'
|
||||||
import * as m from '$lib/paraglide/messages'
|
import * as m from '$lib/paraglide/messages'
|
||||||
|
|
@ -30,6 +31,8 @@
|
||||||
updateParty: (p: Party) => void
|
updateParty: (p: Party) => void
|
||||||
canEdit: () => boolean
|
canEdit: () => boolean
|
||||||
getEditKey: () => string | null
|
getEditKey: () => string | null
|
||||||
|
getSelectedSlot?: () => number
|
||||||
|
getActiveTab?: () => GridType
|
||||||
services: { gridService: any; partyService: any }
|
services: { gridService: any; partyService: any }
|
||||||
openPicker?: (opts: {
|
openPicker?: (opts: {
|
||||||
type: 'character' | 'weapon' | 'summon'
|
type: 'character' | 'weapon' | 'summon'
|
||||||
|
|
@ -53,6 +56,11 @@
|
||||||
return getJobPortraitUrl(job, Gender.Gran) // TODO: Get gender from user preferences
|
return getJobPortraitUrl(job, Gender.Gran) // TODO: Get gender from user preferences
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For protagonist slot without a job, show nothing (relief.png background shows through)
|
||||||
|
if (position === 0 && !job) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
// If no item or no character with granblueId, return placeholder
|
// If no item or no character with granblueId, return placeholder
|
||||||
if (!item || !item.character?.granblueId) {
|
if (!item || !item.character?.granblueId) {
|
||||||
return getCharacterImageWithPose(null, 'main', 0, 0)
|
return getCharacterImageWithPose(null, 'main', 0, 0)
|
||||||
|
|
@ -74,6 +82,14 @@
|
||||||
// Check if this item is currently active in the sidebar
|
// Check if this item is currently active in the sidebar
|
||||||
let isActive = $derived(item?.id && sidebar.activeItemId === String(item.id))
|
let isActive = $derived(item?.id && sidebar.activeItemId === String(item.id))
|
||||||
|
|
||||||
|
// Check if this empty slot is currently selected for adding an item
|
||||||
|
let isEmptySelected = $derived(
|
||||||
|
!item &&
|
||||||
|
!isProtagonist &&
|
||||||
|
ctx?.getSelectedSlot?.() === position &&
|
||||||
|
ctx?.getActiveTab?.() === GridType.Character
|
||||||
|
)
|
||||||
|
|
||||||
// Determine element class for focus ring
|
// Determine element class for focus ring
|
||||||
let elementClass = $derived.by(() => {
|
let elementClass = $derived.by(() => {
|
||||||
const element = item?.character?.element || partyElement
|
const element = item?.character?.element || partyElement
|
||||||
|
|
@ -180,6 +196,7 @@
|
||||||
class="frame character cell {elementClass}"
|
class="frame character cell {elementClass}"
|
||||||
class:protagonist={position === 0}
|
class:protagonist={position === 0}
|
||||||
class:editable={ctx?.canEdit()}
|
class:editable={ctx?.canEdit()}
|
||||||
|
class:is-active={isActive}
|
||||||
onclick={() => viewDetails()}
|
onclick={() => viewDetails()}
|
||||||
>
|
>
|
||||||
{#if position !== 0}
|
{#if position !== 0}
|
||||||
|
|
@ -210,6 +227,7 @@
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if imageUrl}
|
||||||
<img
|
<img
|
||||||
class="image {elementClass}"
|
class="image {elementClass}"
|
||||||
class:placeholder={!item?.character?.granblueId && !isProtagonist}
|
class:placeholder={!item?.character?.granblueId && !isProtagonist}
|
||||||
|
|
@ -217,6 +235,7 @@
|
||||||
alt={isProtagonist && job ? job.name.en : displayName(item?.character)}
|
alt={isProtagonist && job ? job.name.en : displayName(item?.character)}
|
||||||
src={imageUrl}
|
src={imageUrl}
|
||||||
/>
|
/>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/key}
|
{/key}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -254,20 +273,28 @@
|
||||||
class="frame character cell"
|
class="frame character cell"
|
||||||
class:editable={ctx?.canEdit() && !isProtagonist}
|
class:editable={ctx?.canEdit() && !isProtagonist}
|
||||||
class:protagonist={isProtagonist}
|
class:protagonist={isProtagonist}
|
||||||
|
class:empty-protagonist={isProtagonist && !job}
|
||||||
|
class:is-selected={isEmptySelected}
|
||||||
onclick={() =>
|
onclick={() =>
|
||||||
!isProtagonist &&
|
!isProtagonist &&
|
||||||
ctx?.canEdit() &&
|
ctx?.canEdit() &&
|
||||||
ctx?.openPicker &&
|
ctx?.openPicker &&
|
||||||
ctx.openPicker({ type: 'character', position, item })}
|
ctx.openPicker({ type: 'character', position, item })}
|
||||||
>
|
>
|
||||||
<img
|
{#if !isProtagonist}
|
||||||
class="image"
|
<img
|
||||||
class:placeholder={!isProtagonist || !job}
|
class="image placeholder"
|
||||||
class:protagonist={isProtagonist}
|
alt=""
|
||||||
alt={isProtagonist && job ? job.name.en : ''}
|
src="/images/placeholders/placeholder-weapon-grid.png"
|
||||||
src={isProtagonist ? imageUrl : '/images/placeholders/placeholder-weapon-grid.png'}
|
/>
|
||||||
/>
|
{:else if job && imageUrl}
|
||||||
{#if ctx?.canEdit()}
|
<img
|
||||||
|
class="image protagonist"
|
||||||
|
alt={job.name.en}
|
||||||
|
src={imageUrl}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
{#if ctx?.canEdit() && !isProtagonist}
|
||||||
<span class="icon">
|
<span class="icon">
|
||||||
<Icon name="plus" size={24} />
|
<Icon name="plus" size={24} />
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -381,6 +408,22 @@
|
||||||
opacity: 0.95;
|
opacity: 0.95;
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Slot selection - subtle dark pulsing glow (works for both empty and filled)
|
||||||
|
&.is-selected,
|
||||||
|
&.is-active {
|
||||||
|
animation: pulse-slot-shadow 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse-slot-shadow {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.12), 0 0 4px 2px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.24), 0 0 8px 4px rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.frame.character.cell {
|
.frame.character.cell {
|
||||||
|
|
@ -391,6 +434,10 @@
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
background-position: center -20px;
|
background-position: center -20px;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
|
|
||||||
|
&.empty-protagonist {
|
||||||
|
background-position: center 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ export interface SlotRange {
|
||||||
const GRID_CONFIGS: Record<GridType, SlotRange> = {
|
const GRID_CONFIGS: Record<GridType, SlotRange> = {
|
||||||
[GridType.Weapon]: { start: 0, end: 8, specialSlots: [-1] }, // mainhand + 9 grid slots
|
[GridType.Weapon]: { start: 0, end: 8, specialSlots: [-1] }, // mainhand + 9 grid slots
|
||||||
[GridType.Summon]: { start: 0, end: 5, specialSlots: [-1, 6] }, // main + 6 grid + friend
|
[GridType.Summon]: { start: 0, end: 5, specialSlots: [-1, 6] }, // main + 6 grid + friend
|
||||||
[GridType.Character]: { start: 0, end: 4, specialSlots: [] } // 5 slots (0-4)
|
[GridType.Character]: { start: 1, end: 4, specialSlots: [] } // 4 slots (1-4), position 0 is protagonist (not user-selectable)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue