From ebbeac2231ab87ba0227bc7ac36e65e48edd76c2 Mon Sep 17 00:00:00 2001 From: seung6lee Date: Tue, 26 May 2026 09:25:11 -0500 Subject: [PATCH] initial commit --- .DS_Store | Bin 0 -> 6148 bytes data/responses.csv | 1 + data/responses.json | 1 + package-lock.json | 1255 ++++++++++++++++++++++++++++++++++++++++++ package.json | 14 + public/.DS_Store | Bin 0 -> 6148 bytes public/css/main.css | 1 + public/index.html | 20 + public/js/ahp.js | 86 +++ public/js/app.js | 716 ++++++++++++++++++++++++ public/js/cases.js | 116 ++++ public/js/i18n.js | 195 +++++++ server.js | 117 ++++ src/styles/main.scss | 956 ++++++++++++++++++++++++++++++++ 설문 세트.xlsx | Bin 0 -> 10181 bytes 15 files changed, 3478 insertions(+) create mode 100644 .DS_Store create mode 100644 data/responses.csv create mode 100644 data/responses.json create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 public/.DS_Store create mode 100644 public/css/main.css create mode 100644 public/index.html create mode 100644 public/js/ahp.js create mode 100644 public/js/app.js create mode 100644 public/js/cases.js create mode 100644 public/js/i18n.js create mode 100644 server.js create mode 100644 src/styles/main.scss create mode 100644 설문 세트.xlsx diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..23101f140e1cac73fdeed2457f95e71fb7cd4a86 GIT binary patch literal 6148 zcmeHK%}T>S5T3QM#6M_nUW8nFw?08E@dDTfEWtqj5vd)KLS<;?Not3Rp1N5o{e$< literal 0 HcmV?d00001 diff --git a/data/responses.csv b/data/responses.csv new file mode 100644 index 0000000..127ccf4 --- /dev/null +++ b/data/responses.csv @@ -0,0 +1 @@ +timestamp,country,age,gender,case_1_likert,case_2_likert,case_3_likert,case_4_likert,case_5_likert,case_6_likert,case_7_likert,case_8_likert,case_9_likert,case_10_likert,case_11_likert,case_12_likert,case_13_likert,case_14_likert,case_15_likert,case_1_order,case_2_order,case_3_order,case_4_order,case_5_order,case_6_order,case_7_order,case_8_order,case_9_order,case_10_order,case_11_order,case_12_order,case_13_order,case_14_order,case_15_order,case_1_ms,case_2_ms,case_3_ms,case_4_ms,case_5_ms,case_6_ms,case_7_ms,case_8_ms,case_9_ms,case_10_ms,case_11_ms,case_12_ms,case_13_ms,case_14_ms,case_15_ms,ahp_w_stability,ahp_w_revenue,ahp_w_capital,ahp_w_plan,ahp_w_social,ahp_lambda_max,ahp_CI,ahp_CR,ahp_cr_warning,match_case_1,match_case_2,match_case_3,match_case_4,match_case_5,match_case_6,match_case_7,match_case_8,match_case_9,match_case_10,match_case_11,match_case_12,match_case_13,match_case_14,match_case_15,post_q1,post_q2,post_q3,post_q4,open_response diff --git a/data/responses.json b/data/responses.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/data/responses.json @@ -0,0 +1 @@ +[] diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..b333f72 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1255 @@ +{ + "name": "startup-survey", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "startup-survey", + "version": "1.0.0", + "dependencies": { + "express": "^4.18.2", + "sass": "^1.69.5" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", + "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.6", + "@parcel/watcher-darwin-arm64": "2.5.6", + "@parcel/watcher-darwin-x64": "2.5.6", + "@parcel/watcher-freebsd-x64": "2.5.6", + "@parcel/watcher-linux-arm-glibc": "2.5.6", + "@parcel/watcher-linux-arm-musl": "2.5.6", + "@parcel/watcher-linux-arm64-glibc": "2.5.6", + "@parcel/watcher-linux-arm64-musl": "2.5.6", + "@parcel/watcher-linux-x64-glibc": "2.5.6", + "@parcel/watcher-linux-x64-musl": "2.5.6", + "@parcel/watcher-win32-arm64": "2.5.6", + "@parcel/watcher-win32-ia32": "2.5.6", + "@parcel/watcher-win32-x64": "2.5.6" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz", + "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz", + "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz", + "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz", + "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz", + "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz", + "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz", + "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz", + "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz", + "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz", + "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz", + "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz", + "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz", + "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.5", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", + "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.15.1", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.14.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/immutable": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.5.tgz", + "integrity": "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==", + "license": "MIT" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "optional": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT", + "optional": true + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", + "license": "MIT" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/sass": { + "version": "1.99.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.99.0.tgz", + "integrity": "sha512-kgW13M54DUB7IsIRM5LvJkNlpH+WhMpooUcaWGFARkF1Tc82v9mIWkCbCYf+MBvpIUBSeSOTilpZjEPr2VYE6Q==", + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.1.5", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..602e8af --- /dev/null +++ b/package.json @@ -0,0 +1,14 @@ +{ + "name": "startup-survey", + "version": "1.0.0", + "description": "스타트업 지원 심사 판단 실험 웹사이트", + "main": "server.js", + "scripts": { + "start": "node server.js", + "dev": "node server.js" + }, + "dependencies": { + "express": "^4.18.2", + "sass": "^1.69.5" + } +} diff --git a/public/.DS_Store b/public/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..1b813daee9f7092c51d833d926c434abb2876cd1 GIT binary patch literal 6148 zcmeHKOG*Pl5UomrjKoEjF8c_^3)qJ71i64D0Yx%QAh^kD?vRb1MtAPSt+(-2SH;Z4 z#-&K8g6`K{-NEXU<||$uXYJ>uQxr@qbnF?0uTk zn%2v^_VZtT>@8JYma}C&gQhrrIlDOde7kO27r$6n_wB3^Vbr6C!)ni$JAJ#>=6(A# z)LJQ~s`r%+ZQVMrQpd#^a0Z+KXTTZAF@QT;q!=pt> zY5{-)sFPqVy@bRB!)Vwj!UACp1!^c;i@_QWeK5ah*eGf^u{9rTm3Pexr|QTbk~?u! z^w}A32D%LNbvu&#{|28-w#n~?_|6${29AsY9+cB^f|s(p^~dYUT^lfNF+{|#kp%?n l&QAa~a*muQquPV4;TH`XMad%i6b|$sfkcQ;&cGiq@C7uMKRW;b literal 0 HcmV?d00001 diff --git a/public/css/main.css b/public/css/main.css new file mode 100644 index 0000000..0574a0b --- /dev/null +++ b/public/css/main.css @@ -0,0 +1 @@ +*,*::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],.form-group input[type=number]{width:100%;padding:.6rem .9rem;border:1.5px solid #e5e7eb;border-radius:8px;font-size:.9rem;outline:none;transition:border-color .2s;font-family:inherit}.form-group input[type=text]:focus,.form-group input[type=number]:focus{border-color:#2563eb}.form-group input[type=text]::placeholder,.form-group input[type=number]::placeholder{color:#9ca3af}.form-select{width:100%;padding:.6rem .9rem;border:1.5px solid #e5e7eb;border-radius:8px;font-size:.9rem;font-family:inherit;outline:none;background:#fff;color:#111827;cursor:pointer;transition:border-color .2s;appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%236B7280' stroke-width='1.5' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right .9rem center;padding-right:2.5rem}.form-select:focus{border-color:#2563eb}.radio-label{display:flex;align-items:center;gap:.4rem;padding:.5rem 1rem;border:1.5px solid #e5e7eb;border-radius:24px;cursor:pointer;font-size:.875rem;color:#374151;transition:all .15s;margin:0;white-space:nowrap}.radio-label input[type=radio]{display:none}.radio-label:hover{border-color:#2563eb;color:#2563eb}.radio-label.selected{border-color:#2563eb;background:rgba(37,99,235,.07);color:#2563eb;font-weight:600}.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;width:140px;flex-shrink:0;flex-grow: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:stretch;justify-content:space-between;gap:1rem;margin-bottom:1.2rem;min-height:4rem}.ahp-pair-card .pair-criteria .crit{flex:1;display:flex;align-items:center;justify-content:flex-end;text-align:right;font-size:1rem;font-weight:700;color:#111827;padding:.6rem;border-radius:8px;transition:background .2s;word-break:break-word}.ahp-pair-card .pair-criteria .crit.crit-left{justify-content:flex-end;text-align:right}.ahp-pair-card .pair-criteria .crit.crit-right{justify-content:flex-start;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;align-self:center}.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{width:100px;font-size:.76rem}.ahp-pair-card .pair-criteria{flex-direction:column;gap:.5rem;min-height:0}.ahp-pair-card .pair-criteria .crit{text-align:center !important;justify-content: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 diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..21bdf42 --- /dev/null +++ b/public/index.html @@ -0,0 +1,20 @@ + + + + + + 스타트업 지원 심사 판단 실험 + + + +
+
+
+
+ + + + + + + diff --git a/public/js/ahp.js b/public/js/ahp.js new file mode 100644 index 0000000..674b86d --- /dev/null +++ b/public/js/ahp.js @@ -0,0 +1,86 @@ +// PAIRS: indices into VARS array [0=안정성, 1=수익_가능성, 2=자금_효율성, 3=사업_계획_완성도, 4=사회적_가치] +const AHP_PAIRS = [ + [0,1],[0,2],[0,3],[0,4], + [1,2],[1,3],[1,4], + [2,3],[2,4], + [3,4] +]; + +function buildMatrix(pairwiseResults) { + const n = 5; + const matrix = Array.from({length: n}, () => Array(n).fill(1)); + AHP_PAIRS.forEach(([i, j], idx) => { + const val = pairwiseResults[idx]; + if (val >= 1) { + matrix[i][j] = val; + matrix[j][i] = 1 / val; + } else { + matrix[j][i] = Math.abs(val); + matrix[i][j] = 1 / Math.abs(val); + } + }); + return matrix; +} + +function powerMethod(matrix, maxIter = 1000, threshold = 1e-6) { + const n = matrix.length; + let v = Array(n).fill(1 / n); + for (let iter = 0; iter < maxIter; iter++) { + const Av = Array(n).fill(0); + for (let i = 0; i < n; i++) + for (let j = 0; j < n; j++) + Av[i] += matrix[i][j] * v[j]; + const sum = Av.reduce((a, b) => a + b, 0); + const vNew = Av.map(x => x / sum); + const maxDiff = Math.max(...vNew.map((x, k) => Math.abs(x - v[k]))); + v = vNew; + if (maxDiff < threshold) break; + } + return v; +} + +function calcConsistency(matrix, weights) { + const n = matrix.length; + const RI = [0, 0, 0.58, 0.90, 1.12, 1.24, 1.32, 1.41, 1.45]; + const Aw = Array(n).fill(0); + for (let i = 0; i < n; i++) + for (let j = 0; j < n; j++) + Aw[i] += matrix[i][j] * weights[j]; + const lambdaMax = Aw.reduce((s, v, i) => s + v / weights[i], 0) / n; + const CI = (lambdaMax - n) / (n - 1); + const CR = CI / RI[n]; + return { lambdaMax, CI, CR }; +} + +function calcModelScore(caseData, weights) { + return VARS.reduce((sum, varKey, i) => sum + caseData[varKey] * weights[i], 0); +} + +// Slider positions -4 to +4 map to AHP odd values 1,3,5,7,9 +// pos=0 → 1 (equal) +// pos<0 → left more important, AHP value = 2*|pos|+1 (1,3,5,7,9) +// pos>0 → right more important, stored as negative: -(2*pos+1) +function sliderToStored(sliderPos) { + if (sliderPos === 0) return 1; + if (sliderPos < 0) return 2 * Math.abs(sliderPos) + 1; + return -(2 * sliderPos + 1); +} + +function storedToSlider(val) { + if (val === 1) return 0; + if (val > 1) return -((val - 1) / 2); // left was important + return (Math.abs(val) - 1) / 2; // right was important +} + +function ahpValueToScale(absVal) { + // absVal is 2-9 + const levels = { 2: '2', 3: '3', 4: '4', 5: '5', 6: '6', 7: '7', 8: '8', 9: '9' }; + return absVal; +} + +function runAHP(pairwiseRaw) { + const matrix = buildMatrix(pairwiseRaw); + const weights = powerMethod(matrix); + const { lambdaMax, CI, CR } = calcConsistency(matrix, weights); + return { weights, lambdaMax, CI, CR, matrix }; +} diff --git a/public/js/app.js b/public/js/app.js new file mode 100644 index 0000000..e2e0b9c --- /dev/null +++ b/public/js/app.js @@ -0,0 +1,716 @@ +// ────────────────────────────────────────── +// State +// ────────────────────────────────────────── +const state = { + step: 1, + started: false, + + // Step 1 + meta: { country: '', age: '', gender: '', timestamp: null }, + + // 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 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(); +} + +function renderHeader() { + const el = document.getElementById('app-header'); + if (!el) return; + el.innerHTML = ` +
+ ${t('appTitle')} + +
+ `; +} + +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; + } +} + +// ────────────────────────────────────────── +// STEP 1 +// ────────────────────────────────────────── +const COUNTRIES = [ + 'Afghanistan','Albania','Algeria','Angola','Argentina','Armenia','Australia','Austria', + 'Azerbaijan','Bangladesh','Belarus','Belgium','Bolivia','Bosnia and Herzegovina','Brazil', + 'Bulgaria','Cambodia','Cameroon','Canada','Chile','China','Colombia','Congo','Costa Rica', + 'Croatia','Cuba','Czech Republic','Denmark','Dominican Republic','Ecuador','Egypt', + 'El Salvador','Ethiopia','Finland','France','Georgia','Germany','Ghana','Greece', + 'Guatemala','Haiti','Honduras','Hungary','India','Indonesia','Iran','Iraq','Ireland', + 'Israel','Italy','Jamaica','Japan','Jordan','Kazakhstan','Kenya','Kuwait','Kyrgyzstan', + 'Laos','Lebanon','Libya','Lithuania','Malaysia','Mexico','Moldova','Mongolia','Morocco', + 'Mozambique','Myanmar','Nepal','Netherlands','New Zealand','Nicaragua','Nigeria','Norway', + 'Pakistan','Palestine','Panama','Paraguay','Peru','Philippines','Poland','Portugal', + 'Romania','Russia','Rwanda','Saudi Arabia','Senegal','Serbia','Singapore','Slovakia', + 'Somalia','South Africa','South Korea','Spain','Sri Lanka','Sudan','Sweden','Switzerland', + 'Syria','Taiwan','Tajikistan','Tanzania','Thailand','Tunisia','Turkey','Turkmenistan', + 'Uganda','Ukraine','United Arab Emirates','United Kingdom','United States','Uruguay', + 'Uzbekistan','Venezuela','Vietnam','Yemen','Zambia','Zimbabwe','Other', +]; + +function renderStep1() { + const { meta } = state; + const notices = t('step1Notices').map(n => `

