Compare commits
3 Commits
ab34468717
...
8b9ca0d470
| Author | SHA1 | Date | |
|---|---|---|---|
|
8b9ca0d470
|
|||
|
d79c71d5e2
|
|||
|
fc3f2da3c8
|
@@ -6,18 +6,16 @@
|
||||
"main": {
|
||||
"type": "record",
|
||||
"key": "literal:self",
|
||||
"description": "Card game configuration (admin only)",
|
||||
"description": "Card game configuration and master data (admin only)",
|
||||
"record": {
|
||||
"type": "object",
|
||||
"required": ["card", "rate", "createdAt", "updatedAt"],
|
||||
"required": ["gacha", "card", "createdAt", "updatedAt"],
|
||||
"properties": {
|
||||
"card": {
|
||||
"gacha": {
|
||||
"type": "object",
|
||||
"required": ["pickup"],
|
||||
"required": ["pickup", "rate"],
|
||||
"properties": {
|
||||
"pickup": { "type": "integer", "description": "Pickup card ID" }
|
||||
}
|
||||
},
|
||||
"pickup": { "type": "integer", "description": "Pickup card ID" },
|
||||
"rate": {
|
||||
"type": "object",
|
||||
"required": ["pickup", "rare"],
|
||||
@@ -25,6 +23,39 @@
|
||||
"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)" }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"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" },
|
||||
"updatedAt": { "type": "string", "format": "datetime" }
|
||||
|
||||
@@ -11,15 +11,17 @@
|
||||
}
|
||||
},
|
||||
"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", "key": null },
|
||||
{ "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": 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", "key": null },
|
||||
{ "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": "world" }, "text": { "ja": "このカードのcpはキャラクターの基礎ステータスに参照される", "en": "This card's CP is referenced for the character's base status" }, "cp": "status", "effect": "status", "key": null },
|
||||
{ "id": 200, "character": 2, "name": { "ja": "d", "en": "d" }, "text": { "ja": "このカードのcpはキャラクターの基礎ステータスに参照される", "en": "This card's CP is referenced for the character's base status" }, "cp": "status", "effect": "status", "key": null },
|
||||
{ "id": 201, "character": 2, "name": { "ja": "ドラゴン", "en": "dragon" }, "text": { "ja": "", "en": "" }, "cp": "skill", "effect": "skill", "key": null },
|
||||
{ "id": 300, "character": 3, "name": { "ja": "r", "en": "r" }, "text": { "ja": "このカードのcpはキャラクターの基礎ステータスに参照される", "en": "This card's CP is referenced for the character's base status" }, "cp": "status", "effect": "status", "key": null },
|
||||
{ "id": 400, "character": 4, "name": { "ja": "c", "en": "c" }, "text": { "ja": "このカードのcpはキャラクターの基礎ステータスに参照される", "en": "This card's CP is referenced for the character's base status" }, "cp": "status", "effect": "status", "key": null }
|
||||
{ "id": 301, "character": 3, "name": { "ja": "ロボット", "en": "robot" }, "text": { "ja": "", "en": "" }, "cp": "skill", "effect": "skill", "key": null },
|
||||
{ "id": 400, "character": 4, "name": { "ja": "c", "en": "c" }, "text": { "ja": "", "en": "This card's CP is referenced for the character's base status" }, "cp": "status", "effect": "status", "key": null }
|
||||
],
|
||||
"createdAt": "2026-01-25T09:02:20.000Z",
|
||||
"updatedAt": "2026-01-25T09:02:20.000Z"
|
||||
|
||||
@@ -14,6 +14,33 @@ export interface CardCollection {
|
||||
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
|
||||
function getRarityClass(card: UserCard): string {
|
||||
if (card.unique) return 'unique'
|
||||
@@ -21,9 +48,13 @@ function getRarityClass(card: UserCard): string {
|
||||
return ''
|
||||
}
|
||||
|
||||
|
||||
// Render single card with optional count badge
|
||||
export function renderCard(card: UserCard, baseUrl: string = '/card', count?: number): string {
|
||||
// Render single card with optional count badge and admin info
|
||||
export function renderCard(
|
||||
card: UserCard,
|
||||
baseUrl: string = '/card',
|
||||
count?: number,
|
||||
adminEntry?: CardAdminEntry
|
||||
): string {
|
||||
const rarityClass = getRarityClass(card)
|
||||
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>` : ''
|
||||
|
||||
// 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 `
|
||||
<div class="card-item">
|
||||
<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">
|
||||
<span class="card-cp">${card.cp}</span>
|
||||
</div>
|
||||
${infoHtml}
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// RSE display component for ai.syui.rse.user collection
|
||||
import { renderCard, type UserCard, type CardAdminEntry, type CardAdminData } from './card'
|
||||
|
||||
export interface RseItem {
|
||||
id: number
|
||||
@@ -20,8 +21,85 @@ function getRarityClass(item: RseItem): string {
|
||||
return ''
|
||||
}
|
||||
|
||||
// Render single item/character
|
||||
function renderRseItem(item: RseItem, type: 'item' | 'character'): string {
|
||||
// Get cards for a character (character 0 = cards 0-99, character 1 = cards 100-199, etc.)
|
||||
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 effectsHtml = rarityClass ? `
|
||||
<div class="card-status pattern-${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 `
|
||||
<div class="rse-character-section">
|
||||
<div class="rse-character-main">
|
||||
<div class="card-item">
|
||||
<div class="card-wrapper">
|
||||
<div class="card-reflection">
|
||||
<img src="/rse/character/${item.id}.webp" alt="character ${item.id}" loading="lazy" />
|
||||
</div>
|
||||
${effectsHtml}
|
||||
</div>
|
||||
<div class="card-detail">
|
||||
<span class="card-cp">${item.cp}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
${characterCards.length > 0 ? `
|
||||
<div class="rse-card-grid">${cardsHtml}</div>
|
||||
` : ''}
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
// Render single item
|
||||
function renderRseItem(item: RseItem): string {
|
||||
const rarityClass = getRarityClass(item)
|
||||
const effectsHtml = rarityClass ? `
|
||||
<div class="card-status pattern-${rarityClass}"></div>
|
||||
@@ -32,7 +110,7 @@ function renderRseItem(item: RseItem, type: 'item' | 'character'): string {
|
||||
<div class="card-item">
|
||||
<div class="card-wrapper">
|
||||
<div class="card-reflection">
|
||||
<img src="/rse/${type}/${item.id}.webp" alt="${type} ${item.id}" loading="lazy" />
|
||||
<img src="/rse/item/${item.id}.webp" alt="item ${item.id}" loading="lazy" />
|
||||
</div>
|
||||
${effectsHtml}
|
||||
</div>
|
||||
@@ -46,7 +124,9 @@ function renderRseItem(item: RseItem, type: 'item' | 'character'): string {
|
||||
// Render RSE page
|
||||
export function renderRsePage(
|
||||
collection: RseCollection | null,
|
||||
handle: string
|
||||
handle: string,
|
||||
userCards: UserCard[] = [],
|
||||
adminData: CardAdminData | null = null
|
||||
): string {
|
||||
const jsonUrl = `/@${handle}/at/collection/ai.syui.rse.user/self`
|
||||
|
||||
@@ -70,19 +150,14 @@ export function renderRsePage(
|
||||
const totalItems = items.length
|
||||
const uniqueChars = characters.filter(c => c.unique).length
|
||||
|
||||
// Sort by unique > id
|
||||
const sortedChars = [...characters].sort((a, b) => {
|
||||
if (a.unique !== b.unique) return a.unique ? -1 : 1
|
||||
return a.id - b.id
|
||||
})
|
||||
// Sort by id
|
||||
const sortedChars = [...characters].sort((a, b) => a.id - b.id)
|
||||
const sortedItems = [...items].sort((a, b) => a.id - b.id)
|
||||
|
||||
const sortedItems = [...items].sort((a, b) => {
|
||||
if (a.unique !== b.unique) return a.unique ? -1 : 1
|
||||
return a.id - b.id
|
||||
})
|
||||
|
||||
const charsHtml = sortedChars.map(c => renderRseItem(c, 'character')).join('')
|
||||
const itemsHtml = sortedItems.map(i => renderRseItem(i, 'item')).join('')
|
||||
const charsHtml = sortedChars.map(c =>
|
||||
renderCharacterSection(c, userCards, adminData)
|
||||
).join('')
|
||||
const itemsHtml = sortedItems.map(i => renderRseItem(i)).join('')
|
||||
|
||||
return `
|
||||
<div class="card-page">
|
||||
@@ -107,7 +182,7 @@ export function renderRsePage(
|
||||
</div>
|
||||
${charsHtml ? `
|
||||
<h3 class="rse-section-title">Characters</h3>
|
||||
<div class="card-grid">${charsHtml}</div>
|
||||
<div class="rse-characters">${charsHtml}</div>
|
||||
` : ''}
|
||||
${itemsHtml ? `
|
||||
<h3 class="rse-section-title">Items</h3>
|
||||
|
||||
@@ -679,6 +679,55 @@ export interface LinkCollection {
|
||||
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
|
||||
}
|
||||
|
||||
// Get user's links (ai.syui.at.link)
|
||||
export async function getLinks(did: string): Promise<LinkCollection | null> {
|
||||
const collection = 'ai.syui.at.link'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import './styles/main.css'
|
||||
import './styles/card.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, getLinks } from './lib/api'
|
||||
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 { validateRecord } from './lib/lexicon'
|
||||
@@ -253,9 +253,16 @@ async function render(route: Route): Promise<void> {
|
||||
html += `<nav class="back-nav"><a href="/@${handle}">${handle}</a></nav>`
|
||||
|
||||
} else if (route.type === 'rse') {
|
||||
// RSE page
|
||||
const rseData = await getRse(did)
|
||||
html += `<div id="content">${renderRsePage(rseData, handle)}</div>`
|
||||
// RSE page with character cards
|
||||
const cardCollection = config.cardCollection || 'ai.syui.card.user'
|
||||
const adminDid = config.bot?.did || config.did || did
|
||||
const [rseData, cards, adminData] = await Promise.all([
|
||||
getRse(did),
|
||||
getCards(did, cardCollection),
|
||||
getCardAdmin(adminDid)
|
||||
])
|
||||
const userCards = cards?.card || []
|
||||
html += `<div id="content">${renderRsePage(rseData, handle, userCards, adminData)}</div>`
|
||||
html += `<nav class="back-nav"><a href="/@${handle}">${handle}</a></nav>`
|
||||
|
||||
} else if (route.type === 'link') {
|
||||
|
||||
@@ -522,3 +522,132 @@
|
||||
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;
|
||||
}
|
||||
|
||||
.rse-character-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.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(3, 1fr);
|
||||
gap: 20px;
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
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: 10px;
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user