Refine installer step 2/4-1 and bind server to 127.0.0.1 by default

Step 2 (싱글/멀티): replace auto-advance with a card selection plus a
"다음" button so the user can review their choice before moving on.

Step 4-1 (모드 플랫폼): replace the "설치 / 건너뛰기" buttons with two
cards — "권장 플랫폼 설치" and "기본 마인크래프트로 설치". Selection
only records intent; the actual platform install fires in 4-2 along
with mods/resourcepacks (already handled by installer:client install).

Server: default HOST to 127.0.0.1 instead of 0.0.0.0 so the startup
log prints a Ctrl+클릭으로 바로 열 수 있는 URL. Set HOST=0.0.0.0
externally when public exposure is needed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-10 19:51:17 +09:00
parent bda79e18eb
commit 7a963d0409
2 changed files with 66 additions and 24 deletions

View File

@@ -119,20 +119,36 @@ function renderStep2() {
section.innerHTML = section.innerHTML =
'<h2>2단계. 싱글 / 멀티 선택</h2>' + '<h2>2단계. 싱글 / 멀티 선택</h2>' +
'<div class="cardChoice">' + '<div class="cardChoice">' +
'<button id="single" type="button"><strong>싱글</strong><br><small>혼자 즐기는 모드. 4단계만 진행합니다.</small></button>' + '<button id="single" type="button" data-mode="single"><strong>싱글</strong><br><small>혼자 즐기는 모드. 4단계만 진행합니다.</small></button>' +
'<button id="multi" type="button"><strong>멀티</strong><br><small>친구들과 함께. 3단계 서버 설치 후 4단계를 진행합니다.</small></button>' + '<button id="multi" type="button" data-mode="multi"><strong>멀티</strong><br><small>친구들과 함께. 3단계 서버 설치 후 4단계를 진행합니다.</small></button>' +
'</div>' + '</div>' +
'<div class="actionRow"><button class="secondaryBtn" id="back">이전</button><span></span></div>' '<div class="actionRow"><button class="secondaryBtn" id="back">이전</button><button class="primaryBtn" id="next" disabled>다음</button></div>'
pageHost.appendChild(section) pageHost.appendChild(section)
section.querySelector('#single').addEventListener('click', function () { var nextBtn = section.querySelector('#next')
state.mode = 'single' var modeButtons = section.querySelectorAll('[data-mode]')
state.stepDone[2] = true
renderStep4() function applySelection(mode) {
state.mode = mode
modeButtons.forEach(function (btn) {
if (btn.getAttribute('data-mode') === mode) btn.classList.add('selected')
else btn.classList.remove('selected')
}) })
section.querySelector('#multi').addEventListener('click', function () { nextBtn.disabled = false
state.mode = 'multi' }
modeButtons.forEach(function (btn) {
btn.addEventListener('click', function () {
applySelection(btn.getAttribute('data-mode'))
})
})
if (state.mode === 'single' || state.mode === 'multi') applySelection(state.mode)
nextBtn.addEventListener('click', function () {
if (!state.mode) return
state.stepDone[2] = true state.stepDone[2] = true
renderStep3() if (state.mode === 'single') renderStep4()
else renderStep3()
}) })
section.querySelector('#back').addEventListener('click', renderStep1) section.querySelector('#back').addEventListener('click', renderStep1)
} }
@@ -413,25 +429,49 @@ function renderStep4() {
function renderSubStep41(host, pack, done) { function renderSubStep41(host, pack, done) {
var platformType = pack ? pack.pack.platform.type : 'vanilla' var platformType = pack ? pack.pack.platform.type : 'vanilla'
host.innerHTML =
'<h3>4-1. 모드 플랫폼</h3>' +
'<p class="formMessage">선택한 음악퀴즈의 플랫폼: <strong>' + platformType + '</strong></p>' +
(platformType === 'vanilla'
? '<p class="formMessage">바닐라이므로 별도 설치는 필요 없습니다.</p><button class="primaryBtn" id="next">다음</button>'
: '<div class="actionRow"><button class="primaryBtn" id="install">설치</button><button class="secondaryBtn" id="skip">건너뛰기</button></div>')
if (platformType === 'vanilla') { if (platformType === 'vanilla') {
state.client.installPlatform = false state.client.installPlatform = false
host.innerHTML =
'<h3>4-1. 모드 플랫폼</h3>' +
'<p class="formMessage">선택한 음악퀴즈의 플랫폼: <strong>vanilla</strong></p>' +
'<p class="formMessage">바닐라이므로 별도 설치는 필요 없습니다.</p>' +
'<div class="actionRow"><span></span><button class="primaryBtn" id="next">다음</button></div>'
host.querySelector('#next').addEventListener('click', done) host.querySelector('#next').addEventListener('click', done)
return return
} }
host.querySelector('#install').addEventListener('click', function () {
state.client.installPlatform = true host.innerHTML =
done() '<h3>4-1. 모드 플랫폼</h3>' +
'<p class="formMessage">선택한 음악퀴즈의 플랫폼: <strong>' + platformType + '</strong></p>' +
'<div class="cardChoice">' +
'<button type="button" data-choice="install"><strong>권장 플랫폼 설치</strong><br><small>' + platformType + ' 설치파일을 함께 다운로드해 4-2 설치 시작 시 함께 설치됩니다.</small></button>' +
'<button type="button" data-choice="skip"><strong>기본 마인크래프트로 설치</strong><br><small>플랫폼은 설치하지 않고 바닐라로 진행합니다.</small></button>' +
'</div>' +
'<div class="actionRow"><span></span><button class="primaryBtn" id="next" disabled>다음</button></div>'
var nextBtn = host.querySelector('#next')
var choiceButtons = host.querySelectorAll('[data-choice]')
function applyChoice(choice) {
state.client.installPlatform = choice === 'install'
choiceButtons.forEach(function (btn) {
if (btn.getAttribute('data-choice') === choice) btn.classList.add('selected')
else btn.classList.remove('selected')
}) })
host.querySelector('#skip').addEventListener('click', function () { nextBtn.disabled = false
state.client.installPlatform = false }
done()
choiceButtons.forEach(function (btn) {
btn.addEventListener('click', function () {
applyChoice(btn.getAttribute('data-choice'))
}) })
})
if (typeof state.client.installPlatform === 'boolean') {
applyChoice(state.client.installPlatform ? 'install' : 'skip')
}
nextBtn.addEventListener('click', done)
} }
function renderSubStep42(host, done) { function renderSubStep42(host, done) {

View File

@@ -6,7 +6,9 @@ import { indexRouter } from './routes/index'
import { opRouter } from './routes/op' import { opRouter } from './routes/op'
const PORT = Number(process.env.PORT ?? 3000) const PORT = Number(process.env.PORT ?? 3000)
const HOST = process.env.HOST ?? '0.0.0.0' // 터미널에서 Ctrl+클릭으로 바로 열 수 있도록 기본값은 127.0.0.1.
// 외부 노출이 필요할 때만 HOST=0.0.0.0 환경변수로 덮어씀.
const HOST = process.env.HOST ?? '127.0.0.1'
const app = express() const app = express()