add mastery display
This commit is contained in:
parent
4161a615ba
commit
1298ae1a35
2 changed files with 268 additions and 0 deletions
205
src/lib/components/sidebar/modifications/MasteryDisplay.svelte
Normal file
205
src/lib/components/sidebar/modifications/MasteryDisplay.svelte
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
<script lang="ts">
|
||||
import type { GridCharacter } from '$lib/types/api/party'
|
||||
import { formatRingStat, formatEarringStat } from '$lib/utils/modificationFormatters'
|
||||
import { getRingStat, getElementalizedEarringStat } from '$lib/utils/masteryUtils'
|
||||
import { getLocale } from '$lib/paraglide/runtime.js'
|
||||
|
||||
interface Props {
|
||||
rings?: GridCharacter['overMastery']
|
||||
earring?: GridCharacter['aetherialMastery']
|
||||
characterElement?: number
|
||||
variant?: 'compact' | 'detailed'
|
||||
showIcons?: boolean
|
||||
}
|
||||
|
||||
let { rings, earring, characterElement, variant = 'compact', showIcons = true }: Props = $props()
|
||||
|
||||
// Get current locale
|
||||
const locale = $derived(getLocale() as 'en' | 'ja')
|
||||
|
||||
// Get icon for mastery type based on modifier
|
||||
function getMasteryIcon(type: 'ring' | 'earring', modifier: number): string | null {
|
||||
if (!showIcons) return null
|
||||
|
||||
const stat = type === 'ring'
|
||||
? getRingStat(modifier)
|
||||
: (characterElement !== undefined && (modifier === 3 || modifier === 4))
|
||||
? getElementalizedEarringStat(modifier, characterElement, locale)
|
||||
: getElementalizedEarringStat(modifier, undefined, locale)
|
||||
|
||||
if (!stat || !stat.slug) return null
|
||||
|
||||
return `/images/mastery/${stat.slug}.png`
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if rings && rings.length > 0}
|
||||
<div class="mastery-display rings {variant}">
|
||||
{#if variant === 'detailed'}
|
||||
<h4 class="mastery-title">Over Mastery</h4>
|
||||
{/if}
|
||||
<ul class="mastery-list">
|
||||
{#each rings as ring}
|
||||
<li class="mastery-item">
|
||||
{#if showIcons}
|
||||
{@const iconUrl = getMasteryIcon('ring', ring.modifier)}
|
||||
{#if iconUrl}
|
||||
<img
|
||||
src={iconUrl}
|
||||
alt={formatRingStat(ring.modifier, ring.strength, locale).split('+')[0].trim()}
|
||||
class="mastery-icon"
|
||||
/>
|
||||
{/if}
|
||||
{/if}
|
||||
<span class="mastery-content">
|
||||
{#if variant === 'detailed'}
|
||||
<strong class="mastery-label">
|
||||
{formatRingStat(ring.modifier, ring.strength, locale).split('+')[0].trim()}
|
||||
</strong>
|
||||
<span class="mastery-value">
|
||||
+{ring.strength}{getRingStat(ring.modifier)?.suffix || ''}
|
||||
</span>
|
||||
{:else}
|
||||
{formatRingStat(ring.modifier, ring.strength, locale)}
|
||||
{/if}
|
||||
</span>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if earring}
|
||||
<div class="mastery-display earring {variant}">
|
||||
{#if variant === 'detailed'}
|
||||
<h4 class="mastery-title">Aetherial Mastery</h4>
|
||||
{/if}
|
||||
<ul class="mastery-list">
|
||||
<li class="mastery-item enhanced">
|
||||
{#if showIcons}
|
||||
{@const iconUrl = getMasteryIcon('earring', earring.modifier)}
|
||||
{#if iconUrl}
|
||||
<img
|
||||
src={iconUrl}
|
||||
alt={formatEarringStat(earring.modifier, earring.strength, locale, characterElement).split('+')[0].trim()}
|
||||
class="mastery-icon"
|
||||
/>
|
||||
{/if}
|
||||
{/if}
|
||||
<span class="mastery-content">
|
||||
{#if variant === 'detailed'}
|
||||
<strong class="mastery-label">
|
||||
{formatEarringStat(earring.modifier, earring.strength, locale, characterElement).split('+')[0].trim()}
|
||||
</strong>
|
||||
<span class="mastery-value enhanced">
|
||||
+{earring.strength}{getElementalizedEarringStat(earring.modifier, characterElement, locale)?.suffix || ''}
|
||||
</span>
|
||||
{:else}
|
||||
{formatEarringStat(earring.modifier, earring.strength, locale, characterElement)}
|
||||
{/if}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
@use '$src/themes/colors' as colors;
|
||||
@use '$src/themes/spacing' as spacing;
|
||||
@use '$src/themes/typography' as typography;
|
||||
@use '$src/themes/layout' as layout;
|
||||
|
||||
.mastery-display {
|
||||
&.detailed {
|
||||
margin-bottom: spacing.$unit-2x;
|
||||
}
|
||||
|
||||
&.compact {
|
||||
margin-bottom: spacing.$unit;
|
||||
}
|
||||
}
|
||||
|
||||
.mastery-title {
|
||||
margin: 0 0 spacing.$unit 0;
|
||||
font-size: typography.$font-regular;
|
||||
font-weight: typography.$medium;
|
||||
color: var(--text-secondary, colors.$grey-40);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.mastery-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: calc(spacing.$unit * 0.5);
|
||||
}
|
||||
|
||||
.mastery-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: spacing.$unit;
|
||||
padding: calc(spacing.$unit * 0.75);
|
||||
background: colors.$grey-85;
|
||||
border-radius: layout.$item-corner-small;
|
||||
|
||||
&.enhanced {
|
||||
background: linear-gradient(135deg, colors.$grey-85, rgba(#d4af37, 0.1));
|
||||
border: 1px solid rgba(#d4af37, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.mastery-icon {
|
||||
width: spacing.$unit-3x;
|
||||
height: spacing.$unit-3x;
|
||||
object-fit: contain;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.mastery-content {
|
||||
display: flex;
|
||||
gap: spacing.$unit;
|
||||
align-items: center;
|
||||
font-size: typography.$font-small;
|
||||
color: var(--text-primary);
|
||||
flex: 1;
|
||||
|
||||
.detailed & {
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
.mastery-label {
|
||||
font-weight: typography.$medium;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.mastery-value {
|
||||
font-weight: typography.$normal;
|
||||
color: var(--text-secondary);
|
||||
|
||||
&.enhanced {
|
||||
color: #d4af37;
|
||||
font-weight: typography.$medium;
|
||||
}
|
||||
}
|
||||
|
||||
// Compact variant styles
|
||||
.compact {
|
||||
.mastery-list {
|
||||
gap: spacing.$unit-half;
|
||||
}
|
||||
|
||||
.mastery-item {
|
||||
padding: spacing.$unit-half;
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.mastery-content {
|
||||
font-size: typography.$font-small;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
63
src/lib/utils/masteryUtils.ts
Normal file
63
src/lib/utils/masteryUtils.ts
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
import { overMastery, aetherialMastery, type ItemSkill } from '$lib/data/overMastery'
|
||||
import { getElementName, getOppositeElement } from './element'
|
||||
|
||||
// Helper to find the right mastery category for rings
|
||||
export function getRingMasteryCategory(modifier: number): ItemSkill[] {
|
||||
if (modifier <= 2) return overMastery.a // Primary (ATK, HP)
|
||||
if (modifier <= 9) return overMastery.b // Secondary
|
||||
return overMastery.c // Tertiary
|
||||
}
|
||||
|
||||
// Helper to get ring stat
|
||||
export function getRingStat(modifier: number): ItemSkill | undefined {
|
||||
const category = getRingMasteryCategory(modifier)
|
||||
return category.find(item => item.id === modifier)
|
||||
}
|
||||
|
||||
// Helper to get earring stat
|
||||
export function getEarringStat(modifier: number): ItemSkill | undefined {
|
||||
return aetherialMastery.find(item => item.id === modifier)
|
||||
}
|
||||
|
||||
// Helper to get earring stat with element substitution
|
||||
export function getElementalizedEarringStat(
|
||||
modifier: number,
|
||||
characterElement?: number,
|
||||
locale: 'en' | 'ja' = 'en'
|
||||
): ItemSkill | undefined {
|
||||
const stat = getEarringStat(modifier)
|
||||
if (!stat) return undefined
|
||||
|
||||
// Deep clone to avoid mutation
|
||||
const elementalizedStat = JSON.parse(JSON.stringify(stat)) as ItemSkill
|
||||
|
||||
// Handle element substitution for IDs 3 and 4
|
||||
if (characterElement !== undefined && characterElement !== null) {
|
||||
if (modifier === 3) {
|
||||
// ID 3: Use character's element
|
||||
const elementName = getElementName(characterElement, locale)
|
||||
if (locale === 'en') {
|
||||
elementalizedStat.name.en = elementalizedStat.name.en.replace('{Element}', elementName)
|
||||
} else {
|
||||
elementalizedStat.name.ja = elementalizedStat.name.ja.replace('{属性}', `${elementName}属性`)
|
||||
}
|
||||
// Update slug for icon purposes - using element ID for icon path
|
||||
elementalizedStat.slug = `ele-${characterElement}`
|
||||
} else if (modifier === 4) {
|
||||
// ID 4: Use opposite element
|
||||
const oppositeElementId = getOppositeElement(characterElement)
|
||||
if (oppositeElementId !== undefined) {
|
||||
const elementName = getElementName(oppositeElementId, locale)
|
||||
if (locale === 'en') {
|
||||
elementalizedStat.name.en = elementalizedStat.name.en.replace('{Element}', elementName)
|
||||
} else {
|
||||
elementalizedStat.name.ja = elementalizedStat.name.ja.replace('{属性}', `${elementName}属性`)
|
||||
}
|
||||
// Update slug for icon purposes - using element ID for icon path
|
||||
elementalizedStat.slug = `ele-${oppositeElementId}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return elementalizedStat
|
||||
}
|
||||
Loading…
Reference in a new issue