${n}

`).join(''); + const countryOpts = COUNTRIES.map(c => + `` + ).join(''); + const genderOpts = t('step1Q3opts').map(o => ` + + `).join(''); + + return ` +
+
+

${t('step1Title')}

+

${t('step1Intro')}

+ +
${notices}
+ +
+ + +
+ +
+ + +
+ +
+ +
${genderOpts}
+
+ +
+ +
+
+
+ `; +} + +function bindStep1() { + document.getElementById('sel-country').addEventListener('change', e => { + state.meta.country = e.target.value; + }); + + document.getElementById('inp-age').addEventListener('input', e => { + state.meta.age = e.target.value; + }); + + document.getElementById('rg-gender').addEventListener('click', e => { + const lbl = e.target.closest('label'); + if (!lbl) return; + const val = lbl.querySelector('input').value; + state.meta.gender = val; + document.querySelectorAll('#rg-gender label').forEach(l => + l.classList.toggle('selected', l.querySelector('input').value === val)); + }); + + document.getElementById('btn-start').addEventListener('click', () => { + if (!state.meta.country || !state.meta.age || !state.meta.gender) { + alert(t('alertFillAll')); 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 varRows = VARS.map(key => { + const score = c[key]; + const level = getScoreLevel(score); + return ` +
+ ${varNames[key]} +
+
+
+ ${score} +
+ `; + }).join(''); + + const scale = t('judgmentScale'); + const likertBtns = scale.map((label, i) => { + const val = i + 1; + const active = state.currentJudgment === val; + return ` + + `; + }).join(''); + + return ` +
+
+
+ ${idx + 1} / ${total} +
+
+
+
+
+ +
+
+ Team ${c.teamLetter} +
+ +
${varRows}
+ +
+

