Update rep components with improved styling
This commit is contained in:
parent
7e862ed56a
commit
ff6074675b
4 changed files with 300 additions and 167 deletions
|
|
@ -1,13 +1,17 @@
|
|||
<script lang="ts">
|
||||
import type { PartyView } from '$lib/api/schemas/party'
|
||||
export let party: PartyView
|
||||
import type { Party, GridWeapon, GridCharacter } from '$lib/types/api/party'
|
||||
export let party: Party
|
||||
|
||||
const characters = party.characters || []
|
||||
const grid = Array.from({ length: 3 }, (_, i) => characters.find((c: any) => c?.position === i))
|
||||
const grid = Array.from({ length: 3 }, (_, i) =>
|
||||
characters.find((c: GridCharacter) => c?.position === i)
|
||||
)
|
||||
|
||||
function protagonistClass(): string {
|
||||
const main = (party.weapons || []).find((w: any) => w?.mainhand || w?.position === -1)
|
||||
const el = (main as any)?.element || (main as any)?.object?.element
|
||||
const main: GridWeapon | undefined = (party.weapons || []).find(
|
||||
(w: GridWeapon) => w?.mainhand || w?.position === -1
|
||||
)
|
||||
const el = main?.element ?? main?.weapon?.element
|
||||
switch (el) {
|
||||
case 1:
|
||||
return 'wind'
|
||||
|
|
@ -26,8 +30,8 @@
|
|||
}
|
||||
}
|
||||
|
||||
function characterImageUrl(c?: any): string {
|
||||
const id = c?.object?.granblueId
|
||||
function characterImageUrl(c?: GridCharacter): string {
|
||||
const id = c?.character?.granblueId
|
||||
if (!id) return ''
|
||||
const uncap = c?.uncapLevel ?? 0
|
||||
const trans = c?.transcendenceStep ?? 0
|
||||
|
|
@ -36,8 +40,10 @@
|
|||
else if (uncap >= 5) suffix = '03'
|
||||
else if (uncap > 2) suffix = '02'
|
||||
if (String(id) === '3030182000') {
|
||||
const main = (party.weapons || []).find((w: any) => w?.mainhand || w?.position === -1)
|
||||
const el = (main as any)?.element || (main as any)?.object?.element || 1
|
||||
const main: GridWeapon | undefined = (party.weapons || []).find(
|
||||
(w: GridWeapon) => w?.mainhand || w?.position === -1
|
||||
)
|
||||
const el = main?.element ?? main?.weapon?.element ?? 1
|
||||
suffix = `${suffix}_0${el}`
|
||||
}
|
||||
return `/images/character-main/${id}_${suffix}.jpg`
|
||||
|
|
@ -46,79 +52,108 @@
|
|||
|
||||
<div class="rep">
|
||||
<ul class="characters">
|
||||
<li class={`protagonist ${protagonistClass()}`}></li>
|
||||
<li class={`protagonist ${protagonistClass()}`} class:empty={!protagonistClass()}></li>
|
||||
{#each grid as c, i}
|
||||
<li class="character">
|
||||
{#if c}<img alt="Character" src={characterImageUrl(c)} />{/if}
|
||||
<li class="character" class:empty={!c}>
|
||||
{#if c}<img
|
||||
alt="Character"
|
||||
src={characterImageUrl(c)}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>{/if}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@use '$src/themes/layout' as *;
|
||||
@use '$src/themes/spacing' as *;
|
||||
@use '$src/themes/rep' as rep;
|
||||
|
||||
.rep {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 10px;
|
||||
border-radius: $item-corner-small;
|
||||
grid-gap: $unit-half;
|
||||
}
|
||||
.character,
|
||||
.protagonist {
|
||||
aspect-ratio: 16/33;
|
||||
background: var(--card-bg);
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
display: grid;
|
||||
overflow: hidden;
|
||||
}
|
||||
.character img {
|
||||
border-radius: 4px;
|
||||
width: 100%;
|
||||
}
|
||||
.characters {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: $unit-half;
|
||||
}
|
||||
.protagonist {
|
||||
border-color: transparent;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
@include rep.aspect(32, 66);
|
||||
}
|
||||
.protagonist img {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.protagonist.wind {
|
||||
background: var(--wind-portrait-bg);
|
||||
border-color: var(--wind-bg);
|
||||
}
|
||||
.protagonist.fire {
|
||||
background: var(--fire-portrait-bg);
|
||||
border-color: var(--fire-bg);
|
||||
}
|
||||
.protagonist.water {
|
||||
background: var(--water-portrait-bg);
|
||||
border-color: var(--water-bg);
|
||||
}
|
||||
.protagonist.earth {
|
||||
background: var(--earth-portrait-bg);
|
||||
border-color: var(--earth-bg);
|
||||
}
|
||||
.protagonist.light {
|
||||
background: var(--light-portrait-bg);
|
||||
border-color: var(--light-bg);
|
||||
}
|
||||
.protagonist.dark {
|
||||
background: var(--dark-portrait-bg);
|
||||
border-color: var(--dark-bg);
|
||||
}
|
||||
.protagonist.empty {
|
||||
background: var(--card-bg);
|
||||
|
||||
.characters {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: $unit-half;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
|
||||
.character,
|
||||
.protagonist {
|
||||
aspect-ratio: 16/33;
|
||||
background: var(--placeholder-bg);
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
display: grid;
|
||||
overflow: hidden;
|
||||
|
||||
&.empty {
|
||||
background: var(--placeholder-bg);
|
||||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
}
|
||||
|
||||
.character img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.protagonist {
|
||||
border-color: transparent;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
@include rep.aspect(32, 66);
|
||||
|
||||
img {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&.wind {
|
||||
background: var(--wind-portrait-bg);
|
||||
border-color: var(--wind-bg);
|
||||
}
|
||||
|
||||
&.fire {
|
||||
background: var(--fire-portrait-bg);
|
||||
border-color: var(--fire-bg);
|
||||
}
|
||||
|
||||
&.water {
|
||||
background: var(--water-portrait-bg);
|
||||
border-color: var(--water-bg);
|
||||
}
|
||||
|
||||
&.earth {
|
||||
background: var(--earth-portrait-bg);
|
||||
border-color: var(--earth-bg);
|
||||
}
|
||||
|
||||
&.light {
|
||||
background: var(--light-portrait-bg);
|
||||
border-color: var(--light-bg);
|
||||
}
|
||||
|
||||
&.dark {
|
||||
background: var(--dark-portrait-bg);
|
||||
border-color: var(--dark-bg);
|
||||
}
|
||||
|
||||
&.empty {
|
||||
background: var(--placeholder-bg);
|
||||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,24 +1,26 @@
|
|||
<script lang="ts">
|
||||
import type { PartyView, GridSummonItemView } from '$lib/api/schemas/party'
|
||||
import type { Party, GridSummon } from '$lib/types/api/party'
|
||||
|
||||
export let party: PartyView
|
||||
export let party: Party
|
||||
export let extendedView = false
|
||||
|
||||
const summons = party.summons || []
|
||||
const main = summons.find((s: any) => s?.main || s?.position === -1)
|
||||
const friend = extendedView
|
||||
? summons.find((s: any) => s?.friend || s?.position === -2)
|
||||
const main: GridSummon | undefined = summons.find(
|
||||
(s: GridSummon) => s?.main || s?.position === -1
|
||||
)
|
||||
const friend: GridSummon | undefined = extendedView
|
||||
? summons.find((s: GridSummon) => s?.friend || s?.position === -2)
|
||||
: undefined
|
||||
|
||||
// In standard view: show positions 0-3 (4 summons)
|
||||
// In extended view: show positions 0-5 (6 summons including subauras)
|
||||
const gridLength = extendedView ? 6 : 4
|
||||
const grid = Array.from({ length: gridLength }, (_, i) =>
|
||||
summons.find((s: any) => s?.position === i)
|
||||
summons.find((s: GridSummon) => s?.position === i)
|
||||
)
|
||||
|
||||
function summonImageUrl(s?: any, isMain = false): string {
|
||||
const id = s?.object?.granblueId
|
||||
function summonImageUrl(s?: GridSummon, isMain = false): string {
|
||||
const id = s?.summon?.granblueId
|
||||
if (!id) return ''
|
||||
const folder = isMain ? 'summon-main' : 'summon-grid'
|
||||
return `/images/${folder}/${id}.jpg`
|
||||
|
|
@ -26,24 +28,35 @@
|
|||
</script>
|
||||
|
||||
<div class="rep" class:extended={extendedView}>
|
||||
<div class="mainSummon">
|
||||
{#if main}<img alt="Main Summon" src={summonImageUrl(main, true)} />{/if}
|
||||
<div class="mainSummon" class:empty={!main}>
|
||||
{#if main}<img
|
||||
alt="Main Summon"
|
||||
src={summonImageUrl(main, true)}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>{/if}
|
||||
</div>
|
||||
<ul class="summons">
|
||||
{#each grid as s, i}
|
||||
<li class="summon">
|
||||
{#if s}<img alt="Summon" src={summonImageUrl(s)} />{/if}
|
||||
<li class="summon" class:empty={!s}>
|
||||
{#if s}<img alt="Summon" src={summonImageUrl(s)} loading="lazy" decoding="async" />{/if}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{#if extendedView}
|
||||
<div class="friendSummon">
|
||||
{#if friend}<img alt="Friend Summon" src={summonImageUrl(friend, true)} />{/if}
|
||||
<div class="friendSummon" class:empty={!friend}>
|
||||
{#if friend}<img
|
||||
alt="Friend Summon"
|
||||
src={summonImageUrl(friend, true)}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@use '$src/themes/layout' as *;
|
||||
@use '$src/themes/spacing' as *;
|
||||
@use '$src/themes/rep' as rep;
|
||||
|
||||
|
|
@ -51,60 +64,69 @@
|
|||
width: 100%;
|
||||
height: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr #{rep.$summon-cols-proportion}fr;
|
||||
grid-gap: $unit-half;
|
||||
}
|
||||
gap: calc($unit-half + 1px);
|
||||
|
||||
// Extended view layout: main summon | 6 grid summons | friend summon
|
||||
.rep.extended {
|
||||
display: flex;
|
||||
gap: $unit-half;
|
||||
grid-template-columns: none;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
// Extended view layout: main summon | 6 grid summons | friend summon
|
||||
&.extended {
|
||||
grid-template-columns: auto 1fr auto;
|
||||
|
||||
.summon,
|
||||
.mainSummon,
|
||||
.friendSummon {
|
||||
background: var(--card-bg);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.mainSummon,
|
||||
.friendSummon {
|
||||
@include rep.aspect(56, 97);
|
||||
display: grid;
|
||||
flex: 0 0 auto;
|
||||
min-width: 70px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.mainSummon {
|
||||
@include rep.aspect(56, 97);
|
||||
display: grid;
|
||||
}
|
||||
.summons {
|
||||
display: grid;
|
||||
grid-template-rows: repeat(3, 1fr);
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
column-gap: calc($unit-half + 1px);
|
||||
row-gap: calc($unit * 1.5 - 2px);
|
||||
min-width: 0; /* allow grid to shrink without overflowing */
|
||||
}
|
||||
}
|
||||
|
||||
.extended .mainSummon,
|
||||
.extended .friendSummon {
|
||||
@include rep.aspect(56, 97);
|
||||
display: grid;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.summon,
|
||||
.mainSummon,
|
||||
.friendSummon {
|
||||
background: var(--unit-bg);
|
||||
border-radius: $item-corner-small;
|
||||
overflow: hidden;
|
||||
|
||||
.summons {
|
||||
@include rep.grid(2, 2, $unit-half);
|
||||
}
|
||||
&.empty {
|
||||
background: var(--placeholder-bg);
|
||||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
.extended .summons {
|
||||
display: grid;
|
||||
grid-template-rows: repeat(3, 1fr);
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: $unit-half;
|
||||
flex: 1 1 0;
|
||||
min-width: 0; /* allow grid to shrink without overflowing */
|
||||
}
|
||||
img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.summon {
|
||||
@include rep.aspect(184, 138);
|
||||
display: grid;
|
||||
}
|
||||
.mainSummon {
|
||||
@include rep.aspect(56, 97);
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.summon img,
|
||||
.mainSummon img,
|
||||
.friendSummon img {
|
||||
border-radius: 4px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
.summons {
|
||||
@include rep.grid(2, 2, $unit-half);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.summon {
|
||||
background: var(--unit-bg);
|
||||
border-radius: $item-corner-small;
|
||||
overflow: hidden;
|
||||
min-width: 43px;
|
||||
display: grid;
|
||||
@include rep.aspect(184, 138);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,19 +1,21 @@
|
|||
<script lang="ts">
|
||||
import type { PartyView, GridWeaponItemView } from '$lib/api/schemas/party'
|
||||
import type { Party, GridWeapon } from '$lib/types/api/party'
|
||||
|
||||
export let party: PartyView
|
||||
export let party: Party
|
||||
|
||||
const weapons = party.weapons || []
|
||||
const mainhand: GridWeaponItemView | undefined = weapons.find(
|
||||
(w: any) => w?.mainhand || w?.position === -1
|
||||
const mainhand: GridWeapon | undefined = weapons.find(
|
||||
(w: GridWeapon) => w?.mainhand || w?.position === -1
|
||||
)
|
||||
const grid = Array.from({ length: 9 }, (_, i) =>
|
||||
weapons.find((w: GridWeapon) => w?.position === i)
|
||||
)
|
||||
const grid = Array.from({ length: 9 }, (_, i) => weapons.find((w: any) => w?.position === i))
|
||||
|
||||
function weaponImageUrl(w?: any, isMain = false): string {
|
||||
const id = w?.object?.granblueId
|
||||
function weaponImageUrl(w?: GridWeapon, isMain = false): string {
|
||||
const id = w?.weapon?.granblueId
|
||||
if (!id) return ''
|
||||
const folder = isMain ? 'weapon-main' : 'weapon-grid'
|
||||
const objElement = w?.object?.element
|
||||
const objElement = w?.weapon?.element
|
||||
const instElement = w?.element
|
||||
if (objElement === 0 && instElement) return `/images/${folder}/${id}_${instElement}.jpg`
|
||||
return `/images/${folder}/${id}.jpg`
|
||||
|
|
@ -21,28 +23,96 @@
|
|||
</script>
|
||||
|
||||
<div class="rep">
|
||||
<div class="mainhand">
|
||||
{#if mainhand}<img alt="Mainhand" src={weaponImageUrl(mainhand, true)} />{/if}
|
||||
<div class="mainhand" class:empty={!mainhand}>
|
||||
{#if mainhand}<img
|
||||
alt="Mainhand"
|
||||
src={weaponImageUrl(mainhand, true)}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>{/if}
|
||||
</div>
|
||||
<ul class="weapons">
|
||||
{#each grid as w, i}
|
||||
<li class="weapon">
|
||||
{#if w}<img alt="Weapon" src={weaponImageUrl(w)} />{/if}
|
||||
</li>
|
||||
<div class="weapons">
|
||||
{#each Array.from( { length: 3 }, (_, rowIndex) => grid.slice(rowIndex * 3, (rowIndex + 1) * 3) ) as row, rowIndex}
|
||||
<ul class="weapon-row">
|
||||
{#each row as w, colIndex}
|
||||
<li class="weapon" class:empty={!w}>
|
||||
{#if w}<img alt="Weapon" src={weaponImageUrl(w)} loading="lazy" decoding="async" />{/if}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@use '$src/themes/spacing' as *;
|
||||
@use '$src/themes/rep' as rep;
|
||||
@use '$src/themes/layout' as *;
|
||||
@use '$src/themes/spacing' as *;
|
||||
@use '$src/themes/rep' as rep;
|
||||
|
||||
.rep { width: 100%; height: 100%; display: grid; grid-template-columns: 1fr #{rep.$weapon-cols-proportion}fr; gap: $unit-half; }
|
||||
.rep {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: 1.11fr #{rep.$weapon-cols-proportion}fr;
|
||||
gap: $unit-half;
|
||||
|
||||
.mainhand { background: var(--unit-bg); border-radius: 4px; @include rep.aspect(rep.$weapon-main-w, rep.$weapon-main-h); overflow: hidden; }
|
||||
.mainhand img { width: 100%; height: 100%; object-fit: cover; border-radius: 4px; }
|
||||
.mainhand {
|
||||
background: var(--placeholder-bg);
|
||||
border-radius: $item-corner-small;
|
||||
@include rep.aspect(rep.$weapon-main-w, rep.$weapon-main-h);
|
||||
overflow: hidden;
|
||||
min-height: 115px;
|
||||
|
||||
.weapons { margin: 0; padding: 0; list-style: none; height: 100%; @include rep.grid(3, 3, $unit-half); }
|
||||
.weapon { background: var(--unit-bg); border-radius: 4px; overflow: hidden; @include rep.aspect(rep.$weapon-cell-w, rep.$weapon-cell-h); }
|
||||
.weapon img { width: 100%; height: 100%; object-fit: cover; border-radius: 4px; }
|
||||
&.empty {
|
||||
background: var(--placeholder-bg);
|
||||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.weapons {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $unit-half;
|
||||
height: 100%;
|
||||
|
||||
.weapon-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: $unit-half;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
flex: 1;
|
||||
|
||||
.weapon {
|
||||
background: var(--unit-bg);
|
||||
border-radius: $item-corner-small;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&.empty {
|
||||
background: var(--placeholder-bg);
|
||||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
img {
|
||||
border-radius: $item-corner-small;
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -2,29 +2,35 @@
|
|||
// Keep grid and aspect utilities in one place for consistency.
|
||||
|
||||
@mixin aspect($w, $h) {
|
||||
// Use calc to avoid unit issues and keep intent clear.
|
||||
aspect-ratio: calc(#{$w} / #{$h});
|
||||
// Use calc to avoid unit issues and keep intent clear.
|
||||
aspect-ratio: calc(#{$w} / #{$h});
|
||||
}
|
||||
|
||||
@mixin grid($rows, $cols, $gap) {
|
||||
display: grid;
|
||||
grid-template-rows: repeat(#{$rows}, 1fr);
|
||||
grid-template-columns: repeat(#{$cols}, 1fr);
|
||||
gap: $gap;
|
||||
display: grid;
|
||||
grid-template-rows: repeat(#{$rows}, 1fr);
|
||||
grid-template-columns: repeat(#{$cols}, 1fr);
|
||||
gap: $gap;
|
||||
}
|
||||
|
||||
// Common proportions and ratios for reps
|
||||
// Chosen to match legacy visual geometry without redesign.
|
||||
$rep-body-ratio: 1.95; // width / height for GridRep body; ensures no content cut-off
|
||||
$rep-body-ratio: 2.03; // width / height for GridRep body; ensures no content cut-off
|
||||
|
||||
// Column proportions for weapon/summon layouts
|
||||
$weapon-cols-proportion: 3.55; // mainhand : grid width proportion (1 : 3.55)
|
||||
$summon-cols-proportion: 2.25; // main : grid width proportion (1 : 2.25)
|
||||
|
||||
// Aspect pairs (w, h) for key cells
|
||||
$weapon-main-w: 73; $weapon-main-h: 153;
|
||||
$weapon-cell-w: 280; $weapon-cell-h: 160;
|
||||
$summon-main-w: 56; $summon-main-h: 97;
|
||||
$summon-cell-w: 184; $summon-cell-h: 138;
|
||||
$char-protag-w: 32; $char-protag-h: 66;
|
||||
$char-cell-w: 16; $char-cell-h: 33;
|
||||
$weapon-main-w: 73;
|
||||
$weapon-main-h: 153;
|
||||
$weapon-cell-w: 280;
|
||||
$weapon-cell-h: 160;
|
||||
$summon-main-w: 56;
|
||||
$summon-main-h: 97;
|
||||
$summon-cell-w: 184;
|
||||
$summon-cell-h: 138;
|
||||
$char-protag-w: 32;
|
||||
$char-protag-h: 66;
|
||||
$char-cell-w: 16;
|
||||
$char-cell-h: 33;
|
||||
|
|
|
|||
Loading…
Reference in a new issue