feat: use GitHub pfp as logo/favicon and replace element/path text with official game icons

This commit is contained in:
CAtlas0784 committed 2026-10-16 23:33:26 +07:00
1 parent 09f2061021
commit edc2843f0c
21 files changed
+123 -28

No files matched your search

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

+59
View File
@@ -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;
+18 -6
View File
@@ -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>
+46 -22
View File
@@ -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