feat: use GitHub pfp as logo/favicon and replace element/path text with official game icons
No files matched your search
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 39 KiB |
@@ -69,6 +69,21 @@ body {
|
||||
gap: 0.85rem;
|
||||
}
|
||||
|
||||
.brand-logo-img {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
border: 2px solid var(--accent-cyan);
|
||||
box-shadow: 0 0 12px rgba(56, 189, 248, 0.45);
|
||||
transition: transform 0.25s ease;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.brand-logo-img:hover {
|
||||
transform: rotate(8deg) scale(1.08);
|
||||
}
|
||||
|
||||
.brand-icon {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
@@ -536,6 +551,50 @@ body {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Star Rail Official Element & Path Icon Badges */
|
||||
.icon-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border-radius: 6px;
|
||||
background: rgba(0, 0, 0, 0.45);
|
||||
border: 1px solid var(--border-color);
|
||||
padding: 3px;
|
||||
transition: transform 0.15s ease, box-shadow 0.15s ease;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.icon-badge:hover {
|
||||
transform: scale(1.18);
|
||||
}
|
||||
|
||||
.icon-badge img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.icon-badge.elem-fire { border-color: rgba(239, 68, 68, 0.6); background: rgba(239, 68, 68, 0.18); box-shadow: 0 0 8px rgba(239, 68, 68, 0.3); }
|
||||
.icon-badge.elem-ice { border-color: rgba(56, 189, 248, 0.6); background: rgba(56, 189, 248, 0.18); box-shadow: 0 0 8px rgba(56, 189, 248, 0.3); }
|
||||
.icon-badge.elem-lightning { border-color: rgba(168, 85, 247, 0.6); background: rgba(168, 85, 247, 0.18); box-shadow: 0 0 8px rgba(168, 85, 247, 0.3); }
|
||||
.icon-badge.elem-wind { border-color: rgba(16, 185, 129, 0.6); background: rgba(16, 185, 129, 0.18); box-shadow: 0 0 8px rgba(16, 185, 129, 0.3); }
|
||||
.icon-badge.elem-physical { border-color: rgba(156, 163, 175, 0.6); background: rgba(156, 163, 175, 0.18); box-shadow: 0 0 8px rgba(156, 163, 175, 0.3); }
|
||||
.icon-badge.elem-quantum { border-color: rgba(99, 102, 241, 0.6); background: rgba(99, 102, 241, 0.18); box-shadow: 0 0 8px rgba(99, 102, 241, 0.3); }
|
||||
.icon-badge.elem-imaginary { border-color: rgba(245, 158, 11, 0.6); background: rgba(245, 158, 11, 0.18); box-shadow: 0 0 8px rgba(245, 158, 11, 0.3); }
|
||||
|
||||
.path-icon-badge {
|
||||
border-color: rgba(255, 255, 255, 0.25);
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.path-icon-badge:hover {
|
||||
border-color: rgba(255, 255, 255, 0.6);
|
||||
box-shadow: 0 0 8px rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
|
||||
/* Avatar Card Footer & Status */
|
||||
.avatar-card-footer {
|
||||
display: flex;
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>GaC-Srtool — Ultimate Star Rail Server & Build Manager</title>
|
||||
<link rel="stylesheet" href="css/styles.css">
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🌌</text></svg>">
|
||||
<link rel="icon" type="image/png" href="assets/logo.png">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- App Header -->
|
||||
<header class="app-header">
|
||||
<div class="brand">
|
||||
<div class="brand-icon">✦</div>
|
||||
<img src="assets/logo.png" alt="GaC Logo" class="brand-logo-img">
|
||||
<div>
|
||||
<div class="brand-title">GaC-Srtool</div>
|
||||
<div class="brand-subtitle">Star Rail FreeSR & Server Controller</div>
|
||||
@@ -140,14 +140,14 @@
|
||||
<!-- 2. Avatars Tab (Build & Eidolon Editor) -->
|
||||
<section id="tab-avatars" class="tab-content">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.25rem; flex-wrap: wrap; gap: 1rem;">
|
||||
<div style="display: flex; gap: 0.75rem; flex: 1; max-width: 520px;">
|
||||
<input type="text" id="avatar-search" class="form-input" placeholder="🔍 ค้นหาชื่อหรือ ID ตัวละคร...">
|
||||
<select id="avatar-filter-rarity" class="form-select" style="max-width: 140px;">
|
||||
<div style="display: flex; gap: 0.75rem; flex: 1; max-width: 680px; flex-wrap: wrap;">
|
||||
<input type="text" id="avatar-search" class="form-input" placeholder="🔍 ค้นหาชื่อหรือ ID ตัวละคร..." style="min-width: 180px; flex: 1;">
|
||||
<select id="avatar-filter-rarity" class="form-select" style="max-width: 130px;">
|
||||
<option value="all">ทุกระดับดาว</option>
|
||||
<option value="5">5 ดาว</option>
|
||||
<option value="4">4 ดาว</option>
|
||||
</select>
|
||||
<select id="avatar-filter-element" class="form-select" style="max-width: 140px;">
|
||||
<select id="avatar-filter-element" class="form-select" style="max-width: 130px;">
|
||||
<option value="all">ทุกธาตุ</option>
|
||||
<option value="Fire">ไฟ</option>
|
||||
<option value="Ice">น้ำแข็ง</option>
|
||||
@@ -157,6 +157,18 @@
|
||||
<option value="Quantum">ควอนตัม</option>
|
||||
<option value="Imaginary">จินตภาพ</option>
|
||||
</select>
|
||||
<select id="avatar-filter-path" class="form-select" style="max-width: 130px;">
|
||||
<option value="all">ทุกประเภท</option>
|
||||
<option value="Destruction">ทำลายล้าง</option>
|
||||
<option value="Hunt">ล่าสังหาร</option>
|
||||
<option value="Erudition">ปัญญา</option>
|
||||
<option value="Harmony">ประสาน</option>
|
||||
<option value="Nihility">ลบล้าง</option>
|
||||
<option value="Preservation">อนุรักษ์</option>
|
||||
<option value="Abundance">เฟื่องฟู</option>
|
||||
<option value="Remembrance">ความทรงจำ</option>
|
||||
<option value="Elation">ปีติ</option>
|
||||
</select>
|
||||
</div>
|
||||
<div style="display: flex; gap: 0.5rem;">
|
||||
<button class="btn btn-gold" id="btn-max-all-avatars">⭐ ปรับ Max E6 ทุกตัว</button>
|
||||
|
||||
@@ -50,6 +50,7 @@ const el = {
|
||||
avatarSearch: document.getElementById('avatar-search'),
|
||||
avatarFilterRarity: document.getElementById('avatar-filter-rarity'),
|
||||
avatarFilterElement: document.getElementById('avatar-filter-element'),
|
||||
avatarFilterPath: document.getElementById('avatar-filter-path'),
|
||||
btnMaxAllAvatars: document.getElementById('btn-max-all-avatars'),
|
||||
btnReloadAvatars: document.getElementById('btn-reload-avatars'),
|
||||
|
||||
@@ -205,6 +206,30 @@ function getElementClass(elem) {
|
||||
return map[elem] || 'elem-physical';
|
||||
}
|
||||
|
||||
// Render Element Icon HTML (Official Star Rail Game Icon)
|
||||
function getElementIconHtml(element, size = 20) {
|
||||
if (!element) return '';
|
||||
const elemClass = getElementClass(element);
|
||||
return `<span class="icon-badge element-icon-badge ${elemClass}" title="${element}">
|
||||
<img src="assets/elements/${element}.png"
|
||||
alt="${element}"
|
||||
style="width: ${size}px; height: ${size}px;"
|
||||
onerror="if(this.src.indexOf('StarRailRes')===-1){this.src='https://raw.githubusercontent.com/Mar-7th/StarRailRes/master/icon/element/${element === 'Lightning' ? 'Thunder' : element}.png';}">
|
||||
</span>`;
|
||||
}
|
||||
|
||||
// Render Path Icon HTML (Official Star Rail Game Icon)
|
||||
function getPathIconHtml(path, size = 20) {
|
||||
if (!path) return '';
|
||||
const safePath = path === 'The Hunt' ? 'Hunt' : path;
|
||||
return `<span class="icon-badge path-icon-badge" title="${path}">
|
||||
<img src="assets/paths/${path}.png"
|
||||
alt="${path}"
|
||||
style="width: ${size}px; height: ${size}px;"
|
||||
onerror="if(this.src.indexOf('StarRailRes')===-1){this.src='https://raw.githubusercontent.com/Mar-7th/StarRailRes/master/icon/path/${safePath}.png';}">
|
||||
</span>`;
|
||||
}
|
||||
|
||||
// Apply multi-language translations across UI
|
||||
function applyTranslations() {
|
||||
document.querySelectorAll('[data-i18n]').forEach(elem => {
|
||||
@@ -400,7 +425,6 @@ function renderLineup(lineups) {
|
||||
`;
|
||||
} else {
|
||||
const info = getAvatarInfo(avatarId);
|
||||
const elemClass = getElementClass(info.element);
|
||||
slotCard.innerHTML = `
|
||||
<div class="lineup-slot-num">Slot ${slot + 1}</div>
|
||||
<img class="lineup-slot-img"
|
||||
@@ -408,9 +432,9 @@ function renderLineup(lineups) {
|
||||
onerror="if(this.src.indexOf('jsdelivr')===-1){this.src='https://cdn.jsdelivr.net/gh/Mar-7th/StarRailRes@master/icon/avatar/${avatarId}.png';}else{this.style.display='none';}"
|
||||
alt="${info.name}">
|
||||
<div style="font-weight: 800; font-size: 1rem; color: var(--text-highlight); margin-top: 0.35rem;">${info.name}</div>
|
||||
<div style="display: flex; gap: 0.35rem; margin-top: 0.35rem;">
|
||||
<span class="element-badge ${elemClass}">${info.element}</span>
|
||||
<span class="path-badge">${info.path}</span>
|
||||
<div style="display: flex; gap: 0.4rem; margin-top: 0.4rem; justify-content: center; align-items: center;">
|
||||
${getElementIconHtml(info.element, 20)}
|
||||
${getPathIconHtml(info.path, 20)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -459,7 +483,6 @@ function renderPickerGrid() {
|
||||
return;
|
||||
}
|
||||
|
||||
const elemClass = getElementClass(meta.element);
|
||||
const card = document.createElement('div');
|
||||
card.className = `card avatar-card card-rarity-${meta.rarity}`;
|
||||
card.style.minHeight = 'auto';
|
||||
@@ -476,9 +499,10 @@ function renderPickerGrid() {
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-weight: 700; font-size: 0.9rem;">${meta.name}</div>
|
||||
<div style="font-size: 0.725rem; color: var(--text-muted); margin-top: 0.15rem;">
|
||||
<span class="element-badge ${elemClass}" style="font-size: 0.65rem; padding: 0.05rem 0.3rem;">${meta.element}</span>
|
||||
<span>Lv. ${item.level} • E${item.rank}</span>
|
||||
<div style="font-size: 0.725rem; color: var(--text-muted); margin-top: 0.25rem; display: flex; align-items: center; gap: 0.35rem;">
|
||||
${getElementIconHtml(meta.element, 16)}
|
||||
${getPathIconHtml(meta.path, 16)}
|
||||
<span style="margin-left: 0.25rem;">Lv. ${item.level} • E${item.rank}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -544,13 +568,15 @@ function renderAvatarsGrid() {
|
||||
const searchTerm = el.avatarSearch.value.trim().toLowerCase();
|
||||
const rarityFilter = el.avatarFilterRarity.value;
|
||||
const elementFilter = el.avatarFilterElement.value;
|
||||
const pathFilter = el.avatarFilterPath ? el.avatarFilterPath.value : 'all';
|
||||
|
||||
const filtered = state.avatarsList.filter(item => {
|
||||
const meta = getAvatarInfo(item.avatar_id);
|
||||
const matchesSearch = meta.name.toLowerCase().includes(searchTerm) || String(item.avatar_id).includes(searchTerm);
|
||||
const matchesRarity = rarityFilter === 'all' || String(meta.rarity) === rarityFilter;
|
||||
const matchesElement = elementFilter === 'all' || meta.element === elementFilter;
|
||||
return matchesSearch && matchesRarity && matchesElement;
|
||||
const matchesPath = pathFilter === 'all' || meta.path === pathFilter || (pathFilter === 'Hunt' && (meta.path === 'Hunt' || meta.path === 'The Hunt'));
|
||||
return matchesSearch && matchesRarity && matchesElement && matchesPath;
|
||||
});
|
||||
|
||||
if (filtered.length === 0) {
|
||||
@@ -561,7 +587,6 @@ function renderAvatarsGrid() {
|
||||
el.avatarsGrid.innerHTML = '';
|
||||
filtered.forEach(item => {
|
||||
const meta = getAvatarInfo(item.avatar_id);
|
||||
const elemClass = getElementClass(meta.element);
|
||||
const card = document.createElement('div');
|
||||
card.className = `card avatar-card card-rarity-${meta.rarity}`;
|
||||
|
||||
@@ -578,8 +603,8 @@ function renderAvatarsGrid() {
|
||||
<div class="avatar-meta-details">
|
||||
<div class="avatar-name" title="${meta.name}">${meta.name}</div>
|
||||
<div class="avatar-subinfo">
|
||||
<span class="element-badge ${elemClass}">${translateElement(meta.element)}</span>
|
||||
<span class="path-badge">${translatePath(meta.path)}</span>
|
||||
${getElementIconHtml(meta.element, 20)}
|
||||
${getPathIconHtml(meta.path, 20)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -650,7 +675,6 @@ async function maxSingleAvatar(avatarId) {
|
||||
function openAvatarDetailModal(avatarItem) {
|
||||
state.selectedAvatarDetail = avatarItem;
|
||||
const meta = getAvatarInfo(avatarItem.avatar_id);
|
||||
const elemClass = getElementClass(meta.element);
|
||||
|
||||
el.modalCharTitle.textContent = `ปรับแต่งตัวละคร: ${meta.name} (ID: ${avatarItem.avatar_id})`;
|
||||
el.modalCharImg.src = `./assets/avatars/${avatarItem.avatar_id}.png`;
|
||||
@@ -659,9 +683,9 @@ function openAvatarDetailModal(avatarItem) {
|
||||
};
|
||||
el.modalCharName.textContent = meta.name;
|
||||
el.modalCharMeta.innerHTML = `
|
||||
<span class="element-badge ${elemClass}">${meta.element}</span>
|
||||
<span class="path-badge">${meta.path}</span>
|
||||
<span class="avatar-badge-rarity rarity-${meta.rarity}">★ ${meta.rarity}</span>
|
||||
${getElementIconHtml(meta.element, 24)}
|
||||
${getPathIconHtml(meta.path, 24)}
|
||||
<span class="avatar-badge-rarity rarity-${meta.rarity}" style="position: static;">★ ${meta.rarity}</span>
|
||||
`;
|
||||
|
||||
el.modalCharLevel.value = avatarItem.level || 80;
|
||||
@@ -963,7 +987,6 @@ function renderExRanks(exList) {
|
||||
item.style.gap = '1rem';
|
||||
|
||||
const info = getAvatarInfo(ex.avatar_id);
|
||||
const elemClass = getElementClass(info.element);
|
||||
|
||||
item.innerHTML = `
|
||||
<div style="display: flex; align-items: center; gap: 1rem;">
|
||||
@@ -975,10 +998,10 @@ function renderExRanks(exList) {
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-weight: 800; font-size: 1.05rem;">${info.name}</div>
|
||||
<div style="display: flex; gap: 0.35rem; margin-top: 0.35rem;">
|
||||
<span class="element-badge ${elemClass}">${info.element}</span>
|
||||
<span class="path-badge">${info.path}</span>
|
||||
<span style="font-size: 0.775rem; color: var(--text-muted);">Ex-Rank ID: ${ex.ex_rank_id}</span>
|
||||
<div style="display: flex; gap: 0.4rem; margin-top: 0.35rem; align-items: center;">
|
||||
${getElementIconHtml(info.element, 20)}
|
||||
${getPathIconHtml(info.path, 20)}
|
||||
<span style="font-size: 0.775rem; color: var(--text-muted); margin-left: 0.25rem;">Ex-Rank ID: ${ex.ex_rank_id}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -986,7 +1009,7 @@ function renderExRanks(exList) {
|
||||
<button class="btn btn-sm ${ex.unlocked ? 'btn-success' : 'btn-secondary'} btn-toggle-ex"
|
||||
data-avatar="${ex.avatar_id}"
|
||||
data-unlocked="${ex.unlocked}">
|
||||
${ex.unlocked ? '⚡ ปลดล็อคแล้ว (Active)' : '🔒 ปิดการใช้งาน (Locked)'}
|
||||
${ex.unlocked ? '⚡ เปิดใช้งานแล้ว' : '🔒 ปิดการใช้งาน'}
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
@@ -1259,6 +1282,7 @@ function setupEvents() {
|
||||
el.avatarSearch.addEventListener('input', renderAvatarsGrid);
|
||||
el.avatarFilterRarity.addEventListener('change', renderAvatarsGrid);
|
||||
el.avatarFilterElement.addEventListener('change', renderAvatarsGrid);
|
||||
el.avatarFilterPath?.addEventListener('change', renderAvatarsGrid);
|
||||
el.btnReloadAvatars.addEventListener('click', loadAvatars);
|
||||
|
||||
// Ex-Rank All Controls
|
||||
|
||||