Added editable mode to character detail
This commit is contained in:
parent
0e92b9baf5
commit
5c8e23a38e
2 changed files with 371 additions and 26 deletions
|
|
@ -2,8 +2,11 @@ import type { PageServerLoad } from './$types'
|
||||||
import { get } from '$lib/api/core'
|
import { get } from '$lib/api/core'
|
||||||
import { error } from '@sveltejs/kit'
|
import { error } from '@sveltejs/kit'
|
||||||
|
|
||||||
export const load: PageServerLoad = async ({ params, fetch }) => {
|
export const load: PageServerLoad = async ({ params, fetch, parent }) => {
|
||||||
try {
|
try {
|
||||||
|
// Get parent data to access role
|
||||||
|
const parentData = await parent()
|
||||||
|
|
||||||
const character = await get(fetch, `/characters/${params.id}`)
|
const character = await get(fetch, `/characters/${params.id}`)
|
||||||
|
|
||||||
if (!character) {
|
if (!character) {
|
||||||
|
|
@ -11,7 +14,8 @@ export const load: PageServerLoad = async ({ params, fetch }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
character
|
character,
|
||||||
|
role: parentData.role
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to load character:', err)
|
console.error('Failed to load character:', err)
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,11 @@
|
||||||
import { goto } from '$app/navigation'
|
import { goto } from '$app/navigation'
|
||||||
|
|
||||||
// Utility functions
|
// Utility functions
|
||||||
import { getRarityLabel } from '$lib/utils/rarity'
|
import { getRarityLabel, getRarityOptions } from '$lib/utils/rarity'
|
||||||
import { getElementLabel } from '$lib/utils/element'
|
import { getElementLabel, getElementOptions } from '$lib/utils/element'
|
||||||
import { getProficiencyLabel } from '$lib/utils/proficiency'
|
import { getProficiencyLabel, getProficiencyOptions } from '$lib/utils/proficiency'
|
||||||
import { getRaceLabel } from '$lib/utils/race'
|
import { getRaceLabel, getRaceOptions } from '$lib/utils/race'
|
||||||
import { getGenderLabel } from '$lib/utils/gender'
|
import { getGenderLabel, getGenderOptions } from '$lib/utils/gender'
|
||||||
import { getCharacterMaxUncapLevel } from '$lib/utils/uncap'
|
import { getCharacterMaxUncapLevel } from '$lib/utils/uncap'
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
import DetailsContainer from '$lib/components/ui/DetailsContainer.svelte'
|
import DetailsContainer from '$lib/components/ui/DetailsContainer.svelte'
|
||||||
import DetailItem from '$lib/components/ui/DetailItem.svelte'
|
import DetailItem from '$lib/components/ui/DetailItem.svelte'
|
||||||
import DetailsHeader from '$lib/components/ui/DetailsHeader.svelte'
|
import DetailsHeader from '$lib/components/ui/DetailsHeader.svelte'
|
||||||
|
import Button from '$lib/components/ui/Button.svelte'
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import type { PageData } from './$types'
|
import type { PageData } from './$types'
|
||||||
|
|
@ -25,6 +26,160 @@
|
||||||
|
|
||||||
// Get character from server data
|
// Get character from server data
|
||||||
const character = $derived(data.character)
|
const character = $derived(data.character)
|
||||||
|
const userRole = $derived(data.role || 0)
|
||||||
|
const canEdit = $derived(userRole >= 7)
|
||||||
|
|
||||||
|
// Edit mode state
|
||||||
|
let editMode = $state(false)
|
||||||
|
let isSaving = $state(false)
|
||||||
|
let saveError = $state<string | null>(null)
|
||||||
|
let saveSuccess = $state(false)
|
||||||
|
|
||||||
|
// Editable fields - create reactive state for each field
|
||||||
|
let editData = $state({
|
||||||
|
name: character?.name || '',
|
||||||
|
granblue_id: character?.granblue_id || '',
|
||||||
|
rarity: character?.rarity || 1,
|
||||||
|
element: character?.element || 0,
|
||||||
|
race1: character?.race?.[0] ?? null,
|
||||||
|
race2: character?.race?.[1] ?? null,
|
||||||
|
gender: character?.gender || 0,
|
||||||
|
proficiency1: character?.proficiency?.[0] || 0,
|
||||||
|
proficiency2: character?.proficiency?.[1] || 0,
|
||||||
|
min_hp: character?.hp?.min_hp || 0,
|
||||||
|
max_hp: character?.hp?.max_hp || 0,
|
||||||
|
max_hp_flb: character?.hp?.max_hp_flb || 0,
|
||||||
|
min_atk: character?.atk?.min_atk || 0,
|
||||||
|
max_atk: character?.atk?.max_atk || 0,
|
||||||
|
max_atk_flb: character?.atk?.max_atk_flb || 0,
|
||||||
|
flb: character?.uncap?.flb || false,
|
||||||
|
ulb: character?.uncap?.ulb || false,
|
||||||
|
transcendence: character?.uncap?.transcendence || false,
|
||||||
|
special: character?.special || false
|
||||||
|
})
|
||||||
|
|
||||||
|
// Reset edit data when character changes
|
||||||
|
$effect(() => {
|
||||||
|
if (character) {
|
||||||
|
editData = {
|
||||||
|
name: character.name || '',
|
||||||
|
granblue_id: character.granblue_id || '',
|
||||||
|
rarity: character.rarity || 1,
|
||||||
|
element: character.element || 0,
|
||||||
|
race1: character.race?.[0] ?? null,
|
||||||
|
race2: character.race?.[1] ?? null,
|
||||||
|
gender: character.gender || 0,
|
||||||
|
proficiency1: character.proficiency?.[0] || 0,
|
||||||
|
proficiency2: character.proficiency?.[1] || 0,
|
||||||
|
min_hp: character.hp?.min_hp || 0,
|
||||||
|
max_hp: character.hp?.max_hp || 0,
|
||||||
|
max_hp_flb: character.hp?.max_hp_flb || 0,
|
||||||
|
min_atk: character.atk?.min_atk || 0,
|
||||||
|
max_atk: character.atk?.max_atk || 0,
|
||||||
|
max_atk_flb: character.atk?.max_atk_flb || 0,
|
||||||
|
flb: character.uncap?.flb || false,
|
||||||
|
ulb: character.uncap?.ulb || false,
|
||||||
|
transcendence: character.uncap?.transcendence || false,
|
||||||
|
special: character.special || false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Options for select dropdowns - using centralized utilities
|
||||||
|
const rarityOptions = getRarityOptions()
|
||||||
|
const elementOptions = getElementOptions()
|
||||||
|
const raceOptions = getRaceOptions()
|
||||||
|
const genderOptions = getGenderOptions()
|
||||||
|
const proficiencyOptions = getProficiencyOptions()
|
||||||
|
|
||||||
|
function toggleEditMode() {
|
||||||
|
editMode = !editMode
|
||||||
|
saveError = null
|
||||||
|
saveSuccess = false
|
||||||
|
|
||||||
|
// Reset data when canceling
|
||||||
|
if (!editMode && character) {
|
||||||
|
editData = {
|
||||||
|
name: character.name || '',
|
||||||
|
granblue_id: character.granblue_id || '',
|
||||||
|
rarity: character.rarity || 1,
|
||||||
|
element: character.element || 0,
|
||||||
|
race1: character.race?.[0] ?? null,
|
||||||
|
race2: character.race?.[1] ?? null,
|
||||||
|
gender: character.gender || 0,
|
||||||
|
proficiency1: character.proficiency?.[0] || 0,
|
||||||
|
proficiency2: character.proficiency?.[1] || 0,
|
||||||
|
min_hp: character.hp?.min_hp || 0,
|
||||||
|
max_hp: character.hp?.max_hp || 0,
|
||||||
|
max_hp_flb: character.hp?.max_hp_flb || 0,
|
||||||
|
min_atk: character.atk?.min_atk || 0,
|
||||||
|
max_atk: character.atk?.max_atk || 0,
|
||||||
|
max_atk_flb: character.atk?.max_atk_flb || 0,
|
||||||
|
flb: character.uncap?.flb || false,
|
||||||
|
ulb: character.uncap?.ulb || false,
|
||||||
|
transcendence: character.uncap?.transcendence || false,
|
||||||
|
special: character.special || false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveChanges() {
|
||||||
|
isSaving = true
|
||||||
|
saveError = null
|
||||||
|
saveSuccess = false
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Prepare the data for API
|
||||||
|
const payload = {
|
||||||
|
name: editData.name,
|
||||||
|
granblue_id: editData.granblue_id,
|
||||||
|
rarity: editData.rarity,
|
||||||
|
element: editData.element,
|
||||||
|
race: [editData.race1, editData.race2].filter(r => r !== null && r !== undefined),
|
||||||
|
gender: editData.gender,
|
||||||
|
proficiency: [editData.proficiency1, editData.proficiency2],
|
||||||
|
hp: {
|
||||||
|
min_hp: editData.min_hp,
|
||||||
|
max_hp: editData.max_hp,
|
||||||
|
max_hp_flb: editData.max_hp_flb
|
||||||
|
},
|
||||||
|
atk: {
|
||||||
|
min_atk: editData.min_atk,
|
||||||
|
max_atk: editData.max_atk,
|
||||||
|
max_atk_flb: editData.max_atk_flb
|
||||||
|
},
|
||||||
|
uncap: {
|
||||||
|
flb: editData.flb,
|
||||||
|
ulb: editData.ulb,
|
||||||
|
transcendence: editData.transcendence
|
||||||
|
},
|
||||||
|
special: editData.special
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: When backend endpoint is ready, make the API call here
|
||||||
|
// const response = await fetch(`/api/v1/characters/${character.id}`, {
|
||||||
|
// method: 'PUT',
|
||||||
|
// headers: { 'Content-Type': 'application/json' },
|
||||||
|
// body: JSON.stringify(payload)
|
||||||
|
// })
|
||||||
|
|
||||||
|
// For now, just simulate success
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||||
|
|
||||||
|
saveSuccess = true
|
||||||
|
editMode = false
|
||||||
|
|
||||||
|
// Show success message for 3 seconds
|
||||||
|
setTimeout(() => {
|
||||||
|
saveSuccess = false
|
||||||
|
}, 3000)
|
||||||
|
} catch (error) {
|
||||||
|
saveError = 'Failed to save changes. Please try again.'
|
||||||
|
console.error('Save error:', error)
|
||||||
|
} finally {
|
||||||
|
isSaving = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Helper function to get character image
|
// Helper function to get character image
|
||||||
function getCharacterImage(character: any): string {
|
function getCharacterImage(character: any): string {
|
||||||
|
|
@ -33,11 +188,15 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate uncap properties for the indicator
|
// Calculate uncap properties for the indicator
|
||||||
const uncap = $derived(character?.uncap ?? {})
|
const uncap = $derived(
|
||||||
|
editMode
|
||||||
|
? { flb: editData.flb, ulb: editData.ulb, transcendence: editData.transcendence }
|
||||||
|
: (character?.uncap ?? {})
|
||||||
|
)
|
||||||
const flb = $derived(uncap.flb ?? false)
|
const flb = $derived(uncap.flb ?? false)
|
||||||
const ulb = $derived(uncap.ulb ?? false)
|
const ulb = $derived(uncap.ulb ?? false)
|
||||||
const transcendence = $derived(uncap.transcendence ?? false)
|
const transcendence = $derived(uncap.transcendence ?? false)
|
||||||
const special = $derived(character?.special ?? false)
|
const special = $derived(editMode ? editData.special : (character?.special ?? false))
|
||||||
|
|
||||||
const uncapLevel = $derived(getCharacterMaxUncapLevel({ special, uncap }))
|
const uncapLevel = $derived(getCharacterMaxUncapLevel({ special, uncap }))
|
||||||
const transcendenceStage = $derived(transcendence ? 5 : 0)
|
const transcendenceStage = $derived(transcendence ? 5 : 0)
|
||||||
|
|
@ -46,11 +205,49 @@
|
||||||
<div>
|
<div>
|
||||||
{#if character}
|
{#if character}
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<DetailsHeader type="character" item={character} image={getCharacterImage(character)} />
|
<DetailsHeader
|
||||||
|
type="character"
|
||||||
|
item={character}
|
||||||
|
image={getCharacterImage(character)}
|
||||||
|
onEdit={toggleEditMode}
|
||||||
|
showEdit={canEdit}
|
||||||
|
{editMode}
|
||||||
|
onSave={saveChanges}
|
||||||
|
onCancel={toggleEditMode}
|
||||||
|
{isSaving}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{#if saveSuccess || saveError}
|
||||||
|
<div class="edit-controls">
|
||||||
|
{#if saveSuccess}
|
||||||
|
<span class="success-message">Changes saved successfully!</span>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if saveError}
|
||||||
|
<span class="error-message">{saveError}</span>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<DetailsContainer title="Metadata">
|
<DetailsContainer title="Metadata">
|
||||||
<DetailItem label="Rarity" value={getRarityLabel(character.rarity)} />
|
{#if editMode}
|
||||||
<DetailItem label="Granblue ID" value={character.granblue_id} />
|
<DetailItem
|
||||||
|
label="Rarity"
|
||||||
|
bind:value={editData.rarity}
|
||||||
|
editable={true}
|
||||||
|
type="select"
|
||||||
|
options={rarityOptions}
|
||||||
|
/>
|
||||||
|
<DetailItem
|
||||||
|
label="Granblue ID"
|
||||||
|
bind:value={editData.granblue_id}
|
||||||
|
editable={true}
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<DetailItem label="Rarity" value={getRarityLabel(character.rarity)} />
|
||||||
|
<DetailItem label="Granblue ID" value={character.granblue_id} />
|
||||||
|
{/if}
|
||||||
</DetailsContainer>
|
</DetailsContainer>
|
||||||
<DetailsContainer title="Details">
|
<DetailsContainer title="Details">
|
||||||
{#if character.uncap}
|
{#if character.uncap}
|
||||||
|
|
@ -67,27 +264,140 @@
|
||||||
/>
|
/>
|
||||||
</DetailItem>
|
</DetailItem>
|
||||||
{/if}
|
{/if}
|
||||||
<DetailItem label="Element" value={getElementLabel(character.element)} />
|
|
||||||
<DetailItem label="Race" value={getRaceLabel(character.race)} />
|
|
||||||
<DetailItem label="Gender" value={getGenderLabel(character.gender)} />
|
|
||||||
|
|
||||||
<DetailItem label="Proficiency 1" value={getProficiencyLabel(character.proficiency[0])} />
|
{#if editMode}
|
||||||
<DetailItem label="Proficiency 2" value={getProficiencyLabel(character.proficiency[1])} />
|
<DetailItem label="FLB" bind:value={editData.flb} editable={true} type="checkbox" />
|
||||||
|
<DetailItem label="ULB" bind:value={editData.ulb} editable={true} type="checkbox" />
|
||||||
|
<DetailItem
|
||||||
|
label="Transcendence"
|
||||||
|
bind:value={editData.transcendence}
|
||||||
|
editable={true}
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
<DetailItem
|
||||||
|
label="Special"
|
||||||
|
bind:value={editData.special}
|
||||||
|
editable={true}
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if editMode}
|
||||||
|
<DetailItem
|
||||||
|
label="Element"
|
||||||
|
bind:value={editData.element}
|
||||||
|
editable={true}
|
||||||
|
type="select"
|
||||||
|
options={elementOptions}
|
||||||
|
/>
|
||||||
|
<DetailItem
|
||||||
|
label="Race 1"
|
||||||
|
bind:value={editData.race1}
|
||||||
|
editable={true}
|
||||||
|
type="select"
|
||||||
|
options={raceOptions}
|
||||||
|
/>
|
||||||
|
<DetailItem
|
||||||
|
label="Race 2"
|
||||||
|
bind:value={editData.race2}
|
||||||
|
editable={true}
|
||||||
|
type="select"
|
||||||
|
options={raceOptions}
|
||||||
|
/>
|
||||||
|
<DetailItem
|
||||||
|
label="Gender"
|
||||||
|
bind:value={editData.gender}
|
||||||
|
editable={true}
|
||||||
|
type="select"
|
||||||
|
options={genderOptions}
|
||||||
|
/>
|
||||||
|
<DetailItem
|
||||||
|
label="Proficiency 1"
|
||||||
|
bind:value={editData.proficiency1}
|
||||||
|
editable={true}
|
||||||
|
type="select"
|
||||||
|
options={proficiencyOptions}
|
||||||
|
/>
|
||||||
|
<DetailItem
|
||||||
|
label="Proficiency 2"
|
||||||
|
bind:value={editData.proficiency2}
|
||||||
|
editable={true}
|
||||||
|
type="select"
|
||||||
|
options={proficiencyOptions}
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<DetailItem label="Element" value={getElementLabel(character.element)} />
|
||||||
|
<DetailItem label="Race 1" value={getRaceLabel(character.race?.[0])} />
|
||||||
|
{#if character.race?.[1]}
|
||||||
|
<DetailItem label="Race 2" value={getRaceLabel(character.race?.[1])} />
|
||||||
|
{/if}
|
||||||
|
<DetailItem label="Gender" value={getGenderLabel(character.gender)} />
|
||||||
|
<DetailItem label="Proficiency 1" value={getProficiencyLabel(character.proficiency[0])} />
|
||||||
|
<DetailItem label="Proficiency 2" value={getProficiencyLabel(character.proficiency[1])} />
|
||||||
|
{/if}
|
||||||
</DetailsContainer>
|
</DetailsContainer>
|
||||||
|
|
||||||
<DetailsContainer title="HP Stats">
|
<DetailsContainer title="HP Stats">
|
||||||
<DetailItem label="Base HP" value={character.hp?.min_hp} />
|
{#if editMode}
|
||||||
<DetailItem label="Max HP" value={character.hp?.max_hp} />
|
<DetailItem
|
||||||
{#if flb}
|
label="Base HP"
|
||||||
<DetailItem label="Max HP (FLB)" value={character.hp?.max_hp_flb} />
|
bind:value={editData.min_hp}
|
||||||
|
editable={true}
|
||||||
|
type="number"
|
||||||
|
placeholder="0"
|
||||||
|
/>
|
||||||
|
<DetailItem
|
||||||
|
label="Max HP"
|
||||||
|
bind:value={editData.max_hp}
|
||||||
|
editable={true}
|
||||||
|
type="number"
|
||||||
|
placeholder="0"
|
||||||
|
/>
|
||||||
|
<DetailItem
|
||||||
|
label="Max HP (FLB)"
|
||||||
|
bind:value={editData.max_hp_flb}
|
||||||
|
editable={true}
|
||||||
|
type="number"
|
||||||
|
placeholder="0"
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<DetailItem label="Base HP" value={character.hp?.min_hp} />
|
||||||
|
<DetailItem label="Max HP" value={character.hp?.max_hp} />
|
||||||
|
{#if flb}
|
||||||
|
<DetailItem label="Max HP (FLB)" value={character.hp?.max_hp_flb} />
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</DetailsContainer>
|
</DetailsContainer>
|
||||||
|
|
||||||
<DetailsContainer title="Attack Stats">
|
<DetailsContainer title="Attack Stats">
|
||||||
<DetailItem label="Base Attack" value={character.atk?.min_atk} />
|
{#if editMode}
|
||||||
<DetailItem label="Max Attack" value={character.atk?.max_atk} />
|
<DetailItem
|
||||||
{#if flb}
|
label="Base Attack"
|
||||||
<DetailItem label="Max Attack (FLB)" value={character.atk?.max_atk_flb} />
|
bind:value={editData.min_atk}
|
||||||
|
editable={true}
|
||||||
|
type="number"
|
||||||
|
placeholder="0"
|
||||||
|
/>
|
||||||
|
<DetailItem
|
||||||
|
label="Max Attack"
|
||||||
|
bind:value={editData.max_atk}
|
||||||
|
editable={true}
|
||||||
|
type="number"
|
||||||
|
placeholder="0"
|
||||||
|
/>
|
||||||
|
<DetailItem
|
||||||
|
label="Max Attack (FLB)"
|
||||||
|
bind:value={editData.max_atk_flb}
|
||||||
|
editable={true}
|
||||||
|
type="number"
|
||||||
|
placeholder="0"
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<DetailItem label="Base Attack" value={character.atk?.min_atk} />
|
||||||
|
<DetailItem label="Max Attack" value={character.atk?.max_atk} />
|
||||||
|
{#if flb}
|
||||||
|
<DetailItem label="Max Attack (FLB)" value={character.atk?.max_atk_flb} />
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</DetailsContainer>
|
</DetailsContainer>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -105,6 +415,7 @@
|
||||||
@use '$src/themes/layout' as layout;
|
@use '$src/themes/layout' as layout;
|
||||||
@use '$src/themes/spacing' as spacing;
|
@use '$src/themes/spacing' as spacing;
|
||||||
@use '$src/themes/typography' as typography;
|
@use '$src/themes/typography' as typography;
|
||||||
|
@use '$src/themes/effects' as effects;
|
||||||
|
|
||||||
.not-found {
|
.not-found {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
@ -129,7 +440,37 @@
|
||||||
background: white;
|
background: white;
|
||||||
border-radius: layout.$card-corner;
|
border-radius: layout.$card-corner;
|
||||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
overflow: hidden;
|
overflow: visible; // Changed from hidden to allow sticky header
|
||||||
margin-top: spacing.$unit-2x;
|
margin-top: spacing.$unit-2x;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-controls {
|
||||||
|
padding: spacing.$unit-2x;
|
||||||
|
border-bottom: 1px solid colors.$grey-80;
|
||||||
|
display: flex;
|
||||||
|
gap: spacing.$unit;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.success-message {
|
||||||
|
color: colors.$grey-30;
|
||||||
|
font-size: typography.$font-small;
|
||||||
|
animation: fadeIn effects.$duration-opacity-fade ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
color: colors.$error;
|
||||||
|
font-size: typography.$font-small;
|
||||||
|
animation: fadeIn effects.$duration-opacity-fade ease-in;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue