feat: upgrade character build modal with skills info, traces, energy, and lightcones
This commit is contained in:
1 parent
edc2843f0c
commit
42b6d6a5a9
4 files changed
+761
-106
No files matched your search
+158
@@ -966,6 +966,164 @@ input:checked + .slider:before {
|
|||||||
padding-bottom: 0.75rem;
|
padding-bottom: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Modal Sub-tabs */
|
||||||
|
.modal-subtabs {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-bottom: 1.25rem;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-subtab-btn {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 700;
|
||||||
|
padding: 0.45rem 0.95rem;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: var(--transition);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-subtab-btn:hover {
|
||||||
|
color: var(--text-highlight);
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-subtab-btn.active {
|
||||||
|
color: #fff;
|
||||||
|
background: var(--accent-blue);
|
||||||
|
box-shadow: 0 0 12px rgba(2, 132, 199, 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Skill Stepper & Row Cards (Inspired by srtools.punklorde.org/skills-info) */
|
||||||
|
.skill-row-card {
|
||||||
|
background: rgba(0, 0, 0, 0.35);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-row-card:hover {
|
||||||
|
border-color: rgba(56, 189, 248, 0.4);
|
||||||
|
box-shadow: 0 0 12px rgba(56, 189, 248, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-row-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-name {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 0.925rem;
|
||||||
|
color: var(--text-highlight);
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-lvl-badge {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-family: monospace;
|
||||||
|
color: var(--accent-cyan);
|
||||||
|
background: rgba(56, 189, 248, 0.12);
|
||||||
|
padding: 0.2rem 0.55rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid rgba(56, 189, 248, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-stepper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-step {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
background: var(--bg-card);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 1.15rem;
|
||||||
|
font-weight: 800;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-step:hover {
|
||||||
|
border-color: var(--accent-cyan);
|
||||||
|
background: rgba(56, 189, 248, 0.25);
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-max-pill {
|
||||||
|
padding: 0.25rem 0.65rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid rgba(251, 191, 36, 0.5);
|
||||||
|
background: rgba(251, 191, 36, 0.15);
|
||||||
|
color: var(--accent-gold);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 800;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-max-pill:hover {
|
||||||
|
background: rgba(251, 191, 36, 0.35);
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-range {
|
||||||
|
flex: 1;
|
||||||
|
accent-color: var(--accent-cyan);
|
||||||
|
cursor: pointer;
|
||||||
|
height: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trace-toggle-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.6rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--text-main);
|
||||||
|
background: rgba(0, 0, 0, 0.3);
|
||||||
|
padding: 0.55rem 0.85rem;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border-color 0.2s ease, background 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trace-toggle-label:hover {
|
||||||
|
border-color: var(--accent-cyan);
|
||||||
|
background: rgba(56, 189, 248, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.trace-checkbox {
|
||||||
|
width: 17px;
|
||||||
|
height: 17px;
|
||||||
|
accent-color: var(--accent-cyan);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lc-super-pip {
|
||||||
|
width: 40px;
|
||||||
|
height: 36px;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
/* Responsive */
|
/* Responsive */
|
||||||
@media (max-width: 900px) {
|
@media (max-width: 900px) {
|
||||||
.lineup-grid {
|
.lineup-grid {
|
||||||
|
|||||||
+203
-52
@@ -115,7 +115,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span class="form-label">พิกัดตัวละคร (X, Y, Z)</span>
|
<span class="form-label">พิกัดตัวละคร XYZ</span>
|
||||||
<span id="lbl-coordinates" style="color: var(--text-muted); font-size: 0.85rem;">-</span>
|
<span id="lbl-coordinates" style="color: var(--text-muted); font-size: 0.85rem;">-</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -380,7 +380,8 @@
|
|||||||
|
|
||||||
<!-- Character Detail / Build Modal -->
|
<!-- Character Detail / Build Modal -->
|
||||||
<div class="modal-overlay" id="modal-avatar-detail">
|
<div class="modal-overlay" id="modal-avatar-detail">
|
||||||
<div class="modal-content" style="max-width: 640px;">
|
<div class="modal-content" style="max-width: 760px;">
|
||||||
|
<!-- Modal Header -->
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h3 id="modal-char-title" style="font-weight: 800; font-size: 1.15rem; color: var(--text-highlight);">
|
<h3 id="modal-char-title" style="font-weight: 800; font-size: 1.15rem; color: var(--text-highlight);">
|
||||||
ปรับแต่งตัวละคร
|
ปรับแต่งตัวละคร
|
||||||
@@ -388,75 +389,225 @@
|
|||||||
<button class="btn btn-sm btn-secondary" id="btn-close-avatar-modal">✕</button>
|
<button class="btn btn-sm btn-secondary" id="btn-close-avatar-modal">✕</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Character Top Banner & Quick Max All -->
|
||||||
<div style="display: flex; gap: 1.25rem; align-items: center; margin-bottom: 1.25rem; flex-wrap: wrap;">
|
<div style="display: flex; gap: 1.25rem; align-items: center; margin-bottom: 1.25rem; flex-wrap: wrap;">
|
||||||
<div class="avatar-image-container" style="width: 76px; height: 76px;">
|
<div class="avatar-image-container" style="width: 76px; height: 76px;">
|
||||||
<img id="modal-char-img" class="avatar-img" src="" alt="">
|
<img id="modal-char-img" class="avatar-img" src="" alt="">
|
||||||
</div>
|
</div>
|
||||||
<div style="flex: 1; min-width: 180px;">
|
<div style="flex: 1; min-width: 180px;">
|
||||||
<div id="modal-char-name" style="font-size: 1.25rem; font-weight: 800;"></div>
|
<div id="modal-char-name" style="font-size: 1.25rem; font-weight: 800;"></div>
|
||||||
<div id="modal-char-meta" style="font-size: 0.85rem; color: var(--text-muted); margin-top: 0.25rem; display: flex; gap: 0.35rem; align-items: center; flex-wrap: wrap;"></div>
|
<div id="modal-char-meta" style="font-size: 0.85rem; color: var(--text-muted); margin-top: 0.25rem; display: flex; gap: 0.4rem; align-items: center; flex-wrap: wrap;"></div>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-gold btn-sm" id="btn-modal-max-all-char">
|
<button type="button" class="btn btn-gold" id="btn-modal-max-all-char" style="padding: 0.5rem 1rem; font-weight: 800;">
|
||||||
⭐ ปรับเต็มทั้งหมด
|
⭐ ปรับเต็มทั้งหมด
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid-2" style="gap: 1rem; margin-bottom: 1rem;">
|
<!-- Sub Tabs Navigation (srtools style) -->
|
||||||
<div class="form-group">
|
<div class="modal-subtabs" style="display: flex; gap: 0.5rem; margin-bottom: 1.25rem; border-bottom: 1px solid var(--border-color); padding-bottom: 0.5rem; overflow-x: auto;">
|
||||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.35rem;">
|
<button type="button" class="modal-subtab-btn active" data-modaltab="modaltab-skills">
|
||||||
<label class="form-label" style="margin: 0;">ระดับเลเวล (1-80)</label>
|
⚡ สกิลและร่องรอย
|
||||||
<div style="display: flex; gap: 0.25rem;">
|
</button>
|
||||||
<button type="button" class="btn btn-sm btn-secondary btn-lvl-preset" data-lvl="1" style="padding: 0.15rem 0.45rem; font-size: 0.75rem;">1</button>
|
<button type="button" class="modal-subtab-btn" data-modaltab="modaltab-status">
|
||||||
<button type="button" class="btn btn-sm btn-secondary btn-lvl-preset" data-lvl="20" style="padding: 0.15rem 0.45rem; font-size: 0.75rem;">20</button>
|
⚙️ เลเวล & Eidolon
|
||||||
<button type="button" class="btn btn-sm btn-secondary btn-lvl-preset" data-lvl="60" style="padding: 0.15rem 0.45rem; font-size: 0.75rem;">60</button>
|
</button>
|
||||||
<button type="button" class="btn btn-sm btn-secondary btn-lvl-preset" data-lvl="70" style="padding: 0.15rem 0.45rem; font-size: 0.75rem;">70</button>
|
<button type="button" class="modal-subtab-btn" data-modaltab="modaltab-lightcone">
|
||||||
<button type="button" class="btn btn-sm btn-primary btn-lvl-preset" data-lvl="80" style="padding: 0.15rem 0.45rem; font-size: 0.75rem;">80</button>
|
🗡️ กรวยแสง
|
||||||
</div>
|
</button>
|
||||||
</div>
|
|
||||||
<input type="number" id="modal-char-level" class="form-input" min="1" max="80" value="80">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.35rem;">
|
|
||||||
<label class="form-label" style="margin: 0;">การเลื่อนขั้น (0-6)</label>
|
|
||||||
<div style="display: flex; gap: 0.25rem;">
|
|
||||||
<button type="button" class="btn btn-sm btn-secondary btn-prom-preset" data-prom="0" style="padding: 0.15rem 0.45rem; font-size: 0.75rem;">0</button>
|
|
||||||
<button type="button" class="btn btn-sm btn-secondary btn-prom-preset" data-prom="3" style="padding: 0.15rem 0.45rem; font-size: 0.75rem;">3</button>
|
|
||||||
<button type="button" class="btn btn-sm btn-primary btn-prom-preset" data-prom="6" style="padding: 0.15rem 0.45rem; font-size: 0.75rem;">6</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<input type="number" id="modal-char-promotion" class="form-input" min="0" max="6" value="6">
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<!-- Sub Tab 1: Skills & Traces (Like https://srtools.punklorde.org/skills-info) -->
|
||||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.35rem;">
|
<div id="modaltab-skills" class="modal-subtab-content active">
|
||||||
<label class="form-label" style="margin: 0;">ระดับ Eidolon</label>
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem; flex-wrap: wrap; gap: 0.5rem;">
|
||||||
<div style="display: flex; gap: 0.4rem;">
|
<div style="font-weight: 700; font-size: 0.95rem; color: var(--text-highlight);">
|
||||||
<button type="button" class="btn btn-sm btn-secondary" id="btn-modal-quick-e0" style="padding: 0.2rem 0.6rem; font-size: 0.75rem;">
|
ระดับสกิลต่อสู้
|
||||||
รีเซ็ต E0
|
|
||||||
</button>
|
|
||||||
<button type="button" class="btn btn-sm btn-gold" id="btn-modal-quick-e6" style="padding: 0.2rem 0.6rem; font-size: 0.75rem;">
|
|
||||||
⭐ ปลดเต็ม E6
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="eidolon-buttons" id="modal-char-eidolon-btns" style="margin-top: 0.5rem; gap: 0.5rem;">
|
|
||||||
<!-- Injected pips -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group" style="margin-top: 1rem;">
|
|
||||||
<label class="form-label">สกิลและร่องรอย</label>
|
|
||||||
<div style="background: rgba(0,0,0,0.3); border: 1px solid var(--border-color); border-radius: var(--radius-sm); padding: 0.75rem; display: flex; justify-content: space-between; align-items: center;">
|
|
||||||
<div style="font-size: 0.85rem; color: var(--text-muted);">
|
|
||||||
โจมตีปกติเลเวล 6, สกิลต่อสู้เลเวล 10, ท่าไม้ตายเลเวล 10, พรสวรรค์เลเวล 10 และโหนดเสริมทั้งหมด
|
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-sm btn-gold" id="btn-modal-max-skills">
|
<button type="button" class="btn btn-sm btn-gold" id="btn-modal-max-skills">
|
||||||
⭐ อัพสกิล 10/10
|
⭐ ปรับสกิลเต็ม 10/10
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 4 Core Skills -->
|
||||||
|
<div class="skills-grid" style="display: flex; flex-direction: column; gap: 0.75rem;">
|
||||||
|
<!-- 1. Basic Attack -->
|
||||||
|
<div class="skill-row-card">
|
||||||
|
<div class="skill-row-header">
|
||||||
|
<span class="skill-name">โจมตีปกติ</span>
|
||||||
|
<span class="skill-lvl-badge">Lv. <strong id="val-skill-basic">6</strong> / 6</span>
|
||||||
|
</div>
|
||||||
|
<div class="skill-stepper">
|
||||||
|
<button type="button" class="btn-step" id="btn-step-dec-basic">-</button>
|
||||||
|
<input type="range" min="1" max="6" value="6" id="range-skill-basic" class="skill-range">
|
||||||
|
<button type="button" class="btn-step" id="btn-step-inc-basic">+</button>
|
||||||
|
<button type="button" class="btn-max-pill" id="btn-skill-max-basic">MAX</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 2. Skill -->
|
||||||
|
<div class="skill-row-card">
|
||||||
|
<div class="skill-row-header">
|
||||||
|
<span class="skill-name">สกิลต่อสู้</span>
|
||||||
|
<span class="skill-lvl-badge">Lv. <strong id="val-skill-skill">10</strong> / 10</span>
|
||||||
|
</div>
|
||||||
|
<div class="skill-stepper">
|
||||||
|
<button type="button" class="btn-step" id="btn-step-dec-skill">-</button>
|
||||||
|
<input type="range" min="1" max="10" value="10" id="range-skill-skill" class="skill-range">
|
||||||
|
<button type="button" class="btn-step" id="btn-step-inc-skill">+</button>
|
||||||
|
<button type="button" class="btn-max-pill" id="btn-skill-max-skill">MAX</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 3. Ultimate -->
|
||||||
|
<div class="skill-row-card">
|
||||||
|
<div class="skill-row-header">
|
||||||
|
<span class="skill-name">ท่าไม้ตาย</span>
|
||||||
|
<span class="skill-lvl-badge">Lv. <strong id="val-skill-ultra">10</strong> / 10</span>
|
||||||
|
</div>
|
||||||
|
<div class="skill-stepper">
|
||||||
|
<button type="button" class="btn-step" id="btn-step-dec-ultra">-</button>
|
||||||
|
<input type="range" min="1" max="10" value="10" id="range-skill-ultra" class="skill-range">
|
||||||
|
<button type="button" class="btn-step" id="btn-step-inc-ultra">+</button>
|
||||||
|
<button type="button" class="btn-max-pill" id="btn-skill-max-ultra">MAX</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 4. Talent -->
|
||||||
|
<div class="skill-row-card">
|
||||||
|
<div class="skill-row-header">
|
||||||
|
<span class="skill-name">พรสวรรค์</span>
|
||||||
|
<span class="skill-lvl-badge">Lv. <strong id="val-skill-talent">10</strong> / 10</span>
|
||||||
|
</div>
|
||||||
|
<div class="skill-stepper">
|
||||||
|
<button type="button" class="btn-step" id="btn-step-dec-talent">-</button>
|
||||||
|
<input type="range" min="1" max="10" value="10" id="range-skill-talent" class="skill-range">
|
||||||
|
<button type="button" class="btn-step" id="btn-step-inc-talent">+</button>
|
||||||
|
<button type="button" class="btn-max-pill" id="btn-skill-max-talent">MAX</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Traces & Technique Section -->
|
||||||
|
<div style="margin-top: 1.25rem;">
|
||||||
|
<div style="font-weight: 700; font-size: 0.95rem; color: var(--text-highlight); margin-bottom: 0.75rem;">
|
||||||
|
ร่องรอยและเทคนิค
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 0.6rem;">
|
||||||
|
<label class="trace-toggle-label">
|
||||||
|
<input type="checkbox" id="check-trace-technique" checked class="trace-checkbox">
|
||||||
|
<span>⚡ เทคนิค — เปิดใช้งานก่อนเริ่มการต่อสู้</span>
|
||||||
|
</label>
|
||||||
|
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 0.6rem;">
|
||||||
|
<label class="trace-toggle-label">
|
||||||
|
<input type="checkbox" id="check-trace-a2" checked class="trace-checkbox">
|
||||||
|
<span>🌟 ร่องรอยพิเศษ A2</span>
|
||||||
|
</label>
|
||||||
|
<label class="trace-toggle-label">
|
||||||
|
<input type="checkbox" id="check-trace-a4" checked class="trace-checkbox">
|
||||||
|
<span>🌟 ร่องรอยพิเศษ A4</span>
|
||||||
|
</label>
|
||||||
|
<label class="trace-toggle-label">
|
||||||
|
<input type="checkbox" id="check-trace-a6" checked class="trace-checkbox">
|
||||||
|
<span>🌟 ร่องรอยพิเศษ A6</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<label class="trace-toggle-label">
|
||||||
|
<input type="checkbox" id="check-trace-stats" checked class="trace-checkbox">
|
||||||
|
<span>💎 โหนดสเตตัสเสริมทั้งหมด (10 โหนด)</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Sub Tab 2: Status & Eidolon -->
|
||||||
|
<div id="modaltab-status" class="modal-subtab-content" style="display: none;">
|
||||||
|
<div class="grid-2" style="gap: 1rem; margin-bottom: 1rem;">
|
||||||
|
<div class="form-group">
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.35rem;">
|
||||||
|
<label class="form-label" style="margin: 0;">ระดับเลเวล (1-80)</label>
|
||||||
|
<div style="display: flex; gap: 0.25rem;">
|
||||||
|
<button type="button" class="btn btn-sm btn-secondary btn-lvl-preset" data-lvl="1">1</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-secondary btn-lvl-preset" data-lvl="20">20</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-secondary btn-lvl-preset" data-lvl="60">60</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-secondary btn-lvl-preset" data-lvl="70">70</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-primary btn-lvl-preset" data-lvl="80">80</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<input type="number" id="modal-char-level" class="form-input" min="1" max="80" value="80">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.35rem;">
|
||||||
|
<label class="form-label" style="margin: 0;">การเลื่อนขั้น (0-6)</label>
|
||||||
|
<div style="display: flex; gap: 0.25rem;">
|
||||||
|
<button type="button" class="btn btn-sm btn-secondary btn-prom-preset" data-prom="0">0</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-secondary btn-prom-preset" data-prom="3">3</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-primary btn-prom-preset" data-prom="6">6</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<input type="number" id="modal-char-promotion" class="form-input" min="0" max="6" value="6">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Eidolon Section -->
|
||||||
|
<div class="form-group">
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.35rem;">
|
||||||
|
<label class="form-label" style="margin: 0;">ระดับ Eidolon</label>
|
||||||
|
<div style="display: flex; gap: 0.4rem;">
|
||||||
|
<button type="button" class="btn btn-sm btn-secondary" id="btn-modal-quick-e0">รีเซ็ต E0</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-gold" id="btn-modal-quick-e6">⭐ ปลดเต็ม E6</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="eidolon-buttons" id="modal-char-eidolon-btns" style="margin-top: 0.5rem; gap: 0.5rem;">
|
||||||
|
<!-- Injected pips -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Ultimate Energy Section -->
|
||||||
|
<div class="form-group" style="margin-top: 1.25rem;">
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.35rem;">
|
||||||
|
<label class="form-label" style="margin: 0;">พลังงานท่าไม้ตาย</label>
|
||||||
|
<div style="display: flex; gap: 0.35rem;">
|
||||||
|
<button type="button" class="btn btn-sm btn-secondary" id="btn-modal-energy-50">ตั้งค่า 50%</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-primary" id="btn-modal-energy-100">เต็ม 100%</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; gap: 0.75rem; align-items: center;">
|
||||||
|
<input type="range" id="range-char-energy" min="0" max="140" value="140" class="skill-range" style="flex: 1;">
|
||||||
|
<span id="val-char-energy" style="font-weight: 800; font-family: monospace; min-width: 60px; text-align: right;">140/140</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Sub Tab 3: Lightcone Equipment -->
|
||||||
|
<div id="modaltab-lightcone" class="modal-subtab-content" style="display: none;">
|
||||||
|
<div class="card" style="padding: 1.25rem; background: rgba(0,0,0,0.3); border: 1px solid var(--border-color);">
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem; flex-wrap: wrap; gap: 0.5rem;">
|
||||||
|
<span style="font-weight: 800; font-size: 1rem; color: var(--text-highlight);">กรวยแสงที่สวมใส่</span>
|
||||||
|
<button type="button" class="btn btn-sm btn-gold" id="btn-modal-max-lc">⭐ ปรับกรวยแสงเต็มขั้น</button>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">ID กรวยแสง</label>
|
||||||
|
<input type="number" id="modal-lc-id" class="form-input" placeholder="เช่น 21002 หรือ 24001">
|
||||||
|
</div>
|
||||||
|
<div class="grid-2" style="gap: 1rem;">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">เลเวลกรวยแสง (1-80)</label>
|
||||||
|
<input type="number" id="modal-lc-level" class="form-input" min="1" max="80" value="80">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">ระดับการขัดเกลา</label>
|
||||||
|
<div class="eidolon-buttons" id="modal-lc-rank-btns" style="margin-top: 0.25rem;">
|
||||||
|
<button type="button" class="eidolon-pip lc-super-pip" data-rank="1">S1</button>
|
||||||
|
<button type="button" class="eidolon-pip lc-super-pip" data-rank="2">S2</button>
|
||||||
|
<button type="button" class="eidolon-pip lc-super-pip" data-rank="3">S3</button>
|
||||||
|
<button type="button" class="eidolon-pip lc-super-pip" data-rank="4">S4</button>
|
||||||
|
<button type="button" class="eidolon-pip lc-super-pip active" data-rank="5">S5</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal Footer -->
|
||||||
<div style="display: flex; gap: 0.75rem; margin-top: 1.5rem; justify-content: flex-end; flex-wrap: wrap;">
|
<div style="display: flex; gap: 0.75rem; margin-top: 1.5rem; justify-content: flex-end; flex-wrap: wrap;">
|
||||||
<button type="button" class="btn btn-secondary" id="btn-modal-cancel-avatar">
|
<button type="button" class="btn btn-secondary" id="btn-modal-cancel-avatar">
|
||||||
✕ ปิด
|
✕ ปิด
|
||||||
|
|||||||
@@ -106,6 +106,47 @@ const el = {
|
|||||||
btnModalMaxSkills: document.getElementById('btn-modal-max-skills'),
|
btnModalMaxSkills: document.getElementById('btn-modal-max-skills'),
|
||||||
btnModalSaveAvatar: document.getElementById('btn-modal-save-avatar'),
|
btnModalSaveAvatar: document.getElementById('btn-modal-save-avatar'),
|
||||||
|
|
||||||
|
// Skills Info Steppers (Inspired by srtools.punklorde.org/skills-info)
|
||||||
|
valSkillBasic: document.getElementById('val-skill-basic'),
|
||||||
|
rangeSkillBasic: document.getElementById('range-skill-basic'),
|
||||||
|
btnStepDecBasic: document.getElementById('btn-step-dec-basic'),
|
||||||
|
btnStepIncBasic: document.getElementById('btn-step-inc-basic'),
|
||||||
|
btnSkillMaxBasic: document.getElementById('btn-skill-max-basic'),
|
||||||
|
|
||||||
|
valSkillSkill: document.getElementById('val-skill-skill'),
|
||||||
|
rangeSkillSkill: document.getElementById('range-skill-skill'),
|
||||||
|
btnStepDecSkill: document.getElementById('btn-step-dec-skill'),
|
||||||
|
btnStepIncSkill: document.getElementById('btn-step-inc-skill'),
|
||||||
|
btnSkillMaxSkill: document.getElementById('btn-skill-max-skill'),
|
||||||
|
|
||||||
|
valSkillUltra: document.getElementById('val-skill-ultra'),
|
||||||
|
rangeSkillUltra: document.getElementById('range-skill-ultra'),
|
||||||
|
btnStepDecUltra: document.getElementById('btn-step-dec-ultra'),
|
||||||
|
btnStepIncUltra: document.getElementById('btn-step-inc-ultra'),
|
||||||
|
btnSkillMaxUltra: document.getElementById('btn-skill-max-ultra'),
|
||||||
|
|
||||||
|
valSkillTalent: document.getElementById('val-skill-talent'),
|
||||||
|
rangeSkillTalent: document.getElementById('range-skill-talent'),
|
||||||
|
btnStepDecTalent: document.getElementById('btn-step-dec-talent'),
|
||||||
|
btnStepIncTalent: document.getElementById('btn-step-inc-talent'),
|
||||||
|
btnSkillMaxTalent: document.getElementById('btn-skill-max-talent'),
|
||||||
|
|
||||||
|
checkTraceTechnique: document.getElementById('check-trace-technique'),
|
||||||
|
checkTraceA2: document.getElementById('check-trace-a2'),
|
||||||
|
checkTraceA4: document.getElementById('check-trace-a4'),
|
||||||
|
checkTraceA6: document.getElementById('check-trace-a6'),
|
||||||
|
checkTraceStats: document.getElementById('check-trace-stats'),
|
||||||
|
|
||||||
|
btnModalEnergy50: document.getElementById('btn-modal-energy-50'),
|
||||||
|
btnModalEnergy100: document.getElementById('btn-modal-energy-100'),
|
||||||
|
rangeCharEnergy: document.getElementById('range-char-energy'),
|
||||||
|
valCharEnergy: document.getElementById('val-char-energy'),
|
||||||
|
|
||||||
|
modalLcId: document.getElementById('modal-lc-id'),
|
||||||
|
modalLcLevel: document.getElementById('modal-lc-level'),
|
||||||
|
btnModalMaxLc: document.getElementById('btn-modal-max-lc'),
|
||||||
|
modalLcRankBtns: document.getElementById('modal-lc-rank-btns'),
|
||||||
|
|
||||||
// Lineup Picker Modal
|
// Lineup Picker Modal
|
||||||
modalLineupPicker: document.getElementById('modal-lineup-picker'),
|
modalLineupPicker: document.getElementById('modal-lineup-picker'),
|
||||||
btnClosePickerModal: document.getElementById('btn-close-picker-modal'),
|
btnClosePickerModal: document.getElementById('btn-close-picker-modal'),
|
||||||
@@ -671,15 +712,52 @@ async function maxSingleAvatar(avatarId) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Character Detail / Build Modal
|
// Active Lightcone Superimposition Rank in Modal (1 - 5)
|
||||||
|
let selectedLcRank = 5;
|
||||||
|
|
||||||
|
// Modal Sub-tabs Handler
|
||||||
|
function setupModalSubtabs() {
|
||||||
|
const tabBtns = document.querySelectorAll('.modal-subtab-btn');
|
||||||
|
tabBtns.forEach(btn => {
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
tabBtns.forEach(b => b.classList.remove('active'));
|
||||||
|
document.querySelectorAll('.modal-subtab-content').forEach(c => {
|
||||||
|
c.style.display = 'none';
|
||||||
|
c.classList.remove('active');
|
||||||
|
});
|
||||||
|
btn.classList.add('active');
|
||||||
|
const targetId = btn.getAttribute('data-modaltab');
|
||||||
|
const targetContent = document.getElementById(targetId);
|
||||||
|
if (targetContent) {
|
||||||
|
targetContent.style.display = 'block';
|
||||||
|
targetContent.classList.add('active');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateLcRankPips(rank) {
|
||||||
|
selectedLcRank = rank;
|
||||||
|
document.querySelectorAll('.lc-super-pip').forEach(btn => {
|
||||||
|
const r = parseInt(btn.getAttribute('data-rank'), 10);
|
||||||
|
if (r === rank) {
|
||||||
|
btn.classList.add('active');
|
||||||
|
} else {
|
||||||
|
btn.classList.remove('active');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Character Detail / Build Modal (Inspired by srtools.punklorde.org/skills-info)
|
||||||
function openAvatarDetailModal(avatarItem) {
|
function openAvatarDetailModal(avatarItem) {
|
||||||
state.selectedAvatarDetail = avatarItem;
|
state.selectedAvatarDetail = avatarItem;
|
||||||
const meta = getAvatarInfo(avatarItem.avatar_id);
|
const aid = avatarItem.avatar_id;
|
||||||
|
const meta = getAvatarInfo(aid);
|
||||||
|
|
||||||
el.modalCharTitle.textContent = `ปรับแต่งตัวละคร: ${meta.name} (ID: ${avatarItem.avatar_id})`;
|
el.modalCharTitle.textContent = `ปรับแต่งตัวละคร: ${meta.name} (ID: ${aid})`;
|
||||||
el.modalCharImg.src = `./assets/avatars/${avatarItem.avatar_id}.png`;
|
el.modalCharImg.src = `./assets/avatars/${aid}.png`;
|
||||||
el.modalCharImg.onerror = function() {
|
el.modalCharImg.onerror = function() {
|
||||||
this.src = `https://cdn.jsdelivr.net/gh/Mar-7th/StarRailRes@master/icon/avatar/${avatarItem.avatar_id}.png`;
|
this.src = `https://cdn.jsdelivr.net/gh/Mar-7th/StarRailRes@master/icon/avatar/${aid}.png`;
|
||||||
};
|
};
|
||||||
el.modalCharName.textContent = meta.name;
|
el.modalCharName.textContent = meta.name;
|
||||||
el.modalCharMeta.innerHTML = `
|
el.modalCharMeta.innerHTML = `
|
||||||
@@ -688,10 +766,97 @@ function openAvatarDetailModal(avatarItem) {
|
|||||||
<span class="avatar-badge-rarity rarity-${meta.rarity}" style="position: static;">★ ${meta.rarity}</span>
|
<span class="avatar-badge-rarity rarity-${meta.rarity}" style="position: static;">★ ${meta.rarity}</span>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
// Reset to first sub-tab (Skills & Traces)
|
||||||
|
document.querySelectorAll('.modal-subtab-btn').forEach(b => b.classList.remove('active'));
|
||||||
|
document.querySelectorAll('.modal-subtab-content').forEach(c => {
|
||||||
|
c.style.display = 'none';
|
||||||
|
c.classList.remove('active');
|
||||||
|
});
|
||||||
|
const firstTabBtn = document.querySelector('.modal-subtab-btn[data-modaltab="modaltab-skills"]');
|
||||||
|
if (firstTabBtn) firstTabBtn.classList.add('active');
|
||||||
|
const firstTabContent = document.getElementById('modaltab-skills');
|
||||||
|
if (firstTabContent) {
|
||||||
|
firstTabContent.style.display = 'block';
|
||||||
|
firstTabContent.classList.add('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Basic Status
|
||||||
el.modalCharLevel.value = avatarItem.level || 80;
|
el.modalCharLevel.value = avatarItem.level || 80;
|
||||||
el.modalCharPromotion.value = avatarItem.promotion || 6;
|
el.modalCharPromotion.value = avatarItem.promotion || 6;
|
||||||
|
|
||||||
renderModalEidolonPips(avatarItem.rank ?? 6);
|
renderModalEidolonPips(avatarItem.rank ?? 6);
|
||||||
|
|
||||||
|
// Load avatar data from fullFreesrData if available
|
||||||
|
const av = state.fullFreesrData?.avatars?.[aid];
|
||||||
|
const skills = av?.data?.skills || {};
|
||||||
|
const anchorSkills = av?.data?.skills_by_anchor_type || {};
|
||||||
|
|
||||||
|
// 1. Basic Attack: anchor 1 or aid + '001' (1-6)
|
||||||
|
const basicVal = anchorSkills['1'] ?? skills[`${aid}001`] ?? 6;
|
||||||
|
if (el.rangeSkillBasic && el.valSkillBasic) {
|
||||||
|
el.rangeSkillBasic.value = Math.min(6, Math.max(1, basicVal));
|
||||||
|
el.valSkillBasic.textContent = el.rangeSkillBasic.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Skill: anchor 2 or aid + '002' (1-10)
|
||||||
|
const skillVal = anchorSkills['2'] ?? skills[`${aid}002`] ?? 10;
|
||||||
|
if (el.rangeSkillSkill && el.valSkillSkill) {
|
||||||
|
el.rangeSkillSkill.value = Math.min(10, Math.max(1, skillVal));
|
||||||
|
el.valSkillSkill.textContent = el.rangeSkillSkill.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Ultimate: anchor 3 or aid + '003' (1-10)
|
||||||
|
const ultraVal = anchorSkills['3'] ?? skills[`${aid}003`] ?? 10;
|
||||||
|
if (el.rangeSkillUltra && el.valSkillUltra) {
|
||||||
|
el.rangeSkillUltra.value = Math.min(10, Math.max(1, ultraVal));
|
||||||
|
el.valSkillUltra.textContent = el.rangeSkillUltra.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Talent: anchor 4 or aid + '004' (1-10)
|
||||||
|
const talentVal = anchorSkills['4'] ?? skills[`${aid}004`] ?? 10;
|
||||||
|
if (el.rangeSkillTalent && el.valSkillTalent) {
|
||||||
|
el.rangeSkillTalent.value = Math.min(10, Math.max(1, talentVal));
|
||||||
|
el.valSkillTalent.textContent = el.rangeSkillTalent.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Traces & Technique
|
||||||
|
if (el.checkTraceTechnique) {
|
||||||
|
el.checkTraceTechnique.checked = (anchorSkills['5'] ?? skills[`${aid}007`] ?? 1) > 0;
|
||||||
|
}
|
||||||
|
if (el.checkTraceA2) {
|
||||||
|
el.checkTraceA2.checked = (anchorSkills['6'] ?? skills[`${aid}101`] ?? 1) > 0;
|
||||||
|
}
|
||||||
|
if (el.checkTraceA4) {
|
||||||
|
el.checkTraceA4.checked = (anchorSkills['7'] ?? skills[`${aid}102`] ?? 1) > 0;
|
||||||
|
}
|
||||||
|
if (el.checkTraceA6) {
|
||||||
|
el.checkTraceA6.checked = (anchorSkills['8'] ?? skills[`${aid}103`] ?? 1) > 0;
|
||||||
|
}
|
||||||
|
if (el.checkTraceStats) {
|
||||||
|
el.checkTraceStats.checked = (anchorSkills['9'] ?? skills[`${aid}201`] ?? 1) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ultimate Energy
|
||||||
|
const maxSp = av?.sp_max ?? 140;
|
||||||
|
const currentSp = av?.sp_value ?? maxSp;
|
||||||
|
if (el.rangeCharEnergy && el.valCharEnergy) {
|
||||||
|
el.rangeCharEnergy.max = maxSp;
|
||||||
|
el.rangeCharEnergy.value = currentSp;
|
||||||
|
el.valCharEnergy.textContent = `${currentSp}/${maxSp}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lightcone
|
||||||
|
const equippedLc = (state.fullFreesrData?.lightcones || []).find(lc => lc.equip_avatar === aid);
|
||||||
|
if (equippedLc) {
|
||||||
|
if (el.modalLcId) el.modalLcId.value = equippedLc.item_id || '';
|
||||||
|
if (el.modalLcLevel) el.modalLcLevel.value = equippedLc.level || 80;
|
||||||
|
selectedLcRank = equippedLc.rank || 5;
|
||||||
|
} else {
|
||||||
|
if (el.modalLcId) el.modalLcId.value = '';
|
||||||
|
if (el.modalLcLevel) el.modalLcLevel.value = 80;
|
||||||
|
selectedLcRank = 5;
|
||||||
|
}
|
||||||
|
updateLcRankPips(selectedLcRank);
|
||||||
|
|
||||||
el.modalAvatarDetail.classList.add('active');
|
el.modalAvatarDetail.classList.add('active');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -746,6 +911,63 @@ async function setAvatarRank(avatarId, rank) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Max Skills & Traces for character
|
||||||
|
function maxSkillsForAvatar() {
|
||||||
|
if (el.rangeSkillBasic && el.valSkillBasic) {
|
||||||
|
el.rangeSkillBasic.value = 6;
|
||||||
|
el.valSkillBasic.textContent = 6;
|
||||||
|
}
|
||||||
|
if (el.rangeSkillSkill && el.valSkillSkill) {
|
||||||
|
el.rangeSkillSkill.value = 10;
|
||||||
|
el.valSkillSkill.textContent = 10;
|
||||||
|
}
|
||||||
|
if (el.rangeSkillUltra && el.valSkillUltra) {
|
||||||
|
el.rangeSkillUltra.value = 10;
|
||||||
|
el.valSkillUltra.textContent = 10;
|
||||||
|
}
|
||||||
|
if (el.rangeSkillTalent && el.valSkillTalent) {
|
||||||
|
el.rangeSkillTalent.value = 10;
|
||||||
|
el.valSkillTalent.textContent = 10;
|
||||||
|
}
|
||||||
|
if (el.checkTraceTechnique) el.checkTraceTechnique.checked = true;
|
||||||
|
if (el.checkTraceA2) el.checkTraceA2.checked = true;
|
||||||
|
if (el.checkTraceA4) el.checkTraceA4.checked = true;
|
||||||
|
if (el.checkTraceA6) el.checkTraceA6.checked = true;
|
||||||
|
if (el.checkTraceStats) el.checkTraceStats.checked = true;
|
||||||
|
|
||||||
|
showToast('⭐ ปรับสกิลและร่องรอยเต็มทั้งหมดแล้ว', 'info');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Max All Build For Current Avatar (Lv.80, Prom 6, E6, Max Skills, Max Traces, 100% Energy, LC Lv.80 S5)
|
||||||
|
async function maxAllBuildForCurrentAvatar() {
|
||||||
|
if (!state.selectedAvatarDetail) return;
|
||||||
|
const aid = state.selectedAvatarDetail.avatar_id;
|
||||||
|
|
||||||
|
// Level & Eidolon
|
||||||
|
el.modalCharLevel.value = 80;
|
||||||
|
el.modalCharPromotion.value = 6;
|
||||||
|
state.selectedAvatarDetail.level = 80;
|
||||||
|
state.selectedAvatarDetail.promotion = 6;
|
||||||
|
state.selectedAvatarDetail.rank = 6;
|
||||||
|
renderModalEidolonPips(6);
|
||||||
|
|
||||||
|
// Skills & Traces
|
||||||
|
maxSkillsForAvatar();
|
||||||
|
|
||||||
|
// Energy 100%
|
||||||
|
if (el.rangeCharEnergy && el.valCharEnergy) {
|
||||||
|
el.rangeCharEnergy.value = el.rangeCharEnergy.max;
|
||||||
|
el.valCharEnergy.textContent = `${el.rangeCharEnergy.max}/${el.rangeCharEnergy.max}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lightcone
|
||||||
|
if (el.modalLcLevel) el.modalLcLevel.value = 80;
|
||||||
|
updateLcRankPips(5);
|
||||||
|
|
||||||
|
// Save changes
|
||||||
|
await saveAvatarDetails();
|
||||||
|
}
|
||||||
|
|
||||||
// Save avatar details from modal
|
// Save avatar details from modal
|
||||||
async function saveAvatarDetails() {
|
async function saveAvatarDetails() {
|
||||||
if (!state.selectedAvatarDetail) return;
|
if (!state.selectedAvatarDetail) return;
|
||||||
@@ -765,55 +987,133 @@ async function saveAvatarDetails() {
|
|||||||
target.rank = rank;
|
target.rank = rank;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.fullFreesrData?.avatars?.[aid]) {
|
if (!state.fullFreesrData) {
|
||||||
state.fullFreesrData.avatars[aid].level = level;
|
state.fullFreesrData = { avatars: {}, lightcones: [], relics: [] };
|
||||||
state.fullFreesrData.avatars[aid].promotion = promotion;
|
}
|
||||||
state.fullFreesrData.avatars[aid].data.rank = rank;
|
if (!state.fullFreesrData.avatars) {
|
||||||
|
state.fullFreesrData.avatars = {};
|
||||||
|
}
|
||||||
|
if (!state.fullFreesrData.avatars[aid]) {
|
||||||
|
state.fullFreesrData.avatars[aid] = {
|
||||||
|
owner_uid: 0,
|
||||||
|
avatar_id: aid,
|
||||||
|
level,
|
||||||
|
promotion,
|
||||||
|
techniques: [],
|
||||||
|
sp_value: parseInt(el.rangeCharEnergy?.value, 10) || 140,
|
||||||
|
sp_max: parseInt(el.rangeCharEnergy?.max, 10) || 140,
|
||||||
|
data: {
|
||||||
|
rank,
|
||||||
|
skills: {},
|
||||||
|
skills_by_anchor_type: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const av = state.fullFreesrData.avatars[aid];
|
||||||
|
av.level = level;
|
||||||
|
av.promotion = promotion;
|
||||||
|
av.data = av.data || {};
|
||||||
|
av.data.rank = rank;
|
||||||
|
if (el.rangeCharEnergy) {
|
||||||
|
av.sp_value = parseInt(el.rangeCharEnergy.value, 10) || av.sp_max || 140;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract skills values
|
||||||
|
const basicVal = parseInt(el.rangeSkillBasic?.value, 10) || 6;
|
||||||
|
const skillVal = parseInt(el.rangeSkillSkill?.value, 10) || 10;
|
||||||
|
const ultraVal = parseInt(el.rangeSkillUltra?.value, 10) || 10;
|
||||||
|
const talentVal = parseInt(el.rangeSkillTalent?.value, 10) || 10;
|
||||||
|
const techVal = el.checkTraceTechnique?.checked ? 1 : 0;
|
||||||
|
const a2Val = el.checkTraceA2?.checked ? 1 : 0;
|
||||||
|
const a4Val = el.checkTraceA4?.checked ? 1 : 0;
|
||||||
|
const a6Val = el.checkTraceA6?.checked ? 1 : 0;
|
||||||
|
const statVal = el.checkTraceStats?.checked ? 1 : 0;
|
||||||
|
|
||||||
|
av.data.skills = av.data.skills || {};
|
||||||
|
av.data.skills[`${aid}001`] = basicVal;
|
||||||
|
av.data.skills[`${aid}002`] = skillVal;
|
||||||
|
av.data.skills[`${aid}003`] = ultraVal;
|
||||||
|
av.data.skills[`${aid}004`] = talentVal;
|
||||||
|
av.data.skills[`${aid}007`] = techVal;
|
||||||
|
av.data.skills[`${aid}101`] = a2Val;
|
||||||
|
av.data.skills[`${aid}102`] = a4Val;
|
||||||
|
av.data.skills[`${aid}103`] = a6Val;
|
||||||
|
for (let i = 1; i <= 10; i++) {
|
||||||
|
const pad = i < 10 ? `0${i}` : `${i}`;
|
||||||
|
av.data.skills[`${aid}2${pad}`] = statVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
av.data.skills_by_anchor_type = {
|
||||||
|
"1": basicVal,
|
||||||
|
"2": skillVal,
|
||||||
|
"3": ultraVal,
|
||||||
|
"4": talentVal,
|
||||||
|
"5": techVal,
|
||||||
|
"6": a2Val,
|
||||||
|
"7": a4Val,
|
||||||
|
"8": a6Val,
|
||||||
|
"9": statVal,
|
||||||
|
"10": statVal,
|
||||||
|
"11": statVal,
|
||||||
|
"12": statVal,
|
||||||
|
"13": statVal,
|
||||||
|
"14": statVal,
|
||||||
|
"15": statVal,
|
||||||
|
"16": statVal,
|
||||||
|
"17": statVal,
|
||||||
|
"18": statVal
|
||||||
|
};
|
||||||
|
|
||||||
|
// Lightcone update
|
||||||
|
state.fullFreesrData.lightcones = state.fullFreesrData.lightcones || [];
|
||||||
|
const lcId = parseInt(el.modalLcId?.value, 10);
|
||||||
|
const lcLevel = parseInt(el.modalLcLevel?.value, 10) || 80;
|
||||||
|
const lcRank = selectedLcRank || 5;
|
||||||
|
const lcProm = lcLevel > 70 ? 6 : (lcLevel > 60 ? 5 : (lcLevel > 50 ? 4 : (lcLevel > 40 ? 3 : (lcLevel > 20 ? 1 : 0))));
|
||||||
|
|
||||||
|
const existingLcIndex = state.fullFreesrData.lightcones.findIndex(lc => lc.equip_avatar === aid);
|
||||||
|
if (lcId && lcId > 0) {
|
||||||
|
if (existingLcIndex !== -1) {
|
||||||
|
state.fullFreesrData.lightcones[existingLcIndex].item_id = lcId;
|
||||||
|
state.fullFreesrData.lightcones[existingLcIndex].level = lcLevel;
|
||||||
|
state.fullFreesrData.lightcones[existingLcIndex].rank = lcRank;
|
||||||
|
state.fullFreesrData.lightcones[existingLcIndex].promotion = lcProm;
|
||||||
|
} else {
|
||||||
|
state.fullFreesrData.lightcones.push({
|
||||||
|
internal_uid: state.fullFreesrData.lightcones.length,
|
||||||
|
item_id: lcId,
|
||||||
|
equip_avatar: aid,
|
||||||
|
level: lcLevel,
|
||||||
|
rank: lcRank,
|
||||||
|
promotion: lcProm
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (existingLcIndex !== -1) {
|
||||||
|
state.fullFreesrData.lightcones[existingLcIndex].equip_avatar = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderAvatarsGrid();
|
renderAvatarsGrid();
|
||||||
|
loadEquipment();
|
||||||
el.modalAvatarDetail.classList.remove('active');
|
el.modalAvatarDetail.classList.remove('active');
|
||||||
|
|
||||||
if (state.isConnected) {
|
if (state.isConnected) {
|
||||||
try {
|
try {
|
||||||
if (state.serverMode === 'gac') {
|
if (state.serverMode === 'gac') {
|
||||||
await apiCall('/api/freesr/avatars', 'POST', { avatar_id: aid, level, promotion, rank });
|
await apiCall('/api/freesr/avatars', 'POST', { avatar_id: aid, level, promotion, rank });
|
||||||
|
await saveFreesrChangesToServer();
|
||||||
} else {
|
} else {
|
||||||
await saveFreesrChangesToServer();
|
await saveFreesrChangesToServer();
|
||||||
}
|
}
|
||||||
showToast(`บันทึกตัวละคร ${getAvatarInfo(aid).name} เรียบร้อย (Lv. ${level}, Prom. ${promotion}, E${rank})`, 'success');
|
showToast(`บันทึกตัวละคร ${getAvatarInfo(aid).name} เรียบร้อย`, 'success');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showToast(`บันทึกในเครื่องแล้ว (ส่งเซิร์ฟเวอร์ไม่ได้: ${err.message})`, 'warning');
|
showToast(`บันทึกในเครื่องแล้ว (ส่งเซิร์ฟเวอร์ไม่ได้: ${err.message})`, 'warning');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
showToast(`บันทึกตัวละคร ${getAvatarInfo(aid).name} เรียบร้อย (Lv. ${level}, Prom. ${promotion}, E${rank})`, 'success');
|
showToast(`บันทึกตัวละคร ${getAvatarInfo(aid).name} เรียบร้อย`, 'success');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Max Skills & Traces for character
|
|
||||||
async function maxSkillsForAvatar() {
|
|
||||||
if (!state.selectedAvatarDetail) return;
|
|
||||||
const aid = state.selectedAvatarDetail.avatar_id;
|
|
||||||
el.modalCharLevel.value = 80;
|
|
||||||
el.modalCharPromotion.value = 6;
|
|
||||||
state.selectedAvatarDetail.level = 80;
|
|
||||||
state.selectedAvatarDetail.promotion = 6;
|
|
||||||
state.selectedAvatarDetail.rank = 6;
|
|
||||||
renderModalEidolonPips(6);
|
|
||||||
|
|
||||||
if (state.fullFreesrData?.avatars?.[aid]) {
|
|
||||||
const av = state.fullFreesrData.avatars[aid];
|
|
||||||
av.level = 80;
|
|
||||||
av.promotion = 6;
|
|
||||||
av.data.rank = 6;
|
|
||||||
if (av.data.skills) {
|
|
||||||
for (const k in av.data.skills) av.data.skills[k] = 10;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
showToast(`อัพเลเวล 80, เลื่อนขั้น 6, E6 และ Traces 10/10 สำหรับ ${getAvatarInfo(aid).name} สำเร็จ!`, 'success');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Max all avatars
|
// Max all avatars
|
||||||
async function maxAllAvatars() {
|
async function maxAllAvatars() {
|
||||||
if (!confirm(t('confirmMaxAll'))) return;
|
if (!confirm(t('confirmMaxAll'))) return;
|
||||||
@@ -1184,17 +1484,62 @@ function setupEvents() {
|
|||||||
document.getElementById('btn-modal-cancel-avatar')?.addEventListener('click', () => el.modalAvatarDetail.classList.remove('active'));
|
document.getElementById('btn-modal-cancel-avatar')?.addEventListener('click', () => el.modalAvatarDetail.classList.remove('active'));
|
||||||
el.btnModalSaveAvatar.addEventListener('click', saveAvatarDetails);
|
el.btnModalSaveAvatar.addEventListener('click', saveAvatarDetails);
|
||||||
el.btnModalMaxSkills.addEventListener('click', maxSkillsForAvatar);
|
el.btnModalMaxSkills.addEventListener('click', maxSkillsForAvatar);
|
||||||
|
document.getElementById('btn-modal-max-all-char')?.addEventListener('click', maxAllBuildForCurrentAvatar);
|
||||||
|
|
||||||
document.getElementById('btn-modal-max-all-char')?.addEventListener('click', async () => {
|
// Steppers for 4 Skills (Basic, Skill, Ultra, Talent)
|
||||||
if (!state.selectedAvatarDetail) return;
|
const setupSkillStepper = (rangeEl, valEl, decBtn, incBtn, maxBtn, maxVal) => {
|
||||||
el.modalCharLevel.value = 80;
|
if (!rangeEl || !valEl) return;
|
||||||
el.modalCharPromotion.value = 6;
|
rangeEl.addEventListener('input', () => { valEl.textContent = rangeEl.value; });
|
||||||
state.selectedAvatarDetail.level = 80;
|
decBtn?.addEventListener('click', () => {
|
||||||
state.selectedAvatarDetail.promotion = 6;
|
let cur = parseInt(rangeEl.value, 10);
|
||||||
state.selectedAvatarDetail.rank = 6;
|
if (cur > parseInt(rangeEl.min, 10)) {
|
||||||
renderModalEidolonPips(6);
|
rangeEl.value = cur - 1;
|
||||||
await maxSkillsForAvatar();
|
valEl.textContent = rangeEl.value;
|
||||||
await saveAvatarDetails();
|
}
|
||||||
|
});
|
||||||
|
incBtn?.addEventListener('click', () => {
|
||||||
|
let cur = parseInt(rangeEl.value, 10);
|
||||||
|
if (cur < parseInt(rangeEl.max, 10)) {
|
||||||
|
rangeEl.value = cur + 1;
|
||||||
|
valEl.textContent = rangeEl.value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
maxBtn?.addEventListener('click', () => {
|
||||||
|
rangeEl.value = maxVal;
|
||||||
|
valEl.textContent = maxVal;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
setupSkillStepper(el.rangeSkillBasic, el.valSkillBasic, el.btnStepDecBasic, el.btnStepIncBasic, el.btnSkillMaxBasic, 6);
|
||||||
|
setupSkillStepper(el.rangeSkillSkill, el.valSkillSkill, el.btnStepDecSkill, el.btnStepIncSkill, el.btnSkillMaxSkill, 10);
|
||||||
|
setupSkillStepper(el.rangeSkillUltra, el.valSkillUltra, el.btnStepDecUltra, el.btnStepIncUltra, el.btnSkillMaxUltra, 10);
|
||||||
|
setupSkillStepper(el.rangeSkillTalent, el.valSkillTalent, el.btnStepDecTalent, el.btnStepIncTalent, el.btnSkillMaxTalent, 10);
|
||||||
|
|
||||||
|
// Ultimate Energy Controls
|
||||||
|
el.rangeCharEnergy?.addEventListener('input', () => {
|
||||||
|
el.valCharEnergy.textContent = `${el.rangeCharEnergy.value}/${el.rangeCharEnergy.max}`;
|
||||||
|
});
|
||||||
|
el.btnModalEnergy50?.addEventListener('click', () => {
|
||||||
|
const half = Math.floor(parseInt(el.rangeCharEnergy.max, 10) * 0.5);
|
||||||
|
el.rangeCharEnergy.value = half;
|
||||||
|
el.valCharEnergy.textContent = `${half}/${el.rangeCharEnergy.max}`;
|
||||||
|
});
|
||||||
|
el.btnModalEnergy100?.addEventListener('click', () => {
|
||||||
|
el.rangeCharEnergy.value = el.rangeCharEnergy.max;
|
||||||
|
el.valCharEnergy.textContent = `${el.rangeCharEnergy.max}/${el.rangeCharEnergy.max}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Lightcone Controls in Modal
|
||||||
|
document.querySelectorAll('.lc-super-pip').forEach(btn => {
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
const r = parseInt(btn.getAttribute('data-rank'), 10);
|
||||||
|
updateLcRankPips(r);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
el.btnModalMaxLc?.addEventListener('click', () => {
|
||||||
|
if (el.modalLcLevel) el.modalLcLevel.value = 80;
|
||||||
|
updateLcRankPips(5);
|
||||||
|
showToast('ปรับกรวยแสงเป็นเลเวล 80 ขัดเกลา 5 เรียบร้อย', 'info');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Quick Preset Buttons for Character Modal
|
// Quick Preset Buttons for Character Modal
|
||||||
@@ -1430,6 +1775,7 @@ function init() {
|
|||||||
setupServerModes();
|
setupServerModes();
|
||||||
setupLanguageSelector();
|
setupLanguageSelector();
|
||||||
setupTabs();
|
setupTabs();
|
||||||
|
setupModalSubtabs();
|
||||||
setupEvents();
|
setupEvents();
|
||||||
applyTranslations();
|
applyTranslations();
|
||||||
refreshStatus();
|
refreshStatus();
|
||||||
|
|||||||
+8
-8
@@ -46,8 +46,8 @@ export const TRANSLATIONS = {
|
|||||||
btnSyncPersistent: "💾 ซิงค์ข้อมูล Persistent ล่าสุด",
|
btnSyncPersistent: "💾 ซิงค์ข้อมูล Persistent ล่าสุด",
|
||||||
currentLineupTitle: "🎮 ทีมปัจจุบัน",
|
currentLineupTitle: "🎮 ทีมปัจจุบัน",
|
||||||
slot: "ตำแหน่งที่",
|
slot: "ตำแหน่งที่",
|
||||||
slotEmpty: "(ว่าง)",
|
slotEmpty: "ว่าง",
|
||||||
coords: "พิกัดตัวละคร (X, Y, Z)",
|
coords: "พิกัดตัวละคร XYZ",
|
||||||
// Avatars
|
// Avatars
|
||||||
searchPlaceholder: "🔍 ค้นหาชื่อหรือ ID ตัวละคร...",
|
searchPlaceholder: "🔍 ค้นหาชื่อหรือ ID ตัวละคร...",
|
||||||
filterAllRarity: "ทุกระดับดาว",
|
filterAllRarity: "ทุกระดับดาว",
|
||||||
@@ -56,7 +56,7 @@ export const TRANSLATIONS = {
|
|||||||
btnReload: "🔄 รีโหลด",
|
btnReload: "🔄 รีโหลด",
|
||||||
eidolonLabel: "Eidolon:",
|
eidolonLabel: "Eidolon:",
|
||||||
confirmMaxAll: "ยืนยันปรับตัวละครทั้งหมดให้เป็นเลเวล 80 และปลด E6 หรือไม่?",
|
confirmMaxAll: "ยืนยันปรับตัวละครทั้งหมดให้เป็นเลเวล 80 และปลด E6 หรือไม่?",
|
||||||
maxAllSuccess: "ปรับ Max ทุกตัวละครสำเร็จแล้ว!",
|
maxAllSuccess: "ปรับเต็มทุกตัวละครสำเร็จแล้ว!",
|
||||||
avatarRankSuccess: "ปรับตัวละคร {id} เป็น E{rank} สำเร็จ",
|
avatarRankSuccess: "ปรับตัวละคร {id} เป็น E{rank} สำเร็จ",
|
||||||
// Ex-Rank
|
// Ex-Rank
|
||||||
exrankTitle: "⚡ ระบบปลดล็อค Ex-Rank Skill",
|
exrankTitle: "⚡ ระบบปลดล็อค Ex-Rank Skill",
|
||||||
@@ -74,20 +74,20 @@ export const TRANSLATIONS = {
|
|||||||
customTeleportSubtitle: "ใส่ PlaneID, FloorID และ EntryID ตามต้องการ",
|
customTeleportSubtitle: "ใส่ PlaneID, FloorID และ EntryID ตามต้องการ",
|
||||||
btnExecuteWarp: "🚀 ยืนยันการวาร์ป",
|
btnExecuteWarp: "🚀 ยืนยันการวาร์ป",
|
||||||
warpBtn: "🚀 วาร์ป",
|
warpBtn: "🚀 วาร์ป",
|
||||||
warpSuccess: "วาร์ปไปยัง {label} สำเร็จ (บันทึกลง Persistent)",
|
warpSuccess: "วาร์ปไปยัง {label} สำเร็จ",
|
||||||
warpFailed: "วาร์ปไม่สำเร็จ: {err}",
|
warpFailed: "วาร์ปไม่สำเร็จ: {err}",
|
||||||
// JSON
|
// JSON
|
||||||
btnFreesr: "📄 freesr-data.json",
|
btnFreesr: "📄 freesr-data.json",
|
||||||
btnPersistent: "💾 ไฟล์เซฟ persistent",
|
btnPersistent: "💾 ไฟล์เซฟ persistent",
|
||||||
btnBeautify: "✨ จัดรูปแบบ JSON",
|
btnBeautify: "✨ จัดรูปแบบ JSON",
|
||||||
btnDownload: "📥 ดาวน์โหลด Backup",
|
btnDownload: "📥 ดาวน์โหลดสำรอง",
|
||||||
btnUpload: "📤 อัปโหลดไฟล์",
|
btnUpload: "📤 อัปโหลดไฟล์",
|
||||||
btnSaveServer: "💾 บันทึกไปยังเซิร์ฟเวอร์",
|
btnSaveServer: "💾 บันทึกไปยังเซิร์ฟเวอร์",
|
||||||
jsonTip: "Tip: สามารถแก้ไขค่าดิบแล้วกด 'บันทึกไปยังเซิร์ฟเวอร์' ได้ทันที",
|
jsonTip: "คำแนะนำ: สามารถแก้ไขค่าดิบแล้วกด 'บันทึกไปยังเซิร์ฟเวอร์' ได้ทันที",
|
||||||
jsonStatusReady: "พร้อมใช้งาน",
|
jsonStatusReady: "พร้อมใช้งาน",
|
||||||
jsonStatusOpening: "กำลังเปิด: {name}",
|
jsonStatusOpening: "กำลังเปิด: {name}",
|
||||||
jsonSaveSuccess: "บันทึกไฟล์ {name} เรียบร้อยแล้ว",
|
jsonSaveSuccess: "บันทึกไฟล์ {name} เรียบร้อยแล้ว",
|
||||||
jsonInvalid: "JSON ไม่ถูกต้อง",
|
jsonInvalid: "รูปแบบ JSON ไม่ถูกต้อง",
|
||||||
// Settings
|
// Settings
|
||||||
apiSettingsTitle: "⚙️ ตั้งค่าการเชื่อมต่อ API Gateway",
|
apiSettingsTitle: "⚙️ ตั้งค่าการเชื่อมต่อ API Gateway",
|
||||||
apiSettingsSubtitle: "ปรับเปลี่ยนพอร์ตหรือ IP ได้ตามไฟล์ server.json ของคุณ",
|
apiSettingsSubtitle: "ปรับเปลี่ยนพอร์ตหรือ IP ได้ตามไฟล์ server.json ของคุณ",
|
||||||
@@ -95,7 +95,7 @@ export const TRANSLATIONS = {
|
|||||||
lblPingInterval: "ความถี่ในการตรวจสอบสถานะ (วินาที)",
|
lblPingInterval: "ความถี่ในการตรวจสอบสถานะ (วินาที)",
|
||||||
btnSaveSettings: "💾 บันทึกการตั้งค่า",
|
btnSaveSettings: "💾 บันทึกการตั้งค่า",
|
||||||
btnResetDefault: "คืนค่าเริ่มต้น",
|
btnResetDefault: "คืนค่าเริ่มต้น",
|
||||||
hostGuideTitle: "🌐 วิธีนำขึ้นโฮสต์ GitHub Pages (ฟรี 100%)",
|
hostGuideTitle: "🌐 วิธีนำขึ้นโฮสต์ GitHub Pages",
|
||||||
hostGuideSubtitle: "สามารถเปิดใช้งานเว็บนี้บนอินเทอร์เน็ตได้โดยไม่ต้องเสียค่าโฮสต์ใดๆ",
|
hostGuideSubtitle: "สามารถเปิดใช้งานเว็บนี้บนอินเทอร์เน็ตได้โดยไม่ต้องเสียค่าโฮสต์ใดๆ",
|
||||||
settingsSaved: "บันทึกการตั้งค่าสำเร็จ!",
|
settingsSaved: "บันทึกการตั้งค่าสำเร็จ!",
|
||||||
settingsReset: "คืนค่าเริ่มต้นเรียบร้อย",
|
settingsReset: "คืนค่าเริ่มต้นเรียบร้อย",
|
||||||
|
|||||||
Reference in new issue
Block a user