add artifact skills database pages
- list page with skills grouped by slot - edit page for all skill properties
This commit is contained in:
parent
1e1f4f9478
commit
63e7e3d273
2 changed files with 761 additions and 0 deletions
345
src/routes/(app)/database/artifact-skills/+page.svelte
Normal file
345
src/routes/(app)/database/artifact-skills/+page.svelte
Normal file
|
|
@ -0,0 +1,345 @@
|
|||
<svelte:options runes={true} />
|
||||
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation'
|
||||
import { createQuery } from '@tanstack/svelte-query'
|
||||
import { artifactQueries } from '$lib/api/queries/artifact.queries'
|
||||
import PageMeta from '$lib/components/PageMeta.svelte'
|
||||
import type { ArtifactSkill } from '$lib/types/api/artifact'
|
||||
|
||||
// Fetch all skills
|
||||
const skillsQuery = createQuery(() => artifactQueries.skills())
|
||||
|
||||
// Search state
|
||||
let searchTerm = $state('')
|
||||
|
||||
// Filter skills based on search
|
||||
const filteredSkills = $derived.by(() => {
|
||||
const skills = skillsQuery.data ?? []
|
||||
if (!searchTerm.trim()) return skills
|
||||
|
||||
const term = searchTerm.toLowerCase()
|
||||
return skills.filter(
|
||||
(skill) =>
|
||||
skill.name.en.toLowerCase().includes(term) ||
|
||||
skill.name.ja?.toLowerCase().includes(term) ||
|
||||
skill.gameName?.en?.toLowerCase().includes(term) ||
|
||||
skill.gameName?.ja?.toLowerCase().includes(term) ||
|
||||
skill.modifier.toString().includes(term) ||
|
||||
skill.skillGroup.toLowerCase().includes(term)
|
||||
)
|
||||
})
|
||||
|
||||
// Group skills by slot group
|
||||
const groupedSkills = $derived.by(() => {
|
||||
const skills = [...filteredSkills]
|
||||
|
||||
// Sort by modifier within each group
|
||||
skills.sort((a, b) => a.modifier - b.modifier)
|
||||
|
||||
return {
|
||||
group_i: skills.filter((s) => s.skillGroup === 'group_i'),
|
||||
group_ii: skills.filter((s) => s.skillGroup === 'group_ii'),
|
||||
group_iii: skills.filter((s) => s.skillGroup === 'group_iii')
|
||||
}
|
||||
})
|
||||
|
||||
const totalCount = $derived(
|
||||
groupedSkills.group_i.length + groupedSkills.group_ii.length + groupedSkills.group_iii.length
|
||||
)
|
||||
|
||||
function handleRowClick(skill: ArtifactSkill) {
|
||||
goto(`/database/artifact-skills/${skill.id}`)
|
||||
}
|
||||
|
||||
function getPolarityClass(polarity: string): string {
|
||||
return polarity === 'positive' ? 'positive' : 'negative'
|
||||
}
|
||||
</script>
|
||||
|
||||
<PageMeta title="Artifact Skills" description="Database of artifact skills" />
|
||||
|
||||
<div class="page">
|
||||
<div class="grid-container">
|
||||
<div class="controls">
|
||||
<input type="text" placeholder="Search skills..." bind:value={searchTerm} class="search" />
|
||||
</div>
|
||||
|
||||
{#if skillsQuery.isLoading}
|
||||
<div class="loading">Loading skills...</div>
|
||||
{:else if skillsQuery.isError}
|
||||
<div class="error">Failed to load skills</div>
|
||||
{:else}
|
||||
{#each [{ key: 'group_i', label: 'Group I — Slots 1 & 2', skills: groupedSkills.group_i }, { key: 'group_ii', label: 'Group II — Slot 3', skills: groupedSkills.group_ii }, { key: 'group_iii', label: 'Group III — Slot 4', skills: groupedSkills.group_iii }] as group (group.key)}
|
||||
{#if group.skills.length > 0}
|
||||
<div class="group-section">
|
||||
<h3 class="group-header">{group.label}</h3>
|
||||
<div class="table-wrapper">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="col-modifier">Mod</th>
|
||||
<th class="col-name">Display Name</th>
|
||||
<th class="col-game-name">Game Name</th>
|
||||
<th class="col-polarity">Polarity</th>
|
||||
<th class="col-values">Base Values</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each group.skills as skill (skill.id)}
|
||||
<tr onclick={() => handleRowClick(skill)} class="clickable">
|
||||
<td class="col-modifier">
|
||||
<span class="modifier-badge">{skill.modifier}</span>
|
||||
</td>
|
||||
<td class="col-name">
|
||||
<div class="name-cell">
|
||||
<span class="name-en">{skill.name.en}</span>
|
||||
{#if skill.name.ja}
|
||||
<span class="name-jp">{skill.name.ja}</span>
|
||||
{/if}
|
||||
</div>
|
||||
</td>
|
||||
<td class="col-game-name">
|
||||
<div class="name-cell">
|
||||
{#if skill.gameName?.en || skill.gameName?.ja}
|
||||
<span class="name-en">{skill.gameName?.en || '—'}</span>
|
||||
{#if skill.gameName?.ja}
|
||||
<span class="name-jp">{skill.gameName.ja}</span>
|
||||
{/if}
|
||||
{:else}
|
||||
<span class="not-set">Not set</span>
|
||||
{/if}
|
||||
</div>
|
||||
</td>
|
||||
<td class="col-polarity">
|
||||
<span class="polarity-badge {getPolarityClass(skill.polarity)}">
|
||||
{skill.polarity}
|
||||
</span>
|
||||
</td>
|
||||
<td class="col-values">
|
||||
<span class="values">
|
||||
{skill.baseValues.map((v) => v ?? '?').join(', ')}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
<div class="footer">
|
||||
Showing {totalCount} of {skillsQuery.data?.length ?? 0} skills
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@use '$src/themes/colors' as colors;
|
||||
@use '$src/themes/effects' as effects;
|
||||
@use '$src/themes/layout' as layout;
|
||||
@use '$src/themes/spacing' as spacing;
|
||||
@use '$src/themes/typography' as typography;
|
||||
|
||||
.page {
|
||||
padding: spacing.$unit-2x 0;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.grid-container {
|
||||
background: var(--card-bg);
|
||||
border: 0.5px solid rgba(0, 0, 0, 0.18);
|
||||
border-radius: layout.$page-corner;
|
||||
box-shadow: effects.$page-elevation;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: spacing.$unit;
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
gap: spacing.$unit;
|
||||
|
||||
.search {
|
||||
padding: spacing.$unit spacing.$unit-2x;
|
||||
background: var(--input-bound-bg);
|
||||
border: none;
|
||||
border-radius: layout.$item-corner;
|
||||
font-size: typography.$font-medium;
|
||||
width: 100%;
|
||||
|
||||
&:hover {
|
||||
background: var(--input-bound-bg-hover);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px colors.$blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.loading,
|
||||
.error {
|
||||
text-align: center;
|
||||
padding: spacing.$unit * 4;
|
||||
color: colors.$grey-50;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: colors.$red;
|
||||
}
|
||||
|
||||
.table-wrapper {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.data-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
|
||||
th,
|
||||
td {
|
||||
padding: spacing.$unit-2x spacing.$unit;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
th {
|
||||
background: #f8f9fa;
|
||||
font-weight: typography.$bold;
|
||||
color: #495057;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
tbody tr {
|
||||
&.clickable {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.group-section {
|
||||
&:not(:first-child) {
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
}
|
||||
|
||||
.group-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
padding: spacing.$unit spacing.$unit-2x;
|
||||
background: #f8f9fa;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
font-size: typography.$font-small;
|
||||
font-weight: typography.$medium;
|
||||
color: colors.$grey-50;
|
||||
}
|
||||
|
||||
.col-modifier {
|
||||
width: 60px;
|
||||
padding-left: spacing.$unit-2x !important;
|
||||
}
|
||||
|
||||
.col-name {
|
||||
min-width: 160px;
|
||||
}
|
||||
|
||||
.col-game-name {
|
||||
min-width: 160px;
|
||||
}
|
||||
|
||||
.col-group {
|
||||
width: 140px;
|
||||
}
|
||||
|
||||
.col-polarity {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.col-values {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.modifier-badge {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
background: colors.$grey-90;
|
||||
border-radius: 4px;
|
||||
font-size: typography.$font-small;
|
||||
font-weight: typography.$medium;
|
||||
color: colors.$grey-30;
|
||||
}
|
||||
|
||||
.name-cell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
|
||||
.name-en {
|
||||
font-weight: typography.$medium;
|
||||
}
|
||||
|
||||
.name-jp {
|
||||
font-size: typography.$font-small;
|
||||
color: colors.$grey-50;
|
||||
}
|
||||
|
||||
.not-set {
|
||||
color: colors.$grey-60;
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
|
||||
.group-badge {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
background: colors.$blue;
|
||||
color: white;
|
||||
border-radius: 4px;
|
||||
font-size: typography.$font-small;
|
||||
}
|
||||
|
||||
.polarity-badge {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: typography.$font-small;
|
||||
font-weight: typography.$medium;
|
||||
|
||||
&.positive {
|
||||
background: colors.$wind-bg-00;
|
||||
color: white;
|
||||
}
|
||||
|
||||
&.negative {
|
||||
background: colors.$red;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.values {
|
||||
font-size: typography.$font-small;
|
||||
color: colors.$grey-40;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding: spacing.$unit;
|
||||
text-align: center;
|
||||
color: colors.$grey-50;
|
||||
font-size: typography.$font-small;
|
||||
background: #f8f9fa;
|
||||
border-top: 1px solid #e5e5e5;
|
||||
}
|
||||
</style>
|
||||
416
src/routes/(app)/database/artifact-skills/[id]/+page.svelte
Normal file
416
src/routes/(app)/database/artifact-skills/[id]/+page.svelte
Normal file
|
|
@ -0,0 +1,416 @@
|
|||
<svelte:options runes={true} />
|
||||
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation'
|
||||
import { page } from '$app/stores'
|
||||
import { createQuery, useQueryClient } from '@tanstack/svelte-query'
|
||||
import { artifactQueries, artifactKeys } from '$lib/api/queries/artifact.queries'
|
||||
import { artifactAdapter } from '$lib/api/adapters/artifact.adapter'
|
||||
import PageMeta from '$lib/components/PageMeta.svelte'
|
||||
import Button from '$lib/components/ui/Button.svelte'
|
||||
import DetailsContainer from '$lib/components/ui/DetailsContainer.svelte'
|
||||
import DetailItem from '$lib/components/ui/DetailItem.svelte'
|
||||
import Input from '$lib/components/ui/Input.svelte'
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
// Get skill ID from URL
|
||||
const skillId = $derived($page.params.id)
|
||||
|
||||
// Fetch skill data
|
||||
const skillQuery = createQuery(() => artifactQueries.skillById(skillId))
|
||||
const skill = $derived(skillQuery.data)
|
||||
|
||||
// Save state
|
||||
let isSaving = $state(false)
|
||||
let saveError = $state<string | null>(null)
|
||||
let saveSuccess = $state(false)
|
||||
|
||||
// Editable fields
|
||||
let editData = $state({
|
||||
nameEn: '',
|
||||
nameJp: '',
|
||||
gameNameEn: '',
|
||||
gameNameJp: '',
|
||||
skillGroup: 1,
|
||||
modifier: 0,
|
||||
polarity: 'positive',
|
||||
baseValues: [] as (number | null)[],
|
||||
growth: null as number | null,
|
||||
suffixEn: '',
|
||||
suffixJp: ''
|
||||
})
|
||||
|
||||
// Populate edit data when skill loads
|
||||
$effect(() => {
|
||||
if (skill) {
|
||||
editData = {
|
||||
nameEn: skill.name?.en || '',
|
||||
nameJp: skill.name?.ja || '',
|
||||
gameNameEn: skill.gameName?.en || '',
|
||||
gameNameJp: skill.gameName?.ja || '',
|
||||
skillGroup: getSkillGroupNumber(skill.skillGroup),
|
||||
modifier: skill.modifier || 0,
|
||||
polarity: skill.polarity || 'positive',
|
||||
baseValues: skill.baseValues || [],
|
||||
growth: skill.growth ?? null,
|
||||
suffixEn: skill.suffix?.en || '',
|
||||
suffixJp: skill.suffix?.ja || ''
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
async function saveChanges() {
|
||||
if (!skill?.id) return
|
||||
|
||||
isSaving = true
|
||||
saveError = null
|
||||
saveSuccess = false
|
||||
|
||||
try {
|
||||
const payload = {
|
||||
name_en: editData.nameEn,
|
||||
name_jp: editData.nameJp,
|
||||
game_name_en: editData.gameNameEn || null,
|
||||
game_name_jp: editData.gameNameJp || null,
|
||||
skill_group: editData.skillGroup,
|
||||
modifier: editData.modifier,
|
||||
polarity: editData.polarity,
|
||||
base_values: editData.baseValues,
|
||||
growth: editData.growth,
|
||||
suffix_en: editData.suffixEn,
|
||||
suffix_jp: editData.suffixJp
|
||||
}
|
||||
|
||||
await artifactAdapter.updateSkill(skill.id, payload)
|
||||
await queryClient.invalidateQueries({ queryKey: artifactKeys.skills })
|
||||
|
||||
saveSuccess = true
|
||||
|
||||
setTimeout(() => {
|
||||
goto('/database/artifact-skills')
|
||||
}, 500)
|
||||
} catch (error) {
|
||||
saveError = 'Failed to save changes. Please try again.'
|
||||
console.error('Save error:', error)
|
||||
} finally {
|
||||
isSaving = false
|
||||
}
|
||||
}
|
||||
|
||||
function handleCancel() {
|
||||
goto('/database/artifact-skills')
|
||||
}
|
||||
|
||||
function getSkillGroupNumber(group: string): number {
|
||||
switch (group) {
|
||||
case 'group_i':
|
||||
return 1
|
||||
case 'group_ii':
|
||||
return 2
|
||||
case 'group_iii':
|
||||
return 3
|
||||
default:
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
const skillGroupOptions = [
|
||||
{ value: 1, label: 'Group I (Slots 1 & 2)' },
|
||||
{ value: 2, label: 'Group II (Slot 3)' },
|
||||
{ value: 3, label: 'Group III (Slot 4)' }
|
||||
]
|
||||
|
||||
const polarityOptions = [
|
||||
{ value: 'positive', label: 'Positive' },
|
||||
{ value: 'negative', label: 'Negative' }
|
||||
]
|
||||
|
||||
const pageTitle = $derived(`Edit: ${skill?.name?.en ?? 'Artifact Skill'}`)
|
||||
</script>
|
||||
|
||||
<PageMeta title={pageTitle} description="Edit artifact skill" />
|
||||
|
||||
<div class="page">
|
||||
{#if skillQuery.isLoading}
|
||||
<div class="loading">Loading skill...</div>
|
||||
{:else if skillQuery.isError}
|
||||
<div class="error">Failed to load skill</div>
|
||||
{:else if skill}
|
||||
<div class="content">
|
||||
<header class="header">
|
||||
<div class="left">
|
||||
<div class="modifier-badge">{skill.modifier}</div>
|
||||
<div class="info">
|
||||
<h2>{skill.name.en}</h2>
|
||||
<div class="meta">
|
||||
<span class="skill-group">{skillGroupOptions.find(o => o.value === getSkillGroupNumber(skill.skillGroup))?.label}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<Button variant="secondary" size="medium" onclick={handleCancel} disabled={isSaving}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="primary" size="medium" onclick={saveChanges} disabled={isSaving}>
|
||||
{isSaving ? 'Saving...' : 'Save'}
|
||||
</Button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{#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}
|
||||
|
||||
<section class="details">
|
||||
<DetailsContainer title="Display Names">
|
||||
<DetailItem label="Name (EN)" bind:value={editData.nameEn} editable={true} type="text" />
|
||||
<DetailItem label="Name (JP)" bind:value={editData.nameJp} editable={true} type="text" />
|
||||
</DetailsContainer>
|
||||
|
||||
<DetailsContainer title="Game Names (for Import Matching)">
|
||||
<DetailItem
|
||||
label="Game Name (EN)"
|
||||
sublabel="Used to match skills during artifact import"
|
||||
bind:value={editData.gameNameEn}
|
||||
editable={true}
|
||||
type="text"
|
||||
placeholder="Leave blank to use display name"
|
||||
/>
|
||||
<DetailItem
|
||||
label="Game Name (JP)"
|
||||
sublabel="Used to match skills during artifact import"
|
||||
bind:value={editData.gameNameJp}
|
||||
editable={true}
|
||||
type="text"
|
||||
placeholder="Leave blank to use display name"
|
||||
/>
|
||||
</DetailsContainer>
|
||||
|
||||
<DetailsContainer title="Skill Properties">
|
||||
<DetailItem
|
||||
label="Skill Group"
|
||||
bind:value={editData.skillGroup}
|
||||
editable={true}
|
||||
type="select"
|
||||
options={skillGroupOptions}
|
||||
/>
|
||||
<DetailItem
|
||||
label="Modifier"
|
||||
bind:value={editData.modifier}
|
||||
editable={true}
|
||||
type="number"
|
||||
/>
|
||||
<DetailItem
|
||||
label="Polarity"
|
||||
bind:value={editData.polarity}
|
||||
editable={true}
|
||||
type="select"
|
||||
options={polarityOptions}
|
||||
/>
|
||||
<DetailItem
|
||||
label="Growth"
|
||||
bind:value={editData.growth}
|
||||
editable={true}
|
||||
type="number"
|
||||
/>
|
||||
</DetailsContainer>
|
||||
|
||||
<DetailsContainer title="Base Values (Quality 1-5)">
|
||||
{#each [0, 1, 2, 3, 4] as index}
|
||||
<DetailItem label="Quality {index + 1}" editable={true}>
|
||||
<Input
|
||||
type="number"
|
||||
variant="number"
|
||||
contained={true}
|
||||
value={editData.baseValues[index] ?? ''}
|
||||
oninput={(e) => {
|
||||
const newValues = [...editData.baseValues]
|
||||
const val = e.currentTarget.value
|
||||
newValues[index] = val === '' ? null : parseFloat(val)
|
||||
editData.baseValues = newValues
|
||||
}}
|
||||
placeholder="—"
|
||||
/>
|
||||
</DetailItem>
|
||||
{/each}
|
||||
</DetailsContainer>
|
||||
|
||||
<DetailsContainer title="Suffix">
|
||||
<DetailItem
|
||||
label="Suffix (EN)"
|
||||
bind:value={editData.suffixEn}
|
||||
editable={true}
|
||||
type="text"
|
||||
placeholder="%"
|
||||
/>
|
||||
<DetailItem
|
||||
label="Suffix (JP)"
|
||||
bind:value={editData.suffixJp}
|
||||
editable={true}
|
||||
type="text"
|
||||
placeholder="%"
|
||||
/>
|
||||
</DetailsContainer>
|
||||
</section>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="not-found">
|
||||
<h2>Skill Not Found</h2>
|
||||
<p>The skill you're looking for could not be found.</p>
|
||||
<button onclick={() => goto('/database/artifact-skills')}>Back to Skills</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@use '$src/themes/colors' as colors;
|
||||
@use '$src/themes/layout' as layout;
|
||||
@use '$src/themes/spacing' as spacing;
|
||||
@use '$src/themes/typography' as typography;
|
||||
@use '$src/themes/effects' as effects;
|
||||
|
||||
.page {
|
||||
background: white;
|
||||
border-radius: layout.$card-corner;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.loading,
|
||||
.error {
|
||||
text-align: center;
|
||||
padding: spacing.$unit * 4;
|
||||
color: colors.$grey-50;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: colors.$red;
|
||||
}
|
||||
|
||||
.content {
|
||||
background: white;
|
||||
border-radius: layout.$card-corner;
|
||||
overflow: visible;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: spacing.$unit * 2;
|
||||
padding: spacing.$unit * 2;
|
||||
background: white;
|
||||
border-top-left-radius: layout.$card-corner;
|
||||
border-top-right-radius: layout.$card-corner;
|
||||
|
||||
.left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: spacing.$unit-2x;
|
||||
}
|
||||
|
||||
.right {
|
||||
display: flex;
|
||||
gap: spacing.$unit;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modifier-badge {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 48px;
|
||||
height: 48px;
|
||||
padding: 0 spacing.$unit;
|
||||
background: colors.$blue;
|
||||
color: white;
|
||||
border-radius: layout.$item-corner;
|
||||
font-size: typography.$font-xlarge;
|
||||
font-weight: typography.$bold;
|
||||
}
|
||||
|
||||
.info {
|
||||
flex: 1;
|
||||
|
||||
h2 {
|
||||
font-size: typography.$font-xlarge;
|
||||
font-weight: typography.$bold;
|
||||
margin: 0 0 spacing.$unit-half 0;
|
||||
color: colors.$grey-30;
|
||||
}
|
||||
|
||||
.meta {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: spacing.$unit;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.skill-group {
|
||||
font-size: typography.$font-small;
|
||||
color: colors.$grey-50;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
.details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.not-found {
|
||||
text-align: center;
|
||||
padding: spacing.$unit * 4;
|
||||
|
||||
button {
|
||||
background: colors.$blue;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: spacing.$unit spacing.$unit-2x;
|
||||
border-radius: layout.$item-corner;
|
||||
cursor: pointer;
|
||||
margin-top: spacing.$unit;
|
||||
|
||||
&:hover {
|
||||
filter: brightness(0.9);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Loading…
Reference in a new issue