add settings section components
This commit is contained in:
parent
c785d1d0ab
commit
242aa7c0a9
3 changed files with 620 additions and 0 deletions
208
src/lib/components/settings/AccountSettings.svelte
Normal file
208
src/lib/components/settings/AccountSettings.svelte
Normal file
|
|
@ -0,0 +1,208 @@
|
||||||
|
<svelte:options runes={true} />
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Input from '../ui/Input.svelte'
|
||||||
|
import Switch from '../ui/switch/Switch.svelte'
|
||||||
|
import SettingsRow from '../ui/SettingsRow.svelte'
|
||||||
|
import type { ElementType } from '../ui/SettingsNav.svelte'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
username: string
|
||||||
|
email: string
|
||||||
|
currentPassword: string
|
||||||
|
newPassword: string
|
||||||
|
confirmPassword: string
|
||||||
|
bahamut: boolean
|
||||||
|
role: number
|
||||||
|
element: ElementType
|
||||||
|
onUsernameChange: (value: string) => void
|
||||||
|
onEmailChange: (value: string) => void
|
||||||
|
onCurrentPasswordChange: (value: string) => void
|
||||||
|
onNewPasswordChange: (value: string) => void
|
||||||
|
onConfirmPasswordChange: (value: string) => void
|
||||||
|
onBahamutChange: (value: boolean) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
let {
|
||||||
|
username,
|
||||||
|
email,
|
||||||
|
currentPassword,
|
||||||
|
newPassword,
|
||||||
|
confirmPassword,
|
||||||
|
bahamut,
|
||||||
|
role,
|
||||||
|
element,
|
||||||
|
onUsernameChange,
|
||||||
|
onEmailChange,
|
||||||
|
onCurrentPasswordChange,
|
||||||
|
onNewPasswordChange,
|
||||||
|
onConfirmPasswordChange,
|
||||||
|
onBahamutChange
|
||||||
|
}: Props = $props()
|
||||||
|
|
||||||
|
// Local state for inputs
|
||||||
|
let localUsername = $state(username)
|
||||||
|
let localEmail = $state(email)
|
||||||
|
let localCurrentPassword = $state(currentPassword)
|
||||||
|
let localNewPassword = $state(newPassword)
|
||||||
|
let localConfirmPassword = $state(confirmPassword)
|
||||||
|
|
||||||
|
// Sync local state with props when props change
|
||||||
|
$effect(() => {
|
||||||
|
localUsername = username
|
||||||
|
})
|
||||||
|
$effect(() => {
|
||||||
|
localEmail = email
|
||||||
|
})
|
||||||
|
$effect(() => {
|
||||||
|
localCurrentPassword = currentPassword
|
||||||
|
})
|
||||||
|
$effect(() => {
|
||||||
|
localNewPassword = newPassword
|
||||||
|
})
|
||||||
|
$effect(() => {
|
||||||
|
localConfirmPassword = confirmPassword
|
||||||
|
})
|
||||||
|
|
||||||
|
// Propagate changes back to parent
|
||||||
|
function handleUsernameInput() {
|
||||||
|
onUsernameChange(localUsername)
|
||||||
|
}
|
||||||
|
function handleEmailInput() {
|
||||||
|
onEmailChange(localEmail)
|
||||||
|
}
|
||||||
|
function handleCurrentPasswordInput() {
|
||||||
|
onCurrentPasswordChange(localCurrentPassword)
|
||||||
|
}
|
||||||
|
function handleNewPasswordInput() {
|
||||||
|
onNewPasswordChange(localNewPassword)
|
||||||
|
}
|
||||||
|
function handleConfirmPasswordInput() {
|
||||||
|
onConfirmPasswordChange(localConfirmPassword)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user is admin
|
||||||
|
const isAdmin = $derived(role === 9)
|
||||||
|
|
||||||
|
// Check if any sensitive field has been modified
|
||||||
|
const hasSecurityChanges = $derived(localNewPassword !== '' || localConfirmPassword !== '')
|
||||||
|
|
||||||
|
// Password match validation
|
||||||
|
const passwordsMatch = $derived(localNewPassword === '' || localNewPassword === localConfirmPassword)
|
||||||
|
const passwordError = $derived(!passwordsMatch ? 'Passwords do not match' : '')
|
||||||
|
|
||||||
|
// Current password required when changing password
|
||||||
|
const currentPasswordRequired = $derived(hasSecurityChanges && localCurrentPassword === '')
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<div class="form-fields">
|
||||||
|
<!-- Username -->
|
||||||
|
<Input
|
||||||
|
label="Username"
|
||||||
|
placeholder="Enter username"
|
||||||
|
contained
|
||||||
|
fullWidth
|
||||||
|
bind:value={localUsername}
|
||||||
|
handleInput={handleUsernameInput}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Email -->
|
||||||
|
<Input
|
||||||
|
label="Email"
|
||||||
|
type="email"
|
||||||
|
placeholder="Enter email"
|
||||||
|
contained
|
||||||
|
fullWidth
|
||||||
|
bind:value={localEmail}
|
||||||
|
handleInput={handleEmailInput}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<hr class="separator" />
|
||||||
|
|
||||||
|
<p class="section-note">
|
||||||
|
To change your password, enter your current password and a new password below.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- Current Password (required for changes) -->
|
||||||
|
<Input
|
||||||
|
label="Current Password"
|
||||||
|
type="password"
|
||||||
|
placeholder="Enter current password"
|
||||||
|
contained
|
||||||
|
fullWidth
|
||||||
|
required={hasSecurityChanges}
|
||||||
|
error={currentPasswordRequired ? 'Required to change password' : ''}
|
||||||
|
bind:value={localCurrentPassword}
|
||||||
|
handleInput={handleCurrentPasswordInput}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- New Password -->
|
||||||
|
<Input
|
||||||
|
label="New Password"
|
||||||
|
type="password"
|
||||||
|
placeholder="Enter new password"
|
||||||
|
contained
|
||||||
|
fullWidth
|
||||||
|
bind:value={localNewPassword}
|
||||||
|
handleInput={handleNewPasswordInput}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Confirm Password -->
|
||||||
|
<Input
|
||||||
|
label="Confirm New Password"
|
||||||
|
type="password"
|
||||||
|
placeholder="Confirm new password"
|
||||||
|
contained
|
||||||
|
fullWidth
|
||||||
|
error={passwordError}
|
||||||
|
bind:value={localConfirmPassword}
|
||||||
|
handleInput={handleConfirmPasswordInput}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Bahamut Mode (admin only) -->
|
||||||
|
{#if isAdmin}
|
||||||
|
<hr class="separator" />
|
||||||
|
<SettingsRow title="Bahamut Mode" subtitle="Enable admin features and tools">
|
||||||
|
{#snippet control()}
|
||||||
|
<Switch
|
||||||
|
checked={bahamut}
|
||||||
|
name="bahamut-mode"
|
||||||
|
{element}
|
||||||
|
onCheckedChange={onBahamutChange}
|
||||||
|
/>
|
||||||
|
{/snippet}
|
||||||
|
</SettingsRow>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@use '$src/themes/spacing' as spacing;
|
||||||
|
@use '$src/themes/typography' as typography;
|
||||||
|
@use '$src/themes/colors' as colors;
|
||||||
|
@use '$src/themes/layout' as layout;
|
||||||
|
|
||||||
|
.section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-note {
|
||||||
|
font-size: typography.$font-small;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-fields {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: spacing.$unit-3x;
|
||||||
|
}
|
||||||
|
|
||||||
|
.separator {
|
||||||
|
border: none;
|
||||||
|
border-top: 1px solid var(--border-color, rgba(0, 0, 0, 0.08));
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
118
src/lib/components/settings/PrivacySettings.svelte
Normal file
118
src/lib/components/settings/PrivacySettings.svelte
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
<svelte:options runes={true} />
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Select from '../ui/Select.svelte'
|
||||||
|
import Switch from '../ui/switch/Switch.svelte'
|
||||||
|
import SettingsRow from '../ui/SettingsRow.svelte'
|
||||||
|
import type { ElementType } from '../ui/SettingsNav.svelte'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
showGranblueId: boolean
|
||||||
|
collectionPrivacy: number
|
||||||
|
showCrewGamertag: boolean
|
||||||
|
isInCrew: boolean
|
||||||
|
crewGamertag?: string
|
||||||
|
element: ElementType
|
||||||
|
onShowGranblueIdChange: (value: boolean) => void
|
||||||
|
onCollectionPrivacyChange: (value: number) => void
|
||||||
|
onShowCrewGamertagChange: (value: boolean) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
let {
|
||||||
|
showGranblueId,
|
||||||
|
collectionPrivacy,
|
||||||
|
showCrewGamertag,
|
||||||
|
isInCrew,
|
||||||
|
crewGamertag,
|
||||||
|
element,
|
||||||
|
onShowGranblueIdChange,
|
||||||
|
onCollectionPrivacyChange,
|
||||||
|
onShowCrewGamertagChange
|
||||||
|
}: Props = $props()
|
||||||
|
|
||||||
|
// Collection privacy options (1-based to avoid JavaScript falsy 0 issues)
|
||||||
|
const collectionPrivacyOptions = [
|
||||||
|
{ value: 1, label: 'Everyone', description: 'Anyone can view your collection' },
|
||||||
|
{ value: 2, label: 'Crew only', description: 'Only crew members can view' },
|
||||||
|
{ value: 3, label: 'Private', description: 'Only you can view your collection' }
|
||||||
|
]
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
console.log('[PrivacySettings] collectionPrivacy prop:', collectionPrivacy, typeof collectionPrivacy)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<div class="form-fields">
|
||||||
|
<!-- Show Granblue ID on profile -->
|
||||||
|
<SettingsRow
|
||||||
|
title="Show Granblue ID on profile"
|
||||||
|
subtitle="Display your in-game ID on your public profile"
|
||||||
|
>
|
||||||
|
{#snippet control()}
|
||||||
|
<Switch
|
||||||
|
checked={showGranblueId}
|
||||||
|
name="show-granblue-id"
|
||||||
|
{element}
|
||||||
|
onCheckedChange={onShowGranblueIdChange}
|
||||||
|
/>
|
||||||
|
{/snippet}
|
||||||
|
</SettingsRow>
|
||||||
|
|
||||||
|
<!-- Show Crew Gamertag (only if in a crew with a gamertag) -->
|
||||||
|
{#if isInCrew && crewGamertag}
|
||||||
|
<SettingsRow
|
||||||
|
title="Show crew tag on profile"
|
||||||
|
subtitle={`Display "${crewGamertag}" next to your name`}
|
||||||
|
>
|
||||||
|
{#snippet control()}
|
||||||
|
<Switch
|
||||||
|
checked={showCrewGamertag}
|
||||||
|
name="show-crew-gamertag"
|
||||||
|
{element}
|
||||||
|
onCheckedChange={onShowCrewGamertagChange}
|
||||||
|
/>
|
||||||
|
{/snippet}
|
||||||
|
</SettingsRow>
|
||||||
|
|
||||||
|
<hr class="separator" />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<!-- Collection Privacy -->
|
||||||
|
<SettingsRow
|
||||||
|
title="Collection visibility"
|
||||||
|
subtitle="Control who can view your character, weapon, and summon collection"
|
||||||
|
>
|
||||||
|
{#snippet control()}
|
||||||
|
<Select
|
||||||
|
value={collectionPrivacy}
|
||||||
|
onValueChange={onCollectionPrivacyChange}
|
||||||
|
options={collectionPrivacyOptions}
|
||||||
|
placeholder="Who can see your collection"
|
||||||
|
contained
|
||||||
|
/>
|
||||||
|
{/snippet}
|
||||||
|
</SettingsRow>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@use '$src/themes/spacing' as spacing;
|
||||||
|
|
||||||
|
.section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-fields {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: spacing.$unit-3x;
|
||||||
|
}
|
||||||
|
|
||||||
|
.separator {
|
||||||
|
border: none;
|
||||||
|
border-top: 1px solid var(--border-color, rgba(0, 0, 0, 0.08));
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
294
src/lib/components/settings/ProfileSettings.svelte
Normal file
294
src/lib/components/settings/ProfileSettings.svelte
Normal file
|
|
@ -0,0 +1,294 @@
|
||||||
|
<svelte:options runes={true} />
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Select from '../ui/Select.svelte'
|
||||||
|
import Input from '../ui/Input.svelte'
|
||||||
|
import SettingsRow from '../ui/SettingsRow.svelte'
|
||||||
|
import { pictureData } from '$lib/utils/pictureData'
|
||||||
|
import { getAvatarSrc, getAvatarSrcSet } from '$lib/utils/avatar'
|
||||||
|
import type { ElementType } from '../ui/SettingsNav.svelte'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
picture: string
|
||||||
|
element: ElementType
|
||||||
|
granblueId: string
|
||||||
|
gender: number
|
||||||
|
language: string
|
||||||
|
theme: string
|
||||||
|
onPictureChange: (value: string) => void
|
||||||
|
onElementChange: (value: string) => void
|
||||||
|
onGranblueIdChange: (value: string) => void
|
||||||
|
onGenderChange: (value: number) => void
|
||||||
|
onLanguageChange: (value: string) => void
|
||||||
|
onThemeChange: (value: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
let {
|
||||||
|
picture,
|
||||||
|
element,
|
||||||
|
granblueId,
|
||||||
|
gender,
|
||||||
|
language,
|
||||||
|
theme,
|
||||||
|
onPictureChange,
|
||||||
|
onElementChange,
|
||||||
|
onGranblueIdChange,
|
||||||
|
onGenderChange,
|
||||||
|
onLanguageChange,
|
||||||
|
onThemeChange
|
||||||
|
}: Props = $props()
|
||||||
|
|
||||||
|
// Get current locale from user settings
|
||||||
|
const locale = $derived(language as 'en' | 'ja')
|
||||||
|
|
||||||
|
// Prepare options for selects
|
||||||
|
const pictureOptions = $derived(
|
||||||
|
pictureData
|
||||||
|
.sort((a, b) => a.name.en.localeCompare(b.name.en))
|
||||||
|
.map((p) => ({
|
||||||
|
value: p.filename,
|
||||||
|
label: p.name[locale] || p.name.en,
|
||||||
|
image: getAvatarSrc(p.filename)
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
|
||||||
|
// Element colors for circle indicators
|
||||||
|
const elementColors: Record<string, string> = {
|
||||||
|
wind: '#3ee489',
|
||||||
|
fire: '#fa6d6d',
|
||||||
|
water: '#6cc9ff',
|
||||||
|
earth: '#fd9f5b',
|
||||||
|
dark: '#de7bff',
|
||||||
|
light: '#e8d633'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create SVG circle data URL for element color
|
||||||
|
function getElementCircle(el: string): string {
|
||||||
|
const color = elementColors[el] || '#888'
|
||||||
|
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><circle cx="8" cy="8" r="7" fill="${color}"/></svg>`
|
||||||
|
return `data:image/svg+xml,${encodeURIComponent(svg)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const elementOptions = [
|
||||||
|
{ value: 'wind', label: 'Wind', image: getElementCircle('wind') },
|
||||||
|
{ value: 'fire', label: 'Fire', image: getElementCircle('fire') },
|
||||||
|
{ value: 'water', label: 'Water', image: getElementCircle('water') },
|
||||||
|
{ value: 'earth', label: 'Earth', image: getElementCircle('earth') },
|
||||||
|
{ value: 'dark', label: 'Dark', image: getElementCircle('dark') },
|
||||||
|
{ value: 'light', label: 'Light', image: getElementCircle('light') }
|
||||||
|
]
|
||||||
|
|
||||||
|
const genderOptions = [
|
||||||
|
{ value: 0, label: 'Gran' },
|
||||||
|
{ value: 1, label: 'Djeeta' }
|
||||||
|
]
|
||||||
|
|
||||||
|
const languageOptions = [
|
||||||
|
{ value: 'en', label: 'English' },
|
||||||
|
{ value: 'ja', label: '日本語' }
|
||||||
|
]
|
||||||
|
|
||||||
|
const themeOptions = [
|
||||||
|
{ value: 'system', label: 'System' },
|
||||||
|
{ value: 'light', label: 'Light' },
|
||||||
|
{ value: 'dark', label: 'Dark' }
|
||||||
|
]
|
||||||
|
|
||||||
|
// Get current picture data
|
||||||
|
const currentPicture = $derived(pictureData.find((p) => p.filename === picture))
|
||||||
|
|
||||||
|
// Local state for bound values
|
||||||
|
let localPicture = $state(picture)
|
||||||
|
let localElement = $state(element)
|
||||||
|
let localGranblueId = $state(granblueId)
|
||||||
|
let localGender = $state(gender)
|
||||||
|
let localLanguage = $state(language)
|
||||||
|
let localTheme = $state(theme)
|
||||||
|
|
||||||
|
// Sync local state with props
|
||||||
|
$effect(() => {
|
||||||
|
localPicture = picture
|
||||||
|
})
|
||||||
|
$effect(() => {
|
||||||
|
localElement = element
|
||||||
|
})
|
||||||
|
$effect(() => {
|
||||||
|
localGranblueId = granblueId
|
||||||
|
})
|
||||||
|
$effect(() => {
|
||||||
|
localGender = gender
|
||||||
|
})
|
||||||
|
$effect(() => {
|
||||||
|
localLanguage = language
|
||||||
|
})
|
||||||
|
$effect(() => {
|
||||||
|
localTheme = theme
|
||||||
|
})
|
||||||
|
|
||||||
|
// Propagate changes
|
||||||
|
$effect(() => {
|
||||||
|
if (localPicture !== picture) onPictureChange(localPicture)
|
||||||
|
})
|
||||||
|
$effect(() => {
|
||||||
|
if (localElement !== element) onElementChange(localElement)
|
||||||
|
})
|
||||||
|
$effect(() => {
|
||||||
|
if (localGranblueId !== granblueId) onGranblueIdChange(localGranblueId)
|
||||||
|
})
|
||||||
|
$effect(() => {
|
||||||
|
if (localGender !== gender) onGenderChange(localGender)
|
||||||
|
})
|
||||||
|
$effect(() => {
|
||||||
|
if (localLanguage !== language) onLanguageChange(localLanguage)
|
||||||
|
})
|
||||||
|
$effect(() => {
|
||||||
|
if (localTheme !== theme) onThemeChange(localTheme)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<div class="form-fields">
|
||||||
|
<!-- Picture Selection with Preview -->
|
||||||
|
<div class="picture-section">
|
||||||
|
<div class="current-avatar">
|
||||||
|
<img
|
||||||
|
src={getAvatarSrc(localPicture)}
|
||||||
|
srcset={getAvatarSrcSet(localPicture)}
|
||||||
|
alt={currentPicture?.name[locale] || ''}
|
||||||
|
class="avatar-preview element-{localElement}"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Select
|
||||||
|
bind:value={localPicture}
|
||||||
|
options={pictureOptions}
|
||||||
|
label="Avatar"
|
||||||
|
placeholder="Select an avatar"
|
||||||
|
fullWidth
|
||||||
|
contained
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Element Selection -->
|
||||||
|
<SettingsRow title="Element" subtitle="Your profile accent color">
|
||||||
|
{#snippet control()}
|
||||||
|
<Select
|
||||||
|
bind:value={localElement}
|
||||||
|
options={elementOptions}
|
||||||
|
placeholder="Select an element"
|
||||||
|
contained
|
||||||
|
/>
|
||||||
|
{/snippet}
|
||||||
|
</SettingsRow>
|
||||||
|
|
||||||
|
<hr class="separator" />
|
||||||
|
|
||||||
|
<!-- Granblue ID -->
|
||||||
|
<SettingsRow title="Granblue ID" subtitle="Your in-game player ID">
|
||||||
|
{#snippet control()}
|
||||||
|
<Input bind:value={localGranblueId} placeholder="Enter ID" contained />
|
||||||
|
{/snippet}
|
||||||
|
</SettingsRow>
|
||||||
|
|
||||||
|
<hr class="separator" />
|
||||||
|
|
||||||
|
<!-- Gender Selection -->
|
||||||
|
<SettingsRow title="Gender" subtitle="Your in-game character">
|
||||||
|
{#snippet control()}
|
||||||
|
<Select
|
||||||
|
bind:value={localGender}
|
||||||
|
options={genderOptions}
|
||||||
|
placeholder="Select gender"
|
||||||
|
contained
|
||||||
|
/>
|
||||||
|
{/snippet}
|
||||||
|
</SettingsRow>
|
||||||
|
|
||||||
|
<!-- Language Selection -->
|
||||||
|
<SettingsRow title="Language" subtitle="Display language for the site">
|
||||||
|
{#snippet control()}
|
||||||
|
<Select
|
||||||
|
bind:value={localLanguage}
|
||||||
|
options={languageOptions}
|
||||||
|
placeholder="Select language"
|
||||||
|
contained
|
||||||
|
/>
|
||||||
|
{/snippet}
|
||||||
|
</SettingsRow>
|
||||||
|
|
||||||
|
<!-- Theme Selection -->
|
||||||
|
<SettingsRow title="Theme" subtitle="Light, dark, or system default">
|
||||||
|
{#snippet control()}
|
||||||
|
<Select
|
||||||
|
bind:value={localTheme}
|
||||||
|
options={themeOptions}
|
||||||
|
placeholder="Select theme"
|
||||||
|
contained
|
||||||
|
/>
|
||||||
|
{/snippet}
|
||||||
|
</SettingsRow>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@use '$src/themes/spacing' as spacing;
|
||||||
|
@use '$src/themes/typography' as typography;
|
||||||
|
@use '$src/themes/colors' as colors;
|
||||||
|
@use '$src/themes/layout' as layout;
|
||||||
|
|
||||||
|
.section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-fields {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: spacing.$unit-3x;
|
||||||
|
}
|
||||||
|
|
||||||
|
.separator {
|
||||||
|
border: none;
|
||||||
|
border-top: 1px solid var(--border-color, rgba(0, 0, 0, 0.08));
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.picture-section {
|
||||||
|
display: flex;
|
||||||
|
gap: spacing.$unit-3x;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.current-avatar {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
|
||||||
|
.avatar-preview {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
border-radius: layout.$full-corner;
|
||||||
|
padding: spacing.$unit;
|
||||||
|
background-color: var(--placeholder-bg);
|
||||||
|
|
||||||
|
&.element-fire {
|
||||||
|
background-color: colors.$fire-bg-20;
|
||||||
|
}
|
||||||
|
&.element-water {
|
||||||
|
background-color: colors.$water-bg-20;
|
||||||
|
}
|
||||||
|
&.element-earth {
|
||||||
|
background-color: colors.$earth-bg-20;
|
||||||
|
}
|
||||||
|
&.element-wind {
|
||||||
|
background-color: colors.$wind-bg-20;
|
||||||
|
}
|
||||||
|
&.element-light {
|
||||||
|
background-color: colors.$light-bg-20;
|
||||||
|
}
|
||||||
|
&.element-dark {
|
||||||
|
background-color: colors.$dark-bg-20;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
Reference in a new issue