${t('judgmentLabel')}

+
${likertBtns}
+
+ +
+ +
+
+ +

${t('step2Instruction')}

+
+ `; +} + +function bindStep2() { + 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('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, + }); + state.caseIndex++; + state.currentJudgment = null; + if (state.caseIndex >= state.shuffledCases.length) { + goStep(3); + } else { + renderMain(); + window.scrollTo({ top: 0, behavior: 'smooth' }); + } + }); +} + +// ────────────────────────────────────────── +// 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'], +]; + +function getPairLabels(idx) { + return currentLang === 'en' ? AHP_PAIR_LABELS_EN[idx] : AHP_PAIR_LABELS_KO[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); +} + +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 leftActive = sliderVal < 0 ? 'crit-active-left' : ''; + const rightActive = sliderVal > 0 ? 'crit-active-right' : ''; + + return ` +
+
+
+ ${idx + 1} / ${total} +
+
+
+
+
+ +
+

${t('step3Title')}

+

${t('step3Instruction')}

+ +
+
+
${left}
+
VS
+
${right}
+
+ +
+
+ 9753 + 1 + 3579 +
+ +
+ +
${desc}
+
+ + ${state.crWarningPending ? ` +
+

${t('crWarning')}

+
+ + +
+
+ ` : ` +
+ +
+ `} +
+
+ `; +} + +function bindStep3() { + 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 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); + }); +} + +function advancePair() { + const total = AHP_PAIRS.length; + if (state.pairIndex < total - 1) { + state.pairIndex++; + renderMain(); + window.scrollTo({ top: 0, behavior: 'smooth' }); + } 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(); + } else { + 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 +} + +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, + }; + }); +} + +// ────────────────────────────────────────── +// STEP 4 +// ────────────────────────────────────────── +function renderStep4() { + const weights = state.ahpResult.weights; + const varNames = t('varNames'); + + const weightRows = VARS.map((key, i) => { + const pct = Math.round(weights[i] * 100); + return ` +
+ ${varNames[key]} +
+ ${pct}% +
+ `; + }).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 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 ` + + Team ${c.team} + ${c.model_score} + ${mLabel} + ${iLabel} + ${matchBadge} + + `; + }).join(''); + + return ` +
+
+

