weapon series detail/edit pages (#449)

## Summary
- add detail and edit pages for weapon series at
`/database/series/weapons/[slug]`
- fix series table navigation to use slug instead of uuid
- add `?view=series` URL param for bookmarkable series view
- remove redundant `/database/series` page
- rename augment type `none` → `no_augment` for consistency

## Test plan
- [ ] navigate to /database/weapons, toggle to series view
- [ ] verify URL updates to ?view=series
- [ ] click a series row, should go to detail page
- [ ] back button returns to series view
- [ ] edit page saves correctly
This commit is contained in:
Justin Edmund 2025-12-31 23:55:03 -08:00 committed by GitHub
parent 839365a5a1
commit 1b00889e81
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 496 additions and 261 deletions

View file

@ -29,7 +29,7 @@ describe('EntityAdapter', () => {
name: { en: 'Dark Opus', ja: 'ダークオーパス' },
hasWeaponKeys: true,
hasAwakening: true,
augmentType: 'none',
augmentType: 'no_augment',
extra: false,
elementChangeable: false
},

View file

@ -29,7 +29,7 @@ describe('GridAdapter', () => {
name: { en: 'Dark Opus', ja: 'ダークオーパス' },
hasWeaponKeys: true,
hasAwakening: true,
augmentType: 'none',
augmentType: 'no_augment',
extra: false,
elementChangeable: false
},

View file

@ -114,7 +114,7 @@
const keySlotCount = $derived(seriesSlug ? (WEAPON_KEY_SLOTS[seriesSlug] ?? 2) : 0)
// Augment type from series determines AX skills vs befoulment
const augmentType = $derived(series?.augmentType ?? 'none')
const augmentType = $derived(series?.augmentType ?? 'no_augment')
const hasAxSkills = $derived(augmentType === 'ax')
const hasBefoulment = $derived(augmentType === 'befoulment')
const hasAwakening = $derived((weaponData?.maxAwakeningLevel ?? 0) > 0)

View file

@ -82,7 +82,7 @@
const keySlotCount = $derived(seriesSlug ? (WEAPON_KEY_SLOTS[seriesSlug] ?? 2) : 0)
// Augment type from series determines AX skills vs befoulment
const augmentType = $derived(series?.augmentType ?? 'none')
const augmentType = $derived(series?.augmentType ?? 'no_augment')
const hasAxSkills = $derived(augmentType === 'ax')
const hasBefoulment = $derived(augmentType === 'befoulment')
const hasAwakening = $derived((weaponData?.maxAwakeningLevel ?? 0) > 0)

View file

@ -20,7 +20,7 @@ export interface WeaponSeriesRef {
name: { en: string; ja: string }
hasWeaponKeys: boolean
hasAwakening: boolean
/** Type of augment this series supports: "ax", "befoulment", or "none" */
/** Type of augment this series supports: "ax", "befoulment", or "no_augment" */
augmentType: AugmentType
extra: boolean
elementChangeable: boolean
@ -40,7 +40,7 @@ export interface WeaponSeries {
elementChangeable: boolean
hasWeaponKeys: boolean
hasAwakening: boolean
/** Type of augment this series supports: "ax", "befoulment", or "none" */
/** Type of augment this series supports: "ax", "befoulment", or "no_augment" */
augmentType: AugmentType
// Only included in :full view (show endpoint)
weaponCount?: number

View file

@ -11,7 +11,7 @@
* Augment type enum for weapon series.
* Determines whether a weapon series supports AX skills, befoulments, or neither.
*/
export type AugmentType = 'none' | 'ax' | 'befoulment'
export type AugmentType = 'no_augment' | 'ax' | 'befoulment'
/**
* WeaponStatModifier from the API.

View file

@ -0,0 +1,42 @@
import type { AugmentType } from '$lib/types/api/weaponStatModifier'
interface AugmentTypeData {
en: string
ja: string
}
export const AUGMENT_TYPES: Record<AugmentType, AugmentTypeData> = {
no_augment: { en: 'None', ja: 'なし' },
ax: { en: 'AX Skills', ja: 'AXスキル' },
befoulment: { en: 'Befoulment', ja: '禍スキル' }
}
export const AUGMENT_TYPE_LABELS: Record<AugmentType, string> = {
no_augment: 'None',
ax: 'AX Skills',
befoulment: 'Befoulment'
}
export function getAugmentTypeLabel(type?: AugmentType): string {
if (!type) return '—'
return AUGMENT_TYPE_LABELS[type] || '—'
}
export function getAugmentTypeName(type?: AugmentType, locale: 'en' | 'ja' = 'en'): string {
if (!type) return '—'
const data = AUGMENT_TYPES[type]
if (!data) return '—'
return data[locale]
}
export function getAugmentTypeOptions(): Array<{ value: AugmentType; label: string }> {
return Object.entries(AUGMENT_TYPE_LABELS).map(([value, label]) => ({
value: value as AugmentType,
label
}))
}
export function getAugmentTypeClass(type?: AugmentType): string {
if (!type || type === 'no_augment') return ''
return `augment-${type}`
}

View file

@ -116,8 +116,8 @@ export function canWeaponBeModified(gridWeapon: GridWeapon | undefined): boolean
const hasWeaponKeys = seriesHasWeaponKeys(weapon.series)
// AX skills or Befoulment - check augmentType from series
const augmentType = weapon.series?.augmentType ?? 'none'
const hasAugments = augmentType !== 'none'
const augmentType = weapon.series?.augmentType ?? 'no_augment'
const hasAugments = augmentType !== 'no_augment'
// Awakening (maxAwakeningLevel > 0 means it can have awakening)
const hasAwakening = (weapon.maxAwakeningLevel ?? 0) > 0

View file

@ -1,241 +0,0 @@
<svelte:options runes={true} />
<script lang="ts">
import PageMeta from '$lib/components/PageMeta.svelte'
import * as m from '$lib/paraglide/messages'
import { createQuery } from '@tanstack/svelte-query'
import { entityQueries } from '$lib/api/queries/entity.queries'
import SegmentedControl from '$lib/components/ui/segmented-control/SegmentedControl.svelte'
import Segment from '$lib/components/ui/segmented-control/Segment.svelte'
type SeriesType = 'weapons' | 'characters' | 'summons'
let activeType = $state<SeriesType>('weapons')
// Fetch all series lists
const weaponSeriesQuery = createQuery(() => entityQueries.weaponSeriesList())
const characterSeriesQuery = createQuery(() => entityQueries.characterSeriesList())
const summonSeriesQuery = createQuery(() => entityQueries.summonSeriesList())
// Get active query based on selected type
const activeQuery = $derived.by(() => {
switch (activeType) {
case 'weapons':
return weaponSeriesQuery
case 'characters':
return characterSeriesQuery
case 'summons':
return summonSeriesQuery
}
})
// Get sorted data
const sortedData = $derived.by(() => {
if (!activeQuery.data) return []
return [...activeQuery.data].sort((a, b) => a.order - b.order)
})
// Check if the current type has flags (only weapons)
const hasFlags = $derived(activeType === 'weapons')
</script>
<PageMeta title={m.page_title_db_series()} description={m.page_desc_home()} />
<div class="database-page">
<div class="grid-container">
<nav class="series-nav" aria-label="Series type">
<SegmentedControl bind:value={activeType} variant="blended" size="small">
<Segment value="weapons">Weapons</Segment>
<Segment value="characters">Characters</Segment>
<Segment value="summons">Summons</Segment>
</SegmentedControl>
</nav>
{#if activeQuery.isPending}
<div class="loading">Loading {activeType} series...</div>
{:else if activeQuery.error}
<div class="error">Failed to load {activeType} series</div>
{:else if sortedData.length > 0}
<div class="series-table">
<table>
<thead>
<tr>
<th class="order">Order</th>
<th class="name">Name (EN)</th>
<th class="name-ja">Name (JA)</th>
<th class="slug">Slug</th>
{#if hasFlags}
<th class="flags">Flags</th>
{/if}
</tr>
</thead>
<tbody>
{#each sortedData as series (series.id)}
<tr>
<td class="order">{series.order}</td>
<td class="name">{series.name.en}</td>
<td class="name-ja">{series.name.ja}</td>
<td class="slug"><code>{series.slug}</code></td>
{#if hasFlags && 'extra' in series}
<td class="flags">
{#if series.extra}<span class="flag extra">Extra</span>{/if}
{#if series.elementChangeable}<span class="flag element">Element</span>{/if}
{#if series.hasWeaponKeys}<span class="flag keys">Keys</span>{/if}
{#if series.hasAwakening}<span class="flag awaken">Awaken</span>{/if}
{#if series.hasAxSkills}<span class="flag ax">AX</span>{/if}
</td>
{/if}
</tr>
{/each}
</tbody>
</table>
</div>
{:else}
<div class="empty">No {activeType} series found</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;
.database-page {
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;
}
.series-nav {
display: flex;
align-items: center;
gap: spacing.$unit-2x;
padding: spacing.$unit-2x;
border-bottom: 1px solid var(--border-subtle);
}
.loading,
.error,
.empty {
padding: spacing.$unit-4x;
text-align: center;
color: var(--text-secondary);
}
.error {
color: var(--text-error, #ef4444);
}
.series-table {
overflow-x: auto;
table {
width: 100%;
border-collapse: collapse;
}
th,
td {
padding: spacing.$unit-2x spacing.$unit-3x;
text-align: left;
border-bottom: 1px solid var(--border-secondary, #e5e5e5);
}
th {
background: var(--table-header-bg, #f9f9f9);
font-weight: typography.$medium;
font-size: typography.$font-small;
color: colors.$grey-40;
}
td {
font-size: typography.$font-regular;
}
.order {
width: 80px;
text-align: center;
}
.name {
min-width: 200px;
}
.name-ja {
min-width: 180px;
}
.slug code {
font-family: 'SF Mono', Monaco, monospace;
font-size: typography.$font-small;
background: var(--code-bg, #f0f0f0);
padding: spacing.$unit-fourth spacing.$unit-half;
border-radius: 4px;
}
.flags {
.flag {
display: inline-block;
font-size: typography.$font-tiny;
padding: spacing.$unit-fourth spacing.$unit-half;
margin-right: spacing.$unit-half;
border-radius: 4px;
font-weight: typography.$medium;
&.extra {
background: #f3e8ff;
color: #6b21a8;
}
&.element {
background: linear-gradient(
to right,
#fecaca,
#fef08a,
#bbf7d0,
#bfdbfe,
#e9d5ff,
#fbcfe8
);
color: #374151;
}
&.keys {
background: #fef3c7;
color: #92400e;
}
&.awaken {
background: #dcfce7;
color: #166534;
}
&.ax {
background: #ffe4e6;
color: #9f1239;
}
}
}
tbody tr:nth-child(odd) {
background: var(--table-row-alt, #fafafa);
}
tbody tr:hover {
background: var(--table-row-hover, #f0f0f0);
}
tbody tr:last-child td {
border-bottom: none;
}
}
</style>

View file

@ -0,0 +1,28 @@
import type { PageServerLoad } from './$types'
import { entityAdapter } from '$lib/api/adapters/entity.adapter'
import { error } from '@sveltejs/kit'
export const load: PageServerLoad = async ({ params, parent }) => {
const parentData = await parent()
try {
const series = await entityAdapter.getWeaponSeries(params.slug)
if (!series) {
throw error(404, 'Weapon series not found')
}
return {
series,
role: parentData.role
}
} catch (err) {
console.error('Failed to load weapon series:', err)
if (err instanceof Error && 'status' in err && err.status === 404) {
throw error(404, 'Weapon series not found')
}
throw error(500, 'Failed to load weapon series')
}
}

View file

@ -0,0 +1,110 @@
<svelte:options runes={true} />
<script lang="ts">
import { goto } from '$app/navigation'
import { createQuery } from '@tanstack/svelte-query'
import { entityQueries } from '$lib/api/queries/entity.queries'
import { withInitialData } from '$lib/query/ssr'
import PageMeta from '$lib/components/PageMeta.svelte'
import * as m from '$lib/paraglide/messages'
import DatabasePageHeader from '$lib/components/database/DatabasePageHeader.svelte'
import DetailsContainer from '$lib/components/ui/DetailsContainer.svelte'
import DetailItem from '$lib/components/ui/DetailItem.svelte'
import Button from '$lib/components/ui/Button.svelte'
import { getAugmentTypeLabel } from '$lib/utils/augmentType'
import type { PageData } from './$types'
let { data }: { data: PageData } = $props()
const seriesQuery = createQuery(() => ({
...entityQueries.weaponSeries(data.series?.slug ?? ''),
...withInitialData(data.series)
}))
const series = $derived(seriesQuery.data)
const userRole = $derived(data.role || 0)
const canEdit = $derived(userRole >= 7)
const editUrl = $derived(series?.slug ? `/database/series/weapons/${series.slug}/edit` : undefined)
const pageTitle = $derived(series?.name?.en ? `${series.name.en} Series` : 'Weapon Series')
</script>
<PageMeta title={pageTitle} description={m.page_desc_home()} />
<div class="page">
<DatabasePageHeader title="Weapon Series" backHref="/database/weapons?view=series">
{#snippet rightAction()}
{#if canEdit && editUrl}
<Button variant="ghost" size="small" href={editUrl}>Edit</Button>
{/if}
{/snippet}
</DatabasePageHeader>
{#if series}
<div class="content">
<DetailsContainer title="Basic Info">
<DetailItem label="Name (EN)" value={series.name.en} />
<DetailItem label="Name (JA)" value={series.name.ja} />
<DetailItem label="Slug" value={series.slug} />
<DetailItem label="Order" value={series.order} />
</DetailsContainer>
<DetailsContainer title="Flags">
<DetailItem label="Extra Grid" value={series.extra ? 'Yes' : 'No'} />
<DetailItem label="Element Changeable" value={series.elementChangeable ? 'Yes' : 'No'} />
<DetailItem label="Has Weapon Keys" value={series.hasWeaponKeys ? 'Yes' : 'No'} />
<DetailItem label="Has Awakening" value={series.hasAwakening ? 'Yes' : 'No'} />
<DetailItem label="Augment Type" value={getAugmentTypeLabel(series.augmentType)} />
</DetailsContainer>
{#if series.weaponCount !== undefined}
<DetailsContainer title="Statistics">
<DetailItem label="Weapon Count" value={series.weaponCount} />
</DetailsContainer>
{/if}
</div>
{:else}
<div class="not-found">
<h2>Series Not Found</h2>
<p>The weapon series you're looking for could not be found.</p>
<button onclick={() => goto('/database/weapons?view=series')}>Back to Series</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;
.page {
background: white;
border-radius: layout.$page-corner;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.content {
display: flex;
flex-direction: column;
}
.not-found {
text-align: center;
padding: spacing.$unit * 4;
button {
background: #007bff;
color: white;
border: none;
padding: spacing.$unit-half spacing.$unit;
border-radius: 4px;
cursor: pointer;
margin-top: spacing.$unit;
&:hover {
background: #0056b3;
}
}
}
</style>

View file

@ -0,0 +1,33 @@
import type { PageServerLoad } from './$types'
import { entityAdapter } from '$lib/api/adapters/entity.adapter'
import { error, redirect } from '@sveltejs/kit'
export const load: PageServerLoad = async ({ params, parent }) => {
const parentData = await parent()
// Role check - must be editor level (>= 7) to edit
if (!parentData.role || parentData.role < 7) {
throw redirect(303, `/database/series/weapons/${params.slug}`)
}
try {
const series = await entityAdapter.getWeaponSeries(params.slug)
if (!series) {
throw error(404, 'Weapon series not found')
}
return {
series,
role: parentData.role
}
} catch (err) {
console.error('Failed to load weapon series:', err)
if (err instanceof Error && 'status' in err && err.status === 404) {
throw error(404, 'Weapon series not found')
}
throw error(500, 'Failed to load weapon series')
}
}

View file

@ -0,0 +1,252 @@
<svelte:options runes={true} />
<script lang="ts">
import { goto } from '$app/navigation'
import { createQuery, useQueryClient } from '@tanstack/svelte-query'
import { entityQueries } from '$lib/api/queries/entity.queries'
import { entityAdapter } from '$lib/api/adapters/entity.adapter'
import { withInitialData } from '$lib/query/ssr'
import PageMeta from '$lib/components/PageMeta.svelte'
import * as m from '$lib/paraglide/messages'
import DatabasePageHeader from '$lib/components/database/DatabasePageHeader.svelte'
import DetailsContainer from '$lib/components/ui/DetailsContainer.svelte'
import DetailItem from '$lib/components/ui/DetailItem.svelte'
import Button from '$lib/components/ui/Button.svelte'
import { getAugmentTypeOptions } from '$lib/utils/augmentType'
import type { AugmentType } from '$lib/types/api/weaponStatModifier'
import type { PageData } from './$types'
let { data }: { data: PageData } = $props()
const queryClient = useQueryClient()
const seriesQuery = createQuery(() => ({
...entityQueries.weaponSeries(data.series?.slug ?? ''),
...withInitialData(data.series)
}))
const series = $derived(seriesQuery.data)
const pageTitle = $derived(series?.name?.en ? `Edit ${series.name.en}` : 'Edit Weapon Series')
// Save state
let isSaving = $state(false)
let saveError = $state<string | null>(null)
// Editable fields
let editData = $state({
nameEn: '',
nameJa: '',
slug: '',
order: 0,
extra: false,
elementChangeable: false,
hasWeaponKeys: false,
hasAwakening: false,
augmentType: 'no_augment' as AugmentType
})
// Populate edit data when series loads
$effect(() => {
if (series) {
editData = {
nameEn: series.name.en || '',
nameJa: series.name.ja || '',
slug: series.slug || '',
order: series.order || 0,
extra: series.extra || false,
elementChangeable: series.elementChangeable || false,
hasWeaponKeys: series.hasWeaponKeys || false,
hasAwakening: series.hasAwakening || false,
augmentType: series.augmentType || 'no_augment'
}
}
})
// Augment type options for dropdown
const augmentTypeOptions = getAugmentTypeOptions().map((opt) => ({
value: opt.value,
label: opt.label
}))
async function saveChanges() {
if (!series?.id) return
isSaving = true
saveError = null
try {
const payload = {
name_en: editData.nameEn,
name_jp: editData.nameJa,
slug: editData.slug,
order: editData.order,
extra: editData.extra,
element_changeable: editData.elementChangeable,
has_weapon_keys: editData.hasWeaponKeys,
has_awakening: editData.hasAwakening,
augment_type: editData.augmentType
}
await entityAdapter.updateWeaponSeries(series.id, payload)
// Invalidate cache
await queryClient.invalidateQueries({
queryKey: ['weaponSeries'],
refetchType: 'all'
})
// Navigate back to detail page (use new slug if changed)
goto(`/database/series/weapons/${editData.slug}`)
} catch (error) {
saveError = 'Failed to save changes. Please try again.'
console.error('Save error:', error)
} finally {
isSaving = false
}
}
</script>
<PageMeta title={pageTitle} description={m.page_desc_home()} />
<div class="page">
<DatabasePageHeader title="Edit Weapon Series" backHref={`/database/series/weapons/${series?.slug}`}>
{#snippet rightAction()}
<Button variant="ghost" size="small" onclick={saveChanges} disabled={isSaving}>
{isSaving ? 'Saving...' : 'Save'}
</Button>
{/snippet}
</DatabasePageHeader>
{#if series}
<div class="content">
{#if saveError}
<div class="error-banner">{saveError}</div>
{/if}
<DetailsContainer title="Basic Info">
<DetailItem
label="Name (EN)"
bind:value={editData.nameEn}
editable={true}
type="text"
placeholder="English name"
width="320px"
/>
<DetailItem
label="Name (JA)"
bind:value={editData.nameJa}
editable={true}
type="text"
placeholder="Japanese name"
width="320px"
/>
<DetailItem
label="Slug"
bind:value={editData.slug}
editable={true}
type="text"
placeholder="url-friendly-slug"
width="240px"
/>
<DetailItem
label="Order"
bind:value={editData.order}
editable={true}
type="number"
placeholder="0"
/>
</DetailsContainer>
<DetailsContainer title="Flags">
<DetailItem
label="Extra Grid"
sublabel="Weapon can be placed in Extra grid slots"
bind:value={editData.extra}
editable={true}
type="checkbox"
/>
<DetailItem
label="Element Changeable"
sublabel="Weapon element can be changed by player"
bind:value={editData.elementChangeable}
editable={true}
type="checkbox"
/>
<DetailItem
label="Has Weapon Keys"
sublabel="Weapon supports Pendulum/Teluma keys"
bind:value={editData.hasWeaponKeys}
editable={true}
type="checkbox"
/>
<DetailItem
label="Has Awakening"
sublabel="Weapon can be awakened"
bind:value={editData.hasAwakening}
editable={true}
type="checkbox"
/>
<DetailItem
label="Augment Type"
sublabel="Type of stat augments this series supports"
bind:value={editData.augmentType}
editable={true}
type="select"
options={augmentTypeOptions}
/>
</DetailsContainer>
</div>
{:else}
<div class="not-found">
<h2>Series Not Found</h2>
<p>The weapon series you're looking for could not be found.</p>
<button onclick={() => goto('/database/weapons?view=series')}>Back to Series</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;
.page {
background: white;
border-radius: layout.$page-corner;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.content {
display: flex;
flex-direction: column;
}
.error-banner {
background: #fef2f2;
border: 1px solid #fecaca;
color: #dc2626;
padding: spacing.$unit-2x;
margin: spacing.$unit-2x;
border-radius: layout.$item-corner;
}
.not-found {
text-align: center;
padding: spacing.$unit * 4;
button {
background: #007bff;
color: white;
border: none;
padding: spacing.$unit-half spacing.$unit;
border-radius: 4px;
cursor: pointer;
margin-top: spacing.$unit;
&:hover {
background: #0056b3;
}
}
}
</style>

View file

@ -4,6 +4,7 @@
import PageMeta from '$lib/components/PageMeta.svelte'
import * as m from '$lib/paraglide/messages'
import { goto } from '$app/navigation'
import { page } from '$app/stores'
import { createQuery } from '@tanstack/svelte-query'
import { entityQueries } from '$lib/api/queries/entity.queries'
import DatabaseGridWithProvider from '$lib/components/database/DatabaseGridWithProvider.svelte'
@ -17,8 +18,19 @@
import LastUpdatedCell from '$lib/components/database/cells/LastUpdatedCell.svelte'
import { getRarityLabel } from '$lib/utils/rarity'
// View mode state
let viewMode = $state<'weapons' | 'series'>('weapons')
// View mode state - read initial value from URL
const initialView = $page.url.searchParams.get('view')
let viewMode = $state<'weapons' | 'series'>(initialView === 'series' ? 'series' : 'weapons')
// Sync viewMode changes to URL
$effect(() => {
const currentView = $page.url.searchParams.get('view')
if (viewMode === 'series' && currentView !== 'series') {
goto('?view=series', { replaceState: true, noScroll: true })
} else if (viewMode === 'weapons' && currentView === 'series') {
goto('/database/weapons', { replaceState: true, noScroll: true })
}
})
// Query for weapon series
const weaponSeriesQuery = createQuery(() => entityQueries.weaponSeriesList())
@ -30,8 +42,8 @@
})
// Navigate to series detail
function handleSeriesClick(seriesId: string) {
goto(`/database/series/${seriesId}`)
function handleSeriesClick(slug: string) {
goto(`/database/series/weapons/${slug}`)
}
// Column configuration for weapons
@ -130,7 +142,7 @@
</thead>
<tbody>
{#each sortedSeries as series (series.id)}
<tr onclick={() => handleSeriesClick(series.id)} class="clickable">
<tr onclick={() => handleSeriesClick(series.slug)} class="clickable">
<td class="col-order">{series.order}</td>
<td class="col-name">
<span class="series-name">{series.name.en}</span>
@ -188,8 +200,7 @@
.controls {
display: flex;
align-items: center;
padding: spacing.$unit;
border-bottom: 1px solid #e5e5e5;
padding: spacing.$unit-2x;
gap: spacing.$unit;
}
@ -216,7 +227,7 @@
th,
td {
padding: spacing.$unit spacing.$unit-2x;
padding: spacing.$unit-2x spacing.$unit-2x;
text-align: left;
border-bottom: 1px solid #e5e5e5;
}
@ -262,7 +273,7 @@
}
.series-name {
font-weight: typography.$bold;
font-weight: typography.$normal;
}
.no-flags {

View file

@ -19,7 +19,7 @@ const mockOpusSeries = {
name: { en: 'Opus', ja: 'オプス' },
hasWeaponKeys: true,
hasAwakening: true,
augmentType: 'none' as const,
augmentType: 'no_augment' as const,
extra: false,
elementChangeable: false
};
@ -30,7 +30,7 @@ const mockDraconicSeries = {
name: { en: 'Draconic', ja: 'ドラゴニック' },
hasWeaponKeys: true,
hasAwakening: false,
augmentType: 'none' as const,
augmentType: 'no_augment' as const,
extra: false,
elementChangeable: false
};