mirror of
https://github.com/MewoLab/AquaDX.git
synced 2026-02-08 10:17:27 +08:00
[+] Chuni Userbox with Assets
Co-authored-by: split / May <split@split.pet>
This commit is contained in:
@@ -16,6 +16,14 @@
|
||||
import { filter } from "d3";
|
||||
import { coverNotFound } from "../../libs/ui";
|
||||
|
||||
import { userboxFileProcess, ddsDB, initializeDb } from "../../libs/userbox/userbox"
|
||||
|
||||
import ChuniPenguinComponent from "./userbox/ChuniPenguin.svelte"
|
||||
import ChuniUserplateComponent from "./userbox/ChuniUserplate.svelte";
|
||||
|
||||
import useLocalStorage from "../../libs/hooks/useLocalStorage.svelte";
|
||||
import { DDS } from "../../libs/userbox/dds";
|
||||
|
||||
let user: AquaNetUser
|
||||
let [loading, error, submitting, preview] = [true, "", "", ""]
|
||||
let changed: string[] = [];
|
||||
@@ -26,7 +34,7 @@
|
||||
let iKinds = { namePlate: 1, frame: 2, trophy: 3, mapIcon: 8, systemVoice: 9, avatarAccessory: 11 }
|
||||
// In userbox: 'nameplateId', 'frameId', 'trophyId', 'mapIconId', 'voiceId', 'avatar{Wear/Head/Face/Skin/Item/Front/Back}'
|
||||
let userbox: UserBox
|
||||
let avatarKinds = ['Wear', 'Head', 'Face', 'Skin', 'Item', 'Front', 'Back']
|
||||
let avatarKinds = ['Wear', 'Head', 'Face', 'Skin', 'Item', 'Front', 'Back'] as const
|
||||
// iKey should match allItems keys, and ubKey should match userbox keys
|
||||
let userItems: { iKey: string, ubKey: keyof UserBox, items: UserItem[] }[] = []
|
||||
|
||||
@@ -83,6 +91,40 @@
|
||||
user = u
|
||||
return fetchData()
|
||||
}).catch((e) => { loading = false; error = e.message });
|
||||
|
||||
let DDSreader: DDS | undefined;
|
||||
|
||||
let USERBOX_PROGRESS = 0;
|
||||
let USERBOX_SETUP_RUN = false;
|
||||
let USERBOX_SETUP_TEXT = t("userbox.new.setup");
|
||||
|
||||
let USERBOX_ENABLED = useLocalStorage("userboxNew", false);
|
||||
let USERBOX_INSTALLED = false;
|
||||
let USERBOX_SUPPORT = "webkitGetAsEntry" in DataTransferItem.prototype;
|
||||
|
||||
type OnlyNumberPropsOf<T extends Record<string, any>> = {[Prop in keyof T as (T[Prop] extends number ? Prop : never)]: T[Prop]}
|
||||
let userboxSelected: keyof OnlyNumberPropsOf<UserBox> = "avatarWear";
|
||||
const userboxNewOptions = ["systemVoice", "frame", "trophy", "mapIcon"]
|
||||
|
||||
async function userboxSafeDrop(event: Event & { currentTarget: EventTarget & HTMLInputElement; }) {
|
||||
if (!event.target) return null;
|
||||
let input = event.target as HTMLInputElement;
|
||||
let folder = input.webkitEntries[0];
|
||||
error = await userboxFileProcess(folder, (progress: number, progressString: string) => {
|
||||
USERBOX_SETUP_TEXT = progressString;
|
||||
USERBOX_PROGRESS = progress;
|
||||
}) ?? "";
|
||||
}
|
||||
|
||||
indexedDB.databases().then(async (dbi) => {
|
||||
let databaseExists = dbi.some(db => db.name == "userboxChusanDDS");
|
||||
if (databaseExists) {
|
||||
await initializeDb();
|
||||
DDSreader = new DDS(ddsDB);
|
||||
USERBOX_INSTALLED = databaseExists;
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<StatusOverlays {error} loading={loading || !!submitting} />
|
||||
@@ -91,42 +133,123 @@
|
||||
<h2>{t("userbox.header.general")}</h2>
|
||||
<GameSettingFields game="chu3"/>
|
||||
<h2>{t("userbox.header.userbox")}</h2>
|
||||
<div class="fields">
|
||||
{#each userItems as { iKey, ubKey, items }, i}
|
||||
<div class="field">
|
||||
<label for={ubKey}>{ts(`userbox.${ubKey}`)}</label>
|
||||
<div>
|
||||
<select bind:value={userbox[ubKey]} id={ubKey} on:change={() => changed = [...changed, ubKey]}>
|
||||
{#each items as option}
|
||||
<option value={option.itemId}>{allItems[iKey][option.itemId]?.name || `(unknown ${option.itemId})`}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{#if changed.includes(ubKey)}
|
||||
<button transition:slide={{axis: "x"}} on:click={() => submit(ubKey)} disabled={!!submitting}>
|
||||
{t("settings.profile.save")}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{#if HAS_USERBOX_ASSETS}
|
||||
<h2>{t("userbox.header.preview")}</h2>
|
||||
<p class="notice">{t("userbox.preview.notice")}</p>
|
||||
<input bind:value={preview} placeholder={t("userbox.preview.url")}/>
|
||||
{#if preview}
|
||||
<div class="preview">
|
||||
{#each userItems.filter(v => v.iKey != 'trophy' && v.iKey != 'systemVoice') as { iKey, ubKey, items }, i}
|
||||
{#if !USERBOX_ENABLED.value || !USERBOX_INSTALLED}
|
||||
<div class="fields">
|
||||
{#each userItems as { iKey, ubKey, items }, i}
|
||||
<div class="field">
|
||||
<label for={ubKey}>{ts(`userbox.${ubKey}`)}</label>
|
||||
<div>
|
||||
<span>{ts(`userbox.${ubKey}`)}</span>
|
||||
<img src={`${preview}/${iKey}/${userbox[ubKey].toString().padStart(8, '0')}.png`} alt="" on:error={coverNotFound} />
|
||||
<select bind:value={userbox[ubKey]} id={ubKey} on:change={() => changed = [...changed, ubKey]}>
|
||||
{#each items as option}
|
||||
<option value={option.itemId}>{allItems[iKey][option.itemId]?.name || `(unknown ${option.itemId})`}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{#if changed.includes(ubKey)}
|
||||
<button transition:slide={{axis: "x"}} on:click={() => submit(ubKey)} disabled={!!submitting}>
|
||||
{t("settings.profile.save")}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="chuni-userbox-container">
|
||||
<ChuniUserplateComponent on:click={() => userboxSelected = "nameplateId"} chuniCharacter={userbox.characterId} chuniLevel={userbox.level} chuniRating={userbox.playerRating / 100}
|
||||
chuniNameplate={userbox.nameplateId} chuniName={userbox.userName} chuniTrophyName={allItems.trophy[userbox.trophyId].name}></ChuniUserplateComponent>
|
||||
<ChuniPenguinComponent classPassthrough="chuni-penguin-float" chuniWear={userbox.avatarWear} chuniHead={userbox.avatarHead} chuniBack={userbox.avatarBack}
|
||||
chuniFront={userbox.avatarFront} chuniFace={userbox.avatarFace} chuniItem={userbox.avatarItem}
|
||||
chuniSkin={userbox.avatarSkin}></ChuniPenguinComponent>
|
||||
</div>
|
||||
<div class="chuni-userbox-row">
|
||||
{#each avatarKinds as avatarKind}
|
||||
{#await DDSreader?.getFile(`avatarAccessoryThumbnail:${userbox[`avatar${avatarKind}`].toString().padStart(8, "0")}`) then imageURL}
|
||||
<button on:click={() => userboxSelected = `avatar${avatarKind}`}>
|
||||
<img src={imageURL} class={userboxSelected == `avatar${avatarKind}` ? "focused" : ""} alt={allItems.avatarAccessory[userbox[`avatar${avatarKind}`]].name} title={allItems.avatarAccessory[userbox[`avatar${avatarKind}`]].name}>
|
||||
</button>
|
||||
{/await}
|
||||
{/each}
|
||||
</div>
|
||||
<div class="chuni-userbox">
|
||||
{#if userboxSelected == "nameplateId"}
|
||||
{#each userItems.find(f => f.ubKey == "nameplateId")?.items ?? [] as item}
|
||||
{#await DDSreader?.getFile(`nameplate:${item.itemId.toString().padStart(8, "0")}`) then imageURL}
|
||||
<button on:click={() => {userbox[userboxSelected] = item.itemId; submit(userboxSelected)}}>
|
||||
<img src={imageURL} alt={allItems.namePlate[item.itemId].name} title={allItems.namePlate[item.itemId].name}>
|
||||
</button>
|
||||
{/await}
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
{#each userItems.find(f => f.ubKey == userboxSelected)?.items ?? [] as item}
|
||||
{#await DDSreader?.getFile(`avatarAccessoryThumbnail:${item.itemId.toString().padStart(8, "0")}`) then imageURL}
|
||||
<button on:click={() => {userbox[userboxSelected] = item.itemId; submit(userboxSelected)}}>
|
||||
<img src={imageURL} alt={allItems.avatarAccessory[item.itemId].name} title={allItems.avatarAccessory[item.itemId].name}>
|
||||
</button>
|
||||
{/await}
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
<div class="fields">
|
||||
{#each userItems.filter(i => userboxNewOptions.includes(i.iKey)) as { iKey, ubKey, items }, i}
|
||||
<div class="field">
|
||||
<label for={ubKey}>{ts(`userbox.${ubKey}`)}</label>
|
||||
<div>
|
||||
<select bind:value={userbox[ubKey]} id={ubKey} on:change={() => changed = [...changed, ubKey]}>
|
||||
{#each items as option}
|
||||
<option value={option.itemId}>{allItems[iKey][option.itemId]?.name || `(unknown ${option.itemId})`}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{#if changed.includes(ubKey)}
|
||||
<button transition:slide={{axis: "x"}} on:click={() => submit(ubKey)} disabled={!!submitting}>
|
||||
{t("settings.profile.save")}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{#if USERBOX_INSTALLED}
|
||||
<!-- god this is a mess but idgaf atp -->
|
||||
<div class="field boolean" style:margin-top="1em">
|
||||
<input type="checkbox" bind:checked={USERBOX_ENABLED.value} id="newUserbox">
|
||||
<label for="newUserbox">
|
||||
<span class="name">{t("userbox.new.activate")}</span>
|
||||
<span class="desc">{t(`userbox.new.activate_desc`)}</span>
|
||||
</label>
|
||||
</div>
|
||||
{/if}
|
||||
{#if USERBOX_SUPPORT}
|
||||
<p>
|
||||
<button on:click={() => USERBOX_SETUP_RUN = !USERBOX_SETUP_RUN}>{t(!USERBOX_INSTALLED ? `userbox.new.activate_first` : `userbox.new.activate_update`)}</button>
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if USERBOX_SETUP_RUN && !error}
|
||||
<div class="overlay" transition:fade>
|
||||
<div>
|
||||
<h2>{t('userbox.new.name')}</h2>
|
||||
<span>{USERBOX_SETUP_TEXT}</span>
|
||||
<div class="actions">
|
||||
{#if USERBOX_PROGRESS != 0}
|
||||
<div class="progress">
|
||||
<div class="progress-bar" style="width: {USERBOX_PROGRESS}%"></div>
|
||||
</div>
|
||||
{:else}
|
||||
<button class="drop-btn">
|
||||
<input type="file" on:input={userboxSafeDrop} on:click={e => e.preventDefault()}>
|
||||
{t('userbox.new.drop')}
|
||||
</button>
|
||||
<button on:click={() => USERBOX_SETUP_RUN = false}>
|
||||
{t('back')}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="sass">
|
||||
@use "../../vars"
|
||||
@@ -134,6 +257,7 @@
|
||||
input
|
||||
width: 100%
|
||||
|
||||
|
||||
h2
|
||||
margin-bottom: 0.5rem
|
||||
|
||||
@@ -141,6 +265,36 @@ p.notice
|
||||
opacity: 0.6
|
||||
margin-top: 0
|
||||
|
||||
.progress
|
||||
width: 100%
|
||||
height: 10px
|
||||
box-shadow: 0 0 1px 1px vars.$ov-lighter
|
||||
border-radius: 25px
|
||||
margin-bottom: 15px
|
||||
overflow: hidden
|
||||
|
||||
.progress-bar
|
||||
background: #b3c6ff
|
||||
height: 100%
|
||||
border-radius: 25px
|
||||
|
||||
|
||||
.drop-btn
|
||||
position: relative
|
||||
width: 100%
|
||||
aspect-ratio: 3
|
||||
background: transparent
|
||||
box-shadow: 0 0 1px 1px vars.$ov-lighter
|
||||
margin-bottom: 1em
|
||||
|
||||
> input
|
||||
position: absolute
|
||||
top: 0
|
||||
left: 0
|
||||
width: 100%
|
||||
height: 100%
|
||||
opacity: 0
|
||||
|
||||
.preview
|
||||
margin-top: 32px
|
||||
display: flex
|
||||
@@ -202,4 +356,79 @@ p.notice
|
||||
|
||||
> select
|
||||
flex: 1
|
||||
|
||||
|
||||
.field.boolean
|
||||
display: flex
|
||||
flex-direction: row
|
||||
align-items: center
|
||||
gap: 1rem
|
||||
width: auto
|
||||
|
||||
input
|
||||
width: auto
|
||||
aspect-ratio: 1 / 1
|
||||
|
||||
label
|
||||
display: flex
|
||||
flex-direction: column
|
||||
max-width: max-content
|
||||
|
||||
.desc
|
||||
opacity: 0.6
|
||||
|
||||
/* AquaBox */
|
||||
|
||||
.chuni-userbox-row
|
||||
width: 100%
|
||||
display: flex
|
||||
|
||||
button
|
||||
padding: 0
|
||||
margin: 0
|
||||
width: 100%
|
||||
flex: 0 1 100%
|
||||
background: none
|
||||
aspect-ratio: 1
|
||||
|
||||
img
|
||||
width: 100%
|
||||
filter: brightness(50%)
|
||||
|
||||
&.focused
|
||||
filter: brightness(75%)
|
||||
|
||||
.chuni-userbox
|
||||
width: calc(100% - 20px)
|
||||
height: 350px
|
||||
|
||||
display: flex
|
||||
flex-direction: row
|
||||
flex-wrap: wrap
|
||||
padding: 10px
|
||||
background: vars.$c-bg
|
||||
border-radius: 16px
|
||||
overflow-y: auto
|
||||
margin-bottom: 15px
|
||||
justify-content: center
|
||||
|
||||
button
|
||||
padding: 0
|
||||
margin: 0
|
||||
width: 20%
|
||||
align-self: flex-start
|
||||
background: none
|
||||
aspect-ratio: 1
|
||||
|
||||
img
|
||||
width: 100%
|
||||
|
||||
.chuni-userbox-container
|
||||
display: flex
|
||||
align-items: center
|
||||
justify-content: center
|
||||
|
||||
@media (max-width: 1000px)
|
||||
.chuni-userbox-container
|
||||
flex-wrap: wrap
|
||||
</style>
|
||||
|
||||
165
AquaNet/src/components/settings/userbox/ChuniPenguin.svelte
Normal file
165
AquaNet/src/components/settings/userbox/ChuniPenguin.svelte
Normal file
@@ -0,0 +1,165 @@
|
||||
<script lang="ts">
|
||||
import { DDS } from "../../../libs/userbox/dds"
|
||||
import { ddsDB } from "../../../libs/userbox/userbox"
|
||||
|
||||
const DDSreader = new DDS(ddsDB);
|
||||
|
||||
export var chuniWear = 1100001;
|
||||
export var chuniHead = 1200001;
|
||||
export var chuniFace = 1300001;
|
||||
export var chuniSkin = 1400001;
|
||||
export var chuniItem = 1500001;
|
||||
export var chuniFront = 1600001;
|
||||
export var chuniBack = 1700001;
|
||||
export var classPassthrough: string = ``
|
||||
</script>
|
||||
<div class="chuni-penguin {classPassthrough}">
|
||||
<div class="chuni-penguin-body">
|
||||
<!-- Body -->
|
||||
{#await DDSreader.getFileFromSheet(`avatarAccessory:${chuniSkin.toString().padStart(8, "0")}`, 0, 0, 256, 400, 0.75) then imageURL}
|
||||
<img class="chuni-penguin-skin" src={imageURL} alt="Body">
|
||||
{/await}
|
||||
|
||||
<!-- Face -->
|
||||
{#await DDSreader.getFileFromSheet("surfboard:CHU_UI_Common_Avatar_face_00.dds", 0, 0, 225, 150, 0.75) then imageURL}
|
||||
<img class="chuni-penguin-eyes chuni-penguin-accessory" src={imageURL} alt="Eyes">
|
||||
{/await}
|
||||
{#await DDSreader.getFileFromSheet("surfboard:CHU_UI_Common_Avatar_body_00.dds", 86, 103, 96, 43, 0.75) then imageURL}
|
||||
<img class="chuni-penguin-beak chuni-penguin-accessory" src={imageURL} alt="Beak">
|
||||
{/await}
|
||||
|
||||
<!-- Arms (surfboard) -->
|
||||
{#await DDSreader.getFileFromSheet("surfboard:CHU_UI_Common_Avatar_body_00.dds", 0, 0, 85, 160, 0.75) then imageURL}
|
||||
<img class="chuni-penguin-arm-left chuni-penguin-arm" src={imageURL} alt="Left Arm">
|
||||
{/await}
|
||||
{#await DDSreader.getFileFromSheet("surfboard:CHU_UI_Common_Avatar_body_00.dds", 0, 0, 85, 160, 0.75) then imageURL}
|
||||
<img class="chuni-penguin-arm-right chuni-penguin-arm" src={imageURL} alt="Right Arm">
|
||||
{/await}
|
||||
|
||||
<!-- Wear -->
|
||||
{#await DDSreader.getFileScaled(`avatarAccessory:${chuniWear.toString().padStart(8, "0")}`, 0.75, `avatarAccessory:01100001`) then imageURL}
|
||||
<img class="chuni-penguin-wear chuni-penguin-accessory" src={imageURL} alt="Wear">
|
||||
{/await}
|
||||
|
||||
<!-- Head -->
|
||||
{#await DDSreader.getFileScaled(`avatarAccessory:${chuniHead.toString().padStart(8, "0")}`, 0.75, `avatarAccessory:01200001`) then imageURL}
|
||||
<img class="chuni-penguin-head chuni-penguin-accessory" src={imageURL} alt="Head">
|
||||
{/await}
|
||||
{#if chuniHead == 1200001}
|
||||
<!-- If wearing original hat, add the feather and attachment -->
|
||||
{#await DDSreader.getFileFromSheet("surfboard:CHU_UI_Common_Avatar_body_00.dds", 104, 153, 57, 58, 0.75) then imageURL}
|
||||
<img class="chuni-penguin-head-2 chuni-penguin-accessory" src={imageURL} alt="Head2">
|
||||
{/await}
|
||||
{#await DDSreader.getFileFromSheet("surfboard:CHU_UI_Common_Avatar_body_00.dds", 5, 160, 100, 150, 0.75) then imageURL}
|
||||
<img class="chuni-penguin-head-3 chuni-penguin-accessory" src={imageURL} alt="Head3">
|
||||
{/await}
|
||||
{/if}
|
||||
|
||||
<!-- Face (Accessory) -->
|
||||
{#await DDSreader.getFileScaled(`avatarAccessory:${chuniFace.toString().padStart(8, "0")}`, 0.75, `avatarAccessory:01300001`) then imageURL}
|
||||
<img class="chuni-penguin-face-accessory chuni-penguin-accessory" src={imageURL} alt="Face (Accessory)">
|
||||
{/await}
|
||||
|
||||
<!-- Item -->
|
||||
{#await DDSreader.getFileScaled(`avatarAccessory:${chuniItem.toString().padStart(8, "0")}`, 0.75, `avatarAccessory:01500001`) then imageURL}
|
||||
<img class="chuni-penguin-item chuni-penguin-accessory" src={imageURL} alt="Item">
|
||||
{/await}
|
||||
|
||||
<!-- Front -->
|
||||
{#await DDSreader.getFileScaled(`avatarAccessory:${chuniFront.toString().padStart(8, "0")}`, 0.75) then imageURL}
|
||||
<img class="chuni-penguin-front chuni-penguin-accessory" src={imageURL} alt="Front">
|
||||
{/await}
|
||||
|
||||
<!-- Back -->
|
||||
{#await DDSreader.getFileScaled(`avatarAccessory:${chuniBack.toString().padStart(8, "0")}`, 0.75) then imageURL}
|
||||
<img class="chuni-penguin-back chuni-penguin-accessory" src={imageURL} alt="Back">
|
||||
{/await}
|
||||
</div>
|
||||
<div class="chuni-penguin-feet">
|
||||
<!-- Feet -->
|
||||
{#await DDSreader.getFileFromSheet(`avatarAccessory:${chuniSkin.toString().padStart(8, "0")}`, 0, 410, 167, 80, 0.75) then imageURL}
|
||||
<img src={imageURL} alt="Feet">
|
||||
{/await}
|
||||
</div>
|
||||
</div>
|
||||
<!-- Truly sorry for the horrors below -->
|
||||
<style lang="sass">
|
||||
@keyframes chuniPenguinBodyBob
|
||||
0%
|
||||
transform: translate(-50%, 0%) translate(0%, -50%)
|
||||
50%
|
||||
transform: translate(-50%, 10px) translate(0%, -50%)
|
||||
100%
|
||||
transform: translate(-50%, 0%) translate(0%, -50%)
|
||||
@keyframes chuniPenguinArmLeft
|
||||
0%
|
||||
transform: translate(-50%, 0) rotate(-2deg)
|
||||
50%
|
||||
transform: translate(-50%, 0) rotate(2deg)
|
||||
100%
|
||||
transform: translate(-50%, 0) rotate(-2deg)
|
||||
@keyframes chuniPenguinArmRight
|
||||
0%
|
||||
transform: translate(-50%, 0) scaleX(-1) rotate(-2deg)
|
||||
50%
|
||||
transform: translate(-50%, 0) scaleX(-1) rotate(2deg)
|
||||
100%
|
||||
transform: translate(-50%, 0) scaleX(-1) rotate(-2deg)
|
||||
|
||||
img
|
||||
-webkit-user-drag: none
|
||||
|
||||
.chuni-penguin
|
||||
height: 512px
|
||||
aspect-ratio: 1/2
|
||||
position: relative
|
||||
|
||||
.chuni-penguin-body, .chuni-penguin-feet
|
||||
transform: translate(-50%, -50%)
|
||||
position: absolute
|
||||
left: 50%
|
||||
|
||||
.chuni-penguin-body
|
||||
top: 50%
|
||||
z-index: 1
|
||||
animation: chuniPenguinBodyBob 2s infinite cubic-bezier(0.45, 0, 0.55, 1)
|
||||
.chuni-penguin-feet
|
||||
top: 82.5%
|
||||
z-index: 0
|
||||
|
||||
.chuni-penguin-arm
|
||||
transform-origin: 95% 10%
|
||||
position: absolute
|
||||
top: 40%
|
||||
.chuni-penguin-arm-left
|
||||
left: 0%
|
||||
animation: chuniPenguinArmLeft 1.5s infinite cubic-bezier(0.45, 0, 0.55, 1)
|
||||
.chuni-penguin-arm-right
|
||||
left: 70%
|
||||
animation: chuniPenguinArmRight 1.5s infinite cubic-bezier(0.45, 0, 0.55, 1)
|
||||
|
||||
.chuni-penguin-accessory
|
||||
transform: translate(-50%, -50%)
|
||||
position: absolute
|
||||
top: 50%
|
||||
left: 50%
|
||||
|
||||
.chuni-penguin-eyes
|
||||
top: 22.5%
|
||||
.chuni-penguin-beak
|
||||
top: 29.5%
|
||||
.chuni-penguin-wear
|
||||
top: 57.5%
|
||||
.chuni-penguin-head
|
||||
top: 7.5%
|
||||
z-index: 10
|
||||
.chuni-penguin-head-2
|
||||
top: 12.5%
|
||||
.chuni-penguin-head-3
|
||||
top: -12.5%
|
||||
.chuni-penguin-face-accessory
|
||||
top: 27.5%
|
||||
.chuni-penguin-back
|
||||
z-index: -1
|
||||
|
||||
</style>
|
||||
137
AquaNet/src/components/settings/userbox/ChuniUserplate.svelte
Normal file
137
AquaNet/src/components/settings/userbox/ChuniUserplate.svelte
Normal file
@@ -0,0 +1,137 @@
|
||||
<script lang="ts">
|
||||
import { DDS } from "../../../libs/userbox/dds"
|
||||
import { ddsDB } from "../../../libs/userbox/userbox"
|
||||
|
||||
const DDSreader = new DDS(ddsDB);
|
||||
|
||||
export var chuniLevel: number = 1
|
||||
export var chuniName: string = "CHUNITHM"
|
||||
export var chuniRating: number = 1.23
|
||||
export var chuniNameplate: number = 1
|
||||
export var chuniCharacter: number = 0
|
||||
export var chuniTrophyName: string = "NEWCOMER"
|
||||
</script>
|
||||
{#await DDSreader?.getFile(`nameplate:${chuniNameplate.toString().padStart(8, "0")}`) then nameplateURL}
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div on:click class="chuni-nameplate" style:background={`url(${nameplateURL})`}>
|
||||
{#await DDSreader?.getFile(`characterThumbnail:${chuniCharacter.toString().padStart(6, "0")}`) then characterThumbnailURL}
|
||||
<img class="chuni-character" src={characterThumbnailURL} alt="Character">
|
||||
{/await}
|
||||
{#await DDSreader?.getFileFromSheet("surfboard:CHU_UI_title_rank_00_v10.dds", 5, 5 + (75 * 2), 595, 64) then trophyURL}
|
||||
<div class="chuni-trophy">
|
||||
{chuniTrophyName}
|
||||
<img src={trophyURL} class="chuni-trophy-bg" alt="Trophy">
|
||||
</div>
|
||||
{/await}
|
||||
<div class="chuni-user-info">
|
||||
<div class="chuni-user-name">
|
||||
<span>
|
||||
Lv.
|
||||
<span class="chuni-user-level">
|
||||
{chuniLevel}
|
||||
</span>
|
||||
</span>
|
||||
<span class="chuni-user-name-text">
|
||||
{chuniName}
|
||||
</span>
|
||||
</div>
|
||||
<div class="chuni-user-rating">
|
||||
RATING
|
||||
<span class="chuni-user-rating-number">
|
||||
{chuniRating}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/await}
|
||||
<style lang="sass">
|
||||
@use "../../../vars"
|
||||
.chuni-nameplate
|
||||
width: 576px
|
||||
height: 228px
|
||||
position: relative
|
||||
font-size: 16px
|
||||
/* Overlap penguin avatar when put side to side */
|
||||
z-index: 2
|
||||
cursor: pointer
|
||||
|
||||
.chuni-trophy
|
||||
width: 410px
|
||||
height: 45px
|
||||
background-position: center
|
||||
background-size: cover
|
||||
color: black
|
||||
|
||||
display: flex
|
||||
justify-content: center
|
||||
align-items: center
|
||||
position: absolute
|
||||
right: 25px
|
||||
top: 40px
|
||||
|
||||
font-size: 1.15em
|
||||
font-family: sans-serif
|
||||
font-weight: bold
|
||||
|
||||
z-index: 1
|
||||
text-shadow: 0 1px white
|
||||
|
||||
img
|
||||
width: 100%
|
||||
height: 100%
|
||||
position: absolute
|
||||
z-index: -1
|
||||
|
||||
.chuni-character
|
||||
position: absolute
|
||||
top: 87px
|
||||
right: 25px
|
||||
width: 82px
|
||||
aspect-ratio: 1
|
||||
box-shadow: 0 0 1px 1px white
|
||||
background: #efefef
|
||||
|
||||
.chuni-user-info
|
||||
height: 82px
|
||||
width: 320px
|
||||
position: absolute
|
||||
top: 87px
|
||||
right: 110px
|
||||
background: #fff9
|
||||
border-radius: 1px
|
||||
box-shadow: 0 0 1px 1px #ccc
|
||||
display: flex
|
||||
flex-direction: column
|
||||
|
||||
.chuni-user-name, .chuni-user-rating
|
||||
margin: 0 4px
|
||||
display: flex
|
||||
align-items: center
|
||||
color: black
|
||||
font-family: sans-serif
|
||||
font-weight: bold
|
||||
|
||||
.chuni-user-name
|
||||
flex: 1 0 65%
|
||||
box-shadow: 0 1px 0 #ccc
|
||||
|
||||
.chuni-user-level
|
||||
font-size: 2em
|
||||
margin-left: 10px
|
||||
|
||||
.chuni-user-name-text
|
||||
margin-left: auto
|
||||
font-size: 2em
|
||||
|
||||
.chuni-user-rating
|
||||
flex: 1 0 35%
|
||||
font-size: 0.875em
|
||||
text-shadow: #333 1px 1px, #333 1px -1px, #333 -1px 1px, #333 -1px -1px
|
||||
color: #ddf
|
||||
|
||||
.chuni-user-rating-number
|
||||
font-size: 1.5em
|
||||
margin-left: 10px
|
||||
|
||||
</style>
|
||||
Reference in New Issue
Block a user