${t('step4Title')}

+

${t('weightsTitle')}

+
${weightRows}
+
+ +
+

${t('statsTitle')}

+
+
+
${totalMismatch}/${decidable.length}
+
${t('statTotal')}
+
+
+
${baseline.filter(c=>!c.match).length}/${baseline.length}
+
${t('statBaseline')}
+
+
+
${spike.filter(c=>!c.match).length}/${spike.length}
+
${t('statSpike')}
+
+
+
${conflict.filter(c=>!c.match).length}/${conflict.length}
+
${t('statConflict')}
+
+
+
${CR.toFixed(3)}
+
${t('statCR')}
+
+
+

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

+
+ +
+

${t('tableTitle')}

+
+ + + + + + + + + + + ${rows} +
${t('colTeam')}${t('colModelScore')}${t('colModelJudge')}${t('colIntuition')}${t('colMatch')}
+
+
+ +
+ +
+
+ `; +} + +// ────────────────────────────────────────── +// 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 ` + + `; + }).join(''); + return ` +
+

${qi+1}. ${q}

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

${t('step5Title')}

+

${t('step5Desc')}

+ ${groups} +
+ + +
+
+ +
+
+
+ `; +} + +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.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, country: state.meta.country, age: state.meta.age, gender: state.meta.gender }, + 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, + }; +} + +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(); +} + +function renderComplete() { + return ` +
+
+
+
🎉
+

${t('completeTitle')}

+

${t('completeDesc')}

