diff --git a/css/styles.css b/css/styles.css
index eae968e..b1344f0 100644
--- a/css/styles.css
+++ b/css/styles.css
@@ -1,10 +1,10 @@
:root {
- --bg-primary: #0a0d14;
- --bg-secondary: #121826;
- --bg-card: rgba(22, 30, 48, 0.7);
- --bg-card-hover: rgba(30, 41, 66, 0.85);
+ --bg-primary: #080a10;
+ --bg-secondary: #0f1422;
+ --bg-card: rgba(18, 24, 38, 0.75);
+ --bg-card-hover: rgba(26, 35, 56, 0.9);
--border-color: rgba(99, 120, 160, 0.25);
- --border-focus: #4ef2bb;
+ --border-focus: #38bdf8;
--text-main: #f0f4fc;
--text-muted: #8e9bb5;
@@ -13,18 +13,18 @@
--accent-cyan: #38bdf8;
--accent-green: #34d399;
--accent-gold: #fbbf24;
- --accent-purple: #a855f7;
+ --accent-purple: #c084fc;
--accent-red: #f87171;
- --glow-cyan: 0 0 15px rgba(56, 189, 248, 0.35);
- --glow-green: 0 0 15px rgba(52, 211, 153, 0.35);
- --glow-gold: 0 0 15px rgba(251, 191, 36, 0.35);
+ --glow-cyan: 0 0 20px rgba(56, 189, 248, 0.35);
+ --glow-gold: 0 0 20px rgba(251, 191, 36, 0.35);
+ --glow-purple: 0 0 20px rgba(192, 132, 252, 0.35);
--font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
--radius-sm: 6px;
--radius-md: 10px;
--radius-lg: 16px;
- --transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
+ --transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
}
* {
@@ -37,8 +37,9 @@ body {
font-family: var(--font-family);
background-color: var(--bg-primary);
background-image:
- radial-gradient(circle at 15% 15%, rgba(56, 189, 248, 0.08) 0%, transparent 40%),
- radial-gradient(circle at 85% 85%, rgba(168, 85, 247, 0.08) 0%, transparent 45%);
+ radial-gradient(circle at 10% 10%, rgba(56, 189, 248, 0.08) 0%, transparent 40%),
+ radial-gradient(circle at 90% 90%, rgba(168, 85, 247, 0.08) 0%, transparent 45%),
+ linear-gradient(180deg, rgba(8, 10, 16, 0.95) 0%, rgba(12, 16, 26, 0.98) 100%);
color: var(--text-main);
min-height: 100vh;
display: flex;
@@ -52,8 +53,8 @@ body {
position: sticky;
top: 0;
z-index: 50;
- background: rgba(10, 13, 20, 0.85);
- backdrop-filter: blur(14px);
+ background: rgba(8, 10, 16, 0.88);
+ backdrop-filter: blur(16px);
border-bottom: 1px solid var(--border-color);
padding: 0.75rem 1.5rem;
display: flex;
@@ -65,28 +66,29 @@ body {
.brand {
display: flex;
align-items: center;
- gap: 0.75rem;
+ gap: 0.85rem;
}
.brand-icon {
- width: 38px;
- height: 38px;
+ width: 42px;
+ height: 42px;
border-radius: var(--radius-md);
- background: linear-gradient(135deg, #38bdf8, #818cf8);
+ background: linear-gradient(135deg, #0284c7, #6366f1);
display: flex;
align-items: center;
justify-content: center;
box-shadow: var(--glow-cyan);
- font-size: 1.25rem;
+ font-size: 1.35rem;
font-weight: bold;
color: #fff;
+ border: 1px solid rgba(56, 189, 248, 0.4);
}
.brand-title {
- font-size: 1.25rem;
- font-weight: 700;
+ font-size: 1.35rem;
+ font-weight: 800;
letter-spacing: -0.025em;
- background: linear-gradient(90deg, #ffffff, #cbd5e1);
+ background: linear-gradient(90deg, #ffffff, #cbd5e1, #38bdf8);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
@@ -95,25 +97,55 @@ body {
font-size: 0.75rem;
color: var(--text-muted);
text-transform: uppercase;
- letter-spacing: 0.05em;
+ letter-spacing: 0.06em;
}
.header-controls {
display: flex;
align-items: center;
- gap: 0.85rem;
+ gap: 0.75rem;
+ flex-wrap: wrap;
+}
+
+/* Server Mode Selector */
+.server-mode-badge {
+ display: flex;
+ align-items: center;
+ gap: 0.4rem;
+ padding: 0.4rem 0.85rem;
+ background: rgba(2, 132, 199, 0.15);
+ border: 1px solid rgba(56, 189, 248, 0.4);
+ border-radius: 9999px;
+ font-size: 0.825rem;
+ font-weight: 700;
+ color: var(--accent-cyan);
+}
+
+.server-mode-select {
+ background: transparent;
+ border: none;
+ color: inherit;
+ font-size: inherit;
+ font-weight: inherit;
+ cursor: pointer;
+ outline: none;
+}
+
+.server-mode-select option {
+ background: var(--bg-secondary);
+ color: var(--text-main);
}
.connection-pill {
display: flex;
align-items: center;
gap: 0.5rem;
- padding: 0.35rem 0.75rem;
+ padding: 0.4rem 0.85rem;
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 9999px;
font-size: 0.8rem;
- font-weight: 500;
+ font-weight: 600;
cursor: pointer;
transition: var(--transition);
}
@@ -123,8 +155,8 @@ body {
}
.status-dot {
- width: 8px;
- height: 8px;
+ width: 9px;
+ height: 9px;
border-radius: 50%;
background-color: var(--accent-red);
box-shadow: 0 0 8px var(--accent-red);
@@ -133,13 +165,13 @@ body {
.status-dot.connected {
background-color: var(--accent-green);
- box-shadow: var(--glow-green);
+ box-shadow: 0 0 10px var(--accent-green);
}
/* Navigation Tabs */
.nav-tabs {
display: flex;
- gap: 0.5rem;
+ gap: 0.35rem;
background: var(--bg-secondary);
padding: 0.5rem 1.5rem;
border-bottom: 1px solid var(--border-color);
@@ -150,7 +182,7 @@ body {
background: transparent;
border: none;
color: var(--text-muted);
- padding: 0.5rem 1rem;
+ padding: 0.6rem 1.1rem;
border-radius: var(--radius-sm);
font-size: 0.875rem;
font-weight: 600;
@@ -164,21 +196,21 @@ body {
.tab-btn:hover {
color: var(--text-main);
- background: rgba(255, 255, 255, 0.04);
+ background: rgba(255, 255, 255, 0.05);
}
.tab-btn.active {
color: #fff;
background: rgba(56, 189, 248, 0.15);
- border: 1px solid rgba(56, 189, 248, 0.3);
- box-shadow: 0 0 10px rgba(56, 189, 248, 0.15);
+ border: 1px solid rgba(56, 189, 248, 0.35);
+ box-shadow: 0 0 12px rgba(56, 189, 248, 0.2);
}
-/* Container */
+/* Main Container */
.main-container {
flex: 1;
padding: 1.5rem;
- max-width: 1300px;
+ max-width: 1400px;
width: 100%;
margin: 0 auto;
}
@@ -197,7 +229,7 @@ body {
to { opacity: 1; transform: translateY(0); }
}
-/* Cards & Grid */
+/* Grids */
.grid-2 {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
@@ -212,13 +244,14 @@ body {
.grid-4 {
display: grid;
- grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
- gap: 1.1rem;
+ grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
+ gap: 1.25rem;
}
+/* Cards */
.card {
background: var(--bg-card);
- backdrop-filter: blur(12px);
+ backdrop-filter: blur(14px);
border: 1px solid var(--border-color);
border-radius: var(--radius-lg);
padding: 1.25rem;
@@ -229,8 +262,8 @@ body {
.card:hover {
background: var(--bg-card-hover);
- border-color: rgba(99, 120, 160, 0.45);
- box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
+ border-color: rgba(99, 120, 160, 0.5);
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4);
}
.card-title {
@@ -253,11 +286,11 @@ body {
.stat-box {
display: flex;
flex-direction: column;
- gap: 0.25rem;
+ gap: 0.35rem;
}
.stat-number {
- font-size: 2rem;
+ font-size: 2.25rem;
font-weight: 800;
color: var(--accent-cyan);
line-height: 1;
@@ -268,6 +301,7 @@ body {
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.05em;
+ font-weight: 600;
}
/* Buttons */
@@ -276,7 +310,7 @@ body {
align-items: center;
justify-content: center;
gap: 0.5rem;
- padding: 0.6rem 1.1rem;
+ padding: 0.6rem 1.15rem;
border-radius: var(--radius-sm);
font-size: 0.875rem;
font-weight: 600;
@@ -304,14 +338,14 @@ body {
}
.btn-success:hover {
- box-shadow: var(--glow-green);
+ box-shadow: 0 0 15px rgba(52, 211, 153, 0.4);
filter: brightness(1.1);
}
.btn-gold {
background: linear-gradient(135deg, #d97706, #f59e0b);
color: #fff;
- border-color: rgba(251, 191, 36, 0.4);
+ border-color: rgba(251, 191, 36, 0.5);
}
.btn-gold:hover {
@@ -335,34 +369,62 @@ body {
font-size: 0.775rem;
}
-/* Avatar Card */
+/* ==========================================================================
+ Honkai: Star Rail Themed Avatar Cards & Portraits
+ ========================================================================== */
+
.avatar-card {
display: flex;
flex-direction: column;
justify-content: space-between;
- min-height: 180px;
+ min-height: 220px;
position: relative;
+ cursor: pointer;
+ border-width: 1.5px;
+}
+
+/* 5-Star Card Theme */
+.avatar-card.card-rarity-5 {
+ border-color: rgba(251, 191, 36, 0.4);
+ background: linear-gradient(145deg, rgba(251, 191, 36, 0.05) 0%, rgba(18, 24, 38, 0.8) 100%);
+}
+
+.avatar-card.card-rarity-5:hover {
+ border-color: rgba(251, 191, 36, 0.8);
+ box-shadow: var(--glow-gold);
+}
+
+/* 4-Star Card Theme */
+.avatar-card.card-rarity-4 {
+ border-color: rgba(192, 132, 252, 0.35);
+ background: linear-gradient(145deg, rgba(192, 132, 252, 0.05) 0%, rgba(18, 24, 38, 0.8) 100%);
+}
+
+.avatar-card.card-rarity-4:hover {
+ border-color: rgba(192, 132, 252, 0.8);
+ box-shadow: var(--glow-purple);
}
.avatar-badge-rarity {
position: absolute;
- top: 1rem;
- right: 1rem;
+ top: 0.85rem;
+ right: 0.85rem;
font-size: 0.75rem;
padding: 0.15rem 0.5rem;
border-radius: var(--radius-sm);
- font-weight: 700;
+ font-weight: 800;
+ letter-spacing: 0.05em;
}
.rarity-5 {
background: rgba(251, 191, 36, 0.2);
- border: 1px solid rgba(251, 191, 36, 0.5);
+ border: 1px solid rgba(251, 191, 36, 0.6);
color: var(--accent-gold);
}
.rarity-4 {
- background: rgba(168, 85, 247, 0.2);
- border: 1px solid rgba(168, 85, 247, 0.5);
+ background: rgba(192, 132, 252, 0.2);
+ border: 1px solid rgba(192, 132, 252, 0.6);
color: var(--accent-purple);
}
@@ -372,29 +434,39 @@ body {
align-items: center;
}
+/* Circular / Rounded Avatar Portrait */
.avatar-image-container {
position: relative;
- width: 52px;
- height: 52px;
- border-radius: var(--radius-md);
+ width: 60px;
+ height: 60px;
+ border-radius: 50%;
overflow: hidden;
background: var(--bg-secondary);
- border: 1px solid var(--border-color);
+ border: 2px solid var(--border-color);
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.4);
+}
+
+.card-rarity-5 .avatar-image-container {
+ border-color: var(--accent-gold);
+}
+
+.card-rarity-4 .avatar-image-container {
+ border-color: var(--accent-purple);
}
.avatar-img {
width: 100%;
height: 100%;
object-fit: cover;
- transition: transform 0.25s ease;
+ transition: transform 0.3s ease;
}
-.card:hover .avatar-img {
- transform: scale(1.08);
+.avatar-card:hover .avatar-img {
+ transform: scale(1.12);
}
.avatar-icon-placeholder {
@@ -405,28 +477,59 @@ body {
align-items: center;
justify-content: center;
font-weight: bold;
- font-size: 1.1rem;
+ font-size: 1.25rem;
+ color: var(--text-muted);
}
.avatar-name {
- font-size: 1rem;
- font-weight: 700;
+ font-size: 1.05rem;
+ font-weight: 800;
color: var(--text-highlight);
+ line-height: 1.25;
}
.avatar-subinfo {
- font-size: 0.75rem;
- color: var(--text-muted);
+ font-size: 0.775rem;
+ margin-top: 0.35rem;
display: flex;
- gap: 0.5rem;
+ gap: 0.4rem;
+ flex-wrap: wrap;
+ align-items: center;
}
+/* Star Rail Element Badges */
+.element-badge {
+ padding: 0.15rem 0.45rem;
+ border-radius: 4px;
+ font-size: 0.725rem;
+ font-weight: 700;
+}
+
+.elem-fire { background: rgba(239, 68, 68, 0.2); color: #f87171; border: 1px solid rgba(239, 68, 68, 0.4); }
+.elem-ice { background: rgba(56, 189, 248, 0.2); color: #38bdf8; border: 1px solid rgba(56, 189, 248, 0.4); }
+.elem-lightning { background: rgba(168, 85, 247, 0.2); color: #c084fc; border: 1px solid rgba(168, 85, 247, 0.4); }
+.elem-wind { background: rgba(16, 185, 129, 0.2); color: #34d399; border: 1px solid rgba(16, 185, 129, 0.4); }
+.elem-physical { background: rgba(156, 163, 175, 0.2); color: #e5e7eb; border: 1px solid rgba(156, 163, 175, 0.4); }
+.elem-quantum { background: rgba(99, 102, 241, 0.2); color: #818cf8; border: 1px solid rgba(99, 102, 241, 0.4); }
+.elem-imaginary { background: rgba(245, 158, 11, 0.2); color: #fbbf24; border: 1px solid rgba(245, 158, 11, 0.4); }
+
+.path-badge {
+ background: rgba(255, 255, 255, 0.08);
+ border: 1px solid var(--border-color);
+ color: var(--text-main);
+ padding: 0.15rem 0.45rem;
+ border-radius: 4px;
+ font-size: 0.725rem;
+ font-weight: 600;
+}
+
+/* Eidolon Selector */
.eidolon-selector {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 1rem;
- background: rgba(0, 0, 0, 0.2);
+ background: rgba(0, 0, 0, 0.35);
padding: 0.5rem 0.75rem;
border-radius: var(--radius-sm);
border: 1px solid var(--border-color);
@@ -434,18 +537,18 @@ body {
.eidolon-buttons {
display: flex;
- gap: 0.25rem;
+ gap: 0.3rem;
}
.eidolon-pip {
- width: 24px;
- height: 24px;
+ width: 25px;
+ height: 25px;
border-radius: 4px;
border: 1px solid var(--border-color);
background: transparent;
color: var(--text-muted);
font-size: 0.75rem;
- font-weight: bold;
+ font-weight: 800;
cursor: pointer;
display: flex;
align-items: center;
@@ -459,10 +562,69 @@ body {
}
.eidolon-pip.active {
- background: var(--accent-cyan);
- border-color: var(--accent-cyan);
+ background: linear-gradient(135deg, #0284c7, #38bdf8);
+ border-color: #38bdf8;
color: #0b0d14;
- box-shadow: 0 0 8px rgba(56, 189, 248, 0.5);
+ box-shadow: 0 0 10px rgba(56, 189, 248, 0.6);
+}
+
+/* ==========================================================================
+ Party / Lineup Builder
+ ========================================================================== */
+.lineup-grid {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 1.25rem;
+}
+
+.lineup-slot-card {
+ background: var(--bg-card);
+ border: 2px dashed var(--border-color);
+ border-radius: var(--radius-lg);
+ padding: 1.25rem;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ text-align: center;
+ min-height: 200px;
+ cursor: pointer;
+ transition: var(--transition);
+ position: relative;
+}
+
+.lineup-slot-card.filled {
+ border-style: solid;
+ border-color: rgba(56, 189, 248, 0.4);
+ background: linear-gradient(180deg, rgba(56, 189, 248, 0.08) 0%, rgba(18, 24, 38, 0.85) 100%);
+}
+
+.lineup-slot-card:hover {
+ border-color: var(--accent-cyan);
+ box-shadow: var(--glow-cyan);
+}
+
+.lineup-slot-img {
+ width: 72px;
+ height: 72px;
+ border-radius: 50%;
+ border: 2px solid var(--accent-cyan);
+ object-fit: cover;
+ margin-bottom: 0.75rem;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
+}
+
+.lineup-slot-num {
+ position: absolute;
+ top: 0.75rem;
+ left: 0.75rem;
+ font-size: 0.75rem;
+ font-weight: 800;
+ color: var(--accent-cyan);
+ background: rgba(2, 132, 199, 0.2);
+ padding: 0.2rem 0.5rem;
+ border-radius: 9999px;
+ border: 1px solid rgba(56, 189, 248, 0.3);
}
/* Ex Rank Toggle Switch */
@@ -470,7 +632,7 @@ body {
display: flex;
align-items: center;
justify-content: space-between;
- padding: 1rem;
+ padding: 1rem 1.25rem;
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
@@ -480,6 +642,7 @@ body {
.ex-switch-card:hover {
background: var(--bg-card-hover);
+ border-color: rgba(99, 120, 160, 0.45);
}
.switch {
@@ -518,14 +681,14 @@ body {
input:checked + .slider {
background-color: var(--accent-green);
- box-shadow: var(--glow-green);
+ box-shadow: 0 0 10px var(--accent-green);
}
input:checked + .slider:before {
transform: translateX(22px);
}
-/* Inputs & Form */
+/* Forms */
.form-group {
margin-bottom: 1rem;
}
@@ -542,7 +705,7 @@ input:checked + .slider:before {
.form-input, .form-select {
width: 100%;
- padding: 0.6rem 0.85rem;
+ padding: 0.65rem 0.85rem;
background: rgba(10, 13, 20, 0.7);
border: 1px solid var(--border-color);
border-radius: var(--radius-sm);
@@ -554,10 +717,10 @@ input:checked + .slider:before {
.form-input:focus, .form-select:focus {
border-color: var(--accent-cyan);
- box-shadow: 0 0 10px rgba(56, 189, 248, 0.2);
+ box-shadow: 0 0 10px rgba(56, 189, 248, 0.25);
}
-/* JSON Code Editor Area */
+/* JSON Editor */
.json-editor-wrapper {
display: flex;
flex-direction: column;
@@ -583,7 +746,7 @@ input:checked + .slider:before {
border-color: var(--accent-cyan);
}
-/* Toast notifications */
+/* Toast */
.toast-container {
position: fixed;
bottom: 1.5rem;
@@ -622,12 +785,12 @@ input:checked + .slider:before {
to { transform: translateX(0); opacity: 1; }
}
-/* Modal */
+/* Modals */
.modal-overlay {
position: fixed;
inset: 0;
- background: rgba(0, 0, 0, 0.7);
- backdrop-filter: blur(6px);
+ background: rgba(0, 0, 0, 0.75);
+ backdrop-filter: blur(8px);
display: none;
align-items: center;
justify-content: center;
@@ -643,10 +806,11 @@ input:checked + .slider:before {
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: var(--radius-lg);
- max-width: 560px;
+ max-width: 600px;
width: 100%;
- padding: 1.5rem;
- box-shadow: 0 20px 40px rgba(0, 0, 0, 0.6);
+ padding: 1.75rem;
+ box-shadow: 0 20px 50px rgba(0, 0, 0, 0.7);
+ animation: fadeIn 0.2s ease-out;
}
.modal-header {
@@ -654,10 +818,18 @@ input:checked + .slider:before {
justify-content: space-between;
align-items: center;
margin-bottom: 1.25rem;
+ border-bottom: 1px solid var(--border-color);
+ padding-bottom: 0.75rem;
}
/* Responsive */
-@media (max-width: 768px) {
+@media (max-width: 900px) {
+ .lineup-grid {
+ grid-template-columns: repeat(2, 1fr);
+ }
+}
+
+@media (max-width: 640px) {
.app-header {
flex-direction: column;
align-items: flex-start;
@@ -666,10 +838,7 @@ input:checked + .slider:before {
width: 100%;
justify-content: space-between;
}
- .nav-tabs {
- padding: 0.5rem 1rem;
- }
- .main-container {
- padding: 1rem;
+ .lineup-grid {
+ grid-template-columns: 1fr;
}
}
diff --git a/index.html b/index.html
index 6f19eb8..ac6d348 100644
--- a/index.html
+++ b/index.html
@@ -3,7 +3,7 @@
- GaC-Srtool — FreeSR & Game Management Dashboard
+ GaC-Srtool — Ultimate Star Rail Server & Build Manager
@@ -14,54 +14,78 @@
✦
-
GaC-Srtool
-
Star Rail FreeSR & Server Controller
+
GaC-Srtool
+
Star Rail FreeSR & Server Controller
-
-
-
-
🎮 ทีมปัจจุบัน (Current Lineup)
-
-
กำลังโหลดข้อมูลทีม...
-
-
-
+
-
- กำลังดึงข้อมูลตัวละครจากเซิร์ฟเวอร์...
+ กำลังดึงข้อมูลตัวละคร...
-
+
+
+
+
+
+
+ 🗡️ กรวยแสงในคลัง (Lightcones)
+ 0 ชิ้น
+
+
รายการกรวยแสงทั้งหมดจาก freesr-data.json
+
+
กำลังโหลดข้อมูลกรวยแสง...
+
+
+
+
+
+
+ 🛡️ รีลิกส์ในคลัง (Relics)
+ 0 ชิ้น
+
+
รายการรีลิกส์ทั้งหมดจาก freesr-data.json
+
+
กำลังโหลดข้อมูลรีลิกส์...
+
+
+
+
+
+
+
+
+
+ 💾 จัดการไฟล์เซฟ Persistent (GaC-hkrpg-go Dedicated)
+ เซิร์ฟเวอร์ของเราเอง
+
+
+ ไฟล์ persistent เก็บการตั้งค่าสถานะผู้เล่นเฉพาะของเซิร์ฟเวอร์ GaC เช่น เพศ/ธาตุตัวเอก, สเตตัส March 7th, สวิตช์ฟังก์ชัน Global ต่างๆ
+
+
+
+
+
+
+
👤 ผู้บุกเบิก & March 7th
+
+
+
+
+
+
⚙️ สวิตช์ระบบพิเศษของ GaC
+
+
+
+
Silver Wolf Global Feature
+
เปิดใช้งานฟังก์ชันพิเศษ SW ทั่วทั้งเซิร์ฟเวอร์
+
+
+
+
+
+
+
Castorice Global Feature
+
เปิดใช้งานฟังก์ชันพิเศษ Castorice ทั่วทั้งเซิร์ฟเวอร์
+
+
+
+
+
+
+
+
+
+
+
-
⚡ ระบบปลดล็อค Ex-Rank Skill
+
⚡ ระบบปลดล็อค Ex-Rank Skill
-
-
+
+
-
+
เปิดใช้งานพรสวรรค์และกลไกพิเศษ Ex-Rank สำหรับตัวละครเฉพาะใน FreeSR (เช่น Jing Yuan โดน CC ไม่หยุดชะงัก, Dan Heng IL, Acheron)
-
- กำลังโหลดข้อมูล Ex-Rank...
-
+
-
+
-
📍 จุดวาร์ปปลอดภัยยอดนิยม (Safe Presets)
-
คลิกจุดวาร์ปเพื่อย้ายฉากของผู้เล่นในไฟล์เซฟได้ทันที
+
📍 จุดวาร์ปปลอดภัยยอดนิยม (Safe Presets)
+
คลิกจุดวาร์ปเพื่อย้ายฉากของผู้เล่นในไฟล์เซฟได้ทันที
@@ -181,8 +317,8 @@
-
🎯 กำหนดพิกัดวาร์ปเอง (Custom Teleport)
-
ใส่ PlaneID, FloorID และ EntryID ตามต้องการ
+
🎯 กำหนดพิกัดวาร์ปเอง (Custom Teleport)
+
ใส่ PlaneID, FloorID และ EntryID ตามต้องการ
-
-
+
- พร้อมใช้งาน
- Tip: สามารถแก้ไขค่าดิบแล้วกด 'บันทึกไปยังเซิร์ฟเวอร์' ได้ทันที
+ พร้อมใช้งาน
+ Tip: สามารถแก้ไขค่าดิบแล้วกด 'บันทึกไปยังเซิร์ฟเวอร์' ได้ทันที
-
+
-
⚙️ ตั้งค่าการเชื่อมต่อ API Gateway
-
ปรับเปลี่ยนพอร์ตหรือ IP ได้ตามไฟล์ server.json ของคุณ
+
⚙️ ตั้งค่าการเชื่อมต่อ API Gateway
+
ปรับเปลี่ยนพอร์ตหรือ IP ได้ตามเซิร์ฟเวอร์ที่คุณใช้
-
+
-
🌐 วิธีนำขึ้นโฮสต์ GitHub Pages (ฟรี 100%)
-
สามารถเปิดใช้งานเว็บนี้บนอินเทอร์เน็ตได้โดยไม่ต้องเสียค่าโฮสต์ใดๆ
-
-
- -
- 1. Create GitHub Repository:
- Create new repo e.g. GaC-srtool
-
- -
- 2. Push to GitHub:
- git remote add origin https://github.com/<user>/GaC-srtool.git
- git add . && git commit -m "Init" && git push -u origin main
-
- -
- 3. Enable GitHub Pages:
- Go to Repo > Settings > Pages > Source: Deploy from a branch > Branch: main / (root) > Click Save
-
- -
- 4. Done:
- Your dashboard will be online at https://<user>.github.io/GaC-srtool/!
-
-
+
💡 ข้อมูลโหมดเซิร์ฟเวอร์ & สำหรับผู้อื่น
+
+
+ ⚡ GaC-hkrpg-go (Full Access):
+ โหมดเฉพาะของเซิร์ฟเวอร์เรา สามารถอ่านและเขียนทั้ง freesr-data.json และ persistent ได้แบบ 100% เรียลไทม์ผ่าน REST API Gateway พร้อมระบบ Ex-Rank และ Teleport
+
+
+ 🔥 FireflyGo:
+ สำหรับผู้ใช้งานที่รันเซิร์ฟเวอร์ FireflyGo สามารถใช้เครื่องมือนี้จัดทีม, แม็กซ์ตัวละคร, หรือปรับแต่งอาวุธ/รีลิกส์ แล้วสั่งซิงค์ผ่าน /srtools หรือกดดาวน์โหลดไฟล์ freesr-data.json ไปวางในเซิร์ฟเวอร์ได้ทันที
+
+
+ 🕊️ RobinSR:
+ สำหรับผู้ใช้งาน RobinSR สามารถใช้เครื่องมือนี้ในการสร้างเซฟและตัวละครตามมาตรฐาน FreeSR ได้อย่างสมบูรณ์แบบ
+
+
+
+
+
+
+
+
+
+
![]()
+
+
+
+
+
+
+
+
+
+
+ ⭐ อัพสกิล & Traces ทั้งหมด (10/10)
+
+
+ 💾 บันทึกตัวละคร
+
+
+
+
+
+
+
+
diff --git a/js/app.js b/js/app.js
index dac494a..41b61b2 100644
--- a/js/app.js
+++ b/js/app.js
@@ -1,20 +1,28 @@
import { AVATAR_METADATA, getAvatarInfo } from './avatars-data.js';
import { PRESET_TELEPORTS, getTeleportName } from './teleports-data.js';
-import { SUPPORTED_LANGUAGES, getCurrentLanguage, setLanguage, t } from './i18n.js';
+import { SUPPORTED_LANGUAGES, getCurrentLanguage, setLanguage, t, translateElement, translatePath } from './i18n.js';
// Application State
const state = {
+ serverMode: localStorage.getItem('gac_srtool_mode') || 'gac', // 'gac' | 'firefly' | 'robin'
apiUrl: localStorage.getItem('gac_srtool_api_url') || 'http://localhost:21000',
pingIntervalSec: parseInt(localStorage.getItem('gac_srtool_ping_interval') || '10', 10),
isConnected: false,
statusData: null,
avatarsList: [],
- currentJsonMode: 'freesr', // 'freesr' or 'persistent'
- activeTab: 'tab-dashboard'
+ fullFreesrData: null,
+ persistentData: null,
+ currentJsonMode: 'freesr',
+ activeTab: 'tab-dashboard',
+ selectedLineupSlot: null,
+ selectedAvatarDetail: null
};
// UI Elements
const el = {
+ serverModeSelect: document.getElementById('server-mode-select'),
+ settingServerMode: document.getElementById('setting-server-mode'),
+ lblActiveMode: document.getElementById('lbl-active-mode'),
langSelect: document.getElementById('lang-select'),
statusDot: document.getElementById('status-dot'),
connectionStatusText: document.getElementById('connection-status-text'),
@@ -43,9 +51,25 @@ const el = {
avatarsGrid: document.getElementById('avatars-grid'),
avatarSearch: document.getElementById('avatar-search'),
avatarFilterRarity: document.getElementById('avatar-filter-rarity'),
+ avatarFilterElement: document.getElementById('avatar-filter-element'),
btnMaxAllAvatars: document.getElementById('btn-max-all-avatars'),
btnReloadAvatars: document.getElementById('btn-reload-avatars'),
+ // Equipment
+ lightconesList: document.getElementById('lightcones-list'),
+ relicsList: document.getElementById('relics-list'),
+ badgeTotalLc: document.getElementById('badge-total-lc'),
+ badgeTotalRelics: document.getElementById('badge-total-relics'),
+
+ // Persistent Tab
+ tabBtnPersistent: document.getElementById('tab-btn-persistent'),
+ formPersistentChars: document.getElementById('form-persistent-chars'),
+ persistentMcSelect: document.getElementById('persistent-mc-select'),
+ persistentMarchSelect: document.getElementById('persistent-march-select'),
+ toggleSwGlobal: document.getElementById('toggle-sw-global'),
+ toggleCastoriceGlobal: document.getElementById('toggle-castorice-global'),
+ btnSavePersistentToggles: document.getElementById('btn-save-persistent-toggles'),
+
// Ex-Rank
exrankList: document.getElementById('exrank-list'),
btnExrankEnableAll: document.getElementById('btn-exrank-enable-all'),
@@ -77,6 +101,26 @@ const el = {
settingPingInterval: document.getElementById('setting-ping-interval'),
btnResetSettings: document.getElementById('btn-reset-settings'),
+ // Character Detail Modal
+ modalAvatarDetail: document.getElementById('modal-avatar-detail'),
+ btnCloseAvatarModal: document.getElementById('btn-close-avatar-modal'),
+ modalCharTitle: document.getElementById('modal-char-title'),
+ modalCharImg: document.getElementById('modal-char-img'),
+ modalCharName: document.getElementById('modal-char-name'),
+ modalCharMeta: document.getElementById('modal-char-meta'),
+ modalCharLevel: document.getElementById('modal-char-level'),
+ modalCharPromotion: document.getElementById('modal-char-promotion'),
+ modalCharEidolonBtns: document.getElementById('modal-char-eidolon-btns'),
+ btnModalMaxSkills: document.getElementById('btn-modal-max-skills'),
+ btnModalSaveAvatar: document.getElementById('btn-modal-save-avatar'),
+
+ // Lineup Picker Modal
+ modalLineupPicker: document.getElementById('modal-lineup-picker'),
+ btnClosePickerModal: document.getElementById('btn-close-picker-modal'),
+ modalPickerTitle: document.getElementById('modal-picker-title'),
+ pickerSearch: document.getElementById('picker-search'),
+ pickerAvatarsGrid: document.getElementById('picker-avatars-grid'),
+
toastContainer: document.getElementById('toast-container')
};
@@ -150,7 +194,21 @@ function setConnectionStatus(connected, extraText = '') {
}
}
-// Apply multi-language translations across whole UI
+// Helper to get element CSS class
+function getElementClass(elem) {
+ const map = {
+ Fire: 'elem-fire',
+ Ice: 'elem-ice',
+ Lightning: 'elem-lightning',
+ Wind: 'elem-wind',
+ Physical: 'elem-physical',
+ Quantum: 'elem-quantum',
+ Imaginary: 'elem-imaginary'
+ };
+ return map[elem] || 'elem-physical';
+}
+
+// Apply multi-language translations across UI
function applyTranslations() {
document.querySelectorAll('[data-i18n]').forEach(elem => {
const key = elem.getAttribute('data-i18n');
@@ -162,7 +220,6 @@ function applyTranslations() {
elem.placeholder = t(key);
});
- // Re-render localized UI parts
if (state.statusData) {
renderLineup(state.statusData.lineups);
renderExRanks(state.statusData.ex_ranks || []);
@@ -173,7 +230,7 @@ function applyTranslations() {
}
}
-// Language selector initialization
+// Setup Language Selector
function setupLanguageSelector() {
el.langSelect.innerHTML = '';
SUPPORTED_LANGUAGES.forEach(lang => {
@@ -190,14 +247,73 @@ function setupLanguageSelector() {
const newLang = e.target.value;
setLanguage(newLang);
applyTranslations();
- showToast(`Language switched to ${SUPPORTED_LANGUAGES.find(l => l.code === newLang)?.name || newLang}`, 'info');
+ showToast(`Language: ${SUPPORTED_LANGUAGES.find(l => l.code === newLang)?.name || newLang}`, 'info');
});
}
-// Refresh status from /api/status
+// Setup Server Mode Selector
+function setupServerModes() {
+ const updateModeDisplay = (mode) => {
+ state.serverMode = mode;
+ localStorage.setItem('gac_srtool_mode', mode);
+ el.serverModeSelect.value = mode;
+ el.settingServerMode.value = mode;
+
+ if (mode === 'gac') {
+ el.lblActiveMode.textContent = 'GaC-hkrpg-go (เซิร์ฟเวอร์หลัก / Full Control)';
+ el.lblActiveMode.style.color = 'var(--accent-cyan)';
+ el.tabBtnPersistent.style.display = 'inline-flex';
+ } else if (mode === 'firefly') {
+ el.lblActiveMode.textContent = 'FireflyGo (FreeSR Compatible)';
+ el.lblActiveMode.style.color = '#f87171';
+ el.tabBtnPersistent.style.display = 'none';
+ } else if (mode === 'robin') {
+ el.lblActiveMode.textContent = 'RobinSR (FreeSR Compatible)';
+ el.lblActiveMode.style.color = '#c084fc';
+ el.tabBtnPersistent.style.display = 'none';
+ }
+ };
+
+ el.serverModeSelect.addEventListener('change', (e) => {
+ updateModeDisplay(e.target.value);
+ showToast(`สลับโหมดเซิร์ฟเวอร์เป็น: ${el.lblActiveMode.textContent}`, 'info');
+ refreshStatus();
+ });
+
+ el.settingServerMode.addEventListener('change', (e) => {
+ updateModeDisplay(e.target.value);
+ showToast(`สลับโหมดเซิร์ฟเวอร์เป็น: ${el.lblActiveMode.textContent}`, 'info');
+ refreshStatus();
+ });
+
+ updateModeDisplay(state.serverMode);
+}
+
+// Refresh status from server
async function refreshStatus() {
try {
- const data = await apiCall('/api/status');
+ let data;
+ if (state.serverMode === 'gac') {
+ data = await apiCall('/api/status');
+ } else {
+ // FireflyGo / RobinSR fallback check
+ const freesr = await apiCall('/api/freesr').catch(() => null);
+ if (freesr && freesr.data) {
+ data = {
+ status: 200,
+ avatar_count: Object.keys(freesr.data.avatars || {}).length,
+ lightcone_count: (freesr.data.lightcones || []).length,
+ relic_count: (freesr.data.relics || []).length,
+ lineups: freesr.data.lineups || { 0: 8001 },
+ scene: freesr.data.scene,
+ position: freesr.data.position,
+ ex_ranks: []
+ };
+ } else {
+ throw new Error('Server not reachable');
+ }
+ }
+
state.statusData = data;
setConnectionStatus(true);
@@ -223,45 +339,135 @@ async function refreshStatus() {
}
}
-// Render current lineup cards
+// Render party lineup cards
function renderLineup(lineups) {
- if (!lineups || Object.keys(lineups).length === 0) {
- el.lineupContainer.innerHTML = `
${t('slotEmpty')}
`;
- return;
- }
-
el.lineupContainer.innerHTML = '';
for (let slot = 0; slot < 4; slot++) {
- const avatarId = lineups[slot] || 0;
- const card = document.createElement('div');
- card.className = 'card';
- card.style.padding = '0.85rem';
+ const avatarId = lineups ? (lineups[slot] || 0) : 0;
+ const slotCard = document.createElement('div');
+ slotCard.className = `lineup-slot-card ${avatarId ? 'filled' : ''}`;
+ slotCard.title = 'คลิกเพื่อเลือกตัวละครลงตำแหน่งนี้';
if (avatarId === 0) {
- card.innerHTML = `
-
${t('slot')} ${slot + 1}
-
${t('slotEmpty')}
+ slotCard.innerHTML = `
+
Slot ${slot + 1}
+
➕
+
ว่าง (คลิกเพื่อเพิ่ม)
`;
} else {
const info = getAvatarInfo(avatarId);
- card.innerHTML = `
-
-
-

-
${info.name.charAt(0)}
-
-
-
${t('slot')} ${slot + 1}
-
${info.name}
-
${info.element} • ${info.path}
-
+ const elemClass = getElementClass(info.element);
+ slotCard.innerHTML = `
+
Slot ${slot + 1}
+

+
${info.name}
+
+ ${info.element}
+ ${info.path}
`;
}
- el.lineupContainer.appendChild(card);
+
+ slotCard.addEventListener('click', () => {
+ openLineupPicker(slot);
+ });
+
+ el.lineupContainer.appendChild(slotCard);
+ }
+}
+
+// Lineup Picker Modal
+function openLineupPicker(slotIndex) {
+ state.selectedLineupSlot = slotIndex;
+ el.modalPickerTitle.textContent = `เลือกตัวละครเข้าสู่ Slot ${slotIndex + 1}`;
+ el.pickerSearch.value = '';
+ renderPickerGrid();
+ el.modalLineupPicker.classList.add('active');
+}
+
+function renderPickerGrid() {
+ const search = el.pickerSearch.value.trim().toLowerCase();
+ el.pickerAvatarsGrid.innerHTML = '';
+
+ // Add Remove button
+ const removeCard = document.createElement('div');
+ removeCard.className = 'card';
+ removeCard.style.padding = '0.75rem';
+ removeCard.style.textAlign = 'center';
+ removeCard.style.cursor = 'pointer';
+ removeCard.innerHTML = `
+
❌
+
นำออกจากทีม
+ `;
+ removeCard.addEventListener('click', async () => {
+ await assignAvatarToLineup(state.selectedLineupSlot, 0);
+ el.modalLineupPicker.classList.remove('active');
+ });
+ el.pickerAvatarsGrid.appendChild(removeCard);
+
+ // Available avatars
+ state.avatarsList.forEach(item => {
+ const meta = getAvatarInfo(item.avatar_id);
+ if (search && !meta.name.toLowerCase().includes(search) && !String(item.avatar_id).includes(search)) {
+ return;
+ }
+
+ const elemClass = getElementClass(meta.element);
+ const card = document.createElement('div');
+ card.className = `card avatar-card card-rarity-${meta.rarity}`;
+ card.style.minHeight = 'auto';
+ card.style.padding = '0.85rem';
+ card.style.cursor = 'pointer';
+
+ card.innerHTML = `
+
+
+

+
+
+
${meta.name}
+
Lv. ${item.level} • E${item.rank}
+
+
+ `;
+
+ card.addEventListener('click', async () => {
+ await assignAvatarToLineup(state.selectedLineupSlot, item.avatar_id);
+ el.modalLineupPicker.classList.remove('active');
+ });
+
+ el.pickerAvatarsGrid.appendChild(card);
+ });
+}
+
+// Assign avatar to lineup slot
+async function assignAvatarToLineup(slot, avatarId) {
+ try {
+ if (!state.statusData || !state.statusData.lineups) return;
+ const lineups = { ...state.statusData.lineups };
+ lineups[slot] = avatarId;
+ state.statusData.lineups = lineups;
+
+ if (state.serverMode === 'gac') {
+ await apiCall('/api/persistent', 'POST', { lineups });
+ } else {
+ // In FreeSR standard mode
+ const freesr = await apiCall('/api/freesr');
+ if (freesr && freesr.data) {
+ freesr.data.lineups = lineups;
+ await apiCall('/api/freesr', 'POST', freesr.data);
+ }
+ }
+ showToast(`อัปเดตทีม Slot ${slot + 1} เรียบร้อย`, 'success');
+ renderLineup(lineups);
+ } catch (err) {
+ showToast(`เกิดข้อผิดพลาด: ${err.message}`, 'error');
}
}
@@ -280,24 +486,27 @@ async function loadAvatars() {
function renderAvatarsGrid() {
const searchTerm = el.avatarSearch.value.trim().toLowerCase();
const rarityFilter = el.avatarFilterRarity.value;
+ const elementFilter = el.avatarFilterElement.value;
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;
- return matchesSearch && matchesRarity;
+ const matchesElement = elementFilter === 'all' || meta.element === elementFilter;
+ return matchesSearch && matchesRarity && matchesElement;
});
if (filtered.length === 0) {
- el.avatarsGrid.innerHTML = '
No matching avatars
';
+ el.avatarsGrid.innerHTML = '
ไม่พบตัวละครที่ตรงกับเงื่อนไข
';
return;
}
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.className = `card avatar-card card-rarity-${meta.rarity}`;
const pipsHtml = [0, 1, 2, 3, 4, 5, 6].map(rank => `
@@ -319,18 +528,16 @@ function renderAvatarsGrid() {
${meta.name}
- Lv. ${item.level}
- •
- ${meta.element}
- •
- ${meta.path}
+ ${meta.element}
+ ${meta.path}
+ Lv. ${item.level}
-
${t('eidolonLabel')}
+
${t('eidolonLabel')}
${pipsHtml}
@@ -347,17 +554,121 @@ function renderAvatarsGrid() {
});
});
+ // Clicking card opens full character build modal!
+ card.addEventListener('click', () => {
+ openAvatarDetailModal(item);
+ });
+
el.avatarsGrid.appendChild(card);
});
}
+// Character Detail / Build Modal
+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`;
+ el.modalCharImg.onerror = function() {
+ this.src = `https://cdn.jsdelivr.net/gh/Mar-7th/StarRailRes@master/icon/avatar/${avatarItem.avatar_id}.png`;
+ };
+ el.modalCharName.textContent = meta.name;
+ el.modalCharMeta.innerHTML = `
+
${meta.element}
+
${meta.path}
+
★ ${meta.rarity}
+ `;
+
+ el.modalCharLevel.value = avatarItem.level || 80;
+ el.modalCharPromotion.value = avatarItem.promotion || 6;
+
+ // Render modal eidolon pips
+ el.modalCharEidolonBtns.innerHTML = '';
+ for (let r = 0; r <= 6; r++) {
+ const btn = document.createElement('button');
+ btn.className = `eidolon-pip ${avatarItem.rank >= r ? 'active' : ''}`;
+ btn.textContent = r;
+ btn.style.width = '32px';
+ btn.style.height = '32px';
+ btn.style.fontSize = '0.9rem';
+ btn.addEventListener('click', () => {
+ avatarItem.rank = r;
+ el.modalCharEidolonBtns.querySelectorAll('.eidolon-pip').forEach((b, idx) => {
+ if (idx <= r) b.classList.add('active');
+ else b.classList.remove('active');
+ });
+ });
+ el.modalCharEidolonBtns.appendChild(btn);
+ }
+
+ el.modalAvatarDetail.classList.add('active');
+}
+
+// Save avatar details from modal
+async function saveAvatarDetails() {
+ if (!state.selectedAvatarDetail) return;
+ const aid = state.selectedAvatarDetail.avatar_id;
+ const level = parseInt(el.modalCharLevel.value, 10) || 80;
+ const promotion = parseInt(el.modalCharPromotion.value, 10) || 6;
+ const rank = state.selectedAvatarDetail.rank ?? 6;
+
+ try {
+ await apiCall('/api/freesr/avatars', 'POST', {
+ avatar_id: aid,
+ level,
+ promotion,
+ rank
+ });
+ showToast(`บันทึกตัวละคร ${aid} สำเร็จ (Lv. ${level}, Prom. ${promotion}, E${rank})`, 'success');
+ el.modalAvatarDetail.classList.remove('active');
+
+ // Update local state and re-render
+ const target = state.avatarsList.find(a => a.avatar_id === aid);
+ if (target) {
+ target.level = level;
+ target.promotion = promotion;
+ target.rank = rank;
+ renderAvatarsGrid();
+ }
+ } catch (err) {
+ showToast(`เกิดข้อผิดพลาด: ${err.message}`, 'error');
+ }
+}
+
+// Max Skills & Traces for character
+async function maxSkillsForAvatar() {
+ if (!state.selectedAvatarDetail) return;
+ const aid = state.selectedAvatarDetail.avatar_id;
+ try {
+ // Read freesr data, max skill levels for this avatar, and push back
+ const freesr = await apiCall('/api/freesr');
+ if (freesr && freesr.data && freesr.data.avatars && freesr.data.avatars[aid]) {
+ const av = freesr.data.avatars[aid];
+ if (av.data && av.data.skills) {
+ for (const k in av.data.skills) {
+ av.data.skills[k] = 10;
+ }
+ }
+ av.level = 80;
+ av.promotion = 6;
+ av.data.rank = 6;
+ await apiCall('/api/freesr', 'POST', freesr.data);
+ showToast(`อัพสกิลและ Traces ทั้งหมดของตัวละคร ${aid} เป็น 10/10 สำเร็จ!`, 'success');
+ loadAvatars();
+ }
+ } catch (err) {
+ showToast(`เกิดข้อผิดพลาด: ${err.message}`, 'error');
+ }
+}
+
// Set avatar rank API
async function setAvatarRank(avatarId, rank) {
try {
await apiCall('/api/avatar/rank', 'POST', { avatar_id: avatarId, rank });
showToast(t('avatarRankSuccess', { id: avatarId, rank }), 'success');
- // Update local state and re-render
const target = state.avatarsList.find(a => a.avatar_id === avatarId);
if (target) {
target.rank = rank;
@@ -381,10 +692,94 @@ async function maxAllAvatars() {
}
}
+// Load Equipment (Lightcones & Relics)
+async function loadEquipment() {
+ try {
+ const res = await apiCall('/api/freesr');
+ if (!res || !res.data) return;
+ state.fullFreesrData = res.data;
+
+ const lcs = res.data.lightcones || [];
+ const relics = res.data.relics || [];
+
+ el.badgeTotalLc.textContent = `${lcs.length} ชิ้น`;
+ el.badgeTotalRelics.textContent = `${relics.length} ชิ้น`;
+
+ // Render Lightcones
+ el.lightconesList.innerHTML = '';
+ lcs.slice(0, 100).forEach(lc => {
+ const item = document.createElement('div');
+ item.className = 'card';
+ item.style.padding = '0.75rem';
+ item.style.display = 'flex';
+ item.style.justifyContent = 'space-between';
+ item.style.alignItems = 'center';
+
+ const equipped = lc.equip_avatar ? getAvatarInfo(lc.equip_avatar).name : 'ไม่ได้สวมใส่';
+ item.innerHTML = `
+
+
Lightcone ID: ${lc.item_id} (UID: ${lc.internal_uid})
+
+ Lv. ${lc.level} • ขัดเกลา S${lc.rank} • สวมใส่โดย: ${equipped}
+
+
+ `;
+ el.lightconesList.appendChild(item);
+ });
+
+ // Render Relics
+ el.relicsList.innerHTML = '';
+ relics.slice(0, 100).forEach(r => {
+ const item = document.createElement('div');
+ item.className = 'card';
+ item.style.padding = '0.75rem';
+ item.style.display = 'flex';
+ item.style.justifyContent = 'space-between';
+ item.style.alignItems = 'center';
+
+ const equipped = r.equip_avatar ? getAvatarInfo(r.equip_avatar).name : 'ไม่ได้สวมใส่';
+ const slotNum = r.relic_id % 10;
+ item.innerHTML = `
+
+
Relic ID: ${r.relic_id} (Slot: ${slotNum})
+
+ Lv. ${r.level} • สเตตัสหลัก: ${r.main_affix_id} • สวมใส่โดย: ${equipped}
+
+
+ `;
+ el.relicsList.appendChild(item);
+ });
+
+ } catch (err) {
+ showToast(`โหลดคลังอุปกรณ์ไม่สำเร็จ: ${err.message}`, 'error');
+ }
+}
+
+// Load Persistent Save Settings
+async function loadPersistentSettings() {
+ try {
+ const res = await apiCall('/api/persistent');
+ if (!res || !res.persistent) return;
+ const p = res.persistent;
+ state.persistentData = p;
+
+ if (p.main_character) {
+ el.persistentMcSelect.value = p.main_character;
+ }
+ if (p.march_type) {
+ el.persistentMarchSelect.value = p.march_type;
+ }
+ el.toggleSwGlobal.checked = !!p.enable_sw_global;
+ el.toggleCastoriceGlobal.checked = !!p.enable_castorice_global;
+ } catch (err) {
+ console.warn('Persistent settings not available in current mode:', err.message);
+ }
+}
+
// Render Ex-Ranks
function renderExRanks(exList) {
if (!exList || exList.length === 0) {
- el.exrankList.innerHTML = '
No Ex-Rank Avatars
';
+ el.exrankList.innerHTML = '
ไม่มีข้อมูลตัวละคร Ex-Rank
';
return;
}
@@ -396,7 +791,7 @@ function renderExRanks(exList) {
card.innerHTML = `
-
+

${localizedMeta.name.charAt(0)}
-
${localizedMeta.name}
+
${localizedMeta.name}
Avatar ID: ${item.avatar_id} • Ex-Rank Skill ID: ${item.ex_rank_id}
@@ -514,7 +909,7 @@ async function loadJsonData(mode) {
async function saveJsonData() {
const text = el.jsonEditor.value.trim();
if (!text) {
- showToast('No JSON data to save', 'error');
+ showToast('ไม่มีข้อมูล JSON ให้บันทึก', 'error');
return;
}
@@ -554,6 +949,10 @@ function setupTabs() {
// On-demand tab refresh
if (targetId === 'tab-avatars' && state.avatarsList.length === 0) {
loadAvatars();
+ } else if (targetId === 'tab-equipment') {
+ loadEquipment();
+ } else if (targetId === 'tab-persistent') {
+ loadPersistentSettings();
} else if (targetId === 'tab-json') {
loadJsonData(state.currentJsonMode);
}
@@ -590,6 +989,15 @@ function setupEvents() {
}
});
+ // Character Detail Modal Controls
+ el.btnCloseAvatarModal.addEventListener('click', () => el.modalAvatarDetail.classList.remove('active'));
+ el.btnModalSaveAvatar.addEventListener('click', saveAvatarDetails);
+ el.btnModalMaxSkills.addEventListener('click', maxSkillsForAvatar);
+
+ // Lineup Picker Modal Controls
+ el.btnClosePickerModal.addEventListener('click', () => el.modalLineupPicker.classList.remove('active'));
+ el.pickerSearch.addEventListener('input', renderPickerGrid);
+
// Quick Action Buttons
el.btnQuickMaxAll.addEventListener('click', maxAllAvatars);
el.btnMaxAllAvatars.addEventListener('click', maxAllAvatars);
@@ -602,16 +1010,50 @@ function setupEvents() {
el.btnQuickSyncPersistent.addEventListener('click', async () => {
try {
await apiCall('/api/persistent');
- showToast('Persistent data synced', 'success');
+ showToast('ซิงค์ข้อมูล Persistent เรียบร้อย', 'success');
refreshStatus();
} catch (err) {
showToast(`Sync failed: ${err.message}`, 'error');
}
});
- // Avatar Search & Filter
+ // Persistent Form Submit
+ el.formPersistentChars.addEventListener('submit', async (e) => {
+ e.preventDefault();
+ const mc = el.persistentMcSelect.value;
+ const march = el.persistentMarchSelect.value;
+ try {
+ await apiCall('/api/persistent', 'POST', {
+ main_character: mc,
+ march_type: march
+ });
+ showToast('บันทึกการตั้งค่าตัวละครสำเร็จ!', 'success');
+ refreshStatus();
+ } catch (err) {
+ showToast(`เกิดข้อผิดพลาด: ${err.message}`, 'error');
+ }
+ });
+
+ // Persistent Toggles
+ el.btnSavePersistentToggles.addEventListener('click', async () => {
+ const sw = el.toggleSwGlobal.checked;
+ const castorice = el.toggleCastoriceGlobal.checked;
+ try {
+ await apiCall('/api/persistent', 'POST', {
+ enable_sw_global: sw,
+ enable_castorice_global: castorice
+ });
+ showToast('บันทึกสวิตช์ Global เรียบร้อย!', 'success');
+ refreshStatus();
+ } catch (err) {
+ showToast(`เกิดข้อผิดพลาด: ${err.message}`, 'error');
+ }
+ });
+
+ // Avatar Search & Filters
el.avatarSearch.addEventListener('input', renderAvatarsGrid);
el.avatarFilterRarity.addEventListener('change', renderAvatarsGrid);
+ el.avatarFilterElement.addEventListener('change', renderAvatarsGrid);
el.btnReloadAvatars.addEventListener('click', loadAvatars);
// Ex-Rank All Controls
@@ -657,7 +1099,7 @@ function setupEvents() {
try {
const parsed = JSON.parse(el.jsonEditor.value);
el.jsonEditor.value = JSON.stringify(parsed, null, 2);
- showToast('JSON beautified', 'info');
+ showToast('จัดรูปแบบ JSON สวยงามแล้ว', 'info');
} catch (err) {
showToast(t('jsonInvalid'), 'error');
}
@@ -672,7 +1114,7 @@ function setupEvents() {
a.href = URL.createObjectURL(blob);
a.download = `${state.currentJsonMode}-${Date.now()}.json`;
a.click();
- showToast(`Downloaded ${a.download}`, 'success');
+ showToast(`ดาวน์โหลด ${a.download} เรียบร้อย`, 'success');
});
el.jsonFileInput.addEventListener('change', (e) => {
@@ -683,7 +1125,7 @@ function setupEvents() {
try {
const parsed = JSON.parse(event.target.result);
el.jsonEditor.value = JSON.stringify(parsed, null, 2);
- showToast(`Loaded ${file.name}`, 'success');
+ showToast(`โหลดเนื้อหาไฟล์ ${file.name} เรียบร้อย`, 'success');
} catch (err) {
showToast(t('jsonInvalid'), 'error');
}
@@ -728,11 +1170,13 @@ function startPingLoop() {
// Initialize Application
function init() {
+ setupServerModes();
setupLanguageSelector();
setupTabs();
setupEvents();
applyTranslations();
refreshStatus();
+ loadAvatars();
startPingLoop();
}