From d87bd4111e89043411adaabe908232bc44b3134c Mon Sep 17 00:00:00 2001 From: seung6lee Date: Thu, 13 Aug 2026 21:35:38 +0900 Subject: [PATCH] refine ui --- package.json | 4 +- public/css/main.css | 980 ++++++++++++++++++++++++++++++++++++++++++- public/index.html | 1 - public/js/app.js | 781 ++++++++++++++++++---------------- public/js/i18n.js | 187 --------- src/styles/main.scss | 551 ++++++++++++------------ 6 files changed, 1671 insertions(+), 833 deletions(-) delete mode 100644 public/js/i18n.js diff --git a/package.json b/package.json index 602e8af..1b8c132 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,9 @@ "main": "server.js", "scripts": { "start": "node server.js", - "dev": "node server.js" + "dev": "node server.js", + "build:css": "sass src/styles/main.scss public/css/main.css --no-source-map --style=expanded", + "watch:css": "sass --watch src/styles/main.scss public/css/main.css --no-source-map --style=expanded" }, "dependencies": { "express": "^4.18.2", diff --git a/public/css/main.css b/public/css/main.css index 23b658a..64a2b79 100644 --- a/public/css/main.css +++ b/public/css/main.css @@ -1 +1,979 @@ -*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}html{font-size:16px;scroll-behavior:smooth}body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","Noto Sans KR",sans-serif;background:#f3f4f6;color:#111827;line-height:1.6;min-height:100vh}#app{display:flex;flex-direction:column;min-height:100vh}#app-header{background:#fff;border-bottom:1px solid #e5e7eb;padding:0 1.5rem;position:sticky;top:0;z-index:100;box-shadow:0 1px 8px rgba(0,0,0,.06)}.header-inner{max-width:760px;margin:0 auto;display:flex;align-items:center;gap:1rem;height:56px}.header-title{font-size:.85rem;font-weight:600;color:#2563eb;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.lang-btn{font-size:.78rem;padding:.3rem .8rem;border:1px solid #e5e7eb;border-radius:20px;background:rgba(0,0,0,0);cursor:pointer;color:#6b7280;transition:all .15s;white-space:nowrap}.lang-btn:hover{border-color:#2563eb;color:#2563eb}#app-main{flex:1;padding:2rem 1rem 4rem}.step-wrap{max-width:720px;margin:0 auto}.step-progress{display:flex;align-items:center;gap:0;margin-bottom:2rem}.step-progress .step-dot{display:flex;flex-direction:column;align-items:center;gap:.25rem;flex:1;position:relative}.step-progress .step-dot:not(:last-child)::after{content:"";position:absolute;top:14px;left:calc(50% + 14px);right:calc(-50% + 14px);height:2px;background:#e5e7eb;transition:background .3s}.step-progress .step-dot.done::after{background:#2563eb}.step-progress .step-dot .dot-circle{width:28px;height:28px;border-radius:50%;border:2px solid #e5e7eb;background:#fff;display:flex;align-items:center;justify-content:center;font-size:.72rem;font-weight:700;color:#6b7280;transition:all .3s;position:relative;z-index:1}.step-progress .step-dot .dot-label{font-size:.68rem;color:#6b7280;text-align:center;line-height:1.2}.step-progress .step-dot.done .dot-circle{background:#2563eb;border-color:#2563eb;color:#fff}.step-progress .step-dot.active .dot-circle{background:#2563eb;border-color:#2563eb;color:#fff;box-shadow:0 0 0 4px rgba(37,99,235,.2)}.step-progress .step-dot.active .dot-label{color:#2563eb;font-weight:600}.card{background:#fff;border-radius:12px;box-shadow:0 1px 4px rgba(0,0,0,.1),0 4px 16px rgba(0,0,0,.06);padding:2rem;margin-bottom:1.5rem}.card.card-sm{padding:1.5rem}.section-title{font-size:1.3rem;font-weight:700;margin-bottom:.4rem}.section-subtitle{color:#6b7280;font-size:.9rem;margin-bottom:1.5rem;line-height:1.5}.form-group{margin-bottom:1.2rem}.form-group label{display:block;font-size:.875rem;font-weight:600;margin-bottom:.5rem;color:#374151}.form-group input[type=text]{width:100%;padding:.6rem .9rem;border:1.5px solid #e5e7eb;border-radius:8px;font-size:.9rem;outline:none;transition:border-color .2s}.form-group input[type=text]:focus{border-color:#2563eb}.form-group input[type=text]::placeholder{color:#9ca3af}.radio-group{display:flex;flex-wrap:wrap;gap:.6rem}.radio-group label{display:flex;align-items:center;gap:.4rem;padding:.5rem 1rem;border:1.5px solid #e5e7eb;border-radius:24px;cursor:pointer;font-size:.875rem;font-weight:normal;color:#374151;transition:all .15s;margin:0;white-space:nowrap}.radio-group label input[type=radio]{display:none}.radio-group label:hover{border-color:#2563eb;color:#2563eb}.radio-group label.selected{border-color:#2563eb;background:rgba(37,99,235,.07);color:#2563eb;font-weight:600}.notice-box{background:#eff6ff;border:1px solid #bfdbfe;border-radius:8px;padding:1rem 1.2rem;margin-bottom:1.5rem}.notice-box p{font-size:.85rem;color:#1e40af;line-height:1.6}.notice-box p::before{content:"• "}.notice-box p+p{margin-top:.3rem}.btn{display:inline-flex;align-items:center;justify-content:center;gap:.4rem;padding:.65rem 1.6rem;border-radius:8px;border:none;font-size:.9rem;font-weight:600;cursor:pointer;transition:all .15s;text-decoration:none}.btn:disabled{opacity:.45;cursor:not-allowed}.btn.btn-primary{background:#2563eb;color:#fff}.btn.btn-primary:not(:disabled):hover{background:#1d4ed8;transform:translateY(-1px);box-shadow:0 4px 12px rgba(37,99,235,.35)}.btn.btn-outline{background:rgba(0,0,0,0);color:#2563eb;border:1.5px solid #2563eb}.btn.btn-outline:hover{background:rgba(37,99,235,.06)}.btn.btn-gray{background:#f3f4f6;color:#374151;border:1.5px solid #e5e7eb}.btn.btn-gray:not(:disabled):hover{background:#e5e7eb}.btn.btn-lg{padding:.85rem 2.2rem;font-size:1rem;border-radius:10px}.btn.btn-full{width:100%}.btn.btn-select{background:#2563eb;color:#fff;flex:1;font-size:1rem;padding:.9rem 1rem}.btn.btn-select:not(:disabled):hover{background:#1d4ed8}.btn.btn-reject{background:#f9fafb;color:#6b7280;border:1.5px solid #e5e7eb;flex:1;font-size:1rem;padding:.9rem 1rem}.btn.btn-reject:not(:disabled):hover{background:#f3f4f6;color:#374151}.btn.btn-selected-select{background:#16a34a;color:#fff;flex:1;padding:.9rem 1rem;font-size:1rem}.btn.btn-selected-reject{background:#dc2626;color:#fff;flex:1;padding:.9rem 1rem;font-size:1rem}.btn-row{display:flex;gap:.75rem;margin-top:1.2rem}.btn-row.center{justify-content:center}.btn-row.end{justify-content:flex-end}.progress-bar-wrap{margin-bottom:1.5rem}.progress-bar-wrap .progress-label{display:flex;justify-content:space-between;font-size:.82rem;color:#6b7280;margin-bottom:.4rem;font-weight:600}.progress-bar-wrap .progress-track{height:6px;background:#e5e7eb;border-radius:3px;overflow:hidden}.progress-bar-wrap .progress-track .progress-fill{height:100%;background:#2563eb;border-radius:3px;transition:width .4s ease}.case-card .case-header{display:flex;align-items:center;gap:.75rem;margin-bottom:1.4rem;padding-bottom:1rem;border-bottom:1px solid #e5e7eb}.case-card .case-header .team-badge{font-size:1.4rem;font-weight:800;color:#2563eb;background:rgba(37,99,235,.08);border-radius:8px;padding:.3rem .8rem}.case-card .case-header .industry-tag{font-size:.82rem;background:#f3f4f6;border:1px solid #e5e7eb;border-radius:20px;padding:.25rem .75rem;color:#6b7280;font-weight:500}.var-row{display:flex;align-items:center;gap:.75rem;margin-bottom:.85rem}.var-row .var-name{font-size:.82rem;font-weight:600;color:#374151;min-width:110px;flex-shrink:0}.var-row .var-bar-track{flex:1;height:10px;background:#f3f4f6;border-radius:5px;overflow:hidden}.var-row .var-bar-track .var-bar-fill{height:100%;border-radius:5px;transition:width .5s ease}.var-row .var-score{font-size:.85rem;font-weight:700;min-width:32px;text-align:right;color:#111827}.var-bar-fill[data-level=low]{background:linear-gradient(90deg, #EF4444, #F87171)}.var-bar-fill[data-level=mid]{background:linear-gradient(90deg, #FBBF24, #FCD34D)}.var-bar-fill[data-level=high]{background:linear-gradient(90deg, #22C55E, #4ADE80)}.judgment-section{margin-top:1.5rem;padding-top:1.2rem;border-top:1px solid #e5e7eb}.judgment-section .judgment-label{font-size:.875rem;font-weight:600;margin-bottom:.75rem;color:#374151}.confidence-section{margin-top:1.2rem;padding:1rem 1.2rem;background:#f9fafb;border-radius:8px;border:1px solid #e5e7eb}.confidence-section .confidence-label{font-size:.82rem;font-weight:600;color:#374151;margin-bottom:.6rem;display:flex;justify-content:space-between}.confidence-section .confidence-slider{width:100%;appearance:none;height:6px;border-radius:3px;background:#e5e7eb;outline:none;cursor:pointer}.confidence-section .confidence-slider::-webkit-slider-thumb{appearance:none;width:20px;height:20px;border-radius:50%;background:#2563eb;cursor:pointer;border:2px solid #fff;box-shadow:0 1px 4px rgba(0,0,0,.2)}.confidence-section .confidence-labels{display:flex;justify-content:space-between;margin-top:.4rem}.confidence-section .confidence-labels .cl{font-size:.7rem;color:#6b7280}.judgment-likert-row{display:flex;gap:.5rem;margin-top:.5rem}.judgment-likert-btn{flex:1;display:flex;flex-direction:column;align-items:center;gap:.3rem;padding:.7rem .3rem;border:2px solid #e5e7eb;border-radius:8px;background:#fff;cursor:pointer;transition:all .15s}.judgment-likert-btn .jl-num{font-size:1.1rem;font-weight:800;color:#6b7280}.judgment-likert-btn .jl-label{font-size:.65rem;color:#6b7280;text-align:center;line-height:1.3}.judgment-likert-btn:hover{border-color:#2563eb}.judgment-likert-btn.active{border-color:#2563eb;background:rgba(37,99,235,.07)}.judgment-likert-btn.active .jl-num{color:#2563eb}.judgment-likert-btn.active .jl-label{color:#2563eb}.judgment-likert-btn[data-val="1"].active{border-color:#dc2626;background:rgba(220,38,38,.07)}.judgment-likert-btn[data-val="1"].active .jl-num,.judgment-likert-btn[data-val="1"].active .jl-label{color:#dc2626}.judgment-likert-btn[data-val="2"].active{border-color:#f97316;background:rgba(249,115,22,.07)}.judgment-likert-btn[data-val="2"].active .jl-num,.judgment-likert-btn[data-val="2"].active .jl-label{color:#f97316}.judgment-likert-btn[data-val="3"].active{border-color:#d97706;background:rgba(217,119,6,.07)}.judgment-likert-btn[data-val="3"].active .jl-num,.judgment-likert-btn[data-val="3"].active .jl-label{color:#d97706}.judgment-likert-btn[data-val="4"].active{border-color:#22c55e;background:rgba(34,197,94,.07)}.judgment-likert-btn[data-val="4"].active .jl-num,.judgment-likert-btn[data-val="4"].active .jl-label{color:#22c55e}.judgment-likert-btn[data-val="5"].active{border-color:#16a34a;background:rgba(22,163,74,.07)}.judgment-likert-btn[data-val="5"].active .jl-num,.judgment-likert-btn[data-val="5"].active .jl-label{color:#16a34a}.ahp-pair-card .pair-title{font-size:.85rem;color:#6b7280;margin-bottom:1.2rem;font-weight:500}.ahp-pair-card .pair-criteria{display:flex;align-items:center;justify-content:space-between;gap:1rem;margin-bottom:1.2rem}.ahp-pair-card .pair-criteria .crit{flex:1;text-align:center;font-size:1rem;font-weight:700;color:#111827;padding:.6rem;border-radius:8px;transition:background .2s}.ahp-pair-card .pair-criteria .crit.crit-left{text-align:right}.ahp-pair-card .pair-criteria .crit.crit-right{text-align:left}.ahp-pair-card .pair-criteria .crit.crit-active-left{color:#2563eb;background:rgba(37,99,235,.07)}.ahp-pair-card .pair-criteria .crit.crit-active-right{color:#16a34a;background:rgba(22,163,74,.07)}.ahp-pair-card .pair-criteria .vs-badge{font-size:.8rem;font-weight:700;color:#6b7280;background:#f3f4f6;border:1px solid #e5e7eb;border-radius:20px;padding:.25rem .6rem;flex-shrink:0}.ahp-slider-wrap{padding:.5rem 0}.ahp-scale-labels{display:flex;justify-content:space-between;margin-bottom:.5rem;padding:0 12px}.ahp-scale-labels span{font-size:.7rem;color:#6b7280;font-weight:600;text-align:center;min-width:0}.ahp-scale-labels .sc-mid{font-weight:800;color:#374151;font-size:.75rem}.ahp-slider{width:100%;appearance:none;height:8px;border-radius:4px;background:linear-gradient(to right, rgba(37, 99, 235, 0.15) 0%, #2563EB 50%, rgba(22, 163, 74, 0.15) 100%);outline:none;cursor:pointer}.ahp-slider::-webkit-slider-thumb{appearance:none;width:24px;height:24px;border-radius:50%;background:#fff;border:3px solid #2563eb;box-shadow:0 2px 6px rgba(0,0,0,.2);cursor:pointer;transition:border-color .2s}.ahp-description{text-align:center;margin-top:1rem;padding:.7rem 1rem;background:#f0f9ff;border:1px solid #bae6fd;border-radius:8px;font-size:.875rem;font-weight:600;color:#0369a1;min-height:42px}.cr-warning{background:#fef9c3;border:1px solid #fde047;border-radius:8px;padding:1rem 1.2rem;margin-top:1rem}.cr-warning p{font-size:.875rem;color:#713f12;margin-bottom:.75rem;font-weight:600}.weight-chart{margin-top:.5rem}.weight-chart .wc-row{display:flex;align-items:center;gap:.75rem;margin-bottom:.7rem}.weight-chart .wc-row .wc-label{font-size:.82rem;font-weight:600;color:#374151;min-width:110px;flex-shrink:0}.weight-chart .wc-row .wc-track{flex:1;height:18px;background:#f3f4f6;border-radius:9px;overflow:hidden}.weight-chart .wc-row .wc-track .wc-fill{height:100%;background:linear-gradient(90deg, #2563EB, #60A5FA);border-radius:9px;transition:width .6s ease}.weight-chart .wc-row .wc-pct{font-size:.82rem;font-weight:700;color:#2563eb;min-width:38px;text-align:right}.stats-grid{display:grid;grid-template-columns:repeat(auto-fill, minmax(160px, 1fr));gap:1rem;margin-top:1rem}.stats-grid .stat-box{background:#f3f4f6;border:1px solid #e5e7eb;border-radius:8px;padding:1rem;text-align:center}.stats-grid .stat-box .stat-val{font-size:1.6rem;font-weight:800;color:#2563eb}.stats-grid .stat-box .stat-label{font-size:.75rem;color:#6b7280;margin-top:.25rem;line-height:1.3}.stats-grid .stat-box.stat-warn .stat-val{color:#d97706}.stats-grid .stat-box.stat-ok .stat-val{color:#16a34a}.comparison-table-wrap{overflow-x:auto;margin-top:1rem;border-radius:8px;border:1px solid #e5e7eb}.comparison-table-wrap table{width:100%;border-collapse:collapse;font-size:.82rem}.comparison-table-wrap table th{background:#f9fafb;padding:.6rem .8rem;text-align:center;font-weight:700;border-bottom:1px solid #e5e7eb;white-space:nowrap;color:#374151}.comparison-table-wrap table td{padding:.55rem .8rem;text-align:center;border-bottom:1px solid #e5e7eb}.comparison-table-wrap table td:first-child{font-weight:700}.comparison-table-wrap table tr:last-child td{border-bottom:none}.comparison-table-wrap table tr.mismatch td{background:#fef2f2;color:#dc2626}.comparison-table-wrap table tr.mismatch td:first-child{color:#dc2626}.badge{display:inline-block;padding:.2rem .55rem;border-radius:12px;font-size:.75rem;font-weight:700;line-height:1}.badge.badge-select{background:#dbeafe;color:#2563eb}.badge.badge-reject{background:#f3f4f6;color:#6b7280}.badge.badge-match{background:#dcfce7;color:#16a34a}.badge.badge-mismatch{background:#fee2e2;color:#dc2626}.badge.badge-neutral{background:#fef9c3;color:#d97706}.neutral-row td{background:#fafaf0;color:#d97706}.likert-group{margin-bottom:1.5rem}.likert-group .likert-q{font-size:.9rem;font-weight:600;color:#111827;margin-bottom:.75rem;line-height:1.5}.likert-group .likert-options{display:flex;gap:.5rem}.likert-group .likert-options label{flex:1;display:flex;flex-direction:column;align-items:center;gap:.3rem;cursor:pointer}.likert-group .likert-options label input[type=radio]{display:none}.likert-group .likert-options label .likert-btn{width:100%;padding:.55rem .2rem;border:2px solid #e5e7eb;border-radius:8px;text-align:center;font-size:.9rem;font-weight:700;color:#6b7280;transition:all .15s;background:#fff}.likert-group .likert-options label .likert-desc{font-size:.65rem;color:#6b7280;text-align:center;line-height:1.2}.likert-group .likert-options label input:checked+.likert-btn{border-color:#2563eb;background:rgba(37,99,235,.08);color:#2563eb}.likert-group .likert-options label:hover .likert-btn{border-color:#2563eb}.open-textarea{width:100%;padding:.8rem 1rem;border:1.5px solid #e5e7eb;border-radius:8px;font-size:.875rem;font-family:inherit;resize:vertical;min-height:100px;outline:none;transition:border-color .2s}.open-textarea:focus{border-color:#2563eb}.complete-screen{text-align:center;padding:2rem 1rem}.complete-screen .complete-icon{font-size:3.5rem;margin-bottom:1rem}.complete-screen .complete-title{font-size:1.5rem;font-weight:800;margin-bottom:.5rem}.complete-screen .complete-desc{color:#6b7280;font-size:.9rem;margin-bottom:2rem;line-height:1.7}.json-output{background:#1e293b;color:#94a3b8;border-radius:8px;padding:1.2rem;font-family:"Menlo","Monaco","Consolas",monospace;font-size:.75rem;line-height:1.6;text-align:left;overflow-x:auto;max-height:400px;overflow-y:auto;white-space:pre;margin-top:1rem}.divider{height:1px;background:#e5e7eb;margin:1.5rem 0}.text-muted{color:#6b7280;font-size:.85rem}.text-center{text-align:center}.mt-1{margin-top:.5rem}.mt-2{margin-top:1rem}.mt-3{margin-top:1.5rem}.mb-1{margin-bottom:.5rem}.highlight-text{color:#2563eb;font-weight:700}@media(max-width: 600px){#app-header{padding:0 1rem}.header-title{font-size:.78rem}.card{padding:1.2rem}.step-progress .step-dot .dot-label{display:none}.var-row .var-name{min-width:80px;font-size:.76rem}.ahp-pair-card .pair-criteria{flex-direction:column;text-align:center;gap:.5rem}.ahp-pair-card .pair-criteria .crit{text-align:center !important}.stats-grid{grid-template-columns:repeat(2, 1fr)}.comparison-table-wrap table{font-size:.75rem}.comparison-table-wrap table td,.comparison-table-wrap table th{padding:.4rem .5rem}.likert-group .likert-options label .likert-desc{display:none}.btn.btn-lg{padding:.75rem 1.5rem;font-size:.9rem}.btn-row.judgment-btns{gap:.5rem}}@media(max-width: 380px){.step-progress .step-dot .dot-circle{width:22px;height:22px;font-size:.65rem}} \ No newline at end of file +*, *::before, *::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +html { + font-size: 16px; + scroll-behavior: smooth; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans KR", sans-serif; + background: #FFFFFF; + color: #111111; + line-height: 1.6; + min-height: 100vh; + -webkit-font-smoothing: antialiased; +} + +:focus, +:focus-visible { + outline: none; +} + +#app { + display: flex; + flex-direction: column; + min-height: 100vh; +} + +#app-header { + background: #FFFFFF; + border-bottom: 1px solid #111111; + padding: 0 1.5rem; + position: sticky; + top: 0; + z-index: 100; +} + +.header-inner { + max-width: 760px; + margin: 0 auto; + display: flex; + align-items: center; + gap: 1rem; + height: 56px; +} + +.header-title { + font-size: 0.85rem; + font-weight: 700; + color: #111111; + flex: 1; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + letter-spacing: -0.01em; +} + +.header-name { + direction: rtl; + font-size: 0.85rem; + font-weight: 700; + color: #111111; + flex: 1; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + letter-spacing: -0.01em; +} + +#app-main { + flex: 1; + padding: 2rem 1rem 4rem; + display: flex; + justify-content: center; + align-items: center; +} + +.step-wrap { + width: 100%; + max-width: 720px; + margin: 0 auto; +} + +.step-progress { + display: flex; + align-items: center; + gap: 0; + margin-bottom: 2rem; +} +.step-progress .step-dot { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.25rem; + flex: 1; + position: relative; +} +.step-progress .step-dot:not(:last-child)::after { + content: ""; + position: absolute; + top: 14px; + left: calc(50% + 14px); + right: calc(-50% + 14px); + height: 1px; + background: #CCCCCC; +} +.step-progress .step-dot.done::after { + background: #111111; +} +.step-progress .step-dot .dot-circle { + width: 28px; + height: 28px; + border-radius: 50%; + border: 1px solid #CCCCCC; + background: #FFFFFF; + display: flex; + align-items: center; + justify-content: center; + font-size: 0.72rem; + font-weight: 700; + color: #6E6E6E; + position: relative; + z-index: 1; +} +.step-progress .step-dot .dot-label { + font-size: 0.68rem; + color: #6E6E6E; + text-align: center; + line-height: 1.2; +} +.step-progress .step-dot.done .dot-circle { + background: #111111; + border-color: #111111; + color: #FFFFFF; +} +.step-progress .step-dot.active .dot-circle { + background: #111111; + border-color: #111111; + color: #FFFFFF; +} +.step-progress .step-dot.active .dot-label { + color: #111111; + font-weight: 700; +} + +.card { + background: #FFFFFF; + border: 1px solid #111111; + border-radius: 0; + padding: 2rem; + margin-bottom: 1.5rem; +} +.card.card-sm { + padding: 1.5rem; +} + +.section-title { + font-size: 1.3rem; + font-weight: 800; + color: #111111; + letter-spacing: -0.02em; + margin-bottom: 0.4rem; +} + +.section-subtitle { + color: #6E6E6E; + font-size: 0.9rem; + margin-bottom: 1.5rem; + line-height: 1.5; +} + +.form-group { + margin-bottom: 1.2rem; +} +.form-group label { + display: block; + font-size: 0.875rem; + font-weight: 700; + margin-bottom: 0.5rem; + color: #111111; +} +.form-group input[type=text] { + width: 100%; + padding: 0.6rem 0.9rem; + border: 1px solid #CCCCCC; + border-radius: 0; + font-size: 0.9rem; + color: #111111; + background: #FFFFFF; +} +.form-group input[type=text]::placeholder { + color: #A3A3A3; +} + +.radio-group { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; +} +.radio-group label { + display: flex; + align-items: center; + gap: 0.4rem; + padding: 0.5rem 1rem; + border: 1px solid #CCCCCC; + border-radius: 0; + cursor: pointer; + font-size: 0.875rem; + font-weight: 400; + color: #6E6E6E; + margin: 0; + white-space: nowrap; +} +.radio-group label input[type=radio] { + display: none; +} +.radio-group label:hover { + border-color: #111111; + color: #111111; +} +.radio-group label.selected { + border-color: #111111; + background: #111111; + color: #FFFFFF; + font-weight: 700; +} + +.step-instruction { + font-size: 1.05rem; + line-height: 1.65; + font-weight: 500; + color: #111111; + margin-bottom: 1.5rem; +} + +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 0.4rem; + padding: 0.65rem 1.6rem; + border-radius: 0; + border: 1px solid transparent; + font-size: 0.9rem; + font-weight: 700; + cursor: pointer; + text-decoration: none; + background: #FFFFFF; + color: #111111; +} +.btn:disabled { + opacity: 0.35; + cursor: not-allowed; +} +.btn.btn-primary { + background: #111111; + color: #FFFFFF; + border-color: #111111; +} +.btn.btn-primary:not(:disabled):hover { + background: #FFFFFF; + color: #111111; +} +.btn.btn-outline { + background: #FFFFFF; + color: #111111; + border-color: #111111; +} +.btn.btn-outline:not(:disabled):hover { + background: #111111; + color: #FFFFFF; +} +.btn.btn-gray { + background: #FFFFFF; + color: #6E6E6E; + border-color: #CCCCCC; +} +.btn.btn-gray:not(:disabled):hover { + border-color: #111111; + color: #111111; +} +.btn.btn-lg { + padding: 0.85rem 2.2rem; + font-size: 1rem; +} +.btn.btn-full { + width: 100%; +} +.btn.btn-select, .btn.btn-reject { + flex: 1; + font-size: 1rem; + padding: 0.9rem 1rem; + background: #FFFFFF; + color: #111111; + border-color: #CCCCCC; +} +.btn.btn-select:not(:disabled):hover, .btn.btn-reject:not(:disabled):hover { + border-color: #111111; +} +.btn.btn-selected-select, .btn.btn-selected-reject { + flex: 1; + padding: 0.9rem 1rem; + font-size: 1rem; + background: #111111; + color: #FFFFFF; + border-color: #111111; +} + +.btn-row { + display: flex; + gap: 0.75rem; + margin-top: 1.2rem; +} +.btn-row.center { + justify-content: center; +} +.btn-row.end { + justify-content: flex-end; +} + +.progress-bar-wrap { + margin-bottom: 1.5rem; +} +.progress-bar-wrap .progress-label { + display: flex; + justify-content: space-between; + font-size: 0.82rem; + color: #6E6E6E; + margin-bottom: 0.4rem; + font-weight: 700; +} +.progress-bar-wrap .progress-track { + height: 4px; + background: #E5E5E5; + border-radius: 0; + overflow: hidden; +} +.progress-bar-wrap .progress-track .progress-fill { + height: 100%; + background: #111111; + transition: width 0.3s ease; +} + +.case-card .case-header { + display: flex; + align-items: center; + gap: 0.75rem; + margin-bottom: 1.4rem; + padding-bottom: 1rem; + border-bottom: 1px solid #CCCCCC; +} +.case-card .case-header .team-badge { + font-size: 1.4rem; + font-weight: 800; + color: #111111; + background: #FFFFFF; + border: 1px solid #111111; + border-radius: 0; + padding: 0.2rem 0.7rem; + line-height: 1.4; +} +.case-card .case-header .industry-tag { + font-size: 0.82rem; + background: #FFFFFF; + border: 1px solid #CCCCCC; + border-radius: 0; + padding: 0.25rem 0.7rem; + color: #6E6E6E; + font-weight: 400; +} + +.var-row { + display: flex; + align-items: center; + gap: 0.75rem; + margin-bottom: 0.85rem; +} +.var-row .var-name { + font-size: 0.82rem; + font-weight: 400; + color: #333333; + min-width: 110px; + flex-shrink: 0; +} +.var-row .var-bar-track { + flex: 1; + height: 8px; + background: #E5E5E5; + border-radius: 0; + overflow: hidden; +} +.var-row .var-bar-track .var-bar-fill { + height: 100%; + transition: width 0.4s ease; +} +.var-row .var-score { + font-size: 0.85rem; + font-weight: 700; + min-width: 32px; + text-align: right; + color: #111111; + font-variant-numeric: tabular-nums; +} + +.var-bar-fill[data-level=low] { + background: #C7C7C7; +} + +.var-bar-fill[data-level=mid] { + background: #8A8A8A; +} + +.var-bar-fill[data-level=high] { + background: #111111; +} + +.judgment-section { + margin-top: 1.5rem; + padding-top: 1.2rem; + border-top: 1px solid #CCCCCC; +} +.judgment-section .judgment-label { + font-size: 0.875rem; + font-weight: 700; + margin-bottom: 0.75rem; + color: #111111; +} + +.confidence-section { + margin-top: 1.2rem; + padding: 1rem 1.2rem; + background: #FFFFFF; + border: 1px solid #CCCCCC; + border-radius: 0; +} +.confidence-section .confidence-label { + font-size: 0.82rem; + font-weight: 700; + color: #111111; + margin-bottom: 0.6rem; + display: flex; + justify-content: space-between; +} +.confidence-section .confidence-slider { + width: 100%; + appearance: none; + height: 4px; + border-radius: 0; + background: #E5E5E5; + cursor: pointer; +} +.confidence-section .confidence-slider::-webkit-slider-thumb { + appearance: none; + width: 18px; + height: 18px; + border-radius: 50%; + background: #111111; + cursor: pointer; + border: none; +} +.confidence-section .confidence-slider::-moz-range-thumb { + width: 18px; + height: 18px; + border-radius: 50%; + background: #111111; + border: none; + cursor: pointer; +} +.confidence-section .confidence-labels { + display: flex; + justify-content: space-between; + margin-top: 0.4rem; +} +.confidence-section .confidence-labels .cl { + font-size: 0.7rem; + color: #6E6E6E; +} + +.judgment-likert-row { + display: flex; + margin-top: 0.5rem; +} + +.judgment-likert-btn { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + gap: 0.3rem; + padding: 0.7rem 0.3rem; + border: 1px solid #CCCCCC; + border-radius: 0; + background: #FFFFFF; + cursor: pointer; +} +.judgment-likert-btn + .judgment-likert-btn { + margin-left: -1px; +} +.judgment-likert-btn .jl-num { + font-size: 1.1rem; + font-weight: 800; + color: #6E6E6E; +} +.judgment-likert-btn .jl-label { + font-size: 0.65rem; + color: #6E6E6E; + text-align: center; + line-height: 1.3; +} +.judgment-likert-btn:hover { + border-color: #111111; + position: relative; + z-index: 1; +} +.judgment-likert-btn.active, .judgment-likert-btn[data-val].active { + border-color: #111111; + background: #111111; + position: relative; + z-index: 2; +} +.judgment-likert-btn.active .jl-num, .judgment-likert-btn.active .jl-label, .judgment-likert-btn[data-val].active .jl-num, .judgment-likert-btn[data-val].active .jl-label { + color: #FFFFFF; +} + +.ahp-pair-card .pair-title { + font-size: 0.85rem; + color: #6E6E6E; + margin-bottom: 1.2rem; + font-weight: 400; +} +.ahp-pair-card .pair-criteria { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + margin-bottom: 1.2rem; +} +.ahp-pair-card .pair-criteria .crit { + flex: 1; + text-align: center; + font-size: 1rem; + font-weight: 400; + color: #6E6E6E; + padding: 0.6rem; + border-radius: 0; +} +.ahp-pair-card .pair-criteria .crit.crit-left { + text-align: right; +} +.ahp-pair-card .pair-criteria .crit.crit-right { + text-align: left; +} +.ahp-pair-card .pair-criteria .crit.crit-active-left, .ahp-pair-card .pair-criteria .crit.crit-active-right { + color: #111111; + font-weight: 800; +} +.ahp-pair-card .pair-criteria .vs-badge { + font-size: 0.75rem; + font-weight: 700; + color: #6E6E6E; + background: #FFFFFF; + border: 1px solid #CCCCCC; + border-radius: 0; + padding: 0.2rem 0.5rem; + flex-shrink: 0; +} + +.ahp-slider-wrap { + padding: 0.5rem 0; +} + +.ahp-scale-labels { + display: flex; + justify-content: space-between; + margin-bottom: 0.5rem; + padding: 0 11px; +} +.ahp-scale-labels span { + font-size: 0.7rem; + color: #6E6E6E; + font-weight: 400; + text-align: center; + min-width: 0; +} +.ahp-scale-labels .sc-mid { + font-weight: 800; + color: #111111; + font-size: 0.72rem; +} + +.ahp-slider { + width: 100%; + appearance: none; + height: 4px; + border-radius: 0; + background: #E5E5E5; + cursor: pointer; +} +.ahp-slider::-webkit-slider-thumb { + appearance: none; + width: 22px; + height: 22px; + border-radius: 50%; + background: #111111; + border: none; + cursor: pointer; +} +.ahp-slider::-moz-range-thumb { + width: 22px; + height: 22px; + border-radius: 50%; + background: #111111; + border: none; + cursor: pointer; +} + +.ahp-description { + text-align: center; + margin-top: 1rem; + padding: 0.7rem 1rem; + background: #FFFFFF; + border: 1px solid #CCCCCC; + border-radius: 0; + font-size: 0.875rem; + font-weight: 700; + color: #111111; + min-height: 42px; +} + +.weight-chart { + margin-top: 0.5rem; +} +.weight-chart .wc-row { + display: flex; + align-items: center; + gap: 0.75rem; + margin-bottom: 0.7rem; +} +.weight-chart .wc-row .wc-label { + font-size: 0.82rem; + font-weight: 400; + color: #333333; + min-width: 110px; + flex-shrink: 0; +} +.weight-chart .wc-row .wc-track { + flex: 1; + height: 14px; + background: #E5E5E5; + border-radius: 0; + overflow: hidden; +} +.weight-chart .wc-row .wc-track .wc-fill { + height: 100%; + background: #111111; + transition: width 0.5s ease; +} +.weight-chart .wc-row .wc-pct { + font-size: 0.82rem; + font-weight: 700; + color: #111111; + min-width: 38px; + text-align: right; + font-variant-numeric: tabular-nums; +} + +.stats-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); + gap: 0.75rem; + margin-top: 1rem; +} +.stats-grid .stat-box { + background: #FFFFFF; + border: 1px solid #CCCCCC; + border-radius: 0; + padding: 1rem; + text-align: center; +} +.stats-grid .stat-box .stat-val { + font-size: 1.6rem; + font-weight: 800; + color: #111111; + font-variant-numeric: tabular-nums; +} +.stats-grid .stat-box .stat-label { + font-size: 0.75rem; + color: #6E6E6E; + margin-top: 0.25rem; + line-height: 1.3; +} +.stats-grid .stat-box.stat-warn { + border-color: #111111; +} +.stats-grid .stat-box.stat-ok { + border-color: #CCCCCC; +} + +.comparison-table-wrap { + overflow-x: auto; + margin-top: 1rem; + border: 1px solid #111111; + border-radius: 0; +} +.comparison-table-wrap table { + width: 100%; + border-collapse: collapse; + font-size: 0.82rem; +} +.comparison-table-wrap table th { + background: #FFFFFF; + padding: 0.6rem 0.8rem; + text-align: center; + font-weight: 700; + border-bottom: 1px solid #111111; + white-space: nowrap; + color: #111111; +} +.comparison-table-wrap table td { + padding: 0.55rem 0.8rem; + text-align: center; + border-bottom: 1px solid #CCCCCC; + color: #333333; +} +.comparison-table-wrap table td:first-child { + font-weight: 700; + color: #111111; +} +.comparison-table-wrap table tr:last-child td { + border-bottom: none; +} +.comparison-table-wrap table tr.mismatch td { + background: #F5F5F5; + color: #111111; +} +.comparison-table-wrap table tr.mismatch td:first-child { + color: #111111; +} + +.badge { + display: inline-block; + padding: 0.2rem 0.55rem; + border-radius: 0; + font-size: 0.72rem; + font-weight: 700; + line-height: 1.4; + border: 1px solid transparent; +} +.badge.badge-select { + background: #111111; + color: #FFFFFF; + border-color: #111111; +} +.badge.badge-reject { + background: #FFFFFF; + color: #6E6E6E; + border-color: #CCCCCC; +} +.badge.badge-match { + background: #FFFFFF; + color: #111111; + border-color: #111111; +} +.badge.badge-mismatch { + background: #111111; + color: #FFFFFF; + border-color: #111111; +} +.badge.badge-neutral { + background: #FFFFFF; + color: #6E6E6E; + border-color: #CCCCCC; +} + +.neutral-row td { + background: #F5F5F5; + color: #333333; +} + +.likert-group { + margin-bottom: 1.5rem; +} +.likert-group .likert-q { + font-size: 0.9rem; + font-weight: 700; + color: #111111; + margin-bottom: 0.75rem; + line-height: 1.5; +} +.likert-group .likert-options { + display: flex; +} +.likert-group .likert-options label { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + gap: 0.3rem; + cursor: pointer; +} +.likert-group .likert-options label + label .likert-btn { + margin-left: -1px; +} +.likert-group .likert-options label input[type=radio] { + display: none; +} +.likert-group .likert-options label .likert-btn { + width: 100%; + padding: 0.55rem 0.2rem; + border: 1px solid #CCCCCC; + border-radius: 0; + text-align: center; + font-size: 0.9rem; + font-weight: 700; + color: #6E6E6E; + background: #FFFFFF; +} +.likert-group .likert-options label .likert-desc { + font-size: 0.65rem; + color: #6E6E6E; + text-align: center; + line-height: 1.2; +} +.likert-group .likert-options label input:checked + .likert-btn { + border-color: #111111; + background: #111111; + color: #FFFFFF; + position: relative; + z-index: 1; +} +.likert-group .likert-options label:hover .likert-btn { + border-color: #111111; +} + +.open-textarea { + width: 100%; + padding: 0.8rem 1rem; + border: 1px solid #CCCCCC; + border-radius: 0; + font-size: 0.875rem; + font-family: inherit; + color: #111111; + background: #FFFFFF; + resize: vertical; + min-height: 100px; +} + +.complete-screen { + text-align: center; + padding: 2rem 1rem; +} +.complete-screen .complete-icon { + font-size: 2.5rem; + margin-bottom: 1rem; + filter: grayscale(1); +} +.complete-screen .complete-title { + font-size: 1.5rem; + font-weight: 800; + letter-spacing: -0.02em; + margin-bottom: 0.5rem; +} +.complete-screen .complete-desc { + color: #6E6E6E; + font-size: 0.9rem; + margin-bottom: 2rem; + line-height: 1.7; +} + +.json-output { + background: #FFFFFF; + color: #333333; + border: 1px solid #CCCCCC; + border-radius: 0; + padding: 1.2rem; + font-family: "Menlo", "Monaco", "Consolas", monospace; + font-size: 0.75rem; + line-height: 1.6; + text-align: left; + overflow-x: auto; + max-height: 400px; + overflow-y: auto; + white-space: pre; + margin-top: 1rem; +} + +.divider { + height: 1px; + background: #CCCCCC; + margin: 1.5rem 0; +} + +.text-muted { + color: #6E6E6E; + font-size: 0.85rem; +} + +.text-center { + text-align: center; +} + +.mt-1 { + margin-top: 0.5rem; +} + +.mt-2 { + margin-top: 1rem; +} + +.mt-3 { + margin-top: 1.5rem; +} + +.mb-1 { + margin-bottom: 0.5rem; +} + +.highlight-text { + color: #111111; + font-weight: 800; +} + +@media (max-width: 600px) { + #app-header { + padding: 0 1rem; + } + .header-title { + font-size: 0.78rem; + } + .card { + padding: 1.2rem; + } + .step-instruction { + font-size: 0.95rem; + margin-bottom: 1.2rem; + } + .step-progress .step-dot .dot-label { + display: none; + } + .var-row .var-name { + min-width: 80px; + font-size: 0.76rem; + } + .ahp-pair-card .pair-criteria { + flex-direction: column; + text-align: center; + gap: 0.5rem; + } + .ahp-pair-card .pair-criteria .crit { + text-align: center !important; + } + .stats-grid { + grid-template-columns: repeat(2, 1fr); + } + .comparison-table-wrap table { + font-size: 0.75rem; + } + .comparison-table-wrap table td, + .comparison-table-wrap table th { + padding: 0.4rem 0.5rem; + } + .likert-group .likert-options label .likert-desc { + display: none; + } + .btn.btn-lg { + padding: 0.75rem 1.5rem; + font-size: 0.9rem; + } +} +@media (max-width: 380px) { + .step-progress .step-dot .dot-circle { + width: 22px; + height: 22px; + font-size: 0.65rem; + } +} diff --git a/public/index.html b/public/index.html index 3526504..259c71d 100644 --- a/public/index.html +++ b/public/index.html @@ -12,7 +12,6 @@
- diff --git a/public/js/app.js b/public/js/app.js index 066b327..81d1447 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -1,95 +1,147 @@ +// ────────────────────────────────────────── +// Text (한국어 전용) +// ────────────────────────────────────────── +const APP_TITLE = '스타트업 지원 심사 판단 실험'; + +const GRADE_OPTIONS = ['1학년', '2학년', '3학년']; + +const VAR_LABELS = { + 안정성: '안정성', + 수익_가능성: '수익 가능성', + 자금_효율성: '자금 효율성', + 사업_계획_완성도: '사업 계획 완성도', + 사회적_가치: '사회적 가치', +}; + +const JUDGMENT_SCALE = ['무조건\n탈락', '탈락', '보통', '선발', '무조건\n선발']; + +const AHP_LEVELS = { 3: '약간', 5: '꽤', 7: '매우', 9: '극히' }; + +const LIKERT_QUESTIONS = [ + '모델의 판단 결과를 보고 놀라웠다.', + '내 직관보다 모델의 판단이 더 합리적이라고 생각한다.', + '이 선발 결정을 모델에게 맡겨도 괜찮다고 생각한다.', + '이번 판단은 온전히 내 의지로 한 것이라고 느낀다.', +]; + +const LIKERT_SCALE = [ + '전혀\n아니다', + '아니다', + '보통', + '그렇다', + '매우\n그렇다', +]; + +const ALERT_FILL_ALL = '모든 항목을 입력해 주세요.'; +const ALERT_SELECT_JUDGMENT = '먼저 판단을 선택해 주세요.'; +const LEAVE_WARNING = '실험이 완료되지 않았습니다. 나가시겠습니까?'; + // ────────────────────────────────────────── // State // ────────────────────────────────────────── const state = { - step: 1, - started: false, + step: 1, + started: false, - // Step 1 - meta: { grade: '', timestamp: null }, + // Step 1 + meta: { grade: '', timestamp: null }, - // Step 2 - shuffledCases: [], - caseIndex: 0, - currentJudgment: null, // 1-5 Likert - caseStartTime: null, - intuitive_judgments: [], + // Step 2 + shuffledCases: [], + caseIndex: 0, + currentJudgment: null, // 1-5 Likert + caseStartTime: null, + intuitive_judgments: [], - // Step 3 - pairIndex: 0, - sliderValues: Array(10).fill(0), - pairwiseRaw: null, - ahpResult: null, - crWarningShown: false, - crWarningPending: false, + // Step 3 + pairIndex: 0, + sliderValues: Array(10).fill(0), + pairwiseRaw: null, + ahpResult: null, - // Step 4 / 5 - comparison: [], - post_survey: { q1: null, q2: null, q3: null, q4: null, open_response: '' }, - submitted: false, - _payload: null, + // Step 4 / 5 + comparison: [], + post_survey: { q1: null, q2: null, q3: null, q4: null, open_response: '' }, + submitted: false, + _payload: null, }; // ────────────────────────────────────────── // Render entry point // ────────────────────────────────────────── function renderApp() { - renderHeader(); - renderMain(); + renderHeader(); + renderMain(); } function renderHeader() { - const el = document.getElementById('app-header'); - if (!el) return; - el.innerHTML = ` + const el = document.getElementById('app-header'); + if (!el) return; + el.innerHTML = `
- ${t('appTitle')} - + ${APP_TITLE} + 대구국제고 3학년 5반 이승준
`; } function renderMain() { - const el = document.getElementById('app-main'); - if (!el) return; - switch (state.step) { - case 1: el.innerHTML = renderStep1(); bindStep1(); break; - case 2: el.innerHTML = renderStep2(); bindStep2(); break; - case 3: el.innerHTML = renderStep3(); bindStep3(); break; - case 4: el.innerHTML = renderStep4(); break; - case 5: el.innerHTML = renderStep5(); bindStep5(); break; - case 'complete': el.innerHTML = renderComplete(); bindComplete(); break; - } + const el = document.getElementById('app-main'); + if (!el) return; + switch (state.step) { + case 1: + el.innerHTML = renderStep1(); + bindStep1(); + break; + case 2: + el.innerHTML = renderStep2(); + bindStep2(); + break; + case 3: + el.innerHTML = renderStep3(); + bindStep3(); + break; + case 4: + el.innerHTML = renderStep4(); + break; + case 5: + el.innerHTML = renderStep5(); + bindStep5(); + break; + case 'complete': + el.innerHTML = renderComplete(); + bindComplete(); + break; + } } // ────────────────────────────────────────── // STEP 1 // ────────────────────────────────────────── function renderStep1() { - const { meta } = state; - const notices = t('step1Notices').map(n => `