+
+
+
+ `; +} + +function bindComplete() {} + +// ────────────────────────────────────────── +// Navigation & utilities +// ────────────────────────────────────────── +function goStep(n) { + 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); +} + +function disableLeaveWarning() { + if (window._leaveHandler) { + window.removeEventListener('beforeunload', window._leaveHandler); + window._leaveHandler = null; + } +} + +function escHtml(str) { + return String(str) + .replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); +} + +document.addEventListener('DOMContentLoaded', renderApp); diff --git a/public/js/cases.js b/public/js/cases.js new file mode 100644 index 0000000..f82520b --- /dev/null +++ b/public/js/cases.js @@ -0,0 +1,116 @@ +const CASES = [ + { + id: 1, industry: '헬스케어', industryEn: 'Healthcare', + 안정성: 85, 수익_가능성: 82, 자금_효율성: 80, + 사업_계획_완성도: 88, 사회적_가치: 84, + type: 'baseline', note: '명확 선발' + }, + { + id: 2, industry: '물류', industryEn: 'Logistics', + 안정성: 18, 수익_가능성: 22, 자금_효율성: 15, + 사업_계획_완성도: 20, 사회적_가치: 25, + type: 'baseline', note: '명확 탈락' + }, + { + id: 3, industry: '교육', industryEn: 'Education', + 안정성: 68, 수익_가능성: 72, 자금_효율성: 65, + 사업_계획_완성도: 70, 사회적_가치: 66, + type: 'baseline', note: '무난 중상' + }, + { + id: 4, industry: '제조', industryEn: 'Manufacturing', + 안정성: 35, 수익_가능성: 38, 자금_효율성: 42, + 사업_계획_완성도: 30, 사회적_가치: 35, + type: 'baseline', note: '무난 중하' + }, + { + id: 5, industry: '핀테크', industryEn: 'Fintech', + 안정성: 5, 수익_가능성: 68, 자금_효율성: 72, + 사업_계획_완성도: 70, 사회적_가치: 65, + type: 'spike', spike_var: '안정성', spike_dir: 'down' + }, + { + id: 6, industry: '에너지', industryEn: 'Energy', + 안정성: 70, 수익_가능성: 97, 자금_효율성: 68, + 사업_계획_완성도: 65, 사회적_가치: 72, + type: 'spike', spike_var: '수익 가능성', spike_dir: 'up' + }, + { + id: 7, industry: 'IT', industryEn: 'IT', + 안정성: 65, 수익_가능성: 70, 자금_효율성: 4, + 사업_계획_완성도: 68, 사회적_가치: 72, + type: 'spike', spike_var: '자금 효율성', spike_dir: 'down' + }, + { + id: 8, industry: '바이오', industryEn: 'Biotech', + 안정성: 68, 수익_가능성: 65, 자금_효율성: 70, + 사업_계획_완성도: 96, 사회적_가치: 66, + type: 'spike', spike_var: '사업 계획 완성도', spike_dir: 'up' + }, + { + id: 9, industry: '미디어', industryEn: 'Media', + 안정성: 72, 수익_가능성: 68, 자금_효율성: 65, + 사업_계획_완성도: 70, 사회적_가치: 6, + type: 'spike', spike_var: '사회적 가치', spike_dir: 'down' + }, + { + id: 10, industry: '소셜', industryEn: 'Social', + 안정성: 95, 수익_가능성: 95, 자금_효율성: 15, + 사업_계획_완성도: 20, 사회적_가치: 25, + type: 'conflict', note: '수익·안정성 최고 vs 나머지 최저' + }, + { + id: 11, industry: '복지', industryEn: 'Welfare', + 안정성: 20, 수익_가능성: 18, 자금_효율성: 22, + 사업_계획_완성도: 25, 사회적_가치: 95, + type: 'conflict', note: '사회가치만 최고 나머지 최저' + }, + { + id: 12, industry: '환경', industryEn: 'Environment', + 안정성: 88, 수익_가능성: 85, 자금_효율성: 90, + 사업_계획_완성도: 92, 사회적_가치: 12, + type: 'conflict', note: '사회가치만 최저 나머지 모두 최고' + }, + { + id: 13, industry: '문화', industryEn: 'Culture', + 안정성: 15, 수익_가능성: 92, 자금_효율성: 18, + 사업_계획_완성도: 20, 사회적_가치: 88, + type: 'conflict', note: '수익·사회가치 높음 나머지 낮음' + }, + { + id: 14, industry: '농업', industryEn: 'Agriculture', + 안정성: 90, 수익_가능성: 22, 자금_효율성: 88, + 사업_계획_완성도: 85, 사회적_가치: 82, + type: 'conflict', note: '수익만 최저 나머지 모두 높음' + }, + { + id: 15, industry: '패션', industryEn: 'Fashion', + 안정성: 48, 수익_가능성: 52, 자금_효율성: 50, + 사업_계획_완성도: 47, 사회적_가치: 53, + type: 'conflict', note: '모든 변인 50점대 경계선' + }, +]; + +const TEAM_LETTERS = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O']; + +const VARS = ['안정성', '수익_가능성', '자금_효율성', '사업_계획_완성도', '사회적_가치']; + +function shuffleCases() { + const indices = CASES.map((_, i) => i); + // Fisher-Yates shuffle + for (let i = indices.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [indices[i], indices[j]] = [indices[j], indices[i]]; + } + return indices.map((origIdx, newIdx) => ({ + ...CASES[origIdx], + teamLetter: TEAM_LETTERS[newIdx], + presentedOrder: newIdx + 1, + })); +} + +function getScoreLevel(score) { + if (score < 35) return 'low'; + if (score < 65) return 'mid'; + return 'high'; +} diff --git a/public/js/i18n.js b/public/js/i18n.js new file mode 100644 index 0000000..21a9daf --- /dev/null +++ b/public/js/i18n.js @@ -0,0 +1,195 @@ +const I18N = { + ko: { + appTitle: '스타트업 지원 심사 판단 실험', + langBtn: 'EN', + + stepLabels: ['안내', '직관 판단', 'AHP 비교', '결과', '사후 설문'], + + // Step 1 + step1Title: '스타트업 지원 심사 판단 실험', + step1Intro: '당신은 스타트업 지원 프로그램의 심사위원입니다. 이 실험은 두 가지 판단 방식 — 인간의 직관과 AI 기반 의사결정 모델(AHP) — 을 비교 분석합니다. 15개의 스타트업 사례를 보고 지원금 지급 가능성을 평가한 뒤, 본인만의 평가 기준 가중치를 설정하게 됩니다.', + step1Notices: [ + '정답은 없습니다. 느낌과 직관대로 판단해 주세요.', + '총 소요 시간은 약 5~6분입니다.', + '중간에 페이지를 나가면 데이터가 저장되지 않습니다.', + ], + step1Q1: '국가', + step1Q1placeholder: '국가를 선택하세요', + step1Q2: '나이', + step1Q2placeholder: '나이를 입력하세요', + step1Q3: '성별', + step1Q3opts: ['남성', '여성', '기타'], + 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 5–6 minutes.', + 'Leaving the page mid-experiment will result in lost data.', + ], + step1Q1: 'Country', + step1Q1placeholder: 'Select your country', + step1Q2: 'Age', + step1Q2placeholder: 'Enter your age', + step1Q3: 'Gender', + step1Q3opts: ['Male', 'Female', 'Other'], + 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.', + 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 = 'en'; + +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/server.js b/server.js new file mode 100644 index 0000000..d880bcb --- /dev/null +++ b/server.js @@ -0,0 +1,117 @@ +const express = require('express'); +const fs = require('fs'); +const path = require('path'); +const sass = require('sass'); + +const app = express(); +const BASE = __dirname; +const DATA_FILE = path.join(BASE, 'data', 'responses.json'); +const CSV_FILE = path.join(BASE, 'data', 'responses.csv'); + +// Compile SCSS on startup +try { + const result = sass.compile(path.join(BASE, 'src', 'styles', 'main.scss'), { style: 'compressed' }); + fs.writeFileSync(path.join(BASE, 'public', 'css', 'main.css'), result.css); + console.log('SCSS compiled successfully'); +} catch (err) { + console.error('SCSS compile error:', err.message); +} + +// Ensure data files exist +if (!fs.existsSync(DATA_FILE)) fs.writeFileSync(DATA_FILE, JSON.stringify([], null, 2)); +if (!fs.existsSync(CSV_FILE)) fs.writeFileSync(CSV_FILE, buildCSVHeader() + '\n'); + +// ────────────────────────────────────────── +// CSV helpers +// ────────────────────────────────────────── +function csvEscape(val) { + const s = String(val ?? ''); + if (s.includes(',') || s.includes('"') || s.includes('\n')) { + return '"' + s.replace(/"/g, '""') + '"'; + } + return s; +} + +function buildCSVHeader() { + const cols = ['timestamp', 'country', 'age', 'gender']; + for (let i = 1; i <= 15; i++) cols.push(`case_${i}_likert`); + for (let i = 1; i <= 15; i++) cols.push(`case_${i}_order`); + for (let i = 1; i <= 15; i++) cols.push(`case_${i}_ms`); + cols.push('ahp_w_stability','ahp_w_revenue','ahp_w_capital','ahp_w_plan','ahp_w_social'); + cols.push('ahp_lambda_max','ahp_CI','ahp_CR','ahp_cr_warning'); + for (let i = 1; i <= 15; i++) cols.push(`match_case_${i}`); + cols.push('post_q1','post_q2','post_q3','post_q4','open_response'); + return cols.join(','); +} + +function buildCSVRow(payload) { + const fields = []; + + fields.push(payload.meta.timestamp, csvEscape(payload.meta.country), csvEscape(payload.meta.age), csvEscape(payload.meta.gender)); + + // Case judgments ordered by case_id 1-15 + const byCase = {}; + (payload.intuitive_judgments || []).forEach(j => { byCase[j.case_id] = j; }); + for (let i = 1; i <= 15; i++) fields.push(byCase[i]?.judgment ?? ''); + for (let i = 1; i <= 15; i++) fields.push(byCase[i]?.presented_order ?? ''); + for (let i = 1; i <= 15; i++) fields.push(byCase[i]?.response_time_ms ?? ''); + + // AHP + const ahp = payload.ahp || {}; + (ahp.weights || [0,0,0,0,0]).forEach(w => fields.push(Number(w).toFixed(4))); + fields.push(Number(ahp.lambda_max).toFixed(4)); + fields.push(Number(ahp.CI).toFixed(4)); + fields.push(Number(ahp.CR).toFixed(4)); + fields.push(ahp.cr_warning_shown ? 1 : 0); + + // Match by case_id + const matchByCase = {}; + (payload.comparison || []).forEach(c => { matchByCase[c.case_id] = c.match; }); + for (let i = 1; i <= 15; i++) { + const m = matchByCase[i]; + fields.push(m === true ? 1 : m === false ? 0 : ''); + } + + // Post survey + const ps = payload.post_survey || {}; + fields.push(ps.q1 ?? '', ps.q2 ?? '', ps.q3 ?? '', ps.q4 ?? ''); + fields.push(csvEscape(ps.open_response ?? '')); + + return fields.join(','); +} + +// ────────────────────────────────────────── +// Routes +// ────────────────────────────────────────── +app.use(express.json({ limit: '1mb' })); +app.use(express.static(path.join(BASE, 'public'))); + +app.post('/api/submit', (req, res) => { + try { + // Save JSON + const data = JSON.parse(fs.readFileSync(DATA_FILE, 'utf8')); + const submission = { ...req.body, server_timestamp: Date.now() }; + data.push(submission); + fs.writeFileSync(DATA_FILE, JSON.stringify(data, null, 2)); + + // Append CSV row + const row = buildCSVRow(submission); + fs.appendFileSync(CSV_FILE, row + '\n'); + + res.json({ success: true, id: data.length }); + } catch (err) { + console.error('Submit error:', err); + res.status(500).json({ error: err.message }); + } +}); + +app.get('/api/responses', (req, res) => { + try { + res.json(JSON.parse(fs.readFileSync(DATA_FILE, 'utf8'))); + } catch (err) { + res.status(500).json({ error: err.message }); + } +}); + +const PORT = process.env.PORT || 3000; +app.listen(PORT, () => console.log(`Server running at http://localhost:${PORT}`)); diff --git a/src/styles/main.scss b/src/styles/main.scss new file mode 100644 index 0000000..e5e2792 --- /dev/null +++ b/src/styles/main.scss @@ -0,0 +1,956 @@ +// ────────────────────────────────────────── +// Variables +// ────────────────────────────────────────── +$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; + +$radius: 12px; +$radius-sm: 8px; +$shadow: 0 1px 4px rgba(0,0,0,.10), 0 4px 16px rgba(0,0,0,.06); + +// ────────────────────────────────────────── +// Reset & Base +// ────────────────────────────────────────── +*, *::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: $bg; + color: $text; + line-height: 1.6; + min-height: 100vh; +} + +// ────────────────────────────────────────── +// Layout +// ────────────────────────────────────────── +#app { display: flex; flex-direction: column; min-height: 100vh; } + +#app-header { + background: $card-bg; + border-bottom: 1px solid $border; + 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: $primary; + flex: 1; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.lang-btn { + font-size: .78rem; + padding: .3rem .8rem; + border: 1px solid $border; + border-radius: 20px; + background: transparent; + cursor: pointer; + color: $muted; + transition: all .15s; + white-space: nowrap; + &:hover { border-color: $primary; color: $primary; } +} + +#app-main { + flex: 1; + padding: 2rem 1rem 4rem; +} + +.step-wrap { + max-width: 720px; + margin: 0 auto; +} + +// ────────────────────────────────────────── +// Step Progress Indicator +// ────────────────────────────────────────── +.step-progress { + display: flex; + align-items: center; + gap: 0; + margin-bottom: 2rem; + + .step-dot { + display: flex; + flex-direction: column; + align-items: center; + gap: .25rem; + flex: 1; + position: relative; + + &:not(:last-child)::after { + content: ''; + position: absolute; + top: 14px; + left: calc(50% + 14px); + right: calc(-50% + 14px); + height: 2px; + background: $border; + transition: background .3s; + } + + &.done::after { background: $primary; } + + .dot-circle { + width: 28px; + height: 28px; + border-radius: 50%; + border: 2px solid $border; + background: $card-bg; + display: flex; + align-items: center; + justify-content: center; + font-size: .72rem; + font-weight: 700; + color: $muted; + transition: all .3s; + position: relative; + z-index: 1; + } + + .dot-label { + font-size: .68rem; + color: $muted; + text-align: center; + 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; } + } +} + +// ────────────────────────────────────────── +// Cards +// ────────────────────────────────────────── +.card { + background: $card-bg; + border-radius: $radius; + box-shadow: $shadow; + padding: 2rem; + margin-bottom: 1.5rem; + + &.card-sm { padding: 1.5rem; } +} + +.section-title { + font-size: 1.3rem; + font-weight: 700; + margin-bottom: .4rem; +} + +.section-subtitle { + color: $muted; + font-size: .9rem; + margin-bottom: 1.5rem; + line-height: 1.5; +} + +// ────────────────────────────────────────── +// Forms (Step 1) +// ────────────────────────────────────────── +.form-group { + margin-bottom: 1.2rem; + + label { + display: block; + font-size: .875rem; + font-weight: 600; + margin-bottom: .5rem; + color: $text-sm; + } + + input[type="text"], + input[type="number"] { + width: 100%; + padding: .6rem .9rem; + border: 1.5px solid $border; + border-radius: $radius-sm; + font-size: .9rem; + outline: none; + transition: border-color .2s; + font-family: inherit; + &:focus { border-color: $primary; } + &::placeholder { color: #9CA3AF; } + } +} + +.form-select { + width: 100%; + padding: .6rem .9rem; + border: 1.5px solid $border; + border-radius: $radius-sm; + font-size: .9rem; + font-family: inherit; + outline: none; + background: #fff; + color: $text; + cursor: pointer; + transition: border-color .2s; + appearance: none; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%236B7280' stroke-width='1.5' fill='none' stroke-linecap='round'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right .9rem center; + padding-right: 2.5rem; + &:focus { border-color: $primary; } +} + +.radio-label { + display: flex; + align-items: center; + gap: .4rem; + padding: .5rem 1rem; + border: 1.5px solid $border; + border-radius: 24px; + cursor: pointer; + font-size: .875rem; + color: $text-sm; + transition: all .15s; + 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; } +} + +.radio-group { + display: flex; + flex-wrap: wrap; + gap: .6rem; + + label { + display: flex; + align-items: center; + gap: .4rem; + padding: .5rem 1rem; + border: 1.5px solid $border; + border-radius: 24px; + cursor: pointer; + font-size: .875rem; + font-weight: normal; + color: $text-sm; + transition: all .15s; + 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; } + } +} + +.notice-box { + background: #EFF6FF; + border: 1px solid #BFDBFE; + border-radius: $radius-sm; + padding: 1rem 1.2rem; + margin-bottom: 1.5rem; + + p { + font-size: .85rem; + color: #1E40AF; + line-height: 1.6; + &::before { content: '• '; } + + p { margin-top: .3rem; } + } +} + +// ────────────────────────────────────────── +// Buttons +// ────────────────────────────────────────── +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: .4rem; + padding: .65rem 1.6rem; + border-radius: $radius-sm; + border: none; + font-size: .9rem; + font-weight: 600; + cursor: pointer; + transition: all .15s; + text-decoration: none; + + &:disabled { opacity: .45; 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); } + } + + &.btn-outline { + background: transparent; + color: $primary; + border: 1.5px solid $primary; + &:hover { background: rgba($primary,.06); } + } + + &.btn-gray { + background: #F3F4F6; + color: $text-sm; + border: 1.5px solid $border; + &:not(:disabled):hover { background: $border; } + } + + &.btn-lg { padding: .85rem 2.2rem; font-size: 1rem; border-radius: 10px; } + &.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-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; } + } + + &.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-row { + display: flex; + gap: .75rem; + margin-top: 1.2rem; + + &.center { justify-content: center; } + &.end { justify-content: flex-end; } +} + +// ────────────────────────────────────────── +// Case Card (Step 2) +// ────────────────────────────────────────── +.progress-bar-wrap { + margin-bottom: 1.5rem; + .progress-label { + display: flex; + justify-content: space-between; + font-size: .82rem; + color: $muted; + margin-bottom: .4rem; + font-weight: 600; + } + .progress-track { + height: 6px; + background: $border; + border-radius: 3px; + overflow: hidden; + .progress-fill { + height: 100%; + background: $primary; + 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 $border; + + .team-badge { + font-size: 1.4rem; + font-weight: 800; + color: $primary; + background: rgba($primary,.08); + border-radius: $radius-sm; + padding: .3rem .8rem; + } + + .industry-tag { + font-size: .82rem; + background: #F3F4F6; + border: 1px solid $border; + border-radius: 20px; + padding: .25rem .75rem; + color: $muted; + font-weight: 500; + } + } +} + +.var-row { + display: flex; + align-items: center; + gap: .75rem; + margin-bottom: .85rem; + + .var-name { + font-size: .82rem; + font-weight: 600; + color: $text-sm; + width: 140px; + flex-shrink: 0; + flex-grow: 0; + } + + .var-bar-track { + flex: 1; + height: 10px; + background: #F3F4F6; + border-radius: 5px; + overflow: hidden; + + .var-bar-fill { + height: 100%; + border-radius: 5px; + transition: width .5s ease; + } + } + + .var-score { + font-size: .85rem; + font-weight: 700; + min-width: 32px; + text-align: right; + color: $text; + } +} + +// 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); } + +.judgment-section { + margin-top: 1.5rem; + padding-top: 1.2rem; + border-top: 1px solid $border; + + .judgment-label { + font-size: .875rem; + font-weight: 600; + margin-bottom: .75rem; + color: $text-sm; + } +} + +.confidence-section { + margin-top: 1.2rem; + padding: 1rem 1.2rem; + background: #F9FAFB; + border-radius: $radius-sm; + border: 1px solid $border; + + .confidence-label { + font-size: .82rem; + font-weight: 600; + color: $text-sm; + margin-bottom: .6rem; + display: flex; + justify-content: space-between; + } + + .confidence-slider { + width: 100%; + appearance: none; + height: 6px; + border-radius: 3px; + background: $border; + outline: none; + cursor: pointer; + + &::-webkit-slider-thumb { + appearance: none; + width: 20px; + height: 20px; + border-radius: 50%; + background: $primary; + cursor: pointer; + border: 2px solid #fff; + box-shadow: 0 1px 4px rgba(0,0,0,.2); + } + } + + .confidence-labels { + display: flex; + justify-content: space-between; + margin-top: .4rem; + .cl { font-size: .7rem; color: $muted; } + } +} + +// ────────────────────────────────────────── +// Judgment Likert (Step 2) +// ────────────────────────────────────────── +.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 $border; + border-radius: $radius-sm; + background: $card-bg; + cursor: pointer; + transition: all .15s; + + .jl-num { + font-size: 1.1rem; + font-weight: 800; + color: $muted; + } + + .jl-label { + font-size: .65rem; + color: $muted; + text-align: center; + line-height: 1.3; + } + + &:hover { border-color: $primary; } + + &.active { + border-color: $primary; + background: rgba($primary, .07); + .jl-num { color: $primary; } + .jl-label { color: $primary; } + } + + // 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; } } +} + +// ────────────────────────────────────────── +// AHP (Step 3) +// ────────────────────────────────────────── +.ahp-pair-card { + .pair-title { + font-size: .85rem; + color: $muted; + margin-bottom: 1.2rem; + font-weight: 500; + } + + .pair-criteria { + display: flex; + align-items: stretch; + justify-content: space-between; + gap: 1rem; + margin-bottom: 1.2rem; + min-height: 4rem; + + .crit { + flex: 1; + display: flex; + align-items: center; + justify-content: flex-end; + text-align: right; + font-size: 1rem; + font-weight: 700; + color: $text; + padding: .6rem; + border-radius: $radius-sm; + transition: background .2s; + word-break: break-word; + + &.crit-left { justify-content: flex-end; text-align: right; } + &.crit-right { justify-content: flex-start; text-align: left; } + + &.crit-active-left { color: $primary; background: rgba($primary,.07); } + &.crit-active-right { color: $success; background: rgba($success,.07); } + } + + .vs-badge { + font-size: .8rem; + font-weight: 700; + color: $muted; + background: $bg; + border: 1px solid $border; + border-radius: 20px; + padding: .25rem .6rem; + flex-shrink: 0; + align-self: center; + } + } +} + +.ahp-slider-wrap { + padding: .5rem 0; +} + +.ahp-scale-labels { + 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; + + span { + font-size: .7rem; + color: $muted; + font-weight: 600; + text-align: center; + min-width: 0; + } + + .sc-mid { font-weight: 800; color: $text-sm; font-size: .75rem; } +} + +.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; + cursor: pointer; + + &::-webkit-slider-thumb { + appearance: none; + width: 24px; + height: 24px; + border-radius: 50%; + background: #fff; + border: 3px solid $primary; + 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: $radius-sm; + font-size: .875rem; + font-weight: 600; + color: #0369A1; + 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) +// ────────────────────────────────────────── +.weight-chart { + margin-top: .5rem; + + .wc-row { + display: flex; + align-items: center; + gap: .75rem; + margin-bottom: .7rem; + + .wc-label { + font-size: .82rem; + font-weight: 600; + color: $text-sm; + min-width: 110px; + flex-shrink: 0; + } + + .wc-track { + flex: 1; + height: 18px; + background: #F3F4F6; + border-radius: 9px; + overflow: hidden; + + .wc-fill { + height: 100%; + background: linear-gradient(90deg, $primary, #60A5FA); + border-radius: 9px; + transition: width .6s ease; + } + } + + .wc-pct { font-size: .82rem; font-weight: 700; color: $primary; min-width: 38px; text-align: right; } + } +} + +.stats-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); + gap: 1rem; + margin-top: 1rem; + + .stat-box { + background: $bg; + border: 1px solid $border; + border-radius: $radius-sm; + padding: 1rem; + text-align: center; + + .stat-val { font-size: 1.6rem; font-weight: 800; color: $primary; } + .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; } + } +} + +.comparison-table-wrap { + overflow-x: auto; + margin-top: 1rem; + border-radius: $radius-sm; + border: 1px solid $border; + + table { + width: 100%; + border-collapse: collapse; + font-size: .82rem; + + th { + background: #F9FAFB; + padding: .6rem .8rem; + text-align: center; + font-weight: 700; + border-bottom: 1px solid $border; + white-space: nowrap; + color: $text-sm; + } + + td { + padding: .55rem .8rem; + text-align: center; + border-bottom: 1px solid $border; + + &:first-child { font-weight: 700; } + } + + tr:last-child td { border-bottom: none; } + + tr.mismatch td { background: #FEF2F2; color: $danger; } + tr.mismatch td:first-child { color: $danger; } + } +} + +.badge { + display: inline-block; + padding: .2rem .55rem; + border-radius: 12px; + font-size: .75rem; + font-weight: 700; + line-height: 1; + + &.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; } +} + +.neutral-row td { background: #FAFAF0; color: $warning; } + +// ────────────────────────────────────────── +// Post Survey (Step 5) +// ────────────────────────────────────────── +.likert-group { + margin-bottom: 1.5rem; + + .likert-q { + font-size: .9rem; + font-weight: 600; + color: $text; + margin-bottom: .75rem; + line-height: 1.5; + } + + .likert-options { + display: flex; + gap: .5rem; + + label { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + gap: .3rem; + cursor: pointer; + + input[type="radio"] { display: none; } + + .likert-btn { + width: 100%; + padding: .55rem .2rem; + border: 2px solid $border; + border-radius: $radius-sm; + text-align: center; + font-size: .9rem; + font-weight: 700; + color: $muted; + transition: all .15s; + background: $card-bg; + } + + .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; } + } + } +} + +.open-textarea { + width: 100%; + padding: .8rem 1rem; + border: 1.5px solid $border; + border-radius: $radius-sm; + font-size: .875rem; + font-family: inherit; + resize: vertical; + min-height: 100px; + outline: none; + transition: border-color .2s; + &:focus { border-color: $primary; } +} + +// ────────────────────────────────────────── +// Complete Screen +// ────────────────────────────────────────── +.complete-screen { + 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-desc { color: $muted; font-size: .9rem; margin-bottom: 2rem; line-height: 1.7; } +} + +.json-output { + background: #1E293B; + color: #94A3B8; + border-radius: $radius-sm; + 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; +} + +// ────────────────────────────────────────── +// Utilities +// ────────────────────────────────────────── +.divider { + height: 1px; + background: $border; + margin: 1.5rem 0; +} + +.text-muted { color: $muted; 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: $primary; + font-weight: 700; +} + +// ────────────────────────────────────────── +// Responsive +// ────────────────────────────────────────── +@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 { width: 100px; font-size: .76rem; } + + .ahp-pair-card .pair-criteria { flex-direction: column; gap: .5rem; min-height: 0; } + + .ahp-pair-card .pair-criteria .crit { text-align: center !important; justify-content: 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; } +} diff --git a/설문 세트.xlsx b/설문 세트.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..69cb6c3959a91ecbe8bdcbe06b1a1786af161ab4 GIT binary patch literal 10181 zcmeHN1zTLn(jJ0qaCdhnI0P7+;O-vW9RdLoTn7&lLI_E4hrunlO9<{^aCiGA+1
    -olRlB@+lJE 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 literal 0 HcmV?d00001