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