${n}

`).join(''); - const gradeOpts = t('step1Q1opts').map(o => ` + const { meta } = state; + const gradeOpts = GRADE_OPTIONS.map( + (o) => ` - `).join(''); + `, + ).join(''); - return ` + return `
-

${t('step1Title')}

-

${t('step1Intro')}

- -
${notices}
+

스타트업 지원 심사 판단 실험

+

당신은 스타트업 지원 프로그램의 심사위원입니다. 제기되는 질문에 따라서 성실히 답변해주시면 감사하겠습니다.
총 소요 시간은 10분 이내입니다.

- +
${gradeOpts}
- +
@@ -97,64 +149,73 @@ function renderStep1() { } function bindStep1() { - document.getElementById('rg-grade').addEventListener('click', e => { - const lbl = e.target.closest('label'); - if (!lbl) return; - const val = lbl.querySelector('input').value; - state.meta.grade = val; - document.querySelectorAll('#rg-grade label').forEach(l => - l.classList.toggle('selected', l.querySelector('input').value === val)); - }); + document.getElementById('rg-grade').addEventListener('click', (e) => { + const lbl = e.target.closest('label'); + if (!lbl) return; + const val = lbl.querySelector('input').value; + state.meta.grade = val; + document + .querySelectorAll('#rg-grade label') + .forEach((l) => + l.classList.toggle( + 'selected', + l.querySelector('input').value === val, + ), + ); + }); - document.getElementById('btn-start').addEventListener('click', () => { - if (!state.meta.grade) { alert(t('alertFillAll')); return; } - state.meta.timestamp = Date.now(); - state.shuffledCases = shuffleCases(); - state.caseIndex = 0; - state.started = true; - enableLeaveWarning(); - goStep(2); - }); + document.getElementById('btn-start').addEventListener('click', () => { + if (!state.meta.grade) { + alert(ALERT_FILL_ALL); + return; + } + state.meta.timestamp = Date.now(); + state.shuffledCases = shuffleCases(); + state.caseIndex = 0; + state.started = true; + enableLeaveWarning(); + goStep(2); + }); } // ────────────────────────────────────────── // STEP 2 // ────────────────────────────────────────── function renderStep2() { - const total = state.shuffledCases.length; - const idx = state.caseIndex; - const pct = Math.round((idx / total) * 100); - const c = state.shuffledCases[idx]; - const varNames = t('varNames'); + const total = state.shuffledCases.length; + const idx = state.caseIndex; + const pct = Math.round((idx / total) * 100); + const c = state.shuffledCases[idx]; - const varRows = VARS.map(key => { - const score = c[key]; - const level = getScoreLevel(score); - return ` + const varRows = VARS.map((key) => { + const score = c[key]; + const level = getScoreLevel(score); + return `
- ${varNames[key]} + ${VAR_LABELS[key]}
${score}
`; - }).join(''); + }).join(''); - const scale = t('judgmentScale'); - const likertBtns = scale.map((label, i) => { - const val = i + 1; - const active = state.currentJudgment === val; - return ` + const likertBtns = JUDGMENT_SCALE.map((label, i) => { + const val = i + 1; + const active = state.currentJudgment === val; + return ` `; - }).join(''); + }).join(''); - return ` + return `
+

당신은 스타트업 지원 프로그램의 심사위원입니다. 아래 팀의 정보를 보고 지원금 지급 가능성을 직관적으로 평가해주세요.

+
${idx + 1} / ${total} @@ -172,107 +233,100 @@ function renderStep2() {
${varRows}
-

${t('judgmentLabel')}

+

이 팀의 지원금 지급 가능성을 평가하세요

${likertBtns}
- +
- -

${t('step2Instruction')}

`; } function bindStep2() { - const caseStart = Date.now(); + const caseStart = Date.now(); - document.getElementById('jl-row').addEventListener('click', e => { - const btn = e.target.closest('.judgment-likert-btn'); - if (!btn) return; - const val = parseInt(btn.dataset.val); - state.currentJudgment = val; - document.querySelectorAll('.judgment-likert-btn').forEach(b => - b.classList.toggle('active', parseInt(b.dataset.val) === val)); - document.getElementById('btn-case-next').disabled = false; - }); + document.getElementById('jl-row').addEventListener('click', (e) => { + const btn = e.target.closest('.judgment-likert-btn'); + if (!btn) return; + const val = parseInt(btn.dataset.val); + state.currentJudgment = val; + document + .querySelectorAll('.judgment-likert-btn') + .forEach((b) => + b.classList.toggle('active', parseInt(b.dataset.val) === val), + ); + document.getElementById('btn-case-next').disabled = false; + }); - document.getElementById('btn-case-next').addEventListener('click', () => { - if (state.currentJudgment === null) { alert(t('alertSelectJudgment')); return; } - const elapsed = Date.now() - caseStart; - const c = state.shuffledCases[state.caseIndex]; - state.intuitive_judgments.push({ - case_id: c.id, - presented_order: state.caseIndex + 1, - judgment: state.currentJudgment, // 1-5 - response_time_ms: elapsed, + document.getElementById('btn-case-next').addEventListener('click', () => { + if (state.currentJudgment === null) { + alert(ALERT_SELECT_JUDGMENT); + return; + } + const elapsed = Date.now() - caseStart; + const c = state.shuffledCases[state.caseIndex]; + state.intuitive_judgments.push({ + case_id: c.id, + presented_order: state.caseIndex + 1, + judgment: state.currentJudgment, // 1-5 + response_time_ms: elapsed, + }); + state.caseIndex++; + state.currentJudgment = null; + if (state.caseIndex >= state.shuffledCases.length) { + goStep(3); + } else { + renderMain(); + } }); - state.caseIndex++; - state.currentJudgment = null; - if (state.caseIndex >= state.shuffledCases.length) { - goStep(3); - } else { - renderMain(); - } - }); } // ────────────────────────────────────────── // STEP 3 // ────────────────────────────────────────── -const AHP_PAIR_LABELS_KO = [ - ['안정성', '수익 가능성'], - ['안정성', '자금 효율성'], - ['안정성', '사업 계획 완성도'], - ['안정성', '사회적 가치'], - ['수익 가능성', '자금 효율성'], - ['수익 가능성', '사업 계획 완성도'], - ['수익 가능성', '사회적 가치'], - ['자금 효율성', '사업 계획 완성도'], - ['자금 효율성', '사회적 가치'], - ['사업 계획 완성도', '사회적 가치'], -]; - -const AHP_PAIR_LABELS_EN = [ - ['Stability', 'Revenue Potential'], - ['Stability', 'Capital Efficiency'], - ['Stability', 'Business Plan Quality'], - ['Stability', 'Social Value'], - ['Revenue Potential', 'Capital Efficiency'], - ['Revenue Potential', 'Business Plan Quality'], - ['Revenue Potential', 'Social Value'], - ['Capital Efficiency', 'Business Plan Quality'], - ['Capital Efficiency', 'Social Value'], - ['Business Plan Quality', 'Social Value'], +const AHP_PAIR_LABELS = [ + ['안정성', '수익 가능성'], + ['안정성', '자금 효율성'], + ['안정성', '사업 계획 완성도'], + ['안정성', '사회적 가치'], + ['수익 가능성', '자금 효율성'], + ['수익 가능성', '사업 계획 완성도'], + ['수익 가능성', '사회적 가치'], + ['자금 효율성', '사업 계획 완성도'], + ['자금 효율성', '사회적 가치'], + ['사업 계획 완성도', '사회적 가치'], ]; function getPairLabels(idx) { - return currentLang === 'en' ? AHP_PAIR_LABELS_EN[idx] : AHP_PAIR_LABELS_KO[idx]; + return AHP_PAIR_LABELS[idx]; } function getAHPDescription(sliderPos, leftLabel, rightLabel) { - if (sliderPos === 0) return t('ahpEqual'); - const ahpVal = Math.abs(sliderToStored(sliderPos)); // 3,5,7,9 - const level = t('ahpLevels')[ahpVal] || String(ahpVal); - if (sliderPos < 0) return t('ahpLeftMore', leftLabel, level); - return t('ahpRightMore', rightLabel, level); + if (sliderPos === 0) return '두 기준이 동등하게 중요합니다'; + const ahpVal = Math.abs(sliderToStored(sliderPos)); // 3,5,7,9 + const level = AHP_LEVELS[ahpVal] || String(ahpVal); + const crit = sliderPos < 0 ? leftLabel : rightLabel; + return `${crit}이(가) ${level} 더 중요합니다`; } function renderStep3() { - const idx = state.pairIndex; - const total = AHP_PAIRS.length; - const pct = Math.round((idx / total) * 100); - const [left, right] = getPairLabels(idx); - const sliderVal = state.sliderValues[idx]; - const desc = getAHPDescription(sliderVal, left, right); + const idx = state.pairIndex; + const total = AHP_PAIRS.length; + const pct = Math.round((idx / total) * 100); + const [left, right] = getPairLabels(idx); + const sliderVal = state.sliderValues[idx]; + const desc = getAHPDescription(sliderVal, left, right); - const leftActive = sliderVal < 0 ? 'crit-active-left' : ''; - const rightActive = sliderVal > 0 ? 'crit-active-right' : ''; + const leftActive = sliderVal < 0 ? 'crit-active-left' : ''; + const rightActive = sliderVal > 0 ? 'crit-active-right' : ''; - return ` + return `
+

제시되는 2개의 평가 기준 중 어느 것이 더 중요한지 비교해주세요. 왼쪽이 더 중요하면 슬라이더를 왼쪽으로, 오른쪽이 더 중요하면 오른쪽으로 움직이세요.

+
${idx + 1} / ${total} @@ -283,8 +337,7 @@ function renderStep3() {
-

${t('step3Title')}

-

${t('step3Instruction')}

+

AHP 쌍대비교

@@ -306,153 +359,135 @@ function renderStep3() {
${desc}
- ${state.crWarningPending ? ` -
-

${t('crWarning')}

-
- - -
-
- ` : ` -
- -
- `} +
+ +
`; } function bindStep3() { - const idx = state.pairIndex; - const [left, right] = getPairLabels(idx); + const idx = state.pairIndex; + const [left, right] = getPairLabels(idx); - const slider = document.getElementById('ahp-slider'); - if (slider) { - slider.addEventListener('input', e => { - const val = parseInt(e.target.value); - state.sliderValues[idx] = val; - document.getElementById('ahp-desc').textContent = getAHPDescription(val, left, right); - document.getElementById('crit-left').className = `crit crit-left ${val < 0 ? 'crit-active-left' : ''}`; - document.getElementById('crit-right').className = `crit crit-right ${val > 0 ? 'crit-active-right' : ''}`; - }); - } + const slider = document.getElementById('ahp-slider'); + if (slider) { + slider.addEventListener('input', (e) => { + const val = parseInt(e.target.value); + state.sliderValues[idx] = val; + document.getElementById('ahp-desc').textContent = getAHPDescription( + val, + left, + right, + ); + document.getElementById('crit-left').className = + `crit crit-left ${val < 0 ? 'crit-active-left' : ''}`; + document.getElementById('crit-right').className = + `crit crit-right ${val > 0 ? 'crit-active-right' : ''}`; + }); + } - const nextBtn = document.getElementById('btn-pair-next'); - if (nextBtn) nextBtn.addEventListener('click', advancePair); - - const redoBtn = document.getElementById('btn-redo'); - if (redoBtn) redoBtn.addEventListener('click', () => { - state.crWarningPending = false; - state.pairIndex = 0; - state.sliderValues = Array(10).fill(0); - renderMain(); - }); - - const contBtn = document.getElementById('btn-cr-continue'); - if (contBtn) contBtn.addEventListener('click', () => { - state.crWarningPending = false; - finalizeAHP(); - goStep(4); - }); + const nextBtn = document.getElementById('btn-pair-next'); + if (nextBtn) nextBtn.addEventListener('click', advancePair); } function advancePair() { - const total = AHP_PAIRS.length; - if (state.pairIndex < total - 1) { - state.pairIndex++; - renderMain(); - } else { - const raw = state.sliderValues.map(s => sliderToStored(s)); - state.pairwiseRaw = raw; - const result = runAHP(raw); - state.ahpResult = result; - - if (result.CR > 0.10) { - state.crWarningPending = true; - state.crWarningShown = true; - renderMain(); + const total = AHP_PAIRS.length; + if (state.pairIndex < total - 1) { + state.pairIndex++; + renderMain(); } else { - finalizeAHP(); - goStep(4); + const raw = state.sliderValues.map((s) => sliderToStored(s)); + state.pairwiseRaw = raw; + // CR은 분석용으로 계속 기록하되, 모순이 있어도 재입력 없이 그대로 진행 + state.ahpResult = runAHP(raw); + finalizeAHP(); + goStep(4); } - } } // Convert Likert 1-5 to judgment category for comparison with model function likertToJudgment(likert) { - if (likert >= 4) return 'select'; - if (likert <= 2) return 'reject'; - return 'neutral'; // 3 + if (likert >= 4) return 'select'; + if (likert <= 2) return 'reject'; + return 'neutral'; // 3 } function finalizeAHP() { - const weights = state.ahpResult.weights; - state.comparison = state.shuffledCases.map((c, i) => { - const modelScore = calcModelScore(c, weights); - const modelJudgment = modelScore >= 50 ? 'select' : 'reject'; - const rawLikert = state.intuitive_judgments[i]?.judgment ?? null; - const intuitiveCategory = rawLikert !== null ? likertToJudgment(rawLikert) : null; - const match = intuitiveCategory === 'neutral' ? null - : intuitiveCategory === modelJudgment ? true : false; - return { - case_id: c.id, - team: c.teamLetter, - type: c.type, - model_score: Math.round(modelScore * 10) / 10, - model_judgment: modelJudgment, - intuitive_likert: rawLikert, - intuitive_category: intuitiveCategory, - match, - }; - }); + const weights = state.ahpResult.weights; + state.comparison = state.shuffledCases.map((c, i) => { + const modelScore = calcModelScore(c, weights); + const modelJudgment = modelScore >= 50 ? 'select' : 'reject'; + const rawLikert = state.intuitive_judgments[i]?.judgment ?? null; + const intuitiveCategory = + rawLikert !== null ? likertToJudgment(rawLikert) : null; + const match = + intuitiveCategory === 'neutral' + ? null + : intuitiveCategory === modelJudgment + ? true + : false; + return { + case_id: c.id, + team: c.teamLetter, + type: c.type, + model_score: Math.round(modelScore * 10) / 10, + model_judgment: modelJudgment, + intuitive_likert: rawLikert, + intuitive_category: intuitiveCategory, + match, + }; + }); } // ────────────────────────────────────────── // STEP 4 // ────────────────────────────────────────── function renderStep4() { - const weights = state.ahpResult.weights; - const varNames = t('varNames'); + const weights = state.ahpResult.weights; - const weightRows = VARS.map((key, i) => { - const pct = Math.round(weights[i] * 100); - return ` + const weightRows = VARS.map((key, i) => { + const pct = Math.round(weights[i] * 100); + return `
- ${varNames[key]} + ${VAR_LABELS[key]}
${pct}%
`; - }).join(''); + }).join(''); - const comp = state.comparison; - // Exclude neutral (match===null) from mismatch counts - const decidable = comp.filter(c => c.match !== null); - const totalMismatch = decidable.filter(c => !c.match).length; - const baseline = decidable.filter(c => c.type === 'baseline'); - const spike = decidable.filter(c => c.type === 'spike'); - const conflict = decidable.filter(c => c.type === 'conflict'); - const { CR } = state.ahpResult; + const comp = state.comparison; + // Exclude neutral (match===null) from mismatch counts + const decidable = comp.filter((c) => c.match !== null); + const totalMismatch = decidable.filter((c) => !c.match).length; + const baseline = decidable.filter((c) => c.type === 'baseline'); + const spike = decidable.filter((c) => c.type === 'spike'); + const conflict = decidable.filter((c) => c.type === 'conflict'); + const { CR } = state.ahpResult; - const rows = comp.map(c => { - const mis = c.match === false; - const neu = c.match === null; - const mLabel = c.model_judgment === 'select' - ? `${t('select')}` - : `${t('reject')}`; - const iLabel = c.intuitive_category === 'select' - ? `${c.intuitive_likert}` - : c.intuitive_category === 'reject' - ? `${c.intuitive_likert}` - : `${c.intuitive_likert}`; - const matchBadge = c.match === true - ? `${t('colYes')}` - : c.match === false - ? `${t('colNo')}` - : `${t('colNeutral')}`; - return ` + const rows = comp + .map((c) => { + const mis = c.match === false; + const neu = c.match === null; + const mLabel = + c.model_judgment === 'select' + ? `선발` + : `탈락`; + const iLabel = + c.intuitive_category === 'select' + ? `${c.intuitive_likert}` + : c.intuitive_category === 'reject' + ? `${c.intuitive_likert}` + : `${c.intuitive_likert}`; + const matchBadge = + c.match === true + ? `` + : c.match === false + ? `` + : ``; + return ` Team ${c.team} ${c.model_score} @@ -461,54 +496,55 @@ function renderStep4() { ${matchBadge} `; - }).join(''); + }) + .join(''); - return ` + return `
-

${t('step4Title')}

-

${t('weightsTitle')}

+

결과 비교

+

나의 판단 기준 (AHP 가중치)

${weightRows}
-

${t('statsTitle')}

+

핵심 통계

${totalMismatch}/${decidable.length}
-
${t('statTotal')}
+
전체 불일치율
-
${baseline.filter(c=>!c.match).length}/${baseline.length}
-
${t('statBaseline')}
+
${baseline.filter((c) => !c.match).length}/${baseline.length}
+
베이스라인
불일치율
-
${spike.filter(c=>!c.match).length}/${spike.length}
-
${t('statSpike')}
+
${spike.filter((c) => !c.match).length}/${spike.length}
+
스파이크 사례
불일치율
-
${conflict.filter(c=>!c.match).length}/${conflict.length}
-
${t('statConflict')}
+
${conflict.filter((c) => !c.match).length}/${conflict.length}
+
충돌 사례
불일치율
-
+
${CR.toFixed(3)}
-
${t('statCR')}
+
CR (일관성 비율)
-

${t('totalMismatch', totalMismatch, decidable.length)}

+

총 불일치: ${totalMismatch} / ${decidable.length}건 (보통 제외)

-

${t('tableTitle')}

+

사례별 모델 vs 직관 비교

- - - - - + + + + + ${rows} @@ -517,7 +553,7 @@ function renderStep4() {
- +
`; @@ -527,41 +563,38 @@ function renderStep4() { // STEP 5 // ────────────────────────────────────────── function renderStep5() { - const qs = t('likertQ'); - const scale = t('likertScale'); - - const groups = qs.map((q, qi) => { - const opts = scale.map((label, si) => { - const val = si + 1; - const checked = state.post_survey[`q${qi+1}`] === val; - return ` + const groups = LIKERT_QUESTIONS.map((q, qi) => { + const opts = LIKERT_SCALE.map((label, si) => { + const val = si + 1; + const checked = state.post_survey[`q${qi + 1}`] === val; + return ` `; - }).join(''); - return ` + }).join(''); + return `
-

${qi+1}. ${q}

+

${qi + 1}. ${q}

${opts}
`; - }).join(''); + }).join(''); - return ` + return `
-

${t('step5Title')}

-

${t('step5Desc')}

+

사후 설문

+

마지막으로 간단한 설문에 답해주세요.

${groups}
- - + +
- +
@@ -569,68 +602,73 @@ function renderStep5() { } function bindStep5() { - document.querySelectorAll('.likert-options input[type="radio"]').forEach(inp => { - inp.addEventListener('change', e => { - state.post_survey[e.target.name] = parseInt(e.target.value); + document + .querySelectorAll('.likert-options input[type="radio"]') + .forEach((inp) => { + inp.addEventListener('change', (e) => { + state.post_survey[e.target.name] = parseInt(e.target.value); + }); + }); + document.getElementById('open-resp').addEventListener('input', (e) => { + state.post_survey.open_response = e.target.value; + }); + document.getElementById('btn-submit').addEventListener('click', () => { + const { q1, q2, q3, q4 } = state.post_survey; + if (!q1 || !q2 || !q3 || !q4) { + alert(ALERT_FILL_ALL); + return; + } + submitData(); }); - }); - document.getElementById('open-resp').addEventListener('input', e => { - state.post_survey.open_response = e.target.value; - }); - document.getElementById('btn-submit').addEventListener('click', () => { - const { q1, q2, q3, q4 } = state.post_survey; - if (!q1 || !q2 || !q3 || !q4) { alert(t('alertFillAll')); return; } - submitData(); - }); } // ────────────────────────────────────────── // Submit & Complete // ────────────────────────────────────────── function buildPayload() { - return { - meta: { timestamp: state.meta.timestamp, grade: state.meta.grade }, - intuitive_judgments: state.intuitive_judgments, - ahp: { - pairwise_raw: state.pairwiseRaw, - weights: state.ahpResult.weights, - lambda_max: state.ahpResult.lambdaMax, - CI: state.ahpResult.CI, - CR: state.ahpResult.CR, - cr_warning_shown: state.crWarningShown, - }, - comparison: state.comparison, - post_survey: state.post_survey, - }; + return { + meta: { timestamp: state.meta.timestamp, grade: state.meta.grade }, + intuitive_judgments: state.intuitive_judgments, + ahp: { + pairwise_raw: state.pairwiseRaw, + weights: state.ahpResult.weights, + lambda_max: state.ahpResult.lambdaMax, + CI: state.ahpResult.CI, + CR: state.ahpResult.CR, + cr_warning_shown: false, // CR 경고 UI 제거 — CSV 컬럼 호환용으로 유지 + }, + comparison: state.comparison, + post_survey: state.post_survey, + }; } async function submitData() { - if (state.submitted) return; - state.submitted = true; - disableLeaveWarning(); - const payload = buildPayload(); - try { - await fetch('/api/submit', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(payload), - }); - } catch (e) { - console.warn('Server submit failed:', e); - } - state._payload = payload; - state.step = 'complete'; - renderApp(); + if (state.submitted) return; + state.submitted = true; + disableLeaveWarning(); + const payload = buildPayload(); + try { + await fetch('/api/submit', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload), + }); + } catch (e) { + console.warn('Server submit failed:', e); + } + state._payload = payload; + state.step = 'complete'; + renderApp(); } function renderComplete() { - return ` + return `
-
🎉
-

${t('completeTitle')}

-

${t('completeDesc')}

+

참여해 주셔서 감사합니다!

+

실험이 완료되었습니다. +소중한 시간을 내어 참여해 주셔서 진심으로 감사드립니다.

@@ -643,27 +681,32 @@ function bindComplete() {} // Navigation & utilities // ────────────────────────────────────────── function goStep(n) { - state.step = n; - renderApp(); - window.scrollTo({ top: 0, behavior: 'smooth' }); + state.step = n; + renderApp(); + window.scrollTo({ top: 0, behavior: 'smooth' }); } function enableLeaveWarning() { - window._leaveHandler = e => { e.preventDefault(); e.returnValue = t('leaveWarning'); }; - window.addEventListener('beforeunload', window._leaveHandler); + window._leaveHandler = (e) => { + e.preventDefault(); + e.returnValue = LEAVE_WARNING; + }; + window.addEventListener('beforeunload', window._leaveHandler); } function disableLeaveWarning() { - if (window._leaveHandler) { - window.removeEventListener('beforeunload', window._leaveHandler); - window._leaveHandler = null; - } + if (window._leaveHandler) { + window.removeEventListener('beforeunload', window._leaveHandler); + window._leaveHandler = null; + } } function escHtml(str) { - return String(str) - .replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); + return String(str) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"'); } document.addEventListener('DOMContentLoaded', renderApp); diff --git a/public/js/i18n.js b/public/js/i18n.js deleted file mode 100644 index 8e01621..0000000 --- a/public/js/i18n.js +++ /dev/null @@ -1,187 +0,0 @@ -const I18N = { - ko: { - appTitle: '스타트업 지원 심사 판단 실험', - langBtn: 'EN', - - stepLabels: ['안내', '직관 판단', 'AHP 비교', '결과', '사후 설문'], - - // Step 1 - step1Title: '스타트업 지원 심사 판단 실험', - step1Intro: '당신은 스타트업 지원 프로그램의 심사위원입니다. 이 실험은 두 가지 판단 방식 — 인간의 직관과 AI 기반 의사결정 모델(AHP) — 을 비교 분석합니다. 15개의 스타트업 사례를 보고 지원금 지급 가능성을 평가한 뒤, 본인만의 평가 기준 가중치를 설정하게 됩니다.', - step1Notices: [ - '정답은 없습니다. 느낌과 직관대로 판단해 주세요.', - '총 소요 시간은 약 20~25분입니다.', - '중간에 페이지를 나가면 데이터가 저장되지 않습니다.', - ], - step1Q1: '학년', - step1Q1opts: ['1학년', '2학년', '3학년'], - startBtn: '실험 시작', - - // Step 2 - step2Instruction: '당신은 스타트업 지원 프로그램의 심사위원입니다. 아래 팀의 정보를 보고 지원금 지급 가능성을 직관적으로 평가하세요. 계산하지 말고 느낌대로 판단하세요.', - varNames: { - '안정성': '안정성', - '수익_가능성': '수익 가능성', - '자금_효율성': '자금 효율성', - '사업_계획_완성도': '사업 계획 완성도', - '사회적_가치': '사회적 가치', - }, - judgmentLabel: '이 팀의 지원금 지급 가능성을 평가하세요', - judgmentScale: ['매우\n탈락', '탈락', '보통', '선발', '매우\n선발'], - btnNext: '다음', - - // Step 3 - step3Title: 'AHP 쌍대비교', - step3Instruction: '5개의 평가 기준 중 어느 것이 더 중요한지 비교해주세요. 왼쪽이 더 중요하면 슬라이더를 왼쪽으로, 오른쪽이 더 중요하면 오른쪽으로 움직이세요.', - ahpEqual: '두 기준이 동등하게 중요합니다', - ahpLeftMore: (crit, level) => `${crit}이(가) ${level} 더 중요합니다`, - ahpRightMore: (crit, level) => `${crit}이(가) ${level} 더 중요합니다`, - ahpLevels: { 3: '약간', 5: '꽤', 7: '매우', 9: '극히' }, - crWarning: '판단에 약간의 모순이 감지되었습니다(CR > 0.10). 다시 입력하시겠습니까?', - btnRedo: '다시 입력', - btnContinue: '그대로 진행', - - // Step 4 - step4Title: '결과 비교', - weightsTitle: '나의 판단 기준 (AHP 가중치)', - tableTitle: '사례별 모델 vs 직관 비교', - colTeam: '팀', - colModelScore: '모델 점수', - colModelJudge: '모델 판단', - colIntuition: '직관 (1-5)', - colMatch: '일치', - colYes: '✓', - colNo: '✗', - colNeutral: '△', - totalMismatch: (n, t) => `총 불일치: ${n} / ${t}건 (보통 제외)`, - statsTitle: '핵심 통계', - statTotal: '전체 불일치율', - statBaseline: '베이스라인\n불일치율', - statSpike: '스파이크 사례\n불일치율', - statConflict: '충돌 사례\n불일치율', - statCR: 'CR (일관성 비율)', - select: '선발', - reject: '탈락', - neutral: '보통', - btnGoSurvey: '사후 설문으로 이동', - - // Step 5 - step5Title: '사후 설문', - step5Desc: '마지막으로 간단한 설문에 답해주세요.', - likertQ: [ - '모델의 판단 결과를 보고 놀라웠다.', - '내 직관보다 모델의 판단이 더 합리적이라고 생각한다.', - '이 선발 결정을 모델에게 맡겨도 괜찮다고 생각한다.', - '이번 판단은 온전히 내 의지로 한 것이라고 느낀다.', - ], - likertScale: ['전혀\n아니다', '아니다', '보통', '그렇다', '매우\n그렇다'], - openQ: '모델 판단과 직관이 달랐을 때 어떤 생각이 들었나요? (선택)', - openPh: '자유롭게 작성해 주세요...', - btnSubmit: '제출하기', - - // Complete - completeTitle: '참여해 주셔서 감사합니다!', - completeDesc: '실험이 완료되었습니다.\n소중한 시간을 내어 참여해 주셔서 진심으로 감사드립니다.\n응답 데이터는 안전하게 저장되었습니다.', - - alertFillAll: '모든 항목을 입력해 주세요.', - alertSelectJudgment: '먼저 판단을 선택해 주세요.', - leaveWarning: '실험이 완료되지 않았습니다. 나가시겠습니까?', - }, - - en: { - appTitle: 'Startup Funding Evaluation Experiment', - langBtn: '한국어', - - stepLabels: ['Intro', 'Intuition', 'AHP', 'Results', 'Survey'], - - step1Title: 'Startup Funding Evaluation Experiment', - step1Intro: 'You are a judge in a startup support program. This experiment compares two decision-making approaches — human intuition and an AI-based model (AHP). You will evaluate 15 startup cases and then set your own weighting criteria.', - step1Notices: [ - 'There are no right or wrong answers. Judge by your gut feeling.', - 'The experiment takes approximately 20–25 minutes.', - 'Leaving the page mid-experiment will result in lost data.', - ], - step1Q1: 'Grade', - step1Q1opts: ['Grade 1', 'Grade 2', 'Grade 3'], - startBtn: 'Start Experiment', - - step2Instruction: 'You are a judge in a startup support program. Based on the team\'s information below, intuitively evaluate their funding potential. Do not calculate — go with your gut.', - varNames: { - '안정성': 'Stability', - '수익_가능성': 'Revenue Potential', - '자금_효율성': 'Capital Efficiency', - '사업_계획_완성도': 'Business Plan Quality', - '사회적_가치': 'Social Value', - }, - judgmentLabel: 'Evaluate this team\'s funding potential', - judgmentScale: ['Strongly\nReject', 'Reject', 'Neutral', 'Fund', 'Strongly\nFund'], - btnNext: 'Next', - - step3Title: 'AHP Pairwise Comparison', - step3Instruction: 'Compare which of the 5 criteria matters more. Move the slider left if the left criterion is more important, right if the right one is.', - ahpEqual: 'Both criteria are equally important', - ahpLeftMore: (crit, level) => `${crit} is ${level} more important`, - ahpRightMore: (crit, level) => `${crit} is ${level} more important`, - ahpLevels: { 3: 'somewhat', 5: 'considerably', 7: 'much', 9: 'extremely' }, - crWarning: 'Some inconsistency detected (CR > 0.10). Would you like to redo the comparison?', - btnRedo: 'Redo', - btnContinue: 'Continue anyway', - - step4Title: 'Results Comparison', - weightsTitle: 'Your Judgment Criteria (AHP Weights)', - tableTitle: 'Model vs. Intuition per Case', - colTeam: 'Team', - colModelScore: 'Model Score', - colModelJudge: 'Model', - colIntuition: 'Intuition (1-5)', - colMatch: 'Match', - colYes: '✓', - colNo: '✗', - colNeutral: '△', - totalMismatch: (n, tot) => `Total mismatches: ${n} / ${tot} (neutral excluded)`, - statsTitle: 'Key Statistics', - statTotal: 'Overall Mismatch Rate', - statBaseline: 'Baseline\nMismatch Rate', - statSpike: 'Spike Case\nMismatch Rate', - statConflict: 'Conflict Case\nMismatch Rate', - statCR: 'CR (Consistency Ratio)', - select: 'Fund', - reject: 'Reject', - neutral: 'Neutral', - btnGoSurvey: 'Go to Post-Survey', - - step5Title: 'Post-Survey', - step5Desc: 'Please answer a few final questions.', - likertQ: [ - 'I was surprised by the model\'s judgment.', - 'I think the model\'s judgment is more rational than my intuition.', - 'I would be comfortable letting the model make these selection decisions.', - 'I feel the decisions I made were entirely my own.', - ], - likertScale: ['Strongly\nDisagree', 'Disagree', 'Neutral', 'Agree', 'Strongly\nAgree'], - openQ: 'What did you think when your intuition differed from the model? (Optional)', - openPh: 'Write freely...', - btnSubmit: 'Submit', - - completeTitle: 'Thank you for participating!', - completeDesc: 'The experiment is complete.\nThank you sincerely for your time and participation.\nYour responses have been saved securely.', - - alertFillAll: 'Please fill in all fields.', - alertSelectJudgment: 'Please make a judgment first.', - leaveWarning: 'The experiment is not complete. Are you sure you want to leave?', - }, -}; - -let currentLang = 'ko'; - -function t(key, ...args) { - const val = I18N[currentLang][key]; - if (typeof val === 'function') return val(...args); - return val ?? key; -} - -function switchLang() { - currentLang = currentLang === 'ko' ? 'en' : 'ko'; - document.documentElement.lang = currentLang; - if (typeof renderApp === 'function') renderApp(); -} diff --git a/src/styles/main.scss b/src/styles/main.scss index 8777a23..2d29614 100644 --- a/src/styles/main.scss +++ b/src/styles/main.scss @@ -1,21 +1,16 @@ // ────────────────────────────────────────── -// Variables +// Variables — monochrome only // ────────────────────────────────────────── -$primary: #2563EB; -$primary-dk: #1D4ED8; -$success: #16A34A; -$danger: #DC2626; -$warning: #D97706; -$muted: #6B7280; -$border: #E5E7EB; -$bg: #F3F4F6; -$card-bg: #FFFFFF; -$text: #111827; -$text-sm: #374151; +$ink: #111111; // 본문 / 강조 (검정) +$ink-soft: #333333; +$muted: #6E6E6E; // 보조 텍스트 (회색) +$line: #111111; // 구조 구분선 (검정 border) +$line-soft: #CCCCCC; // 입력/바/내부 구분선 (회색) +$track: #E5E5E5; // 바 트랙 (회색) +$wash: #F5F5F5; // 유일하게 허용하는 옅은 회색 면 +$white: #FFFFFF; -$radius: 12px; -$radius-sm: 8px; -$shadow: 0 1px 4px rgba(0,0,0,.10), 0 4px 16px rgba(0,0,0,.06); +$radius: 0; // ────────────────────────────────────────── // Reset & Base @@ -26,25 +21,29 @@ html { font-size: 16px; scroll-behavior: smooth; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans KR', sans-serif; - background: $bg; - color: $text; + background: $white; + color: $ink; line-height: 1.6; min-height: 100vh; + -webkit-font-smoothing: antialiased; } +// 클릭 요소 focus 시 별도 디자인 없음 +:focus, +:focus-visible { outline: none; } + // ────────────────────────────────────────── // Layout // ────────────────────────────────────────── #app { display: flex; flex-direction: column; min-height: 100vh; } #app-header { - background: $card-bg; - border-bottom: 1px solid $border; + background: $white; + border-bottom: 1px solid $line; padding: 0 1.5rem; position: sticky; top: 0; z-index: 100; - box-shadow: 0 1px 8px rgba(0,0,0,.06); } .header-inner { @@ -58,33 +57,40 @@ body { .header-title { font-size: .85rem; - font-weight: 600; - color: $primary; + font-weight: 700; + color: $ink; flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + letter-spacing: -.01em; } -.lang-btn { - font-size: .78rem; - padding: .3rem .8rem; - border: 1px solid $border; - border-radius: 20px; - background: transparent; - cursor: pointer; - color: $muted; - transition: all .15s; +.header-name { + direction: rtl; + font-size: .85rem; + font-weight: 700; + color: $ink; + flex: 1; white-space: nowrap; - &:hover { border-color: $primary; color: $primary; } + overflow: hidden; + text-overflow: ellipsis; + letter-spacing: -.01em; } + #app-main { flex: 1; padding: 2rem 1rem 4rem; + + display: flex; + justify-content: center; + align-items: center; } .step-wrap { + // #app-main이 flex 컨테이너라 폭을 명시하지 않으면 콘텐츠 크기로 줄어듦 + width: 100%; max-width: 720px; margin: 0 auto; } @@ -112,26 +118,24 @@ body { top: 14px; left: calc(50% + 14px); right: calc(-50% + 14px); - height: 2px; - background: $border; - transition: background .3s; + height: 1px; + background: $line-soft; } - &.done::after { background: $primary; } + &.done::after { background: $ink; } .dot-circle { width: 28px; height: 28px; border-radius: 50%; - border: 2px solid $border; - background: $card-bg; + border: 1px solid $line-soft; + background: $white; display: flex; align-items: center; justify-content: center; font-size: .72rem; font-weight: 700; color: $muted; - transition: all .3s; position: relative; z-index: 1; } @@ -143,9 +147,9 @@ body { line-height: 1.2; } - &.done .dot-circle { background: $primary; border-color: $primary; color: #fff; } - &.active .dot-circle { background: $primary; border-color: $primary; color: #fff; box-shadow: 0 0 0 4px rgba($primary, .2); } - &.active .dot-label { color: $primary; font-weight: 600; } + &.done .dot-circle { background: $ink; border-color: $ink; color: $white; } + &.active .dot-circle { background: $ink; border-color: $ink; color: $white; } + &.active .dot-label { color: $ink; font-weight: 700; } } } @@ -153,9 +157,9 @@ body { // Cards // ────────────────────────────────────────── .card { - background: $card-bg; + background: $white; + border: 1px solid $line; border-radius: $radius; - box-shadow: $shadow; padding: 2rem; margin-bottom: 1.5rem; @@ -164,7 +168,9 @@ body { .section-title { font-size: 1.3rem; - font-weight: 700; + font-weight: 800; + color: $ink; + letter-spacing: -.02em; margin-bottom: .4rem; } @@ -184,65 +190,56 @@ body { label { display: block; font-size: .875rem; - font-weight: 600; + font-weight: 700; margin-bottom: .5rem; - color: $text-sm; + color: $ink; } input[type="text"] { width: 100%; padding: .6rem .9rem; - border: 1.5px solid $border; - border-radius: $radius-sm; + border: 1px solid $line-soft; + border-radius: $radius; font-size: .9rem; - outline: none; - transition: border-color .2s; - &:focus { border-color: $primary; } - &::placeholder { color: #9CA3AF; } + color: $ink; + background: $white; + &::placeholder { color: #A3A3A3; } } } .radio-group { display: flex; flex-wrap: wrap; - gap: .6rem; + gap: .5rem; label { display: flex; align-items: center; gap: .4rem; padding: .5rem 1rem; - border: 1.5px solid $border; - border-radius: 24px; + border: 1px solid $line-soft; + border-radius: $radius; cursor: pointer; font-size: .875rem; - font-weight: normal; - color: $text-sm; - transition: all .15s; + font-weight: 400; + color: $muted; margin: 0; white-space: nowrap; input[type="radio"] { display: none; } - &:hover { border-color: $primary; color: $primary; } - &.selected { border-color: $primary; background: rgba($primary,.07); color: $primary; font-weight: 600; } + &:hover { border-color: $line; color: $ink; } + &.selected { border-color: $ink; background: $ink; color: $white; font-weight: 700; } } } -.notice-box { - background: #EFF6FF; - border: 1px solid #BFDBFE; - border-radius: $radius-sm; - padding: 1rem 1.2rem; +// 카드 바깥에 놓이는 단계 안내문 — 본문보다 크게 +.step-instruction { + font-size: 1.05rem; + line-height: 1.65; + font-weight: 500; + color: $ink; margin-bottom: 1.5rem; - - p { - font-size: .85rem; - color: #1E40AF; - line-height: 1.6; - &::before { content: '• '; } - + p { margin-top: .3rem; } - } } // ────────────────────────────────────────── @@ -254,60 +251,62 @@ body { justify-content: center; gap: .4rem; padding: .65rem 1.6rem; - border-radius: $radius-sm; - border: none; + border-radius: $radius; + border: 1px solid transparent; font-size: .9rem; - font-weight: 600; + font-weight: 700; cursor: pointer; - transition: all .15s; text-decoration: none; + background: $white; + color: $ink; - &:disabled { opacity: .45; cursor: not-allowed; } + &:disabled { opacity: .35; cursor: not-allowed; } &.btn-primary { - background: $primary; - color: #fff; - &:not(:disabled):hover { background: $primary-dk; transform: translateY(-1px); box-shadow: 0 4px 12px rgba($primary,.35); } + background: $ink; + color: $white; + border-color: $ink; + &:not(:disabled):hover { background: $white; color: $ink; } } &.btn-outline { - background: transparent; - color: $primary; - border: 1.5px solid $primary; - &:hover { background: rgba($primary,.06); } + background: $white; + color: $ink; + border-color: $ink; + &:not(:disabled):hover { background: $ink; color: $white; } } &.btn-gray { - background: #F3F4F6; - color: $text-sm; - border: 1.5px solid $border; - &:not(:disabled):hover { background: $border; } + background: $white; + color: $muted; + border-color: $line-soft; + &:not(:disabled):hover { border-color: $line; color: $ink; } } - &.btn-lg { padding: .85rem 2.2rem; font-size: 1rem; border-radius: 10px; } + &.btn-lg { padding: .85rem 2.2rem; font-size: 1rem; } &.btn-full { width: 100%; } - &.btn-select { - background: $primary; - color: #fff; - flex: 1; - font-size: 1rem; - padding: .9rem 1rem; - &:not(:disabled):hover { background: $primary-dk; } - } - + // 선정 / 미선정 — 미선택 상태는 회색 테두리, 선택 시 검정 반전 + &.btn-select, &.btn-reject { - background: #F9FAFB; - color: #6B7280; - border: 1.5px solid $border; flex: 1; font-size: 1rem; padding: .9rem 1rem; - &:not(:disabled):hover { background: #F3F4F6; color: #374151; } + background: $white; + color: $ink; + border-color: $line-soft; + &:not(:disabled):hover { border-color: $line; } } - &.btn-selected-select { background: $success; color: #fff; flex: 1; padding: .9rem 1rem; font-size: 1rem; } - &.btn-selected-reject { background: $danger; color: #fff; flex: 1; padding: .9rem 1rem; font-size: 1rem; } + &.btn-selected-select, + &.btn-selected-reject { + flex: 1; + padding: .9rem 1rem; + font-size: 1rem; + background: $ink; + color: $white; + border-color: $ink; + } } .btn-row { @@ -330,18 +329,17 @@ body { font-size: .82rem; color: $muted; margin-bottom: .4rem; - font-weight: 600; + font-weight: 700; } .progress-track { - height: 6px; - background: $border; - border-radius: 3px; + height: 4px; + background: $track; + border-radius: $radius; overflow: hidden; .progress-fill { height: 100%; - background: $primary; - border-radius: 3px; - transition: width .4s ease; + background: $ink; + transition: width .3s ease; } } } @@ -353,25 +351,27 @@ body { gap: .75rem; margin-bottom: 1.4rem; padding-bottom: 1rem; - border-bottom: 1px solid $border; + border-bottom: 1px solid $line-soft; .team-badge { font-size: 1.4rem; font-weight: 800; - color: $primary; - background: rgba($primary,.08); - border-radius: $radius-sm; - padding: .3rem .8rem; + color: $ink; + background: $white; + border: 1px solid $line; + border-radius: $radius; + padding: .2rem .7rem; + line-height: 1.4; } .industry-tag { font-size: .82rem; - background: #F3F4F6; - border: 1px solid $border; - border-radius: 20px; - padding: .25rem .75rem; + background: $white; + border: 1px solid $line-soft; + border-radius: $radius; + padding: .25rem .7rem; color: $muted; - font-weight: 500; + font-weight: 400; } } } @@ -384,23 +384,22 @@ body { .var-name { font-size: .82rem; - font-weight: 600; - color: $text-sm; + font-weight: 400; + color: $ink-soft; min-width: 110px; flex-shrink: 0; } .var-bar-track { flex: 1; - height: 10px; - background: #F3F4F6; - border-radius: 5px; + height: 8px; + background: $track; + border-radius: $radius; overflow: hidden; .var-bar-fill { height: 100%; - border-radius: 5px; - transition: width .5s ease; + transition: width .4s ease; } } @@ -409,39 +408,40 @@ body { font-weight: 700; min-width: 32px; text-align: right; - color: $text; + color: $ink; + font-variant-numeric: tabular-nums; } } -// Score color via data-score attribute (set inline via JS) -.var-bar-fill[data-level="low"] { background: linear-gradient(90deg, #EF4444, #F87171); } -.var-bar-fill[data-level="mid"] { background: linear-gradient(90deg, #FBBF24, #FCD34D); } -.var-bar-fill[data-level="high"] { background: linear-gradient(90deg, #22C55E, #4ADE80); } +// 점수 수준은 색이 아니라 회색 농도로 구분 +.var-bar-fill[data-level="low"] { background: #C7C7C7; } +.var-bar-fill[data-level="mid"] { background: #8A8A8A; } +.var-bar-fill[data-level="high"] { background: $ink; } .judgment-section { margin-top: 1.5rem; padding-top: 1.2rem; - border-top: 1px solid $border; + border-top: 1px solid $line-soft; .judgment-label { font-size: .875rem; - font-weight: 600; + font-weight: 700; margin-bottom: .75rem; - color: $text-sm; + color: $ink; } } .confidence-section { margin-top: 1.2rem; padding: 1rem 1.2rem; - background: #F9FAFB; - border-radius: $radius-sm; - border: 1px solid $border; + background: $white; + border: 1px solid $line-soft; + border-radius: $radius; .confidence-label { font-size: .82rem; - font-weight: 600; - color: $text-sm; + font-weight: 700; + color: $ink; margin-bottom: .6rem; display: flex; justify-content: space-between; @@ -450,21 +450,27 @@ body { .confidence-slider { width: 100%; appearance: none; - height: 6px; - border-radius: 3px; - background: $border; - outline: none; + height: 4px; + border-radius: $radius; + background: $track; cursor: pointer; &::-webkit-slider-thumb { appearance: none; - width: 20px; - height: 20px; + width: 18px; + height: 18px; border-radius: 50%; - background: $primary; + background: $ink; + cursor: pointer; + border: none; + } + &::-moz-range-thumb { + width: 18px; + height: 18px; + border-radius: 50%; + background: $ink; + border: none; cursor: pointer; - border: 2px solid #fff; - box-shadow: 0 1px 4px rgba(0,0,0,.2); } } @@ -481,7 +487,6 @@ body { // ────────────────────────────────────────── .judgment-likert-row { display: flex; - gap: .5rem; margin-top: .5rem; } @@ -492,11 +497,12 @@ body { align-items: center; gap: .3rem; padding: .7rem .3rem; - border: 2px solid $border; - border-radius: $radius-sm; - background: $card-bg; + border: 1px solid $line-soft; + border-radius: $radius; + background: $white; cursor: pointer; - transition: all .15s; + + + .judgment-likert-btn { margin-left: -1px; } .jl-num { font-size: 1.1rem; @@ -511,26 +517,17 @@ body { line-height: 1.3; } - &:hover { border-color: $primary; } + &:hover { border-color: $line; position: relative; z-index: 1; } - &.active { - border-color: $primary; - background: rgba($primary, .07); - .jl-num { color: $primary; } - .jl-label { color: $primary; } + // 값별 색상 구분 없이 선택만 검정 반전 + &.active, + &[data-val].active { + border-color: $ink; + background: $ink; + position: relative; + z-index: 2; + .jl-num, .jl-label { color: $white; } } - - // Color coding by value - &[data-val="1"].active { border-color: $danger; background: rgba($danger,.07); - .jl-num, .jl-label { color: $danger; } } - &[data-val="2"].active { border-color: #F97316; background: rgba(#F97316,.07); - .jl-num, .jl-label { color: #F97316; } } - &[data-val="3"].active { border-color: $warning; background: rgba($warning,.07); - .jl-num, .jl-label { color: $warning; } } - &[data-val="4"].active { border-color: #22C55E; background: rgba(#22C55E,.07); - .jl-num, .jl-label { color: #22C55E; } } - &[data-val="5"].active { border-color: $success; background: rgba($success,.07); - .jl-num, .jl-label { color: $success; } } } // ────────────────────────────────────────── @@ -541,7 +538,7 @@ body { font-size: .85rem; color: $muted; margin-bottom: 1.2rem; - font-weight: 500; + font-weight: 400; } .pair-criteria { @@ -555,27 +552,27 @@ body { flex: 1; text-align: center; font-size: 1rem; - font-weight: 700; - color: $text; + font-weight: 400; + color: $muted; padding: .6rem; - border-radius: $radius-sm; - transition: background .2s; + border-radius: $radius; &.crit-left { text-align: right; } &.crit-right { text-align: left; } - &.crit-active-left { color: $primary; background: rgba($primary,.07); } - &.crit-active-right { color: $success; background: rgba($success,.07); } + // 우세한 쪽은 색이 아니라 굵기/명도로 강조 + &.crit-active-left, + &.crit-active-right { color: $ink; font-weight: 800; } } .vs-badge { - font-size: .8rem; + font-size: .75rem; font-weight: 700; color: $muted; - background: $bg; - border: 1px solid $border; - border-radius: 20px; - padding: .25rem .6rem; + background: $white; + border: 1px solid $line-soft; + border-radius: $radius; + padding: .2rem .5rem; flex-shrink: 0; } } @@ -589,39 +586,44 @@ body { display: flex; justify-content: space-between; margin-bottom: .5rem; - // Pad by half the thumb width (12px) so labels align with thumb at min/max - padding: 0 12px; + // Pad by half the thumb width (11px) so labels align with thumb at min/max + padding: 0 11px; span { font-size: .7rem; color: $muted; - font-weight: 600; + font-weight: 400; text-align: center; min-width: 0; } - .sc-mid { font-weight: 800; color: $text-sm; font-size: .75rem; } + .sc-mid { font-weight: 800; color: $ink; font-size: .72rem; } } .ahp-slider { width: 100%; appearance: none; - height: 8px; - border-radius: 4px; - background: linear-gradient(to right, rgba($primary,.15) 0%, $primary 50%, rgba($success,.15) 100%); - outline: none; + height: 4px; + border-radius: $radius; + background: $track; cursor: pointer; &::-webkit-slider-thumb { appearance: none; - width: 24px; - height: 24px; + width: 22px; + height: 22px; border-radius: 50%; - background: #fff; - border: 3px solid $primary; - box-shadow: 0 2px 6px rgba(0,0,0,.2); + background: $ink; + border: none; + cursor: pointer; + } + &::-moz-range-thumb { + width: 22px; + height: 22px; + border-radius: 50%; + background: $ink; + border: none; cursor: pointer; - transition: border-color .2s; } } @@ -629,25 +631,15 @@ body { text-align: center; margin-top: 1rem; padding: .7rem 1rem; - background: #F0F9FF; - border: 1px solid #BAE6FD; - border-radius: $radius-sm; + background: $white; + border: 1px solid $line-soft; + border-radius: $radius; font-size: .875rem; - font-weight: 600; - color: #0369A1; + font-weight: 700; + color: $ink; min-height: 42px; } -.cr-warning { - background: #FEF9C3; - border: 1px solid #FDE047; - border-radius: $radius-sm; - padding: 1rem 1.2rem; - margin-top: 1rem; - - p { font-size: .875rem; color: #713F12; margin-bottom: .75rem; font-weight: 600; } -} - // ────────────────────────────────────────── // Results (Step 4) // ────────────────────────────────────────── @@ -662,57 +654,64 @@ body { .wc-label { font-size: .82rem; - font-weight: 600; - color: $text-sm; + font-weight: 400; + color: $ink-soft; min-width: 110px; flex-shrink: 0; } .wc-track { flex: 1; - height: 18px; - background: #F3F4F6; - border-radius: 9px; + height: 14px; + background: $track; + border-radius: $radius; overflow: hidden; .wc-fill { height: 100%; - background: linear-gradient(90deg, $primary, #60A5FA); - border-radius: 9px; - transition: width .6s ease; + background: $ink; + transition: width .5s ease; } } - .wc-pct { font-size: .82rem; font-weight: 700; color: $primary; min-width: 38px; text-align: right; } + .wc-pct { + font-size: .82rem; + font-weight: 700; + color: $ink; + min-width: 38px; + text-align: right; + font-variant-numeric: tabular-nums; + } } } .stats-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); - gap: 1rem; + gap: .75rem; margin-top: 1rem; .stat-box { - background: $bg; - border: 1px solid $border; - border-radius: $radius-sm; + background: $white; + border: 1px solid $line-soft; + border-radius: $radius; padding: 1rem; text-align: center; - .stat-val { font-size: 1.6rem; font-weight: 800; color: $primary; } + .stat-val { font-size: 1.6rem; font-weight: 800; color: $ink; font-variant-numeric: tabular-nums; } .stat-label { font-size: .75rem; color: $muted; margin-top: .25rem; line-height: 1.3; } - &.stat-warn .stat-val { color: $warning; } - &.stat-ok .stat-val { color: $success; } + // 경고/정상 모두 색 대신 테두리 굵기로만 구분 + &.stat-warn { border-color: $line; } + &.stat-ok { border-color: $line-soft; } } } .comparison-table-wrap { overflow-x: auto; margin-top: 1rem; - border-radius: $radius-sm; - border: 1px solid $border; + border: 1px solid $line; + border-radius: $radius; table { width: 100%; @@ -720,46 +719,49 @@ body { font-size: .82rem; th { - background: #F9FAFB; + background: $white; padding: .6rem .8rem; text-align: center; font-weight: 700; - border-bottom: 1px solid $border; + border-bottom: 1px solid $line; white-space: nowrap; - color: $text-sm; + color: $ink; } td { padding: .55rem .8rem; text-align: center; - border-bottom: 1px solid $border; + border-bottom: 1px solid $line-soft; + color: $ink-soft; - &:first-child { font-weight: 700; } + &:first-child { font-weight: 700; color: $ink; } } tr:last-child td { border-bottom: none; } - tr.mismatch td { background: #FEF2F2; color: $danger; } - tr.mismatch td:first-child { color: $danger; } + // 불일치 행은 옅은 회색 면으로만 표시 + tr.mismatch td { background: $wash; color: $ink; } + tr.mismatch td:first-child { color: $ink; } } } .badge { display: inline-block; padding: .2rem .55rem; - border-radius: 12px; - font-size: .75rem; + border-radius: $radius; + font-size: .72rem; font-weight: 700; - line-height: 1; + line-height: 1.4; + border: 1px solid transparent; - &.badge-select { background: #DBEAFE; color: $primary; } - &.badge-reject { background: #F3F4F6; color: $muted; } - &.badge-match { background: #DCFCE7; color: $success; } - &.badge-mismatch { background: #FEE2E2; color: $danger; } - &.badge-neutral { background: #FEF9C3; color: $warning; } + &.badge-select { background: $ink; color: $white; border-color: $ink; } + &.badge-reject { background: $white; color: $muted; border-color: $line-soft; } + &.badge-match { background: $white; color: $ink; border-color: $line; } + &.badge-mismatch { background: $ink; color: $white; border-color: $ink; } + &.badge-neutral { background: $white; color: $muted; border-color: $line-soft; } } -.neutral-row td { background: #FAFAF0; color: $warning; } +.neutral-row td { background: $wash; color: $ink-soft; } // ────────────────────────────────────────── // Post Survey (Step 5) @@ -769,15 +771,14 @@ body { .likert-q { font-size: .9rem; - font-weight: 600; - color: $text; + font-weight: 700; + color: $ink; margin-bottom: .75rem; line-height: 1.5; } .likert-options { display: flex; - gap: .5rem; label { flex: 1; @@ -787,25 +788,26 @@ body { gap: .3rem; cursor: pointer; + + label .likert-btn { margin-left: -1px; } + input[type="radio"] { display: none; } .likert-btn { width: 100%; padding: .55rem .2rem; - border: 2px solid $border; - border-radius: $radius-sm; + border: 1px solid $line-soft; + border-radius: $radius; text-align: center; font-size: .9rem; font-weight: 700; color: $muted; - transition: all .15s; - background: $card-bg; + background: $white; } .likert-desc { font-size: .65rem; color: $muted; text-align: center; line-height: 1.2; } - input:checked + .likert-btn { border-color: $primary; background: rgba($primary,.08); color: $primary; } - &:hover .likert-btn { border-color: $primary; } + input:checked + .likert-btn { border-color: $ink; background: $ink; color: $white; position: relative; z-index: 1; } + &:hover .likert-btn { border-color: $line; } } } } @@ -813,15 +815,14 @@ body { .open-textarea { width: 100%; padding: .8rem 1rem; - border: 1.5px solid $border; - border-radius: $radius-sm; + border: 1px solid $line-soft; + border-radius: $radius; font-size: .875rem; font-family: inherit; + color: $ink; + background: $white; resize: vertical; min-height: 100px; - outline: none; - transition: border-color .2s; - &:focus { border-color: $primary; } } // ────────────────────────────────────────── @@ -831,15 +832,16 @@ body { text-align: center; padding: 2rem 1rem; - .complete-icon { font-size: 3.5rem; margin-bottom: 1rem; } - .complete-title { font-size: 1.5rem; font-weight: 800; margin-bottom: .5rem; } + .complete-icon { font-size: 2.5rem; margin-bottom: 1rem; filter: grayscale(1); } + .complete-title { font-size: 1.5rem; font-weight: 800; letter-spacing: -.02em; margin-bottom: .5rem; } .complete-desc { color: $muted; font-size: .9rem; margin-bottom: 2rem; line-height: 1.7; } } .json-output { - background: #1E293B; - color: #94A3B8; - border-radius: $radius-sm; + background: $white; + color: $ink-soft; + border: 1px solid $line-soft; + border-radius: $radius; padding: 1.2rem; font-family: 'Menlo', 'Monaco', 'Consolas', monospace; font-size: .75rem; @@ -857,7 +859,7 @@ body { // ────────────────────────────────────────── .divider { height: 1px; - background: $border; + background: $line-soft; margin: 1.5rem 0; } @@ -868,9 +870,10 @@ body { .mt-3 { margin-top: 1.5rem; } .mb-1 { margin-bottom: .5rem; } +// 파란색 강조 대신 굵기로만 강조 .highlight-text { - color: $primary; - font-weight: 700; + color: $ink; + font-weight: 800; } // ────────────────────────────────────────── @@ -883,6 +886,8 @@ body { .card { padding: 1.2rem; } + .step-instruction { font-size: .95rem; margin-bottom: 1.2rem; } + .step-progress .step-dot .dot-label { display: none; } .var-row .var-name { min-width: 80px; font-size: .76rem; } @@ -900,8 +905,6 @@ body { .likert-group .likert-options label .likert-desc { display: none; } .btn.btn-lg { padding: .75rem 1.5rem; font-size: .9rem; } - - .btn-row.judgment-btns { gap: .5rem; } } @media (max-width: 380px) {
${t('colTeam')}${t('colModelScore')}${t('colModelJudge')}${t('colIntuition')}${t('colMatch')}모델 점수모델 판단직관 (1-5)일치