mirror of https://github.com/hykilpikonna/AquaDX
Merge branch 'v1-dev' into pr/119
commit
338819416f
Binary file not shown.
Binary file not shown.
|
@ -7,8 +7,10 @@
|
||||||
import { USER } from "./libs/sdk";
|
import { USER } from "./libs/sdk";
|
||||||
import type { AquaNetUser } from "./libs/generalTypes";
|
import type { AquaNetUser } from "./libs/generalTypes";
|
||||||
import Settings from "./pages/User/Settings.svelte";
|
import Settings from "./pages/User/Settings.svelte";
|
||||||
import { pfp } from "./libs/ui"
|
|
||||||
import MaiPhoto from "./pages/MaiPhoto.svelte";
|
import MaiPhoto from "./pages/MaiPhoto.svelte";
|
||||||
|
import { pfp, tooltip } from "./libs/ui"
|
||||||
|
import { ANNOUNCEMENT } from "./libs/config";
|
||||||
|
import { t } from "./libs/i18n";
|
||||||
|
|
||||||
console.log(`%c
|
console.log(`%c
|
||||||
┏━┓ ┳━┓━┓┏━
|
┏━┓ ┳━┓━┓┏━
|
||||||
|
@ -37,13 +39,18 @@
|
||||||
<span>AquaNet</span>
|
<span>AquaNet</span>
|
||||||
</a>
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
<a href="/home">home</a>
|
{#if ANNOUNCEMENT}
|
||||||
<div on:click={() => alert("Coming soon™")} on:keydown={e => e.key === "Enter" && alert("Coming soon™")}
|
<div class="announcement">
|
||||||
role="button" tabindex="0">maps</div>
|
<strong>{t('navigation.notice')}</strong>: {ANNOUNCEMENT}
|
||||||
<a href="/ranking">rankings</a>
|
</div>
|
||||||
|
{/if}
|
||||||
|
<a href="/home">{t('navigation.home').toLowerCase()}</a>
|
||||||
|
<!-- <div on:click={() => alert("Coming soon™")} on:keydown={e => e.key === "Enter" && alert("Coming soon™")}
|
||||||
|
role="button" tabindex="0">{t('navigation.maps').toLowerCase()}</div> -->
|
||||||
|
<a href="/ranking">{t('navigation.rankings').toLowerCase()}</a>
|
||||||
<a href="/pictures">pictures</a>
|
<a href="/pictures">pictures</a>
|
||||||
{#if me}
|
{#if me}
|
||||||
<a href="/u/{me.username}">
|
<a href="/u/{me.username}" use:tooltip={t('navigation.profile')}>
|
||||||
<img alt="profile" class="pfp" use:pfp={me}/>
|
<img alt="profile" class="pfp" use:pfp={me}/>
|
||||||
</a>
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -81,6 +88,22 @@
|
||||||
border-radius: vars.$border-radius
|
border-radius: vars.$border-radius
|
||||||
object-fit: cover
|
object-fit: cover
|
||||||
|
|
||||||
|
.announcement
|
||||||
|
position: absolute
|
||||||
|
left: 50%
|
||||||
|
transform: translate(-50%, 0)
|
||||||
|
top: 0
|
||||||
|
width: 50%
|
||||||
|
height: 100%
|
||||||
|
display: flex
|
||||||
|
justify-content: center
|
||||||
|
align-content: center
|
||||||
|
z-index: -1
|
||||||
|
background: linear-gradient(90deg, #6f0f0f00 0%, vars.$c-shadow 50%, #6f0f0f00 100%)
|
||||||
|
font-size: 1.125em
|
||||||
|
text-decoration: none !important
|
||||||
|
color: inherit !important
|
||||||
|
|
||||||
.pfp
|
.pfp
|
||||||
width: 2rem
|
width: 2rem
|
||||||
height: 2rem
|
height: 2rem
|
||||||
|
|
|
@ -134,7 +134,7 @@ button.icon
|
||||||
.error
|
.error
|
||||||
color: vars.$c-error
|
color: vars.$c-error
|
||||||
|
|
||||||
input
|
input, textarea
|
||||||
border-radius: vars.$border-radius
|
border-radius: vars.$border-radius
|
||||||
border: 1px solid transparent
|
border: 1px solid transparent
|
||||||
padding: 0.6em 1.2em
|
padding: 0.6em 1.2em
|
||||||
|
@ -144,6 +144,10 @@ input
|
||||||
background-color: vars.$ov-lighter
|
background-color: vars.$ov-lighter
|
||||||
transition: vars.$transition
|
transition: vars.$transition
|
||||||
box-sizing: border-box
|
box-sizing: border-box
|
||||||
|
resize: none
|
||||||
|
|
||||||
|
textarea
|
||||||
|
height: 5em
|
||||||
|
|
||||||
// Dropdown
|
// Dropdown
|
||||||
select
|
select
|
||||||
|
@ -314,6 +318,9 @@ main.content
|
||||||
|
|
||||||
max-width: 400px
|
max-width: 400px
|
||||||
|
|
||||||
|
.aqua-tooltip
|
||||||
|
z-index: 900
|
||||||
|
|
||||||
.no-margin
|
.no-margin
|
||||||
margin: 0
|
margin: 0
|
||||||
|
|
||||||
|
|
|
@ -60,7 +60,7 @@
|
||||||
|
|
||||||
.tooltip
|
.tooltip
|
||||||
position: absolute
|
position: absolute
|
||||||
z-index: 1000
|
z-index: 900
|
||||||
background: white
|
background: white
|
||||||
padding: 10px 16px
|
padding: 10px 16px
|
||||||
border-radius: vars.$border-radius
|
border-radius: vars.$border-radius
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { DDS } from "../../../libs/userbox/dds"
|
import { DDS, type RGB } from "../../../libs/userbox/dds"
|
||||||
import { ddsDB } from "../../../libs/userbox/userbox"
|
import { ddsDB } from "../../../libs/userbox/userbox"
|
||||||
|
|
||||||
const DDSreader = new DDS(ddsDB);
|
const DDSreader = new DDS(ddsDB);
|
||||||
|
@ -15,6 +15,26 @@
|
||||||
let ratingToString = (rating: number) => {
|
let ratingToString = (rating: number) => {
|
||||||
return rating.toFixed(2)
|
return rating.toFixed(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface RatingRange {
|
||||||
|
min: number,
|
||||||
|
offset: number,
|
||||||
|
color?: RGB
|
||||||
|
};
|
||||||
|
// https://en.wikipedia.org/wiki/Chunithm#Rating
|
||||||
|
const ratingColors: RatingRange[] = ([
|
||||||
|
{min: 0.00, offset: 4, color: {r: 0, g: 191, b: 64}},
|
||||||
|
{min: 4.00, offset: 4, color: {r: 255, g: 111, b: 0}},
|
||||||
|
{min: 7.00, offset: 4, color: {r: 255, g: 64, b: 64}},
|
||||||
|
{min: 10.00, offset: 4, color: {r: 147, g: 38, b: 255}},
|
||||||
|
{min: 12.00, offset: 3},
|
||||||
|
{min: 13.25, offset: 2},
|
||||||
|
{min: 14.50, offset: 1},
|
||||||
|
{min: 15.25, offset: 0},
|
||||||
|
{min: 16.00, offset: 5}
|
||||||
|
]).filter(f => f.min <= chuniRating);
|
||||||
|
const ratingDigitOrder = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "."]
|
||||||
|
const ratingColorData = (ratingColors[ratingColors.length - 1] ?? ratingColors[0]);
|
||||||
</script>
|
</script>
|
||||||
{#await DDSreader?.getFile(`nameplate:${chuniNameplate.toString().padStart(8, "0")}`, `nameplate:00000001`) then nameplateURL}
|
{#await DDSreader?.getFile(`nameplate:${chuniNameplate.toString().padStart(8, "0")}`, `nameplate:00000001`) then nameplateURL}
|
||||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||||
|
@ -41,17 +61,36 @@
|
||||||
{chuniName}
|
{chuniName}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="chuni-user-rating">
|
<div class={`chuni-user-rating color-${ratingColorData.color}`}>
|
||||||
RATING
|
|
||||||
<span class="chuni-user-rating-number">
|
{#await DDSreader?.getFileFromSheet("surfboard:CHU_UI_Common_01_v11.dds", 485, 5 + (28 * ratingColorData.offset), 62, 15, undefined, ratingColorData.color) then url}
|
||||||
{ratingToString(chuniRating)}
|
{#if url}
|
||||||
</span>
|
<img src={url} alt="Rating">
|
||||||
|
<span class="chuni-user-rating-number">
|
||||||
|
{#each ratingToString(chuniRating).split("") as digit}
|
||||||
|
{#await DDSreader?.getFileFromSheet("surfboard:CHU_UI_Common_01_v11.dds", 552 + (24 * (ratingDigitOrder.indexOf(digit) ?? 0)), 1 + (28 * ratingColorData.offset), 16, 20, undefined, ratingColorData.color) then url}
|
||||||
|
<img src={url} alt="Rating Digit">
|
||||||
|
{/await}
|
||||||
|
{/each}
|
||||||
|
</span>
|
||||||
|
{:else}
|
||||||
|
RATING
|
||||||
|
<span class="chuni-user-rating-number">
|
||||||
|
{ratingToString(chuniRating)}
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
{/await}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/await}
|
{/await}
|
||||||
<style lang="sass">
|
<style lang="sass">
|
||||||
@use "../../../vars"
|
@use "../../../vars"
|
||||||
|
|
||||||
|
@font-face
|
||||||
|
font-family: "Gothic A1"
|
||||||
|
src: url("/assets/fonts/GothicA1.woff2")
|
||||||
|
|
||||||
.chuni-nameplate
|
.chuni-nameplate
|
||||||
width: 576px
|
width: 576px
|
||||||
height: 228px
|
height: 228px
|
||||||
|
@ -78,7 +117,7 @@
|
||||||
top: 40px
|
top: 40px
|
||||||
|
|
||||||
font-size: 1.15em
|
font-size: 1.15em
|
||||||
font-family: sans-serif
|
font-family: "Gothic A1", sans-serif
|
||||||
font-weight: bold
|
font-weight: bold
|
||||||
|
|
||||||
overflow-x: hidden
|
overflow-x: hidden
|
||||||
|
@ -123,7 +162,7 @@
|
||||||
display: flex
|
display: flex
|
||||||
align-items: center
|
align-items: center
|
||||||
color: black
|
color: black
|
||||||
font-family: sans-serif
|
font-family: "Gothic A1", sans-serif
|
||||||
font-weight: bold
|
font-weight: bold
|
||||||
|
|
||||||
.chuni-user-name
|
.chuni-user-name
|
||||||
|
@ -144,7 +183,7 @@
|
||||||
flex: 1 0 35%
|
flex: 1 0 35%
|
||||||
font-size: 0.875em
|
font-size: 0.875em
|
||||||
text-shadow: #333 1px 1px, #333 1px -1px, #333 -1px 1px, #333 -1px -1px
|
text-shadow: #333 1px 1px, #333 1px -1px, #333 -1px 1px, #333 -1px -1px
|
||||||
color: #ddf
|
color: #fff
|
||||||
|
|
||||||
.chuni-user-rating-number
|
.chuni-user-rating-number
|
||||||
font-size: 1.5em
|
font-size: 1.5em
|
||||||
|
|
|
@ -16,6 +16,8 @@ export const FADE_OUT = { duration: 200 }
|
||||||
export const FADE_IN = { delay: 400 }
|
export const FADE_IN = { delay: 400 }
|
||||||
export const DEFAULT_PFP = '/assets/imgs/no_profile.png'
|
export const DEFAULT_PFP = '/assets/imgs/no_profile.png'
|
||||||
|
|
||||||
|
export const ANNOUNCEMENT = '' // If set, will add an announcement to the top bar. Keep it short.
|
||||||
|
|
||||||
// Documentation for Userbox mode can be found in `docs/aquabox-url-mode.md`
|
// Documentation for Userbox mode can be found in `docs/aquabox-url-mode.md`
|
||||||
// Please note that if this is set, it must be manually unset by users in Chuni Settings -> Update Userbox -> Switch to URL mode -> (empty value) -> Enter key
|
// Please note that if this is set, it must be manually unset by users in Chuni Settings -> Update Userbox -> Switch to URL mode -> (empty value) -> Enter key
|
||||||
export const USERBOX_DEFAULT_URL = ""
|
export const USERBOX_DEFAULT_URL = ""
|
||||||
|
|
|
@ -72,6 +72,11 @@ export const EN_REF_GENERAL = {
|
||||||
'action.refresh': 'Refresh',
|
'action.refresh': 'Refresh',
|
||||||
'action.cancel': 'Cancel',
|
'action.cancel': 'Cancel',
|
||||||
'action.confirm': 'Confirm',
|
'action.confirm': 'Confirm',
|
||||||
|
'navigation.profile': 'Profile',
|
||||||
|
'navigation.maps': 'Maps',
|
||||||
|
'navigation.home': 'Home',
|
||||||
|
'navigation.rankings': 'Rankings',
|
||||||
|
'navigation.notice': 'Notice'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EN_REF_HOME = {
|
export const EN_REF_HOME = {
|
||||||
|
|
|
@ -67,7 +67,7 @@ const multTable = {
|
||||||
[ 60.0, 0, 'B' ],
|
[ 60.0, 0, 'B' ],
|
||||||
[ 1.0, 0, 'C' ],
|
[ 1.0, 0, 'C' ],
|
||||||
[ 0.0, 0, 'D' ]
|
[ 0.0, 0, 'D' ]
|
||||||
]
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMult(achievement: number, game: GameName) {
|
export function getMult(achievement: number, game: GameName) {
|
||||||
|
|
|
@ -56,6 +56,12 @@ void main() {
|
||||||
gl_FragColor = texture2D(uTexture, vTextureCoord);
|
gl_FragColor = texture2D(uTexture, vTextureCoord);
|
||||||
}`
|
}`
|
||||||
|
|
||||||
|
export interface RGB {
|
||||||
|
r: number,
|
||||||
|
g: number,
|
||||||
|
b: number
|
||||||
|
}
|
||||||
|
|
||||||
export class DDS {
|
export class DDS {
|
||||||
constructor(db: IDBDatabase | undefined) {
|
constructor(db: IDBDatabase | undefined) {
|
||||||
this.cache = new DDSCache(db);
|
this.cache = new DDSCache(db);
|
||||||
|
@ -241,13 +247,27 @@ export class DDS {
|
||||||
* @param s Scale factor
|
* @param s Scale factor
|
||||||
* @returns An object URL which correlates to a Blob
|
* @returns An object URL which correlates to a Blob
|
||||||
*/
|
*/
|
||||||
async getFileFromSheet(path: string, x: number, y: number, w: number, h: number, s?: number): Promise<string> {
|
async getFileFromSheet(path: string, x: number, y: number, w: number, h: number, s?: number, color?: RGB): Promise<string> {
|
||||||
if (!await this.loadFile(path))
|
if (!await this.loadFile(path))
|
||||||
return "";
|
return "";
|
||||||
this.canvas2D.width = w * (s ?? 1);
|
this.canvas2D.width = w * (s ?? 1);
|
||||||
this.canvas2D.height = h * (s ?? 1);
|
this.canvas2D.height = h * (s ?? 1);
|
||||||
this.ctx.drawImage(this.canvasGL, x, y, w, h, 0, 0, w * (s ?? 1), h * (s ?? 1));
|
this.ctx.drawImage(this.canvasGL, x, y, w, h, 0, 0, w * (s ?? 1), h * (s ?? 1));
|
||||||
|
|
||||||
|
if (color) {
|
||||||
|
let colorData = this.ctx.getImageData(0, 0, this.canvas2D.width, this.canvas2D.height);
|
||||||
|
for (let i = 0; colorData.data.length > i; i++)
|
||||||
|
switch (i % 4) {
|
||||||
|
case 0:
|
||||||
|
colorData.data[i] *= (color.r / 255); break;
|
||||||
|
case 1:
|
||||||
|
colorData.data[i] *= (color.g / 255); break;
|
||||||
|
case 2:
|
||||||
|
colorData.data[i] *= (color.b / 255); break;
|
||||||
|
}
|
||||||
|
this.ctx.putImageData(colorData, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
/* We don't want to cache this, it's a spritesheet piece. */
|
/* We don't want to cache this, it's a spritesheet piece. */
|
||||||
return URL.createObjectURL(await this.get2DBlob("image/png") ?? new Blob([]));
|
return URL.createObjectURL(await this.get2DBlob("image/png") ?? new Blob([]));
|
||||||
};
|
};
|
||||||
|
|
|
@ -85,6 +85,8 @@ const DIRECTORY_PATHS = ([
|
||||||
([
|
([
|
||||||
"CHU_UI_Common_Avatar_body_00.dds",
|
"CHU_UI_Common_Avatar_body_00.dds",
|
||||||
"CHU_UI_Common_Avatar_face_00.dds",
|
"CHU_UI_Common_Avatar_face_00.dds",
|
||||||
|
"CHU_UI_Common_01_v11.dds",
|
||||||
|
"CHU_UI_TeamEmblem_01_v14.dds",
|
||||||
"CHU_UI_title_rank_00_v10.dds"
|
"CHU_UI_title_rank_00_v10.dds"
|
||||||
]).includes(name),
|
]).includes(name),
|
||||||
id: (name: string) => name
|
id: (name: string) => name
|
||||||
|
|
|
@ -28,8 +28,9 @@
|
||||||
[ 'displayName', t('settings.profile.name') ],
|
[ 'displayName', t('settings.profile.name') ],
|
||||||
[ 'username', t('settings.profile.username') ],
|
[ 'username', t('settings.profile.username') ],
|
||||||
[ 'password', t('settings.profile.password') ],
|
[ 'password', t('settings.profile.password') ],
|
||||||
|
/* Neither of these did anything of importance
|
||||||
[ 'country', t('settings.profile.country') ],
|
[ 'country', t('settings.profile.country') ],
|
||||||
[ 'profileLocation', t('settings.profile.location') ],
|
[ 'profileLocation', t('settings.profile.location') ],*/
|
||||||
[ 'profileBio', t('settings.profile.bio') ],
|
[ 'profileBio', t('settings.profile.bio') ],
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
|
@ -165,9 +166,14 @@
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for={field}>{name}</label>
|
<label for={field}>{name}</label>
|
||||||
<div>
|
<div>
|
||||||
<input id={field} type="text" use:passwordAction={field === 'password'}
|
{#if field == "profileBio"}
|
||||||
bind:value={me[field]} on:input={() => changed = [...changed, field]}
|
<textarea id={field} bind:value={me[field]} on:input={() => changed = [...changed, field]} maxlength=255 placeholder={t('settings.profile.unset')}></textarea>
|
||||||
placeholder={field === 'password' ? t('settings.profile.unchanged') : t('settings.profile.unset')}/>
|
{:else}
|
||||||
|
<input id={field} type="text" use:passwordAction={field === 'password'}
|
||||||
|
bind:value={me[field]} on:input={() => changed = [...changed, field]}
|
||||||
|
placeholder={field === 'password' ? t('settings.profile.unchanged') : t('settings.profile.unset')}/>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if changed.includes(field) && me[field]}
|
{#if changed.includes(field) && me[field]}
|
||||||
<button transition:slide={{axis: 'x'}} on:click={() => submit(field, me[field])}>
|
<button transition:slide={{axis: 'x'}} on:click={() => submit(field, me[field])}>
|
||||||
{#if submitting === field}
|
{#if submitting === field}
|
||||||
|
@ -256,7 +262,7 @@
|
||||||
gap: 1rem
|
gap: 1rem
|
||||||
margin-top: 0.5rem
|
margin-top: 0.5rem
|
||||||
|
|
||||||
> input
|
> input, > textarea
|
||||||
flex: 1
|
flex: 1
|
||||||
|
|
||||||
img
|
img
|
||||||
|
@ -266,6 +272,8 @@
|
||||||
object-fit: cover
|
object-fit: cover
|
||||||
aspect-ratio: 1
|
aspect-ratio: 1
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.cropper-container
|
.cropper-container
|
||||||
position: relative
|
position: relative
|
||||||
width: 400px
|
width: 400px
|
||||||
|
|
|
@ -30,14 +30,14 @@
|
||||||
registerChart()
|
registerChart()
|
||||||
|
|
||||||
export let username: string;
|
export let username: string;
|
||||||
export let game: GameName = "mai2"
|
export let game: GameName | "auto" = "auto"
|
||||||
let calElement: HTMLElement
|
let calElement: HTMLElement
|
||||||
let error: string;
|
let error: string;
|
||||||
let me: AquaNetUser
|
let me: AquaNetUser
|
||||||
title(`User ${username}`)
|
title(`User ${username}`)
|
||||||
const rounding = useLocalStorage("rounding", true);
|
const rounding = useLocalStorage("rounding", true);
|
||||||
|
|
||||||
const titleText = GAME_TITLE[game]
|
const titleText = game != "auto" ? GAME_TITLE[game] : "?"
|
||||||
|
|
||||||
interface MusicAndPlay extends MusicMeta, GenericGamePlaylog {}
|
interface MusicAndPlay extends MusicMeta, GenericGamePlaylog {}
|
||||||
|
|
||||||
|
@ -57,6 +57,19 @@
|
||||||
USER.isLoggedIn() && USER.me().then(u => me = u)
|
USER.isLoggedIn() && USER.me().then(u => me = u)
|
||||||
|
|
||||||
CARD.userGames(username).then(games => {
|
CARD.userGames(username).then(games => {
|
||||||
|
if (game == "auto") {
|
||||||
|
let targetGames = Object.entries(games)
|
||||||
|
.map(d => {
|
||||||
|
if (d[1])
|
||||||
|
d[1].lastLogin = d[1].lastLogin ? new Date(d[1].lastLogin) : new Date(0);
|
||||||
|
return d;
|
||||||
|
}).sort((a,b) => {
|
||||||
|
return b[1]?.lastLogin - a[1]?.lastLogin;
|
||||||
|
});
|
||||||
|
if (targetGames[0])
|
||||||
|
window.location.href = `/u/${username}/${targetGames[0][0]}`
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!games[game]) {
|
if (!games[game]) {
|
||||||
// Find a valid game
|
// Find a valid game
|
||||||
const valid = Object.entries(games).filter(([g, valid]) => valid)
|
const valid = Object.entries(games).filter(([g, valid]) => valid)
|
||||||
|
@ -105,10 +118,11 @@
|
||||||
}).catch((e) => { error = e.message; console.error(e) } );
|
}).catch((e) => { error = e.message; console.error(e) } );
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Object.keys(GAME_TITLE).includes(game)) init()
|
if (Object.keys(GAME_TITLE).includes(game) || game == "auto") init()
|
||||||
else error = t("UserHome.InvalidGame", {game})
|
else error = t("UserHome.InvalidGame", {game})
|
||||||
|
|
||||||
const setRival = (isAdd: boolean) => {
|
const setRival = (isAdd: boolean) => {
|
||||||
|
if (game == "auto") return;
|
||||||
isLoading = true
|
isLoading = true
|
||||||
GAME.setRival(game, username, isAdd).then(() => {
|
GAME.setRival(game, username, isAdd).then(() => {
|
||||||
d!.user.rival = isAdd
|
d!.user.rival = isAdd
|
||||||
|
@ -122,9 +136,22 @@
|
||||||
<img use:pfp={d.user.aquaUser} alt="" class="pfp" on:error={pfpNotFound}>
|
<img use:pfp={d.user.aquaUser} alt="" class="pfp" on:error={pfpNotFound}>
|
||||||
<div class="name-box">
|
<div class="name-box">
|
||||||
<div class="name-left">
|
<div class="name-left">
|
||||||
<h2>{d.user.name}</h2>
|
|
||||||
{#if d.user.aquaUser}
|
{#if d.user.aquaUser}
|
||||||
|
{#if d.user.aquaUser.displayName}
|
||||||
|
<h2>{d.user.aquaUser?.displayName}</h2>
|
||||||
|
{:else}
|
||||||
|
<h2>{d.user.name}</h2>
|
||||||
|
{/if}
|
||||||
|
<div class="game-name">
|
||||||
|
{#if d.user.aquaUser.displayName}
|
||||||
|
{d.user.name}
|
||||||
|
{/if}
|
||||||
|
(@{d.user.aquaUser.username})
|
||||||
|
</div>
|
||||||
<div class="country">{countryCodeToEmoji(d.user.aquaUser?.country)}</div>
|
<div class="country">{countryCodeToEmoji(d.user.aquaUser?.country)}</div>
|
||||||
|
{:else}
|
||||||
|
<h2>{d.user.name}</h2>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{#if typeof d.user.rival === 'boolean' && game === 'mai2'}
|
{#if typeof d.user.rival === 'boolean' && game === 'mai2'}
|
||||||
|
@ -133,19 +160,31 @@
|
||||||
{d.user.rival ? t("UserHome.RemoveRival") : t("UserHome.AddRival")}
|
{d.user.rival ? t("UserHome.RemoveRival") : t("UserHome.AddRival")}
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
{#if me && me.username === username}
|
|
||||||
<a class="setting-icon clickable" use:tooltip={t("UserHome.Settings")} href="/settings">
|
|
||||||
<Icon icon="eos-icons:rotating-gear"/>
|
|
||||||
</a>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
<nav>
|
<nav>
|
||||||
{#each d.validGames as [g, name]}
|
{#each d.validGames as [g, name]}
|
||||||
<a href={`/u/${username}/${g}`} class:active={game === g}>{name}</a>
|
<a href={`/u/${username}/${g}`} class:active={game === g}>{name}</a>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
|
{#if me && me.username === username}
|
||||||
|
<a class="setting-icon clickable" use:tooltip={t("UserHome.Settings")} href="/settings">
|
||||||
|
<Icon icon="eos-icons:rotating-gear"/>
|
||||||
|
</a>
|
||||||
|
{/if}
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{#if d.user.aquaUser?.profileBio}
|
||||||
|
<div class="activity-info">
|
||||||
|
<div class="info-bottom profile-bio-container">
|
||||||
|
<div class="profile-bio">
|
||||||
|
<span>{t("settings.profile.bio")}</span>
|
||||||
|
<span class="profile-bio-text">{d.user.aquaUser?.profileBio}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<ChuniUserboxDisplay {game} {username} bind:error={error} />
|
<ChuniUserboxDisplay {game} {username} bind:error={error} />
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
@ -272,12 +311,14 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<RatingComposition title="B30" comp={d.user.ratingComposition.best30} {allMusics} {game}/>
|
<!-- I don't like doing this but it may be preferable to gaslighting the types -->
|
||||||
<RatingComposition title="B35" comp={d.user.ratingComposition.best35} {allMusics} {game}/>
|
|
||||||
<RatingComposition title="B15" comp={d.user.ratingComposition.best15} {allMusics} {game}/>
|
<RatingComposition title="B30" comp={d.user.ratingComposition.best30} {allMusics} game={game != "auto" ? game : "mai2"}/>
|
||||||
|
<RatingComposition title="B35" comp={d.user.ratingComposition.best35} {allMusics} game={game != "auto" ? game : "mai2"}/>
|
||||||
|
<RatingComposition title="B15" comp={d.user.ratingComposition.best15} {allMusics} game={game != "auto" ? game : "mai2"}/>
|
||||||
<!-- <RatingComposition title="Hot 10" comp={d.user.ratingComposition.hot10} {allMusics} {game}/> -->
|
<!-- <RatingComposition title="Hot 10" comp={d.user.ratingComposition.hot10} {allMusics} {game}/> -->
|
||||||
<!-- <RatingComposition title="N10" comp={d.user.ratingComposition.next10} {allMusics} {game}/> -->
|
<!-- <RatingComposition title="N10" comp={d.user.ratingComposition.next10} {allMusics} {game}/> -->
|
||||||
<RatingComposition title="Recent 10" comp={d.user.ratingComposition.recent10} {allMusics} {game} top={10}/>
|
<RatingComposition title="Recent 10" comp={d.user.ratingComposition.recent10} {allMusics} game={game != "auto" ? game : "mai2"} top={10}/>
|
||||||
|
|
||||||
<div class="recent">
|
<div class="recent">
|
||||||
<h2>{t('UserHome.RecentScores')}</h2>
|
<h2>{t('UserHome.RecentScores')}</h2>
|
||||||
|
@ -298,12 +339,12 @@
|
||||||
{r.notes?.[r.level === 10 ? 0 : r.level]?.lv?.toFixed(1) ?? r.worldsEndTag ?? '-'}
|
{r.notes?.[r.level === 10 ? 0 : r.level]?.lv?.toFixed(1) ?? r.worldsEndTag ?? '-'}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<span class={`rank-${getMult(r.achievement, game)[2].toString()[0]}`}>
|
<span class={`rank-${getMult(r.achievement, game != "auto" ? game : "mai2")[2].toString()[0]}`}>
|
||||||
<span class="rank-text">{("" + getMult(r.achievement, game)[2]).replace("p", "+")}</span>
|
<span class="rank-text">{("" + getMult(r.achievement, game != "auto" ? game : "mai2")[2]).replace("p", "+")}</span>
|
||||||
<span class="rank-num" use:tooltip={(r.achievement / 10000).toFixed(4)}>
|
<span class="rank-num" use:tooltip={(r.achievement / 10000).toFixed(4)}>
|
||||||
{
|
{
|
||||||
rounding.value ?
|
rounding.value ?
|
||||||
roundFloor(r.achievement, game, 1) :
|
roundFloor(r.achievement, game != "auto" ? game : "mai2", 1) :
|
||||||
(r.achievement / 10000).toFixed(4)
|
(r.achievement / 10000).toFixed(4)
|
||||||
}%
|
}%
|
||||||
</span>
|
</span>
|
||||||
|
@ -357,6 +398,9 @@
|
||||||
display: flex
|
display: flex
|
||||||
align-items: center
|
align-items: center
|
||||||
|
|
||||||
|
position: relative
|
||||||
|
z-index: 20
|
||||||
|
|
||||||
.name-box
|
.name-box
|
||||||
flex: 1
|
flex: 1
|
||||||
display: flex
|
display: flex
|
||||||
|
@ -367,6 +411,16 @@
|
||||||
.name-left
|
.name-left
|
||||||
display: flex
|
display: flex
|
||||||
gap: 1em
|
gap: 1em
|
||||||
|
position: relative
|
||||||
|
|
||||||
|
.game-name
|
||||||
|
position: absolute
|
||||||
|
left: 0.5em
|
||||||
|
bottom: 0
|
||||||
|
transform: translate(0, 75%)
|
||||||
|
opacity: 50%
|
||||||
|
white-space: nowrap
|
||||||
|
max-width: 50%
|
||||||
|
|
||||||
.pfp
|
.pfp
|
||||||
width: 100px
|
width: 100px
|
||||||
|
@ -403,6 +457,16 @@
|
||||||
.info-bottom
|
.info-bottom
|
||||||
width: max-content
|
width: max-content
|
||||||
|
|
||||||
|
&.profile-bio-container,
|
||||||
|
&.profile-bio-container div
|
||||||
|
width: 100%
|
||||||
|
|
||||||
|
.profile-bio-text
|
||||||
|
white-space: pre
|
||||||
|
max-height: 10em
|
||||||
|
overflow-y: auto
|
||||||
|
flex: 1
|
||||||
|
|
||||||
.info-top > div > span:last-child
|
.info-top > div > span:last-child
|
||||||
font-size: 1.5rem
|
font-size: 1.5rem
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue