diff --git a/src/lib/components/database/DatabaseGrid.svelte b/src/lib/components/database/DatabaseGrid.svelte
new file mode 100644
index 00000000..e5f766da
--- /dev/null
+++ b/src/lib/components/database/DatabaseGrid.svelte
@@ -0,0 +1,325 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {#if loading}
+
+ {/if}
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/lib/components/database/cells/CharacterImageCell.svelte b/src/lib/components/database/cells/CharacterImageCell.svelte
new file mode 100644
index 00000000..55c78c42
--- /dev/null
+++ b/src/lib/components/database/cells/CharacterImageCell.svelte
@@ -0,0 +1,35 @@
+
+
+
+
+
+
})
+
+
+
\ No newline at end of file
diff --git a/src/lib/components/database/cells/SummonImageCell.svelte b/src/lib/components/database/cells/SummonImageCell.svelte
new file mode 100644
index 00000000..0baac6e8
--- /dev/null
+++ b/src/lib/components/database/cells/SummonImageCell.svelte
@@ -0,0 +1,35 @@
+
+
+
+
+
+
})
+
+
+
\ No newline at end of file
diff --git a/src/lib/components/database/cells/WeaponImageCell.svelte b/src/lib/components/database/cells/WeaponImageCell.svelte
new file mode 100644
index 00000000..5d15be8f
--- /dev/null
+++ b/src/lib/components/database/cells/WeaponImageCell.svelte
@@ -0,0 +1,35 @@
+
+
+
+
+
+
})
+
+
+
\ No newline at end of file
diff --git a/src/lib/utils/database.ts b/src/lib/utils/database.ts
new file mode 100644
index 00000000..9048711a
--- /dev/null
+++ b/src/lib/utils/database.ts
@@ -0,0 +1,67 @@
+import { TeamElement } from '$lib/types/enums'
+
+export function elementLabel(n?: number): string {
+ switch (n) {
+ case TeamElement.Wind:
+ return 'Wind'
+ case TeamElement.Fire:
+ return 'Fire'
+ case TeamElement.Water:
+ return 'Water'
+ case TeamElement.Earth:
+ return 'Earth'
+ case TeamElement.Dark:
+ return 'Dark'
+ case TeamElement.Light:
+ return 'Light'
+ case TeamElement.Null:
+ return 'Null'
+ default:
+ return '—'
+ }
+}
+
+export function elementClass(n?: number): string {
+ switch (n) {
+ case TeamElement.Wind:
+ return 'element-wind'
+ case TeamElement.Fire:
+ return 'element-fire'
+ case TeamElement.Water:
+ return 'element-water'
+ case TeamElement.Earth:
+ return 'element-earth'
+ case TeamElement.Dark:
+ return 'element-dark'
+ case TeamElement.Light:
+ return 'element-light'
+ default:
+ return ''
+ }
+}
+
+export function getCharacterImageUrl(gbid?: string | number): string {
+ if (!gbid) return '/images/placeholders/placeholder-character-grid.png'
+ return `https://prd-game-a1-granbluefantasy.akamaized.net/assets/img/sp/assets/npc/m/${gbid}_01.jpg`
+}
+
+export function getWeaponImageUrl(gbid?: string | number): string {
+ if (!gbid) return '/images/placeholders/placeholder-weapon-grid.png'
+ return `https://prd-game-a1-granbluefantasy.akamaized.net/assets/img/sp/assets/weapon/m/${gbid}.jpg`
+}
+
+export function getSummonImageUrl(gbid?: string | number): string {
+ if (!gbid) return '/images/placeholders/placeholder-summon-main.png'
+ return `https://prd-game-a1-granbluefantasy.akamaized.net/assets/img/sp/assets/summon/m/${gbid}.jpg`
+}
+
+export function getItemName(item: { name?: string | { en?: string; ja?: string } }): string {
+ const name = item.name
+
+ // Handle name object
+ if (!name) return '—'
+ if (typeof name === 'string') return name
+
+ // Handle name.en/name.ja structure (API returns { en: "...", ja: "..." })
+ return name.en || name.ja || '—'
+}
\ No newline at end of file
diff --git a/src/routes/database/+layout.server.ts b/src/routes/database/+layout.server.ts
new file mode 100644
index 00000000..b12e8629
--- /dev/null
+++ b/src/routes/database/+layout.server.ts
@@ -0,0 +1,21 @@
+import { redirect } from '@sveltejs/kit'
+import type { LayoutServerLoad } from './$types'
+
+export const load: LayoutServerLoad = async ({ locals, url }) => {
+ // Check authentication first
+ if (!locals.session.isAuthenticated) {
+ throw redirect(302, '/login')
+ }
+
+ // Check role authorization
+ const role = locals.session.account?.role ?? 0
+ if (role < 7) {
+ // Redirect to home with no indication of why (security best practice)
+ throw redirect(302, '/')
+ }
+
+ return {
+ role
+ }
+}
+
diff --git a/src/routes/database/+layout.svelte b/src/routes/database/+layout.svelte
new file mode 100644
index 00000000..627e7bf5
--- /dev/null
+++ b/src/routes/database/+layout.svelte
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
diff --git a/src/routes/database/+page.server.ts b/src/routes/database/+page.server.ts
new file mode 100644
index 00000000..9fa3bb74
--- /dev/null
+++ b/src/routes/database/+page.server.ts
@@ -0,0 +1,16 @@
+import { redirect } from '@sveltejs/kit'
+import type { PageServerLoad } from './$types'
+
+export const load: PageServerLoad = async ({ locals }) => {
+ // Double-check authorization at page level
+ if (!locals.session.isAuthenticated) {
+ throw redirect(302, '/login')
+ }
+
+ const role = locals.session.account?.role ?? 0
+ if (role < 7) {
+ throw redirect(302, '/')
+ }
+
+ return {}
+}
\ No newline at end of file
diff --git a/src/routes/database/+page.ts b/src/routes/database/+page.ts
new file mode 100644
index 00000000..547d4f51
--- /dev/null
+++ b/src/routes/database/+page.ts
@@ -0,0 +1,7 @@
+import { redirect } from '@sveltejs/kit'
+import type { PageLoad } from './$types'
+
+export const load: PageLoad = async () => {
+ throw redirect(302, '/database/weapons')
+}
+
diff --git a/src/routes/database/characters/+page.server.ts b/src/routes/database/characters/+page.server.ts
new file mode 100644
index 00000000..48582bb4
--- /dev/null
+++ b/src/routes/database/characters/+page.server.ts
@@ -0,0 +1,16 @@
+import { redirect } from '@sveltejs/kit'
+import type { PageServerLoad } from './$types'
+
+export const load: PageServerLoad = async ({ locals }) => {
+ // Enforce authorization at individual page level
+ if (!locals.session.isAuthenticated) {
+ throw redirect(302, '/login')
+ }
+
+ const role = locals.session.account?.role ?? 0
+ if (role < 7) {
+ throw redirect(302, '/')
+ }
+
+ return {}
+}
\ No newline at end of file
diff --git a/src/routes/database/characters/+page.svelte b/src/routes/database/characters/+page.svelte
new file mode 100644
index 00000000..faaea6eb
--- /dev/null
+++ b/src/routes/database/characters/+page.svelte
@@ -0,0 +1,127 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/routes/database/characters/+page.ts b/src/routes/database/characters/+page.ts
new file mode 100644
index 00000000..4c883757
--- /dev/null
+++ b/src/routes/database/characters/+page.ts
@@ -0,0 +1,18 @@
+import type { PageLoad } from './$types'
+import { searchCharacters } from '$lib/api/resources/search'
+
+export const load: PageLoad = async ({ fetch, url }) => {
+ const page = Number(url.searchParams.get('page') || '1') || 1
+ const pageSize = Number(url.searchParams.get('pageSize') || '20') || 20
+
+ const search = await searchCharacters({ page, per: pageSize }, undefined, fetch)
+
+ return {
+ items: search.results || [],
+ page: search.meta?.page || page,
+ totalPages: search.meta?.total_pages || 1,
+ total: search.meta?.count || 0,
+ pageSize: search.meta?.per_page || pageSize
+ }
+}
+
diff --git a/src/routes/database/summons/+page.server.ts b/src/routes/database/summons/+page.server.ts
new file mode 100644
index 00000000..48582bb4
--- /dev/null
+++ b/src/routes/database/summons/+page.server.ts
@@ -0,0 +1,16 @@
+import { redirect } from '@sveltejs/kit'
+import type { PageServerLoad } from './$types'
+
+export const load: PageServerLoad = async ({ locals }) => {
+ // Enforce authorization at individual page level
+ if (!locals.session.isAuthenticated) {
+ throw redirect(302, '/login')
+ }
+
+ const role = locals.session.account?.role ?? 0
+ if (role < 7) {
+ throw redirect(302, '/')
+ }
+
+ return {}
+}
\ No newline at end of file
diff --git a/src/routes/database/summons/+page.svelte b/src/routes/database/summons/+page.svelte
new file mode 100644
index 00000000..94d7adfe
--- /dev/null
+++ b/src/routes/database/summons/+page.svelte
@@ -0,0 +1,127 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/routes/database/summons/+page.ts b/src/routes/database/summons/+page.ts
new file mode 100644
index 00000000..25f69ec4
--- /dev/null
+++ b/src/routes/database/summons/+page.ts
@@ -0,0 +1,18 @@
+import type { PageLoad } from './$types'
+import { searchSummons } from '$lib/api/resources/search'
+
+export const load: PageLoad = async ({ fetch, url }) => {
+ const page = Number(url.searchParams.get('page') || '1') || 1
+ const pageSize = Number(url.searchParams.get('pageSize') || '20') || 20
+
+ const search = await searchSummons({ page, per: pageSize }, undefined, fetch)
+
+ return {
+ items: search.results || [],
+ page: search.meta?.page || page,
+ totalPages: search.meta?.total_pages || 1,
+ total: search.meta?.count || 0,
+ pageSize: search.meta?.per_page || pageSize
+ }
+}
+
diff --git a/src/routes/database/weapons/+page.server.ts b/src/routes/database/weapons/+page.server.ts
new file mode 100644
index 00000000..48582bb4
--- /dev/null
+++ b/src/routes/database/weapons/+page.server.ts
@@ -0,0 +1,16 @@
+import { redirect } from '@sveltejs/kit'
+import type { PageServerLoad } from './$types'
+
+export const load: PageServerLoad = async ({ locals }) => {
+ // Enforce authorization at individual page level
+ if (!locals.session.isAuthenticated) {
+ throw redirect(302, '/login')
+ }
+
+ const role = locals.session.account?.role ?? 0
+ if (role < 7) {
+ throw redirect(302, '/')
+ }
+
+ return {}
+}
\ No newline at end of file
diff --git a/src/routes/database/weapons/+page.svelte b/src/routes/database/weapons/+page.svelte
new file mode 100644
index 00000000..92aaaa2d
--- /dev/null
+++ b/src/routes/database/weapons/+page.svelte
@@ -0,0 +1,131 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/routes/database/weapons/+page.ts b/src/routes/database/weapons/+page.ts
new file mode 100644
index 00000000..e94318eb
--- /dev/null
+++ b/src/routes/database/weapons/+page.ts
@@ -0,0 +1,29 @@
+import type { PageLoad } from './$types'
+import { searchWeapons } from '$lib/api/resources/search'
+
+export const load: PageLoad = async ({ fetch, url }) => {
+ const page = Number(url.searchParams.get('page') || '1') || 1
+ const pageSize = Number(url.searchParams.get('pageSize') || '20') || 20
+
+ console.log('[Database Weapons] Loading page:', page, 'pageSize:', pageSize)
+
+ const search = await searchWeapons({ page, per: pageSize }, undefined, fetch)
+
+ console.log('[Database Weapons] API Response:', search)
+ console.log('[Database Weapons] Meta:', search.meta)
+ console.log('[Database Weapons] Results count:', search.results?.length || 0)
+
+ // Extract data from meta object
+ const result = {
+ items: search.results || [],
+ page: search.meta?.page || page,
+ totalPages: search.meta?.total_pages || 1,
+ total: search.meta?.count || 0,
+ pageSize: search.meta?.per_page || pageSize
+ }
+
+ console.log('[Database Weapons] Returning to component:', result)
+
+ return result
+}
+