add card.admin, rse.admin
@@ -6,18 +6,16 @@
|
|||||||
"main": {
|
"main": {
|
||||||
"type": "record",
|
"type": "record",
|
||||||
"key": "literal:self",
|
"key": "literal:self",
|
||||||
"description": "Card game configuration (admin only)",
|
"description": "Card game configuration and master data (admin only)",
|
||||||
"record": {
|
"record": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": ["card", "rate", "createdAt", "updatedAt"],
|
"required": ["gacha", "card", "createdAt", "updatedAt"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"card": {
|
"gacha": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": ["pickup"],
|
"required": ["pickup", "rate"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"pickup": { "type": "integer", "description": "Pickup card ID" }
|
"pickup": { "type": "integer", "description": "Pickup card ID" },
|
||||||
}
|
|
||||||
},
|
|
||||||
"rate": {
|
"rate": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": ["pickup", "rare"],
|
"required": ["pickup", "rare"],
|
||||||
@@ -25,6 +23,39 @@
|
|||||||
"pickup": { "type": "integer", "description": "1/n for pickup rate (100 = 1%)" },
|
"pickup": { "type": "integer", "description": "1/n for pickup rate (100 = 1%)" },
|
||||||
"rare": { "type": "integer", "description": "1/n for rare:1 rate (10 = 10%), rare:2 = 1/(n*10), rare:3 = 1/(n*100)" }
|
"rare": { "type": "integer", "description": "1/n for rare:1 rate (10 = 10%), rare:2 = 1/(n*10), rare:3 = 1/(n*100)" }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"card": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "Card master data",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["id", "character", "name", "text", "cp", "effect"],
|
||||||
|
"properties": {
|
||||||
|
"id": { "type": "integer", "description": "Card ID" },
|
||||||
|
"character": { "type": "integer", "description": "Associated character ID" },
|
||||||
|
"name": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["ja", "en"],
|
||||||
|
"properties": {
|
||||||
|
"ja": { "type": "string" },
|
||||||
|
"en": { "type": "string" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"text": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["ja", "en"],
|
||||||
|
"properties": {
|
||||||
|
"ja": { "type": "string" },
|
||||||
|
"en": { "type": "string" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cp": { "type": "string", "description": "CP type (status, time, damage)" },
|
||||||
|
"effect": { "type": "string", "description": "Effect type (status, fly, mode, damage)" },
|
||||||
|
"key": { "type": "string", "description": "Key binding (R1, L1, Y, X, etc.)" }
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"createdAt": { "type": "string", "format": "datetime" },
|
"createdAt": { "type": "string", "format": "datetime" },
|
||||||
"updatedAt": { "type": "string", "format": "datetime" }
|
"updatedAt": { "type": "string", "format": "datetime" }
|
||||||
|
|||||||
@@ -21,10 +21,12 @@
|
|||||||
"id": { "type": "integer", "description": "Ability ID" },
|
"id": { "type": "integer", "description": "Ability ID" },
|
||||||
"name": { "type": "string", "description": "Ability name (ai, quark, neutron, atom, sun)" },
|
"name": { "type": "string", "description": "Ability name (ai, quark, neutron, atom, sun)" },
|
||||||
"kind": { "type": "string", "description": "Attribute type (consciousness, matter)" },
|
"kind": { "type": "string", "description": "Attribute type (consciousness, matter)" },
|
||||||
|
"color": { "type": "string", "description": "Color code (e.g., #ffd700)" },
|
||||||
"level": { "type": "integer", "description": "Hierarchy level (0=fundamental)" },
|
"level": { "type": "integer", "description": "Hierarchy level (0=fundamental)" },
|
||||||
"relation": { "type": "array", "items": { "type": "integer" }, "description": "Advantage IDs" },
|
"relation": { "type": "array", "items": { "type": "integer" }, "description": "Advantage IDs" },
|
||||||
"weakness": { "type": "array", "items": { "type": "integer" }, "description": "Weakness IDs" },
|
"weakness": { "type": "array", "items": { "type": "integer" }, "description": "Weakness IDs" },
|
||||||
"multiplier": { "type": "integer", "description": "Damage multiplier percent (e.g., 150 = 1.5x)" }
|
"multiplier": { "type": "integer", "description": "Damage multiplier percent (e.g., 150 = 1.5x)" },
|
||||||
|
"phantom": { "type": "boolean", "description": "Whether this ability is phantom/lost" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -55,6 +57,26 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"item": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "Item definitions",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["id", "name", "text"],
|
||||||
|
"properties": {
|
||||||
|
"id": { "type": "integer", "description": "Item ID" },
|
||||||
|
"name": { "type": "string", "description": "Item name" },
|
||||||
|
"text": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Item description (localized)",
|
||||||
|
"properties": {
|
||||||
|
"en": { "type": "string", "description": "English text" },
|
||||||
|
"ja": { "type": "string", "description": "Japanese text" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"collection": {
|
"collection": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"description": "ATProto collection definitions",
|
"description": "ATProto collection definitions",
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 130 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 130 KiB After Width: | Height: | Size: 32 KiB |
BIN
public/card/201.webp
Normal file
|
After Width: | Height: | Size: 130 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 32 KiB |
BIN
public/card/301.webp
Normal file
|
After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 16 KiB |
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"uri": "at://did:plc:6qyecktefllvenje24fcxnie/ai.syui.card.admin/self",
|
||||||
|
"cid": "",
|
||||||
|
"value": {
|
||||||
|
"$type": "ai.syui.card.admin",
|
||||||
|
"gacha": {
|
||||||
|
"pickup": 13,
|
||||||
|
"rate": {
|
||||||
|
"rare": 10,
|
||||||
|
"pickup": 100
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"card": [
|
||||||
|
{ "id": 0, "character": 0, "name": { "ja": "アイ", "en": "ai" }, "text": { "ja": "アイの基礎ステータスは、このカードのcpを基準にアップする。", "en": "This card's CP is referenced for the character's base status" }, "cp": "status", "effect": "status" },
|
||||||
|
{ "id": 1, "character": 0, "name": { "ja": "夢幻", "en": "dream" }, "text": { "ja": "質量解放により星間空間を自由に移動する。飛行時間は、このカードのcpを基準に延長される。", "en": "This card's CP is referenced for flight duration" }, "cp": "time", "effect": "fly", "key": "R1" },
|
||||||
|
{ "id": 2, "character": 0, "name": { "ja": "光彩", "en": "shiny" }, "text": { "ja": "モードと呼ばれる変身を行う。スキル発動時、動作スピードがアップする。継続時間及び効果は、このカードのcpを基準にアップする。", "en": "This card's CP is referenced for transformation duration" }, "cp": "time", "effect": "mode", "key": "L1" },
|
||||||
|
{ "id": 3, "character": 0, "name": { "ja": "中性子", "en": "neutron" }, "text": { "ja": "手のひらに中性子星を作り出して前方に放つ。ヒットした敵に物理と属性の大ダメージを与える。敵に与える物理ダメージは、このカードのcpを基準にアップする。", "en": "This card's CP is referenced for physical damage" }, "cp": "damage", "effect": "damage" , "key": "Y" },
|
||||||
|
{ "id": 13, "character": 0, "name": { "ja": "創造", "en": "create" }, "text": { "ja": "自身の周囲に真空を作り出し、物理と属性の範囲ダメージを与える。敵に与える属性ダメージは、このカードのcpを基準にアップする。", "en": "This card's CP is referenced for elemental damage" }, "cp": "damage", "effect": "damage", "key": "X" },
|
||||||
|
{ "id": 100, "character": 1, "name": { "ja": "ユイ", "en": "yui" }, "text": { "ja": "", "en": "" }, "cp": "", "effect": "" },
|
||||||
|
{ "id": 101, "character": 1, "name": { "ja": "", "en": "" }, "text": { "ja": "", "en": "" }, "cp": "", "effect": "" },
|
||||||
|
{ "id": 200, "character": 2, "name": { "ja": "ドラゴン", "en": "dragon" }, "text": { "ja": "", "en": "" }, "cp": "", "effect": "" },
|
||||||
|
{ "id": 201, "character": 2, "name": { "ja": "", "en": "" }, "text": { "ja": "", "en": "" }, "cp": "", "effect": "" },
|
||||||
|
{ "id": 300, "character": 3, "name": { "ja": "ロボット", "en": "robot" }, "text": { "ja": "", "en": "" }, "cp": "", "effect": "" },
|
||||||
|
{ "id": 301, "character": 3, "name": { "ja": "", "en": "" }, "text": { "ja": "", "en": "" }, "cp": "", "effect": "" },
|
||||||
|
{ "id": 400, "character": 4, "name": { "ja": "こんにゃーん", "en": "conyan" }, "text": { "ja": "", "en": "" }, "cp": "", "effect": "" },
|
||||||
|
{ "id": 401, "character": 4, "name": { "ja": "", "en": "" }, "text": { "ja": "", "en": "" }, "cp": "", "effect": "" }
|
||||||
|
],
|
||||||
|
"createdAt": "2026-01-25T09:02:20.000Z",
|
||||||
|
"updatedAt": "2026-01-25T09:02:20.000Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"uri": "at://did:plc:6qyecktefllvenje24fcxnie/ai.syui.card.user/self",
|
||||||
|
"cid": "bafyreicjbb6qdl77sdsma5ep674znlgvnhenqqogppx3mmawlpengoxol4",
|
||||||
|
"value": {
|
||||||
|
"card": [
|
||||||
|
{
|
||||||
|
"cp": 464,
|
||||||
|
"id": 1,
|
||||||
|
"cid": "5w4xmnyzukh62",
|
||||||
|
"rare": 1,
|
||||||
|
"unique": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cp": 634,
|
||||||
|
"id": 0,
|
||||||
|
"cid": "6lzqdddvggzcs",
|
||||||
|
"rare": 0,
|
||||||
|
"unique": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cp": 2,
|
||||||
|
"id": 2,
|
||||||
|
"cid": "5cjxquqzywetg",
|
||||||
|
"rare": 0,
|
||||||
|
"unique": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cp": 3,
|
||||||
|
"id": 3,
|
||||||
|
"cid": "bmdlhjwedjlmu",
|
||||||
|
"rare": 0,
|
||||||
|
"unique": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cp": 43,
|
||||||
|
"id": 13,
|
||||||
|
"cid": "25kp6sbgcdug7",
|
||||||
|
"rare": 4,
|
||||||
|
"unique": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"$type": "ai.syui.card.user",
|
||||||
|
"createdAt": "2026-01-28T17:10:10.535Z",
|
||||||
|
"updatedAt": "2026-01-28T17:10:10.535Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,285 @@
|
|||||||
|
{
|
||||||
|
"uri": "at://did:plc:6qyecktefllvenje24fcxnie/ai.syui.rse.admin/self",
|
||||||
|
"cid": "bafyreihgv33l3kdbpghum3ul5uugscfoaooyyx7nu3eaiuge4l6pru6je4",
|
||||||
|
"value": {
|
||||||
|
"item": [
|
||||||
|
{ "id": 0, "name": "a", "text": { "en": "world", "ja": "世界" } },
|
||||||
|
{ "id": 1, "name": "i", "text": { "en": "The Pillar of Creation at the center of the galaxy stands tall in a place called the Eternal Stone, which is said to tolerate no change. The three attributes of gold, silver, and copper are engraved on it.", "ja": "銀河の中心にある創造の柱。そこにそびえ立つ永遠の石には金、銀、銅の3つの属性が刻み込まれている" } },
|
||||||
|
{ "id": 2, "name": "gordbox", "text": { "en": "The first color in this world", "ja": "この世界の最初の色" } },
|
||||||
|
{ "id": 3, "name": "silverbox", "text": { "en": "The second color in this world", "ja": "この世界の2番目の色" } },
|
||||||
|
{ "id": 4, "name": "bluebox", "text": { "en": "The third color in this world. It was once red, but was rewritten by someone", "ja": "この世界の3番目の色。かつては赤だったが、何者かに書き換えられた" } },
|
||||||
|
{ "id": 5, "name": "copperbox", "text": { "en": "The Third Lost Color of the World", "ja": "この世界から失われた3番目の色" } }
|
||||||
|
],
|
||||||
|
"$type": "ai.syui.rse.admin",
|
||||||
|
"system": [
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"name": "ai",
|
||||||
|
"domain": "ability"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "yui",
|
||||||
|
"domain": "unique"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "at",
|
||||||
|
"domain": "account"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"name": "world",
|
||||||
|
"domain": "planet"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"name": "dream",
|
||||||
|
"domain": "origin"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ability": [
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"kind": "i",
|
||||||
|
"name": "ai",
|
||||||
|
"color": "#000",
|
||||||
|
"level": 0,
|
||||||
|
"relation": [
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
3,
|
||||||
|
4,
|
||||||
|
5,
|
||||||
|
6,
|
||||||
|
7,
|
||||||
|
8
|
||||||
|
],
|
||||||
|
"weakness": [
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"multiplier": 150
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"kind": "unique",
|
||||||
|
"name": "yui",
|
||||||
|
"color": "#fff",
|
||||||
|
"level": 0,
|
||||||
|
"relation": [],
|
||||||
|
"weakness": [],
|
||||||
|
"multiplier": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"kind": "phantom",
|
||||||
|
"name": "axion",
|
||||||
|
"color": "#8b00ff",
|
||||||
|
"level": 1,
|
||||||
|
"relation": [
|
||||||
|
3,
|
||||||
|
5,
|
||||||
|
6
|
||||||
|
],
|
||||||
|
"weakness": [
|
||||||
|
4
|
||||||
|
],
|
||||||
|
"multiplier": 150
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"kind": "phantom",
|
||||||
|
"name": "baryon",
|
||||||
|
"color": "#0066cc",
|
||||||
|
"level": 1,
|
||||||
|
"relation": [
|
||||||
|
4,
|
||||||
|
6,
|
||||||
|
7
|
||||||
|
],
|
||||||
|
"weakness": [
|
||||||
|
2
|
||||||
|
],
|
||||||
|
"multiplier": 150
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"kind": "phantom",
|
||||||
|
"name": "photon",
|
||||||
|
"color": "#ffff00",
|
||||||
|
"level": 1,
|
||||||
|
"relation": [
|
||||||
|
2,
|
||||||
|
7,
|
||||||
|
8
|
||||||
|
],
|
||||||
|
"weakness": [
|
||||||
|
3
|
||||||
|
],
|
||||||
|
"multiplier": 150
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"kind": "matter",
|
||||||
|
"name": "quark",
|
||||||
|
"color": "#ff0000",
|
||||||
|
"level": 2,
|
||||||
|
"relation": [
|
||||||
|
6
|
||||||
|
],
|
||||||
|
"weakness": [
|
||||||
|
0,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
"multiplier": 150
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"kind": "matter",
|
||||||
|
"name": "neutron",
|
||||||
|
"color": "#808080",
|
||||||
|
"level": 2,
|
||||||
|
"relation": [
|
||||||
|
7
|
||||||
|
],
|
||||||
|
"weakness": [
|
||||||
|
5,
|
||||||
|
2,
|
||||||
|
3
|
||||||
|
],
|
||||||
|
"multiplier": 150
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 7,
|
||||||
|
"kind": "matter",
|
||||||
|
"name": "atom",
|
||||||
|
"color": "#00cc66",
|
||||||
|
"level": 2,
|
||||||
|
"relation": [
|
||||||
|
8
|
||||||
|
],
|
||||||
|
"weakness": [
|
||||||
|
6,
|
||||||
|
3,
|
||||||
|
4
|
||||||
|
],
|
||||||
|
"multiplier": 150
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 8,
|
||||||
|
"kind": "matter",
|
||||||
|
"name": "sun",
|
||||||
|
"color": "#ff8c00",
|
||||||
|
"level": 2,
|
||||||
|
"relation": [
|
||||||
|
5
|
||||||
|
],
|
||||||
|
"weakness": [
|
||||||
|
7,
|
||||||
|
4
|
||||||
|
],
|
||||||
|
"multiplier": 150
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 9,
|
||||||
|
"kind": "element",
|
||||||
|
"name": "gold",
|
||||||
|
"color": "#ffd700",
|
||||||
|
"level": 3,
|
||||||
|
"relation": [
|
||||||
|
10
|
||||||
|
],
|
||||||
|
"weakness": [
|
||||||
|
11,
|
||||||
|
12
|
||||||
|
],
|
||||||
|
"multiplier": 150
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 10,
|
||||||
|
"kind": "element",
|
||||||
|
"name": "silver",
|
||||||
|
"color": "#c0c0c0",
|
||||||
|
"level": 3,
|
||||||
|
"relation": [
|
||||||
|
11,
|
||||||
|
12
|
||||||
|
],
|
||||||
|
"weakness": [
|
||||||
|
9
|
||||||
|
],
|
||||||
|
"multiplier": 150
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 11,
|
||||||
|
"kind": "element",
|
||||||
|
"name": "copper",
|
||||||
|
"color": "#b87333",
|
||||||
|
"level": 3,
|
||||||
|
"relation": [
|
||||||
|
9
|
||||||
|
],
|
||||||
|
"weakness": [
|
||||||
|
10
|
||||||
|
],
|
||||||
|
"multiplier": 150,
|
||||||
|
"phantom": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 12,
|
||||||
|
"kind": "element",
|
||||||
|
"name": "oxygen",
|
||||||
|
"color": "#87ceeb",
|
||||||
|
"level": 3,
|
||||||
|
"relation": [
|
||||||
|
9
|
||||||
|
],
|
||||||
|
"weakness": [
|
||||||
|
10
|
||||||
|
],
|
||||||
|
"multiplier": 150
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"character": [
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"mode": 2,
|
||||||
|
"name": "ai",
|
||||||
|
"ability": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"mode": 2,
|
||||||
|
"name": "yui",
|
||||||
|
"ability": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"mode": 0,
|
||||||
|
"name": "dragon",
|
||||||
|
"ability": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"mode": 0,
|
||||||
|
"name": "robot",
|
||||||
|
"ability": 5
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"createdAt": "2026-01-27T16:25:09.000Z",
|
||||||
|
"updatedAt": "2026-01-27T16:25:09.000Z",
|
||||||
|
"collection": [
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"name": "card",
|
||||||
|
"nsid": "ai.syui.card"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "rse",
|
||||||
|
"nsid": "ai.syui.rse"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
{
|
||||||
|
"uri": "at://did:plc:6qyecktefllvenje24fcxnie/ai.syui.rse.user/self",
|
||||||
|
"cid": "bafyreih36zbcu2jx3yqxpjftvagarinwalaiodwxgyi5fqnw4iqkgslmya",
|
||||||
|
"value": {
|
||||||
|
"item": [
|
||||||
|
{
|
||||||
|
"cp": 0,
|
||||||
|
"id": 0,
|
||||||
|
"cid": "0",
|
||||||
|
"rare": 0,
|
||||||
|
"unique": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cp": 1,
|
||||||
|
"id": 1,
|
||||||
|
"cid": "1",
|
||||||
|
"rare": 0,
|
||||||
|
"unique": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cp": 2,
|
||||||
|
"id": 2,
|
||||||
|
"cid": "2",
|
||||||
|
"rare": 0,
|
||||||
|
"unique": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cp": 3,
|
||||||
|
"id": 3,
|
||||||
|
"cid": "3",
|
||||||
|
"rare": 0,
|
||||||
|
"unique": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cp": 4,
|
||||||
|
"id": 4,
|
||||||
|
"cid": "4",
|
||||||
|
"rare": 0,
|
||||||
|
"unique": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cp": 5,
|
||||||
|
"id": 5,
|
||||||
|
"cid": "5",
|
||||||
|
"rare": 0,
|
||||||
|
"unique": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"$type": "ai.syui.rse.user",
|
||||||
|
"character": [
|
||||||
|
{
|
||||||
|
"cp": 0,
|
||||||
|
"id": 0,
|
||||||
|
"cid": "0",
|
||||||
|
"rare": 0,
|
||||||
|
"unique": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cp": 1,
|
||||||
|
"id": 1,
|
||||||
|
"cid": "1",
|
||||||
|
"rare": 0,
|
||||||
|
"unique": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cp": 2,
|
||||||
|
"id": 2,
|
||||||
|
"cid": "2",
|
||||||
|
"rare": 0,
|
||||||
|
"unique": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cp": 3,
|
||||||
|
"id": 3,
|
||||||
|
"cid": "3",
|
||||||
|
"rare": 0,
|
||||||
|
"unique": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"createdAt": "2026-01-28T12:04:27.000Z",
|
||||||
|
"updatedAt": "2026-01-28T12:04:27.000Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 27 KiB |
BIN
public/rse/item/1.webp
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
public/rse/item/2.webp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
public/rse/item/3.webp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
public/rse/item/4.webp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
public/rse/item/5.webp
Normal file
|
After Width: | Height: | Size: 18 KiB |
@@ -14,6 +14,33 @@ export interface CardCollection {
|
|||||||
updatedAt: string
|
updatedAt: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CardAdminEntry {
|
||||||
|
id: number
|
||||||
|
character: number
|
||||||
|
name: { ja: string; en: string }
|
||||||
|
text: { ja: string; en: string }
|
||||||
|
cp: string
|
||||||
|
effect: string
|
||||||
|
key?: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CardAdminData {
|
||||||
|
gacha: { pickup: number; rate: { rare: number; pickup: number } }
|
||||||
|
card: CardAdminEntry[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current language
|
||||||
|
function getLang(): string {
|
||||||
|
return localStorage.getItem('preferredLang') || 'ja'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get localized text
|
||||||
|
function getLocalizedText(obj: { ja: string; en: string } | undefined): string {
|
||||||
|
if (!obj) return ''
|
||||||
|
const lang = getLang()
|
||||||
|
return obj[lang as 'ja' | 'en'] || obj.ja || obj.en || ''
|
||||||
|
}
|
||||||
|
|
||||||
// Get rarity class name
|
// Get rarity class name
|
||||||
function getRarityClass(card: UserCard): string {
|
function getRarityClass(card: UserCard): string {
|
||||||
if (card.unique) return 'unique'
|
if (card.unique) return 'unique'
|
||||||
@@ -21,9 +48,13 @@ function getRarityClass(card: UserCard): string {
|
|||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Render single card with optional count badge and admin info
|
||||||
// Render single card with optional count badge
|
export function renderCard(
|
||||||
export function renderCard(card: UserCard, baseUrl: string = '/card', count?: number): string {
|
card: UserCard,
|
||||||
|
baseUrl: string = '/card',
|
||||||
|
count?: number,
|
||||||
|
adminEntry?: CardAdminEntry
|
||||||
|
): string {
|
||||||
const rarityClass = getRarityClass(card)
|
const rarityClass = getRarityClass(card)
|
||||||
const imageUrl = `${baseUrl}/${card.id}.webp`
|
const imageUrl = `${baseUrl}/${card.id}.webp`
|
||||||
|
|
||||||
@@ -34,6 +65,21 @@ export function renderCard(card: UserCard, baseUrl: string = '/card', count?: nu
|
|||||||
|
|
||||||
const countBadge = count && count > 1 ? `<span class="card-count">x${count}</span>` : ''
|
const countBadge = count && count > 1 ? `<span class="card-count">x${count}</span>` : ''
|
||||||
|
|
||||||
|
// Admin info (name, key, text)
|
||||||
|
const name = adminEntry ? getLocalizedText(adminEntry.name) : ''
|
||||||
|
const text = adminEntry ? getLocalizedText(adminEntry.text) : ''
|
||||||
|
const key = adminEntry?.key || ''
|
||||||
|
|
||||||
|
const infoHtml = (name || text || key) ? `
|
||||||
|
<div class="card-info">
|
||||||
|
<div class="card-info-header">
|
||||||
|
<span class="card-info-name">${name}</span>
|
||||||
|
${key ? `<button class="card-key-btn">${key}</button>` : ''}
|
||||||
|
</div>
|
||||||
|
${text ? `<div class="card-info-text">${text}</div>` : ''}
|
||||||
|
</div>
|
||||||
|
` : ''
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="card-item">
|
<div class="card-item">
|
||||||
<div class="card-wrapper" data-card-id="${card.id}" data-cid="${card.cid}">
|
<div class="card-wrapper" data-card-id="${card.id}" data-cid="${card.cid}">
|
||||||
@@ -46,6 +92,7 @@ export function renderCard(card: UserCard, baseUrl: string = '/card', count?: nu
|
|||||||
<div class="card-detail">
|
<div class="card-detail">
|
||||||
<span class="card-cp">${card.cp}</span>
|
<span class="card-cp">${card.cp}</span>
|
||||||
</div>
|
</div>
|
||||||
|
${infoHtml}
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
@@ -109,13 +156,9 @@ export function renderCardPage(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort by unique first, then rarity (desc), then by id
|
// Sort by id
|
||||||
const sortedGroups = Array.from(cardGroups.values())
|
const sortedGroups = Array.from(cardGroups.values())
|
||||||
.sort((a, b) => {
|
.sort((a, b) => a.card.id - b.card.id)
|
||||||
if (a.card.unique !== b.card.unique) return a.card.unique ? -1 : 1
|
|
||||||
if (b.card.rare !== a.card.rare) return b.card.rare - a.card.rare
|
|
||||||
return a.card.id - b.card.id
|
|
||||||
})
|
|
||||||
|
|
||||||
const cardsHtml = sortedGroups.map(({ card, count }) => {
|
const cardsHtml = sortedGroups.map(({ card, count }) => {
|
||||||
return renderCard(card, '/card', count)
|
return renderCard(card, '/card', count)
|
||||||
|
|||||||
@@ -1,4 +1,15 @@
|
|||||||
// RSE display component for ai.syui.rse.user collection
|
// RSE display component for ai.syui.rse.user collection
|
||||||
|
import { renderCard, type UserCard, type CardAdminEntry, type CardAdminData } from './card'
|
||||||
|
|
||||||
|
export interface RseAdminItem {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
text: { ja: string; en: string }
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RseAdminData {
|
||||||
|
item: RseAdminItem[]
|
||||||
|
}
|
||||||
|
|
||||||
export interface RseItem {
|
export interface RseItem {
|
||||||
id: number
|
id: number
|
||||||
@@ -14,25 +25,86 @@ export interface RseCollection {
|
|||||||
updatedAt: string
|
updatedAt: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get current language
|
||||||
|
function getLang(): string {
|
||||||
|
return localStorage.getItem('preferredLang') || 'ja'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get localized text
|
||||||
|
function getLocalizedText(obj: { ja: string; en: string } | undefined): string {
|
||||||
|
if (!obj) return ''
|
||||||
|
const lang = getLang()
|
||||||
|
return obj[lang as 'ja' | 'en'] || obj.ja || obj.en || ''
|
||||||
|
}
|
||||||
|
|
||||||
// Get rarity class from unique flag
|
// Get rarity class from unique flag
|
||||||
function getRarityClass(item: RseItem): string {
|
function getRarityClass(item: RseItem): string {
|
||||||
if (item.unique) return 'unique'
|
if (item.unique) return 'unique'
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render single item/character
|
// Get cards for a character (character 0 = cards 0-99, character 1 = cards 100-199, etc.)
|
||||||
function renderRseItem(item: RseItem, type: 'item' | 'character'): string {
|
function getCardsForCharacter(
|
||||||
|
characterId: number,
|
||||||
|
userCards: UserCard[],
|
||||||
|
adminData: CardAdminData | null
|
||||||
|
): { card: UserCard; adminEntry?: CardAdminEntry }[] {
|
||||||
|
const minId = characterId * 100
|
||||||
|
const maxId = minId + 99
|
||||||
|
|
||||||
|
// Build admin lookup map
|
||||||
|
const adminMap = new Map<number, CardAdminEntry>()
|
||||||
|
if (adminData?.card) {
|
||||||
|
for (const entry of adminData.card) {
|
||||||
|
adminMap.set(entry.id, entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter and dedupe cards for this character
|
||||||
|
const cardGroups = new Map<number, UserCard>()
|
||||||
|
for (const card of userCards) {
|
||||||
|
if (card.id >= minId && card.id <= maxId) {
|
||||||
|
const existing = cardGroups.get(card.id)
|
||||||
|
if (!existing || card.cp > existing.cp || card.unique) {
|
||||||
|
cardGroups.set(card.id, card)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by ID and add admin entries
|
||||||
|
return Array.from(cardGroups.values())
|
||||||
|
.sort((a, b) => a.id - b.id)
|
||||||
|
.map(card => ({
|
||||||
|
card,
|
||||||
|
adminEntry: adminMap.get(card.id)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render character section with its cards below
|
||||||
|
function renderCharacterSection(
|
||||||
|
item: RseItem,
|
||||||
|
userCards: UserCard[],
|
||||||
|
adminData: CardAdminData | null
|
||||||
|
): string {
|
||||||
const rarityClass = getRarityClass(item)
|
const rarityClass = getRarityClass(item)
|
||||||
const effectsHtml = rarityClass ? `
|
const effectsHtml = rarityClass ? `
|
||||||
<div class="card-status pattern-${rarityClass}"></div>
|
<div class="card-status pattern-${rarityClass}"></div>
|
||||||
<div class="card-status color-${rarityClass}"></div>
|
<div class="card-status color-${rarityClass}"></div>
|
||||||
` : ''
|
` : ''
|
||||||
|
|
||||||
|
// Get cards for this character
|
||||||
|
const characterCards = getCardsForCharacter(item.id, userCards, adminData)
|
||||||
|
const cardsHtml = characterCards.map(({ card, adminEntry }) =>
|
||||||
|
renderCard(card, '/card', undefined, adminEntry)
|
||||||
|
).join('')
|
||||||
|
|
||||||
return `
|
return `
|
||||||
|
<div class="rse-character-section">
|
||||||
|
<div class="rse-character-main">
|
||||||
<div class="card-item">
|
<div class="card-item">
|
||||||
<div class="card-wrapper">
|
<div class="card-wrapper">
|
||||||
<div class="card-reflection">
|
<div class="card-reflection">
|
||||||
<img src="/rse/${type}/${item.id}.webp" alt="${type} ${item.id}" loading="lazy" />
|
<img src="/rse/character/${item.id}.webp" alt="character ${item.id}" loading="lazy" />
|
||||||
</div>
|
</div>
|
||||||
${effectsHtml}
|
${effectsHtml}
|
||||||
</div>
|
</div>
|
||||||
@@ -40,13 +112,59 @@ function renderRseItem(item: RseItem, type: 'item' | 'character'): string {
|
|||||||
<span class="card-cp">${item.cp}</span>
|
<span class="card-cp">${item.cp}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
${characterCards.length > 0 ? `
|
||||||
|
<div class="rse-card-grid">${cardsHtml}</div>
|
||||||
|
` : ''}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render single item
|
||||||
|
function renderRseItem(item: RseItem, rseAdminData: RseAdminData | null): string {
|
||||||
|
const rarityClass = getRarityClass(item)
|
||||||
|
const effectsHtml = rarityClass ? `
|
||||||
|
<div class="card-status pattern-${rarityClass}"></div>
|
||||||
|
<div class="card-status color-${rarityClass}"></div>
|
||||||
|
` : ''
|
||||||
|
|
||||||
|
// Get admin entry for this item
|
||||||
|
const adminEntry = rseAdminData?.item?.find(i => i.id === item.id)
|
||||||
|
const name = adminEntry?.name || ''
|
||||||
|
const text = adminEntry ? getLocalizedText(adminEntry.text) : ''
|
||||||
|
|
||||||
|
const infoHtml = (name || text) ? `
|
||||||
|
<div class="card-info">
|
||||||
|
<div class="card-info-header">
|
||||||
|
<span class="card-info-name">${name}</span>
|
||||||
|
</div>
|
||||||
|
${text ? `<div class="card-info-text">${text}</div>` : ''}
|
||||||
|
</div>
|
||||||
|
` : ''
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="card-item">
|
||||||
|
<div class="card-wrapper">
|
||||||
|
<div class="card-reflection">
|
||||||
|
<img src="/rse/item/${item.id}.webp" alt="item ${item.id}" loading="lazy" />
|
||||||
|
</div>
|
||||||
|
${effectsHtml}
|
||||||
|
</div>
|
||||||
|
<div class="card-detail">
|
||||||
|
<span class="card-cp">${item.cp}</span>
|
||||||
|
</div>
|
||||||
|
${infoHtml}
|
||||||
|
</div>
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render RSE page
|
// Render RSE page
|
||||||
export function renderRsePage(
|
export function renderRsePage(
|
||||||
collection: RseCollection | null,
|
collection: RseCollection | null,
|
||||||
handle: string
|
handle: string,
|
||||||
|
userCards: UserCard[] = [],
|
||||||
|
adminData: CardAdminData | null = null,
|
||||||
|
rseAdminData: RseAdminData | null = null
|
||||||
): string {
|
): string {
|
||||||
const jsonUrl = `/@${handle}/at/collection/ai.syui.rse.user/self`
|
const jsonUrl = `/@${handle}/at/collection/ai.syui.rse.user/self`
|
||||||
|
|
||||||
@@ -70,19 +188,14 @@ export function renderRsePage(
|
|||||||
const totalItems = items.length
|
const totalItems = items.length
|
||||||
const uniqueChars = characters.filter(c => c.unique).length
|
const uniqueChars = characters.filter(c => c.unique).length
|
||||||
|
|
||||||
// Sort by unique > id
|
// Sort by id
|
||||||
const sortedChars = [...characters].sort((a, b) => {
|
const sortedChars = [...characters].sort((a, b) => a.id - b.id)
|
||||||
if (a.unique !== b.unique) return a.unique ? -1 : 1
|
const sortedItems = [...items].sort((a, b) => a.id - b.id)
|
||||||
return a.id - b.id
|
|
||||||
})
|
|
||||||
|
|
||||||
const sortedItems = [...items].sort((a, b) => {
|
const charsHtml = sortedChars.map(c =>
|
||||||
if (a.unique !== b.unique) return a.unique ? -1 : 1
|
renderCharacterSection(c, userCards, adminData)
|
||||||
return a.id - b.id
|
).join('')
|
||||||
})
|
const itemsHtml = sortedItems.map(i => renderRseItem(i, rseAdminData)).join('')
|
||||||
|
|
||||||
const charsHtml = sortedChars.map(c => renderRseItem(c, 'character')).join('')
|
|
||||||
const itemsHtml = sortedItems.map(i => renderRseItem(i, 'item')).join('')
|
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="card-page">
|
<div class="card-page">
|
||||||
@@ -107,7 +220,7 @@ export function renderRsePage(
|
|||||||
</div>
|
</div>
|
||||||
${charsHtml ? `
|
${charsHtml ? `
|
||||||
<h3 class="rse-section-title">Characters</h3>
|
<h3 class="rse-section-title">Characters</h3>
|
||||||
<div class="card-grid">${charsHtml}</div>
|
<div class="rse-characters">${charsHtml}</div>
|
||||||
` : ''}
|
` : ''}
|
||||||
${itemsHtml ? `
|
${itemsHtml ? `
|
||||||
<h3 class="rse-section-title">Items</h3>
|
<h3 class="rse-section-title">Items</h3>
|
||||||
|
|||||||
@@ -679,6 +679,105 @@ export interface LinkCollection {
|
|||||||
updatedAt?: string
|
updatedAt?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Card admin data types
|
||||||
|
export interface CardAdminEntry {
|
||||||
|
id: number
|
||||||
|
character: number
|
||||||
|
name: { ja: string; en: string }
|
||||||
|
text: { ja: string; en: string }
|
||||||
|
cp: string
|
||||||
|
effect: string
|
||||||
|
key?: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CardAdminData {
|
||||||
|
gacha: { pickup: number; rate: { rare: number; pickup: number } }
|
||||||
|
card: CardAdminEntry[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get card admin data (ai.syui.card.admin)
|
||||||
|
export async function getCardAdmin(did: string): Promise<CardAdminData | null> {
|
||||||
|
const collection = 'ai.syui.card.admin'
|
||||||
|
|
||||||
|
// Try local first
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/content/${did}/${collection}/self.json`)
|
||||||
|
if (res.ok && isJsonResponse(res)) {
|
||||||
|
const record = await res.json()
|
||||||
|
return record.value as CardAdminData
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Try remote
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remote fallback
|
||||||
|
const pds = await getPds(did)
|
||||||
|
if (!pds) return null
|
||||||
|
|
||||||
|
try {
|
||||||
|
const host = pds.replace('https://', '')
|
||||||
|
const url = `${xrpcUrl(host, comAtprotoRepo.getRecord)}?repo=${did}&collection=${collection}&rkey=self`
|
||||||
|
const res = await fetch(url)
|
||||||
|
if (res.ok) {
|
||||||
|
const record = await res.json()
|
||||||
|
return record.value as CardAdminData
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Failed
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// RSE admin data types
|
||||||
|
export interface RseAdminItem {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
text: { ja: string; en: string }
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RseAdminData {
|
||||||
|
item: RseAdminItem[]
|
||||||
|
ability: unknown[]
|
||||||
|
character: unknown[]
|
||||||
|
system: unknown[]
|
||||||
|
collection: unknown[]
|
||||||
|
createdAt: string
|
||||||
|
updatedAt: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get RSE admin data (ai.syui.rse.admin)
|
||||||
|
export async function getRseAdmin(did: string): Promise<RseAdminData | null> {
|
||||||
|
const collection = 'ai.syui.rse.admin'
|
||||||
|
|
||||||
|
// Try local first
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/content/${did}/${collection}/self.json`)
|
||||||
|
if (res.ok && isJsonResponse(res)) {
|
||||||
|
const record = await res.json()
|
||||||
|
return record.value as RseAdminData
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Try remote
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remote fallback
|
||||||
|
const pds = await getPds(did)
|
||||||
|
if (!pds) return null
|
||||||
|
|
||||||
|
try {
|
||||||
|
const host = pds.replace('https://', '')
|
||||||
|
const url = `${xrpcUrl(host, comAtprotoRepo.getRecord)}?repo=${did}&collection=${collection}&rkey=self`
|
||||||
|
const res = await fetch(url)
|
||||||
|
if (res.ok) {
|
||||||
|
const record = await res.json()
|
||||||
|
return record.value as RseAdminData
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Failed
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
// Get user's links (ai.syui.at.link)
|
// Get user's links (ai.syui.at.link)
|
||||||
export async function getLinks(did: string): Promise<LinkCollection | null> {
|
export async function getLinks(did: string): Promise<LinkCollection | null> {
|
||||||
const collection = 'ai.syui.at.link'
|
const collection = 'ai.syui.at.link'
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import './styles/main.css'
|
import './styles/main.css'
|
||||||
import './styles/card.css'
|
import './styles/card.css'
|
||||||
import './styles/card-migrate.css'
|
import './styles/card-migrate.css'
|
||||||
import { getConfig, resolveHandle, getProfile, getPosts, getPost, describeRepo, listRecords, getRecord, getPds, getNetworks, getChatMessages, getCards, getRse, getLinks } from './lib/api'
|
import { getConfig, resolveHandle, getProfile, getPosts, getPost, describeRepo, listRecords, getRecord, getPds, getNetworks, getChatMessages, getCards, getCardAdmin, getRse, getRseAdmin, getLinks } from './lib/api'
|
||||||
import { parseRoute, onRouteChange, navigate, type Route } from './lib/router'
|
import { parseRoute, onRouteChange, navigate, type Route } from './lib/router'
|
||||||
import { login, logout, handleCallback, restoreSession, isLoggedIn, getLoggedInHandle, getLoggedInDid, deleteRecord, updatePost, updateChat, updateLinks } from './lib/auth'
|
import { login, logout, handleCallback, restoreSession, isLoggedIn, getLoggedInHandle, getLoggedInDid, deleteRecord, updatePost, updateChat, updateLinks } from './lib/auth'
|
||||||
import { validateRecord } from './lib/lexicon'
|
import { validateRecord } from './lib/lexicon'
|
||||||
@@ -253,9 +253,17 @@ async function render(route: Route): Promise<void> {
|
|||||||
html += `<nav class="back-nav"><a href="/@${handle}">${handle}</a></nav>`
|
html += `<nav class="back-nav"><a href="/@${handle}">${handle}</a></nav>`
|
||||||
|
|
||||||
} else if (route.type === 'rse') {
|
} else if (route.type === 'rse') {
|
||||||
// RSE page
|
// RSE page with character cards
|
||||||
const rseData = await getRse(did)
|
const cardCollection = config.cardCollection || 'ai.syui.card.user'
|
||||||
html += `<div id="content">${renderRsePage(rseData, handle)}</div>`
|
const adminDid = config.bot?.did || config.did || did
|
||||||
|
const [rseData, cards, adminData, rseAdminData] = await Promise.all([
|
||||||
|
getRse(did),
|
||||||
|
getCards(did, cardCollection),
|
||||||
|
getCardAdmin(adminDid),
|
||||||
|
getRseAdmin(adminDid)
|
||||||
|
])
|
||||||
|
const userCards = cards?.card || []
|
||||||
|
html += `<div id="content">${renderRsePage(rseData, handle, userCards, adminData, rseAdminData)}</div>`
|
||||||
html += `<nav class="back-nav"><a href="/@${handle}">${handle}</a></nav>`
|
html += `<nav class="back-nav"><a href="/@${handle}">${handle}</a></nav>`
|
||||||
|
|
||||||
} else if (route.type === 'link') {
|
} else if (route.type === 'link') {
|
||||||
|
|||||||
@@ -522,3 +522,133 @@
|
|||||||
color: var(--text-secondary, #aaa);
|
color: var(--text-secondary, #aaa);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* RSE section title */
|
||||||
|
.rse-section-title {
|
||||||
|
margin: 24px 0 12px;
|
||||||
|
font-size: 1.1em;
|
||||||
|
color: var(--text-primary, #333);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RSE character sections */
|
||||||
|
.rse-characters {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 32px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rse-character-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rse-character-main {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rse-character-main .card-wrapper {
|
||||||
|
width: 250px;
|
||||||
|
max-width: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RSE card grid (cards below character) */
|
||||||
|
.rse-card-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
width: 100%;
|
||||||
|
padding: 20px;
|
||||||
|
background: rgba(128, 128, 128, 0.08);
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rse-card-grid .card-wrapper {
|
||||||
|
max-width: 180px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rse-card-grid .card-info-name {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rse-card-grid .card-info-text {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rse-card-grid .card-key-btn {
|
||||||
|
font-size: 9px;
|
||||||
|
padding: 2px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Card info (below card) */
|
||||||
|
.card-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
margin-top: 8px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-info-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-info-name {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary, #333);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-info-header .card-key-btn {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-info-text {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--text-secondary, #666);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-key-btn {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
padding: 3px 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: default;
|
||||||
|
box-shadow: 0 2px 6px rgba(102, 126, 234, 0.3);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark mode for RSE */
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.rse-section-title {
|
||||||
|
color: var(--text-primary, #eee);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rse-card-grid {
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-info-name {
|
||||||
|
color: var(--text-primary, #eee);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-info-text {
|
||||||
|
color: var(--text-secondary, #aaa);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||