diff --git a/.gitignore b/.gitignore index 1e040dc..abe6f4b 100644 --- a/.gitignore +++ b/.gitignore @@ -110,4 +110,6 @@ TODO.txt diff *rinjae* -*gf63* \ No newline at end of file +*gf63* +*obsidian* +design-backup \ No newline at end of file diff --git a/Jenkinsfile.deploy b/Jenkinsfile.deploy index 0dd45e7..930f2ca 100644 --- a/Jenkinsfile.deploy +++ b/Jenkinsfile.deploy @@ -1,180 +1,172 @@ pipeline { - agent { label 'djb-vm' } - - parameters { - string(name: 'BRANCH', defaultValue: 'master', description: 'Branch to deploy') - } + agent none options { timestamps() disableConcurrentBuilds() + skipDefaultCheckout() // stage별 agent의 자동 SCM checkout 방지 (Deploy 노드는 git 접근 불필요, unstash로만 WAR 수신) buildDiscarder(logRotator(numToKeepStr: '20', artifactNumToKeepStr: '20')) } environment { - JAVA_HOME = '/apps/opts/jdk8' - JAVA_HOME_TOMCAT = '/apps/opts/jdk17' - GRADLE_HOME = '/apps/opts/gradle-8.7' - GRADLE_USER_HOME = '/apps/opts/gradle-home' - NODE_HOME = '/apps/opts/node-v24' - PATH = "/apps/opts/jdk8/bin:/apps/opts/gradle-8.7/bin:/apps/opts/node-v24/bin:/apps/opts/bin:${env.PATH}" GIT_SSH_COMMAND = 'ssh -o StrictHostKeyChecking=accept-new' - CATALINA_BASE = '/prod/eapim/devportal' - CATALINA_HOME = '/prod/eapim/apache-tomcat-9.0.116' - CATALINA_PID = '/prod/eapim/devportal/temp/catalina.pid' - DEPLOY_HTTP_PORT = '39130' + WEBHOOK_URL = 'http://172.30.1.50:18000/api/hooks/01ba51b01d3c4c98ae8f3183a23a84ed713197857d024b5da4f303458195eb32' } stages { - stage('Checkout') { - steps { - checkout([ - $class: 'GitSCM', - branches: [[name: "*/${params.BRANCH}"]], - userRemoteConfigs: [[url: 'ssh://git@172.30.1.50:2222/djb-eapim/eapim-portal.git']] - ]) + stage('Build (djb-vm)') { + agent { label 'djb-vm' } + environment { + JAVA_HOME = '/apps/opts/jdk8' + GRADLE_HOME = '/apps/opts/gradle-8.7' + GRADLE_USER_HOME = '/apps/opts/gradle-home' + NODE_HOME = '/apps/opts/node-v24' + PATH = "/apps/opts/jdk8/bin:/apps/opts/gradle-8.7/bin:/apps/opts/node-v24/bin:/apps/opts/bin:${env.PATH}" } - } + stages { + stage('Checkout') { + steps { checkout scm } + } + stage('Checkout dependencies') { + steps { + sh ''' + set -eu + cd "$WORKSPACE/.." + if [ ! -d elink-portal-common/.git ]; then + rm -rf elink-portal-common + git clone --depth=1 --branch master \ + ssh://git@172.30.1.50:2222/djb-eapim/elink-portal-common.git elink-portal-common + else + git -C elink-portal-common fetch --depth=1 origin master + git -C elink-portal-common reset --hard origin/master + git -C elink-portal-common clean -fdx + fi + mkdir -p eapim-online + if [ ! -d eapim-online/elink-online-core-jpa/.git ]; then + rm -rf eapim-online/elink-online-core-jpa + git clone --depth=1 --branch master \ + ssh://git@172.30.1.50:2222/djb-eapim/elink-online-core-jpa.git eapim-online/elink-online-core-jpa + else + git -C eapim-online/elink-online-core-jpa fetch --depth=1 origin master + git -C eapim-online/elink-online-core-jpa reset --hard origin/master + git -C eapim-online/elink-online-core-jpa clean -fdx + fi + ''' + } + } + stage('Verify toolchain') { + steps { sh 'set -eu; java -version; gradle --version' } + } + stage('Test') { + steps { sh 'gradle clean test --no-daemon -Pprofile=weblogic' } + post { + always { + junit allowEmptyResults: true, testResults: 'build/test-results/test/*.xml' + archiveArtifacts allowEmptyArchive: true, artifacts: 'build/reports/tests/test/**,build/test-results/test/**' + } + } + } + stage('Build WAR') { + steps { sh 'gradle build -x test --no-daemon -Pprofile=weblogic' } + post { + success { + sh ''' + set -eu + cd build/libs + sha256sum eapim-portal.war > eapim-portal.war.sha256 + ''' + archiveArtifacts artifacts: 'build/libs/eapim-portal.war,build/libs/eapim-portal.war.sha256', fingerprint: true + stash name: 'war', includes: 'build/libs/eapim-portal.war' + } + } + } - stage('Checkout dependencies') { - steps { - sh ''' - set -eu - cd "$WORKSPACE/.." - - if [ ! -d elink-portal-common/.git ]; then - git clone --depth=1 --branch master \ - ssh://git@172.30.1.50:2222/djb-eapim/elink-portal-common.git \ - elink-portal-common - else - git -C elink-portal-common fetch --depth=1 origin master - git -C elink-portal-common reset --hard origin/master - git -C elink-portal-common clean -fdx - fi - - mkdir -p eapim-online - if [ ! -d eapim-online/elink-online-core-jpa/.git ]; then - git clone --depth=1 --branch master \ - ssh://git@172.30.1.50:2222/djb-eapim/elink-online-core-jpa.git \ - eapim-online/elink-online-core-jpa - else - git -C eapim-online/elink-online-core-jpa fetch --depth=1 origin master - git -C eapim-online/elink-online-core-jpa reset --hard origin/master - git -C eapim-online/elink-online-core-jpa clean -fdx - fi - ''' - } - } - - stage('Verify toolchain') { - steps { - sh ''' - set -eu - java -version - gradle --version - ''' - } - } - - stage('Build WAR') { - steps { - sh 'gradle clean build -x test --no-daemon' - } - post { - success { - sh ''' - set -eu - cd build/libs - sha1sum eapim-portal.war eapim-portal-boot.war > SHA1SUMS - sha256sum eapim-portal.war eapim-portal-boot.war > SHA256SUMS - ''' - archiveArtifacts artifacts: 'build/libs/eapim-portal.war,build/libs/eapim-portal-boot.war,build/libs/SHA1SUMS,build/libs/SHA256SUMS', fingerprint: true + // SBOM(CycloneDX) -> xlsx. 산출물 eapim-portal-sbom.xlsx 를 아티팩트로 보관. + // 실패해도 배포는 진행하도록 UNSTABLE 로만 표시한다. + stage('SBOM') { + steps { + catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') { + sh 'gradle sbomXlsx --no-daemon -Pprofile=weblogic' + } + } + post { + always { + archiveArtifacts allowEmptyArchive: true, artifacts: 'build/reports/sbom/*.xlsx', fingerprint: true + } + } } } } - stage('Stop Tomcat') { - steps { + stage('Deploy (weblogic)') { + agent { label 'weblogic' } + environment { + JENKINS_NODE_COOKIE = 'dontKillMe' // background weblogic를 빌드 종료 시 죽이지 않게 + WL_HOME = '/app/eapim/devportal' + WL_DEPLOY_DIR = '/app/eapim/devportal' + WL_WAR_NAME = 'eapim-portal.war' + WL_HTTP_PORT = '39130' + WL_NOHUP = '/logs/weblogic/domains/eapimDomain/nohup.devSvr11.out' + } + stages { + stage('Stop WebLogic') { + steps { + sh '"$WL_HOME/stopDev11.sh"' // 동기: 완전 종료까지 블록, 미기동 시에도 안전 + } + } + stage('Deploy WAR') { + steps { + unstash 'war' + sh ''' + set -eu + cp build/libs/eapim-portal.war "$WL_DEPLOY_DIR/$WL_WAR_NAME" + ls -la "$WL_DEPLOY_DIR" + ''' + } + } + stage('Start WebLogic and readiness') { + steps { + sh ''' + set -eu + "$WL_HOME/startDev11.sh" # nohup & 로 백그라운드 기동, 즉시 리턴 + + DEADLINE=$(($(date +%s) + 300)) + STATUS=000 + while [ "$(date +%s)" -lt "$DEADLINE" ]; do + STATUS=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 3 \ + "http://localhost:$WL_HTTP_PORT/health/ready" 2>/dev/null || echo "000") + [ "$STATUS" = "200" ] && { echo "Readiness OK (/health/ready)"; break; } + sleep 3 + done + + if [ "$STATUS" != "200" ]; then + echo "Readiness failed within 300s, last HTTP=$STATUS" + [ -f "$WL_NOHUP" ] && tail -120 "$WL_NOHUP" + exit 1 + fi + ''' + } + } + } + } + } + + post { + success { + node('weblogic') { sh ''' set +e - systemctl --user stop eapim-portal 2>/dev/null - - if [ -f "$CATALINA_PID" ] && kill -0 "$(cat "$CATALINA_PID")" 2>/dev/null; then - JAVA_HOME="$JAVA_HOME_TOMCAT" "$CATALINA_HOME/bin/shutdown.sh" 30 -force || true - for i in $(seq 1 30); do - [ -f "$CATALINA_PID" ] && kill -0 "$(cat "$CATALINA_PID")" 2>/dev/null || break - sleep 1 - done - fi - rm -f "$CATALINA_PID" + payload=$(printf '{"text":"[%s #%s](%s) SUCCESS"}' "$JOB_NAME" "$BUILD_NUMBER" "$BUILD_URL") + curl -sS --fail --max-time 5 -H 'Content-Type: application/json' -X POST --data "$payload" "$WEBHOOK_URL" >/dev/null || echo "Webhook failed" ''' } } - - stage('Deploy ROOT.war') { - steps { + failure { + node('weblogic') { sh ''' - set -eu - DEPLOY_DIR="$CATALINA_BASE/webapps" - rm -rf "$DEPLOY_DIR/ROOT" "$DEPLOY_DIR/ROOT.war" - cp build/libs/eapim-portal.war "$DEPLOY_DIR/ROOT.war" - ls -la "$DEPLOY_DIR" - ''' - } - } - - stage('Start Tomcat and readiness') { - steps { - sh ''' - set -eu - LOG="$CATALINA_BASE/logs/catalina.$(date +%Y-%m-%d).log" - BEFORE=0 - [ -f "$LOG" ] && BEFORE=$(stat -c %s "$LOG") - - systemctl --user start eapim-portal - - DEADLINE=$(($(date +%s) + 120)) - LIVE_OK=0 - STATUS=000 - - while [ "$(date +%s)" -lt "$DEADLINE" ]; do - if [ "$LIVE_OK" = "0" ]; then - LIVE=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 3 \ - "http://localhost:$DEPLOY_HTTP_PORT/health" 2>/dev/null || echo "000") - if [ "$LIVE" = "200" ]; then - echo "Liveness OK (/health)" - LIVE_OK=1 - fi - fi - - if [ "$LIVE_OK" = "1" ]; then - STATUS=$(curl -sS -o /tmp/eapim-ready-body.json -w '%{http_code}' --max-time 3 \ - "http://localhost:$DEPLOY_HTTP_PORT/health/ready" 2>/dev/null || echo "000") - if [ "$STATUS" = "200" ]; then - echo "Readiness OK (/health/ready)" - cat /tmp/eapim-ready-body.json - echo - break - fi - fi - - if [ -f "$LOG" ] && tail -c +$((BEFORE+1)) "$LOG" | grep -qE "Application listener .* Exception|SEVERE.*startup.Catalina"; then - echo "Startup error detected in log" - tail -c +$((BEFORE+1)) "$LOG" | tail -80 - exit 1 - fi - sleep 2 - done - - if [ "$STATUS" != "200" ]; then - echo "Readiness probe failed within 120s, last HTTP status: $STATUS" - [ -f /tmp/eapim-ready-body.json ] && cat /tmp/eapim-ready-body.json && echo - [ -f "$LOG" ] && tail -c +$((BEFORE+1)) "$LOG" | tail -100 - exit 1 - fi - - echo "--- key boot log lines ---" - tail -c +$((BEFORE+1)) "$LOG" | grep -E "Server startup|Deployment of web" | head -10 || true + set +e + payload=$(printf '{"text":"[%s #%s](%s) FAILURE"}' "$JOB_NAME" "$BUILD_NUMBER" "$BUILD_URL") + curl -sS --fail --max-time 5 -H 'Content-Type: application/json' -X POST --data "$payload" "$WEBHOOK_URL" >/dev/null || echo "Webhook failed" ''' } } diff --git a/Jenkinsfile.test-build b/Jenkinsfile.test-build index e1d2d4c..2935c2e 100644 --- a/Jenkinsfile.test-build +++ b/Jenkinsfile.test-build @@ -91,10 +91,28 @@ pipeline { sh ''' set -eu cd build/libs - sha1sum eapim-portal.war eapim-portal-boot.war > SHA1SUMS - sha256sum eapim-portal.war eapim-portal-boot.war > SHA256SUMS + for f in eapim-portal.war eapim-portal-boot.war; do + sha1sum "$f" > "$f.sha1" + sha256sum "$f" > "$f.sha256" + md5sum "$f" > "$f.md5" + done ''' - archiveArtifacts artifacts: 'build/libs/eapim-portal.war,build/libs/eapim-portal-boot.war,build/libs/SHA1SUMS,build/libs/SHA256SUMS', fingerprint: true + archiveArtifacts artifacts: 'build/libs/eapim-portal.war,build/libs/eapim-portal-boot.war,build/libs/eapim-portal.war.sha1,build/libs/eapim-portal.war.sha256,build/libs/eapim-portal.war.md5,build/libs/eapim-portal-boot.war.sha1,build/libs/eapim-portal-boot.war.sha256,build/libs/eapim-portal-boot.war.md5', fingerprint: true + } + } + } + + // SBOM(CycloneDX) -> xlsx. 산출물 eapim-portal-sbom.xlsx 를 아티팩트로 보관. + // 실패해도 빌드는 진행하도록 UNSTABLE 로만 표시한다. + stage('SBOM') { + steps { + catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') { + sh 'gradle sbomXlsx --no-daemon' + } + } + post { + always { + archiveArtifacts allowEmptyArchive: true, artifacts: 'build/reports/sbom/*.xlsx', fingerprint: true } } } diff --git a/README.md b/README.md index b96b873..88c3868 100644 --- a/README.md +++ b/README.md @@ -521,6 +521,7 @@ ls -lh src/main/resources/static/css/main.min.css # minified ## 문서 - **개발환경 준비 사항**: [`djb-docs/개발환경-준비-사항.md`](djb-docs/개발환경-준비-사항.md) — JDK·Gradle·Node.js·SASS 설치 가이드 +- **메뉴 관리 개발 가이드**: [`readme-docs/메뉴-관리-개발-가이드.md`](readme-docs/메뉴-관리-개발-가이드.md) — menu.yml/roles.yml 스키마·시딩 규칙·캐시 리로드·admin 포탈메뉴관리 연동 - **프로젝트 상세 지침**: `CLAUDE.md` (한글) - **사용자 가이드**: `개발자포탈.md` (한글) - **빌드 스크립트**: `build-gf63.sh`, `deploy_portal.sh` diff --git a/build-gf63.sh b/build-gf63.sh deleted file mode 100644 index 9a8060e..0000000 --- a/build-gf63.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -export JAVA_HOME=/c/eactive/apps/jdk1.8.0_441 -export GRADLE_HOME=/c/eactive/apps/gradle-8.7 -export GRADLE_USER_HOME=/c/eactive/workspaces/gradle-user-home-kjbank - -export PATH=$JAVA_HOME/bin:$GRADLE_HOME/bin:$PATH - - -echo "========================" -java -version -javac -version -echo "========================" -gradle --version -echo "========================" - -gradle clean build -x test --no-daemon \ No newline at end of file diff --git a/build.gradle b/build.gradle index 26be4b9..3f1ad5b 100644 --- a/build.gradle +++ b/build.gradle @@ -3,6 +3,7 @@ plugins { id 'war' id 'eclipse' id 'idea' + id 'org.cyclonedx.bom' version '3.2.4' id 'org.springframework.boot' version '2.7.18' id 'io.spring.dependency-management' version '1.1.3' } @@ -23,15 +24,36 @@ allprojects { } } +// 로컬 전용 라이브러리 — WebLogic 배포 산출물(war/bootWar)에서 제외한다. +// 정보보호 점검에서 Actuator/모니터링 라이브러리가 배포본에 실려 있으면 불필요하게 탐지되므로 +// 로컬(bootRun/IDE)에서만 classpath 에 오르게 한다. 제외 로직은 아래 war/bootWar 블록. +// +// ※ Spring Boot 의 developmentOnly 를 쓰지 않는 이유 +// developmentOnly 는 bootJar/bootWar 에서만 제외된다. 표준 war task 는 runtimeClasspath 를 +// 그대로 쓰므로 산출물에 실린다 (실측: devtools 가 eapim-portal.war 에 포함되어 있었음). +// ※ 별도 configuration(localOnly)을 쓰지 않는 이유 +// runtimeClasspath 에서 빠지면 IntelliJ 가 모듈 classpath 를 구성할 때도 빠져 +// IDE 로 기동할 때 Actuator/SBA 가 동작하지 않는다. +def localOnlyLibPrefixes = [ + 'spring-boot-devtools', + 'spring-boot-starter-actuator', + 'spring-boot-actuator', // spring-boot-actuator, -autoconfigure 모두 매칭 + 'micrometer-', // actuator 전용(runtimeClasspath 상 다른 출처 없음 — 확인함) + 'spring-boot-admin-', +] + dependencies { annotationProcessor "org.projectlombok:lombok:1.18.28" annotationProcessor "org.projectlombok:lombok-mapstruct-binding:0.2.0", "org.mapstruct:mapstruct-processor:1.5.5.Final" implementation 'com.eactive.elink.common:elink-common-data:4.5.5' -// implementation 'io.swagger.core.v3:swagger-core:2.2.25' -// implementation 'io.swagger.parser.v3:swagger-parser:2.1.23' - implementation fileTree(dir: 'libs/swagger', include: ['*.jar']) + // OpenAPI 3.0/3.1 spec 지원 (Nexus/Maven 해석). 기존 libs/swagger fileTree 대체. + // swagger 최신 릴리스(2.2.52/2.1.45)도 SpecVersion 은 V30/V31 까지만 — OpenAPI 3.2 는 + // 아직 swagger-core/parser 어느 버전도 미지원. 지원 추가 시 버전만 상향하면 됨. JDK8 호환. + implementation 'io.swagger.core.v3:swagger-core:2.2.52' // io.swagger.v3.oas.models.*, io.swagger.v3.core.util.Json + implementation 'io.swagger.parser.v3:swagger-parser:2.1.45' // io.swagger.parser.OpenAPIParser, io.swagger.v3.parser.* + implementation 'io.swagger:swagger-annotations:1.6.14' // io.swagger.annotations.ApiModelProperty (CommissionSearch) implementation project(':elink-online-core-jpa') implementation project(':elink-portal-common') @@ -41,6 +63,23 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-jta-atomikos' implementation('org.springframework.boot:spring-boot-starter-web') implementation('org.springframework.boot:spring-boot-starter-validation') + + // ↓ 로컬 전용. war/bootWar 산출물에서는 localOnlyLibPrefixes 로 제외된다. + // runtimeOnly 인 이유: compileClasspath 에서 빠지므로 자바 코드가 이 API 를 참조하면 + // 컴파일 단계에서 막힌다. implementation 이면 참조가 컴파일에 통과해버리고, + // 배포본(actuator/SBA 제외됨)에서 NoClassDefFoundError 로 터진다. + // IntelliJ / bootRun 은 runtimeClasspath 기준이라 로컬 기동에는 정상 포함된다. + // 설정(application-local*.yml)으로만 사용한다. + runtimeOnly('org.springframework.boot:spring-boot-starter-actuator') + // Spring Boot Admin client. 2.7.16 = Spring Boot 2.7.x 대응 마지막 계열(Java 8 호환). + runtimeOnly('de.codecentric:spring-boot-admin-starter-client:2.7.16') + // 위 runtimeOnly 원칙의 유일한 예외. InterceptorsEndpointConfig 가 actuator API + // (@Endpoint / InfoContributor)를 참조해야 하므로 compileClasspath 에만 올린다. + // compileOnly 는 runtimeClasspath 에 포함되지 않으므로 war/bootWar 산출물에는 영향이 없다. + // 해당 클래스는 @ConditionalOnClass 로 actuator 부재 시 로드되지 않으니 + // 배포본(actuator 제외)에서 NoClassDefFoundError 가 나지 않는다. + compileOnly('org.springframework.boot:spring-boot-actuator') + implementation group: 'javax.xml.bind', name: 'jaxb-api', version: '2.3.0' implementation group: 'com.fasterxml.woodstox', name: 'woodstox-core', version: '6.5.1' @@ -76,9 +115,10 @@ dependencies { // exclude group: 'commons-collections', module: 'commons-collections' } implementation 'org.mapstruct:mapstruct:1.5.5.Final' - implementation 'com.fasterxml.jackson.core:jackson-core:2.15.3' - implementation 'com.fasterxml.jackson.core:jackson-annotations:2.15.3' - implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.3' + // WS-2026-0003 (jackson-core async parser DoS, CVSS 7.5) — 2.18.6 에서 수정. JDK8 호환. + implementation 'com.fasterxml.jackson.core:jackson-core:2.18.6' + implementation 'com.fasterxml.jackson.core:jackson-annotations:2.18.6' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.18.6' implementation group: 'org.apache.velocity', name: 'velocity-engine-core', version: '2.3' @@ -124,7 +164,16 @@ bootRun { // jvmArgs '-Xdebug', '-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005' sourceResources sourceSets.main // processResources 필터 우회 (dev yml 직접 참조) - args = ["--spring.profiles.active=dev"] + + // 로컬 기동 기본 프로파일. + // dev/stage/prod 는 리버스프록시(OHS) 뒤 WAR 배포용이라 server.forward-headers-strategy=framework 가 + // 걸려 있다. framework 는 신뢰 프록시 목록 없이 X-Forwarded-* 를 그대로 신뢰하므로 앞단이 없는 + // 로컬 기동에는 쓰지 않는다(로컬은 공통 기본값 native = Tomcat RemoteIpValve, 사설대역만 신뢰). + // 다른 프로파일로 띄우려면: gradle bootRun -PbootProfile=dev + // ('profile' 이 아니라 'bootProfile' 인 이유: 위 ext 블록이 profile='local' 을 이미 점유하고 있어 + // findProperty('profile') 은 -P 지정 여부와 무관하게 항상 'local' 을 돌려준다.) + def bootRunProfile = (project.findProperty('bootProfile') ?: 'local_rinjaemac').toString() + args = ["--spring.profiles.active=" + bootRunProfile] } @@ -141,6 +190,14 @@ sourceSets { configurations { annotationProcessor + + // WebLogic 배포 시 Tyrus WebSocket 필터(weblogic.websocket.tyrus.TyrusServletFilter)와 + // 충돌 방지: WAR 에 번들된 Tomcat WsSci 가 javax.websocket.server.ServerContainer 속성을 + // WsServerContainer 로 등록 → WebLogic Tyrus 필터가 TyrusServerContainer 로 캐스팅하다 실패. + // 앱은 WebSocket 미사용이므로 Tomcat WebSocket 모듈 제외. + all { + exclude group: 'org.apache.tomcat.embed', module: 'tomcat-embed-websocket' + } } compileJava { @@ -150,11 +207,11 @@ compileJava { } processResources { - exclude { details -> - details.file.name.startsWith('application-') && - details.file.name.endsWith('.yml') && - !(details.file.name in ['application-stage.yml', 'application-prod.yml']) - } +// exclude { details -> +// details.file.name.startsWith('application-') && +// details.file.name.endsWith('.yml') && +// !(details.file.name in ['application-stage.yml', 'application-prod.yml']) +// } } test { @@ -168,8 +225,21 @@ test { enabled = true } +// 로컬 전용 설정 파일. 배포 산출물(WAR)에 실리면 Actuator/SBA 설정이 그대로 노출되어 +// 정보보호 점검에 불필요하게 걸린다. processResources 는 건드리지 않는다 +// (bootRun 이 build/resources/main 을 그대로 쓰므로 로컬 기동이 깨진다). +def localOnlyResources = ['**/application-local*.yml'] + +// 배포 산출물에서 로컬 전용 라이브러리를 걸러낸다. localOnlyLibPrefixes 는 파일 상단 정의. +def excludeLocalOnlyLibs = { org.gradle.api.file.FileCollection cp -> + cp.filter { f -> !localOnlyLibPrefixes.any { p -> f.name.startsWith(p) } } +} + bootWar { archiveFileName = "eapim-portal-boot.war" + mainClass = 'com.eactive.apim.portal.PortalApplication' + rootSpec.exclude(localOnlyResources) + classpath = excludeLocalOnlyLibs(classpath) } war { @@ -178,6 +248,8 @@ war { from('src/main/resources/jeus-web-dd.xml') { into 'WEB-INF' } from('src/main/resources/weblogic.xml') { into 'WEB-INF' } + rootSpec.exclude(localOnlyResources) + classpath = excludeLocalOnlyLibs(classpath) } task printSourceSets { @@ -189,4 +261,6 @@ task printSourceSets { println " Output dir : ${srcSet.output.classesDirs.asPath}" } } -} \ No newline at end of file +} +// CycloneDX SBOM -> xlsx 변환 (gradle sbomXlsx) +apply from: "$projectDir/gradle/sbom-xlsx.gradle" diff --git a/build_docker.sh b/build_docker.sh deleted file mode 100644 index 1cf0a19..0000000 --- a/build_docker.sh +++ /dev/null @@ -1,3 +0,0 @@ -./gradlew bootWar -docker build -t eactive-portal . - diff --git a/deploy_portal.sh b/deploy_portal.sh deleted file mode 100644 index 5344dea..0000000 --- a/deploy_portal.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/bash - -# Check if the war file is provided -if [ -z "$1" ]; then - echo "Usage: $0 /path/to/DEVPortal.war" - exit 1 -fi - -# Variables -WAR_FILE="$1" -TIMESTAMP=$(date +%Y%m%d%H%M%S) -DEST_DIR="/app/apim/deploy/DVPWeb" -BACKUP_DIR="/app/apim/backup" - -# Create backup directory if not exists -mkdir -p $BACKUP_DIR - -# Backup the existing directory -if [ -d "$DEST_DIR" ]; then - echo "Backing up existing directory..." - mv $DEST_DIR "${BACKUP_DIR}/dvp_${TIMESTAMP}" -fi - -# Create destination directory if not exists -mkdir -p $DEST_DIR - -# Unzip the file to the dedicated location -echo "Extracting WAR file to $DEST_DIR..." -unzip $WAR_FILE -d $DEST_DIR - -echo "deploy successful" diff --git a/deploy_portal2.sh b/deploy_portal2.sh deleted file mode 100644 index 952665c..0000000 --- a/deploy_portal2.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/bin/bash - -# Check if the war file is provided -if [ -z "$1" ]; then - echo "Usage: $0 /path/to/DEVPortal.war" - exit 1 -fi - -# Variables -WAR_FILE="$1" -TIMESTAMP=$(date +%Y%m%d%H%M%S) -DEST_DIR="/app/apim/deploy/DVPWeb" -STATIC_DIR="/app/apim/deploy/static" -BACKUP_DIR="/app/apim/backup" - -# Create backup directory if not exists -mkdir -p $BACKUP_DIR - -# Backup the existing directory -if [ -d "$STATIC_DIR" ]; then - echo "Backing up existing static directory..." - mv $STATIC_DIR "${BACKUP_DIR}/dvp_static_${TIMESTAMP}" -fi - -if [ -d "$DEST_DIR" ]; then - echo "Backing up existing directory..." - mv $DEST_DIR "${BACKUP_DIR}/dvp_${TIMESTAMP}" -fi - - -# Create destination directory if not exists -mkdir -p $DEST_DIR -mkdir -p $STATIC_DIR - -# unzip the file to the dedicated location -echo "Extracting WAR file to $DEST_DIR..." -unzip $WAR_FILE -d $DEST_DIR -mv $DEST_DIR/static/* $STATIC_DIR/ - -echo "deploy successful" diff --git a/email_template/djbank/email-auth-template-datauri.html b/email_template/djbank/email-auth-template-datauri.html new file mode 100644 index 0000000..e273f5d --- /dev/null +++ b/email_template/djbank/email-auth-template-datauri.html @@ -0,0 +1,120 @@ + + + + + + +DJBank 파트너포탈 이메일 인증 + + + + +
DJBank 파트너포탈 이메일 인증번호 안내입니다.
+ + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
  + DJ Bank +   + API Portal +
+
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
이메일인증
+ + + + +
${txt_1}
+
+ + 위 이메일 인증코드를 입력하여 이메일을 인증해 주세요.
+ +
※ 본 메일을 요청하지 않으셨다면 이 메일을 무시해 주시기 바랍니다.
+
+
+ + + + +
+ + + + + +
+ 제주은행 + +

본 메일은 발신 전용으로 회신되지 않습니다.

+

관련 문의사항은 고객센터 (1588-0079) 또는 홈페이지를 이용하시기 바랍니다.

+

제주 제주시 1100로 3351 (노형동) | 고객센터 : 1588-0079

+

Copyright JEJUBANK. ALL Rights Reserved.

+
+
+
+ + +
+ + diff --git a/email_template/djbank/email-auth-template-linked.html b/email_template/djbank/email-auth-template-linked.html new file mode 100644 index 0000000..a151e95 --- /dev/null +++ b/email_template/djbank/email-auth-template-linked.html @@ -0,0 +1,120 @@ + + + + + + +DJBank API Portal 이메일 인증 + + + + +
DJBank 개발자포탈 이메일 인증번호 안내입니다.
+ + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
  + DJ Bank +   + API Portal +
+
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
##subject:이메일인증##
+ + + + +
123456
+
+ 안녕하세요, 김철수 님.
+ DJBank 개발자포탈 회원가입 시 입력하신 이메일을 인증해 주세요.
+ 아래 인증번호를 회원가입 화면에 입력하시면 인증이 완료됩니다. +
※ 본 메일을 요청하지 않으셨다면 이 메일을 무시해 주시기 바랍니다.
+
+
+ + + + +
+ + + + + +
+ 제주은행 + +

본 메일은 발신 전용으로 회신되지 않습니다.

+

관련 문의사항은 고객센터 (????-????) 또는 홈페이지를 이용하시기 바랍니다.

+

제주 제주시 1100로 3351 (노형동) | 고객센터 : ????-????? | 사업자 등록번호 : 616-81-00615

+

Copyright JEJUBANK. ALL Rights Reserved.

+
+
+
+ + +
+ + diff --git a/email_template/djbank/images/logo-apiportal.png b/email_template/djbank/images/logo-apiportal.png new file mode 100644 index 0000000..2800423 Binary files /dev/null and b/email_template/djbank/images/logo-apiportal.png differ diff --git a/email_template/djbank/images/logo-djbank.png b/email_template/djbank/images/logo-djbank.png new file mode 100644 index 0000000..2155729 Binary files /dev/null and b/email_template/djbank/images/logo-djbank.png differ diff --git a/email_template/djbank/images/logo-jejubank.png b/email_template/djbank/images/logo-jejubank.png new file mode 100644 index 0000000..600a58f Binary files /dev/null and b/email_template/djbank/images/logo-jejubank.png differ diff --git a/email_template/email_common_template.html b/email_template/kjbank/email_common_template.html similarity index 100% rename from email_template/email_common_template.html rename to email_template/kjbank/email_common_template.html diff --git a/email_template/email_logo_bottom.png b/email_template/kjbank/email_logo_bottom.png similarity index 100% rename from email_template/email_logo_bottom.png rename to email_template/kjbank/email_logo_bottom.png diff --git a/email_template/email_logo_top.png b/email_template/kjbank/email_logo_top.png similarity index 100% rename from email_template/email_logo_top.png rename to email_template/kjbank/email_logo_top.png diff --git a/email_template/email_otp_password.html b/email_template/kjbank/email_otp_password.html similarity index 100% rename from email_template/email_otp_password.html rename to email_template/kjbank/email_otp_password.html diff --git a/email_template/email_password_changed.html b/email_template/kjbank/email_password_changed.html similarity index 100% rename from email_template/email_password_changed.html rename to email_template/kjbank/email_password_changed.html diff --git a/email_template/email_verification.html b/email_template/kjbank/email_verification.html similarity index 100% rename from email_template/email_verification.html rename to email_template/kjbank/email_verification.html diff --git a/gradle-gf63.sh b/gradle-gf63.sh deleted file mode 100644 index 35d9cc1..0000000 --- a/gradle-gf63.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -export JAVA_HOME=/c/eactive/apps/jdk1.8.0_441 -export GRADLE_HOME=/c/eactive/apps/gradle-8.7 -export GRADLE_USER_HOME=/c/eactive/workspaces/gradle-user-home-kjbank - -export PATH=$JAVA_HOME/bin:$GRADLE_HOME/bin:$PATH - - -# gradle 명령을 대체합니다. -command gradle $@ \ No newline at end of file diff --git a/gradle/sbom-xlsx.gradle b/gradle/sbom-xlsx.gradle new file mode 100644 index 0000000..8e506d8 --- /dev/null +++ b/gradle/sbom-xlsx.gradle @@ -0,0 +1,326 @@ +/* + * CycloneDX SBOM(bom.json) -> Excel(xlsx) 변환 태스크. + * + * gradle sbomXlsx # cyclonedxBom 실행 후 변환 + * gradle sbomXlsx -PsbomJson=path.json # 기존 bom.json 사용(cyclonedxBom 생략) + * gradle sbomXlsx -PsbomOut=out.xlsx # 출력 경로 지정 + * + * buildscript 블록이 이 스크립트에만 적용되므로 POI 의존성이 메인 빌드 + * classpath 나 WAR 산출물에는 포함되지 않는다. + * + * 시트: 요약 / WAR 기준 + * 산출 기준은 war 태스크의 classpath(= runtimeClasspath) 이므로 + * test·annotationProcessor·developmentOnly·compileOnly 의존은 모두 제외된다. + * bom.json 은 라이선스/해시/설명/직접-전이 판별을 위한 메타 소스로만 쓴다. + */ +buildscript { + repositories { + maven { + url "https://nexus.eactive.synology.me:8090/repository/maven-public/" + allowInsecureProtocol = true + } + mavenCentral() + } + dependencies { + classpath 'org.apache.poi:poi-ooxml:3.17' + } +} + +import groovy.json.JsonSlurper +import org.apache.poi.ss.usermodel.BorderStyle +import org.apache.poi.ss.usermodel.FillPatternType +import org.apache.poi.ss.usermodel.HorizontalAlignment +import org.apache.poi.ss.usermodel.IndexedColors +import org.apache.poi.ss.usermodel.VerticalAlignment +import org.apache.poi.ss.util.CellRangeAddress +import org.apache.poi.xssf.usermodel.XSSFWorkbook + +// 엑셀 셀 문자열 상한(32767)보다 여유를 둔 절단 길이 +ext.SBOM_CELL_LIMIT = 32000 + +task sbomXlsx { + group = 'sbom' + description = 'CycloneDX bom.json 을 WAR 수록 기준 xlsx 로 변환한다' + + // -PsbomJson 으로 기존 산출물을 지정하면 재생성하지 않는다 + if (!project.hasProperty('sbomJson')) { + dependsOn 'cyclonedxBom' + } + + doLast { + File src = resolveBomJson(project) + File out = project.hasProperty('sbomOut') + ? project.file(project.property('sbomOut')) + : new File(project.buildDir, "reports/sbom/${sbomFileName(project)}") + out.parentFile.mkdirs() + + def bom = new JsonSlurper().parse(src, 'UTF-8') + def deploy = collectDeployJars(project) + def warRows = joinWarRows(deploy.jars, indexComponents(bom)) + + def wb = new XSSFWorkbook() + def st = createStyles(wb) + writeSummarySheet(wb, st, bom, warRows, src, deploy.label) + writeWarSheet(wb, st, warRows) + + out.withOutputStream { os -> wb.write(os) } + wb.close() + + int unmatched = warRows.count { it.matched == 'N' } + logger.lifecycle("SBOM xlsx 생성: ${out.absolutePath} " + + "(배포 수록 ${warRows.size()}개, SBOM 미매칭 ${unmatched}개, 원본 ${src.name})") + } +} + +/** 산출 파일명: 배포 패키지명 기준 (war 있으면 war 파일명, 없으면 project 이름[-버전]) */ +String sbomFileName(Project p) { + def warTask = p.tasks.findByName('war') + if (warTask != null) { + String archive = warTask.archiveFileName.get() + return archive.replaceAll(/\.(war|jar|ear)$/, '') + '-sbom.xlsx' + } + String ver = (p.version == null || p.version.toString() in ['', 'unspecified']) ? '' : "-${p.version}" + return "${p.name}${ver}-sbom.xlsx" +} + +/** bom.json 위치 결정: -PsbomJson > cyclonedxBom 산출 경로 후보 */ +File resolveBomJson(Project p) { + if (p.hasProperty('sbomJson')) { + File f = p.file(p.property('sbomJson')) + if (!f.exists()) { + throw new GradleException("bom.json 없음: ${f.absolutePath}") + } + return f + } + def candidates = [ + new File(p.buildDir, 'reports/cyclonedx/bom.json'), + new File(p.buildDir, 'reports/bom.json'), + ] + File found = candidates.find { it.exists() } + if (found == null) { + throw new GradleException( + "bom.json 을 찾지 못했다. 확인한 경로: " + candidates*.absolutePath.join(', ') + + "\n'gradle cyclonedxBom' 실행 후 재시도하거나 -PsbomJson=<경로> 로 지정한다.") + } + return found +} + +/** + * 실제 배포물에 packaging 되는 jar 목록. + * war 프로젝트는 war 태스크 classpath(= runtimeClasspath), 그 외는 runtimeClasspath 를 + * 기준으로 하므로 test/annotationProcessor/developmentOnly/compileOnly 는 자동으로 빠진다. + * + * @return [label: 기준 설명, jars: 행 목록] + */ +Map collectDeployJars(Project p) { + def cfg = p.configurations.findByName('runtimeClasspath') + if (cfg == null) { + p.logger.warn("[${p.name}] runtimeClasspath 가 없어 배포 기준 시트를 비운다") + return [label: '(없음)', jars: []] + } + + def warTask = p.tasks.findByName('war') + def files + String label + if (warTask != null) { + files = warTask.classpath.files + label = 'WAR WEB-INF/lib (war 태스크 classpath)' + } else { + files = cfg.files + label = 'runtimeClasspath (war 태스크 없음)' + } + + def coordByFile = [:] + cfg.resolvedConfiguration.resolvedArtifacts.each { a -> + def id = a.moduleVersion.id + coordByFile[a.file] = [group: id.group, name: id.name, version: id.version] + } + def jars = files.findAll { it.name.endsWith('.jar') }.collect { f -> + def c = coordByFile[f] + [ + file : f.name, + group : c?.group ?: '', + name : c?.name ?: f.name.replaceAll(/\.jar$/, ''), + version: c?.version ?: '', + coord : c ? "${c.group}:${c.name}:${c.version}".toString() : '', + ] + }.sort { it.file } + + return [label: label, jars: jars] +} + +/** bom.json 컴포넌트를 'group:name:version' 키로 색인 (라이선스/해시/설명/직접-전이) */ +Map indexComponents(bom) { + String rootRef = bom.metadata?.component?.'bom-ref' + Set directRefs = (bom.dependencies?.find { it.ref == rootRef }?.dependsOn ?: []) as Set + + def index = [:] + bom.components?.each { c -> + def hashes = [:] + c.hashes?.each { h -> hashes[h.alg] = h.content } + + def licenses = (c.licenses ?: []).collect { l -> + l.license?.id ?: l.license?.name ?: l.expression ?: '' + }.findAll { it } + + index["${c.group ?: ''}:${c.name ?: ''}:${c.version ?: ''}".toString()] = [ + direct : directRefs.contains(c.'bom-ref') ? '직접' : '전이', + licenses : licenses.join('; '), + licenseList: licenses.isEmpty() ? ['(미상)'] : licenses, + purl : c.purl ?: '', + sha256 : hashes['SHA-256'] ?: '', + sha1 : hashes['SHA-1'] ?: '', + description: c.description ?: '', + ] + } + return index +} + +/** WAR jar 목록에 SBOM 메타를 좌표로 결합 */ +List joinWarRows(List warJars, Map index) { + def result = [] + warJars.eachWithIndex { j, i -> + def m = j.coord ? index[j.coord] : null + result << [ + no : i + 1, + file : j.file, + group : j.group, + name : j.name, + version : j.version, + direct : m?.direct ?: '', + licenses : m?.licenses ?: '', + licenseList: m?.licenseList ?: ['(미상)'], + purl : m?.purl ?: '', + sha256 : m?.sha256 ?: '', + sha1 : m?.sha1 ?: '', + matched : (m != null) ? 'Y' : 'N', + description: m?.description ?: '', + ] + } + return result +} + +Map createStyles(wb) { + def headFont = wb.createFont() + headFont.setBold(true) + headFont.setColor(IndexedColors.WHITE.getIndex()) + + def head = wb.createCellStyle() + head.setFont(headFont) + head.setFillForegroundColor(IndexedColors.DARK_BLUE.getIndex()) + head.setFillPattern(FillPatternType.SOLID_FOREGROUND) + head.setAlignment(HorizontalAlignment.CENTER) + head.setVerticalAlignment(VerticalAlignment.CENTER) + head.setBorderBottom(BorderStyle.THIN) + + def body = wb.createCellStyle() + body.setVerticalAlignment(VerticalAlignment.TOP) + + def wrap = wb.createCellStyle() + wrap.setVerticalAlignment(VerticalAlignment.TOP) + wrap.setWrapText(true) + + def labelFont = wb.createFont() + labelFont.setBold(true) + def label = wb.createCellStyle() + label.setFont(labelFont) + + return [head: head, body: body, wrap: wrap, label: label] +} + +/** 헤더 행 생성 + 폭 지정 + 틀고정 */ +def writeHeader(sheet, style, List headers, List widths) { + def row = sheet.createRow(0) + row.setHeightInPoints(20f) + headers.eachWithIndex { h, i -> + def cell = row.createCell(i) + cell.setCellValue(h) + cell.setCellStyle(style) + sheet.setColumnWidth(i, widths[i] * 256) + } + sheet.createFreezePane(0, 1) +} + +def cellOf(row, int idx, value, style) { + def cell = row.createCell(idx) + String s = (value == null) ? '' : value.toString() + if (s.length() > SBOM_CELL_LIMIT) { + s = s.substring(0, SBOM_CELL_LIMIT) + '…(생략)' + } + cell.setCellValue(s) + cell.setCellStyle(style) + return cell +} + +def writeSummarySheet(wb, st, bom, List warRows, File src, String basisLabel) { + def sheet = wb.createSheet('요약') + def comp = bom.metadata?.component ?: [:] + def tool = bom.metadata?.tools?.components?.getAt(0) + Set licenseKinds = warRows.collectMany { it.licenseList } as Set + + def items = [ + ['대상 프로젝트', "${comp.group ?: ''}:${comp.name ?: ''}:${comp.version ?: ''}"], + ['산출 기준', "${basisLabel} — test/annotationProcessor/compileOnly 제외"], + ['BOM 포맷', "${bom.bomFormat ?: ''} ${bom.specVersion ?: ''}"], + ['serialNumber', bom.serialNumber ?: ''], + ['생성 시각', bom.metadata?.timestamp ?: ''], + ['생성 도구', tool ? "${tool.name} ${tool.version}" : ''], + ['원본 파일', src.absolutePath], + ['배포 수록 jar', warRows.size()], + [' └ 직접 의존', warRows.count { it.direct == '직접' }], + [' └ 전이 의존', warRows.count { it.direct == '전이' }], + [' └ SBOM 미매칭', warRows.count { it.matched == 'N' }], + ['라이선스 종류', licenseKinds.size()], + ['라이선스 미상', warRows.count { it.licenses.isEmpty() }], + ] + + writeHeader(sheet, st.head, ['항목', '값'], [30, 90]) + items.eachWithIndex { item, i -> + def row = sheet.createRow(i + 1) + cellOf(row, 0, item[0], st.label) + cellOf(row, 1, item[1], st.body) + } + + // 라이선스 분포 (요약 하단) + def byLicense = [:].withDefault { 0 } + warRows.each { r -> r.licenseList.each { lic -> byLicense[lic] = byLicense[lic] + 1 } } + def sorted = byLicense.entrySet().sort { a, b -> (b.value <=> a.value) ?: (a.key <=> b.key) } + + int base = items.size() + 2 + def hdr = sheet.createRow(base) + cellOf(hdr, 0, '라이선스', st.head) + cellOf(hdr, 1, 'jar 수', st.head) + sorted.eachWithIndex { e, i -> + def row = sheet.createRow(base + 1 + i) + cellOf(row, 0, e.key, st.body) + cellOf(row, 1, e.value, st.body) + } +} + +/** 실제 배포물(WAR WEB-INF/lib) 기준 시트 */ +def writeWarSheet(wb, st, List warRows) { + def sheet = wb.createSheet('WAR 기준') + def headers = ['No', 'jar 파일명', 'Group', 'Name', 'Version', '구분', + 'License', 'purl', 'SHA-256', 'SHA-1', 'SBOM매칭', 'Description'] + def widths = [6, 46, 32, 34, 16, 7, 30, 60, 40, 30, 10, 60] + writeHeader(sheet, st.head, headers, widths) + + warRows.eachWithIndex { r, i -> + def row = sheet.createRow(i + 1) + cellOf(row, 0, r.no, st.body) + cellOf(row, 1, r.file, st.body) + cellOf(row, 2, r.group, st.body) + cellOf(row, 3, r.name, st.body) + cellOf(row, 4, r.version, st.body) + cellOf(row, 5, r.direct, st.body) + cellOf(row, 6, r.licenses, st.body) + cellOf(row, 7, r.purl, st.body) + cellOf(row, 8, r.sha256, st.body) + cellOf(row, 9, r.sha1, st.body) + cellOf(row, 10, r.matched, st.body) + cellOf(row, 11, r.description, st.wrap) + } + if (!warRows.isEmpty()) { + sheet.setAutoFilter(new CellRangeAddress(0, warRows.size(), 0, headers.size() - 1)) + } +} diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 diff --git a/libs/swagger/jackson-dataformat-yaml-2.18.1.jar b/libs/swagger/jackson-dataformat-yaml-2.18.1.jar deleted file mode 100644 index a1e25c1..0000000 Binary files a/libs/swagger/jackson-dataformat-yaml-2.18.1.jar and /dev/null differ diff --git a/libs/swagger/swagger-annotations-1.6.14.jar b/libs/swagger/swagger-annotations-1.6.14.jar deleted file mode 100644 index 1f8db9b..0000000 Binary files a/libs/swagger/swagger-annotations-1.6.14.jar and /dev/null differ diff --git a/libs/swagger/swagger-annotations-2.2.25.jar b/libs/swagger/swagger-annotations-2.2.25.jar deleted file mode 100644 index 23ef295..0000000 Binary files a/libs/swagger/swagger-annotations-2.2.25.jar and /dev/null differ diff --git a/libs/swagger/swagger-compat-spec-parser-1.0.71.jar b/libs/swagger/swagger-compat-spec-parser-1.0.71.jar deleted file mode 100644 index d768696..0000000 Binary files a/libs/swagger/swagger-compat-spec-parser-1.0.71.jar and /dev/null differ diff --git a/libs/swagger/swagger-core-1.6.14.jar b/libs/swagger/swagger-core-1.6.14.jar deleted file mode 100644 index 037dce7..0000000 Binary files a/libs/swagger/swagger-core-1.6.14.jar and /dev/null differ diff --git a/libs/swagger/swagger-core-2.2.25.jar b/libs/swagger/swagger-core-2.2.25.jar deleted file mode 100644 index 286953d..0000000 Binary files a/libs/swagger/swagger-core-2.2.25.jar and /dev/null differ diff --git a/libs/swagger/swagger-models-1.6.14.jar b/libs/swagger/swagger-models-1.6.14.jar deleted file mode 100644 index fcb069a..0000000 Binary files a/libs/swagger/swagger-models-1.6.14.jar and /dev/null differ diff --git a/libs/swagger/swagger-models-2.2.25.jar b/libs/swagger/swagger-models-2.2.25.jar deleted file mode 100644 index 8113753..0000000 Binary files a/libs/swagger/swagger-models-2.2.25.jar and /dev/null differ diff --git a/libs/swagger/swagger-parser-1.0.71.jar b/libs/swagger/swagger-parser-1.0.71.jar deleted file mode 100644 index 2d34db0..0000000 Binary files a/libs/swagger/swagger-parser-1.0.71.jar and /dev/null differ diff --git a/libs/swagger/swagger-parser-2.1.23.jar b/libs/swagger/swagger-parser-2.1.23.jar deleted file mode 100644 index eb18923..0000000 Binary files a/libs/swagger/swagger-parser-2.1.23.jar and /dev/null differ diff --git a/libs/swagger/swagger-parser-core-2.1.23.jar b/libs/swagger/swagger-parser-core-2.1.23.jar deleted file mode 100644 index e1455ae..0000000 Binary files a/libs/swagger/swagger-parser-core-2.1.23.jar and /dev/null differ diff --git a/libs/swagger/swagger-parser-safe-url-resolver-1.0.71.jar b/libs/swagger/swagger-parser-safe-url-resolver-1.0.71.jar deleted file mode 100644 index e26b64e..0000000 Binary files a/libs/swagger/swagger-parser-safe-url-resolver-1.0.71.jar and /dev/null differ diff --git a/libs/swagger/swagger-parser-safe-url-resolver-2.1.23.jar b/libs/swagger/swagger-parser-safe-url-resolver-2.1.23.jar deleted file mode 100644 index 7e88d67..0000000 Binary files a/libs/swagger/swagger-parser-safe-url-resolver-2.1.23.jar and /dev/null differ diff --git a/libs/swagger/swagger-parser-v2-converter-2.1.23.jar b/libs/swagger/swagger-parser-v2-converter-2.1.23.jar deleted file mode 100644 index dc0fe3c..0000000 Binary files a/libs/swagger/swagger-parser-v2-converter-2.1.23.jar and /dev/null differ diff --git a/libs/swagger/swagger-parser-v3-2.1.23.jar b/libs/swagger/swagger-parser-v3-2.1.23.jar deleted file mode 100644 index 2dbf66c..0000000 Binary files a/libs/swagger/swagger-parser-v3-2.1.23.jar and /dev/null differ diff --git a/readme-docs/메뉴-관리-개발-가이드.md b/readme-docs/메뉴-관리-개발-가이드.md new file mode 100644 index 0000000..95aa529 --- /dev/null +++ b/readme-docs/메뉴-관리-개발-가이드.md @@ -0,0 +1,85 @@ +# 메뉴 관리 개발 가이드 + +포탈 GNB/마이페이지 메뉴는 `menu.yml` → DB(PTL_MENU_*) → 캐시 → 템플릿 렌더 구조로 동작하며, +노출/배치 관리는 eapim-admin **포탈메뉴관리**(파트너포탈 > 포탈관리 > 메뉴 관리)에서 수행한다. + +## 구성 요소 + +| 구성 | 위치 | 역할 | +|---|---|---| +| `menu.yml` | `src/main/resources/menu.yml` | 기본 메뉴 정의 (id/노출명/path/권한/기본 배치) | +| `roles.yml` | `src/main/resources/roles.yml` | 역할 정의 (`portal.portal_security` 이동분) | +| 엔티티/공유 서비스 | `elink-portal-common` `com.eactive.apim.portal.menu.*` | PTL_MENU_ITEM·PTL_MENU_PLACEMENT·PTL_ROLE(+AUTHORITY), `PortalMenuDataService` | +| 시더 | `djb/menu/MenuSeeder.java` | 부팅 시 yml→DB 적재 (ApplicationReadyEvent) | +| 캐시 | `djb/menu/MenuService.java` | role 비의존 트리 스냅샷, TTL 1시간(PTL_PROPERTY) | +| 렌더 | `djb/menu/MenuModelAdvice.java` → 모델 `menuView` | 요청별 노출(EXPOSE_ROLES) 필터 | +| 접근 제어 | `djb/menu/MenuAccessInterceptor.java` | ACCESS_ROLES 서버측 집행 (경로 정확 일치) | +| 내부 API | `djb/menu/MenuInternalController.java` | `POST /internal/menu/reload` (admin 캐시 리로드 수신) | + +메뉴를 소비하는 템플릿: `fragment/djbank/header_container.html`(데스크톱 nav·마이페이지 드롭다운·모바일 drawer), +`fragment/djbank/service_sidebar.html`. 모두 `${menuView}` 를 반복 렌더하므로 **메뉴 추가 시 템플릿 수정 불필요**. + +## menu.yml 스키마 + +```yaml +portal-menu: + items: + - id: support # kebab-case 필수 (^[a-z0-9-]+$). 변경 금지(변경=신규 항목) + name: "고객지원" + group: true # 상위 그룹. path 생략 시 클릭 없음(자식 있어야 노출) + section: GNB # GNB(기본) | MYPAGE. 자식은 부모 섹션 상속 + expose-roles: [] # 생략=전체(익명 포함), AUTHENTICATED=로그인자, 그 외 역할코드 any-of + children: + - { id: support-faq, name: "FAQ", path: /faq_list } + - { id: my-page-webhook, name: "Webhook 관리", path: /webhook, icon: fa-bell, + expose-roles: [ROLE_WEBHOOK], access-roles: [ROLE_WEBHOOK] } +``` + +- `expose-roles` = 메뉴 **노출** 조건, `access-roles` = URL **접근** 조건(인터셉터 차단, redirect). +- `icon` 은 마이페이지 드롭다운 전용(FontAwesome 클래스). +- 정렬은 yml 나열 순서(기본 배치 sort = index×10). + +## 시딩 규칙 (MenuSeeder) + +1. **항목**: id 기준 upsert. yml 값이 바뀌면 DFLT_*(기본값 스냅샷)를 갱신하고, + **관리자가 수정하지 않은 필드(현재값==구 기본값)만** 새 기본값을 따라간다. + 구조 필드(`group`/`section`/`icon`/`new-window`)는 항상 yml 이 이긴다. +2. **배치**: `PTL_MENU_PLACEMENT` 가 **비어있을 때만** 기본 배치로 최초 시딩. + 이후 배치는 admin 이 소유한다 — 재배포/재기동에도 보존됨. +3. yml 에서 항목을 제거해도 DB 는 삭제하지 않고 경고 로그만 남긴다(수동 정리). +4. 부팅 시딩 주체는 인증 사용자가 없으므로 `CREATED_BY=SYSTEM`. + +## 캐시와 리로드 + +- 스냅샷 TTL: PTL_PROPERTY `Portal / menu.cache.ttl-seconds` (기본 3600초). +- 즉시 반영: `curl -X POST http://127.0.0.1:39130/internal/menu/reload` + (admin 포탈메뉴관리의 [캐시 Reload] 버튼이 동일 호출 수행). +- 내부 API 가드: `Portal / menu.internal.allow-ips` 허용 IP 목록(기본 loopback) + + X-Forwarded-For 동반 요청 거부. CSRF 면제(`/internal/menu/**`). +- admin 측 호출 URL: `Portal / portal.internal.menu-reload-url`. + +## 새 메뉴 추가 절차 + +**기본 메뉴(코드 배포와 함께)** +1. 페이지/라우트 준비 (`portal.pages` 또는 `@GetMapping` — 기존 방식 그대로) +2. `menu.yml` 에 항목 추가 (필요 시 breadcrumb 용 `page.home` 트리도 갱신 — 별도 체계 유지) +3. 재기동 → 시딩 로그 확인 → 헤더/드로어 노출 확인 +4. 이미 운영 중인 DB 라면 배치는 자동 추가되지 않음(배치 시딩은 최초 1회) — + admin 화면에서 미배치 → 원하는 위치로 드래그 후 저장 + +**운영자 임시 메뉴(외부 링크 등)**: admin 포탈메뉴관리 [메뉴 추가] → 미배치 생성 → 드래그 배치 → 저장 → 캐시 Reload. +커스텀 항목은 미배치 시 삭제된다. + +## 로컬 개발 주의 + +- `gradle bootRun` 으로 시딩까지 확인하려면 damo-manager 가 classpath 에 필요: + `JAVA_TOOL_OPTIONS="-Xbootclasspath/a:<...>/apache-tomcat-9.0.115-djb/lib/damo-manager.jar"` + (미지정 시 감사 컬럼 암호화 컨버터에서 NoClassDefFoundError). +- 템플릿/메뉴 반영 확인은 서버 재시작 후 curl 로. +- elink-portal-common 수정 후 Q클래스 duplicate 컴파일 오류 시 각 모듈 `build/generated` 삭제 후 재컴파일. + +## 역할(roles.yml) 변경 + +- 로그인 권한 확장은 `PortalRolesProperties`(yml 바인딩)를 직접 사용 — DB 미러(PTL_ROLE*)는 + admin 권한 선택 체크박스 소스 전용. +- 역할 추가 시 `roles.yml` 의 `authority-names` 에 한글 라벨을 함께 등록해야 admin 화면에 표기된다. diff --git a/script/djb-custom-export/README.md b/script/djb-custom-export/README.md new file mode 100644 index 0000000..624ed95 --- /dev/null +++ b/script/djb-custom-export/README.md @@ -0,0 +1,112 @@ +# DJB 커스텀 소스 납품본 수집 + +발주사 납품용으로, 기준 시점 이후 **신규로 추가된 커스텀 소스 파일**만 모아 +디렉터리 구조를 유지한 채 한곳에 복사하고 `MANIFEST.csv` / `SUMMARY.md` / zip 을 만든다. + +## 파일 + +| 파일 | 용도 | +|---|---| +| `export-custom.sh` | macOS / Linux 실행 스크립트 | +| `export-custom.ps1` | Windows PowerShell 실행 스크립트 (실제 로직) | +| `export-custom.bat` | Windows cmd 래퍼 — 인자를 `.ps1` 로 그대로 전달 | +| `modules.conf` | 대상 모듈 / 소스 경로 / 수집 확장자 | +| `export-filter.rules` | 포함·제외 규칙 (`.gitignore` 유사 문법) | +| `filelist.txt` | 마지막으로 뽑은 수집 대상 목록 (검토용, 재생성 가능) | +| `filelist-no-djb-marker.txt` | 그중 `djb` / `custom` 마커가 **없는** 파일 — 업그레이드 유입분 검토 대상 | + +## 수집 방식 + +1. 모듈별로 **컷오프 날짜 직전의 마지막 커밋**을 base 로 잡는다. + `git rev-list -1 --before= HEAD` + 컷오프 이전 커밋이 아예 없으면 저장소 전체를 신규로 본다. +2. `git diff --name-only --diff-filter=A -M HEAD -- <소스경로>` 로 + **신규 추가(A)** 파일만 뽑는다. `-M` 이라 단순 rename 은 신규로 잡히지 않는다. +3. `modules.conf` 의 확장자 allowlist 로 거른다. (확장자 없는 파일은 제외) +4. `export-filter.rules` 로 포함·제외를 최종 결정한다. +5. 남은 파일을 `/src/<모듈>/<원래 경로>` 로 복사한다. + +## 사용법 + +```bash +# macOS / Linux +./export-custom.sh --dry-run > filelist.txt # 목록만 뽑기 (로그는 stderr) +./export-custom.sh --show-excluded # 규칙에 걸려 빠진 목록 + 걸린 규칙 +./export-custom.sh # 실제 수집 +./export-custom.sh --zip # 수집 + zip +./export-custom.sh -c 2026-05-01 -m eapim-portal +``` + +```powershell +# Windows +.\export-custom.ps1 -DryRun > filelist.txt +.\export-custom.ps1 -ShowExcluded +.\export-custom.ps1 -Zip +.\export-custom.ps1 -Cutoff 2026-05-01 -Module eapim-portal,eapim-admin + +# cmd 에서 +export-custom.bat -DryRun > filelist.txt +``` + +### 주요 옵션 + +| sh | ps1 | 설명 | +|---|---|---| +| `-c, --cutoff` | `-Cutoff` | 기준 날짜 (기본 `2026-05-01`) | +| `-r, --root` | `-Root` | 저장소 루트. 미지정 시 스크립트 위치 기준 자동 탐지 | +| `-o, --out` | `-Out` | 출력 디렉터리 (기본 `/build/djb-custom-export`) | +| `-m, --module` | `-Module` | 특정 모듈만 | +| `--zip` | `-Zip` | zip 생성 | +| `--include-untracked` | `-IncludeUntracked` | 미커밋 신규 파일도 포함 | +| `--dry-run` | `-DryRun` | 복사 없이 목록만 | +| `--show-excluded` | `-ShowExcluded` | 규칙에 걸려 빠진 목록 | + +## `export-filter.rules` 문법 + +`.gitignore` 와 비슷하되 더 단순하다. + +- 한 줄에 패턴 하나. `#` 주석, 빈 줄 무시 +- 기본은 **모두 포함**. 패턴에 걸리면 제외 +- `!` 로 시작하면 예외(다시 포함) +- **뒤에 오는 규칙이 앞 규칙을 덮는다** — 순서가 중요하다 +- 패턴에 `/` 가 있으면 `<모듈>/<경로>` 전체와 매칭, 없으면 파일명만 매칭 +- `/` 로 끝나면 그 디렉터리 하위 전체 +- `*` 는 `/` 도 포함해 매칭한다 (단순 glob, `**` 없음) + +매칭 대상 경로 예: + +``` +eapim-portal/src/main/java/com/eactive/apim/portal/djb/webhook/WebhookService.java +eapim-admin/WebContent/jsp/onl/apim/webhook/webhookList.jsp +``` + +규칙 파일은 세 구역으로 나뉜다. + +1. **업그레이드 유입분 제외** — `filelist-no-djb-marker.txt` 를 검토하며 채운다 +2. **`!*djb*` 예외** — djb 마커가 붙은 파일은 커스텀이므로 되살린다 +3. **무조건 제외** — 빌드 산출물·서드파티. 2)보다 뒤에 있어 최종 승리한다 + +## 신규지만 커스텀이 아닌 파일 걸러내기 + +신규 파일이라도 제품 업그레이드로 딸려 온 것일 수 있다. 판별 순서: + +```bash +# 1) 전체 목록 +./export-custom.sh --dry-run > filelist.txt + +# 2) djb / custom 마커가 없는 것 = 검토 대상 +grep -vi djb filelist.txt | grep -v '/custom/' > filelist-no-djb-marker.txt + +# 3) 검토 후 업그레이드 유입분을 export-filter.rules 의 1) 구역에 추가 + +# 4) 반영 확인 +./export-custom.sh --show-excluded +``` + +## 주의 + +- 컷오프 이전 커밋이 없으면 base 가 **빈 트리**가 되어 저장소 전체가 신규로 잡힌다. + `SUMMARY.md` 의 base 컬럼에 `(컷오프 이전 커밋 없음 …)` 으로 표시된다. +- 모듈에 컷오프 직전 커밋이 드문드문 있으면 base 가 컷오프보다 훨씬 이전으로 잡힐 수 있다. + 실행 로그의 `base:` 날짜를 항상 확인할 것. +- 추가된 뒤 삭제·이동된 파일은 현재 트리에 없으므로 건너뛰고, 건수를 `SUMMARY.md` 에 남긴다. diff --git a/script/djb-custom-export/export-custom.bat b/script/djb-custom-export/export-custom.bat new file mode 100644 index 0000000..5325663 --- /dev/null +++ b/script/djb-custom-export/export-custom.bat @@ -0,0 +1,29 @@ +@echo off +REM --------------------------------------------------------------------------- +REM DJB 커스텀 소스 납품본 수집 (Windows cmd 래퍼) +REM +REM 실제 로직은 export-custom.ps1 에 있다. 이 배치는 PowerShell 을 띄우고 +REM 전달받은 인자를 그대로 넘기기만 한다. +REM +REM 사용 예: +REM export-custom.bat +REM export-custom.bat -Cutoff 2026-05-01 -Zip +REM export-custom.bat -DryRun > filelist.txt +REM export-custom.bat -ShowExcluded +REM export-custom.bat -Module eapim-portal,eapim-admin +REM --------------------------------------------------------------------------- +setlocal + +REM 한글 경로/출력이 깨지지 않도록 코드페이지를 UTF-8 로 전환 +chcp 65001 >nul + +where powershell >nul 2>nul +if errorlevel 1 ( + echo [오류] PowerShell 을 찾을 수 없습니다. 1>&2 + exit /b 1 +) + +powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0export-custom.ps1" %* +set RC=%ERRORLEVEL% + +endlocal & exit /b %RC% diff --git a/script/djb-custom-export/export-custom.ps1 b/script/djb-custom-export/export-custom.ps1 new file mode 100644 index 0000000..7b859ec --- /dev/null +++ b/script/djb-custom-export/export-custom.ps1 @@ -0,0 +1,278 @@ +<# +.SYNOPSIS + DJB 커스텀 소스 납품본 수집 스크립트 (Windows / PowerShell) + +.DESCRIPTION + 기준 커밋(컷오프 직전 마지막 커밋) 이후 "신규로 추가된" 소스 파일만 모아 + 모듈별 디렉터리 구조를 유지한 채 출력 디렉터리에 복사하고, + MANIFEST.csv / SUMMARY.md / (선택) zip 아카이브를 생성한다. + +.EXAMPLE + .\export-custom.ps1 + .\export-custom.ps1 -Cutoff 2026-05-01 -Zip + .\export-custom.ps1 -DryRun + .\export-custom.ps1 -Module eapim-portal,eapim-admin +#> +[CmdletBinding()] +param( + [string] $Cutoff = '2026-05-01', + [string] $Root, + [string] $Out, + [string[]] $Module, + [switch] $Zip, + [switch] $IncludeUntracked, + [switch] $DryRun, + [switch] $ShowExcluded +) + +if ($ShowExcluded) { $DryRun = $true } + +$ErrorActionPreference = 'Stop' + +# git 출력의 한글이 깨지지 않도록 콘솔 인코딩을 UTF-8로 고정한다. +try { [Console]::OutputEncoding = [Text.Encoding]::UTF8 } catch { } +$OutputEncoding = [Text.Encoding]::UTF8 + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$ConfFile = Join-Path $ScriptDir 'modules.conf' +$RulesFile = Join-Path $ScriptDir 'export-filter.rules' + +# 저장소 루트: 스크립트는 \eapim-portal\script\djb-custom-export\ 에 있다. +if (-not $Root) { $Root = (Resolve-Path (Join-Path $ScriptDir '..\..\..')).Path } +if (-not (Test-Path -LiteralPath $Root -PathType Container)) { + throw "저장소 루트를 찾을 수 없다: $Root" +} +if (-not $Out) { $Out = Join-Path $Root 'build\djb-custom-export' } +if (-not (Test-Path -LiteralPath $ConfFile)) { throw "설정 파일 없음: $ConfFile" } + +# 빈 트리 해시 — base 커밋이 없을 때(= 모든 이력이 컷오프 이후) 사용한다. +$EmptyTree = '4b825dc642cb6eb9a060e54bf8d69288fbee4904' + +# ---- 설정 로드 ------------------------------------------------------------ +$Modules = @() +$AllowExt = @() + +foreach ($line in (Get-Content -LiteralPath $ConfFile -Encoding UTF8)) { + $t = $line.Trim() + if ($t -eq '' -or $t.StartsWith('#')) { continue } + $idx = $t.IndexOf('|') + if ($idx -lt 0) { continue } + $key = $t.Substring(0, $idx) + $rest = $t.Substring($idx + 1) + + switch ($key) { + 'MODULE' { + $i2 = $rest.IndexOf('|') + if ($i2 -lt 0) { continue } + $Modules += [pscustomobject]@{ + Name = $rest.Substring(0, $i2) + Paths = $rest.Substring($i2 + 1).Split(' ') | Where-Object { $_ -ne '' } + } + } + 'EXT' { $AllowExt = $rest.Split(',') | ForEach-Object { $_.Trim().ToLower() } | Where-Object { $_ -ne '' } } + } +} + +# ---- 포함/제외 규칙 로드 (.gitignore 유사) -------------------------------- +$Rules = @() +if (Test-Path -LiteralPath $RulesFile) { + foreach ($line in (Get-Content -LiteralPath $RulesFile -Encoding UTF8)) { + $t = $line.Trim() + if ($t -eq '' -or $t.StartsWith('#')) { continue } + $Rules += $t + } +} + +# 마지막으로 매칭된 규칙이 이긴다. 기본 포함, '!' 는 예외(다시 포함). +# 반환: @{ Keep = $true/$false; Rule = '<제외시킨 규칙>' } +function Test-RuleKeep([string]$RelPath) { + $leaf = Split-Path $RelPath -Leaf + $keep = $true + $matched = '' + foreach ($raw in $Rules) { + $rule = $raw + $neg = $false + if ($rule.StartsWith('!')) { $neg = $true; $rule = $rule.Substring(1) } + if ($rule.EndsWith('/')) { $rule = $rule + '*' } # 디렉터리 규칙 + $target = if ($rule.Contains('/')) { $RelPath } else { $leaf } + + if ($target -like $rule) { + if ($neg) { $keep = $true; $matched = '' } + else { $keep = $false; $matched = $rule } + } + } + return @{ Keep = $keep; Rule = $matched } +} + +function Test-AllowedExt([string]$RelPath) { + $leaf = Split-Path $RelPath -Leaf + if ($leaf -notmatch '\.') { return $false } # 확장자 없는 파일 제외 + $ext = $leaf.Substring($leaf.LastIndexOf('.') + 1).ToLower() + return $AllowExt -contains $ext +} + +function Invoke-Git([string]$Dir, [string[]]$GitArgs) { + $all = @('-C', $Dir, '-c', 'core.quotepath=false') + $GitArgs + $res = & git @all 2>$null + if ($LASTEXITCODE -ne 0) { return @() } + return @($res) +} + +# ---- 준비 ----------------------------------------------------------------- +$Manifest = Join-Path $Out 'MANIFEST.csv' +$Summary = Join-Path $Out 'SUMMARY.md' + +if (-not $DryRun) { + if (Test-Path -LiteralPath $Out) { Remove-Item -LiteralPath $Out -Recurse -Force } + New-Item -ItemType Directory -Path (Join-Path $Out 'src') -Force | Out-Null + # Excel 한글용 UTF-8 BOM + $utf8Bom = New-Object System.Text.UTF8Encoding($true) + [IO.File]::WriteAllText($Manifest, "module,path,ext,added_commit,added_date,author`r`n", $utf8Bom) +} + +Write-Host "저장소 루트 : $Root" +Write-Host "기준 날짜 : $Cutoff (이 날짜 직전 마지막 커밋이 base)" +Write-Host "출력 경로 : $Out" +if ($DryRun) { Write-Host '모드 : DRY-RUN (복사 안 함)' } +Write-Host '' + +$Rows = New-Object System.Collections.Generic.List[string] +$SummaryRs = New-Object System.Collections.Generic.List[object] +$Total = 0 +$Missing = 0 +$Excluded = 0 + +# ---- 모듈 순회 ------------------------------------------------------------ +foreach ($m in $Modules) { + if ($Module -and ($Module -notcontains $m.Name)) { continue } + + $modDir = Join-Path $Root $m.Name + if (-not (Test-Path -LiteralPath (Join-Path $modDir '.git'))) { + Write-Host "[건너뜀] $($m.Name) — git 저장소 아님 ($modDir)" + continue + } + + # base 커밋 결정 + $base = (Invoke-Git $modDir @('rev-list', '-1', "--before=$Cutoff", 'HEAD') | Select-Object -First 1) + if ([string]::IsNullOrWhiteSpace($base)) { + $base = $EmptyTree + $baseDesc = '(컷오프 이전 커밋 없음 → 전체를 신규로 간주)' + } else { + $d = (Invoke-Git $modDir @('log', '-1', '--format=%h %ad %s', '--date=short', $base) | Select-Object -First 1) + $baseDesc = if ($d.Length -gt 80) { $d.Substring(0, 80) } else { $d } + } + + # 실제 존재하는 소스 경로만 pathspec 으로 사용 + $spec = @() + foreach ($p in $m.Paths) { + if (Test-Path -LiteralPath (Join-Path $modDir ($p -replace '/', '\')) -PathType Container) { $spec += $p } + } + if ($spec.Count -eq 0) { + Write-Host "[건너뜀] $($m.Name) — 설정된 소스 경로가 존재하지 않음" + continue + } + + # 신규(Added) 파일만. -M 으로 rename 은 신규에서 제외한다. + $files = @(Invoke-Git $modDir (@('diff', '--name-only', '--diff-filter=A', '-M', $base, 'HEAD', '--') + $spec)) + + if ($IncludeUntracked) { + $files += @(Invoke-Git $modDir (@('ls-files', '--others', '--exclude-standard', '--') + $spec)) + } + + $files = $files | Where-Object { $_ -ne '' } | Sort-Object -Unique + + $count = 0 + $miss = 0 + foreach ($f in $files) { + if (-not (Test-AllowedExt $f)) { continue } + + # 규칙 매칭은 <모듈>/<경로> 전체 문자열 기준 + $rk = Test-RuleKeep "$($m.Name)/$f" + if (-not $rk.Keep) { + $Excluded++ + if ($ShowExcluded) { Write-Output "$($m.Name)/$f`t# $($rk.Rule)" } + continue + } + if ($ShowExcluded) { continue } + + $src = Join-Path $modDir ($f -replace '/', '\') + if (-not (Test-Path -LiteralPath $src -PathType Leaf)) { + # 추가된 뒤 삭제/이동된 파일 — 납품 대상 아님 + Write-Warning " [없음] $($m.Name)/$f" + $miss++ + continue + } + + $count++ + # 목록은 Write-Output(성공 스트림) 으로 — 리다이렉트하면 목록만 파일로 떨어진다. + if ($DryRun) { Write-Output "$($m.Name)/$f"; continue } + + $dst = Join-Path (Join-Path $Out 'src') (Join-Path $m.Name ($f -replace '/', '\')) + $dstDir = Split-Path -Parent $dst + if (-not (Test-Path -LiteralPath $dstDir)) { New-Item -ItemType Directory -Path $dstDir -Force | Out-Null } + Copy-Item -LiteralPath $src -Destination $dst -Force + + $meta = (Invoke-Git $modDir @('log', '-1', '--diff-filter=A', '--format=%h|%ad|%an', '--date=short', '--', $f) | Select-Object -First 1) + $cHash = ''; $cDate = ''; $cAuth = '' + if ($meta) { + $parts = $meta.Split('|') + if ($parts.Count -ge 3) { $cHash = $parts[0]; $cDate = $parts[1]; $cAuth = $parts[2] } + } + $leaf = Split-Path $f -Leaf + $ext = if ($leaf -match '\.') { $leaf.Substring($leaf.LastIndexOf('.') + 1) } else { '' } + + $esc = { param($s) '"' + ($s -replace '"', '""') + '"' } + $Rows.Add((@( + (& $esc $m.Name), (& $esc $f), (& $esc $ext), + (& $esc $cHash), (& $esc $cDate), (& $esc $cAuth) + ) -join ',')) + } + + $Total += $count + $Missing += $miss + $SummaryRs.Add([pscustomobject]@{ Module = $m.Name; Count = $count; Base = $baseDesc }) + Write-Host ("[수집] {0,-22} {1,5} 개 base: {2}" -f $m.Name, $count, $baseDesc) +} + +# ---- 출력 ------------------------------------------------------------------ +if (-not $DryRun) { + if ($Rows.Count -gt 0) { + [IO.File]::AppendAllText($Manifest, (($Rows -join "`r`n") + "`r`n"), (New-Object System.Text.UTF8Encoding($false))) + } + + $sb = New-Object System.Text.StringBuilder + [void]$sb.AppendLine('# DJB 커스텀 소스 납품본') + [void]$sb.AppendLine('') + [void]$sb.AppendLine("- 생성 일시: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')") + [void]$sb.AppendLine("- 기준 날짜: ``$Cutoff`` (이 날짜 직전 마지막 커밋을 base로 삼아, 이후 **신규 추가된** 파일만 수집)") + [void]$sb.AppendLine('- 수집 규칙: `git diff --diff-filter=A -M HEAD` / 확장자 allowlist / `export-filter.rules` 필터') + [void]$sb.AppendLine("- 대상 확장자: ``$($AllowExt -join ',')``") + [void]$sb.AppendLine("- 규칙 파일: ``$(Split-Path $RulesFile -Leaf)`` ($($Rules.Count) 개 규칙, 제외 $Excluded 건)") + [void]$sb.AppendLine('') + [void]$sb.AppendLine('## 모듈별 수집 결과') + [void]$sb.AppendLine('') + [void]$sb.AppendLine('| 모듈 | 파일 수 | base 커밋 |') + [void]$sb.AppendLine('|---|---:|---|') + foreach ($r in $SummaryRs) { [void]$sb.AppendLine("| $($r.Module) | $($r.Count) | $($r.Base) |") } + [void]$sb.AppendLine('') + [void]$sb.AppendLine("**합계: $Total 개**") + if ($Missing -gt 0) { [void]$sb.AppendLine("> 추가 후 삭제/이동되어 현재 트리에 없는 파일 $Missing 개는 제외됨.") } + [void]$sb.AppendLine('') + [void]$sb.AppendLine('## 파일 목록') + [void]$sb.AppendLine('') + [void]$sb.AppendLine('`MANIFEST.csv` 참조 (module, path, ext, 최초 추가 커밋/일자/작성자).') + + [IO.File]::WriteAllText($Summary, $sb.ToString(), (New-Object System.Text.UTF8Encoding($true))) + + Write-Host '' + Write-Host "MANIFEST : $Manifest" + Write-Host "SUMMARY : $Summary" + + if ($Zip) { + $zipName = "djb-custom-export-$(Get-Date -Format 'yyyyMMdd').zip" + $zipPath = Join-Path (Split-Path -Parent $Out) $zipName + if (Test-Path -LiteralPath $zipPath) { Remove-Item -LiteralPath $zipPath -Force } + Compress-Archive -Path (Join-Path $Out '*') -DestinationPath $zipPath + Write-Host "ZIP : $zipPath" + } +} diff --git a/script/djb-custom-export/export-custom.sh b/script/djb-custom-export/export-custom.sh new file mode 100755 index 0000000..1c5e30b --- /dev/null +++ b/script/djb-custom-export/export-custom.sh @@ -0,0 +1,352 @@ +#!/usr/bin/env bash +# +# DJB 커스텀 소스 납품본 수집 스크립트 (macOS / Linux) +# +# 기준 커밋(컷오프 직전 마지막 커밋) 이후 "신규로 추가된" 소스 파일만 모아 +# 모듈별 디렉터리 구조를 유지한 채 출력 디렉터리에 복사하고, +# MANIFEST.csv / SUMMARY.md / (선택) zip 아카이브를 생성한다. +# +# 사용법: +# ./export-custom.sh # 기본값으로 실행 +# ./export-custom.sh -c 2026-05-01 --zip +# ./export-custom.sh --dry-run +# ./export-custom.sh -m eapim-portal -m eapim-admin +# +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +CONF_FILE="$SCRIPT_DIR/modules.conf" +RULES_FILE="$SCRIPT_DIR/export-filter.rules" + +# ---- 기본값 --------------------------------------------------------------- +CUTOFF="2026-05-01" +REPO_ROOT="" +OUT_DIR="" +MAKE_ZIP=0 +DRY_RUN=0 +INCLUDE_UNTRACKED=0 +ONLY_MODULES="" +SHOW_EXCLUDED=0 + +usage() { + cat <<'EOF' +DJB 커스텀 소스 납품본 수집 + +옵션: + -c, --cutoff 기준 날짜. 이 날짜 직전 마지막 커밋을 base로 삼는다. + (기본: 2026-05-01) + -r, --root 저장소 루트(djb-eapim). 미지정 시 스크립트 위치에서 자동 탐지. + -o, --out 출력 디렉터리. (기본: /build/djb-custom-export) + -m, --module 특정 모듈만 수집. 여러 번 지정 가능. + --zip 수집 후 zip 아카이브 생성. + --include-untracked git에 아직 커밋되지 않은 신규 파일도 포함. + --dry-run 복사하지 않고 대상 목록만 stdout 으로 출력(로그는 stderr). + --show-excluded --dry-run 과 함께. 규칙에 걸려 제외된 목록을 대신 출력. + -h, --help 도움말. + +포함/제외 규칙은 export-filter.rules (.gitignore 유사 문법) 에서 관리한다. +EOF +} + +while [ $# -gt 0 ]; do + case "$1" in + -c|--cutoff) CUTOFF="$2"; shift 2 ;; + -r|--root) REPO_ROOT="$2"; shift 2 ;; + -o|--out) OUT_DIR="$2"; shift 2 ;; + -m|--module) ONLY_MODULES="$ONLY_MODULES $2"; shift 2 ;; + --zip) MAKE_ZIP=1; shift ;; + --include-untracked) INCLUDE_UNTRACKED=1; shift ;; + --dry-run) DRY_RUN=1; shift ;; + --show-excluded) SHOW_EXCLUDED=1; DRY_RUN=1; shift ;; + -h|--help) usage; exit 0 ;; + *) echo "알 수 없는 옵션: $1" >&2; usage >&2; exit 1 ;; + esac +done + +# ---- 저장소 루트 탐지 ----------------------------------------------------- +# 스크립트는 /eapim-portal/script/djb-custom-export/ 에 있다. +if [ -z "$REPO_ROOT" ]; then + REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" +fi +if [ ! -d "$REPO_ROOT" ]; then + echo "저장소 루트를 찾을 수 없다: $REPO_ROOT" >&2 + exit 1 +fi +[ -z "$OUT_DIR" ] && OUT_DIR="$REPO_ROOT/build/djb-custom-export" + +if [ ! -f "$CONF_FILE" ]; then + echo "설정 파일 없음: $CONF_FILE" >&2 + exit 1 +fi + +# ---- 설정 로드 ------------------------------------------------------------ +ALLOW_EXT="" +EXCLUDE_PAT="" +MODULE_LINES="" +while IFS= read -r line || [ -n "$line" ]; do + case "$line" in + ''|'#'*) continue ;; + esac + key="${line%%|*}" + rest="${line#*|}" + case "$key" in + MODULE) MODULE_LINES="$MODULE_LINES +$rest" ;; + EXT) ALLOW_EXT="$rest" ;; + EXCLUDE) EXCLUDE_PAT="$rest" ;; + esac +done < "$CONF_FILE" + +# 빈 트리 해시 — base 커밋이 없을 때(= 모든 이력이 컷오프 이후) 사용한다. +EMPTY_TREE="4b825dc642cb6eb9a060e54bf8d69288fbee4904" + +is_allowed_ext() { + _f="$1" + _base="${_f##*/}" + case "$_base" in + *.*) _ext="${_base##*.}" ;; + *) return 1 ;; # 확장자 없는 파일 제외 + esac + # 소문자 변환 (bash 3.2 호환) + _ext="$(printf '%s' "$_ext" | tr '[:upper:]' '[:lower:]')" + case ",$ALLOW_EXT," in + *",$_ext,"*) return 0 ;; + *) return 1 ;; + esac +} + +# ---- 포함/제외 규칙 (.gitignore 유사) -------------------------------------- +RULES=() +if [ -f "$RULES_FILE" ]; then + while IFS= read -r r || [ -n "$r" ]; do + # 앞뒤 공백 제거 + r="${r#"${r%%[![:space:]]*}"}" + r="${r%"${r##*[![:space:]]}"}" + case "$r" in + ''|'#'*) continue ;; + esac + RULES[${#RULES[@]}]="$r" + done < "$RULES_FILE" +fi + +# 경로 하나에 규칙을 순서대로 적용한다. 마지막으로 매칭된 규칙이 이긴다. +# 기본은 포함(0), '!' 규칙은 다시 포함, 일반 규칙은 제외(1). +# 반환: 0 = 포함, 1 = 제외. 제외시킨 규칙은 MATCHED_RULE 에 담긴다. +MATCHED_RULE="" +rule_keep() { + _path="$1" + _leaf="${_path##*/}" + _keep=0 + MATCHED_RULE="" + _i=0 + while [ $_i -lt ${#RULES[@]} ]; do + _rule="${RULES[$_i]}" + _i=$((_i + 1)) + + _neg=0 + case "$_rule" in + '!'*) _neg=1; _rule="${_rule#!}" ;; + esac + # 디렉터리 규칙: 하위 전체 + case "$_rule" in + */) _rule="${_rule}*" ;; + esac + # '/' 가 있으면 전체 경로, 없으면 파일명과 매칭 + case "$_rule" in + */*) _target="$_path" ;; + *) _target="$_leaf" ;; + esac + + # shellcheck disable=SC2254 # 의도적으로 unquoted — glob 패턴으로 쓴다 + case "$_target" in + $_rule) + if [ $_neg -eq 1 ]; then + _keep=0; MATCHED_RULE="" + else + _keep=1; MATCHED_RULE="$_rule" + fi + ;; + esac + done + return $_keep +} + +csv_escape() { + printf '"%s"' "$(printf '%s' "$1" | sed 's/"/""/g')" +} + +# ---- 준비 ----------------------------------------------------------------- +if [ "$DRY_RUN" -eq 0 ]; then + rm -rf "$OUT_DIR" + mkdir -p "$OUT_DIR/src" +fi + +MANIFEST="$OUT_DIR/MANIFEST.csv" +SUMMARY="$OUT_DIR/SUMMARY.md" +TMP_SUMMARY="$(mktemp)" +trap 'rm -f "$TMP_SUMMARY"' EXIT + +if [ "$DRY_RUN" -eq 0 ]; then + printf '\357\273\277' > "$MANIFEST" # Excel 한글용 UTF-8 BOM + echo 'module,path,ext,added_commit,added_date,author' >> "$MANIFEST" +fi + +# dry-run(목록 뽑기) 모드에서는 파일 목록만 stdout으로 내보내고 +# 진행 로그는 전부 stderr로 보낸다. 그래야 파이프/리다이렉트로 목록을 바로 가공할 수 있다. +if [ "$DRY_RUN" -eq 1 ]; then LOG=2; else LOG=1; fi + +{ + echo "저장소 루트 : $REPO_ROOT" + echo "기준 날짜 : $CUTOFF (이 날짜 직전 마지막 커밋이 base)" + echo "출력 경로 : $OUT_DIR" + [ "$DRY_RUN" -eq 1 ] && echo "모드 : DRY-RUN (목록만 출력, 복사 안 함)" + echo +} >&$LOG + +TOTAL=0 +MISSING=0 +EXCLUDED=0 + +# ---- 모듈 순회 ------------------------------------------------------------ +# 파이프 대신 process substitution 을 써서 집계 변수가 서브셸에 갇히지 않게 한다. +while IFS= read -r mline; do + [ -z "$mline" ] && continue + MOD="${mline%%|*}" + PATHS="${mline#*|}" + + if [ -n "$ONLY_MODULES" ]; then + case " $ONLY_MODULES " in + *" $MOD "*) : ;; + *) continue ;; + esac + fi + + MOD_DIR="$REPO_ROOT/$MOD" + if [ ! -d "$MOD_DIR/.git" ]; then + echo "[건너뜀] $MOD — git 저장소 아님 ($MOD_DIR)" >&$LOG + continue + fi + + # base 커밋 결정 + BASE="$(git -C "$MOD_DIR" -c core.quotepath=false rev-list -1 --before="$CUTOFF" HEAD 2>/dev/null || true)" + if [ -z "$BASE" ]; then + BASE="$EMPTY_TREE" + BASE_DESC="(컷오프 이전 커밋 없음 → 전체를 신규로 간주)" + else + BASE_DESC="$(git -C "$MOD_DIR" -c core.quotepath=false log -1 --format='%h %ad %s' --date=short "$BASE" | cut -c1-80)" + fi + + # 실제 존재하는 소스 경로만 pathspec으로 사용 + SPEC="" + for p in $PATHS; do + [ -d "$MOD_DIR/$p" ] && SPEC="$SPEC $p" + done + if [ -z "$SPEC" ]; then + echo "[건너뜀] $MOD — 설정된 소스 경로가 존재하지 않음" >&$LOG + continue + fi + + # 신규(Added) 파일만. -M 으로 rename은 신규에서 제외한다. + FILES="$(git -C "$MOD_DIR" -c core.quotepath=false diff --name-only --diff-filter=A -M "$BASE" HEAD -- $SPEC 2>/dev/null || true)" + + if [ "$INCLUDE_UNTRACKED" -eq 1 ]; then + UNTRACKED="$(git -C "$MOD_DIR" -c core.quotepath=false ls-files --others --exclude-standard -- $SPEC 2>/dev/null || true)" + FILES="$FILES +$UNTRACKED" + fi + + COUNT=0 + MISS=0 + while IFS= read -r f; do + [ -z "$f" ] && continue + is_allowed_ext "$f" || continue + + # 규칙 매칭은 <모듈>/<경로> 전체 문자열 기준 + if ! rule_keep "$MOD/$f"; then + EXCLUDED=$((EXCLUDED + 1)) + [ "$SHOW_EXCLUDED" -eq 1 ] && printf '%s\t# %s\n' "$MOD/$f" "$MATCHED_RULE" + continue + fi + [ "$SHOW_EXCLUDED" -eq 1 ] && continue + + SRC="$MOD_DIR/$f" + if [ ! -f "$SRC" ]; then + # 추가된 뒤 삭제/이동된 파일 — 납품 대상 아님 + echo " [없음] $MOD/$f" >&2 + MISS=$((MISS + 1)) + continue + fi + + COUNT=$((COUNT + 1)) + if [ "$DRY_RUN" -eq 1 ]; then + echo "$MOD/$f" # 목록은 stdout으로 (가공하기 쉽게 접두 공백 없음) + continue + fi + + DST="$OUT_DIR/src/$MOD/$f" + mkdir -p "$(dirname "$DST")" + cp -p "$SRC" "$DST" + + META="$(git -C "$MOD_DIR" -c core.quotepath=false log -1 --diff-filter=A --format='%h|%ad|%an' --date=short -- "$f" 2>/dev/null || true)" + C_HASH="${META%%|*}"; _r="${META#*|}" + C_DATE="${_r%%|*}" + C_AUTH="${_r#*|}" + BASE_F="${f##*/}" + case "$BASE_F" in *.*) EXT="${BASE_F##*.}" ;; *) EXT="" ;; esac + + { + csv_escape "$MOD"; printf ',' + csv_escape "$f"; printf ',' + csv_escape "$EXT"; printf ',' + csv_escape "$C_HASH"; printf ',' + csv_escape "$C_DATE"; printf ',' + csv_escape "$C_AUTH"; printf '\n' + } >> "$MANIFEST" + done < <(printf '%s\n' "$FILES" | sort -u) + + TOTAL=$((TOTAL + COUNT)) + MISSING=$((MISSING + MISS)) + printf '%s|%s|%s\n' "$MOD" "$COUNT" "$BASE_DESC" >> "$TMP_SUMMARY" + printf '[수집] %-22s %5s 개 base: %s\n' "$MOD" "$COUNT" "$BASE_DESC" >&$LOG +done < <(printf '%s\n' "$MODULE_LINES") + +# ---- 요약 ------------------------------------------------------------------ +if [ "$DRY_RUN" -eq 0 ]; then + { + echo "# DJB 커스텀 소스 납품본" + echo + echo "- 생성 일시: $(date '+%Y-%m-%d %H:%M:%S')" + echo "- 기준 날짜: \`$CUTOFF\` (이 날짜 직전 마지막 커밋을 base로 삼아, 이후 **신규 추가된** 파일만 수집)" + echo "- 수집 규칙: \`git diff --diff-filter=A -M HEAD\` / 확장자 allowlist / \`export-filter.rules\` 필터" + echo "- 대상 확장자: \`$ALLOW_EXT\`" + echo "- 규칙 파일: \`$(basename "$RULES_FILE")\` (${#RULES[@]} 개 규칙, 제외 ${EXCLUDED} 건)" + echo + echo "## 모듈별 수집 결과" + echo + echo "| 모듈 | 파일 수 | base 커밋 |" + echo "|---|---:|---|" + while IFS='|' read -r m n d; do + [ -z "$m" ] && continue + echo "| $m | $n | $d |" + done < "$TMP_SUMMARY" + echo + echo "**합계: ${TOTAL} 개**" + [ "$MISSING" -gt 0 ] && echo "> 추가 후 삭제/이동되어 현재 트리에 없는 파일 ${MISSING} 개는 제외됨." + echo + echo "## 파일 목록" + echo + echo "\`MANIFEST.csv\` 참조 (module, path, ext, 최초 추가 커밋/일자/작성자)." + } > "$SUMMARY" + + echo + echo "MANIFEST : $MANIFEST" + echo "SUMMARY : $SUMMARY" + + if [ "$MAKE_ZIP" -eq 1 ]; then + ZIP_NAME="djb-custom-export-$(date '+%Y%m%d').zip" + ZIP_PATH="$(dirname "$OUT_DIR")/$ZIP_NAME" + rm -f "$ZIP_PATH" + ( cd "$OUT_DIR" && zip -qr "$ZIP_PATH" . ) + echo "ZIP : $ZIP_PATH" + fi +fi diff --git a/script/djb-custom-export/export-filter.rules b/script/djb-custom-export/export-filter.rules new file mode 100644 index 0000000..3dba8bc --- /dev/null +++ b/script/djb-custom-export/export-filter.rules @@ -0,0 +1,74 @@ +# DJB 커스텀 소스 납품 - 포함/제외 규칙 +# +# .gitignore 와 비슷하게 동작한다. +# - 한 줄에 패턴 하나. '#' 주석, 빈 줄 무시. +# - 기본은 "모두 포함". 패턴에 걸리면 제외된다. +# - '!' 로 시작하면 예외(다시 포함). +# - 뒤에 오는 규칙이 앞 규칙을 덮는다 (마지막 매칭 승리). → 순서가 중요하다. +# - 패턴에 '/' 가 있으면 <모듈>/<경로> 전체와 glob 매칭, +# 없으면 파일명(basename)과만 매칭한다. +# - '/' 로 끝나면 그 디렉터리 하위 전체를 뜻한다. +# - '*' 는 '/' 도 포함해 매칭한다(단순 glob). +# +# 매칭 대상 경로 예: +# eapim-portal/src/main/java/com/eactive/apim/portal/djb/webhook/WebhookService.java +# eapim-admin/WebContent/jsp/onl/apim/webhook/webhookList.jsp +# +# 확인: ./export-custom.sh --dry-run (남는 목록) +# ./export-custom.sh --dry-run --show-excluded (규칙에 걸려 빠진 목록) + +# =========================================================================== +# 1) 제품 업그레이드로 유입된 신규 파일 — 커스텀 아니므로 제외 +# filelist-no-djb-marker.txt(djb/custom 마커 없는 신규 파일)를 검토하며 채운다. +# =========================================================================== +# 예시) +# eapim-online/src/main/java/com/eactive/eai/agent/inflow/ +# eapim-admin/src/main/java/com/eactive/eai/rms/common/acl/sitemap/ + + +eapim-portal/src/main/java/com/eactive/apim/gateway/data/statistics/ +eapim-portal/src/main/java/com/eactive/apim/portal/apps/ +eapim-portal/src/main/java/com/eactive/apim/portal/apps/apis/ +eapim-portal/src/main/java/com/eactive/apim/portal/apps/app/ +eapim-portal/src/main/java/com/eactive/apim/portal/apps/auth/ +eapim-portal/src/main/java/com/eactive/apim/portal/apps/login/ +eapim-portal/src/main/java/com/eactive/apim/portal/apps/session/ +eapim-portal/src/main/java/com/eactive/apim/portal/config/ +eapim-portal/src/main/java/com/eactive/apim/portal/common/ +eapim-portal/src/main/resources/templates/views/fragment/popup/ +eapim-portal/src/test/ + +elink-portal-common/src/main/java/com/eactive/apim/portal/common/util/ +elink-portal-common/src/main/java/com/eactive/apim/portal/jpa/ +elink-portal-common/src/main/java/com/eactive/apim/portal/menu/ + + +# =========================================================================== +# 2) 위 제외에 걸렸더라도 되살릴 예외 +# djb 마커가 붙은 파일은 어떤 경우에도 커스텀이다. +# ※ 아래 3)의 빌드 산출물 제외보다 반드시 앞에 와야 한다. +# =========================================================================== +!*djb* +!*DJB* +!*Djb* + + +# =========================================================================== +# 3) 무조건 제외 (최종 승리) — 소스가 아니거나 서드파티 +# =========================================================================== +*/build/ +*/out/ +*/bin/ +*/.gradle/ +*/.metadata/ +*/node_modules/ +*/generated/ +*/design-backup/ +*/WebContent/plugins/ + +# SafeDB(damo) 벤더 API stub — 컴파일용 껍데기라 납품 대상 아님 +damo-manager/src/stub/ + +# 서드파티 폴더 안에 섞여 있는 우리 커스텀 파일은 되살린다 +# (예: WebContent/plugins/swaggerUI/djb-swagger-i18n.js) +!*/WebContent/plugins/*djb* diff --git a/script/djb-custom-export/filelist-no-djb-marker.txt b/script/djb-custom-export/filelist-no-djb-marker.txt new file mode 100644 index 0000000..1b12182 --- /dev/null +++ b/script/djb-custom-export/filelist-no-djb-marker.txt @@ -0,0 +1,157 @@ +eapim-portal/src/main/java/com/eactive/apim/gateway/data/statistics/entity/ApiStatsDay.java +eapim-portal/src/main/java/com/eactive/apim/gateway/data/statistics/entity/ApiStatsDayId.java +eapim-portal/src/main/java/com/eactive/apim/gateway/data/statistics/entity/ApiStatsHour.java +eapim-portal/src/main/java/com/eactive/apim/gateway/data/statistics/entity/ApiStatsHourId.java +eapim-portal/src/main/java/com/eactive/apim/gateway/data/statistics/entity/ApiStatsMonth.java +eapim-portal/src/main/java/com/eactive/apim/gateway/data/statistics/entity/ApiStatsMonthId.java +eapim-portal/src/main/java/com/eactive/apim/gateway/data/statistics/entity/GwApiStatus.java +eapim-portal/src/main/java/com/eactive/apim/gateway/data/statistics/entity/GwAuthClient.java +eapim-portal/src/main/java/com/eactive/apim/gateway/data/statistics/entity/GwSvcInfo.java +eapim-portal/src/main/java/com/eactive/apim/gateway/data/statistics/repository/ApiStatsDayRepository.java +eapim-portal/src/main/java/com/eactive/apim/gateway/data/statistics/repository/ApiStatsHourRepository.java +eapim-portal/src/main/java/com/eactive/apim/gateway/data/statistics/repository/ApiStatsMonthRepository.java +eapim-portal/src/main/java/com/eactive/apim/gateway/data/statistics/repository/GwApiStatusRepository.java +eapim-portal/src/main/java/com/eactive/apim/gateway/data/statistics/repository/GwAuthClientRepository.java +eapim-portal/src/main/java/com/eactive/apim/gateway/data/statistics/repository/GwSvcInfoRepository.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/apis/filter/ApiTesterAuditLogger.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/app/service/AdminGatewayClient.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/auth/AuthNoticeProperties.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/dto/TwoFactorChannel.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/dto/TwoFactorInfoResponse.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/dto/TwoFactorSendResponse.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/dto/TwoFactorVerifyResponse.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/StepUpAuthInterceptor.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/StepUpPasswordController.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/StepUpProtectedPaths.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/TwoFactorCleanupScheduler.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/TwoFactorContext.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/TwoFactorController.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/TwoFactorProperties.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/TwoFactorService.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/community/notice/dto/IncidentAffectedApiDTO.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/community/partnership/dto/PartnershipApplicationSummaryDTO.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/login/constants/LoginFailureReason.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/login/constants/LoginType.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/login/controller/DuplicateLoginController.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/login/service/DuplicateLoginService.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/login/service/LoginFinalizer.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/ReadinessController.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/session/controller/SessionApiController.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/session/entity/UserSession.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/session/filter/SessionValidationFilter.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/session/repository/UserSessionRepository.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/session/service/UserSessionService.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/statistics/dto/ApiStatisticsPeriodDto.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/statistics/repository/entity/JobInfo.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/statistics/repository/JobInfoRepository.java +eapim-portal/src/main/java/com/eactive/apim/portal/apps/user/service/UserRoleHistoryService.java +eapim-portal/src/main/java/com/eactive/apim/portal/common/exception/UserErrorMessageResolver.java +eapim-portal/src/main/java/com/eactive/apim/portal/common/migration/LegacyEncryptionMigrationController.java +eapim-portal/src/main/java/com/eactive/apim/portal/common/security/ClientGuardService.java +eapim-portal/src/main/java/com/eactive/apim/portal/common/security/LoginLockPolicy.java +eapim-portal/src/main/java/com/eactive/apim/portal/config/PasswordChangeEnforcementInterceptor.java +eapim-portal/src/main/java/com/eactive/apim/portal/config/PasswordEnforcementPolicy.java +eapim-portal/src/main/java/com/eactive/apim/portal/config/PortalPropertyDuplicateChecker.java +eapim-portal/src/main/java/com/eactive/apim/portal/config/StartupInfoPrinter.java +eapim-portal/src/main/resources/menu.yml +eapim-portal/src/main/resources/roles.yml +eapim-portal/src/main/resources/static/js/api-selector.js +eapim-portal/src/main/resources/static/js/password-policy.js +eapim-portal/src/main/resources/static/js/popup/two-factor-auth.js +eapim-portal/src/main/resources/static/sass/components/_board-common.scss +eapim-portal/src/main/resources/static/sass/components/_password-policy.scss +eapim-portal/src/main/resources/static/sass/components/_session-timer.scss +eapim-portal/src/main/resources/static/sass/components/_toast.scss +eapim-portal/src/main/resources/static/sass/components/_two-factor.scss +eapim-portal/src/main/resources/static/sass/pages/_api-status.scss +eapim-portal/src/main/resources/static/sass/pages/_webhook.scss +eapim-portal/src/main/resources/templates/views/apps/auth/stepupPassword.html +eapim-portal/src/main/resources/templates/views/apps/auth/twoFactorChallenge.html +eapim-portal/src/main/resources/templates/views/apps/community/mainFaqDetail.html +eapim-portal/src/main/resources/templates/views/apps/register/signupVerificationEmail.html +eapim-portal/src/main/resources/templates/views/apps/service/oauth2-guide.html +eapim-portal/src/main/resources/templates/views/apps/service/webhook-dev-guide.html +eapim-portal/src/main/resources/templates/views/apps/webhook/webhookEmpty.html +eapim-portal/src/main/resources/templates/views/apps/webhook/webhookList.html +eapim-portal/src/main/resources/templates/views/apps/webhook/webhookModifyStep1.html +eapim-portal/src/main/resources/templates/views/apps/webhook/webhookModifyStep2.html +eapim-portal/src/main/resources/templates/views/apps/webhook/webhookModifyStep3.html +eapim-portal/src/main/resources/templates/views/apps/webhook/webhookRegisterStep1.html +eapim-portal/src/main/resources/templates/views/apps/webhook/webhookRegisterStep2.html +eapim-portal/src/main/resources/templates/views/apps/webhook/webhookRegisterStep3.html +eapim-portal/src/main/resources/templates/views/fragment/api_selector.html +eapim-portal/src/main/resources/templates/views/fragment/popup/cancelInvitationPopup.html +eapim-portal/src/main/resources/templates/views/fragment/popup/terminateRequestPopup.html +eapim-portal/src/main/resources/templates/views/fragment/popup/twoFactorAuthPopup.html +eapim-portal/src/test/java/com/eactive/apim/portal/common/exception/UserErrorMessageResolverTest.java +eapim-portal/src/test/java/com/eactive/apim/portal/common/util/PhoneNumberUtilTest.java +eapim-portal/src/test/java/com/eactive/apim/portal/common/util/StringMaskingUtilTest.java +eapim-admin/src/main/java/com/eactive/eai/rms/common/acl/sitemap/SitemapController.java +eapim-admin/src/main/java/com/eactive/eai/rms/common/acl/sitemap/SitemapService.java +eapim-admin/src/main/java/com/eactive/eai/rms/common/acl/sitemap/SitemapServiceImpl.java +eapim-admin/src/main/java/com/eactive/eai/rms/common/acl/sitemap/ui/SitemapNode.java +eapim-admin/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/messagerequest/MessageRequestEmsRepository.java +eapim-admin/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/messagerequest/MessageRequestService.java +eapim-admin/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/messagerequest/MessageRequestUISearch.java +eapim-admin/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/portalinquiry/PortalInquiryCommentRepository.java +eapim-admin/src/main/java/com/eactive/eai/rms/data/entity/onl/apim/portalinquiry/PortalInquiryCommentService.java +eapim-admin/src/main/java/com/eactive/eai/rms/data/entity/onl/security/CryptoModuleConfigDataService.java +eapim-admin/src/main/java/com/eactive/eai/rms/data/entity/onl/security/CryptoModuleConfigDataServiceImpl.java +eapim-admin/src/main/java/com/eactive/eai/rms/data/entity/onl/security/CryptoModuleConfigRepository.java +eapim-admin/src/main/java/com/eactive/eai/rms/onl/apim/authserver/ClientBlockApiController.java +eapim-admin/src/main/java/com/eactive/eai/rms/onl/apim/masking/MessagePatternMaskingUtils.java +eapim-admin/src/main/java/com/eactive/eai/rms/onl/apim/messagerequest/MessageRequestManController.java +eapim-admin/src/main/java/com/eactive/eai/rms/onl/apim/messagerequest/MessageRequestManService.java +eapim-admin/src/main/java/com/eactive/eai/rms/onl/apim/messagerequest/MessageRequestUI.java +eapim-admin/src/main/java/com/eactive/eai/rms/onl/apim/portalInquiry/PortalInquiryClosingService.java +eapim-admin/src/main/java/com/eactive/eai/rms/onl/apim/portalInquiry/PortalInquiryCommentUI.java +eapim-admin/src/main/java/com/eactive/eai/rms/onl/apim/portalInquiry/PortalInquiryCommentUIMapper.java +eapim-admin/src/main/java/com/eactive/eai/rms/onl/apim/portalmenu/PortalMenuCacheClient.java +eapim-admin/src/main/java/com/eactive/eai/rms/onl/apim/portalmenu/PortalMenuManController.java +eapim-admin/src/main/java/com/eactive/eai/rms/onl/apim/portalmenu/PortalMenuManService.java +eapim-admin/src/main/java/com/eactive/eai/rms/onl/apim/portalmenu/PortalMenuUI.java +eapim-admin/src/main/java/com/eactive/eai/rms/onl/apim/portalmenu/PortalMenuUIMapper.java +eapim-admin/src/main/java/com/eactive/eai/rms/onl/apim/portalnotice/IncidentAffectedApiUI.java +eapim-admin/src/main/java/com/eactive/eai/rms/onl/apim/portalnotice/IncidentTimelineUI.java +eapim-admin/src/main/java/com/eactive/eai/rms/onl/manage/crypto/CryptoModuleConfigManController.java +eapim-admin/src/main/java/com/eactive/eai/rms/onl/manage/crypto/CryptoModuleConfigManService.java +eapim-admin/src/main/java/com/eactive/eai/rms/onl/manage/crypto/CryptoModuleConfigUI.java +eapim-admin/src/main/java/com/eactive/eai/rms/onl/manage/crypto/CryptoModuleConfigUiMapper.java +eapim-admin/src/main/java/com/eactive/eai/rms/onl/manage/inflow/inflow/InflowClientControlManController.java +eapim-admin/src/main/resources/apistatus-draft.yml +eapim-admin/src/main/resources/logback-rinjae_ma.xml +eapim-admin/src/test/java/com/eactive/eai/rms/onl/apim/masking/MaskingUtilsTest.java +eapim-admin/src/test/java/com/eactive/eai/rms/onl/apim/masking/MessagePatternMaskingUtilsTest.java +eapim-admin/WebContent/damo.jsp +eapim-admin/WebContent/jsp/common/acl/sitemap/sitemapMan.jsp +eapim-admin/WebContent/jsp/onl/admin/inflow/inflowClientControlMan.jsp +eapim-admin/WebContent/jsp/onl/admin/inflow/inflowClientControlManDetail.jsp +eapim-admin/WebContent/jsp/onl/admin/rule/transform2/transform2ManApiPopup.jsp +eapim-admin/WebContent/jsp/onl/admin/security/cryptoModuleMan.jsp +eapim-admin/WebContent/jsp/onl/admin/security/cryptoModuleManDetail.jsp +eapim-admin/WebContent/jsp/onl/apim/messagerequest/messageRequestMan.jsp +eapim-admin/WebContent/jsp/onl/apim/messagerequest/messageRequestManDetail.jsp +eapim-admin/WebContent/jsp/onl/apim/portalmenu/portalMenuMan.jsp +eapim-admin/WebContent/jsp/onl/apim/webhook/webhookSendLogMan.jsp +eapim-admin/WebContent/jsp/onl/apim/webhook/webhookSendLogManDetail.jsp +eapim-admin/WebContent/jsp/onl/kjb/statistics/apiUseStatsMan.jsp +elink-portal-common/src/main/java/com/eactive/apim/portal/apprequest/entity/GwAction.java +elink-portal-common/src/main/java/com/eactive/apim/portal/common/util/IpAddressMatcher.java +elink-portal-common/src/main/java/com/eactive/apim/portal/common/util/PhoneNumberUtil.java +elink-portal-common/src/main/java/com/eactive/apim/portal/jpa/PersonalDataEncryptConverter.java +elink-portal-common/src/main/java/com/eactive/apim/portal/jpa/UuidV7Generator.java +elink-portal-common/src/main/java/com/eactive/apim/portal/menu/entity/PortalMenuItem.java +elink-portal-common/src/main/java/com/eactive/apim/portal/menu/entity/PortalMenuPlacement.java +elink-portal-common/src/main/java/com/eactive/apim/portal/menu/entity/PortalRole.java +elink-portal-common/src/main/java/com/eactive/apim/portal/menu/entity/PortalRoleAuthority.java +elink-portal-common/src/main/java/com/eactive/apim/portal/menu/entity/PortalRoleAuthorityId.java +elink-portal-common/src/main/java/com/eactive/apim/portal/menu/repository/PortalMenuItemRepository.java +elink-portal-common/src/main/java/com/eactive/apim/portal/menu/repository/PortalMenuPlacementRepository.java +elink-portal-common/src/main/java/com/eactive/apim/portal/menu/repository/PortalRoleAuthorityRepository.java +elink-portal-common/src/main/java/com/eactive/apim/portal/menu/repository/PortalRoleRepository.java +elink-portal-common/src/main/java/com/eactive/apim/portal/menu/service/PortalMenuDataService.java +elink-portal-common/src/main/java/com/eactive/apim/portal/portaluser/entity/UserRoleHistory.java +elink-portal-common/src/main/java/com/eactive/apim/portal/portaluser/repository/UserRoleHistoryRepository.java +elink-portal-common/src/main/java/com/eactive/apim/portal/qna/entity/InquiryComment.java +elink-portal-common/src/main/java/com/eactive/apim/portal/qna/entity/VisibilityScope.java +elink-portal-common/src/test/java/com/eactive/apim/portal/common/util/IpAddressMatcherTest.java diff --git a/script/djb-custom-export/filelist.txt b/script/djb-custom-export/filelist.txt new file mode 100644 index 0000000..7254cdd --- /dev/null +++ b/script/djb-custom-export/filelist.txt @@ -0,0 +1,173 @@ +eapim-portal/src/main/java/com/eactive/apim/portal/custom/config/DjbPasswordEncoder.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/apistatus/controller/ApiStatusController.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/ActiveIncidentDTO.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/AffectedApiDTO.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/ApiCurrentStatusDTO.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/ApiOptionDTO.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/DailyStatDTO.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/IssueDateEntryDTO.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/MaintenanceCardDTO.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/MyApiStatusDTO.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/PastIssueCardDTO.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/TimelineEntryDTO.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/apistatus/repository/ApiStatusIncidentQueryRepository.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/apistatus/service/ApiCurrentStatusService.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/apistatus/service/ApiStatusAssembler.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/apistatus/service/ApiStatusCatalogService.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/apistatus/service/ApiStatusIssueHistoryService.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/apistatus/service/ApiStatusQueryService.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/apistatus/service/ApiStatusSupport.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/apistatus/service/ApiStatusUptimeService.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/apistatus/service/MyApiStatusQueryService.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/community/qna/comment/controller/InquiryCommentController.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/community/qna/comment/dto/InquiryCommentCreateRequest.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/community/qna/comment/dto/InquiryCommentDTO.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/community/qna/comment/repository/InquiryCommentRepository.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/community/qna/comment/repository/UserInfoRepository.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/community/qna/comment/service/InquiryCommentFacade.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/community/qna/comment/service/InquiryCommentFacadeImpl.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/community/qna/comment/service/InquiryCommentService.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/community/qna/constant/DjbInquiryStatus.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/community/qna/exception/InquiryClosedException.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/community/qna/exception/InquiryCommentNotOwnedException.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/community/qna/support/InquiryCommentPermissionChecker.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/footer/RelatedSite.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/footer/RelatedSiteService.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/menu/MenuAccessInterceptor.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/menu/MenuInternalController.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/menu/MenuModelAdvice.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/menu/MenuNode.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/menu/MenuSeeder.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/menu/MenuService.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/menu/MenuView.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/menu/PortalMenuYmlProperties.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/menu/PortalRolesProperties.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/swing/EmployeeIdPolicy.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/swing/SwingMessageWriter.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/swing/SwingNotifier.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/swing/SwingNotifyProperties.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/swing/config/SwingAsyncConfig.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/swing/repository/SwingStaffRepository.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/testbed/advice/DjbTestbedExceptionHandler.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/testbed/config/DjbTestbedGatewayProperty.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/testbed/controller/DjbTestbedAuthController.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/testbed/controller/DjbTestbedSpecController.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/testbed/dto/DjbCredentialOptionDto.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/testbed/dto/DjbCredentialSecretDto.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/testbed/dto/DjbTestbedContextDto.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/testbed/enums/DjbAuthType.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/testbed/enums/DjbGatewayMode.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/testbed/exception/DjbUnsupportedAuthTypeException.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/testbed/service/DjbSwaggerSpecEnricher.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/testbed/service/DjbTestbedAuthService.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/testbed/service/DjbTestbedSpecServerRewriter.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/webhook/controller/WebhookController.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/webhook/dto/WebhookCreatedResult.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/webhook/dto/WebhookDTO.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/webhook/dto/WebhookEventTypeDTO.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/webhook/dto/WebhookRegistrationDTO.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/webhook/exception/WebhookAlreadyExistsException.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/webhook/exception/WebhookNotFoundException.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/webhook/mapper/WebhookMapper.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/webhook/repository/WebhookRequestApiRepository.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/webhook/repository/WebhookRequestEventRepository.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/webhook/repository/WebhookRequestRepository.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/webhook/repository/entity/WebhookRequest.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/webhook/repository/entity/WebhookRequestApi.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/webhook/repository/entity/WebhookRequestApiId.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/webhook/repository/entity/WebhookRequestEvent.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/webhook/repository/entity/WebhookRequestEventId.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/webhook/service/WebhookEventTypeProvider.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/webhook/service/WebhookSecretGenerator.java +eapim-portal/src/main/java/com/eactive/apim/portal/djb/webhook/service/WebhookService.java +eapim-portal/src/main/resources/menu.yml +eapim-portal/src/main/resources/roles.yml +eapim-portal/src/main/resources/static/favicon.png +eapim-portal/src/main/resources/static/img/avatar1.svg +eapim-portal/src/main/resources/static/img/avatar2.svg +eapim-portal/src/main/resources/static/img/favicon/apple-touch-icon.png +eapim-portal/src/main/resources/static/img/favicon/favicon-16x16.png +eapim-portal/src/main/resources/static/img/favicon/favicon-180x180.png +eapim-portal/src/main/resources/static/img/favicon/favicon-192x192.png +eapim-portal/src/main/resources/static/img/favicon/favicon-32x32.png +eapim-portal/src/main/resources/static/img/favicon/favicon-48x48.png +eapim-portal/src/main/resources/static/img/favicon/favicon-512x512.png +eapim-portal/src/main/resources/static/img/favicon/favicon.png +eapim-portal/src/main/resources/static/img/icon/icon_check_green.png +eapim-portal/src/main/resources/static/img/icon/img_icon.png +eapim-portal/src/main/resources/static/img/keyimage/api_img.png +eapim-portal/src/main/resources/static/img/keyimage/api_img.svg +eapim-portal/src/main/resources/static/img/keyimage/faq_img.png +eapim-portal/src/main/resources/static/img/keyimage/faq_img.svg +eapim-portal/src/main/resources/static/img/keyimage/feedback_img.svg +eapim-portal/src/main/resources/static/img/keyimage/notice_img.png +eapim-portal/src/main/resources/static/img/keyimage/notice_img.svg +eapim-portal/src/main/resources/static/img/keyimage/q&a_img.png +eapim-portal/src/main/resources/static/img/keyimage/q&a_img.svg +eapim-portal/src/main/resources/static/img/keyimage/sigdnUp_img.svg +eapim-portal/src/main/resources/static/img/keyimage/signUp_img.png +eapim-portal/src/main/resources/static/img/logo/logo-djb.png +eapim-portal/src/main/resources/static/img/logo/logo-jjb.png +eapim-portal/src/main/resources/static/img/logo/logo-jjb_white.png +eapim-portal/src/main/resources/static/js/api-selector.js +eapim-portal/src/main/resources/static/js/djb/api-status-issues.js +eapim-portal/src/main/resources/static/js/djb/api-status.js +eapim-portal/src/main/resources/static/js/djb/client-guard.js +eapim-portal/src/main/resources/static/js/djb/inquiry-comments.js +eapim-portal/src/main/resources/static/js/djb/inquiry-view.js +eapim-portal/src/main/resources/static/js/djb/toast.js +eapim-portal/src/main/resources/static/js/password-policy.js +eapim-portal/src/main/resources/static/js/popup/two-factor-auth.js +eapim-portal/src/main/resources/static/plugins/swaggerUI/djb-swagger-i18n.js +eapim-portal/src/main/resources/static/plugins/swaggerUI/djb-swagger-response.js +eapim-portal/src/main/resources/static/plugins/swaggerUI/djb-swagger-snippet-panel.js +eapim-portal/src/main/resources/static/plugins/swaggerUI/djb-swagger-snippets.js +eapim-portal/src/main/resources/static/plugins/swaggerUI/djb-swagger-testbed.css +eapim-portal/src/main/resources/static/plugins/swaggerUI/djb-swagger-validator.js +eapim-portal/src/main/resources/static/sass/base/_djb-font.scss +eapim-portal/src/main/resources/static/sass/components/_board-common.scss +eapim-portal/src/main/resources/static/sass/components/_djb-inquiry-comments.scss +eapim-portal/src/main/resources/static/sass/components/_password-policy.scss +eapim-portal/src/main/resources/static/sass/components/_session-timer.scss +eapim-portal/src/main/resources/static/sass/components/_toast.scss +eapim-portal/src/main/resources/static/sass/components/_two-factor.scss +eapim-portal/src/main/resources/static/sass/pages/_api-status.scss +eapim-portal/src/main/resources/static/sass/pages/_webhook.scss +eapim-portal/src/main/resources/templates/views/apps/auth/stepupPassword.html +eapim-portal/src/main/resources/templates/views/apps/auth/twoFactorChallenge.html +eapim-portal/src/main/resources/templates/views/apps/community/djb/fragments-inquiry-comments.html +eapim-portal/src/main/resources/templates/views/apps/community/mainFaqDetail.html +eapim-portal/src/main/resources/templates/views/apps/register/signupVerificationEmail.html +eapim-portal/src/main/resources/templates/views/apps/service/oauth2-guide.html +eapim-portal/src/main/resources/templates/views/apps/service/webhook-dev-guide.html +eapim-portal/src/main/resources/templates/views/apps/webhook/webhookEmpty.html +eapim-portal/src/main/resources/templates/views/apps/webhook/webhookList.html +eapim-portal/src/main/resources/templates/views/apps/webhook/webhookModifyStep1.html +eapim-portal/src/main/resources/templates/views/apps/webhook/webhookModifyStep2.html +eapim-portal/src/main/resources/templates/views/apps/webhook/webhookModifyStep3.html +eapim-portal/src/main/resources/templates/views/apps/webhook/webhookRegisterStep1.html +eapim-portal/src/main/resources/templates/views/apps/webhook/webhookRegisterStep2.html +eapim-portal/src/main/resources/templates/views/apps/webhook/webhookRegisterStep3.html +eapim-portal/src/main/resources/templates/views/djb/apistatus/index.html +eapim-portal/src/main/resources/templates/views/djb/apistatus/issues.html +eapim-portal/src/main/resources/templates/views/fragment/api_selector.html +eapim-portal/src/main/resources/templates/views/fragment/djbank/footer.html +eapim-portal/src/main/resources/templates/views/fragment/djbank/header_container.html +eapim-portal/src/main/resources/templates/views/fragment/djbank/service_sidebar.html +eapim-portal/src/main/resources/templates/views/fragment/djbank/terms_agreements.html +elink-portal-common/src/main/java/com/eactive/apim/portal/apprequest/entity/GwAction.java +elink-portal-common/src/main/java/com/eactive/apim/portal/djb/apistatus/incident/entity/DjbApistatusIncident.java +elink-portal-common/src/main/java/com/eactive/apim/portal/djb/apistatus/incident/entity/DjbApistatusIncidentApi.java +elink-portal-common/src/main/java/com/eactive/apim/portal/djb/apistatus/incident/entity/DjbApistatusIncidentApiId.java +elink-portal-common/src/main/java/com/eactive/apim/portal/djb/apistatus/incident/entity/DjbApistatusIncidentTimeline.java +elink-portal-common/src/main/java/com/eactive/apim/portal/djb/apistatus/incident/entity/IncidentKind.java +elink-portal-common/src/main/java/com/eactive/apim/portal/djb/apistatus/incident/entity/IncidentState.java +elink-portal-common/src/main/java/com/eactive/apim/portal/djb/apistatus/incident/repository/DjbApistatusIncidentApiRepository.java +elink-portal-common/src/main/java/com/eactive/apim/portal/djb/apistatus/incident/repository/DjbApistatusIncidentRepository.java +elink-portal-common/src/main/java/com/eactive/apim/portal/djb/apistatus/incident/repository/DjbApistatusIncidentTimelineRepository.java +elink-portal-common/src/main/java/com/eactive/apim/portal/portaluser/entity/UserRoleHistory.java +elink-portal-common/src/main/java/com/eactive/apim/portal/portaluser/repository/UserRoleHistoryRepository.java +elink-portal-common/src/main/java/com/eactive/apim/portal/qna/entity/InquiryComment.java +elink-portal-common/src/main/java/com/eactive/apim/portal/qna/entity/VisibilityScope.java +damo-manager/src/main/java/com/eactive/ext/djb/DamoCli.java +damo-manager/src/main/java/com/eactive/ext/djb/DamoManager.java diff --git a/script/djb-custom-export/modules.conf b/script/djb-custom-export/modules.conf new file mode 100644 index 0000000..ebdb2c2 --- /dev/null +++ b/script/djb-custom-export/modules.conf @@ -0,0 +1,26 @@ +# DJB 커스텀 소스 납품 수집 설정 +# +# 형식: +# MODULE|<모듈 디렉터리>|<소스 경로들, 공백 구분> +# EXT|<수집 대상 확장자, 콤마 구분> +# +# - 모듈 디렉터리는 저장소 루트(djb-eapim) 기준 상대 경로. +# - 소스 경로가 실제로 없으면 조용히 건너뛴다. +# - '#'으로 시작하는 줄과 빈 줄은 무시된다. +# - 파일 단위 포함/제외는 export-filter.rules 에서 관리한다. + +MODULE|eapim-portal|src/main +#MODULE|eapim-admin|src WebContent +MODULE|elink-portal-common|src/main +MODULE|damo-manager|src + +# eapim-online 은 납품 범위에서 제외한다. +# Gateway 제품(elink-online) 본체라 신규 파일 중 무엇이 DJB 커스텀이고 +# 무엇이 제품 업그레이드분인지 포털 쪽에서 판정할 수 없다. +# 규칙 파일이 아니라 여기서 빼는 이유: export-filter.rules 의 '!*djb*' 예외가 +# 뒤에 있어 DJBEncrypt.java 같은 파일이 되살아나기 때문. +#MODULE|eapim-online|src elink-online-common/src elink-online-core/src elink-online-core-jpa/src elink-online-emsclient/src elink-online-transformer/src + +# 수집 대상 확장자 (확장자 없는 파일은 제외된다) +# EXT|java,xml,yml,yaml,properties,sql,html,jsp,jspf,tag,js,ts,css,scss,json,txt,md +EXT|java,xml,yml,yaml,properties,html,jsp,jspf,tag,js,ts,css,scss,jpg,jpeg,png,gif,svg,ico diff --git a/script/portal/oracle/1_portal.sql b/script/portal/oracle/1_portal.sql index 520b32d..774a557 100644 --- a/script/portal/oracle/1_portal.sql +++ b/script/portal/oracle/1_portal.sql @@ -254,7 +254,8 @@ CREATE TABLE DVPOWN.PT_MESSAGE_RECIPIENT ) ; -create table DVPOWN.PT_TOKEN +create table PT_TOKEN +( ( TOKEN VARCHAR2(255) not null primary key, diff --git a/script/portal/oracle/5_ptl_user_session.sql b/script/portal/oracle/5_ptl_user_session.sql new file mode 100644 index 0000000..e0b3f2d --- /dev/null +++ b/script/portal/oracle/5_ptl_user_session.sql @@ -0,0 +1,36 @@ +-- ============================================================================= +-- 세션 타이머 / 유휴 자동 로그아웃 / 중복로그인 처리용 사용자 세션 추적 테이블 +-- 대상: EMS 스키마 소유자(EMSAPP 등)로 접속하여 실행 +-- 엔티티: com.eactive.apim.portal.apps.session.entity.UserSession +-- ============================================================================= + +CREATE TABLE PTL_USER_SESSION +( + SESSION_ID VARCHAR2(128) NOT NULL, + USER_ID VARCHAR2(36) NOT NULL, + LOGIN_ID VARCHAR2(200) NOT NULL, + LOGIN_TIME TIMESTAMP(6) NOT NULL, + LAST_ACCESS_TIME TIMESTAMP(6) NOT NULL, + IP_ADDRESS VARCHAR2(45), + USER_AGENT VARCHAR2(500), + FORCE_LOGOUT CHAR(1) DEFAULT 'N' NOT NULL, + CONSTRAINT PK_PTL_USER_SESSION PRIMARY KEY (SESSION_ID) +); + +-- 중복로그인 강제 로그아웃 / 활성 세션 조회는 LOGIN_ID 기준 +CREATE INDEX IX_PTL_USER_SESSION_LOGIN ON PTL_USER_SESSION (LOGIN_ID); + +COMMENT ON TABLE PTL_USER_SESSION IS '포털 사용자 세션 추적 (타이머/유휴 로그아웃/중복로그인)'; +COMMENT ON COLUMN PTL_USER_SESSION.SESSION_ID IS 'HTTP 세션 ID (PK)'; +COMMENT ON COLUMN PTL_USER_SESSION.USER_ID IS '포털 사용자 ID'; +COMMENT ON COLUMN PTL_USER_SESSION.LOGIN_ID IS '로그인 ID (이메일, 소문자)'; +COMMENT ON COLUMN PTL_USER_SESSION.LOGIN_TIME IS '로그인 시각'; +COMMENT ON COLUMN PTL_USER_SESSION.LAST_ACCESS_TIME IS '마지막 접근 시각 (만료 판단 기준)'; +COMMENT ON COLUMN PTL_USER_SESSION.IP_ADDRESS IS '접속 IP'; +COMMENT ON COLUMN PTL_USER_SESSION.USER_AGENT IS 'User-Agent'; +COMMENT ON COLUMN PTL_USER_SESSION.FORCE_LOGOUT IS '강제 로그아웃 플래그 (Y/N)'; + +-- (선택) 세션 타임아웃(분) 설정값을 미리 지정. 미삽입 시 최초 접근 때 기본값 15로 자동 생성됨. +-- INSERT INTO PTL_PROPERTY (PROPERTY_GROUP_NAME, PROPERTY_NAME, PROPERTY_VALUE, PROPERTY_DESC) +-- VALUES ('Portal', 'session.timeout.minutes', '15', '세션 타임아웃 시간 (분)'); +-- COMMIT; diff --git a/script/portal/oracle/6_ptl_two_factor_auth_recipient_key.sql b/script/portal/oracle/6_ptl_two_factor_auth_recipient_key.sql new file mode 100644 index 0000000..96139b5 --- /dev/null +++ b/script/portal/oracle/6_ptl_two_factor_auth_recipient_key.sql @@ -0,0 +1,23 @@ +-- ============================================================================= +-- 2FA 인증코드 조회용 결정적 해시 키 컬럼 추가 +-- 대상: EMS 스키마 소유자(EMSAPP 등)로 접속하여 실행 +-- 엔티티: com.eactive.apim.portal.portaluser.entity.TwoFactorAuth +-- +-- 배경: RECIPIENT(이메일/휴대폰) 컬럼에 PersonalDataEncryptConverter 암호화가 +-- 적용되면서, 값 동등 조회(WHERE RECIPIENT = ?)와 중복정리가 깨져 +-- 인증코드 검증이 "일치하지 않습니다"로 실패함. +-- 해결: RECIPIENT 는 암호화 보관(감사/표시용)을 유지하고, 평문의 결정적 해시 +-- (HMAC-SHA256, 소문자 hex 64자)를 RECIPIENT_KEY 에 저장하여 조회/정리에 사용. +-- +-- ※ hibernate.hbm2ddl.auto=validate 이므로, 신규 엔티티 배포(재기동) "전"에 +-- 반드시 이 스크립트를 먼저 실행해야 기동 검증을 통과함. +-- ============================================================================= + +ALTER TABLE PTL_TWO_FACTOR_AUTH ADD (RECIPIENT_KEY VARCHAR2(64)); + +-- 인증코드 조회/중복정리는 RECIPIENT_KEY 기준 +CREATE INDEX IX_PTL_TWO_FACTOR_AUTH_RKEY ON PTL_TWO_FACTOR_AUTH (RECIPIENT_KEY); + +COMMENT ON COLUMN PTL_TWO_FACTOR_AUTH.RECIPIENT_KEY IS '수신자 결정적 해시 조회키 (HMAC-SHA256 hex). RECIPIENT 암호화로 깨지는 동등조회/중복정리용'; + +COMMIT; diff --git a/src/main/java/com/eactive/apim/gateway/data/statistics/entity/ApiStatsDay.java b/src/main/java/com/eactive/apim/gateway/data/statistics/entity/ApiStatsDay.java new file mode 100644 index 0000000..080aec8 --- /dev/null +++ b/src/main/java/com/eactive/apim/gateway/data/statistics/entity/ApiStatsDay.java @@ -0,0 +1,72 @@ +package com.eactive.apim.gateway.data.statistics.entity; + +import java.io.Serializable; +import java.time.LocalDate; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.IdClass; +import javax.persistence.Table; + +import lombok.Getter; +import lombok.Setter; + +/** + * API Gateway 일별 처리 통계 (AGWAPP.API_STATS_DAY) — 읽기 전용. + * + * 월별 조회 시 "현재 월"은 아직 API_STATS_MONTH 에 집계되지 않으므로(월 집계 잡은 익월 1일 실행) + * 현재 월분은 이 DAY 테이블에서 조회한다. + * 스키마명은 소스에 하드코딩하지 않는다(gateway default_schema=AGWAPP). + */ +@Getter +@Setter +@Entity +@Table(name = "API_STATS_DAY") +@IdClass(ApiStatsDayId.class) +public class ApiStatsDay implements Serializable { + private static final long serialVersionUID = 1L; + + @Id + @Column(name = "STAT_TIME", nullable = false) + private LocalDate statTime; + + @Id + @Column(name = "API_NAME", nullable = false, length = 100) + private String apiName; + + @Id + @Column(name = "GW_INSTANCE_ID", nullable = false, length = 50) + private String gwInstanceId; + + @Id + @Column(name = "BIZ_DIV_CODE", nullable = false, length = 50) + private String bizDivCode = "NONE"; + + @Id + @Column(name = "CLIENT_ID", nullable = false, length = 100) + private String clientId = "NONE"; + + @Id + @Column(name = "INBOUND_ADAPTER", nullable = false, length = 100) + private String inboundAdapter = "NONE"; + + @Id + @Column(name = "OUTBOUND_ADAPTER", nullable = false, length = 100) + private String outboundAdapter = "NONE"; + + @Column(name = "TOTAL_CNT", nullable = false) + private Long totalCnt = 0L; + + @Column(name = "SUCCESS_CNT", nullable = false) + private Long successCnt = 0L; + + @Column(name = "TIMEOUT_CNT", nullable = false) + private Long timeoutCnt = 0L; + + @Column(name = "SYSTEM_ERR_CNT", nullable = false) + private Long systemErrCnt = 0L; + + @Column(name = "BIZ_ERR_CNT", nullable = false) + private Long bizErrCnt = 0L; +} diff --git a/src/main/java/com/eactive/apim/gateway/data/statistics/entity/ApiStatsDayId.java b/src/main/java/com/eactive/apim/gateway/data/statistics/entity/ApiStatsDayId.java new file mode 100644 index 0000000..cdc1694 --- /dev/null +++ b/src/main/java/com/eactive/apim/gateway/data/statistics/entity/ApiStatsDayId.java @@ -0,0 +1,26 @@ +package com.eactive.apim.gateway.data.statistics.entity; + +import java.io.Serializable; +import java.time.LocalDate; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * API 통계 일별 복합키 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ApiStatsDayId implements Serializable { + private static final long serialVersionUID = 1L; + + private LocalDate statTime; + private String apiName; + private String gwInstanceId; + private String bizDivCode; + private String clientId; + private String inboundAdapter; + private String outboundAdapter; +} diff --git a/src/main/java/com/eactive/apim/gateway/data/statistics/entity/ApiStatsHour.java b/src/main/java/com/eactive/apim/gateway/data/statistics/entity/ApiStatsHour.java new file mode 100644 index 0000000..644577e --- /dev/null +++ b/src/main/java/com/eactive/apim/gateway/data/statistics/entity/ApiStatsHour.java @@ -0,0 +1,71 @@ +package com.eactive.apim.gateway.data.statistics.entity; + +import java.io.Serializable; +import java.time.LocalDateTime; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.IdClass; +import javax.persistence.Table; + +import lombok.Getter; +import lombok.Setter; + +/** + * API Gateway 시간별 처리 통계 (AGWAPP.API_STATS_HOUR). + * + * 스키마명은 소스에 하드코딩하지 않는다 — gateway datasource 의 hibernate.default_schema(AGWAPP)로 해석된다. + * 포털은 읽기 전용으로만 사용한다(집계 주체는 eapim-admin 스케줄러). 사용 컬럼(카운트)만 매핑한다. + */ +@Getter +@Setter +@Entity +@Table(name = "API_STATS_HOUR") +@IdClass(ApiStatsHourId.class) +public class ApiStatsHour implements Serializable { + private static final long serialVersionUID = 1L; + + @Id + @Column(name = "STAT_TIME", nullable = false) + private LocalDateTime statTime; + + @Id + @Column(name = "API_NAME", nullable = false, length = 100) + private String apiName; + + @Id + @Column(name = "GW_INSTANCE_ID", nullable = false, length = 50) + private String gwInstanceId; + + @Id + @Column(name = "BIZ_DIV_CODE", nullable = false, length = 50) + private String bizDivCode = "NONE"; + + @Id + @Column(name = "CLIENT_ID", nullable = false, length = 100) + private String clientId = "NONE"; + + @Id + @Column(name = "INBOUND_ADAPTER", nullable = false, length = 100) + private String inboundAdapter = "NONE"; + + @Id + @Column(name = "OUTBOUND_ADAPTER", nullable = false, length = 100) + private String outboundAdapter = "NONE"; + + @Column(name = "TOTAL_CNT", nullable = false) + private Long totalCnt = 0L; + + @Column(name = "SUCCESS_CNT", nullable = false) + private Long successCnt = 0L; + + @Column(name = "TIMEOUT_CNT", nullable = false) + private Long timeoutCnt = 0L; + + @Column(name = "SYSTEM_ERR_CNT", nullable = false) + private Long systemErrCnt = 0L; + + @Column(name = "BIZ_ERR_CNT", nullable = false) + private Long bizErrCnt = 0L; +} diff --git a/src/main/java/com/eactive/apim/gateway/data/statistics/entity/ApiStatsHourId.java b/src/main/java/com/eactive/apim/gateway/data/statistics/entity/ApiStatsHourId.java new file mode 100644 index 0000000..b8b2f53 --- /dev/null +++ b/src/main/java/com/eactive/apim/gateway/data/statistics/entity/ApiStatsHourId.java @@ -0,0 +1,26 @@ +package com.eactive.apim.gateway.data.statistics.entity; + +import java.io.Serializable; +import java.time.LocalDateTime; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * API 통계 시간별 복합키 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ApiStatsHourId implements Serializable { + private static final long serialVersionUID = 1L; + + private LocalDateTime statTime; + private String apiName; + private String gwInstanceId; + private String bizDivCode; + private String clientId; + private String inboundAdapter; + private String outboundAdapter; +} diff --git a/src/main/java/com/eactive/apim/gateway/data/statistics/entity/ApiStatsMonth.java b/src/main/java/com/eactive/apim/gateway/data/statistics/entity/ApiStatsMonth.java new file mode 100644 index 0000000..b4ec522 --- /dev/null +++ b/src/main/java/com/eactive/apim/gateway/data/statistics/entity/ApiStatsMonth.java @@ -0,0 +1,70 @@ +package com.eactive.apim.gateway.data.statistics.entity; + +import java.io.Serializable; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.IdClass; +import javax.persistence.Table; + +import lombok.Getter; +import lombok.Setter; + +/** + * API Gateway 월별 처리 통계 (AGWAPP.API_STATS_MONTH). + * + * STAT_TIME 은 YYYYMM 문자열. 스키마명은 소스에 하드코딩하지 않는다(gateway default_schema=AGWAPP). + * 포털은 읽기 전용으로만 사용한다. 사용 컬럼(카운트)만 매핑한다. + */ +@Getter +@Setter +@Entity +@Table(name = "API_STATS_MONTH") +@IdClass(ApiStatsMonthId.class) +public class ApiStatsMonth implements Serializable { + private static final long serialVersionUID = 1L; + + @Id + @Column(name = "STAT_TIME", nullable = false, length = 6) + private String statTime; // YYYYMM format + + @Id + @Column(name = "API_NAME", nullable = false, length = 100) + private String apiName; + + @Id + @Column(name = "GW_INSTANCE_ID", nullable = false, length = 50) + private String gwInstanceId; + + @Id + @Column(name = "BIZ_DIV_CODE", nullable = false, length = 50) + private String bizDivCode = "NONE"; + + @Id + @Column(name = "CLIENT_ID", nullable = false, length = 100) + private String clientId = "NONE"; + + @Id + @Column(name = "INBOUND_ADAPTER", nullable = false, length = 100) + private String inboundAdapter = "NONE"; + + @Id + @Column(name = "OUTBOUND_ADAPTER", nullable = false, length = 100) + private String outboundAdapter = "NONE"; + + @Column(name = "TOTAL_CNT", nullable = false) + private Long totalCnt = 0L; + + @Column(name = "SUCCESS_CNT", nullable = false) + private Long successCnt = 0L; + + @Column(name = "TIMEOUT_CNT", nullable = false) + private Long timeoutCnt = 0L; + + @Column(name = "SYSTEM_ERR_CNT", nullable = false) + private Long systemErrCnt = 0L; + + @Column(name = "BIZ_ERR_CNT", nullable = false) + private Long bizErrCnt = 0L; +} diff --git a/src/main/java/com/eactive/apim/gateway/data/statistics/entity/ApiStatsMonthId.java b/src/main/java/com/eactive/apim/gateway/data/statistics/entity/ApiStatsMonthId.java new file mode 100644 index 0000000..0c7b93c --- /dev/null +++ b/src/main/java/com/eactive/apim/gateway/data/statistics/entity/ApiStatsMonthId.java @@ -0,0 +1,25 @@ +package com.eactive.apim.gateway.data.statistics.entity; + +import java.io.Serializable; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * API 통계 월별 복합키 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ApiStatsMonthId implements Serializable { + private static final long serialVersionUID = 1L; + + private String statTime; // YYYYMM format + private String apiName; + private String gwInstanceId; + private String bizDivCode; + private String clientId; + private String inboundAdapter; + private String outboundAdapter; +} diff --git a/src/main/java/com/eactive/apim/gateway/data/statistics/entity/GwApiStatus.java b/src/main/java/com/eactive/apim/gateway/data/statistics/entity/GwApiStatus.java new file mode 100644 index 0000000..b9313c2 --- /dev/null +++ b/src/main/java/com/eactive/apim/gateway/data/statistics/entity/GwApiStatus.java @@ -0,0 +1,36 @@ +package com.eactive.apim.gateway.data.statistics.entity; + +import lombok.Data; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import java.time.LocalDateTime; + +/** + * API 상태 모니터링 결과 (AGWAPP.API_STATUS). + * + *

eapim-admin 의 {@code ApiStatusMonitorJob} 이 상태 변화가 있을 때만 upsert 한다. + * 포털은 읽기 전용으로 "마지막 상태 변경 시각" 표시에 사용한다.

+ */ +@Entity +@Table(name = "API_STATUS") +@Data +public class GwApiStatus { + + /** EAI 서비스명 */ + @Id + @Column(name = "EAISVCNAME", length = 30) + private String eaisvcname; + + /** N 정상 / C 점검 / D 지연 / E 장애 */ + @Column(name = "STATUS_CODE", length = 1) + private String statusCode; + + @Column(name = "MODIFIED_BY", length = 20) + private String modifiedBy; + + @Column(name = "MODIFIED_DATE") + private LocalDateTime modifiedDate; +} diff --git a/src/main/java/com/eactive/apim/gateway/data/statistics/entity/GwAuthClient.java b/src/main/java/com/eactive/apim/gateway/data/statistics/entity/GwAuthClient.java new file mode 100644 index 0000000..d2df93c --- /dev/null +++ b/src/main/java/com/eactive/apim/gateway/data/statistics/entity/GwAuthClient.java @@ -0,0 +1,41 @@ +package com.eactive.apim.gateway.data.statistics.entity; + +import java.io.Serializable; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +import lombok.Getter; +import lombok.Setter; + +/** + * GW 인증 Client 정보 (AGWAPP.TSEAIAU01) — 읽기 전용. + * + * API 통계의 org 스코핑에 사용한다: 통계 테이블에는 org_id 가 없으므로 + * TSEAIAU01.ORGID 로 org 의 CLIENTID 목록을 구해 API_STATS_*.CLIENT_ID 와 매칭한다. + * (admin ApiUseStatsService 도 API_STATS.CLIENT_ID = TSEAIAU01.CLIENTID 로 조인) + * + * 스키마명은 소스에 하드코딩하지 않는다(gateway default_schema=AGWAPP). + */ +@Getter +@Setter +@Entity +@Table(name = "TSEAIAU01") +public class GwAuthClient implements Serializable { + private static final long serialVersionUID = 1L; + + @Id + @Column(name = "CLIENTID", nullable = false, length = 256) + private String clientId; + + @Column(name = "CLIENTNAME", length = 150) + private String clientName; + + @Column(name = "ORGID", length = 38) + private String orgId; + + @Column(name = "APPSTATUS", length = 1) + private String appStatus; +} diff --git a/src/main/java/com/eactive/apim/gateway/data/statistics/entity/GwSvcInfo.java b/src/main/java/com/eactive/apim/gateway/data/statistics/entity/GwSvcInfo.java new file mode 100644 index 0000000..63ad96f --- /dev/null +++ b/src/main/java/com/eactive/apim/gateway/data/statistics/entity/GwSvcInfo.java @@ -0,0 +1,33 @@ +package com.eactive.apim.gateway.data.statistics.entity; + +import java.io.Serializable; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +import lombok.Getter; +import lombok.Setter; + +/** + * GW 인터페이스(서비스) 정보 (AGWAPP.TSEAIHE01) — 읽기 전용. + * + * API_STATS_*.API_NAME 은 인터페이스 ID(=EAISVCNAME)이며, 화면 표시용 실제 명칭은 EAISVCDESC 다. + * (admin ApiUseStatsService 도 API_STATS.API_NAME = TSEAIHE01.EAISVCNAME 으로 조인) + * 스키마명은 소스에 하드코딩하지 않는다(gateway default_schema=AGWAPP). + */ +@Getter +@Setter +@Entity +@Table(name = "TSEAIHE01") +public class GwSvcInfo implements Serializable { + private static final long serialVersionUID = 1L; + + @Id + @Column(name = "EAISVCNAME", nullable = false, length = 30) + private String svcName; // 인터페이스 ID (= API_STATS.API_NAME) + + @Column(name = "EAISVCDESC", length = 400) + private String svcDesc; // 인터페이스 표시 명칭 +} diff --git a/src/main/java/com/eactive/apim/gateway/data/statistics/repository/ApiStatsDayRepository.java b/src/main/java/com/eactive/apim/gateway/data/statistics/repository/ApiStatsDayRepository.java new file mode 100644 index 0000000..66c095f --- /dev/null +++ b/src/main/java/com/eactive/apim/gateway/data/statistics/repository/ApiStatsDayRepository.java @@ -0,0 +1,66 @@ +package com.eactive.apim.gateway.data.statistics.repository; + +import com.eactive.apim.portal.apps.statistics.dto.ApiStatisticsDetailDto; +import com.eactive.apim.portal.apps.statistics.dto.ApiStatisticsSummaryDto; +import com.eactive.apim.gateway.data.statistics.entity.ApiStatsDay; +import com.eactive.apim.gateway.data.statistics.entity.ApiStatsDayId; +import java.time.LocalDate; +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +/** + * API_STATS_DAY(AGWAPP) 조회 리포지토리. + * 월별 조회의 "현재 월" 분(아직 API_STATS_MONTH 미집계)을 일 범위로 합산하는 데 사용한다. + */ +public interface ApiStatsDayRepository extends JpaRepository { + + /** + * 전체 요약 (일자 범위, 지정 clientId 집합). + */ + @Query("SELECT new com.eactive.apim.portal.apps.statistics.dto.ApiStatisticsSummaryDto(" + + "COALESCE(SUM(s.totalCnt), 0), COALESCE(SUM(s.successCnt), 0), COALESCE(SUM(s.timeoutCnt), 0), " + + "COALESCE(SUM(s.systemErrCnt), 0), COALESCE(SUM(s.bizErrCnt), 0)) " + + "FROM ApiStatsDay s " + + "WHERE s.clientId IN :clientIds " + + "AND s.statTime BETWEEN :startDate AND :endDate") + ApiStatisticsSummaryDto findSummary( + @Param("clientIds") List clientIds, + @Param("startDate") LocalDate startDate, + @Param("endDate") LocalDate endDate); + + /** + * API별 통계 (API_NAME 단위 집계). + */ + @Query("SELECT new com.eactive.apim.portal.apps.statistics.dto.ApiStatisticsDetailDto(" + + "s.apiName, COALESCE(SUM(s.totalCnt), 0), COALESCE(SUM(s.successCnt), 0), " + + "COALESCE(SUM(s.timeoutCnt), 0), COALESCE(SUM(s.systemErrCnt), 0), COALESCE(SUM(s.bizErrCnt), 0)) " + + "FROM ApiStatsDay s " + + "WHERE s.clientId IN :clientIds " + + "AND s.statTime BETWEEN :startDate AND :endDate " + + "GROUP BY s.apiName " + + "ORDER BY SUM(s.totalCnt) DESC") + List findDetail( + @Param("clientIds") List clientIds, + @Param("startDate") LocalDate startDate, + @Param("endDate") LocalDate endDate); + + /** + * 일자별 누적 통계 (월별 조회 하단 표에 사용 — 월별이라도 일별로 표기). + * 생성자 표현식 안의 FUNCTION 은 Hibernate 5.6 이 타입 해석을 못 하므로 Object[] 로 받는다. + * row: [0]=일자(String yyyy-MM-dd), [1]=total, [2]=success, [3]=timeout, [4]=systemErr, [5]=biz + */ + @Query("SELECT FUNCTION('TO_CHAR', s.statTime, 'YYYY-MM-DD'), " + + "COALESCE(SUM(s.totalCnt), 0), COALESCE(SUM(s.successCnt), 0), " + + "COALESCE(SUM(s.timeoutCnt), 0), COALESCE(SUM(s.systemErrCnt), 0), COALESCE(SUM(s.bizErrCnt), 0) " + + "FROM ApiStatsDay s " + + "WHERE s.clientId IN :clientIds " + + "AND s.statTime BETWEEN :startDate AND :endDate " + + "GROUP BY FUNCTION('TO_CHAR', s.statTime, 'YYYY-MM-DD') " + + "ORDER BY FUNCTION('TO_CHAR', s.statTime, 'YYYY-MM-DD')") + List findPeriodByDay( + @Param("clientIds") List clientIds, + @Param("startDate") LocalDate startDate, + @Param("endDate") LocalDate endDate); +} diff --git a/src/main/java/com/eactive/apim/gateway/data/statistics/repository/ApiStatsHourRepository.java b/src/main/java/com/eactive/apim/gateway/data/statistics/repository/ApiStatsHourRepository.java new file mode 100644 index 0000000..94098fa --- /dev/null +++ b/src/main/java/com/eactive/apim/gateway/data/statistics/repository/ApiStatsHourRepository.java @@ -0,0 +1,56 @@ +package com.eactive.apim.gateway.data.statistics.repository; + +import com.eactive.apim.portal.apps.statistics.dto.ApiStatisticsDetailDto; +import com.eactive.apim.portal.apps.statistics.dto.ApiStatisticsSummaryDto; +import com.eactive.apim.gateway.data.statistics.entity.ApiStatsHour; +import com.eactive.apim.gateway.data.statistics.entity.ApiStatsHourId; +import java.time.LocalDateTime; +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +/** + * API_STATS_HOUR(AGWAPP) 조회 리포지토리. + * + * 통계 수치는 보존기간이 긴 API_STATS_DAY 를 사용하되, "오늘"은 아직 DAY 로 집계되지 않았으므로 + * (일 집계 잡은 익일 실행) 오늘분만 HOUR 에서 합산해 DAY 결과에 더한다. 최신 집계 시각도 HOUR 로 산출. + */ +public interface ApiStatsHourRepository extends JpaRepository { + + /** + * 전체 요약 (시각 범위, 지정 clientId 집합) — 오늘분 합산용. + */ + @Query("SELECT new com.eactive.apim.portal.apps.statistics.dto.ApiStatisticsSummaryDto(" + + "COALESCE(SUM(s.totalCnt), 0), COALESCE(SUM(s.successCnt), 0), COALESCE(SUM(s.timeoutCnt), 0), " + + "COALESCE(SUM(s.systemErrCnt), 0), COALESCE(SUM(s.bizErrCnt), 0)) " + + "FROM ApiStatsHour s " + + "WHERE s.clientId IN :clientIds " + + "AND s.statTime BETWEEN :startTime AND :endTime") + ApiStatisticsSummaryDto findSummary( + @Param("clientIds") List clientIds, + @Param("startTime") LocalDateTime startTime, + @Param("endTime") LocalDateTime endTime); + + /** + * API별 통계 (API_NAME 단위 집계) — 오늘분 합산용. + */ + @Query("SELECT new com.eactive.apim.portal.apps.statistics.dto.ApiStatisticsDetailDto(" + + "s.apiName, COALESCE(SUM(s.totalCnt), 0), COALESCE(SUM(s.successCnt), 0), " + + "COALESCE(SUM(s.timeoutCnt), 0), COALESCE(SUM(s.systemErrCnt), 0), COALESCE(SUM(s.bizErrCnt), 0)) " + + "FROM ApiStatsHour s " + + "WHERE s.clientId IN :clientIds " + + "AND s.statTime BETWEEN :startTime AND :endTime " + + "GROUP BY s.apiName " + + "ORDER BY SUM(s.totalCnt) DESC") + List findDetail( + @Param("clientIds") List clientIds, + @Param("startTime") LocalDateTime startTime, + @Param("endTime") LocalDateTime endTime); + + /** + * 최신 집계 시각 (안내 문구용). 데이터 없으면 null. + */ + @Query("SELECT MAX(s.statTime) FROM ApiStatsHour s WHERE s.clientId IN :clientIds") + LocalDateTime findLatestStatTime(@Param("clientIds") List clientIds); +} diff --git a/src/main/java/com/eactive/apim/gateway/data/statistics/repository/ApiStatsMonthRepository.java b/src/main/java/com/eactive/apim/gateway/data/statistics/repository/ApiStatsMonthRepository.java new file mode 100644 index 0000000..1dcbf92 --- /dev/null +++ b/src/main/java/com/eactive/apim/gateway/data/statistics/repository/ApiStatsMonthRepository.java @@ -0,0 +1,54 @@ +package com.eactive.apim.gateway.data.statistics.repository; + +import com.eactive.apim.portal.apps.statistics.dto.ApiStatisticsDetailDto; +import com.eactive.apim.portal.apps.statistics.dto.ApiStatisticsSummaryDto; +import com.eactive.apim.gateway.data.statistics.entity.ApiStatsMonth; +import com.eactive.apim.gateway.data.statistics.entity.ApiStatsMonthId; +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +/** + * API_STATS_MONTH(AGWAPP) 통계 조회 리포지토리 (월별 모드). + * STAT_TIME 은 YYYYMM 문자열이므로 문자열 범위 비교로 조회한다. + */ +public interface ApiStatsMonthRepository extends JpaRepository { + + /** + * 전체 요약 (YYYYMM 범위, 지정 clientId 집합). + */ + @Query("SELECT new com.eactive.apim.portal.apps.statistics.dto.ApiStatisticsSummaryDto(" + + "COALESCE(SUM(s.totalCnt), 0), COALESCE(SUM(s.successCnt), 0), COALESCE(SUM(s.timeoutCnt), 0), " + + "COALESCE(SUM(s.systemErrCnt), 0), COALESCE(SUM(s.bizErrCnt), 0)) " + + "FROM ApiStatsMonth s " + + "WHERE s.clientId IN :clientIds " + + "AND s.statTime BETWEEN :startMonth AND :endMonth") + ApiStatisticsSummaryDto findSummary( + @Param("clientIds") List clientIds, + @Param("startMonth") String startMonth, + @Param("endMonth") String endMonth); + + /** + * API별 통계 (API_NAME 단위 집계). + */ + @Query("SELECT new com.eactive.apim.portal.apps.statistics.dto.ApiStatisticsDetailDto(" + + "s.apiName, COALESCE(SUM(s.totalCnt), 0), COALESCE(SUM(s.successCnt), 0), " + + "COALESCE(SUM(s.timeoutCnt), 0), COALESCE(SUM(s.systemErrCnt), 0), COALESCE(SUM(s.bizErrCnt), 0)) " + + "FROM ApiStatsMonth s " + + "WHERE s.clientId IN :clientIds " + + "AND s.statTime BETWEEN :startMonth AND :endMonth " + + "GROUP BY s.apiName " + + "ORDER BY SUM(s.totalCnt) DESC") + List findDetail( + @Param("clientIds") List clientIds, + @Param("startMonth") String startMonth, + @Param("endMonth") String endMonth); + + /** + * 조회 가능한 월 목록 (YYYYMM, 최신순). + */ + @Query("SELECT DISTINCT s.statTime FROM ApiStatsMonth s " + + "WHERE s.clientId IN :clientIds ORDER BY s.statTime DESC") + List findAvailableMonths(@Param("clientIds") List clientIds); +} diff --git a/src/main/java/com/eactive/apim/gateway/data/statistics/repository/GwApiStatusRepository.java b/src/main/java/com/eactive/apim/gateway/data/statistics/repository/GwApiStatusRepository.java new file mode 100644 index 0000000..f714332 --- /dev/null +++ b/src/main/java/com/eactive/apim/gateway/data/statistics/repository/GwApiStatusRepository.java @@ -0,0 +1,17 @@ +package com.eactive.apim.gateway.data.statistics.repository; + +import com.eactive.apim.gateway.data.statistics.entity.GwApiStatus; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.Repository; + +import java.time.LocalDateTime; +import java.util.Optional; + +public interface GwApiStatusRepository extends Repository { + + /** + * API 상태가 마지막으로 변경된 시각. 데이터가 없으면 empty. + */ + @Query("SELECT MAX(s.modifiedDate) FROM GwApiStatus s") + Optional findLastModifiedDate(); +} diff --git a/src/main/java/com/eactive/apim/gateway/data/statistics/repository/GwAuthClientRepository.java b/src/main/java/com/eactive/apim/gateway/data/statistics/repository/GwAuthClientRepository.java new file mode 100644 index 0000000..46dd8d7 --- /dev/null +++ b/src/main/java/com/eactive/apim/gateway/data/statistics/repository/GwAuthClientRepository.java @@ -0,0 +1,25 @@ +package com.eactive.apim.gateway.data.statistics.repository; + +import com.eactive.apim.gateway.data.statistics.entity.GwAuthClient; +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +/** + * TSEAIAU01(AGWAPP 인증 Client) 조회 — API 통계 org 스코핑용. + * PTL_ORG.ID == TSEAIAU01.ORGID (1:N), TSEAIAU01.CLIENTID == API_STATS_*.CLIENT_ID. + */ +public interface GwAuthClientRepository extends JpaRepository { + + /** + * org 소속 클라이언트 목록 (앱 선택 드롭다운용). + */ + List findByOrgIdOrderByClientNameAsc(String orgId); + + /** + * org 소속 CLIENTID 목록 (통계 필터용). + */ + @Query("SELECT c.clientId FROM GwAuthClient c WHERE c.orgId = :orgId") + List findClientIdsByOrgId(@Param("orgId") String orgId); +} diff --git a/src/main/java/com/eactive/apim/gateway/data/statistics/repository/GwSvcInfoRepository.java b/src/main/java/com/eactive/apim/gateway/data/statistics/repository/GwSvcInfoRepository.java new file mode 100644 index 0000000..ae71b0b --- /dev/null +++ b/src/main/java/com/eactive/apim/gateway/data/statistics/repository/GwSvcInfoRepository.java @@ -0,0 +1,14 @@ +package com.eactive.apim.gateway.data.statistics.repository; + +import com.eactive.apim.gateway.data.statistics.entity.GwSvcInfo; +import java.util.Collection; +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; + +/** + * TSEAIHE01(AGWAPP 인터페이스 정보) 조회 — API_NAME(인터페이스 ID) → 표시명(EAISVCDESC) 매핑용. + */ +public interface GwSvcInfoRepository extends JpaRepository { + + List findBySvcNameIn(Collection svcNames); +} diff --git a/src/main/java/com/eactive/apim/portal/PortalApplication.java b/src/main/java/com/eactive/apim/portal/PortalApplication.java index 1b2a38d..1351fa3 100644 --- a/src/main/java/com/eactive/apim/portal/PortalApplication.java +++ b/src/main/java/com/eactive/apim/portal/PortalApplication.java @@ -22,6 +22,10 @@ public class PortalApplication extends SpringBootServletInitializer { private static final Logger portal_logger = LoggerFactory.getLogger(PortalApplication.class); + public PortalApplication() { + super(); + } + public static void main(String[] args) { portal_logger.info("##### PortalApplication Start #####"); diff --git a/src/main/java/com/eactive/apim/portal/apps/agreements/controller/AgreementsController.java b/src/main/java/com/eactive/apim/portal/apps/agreements/controller/AgreementsController.java index c24f459..c147887 100644 --- a/src/main/java/com/eactive/apim/portal/apps/agreements/controller/AgreementsController.java +++ b/src/main/java/com/eactive/apim/portal/apps/agreements/controller/AgreementsController.java @@ -15,7 +15,11 @@ import org.springframework.web.bind.annotation.RequestParam; @Controller @RequestMapping("/agreements") public class AgreementsController { - public static final String TERMS_AGREEMENTS = "fragment/kjbank/terms_agreements"; + public static final String TERMS_AGREEMENTS = "fragment/djbank/terms_agreements"; + + /** 개인정보처리방침은 약관 페이지에서 분리되어 제주은행 공식 사이트 외부 링크로 대체됨 */ + public static final String PRIVACY_POLICY_EXTERNAL_URL = + "https://www.jejubank.co.kr/hmpg/csct/secuCenr/ptctPlcy/procsPlcy/ctnt.do"; private final AgreementsFacade agreementsFacade; @@ -28,28 +32,49 @@ public class AgreementsController { public String showTerms(@RequestParam(required = false) String tab, @RequestParam(required = false) String publishedOn, Model model) { + String currentTab = tab != null ? tab : "terms"; + + // 구 개인정보처리방침 탭(tab=privacy)은 외부 링크로 이동했으므로 외부 URL로 리다이렉트(북마크 호환) + if ("privacy".equals(currentTab)) { + return "redirect:" + PRIVACY_POLICY_EXTERNAL_URL; + } + AgreementType type; - if ("privacy".equals(tab)) { - type = AgreementType.PRIVACY_POLICY; - } else { - type = AgreementType.TERMS_OF_USE; + switch (currentTab) { + case "privacy-collect": + // 개인정보수집동의서 = PRIVACY_COLLECT (신설항목) + type = AgreementType.PRIVACY_COLLECT; + break; + case "notification": + type = AgreementType.NOTIFICATION_CONSENT; + break; + case "terms": + default: + currentTab = "terms"; + type = AgreementType.TERMS_OF_USE; + break; } List agreementsList = agreementsFacade.getAgreementsList(String.valueOf(type)); model.addAttribute("agreementsList", agreementsList); - AgreementsDTO selectedAgreement = publishedOn != null && !publishedOn.isEmpty() ? - findAgreementByDate(agreementsList, publishedOn) : agreementsList.get(0); + // 해당 약관 종류가 아직 시드되지 않았을 수 있으므로 빈 리스트 방어 + AgreementsDTO selectedAgreement = null; + if (agreementsList != null && !agreementsList.isEmpty()) { + selectedAgreement = publishedOn != null && !publishedOn.isEmpty() ? + findAgreementByDate(agreementsList, publishedOn) : agreementsList.get(0); + } model.addAttribute("selectedAgreement", selectedAgreement); model.addAttribute("selectedDate", publishedOn); model.addAttribute("isTermsOfUse", type == AgreementType.TERMS_OF_USE); - model.addAttribute("isPrivacyPolicy", type == AgreementType.PRIVACY_POLICY); + model.addAttribute("isPrivacyCollect", type == AgreementType.PRIVACY_COLLECT); + model.addAttribute("isNotification", type == AgreementType.NOTIFICATION_CONSENT); model.addAttribute("agreementTitle", type.getDescription()); model.addAttribute("agreementType", type.getCode()); - model.addAttribute("currentTab", tab != null ? tab : "terms"); + model.addAttribute("currentTab", currentTab); return TERMS_AGREEMENTS; } diff --git a/src/main/java/com/eactive/apim/portal/apps/agreements/service/AgreementsFacadeImpl.java b/src/main/java/com/eactive/apim/portal/apps/agreements/service/AgreementsFacadeImpl.java index 82cac26..2acb8a4 100644 --- a/src/main/java/com/eactive/apim/portal/apps/agreements/service/AgreementsFacadeImpl.java +++ b/src/main/java/com/eactive/apim/portal/apps/agreements/service/AgreementsFacadeImpl.java @@ -71,8 +71,9 @@ public class AgreementsFacadeImpl implements AgreementsFacade { throw new NotFoundException("현재 시행중인 이용약관이 없습니다."); } - // 2. 개인정보수집동의서 처리 - if (privacyCollectType != null && privacyCollectType.isPrivacyCollect()) { + // 2. 개인정보수집동의서 처리 (개인정보수집동의서 = PRIVACY_COLLECT 신설항목 포함) + if (privacyCollectType != null + && (privacyCollectType.isPrivacyCollect() || privacyCollectType == AgreementType.PRIVACY_COLLECT)) { Optional privacyCollect = agreementsService .findLastesBeforeDate( privacyCollectType, @@ -199,6 +200,10 @@ public class AgreementsFacadeImpl implements AgreementsFacade { defaultAgreement.setName("개인정보처리방침"); defaultAgreement.setContents("현재 개인정보처리방침을 불러올 수 없습니다. 잠시 후 다시 시도해 주세요."); break; + case "PRIVACY_COLLECT": + defaultAgreement.setName("개인정보수집동의서"); + defaultAgreement.setContents("현재 개인정보수집동의서를 불러올 수 없습니다. 잠시 후 다시 시도해 주세요."); + break; case "PRIVACY_COLLECT_IND": defaultAgreement.setName("개인용 개인정보수집동의서"); defaultAgreement.setContents("현재 개인용 개인정보수집동의서를 불러올 수 없습니다. 잠시 후 다시 시도해 주세요."); diff --git a/src/main/java/com/eactive/apim/portal/apps/apis/controller/ApiController.java b/src/main/java/com/eactive/apim/portal/apps/apis/controller/ApiController.java index 649f1a2..8d93190 100644 --- a/src/main/java/com/eactive/apim/portal/apps/apis/controller/ApiController.java +++ b/src/main/java/com/eactive/apim/portal/apps/apis/controller/ApiController.java @@ -8,6 +8,8 @@ import com.eactive.apim.portal.apps.apiservice.dto.ApiGroupSearch; import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceDTO; import com.eactive.apim.portal.apps.apiservice.service.ApiServiceService; import com.eactive.apim.portal.common.exception.NotFoundException; +import com.eactive.apim.portal.common.util.SecurityUtil; +import com.eactive.apim.portal.djb.apistatus.service.ApiStatusCatalogService; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -30,6 +32,7 @@ public class ApiController { private final ApiService apiService; private final ApiServiceService apiServiceService; private final ApiSearchFacade apiSearchFacade; + private final ApiStatusCatalogService apiStatusCatalogService; private static final String DEFAULT_TOKEN_API_ID = "default-token-api-spec"; private static final String DEFAULT_TOKEN_API_NAME = "인증"; @@ -38,7 +41,24 @@ public class ApiController { if (id == null) { return "redirect:/apis/common"; } + populateDetailModel(id, model); + model.addAttribute("activeTab", "api-info"); + return "apps/apis/mainApiDetail"; + } + // 테스트베드를 API 정보와 별도 URL로 분리(딥링크·북마크 가능). 미인증 사용자도 페이지 진입은 + // 허용하되, 실제 테스트베드(Swagger)는 인증 사용자에게만 렌더하고 미인증에는 로그인 안내를 노출한다. + @GetMapping("/detail/testbed") + public String apidetailTestbed(@RequestParam(value = "id", required = false) String id, ModelMap model) { + if (id == null) { + return "redirect:/apis/common"; + } + populateDetailModel(id, model); + model.addAttribute("activeTab", "testbed"); + return "apps/apis/mainApiDetail"; + } + + private void populateDetailModel(String id, ModelMap model) { ApiSpecInfoDto api = apiService.selectDetail(id); if (api == null) { throw new NotFoundException(NOT_FOUND_MESSAGE); @@ -46,10 +66,16 @@ public class ApiController { Map searchResult = apiSearchFacade.searchApis(new ApiGroupSearch()); + // 상세 타이틀에 노출할 현재 API의 그룹명 세팅(selectDetail은 apiGroupName을 채우지 않음) + ApiServiceDTO apiGroup = apiServiceService.findApiServiceByApiId(id); + if (apiGroup != null) { + api.setApiGroupName(apiGroup.getGroupName()); + } + model.addAttribute("apiSpecInfo", api); model.addAttribute("totalApiCount", searchResult.get("totalApiCount")); model.addAttribute("services", searchResult.get("services")); - return "apps/apis/mainApiDetail"; + model.addAttribute("authenticated", SecurityUtil.isAuthenticated()); } @GetMapping @@ -62,12 +88,18 @@ public class ApiController { model.addAttribute("totalApiCount", searchResult.get("totalApiCount")); model.addAttribute("selectedApiCount", searchResult.get("selectedApiCount")); model.addAttribute("selected", search.getGroupIds().size() > 0 ? search.getGroupIds().get(0) : "-1"); + // 카드의 현재 상태 태그 노출 여부 (PTL_PROPERTY djb.apistatus.api-list-status-badge) + model.addAttribute("apiStatusBadgeEnabled", apiStatusCatalogService.isApiListStatusBadgeEnabled()); return "apps/apis/mainApiList"; } @GetMapping("/testbed/api") public String testbedByApi(@RequestParam(value = "id", required = false) String id, Model model) { + // 테스트베드는 로그인한 사용자만 접근 가능. 미인증 시 사유와 함께 로그인 페이지로 유도. + if (!SecurityUtil.isAuthenticated()) { + return "redirect:/login?reason=auth"; + } String selectedApiServiceName = "API 서비스 선택"; String selectedServiceId = ""; boolean idExists = false; @@ -93,6 +125,10 @@ public class ApiController { @GetMapping("/testbed") public String testbedByApiService(@RequestParam(value = "id", required = false) String id, Model model) { + // 테스트베드는 로그인한 사용자만 접근 가능. 미인증 시 사유와 함께 로그인 페이지로 유도. + if (!SecurityUtil.isAuthenticated()) { + return "redirect:/login?reason=auth"; + } String selectedApiServiceName = "API 서비스 선택"; boolean idExists = false; diff --git a/src/main/java/com/eactive/apim/portal/apps/apis/controller/TestbedSpecController.java b/src/main/java/com/eactive/apim/portal/apps/apis/controller/TestbedSpecController.java index 4c1c670..ef363f3 100644 --- a/src/main/java/com/eactive/apim/portal/apps/apis/controller/TestbedSpecController.java +++ b/src/main/java/com/eactive/apim/portal/apps/apis/controller/TestbedSpecController.java @@ -2,15 +2,15 @@ package com.eactive.apim.portal.apps.apis.controller; import com.eactive.apim.portal.apispec.entity.ApiSpecInfo; import com.eactive.apim.portal.apispec.service.ApiSpecInfoService; -import com.eactive.apim.portal.apps.apis.service.ApiService; -import com.eactive.apim.portal.apps.apiservice.service.ApiSpecService; +import com.eactive.apim.portal.djb.testbed.service.DjbTestbedSpecServerRewriter; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.Optional; +import javax.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; -import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.util.FileCopyUtils; @@ -22,35 +22,46 @@ import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/api/apis") +@RequiredArgsConstructor @Slf4j public class TestbedSpecController { - @Autowired - private ApiSpecInfoService apiSpecInfoService; + private final ApiSpecInfoService apiSpecInfoService; + private final DjbTestbedSpecServerRewriter serverRewriter; private static final String DEFAULT_TOKEN_API_ID = "default-token-api-spec"; private static final String DEFAULT_SPEC_PATH = "swagger/default-token-api-spec.json"; @GetMapping(value = "/{id}/swagger.json", produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity getSwagger(@PathVariable String id) { + public ResponseEntity getSwagger(@PathVariable String id, HttpServletRequest request) { + String json = buildSpecJson(id, request); + return json == null ? ResponseEntity.notFound().build() : ResponseEntity.ok(json); + } + @GetMapping(value = "/{id}/swagger.yaml", produces = "application/x-yaml") + public ResponseEntity getSwaggerYaml(@PathVariable String id, HttpServletRequest request) { + String json = buildSpecJson(id, request); + return json == null ? ResponseEntity.notFound().build() : ResponseEntity.ok(serverRewriter.toYaml(json)); + } + + /** default 토큰 spec 또는 저장 spec(서버 → GW 주소 치환)을 JSON 으로 반환. 없으면 null. */ + private String buildSpecJson(String id, HttpServletRequest request) { if (DEFAULT_TOKEN_API_ID.equals(id)) { try { + // 기본 토큰 spec 은 서버 치환 대상이 아니다. path 가 포탈 mock 토큰 경로라 + // GW 호스트를 붙이면 실재하지 않는 주소가 된다(servers 없음 → 문서 origin 사용). Resource resource = new ClassPathResource(DEFAULT_SPEC_PATH); - String content = new String(FileCopyUtils.copyToByteArray(resource.getInputStream())); - return ResponseEntity.ok().body(content); + return new String(FileCopyUtils.copyToByteArray(resource.getInputStream()), StandardCharsets.UTF_8); } catch (IOException e) { log.error("Failed to read default token api spec file", e); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); + return null; } } Optional spec = apiSpecInfoService.findById(id); - - if (!spec.isPresent()) { - StringUtils.hasText(spec.get().getTestbedSpec()); + if (!spec.isPresent() || !StringUtils.hasText(spec.get().getTestbedSpec())) { + return null; } - return ResponseEntity.ok().body(spec.get().getTestbedSpec()); - + return serverRewriter.rewriteServerToGateway(spec.get().getTestbedSpec(), request); } } diff --git a/src/main/java/com/eactive/apim/portal/apps/apis/filter/APISender.java b/src/main/java/com/eactive/apim/portal/apps/apis/filter/APISender.java index 0a3874a..37635f9 100644 --- a/src/main/java/com/eactive/apim/portal/apps/apis/filter/APISender.java +++ b/src/main/java/com/eactive/apim/portal/apps/apis/filter/APISender.java @@ -1,5 +1,7 @@ package com.eactive.apim.portal.apps.apis.filter; +import com.eactive.apim.portal.common.util.StringMaskingUtil; +import com.eactive.apim.portal.djb.testbed.config.DjbTestbedGatewayProperty; import java.io.BufferedReader; import java.io.DataOutputStream; import java.io.IOException; @@ -10,6 +12,9 @@ import java.net.HttpURLConnection; import java.net.URL; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -20,15 +25,85 @@ public class APISender { private static final Logger logger = LoggerFactory.getLogger(APISender.class); - public String requestPost(String uri, String requestBody) throws IOException { + /** 응답 charset 을 Content-Type 에서 얻지 못할 때 사용할 기본값. */ + private static final String DEFAULT_CHARSET = "UTF-8"; + + /** + * 프록시 대상(GW/mock) 응답을 상태코드까지 포함해 전달하기 위한 홀더. + * + *

본문만 반환하면 대상이 4xx/5xx 를 내려도 호출측이 200 으로 되돌려주게 되므로 + * 상태코드와 Content-Type 을 함께 담는다.

+ */ + public static class ApiResponse { + + private final int status; + private final String body; + private final String contentType; + private final Map> headers; + + public ApiResponse(int status, String body, String contentType, Map> headers) { + this.status = status; + this.body = body; + this.contentType = contentType; + this.headers = headers == null + ? Collections.>emptyMap() + : Collections.unmodifiableMap(headers); + } + + public int getStatus() { + return status; + } + + public String getBody() { + return body; + } + + /** 대상 응답의 Content-Type 원본 (없으면 null). */ + public String getContentType() { + return contentType; + } + + /** 대상 응답 헤더 (상태줄 의사헤더인 null 키는 제외). 값은 헤더당 복수 가능. */ + public Map> getHeaders() { + return headers; + } + + @Override + public String toString() { + return "ApiResponse{status=" + status + ", contentType=" + contentType + + ", bodyLen=" + (body == null ? 0 : body.length()) + + ", headers=" + headers.keySet() + "}"; + } + } + + // 테스트베드 프록시 연결/응답 타임아웃 — DjbTestbedGatewayProperty(djb.gateway.timeout, 단위: 초) 단일 기준. + private final DjbTestbedGatewayProperty gatewayProperty; + + public APISender(DjbTestbedGatewayProperty gatewayProperty) { + this.gatewayProperty = gatewayProperty; + } + + /** connect/read 타임아웃 적용. 반드시 connect(getOutputStream/getResponseCode) 이전에 호출. */ + private void applyTimeout(HttpURLConnection connection) { + int t = gatewayProperty.timeoutMillis(); + connection.setConnectTimeout(t); + connection.setReadTimeout(t); + } + + public ApiResponse requestPost(String uri, String requestBody) throws IOException { + + if (logger.isDebugEnabled()) { + logger.debug("APISender POST(json) 요청 - uri={}, bodyLen={}, body={}", + uri, requestBody == null ? 0 : requestBody.length(), StringMaskingUtil.maskFormBody(requestBody)); + } HttpURLConnection connection = getHttpURLConnection(uri, requestBody); - String response = getResponse(connection); + ApiResponse response = getResponse(connection); connection.disconnect(); if (logger.isDebugEnabled()) { - logger.debug(response); + logger.debug("APISender POST(json) 응답 - uri={}, status={}, response={}", uri, response.getStatus(), response.getBody()); } return response; } @@ -54,10 +129,11 @@ public class APISender { return uriBuilder.toString(); } - public String requestGet(String uri, Map headers, Map params) throws IOException { + public ApiResponse requestGet(String uri, Map headers, Map params) throws IOException { URL endpoint = new URL(appendUriAndParams(uri, params)); HttpURLConnection connection = (HttpURLConnection) endpoint.openConnection(); + applyTimeout(connection); connection.setRequestMethod("GET"); connection.setRequestProperty("Accept", "application/json"); for (Map.Entry entry : headers.entrySet()) { @@ -69,19 +145,24 @@ public class APISender { } connection.setDoOutput(true); - String response = getResponse(connection); + if (logger.isDebugEnabled()) { + logger.debug("APISender GET 요청 - uri={}", appendUriAndParams(uri, params)); + } + + ApiResponse response = getResponse(connection); connection.disconnect(); if (logger.isDebugEnabled()) { - logger.debug(response); + logger.debug("APISender GET 응답 - uri={}, status={}, response={}", uri, response.getStatus(), response.getBody()); } return response; } - public String requestPost(String uri, Map headers, Map params, String requestBody) throws IOException { + public ApiResponse requestPost(String uri, Map headers, Map params, String requestBody) throws IOException { URL endpoint = new URL(appendUriAndParams(uri, params)); HttpURLConnection connection = (HttpURLConnection) endpoint.openConnection(); + applyTimeout(connection); connection.setRequestMethod("POST"); for (Map.Entry entry : headers.entrySet()) { @@ -93,42 +174,86 @@ public class APISender { } connection.setDoOutput(true); + if (logger.isDebugEnabled()) { + // client_secret 등 민감 파라미터는 마스킹. body 비어있으면 상위에서 본문 전송 유실. + logger.debug("APISender POST 요청 - uri={}, bodyLen={}, body={}", + uri, requestBody == null ? 0 : requestBody.length(), StringMaskingUtil.maskFormBody(requestBody)); + } + try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) { byte[] requestBodyBytes = requestBody.getBytes(StandardCharsets.UTF_8); outputStream.write(requestBodyBytes); outputStream.flush(); } - String response = getResponse(connection); + ApiResponse response = getResponse(connection); connection.disconnect(); if (logger.isDebugEnabled()) { - logger.debug(response); + logger.debug("APISender POST 응답 - uri={}, status={}, response={}", uri, response.getStatus(), response.getBody()); } return response; } - private static String getResponse(HttpURLConnection connection) throws IOException { + /** + * 대상 응답을 상태코드·Content-Type·본문으로 읽는다. + * + *

4xx/5xx 는 {@code getInputStream()} 이 IOException 을 던지므로 errorStream 으로 본문을 읽고, + * 본문이 아예 없는 응답(errorStream == null)은 빈 문자열로 처리한다.

+ */ + private static ApiResponse getResponse(HttpURLConnection connection) throws IOException { int responseCode = connection.getResponseCode(); + String contentType = connection.getContentType(); StringBuilder response = new StringBuilder(); - try (InputStream stream = (responseCode < 400) ? connection.getInputStream() : connection.getErrorStream(); InputStreamReader isr = new InputStreamReader(stream); - BufferedReader reader = new BufferedReader(isr)) { + InputStream stream = (responseCode < 400) ? connection.getInputStream() : connection.getErrorStream(); + if (stream != null) { + try (InputStreamReader isr = new InputStreamReader(stream, charsetOf(contentType)); + BufferedReader reader = new BufferedReader(isr)) { - String line; - while ((line = reader.readLine()) != null) { - response.append(line); + String line; + while ((line = reader.readLine()) != null) { + response.append(line); + } } } - return response.toString(); + return new ApiResponse(responseCode, response.toString(), contentType, copyHeaders(connection)); } - private static HttpURLConnection getHttpURLConnection(String uri, String requestBody) throws IOException { + /** 대상 응답 헤더 복사. {@code getHeaderFields()} 의 null 키(상태줄)는 제외. */ + private static Map> copyHeaders(HttpURLConnection connection) { + Map> headers = new LinkedHashMap<>(); + for (Map.Entry> entry : connection.getHeaderFields().entrySet()) { + if (entry.getKey() != null) { + headers.put(entry.getKey(), entry.getValue()); + } + } + return headers; + } + + /** Content-Type 의 charset 파라미터를 파싱. 없거나 인식 불가면 UTF-8. */ + private static String charsetOf(String contentType) { + if (contentType != null) { + for (String part : contentType.split(";")) { + String token = part.trim(); + if (token.toLowerCase().startsWith("charset=")) { + String charset = token.substring("charset=".length()).replace("\"", "").trim(); + if (!charset.isEmpty() && java.nio.charset.Charset.isSupported(charset)) { + return charset; + } + } + } + } + return DEFAULT_CHARSET; + } + + private HttpURLConnection getHttpURLConnection(String uri, String requestBody) throws IOException { URL endpoint = new URL(uri); HttpURLConnection connection = (HttpURLConnection) endpoint.openConnection(); + applyTimeout(connection); connection.setRequestMethod("POST"); connection.setRequestProperty("Content-Type", "application/json; charset=utf-8"); diff --git a/src/main/java/com/eactive/apim/portal/apps/apis/filter/ApiTesterAuditLogger.java b/src/main/java/com/eactive/apim/portal/apps/apis/filter/ApiTesterAuditLogger.java new file mode 100644 index 0000000..15e5031 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/apis/filter/ApiTesterAuditLogger.java @@ -0,0 +1,294 @@ +package com.eactive.apim.portal.apps.apis.filter; + +import com.eactive.apim.portal.common.util.HttpRequestUtil; +import com.eactive.apim.portal.common.util.StringMaskingUtil; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.TextNode; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.UUID; +import javax.servlet.http.HttpServletRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; + +/** + * API 테스트베드(/api/call-api) 감사(audit) 로그 기록기. + * + *

logback 의 {@code eapim.portal.apitester.audit} 로거(전용 파일, 1년 보관)로 기록한다. + * 요청 1건당 REQ/RES 두 줄을 같은 auditId 로 남긴다.

+ * + *

마스킹 정책:

+ *
    + *
  • secret 계열 키(client_secret, password, api_key, authorization 등)의 값은 전체 마스킹
  • + *
  • 그 외 파라미터/JSON 값은 앞 일부만 남기고 마스킹
  • + *
  • JSON 이 아닌 본문은 전체 길이(byte)와 앞 {@value #NON_JSON_PREVIEW_LENGTH}글자만 남기고 마스킹
  • + *
+ */ +public final class ApiTesterAuditLogger { + + private static final Logger auditLogger = LoggerFactory.getLogger("eapim.portal.apitester.audit"); + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + /** 값 전체를 마스킹할 키(소문자 비교) */ + private static final Set SECRET_KEYS = new HashSet<>(Arrays.asList( + "client_secret", "clientsecret", "secret", "password", "passwd", "pwd", + "api_key", "apikey", "access_token", "refresh_token", "authorization")); + + /** 감사 로그에 남길 주요 요청 헤더 화이트리스트 */ + private static final String[] AUDIT_HEADERS = { + "content-type", "accept", "referer", "origin", "x-forwarded-for", + "original-api-id", "authorization"}; + + /** 마스킹된 JSON 본문 로그 최대 길이(초과분 절단) — 대용량 본문의 로그 파일 비대화 방지 */ + private static final int JSON_LOG_MAX_LENGTH = 2000; + + /** JSON 이 아닌 본문의 노출 프리뷰 글자 수 */ + private static final int NON_JSON_PREVIEW_LENGTH = 8; + + private ApiTesterAuditLogger() { + } + + /** REQ/RES 두 줄을 연결하는 짧은 감사 ID */ + public static String newAuditId() { + return UUID.randomUUID().toString().substring(0, 8); + } + + /** + * 요청 수신 시점 기록. 감사 로그 실패가 프록시 동작을 막지 않도록 예외는 삼킨다. + * + * @param targetUrl original-url 헤더 값 (없으면 null) + * @param gatewayMode 게이트웨이 모드명 (판별 전이면 "-") + * @param tokenRequest OAuth 토큰 발급 요청 여부 + * @param body 이미 읽어 둔 요청 본문 (없으면 null/빈 문자열) + */ + public static void logRequest(String auditId, HttpServletRequest request, String targetUrl, + String gatewayMode, boolean tokenRequest, String body) { + try { + StringBuilder sb = new StringBuilder(256); + sb.append("REQ [").append(auditId).append(']'); + sb.append(" ip=").append(HttpRequestUtil.getClientIpAddress(request)); + sb.append(" proxied=").append(HttpRequestUtil.isProxied(request)); + sb.append(" user=").append(currentUser()); + sb.append(" method=").append(request.getMethod()); + sb.append(" mode=").append(gatewayMode); + sb.append(" token=").append(tokenRequest); + sb.append(" target=").append(targetUrl == null ? "-" : maskQueryValues(sanitize(targetUrl))); + sb.append(" ua=\"").append(sanitize(request.getHeader("User-Agent"))).append('"'); + sb.append(" headers=").append(buildHeaderSummary(request)); + sb.append(" body=").append(buildBodySummary(request.getContentType(), tokenRequest, body)); + auditLogger.info(sb.toString()); + } catch (Exception e) { + auditLogger.warn("REQ [{}] 감사 로그 기록 실패: {}", auditId, e.toString()); + } + } + + /** 처리 완료 시점 기록. type 은 처리 분기(TOKEN_GW/TOKEN_MOCK/SAMPLE/GW/MOCK 등). */ + public static void logResult(String auditId, int status, String type, long elapsedMillis) { + auditLogger.info("RES [{}] status={} type={} elapsedMs={}", auditId, status, type, elapsedMillis); + } + + // ========================================================================= + // 요청 정보 구성 + // ========================================================================= + + /** 로그인 사용자 식별자(마스킹). 미인증이면 anonymous. */ + private static String currentUser() { + try { + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + if (auth == null || !auth.isAuthenticated() || "anonymousUser".equals(auth.getName())) { + return "anonymous"; + } + String name = auth.getName(); + return name.contains("@") ? StringMaskingUtil.maskEmail(name) : partialMask(name); + } catch (Exception e) { + return "unknown"; + } + } + + /** 화이트리스트 헤더만 {k:"v"} 형태로 요약. secret 계열 헤더 값은 마스킹. */ + private static String buildHeaderSummary(HttpServletRequest request) { + StringBuilder sb = new StringBuilder("{"); + boolean first = true; + for (String name : AUDIT_HEADERS) { + String value = request.getHeader(name); + if (value == null) { + continue; + } + if (!first) { + sb.append(", "); + } + first = false; + sb.append(name).append(":\"").append(maskHeaderValue(name, sanitize(value))).append('"'); + } + return sb.append('}').toString(); + } + + /** Authorization 등 인증 헤더는 스킴만 남기고 토큰부 마스킹. */ + private static String maskHeaderValue(String name, String value) { + if (!SECRET_KEYS.contains(name.toLowerCase())) { + return value; + } + int space = value.indexOf(' '); + if (space > 0) { + return value.substring(0, space) + " " + partialMask(value.substring(space + 1).trim()); + } + return partialMask(value); + } + + // ========================================================================= + // 본문 마스킹 + // ========================================================================= + + private static String buildBodySummary(String contentType, boolean tokenRequest, String body) { + if (body == null || body.isEmpty()) { + return "-"; + } + // 토큰 발급: form 필드 단위 마스킹 (client_secret 전체 마스킹) + if (tokenRequest) { + return "\"" + maskFormBody(body) + "\""; + } + // 일반 요청: JSON 이면 값 단위 부분 마스킹, 그 외(비 JSON)는 길이 + 프리뷰만 + if (contentType != null && contentType.toLowerCase().contains("json")) { + String maskedJson = tryMaskJson(body); + if (maskedJson != null) { + return maskedJson; + } + } + return nonJsonSummary(body); + } + + /** k=v&k=v 형태 본문의 값 단위 마스킹. secret 키는 전체 마스킹. */ + private static String maskFormBody(String body) { + StringBuilder sb = new StringBuilder(body.length()); + String[] pairs = body.split("&"); + for (int i = 0; i < pairs.length; i++) { + if (i > 0) { + sb.append('&'); + } + int eq = pairs[i].indexOf('='); + if (eq < 0) { + sb.append(partialMask(pairs[i])); + continue; + } + String key = pairs[i].substring(0, eq); + String value = pairs[i].substring(eq + 1); + sb.append(key).append('='); + sb.append(SECRET_KEYS.contains(key.toLowerCase()) ? "*****" : partialMask(value)); + } + return sanitize(sb.toString()); + } + + /** URL 쿼리스트링 값 단위 마스킹 (경로는 그대로). */ + private static String maskQueryValues(String url) { + int qs = url.indexOf('?'); + if (qs < 0) { + return url; + } + return url.substring(0, qs) + "?" + maskFormBody(url.substring(qs + 1)); + } + + /** JSON 파싱 성공 시 값 단위 마스킹 문자열, 실패 시 null. */ + private static String tryMaskJson(String body) { + try { + JsonNode masked = maskJsonNode(OBJECT_MAPPER.readTree(body)); + String out = OBJECT_MAPPER.writeValueAsString(masked); + if (out.length() > JSON_LOG_MAX_LENGTH) { + out = out.substring(0, JSON_LOG_MAX_LENGTH) + "...(truncated)"; + } + return out; + } catch (Exception e) { + return null; + } + } + + /** JSON 트리의 leaf 값을 재귀적으로 마스킹. secret 키 필드는 전체 마스킹. */ + private static JsonNode maskJsonNode(JsonNode node) { + if (node.isObject()) { + ObjectNode obj = (ObjectNode) node; + Iterator names = obj.fieldNames(); + Set fieldNames = new HashSet<>(); + while (names.hasNext()) { + fieldNames.add(names.next()); + } + for (String field : fieldNames) { + if (SECRET_KEYS.contains(field.toLowerCase())) { + obj.set(field, TextNode.valueOf("*****")); + } else { + obj.set(field, maskJsonNode(obj.get(field))); + } + } + return obj; + } + if (node.isArray()) { + ArrayNode arr = (ArrayNode) node; + for (int i = 0; i < arr.size(); i++) { + arr.set(i, maskJsonNode(arr.get(i))); + } + return arr; + } + if (node.isNull() || node.isMissingNode()) { + return node; + } + return TextNode.valueOf(partialMask(node.asText())); + } + + /** 비 JSON 본문: 전체 길이(byte)와 앞 몇 글자만 노출. */ + private static String nonJsonSummary(String body) { + int bytes = body.getBytes(StandardCharsets.UTF_8).length; + String preview = body.length() <= NON_JSON_PREVIEW_LENGTH + ? body : body.substring(0, NON_JSON_PREVIEW_LENGTH); + return "(non-json,bytes=" + bytes + ",preview=\"" + sanitize(preview) + "***\")"; + } + + // ========================================================================= + // 공통 helper + // ========================================================================= + + /** 앞 일부(최대 4자)만 남기고 마스킹. 2자 이하는 전체 마스킹. */ + private static String partialMask(String value) { + if (value == null || value.isEmpty()) { + return ""; + } + int len = value.length(); + if (len <= 2) { + return stars(len); + } + int visible = Math.min(4, Math.max(1, len / 3)); + return value.substring(0, visible) + "***"; + } + + private static String stars(int count) { + char[] arr = new char[count]; + Arrays.fill(arr, '*'); + return new String(arr); + } + + /** 제어문자·개행·따옴표를 치환해 한 줄 로그 형식을 보존. */ + private static String sanitize(String value) { + if (value == null) { + return "-"; + } + StringBuilder sb = new StringBuilder(value.length()); + for (int i = 0; i < value.length(); i++) { + char c = value.charAt(i); + if (c == '"') { + sb.append('\''); + } else if (c == '\r' || c == '\n' || c == '\t') { + sb.append(' '); + } else if (c < 0x20) { + sb.append('?'); + } else { + sb.append(c); + } + } + return sb.toString(); + } +} diff --git a/src/main/java/com/eactive/apim/portal/apps/apis/filter/ApiTesterFilter.java b/src/main/java/com/eactive/apim/portal/apps/apis/filter/ApiTesterFilter.java index 31f1574..c7496c9 100644 --- a/src/main/java/com/eactive/apim/portal/apps/apis/filter/ApiTesterFilter.java +++ b/src/main/java/com/eactive/apim/portal/apps/apis/filter/ApiTesterFilter.java @@ -4,13 +4,20 @@ package com.eactive.apim.portal.apps.apis.filter; import com.eactive.apim.portal.apps.apis.dto.ApiSpecInfoDto; import com.eactive.apim.portal.apps.apis.service.ApiService; import com.eactive.apim.portal.common.util.ApplicationContextUtil; +import com.eactive.apim.portal.common.util.StringMaskingUtil; +import com.eactive.apim.portal.djb.testbed.config.DjbTestbedGatewayProperty; import java.io.BufferedReader; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; +import java.net.URLEncoder; +import java.util.Arrays; import java.util.Enumeration; import java.util.HashMap; +import java.util.HashSet; +import java.util.List; import java.util.Map; +import java.util.Set; import java.util.UUID; import javax.servlet.Filter; import javax.servlet.FilterChain; @@ -20,6 +27,7 @@ import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -28,6 +36,13 @@ public class ApiTesterFilter implements Filter { private static final Logger logger = LoggerFactory.getLogger(ApiTesterFilter.class); + /** 대상 응답에서 클라이언트로 되돌리지 않는 헤더 (소문자 비교). */ + private static final Set BLOCKED_RESPONSE_HEADERS = new HashSet<>(Arrays.asList( + "connection", "keep-alive", "proxy-authenticate", "proxy-authorization", + "te", "trailer", "transfer-encoding", "upgrade", + "content-length", "content-encoding", "content-type", + "set-cookie", "set-cookie2")); + @Override public void init(FilterConfig filterConfig) throws ServletException { logger.debug("ApiTesterFilter initialized"); @@ -65,91 +80,375 @@ public class ApiTesterFilter implements Filter { ApiService apiSpecInfoDtoService = ApplicationContextUtil.getContext().getBean(ApiService.class); String url = httpServletRequest.getHeader("original-url"); - if (url.contains("/api/v1/oauth/token")){ - StringBuilder sb = new StringBuilder(); - BufferedReader reader = httpServletRequest.getReader(); - String line; - while ((line = reader.readLine()) != null) { - sb.append(line); - } - String body = sb.toString(); + // 감사 로그: 요청 1건당 REQ/RES 두 줄을 같은 auditId 로 남긴다 (전용 파일, 1년 보관) + String auditId = ApiTesterAuditLogger.newAuditId(); + long auditStart = System.currentTimeMillis(); + String auditType = "-"; + // 실제 프록시 호출 대상 URL (mock 은 mockUrl, 토큰 GW 는 base-url+token-path 로 original-url 과 다를 수 있음) — 오류 로그용 + String proxyTarget = null; - // Parse request parameters - Map params = new HashMap<>(); - String[] pairs = body.split("&"); - for (String pair : pairs) { - String[] keyValue = pair.split("="); - if (keyValue.length == 2) { - params.put(keyValue[0], keyValue[1]); + // original-url 헤더가 없으면 프록시 대상을 알 수 없음 → 400 (NPE 방지) + if (url == null || url.trim().isEmpty()) { + ApiTesterAuditLogger.logRequest(auditId, httpServletRequest, null, "-", false, null); + ApiTesterAuditLogger.logResult(auditId, HttpServletResponse.SC_BAD_REQUEST, "BAD_REQUEST", + System.currentTimeMillis() - auditStart); + writeJson(response, HttpServletResponse.SC_BAD_REQUEST, "{\"error\":\"original-url 헤더가 없습니다.\"}"); + return; + } + + // 실제 프록시 호출(토큰/gw/mock)은 네트워크 오류·타임아웃도 응답 content-type(JSON)에 맞춰 + // 반환하기 위해 try 로 감싼다. + try { + + // 토큰 발급 분기는 전역 게이트웨이 모드가 아니라 "요청 URL 경로"로 판단한다 (API 별 responseType 기반). + // - mock API → 프론트가 포탈 mock 토큰 경로(/api/v1/oauth/token)로 요청 → 즉시 mock 토큰 발급 + // - gw API → 프론트가 실 GW 토큰 경로(token-path)로 요청 → 실 게이트웨이 forward + DjbTestbedGatewayProperty gatewayProperty = ApplicationContextUtil.getContext().getBean(DjbTestbedGatewayProperty.class); + boolean mockTokenRequest = url.contains(DjbTestbedGatewayProperty.PORTAL_MOCK_TOKEN_PATH); + boolean tokenRequest = mockTokenRequest || url.contains(gatewayProperty.tokenPath()); + + // 본문은 한 번만 읽어 프록시 forward 와 감사 로그에 함께 사용 (GET 이면 빈 문자열) + String requestBody = readBody(httpServletRequest); + ApiTesterAuditLogger.logRequest(auditId, httpServletRequest, url, + mockTokenRequest ? "MOCK_TOKEN" : "GW", tokenRequest, requestBody); + + if (tokenRequest) { + String body = requestBody; + + if (mockTokenRequest) { + auditType = "TOKEN_MOCK"; + // mock 응답유형 API: 고정 mock 토큰 즉시 발급 (Secret 검증 없음) + Map params = new HashMap<>(); + String[] pairs = body.split("&"); + for (String pair : pairs) { + String[] keyValue = pair.split("="); + if (keyValue.length == 2) { + params.put(keyValue[0], keyValue[1]); + } } + String scope = params.getOrDefault("scope", "default"); + + String token = "{\n" + + " \"access_token\": \"" + escapeJson(gatewayProperty.mockAccessToken()) + "\",\n" + + " \"token_type\": \"bearer\",\n" + + " \"expires_in\": 86400,\n" + + " \"scope\": \""+scope +"\",\n" + + " \"jti\": \""+ UUID.randomUUID().toString()+"\"\n" + + "}"; + + response.setContentType("application/json"); + response.getWriter().println(token); + } else { + // GATEWAY: 실 게이트웨이 토큰 엔드포인트로 forward (token 발급만) + auditType = "TOKEN_GW"; + APISender apiSender = ApplicationContextUtil.getContext().getBean(APISender.class); + Map headers = new HashMap<>(); + headers.put("Content-Type", "application/x-www-form-urlencoded"); + headers.put("Accept", "application/json"); + String target = gatewayProperty.baseUrl() + gatewayProperty.tokenPath(); + proxyTarget = target; + if (logger.isDebugEnabled()) { + // client_secret 은 마스킹. body 가 비면 프론트→프록시 전송 유실, client_id 없으면 GW "client not found" 원인. + logger.debug("TOKEN_GW forward - auditId={}, target={}, bodyLen={}, body={}", + auditId, target, body.length(), StringMaskingUtil.maskFormBody(body)); + } + APISender.ApiResponse tokenResponse = apiSender.requestPost(target, headers, new HashMap<>(), body); + + writeUpstream(response, tokenResponse); } - String scope = params.getOrDefault("scope", "default"); - - String token = "{\n" + - " \"access_token\": \"djbank_gw_sample_token\",\n" + - " \"token_type\": \"bearer\",\n" + - " \"expires_in\": 86400,\n" + - " \"scope\": \""+scope +"\",\n" + - " \"jti\": \""+ UUID.randomUUID().toString()+"\"\n" + - "}"; - - response.setContentType("application/json"); - response.getWriter().println(token); } else { - ApiSpecInfoDto apiSpecInfoDto = apiSpecInfoDtoService.selectDetailByURLAndMethod(parseUri(url), httpServletRequest.getMethod()); + // 스펙 조회는 original-api-id 헤더(Swagger UI 가 x-original-api-id 확장에서 전달) 우선. + // mock/gw 응답유형은 서버주소가 mockUrl·GW base 로 치환돼 original-url 의 path 가 저장된 + // api_url 과 일치하지 않으므로 URL 매칭만으로는 스펙을 찾지 못한다. + ApiSpecInfoDto apiSpecInfoDto = selectSpec(apiSpecInfoDtoService, httpServletRequest, url); - if (apiSpecInfoDto.getResponseType() == null || apiSpecInfoDto.getResponseType().equals("sample")) { + // URL/메서드에 해당하는 API 명세가 없으면 404 (NPE 방지) + if (apiSpecInfoDto == null) { + auditType = "SPEC_NOT_FOUND"; + writeJson(response, HttpServletResponse.SC_NOT_FOUND, + "{\"error\":\"해당 URL/메서드의 API 명세를 찾을 수 없습니다.\"}"); + return; + } + + String responseType = apiSpecInfoDto.getResponseType(); + + // sample(기본): 저장된 샘플 응답 반환 (실호출 없음) + if (responseType == null || responseType.equalsIgnoreCase("sample")) { + auditType = "SAMPLE"; response.setContentType("application/json"); response.getWriter().println(apiSpecInfoDto.getSampleResponse()); + return; + } + + // gw / mock: 실주소로 forward. swagger 서버 표시(DjbTestbedSpecServerRewriter)와 동일하게 맞춘다. + // - gw : djb.gateway.base-url + path == original-url 전체 (spec servers[0].url + path) + // - mock : ApiSpecInfo.mockUrl (기존 동작) + boolean gw = "gw".equalsIgnoreCase(responseType); + auditType = gw ? "GW" : "MOCK"; + + Map headers = new HashMap<>(); + Enumeration headerNames = httpServletRequest.getHeaderNames(); + while (headerNames.hasMoreElements()) { + String header = headerNames.nextElement(); + headers.put(header, httpServletRequest.getHeader(header)); + } + headers.remove("original-url"); + headers.remove("original-api-id"); + // readBody()가 개행을 제거해 원본 Content-Length와 실제 전송 바이트가 달라질 수 있고, + // WebLogic HTTP 클라이언트는 이 불일치를 IOException으로 처리하므로 length 계열 헤더는 + // 전달하지 않는다(HttpURLConnection이 실제 바이트 수로 재설정). + headers.keySet().removeIf(k -> "content-length".equalsIgnoreCase(k) || "transfer-encoding".equalsIgnoreCase(k)); + + String targetUri; + Map paramMap; + if (gw) { + // original-url 은 브라우저가 spec 서버주소(대외)로 만든 값이라 포탈 서버에서 도달하지 못할 + // 수 있다. path/query 만 떼어 대내 base-url 에 재조립한 주소로 forward 한다. + // 프록시 대상 호스트가 바뀌므로 Host 헤더는 제거해 대상 호스트로 자동 설정되게 한다. + headers.remove("host"); + headers.remove("Host"); + targetUri = gatewayProperty.resolveGatewayCallUrl(url); + paramMap = new HashMap<>(); } else { - String mockUrl = apiSpecInfoDto.getMockUrl(); - String method = apiSpecInfoDto.getApiMethod(); - Enumeration headerNames = httpServletRequest.getHeaderNames(); + // mock: 저장된 mockUrl 로 forward하고, original-url 의 쿼리스트링을 재부착 (기존 동작 유지) + targetUri = apiSpecInfoDto.getMockUrl(); + paramMap = extractQueryParams(url); + } - Map headers = new HashMap<>(); - while (headerNames.hasMoreElements()) { - String header = headerNames.nextElement(); - headers.put(header, httpServletRequest.getHeader(header)); - } - Map paramMap = new HashMap<>(); + proxyTarget = targetUri; + if (logger.isDebugEnabled()) { + // GW SERVICE_NOT_FOUND(어댑터 URI 미등록)·AUTH_FAIL 진단용: + // 스펙 식별/응답유형, 실제 forward 대상, 전달 헤더(민감값 마스킹), 본문 길이를 남긴다. + logger.debug("{} forward - auditId={}, apiId={}, apiUrl={}, apiMethod={}, responseType={}, originalUrl={}, target={}, bodyLen={}, headers={}", + auditType, auditId, apiSpecInfoDto.getApiId(), apiSpecInfoDto.getApiUrl(), + apiSpecInfoDto.getApiMethod(), responseType, url, targetUri, + requestBody == null ? 0 : requestBody.length(), maskHeaders(headers)); + } + APISender apiSender = ApplicationContextUtil.getContext().getBean(APISender.class); + APISender.ApiResponse upstream; + if ("post".equalsIgnoreCase(apiSpecInfoDto.getApiMethod())) { + upstream = apiSender.requestPost(targetUri, headers, paramMap, requestBody); + } else { + upstream = apiSender.requestGet(targetUri, headers, paramMap); + } + if (logger.isDebugEnabled()) { + logger.debug("{} response - auditId={}, target={}, status={}, respLen={}, preview={}", + auditType, auditId, targetUri, upstream.getStatus(), + upstream.getBody() == null ? 0 : upstream.getBody().length(), previewOf(upstream.getBody())); + } + writeUpstream(response, upstream); + } - String originalUrl = headers.get("original-url"); - //sprlit original url with ? get the second part then split with & put into paramMap + } catch (java.net.SocketTimeoutException e) { + // 연결/응답 타임아웃 (djb.gateway.timeout 초과) + logger.warn("테스트베드 프록시 타임아웃 - auditId={}, type={}, method={}, originalUrl={}, proxyTarget={}, elapsedMs={}, cause={}: {}", + auditId, auditType, httpServletRequest.getMethod(), url, proxyTarget, + System.currentTimeMillis() - auditStart, e.getClass().getSimpleName(), e.getMessage()); + writeJson(response, HttpServletResponse.SC_GATEWAY_TIMEOUT, + "{\"error\":\"게이트웨이 응답 시간 초과(timeout)\",\"detail\":\"" + escapeJson(e.getMessage()) + "\"}"); + } catch (IOException e) { + // 연결 실패 등 네트워크 오류 (ConnectException: 대상 다운/포트 닫힘, UnknownHostException: 주소 오기입 등) + logger.error("테스트베드 프록시 호출 실패 - auditId={}, type={}, method={}, originalUrl={}, proxyTarget={}, elapsedMs={}, cause={}: {}", + auditId, auditType, httpServletRequest.getMethod(), url, proxyTarget, + System.currentTimeMillis() - auditStart, e.getClass().getSimpleName(), e.getMessage(), e); + writeJson(response, HttpServletResponse.SC_BAD_GATEWAY, + "{\"error\":\"게이트웨이 호출 실패\",\"detail\":\"" + escapeJson(e.getClass().getSimpleName() + ": " + e.getMessage()) + "\"}"); + } catch (Exception e) { + // 그 외 예기치 못한 오류도 JSON 으로 반환 + logger.error("테스트베드 프록시 처리 오류 - auditId={}, type={}, method={}, originalUrl={}, proxyTarget={}, elapsedMs={}, cause={}: {}", + auditId, auditType, httpServletRequest.getMethod(), url, proxyTarget, + System.currentTimeMillis() - auditStart, e.getClass().getSimpleName(), e.getMessage(), e); + writeJson(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, + "{\"error\":\"요청 처리 중 오류\",\"detail\":\"" + escapeJson(e.getMessage()) + "\"}"); + } finally { + ApiTesterAuditLogger.logResult(auditId, ((HttpServletResponse) response).getStatus(), auditType, + System.currentTimeMillis() - auditStart); + } + } - String[] originalUrlArr = originalUrl.split("\\?"); - if (originalUrlArr.length > 1) { - String[] paramArr = originalUrlArr[1].split("&"); - for (String param : paramArr) { - String[] paramKeyValue = param.split("="); - if (paramKeyValue.length > 1) { - paramMap.put(paramKeyValue[0], new String[]{paramKeyValue[1]}); - } - } - } - - headers.remove("original-url"); - headers.remove("original-api-id"); - APISender apiSender = ApplicationContextUtil.getContext().getBean(APISender.class); - - String responseStr = ""; - if (method.equalsIgnoreCase("post")) { - StringBuilder sb = new StringBuilder(); - BufferedReader reader = httpServletRequest.getReader(); - String line; - while ((line = reader.readLine()) != null) { - sb.append(line); - } - String body = sb.toString(); - responseStr = apiSender.requestPost(mockUrl, headers, paramMap, body); - - } else { - responseStr = apiSender.requestGet(mockUrl, headers, paramMap); - } - response.setContentType("application/json"); - response.getWriter().println(responseStr); + /** + * 요청 본문 전체를 문자열로 읽는다. + * + *

form-urlencoded 요청에서 상위 필터(XSS/CSRF/Multipart 등)가 이미 {@code getParameter*} 로 + * 본문 스트림을 소비했으면 {@code getReader()} 는 빈 문자열을 반환한다. 이 경우 토큰 발급 본문 + * (grant_type/client_id/client_secret/scope)이 게이트웨이로 전달되지 않아 "client not found" 로 + * 실패하므로, 파싱된 파라미터 맵으로 본문을 재구성해 복원한다.

+ */ + private String readBody(HttpServletRequest request) throws IOException { + StringBuilder sb = new StringBuilder(); + BufferedReader reader = request.getReader(); + String line; + while ((line = reader.readLine()) != null) { + sb.append(line); + } + if (sb.length() == 0 && isFormUrlEncoded(request)) { + String rebuilt = rebuildFormBodyFromParams(request); + if (!rebuilt.isEmpty()) { + logger.debug("요청 본문이 비어 파라미터 맵으로 재구성 - body={}", StringMaskingUtil.maskFormBody(rebuilt)); + return rebuilt; } } + return sb.toString(); + } + + /** Content-Type 이 application/x-www-form-urlencoded 계열인지. */ + private boolean isFormUrlEncoded(HttpServletRequest request) { + String contentType = request.getContentType(); + return contentType != null && contentType.toLowerCase().contains("application/x-www-form-urlencoded"); + } + + /** 파싱된 파라미터 맵을 form-urlencoded 본문 문자열로 재구성 (본문 스트림이 이미 소비된 경우 복원용). */ + private String rebuildFormBodyFromParams(HttpServletRequest request) { + StringBuilder sb = new StringBuilder(); + for (Map.Entry entry : request.getParameterMap().entrySet()) { + for (String value : entry.getValue()) { + if (sb.length() > 0) { + sb.append('&'); + } + try { + sb.append(URLEncoder.encode(entry.getKey(), "UTF-8")) + .append('=') + .append(URLEncoder.encode(value == null ? "" : value, "UTF-8")); + } catch (java.io.UnsupportedEncodingException e) { + sb.append(entry.getKey()).append('=').append(value == null ? "" : value); + } + } + } + return sb.toString(); + } + + /** + * 호출 대상 API 명세를 찾는다. {@code original-api-id} 헤더가 있으면 API ID 로, 없으면 기존처럼 + * {@code original-url} 의 path + 메서드로 조회한다. 없으면 null. + * + *

API ID 는 클라이언트가 보내는 값이므로 URL 조회와 동일하게 포탈 게시(display_yn='Y') 인 + * 스펙만 허용한다 — 비공개 API 가 ID 추측으로 호출되지 않도록.

+ */ + private ApiSpecInfoDto selectSpec(ApiService apiService, HttpServletRequest request, String url) { + String apiId = request.getHeader("original-api-id"); + if (apiId != null && !apiId.trim().isEmpty()) { + ApiSpecInfoDto dto = apiService.selectDetail(apiId.trim()); + if (dto != null && "Y".equalsIgnoreCase(dto.getDisplayYn())) { + return dto; + } + logger.debug("original-api-id 로 게시된 스펙을 찾지 못해 URL 매칭으로 폴백 - apiId={}", apiId); + } + return apiService.selectDetailByURLAndMethod(parseUri(url), request.getMethod()); + } + + /** original-url 의 쿼리스트링(?a=1&b=2)을 파라미터 맵으로 파싱. */ + private Map extractQueryParams(String originalUrl) { + Map paramMap = new HashMap<>(); + String[] parts = originalUrl.split("\\?"); + if (parts.length > 1) { + for (String param : parts[1].split("&")) { + String[] kv = param.split("="); + if (kv.length > 1) { + paramMap.put(kv[0], new String[]{kv[1]}); + } + } + } + return paramMap; + } + + /** + * 프록시 대상(GW/mock) 응답을 상태코드·헤더·본문 그대로 클라이언트에 전달한다. + * + *

대상이 404/400 을 내려도 200 으로 포장되지 않도록 상태코드를 그대로 세팅한다. + * 단 아래 헤더는 전달하지 않는다.

+ *
    + *
  • hop-by-hop 헤더(connection/keep-alive/transfer-encoding 등) — 연결 단위 헤더라 재전송 대상 아님
  • + *
  • content-length / content-encoding — 본문을 문자열로 다시 쓰므로 원본 길이·압축 정보가 맞지 않음
  • + *
  • set-cookie — 대상 쿠키가 포탈 도메인에 심겨 세션 쿠키를 덮어쓸 수 있어 차단
  • + *
+ */ + private void writeUpstream(ServletResponse response, APISender.ApiResponse upstream) throws IOException { + HttpServletResponse httpResponse = (HttpServletResponse) response; + httpResponse.setStatus(upstream.getStatus()); + + for (Map.Entry> entry : upstream.getHeaders().entrySet()) { + String name = entry.getKey(); + if (isBlockedResponseHeader(name) || entry.getValue() == null) { + continue; + } + boolean first = true; + for (String value : entry.getValue()) { + if (value == null) { + continue; + } + if (first) { + httpResponse.setHeader(name, value); + first = false; + } else { + httpResponse.addHeader(name, value); + } + } + } + + // Content-Type 은 응답 문자셋까지 결정하므로 헤더 복사와 별개로 마지막에 확정한다. + String contentType = upstream.getContentType(); + response.setContentType(contentType == null || contentType.trim().isEmpty() + ? "application/json" : contentType); + + response.getWriter().println(upstream.getBody() == null ? "" : upstream.getBody()); + } + + /** 클라이언트로 되돌리면 안 되는 응답 헤더인지. */ + private boolean isBlockedResponseHeader(String name) { + return name == null || BLOCKED_RESPONSE_HEADERS.contains(name.toLowerCase()); + } + + /** forward 헤더 debug 출력용 — 민감 헤더(토큰/쿠키 등)는 StringMaskingUtil 로 마스킹. */ + private String maskHeaders(Map headers) { + StringBuilder sb = new StringBuilder("{"); + for (Map.Entry e : headers.entrySet()) { + if (sb.length() > 1) { + sb.append(", "); + } + sb.append(e.getKey()).append(':').append(StringMaskingUtil.maskHeaderValue(e.getKey(), e.getValue())); + } + return sb.append('}').toString(); + } + + /** 응답 body debug 프리뷰 — 앞 300자까지만 (개행 제거). */ + private String previewOf(String body) { + if (body == null) { + return "null"; + } + String flat = body.replaceAll("\\s+", " ").trim(); + return flat.length() > 300 ? flat.substring(0, 300) + "…" : flat; + } + + private void writeJson(ServletResponse response, int status, String json) throws IOException { + ((HttpServletResponse) response).setStatus(status); + response.setContentType("application/json"); + response.getWriter().println(json); + } + + /** JSON 문자열 값에 넣기 위한 최소 escape (예외 메시지 등). */ + private String escapeJson(String s) { + if (s == null) { + return ""; + } + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + switch (c) { + case '\\': sb.append("\\\\"); break; + case '"': sb.append("\\\""); break; + case '\n': sb.append("\\n"); break; + case '\r': sb.append("\\r"); break; + case '\t': sb.append("\\t"); break; + default: + if (c < 0x20) { + sb.append(String.format("\\u%04x", (int) c)); + } else { + sb.append(c); + } + } + } + return sb.toString(); } @Override diff --git a/src/main/java/com/eactive/apim/portal/apps/apis/mapper/ApiSpecMapper.java b/src/main/java/com/eactive/apim/portal/apps/apis/mapper/ApiSpecMapper.java index 4787100..bd6ddc6 100644 --- a/src/main/java/com/eactive/apim/portal/apps/apis/mapper/ApiSpecMapper.java +++ b/src/main/java/com/eactive/apim/portal/apps/apis/mapper/ApiSpecMapper.java @@ -11,8 +11,14 @@ public interface ApiSpecMapper { ApiSpecInfoDto mapToDto(ApiSpecInfo apiSpecInfo); + /** + * 조회 결과가 없으면 {@code null} 을 반환한다. + * + *

빈 DTO 를 반환하면 호출측의 "스펙 없음" 분기가 동작하지 않고 모든 필드가 null 인 DTO 로 + * 진행돼(예: {@code responseType == null} → sample 취급) 오동작한다.

+ */ default ApiSpecInfoDto map(Optional optionalApiSpecInfo) { - return optionalApiSpecInfo.map(this::mapToDto).orElse(new ApiSpecInfoDto()); + return optionalApiSpecInfo.map(this::mapToDto).orElse(null); } } diff --git a/src/main/java/com/eactive/apim/portal/apps/app/controller/MyAppController.java b/src/main/java/com/eactive/apim/portal/apps/app/controller/MyAppController.java index c01fec2..81aebe4 100644 --- a/src/main/java/com/eactive/apim/portal/apps/app/controller/MyAppController.java +++ b/src/main/java/com/eactive/apim/portal/apps/app/controller/MyAppController.java @@ -1,6 +1,7 @@ package com.eactive.apim.portal.apps.app.controller; import com.eactive.apim.portal.apprequest.entity.AppRequest; +import com.eactive.apim.portal.approval.statemachine.InvalidApprovalTransitionException; import com.eactive.apim.portal.apps.apis.dto.ApiSpecInfoDto; import com.eactive.apim.portal.apps.apis.service.ApiService; import com.eactive.apim.portal.apps.apiservice.dto.ApiGroupSearch; @@ -11,6 +12,10 @@ import com.eactive.apim.portal.apps.app.dto.ApiKeyRegistrationDTO; import com.eactive.apim.portal.apps.app.dto.AppRequestDTO; import com.eactive.apim.portal.apps.app.dto.ClientDTO; import com.eactive.apim.portal.apps.app.service.AppServiceFacade; +import com.eactive.apim.portal.apps.auth.twofactor.StepUpProtectedPaths; +import com.eactive.apim.portal.apps.auth.twofactor.TwoFactorProperties; +import com.eactive.apim.portal.apps.auth.twofactor.TwoFactorService; +import com.eactive.apim.portal.common.exception.UserErrorMessageResolver; import com.eactive.apim.portal.common.user.PortalAuthenticatedUser; import com.eactive.apim.portal.common.util.ApiServiceHelper; import com.eactive.apim.portal.common.util.SecurityUtil; @@ -18,9 +23,12 @@ import com.eactive.apim.portal.file.service.FileTypeDetector; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; import javax.servlet.http.HttpSession; import javax.validation.Valid; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -44,8 +52,9 @@ import org.springframework.web.multipart.MultipartFile; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.support.RedirectAttributes; +@Slf4j @Controller -@RequestMapping("/myapikey") +@RequestMapping("/clients") @RequiredArgsConstructor @SessionAttributes({"apiKeyRegistration", "apiKeyModification"}) public class MyAppController { @@ -77,6 +86,8 @@ public class MyAppController { private final ApiService apiService; private final ApiServiceHelper apiServiceHelper; private final FileTypeDetector fileTypeDetector; + private final TwoFactorService twoFactorService; + private final TwoFactorProperties twoFactorProperties; private static final long MAX_APP_ICON_BYTES = 2L * 1024 * 1024; // 2MB @@ -93,8 +104,28 @@ public class MyAppController { List appRequests = appServiceFacade.getPendingApiKeyList(user.getPortalOrg()); List apikeys = appServiceFacade.getApikeyList(user.getPortalOrg()); - model.addAttribute("appRequests", appRequests); + // 기존 클라이언트에 걸린 변경/해지 신청은 별도 카드로 내지 않고 해당 클라이언트 카드의 배지로 흡수한다. + // (같은 이름의 카드가 둘로 보여 클라이언트가 두 개인 것처럼 읽히기 때문) + // 신규(NEW) 신청처럼 아직 클라이언트가 없는 건만 신청 카드로 남긴다. + Set ownedClientIds = apikeys.stream() + .map(ClientDTO::getClientid) + .filter(StringUtils::isNotBlank) + .collect(Collectors.toSet()); + + Map openRequestByClient = new java.util.HashMap<>(); + List standaloneRequests = new java.util.ArrayList<>(); + for (AppRequest request : appRequests) { + String clientId = request.getClientId(); + if (StringUtils.isNotBlank(clientId) && ownedClientIds.contains(clientId) + && openRequestByClient.putIfAbsent(clientId, request) == null) { + continue; + } + standaloneRequests.add(request); + } + + model.addAttribute("appRequests", standaloneRequests); model.addAttribute("apiKeys", apikeys); + model.addAttribute("openRequestByClient", openRequestByClient); return new ModelAndView(API_KEY_LIST); } @@ -108,14 +139,14 @@ public class MyAppController { @Secured("ROLE_APP") public ModelAndView appRequestDetail(@RequestParam(value = "id", required = false) String id, Model model) { if (id == null) { - return new ModelAndView("redirect:/myapikey"); + return new ModelAndView("redirect:/clients"); } PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser(); AppRequestDTO appRequest = appServiceFacade.getAppRequestById(id, user.getPortalOrg()); if (appRequest == null) { - return new ModelAndView("redirect:/myapikey"); + return new ModelAndView("redirect:/clients"); } // API 목록 조회 및 설정 @@ -146,14 +177,14 @@ public class MyAppController { @Secured("ROLE_APP") public ModelAndView credentialDetail(@RequestParam(value = "id", required = false) String id, Model model) { if (id == null) { - return new ModelAndView("redirect:/myapikey"); + return new ModelAndView("redirect:/clients"); } PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser(); ClientDTO apiKey = appServiceFacade.getApiKey(user.getPortalOrg().getId(), id); if (apiKey == null) { - return new ModelAndView("redirect:/myapikey"); + return new ModelAndView("redirect:/clients"); } // API 목록에 서비스 정보 추가 @@ -168,7 +199,17 @@ public class MyAppController { } }); + // Client Secret은 초기 HTML에 담지 않는다. (비번 확인 후 서버가 1회만 반환) + boolean secretAvailable = StringUtils.isNotBlank(apiKey.getClientsecret()); + apiKey.setClientsecret(null); + model.addAttribute("apiKey", apiKey); + model.addAttribute("secretAvailable", secretAvailable); + model.addAttribute("pendingDeleteRequest", appServiceFacade.hasPendingDeleteRequest(id)); + // 해지 불가 사유(변경 신청 진행 중 등) — 클릭 시 사전검사가 이 사유를 안내하고 2FA 로 넘어가지 않는다 + model.addAttribute("deleteBlockReason", appServiceFacade.resolveDeleteBlockReason(id)); + // 걸려 있는 미완료 변경/해지 신청 — 상태 표시 + 취소 버튼용(결재 미개시 건도 포함) + model.addAttribute("openRequest", appServiceFacade.findOpenRequest(id)); return new ModelAndView(CREDENTIAL_DETAIL); } @@ -207,49 +248,112 @@ public class MyAppController { appServiceFacade.cancelApiRequest(id, SecurityUtil.getPortalAuthenticatedUser().getPortalOrg()); result.put("success", true); result.put("message", "신청이 취소되었습니다."); - } catch (Exception e) { + } catch (InvalidApprovalTransitionException e) { result.put("success", false); - result.put("message", "신청 취소 중 오류가 발생했습니다: " + e.getMessage()); + result.put("message", "내부 결재가 진행 중이라 신청을 취소할 수 없습니다. 취소가 필요한 경우 관리자에게 문의해 주세요."); + } catch (IllegalStateException e) { + log.error("API Key 신청 취소 중 GW 차단 실패. id={}", id, e); + result.put("success", false); + result.put("message", e.getMessage()); + } catch (Exception e) { + log.error("API Key 신청 취소 실패. id={}", id, e); + result.put("success", false); + result.put("message", "신청 취소 중 오류가 발생했습니다."); } return result; } /** - * API Key를 삭제합니다. - * AJAX 요청을 지원하기 위해 @ResponseBody를 사용하여 JSON 응답 반환 + * API 이용 해지를 신청합니다. (AppRequestType.DELETE 결재 신청 생성) + * 즉시 차단/삭제하지 않으며, eapim-admin 관리자 승인 시점에 GW 차단/삭제와 + * PTL_CREDENTIAL 삭제가 실행됩니다. 승인 전까지 API는 정상 동작합니다. + * 본인 확인은 step-up 2FA({@link StepUpProtectedPaths#APP_KEY_DELETE} 인터셉터 가드)가 담당합니다. * - * @param requestData 요청 데이터 (clientId와 type 포함) + * @param requestData 요청 데이터 (clientId, reason) * @return 성공/실패 결과를 담은 Map */ + /** + * 해지 신청 사전검사. 진행 중인 변경/해지 결재가 있어 신청이 불가한지 알려줍니다. + * + *

step-up 2FA 가 걸린 {@code /clients/api_key_delete} 이전에 호출해, + * 어차피 거절될 요청으로 2FA 를 반복 유도하지 않도록 한다(2FA 가드 대상 경로가 아니다). + * 최종 판정은 {@code createDeleteRequest} 가 다시 수행하므로 이 검사는 안내 목적이다.

+ */ + @GetMapping("/credential/delete-precheck") + @Secured("ROLE_API_KEY_REQUEST") + @ResponseBody + public Map deleteApiKeyPrecheck(@RequestParam("clientId") String clientId) { + Map result = new java.util.HashMap<>(); + + PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser(); + String orgId = user.getPortalOrg().getId(); + + // 소유권 확인 (다른 조직 인증키의 결재 진행 상태 노출 방지) + if (clientId == null || clientId.trim().isEmpty() + || appServiceFacade.getApiKey(orgId, clientId) == null) { + result.put("blocked", true); + result.put("msg", "해당 인증키를 찾을 수 없습니다."); + return result; + } + + String blockReason = appServiceFacade.resolveDeleteBlockReason(clientId); + result.put("blocked", blockReason != null); + result.put("msg", blockReason); + return result; + } + @PostMapping("/api_key_delete") @Secured("ROLE_API_KEY_REQUEST") @ResponseBody public Map deleteApiKey(@RequestBody Map requestData) { Map result = new java.util.HashMap<>(); - try { - String clientId = requestData.get("clientId"); - String type = requestData.get("type"); - - if (clientId == null || clientId.trim().isEmpty()) { - result.put("success", false); - result.put("msg", "클라이언트 ID가 필요합니다."); - return result; - } - - PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser(); - - // API Key 즉시 삭제 (승인 프로세스 없이) - appServiceFacade.deleteApp(user.getPortalOrg().getId(), clientId); - - result.put("success", true); - result.put("msg", "API Key가 삭제되었습니다."); - } catch (Exception e) { + String clientId = requestData.get("clientId"); + if (clientId == null || clientId.trim().isEmpty()) { result.put("success", false); - result.put("msg", "삭제 요청 중 오류가 발생했습니다: " + e.getMessage()); + result.put("msg", "클라이언트 ID가 필요합니다."); + return result; } + String reason = requestData.get("reason"); + if (reason == null || reason.trim().isEmpty()) { + result.put("success", false); + result.put("msg", "해지 사유를 입력해 주세요."); + return result; + } + if (reason.length() > 1000) { + result.put("success", false); + result.put("msg", "해지 사유는 1000자 이내로 입력해 주세요."); + return result; + } + + PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser(); + String orgId = user.getPortalOrg().getId(); + + // 1. 소유권 확인 (다른 조직의 인증키 해지 방지) + if (appServiceFacade.getApiKey(orgId, clientId) == null) { + result.put("success", false); + result.put("msg", "해당 인증키를 찾을 수 없습니다."); + return result; + } + + // 2. 해지 신청 생성 + 결재 개시 (GW/credential 은 승인 시점에 admin 이 처리) + try { + appServiceFacade.createDeleteRequest(clientId, reason.trim(), user.getPortalOrg()); + } catch (IllegalStateException e) { + result.put("success", false); + result.put("msg", e.getMessage()); + return result; + } catch (Exception e) { + log.error("API 이용 해지 신청 실패 - clientId={}", clientId, e); + result.put("success", false); + result.put("msg", UserErrorMessageResolver.resolveAsHtml(e)); + return result; + } + + result.put("success", true); + result.put("msg", "해지 신청이 접수되었습니다. 관리자 승인 후 인증키가 삭제됩니다."); return result; } @@ -282,6 +386,51 @@ public class MyAppController { return result; } + /** + * Client Secret을 1회 노출하고 즉시 DB에서 물리 삭제합니다. + * 본인 확인은 step-up 2FA({@link StepUpProtectedPaths#REVEAL_SECRET} 인터셉터 가드)가 담당하며, + * 통과권 없이 진입하면 401(stepUpRequired) 로 차단됩니다. + * 보안 정책상 비밀정보는 최초 1회만 제공됩니다. + * + * @param requestData clientId 포함 + * @return {success, secret} / {success:false, alreadyRevealed:true} / {success:false, message} + */ + @PostMapping("/credential/reveal-secret") + @Secured("ROLE_APP") + @ResponseBody + public Map revealClientSecret(@RequestBody Map requestData) { + Map result = new java.util.HashMap<>(); + + String clientId = requestData.get("clientId"); + + if (clientId == null || clientId.trim().isEmpty()) { + result.put("success", false); + result.put("message", "클라이언트 ID가 필요합니다."); + return result; + } + + PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser(); + + // 소유권 확인 + 1회 노출 + 물리 삭제 (본인 확인은 step-up 2FA 인터셉터가 선행) + try { + String secret = appServiceFacade.revealAndDeleteClientSecret(user.getPortalOrg().getId(), clientId); + if (secret == null) { + result.put("success", false); + result.put("alreadyRevealed", true); + result.put("message", "이미 1회 노출되어 삭제된 인증정보입니다."); + return result; + } + result.put("success", true); + result.put("secret", secret); + } catch (Exception e) { + log.error("Client Secret 노출 처리 실패 - clientId={}", clientId, e); + result.put("success", false); + result.put("message", "인증정보 조회 중 오류가 발생했습니다."); + } + + return result; + } + /** * API Key 요청 이력을 페이지 형태로 조회합니다. * 각 요청의 내용을 사용자 친화적인 형식으로 포맷팅하여 표시합니다. @@ -344,7 +493,7 @@ public class MyAppController { @Secured("ROLE_API_KEY_REQUEST_VIEW") public ModelAndView apiRequestDetail(@RequestParam(value = "id", required = false) String id, Model model) { if (id == null) { - return new ModelAndView("redirect:/myapikey/api_key_request/history"); + return new ModelAndView("redirect:/clients/api_key_request/history"); } PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser(); @@ -442,7 +591,7 @@ public class MyAppController { registration.setIpWhitelistFromString(ipWhitelist); } - return new ModelAndView("redirect:/myapikey/register/step2"); + return new ModelAndView("redirect:/clients/register/step2"); } catch (Exception e) { setupStepModel(model, 1); @@ -507,7 +656,7 @@ public class MyAppController { // 1단계가 완료되었는지 검증 if (!registration.isStep1Complete()) { redirectAttributes.addFlashAttribute("error", "먼저 기본 정보를 입력해주세요."); - return new ModelAndView("redirect:/myapikey/register/step1"); + return new ModelAndView("redirect:/clients/register/step1"); } // 서비스 카테고리만 가져오기 (API는 AJAX로 로드됨) @@ -538,7 +687,7 @@ public class MyAppController { } // 1단계로 리다이렉트 - return new ModelAndView("redirect:/myapikey/register/step1"); + return new ModelAndView("redirect:/clients/register/step1"); } /** @@ -556,22 +705,16 @@ public class MyAppController { // 1단계가 완료되었는지 검증 if (!registration.isStep1Complete()) { redirectAttributes.addFlashAttribute("error", "먼저 기본 정보를 입력해주세요."); - return new ModelAndView("redirect:/myapikey/register/step1"); + return new ModelAndView("redirect:/clients/register/step1"); } - // API 선택 검증 - if (selectedApis == null || selectedApis.isEmpty()) { - redirectAttributes.addFlashAttribute("error", "최소 1개 이상의 API를 선택해주세요."); - return new ModelAndView("redirect:/myapikey/register/step2"); - } - - // 선택된 API를 세션에 저장 - registration.setSelectedApis(selectedApis); + // API 선택은 선택 사항 — 미선택(빈 목록)도 허용한다. + registration.setSelectedApis(selectedApis != null ? selectedApis : new ArrayList<>()); // 등록이 완료되었는지 최종 검증 if (!registration.isComplete()) { redirectAttributes.addFlashAttribute("error", "등록 정보가 완전하지 않습니다."); - return new ModelAndView("redirect:/myapikey/register/step1"); + return new ModelAndView("redirect:/clients/register/step1"); } PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser(); @@ -588,12 +731,12 @@ public class MyAppController { // 성공적으로 완료된 후 세션 초기화 sessionStatus.setComplete(); - return new ModelAndView("redirect:/myapikey/register/step3"); + return new ModelAndView("redirect:/clients/register/step3"); } catch (Exception e) { // 실패 시 에러 메시지와 함께 step2로 돌아감 redirectAttributes.addFlashAttribute("error", "API Key 등록 중 오류가 발생했습니다. 다시 시도해주세요."); - return new ModelAndView("redirect:/myapikey/register/step2"); + return new ModelAndView("redirect:/clients/register/step2"); } } @@ -611,7 +754,7 @@ public class MyAppController { // 직접 접근 방지: Step 2 POST를 거치지 않고 직접 접근한 경우 if (registrationSuccess == null || !registrationSuccess) { - return new ModelAndView("redirect:/myapikey"); + return new ModelAndView("redirect:/clients"); } // 결과 페이지 표시용 속성 설정 @@ -630,7 +773,7 @@ public class MyAppController { public String cancelRegistration(SessionStatus sessionStatus) { // 등록과 관련된 세션 데이터 초기화 sessionStatus.setComplete(); - return "redirect:/myapikey"; + return "redirect:/clients"; } /** @@ -674,13 +817,15 @@ public class MyAppController { @Secured("ROLE_API_KEY_REQUEST") public ModelAndView modifyStep1( @RequestParam(value = "clientId", required = false) String clientId, + @RequestParam(value = "goto", required = false) String gotoStep, + @RequestParam(value = "apiApplyToast", required = false) String apiApplyToast, @ModelAttribute("apiKeyModification") ApiKeyRegistrationDTO modification, SessionStatus sessionStatus, Model model, RedirectAttributes redirectAttributes) { if (clientId == null) { - return new ModelAndView("redirect:/myapikey"); + return new ModelAndView("redirect:/clients"); } PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser(); @@ -688,7 +833,7 @@ public class MyAppController { // 기존 API Key 정보 조회 ClientDTO apiKey = appServiceFacade.getApiKey(user.getPortalOrg().getId(), clientId); if (apiKey == null) { - return new ModelAndView("redirect:/myapikey"); + return new ModelAndView("redirect:/clients"); } // 새로운 수정 세션 시작시에만 초기화 @@ -730,6 +875,16 @@ public class MyAppController { model.addAttribute("apiKeyModification", modification); } + // API 신청 절차: 클라이언트 1건 보유 시 API 상세에서 goto=apis 로 진입 + // → 세션 초기화 후 API 선택(2단계)로 직행 (기본 정보 미완성이면 step2 가드가 1단계로 되돌림) + if ("apis".equals(gotoStep)) { + String redirectUrl = "redirect:/clients/modify/step2"; + if (apiApplyToast != null && apiApplyToast.matches("[a-zA-Z_-]{1,30}")) { + redirectUrl += "?apiApplyToast=" + apiApplyToast; + } + return new ModelAndView(redirectUrl); + } + // Step 모델 설정 setupStepModel(model, 1); model.addAttribute("userOrg", user.getPortalOrg()); @@ -781,7 +936,7 @@ public class MyAppController { modification.setIpWhitelistFromString(ipWhitelist); } - return new ModelAndView("redirect:/myapikey/modify/step2"); + return new ModelAndView("redirect:/clients/modify/step2"); } catch (Exception e) { setupStepModel(model, 1); @@ -803,7 +958,7 @@ public class MyAppController { // 1단계가 완료되었는지 검증 if (!modification.isStep1Complete()) { redirectAttributes.addFlashAttribute("error", "먼저 기본 정보를 입력해주세요."); - return new ModelAndView("redirect:/myapikey/modify/step1?clientId=" + modification.getClientId()); + return new ModelAndView("redirect:/clients/modify/step1?clientId=" + modification.getClientId()); } // 서비스 카테고리와 API 목록 가져오기 @@ -813,6 +968,8 @@ public class MyAppController { setupStepModel(model, 2); model.addAttribute("apiServices", apiServices); model.addAttribute("modification", modification); + // 최종 반영(저장) 직전 2FA 필요 여부 → 폼 JS 분기용 + model.addAttribute("twofaRequired", isAppModifyTwofaRequired()); return new ModelAndView(API_KEY_MODIFY_STEP2); } @@ -834,7 +991,7 @@ public class MyAppController { } // 1단계로 리다이렉트 - return new ModelAndView("redirect:/myapikey/modify/step1?clientId=" + modification.getClientId()); + return new ModelAndView("redirect:/clients/modify/step1?clientId=" + modification.getClientId()); } /** @@ -847,18 +1004,19 @@ public class MyAppController { @RequestParam(value = "selectedApis", required = false) List selectedApis, @ModelAttribute("apiKeyModification") ApiKeyRegistrationDTO modification, SessionStatus sessionStatus, + HttpSession session, RedirectAttributes redirectAttributes) { // 1단계가 완료되었는지 검증 if (!modification.isStep1Complete()) { redirectAttributes.addFlashAttribute("error", "먼저 기본 정보를 입력해주세요."); - return new ModelAndView("redirect:/myapikey/modify/step1?clientId=" + modification.getClientId()); + return new ModelAndView("redirect:/clients/modify/step1?clientId=" + modification.getClientId()); } // API 선택 검증 if (selectedApis == null || selectedApis.isEmpty()) { redirectAttributes.addFlashAttribute("error", "최소 1개 이상의 API를 선택해주세요."); - return new ModelAndView("redirect:/myapikey/modify/step2"); + return new ModelAndView("redirect:/clients/modify/step2"); } // 선택된 API를 세션에 저장 @@ -867,7 +1025,15 @@ public class MyAppController { // 등록이 완료되었는지 최종 검증 if (!modification.isComplete()) { redirectAttributes.addFlashAttribute("error", "수정 정보가 완전하지 않습니다."); - return new ModelAndView("redirect:/myapikey/modify/step1?clientId=" + modification.getClientId()); + return new ModelAndView("redirect:/clients/modify/step1?clientId=" + modification.getClientId()); + } + + // 반영 직전 2FA: 통과권이 없으면 커밋하지 않고 step2 로 되돌린다(프론트가 먼저 2FA 팝업을 띄운다). + // 진입(step1)이 아닌 최종 반영 시점에만 인증을 요구해 다단계 진행 중 중복 인증을 막는다. + if (isAppModifyTwofaRequired() + && !twoFactorService.consumeStepUpPass(session, StepUpProtectedPaths.APP_MODIFY_COMMIT)) { + redirectAttributes.addFlashAttribute("error", "추가 인증(2FA) 후 다시 시도해 주세요."); + return new ModelAndView("redirect:/clients/modify/step2"); } PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser(); @@ -884,12 +1050,12 @@ public class MyAppController { // 성공적으로 완료된 후 세션 초기화 sessionStatus.setComplete(); - return new ModelAndView("redirect:/myapikey/modify/step3"); + return new ModelAndView("redirect:/clients/modify/step3"); } catch (Exception e) { // 실패 시 에러 메시지와 함께 step2로 돌아감 redirectAttributes.addFlashAttribute("error", "API Key 수정 요청 중 오류가 발생했습니다. 다시 시도해주세요."); - return new ModelAndView("redirect:/myapikey/modify/step2"); + return new ModelAndView("redirect:/clients/modify/step2"); } } @@ -906,7 +1072,7 @@ public class MyAppController { // 직접 접근 방지: Step 2 POST를 거치지 않고 직접 접근한 경우 if (modificationComplete == null || !modificationComplete) { - return new ModelAndView("redirect:/myapikey"); + return new ModelAndView("redirect:/clients"); } // 결과 페이지 표시용 속성 설정 @@ -935,12 +1101,18 @@ public class MyAppController { // clientId가 있으면 상세 페이지로, 없으면 목록으로 if (clientId != null && !clientId.isEmpty()) { - return "redirect:/myapikey/credential_detail?id=" + clientId; + return "redirect:/clients/credential_detail?id=" + clientId; } else { - return "redirect:/myapikey"; + return "redirect:/clients"; } } + /** 앱 수정 최종 반영 직전 2FA(step-up)가 현재 활성인지 — 전체/지점 스위치 AND */ + private boolean isAppModifyTwofaRequired() { + return twoFactorProperties.isStepUpEnabled() + && twoFactorProperties.isStepUpPointEnabled(StepUpProtectedPaths.APP_MODIFY_COMMIT); + } + } diff --git a/src/main/java/com/eactive/apim/portal/apps/app/dto/ApiKeyRegistrationDTO.java b/src/main/java/com/eactive/apim/portal/apps/app/dto/ApiKeyRegistrationDTO.java index 2672c3a..486ebeb 100644 --- a/src/main/java/com/eactive/apim/portal/apps/app/dto/ApiKeyRegistrationDTO.java +++ b/src/main/java/com/eactive/apim/portal/apps/app/dto/ApiKeyRegistrationDTO.java @@ -36,12 +36,12 @@ public class ApiKeyRegistrationDTO implements Serializable { private byte[] appIconData; private String appIconContentType; - @NotBlank(message = "앱 이름을 입력해주세요.") - @Length(max = 100, message = "앱 이름은 100자를 초과할 수 없습니다.") + @NotBlank(message = "클라이언트 이름을 입력해주세요.") + @Length(max = 100, message = "클라이언트 이름은 100자를 초과할 수 없습니다.") private String appName; - @NotBlank(message = "앱 설명을 입력해주세요.") - @Length(max = 500, message = "앱 설명은 500자를 초과할 수 없습니다.") + @NotBlank(message = "클라이언트 설명을 입력해주세요.") + @Length(max = 500, message = "클라이언트 설명은 500자를 초과할 수 없습니다.") private String appDescription; private String callbackUrl; @@ -99,6 +99,7 @@ public class ApiKeyRegistrationDTO implements Serializable { } public boolean isComplete() { - return isStep1Complete() && isStep2Complete(); + // API 선택은 선택 사항이므로 기본 정보(Step1)만 완료되면 등록 가능하다. + return isStep1Complete(); } } diff --git a/src/main/java/com/eactive/apim/portal/apps/app/dto/AppRequestDTO.java b/src/main/java/com/eactive/apim/portal/apps/app/dto/AppRequestDTO.java index 2889f4c..adec83c 100644 --- a/src/main/java/com/eactive/apim/portal/apps/app/dto/AppRequestDTO.java +++ b/src/main/java/com/eactive/apim/portal/apps/app/dto/AppRequestDTO.java @@ -9,7 +9,6 @@ import com.eactive.apim.portal.apps.user.dto.PortalOrgDTO; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; -import javax.validation.constraints.NotEmpty; import lombok.Data; @Data @@ -25,8 +24,7 @@ public class AppRequestDTO { private ApprovalDTO approval; - @NotEmpty - private String apiList = ""; //comma separated api id list + private String apiList = ""; //comma separated api id list (미선택 허용) private String apiGroupList = ""; //comma separated api group id list diff --git a/src/main/java/com/eactive/apim/portal/apps/app/service/AdminGatewayClient.java b/src/main/java/com/eactive/apim/portal/apps/app/service/AdminGatewayClient.java new file mode 100644 index 0000000..550233a --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/app/service/AdminGatewayClient.java @@ -0,0 +1,54 @@ +package com.eactive.apim.portal.apps.app.service; + +import com.eactive.apim.portal.portalproperty.service.PortalPropertyService; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +/** + * 관리자(admin) 포털의 내부 API를 호출하는 클라이언트. + * + *

GW 인증서버(TSEAIAU01) 제어는 포털이 직접 하지 않고, broadcast 인프라를 갖춘 admin 에 위임한다. + * admin base URL 은 {@code PTL_PROPERTY} (group={@code Portal}, name={@code djb.admin.base-url}) 에서 조회한다.

+ */ +@Slf4j +@Service +@RequiredArgsConstructor +public class AdminGatewayClient { + + private static final String PROP_GROUP = "Portal"; + private static final String PROP_ADMIN_BASE_URL = "admin.base-url"; + private static final String DEFAULT_ADMIN_BASE_URL = "http://localhost:39120"; + private static final String CLIENT_BLOCK_PATH = "/onl/admin/authserver/clientBlock.json?clientId={clientId}"; + + private final RestTemplate restTemplate; + private final PortalPropertyService portalPropertyService; + + /** + * clientId 의 GW 인증 클라이언트 차단(appstatus=0) + GW 캐시 리로드를 admin 에 요청한다. + * + * @param clientId 차단할 클라이언트 ID + * @throws RuntimeException admin 미응답/네트워크 오류 또는 admin 처리 실패 시 (호출측에서 처리) + */ + public void blockClient(String clientId) { + String baseUrl = portalPropertyService.getOrCreateProperty( + PROP_GROUP, PROP_ADMIN_BASE_URL, DEFAULT_ADMIN_BASE_URL, "admin(관리자포털) 내부 API base URL"); + + String url = baseUrl.replaceAll("/+$", "") + CLIENT_BLOCK_PATH; + + // 네트워크/HTTP 오류는 RestTemplate 이 예외로 던진다. + ResponseEntity response = restTemplate.postForEntity(url, null, Map.class, clientId); + + Map body = response.getBody(); + boolean success = body != null && Boolean.TRUE.equals(body.get("success")); + if (!success) { + String msg = body != null ? String.valueOf(body.get("msg")) : "응답 본문 없음"; + throw new IllegalStateException("admin clientBlock 처리 실패 - clientId=" + clientId + ", msg=" + msg); + } + + log.info("admin GW 차단/리로드 위임 성공 - clientId={}", clientId); + } +} diff --git a/src/main/java/com/eactive/apim/portal/apps/app/service/AppServiceFacade.java b/src/main/java/com/eactive/apim/portal/apps/app/service/AppServiceFacade.java index b765fec..a2e1576 100644 --- a/src/main/java/com/eactive/apim/portal/apps/app/service/AppServiceFacade.java +++ b/src/main/java/com/eactive/apim/portal/apps/app/service/AppServiceFacade.java @@ -7,6 +7,7 @@ import com.eactive.apim.portal.apprequest.entity.AppRequest; import com.eactive.apim.portal.apprequest.entity.AppRequestType; import com.eactive.apim.portal.apprequest.repository.AppRequestRepository; import com.eactive.apim.portal.approval.entity.Approval; +import com.eactive.apim.portal.approval.statemachine.CreatedState; import com.eactive.apim.portal.approval.statemachine.ProcessingState; import com.eactive.apim.portal.approval.statemachine.RequestedState; import com.eactive.apim.portal.apps.apis.dto.ApiSpecInfoDto; @@ -31,6 +32,7 @@ import com.eactive.apim.portal.portalorg.entity.PortalOrg; import java.io.IOException; import java.time.LocalDateTime; import java.util.Arrays; +import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Optional; @@ -59,21 +61,47 @@ public class AppServiceFacade { private final ApiServiceHelper apiServiceHelper; private final FileService fileService; private final PasswordEncoder passwordEncoder; + private final AdminGatewayClient adminGatewayClient; public List getApikeyList(PortalOrg portalOrg) { List clients = credentialRepository.findAllByOrgid(portalOrg.getId()); - return clients.stream().map(credentialMapper::toVo).collect(Collectors.toList()); + // 최근 수정(발급/변경) 순으로 정렬. 수정일 없는 건은 뒤로. + return clients.stream() + .sorted(Comparator.comparing(Credential::getModifiedon, + Comparator.nullsLast(Comparator.reverseOrder()))) + .map(credentialMapper::toVo).collect(Collectors.toList()); } public List getPendingApiKeyList(PortalOrg portalOrg) { - List appRequests = appRequestRepository.findAllByOrgAndTypeIsInAndApproval_ApprovalStatusIn(portalOrg, Arrays.asList(AppRequestType.NEW, AppRequestType.MODIFY, AppRequestType.DELETE), + List types = Arrays.asList(AppRequestType.NEW, AppRequestType.MODIFY, AppRequestType.DELETE); + List appRequests = appRequestRepository.findAllByOrgAndTypeIsInAndApproval_ApprovalStatusIn(portalOrg, types, Arrays.asList(new ProcessingState(), new RequestedState())); + // 승인정보(approval) 없는 신청도 목록에 노출` 한다. (사용자가 직접 삭제 가능) + appRequests.addAll(appRequestRepository .findAllByOrgAndTypeIsInAndApprovalIsNull(portalOrg, types)); + + // 진행중(PROCESSING) → 요청됨(REQUESTED) → 승인정보 없음 순, 같은 상태끼리는 최근 신청 순 + appRequests.sort(Comparator.comparingInt(this::pendingStatusRank) + .thenComparing(AppRequest::getCreatedDate, Comparator.nullsLast(Comparator.reverseOrder()))); + return appRequests; } + private int pendingStatusRank(AppRequest request) { + if (request.getApproval() == null) { + return 3; + } + if (request.getApproval().getApprovalStatus() instanceof ProcessingState) { + return 1; + } + if (request.getApproval().getApprovalStatus() instanceof RequestedState) { + return 2; + } + return 3; + } + public ClientDTO getApiKey(String orgid, String clientId) { return credentialRepository.findByClientidAndOrgid(clientId, orgid).map(credentialMapper::toVo).orElse(null); } @@ -133,8 +161,121 @@ public class AppServiceFacade { approvalService.beginApproval(approvalId); } + /** + * API 이용 해지(DELETE) 결재 신청을 생성하고 결재를 개시합니다. + * GW 차단/삭제와 PTL_CREDENTIAL 삭제는 여기서 하지 않으며, + * eapim-admin 승인 시점에 PortalAppApprovalListener 가 수행합니다. + * + * @throws IllegalStateException 중복 신청, 변경 신청 진행 중, 승인라인 미등록 등 사용자에게 안내할 상황 + */ + public void createDeleteRequest(String clientId, String reason, PortalOrg portalOrg) { + // 1. 진행 중(REQUESTED/PROCESSING)인 해지·변경 신청 중복 가드 + String blockReason = resolveDeleteBlockReason(clientId); + if (blockReason != null) { + throw new IllegalStateException(blockReason); + } + + // 2. DELETE 신청 생성 (createAppRequest 의 DELETE 분기가 clientName/prevApiList/apiList 를 채운다) + AppRequestDTO dto = new AppRequestDTO(); + dto.setType(AppRequestType.DELETE); + dto.setClientId(clientId); + dto.setReason(reason); + dto.setOrg(portalOrgMapper.toVo(portalOrg)); + + AppRequestDTO saved = createAppRequest(dto); + + // 3. 승인라인 미등록이면 approval 이 null — 결재 없는 해지 신청은 만들지 않는다(트랜잭션 롤백) + if (saved.getApproval() == null || saved.getApproval().getId() == null) { + throw new IllegalStateException("APP 승인라인이 등록되어 있지 않아 해지를 신청할 수 없습니다. 관리자에게 문의해 주세요."); + } + + beginApproval(saved.getApproval().getId()); + } + + /** + * 해지 신청을 막는 사유가 있으면 안내 메시지를, 없으면 {@code null} 을 반환합니다. + * + *

미완료 해지·변경 신청이 있으면 해지를 받을 수 없다. {@link #createDeleteRequest} 의 최종 가드와 + * 화면/사전검사가 같은 판정({@link #findOpenRequestEntity})을 쓰도록 이 메서드 하나로 모은다. + * (사전검사가 없으면 사용자가 step-up 2FA 를 통과한 뒤에야 차단 사실을 알게 되고, + * 재시도할 때마다 2FA 가 반복된다.)

+ */ + public String resolveDeleteBlockReason(String clientId) { + AppRequest open = findOpenRequestEntity(clientId); + if (open == null) { + return null; + } + if (AppRequestType.DELETE.equals(open.getType())) { + return "이미 해지 신청이 진행 중입니다. 결재 완료 후 다시 확인해 주세요."; + } + return "해당 인증키의 변경 신청이 진행 중이라 해지를 신청할 수 없습니다. 변경 결재 완료 또는 취소 후 다시 시도해 주세요."; + } + + /** + * 해당 인증키에 걸려 있는 미완료 변경/해지 신청 1건. 없으면 {@code null}. + * 상세 화면에서 진행 상태를 표시하고 신청을 취소할 수 있게 하는 데 사용한다. + */ + public AppRequestDTO findOpenRequest(String clientId) { + AppRequest open = findOpenRequestEntity(clientId); + return open == null ? null : appRequestMapper.toVo(open); + } + + /** + * 미완료(=결재가 끝나지 않은) 변경/해지 신청 1건. + * + *

결재가 개시되지 않아 {@code approval} 이 없는 신청도 포함한다. 이런 건은 관리자 결재함에 + * 뜨지 않아 사용자가 상태를 알 수도, 정리할 수도 없는 채로 남아 이후 신청을 계속 막는다. + * 화면에 노출해 취소할 수 있게 하려면 여기서 잡아야 한다.

+ */ + private AppRequest findOpenRequestEntity(String clientId) { + List related = appRequestRepository.findAllByClientIdsContainsAndTypeIsIn( + clientId, Arrays.asList(AppRequestType.MODIFY, AppRequestType.DELETE)); + for (AppRequest r : related) { + if (r.getApproval() == null) { + return r; + } + Object status = r.getApproval().getApprovalStatus(); + if (status instanceof CreatedState + || status instanceof RequestedState + || status instanceof ProcessingState) { + return r; + } + } + return null; + } + + /** + * 해당 클라이언트의 해지 신청이 결재 진행 중(REQUESTED/PROCESSING)인지 확인합니다. + * 상세 화면의 해지 버튼 비활성화에 사용됩니다. + */ + public boolean hasPendingDeleteRequest(String clientId) { + return appRequestRepository.findAllByClientIdsContainsAndTypeIsIn( + clientId, Arrays.asList(AppRequestType.DELETE)).stream() + .anyMatch(r -> r.getApproval() != null + && (r.getApproval().getApprovalStatus() instanceof RequestedState + || r.getApproval().getApprovalStatus() instanceof ProcessingState)); + } + public void cancelApiRequest(String id, PortalOrg portalOrg) { - appRequestRepository.findByIdAndOrg(id, portalOrg).ifPresent(approvalService::cancelAppApproval); + appRequestRepository.findByIdAndOrg(id, portalOrg).ifPresent(request -> { + if (request.getApproval() == null) { + // 승인정보 없는 신청은 결재 워크플로우가 없으므로 즉시 삭제. + // 단, GW에 클라이언트가 존재할 수 있으므로 차단(appstatus=0)+리로드를 먼저 수행하고 + // 실패 시 삭제를 중단한다. (/api_key_delete 와 동일한 순서) + // DELETE(해지) 신청은 살아있는 클라이언트가 대상이므로 취소 시 GW 를 건드리면 안 된다. + if (StringUtils.isNotBlank(request.getClientId()) + && !AppRequestType.DELETE.equals(request.getType())) { + try { + adminGatewayClient.blockClient(request.getClientId()); + } catch (Exception e) { + throw new IllegalStateException("게이트웨이 차단 처리에 실패하여 삭제를 중단했습니다. 잠시 후 다시 시도해 주세요.", e); + } + } + appRequestRepository.delete(request); + } else { + approvalService.cancelAppApproval(request); + } + }); } @@ -164,9 +305,15 @@ public class AppServiceFacade { Map mainIconsMap = apiServiceHelper.getMainIconsFromServiceDtos(apiServices); for (String apiId : apiList) { - ApiServiceDTO serviceDTO = mainIconsMap.get(apiId); + // 신청 이후 API 스펙/그룹이 삭제된 경우 null 가능 ApiSpecInfoDto spec = apiService.selectDetail(apiId); - spec.setService(serviceDTO.getGroupName()); + if (spec == null) { + continue; + } + ApiServiceDTO serviceDTO = mainIconsMap.get(apiId); + if (serviceDTO != null) { + spec.setService(serviceDTO.getGroupName()); + } appRequest.getApiSpecList().add(spec); } } @@ -294,17 +441,26 @@ public class AppServiceFacade { } /** - * API Key(Credential)를 즉시 삭제합니다. - * 승인 프로세스 없이 바로 삭제 처리됩니다. + * Client Secret을 1회 조회하고 그 즉시 DB(PTL_CREDENTIAL)에서 물리 삭제합니다. + * 보안 정책상 비밀정보는 최초 1회만 제공되며, 조회 후에는 복구할 수 없습니다. * - * @param orgId 조직 ID - * @param clientId 삭제할 클라이언트 ID + * @param orgId 조직 ID (소유권 확인용) + * @param clientId 대상 클라이언트 ID + * @return 삭제 전 Client Secret 값. 이미 노출되어 비어있으면 {@code null} * @throws NotFoundException 클라이언트를 찾을 수 없는 경우 */ - public void deleteApp(String orgId, String clientId) { + public String revealAndDeleteClientSecret(String orgId, String clientId) { Credential credential = credentialRepository.findByClientidAndOrgid(clientId, orgId) .orElseThrow(() -> new NotFoundException("Client not found: " + clientId)); - credentialRepository.delete(credential); + String secret = credential.getClientsecret(); + if (StringUtils.isBlank(secret)) { + return null; // 이미 1회 노출되어 삭제됨 + } + + credential.setClientsecret(null); // 물리 삭제 (1회 제공) + credentialRepository.save(credential); + return secret; } + } diff --git a/src/main/java/com/eactive/apim/portal/apps/approval/dto/ApprovalDTO.java b/src/main/java/com/eactive/apim/portal/apps/approval/dto/ApprovalDTO.java index 154503d..ea46b56 100644 --- a/src/main/java/com/eactive/apim/portal/apps/approval/dto/ApprovalDTO.java +++ b/src/main/java/com/eactive/apim/portal/apps/approval/dto/ApprovalDTO.java @@ -31,4 +31,21 @@ public class ApprovalDTO { private LocalDateTime createdDate; private LocalDateTime approvalDate; + + private String expectEndDate; //예상완료일 (yyyyMMdd) + + /** 예상완료일(yyyyMMdd)의 한글 요일. 값이 없거나 형식이 맞지 않으면 빈 문자열. (예: "토") */ + public String getExpectEndDateDayOfWeek() { + if (expectEndDate == null || expectEndDate.length() < 8) { + return ""; + } + try { + return java.time.LocalDate + .parse(expectEndDate.substring(0, 8), java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd")) + .getDayOfWeek() + .getDisplayName(java.time.format.TextStyle.SHORT, java.util.Locale.KOREAN); + } catch (Exception e) { + return ""; + } + } } diff --git a/src/main/java/com/eactive/apim/portal/apps/auth/AuthNoticeProperties.java b/src/main/java/com/eactive/apim/portal/apps/auth/AuthNoticeProperties.java new file mode 100644 index 0000000..44d2c26 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/auth/AuthNoticeProperties.java @@ -0,0 +1,43 @@ +package com.eactive.apim.portal.apps.auth; + +import com.eactive.apim.portal.portalproperty.service.PortalPropertyService; +import lombok.RequiredArgsConstructor; +import org.springframework.core.env.Environment; +import org.springframework.core.env.Profiles; +import org.springframework.stereotype.Component; + +/** + * 인증(이메일/SMS) 테스트 안내 관련 PTL_PROPERTY 접근 래퍼. + * + *

그룹 {@code Portal}, 키 {@code auth.test-notice.enabled}(true/false). 값이 참이면 인증 요청 + * 응답에 인증번호를 실어 화면에 노출한다(실제 발송 대신 테스트 확인 용도). 기존 application.yml + * {@code portal.test-auth-notice-enabled} 설정을 DB PTL_PROPERTY 로 이전한 것으로, + * {@link TwoFactorProperties} 와 동일한 {@code getOrCreateProperty} 패턴을 따른다.

+ * + *

prod 프로파일에서는 DB 값과 무관하게 항상 false 를 반환한다(운영 환경 인증번호 노출 금지). + * 세션 keepalive 등 다른 비운영 전용 스위치와 동일한 정책이다.

+ */ +@Component +@RequiredArgsConstructor +public class AuthNoticeProperties { + + public static final String GROUP = "Portal"; + public static final String KEY_TEST_NOTICE_ENABLED = "auth.test-notice.enabled"; + + private final PortalPropertyService portalPropertyService; + private final Environment environment; + + /** + * 인증 요청 응답에 인증번호를 실어 UI 에 노출할지 여부(개발/테스트 전용). + * prod 환경에서는 property 값과 무관하게 항상 false. + */ + public boolean isTestNoticeEnabled() { + if (environment.acceptsProfiles(Profiles.of("prod"))) { + return false; + } + String value = portalPropertyService.getOrCreateProperty( + GROUP, KEY_TEST_NOTICE_ENABLED, "true", + "인증(이메일/SMS) 요청 시 인증번호를 화면에 표시할지 여부 (true/false, 테스트 전용)"); + return value != null && "true".equalsIgnoreCase(value.trim()); + } +} diff --git a/src/main/java/com/eactive/apim/portal/apps/auth/service/AuthNumberService.java b/src/main/java/com/eactive/apim/portal/apps/auth/service/AuthNumberService.java index fc1ac66..b747af2 100644 --- a/src/main/java/com/eactive/apim/portal/apps/auth/service/AuthNumberService.java +++ b/src/main/java/com/eactive/apim/portal/apps/auth/service/AuthNumberService.java @@ -4,5 +4,23 @@ public interface AuthNumberService { String sendRequestAuthNumber(String recipientKey, String msgType); + /** + * 기본 TTL 로 발송하되 수신자 이름을 지정한다. 세 번째 인자가 int 인 오버로드(TTL 지정)와 혼동하지 말 것. + */ + String sendRequestAuthNumber(String recipientKey, String msgType, String username); + + /** + * 인증번호를 지정한 유효시간(초)으로 발송한다. 로그인/step-up 2FA 는 회원가입 기본 TTL 과 + * 다른 값을 쓸 수 있으므로 호출부에서 TTL 을 지정한다. + */ + String sendRequestAuthNumber(String recipientKey, String msgType, int ttlSeconds); + + /** + * 수신자 이름을 지정해 인증번호를 발송한다. 메시지 템플릿의 %USER_NAME% 치환에 사용되며, + * 회원가입·아이디/비밀번호 찾기처럼 사용자 이름을 알 수 없는 흐름은 "guest" 를 넘긴다. + * username 이 비어 있으면 %USER_NAME% 은 치환되지 않고 원문이 그대로 남는다. + */ + String sendRequestAuthNumber(String recipientKey, String msgType, int ttlSeconds, String username); + boolean verifyAuthNumber(String recipientKey, String authNumber); } diff --git a/src/main/java/com/eactive/apim/portal/apps/auth/service/AuthNumberServiceImpl.java b/src/main/java/com/eactive/apim/portal/apps/auth/service/AuthNumberServiceImpl.java index 3510e60..b140f76 100644 --- a/src/main/java/com/eactive/apim/portal/apps/auth/service/AuthNumberServiceImpl.java +++ b/src/main/java/com/eactive/apim/portal/apps/auth/service/AuthNumberServiceImpl.java @@ -45,17 +45,35 @@ public class AuthNumberServiceImpl implements AuthNumberService { @Override @Transactional(noRollbackFor = AuthNumberException.class) public String sendRequestAuthNumber(String recipientKey, String msgType) { - logger.info("Sending auth number to: {} via {}", recipientKey, msgType); + return sendRequestAuthNumber(recipientKey, msgType, authNumberExpirationTime, null); + } + + @Override + @Transactional(noRollbackFor = AuthNumberException.class) + public String sendRequestAuthNumber(String recipientKey, String msgType, String username) { + return sendRequestAuthNumber(recipientKey, msgType, authNumberExpirationTime, username); + } + + @Override + @Transactional(noRollbackFor = AuthNumberException.class) + public String sendRequestAuthNumber(String recipientKey, String msgType, int ttlSeconds) { + return sendRequestAuthNumber(recipientKey, msgType, ttlSeconds, null); + } + + @Override + @Transactional(noRollbackFor = AuthNumberException.class) + public String sendRequestAuthNumber(String recipientKey, String msgType, int ttlSeconds, String username) { + logger.info("Sending auth number to: {} via {} (ttl={}s)", recipientKey, msgType, ttlSeconds); validateResendTime(recipientKey); String authNumber = generator.generateAuthNumber(); - MessageRecipient recipient = createMessageRecipient(recipientKey, msgType); + MessageRecipient recipient = createMessageRecipient(recipientKey, msgType, username); messageSender.sendAuthMessage(recipient, authNumber, msgType); storage.saveAuthNumber(recipientKey, authNumber, - LocalDateTime.now().plusSeconds(authNumberExpirationTime)); + LocalDateTime.now().plusSeconds(ttlSeconds)); return authNumber; } @@ -66,17 +84,20 @@ public class AuthNumberServiceImpl implements AuthNumberService { logger.info("Verifying auth number for: {}", recipientKey); TwoFactorAuth storedAuth = storage.getAuthNumber(recipientKey) - .orElseThrow(() -> new AuthNumberException("인증번호가 존재하지 않습니다. 인증번호를 다시 발송해주세요.")); + .orElseThrow(() -> new AuthNumberException("인증번호가 존재하지 않습니다. 인증번호를 다시 발송해주세요.", + AuthNumberException.Reason.NOT_FOUND)); if (storedAuth.getExpiresAt().isBefore(LocalDateTime.now())) { storage.deleteAuthNumber(recipientKey); - throw new AuthNumberException("입력 시간이 초과되었습니다. 인증번호를 다시 발송해주세요."); + throw new AuthNumberException("입력 시간이 초과되었습니다. 인증번호를 다시 발송해주세요.", + AuthNumberException.Reason.EXPIRED); } if (authNumber.equals(storedAuth.getAuthNumber())) { return true; } else { - throw new AuthNumberException("입력된 인증번호가 올바르지 않습니다."); + throw new AuthNumberException("입력된 인증번호가 올바르지 않습니다.", + AuthNumberException.Reason.MISMATCH); } } @@ -90,9 +111,13 @@ public class AuthNumberServiceImpl implements AuthNumberService { }); } - private MessageRecipient createMessageRecipient(String recipientKey, String msgType) { + private MessageRecipient createMessageRecipient(String recipientKey, String msgType, String username) { MessageRecipient recipient = new MessageRecipient(); recipient.setUserId(recipientKey); + // 메시지 템플릿 %USER_NAME% 치환용. 비어 있으면 MessageSendService 가 파라미터 자체를 넣지 않는다. + if (username != null && !username.trim().isEmpty()) { + recipient.setUsername(username); + } if ("SMS".equalsIgnoreCase(msgType)) { recipient.setPhone(recipientKey); } else if ("EMAIL".equalsIgnoreCase(msgType)) { diff --git a/src/main/java/com/eactive/apim/portal/apps/auth/service/AuthNumberStorage.java b/src/main/java/com/eactive/apim/portal/apps/auth/service/AuthNumberStorage.java index 0e77bd5..765e18c 100644 --- a/src/main/java/com/eactive/apim/portal/apps/auth/service/AuthNumberStorage.java +++ b/src/main/java/com/eactive/apim/portal/apps/auth/service/AuthNumberStorage.java @@ -3,31 +3,72 @@ package com.eactive.apim.portal.apps.auth.service; import com.eactive.apim.portal.portaluser.entity.TwoFactorAuth; import com.eactive.apim.portal.portaluser.repository.TwoFactorAuthRepository; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; import java.time.LocalDateTime; import java.util.Optional; @Component public class AuthNumberStorage { + + private static final String HASH_ALGORITHM = "HmacSHA256"; + private final TwoFactorAuthRepository twoFactorAuthRepository; + /** + * recipient(이메일/휴대폰)는 암호화 컬럼이라 값으로 동등 조회/중복정리가 불가능하다. + * 그래서 recipient 평문을 결정적 해시(HMAC-SHA256)로 변환해 별도 조회 키(recipientKey)로 사용한다. + * 평문을 그대로 노출하지 않도록 pepper 로 기존 암호화 키를 재사용한다. + */ + @Value("${encryption.key:kjbank_portal_application_1357902}") + private String hashPepper; + @Autowired public AuthNumberStorage(TwoFactorAuthRepository twoFactorAuthRepository) { this.twoFactorAuthRepository = twoFactorAuthRepository; } public void saveAuthNumber(String recipientKey, String authNumber, LocalDateTime expiresAt) { - twoFactorAuthRepository.deleteAllByRecipient(recipientKey); + String lookupKey = hashRecipient(recipientKey); + twoFactorAuthRepository.deleteAllByRecipientKey(lookupKey); TwoFactorAuth auth = new TwoFactorAuth(recipientKey, authNumber, expiresAt); + auth.setRecipientKey(lookupKey); twoFactorAuthRepository.save(auth); } public Optional getAuthNumber(String recipientKey) { - return twoFactorAuthRepository.findByRecipient(recipientKey); + return twoFactorAuthRepository.findByRecipientKey(hashRecipient(recipientKey)); } public void deleteAuthNumber(String recipientKey) { - twoFactorAuthRepository.deleteById(recipientKey); + twoFactorAuthRepository.deleteAllByRecipientKey(hashRecipient(recipientKey)); } -} \ No newline at end of file + + /** + * recipient 평문을 결정적 해시(HMAC-SHA256, 소문자 hex)로 변환한다. + * 동일 입력 → 동일 키이므로 저장/조회/정리에서 동등 매칭이 가능하다. + */ + private String hashRecipient(String recipient) { + if (recipient == null) { + return null; + } + try { + Mac mac = Mac.getInstance(HASH_ALGORITHM); + mac.init(new SecretKeySpec(hashPepper.getBytes(StandardCharsets.UTF_8), HASH_ALGORITHM)); + byte[] digest = mac.doFinal(recipient.getBytes(StandardCharsets.UTF_8)); + StringBuilder sb = new StringBuilder(digest.length * 2); + for (byte b : digest) { + sb.append(Character.forDigit((b >> 4) & 0xF, 16)); + sb.append(Character.forDigit(b & 0xF, 16)); + } + return sb.toString(); + } catch (GeneralSecurityException e) { + throw new IllegalStateException("2FA recipient 해시 생성 실패", e); + } + } +} diff --git a/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/StepUpAuthInterceptor.java b/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/StepUpAuthInterceptor.java new file mode 100644 index 0000000..31aeaa2 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/StepUpAuthInterceptor.java @@ -0,0 +1,80 @@ +package com.eactive.apim.portal.apps.auth.twofactor; + +import org.springframework.web.servlet.HandlerInterceptor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; + +/** + * step-up 2FA(민감기능 추가 인증) 진입 가드. + * + *

{@link StepUpProtectedPaths#isInterceptorGuarded(String)} 경로 진입 시, 유효한 1회용 + * 통과권이 없으면 2FA 를 요구한다. + *

    + *
  • GET(페이지 진입) → {@code /auth/2fa/challenge} 로 리다이렉트(원경로는 returnUrl 로 보존)
  • + *
  • POST(AJAX: Secret 조회/앱 해지) → {@code 401 + {"stepUpRequired":true}} JSON
  • + *
+ * "매번 인증" 정책이므로 통과권은 {@code consumeStepUpPass} 에서 즉시 소멸한다.

+ * + *

내 정보 변경({@code /mypage}, PASSWORD 레벨)과 비밀번호 반영({@code POST /password/change}, + * 반영 직전 2FA)은 각 컨트롤러가 직접 관장하므로 이 인터셉터 대상이 아니다.

+ */ +public class StepUpAuthInterceptor implements HandlerInterceptor { + + private final TwoFactorService twoFactorService; + private final TwoFactorProperties twoFactorProperties; + + public StepUpAuthInterceptor(TwoFactorService twoFactorService, TwoFactorProperties twoFactorProperties) { + this.twoFactorService = twoFactorService; + this.twoFactorProperties = twoFactorProperties; + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + if (!twoFactorProperties.isStepUpEnabled()) { + return true; + } + + String path = request.getServletPath(); + if (!StepUpProtectedPaths.isInterceptorGuarded(path)) { + return true; + } + + // 지점별 스위치가 꺼져 있으면 해당 경로는 step-up 미적용 + if (!twoFactorProperties.isStepUpPointEnabled(path)) { + return true; + } + + HttpSession session = request.getSession(false); + if (session == null) { + // 세션(=인증)이 없으면 여기서 다루지 않고 보안 계층(@Secured)에 맡긴다. + return true; + } + + // 1회용 통과권 소비 시도 (매번 인증: 있으면 소멸 후 통과) + if (twoFactorService.consumeStepUpPass(session, path)) { + return true; + } + + if ("POST".equalsIgnoreCase(request.getMethod())) { + // AJAX 지점(Secret 조회/앱 해지) → 프론트가 팝업을 띄우도록 신호 + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.setContentType("application/json;charset=UTF-8"); + response.getWriter().write("{\"stepUpRequired\":true}"); + return false; + } + + // GET 페이지 진입 → 챌린지 페이지로 유도(원경로+쿼리 보존) + String returnUrl = path; + String query = request.getQueryString(); + if (query != null && !query.isEmpty()) { + returnUrl = returnUrl + "?" + query; + } + String encoded = URLEncoder.encode(returnUrl, StandardCharsets.UTF_8.name()); + response.sendRedirect(request.getContextPath() + "/auth/2fa/challenge?returnUrl=" + encoded); + return false; + } +} diff --git a/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/StepUpPasswordController.java b/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/StepUpPasswordController.java new file mode 100644 index 0000000..26b129b --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/StepUpPasswordController.java @@ -0,0 +1,73 @@ +package com.eactive.apim.portal.apps.auth.twofactor; + +import com.eactive.apim.portal.apps.user.facade.UserFacade; +import com.eactive.apim.portal.common.util.SecurityUtil; +import lombok.RequiredArgsConstructor; +import org.springframework.security.access.annotation.Secured; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; + +import javax.servlet.http.HttpSession; + +/** + * 완화된 step-up(PASSWORD 레벨) 확인 페이지. + * + *

{@link StepUpProtectedPaths#isPasswordGated(String)} 경로(예: {@code /mypage})는 2FA 대신 + * 현재 비밀번호 재확인만 요구한다. 각 컨트롤러가 통과권이 없을 때 이 페이지로 유도하고, + * 확인 성공 시 해당 경로의 통과권을 발급한 뒤 원경로로 복귀시킨다.

+ * + *

returnUrl 은 PASSWORD 레벨 화이트리스트로만 검증·복귀하여 open redirect 를 막는다.

+ */ +@Controller +@Secured("ROLE_ACCOUNT") +@RequiredArgsConstructor +@RequestMapping("/auth/stepup") +public class StepUpPasswordController { + + private final UserFacade userFacade; + private final TwoFactorService twoFactorService; + + @GetMapping("/password") + public String page(@RequestParam(required = false) String returnUrl, Model model) { + String path = pathOf(returnUrl); + if (!StepUpProtectedPaths.isPasswordGated(path)) { + return "redirect:/"; + } + model.addAttribute("returnUrl", path); + return "apps/auth/stepupPassword"; + } + + @PostMapping("/password") + public String verify(@RequestParam String currentPassword, + @RequestParam(required = false) String returnUrl, + HttpSession session, Model model) { + String path = pathOf(returnUrl); + if (!StepUpProtectedPaths.isPasswordGated(path)) { + return "redirect:/"; + } + + String loginId = SecurityUtil.getCurrentLoginId(); + if (userFacade.verifyCurrentPassword(loginId, currentPassword)) { + // 확인 성공 → 해당 경로 통과권 발급 후 원경로(화이트리스트 경로)로만 복귀 + twoFactorService.grantStepUpPass(session, path); + return "redirect:" + path; + } + + model.addAttribute("error", "현재 비밀번호가 일치하지 않습니다."); + model.addAttribute("returnUrl", path); + return "apps/auth/stepupPassword"; + } + + /** 쿼리스트링을 제외한 경로 부분만 추출(화이트리스트 검증용, open redirect 방지) */ + private static String pathOf(String url) { + if (url == null) { + return null; + } + int q = url.indexOf('?'); + return q >= 0 ? url.substring(0, q) : url; + } +} diff --git a/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/StepUpProtectedPaths.java b/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/StepUpProtectedPaths.java new file mode 100644 index 0000000..4919ff0 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/StepUpProtectedPaths.java @@ -0,0 +1,126 @@ +package com.eactive.apim.portal.apps.auth.twofactor; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +/** + * step-up(민감기능 추가 인증) 대상 서블릿 경로 화이트리스트 + 지점별 프로퍼티 키 + 검증 레벨. + * + *

검증 레벨(완화 정책)

+ *
    + *
  • {@link Level#TWO_FACTOR} — 공통 2FA 팝업(휴대폰/이메일 인증번호). 진입 인터셉터 또는 + * AJAX 401 신호로 유도. 예: Secret 조회/앱 해지. 앱 정보수정은 최종 반영(commit) + * 직전에 컨트롤러가 통과권을 요구한다(다단계 진행 중 중복 인증 방지).
  • + *
  • {@link Level#PASSWORD} — 현재 비밀번호 재확인만 요구(2FA 없음). 별도 확인 페이지 + * ({@code /auth/stepup/password})로 유도. 예: 내 정보 변경({@code /mypage}).
  • + *
+ * + *

세 가지 관심사를 분리한다.

+ *
    + *
  1. {@link #isTwoFactorPurpose(String)} — 공통 2FA 팝업의 유효 대상(purpose) 화이트리스트. + * open redirect / 임의 purpose 로의 2FA 발송·통과권 발급을 막는 데 쓴다.
  2. + *
  3. {@link #isInterceptorGuarded(String)} — {@link StepUpAuthInterceptor} 가 진입 시점에 + * 자동 차단하는 경로. 비밀번호 변경(반영 직전 확인)·내 정보(별도 확인 페이지)는 + * 각 컨트롤러가 직접 관장하므로 여기서 제외한다.
  4. + *
  5. {@link #isPasswordGated(String)} — 현재 비밀번호 재확인으로 보호하는 경로.
  6. + *
+ * + *

지점별 활성화는 PTL_PROPERTY 키({@code two-factor.stepup.<지점>})로 개별 제어하며 + * 전체 스위치 {@code two-factor.stepup.enabled} 와 AND 로 동작한다.

+ */ +public final class StepUpProtectedPaths { + + /** step-up 검증 레벨(완화 정책) */ + public enum Level { + /** 공통 2FA 팝업(인증번호) */ + TWO_FACTOR, + /** 현재 비밀번호 재확인만 */ + PASSWORD + } + + /** Secret 키 조회 (AJAX POST) */ + public static final String REVEAL_SECRET = "/clients/credential/reveal-secret"; + /** 앱 해지 신청 (AJAX POST) */ + public static final String APP_KEY_DELETE = "/clients/api_key_delete"; + /** 앱 정보 수정 최종 반영(commit, POST /modify/step2) — 반영 직전 2FA. 진입/중간 단계는 가드하지 않음 */ + public static final String APP_MODIFY_COMMIT = "/clients/modify/step2"; + /** 개인정보 변경 페이지 진입 (GET, 정확 일치) — PASSWORD 레벨 */ + public static final String MYPAGE = "/mypage"; + /** 비밀번호 변경 반영(commit, POST) — 반영 직전 2FA. 진입(GET)은 가드하지 않음 */ + public static final String PASSWORD_CHANGE = "/password/change"; + /** 회원 탈퇴 반영(commit, POST) — 반영 직전 2FA. 팝업(사유 입력) 후 프론트가 2FA 를 띄운다 */ + public static final String WITHDRAW = "/withdraw"; + + /** PTL_PROPERTY 지점 키 접두 (전체 스위치 two-factor.stepup.enabled 와 구분) */ + private static final String KEY_PREFIX = "two-factor.stepup."; + + /** 경로 → 지점별 프로퍼티 키. 삽입 순서 유지(LinkedHashMap) */ + private static final Map PATH_TO_KEY; + /** 경로 → 검증 레벨 */ + private static final Map PATH_TO_LEVEL; + /** 인터셉터가 진입 시점에 자동 차단하는 경로(2FA) */ + private static final Set INTERCEPTOR_GUARDED; + + static { + Map keys = new LinkedHashMap<>(); + keys.put(REVEAL_SECRET, KEY_PREFIX + "reveal-secret"); + keys.put(APP_MODIFY_COMMIT, KEY_PREFIX + "app-modify"); + keys.put(APP_KEY_DELETE, KEY_PREFIX + "app-delete"); + keys.put(MYPAGE, KEY_PREFIX + "mypage"); + keys.put(PASSWORD_CHANGE, KEY_PREFIX + "password-change"); + keys.put(WITHDRAW, KEY_PREFIX + "withdraw"); + PATH_TO_KEY = Collections.unmodifiableMap(keys); + + Map levels = new LinkedHashMap<>(); + levels.put(REVEAL_SECRET, Level.TWO_FACTOR); + levels.put(APP_MODIFY_COMMIT, Level.TWO_FACTOR); + levels.put(APP_KEY_DELETE, Level.TWO_FACTOR); + levels.put(MYPAGE, Level.PASSWORD); + levels.put(PASSWORD_CHANGE, Level.TWO_FACTOR); + levels.put(WITHDRAW, Level.TWO_FACTOR); + PATH_TO_LEVEL = Collections.unmodifiableMap(levels); + + // 인터셉터 진입 자동 차단: 2FA 레벨 중 "진입 시점" 보호가 필요한 경로만. + // - PASSWORD_CHANGE 는 반영(POST commit) 직전에 컨트롤러가 통과권을 요구 → 제외 + // - APP_MODIFY_COMMIT 도 동일 — 다단계(step1→step2) 진행 중 중복 인증을 막기 위해 + // 최종 반영 직전에만 컨트롤러가 통과권을 요구 → 제외 + // - MYPAGE 는 별도 확인 페이지로 컨트롤러가 유도(PASSWORD 레벨) → 제외 + // - WITHDRAW 는 팝업(사유 입력)→2FA→제출 순서로 프론트가 유도하고 + // 컨트롤러가 커밋 직전 통과권을 요구 → 제외 + Set guarded = new java.util.LinkedHashSet<>(); + guarded.add(REVEAL_SECRET); + guarded.add(APP_KEY_DELETE); + INTERCEPTOR_GUARDED = Collections.unmodifiableSet(guarded); + } + + private StepUpProtectedPaths() { + } + + /** 공통 2FA 팝업의 유효 대상(purpose)인지 — open redirect / 임의 purpose 차단용 */ + public static boolean isTwoFactorPurpose(String servletPath) { + return servletPath != null + && PATH_TO_LEVEL.get(servletPath) == Level.TWO_FACTOR; + } + + /** 인터셉터가 진입 시점에 자동 차단하는 경로인지 */ + public static boolean isInterceptorGuarded(String servletPath) { + return servletPath != null && INTERCEPTOR_GUARDED.contains(servletPath); + } + + /** 현재 비밀번호 재확인으로 보호하는 경로인지(PASSWORD 레벨) */ + public static boolean isPasswordGated(String servletPath) { + return servletPath != null && PATH_TO_LEVEL.get(servletPath) == Level.PASSWORD; + } + + /** 해당 경로의 지점별 활성화 프로퍼티 키. 대상 경로가 아니면 null */ + public static String propertyKeyOf(String servletPath) { + return servletPath == null ? null : PATH_TO_KEY.get(servletPath); + } + + /** 지점별 프로퍼티 키 접두 */ + public static String keyPrefix() { + return KEY_PREFIX; + } +} diff --git a/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/TwoFactorCleanupScheduler.java b/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/TwoFactorCleanupScheduler.java new file mode 100644 index 0000000..d232aa7 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/TwoFactorCleanupScheduler.java @@ -0,0 +1,43 @@ +package com.eactive.apim.portal.apps.auth.twofactor; + +import com.eactive.apim.portal.portaluser.repository.TwoFactorAuthRepository; +import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; + +/** + * 만료된 2FA 인증번호(PTL_TWO_FACTOR_AUTH) 정리 스케줄러. + * + *

검증은 접근 시점 lazy 만료 검사만 하므로, 발송 후 검증 없이 방치된 레코드가 남는다. + * 1분 주기로 만료분을 일괄 삭제한다.

+ * + *

다중화(스케일아웃) 안전성: 작업이 "만료된 행만" 지우는 멱등 delete 라 + * 여러 인스턴스가 동시에 실행해도 결과가 동일하고 부작용이 없다. 따라서 분산 락 + * (ShedLock 등)이 필요 없다. 동일 행을 둘이 지우려 하면 한쪽이 0건 삭제로 끝날 뿐이다.

+ */ +@Component +@RequiredArgsConstructor +public class TwoFactorCleanupScheduler { + + private static final Logger log = LoggerFactory.getLogger(TwoFactorCleanupScheduler.class); + + private final TwoFactorAuthRepository twoFactorAuthRepository; + + @Scheduled(fixedRate = 60000) + @Transactional + public void cleanupExpired() { + try { + int deleted = twoFactorAuthRepository.deleteAllByExpiresAtBefore(LocalDateTime.now()); + if (deleted > 0) { + log.debug("만료된 2FA 인증번호 {}건 정리", deleted); + } + } catch (Exception e) { + log.warn("2FA 인증번호 정리 실패", e); + } + } +} diff --git a/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/TwoFactorContext.java b/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/TwoFactorContext.java new file mode 100644 index 0000000..f5d381a --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/TwoFactorContext.java @@ -0,0 +1,102 @@ +package com.eactive.apim.portal.apps.auth.twofactor; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 진행 중인 2FA 절차 상태. HTTP 세션에 단일 소스로 보관한다. + * + *

세션 클러스터링(stage/prod Redis/Ehcache) 대상이므로 {@link Serializable} 이다. + * 여러 탭/페이지에서 동시에 2FA 가 발동되지 않도록, 발송 시 이 컨텍스트 존재 여부로 + * "진행 중" 을 판정하고 confirm 후 강제 종료(force)로만 새 절차를 시작한다.

+ */ +public class TwoFactorContext implements Serializable { + + private static final long serialVersionUID = 1L; + + public enum Mode { + /** 로그인 1차 인증 통과 후 대기(pending) 상태의 2FA */ + LOGIN, + /** 로그인 이후 민감기능 접근 시 추가 인증(step-up) */ + STEPUP + } + + private Mode mode; + /** 발송 채널 (EMAIL | SMS) */ + private String channel; + /** AuthNumberService 에 전달한 실제 수신처 문자열(이메일 소문자 / 휴대폰 digits). 검증 시 동일 값 사용 */ + private String recipient; + /** step-up 대상 보호 경로(purpose). LOGIN 모드에서는 null */ + private String purpose; + /** 발송 시각 */ + private LocalDateTime startedAt; + /** 유효시간(초) */ + private int ttlSeconds; + /** 검증 시도 횟수 */ + private int attempts; + + public Mode getMode() { + return mode; + } + + public void setMode(Mode mode) { + this.mode = mode; + } + + public String getChannel() { + return channel; + } + + public void setChannel(String channel) { + this.channel = channel; + } + + public String getRecipient() { + return recipient; + } + + public void setRecipient(String recipient) { + this.recipient = recipient; + } + + public String getPurpose() { + return purpose; + } + + public void setPurpose(String purpose) { + this.purpose = purpose; + } + + public LocalDateTime getStartedAt() { + return startedAt; + } + + public void setStartedAt(LocalDateTime startedAt) { + this.startedAt = startedAt; + } + + public int getTtlSeconds() { + return ttlSeconds; + } + + public void setTtlSeconds(int ttlSeconds) { + this.ttlSeconds = ttlSeconds; + } + + public int getAttempts() { + return attempts; + } + + public void setAttempts(int attempts) { + this.attempts = attempts; + } + + public int incrementAttempts() { + return ++this.attempts; + } + + /** startedAt + ttl 기준 만료 여부(세션 컨텍스트 lazy 만료 판정용) */ + public boolean isExpired(LocalDateTime now) { + return startedAt == null || startedAt.plusSeconds(ttlSeconds).isBefore(now); + } +} diff --git a/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/TwoFactorController.java b/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/TwoFactorController.java new file mode 100644 index 0000000..46f9391 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/TwoFactorController.java @@ -0,0 +1,90 @@ +package com.eactive.apim.portal.apps.auth.twofactor; + +import com.eactive.apim.portal.apps.auth.twofactor.dto.TwoFactorInfoResponse; +import com.eactive.apim.portal.apps.auth.twofactor.dto.TwoFactorSendResponse; +import com.eactive.apim.portal.apps.auth.twofactor.dto.TwoFactorVerifyResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; + +/** + * 공통 2FA 팝업 백엔드. 로그인 pending·step-up 을 모두 처리한다. + * + *

수신처는 서버가 세션 대상 사용자로부터 결정하므로 클라이언트는 채널만 전달한다. + * 모든 POST 는 세션 기반 CSRF(X-XSRF-TOKEN) 보호를 받는다.

+ */ +@Controller +@RequestMapping("/auth/2fa") +@RequiredArgsConstructor +public class TwoFactorController { + + private final TwoFactorService twoFactorService; + + /** 팝업 초기 정보(채널·TTL·진행중 여부) */ + @GetMapping("/info") + @ResponseBody + public TwoFactorInfoResponse info(@RequestParam(required = false) String purpose, HttpSession session) { + return twoFactorService.getInfo(session, purpose); + } + + /** 인증번호 발송 */ + @PostMapping("/send") + @ResponseBody + public TwoFactorSendResponse send(@RequestParam String channel, + @RequestParam(required = false) String purpose, + @RequestParam(required = false, defaultValue = "false") boolean force, + HttpServletRequest request, + HttpSession session) { + return twoFactorService.send(request, session, channel, purpose, force); + } + + /** 인증번호 검증 */ + @PostMapping("/verify") + @ResponseBody + public TwoFactorVerifyResponse verify(@RequestParam String code, + HttpServletRequest request, + HttpSession session) { + return twoFactorService.verify(request, session, code); + } + + /** 팝업 닫기/타이머 만료 → 2차 인증 실패 처리 */ + @PostMapping("/cancel") + @ResponseBody + public void cancel(@RequestParam(required = false, defaultValue = "CANCELLED") String reason, + HttpServletRequest request, + HttpSession session) { + twoFactorService.cancel(request, session, reason); + } + + /** + * step-up GET 진입 지점용 챌린지 페이지. 인터셉터가 리다이렉트하며, 화면이 공통 팝업을 자동 오픈한다. + * returnUrl 은 보호 경로 화이트리스트로 검증(open redirect 방지)한다. + */ + @GetMapping("/challenge") + public String challenge(@RequestParam(required = false) String returnUrl, Model model) { + // returnUrl 은 쿼리스트링을 포함할 수 있으므로 경로 부분만 화이트리스트로 검증(open redirect 방지) + String purpose = pathOf(returnUrl); + if (!StepUpProtectedPaths.isTwoFactorPurpose(purpose)) { + return "redirect:/"; + } + model.addAttribute("returnUrl", returnUrl); + model.addAttribute("purpose", purpose); + return "apps/auth/twoFactorChallenge"; + } + + private static String pathOf(String url) { + if (url == null) { + return null; + } + int q = url.indexOf('?'); + return q >= 0 ? url.substring(0, q) : url; + } +} diff --git a/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/TwoFactorProperties.java b/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/TwoFactorProperties.java new file mode 100644 index 0000000..abe8ea9 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/TwoFactorProperties.java @@ -0,0 +1,119 @@ +package com.eactive.apim.portal.apps.auth.twofactor; + +import com.eactive.apim.portal.portalproperty.service.PortalPropertyService; +import com.eactive.apim.portal.portaluser.entity.PortalUserEnums.RoleCode; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +/** + * 2FA 관련 PTL_PROPERTY 접근 래퍼. + * + *

그룹 {@code Portal}, 점 구분 소문자 키 관례({@code session.timeout.minutes}, + * {@code org.hard-delete.enabled} 등)를 따른다. + * {@link PortalPropertyService#getOrCreateProperty} 는 최초 접근 시 기본값으로 DB row 를 + * 생성(그룹 존재 시)하므로 별도 초기 데이터가 필요 없다. 캐시가 없어 매 호출 DB 조회지만 + * 2FA 진입 경로가 제한적이라 허용 범위다.

+ */ +@Component +@RequiredArgsConstructor +public class TwoFactorProperties { + + public static final String GROUP = "Portal"; + + public static final String KEY_LOGIN_ENABLED = "two-factor.login.enabled"; + public static final String KEY_LOGIN_TARGET_ROLES = "two-factor.login.target-roles"; + public static final String KEY_TTL_SECONDS = "two-factor.ttl.seconds"; + public static final String KEY_ATTEMPT_LIMIT = "two-factor.attempt.limit"; + public static final String KEY_TEST_NOTICE_ENABLED = "two-factor.test-notice.enabled"; + public static final String KEY_STEPUP_ENABLED = "two-factor.stepup.enabled"; + + private final PortalPropertyService portalPropertyService; + + /** 로그인 2FA 활성화 여부 */ + public boolean isLoginEnabled() { + return parseBool(resolve(KEY_LOGIN_ENABLED, "false", "로그인 2차 인증 활성화 여부 (true/false)")); + } + + /** + * 로그인 2FA 적용 대상 역할인지 여부. + * 프로퍼티 값: 쉼표 구분 RoleCode 목록(예: {@code ROLE_CORP_MANAGER,ROLE_CORP_USER}) + * 또는 {@code ALL}(전체 대상). 기본값은 법인관리자만. + * 미기재 역할은 로그인 2FA 를 건너뛴다(전체 스위치 {@link #isLoginEnabled()}와 AND 동작). + */ + public boolean isLoginTargetRole(RoleCode roleCode) { + if (roleCode == null) { + roleCode = RoleCode.ROLE_USER; + } + String value = resolve(KEY_LOGIN_TARGET_ROLES, RoleCode.ROLE_CORP_MANAGER.name(), + "로그인 2차 인증 대상 역할 (쉼표구분: ROLE_USER,ROLE_CORP_USER,ROLE_CORP_MANAGER / 전체: ALL)"); + if (value == null || value.trim().isEmpty()) { + return false; + } + String trimmed = value.trim(); + if ("ALL".equalsIgnoreCase(trimmed)) { + return true; + } + for (String token : trimmed.split(",")) { + if (roleCode.name().equalsIgnoreCase(token.trim())) { + return true; + } + } + return false; + } + + /** step-up(민감기능) 2FA 전체 활성화 여부(마스터 스위치) */ + public boolean isStepUpEnabled() { + return parseBool(resolve(KEY_STEPUP_ENABLED, "false", "민감기능 추가 인증(step-up) 전체 활성화 여부 (true/false)")); + } + + /** + * 특정 보호 경로에 step-up 2FA 를 적용할지 여부(지점별 스위치). + * 전체 스위치({@link #isStepUpEnabled()})가 켜진 상태에서 지점별로 개별 on/off 한다. + * 지점 프로퍼티({@code two-factor.stepup.<지점>})의 기본값은 true(전체 스위치를 켜면 기본 전 지점 적용). + * + * @param servletPath 보호 경로. 매핑 키가 없으면(비보호 경로) false + */ + public boolean isStepUpPointEnabled(String servletPath) { + String key = StepUpProtectedPaths.propertyKeyOf(servletPath); + if (key == null) { + return false; + } + return parseBool(resolve(key, "true", "step-up 2FA 지점 적용 여부 (true/false): " + servletPath)); + } + + /** 2FA 인증번호 유효시간(초). 기본 180초(3분) */ + public int getTtlSeconds() { + return parseInt(resolve(KEY_TTL_SECONDS, "180", "2차 인증번호 유효시간(초)"), 180); + } + + /** 인증번호 검증 시도 한도. 기본 5회 */ + public int getAttemptLimit() { + return parseInt(resolve(KEY_ATTEMPT_LIMIT, "5", "2차 인증번호 검증 시도 한도"), 5); + } + + /** 팝업에 테스트용 인증번호를 노출할지 여부(개발/테스트 전용) */ + public boolean isTestNoticeEnabled() { + return parseBool(resolve(KEY_TEST_NOTICE_ENABLED, "false", "2차 인증 팝업에 테스트용 인증번호 표시 여부 (true/false)")); + } + + private String resolve(String key, String defaultValue, String description) { + return portalPropertyService.getOrCreateProperty(GROUP, key, defaultValue, description); + } + + /** + * boolean PTL_PROPERTY 값 파싱. + * DB 관례에 맞춰 true/false 문자열을 사용한다(예: {@code org.hard-delete.enabled=true}). + * "true"(대소문자 무시)만 참으로 본다. 그 외(false/공백/null 등)는 모두 거짓. + */ + private static boolean parseBool(String value) { + return value != null && "true".equalsIgnoreCase(value.trim()); + } + + private static int parseInt(String value, int fallback) { + try { + return Integer.parseInt(value.trim()); + } catch (Exception e) { + return fallback; + } + } +} diff --git a/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/TwoFactorService.java b/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/TwoFactorService.java new file mode 100644 index 0000000..aa1e6b5 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/TwoFactorService.java @@ -0,0 +1,510 @@ +package com.eactive.apim.portal.apps.auth.twofactor; + +import com.eactive.apim.portal.apps.auth.service.AuthNumberService; +import com.eactive.apim.portal.apps.auth.service.AuthNumberStorage; +import com.eactive.apim.portal.apps.auth.twofactor.dto.TwoFactorChannel; +import com.eactive.apim.portal.apps.auth.twofactor.dto.TwoFactorInfoResponse; +import com.eactive.apim.portal.apps.auth.twofactor.dto.TwoFactorSendResponse; +import com.eactive.apim.portal.apps.auth.twofactor.dto.TwoFactorVerifyResponse; +import com.eactive.apim.portal.apps.login.constants.LoginFailureReason; +import com.eactive.apim.portal.apps.login.constants.LoginType; +import com.eactive.apim.portal.apps.login.service.LoginFinalizer; +import com.eactive.apim.portal.apps.user.service.PortalUserAuthService; +import com.eactive.apim.portal.apps.user.service.PortalUserLogService; +import com.eactive.apim.portal.common.user.PortalAuthenticatedUser; +import com.eactive.apim.portal.common.util.PhoneNumberUtil; +import com.eactive.apim.portal.common.util.SecurityUtil; +import com.eactive.apim.portal.common.util.StringMaskingUtil; +import com.eactive.apim.portal.portalorg.entity.PortalOrgEnums; +import com.eactive.apim.portal.portaluser.entity.PortalUser; +import com.eactive.apim.portal.portaluser.entity.PortalUserEnums; +import com.eactive.apim.portal.portaluser.repository.PortalUserRepository; +import com.eactive.apim.portal.portaluser.service.AuthNumberException; +import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +/** + * 2FA(2차 인증/추가 인증) 공통 서비스. 로그인 pending 인증과 step-up(민감기능) 인증을 모두 처리한다. + * + *

핵심 원칙: + *

    + *
  • 수신처는 서버가 세션의 대상 사용자로부터 DB 기준으로 결정한다(클라이언트는 채널만 선택).
  • + *
  • 진행 상태는 세션 {@link TwoFactorContext} 단일 소스로 관리한다.
  • + *
  • 다른 플로우가 진행 중이면 발송을 막고(inProgress), confirm 후 force 로만 강제 종료·재시작한다.
  • + *
+ */ +@Service +@Transactional +@RequiredArgsConstructor +public class TwoFactorService { + + private static final Logger log = LoggerFactory.getLogger(TwoFactorService.class); + + // === 세션 attribute 키 === + /** 로그인 1차 인증 통과 후 대기 중인 사용자 id (존재 시 LOGIN 모드) */ + public static final String ATTR_PENDING_USER_ID = "TFA_PENDING_USER_ID"; + /** 대기 중 사용자 loginId (감사/세션 표기) */ + public static final String ATTR_PENDING_LOGIN_ID = "TFA_PENDING_LOGIN_ID"; + /** 진행 중 2FA 컨텍스트 */ + public static final String ATTR_CONTEXT = "TFA_CONTEXT"; + /** step-up 1회용 통과권 - 대상 경로 */ + public static final String ATTR_STEPUP_PASS_PATH = "TFA_STEPUP_PASS_PATH"; + /** step-up 1회용 통과권 - 발급 시각 */ + public static final String ATTR_STEPUP_PASS_AT = "TFA_STEPUP_PASS_AT"; + + /** step-up 통과권 유효시간(초). 인증 성공 후 대상 페이지 진입까지의 이동 여유분 */ + public static final int STEPUP_PASS_TTL_SECONDS = 120; + /** 재발송/채널전환 통합 최소 간격(초) */ + private static final int RESEND_THROTTLE_SECONDS = 30; + + private final TwoFactorProperties properties; + private final AuthNumberService authNumberService; + private final AuthNumberStorage authNumberStorage; + private final PortalUserRepository portalUserRepository; + private final PortalUserAuthService portalUserAuthService; + private final PortalUserLogService userLogService; + private final LoginFinalizer loginFinalizer; + + // ========================================================================= + // INFO + // ========================================================================= + + public TwoFactorInfoResponse getInfo(HttpSession session, String purpose) { + TwoFactorInfoResponse res = new TwoFactorInfoResponse(); + + TwoFactorContext.Mode mode = resolveMode(session); + if (mode == null) { + res.setAvailable(false); + return res; + } + PortalUser user = resolveTargetUser(session, mode); + if (user == null) { + res.setAvailable(false); + return res; + } + + res.setAvailable(true); + res.setMode(mode.name()); + res.setChannels(buildChannels(user)); + res.setTtlSeconds(properties.getTtlSeconds()); + res.setTestNoticeEnabled(properties.isTestNoticeEnabled()); + + TwoFactorContext ctx = getActiveContext(session); + if (ctx != null && !isSameFlow(ctx, mode, purpose)) { + res.setInProgress(true); + res.setMessage("진행 중인 다른 인증 절차가 있습니다."); + } + return res; + } + + // ========================================================================= + // SEND + // ========================================================================= + + public TwoFactorSendResponse send(HttpServletRequest request, HttpSession session, String channel, + String purpose, boolean force) { + TwoFactorSendResponse res = new TwoFactorSendResponse(); + + TwoFactorContext.Mode mode = resolveMode(session); + if (mode == null) { + res.setValid(false); + res.setMessage("인증 대상 정보가 없습니다. 다시 시도해주세요."); + return res; + } + if (mode == TwoFactorContext.Mode.STEPUP && !StepUpProtectedPaths.isTwoFactorPurpose(purpose)) { + res.setValid(false); + res.setMessage("허용되지 않은 요청입니다."); + return res; + } + + PortalUser user = resolveTargetUser(session, mode); + if (user == null) { + res.setValid(false); + res.setMessage("인증 대상 사용자를 찾을 수 없습니다."); + return res; + } + + String normalizedChannel = channel == null ? "" : channel.trim().toUpperCase(); + String recipient = resolveRecipient(user, normalizedChannel); + if (recipient == null) { + res.setValid(false); + res.setMessage("선택한 방법으로 인증할 수 있는 정보가 없습니다."); + return res; + } + + // 진행 중 컨텍스트 처리 + TwoFactorContext ctx = getActiveContext(session); + if (ctx != null) { + boolean sameFlow = isSameFlow(ctx, mode, purpose); + if (!sameFlow) { + if (!force) { + res.setValid(false); + res.setInProgress(true); + res.setMessage("진행 중인 다른 인증 절차가 있습니다. 강제 종료 후 진행하시겠습니까?"); + return res; + } + discardContext(request, session, ctx); // 강제 종료(감사 기록 포함) + } else if (ctx.getStartedAt() != null + && ctx.getStartedAt().plusSeconds(RESEND_THROTTLE_SECONDS).isAfter(LocalDateTime.now())) { + res.setValid(false); + res.setMessage("잠시 후에 다시 시도해 주세요."); + return res; + } + } + + int ttl = properties.getTtlSeconds(); + String authNumber; + try { + authNumber = authNumberService.sendRequestAuthNumber(recipient, + "SMS".equals(normalizedChannel) ? "SMS" : "EMAIL", ttl); + } catch (AuthNumberException e) { + res.setValid(false); + res.setMessage(e.getMessage()); + return res; + } + + TwoFactorContext newCtx = new TwoFactorContext(); + newCtx.setMode(mode); + newCtx.setChannel(normalizedChannel); + newCtx.setRecipient(recipient); + newCtx.setPurpose(mode == TwoFactorContext.Mode.STEPUP ? purpose : null); + newCtx.setStartedAt(LocalDateTime.now()); + newCtx.setTtlSeconds(ttl); + newCtx.setAttempts(0); + session.setAttribute(ATTR_CONTEXT, newCtx); + + res.setValid(true); + res.setMessage("인증번호를 발송하였습니다."); + res.setTtlSeconds(ttl); + if (properties.isTestNoticeEnabled()) { + res.setTestAuthNumber(authNumber); + } + return res; + } + + // ========================================================================= + // VERIFY + // ========================================================================= + + public TwoFactorVerifyResponse verify(HttpServletRequest request, HttpSession session, String code) { + TwoFactorVerifyResponse res = new TwoFactorVerifyResponse(); + + TwoFactorContext ctx = getActiveContext(session); + if (ctx == null) { + res.setValid(false); + res.setTerminated(true); + res.setMessage("인증 시간이 만료되었습니다. 처음부터 다시 진행해주세요."); + return res; + } + + if (ctx.isExpired(LocalDateTime.now())) { + terminateWithFailure(session, ctx, LoginFailureReason.TWO_FACTOR_TIMEOUT, request); + res.setValid(false); + res.setTerminated(true); + res.setMessage("입력 시간이 초과되었습니다. 처음부터 다시 진행해주세요."); + return res; + } + + int attempts = ctx.incrementAttempts(); + int limit = properties.getAttemptLimit(); + + try { + authNumberService.verifyAuthNumber(ctx.getRecipient(), code); + } catch (AuthNumberException e) { + AuthNumberException.Reason reason = e.getReason(); + if (reason == AuthNumberException.Reason.EXPIRED || reason == AuthNumberException.Reason.NOT_FOUND) { + terminateWithFailure(session, ctx, LoginFailureReason.TWO_FACTOR_TIMEOUT, request); + res.setValid(false); + res.setTerminated(true); + res.setMessage("입력 시간이 초과되었습니다. 처음부터 다시 진행해주세요."); + return res; + } + // 코드 불일치 + if (attempts >= limit) { + terminateWithFailure(session, ctx, LoginFailureReason.TWO_FACTOR_ATTEMPT_EXCEEDED, request); + res.setValid(false); + res.setTerminated(true); + res.setMessage("인증 시도 횟수를 초과했습니다. 처음부터 다시 진행해주세요."); + return res; + } + session.setAttribute(ATTR_CONTEXT, ctx); // attempts 갱신 반영 + res.setValid(false); + res.setRemainingAttempts(limit - attempts); + res.setMessage("인증번호가 일치하지 않습니다. (남은 횟수 " + (limit - attempts) + "회)"); + return res; + } + + // 검증 성공 — 인증번호 즉시 소비(재사용 방지, 2FA 한정) + authNumberStorage.deleteAuthNumber(ctx.getRecipient()); + session.removeAttribute(ATTR_CONTEXT); + + if (ctx.getMode() == TwoFactorContext.Mode.LOGIN) { + return completeLogin(request, session, res); + } + + // STEPUP — 1회용 통과권 발급 + issueStepUpPass(session, ctx.getPurpose()); + res.setValid(true); + res.setMessage("인증이 완료되었습니다."); + return res; + } + + private TwoFactorVerifyResponse completeLogin(HttpServletRequest request, HttpSession session, + TwoFactorVerifyResponse res) { + String userId = (String) session.getAttribute(ATTR_PENDING_USER_ID); + String loginId = (String) session.getAttribute(ATTR_PENDING_LOGIN_ID); + + PortalUser user = userId != null ? portalUserRepository.findById(userId).orElse(null) : null; + if (user == null) { + clearPending(session); + res.setValid(false); + res.setTerminated(true); + res.setMessage("로그인 정보를 찾을 수 없습니다. 다시 로그인해주세요."); + return res; + } + + // 1차 인증~2FA 사이 상태 변경 방어(잠금/차단/승인 취소) + String stateError = revalidateLoginState(user); + if (stateError != null) { + userLogService.logFailure(loginId, request.getRemoteAddr(), session.getId(), + LoginFailureReason.ACCOUNT_DISABLED, request.getHeader("User-Agent")); + clearPending(session); + res.setValid(false); + res.setTerminated(true); + res.setMessage(stateError); + return res; + } + + // 프로그래매틱 인증 확정 (요청 종료 시 SecurityContextPersistenceFilter 가 세션에 저장) + PortalAuthenticatedUser authUser = portalUserAuthService.buildAuthenticatedUser(user); + UsernamePasswordAuthenticationToken token = + new UsernamePasswordAuthenticationToken(authUser, null, authUser.getAuthorities()); + token.setDetails(authUser); + SecurityContextHolder.getContext().setAuthentication(token); + + String redirect = loginFinalizer.finalizeLogin(user, loginId, request, LoginType.TWO_FACTOR); + clearPending(session); + + res.setValid(true); + res.setRedirect(redirect); + res.setMessage("인증이 완료되었습니다."); + return res; + } + + // ========================================================================= + // CANCEL (팝업 닫기 / 타이머 만료) + // ========================================================================= + + public void cancel(HttpServletRequest request, HttpSession session, String reason) { + TwoFactorContext ctx = getActiveContext(session); + boolean timeout = "TIMEOUT".equalsIgnoreCase(reason); + LoginFailureReason failureReason = timeout + ? LoginFailureReason.TWO_FACTOR_TIMEOUT : LoginFailureReason.TWO_FACTOR_CANCELLED; + + if (ctx != null && ctx.getMode() == TwoFactorContext.Mode.LOGIN) { + String loginId = (String) session.getAttribute(ATTR_PENDING_LOGIN_ID); + userLogService.logFailure(loginId, request.getRemoteAddr(), session.getId(), failureReason, + request.getHeader("User-Agent")); + } + if (ctx != null && ctx.getRecipient() != null) { + authNumberStorage.deleteAuthNumber(ctx.getRecipient()); + } + session.removeAttribute(ATTR_CONTEXT); + + // 로그인 2FA 취소는 로그인 자체를 포기(익명 유지) → pending 제거 + if (ctx == null || ctx.getMode() == TwoFactorContext.Mode.LOGIN) { + clearPending(session); + } + } + + // ========================================================================= + // LOGIN pending 진입 (SuccessHandler 에서 호출) + // ========================================================================= + + /** 로그인 1차 인증 통과 사용자를 2FA 대기 상태로 세팅한다. (SecurityContext 클리어는 호출부 책임) */ + public void beginLoginChallenge(HttpSession session, PortalUser user) { + session.setAttribute(ATTR_PENDING_USER_ID, user.getId()); + session.setAttribute(ATTR_PENDING_LOGIN_ID, user.getLoginId()); + session.removeAttribute(ATTR_CONTEXT); + } + + public boolean hasPendingLogin(HttpSession session) { + return session != null && session.getAttribute(ATTR_PENDING_USER_ID) != null; + } + + // ========================================================================= + // STEP-UP 통과권 + // ========================================================================= + + private void issueStepUpPass(HttpSession session, String path) { + session.setAttribute(ATTR_STEPUP_PASS_PATH, path); + session.setAttribute(ATTR_STEPUP_PASS_AT, LocalDateTime.now()); + } + + /** + * 보호 경로 수정 저장 직후 원경로로 되돌아가는 즉시 왕복(예: {@code /mypage} 수정 → + * {@code redirect:/mypage})에서 중복 step-up 을 막기 위해 통과권을 재발급한다. + * + *

경로 고정 + TTL({@link #STEPUP_PASS_TTL_SECONDS}s) 로 1회 왕복만 커버하며, + * 이후 새로 {@code /mypage} 에 진입하면 정상적으로 다시 인증을 요구한다("매번 인증" 유지).

+ */ + public void grantStepUpPass(HttpSession session, String path) { + if (session != null && path != null) { + issueStepUpPass(session, path); + } + } + + /** + * 지정 경로에 대한 유효한 1회용 통과권이 있으면 소비(제거)하고 true 를 반환한다. + * (매번 인증 정책 — 통과권은 즉시 소멸) + */ + public boolean consumeStepUpPass(HttpSession session, String servletPath) { + Object passPath = session.getAttribute(ATTR_STEPUP_PASS_PATH); + Object passAt = session.getAttribute(ATTR_STEPUP_PASS_AT); + if (!(passPath instanceof String) || !(passAt instanceof LocalDateTime)) { + return false; + } + boolean valid = passPath.equals(servletPath) + && ((LocalDateTime) passAt).plusSeconds(STEPUP_PASS_TTL_SECONDS).isAfter(LocalDateTime.now()); + // 매번 인증: 일치/불일치 무관하게 통과권은 이번 판정에서 소멸시킨다. + session.removeAttribute(ATTR_STEPUP_PASS_PATH); + session.removeAttribute(ATTR_STEPUP_PASS_AT); + return valid; + } + + // ========================================================================= + // 내부 helper + // ========================================================================= + + private TwoFactorContext.Mode resolveMode(HttpSession session) { + if (session.getAttribute(ATTR_PENDING_USER_ID) != null) { + return TwoFactorContext.Mode.LOGIN; + } + if (SecurityUtil.isAuthenticated()) { + return TwoFactorContext.Mode.STEPUP; + } + return null; + } + + private PortalUser resolveTargetUser(HttpSession session, TwoFactorContext.Mode mode) { + if (mode == TwoFactorContext.Mode.LOGIN) { + String userId = (String) session.getAttribute(ATTR_PENDING_USER_ID); + return userId != null ? portalUserRepository.findById(userId).orElse(null) : null; + } + PortalAuthenticatedUser current = SecurityUtil.getPortalAuthenticatedUser(); + if (current == null) { + return null; + } + // 세션 로드 이후 연락처 변경 반영을 위해 DB 재조회 + return portalUserRepository.findById(current.getId()).orElse(null); + } + + private List buildChannels(PortalUser user) { + List channels = new ArrayList<>(); + if (StringUtils.hasText(user.getEmailAddr())) { + channels.add(new TwoFactorChannel("EMAIL", StringMaskingUtil.maskEmail(user.getEmailAddr()))); + } + if (StringUtils.hasText(user.getMobileNumber())) { + channels.add(new TwoFactorChannel("SMS", StringMaskingUtil.maskMobileNumber(user.getMobileNumber()))); + } + return channels; + } + + private String resolveRecipient(PortalUser user, String channel) { + if ("EMAIL".equals(channel)) { + return StringUtils.hasText(user.getEmailAddr()) ? user.getEmailAddr() : null; + } + if ("SMS".equals(channel)) { + return StringUtils.hasText(user.getMobileNumber()) + ? PhoneNumberUtil.digitsOnly(user.getMobileNumber()) : null; + } + return null; + } + + private TwoFactorContext getActiveContext(HttpSession session) { + Object ctx = session.getAttribute(ATTR_CONTEXT); + if (!(ctx instanceof TwoFactorContext)) { + return null; + } + TwoFactorContext context = (TwoFactorContext) ctx; + if (context.isExpired(LocalDateTime.now())) { + // 만료 컨텍스트는 정리(감사는 verify/cancel 경로에서 처리) + session.removeAttribute(ATTR_CONTEXT); + if (context.getRecipient() != null) { + authNumberStorage.deleteAuthNumber(context.getRecipient()); + } + return null; + } + return context; + } + + private boolean isSameFlow(TwoFactorContext ctx, TwoFactorContext.Mode mode, String purpose) { + return ctx.getMode() == mode && Objects.equals(ctx.getPurpose(), + mode == TwoFactorContext.Mode.STEPUP ? purpose : null); + } + + /** 강제 종료: 인증번호 삭제 + (로그인 컨텍스트면) 취소 감사 기록 */ + private void discardContext(HttpServletRequest request, HttpSession session, TwoFactorContext ctx) { + if (ctx.getMode() == TwoFactorContext.Mode.LOGIN) { + String loginId = (String) session.getAttribute(ATTR_PENDING_LOGIN_ID); + userLogService.logFailure(loginId, request.getRemoteAddr(), session.getId(), + LoginFailureReason.TWO_FACTOR_CANCELLED, request.getHeader("User-Agent")); + } + if (ctx.getRecipient() != null) { + authNumberStorage.deleteAuthNumber(ctx.getRecipient()); + } + session.removeAttribute(ATTR_CONTEXT); + } + + /** 검증 실패로 절차 종료: 인증번호 삭제 + 감사 + 컨텍스트/pending 정리 */ + private void terminateWithFailure(HttpSession session, TwoFactorContext ctx, + LoginFailureReason reason, HttpServletRequest request) { + if (ctx.getMode() == TwoFactorContext.Mode.LOGIN) { + String loginId = (String) session.getAttribute(ATTR_PENDING_LOGIN_ID); + userLogService.logFailure(loginId, request.getRemoteAddr(), session.getId(), reason, + request.getHeader("User-Agent")); + clearPending(session); + } + if (ctx.getRecipient() != null) { + authNumberStorage.deleteAuthNumber(ctx.getRecipient()); + } + session.removeAttribute(ATTR_CONTEXT); + } + + private void clearPending(HttpSession session) { + session.removeAttribute(ATTR_PENDING_USER_ID); + session.removeAttribute(ATTR_PENDING_LOGIN_ID); + } + + /** 1차 인증~2FA 사이 계정 상태 재검증. 문제 있으면 사용자 안내 메시지 반환, 정상이면 null */ + private String revalidateLoginState(PortalUser user) { + if ("Y".equalsIgnoreCase(user.getAccountLockYn())) { + return "계정이 잠겼습니다. 비밀번호 초기화 또는 관리자에게 문의하세요."; + } + if (PortalUserEnums.UserStatus.ADMINBLOCK.equals(user.getUserStatus())) { + return "법인 관리자에 의해 비활성화된 계정입니다."; + } + if (PortalUserEnums.ApprovalStatus.PENDING.equals(user.getApprovalStatus())) { + return "사용자 승인 대기중입니다."; + } + if (user.getPortalOrg() != null + && !PortalOrgEnums.ApprovalStatus.COMPLETED.equals(user.getPortalOrg().getApprovalStatus())) { + return "로그인할 수 없습니다. 관리자에게 문의하세요. (법인 승인대기중)"; + } + return null; + } +} diff --git a/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/dto/TwoFactorChannel.java b/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/dto/TwoFactorChannel.java new file mode 100644 index 0000000..10b4d6f --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/dto/TwoFactorChannel.java @@ -0,0 +1,14 @@ +package com.eactive.apim.portal.apps.auth.twofactor.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; + +/** 2FA 발송 가능 채널 1건. masked 는 화면 표기용 마스킹 수신처. */ +@Data +@AllArgsConstructor +public class TwoFactorChannel { + /** EMAIL | SMS */ + private String type; + /** 마스킹된 수신처 (예: te**@ex**.com, 010-12**-34**) */ + private String masked; +} diff --git a/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/dto/TwoFactorInfoResponse.java b/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/dto/TwoFactorInfoResponse.java new file mode 100644 index 0000000..16c406d --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/dto/TwoFactorInfoResponse.java @@ -0,0 +1,24 @@ +package com.eactive.apim.portal.apps.auth.twofactor.dto; + +import lombok.Data; + +import java.util.List; + +/** GET /auth/2fa/info 응답. 팝업 초기 렌더용. */ +@Data +public class TwoFactorInfoResponse { + /** 컨텍스트 유효 여부(로그인 pending 또는 인증 사용자). false 면 팝업 진입 불가 */ + private boolean available; + /** LOGIN | STEPUP */ + private String mode; + /** 발송 가능 채널(휴대폰 없으면 이메일만) */ + private List channels; + /** 인증번호 유효시간(초) — 타이머 초기값 */ + private int ttlSeconds; + /** 테스트용 인증번호 노출 여부 */ + private boolean testNoticeEnabled; + /** 이미 진행 중인 절차 존재 여부(다른 탭/페이지) */ + private boolean inProgress; + /** 진행 중인 절차의 안내 메시지(있으면) */ + private String message; +} diff --git a/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/dto/TwoFactorSendResponse.java b/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/dto/TwoFactorSendResponse.java new file mode 100644 index 0000000..91fd58c --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/dto/TwoFactorSendResponse.java @@ -0,0 +1,16 @@ +package com.eactive.apim.portal.apps.auth.twofactor.dto; + +import lombok.Data; + +/** POST /auth/2fa/send 응답. */ +@Data +public class TwoFactorSendResponse { + private boolean valid; + private String message; + /** 타이머 유효시간(초) */ + private int ttlSeconds; + /** 테스트용 인증번호(테스트 노출 활성 시에만 채워짐) */ + private String testAuthNumber; + /** 이미 진행 중인 절차가 있어 발송을 막은 경우 true (confirm 후 force 재요청 유도) */ + private boolean inProgress; +} diff --git a/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/dto/TwoFactorVerifyResponse.java b/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/dto/TwoFactorVerifyResponse.java new file mode 100644 index 0000000..f164951 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/auth/twofactor/dto/TwoFactorVerifyResponse.java @@ -0,0 +1,16 @@ +package com.eactive.apim.portal.apps.auth.twofactor.dto; + +import lombok.Data; + +/** POST /auth/2fa/verify 응답. */ +@Data +public class TwoFactorVerifyResponse { + private boolean valid; + private String message; + /** LOGIN 모드 성공 시 이동 대상 URL */ + private String redirect; + /** 실패 시 남은 시도 횟수 */ + private int remainingAttempts; + /** 시도 초과/타임아웃 등으로 절차가 강제 종료되어 재시작이 필요한 경우 true */ + private boolean terminated; +} diff --git a/src/main/java/com/eactive/apim/portal/apps/commission/constant/Constants.java b/src/main/java/com/eactive/apim/portal/apps/commission/constant/Constants.java deleted file mode 100755 index 62df1d0..0000000 --- a/src/main/java/com/eactive/apim/portal/apps/commission/constant/Constants.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.eactive.apim.portal.apps.commission.constant; - -/** - * Application constants. - */ -public final class Constants { - - public static final String SYSTEM_ACCOUNT = "system"; - - public static final String APIM_FOR_OBP_KEY = "x-obp-trust-system"; - - public static final String APIM_CODE_FOR_OBP_KEY = "x-obp-partnercode"; - - public static final String SMS_URL = "/api/messaging/sms"; - - public static final String COMMISSION_URL = "/api/billing/billing/findBillingForCondition"; - - public static final String COMMISSION_PRINT_URL = "/api/billing/billing/findBillingForCondition/print"; - - public static final String OBP_ORGANIZATION_URL = "/api/customer/findCustomerByBusinessManRegistrationNo/"; - - public static String LANGUAGE = "ko"; - - private Constants() { - } -} diff --git a/src/main/java/com/eactive/apim/portal/apps/commission/controller/CommissionController.java b/src/main/java/com/eactive/apim/portal/apps/commission/controller/CommissionController.java deleted file mode 100755 index 3fb0963..0000000 --- a/src/main/java/com/eactive/apim/portal/apps/commission/controller/CommissionController.java +++ /dev/null @@ -1,320 +0,0 @@ -package com.eactive.apim.portal.apps.commission.controller; - -import com.eactive.apim.portal.apps.commission.constant.Constants; -import com.eactive.apim.portal.apps.commission.dto.CommissionDTO; -import com.eactive.apim.portal.apps.commission.dto.CommissionSearch; -import com.eactive.apim.portal.apps.commission.exception.ErrorUtil; -import com.eactive.apim.portal.apps.commission.service.ExternalService; -import com.eactive.apim.portal.common.util.SecurityUtil; -import com.eactive.apim.portal.portalproperty.service.PortalPropertyService; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import lombok.RequiredArgsConstructor; -import org.apache.commons.collections.MapUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.http.ResponseEntity; -import org.springframework.security.access.annotation.Secured; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.util.StringUtils; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; - - -@Controller -@RequestMapping(value = "/commission") -@Secured("ROLE_APP") -@RequiredArgsConstructor -public class CommissionController { - - private final Logger log = LoggerFactory.getLogger(CommissionController.class); - - private final ExternalService externalService; - private final PortalPropertyService portalPropertyService; - private final ObjectMapper objectMapper = new ObjectMapper() - .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - - @GetMapping("/manage") - public String commissionManagePage(Model model) { - // 기본 정보 설정 - model.addAttribute("organization", SecurityUtil.getUserOrg()); - model.addAttribute("noOrgCode", StringUtils.isEmpty(SecurityUtil.getUserOrg().getOrgCode())); - return "apps/commission/commissionManage"; - } - - @GetMapping("/print/{year}/{month}") - public String commissionPrintPage( - @PathVariable("year") String year, - @PathVariable("month") String month, - Model model) { - - CommissionSearch search = new CommissionSearch(); - search.setPartnerCode(SecurityUtil.getUserOrg().getOrgCode()); - search.setYear(year); - search.setMonth(month); - - CommissionResult result = fetchCommissionData(search, Constants.COMMISSION_PRINT_URL); - - if (result.hasError()) { - model.addAttribute("error", result.getError()); - return "apps/commission/commissionPrint"; - } - - // Model에 데이터 바인딩 - model.addAttribute("commission", result.getData()); - model.addAttribute("organization", SecurityUtil.getUserOrg().getOrgName()); - model.addAttribute("year", year); - model.addAttribute("month", month); - model.addAttribute("toDay", java.time.LocalDate.now().toString()); - - return "apps/commission/commissionPrint"; - } - - // ==================== API Endpoints ==================== - - @PostMapping("/print/check") - public ResponseEntity checkPrintData(@RequestBody CommissionSearch search) { - search.setPartnerCode(SecurityUtil.getUserOrg().getOrgCode()); - - if (StringUtils.isEmpty(search.getPartnerCode())) { - return ErrorUtil.create("fail.commission.needPartnerCode"); - } - - CommissionResult result = fetchCommissionData(search, Constants.COMMISSION_PRINT_URL); - - if (result.hasError()) { - return ErrorUtil.create("fail.commission.print", result.getError()); - } - - return ResponseEntity.ok().body(result.getData()); - } - - @PostMapping() - public ResponseEntity list(@RequestBody CommissionSearch search) { - search.setPartnerCode(SecurityUtil.getUserOrg().getOrgCode()); - - if (StringUtils.isEmpty(search.getPartnerCode())) { - return ErrorUtil.create("fail.commission.needPartnerCode"); - } - - CommissionResult result = fetchCommissionData(search, Constants.COMMISSION_URL); - - if (result.hasError()) { - return ErrorUtil.create("fail.commission.list", result.getError()); - } - - return ResponseEntity.ok().body(result.getData()); - } - - // ==================== Private Methods ==================== - - /** - * 외부 API를 호출하여 수수료 데이터를 조회합니다. - * - * @param search 검색 조건 - * @param apiPath API 경로 (Constants.COMMISSION_URL 또는 Constants.COMMISSION_PRINT_URL) - * @return 조회 결과 - */ - private CommissionResult fetchCommissionData(CommissionSearch search, String apiPath) { - if (StringUtils.isEmpty(search.getPartnerCode())) { - return CommissionResult.error("파트너 코드가 없습니다."); - } - - try { - String obmUrl = portalPropertyService.getPortalPropertiesAsMap("Portal").getOrDefault("obp.obm.url", ""); - String url = obmUrl + apiPath; - - // 첫 번째 API 호출 - String result = externalService.getResponseHttpPost(url, toJson(search)); - if (StringUtils.isEmpty(result)) { - return CommissionResult.error("외부 서버 연결에 실패하였습니다."); - } - - HashMap returnObj = objectMapper.readValue(result, HashMap.class); - - // 에러 응답 체크 - String errorMessage = extractErrorMessage(returnObj); - if (errorMessage != null) { - return CommissionResult.error(errorMessage); - } - - // Together 관련 추가 처리 (000002-01) - if (StringUtils.pathEquals(search.getPartnerCode(), "000002-01")) { - log.debug("Found Together : " + search.getPartnerCode()); - - CommissionSearch togetherSearch = new CommissionSearch(); - togetherSearch.setPartnerCode("000002-02"); - togetherSearch.setYear(search.getYear()); - togetherSearch.setMonth(search.getMonth()); - - String togetherResult = externalService.getResponseHttpPost(url, toJson(togetherSearch)); - if (StringUtils.isEmpty(togetherResult)) { - return CommissionResult.error("외부 서버 연결에 실패하였습니다."); - } - - HashMap togetherReturnObj = objectMapper.readValue(togetherResult, HashMap.class); - log.debug("togetherLendingReturnObj : " + togetherReturnObj.toString()); - - String togetherErrorMessage = extractErrorMessage(togetherReturnObj); - if (togetherErrorMessage != null) { - return CommissionResult.error(togetherErrorMessage); - } - - HashMap mergeMap = mergeCommissionData(returnObj, togetherReturnObj); - result = objectMapper.writeValueAsString(mergeMap); - } - - CommissionDTO commissionDTO = objectMapper.readValue(result, CommissionDTO.class); - return CommissionResult.success(commissionDTO); - - } catch (Exception e) { - log.warn(e.getMessage(), e); - return CommissionResult.error("수수료 조회에 실패하였습니다."); - } - } - - /** - * 응답에서 에러 메시지를 추출합니다. - */ - private String extractErrorMessage(HashMap response) { - if (!StringUtils.isEmpty(response.get("error"))) { - HashMap error = (HashMap) response.get("error"); - return error.get("message").toString(); - } - return null; - } - - private String toJson(CommissionSearch search) { - try { - Map jsonMap = new HashMap<>(); - jsonMap.put("partnerCode", search.getPartnerCode()); - // 월을 두 자리로 패딩 (예: 1 -> 01, 12 -> 12) - String paddedMonth = String.format("%02d", Integer.parseInt(search.getMonth())); - jsonMap.put("targetOnMonth", search.getYear() + paddedMonth); - - return objectMapper.writeValueAsString(jsonMap); - } catch (Exception e) { - log.warn("Failed to serialize CommissionSearch to JSON", e); - return "{}"; - } - } - - /** - * Safely parse double value from HashMap - * Handles both numeric types and string representations - */ - private double safeGetDouble(HashMap map, String key) { - Object value = map.get(key); - if (value == null) { - return 0.0; - } - try { - if (value instanceof Number) { - return ((Number) value).doubleValue(); - } else if (value instanceof String) { - return Double.parseDouble((String) value); - } - return 0.0; - } catch (NumberFormatException e) { - log.warn("Failed to parse double value for key: {} with value: {}", key, value); - return 0.0; - } - } - - public HashMap mergeCommissionData(HashMap o1, HashMap o2) { - - HashMap mergeMap = new HashMap(); - - String o1Status = MapUtils.getString(o1, "status"); - String o2Status = MapUtils.getString(o2, "status"); - String resultStatus = null; - if (Integer.parseInt(o1Status) <= Integer.parseInt(o2Status)) { - resultStatus = o1Status; - } else { - resultStatus = o2Status; - } - double apiTotalValueSum = safeGetDouble(o1, "apiTotalValueSum") + safeGetDouble(o2, "apiTotalValueSum"); - double apiSum = safeGetDouble(o1, "apiSum") + safeGetDouble(o2, "apiSum"); - double apiTotalValueSum2 = safeGetDouble(o1, "apiTotalValueSum2") + safeGetDouble(o2, "apiTotalValueSum2"); - double apiSum2 = safeGetDouble(o1, "apiSum2") + safeGetDouble(o2, "apiSum2"); - double productTotalValueCountSum = safeGetDouble(o1, "productTotalValueCountSum") + safeGetDouble(o2, "productTotalValueCountSum"); - double productTotalValueAmountSum = safeGetDouble(o1, "productTotalValueAmountSum") + safeGetDouble(o2, "productTotalValueAmountSum"); - double productSum = safeGetDouble(o1, "productSum") + safeGetDouble(o2, "productSum"); - double productTotalValueCountSum2 = safeGetDouble(o1, "productTotalValueCountSum2") + safeGetDouble(o2, "productTotalValueCountSum2"); - double productTotalValueAmountSum2 = safeGetDouble(o1, "productTotalValueAmountSum2") + safeGetDouble(o2, "productTotalValueAmountSum2"); - double productSum2 = safeGetDouble(o1, "productSum2") + safeGetDouble(o2, "productSum2"); - double totalValueCountSum = safeGetDouble(o1, "totalValueCountSum") + safeGetDouble(o2, "totalValueCountSum"); - double totalValueCountSum2 = safeGetDouble(o1, "totalValueCountSum2") + safeGetDouble(o2, "totalValueCountSum2"); - double totalValueAmountSum = safeGetDouble(o1, "totalValueAmountSum") + safeGetDouble(o2, "totalValueAmountSum"); - double totalValueAmountSum2 = safeGetDouble(o1, "totalValueAmountSum2") + safeGetDouble(o2, "totalValueAmountSum2"); - double sum = safeGetDouble(o1, "sum") + safeGetDouble(o2, "sum"); - double sum2 = safeGetDouble(o1, "sum2") + safeGetDouble(o2, "sum2"); - - List o1List = (ArrayList) o1.get("list"); - List o2pList = (ArrayList) o2.get("list"); - o1List.addAll(o2pList); - - mergeMap.put("status", resultStatus); - mergeMap.put("apiTotalValueSum", String.format("%.2f", apiTotalValueSum)); - mergeMap.put("apiSum", String.format("%.2f", apiSum)); - mergeMap.put("apiTotalValueSum2", String.format("%.2f", apiTotalValueSum2)); - mergeMap.put("apiSum2", String.format("%.2f", apiSum2)); - mergeMap.put("productTotalValueCountSum", String.format("%.2f", productTotalValueCountSum)); - mergeMap.put("productTotalValueAmountSum", String.format("%.2f", productTotalValueAmountSum)); - mergeMap.put("productSum", String.format("%.2f", productSum)); - mergeMap.put("productTotalValueCountSum2", String.format("%.2f", productTotalValueCountSum2)); - mergeMap.put("productTotalValueAmountSum2", String.format("%.2f", productTotalValueAmountSum2)); - mergeMap.put("productSum2", String.format("%.2f", productSum2)); - mergeMap.put("totalValueCountSum", String.format("%.2f", totalValueCountSum)); - mergeMap.put("totalValueCountSum2", String.format("%.2f", totalValueCountSum2)); - mergeMap.put("totalValueAmountSum", String.format("%.2f", totalValueAmountSum)); - mergeMap.put("totalValueAmountSum2", String.format("%.2f", totalValueAmountSum2)); - mergeMap.put("sum", String.format("%.2f", sum)); - mergeMap.put("sum2", String.format("%.2f", sum2)); - mergeMap.put("list", o1List); - - return mergeMap; - } - - // ==================== Inner Classes ==================== - - /** - * 수수료 조회 결과를 담는 내부 클래스 - */ - private static class CommissionResult { - private CommissionDTO data; - private String error; - - static CommissionResult success(CommissionDTO data) { - CommissionResult result = new CommissionResult(); - result.data = data; - return result; - } - - static CommissionResult error(String message) { - CommissionResult result = new CommissionResult(); - result.error = message; - return result; - } - - boolean hasError() { - return error != null; - } - - CommissionDTO getData() { - return data; - } - - String getError() { - return error; - } - } -} diff --git a/src/main/java/com/eactive/apim/portal/apps/commission/dto/CommissionDTO.java b/src/main/java/com/eactive/apim/portal/apps/commission/dto/CommissionDTO.java deleted file mode 100755 index 52281a3..0000000 --- a/src/main/java/com/eactive/apim/portal/apps/commission/dto/CommissionDTO.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.eactive.apim.portal.apps.commission.dto; - -import java.util.List; -import lombok.Data; - -@Data -public class CommissionDTO { - - private String status; - private String apiTotalValueSum; - private String apiSum; - private String apiTotalValueSum2; - private String apiSum2; - private String productTotalValueCountSum; - private String productTotalValueAmountSum; - private String productSum; - private String productTotalValueCountSum2; - private String productTotalValueAmountSum2; - private String productSum2; - private String totalValueCountSum; - private String totalValueCountSum2; - private String totalValueAmountSum; - private String totalValueAmountSum2; - private String sum; - private String sum2; - private List list; - private String documentFormatNo; -} diff --git a/src/main/java/com/eactive/apim/portal/apps/commission/dto/CommissionList.java b/src/main/java/com/eactive/apim/portal/apps/commission/dto/CommissionList.java deleted file mode 100755 index d967539..0000000 --- a/src/main/java/com/eactive/apim/portal/apps/commission/dto/CommissionList.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.eactive.apim.portal.apps.commission.dto; - -import lombok.Data; - -/** - * Created by jskim on 17. 01. 26. - * - * @author jskim - */ -@Data -public class CommissionList { - - private String paymentTargetName; - private String paymentTargetType; - private String value; - private String minimumAmount; - private String maximumAmount; - private String totalCount; - private String totalCount2; - private String totalValue; - private String totalValue2; - private String minimumAmount2; - private String maximumAmount2; - private String paymentBaseName; - private String paymentTimingName; - private String valueDivisionName; - private String sum; - private String sum2; - private String detailFeeTypeName; - private String value2; - private String changeReason2; -} diff --git a/src/main/java/com/eactive/apim/portal/apps/commission/dto/CommissionSearch.java b/src/main/java/com/eactive/apim/portal/apps/commission/dto/CommissionSearch.java deleted file mode 100755 index 70b1e78..0000000 --- a/src/main/java/com/eactive/apim/portal/apps/commission/dto/CommissionSearch.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.eactive.apim.portal.apps.commission.dto; - -import io.swagger.annotations.ApiModelProperty; -import lombok.Data; - -@Data -public class CommissionSearch { - - @ApiModelProperty(value = "대상년") - private String year; - - @ApiModelProperty(value = "대상월") - private String month; - - private String partnerCode; - -} diff --git a/src/main/java/com/eactive/apim/portal/apps/commission/exception/ErrorUtil.java b/src/main/java/com/eactive/apim/portal/apps/commission/exception/ErrorUtil.java deleted file mode 100755 index ce0f6db..0000000 --- a/src/main/java/com/eactive/apim/portal/apps/commission/exception/ErrorUtil.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.eactive.apim.portal.apps.commission.exception; - -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; - -/** - * Created by ybsong on 16. 12. 7. - * - * @author ybsong - */ -public class ErrorUtil { - public static ResponseEntity create(HttpStatus status, String message, String description) { - return new ResponseEntity<>(new ErrorVM(message, description), status); - } - - public static ResponseEntity create(HttpStatus status, String message) { - return create(status, message, null); - } - - public static ResponseEntity create(String message, String description) { - return create(HttpStatus.BAD_REQUEST, message, description); - } - - public static ResponseEntity create(String message) { - return create(message, null); - } -} diff --git a/src/main/java/com/eactive/apim/portal/apps/commission/exception/ErrorVM.java b/src/main/java/com/eactive/apim/portal/apps/commission/exception/ErrorVM.java deleted file mode 100755 index 8a00cd6..0000000 --- a/src/main/java/com/eactive/apim/portal/apps/commission/exception/ErrorVM.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.eactive.apim.portal.apps.commission.exception; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; -import lombok.Data; - -/** - * View Model for transferring error message with a list of field errors. - */ -@Data -public class ErrorVM implements Serializable { - - private static final long serialVersionUID = 1L; - - private final String message; - private final String description; - - private List fieldErrors; - - public ErrorVM(String message) { - this(message, null); - } - - public ErrorVM(String message, String description) { - this.message = message; - this.description = description; - } - - public ErrorVM(String message, String description, List fieldErrors) { - this.message = message; - this.description = description; - this.fieldErrors = fieldErrors; - } - - public void add(String objectName, String field, String message) { - if (fieldErrors == null) { - fieldErrors = new ArrayList<>(); - } - fieldErrors.add(new FieldErrorVM(objectName, field, message)); - } - -} diff --git a/src/main/java/com/eactive/apim/portal/apps/commission/exception/FieldErrorVM.java b/src/main/java/com/eactive/apim/portal/apps/commission/exception/FieldErrorVM.java deleted file mode 100755 index 7b5315f..0000000 --- a/src/main/java/com/eactive/apim/portal/apps/commission/exception/FieldErrorVM.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.eactive.apim.portal.apps.commission.exception; - -import java.io.Serializable; - -public class FieldErrorVM implements Serializable { - - private static final long serialVersionUID = 1L; - - private final String objectName; - - private final String field; - - private final String message; - - public FieldErrorVM(String dto, String field, String message) { - this.objectName = dto; - this.field = field; - this.message = message; - } - - public String getObjectName() { - return objectName; - } - - public String getField() { - return field; - } - - public String getMessage() { - return message; - } - -} diff --git a/src/main/java/com/eactive/apim/portal/apps/commission/service/ExternalService.java b/src/main/java/com/eactive/apim/portal/apps/commission/service/ExternalService.java deleted file mode 100755 index e55e506..0000000 --- a/src/main/java/com/eactive/apim/portal/apps/commission/service/ExternalService.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.eactive.apim.portal.apps.commission.service; - -import com.eactive.apim.portal.apps.commission.constant.Constants; -import com.eactive.apim.portal.portalproperty.service.PortalPropertyService; -import java.util.Map; -import javax.inject.Inject; -import lombok.RequiredArgsConstructor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.client.HttpStatusCodeException; -import org.springframework.web.client.RestClientException; -import org.springframework.web.client.RestTemplate; - -/** - * 외부 API 호출 서비스 - * RestTemplate 기반으로 외부 시스템과 HTTP 통신을 수행합니다. - * - * @author ybsong - * @since 2017-03-02 - */ -@Service -@Transactional -@RequiredArgsConstructor -public class ExternalService { - - private final Logger log = LoggerFactory.getLogger(ExternalService.class); - - private final RestTemplate restTemplate; - private final PortalPropertyService portalPropertyService; - - /** - * HTTP POST 요청을 통해 외부 API를 호출하고 응답을 받습니다. - * - * @param url 호출할 API URL - * @param payload 요청 본문 (JSON 문자열) - * @return API 응답 본문 (문자열), 오류 발생 시 null - */ - public String getResponseHttpPost(String url, String payload) { - try { - - Map portalProperties = portalPropertyService.getPortalPropertiesAsMap("Portal"); - - // HTTP 헤더 설정 - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_JSON); - headers.set(Constants.APIM_FOR_OBP_KEY, portalProperties.getOrDefault("obp.api_key","APIMPT-0002-QVBJTVBULTAwMDI=")); - headers.set(Constants.APIM_CODE_FOR_OBP_KEY, portalProperties.getOrDefault("obp.partner_code","100001-01")); - - // HTTP 요청 엔티티 생성 (헤더 + 본문) - HttpEntity requestEntity = new HttpEntity<>(payload, headers); - - // POST 요청 실행 - ResponseEntity response = restTemplate.postForEntity(url, requestEntity, String.class); - - // 응답 본문 반환 - return response.getBody(); - - } catch (HttpStatusCodeException e) { - // 4xx, 5xx 에러더라도 응답 본문이 있으면 반환 (에러 메시지 포함) - String responseBody = e.getResponseBodyAsString(); - log.warn("HTTP error from external API: url={}, status={}, body={}", url, e.getStatusCode(), responseBody); - if (responseBody != null && !responseBody.isEmpty()) { - return responseBody; - } - return null; - } catch (RestClientException e) { - log.error("Failed to call external API: url={}, error={}", url, e.getMessage(), e); - return null; - } catch (Exception e) { - log.error("Unexpected error during HTTP POST: url={}", url, e); - return null; - } - } -} diff --git a/src/main/java/com/eactive/apim/portal/apps/community/faq/controller/FaqController.java b/src/main/java/com/eactive/apim/portal/apps/community/faq/controller/FaqController.java index d78dcfd..bbb490d 100644 --- a/src/main/java/com/eactive/apim/portal/apps/community/faq/controller/FaqController.java +++ b/src/main/java/com/eactive/apim/portal/apps/community/faq/controller/FaqController.java @@ -13,6 +13,7 @@ import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; @Controller public class FaqController { @@ -47,4 +48,16 @@ public class FaqController { return "apps/community/mainFaqList"; } + + @GetMapping("/faq_view") + public String view(@RequestParam(value = "id", required = false) String id, Model model) { + if (id == null) { + return "redirect:/faq_list"; + } + + FaqDTO faq = faqFacade.getFaq(id); + model.addAttribute("faq", faq); + + return "apps/community/mainFaqDetail"; + } } diff --git a/src/main/java/com/eactive/apim/portal/apps/community/faq/service/FaqFacade.java b/src/main/java/com/eactive/apim/portal/apps/community/faq/service/FaqFacade.java index 4931830..590c4e1 100644 --- a/src/main/java/com/eactive/apim/portal/apps/community/faq/service/FaqFacade.java +++ b/src/main/java/com/eactive/apim/portal/apps/community/faq/service/FaqFacade.java @@ -7,4 +7,6 @@ import org.springframework.data.domain.Pageable; public interface FaqFacade { Page getFaqs(FaqSearch search, Pageable pageable); + + FaqDTO getFaq(String id); } diff --git a/src/main/java/com/eactive/apim/portal/apps/community/faq/service/FaqFacadeImpl.java b/src/main/java/com/eactive/apim/portal/apps/community/faq/service/FaqFacadeImpl.java index 779fc7e..de4fcb2 100644 --- a/src/main/java/com/eactive/apim/portal/apps/community/faq/service/FaqFacadeImpl.java +++ b/src/main/java/com/eactive/apim/portal/apps/community/faq/service/FaqFacadeImpl.java @@ -34,4 +34,9 @@ public class FaqFacadeImpl implements FaqFacade { return faqPage.map(faqMapper::map); } + @Override + public FaqDTO getFaq(String id) { + return faqMapper.map(faqService.findById(id)); + } + } diff --git a/src/main/java/com/eactive/apim/portal/apps/community/notice/dto/IncidentAffectedApiDTO.java b/src/main/java/com/eactive/apim/portal/apps/community/notice/dto/IncidentAffectedApiDTO.java new file mode 100644 index 0000000..30bcd8b --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/community/notice/dto/IncidentAffectedApiDTO.java @@ -0,0 +1,13 @@ +package com.eactive.apim.portal.apps.community.notice.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class IncidentAffectedApiDTO { + private String apiId; + private String apiName; +} diff --git a/src/main/java/com/eactive/apim/portal/apps/community/notice/dto/PortalNoticeDTO.java b/src/main/java/com/eactive/apim/portal/apps/community/notice/dto/PortalNoticeDTO.java index 450f195..c1c487a 100644 --- a/src/main/java/com/eactive/apim/portal/apps/community/notice/dto/PortalNoticeDTO.java +++ b/src/main/java/com/eactive/apim/portal/apps/community/notice/dto/PortalNoticeDTO.java @@ -1,5 +1,6 @@ package com.eactive.apim.portal.apps.community.notice.dto; +import com.eactive.apim.portal.djb.apistatus.dto.TimelineEntryDTO; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -8,11 +9,18 @@ import org.hibernate.validator.constraints.Length; import javax.validation.constraints.NotEmpty; import javax.validation.constraints.Size; import java.time.LocalDateTime; +import java.util.Collections; +import java.util.List; @Data @NoArgsConstructor @AllArgsConstructor public class PortalNoticeDTO { + + public static final String NOTICE_TYPE_NORMAL = "1"; + public static final String NOTICE_TYPE_MAINTENANCE = "2"; + public static final String NOTICE_TYPE_INCIDENT = "3"; + private String id; @NotEmpty(message = "제목을 입력해주세요.") @@ -29,9 +37,41 @@ public class PortalNoticeDTO { private boolean useYn; + private String fixYn; + + private String noticeType; + private String inquirerName; private LocalDateTime createdDate; private LocalDateTime lastModifiedDate; + + // ───── 장애/점검 연동 필드 ───── + private String summary; + private LocalDateTime startedAt; + private LocalDateTime endAt; + private String state; + private String previousState; + private List affectedApis = Collections.emptyList(); + /** 개발자포탈에 게시되지 않아 개별 노출하지 않는 GW 인터페이스 건수 */ + private int hiddenApiCount; + /** 장애 처리 타임라인. 최신순(내림차순), 공개(visibleYn='Y') 항목만 담는다. */ + private List timeline = Collections.emptyList(); + + public boolean hasTimeline() { + return timeline != null && !timeline.isEmpty(); + } + + public boolean isIncidentType() { + return NOTICE_TYPE_INCIDENT.equals(noticeType); + } + + public boolean isMaintenanceType() { + return NOTICE_TYPE_MAINTENANCE.equals(noticeType); + } + + public boolean isIncidentOrMaintenance() { + return isIncidentType() || isMaintenanceType(); + } } \ No newline at end of file diff --git a/src/main/java/com/eactive/apim/portal/apps/community/notice/repository/PortalNoticeRepository.java b/src/main/java/com/eactive/apim/portal/apps/community/notice/repository/PortalNoticeRepository.java index e0a65bb..3b96464 100644 --- a/src/main/java/com/eactive/apim/portal/apps/community/notice/repository/PortalNoticeRepository.java +++ b/src/main/java/com/eactive/apim/portal/apps/community/notice/repository/PortalNoticeRepository.java @@ -2,11 +2,18 @@ package com.eactive.apim.portal.apps.community.notice.repository; import com.eactive.apim.portal.portalNotice.entity.PortalNotice; import com.eactive.eai.rms.data.EMSDataSource; +import java.util.Collection; +import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; @EMSDataSource public interface PortalNoticeRepository extends JpaRepository, JpaSpecificationExecutor { + /** + * 게시 중인 공지만 골라 한 번에 읽는다. API Status 카드가 연결 공지 본문을 붙일 때 사용한다. + * 미게시(USE_YN='N')·삭제된 공지는 결과에서 자연히 빠진다. + */ + List findByIdInAndUseYn(Collection ids, String useYn); } diff --git a/src/main/java/com/eactive/apim/portal/apps/community/notice/service/PortalNoticeFacadeImpl.java b/src/main/java/com/eactive/apim/portal/apps/community/notice/service/PortalNoticeFacadeImpl.java index 1a8f6f0..992d5a9 100644 --- a/src/main/java/com/eactive/apim/portal/apps/community/notice/service/PortalNoticeFacadeImpl.java +++ b/src/main/java/com/eactive/apim/portal/apps/community/notice/service/PortalNoticeFacadeImpl.java @@ -1,8 +1,15 @@ package com.eactive.apim.portal.apps.community.notice.service; +import com.eactive.apim.portal.apps.community.notice.dto.IncidentAffectedApiDTO; import com.eactive.apim.portal.apps.community.notice.dto.PortalNoticeDTO; import com.eactive.apim.portal.apps.community.notice.dto.PortalNoticeSearch; import com.eactive.apim.portal.apps.community.notice.mapper.PortalNoticeMapper; +import com.eactive.apim.portal.djb.apistatus.incident.entity.DjbApistatusIncident; +import com.eactive.apim.portal.djb.apistatus.incident.entity.DjbApistatusIncidentApi; +import com.eactive.apim.portal.djb.apistatus.service.ApiStatusAssembler; +import com.eactive.apim.portal.djb.apistatus.service.ApiStatusCatalogService; +import com.eactive.apim.portal.djb.apistatus.incident.repository.DjbApistatusIncidentApiRepository; +import com.eactive.apim.portal.djb.apistatus.incident.repository.DjbApistatusIncidentRepository; import com.eactive.apim.portal.portalNotice.entity.PortalNotice; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; @@ -12,7 +19,12 @@ import org.springframework.data.domain.Sort; import org.springframework.data.jpa.domain.Specification; import org.springframework.stereotype.Service; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; @Service("portalNoticeFacade") @RequiredArgsConstructor @@ -20,12 +32,15 @@ public class PortalNoticeFacadeImpl implements PortalNoticeFacade { private final PortalNoticeService portalNoticeService; private final PortalNoticeMapper portalNoticeMapper; + private final DjbApistatusIncidentRepository incidentRepository; + private final DjbApistatusIncidentApiRepository incidentApiRepository; + private final ApiStatusAssembler apiStatusAssembler; + private final ApiStatusCatalogService apiStatusCatalogService; @Override public List getLatestNotices() { PortalNoticeSearch search = new PortalNoticeSearch(); Pageable pageable = PageRequest.of(0, 3, Sort.by(Sort.Direction.DESC, "createdDate")); -// Pageable pageable = Pageable.ofSize(3); Page notices = getNotices(search, pageable); return notices.getContent(); } @@ -36,13 +51,67 @@ public class PortalNoticeFacadeImpl implements PortalNoticeFacade { Specification spec = Specification.where(search.buildSpecification()) .and((root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("useYn"),"Y")); - Page noticesPage = portalNoticeService.getNotices(spec, pageable); + // 상단고정(fixYn='Y') 우선 정렬 — 호출자의 sort 앞에 결합 (admin Service 와 일관) + Sort fixYnFirst = Sort.by(Sort.Direction.DESC, "fixYn"); + Pageable fixedPageable = PageRequest.of( + pageable.getPageNumber(), + pageable.getPageSize(), + fixYnFirst.and(pageable.getSort()) + ); + + Page noticesPage = portalNoticeService.getNotices(spec, fixedPageable); return noticesPage.map(portalNoticeMapper::map); } @Override public PortalNoticeDTO getNotice(String id) { PortalNotice portalNotice = portalNoticeService.getNotice(id); - return portalNoticeMapper.map(portalNotice); + PortalNoticeDTO dto = portalNoticeMapper.map(portalNotice); + populateIncident(dto); + return dto; + } + + private void populateIncident(PortalNoticeDTO dto) { + if (!dto.isIncidentOrMaintenance()) { + dto.setAffectedApis(Collections.emptyList()); + dto.setTimeline(Collections.emptyList()); + return; + } + Optional incidentOpt = incidentRepository.findByNoticeId(dto.getId()); + if (!incidentOpt.isPresent()) { + dto.setAffectedApis(Collections.emptyList()); + dto.setTimeline(Collections.emptyList()); + return; + } + DjbApistatusIncident incident = incidentOpt.get(); + dto.setSummary(incident.getSummary()); + dto.setStartedAt(incident.getStartedAt()); + dto.setEndAt(incident.getEndAt()); + dto.setState(incident.getState() == null ? null : incident.getState().name()); + dto.setPreviousState(incident.getPreviousState() == null ? null : incident.getPreviousState().name()); + + // 영향 인터페이스 중 개발자포탈에 게시된 API 만 개별 노출한다. + // 나머지 GW 인터페이스는 이름·ID 를 감추고 건수로만 알린다. + Map visibleNames = apiStatusCatalogService.getVisibleApiNames(); + List apis = new ArrayList<>(); + int hiddenCount = 0; + for (DjbApistatusIncidentApi api : + incidentApiRepository.findByIncidentIdOrderByApiId(incident.getIncidentId())) { + String publishedName = visibleNames.get(api.getApiId()); + if (publishedName == null) { + hiddenCount++; + continue; + } + apis.add(new IncidentAffectedApiDTO(api.getApiId(), publishedName)); + } + dto.setAffectedApis(apis); + dto.setHiddenApiCount(hiddenCount); + + // 장애·지연만 타임라인을 붙인다 (점검은 타임라인을 쌓지 않음 — ADR-F15) + boolean degrading = incident.getKind() != null && incident.getKind().isDegrading(); + dto.setTimeline(degrading + ? apiStatusAssembler.loadTimelines(Collections.singletonList(incident.getIncidentId())) + .getOrDefault(incident.getIncidentId(), Collections.emptyList()) + : Collections.emptyList()); } } diff --git a/src/main/java/com/eactive/apim/portal/apps/community/partnership/controller/PartnershipApplicationController.java b/src/main/java/com/eactive/apim/portal/apps/community/partnership/controller/PartnershipApplicationController.java index 4b053a5..0823b20 100644 --- a/src/main/java/com/eactive/apim/portal/apps/community/partnership/controller/PartnershipApplicationController.java +++ b/src/main/java/com/eactive/apim/portal/apps/community/partnership/controller/PartnershipApplicationController.java @@ -30,7 +30,7 @@ public class PartnershipApplicationController { @GetMapping public String newPartnershipApplicationForm(Model model, HttpServletRequest request) { if (!SecurityUtil.isAuthenticated()) { - return "redirect:/login?redirect=/partnership"; + return "redirect:/login?reason=auth&redirect=/partnership"; } String referer = request.getHeader("Referer"); @@ -40,6 +40,7 @@ public class PartnershipApplicationController { model.addAttribute("partnership", new PartnershipApplicationDTO()); model.addAttribute("isInternalUser", UserTypeUtil.isCurrentUserInternal()); + model.addAttribute("recentApplications", partnershipApplicationFacade.getMyRecentApplications()); return "apps/community/mainPartnershipForm"; } @@ -54,7 +55,7 @@ public class PartnershipApplicationController { partnershipApplicationFacade.createPartnershipApplication(partnershipApplicationDTO); // 성공 메시지 추가 - redirectAttributes.addFlashAttribute("success", "사업제휴신청이 완료되었습니다."); + redirectAttributes.addFlashAttribute("success", "피드백/개선요청 등록이 완료되었습니다."); httpServletRequest.getSession().removeAttribute("previousPage"); return "redirect:/partnership"; diff --git a/src/main/java/com/eactive/apim/portal/apps/community/partnership/dto/PartnershipApplicationSummaryDTO.java b/src/main/java/com/eactive/apim/portal/apps/community/partnership/dto/PartnershipApplicationSummaryDTO.java new file mode 100644 index 0000000..74d161c --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/community/partnership/dto/PartnershipApplicationSummaryDTO.java @@ -0,0 +1,23 @@ +package com.eactive.apim.portal.apps.community.partnership.dto; + +import java.time.LocalDateTime; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 작성폼 상단 "내가 작성한 최근 글" 아코디언에 노출하기 위한 조회 전용 DTO. + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class PartnershipApplicationSummaryDTO { + + private String id; + + private String bizSubject; + + private String bizDetail; + + private LocalDateTime createdDate; +} diff --git a/src/main/java/com/eactive/apim/portal/apps/community/partnership/mapper/PartnershipApplicationMapper.java b/src/main/java/com/eactive/apim/portal/apps/community/partnership/mapper/PartnershipApplicationMapper.java index cf45014..8d7e950 100644 --- a/src/main/java/com/eactive/apim/portal/apps/community/partnership/mapper/PartnershipApplicationMapper.java +++ b/src/main/java/com/eactive/apim/portal/apps/community/partnership/mapper/PartnershipApplicationMapper.java @@ -2,8 +2,10 @@ package com.eactive.apim.portal.apps.community.partnership.mapper; import com.eactive.apim.portal.apps.community.partnership.dto.PartnershipApplicationDTO; +import com.eactive.apim.portal.apps.community.partnership.dto.PartnershipApplicationSummaryDTO; import com.eactive.apim.portal.common.mapper.CommonMapper; import com.eactive.apim.portal.partnershipapplication.entity.PartnershipApplication; +import java.util.List; import org.mapstruct.Mapper; import org.mapstruct.ReportingPolicy; import org.springframework.stereotype.Component; @@ -16,4 +18,8 @@ import org.springframework.stereotype.Component; public interface PartnershipApplicationMapper { PartnershipApplication toEntity(PartnershipApplicationDTO dto); + + PartnershipApplicationSummaryDTO toSummaryDto(PartnershipApplication entity); + + List toSummaryDtoList(List entities); } diff --git a/src/main/java/com/eactive/apim/portal/apps/community/partnership/repository/PartnershipApplicationRepository.java b/src/main/java/com/eactive/apim/portal/apps/community/partnership/repository/PartnershipApplicationRepository.java index 726f4af..ada77dd 100644 --- a/src/main/java/com/eactive/apim/portal/apps/community/partnership/repository/PartnershipApplicationRepository.java +++ b/src/main/java/com/eactive/apim/portal/apps/community/partnership/repository/PartnershipApplicationRepository.java @@ -2,6 +2,7 @@ package com.eactive.apim.portal.apps.community.partnership.repository; import com.eactive.apim.portal.partnershipapplication.entity.PartnershipApplication; import com.eactive.eai.rms.data.EMSDataSource; +import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.stereotype.Repository; @@ -9,4 +10,10 @@ import org.springframework.stereotype.Repository; @Repository @EMSDataSource public interface PartnershipApplicationRepository extends JpaRepository, JpaSpecificationExecutor { + + /** + * 특정 작성자(createdBy)가 등록한 최근 3건을 최신순으로 조회한다. + * createdBy 는 PersonalDataEncryptConverter 로 결정적 암호화되므로 평문 사용자 id 로 등가 조회가 가능하다. + */ + List findTop3ByCreatedByOrderByCreatedDateDesc(String createdBy); } diff --git a/src/main/java/com/eactive/apim/portal/apps/community/partnership/service/PartnershipApplicationFacade.java b/src/main/java/com/eactive/apim/portal/apps/community/partnership/service/PartnershipApplicationFacade.java index 4d2289d..70d6cef 100644 --- a/src/main/java/com/eactive/apim/portal/apps/community/partnership/service/PartnershipApplicationFacade.java +++ b/src/main/java/com/eactive/apim/portal/apps/community/partnership/service/PartnershipApplicationFacade.java @@ -1,9 +1,16 @@ package com.eactive.apim.portal.apps.community.partnership.service; import com.eactive.apim.portal.apps.community.partnership.dto.PartnershipApplicationDTO; +import com.eactive.apim.portal.apps.community.partnership.dto.PartnershipApplicationSummaryDTO; import java.io.IOException; +import java.util.List; public interface PartnershipApplicationFacade { void createPartnershipApplication(PartnershipApplicationDTO partnershipApplicationDTO) throws IOException; + + /** + * 현재 로그인 사용자가 작성한 최근 3건을 조회한다. 미인증이면 빈 목록. + */ + List getMyRecentApplications(); } diff --git a/src/main/java/com/eactive/apim/portal/apps/community/partnership/service/PartnershipApplicationFacadeImpl.java b/src/main/java/com/eactive/apim/portal/apps/community/partnership/service/PartnershipApplicationFacadeImpl.java index 0cc7d4b..3272959 100644 --- a/src/main/java/com/eactive/apim/portal/apps/community/partnership/service/PartnershipApplicationFacadeImpl.java +++ b/src/main/java/com/eactive/apim/portal/apps/community/partnership/service/PartnershipApplicationFacadeImpl.java @@ -1,18 +1,25 @@ package com.eactive.apim.portal.apps.community.partnership.service; import com.eactive.apim.portal.apps.community.partnership.dto.PartnershipApplicationDTO; +import com.eactive.apim.portal.apps.community.partnership.dto.PartnershipApplicationSummaryDTO; import com.eactive.apim.portal.apps.community.partnership.mapper.PartnershipApplicationMapper; import com.eactive.apim.portal.common.user.PortalAuthenticatedUser; import com.eactive.apim.portal.common.util.SecurityUtil; import com.eactive.apim.portal.common.util.UserTypeUtil; +import com.eactive.apim.portal.djb.swing.SwingNotifier; import com.eactive.apim.portal.file.entity.FileInfo; import com.eactive.apim.portal.file.service.FileService; import com.eactive.apim.portal.file.service.FileTypeContext; import com.eactive.apim.portal.partnershipapplication.entity.PartnershipApplication; +import com.eactive.apim.portal.template.entity.MessageCode; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; @Service @RequiredArgsConstructor @@ -21,13 +28,15 @@ public class PartnershipApplicationFacadeImpl implements PartnershipApplicationF private final PartnershipApplicationService partnershipApplicationService; private final PartnershipApplicationMapper partnershipApplicationMapper; private final FileService fileService; + // portal-admin 알림 발행기(범용). Q&A 등록 알림과 동일 컴포넌트를 재사용한다. + private final SwingNotifier swingNotifier; @Override public void createPartnershipApplication(PartnershipApplicationDTO partnershipApplicationDTO) throws IOException { FileInfo file = null; - if (!partnershipApplicationDTO.getFiles().isEmpty()) { + if (partnershipApplicationDTO.getFiles() != null && !partnershipApplicationDTO.getFiles().isEmpty()) { try { PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser(); FileService.setInternalUserContext(UserTypeUtil.isInternalUser(user)); @@ -35,6 +44,7 @@ public class PartnershipApplicationFacadeImpl implements PartnershipApplicationF FileTypeContext.setFileType("business"); file = fileService.createFile(partnershipApplicationDTO.getFiles()); } finally { + FileTypeContext.clear(); FileService.clearInternalUserContext(); } } @@ -43,5 +53,25 @@ public class PartnershipApplicationFacadeImpl implements PartnershipApplicationF partnershipApplication.setFileId(file.getFileId()); } partnershipApplicationService.createPartnershipApplication(partnershipApplication); + + // 개선요청/제휴 게시물 등록 시 portal-admin 에게 알림 (INQUIRY_CREATED 와 동일 패턴) + PortalAuthenticatedUser writer = SecurityUtil.getPortalAuthenticatedUser(); + Map params = new HashMap<>(); + params.put("partnershipId", partnershipApplication.getId()); + params.put("bizSubject", partnershipApplication.getBizSubject()); + if (writer != null) { + params.put("writerName", writer.getUserName()); + } + swingNotifier.notifyPortalAdmins(MessageCode.PARTNERSHIP_CREATED, params); + } + + @Override + public List getMyRecentApplications() { + PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser(); + if (user == null) { + return Collections.emptyList(); + } + List recent = partnershipApplicationService.findRecentByCreatedBy(user.getId()); + return partnershipApplicationMapper.toSummaryDtoList(recent); } } diff --git a/src/main/java/com/eactive/apim/portal/apps/community/partnership/service/PartnershipApplicationService.java b/src/main/java/com/eactive/apim/portal/apps/community/partnership/service/PartnershipApplicationService.java index 8f76844..35fcb7a 100644 --- a/src/main/java/com/eactive/apim/portal/apps/community/partnership/service/PartnershipApplicationService.java +++ b/src/main/java/com/eactive/apim/portal/apps/community/partnership/service/PartnershipApplicationService.java @@ -2,6 +2,7 @@ package com.eactive.apim.portal.apps.community.partnership.service; import com.eactive.apim.portal.apps.community.partnership.repository.PartnershipApplicationRepository; import com.eactive.apim.portal.partnershipapplication.entity.PartnershipApplication; +import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -21,4 +22,12 @@ public class PartnershipApplicationService { public void createPartnershipApplication(PartnershipApplication partnershipApplication) { partnershipApplicationRepository.save(partnershipApplication); } + + /** + * 작성자 id 로 최근 등록 3건 조회(최신순). + */ + @Transactional(readOnly = true) + public List findRecentByCreatedBy(String createdBy) { + return partnershipApplicationRepository.findTop3ByCreatedByOrderByCreatedDateDesc(createdBy); + } } diff --git a/src/main/java/com/eactive/apim/portal/apps/community/qna/controller/InquiryController.java b/src/main/java/com/eactive/apim/portal/apps/community/qna/controller/InquiryController.java index 5e99a18..4dbbb02 100644 --- a/src/main/java/com/eactive/apim/portal/apps/community/qna/controller/InquiryController.java +++ b/src/main/java/com/eactive/apim/portal/apps/community/qna/controller/InquiryController.java @@ -6,17 +6,24 @@ import com.eactive.apim.portal.apps.community.qna.service.InquiryFacade; import com.eactive.apim.portal.common.user.PortalAuthenticatedUser; import com.eactive.apim.portal.common.util.SecurityUtil; import com.eactive.apim.portal.common.util.UserTypeUtil; +import com.eactive.apim.portal.djb.community.qna.comment.service.InquiryCommentFacade; +import com.eactive.apim.portal.qna.entity.VisibilityScope; import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.web.PageableDefault; +import org.springframework.http.ResponseEntity; import org.springframework.security.access.annotation.Secured; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import org.springframework.web.servlet.mvc.support.RedirectAttributes; import javax.validation.Valid; @@ -29,9 +36,12 @@ public class InquiryController { public static final String APPS_COMMUNITY_MAIN_INQUIRY_FORM = "apps/community/mainInquiryForm"; public static final String REDIRECT_INQUIRY = "redirect:/inquiry"; private final InquiryFacade inquiryFacade; + private final InquiryCommentFacade inquiryCommentFacade; - public InquiryController(InquiryFacade inquiryFacade) { + public InquiryController(InquiryFacade inquiryFacade, + InquiryCommentFacade inquiryCommentFacade) { this.inquiryFacade = inquiryFacade; + this.inquiryCommentFacade = inquiryCommentFacade; } /** @@ -42,8 +52,14 @@ public class InquiryController { Model model) { PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser(); Page inquiries = inquiryFacade.getInquiries(search, pageable, user); + List ids = inquiries.getContent().stream() + .map(InquiryDTO::getId) + .collect(Collectors.toList()); + Map commentCounts = inquiryCommentFacade.countActiveByInquiryIds(ids); model.addAttribute("inquiries", inquiries.getContent()); model.addAttribute("page", inquiries); + model.addAttribute("commentCounts", commentCounts); + model.addAttribute("visibilityCeiling", inquiryFacade.getVisibilityCeiling()); return "apps/community/mainInquiryList"; } @@ -57,18 +73,31 @@ public class InquiryController { return REDIRECT_INQUIRY; } - InquiryDTO inquiry = inquiryFacade.getMyInquiry(SecurityUtil.getPortalAuthenticatedUser(), id); + InquiryDTO inquiry = inquiryFacade.getAccessibleInquiry(SecurityUtil.getPortalAuthenticatedUser(), id); model.addAttribute("inquiry", inquiry); + model.addAttribute("commentWritable", !"CLOSED".equals(inquiry.getInquiryStatus())); return "apps/community/mainInquiryDetail"; } + /** + * 2-1. 조회수 증가 (sessionStorage dedup 통과 시 JS 가 POST 호출) + */ + @PostMapping("/{id}/view") + public ResponseEntity increaseViewCount(@PathVariable String id) { + inquiryFacade.incrementViewCount(SecurityUtil.getPortalAuthenticatedUser(), id); + return ResponseEntity.ok().build(); + } + /** * 3. inquiry 등록 페이지 */ @GetMapping("/new") public String newInquiryForm(Model model) { - model.addAttribute("inquiry", new InquiryDTO()); + InquiryDTO inquiry = new InquiryDTO(); + inquiry.setVisibility(VisibilityScope.ORG.name()); // 기본 공개범위: 법인공개 + model.addAttribute("inquiry", inquiry); model.addAttribute("isInternalUser", UserTypeUtil.isCurrentUserInternal()); + addVisibilityOptions(model); return APPS_COMMUNITY_MAIN_INQUIRY_FORM; } @@ -80,14 +109,23 @@ public class InquiryController { InquiryDTO inquiry = inquiryFacade.getMyInquiry(SecurityUtil.getPortalAuthenticatedUser(), id); model.addAttribute("inquiry", inquiry); model.addAttribute("isInternalUser", UserTypeUtil.isCurrentUserInternal()); + addVisibilityOptions(model); return APPS_COMMUNITY_MAIN_INQUIRY_FORM; } + /** 공개범위 상한에 따른 폼 옵션 노출 플래그. PRIVATE 는 항상 허용. */ + private void addVisibilityOptions(Model model) { + VisibilityScope ceiling = inquiryFacade.getVisibilityCeiling(); + model.addAttribute("allowAll", ceiling.width() >= VisibilityScope.ALL.width()); + model.addAttribute("allowOrg", ceiling.width() >= VisibilityScope.ORG.width()); + } + /** * 5. inquiry 등록 요청 */ @PostMapping public String createInquiry(@Valid @ModelAttribute InquiryDTO inquiryDTO, BindingResult bindingResult, + @RequestParam(value = "image", required = false) MultipartFile image, RedirectAttributes redirectAttributes, Model model) throws IOException { if (bindingResult.hasErrors()) { @@ -95,7 +133,7 @@ public class InquiryController { return APPS_COMMUNITY_MAIN_INQUIRY_FORM; } - inquiryFacade.createInquiry(inquiryDTO); + inquiryFacade.createInquiry(inquiryDTO, image); redirectAttributes.addFlashAttribute("success", "Q&A 작성이 완료되었습니다."); return REDIRECT_INQUIRY; @@ -106,11 +144,12 @@ public class InquiryController { */ @PostMapping("/edit") public String updateInquiry(@Valid @ModelAttribute InquiryDTO inquiryDTO, BindingResult bindingResult, + @RequestParam(value = "image", required = false) MultipartFile image, RedirectAttributes redirectAttributes) throws IOException { if (bindingResult.hasErrors()) { return APPS_COMMUNITY_MAIN_INQUIRY_FORM; } - inquiryFacade.updateUserInquiry(SecurityUtil.getPortalAuthenticatedUser(), inquiryDTO.getId(), inquiryDTO); + inquiryFacade.updateUserInquiry(SecurityUtil.getPortalAuthenticatedUser(), inquiryDTO.getId(), inquiryDTO, image); redirectAttributes.addFlashAttribute("success", "Q&A 수정이 완료되었습니다."); return "redirect:/inquiry/detail?id=" + inquiryDTO.getId(); } diff --git a/src/main/java/com/eactive/apim/portal/apps/community/qna/dto/InquiryDTO.java b/src/main/java/com/eactive/apim/portal/apps/community/qna/dto/InquiryDTO.java index 63b11db..7125211 100644 --- a/src/main/java/com/eactive/apim/portal/apps/community/qna/dto/InquiryDTO.java +++ b/src/main/java/com/eactive/apim/portal/apps/community/qna/dto/InquiryDTO.java @@ -5,13 +5,10 @@ import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.hibernate.validator.constraints.NotEmpty; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.web.multipart.MultipartFile; import javax.validation.constraints.Pattern; import javax.validation.constraints.Size; import java.time.LocalDateTime; -import java.util.List; @Data @NoArgsConstructor @@ -28,7 +25,7 @@ public class InquiryDTO { @Size(max = 4000, message = "제목은 50자를 초과할 수 없습니다.") private String inquiryDetail; - @Pattern(regexp = "^(PENDING|IN_PROGRESS|CLOSED)$", message = "유효하지 않은 문의 상태입니다.") + @Pattern(regexp = "^(PENDING|REVIEWING|RESPONDED|CLOSED)$", message = "유효하지 않은 문의 상태입니다.") private String inquiryStatus; private String inquirerName; @@ -50,10 +47,26 @@ public class InquiryDTO { private LocalDateTime lastModifiedDate; - private List files; - private String attachFile; private String maskedInquirerName; + /** 목록 작성자 표기용 법인명(이름과 별도 줄 표기). */ + private String inquirerOrgName; + + /** 상세 작성자 표기용 "이름 (법인명)". */ + private String inquirerDisplay; + + /** 공개범위(ALL/ORG/PRIVATE). 미지정 시 서버에서 상한으로 clamp. */ + private String visibility; + + /** 공개범위 표기용 한글 라벨(전체공개/법인공개/비공개). */ + private String visibilityLabel; + + /** 조회수(표시용). */ + private long viewCount; + + /** 목록에서 비공개 게시물을 "비공개 게시물"로 흐리게 표기할지 여부(비-소유자). */ + private boolean privatePlaceholder; + } diff --git a/src/main/java/com/eactive/apim/portal/apps/community/qna/mapper/InquiryMapper.java b/src/main/java/com/eactive/apim/portal/apps/community/qna/mapper/InquiryMapper.java index 3aaa400..1c09761 100644 --- a/src/main/java/com/eactive/apim/portal/apps/community/qna/mapper/InquiryMapper.java +++ b/src/main/java/com/eactive/apim/portal/apps/community/qna/mapper/InquiryMapper.java @@ -14,6 +14,9 @@ import org.springframework.stereotype.Component; @Component public interface InquiryMapper { + // visibility(공개범위)는 facade 에서 상한 clamp 후 수동 세팅, viewCount 는 서버가 관리 + @Mapping(target = "visibility", ignore = true) + @Mapping(target = "viewCount", ignore = true) Inquiry toEntity(InquiryDTO inquiry); @Mapping(target = "inquirerName", source = "inquirer.userName") diff --git a/src/main/java/com/eactive/apim/portal/apps/community/qna/service/InquiryFacade.java b/src/main/java/com/eactive/apim/portal/apps/community/qna/service/InquiryFacade.java index a2f3874..2fc8a67 100644 --- a/src/main/java/com/eactive/apim/portal/apps/community/qna/service/InquiryFacade.java +++ b/src/main/java/com/eactive/apim/portal/apps/community/qna/service/InquiryFacade.java @@ -3,21 +3,30 @@ package com.eactive.apim.portal.apps.community.qna.service; import com.eactive.apim.portal.apps.community.qna.dto.InquiryDTO; import com.eactive.apim.portal.apps.community.qna.dto.InquirySearch; import com.eactive.apim.portal.common.user.PortalAuthenticatedUser; +import com.eactive.apim.portal.qna.entity.VisibilityScope; import java.io.IOException; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.web.multipart.MultipartFile; public interface InquiryFacade { Page getInquiries(InquirySearch search, Pageable pageable, PortalAuthenticatedUser user); + /** 문의 공개범위 상한(폼 옵션 노출 제어용). */ + VisibilityScope getVisibilityCeiling(); + InquiryDTO getUserInquiry(PortalAuthenticatedUser user, String id); InquiryDTO getMyInquiry(PortalAuthenticatedUser user, String id); - void createInquiry(InquiryDTO inquiryDTO) throws IOException; + InquiryDTO getAccessibleInquiry(PortalAuthenticatedUser user, String id); - void updateUserInquiry(PortalAuthenticatedUser user, String id, InquiryDTO inquiryDTO) throws IOException; + void createInquiry(InquiryDTO inquiryDTO, MultipartFile image) throws IOException; + + void updateUserInquiry(PortalAuthenticatedUser user, String id, InquiryDTO inquiryDTO, MultipartFile image) throws IOException; + + void incrementViewCount(PortalAuthenticatedUser user, String id); void deleteUserInquiry(PortalAuthenticatedUser user, String id); } diff --git a/src/main/java/com/eactive/apim/portal/apps/community/qna/service/InquiryFacadeImpl.java b/src/main/java/com/eactive/apim/portal/apps/community/qna/service/InquiryFacadeImpl.java index 5b7af77..e675f1e 100644 --- a/src/main/java/com/eactive/apim/portal/apps/community/qna/service/InquiryFacadeImpl.java +++ b/src/main/java/com/eactive/apim/portal/apps/community/qna/service/InquiryFacadeImpl.java @@ -6,28 +6,42 @@ import com.eactive.apim.portal.apps.community.qna.mapper.InquiryMapper; import com.eactive.apim.portal.common.user.PortalAuthenticatedUser; import com.eactive.apim.portal.common.util.SecurityUtil; import com.eactive.apim.portal.common.util.StringMaskingUtil; -import com.eactive.apim.portal.common.util.UserTypeUtil; +import com.eactive.apim.portal.djb.swing.SwingNotifier; import com.eactive.apim.portal.file.entity.FileInfo; +import com.eactive.apim.portal.file.exception.InvalidFileException; import com.eactive.apim.portal.file.service.FileService; -import com.eactive.apim.portal.file.service.FileTypeContext; +import com.eactive.apim.portal.portalorg.entity.PortalOrg; +import com.eactive.apim.portal.portaluser.entity.PortalUser; import com.eactive.apim.portal.portaluser.entity.PortalUserEnums; import com.eactive.apim.portal.qna.entity.Inquiry; +import com.eactive.apim.portal.qna.entity.VisibilityScope; +import com.eactive.apim.portal.template.entity.MessageCode; import lombok.RequiredArgsConstructor; +import org.apache.commons.io.FilenameUtils; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.domain.Specification; import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; import java.io.IOException; +import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; @Service @RequiredArgsConstructor public class InquiryFacadeImpl implements InquiryFacade { - private final FileService fileService; + private static final Set IMAGE_EXTENSIONS = + new HashSet<>(Arrays.asList("jpg", "jpeg", "png", "gif")); + private final InquiryService inquiryService; private final InquiryMapper inquiryMapper; + private final SwingNotifier swingNotifier; + private final FileService fileService; @Override public Page getInquiries(InquirySearch search, Pageable pageable, PortalAuthenticatedUser user) { @@ -41,6 +55,8 @@ public class InquiryFacadeImpl implements InquiryFacade { Page inquiriesPage = inquiryService.getInquiries(spec, pageable); + VisibilityScope ceiling = inquiryService.getVisibilityCeiling(); + return inquiriesPage.map(inquiry -> { InquiryDTO dto = inquiryMapper.map(inquiry); if (dto != null && dto.getInquirer() != null) { @@ -49,8 +65,20 @@ public class InquiryFacadeImpl implements InquiryFacade { inquiry.getInquirer().getId(), user.getId() )); + PortalOrg org = inquiry.getInquirer().getPortalOrg(); + if (org != null) { + dto.setInquirerOrgName(org.getOrgName()); + } dto.getInquirer().setUserName(null); // 원본 데이터 제거 } + // 비공개 게시물은 본인/같은 법인 관리자 외에는 "비공개 게시물"로만 노출 + VisibilityScope effective = VisibilityScope.clampTo(inquiry.getVisibility(), ceiling); + dto.setVisibility(effective.name()); + dto.setVisibilityLabel(effective.label()); + if (effective == VisibilityScope.PRIVATE && !canReadPrivate(user, inquiry)) { + dto.setPrivatePlaceholder(true); + dto.setInquirySubject(null); + } return dto; }); } @@ -80,62 +108,129 @@ public class InquiryFacadeImpl implements InquiryFacade { } @Override - public void createInquiry(InquiryDTO inquiryDTO) throws IOException { - - FileInfo file = null; - if (!inquiryDTO.getFiles().isEmpty()) { - try { - PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser(); - FileService.setInternalUserContext(UserTypeUtil.isInternalUser(user)); - - FileTypeContext.setFileType("qna"); - file = fileService.createFile(inquiryDTO.getFiles()); - } finally { - FileService.clearInternalUserContext(); - } + public InquiryDTO getAccessibleInquiry(PortalAuthenticatedUser user, String id) { + Inquiry inquiry = inquiryService.getAccessibleInquiry(user, id); + InquiryDTO dto = inquiryMapper.map(inquiry); + if (dto != null) { + VisibilityScope effective = inquiryService.effectiveVisibility(inquiry); + dto.setVisibility(effective.name()); + dto.setVisibilityLabel(effective.label()); } - - Inquiry inquiry = inquiryMapper.toEntity(inquiryDTO); - if (file != null) { - inquiry.setAttachFile(file.getFileId()); + if (dto != null && inquiry.getInquirer() != null) { + // 상세 작성자 표기: "이름 (법인명)" — 본인은 owner-bypass 로 원문 + String maskedName = StringMaskingUtil.maskName( + inquiry.getInquirer().getUserName(), + inquiry.getInquirer().getId(), + user.getId()); + dto.setInquirerDisplay(withOrgName(maskedName, inquiry.getInquirer())); } - inquiry.setInquirer(SecurityUtil.getPortalAuthenticatedUser()); - inquiryService.createInquiry(inquiry); - - HashMap params = new HashMap<>(); - params.put("inquiryId", inquiry.getId()); - params.put("subject", inquiry.getInquirySubject()); - + return dto; } @Override - public void updateUserInquiry(PortalAuthenticatedUser user, String id, InquiryDTO inquiryDTO) throws IOException { - Inquiry existingInquiry = inquiryService.getMyInquiry(user, id); - - if (!inquiryDTO.getFiles().isEmpty() && !inquiryDTO.getFiles().get(0).isEmpty()) { - try { - FileService.setInternalUserContext(UserTypeUtil.isInternalUser(user)); - - FileTypeContext.setFileType("qna"); - FileInfo file = fileService.createFile(inquiryDTO.getFiles()); - if(file != null) { - inquiryDTO.setAttachFile(file.getFileId()); - } - } finally { - FileService.clearInternalUserContext(); - } - } else if (existingInquiry.getAttachFile() != null && inquiryDTO.getAttachFile() == null) { - fileService.deleteFile(inquiryDTO.getAttachFile()); - inquiryDTO.setAttachFile(null); + public void createInquiry(InquiryDTO inquiryDTO, MultipartFile image) throws IOException { + PortalAuthenticatedUser current = SecurityUtil.getPortalAuthenticatedUser(); + Inquiry inquiry = inquiryMapper.toEntity(inquiryDTO); + inquiry.setInquirer(current); + inquiry.setVisibility(clampedVisibility(inquiryDTO.getVisibility())); + if (hasFile(image)) { + inquiry.setAttachFile(storeImage(null, image)); } + inquiryService.createInquiry(inquiry); + + Map params = new HashMap<>(); + params.put("inquiryId", inquiry.getId()); + params.put("inquirySubject", inquiry.getInquirySubject()); + params.put("writerName", current.getUserName()); + swingNotifier.notifyPortalAdmins(MessageCode.INQUIRY_CREATED, params); + } + + @Override + public void updateUserInquiry(PortalAuthenticatedUser user, String id, InquiryDTO inquiryDTO, MultipartFile image) throws IOException { + Inquiry existingInquiry = inquiryService.getMyInquiry(user, id); Inquiry inquiry = inquiryMapper.toEntity(inquiryDTO); inquiry.setInquirer(existingInquiry.getInquirer()); + inquiry.setVisibility(clampedVisibility(inquiryDTO.getVisibility())); + // 이미지 교체 시 기존 fileId 로 업데이트, 없으면 기존 첨부 유지 + if (hasFile(image)) { + inquiry.setAttachFile(storeImage(existingInquiry.getAttachFile(), image)); + } else { + inquiry.setAttachFile(existingInquiry.getAttachFile()); + } inquiryService.updateInquiry(id, inquiry); } + @Override + public void incrementViewCount(PortalAuthenticatedUser user, String id) { + // 접근 가능한 게시물만 조회수 증가 + inquiryService.getAccessibleInquiry(user, id); + inquiryService.incrementViewCount(id); + } + + @Override + public VisibilityScope getVisibilityCeiling() { + return inquiryService.getVisibilityCeiling(); + } + @Override public void deleteUserInquiry(PortalAuthenticatedUser user, String id) { inquiryService.deleteMyInquiry(user, id); } + + // ===================================================================== + // helpers + // ===================================================================== + + /** 요청 공개범위를 기본 ORG 로 두고 property 상한으로 clamp. */ + private VisibilityScope clampedVisibility(String requested) { + VisibilityScope ceiling = inquiryService.getVisibilityCeiling(); + VisibilityScope scope = VisibilityScope.fromString(requested, VisibilityScope.ORG); + return VisibilityScope.clampTo(scope, ceiling); + } + + /** 비공개 게시물을 읽을 수 있는 사용자(작성자 본인 또는 같은 법인 관리자). */ + private boolean canReadPrivate(PortalAuthenticatedUser user, Inquiry inquiry) { + PortalUser inquirer = inquiry.getInquirer(); + if (inquirer == null || user == null) { + return false; + } + if (user.getId() != null && user.getId().equals(inquirer.getId())) { + return true; + } + return user.getRoleCode() == PortalUserEnums.RoleCode.ROLE_CORP_MANAGER + && isSameOrg(user, inquirer); + } + + private boolean isSameOrg(PortalAuthenticatedUser user, PortalUser inquirer) { + PortalOrg userOrg = user.getPortalOrg(); + PortalOrg inquirerOrg = inquirer.getPortalOrg(); + return userOrg != null && inquirerOrg != null + && userOrg.getId() != null + && userOrg.getId().equals(inquirerOrg.getId()); + } + + /** "이름 (법인명)" 조합. 법인명이 없으면 이름만. */ + private String withOrgName(String name, PortalUser inquirer) { + PortalOrg org = inquirer.getPortalOrg(); + if (org != null && org.getOrgName() != null && !org.getOrgName().isEmpty()) { + return name + " (" + org.getOrgName() + ")"; + } + return name; + } + + private boolean hasFile(MultipartFile file) { + return file != null && !file.isEmpty(); + } + + /** 이미지 확장자 검증 후 단일 파일 저장, fileId 반환. */ + private String storeImage(String existingFileId, MultipartFile image) throws IOException { + String ext = FilenameUtils.getExtension(image.getOriginalFilename()); + if (ext == null || !IMAGE_EXTENSIONS.contains(ext.toLowerCase())) { + throw new InvalidFileException("이미지 파일(jpg, jpeg, png, gif)만 첨부할 수 있습니다."); + } + FileInfo fileInfo = fileService.createOrUpdateSingleFile( + existingFileId, image, image.getOriginalFilename(), true); + return fileInfo.getFileId(); + } } diff --git a/src/main/java/com/eactive/apim/portal/apps/community/qna/service/InquiryService.java b/src/main/java/com/eactive/apim/portal/apps/community/qna/service/InquiryService.java index cf64989..4c3a587 100644 --- a/src/main/java/com/eactive/apim/portal/apps/community/qna/service/InquiryService.java +++ b/src/main/java/com/eactive/apim/portal/apps/community/qna/service/InquiryService.java @@ -4,7 +4,12 @@ package com.eactive.apim.portal.apps.community.qna.service; import com.eactive.apim.portal.apps.community.qna.repository.InquiryRepository; import com.eactive.apim.portal.common.exception.NotFoundException; import com.eactive.apim.portal.common.user.PortalAuthenticatedUser; +import com.eactive.apim.portal.portalorg.entity.PortalOrg; +import com.eactive.apim.portal.portaluser.entity.PortalUser; +import com.eactive.apim.portal.portaluser.entity.PortalUserEnums; +import com.eactive.apim.portal.portalproperty.service.PortalPropertyService; import com.eactive.apim.portal.qna.entity.Inquiry; +import com.eactive.apim.portal.qna.entity.VisibilityScope; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.domain.Specification; @@ -16,10 +21,19 @@ import org.springframework.transaction.annotation.Transactional; public class InquiryService { public static final String PENDING = "PENDING"; - private final InquiryRepository inquiryRepository; - public InquiryService(InquiryRepository inquiryRepository) { + /** 문의 공개범위 상한/기본값 property. */ + private static final String PROP_GROUP = "Portal"; + private static final String PROP_VISIBILITY_CEILING = "djb.inquiry.default-visibility"; + private static final String PROP_VISIBILITY_DESC = "Q&A 문의 공개범위 상한/기본값 (ALL/ORG/PRIVATE)"; + + private final InquiryRepository inquiryRepository; + private final PortalPropertyService portalPropertyService; + + public InquiryService(InquiryRepository inquiryRepository, + PortalPropertyService portalPropertyService) { this.inquiryRepository = inquiryRepository; + this.portalPropertyService = portalPropertyService; } /** @@ -72,6 +86,7 @@ public class InquiryService { inquiry.setInquirySubject(updatedInquiry.getInquirySubject()); inquiry.setInquiryDetail(updatedInquiry.getInquiryDetail()); inquiry.setAttachFile(updatedInquiry.getAttachFile()); + inquiry.setVisibility(updatedInquiry.getVisibility()); return inquiryRepository.save(inquiry); } @@ -90,5 +105,77 @@ public class InquiryService { return inquiryRepository.findByInquirerAndId(user, id).orElseThrow(() -> new NotFoundException("내 질문을 찾을 수 없습니다.")); } + /** + * 본인 또는 같은 법인 소속 이용자가 작성한 Q&A를 조회한다. + * - ROLE_USER: 본인 글만 접근 가능 + * - ROLE_CORP_*: 같은 법인 소속 이용자가 작성한 글까지 접근 가능 + */ + @Transactional(readOnly = true) + public Inquiry getAccessibleInquiry(PortalAuthenticatedUser user, String id) { + Inquiry inquiry = getInquiry(id); + if (!isAccessibleByUser(user, inquiry)) { + throw new NotFoundException("문의글을 찾을 수 없습니다. id=" + id); + } + return inquiry; + } + + private boolean isAccessibleByUser(PortalAuthenticatedUser user, Inquiry inquiry) { + PortalUser inquirer = inquiry.getInquirer(); + if (inquirer == null || user == null) { + return false; + } + // 작성자 본인은 공개범위와 무관하게 항상 접근 가능 + if (user.getId() != null && user.getId().equals(inquirer.getId())) { + return true; + } + VisibilityScope effective = effectiveVisibility(inquiry); + switch (effective) { + case ALL: + // 전체공개: 로그인 사용자면 접근 가능(@Secured 로 이미 인증됨) + return true; + case ORG: + return isSameOrg(user, inquirer); + case PRIVATE: + // 비공개: 본인 외에는 같은 법인 관리자만 열람 + return isSameOrg(user, inquirer) + && user.getRoleCode() == PortalUserEnums.RoleCode.ROLE_CORP_MANAGER; + default: + return false; + } + } + + private boolean isSameOrg(PortalAuthenticatedUser user, PortalUser inquirer) { + PortalOrg userOrg = user.getPortalOrg(); + PortalOrg inquirerOrg = inquirer.getPortalOrg(); + return userOrg != null && inquirerOrg != null + && userOrg.getId() != null + && userOrg.getId().equals(inquirerOrg.getId()); + } + + /** + * 게시물의 유효 공개범위 = 저장값을 property 상한으로 clamp 한 값. + * (property 가 항상 우선하므로 저장값이 옛 상한이라 넓더라도 재-clamp) + */ + @Transactional(readOnly = true) + public VisibilityScope effectiveVisibility(Inquiry inquiry) { + return VisibilityScope.clampTo(inquiry.getVisibility(), getVisibilityCeiling()); + } + + /** PTL_PROPERTY 에 설정된 공개범위 상한(없으면 ORG). */ + @Transactional(readOnly = true) + public VisibilityScope getVisibilityCeiling() { + String value = portalPropertyService.getOrCreateProperty( + PROP_GROUP, PROP_VISIBILITY_CEILING, VisibilityScope.ORG.name(), PROP_VISIBILITY_DESC); + return VisibilityScope.fromString(value, VisibilityScope.ORG); + } + + /** + * 조회수 1 증가. sessionStorage dedup 을 통과한 POST 요청에서만 호출된다. + */ + public void incrementViewCount(String id) { + Inquiry inquiry = getInquiry(id); + inquiry.increaseViewCount(); + inquiryRepository.save(inquiry); + } } diff --git a/src/main/java/com/eactive/apim/portal/apps/login/constants/LoginFailureReason.java b/src/main/java/com/eactive/apim/portal/apps/login/constants/LoginFailureReason.java new file mode 100644 index 0000000..dde05be --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/login/constants/LoginFailureReason.java @@ -0,0 +1,28 @@ +package com.eactive.apim.portal.apps.login.constants; + +/** + * 로그인 실패 사유 코드. PTL_USER_LOG.FAILURE_REASON 에 문자열(name())로 저장된다. + */ +public enum LoginFailureReason { + + /** 아이디(이메일) 미존재 */ + ID_NOT_FOUND, + /** 비밀번호 불일치 */ + PASSWORD_MISMATCH, + /** 계정 잠금(5회 실패 등) */ + ACCOUNT_LOCKED, + /** 비활성 계정(승인 대기/관리자 차단/법인 미승인) */ + ACCOUNT_DISABLED, + /** 세션 인증 오류(중복 로그인 등) */ + SESSION_AUTH, + /** 2차 인증 - 인증번호 유효시간 초과 */ + TWO_FACTOR_TIMEOUT, + /** 2차 인증 - 인증번호 불일치 */ + TWO_FACTOR_CODE_MISMATCH, + /** 2차 인증 - 사용자가 팝업을 닫아 취소 */ + TWO_FACTOR_CANCELLED, + /** 2차 인증 - 시도 횟수 초과 */ + TWO_FACTOR_ATTEMPT_EXCEEDED, + /** 분류 불가 */ + UNKNOWN +} diff --git a/src/main/java/com/eactive/apim/portal/apps/login/constants/LoginType.java b/src/main/java/com/eactive/apim/portal/apps/login/constants/LoginType.java new file mode 100644 index 0000000..31f42c3 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/login/constants/LoginType.java @@ -0,0 +1,14 @@ +package com.eactive.apim.portal.apps.login.constants; + +/** + * 로그인 유형 코드. PTL_USER_LOG.LOGIN_TYPE 에 문자열(name())로 저장된다. + */ +public enum LoginType { + + /** 일반 로그인 (2FA 미적용) */ + NORMAL, + /** 2차 인증을 통과한 로그인 */ + TWO_FACTOR, + /** 회원가입 직후 자동 로그인 (2FA 미적용) */ + SIGNUP_AUTO +} diff --git a/src/main/java/com/eactive/apim/portal/apps/login/controller/AccountRecoveryController.java b/src/main/java/com/eactive/apim/portal/apps/login/controller/AccountRecoveryController.java index c254ed9..6bb2216 100644 --- a/src/main/java/com/eactive/apim/portal/apps/login/controller/AccountRecoveryController.java +++ b/src/main/java/com/eactive/apim/portal/apps/login/controller/AccountRecoveryController.java @@ -152,25 +152,6 @@ public class AccountRecoveryController { } } - @PostMapping("/resend_verification_email") - public ResponseEntity> resendVerificationEmail(@RequestParam String loginId) { - Map response = new HashMap<>(); - - try { - portalUserAuthService.resendVerificationEmail(loginId); - response.put("success", "인증 이메일이 재발송되었습니다."); - return ResponseEntity.ok(response); - - } catch (IllegalArgumentException e) { - response.put("error", e.getMessage()); - return ResponseEntity.badRequest().body(response); - - } catch (Exception e) { - response.put("error", "이메일 발송 중 오류가 발생했습니다."); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response); - } - } - @GetMapping("/dormant_account") public String showDormantAccountPage(HttpSession session,Model model) { diff --git a/src/main/java/com/eactive/apim/portal/apps/login/controller/DuplicateLoginController.java b/src/main/java/com/eactive/apim/portal/apps/login/controller/DuplicateLoginController.java new file mode 100644 index 0000000..c07b708 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/login/controller/DuplicateLoginController.java @@ -0,0 +1,51 @@ +package com.eactive.apim.portal.apps.login.controller; + +import com.eactive.apim.portal.apps.login.service.DuplicateLoginService; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; +import java.util.HashMap; +import java.util.Map; + +/** + * 동시 접속(중복 세션) 확인 대기 상태(로그인 2FA off 경로)의 확정/취소 API. + * + *

대기 상태는 1차 인증(ID/PW) 성공 후 SuccessHandler 만 세팅하므로, 이 엔드포인트는 + * 비밀번호 검증을 통과한 세션에서만 의미가 있다. 모든 POST 는 세션 기반 + * CSRF(X-XSRF-TOKEN) 보호를 받는다.

+ */ +@RestController +@RequestMapping("/login/duplicate") +@RequiredArgsConstructor +public class DuplicateLoginController { + + private final DuplicateLoginService duplicateLoginService; + + /** 기존 접속 해제 확인 → 로그인 확정. 무효(만료/상태 변경) 시 재로그인 안내 */ + @PostMapping("/confirm") + public Map confirm(HttpServletRequest request, HttpSession session) { + Map result = new HashMap<>(); + String redirect = duplicateLoginService.confirm(request, session); + if (redirect != null) { + result.put("valid", true); + result.put("redirect", redirect); + } else { + result.put("valid", false); + result.put("message", "로그인 확인이 만료되었습니다. 다시 로그인해주세요."); + } + return result; + } + + /** 확인 취소 — 로그인 포기(익명 유지) */ + @PostMapping("/cancel") + public Map cancel(HttpSession session) { + duplicateLoginService.cancel(session); + Map result = new HashMap<>(); + result.put("valid", true); + return result; + } +} diff --git a/src/main/java/com/eactive/apim/portal/apps/login/controller/LoginHandler.java b/src/main/java/com/eactive/apim/portal/apps/login/controller/LoginHandler.java index 3ab9e52..ffb2f24 100644 --- a/src/main/java/com/eactive/apim/portal/apps/login/controller/LoginHandler.java +++ b/src/main/java/com/eactive/apim/portal/apps/login/controller/LoginHandler.java @@ -1,5 +1,7 @@ package com.eactive.apim.portal.apps.login.controller; +import com.eactive.apim.portal.apps.auth.twofactor.TwoFactorService; +import com.eactive.apim.portal.apps.login.service.DuplicateLoginService; import com.eactive.apim.portal.common.exception.PortalRedirectException; import com.eactive.apim.portal.common.pagerouter.PageHandler; import org.apache.commons.lang3.StringUtils; @@ -22,6 +24,13 @@ import static com.eactive.apim.portal.apps.login.constants.LoginConstants.LOGIN_ @Component("LoginHandler") public class LoginHandler implements PageHandler { + private final TwoFactorService twoFactorService; + private final DuplicateLoginService duplicateLoginService; + + public LoginHandler(TwoFactorService twoFactorService, DuplicateLoginService duplicateLoginService) { + this.twoFactorService = twoFactorService; + this.duplicateLoginService = duplicateLoginService; + } /** * 로그인 화면으로 들어간다 @@ -47,6 +56,27 @@ public class LoginHandler implements PageHandler { session.removeAttribute("loginId"); } + // 로그인 2FA 대기 상태면(1차 인증 통과 후) 추가 인증 팝업 자동 오픈 플래그를 내려준다. + // pending 중에는 아직 익명이므로 아래 인증자 리다이렉트에 걸리지 않는다. + boolean twoFactorPending = twoFactorService.hasPendingLogin(session); + model.addAttribute("twoFactorPending", twoFactorPending); + + // 동시 접속 안내 — 반드시 1차 인증 통과 후(2FA pending 또는 중복 확인 대기)에만 노출한다. + // - 2FA on: 확인 후 2FA 팝업 진행(취소 시 /auth/2fa/cancel) + // - 2FA off: 확인 후 /login/duplicate/confirm 으로 확정 + String pendingLoginId = null; + boolean duplicateConfirmPending = false; + if (twoFactorPending) { + pendingLoginId = (String) session.getAttribute(TwoFactorService.ATTR_PENDING_LOGIN_ID); + } else if (duplicateLoginService.hasPending(session) + && "1".equals(httpRequest.getParameter("duplicate"))) { + pendingLoginId = duplicateLoginService.pendingLoginId(session); + duplicateConfirmPending = true; + } + model.addAttribute("duplicateConfirmPending", duplicateConfirmPending); + model.addAttribute("duplicateInfo", + pendingLoginId != null ? duplicateLoginService.activeSessionInfo(pendingLoginId) : null); + // 이미 인증된 사용자인지 확인 Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication != null && !"anonymousUser".equalsIgnoreCase(authentication.getPrincipal().toString())) { diff --git a/src/main/java/com/eactive/apim/portal/apps/login/service/DuplicateLoginService.java b/src/main/java/com/eactive/apim/portal/apps/login/service/DuplicateLoginService.java new file mode 100644 index 0000000..9a03356 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/login/service/DuplicateLoginService.java @@ -0,0 +1,185 @@ +package com.eactive.apim.portal.apps.login.service; + +import com.eactive.apim.portal.apps.login.constants.LoginType; +import com.eactive.apim.portal.apps.session.entity.UserSession; +import com.eactive.apim.portal.apps.session.service.UserSessionService; +import com.eactive.apim.portal.apps.user.service.PortalUserAuthService; +import com.eactive.apim.portal.common.user.PortalAuthenticatedUser; +import com.eactive.apim.portal.portalorg.entity.PortalOrgEnums; +import com.eactive.apim.portal.portaluser.entity.PortalUser; +import com.eactive.apim.portal.portaluser.entity.PortalUserEnums; +import com.eactive.apim.portal.portaluser.repository.PortalUserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +/** + * 로그인 시 동시 접속(중복 세션) 확인 처리. + * + *

중복 확인은 반드시 1차 인증(ID/PW) 성공 후에만 수행한다. 비밀번호 검증 전에 + * 노출하면 임의 계정의 접속 여부·IP 가 인증 없이 조회되는 정보 노출이 된다(기존 + * {@code /api/session/check-duplicate} 사전 체크 방식의 문제).

+ * + *

두 경로에서 쓰인다: + *

    + *
  • 로그인 2FA on — 2FA pending 상태의 로그인 페이지가 {@link #activeSessionInfo(String)} + * 로 안내 정보를 내려주고, 확인 후 2FA 팝업으로 진행(취소 시 {@code /auth/2fa/cancel}).
  • + *
  • 로그인 2FA off — SuccessHandler 가 확정을 보류하고 {@link #begin} 으로 대기 상태 전환. + * 사용자가 확인하면 {@link #confirm} 이 인증을 확정한다(기존 세션은 + * {@link LoginFinalizer#finalizeLogin} 의 forceLogoutOtherSessions 로 해제).
  • + *

+ */ +@Service +@Transactional +@RequiredArgsConstructor +public class DuplicateLoginService { + + /** 동시 접속 확인 대기 - 대상 사용자 id (2FA off 경로) */ + public static final String ATTR_PENDING_USER_ID = "DUP_PENDING_USER_ID"; + /** 동시 접속 확인 대기 - loginId */ + public static final String ATTR_PENDING_LOGIN_ID = "DUP_PENDING_LOGIN_ID"; + /** 동시 접속 확인 대기 - 진입 시각 */ + public static final String ATTR_PENDING_AT = "DUP_PENDING_AT"; + + /** 확인 대기 유효시간(초). 초과 시 처음부터 재로그인 */ + public static final int PENDING_TTL_SECONDS = 120; + + private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + private final UserSessionService userSessionService; + private final PortalUserRepository portalUserRepository; + private final PortalUserAuthService portalUserAuthService; + private final LoginFinalizer loginFinalizer; + + /** 해당 계정의 활성 세션(다른 곳 접속) 존재 여부 */ + @Transactional(readOnly = true) + public boolean hasActiveSession(String loginId) { + return activeSession(loginId).isPresent(); + } + + /** + * 활성 세션 안내 정보(마스킹 IP·접속 시각). 없으면 null. + * 로그인 페이지 확인 팝업 표시용 — 1차 인증 통과 후에만 호출해야 한다. + */ + @Transactional(readOnly = true) + public Map activeSessionInfo(String loginId) { + Optional active = activeSession(loginId); + if (!active.isPresent()) { + return null; + } + Map info = new HashMap<>(); + info.put("ipAddress", maskIpAddress(active.get().getIpAddress())); + info.put("loginTime", active.get().getLoginTime().format(TIME_FORMATTER)); + return info; + } + + // ========================================================================= + // 2FA off 경로: 확정 보류 → 확인 → 확정 + // ========================================================================= + + /** 1차 인증 성공 사용자를 동시 접속 확인 대기 상태로 세팅한다. (SecurityContext 클리어는 호출부 책임) */ + public void begin(HttpSession session, PortalUser user) { + session.setAttribute(ATTR_PENDING_USER_ID, user.getId()); + session.setAttribute(ATTR_PENDING_LOGIN_ID, user.getLoginId()); + session.setAttribute(ATTR_PENDING_AT, LocalDateTime.now()); + } + + public boolean hasPending(HttpSession session) { + return session != null && session.getAttribute(ATTR_PENDING_USER_ID) != null; + } + + public String pendingLoginId(HttpSession session) { + return session == null ? null : (String) session.getAttribute(ATTR_PENDING_LOGIN_ID); + } + + /** + * 동시 접속 확인 후 로그인 확정. 대기 상태가 유효하면 인증을 세팅하고 최종 이동 URL 을 + * 반환한다(기존 세션 해제 포함). 무효(만료/상태 변경)면 null — 재로그인 필요. + */ + public String confirm(HttpServletRequest request, HttpSession session) { + String userId = (String) session.getAttribute(ATTR_PENDING_USER_ID); + String loginId = (String) session.getAttribute(ATTR_PENDING_LOGIN_ID); + Object at = session.getAttribute(ATTR_PENDING_AT); + cancel(session); // 1회용 — 성공/실패 무관하게 대기 상태는 소멸 + + if (userId == null || !(at instanceof LocalDateTime) + || ((LocalDateTime) at).plusSeconds(PENDING_TTL_SECONDS).isBefore(LocalDateTime.now())) { + return null; + } + + PortalUser user = portalUserRepository.findById(userId).orElse(null); + if (user == null || !isLoginStillAllowed(user)) { + return null; + } + + // 프로그래매틱 인증 확정 (요청 종료 시 SecurityContextPersistenceFilter 가 세션에 저장) + PortalAuthenticatedUser authUser = portalUserAuthService.buildAuthenticatedUser(user); + UsernamePasswordAuthenticationToken token = + new UsernamePasswordAuthenticationToken(authUser, null, authUser.getAuthorities()); + token.setDetails(authUser); + SecurityContextHolder.getContext().setAuthentication(token); + + return loginFinalizer.finalizeLogin(user, loginId, request, LoginType.NORMAL); + } + + /** 확인 취소 — 대기 상태 정리(익명 유지) */ + public void cancel(HttpSession session) { + session.removeAttribute(ATTR_PENDING_USER_ID); + session.removeAttribute(ATTR_PENDING_LOGIN_ID); + session.removeAttribute(ATTR_PENDING_AT); + } + + // ========================================================================= + // 내부 helper + // ========================================================================= + + private Optional activeSession(String loginId) { + if (loginId == null) { + return Optional.empty(); + } + return userSessionService.getActiveSession(loginId.toLowerCase()); + } + + /** 1차 인증~확인 사이 계정 상태 변경 방어 (TwoFactorService.revalidateLoginState 와 동일 기준) */ + private boolean isLoginStillAllowed(PortalUser user) { + if ("Y".equalsIgnoreCase(user.getAccountLockYn())) { + return false; + } + if (PortalUserEnums.UserStatus.ADMINBLOCK.equals(user.getUserStatus())) { + return false; + } + if (PortalUserEnums.ApprovalStatus.PENDING.equals(user.getApprovalStatus())) { + return false; + } + return user.getPortalOrg() == null + || PortalOrgEnums.ApprovalStatus.COMPLETED.equals(user.getPortalOrg().getApprovalStatus()); + } + + /** + * IP 주소 마스킹 (3번째 옥텟을 ***로 치환). 예: 192.168.240.178 → 192.168.***.178 + */ + private static String maskIpAddress(String ip) { + if (ip == null || ip.isEmpty()) { + return "알 수 없음"; + } + String[] parts = ip.split("\\."); + if (parts.length == 4) { + return parts[0] + "." + parts[1] + ".***." + parts[3]; + } + // IPv6 등 다른 형식은 일부만 표시 + if (ip.length() > 8) { + return ip.substring(0, 4) + "****" + ip.substring(ip.length() - 4); + } + return "***"; + } +} diff --git a/src/main/java/com/eactive/apim/portal/apps/login/service/LoginFinalizer.java b/src/main/java/com/eactive/apim/portal/apps/login/service/LoginFinalizer.java new file mode 100644 index 0000000..2ca023b --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/login/service/LoginFinalizer.java @@ -0,0 +1,289 @@ +package com.eactive.apim.portal.apps.login.service; + +import com.eactive.apim.portal.apps.login.constants.LoginType; +import com.eactive.apim.portal.apps.session.service.UserSessionService; +import com.eactive.apim.portal.apps.user.repository.PortalOrgRepository; +import com.eactive.apim.portal.apps.user.service.PortalUserLogService; +import com.eactive.apim.portal.common.util.HttpRequestUtil; +import com.eactive.apim.portal.common.util.PhoneNumberUtil; +import com.eactive.apim.portal.common.util.StringMaskingUtil; +import com.eactive.apim.portal.common.util.StringRepeatUtil; +import com.eactive.apim.portal.config.PasswordChangeEnforcementInterceptor; +import com.eactive.apim.portal.config.PasswordEnforcementPolicy; +import com.eactive.apim.portal.config.PortalProperties; +import com.eactive.apim.portal.invitation.entity.UserInvitation; +import com.eactive.apim.portal.invitation.entity.UserInvitationEnums.InvitationStatus; +import com.eactive.apim.portal.invitation.repository.UserInvitationRepository; +import com.eactive.apim.portal.portalorg.entity.PortalOrg; +import com.eactive.apim.portal.portaluser.entity.PortalUser; +import com.eactive.apim.portal.portaluser.entity.PortalUserEnums; +import com.eactive.apim.portal.portaluser.entity.UserPasswordHistory; +import com.eactive.apim.portal.portalproperty.service.PortalPropertyService; +import com.eactive.apim.portal.portaluser.repository.PortalUserRepository; +import com.eactive.apim.portal.portaluser.repository.UserPasswordHistoryRepository; +import com.eactive.apim.portal.template.entity.MessageCode; +import com.eactive.apim.portal.template.entity.MessageRequest; +import com.eactive.apim.portal.template.repository.MessageRequestRepository; +import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Optional; + +/** + * 로그인 최종 확정(finalize) 처리를 공통화한다. + * + *

기존 {@code PortalAuthenticationSuccessHandler} 에 인라인되어 있던 후처리 + * (실패카운트 리셋, 감사 성공 기록, 후속 유도 세션 플래그, 초대 확인, 중복로그인 정리, + * 물리 세션 타임아웃, 최종 이동 URL 결정)를 여기로 추출했다.

+ * + *

세 경로가 이 로직을 공유한다: + *

    + *
  • 일반 로그인 — 2FA off 시 SuccessHandler 가 직접 호출({@link LoginType#NORMAL})
  • + *
  • 로그인 2FA 통과 — TwoFactorService 가 호출({@link LoginType#TWO_FACTOR})
  • + *
  • 회원가입 자동 로그인 — 가입 컨트롤러가 호출({@link LoginType#SIGNUP_AUTO})
  • + *
+ * 최종 이동 URL 을 반환하며, 리다이렉트(HTTP 302)는 호출부 책임이다.

+ */ +@Service +@Transactional +@RequiredArgsConstructor +public class LoginFinalizer { + + private static final Logger sessionLogger = LoggerFactory.getLogger("eapim.portal.session"); + private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"); + + /** + * 미인증 상태로 보호 페이지 접근 시 저장해 둔 원래 요청 경로(로그인+2FA 완료 후 복귀 대상). + * {@code PortalGlobalExceptionHandler} 가 저장하고 여기서 소비(1회용)한다. + */ + public static final String SESSION_POST_LOGIN_REDIRECT = "postLoginRedirect"; + + private final PortalUserRepository portalUserRepository; + private final PortalProperties portalProperties; + private final PortalUserLogService userLogService; + private final UserPasswordHistoryRepository passwordHistoryRepository; + private final MessageRequestRepository messageRequestRepository; + private final UserInvitationRepository userInvitationRepository; + private final PortalOrgRepository portalOrgRepository; + private final UserSessionService userSessionService; + private final PortalPropertyService portalPropertyService; + + /** + * 로그인 확정 후처리를 수행하고 최종 이동 URL 을 반환한다. + * + * @param user 인증된 사용자 + * @param rawUsername 감사/세션 표기에 쓸 사용자 식별자(로그인 폼 입력 원본 또는 loginId) + * @param request 현재 요청(IP/헤더/세션) + * @param loginType 로그인 유형(감사 기록용) + * @return 리다이렉트 대상 URL (contextPath 포함) + */ + public String finalizeLogin(PortalUser user, String rawUsername, HttpServletRequest request, LoginType loginType) { + String normalizedUsername = rawUsername != null ? rawUsername.toLowerCase() : null; + + user.setLoginFailureCount(0); + portalUserRepository.save(user); + + String ip = request.getRemoteAddr(); + String sessionId = request.getSession().getId(); + // 다른 기기/브라우저의 활성 세션이 남아 있는 상태의 로그인인지(아래 forceLogoutOtherSessions 로 + // 기존 세션이 끊기는 건인지) 기록한다. 현재 세션 등록(registerSession)은 이 뒤에 일어나므로 + // 여기서 조회되는 활성 세션은 항상 "다른 곳"의 세션이다. + boolean duplicate = userSessionService.getActiveSession(normalizedUsername).isPresent(); + userLogService.logSuccess(rawUsername, ip, sessionId, loginType, request.getHeader("User-Agent"), + duplicate); + + String contextPath = request.getContextPath(); + HttpSession session = request.getSession(); + + applyPostLoginState(user, session, rawUsername, contextPath); + + // 초대 코드 확인 - ROLE_USER만 (메인 페이지에서 팝업으로 표시) + if (user.getRoleCode() == PortalUserEnums.RoleCode.ROLE_USER) { + Optional pendingInvitation = + userInvitationRepository.findFirstByInvitationMobileAndStatus( + PhoneNumberUtil.normalize(user.getMobileNumber()), InvitationStatus.PENDING); + + if (pendingInvitation.isPresent()) { + UserInvitation invitation = pendingInvitation.get(); + if (invitation.getExpiresOn().isAfter(LocalDateTime.now())) { + session.setAttribute("pendingInvitation", true); + session.setAttribute("pendingInvitationToken", invitation.getToken()); + String orgName = portalOrgRepository.findById(invitation.getOrgId()) + .map(PortalOrg::getOrgName) + .orElse("알 수 없는 기관"); + session.setAttribute("pendingInvitationOrgName", orgName); + } + } + } + + // 법인 사용자(관리자/개발자) 로그인 시, 홈 최초 진입에서 클라이언트 신규 신청 유도 팝업을 1회 노출하도록 마킹한다. + // 실제 클라이언트(승인+요청중) 보유 여부 판정과 1회 소비는 IndexController 가 담당한다. + PortalUserEnums.RoleCode roleCode = user.getRoleCode(); + if (roleCode == PortalUserEnums.RoleCode.ROLE_CORP_MANAGER + || roleCode == PortalUserEnums.RoleCode.ROLE_CORP_USER) { + session.setAttribute("checkClientRegister", true); + } + + // 중복 로그인 방지: 기존 세션 강제 로그아웃 + 현재 세션 등록 + String clientIp = HttpRequestUtil.getClientIpAddress(request); + userSessionService.forceLogoutOtherSessions(normalizedUsername, sessionId); + userSessionService.registerSession(sessionId, String.valueOf(user.getId()), normalizedUsername, + clientIp, request.getHeader("User-Agent")); + + // 물리 세션 타임아웃 10분 고정 (콘솔 override 무관하게 물리=논리 단일화, CSRF 수명 포함) + session.setMaxInactiveInterval(userSessionService.getSessionTimeoutMinutes() * 60); + + logLoginSuccess(request, session, rawUsername); + + String decisionToken = (String) session.getAttribute("decisionToken"); + if (decisionToken != null) { + return contextPath + "/signup/decision_process"; + } + + // 미인증 접근으로 저장해 둔 원래 요청 페이지로 복귀(1회용). + // 단, 강제 유도 흐름(이메일 인증/휴면/비밀번호 변경 — session.redirectUrl 세팅)이 있으면 + // 그쪽이 우선이므로 복귀시키지 않고 기본 경로로 보낸다. + String postLoginRedirect = (String) session.getAttribute(SESSION_POST_LOGIN_REDIRECT); + session.removeAttribute(SESSION_POST_LOGIN_REDIRECT); + if (postLoginRedirect != null && session.getAttribute("redirectUrl") == null) { + return contextPath + postLoginRedirect; + } + return contextPath + "/"; + } + + /** 후속 유도(이메일 인증/휴면/비밀번호 변경) 세션 플래그 세팅 */ + private void applyPostLoginState(PortalUser user, HttpSession session, String username, String contextPath) { + if (isEmailVerificationRequired(user)) { + session.setAttribute("success", "이메일 인증이 완료되지 않았습니다. 이메일을 확인하여 인증을 완료해주세요."); + session.setAttribute("emailVerificationRequired", true); + session.setAttribute("redirectUrl", contextPath + "/mypage/verification-email"); + } else if (isDormantAccount(user)) { + session.setAttribute("success", "90일 이상 미접속하여 계정이 잠금 처리되었습니다. 본인인증 후 이용해주세요."); + session.setAttribute("dormantAccount", true); + session.setAttribute("dormantLoginId", username); + session.setAttribute("redirectUrl", contextPath + "/dormant_account"); + } else if (isTemporaryPasswordLogin(user)) { + applyPasswordChangeState(session, + "임시 비밀번호로 로그인하셨습니다.
계정 보안을 위해 비밀번호를 변경해 주세요.", + contextPath + "/password/change"); + } else if (isPasswordChangeRequired(user)) { + applyPasswordChangeState(session, + "비밀번호를 변경한 지 " + portalProperties.getPasswordExpirationDays() + "일이 경과하였습니다.
계정 보안을 위해 비밀번호를 변경해 주세요.", + contextPath + "/password/change"); + } else if (user.getPasswordChangeDate() == null) { + // 완화 정책: 현재 비밀번호 확인 단계를 제거했으므로 새 비밀번호 폼으로 바로 유도한다. + applyPasswordChangeState(session, + "계정 보안을 위해 비밀번호 재설정이 필요합니다.
비밀번호를 변경해 주세요.", + contextPath + "/password/change"); + } + } + + /** + * 비밀번호 변경 대상자에게 정책(NONE/PERMISSIVE/ENFORCE)을 적용한다. + */ + private void applyPasswordChangeState(HttpSession session, String message, String redirectUrl) { + PasswordEnforcementPolicy policy = PasswordEnforcementPolicy.from( + portalPropertyService.getOrCreateProperty( + PasswordEnforcementPolicy.PROPERTY_GROUP, + PasswordEnforcementPolicy.PROPERTY_NAME, + PasswordEnforcementPolicy.DEFAULT.name(), + "비밀번호 변경 강제 정책 (NONE|PERMISSIVE|ENFORCE)")); + + if (policy == PasswordEnforcementPolicy.NONE) { + return; + } + + session.setAttribute("success", message); + session.setAttribute("passwordExpired", true); + session.setAttribute("redirectUrl", redirectUrl); + + if (policy == PasswordEnforcementPolicy.ENFORCE) { + session.setAttribute(PasswordChangeEnforcementInterceptor.ENFORCE_SESSION_ATTR, Boolean.TRUE); + } + } + + private boolean isPasswordChangeRequired(PortalUser user) { + Optional latestHistory = passwordHistoryRepository + .findTopByUserIdOrderByChangeDateDesc(user.getId()); + + if (latestHistory.isPresent()) { + LocalDateTime lastChangeDate = latestHistory.get().getChangeDate(); + return LocalDateTime.now() + .minusDays(portalProperties.getPasswordExpirationDays()) + .isAfter(lastChangeDate); + } + + return LocalDateTime.now() + .minusDays(portalProperties.getPasswordExpirationDays()) + .isAfter(user.getCreatedDate()); + } + + private boolean isTemporaryPasswordLogin(PortalUser user) { + Optional latestResetRequest = messageRequestRepository.findFirstByEmailAndMessageCodeOrderByRequestDateDesc( + user.getLoginId(), MessageCode.USER_PASSWORD_RESET); + + if (latestResetRequest.isPresent()) { + Optional latestHistory = passwordHistoryRepository + .findTopByUserIdOrderByChangeDateDesc(user.getId()); + return !latestHistory.isPresent() || latestHistory.get().getChangeDate().isBefore(latestResetRequest.get().getRequestDate()); + } + return false; + } + + private boolean isDormantAccount(PortalUser user) { + return PortalUserEnums.UserStatus.DORMANT.equals(user.getUserStatus()); + } + + private boolean isEmailVerificationRequired(PortalUser user) { + return PortalUserEnums.UserStatus.READY.equals(user.getUserStatus()); + } + + private void logLoginSuccess(HttpServletRequest request, HttpSession session, String username) { + StringBuilder logMessage = new StringBuilder(); + logMessage.append("\n"); + logMessage.append(StringRepeatUtil.repeat('=', 80)).append("\n"); + logMessage.append("USER LOGIN SUCCESS\n"); + logMessage.append(StringRepeatUtil.repeat('=', 80)).append("\n"); + logMessage.append("Username: ").append(StringMaskingUtil.maskLoginId(username)).append("\n"); + logMessage.append("Session ID: ").append(StringMaskingUtil.maskToken(session.getId())).append("\n"); + logMessage.append("Login At: ").append(LocalDateTime.now().format(formatter)).append("\n"); + logMessage.append("\n"); + logMessage.append(StringRepeatUtil.repeat('-', 80)).append("\n"); + logMessage.append("REQUEST INFORMATION\n"); + logMessage.append(StringRepeatUtil.repeat('-', 80)).append("\n"); + logMessage.append("Client IP Address: ").append(StringMaskingUtil.maskIpAddress(HttpRequestUtil.getClientIpAddress(request))).append("\n"); + logMessage.append("Client Host: ").append(HttpRequestUtil.getClientHost(request)).append("\n"); + logMessage.append("Is Proxied: ").append(HttpRequestUtil.isProxied(request)).append("\n"); + logMessage.append("Remote Address (Direct): ").append(StringMaskingUtil.maskIpAddress(request.getRemoteAddr())).append("\n"); + logMessage.append("Remote Host (Direct): ").append(request.getRemoteHost()).append("\n"); + logMessage.append("Request Method: ").append(request.getMethod()).append("\n"); + logMessage.append("Request URI: ").append(request.getRequestURI()).append("\n"); + logMessage.append("Query String: ").append(request.getQueryString()).append("\n"); + + logMessage.append("\n"); + logMessage.append(StringRepeatUtil.repeat('-', 80)).append("\n"); + logMessage.append("REQUEST HEADERS\n"); + logMessage.append(StringRepeatUtil.repeat('-', 80)).append("\n"); + + java.util.Enumeration headerNames = request.getHeaderNames(); + while (headerNames.hasMoreElements()) { + String headerName = headerNames.nextElement(); + java.util.Enumeration headerValues = request.getHeaders(headerName); + while (headerValues.hasMoreElements()) { + String headerValue = headerValues.nextElement(); + logMessage.append(String.format(" %-30s : %s\n", headerName, StringMaskingUtil.maskHeaderValue(headerName, headerValue))); + } + } + + logMessage.append(StringRepeatUtil.repeat('=', 80)).append("\n"); + + sessionLogger.info(logMessage.toString()); + } +} diff --git a/src/main/java/com/eactive/apim/portal/apps/main/controller/IndexController.java b/src/main/java/com/eactive/apim/portal/apps/main/controller/IndexController.java index 1f6716b..e32e0d4 100644 --- a/src/main/java/com/eactive/apim/portal/apps/main/controller/IndexController.java +++ b/src/main/java/com/eactive/apim/portal/apps/main/controller/IndexController.java @@ -1,10 +1,14 @@ package com.eactive.apim.portal.apps.main.controller; import com.eactive.apim.portal.apps.apiservice.dto.ApiServiceDTO; +import com.eactive.apim.portal.apps.app.service.AppServiceFacade; import com.eactive.apim.portal.apps.main.dto.IndexStatisticsDTO; import com.eactive.apim.portal.apps.main.service.IndexStatisticsService; import com.eactive.apim.portal.apps.main.service.MainApiFacade; +import com.eactive.apim.portal.common.user.PortalAuthenticatedUser; +import com.eactive.apim.portal.common.util.SecurityUtil; import java.util.List; +import javax.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; @@ -17,6 +21,7 @@ public class IndexController { private final MainApiFacade mainApiFacade; private final IndexStatisticsService indexStatisticsService; + private final AppServiceFacade appServiceFacade; /** * 메인 페이지 API는 Portal Property 에 main.service.list 에 등록된 그룹을 기준으로 API를 조회함. @@ -29,7 +34,7 @@ public class IndexController { * @return */ @GetMapping("/") - public String index(Model model) { + public String index(Model model, HttpSession session) { List services = mainApiFacade.getOpenApiServices(); List hashTags = mainApiFacade.getHashTags(); @@ -39,9 +44,37 @@ public class IndexController { addAttributesToModel(model, services, hashTags, statistics); + resolveClientRegisterNudge(model, session); + return "apps/main/index"; } + /** + * 법인 사용자(관리자/개발자) 로그인 직후, 클라이언트(승인+요청중)가 하나도 없으면 + * 클라이언트 신규 신청 유도 팝업 노출 플래그를 세팅한다. + * + *

노출 시점은 {@code LoginFinalizer} 가 로그인 시 세팅한 {@code checkClientRegister} + * 세션 마커로 통제한다. 마커는 홈 최초 진입에서 1회 소비하여, 이후 홈 재방문 시 + * 반복 노출되지 않게 한다. (역할 게이팅은 마커 세팅 측에서 이미 수행됨)

+ */ + private void resolveClientRegisterNudge(Model model, HttpSession session) { + if (session.getAttribute("checkClientRegister") == null) { + return; + } + session.removeAttribute("checkClientRegister"); // 1회 소비 + + PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser(); + if (user == null || user.getPortalOrg() == null) { + return; + } + + boolean hasClient = !appServiceFacade.getApikeyList(user.getPortalOrg()).isEmpty() + || !appServiceFacade.getPendingApiKeyList(user.getPortalOrg()).isEmpty(); + if (!hasClient) { + model.addAttribute("needClientRegister", true); + } + } + private void addAttributesToModel(Model model, List apiServices, List hashTags, IndexStatisticsDTO statistics) { model.addAttribute("services", apiServices); diff --git a/src/main/java/com/eactive/apim/portal/apps/session/controller/SessionApiController.java b/src/main/java/com/eactive/apim/portal/apps/session/controller/SessionApiController.java new file mode 100644 index 0000000..cee15b8 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/session/controller/SessionApiController.java @@ -0,0 +1,120 @@ +package com.eactive.apim.portal.apps.session.controller; + +import com.eactive.apim.portal.apps.session.service.UserSessionService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.security.web.csrf.CsrfToken; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; +import java.util.HashMap; +import java.util.Map; + +/** + * 세션 타이머/유휴 로그아웃 처리용 REST API. + * + *

중복 세션 확인은 비밀번호 검증 전 정보 노출 문제로 로그인 전 사전 체크 + * ({@code /api/session/check-duplicate})를 제거하고, 1차 인증 통과 후 + * {@code DuplicateLoginService} 가 처리한다.

+ * + *
    + *
  • GET /api/session/status - 잔여 시간/유효성 폴링 (인증 필요)
  • + *
  • POST /api/session/heartbeat - 세션 연장 (lastAccessTime 갱신)
  • + *
  • GET /api/session/ping - 익명 세션 keepalive (로그인/회원가입 페이지)
  • + *
  • GET /api/session/csrf - 현재 CSRF 토큰 조회 (로그인 제출 직전 안전망)
  • + *
+ */ +@Slf4j +@RestController +@RequestMapping("/api/session") +@RequiredArgsConstructor +public class SessionApiController { + + private final UserSessionService userSessionService; + + /** + * 세션 상태 폴링 (인증 필요) + */ + @GetMapping("/status") + public ResponseEntity> getSessionStatus(HttpServletRequest request) { + Map result = new HashMap<>(); + HttpSession httpSession = request.getSession(false); + + if (httpSession == null) { + result.put("valid", false); + result.put("remainingSeconds", 0); + return ResponseEntity.ok(result); + } + + String sessionId = httpSession.getId(); + boolean forceLoggedOut = userSessionService.isForceLoggedOut(sessionId); + + if (forceLoggedOut) { + result.put("valid", false); + result.put("remainingSeconds", 0); + } else { + long remaining = userSessionService.getRemainingSeconds(sessionId); + result.put("valid", remaining > 0); + result.put("remainingSeconds", remaining); + } + + return ResponseEntity.ok(result); + } + + /** + * 하트비트 - lastAccessTime 갱신 (세션 연장) + */ + @PostMapping("/heartbeat") + public ResponseEntity> heartbeat(HttpServletRequest request) { + Map result = new HashMap<>(); + HttpSession httpSession = request.getSession(false); + + if (httpSession == null) { + result.put("remainingSeconds", 0); + return ResponseEntity.ok(result); + } + + String sessionId = httpSession.getId(); + userSessionService.updateLastAccessTime(sessionId); + long remaining = userSessionService.getRemainingSeconds(sessionId); + result.put("remainingSeconds", remaining); + + return ResponseEntity.ok(result); + } + + /** + * 익명(비로그인) 페이지용 세션 keepalive ping. + * 요청이 기존 세션에 접근하는 것만으로 컨테이너의 세션 비활성 타이머가 리셋되어 + * 익명 세션(세션 저장 CSRF 토큰, 회원가입 본인인증 상태 포함)이 유지된다. + * 세션이 없으면 새로 만들지 않는다. + */ + @GetMapping("/ping") + public ResponseEntity ping(HttpServletRequest request) { + request.getSession(false); + return ResponseEntity.noContent().build(); + } + + /** + * 현재 CSRF 토큰 조회 (로그인 제출 직전 안전망). + * 세션 만료로 토큰이 사라진 경우 CsrfFilter가 새 토큰을 생성하고, + * 이 핸들러가 토큰 값을 읽는 시점에 새 세션에 저장된다(LazyCsrfTokenRepository). + * 회원가입 절차는 세션에 본인인증 상태를 들고 있어 토큰 재발급만으로는 복구가 안 되므로 + * 로그인 페이지 안전망으로만 사용한다. + */ + @GetMapping("/csrf") + public ResponseEntity> csrfToken(HttpServletRequest request) { + CsrfToken token = (CsrfToken) request.getAttribute(CsrfToken.class.getName()); + Map result = new HashMap<>(); + if (token != null) { + result.put("headerName", token.getHeaderName()); + result.put("parameterName", token.getParameterName()); + result.put("token", token.getToken()); + } + return ResponseEntity.ok(result); + } +} diff --git a/src/main/java/com/eactive/apim/portal/apps/session/entity/UserSession.java b/src/main/java/com/eactive/apim/portal/apps/session/entity/UserSession.java new file mode 100644 index 0000000..03bf888 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/session/entity/UserSession.java @@ -0,0 +1,54 @@ +package com.eactive.apim.portal.apps.session.entity; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import java.time.LocalDateTime; + +/** + * 사용자 세션 추적 엔티티 (EMS 스키마, PTL_USER_SESSION) + * + *

화면 세션 타이머/유휴 자동 로그아웃 및 중복로그인 강제 로그아웃에 사용된다. + * {@code lastAccessTime} + 타임아웃(분) 이 세션 만료의 기준이며, {@code forceLogout='Y'} 인 세션은 + * 다음 요청 시 {@link com.eactive.apim.portal.apps.session.filter.SessionValidationFilter} 가 강제 로그아웃한다. + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Entity +@Table(name = "PTL_USER_SESSION") +public class UserSession { + + @Id + @Column(name = "SESSION_ID", length = 128, nullable = false) + private String sessionId; + + @Column(name = "USER_ID", length = 36, nullable = false) + private String userId; + + @Column(name = "LOGIN_ID", length = 200, nullable = false) + private String loginId; + + @Column(name = "LOGIN_TIME", nullable = false) + private LocalDateTime loginTime; + + @Column(name = "LAST_ACCESS_TIME", nullable = false) + private LocalDateTime lastAccessTime; + + @Column(name = "IP_ADDRESS", length = 45) + private String ipAddress; + + @Column(name = "USER_AGENT", length = 500) + private String userAgent; + + @Column(name = "FORCE_LOGOUT", length = 1, nullable = false) + @Builder.Default + private String forceLogout = "N"; +} diff --git a/src/main/java/com/eactive/apim/portal/apps/session/filter/SessionValidationFilter.java b/src/main/java/com/eactive/apim/portal/apps/session/filter/SessionValidationFilter.java new file mode 100644 index 0000000..412fc0c --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/session/filter/SessionValidationFilter.java @@ -0,0 +1,124 @@ +package com.eactive.apim.portal.apps.session.filter; + +import com.eactive.apim.portal.apps.session.service.UserSessionService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +/** + * 인증 사용자 요청마다 DB 세션 상태를 검증한다. + * + *

    + *
  1. 강제 로그아웃(forceLogout='Y') 감지 시 세션 무효화 후 {@code /login?forceLogout=true}
  2. + *
  3. 잔여 시간 만료 시 세션 무효화 후 {@code /login?expired=true}
  4. + *
  5. 그 외에는 60초 스로틀로 lastAccessTime 갱신 (실제 활동 반영)
  6. + *
+ */ +@Slf4j +@Component +@RequiredArgsConstructor +public class SessionValidationFilter extends OncePerRequestFilter { + + private static final String LAST_DB_UPDATE_ATTR = "SESSION_LAST_DB_UPDATE"; + private static final long DB_UPDATE_THROTTLE_MS = 60_000; // 60초 스로틀링 + + private static final List EXCLUDED_PREFIXES = Arrays.asList( + "/api/session/", + "/login", + "/actionLogin.do", + "/actionLogout.do", + "/css/", + "/js/", + "/img/", + "/plugins/", + "/fonts/", + "/favicon.ico", + "/error", + "/health" + ); + + private final UserSessionService userSessionService; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + + String path = request.getRequestURI(); + String contextPath = request.getContextPath(); + if (contextPath != null && !contextPath.isEmpty()) { + path = path.substring(contextPath.length()); + } + + // 제외 경로 체크 + if (isExcludedPath(path)) { + filterChain.doFilter(request, response); + return; + } + + // 인증되지 않은 요청은 통과 + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + if (auth == null || !auth.isAuthenticated() || "anonymousUser".equals(auth.getPrincipal())) { + filterChain.doFilter(request, response); + return; + } + + HttpSession httpSession = request.getSession(false); + if (httpSession == null) { + filterChain.doFilter(request, response); + return; + } + + String sessionId = httpSession.getId(); + + // 1. 강제 로그아웃 체크 (중복 로그인) + if (userSessionService.isForceLoggedOut(sessionId)) { + log.info("강제 로그아웃 감지 - sessionId: {}", sessionId); + userSessionService.removeSession(sessionId); + httpSession.invalidate(); + SecurityContextHolder.clearContext(); + response.sendRedirect(contextPath + "/login?forceLogout=true"); + return; + } + + // 2. 세션 만료 체크 (DB 레코드 없거나 잔여 0) + long remaining = userSessionService.getRemainingSeconds(sessionId); + if (remaining <= 0) { + userSessionService.removeSession(sessionId); + httpSession.invalidate(); + SecurityContextHolder.clearContext(); + response.sendRedirect(contextPath + "/login?expired=true"); + return; + } + + // 3. lastAccessTime 갱신 (60초 스로틀링) + Long lastUpdate = (Long) httpSession.getAttribute(LAST_DB_UPDATE_ATTR); + long now = System.currentTimeMillis(); + if (lastUpdate == null || (now - lastUpdate) > DB_UPDATE_THROTTLE_MS) { + userSessionService.updateLastAccessTime(sessionId); + httpSession.setAttribute(LAST_DB_UPDATE_ATTR, now); + } + + filterChain.doFilter(request, response); + } + + private boolean isExcludedPath(String path) { + for (String prefix : EXCLUDED_PREFIXES) { + if (path.startsWith(prefix)) { + return true; + } + } + return false; + } +} diff --git a/src/main/java/com/eactive/apim/portal/apps/session/repository/UserSessionRepository.java b/src/main/java/com/eactive/apim/portal/apps/session/repository/UserSessionRepository.java new file mode 100644 index 0000000..71cc206 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/session/repository/UserSessionRepository.java @@ -0,0 +1,33 @@ +package com.eactive.apim.portal.apps.session.repository; + +import com.eactive.apim.portal.apps.session.entity.UserSession; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.time.LocalDateTime; +import java.util.List; + +@Repository +public interface UserSessionRepository extends JpaRepository { + + List findByLoginId(String loginId); + + @Modifying + @Query("UPDATE UserSession s SET s.forceLogout = 'Y' WHERE s.loginId = :loginId AND s.sessionId <> :currentSessionId") + int forceLogoutOtherSessions(@Param("loginId") String loginId, @Param("currentSessionId") String currentSessionId); + + @Modifying + @Query("UPDATE UserSession s SET s.forceLogout = 'Y' WHERE s.loginId = :loginId") + int forceLogoutAllSessions(@Param("loginId") String loginId); + + @Modifying + @Query("DELETE FROM UserSession s WHERE s.lastAccessTime < :cutoffTime") + int deleteExpiredSessions(@Param("cutoffTime") LocalDateTime cutoffTime); + + @Modifying + @Query("UPDATE UserSession s SET s.lastAccessTime = :now WHERE s.sessionId = :sessionId") + int updateLastAccessTime(@Param("sessionId") String sessionId, @Param("now") LocalDateTime now); +} diff --git a/src/main/java/com/eactive/apim/portal/apps/session/service/UserSessionService.java b/src/main/java/com/eactive/apim/portal/apps/session/service/UserSessionService.java new file mode 100644 index 0000000..ba0da58 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/session/service/UserSessionService.java @@ -0,0 +1,183 @@ +package com.eactive.apim.portal.apps.session.service; + +import com.eactive.apim.portal.apps.session.entity.UserSession; +import com.eactive.apim.portal.apps.session.repository.UserSessionRepository; +import com.eactive.apim.portal.common.util.StringMaskingUtil; +import com.eactive.apim.portal.portalproperty.service.PortalPropertyService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +@Slf4j +@Service +@RequiredArgsConstructor +public class UserSessionService { + + private static final String PROPERTY_GROUP = "Portal"; + + /** 세션 타임아웃(분) 고정값. application.yml(timeout: 10m)·weblogic.xml(timeout-secs 600)과 동일하게 유지한다. */ + public static final int SESSION_TIMEOUT_MINUTES = 10; + + /** 세션 유지(타임아웃 무시) 기능 활성화 여부 프로퍼티 (true/false). 비운영 전용 — prod 가드는 상위(GlobalControllerAdvice)에서 적용 */ + private static final String KEEPALIVE_PROPERTY_NAME = "session.keepalive.enabled"; + private static final String DEFAULT_KEEPALIVE_ENABLED = "false"; + + /** 논리적 만료(lastAccessTime + 타임아웃) 이후 물리적 삭제까지 두는 안전 마진(분) */ + private static final long CLEANUP_MARGIN_MINUTES = 1; + + private final UserSessionRepository userSessionRepository; + private final PortalPropertyService portalPropertyService; + + /** + * 활성 세션 존재 여부 확인 (만료되지 않고 강제 로그아웃되지 않은 세션) + */ + @Transactional(readOnly = true) + public Optional getActiveSession(String loginId) { + int timeoutMinutes = getSessionTimeoutMinutes(); + LocalDateTime cutoff = LocalDateTime.now().minusMinutes(timeoutMinutes); + + List sessions = userSessionRepository.findByLoginId(loginId); + return sessions.stream() + .filter(s -> "N".equals(s.getForceLogout())) + .filter(s -> s.getLastAccessTime().isAfter(cutoff)) + .findFirst(); + } + + /** + * 새 세션 등록 + */ + @Transactional + public void registerSession(String sessionId, String userId, String loginId, String ipAddress, String userAgent) { + UserSession session = UserSession.builder() + .sessionId(sessionId) + .userId(userId) + .loginId(loginId) + .loginTime(LocalDateTime.now()) + .lastAccessTime(LocalDateTime.now()) + .ipAddress(ipAddress) + .userAgent(truncate(userAgent, 500)) + .forceLogout("N") + .build(); + userSessionRepository.save(session); + log.info("세션 등록 - loginId: {}, sessionId: {}, ip: {}", + StringMaskingUtil.maskLoginId(loginId), StringMaskingUtil.maskToken(sessionId), StringMaskingUtil.maskIpAddress(ipAddress)); + } + + /** + * 다른 세션 강제 로그아웃 플래그 설정 (중복 로그인 방지) + */ + @Transactional + public void forceLogoutOtherSessions(String loginId, String currentSessionId) { + int count = userSessionRepository.forceLogoutOtherSessions(loginId, currentSessionId); + if (count > 0) { + log.info("강제 로그아웃 처리 - loginId: {}, 대상 세션 수: {}", StringMaskingUtil.maskLoginId(loginId), count); + } + } + + /** + * 해당 사용자의 모든 세션에 강제 로그아웃 플래그 설정. + * 관리자의 권한 변경(위임/회수/소속제외)을 대상 사용자의 활성 세션에 반영하기 위해 사용한다. + */ + @Transactional + public void forceLogoutAllSessions(String loginId) { + int count = userSessionRepository.forceLogoutAllSessions(loginId); + if (count > 0) { + log.info("전체 강제 로그아웃 처리 - loginId: {}, 대상 세션 수: {}", StringMaskingUtil.maskLoginId(loginId), count); + } + } + + /** + * 현재 세션의 강제 로그아웃 여부 확인 + */ + @Transactional(readOnly = true) + public boolean isForceLoggedOut(String sessionId) { + return userSessionRepository.findById(sessionId) + .map(s -> "Y".equals(s.getForceLogout())) + .orElse(false); + } + + /** + * 세션 삭제 + */ + @Transactional + public void removeSession(String sessionId) { + if (userSessionRepository.existsById(sessionId)) { + userSessionRepository.deleteById(sessionId); + log.debug("세션 삭제 - sessionId: {}", StringMaskingUtil.maskToken(sessionId)); + } + } + + /** + * 마지막 접근 시간 갱신 (세션 연장) + */ + @Transactional + public void updateLastAccessTime(String sessionId) { + userSessionRepository.updateLastAccessTime(sessionId, LocalDateTime.now()); + } + + /** + * 세션 타임아웃(분). {@value #SESSION_TIMEOUT_MINUTES}분 고정 (DB property 관리 폐지). + */ + public int getSessionTimeoutMinutes() { + return SESSION_TIMEOUT_MINUTES; + } + + /** + * DB(PortalProperty)에서 세션 유지(타임아웃 무시) 기능 활성화 여부 조회. + * ⚠️ prod 가드는 포함하지 않는다 — prod 차단은 호출부(GlobalControllerAdvice)에서 {@code !prod && isKeepAliveEnabled()}로 처리. + */ + public boolean isKeepAliveEnabled() { + String value = portalPropertyService.getOrCreateProperty( + PROPERTY_GROUP, + KEEPALIVE_PROPERTY_NAME, + DEFAULT_KEEPALIVE_ENABLED, + "세션 유지(타임아웃 무시) 기능 활성화 여부 (true/false, 비운영 전용)" + ); + return value != null && Boolean.parseBoolean(value.trim()); + } + + /** + * 세션의 잔여 시간(초) 계산. 세션 레코드가 없으면 0. + */ + @Transactional(readOnly = true) + public long getRemainingSeconds(String sessionId) { + int timeoutMinutes = getSessionTimeoutMinutes(); + return userSessionRepository.findById(sessionId) + .map(s -> { + LocalDateTime expireTime = s.getLastAccessTime().plusMinutes(timeoutMinutes); + long remaining = Duration.between(LocalDateTime.now(), expireTime).getSeconds(); + return Math.max(0, remaining); + }) + .orElse(0L); + } + + /** + * 만료 세션 정리 (1분 주기). + * 논리적 만료(lastAccessTime + 타임아웃)가 지난 세션을 소량 마진({@value #CLEANUP_MARGIN_MINUTES}분) 후 삭제한다. + * heartbeat/활동이 멈춰 더 이상 갱신되지 않는 세션(닫힌 탭·크래시·네트워크 단절 등)을 신속히 제거한다. + */ + @Scheduled(fixedRate = 60000) + @Transactional + public void cleanupExpiredSessions() { + int timeoutMinutes = getSessionTimeoutMinutes(); + LocalDateTime cutoff = LocalDateTime.now().minusMinutes(timeoutMinutes + CLEANUP_MARGIN_MINUTES); + int deleted = userSessionRepository.deleteExpiredSessions(cutoff); + if (deleted > 0) { + log.info("만료 세션 정리 - 삭제 수: {}", deleted); + } + } + + private String truncate(String value, int maxLength) { + if (value == null) { + return null; + } + return value.length() > maxLength ? value.substring(0, maxLength) : value; + } +} diff --git a/src/main/java/com/eactive/apim/portal/apps/statistics/controller/ApiStatisticsController.java b/src/main/java/com/eactive/apim/portal/apps/statistics/controller/ApiStatisticsController.java index 1c3ad11..83ed0f3 100644 --- a/src/main/java/com/eactive/apim/portal/apps/statistics/controller/ApiStatisticsController.java +++ b/src/main/java/com/eactive/apim/portal/apps/statistics/controller/ApiStatisticsController.java @@ -20,7 +20,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; /** - * API 통계 Controller + * API 통계 Controller. */ @Slf4j @Controller @@ -31,21 +31,27 @@ public class ApiStatisticsController { private final ApiStatisticsService apiStatisticsService; /** - * 통계 페이지 진입 + * 통계 페이지 진입. */ @GetMapping public String statisticsPage(Model model) { + // 미로그인 접근은 사유를 노출하도록 reason=auth 로 유도(로그인 페이지 안내 배너) + if (!SecurityUtil.isAuthenticated()) { + return "redirect:/login?reason=auth"; + } PortalOrg org = getPortalOrg(); if (org == null) { - return "redirect:/login"; + // 로그인은 했으나 조직이 없는(권한 밖) 사용자는 홈으로 + return "redirect:/"; } String orgId = org.getId(); + log.debug("[통계] 페이지 진입 - orgId={}", orgId); // 앱 목록 조회 (선택용) model.addAttribute("appList", apiStatisticsService.getAppListByOrg(orgId)); - // 기본 조회 (7일 전 ~ 오늘, 전체 앱) + // 기본 조회 (일별, 7일 전 ~ 오늘, 전체 앱) ApiStatisticsSearchDto searchDto = new ApiStatisticsSearchDto(); searchDto.setStartDate(LocalDate.now().minusDays(7)); searchDto.setEndDate(LocalDate.now()); @@ -54,13 +60,19 @@ public class ApiStatisticsController { ApiStatisticsResultDto result = apiStatisticsService.getStatistics(orgId, searchDto); model.addAttribute("summary", result.getSummary()); model.addAttribute("details", result.getDetails()); + model.addAttribute("periods", result.getPeriods()); model.addAttribute("searchDto", searchDto); + // 월별 선택 가능 월 + 집계 안내 문구 데이터 + model.addAttribute("availableMonths", apiStatisticsService.getAvailableMonths(orgId)); + model.addAttribute("statsAggregationMinute", apiStatisticsService.getAggregationMinute()); + model.addAttribute("statsLatestTime", apiStatisticsService.getLatestStatTime(orgId)); + return "apps/statistics/apiStatistics"; } /** - * 통계 조회 (AJAX) + * 통계 조회 (AJAX). */ @PostMapping("/search") @ResponseBody @@ -70,11 +82,12 @@ public class ApiStatisticsController { return ResponseEntity.status(401).build(); } - // 날짜 범위 검증 (최대 40일) - if (!searchDto.isValidDateRange()) { + log.debug("[통계] 검색 요청(AJAX) - orgId={}, mode={}, clientId={}", + org.getId(), searchDto.getMode(), searchDto.getClientId()); + + if (!isValidRange(searchDto)) { return ResponseEntity.badRequest() - .body(java.util.Collections.singletonMap("error", - "조회 기간은 최대 " + ApiStatisticsSearchDto.MAX_DATE_RANGE_DAYS + "일까지 가능합니다.")); + .body(java.util.Collections.singletonMap("error", rangeErrorMessage(searchDto))); } String orgId = org.getId(); @@ -83,7 +96,9 @@ public class ApiStatisticsController { } /** - * CSV 다운로드 + * CSV 다운로드. + * + * ※ UI에서 다운로드 버튼은 제거되었으나 백엔드 기능은 유지한다(직접 호출용). */ @GetMapping("/download") public void downloadCsv(ApiStatisticsSearchDto searchDto, HttpServletResponse response) throws IOException { @@ -93,9 +108,8 @@ public class ApiStatisticsController { return; } - // 날짜 범위 검증 (최대 40일) - if (!searchDto.isValidDateRange()) { - response.sendError(400, "조회 기간은 최대 " + ApiStatisticsSearchDto.MAX_DATE_RANGE_DAYS + "일까지 가능합니다."); + if (!isValidRange(searchDto)) { + response.sendError(400, rangeErrorMessage(searchDto)); return; } @@ -103,6 +117,16 @@ public class ApiStatisticsController { apiStatisticsService.downloadCsv(orgId, searchDto, response); } + private boolean isValidRange(ApiStatisticsSearchDto searchDto) { + return searchDto.isMonthly() ? searchDto.isValidMonth() : searchDto.isValidDateRange(); + } + + private String rangeErrorMessage(ApiStatisticsSearchDto searchDto) { + return searchDto.isMonthly() + ? "조회할 월이 올바르지 않습니다." + : "조회 기간은 최대 " + ApiStatisticsSearchDto.MAX_DATE_RANGE_DAYS + "일까지 가능합니다."; + } + private PortalOrg getPortalOrg() { if (SecurityUtil.getPortalAuthenticatedUser() != null) { return SecurityUtil.getPortalAuthenticatedUser().getPortalOrg(); diff --git a/src/main/java/com/eactive/apim/portal/apps/statistics/dto/ApiStatisticsDetailDto.java b/src/main/java/com/eactive/apim/portal/apps/statistics/dto/ApiStatisticsDetailDto.java index 16e6038..02aa9fa 100644 --- a/src/main/java/com/eactive/apim/portal/apps/statistics/dto/ApiStatisticsDetailDto.java +++ b/src/main/java/com/eactive/apim/portal/apps/statistics/dto/ApiStatisticsDetailDto.java @@ -5,50 +5,52 @@ import lombok.Data; import lombok.NoArgsConstructor; /** - * 개별 API 통계 상세 DTO + * 개별 API 통계 상세 DTO (API_NAME 단위 집계). + * 성공/타임아웃/실패(오류) 배타 구분. {@link ApiStatisticsSummaryDto} 참고. */ @Data @NoArgsConstructor @AllArgsConstructor public class ApiStatisticsDetailDto { - private String apiId; private String apiName; - private Long attemptedCount; // 호출 건 - private Long completedCount; // 성공 건 - private Long failedCount; // 실패 건 - private Double successRate; // 성공율 (%) - private Double failureRate; // 실패율 (%) + private Long totalCount; // 호출 건 + private Long successCount; // 성공 건 + private Long timeoutCount; // 타임아웃 건 + private Long errorCount; // 실패(오류) 건 + private Double successRate; // 성공율 (%) + private Double timeoutRate; // 타임아웃율 (%) + private Double errorRate; // 실패(오류)율 (%) /** - * Repository 조회용 생성자 + * Repository 조회용 생성자. */ - public ApiStatisticsDetailDto(String apiId, String apiName, Long attemptedCount, Long completedCount) { - this.apiId = apiId; + public ApiStatisticsDetailDto(String apiName, Long totalCount, Long successCount, + Long timeoutCount, Long systemErrCount, Long bizErrCount) { this.apiName = apiName; - this.attemptedCount = attemptedCount != null ? attemptedCount : 0L; - this.completedCount = completedCount != null ? completedCount : 0L; + this.totalCount = ApiStatisticsSummaryDto.nz(totalCount); + this.successCount = ApiStatisticsSummaryDto.nz(successCount); + this.timeoutCount = ApiStatisticsSummaryDto.nz(timeoutCount); + this.errorCount = ApiStatisticsSummaryDto.nz(systemErrCount) + ApiStatisticsSummaryDto.nz(bizErrCount); } /** - * 실패건, 성공률, 실패율 계산 + * 성공율/타임아웃율/실패율 계산. */ public void calculateRates() { - if (attemptedCount == null) { - attemptedCount = 0L; - } - if (completedCount == null) { - completedCount = 0L; - } + this.totalCount = ApiStatisticsSummaryDto.nz(totalCount); + this.successCount = ApiStatisticsSummaryDto.nz(successCount); + this.timeoutCount = ApiStatisticsSummaryDto.nz(timeoutCount); + this.errorCount = ApiStatisticsSummaryDto.nz(errorCount); - this.failedCount = attemptedCount - completedCount; - - if (attemptedCount > 0) { - this.successRate = Math.round((completedCount * 1000.0) / attemptedCount) / 10.0; - this.failureRate = Math.round((failedCount * 1000.0) / attemptedCount) / 10.0; + if (totalCount > 0) { + this.successRate = ApiStatisticsSummaryDto.rate(successCount, totalCount); + this.timeoutRate = ApiStatisticsSummaryDto.rate(timeoutCount, totalCount); + this.errorRate = ApiStatisticsSummaryDto.rate(errorCount, totalCount); } else { this.successRate = 0.0; - this.failureRate = 0.0; + this.timeoutRate = 0.0; + this.errorRate = 0.0; } } } diff --git a/src/main/java/com/eactive/apim/portal/apps/statistics/dto/ApiStatisticsPeriodDto.java b/src/main/java/com/eactive/apim/portal/apps/statistics/dto/ApiStatisticsPeriodDto.java new file mode 100644 index 0000000..1f00085 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/statistics/dto/ApiStatisticsPeriodDto.java @@ -0,0 +1,62 @@ +package com.eactive.apim.portal.apps.statistics.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 기간별(일별/월별) 누적 통계 DTO — API별 통계 하단 표에 사용. + * + * period 는 일별 모드에서 "yyyy-MM-dd", 월별 모드에서 "yyyy-MM" 형태의 라벨. + * 각 행은 해당 기간의 전체 앱/전체 API 합계이다. + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ApiStatisticsPeriodDto { + + private String period; // 기간 라벨 (yyyy-MM-dd 또는 yyyy-MM) + private Long totalCount; + private Long successCount; + private Long timeoutCount; + private Long errorCount; + private Double successRate; + private Double timeoutRate; + private Double errorRate; + + /** 해당 일자에 실제 집계 데이터가 존재하는지 여부. false면 표에 '-'로 표기(0과 구분). */ + private boolean hasData; + + /** + * Repository 조회용 생성자 (실제 데이터 행 → hasData=true). + */ + public ApiStatisticsPeriodDto(String period, Long totalCount, Long successCount, + Long timeoutCount, Long systemErrCount, Long bizErrCount) { + this.period = period; + this.totalCount = ApiStatisticsSummaryDto.nz(totalCount); + this.successCount = ApiStatisticsSummaryDto.nz(successCount); + this.timeoutCount = ApiStatisticsSummaryDto.nz(timeoutCount); + this.errorCount = ApiStatisticsSummaryDto.nz(systemErrCount) + ApiStatisticsSummaryDto.nz(bizErrCount); + this.hasData = true; + } + + /** + * 성공율/타임아웃율/실패율 계산. + */ + public void calculateRates() { + this.totalCount = ApiStatisticsSummaryDto.nz(totalCount); + this.successCount = ApiStatisticsSummaryDto.nz(successCount); + this.timeoutCount = ApiStatisticsSummaryDto.nz(timeoutCount); + this.errorCount = ApiStatisticsSummaryDto.nz(errorCount); + + if (totalCount > 0) { + this.successRate = ApiStatisticsSummaryDto.rate(successCount, totalCount); + this.timeoutRate = ApiStatisticsSummaryDto.rate(timeoutCount, totalCount); + this.errorRate = ApiStatisticsSummaryDto.rate(errorCount, totalCount); + } else { + this.successRate = 0.0; + this.timeoutRate = 0.0; + this.errorRate = 0.0; + } + } +} diff --git a/src/main/java/com/eactive/apim/portal/apps/statistics/dto/ApiStatisticsResultDto.java b/src/main/java/com/eactive/apim/portal/apps/statistics/dto/ApiStatisticsResultDto.java index b254cb6..2e29c9e 100644 --- a/src/main/java/com/eactive/apim/portal/apps/statistics/dto/ApiStatisticsResultDto.java +++ b/src/main/java/com/eactive/apim/portal/apps/statistics/dto/ApiStatisticsResultDto.java @@ -7,7 +7,8 @@ import lombok.Data; import lombok.NoArgsConstructor; /** - * API 통계 조회 결과 DTO + * API 통계 조회 결과 DTO. + * summary: 전체 요약, details: API별, periods: 기간별(일/월) 누적. */ @Data @NoArgsConstructor @@ -16,13 +17,15 @@ public class ApiStatisticsResultDto { private ApiStatisticsSummaryDto summary; private List details; + private List periods; /** - * 빈 결과 생성 + * 빈 결과 생성. */ public static ApiStatisticsResultDto empty() { return new ApiStatisticsResultDto( ApiStatisticsSummaryDto.empty(), + new ArrayList<>(), new ArrayList<>() ); } diff --git a/src/main/java/com/eactive/apim/portal/apps/statistics/dto/ApiStatisticsSearchDto.java b/src/main/java/com/eactive/apim/portal/apps/statistics/dto/ApiStatisticsSearchDto.java index 87de30e..50d3a39 100644 --- a/src/main/java/com/eactive/apim/portal/apps/statistics/dto/ApiStatisticsSearchDto.java +++ b/src/main/java/com/eactive/apim/portal/apps/statistics/dto/ApiStatisticsSearchDto.java @@ -6,16 +6,25 @@ import lombok.Data; import org.springframework.format.annotation.DateTimeFormat; /** - * API 통계 검색 조건 DTO + * API 통계 검색 조건 DTO. + * + * mode = DAILY 이면 startDate~endDate(날짜범위, API_STATS_HOUR), + * mode = MONTHLY 이면 month(YYYYMM 단일 선택)를 사용한다. */ @Data public class ApiStatisticsSearchDto { /** - * 최대 조회 가능 일수 + * 최대 조회 가능 일수 (일별 모드). */ public static final int MAX_DATE_RANGE_DAYS = 40; + public enum Mode { + DAILY, MONTHLY + } + + private Mode mode = Mode.DAILY; + @DateTimeFormat(pattern = "yyyy-MM-dd") private LocalDate startDate; @@ -23,7 +32,12 @@ public class ApiStatisticsSearchDto { private LocalDate endDate; /** - * 선택된 앱의 clientId (null 또는 빈값이면 전체 앱) + * 월별 조회 대상 월 (YYYYMM, 단일 선택). + */ + private String month; + + /** + * 선택된 앱의 clientId (null 또는 빈값이면 전체 앱). */ private String clientId; @@ -33,15 +47,19 @@ public class ApiStatisticsSearchDto { this.clientId = null; } + public boolean isMonthly() { + return mode == Mode.MONTHLY; + } + /** - * 특정 앱이 선택되었는지 여부 + * 특정 앱이 선택되었는지 여부. */ public boolean hasSelectedApp() { return clientId != null && !clientId.trim().isEmpty(); } /** - * 날짜 범위가 유효한지 검증 (최대 40일) + * 날짜(일별) 범위가 유효한지 검증 (최대 40일). */ public boolean isValidDateRange() { if (startDate == null || endDate == null) { @@ -55,12 +73,13 @@ public class ApiStatisticsSearchDto { } /** - * 조회 기간 일수 반환 + * 월(월별) 선택값이 YYYYMM 형식인지 검증 (가용월 여부는 서비스/드롭다운에서 제한). */ - public long getDateRangeDays() { - if (startDate == null || endDate == null) { - return 0; - } - return ChronoUnit.DAYS.between(startDate, endDate); + public boolean isValidMonth() { + return isYyyymm(month); + } + + private static boolean isYyyymm(String s) { + return s != null && s.matches("^\\d{6}$"); } } diff --git a/src/main/java/com/eactive/apim/portal/apps/statistics/dto/ApiStatisticsSummaryDto.java b/src/main/java/com/eactive/apim/portal/apps/statistics/dto/ApiStatisticsSummaryDto.java index bc4411f..a02b5dd 100644 --- a/src/main/java/com/eactive/apim/portal/apps/statistics/dto/ApiStatisticsSummaryDto.java +++ b/src/main/java/com/eactive/apim/portal/apps/statistics/dto/ApiStatisticsSummaryDto.java @@ -5,54 +5,72 @@ import lombok.Data; import lombok.NoArgsConstructor; /** - * API 전체 통계 요약 DTO + * API 전체 통계 요약 DTO. + * + * 집계 소스: AGWAPP.API_STATS_HOUR / API_STATS_MONTH. + * 성공/타임아웃/실패(오류)를 배타적으로 구분한다. + * - 성공 = SUCCESS_CNT + * - 타임아웃 = TIMEOUT_CNT + * - 실패(오류) = SYSTEM_ERR_CNT + BIZ_ERR_CNT + * - 호출수(total) = TOTAL_CNT */ @Data @NoArgsConstructor @AllArgsConstructor public class ApiStatisticsSummaryDto { - private Long totalAttempted; // 전체 호출 건 - private Long totalCompleted; // 전체 성공 건 - private Long totalFailed; // 전체 실패 건 - private Double successRate; // 성공율 (%) - private Double failureRate; // 실패율 (%) + private Long totalCount; // 전체 호출 건 (TOTAL_CNT) + private Long successCount; // 성공 건 (SUCCESS_CNT) + private Long timeoutCount; // 타임아웃 건 (TIMEOUT_CNT) + private Long errorCount; // 실패(오류) 건 (SYSTEM_ERR_CNT + BIZ_ERR_CNT) + private Double successRate; // 성공율 (%) + private Double timeoutRate; // 타임아웃율 (%) + private Double errorRate; // 실패(오류)율 (%) /** - * Repository 조회용 생성자 (attempted, completed만) + * Repository 조회용 생성자 (SUM 집계값). */ - public ApiStatisticsSummaryDto(Long totalAttempted, Long totalCompleted) { - this.totalAttempted = totalAttempted != null ? totalAttempted : 0L; - this.totalCompleted = totalCompleted != null ? totalCompleted : 0L; + public ApiStatisticsSummaryDto(Long totalCount, Long successCount, Long timeoutCount, + Long systemErrCount, Long bizErrCount) { + this.totalCount = nz(totalCount); + this.successCount = nz(successCount); + this.timeoutCount = nz(timeoutCount); + this.errorCount = nz(systemErrCount) + nz(bizErrCount); } /** - * 실패건, 성공률, 실패율 계산 + * 성공률/타임아웃율/실패율 계산. */ public void calculateRates() { - if (totalAttempted == null) { - totalAttempted = 0L; - } - if (totalCompleted == null) { - totalCompleted = 0L; - } + this.totalCount = nz(totalCount); + this.successCount = nz(successCount); + this.timeoutCount = nz(timeoutCount); + this.errorCount = nz(errorCount); - this.totalFailed = totalAttempted - totalCompleted; - - if (totalAttempted > 0) { - this.successRate = Math.round((totalCompleted * 1000.0) / totalAttempted) / 10.0; - this.failureRate = Math.round((totalFailed * 1000.0) / totalAttempted) / 10.0; + if (totalCount > 0) { + this.successRate = rate(successCount, totalCount); + this.timeoutRate = rate(timeoutCount, totalCount); + this.errorRate = rate(errorCount, totalCount); } else { this.successRate = 0.0; - this.failureRate = 0.0; + this.timeoutRate = 0.0; + this.errorRate = 0.0; } } + static long nz(Long v) { + return v != null ? v : 0L; + } + + static double rate(long part, long total) { + return Math.round((part * 1000.0) / total) / 10.0; + } + /** - * 빈 통계 생성 + * 빈 통계 생성. */ public static ApiStatisticsSummaryDto empty() { - ApiStatisticsSummaryDto dto = new ApiStatisticsSummaryDto(0L, 0L); + ApiStatisticsSummaryDto dto = new ApiStatisticsSummaryDto(0L, 0L, 0L, 0L, 0L); dto.calculateRates(); return dto; } diff --git a/src/main/java/com/eactive/apim/portal/apps/statistics/repository/ApiStatisticsRepository.java b/src/main/java/com/eactive/apim/portal/apps/statistics/repository/ApiStatisticsRepository.java deleted file mode 100644 index 2c688e2..0000000 --- a/src/main/java/com/eactive/apim/portal/apps/statistics/repository/ApiStatisticsRepository.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.eactive.apim.portal.apps.statistics.repository; - -import com.eactive.apim.portal.apps.statistics.dto.ApiStatisticsDetailDto; -import com.eactive.apim.portal.apps.statistics.dto.ApiStatisticsSummaryDto; -import com.eactive.apim.portal.obp.entity.ObpGwMetric; -import com.eactive.eai.data.jpa.BaseRepository; -import com.eactive.eai.rms.data.EMSDataSource; -import java.time.LocalDateTime; -import java.util.List; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; - -/** - * API 통계 Repository - * ObpGwMetric 테이블에서 통계 데이터를 조회 - */ -@EMSDataSource -public interface ApiStatisticsRepository extends BaseRepository { - - /** - * 전체 통계 조회 (특정 기간, 특정 조직의 모든 앱) - */ - @Query("SELECT new com.eactive.apim.portal.apps.statistics.dto.ApiStatisticsSummaryDto(" + - "COALESCE(SUM(m.attemptedCount), 0), COALESCE(SUM(m.completedCount), 0)) " + - "FROM ObpGwMetric m " + - "WHERE m.orgId = :orgId " + - "AND m.timeslice BETWEEN :startTime AND :endTime") - ApiStatisticsSummaryDto findSummaryByOrgAndPeriod( - @Param("orgId") String orgId, - @Param("startTime") LocalDateTime startTime, - @Param("endTime") LocalDateTime endTime); - - /** - * 전체 통계 조회 (특정 기간, 특정 앱) - */ - @Query("SELECT new com.eactive.apim.portal.apps.statistics.dto.ApiStatisticsSummaryDto(" + - "COALESCE(SUM(m.attemptedCount), 0), COALESCE(SUM(m.completedCount), 0)) " + - "FROM ObpGwMetric m " + - "WHERE m.orgId = :orgId " + - "AND m.clientId = :clientId " + - "AND m.timeslice BETWEEN :startTime AND :endTime") - ApiStatisticsSummaryDto findSummaryByOrgAndApp( - @Param("orgId") String orgId, - @Param("clientId") String clientId, - @Param("startTime") LocalDateTime startTime, - @Param("endTime") LocalDateTime endTime); - - /** - * 개별 API 통계 조회 (특정 기간, 특정 조직의 모든 앱) - */ - @Query("SELECT new com.eactive.apim.portal.apps.statistics.dto.ApiStatisticsDetailDto(" + - "m.apiId, m.apiName, " + - "COALESCE(SUM(m.attemptedCount), 0), COALESCE(SUM(m.completedCount), 0)) " + - "FROM ObpGwMetric m " + - "WHERE m.orgId = :orgId " + - "AND m.timeslice BETWEEN :startTime AND :endTime " + - "GROUP BY m.apiId, m.apiName " + - "ORDER BY SUM(m.attemptedCount) DESC") - List findDetailByOrgAndPeriod( - @Param("orgId") String orgId, - @Param("startTime") LocalDateTime startTime, - @Param("endTime") LocalDateTime endTime); - - /** - * 개별 API 통계 조회 (특정 기간, 특정 앱) - */ - @Query("SELECT new com.eactive.apim.portal.apps.statistics.dto.ApiStatisticsDetailDto(" + - "m.apiId, m.apiName, " + - "COALESCE(SUM(m.attemptedCount), 0), COALESCE(SUM(m.completedCount), 0)) " + - "FROM ObpGwMetric m " + - "WHERE m.orgId = :orgId " + - "AND m.clientId = :clientId " + - "AND m.timeslice BETWEEN :startTime AND :endTime " + - "GROUP BY m.apiId, m.apiName " + - "ORDER BY SUM(m.attemptedCount) DESC") - List findDetailByOrgAndApp( - @Param("orgId") String orgId, - @Param("clientId") String clientId, - @Param("startTime") LocalDateTime startTime, - @Param("endTime") LocalDateTime endTime); -} diff --git a/src/main/java/com/eactive/apim/portal/apps/statistics/repository/JobInfoRepository.java b/src/main/java/com/eactive/apim/portal/apps/statistics/repository/JobInfoRepository.java new file mode 100644 index 0000000..204ce74 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/statistics/repository/JobInfoRepository.java @@ -0,0 +1,15 @@ +package com.eactive.apim.portal.apps.statistics.repository; + +import com.eactive.apim.portal.apps.statistics.repository.entity.JobInfo; +import com.eactive.eai.rms.data.EMSDataSource; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; + +/** + * TSEAIRM21(스케줄러 잡) 조회 — 통계 집계 크론 추출용. + */ +@EMSDataSource +public interface JobInfoRepository extends JpaRepository { + + Optional findByJobname(String jobname); +} diff --git a/src/main/java/com/eactive/apim/portal/apps/statistics/repository/entity/JobInfo.java b/src/main/java/com/eactive/apim/portal/apps/statistics/repository/entity/JobInfo.java new file mode 100644 index 0000000..3a0e9f2 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/statistics/repository/entity/JobInfo.java @@ -0,0 +1,28 @@ +package com.eactive.apim.portal.apps.statistics.repository.entity; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import lombok.Getter; +import lombok.Setter; + +/** + * 스케줄러 잡 정보(EMSAPP.TSEAIRM21) — 통계 집계 크론 조회 전용. + * + * 안내 문구의 "매시 N분 집계" 값을 실제 크론(CRONEXP)에서 추출하기 위해 JOBNAME/CRONEXP 두 컬럼만 매핑한다. + * (읽기 전용. 잡 실행/스케줄 변경은 eapim-admin 소관) + */ +@Getter +@Setter +@Entity +@Table(name = "TSEAIRM21") +public class JobInfo { + + @Id + @Column(name = "JOBNAME") + private String jobname; + + @Column(name = "CRONEXP") + private String cronexp; +} diff --git a/src/main/java/com/eactive/apim/portal/apps/statistics/service/ApiStatisticsService.java b/src/main/java/com/eactive/apim/portal/apps/statistics/service/ApiStatisticsService.java index a12b869..1df8644 100644 --- a/src/main/java/com/eactive/apim/portal/apps/statistics/service/ApiStatisticsService.java +++ b/src/main/java/com/eactive/apim/portal/apps/statistics/service/ApiStatisticsService.java @@ -1,21 +1,37 @@ package com.eactive.apim.portal.apps.statistics.service; -import com.eactive.apim.portal.app.entity.Credential; -import com.eactive.apim.portal.app.repository.CredentialRepository; +import com.eactive.apim.gateway.data.statistics.repository.ApiStatsDayRepository; +import com.eactive.apim.gateway.data.statistics.repository.ApiStatsHourRepository; +import com.eactive.apim.gateway.data.statistics.repository.ApiStatsMonthRepository; +import com.eactive.apim.gateway.data.statistics.repository.GwAuthClientRepository; +import com.eactive.apim.gateway.data.statistics.repository.GwSvcInfoRepository; import com.eactive.apim.portal.apps.statistics.dto.ApiStatisticsDetailDto; +import com.eactive.apim.portal.apps.statistics.dto.ApiStatisticsPeriodDto; import com.eactive.apim.portal.apps.statistics.dto.ApiStatisticsResultDto; import com.eactive.apim.portal.apps.statistics.dto.ApiStatisticsSearchDto; import com.eactive.apim.portal.apps.statistics.dto.ApiStatisticsSummaryDto; -import com.eactive.apim.portal.apps.statistics.repository.ApiStatisticsRepository; +import com.eactive.apim.portal.apps.statistics.repository.JobInfoRepository; +import com.eactive.apim.portal.portalproperty.service.PortalPropertyService; +import com.eactive.apim.gateway.data.statistics.entity.GwAuthClient; +import com.eactive.apim.gateway.data.statistics.entity.GwSvcInfo; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; +import java.time.YearMonth; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; import javax.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -23,7 +39,10 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; /** - * API 통계 Service + * API 통계 Service. + * + * 데이터 소스: Gateway DB(AGWAPP) API_STATS_HOUR(일별) / API_STATS_MONTH(월별). + * 통계 테이블에 org_id 가 없으므로 org 소속 앱의 CLIENT_ID 목록으로 필터한다. */ @Slf4j @Service @@ -31,79 +50,419 @@ import org.springframework.transaction.annotation.Transactional; @Transactional(readOnly = true) public class ApiStatisticsService { - private final CredentialRepository credentialRepository; - private final ApiStatisticsRepository apiStatisticsRepository; + /** 시간별 집계 스케줄러 잡명 기본값 — PTL_PROPERTY(Portal/{@value #JOB_NAME_PROP})로 재정의 가능. */ + private static final String DEFAULT_HOURLY_JOB_NAME = "ApiStatsHourlyAggregationJob"; + private static final String PROP_GROUP = "Portal"; + private static final String JOB_NAME_PROP = "stats.hourly.aggregation.job.name"; + + private static final DateTimeFormatter LATEST_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH시"); + private static final DateTimeFormatter YYYYMM_FMT = DateTimeFormatter.ofPattern("yyyyMM"); + private static final DateTimeFormatter DAY_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + /** 요일 한글 한 글자 (월요일=index 0). */ + private static final String[] WEEKDAYS = {"월", "화", "수", "목", "금", "토", "일"}; + + private final GwAuthClientRepository gwAuthClientRepository; + private final GwSvcInfoRepository gwSvcInfoRepository; + private final ApiStatsHourRepository apiStatsHourRepository; + private final ApiStatsDayRepository apiStatsDayRepository; + private final ApiStatsMonthRepository apiStatsMonthRepository; + private final JobInfoRepository jobInfoRepository; + private final PortalPropertyService portalPropertyService; /** - * 로그인 사용자 법인의 앱 목록 조회 (전체) + * 로그인 사용자 법인의 앱 목록 조회 (전체). TSEAIAU01.ORGID 기준. + * 드롭다운 값(CLIENTID)이 API_STATS_*.CLIENT_ID 와 동일해야 하므로 게이트웨이 인증 클라이언트를 소스로 한다. */ - public List getAppListByOrg(String orgId) { - return credentialRepository.findAllByOrgid(orgId); + public List getAppListByOrg(String orgId) { + return gwAuthClientRepository.findByOrgIdOrderByClientNameAsc(orgId); } /** - * 통계 조회 + * 조회 대상 clientId 목록. 특정 앱 선택 시 org 소속 검증 후 해당 1건, 아니면 org 전체 앱. + * org→clientId 는 TSEAIAU01.ORGID(=PTL_ORG.ID) 로 조회한다. + */ + private List resolveClientIds(String orgId, ApiStatisticsSearchDto searchDto) { + List orgClientIds = orgClientIds(orgId); + if (searchDto.hasSelectedApp()) { + String clientId = searchDto.getClientId(); + return orgClientIds.contains(clientId) ? Collections.singletonList(clientId) : Collections.emptyList(); + } + return orgClientIds; + } + + /** + * 통계 조회 (일별/월별 모드 분기). */ public ApiStatisticsResultDto getStatistics(String orgId, ApiStatisticsSearchDto searchDto) { - LocalDateTime startTime = searchDto.getStartDate().atStartOfDay(); - LocalDateTime endTime = searchDto.getEndDate().atTime(LocalTime.MAX); + log.debug("[통계] 조회 시작 - orgId={}, mode={}, clientId={}, 일별[{}~{}], 월별[{}]", + orgId, searchDto.getMode(), searchDto.getClientId(), + searchDto.getStartDate(), searchDto.getEndDate(), searchDto.getMonth()); + + List clientIds = resolveClientIds(orgId, searchDto); + log.debug("[통계] org 스코핑 clientId {}건 - {}", clientIds.size(), clientIds); + if (clientIds.isEmpty()) { + log.debug("[통계] 대상 clientId 없음 - 빈 결과 반환 (orgId={})", orgId); + return ApiStatisticsResultDto.empty(); + } ApiStatisticsSummaryDto summary; List details; + List periods; - if (searchDto.hasSelectedApp()) { - // 특정 앱 통계 - String clientId = searchDto.getClientId(); + if (searchDto.isMonthly()) { + String month = searchDto.getMonth(); + String currentYm = YearMonth.now().format(YYYYMM_FMT); + YearMonth ym = YearMonth.parse(month, YYYYMM_FMT); + boolean isCurrent = month.equals(currentYm); + LocalDate monthStart = ym.atDay(1); + LocalDate monthEnd = isCurrent ? LocalDate.now() : ym.atEndOfMonth(); - // 해당 앱이 조직 소속인지 검증 - if (!isAppBelongsToOrg(clientId, orgId)) { - return ApiStatisticsResultDto.empty(); + if (isCurrent) { + // 현재 월: DAY(과거일) + HOUR(오늘) 합산. 하단 일별표도 동일 소스. + log.debug("[통계] 월별-현재월 {} → DAY+오늘(HOUR) {}~{}", month, monthStart, monthEnd); + RangeStats rs = combineDayAndToday(clientIds, monthStart, monthEnd); + summary = rs.summary; + details = rs.details; + periods = rs.periods; + } else { + // 과거 월: 요약/API별은 API_STATS_MONTH, 하단 일별표는 해당 월의 DAY 일자별 + log.debug("[통계] 월별-과거월 {} → API_STATS_MONTH + 하단 DAY 일별", month); + summary = orEmpty(apiStatsMonthRepository.findSummary(clientIds, month, month)); + details = nvl(apiStatsMonthRepository.findDetail(clientIds, month, month)); + List monthlyDayRows = apiStatsDayRepository.findPeriodByDay(clientIds, monthStart, monthEnd); + periods = buildDailyPeriods(monthlyDayRows, monthStart, monthEnd); } - - summary = apiStatisticsRepository.findSummaryByOrgAndApp( - orgId, clientId, startTime, endTime); - details = apiStatisticsRepository.findDetailByOrgAndApp( - orgId, clientId, startTime, endTime); } else { - // 전체 앱 통계 - summary = apiStatisticsRepository.findSummaryByOrgAndPeriod( - orgId, startTime, endTime); - details = apiStatisticsRepository.findDetailByOrgAndPeriod( - orgId, startTime, endTime); + // 일별 조회: DAY(과거일) + HOUR(오늘) 합산. (HOUR는 보존 짧음/오늘만, DAY는 과거 보존) + LocalDate start = searchDto.getStartDate(); + LocalDate end = searchDto.getEndDate(); + log.debug("[통계] 일별 조회 - DAY+오늘(HOUR), 범위 {}~{}", start, end); + RangeStats rs = combineDayAndToday(clientIds, start, end); + summary = rs.summary; + details = rs.details; + periods = rs.periods; } - // null 체크 및 빈 결과 처리 if (summary == null) { summary = ApiStatisticsSummaryDto.empty(); } else { summary.calculateRates(); } - if (details == null) { details = Collections.emptyList(); } else { details.forEach(ApiStatisticsDetailDto::calculateRates); } + if (periods == null) { + periods = Collections.emptyList(); + } else { + periods.forEach(ApiStatisticsPeriodDto::calculateRates); + } - return new ApiStatisticsResultDto(summary, details); + // API_NAME(인터페이스 ID) → 표시명(TSEAIHE01.EAISVCDESC) 매핑 + applyDisplayNames(details); + + log.debug("[통계] 조회 완료 - 총호출={}, 성공={}, 타임아웃={}, 실패={} / API별 {}건, 기간별 {}건", + summary.getTotalCount(), summary.getSuccessCount(), summary.getTimeoutCount(), + summary.getErrorCount(), details.size(), periods.size()); + + return new ApiStatisticsResultDto(summary, details, periods); } /** - * 앱이 해당 조직 소속인지 검증 + * 일별 기간표 Object[] row → ApiStatisticsPeriodDto. + * row: [0]=일자(String), [1]=total, [2]=success, [3]=timeout, [4]=systemErr, [5]=biz */ - private boolean isAppBelongsToOrg(String clientId, String orgId) { - return credentialRepository.findByClientidAndOrgid(clientId, orgId).isPresent(); + private static ApiStatisticsPeriodDto toPeriodDto(Object[] row) { + return new ApiStatisticsPeriodDto( + row[0] != null ? row[0].toString() : null, + toLong(row[1]), toLong(row[2]), toLong(row[3]), toLong(row[4]), toLong(row[5])); + } + + private static Long toLong(Object o) { + return (o instanceof Number) ? ((Number) o).longValue() : 0L; } /** - * CSV 다운로드 + * 지정 날짜 범위 [start,end] 의 통계를 조립한다. + * 과거일은 API_STATS_DAY, "오늘"은 아직 DAY 미집계이므로 API_STATS_HOUR 에서 합산해 더한다. + */ + private RangeStats combineDayAndToday(List clientIds, LocalDate start, LocalDate end) { + LocalDate today = LocalDate.now(); + boolean includesToday = !today.isBefore(start) && !today.isAfter(end); + LocalDate dayEnd = includesToday ? today.minusDays(1) : end; + + ApiStatisticsSummaryDto summary = ApiStatisticsSummaryDto.empty(); + List details = new ArrayList<>(); + Map byDate = new LinkedHashMap<>(); + + // 과거일: API_STATS_DAY [start .. dayEnd] + if (!start.isAfter(dayEnd)) { + summary = orEmpty(apiStatsDayRepository.findSummary(clientIds, start, dayEnd)); + details = nvl(apiStatsDayRepository.findDetail(clientIds, start, dayEnd)); + byDate = rowsToMap(apiStatsDayRepository.findPeriodByDay(clientIds, start, dayEnd)); + } + + // 오늘: API_STATS_HOUR (익일 DAY 집계 전) + if (includesToday) { + LocalDateTime ts = today.atStartOfDay(); + LocalDateTime te = today.atTime(LocalTime.MAX); + ApiStatisticsSummaryDto todaySummary = orEmpty(apiStatsHourRepository.findSummary(clientIds, ts, te)); + List todayDetails = nvl(apiStatsHourRepository.findDetail(clientIds, ts, te)); + summary = mergeSummaries(summary, todaySummary); + details = mergeDetails(details, todayDetails); + if (nz(todaySummary.getTotalCount()) > 0) { + ApiStatisticsPeriodDto todayRow = new ApiStatisticsPeriodDto(); + todayRow.setPeriod(today.format(DAY_FMT)); + todayRow.setTotalCount(todaySummary.getTotalCount()); + todayRow.setSuccessCount(todaySummary.getSuccessCount()); + todayRow.setTimeoutCount(todaySummary.getTimeoutCount()); + todayRow.setErrorCount(todaySummary.getErrorCount()); + todayRow.setHasData(true); + byDate.put(todayRow.getPeriod(), todayRow); + } + log.debug("[통계] 오늘분 HOUR 합산 - total={}", todaySummary.getTotalCount()); + } + + return new RangeStats(summary, details, fillDailyPeriods(byDate, start, end)); + } + + private static Map rowsToMap(List rows) { + Map byDate = new LinkedHashMap<>(); + if (rows != null) { + for (Object[] r : rows) { + ApiStatisticsPeriodDto dto = toPeriodDto(r); + byDate.put(dto.getPeriod(), dto); + } + } + return byDate; + } + + /** + * 일별 기간표를 지정 날짜 범위의 '모든 일자'로 채운다(데이터 없는 날은 hasData=false → 표에 '-'). + * 각 행 라벨은 "yyyy-MM-dd (요일)". + */ + private static List buildDailyPeriods(List rows, LocalDate start, LocalDate end) { + return fillDailyPeriods(rowsToMap(rows), start, end); + } + + private static List fillDailyPeriods(Map byDate, + LocalDate start, LocalDate end) { + if (start == null || end == null) { + return new ArrayList<>(byDate.values()); + } + List result = new ArrayList<>(); + for (LocalDate d = start; !d.isAfter(end); d = d.plusDays(1)) { + String key = d.format(DAY_FMT); + String label = key + " (" + WEEKDAYS[d.getDayOfWeek().getValue() - 1] + ")"; + ApiStatisticsPeriodDto dto = byDate.get(key); + if (dto == null) { + dto = new ApiStatisticsPeriodDto(); + dto.setPeriod(label); + dto.setTotalCount(0L); + dto.setSuccessCount(0L); + dto.setTimeoutCount(0L); + dto.setErrorCount(0L); + } else { + dto.setPeriod(label); + } + result.add(dto); + } + return result; + } + + private static List nvl(List l) { + return l != null ? new ArrayList<>(l) : new ArrayList<>(); + } + + private static ApiStatisticsSummaryDto orEmpty(ApiStatisticsSummaryDto s) { + return s != null ? s : ApiStatisticsSummaryDto.empty(); + } + + private static long nz(Long v) { + return v != null ? v : 0L; + } + + /** + * 두 요약(DAY + 오늘 HOUR)을 합산. rate 는 이후 calculateRates 에서 재계산. + */ + private static ApiStatisticsSummaryDto mergeSummaries(ApiStatisticsSummaryDto a, ApiStatisticsSummaryDto b) { + ApiStatisticsSummaryDto m = new ApiStatisticsSummaryDto(); + m.setTotalCount(nz(a.getTotalCount()) + nz(b.getTotalCount())); + m.setSuccessCount(nz(a.getSuccessCount()) + nz(b.getSuccessCount())); + m.setTimeoutCount(nz(a.getTimeoutCount()) + nz(b.getTimeoutCount())); + m.setErrorCount(nz(a.getErrorCount()) + nz(b.getErrorCount())); + return m; + } + + /** + * API별 상세를 API_NAME 기준으로 합산(DAY + 오늘 HOUR). rate 는 이후 calculateRates 에서 재계산. + */ + private static List mergeDetails(List a, + List b) { + Map acc = new LinkedHashMap<>(); + accumulate(acc, a); + accumulate(acc, b); + List merged = new ArrayList<>(acc.values()); + merged.sort(Comparator.comparing(ApiStatisticsDetailDto::getTotalCount, Comparator.reverseOrder())); + return merged; + } + + private static void accumulate(Map acc, List list) { + if (list == null) { + return; + } + for (ApiStatisticsDetailDto d : list) { + ApiStatisticsDetailDto cur = acc.computeIfAbsent(d.getApiName(), k -> { + ApiStatisticsDetailDto n = new ApiStatisticsDetailDto(); + n.setApiName(k); + n.setTotalCount(0L); + n.setSuccessCount(0L); + n.setTimeoutCount(0L); + n.setErrorCount(0L); + return n; + }); + cur.setTotalCount(nz(cur.getTotalCount()) + nz(d.getTotalCount())); + cur.setSuccessCount(nz(cur.getSuccessCount()) + nz(d.getSuccessCount())); + cur.setTimeoutCount(nz(cur.getTimeoutCount()) + nz(d.getTimeoutCount())); + cur.setErrorCount(nz(cur.getErrorCount()) + nz(d.getErrorCount())); + } + } + + /** combineDayAndToday 반환 홀더. */ + private static final class RangeStats { + final ApiStatisticsSummaryDto summary; + final List details; + final List periods; + + RangeStats(ApiStatisticsSummaryDto summary, List details, + List periods) { + this.summary = summary; + this.details = details; + this.periods = periods; + } + } + + /** + * API별 상세의 apiName(인터페이스 ID)을 TSEAIHE01.EAISVCDESC(표시명)로 치환. + * 매핑 없으면 인터페이스 ID 를 그대로 둔다. + */ + private void applyDisplayNames(List details) { + if (details == null || details.isEmpty()) { + return; + } + Set ids = details.stream() + .map(ApiStatisticsDetailDto::getApiName) + .filter(n -> n != null && !n.trim().isEmpty()) + .collect(Collectors.toSet()); + if (ids.isEmpty()) { + return; + } + Map descMap = gwSvcInfoRepository.findBySvcNameIn(ids).stream() + .filter(s -> s.getSvcDesc() != null && !s.getSvcDesc().trim().isEmpty()) + .collect(Collectors.toMap(GwSvcInfo::getSvcName, GwSvcInfo::getSvcDesc, (x, y) -> x)); + details.forEach(d -> { + String desc = descMap.get(d.getApiName()); + if (desc != null && !desc.trim().isEmpty()) { + d.setApiName(desc); + } + }); + log.debug("[통계] API 표시명 매핑 - 대상 {}건, 매칭 {}건", ids.size(), descMap.size()); + } + + /** + * 월별 모드에서 선택 가능한 월 목록(YYYYMM, 최신순). + */ + public List getAvailableMonths(String orgId) { + List clientIds = orgClientIds(orgId); + List months = clientIds.isEmpty() + ? new ArrayList<>() + : new ArrayList<>(apiStatsMonthRepository.findAvailableMonths(clientIds)); + + // 데이터가 없더라도 현재 년-월은 항상 선택 가능하도록 포함 (최신순 유지) + String currentYm = YearMonth.now().format(YYYYMM_FMT); + if (!months.contains(currentYm)) { + months.add(0, currentYm); + } + log.debug("[통계] 선택가능 월 {}건 (현재월 {} 포함) - orgId={}", months.size(), currentYm, orgId); + return months; + } + + /** + * 최신 집계 시각 안내 문자열("yyyy-MM-dd HH시"). 데이터 없으면 null. + */ + public String getLatestStatTime(String orgId) { + List clientIds = orgClientIds(orgId); + if (clientIds.isEmpty()) { + return null; + } + LocalDateTime latest = apiStatsHourRepository.findLatestStatTime(clientIds); + String result = latest != null ? latest.format(LATEST_FMT) : null; + log.debug("[통계] 최신 집계 시각 - {} (orgId={})", result, orgId); + return result; + } + + /** + * 시간별 집계 잡의 크론에서 분(minute)을 추출한다. 파싱 불가 시 null → 문구는 "매시 집계"로 폴백. + * Quartz 크론 필드: [sec] [min] [hour] ... → 인덱스 1이 분. + */ + @Transactional + public Integer getAggregationMinute() { + try { + // 잡명은 PTL_PROPERTY(Portal/stats.hourly.aggregation.job.name)로 지정 가능(없으면 기본값 생성) + String jobName = portalPropertyService.getOrCreateProperty( + PROP_GROUP, JOB_NAME_PROP, DEFAULT_HOURLY_JOB_NAME, + "통계 시간별 집계 스케줄러 잡명(집계 주기 안내 문구용)"); + Integer minute = jobInfoRepository.findByJobname(jobName) + .map(JobInfoCron::minuteOf) + .orElse(null); + log.debug("[통계] 집계 크론 분(N) = {} (job={})", minute, jobName); + return minute; + } catch (Exception e) { + log.warn("집계 크론 조회 실패 - 안내 문구 분 표기 생략", e); + return null; + } + } + + private List orgClientIds(String orgId) { + return gwAuthClientRepository.findClientIdsByOrgId(orgId).stream() + .filter(id -> id != null && !id.trim().isEmpty()) + .collect(Collectors.toList()); + } + + /** + * 크론에서 분 필드를 추출하는 헬퍼. + */ + private static final class JobInfoCron { + static Integer minuteOf(com.eactive.apim.portal.apps.statistics.repository.entity.JobInfo job) { + String cron = job.getCronexp(); + if (cron == null) { + return null; + } + String[] fields = cron.trim().split("\\s+"); + if (fields.length >= 2 && fields[1].matches("^\\d{1,2}$")) { + return Integer.parseInt(fields[1]); + } + return null; + } + } + + /** + * CSV 다운로드. + * + * ※ UI에서 다운로드 버튼은 제거되었으나(요청사항), 백엔드 기능은 유지한다. + * 외부/직접 호출로 재사용될 수 있어 엔드포인트(/statistics/api/download)와 함께 남겨둔다. */ public void downloadCsv(String orgId, ApiStatisticsSearchDto searchDto, HttpServletResponse response) throws IOException { + log.debug("[통계] CSV 다운로드 - orgId={}, mode={}", orgId, searchDto.getMode()); ApiStatisticsResultDto result = getStatistics(orgId, searchDto); - String fileName = String.format("API_Statistics_%s_%s.csv", - searchDto.getStartDate().toString(), - searchDto.getEndDate().toString()); + String rangeLabel = searchDto.isMonthly() + ? searchDto.getMonth() + : searchDto.getStartDate() + " ~ " + searchDto.getEndDate(); + + String fileName = String.format("API_Statistics_%s.csv", + rangeLabel.replace(" ", "").replace("~", "_")); response.setContentType("text/csv; charset=UTF-8"); response.setHeader("Content-Disposition", "attachment; filename=\"" + URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()) + "\""); @@ -114,53 +473,52 @@ public class ApiStatisticsService { response.getOutputStream().write(0xBF); try (PrintWriter writer = new PrintWriter(new OutputStreamWriter(response.getOutputStream(), StandardCharsets.UTF_8), true)) { - // 조회 조건 - writer.println("조회 기간," + searchDto.getStartDate() + " ~ " + searchDto.getEndDate()); + writer.println("조회 기간," + rangeLabel); String appName = "전체 앱"; if (searchDto.hasSelectedApp()) { - appName = credentialRepository.findById(searchDto.getClientId()) - .map(Credential::getClientname) + appName = gwAuthClientRepository.findById(searchDto.getClientId()) + .map(GwAuthClient::getClientName) .orElse("선택된 앱"); } writer.println("앱 구분," + appName); writer.println(); - // 전체 통계 ApiStatisticsSummaryDto summary = result.getSummary(); writer.println("[전체 통계]"); - writer.println("총 호출 건," + summary.getTotalAttempted()); - writer.println("성공 건," + summary.getTotalCompleted() + "," + summary.getSuccessRate() + "%"); - writer.println("실패 건," + summary.getTotalFailed() + "," + summary.getFailureRate() + "%"); + writer.println("총 호출 건," + summary.getTotalCount()); + writer.println("성공 건," + summary.getSuccessCount() + "," + summary.getSuccessRate() + "%"); + writer.println("타임아웃 건," + summary.getTimeoutCount() + "," + summary.getTimeoutRate() + "%"); + writer.println("실패 건," + summary.getErrorCount() + "," + summary.getErrorRate() + "%"); writer.println(); - // API별 통계 헤더 writer.println("[API별 통계]"); - writer.println("API명,호출 건,성공 건,성공율,실패 건,실패율"); - - // API별 통계 데이터 + writer.println("API명,호출 건,성공 건,타임아웃 건,실패 건"); for (ApiStatisticsDetailDto detail : result.getDetails()) { - writer.println(String.format("%s,%d,%d,%s%%,%d,%s%%", + writer.println(String.format("%s,%d,%d,%d,%d", escapeCsv(detail.getApiName()), - detail.getAttemptedCount(), - detail.getCompletedCount(), - detail.getSuccessRate(), - detail.getFailedCount(), - detail.getFailureRate())); + detail.getTotalCount(), + detail.getSuccessCount(), + detail.getTimeoutCount(), + detail.getErrorCount())); } + writer.println(); - // 전체 합계 행 - writer.println(String.format("전체,%d,%d,%s%%,%d,%s%%", - summary.getTotalAttempted(), - summary.getTotalCompleted(), - summary.getSuccessRate(), - summary.getTotalFailed(), - summary.getFailureRate())); + writer.println("[기간별 통계]"); + writer.println("기간,호출 건,성공 건,타임아웃 건,실패 건"); + for (ApiStatisticsPeriodDto period : result.getPeriods()) { + writer.println(String.format("%s,%d,%d,%d,%d", + escapeCsv(period.getPeriod()), + period.getTotalCount(), + period.getSuccessCount(), + period.getTimeoutCount(), + period.getErrorCount())); + } } } /** - * CSV 값 이스케이프 (쉼표, 따옴표 처리) + * CSV 값 이스케이프 (쉼표, 따옴표 처리). */ private String escapeCsv(String value) { if (value == null) return ""; diff --git a/src/main/java/com/eactive/apim/portal/apps/user/controller/AccountController.java b/src/main/java/com/eactive/apim/portal/apps/user/controller/AccountController.java index 67ad0f1..2f90a16 100644 --- a/src/main/java/com/eactive/apim/portal/apps/user/controller/AccountController.java +++ b/src/main/java/com/eactive/apim/portal/apps/user/controller/AccountController.java @@ -1,28 +1,22 @@ package com.eactive.apim.portal.apps.user.controller; import com.eactive.apim.portal.apps.agreements.service.AgreementsFacade; -import com.eactive.apim.portal.apps.user.dto.PasswordChangeRequestDTO; -import com.eactive.apim.portal.apps.user.dto.PortalOrgRegistrationDTO; -import com.eactive.apim.portal.apps.user.dto.PortalUserDTO; -import com.eactive.apim.portal.apps.user.dto.UserAgreementDTO; -import com.eactive.apim.portal.apps.user.dto.ValidationResponse; +import com.eactive.apim.portal.apps.auth.twofactor.StepUpProtectedPaths; +import com.eactive.apim.portal.apps.auth.twofactor.TwoFactorProperties; +import com.eactive.apim.portal.apps.auth.twofactor.TwoFactorService; +import com.eactive.apim.portal.config.PasswordChangeEnforcementInterceptor; +import com.eactive.apim.portal.apps.session.service.UserSessionService; +import com.eactive.apim.portal.apps.user.dto.*; import com.eactive.apim.portal.apps.user.facade.OrgRegisterFacade; import com.eactive.apim.portal.apps.user.facade.UserFacade; import com.eactive.apim.portal.common.user.PortalAuthenticatedUser; +import com.eactive.apim.portal.common.util.PhoneNumberUtil; import com.eactive.apim.portal.common.util.SecurityUtil; import com.eactive.apim.portal.invitation.entity.UserInvitation; import com.eactive.apim.portal.invitation.entity.UserInvitationEnums.InvitationStatus; import com.eactive.apim.portal.invitation.repository.UserInvitationRepository; import com.eactive.apim.portal.portaluser.entity.PortalUserEnums; import com.eactive.apim.portal.portaluser.entity.PortalUserEnums.RoleCode; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,14 +27,19 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.ModelAttribute; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.support.RedirectAttributes; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + @Controller @Secured("ROLE_ACCOUNT") @RequiredArgsConstructor @@ -53,9 +52,19 @@ public class AccountController { private final AgreementsFacade agreementsFacade; private final com.eactive.apim.portal.apps.user.facade.AuthFacade authFacade; private final UserInvitationRepository userInvitationRepository; + private final UserSessionService userSessionService; + private final TwoFactorService twoFactorService; + private final TwoFactorProperties twoFactorProperties; - @PostMapping("/confirm_password") + /** 비밀번호 변경 화면 라이브 체크: 입력 중인 비밀번호에 아이디/휴대전화가 포함되는지 (민감정보는 응답에 미포함) */ + @PostMapping("/password/content-check") + public ResponseEntity> checkPasswordContent(@RequestParam String password) { + String currentLoginId = SecurityUtil.getCurrentLoginId(); + return ResponseEntity.ok(userFacade.checkPasswordContent(currentLoginId, password)); + } + + @PostMapping("/password/confirm") public ResponseEntity confirmPassword(@RequestParam String inputPassword) { String currentLoginId = SecurityUtil.getCurrentLoginId(); boolean isPasswordCorrect = userFacade.verifyCurrentPassword(currentLoginId, inputPassword); @@ -64,19 +73,29 @@ public class AccountController { return ResponseEntity.ok(new ValidationResponse(isPasswordCorrect, message)); } - @GetMapping("/change_password") - public String showChangePasswordPage(Model model) { + @GetMapping("/password/verify") + public String showChangePasswordPage(Model model, HttpSession session) { model.addAttribute("passwordChangeRequest", new PasswordChangeRequestDTO()); + // ENFORCE 강제 상태면 변경 페이지에 "변경/로그아웃" 강제 팝업을 띄운다. + if (Boolean.TRUE.equals(session.getAttribute("pwEnforce"))) { + model.addAttribute("forcedPasswordReset", true); + } return "apps/mypage/passwordChangeEntry"; } - @GetMapping("/new_password") - public String showNewPasswordPage(Model model) { + @GetMapping("/password/change") + public String showNewPasswordPage(Model model, HttpSession session) { model.addAttribute("passwordChangeRequest", new PasswordChangeRequestDTO()); + // 강제 변경(ENFORCE/만료) 진입 시 "변경/로그아웃" 강제 팝업을 띄운다. + if (isPasswordEnforced(session)) { + model.addAttribute("forcedPasswordReset", true); + } + // 반영 직전 2FA 필요 여부(강제 변경/2FA off 면 불필요) → 폼 JS 분기용 + model.addAttribute("twofaRequired", isPwChangeTwofaRequired(session)); return "apps/mypage/passwordChange"; } - @PostMapping("/verify_current_password") + @PostMapping("/password/verify") public String verifyCurrentPassword(@RequestParam String currentPassword, RedirectAttributes redirectAttributes, HttpSession session, Model model) { String currentLoginId = SecurityUtil.getCurrentLoginId(); if (userFacade.verifyCurrentPassword(currentLoginId, currentPassword)) { @@ -84,11 +103,11 @@ public class AccountController { return "apps/mypage/passwordChange"; } else { redirectAttributes.addFlashAttribute("error", "현재 비밀번호가 일치하지 않습니다."); - return "redirect:/change_password"; + return "redirect:/password/verify"; } } - @PostMapping("/mypage/change_new_password") + @PostMapping("/password/change") public String updatePassword(@RequestParam String newPassword, @RequestParam String confirmPassword, HttpSession session, @@ -97,33 +116,65 @@ public class AccountController { RedirectAttributes redirectAttributes, Model model) { try { + // 반영 직전 2FA: 통과권이 없으면 커밋하지 않고 폼으로 되돌린다(프론트가 먼저 2FA 팝업을 띄운다). + if (isPwChangeTwofaRequired(session) + && !twoFactorService.consumeStepUpPass(session, StepUpProtectedPaths.PASSWORD_CHANGE)) { + model.addAttribute("error", "추가 인증(2FA) 후 다시 시도해 주세요."); + model.addAttribute("passwordChangeRequest", new PasswordChangeRequestDTO()); + model.addAttribute("twofaRequired", true); + return "apps/mypage/passwordChange"; + } + String currentLoginId = SecurityUtil.getCurrentLoginId(); userFacade.updatePassword(currentLoginId, newPassword, confirmPassword); - // 비밀번호 만료 관련 세션 속성 제거 + // 비밀번호 만료/강제 관련 세션 속성 제거 session.removeAttribute("passwordExpired"); session.removeAttribute("success"); session.removeAttribute("redirectUrl"); + session.removeAttribute("pwEnforce"); + + // 세션 무효화 전에 DB 세션 레코드를 정리한다. + // SecurityContextLogoutHandler 는 HTTP 세션만 invalidate 하고 UserSession DB 레코드는 + // 남기므로(정상 로그아웃의 PortalLogoutSuccessHandler 경로를 우회), 정리하지 않으면 + // 재로그인 시 check-duplicate 가 이 옛 세션을 활성으로 보고 "이미 접속중" 으로 오탐한다. + userSessionService.removeSession(session.getId()); new SecurityContextLogoutHandler().logout(request, response, SecurityContextHolder.getContext().getAuthentication()); - redirectAttributes.addFlashAttribute("success", "비밀번호가 성공적으로 변경되었습니다."); + redirectAttributes.addFlashAttribute("success", + "비밀번호가 성공적으로 변경되었습니다.
새 비밀번호로 다시 로그인해주세요."); return "redirect:/login"; } catch (IllegalArgumentException e) { + // 검증 실패(비밀번호 규칙/이력 등) — 사용자에게 안내, 스택은 불필요 + logger.warn("비밀번호 변경 검증 실패: {}", e.getMessage()); model.addAttribute("error", e.getMessage()); model.addAttribute("passwordChangeRequest", new PasswordChangeRequestDTO()); + // 2FA 통과권은 이미 소비됨 → 재제출 시 다시 2FA 를 요구하도록 플래그 유지 + model.addAttribute("twofaRequired", isPwChangeTwofaRequired(session)); return "apps/mypage/passwordChange"; } catch (Exception e) { - model.addAttribute("error", e.getMessage()); + // 예기치 못한 오류(트랜잭션 롤백 등) — 원인 추적을 위해 스택은 남기되, + // 사용자에게는 시스템 예외 메시지를 노출하지 않고 일반 안내만 보여준다. + logger.error("비밀번호 변경 처리 중 오류", e); + model.addAttribute("error", "비밀번호 변경 중 오류가 발생했습니다. 잠시 후 다시 시도해 주세요."); model.addAttribute("passwordChangeRequest", new PasswordChangeRequestDTO()); + model.addAttribute("twofaRequired", isPwChangeTwofaRequired(session)); return "apps/mypage/passwordChange"; } } @GetMapping("/mypage") - public ModelAndView mypage() { + public ModelAndView mypage(HttpSession session) { + // 완화된 step-up(PASSWORD 레벨): 통과권이 없으면 현재 비밀번호 확인 페이지로 유도한다. + // (내 정보 변경은 2FA 대신 비밀번호 재확인만 요구) + if (isMypageStepUpActive() + && !twoFactorService.consumeStepUpPass(session, StepUpProtectedPaths.MYPAGE)) { + return new ModelAndView("redirect:/auth/stepup/password?returnUrl=" + StepUpProtectedPaths.MYPAGE); + } + ModelAndView mav = new ModelAndView(); try { @@ -139,11 +190,14 @@ public class AccountController { // 기존 user 객체도 유지 (다른 곳에서 필요할 수 있으므로) mav.addObject("user", user); + // 회원 탈퇴 팝업: 제출 전에 2FA 팝업을 띄울지 여부 + mav.addObject("withdrawTwofaRequired", isWithdrawTwofaRequired()); + // ROLE_USER인 경우 초대 여부 확인 if (currentUser.getRoleCode() == RoleCode.ROLE_USER) { java.util.Optional pendingInvitation = - userInvitationRepository.findFirstByInvitationEmailAndStatus( - user.getEmailAddr(), InvitationStatus.PENDING); + userInvitationRepository.findFirstByInvitationMobileAndStatus( + PhoneNumberUtil.normalize(currentUser.getMobileNumber()), InvitationStatus.PENDING); if (pendingInvitation.isPresent()) { mav.addObject("hasPendingInvitation", true); @@ -236,6 +290,8 @@ public class AccountController { return "redirect:/mypage"; } finally { cleanupAuthSession(session); + // 수정 저장 후 redirect:/mypage 로 되돌아갈 때 step-up 재요구를 막는다(1회 왕복 한정). + twoFactorService.grantStepUpPass(session, StepUpProtectedPaths.MYPAGE); } } @@ -248,6 +304,29 @@ public class AccountController { } } + /** 내 정보({@code /mypage}) 완화 step-up(비밀번호 재확인)이 현재 활성인지 */ + private boolean isMypageStepUpActive() { + return twoFactorProperties.isStepUpEnabled() + && twoFactorProperties.isStepUpPointEnabled(StepUpProtectedPaths.MYPAGE); + } + + /** 비밀번호 강제 변경 상태(ENFORCE/만료)인지 */ + private boolean isPasswordEnforced(HttpSession session) { + return Boolean.TRUE.equals(session.getAttribute(PasswordChangeEnforcementInterceptor.ENFORCE_SESSION_ATTR)) + || Boolean.TRUE.equals(session.getAttribute("passwordExpired")); + } + + /** + * 비밀번호 변경 반영 직전에 2FA 를 요구할지 여부. + * 전체/지점 step-up 스위치가 켜져 있고 강제 변경 상태가 아닐 때만 요구한다 + * (강제/만료 변경은 기존대로 2FA 없이 진행). + */ + private boolean isPwChangeTwofaRequired(HttpSession session) { + return twoFactorProperties.isStepUpEnabled() + && twoFactorProperties.isStepUpPointEnabled(StepUpProtectedPaths.PASSWORD_CHANGE) + && !isPasswordEnforced(session); + } + @GetMapping("/mypage/org-transfer") public String showOrgTransferPage(Model model) { try { @@ -262,7 +341,7 @@ public class AccountController { model.addAttribute("portalOrg", new PortalOrgRegistrationDTO()); model.addAttribute("registrationType", "corporate"); model.addAttribute("termsOfUse", agreementsFacade.getAgreement("TERMS_OF_USE")); - model.addAttribute("privacyCollect", agreementsFacade.getAgreement("PRIVACY_COLLECT_ORG")); + model.addAttribute("privacyCollect", agreementsFacade.getAgreement("PRIVACY_COLLECT")); model.addAttribute("registrationType", "corporate"); return "apps/mypage/orgTransfer"; @@ -333,21 +412,35 @@ public class AccountController { @PostMapping("/withdraw") public String processWithdrawal( + @RequestParam(value = "withdrawReason", required = false) String withdrawReason, HttpSession session, RedirectAttributes redirectAttributes) { try { + // 탈퇴 사유 필수 + if (withdrawReason == null || withdrawReason.trim().isEmpty()) { + redirectAttributes.addFlashAttribute("error", "탈퇴 사유를 입력해 주세요."); + return "redirect:/mypage"; + } + + // 반영 직전 2FA: 통과권이 없으면 커밋하지 않는다(프론트가 먼저 2FA 팝업을 띄운다). + if (isWithdrawTwofaRequired() + && !twoFactorService.consumeStepUpPass(session, StepUpProtectedPaths.WITHDRAW)) { + redirectAttributes.addFlashAttribute("error", "추가 인증(2FA) 후 다시 시도해 주세요."); + return "redirect:/mypage"; + } + // 현재 로그인한 사용자 정보 가져오기 PortalAuthenticatedUser currentUser = SecurityUtil.getPortalAuthenticatedUser(); // 회원 탈퇴 처리 if (currentUser != null) { - userFacade.withdrawUser(currentUser.getId()); + userFacade.withdrawUser(currentUser.getId(), withdrawReason.trim()); } session.invalidate(); SecurityContextHolder.clearContext(); - redirectAttributes.addFlashAttribute("success", "회원 탈퇴 신청이 완료 되었습니다. API Portal 회원 정보가 완전히 삭제 됩니다."); + redirectAttributes.addFlashAttribute("success", "회원 탈퇴가 완료 되었습니다. API Portal 회원 정보가 완전히 삭제 되었습니다."); return "redirect:/"; } catch (IllegalArgumentException e) { redirectAttributes.addFlashAttribute("error", e.getMessage()); @@ -355,6 +448,12 @@ public class AccountController { } } + /** 회원 탈퇴({@code /withdraw}) 반영 직전 2FA 를 요구할지 여부 */ + private boolean isWithdrawTwofaRequired() { + return twoFactorProperties.isStepUpEnabled() + && twoFactorProperties.isStepUpPointEnabled(StepUpProtectedPaths.WITHDRAW); + } + @GetMapping("/mypage/verification-email") public String showVerificationEmailPage(Model model) { try { @@ -362,7 +461,7 @@ public class AccountController { PortalAuthenticatedUser currentUser = SecurityUtil.getPortalAuthenticatedUser(); if (currentUser == null) { - return "redirect:/login"; + return "redirect:/login?reason=auth"; } // 사용자 이메일 주소를 모델에 추가 diff --git a/src/main/java/com/eactive/apim/portal/apps/user/controller/AuthController.java b/src/main/java/com/eactive/apim/portal/apps/user/controller/AuthController.java index b933c9c..e05e747 100644 --- a/src/main/java/com/eactive/apim/portal/apps/user/controller/AuthController.java +++ b/src/main/java/com/eactive/apim/portal/apps/user/controller/AuthController.java @@ -2,6 +2,7 @@ package com.eactive.apim.portal.apps.user.controller; import com.eactive.apim.portal.apps.user.dto.ValidationResponse; import com.eactive.apim.portal.apps.user.facade.AuthFacade; +import com.eactive.apim.portal.apps.user.service.PortalUserService; import com.eactive.apim.portal.apps.user.validator.EmailValidator; import com.eactive.apim.portal.common.validator.CellPhoneValidator; import org.springframework.web.bind.annotation.PostMapping; @@ -22,18 +23,22 @@ public class AuthController { private final AuthFacade authFacade; private final CellPhoneValidator cellPhoneValidator; private final EmailValidator emailValidator; + private final PortalUserService portalUserService; - public AuthController(AuthFacade authFacade, CellPhoneValidator cellPhoneValidator, EmailValidator emailValidator) { + public AuthController(AuthFacade authFacade, CellPhoneValidator cellPhoneValidator, EmailValidator emailValidator, + PortalUserService portalUserService) { this.authFacade = authFacade; this.cellPhoneValidator = cellPhoneValidator; this.emailValidator = emailValidator; + this.portalUserService = portalUserService; } @PostMapping("/request_auth_number") public ValidationResponse requestAuthNumber( @RequestParam(required = false) String mobileNumber, + @RequestParam(required = false) String purpose, HttpSession session) { ValidationResponse response = new ValidationResponse(); @@ -44,6 +49,16 @@ public class AuthController { return response; } + // 회원가입 흐름에서는 인증번호 발송 전에 휴대폰 번호 중복을 미리 검증한다. + // (중복 번호를 인증까지 마친 뒤 가입 단계에서야 거절되는 것을 방지) + // mobileNumber 는 저장 포맷과 동일한 하이픈 포함 형태이므로 sanitize 전 값으로 조회한다. + if ("signup".equals(purpose) && portalUserService.isMobileDuplicateCheckEnabled() + && portalUserService.existsByMobileNumber(mobileNumber)) { + response.setValid(false); + response.setMessage("이미 가입된 휴대폰 번호입니다."); + return response; + } + String sanitizedMobile = HtmlUtils.htmlEscape(mobileNumber.replace("-", "")); // 이전 세션 데이터 정리 diff --git a/src/main/java/com/eactive/apim/portal/apps/user/controller/OrgRegisterController.java b/src/main/java/com/eactive/apim/portal/apps/user/controller/OrgRegisterController.java index 3ae08aa..f58521c 100644 --- a/src/main/java/com/eactive/apim/portal/apps/user/controller/OrgRegisterController.java +++ b/src/main/java/com/eactive/apim/portal/apps/user/controller/OrgRegisterController.java @@ -11,6 +11,8 @@ import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.*; +import javax.servlet.http.HttpSession; + @Controller @RequestMapping("/signup/portalOrg") @RequiredArgsConstructor @@ -29,7 +31,8 @@ public class OrgRegisterController { model.addAttribute("authTtl", portalProperties.getAuthTtl()); model.addAttribute("portalOrg", new PortalOrgRegistrationDTO()); model.addAttribute("termsOfUse", agreementsFacade.getAgreement("TERMS_OF_USE")); - model.addAttribute("privacyCollect", agreementsFacade.getAgreement("PRIVACY_COLLECT_ORG")); + model.addAttribute("privacyCollect", agreementsFacade.getAgreement("PRIVACY_COLLECT")); + model.addAttribute("notificationConsent", agreementsFacade.getAgreement("NOTIFICATION_CONSENT")); return MAIN_ORG_REGISTER; } @@ -38,17 +41,28 @@ public class OrgRegisterController { @RequestParam("registrationScenario") String scenario, @ModelAttribute PortalOrgRegistrationDTO orgDTO, @ModelAttribute UserAgreementDTO agreementDTO, + HttpSession session, Model model) { try { ValidationResponse response; switch (scenario) { - case "new": + case "new": { + // 가입 폼에서 이메일 인증을 마쳤는지 세션으로 검증 (개인 경로와 동일, 클라 hidden 불신) + String verifiedEmail = (String) session.getAttribute("signupVerifiedEmail"); + boolean emailVerified = verifiedEmail != null + && verifiedEmail.equalsIgnoreCase(orgDTO.getLoginId()); response = orgRegisterFacade.registerNewOrgUser( orgDTO, - agreementDTO); + agreementDTO, + emailVerified); + if (response.isValid() && emailVerified) { + session.removeAttribute("signupVerifiedEmail"); + session.removeAttribute("signupEmailPending"); + } break; + } case "retain": response = orgRegisterFacade.convertToOrgUser( @@ -74,6 +88,7 @@ public class OrgRegisterController { if (response.isValid()) { model.addAttribute("message", response.getMessage()); + model.addAttribute("registrationType", "corporate"); return MAIN_REGISTER_RESULT; } else { setModelForErrorOrg(model, orgDTO, response.getMessage()); @@ -91,6 +106,7 @@ public class OrgRegisterController { model.addAttribute("portalOrg", orgDTO); model.addAttribute("error", errorMessage); model.addAttribute("termsOfUse", agreementsFacade.getAgreement("TERMS_OF_USE")); - model.addAttribute("privacyCollect", agreementsFacade.getAgreement("PRIVACY_COLLECT_ORG")); + model.addAttribute("privacyCollect", agreementsFacade.getAgreement("PRIVACY_COLLECT")); + model.addAttribute("notificationConsent", agreementsFacade.getAgreement("NOTIFICATION_CONSENT")); } } diff --git a/src/main/java/com/eactive/apim/portal/apps/user/controller/UserManRestController.java b/src/main/java/com/eactive/apim/portal/apps/user/controller/UserManRestController.java index f64ae36..3af6d6c 100644 --- a/src/main/java/com/eactive/apim/portal/apps/user/controller/UserManRestController.java +++ b/src/main/java/com/eactive/apim/portal/apps/user/controller/UserManRestController.java @@ -4,6 +4,7 @@ import com.eactive.apim.portal.apps.user.dto.PortalUserDTO; import com.eactive.apim.portal.apps.user.facade.UserManFacade; import com.eactive.apim.portal.common.dto.ResponseDTO; import com.eactive.apim.portal.common.util.SecurityUtil; +import com.eactive.apim.portal.common.validator.CellPhoneValidator; import lombok.RequiredArgsConstructor; import org.springframework.security.access.annotation.Secured; import org.springframework.web.bind.annotation.PostMapping; @@ -18,12 +19,16 @@ import org.springframework.web.bind.annotation.RestController; public class UserManRestController { private final UserManFacade userManFacade; + private final CellPhoneValidator cellPhoneValidator; // 1. Cancel invitation @PostMapping("/cancel-invitation") public ResponseDTO cancelInvitation(@RequestBody PortalUserDTO user) { - userManFacade.cancelInvitation(SecurityUtil.getPortalAuthenticatedUser(), user.getId()); - return new ResponseDTO(200, "SUCCESS", "초대가 취소되었습니다."); + boolean notifyConsent = user.isNotifyConsent(); + userManFacade.cancelInvitation(SecurityUtil.getPortalAuthenticatedUser(), user.getId(), notifyConsent); + return new ResponseDTO(200, "SUCCESS", notifyConsent + ? "초대가 취소되었습니다.
취소 알림이 발송되었습니다." + : "초대가 취소되었습니다.
(알림 수신 미동의로 메시지는 발송되지 않았습니다.)"); } // 2. Inactivate user @@ -40,25 +45,25 @@ public class UserManRestController { return new ResponseDTO(200, "SUCCESS", "사용자가 활성화되었습니다."); } - //5. register new user + //5. register new user (휴대폰 번호 기반 초대) @PostMapping("/invite") public ResponseDTO sendInvitation(@RequestBody PortalUserDTO newUser) { - String email = newUser.getEmailAddr(); + String mobile = newUser.getMobileNumber(); - // 이메일 형식 검증 - if (email == null || email.trim().isEmpty()) { - return new ResponseDTO(400, "ERROR", "이메일 주소를 입력해주세요."); + // 휴대폰 번호 검증 + if (mobile == null || mobile.trim().isEmpty()) { + return new ResponseDTO(400, "ERROR", "휴대폰 번호를 입력해주세요."); + } + if (!cellPhoneValidator.isValid(mobile, null)) { + return new ResponseDTO(400, "ERROR", "올바른 휴대폰 번호 형식을 입력해주세요."); } - // 이메일 정규식 검증 - String emailPattern = "^[a-zA-Z0-9._%+\\-]+@[a-zA-Z0-9.\\-]+\\.[a-zA-Z]{2,}$"; - if (!email.matches(emailPattern)) { - return new ResponseDTO(400, "ERROR", "올바른 이메일 형식을 입력해주세요."); - } + boolean notifyConsent = newUser.isNotifyConsent(); + userManFacade.sendInvitation(SecurityUtil.getPortalAuthenticatedUser(), mobile, notifyConsent); - userManFacade.sendInvitation(SecurityUtil.getPortalAuthenticatedUser(), email); - - return new ResponseDTO(200, "SUCCESS", "요청 메일이 발송되었습니다."); + return new ResponseDTO(200, "SUCCESS", notifyConsent + ? "초대 메시지가 발송되었습니다." + : "초대가 등록되었습니다.
(알림 수신 미동의로 메시지는 발송되지 않았습니다.)"); } @PostMapping("/change-role") @@ -69,6 +74,20 @@ public class UserManRestController { return new ResponseDTO(200, "SUCCESS", "변경되었습니다. 확인 버튼을 누르시면 계정을 로그아웃 합니다."); } + // 관리자 -> 개발자 변경 (본인 제외) + @PostMapping("/revoke-manager") + public ResponseDTO revokeManager(@RequestBody PortalUserDTO user) { + userManFacade.revokeManager(SecurityUtil.getPortalAuthenticatedUser(), user.getId()); + return new ResponseDTO(200, "SUCCESS", "개발자로 변경되었습니다."); + } + + // 소속 제외 -> 개인이용자로 전환 + @PostMapping("/remove-from-org") + public ResponseDTO removeFromOrg(@RequestBody PortalUserDTO user) { + userManFacade.removeFromOrg(SecurityUtil.getPortalAuthenticatedUser(), user.getId()); + return new ResponseDTO(200, "SUCCESS", "소속에서 제외되어 개인이용자로 변경되었습니다."); + } + // 6. Resend invitation @PostMapping("/resend-invitation") public ResponseDTO resendInvitation(@RequestBody PortalUserDTO user) { diff --git a/src/main/java/com/eactive/apim/portal/apps/user/controller/UserRegisterController.java b/src/main/java/com/eactive/apim/portal/apps/user/controller/UserRegisterController.java index f6f872c..f3cd2d8 100644 --- a/src/main/java/com/eactive/apim/portal/apps/user/controller/UserRegisterController.java +++ b/src/main/java/com/eactive/apim/portal/apps/user/controller/UserRegisterController.java @@ -4,11 +4,14 @@ import com.eactive.apim.portal.apps.agreements.service.AgreementsFacade; import com.eactive.apim.portal.apps.user.dto.PortalUserRegistrationDTO; import com.eactive.apim.portal.apps.user.dto.UserAgreementDTO; import com.eactive.apim.portal.apps.user.dto.ValidationResponse; +import com.eactive.apim.portal.apps.user.facade.AuthFacade; import com.eactive.apim.portal.apps.user.facade.UserRegisterFacade; import com.eactive.apim.portal.apps.user.repository.PortalOrgRepository; +import com.eactive.apim.portal.apps.user.service.PortalUserAuthService; import com.eactive.apim.portal.apps.user.service.PortalUserService; import com.eactive.apim.portal.apps.user.validator.AgreementValidator; import com.eactive.apim.portal.common.util.EncryptionUtil; +import com.eactive.apim.portal.common.util.PhoneNumberUtil; import com.eactive.apim.portal.common.util.SecurityUtil; import com.eactive.apim.portal.config.PortalProperties; import com.eactive.apim.portal.invitation.entity.UserInvitation; @@ -16,14 +19,19 @@ import com.eactive.apim.portal.invitation.entity.UserInvitationEnums; import com.eactive.apim.portal.invitation.repository.UserInvitationRepository; import com.eactive.apim.portal.portalorg.entity.PortalOrg; import com.eactive.apim.portal.portaluser.entity.PortalUser; +import com.eactive.apim.portal.portaluser.entity.PortalUserEnums; import com.eactive.apim.portal.portaluser.repository.PortalUserRepository; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; +import java.util.Map; import java.util.Optional; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestBody; import javax.crypto.BadPaddingException; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; +import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import javax.validation.Valid; @@ -50,6 +58,7 @@ public class UserRegisterController { private final PortalUserService portalUserService; private final UserRegisterFacade userRegisterFacade; + private final AuthFacade authFacade; private final PortalUserRepository portalUserRepository; private final PortalOrgRepository portalOrgRepository; private final AgreementsFacade agreementsFacade; @@ -57,6 +66,7 @@ public class UserRegisterController { private final UserInvitationRepository userInvitationRepository; private final EncryptionUtil encryptionUtil; private final AgreementValidator agreementValidator; + private final PortalUserAuthService portalUserAuthService; @GetMapping("/signup") public String showSignupSelection() { @@ -67,8 +77,6 @@ public class UserRegisterController { public String getUserAgreement(@RequestParam(name = "invitation", required = false) String invitationToken, HttpSession session, Model model) { boolean isInvited = false; String orgName = null; - String email = null; - String[] parts = null; if (invitationToken != null) { // 8글자 토큰 사용 @@ -80,29 +88,23 @@ public class UserRegisterController { String orgId = invitation.get().getOrgId(); Optional org = portalOrgRepository.findById(orgId); orgName = org.map(PortalOrg::getOrgName).orElse(null); - email = invitation.get().getInvitationEmail(); - parts = email.split("@"); } } model.addAttribute("registrationType", isInvited ? "corporate" : "personal"); model.addAttribute("isInvited", isInvited); model.addAttribute("orgName", orgName); - model.addAttribute("email", email); - model.addAttribute("loginId", email); - - if (parts != null) { - model.addAttribute("emailId", parts[0]); - model.addAttribute("domain", parts[1]); - } else { - model.addAttribute("emailId", ""); - model.addAttribute("domain", ""); - } + // 휴대폰 번호 기반 초대 전환: 이메일/로그인ID 는 가입자가 직접 입력한다 + model.addAttribute("email", ""); + model.addAttribute("loginId", ""); + model.addAttribute("emailId", ""); + model.addAttribute("domain", ""); model.addAttribute("authTtl", portalProperties.getAuthTtl()); model.addAttribute("portalUser", new PortalUserRegistrationDTO()); model.addAttribute("termsOfUse", agreementsFacade.getAgreement("TERMS_OF_USE")); - model.addAttribute("privacyCollect", agreementsFacade.getAgreement(isInvited ? "PRIVACY_COLLECT_ORG" : "PRIVACY_COLLECT_IND")); + model.addAttribute("privacyCollect", agreementsFacade.getAgreement("PRIVACY_COLLECT")); + model.addAttribute("notificationConsent", agreementsFacade.getAgreement("NOTIFICATION_CONSENT")); return MAIN_USER_REGISTER; @@ -113,6 +115,7 @@ public class UserRegisterController { @Valid @ModelAttribute("portalUser") PortalUserRegistrationDTO portalUserRegistrationDTO, BindingResult bindingResult, HttpSession session, + HttpServletRequest request, RedirectAttributes redirectAttributes, Model model) { try { @@ -138,8 +141,32 @@ public class UserRegisterController { } // 성공 시 PRG 패턴 적용: 결과 페이지로 리다이렉트 session.removeAttribute("invitationToken"); + + // 개인 가입자 처리 분기 + if (invitationToken == null) { + Optional registered = portalUserService.findByLoginId(portalUserRegistrationDTO.getLoginId()); + + // 이메일 인증을 마쳐 ACTIVE 로 저장된 경우 → 자동 로그인(2차 인증 없이) 후 가입 완료 페이지 노출 + // (완료 페이지의 '홈으로' 버튼으로 로그인 상태 그대로 메인 이동) + if (registered.isPresent() + && PortalUserEnums.UserStatus.ACTIVE.equals(registered.get().getUserStatus())) { + portalUserAuthService.autoLoginAfterSignup(registered.get(), request); + redirectAttributes.addFlashAttribute("message", "회원가입이 완료되었습니다."); + return "redirect:/signup/complete"; + } + + // 이메일 미인증(READY) → 회원가입 직후 이메일 인증 단계로 이동(기존 흐름 유지) + if (registered.isPresent() + && PortalUserEnums.UserStatus.READY.equals(registered.get().getUserStatus())) { + session.setAttribute("signupVerificationEmail", registered.get().getEmailAddr()); + return "redirect:/signup/verification-email"; + } + redirectAttributes.addFlashAttribute("message", "회원가입이 완료되었습니다."); + return "redirect:/signup/complete"; + } + redirectAttributes.addFlashAttribute("message", "회원가입이 완료되었습니다."); - return invitationToken != null ? "redirect:/signup/complete/corporate" : "redirect:/signup/complete"; + return "redirect:/signup/complete/corporate"; } catch (Exception e) { setModelForError(session, model, agreement, portalUserRegistrationDTO, "처리 중 오류가 발생했습니다: " + e.getMessage()); @@ -169,6 +196,101 @@ public class UserRegisterController { return MAIN_EMAIL_COMPLETED; } + /** + * 회원가입 직후 이메일 인증 페이지. + * 가입 단계에서 세션에 저장된 이메일이 있어야 진입 가능하다. + */ + @GetMapping("/signup/verification-email") + public String showSignupVerificationEmail(HttpSession session, Model model) { + String email = (String) session.getAttribute("signupVerificationEmail"); + if (email == null) { + return "redirect:/login"; + } + model.addAttribute("email", email); + return "apps/register/signupVerificationEmail"; + } + + /** + * 회원가입 폼 내 이메일 인증코드 발송. 형식·중복 선검증 후 발송하고, 세션에 대상 이메일을 저장한다. + * (가입 완료 전, 폼에서 인라인으로 호출) + */ + @PostMapping("/signup/email-code/send") + public ResponseEntity sendSignupFormEmailCode(@RequestParam String email, + HttpSession session) { + String normalized = email != null ? email.trim().toLowerCase() : null; + + // 형식 + 중복 선검증 (중복 이메일을 인증까지 마친 뒤 가입 단계에서 거절되는 것을 방지) + ValidationResponse check = userRegisterFacade.handleCheckNewEmail(normalized); + if (!check.isValid()) { + return ResponseEntity.ok(check); + } + + ValidationResponse response = authFacade.requestAuth(normalized, "EMAIL"); + if (response.isValid()) { + session.setAttribute("signupEmailPending", normalized); + session.removeAttribute("signupVerifiedEmail"); + } + return ResponseEntity.ok(response); + } + + /** + * 회원가입 폼 내 이메일 인증코드 검증. 성공 시 세션에 인증 완료 이메일을 저장한다. + * (가입 제출 시 서버가 이 값과 DTO 이메일 일치를 재검증한다) + */ + @PostMapping("/signup/email-code/verify") + public ResponseEntity verifySignupFormEmailCode(@RequestParam String email, + @RequestParam String code, + HttpSession session) { + String normalized = email != null ? email.trim().toLowerCase() : null; + String pending = (String) session.getAttribute("signupEmailPending"); + if (pending == null || !pending.equalsIgnoreCase(normalized)) { + return ResponseEntity.ok(new ValidationResponse(false, "인증 요청된 이메일과 일치하지 않습니다.")); + } + + ValidationResponse response = authFacade.verifyAuthNumber(normalized, code); + if (response.isValid()) { + session.setAttribute("signupVerifiedEmail", normalized); + } + return ResponseEntity.ok(response); + } + + /** + * 회원가입 이메일 인증코드 발송. 임의 이메일 타깃 방지를 위해 세션에 저장된 가입 이메일만 사용한다. + */ + @PostMapping("/signup/send-verification-code") + public ResponseEntity sendSignupVerificationCode(HttpSession session) { + String email = (String) session.getAttribute("signupVerificationEmail"); + if (email == null) { + return ResponseEntity.badRequest() + .body(new ValidationResponse(false, "유효하지 않은 요청입니다. 회원가입을 다시 진행해주세요.")); + } + ValidationResponse response = authFacade.requestAuth(email, "EMAIL"); + return ResponseEntity.ok(response); + } + + /** + * 회원가입 이메일 인증코드 검증. 성공 시 사용자 상태를 READY → ACTIVE 로 활성화한다. + */ + @PostMapping("/signup/verify-email-code") + public ResponseEntity verifySignupEmailCode(@RequestBody Map request, + HttpSession session) { + String email = (String) session.getAttribute("signupVerificationEmail"); + if (email == null) { + return ResponseEntity.badRequest() + .body(new ValidationResponse(false, "유효하지 않은 요청입니다. 회원가입을 다시 진행해주세요.")); + } + + String code = request.get("code"); + ValidationResponse response = authFacade.verifyAuthNumber(email, code); + + if (response.isValid()) { + PortalUser user = portalUserService.findByEmailAddr(email); + portalUserService.activateUser(user.getId()); + session.removeAttribute("signupVerificationEmail"); + } + return ResponseEntity.ok(response); + } + @GetMapping("/signup/decision") public String showDecisionPage(@RequestParam(name = "invitation", required = false) String invitationToken, HttpSession session, @@ -181,6 +303,11 @@ public class UserRegisterController { String decodedToken = decodeInvitationToken(invitationToken); session.setAttribute("decisionToken", decodedToken); + // 이미 로그인된 상태면 수락 화면으로 직행 + if (SecurityUtil.isAuthenticated()) { + return "redirect:/signup/decision_process"; + } + Optional invitation = userInvitationRepository.findByToken(decodedToken); if (!invitation.isPresent() || invitation.get().getStatus() != UserInvitationEnums.InvitationStatus.PENDING) { @@ -188,9 +315,9 @@ public class UserRegisterController { return "redirect:/"; } - // 이메일로 사용자 정보 조회 - String email = invitation.get().getInvitationEmail(); - Optional portalUser = portalUserRepository.findPortalUserByEmailAddr(email); + // 휴대폰 번호로 사용자 정보 조회 + String mobile = invitation.get().getInvitationMobile(); + Optional portalUser = portalUserRepository.findAllByMobileNumber(mobile).stream().findFirst(); if (!portalUser.isPresent()) { model.addAttribute("error", "사용자 정보를 찾을 수 없습니다."); @@ -219,13 +346,14 @@ public class UserRegisterController { public String showDecisionProcessPage(HttpSession session, Model model) { session.removeAttribute("decisionToken"); - Optional invitation = userInvitationRepository.findFirstByInvitationEmailAndStatus(SecurityUtil.getPortalAuthenticatedUser().getLoginId(), UserInvitationEnums.InvitationStatus.PENDING); + String mobile = PhoneNumberUtil.normalize(SecurityUtil.getPortalAuthenticatedUser().getMobileNumber()); + Optional invitation = userInvitationRepository.findFirstByInvitationMobileAndStatus(mobile, UserInvitationEnums.InvitationStatus.PENDING); if (!invitation.isPresent()) { model.addAttribute("error", "유효하지 않은 초대입니다"); return "redirect:/"; } - if (!SecurityUtil.getPortalAuthenticatedUser().getLoginId().equals(invitation.get().getInvitationEmail())) { + if (mobile == null || !mobile.equals(invitation.get().getInvitationMobile())) { model.addAttribute("error", "유효하지 않은 초대입니다"); return "redirect:/"; } @@ -241,7 +369,9 @@ public class UserRegisterController { model.addAttribute("orgName", org.get().getOrgName()); model.addAttribute("invitationCode", invitation.get().getToken()); model.addAttribute("termsOfUse", agreementsFacade.getAgreement("TERMS_OF_USE")); - model.addAttribute("privacyCollect", agreementsFacade.getAgreement("PRIVACY_COLLECT_ORG")); + model.addAttribute("privacyCollect", agreementsFacade.getAgreement("PRIVACY_COLLECT")); + model.addAttribute("notificationConsent", agreementsFacade.getAgreement("NOTIFICATION_CONSENT")); + model.addAttribute("agreementTitle", "법인 회원 전환을 위한 약관 동의"); model.addAttribute("registrationType", "corporate"); return "apps/register/userDecisionProcess"; @@ -260,7 +390,7 @@ public class UserRegisterController { agreementValidator.validate(agreement, bindingResult); - Optional invitation = userInvitationRepository.findFirstByInvitationEmailAndStatus(SecurityUtil.getPortalAuthenticatedUser().getLoginId(), UserInvitationEnums.InvitationStatus.PENDING); + Optional invitation = userInvitationRepository.findFirstByInvitationMobileAndStatus(PhoneNumberUtil.normalize(SecurityUtil.getPortalAuthenticatedUser().getMobileNumber()), UserInvitationEnums.InvitationStatus.PENDING); if (action.equalsIgnoreCase("accept") && bindingResult.hasErrors()) { @@ -270,13 +400,20 @@ public class UserRegisterController { model.addAttribute("userName", SecurityUtil.getPortalAuthenticatedUser().getUsername()); model.addAttribute("orgName", org.get().getOrgName()); model.addAttribute("termsOfUse", agreementsFacade.getAgreement("TERMS_OF_USE")); - model.addAttribute("privacyCollect", agreementsFacade.getAgreement("PRIVACY_COLLECT_ORG")); + model.addAttribute("privacyCollect", agreementsFacade.getAgreement("PRIVACY_COLLECT")); + model.addAttribute("notificationConsent", agreementsFacade.getAgreement("NOTIFICATION_CONSENT")); + model.addAttribute("agreementTitle", "법인 회원 전환을 위한 약관 동의"); model.addAttribute("registrationType", "corporate"); return "apps/register/userDecisionProcess"; } else { ValidationResponse response = userRegisterFacade.processInvitation(action, invitation.get()); + // 수락 성공 시 재로그인 없이 본인 세션 권한 갱신 (ROLE_USER → ROLE_CORP_USER) + if ("accept".equalsIgnoreCase(action) && response.isValid()) { + portalUserAuthService.reloadCurrentAuthentication(); + } + // 초대 처리 완료 후 세션에서 초대 관련 속성 제거 session.removeAttribute("pendingInvitation"); session.removeAttribute("pendingInvitationToken"); @@ -294,24 +431,6 @@ public class UserRegisterController { return "redirect:/"; } - @GetMapping("/validate_email") - public String validateUserEmail(@RequestParam(name = "token", required = false) String token) { - String decodedToken = new String(Base64.decode(token)); - try { - String decToken = encryptionUtil.decrypt(decodedToken); - String[] tokens = decToken.split(":"); - boolean approvalRequired = portalUserService.activateUser(tokens[1]); - if (approvalRequired) { - return "apps/register/emailValidationResultCorpManager"; - } else { - return "apps/register/emailValidationResultUser"; - } - } catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException | IllegalBlockSizeException | - BadPaddingException e) { - throw new IllegalArgumentException("인증 정보 오류. 관리자 문의하세요."); - } - - } // 에러 발생 시 모델 세팅을 위한 private 메서드 private void setModelForError(HttpSession session, Model model, UserAgreementDTO agreement, @@ -325,7 +444,8 @@ public class UserRegisterController { model.addAttribute("agreement", agreement); model.addAttribute("termsOfUse", agreementsFacade.getAgreement("TERMS_OF_USE")); - model.addAttribute("privacyCollect", agreementsFacade.getAgreement("PRIVACY_COLLECT_IND")); + model.addAttribute("privacyCollect", agreementsFacade.getAgreement("PRIVACY_COLLECT")); + model.addAttribute("notificationConsent", agreementsFacade.getAgreement("NOTIFICATION_CONSENT")); model.addAttribute("msgType", "sms"); } @@ -437,10 +557,10 @@ public class UserRegisterController { session.removeAttribute("invitationCodeAttempts"); session.removeAttribute("invitationCodeLockoutUntil"); - // 기존 사용자 여부 확인 - Optional existingUser = portalUserRepository.findPortalUserByEmailAddr(invitation.getInvitationEmail()); + // 기존 사용자 여부 확인 (휴대폰 번호 기반) + boolean existingUser = !portalUserRepository.findAllByMobileNumber(invitation.getInvitationMobile()).isEmpty(); - if (existingUser.isPresent()) { + if (existingUser) { // 기존 사용자: 초대 수락 페이지로 이동 return "redirect:/signup/decision?invitation=" + normalizedCode; } else { diff --git a/src/main/java/com/eactive/apim/portal/apps/user/dto/PortalUserDTO.java b/src/main/java/com/eactive/apim/portal/apps/user/dto/PortalUserDTO.java index a5af579..0f9f1a0 100644 --- a/src/main/java/com/eactive/apim/portal/apps/user/dto/PortalUserDTO.java +++ b/src/main/java/com/eactive/apim/portal/apps/user/dto/PortalUserDTO.java @@ -23,6 +23,9 @@ public class PortalUserDTO implements Serializable { private String emailAddr; + // 초대 시 알림 수신 동의 여부 (false 면 초대 레코드만 생성, 메시지 미발송) + private boolean notifyConsent; + private PortalOrgDTO portalOrg; private UserStatus userStatus; diff --git a/src/main/java/com/eactive/apim/portal/apps/user/dto/PortalUserRegistrationDTO.java b/src/main/java/com/eactive/apim/portal/apps/user/dto/PortalUserRegistrationDTO.java index 238ca57..de74130 100644 --- a/src/main/java/com/eactive/apim/portal/apps/user/dto/PortalUserRegistrationDTO.java +++ b/src/main/java/com/eactive/apim/portal/apps/user/dto/PortalUserRegistrationDTO.java @@ -3,7 +3,7 @@ package com.eactive.apim.portal.apps.user.dto; import com.eactive.apim.portal.common.validator.AuthNumberMatch; import com.eactive.apim.portal.common.validator.CellPhone; import com.eactive.apim.portal.common.validator.PasswordMatch; -import com.eactive.apim.portal.common.validator.PasswordRuleForDjbank; +import com.eactive.apim.portal.common.validator.PasswordRule; import lombok.Data; import org.hibernate.validator.constraints.Length; import org.hibernate.validator.constraints.NotEmpty; @@ -12,7 +12,7 @@ import org.hibernate.validator.constraints.NotEmpty; @AuthNumberMatch(recipient = "loginId", authField = "authNumber") @PasswordMatch(input = "password", confirm = "password2") @Data -@PasswordRuleForDjbank(password = "password", loginId = "loginId", mobile = "mobileNumber") +@PasswordRule(password = "password", loginId = "loginId", mobile = "mobileNumber") public class PortalUserRegistrationDTO { /** diff --git a/src/main/java/com/eactive/apim/portal/apps/user/dto/UserRegisterDTO.java b/src/main/java/com/eactive/apim/portal/apps/user/dto/UserRegisterDTO.java index 3a0bfc6..83058f1 100644 --- a/src/main/java/com/eactive/apim/portal/apps/user/dto/UserRegisterDTO.java +++ b/src/main/java/com/eactive/apim/portal/apps/user/dto/UserRegisterDTO.java @@ -1,7 +1,7 @@ package com.eactive.apim.portal.apps.user.dto; import com.eactive.apim.portal.common.validator.PasswordMatch; -import com.eactive.apim.portal.common.validator.PasswordRuleForDjbank; +import com.eactive.apim.portal.common.validator.PasswordRule; import com.eactive.apim.portal.common.validator.UniqueId; import com.eactive.apim.portal.portaluser.entity.UserStatus; import lombok.Data; @@ -13,7 +13,7 @@ import java.io.Serializable; @PasswordMatch(input = "password", confirm = "password2") @Data -@PasswordRuleForDjbank(loginId = "userId", password = "password", mobile = "mobilePhone") +@PasswordRule(loginId = "userId", password = "password", mobile = "mobilePhone") public class UserRegisterDTO implements Serializable { diff --git a/src/main/java/com/eactive/apim/portal/apps/user/facade/AuthFacadeImpl.java b/src/main/java/com/eactive/apim/portal/apps/user/facade/AuthFacadeImpl.java index 371f87d..0c3776e 100644 --- a/src/main/java/com/eactive/apim/portal/apps/user/facade/AuthFacadeImpl.java +++ b/src/main/java/com/eactive/apim/portal/apps/user/facade/AuthFacadeImpl.java @@ -1,5 +1,6 @@ package com.eactive.apim.portal.apps.user.facade; +import com.eactive.apim.portal.apps.auth.AuthNoticeProperties; import com.eactive.apim.portal.apps.auth.service.AuthNumberService; import com.eactive.apim.portal.apps.user.dto.ValidationResponse; import lombok.RequiredArgsConstructor; @@ -13,8 +14,15 @@ import org.slf4j.LoggerFactory; public class AuthFacadeImpl implements AuthFacade { private final AuthNumberService authNumberService; + private final AuthNoticeProperties authNoticeProperties; private static final Logger log = LoggerFactory.getLogger(AuthFacadeImpl.class); + /** + * 회원가입·아이디/비밀번호 찾기 등 로그인 이전 흐름은 수신자 이름을 알 수 없으므로 + * 메시지 템플릿 %USER_NAME% 자리에 넣을 기본값. + */ + private static final String GUEST_USER_NAME = "guest"; + /** * 인증 요청 @@ -41,10 +49,13 @@ public class AuthFacadeImpl implements AuthFacade { } try { - String generatedAuthNumber = authNumberService.sendRequestAuthNumber(recipientKey, msgType); + String generatedAuthNumber = authNumberService.sendRequestAuthNumber(recipientKey, msgType, GUEST_USER_NAME); response.setValid(true); response.setMessage("인증번호를 발송하였습니다."); - response.setAuthNumber(generatedAuthNumber); // 테스트 환경에서 인증번호 표시용 + // 테스트 환경(PTL_PROPERTY auth.test-notice.enabled=true, prod 제외)에서만 인증번호를 응답에 노출 + if (authNoticeProperties.isTestNoticeEnabled()) { + response.setAuthNumber(generatedAuthNumber); + } } catch (Exception e) { response.setValid(false); response.setMessage(e.getMessage()); diff --git a/src/main/java/com/eactive/apim/portal/apps/user/facade/OrgRegisterFacade.java b/src/main/java/com/eactive/apim/portal/apps/user/facade/OrgRegisterFacade.java index b7ac80a..e916f8c 100644 --- a/src/main/java/com/eactive/apim/portal/apps/user/facade/OrgRegisterFacade.java +++ b/src/main/java/com/eactive/apim/portal/apps/user/facade/OrgRegisterFacade.java @@ -7,10 +7,11 @@ import org.springframework.http.ResponseEntity; public interface OrgRegisterFacade { - // 신규 법인 회원 등록 + // 신규 법인 회원 등록 (emailVerified: 가입 폼에서 이메일 인증 완료 시 true → 바로 ACTIVE) ValidationResponse registerNewOrgUser( PortalOrgRegistrationDTO orgDTO, - UserAgreementDTO agreementDTO) ; + UserAgreementDTO agreementDTO, + boolean emailVerified) ; // 기존 회원의 법인 전환 ValidationResponse convertToOrgUser( diff --git a/src/main/java/com/eactive/apim/portal/apps/user/facade/OrgRegisterFacadeImpl.java b/src/main/java/com/eactive/apim/portal/apps/user/facade/OrgRegisterFacadeImpl.java index f2f39c7..2786224 100644 --- a/src/main/java/com/eactive/apim/portal/apps/user/facade/OrgRegisterFacadeImpl.java +++ b/src/main/java/com/eactive/apim/portal/apps/user/facade/OrgRegisterFacadeImpl.java @@ -54,6 +54,7 @@ public class OrgRegisterFacadeImpl implements OrgRegisterFacade { private final FileService fileService; private final BasicValidationService validationService; private final UserRegistrationValidationService userRegistrationValidationService; + private final com.eactive.apim.portal.apps.user.validator.PasswordValidator passwordValidator; private final PasswordEncoder passwordEncoder; private final AgreementValidator agreementValidator; private final ApprovalService approvalService; @@ -64,7 +65,8 @@ public class OrgRegisterFacadeImpl implements OrgRegisterFacade { @Transactional public ValidationResponse registerNewOrgUser( PortalOrgRegistrationDTO orgDTO, - UserAgreementDTO agreementDTO) { + UserAgreementDTO agreementDTO, + boolean emailVerified) { if (!agreementValidator.isAgreementAccepted(agreementDTO)) { return new ValidationResponse(false, "약관에 모두 동의해야 합니다."); @@ -74,20 +76,35 @@ public class OrgRegisterFacadeImpl implements OrgRegisterFacade { return new ValidationResponse(false, "입력 정보가 올바르지 않습니다."); } - // 2025.10.20 - 휴대폰 번호 중복 무시 -// PortalUser existingUser = portalUserRepository.findByUserNameAndMobileNumber(orgDTO.getUserName(), orgDTO.getMobileNumber()); + // 개인 가입(@Valid @PasswordRule)과 달리 법인 가입은 컨트롤러 바인딩 검증이 없어 + // 여기서 서버 측 비밀번호 규칙을 직접 검증한다 (retain/change 시나리오는 기존 비밀번호 유지라 제외) + if (!passwordValidator.isValidPassword(orgDTO.getPassword(), orgDTO.getLoginId(), orgDTO.getMobileNumber())) { + return new ValidationResponse(false, + "비밀번호는 영문/숫자/특수문자 포함 8~50자이며, 아이디·휴대전화 번호, 3자리 이상 연속·반복 문자는 사용할 수 없습니다."); + } + + if (orgDTO.getConfirmPassword() == null || !orgDTO.getConfirmPassword().equals(orgDTO.getPassword())) { + return new ValidationResponse(false, "비밀번호와 비밀번호 확인이 일치하지 않습니다."); + } + Optional existingUser = portalUserRepository.findByLoginId(orgDTO.getLoginId()); if(existingUser.isPresent()) { return new ValidationResponse(false, "가입된 계정이 이미 존재합니다."); } + // 휴대폰 번호 중복 검증 (Portal/user.mobile.duplicate.allow 프로퍼티에 따라 차단) + if (portalUserService.isMobileDuplicateCheckEnabled() + && portalUserService.existsByMobileNumber(orgDTO.getMobileNumber())) { + return new ValidationResponse(false, "이미 가입된 휴대폰 번호입니다."); + } + try { FileInfo uploadedFile = handleFileUpload(orgDTO.getFiles()); if (uploadedFile == null) { return new ValidationResponse(false, "첨부파일이 올바르지 않습니다."); } - return registerNewCorporateUser(orgDTO, uploadedFile); + return registerNewCorporateUser(orgDTO, uploadedFile, emailVerified); } catch (IllegalArgumentException | IOException e) { return new ValidationResponse(false, e.getMessage()); } @@ -179,7 +196,8 @@ public class OrgRegisterFacadeImpl implements OrgRegisterFacade { // 신규 법인 사용자 등록 메서드 private ValidationResponse registerNewCorporateUser( PortalOrgRegistrationDTO orgDTO, - FileInfo uploadedFile) { + FileInfo uploadedFile, + boolean emailVerified) { // 사업자등록번호 중복 체크 (이중 방어) if (portalOrgService.existsByCompRegNo(orgDTO.getCompRegNo())) { @@ -190,9 +208,9 @@ public class OrgRegisterFacadeImpl implements OrgRegisterFacade { PortalOrg newOrg = portalOrgService.registerOrgFromDTOWithFile(orgDTO, uploadedFile); // 사용자 생성 및 기관 연결 - PortalUser newUser = portalUserService.createUserWithOrg(orgDTO, newOrg, "corporate"); + PortalUser newUser = portalUserService.createUserWithOrg(orgDTO, newOrg, "corporate", emailVerified); - agreementsFacade.saveUserAgreements(newUser.getId(), AgreementType.PRIVACY_COLLECT_ORG); + agreementsFacade.saveUserAgreements(newUser.getId(), AgreementType.PRIVACY_COLLECT); approvalService.createUserApproval(newUser); // 11.28 - 회원 가입단계가 아닌 로그인 단계로 이메일 인증 이동 @@ -240,7 +258,7 @@ public class OrgRegisterFacadeImpl implements OrgRegisterFacade { portalUserRepository.save(existingUser); agreementsFacade.deleteUserAgreements(existingUser.getId()); //기존 개인약관 동의 삭제 - agreementsFacade.saveUserAgreements(existingUser.getId(), AgreementType.PRIVACY_COLLECT_ORG); + agreementsFacade.saveUserAgreements(existingUser.getId(), AgreementType.PRIVACY_COLLECT); approvalService.createUserApproval(existingUser); @@ -274,7 +292,7 @@ public class OrgRegisterFacadeImpl implements OrgRegisterFacade { portalUserRepository.save(existingUser); // 새로운 법인용 개인정보수집동의서 저장 - agreementsFacade.saveUserAgreements(existingUser.getId(), AgreementType.PRIVACY_COLLECT_ORG); + agreementsFacade.saveUserAgreements(existingUser.getId(), AgreementType.PRIVACY_COLLECT); approvalService.createUserApproval(existingUser); diff --git a/src/main/java/com/eactive/apim/portal/apps/user/facade/UserFacade.java b/src/main/java/com/eactive/apim/portal/apps/user/facade/UserFacade.java index 47cedb2..c6a2a18 100644 --- a/src/main/java/com/eactive/apim/portal/apps/user/facade/UserFacade.java +++ b/src/main/java/com/eactive/apim/portal/apps/user/facade/UserFacade.java @@ -10,11 +10,14 @@ public interface UserFacade { void updatePassword(String loginId, String newPassword, String confirmPassword); + /** 비밀번호에 아이디/휴대전화 번호가 포함되는지 라이브 체크용 판정 (키: idIncluded, mobileIncluded) */ + java.util.Map checkPasswordContent(String loginId, String password); + void updateUser(PortalUserDTO portalUserDTO); void updateCorporateManager(PortalUserDTO portalUserDTO); - void withdrawUser(String userId); + void withdrawUser(String userId, String withdrawalReason); void activateUserByEmail(String email); } \ No newline at end of file diff --git a/src/main/java/com/eactive/apim/portal/apps/user/facade/UserFacadeImpl.java b/src/main/java/com/eactive/apim/portal/apps/user/facade/UserFacadeImpl.java index 66ce1bf..db69e52 100644 --- a/src/main/java/com/eactive/apim/portal/apps/user/facade/UserFacadeImpl.java +++ b/src/main/java/com/eactive/apim/portal/apps/user/facade/UserFacadeImpl.java @@ -2,6 +2,7 @@ package com.eactive.apim.portal.apps.user.facade; import com.eactive.apim.portal.apps.agreements.service.AgreementsFacade; import com.eactive.apim.portal.apps.user.dto.PortalUserDTO; +import com.eactive.apim.portal.common.util.StringMaskingUtil; import com.eactive.apim.portal.apps.user.mapper.PortalUserMapper; import com.eactive.apim.portal.apps.user.service.PasswordService; import com.eactive.apim.portal.apps.user.service.PortalOrgService; @@ -59,6 +60,12 @@ public class UserFacadeImpl implements UserFacade { messageHandlerService.publishEvent(UserPasswordChangedEvent.KEY, recipient, params); } + @Override + @Transactional(readOnly = true) + public HashMap checkPasswordContent(String loginId, String password) { + return passwordService.checkPasswordContent(loginId, password); + } + @Override @Transactional public void updateUser(PortalUserDTO portalUserDTO) { @@ -71,7 +78,7 @@ public class UserFacadeImpl implements UserFacade { updateUserBasicInfo(user, portalUserDTO); portalUserService.updateUser(user); - log.info("사용자 정보 업데이트 완료: {}", user.getLoginId()); + log.info("사용자 정보 업데이트 완료: {}", StringMaskingUtil.maskLoginId(user.getLoginId())); } @Override @@ -98,7 +105,7 @@ public class UserFacadeImpl implements UserFacade { portalUserService.updateUser(user); portalOrgService.updateOrg(org); - log.info("법인 관리자 정보 업데이트 완료: {}", user.getLoginId()); + log.info("법인 관리자 정보 업데이트 완료: {}", StringMaskingUtil.maskLoginId(user.getLoginId())); } // 사용자 기본 업데이트 @@ -130,13 +137,13 @@ public class UserFacadeImpl implements UserFacade { } @Override - public void withdrawUser(String userId) { + public void withdrawUser(String userId, String withdrawalReason) { PortalUser user = portalUserService.findById(userId); // 법인 관리자 탈퇴 제한 if (user.getRoleCode() == PortalUserEnums.RoleCode.ROLE_CORP_MANAGER) { if(portalUserService.checkOrgHasOtherUsers(user.getPortalOrg())){ - throw new IllegalArgumentException("법인 관리자권한을 다른 이용자에게 위임하신 후 탈퇴가 가능합니다."); + throw new IllegalArgumentException("법인 관리자권한을 다른 개발자에게 위임하신 후 탈퇴가 가능합니다."); } } @@ -146,8 +153,8 @@ public class UserFacadeImpl implements UserFacade { // 메시지 요청정보 삭제 messageRequestFacade.deleteUserMessage(user.getUserName(),user.getLoginId()); - portalUserService.deleteUser(user); - log.info("회원 탈퇴 처리 완료: {}", user.getLoginId()); + portalUserService.deleteUser(user, withdrawalReason); + log.info("회원 탈퇴 처리 완료: {}", StringMaskingUtil.maskLoginId(user.getLoginId())); } // 사용자 정보 업데이트 유효성 확인 @@ -215,6 +222,6 @@ public class UserFacadeImpl implements UserFacade { user.setUserStatus(PortalUserEnums.UserStatus.ACTIVE); portalUserService.save(user); - log.info("사용자 이메일 인증 완료 - 활성화: {}", email); + log.info("사용자 이메일 인증 완료 - 활성화: {}", StringMaskingUtil.maskEmail(email)); } } diff --git a/src/main/java/com/eactive/apim/portal/apps/user/facade/UserManFacade.java b/src/main/java/com/eactive/apim/portal/apps/user/facade/UserManFacade.java index 371700a..2bb75d0 100644 --- a/src/main/java/com/eactive/apim/portal/apps/user/facade/UserManFacade.java +++ b/src/main/java/com/eactive/apim/portal/apps/user/facade/UserManFacade.java @@ -7,8 +7,10 @@ import com.eactive.apim.portal.apps.user.mapper.PortalUserMapper; import com.eactive.apim.portal.common.exception.NotFoundException; import com.eactive.apim.portal.common.user.PortalAuthenticatedUser; import com.eactive.apim.portal.common.util.DurationParser; +import com.eactive.apim.portal.common.util.PhoneNumberUtil; import com.eactive.apim.portal.common.util.SecurityUtil; import com.eactive.apim.portal.common.util.StringMaskingUtil; +import com.eactive.apim.portal.common.validator.CellPhoneValidator; import com.eactive.apim.portal.invitation.entity.UserInvitation; import com.eactive.apim.portal.invitation.entity.UserInvitationEnums.InvitationStatus; import com.eactive.apim.portal.invitation.event.UserInvitationCancelEvent; @@ -18,6 +20,10 @@ import com.eactive.apim.portal.invitation.repository.UserInvitationRepository; import com.eactive.apim.portal.portaluser.entity.PortalUser; import com.eactive.apim.portal.portaluser.entity.PortalUserEnums.RoleCode; import com.eactive.apim.portal.portaluser.entity.PortalUserEnums.UserStatus; +import com.eactive.apim.portal.apps.session.service.UserSessionService; +import com.eactive.apim.portal.apps.user.service.PortalUserAuthService; +import com.eactive.apim.portal.apps.user.service.UserRoleHistoryService; +import com.eactive.apim.portal.apps.user.service.UserRoleHistoryService.ChangeType; import com.eactive.apim.portal.portaluser.repository.PortalUserRepository; import com.eactive.apim.portal.portalproperty.service.PortalPropertyService; import com.eactive.apim.portal.template.service.MessageHandlerService; @@ -52,6 +58,10 @@ public class UserManFacade { private final MessageHandlerService messageHandlerService; private final UserLogRepository userLogRepository; private final PortalPropertyService portalPropertyService; + private final CellPhoneValidator cellPhoneValidator; + private final UserSessionService userSessionService; + private final PortalUserAuthService portalUserAuthService; + private final UserRoleHistoryService userRoleHistoryService; public Page getUsers(PortalOrgDTO userOrg, Pageable pageable) { return portalUserRepository @@ -94,32 +104,49 @@ public class UserManFacade { dto.setMobileNumber(null); } - public void sendInvitation(PortalUser sender, String emailAddr) { - // 이메일 형식 검증 - if (emailAddr == null || emailAddr.trim().isEmpty()) { - throw new IllegalArgumentException("이메일 주소를 입력해주세요."); + /** + * 법인 개발자 초대 (휴대폰 번호 기반). + * + * @param sender 초대를 보내는 법인 관리자 + * @param mobileRaw 초대 대상 휴대폰 번호 (하이픈 유무 무관) + * @param notifyConsent 알림 수신 동의 여부. false 면 초대 레코드만 생성하고 메시지 발송은 스킵한다. + */ + public void sendInvitation(PortalUser sender, String mobileRaw, boolean notifyConsent) { + // 휴대폰 번호 형식 검증 + if (mobileRaw == null || mobileRaw.trim().isEmpty()) { + throw new IllegalArgumentException("휴대폰 번호를 입력해주세요."); + } + if (!cellPhoneValidator.isValid(mobileRaw, null)) { + throw new IllegalArgumentException("올바른 휴대폰 번호 형식을 입력해주세요."); } - // 이메일 정규식 검증 - String emailPattern = "^[a-zA-Z0-9._%+\\-]+@[a-zA-Z0-9.\\-]+\\.[a-zA-Z]{2,}$"; - if (!emailAddr.matches(emailPattern)) { - throw new IllegalArgumentException("올바른 이메일 형식을 입력해주세요."); - } + // 저장 포맷(하이픈 포함, 예: 010-1234-5678)으로 정규화 — PortalUser.mobileNumber 저장 포맷과 + // 일치시켜야 결정적 암호화 equality 조회가 매칭된다. + String mobile = normalizeMobile(mobileRaw); // 중복 초대 확인 - checkDuplicateInvitation(sender.getPortalOrg().getId(), emailAddr); + checkDuplicateInvitation(sender.getPortalOrg().getId(), mobile); - //기존 사용자인지 확인 - Optional existingUser = portalUserRepository.findPortalUserByEmailAddr(emailAddr); + // 기존 사용자인지 휴대폰 번호로 확인 (mobileNumber 는 유니크가 아니므로 다건 가능) + List matched = portalUserRepository.findAllByMobileNumber(mobile); + if (matched.size() > 1) { + throw new IllegalArgumentException("해당 휴대폰 번호로 가입된 계정이 여러 건 존재하여 초대할 수 없습니다. 관리자에게 문의해주세요."); + } + Optional existingUser = matched.isEmpty() ? Optional.empty() : Optional.of(matched.get(0)); if (existingUser.isPresent()) { PortalUser user = existingUser.get(); if (user.getPortalOrg() != null && !user.getRoleCode().equals(RoleCode.ROLE_USER)) { - throw new IllegalArgumentException("기관에 등록된 이용자 입니다."); + throw new IllegalArgumentException("기관에 등록된 개발자 입니다."); } } - String token = createInvitation(sender, emailAddr); + String token = createInvitation(sender, mobile); + + // 알림 수신 미동의 시 초대 레코드만 생성하고 메시지 발송은 스킵 + if (!notifyConsent) { + return; + } // {"CRPT_NM":"%corpName%", "MNGR_NM":"%managerName%", "CUST_ID":"%loginId%", "AUTH_NO": "%authNumber%", "NAME": "%loginId%"} MessageRecipient recipient; @@ -129,7 +156,7 @@ public class UserManFacade { params.put("corpName", sender.getPortalOrg().getOrgName()); params.put("authNumber", token); // 8-character invitation code - // 기존 사용자인 경우 회원 이름 사용, 비회원인 경우 이메일 사용 + // 기존 사용자인 경우 회원 이름 사용, 비회원인 경우 휴대폰 번호 사용 if (existingUser.isPresent()) { PortalUser user = existingUser.get(); params.put("loginId", user.getUserName()); @@ -137,26 +164,36 @@ public class UserManFacade { params.put("url", "signup/decision?invitation=" + token); recipient = MessageRecipient.of(user); + // MessageRecipient.of() 는 유선전화(phoneNumber)를 채우므로 휴대폰 번호로 보정 + recipient.setPhone(user.getMobileNumber()); } else { - params.put("loginId", emailAddr); - params.put("NAME", emailAddr); + params.put("loginId", mobile); + params.put("NAME", mobile); params.put("url", "signup/portalUser?invitation=" + token); - String emailUsername = emailAddr.contains("@") ? emailAddr.substring(0, emailAddr.indexOf("@")) : emailAddr; recipient = MessageRecipient.builder() - .userId(emailAddr) - .username(emailUsername) + .userId(mobile) + .username(mobile) + .phone(mobile) .build(); } messageHandlerService.publishEvent(UserInvitationEvent.KEY, recipient, params); } - public String createInvitation(PortalUser sender, String emailAddr) { + /** + * 휴대폰 번호를 저장 포맷(하이픈 포함)으로 정규화한다. + * 010-XXXX-XXXX(11자리) / 010-XXX-XXXX(10자리). 형식 불명은 원본 반환(검증에서 차단됨). + */ + private String normalizeMobile(String raw) { + return PhoneNumberUtil.normalize(raw); + } + + public String createInvitation(PortalUser sender, String mobile) { UserInvitation newInvitation = new UserInvitation(); newInvitation.setAdminId(sender.getId()); newInvitation.setOrgId(sender.getPortalOrg().getId()); - newInvitation.setInvitationEmail(emailAddr); + newInvitation.setInvitationMobile(mobile); // Set additional required fields newInvitation.setInvitationDate(LocalDateTime.now()); @@ -206,6 +243,9 @@ public class UserManFacade { PortalUser currentUser = portalUserRepository.findById(portalAuthenticatedUser.getId()) .orElseThrow(() -> new NotFoundException("사용자를 찾을 수 없습니다. ")); + RoleCode targetBefore = targetUser.getRoleCode(); + RoleCode currentBefore = currentUser.getRoleCode(); + // 3. Assign role code ROLE_CORP_MANAGER to target User targetUser.setRoleCode(RoleCode.ROLE_CORP_MANAGER); @@ -215,6 +255,14 @@ public class UserManFacade { portalUserRepository.save(targetUser); portalUserRepository.save(currentUser); + // 역할 변경 감사 이력: 대상 위임 + 본인 회수 + userRoleHistoryService.record(targetUser.getLoginId(), targetBefore, RoleCode.ROLE_CORP_MANAGER, ChangeType.MANAGER_ASSIGN); + userRoleHistoryService.record(currentUser.getLoginId(), currentBefore, RoleCode.ROLE_CORP_USER, ChangeType.MANAGER_REVOKE); + + // 권한 변경 즉시 반영: 대상(타 세션)은 강제 로그아웃, 본인(현재 세션)은 in-place 재인증 + userSessionService.forceLogoutAllSessions(targetUser.getLoginId()); + portalUserAuthService.reloadCurrentAuthentication(); + MessageRecipient recipient = new MessageRecipient(); recipient.setUserId(targetUser.getEmailAddr()); recipient.setPhone(targetUser.getMobileNumber()); @@ -225,7 +273,65 @@ public class UserManFacade { } - public void cancelInvitation(PortalAuthenticatedUser user, String invitationId) { + /** + * 법인 관리자 권한 회수 (관리자 → 개발자). + * 대상은 본인이 아닌 같은 기관의 ROLE_CORP_MANAGER 여야 한다. + */ + public void revokeManager(PortalAuthenticatedUser admin, String targetUserId) { + PortalUser targetUser = portalUserRepository.findById(targetUserId) + .orElseThrow(() -> new NotFoundException("ID [" + targetUserId + "] 사용자를 찾을 수 없습니다. ")); + + if (targetUser.getPortalOrg() == null + || !targetUser.getPortalOrg().getId().equals(admin.getPortalOrg().getId())) { + throw new IllegalArgumentException("잘 못 된 접근입니다"); + } + if (targetUser.getId().equals(admin.getId())) { + throw new IllegalArgumentException("본인의 권한은 변경할 수 없습니다."); + } + if (targetUser.getRoleCode() != RoleCode.ROLE_CORP_MANAGER) { + throw new IllegalArgumentException("관리자만 개발자로 변경할 수 있습니다."); + } + + RoleCode revokeBefore = targetUser.getRoleCode(); + targetUser.setRoleCode(RoleCode.ROLE_CORP_USER); + portalUserRepository.save(targetUser); + + // 역할 변경 감사 이력: 관리자 → 개발자 + userRoleHistoryService.record(targetUser.getLoginId(), revokeBefore, RoleCode.ROLE_CORP_USER, ChangeType.MANAGER_REVOKE); + + // 권한 회수를 대상 사용자의 활성 세션에 반영 (강제 로그아웃 → 재로그인 시 새 권한) + userSessionService.forceLogoutAllSessions(targetUser.getLoginId()); + } + + /** + * 소속 제외 (기관에서 제외 → 개인이용자). + * 대상은 본인이 아닌 같은 기관의 사용자여야 한다. portalOrg 를 비우고 ROLE_USER 로 전환한다. + */ + public void removeFromOrg(PortalAuthenticatedUser admin, String targetUserId) { + PortalUser targetUser = portalUserRepository.findById(targetUserId) + .orElseThrow(() -> new NotFoundException("ID [" + targetUserId + "] 사용자를 찾을 수 없습니다. ")); + + if (targetUser.getPortalOrg() == null + || !targetUser.getPortalOrg().getId().equals(admin.getPortalOrg().getId())) { + throw new IllegalArgumentException("잘 못 된 접근입니다"); + } + if (targetUser.getId().equals(admin.getId())) { + throw new IllegalArgumentException("본인은 소속에서 제외할 수 없습니다."); + } + + RoleCode removeBefore = targetUser.getRoleCode(); + targetUser.setPortalOrg(null); + targetUser.setRoleCode(RoleCode.ROLE_USER); + portalUserRepository.save(targetUser); + + // 역할 변경 감사 이력: 소속 제외 → 개인 전환 + userRoleHistoryService.record(targetUser.getLoginId(), removeBefore, RoleCode.ROLE_USER, ChangeType.ORG_REMOVE); + + // 소속 제외를 대상 사용자의 활성 세션에 반영 (강제 로그아웃 → 재로그인 시 새 권한) + userSessionService.forceLogoutAllSessions(targetUser.getLoginId()); + } + + public void cancelInvitation(PortalAuthenticatedUser user, String invitationId, boolean notifyConsent) { UserInvitation invitation = userInvitationRepository.findById(invitationId) .orElseThrow(() -> new NotFoundException("Invitation not found with id: " + invitationId)); @@ -237,17 +343,19 @@ public class UserManFacade { throw new IllegalStateException("Invitation is not in a cancelable state"); } - // invitationId 로 사용자 이름 조회, 실패시 id 를 이름으로 대체 - String name = portalUserRepository.findById(invitation.getInvitationEmail()) - .map(PortalUser::getUserName) - .orElse(invitation.getInvitationEmail()); + invitation.setStatus(InvitationStatus.CANCELED); + userInvitationRepository.save(invitation); - // name 이 이메일 일 경우 @ 앞부분 만 사용 (안하면 암호화 해야함...) - if (name.contains("@")) { - name = name.substring(0, name.indexOf("@")); + // 알림 수신 미동의 시 취소 알림 메시지 발송은 스킵 + if (!notifyConsent) { + return; } - invitation.setStatus(InvitationStatus.CANCELED); + // invitationMobile 로 사용자 이름 조회, 실패시 마스킹된 번호로 대체 + String name = portalUserRepository.findAllByMobileNumber(invitation.getInvitationMobile()) + .stream().findFirst() + .map(PortalUser::getUserName) + .orElse(StringMaskingUtil.maskMobileNumber(invitation.getInvitationMobile())); messageHandlerService.publishEvent(UserInvitationCancelEvent.KEY, MessageRecipient.builder() @@ -260,8 +368,6 @@ public class UserManFacade { }} ); - userInvitationRepository.save(invitation); - } public void inactivateUser(PortalAuthenticatedUser admin, String userId) { @@ -298,8 +404,8 @@ public class UserManFacade { .stream() .map(invitation -> { PortalUserDTO dto = portalUserMapper.pendingUserVO(invitation); - dto.setMaskedEmailAddr(StringMaskingUtil.maskEmail(invitation.getInvitationEmail())); - dto.setEmailAddr(null); // Clear unmasked email + dto.setMaskedMobileNumber(StringMaskingUtil.maskMobileNumber(invitation.getInvitationMobile())); + dto.setMobileNumber(null); // Clear unmasked mobile return dto; }) .collect(Collectors.toList()); @@ -328,32 +434,32 @@ public class UserManFacade { existingInvitation.setStatus(InvitationStatus.CANCELED); userInvitationRepository.save(existingInvitation); - // 새로운 초대 생성 및 발송 - sendInvitation(sender, existingInvitation.getInvitationEmail()); + // 새로운 초대 생성 및 발송 (재발송 시 최초 동의값을 알 수 없으므로 발송함을 기본으로) + sendInvitation(sender, existingInvitation.getInvitationMobile(), true); } /** * 중복 초대 확인 - * 같은 기관에서 같은 이메일로 이미 PENDING 상태인 초대가 있는지 확인 + * 같은 기관에서 같은 휴대폰 번호로 이미 PENDING 상태인 초대가 있는지 확인 */ - private void checkDuplicateInvitation(String orgId, String emailAddr) { + private void checkDuplicateInvitation(String orgId, String mobile) { // 1. 같은 기관에서 PENDING 상태인 초대가 있는지 확인 Optional pendingInvitation = - userInvitationRepository.findFirstByInvitationEmailAndOrgIdAndStatus( - emailAddr, orgId, InvitationStatus.PENDING); + userInvitationRepository.findFirstByInvitationMobileAndOrgIdAndStatus( + mobile, orgId, InvitationStatus.PENDING); if (pendingInvitation.isPresent()) { - throw new IllegalArgumentException("이미 초대가 진행 중인 이메일 주소입니다."); + throw new IllegalArgumentException("이미 초대가 진행 중인 휴대폰 번호입니다."); } // 2. 다른 기관에서 PENDING 상태인 초대가 있는지 확인 Optional otherOrgPendingInvitation = - userInvitationRepository.findFirstByInvitationEmailAndStatus( - emailAddr, InvitationStatus.PENDING); + userInvitationRepository.findFirstByInvitationMobileAndStatus( + mobile, InvitationStatus.PENDING); if (otherOrgPendingInvitation.isPresent() && !otherOrgPendingInvitation.get().getOrgId().equals(orgId)) { - throw new IllegalArgumentException("해당 이메일은 다른 기관에서 초대 진행 중입니다."); + throw new IllegalArgumentException("해당 휴대폰 번호는 다른 기관에서 초대 진행 중입니다."); } } diff --git a/src/main/java/com/eactive/apim/portal/apps/user/facade/UserRegisterFacadeImpl.java b/src/main/java/com/eactive/apim/portal/apps/user/facade/UserRegisterFacadeImpl.java index c34f9a8..3ef2f33 100644 --- a/src/main/java/com/eactive/apim/portal/apps/user/facade/UserRegisterFacadeImpl.java +++ b/src/main/java/com/eactive/apim/portal/apps/user/facade/UserRegisterFacadeImpl.java @@ -89,7 +89,7 @@ public class UserRegisterFacadeImpl implements UserRegisterFacade { @Override public ValidationResponse checkPassword(String password, String loginId, String mobileNumber) { boolean isValid = passwordValidator.isValidPassword(password, loginId, mobileNumber); - String message = isValid ? "유효한 비밀번호입니다." : "비밀번호는 영문/숫자/특수문자 포함 8~20자, 로그인 아이디, 휴대폰 번호, 3자리 이상 연속, 반복 문자 사용 불가능 합니다."; + String message = isValid ? "유효한 비밀번호입니다." : "비밀번호는 영문/숫자/특수문자 포함 8~50자, 로그인 아이디, 휴대폰 번호, 3자리 이상 연속, 반복 문자 사용 불가능 합니다."; return new ValidationResponse(isValid, message); } @@ -137,20 +137,28 @@ public class UserRegisterFacadeImpl implements UserRegisterFacade { return new ValidationResponse(false, "이미 사용 중인 이메일입니다."); } - // 25.10.01 - 휴대폰 번호 중복이여도 가입 가능 -// PortalUser existingUser = portalUserRepository.findByUserNameAndMobileNumber(registrationDTO.getUserName(), registrationDTO.getMobileNumber()); -// -// if(existingUser != null) { -// return new ValidationResponse(false, "가입된 계정이 이미 존재합니다."); -// } + // 휴대폰 번호 중복 검증 (Portal/user.mobile.duplicate.allow 프로퍼티에 따라 차단) + if (portalUserService.isMobileDuplicateCheckEnabled() + && portalUserService.existsByMobileNumber(registrationDTO.getMobileNumber())) { + return new ValidationResponse(false, "이미 가입된 휴대폰 번호입니다."); + } + + // 가입 폼에서 이메일 인증을 마쳤는지 확인(세션 signupVerifiedEmail 이 가입 이메일과 일치) + String verifiedEmail = (String) session.getAttribute("signupVerifiedEmail"); + boolean emailVerified = verifiedEmail != null + && verifiedEmail.equalsIgnoreCase(registrationDTO.getLoginId()); // 3. 사용자 등록 ("personal" 등록 유형으로 가정) - PortalUser newUser = portalUserService.registerActiveUser(registrationDTO, "personal"); + PortalUser newUser = portalUserService.registerActiveUser(registrationDTO, "personal", emailVerified); if (newUser == null) { return new ValidationResponse(false,"사용자 등록에 실패했습니다."); } + if (emailVerified) { + session.removeAttribute("signupVerifiedEmail"); + session.removeAttribute("signupEmailPending"); + } - agreementsFacade.saveUserAgreements(newUser.getId(), AgreementType.PRIVACY_COLLECT_IND); + agreementsFacade.saveUserAgreements(newUser.getId(), AgreementType.PRIVACY_COLLECT); // 11.13 - 회원 가입단계가 아닌 로그인 단계로 이메일 인증 이동 // sendEmailActivation(newUser); return new ValidationResponse(true,"회원가입이 완료되었습니다."); @@ -173,7 +181,14 @@ public class UserRegisterFacadeImpl implements UserRegisterFacade { return new ValidationResponse(false,"입력값을 확인해 주세요."); } - PortalUser existingUser = portalUserRepository.findByUserNameAndMobileNumber(registrationDTO.getUserName(), registrationDTO.getMobileNumber()); + // 휴대폰 번호 중복 검증 (Portal/user.mobile.duplicate.allow 프로퍼티에 따라 차단) + if (portalUserService.isMobileDuplicateCheckEnabled() + && portalUserService.existsByMobileNumber(registrationDTO.getMobileNumber())) { + return new ValidationResponse(false, "이미 가입된 휴대폰 번호입니다."); + } + + PortalUser existingUser = portalUserRepository.findByUserNameAndMobileNumber(registrationDTO.getUserName(), + com.eactive.apim.portal.common.util.PhoneNumberUtil.normalize(registrationDTO.getMobileNumber())); if(existingUser != null) { return new ValidationResponse(false, "가입된 계정이 이미 존재합니다."); @@ -190,7 +205,7 @@ public class UserRegisterFacadeImpl implements UserRegisterFacade { } agreementsFacade.deleteUserAgreements(newUser.getId()); //이전에 동의한 내역은 삭제 - agreementsFacade.saveUserAgreements(newUser.getId(), AgreementType.PRIVACY_COLLECT_ORG); + agreementsFacade.saveUserAgreements(newUser.getId(), AgreementType.PRIVACY_COLLECT); invitation.setStatus(UserInvitationEnums.InvitationStatus.COMPLETED); invitation.setCompleteDate(LocalDateTime.now()); @@ -242,7 +257,8 @@ public class UserRegisterFacadeImpl implements UserRegisterFacade { try { if ("accept".equals(action)) { // 수락 처리 - PortalUser user = portalUserRepository.findPortalUserByEmailAddr(invitation.getInvitationEmail()) + PortalUser user = portalUserRepository.findAllByMobileNumber(invitation.getInvitationMobile()) + .stream().findFirst() .orElseThrow(() -> new IllegalArgumentException("사용자 정보를 찾을 수 없습니다.")); // 서비스 계층에 위임 @@ -253,11 +269,11 @@ public class UserRegisterFacadeImpl implements UserRegisterFacade { userInvitationRepository.save(invitation); agreementsFacade.deleteUserAgreements(user.getId()); - agreementsFacade.saveUserAgreements(user.getId(), AgreementType.PRIVACY_COLLECT_ORG); + agreementsFacade.saveUserAgreements(user.getId(), AgreementType.PRIVACY_COLLECT); ValidationResponse response = new ValidationResponse(); response.setValid(true); - response.setMessage("초대가 성공적으로 수락되었습니다. 로그아웃 후 다시 로그인 해 주세요."); + response.setMessage("초대가 성공적으로 수락되었습니다. 법인회원으로 전환되었습니다."); response.setShowPopup(true); response.setPopupType("success"); return response; diff --git a/src/main/java/com/eactive/apim/portal/apps/user/mapper/PortalUserMapper.java b/src/main/java/com/eactive/apim/portal/apps/user/mapper/PortalUserMapper.java index 18ef3bf..f8d7773 100644 --- a/src/main/java/com/eactive/apim/portal/apps/user/mapper/PortalUserMapper.java +++ b/src/main/java/com/eactive/apim/portal/apps/user/mapper/PortalUserMapper.java @@ -15,7 +15,7 @@ public abstract class PortalUserMapper implements GenericMapper checkPasswordContent(String loginId, String password) { + PortalUser user = portalUserRepository.findByLoginId(loginId) + .orElseThrow(() -> new IllegalArgumentException("해당 사용자를 찾을 수 없습니다.")); + java.util.HashMap result = new java.util.HashMap<>(); + result.put("idIncluded", + PasswordRuleValidator.containsLoginIdLocalPart(password, user.getLoginId())); + result.put("mobileIncluded", + PasswordRuleValidator.containsMobileSegment(password, user.getMobileNumber())); + return result; + } + private void checkPasswordHistory(String userId, String newPassword) { List passwordHistories = passwordHistoryRepository.findRecentPasswordsByUserId(userId); diff --git a/src/main/java/com/eactive/apim/portal/apps/user/service/PortalUserAuthService.java b/src/main/java/com/eactive/apim/portal/apps/user/service/PortalUserAuthService.java index d62fd1c..e20a696 100644 --- a/src/main/java/com/eactive/apim/portal/apps/user/service/PortalUserAuthService.java +++ b/src/main/java/com/eactive/apim/portal/apps/user/service/PortalUserAuthService.java @@ -1,13 +1,16 @@ package com.eactive.apim.portal.apps.user.service; +import com.eactive.apim.portal.apps.login.constants.LoginType; +import com.eactive.apim.portal.apps.login.service.LoginFinalizer; import com.eactive.apim.portal.apps.user.dto.PortalUserDTO; import com.eactive.apim.portal.apps.user.mapper.PortalUserMapper; import com.eactive.apim.portal.common.exception.SystemException; +import com.eactive.apim.portal.common.util.PhoneNumberUtil; import com.eactive.apim.portal.common.exception.UserNotFoundException; import com.eactive.apim.portal.common.user.PortalAuthenticatedUser; import com.eactive.apim.portal.common.util.EncryptionUtil; -import com.eactive.apim.portal.common.util.StringMaskingUtil; -import com.eactive.apim.portal.config.PortalProperties; +import com.eactive.apim.portal.common.util.SecurityUtil; +import com.eactive.apim.portal.djb.menu.PortalRolesProperties; import com.eactive.apim.portal.portaluser.entity.PortalUser; import com.eactive.apim.portal.portaluser.entity.PortalUserEnums; import com.eactive.apim.portal.portaluser.entity.PortalUserEnums.RoleCode; @@ -30,7 +33,9 @@ import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import lombok.RequiredArgsConstructor; import org.apache.xerces.impl.dv.util.Base64; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; @@ -45,11 +50,12 @@ public class PortalUserAuthService implements UserDetailsService { private final PortalUserRepository portalUserRepository; private final PortalUserMapper portalUserMapper; - private final PortalProperties portalProperties; + private final PortalRolesProperties portalRolesProperties; private final PasswordEncoder passwordEncoder; private final MessageHandlerService messageHandlerService; private final MessageRequestRepository messageRequestRepository; private final EncryptionUtil encryptionUtil; + private final LoginFinalizer loginFinalizer; @Override @Transactional(noRollbackFor = UsernameNotFoundException.class) @@ -58,21 +64,68 @@ public class PortalUserAuthService implements UserDetailsService { // 이메일 소문자 변환 적용 (대소문자 구분 없이 로그인 가능하도록) String normalizedUsername = username != null ? username.toLowerCase() : null; PortalUser portalUser = findByEmailAddr(normalizedUsername); - RoleCode userRole = portalUser.getRoleCode() == null ? RoleCode.ROLE_USER : portalUser.getRoleCode(); - List roles = portalProperties.getPortalSecurity().get(userRole); - PortalAuthenticatedUser authenticatedUser = portalUserMapper.portalUserToAuthenticatedUser(portalUser); - - authenticatedUser.getAuthorities().add(new SimpleGrantedAuthority(userRole.name())); - - roles.forEach(role -> authenticatedUser.getAuthorities().add(new SimpleGrantedAuthority(role))); - - return authenticatedUser; - + return buildAuthenticatedUser(portalUser); } catch (UserNotFoundException e) { throw new UsernameNotFoundException("입력하신 사용자 정보가 올바르지 않습니다. 다시 확인해 주세요."); } } + /** + * DB의 PortalUser 로부터 권한(roleCode + 역할 계층)을 채운 PortalAuthenticatedUser 를 생성한다. + * 로그인과 세션 재로드가 동일한 권한 구성을 사용하도록 공통화한다. + */ + public PortalAuthenticatedUser buildAuthenticatedUser(PortalUser portalUser) { + RoleCode userRole = portalUser.getRoleCode() == null ? RoleCode.ROLE_USER : portalUser.getRoleCode(); + List roles = portalRolesProperties.getAuthorities(userRole); + PortalAuthenticatedUser authenticatedUser = portalUserMapper.portalUserToAuthenticatedUser(portalUser); + + authenticatedUser.getAuthorities().add(new SimpleGrantedAuthority(userRole.name())); + roles.forEach(role -> authenticatedUser.getAuthorities().add(new SimpleGrantedAuthority(role))); + + return authenticatedUser; + } + + /** + * 현재 로그인한 사용자의 권한을 DB에서 다시 읽어 SecurityContext 의 Authentication 을 교체한다. + * 재로그인 없이 본인 세션에 역할 변경(초대 수락, 관리자 위임으로 인한 본인 강등 등)을 즉시 반영한다. + */ + public void reloadCurrentAuthentication() { + PortalAuthenticatedUser current = SecurityUtil.getPortalAuthenticatedUser(); + if (current == null) { + return; + } + PortalUser fresh = portalUserRepository.findByLoginId(current.getLoginId()) + .orElseThrow(() -> new UserNotFoundException("사용자를 찾을 수 없습니다.")); + PortalAuthenticatedUser refreshed = buildAuthenticatedUser(fresh); + + UsernamePasswordAuthenticationToken newAuth = + new UsernamePasswordAuthenticationToken(refreshed, null, refreshed.getAuthorities()); + newAuth.setDetails(refreshed); + SecurityContextHolder.getContext().setAuthentication(newAuth); + } + + /** + * 회원가입 직후 자동 로그인. formLogin 을 경유하지 않으므로 세션 고정 방어(changeSessionId)를 + * 수동 수행하고, SuccessHandler 와 동일한 후처리({@link LoginFinalizer})로 세션 등록·감사 기록을 맞춘다. + * SuccessHandler 를 타지 않으므로 로그인 2FA 는 자연히 건너뛴다. + * + * @return 이동 대상 URL + */ + @Transactional + public String autoLoginAfterSignup(PortalUser user, javax.servlet.http.HttpServletRequest request) { + // 세션 고정 공격 방어 (form login 미경유 → 수동) + request.changeSessionId(); + + PortalAuthenticatedUser authUser = buildAuthenticatedUser(user); + UsernamePasswordAuthenticationToken token = + new UsernamePasswordAuthenticationToken(authUser, null, authUser.getAuthorities()); + token.setDetails(authUser); + SecurityContextHolder.getContext().setAuthentication(token); + + // 세션 등록 / 감사 성공 기록(SIGNUP_AUTO) / 타임아웃 설정 재사용 + return loginFinalizer.finalizeLogin(user, user.getLoginId(), request, LoginType.SIGNUP_AUTO); + } + public List findAllUsersByNameAndMobile(String userName, String mobileNumber) { try { if (mobileNumber == null || !mobileNumber.matches("^\\d{2,3}-\\d{3,4}-\\d{4}$")) { @@ -83,12 +136,9 @@ public class PortalUserAuthService implements UserDetailsService { throw new UserNotFoundException("입력하신 사용자 정보가 올바르지 않습니다. 다시 확인해 주세요."); } - return users.stream().map(user -> { - PortalUserDTO dto = portalUserMapper.toDTO(user); - dto.setMaskedEmailAddr(StringMaskingUtil.maskEmail(dto.getLoginId())); - dto.setLoginId(null); - return dto; - }).collect(Collectors.toList()); + // 아이디 찾기: 이름+휴대폰 본인인증을 마친 사용자에게 보여주는 결과이므로 + // 이메일(아이디)을 마스킹 없이 그대로 반환한다. + return users.stream().map(portalUserMapper::toDTO).collect(Collectors.toList()); } catch (UserNotFoundException e) { throw e; @@ -102,6 +152,8 @@ public class PortalUserAuthService implements UserDetailsService { } public void resetPassword(String loginId, String userName, String mobileNumber) { + // 입력 그룹핑이 저장 정규형과 달라도 매칭되도록 조회 전 정규화 (암호화 컬럼 등가 비교) + mobileNumber = PhoneNumberUtil.normalize(mobileNumber); if (mobileNumber == null || !mobileNumber.matches("^\\d{2,3}-\\d{3,4}-\\d{4}$")) { throw new UserNotFoundException("유효하지 않은 휴대폰 번호 형식입니다."); } @@ -111,6 +163,9 @@ public class PortalUserAuthService implements UserDetailsService { String tempPassword = EncryptionUtil.generateNewPassword(); portalUser.setPasswordHash(passwordEncoder.encode(tempPassword)); + // 임시 비밀번호 발급 → 변경일을 null 로 초기화해 로그인 시 강제 비밀번호 변경을 유도한다 + // (LoginFinalizer.applyPostLoginState 의 passwordChangeDate == null 분기) + portalUser.setPasswordChangeDate(null); if ("Y".equalsIgnoreCase(portalUser.getAccountLockYn())) { portalUser.setAccountLockYn("N"); } @@ -122,37 +177,6 @@ public class PortalUserAuthService implements UserDetailsService { messageHandlerService.publishEvent(UserPasswordResetEvent.KEY, recipient, params); } - public void resendVerificationEmail(String loginId) { - - PortalUser user = portalUserRepository.findByLoginId(loginId) - .orElseThrow(() -> new IllegalArgumentException("입력하신 사용자 정보가 올바르지 않습니다. 다시 확인해 주세요.")); - - if (user.getUserStatus().equals(PortalUserEnums.UserStatus.ACTIVE)) { - throw new IllegalArgumentException("이미 인증이 완료된 계정입니다."); - } - - // Enum을 직접 전달 - List messageRequests = messageRequestRepository.findByEmailAndMessageCode( - user.getLoginId(), MessageCode.USER_VERIFICATION_EMAIL - ); - - if (!messageRequests.isEmpty()) { - messageRequestRepository.deleteAll(messageRequests); - } - - HashMap params = new HashMap<>(); - String tokenValue = user.getCreatedDate().format(DateTimeFormatter.ofPattern("yyyyMMddHHmm")) + ":" + user.getId(); - - try { - String encToken = encryptionUtil.encrypt(tokenValue); - params.put("token", Base64.encode(encToken.getBytes(StandardCharsets.UTF_8))); - MessageRecipient recipient = createMessageRecipient(user); - messageHandlerService.publishEvent(MessageCode.USER_VERIFICATION_EMAIL, recipient, params); - } catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) { - throw new SystemException("암호화 모듈 오류"); - } - } - private MessageRecipient createMessageRecipient(PortalUser user) { MessageRecipient recipient = new MessageRecipient(); recipient.setUsername(user.getUserName()); @@ -164,7 +188,7 @@ public class PortalUserAuthService implements UserDetailsService { @Transactional public void reactivateDormantAccount(String loginId, String password, String mobileNumber) { try { - PortalUser portalUser = portalUserRepository.findByLoginIdAndMobileNumber(loginId, mobileNumber) + PortalUser portalUser = portalUserRepository.findByLoginIdAndMobileNumber(loginId, PhoneNumberUtil.normalize(mobileNumber)) .orElseThrow(() -> new UserNotFoundException("입력하신 사용자 정보가 올바르지 않습니다. 다시 확인해 주세요.")); if (!passwordEncoder.matches(password, portalUser.getPasswordHash())) { diff --git a/src/main/java/com/eactive/apim/portal/apps/user/service/PortalUserLogService.java b/src/main/java/com/eactive/apim/portal/apps/user/service/PortalUserLogService.java index 645b90d..13abf39 100644 --- a/src/main/java/com/eactive/apim/portal/apps/user/service/PortalUserLogService.java +++ b/src/main/java/com/eactive/apim/portal/apps/user/service/PortalUserLogService.java @@ -1,5 +1,7 @@ package com.eactive.apim.portal.apps.user.service; +import com.eactive.apim.portal.apps.login.constants.LoginFailureReason; +import com.eactive.apim.portal.apps.login.constants.LoginType; import com.eactive.apim.portal.user.entity.UserLog; import com.eactive.apim.portal.user.repository.UserLogRepository; import java.time.LocalDateTime; @@ -10,43 +12,66 @@ import org.springframework.transaction.annotation.Transactional; @Transactional public class PortalUserLogService { + /** PTL_USER_LOG.USER_AGENT 컬럼 길이. 초과분은 잘라서 저장한다(초과 시 ORA-12899 로 로그인 실패). */ + private static final int USER_AGENT_MAX_LENGTH = 500; + private final UserLogRepository userLogRepository; public PortalUserLogService(UserLogRepository userLogRepository) { this.userLogRepository = userLogRepository; } - public void logSuccess(String userId, String ip, String sessionId) { + /** + * 로그인 성공 기록. + * + * @param duplicate 로그인 시점에 다른 활성 세션이 남아 있어 기존 세션이 강제 해제된 건이면 true + */ + public void logSuccess(String userId, String ip, String sessionId, LoginType loginType, String userAgent, + boolean duplicate) { UserLog log = new UserLog(); log.setLoginId(userId); log.setLoginTime(LocalDateTime.now()); log.setIp(ip); log.setSessionId(sessionId); log.setSuccess(true); + log.setLoginType(loginType != null ? loginType.name() : null); + log.setUserAgent(truncateUserAgent(userAgent)); + log.setDuplicate(duplicate); userLogRepository.save(log); } - public void logFailure(String userId, String ip, String sessionId) { + public void logFailure(String userId, String ip, String sessionId, LoginFailureReason reason, String userAgent) { UserLog log = new UserLog(); log.setLoginId(userId); log.setLoginTime(LocalDateTime.now()); log.setIp(ip); log.setSessionId(sessionId); log.setSuccess(false); + log.setFailureReason(reason != null ? reason.name() : LoginFailureReason.UNKNOWN.name()); + log.setUserAgent(truncateUserAgent(userAgent)); userLogRepository.save(log); } @Transactional - public void logLogout(String userId, String ip, String sessionId) { + public void logLogout(String userId, String ip, String sessionId, String userAgent) { UserLog log = new UserLog(); log.setLoginId(userId); log.setLogoutTime(LocalDateTime.now()); log.setIp(ip); log.setSessionId(sessionId); log.setSuccess(true); + log.setUserAgent(truncateUserAgent(userAgent)); userLogRepository.save(log); } + + private String truncateUserAgent(String userAgent) { + if (userAgent == null) { + return null; + } + return userAgent.length() <= USER_AGENT_MAX_LENGTH + ? userAgent : userAgent.substring(0, USER_AGENT_MAX_LENGTH); + } } diff --git a/src/main/java/com/eactive/apim/portal/apps/user/service/PortalUserService.java b/src/main/java/com/eactive/apim/portal/apps/user/service/PortalUserService.java index 20921e5..6b58d2f 100644 --- a/src/main/java/com/eactive/apim/portal/apps/user/service/PortalUserService.java +++ b/src/main/java/com/eactive/apim/portal/apps/user/service/PortalUserService.java @@ -2,6 +2,7 @@ package com.eactive.apim.portal.apps.user.service; import com.eactive.apim.portal.apps.user.dto.PortalUserRegistrationDTO; import com.eactive.apim.portal.apps.user.repository.PortalOrgRepository; +import com.eactive.apim.portal.common.util.PhoneNumberUtil; import com.eactive.apim.portal.portalorg.entity.PortalOrg; import com.eactive.apim.portal.portalproperty.service.PortalPropertyService; import com.eactive.apim.portal.portaluser.entity.PortalUser; @@ -24,6 +25,12 @@ import org.springframework.util.StringUtils; @Service @Transactional public class PortalUserService { + + /** PortalProperty 그룹명 */ + private static final String PORTAL_PROPERTY_GROUP = "Portal"; + /** 휴대폰 번호 중복 허용 프로퍼티 키 (true:허용, false:허용안함, 기본 false) */ + private static final String PROP_MOBILE_DUPLICATE_ALLOW = "user.mobile.duplicate.allow"; + private final PortalUserRepository portalUserRepository; private final PortalOrgRepository portalOrgRepository; private final PasswordEncoder passwordEncoder; @@ -68,6 +75,33 @@ public class PortalUserService { return portalUserRepository.existsByLoginId(normalizedLoginId); } + /** + * 휴대폰 번호 중복 여부 확인. + * mobileNumber 는 저장 포맷(하이픈 포함, 예: 010-1234-5678)과 동일한 형태로 전달해야 한다. + * (암호화 컬럼이므로 결정적 암호화 기준 평등 조회로 매칭된다) + */ + public boolean existsByMobileNumber(String mobileNumber) { + if (mobileNumber == null || mobileNumber.trim().isEmpty()) { + return false; + } + return portalUserRepository.existsByMobileNumber(PhoneNumberUtil.normalize(mobileNumber)); + } + + /** + * 회원가입 시 휴대폰 번호 중복 차단 활성 여부. + * 공유 프로퍼티(Portal/user.mobile.duplicate.allow)를 true/false 로 해석한다. + * - allow=true(또는 레거시 Y): 중복 허용 → 차단 비활성 + * - allow=false(또는 레거시 N) / 미설정: 중복 허용안함 → 차단 활성 (기본값 false) + */ + @Transactional(readOnly = true) + public boolean isMobileDuplicateCheckEnabled() { + String allow = portalPropertyService + .getPortalPropertiesAsMap(PORTAL_PROPERTY_GROUP) + .getOrDefault(PROP_MOBILE_DUPLICATE_ALLOW, "false"); + boolean allowed = "true".equalsIgnoreCase(allow) || "Y".equalsIgnoreCase(allow); + return !allowed; + } + public boolean checkOrgHasOtherUsers(PortalOrg portalOrg) { return portalUserRepository.countByPortalOrg(portalOrg) > 1; } @@ -107,6 +141,14 @@ public class PortalUserService { // 승인 대기 상태. public PortalUser registerActiveUser(PortalUserRegistrationDTO newUserDTO, String registrationType) { + return registerActiveUser(newUserDTO, registrationType, false); + } + + /** + * @param emailVerified 가입 폼에서 이메일 인증을 이미 완료했으면 true → 바로 ACTIVE 로 저장 + * (가입 후 별도 이메일 인증 단계를 건너뛴다) + */ + public PortalUser registerActiveUser(PortalUserRegistrationDTO newUserDTO, String registrationType, boolean emailVerified) { PortalUser newUser = new PortalUser(); mapDtoToEntity(newUser, newUserDTO); setUserProperties(newUser); @@ -115,8 +157,9 @@ public class PortalUserService { newUser.setUserStatus(UserStatus.READY); - // 이메일 인증 기능 비활성화 시 바로 활성화 처리 - if ("true".equalsIgnoreCase(propertyMap.getOrDefault("disable_features.user_email_verify", ""))) { + // 이메일 인증 기능 비활성화 시, 또는 가입 폼에서 이미 인증을 마친 경우 바로 활성화 처리 + if (emailVerified + || "true".equalsIgnoreCase(propertyMap.getOrDefault("disable_features.user_email_verify", ""))) { newUser.setUserStatus(UserStatus.ACTIVE); } newUser.setApprovalStatus(ApprovalStatus.COMPLETED); @@ -132,11 +175,26 @@ public class PortalUserService { } public PortalUser createUserWithOrg(PortalUserRegistrationDTO userDTO, PortalOrg org, String registrationType) { + return createUserWithOrg(userDTO, org, registrationType, false); + } + + /** + * @param emailVerified 가입 폼에서 이메일 인증을 이미 완료했으면 true → 바로 ACTIVE 로 저장 + * (개인 경로 registerActiveUser 와 동일. 법인 신규가입 경로에서 사용) + */ + public PortalUser createUserWithOrg(PortalUserRegistrationDTO userDTO, PortalOrg org, String registrationType, boolean emailVerified) { PortalUser newUser = new PortalUser(); mapDtoToEntity(newUser, userDTO); newUser.setPortalOrg(org); setUserProperties(newUser); newUser.setUserStatus(UserStatus.READY); + + // 이메일 인증 기능 비활성화 시, 또는 가입 폼에서 이미 인증을 마친 경우 바로 활성화 처리 + Map propertyMap = portalPropertyService.getPortalPropertiesAsMap("Portal"); + if (emailVerified + || "true".equalsIgnoreCase(propertyMap.getOrDefault("disable_features.user_email_verify", ""))) { + newUser.setUserStatus(UserStatus.ACTIVE); + } newUser.setApprovalStatus(ApprovalStatus.COMPLETED); setUserRole(newUser, registrationType); return portalUserRepository.save(newUser); @@ -150,6 +208,8 @@ public class PortalUserService { user.setPasswordHash(passwordEncoder.encode(dto.getPassword())); user.setMobileNumber(dto.getMobileNumber()); user.setEmailAddr(normalizedEmail); + // 가입 시점을 비밀번호 변경일로 기록 → 신규 가입자는 재설정 강제 대상에서 제외된다. + user.setPasswordChangeDate(java.time.LocalDateTime.now()); } /** @@ -220,7 +280,7 @@ public class PortalUserService { return portalUserRepository.save(user); } - public void deleteUser(PortalUser user) { + public void deleteUser(PortalUser user, String withdrawalReason) { LocalDateTime now = LocalDateTime.now(); String withdrawalDate = now.format(DateTimeFormatter.ofPattern("yyyyMMddHHmm")); @@ -230,6 +290,12 @@ public class PortalUserService { user.setMobileNumber(""); user.setPasswordHash(""); + // 탈퇴 사유 보존 (컬럼 길이 200 초과분은 잘라 저장) + if (withdrawalReason != null && withdrawalReason.length() > 200) { + withdrawalReason = withdrawalReason.substring(0, 200); + } + user.setWithdrawalReason(withdrawalReason); + user.setUserStatus(PortalUserEnums.UserStatus.REMOVED); portalUserRepository.save(user); diff --git a/src/main/java/com/eactive/apim/portal/apps/user/service/UserRoleHistoryService.java b/src/main/java/com/eactive/apim/portal/apps/user/service/UserRoleHistoryService.java new file mode 100644 index 0000000..395708a --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/apps/user/service/UserRoleHistoryService.java @@ -0,0 +1,63 @@ +package com.eactive.apim.portal.apps.user.service; + +import com.eactive.apim.portal.common.util.SecurityUtil; +import com.eactive.apim.portal.portaluser.entity.PortalUserEnums.RoleCode; +import com.eactive.apim.portal.portaluser.entity.UserRoleHistory; +import com.eactive.apim.portal.portaluser.repository.UserRoleHistoryRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; + +/** + * 사용자 역할(권한) 변경 감사 이력 기록 서비스. + * + *

법인 관리자 위임/회수, 소속 제외 등 역할 변경 이벤트를 {@code ptl_user_role_history} 에 남긴다. + * 변경 수행자(changedBy)는 현재 인증된 관리자의 loginId 로 기록한다.

+ */ +@Slf4j +@Service +@RequiredArgsConstructor +public class UserRoleHistoryService { + + /** 역할 변경 유형. */ + public enum ChangeType { + MANAGER_ASSIGN, // 관리자 권한 위임 + MANAGER_REVOKE, // 관리자 권한 회수 + ORG_REMOVE // 소속 제외(개인으로 전환) + } + + private final UserRoleHistoryRepository userRoleHistoryRepository; + + /** + * 역할 변경 이력을 기록한다. 감사 목적이므로 실패해도 본 트랜잭션을 롤백시키지 않도록 + * 호출부에서 예외를 전파하지 않는다(내부에서 로깅만). + */ + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void record(String targetLoginId, RoleCode before, RoleCode after, ChangeType changeType) { + try { + String actor = SecurityUtil.getCurrentLoginId(); + if (actor == null || actor.isEmpty()) { + actor = "SYSTEM"; + } + LocalDateTime now = LocalDateTime.now(); + + UserRoleHistory history = new UserRoleHistory(); + history.setUserId(targetLoginId); + history.setBeforeRole(before != null ? before.name() : null); + history.setAfterRole(after != null ? after.name() : null); + history.setChangeType(changeType.name()); + history.setChangedBy(actor); + history.setChangeDate(now); + history.setCreatedBy(actor); + history.setCreatedDate(now); + + userRoleHistoryRepository.save(history); + } catch (Exception e) { + log.error("역할 변경 이력 기록 실패 - target: {}, type: {}", targetLoginId, changeType, e); + } + } +} diff --git a/src/main/java/com/eactive/apim/portal/apps/user/validator/PasswordValidator.java b/src/main/java/com/eactive/apim/portal/apps/user/validator/PasswordValidator.java index ec4b1a8..1d954f8 100644 --- a/src/main/java/com/eactive/apim/portal/apps/user/validator/PasswordValidator.java +++ b/src/main/java/com/eactive/apim/portal/apps/user/validator/PasswordValidator.java @@ -1,7 +1,6 @@ package com.eactive.apim.portal.apps.user.validator; -import com.eactive.apim.portal.common.validator.PasswordRuleForKbankValidator; -import com.eactive.apim.portal.common.validator.PasswordRuleForDjbankValidator; +import com.eactive.apim.portal.common.validator.PasswordRuleValidator; import org.springframework.stereotype.Component; @Component @@ -17,13 +16,13 @@ public class PasswordValidator { } public boolean isValidPassword(String password, String loginId, String mobileNumber) { - PasswordRuleForDjbankValidator validator = new PasswordRuleForDjbankValidator(); + PasswordRuleValidator validator = new PasswordRuleValidator(); return validator.isValid(password, loginId, mobileNumber); } private boolean isValidLengthAndCharacters(String password) { final int MIN = 8; - final int MAX = 20; + final int MAX = 50; final String REGEX = "^(?=.*\\d)(?=.*[a-zA-Z])(?=.*[\\W]).{" + MIN + "," + MAX + "}$"; return password.matches(REGEX); } diff --git a/src/main/java/com/eactive/apim/portal/common/breadcrumb/GlobalControllerAdvice.java b/src/main/java/com/eactive/apim/portal/common/breadcrumb/GlobalControllerAdvice.java index e1090c4..6645606 100644 --- a/src/main/java/com/eactive/apim/portal/common/breadcrumb/GlobalControllerAdvice.java +++ b/src/main/java/com/eactive/apim/portal/common/breadcrumb/GlobalControllerAdvice.java @@ -4,10 +4,18 @@ import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; +import org.springframework.core.env.Profiles; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ModelAttribute; -import com.eactive.apim.portal.portalproperty.service.PortalPropertyService; import com.eactive.apim.portal.config.PortalProperties; +import com.eactive.apim.portal.apps.auth.AuthNoticeProperties; +import com.eactive.apim.portal.apps.session.service.UserSessionService; +import com.eactive.apim.portal.common.security.ClientGuardService; +import com.eactive.apim.portal.djb.footer.RelatedSite; +import com.eactive.apim.portal.djb.footer.RelatedSiteService; +import com.eactive.apim.portal.djb.guide.GuideProperty; +import com.eactive.apim.portal.portalproperty.service.PortalPropertyService; @ControllerAdvice public class GlobalControllerAdvice { @@ -15,11 +23,29 @@ public class GlobalControllerAdvice { @Autowired private PageService pageService; + @Autowired + private PortalProperties portalProperties; + + @Autowired + private UserSessionService userSessionService; + + @Autowired + private ClientGuardService clientGuardService; + @Autowired private PortalPropertyService portalPropertyService; @Autowired - private PortalProperties portalProperties; + private AuthNoticeProperties authNoticeProperties; + + @Autowired + private RelatedSiteService relatedSiteService; + + @Autowired + private GuideProperty guideProperty; + + @Autowired + private Environment environment; @ModelAttribute("breadcrumb") public List addBreadcrumbToModel(HttpServletRequest request) { @@ -33,29 +59,97 @@ public class GlobalControllerAdvice { return pageService.getPageName(currentPath); } - @ModelAttribute("designSurveyEnabled") - public boolean isDesignSurveyEnabled() { - String value = portalPropertyService.getOrCreateProperty( - "Portal", - "ui.design-survey", - "false", - "네비게이션 바 디자인 설문 활성화 여부 (true/false)" - ); - return "true".equalsIgnoreCase(value); - } - @ModelAttribute("showTestAuthNotice") public boolean showTestAuthNotice() { - return portalProperties.isTestAuthNoticeEnabled(); + return authNoticeProperties.isTestNoticeEnabled(); } @ModelAttribute("testAuthNumber") public String getTestAuthNumber() { - if (portalProperties.isTestAuthNoticeEnabled()) { + if (authNoticeProperties.isTestNoticeEnabled()) { String authVirtualCode = portalProperties.getAuthVirtualCode(); // 고정 인증번호가 있으면 반환, 없으면 "random" 표시 return (authVirtualCode != null && !authVirtualCode.isEmpty()) ? authVirtualCode : "random"; } return null; } + + /** + * 화면 세션 타이머 기준이 되는 타임아웃(분). 10분 고정 (UserSessionService.SESSION_TIMEOUT_MINUTES). + */ + @ModelAttribute("sessionTimeoutMinutes") + public int sessionTimeoutMinutes() { + return userSessionService.getSessionTimeoutMinutes(); + } + + /** + * 세션 유지(타임아웃 무시) 기능 사용 가능 여부. + * 비운영(!prod) + DB property(Portal/session.keepalive.enabled=Y)일 때만 true. + * prod 환경에서는 property 값과 무관하게 항상 false → UI 체크박스 미렌더. + */ + @ModelAttribute("sessionKeepAliveAllowed") + public boolean sessionKeepAliveAllowed() { + boolean prod = environment.acceptsProfiles(Profiles.of("prod")); + return !prod && userSessionService.isKeepAliveEnabled(); + } + + /** + * 클라이언트 가드 - 우클릭(contextmenu) 차단 활성화 여부. + * PortalProperty(Portal/djb.client-guard.contextmenu.enabled)에서 조회. + */ + @ModelAttribute("clientGuardContextmenu") + public boolean clientGuardContextmenu() { + return clientGuardService.isContextmenuBlockEnabled(); + } + + /** + * 클라이언트 가드 - DevTools 감지 시 화면 삭제 활성화 여부. + * PortalProperty(Portal/djb.client-guard.devtools.enabled)에서 조회. + */ + @ModelAttribute("clientGuardDevtools") + public boolean clientGuardDevtools() { + return clientGuardService.isDevtoolsGuardEnabled(); + } + + /** + * 푸터 고객센터 연락처. PortalProperty(Portal/customer.center.contact)에서 조회. + * 전화번호가 아닐 수도 있으므로 값 그대로 출력하되 템플릿에서 th:text(HTML escape)로 렌더한다. + */ + @ModelAttribute("customerCenterContact") + public String customerCenterContact() { + return portalPropertyService.getOrCreateProperty( + "Portal", "customer.center.contact", "1588-3388", "고객센터 연락처"); + } + + /** + * 푸터 관련 사이트 셀렉트 라벨. PortalProperty(Portal/footer.related-sites.label)에서 조회. + */ + @ModelAttribute("relatedSitesLabel") + public String relatedSitesLabel() { + return relatedSiteService.getLabel(); + } + + /** + * 푸터 관련 사이트 목록. PortalProperty(Portal/footer.related-sites)의 '이름=URL' 줄 목록을 파싱한 결과. + * 비어 있으면 푸터에서 셀렉트 자체를 렌더하지 않는다. + */ + @ModelAttribute("relatedSites") + public List relatedSites() { + return relatedSiteService.getSites(); + } + + /** + * 개발가이드 안내페이지에 표기할 게이트웨이 base URL. + * PortalProperty(Portal/djb.guide.gw-url)에서 조회하며, 환경별로 값이 달라진다. + * + *

가이드 페이지(/service/*-guide)에서만 필요하므로 다른 요청에서는 DB 조회 없이 null 을 반환한다. + */ + @ModelAttribute("guideGwUrl") + public String guideGwUrl(HttpServletRequest request) { + String uri = request.getRequestURI(); + if (uri == null || !uri.endsWith("-guide")) { + return null; + } + return guideProperty.gatewayUrl(); + } } diff --git a/src/main/java/com/eactive/apim/portal/common/dto/PasswordValidationDTO.java b/src/main/java/com/eactive/apim/portal/common/dto/PasswordValidationDTO.java index 8adf58a..2f1ac5c 100644 --- a/src/main/java/com/eactive/apim/portal/common/dto/PasswordValidationDTO.java +++ b/src/main/java/com/eactive/apim/portal/common/dto/PasswordValidationDTO.java @@ -1,12 +1,12 @@ package com.eactive.apim.portal.common.dto; -import com.eactive.apim.portal.common.validator.PasswordRuleForKbank; +import com.eactive.apim.portal.common.validator.PasswordRule; import lombok.Getter; import lombok.Setter; @Getter @Setter -@PasswordRuleForKbank(password = "password", loginId = "loginId", mobile = "mobile") +@PasswordRule(password = "password", loginId = "loginId", mobile = "mobile") public class PasswordValidationDTO { private String password; private String loginId; diff --git a/src/main/java/com/eactive/apim/portal/common/exception/PortalErrorController.java b/src/main/java/com/eactive/apim/portal/common/exception/PortalErrorController.java index 3bc19dc..6337764 100644 --- a/src/main/java/com/eactive/apim/portal/common/exception/PortalErrorController.java +++ b/src/main/java/com/eactive/apim/portal/common/exception/PortalErrorController.java @@ -1,6 +1,8 @@ package com.eactive.apim.portal.common.exception; import org.springframework.boot.web.servlet.error.ErrorController; +import org.springframework.core.env.Environment; +import org.springframework.core.env.Profiles; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @@ -9,6 +11,8 @@ import org.springframework.web.servlet.ModelAndView; import javax.servlet.RequestDispatcher; import javax.servlet.http.HttpServletRequest; +import java.io.PrintWriter; +import java.io.StringWriter; /** * Created by Sungpil Hyun @@ -16,6 +20,11 @@ import javax.servlet.http.HttpServletRequest; @Controller public class PortalErrorController implements ErrorController { + private final Environment environment; + + public PortalErrorController(Environment environment) { + this.environment = environment; + } @RequestMapping(value = "/error", method = {RequestMethod.POST, RequestMethod.GET}) public ModelAndView handleError(HttpServletRequest request) { @@ -27,10 +36,134 @@ public class PortalErrorController implements ErrorController { } ModelAndView modelAndView = new ModelAndView("error"); modelAndView.addObject("status", status); + + if (isProd()) { + // prod 환경에서는 내부 오류 정보를 노출하지 않고 이전과 동일한 일반 안내 메시지만 표시 + modelAndView.addObject("errorTitle", "일시적인 장애로 서비스가 중단되었습니다."); + modelAndView.addObject("errorDescription", "서비스 이용에 불편을 드려 죄송합니다. 빠른 서비스를 제공할 수 있게 준비하겠습니다."); + } else { + modelAndView.addObject("errorTitle", resolveTitle(status)); + modelAndView.addObject("errorDescription", resolveDescription(status)); + modelAndView.addObject("errorMessage", resolveErrorMessage(request, status)); + modelAndView.addObject("activeProfile", resolveActiveProfile()); + // include-stacktrace=always(로컬 디버깅 프로파일)일 때만 화면에 스택 트레이스까지 노출 + if (isErrorDetailEnabled()) { + Throwable throwable = resolveThrowable(request); + if (throwable != null) { + modelAndView.addObject("errorException", throwable.getClass().getName()); + modelAndView.addObject("errorStackTrace", stackTraceAsString(throwable)); + } + } + } return modelAndView; } return new ModelAndView("redirect:/"); } + private boolean isProd() { + return environment.acceptsProfiles(Profiles.of("prod")); + } + + /** + * server.error.include-stacktrace=always 인 경우에만 화면에 스택 트레이스를 노출한다. + * (로컬 디버깅 프로파일에서만 해당 값을 always 로 설정 → 운영/스테이징에는 노출되지 않음) + */ + private boolean isErrorDetailEnabled() { + String includeStacktrace = environment.getProperty("server.error.include-stacktrace", "never"); + return "always".equalsIgnoreCase(includeStacktrace); + } + + private String resolveActiveProfile() { + String[] activeProfiles = environment.getActiveProfiles(); + if (activeProfiles.length == 0) { + return "default"; + } + return String.join(", ", activeProfiles); + } + + private Throwable resolveThrowable(HttpServletRequest request) { + Object exception = request.getAttribute(RequestDispatcher.ERROR_EXCEPTION); + if (exception instanceof Throwable) { + return (Throwable) exception; + } + return null; + } + + private String stackTraceAsString(Throwable throwable) { + StringWriter stringWriter = new StringWriter(); + throwable.printStackTrace(new PrintWriter(stringWriter)); + return stringWriter.toString(); + } + + private String resolveTitle(Object status) { + HttpStatus httpStatus = resolveHttpStatus(status); + if (httpStatus == null) { + return "서비스 처리 중 오류가 발생했습니다."; + } + + if (httpStatus == HttpStatus.FORBIDDEN) { + return "접근이 거부되었습니다."; + } + if (httpStatus == HttpStatus.NOT_FOUND) { + return "요청하신 페이지를 찾을 수 없습니다."; + } + if (httpStatus.is4xxClientError()) { + return "요청을 처리할 수 없습니다."; + } + if (httpStatus.is5xxServerError()) { + return "서비스 처리 중 오류가 발생했습니다."; + } + return httpStatus.getReasonPhrase(); + } + + private String resolveDescription(Object status) { + HttpStatus httpStatus = resolveHttpStatus(status); + if (httpStatus == HttpStatus.FORBIDDEN) { + return "요청이 보안 검증을 통과하지 못했습니다."; + } + if (httpStatus == HttpStatus.NOT_FOUND) { + return "주소가 변경되었거나 삭제되었을 수 있습니다."; + } + if (httpStatus != null && httpStatus.is5xxServerError()) { + return "잠시 후 다시 시도해 주세요."; + } + return "요청 내용을 확인한 뒤 다시 시도해 주세요."; + } + + private String resolveErrorMessage(HttpServletRequest request, Object status) { + Object requestUri = request.getAttribute(RequestDispatcher.ERROR_REQUEST_URI); + HttpStatus httpStatus = resolveHttpStatus(status); + StringBuilder message = new StringBuilder(); + + message.append("HTTP ").append(status); + if (httpStatus != null) { + message.append(" ").append(httpStatus.getReasonPhrase()); + } + + if (requestUri != null) { + message.append(" / 요청 경로: ").append(requestUri); + } + + Object exceptionMessage = request.getAttribute(RequestDispatcher.ERROR_MESSAGE); + if (exceptionMessage != null && !exceptionMessage.toString().trim().isEmpty()) { + message.append(" / 원인: ").append(exceptionMessage); + } else if (httpStatus == HttpStatus.FORBIDDEN) { + message.append(" / 원인: 접근 권한 또는 CSRF 토큰을 확인하세요."); + } + + return message.toString(); + } + + private HttpStatus resolveHttpStatus(Object status) { + if (status instanceof Integer) { + return HttpStatus.resolve((Integer) status); + } + try { + return HttpStatus.resolve(Integer.parseInt(status.toString())); + } catch (NumberFormatException e) { + return null; + } + } + } diff --git a/src/main/java/com/eactive/apim/portal/common/exception/PortalGlobalExceptionHandler.java b/src/main/java/com/eactive/apim/portal/common/exception/PortalGlobalExceptionHandler.java index e848f6d..8afd94e 100644 --- a/src/main/java/com/eactive/apim/portal/common/exception/PortalGlobalExceptionHandler.java +++ b/src/main/java/com/eactive/apim/portal/common/exception/PortalGlobalExceptionHandler.java @@ -2,19 +2,21 @@ package com.eactive.apim.portal.common.exception; import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; +import com.eactive.apim.portal.apps.login.service.LoginFinalizer; +import com.eactive.apim.portal.common.util.SecurityUtil; +import com.eactive.apim.portal.common.util.StringMaskingUtil; import com.eactive.apim.portal.config.PortalProperties; import com.eactive.apim.portal.file.exception.InvalidFileException; import lombok.RequiredArgsConstructor; -import org.apache.commons.lang3.exception.ExceptionUtils; import org.springframework.web.multipart.MaxUploadSizeExceededException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.annotation.Order; +import org.springframework.core.env.Environment; +import org.springframework.core.env.Profiles; import org.springframework.security.access.AccessDeniedException; import org.springframework.stereotype.Controller; import org.springframework.web.HttpMediaTypeNotSupportedException; @@ -36,6 +38,7 @@ public class PortalGlobalExceptionHandler { private final Logger log = LoggerFactory.getLogger(getClass()); private final PortalProperties portalProperties; + private final Environment environment; @ExceptionHandler(value = NotFoundException.class) public ModelAndView handleINotFoundException(HttpServletRequest request, NotFoundException ex) { @@ -49,12 +52,74 @@ public class PortalGlobalExceptionHandler { @ExceptionHandler(value = UserNotLoginException.class) public ModelAndView handleUserNotLoginException(HttpServletRequest request, UserNotLoginException ex) { - return new ModelAndView("redirect:/login"); + return new ModelAndView("redirect:/login?reason=auth"); } @ExceptionHandler(value = AccessDeniedException.class) public ModelAndView handleAccessDeniedException(HttpServletRequest request, AccessDeniedException ex) { - return new ModelAndView("redirect:/login"); + // 미로그인 사용자는 로그인 페이지로 유도, 로그인 상태에서의 권한 부족은 오류 안내 페이지로 표시한다. + if (!SecurityUtil.isAuthenticated()) { + // 원래 요청 페이지를 세션에 저장 → 로그인+2FA 완료 후 LoginFinalizer 가 복귀시킨다. + savePostLoginRedirect(request); + return new ModelAndView("redirect:/login?reason=auth"); + } + log.warn("접근 권한 없음: loginId={}, uri={}", StringMaskingUtil.maskLoginId(SecurityUtil.getCurrentLoginId()), request.getRequestURI()); + ModelAndView modelAndView = new ModelAndView("error"); + modelAndView.addObject("errorTitle", "페이지 접근 권한이 없습니다."); + modelAndView.addObject("errorDescription", "해당 페이지를 이용할 수 있는 권한이 없는 계정입니다.\n권한이 필요한 경우 관리자에게 문의해 주세요."); + return modelAndView; + } + + /** + * 미인증 상태로 보호 페이지(HTML 화면)에 접근한 GET 요청의 원 경로를 세션에 저장한다. + * 로그인+2FA 완료 후 {@link LoginFinalizer} 가 이 값으로 복귀시킨다(1회용). + * open redirect / 무의미 복귀를 막기 위해 내부 화면 GET 경로만 저장한다. + */ + private void savePostLoginRedirect(HttpServletRequest request) { + if (!"GET".equalsIgnoreCase(request.getMethod())) { + return; + } + // 페이지 네비게이션만 대상(AJAX/데이터 요청 제외) + String accept = request.getHeader("Accept"); + if (accept == null || !accept.contains("text/html")) { + return; + } + String uri = request.getRequestURI(); + if (uri == null) { + return; + } + String ctx = request.getContextPath(); + String path = (ctx != null && !ctx.isEmpty() && uri.startsWith(ctx)) ? uri.substring(ctx.length()) : uri; + if (path.isEmpty()) { + path = "/"; + } + if (!isSafePostLoginPath(path)) { + return; + } + String qs = request.getQueryString(); + String target = (qs != null && !qs.isEmpty()) ? path + "?" + qs : path; + request.getSession().setAttribute(LoginFinalizer.SESSION_POST_LOGIN_REDIRECT, target); + } + + /** 복귀 대상으로 허용할 내부 경로인지(로그인/인증/에러/정적/액션/홈 제외) */ + private boolean isSafePostLoginPath(String path) { + if (path == null || !path.startsWith("/") || path.startsWith("//")) { + return false; + } + return !(path.equals("/") + || path.startsWith("/login") + || path.startsWith("/auth/") + || path.startsWith("/actionLogin") + || path.startsWith("/actionLogout") + || path.startsWith("/error") + || path.startsWith("/css/") + || path.startsWith("/js/") + || path.startsWith("/img/") + || path.startsWith("/webfonts/") + || path.startsWith("/plugins/") + || path.startsWith("/api/") + || path.startsWith("/_proxy") + || path.startsWith("/favicon")); } @ExceptionHandler(value = PortalRedirectException.class) @@ -64,10 +129,10 @@ public class PortalGlobalExceptionHandler { @ExceptionHandler(value = {IllegalArgumentException.class}) public ModelAndView handleIllegalArgumentException(HttpServletRequest request, IllegalArgumentException ex) { - log.error(ex.getMessage()); - ModelAndView modelAndView = new ModelAndView(); - modelAndView.addObject("errorMessage", ex.getMessage()); - modelAndView.setViewName("error"); + log.error("잘못된 요청 - uri={}, message={}", request.getRequestURI(), ex.getMessage()); + ModelAndView modelAndView = new ModelAndView("error"); + modelAndView.addObject("errorTitle", "요청을 처리할 수 없습니다."); + modelAndView.addObject("errorDescription", UserErrorMessageResolver.resolve(ex)); return modelAndView; } @@ -81,34 +146,40 @@ public class PortalGlobalExceptionHandler { @ExceptionHandler(value = HttpMediaTypeNotSupportedException.class) public ModelAndView handleHttpMediaTypeNotSupportedException(HttpServletRequest request, HttpMediaTypeNotSupportedException ex) { - log.error(ex.getMessage()); - ModelAndView modelAndView = new ModelAndView(); - modelAndView.addObject("errorMessage", ex.getMessage()); - modelAndView.setViewName("error"); + log.error("지원하지 않는 요청 형식 - uri={}, message={}", request.getRequestURI(), ex.getMessage()); + ModelAndView modelAndView = new ModelAndView("error"); + modelAndView.addObject("errorTitle", "요청을 처리할 수 없습니다."); + modelAndView.addObject("errorDescription", "지원하지 않는 요청 형식입니다.\n잠시 후 다시 시도해 주세요."); return modelAndView; } + /** + * 처리되지 않은 예외. 예외 원문은 로그에만 남기고 화면에는 사용자 안내 문구를 표시한다. + * 원문(클래스명/메시지)은 운영(prod) 이외 환경의 상세 영역에만 노출한다. + */ @ExceptionHandler(value = Exception.class) public ModelAndView handleException(HttpServletRequest request, Exception ex) { - Map params = new HashMap<>(2); - params.put("errorMessage", ex.getLocalizedMessage()); - params.put("stackTrace", ExceptionUtils.getStackTrace(ex)); // Apache Commons Lang - params.put("requestURL", request.getRequestURL().toString()); - - String mapAsString = request.getParameterMap().entrySet() + String requestParams = request.getParameterMap().entrySet() .stream() .map(entry -> entry.getKey() + "=" + Arrays.toString(entry.getValue())) .collect(Collectors.joining(", ")); - params.put("requestParams", mapAsString); + log.error("Exception occurred - url={}, params={}", request.getRequestURL(), requestParams, ex); - log.error("Exception occurred: ", ex); - ModelAndView modelAndView = new ModelAndView(); - modelAndView.addObject("errorMessage", ex.getMessage()); - modelAndView.setViewName("error"); + ModelAndView modelAndView = new ModelAndView("error"); + modelAndView.addObject("errorTitle", "서비스 처리 중 오류가 발생했습니다."); + modelAndView.addObject("errorDescription", UserErrorMessageResolver.resolve(ex)); + if (!isProd()) { + modelAndView.addObject("errorMessage", ex.getClass().getName() + ": " + ex.getMessage()); + modelAndView.addObject("activeProfile", String.join(", ", environment.getActiveProfiles())); + } return modelAndView; } + private boolean isProd() { + return environment.acceptsProfiles(Profiles.of("prod")); + } + @ExceptionHandler(value = InvalidFileException.class) public ModelAndView handleInvalidFileException(HttpServletRequest request, RedirectAttributes redirectAttributes, InvalidFileException ex) { ModelAndView modelAndView = new ModelAndView(); diff --git a/src/main/java/com/eactive/apim/portal/common/exception/PortalRestExceptionHandler.java b/src/main/java/com/eactive/apim/portal/common/exception/PortalRestExceptionHandler.java index 1b69b4f..915e19b 100644 --- a/src/main/java/com/eactive/apim/portal/common/exception/PortalRestExceptionHandler.java +++ b/src/main/java/com/eactive/apim/portal/common/exception/PortalRestExceptionHandler.java @@ -53,10 +53,14 @@ public class PortalRestExceptionHandler { return new ResponseEntity<>(response, HttpStatus.NOT_FOUND); } + /** + * 처리되지 않은 예외. 원문 메시지는 로그에만 남기고, 화면에는 사용자 안내 문구를 내려준다. + * (JTA 롤백/DB 락 같은 인프라 예외의 영문 원문이 팝업에 그대로 노출되지 않도록) + */ @ExceptionHandler(value = Exception.class) public ResponseEntity handleUnknownxception(HttpServletRequest request, Exception ex) { - ex.printStackTrace(); - ResponseDTO response = new ResponseDTO(500, "500", ex.getMessage()); + log.error("처리되지 않은 예외 - uri={}", request.getRequestURI(), ex); + ResponseDTO response = new ResponseDTO(500, "500", UserErrorMessageResolver.resolveAsHtml(ex)); return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR); } diff --git a/src/main/java/com/eactive/apim/portal/common/exception/UserErrorMessageResolver.java b/src/main/java/com/eactive/apim/portal/common/exception/UserErrorMessageResolver.java new file mode 100644 index 0000000..9e43f66 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/common/exception/UserErrorMessageResolver.java @@ -0,0 +1,238 @@ +package com.eactive.apim.portal.common.exception; + +import org.springframework.dao.CannotAcquireLockException; +import org.springframework.dao.DataAccessResourceFailureException; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.dao.OptimisticLockingFailureException; +import org.springframework.dao.PessimisticLockingFailureException; +import org.springframework.dao.QueryTimeoutException; +import org.springframework.transaction.CannotCreateTransactionException; +import org.springframework.transaction.TransactionException; + +/** + * 예외를 사용자에게 보여줄 안내 문구로 변환한다. + * + *

JTA 롤백/DB 락/타임아웃 같은 인프라 예외는 원문(예: {@code "JTA transaction unexpectedly rolled back + * (maybe due to a timeout); nested exception is javax.transaction.RollbackException"})이 그대로 + * 팝업·에러 화면에 노출되면 사용자가 이해할 수 없고 내부 구조까지 드러난다. 이 클래스가 정해진 한글 문구로 치환한다. + * + *

서비스 코드가 의도적으로 던진 한글 안내문(예: {@code IllegalStateException("이미 초대가 진행 중입니다.")})은 + * 그대로 유지한다. 판단은 {@link #looksUserFacing(String)} 의 휴리스틱(한글 포함 + 기술 토큰 없음)을 따른다. + * + *

반환 문구는 평문이며 줄바꿈은 {@code \n} 이다. HTML 팝업으로 내려줄 때는 {@link #toHtml(String)} 을 쓴다. + */ +public final class UserErrorMessageResolver { + + /** 원인을 특정할 수 없을 때의 기본 문구. */ + public static final String DEFAULT_MESSAGE = + "요청을 처리하는 중 오류가 발생했습니다.\n잠시 후 다시 시도해 주세요."; + + private static final String ROLLBACK_MESSAGE = + "요청 처리가 정상적으로 끝나지 않아 변경 내용이 저장되지 않았습니다.\n잠시 후 다시 시도해 주세요. 같은 문제가 반복되면 관리자에게 문의해 주세요."; + + private static final String TIMEOUT_MESSAGE = + "처리 시간이 초과되어 요청이 취소되었습니다.\n잠시 후 다시 시도해 주세요."; + + private static final String CONFLICT_MESSAGE = + "이미 등록된 정보이거나 다른 정보와 충돌하여 저장할 수 없습니다.\n입력 내용을 확인해 주세요."; + + private static final String LOCK_MESSAGE = + "다른 사용자가 동일한 정보를 변경하고 있습니다.\n잠시 후 다시 시도해 주세요."; + + private static final String STALE_MESSAGE = + "다른 사용자가 먼저 정보를 변경했습니다.\n화면을 새로 고친 뒤 다시 시도해 주세요."; + + private static final String CONNECTION_MESSAGE = + "시스템 연결이 원활하지 않아 요청을 처리하지 못했습니다.\n잠시 후 다시 시도해 주세요."; + + /** 원문 노출을 막아야 하는 기술 토큰. 메시지에 하나라도 있으면 사용자 안내문으로 보지 않는다. */ + private static final String[] TECHNICAL_TOKENS = { + "exception", "Exception", "rollback", "Rollback", "transaction", "Transaction", + "SQL", "ORA-", "JTA", "JDBC", "Hibernate", "hibernate", "com.eactive", "org.springframework", + "javax.", "java.", "oracle.", "at com.", "Caused by", "null pointer", "NullPointer", + "constraint", "Constraint", "statement", "Statement", "SocketTimeout", "Connection" + }; + + private UserErrorMessageResolver() { + } + + /** + * 예외에서 사용자 안내 문구를 만든다. + * + * @param ex 발생한 예외 (null 허용) + * @return 사용자에게 보여줄 평문 문구. 줄바꿈은 {@code \n} + */ + public static String resolve(Throwable ex) { + if (ex == null) { + return DEFAULT_MESSAGE; + } + + String infraMessage = resolveInfrastructureMessage(ex); + if (infraMessage != null) { + return infraMessage; + } + + // 서비스가 의도적으로 던진 한글 안내문은 그대로 전달 + String original = ex.getMessage(); + if (looksUserFacing(original)) { + return original; + } + + return DEFAULT_MESSAGE; + } + + /** {@link #resolve(Throwable)} 결과를 팝업(HTML)용으로 변환한다. */ + public static String resolveAsHtml(Throwable ex) { + return toHtml(resolve(ex)); + } + + /** 평문 줄바꿈을 {@code
} 로 바꾼다. */ + public static String toHtml(String plainMessage) { + if (plainMessage == null) { + return null; + } + return plainMessage.replace("\n", "
"); + } + + /** + * 트랜잭션/DB/연결 계열 인프라 예외인지 원인 체인을 따라가며 판별한다. + * + * @return 해당 문구, 인프라 예외가 아니면 null + */ + private static String resolveInfrastructureMessage(Throwable ex) { + String chainText = causeChainText(ex); + + // 1. 데이터 충돌(유니크/FK/NOT NULL) — 롤백 판정보다 먼저: 롤백 예외가 이를 감싸고 있어도 원인이 더 구체적이다. + if (hasType(ex, DataIntegrityViolationException.class) + || containsAny(chainText, "org.hibernate.exception.ConstraintViolationException", + "SQLIntegrityConstraintViolationException", + "ORA-00001", "ORA-01400", "ORA-02291", "ORA-02292", "ORA-12899")) { + return CONFLICT_MESSAGE; + } + + // 2. 낙관적 락 충돌 + if (hasType(ex, OptimisticLockingFailureException.class) + || containsAny(chainText, "OptimisticLockException", "StaleObjectStateException", "StaleStateException")) { + return STALE_MESSAGE; + } + + // 3. 비관적 락 / 데드락 + if (hasType(ex, PessimisticLockingFailureException.class) + || hasType(ex, CannotAcquireLockException.class) + || containsAny(chainText, "ORA-00060", "ORA-02049", "ORA-00054", "deadlock")) { + return LOCK_MESSAGE; + } + + // 4. 타임아웃 (쿼리/소켓/사용자 취소) + if (hasType(ex, QueryTimeoutException.class) + || containsAny(chainText, "SocketTimeoutException", "QueryTimeoutException", "ORA-01013")) { + return TIMEOUT_MESSAGE; + } + + // 5. 연결 실패 + if (hasType(ex, DataAccessResourceFailureException.class) + || hasType(ex, CannotCreateTransactionException.class) + || containsAny(chainText, "SQLRecoverableException", "ConnectException", "UnknownHostException", + "ORA-03113", "ORA-03114", "ORA-12541", "ORA-12170")) { + return CONNECTION_MESSAGE; + } + + // 6. JTA 롤백 계열 — 원인을 특정하지 못한 트랜잭션 실패 + if (hasType(ex, TransactionException.class) + || containsAny(chainText, "RollbackException", "HeuristicMixedException", "HeuristicRollbackException", + "rolled back", "rollback only")) { + // "Transaction set to rollback only" 은 내부 예외가 삼켜진 경우다. Spring 원문에 "maybe due to a + // timeout" 이 붙어 있어도 실제 타임아웃이 아니므로 타임아웃 문구를 쓰지 않는다. + if (!containsAny(chainText, "rollback only") + && containsAny(chainText, "timeout", "timed out")) { + return TIMEOUT_MESSAGE; + } + return ROLLBACK_MESSAGE; + } + + return null; + } + + /** + * 원문을 그대로 사용자에게 보여줘도 되는 안내문인지 판단한다. + * 한글이 포함되고 기술 토큰이 없어야 한다. + */ + private static boolean looksUserFacing(String message) { + if (message == null) { + return false; + } + String trimmed = message.trim(); + if (trimmed.isEmpty() || trimmed.length() > 200) { + return false; + } + if (!containsHangul(trimmed)) { + return false; + } + for (String token : TECHNICAL_TOKENS) { + if (trimmed.contains(token)) { + return false; + } + } + return true; + } + + private static boolean containsHangul(String text) { + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); + if (c >= 0xAC00 && c <= 0xD7A3) { // 한글 음절 + return true; + } + if (c >= 0x1100 && c <= 0x11FF) { // 한글 자모 + return true; + } + } + return false; + } + + /** 원인 체인의 클래스명 + 메시지를 한 문자열로 모은다(순환 참조 방지). */ + private static String causeChainText(Throwable ex) { + StringBuilder sb = new StringBuilder(); + Throwable current = ex; + int depth = 0; + while (current != null && depth < 10) { + sb.append(current.getClass().getName()); + if (current.getMessage() != null) { + sb.append(' ').append(current.getMessage()); + } + sb.append('\n'); + Throwable cause = current.getCause(); + if (cause == current) { + break; + } + current = cause; + depth++; + } + return sb.toString(); + } + + private static boolean hasType(Throwable ex, Class type) { + Throwable current = ex; + int depth = 0; + while (current != null && depth < 10) { + if (type.isInstance(current)) { + return true; + } + Throwable cause = current.getCause(); + if (cause == current) { + return false; + } + current = cause; + depth++; + } + return false; + } + + private static boolean containsAny(String text, String... keywords) { + for (String keyword : keywords) { + if (text.contains(keyword)) { + return true; + } + } + return false; + } +} diff --git a/src/main/java/com/eactive/apim/portal/common/migration/LegacyEncryptionMigrationController.java b/src/main/java/com/eactive/apim/portal/common/migration/LegacyEncryptionMigrationController.java new file mode 100644 index 0000000..c9d4680 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/common/migration/LegacyEncryptionMigrationController.java @@ -0,0 +1,258 @@ +package com.eactive.apim.portal.common.migration; + +import com.eactive.apim.portal.common.util.StringMaskingUtil; +import com.eactive.apim.portal.jpa.PersonalDataEncryptConverter; +import com.eactive.apim.portal.portalproperty.service.PortalPropertyService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.http.HttpStatus; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.ResponseStatusException; + +import javax.servlet.http.HttpServletRequest; +import javax.sql.DataSource; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * [임시] 레거시 평문 데이터를 {@link PersonalDataEncryptConverter} 규칙으로 일괄 정규화(암호화)하는 운영 도구. + * + *

배경: {@code @Convert} 컬럼이 평문으로 저장된 레거시 행은, derived query가 검색값을 암호화하면서 + * 평문 DB값과 불일치해 검색/로그인이 실패한다. 컨버터는 읽기에서 평문/암호문을 자동 구분하고 + * 쓰기에서 무조건 인코딩하므로, {@code convertToDatabaseColumn(convertToEntityAttribute(x))}는 + * 평문→인코딩, 인코딩→동일값(멱등)으로 정규화된다. 이 값이 기존과 다를 때만 UPDATE 한다.

+ * + *

보안: PTL_PROPERTY {@code Portal / migration.internal.allow-ips} 허용 IP 목록(콤마 구분, + * 기본 loopback)에 포함된 IP 의 직접 호출만 허용한다 ({@code MenuInternalController} 모델). + * 운영 서버는 bind IP 가 NIC IP 라 loopback 호출이 불가하므로, 실행 전 property 에 호출자 IP 를 + * 추가하고 작업 완료 후 원복한다. 프록시 경유(X-Forwarded-For 존재) 요청은 거부한다. + * 기본은 dry-run(미변경)이며, 실제 실행은 {@code dryRun=false}를 명시해야 한다. + * 작업 완료 후 이 클래스는 제거한다.

+ * + *
+ *   # 미리보기(변경 안 함)
+ *   curl -X POST 'http://127.0.0.1:39130/internal/migration/encrypt-legacy'
+ *   # 실제 실행 (PII 컬럼)
+ *   curl -X POST 'http://127.0.0.1:39130/internal/migration/encrypt-legacy?dryRun=false'
+ *   # audit 컬럼(created_by/last_modified_by, 19개 테이블)까지 포함
+ *   curl -X POST 'http://127.0.0.1:39130/internal/migration/encrypt-legacy?dryRun=false&includeAudit=true'
+ * 
+ */ +@Slf4j +@RestController +@RequestMapping("/internal/migration") +public class LegacyEncryptionMigrationController { + + /** PII 직접 컬럼 (로그인/검색에 직접 영향). ofctelno 는 admin(UnifbwkManService)이 컨버터를 수동 호출해 암호화하는 컬럼 */ + private static final List PII_TARGETS = Arrays.asList( + new TargetTable("PTL_USER", Arrays.asList("login_id", "email_addr", "phone_number", "mobile_number")), + new TargetTable("PTL_MESSAGE_REQUEST", Arrays.asList("email", "phone")), + new TargetTable("tseairm02", Arrays.asList("cphnno", "emad", "ofctelno")), + new TargetTable("PTL_USER_LOG", Arrays.asList("login_id")), + new TargetTable("PTL_TWO_FACTOR_AUTH", Arrays.asList("recipient")), + new TargetTable("PTL_USER_INVITATION", Arrays.asList("INVITATION_MOBILE")) + ); + + /** Auditable(@MappedSuperclass) 상속 테이블의 감사 컬럼 (옵션) */ + private static final List AUDIT_TABLES = Arrays.asList( + "PTL_USER", "ptl_faq", "ptl_org", + "DJB_APISTATUS_INCIDENT", "DJB_APISTATUS_INCIDENT_TIMELINE", "DJB_APISTATUS_INCIDENT_API", + "ptl_file", "PTL_MESSAGE_TEMPLATE", "ptl_notice", "ptl_terms", + "ptl_user_privacy_policy_agreement", "ptl_approval_line", + "PTL_INQUIRY_COMMENT", "ptl_inquiry", "ptl_partnership_application", + "PTL_MENU_ITEM", "PTL_MENU_PLACEMENT", "PTL_ROLE", "PTL_ROLE_AUTHORITY" + ); + private static final List AUDIT_COLUMNS = Arrays.asList("created_by", "last_modified_by"); + + static final String PROP_GROUP = "Portal"; + static final String PROP_ALLOW_IPS = "migration.internal.allow-ips"; + static final String DEFAULT_ALLOW_IPS = "127.0.0.1,::1"; + + private final JdbcTemplate jdbcTemplate; + private final PortalPropertyService portalPropertyService; + private final PersonalDataEncryptConverter converter = new PersonalDataEncryptConverter(); + + public LegacyEncryptionMigrationController(@Qualifier("portalDataSource") DataSource emsDataSource, + PortalPropertyService portalPropertyService) { + // EMS(EMSAPP) 스키마 데이터소스. 컨버터 적용 테이블은 모두 EMS에 존재한다. + this.jdbcTemplate = new JdbcTemplate(emsDataSource); + this.portalPropertyService = portalPropertyService; + } + + @PostMapping("/encrypt-legacy") + @Transactional("transactionManager") + public Map encryptLegacy(HttpServletRequest request, + @RequestParam(defaultValue = "true") boolean dryRun, + @RequestParam(defaultValue = "false") boolean includeAudit) { + assertAllowedIp(request); + assertNotBypass(); + + List targets = new ArrayList<>(PII_TARGETS); + if (includeAudit) { + for (String table : AUDIT_TABLES) { + targets.add(new TargetTable(table, AUDIT_COLUMNS)); + } + } + + List> results = new ArrayList<>(); + int totalChanged = 0; + int totalSkipped = 0; + for (TargetTable target : targets) { + for (String column : target.columns) { + Map r = processColumn(target.table, column, dryRun); + results.add(r); + totalChanged += (int) r.get("changed"); + totalSkipped += (int) r.get("skipped"); + } + } + + Map response = new LinkedHashMap<>(); + response.put("mode", dryRun ? "dry-run (변경 없음)" : "executed"); + response.put("damoMode", resolveDamoMode()); + response.put("includeAudit", includeAudit); + response.put("totalChanged", totalChanged); + // 정규화 결과가 빈 값이라 UPDATE 를 생략한 건수. 0 이 아니면 원인 조사 후 진행할 것. + response.put("totalSkipped", totalSkipped); + response.put("results", results); + log.info("[레거시 암호화 마이그레이션] mode={} includeAudit={} totalChanged={} totalSkipped={}", + dryRun ? "dry-run" : "executed", includeAudit, totalChanged, totalSkipped); + return response; + } + + private String resolveDamoMode() { + if (converter.isBypassMode()) { + return "BYPASS"; + } + return converter.isFakeMode() ? "FAKE" : "REAL"; + } + + /** + * 단일 (테이블, 컬럼)의 고유값을 정규화하고, 값이 바뀌는 경우에만 UPDATE. + */ + private Map processColumn(String table, String column, boolean dryRun) { + Map r = new LinkedHashMap<>(); + r.put("table", table); + r.put("column", column); + + List values; + try { + values = jdbcTemplate.queryForList( + "SELECT DISTINCT " + column + " FROM " + table + " WHERE " + column + " IS NOT NULL", + String.class); + } catch (Exception e) { + log.warn("[마이그레이션] 조회 실패 table={} column={} : {}", table, column, e.toString()); + r.put("distinct", 0); + r.put("changed", 0); + r.put("skipped", 0); + r.put("error", e.getMessage()); + return r; + } + + int changed = 0; + int skipped = 0; + for (String value : values) { + String normalized; + try { + // 평문 → 인코딩, 이미 인코딩 → 동일값 (멱등) + normalized = converter.convertToDatabaseColumn(converter.convertToEntityAttribute(value)); + } catch (Exception e) { + log.warn("[마이그레이션] 정규화 실패 table={} column={} : {}", table, column, e.toString()); + continue; + } + if ((normalized == null || normalized.isEmpty()) && !value.isEmpty()) { + // 방어: 원본이 비어있지 않은데 정규화 결과가 빈 값 → 절대 UPDATE 하지 않음 (데이터 소실 방지) + skipped++; + log.warn("[마이그레이션] 정규화 결과가 빈 값 — UPDATE 생략 table={} column={} valueLen={}", + table, column, value.length()); + continue; + } + if (normalized != null && !normalized.equals(value)) { + if (!dryRun) { + jdbcTemplate.update( + "UPDATE " + table + " SET " + column + " = ? WHERE " + column + " = ?", + normalized, value); + } + changed++; + } + } + + r.put("distinct", values.size()); + r.put("changed", changed); + r.put("skipped", skipped); + return r; + } + + /** + * damo-manager 가 bypass 모드면 실행 자체를 거부한다. + * bypass 에서는 {@code encrypt} 가 무변환(원문 그대로)이라 정규화가 평문→평문 no-op 이 되어 + * 마이그레이션이 의미가 없고, "완료"로 오인될 위험이 있다. real/fake 모드로 기동 후 실행해야 한다. + */ + private void assertNotBypass() { + if (converter.isBypassMode()) { + log.warn("[마이그레이션] bypass 모드 실행 거부 — 암복호화가 무변환이라 마이그레이션이 무의미함"); + throw new ResponseStatusException(HttpStatus.CONFLICT, + "damo-manager 가 bypass 모드입니다. 암복호화가 무변환(원문 그대로)이라 마이그레이션이 무의미하므로 거부합니다. " + + "real/fake 모드(-Ddamo-manager.enabled=true)로 기동한 뒤 실행하세요."); + } + } + + /** + * PTL_PROPERTY({@code Portal / migration.internal.allow-ips}) 허용 IP 목록 검사. + * 프록시 경유(X-Forwarded-For 존재) 요청은 IP 신뢰 불가로 거부한다. ({@code MenuInternalController} 모델) + * 운영 서버는 bind IP 가 NIC IP 라 loopback 기본값으로는 호출 불가 — 실행 전 property 에 + * 호출자 IP 를 추가하고 완료 후 원복한다. + */ + private void assertAllowedIp(HttpServletRequest request) { + String remote = canonicalize(request.getRemoteAddr()); + boolean viaProxy = request.getHeader("X-Forwarded-For") != null; + + Set allowed = Arrays.stream(resolveAllowIps().split(",")) + .map(String::trim) + .filter(ip -> !ip.isEmpty()) + .map(LegacyEncryptionMigrationController::canonicalize) + .collect(Collectors.toSet()); + + if (viaProxy || !allowed.contains(remote)) { + log.warn("[마이그레이션] 비허용 접근 차단 remoteAddr={} viaProxy={} xff={}", + StringMaskingUtil.maskIpAddress(remote), viaProxy, + StringMaskingUtil.maskIpAddress(request.getHeader("X-Forwarded-For"))); + throw new ResponseStatusException(HttpStatus.FORBIDDEN, + "허용되지 않은 접근입니다. (PTL_PROPERTY " + PROP_GROUP + "/" + PROP_ALLOW_IPS + " 확인)"); + } + } + + private String resolveAllowIps() { + try { + return portalPropertyService.getOrCreateProperty(PROP_GROUP, PROP_ALLOW_IPS, + DEFAULT_ALLOW_IPS, "레거시 암호화 마이그레이션 내부 API 허용 IP 목록(콤마 구분)"); + } catch (Exception e) { + log.warn("[마이그레이션] 허용 IP 목록 조회 실패 - 기본값({}) 사용", DEFAULT_ALLOW_IPS, e); + return DEFAULT_ALLOW_IPS; + } + } + + /** IPv6 loopback 표기 통일 */ + private static String canonicalize(String ip) { + return "0:0:0:0:0:0:0:1".equals(ip) ? "::1" : ip; + } + + private static final class TargetTable { + final String table; + final List columns; + + TargetTable(String table, List columns) { + this.table = table; + this.columns = columns; + } + } +} diff --git a/src/main/java/com/eactive/apim/portal/common/security/ClientGuardService.java b/src/main/java/com/eactive/apim/portal/common/security/ClientGuardService.java new file mode 100644 index 0000000..892715b --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/common/security/ClientGuardService.java @@ -0,0 +1,42 @@ +package com.eactive.apim.portal.common.security; + +import com.eactive.apim.portal.portalproperty.service.PortalPropertyService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +/** + * 클라이언트측 하드닝(우클릭 차단 · DevTools 감지) 토글을 DB(PortalProperty)에서 조회한다. + * + *

두 기능은 서로 독립된 property 로 on/off 한다. group 은 기존 {@code Portal} 을 재사용하여 + * {@link PortalPropertyService#getOrCreateProperty} 의 자동 생성이 동작하도록 한다.

+ * + *
    + *
  • {@code djb.client-guard.contextmenu.enabled} - 우클릭(contextmenu) 차단
  • + *
  • {@code djb.client-guard.devtools.enabled} - DevTools 감지 시 화면 삭제
  • + *
+ */ +@Service +@RequiredArgsConstructor +public class ClientGuardService { + + private static final String GROUP = "Portal"; + private static final String NAME_CONTEXTMENU = "djb.client-guard.contextmenu.enabled"; + private static final String NAME_DEVTOOLS = "djb.client-guard.devtools.enabled"; + + private final PortalPropertyService portalPropertyService; + + /** 우클릭(contextmenu) 차단 활성화 여부 */ + public boolean isContextmenuBlockEnabled() { + return read(NAME_CONTEXTMENU, "우클릭(contextmenu) 차단"); + } + + /** DevTools 감지 시 화면 삭제 활성화 여부 */ + public boolean isDevtoolsGuardEnabled() { + return read(NAME_DEVTOOLS, "DevTools 감지 시 화면 삭제"); + } + + private boolean read(String name, String desc) { + return Boolean.parseBoolean( + portalPropertyService.getOrCreateProperty(GROUP, name, "false", desc).trim()); + } +} diff --git a/src/main/java/com/eactive/apim/portal/common/security/LoginLockPolicy.java b/src/main/java/com/eactive/apim/portal/common/security/LoginLockPolicy.java new file mode 100644 index 0000000..5ccc4f0 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/common/security/LoginLockPolicy.java @@ -0,0 +1,44 @@ +package com.eactive.apim.portal.common.security; + +import com.eactive.apim.portal.portalproperty.service.PortalPropertyService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +/** + * 로그인 실패 계정 잠금 임계 횟수를 DB(PortalProperty)에서 조회한다. + * + *

PTL_PROPERTY (group={@code Portal}, name={@code login.failure.lock.count}) 값으로 제어한다. + * 값이 없으면 기본값 {@value #DEFAULT_LOCK_COUNT}로 자동 생성되고, 숫자가 아니거나 + * 0 이하이면 기본값으로 동작한다.

+ */ +@Slf4j +@Service +@RequiredArgsConstructor +public class LoginLockPolicy { + + private static final String GROUP = "Portal"; + private static final String NAME = "login.failure.lock.count"; + + /** 기본 잠금 임계 횟수 (프로퍼티 미존재/파싱 실패 시) */ + public static final int DEFAULT_LOCK_COUNT = 5; + + private final PortalPropertyService portalPropertyService; + + /** 연속 로그인 실패가 이 값 이상이면 계정을 잠근다. */ + public int lockCount() { + String raw = portalPropertyService.getOrCreateProperty( + GROUP, NAME, String.valueOf(DEFAULT_LOCK_COUNT), + "로그인 연속 실패 계정 잠금 임계 횟수 (이 값 이상 실패 시 잠금)"); + try { + int parsed = Integer.parseInt(raw.trim()); + if (parsed > 0) { + return parsed; + } + log.warn("login.failure.lock.count 값이 0 이하({}) - 기본값 {} 사용", parsed, DEFAULT_LOCK_COUNT); + } catch (NumberFormatException e) { + log.warn("login.failure.lock.count 값이 숫자가 아님('{}') - 기본값 {} 사용", raw, DEFAULT_LOCK_COUNT); + } + return DEFAULT_LOCK_COUNT; + } +} diff --git a/src/main/java/com/eactive/apim/portal/common/util/StringMaskingUtil.java b/src/main/java/com/eactive/apim/portal/common/util/StringMaskingUtil.java index c25609b..0a0767e 100644 --- a/src/main/java/com/eactive/apim/portal/common/util/StringMaskingUtil.java +++ b/src/main/java/com/eactive/apim/portal/common/util/StringMaskingUtil.java @@ -1,10 +1,24 @@ package com.eactive.apim.portal.common.util; +import java.util.Arrays; + /** * 문자열 마스킹 처리를 위한 유틸리티 클래스 + * + *

보안아키텍처 표준 마스킹 규칙(elink-online-core {@code MaskingUtils})과 동일한 알고리즘을 + * 구현한다. eapim-portal은 elink-online-core에 의존하지 않으므로 로직을 직접 포함한다. + * 소유자 본인이 자신의 정보를 조회하는 경우 마스킹하지 않는 owner 인지 동작은 유지한다.

+ * + *
+ *   이름     2자: 뒤 1자리(가나→가*) / 3자 이상: 앞뒤 제외 가운데 전체(가나다라→가**라)
+ *   이메일   아이디 앞2·뒤2 마스킹, 4자 이하 전체(test→****, test123→**st1**)
+ *   전화     2·3번째 세그먼트 각각 뒤 2자리(010-1234-1234→010-12**-12**)
+ * 
*/ public class StringMaskingUtil { + private static final char MASK = '*'; + // ID 비교 private static boolean isOwner(String ownerId, String currentUserId) { return currentUserId != null && ownerId != null && currentUserId.equals(ownerId); @@ -21,8 +35,15 @@ public class StringMaskingUtil { return name; } - return name.length() > 1 ? - name.substring(0, name.length() - 1) + "*" : name; + int len = name.length(); + if (len == 1) { + return name; + } + if (len == 2) { + return name.charAt(0) + String.valueOf(MASK); + } + // 3자 이상: 첫 자 + 가운데 전체 마스킹 + 마지막 자 + return name.charAt(0) + stars(len - 2) + name.charAt(len - 1); } // 이메일 마스킹 처리 @@ -31,32 +52,162 @@ public class StringMaskingUtil { return email; } - String[] parts = email.split("@", 2); - if (parts.length != 2) { - return email; + int atIdx = email.indexOf('@'); + String local = email.substring(0, atIdx); + String domain = email.substring(atIdx); + + if (local.length() <= 4) { + return stars(local.length()) + domain; } - - String localPart = parts[0]; - String maskedLocal; - - if (localPart.length() <= 2) { - maskedLocal = "***"; - } else { - maskedLocal = localPart.substring(0, localPart.length() - 3) + "***"; - } - - return maskedLocal + "@" + parts[1]; + return stars(2) + local.substring(2, local.length() - 2) + stars(2) + domain; } - // 휴대폰 번호 마스킹 처리 + // 휴대폰/전화/FAX 번호 마스킹 처리 public static String maskMobileNumber(String number, String ownerId, String currentUserId) { if (!isValidString(number) || isOwner(ownerId, currentUserId)) { return number; } - String[] parts = number.split("-", 3); - return parts.length == 3 ? - parts[0] + "-***-" + parts[2] : number; + boolean hasHyphen = number.contains("-"); + String normalized = hasHyphen ? number : normalizePhoneNumber(number); + String[] parts = normalized.split("-"); + if (parts.length == 3) { + String m0 = parts[0]; + String m1 = maskSegmentTail(parts[1], 2); + String m2 = maskSegmentTail(parts[2], 2); + return hasHyphen ? m0 + "-" + m1 + "-" + m2 : m0 + m1 + m2; + } + return number; + } + + /** + * 로그인 ID 마스킹 처리 (로그 출력용). + * 이메일 형식이면 {@link #maskEmail(String)} 규칙을, 그 외(스태프 등 비이메일 ID)는 부분 마스킹을 적용한다. + */ + public static String maskLoginId(String loginId) { + if (!isValidString(loginId)) { + return loginId; + } + if (loginId.contains("@")) { + return maskEmail(loginId); + } + int len = loginId.length(); + if (len <= 2) { + return stars(len); + } + if (len <= 4) { + return loginId.charAt(0) + stars(len - 1); + } + // 5자 이상: 앞2 + 마스킹 + 뒤1 + return loginId.substring(0, 2) + stars(len - 3) + loginId.charAt(len - 1); + } + + /** + * IP 주소 마스킹 처리 (로그 출력용). 첫 옥텟만 남기고 나머지를 마스킹한다. + *
127.0.0.1 → 127.***.***.***
+ * IPv4 형식이 아니면 원본을 반환한다(멱등). + */ + public static String maskIpAddress(String ip) { + if (!isValidString(ip)) { + return ip; + } + String[] parts = ip.split("\\."); + if (parts.length != 4) { + return ip; + } + return parts[0] + ".***.***.***"; + } + + /** + * 토큰/세션ID 등 식별자 마스킹 처리 (로그 출력용). 앞 4자 + {@code ***} + 뒤 4자만 노출한다. + * 길이가 짧으면(9자 미만) 전체를 마스킹한다. + */ + public static String maskToken(String token) { + if (!isValidString(token)) { + return token; + } + int len = token.length(); + if (len < 9) { + return stars(len); + } + return token.substring(0, 4) + "***" + token.substring(len - 4); + } + + // 로그에 값을 그대로 남기면 안 되는 민감 헤더(소문자 비교) + private static final java.util.Set SENSITIVE_HEADERS = new java.util.HashSet<>(Arrays.asList( + "cookie", "set-cookie", "authorization", "proxy-authorization", + "x-auth-token", "x-csrf-token", "x-xsrf-token", "x-api-key")); + + private static final String REDACTED = "***REDACTED***"; + + /** + * 요청/응답 헤더 로깅 시 민감 헤더(Cookie/Authorization 등)의 값을 {@code ***REDACTED***} 로 치환한다. + * 그 외 헤더는 원본 값을 반환한다. + */ + public static String maskHeaderValue(String headerName, String headerValue) { + if (headerName != null && SENSITIVE_HEADERS.contains(headerName.toLowerCase())) { + return REDACTED; + } + return headerValue; + } + + // 세션 속성 이름에 포함되면 값을 리댁트할 키워드(소문자 부분일치) + private static final String[] SENSITIVE_ATTRIBUTE_KEYWORDS = { + "token", "secret", "password", "credential", "csrf", + "security_context", "loginid", "authentication"}; + + /** + * 세션 속성 로깅 시 민감 속성(SPRING_SECURITY_CONTEXT, token, loginId 등)의 값을 리댁트한다. + * 속성 이름에 민감 키워드가 부분일치하면 {@code ***REDACTED***} 를 반환한다. + */ + public static String maskAttributeValue(String attrName, String value) { + if (attrName != null) { + String lower = attrName.toLowerCase(); + for (String kw : SENSITIVE_ATTRIBUTE_KEYWORDS) { + if (lower.contains(kw)) { + return REDACTED; + } + } + } + return value; + } + + // 폼(application/x-www-form-urlencoded) 본문에서 값을 리댁트할 파라미터 키(소문자 완전일치) + private static final java.util.Set SENSITIVE_FORM_PARAMS = new java.util.HashSet<>(Arrays.asList( + "client_secret", "clientsecret", "secret", "password", "passwd", "pwd", + "refresh_token", "access_token", "id_token", "code", "assertion")); + + /** + * {@code application/x-www-form-urlencoded} 본문 로깅 시 민감 파라미터(client_secret/password 등)의 + * 값을 {@link #maskToken(String)} 규칙(앞4·뒤4)으로 마스킹한다. client_id·grant_type·scope 등은 + * "client not found" 류 원인 파악을 위해 원본 그대로 남긴다. 폼 형식이 아니면 원본을 반환한다(멱등). + *
grant_type=client_credentials&client_id=ABC&client_secret=s3cr3tValue → …&client_secret=s3cr***alue
+ */ + public static String maskFormBody(String body) { + if (!isValidString(body) || body.indexOf('=') < 0) { + return body; + } + String[] pairs = body.split("&"); + StringBuilder sb = new StringBuilder(body.length()); + for (int i = 0; i < pairs.length; i++) { + if (i > 0) { + sb.append('&'); + } + String pair = pairs[i]; + int eq = pair.indexOf('='); + if (eq < 0) { + sb.append(pair); + continue; + } + String key = pair.substring(0, eq); + String value = pair.substring(eq + 1); + if (SENSITIVE_FORM_PARAMS.contains(key.toLowerCase())) { + sb.append(key).append('=').append(maskToken(value)); + } else { + sb.append(pair); + } + } + return sb.toString(); } // 기존 메서드 오버로딩 (하위 호환성) @@ -71,4 +222,41 @@ public class StringMaskingUtil { public static String maskMobileNumber(String number) { return maskMobileNumber(number, null, null); } -} \ No newline at end of file + + // ========================================================================= + // private helpers + // ========================================================================= + + private static String stars(int count) { + if (count <= 0) return ""; + char[] arr = new char[count]; + Arrays.fill(arr, MASK); + return new String(arr); + } + + private static String maskSegmentTail(String segment, int count) { + if (segment.length() <= count) return stars(segment.length()); + return segment.substring(0, segment.length() - count) + stars(count); + } + + /** + * 한국 전화번호(숫자만) → XXX-XXXX-XXXX 형식으로 정규화 + *
+     *   02 + 7자리  → 02-XXX-XXXX
+     *   02 + 8자리  → 02-XXXX-XXXX
+     *   0XX + 7자리 → 0XX-XXX-XXXX
+     *   0XX + 8자리 → 0XX-XXXX-XXXX
+     * 
+ */ + private static String normalizePhoneNumber(String digits) { + int len = digits.length(); + if (digits.startsWith("02")) { + if (len == 9) return digits.substring(0, 2) + "-" + digits.substring(2, 5) + "-" + digits.substring(5); + if (len == 10) return digits.substring(0, 2) + "-" + digits.substring(2, 6) + "-" + digits.substring(6); + } else if (digits.startsWith("0")) { + if (len == 10) return digits.substring(0, 3) + "-" + digits.substring(3, 6) + "-" + digits.substring(6); + if (len == 11) return digits.substring(0, 3) + "-" + digits.substring(3, 7) + "-" + digits.substring(7); + } + return digits; + } +} diff --git a/src/main/java/com/eactive/apim/portal/common/util/UserTypeUtil.java b/src/main/java/com/eactive/apim/portal/common/util/UserTypeUtil.java index 221d879..489e623 100644 --- a/src/main/java/com/eactive/apim/portal/common/util/UserTypeUtil.java +++ b/src/main/java/com/eactive/apim/portal/common/util/UserTypeUtil.java @@ -3,9 +3,18 @@ package com.eactive.apim.portal.common.util; import com.eactive.apim.portal.common.user.PortalAuthenticatedUser; import org.apache.commons.lang3.StringUtils; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + /** * 사용자 유형 판별 유틸리티 * 내부 운영자와 외부 사용자를 구분하는 공통 로직 제공 + * + * 내부 사용자로 인정할 이메일 도메인은 application.yml 의 + * {@code portal.internal-user.email-domains} 로 관리한다. + * 설정이 없으면 {@link #DEFAULT_INTERNAL_EMAIL_DOMAINS} 를 사용한다. */ public final class UserTypeUtil { @@ -13,7 +22,47 @@ public final class UserTypeUtil { throw new IllegalStateException("Utility class"); } - private static final String INTERNAL_EMAIL_DOMAIN = "@kjbank.com"; + /** 설정 미주입 시 사용할 기본 내부 도메인 */ + private static final List DEFAULT_INTERNAL_EMAIL_DOMAINS = + Collections.unmodifiableList(Arrays.asList("@djbank.com", "@unit-test.com")); + + private static volatile List internalEmailDomains = DEFAULT_INTERNAL_EMAIL_DOMAINS; + + /** + * 내부 사용자 이메일 도메인 목록 설정. + * {@code UserTypeConfigurer} 가 기동 시 application.yml 값으로 주입한다. + * + * @param domains 도메인 목록 ("@" 접두사 유무 무관, 대소문자 무관) + */ + public static void setInternalEmailDomains(List domains) { + if (domains == null || domains.isEmpty()) { + internalEmailDomains = DEFAULT_INTERNAL_EMAIL_DOMAINS; + return; + } + List normalized = new ArrayList<>(); + for (String domain : domains) { + if (StringUtils.isBlank(domain)) { + continue; + } + String value = domain.trim().toLowerCase(); + if (!value.startsWith("@")) { + value = "@" + value; + } + if (!normalized.contains(value)) { + normalized.add(value); + } + } + internalEmailDomains = normalized.isEmpty() + ? DEFAULT_INTERNAL_EMAIL_DOMAINS + : Collections.unmodifiableList(normalized); + } + + /** + * 현재 적용 중인 내부 사용자 이메일 도메인 목록 + */ + public static List getInternalEmailDomains() { + return internalEmailDomains; + } /** * 현재 사용자가 내부 운영자인지 확인 @@ -21,10 +70,28 @@ public final class UserTypeUtil { * @return 내부 운영자 여부 */ public static boolean isInternalUser(PortalAuthenticatedUser user) { - if (user == null || StringUtils.isEmpty(user.getEmailAddr())) { + if (user == null) { return false; } - return user.getEmailAddr().toLowerCase().endsWith(INTERNAL_EMAIL_DOMAIN); + return isInternalEmail(user.getEmailAddr()); + } + + /** + * 이메일 주소의 호스트(도메인)로 내부 운영자 여부 판별 + * @param emailAddr 이메일 주소 + * @return 내부 운영자 여부 + */ + public static boolean isInternalEmail(String emailAddr) { + if (StringUtils.isEmpty(emailAddr)) { + return false; + } + String email = emailAddr.trim().toLowerCase(); + for (String domain : internalEmailDomains) { + if (email.endsWith(domain)) { + return true; + } + } + return false; } /** diff --git a/src/main/java/com/eactive/apim/portal/common/validator/PasswordRule.java b/src/main/java/com/eactive/apim/portal/common/validator/PasswordRule.java index 8ac06b2..f3fbda5 100644 --- a/src/main/java/com/eactive/apim/portal/common/validator/PasswordRule.java +++ b/src/main/java/com/eactive/apim/portal/common/validator/PasswordRule.java @@ -5,14 +5,20 @@ import javax.validation.Payload; import java.lang.annotation.*; @Constraint(validatedBy = PasswordRuleValidator.class) -@Target({ElementType.FIELD}) +@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface PasswordRule { - String message() default "비밀 번호 규칙에 부합하지 않습니다.(영문/숫자/특수문자 포함 8~20자, 3자리 이상 연속,반복 문자 불가)"; + String message() default "비밀 번호 규칙에 부합하지 않습니다.(영문/숫자/특수문자 포함 8~50자, 아이디, 휴대전화, 3자리 이상 연속,반복 문자 불가)"; Class[] groups() default {}; Class[] payload() default {}; + String password(); + + String loginId(); + + String mobile(); + } diff --git a/src/main/java/com/eactive/apim/portal/common/validator/PasswordRuleForDjbank.java b/src/main/java/com/eactive/apim/portal/common/validator/PasswordRuleForDjbank.java deleted file mode 100644 index c51f352..0000000 --- a/src/main/java/com/eactive/apim/portal/common/validator/PasswordRuleForDjbank.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.eactive.apim.portal.common.validator; - -import javax.validation.Constraint; -import javax.validation.Payload; -import java.lang.annotation.*; - -@Constraint(validatedBy = PasswordRuleForDjbankValidator.class) -@Target({ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface PasswordRuleForDjbank { - String message() default "비밀 번호 규칙에 부합하지 않습니다.(영문/숫자/특수문자 포함 8~20자, 아이디, 휴대전화, 3자리 이상 연속,반복 문자 불가)"; - - Class[] groups() default {}; - - Class[] payload() default {}; - - String password(); - - String loginId(); - - String mobile(); - -} diff --git a/src/main/java/com/eactive/apim/portal/common/validator/PasswordRuleForDjbankValidator.java b/src/main/java/com/eactive/apim/portal/common/validator/PasswordRuleForDjbankValidator.java deleted file mode 100644 index ec343d7..0000000 --- a/src/main/java/com/eactive/apim/portal/common/validator/PasswordRuleForDjbankValidator.java +++ /dev/null @@ -1,144 +0,0 @@ -package com.eactive.apim.portal.common.validator; - -import org.apache.commons.beanutils.PropertyUtils; - -import javax.validation.ConstraintValidator; -import javax.validation.ConstraintValidatorContext; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Created by Sungpil Hyun - */ -public class PasswordRuleForDjbankValidator implements ConstraintValidator { - - // 최소 8자, 최대 20자 상수 선언 - private static final int MIN = 8; - private static final int MAX = 20; - - private String password; - private String loginId; - private String mobileNumber; - - // 3자리 연속 문자 정규식 - private static final String SAMEPT = "(\\w)\\1\\1"; - // 공백 문자 정규식 - private static final String BLANKPT = "(\\s)"; - - @Override - public void initialize(PasswordRuleForDjbank constraintAnnotation) { - this.password = constraintAnnotation.password(); - this.loginId = constraintAnnotation.loginId(); - this.mobileNumber = constraintAnnotation.mobile(); - } - - @Override - public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) { - - String passwordValue = null; - String loginIdValue = null; - String mobileNumberValue = null; - - try { - passwordValue = (String) PropertyUtils.getProperty(value, this.password); - loginIdValue = (String) PropertyUtils.getProperty(value, this.loginId); - mobileNumberValue = (String) PropertyUtils.getProperty(value, this.mobileNumber); - } catch (Exception e) { - return false; - } - - return isValid(passwordValue, loginIdValue, mobileNumberValue); - } - - public boolean isValid(String password, String loginId, String mobileNumber) { - // 영어, 숫자, 특수문자 포함한 MIN to MAX 글자 정규식 - String REGEX = "^((?=.*\\d)(?=.*[a-zA-Z])(?=.*[\\W]).{" + MIN + "," + MAX + "})$"; - - // 정규식 검사객체 - Matcher matcher; - - // 공백 체크 - if (password == null || "".equals(password)) { - return false; - } - - // ASCII 문자 비교를 위한 UpperCase - String tmpPw = password.toUpperCase(); - // 문자열 길이 - int strLen = tmpPw.length(); - - // 글자 길이 체크 - if (strLen > 20 || strLen < 8) { - return false; - } - - if (loginId != null && !loginId.isEmpty()) { - String[] loginParts = loginId.split("@"); - if (loginParts.length > 0) { - String username = loginParts[0].toUpperCase(); - if (tmpPw.contains(username)) { - return false; - } - } - } - - // Mobile number validation - if (mobileNumber != null && !mobileNumber.isEmpty()) { - String[] mobileParts = mobileNumber.split("-"); - for (String part : mobileParts) { - if (!part.isEmpty() && tmpPw.contains(part)) { - return false; - } - } - } - - // 공백 체크 - matcher = Pattern.compile(BLANKPT).matcher(tmpPw); - if (matcher.find()) { - return false; - } - - // 비밀번호 정규식 체크 - matcher = Pattern.compile(REGEX).matcher(tmpPw); - if (!matcher.find()) { - return false; - } - - // 동일한 문자 3개 이상 체크 - matcher = Pattern.compile(SAMEPT).matcher(tmpPw); - if (matcher.find()) { - return false; - } - - // 연속된 문자 / 숫자 3개 이상 체크 - // ASCII Char를 담을 배열 선언 - int[] tmpArray = new int[strLen]; - - // Make Array - for (int i = 0; i < strLen; i++) { - tmpArray[i] = tmpPw.charAt(i); - } - - // Validation Array - for (int i = 0; i < strLen - 2; i++) { - if (isContinuous(tmpArray[i], tmpArray[i + 2]) && isContinuous(tmpArray[i], tmpArray[i + 1], tmpArray[i + 2])) { - return false; - } - } - // Validation Complete - return true; - } - - - - static boolean isContinuous(int first, int third) { - // 첫 글자 A-Z / 0-9 - return (first > 47 && third < 58) || (first > 64 && third < 91); - } - - static boolean isContinuous(int first, int second, int third) { - // 배열의 연속된 수 검사 - // 3번째 글자 - 2번째 글자 = 1, 3번째 글자 - 1번째 글자 = 2 - return Math.abs(third - second) == 1 && Math.abs(third - first) == 2; - } -} diff --git a/src/main/java/com/eactive/apim/portal/common/validator/PasswordRuleForKbank.java b/src/main/java/com/eactive/apim/portal/common/validator/PasswordRuleForKbank.java deleted file mode 100644 index 9e2af6f..0000000 --- a/src/main/java/com/eactive/apim/portal/common/validator/PasswordRuleForKbank.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.eactive.apim.portal.common.validator; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import javax.validation.Constraint; -import javax.validation.Payload; - -@Constraint(validatedBy = PasswordRuleForKbankValidator.class) -@Target({ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface PasswordRuleForKbank { - String message() default "비밀 번호 규칙에 부합하지 않습니다.(영문/숫자/특수문자 포함 8~20자, 아이디, 휴대전화, 3자리 이상 연속,반복 문자 불가)"; - - Class[] groups() default {}; - - Class[] payload() default {}; - - String password(); - - String loginId(); - - String mobile(); - -} diff --git a/src/main/java/com/eactive/apim/portal/common/validator/PasswordRuleForKbankValidator.java b/src/main/java/com/eactive/apim/portal/common/validator/PasswordRuleForKbankValidator.java deleted file mode 100644 index 81d3cff..0000000 --- a/src/main/java/com/eactive/apim/portal/common/validator/PasswordRuleForKbankValidator.java +++ /dev/null @@ -1,144 +0,0 @@ -package com.eactive.apim.portal.common.validator; - -import java.util.Objects; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import javax.validation.ConstraintValidator; -import javax.validation.ConstraintValidatorContext; -import org.apache.commons.beanutils.PropertyUtils; - -/** - * Created by Sungpil Hyun - */ -public class PasswordRuleForKbankValidator implements ConstraintValidator { - - // 최소 8자, 최대 20자 상수 선언 - private static final int MIN = 8; - private static final int MAX = 20; - - private String password; - private String loginId; - private String mobileNumber; - - // 3자리 연속 문자 정규식 - private static final String SAMEPT = "(\\w)\\1\\1"; - // 공백 문자 정규식 - private static final String BLANKPT = "(\\s)"; - - @Override - public void initialize(PasswordRuleForKbank constraintAnnotation) { - this.password = constraintAnnotation.password(); - this.loginId = constraintAnnotation.loginId(); - this.mobileNumber = constraintAnnotation.mobile(); - } - - @Override - public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) { - - String passwordValue = null; - String loginIdValue = null; - String mobileNumberValue = null; - - try { - passwordValue = (String) PropertyUtils.getProperty(value, this.password); - loginIdValue = (String) PropertyUtils.getProperty(value, this.loginId); - mobileNumberValue = (String) PropertyUtils.getProperty(value, this.mobileNumber); - } catch (Exception e) { - return false; - } - - return isValid(passwordValue, loginIdValue, mobileNumberValue); - } - - public boolean isValid(String password, String loginId, String mobileNumber) { - // 영어, 숫자, 특수문자 포함한 MIN to MAX 글자 정규식 - String REGEX = "^((?=.*\\d)(?=.*[a-zA-Z])(?=.*[\\W]).{" + MIN + "," + MAX + "})$"; - - // 정규식 검사객체 - Matcher matcher; - - // 공백 체크 - if (password == null || "".equals(password)) { - return false; - } - - // ASCII 문자 비교를 위한 UpperCase - String tmpPw = password.toUpperCase(); - // 문자열 길이 - int strLen = tmpPw.length(); - - // 글자 길이 체크 - if (strLen > 20 || strLen < 8) { - return false; - } - - if (loginId != null && !loginId.isEmpty()) { - String[] loginParts = loginId.split("@"); - if (loginParts.length > 0) { - String username = loginParts[0].toUpperCase(); - if (tmpPw.contains(username)) { - return false; - } - } - } - - // Mobile number validation - if (mobileNumber != null && !mobileNumber.isEmpty()) { - String[] mobileParts = mobileNumber.split("-"); - for (String part : mobileParts) { - if (!part.isEmpty() && tmpPw.contains(part)) { - return false; - } - } - } - - // 공백 체크 - matcher = Pattern.compile(BLANKPT).matcher(tmpPw); - if (matcher.find()) { - return false; - } - - // 비밀번호 정규식 체크 - matcher = Pattern.compile(REGEX).matcher(tmpPw); - if (!matcher.find()) { - return false; - } - - // 동일한 문자 3개 이상 체크 - matcher = Pattern.compile(SAMEPT).matcher(tmpPw); - if (matcher.find()) { - return false; - } - - // 연속된 문자 / 숫자 3개 이상 체크 - // ASCII Char를 담을 배열 선언 - int[] tmpArray = new int[strLen]; - - // Make Array - for (int i = 0; i < strLen; i++) { - tmpArray[i] = tmpPw.charAt(i); - } - - // Validation Array - for (int i = 0; i < strLen - 2; i++) { - if (isContinuous(tmpArray[i], tmpArray[i + 2]) && isContinuous(tmpArray[i], tmpArray[i + 1], tmpArray[i + 2])) { - return false; - } - } - // Validation Complete - return true; - } - - - - static boolean isContinuous(int first, int third) { - // 첫 글자 A-Z / 0-9 - return (first > 47 && third < 58) || (first > 64 && third < 91); - } - - static boolean isContinuous(int first, int second, int third) { - // 배열의 연속된 수 검사 - // 3번째 글자 - 2번째 글자 = 1, 3번째 글자 - 1번째 글자 = 2 - return Math.abs(third - second) == 1 && Math.abs(third - first) == 2; - } -} diff --git a/src/main/java/com/eactive/apim/portal/common/validator/PasswordRuleForKjbank.java b/src/main/java/com/eactive/apim/portal/common/validator/PasswordRuleForKjbank.java deleted file mode 100644 index e3e1704..0000000 --- a/src/main/java/com/eactive/apim/portal/common/validator/PasswordRuleForKjbank.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.eactive.apim.portal.common.validator; - -import javax.validation.Constraint; -import javax.validation.Payload; -import java.lang.annotation.*; - -@Constraint(validatedBy = PasswordRuleForDjbankValidator.class) -@Target({ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface PasswordRuleForKjbank { - String message() default "비밀 번호 규칙에 부합하지 않습니다.(영문/숫자/특수문자 포함 8~20자, 아이디, 휴대전화, 3자리 이상 연속,반복 문자 불가)"; - - Class[] groups() default {}; - - Class[] payload() default {}; - - String password(); - - String loginId(); - - String mobile(); - -} diff --git a/src/main/java/com/eactive/apim/portal/common/validator/PasswordRuleForKjbankValidator.java b/src/main/java/com/eactive/apim/portal/common/validator/PasswordRuleForKjbankValidator.java deleted file mode 100644 index 35d7201..0000000 --- a/src/main/java/com/eactive/apim/portal/common/validator/PasswordRuleForKjbankValidator.java +++ /dev/null @@ -1,144 +0,0 @@ -package com.eactive.apim.portal.common.validator; - -import org.apache.commons.beanutils.PropertyUtils; - -import javax.validation.ConstraintValidator; -import javax.validation.ConstraintValidatorContext; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Created by Sungpil Hyun - */ -public class PasswordRuleForKjbankValidator implements ConstraintValidator { - - // 최소 8자, 최대 20자 상수 선언 - private static final int MIN = 8; - private static final int MAX = 20; - - private String password; - private String loginId; - private String mobileNumber; - - // 3자리 연속 문자 정규식 - private static final String SAMEPT = "(\\w)\\1\\1"; - // 공백 문자 정규식 - private static final String BLANKPT = "(\\s)"; - - @Override - public void initialize(PasswordRuleForKjbank constraintAnnotation) { - this.password = constraintAnnotation.password(); - this.loginId = constraintAnnotation.loginId(); - this.mobileNumber = constraintAnnotation.mobile(); - } - - @Override - public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) { - - String passwordValue = null; - String loginIdValue = null; - String mobileNumberValue = null; - - try { - passwordValue = (String) PropertyUtils.getProperty(value, this.password); - loginIdValue = (String) PropertyUtils.getProperty(value, this.loginId); - mobileNumberValue = (String) PropertyUtils.getProperty(value, this.mobileNumber); - } catch (Exception e) { - return false; - } - - return isValid(passwordValue, loginIdValue, mobileNumberValue); - } - - public boolean isValid(String password, String loginId, String mobileNumber) { - // 영어, 숫자, 특수문자 포함한 MIN to MAX 글자 정규식 - String REGEX = "^((?=.*\\d)(?=.*[a-zA-Z])(?=.*[\\W]).{" + MIN + "," + MAX + "})$"; - - // 정규식 검사객체 - Matcher matcher; - - // 공백 체크 - if (password == null || "".equals(password)) { - return false; - } - - // ASCII 문자 비교를 위한 UpperCase - String tmpPw = password.toUpperCase(); - // 문자열 길이 - int strLen = tmpPw.length(); - - // 글자 길이 체크 - if (strLen > 20 || strLen < 8) { - return false; - } - - if (loginId != null && !loginId.isEmpty()) { - String[] loginParts = loginId.split("@"); - if (loginParts.length > 0) { - String username = loginParts[0].toUpperCase(); - if (tmpPw.contains(username)) { - return false; - } - } - } - - // Mobile number validation - if (mobileNumber != null && !mobileNumber.isEmpty()) { - String[] mobileParts = mobileNumber.split("-"); - for (String part : mobileParts) { - if (!part.isEmpty() && tmpPw.contains(part)) { - return false; - } - } - } - - // 공백 체크 - matcher = Pattern.compile(BLANKPT).matcher(tmpPw); - if (matcher.find()) { - return false; - } - - // 비밀번호 정규식 체크 - matcher = Pattern.compile(REGEX).matcher(tmpPw); - if (!matcher.find()) { - return false; - } - - // 동일한 문자 3개 이상 체크 - matcher = Pattern.compile(SAMEPT).matcher(tmpPw); - if (matcher.find()) { - return false; - } - - // 연속된 문자 / 숫자 3개 이상 체크 - // ASCII Char를 담을 배열 선언 - int[] tmpArray = new int[strLen]; - - // Make Array - for (int i = 0; i < strLen; i++) { - tmpArray[i] = tmpPw.charAt(i); - } - - // Validation Array - for (int i = 0; i < strLen - 2; i++) { - if (isContinuous(tmpArray[i], tmpArray[i + 2]) && isContinuous(tmpArray[i], tmpArray[i + 1], tmpArray[i + 2])) { - return false; - } - } - // Validation Complete - return true; - } - - - - static boolean isContinuous(int first, int third) { - // 첫 글자 A-Z / 0-9 - return (first > 47 && third < 58) || (first > 64 && third < 91); - } - - static boolean isContinuous(int first, int second, int third) { - // 배열의 연속된 수 검사 - // 3번째 글자 - 2번째 글자 = 1, 3번째 글자 - 1번째 글자 = 2 - return Math.abs(third - second) == 1 && Math.abs(third - first) == 2; - } -} diff --git a/src/main/java/com/eactive/apim/portal/common/validator/PasswordRuleValidator.java b/src/main/java/com/eactive/apim/portal/common/validator/PasswordRuleValidator.java index 0dc962b..717df43 100644 --- a/src/main/java/com/eactive/apim/portal/common/validator/PasswordRuleValidator.java +++ b/src/main/java/com/eactive/apim/portal/common/validator/PasswordRuleValidator.java @@ -1,5 +1,8 @@ package com.eactive.apim.portal.common.validator; +import com.eactive.apim.portal.common.util.PhoneNumberUtil; +import org.apache.commons.beanutils.PropertyUtils; + import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import java.util.regex.Matcher; @@ -8,11 +11,15 @@ import java.util.regex.Pattern; /** * Created by Sungpil Hyun */ -public class PasswordRuleValidator implements ConstraintValidator { +public class PasswordRuleValidator implements ConstraintValidator { - // 최소 8자, 최대 20자 상수 선언 + // 최소 8자, 최대 50자 상수 선언 private static final int MIN = 8; - private static final int MAX = 20; + private static final int MAX = 50; + + private String password; + private String loginId; + private String mobileNumber; // 3자리 연속 문자 정규식 private static final String SAMEPT = "(\\w)\\1\\1"; @@ -21,11 +28,30 @@ public class PasswordRuleValidator implements ConstraintValidator 20 || strLen < 8) { + if (strLen > MAX || strLen < MIN) { + return false; + } + + if (containsLoginIdLocalPart(password, loginId)) { + return false; + } + + if (containsMobileSegment(password, mobileNumber)) { return false; } @@ -84,6 +118,33 @@ public class PasswordRuleValidator implements ConstraintValidator 47 && third < 58) || (first > 64 && third < 91); @@ -94,5 +155,4 @@ public class PasswordRuleValidator implements ConstraintValidator배경: Actuator 의 {@code /actuator/mappings} 는 servlet filter / servlet / handler mapping 만 + * 보여주고 interceptor 는 항목 자체가 없다. {@code /actuator/beans} 로도 안 보이는데, + * {@link PortalConfigWebDispatcherServlet#addInterceptors} 가 {@code new} 로 직접 생성해 등록하므로 + * 스프링 빈이 아니기 때문이다. 그래서 HandlerMapping 내부 목록을 직접 읽어서 노출한다. + * + *

노출 경로 2가지: + *

    + *
  • {@code GET /actuator/interceptors} — 전용 엔드포인트
  • + *
  • {@code GET /actuator/info} 의 {@code interceptors} 항목 — Spring Boot Admin 은 커스텀 + * 엔드포인트용 화면이 없으므로, SBA UI(Details 탭의 Info 카드)에서 보려면 이쪽이 필요하다. + * {@code management.info.env.enabled} 와 무관하게 InfoContributor 는 항상 동작한다.
  • + *
+ * + *

이 설정은 actuator 가 classpath 에 있을 때만 활성화된다. actuator 는 build.gradle 에서 + * 로컬 전용(runtimeOnly + war 산출물 제외)이므로 WebLogic 배포본(dev/stage/prod)에서는 + * 조건이 거짓이 되어 이 클래스 자체가 로드되지 않는다. + */ +@Configuration +@ConditionalOnClass(name = "org.springframework.boot.actuate.endpoint.annotation.Endpoint") +public class InterceptorsEndpointConfig { + + @Bean + public InterceptorsEndpoint interceptorsEndpoint(ApplicationContext applicationContext) { + return new InterceptorsEndpoint(applicationContext); + } + + @Endpoint(id = "interceptors") + public static class InterceptorsEndpoint implements InfoContributor { + + /** AbstractHandlerMapping 이 실제로 실행하는 interceptor 목록(private final). */ + private static final Field ADAPTED_INTERCEPTORS = + ReflectionUtils.findField(AbstractHandlerMapping.class, "adaptedInterceptors"); + + /** MappedInterceptor 의 제외 패턴(private final PatternAdapter[]). include 는 public getter 가 있다. */ + private static final Field EXCLUDE_PATTERNS = + ReflectionUtils.findField(MappedInterceptor.class, "excludePatterns"); + + static { + if (ADAPTED_INTERCEPTORS != null) { + ReflectionUtils.makeAccessible(ADAPTED_INTERCEPTORS); + } + if (EXCLUDE_PATTERNS != null) { + ReflectionUtils.makeAccessible(EXCLUDE_PATTERNS); + } + } + + private final ApplicationContext applicationContext; + + public InterceptorsEndpoint(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + /** + * HandlerMapping 빈 이름 → 등록 순서대로의 interceptor 목록. + * 목록 순서가 곧 preHandle 실행 순서다(afterCompletion 은 역순). + */ + @ReadOperation + public Map>> interceptors() { + Map>> result = new TreeMap<>(); + Map mappings = + applicationContext.getBeansOfType(AbstractHandlerMapping.class); + for (Map.Entry entry : mappings.entrySet()) { + List> described = describe(entry.getValue()); + if (!described.isEmpty()) { + result.put(entry.getKey(), described); + } + } + return result; + } + + @Override + public void contribute(Info.Builder builder) { + builder.withDetail("interceptors", interceptors()); + } + + private List> describe(AbstractHandlerMapping mapping) { + if (ADAPTED_INTERCEPTORS == null) { + return Collections.emptyList(); + } + Object raw = ReflectionUtils.getField(ADAPTED_INTERCEPTORS, mapping); + if (!(raw instanceof List)) { + return Collections.emptyList(); + } + List> described = new ArrayList<>(); + int order = 0; + for (Object each : (List) raw) { + described.add(describe(order++, (HandlerInterceptor) each)); + } + return described; + } + + private Map describe(int order, HandlerInterceptor interceptor) { + Map row = new LinkedHashMap<>(); + row.put("order", order); + if (interceptor instanceof MappedInterceptor) { + MappedInterceptor mapped = (MappedInterceptor) interceptor; + row.put("interceptor", mapped.getInterceptor().getClass().getName()); + row.put("include", nullSafe(mapped.getPathPatterns())); + row.put("exclude", excludePatterns(mapped)); + } else { + // WebMvcConfigurationSupport 가 자동으로 넣는 것들(ConversionServiceExposing 등). + // 경로 제한이 없어 모든 요청에 적용된다. + row.put("interceptor", interceptor.getClass().getName()); + row.put("include", Collections.singletonList("/**")); + row.put("exclude", Collections.emptyList()); + } + return row; + } + + /** PatternAdapter[] → 패턴 문자열 목록. PatternAdapter 는 package-private 이라 리플렉션으로 읽는다. */ + private List excludePatterns(MappedInterceptor mapped) { + if (EXCLUDE_PATTERNS == null) { + return Collections.emptyList(); + } + Object adapters = ReflectionUtils.getField(EXCLUDE_PATTERNS, mapped); + if (adapters == null) { + return Collections.emptyList(); + } + int length = Array.getLength(adapters); + List patterns = new ArrayList<>(length); + for (int i = 0; i < length; i++) { + Object adapter = Array.get(adapters, i); + Method getPatternString = ReflectionUtils.findMethod(adapter.getClass(), "getPatternString"); + if (getPatternString == null) { + continue; + } + ReflectionUtils.makeAccessible(getPatternString); + patterns.add((String) ReflectionUtils.invokeMethod(getPatternString, adapter)); + } + return patterns; + } + + private List nullSafe(String[] values) { + return values == null ? Collections.emptyList() : Arrays.asList(values); + } + } +} diff --git a/src/main/java/com/eactive/apim/portal/config/PasswordChangeEnforcementInterceptor.java b/src/main/java/com/eactive/apim/portal/config/PasswordChangeEnforcementInterceptor.java new file mode 100644 index 0000000..7f7fe1a --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/config/PasswordChangeEnforcementInterceptor.java @@ -0,0 +1,86 @@ +package com.eactive.apim.portal.config; + +import org.springframework.web.servlet.HandlerInterceptor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +/** + * 비밀번호 변경 강제(ENFORCE) 가드. + * + *

로그인 시 {@code PortalAuthenticationSuccessHandler} 가 대상자(비밀번호 미변경/만료)에게 + * 세션 플래그 {@link #ENFORCE_SESSION_ATTR} 를 설정한다. 이 플래그가 있는 동안에는 비밀번호 + * 변경/검증/로그아웃 경로를 제외한 모든 요청을 변경 페이지로 리다이렉트하여 접근을 차단한다. + * 비밀번호 변경 완료 시 플래그가 제거되어 정상 접근이 회복된다.

+ * + *

정적 자원 경로는 {@code PortalConfigWebDispatcherServlet.addInterceptors} 의 + * excludePathPatterns 로 제외한다.

+ */ +public class PasswordChangeEnforcementInterceptor implements HandlerInterceptor { + + /** ENFORCE 대상 세션 플래그. 로그인 핸들러가 설정, 변경 완료 시 제거. */ + public static final String ENFORCE_SESSION_ATTR = "pwEnforce"; + + /** 강제 상태에서도 접근 허용하는 경로(화이트리스트) */ + private static final Set ALLOWED_PATHS = new HashSet<>(Arrays.asList( + "/password/verify", // 현재 비밀번호 입력(진입) + 검증(POST) + "/password/change", // 새 비밀번호 폼(GET) + 실제 변경(POST) + "/password/confirm", // 비밀번호 확인 AJAX + "/actionLogout.do", // 로그아웃 + "/login", + "/error", "/403", "/404" + )); + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + HttpSession session = request.getSession(false); + if (session == null || !Boolean.TRUE.equals(session.getAttribute(ENFORCE_SESSION_ATTR))) { + return true; + } + + // AJAX/API 등 비(非)페이지 요청은 강제 리다이렉트 대상에서 제외한다. + // (세션 heartbeat 같은 인프라 호출을 302로 튕기면 keepalive JS가 세션만료로 오판하여 + // 로그인↔홈↔변경페이지 무한 리다이렉트가 발생한다.) + if (!isTopLevelHtmlNavigation(request)) { + return true; + } + + String path = request.getServletPath(); + if (path != null && ALLOWED_PATHS.contains(path)) { + return true; + } + + // 완화 정책: 현재 비밀번호 확인 단계를 제거했으므로 새 비밀번호 폼으로 바로 유도한다. + String target = request.getContextPath() + "/password/change"; + // 이미 목적지면 재리다이렉트하지 않는다(무한 루프 방지). + if (request.getRequestURI().equals(target)) { + return true; + } + + response.sendRedirect(target); + return false; + } + + /** + * 브라우저 주소창 이동(최상위 HTML 문서 요청)인지 판별한다. + * GET + Accept: text/html + 비-AJAX 만 강제 리다이렉트 대상으로 본다. + */ + private boolean isTopLevelHtmlNavigation(HttpServletRequest request) { + if (!"GET".equalsIgnoreCase(request.getMethod())) { + return false; + } + if ("XMLHttpRequest".equalsIgnoreCase(request.getHeader("X-Requested-With"))) { + return false; + } + String fetchMode = request.getHeader("Sec-Fetch-Mode"); + if (fetchMode != null && !"navigate".equalsIgnoreCase(fetchMode)) { + return false; + } + String accept = request.getHeader("Accept"); + return accept != null && accept.contains("text/html"); + } +} diff --git a/src/main/java/com/eactive/apim/portal/config/PasswordEnforcementPolicy.java b/src/main/java/com/eactive/apim/portal/config/PasswordEnforcementPolicy.java new file mode 100644 index 0000000..b66c6e5 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/config/PasswordEnforcementPolicy.java @@ -0,0 +1,40 @@ +package com.eactive.apim.portal.config; + +/** + * 비밀번호 변경 강제 정책 레벨. + * + *

PTL_PROPERTY (group={@code Portal}, name={@code password.change.enforcement}) 값으로 제어한다. + * 키는 DB 관례(점 구분 소문자, 예: {@code session.timeout.minutes})를 따른다. + * 값은 enum 명({@code NONE}/{@code PERMISSIVE}/{@code ENFORCE}, 대소문자 무시)이다.

+ *
    + *
  • {@link #NONE} — 정책 미적용. 안내/강제 없음.
  • + *
  • {@link #PERMISSIVE} — 대상자 로그인 시 1회 안내 팝업만. 강제 없음.
  • + *
  • {@link #ENFORCE} — 대상자는 비밀번호 변경 완료 전까지 변경/검증/로그아웃 외 접근 차단.
  • + *
+ */ +public enum PasswordEnforcementPolicy { + NONE, + PERMISSIVE, + ENFORCE; + + /** PTL_PROPERTY 그룹명 */ + public static final String PROPERTY_GROUP = "Portal"; + /** PTL_PROPERTY 이름 (점 구분 소문자 관례) */ + public static final String PROPERTY_NAME = "password.change.enforcement"; + /** 기본값 (배포 직후 동작) */ + public static final PasswordEnforcementPolicy DEFAULT = ENFORCE; + + /** + * 문자열을 정책으로 파싱한다. 대소문자 무시, 미해당/공백이면 {@link #DEFAULT} 반환. + */ + public static PasswordEnforcementPolicy from(String value) { + if (value == null) { + return DEFAULT; + } + try { + return PasswordEnforcementPolicy.valueOf(value.trim().toUpperCase()); + } catch (IllegalArgumentException e) { + return DEFAULT; + } + } +} diff --git a/src/main/java/com/eactive/apim/portal/config/PortalAuthenticationFailureHandler.java b/src/main/java/com/eactive/apim/portal/config/PortalAuthenticationFailureHandler.java index acf4c30..f2aa349 100644 --- a/src/main/java/com/eactive/apim/portal/config/PortalAuthenticationFailureHandler.java +++ b/src/main/java/com/eactive/apim/portal/config/PortalAuthenticationFailureHandler.java @@ -1,9 +1,12 @@ package com.eactive.apim.portal.config; import com.eactive.apim.portal.apps.login.constants.LoginConstants; +import com.eactive.apim.portal.apps.login.constants.LoginFailureReason; import com.eactive.apim.portal.apps.user.service.PortalUserLogService; import com.eactive.apim.portal.common.exception.UserNotFoundException; +import com.eactive.apim.portal.common.security.LoginLockPolicy; import com.eactive.apim.portal.common.util.HttpRequestUtil; +import com.eactive.apim.portal.common.util.StringMaskingUtil; import com.eactive.apim.portal.common.util.StringRepeatUtil; import com.eactive.apim.portal.portaluser.entity.PortalUser; import com.eactive.apim.portal.portaluser.repository.PortalUserRepository; @@ -13,6 +16,9 @@ import com.eactive.apim.portal.template.service.MessageRecipient; import org.apache.groovy.util.Maps; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.DisabledException; +import org.springframework.security.authentication.LockedException; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.web.authentication.AuthenticationFailureHandler; @@ -42,14 +48,17 @@ public class PortalAuthenticationFailureHandler implements AuthenticationFailure private final PortalUserRepository portalUserRepository; private final PortalUserLogService userLogService; private final MessageHandlerService messageHandlerService; + private final LoginLockPolicy loginLockPolicy; public PortalAuthenticationFailureHandler(PortalUserRepository portalUserRepository, PortalUserLogService userLogService, - MessageHandlerService messageHandlerService) { + MessageHandlerService messageHandlerService, + LoginLockPolicy loginLockPolicy) { this.portalUserRepository = portalUserRepository; this.userLogService = userLogService; this.messageHandlerService = messageHandlerService; + this.loginLockPolicy = loginLockPolicy; } @Override @@ -68,26 +77,28 @@ public class PortalAuthenticationFailureHandler implements AuthenticationFailure PortalUser user = portalUserRepository.findPortalUserByEmailAddr(normalizedUsername) .orElseThrow(() -> new UserNotFoundException(normalizedUsername)); + int lockCount = loginLockPolicy.lockCount(); user.setLoginFailureCount(user.getLoginFailureCount() + 1); - if (user.getLoginFailureCount() >= 5) { + if (user.getLoginFailureCount() >= lockCount) { user.setAccountLockYn("Y"); // 계정 잠금 알림 messageHandlerService.publishEvent( MessageCode.USER_ACCOUNT_LOCKED, MessageRecipient.of(user), - Maps.of("reason", "5회 이상 로그인 실패로 인한 계정 잠금")) ;; + Maps.of("reason", lockCount + "회 이상 로그인 실패로 인한 계정 잠금")); } portalUserRepository.save(user); } catch (UserNotFoundException e) { - logger.error("{} login try {}", username, e.getMessage()); + logger.error("{} login try {}", StringMaskingUtil.maskLoginId(username), e.getMessage()); } } - userLogService.logFailure(username, ip, sessionId); + userLogService.logFailure(username, ip, sessionId, resolveFailureReason(exception), + request.getHeader("User-Agent")); // 로그인 실패 시 세션 정보 로깅 logLoginFailure(request, username, exception); @@ -99,24 +110,45 @@ public class PortalAuthenticationFailureHandler implements AuthenticationFailure response.sendRedirect(contextPath + "/login"); } + /** 인증 예외 타입 → 감사 로그 실패 사유 코드 매핑 */ + private LoginFailureReason resolveFailureReason(AuthenticationException exception) { + if (exception instanceof UsernameNotFoundException) { + return LoginFailureReason.ID_NOT_FOUND; + } + if (exception instanceof BadCredentialsException) { + return LoginFailureReason.PASSWORD_MISMATCH; + } + if (exception instanceof LockedException) { + return LoginFailureReason.ACCOUNT_LOCKED; + } + if (exception instanceof DisabledException) { + return LoginFailureReason.ACCOUNT_DISABLED; + } + if (exception instanceof SessionAuthenticationException) { + return LoginFailureReason.SESSION_AUTH; + } + logger.warn("미분류 로그인 실패 예외 타입: {}", exception.getClass().getName()); + return LoginFailureReason.UNKNOWN; + } + private void logLoginFailure(HttpServletRequest request, String username, AuthenticationException exception) { StringBuilder logMessage = new StringBuilder(); logMessage.append("\n"); logMessage.append(StringRepeatUtil.repeat('=', 80)).append("\n"); logMessage.append("USER LOGIN FAILURE\n"); logMessage.append(StringRepeatUtil.repeat('=', 80)).append("\n"); - logMessage.append("Username: ").append(username).append("\n"); - logMessage.append("Session ID: ").append(request.getSession().getId()).append("\n"); + logMessage.append("Username: ").append(StringMaskingUtil.maskLoginId(username)).append("\n"); + logMessage.append("Session ID: ").append(StringMaskingUtil.maskToken(request.getSession().getId())).append("\n"); logMessage.append("Failed At: ").append(LocalDateTime.now().format(formatter)).append("\n"); logMessage.append("Failure Reason: ").append(exception.getLocalizedMessage()).append("\n"); logMessage.append("\n"); logMessage.append(StringRepeatUtil.repeat('-', 80)).append("\n"); logMessage.append("REQUEST INFORMATION\n"); logMessage.append(StringRepeatUtil.repeat('-', 80)).append("\n"); - logMessage.append("Client IP Address: ").append(HttpRequestUtil.getClientIpAddress(request)).append("\n"); + logMessage.append("Client IP Address: ").append(StringMaskingUtil.maskIpAddress(HttpRequestUtil.getClientIpAddress(request))).append("\n"); logMessage.append("Client Host: ").append(HttpRequestUtil.getClientHost(request)).append("\n"); logMessage.append("Is Proxied: ").append(HttpRequestUtil.isProxied(request)).append("\n"); - logMessage.append("Remote Address (Direct): ").append(request.getRemoteAddr()).append("\n"); + logMessage.append("Remote Address (Direct): ").append(StringMaskingUtil.maskIpAddress(request.getRemoteAddr())).append("\n"); logMessage.append("Remote Host (Direct): ").append(request.getRemoteHost()).append("\n"); logMessage.append("Request Method: ").append(request.getMethod()).append("\n"); logMessage.append("Request URI: ").append(request.getRequestURI()).append("\n"); @@ -133,7 +165,7 @@ public class PortalAuthenticationFailureHandler implements AuthenticationFailure java.util.Enumeration headerValues = request.getHeaders(headerName); while (headerValues.hasMoreElements()) { String headerValue = headerValues.nextElement(); - logMessage.append(String.format(" %-30s : %s\n", headerName, headerValue)); + logMessage.append(String.format(" %-30s : %s\n", headerName, StringMaskingUtil.maskHeaderValue(headerName, headerValue))); } } diff --git a/src/main/java/com/eactive/apim/portal/config/PortalAuthenticationManager.java b/src/main/java/com/eactive/apim/portal/config/PortalAuthenticationManager.java index d8d65b5..f08555d 100644 --- a/src/main/java/com/eactive/apim/portal/config/PortalAuthenticationManager.java +++ b/src/main/java/com/eactive/apim/portal/config/PortalAuthenticationManager.java @@ -2,6 +2,7 @@ package com.eactive.apim.portal.config; import com.eactive.apim.portal.apps.user.service.PortalUserAuthService; +import com.eactive.apim.portal.common.security.LoginLockPolicy; import com.eactive.apim.portal.common.user.PortalAuthenticatedUser; import com.eactive.apim.portal.portalorg.entity.PortalOrgEnums; import com.eactive.apim.portal.portaluser.entity.PortalUserEnums; @@ -28,6 +29,7 @@ public class PortalAuthenticationManager implements AuthenticationManager { private final PortalUserAuthService portalUserAuthService; private final PasswordEncoder passwordEncoder; private final MessageHandlerService messageHandlerService; + private final LoginLockPolicy loginLockPolicy; @Override @Transactional(noRollbackFor = {AuthenticationException.class}) @@ -40,7 +42,7 @@ public class PortalAuthenticationManager implements AuthenticationManager { if (!user.isAccountNonLocked()) { - if (user.getLoginFailureCount() >= 5) { + if (user.getLoginFailureCount() >= loginLockPolicy.lockCount()) { throw new LockedException("계정이 잠겼습니다. 비밀번호 초기화 또는 관리자에게 문의하세요."); } @@ -79,6 +81,21 @@ public class PortalAuthenticationManager implements AuthenticationManager { } if (user.getPortalOrg() != null) { + // 법인은 "정상(ACTIVE)" 상태에서만 로그인 허용 (준비/탈퇴/휴면 차단, 2FA 이전 단계) + PortalOrgEnums.OrgStatus orgStatus = user.getPortalOrg().getOrgStatus(); + if (!PortalOrgEnums.OrgStatus.ACTIVE.equals(orgStatus)) { + log.debug("법인 비정상 상태 로그인 차단 - user : {} / org.orgStatus : {}", user.getUsername(), orgStatus); + if (PortalOrgEnums.OrgStatus.REMOVED.equals(orgStatus)) { + throw new DisabledException("삭제된 법인의 계정입니다. 로그인할 수 없습니다."); + } + if (PortalOrgEnums.OrgStatus.INACTIVE.equals(orgStatus)) { + throw new DisabledException("휴면 상태의 법인입니다. 관리자에게 문의하세요."); + } + // READY 등 승인 전 상태 + throw new DisabledException("로그인할 수 없습니다. 관리자에게 문의하세요. (법인 승인대기중)"); + } + + // 방어적: ACTIVE 인데 승인 미완료인 예외 케이스 차단 if (!user.getPortalOrg().getApprovalStatus().equals(PortalOrgEnums.ApprovalStatus.COMPLETED)) { log.debug("기업사용자 - getApprovalStatus : {} / - org.approvalStatus : {}", user.getApprovalStatus(), user.getPortalOrg().getApprovalStatus()); throw new DisabledException("로그인할 수 없습니다. 관리자에게 문의하세요. (법인 승인대기중)"); diff --git a/src/main/java/com/eactive/apim/portal/config/PortalAuthenticationSuccessHandler.java b/src/main/java/com/eactive/apim/portal/config/PortalAuthenticationSuccessHandler.java index 2935022..977862c 100644 --- a/src/main/java/com/eactive/apim/portal/config/PortalAuthenticationSuccessHandler.java +++ b/src/main/java/com/eactive/apim/portal/config/PortalAuthenticationSuccessHandler.java @@ -1,26 +1,18 @@ package com.eactive.apim.portal.config; -import com.eactive.apim.portal.apps.user.repository.PortalOrgRepository; -import com.eactive.apim.portal.apps.user.service.PortalUserLogService; -import com.eactive.apim.portal.common.util.HttpRequestUtil; -import com.eactive.apim.portal.common.util.StringRepeatUtil; -import com.eactive.apim.portal.invitation.entity.UserInvitation; -import com.eactive.apim.portal.invitation.entity.UserInvitationEnums.InvitationStatus; -import com.eactive.apim.portal.invitation.repository.UserInvitationRepository; -import com.eactive.apim.portal.portalorg.entity.PortalOrg; +import com.eactive.apim.portal.apps.auth.twofactor.TwoFactorProperties; +import com.eactive.apim.portal.apps.auth.twofactor.TwoFactorService; +import com.eactive.apim.portal.apps.login.constants.LoginType; +import com.eactive.apim.portal.apps.login.service.DuplicateLoginService; +import com.eactive.apim.portal.apps.login.service.LoginFinalizer; import com.eactive.apim.portal.portaluser.entity.PortalUser; import com.eactive.apim.portal.portaluser.entity.PortalUserEnums; -import com.eactive.apim.portal.portaluser.entity.UserPasswordHistory; import com.eactive.apim.portal.portaluser.repository.PortalUserRepository; -import com.eactive.apim.portal.portaluser.repository.UserPasswordHistoryRepository; -import com.eactive.apim.portal.template.entity.MessageCode; -import com.eactive.apim.portal.template.entity.MessageRequest; -import com.eactive.apim.portal.template.repository.MessageRequestRepository; import lombok.RequiredArgsConstructor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.security.web.context.HttpSessionSecurityContextRepository; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -29,180 +21,81 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.Optional; - +/** + * 로그인 1차 인증(ID/PW) 성공 핸들러. + * + *

로그인 2FA 가 활성화되어 있고 DORMANT 가 아니면, 후처리를 확정하지 않고 + * 2FA 대기(pending) 상태로 전환한다: 세션에 대기 정보를 저장하고 SecurityContext 를 + * 비워 사용자를 익명으로 되돌린 뒤 {@code /login?twofactor=1} 로 보낸다. 로그인 페이지가 + * 공통 2FA 팝업을 자동 오픈하고, 인증 성공 시 {@code TwoFactorService} 가 최종 확정한다.

+ * + *

2FA off 면 동시 접속(다른 곳 활성 세션) 여부를 확인해, 있으면 확정을 보류하고 + * {@code /login?duplicate=1} 확인 팝업으로 유도한다({@link DuplicateLoginService}). + * 없으면(또는 DORMANT) {@link LoginFinalizer} 로 기존과 동일하게 즉시 확정한다. + * 실질 후처리 로직은 모두 {@link LoginFinalizer} 로 이관되어 로그인/2FA/가입자동로그인이 공유한다.

+ */ @Service @Transactional @RequiredArgsConstructor public class PortalAuthenticationSuccessHandler implements AuthenticationSuccessHandler { - private static final Logger sessionLogger = LoggerFactory.getLogger("eapim.portal.session"); - private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"); - private final PortalUserRepository portalUserRepository; - private final PortalProperties portalProperties; - private final PortalUserLogService userLogService; - private final UserPasswordHistoryRepository passwordHistoryRepository; - private final MessageRequestRepository messageRequestRepository; - private final UserInvitationRepository userInvitationRepository; - private final PortalOrgRepository portalOrgRepository; + private final LoginFinalizer loginFinalizer; + private final TwoFactorService twoFactorService; + private final TwoFactorProperties twoFactorProperties; + private final DuplicateLoginService duplicateLoginService; @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { String username = request.getParameter("id"); - // 이메일 소문자 변환 적용 String normalizedUsername = username != null ? username.toLowerCase() : null; PortalUser user = portalUserRepository.findPortalUserByEmailAddr(normalizedUsername).orElse(null); if (user == null) { response.sendRedirect(request.getContextPath() + "/login?error=true"); return; } - user.setLoginFailureCount(0); - portalUserRepository.save(user); - String ip = request.getRemoteAddr(); - String sessionId = request.getSession().getId(); - userLogService.logSuccess(username, ip, sessionId); + boolean dormant = PortalUserEnums.UserStatus.DORMANT.equals(user.getUserStatus()); - String contextPath = request.getContextPath(); - HttpSession session = request.getSession(); + // 로그인 2FA: ID/PW 는 맞았으므로 실패카운트만 리셋하고, 최종 확정은 2FA 성공까지 보류한다. + // 대상 역할(two-factor.login.target-roles, 기본 법인관리자만)에 한해 적용한다. + if (twoFactorProperties.isLoginEnabled() && !dormant + && twoFactorProperties.isLoginTargetRole(user.getRoleCode())) { + user.setLoginFailureCount(0); + portalUserRepository.save(user); - // 세션에 상태 저장 - if (isEmailVerificationRequired(user)) { - session.setAttribute("success", "이메일 인증이 완료되지 않았습니다. 이메일을 확인하여 인증을 완료해주세요."); - session.setAttribute("emailVerificationRequired", true); - session.setAttribute("redirectUrl", contextPath + "/mypage/verification-email"); - } else if (isDormantAccount(user)) { - session.setAttribute("success", "90일 이상 미접속하여 계정이 잠금 처리되었습니다. 본인인증 후 이용해주세요."); - session.setAttribute("dormantAccount", true); - session.setAttribute("dormantLoginId", username); - session.setAttribute("redirectUrl", contextPath + "/dormant_account"); - } else if (isTemporaryPasswordLogin(user)) { - session.setAttribute("success", "임시 비밀번호로 로그인하셨습니다.
계정 보안을 위해 비밀번호를 변경해 주세요."); - session.setAttribute("passwordExpired", true); - session.setAttribute("redirectUrl", contextPath + "/new_password"); - } else if (isPasswordChangeRequired(user)) { - session.setAttribute("success", "비밀번호를 변경한 지 90일이 경과하였습니다.
계정 보안을 위해 비밀번호를 변경해 주세요."); - session.setAttribute("passwordExpired", true); - session.setAttribute("redirectUrl", contextPath + "/new_password"); + HttpSession session = request.getSession(); + twoFactorService.beginLoginChallenge(session, user); + + // 2FA 완료 전까지 익명 상태로 되돌린다(보호 경로 자동 차단, LoginHandler 튕김 회피). + SecurityContextHolder.clearContext(); + session.removeAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY); + + response.sendRedirect(request.getContextPath() + "/login?twofactor=1"); + return; } - // 초대 코드 확인 - ROLE_USER만 확인 (세션에 저장하여 메인 페이지에서 팝업으로 표시) - if (user.getRoleCode() == PortalUserEnums.RoleCode.ROLE_USER) { - Optional pendingInvitation = - userInvitationRepository.findFirstByInvitationEmailAndStatus( - user.getLoginId(), InvitationStatus.PENDING); + // 2FA off: 동시 접속(다른 곳 활성 세션)이 있으면 확정을 보류하고 확인 팝업으로 유도한다. + // 중복 확인은 비밀번호 검증 통과 후에만 노출한다(사전 체크는 접속 여부/IP 정보 노출). + if (!dormant && duplicateLoginService.hasActiveSession(normalizedUsername)) { + user.setLoginFailureCount(0); + portalUserRepository.save(user); - if (pendingInvitation.isPresent()) { - UserInvitation invitation = pendingInvitation.get(); - if (invitation.getExpiresOn().isAfter(LocalDateTime.now())) { - // 세션에 초대 정보 저장 (메인 페이지에서 팝업으로 표시) - session.setAttribute("pendingInvitation", true); - session.setAttribute("pendingInvitationToken", invitation.getToken()); - // orgId로 기관명 조회 - String orgName = portalOrgRepository.findById(invitation.getOrgId()) - .map(PortalOrg::getOrgName) - .orElse("알 수 없는 기관"); - session.setAttribute("pendingInvitationOrgName", orgName); - } - } + HttpSession session = request.getSession(); + duplicateLoginService.begin(session, user); + + // 확인 완료 전까지 익명 상태로 되돌린다(보호 경로 자동 차단, LoginHandler 튕김 회피). + SecurityContextHolder.clearContext(); + session.removeAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY); + + response.sendRedirect(request.getContextPath() + "/login?duplicate=1"); + return; } - // 로그인 성공 시 세션 정보 로깅 - logLoginSuccess(request, session, username); - - String decisionToken = (String) request.getSession().getAttribute("decisionToken"); - if (decisionToken != null) { - response.sendRedirect(contextPath + "/signup/decision_process"); - } else { - response.sendRedirect(contextPath + "/"); - } + // 2FA off (또는 DORMANT) → 기존과 동일하게 즉시 확정 + String redirect = loginFinalizer.finalizeLogin(user, username, request, LoginType.NORMAL); + response.sendRedirect(redirect); } - - private void logLoginSuccess(HttpServletRequest request, HttpSession session, String username) { - StringBuilder logMessage = new StringBuilder(); - logMessage.append("\n"); - logMessage.append(StringRepeatUtil.repeat('=', 80)).append("\n"); - logMessage.append("USER LOGIN SUCCESS\n"); - logMessage.append(StringRepeatUtil.repeat('=', 80)).append("\n"); - logMessage.append("Username: ").append(username).append("\n"); - logMessage.append("Session ID: ").append(session.getId()).append("\n"); - logMessage.append("Login At: ").append(LocalDateTime.now().format(formatter)).append("\n"); - logMessage.append("\n"); - logMessage.append(StringRepeatUtil.repeat('-', 80)).append("\n"); - logMessage.append("REQUEST INFORMATION\n"); - logMessage.append(StringRepeatUtil.repeat('-', 80)).append("\n"); - logMessage.append("Client IP Address: ").append(HttpRequestUtil.getClientIpAddress(request)).append("\n"); - logMessage.append("Client Host: ").append(HttpRequestUtil.getClientHost(request)).append("\n"); - logMessage.append("Is Proxied: ").append(HttpRequestUtil.isProxied(request)).append("\n"); - logMessage.append("Remote Address (Direct): ").append(request.getRemoteAddr()).append("\n"); - logMessage.append("Remote Host (Direct): ").append(request.getRemoteHost()).append("\n"); - logMessage.append("Request Method: ").append(request.getMethod()).append("\n"); - logMessage.append("Request URI: ").append(request.getRequestURI()).append("\n"); - logMessage.append("Query String: ").append(request.getQueryString()).append("\n"); - - logMessage.append("\n"); - logMessage.append(StringRepeatUtil.repeat('-', 80)).append("\n"); - logMessage.append("REQUEST HEADERS\n"); - logMessage.append(StringRepeatUtil.repeat('-', 80)).append("\n"); - - java.util.Enumeration headerNames = request.getHeaderNames(); - while (headerNames.hasMoreElements()) { - String headerName = headerNames.nextElement(); - java.util.Enumeration headerValues = request.getHeaders(headerName); - while (headerValues.hasMoreElements()) { - String headerValue = headerValues.nextElement(); - logMessage.append(String.format(" %-30s : %s\n", headerName, headerValue)); - } - } - - logMessage.append(StringRepeatUtil.repeat('=', 80)).append("\n"); - - sessionLogger.info(logMessage.toString()); - } - - private boolean isPasswordChangeRequired(PortalUser user) { - // 가장 최근 비밀번호 변경 이력 조회 - Optional latestHistory = passwordHistoryRepository - .findTopByUserIdOrderByChangeDateDesc(user.getId()); - - // 비밀번호 변경 이력이 있는 경우 - if (latestHistory.isPresent()) { - LocalDateTime lastChangeDate = latestHistory.get().getChangeDate(); - return LocalDateTime.now() - .minusDays(portalProperties.getPasswordExpirationDays()) - .isAfter(lastChangeDate); - } - - return LocalDateTime.now() - .minusDays(portalProperties.getPasswordExpirationDays()) - .isAfter(user.getCreatedDate()); - } - - private boolean isTemporaryPasswordLogin(PortalUser user) { - Optional latestResetRequest = messageRequestRepository.findFirstByEmailAndMessageCodeOrderByRequestDateDesc( - user.getLoginId(), MessageCode.USER_PASSWORD_RESET); - - if (latestResetRequest.isPresent()) { - - // 가장 최근 비밀번호 변경 이력 조회 - Optional latestHistory = passwordHistoryRepository - .findTopByUserIdOrderByChangeDateDesc(user.getId()); - return !latestHistory.isPresent() || latestHistory.get().getChangeDate().isBefore(latestResetRequest.get().getRequestDate()); - } - return false; - } - - private boolean isDormantAccount(PortalUser user) { - return PortalUserEnums.UserStatus.DORMANT.equals(user.getUserStatus()); - } - - private boolean isEmailVerificationRequired(PortalUser user) { - return PortalUserEnums.UserStatus.READY.equals(user.getUserStatus()); - } - } diff --git a/src/main/java/com/eactive/apim/portal/config/PortalConfigForwardedHeader.java b/src/main/java/com/eactive/apim/portal/config/PortalConfigForwardedHeader.java new file mode 100644 index 0000000..82e7e72 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/config/PortalConfigForwardedHeader.java @@ -0,0 +1,91 @@ +package com.eactive.apim.portal.config; + +import javax.servlet.DispatcherType; +import javax.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; +import org.springframework.web.filter.ForwardedHeaderFilter; + +/** + * 리버스프록시(OHS) 전달 헤더 처리 필터 등록. + * + *

{@code server.forward-headers-strategy=framework} 일 때만 활성화된다. Spring Boot 오토컨피그 + * ({@code ServletWebServerFactoryAutoConfiguration.ForwardedHeaderFilterConfiguration}) 가 동일 조건에서 + * 기본 {@link ForwardedHeaderFilter} 를 등록하지만, 여기서 직접 등록해 {@code /internal/**} 를 필터 대상에서 + * 제외한다. 오토컨피그는 해당 필터 빈이 이미 있으면 물러난다. + * + *

왜 native 가 아니라 framework 인가

+ * {@code native} 는 내장 Tomcat 의 {@code RemoteIpValve} 에 위임하는 전략이라 WAR 로 WebLogic 에 배포하면 + * 아무 동작도 하지 않는다({@link com.eactive.apim.portal.PortalApplication} 은 {@code SpringBootServletInitializer} + * 상속). {@code framework} 는 평범한 서블릿 필터라 배포 형태와 무관하게 동작한다. + * + *

왜 {@code /internal/**} 를 제외하는가

+ * {@link ForwardedHeaderFilter} 는 (1) {@code getRemoteAddr()} 을 {@code X-Forwarded-For} 값으로 치환하고 + * (2) 처리한 forwarded 헤더를 요청에서 제거한다. 내부 API 는 이 두 값으로 접근을 통제하므로 그대로 두면 가드가 뒤집힌다. + * + *
+ *   공격자: X-Forwarded-For: 127.0.0.1
+ *     → getRemoteAddr()              = "127.0.0.1"  (위조값이 허용 IP 검사를 통과)
+ *     → getHeader("X-Forwarded-For") = null         (필터가 제거 → viaProxy 판정이 false)
+ * 
+ * + * 즉 {@code MenuInternalController#isAllowed} / {@code LegacyEncryptionMigrationController#assertAllowedIp} + * 의 "프록시 경유 거부 + loopback 허용" 가드가 무력화된다. 필터를 제외해 두면 이 경로만은 원 소켓 IP 로 + * 검사되므로 기존 가드가 설계대로 동작한다. + * + *

전제: {@code framework} 는 신뢰 프록시 목록이 없어 헤더를 무조건 신뢰한다. 앞단(OHS)에서 인바운드 + * {@code X-Forwarded-*} / {@code Forwarded} 를 제거한 뒤 재설정해야 하며, WAS 포트로의 직접 접근 경로도 차단해야 한다. + * + * @see com.eactive.apim.portal.djb.menu.MenuInternalController + * @see com.eactive.apim.portal.common.migration.LegacyEncryptionMigrationController + */ +@Slf4j +@Configuration +@ConditionalOnProperty(name = "server.forward-headers-strategy", havingValue = "framework") +public class PortalConfigForwardedHeader { + + /** 프록시 헤더를 신뢰하지 않고 원 소켓 IP 로 판정해야 하는 경로. */ + private static final String INTERNAL_PREFIX = "/internal/"; + + @Bean + public FilterRegistrationBean forwardedHeaderFilterRegistration() { + FilterRegistrationBean registration = + new FilterRegistrationBean<>(new InternalExcludingForwardedHeaderFilter()); + // 다른 필터(XSS, Security 등)가 요청을 읽기 전에 scheme/host/port/remoteAddr 이 교정되어야 한다. + registration.setOrder(Ordered.HIGHEST_PRECEDENCE); + registration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ASYNC, DispatcherType.ERROR); + log.info("ForwardedHeaderFilter 등록 - X-Forwarded-* 반영, 제외 경로: {}**", INTERNAL_PREFIX); + return registration; + } + + /** {@code /internal/**} 요청은 원본 요청 그대로 통과시키는 {@link ForwardedHeaderFilter}. */ + static class InternalExcludingForwardedHeaderFilter extends ForwardedHeaderFilter { + + /** + * {@code super} 구현은 "forwarded 계열 헤더가 하나도 없으면 필터링하지 않는다"이다. + * 그 최적화를 유지해야 프록시를 거치지 않는 환경(로컬·내부 직접 접속)의 동작이 기존과 동일하게 남으므로 + * 내부 경로 판정과 반드시 OR 로 결합한다. + */ + @Override + protected boolean shouldNotFilter(HttpServletRequest request) { + return isInternal(request) || super.shouldNotFilter(request); + } + + private boolean isInternal(HttpServletRequest request) { + String uri = request.getRequestURI(); + if (uri == null) { + return false; + } + // context-path 는 현재 "/" 이지만, 변경되더라도 경로 판정이 어긋나지 않게 접두를 걷어낸다. + String contextPath = request.getContextPath(); + if (contextPath != null && contextPath.length() > 1 && uri.startsWith(contextPath)) { + uri = uri.substring(contextPath.length()); + } + return uri.startsWith(INTERNAL_PREFIX); + } + } +} diff --git a/src/main/java/com/eactive/apim/portal/config/PortalConfigLog.java b/src/main/java/com/eactive/apim/portal/config/PortalConfigLog.java index f79ec29..1bb52bc 100644 --- a/src/main/java/com/eactive/apim/portal/config/PortalConfigLog.java +++ b/src/main/java/com/eactive/apim/portal/config/PortalConfigLog.java @@ -2,6 +2,7 @@ package com.eactive.apim.portal.config; import com.eactive.apim.portal.common.util.HttpRequestUtil; import com.eactive.apim.portal.common.util.SecurityUtil; +import com.eactive.apim.portal.common.util.StringMaskingUtil; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; @@ -32,7 +33,7 @@ public class PortalConfigLog { String query = request.getQueryString(); String ip = HttpRequestUtil.getClientIpAddress(request); - String user = SecurityUtil.getCurrentLoginId(); + String user = StringMaskingUtil.maskEmail(SecurityUtil.getCurrentLoginId()); String message = String.format("Request: %s %s?%s from %s by %s", method, path, query, ip, user); Logger logger = LoggerFactory.getLogger(joinPoint.getTarget().getClass()); diff --git a/src/main/java/com/eactive/apim/portal/config/PortalConfigSecurity.java b/src/main/java/com/eactive/apim/portal/config/PortalConfigSecurity.java index 43bc85b..6402cd1 100644 --- a/src/main/java/com/eactive/apim/portal/config/PortalConfigSecurity.java +++ b/src/main/java/com/eactive/apim/portal/config/PortalConfigSecurity.java @@ -1,6 +1,7 @@ package com.eactive.apim.portal.config; +import com.eactive.apim.portal.apps.session.filter.SessionValidationFilter; import com.navercorp.lucy.security.xss.servletfilter.XssEscapeServletFilter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.web.servlet.FilterRegistrationBean; @@ -10,10 +11,12 @@ import org.springframework.security.config.annotation.method.configuration.Enabl import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.core.session.SessionRegistry; -import org.springframework.security.core.session.SessionRegistryImpl; import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.csrf.CookieCsrfTokenRepository; +import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.security.web.access.AccessDeniedHandlerImpl; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.csrf.CsrfException; +import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository; import org.springframework.security.web.session.HttpSessionEventPublisher; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @@ -33,15 +36,30 @@ public class PortalConfigSecurity { private final PortalLogoutSuccessHandler logoutSuccessHandler; + private final SessionValidationFilter sessionValidationFilter; + @Autowired public PortalConfigSecurity(PortalAuthenticationFailureHandler authenticationFailureHandler, PortalAuthenticationSuccessHandler authenticationSuccessHandler, PortalAuthenticationManager portalAuthenticationManager, - PortalLogoutSuccessHandler logoutSuccessHandler) { + PortalLogoutSuccessHandler logoutSuccessHandler, + SessionValidationFilter sessionValidationFilter) { this.authenticationFailureHandler = authenticationFailureHandler; this.authenticationSuccessHandler = authenticationSuccessHandler; this.portalAuthenticationManager = portalAuthenticationManager; this.logoutSuccessHandler = logoutSuccessHandler; + this.sessionValidationFilter = sessionValidationFilter; + } + + /** + * {@link SessionValidationFilter}가 Spring Security 필터 체인에서만 동작하도록 + * 서블릿 컨테이너의 자동 등록을 비활성화한다. + */ + @Bean + public FilterRegistrationBean sessionValidationFilterRegistration(SessionValidationFilter filter) { + FilterRegistrationBean registration = new FilterRegistrationBean<>(filter); + registration.setEnabled(false); + return registration; } @Bean @@ -64,6 +82,14 @@ public class PortalConfigSecurity { @Bean public SecurityFilterChain filterChain(HttpSecurity http) { try { + // CSRF 토큰을 쿠키(XSRF-TOKEN) 대신 HTTP 세션에 저장한다. + // 운영(prod/eapim/devportal)은 동일 호스트(IP:PORT)에 여러 서비스가 떠 있어 + // 쿠키가 호스트 단위로 공유·과포화되면서 XSRF-TOKEN 쿠키가 누락 → 로그인 403이 발생했다. + // 기존 클라이언트(X-XSRF-TOKEN 헤더, _csrf 파라미터)와 호환되도록 헤더명을 고정한다. + // 세션에 저장되므로 CSRF 토큰 수명은 세션 타임아웃(10분)과 동일하다. + HttpSessionCsrfTokenRepository csrfTokenRepository = new HttpSessionCsrfTokenRepository(); + csrfTokenRepository.setHeaderName("X-XSRF-TOKEN"); + http .authenticationManager(portalAuthenticationManager) .formLogin(form -> form @@ -75,21 +101,28 @@ public class PortalConfigSecurity { .successHandler(authenticationSuccessHandler)) .logout(logout -> logout .logoutRequestMatcher(new AntPathRequestMatcher("/actionLogout.do", "GET")) + // LogoutHandler 는 기본 SecurityContextLogoutHandler(세션 무효화)보다 먼저 실행된다. + // 세션이 살아있는 이 시점에 DB 세션 레코드를 정리해야 중복세션 오탐("이미 접속중")을 막는다. + .addLogoutHandler(logoutSuccessHandler) .logoutSuccessHandler(logoutSuccessHandler)) .csrf(csrf -> csrf - .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) + .csrfTokenRepository(csrfTokenRepository) .ignoringRequestMatchers(new AntPathRequestMatcher("/_proxy/**/*")) + .ignoringRequestMatchers(new AntPathRequestMatcher("/internal/migration/**")) + .ignoringRequestMatchers(new AntPathRequestMatcher("/internal/menu/**")) ) + // 로그인 페이지에 오래 머물러 세션(=CSRF 토큰 저장소)이 타임아웃되면 + // 로그인 제출 시 CsrfFilter가 AnonymousAuthenticationFilter보다 먼저 예외를 던져 + // 익명으로 인식되지 못하고 기본 403("권한없음")으로 떨어진다. + // CSRF 만료는 권한 문제가 아니라 세션 만료이므로 기존 안내(/login?expired=true)로 보낸다. + .exceptionHandling(ex -> ex.accessDeniedHandler(csrfAwareAccessDeniedHandler())) + // 중복로그인/유휴 만료는 DB 기반 SessionValidationFilter가 처리 (maximumSessions 미사용) .sessionManagement(session -> session .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) - .maximumSessions(1) // Allow only one session per user - .maxSessionsPreventsLogin(false) // Prevent new login when session limit is reached - .expiredUrl("/login?expired") // Redirect when session expires - .sessionRegistry(sessionRegistry()) // Session registry bean to track sessions - .and() .sessionFixation() .changeSessionId() - ); + ) + .addFilterBefore(sessionValidationFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } catch (Exception e) { @@ -97,9 +130,21 @@ public class PortalConfigSecurity { } } - @Bean - public SessionRegistry sessionRegistry() { - return new SessionRegistryImpl(); + /** + * CSRF 토큰 누락/불일치는 "권한없음"이 아니라 (대개) 세션 만료다. + * 로그인 페이지에 오래 머물러 세션 내 CSRF 토큰이 사라진 경우가 대표적이므로 + * 기존 세션 만료 안내(login.html의 {@code param.expired})를 재사용해 오해를 막는다. + * 그 외 진짜 권한 위반(@Secured 등)은 기본 핸들러에 위임해 403을 유지한다. + */ + private AccessDeniedHandler csrfAwareAccessDeniedHandler() { + AccessDeniedHandlerImpl delegate = new AccessDeniedHandlerImpl(); + return (request, response, accessDeniedException) -> { + if (accessDeniedException instanceof CsrfException) { + response.sendRedirect(request.getContextPath() + "/login?expired=true"); + return; + } + delegate.handle(request, response, accessDeniedException); + }; } @Bean diff --git a/src/main/java/com/eactive/apim/portal/config/PortalConfigWebDispatcherServlet.java b/src/main/java/com/eactive/apim/portal/config/PortalConfigWebDispatcherServlet.java index 28d33ab..512e90d 100644 --- a/src/main/java/com/eactive/apim/portal/config/PortalConfigWebDispatcherServlet.java +++ b/src/main/java/com/eactive/apim/portal/config/PortalConfigWebDispatcherServlet.java @@ -5,11 +5,15 @@ import com.eactive.apim.portal.common.converter.EnabledStatusConverter; import java.util.List; import java.util.concurrent.TimeUnit; import nz.net.ultraq.thymeleaf.layoutdialect.LayoutDialect; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.ServletComponentScan; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; +import org.springframework.core.env.Environment; +import org.springframework.core.env.Profiles; import org.springframework.data.domain.PageRequest; import org.springframework.data.web.PageableHandlerMethodArgumentResolver; import org.springframework.format.FormatterRegistry; @@ -18,10 +22,15 @@ import org.springframework.web.filter.CharacterEncodingFilter; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.multipart.support.MultipartFilter; import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.config.annotation.ResourceChainRegistration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; import org.springframework.web.servlet.resource.PathResourceResolver; +import org.springframework.web.servlet.resource.ResourceUrlEncodingFilter; +import org.springframework.web.servlet.resource.VersionResourceResolver; import org.springframework.web.servlet.view.json.MappingJackson2JsonView; @@ -32,6 +41,31 @@ public class PortalConfigWebDispatcherServlet implements WebMvcConfigurer { public static final String ERROR = "error"; + private final Environment environment; + private final com.eactive.apim.portal.apps.auth.twofactor.TwoFactorService twoFactorService; + private final com.eactive.apim.portal.apps.auth.twofactor.TwoFactorProperties twoFactorProperties; + private final com.eactive.apim.portal.djb.menu.MenuService menuService; + + // 정적자원 해시 버전닝 토글(application.yml: app.resource-versioning.enabled). + // prod 는 이 값을 무시하고 항상 ON 으로 동작한다(isResourceVersioningEnabled 참고). + @Value("${app.resource-versioning.enabled:true}") + private boolean resourceVersioningEnabled; + + // 정적자원 서버측 캐싱(ResourceChain 캐시) 토글(application.yml: app.resource-caching.enabled). + // prod 는 이 값을 무시하고 항상 ON 으로 동작한다(isResourceCachingEnabled 참고). + // 개발환경에서 OFF 면 sass/JS 변경이 서버 재시작 없이 즉시 반영된다. + @Value("${app.resource-caching.enabled:false}") + private boolean resourceCachingEnabled; + + public PortalConfigWebDispatcherServlet(Environment environment, + com.eactive.apim.portal.apps.auth.twofactor.TwoFactorService twoFactorService, + com.eactive.apim.portal.apps.auth.twofactor.TwoFactorProperties twoFactorProperties, + com.eactive.apim.portal.djb.menu.MenuService menuService) { + this.environment = environment; + this.twoFactorService = twoFactorService; + this.twoFactorProperties = twoFactorProperties; + this.menuService = menuService; + } @Bean @@ -39,6 +73,33 @@ public class PortalConfigWebDispatcherServlet implements WebMvcConfigurer { return new LayoutDialect(); } + /** + * redirect 응답에 전역 {@code @ModelAttribute}(브레드크럼용 pageName·showTestAuthNotice· + * testAuthNumber·sessionTimeoutMinutes 등)가 쿼리스트링으로 노출되는 것을 차단한다. + * + *

Spring Boot 2.6+ 에서 {@code spring.mvc.ignore-default-model-on-redirect} 프로퍼티가 + * 제거됐고, 본 애플리케이션은 {@code @EnableWebMvc} 로 Boot 자동설정을 우회하므로 + * {@link RequestMappingHandlerAdapter} 의 프레임워크 기본값(false)이 적용된다. 그 결과 + * {@code redirect:} 를 반환하는 모든 컨트롤러에서 {@link com.eactive.apim.portal.common.breadcrumb.GlobalControllerAdvice} + * 의 기본 모델 값이 redirect URL 에 append 되어 프론트 전용 파라미터가 주소창에 그대로 드러난다. + * 어댑터 생성 이후 플래그만 true 로 뒤집어(Boot/기존 설정은 그대로 유지) 누수를 막는다.

+ * + *

{@code static} 메서드로 선언해 이 설정 클래스가 조기 초기화되는 것을 피한다. + * 명시적 {@code RedirectAttributes}/flash 속성은 영향받지 않는다(기본 모델만 무시).

+ */ + @Bean + public static BeanPostProcessor ignoreDefaultModelOnRedirectPostProcessor() { + return new BeanPostProcessor() { + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) { + if (bean instanceof RequestMappingHandlerAdapter) { + ((RequestMappingHandlerAdapter) bean).setIgnoreDefaultModelOnRedirect(true); + } + return bean; + } + }; + } + // ------------------------------------------------------------- // RequestMappingHandlerMapping 설정 View Controller 추가 // ------------------------------------------------------------- @@ -54,6 +115,35 @@ public class PortalConfigWebDispatcherServlet implements WebMvcConfigurer { registry.addConverter(enabledStatusConverter()); } + @Override + public void addInterceptors(InterceptorRegistry registry) { + String[] staticExcludes = { + "/css/**", "/js/**", "/img/**", "/images/**", "/webfonts/**", + "/font/**", "/html/**", "/plugins/**", "/favicon.ico", + "/api/**"}; + + // 비밀번호 변경 강제(ENFORCE) 가드. 정적 자원은 제외한다. + registry.addInterceptor(new PasswordChangeEnforcementInterceptor()) + .addPathPatterns("/**") + .excludePathPatterns(staticExcludes); + + // step-up 2FA 가드. 비밀번호 강제 가드 "다음" 순서로 등록(강제 변경 상태가 우선). + // 2FA 엔드포인트 자체(/auth/2fa/**)는 제외해 순환을 막는다. + registry.addInterceptor(new com.eactive.apim.portal.apps.auth.twofactor.StepUpAuthInterceptor( + twoFactorService, twoFactorProperties)) + .addPathPatterns("/**") + .excludePathPatterns(staticExcludes) + .excludePathPatterns("/auth/2fa/**"); + + // 메뉴 접근 권한(ACCESS_ROLES) 가드. 메뉴 경로 정확 일치 시에만 검사하며 + // 하위 경로는 기존 @Secured / PageRoute.role 안전망에 위임한다. + registry.addInterceptor(new com.eactive.apim.portal.djb.menu.MenuAccessInterceptor(menuService)) + .addPathPatterns("/**") + .excludePathPatterns(staticExcludes) + .excludePathPatterns("/internal/**", "/auth/2fa/**", + "/login", "/actionLogin.do", "/actionLogout.do", "/error"); + } + @Bean public EnabledStatusConverter enabledStatusConverter() { return new EnabledStatusConverter(); @@ -61,16 +151,64 @@ public class PortalConfigWebDispatcherServlet implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { + addStaticResourceHandler(registry, "/css/**", "/css/", "classpath:/static/css/"); + addStaticResourceHandler(registry, "/webfonts/**", "/webfonts/", "classpath:/static/webfonts/"); + addStaticResourceHandler(registry, "/font/**", "/font/", "classpath:/static/font/"); + addStaticResourceHandler(registry, "/html/**", "/html/", "classpath:/static/html/"); + addStaticResourceHandler(registry, "/images/**", "/images/", "classpath:/static/images/"); + addStaticResourceHandler(registry, "/img/**", "/img/", "classpath:/static/img/"); + addStaticResourceHandler(registry, "/js/**", "/js/", "classpath:/static/js/"); + addStaticResourceHandler(registry, "/plugins/**", "/plugins/", "classpath:/static/plugins/"); + addStaticResourceHandler(registry, "/favicon.ico", "/favicon.ico", "classpath:/static/favicon.ico"); + } - registry.addResourceHandler("/css/**").addResourceLocations("/css/", "classpath:/static/css/").setCacheControl(CacheControl.maxAge(1, TimeUnit.DAYS)).resourceChain(true).addResolver(new PathResourceResolver()); - registry.addResourceHandler("/webfonts/**").addResourceLocations("/webfonts/", "classpath:/static/webfonts/").setCacheControl(CacheControl.maxAge(1, TimeUnit.DAYS)).resourceChain(true).addResolver(new PathResourceResolver()); - registry.addResourceHandler("/font/**").addResourceLocations("/font/", "classpath:/static/font/").setCacheControl(CacheControl.maxAge(1, TimeUnit.DAYS)).resourceChain(true).addResolver(new PathResourceResolver()); - registry.addResourceHandler("/html/**").addResourceLocations("/html/", "classpath:/static/html/").setCacheControl(CacheControl.maxAge(1, TimeUnit.DAYS)).resourceChain(true).addResolver(new PathResourceResolver()); - registry.addResourceHandler("/images/**").addResourceLocations("/images/", "classpath:/static/images/").setCacheControl(CacheControl.maxAge(1, TimeUnit.DAYS)).resourceChain(true).addResolver(new PathResourceResolver()); - registry.addResourceHandler("/img/**").addResourceLocations("/img/", "classpath:/static/img/").setCacheControl(CacheControl.maxAge(1, TimeUnit.DAYS)).resourceChain(true).addResolver(new PathResourceResolver()); - registry.addResourceHandler("/js/**").addResourceLocations("/js/", "classpath:/static/js/").setCacheControl(CacheControl.maxAge(1, TimeUnit.DAYS)).resourceChain(true).addResolver(new PathResourceResolver()); - registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/", "classpath:/static/plugins/").setCacheControl(CacheControl.maxAge(1, TimeUnit.DAYS)).resourceChain(true).addResolver(new PathResourceResolver()); - registry.addResourceHandler("/favicon.ico").addResourceLocations("/favicon.ico", "classpath:/static/favicon.ico").setCacheControl(CacheControl.maxAge(1, TimeUnit.DAYS)).resourceChain(true).addResolver(new PathResourceResolver()); + /** + * 정적자원 핸들러 공통 등록. + * + *

콘텐츠 해시 버전닝이 켜져 있으면 {@link VersionResourceResolver} 를 체인 앞단에 추가하여 + * {@code /css/main.css} 요청을 내용 해시가 포함된 {@code /css/main-.css} 로 매핑한다. + * 내용이 바뀌면 URL 이 바뀌므로 강제 새로고침 없이 브라우저 캐시가 무효화된다. Thymeleaf 의 + * {@code @{/css/main.css}} 링크는 {@link #resourceUrlEncodingFilter()} 가 해시 URL 로 치환한다.

+ */ + private void addStaticResourceHandler(ResourceHandlerRegistry registry, String pattern, String... locations) { + // 캐싱 ON(prod) 이면 브라우저에 1일 캐시를, OFF(dev/local) 면 no-store 를 내려보낸다. + // no-store 는 브라우저가 아예 저장하지 않으므로 강제 새로고침 없이 sass/JS 변경이 바로 보인다. + CacheControl cacheControl = isResourceCachingEnabled() + ? CacheControl.maxAge(1, TimeUnit.DAYS) + : CacheControl.noStore(); + ResourceChainRegistration chain = registry.addResourceHandler(pattern) + .addResourceLocations(locations) + .setCacheControl(cacheControl) + .resourceChain(isResourceCachingEnabled()); + if (isResourceVersioningEnabled()) { + chain.addResolver(new VersionResourceResolver().addContentVersionStrategy("/**")); + } + chain.addResolver(new PathResourceResolver()); + } + + /** + * 정적자원 해시 버전닝 활성 여부. prod 프로파일은 토글과 무관하게 항상 ON, + * 그 외 프로파일은 {@code app.resource-versioning.enabled} 값을 따른다. + */ + private boolean isResourceVersioningEnabled() { + if (environment.acceptsProfiles(Profiles.of("prod"))) { + return true; + } + return resourceVersioningEnabled; + } + + /** + * 정적자원 서버측 캐싱(ResourceChain 의 CachingResourceResolver) 활성 여부. + * prod 프로파일은 토글과 무관하게 항상 ON, 그 외 프로파일은 {@code app.resource-caching.enabled} 값을 따른다. + * + *

OFF 면 매 요청마다 리소스를 재해석하므로, sass/JS 변경이 서버 재시작 없이 즉시 반영된다. + * ({@code spring.web.resources.cache} 는 브라우저 캐시 헤더만 제어할 뿐 이 서버측 캐시와는 무관하다.)

+ */ + private boolean isResourceCachingEnabled() { + if (environment.acceptsProfiles(Profiles.of("prod"))) { + return true; + } + return resourceCachingEnabled; } @@ -123,4 +261,13 @@ public class PortalConfigWebDispatcherServlet implements WebMvcConfigurer { registrationBean.addUrlPatterns("/*"); return registrationBean; } + + /** + * Thymeleaf {@code @{...}} 링크를 해시 버전 URL 로 치환하기 위한 필터. + * {@code @EnableWebMvc} 환경에서는 자동 등록되지 않으므로 명시적으로 빈 등록한다. + */ + @Bean + public ResourceUrlEncodingFilter resourceUrlEncodingFilter() { + return new ResourceUrlEncodingFilter(); + } } diff --git a/src/main/java/com/eactive/apim/portal/config/PortalLogoutSuccessHandler.java b/src/main/java/com/eactive/apim/portal/config/PortalLogoutSuccessHandler.java index 27214a3..74cd57e 100644 --- a/src/main/java/com/eactive/apim/portal/config/PortalLogoutSuccessHandler.java +++ b/src/main/java/com/eactive/apim/portal/config/PortalLogoutSuccessHandler.java @@ -1,10 +1,13 @@ package com.eactive.apim.portal.config; +import com.eactive.apim.portal.apps.session.service.UserSessionService; import com.eactive.apim.portal.common.util.HttpRequestUtil; +import com.eactive.apim.portal.common.util.StringMaskingUtil; import com.eactive.apim.portal.common.util.StringRepeatUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.logout.LogoutHandler; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; import org.springframework.stereotype.Component; @@ -18,30 +21,47 @@ import java.time.format.DateTimeFormatter; import java.util.Enumeration; /** - * 로그아웃 성공 시 세션 정보를 로깅하는 핸들러 + * 로그아웃 처리 핸들러. + * + *

DB 세션 레코드 정리와 로깅은 {@link LogoutHandler#logout}에서 수행한다. 이 시점은 기본 + * {@code SecurityContextLogoutHandler}가 HTTP 세션을 무효화하기 이라 세션이 유효하다. + * (성공 핸들러 {@link #onLogoutSuccess}는 무효화 에 실행되어 {@code getSession(false)}가 + * null 이 되므로, 거기서 정리하면 DB 행이 남아 재로그인 시 "이미 접속중" 오탐이 발생한다.) */ @Component -public class PortalLogoutSuccessHandler implements LogoutSuccessHandler { +public class PortalLogoutSuccessHandler implements LogoutHandler, LogoutSuccessHandler { private static final Logger sessionLogger = LoggerFactory.getLogger("eapim.portal.session"); private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"); + private final UserSessionService userSessionService; + + public PortalLogoutSuccessHandler(UserSessionService userSessionService) { + this.userSessionService = userSessionService; + } + + /** + * 세션 무효화 전에 실행되어 DB 세션 레코드를 정리하고 로그아웃 정보를 로깅한다. + */ @Override - public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, - Authentication authentication) throws IOException, ServletException { + public void logout(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) { HttpSession session = request.getSession(false); if (session != null) { + // DB 세션 레코드 정리 (중복세션 오탐 방지) + userSessionService.removeSession(session.getId()); + StringBuilder logMessage = new StringBuilder(); logMessage.append("\n"); logMessage.append(StringRepeatUtil.repeat('=', 80)).append("\n"); logMessage.append("USER LOGOUT\n"); logMessage.append(StringRepeatUtil.repeat('=', 80)).append("\n"); - logMessage.append("Session ID: ").append(session.getId()).append("\n"); + logMessage.append("Session ID: ").append(StringMaskingUtil.maskToken(session.getId())).append("\n"); logMessage.append("Logout At: ").append(LocalDateTime.now().format(formatter)).append("\n"); if (authentication != null) { - logMessage.append("Username: ").append(authentication.getName()).append("\n"); + logMessage.append("Username: ").append(StringMaskingUtil.maskLoginId(authentication.getName())).append("\n"); logMessage.append("Authenticated: ").append(authentication.isAuthenticated()).append("\n"); } @@ -49,10 +69,10 @@ public class PortalLogoutSuccessHandler implements LogoutSuccessHandler { logMessage.append(StringRepeatUtil.repeat('-', 80)).append("\n"); logMessage.append("REQUEST INFORMATION\n"); logMessage.append(StringRepeatUtil.repeat('-', 80)).append("\n"); - logMessage.append("Client IP Address: ").append(HttpRequestUtil.getClientIpAddress(request)).append("\n"); + logMessage.append("Client IP Address: ").append(StringMaskingUtil.maskIpAddress(HttpRequestUtil.getClientIpAddress(request))).append("\n"); logMessage.append("Client Host: ").append(HttpRequestUtil.getClientHost(request)).append("\n"); logMessage.append("Is Proxied: ").append(HttpRequestUtil.isProxied(request)).append("\n"); - logMessage.append("Remote Address (Direct): ").append(request.getRemoteAddr()).append("\n"); + logMessage.append("Remote Address (Direct): ").append(StringMaskingUtil.maskIpAddress(request.getRemoteAddr())).append("\n"); logMessage.append("Remote Host (Direct): ").append(request.getRemoteHost()).append("\n"); logMessage.append("Request Method: ").append(request.getMethod()).append("\n"); logMessage.append("Request URI: ").append(request.getRequestURI()).append("\n"); @@ -69,7 +89,7 @@ public class PortalLogoutSuccessHandler implements LogoutSuccessHandler { java.util.Enumeration headerValues = request.getHeaders(headerName); while (headerValues.hasMoreElements()) { String headerValue = headerValues.nextElement(); - logMessage.append(String.format(" %-30s : %s\n", headerName, headerValue)); + logMessage.append(String.format(" %-30s : %s\n", headerName, StringMaskingUtil.maskHeaderValue(headerName, headerValue))); } } @@ -83,7 +103,7 @@ public class PortalLogoutSuccessHandler implements LogoutSuccessHandler { while (attributeNames.hasMoreElements()) { String attrName = attributeNames.nextElement(); Object attrValue = session.getAttribute(attrName); - String valueStr = attrValue != null ? attrValue.toString() : "null"; + String valueStr = StringMaskingUtil.maskAttributeValue(attrName, attrValue != null ? attrValue.toString() : "null"); if (valueStr.length() > 100) { valueStr = valueStr.substring(0, 97) + "..."; } @@ -97,7 +117,14 @@ public class PortalLogoutSuccessHandler implements LogoutSuccessHandler { sessionLogger.info(logMessage.toString()); } + } + /** + * 로그아웃(세션 무효화) 완료 후 메인으로 리다이렉트한다. + */ + @Override + public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) throws IOException, ServletException { response.sendRedirect(request.getContextPath() + "/"); } } diff --git a/src/main/java/com/eactive/apim/portal/config/PortalProperties.java b/src/main/java/com/eactive/apim/portal/config/PortalProperties.java index f1306b5..3de600d 100644 --- a/src/main/java/com/eactive/apim/portal/config/PortalProperties.java +++ b/src/main/java/com/eactive/apim/portal/config/PortalProperties.java @@ -1,13 +1,12 @@ package com.eactive.apim.portal.config; import com.eactive.apim.portal.common.pagerouter.property.PageRoute; -import com.eactive.apim.portal.portaluser.entity.PortalUserEnums.RoleCode; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; +import java.util.ArrayList; import java.util.List; -import java.util.Map; @Data @Component @@ -25,12 +24,22 @@ public class PortalProperties { private String authVirtualCode = ""; - private boolean testAuthNoticeEnabled = false; - - private Map> portalSecurity; - private FileProperties file = new FileProperties(); + private InternalUserProperties internalUser = new InternalUserProperties(); + + /** + * 내부 사용자(운영자) 판별 설정 + */ + @Data + public static class InternalUserProperties { + /** + * 내부 사용자로 분류할 이메일 도메인 목록. + * "@" 접두사는 있어도 되고 없어도 되며, 대소문자를 구분하지 않는다. + */ + private List emailDomains = new ArrayList<>(); + } + /** * 파일 업로드 관련 설정 */ diff --git a/src/main/java/com/eactive/apim/portal/config/PortalPropertyDuplicateChecker.java b/src/main/java/com/eactive/apim/portal/config/PortalPropertyDuplicateChecker.java new file mode 100644 index 0000000..0e3a7ae --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/config/PortalPropertyDuplicateChecker.java @@ -0,0 +1,55 @@ +package com.eactive.apim.portal.config; + +import com.eactive.apim.portal.portalproperty.repository.PortalPropertyRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +/** + * 기동 시 PTL_PROPERTY 무결성 점검. + * + *

PTL_PROPERTY 는 (PROPERTY_GROUP_NAME, PROPERTY_NAME) 조합이 유일해야 하지만, 운영 정책상 + * DB에 유니크 제약을 걸지 않는다. 대신 부팅 완료 시점에 중복 키를 스캔하여 존재하면 ERROR 로그로 + * 남긴다. (Spring Data 리포지토리는 인터페이스라 초기화 훅이 없어, 리포지토리를 사용하는 startup + * 리스너로 점검한다.)

+ * + *

중복은 {@code getOrCreateProperty} 최초 조회가 동시 요청으로 경합할 때 각각 INSERT되며 생길 수 + * 있다. 제약이 없으므로 중복 행은 자동 제거되지 않는다 — 로그를 보고 수동 정리해야 한다.

+ */ +@Slf4j +@Component +@RequiredArgsConstructor +public class PortalPropertyDuplicateChecker implements ApplicationListener { + + private final PortalPropertyRepository portalPropertyRepository; + + @Override + @Transactional(readOnly = true) + public void onApplicationEvent(ApplicationReadyEvent event) { + List duplicates; + try { + duplicates = portalPropertyRepository.findDuplicatePropertyKeys(); + } catch (Exception e) { + log.error("[PortalProperty 무결성] 중복 점검 쿼리 실패", e); + return; + } + + if (duplicates == null || duplicates.isEmpty()) { + log.info("[PortalProperty 무결성] (PROPERTY_GROUP_NAME, PROPERTY_NAME) 중복 없음"); + return; + } + + // 1) (group, name) 은 유일해야 한다 + // 2) 중복된 값 목록을 남긴다 + log.error("[PortalProperty 무결성] (PROPERTY_GROUP_NAME, PROPERTY_NAME) 조합은 유일해야 합니다. " + + "DB 유니크 제약이 없어 중복 {}건이 방치되어 있습니다. 수동 정리 필요:", duplicates.size()); + for (Object[] row : duplicates) { + log.error(" - PROPERTY_GROUP_NAME='{}', PROPERTY_NAME='{}', count={}", row[0], row[1], row[2]); + } + } +} diff --git a/src/main/java/com/eactive/apim/portal/config/SessionLoggingListener.java b/src/main/java/com/eactive/apim/portal/config/SessionLoggingListener.java index 91154df..fe80883 100644 --- a/src/main/java/com/eactive/apim/portal/config/SessionLoggingListener.java +++ b/src/main/java/com/eactive/apim/portal/config/SessionLoggingListener.java @@ -1,6 +1,7 @@ package com.eactive.apim.portal.config; import com.eactive.apim.portal.common.util.HttpRequestUtil; +import com.eactive.apim.portal.common.util.StringMaskingUtil; import com.eactive.apim.portal.common.util.StringRepeatUtil; import lombok.extern.slf4j.Slf4j; import org.slf4j.Logger; @@ -48,7 +49,7 @@ public class SessionLoggingListener implements HttpSessionListener { logMessage.append(StringRepeatUtil.repeat('=', 80)).append("\n"); logMessage.append("NEW HTTP SESSION CREATED\n"); logMessage.append(StringRepeatUtil.repeat('=', 80)).append("\n"); - logMessage.append("Session ID: ").append(session.getId()).append("\n"); + logMessage.append("Session ID: ").append(StringMaskingUtil.maskToken(session.getId())).append("\n"); logMessage.append("Created At: ").append(LocalDateTime.now().format(formatter)).append("\n"); logMessage.append("Max Inactive Interval: ").append(session.getMaxInactiveInterval()).append(" seconds\n"); @@ -57,10 +58,10 @@ public class SessionLoggingListener implements HttpSessionListener { logMessage.append(StringRepeatUtil.repeat('-', 80)).append("\n"); logMessage.append("REQUEST INFORMATION\n"); logMessage.append(StringRepeatUtil.repeat('-', 80)).append("\n"); - logMessage.append("Client IP Address: ").append(HttpRequestUtil.getClientIpAddress(request)).append("\n"); + logMessage.append("Client IP Address: ").append(StringMaskingUtil.maskIpAddress(HttpRequestUtil.getClientIpAddress(request))).append("\n"); logMessage.append("Client Host: ").append(HttpRequestUtil.getClientHost(request)).append("\n"); logMessage.append("Is Proxied: ").append(HttpRequestUtil.isProxied(request)).append("\n"); - logMessage.append("Remote Address (Direct): ").append(request.getRemoteAddr()).append("\n"); + logMessage.append("Remote Address (Direct): ").append(StringMaskingUtil.maskIpAddress(request.getRemoteAddr())).append("\n"); logMessage.append("Remote Host (Direct): ").append(request.getRemoteHost()).append("\n"); logMessage.append("Remote Port: ").append(request.getRemotePort()).append("\n"); logMessage.append("Request Method: ").append(request.getMethod()).append("\n"); @@ -84,7 +85,7 @@ public class SessionLoggingListener implements HttpSessionListener { Enumeration headerValues = request.getHeaders(headerName); while (headerValues.hasMoreElements()) { String headerValue = headerValues.nextElement(); - logMessage.append(String.format(" %-30s : %s\n", headerName, headerValue)); + logMessage.append(String.format(" %-30s : %s\n", headerName, StringMaskingUtil.maskHeaderValue(headerName, headerValue))); } } } else { @@ -106,7 +107,7 @@ public class SessionLoggingListener implements HttpSessionListener { logMessage.append(StringRepeatUtil.repeat('=', 80)).append("\n"); logMessage.append("HTTP SESSION DESTROYED\n"); logMessage.append(StringRepeatUtil.repeat('=', 80)).append("\n"); - logMessage.append("Session ID: ").append(session.getId()).append("\n"); + logMessage.append("Session ID: ").append(StringMaskingUtil.maskToken(session.getId())).append("\n"); logMessage.append("Destroyed At: ").append(LocalDateTime.now().format(formatter)).append("\n"); logMessage.append("Max Inactive Interval: ").append(session.getMaxInactiveInterval()).append(" seconds\n"); @@ -121,7 +122,8 @@ public class SessionLoggingListener implements HttpSessionListener { while (attributeNames.hasMoreElements()) { String attrName = attributeNames.nextElement(); Object attrValue = session.getAttribute(attrName); - logMessage.append(String.format(" %-30s : %s\n", attrName, attrValue)); + logMessage.append(String.format(" %-30s : %s\n", attrName, + StringMaskingUtil.maskAttributeValue(attrName, attrValue != null ? attrValue.toString() : "null"))); } } catch (Exception e) { logMessage.append(" Unable to retrieve session attributes\n"); diff --git a/src/main/java/com/eactive/apim/portal/config/StartupInfoPrinter.java b/src/main/java/com/eactive/apim/portal/config/StartupInfoPrinter.java new file mode 100644 index 0000000..7a2bc65 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/config/StartupInfoPrinter.java @@ -0,0 +1,51 @@ +package com.eactive.apim.portal.config; + +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.boot.system.ApplicationPid; +import org.springframework.context.ApplicationListener; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Component; + +/** + * Prints a startup banner to stdout once boot is complete. + * + *

Uses {@link System#out} directly (not a logger) so the info is shown even when + * the logback console appender is disabled (CONSOLE_LOG_ENABLED=false). + */ +@Component +public class StartupInfoPrinter implements ApplicationListener { + + @Override + public void onApplicationEvent(ApplicationReadyEvent event) { + Environment env = event.getApplicationContext().getEnvironment(); + + String port = env.getProperty("server.port", "8080"); + String contextPath = env.getProperty("server.servlet.context-path", "/"); + if (!contextPath.startsWith("/")) { + contextPath = "/" + contextPath; + } + String url = "http://localhost:" + port + contextPath; + + String[] active = env.getActiveProfiles(); + String profile = active.length == 0 + ? String.join(", ", env.getDefaultProfiles()) + : String.join(", ", active); + + String instanceName = env.getProperty("inst.Name", System.getProperty("inst.Name", "unknown")); + String pid = new ApplicationPid().toString(); + + StringBuilder sb = new StringBuilder(); + sb.append(System.lineSeparator()); + sb.append("=====================================================").append(System.lineSeparator()); + sb.append(" EAPIM Portal - startup complete").append(System.lineSeparator()); + sb.append("=====================================================").append(System.lineSeparator()); + sb.append(" URL : ").append(url).append(System.lineSeparator()); + sb.append(" Spring profile : ").append(profile).append(System.lineSeparator()); + sb.append(" Instance name : ").append(instanceName).append(System.lineSeparator()); + sb.append(" PID : ").append(pid).append(System.lineSeparator()); + sb.append("=====================================================").append(System.lineSeparator()); + + System.out.println(sb); + System.out.flush(); + } +} diff --git a/src/main/java/com/eactive/apim/portal/config/UserTypeConfigurer.java b/src/main/java/com/eactive/apim/portal/config/UserTypeConfigurer.java new file mode 100644 index 0000000..e16076b --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/config/UserTypeConfigurer.java @@ -0,0 +1,26 @@ +package com.eactive.apim.portal.config; + +import com.eactive.apim.portal.common.util.UserTypeUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.PostConstruct; + +/** + * application.yml 의 {@code portal.internal-user.email-domains} 값을 + * 정적 유틸리티인 {@link UserTypeUtil} 에 주입한다. + */ +@Slf4j +@Configuration +@RequiredArgsConstructor +public class UserTypeConfigurer { + + private final PortalProperties portalProperties; + + @PostConstruct + public void init() { + UserTypeUtil.setInternalEmailDomains(portalProperties.getInternalUser().getEmailDomains()); + log.info("내부 사용자 이메일 도메인: {}", UserTypeUtil.getInternalEmailDomains()); + } +} diff --git a/src/main/java/com/eactive/apim/portal/custom/config/DjbPasswordEncoder.java b/src/main/java/com/eactive/apim/portal/custom/config/DjbPasswordEncoder.java index 0b966ac..239995f 100644 --- a/src/main/java/com/eactive/apim/portal/custom/config/DjbPasswordEncoder.java +++ b/src/main/java/com/eactive/apim/portal/custom/config/DjbPasswordEncoder.java @@ -1,6 +1,5 @@ package com.eactive.apim.portal.custom.config; -//import com.eactive.ext.djb.safedb.DjbSafedbWrapper; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; @@ -17,8 +16,5 @@ public class DjbPasswordEncoder implements PasswordEncoder { @Override public boolean matches(CharSequence rawPassword, String encodedPassword) { return bcryptEncoder.matches(rawPassword, encodedPassword); -// DjbSafedbWrapper safedb = DjbSafedbWrapper.getInstance(); -// String bcryptHash = safedb.decryptNotRnno(encodedPassword); -// return bcryptEncoder.matches(rawPassword, bcryptHash); } } diff --git a/src/main/java/com/eactive/apim/portal/djb/apistatus/controller/ApiStatusController.java b/src/main/java/com/eactive/apim/portal/djb/apistatus/controller/ApiStatusController.java new file mode 100644 index 0000000..2dcaa50 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/apistatus/controller/ApiStatusController.java @@ -0,0 +1,211 @@ +package com.eactive.apim.portal.djb.apistatus.controller; + +import com.eactive.apim.portal.common.util.SecurityUtil; +import com.eactive.apim.portal.djb.apistatus.dto.ActiveIncidentDTO; +import com.eactive.apim.portal.djb.apistatus.dto.ApiCurrentStatusDTO; +import com.eactive.apim.portal.djb.apistatus.dto.ApiOptionDTO; +import com.eactive.apim.portal.djb.apistatus.dto.DailyStatDTO; +import com.eactive.apim.portal.djb.apistatus.dto.IssueDateEntryDTO; +import com.eactive.apim.portal.djb.apistatus.dto.MaintenanceCardDTO; +import com.eactive.apim.portal.djb.apistatus.dto.MyApiStatusDTO; +import com.eactive.apim.portal.djb.apistatus.dto.PastIssueCardDTO; +import com.eactive.apim.portal.djb.apistatus.service.ApiCurrentStatusService; +import com.eactive.apim.portal.djb.apistatus.service.ApiStatusCatalogService; +import com.eactive.apim.portal.djb.apistatus.service.ApiStatusIssueHistoryService; +import com.eactive.apim.portal.djb.apistatus.service.ApiStatusQueryService; +import com.eactive.apim.portal.djb.apistatus.service.ApiStatusSupport; +import com.eactive.apim.portal.djb.apistatus.service.ApiStatusUptimeService; +import com.eactive.apim.portal.djb.apistatus.service.MyApiStatusQueryService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.servlet.ModelAndView; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.List; + +/** + * 개발자포탈 API Status 화면. + * + *

장애/점검 데이터는 관리자 공지사항(PTL_NOTICE + DJB_APISTATUS_INCIDENT)과 + * 자동 탐지(eapim-admin ApiStatusDetectionService)가 채운다. 본 컨트롤러는 읽기 전용이다.

+ * + *

비로그인 사용자도 모두 조회 가능하며, "내 API 현황"만 로그인을 요구한다.

+ */ +@Slf4j +@Controller +@RequestMapping("/apistatus") +@RequiredArgsConstructor +public class ApiStatusController { + + private static final int DEFAULT_RECENT_ISSUE_SIZE = 5; + private static final int MAX_PAGE_SIZE = 50; + /** 현재 상태 일괄 조회 시 한 번에 물어볼 수 있는 API 수 */ + private static final int MAX_STATUS_BATCH = 100; + + private final ApiStatusQueryService apiStatusQueryService; + private final ApiStatusUptimeService uptimeService; + private final ApiStatusIssueHistoryService issueHistoryService; + private final MyApiStatusQueryService myApiStatusQueryService; + private final ApiStatusCatalogService catalogService; + private final ApiCurrentStatusService apiCurrentStatusService; + + /** P1 - API Status 메인 */ + @GetMapping + public ModelAndView index() { + ModelAndView mav = new ModelAndView("djb/apistatus/index"); + mav.addObject("windowDays", catalogService.getWindowDays()); + mav.addObject("authenticated", SecurityUtil.isAuthenticated()); + LocalDateTime lastFireAt = catalogService.getLastMonitorFireAt(); + mav.addObject("lastFireAt", lastFireAt); + mav.addObject("lastFireRelative", + ApiStatusSupport.relativeTime(lastFireAt, ApiStatusSupport.now())); + mav.addObject("lastStatusChangedAt", catalogService.getLastStatusChangedAt()); + return mav; + } + + /** + * P8 - 전체 이슈 이력. 날짜를 지정하지 않으면 조회 기간(90일) 전체를 본다. + * 날짜 선택 가능 범위는 서버 기준 일자로 내려 클라이언트 timezone 차이를 없앤다. + */ + @GetMapping("/issues") + public ModelAndView issues( + @RequestParam(value = "date", required = false) + @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date, + @RequestParam(value = "apiId", required = false) String apiId, + @RequestParam(value = "kind", required = false) String kind) { + + LocalDate today = ApiStatusSupport.now().toLocalDate(); + int windowDays = catalogService.getWindowDays(); + + ModelAndView mav = new ModelAndView("djb/apistatus/issues"); + mav.addObject("windowDays", windowDays); + mav.addObject("today", today); + mav.addObject("minDate", today.minusDays(windowDays - 1L)); + mav.addObject("selectedDate", date); + mav.addObject("selectedApiId", apiId); + mav.addObject("selectedKind", kind); + return mav; + } + + /** 이슈 이력 API 필터 - 현재 사용자가 조회 가능한 API 목록 */ + @GetMapping("/apis.json") + @ResponseBody + public List selectableApis() { + return catalogService.getSelectableApis(); + } + + /** P2 - 90일 가동률 */ + @GetMapping("/uptime.json") + @ResponseBody + public List uptime( + @RequestParam(value = "days", defaultValue = "0") int days) { + return uptimeService.getDailyStats(days > 0 ? days : catalogService.getWindowDays()); + } + + /** P3 - 진행 중 장애 */ + @GetMapping("/active.json") + @ResponseBody + public List active() { + return apiStatusQueryService.getActiveIncidents(); + } + + /** P4 - 내 API 현황 (로그인 필수) */ + @GetMapping("/my-apis.json") + @ResponseBody + public ResponseEntity> myApis() { + if (!SecurityUtil.isAuthenticated()) { + return new ResponseEntity<>(Collections.emptyList(), HttpStatus.UNAUTHORIZED); + } + return ResponseEntity.ok(myApiStatusQueryService.getMyApiStatuses()); + } + + /** P5 - 예정/진행 중 점검 */ + @GetMapping("/maintenance.json") + @ResponseBody + public List maintenance() { + return apiStatusQueryService.getOngoingMaintenance(); + } + + /** P6 - 지난 이슈 (종결) */ + @GetMapping("/recent-issues.json") + @ResponseBody + public List recentIssues( + @RequestParam(value = "size", defaultValue = "" + DEFAULT_RECENT_ISSUE_SIZE) int size) { + return apiStatusQueryService.getRecentClosedIssues(size); + } + + /** P7 - 이슈 상세 */ + @GetMapping("/incident/{incidentId}") + @ResponseBody + public ResponseEntity incidentDetail(@PathVariable Long incidentId) { + return apiStatusQueryService.getIssueDetail(incidentId) + .map(ResponseEntity::ok) + .orElseGet(() -> ResponseEntity.notFound().build()); + } + + /** P9 - 90일 이슈 일자 인덱스 */ + @GetMapping("/issues/dates.json") + @ResponseBody + public List issueDates( + @RequestParam(value = "days", defaultValue = "0") int days, + @RequestParam(value = "apiId", required = false) String apiId, + @RequestParam(value = "kind", required = false) String kind) { + return issueHistoryService.getIssueDates(days > 0 ? days : catalogService.getWindowDays(), apiId, kind); + } + + /** P10 - 이슈 목록 (날짜/API/유형 필터) */ + @GetMapping("/issues/list.json") + @ResponseBody + public Page issueList( + @RequestParam(value = "date", required = false) + @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date, + @RequestParam(value = "apiId", required = false) String apiId, + @RequestParam(value = "kind", required = false) String kind, + @RequestParam(value = "page", defaultValue = "0") int page, + @RequestParam(value = "size", defaultValue = "20") int size) { + + int safePage = Math.max(page, 0); + int safeSize = size <= 0 ? 20 : Math.min(size, MAX_PAGE_SIZE); + return issueHistoryService.getIssues(date, apiId, kind, PageRequest.of(safePage, safeSize)); + } + + /** + * P11 - API 한 건의 현재 상태 (정상/점검/지연/장애). API 상세 화면이 사용한다. + * + *

게시된 장애·점검만 반영하므로 비로그인도 조회 가능하다.

+ */ + @GetMapping("/current.json") + @ResponseBody + public ResponseEntity currentStatus(@RequestParam("apiId") String apiId) { + return apiCurrentStatusService.getStatus(apiId) + .map(ResponseEntity::ok) + .orElseGet(() -> ResponseEntity.badRequest().build()); + } + + /** + * P11 - 여러 API 의 현재 상태. {@code ?apiId=A&apiId=B} 로 반복 지정한다. + */ + @GetMapping("/current-list.json") + @ResponseBody + public ResponseEntity> currentStatuses( + @RequestParam(value = "apiId", required = false) List apiIds) { + + if (apiIds != null && apiIds.size() > MAX_STATUS_BATCH) { + return ResponseEntity.badRequest().build(); + } + return ResponseEntity.ok(apiCurrentStatusService.getStatuses(apiIds)); + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/ActiveIncidentDTO.java b/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/ActiveIncidentDTO.java new file mode 100644 index 0000000..12a8c74 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/ActiveIncidentDTO.java @@ -0,0 +1,34 @@ +package com.eactive.apim.portal.djb.apistatus.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +/** + * 진행 중 장애 카드 (P3) + */ +@Data +public class ActiveIncidentDTO { + + private Long incidentId; + private String noticeId; + /** 연결된 공지 제목. 게시 중인 공지가 없으면 null */ + private String noticeSubject; + /** 연결된 공지 본문(HTML). 게시 중인 공지가 없으면 null */ + private String noticeDetail; + private String kind; + private String state; + private String stateLabel; + private String title; + private String summary; + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime startedAt; + private long elapsedMinutes; + private List apis = new ArrayList<>(); + /** 개발자포탈에 게시되지 않아 개별 노출하지 않는 GW 인터페이스 건수 */ + private int hiddenApiCount; + private List recentTimeline = new ArrayList<>(); +} diff --git a/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/AffectedApiDTO.java b/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/AffectedApiDTO.java new file mode 100644 index 0000000..65f4ff2 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/AffectedApiDTO.java @@ -0,0 +1,22 @@ +package com.eactive.apim.portal.djb.apistatus.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +/** + * 이슈 영향 API + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class AffectedApiDTO { + + private String apiId; + private String apiName; + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime recoveredAt; +} diff --git a/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/ApiCurrentStatusDTO.java b/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/ApiCurrentStatusDTO.java new file mode 100644 index 0000000..1036d59 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/ApiCurrentStatusDTO.java @@ -0,0 +1,53 @@ +package com.eactive.apim.portal.djb.apistatus.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * API 한 건의 현재 상태 (정상/점검/지연/장애). + * + *

게시된 장애·점검 공지(PTL_NOTICE + DJB_APISTATUS_INCIDENT)만 근거로 한다. + * 미게시(초안/USE_YN='N') 건은 다른 API Status 화면과 동일하게 보이지 않는다.

+ */ +@Data +public class ApiCurrentStatusDTO { + + private String apiId; + + /** 공개 API 목록(PTL_API_SPEC_INFO, DISPLAY_YN='Y')에 없으면 null */ + private String apiName; + + /** NORMAL / DEGRADED / OUTAGE / MAINTENANCE */ + private String currentStatus; + + /** 정상 / 지연 / 장애 / 점검 */ + private String currentStatusLabel; + + /** 현재 상태의 근거가 된 이슈. 정상이면 null */ + private Long activeIncidentId; + + /** INCIDENT / MAINTENANCE */ + private String activeIncidentKind; + + private String activeIncidentTitle; + + /** 현재 상태가 시작된 시각 (근거 이슈의 시작 시각) */ + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime statusSince; + + /** 점검 종료 예정 시각. 장애이거나 미정이면 null */ + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime expectedEndAt; + + /** 조회 기간 내 마지막 장애 발생 시각 */ + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime lastIncidentAt; + + /** 조회 기간 가동률 (0.0 ~ 1.0). 상태만 계산하는 경로에서는 채우지 않는다 */ + private Double uptimeRatio; + + /** 가동률·최근 장애 집계 기간(일) */ + private int windowDays; +} diff --git a/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/ApiOptionDTO.java b/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/ApiOptionDTO.java new file mode 100644 index 0000000..7dd9911 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/ApiOptionDTO.java @@ -0,0 +1,20 @@ +package com.eactive.apim.portal.djb.apistatus.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 이슈 이력 API 필터 옵션. 화면에는 API 명만 노출하고 apiId 는 조회 파라미터로만 쓴다. + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ApiOptionDTO { + + private String apiId; + private String apiName; + + /** 소속 API 그룹명 (오픈 API 목록과 동일 기준) */ + private String groupName; +} diff --git a/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/DailyStatDTO.java b/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/DailyStatDTO.java new file mode 100644 index 0000000..e78790b --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/DailyStatDTO.java @@ -0,0 +1,50 @@ +package com.eactive.apim.portal.djb.apistatus.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + +/** + * 90일 가동률 일별 집계 (P2) + */ +@Data +public class DailyStatDTO { + + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd") + private LocalDate statDate; + + /** 0.0000 ~ 1.0000 */ + private double uptimeRatio; + + /** 장애 + 지연 합계 (가동률 차감분). 지연도 서비스 저하이므로 계속 차감한다 */ + private long incidentMinutes; + + /** 그 중 지연(자동 탐지) 분 */ + private long delayMinutes; + + private long maintenanceMinutes; + + /** 막대 색상 구분: NORMAL / DEGRADED / OUTAGE / MAINTENANCE */ + private String status; + + private List issues = new ArrayList<>(); + + @Data + public static class IssueRefDTO { + private Long incidentId; + private String kind; + private String title; + + public IssueRefDTO() { + } + + public IssueRefDTO(Long incidentId, String kind, String title) { + this.incidentId = incidentId; + this.kind = kind; + this.title = title; + } + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/IssueDateEntryDTO.java b/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/IssueDateEntryDTO.java new file mode 100644 index 0000000..bf584f6 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/IssueDateEntryDTO.java @@ -0,0 +1,29 @@ +package com.eactive.apim.portal.djb.apistatus.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + +/** + * 이슈 이력 페이지의 90일 인덱스바 1칸 (P9) + */ +@Data +public class IssueDateEntryDTO { + + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd") + private LocalDate date; + + /** INCIDENT / DELAY / MAINTENANCE - 유형 필터와 같은 값 */ + private List kinds = new ArrayList<>(); + + /** 장애 건수 (자동 탐지 지연 제외) */ + private int incCount; + + /** 지연 건수 (자동 탐지 지연 - INCIDENT 중 INTERFACE_ID 가 DELAY_START: 인 건) */ + private int dlyCount; + + private int mntCount; +} diff --git a/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/MaintenanceCardDTO.java b/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/MaintenanceCardDTO.java new file mode 100644 index 0000000..6654664 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/MaintenanceCardDTO.java @@ -0,0 +1,36 @@ +package com.eactive.apim.portal.djb.apistatus.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +/** + * 점검 사항 카드 (P5). 상태 관리를 하지 않으므로(ADR-F15) 진행 단계는 화면이 현재 시각과 비교해 부여한다. + */ +@Data +public class MaintenanceCardDTO { + + private Long incidentId; + private String noticeId; + /** 연결된 공지 제목. 게시 중인 공지가 없으면 null */ + private String noticeSubject; + /** 연결된 공지 본문(HTML). 게시 중인 공지가 없으면 null */ + private String noticeDetail; + private String title; + private String summary; + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime startedAt; + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime endAt; + private Long durationMinutes; + private List impactedApis = new ArrayList<>(); + /** 개발자포탈에 게시되지 않아 개별 노출하지 않는 GW 인터페이스 건수 */ + private int hiddenApiCount; + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime registeredAt; + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime lastModifiedAt; +} diff --git a/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/MyApiStatusDTO.java b/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/MyApiStatusDTO.java new file mode 100644 index 0000000..bae9148 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/MyApiStatusDTO.java @@ -0,0 +1,25 @@ +package com.eactive.apim.portal.djb.apistatus.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 내가 이용 중인 API 의 현재 상태 (P4) + */ +@Data +public class MyApiStatusDTO { + + private String apiId; + private String apiName; + + /** NORMAL / DEGRADED / OUTAGE / MAINTENANCE */ + private String currentStatus; + + private String currentStatusLabel; + private Long activeIncidentId; + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime lastIncidentAt; + private double uptime90d; +} diff --git a/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/PastIssueCardDTO.java b/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/PastIssueCardDTO.java new file mode 100644 index 0000000..c966b8e --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/PastIssueCardDTO.java @@ -0,0 +1,39 @@ +package com.eactive.apim.portal.djb.apistatus.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +/** + * 지난 이슈 카드 (P6, P10). 장애는 타임라인 전체, 점검은 요약만 담는다. + */ +@Data +public class PastIssueCardDTO { + + private Long incidentId; + private String noticeId; + /** 연결된 공지 제목. 게시 중인 공지가 없으면 null */ + private String noticeSubject; + /** 연결된 공지 본문(HTML). 게시 중인 공지가 없으면 null */ + private String noticeDetail; + private String kind; + private String state; + private String stateLabel; + private String title; + private String summary; + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime startedAt; + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime endAt; + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd") + private LocalDate dateGroup; + private Long durationMinutes; + private List impactedApis = new ArrayList<>(); + /** 개발자포탈에 게시되지 않아 개별 노출하지 않는 GW 인터페이스 건수 */ + private int hiddenApiCount; + private List timeline = new ArrayList<>(); +} diff --git a/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/TimelineEntryDTO.java b/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/TimelineEntryDTO.java new file mode 100644 index 0000000..f48d19d --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/apistatus/dto/TimelineEntryDTO.java @@ -0,0 +1,21 @@ +package com.eactive.apim.portal.djb.apistatus.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 장애 처리 타임라인 1건 + */ +@Data +public class TimelineEntryDTO { + + private Long timelineId; + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime eventAt; + private String stateAfter; + private String labelKo; + private String body; + private String authorType; +} diff --git a/src/main/java/com/eactive/apim/portal/djb/apistatus/repository/ApiStatusIncidentQueryRepository.java b/src/main/java/com/eactive/apim/portal/djb/apistatus/repository/ApiStatusIncidentQueryRepository.java new file mode 100644 index 0000000..bc47090 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/apistatus/repository/ApiStatusIncidentQueryRepository.java @@ -0,0 +1,118 @@ +package com.eactive.apim.portal.djb.apistatus.repository; + +import com.eactive.apim.portal.djb.apistatus.incident.entity.DjbApistatusIncident; +import com.eactive.apim.portal.djb.apistatus.incident.entity.IncidentKind; +import com.eactive.apim.portal.djb.apistatus.incident.entity.IncidentState; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.Repository; +import org.springframework.data.repository.query.Param; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +/** + * 개발자포탈 API Status 화면 전용 조회. 읽기 전용이며 공개 조건을 항상 적용한다. + * + *

공개 조건({@link #VISIBLE})은 두 갈래다. + *

    + *
  • 공지가 붙은 이슈(장애·점검) - 그 공지가 게시(USE_YN='Y')되어 있어야 한다. + * 공지가 삭제된 고아 행도 이 조건에서 자연히 빠진다 (물리 FK 없음 - ADR-F10).
  • + *
  • 공지가 없는 이슈(지연) - 자동 탐지 전용이라 검수할 공지가 없다. + * 초안(DRAFT_YN='Y')만 아니면 노출한다.
  • + *
+ * + *

공지 조건을 EXISTS 로 쓰는 이유: 예전처럼 {@code FROM ... , PortalNotice n} 으로 조인하면 + * NOTICE_ID 가 없는 지연 이슈가 행 자체에서 사라진다.

+ */ +public interface ApiStatusIncidentQueryRepository extends Repository { + + String VISIBLE = " i.draftYn = 'N'" + + " AND (i.noticeId IS NULL" + + " OR EXISTS (SELECT 1 FROM PortalNotice n" + + " WHERE n.id = i.noticeId AND n.useYn = 'Y')) "; + + /** 종결 판정이 STATE 로 이뤄지는 종류 (장애·지연). JPQL 리터럴로 써야 해서 FQCN 을 쓴다 */ + String KIND_INCIDENT = "com.eactive.apim.portal.djb.apistatus.incident.entity.IncidentKind.INCIDENT"; + String KIND_DELAY = "com.eactive.apim.portal.djb.apistatus.incident.entity.IncidentKind.DELAY"; + String KIND_MAINTENANCE = "com.eactive.apim.portal.djb.apistatus.incident.entity.IncidentKind.MAINTENANCE"; + + /** 종결 조건 - 장애·지연은 STATE 로, 점검은 종료 시각 경과로 판정한다 */ + String CLOSED_CONDITION = " ((i.kind IN (" + KIND_INCIDENT + ", " + KIND_DELAY + ")" + + " AND i.state IN :closedStates)" + + " OR (i.kind = " + KIND_MAINTENANCE + + " AND i.endAt IS NOT NULL AND i.endAt < :now)) "; + + /** + * 진행 중 이슈 (P3). 장애와 지연을 함께 본다. + */ + @Query("SELECT i FROM DjbApistatusIncident i" + + " WHERE " + VISIBLE + + " AND i.kind IN :kinds" + + " AND i.state NOT IN :closedStates" + + " ORDER BY i.startedAt DESC") + List findVisibleOpenIncidents(@Param("kinds") Collection kinds, + @Param("closedStates") Collection closedStates); + + /** + * 예정/진행 중 점검 (P5). 종료 시각이 없거나 아직 지나지 않은 점검. + */ + @Query("SELECT i FROM DjbApistatusIncident i" + + " WHERE " + VISIBLE + + " AND i.kind = :kind" + + " AND (i.endAt IS NULL OR i.endAt >= :now)" + + " ORDER BY i.startedAt ASC") + List findVisibleOngoingMaintenance(@Param("kind") IncidentKind kind, + @Param("now") LocalDateTime now); + + /** + * 종결된 이슈 (P6). 장애·지연은 종결 상태, 점검은 종료 시각 경과. + */ + @Query(value = "SELECT i FROM DjbApistatusIncident i" + + " WHERE " + VISIBLE + + " AND " + CLOSED_CONDITION + + " ORDER BY i.startedAt DESC", + countQuery = "SELECT COUNT(i) FROM DjbApistatusIncident i" + + " WHERE " + VISIBLE + + " AND " + CLOSED_CONDITION) + Page findVisibleClosedIssues(@Param("closedStates") Collection closedStates, + @Param("now") LocalDateTime now, + Pageable pageable); + + /** + * 기간과 겹치는 모든 이슈 (P2 가동률 집계, P9 일자 인덱스, P10 목록). + * 진행 중(END_AT IS NULL) 이슈도 포함한다. + */ + @Query("SELECT i FROM DjbApistatusIncident i" + + " WHERE " + VISIBLE + + " AND i.startedAt < :to" + + " AND (i.endAt IS NULL OR i.endAt >= :from)" + + " ORDER BY i.startedAt DESC") + List findVisibleOverlapping(@Param("from") LocalDateTime from, + @Param("to") LocalDateTime to); + + /** + * 기간과 겹치고 특정 API 에 영향을 준 이슈 (P9/P10 의 apiId 필터). + */ + @Query("SELECT DISTINCT i FROM DjbApistatusIncident i, DjbApistatusIncidentApi a" + + " WHERE " + VISIBLE + + " AND a.incidentId = i.incidentId" + + " AND a.apiId = :apiId" + + " AND i.startedAt < :to" + + " AND (i.endAt IS NULL OR i.endAt >= :from)" + + " ORDER BY i.startedAt DESC") + List findVisibleOverlappingByApi(@Param("from") LocalDateTime from, + @Param("to") LocalDateTime to, + @Param("apiId") String apiId); + + /** + * 공개 상세 (P7) + */ + @Query("SELECT i FROM DjbApistatusIncident i" + + " WHERE " + VISIBLE + + " AND i.incidentId = :incidentId") + Optional findVisibleById(@Param("incidentId") Long incidentId); +} diff --git a/src/main/java/com/eactive/apim/portal/djb/apistatus/service/ApiCurrentStatusService.java b/src/main/java/com/eactive/apim/portal/djb/apistatus/service/ApiCurrentStatusService.java new file mode 100644 index 0000000..6293cf0 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/apistatus/service/ApiCurrentStatusService.java @@ -0,0 +1,274 @@ +package com.eactive.apim.portal.djb.apistatus.service; + +import com.eactive.apim.portal.apispec.entity.ApiSpecInfo; +import com.eactive.apim.portal.apispec.repository.ApiSpecInfoRepository; +import com.eactive.apim.portal.djb.apistatus.dto.ApiCurrentStatusDTO; +import com.eactive.apim.portal.djb.apistatus.incident.entity.DjbApistatusIncident; +import com.eactive.apim.portal.djb.apistatus.incident.entity.DjbApistatusIncidentApi; +import com.eactive.apim.portal.djb.apistatus.incident.entity.IncidentKind; +import com.eactive.apim.portal.djb.apistatus.incident.entity.IncidentState; +import com.eactive.apim.portal.djb.apistatus.incident.repository.DjbApistatusIncidentApiRepository; +import com.eactive.apim.portal.djb.apistatus.repository.ApiStatusIncidentQueryRepository; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * API 별 현재 상태(정상/점검/지연/장애) 조회. + * + *

"내 API 현황"(P4)과 API 상세 화면이 이 서비스를 공유해 판정 기준을 하나로 유지한다. + * 근거는 게시된 장애·점검 공지뿐이며(PTL_NOTICE.USE_YN='Y' + DRAFT_YN='N'), + * 미게시 건은 다른 API Status 화면과 마찬가지로 반영하지 않는다.

+ * + *

판정 규칙 (심각한 쪽 우선) + *

    + *
  • 진행 중 장애 → 장애(OUTAGE). 단 모니터링 상태이거나 자동 탐지 지연 건이면 지연(DEGRADED)
  • + *
  • 이미 시작된 점검 → 점검(MAINTENANCE)
  • + *
  • 해당 없음 → 정상(NORMAL)
  • + *
+ */ +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class ApiCurrentStatusService { + + private final ApiStatusIncidentQueryRepository incidentQueryRepository; + private final DjbApistatusIncidentApiRepository incidentApiRepository; + private final ApiSpecInfoRepository apiSpecInfoRepository; + private final ApiStatusUptimeService uptimeService; + private final ApiStatusCatalogService catalogService; + + /** API 한 건의 현재 상태. apiId 가 비어 있으면 empty */ + public Optional getStatus(String apiId) { + if (StringUtils.isBlank(apiId)) { + return Optional.empty(); + } + return getStatuses(Collections.singletonList(apiId)).stream().findFirst(); + } + + /** + * 여러 API 의 현재 상태 (API 명·가동률 포함). 요청 순서를 유지하며 중복 apiId 는 한 번만 반환한다. + */ + public List getStatuses(Collection apiIds) { + Set ids = normalize(apiIds); + if (ids.isEmpty()) { + return Collections.emptyList(); + } + + Map statuses = resolveStatuses(ids); + Map names = resolveApiNames(ids); + Map uptimes = uptimeService.getApiUptimeRatios(ids, catalogService.getWindowDays()); + + List result = new ArrayList<>(ids.size()); + for (String apiId : ids) { + ApiCurrentStatusDTO dto = statuses.get(apiId); + dto.setApiName(names.get(apiId)); + dto.setUptimeRatio(uptimes.getOrDefault(apiId, 1.0d)); + result.add(dto); + } + return result; + } + + /** + * 상태만 계산한다 (API 명·가동률 제외). 대상 API 를 이미 알고 있고 이름·가동률을 + * 따로 채우는 호출자(P4 내 API 현황)를 위한 경량 경로다. + */ + public Map resolveStatuses(Set apiIds) { + if (apiIds == null || apiIds.isEmpty()) { + return Collections.emptyMap(); + } + + LocalDateTime now = ApiStatusSupport.now(); + int windowDays = catalogService.getWindowDays(); + + Map openByApi = mapOpenIncidents(apiIds); + Map maintenanceByApi = mapStartedMaintenance(apiIds, now); + Map lastIncidentAt = collectLastIncidentAt(apiIds, now, windowDays); + + Map result = new LinkedHashMap<>(); + for (String apiId : apiIds) { + DjbApistatusIncident open = openByApi.get(apiId); + DjbApistatusIncident maintenance = maintenanceByApi.get(apiId); + + ApiCurrentStatusDTO dto = new ApiCurrentStatusDTO(); + dto.setApiId(apiId); + dto.setCurrentStatus(resolveStatus(open, maintenance != null)); + dto.setCurrentStatusLabel(ApiStatusSupport.statusLabel(dto.getCurrentStatus())); + dto.setLastIncidentAt(lastIncidentAt.get(apiId)); + dto.setWindowDays(windowDays); + + // 장애가 점검보다 심각하므로 근거 이슈도 장애를 우선한다 + DjbApistatusIncident active = open != null ? open : maintenance; + if (active != null) { + dto.setActiveIncidentId(active.getIncidentId()); + dto.setActiveIncidentKind(active.getKind() == null ? null : active.getKind().name()); + dto.setActiveIncidentTitle(active.getTitle()); + dto.setStatusSince(active.getStartedAt()); + dto.setExpectedEndAt(active.getEndAt()); + } + result.put(apiId, dto); + } + return result; + } + + /** 빈 값·중복 제거. 요청 순서는 유지한다 */ + private Set normalize(Collection apiIds) { + if (apiIds == null || apiIds.isEmpty()) { + return Collections.emptySet(); + } + return apiIds.stream() + .filter(StringUtils::isNotBlank) + .map(StringUtils::trim) + .collect(Collectors.toCollection(LinkedHashSet::new)); + } + + /** + * API 명은 공개 목록(DISPLAY_YN='Y')에서만 가져온다. 비공개 API 의 이름이 새어나가지 않게 한다. + */ + private Map resolveApiNames(Set apiIds) { + List specs = + apiSpecInfoRepository.findAllByDisplayYnAndApiIdIn("Y", new ArrayList<>(apiIds)); + Map names = new HashMap<>(); + for (ApiSpecInfo spec : specs) { + names.put(spec.getApiId(), StringUtils.defaultIfBlank(spec.getApiName(), spec.getApiId())); + } + return names; + } + + /** 진행 중(미종결) 장애·지연 중 API 별로 가장 심각한 한 건 */ + private Map mapOpenIncidents(Set apiIds) { + List openIncidents = incidentQueryRepository + .findVisibleOpenIncidents(IncidentKind.DEGRADING, ApiStatusSupport.CLOSED_STATES); + if (openIncidents.isEmpty()) { + return Collections.emptyMap(); + } + Map byId = openIncidents.stream() + .collect(Collectors.toMap(DjbApistatusIncident::getIncidentId, incident -> incident)); + + Map byApi = new HashMap<>(); + for (DjbApistatusIncidentApi api : + incidentApiRepository.findByIncidentIdInOrderByIncidentIdAscApiIdAsc(byId.keySet())) { + if (!apiIds.contains(api.getApiId()) || api.getRecoveredAt() != null) { + continue; + } + DjbApistatusIncident incident = byId.get(api.getIncidentId()); + DjbApistatusIncident previous = byApi.get(api.getApiId()); + // 같은 API 에 여러 장애가 열려 있으면 더 심각한(장애 > 지연, 조사중 > 모니터링) 쪽을 우선한다 + if (previous == null || severity(incident) < severity(previous)) { + byApi.put(api.getApiId(), incident); + } + } + return byApi; + } + + /** 이미 시작된 점검 중 API 별로 가장 먼저 시작된 한 건 (예정 점검은 현재 상태가 아니므로 제외) */ + private Map mapStartedMaintenance(Set apiIds, LocalDateTime now) { + List maintenances = incidentQueryRepository + .findVisibleOngoingMaintenance(IncidentKind.MAINTENANCE, now).stream() + .filter(incident -> incident.getStartedAt() != null && !incident.getStartedAt().isAfter(now)) + .collect(Collectors.toList()); + if (maintenances.isEmpty()) { + return Collections.emptyMap(); + } + Map byId = maintenances.stream() + .collect(Collectors.toMap(DjbApistatusIncident::getIncidentId, incident -> incident)); + + Map byApi = new HashMap<>(); + for (DjbApistatusIncidentApi api : + incidentApiRepository.findByIncidentIdInOrderByIncidentIdAscApiIdAsc(byId.keySet())) { + if (!apiIds.contains(api.getApiId())) { + continue; + } + DjbApistatusIncident incident = byId.get(api.getIncidentId()); + DjbApistatusIncident previous = byApi.get(api.getApiId()); + if (previous == null + || (incident.getStartedAt() != null && previous.getStartedAt() != null + && incident.getStartedAt().isBefore(previous.getStartedAt()))) { + byApi.put(api.getApiId(), incident); + } + } + return byApi; + } + + /** 조회 기간 내 마지막 장애·지연 발생 시각 */ + private Map collectLastIncidentAt(Set apiIds, LocalDateTime now, int windowDays) { + LocalDateTime windowStart = now.toLocalDate().minusDays(windowDays - 1L).atStartOfDay(); + List incidents = incidentQueryRepository + .findVisibleOverlapping(windowStart, now.toLocalDate().plusDays(1).atStartOfDay()).stream() + .filter(incident -> incident.getKind() != null && incident.getKind().isDegrading()) + .collect(Collectors.toList()); + if (incidents.isEmpty()) { + return Collections.emptyMap(); + } + Map byId = incidents.stream() + .collect(Collectors.toMap(DjbApistatusIncident::getIncidentId, incident -> incident)); + + Map result = new HashMap<>(); + for (DjbApistatusIncidentApi api : + incidentApiRepository.findByIncidentIdInOrderByIncidentIdAscApiIdAsc(byId.keySet())) { + if (!apiIds.contains(api.getApiId())) { + continue; + } + LocalDateTime startedAt = byId.get(api.getIncidentId()).getStartedAt(); + LocalDateTime previous = result.get(api.getApiId()); + if (startedAt != null && (previous == null || startedAt.isAfter(previous))) { + result.put(api.getApiId(), startedAt); + } + } + return result; + } + + /** + * 진행 중 장애·지연 + 점검 여부로 현재 상태를 정한다. + * + *

KIND 가 DELAY 면 지연이다. 장애(INCIDENT)라도 모니터링 단계면 이미 완화된 상태라 + * 지연으로 표기한다 - 이슈 이력의 유형 필터(KIND 기준)와는 이 지점만 다르다.

+ */ + private String resolveStatus(DjbApistatusIncident open, boolean underMaintenance) { + if (open != null) { + boolean degraded = open.getKind() == IncidentKind.DELAY + || open.getState() == IncidentState.MONITORING; + return degraded ? ApiStatusSupport.STATUS_DEGRADED : ApiStatusSupport.STATUS_OUTAGE; + } + if (underMaintenance) { + return ApiStatusSupport.STATUS_MAINTENANCE; + } + return ApiStatusSupport.STATUS_NORMAL; + } + + /** + * 같은 API 에 열린 이슈가 여럿일 때의 우선순위 (낮을수록 심각). + * 지연은 장애보다 뒤로 민다. + */ + private int severity(DjbApistatusIncident incident) { + if (incident == null) { + return 99; + } + int base = incident.getKind() == IncidentKind.DELAY ? 10 : 0; + IncidentState state = incident.getState(); + if (state == null) { + return base + 9; + } + switch (state) { + case INVESTIGATING: return base; + case IDENTIFIED: return base + 1; + // 모니터링 단계는 지연으로 표기되므로 같은 장애라도 뒤로 민다 + case MONITORING: return base + 12; + default: return base + 8; + } + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/apistatus/service/ApiStatusAssembler.java b/src/main/java/com/eactive/apim/portal/djb/apistatus/service/ApiStatusAssembler.java new file mode 100644 index 0000000..ddb5767 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/apistatus/service/ApiStatusAssembler.java @@ -0,0 +1,283 @@ +package com.eactive.apim.portal.djb.apistatus.service; + +import com.eactive.apim.portal.djb.apistatus.dto.ActiveIncidentDTO; +import com.eactive.apim.portal.djb.apistatus.dto.AffectedApiDTO; +import com.eactive.apim.portal.djb.apistatus.dto.MaintenanceCardDTO; +import com.eactive.apim.portal.djb.apistatus.dto.PastIssueCardDTO; +import com.eactive.apim.portal.djb.apistatus.dto.TimelineEntryDTO; +import com.eactive.apim.portal.djb.apistatus.incident.entity.DjbApistatusIncident; +import com.eactive.apim.portal.djb.apistatus.incident.entity.DjbApistatusIncidentApi; +import com.eactive.apim.portal.djb.apistatus.incident.entity.DjbApistatusIncidentTimeline; +import com.eactive.apim.portal.djb.apistatus.incident.entity.IncidentKind; +import com.eactive.apim.portal.djb.apistatus.incident.repository.DjbApistatusIncidentApiRepository; +import com.eactive.apim.portal.djb.apistatus.incident.repository.DjbApistatusIncidentTimelineRepository; +import com.eactive.apim.portal.apps.community.notice.repository.PortalNoticeRepository; +import com.eactive.apim.portal.portalNotice.entity.PortalNotice; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 장애/점검 엔티티 → 화면 DTO 변환. 영향 API·타임라인은 한 번에 모아 읽어 1+N 조회를 피한다. + */ +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class ApiStatusAssembler { + + private static final int RECENT_TIMELINE_SIZE = 3; + + private final DjbApistatusIncidentApiRepository incidentApiRepository; + private final DjbApistatusIncidentTimelineRepository timelineRepository; + private final PortalNoticeRepository portalNoticeRepository; + private final ApiStatusCatalogService catalogService; + + /** + * 영향 인터페이스를 "개발자포탈에 게시된 API" 와 그 외 GW 인터페이스로 가른 결과. + * 게시된 것만 개별 노출하고 나머지는 건수로만 알린다. + */ + @Getter + @RequiredArgsConstructor + public static class VisibleApis { + private final List visible; + private final int hiddenCount; + } + + /** + * 이슈 영향 인터페이스에서 현재 사용자에게 공개된 API 만 남긴다. + * + *

이름은 이슈에 캐시된 EAI 서비스명이 아니라 포털 노출명(PTL_API_SPEC_INFO)을 쓴다 - + * 같은 API 가 화면마다 다른 이름으로 보이지 않게 한다.

+ */ + private VisibleApis splitByVisibility(List apis, Map visibleNames) { + if (apis == null || apis.isEmpty()) { + return new VisibleApis(Collections.emptyList(), 0); + } + List visible = new ArrayList<>(); + int hidden = 0; + for (AffectedApiDTO api : apis) { + String publishedName = visibleNames.get(api.getApiId()); + if (publishedName == null) { + hidden++; + continue; + } + visible.add(new AffectedApiDTO(api.getApiId(), publishedName, api.getRecoveredAt())); + } + return new VisibleApis(visible, hidden); + } + + public Map> loadApis(Collection incidentIds) { + if (incidentIds == null || incidentIds.isEmpty()) { + return Collections.emptyMap(); + } + Map> result = new HashMap<>(); + for (DjbApistatusIncidentApi api : + incidentApiRepository.findByIncidentIdInOrderByIncidentIdAscApiIdAsc(incidentIds)) { + result.computeIfAbsent(api.getIncidentId(), key -> new ArrayList<>()) + .add(new AffectedApiDTO(api.getApiId(), + StringUtils.defaultIfBlank(api.getApiName(), api.getApiId()), + api.getRecoveredAt())); + } + return result; + } + + /** + * 공개 타임라인. 최신순으로 담는다. + */ + public Map> loadTimelines(Collection incidentIds) { + if (incidentIds == null || incidentIds.isEmpty()) { + return Collections.emptyMap(); + } + Map> result = new HashMap<>(); + for (DjbApistatusIncidentTimeline timeline : + timelineRepository.findByIncidentIdInAndVisibleYnOrderByEventAtDesc(incidentIds, "Y")) { + result.computeIfAbsent(timeline.getIncidentId(), key -> new ArrayList<>()) + .add(toTimelineEntry(timeline)); + } + return result; + } + + /** + * 이슈에 연결된 공지. 게시 중(USE_YN='Y')인 것만 담는다. + * + *

조회 자체의 공개 조건({@code ApiStatusIncidentQueryRepository.VISIBLE})과 같은 기준이라 + * 여기서 빠지는 건은 공지가 삭제된 고아 행뿐이다.

+ */ + public Map loadNotices(Collection noticeIds) { + if (noticeIds == null || noticeIds.isEmpty()) { + return Collections.emptyMap(); + } + List ids = noticeIds.stream() + .filter(StringUtils::isNotBlank) + .distinct() + .collect(Collectors.toList()); + if (ids.isEmpty()) { + return Collections.emptyMap(); + } + Map result = new HashMap<>(); + for (PortalNotice notice : portalNoticeRepository.findByIdInAndUseYn(ids, "Y")) { + result.put(notice.getId(), notice); + } + return result; + } + + public List toActiveIncidents(List incidents, LocalDateTime now) { + if (incidents.isEmpty()) { + return Collections.emptyList(); + } + List ids = incidentIds(incidents); + Map> apis = loadApis(ids); + Map> timelines = loadTimelines(ids); + Map visibleNames = catalogService.getVisibleApiNames(); + Map notices = loadNotices(incidents.stream() + .map(DjbApistatusIncident::getNoticeId) + .collect(Collectors.toList())); + + List result = new ArrayList<>(); + for (DjbApistatusIncident incident : incidents) { + ActiveIncidentDTO dto = new ActiveIncidentDTO(); + dto.setIncidentId(incident.getIncidentId()); + dto.setNoticeId(incident.getNoticeId()); + dto.setKind(incident.getKind() == null ? null : incident.getKind().name()); + dto.setState(incident.getState() == null ? null : incident.getState().name()); + dto.setStateLabel(ApiStatusSupport.stateLabel(incident.getState())); + dto.setTitle(incident.getTitle()); + dto.setSummary(incident.getSummary()); + dto.setStartedAt(incident.getStartedAt()); + dto.setElapsedMinutes(ApiStatusSupport.minutesBetween(incident.getStartedAt(), now)); + VisibleApis affected = splitByVisibility( + apis.getOrDefault(incident.getIncidentId(), Collections.emptyList()), visibleNames); + dto.setApis(affected.getVisible()); + dto.setHiddenApiCount(affected.getHiddenCount()); + + PortalNotice notice = incident.getNoticeId() == null ? null : notices.get(incident.getNoticeId()); + if (notice != null) { + dto.setNoticeSubject(notice.getNoticeSubject()); + dto.setNoticeDetail(notice.getNoticeDetail()); + } + + List all = timelines.getOrDefault(incident.getIncidentId(), Collections.emptyList()); + dto.setRecentTimeline(all.size() > RECENT_TIMELINE_SIZE + ? new ArrayList<>(all.subList(0, RECENT_TIMELINE_SIZE)) : all); + result.add(dto); + } + return result; + } + + public List toMaintenanceCards(List incidents) { + if (incidents.isEmpty()) { + return Collections.emptyList(); + } + Map> apis = loadApis(incidentIds(incidents)); + Map visibleNames = catalogService.getVisibleApiNames(); + Map notices = loadNotices(incidents.stream() + .map(DjbApistatusIncident::getNoticeId) + .collect(Collectors.toList())); + + List result = new ArrayList<>(); + for (DjbApistatusIncident incident : incidents) { + MaintenanceCardDTO dto = new MaintenanceCardDTO(); + dto.setIncidentId(incident.getIncidentId()); + dto.setNoticeId(incident.getNoticeId()); + + PortalNotice notice = incident.getNoticeId() == null ? null : notices.get(incident.getNoticeId()); + if (notice != null) { + dto.setNoticeSubject(notice.getNoticeSubject()); + dto.setNoticeDetail(notice.getNoticeDetail()); + } + + dto.setTitle(incident.getTitle()); + dto.setSummary(incident.getSummary()); + dto.setStartedAt(incident.getStartedAt()); + dto.setEndAt(incident.getEndAt()); + dto.setDurationMinutes(incident.getEndAt() == null ? null + : ApiStatusSupport.minutesBetween(incident.getStartedAt(), incident.getEndAt())); + VisibleApis affected = splitByVisibility( + apis.getOrDefault(incident.getIncidentId(), Collections.emptyList()), visibleNames); + dto.setImpactedApis(affected.getVisible()); + dto.setHiddenApiCount(affected.getHiddenCount()); + dto.setRegisteredAt(incident.getCreatedDate()); + dto.setLastModifiedAt(incident.getLastModifiedDate()); + result.add(dto); + } + return result; + } + + /** + * 지난 이슈 카드. 장애·지연은 타임라인 전체를 붙이고 점검은 붙이지 않는다 (ADR-F15). + */ + public List toPastIssueCards(List incidents) { + if (incidents.isEmpty()) { + return Collections.emptyList(); + } + List ids = incidentIds(incidents); + Map> apis = loadApis(ids); + Map visibleNames = catalogService.getVisibleApiNames(); + + List incidentKindIds = incidents.stream() + .filter(incident -> incident.getKind() != null && incident.getKind().isDegrading()) + .map(DjbApistatusIncident::getIncidentId) + .collect(Collectors.toList()); + Map> timelines = loadTimelines(incidentKindIds); + Map notices = loadNotices(incidents.stream() + .map(DjbApistatusIncident::getNoticeId) + .collect(Collectors.toList())); + + List result = new ArrayList<>(); + for (DjbApistatusIncident incident : incidents) { + PastIssueCardDTO dto = new PastIssueCardDTO(); + dto.setIncidentId(incident.getIncidentId()); + dto.setNoticeId(incident.getNoticeId()); + + PortalNotice notice = incident.getNoticeId() == null ? null : notices.get(incident.getNoticeId()); + if (notice != null) { + dto.setNoticeSubject(notice.getNoticeSubject()); + dto.setNoticeDetail(notice.getNoticeDetail()); + } + + dto.setKind(incident.getKind() == null ? null : incident.getKind().name()); + dto.setState(incident.getState() == null ? null : incident.getState().name()); + dto.setStateLabel(ApiStatusSupport.stateLabel(incident.getState())); + dto.setTitle(incident.getTitle()); + dto.setSummary(incident.getSummary()); + dto.setStartedAt(incident.getStartedAt()); + dto.setEndAt(incident.getEndAt()); + dto.setDateGroup(incident.getStartedAt() == null ? null : incident.getStartedAt().toLocalDate()); + dto.setDurationMinutes(incident.getEndAt() == null ? null + : ApiStatusSupport.minutesBetween(incident.getStartedAt(), incident.getEndAt())); + VisibleApis affected = splitByVisibility( + apis.getOrDefault(incident.getIncidentId(), Collections.emptyList()), visibleNames); + dto.setImpactedApis(affected.getVisible()); + dto.setHiddenApiCount(affected.getHiddenCount()); + dto.setTimeline(timelines.getOrDefault(incident.getIncidentId(), Collections.emptyList())); + result.add(dto); + } + return result; + } + + private TimelineEntryDTO toTimelineEntry(DjbApistatusIncidentTimeline timeline) { + TimelineEntryDTO dto = new TimelineEntryDTO(); + dto.setTimelineId(timeline.getTimelineId()); + dto.setEventAt(timeline.getEventAt()); + dto.setStateAfter(timeline.getStateAfter() == null ? null : timeline.getStateAfter().name()); + dto.setLabelKo(ApiStatusSupport.stateLabel(timeline.getStateAfter())); + dto.setBody(timeline.getBody()); + dto.setAuthorType(timeline.getAuthorType()); + return dto; + } + + private List incidentIds(List incidents) { + return incidents.stream().map(DjbApistatusIncident::getIncidentId).collect(Collectors.toList()); + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/apistatus/service/ApiStatusCatalogService.java b/src/main/java/com/eactive/apim/portal/djb/apistatus/service/ApiStatusCatalogService.java new file mode 100644 index 0000000..cb9bc8b --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/apistatus/service/ApiStatusCatalogService.java @@ -0,0 +1,198 @@ +package com.eactive.apim.portal.djb.apistatus.service; + +import com.eactive.apim.gateway.data.statistics.repository.GwApiStatusRepository; +import com.eactive.apim.portal.apps.apis.dto.ApiSpecInfoDto; +import com.eactive.apim.portal.apps.apis.service.ApiSearchFacade; +import com.eactive.apim.portal.apps.apiservice.dto.ApiGroupSearch; +import com.eactive.apim.portal.djb.apistatus.dto.ApiOptionDTO; +import com.eactive.apim.portal.portalproperty.service.PortalPropertyService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import java.time.Instant; +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * API Status 화면의 부가 조회 - 필터용 API 목록, 상태 모니터링 최근 실행 시각. + */ +@Slf4j +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class ApiStatusCatalogService { + + public static final String PROPERTY_GROUP = "Portal"; + + /** API 상태 모니터링 Quartz Job 이름 (eapim-admin QRTZ_JOB_DETAILS.JOB_NAME) */ + public static final String KEY_MONITOR_JOB_NAME = "djb.apistatus.monitor.job-name"; + + /** 실서버(DAPM) QRTZ_JOB_DETAILS 실사값 (2026-07-31) */ + private static final String DEFAULT_MONITOR_JOB_NAME = "ApiStatusMonitorJob"; + + /** 상태/이력 조회 기간(일). 가동률 바·이슈 인덱스·date picker 범위가 모두 이 값을 따른다 */ + public static final String KEY_WINDOW_DAYS = "djb.apistatus.window-days"; + + /** OPEN API 목록 카드의 현재 상태 태그 노출 여부 (true/false) */ + public static final String KEY_API_LIST_STATUS_BADGE = "djb.apistatus.api-list-status-badge"; + + private static final boolean DEFAULT_API_LIST_STATUS_BADGE = true; + + private static final int MIN_WINDOW_DAYS = 1; + private static final int MAX_WINDOW_DAYS = 365; + + private final ApiSearchFacade apiSearchFacade; + private final PortalPropertyService portalPropertyService; + private final GwApiStatusRepository gwApiStatusRepository; + + @PersistenceContext + private EntityManager entityManager; + + /** + * 이슈 이력 필터용 API 목록. + * + *

"오픈 API" 목록 화면과 같은 조회 경로({@link ApiSearchFacade#searchApis})를 쓴다. + * 즉 API 그룹(AGWAPP.API_GROUP + API_GROUP_API)에 편성된 API 중 + * PTL_API_SPEC_INFO 에 스펙이 있고 현재 사용자에게 공개된 것만 나온다.

+ */ + @SuppressWarnings("unchecked") + public List getSelectableApis() { + Object apis = apiSearchFacade.searchApis(new ApiGroupSearch()).get("apis"); + if (!(apis instanceof List)) { + return Collections.emptyList(); + } + + return ((List) apis).stream() + .filter(api -> StringUtils.isNotBlank(api.getApiId())) + .map(api -> new ApiOptionDTO(api.getApiId(), + StringUtils.defaultIfBlank(api.getApiName(), api.getApiId()), + api.getApiGroupName())) + .sorted(Comparator.comparing(ApiOptionDTO::getApiName, + Comparator.nullsLast(Comparator.naturalOrder()))) + .collect(Collectors.toList()); + } + + /** + * 현재 사용자에게 공개된 API 의 {API ID → 노출명} 맵. + * + *

GW 인터페이스는 전부 이슈 데이터(DJB_APISTATUS_INCIDENT_API)에 들어오지만 + * 개발자포탈에 게시되는 것은 그 일부(= API)뿐이다. 화면은 이 맵에 있는 것만 + * 개별 이름으로 노출하고 나머지는 건수로 묶는다.

+ * + *

{@link #getSelectableApis()} 와 같은 경로라 역할·소속에 따른 공개 범위가 그대로 반영된다.

+ */ + public Map getVisibleApiNames() { + Map names = new LinkedHashMap<>(); + for (ApiOptionDTO api : getSelectableApis()) { + names.put(api.getApiId(), api.getApiName()); + } + return names; + } + + /** + * API 상태 모니터링 Job(eapim-admin Quartz)의 마지막 실행 시각. + * + *

Job 이름은 PTL_PROPERTY({@code Portal} / {@code djb.apistatus.monitor.job-name})로 + * 바꿀 수 있으며, 최초 접근 시 기본값으로 row 가 자동 생성된다. + * Quartz 메타(QRTZ_TRIGGERS)는 EMSAPP 스키마라 EMS EntityManager 로 직접 읽는다.

+ * + *

readOnly 를 풀어야 최초 접근 시 프로퍼티 row 자동 생성(INSERT)이 가능하다.

+ */ + @Transactional + public LocalDateTime getLastMonitorFireAt() { + try { + String jobName = portalPropertyService.getOrCreateProperty( + PROPERTY_GROUP, KEY_MONITOR_JOB_NAME, DEFAULT_MONITOR_JOB_NAME, + "API 상태 모니터링 Quartz Job 이름 (eapim-admin QRTZ_JOB_DETAILS.JOB_NAME)"); + if (StringUtils.isBlank(jobName)) { + jobName = DEFAULT_MONITOR_JOB_NAME; + } + + Object fireTime = entityManager.createNativeQuery( + "SELECT MAX(PREV_FIRE_TIME) FROM QRTZ_TRIGGERS WHERE JOB_NAME = :jobName") + .setParameter("jobName", jobName) + .getSingleResult(); + if (!(fireTime instanceof Number)) { + return null; + } + long epochMillis = ((Number) fireTime).longValue(); + if (epochMillis <= 0) { + return null; + } + return LocalDateTime.ofInstant(Instant.ofEpochMilli(epochMillis), ApiStatusSupport.ZONE); + } catch (Exception e) { + // 스케줄 메타 조회 실패가 화면 전체를 막지 않도록 한다 + log.warn("API 상태 모니터링 실행 시각 조회 실패", e); + return null; + } + } + + /** + * 상태/이력 조회 기간(일). PTL_PROPERTY({@code Portal} / {@code djb.apistatus.window-days})로 + * 조절하며 1~365 로 clamp 한다. 값이 이상하면 기본 90일. + */ + @Transactional + public int getWindowDays() { + try { + String value = portalPropertyService.getOrCreateProperty( + PROPERTY_GROUP, KEY_WINDOW_DAYS, + String.valueOf(ApiStatusSupport.DEFAULT_WINDOW_DAYS), + "API Status 조회 기간(일). 가동률 바·이슈 인덱스·날짜 선택 범위에 적용 (1~365)"); + int days = Integer.parseInt(StringUtils.trimToEmpty(value)); + return Math.max(MIN_WINDOW_DAYS, Math.min(MAX_WINDOW_DAYS, days)); + } catch (Exception e) { + log.warn("조회 기간 프로퍼티 해석 실패 - 기본값 {}일 사용", ApiStatusSupport.DEFAULT_WINDOW_DAYS, e); + return ApiStatusSupport.DEFAULT_WINDOW_DAYS; + } + } + + /** + * OPEN API 목록(/apis) 카드에 현재 상태 태그를 노출할지 여부. + * PTL_PROPERTY({@code Portal} / {@code djb.apistatus.api-list-status-badge}) 로 켜고 끈다. + * + *

{@code true/Y/1} 이면 노출, {@code false/N/0} 이면 숨김. 값이 비었거나 이상하면 기본 노출.

+ */ + @Transactional + public boolean isApiListStatusBadgeEnabled() { + try { + String value = portalPropertyService.getOrCreateProperty( + PROPERTY_GROUP, KEY_API_LIST_STATUS_BADGE, + String.valueOf(DEFAULT_API_LIST_STATUS_BADGE), + "OPEN API 목록 카드에 현재 상태(정상/점검/지연/장애) 태그 노출 여부 (true/false)"); + String normalized = StringUtils.trimToEmpty(value); + if (StringUtils.equalsAnyIgnoreCase(normalized, "false", "n", "0")) { + return false; + } + if (StringUtils.equalsAnyIgnoreCase(normalized, "true", "y", "1")) { + return true; + } + return DEFAULT_API_LIST_STATUS_BADGE; + } catch (Exception e) { + log.warn("API 목록 상태 태그 노출 프로퍼티 조회 실패 - 기본값 {} 사용", DEFAULT_API_LIST_STATUS_BADGE, e); + return DEFAULT_API_LIST_STATUS_BADGE; + } + } + + /** + * API 상태가 마지막으로 변경된 시각 (AGWAPP.API_STATUS 최근 upsert). + * 실행 시각(fire)과 달리 상태 변화가 있을 때만 갱신된다. + */ + public LocalDateTime getLastStatusChangedAt() { + try { + return gwApiStatusRepository.findLastModifiedDate().orElse(null); + } catch (Exception e) { + log.warn("API 상태 변경 시각 조회 실패", e); + return null; + } + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/apistatus/service/ApiStatusIssueHistoryService.java b/src/main/java/com/eactive/apim/portal/djb/apistatus/service/ApiStatusIssueHistoryService.java new file mode 100644 index 0000000..3c5eb8e --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/apistatus/service/ApiStatusIssueHistoryService.java @@ -0,0 +1,159 @@ +package com.eactive.apim.portal.djb.apistatus.service; + +import com.eactive.apim.portal.djb.apistatus.dto.IssueDateEntryDTO; +import com.eactive.apim.portal.djb.apistatus.dto.PastIssueCardDTO; +import com.eactive.apim.portal.djb.apistatus.incident.entity.DjbApistatusIncident; +import com.eactive.apim.portal.djb.apistatus.incident.entity.IncidentKind; +import com.eactive.apim.portal.djb.apistatus.repository.ApiStatusIncidentQueryRepository; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 전체 이슈 이력 페이지 조회 (ADR-F16/F17). 필터는 날짜와 API 두 축만 지원한다. + */ +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class ApiStatusIssueHistoryService { + + private final ApiStatusIncidentQueryRepository incidentQueryRepository; + private final ApiStatusAssembler assembler; + private final ApiStatusCatalogService catalogService; + + /** + * P9 - 90일 인덱스바용 일자별 이슈 집계. 이슈가 여러 날에 걸치면 걸친 날짜 모두에 집계한다. + */ + public List getIssueDates(int days, String apiId, String kind) { + int windowDays = days <= 0 ? ApiStatusSupport.DEFAULT_WINDOW_DAYS : Math.min(days, 365); + LocalDateTime now = ApiStatusSupport.now(); + LocalDate today = now.toLocalDate(); + LocalDate from = today.minusDays(windowDays - 1L); + LocalDateTime windowStart = from.atStartOfDay(); + LocalDateTime windowEnd = today.plusDays(1).atStartOfDay(); + + List incidents = findOverlapping(windowStart, windowEnd, apiId, kind); + + Map byDate = new LinkedHashMap<>(); + for (int offset = 0; offset < windowDays; offset++) { + LocalDate date = from.plusDays(offset); + IssueDateEntryDTO entry = new IssueDateEntryDTO(); + entry.setDate(date); + byDate.put(date, entry); + } + + for (DjbApistatusIncident incident : incidents) { + if (incident.getStartedAt() == null) { + continue; + } + LocalDate start = incident.getStartedAt().toLocalDate(); + LocalDateTime effectiveEnd = incident.getEndAt() == null ? now : incident.getEndAt(); + LocalDate end = effectiveEnd.toLocalDate(); + + LocalDate cursor = start.isBefore(from) ? from : start; + LocalDate last = end.isAfter(today) ? today : end; + + while (!cursor.isAfter(last)) { + IssueDateEntryDTO entry = byDate.get(cursor); + if (entry != null) { + tally(entry, incident); + } + cursor = cursor.plusDays(1); + } + } + + return new ArrayList<>(byDate.values()); + } + + /** + * 인덱스바 1칸에 이슈 1건을 집계한다. 유형은 이슈 목록 필터와 같은 세 축(장애/지연/점검)으로 가른다. + */ + private void tally(IssueDateEntryDTO entry, DjbApistatusIncident incident) { + IncidentKind kind = incident.getKind(); + if (kind == null) { + return; + } + switch (kind) { + case MAINTENANCE: entry.setMntCount(entry.getMntCount() + 1); break; + case DELAY: entry.setDlyCount(entry.getDlyCount() + 1); break; + default: entry.setIncCount(entry.getIncCount() + 1); break; + } + if (!entry.getKinds().contains(kind.name())) { + entry.getKinds().add(kind.name()); + } + } + + /** + * P10 - 날짜/API/유형 필터 이슈 목록. 날짜 미지정 시 90일 전체. + */ + public Page getIssues(LocalDate date, String apiId, String kind, Pageable pageable) { + LocalDateTime now = ApiStatusSupport.now(); + LocalDateTime from; + LocalDateTime to; + + if (date == null) { + LocalDate today = now.toLocalDate(); + from = today.minusDays(catalogService.getWindowDays() - 1L).atStartOfDay(); + to = today.plusDays(1).atStartOfDay(); + } else { + from = date.atStartOfDay(); + to = date.plusDays(1).atStartOfDay(); + } + + List incidents = findOverlapping(from, to, apiId, kind); + if (incidents.isEmpty()) { + return new PageImpl<>(Collections.emptyList(), pageable, 0); + } + + int offset = (int) pageable.getOffset(); + if (offset >= incidents.size()) { + return new PageImpl<>(Collections.emptyList(), pageable, incidents.size()); + } + int end = Math.min(offset + pageable.getPageSize(), incidents.size()); + + List content = assembler.toPastIssueCards(incidents.subList(offset, end)); + return new PageImpl<>(content, pageable, incidents.size()); + } + + private List findOverlapping(LocalDateTime from, LocalDateTime to, + String apiId, String kind) { + List incidents = StringUtils.isBlank(apiId) + ? incidentQueryRepository.findVisibleOverlapping(from, to) + : incidentQueryRepository.findVisibleOverlappingByApi(from, to, apiId); + + java.util.function.Predicate filter = kindFilter(kind); + if (filter == null) { + return incidents; + } + return incidents.stream().filter(filter).collect(Collectors.toList()); + } + + /** + * 유형 필터. 미지정/ALL/알 수 없는 값은 전체(null)로 본다. + * 필터 값은 {@link IncidentKind} 이름 그대로다 (INCIDENT / DELAY / MAINTENANCE). + */ + private java.util.function.Predicate kindFilter(String kind) { + if (StringUtils.isBlank(kind) || "ALL".equalsIgnoreCase(kind)) { + return null; + } + try { + IncidentKind selected = IncidentKind.valueOf(kind.trim().toUpperCase()); + return incident -> incident.getKind() == selected; + } catch (IllegalArgumentException e) { + return null; + } + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/apistatus/service/ApiStatusQueryService.java b/src/main/java/com/eactive/apim/portal/djb/apistatus/service/ApiStatusQueryService.java new file mode 100644 index 0000000..7ac0314 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/apistatus/service/ApiStatusQueryService.java @@ -0,0 +1,59 @@ +package com.eactive.apim.portal.djb.apistatus.service; + +import com.eactive.apim.portal.djb.apistatus.dto.ActiveIncidentDTO; +import com.eactive.apim.portal.djb.apistatus.dto.MaintenanceCardDTO; +import com.eactive.apim.portal.djb.apistatus.dto.PastIssueCardDTO; +import com.eactive.apim.portal.djb.apistatus.incident.entity.DjbApistatusIncident; +import com.eactive.apim.portal.djb.apistatus.incident.entity.IncidentKind; +import com.eactive.apim.portal.djb.apistatus.repository.ApiStatusIncidentQueryRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +/** + * API Status 메인 화면 조회 (진행 중 장애 / 점검 사항 / 지난 이슈 / 이슈 상세). + */ +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class ApiStatusQueryService { + + private final ApiStatusIncidentQueryRepository incidentQueryRepository; + private final ApiStatusAssembler assembler; + + /** P3 - 진행 중 장애·지연 */ + public List getActiveIncidents() { + LocalDateTime now = ApiStatusSupport.now(); + List incidents = incidentQueryRepository + .findVisibleOpenIncidents(IncidentKind.DEGRADING, ApiStatusSupport.CLOSED_STATES); + return assembler.toActiveIncidents(incidents, now); + } + + /** P5 - 예정/진행 중 점검 */ + public List getOngoingMaintenance() { + List incidents = incidentQueryRepository + .findVisibleOngoingMaintenance(IncidentKind.MAINTENANCE, ApiStatusSupport.now()); + return assembler.toMaintenanceCards(incidents); + } + + /** P6 - 종결된 이슈 최근 N건 */ + public List getRecentClosedIssues(int size) { + int limit = size <= 0 ? 5 : Math.min(size, 50); + List incidents = incidentQueryRepository + .findVisibleClosedIssues(ApiStatusSupport.CLOSED_STATES, ApiStatusSupport.now(), + PageRequest.of(0, limit)) + .getContent(); + return assembler.toPastIssueCards(incidents); + } + + /** P7 - 이슈 공개 상세 */ + public Optional getIssueDetail(Long incidentId) { + return incidentQueryRepository.findVisibleById(incidentId) + .map(incident -> assembler.toPastIssueCards(java.util.Collections.singletonList(incident)).get(0)); + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/apistatus/service/ApiStatusSupport.java b/src/main/java/com/eactive/apim/portal/djb/apistatus/service/ApiStatusSupport.java new file mode 100644 index 0000000..bd9282d --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/apistatus/service/ApiStatusSupport.java @@ -0,0 +1,132 @@ +package com.eactive.apim.portal.djb.apistatus.service; + +import com.eactive.apim.portal.djb.apistatus.incident.entity.DjbApistatusIncident; +import com.eactive.apim.portal.djb.apistatus.incident.entity.IncidentKind; +import com.eactive.apim.portal.djb.apistatus.incident.entity.IncidentState; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * API Status 조회 서비스 공통 상수/헬퍼. + */ +public final class ApiStatusSupport { + + /** 일자 경계 기준 timezone (OQ-10) */ + public static final ZoneId ZONE = ZoneId.of("Asia/Seoul"); + + /** 90일 가동률/이력 인덱스 기본 조회 일수 */ + public static final int DEFAULT_WINDOW_DAYS = 90; + + /** 장애 종결 상태 */ + public static final List CLOSED_STATES = + Collections.unmodifiableList(Arrays.asList(IncidentState.RESOLVED, IncidentState.CANCELED)); + + public static final String STATUS_NORMAL = "NORMAL"; + public static final String STATUS_DEGRADED = "DEGRADED"; + public static final String STATUS_OUTAGE = "OUTAGE"; + public static final String STATUS_MAINTENANCE = "MAINTENANCE"; + + private ApiStatusSupport() { + } + + public static LocalDateTime now() { + return LocalDateTime.now(ZONE); + } + + /** 지연 건인지 여부 (장애와 구분) */ + public static boolean isDelay(DjbApistatusIncident incident) { + return incident != null && incident.getKind() == IncidentKind.DELAY; + } + + /** 이슈 종류에 대응하는 현재 상태 코드 */ + public static String statusOf(IncidentKind kind) { + if (kind == null) { + return STATUS_NORMAL; + } + switch (kind) { + case INCIDENT: return STATUS_OUTAGE; + case DELAY: return STATUS_DEGRADED; + case MAINTENANCE: return STATUS_MAINTENANCE; + default: return STATUS_NORMAL; + } + } + + /** + * 상태 심각도 순위 (낮을수록 심각). 정렬·상태 병합에 함께 쓴다. + */ + public static int statusRank(String status) { + if (status == null) { + return 9; + } + switch (status) { + case STATUS_OUTAGE: return 0; + case STATUS_MAINTENANCE: return 1; + case STATUS_DEGRADED: return 2; + default: return 3; + } + } + + public static String stateLabel(IncidentState state) { + if (state == null) { + return null; + } + switch (state) { + case INVESTIGATING: return "발생"; + case IDENTIFIED: return "원인 확인"; + case MONITORING: return "모니터링"; + case RESOLVED: return "해소"; + case CANCELED: return "취소"; + default: return state.name(); + } + } + + public static String statusLabel(String status) { + if (status == null) { + return null; + } + switch (status) { + case STATUS_NORMAL: return "정상"; + case STATUS_DEGRADED: return "지연"; + case STATUS_OUTAGE: return "장애"; + case STATUS_MAINTENANCE: return "점검"; + default: return status; + } + } + + /** + * 두 시각 사이의 분. 종료가 없거나 역순이면 0. + */ + public static long minutesBetween(LocalDateTime from, LocalDateTime to) { + if (from == null || to == null || !to.isAfter(from)) { + return 0L; + } + return Duration.between(from, to).toMinutes(); + } + + /** + * "N분 전" 형태의 상대 시간 표기. + */ + public static String relativeTime(LocalDateTime past, LocalDateTime now) { + if (past == null || now == null) { + return null; + } + long minutes = Duration.between(past, now).toMinutes(); + if (minutes < 1) { + return "방금 전"; + } + if (minutes < 60) { + return minutes + "분 전"; + } + long hours = minutes / 60; + if (hours < 24) { + long restMinutes = minutes % 60; + return restMinutes == 0 ? hours + "시간 전" : hours + "시간 " + restMinutes + "분 전"; + } + return (hours / 24) + "일 전"; + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/apistatus/service/ApiStatusUptimeService.java b/src/main/java/com/eactive/apim/portal/djb/apistatus/service/ApiStatusUptimeService.java new file mode 100644 index 0000000..c51cf68 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/apistatus/service/ApiStatusUptimeService.java @@ -0,0 +1,236 @@ +package com.eactive.apim.portal.djb.apistatus.service; + +import com.eactive.apim.portal.djb.apistatus.dto.DailyStatDTO; +import com.eactive.apim.portal.djb.apistatus.incident.entity.DjbApistatusIncident; +import com.eactive.apim.portal.djb.apistatus.incident.entity.DjbApistatusIncidentApi; +import com.eactive.apim.portal.djb.apistatus.incident.entity.IncidentKind; +import com.eactive.apim.portal.djb.apistatus.incident.repository.DjbApistatusIncidentApiRepository; +import com.eactive.apim.portal.djb.apistatus.repository.ApiStatusIncidentQueryRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 90일 가동률 집계. 일별 캐시 테이블(DJB_APISTATUS_DAILY_STAT) 없이 이슈 구간을 그때그때 합산한다. + * + *

이슈 건수가 연 1천 건 미만이므로 90일 구간을 메모리에서 합산해도 부담이 없다. + * 같은 시간대에 겹치는 이슈는 구간 합집합으로 계산해 중복 차감을 막는다.

+ */ +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class ApiStatusUptimeService { + + private final ApiStatusIncidentQueryRepository incidentQueryRepository; + private final DjbApistatusIncidentApiRepository incidentApiRepository; + + /** P2 - 90일 가동률 */ + public List getDailyStats(int days) { + int windowDays = days <= 0 ? ApiStatusSupport.DEFAULT_WINDOW_DAYS : Math.min(days, 365); + LocalDateTime now = ApiStatusSupport.now(); + LocalDate today = now.toLocalDate(); + LocalDate from = today.minusDays(windowDays - 1L); + + List incidents = incidentQueryRepository + .findVisibleOverlapping(from.atStartOfDay(), today.plusDays(1).atStartOfDay()); + + List result = new ArrayList<>(); + for (int offset = 0; offset < windowDays; offset++) { + LocalDate date = from.plusDays(offset); + result.add(buildDailyStat(date, incidents, now)); + } + return result; + } + + /** + * API 별 90일 가동률. 장애(INCIDENT) 구간만 차감한다. + */ + public Map getApiUptimeRatios(Set apiIds, int days) { + if (apiIds == null || apiIds.isEmpty()) { + return Collections.emptyMap(); + } + int windowDays = days <= 0 ? ApiStatusSupport.DEFAULT_WINDOW_DAYS : Math.min(days, 365); + LocalDateTime now = ApiStatusSupport.now(); + LocalDateTime windowStart = now.toLocalDate().minusDays(windowDays - 1L).atStartOfDay(); + + // 장애와 지연 모두 서비스 저하이므로 가동률에서 차감한다. 점검은 계획된 작업이라 제외. + List incidents = incidentQueryRepository + .findVisibleOverlapping(windowStart, now.toLocalDate().plusDays(1).atStartOfDay()).stream() + .filter(incident -> incident.getKind() != null && incident.getKind().isDegrading()) + .collect(Collectors.toList()); + + Map> intervalsByApi = new HashMap<>(); + if (!incidents.isEmpty()) { + Map byId = incidents.stream() + .collect(Collectors.toMap(DjbApistatusIncident::getIncidentId, incident -> incident)); + + for (DjbApistatusIncidentApi api : + incidentApiRepository.findByIncidentIdInOrderByIncidentIdAscApiIdAsc(byId.keySet())) { + if (!apiIds.contains(api.getApiId())) { + continue; + } + DjbApistatusIncident incident = byId.get(api.getIncidentId()); + LocalDateTime start = max(incident.getStartedAt(), windowStart); + // 개별 API 복구 시각이 있으면 그 시점까지만 장애로 본다 + LocalDateTime end = min(firstNonNull(api.getRecoveredAt(), incident.getEndAt(), now), now); + long minutes = ApiStatusSupport.minutesBetween(start, end); + if (minutes <= 0) { + continue; + } + intervalsByApi.computeIfAbsent(api.getApiId(), key -> new ArrayList<>()) + .add(new long[]{toEpochMinute(start), toEpochMinute(end)}); + } + } + + long totalMinutes = Math.max(1L, ApiStatusSupport.minutesBetween(windowStart, now)); + Map ratios = new HashMap<>(); + for (String apiId : apiIds) { + long down = unionMinutes(intervalsByApi.get(apiId)); + double ratio = (double) (totalMinutes - Math.min(down, totalMinutes)) / totalMinutes; + ratios.put(apiId, round4(ratio)); + } + return ratios; + } + + private DailyStatDTO buildDailyStat(LocalDate date, List incidents, LocalDateTime now) { + LocalDateTime dayStart = date.atStartOfDay(); + LocalDateTime dayEnd = min(date.plusDays(1).atStartOfDay(), now); + + DailyStatDTO dto = new DailyStatDTO(); + dto.setStatDate(date); + + if (!dayEnd.isAfter(dayStart)) { + // 아직 시작되지 않은 날짜 (오늘 자정 직후 등) + dto.setUptimeRatio(1.0d); + dto.setStatus(ApiStatusSupport.STATUS_NORMAL); + return dto; + } + + // 가동률은 장애+지연을 합쳐 차감하므로 합집합 계산용 리스트를 따로 둔다. + // (장애와 지연을 나눠 union 한 뒤 더하면 겹치는 구간이 이중 차감된다) + List incidentIntervals = new ArrayList<>(); + List outageIntervals = new ArrayList<>(); + List delayIntervals = new ArrayList<>(); + List maintenanceIntervals = new ArrayList<>(); + Set seen = new HashSet<>(); + + for (DjbApistatusIncident incident : incidents) { + LocalDateTime start = max(incident.getStartedAt(), dayStart); + LocalDateTime end = min(firstNonNull(incident.getEndAt(), now), dayEnd); + if (!end.isAfter(start)) { + continue; + } + long[] interval = new long[]{toEpochMinute(start), toEpochMinute(end)}; + if (incident.getKind() == IncidentKind.MAINTENANCE) { + maintenanceIntervals.add(interval); + } else { + incidentIntervals.add(interval); + if (incident.getKind() == IncidentKind.DELAY) { + delayIntervals.add(interval); + } else { + outageIntervals.add(interval); + } + } + if (seen.add(incident.getIncidentId())) { + dto.getIssues().add(new DailyStatDTO.IssueRefDTO(incident.getIncidentId(), + incident.getKind() == null ? null : incident.getKind().name(), + incident.getTitle())); + } + } + + long dayMinutes = Math.max(1L, ApiStatusSupport.minutesBetween(dayStart, dayEnd)); + long incidentMinutes = unionMinutes(incidentIntervals); + long outageMinutes = unionMinutes(outageIntervals); + long delayMinutes = unionMinutes(delayIntervals); + long maintenanceMinutes = unionMinutes(maintenanceIntervals); + + dto.setIncidentMinutes(incidentMinutes); + dto.setDelayMinutes(delayMinutes); + dto.setMaintenanceMinutes(maintenanceMinutes); + + long downMinutes = Math.min(dayMinutes, incidentMinutes + maintenanceMinutes); + dto.setUptimeRatio(round4((double) (dayMinutes - downMinutes) / dayMinutes)); + + // 색상은 심각한 순으로 하나만 고른다 (장애 > 점검 > 지연) + if (outageMinutes > 0) { + dto.setStatus(ApiStatusSupport.STATUS_OUTAGE); + } else if (maintenanceMinutes > 0) { + dto.setStatus(ApiStatusSupport.STATUS_MAINTENANCE); + } else if (delayMinutes > 0) { + dto.setStatus(ApiStatusSupport.STATUS_DEGRADED); + } else { + dto.setStatus(ApiStatusSupport.STATUS_NORMAL); + } + return dto; + } + + /** + * 겹치는 구간을 합쳐 총 분을 구한다. + */ + private long unionMinutes(List intervals) { + if (intervals == null || intervals.isEmpty()) { + return 0L; + } + intervals.sort((left, right) -> Long.compare(left[0], right[0])); + + long total = 0L; + long currentStart = intervals.get(0)[0]; + long currentEnd = intervals.get(0)[1]; + + for (int index = 1; index < intervals.size(); index++) { + long[] interval = intervals.get(index); + if (interval[0] > currentEnd) { + total += currentEnd - currentStart; + currentStart = interval[0]; + currentEnd = interval[1]; + } else if (interval[1] > currentEnd) { + currentEnd = interval[1]; + } + } + total += currentEnd - currentStart; + return total; + } + + private long toEpochMinute(LocalDateTime dateTime) { + return dateTime.atZone(ApiStatusSupport.ZONE).toEpochSecond() / 60L; + } + + private LocalDateTime max(LocalDateTime left, LocalDateTime right) { + if (left == null) { + return right; + } + return left.isAfter(right) ? left : right; + } + + private LocalDateTime min(LocalDateTime left, LocalDateTime right) { + if (left == null) { + return right; + } + return left.isBefore(right) ? left : right; + } + + private LocalDateTime firstNonNull(LocalDateTime... candidates) { + for (LocalDateTime candidate : candidates) { + if (candidate != null) { + return candidate; + } + } + return null; + } + + private double round4(double value) { + double clamped = Math.max(0.0d, Math.min(1.0d, value)); + return Math.round(clamped * 10000.0d) / 10000.0d; + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/apistatus/service/MyApiStatusQueryService.java b/src/main/java/com/eactive/apim/portal/djb/apistatus/service/MyApiStatusQueryService.java new file mode 100644 index 0000000..7fb3660 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/apistatus/service/MyApiStatusQueryService.java @@ -0,0 +1,94 @@ +package com.eactive.apim.portal.djb.apistatus.service; + +import com.eactive.apim.portal.app.entity.Credential; +import com.eactive.apim.portal.app.repository.CredentialRepository; +import com.eactive.apim.portal.apispec.entity.ApiSpecInfo; +import com.eactive.apim.portal.common.user.PortalAuthenticatedUser; +import com.eactive.apim.portal.common.util.SecurityUtil; +import com.eactive.apim.portal.djb.apistatus.dto.ApiCurrentStatusDTO; +import com.eactive.apim.portal.djb.apistatus.dto.MyApiStatusDTO; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +/** + * 로그인 사용자가 이용 중인 API 의 현재 상태 (P4). + * + *

대상 API 는 소속 기관이 발급받은 앱(Credential)의 API 목록(ptl_credential_api)이다. + * 상태 판정은 {@link ApiCurrentStatusService} 에 위임해 API 상세 화면과 기준을 맞춘다.

+ */ +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class MyApiStatusQueryService { + + private final CredentialRepository credentialRepository; + private final ApiCurrentStatusService apiCurrentStatusService; + private final ApiStatusUptimeService uptimeService; + private final ApiStatusCatalogService catalogService; + + public List getMyApiStatuses() { + PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser(); + if (user == null || user.getPortalOrg() == null || StringUtils.isBlank(user.getPortalOrg().getId())) { + return Collections.emptyList(); + } + + Map myApis = collectMyApis(user.getPortalOrg().getId()); + if (myApis.isEmpty()) { + return Collections.emptyList(); + } + + Map statuses = apiCurrentStatusService.resolveStatuses(myApis.keySet()); + Map uptimes = + uptimeService.getApiUptimeRatios(myApis.keySet(), catalogService.getWindowDays()); + + List result = new ArrayList<>(); + for (Map.Entry entry : myApis.entrySet()) { + String apiId = entry.getKey(); + ApiCurrentStatusDTO status = statuses.get(apiId); + + MyApiStatusDTO dto = new MyApiStatusDTO(); + dto.setApiId(apiId); + // 기관이 발급받은 앱 기준 목록이므로 API 명은 credential 쪽 값을 그대로 쓴다 + dto.setApiName(entry.getValue()); + dto.setCurrentStatus(status == null ? ApiStatusSupport.STATUS_NORMAL : status.getCurrentStatus()); + dto.setCurrentStatusLabel(ApiStatusSupport.statusLabel(dto.getCurrentStatus())); + dto.setActiveIncidentId(status == null ? null : status.getActiveIncidentId()); + dto.setLastIncidentAt(status == null ? null : status.getLastIncidentAt()); + dto.setUptime90d(uptimes.getOrDefault(apiId, 1.0d)); + result.add(dto); + } + + result.sort(Comparator + .comparingInt((MyApiStatusDTO dto) -> ApiStatusSupport.statusRank(dto.getCurrentStatus())) + .thenComparing(MyApiStatusDTO::getApiName, Comparator.nullsLast(Comparator.naturalOrder()))); + return result; + } + + /** + * 기관이 보유한 앱들의 API 목록 (중복 제거, API 명 기준 정렬) + */ + private Map collectMyApis(String orgId) { + Map myApis = new TreeMap<>(); + for (Credential credential : credentialRepository.findAllByOrgid(orgId)) { + if (credential.getApiList() == null) { + continue; + } + for (ApiSpecInfo api : credential.getApiList()) { + if (StringUtils.isBlank(api.getApiId())) { + continue; + } + myApis.put(api.getApiId(), StringUtils.defaultIfBlank(api.getApiName(), api.getApiId())); + } + } + return myApis; + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/community/qna/comment/controller/InquiryCommentController.java b/src/main/java/com/eactive/apim/portal/djb/community/qna/comment/controller/InquiryCommentController.java new file mode 100644 index 0000000..ba82247 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/community/qna/comment/controller/InquiryCommentController.java @@ -0,0 +1,74 @@ +package com.eactive.apim.portal.djb.community.qna.comment.controller; + +import com.eactive.apim.portal.common.user.PortalAuthenticatedUser; +import com.eactive.apim.portal.common.util.SecurityUtil; +import com.eactive.apim.portal.djb.community.qna.comment.dto.InquiryCommentCreateRequest; +import com.eactive.apim.portal.djb.community.qna.comment.dto.InquiryCommentDTO; +import com.eactive.apim.portal.djb.community.qna.comment.service.InquiryCommentFacade; +import com.eactive.apim.portal.djb.community.qna.exception.InquiryClosedException; +import com.eactive.apim.portal.djb.community.qna.exception.InquiryCommentNotOwnedException; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.annotation.Secured; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.validation.Valid; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/djb/inquiry") +@Secured("ROLE_INQUIRY") +@RequiredArgsConstructor +public class InquiryCommentController { + + private final InquiryCommentFacade inquiryCommentFacade; + + @GetMapping("/{inquiryId}/comments") + public ResponseEntity> list(@PathVariable String inquiryId) { + PortalAuthenticatedUser current = SecurityUtil.getPortalAuthenticatedUser(); + return ResponseEntity.ok(inquiryCommentFacade.getComments(inquiryId, current)); + } + + @PostMapping("/{inquiryId}/comments") + public ResponseEntity create(@PathVariable String inquiryId, + @Valid @RequestBody InquiryCommentCreateRequest request) { + PortalAuthenticatedUser current = SecurityUtil.getPortalAuthenticatedUser(); + InquiryCommentDTO created = inquiryCommentFacade.createUserComment( + inquiryId, request.getContent(), request.getVisibility(), current); + return ResponseEntity.ok(created); + } + + @DeleteMapping("/comments/{commentId}") + public ResponseEntity delete(@PathVariable String commentId) { + PortalAuthenticatedUser current = SecurityUtil.getPortalAuthenticatedUser(); + inquiryCommentFacade.deleteOwnComment(commentId, current); + return ResponseEntity.noContent().build(); + } + + @ExceptionHandler(InquiryClosedException.class) + public ResponseEntity> handleClosed(InquiryClosedException ex) { + return ResponseEntity.status(HttpStatus.CONFLICT).body(errorBody("INQUIRY_CLOSED", ex.getMessage())); + } + + @ExceptionHandler(InquiryCommentNotOwnedException.class) + public ResponseEntity> handleNotOwned(InquiryCommentNotOwnedException ex) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).body(errorBody("COMMENT_NOT_OWNED", ex.getMessage())); + } + + private Map errorBody(String code, String message) { + Map body = new HashMap<>(); + body.put("code", code); + body.put("message", message); + return body; + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/community/qna/comment/dto/InquiryCommentCreateRequest.java b/src/main/java/com/eactive/apim/portal/djb/community/qna/comment/dto/InquiryCommentCreateRequest.java new file mode 100644 index 0000000..5d92367 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/community/qna/comment/dto/InquiryCommentCreateRequest.java @@ -0,0 +1,17 @@ +package com.eactive.apim.portal.djb.community.qna.comment.dto; + +import lombok.Data; +import org.hibernate.validator.constraints.NotBlank; + +import javax.validation.constraints.Size; + +@Data +public class InquiryCommentCreateRequest { + + @NotBlank(message = "댓글 내용을 입력해주세요.") + @Size(max = 2000, message = "댓글은 2000자를 초과할 수 없습니다.") + private String content; + + /** 공개범위(ALL=공개, PRIVATE=비공개). 미지정 시 공개. */ + private String visibility; +} diff --git a/src/main/java/com/eactive/apim/portal/djb/community/qna/comment/dto/InquiryCommentDTO.java b/src/main/java/com/eactive/apim/portal/djb/community/qna/comment/dto/InquiryCommentDTO.java new file mode 100644 index 0000000..c40b3de --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/community/qna/comment/dto/InquiryCommentDTO.java @@ -0,0 +1,38 @@ +package com.eactive.apim.portal.djb.community.qna.comment.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class InquiryCommentDTO { + + private String id; + + private String content; + + private String writerId; + + private String writerName; + + private String adminYn; + + private LocalDateTime createdDate; + + private boolean deletable; + + /** 공개범위(ALL/PRIVATE). */ + private String visibility; + + /** 비공개 댓글 여부 — 허용 독자에게는 "비공개" 태그로 표시. */ + private boolean privateComment; + + /** 비공개 댓글이지만 열람 권한이 없어 "비공개 댓글"로만 노출되는지 여부. */ + private boolean privatePlaceholder; +} diff --git a/src/main/java/com/eactive/apim/portal/djb/community/qna/comment/repository/InquiryCommentRepository.java b/src/main/java/com/eactive/apim/portal/djb/community/qna/comment/repository/InquiryCommentRepository.java new file mode 100644 index 0000000..6b40e5a --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/community/qna/comment/repository/InquiryCommentRepository.java @@ -0,0 +1,20 @@ +package com.eactive.apim.portal.djb.community.qna.comment.repository; + +import com.eactive.apim.portal.qna.entity.InquiryComment; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.Collection; +import java.util.List; + +public interface InquiryCommentRepository extends JpaRepository { + + List findByInquiry_IdAndDelYnOrderByCreatedDateAsc(String inquiryId, String delYn); + + @Query("select c.inquiry.id, count(c) from InquiryComment c" + + " where c.inquiry.id in :inquiryIds and c.delYn = :delYn" + + " group by c.inquiry.id") + List countActiveGroupByInquiry(@Param("inquiryIds") Collection inquiryIds, + @Param("delYn") String delYn); +} diff --git a/src/main/java/com/eactive/apim/portal/djb/community/qna/comment/repository/UserInfoRepository.java b/src/main/java/com/eactive/apim/portal/djb/community/qna/comment/repository/UserInfoRepository.java new file mode 100644 index 0000000..58525ab --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/community/qna/comment/repository/UserInfoRepository.java @@ -0,0 +1,15 @@ +package com.eactive.apim.portal.djb.community.qna.comment.repository; + +import com.eactive.apim.portal.user.entity.UserInfo; +import com.eactive.eai.data.jpa.BaseRepository; +import com.eactive.eai.rms.data.EMSDataSource; + +/** + * 댓글 작성자(내부 직원) 표시용 TSEAIRM02 조회. + * + *

역할 기반 알림 대상 조회는 + * {@code com.eactive.apim.portal.djb.swing.repository.SwingStaffRepository} 로 분리했다.

+ */ +@EMSDataSource +public interface UserInfoRepository extends BaseRepository { +} diff --git a/src/main/java/com/eactive/apim/portal/djb/community/qna/comment/service/InquiryCommentFacade.java b/src/main/java/com/eactive/apim/portal/djb/community/qna/comment/service/InquiryCommentFacade.java new file mode 100644 index 0000000..b7d4cca --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/community/qna/comment/service/InquiryCommentFacade.java @@ -0,0 +1,19 @@ +package com.eactive.apim.portal.djb.community.qna.comment.service; + +import com.eactive.apim.portal.common.user.PortalAuthenticatedUser; +import com.eactive.apim.portal.djb.community.qna.comment.dto.InquiryCommentDTO; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +public interface InquiryCommentFacade { + + List getComments(String inquiryId, PortalAuthenticatedUser current); + + InquiryCommentDTO createUserComment(String inquiryId, String content, String visibility, PortalAuthenticatedUser current); + + void deleteOwnComment(String commentId, PortalAuthenticatedUser current); + + Map countActiveByInquiryIds(Collection inquiryIds); +} diff --git a/src/main/java/com/eactive/apim/portal/djb/community/qna/comment/service/InquiryCommentFacadeImpl.java b/src/main/java/com/eactive/apim/portal/djb/community/qna/comment/service/InquiryCommentFacadeImpl.java new file mode 100644 index 0000000..b5ea437 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/community/qna/comment/service/InquiryCommentFacadeImpl.java @@ -0,0 +1,261 @@ +package com.eactive.apim.portal.djb.community.qna.comment.service; + +import com.eactive.apim.portal.apps.community.qna.service.InquiryService; +import com.eactive.apim.portal.common.exception.NotFoundException; +import com.eactive.apim.portal.common.user.PortalAuthenticatedUser; +import com.eactive.apim.portal.common.util.StringMaskingUtil; +import com.eactive.apim.portal.djb.community.qna.comment.dto.InquiryCommentDTO; +import com.eactive.apim.portal.djb.community.qna.comment.repository.InquiryCommentRepository; +import com.eactive.apim.portal.djb.community.qna.comment.repository.UserInfoRepository; +import com.eactive.apim.portal.djb.community.qna.constant.DjbInquiryStatus; +import com.eactive.apim.portal.djb.swing.SwingNotifier; +import com.eactive.apim.portal.djb.community.qna.support.InquiryCommentPermissionChecker; +import com.eactive.apim.portal.portalorg.entity.PortalOrg; +import com.eactive.apim.portal.portaluser.entity.PortalUser; +import com.eactive.apim.portal.portaluser.entity.PortalUserEnums; +import com.eactive.apim.portal.portaluser.repository.PortalUserRepository; +import com.eactive.apim.portal.qna.entity.Inquiry; +import com.eactive.apim.portal.qna.entity.InquiryComment; +import com.eactive.apim.portal.qna.entity.VisibilityScope; +import com.eactive.apim.portal.template.entity.MessageCode; +import com.eactive.apim.portal.user.entity.UserInfo; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +@Slf4j +@Service +@Transactional +@RequiredArgsConstructor +public class InquiryCommentFacadeImpl implements InquiryCommentFacade { + + private static final String ADMIN_N = "N"; + private static final String DEL_N = "N"; + private static final String UNKNOWN_WRITER = "(알 수 없음)"; + + private final InquiryService inquiryService; + private final InquiryCommentService commentService; + private final InquiryCommentRepository commentRepository; + private final InquiryCommentPermissionChecker permissionChecker; + private final SwingNotifier swingNotifier; + private final PortalUserRepository portalUserRepository; + private final UserInfoRepository userInfoRepository; + + @Override + @Transactional(readOnly = true) + public List getComments(String inquiryId, PortalAuthenticatedUser current) { + inquiryService.getAccessibleInquiry(current, inquiryId); + List comments = commentService.findActiveByInquiryId(inquiryId); + Map writers = resolveWriters(comments); + return comments.stream() + .map(c -> toDto(c, current, writers)) + .collect(Collectors.toList()); + } + + @Override + public InquiryCommentDTO createUserComment(String inquiryId, String content, String visibility, PortalAuthenticatedUser current) { + Inquiry inquiry = inquiryService.getAccessibleInquiry(current, inquiryId); + permissionChecker.assertWritable(inquiry); + // 댓글은 공개(ALL)/비공개(PRIVATE) 2단계만 지원 + VisibilityScope scope = VisibilityScope.PRIVATE == VisibilityScope.fromString(visibility, VisibilityScope.ALL) + ? VisibilityScope.PRIVATE : VisibilityScope.ALL; + InquiryComment saved = commentService.create(inquiry, content, ADMIN_N, scope); + publishAdminNotification(inquiry, saved, current); + Map writers = new HashMap<>(); + writers.put(current.getId(), currentWriter(current)); + return toDto(saved, current, writers); + } + + @Override + @Transactional(readOnly = true) + public Map countActiveByInquiryIds(Collection inquiryIds) { + if (inquiryIds == null || inquiryIds.isEmpty()) { + return Collections.emptyMap(); + } + List rows = commentRepository.countActiveGroupByInquiry(inquiryIds, DEL_N); + Map result = new HashMap<>(); + for (Object[] row : rows) { + String inquiryId = (String) row[0]; + Long count = ((Number) row[1]).longValue(); + result.put(inquiryId, count); + } + return result; + } + + @Override + public void deleteOwnComment(String commentId, PortalAuthenticatedUser current) { + InquiryComment comment = commentService.findById(commentId) + .orElseThrow(() -> new NotFoundException("댓글을 찾을 수 없습니다. id=" + commentId)); + inquiryService.getAccessibleInquiry(current, comment.getInquiry().getId()); + permissionChecker.assertDeletable(comment, current); + commentService.softDelete(commentId); + } + + private void publishAdminNotification(Inquiry inquiry, InquiryComment comment, PortalAuthenticatedUser current) { + Map params = new HashMap<>(); + params.put("inquiryId", inquiry.getId()); + params.put("inquirySubject", inquiry.getInquirySubject()); + params.put("commentContent", comment.getCommentDetail()); + params.put("writerName", current.getUserName()); + swingNotifier.notifyPortalAdmins(MessageCode.INQUIRY_COMMENT_CREATED, params); + } + + private Map resolveWriters(List comments) { + Set ids = new HashSet<>(); + for (InquiryComment c : comments) { + String createdBy = c.getCreatedBy(); + if (createdBy != null && !createdBy.isEmpty()) { + ids.add(createdBy); + } + } + Map map = new HashMap<>(); + for (String id : ids) { + Writer writer = lookupWriter(id); + if (writer != null && writer.name != null && !writer.name.isEmpty()) { + map.put(id, writer); + } else { + log.warn("댓글 작성자명 조회 실패 — createdBy={}, isUuid={}", id, isUuid(id)); + } + } + return map; + } + + private Writer lookupWriter(String createdBy) { + if (isUuid(createdBy)) { + PortalUser portalUser = portalUserRepository.findById(createdBy).orElse(null); + if (portalUser != null && portalUser.getUserName() != null && !portalUser.getUserName().isEmpty()) { + return toWriter(portalUser); + } + UserInfo userInfo = userInfoRepository.findById(createdBy).orElse(null); + return userInfo != null ? new Writer(userInfo.getUsername(), null, null) : null; + } + UserInfo userInfo = userInfoRepository.findById(createdBy).orElse(null); + if (userInfo != null && userInfo.getUsername() != null && !userInfo.getUsername().isEmpty()) { + return new Writer(userInfo.getUsername(), null, null); + } + PortalUser portalUser = portalUserRepository.findById(createdBy).orElse(null); + return portalUser != null ? toWriter(portalUser) : null; + } + + private Writer toWriter(PortalUser portalUser) { + PortalOrg org = portalUser.getPortalOrg(); + return new Writer(portalUser.getUserName(), + org != null ? org.getId() : null, + org != null ? org.getOrgName() : null); + } + + private Writer currentWriter(PortalAuthenticatedUser current) { + PortalOrg org = current.getPortalOrg(); + return new Writer(current.getUserName(), + org != null ? org.getId() : null, + org != null ? org.getOrgName() : null); + } + + private boolean isUuid(String value) { + if (value == null || value.length() != 36) { + return false; + } + try { + UUID.fromString(value); + return true; + } catch (IllegalArgumentException e) { + return false; + } + } + + private InquiryCommentDTO toDto(InquiryComment entity, PortalAuthenticatedUser current, + Map writers) { + String writerId = entity.getCreatedBy(); + boolean isAdmin = "Y".equals(entity.getAdminYn()); + boolean isPrivate = entity.isPrivate(); + boolean owner = writerId != null && writerId.equals(current.getId()); + Writer writer = writerId != null ? writers.get(writerId) : null; + + // 비공개 댓글 열람 권한: 관리자 댓글은 대상 아님, 작성자 본인 또는 같은 법인 관리자만 + boolean canSee = owner || canSeePrivate(current, writer); + String scopeName = entity.getVisibility() != null ? entity.getVisibility().name() : VisibilityScope.ALL.name(); + + if (isPrivate && !canSee) { + // 열람 권한 없는 비공개 댓글 → "비공개 댓글"로만 노출, 작성자 신원 숨김 + return InquiryCommentDTO.builder() + .id(entity.getId()) + .content("비공개 댓글") + .writerId(null) + .writerName("비공개") + .adminYn(entity.getAdminYn()) + .createdDate(entity.getCreatedDate()) + .deletable(false) + .visibility(scopeName) + .privateComment(true) + .privatePlaceholder(true) + .build(); + } + + boolean deletable = owner + && entity.getInquiry() != null + && !DjbInquiryStatus.isClosed(entity.getInquiry().getInquiryStatus()); + + String writerName; + if (isAdmin) { + // 관리자 댓글은 실명 대신 "관리자"로만 표기 (신원 비노출) + writerName = "관리자"; + } else { + String base = writer != null ? writer.name : null; + String masked = StringMaskingUtil.maskName(base, writerId, current.getId()); + if (masked == null || masked.isEmpty()) { + masked = UNKNOWN_WRITER; + } + writerName = (writer != null && writer.orgName != null && !writer.orgName.isEmpty()) + ? masked + " (" + writer.orgName + ")" + : masked; + } + return InquiryCommentDTO.builder() + .id(entity.getId()) + .content(entity.getCommentDetail()) + .writerId(writerId) + .writerName(writerName) + .adminYn(entity.getAdminYn()) + .createdDate(entity.getCreatedDate()) + .deletable(deletable) + .visibility(scopeName) + .privateComment(isPrivate) + .privatePlaceholder(false) + .build(); + } + + /** 비공개 댓글을 볼 수 있는 같은 법인 관리자 여부. */ + private boolean canSeePrivate(PortalAuthenticatedUser current, Writer writer) { + if (writer == null || writer.orgId == null) { + return false; + } + if (current.getRoleCode() != PortalUserEnums.RoleCode.ROLE_CORP_MANAGER) { + return false; + } + PortalOrg org = current.getPortalOrg(); + return org != null && org.getId() != null && org.getId().equals(writer.orgId); + } + + /** 댓글 작성자 정보(이름/법인). */ + private static final class Writer { + private final String name; + private final String orgId; + private final String orgName; + + private Writer(String name, String orgId, String orgName) { + this.name = name; + this.orgId = orgId; + this.orgName = orgName; + } + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/community/qna/comment/service/InquiryCommentService.java b/src/main/java/com/eactive/apim/portal/djb/community/qna/comment/service/InquiryCommentService.java new file mode 100644 index 0000000..f2f226c --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/community/qna/comment/service/InquiryCommentService.java @@ -0,0 +1,48 @@ +package com.eactive.apim.portal.djb.community.qna.comment.service; + +import com.eactive.apim.portal.common.exception.NotFoundException; +import com.eactive.apim.portal.djb.community.qna.comment.repository.InquiryCommentRepository; +import com.eactive.apim.portal.qna.entity.Inquiry; +import com.eactive.apim.portal.qna.entity.InquiryComment; +import com.eactive.apim.portal.qna.entity.VisibilityScope; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Optional; + +@Service +@Transactional +@RequiredArgsConstructor +public class InquiryCommentService { + + private static final String DEL_N = "N"; + + private final InquiryCommentRepository repository; + + @Transactional(readOnly = true) + public List findActiveByInquiryId(String inquiryId) { + return repository.findByInquiry_IdAndDelYnOrderByCreatedDateAsc(inquiryId, DEL_N); + } + + public InquiryComment create(Inquiry inquiry, String content, String adminYn, VisibilityScope visibility) { + InquiryComment comment = new InquiryComment(); + comment.setInquiry(inquiry); + comment.setCommentDetail(content); + comment.setAdminYn(adminYn); + comment.setDelYn(DEL_N); + comment.setVisibility(visibility != null ? visibility : VisibilityScope.ALL); + return repository.save(comment); + } + + public Optional findById(String commentId) { + return repository.findById(commentId); + } + + public void softDelete(String commentId) { + InquiryComment comment = repository.findById(commentId) + .orElseThrow(() -> new NotFoundException("댓글을 찾을 수 없습니다. id=" + commentId)); + comment.markDeleted(); + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/community/qna/constant/DjbInquiryStatus.java b/src/main/java/com/eactive/apim/portal/djb/community/qna/constant/DjbInquiryStatus.java new file mode 100644 index 0000000..d26e2f7 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/community/qna/constant/DjbInquiryStatus.java @@ -0,0 +1,49 @@ +package com.eactive.apim.portal.djb.community.qna.constant; + +public final class DjbInquiryStatus { + + public static final String PENDING = "PENDING"; + public static final String REVIEWING = "REVIEWING"; + public static final String RESPONDED = "RESPONDED"; + public static final String CLOSED = "CLOSED"; + + private DjbInquiryStatus() { + } + + public static boolean isClosed(String status) { + return CLOSED.equals(status); + } + + /** + * 상태 코드에 대응하는 사용자 노출용 한글명을 반환한다. + * (미매칭/null은 대기 상태로 간주) + */ + public static String displayName(String status) { + if (RESPONDED.equals(status)) { + return "답변완료"; + } + if (CLOSED.equals(status)) { + return "종료"; + } + if (REVIEWING.equals(status)) { + return "검토중"; + } + return "답변대기"; // PENDING 및 기본값 + } + + /** + * 상태 코드에 대응하는 배지 CSS modifier 클래스를 반환한다. + */ + public static String badgeClass(String status) { + if (RESPONDED.equals(status)) { + return "inquiry-status-badge--completed"; + } + if (CLOSED.equals(status)) { + return "inquiry-status-badge--closed"; + } + if (REVIEWING.equals(status)) { + return "inquiry-status-badge--reviewing"; + } + return "inquiry-status-badge--pending"; // PENDING 및 기본값 + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/community/qna/exception/InquiryClosedException.java b/src/main/java/com/eactive/apim/portal/djb/community/qna/exception/InquiryClosedException.java new file mode 100644 index 0000000..4420717 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/community/qna/exception/InquiryClosedException.java @@ -0,0 +1,12 @@ +package com.eactive.apim.portal.djb.community.qna.exception; + +public class InquiryClosedException extends RuntimeException { + + public InquiryClosedException() { + super("종료된 문의에는 댓글을 작성/삭제할 수 없습니다."); + } + + public InquiryClosedException(String message) { + super(message); + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/community/qna/exception/InquiryCommentNotOwnedException.java b/src/main/java/com/eactive/apim/portal/djb/community/qna/exception/InquiryCommentNotOwnedException.java new file mode 100644 index 0000000..086d02f --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/community/qna/exception/InquiryCommentNotOwnedException.java @@ -0,0 +1,12 @@ +package com.eactive.apim.portal.djb.community.qna.exception; + +public class InquiryCommentNotOwnedException extends RuntimeException { + + public InquiryCommentNotOwnedException() { + super("본인이 작성한 댓글만 삭제할 수 있습니다."); + } + + public InquiryCommentNotOwnedException(String message) { + super(message); + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/community/qna/support/InquiryCommentPermissionChecker.java b/src/main/java/com/eactive/apim/portal/djb/community/qna/support/InquiryCommentPermissionChecker.java new file mode 100644 index 0000000..e9f18a7 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/community/qna/support/InquiryCommentPermissionChecker.java @@ -0,0 +1,45 @@ +package com.eactive.apim.portal.djb.community.qna.support; + +import com.eactive.apim.portal.common.user.PortalAuthenticatedUser; +import com.eactive.apim.portal.djb.community.qna.constant.DjbInquiryStatus; +import com.eactive.apim.portal.djb.community.qna.exception.InquiryClosedException; +import com.eactive.apim.portal.djb.community.qna.exception.InquiryCommentNotOwnedException; +import com.eactive.apim.portal.qna.entity.Inquiry; +import com.eactive.apim.portal.qna.entity.InquiryComment; +import org.springframework.stereotype.Component; + +@Component +public class InquiryCommentPermissionChecker { + + public boolean canWrite(Inquiry inquiry) { + return inquiry != null && !DjbInquiryStatus.isClosed(inquiry.getInquiryStatus()); + } + + public boolean canDelete(InquiryComment comment, PortalAuthenticatedUser current) { + if (comment == null || current == null) { + return false; + } + boolean owner = comment.getCreatedBy() != null + && comment.getCreatedBy().equals(current.getId()); + boolean notClosed = comment.getInquiry() == null + || !DjbInquiryStatus.isClosed(comment.getInquiry().getInquiryStatus()); + return owner && notClosed; + } + + public void assertWritable(Inquiry inquiry) { + if (!canWrite(inquiry)) { + throw new InquiryClosedException(); + } + } + + public void assertDeletable(InquiryComment comment, PortalAuthenticatedUser current) { + if (comment.getInquiry() != null + && DjbInquiryStatus.isClosed(comment.getInquiry().getInquiryStatus())) { + throw new InquiryClosedException(); + } + if (comment.getCreatedBy() == null + || !comment.getCreatedBy().equals(current.getId())) { + throw new InquiryCommentNotOwnedException(); + } + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/footer/RelatedSite.java b/src/main/java/com/eactive/apim/portal/djb/footer/RelatedSite.java new file mode 100644 index 0000000..e29f705 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/footer/RelatedSite.java @@ -0,0 +1,22 @@ +package com.eactive.apim.portal.djb.footer; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.ToString; + +/** + * 푸터 "관련 사이트" 셀렉트에 노출되는 사이트 한 건. + * + *

{@link RelatedSiteService} 가 PortalProperty 문자열을 파싱해 만든다.

+ */ +@Getter +@ToString +@AllArgsConstructor +public class RelatedSite { + + /** 셀렉트에 표시되는 이름 (예: 신한은행) */ + private final String name; + + /** 이동 대상 URL (http/https 절대주소 또는 `/` 로 시작하는 사이트 상대주소) */ + private final String url; +} diff --git a/src/main/java/com/eactive/apim/portal/djb/footer/RelatedSiteService.java b/src/main/java/com/eactive/apim/portal/djb/footer/RelatedSiteService.java new file mode 100644 index 0000000..b08495f --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/footer/RelatedSiteService.java @@ -0,0 +1,131 @@ +package com.eactive.apim.portal.djb.footer; + +import com.eactive.apim.portal.portalproperty.service.PortalPropertyService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * 푸터 "관련 사이트" 셀렉트의 라벨과 목록을 DB(PortalProperty)에서 조회한다. + * + *

group 은 기존 {@code Portal} 을 재사용하여 {@link PortalPropertyService#getOrCreateProperty} + * 의 자동 생성(self-seed)이 동작하도록 한다.

+ * + *
    + *
  • {@code footer.related-sites.label} - 셀렉트 첫 항목(플레이스홀더) 문구
  • + *
  • {@code footer.related-sites} - 사이트 목록. 한 줄에 하나씩 {@code 이름=URL}
  • + *
+ * + *

목록 표기 규칙

+ *
+ * # 주석 (# 으로 시작하는 줄은 무시)
+ * 신한은행=http://www.shinhan.com
+ * 제주은행 = https://www.jejubank.co.kr   ← = 앞뒤 공백 허용
+ * 
+ *
    + *
  • 빈 줄 · {@code #} 로 시작하는 줄은 건너뛴다.
  • + *
  • 줄 순서가 곧 화면 노출 순서다.
  • + *
  • URL 에 {@code =} 가 들어가도 첫 번째 {@code =} 만 구분자로 쓰므로 안전하다.
  • + *
  • {@code =} 가 없거나 이름/URL 이 비었거나 허용되지 않은 스킴이면 그 줄만 버리고 WARN 로그를 + * 남긴다. (한 줄이 잘못돼도 나머지 사이트는 정상 노출)
  • + *
+ * + *

허용 스킴을 {@code http/https} 와 사이트 상대경로로 제한하는 이유는, 이 값이 관리자 화면에서 + * 편집되어 그대로 앵커/스크립트 이동 대상이 되기 때문이다({@code javascript:} 등 차단).

+ */ +@Slf4j +@Service +@RequiredArgsConstructor +public class RelatedSiteService { + + private static final String GROUP = "Portal"; + + static final String NAME_LABEL = "footer.related-sites.label"; + static final String NAME_SITES = "footer.related-sites"; + + static final String DESC_LABEL = "푸터 관련 사이트 셀렉트 라벨(첫 항목 문구)"; + static final String DESC_SITES = "푸터 관련 사이트 목록. 한 줄에 하나씩 '이름=URL' (빈 줄·# 주석 무시)"; + + static final String DEFAULT_LABEL = "DJBank 관련 사이트"; + + /** 기본값: 신한금융그룹 Family site (shinhangroup.com 하단 목록 기준) */ + static final String DEFAULT_SITES = String.join("\n", + "신한은행=http://www.shinhan.com", + "신한카드=http://www.shinhancard.com", + "신한투자증권=http://www.shinhansec.com", + "신한라이프=http://www.shinhanlife.co.kr", + "신한캐피탈=http://www.shcap.co.kr", + "신한자산운용=https://www.shinhanfund.com", + "제주은행=https://www.jejubank.co.kr", + "신한저축은행=http://www.shinhansavings.co.kr", + "신한자산신탁=http://www.shinhantrust.kr", + "신한DS=http://www.shinhansys.co.kr", + "신한펀드파트너스=https://www.shinhanfundpartners.com", + "신한리츠운용=http://shinhanrem.com", + "신한벤처투자=http://www.shinhanvc.com", + "신한EZ손해보험=http://www.shinhanez.co.kr", + "신한장학재단=http://www.shsf.or.kr", + "신한금융희망재단=http://www.shinhanfoundation.or.kr"); + + private final PortalPropertyService portalPropertyService; + + /** 셀렉트 첫 항목에 노출할 라벨 */ + @Transactional + public String getLabel() { + String label = portalPropertyService.getOrCreateProperty(GROUP, NAME_LABEL, DEFAULT_LABEL, DESC_LABEL); + return (label == null || label.trim().isEmpty()) ? DEFAULT_LABEL : label.trim(); + } + + /** 셀렉트에 노출할 사이트 목록. 파싱 결과가 없으면 빈 리스트(푸터에서 셀렉트 미노출) */ + @Transactional + public List getSites() { + return parse(portalPropertyService.getOrCreateProperty(GROUP, NAME_SITES, DEFAULT_SITES, DESC_SITES)); + } + + /** + * {@code 이름=URL} 줄 목록을 파싱한다. 잘못된 줄은 건너뛴다. + */ + static List parse(String raw) { + if (raw == null || raw.trim().isEmpty()) { + return Collections.emptyList(); + } + + List sites = new ArrayList<>(); + for (String rawLine : raw.split("\\r?\\n")) { + String line = rawLine.trim(); + if (line.isEmpty() || line.startsWith("#")) { + continue; + } + + int sep = line.indexOf('='); + if (sep <= 0) { + log.warn("관련 사이트 설정 형식 오류(= 구분자 없음) - 해당 줄 무시: {}", line); + continue; + } + + String name = line.substring(0, sep).trim(); + String url = line.substring(sep + 1).trim(); + if (name.isEmpty() || url.isEmpty()) { + log.warn("관련 사이트 설정 형식 오류(이름 또는 URL 없음) - 해당 줄 무시: {}", line); + continue; + } + if (!isAllowedUrl(url)) { + log.warn("관련 사이트 설정 URL 스킴 불허(http/https 또는 / 로 시작해야 함) - 해당 줄 무시: {}", line); + continue; + } + + sites.add(new RelatedSite(name, url)); + } + return sites; + } + + private static boolean isAllowedUrl(String url) { + String lower = url.toLowerCase(); + return lower.startsWith("http://") || lower.startsWith("https://") || url.startsWith("/"); + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/guide/GuideProperty.java b/src/main/java/com/eactive/apim/portal/djb/guide/GuideProperty.java new file mode 100644 index 0000000..b6d70e8 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/guide/GuideProperty.java @@ -0,0 +1,45 @@ +package com.eactive.apim.portal.djb.guide; + +import com.eactive.apim.portal.portalproperty.service.PortalPropertyService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +/** + * 개발가이드 안내페이지에 노출할 값을 {@code PTL_PROPERTY}(그룹 {@code Portal}, key prefix {@code djb.guide.}) + * 에서 읽는 얇은 접근자. + * + *

안내페이지에 표기되는 게이트웨이 주소는 서비스 환경(dev/stage/prod)마다 달라지므로 템플릿에 하드코딩하지 않고 + * DB 프로퍼티로 분리한다. 값 미존재 시 {@link PortalPropertyService#getOrCreateProperty} 가 기본값으로 INSERT 하며, + * 공백 값은 {@link #resolve} 에서 기본값으로 폴백한다({@code getOrCreateProperty} 는 공백 보정을 하지 않음). + */ +@Component +@RequiredArgsConstructor +public class GuideProperty { + + private final PortalPropertyService portalPropertyService; + + private static final String GROUP = "Portal"; + + public static final String KEY_GW_URL = "djb.guide.gw-url"; + + public static final String DEFAULT_GW_URL = "https://openapi.jejubank.co.kr"; + + /** 안내페이지(OAuth2 개발가이드 등)에 표기할 게이트웨이 base URL. 끝 슬래시는 제거한다. */ + public String gatewayUrl() { + return stripTrailingSlash(resolve(KEY_GW_URL, DEFAULT_GW_URL, + "개발가이드 안내페이지에 표기할 게이트웨이 base URL (예: https://openapi.jejubank.co.kr)")); + } + + private String resolve(String key, String defaultValue, String description) { + String value = portalPropertyService.getOrCreateProperty(GROUP, key, defaultValue, description); + return StringUtils.hasText(value) ? value.trim() : defaultValue; + } + + private static String stripTrailingSlash(String s) { + if (s == null) { + return ""; + } + return s.endsWith("/") ? s.substring(0, s.length() - 1) : s; + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/menu/MenuAccessInterceptor.java b/src/main/java/com/eactive/apim/portal/djb/menu/MenuAccessInterceptor.java new file mode 100644 index 0000000..a1ab574 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/menu/MenuAccessInterceptor.java @@ -0,0 +1,70 @@ +package com.eactive.apim.portal.djb.menu; + +import com.eactive.apim.portal.menu.entity.PortalMenuItem; +import com.eactive.apim.portal.common.util.SecurityUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.servlet.HandlerInterceptor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +/** + * 메뉴 접근 권한(ACCESS_ROLES) 서버측 집행. + * + *

요청 경로가 접근 권한이 설정된 메뉴 경로와 정확히 일치할 때만 검사한다 + * (하위 경로는 기존 안전망 @Secured / PageRoute.role 에 위임 — 사용자 결정). + * 미충족 시 익명은 로그인으로, 인증 사용자는 홈으로 리다이렉트한다. + * 메뉴에 등록되지 않은 경로는 통과시킨다. + */ +@Slf4j +@RequiredArgsConstructor +public class MenuAccessInterceptor implements HandlerInterceptor { + + private final MenuService menuService; + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) + throws Exception { + String path = currentPath(request); + List requiredRoles = menuService.getSnapshot().getAccessRolesByPath().get(path); + if (requiredRoles == null || requiredRoles.isEmpty()) { + return true; + } + if (isAllowed(requiredRoles)) { + return true; + } + + if (!SecurityUtil.isAuthenticated()) { + response.sendRedirect(request.getContextPath() + "/login"); + } else { + log.info("메뉴 접근 권한 미충족 - path: {}, 필요 권한: {}, 사용자: {}", + path, requiredRoles, SecurityUtil.getCurrentLoginId()); + response.sendRedirect(request.getContextPath() + "/"); + } + return false; + } + + private boolean isAllowed(List requiredRoles) { + for (String role : requiredRoles) { + if (PortalMenuItem.ROLE_AUTHENTICATED.equals(role)) { + if (SecurityUtil.isAuthenticated()) { + return true; + } + } else if (SecurityUtil.hasRole(role)) { + return true; + } + } + return false; + } + + private String currentPath(HttpServletRequest request) { + String uri = request.getRequestURI(); + String contextPath = request.getContextPath(); + if (contextPath != null && !contextPath.isEmpty() && uri.startsWith(contextPath)) { + uri = uri.substring(contextPath.length()); + } + return uri; + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/menu/MenuInternalController.java b/src/main/java/com/eactive/apim/portal/djb/menu/MenuInternalController.java new file mode 100644 index 0000000..75ea49c --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/menu/MenuInternalController.java @@ -0,0 +1,91 @@ +package com.eactive.apim.portal.djb.menu; + +import com.eactive.apim.portal.common.util.IpAddressMatcher; +import com.eactive.apim.portal.portalproperty.service.PortalPropertyService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * 메뉴 캐시 내부 API — eapim-admin 의 reload 명령 수신용. + * + *

가드: PTL_PROPERTY {@code Portal / menu.internal.allow-ips} 허용 IP 목록 + * (기본 loopback) + X-Forwarded-For 동반 요청 거부 + * ({@code LegacyEncryptionMigrationController.assertLocalOnly} 모델). + * 허용 목록은 {@link IpAddressMatcher} 규칙을 따라 정확 일치 외에 + * IPv4 CIDR({@code 172.30.1.0/24}) 과 옥텟 와일드카드({@code 172.30.*.*}) 를 지원한다. + * CSRF 는 PortalConfigSecurity 에서 {@code /internal/menu/**} 예외 처리.

+ * + *
curl -X POST http://127.0.0.1:39130/internal/menu/reload
+ */ +@Slf4j +@RestController +@RequestMapping("/internal/menu") +@RequiredArgsConstructor +public class MenuInternalController { + + static final String PROP_GROUP = "Portal"; + static final String PROP_ALLOW_IPS = "menu.internal.allow-ips"; + static final String DEFAULT_ALLOW_IPS = "127.0.0.1,::1"; + static final String PROP_ALLOW_IPS_DESCRIPTION = + "메뉴 내부 API(리로드) 허용 IP 목록. 콤마(,)/세미콜론(;)/줄바꿈 구분, " + + "정확일치·IPv4 CIDR(172.30.1.0/24)·와일드카드(172.30.*.*) 지원"; + + private final MenuService menuService; + private final PortalPropertyService portalPropertyService; + + @PostMapping("/reload") + public ResponseEntity> reload(HttpServletRequest request) { + if (!isAllowed(request)) { + Map denied = new LinkedHashMap<>(); + denied.put("result", "DENIED"); + denied.put("message", "허용되지 않은 접근입니다. (menu.internal.allow-ips 확인)"); + return ResponseEntity.status(HttpStatus.FORBIDDEN).body(denied); + } + + MenuService.MenuSnapshot snapshot = menuService.reload(); + log.info("메뉴 캐시 리로드 명령 수신 - from: {}", request.getRemoteAddr()); + + Map body = new LinkedHashMap<>(); + body.put("result", "OK"); + body.put("itemCount", snapshot.getItemCount()); + body.put("reloadedAt", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); + return ResponseEntity.ok(body); + } + + /** + * 허용 IP 검사. 프록시 경유(X-Forwarded-For 존재) 요청은 IP 신뢰 불가로 거부한다. + * (embedded Tomcat 은 forward-headers-strategy: native 로 XFF 가 remoteAddr 에 반영될 수 있으나 + * 그 경우에도 allowlist 검사로 차단된다. WebLogic WAR 배포에서는 원 소켓 IP 로 검사된다.) + */ + private boolean isAllowed(HttpServletRequest request) { + String remote = IpAddressMatcher.canonicalize(request.getRemoteAddr()); + boolean viaProxy = request.getHeader("X-Forwarded-For") != null; + + if (viaProxy || !IpAddressMatcher.matches(resolveAllowIps(), remote)) { + log.warn("메뉴 내부 API 차단 - remote: {}, viaProxy: {}", remote, viaProxy); + return false; + } + return true; + } + + private String resolveAllowIps() { + try { + return portalPropertyService.getOrCreateProperty(PROP_GROUP, PROP_ALLOW_IPS, + DEFAULT_ALLOW_IPS, PROP_ALLOW_IPS_DESCRIPTION); + } catch (Exception e) { + log.warn("허용 IP 목록 조회 실패 - 기본값({}) 사용", DEFAULT_ALLOW_IPS, e); + return DEFAULT_ALLOW_IPS; + } + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/menu/MenuModelAdvice.java b/src/main/java/com/eactive/apim/portal/djb/menu/MenuModelAdvice.java new file mode 100644 index 0000000..3ca7250 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/menu/MenuModelAdvice.java @@ -0,0 +1,94 @@ +package com.eactive.apim.portal.djb.menu; + +import com.eactive.apim.portal.menu.entity.PortalMenuItem; +import com.eactive.apim.portal.common.util.SecurityUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ModelAttribute; + +import javax.servlet.http.HttpServletRequest; +import java.util.ArrayList; +import java.util.List; + +/** + * 모든 뷰 요청에 role 필터가 적용된 {@code menuView} 를 주입한다 + * (breadcrumb 의 GlobalControllerAdvice 와 동일한 방식). + * + *

노출 판정: EXPOSE_ROLES 비어있음(전체) ∥ AUTHENTICATED(로그인) ∥ + * 역할 any-of. 경로 없는 그룹은 노출 자식이 하나도 없으면 제외한다. + */ +@Slf4j +@ControllerAdvice +@RequiredArgsConstructor +public class MenuModelAdvice { + + private final MenuService menuService; + + @ModelAttribute("menuView") + public MenuView menuView(HttpServletRequest request) { + try { + MenuService.MenuSnapshot snapshot = menuService.getSnapshot(); + boolean authenticated = SecurityUtil.isAuthenticated(); + + List gnb = new ArrayList<>(); + MenuNode mypage = null; + for (MenuNode root : snapshot.getRoots()) { + MenuNode filtered = filter(root, authenticated); + if (filtered == null) { + continue; + } + if (PortalMenuItem.SECTION_MYPAGE.equals(filtered.getSection())) { + if (mypage == null) { + mypage = filtered; + } + } else { + gnb.add(filtered); + } + } + return new MenuView(gnb, mypage, request.getRequestURI()); + } catch (Exception e) { + // 메뉴 조회 실패가 화면 전체를 막지 않도록 빈 메뉴로 폴백 + log.error("메뉴 뷰 구성 실패 - 빈 메뉴로 렌더링합니다.", e); + return MenuView.empty(); + } + } + + private MenuNode filter(MenuNode node, boolean authenticated) { + if (!isExposed(node, authenticated)) { + return null; + } + MenuNode copy = node.copyWithoutChildren(); + List children = new ArrayList<>(); + for (MenuNode child : node.getChildren()) { + MenuNode filteredChild = filter(child, authenticated); + if (filteredChild != null) { + children.add(filteredChild); + } + } + copy.setChildren(children); + + // 경로 없는 그룹은 노출할 자식이 없으면 통째로 제외 + if (copy.isGroup() && !copy.hasPath() && children.isEmpty()) { + return null; + } + return copy; + } + + private boolean isExposed(MenuNode node, boolean authenticated) { + List exposeRoles = node.getExposeRoles(); + if (exposeRoles.isEmpty()) { + return true; + } + for (String role : exposeRoles) { + if (PortalMenuItem.ROLE_AUTHENTICATED.equals(role)) { + if (authenticated) { + return true; + } + } else if (SecurityUtil.hasRole(role)) { + return true; + } + } + return false; + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/menu/MenuNode.java b/src/main/java/com/eactive/apim/portal/djb/menu/MenuNode.java new file mode 100644 index 0000000..baf7bb8 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/menu/MenuNode.java @@ -0,0 +1,46 @@ +package com.eactive.apim.portal.djb.menu; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * 렌더용 메뉴 노드. {@link MenuService} 스냅샷 트리와 + * {@link MenuModelAdvice} 의 요청별 필터 사본 양쪽에 사용한다. + */ +@Data +public class MenuNode { + + private String menuId; + private String name; + /** 이동 경로 (null = 클릭 없는 그룹) */ + private String path; + private boolean group; + /** GNB | MYPAGE */ + private String section; + private String icon; + private boolean newWindow; + private List exposeRoles = new ArrayList<>(); + private List accessRoles = new ArrayList<>(); + private List children = new ArrayList<>(); + + /** 필터 사본 생성 (children 제외 필드 복사) */ + public MenuNode copyWithoutChildren() { + MenuNode copy = new MenuNode(); + copy.menuId = menuId; + copy.name = name; + copy.path = path; + copy.group = group; + copy.section = section; + copy.icon = icon; + copy.newWindow = newWindow; + copy.exposeRoles = exposeRoles; + copy.accessRoles = accessRoles; + return copy; + } + + public boolean hasPath() { + return path != null && !path.isEmpty(); + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/menu/MenuSeeder.java b/src/main/java/com/eactive/apim/portal/djb/menu/MenuSeeder.java new file mode 100644 index 0000000..6619afa --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/menu/MenuSeeder.java @@ -0,0 +1,360 @@ +package com.eactive.apim.portal.djb.menu; + +import com.eactive.apim.portal.menu.entity.PortalMenuItem; +import com.eactive.apim.portal.menu.entity.PortalMenuPlacement; +import com.eactive.apim.portal.menu.entity.PortalRole; +import com.eactive.apim.portal.menu.entity.PortalRoleAuthority; +import com.eactive.apim.portal.menu.entity.PortalRoleAuthorityId; +import com.eactive.apim.portal.menu.repository.PortalMenuItemRepository; +import com.eactive.apim.portal.menu.repository.PortalMenuPlacementRepository; +import com.eactive.apim.portal.menu.repository.PortalRoleAuthorityRepository; +import com.eactive.apim.portal.menu.repository.PortalRoleRepository; +import com.eactive.apim.portal.menu.service.PortalMenuDataService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +/** + * 부팅 시 menu.yml / roles.yml → DB 적재. + * + *

    + *
  • 역할: PTL_ROLE upsert + PTL_ROLE_AUTHORITY 재구축 (roles.yml 미러)
  • + *
  • 메뉴 항목: id 기준 upsert — yml 기본값이 바뀌면 DFLT_* 를 갱신하고, + * 관리자가 수정하지 않은(현재값==구 기본값) 필드만 새 기본값을 따라간다
  • + *
  • 배치: PTL_MENU_PLACEMENT 가 비어있을 때만 DFLT_* 로 최초 시딩
  • + *
  • yml 에서 사라진 PORTAL 항목은 경고 로그만 남기고 삭제하지 않는다
  • + *
+ * + * 부팅 컨텍스트에는 인증 주체가 없어 감사(@CreatedBy)가 비므로, + * 신규 행은 {@code createdBy=SYSTEM} 을 명시 세팅한다 + * (AuditingHandler 는 auditor 부재 시 기존 값을 보존). + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class MenuSeeder implements ApplicationListener { + + static final String SYSTEM_AUDITOR = "SYSTEM"; + private static final Pattern MENU_ID_PATTERN = Pattern.compile("^[a-z0-9-]+$"); + + private final PortalMenuYmlProperties menuYmlProperties; + private final PortalRolesProperties rolesProperties; + private final PortalMenuItemRepository menuItemRepository; + private final PortalMenuPlacementRepository menuPlacementRepository; + private final PortalRoleRepository roleRepository; + private final PortalRoleAuthorityRepository roleAuthorityRepository; + private final PortalMenuDataService menuDataService; + private final MenuService menuService; + + @Override + @Transactional + public void onApplicationEvent(ApplicationReadyEvent event) { + try { + seedRoles(); + seedMenuItems(); + seedPlacementsIfEmpty(); + menuService.reload(); + } catch (Exception e) { + // 시딩 실패가 기동 자체를 막지 않도록 로그만 남긴다 (메뉴는 기존 DB 값으로 동작) + log.error("메뉴/역할 시딩 실패 - 기존 DB 데이터로 동작합니다.", e); + } + } + + // ─────────────── 역할 ─────────────── + + private void seedRoles() { + int sortOrder = 10; + int upserted = 0; + Set definedCodes = new HashSet<>(); + + for (PortalRolesProperties.RoleDef roleDef : rolesProperties.getRoles()) { + upserted += upsertRole(roleDef.getCode(), roleDef.getName(), PortalRole.TYPE_BASE, sortOrder) ? 1 : 0; + definedCodes.add(roleDef.getCode()); + sortOrder += 10; + } + for (Map.Entry entry : rolesProperties.getAuthorityNames().entrySet()) { + if (definedCodes.contains(entry.getKey())) { + continue; + } + upserted += upsertRole(entry.getKey(), entry.getValue(), PortalRole.TYPE_AUTHORITY, sortOrder) ? 1 : 0; + sortOrder += 10; + } + + // 역할→권한 매핑은 순수 미러 — 통째로 재구축 + roleAuthorityRepository.deleteAllInBatch(); + roleAuthorityRepository.flush(); + List mappings = rolesProperties.getRoles().stream() + .flatMap(roleDef -> roleDef.getAuthorities().stream() + .map(authority -> { + PortalRoleAuthority mapping = new PortalRoleAuthority(); + mapping.setId(new PortalRoleAuthorityId(roleDef.getCode(), authority)); + mapping.setCreatedBy(SYSTEM_AUDITOR); + return mapping; + })) + .collect(Collectors.toList()); + roleAuthorityRepository.saveAll(mappings); + + log.info("역할 시딩 완료 - PTL_ROLE upsert {}건, PTL_ROLE_AUTHORITY {}건 재구축", upserted, mappings.size()); + } + + private boolean upsertRole(String code, String name, String type, int sortOrder) { + PortalRole role = roleRepository.findById(code).orElse(null); + if (role == null) { + role = new PortalRole(); + role.setRoleCode(code); + role.setCreatedBy(SYSTEM_AUDITOR); + } else if (Objects.equals(role.getRoleName(), name) + && Objects.equals(role.getRoleType(), type) + && Objects.equals(role.getSortOrder(), sortOrder)) { + return false; + } + role.setRoleName(name); + role.setRoleType(type); + role.setSortOrder(sortOrder); + roleRepository.save(role); + return true; + } + + // ─────────────── 메뉴 항목 ─────────────── + + private void seedMenuItems() { + List flatDefs = flatten(menuYmlProperties.getItems()); + validate(flatDefs); + + int inserted = 0; + int updated = 0; + for (FlatMenuDef def : flatDefs) { + PortalMenuItem existing = menuItemRepository.findById(def.id).orElse(null); + if (existing == null) { + menuItemRepository.save(newItem(def)); + inserted++; + } else if (updateItem(existing, def)) { + menuItemRepository.save(existing); + updated++; + } + } + + // yml 에서 사라진 PORTAL 항목 경고 (자동 삭제 금지) + Set ymlIds = flatDefs.stream().map(def -> def.id).collect(Collectors.toSet()); + menuItemRepository.findAllBySourceType(PortalMenuItem.SOURCE_PORTAL).stream() + .map(PortalMenuItem::getMenuId) + .filter(id -> !ymlIds.contains(id)) + .forEach(id -> log.warn("menu.yml 에 없는 기본 메뉴 항목이 DB에 남아 있습니다 (수동 정리 필요): {}", id)); + + log.info("메뉴 항목 시딩 완료 - 신규 {}건, 갱신 {}건, 유지 {}건", + inserted, updated, flatDefs.size() - inserted - updated); + } + + private PortalMenuItem newItem(FlatMenuDef def) { + PortalMenuItem item = new PortalMenuItem(); + item.setMenuId(def.id); + item.setSourceType(PortalMenuItem.SOURCE_PORTAL); + item.setCreatedBy(SYSTEM_AUDITOR); + + item.setMenuName(def.name()); + item.setMenuPath(def.path()); + item.setExposeRoles(def.exposeCsv()); + item.setAccessRoles(def.accessCsv()); + + applyStructure(item, def); + applyDefaults(item, def); + return item; + } + + /** + * 기존 항목 upsert 갱신. 내용 필드는 "관리자 미수정(현재값==구 기본값) 시에만" + * 새 기본값을 따라가고, 구조 필드(group/section/icon/new-window)와 DFLT_* 는 + * 항상 yml 값으로 맞춘다. + * + * @return 변경이 있었으면 true + */ + private boolean updateItem(PortalMenuItem item, FlatMenuDef def) { + boolean[] changed = {false}; + + followDefault(item, changed, def.name(), PortalMenuItem::getDfltMenuName, + PortalMenuItem::getMenuName, PortalMenuItem::setMenuName, PortalMenuItem::setDfltMenuName); + followDefault(item, changed, def.path(), PortalMenuItem::getDfltMenuPath, + PortalMenuItem::getMenuPath, PortalMenuItem::setMenuPath, PortalMenuItem::setDfltMenuPath); + followDefault(item, changed, def.exposeCsv(), PortalMenuItem::getDfltExposeRoles, + PortalMenuItem::getExposeRoles, PortalMenuItem::setExposeRoles, PortalMenuItem::setDfltExposeRoles); + followDefault(item, changed, def.accessCsv(), PortalMenuItem::getDfltAccessRoles, + PortalMenuItem::getAccessRoles, PortalMenuItem::setAccessRoles, PortalMenuItem::setDfltAccessRoles); + + String groupYn = def.source.isGroup() ? "Y" : "N"; + String section = def.section(); + String newWindowYn = def.source.isNewWindow() ? "Y" : "N"; + if (!Objects.equals(item.getGroupYn(), groupYn) + || !Objects.equals(item.getMenuSection(), section) + || !Objects.equals(item.getIconClass(), def.source.getIcon()) + || !Objects.equals(item.getNewWindowYn(), newWindowYn)) { + applyStructure(item, def); + changed[0] = true; + } + + if (!Objects.equals(item.getDfltParentId(), def.parentId) + || !Objects.equals(item.getDfltSortOrder(), def.sortOrder)) { + item.setDfltParentId(def.parentId); + item.setDfltSortOrder(def.sortOrder); + changed[0] = true; + } + return changed[0]; + } + + /** + * 내용 필드 1개에 대한 기본값 추종 처리: yml 기본값이 바뀌었을 때 + * 현재값이 구 기본값과 같으면(관리자 미수정) 현재값도 새 기본값으로 갱신한다. + */ + private void followDefault(PortalMenuItem item, boolean[] changed, String newDefault, + Function defaultGetter, + Function currentGetter, + BiConsumer currentSetter, + BiConsumer defaultSetter) { + String oldDefault = defaultGetter.apply(item); + if (Objects.equals(oldDefault, newDefault)) { + return; + } + if (Objects.equals(currentGetter.apply(item), oldDefault)) { + currentSetter.accept(item, newDefault); + } + defaultSetter.accept(item, newDefault); + changed[0] = true; + } + + private void applyStructure(PortalMenuItem item, FlatMenuDef def) { + item.setGroupYn(def.source.isGroup() ? "Y" : "N"); + item.setMenuSection(def.section()); + item.setIconClass(def.source.getIcon()); + item.setNewWindowYn(def.source.isNewWindow() ? "Y" : "N"); + } + + private void applyDefaults(PortalMenuItem item, FlatMenuDef def) { + item.setDfltMenuName(def.name()); + item.setDfltMenuPath(def.path()); + item.setDfltExposeRoles(def.exposeCsv()); + item.setDfltAccessRoles(def.accessCsv()); + item.setDfltParentId(def.parentId); + item.setDfltSortOrder(def.sortOrder); + item.setDfltVisibleYn("Y"); + } + + // ─────────────── 배치 ─────────────── + + private void seedPlacementsIfEmpty() { + if (menuPlacementRepository.count() > 0) { + log.info("메뉴 배치 존재 - 최초 시딩 건너뜀 (관리자 배치 보존)"); + return; + } + List portalItems = + menuItemRepository.findAllBySourceType(PortalMenuItem.SOURCE_PORTAL); + List placements = menuDataService.buildDefaultPlacements(portalItems); + placements.forEach(placement -> placement.setCreatedBy(SYSTEM_AUDITOR)); + menuPlacementRepository.saveAll(placements); + log.info("메뉴 배치 최초 시딩 완료 - {}건", placements.size()); + } + + // ─────────────── 평탄화 / 검증 ─────────────── + + private List flatten(List roots) { + List result = new ArrayList<>(); + int sortOrder = 10; + for (PortalMenuYmlProperties.MenuItemDef root : roots) { + result.add(new FlatMenuDef(root, null, sortOrder, null)); + int childOrder = 10; + for (PortalMenuYmlProperties.MenuItemDef child : root.getChildren()) { + result.add(new FlatMenuDef(child, root.getId(), childOrder, root)); + childOrder += 10; + } + sortOrder += 10; + } + return result; + } + + private void validate(List defs) { + Set seen = new HashSet<>(); + for (FlatMenuDef def : defs) { + if (def.id == null || !MENU_ID_PATTERN.matcher(def.id).matches()) { + throw new IllegalStateException("menu.yml 항목 id 형식 오류(kebab-case 필수): " + def.id); + } + if (!seen.add(def.id)) { + throw new IllegalStateException("menu.yml 항목 id 중복: " + def.id); + } + } + } + + /** yml 트리 항목의 평탄화 뷰 (부모/기본 정렬 포함) */ + private static class FlatMenuDef { + final PortalMenuYmlProperties.MenuItemDef source; + final String parentId; + final Integer sortOrder; + final PortalMenuYmlProperties.MenuItemDef parent; + final String id; + + FlatMenuDef(PortalMenuYmlProperties.MenuItemDef source, String parentId, Integer sortOrder, + PortalMenuYmlProperties.MenuItemDef parent) { + this.source = source; + this.parentId = parentId; + this.sortOrder = sortOrder; + this.parent = parent; + this.id = source.getId(); + } + + String name() { + return source.getName(); + } + + String path() { + return trimToNull(source.getPath()); + } + + /** 자식은 섹션 미지정 시 부모 섹션 상속 */ + String section() { + String own = trimToNull(source.getSection()); + if (own != null) { + return own; + } + if (parent != null) { + String parentSection = trimToNull(parent.getSection()); + if (parentSection != null) { + return parentSection; + } + } + return PortalMenuItem.SECTION_GNB; + } + + String exposeCsv() { + return toCsv(source.getExposeRoles()); + } + + String accessCsv() { + return toCsv(source.getAccessRoles()); + } + + private static String toCsv(List roles) { + if (roles == null || roles.isEmpty()) { + return null; + } + return String.join(",", roles); + } + + private static String trimToNull(String value) { + if (value == null || value.trim().isEmpty()) { + return null; + } + return value.trim(); + } + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/menu/MenuService.java b/src/main/java/com/eactive/apim/portal/djb/menu/MenuService.java new file mode 100644 index 0000000..3860b7b --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/menu/MenuService.java @@ -0,0 +1,179 @@ +package com.eactive.apim.portal.djb.menu; + +import com.eactive.apim.portal.menu.entity.PortalMenuItem; +import com.eactive.apim.portal.menu.entity.PortalMenuPlacement; +import com.eactive.apim.portal.menu.service.PortalMenuDataService; +import com.eactive.apim.portal.portalproperty.service.PortalPropertyService; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 메뉴 트리 스냅샷 캐시. + * + *

매 요청 DB 조회를 피하기 위해 role 비의존 트리를 메모리에 유지한다 + * (기존 수제 캐시 관례: PageService 의 volatile 필드). TTL 은 + * PTL_PROPERTY {@code Portal / menu.cache.ttl-seconds} (기본 3600초)이며, + * eapim-admin 의 reload 명령(/internal/menu/reload)으로 즉시 갱신된다. + * 요청별 role 필터링은 {@link MenuModelAdvice} 가 수행한다. + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class MenuService { + + static final String PROP_GROUP = "Portal"; + static final String PROP_CACHE_TTL = "menu.cache.ttl-seconds"; + static final String DEFAULT_CACHE_TTL = "3600"; + /** 스냅샷이 비어있을 때의 재시도 TTL(초) — 시딩 전 기동 직후 요청 대비 */ + private static final long EMPTY_RETRY_TTL_SECONDS = 60; + + private final PortalMenuDataService menuDataService; + private final PortalPropertyService portalPropertyService; + + private volatile MenuSnapshot snapshot; + + public MenuSnapshot getSnapshot() { + MenuSnapshot current = snapshot; + if (current != null && !current.isExpired()) { + return current; + } + synchronized (this) { + current = snapshot; + if (current != null && !current.isExpired()) { + return current; + } + return reload(); + } + } + + /** + * DB 에서 트리를 재구축한다. 시더 완료 시점과 admin reload 명령이 호출한다. + */ + public synchronized MenuSnapshot reload() { + long ttlSeconds = resolveTtlSeconds(); + List items = menuDataService.loadAllItems(); + List placements = menuDataService.loadAllPlacements(); + + Map itemsById = items.stream() + .collect(Collectors.toMap(PortalMenuItem::getMenuId, item -> item, (a, b) -> a, LinkedHashMap::new)); + + // 가시(visible=Y) 배치만 렌더 트리에 포함 + Map> byParent = placements.stream() + .filter(PortalMenuPlacement::isVisible) + .filter(placement -> itemsById.containsKey(placement.getMenuId())) + .collect(Collectors.groupingBy( + placement -> placement.getParentId() == null ? "" : placement.getParentId(), + LinkedHashMap::new, Collectors.toList())); + + List roots = buildNodes(byParent.getOrDefault("", Collections.emptyList()), itemsById, byParent); + + // 접근 권한 맵은 배치/노출과 무관하게 항목 기준으로 구성 (숨김 메뉴도 접근 통제 유지) + Map> accessRolesByPath = new LinkedHashMap<>(); + for (PortalMenuItem item : items) { + if (item.getMenuPath() != null && item.getAccessRoles() != null + && !item.getAccessRoles().trim().isEmpty()) { + accessRolesByPath.put(normalizePath(item.getMenuPath()), splitCsv(item.getAccessRoles())); + } + } + + if (roots.isEmpty()) { + ttlSeconds = Math.min(ttlSeconds, EMPTY_RETRY_TTL_SECONDS); + } + MenuSnapshot rebuilt = new MenuSnapshot(roots, accessRolesByPath, items.size(), + System.currentTimeMillis(), ttlSeconds); + snapshot = rebuilt; + log.info("메뉴 캐시 갱신 - 항목 {}건, 최상위 {}건, TTL {}초", items.size(), roots.size(), ttlSeconds); + return rebuilt; + } + + private List buildNodes(List placements, + Map itemsById, + Map> byParent) { + List nodes = new ArrayList<>(); + for (PortalMenuPlacement placement : placements) { + PortalMenuItem item = itemsById.get(placement.getMenuId()); + MenuNode node = toNode(item); + node.setChildren(buildNodes( + byParent.getOrDefault(item.getMenuId(), Collections.emptyList()), itemsById, byParent)); + nodes.add(node); + } + return nodes; + } + + private MenuNode toNode(PortalMenuItem item) { + MenuNode node = new MenuNode(); + node.setMenuId(item.getMenuId()); + node.setName(item.getMenuName()); + node.setPath(normalizePath(item.getMenuPath())); + node.setGroup(item.isGroup()); + node.setSection(item.getMenuSection()); + node.setIcon(item.getIconClass()); + node.setNewWindow("Y".equals(item.getNewWindowYn())); + node.setExposeRoles(splitCsv(item.getExposeRoles())); + node.setAccessRoles(splitCsv(item.getAccessRoles())); + return node; + } + + private long resolveTtlSeconds() { + try { + String value = portalPropertyService.getOrCreateProperty(PROP_GROUP, PROP_CACHE_TTL, + DEFAULT_CACHE_TTL, "포탈 메뉴 캐시 TTL(초)"); + return Long.parseLong(value.trim()); + } catch (Exception e) { + log.warn("메뉴 캐시 TTL 조회 실패 - 기본값 {}초 사용", DEFAULT_CACHE_TTL, e); + return Long.parseLong(DEFAULT_CACHE_TTL); + } + } + + private static List splitCsv(String csv) { + if (csv == null || csv.trim().isEmpty()) { + return Collections.emptyList(); + } + return Arrays.stream(csv.split(",")) + .map(String::trim) + .filter(token -> !token.isEmpty()) + .collect(Collectors.toList()); + } + + private static String normalizePath(String path) { + if (path == null || path.trim().isEmpty()) { + return null; + } + String normalized = path.trim(); + int queryIndex = normalized.indexOf('?'); + return queryIndex >= 0 ? normalized.substring(0, queryIndex) : normalized; + } + + /** role 비의존 메뉴 스냅샷 (불변 취급) */ + @Getter + public static class MenuSnapshot { + private final List roots; + private final Map> accessRolesByPath; + private final int itemCount; + private final long loadedAt; + private final long ttlSeconds; + + MenuSnapshot(List roots, Map> accessRolesByPath, + int itemCount, long loadedAt, long ttlSeconds) { + this.roots = roots; + this.accessRolesByPath = accessRolesByPath; + this.itemCount = itemCount; + this.loadedAt = loadedAt; + this.ttlSeconds = ttlSeconds; + } + + boolean isExpired() { + return System.currentTimeMillis() - loadedAt > ttlSeconds * 1000L; + } + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/menu/MenuView.java b/src/main/java/com/eactive/apim/portal/djb/menu/MenuView.java new file mode 100644 index 0000000..c50a133 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/menu/MenuView.java @@ -0,0 +1,108 @@ +package com.eactive.apim.portal.djb.menu; + +import lombok.Getter; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 요청별(role 필터 적용) 메뉴 뷰 — 템플릿 모델 {@code menuView}. + * + *

    + *
  • {@link #getGnb()} — 상단 글로벌 네비(GNB 섹션 최상위)
  • + *
  • {@link #getMypage()} — 마이페이지 드롭다운 루트 (미노출 시 null)
  • + *
  • {@link #activeGroup(String)} — service_sidebar 용 활성 그룹 탐색
  • + *
+ */ +@Getter +public class MenuView { + + /** + * service_sidebar 기존 호출부의 activeMenu 키 → 메뉴 ID 브리지. + * 호출 페이지 수정 없이 DB 메뉴 기반 사이드바로 전환하기 위한 레거시 맵. + */ + private static final Map LEGACY_ACTIVE_KEYS = new HashMap<>(); + + static { + LEGACY_ACTIVE_KEYS.put("intro", "service-intro"); + LEGACY_ACTIVE_KEYS.put("guide", "service-guide"); + LEGACY_ACTIVE_KEYS.put("oauth2", "service-oauth2-guide"); + LEGACY_ACTIVE_KEYS.put("webhookGuide", "service-webhook-dev-guide"); + LEGACY_ACTIVE_KEYS.put("notice", "support-notice"); + LEGACY_ACTIVE_KEYS.put("faq", "support-faq"); + LEGACY_ACTIVE_KEYS.put("qna", "support-qna"); + LEGACY_ACTIVE_KEYS.put("feedback", "support-partnership"); + LEGACY_ACTIVE_KEYS.put("users", "my-page-users"); + LEGACY_ACTIVE_KEYS.put("apiKey", "my-page-clients"); + LEGACY_ACTIVE_KEYS.put("webhook", "my-page-webhook"); + LEGACY_ACTIVE_KEYS.put("statistics", "my-page-statistics"); + LEGACY_ACTIVE_KEYS.put("profile", "my-page-profile"); + LEGACY_ACTIVE_KEYS.put("password", "my-page-password"); + } + + private final List gnb; + private final MenuNode mypage; + private final String currentPath; + + public MenuView(List gnb, MenuNode mypage, String currentPath) { + this.gnb = gnb; + this.mypage = mypage; + this.currentPath = currentPath; + } + + /** + * 사이드바용 활성 그룹. legacy activeMenu 키 → 메뉴 ID 우선, + * 실패 시 현재 경로(정확/prefix) 매칭으로 폴백. + */ + public MenuNode activeGroup(String activeMenu) { + String targetId = activeMenu == null ? null : LEGACY_ACTIVE_KEYS.get(activeMenu); + MenuNode byId = targetId == null ? null : findRootContaining(node -> targetId.equals(node.getMenuId())); + if (byId != null) { + return byId; + } + return findRootContaining(this::matchesCurrentPath); + } + + /** + * 사이드바 항목 활성 여부: legacy 키 매칭 우선, 경로 매칭 폴백. + */ + public boolean isActive(MenuNode node, String activeMenu) { + String targetId = activeMenu == null ? null : LEGACY_ACTIVE_KEYS.get(activeMenu); + if (targetId != null) { + return targetId.equals(node.getMenuId()); + } + return matchesCurrentPath(node); + } + + private MenuNode findRootContaining(java.util.function.Predicate predicate) { + for (MenuNode root : allRoots()) { + if (predicate.test(root) || root.getChildren().stream().anyMatch(predicate)) { + return root; + } + } + return null; + } + + private List allRoots() { + if (mypage == null) { + return gnb; + } + java.util.ArrayList roots = new java.util.ArrayList<>(gnb); + roots.add(mypage); + return roots; + } + + private boolean matchesCurrentPath(MenuNode node) { + if (!node.hasPath() || currentPath == null) { + return false; + } + return currentPath.equals(node.getPath()) + || currentPath.startsWith(node.getPath() + "/"); + } + + public static MenuView empty() { + return new MenuView(Collections.emptyList(), null, null); + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/menu/PortalMenuYmlProperties.java b/src/main/java/com/eactive/apim/portal/djb/menu/PortalMenuYmlProperties.java new file mode 100644 index 0000000..a08a63f --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/menu/PortalMenuYmlProperties.java @@ -0,0 +1,38 @@ +package com.eactive.apim.portal.djb.menu; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; + +/** + * menu.yml 바인딩 (spring.config.import 로 로드). + * 트리(children 중첩) 형태 그대로 바인딩하며, 평탄화는 {@link MenuSeeder} 가 수행한다. + */ +@Data +@Component +@ConfigurationProperties(prefix = "portal-menu") +public class PortalMenuYmlProperties { + + private List items = new ArrayList<>(); + + @Data + public static class MenuItemDef { + /** kebab-case 자연키 (^[a-z0-9-]+$) */ + private String id; + private String name; + private String path; + /** true = 클릭 없는 상위 그룹 */ + private boolean group; + /** GNB(기본) | MYPAGE */ + private String section; + /** FontAwesome 아이콘 클래스 (마이페이지 드롭다운) */ + private String icon; + private boolean newWindow; + private List exposeRoles = new ArrayList<>(); + private List accessRoles = new ArrayList<>(); + private List children = new ArrayList<>(); + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/menu/PortalRolesProperties.java b/src/main/java/com/eactive/apim/portal/djb/menu/PortalRolesProperties.java new file mode 100644 index 0000000..f2196ba --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/menu/PortalRolesProperties.java @@ -0,0 +1,47 @@ +package com.eactive.apim.portal.djb.menu; + +import com.eactive.apim.portal.portaluser.entity.PortalUserEnums.RoleCode; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * roles.yml 바인딩 (spring.config.import 로 로드). + * 기존 application.yml 의 {@code portal.portal_security} 를 대체한다. + * 로그인 시 권한 확장(PortalUserAuthService)이 직접 사용하며, + * DB 미러(PTL_ROLE / PTL_ROLE_AUTHORITY)는 {@link MenuSeeder} 가 적재한다. + */ +@Data +@Component +@ConfigurationProperties(prefix = "portal-roles") +public class PortalRolesProperties { + + private List roles = new ArrayList<>(); + + /** AUTHORITY 유형 역할 코드 → 한글 라벨 (admin UI 표기용) */ + private Map authorityNames = new LinkedHashMap<>(); + + @Data + public static class RoleDef { + private String code; + private String name; + private List authorities = new ArrayList<>(); + } + + /** + * 기본 역할의 파생 권한 목록. 미정의 역할이면 빈 목록. + */ + public List getAuthorities(RoleCode roleCode) { + return roles.stream() + .filter(role -> role.getCode().equals(roleCode.name())) + .findFirst() + .map(RoleDef::getAuthorities) + .orElse(Collections.emptyList()); + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/swing/EmployeeIdPolicy.java b/src/main/java/com/eactive/apim/portal/djb/swing/EmployeeIdPolicy.java new file mode 100644 index 0000000..2f82978 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/swing/EmployeeIdPolicy.java @@ -0,0 +1,40 @@ +package com.eactive.apim.portal.djb.swing; + +/** + * Swing 메신저 수신자 ID 가 행번(직원번호)이 아닐 때의 처리 정책. + * + *

Swing 메신저는 {@code TSEAIRM02.USERID} 를 수신자 코드로 사용하며 운영 직원은 숫자 행번을 쓴다. + * portal-admin 역할이 부여된 개발용 계정(영문 ID 등)은 Swing 에 존재하지 않아 발송이 무의미하므로 + * 이 정책으로 처리 방식을 선택한다. PTL_PROPERTY + * {@code Portal / djb.swing.notify.non-employee.policy} 로 지정한다.

+ */ +public enum EmployeeIdPolicy { + + /** 정상 발송 (PTL_MESSAGE_REQUEST 에 PENDING 적재) */ + SEND, + + /** 발송 + WARN 로그 */ + SEND_WARN, + + /** 무시 처리 — REQUEST_STATUS='SKIPPED' 로 적재해 이력만 남기고 배치는 수집하지 않음 */ + SKIP, + + /** 거부 — PTL_MESSAGE_REQUEST 에 적재하지 않음 + INFO 로그 */ + REJECT_LOG, + + /** 완전 거부 — 적재하지 않고 로그도 남기지 않음 */ + REJECT; + + /** 프로퍼티 문자열 → enum. 값이 없거나 알 수 없으면 기본값 {@link #SKIP}. */ + public static EmployeeIdPolicy from(String value) { + if (value != null) { + String trimmed = value.trim(); + for (EmployeeIdPolicy policy : values()) { + if (policy.name().equalsIgnoreCase(trimmed)) { + return policy; + } + } + } + return SKIP; + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/swing/SwingMessageWriter.java b/src/main/java/com/eactive/apim/portal/djb/swing/SwingMessageWriter.java new file mode 100644 index 0000000..b80bc11 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/swing/SwingMessageWriter.java @@ -0,0 +1,160 @@ +package com.eactive.apim.portal.djb.swing; + +import com.eactive.apim.portal.djb.swing.repository.SwingStaffRepository; +import com.eactive.apim.portal.template.entity.MessageCode; +import com.eactive.apim.portal.template.entity.MessageRequest; +import com.eactive.apim.portal.template.entity.MessageTemplate; +import com.eactive.apim.portal.template.repository.MessageRequestRepository; +import com.eactive.apim.portal.template.repository.MessageTemplateRepository; +import com.eactive.apim.portal.user.entity.UserInfo; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; + +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Swing 메신저 발송 요청을 PTL_MESSAGE_REQUEST 에 직접 적재한다. + * + *

TSEAIRM02(직원) → PTL_MESSAGE_TEMPLATE(본문) → PTL_MESSAGE_REQUEST(발송 요청) 순으로 + * 처리하며, 실제 발송은 기존과 동일하게 eapim-admin 의 {@code UmsDispatchJob}(5초 주기)이 + * {@code REQUEST_STATUS='PENDING'} 행을 수집해 수행한다.

+ */ +@Slf4j +@Service +@RequiredArgsConstructor +public class SwingMessageWriter { + + private static final String MESSAGE_TYPE_MESSENGER = "MESSENGER"; + private static final String STATUS_PENDING = "PENDING"; + /** 행번 아닌 ID 를 무시 처리할 때 쓰는 신설 상태값. 배치(PENDING 수집)가 건드리지 않는다. */ + private static final String STATUS_SKIPPED = "SKIPPED"; + private static final String ENABLED = "Y"; + + private final SwingStaffRepository swingStaffRepository; + private final MessageTemplateRepository messageTemplateRepository; + private final MessageRequestRepository messageRequestRepository; + private final SwingNotifyProperties properties; + + @Transactional + public void write(MessageCode code, Map params) { + String role = properties.getTargetRole(); + List staffs = swingStaffRepository.findByRole(role); + if (staffs == null || staffs.isEmpty()) { + log.warn("Swing 알림 대상 직원이 없습니다 — role={}, code={}", role, code.name()); + return; + } + + MessageTemplate template = messageTemplateRepository.findById(code.name()).orElse(null); + if (template == null) { + log.warn("메세지 템플릿이 존재하지 않습니다 — code={}", code.name()); + return; + } + if (!ENABLED.equalsIgnoreCase(template.getEnableMessenger())) { + log.warn("메신저 발송이 비활성화된 템플릿입니다 — code={}, enableMessenger={}", + code.name(), template.getEnableMessenger()); + return; + } + + SwingNotifyProperties.UmsMessengerIds umsIds = properties.getUmsMessengerIds(); + EmployeeIdPolicy policy = properties.getNonEmployeePolicy(); + + for (UserInfo staff : staffs) { + try { + writeOne(code, template, params, staff, umsIds, policy); + } catch (Exception e) { + log.warn("Swing 알림 적재 실패 — userid={}, code={}", staff.getUserid(), code.name(), e); + } + } + } + + private void writeOne(MessageCode code, MessageTemplate template, Map params, + UserInfo staff, SwingNotifyProperties.UmsMessengerIds umsIds, + EmployeeIdPolicy policy) { + + String messengerId = staff.getUserid(); + String requestStatus = STATUS_PENDING; + + if (!properties.isEmployeeId(messengerId)) { + switch (policy) { + case REJECT: + return; + case REJECT_LOG: + log.info("행번이 아닌 ID — 발송 거부(미적재). userid={}, code={}", messengerId, code.name()); + return; + case SKIP: + log.info("행번이 아닌 ID — 무시 처리({}). userid={}, code={}", + STATUS_SKIPPED, messengerId, code.name()); + requestStatus = STATUS_SKIPPED; + break; + case SEND_WARN: + log.warn("행번이 아닌 ID — 발송 진행. Swing 미등록 계정이면 발송 실패한다. userid={}, code={}", + messengerId, code.name()); + break; + case SEND: + default: + break; + } + } + + Map messageParams = toStringParams(params); + messageParams.put("userId", messengerId); + messageParams.putIfAbsent("USER_ID", messengerId); + if (StringUtils.hasText(staff.getUsername())) { + messageParams.put("userName", staff.getUsername()); + messageParams.putIfAbsent("USER_NAME", staff.getUsername()); + } + + MessageRequest request = new MessageRequest(); + request.setMessageCode(code); + request.setMessageType(MESSAGE_TYPE_MESSENGER); + request.setSubject(buildMessage(template.getSubjectTemplate(), messageParams)); + request.setMessage(buildMessage(template.getMessengerTemplate(), messageParams)); + request.setUsername(staff.getUsername()); + request.setUserId(messengerId); + request.setMessengerId(messengerId); + request.setEaiInterfaceId(umsIds.getInterfaceId()); + request.setServiceId(umsIds.getServiceId()); + request.setRequestDate(LocalDateTime.now()); + request.setRequestStatus(requestStatus); + // email/phone 은 설정하지 않는다 — 메신저 전용 경로라 개인정보를 적재할 이유가 없다. + + messageRequestRepository.save(request); + log.debug("Swing 알림 적재 — code={}, userid={}, status={}", code.name(), messengerId, requestStatus); + } + + /** 수신자별로 파라미터를 복사한다(공유 맵을 오염시키지 않기 위함). */ + private static Map toStringParams(Map params) { + Map result = new HashMap<>(); + if (params != null) { + for (Map.Entry entry : params.entrySet()) { + if (entry.getKey() == null || entry.getValue() == null) { + continue; + } + result.put(entry.getKey(), String.valueOf(entry.getValue())); + } + } + return result; + } + + /** 템플릿 본문의 {@code %KEY%} 플레이스홀더를 치환한다(정규식 아닌 리터럴 치환). */ + private static String buildMessage(String contents, Map params) { + if (!StringUtils.hasText(contents)) { + return ""; + } + + String result = contents; + for (Map.Entry entry : params.entrySet()) { + if (entry.getKey() == null || entry.getValue() == null) { + continue; + } + result = result.replace("%" + entry.getKey() + "%", entry.getValue()); + } + return result; + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/swing/SwingNotifier.java b/src/main/java/com/eactive/apim/portal/djb/swing/SwingNotifier.java new file mode 100644 index 0000000..b86923d --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/swing/SwingNotifier.java @@ -0,0 +1,43 @@ +package com.eactive.apim.portal.djb.swing; + +import com.eactive.apim.portal.djb.swing.config.SwingAsyncConfig; +import com.eactive.apim.portal.template.entity.MessageCode; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +import java.util.Map; + +/** + * DjBankSwing(사내 메신저) 전용 알림 발행자. + * + *

포털 사용자용 범용 발송 경로({@code MessageHandlerService} → {@code MessageSendService})를 + * 타지 않고 TSEAIRM02 / PTL_MESSAGE_TEMPLATE / PTL_MESSAGE_REQUEST 를 직접 다룬다. + * 내부 직원은 수신자 ID 체계(행번)와 채널(메신저 단일)이 포털 사용자와 완전히 달라 분리했다.

+ * + *

비동기 실행이므로 게시물 등록 트랜잭션과 분리된다. 발송 적재 실패가 등록을 되돌리지 않는 대신, + * 등록이 롤백돼도 알림은 남을 수 있다.

+ */ +@Slf4j +@Service +@RequiredArgsConstructor +public class SwingNotifier { + + private final SwingMessageWriter swingMessageWriter; + + /** + * 대상 역할(PTL_PROPERTY {@code djb.swing.notify.role}) 직원 전원에게 메신저 알림을 적재한다. + * + * @param code 메세지 코드 (PTL_MESSAGE_TEMPLATE.MESSAGE_CODE 와 동일) + * @param params 템플릿 {@code %KEY%} 치환 파라미터 + */ + @Async(SwingAsyncConfig.EXECUTOR) + public void notifyPortalAdmins(MessageCode code, Map params) { + try { + swingMessageWriter.write(code, params); + } catch (Exception e) { + log.warn("Swing 알림 발행 실패 — code={}", code == null ? null : code.name(), e); + } + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/swing/SwingNotifyProperties.java b/src/main/java/com/eactive/apim/portal/djb/swing/SwingNotifyProperties.java new file mode 100644 index 0000000..2319eca --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/swing/SwingNotifyProperties.java @@ -0,0 +1,112 @@ +package com.eactive.apim.portal.djb.swing; + +import com.eactive.apim.portal.portalproperty.service.PortalPropertyService; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.regex.Pattern; + +/** + * Swing 알림 관련 PTL_PROPERTY 접근 래퍼. + * + *

그룹 {@code Portal}, 점 구분 소문자 키 관례를 따른다. + * {@link PortalPropertyService#getOrCreateProperty} 는 최초 접근 시 기본값으로 DB row 를 + * 생성하므로 별도 초기 데이터 없이도 동작한다.

+ */ +@Slf4j +@Component +@RequiredArgsConstructor +public class SwingNotifyProperties { + + public static final String GROUP = "Portal"; + + public static final String KEY_TARGET_ROLE = "djb.swing.notify.role"; + public static final String KEY_EMPLOYEE_ID_PATTERN = "djb.swing.notify.employee-id.pattern"; + public static final String KEY_NON_EMPLOYEE_POLICY = "djb.swing.notify.non-employee.policy"; + + /** UMS 메신저 연계 식별자. 기존 발송 경로(MessageSendService)와 같은 키를 그대로 읽는다. */ + public static final String KEY_MESSENGER_IF_ID = "djb.ums.messenger.if_id"; + public static final String KEY_MESSENGER_TX_ID = "djb.ums.messenger.tx_id"; + + private static final String DEFAULT_TARGET_ROLE = "portal-admin"; + private static final String DEFAULT_EMPLOYEE_ID_PATTERN = "^[0-9]+$"; + + private final PortalPropertyService portalPropertyService; + + /** 컴파일된 행번 정규식 캐시. 프로퍼티 값이 바뀌면 다시 컴파일한다. */ + private volatile String cachedPatternValue; + private volatile Pattern cachedPattern; + + /** 알림 대상 직원을 식별하는 TSEAIRM02.ROLEIDNFINAME 역할명 */ + public String getTargetRole() { + return resolve(KEY_TARGET_ROLE, DEFAULT_TARGET_ROLE, + "Swing 알림 대상 직원 역할명 (TSEAIRM02.ROLEIDNFINAME 의 콤마 구분 토큰)"); + } + + /** 행번 아닌 ID 처리 정책 */ + public EmployeeIdPolicy getNonEmployeePolicy() { + return EmployeeIdPolicy.from(resolve(KEY_NON_EMPLOYEE_POLICY, EmployeeIdPolicy.SKIP.name(), + "행번 아닌 ID 처리 정책 (SEND/SEND_WARN/SKIP/REJECT_LOG/REJECT)")); + } + + /** + * 행번(직원번호) 형식인지 여부. 정규식이 잘못 지정된 경우 기본 정규식으로 폴백한다. + * + * @param userId TSEAIRM02.USERID + */ + public boolean isEmployeeId(String userId) { + if (userId == null || userId.trim().isEmpty()) { + return false; + } + return employeeIdPattern().matcher(userId.trim()).matches(); + } + + /** UMS 메신저 연계 식별자(EAI 인터페이스 ID / 서비스 ID) */ + public UmsMessengerIds getUmsMessengerIds() { + // 환경별 실제 연계값이라 임의 기본값을 만들면 위험 — getOrCreateProperty 대신 조회만 한다. + Map portalProperties = portalPropertyService.getPortalPropertiesAsMap(GROUP); + return new UmsMessengerIds( + portalProperties.get(KEY_MESSENGER_IF_ID), + portalProperties.get(KEY_MESSENGER_TX_ID)); + } + + private Pattern employeeIdPattern() { + String value = resolve(KEY_EMPLOYEE_ID_PATTERN, DEFAULT_EMPLOYEE_ID_PATTERN, + "행번(직원번호) 판별 정규식. 미매치 시 개발용 ID 로 간주"); + + Pattern cached = this.cachedPattern; + if (cached != null && value != null && value.equals(this.cachedPatternValue)) { + return cached; + } + + Pattern compiled; + try { + compiled = Pattern.compile(value); + } catch (Exception e) { + log.warn("행번 판별 정규식이 잘못되어 기본값으로 대체합니다 — pattern={}", value, e); + compiled = Pattern.compile(DEFAULT_EMPLOYEE_ID_PATTERN); + } + this.cachedPatternValue = value; + this.cachedPattern = compiled; + return compiled; + } + + private String resolve(String key, String defaultValue, String description) { + return portalPropertyService.getOrCreateProperty(GROUP, key, defaultValue, description); + } + + /** UMS 메신저 연계 식별자 묶음 */ + @Getter + public static class UmsMessengerIds { + private final String interfaceId; + private final String serviceId; + + public UmsMessengerIds(String interfaceId, String serviceId) { + this.interfaceId = interfaceId; + this.serviceId = serviceId; + } + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/swing/config/SwingAsyncConfig.java b/src/main/java/com/eactive/apim/portal/djb/swing/config/SwingAsyncConfig.java new file mode 100644 index 0000000..207c469 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/swing/config/SwingAsyncConfig.java @@ -0,0 +1,34 @@ +package com.eactive.apim.portal.djb.swing.config; + +import java.util.concurrent.Executor; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +/** + * Swing 알림 전용 비동기 실행기. + * + *

게시물 등록 트랜잭션이 메신저 알림 적재를 기다리지 않도록 분리한다. + * eapim-admin 의 {@code WebhookAsyncConfig} 와 동일한 구성.

+ */ +@Configuration +@EnableAsync +public class SwingAsyncConfig { + + public static final String EXECUTOR = "swingNotifyExecutor"; + + @Bean(name = EXECUTOR) + public Executor swingNotifyExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(2); + executor.setMaxPoolSize(10); + executor.setQueueCapacity(100); + executor.setThreadNamePrefix("swing-notify-"); + executor.setWaitForTasksToCompleteOnShutdown(true); + executor.setAwaitTerminationSeconds(30); + executor.initialize(); + return executor; + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/swing/repository/SwingStaffRepository.java b/src/main/java/com/eactive/apim/portal/djb/swing/repository/SwingStaffRepository.java new file mode 100644 index 0000000..e3c8030 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/swing/repository/SwingStaffRepository.java @@ -0,0 +1,27 @@ +package com.eactive.apim.portal.djb.swing.repository; + +import com.eactive.apim.portal.user.entity.UserInfo; +import com.eactive.eai.rms.data.EMSDataSource; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.util.List; + +/** + * Swing 알림 대상 직원(TSEAIRM02) 조회 전용 리포지토리. + */ +@Repository +@EMSDataSource +public interface SwingStaffRepository extends JpaRepository { + + /** + * TSEAIRM02.roleidnfiname 컬럼은 콤마로 구분된 복수 역할을 저장한다. + * (예: {@code "admin,portal-admin"}). Oracle native query로 콤마 토큰 매치. + */ + @Query(value = "SELECT * FROM TSEAIRM02 t" + + " WHERE ',' || t.ROLEIDNFINAME || ',' LIKE '%,' || :role || ',%'", + nativeQuery = true) + List findByRole(@Param("role") String role); +} diff --git a/src/main/java/com/eactive/apim/portal/djb/testbed/advice/DjbTestbedExceptionHandler.java b/src/main/java/com/eactive/apim/portal/djb/testbed/advice/DjbTestbedExceptionHandler.java new file mode 100644 index 0000000..de4415a --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/testbed/advice/DjbTestbedExceptionHandler.java @@ -0,0 +1,37 @@ +package com.eactive.apim.portal.djb.testbed.advice; + +import com.eactive.apim.portal.djb.testbed.controller.DjbTestbedAuthController; +import com.eactive.apim.portal.djb.testbed.controller.DjbTestbedSpecController; +import com.eactive.apim.portal.djb.testbed.exception.DjbUnsupportedAuthTypeException; +import java.util.HashMap; +import java.util.Map; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +/** + * 테스트베드 두 컨트롤러 한정 예외 → JSON 변환. + * (qna 로컬 핸들러와 동일한 {@code {code, message}} 포맷.) + */ +@RestControllerAdvice(assignableTypes = {DjbTestbedAuthController.class, DjbTestbedSpecController.class}) +public class DjbTestbedExceptionHandler { + + @ExceptionHandler(DjbUnsupportedAuthTypeException.class) + public ResponseEntity> handleUnsupportedAuthType(DjbUnsupportedAuthTypeException ex) { + Map body = new HashMap<>(); + body.put("code", "UNSUPPORTED_AUTHTYPE"); + body.put("message", ex.getMessage()); + body.put("authtype", ex.getAuthtype()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(body); + } + + @ExceptionHandler(AccessDeniedException.class) + public ResponseEntity> handleAccessDenied(AccessDeniedException ex) { + Map body = new HashMap<>(); + body.put("code", "FORBIDDEN"); + body.put("message", ex.getMessage()); + return ResponseEntity.status(HttpStatus.FORBIDDEN).body(body); + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/testbed/config/DjbTestbedGatewayProperty.java b/src/main/java/com/eactive/apim/portal/djb/testbed/config/DjbTestbedGatewayProperty.java new file mode 100644 index 0000000..850f9de --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/testbed/config/DjbTestbedGatewayProperty.java @@ -0,0 +1,231 @@ +package com.eactive.apim.portal.djb.testbed.config; + +import com.eactive.apim.portal.djb.testbed.enums.DjbGatewayMode; +import com.eactive.apim.portal.portalproperty.service.PortalPropertyService; +import java.net.URI; +import java.net.URISyntaxException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +/** + * 테스트베드 게이트웨이 설정을 {@code PTL_PROPERTY}(그룹 {@code Portal}, key prefix {@code djb.gateway.}) + * 에서 읽는 얇은 접근자. 별도 property-injection 인프라 대신 기존 + * {@link PortalPropertyService#getOrCreateProperty} 를 직접 사용한다(사용자 지시). + * + *

{@code getOrCreateProperty} 는 DB row 미존재 시 default 로 INSERT(그룹 존재 시)하지만 + * 공백 값 보정은 하지 않으므로, 여기 {@link #resolve} 에서 {@code isBlank → default} 폴백을 둔다. + * + *

비-Spring 컴포넌트인 {@code ApiTesterFilter}(@WebFilter)는 + * {@code ApplicationContextUtil.getContext().getBean(DjbTestbedGatewayProperty.class)} 로 접근한다. + */ +@Component +@RequiredArgsConstructor +@Slf4j +public class DjbTestbedGatewayProperty { + + private final PortalPropertyService portalPropertyService; + + private static final String GROUP = "Portal"; + + public static final String KEY_BASE_URL = "djb.gateway.base-url"; + public static final String KEY_TOKEN_PATH = "djb.gateway.token-path"; + public static final String KEY_API_KEY_HEADER = "djb.gateway.api-key-header"; + public static final String KEY_OAUTH_HEADER = "djb.gateway.oauth-token-header"; + public static final String KEY_TIMEOUT_SEC = "djb.gateway.timeout"; + public static final String KEY_USE_PROXY = "djb.gateway.use-proxy"; + public static final String KEY_TOKEN_USE_PROXY = "djb.gateway.token-use-proxy"; + public static final String KEY_MOCK_ACCESS_TOKEN = "djb.gateway.mock-access-token"; + public static final String KEY_SPEC_SERVER_URL = "djb.gateway.spec-server-url"; + + public static final String DEFAULT_BASE_URL = "PortalMock"; + /** + * {@link #KEY_SPEC_SERVER_URL} 미설정 표식. spec 서버 주소를 {@link #resolveApiBaseUrl} 기존 규칙 + * (GATEWAY→base-url, PortalMock→포탈 origin)으로 결정한다는 뜻. + * + *

빈 문자열을 기본값으로 쓰면 {@code PTL_PROPERTY.property_value} 가 NOT NULL 이라 + * Oracle 에서 빈 문자열=NULL 로 저장되며 제약 위반이 나므로, 비어 있지 않은 표식을 쓴다.

+ */ + public static final String DEFAULT_SPEC_SERVER_URL = "auto"; + public static final String DEFAULT_TIMEOUT_SEC = "10"; + public static final String DEFAULT_USE_PROXY = "true"; + public static final String DEFAULT_TOKEN_USE_PROXY = "true"; + private static final int TIMEOUT_FALLBACK_MS = 10_000; + /** GW 모드 실제 토큰 엔드포인트 경로 (DJERP_4000 OAuth 발급가이드 v0.2). */ + public static final String DEFAULT_TOKEN_PATH = "/dj/oauth/token"; + public static final String DEFAULT_API_KEY_HEADER = "X-DJB-API-Key"; + /** 게이트웨이 API 호출 시 액세스 토큰 헤더명 (가이드 v0.2: Authorization → X-AUTH-TOKEN). */ + public static final String DEFAULT_OAUTH_HEADER = "X-AUTH-TOKEN"; + + /** PortalMock 모드에서 사용하는 포털 기존 mock 토큰 경로. */ + public static final String PORTAL_MOCK_TOKEN_PATH = "/api/v1/oauth/token"; + + /** PortalMock 모드 토큰 응답의 access_token 기본값. */ + public static final String DEFAULT_MOCK_ACCESS_TOKEN = "djbank_gw_sample_token"; + + public String baseUrl() { + return resolve(KEY_BASE_URL, DEFAULT_BASE_URL, + "GW Base URL. 문자열 \"PortalMock\" 이면 ApiTesterFilter 가 mock 토큰 반환"); + } + + public String tokenPath() { + return resolve(KEY_TOKEN_PATH, DEFAULT_TOKEN_PATH, + "OAuth 토큰 발급 경로 (GW 모드에서만 사용)"); + } + + public String apiKeyHeader() { + return resolve(KEY_API_KEY_HEADER, DEFAULT_API_KEY_HEADER, + "API Key 전달 헤더명"); + } + + public String oauthHeader() { + return resolve(KEY_OAUTH_HEADER, DEFAULT_OAUTH_HEADER, + "OAuth 액세스 토큰 전달 헤더명 (Bearer 토큰)"); + } + + /** + * 테스트베드 API 호출 시 서버 프록시({@code /api/call-api}) 사용 여부. + * {@code false} 이면 브라우저에서 대상 주소로 직접 호출(CORS 허용 필요). 기본 true. + */ + public boolean useProxy() { + return parseBool(resolve(KEY_USE_PROXY, DEFAULT_USE_PROXY, + "테스트베드 API 호출 시 서버 프록시(/api/call-api) 사용 여부(true/false). false 이면 브라우저에서 직접 호출."), true); + } + + /** + * 토큰 발급 시 서버 프록시({@code /api/call-api}) 사용 여부. API 호출 프록시와 별개로 설정. + * {@code false} 이면 브라우저에서 토큰 URL 로 직접 호출(CORS 허용 필요). 기본 true. + * (mock 모드는 포탈 자체 mock 토큰이라 프록시 유지가 자연스럽고, 실 GW 는 직접호출로 뺄 수 있음.) + */ + public boolean tokenUseProxy() { + return parseBool(resolve(KEY_TOKEN_USE_PROXY, DEFAULT_TOKEN_USE_PROXY, + "테스트베드 토큰 발급 시 서버 프록시(/api/call-api) 사용 여부(true/false). false 이면 브라우저에서 직접 호출."), true); + } + + /** PortalMock 모드 토큰 응답에 넣을 access_token 값. */ + public String mockAccessToken() { + return resolve(KEY_MOCK_ACCESS_TOKEN, DEFAULT_MOCK_ACCESS_TOKEN, + "PortalMock 모드 토큰 발급 응답의 access_token 값"); + } + + /** + * 프로퍼티 값을 boolean 으로 해석. 레거시 {@code Y/N} 값도 자동 변환한다. + * {@code true}/{@code Y} → true, {@code false}/{@code N} → false, 그 외/null → {@code def}. + */ + private static boolean parseBool(String v, boolean def) { + if (v == null) { + return def; + } + String s = v.trim(); + if (s.equalsIgnoreCase("true") || s.equalsIgnoreCase("Y")) { + return true; + } + if (s.equalsIgnoreCase("false") || s.equalsIgnoreCase("N")) { + return false; + } + return def; + } + + /** 테스트베드 프록시 연결/응답 타임아웃(ms). 미설정/파싱 실패 시 10초. (단위 저장: 초) */ + public int timeoutMillis() { + try { + int sec = Integer.parseInt(resolve(KEY_TIMEOUT_SEC, DEFAULT_TIMEOUT_SEC, + "테스트베드 프록시 연결/응답 타임아웃(초)")); + return sec > 0 ? sec * 1000 : TIMEOUT_FALLBACK_MS; + } catch (Exception e) { + return TIMEOUT_FALLBACK_MS; + } + } + + public DjbGatewayMode resolveGatewayMode() { + return DEFAULT_BASE_URL.equalsIgnoreCase(baseUrl()) ? DjbGatewayMode.PORTAL_MOCK : DjbGatewayMode.GATEWAY; + } + + /** + * 브라우저에 내려주는 OAuth 토큰 발급 URL — spec 노출 주소({@link #resolveSpecServerUrl}) 기준. + * + *

PortalMock → {@code portalOrigin + /api/v1/oauth/token}(기존 mock 필터가 가로챔), + * GATEWAY → {@code specServerUrl + tokenPath}.

+ * + *

대외/대내 주소가 다른 환경에서 직접호출 모드({@code djb.gateway.token-use-proxy=false})는 + * 브라우저가 이 주소로 바로 나가므로 대외 주소여야 한다. 프록시 경유일 때는 이 값이 + * {@code original-url} 헤더로만 전달되고, 실제 forward 는 필터가 + * {@code baseUrl + tokenPath}(대내)로 수행한다.

+ */ + public String resolveTokenUrl(String portalOrigin) { + if (resolveGatewayMode() == DjbGatewayMode.PORTAL_MOCK) { + return stripTrailingSlash(portalOrigin) + PORTAL_MOCK_TOKEN_PATH; + } + return stripTrailingSlash(resolveSpecServerUrl(portalOrigin)) + tokenPath(); + } + + /** + * gw 응답유형의 실제 forward 대상 URL. 브라우저가 spec 서버주소(대외)로 만든 {@code originalUrl} 에서 + * path/query 만 떼어 대내 {@code base-url} 에 재조립한다 — 포탈 서버는 대외 주소로 나갈 수 없다. + * + *

PortalMock 모드는 실 GW 가 없으므로 원본을 그대로 쓴다. URL 파싱 실패 시에도 원본 유지(멱등).

+ */ + public String resolveGatewayCallUrl(String originalUrl) { + if (originalUrl == null || resolveGatewayMode() == DjbGatewayMode.PORTAL_MOCK) { + return originalUrl; + } + try { + URI uri = new URI(originalUrl); + String path = StringUtils.hasText(uri.getRawPath()) ? uri.getRawPath() : ""; + String query = StringUtils.hasText(uri.getRawQuery()) ? "?" + uri.getRawQuery() : ""; + return stripTrailingSlash(baseUrl()) + path + query; + } catch (URISyntaxException e) { + log.warn("gw 호출 URL 파싱 실패, original-url 그대로 사용 - url={}, cause={}", originalUrl, e.getMessage()); + return originalUrl; + } + } + + /** + * GW 응답유형(spec responseType=gw)에서 사용할 API 호출 base URL. + * PortalMock → 요청 포탈 origin(mock 필터가 프록시), GATEWAY → {@code baseUrl}. + * (별도 {@code swagger.gw.address} 프로퍼티를 두지 않고 이 값을 단일 기준으로 사용한다.) + */ + public String resolveApiBaseUrl(String portalOrigin) { + if (resolveGatewayMode() == DjbGatewayMode.PORTAL_MOCK) { + return stripTrailingSlash(portalOrigin); + } + return stripTrailingSlash(baseUrl()); + } + + /** + * spec({@code servers[0].url})에 노출할 주소. 응답유형과 무관하게 이 값 하나로 고정된다. + * + *

{@link #KEY_SPEC_SERVER_URL} 에 주소를 넣으면 그 값을 그대로 쓴다. PortalMock 모드처럼 + * {@code base-url} 이 실제 URL 이 아니어서 노출할 GW 주소를 코드가 알 수 없을 때 이 값으로 고정한다. + * 대외 공개 도메인이 내부 GW 주소와 다른 경우에도 사용한다.

+ * + *

{@value #DEFAULT_SPEC_SERVER_URL}(기본) 이면 {@link #resolveApiBaseUrl} 규칙을 그대로 따른다.

+ */ + public String resolveSpecServerUrl(String portalOrigin) { + String configured = specServerUrl(); + if (StringUtils.hasText(configured) && !DEFAULT_SPEC_SERVER_URL.equalsIgnoreCase(configured.trim())) { + return stripTrailingSlash(configured); + } + return resolveApiBaseUrl(portalOrigin); + } + + /** spec servers 노출 주소. {@value #DEFAULT_SPEC_SERVER_URL} 이면 미설정(자동 결정). */ + public String specServerUrl() { + return resolve(KEY_SPEC_SERVER_URL, DEFAULT_SPEC_SERVER_URL, + "spec(servers)에 노출할 API 호출 주소. \"" + DEFAULT_SPEC_SERVER_URL + + "\" 이면 GW Base URL(PortalMock 이면 포탈 주소)로 자동 결정"); + } + + private String resolve(String key, String defaultValue, String description) { + String value = portalPropertyService.getOrCreateProperty(GROUP, key, defaultValue, description); + return StringUtils.hasText(value) ? value.trim() : defaultValue; + } + + private static String stripTrailingSlash(String s) { + if (s == null) { + return ""; + } + return s.endsWith("/") ? s.substring(0, s.length() - 1) : s; + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/testbed/controller/DjbTestbedAuthController.java b/src/main/java/com/eactive/apim/portal/djb/testbed/controller/DjbTestbedAuthController.java new file mode 100644 index 0000000..3d9434f --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/testbed/controller/DjbTestbedAuthController.java @@ -0,0 +1,51 @@ +package com.eactive.apim.portal.djb.testbed.controller; + +import com.eactive.apim.portal.djb.testbed.dto.DjbCredentialSecretDto; +import com.eactive.apim.portal.djb.testbed.dto.DjbTestbedContextDto; +import com.eactive.apim.portal.djb.testbed.service.DjbTestbedAuthService; +import javax.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + * 테스트베드 인증 컨텍스트/시크릿 REST. + * 컨트롤러 진입은 인증 없이 허용하되(비대상자도 context 를 받아야 함), + * 실제 인증/역할/소유권 검증은 {@link DjbTestbedAuthService} 가 수행한다. + */ +@RestController +@RequestMapping("/djb/testbed/auth") +@RequiredArgsConstructor +public class DjbTestbedAuthController { + + private final DjbTestbedAuthService authService; + + @GetMapping("/context") + public ResponseEntity context(HttpServletRequest request) { + return ResponseEntity.ok(authService.buildContext(resolveOrigin(request))); + } + + @GetMapping("/credentials/{clientId}/secret") + public ResponseEntity credentialSecret(@PathVariable String clientId, + @RequestParam String apiId) { + DjbCredentialSecretDto secret = authService.getCredentialSecret(clientId, apiId); + return ResponseEntity.ok() + .header(HttpHeaders.CACHE_CONTROL, "no-store") + .header(HttpHeaders.PRAGMA, "no-cache") + .body(secret); + } + + /** scheme://host[:port] (기본 포트는 생략). PortalMock 토큰 URL 구성에 사용. */ + private String resolveOrigin(HttpServletRequest request) { + String scheme = request.getScheme(); + String host = request.getServerName(); + int port = request.getServerPort(); + boolean defaultPort = ("http".equals(scheme) && port == 80) || ("https".equals(scheme) && port == 443); + return scheme + "://" + host + (defaultPort ? "" : ":" + port); + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/testbed/controller/DjbTestbedSpecController.java b/src/main/java/com/eactive/apim/portal/djb/testbed/controller/DjbTestbedSpecController.java new file mode 100644 index 0000000..991d72b --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/testbed/controller/DjbTestbedSpecController.java @@ -0,0 +1,99 @@ +package com.eactive.apim.portal.djb.testbed.controller; + +import com.eactive.apim.portal.apispec.entity.ApiSpecInfo; +import com.eactive.apim.portal.apispec.service.ApiSpecInfoService; +import com.eactive.apim.portal.djb.testbed.enums.DjbAuthType; +import com.eactive.apim.portal.djb.testbed.service.DjbSwaggerSpecEnricher; +import com.eactive.apim.portal.djb.testbed.service.DjbTestbedAuthService; +import com.eactive.apim.portal.djb.testbed.service.DjbTestbedSpecServerRewriter; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Optional; +import javax.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.util.FileCopyUtils; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * testbed spec(swagger.json/yaml)에 AUTHTYPE 기반 securityScheme 를 주입하고, + * 서버 sentinel({@link DjbTestbedSpecServerRewriter#SERVER_SENTINEL})을 실주소로 치환해 반환한다. + *
    + *
  • {@code swagger.json}/{@code swagger.yaml} : 외부 공개/다운로드용 — 항상 GW 주소로 치환
  • + *
  • {@code swagger-ui.json} : Swagger UI 전용 — API SPEC 설정(responseType: sample/mock/gw)에 따라 치환
  • + *
+ * {@code default-token-api-spec} 은 클래스패스 기본 spec 을 그대로 반환(auth enrich 대상 외). + */ +@RestController +@RequestMapping("/djb/testbed/apis") +@RequiredArgsConstructor +@Slf4j +public class DjbTestbedSpecController { + + private final ApiSpecInfoService apiSpecInfoService; + private final DjbTestbedAuthService authService; + private final DjbSwaggerSpecEnricher enricher; + private final DjbTestbedSpecServerRewriter serverRewriter; + + private static final String DEFAULT_TOKEN_API_ID = "default-token-api-spec"; + private static final String DEFAULT_SPEC_PATH = "swagger/default-token-api-spec.json"; + + @GetMapping(value = "/{id}/swagger.json", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity swaggerWithAuth(@PathVariable String id, HttpServletRequest request) throws IOException { + String json = buildSpecJson(id, request, false); + return json == null ? ResponseEntity.notFound().build() : ResponseEntity.ok(json); + } + + @GetMapping(value = "/{id}/swagger.yaml", produces = "application/x-yaml") + public ResponseEntity swaggerYamlWithAuth(@PathVariable String id, HttpServletRequest request) throws IOException { + String json = buildSpecJson(id, request, false); + return json == null ? ResponseEntity.notFound().build() : ResponseEntity.ok(serverRewriter.toYaml(json)); + } + + /** + * Swagger UI 전용 spec. 서버 주소는 다운로드용과 동일하게 GW 기준이며, 여기에만 + * {@code x-original-api-id} 를 심어 UI 실행 시 프록시가 API ID 로 스펙을 찾게 한다. + */ + @GetMapping(value = "/{id}/swagger-ui.json", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity swaggerForUi(@PathVariable String id, HttpServletRequest request) throws IOException { + String json = buildSpecJson(id, request, true); + return json == null ? ResponseEntity.notFound().build() : ResponseEntity.ok(json); + } + + /** + * default 토큰 spec 또는 저장 spec(auth enrich + 서버 GW 치환)을 JSON 으로 반환. 없으면 null. + * @param forUi true 면 Swagger UI 실행용으로 {@code x-original-api-id} 를 주입한다 + */ + private String buildSpecJson(String id, HttpServletRequest request, boolean forUi) throws IOException { + if (DEFAULT_TOKEN_API_ID.equals(id)) { + // 클래스패스 기본 토큰 spec 은 서버 치환 대상이 아니다. path 가 포탈 mock 토큰 경로 + // (PORTAL_MOCK_TOKEN_PATH)라 GW 호스트를 붙이면 실재하지 않는 주소가 되고, servers 를 + // 비워 두면 Swagger UI 가 문서 origin(포탈)을 사용해 프록시가 mock 토큰을 발급한다. + Resource resource = new ClassPathResource(DEFAULT_SPEC_PATH); + return new String(FileCopyUtils.copyToByteArray(resource.getInputStream()), StandardCharsets.UTF_8); + } + + Optional spec = apiSpecInfoService.findById(id); + if (!spec.isPresent() || !StringUtils.hasText(spec.get().getTestbedSpec())) { + return null; + } + + DjbAuthType authType = authService.resolveAuthType(id); + String enriched = enricher.enrich(spec.get().getTestbedSpec(), authType); + String rewritten = serverRewriter.rewriteServerToGateway(enriched, request); + if (!forUi) { + return rewritten; + } + // UI 용 spec 에만 API ID 를 심는다. 서버주소가 GW 로 고정돼 mock 은 호출 URL 의 path 로 스펙을 + // 되찾을 수 없으므로, Swagger UI 가 original-api-id 헤더로 프록시에 API ID 를 전달하게 한다. + return enricher.injectOriginalApiId(rewritten, id); + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/testbed/dto/DjbCredentialOptionDto.java b/src/main/java/com/eactive/apim/portal/djb/testbed/dto/DjbCredentialOptionDto.java new file mode 100644 index 0000000..467628b --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/testbed/dto/DjbCredentialOptionDto.java @@ -0,0 +1,21 @@ +package com.eactive.apim.portal.djb.testbed.dto; + +import lombok.Builder; +import lombok.Data; + +/** + * 테스트베드 앱 셀렉트에 노출할 앱(PTL_CREDENTIAL) 1건. 시크릿은 포함하지 않는다. + */ +@Data +@Builder +public class DjbCredentialOptionDto { + + /** PTL_CREDENTIAL.clientid */ + private String clientId; + + /** 사용자에게 보여줄 앱명(PTL_CREDENTIAL.clientname) */ + private String clientName; + + /** "0": 차단, "1": 정상 */ + private String appStatus; +} diff --git a/src/main/java/com/eactive/apim/portal/djb/testbed/dto/DjbCredentialSecretDto.java b/src/main/java/com/eactive/apim/portal/djb/testbed/dto/DjbCredentialSecretDto.java new file mode 100644 index 0000000..7f2f39e --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/testbed/dto/DjbCredentialSecretDto.java @@ -0,0 +1,20 @@ +package com.eactive.apim.portal.djb.testbed.dto; + +import com.eactive.apim.portal.djb.testbed.enums.DjbAuthType; +import lombok.Builder; +import lombok.Data; + +/** + * 선택한 앱의 시크릿 단건 응답(캐시 금지 · 로그 마스킹 대상). + * OAUTH 시 {@code clientSecret} 은 client_secret, API_KEY 시 API Key 값이다. + */ +@Data +@Builder +public class DjbCredentialSecretDto { + + private String clientId; + + private String clientSecret; + + private DjbAuthType authType; +} diff --git a/src/main/java/com/eactive/apim/portal/djb/testbed/dto/DjbTestbedContextDto.java b/src/main/java/com/eactive/apim/portal/djb/testbed/dto/DjbTestbedContextDto.java new file mode 100644 index 0000000..b9d9a09 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/testbed/dto/DjbTestbedContextDto.java @@ -0,0 +1,42 @@ +package com.eactive.apim.portal.djb.testbed.dto; + +import com.eactive.apim.portal.djb.testbed.enums.DjbGatewayMode; +import java.util.List; +import lombok.Builder; +import lombok.Data; + +/** + * {@code GET /djb/testbed/auth/context} 응답. 시크릿은 포함하지 않는다. + */ +@Data +@Builder +public class DjbTestbedContextDto { + + /** 인증 정보 제공 대상자 여부 */ + private boolean eligible; + + /** 비대상 사유 코드: ANONYMOUS / INDIVIDUAL_USER / NO_CREDENTIAL (대상자면 null) */ + private String reason; + + /** 사용자가 선택할 수 있는 앱 목록 */ + private List credentials; + + /** 게이트웨이 정보(시크릿 제외) */ + private GatewayInfo gateway; + + @Data + @Builder + public static class GatewayInfo { + private DjbGatewayMode mode; + private String baseUrl; + private String tokenUrl; + private String apiKeyHeader; + private String oauthTokenHeader; + + /** + * sample/mock 응답유형 API 에서 실제 토큰 발급 없이 주입할 고정 액세스 토큰. + * (실호출이 없거나 mock 서버로만 나가므로 검증 대상이 아니다 — {@code ApiTesterFilter} 의 mock 토큰과 동일 값) + */ + private String mockAccessToken; + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/testbed/enums/DjbAuthType.java b/src/main/java/com/eactive/apim/portal/djb/testbed/enums/DjbAuthType.java new file mode 100644 index 0000000..4bcffd1 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/testbed/enums/DjbAuthType.java @@ -0,0 +1,42 @@ +package com.eactive.apim.portal.djb.testbed.enums; + +import com.eactive.apim.portal.djb.testbed.exception.DjbUnsupportedAuthTypeException; + +/** + * 테스트베드가 지원하는 인증 체계. {@code TSEAIHE01.AUTHTYPE}(소문자 저장) 값을 매핑한다. + *
    + *
  • {@code oauth}/{@code oauth2}/{@code ca} → {@link #OAUTH}
  • + *
  • {@code api_key}/{@code apikey} → {@link #API_KEY}
  • + *
  • {@code none}/{@code no}/{@code noauth} → {@link #NONE}(인증 없이 호출 — 앱 선택 불필요)
  • + *
  • {@code null}(EAIMessage 미존재) 및 그 외 → {@link DjbUnsupportedAuthTypeException}
  • + *
+ * ({@code ca} 는 게이트웨이 레거시 매퍼 {@code ObpApiService.mapAuthType} 이 OAUTH2 로 취급하는 값.) + */ +public enum DjbAuthType { + OAUTH, + API_KEY, + NONE; + + public static DjbAuthType fromAuthtype(String authtype) { + // null(EAIMessage 미존재)은 데이터 문제 → 종전대로 예외. "none" 은 인증 없는 API. + if (authtype == null) { + throw new DjbUnsupportedAuthTypeException("null"); + } + String normalized = authtype.trim().toLowerCase(); + switch (normalized) { + case "oauth": + case "oauth2": + case "ca": + return OAUTH; + case "api_key": + case "apikey": + return API_KEY; + case "none": + case "no": + case "noauth": + return NONE; + default: + throw new DjbUnsupportedAuthTypeException(authtype); + } + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/testbed/enums/DjbGatewayMode.java b/src/main/java/com/eactive/apim/portal/djb/testbed/enums/DjbGatewayMode.java new file mode 100644 index 0000000..6da47b5 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/testbed/enums/DjbGatewayMode.java @@ -0,0 +1,14 @@ +package com.eactive.apim.portal.djb.testbed.enums; + +/** + * 테스트베드 호출이 향하는 대상 모드. + *
    + *
  • {@code PORTAL_MOCK} — {@code djb.gateway.base-url} 이 문자열 "PortalMock" 인 경우. + * {@code ApiTesterFilter} 가 mock 토큰/샘플 응답을 반환한다.
  • + *
  • {@code GATEWAY} — 그 외(실제 게이트웨이 URL). 토큰 발급 요청을 실 게이트웨이로 forward.
  • + *
+ */ +public enum DjbGatewayMode { + PORTAL_MOCK, + GATEWAY +} diff --git a/src/main/java/com/eactive/apim/portal/djb/testbed/exception/DjbUnsupportedAuthTypeException.java b/src/main/java/com/eactive/apim/portal/djb/testbed/exception/DjbUnsupportedAuthTypeException.java new file mode 100644 index 0000000..24bf1de --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/testbed/exception/DjbUnsupportedAuthTypeException.java @@ -0,0 +1,20 @@ +package com.eactive.apim.portal.djb.testbed.exception; + +/** + * TSEAIHE01.AUTHTYPE 값이 테스트베드가 지원하는 인증 체계(oauth/api_key)로 + * 매핑되지 않을 때 발생. {@link com.eactive.apim.portal.djb.testbed.advice.DjbTestbedExceptionHandler} + * 에서 400 응답으로 변환된다. + */ +public class DjbUnsupportedAuthTypeException extends RuntimeException { + + private final String authtype; + + public DjbUnsupportedAuthTypeException(String authtype) { + super("지원하지 않는 인증 체계: " + authtype); + this.authtype = authtype; + } + + public String getAuthtype() { + return authtype; + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/testbed/service/DjbSwaggerSpecEnricher.java b/src/main/java/com/eactive/apim/portal/djb/testbed/service/DjbSwaggerSpecEnricher.java new file mode 100644 index 0000000..8535186 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/testbed/service/DjbSwaggerSpecEnricher.java @@ -0,0 +1,149 @@ +package com.eactive.apim.portal.djb.testbed.service; + +import com.eactive.apim.portal.djb.testbed.config.DjbTestbedGatewayProperty; +import com.eactive.apim.portal.djb.testbed.enums.DjbAuthType; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +/** + * testbed spec(JSON)에 인증 체계를 주입한다. + * + *

현재 데이터는 OpenAPI 3.0(auto-gen {@code "openapi":"3.0.0"})이므로 + * {@code components.securitySchemes} + 글로벌 {@code security} 에 주입한다. + * (원본이 Swagger 2.0 이면 {@code securityDefinitions} 로 폴백.) + * + *

게이트웨이가 OAuth 액세스 토큰을 표준 {@code Authorization} 이 아니라 커스텀 헤더 + * {@code X-AUTH-TOKEN: Bearer ...} 로 받으므로(DJERP_4000 v0.2), OAuth 도 API_KEY 와 동일하게 + * apiKey-in-header 스킴으로 모델링한다(헤더명만 상이). 토큰 발급은 템플릿 JS가 + * {@code /api/call-api} 경유로 수행해 값을 {@code "Bearer "} 으로 authorize 한다. + * + *

원본의 {@code paths}, {@code x-*} 확장, 기타 {@code components} 는 트리 조작으로 보존한다 + * (문자열 치환 금지). + */ +@Service +@RequiredArgsConstructor +public class DjbSwaggerSpecEnricher { + + private final ObjectMapper objectMapper; + private final DjbTestbedGatewayProperty gatewayProperty; + + public static final String OAUTH_SCHEME = "djbOAuth"; + public static final String API_KEY_SCHEME = "djbApiKey"; + + /** operation 에 주입하는 API ID 확장 키. 템플릿 JS 가 {@code original-api-id} 헤더로 프록시에 전달한다. */ + public static final String API_ID_EXTENSION = "x-original-api-id"; + + /** OpenAPI path item 에서 operation 으로 취급하는 필드 (그 외 parameters/servers/summary 등은 제외). */ + private static final String[] HTTP_METHODS = + {"get", "put", "post", "delete", "options", "head", "patch", "trace"}; + + public String enrich(String specJson, DjbAuthType authType) throws JsonProcessingException { + JsonNode parsed = objectMapper.readTree(specJson); + if (!parsed.isObject()) { + // 예상 밖 형태면 원본 유지(멱등) + return specJson; + } + ObjectNode root = (ObjectNode) parsed; + + // NONE: 인증 없이 호출 — 글로벌 security / securityScheme 흔적을 제거해 + // Swagger UI 의 'Authorize' 버튼이 뜨지 않게 한다. (저장 spec 에 빈 securitySchemes 가 + // 남아 있으면 UI 가 빈 인증 모달 버튼을 렌더하므로 정리한다.) + if (authType == DjbAuthType.NONE) { + return stripSecurity(root); + } + + String schemeName = (authType == DjbAuthType.OAUTH) ? OAUTH_SCHEME : API_KEY_SCHEME; + ObjectNode scheme = buildApiKeyScheme(authType); + + boolean swagger2 = root.has("swagger") && !root.has("openapi"); + if (swagger2) { + getOrCreateObject(root, "securityDefinitions").set(schemeName, scheme); + } else { + ObjectNode components = getOrCreateObject(root, "components"); + getOrCreateObject(components, "securitySchemes").set(schemeName, scheme); + } + + // 글로벌 security 요구사항 (apiKey 스킴은 빈 스코프 배열) + ArrayNode security = objectMapper.createArrayNode(); + ObjectNode requirement = objectMapper.createObjectNode(); + requirement.set(schemeName, objectMapper.createArrayNode()); + security.add(requirement); + root.set("security", security); + + return objectMapper.writeValueAsString(root); + } + + /** + * spec 의 모든 operation 에 {@value #API_ID_EXTENSION} 를 주입한다. + * + *

테스트베드 프록시({@code /api/call-api})는 스펙을 찾을 때 {@code original-url} 의 path 로 매칭했는데, + * mock/gw 응답유형에서는 서버 주소가 mockUrl·GW base 로 치환되어 path 가 저장된 {@code api_url} 과 + * 일치하지 않는다. 템플릿 JS 가 이 확장값을 {@code original-api-id} 헤더로 보내면 프록시가 API ID 로 + * 정확히 스펙을 찾을 수 있다.

+ * + *

파싱 불가·paths 부재 시 원본 유지(멱등).

+ */ + public String injectOriginalApiId(String specJson, String apiId) throws JsonProcessingException { + if (specJson == null || apiId == null || apiId.trim().isEmpty()) { + return specJson; + } + JsonNode parsed = objectMapper.readTree(specJson); + if (!parsed.isObject()) { + return specJson; + } + JsonNode paths = parsed.get("paths"); + if (paths == null || !paths.isObject()) { + return specJson; + } + for (JsonNode pathItem : paths) { + if (!pathItem.isObject()) { + continue; + } + for (String method : HTTP_METHODS) { + JsonNode operation = pathItem.get(method); + if (operation != null && operation.isObject()) { + ((ObjectNode) operation).put(API_ID_EXTENSION, apiId); + } + } + } + return objectMapper.writeValueAsString(parsed); + } + + /** {@code { "type":"apiKey", "in":"header", "name":"
" }} — 2.0/3.0 동일 형태. */ + private ObjectNode buildApiKeyScheme(DjbAuthType authType) { + String headerName = (authType == DjbAuthType.OAUTH) + ? gatewayProperty.oauthHeader() + : gatewayProperty.apiKeyHeader(); + ObjectNode scheme = objectMapper.createObjectNode(); + scheme.put("type", "apiKey"); + scheme.put("in", "header"); + scheme.put("name", headerName); + return scheme; + } + + /** NONE 용: 글로벌 {@code security} / {@code components.securitySchemes} / {@code securityDefinitions} 제거. */ + private String stripSecurity(ObjectNode root) throws JsonProcessingException { + root.remove("security"); + root.remove("securityDefinitions"); // swagger 2.0 + JsonNode components = root.get("components"); + if (components != null && components.isObject()) { + ((ObjectNode) components).remove("securitySchemes"); + } + return objectMapper.writeValueAsString(root); + } + + private ObjectNode getOrCreateObject(ObjectNode parent, String field) { + JsonNode node = parent.get(field); + if (node != null && node.isObject()) { + return (ObjectNode) node; + } + ObjectNode created = objectMapper.createObjectNode(); + parent.set(field, created); + return created; + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/testbed/service/DjbTestbedAuthService.java b/src/main/java/com/eactive/apim/portal/djb/testbed/service/DjbTestbedAuthService.java new file mode 100644 index 0000000..6bd81a8 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/testbed/service/DjbTestbedAuthService.java @@ -0,0 +1,134 @@ +package com.eactive.apim.portal.djb.testbed.service; + +import com.eactive.apim.gateway.data.eaimessage.EAIMessageRepository; +import com.eactive.apim.portal.app.entity.Credential; +import com.eactive.apim.portal.app.repository.CredentialRepository; +import com.eactive.apim.portal.common.user.PortalAuthenticatedUser; +import com.eactive.apim.portal.common.util.SecurityUtil; +import com.eactive.apim.portal.djb.testbed.config.DjbTestbedGatewayProperty; +import com.eactive.apim.portal.djb.testbed.dto.DjbCredentialOptionDto; +import com.eactive.apim.portal.djb.testbed.dto.DjbCredentialSecretDto; +import com.eactive.apim.portal.djb.testbed.dto.DjbTestbedContextDto; +import com.eactive.apim.portal.djb.testbed.dto.DjbTestbedContextDto.GatewayInfo; +import com.eactive.apim.portal.djb.testbed.enums.DjbAuthType; +import com.eactive.apim.portal.portaluser.entity.PortalUserEnums.RoleCode; +import com.eactive.eai.data.entity.onl.message.EAIMessageEntity; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.stereotype.Service; + +/** + * 테스트베드 인증 컨텍스트 구성 · 앱 시크릿 조회 · authtype 매핑. + * 인증/역할/소유권 검증은 이 서비스에서 수행한다({@code SecurityFilterChain} 은 URL 인가 블록이 없음). + */ +@Service +@RequiredArgsConstructor +public class DjbTestbedAuthService { + + private final CredentialRepository credentialRepository; + private final EAIMessageRepository eaiMessageRepository; + private final DjbTestbedGatewayProperty gatewayProperty; + + private static final String APP_STATUS_ACTIVE = "1"; + + /** + * 비대상 사유: {@code ANONYMOUS}(비로그인) / {@code INDIVIDUAL_USER}(ROLE_USER) / + * {@code NO_CREDENTIAL}(appstatus='1' 앱 0건). 하나라도 해당하면 {@code eligible=false}. + */ + public DjbTestbedContextDto buildContext(String portalOrigin) { + GatewayInfo gateway = buildGatewayInfo(portalOrigin); + + if (!SecurityUtil.isAuthenticated()) { + return notEligible("ANONYMOUS", gateway); + } + PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser(); + if (user.getRoleCode() == RoleCode.ROLE_USER) { + return notEligible("INDIVIDUAL_USER", gateway); + } + + String orgId = SecurityUtil.getUserOrg().getId(); + List options = credentialRepository.findAllByOrgid(orgId).stream() + .filter(c -> APP_STATUS_ACTIVE.equals(c.getAppstatus())) + .map(this::toOption) + .collect(Collectors.toList()); + + if (options.isEmpty()) { + return notEligible("NO_CREDENTIAL", gateway); + } + + return DjbTestbedContextDto.builder() + .eligible(true) + .reason(null) + .credentials(options) + .gateway(gateway) + .build(); + } + + /** + * 선택 앱의 시크릿 단건. 로그인 + 비-ROLE_USER + 본인 소유(orgId 일치) 검증 후 반환. + * 소유 불일치/비대상은 {@link AccessDeniedException}(→ 403). + */ + public DjbCredentialSecretDto getCredentialSecret(String clientId, String apiId) { + if (!SecurityUtil.isAuthenticated()) { + throw new AccessDeniedException("로그인이 필요합니다."); + } + PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser(); + if (user.getRoleCode() == RoleCode.ROLE_USER) { + throw new AccessDeniedException("개인사용자는 이용할 수 없습니다."); + } + + String orgId = SecurityUtil.getUserOrg().getId(); + Credential credential = credentialRepository.findByClientidAndOrgid(clientId, orgId) + .orElseThrow(() -> new AccessDeniedException("본인 소유의 앱이 아닙니다.")); + + DjbAuthType authType = resolveAuthType(apiId); + + return DjbCredentialSecretDto.builder() + .clientId(credential.getClientid()) + .clientSecret(credential.getClientsecret()) + .authType(authType) + .build(); + } + + /** + * apiId(=TSEAIHE01.eaisvcname, admin 입력 컨벤션)로 authtype 조회 후 매핑. + * 미매핑/미존재는 {@code DjbUnsupportedAuthTypeException}(→ 400). + */ + public DjbAuthType resolveAuthType(String apiId) { + String authtype = eaiMessageRepository.findById(apiId) + .map(EAIMessageEntity::getAuthtype) + .orElse(null); + return DjbAuthType.fromAuthtype(authtype); + } + + private DjbCredentialOptionDto toOption(Credential c) { + return DjbCredentialOptionDto.builder() + .clientId(c.getClientid()) + .clientName(c.getClientname()) + .appStatus(c.getAppstatus()) + .build(); + } + + private GatewayInfo buildGatewayInfo(String portalOrigin) { + return GatewayInfo.builder() + .mode(gatewayProperty.resolveGatewayMode()) + .baseUrl(gatewayProperty.baseUrl()) + .tokenUrl(gatewayProperty.resolveTokenUrl(portalOrigin)) + .apiKeyHeader(gatewayProperty.apiKeyHeader()) + .oauthTokenHeader(gatewayProperty.oauthHeader()) + .mockAccessToken(gatewayProperty.mockAccessToken()) + .build(); + } + + private DjbTestbedContextDto notEligible(String reason, GatewayInfo gateway) { + return DjbTestbedContextDto.builder() + .eligible(false) + .reason(reason) + .credentials(Collections.emptyList()) + .gateway(gateway) + .build(); + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/testbed/service/DjbTestbedSpecServerRewriter.java b/src/main/java/com/eactive/apim/portal/djb/testbed/service/DjbTestbedSpecServerRewriter.java new file mode 100644 index 0000000..e91e7da --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/testbed/service/DjbTestbedSpecServerRewriter.java @@ -0,0 +1,117 @@ +package com.eactive.apim.portal.djb.testbed.service; + +import com.eactive.apim.portal.djb.testbed.config.DjbTestbedGatewayProperty; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import javax.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.Yaml; + +/** + * testbed spec(JSON)의 서버 주소를 제공 시점에 게이트웨이 주소로 치환한다. + * + *

spec 의 {@code servers} 는 응답유형(sample/mock/gw)과 무관하게 항상 GW 기준이다. + * spec 은 "이 API 를 실제로 어디로 호출하는가"의 공개 계약이고 그 기준은 게이트웨이 하나다. + * admin(app.js buildOpenApiSpec)도 같은 규칙으로 저장하며, 포탈은 제공 시점에 자기 환경의 + * {@link DjbTestbedGatewayProperty#resolveSpecServerUrl} 로 덮어써 저장 환경(dev)의 주소가 다른 환경까지 + * 따라오지 않게 한다. 노출 주소를 명시 고정해야 하면 + * {@code djb.gateway.spec-server-url}(PTL_PROPERTY) 에 넣는다. + * + *

응답유형에 따른 차이는 테스트베드 동작에서만 난다 — Swagger UI 의 실행은 + * {@code /api/call-api} 프록시를 타고, {@code ApiTesterFilter} 가 응답유형별로 + * sample(저장 샘플 응답) / mock({@code mockUrl} forward) / gw(게이트웨이 forward) 를 가른다. + * 즉 표시 주소는 GW 로 고정이고 호출 대상만 갈린다. + * + *

치환은 서버 값 유무와 무관하게 트리 조작으로 덮어쓴다(문자열 치환 아님). 과거 admin 이 mock/포탈 + * 주소를 굳혀 저장한 spec 도 GW 기준으로 정정된다. 다만 그 시절 mock 저장본은 {@code paths} 키가 mock + * 경로로 바뀌어 있어, 어댑터경로 복원은 admin 재저장이 필요하다. + */ +@Service +@RequiredArgsConstructor +@Slf4j +public class DjbTestbedSpecServerRewriter { + + private final DjbTestbedGatewayProperty gatewayProperty; + private final ObjectMapper objectMapper; + + /** + * spec 의 {@code servers} 를 GW 주소로 치환해 반환한다. UI 표시용·다운로드용 구분 없이 동일 기준. + * + * @return 치환된 JSON. 파싱 실패·형태 불일치 시 원본 유지(멱등). + */ + public String rewriteServerToGateway(String specJson, HttpServletRequest request) { + if (specJson == null) { + return null; + } + String base = stripTrailingSlash(gatewayProperty.resolveSpecServerUrl(originOf(request))); + try { + JsonNode parsed = objectMapper.readTree(specJson); + if (!parsed.isObject()) { + return specJson; + } + ObjectNode root = (ObjectNode) parsed; + if (StringUtils.hasText(base)) { + ArrayNode servers = objectMapper.createArrayNode(); + servers.add(objectMapper.createObjectNode().put("url", base)); + root.set("servers", servers); + } else { + // GW 주소 미확정 — servers 를 비워 Swagger UI 가 문서 origin(포탈)을 쓰게 한다. + root.remove("servers"); + } + return objectMapper.writeValueAsString(root); + } catch (Exception e) { + log.error("spec 서버 치환 실패, 원본 유지 - base={}", base, e); + return specJson; + } + } + + /** spec JSON → YAML 문자열. 변환 실패 시 JSON 원본 반환. */ + public String toYaml(String specJson) { + try { + Object tree = objectMapper.readValue(specJson, Object.class); + DumperOptions opts = new DumperOptions(); + opts.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + opts.setPrettyFlow(true); + return new Yaml(opts).dump(tree); + } catch (Exception e) { + log.error("spec YAML 변환 실패, JSON 원본 반환", e); + return specJson; + } + } + + /** 요청 기준 포탈 origin(scheme://host[:port]) — 리버스 프록시 X-Forwarded-* 우선. */ + public static String originOf(HttpServletRequest request) { + if (request == null) { + return ""; + } + String scheme = firstNonBlank(request.getHeader("X-Forwarded-Proto"), request.getScheme()); + String host = firstNonBlank(request.getHeader("X-Forwarded-Host"), request.getHeader("Host")); + if (StringUtils.hasText(host)) { + return scheme + "://" + host.trim(); + } + int port = request.getServerPort(); + String hostPort = request.getServerName() + ((port == 80 || port == 443) ? "" : ":" + port); + return scheme + "://" + hostPort; + } + + private static String firstNonBlank(String a, String b) { + return StringUtils.hasText(a) ? a.trim() : b; + } + + private static String stripTrailingSlash(String s) { + if (s == null) { + return null; + } + String t = s.trim(); + while (t.endsWith("/")) { + t = t.substring(0, t.length() - 1); + } + return t; + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/webhook/controller/WebhookController.java b/src/main/java/com/eactive/apim/portal/djb/webhook/controller/WebhookController.java new file mode 100644 index 0000000..84b0320 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/webhook/controller/WebhookController.java @@ -0,0 +1,388 @@ +package com.eactive.apim.portal.djb.webhook.controller; + +import com.eactive.apim.portal.apps.apiservice.dto.ApiGroupSearch; +import com.eactive.apim.portal.apps.app.service.AppServiceFacade; +import com.eactive.apim.portal.apps.apiservice.service.ApiServiceService; +import com.eactive.apim.portal.common.exception.UserErrorMessageResolver; +import com.eactive.apim.portal.common.user.PortalAuthenticatedUser; +import com.eactive.apim.portal.common.util.SecurityUtil; +import com.eactive.apim.portal.djb.webhook.dto.WebhookDTO; +import com.eactive.apim.portal.djb.webhook.dto.WebhookRegistrationDTO; +import com.eactive.apim.portal.djb.webhook.service.WebhookEventTypeProvider; +import com.eactive.apim.portal.djb.webhook.service.WebhookService; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import javax.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.access.annotation.Secured; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.SessionAttributes; +import org.springframework.web.bind.support.SessionStatus; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; + +/** + * Webhook 신청/관리 (기업 사용자, ROLE_APP). + * + * App API Key 신청의 3-Step + 세션 + 비밀번호 재인증 패턴을 답습하되, 승인 워크플로우 없이 즉시 발급한다. + * 참조: {@code apps/app/controller/MyAppController}. + */ +@Slf4j +@Controller +@RequestMapping("/webhook") +@Secured("ROLE_WEBHOOK") +@RequiredArgsConstructor +@SessionAttributes({"webhookRegistration", "webhookModification"}) +public class WebhookController { + // Webhook 관리는 법인 관리자 전용(ROLE_WEBHOOK). 조회/신청/수정/삭제/Secret 관리 모두 관리자만 가능. + // (개발자 ROLE_CORP_USER 의 앱 권한 확장과 분리하기 위해 ROLE_API_KEY_REQUEST 에서 별도 authority 로 격리) + + private static final int TOTAL_STEPS = 3; + + private final WebhookService webhookService; + private final WebhookEventTypeProvider eventTypeProvider; + private final ApiServiceService apiServiceService; + private final AppServiceFacade appServiceFacade; + + @ModelAttribute("webhookRegistration") + public WebhookRegistrationDTO webhookRegistration() { + return new WebhookRegistrationDTO(); + } + + @ModelAttribute("webhookModification") + public WebhookRegistrationDTO webhookModification() { + return new WebhookRegistrationDTO(); + } + + // ============================ 조회 ============================ + + @Secured("ROLE_WEBHOOK") + @GetMapping + public ModelAndView index() { + String orgId = currentOrgId(); + Optional webhook = webhookService.getByOrg(orgId); + if (webhook.isPresent()) { + ModelAndView mav = new ModelAndView("apps/webhook/webhookList"); + mav.addObject("webhook", webhook.get()); + return mav; + } + return new ModelAndView("apps/webhook/webhookEmpty"); + } + + // ======================= 신규 신청 플로우 ======================= + + @GetMapping("/register/step1") + public ModelAndView registerStep1( + @RequestParam(value = "clear", required = false, defaultValue = "false") boolean clear, + @ModelAttribute("webhookRegistration") WebhookRegistrationDTO registration, + SessionStatus sessionStatus, + Model model) { + + if (webhookService.existsByOrg(currentOrgId())) { + return new ModelAndView("redirect:/webhook"); + } + if (clear) { + sessionStatus.setComplete(); + registration = new WebhookRegistrationDTO(); + model.addAttribute("webhookRegistration", registration); + } + registration.setRequestType("NEW"); + + ModelAndView mav = new ModelAndView("apps/webhook/webhookRegisterStep1"); + mav.addObject("eventTypes", eventTypeProvider.getAll()); + addStepModel(mav, 1); + return mav; + } + + @PostMapping("/register/step1") + public ModelAndView processStep1( + @Valid @ModelAttribute("webhookRegistration") WebhookRegistrationDTO registration, + BindingResult bindingResult, + Model model) { + + validateStep1(registration, bindingResult); + if (bindingResult.hasErrors()) { + ModelAndView mav = new ModelAndView("apps/webhook/webhookRegisterStep1"); + mav.addObject("eventTypes", eventTypeProvider.getAll()); + addStepModel(mav, 1); + return mav; + } + return new ModelAndView("redirect:/webhook/register/step2"); + } + + @GetMapping("/register/step2") + public ModelAndView registerStep2( + @ModelAttribute("webhookRegistration") WebhookRegistrationDTO registration, + RedirectAttributes redirectAttributes) { + + if (!registration.isStep1Complete()) { + return new ModelAndView("redirect:/webhook/register/step1"); + } + ModelAndView mav = new ModelAndView("apps/webhook/webhookRegisterStep2"); + mav.addObject("apiServices", apiServiceService.searchApiGroups(new ApiGroupSearch())); + addStepModel(mav, 2); + return mav; + } + + /** Step2 "이전" — 현재 선택을 세션에 저장하고 Step1 로 복귀 (App 신청 saveStep2 답습). */ + @PostMapping("/register/step2/save") + public ModelAndView saveStep2( + @RequestParam(value = "selectedApis", required = false) List selectedApis, + @ModelAttribute("webhookRegistration") WebhookRegistrationDTO registration) { + registration.setSelectedApis(selectedApis != null ? selectedApis : new java.util.ArrayList<>()); + return new ModelAndView("redirect:/webhook/register/step1"); + } + + @PostMapping("/register/step2") + public ModelAndView processStep2( + @RequestParam(value = "selectedApis", required = false) List selectedApis, + @ModelAttribute("webhookRegistration") WebhookRegistrationDTO registration, + SessionStatus sessionStatus, + RedirectAttributes redirectAttributes) { + + registration.setSelectedApis(selectedApis); + if (!registration.isStep2Complete()) { + redirectAttributes.addFlashAttribute("error", "알림 대상 API를 1개 이상 선택해주세요."); + return new ModelAndView("redirect:/webhook/register/step2"); + } + + try { + // 평문 Secret 은 화면에 노출하지 않는다 — 목록에서 비밀번호 재인증 후 조회. + webhookService.create(registration, currentOrgId()); + sessionStatus.setComplete(); + redirectAttributes.addFlashAttribute("registrationSuccess", true); + return new ModelAndView("redirect:/webhook/register/step3"); + } catch (RuntimeException e) { + log.warn("Webhook 신청 실패 orgId={}", currentOrgId(), e); + redirectAttributes.addFlashAttribute("error", UserErrorMessageResolver.resolve(e)); + return new ModelAndView("redirect:/webhook/register/step2"); + } + } + + @GetMapping("/register/step3") + public ModelAndView registerStep3(Model model) { + if (!Boolean.TRUE.equals(model.getAttribute("registrationSuccess"))) { + return new ModelAndView("redirect:/webhook"); + } + ModelAndView mav = new ModelAndView("apps/webhook/webhookRegisterStep3"); + addStepModel(mav, 3); + return mav; + } + + @GetMapping("/register/cancel") + public String cancelRegistration(SessionStatus sessionStatus) { + sessionStatus.setComplete(); + return "redirect:/webhook"; + } + + // ========================= 수정 플로우 ========================= + + @GetMapping("/modify/step1") + public ModelAndView modifyStep1( + @ModelAttribute("webhookModification") WebhookRegistrationDTO modification, + Model model) { + + Optional current = webhookService.getByOrg(currentOrgId()); + if (!current.isPresent()) { + return new ModelAndView("redirect:/webhook/register/step1?clear=true"); + } + WebhookDTO webhook = current.get(); + // 세션에 아직 채워지지 않았으면 현재 등록값으로 초기화 + if (modification.getId() == null || !webhook.getId().equals(modification.getId())) { + modification.setId(webhook.getId()); + modification.setRequestType("MODIFY"); + modification.setTargetUrl(webhook.getTargetUrl()); + modification.setEventTypes(webhook.getEventTypes().stream() + .map(e -> e.getCode()).collect(java.util.stream.Collectors.toList())); + modification.setSelectedApis(new java.util.ArrayList<>(webhook.getApiIds())); + model.addAttribute("webhookModification", modification); + } + + ModelAndView mav = new ModelAndView("apps/webhook/webhookModifyStep1"); + mav.addObject("eventTypes", eventTypeProvider.getAll()); + addStepModel(mav, 1); + return mav; + } + + @PostMapping("/modify/step1") + public ModelAndView processModifyStep1( + @Valid @ModelAttribute("webhookModification") WebhookRegistrationDTO modification, + BindingResult bindingResult, + Model model) { + + validateStep1(modification, bindingResult); + if (bindingResult.hasErrors()) { + ModelAndView mav = new ModelAndView("apps/webhook/webhookModifyStep1"); + mav.addObject("eventTypes", eventTypeProvider.getAll()); + addStepModel(mav, 1); + return mav; + } + return new ModelAndView("redirect:/webhook/modify/step2"); + } + + @GetMapping("/modify/step2") + public ModelAndView modifyStep2( + @ModelAttribute("webhookModification") WebhookRegistrationDTO modification) { + + if (modification.getId() == null || !modification.isStep1Complete()) { + return new ModelAndView("redirect:/webhook/modify/step1"); + } + ModelAndView mav = new ModelAndView("apps/webhook/webhookModifyStep2"); + mav.addObject("apiServices", apiServiceService.searchApiGroups(new ApiGroupSearch())); + addStepModel(mav, 2); + return mav; + } + + /** 수정 Step2 "이전" — 현재 선택을 세션에 저장하고 Step1 로 복귀. */ + @PostMapping("/modify/step2/save") + public ModelAndView saveModifyStep2( + @RequestParam(value = "selectedApis", required = false) List selectedApis, + @ModelAttribute("webhookModification") WebhookRegistrationDTO modification) { + modification.setSelectedApis(selectedApis != null ? selectedApis : new java.util.ArrayList<>()); + return new ModelAndView("redirect:/webhook/modify/step1"); + } + + @PostMapping("/modify/step2") + public ModelAndView processModifyStep2( + @RequestParam(value = "selectedApis", required = false) List selectedApis, + @ModelAttribute("webhookModification") WebhookRegistrationDTO modification, + SessionStatus sessionStatus, + RedirectAttributes redirectAttributes) { + + modification.setSelectedApis(selectedApis); + if (modification.getId() == null || !modification.isStep2Complete()) { + redirectAttributes.addFlashAttribute("error", "알림 대상 API를 1개 이상 선택해주세요."); + return new ModelAndView("redirect:/webhook/modify/step2"); + } + try { + webhookService.update(modification.getId(), modification, currentOrgId()); + sessionStatus.setComplete(); + redirectAttributes.addFlashAttribute("modifySuccess", true); + return new ModelAndView("redirect:/webhook/modify/step3"); + } catch (RuntimeException e) { + log.warn("Webhook 수정 실패 orgId={}", currentOrgId(), e); + redirectAttributes.addFlashAttribute("error", UserErrorMessageResolver.resolve(e)); + return new ModelAndView("redirect:/webhook/modify/step2"); + } + } + + @GetMapping("/modify/step3") + public ModelAndView modifyStep3(Model model) { + if (!Boolean.TRUE.equals(model.getAttribute("modifySuccess"))) { + return new ModelAndView("redirect:/webhook"); + } + ModelAndView mav = new ModelAndView("apps/webhook/webhookModifyStep3"); + addStepModel(mav, 3); + return mav; + } + + @GetMapping("/modify/cancel") + public String cancelModification(SessionStatus sessionStatus) { + sessionStatus.setComplete(); + return "redirect:/webhook"; + } + + // ==================== AJAX (비밀번호 재인증) ==================== + + @PostMapping("/verify-secret") + @ResponseBody + public Map verifySecret(@RequestParam String password) { + Map result = new HashMap<>(); + if (!verifyPassword(password)) { + result.put("success", false); + result.put("message", "비밀번호가 일치하지 않습니다."); + return result; + } + Optional webhook = webhookService.getByOrg(currentOrgId()); + if (!webhook.isPresent()) { + result.put("success", false); + result.put("message", "등록된 Webhook이 없습니다."); + return result; + } + result.put("success", true); + result.put("secret", webhookService.getPlainSecret(webhook.get().getId(), currentOrgId())); + return result; + } + + @PostMapping("/regenerate-secret") + @ResponseBody + public Map regenerateSecret(@RequestParam String password) { + Map result = new HashMap<>(); + if (!verifyPassword(password)) { + result.put("success", false); + result.put("message", "비밀번호가 일치하지 않습니다."); + return result; + } + Optional webhook = webhookService.getByOrg(currentOrgId()); + if (!webhook.isPresent()) { + result.put("success", false); + result.put("message", "등록된 Webhook이 없습니다."); + return result; + } + String secret = webhookService.regenerateSecret(webhook.get().getId(), currentOrgId()); + result.put("success", true); + result.put("secret", secret); + return result; + } + + @PostMapping("/delete") + @ResponseBody + public Map delete(@RequestParam String password) { + Map result = new HashMap<>(); + if (!verifyPassword(password)) { + result.put("success", false); + result.put("message", "비밀번호가 일치하지 않습니다."); + return result; + } + Optional webhook = webhookService.getByOrg(currentOrgId()); + if (!webhook.isPresent()) { + result.put("success", false); + result.put("message", "등록된 Webhook이 없습니다."); + return result; + } + webhookService.delete(webhook.get().getId(), currentOrgId()); + result.put("success", true); + return result; + } + + // ============================ helper ============================ + + private void validateStep1(WebhookRegistrationDTO dto, BindingResult bindingResult) { + String url = dto.getTargetUrl() == null ? "" : dto.getTargetUrl().trim(); + if (!bindingResult.hasFieldErrors("targetUrl") + && !url.startsWith("http://") && !url.startsWith("https://")) { + bindingResult.rejectValue("targetUrl", "invalid.url", + "URL은 http:// 또는 https:// 로 시작해야 합니다."); + } + if (dto.getEventTypes() == null || dto.getEventTypes().isEmpty()) { + bindingResult.rejectValue("eventTypes", "empty.eventTypes", + "EventType을 1개 이상 선택해주세요."); + } + } + + private boolean verifyPassword(String password) { + PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser(); + return appServiceFacade.verifyUserPassword(user, password); + } + + private String currentOrgId() { + PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser(); + return user != null && user.getPortalOrg() != null ? user.getPortalOrg().getId() : null; + } + + private void addStepModel(ModelAndView mav, int currentStep) { + mav.addObject("currentStep", currentStep); + mav.addObject("totalSteps", TOTAL_STEPS); + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/webhook/dto/WebhookCreatedResult.java b/src/main/java/com/eactive/apim/portal/djb/webhook/dto/WebhookCreatedResult.java new file mode 100644 index 0000000..cb9616c --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/webhook/dto/WebhookCreatedResult.java @@ -0,0 +1,15 @@ +package com.eactive.apim.portal.djb.webhook.dto; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * 신규 신청 결과. 평문 {@code secret} 은 발급 직후 1회 노출 목적으로만 전달된다. + */ +@Getter +@RequiredArgsConstructor +public class WebhookCreatedResult { + + private final Long id; + private final String secret; +} diff --git a/src/main/java/com/eactive/apim/portal/djb/webhook/dto/WebhookDTO.java b/src/main/java/com/eactive/apim/portal/djb/webhook/dto/WebhookDTO.java new file mode 100644 index 0000000..99540ad --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/webhook/dto/WebhookDTO.java @@ -0,0 +1,26 @@ +package com.eactive.apim.portal.djb.webhook.dto; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import lombok.Data; + +/** + * 등록된 Webhook 상세 표시용. SECRET 은 마스킹 값만 담고 평문은 별도 재인증 조회로만 노출한다. + */ +@Data +public class WebhookDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + private Long id; + private String targetUrl; + private String secretMasked; + private String createdDate; + + /** 구독 API ID 목록. */ + private List apiIds = new ArrayList<>(); + + /** 구독 EventType(코드+한글명) 목록. */ + private List eventTypes = new ArrayList<>(); +} diff --git a/src/main/java/com/eactive/apim/portal/djb/webhook/dto/WebhookEventTypeDTO.java b/src/main/java/com/eactive/apim/portal/djb/webhook/dto/WebhookEventTypeDTO.java new file mode 100644 index 0000000..f8be080 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/webhook/dto/WebhookEventTypeDTO.java @@ -0,0 +1,27 @@ +package com.eactive.apim.portal.djb.webhook.dto; + +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * EventType 코드/한글명 쌍. TSEAIRM28(CODEGROUP='EVENT_TYPE') 에서 로드. + * {@code selected} 는 신청 화면에서 현재 구독 여부 표시용. + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class WebhookEventTypeDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + private String code; + private String name; + private boolean selected; + + public WebhookEventTypeDTO(String code, String name) { + this.code = code; + this.name = name; + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/webhook/dto/WebhookRegistrationDTO.java b/src/main/java/com/eactive/apim/portal/djb/webhook/dto/WebhookRegistrationDTO.java new file mode 100644 index 0000000..68b8e2e --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/webhook/dto/WebhookRegistrationDTO.java @@ -0,0 +1,48 @@ +package com.eactive.apim.portal.djb.webhook.dto; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import lombok.Data; +import org.hibernate.validator.constraints.Length; +import org.hibernate.validator.constraints.NotBlank; + +/** + * Webhook 신청/수정 스텝 간 세션 보존 데이터 (App 신청 {@code ApiKeyRegistrationDTO} 답습). + * + * Step1: {@code targetUrl} + {@code eventTypes}. Step2: {@code selectedApis}. + */ +@Data +public class WebhookRegistrationDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + /** 수정 시 대상 신청 ID. 신규 신청이면 null. */ + private Long id; + + /** "NEW" 또는 "MODIFY" */ + private String requestType; + + @NotBlank(message = "Webhook 수신 URL을 입력해주세요.") + @Length(max = 255, message = "URL은 255자를 초과할 수 없습니다.") + private String targetUrl; + + /** Step1: 구독 EventType 코드 목록 (TSEAIRM28 EVENT_TYPE). */ + private List eventTypes = new ArrayList<>(); + + /** Step2: 알림 대상 API ID 목록. */ + private List selectedApis = new ArrayList<>(); + + public boolean isStep1Complete() { + return targetUrl != null && !targetUrl.trim().isEmpty() + && eventTypes != null && !eventTypes.isEmpty(); + } + + public boolean isStep2Complete() { + return selectedApis != null && !selectedApis.isEmpty(); + } + + public boolean isComplete() { + return isStep1Complete() && isStep2Complete(); + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/webhook/exception/WebhookAlreadyExistsException.java b/src/main/java/com/eactive/apim/portal/djb/webhook/exception/WebhookAlreadyExistsException.java new file mode 100644 index 0000000..fecef53 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/webhook/exception/WebhookAlreadyExistsException.java @@ -0,0 +1,13 @@ +package com.eactive.apim.portal.djb.webhook.exception; + +/** + * Org 당 Webhook 1건 정책 위반(이미 등록됨). + */ +public class WebhookAlreadyExistsException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public WebhookAlreadyExistsException(String orgId) { + super("이미 등록된 Webhook이 있습니다. orgId=" + orgId); + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/webhook/exception/WebhookNotFoundException.java b/src/main/java/com/eactive/apim/portal/djb/webhook/exception/WebhookNotFoundException.java new file mode 100644 index 0000000..5d5ab04 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/webhook/exception/WebhookNotFoundException.java @@ -0,0 +1,13 @@ +package com.eactive.apim.portal.djb.webhook.exception; + +/** + * 대상 Webhook 신청을 찾을 수 없음. + */ +public class WebhookNotFoundException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public WebhookNotFoundException(Long id) { + super("Webhook 신청을 찾을 수 없습니다. id=" + id); + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/webhook/mapper/WebhookMapper.java b/src/main/java/com/eactive/apim/portal/djb/webhook/mapper/WebhookMapper.java new file mode 100644 index 0000000..61ea380 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/webhook/mapper/WebhookMapper.java @@ -0,0 +1,19 @@ +package com.eactive.apim.portal.djb.webhook.mapper; + +import com.eactive.apim.portal.djb.webhook.dto.WebhookDTO; +import com.eactive.apim.portal.djb.webhook.repository.entity.WebhookRequest; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +/** + * WebhookRequest → WebhookDTO 기본 필드 매핑. + * secretMasked/apiIds/eventTypes 는 연관 테이블 조립이 필요하므로 서비스에서 채운다. + */ +@Mapper(componentModel = "spring") +public interface WebhookMapper { + + @Mapping(target = "secretMasked", ignore = true) + @Mapping(target = "apiIds", ignore = true) + @Mapping(target = "eventTypes", ignore = true) + WebhookDTO toDto(WebhookRequest entity); +} diff --git a/src/main/java/com/eactive/apim/portal/djb/webhook/repository/WebhookRequestApiRepository.java b/src/main/java/com/eactive/apim/portal/djb/webhook/repository/WebhookRequestApiRepository.java new file mode 100644 index 0000000..a639536 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/webhook/repository/WebhookRequestApiRepository.java @@ -0,0 +1,20 @@ +package com.eactive.apim.portal.djb.webhook.repository; + +import com.eactive.apim.portal.djb.webhook.repository.entity.WebhookRequestApi; +import com.eactive.apim.portal.djb.webhook.repository.entity.WebhookRequestApiId; +import com.eactive.eai.rms.data.EMSDataSource; +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.transaction.annotation.Transactional; + +/** + * PTL_WEBHOOK_REQ_API — 신청별 구독 API 목록. + */ +@EMSDataSource +public interface WebhookRequestApiRepository extends JpaRepository { + + List findByWebhookReqId(Long webhookReqId); + + @Transactional + void deleteByWebhookReqId(Long webhookReqId); +} diff --git a/src/main/java/com/eactive/apim/portal/djb/webhook/repository/WebhookRequestEventRepository.java b/src/main/java/com/eactive/apim/portal/djb/webhook/repository/WebhookRequestEventRepository.java new file mode 100644 index 0000000..ee4bca0 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/webhook/repository/WebhookRequestEventRepository.java @@ -0,0 +1,20 @@ +package com.eactive.apim.portal.djb.webhook.repository; + +import com.eactive.apim.portal.djb.webhook.repository.entity.WebhookRequestEvent; +import com.eactive.apim.portal.djb.webhook.repository.entity.WebhookRequestEventId; +import com.eactive.eai.rms.data.EMSDataSource; +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.transaction.annotation.Transactional; + +/** + * PTL_WEBHOOK_REQ_EVENT — 신청별 구독 EventType 목록. + */ +@EMSDataSource +public interface WebhookRequestEventRepository extends JpaRepository { + + List findByWebhookReqId(Long webhookReqId); + + @Transactional + void deleteByWebhookReqId(Long webhookReqId); +} diff --git a/src/main/java/com/eactive/apim/portal/djb/webhook/repository/WebhookRequestRepository.java b/src/main/java/com/eactive/apim/portal/djb/webhook/repository/WebhookRequestRepository.java new file mode 100644 index 0000000..2aa3313 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/webhook/repository/WebhookRequestRepository.java @@ -0,0 +1,17 @@ +package com.eactive.apim.portal.djb.webhook.repository; + +import com.eactive.apim.portal.djb.webhook.repository.entity.WebhookRequest; +import com.eactive.eai.rms.data.EMSDataSource; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; + +/** + * PTL_WEBHOOK_REQ 조회/저장. Org 당 1건 정책이라 orgId 단건 조회를 제공한다. + */ +@EMSDataSource +public interface WebhookRequestRepository extends JpaRepository { + + Optional findByOrgId(String orgId); + + boolean existsByOrgId(String orgId); +} diff --git a/src/main/java/com/eactive/apim/portal/djb/webhook/repository/entity/WebhookRequest.java b/src/main/java/com/eactive/apim/portal/djb/webhook/repository/entity/WebhookRequest.java new file mode 100644 index 0000000..662721e --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/webhook/repository/entity/WebhookRequest.java @@ -0,0 +1,66 @@ +package com.eactive.apim.portal.djb.webhook.repository.entity; + +import com.eactive.apim.portal.common.util.SecurityUtil; +import java.io.Serializable; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.PrePersist; +import javax.persistence.SequenceGenerator; +import javax.persistence.Table; +import lombok.Getter; +import lombok.Setter; + +/** + * Webhook 신청 마스터 (EMSAPP.PTL_WEBHOOK_REQ). + * + * admin(eapim-admin) 발송엔진이 이 행을 읽어 TARGET_URL 로 HMAC-SHA256 서명 발송한다. + * SECRET 은 서명 키로 그대로 사용되므로 암호화하지 않고 평문 저장한다. + * CREATED_DATE 는 VARCHAR2(14) yyyyMMddHHmmss 문자열이라 AbstractAuditingEntity 를 쓰지 않고 + * {@link #onCreate()} 에서 직접 세팅한다. + */ +@Getter +@Setter +@Entity +@Table(name = "PTL_WEBHOOK_REQ") +public class WebhookRequest implements Serializable { + + private static final long serialVersionUID = 1L; + + private static final DateTimeFormatter TS = DateTimeFormatter.ofPattern("yyyyMMddHHmmss"); + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "webhookReqSeq") + @SequenceGenerator(name = "webhookReqSeq", sequenceName = "SEQ_PTL_WEBHOOK_REQ_ID", allocationSize = 1) + @Column(name = "ID") + private Long id; + + @Column(name = "ORG_ID", length = 36) + private String orgId; + + @Column(name = "TARGET_URL", length = 255) + private String targetUrl; + + @Column(name = "SECRET", length = 500) + private String secret; + + @Column(name = "CREATED_BY", length = 200) + private String createdBy; + + @Column(name = "CREATED_DATE", length = 14) + private String createdDate; + + @PrePersist + public void onCreate() { + if (createdBy == null) { + createdBy = SecurityUtil.getCurrentLoginId(); + } + if (createdDate == null) { + createdDate = LocalDateTime.now().format(TS); + } + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/webhook/repository/entity/WebhookRequestApi.java b/src/main/java/com/eactive/apim/portal/djb/webhook/repository/entity/WebhookRequestApi.java new file mode 100644 index 0000000..4e2248a --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/webhook/repository/entity/WebhookRequestApi.java @@ -0,0 +1,62 @@ +package com.eactive.apim.portal.djb.webhook.repository.entity; + +import com.eactive.apim.portal.common.util.SecurityUtil; +import java.io.Serializable; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.IdClass; +import javax.persistence.PrePersist; +import javax.persistence.Table; +import lombok.Getter; +import lombok.Setter; + +/** + * Webhook 신청이 알림을 받을 API 목록 (EMSAPP.PTL_WEBHOOK_REQ_API). + * 복합키(WEBHOOK_REQ_ID + API_ID). + */ +@Getter +@Setter +@Entity +@Table(name = "PTL_WEBHOOK_REQ_API") +@IdClass(WebhookRequestApiId.class) +public class WebhookRequestApi implements Serializable { + + private static final long serialVersionUID = 1L; + + private static final DateTimeFormatter TS = DateTimeFormatter.ofPattern("yyyyMMddHHmmss"); + + @Id + @Column(name = "WEBHOOK_REQ_ID") + private Long webhookReqId; + + @Id + @Column(name = "API_ID", length = 30) + private String apiId; + + @Column(name = "CREATED_BY", length = 200) + private String createdBy; + + @Column(name = "CREATED_DATE", length = 14) + private String createdDate; + + public WebhookRequestApi() { + } + + public WebhookRequestApi(Long webhookReqId, String apiId) { + this.webhookReqId = webhookReqId; + this.apiId = apiId; + } + + @PrePersist + public void onCreate() { + if (createdBy == null) { + createdBy = SecurityUtil.getCurrentLoginId(); + } + if (createdDate == null) { + createdDate = LocalDateTime.now().format(TS); + } + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/webhook/repository/entity/WebhookRequestApiId.java b/src/main/java/com/eactive/apim/portal/djb/webhook/repository/entity/WebhookRequestApiId.java new file mode 100644 index 0000000..d3e8595 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/webhook/repository/entity/WebhookRequestApiId.java @@ -0,0 +1,24 @@ +package com.eactive.apim.portal.djb.webhook.repository.entity; + +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * {@link WebhookRequestApi} 복합키 (WEBHOOK_REQ_ID + API_ID). + */ +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode +public class WebhookRequestApiId implements Serializable { + + private static final long serialVersionUID = 1L; + + private Long webhookReqId; + private String apiId; +} diff --git a/src/main/java/com/eactive/apim/portal/djb/webhook/repository/entity/WebhookRequestEvent.java b/src/main/java/com/eactive/apim/portal/djb/webhook/repository/entity/WebhookRequestEvent.java new file mode 100644 index 0000000..9e4865c --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/webhook/repository/entity/WebhookRequestEvent.java @@ -0,0 +1,63 @@ +package com.eactive.apim.portal.djb.webhook.repository.entity; + +import com.eactive.apim.portal.common.util.SecurityUtil; +import java.io.Serializable; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.IdClass; +import javax.persistence.PrePersist; +import javax.persistence.Table; +import lombok.Getter; +import lombok.Setter; + +/** + * Webhook 신청이 구독하는 EventType 목록 (EMSAPP.PTL_WEBHOOK_REQ_EVENT). + * EVENT_TYPE 코드값은 EMSAPP.TSEAIRM28(CODEGROUP='EVENT_TYPE') 과 동일 집합. + * 복합키(WEBHOOK_REQ_ID + EVENT_TYPE). + */ +@Getter +@Setter +@Entity +@Table(name = "PTL_WEBHOOK_REQ_EVENT") +@IdClass(WebhookRequestEventId.class) +public class WebhookRequestEvent implements Serializable { + + private static final long serialVersionUID = 1L; + + private static final DateTimeFormatter TS = DateTimeFormatter.ofPattern("yyyyMMddHHmmss"); + + @Id + @Column(name = "WEBHOOK_REQ_ID") + private Long webhookReqId; + + @Id + @Column(name = "EVENT_TYPE", length = 36) + private String eventType; + + @Column(name = "CREATED_BY", length = 200) + private String createdBy; + + @Column(name = "CREATED_DATE", length = 14) + private String createdDate; + + public WebhookRequestEvent() { + } + + public WebhookRequestEvent(Long webhookReqId, String eventType) { + this.webhookReqId = webhookReqId; + this.eventType = eventType; + } + + @PrePersist + public void onCreate() { + if (createdBy == null) { + createdBy = SecurityUtil.getCurrentLoginId(); + } + if (createdDate == null) { + createdDate = LocalDateTime.now().format(TS); + } + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/webhook/repository/entity/WebhookRequestEventId.java b/src/main/java/com/eactive/apim/portal/djb/webhook/repository/entity/WebhookRequestEventId.java new file mode 100644 index 0000000..7780fb0 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/webhook/repository/entity/WebhookRequestEventId.java @@ -0,0 +1,24 @@ +package com.eactive.apim.portal.djb.webhook.repository.entity; + +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * {@link WebhookRequestEvent} 복합키 (WEBHOOK_REQ_ID + EVENT_TYPE). + */ +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode +public class WebhookRequestEventId implements Serializable { + + private static final long serialVersionUID = 1L; + + private Long webhookReqId; + private String eventType; +} diff --git a/src/main/java/com/eactive/apim/portal/djb/webhook/service/WebhookEventTypeProvider.java b/src/main/java/com/eactive/apim/portal/djb/webhook/service/WebhookEventTypeProvider.java new file mode 100644 index 0000000..faf8a66 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/webhook/service/WebhookEventTypeProvider.java @@ -0,0 +1,76 @@ +package com.eactive.apim.portal.djb.webhook.service; + +import com.eactive.apim.portal.djb.webhook.dto.WebhookEventTypeDTO; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +/** + * EventType 코드/한글명 제공자. EMSAPP.TSEAIRM28(CODEGROUP='EVENT_TYPE', USEYN='Y') 를 조회한다. + * admin 발송엔진과 동일 공통코드 테이블을 단일 소스로 사용하므로 코드 추가/변경 시 DB 만 수정하면 된다. + * + * TSEAIRM28 은 elink-portal-common 의 {@code MonitoringCode} 엔티티가 이미 매핑하고 있어(같은 테이블 중복 @Entity 금지), + * 여기서는 별도 엔티티 없이 EMS EntityManager 네이티브 쿼리로 필요한 두 컬럼만 조회한다. + */ +@Component +public class WebhookEventTypeProvider { + + private static final String EVENT_TYPE_SQL = + "SELECT CODE, CODENAME FROM TSEAIRM28 " + + "WHERE CODEGROUP = 'EVENT_TYPE' AND USEYN = 'Y' " + + "ORDER BY SEQ, CODE"; + + /** EMS 데이터소스가 @Primary 이므로 기본 EntityManager 는 EMS 를 가리킨다. */ + @PersistenceContext + private EntityManager entityManager; + + @Transactional(readOnly = true) + public List getAll() { + List list = new ArrayList<>(); + for (Object[] row : rows()) { + list.add(new WebhookEventTypeDTO(asString(row[0]), asString(row[1]))); + } + return list; + } + + @Transactional(readOnly = true) + public Map asMap() { + Map map = new LinkedHashMap<>(); + for (Object[] row : rows()) { + map.put(asString(row[0]), asString(row[1])); + } + return map; + } + + @Transactional(readOnly = true) + public boolean isValid(String code) { + if (code == null) { + return false; + } + for (Object[] row : rows()) { + if (code.equals(asString(row[0]))) { + return true; + } + } + return false; + } + + @Transactional(readOnly = true) + public String getName(String code) { + return asMap().getOrDefault(code, code); + } + + @SuppressWarnings("unchecked") + private List rows() { + return entityManager.createNativeQuery(EVENT_TYPE_SQL).getResultList(); + } + + private String asString(Object value) { + return value == null ? null : value.toString(); + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/webhook/service/WebhookSecretGenerator.java b/src/main/java/com/eactive/apim/portal/djb/webhook/service/WebhookSecretGenerator.java new file mode 100644 index 0000000..bdc0a12 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/webhook/service/WebhookSecretGenerator.java @@ -0,0 +1,28 @@ +package com.eactive.apim.portal.djb.webhook.service; + +import java.security.SecureRandom; +import org.springframework.stereotype.Component; + +/** + * Webhook HMAC Secret 생성기. + * + * admin 발송엔진이 이 값을 그대로 HMAC-SHA256 키로 사용하므로(암복호화 없음), + * 128자 영숫자 랜덤 문자열을 평문으로 발급한다(admin 기존 데이터와 동일 형태). + */ +@Component +public class WebhookSecretGenerator { + + private static final char[] ALPHANUM = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".toCharArray(); + private static final int LENGTH = 128; + + private final SecureRandom random = new SecureRandom(); + + public String generate() { + StringBuilder sb = new StringBuilder(LENGTH); + for (int i = 0; i < LENGTH; i++) { + sb.append(ALPHANUM[random.nextInt(ALPHANUM.length)]); + } + return sb.toString(); + } +} diff --git a/src/main/java/com/eactive/apim/portal/djb/webhook/service/WebhookService.java b/src/main/java/com/eactive/apim/portal/djb/webhook/service/WebhookService.java new file mode 100644 index 0000000..d41b7e9 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/djb/webhook/service/WebhookService.java @@ -0,0 +1,189 @@ +package com.eactive.apim.portal.djb.webhook.service; + +import com.eactive.apim.portal.djb.webhook.dto.WebhookCreatedResult; +import com.eactive.apim.portal.djb.webhook.dto.WebhookDTO; +import com.eactive.apim.portal.djb.webhook.dto.WebhookEventTypeDTO; +import com.eactive.apim.portal.djb.webhook.dto.WebhookRegistrationDTO; +import com.eactive.apim.portal.djb.webhook.exception.WebhookAlreadyExistsException; +import com.eactive.apim.portal.djb.webhook.exception.WebhookNotFoundException; +import com.eactive.apim.portal.djb.webhook.mapper.WebhookMapper; +import com.eactive.apim.portal.djb.webhook.repository.WebhookRequestApiRepository; +import com.eactive.apim.portal.djb.webhook.repository.WebhookRequestEventRepository; +import com.eactive.apim.portal.djb.webhook.repository.WebhookRequestRepository; +import com.eactive.apim.portal.djb.webhook.repository.entity.WebhookRequest; +import com.eactive.apim.portal.djb.webhook.repository.entity.WebhookRequestApi; +import com.eactive.apim.portal.djb.webhook.repository.entity.WebhookRequestEvent; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * Webhook 신청/조회/수정/삭제 및 Secret 발급 오케스트레이션. + * + * 단일 EMS 데이터소스만 사용하므로 기본 {@code @Transactional} 로 충분하다(JTA 분산 트랜잭션 불필요). + * 모든 수정/삭제/Secret 조회는 소유 Org 검증({@link #loadOwned})을 거친다. + */ +@Slf4j +@Service +@Transactional +@RequiredArgsConstructor +public class WebhookService { + + private static final String SECRET_MASK = "••••••••••••"; + + private final WebhookRequestRepository requestRepository; + private final WebhookRequestApiRepository apiRepository; + private final WebhookRequestEventRepository eventRepository; + private final WebhookSecretGenerator secretGenerator; + private final WebhookEventTypeProvider eventTypeProvider; + private final WebhookMapper webhookMapper; + + @Transactional(readOnly = true) + public boolean existsByOrg(String orgId) { + return requestRepository.existsByOrgId(orgId); + } + + @Transactional(readOnly = true) + public Optional getByOrg(String orgId) { + return requestRepository.findByOrgId(orgId).map(this::toDetailDto); + } + + /** + * 신규 신청. Org 당 1건 정책 위반 시 예외. 평문 Secret 을 1회 반환한다. + */ + public WebhookCreatedResult create(WebhookRegistrationDTO dto, String orgId) { + if (requestRepository.existsByOrgId(orgId)) { + throw new WebhookAlreadyExistsException(orgId); + } + validate(dto); + + String secret = secretGenerator.generate(); + WebhookRequest request = new WebhookRequest(); + request.setOrgId(orgId); + request.setTargetUrl(dto.getTargetUrl().trim()); + request.setSecret(secret); + WebhookRequest saved = requestRepository.save(request); + + persistChildren(saved.getId(), dto); + log.info("Webhook 신규 등록 orgId={} id={}", orgId, saved.getId()); + return new WebhookCreatedResult(saved.getId(), secret); + } + + /** + * URL/API/EventType 수정. Secret 은 보존한다. 연관 테이블은 delete-all 후 재삽입. + */ + public WebhookDTO update(Long id, WebhookRegistrationDTO dto, String orgId) { + WebhookRequest request = loadOwned(id, orgId); + validate(dto); + + request.setTargetUrl(dto.getTargetUrl().trim()); + requestRepository.save(request); + + apiRepository.deleteByWebhookReqId(id); + eventRepository.deleteByWebhookReqId(id); + apiRepository.flush(); + eventRepository.flush(); + persistChildren(id, dto); + + log.info("Webhook 수정 orgId={} id={}", orgId, id); + return toDetailDto(request); + } + + /** + * Secret 재발급(교체). 새 평문 Secret 반환. + */ + public String regenerateSecret(Long id, String orgId) { + WebhookRequest request = loadOwned(id, orgId); + String secret = secretGenerator.generate(); + request.setSecret(secret); + requestRepository.save(request); + log.info("Webhook Secret 재발급 orgId={} id={}", orgId, id); + return secret; + } + + /** + * 3개 테이블 HardDelete. + */ + public void delete(Long id, String orgId) { + WebhookRequest request = loadOwned(id, orgId); + apiRepository.deleteByWebhookReqId(id); + eventRepository.deleteByWebhookReqId(id); + requestRepository.delete(request); + log.info("Webhook 삭제 orgId={} id={}", orgId, id); + } + + /** + * 평문 Secret 조회. 컨트롤러에서 비밀번호 재인증 후에만 호출한다. + */ + @Transactional(readOnly = true) + public String getPlainSecret(Long id, String orgId) { + return loadOwned(id, orgId).getSecret(); + } + + // ---------------------------------------------------------------- + + private WebhookRequest loadOwned(Long id, String orgId) { + WebhookRequest request = requestRepository.findById(id) + .orElseThrow(() -> new WebhookNotFoundException(id)); + if (!Objects.equals(request.getOrgId(), orgId)) { + throw new AccessDeniedException("해당 Webhook에 대한 권한이 없습니다."); + } + return request; + } + + private void persistChildren(Long reqId, WebhookRegistrationDTO dto) { + for (String apiId : dedup(dto.getSelectedApis())) { + apiRepository.save(new WebhookRequestApi(reqId, apiId)); + } + for (String eventType : dedup(dto.getEventTypes())) { + if (!eventTypeProvider.isValid(eventType)) { + throw new IllegalArgumentException("유효하지 않은 EventType 코드입니다: " + eventType); + } + eventRepository.save(new WebhookRequestEvent(reqId, eventType)); + } + } + + private void validate(WebhookRegistrationDTO dto) { + String url = dto.getTargetUrl() == null ? "" : dto.getTargetUrl().trim(); + if (!url.startsWith("http://") && !url.startsWith("https://")) { + throw new IllegalArgumentException("URL은 http:// 또는 https:// 로 시작해야 합니다."); + } + if (dto.getEventTypes() == null || dto.getEventTypes().isEmpty()) { + throw new IllegalArgumentException("EventType을 1개 이상 선택해주세요."); + } + if (dto.getSelectedApis() == null || dto.getSelectedApis().isEmpty()) { + throw new IllegalArgumentException("알림 대상 API를 1개 이상 선택해주세요."); + } + } + + private List dedup(List values) { + if (values == null) { + return java.util.Collections.emptyList(); + } + return new java.util.ArrayList<>(new LinkedHashSet<>(values)); + } + + private WebhookDTO toDetailDto(WebhookRequest request) { + WebhookDTO dto = webhookMapper.toDto(request); + dto.setSecretMasked(request.getSecret() == null ? "" : SECRET_MASK); + + dto.setApiIds(apiRepository.findByWebhookReqId(request.getId()).stream() + .map(WebhookRequestApi::getApiId) + .collect(Collectors.toList())); + + Map names = eventTypeProvider.asMap(); + dto.setEventTypes(eventRepository.findByWebhookReqId(request.getId()).stream() + .map(e -> new WebhookEventTypeDTO(e.getEventType(), + names.getOrDefault(e.getEventType(), e.getEventType()))) + .collect(Collectors.toList())); + return dto; + } +} diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 5528fb0..24b95e2 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -1,14 +1,14 @@ spring: jta: - enabled: true + enabled: false config: activate: on-profile: dev jpa: properties: hibernate: - show_sql: true - format_sql: true + show_sql: false + format_sql: false use_sql_comments: true devtools: restart: @@ -35,12 +35,17 @@ gateway: hibernate-dialect: org.hibernate.dialect.Oracle12cDialect hibernate-physical-naming-strategy: com.eactive.apim.portal.common.entity.CustomPhysicalNamingStrategy - entity-package: com.eactive.eai.data.entity.onl + entity-package: com.eactive.eai.data.entity.onl,com.eactive.apim.gateway.data.statistics.entity portal: - auth-virtual-code: 654321 - test-auth-notice-enabled: true +# auth-virtual-code: 654321 dev: # application.yml의 `page:` 트리(브레드크럼/메뉴 이름) 라이브 반영 - hot-reload-pages: true \ No newline at end of file + hot-reload-pages: true + +server: + # 앞단 프록시(OHS 등) 경유 환경. ForwardedHeaderFilter 가 scheme/host/port/remoteAddr 을 교정한다. + # 상세 주석은 application.yml 의 동일 키 참고. /internal/** 은 필터에서 제외된다 + # (PortalConfigForwardedHeader — 원 소켓 IP 기반 허용 IP 검사를 보존하기 위함). + forward-headers-strategy: framework \ No newline at end of file diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index 333b9ba..cc5d35e 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -1,6 +1,6 @@ spring: jta: - enabled: true + enabled: false config: activate: on-profile: prod @@ -11,6 +11,9 @@ spring: cachecontrol: max-age: 86400 public: true + # 정적자원 콘텐츠 해시 버전닝은 PortalConfigWebDispatcherServlet 가 prod 프로파일에서 + # 항상 ON 으로 수행한다(app.resource-versioning.enabled 토글 무시). + # 해시 URL + 장기 캐시(max-age 86400)로 배포 시 자동 캐시 무효화. jpa: properties: hibernate: @@ -25,5 +28,14 @@ spring: thymeleaf: cache: false -portal: - test-auth-notice-enabled: false # prod 환경: UI 안내 미표시 \ No newline at end of file +app: + resource-versioning: + enabled: true + resource-caching: + enabled: true + +server: + # 앞단 프록시(OHS 등) 경유 환경. ForwardedHeaderFilter 가 scheme/host/port/remoteAddr 을 교정한다. + # 상세 주석은 application.yml 의 동일 키 참고. /internal/** 은 필터에서 제외된다 + # (PortalConfigForwardedHeader — 원 소켓 IP 기반 허용 IP 검사를 보존하기 위함). + forward-headers-strategy: framework \ No newline at end of file diff --git a/src/main/resources/application-stage.yml b/src/main/resources/application-stage.yml index f5b1d9c..86f638c 100644 --- a/src/main/resources/application-stage.yml +++ b/src/main/resources/application-stage.yml @@ -1,6 +1,6 @@ spring: jta: - enabled: true + enabled: false web: resources: cache: @@ -22,7 +22,6 @@ spring: cache: false portal: - test-auth-notice-enabled: true # 검증 단계에선 사용하지 않음 # auth-virtual-code: 654321 dev: @@ -46,5 +45,11 @@ gateway: hibernate-dialect: org.hibernate.dialect.Oracle12cDialect hibernate-physical-naming-strategy: com.eactive.apim.portal.common.entity.CustomPhysicalNamingStrategy - entity-package: com.eactive.eai.data.entity.onl + entity-package: com.eactive.eai.data.entity.onl,com.eactive.apim.gateway.data.statistics.entity + +server: + # 앞단 프록시(OHS 등) 경유 환경. ForwardedHeaderFilter 가 scheme/host/port/remoteAddr 을 교정한다. + # 상세 주석은 application.yml 의 동일 키 참고. /internal/** 은 필터에서 제외된다 + # (PortalConfigForwardedHeader — 원 소켓 IP 기반 허용 IP 검사를 보존하기 위함). + forward-headers-strategy: framework diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 92c298e..f57c802 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -2,7 +2,12 @@ server: servlet: context-path: / session: - timeout: 15m + # 세션 타임아웃 10분 고정 + # WebLogic 배포 시에는 weblogic.xml 600 이 동일 값을 적용한다. + # CSRF 토큰은 세션에 저장(HttpSessionCsrfTokenRepository)되므로 수명도 이 값과 동일하다. + timeout: 10m + cookie: + name: JSESSIONID_PORTAL encoding: charset: UTF-8 port: '39130' @@ -14,16 +19,31 @@ server: whitelabel: enabled: false path: /error + # 리버스프록시(OHS) 전달 헤더(X-Forwarded-*)를 요청에 반영하는 전략. + # native = 내장 Tomcat RemoteIpValve 위임 → WAR(WebLogic) 배포에서는 동작하지 않는다. + # framework = Spring ForwardedHeaderFilter(서블릿 필터) → 배포 방식과 무관하게 동작한다. + # 기본값 native: 로컬 개발은 gradle bootRun(내장 Tomcat)으로 기동하므로 컨테이너 기본 처리에 맡긴다. + # RemoteIpValve 는 internalProxies(사설/loopback 대역) 에서 온 요청의 헤더만 신뢰하므로 로컬에서 안전하다. + # 프록시 뒤에 놓이는 WAR 배포 환경(dev/stage/prod)만 각 프로파일에서 framework 로 올린다. + # ※ framework 로 올리는 환경은 OHS 에서 반드시 아래를 수행해야 한다(신뢰 프록시 목록이 없어 헤더를 무조건 신뢰). + # RequestHeader unset X-Forwarded-For / -Proto / -Host / -Port / Forwarded (클라이언트 위조분 제거) + # RequestHeader set X-Forwarded-Proto "https" / X-Forwarded-Port "1443" (mod_wl_ohs 는 미전송) forward-headers-strategy: native spring: + config: + import: + - classpath:menu.yml + - classpath:roles.yml data: web: pageable: default-page-size: '10' jpa: open-in-view: false + hibernate: + ddl-auto: none web: resources: @@ -33,12 +53,17 @@ spring: max-age: 0 must-revalidate: true no-cache: true + # 정적자원 콘텐츠 해시 버전닝(/css/main.css -> /css/main-.css)은 + # @EnableWebMvc 로 WebMvcAutoConfiguration 이 비활성화되어 spring.web.resources.chain + # 설정으로는 적용되지 않는다. 실제 버전닝은 PortalConfigWebDispatcherServlet 가 + # 아래 app.resource-versioning.enabled 토글을 읽어 직접 수행한다. jta: - enabled: true + enabled: false atomikos: properties: - log-base-dir: /logs/atomikos +# log-base-dir: /logs/eapim/${inst.Name:devSvr11}/atomikos + log-base-dir: /dev/null log-base-name: adp_tx thymeleaf: @@ -49,10 +74,28 @@ spring: cache: false main: allow-bean-definition-overriding: true + +# 정적자원 해시 버전닝 토글 (prod 제외 전 프로파일에 적용). +# false 로 변경 시 /css/main.css 가 해시 없이 그대로 서빙되어 sourceMap 디버깅 가능. +# prod 는 PortalConfigWebDispatcherServlet 에서 항상 ON 으로 고정되어 이 값을 무시함. +app: + resource-versioning: + enabled: false + # 정적자원 서버측 캐싱(ResourceChain 캐시) 토글 (prod 제외 전 프로파일에 적용). + # false 면 sass/JS 변경이 서버 재시작 없이 즉시 반영됨(매 요청 재해석). + # prod 는 PortalConfigWebDispatcherServlet 에서 항상 ON 으로 고정되어 이 값을 무시함. + resource-caching: + enabled: false + security: basic: enabled: false +# ※ Actuator / Spring Boot Admin 설정은 이 파일에 두지 않는다. +# application-local*.yml 에만 두고, 해당 파일과 관련 라이브러리는 war/bootWar 산출물에서 +# 제외된다(build.gradle 의 localOnly configuration + rootSpec.exclude 참고). +# WebLogic 배포본(dev/stage/prod)에는 actuator 자체가 존재하지 않는다. + sample-code-path: classpath:/templates/sample_code @@ -74,7 +117,7 @@ gateway: hibernate-dialect: org.hibernate.dialect.Oracle12cDialect hibernate-physical-naming-strategy: com.eactive.apim.portal.common.entity.CustomPhysicalNamingStrategy - entity-package: com.eactive.eai.data.entity.onl + entity-package: com.eactive.eai.data.entity.onl,com.eactive.apim.gateway.data.statistics.entity portal: file: @@ -84,10 +127,17 @@ portal: logging: log-path: /logs/eapim + # 내부 사용자(운영자) 판별 설정 + # 로그인 계정 이메일의 호스트가 아래 도메인이면 내부 사용자로 분류한다. + # "@" 접두사 유무·대소문자 무관 + internal-user: + email-domains: + - djbank.com + - unit-test.com + auth-ttl: 300 auth: resend_limit_seconds: 30 - test-auth-notice-enabled: true # 기본값: true (대부분의 개발환경에서 UI 안내 표시) user-approval: true password-expiration-days: 90 @@ -179,6 +229,10 @@ portal: method: GET view-name: apps/service/oauth2-guide + - path-pattern: /service/webhook-dev-guide + method: GET + view-name: apps/service/webhook-dev-guide + - path-pattern: /dashboard method: GET view-name: apps/mypage/dashboard @@ -190,23 +244,7 @@ portal: view-name: apps/apis/static/tokenApiSpec bean: apiHandler - portal_security: - ROLE_USER: - - ROLE_INQUIRY - - ROLE_ACCOUNT - ROLE_CORP_USER: - - ROLE_INQUIRY - - ROLE_APP - - ROLE_ACCOUNT - ROLE_CORP_MANAGER: - - ROLE_API_KEY_REQUEST - - ROLE_API_KEY_REQUEST_VIEW - - ROLE_INQUIRY - - ROLE_APP - - ROLE_ACCOUNT - - ROLE_CORP_API - - ROLE_DASHBOARD - - ROLE_USER_MANAGER + # portal_security 는 roles.yml (portal-roles) 로 이동 page: home: name: "Home" @@ -235,12 +273,18 @@ page: corp: name: "법인회원가입" path: "/signup/portalOrg" + verification_email: + name: "이메일 인증" + path: "/signup/verification-email" + decision: + name: "법인회원 초대" + path: "/signup/decision_process" agreements: name: 약관 path: "#" children: terms: - name: "이용약관 및 개인정보처리방침" + name: "이용약관" path: "/agreements/terms" about: name: 안내 @@ -265,6 +309,9 @@ page: oauth2_guide: name: "OAuth2 개발가이드" path: "/service/oauth2-guide" + webhook_dev_guide: + name: "웹훅 개발가이드" + path: "/service/webhook-dev-guide" apis: name: "API" path: "#" @@ -275,6 +322,16 @@ page: api_detail: name: "API 상세보기" path: "/apis/detail" + api_testbed: + name: "테스트베드" + path: "/apis/detail/testbed" + apistatus: + name: "API Status" + path: "/apistatus" + children: + issues: + name: "전체 이슈 이력" + path: "/apistatus/issues" community: name: 고객지원 path: "#" @@ -288,6 +345,9 @@ page: faq_list: name: FAQ path: "/faq_list" + faq_detail: + name: FAQ + path: "/faq_view" inquiry: name: "Q&A" path: "/inquiry" @@ -310,51 +370,72 @@ page: mypage: name: "내 정보 관리" path: "/mypage" + verification_email: + name: "이메일 인증" + path: "/mypage/verification-email" user_list: - name: "이용자 관리" + name: "개발자 관리" path: "/users" user_detail: - name: "이용자 정보" + name: "개발자 정보" path: "/users/detail" apikey: - name: "인증키 관리" - path: "/myapikey" + name: "API 신청 관리" + path: "/clients" app_request_detail: name: "인증키 신청 상세" - path: "/myapikey/app_request_detail" + path: "/clients/app_request_detail" credential_detail: name: "인증키 정보" - path: "/myapikey/credential_detail" - change_password: + path: "/clients/credential_detail" + password_verify: name: "비밀번호 변경" - path: "/change_password" - verify_current_password: + path: "/password/verify" + password_change: name: "비밀번호 변경" - path: "/verify_current_password" - myapikey_register_step1: + path: "/password/change" + clients_register_step1: name: "앱 생성 (기본 정보)" - path: "/myapikey/register/step1" - myapikey_register_step2: + path: "/clients/register/step1" + clients_register_step2: name: "앱 생성 (API 선택)" - path: "/myapikey/register/step2" - myapikey_register_step3: + path: "/clients/register/step2" + clients_register_step3: name: "앱 생성 요청 완료" - path: "/myapikey/register/step3" - myapikey_modify_step1: + path: "/clients/register/step3" + clients_modify_step1: name: "앱 수정 (기본 정보)" - path: "/myapikey/modify/step1" - myapikey_modify_step2: + path: "/clients/modify/step1" + clients_modify_step2: name: "앱 수정 (API 선택)" - path: "/myapikey/modify/step2" - myapikey_modify_step3: + path: "/clients/modify/step2" + clients_modify_step3: name: "앱 수정 요청 완료" - path: "/myapikey/modify/step3" - commission: - name: "과금 관리" - path: "/commission/manage" + path: "/clients/modify/step3" api_statistics: name: "이용 통계" path: "/statistics/api" + webhook: + name: "Webhook 관리" + path: "/webhook" + webhook_register_step1: + name: "Webhook 신청 (기본 정보)" + path: "/webhook/register/step1" + webhook_register_step2: + name: "Webhook 신청 (API 선택)" + path: "/webhook/register/step2" + webhook_register_step3: + name: "Webhook 신청 완료" + path: "/webhook/register/step3" + webhook_modify_step1: + name: "Webhook 수정 (기본 정보)" + path: "/webhook/modify/step1" + webhook_modify_step2: + name: "Webhook 수정 (API 선택)" + path: "/webhook/modify/step2" + webhook_modify_step3: + name: "Webhook 수정 완료" + path: "/webhook/modify/step3" # 에디터 이미지 설정 (약관 이미지 표시용) editor: diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml index c595f5a..dce5ee6 100644 --- a/src/main/resources/logback-spring.xml +++ b/src/main/resources/logback-spring.xml @@ -5,13 +5,20 @@ - - - - - + + + + + + + + + + + + ${LOG_PATH}/hibernate.log @@ -37,18 +44,6 @@ - - ${LOG_PATH}/need-fix.log - - ${LOG_PATH}/backup/need-fix.%d{yyyy-MM-dd}.%i.log - 500MB - 90 - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level - %msg%n - - - ${LOG_PATH}/http-session.log @@ -61,86 +56,65 @@ + + + ${LOG_PATH}/apitester-audit.log + + ${LOG_PATH}/backup/apitester-audit.%d{yyyy-MM-dd}.%i.log + 200MB + 365 + + + %d{yyyy-MM-dd HH:mm:ss.SSS} %msg%n + + + + + + ${LOG_PATH}/error.log + + ERROR + ACCEPT + DENY + + + ${LOG_PATH}/backup/error.%d{yyyy-MM-dd}.%i.log + 200MB + 30 + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + + + + + ${CONSOLE_EFFECTIVE_LEVEL} + ${CONSOLE_PATTERN} - - - - + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/main/resources/menu.yml b/src/main/resources/menu.yml new file mode 100644 index 0000000..0adb99e --- /dev/null +++ b/src/main/resources/menu.yml @@ -0,0 +1,55 @@ +# ------------------------------------------------------------------------------ +# 포탈 GNB 메뉴 정의 (menu.yml) +# +# - 부팅 시 PTL_MENU_ITEM 으로 자동 적재(upsert by id). 동일 id 의 내용이 바뀌면 +# 기본값(DFLT_*)이 갱신되고, 관리자가 수정하지 않은 필드만 새 기본값을 따라간다. +# - 배치(위치) 정보는 PTL_MENU_PLACEMENT 가 비어있을 때만 최초 적재된다. +# 이후 배치 관리는 eapim-admin "포탈메뉴관리" 화면에서 수행. +# - id: kebab-case (^[a-z0-9-]+$). 변경 시 새 항목으로 인식되므로 변경 금지. +# - group: true → 상위 그룹(클릭 없음). path 를 주면 그룹도 링크 동작. +# - section: GNB(기본) | MYPAGE(마이페이지 드롭다운) +# - expose-roles / access-roles: 생략=전체 허용, AUTHENTICATED=로그인 사용자, +# 그 외 역할 코드 나열 시 하나라도 보유하면 허용(any-of) +# ------------------------------------------------------------------------------ +portal-menu: + items: + - id: service + name: "서비스 소개" + group: true + children: + - { id: service-intro, name: "API 포탈 소개", path: /service/intro } + - { id: service-guide, name: "회원가입 안내", path: /service/guide } + - { id: service-oauth2-guide, name: "OAuth2 개발가이드", path: /service/oauth2-guide } + - { id: service-webhook-dev-guide, name: "웹훅 개발가이드", path: /service/webhook-dev-guide } + + - { id: open-api, name: "오픈 API", path: /apis } + + - id: support + name: "고객지원" + group: true + children: + - { id: support-notice, name: "공지사항", path: /portalnotice } + - { id: support-faq, name: "FAQ", path: /faq_list } + - { id: support-qna, name: "Q&A", path: /inquiry } + - { id: support-partnership, name: "피드백/개선요청", path: /partnership } + + - { id: api-status, name: "API Status", path: /apistatus } + + - id: my-page + name: "마이페이지" + group: true + section: MYPAGE + expose-roles: [AUTHENTICATED] + children: + - { id: my-page-users, name: "개발자 관리", path: /users, icon: fa-users, + expose-roles: [ROLE_CORP_MANAGER], access-roles: [ROLE_CORP_MANAGER] } + - { id: my-page-clients, name: "API 신청 관리", path: /clients, icon: fa-key, + expose-roles: [ROLE_APP], access-roles: [ROLE_APP] } + - { id: my-page-webhook, name: "Webhook 관리", path: /webhook, icon: fa-bell, + expose-roles: [ROLE_WEBHOOK], access-roles: [ROLE_WEBHOOK] } + - { id: my-page-statistics, name: "이용 통계", path: /statistics/api, icon: fa-chart-bar, + expose-roles: [ROLE_APP], access-roles: [ROLE_APP] } + - { id: my-page-profile, name: "내 정보 관리", path: /mypage, icon: fa-user-circle, + expose-roles: [AUTHENTICATED], access-roles: [AUTHENTICATED] } + - { id: my-page-password, name: "비밀번호 변경", path: /password/change, icon: fa-lock, + expose-roles: [AUTHENTICATED], access-roles: [AUTHENTICATED] } diff --git a/src/main/resources/message/apim/message_en.properties b/src/main/resources/message/apim/message_en.properties index a247714..a716e1c 100644 --- a/src/main/resources/message/apim/message_en.properties +++ b/src/main/resources/message/apim/message_en.properties @@ -39,7 +39,7 @@ deptUserManageRegister.id=Department User ID deptUserManageRegister.name=Department User Name portalUser.confirm.password=Please enter your existing password portalUser.Register.userName=Name -portalUser.Register.pass=Password (Combination of uppercase letters, lowercase letters, numbers, special characters, 8-20 characters) +portalUser.Register.pass=Password (Combination of uppercase letters, lowercase letters, numbers, special characters, 8-50 characters) portalUser.Register.passConfirm=Confirm Password portalUser.Register.email=Email ID portalUser.Register.domain=Domain @@ -82,6 +82,6 @@ status=Status table.reger=Registrar zip=Zip Code ROLE_CORP_MANAGER=Manager -ROLE_CORP_USER=User +ROLE_CORP_USER=Developer ROLE_USER=Individual login.passLengthShort=Password must be over 8 characters. diff --git a/src/main/resources/message/apim/message_ko.properties b/src/main/resources/message/apim/message_ko.properties index 89241d3..58d42cc 100644 --- a/src/main/resources/message/apim/message_ko.properties +++ b/src/main/resources/message/apim/message_ko.properties @@ -39,7 +39,7 @@ deptUserManageRegister.name=\uBD80\uC11C \uC0AC\uC6A9\uC790 \uC774\uB984 entrprsUserManageList.regName=\uBC95\uC778 \uC0AC\uC6A9\uC790 \uB4F1\uB85D \uC774\uB984 portalUser.confirm.password=\uAE30\uC874 \uBE44\uBC00\uBC88\uD638\uB97C \uC785\uB825\uD574\uC8FC\uC138\uC694 portalUser.Register.userName=\uC774\uB984 -portalUser.Register.pass=\uC601\uBB38 \uB300\uBB38\uC790,\uC601\uBB38 \uC18C\uBB38\uC790,\uC22B\uC790,\uD2B9\uC218\uBB38\uC790 \uC870\uD569 8-20\uC790 +portalUser.Register.pass=\uC601\uBB38 \uB300\uBB38\uC790,\uC601\uBB38 \uC18C\uBB38\uC790,\uC22B\uC790,\uD2B9\uC218\uBB38\uC790 \uC870\uD569 8-50\uC790 portalUser.Register.passConfirm=\uBE44\uBC00\uBC88\uD638 \uD655\uC778 portalUser.Register.email=\uC774\uBA54\uC77C \uC544\uC774\uB514 portalUser.Register.domain=\uB3C4\uBA54\uC778 @@ -82,6 +82,6 @@ status=\uC0C1\uD0DC table.reger=\uB4F1\uB85D\uC790 zip=\uC6B0\uD3B8\uBC88\uD638 ROLE_CORP_MANAGER=\uAD00\uB9AC\uC790 -ROLE_CORP_USER=\uC774\uC6A9\uC790 +ROLE_CORP_USER=\uAC1C\uBC1C\uC790 ROLE_USER=\uAC1C\uC778\uC0AC\uC6A9\uC790 ConcurrentSessionControlAuthenticationStrategy.exceededAllowed=\uCD5C\uB300 \uB85C\uADF8\uC778 \uD5C8\uC6A9 \uC5F0\uACB0\uC744 \uCD08\uACFC\uD558\uC600\uC2B5\uB2C8\uB2E4. diff --git a/src/main/resources/roles.yml b/src/main/resources/roles.yml new file mode 100644 index 0000000..c4061da --- /dev/null +++ b/src/main/resources/roles.yml @@ -0,0 +1,47 @@ +# ------------------------------------------------------------------------------ +# 포탈 역할 정의 (roles.yml) +# +# - 기존 application.yml 의 portal.portal_security 를 이동한 파일. +# - 로그인 시 권한 확장(PortalUserAuthService)은 이 파일 바인딩을 직접 사용한다. +# - 부팅 시 PTL_ROLE / PTL_ROLE_AUTHORITY 로 미러 적재되며, +# 해당 테이블은 eapim-admin 메뉴 권한 선택 UI 소스로만 소비된다. +# ------------------------------------------------------------------------------ +portal-roles: + roles: + - code: ROLE_USER + name: "개인사용자" + authorities: + - ROLE_INQUIRY + - ROLE_ACCOUNT + - code: ROLE_CORP_USER + name: "법인사용자" + authorities: + - ROLE_API_KEY_REQUEST + - ROLE_API_KEY_REQUEST_VIEW + - ROLE_INQUIRY + - ROLE_APP + - ROLE_ACCOUNT + - code: ROLE_CORP_MANAGER + name: "법인관리자" + authorities: + - ROLE_API_KEY_REQUEST + - ROLE_API_KEY_REQUEST_VIEW + - ROLE_WEBHOOK + - ROLE_INQUIRY + - ROLE_APP + - ROLE_ACCOUNT + - ROLE_CORP_API + - ROLE_DASHBOARD + - ROLE_USER_MANAGER + + # PTL_ROLE(AUTHORITY 유형) 한글 라벨 — admin 권한 선택 UI 표기용 + authority-names: + ROLE_INQUIRY: "문의 작성" + ROLE_ACCOUNT: "계정 관리" + ROLE_API_KEY_REQUEST: "인증키 신청" + ROLE_API_KEY_REQUEST_VIEW: "인증키 신청 조회" + ROLE_APP: "앱/API 신청 관리" + ROLE_WEBHOOK: "웹훅 관리" + ROLE_CORP_API: "법인 API 조회" + ROLE_DASHBOARD: "대시보드" + ROLE_USER_MANAGER: "개발자 관리" diff --git a/src/main/resources/static/css/api-statistics.css b/src/main/resources/static/css/api-statistics.css index 9e8581f..069558d 100644 --- a/src/main/resources/static/css/api-statistics.css +++ b/src/main/resources/static/css/api-statistics.css @@ -1,18 +1,74 @@ -/* API Statistics Page Styles */ - .api-statistics-container { - /*max-width: 1200px;*/ margin: 0 auto; padding: 48px 0px; } -/* Search Section */ +.statistics-notice { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 8px; + margin-bottom: 16px; + padding: 12px 18px; + background: #eef4fc; + border: 1px solid #d6e4f5; + border-radius: 10px; + font-size: 13px; + color: #4a5568; +} + +.statistics-notice-latest { + color: #0049B4; + font-weight: 600; +} + .statistics-search-section { display: flex; - justify-content: flex-end; + flex-direction: column; + align-items: flex-end; + gap: 12px; margin-bottom: 30px; } +.search-mode-tabs { + display: inline-flex; + background: #eef1f5; + border-radius: 22px; + padding: 4px; +} + +.mode-tab { + border: none; + background: transparent; + padding: 7px 22px; + border-radius: 18px; + font-size: 14px; + color: #666; + cursor: pointer; + transition: all 0.2s; +} +.mode-tab.active { + background: #0049B4; + color: #fff; + font-weight: 600; +} + +.month-range-picker { + display: flex; + align-items: center; + gap: 8px; +} + +.month-input { + border: none; + padding: 8px 12px; + font-size: 14px; + color: #333; + background: transparent; + outline: none; + cursor: pointer; +} + .search-filter-row { display: flex; align-items: center; @@ -54,16 +110,13 @@ cursor: pointer; transition: background 0.2s; } - .btn-search:hover { background: #003d99; } - .btn-search svg { stroke: #fff; } -/* Summary Card */ .statistics-summary-card { background: #f5f8fc; border-radius: 16px; @@ -103,11 +156,9 @@ outline: none; transition: all 0.2s; } - .app-select:hover { background-color: #f0f5ff; } - .app-select:focus { border-color: #003d99; box-shadow: 0 0 0 3px rgba(0, 73, 180, 0.1); @@ -122,7 +173,6 @@ color: #333; margin-bottom: 16px; } - .summary-total-text strong { font-size: 24px; color: #0049B4; @@ -140,11 +190,12 @@ gap: 8px; font-size: 14px; } - .summary-detail-item.success .detail-icon { color: #4A90D9; } - +.summary-detail-item.timeout .detail-icon { + color: #F5C36B; +} .summary-detail-item.failure .detail-icon { color: #F5A3B5; } @@ -163,7 +214,6 @@ color: #999; } -/* Donut Chart */ .summary-center { flex: 0 0 200px; display: flex; @@ -180,7 +230,6 @@ transition: stroke-dasharray 0.3s ease, stroke-dashoffset 0.3s ease; } -/* Rate Display */ .summary-right { flex: 0 0 230px; display: flex; @@ -195,7 +244,6 @@ padding: 16px 0; border-bottom: 1px solid #e0e0e0; } - .rate-display:last-child { border-bottom: none; } @@ -205,11 +253,12 @@ height: 12px; border-radius: 50%; } - .rate-indicator.success { background: #4A90D9; } - +.rate-indicator.timeout { + background: #F5C36B; +} .rate-indicator.failure { background: #F5A3B5; } @@ -228,6 +277,10 @@ color: #4A90D9; } +.timeout-rate .rate-value { + color: #F5C36B; +} + .failure-rate .rate-value { color: #F5A3B5; } @@ -237,12 +290,15 @@ color: #999; } -/* Statistics Table Card */ .statistics-table-card { background: #fff; border-radius: 16px; overflow: hidden; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + margin-bottom: 30px; +} +.statistics-table-card:last-child { + margin-bottom: 0; } .table-header { @@ -262,7 +318,6 @@ font-weight: 600; margin: 0; } - .table-title svg { stroke: #fff; } @@ -279,7 +334,6 @@ font-size: 14px; transition: background 0.2s; } - .btn-download:hover { background: rgba(255, 255, 255, 0.3); color: #fff; @@ -293,7 +347,6 @@ width: 100%; border-collapse: collapse; } - .statistics-table thead th { padding: 16px 20px; text-align: left; @@ -302,16 +355,23 @@ border-bottom: 1px solid #e0e0e0; background: #fafafa; } - .statistics-table tbody td { padding: 16px 20px; border-bottom: 1px solid #f0f0f0; color: #333; } - .statistics-table tbody tr:hover { background: #f9f9f9; } +.statistics-table tfoot .total-row { + background: #e3f2fd; +} +.statistics-table tfoot .total-row td { + padding: 16px 20px; + font-weight: 600; + color: #0049B4; + border-bottom: none; +} .api-name { display: flex; @@ -328,20 +388,16 @@ background: #f0f0f0; border-radius: 6px; } - .api-icon svg { stroke: #666; } - .api-icon.total { background: #e3f2fd; } - .api-icon.total svg { stroke: #0049B4; } -/* Progress Bar */ .progress-cell { min-width: 200px; } @@ -359,83 +415,64 @@ transition: width 0.3s ease; } +.progress-timeout { + background: #F5C36B; + transition: width 0.3s ease; +} + .progress-failure { background: #F5A3B5; transition: width 0.3s ease; } -/* Total Row */ -.statistics-table tfoot .total-row { - background: #e3f2fd; -} - -.statistics-table tfoot .total-row td { - padding: 16px 20px; - font-weight: 600; - color: #0049B4; - border-bottom: none; -} - -/* Empty State */ .empty-row td { text-align: center; padding: 40px 20px; color: #999; } -/* Responsive */ @media (max-width: 992px) { .summary-content { flex-direction: column; gap: 30px; } - .summary-left, .summary-center, .summary-right { flex: none; width: 100%; } - .summary-center { order: -1; } - .summary-right { flex-direction: row; justify-content: center; } } - @media (max-width: 768px) { .api-statistics-container { padding: 20px 16px; } - .statistics-summary-card { padding: 24px 20px; } - .app-toggle-buttons { flex-wrap: wrap; } - .toggle-btn { flex: 1; text-align: center; } - .statistics-table thead th, .statistics-table tbody td, .statistics-table tfoot td { padding: 12px 10px; font-size: 13px; } - .progress-cell { min-width: 100px; } - .search-filter-row { flex-wrap: wrap; } diff --git a/src/main/resources/static/css/main.css b/src/main/resources/static/css/main.css index 71d0dd4..7e881f4 100644 --- a/src/main/resources/static/css/main.css +++ b/src/main/resources/static/css/main.css @@ -18,7 +18,7 @@ html { } body { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; line-height: 1.6; color: #1A1A2E; background-color: #FFFFFF; @@ -87,22 +87,33 @@ section, summary { color: #1A1A2E; } -@font-face { - font-family: "Noto Sans KR"; - src: url("/font/NotoSansKR-VariableFont_wght.ttf") format("truetype"); - font-weight: 100 900; - font-style: normal; - font-display: swap; -} body { + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 16px; font-weight: 400; color: #1A1A2E; } +h1, .h1, +h2, .h2, +h3, .h3, +h4, .h4, +h5, .h5, +h6, .h6 { + font-family: "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-weight: 500; +} + +[class$=-title], +[class*="-title "], +[class$=__title], +[class*="__title "] { + font-family: "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-weight: 500; +} + h1, .h1 { font-size: 56px; - font-weight: 800; margin-bottom: 24px; } @media (max-width: 768px) { @@ -118,7 +129,6 @@ h1, .h1 { h2, .h2 { font-size: 48px; - font-weight: 700; margin-bottom: 16px; } @media (max-width: 768px) { @@ -134,7 +144,6 @@ h2, .h2 { h3, .h3 { font-size: 40px; - font-weight: 700; margin-bottom: 16px; } @media (max-width: 768px) { @@ -145,19 +154,16 @@ h3, .h3 { h4, .h4 { font-size: 32px; - font-weight: 600; margin-bottom: 8px; } h5, .h5 { font-size: 24px; - font-weight: 600; margin-bottom: 8px; } h6, .h6 { font-size: 20px; - font-weight: 600; margin-bottom: 8px; } @@ -464,6 +470,22 @@ hr { animation: float 20s ease-in-out infinite; } +@keyframes float { + 0%, 100% { + transform: translateY(0px); + } + 50% { + transform: translateY(-15px); + } +} +.floating-slow { + animation: float 6s ease-in-out infinite; +} + +.floating-fast { + animation: float 4s ease-in-out infinite; +} + .animate-bounce { animation: bounce 2s infinite; } @@ -472,6 +494,45 @@ hr { animation: spin 1s linear infinite; } +@keyframes drawLine { + to { + stroke-dashoffset: 0; + } +} +.draw-line { + stroke-dasharray: 1000; + stroke-dashoffset: 1000; + animation: drawLine 3s ease-out forwards; +} + +.pulsing { + transform-origin: 400px 110px; + transform-box: fill-box; + animation: pulseLine 2s ease-in-out infinite; +} + +@keyframes pulseLine { + 0%, 100% { + transform: translate(0, 0) scale(1); + opacity: 0.9; + } + 50% { + transform: translateX(75px) scale(1.1); + /* 하나의 transform에 한 줄로 작성 */ + opacity: 0.7; + } +} +@keyframes pulse { + 0%, 100% { + transform: translate(0, 0) scale(1); + opacity: 0.9; + } + 50% { + transform: translateX(75px) scale(1.1); + /* 하나의 transform에 한 줄로 작성 */ + opacity: 0.7; + } +} .animate-pulse { animation: pulse 2s ease-in-out infinite; } @@ -569,95 +630,80 @@ hr { transform: translateX(200%) rotate(45deg); } -@font-face { - font-family: "Spoqa Han Sans Neo"; - font-weight: 700; - src: local("Spoqa Han Sans Neo Bold"), url("/font/kjb/SpoqaHanSansNeo-Bold.woff2") format("woff2"), url("/font/kjb/SpoqaHanSansNeo-Bold.woff") format("woff"), url("/font/kjb/SpoqaHanSansNeo-Bold.ttf") format("truetype"); -} -@font-face { - font-family: "Spoqa Han Sans Neo"; - font-weight: 400; - src: local("Spoqa Han Sans Neo Regular"), url("/font/kjb/SpoqaHanSansNeo-Regular.woff2") format("woff2"), url("/font/kjb/SpoqaHanSansNeo-Regular.woff") format("woff"), url("/font/kjb/SpoqaHanSansNeo-Regular.ttf") format("truetype"); -} -/* OneShinhan — 웹폰트 서빙 (제주은행 inbank/css/font/ 원본을 정적 리소스로 호스팅) - weight 300=Light / 400&500=Medium / 700=Bold */ +/* OneShinhan */ @font-face { font-family: "OneShinhan"; font-weight: 300; font-style: normal; - src: local("원신한 Light"), local("OneShinhan Light"), local("OneShinhan-Light"), url("/font/djb/OneShinhanLight.woff") format("woff"); + font-display: swap; + src: local("OneShinhan Light"), local("OneShinhan-Light"), url("/font/shinhan/OneShinhanLight.woff") format("woff"); } @font-face { font-family: "OneShinhan"; font-weight: 400; font-style: normal; - src: local("원신한 Medium"), local("OneShinhan Medium"), local("OneShinhan-Medium"), url("/font/djb/OneShinhanMedium.woff") format("woff"); + font-display: swap; + src: local("OneShinhan Medium"), local("OneShinhan-Medium"), url("/font/shinhan/OneShinhanMedium.woff") format("woff"); } @font-face { font-family: "OneShinhan"; font-weight: 500; font-style: normal; - src: local("원신한 Medium"), local("OneShinhan Medium"), local("OneShinhan-Medium"), url("/font/djb/OneShinhanMedium.woff") format("woff"); + font-display: swap; + src: local("OneShinhan Medium"), local("OneShinhan-Medium"), url("/font/shinhan/OneShinhanMedium.woff") format("woff"); } @font-face { font-family: "OneShinhan"; font-weight: 700; font-style: normal; - src: local("원신한 Bold"), local("OneShinhan Bold"), local("OneShinhan-Bold"), url("/font/djb/OneShinhanBold.woff") format("woff"); + font-display: swap; + src: local("OneShinhan Bold"), local("OneShinhan-Bold"), url("/font/shinhan/OneShinhanBold.woff") format("woff"); } -/* 한글 family name alias (변수에서 '원신한' 우선 매칭 시 동일 폰트 사용) */ +/* Spoqa Han Sans Neo */ @font-face { - font-family: "원신한"; - font-weight: 300; - font-style: normal; - src: url("/font/djb/OneShinhanLight.woff") format("woff"); -} -@font-face { - font-family: "원신한"; + font-family: "Spoqa Han Sans Neo"; font-weight: 400; font-style: normal; - src: url("/font/djb/OneShinhanMedium.woff") format("woff"); + font-display: swap; + src: local("Spoqa Han Sans Neo Regular"), url("/font/spoqa-hans/SpoqaHanSansNeo-Regular.woff2") format("woff2"), url("/font/spoqa-hans/SpoqaHanSansNeo-Regular.woff") format("woff"), url("/font/spoqa-hans/SpoqaHanSansNeo-Regular.ttf") format("truetype"); } @font-face { - font-family: "원신한"; - font-weight: 500; - font-style: normal; - src: url("/font/djb/OneShinhanMedium.woff") format("woff"); -} -@font-face { - font-family: "원신한"; + font-family: "Spoqa Han Sans Neo"; font-weight: 700; font-style: normal; - src: url("/font/djb/OneShinhanBold.woff") format("woff"); + font-display: swap; + src: local("Spoqa Han Sans Neo Bold"), url("/font/spoqa-hans/SpoqaHanSansNeo-Bold.woff2") format("woff2"), url("/font/spoqa-hans/SpoqaHanSansNeo-Bold.woff") format("woff"), url("/font/spoqa-hans/SpoqaHanSansNeo-Bold.ttf") format("truetype"); } -/* SpoqaHanSans */ +/* SpoqaHanSans (Neo 미설치 환경 폴백) */ @font-face { font-family: "SpoqaHanSans"; font-weight: 400; - src: url("/font/kjb/SpoqaHanSansRegular.woff") format("woff"), url("/font/kjb/SpoqaHanSansRegular.woff2") format("woff2"), url("/font/kjb/SpoqaHanSansRegular.ttf") format("truetype"); + font-style: normal; + font-display: swap; + src: local("Spoqa Han Sans Regular"), url("/font/spoqa-hans/SpoqaHanSansRegular.woff2") format("woff2"), url("/font/spoqa-hans/SpoqaHanSansRegular.woff") format("woff"), url("/font/spoqa-hans/SpoqaHanSansRegular.ttf") format("truetype"); } @font-face { - font-family: "SpoqaHanSansBold"; + font-family: "SpoqaHanSans"; font-weight: 700; - src: url("/font/kjb/SpoqaHanSansBold.woff") format("woff"), url("/font/kjb/SpoqaHanSansBold.woff2") format("woff2"), url("/font/kjb/SpoqaHanSansBold.ttf") format("truetype"); + font-style: normal; + font-display: swap; + src: local("Spoqa Han Sans Bold"), url("/font/spoqa-hans/SpoqaHanSansBold.woff2") format("woff2"), url("/font/spoqa-hans/SpoqaHanSansBold.woff") format("woff"), url("/font/spoqa-hans/SpoqaHanSansBold.ttf") format("truetype"); } -/* HGGGothicssi */ +/* Noto Sans KR (Variable) + - 본문 3순위 폴백이므로 로컬 설치본을 최우선 사용한다. + - 웹폰트는 한글 음절 전체(U+AC00–D7A3)·라틴·기호만 남긴 woff2 서브셋(약 1.1MB). + 원본 VariableFont TTF(9.9MB)는 전송 중 abort로 broken pipe 로그를 유발해 제거했다. + 서브셋 재생성은 docs/font-subset.md 참고. */ @font-face { - font-family: "HGGGothicssi"; - font-weight: 300; - src: url("/font/kjb/HGGGothicssi40g.woff") format("woff"), url("/font/kjb/HGGGothicssi40g.woff2") format("woff2"), url("/font/kjb/HGGGothicssi40g.ttf") format("truetype"); -} -@font-face { - font-family: "HGGGothicssi"; - font-weight: 400; - src: url("/font/kjb/HGGGothicssi60g.woff") format("woff"), url("/font/kjb/HGGGothicssi60g.woff2") format("woff2"), url("/font/kjb/HGGGothicssi60g.ttf") format("truetype"); -} -@font-face { - font-family: "HGGGothicssi"; - font-weight: 700; - src: url("/font/kjb/HGGGothicssi80g.woff") format("woff"), url("/font/kjb/HGGGothicssi80g.woff2") format("woff2"), url("/font/kjb/HGGGothicssi80g.ttf") format("truetype"); + font-family: "Noto Sans KR"; + font-weight: 100 900; + font-style: normal; + font-display: swap; + src: local("Noto Sans KR"), local("NotoSansKR"), local("Noto Sans CJK KR"), url("/font/noto-sans/NotoSansKR-subset.woff2") format("woff2"); } :root { + --primary-color: #0049b4; + --secondary-color: #008ae2; --primary-blue: #4B9BFF; --secondary-blue: #2E7FF7; --accent-cyan: #00D4FF; @@ -665,6 +711,8 @@ hr { --accent-orange: #FF6B6B; --accent-green: #6BCF7F; --accent-purple: #A78BFA; + --accent-color: #efdcb2; + --accent-light: #E9F9FF; --text-dark: #1A1A2E; --text-gray: #64748B; --text-light: #94A3B8; @@ -676,75 +724,9 @@ hr { --shadow-md: 0 4px 12px rgba(75, 155, 255, 0.1); --shadow-lg: 0 8px 24px rgba(75, 155, 255, 0.15); --shadow-xl: 0 16px 40px rgba(75, 155, 255, 0.2); -} - -.design-survey-bar { - position: fixed; - top: 0; - left: 0; - right: 0; - height: 48px; - background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); - z-index: 400; - display: flex; - align-items: center; - justify-content: center; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); -} -.design-survey-bar .survey-container { - display: flex; - align-items: center; - gap: 16px; -} -.design-survey-bar .survey-label { - color: #ffffff; - font-size: 14px; - font-weight: 500; -} -.design-survey-bar .survey-buttons { - display: flex; - gap: 8px; -} -.design-survey-bar .survey-btn { - padding: 6px 16px; - border: 2px solid rgba(255, 255, 255, 0.5); - border-radius: 20px; - background: transparent; - color: #ffffff; - font-size: 14px; - font-weight: 600; - cursor: pointer; - transition: all 0.3s ease; -} -.design-survey-bar .survey-btn:hover { - background: rgba(255, 255, 255, 0.2); - border-color: #ffffff; -} -.design-survey-bar .survey-btn.active { - background: #ffffff; - color: #667eea; - border-color: #ffffff; -} -@media (max-width: 768px) { - .design-survey-bar { - height: 40px; - } - .design-survey-bar .survey-label { - display: none; - } - .design-survey-bar .survey-btn { - padding: 4px 12px; - font-size: 12px; - } -} - -body.design-survey-active .global-header { - margin-top: 48px; -} -@media (max-width: 768px) { - body.design-survey-active .global-header { - margin-top: 40px; - } + --shadow-hover: 0 30px 60px -10px rgba(0, 73, 180, 0.2); + --border-color: #E2E8F0; + --transition-smooth: all 0.4s cubic-bezier(0.16, 1, 0.3, 1); } .blind { @@ -760,53 +742,105 @@ body.design-survey-active .global-header { } .global-header { - position: relative; - background-color: #ffffff; - z-index: 350; - height: 112px; - transition: all 0.3s ease; - border-bottom: 1px solid #dddddd; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 80px; + background: rgba(255, 255, 255, 0.85); + backdrop-filter: blur(15px); + border-bottom: 1px solid rgba(0, 73, 180, 0.08); + z-index: 1000; + display: flex; + align-items: center; + transition: var(--transition-smooth); } .global-header.index-header { - background-color: #E5F8FF; + background-color: white; } .global-header .container { height: 100%; padding: 0; + width: 100%; } .global-header .container .header-content { height: 100%; } +@media (max-width: 1024px) { + .global-header { + height: clamp(44px, 11.73vw, 60px); + background: rgba(255, 255, 255, 0.85); + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); + border-bottom: none; + } + .global-header .container { + padding: 0; + max-width: 100%; + background-color: #FEFEFE; + } + .global-header .container .header-content { + padding: 0 clamp(16px, 4.27vw, 24px) 0 clamp(20px, 5.33vw, 28px); + height: clamp(44px, 11.73vw, 60px); + margin-top: 0; + } +} .header-content { + width: 100%; + max-width: 1280px; + margin: 0 auto; + padding: 0 24px; display: flex; align-items: center; justify-content: space-between; - height: 65px; - max-width: 1920px; - margin: 0 auto; - padding: 0 20px; } .header-left { display: flex; align-items: center; + flex-shrink: 0; } .header-left .logo { height: 32px; - width: 114px; + align-items: flex-end; + flex-wrap: nowrap; +} +.header-left .logo img { + flex-shrink: 0; +} +.header-left .logo .mobile-logo-link { + align-items: flex-end; + flex-shrink: 0; +} +.header-left .logo .mobile-logo-text { + align-items: flex-end; + line-height: 1; + padding-bottom: 2px; + white-space: nowrap; + flex-shrink: 0; } .logo-wrapper { display: flex; align-items: center; gap: 12px; + font-size: 24px; +} +.logo-wrapper .logo-text-bold { + font-weight: 700; + color: var(--primary-color); + letter-spacing: -0.5px; +} +.logo-wrapper .logo-text-thin { + font-weight: 300; + color: var(--secondary-color); + margin-left: 4px; } .logo { - display: block; + display: flex; height: 32px; - width: 114px; vertical-align: middle; z-index: 10; } @@ -843,19 +877,16 @@ body.design-survey-active .global-header { } .logo-link, -.mobile-logo-link, .drawer-logo-link { display: inline-block; text-decoration: none; transition: transform 0.3s ease; } .logo-link:hover, -.mobile-logo-link:hover, .drawer-logo-link:hover { transform: scale(1.05); } .logo-link img, -.mobile-logo-link img, .drawer-logo-link img { display: block; } @@ -889,7 +920,7 @@ body.design-survey-active .global-header { justify-content: space-between; width: 100%; } -@media (max-width: 768px) { +@media (max-width: 1024px) { .desktop-header { display: none; } @@ -901,7 +932,7 @@ body.design-survey-active .global-header { justify-content: space-between; width: 100%; } -@media (max-width: 768px) { +@media (max-width: 1024px) { .mobile-header { display: flex; } @@ -922,7 +953,7 @@ body.design-survey-active .global-header { object-fit: contain; } .mobile-left .mobile-logo-text { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: clamp(14px, 3.73vw, 18px); font-weight: 700; color: #212529; @@ -1042,7 +1073,7 @@ body.design-survey-active .global-header { object-fit: contain; } .mobile-drawer .drawer-header .drawer-header-left .drawer-logo-text { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: clamp(14px, 3.73vw, 18px); font-weight: 700; color: #212529; @@ -1104,7 +1135,7 @@ body.design-survey-active .global-header { height: 56px; } .mobile-drawer .drawer-welcome .welcome-login-prompt { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 16px; font-weight: 700; color: #212529; @@ -1113,7 +1144,7 @@ body.design-survey-active .global-header { text-align: center; } .mobile-drawer .drawer-welcome .welcome-text { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 14px; font-weight: 400; color: #212529; @@ -1135,7 +1166,7 @@ body.design-survey-active .global-header { height: 40px; background: #e5e7eb; color: #5f666c; - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 14px; font-weight: 700; border-radius: 8px; @@ -1153,7 +1184,7 @@ body.design-survey-active .global-header { height: 40px; background: #0049b4; color: #ffffff; - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 14px; font-weight: 700; border-radius: 8px; @@ -1180,7 +1211,7 @@ body.design-survey-active .global-header { gap: 8px; } .mobile-drawer .drawer-welcome.authenticated .welcome-user-name { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 16px; font-weight: 700; color: #212529; @@ -1213,7 +1244,7 @@ body.design-survey-active .global-header { height: 16px; } .mobile-drawer .drawer-footer .drawer-logout-btn span { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 14px; font-weight: 500; color: #0049b4; @@ -1245,7 +1276,7 @@ body.design-survey-active .global-header { background: transparent; border: none; cursor: pointer; - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 16px; font-weight: 500; color: #212529; @@ -1264,7 +1295,7 @@ body.design-survey-active .global-header { .mobile-drawer .drawer-nav .drawer-menu-item .drawer-submenu li a { display: block; padding: 8px 16px 8px 57px; - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 14px; font-weight: 500; color: #212529; @@ -1300,7 +1331,7 @@ body.design-survey-active .global-header { border: none; cursor: pointer; padding: 8px; - z-index: 501; + z-index: 1051; } .btn-mobilemenu span { display: block; @@ -1345,7 +1376,7 @@ body.design-survey-active .global-header { right: 0; bottom: 0; background: #FFFFFF; - z-index: 500; + z-index: 1050; right: -100%; transition: none; overflow-y: auto; @@ -1422,7 +1453,7 @@ body.design-survey-active .global-header { .nav-menu { display: flex; list-style: none; - gap: 8px; + gap: 24px; margin: 0; padding: 0; } @@ -1432,6 +1463,7 @@ body.design-survey-active .global-header { .nav-menu > li.submenu-open > .nav-link { background: var(--light-bg); color: var(--primary-blue); + border-radius: 9px; } .nav-menu > li.submenu-open .sub-menu { visibility: visible; @@ -1440,7 +1472,7 @@ body.design-survey-active .global-header { } .nav-menu > li .sub-menu { position: absolute; - top: calc(100% + 8px); + top: calc(100% + 17px); left: 0; min-width: 200px; background: var(--white); @@ -1474,16 +1506,13 @@ body.design-survey-active .global-header { } .nav-link { - display: flex; - align-items: center; - gap: 6px; - text-decoration: none; - color: var(--text-dark); + font-size: 16px; font-weight: 500; - font-size: 20px; - padding: 8px 16px; - border-radius: 8px; - transition: all 0.3s ease; + color: var(--text-gray); + padding: 12px; + position: relative; + transition: var(--transition-smooth); + white-space: nowrap; } .nav-link:hover { color: var(--primary-blue); @@ -1510,7 +1539,7 @@ body.design-survey-active .global-header { box-shadow: var(--shadow-lg); } -@media (max-width: 768px) { +@media (max-width: 1024px) { .global-header { height: clamp(44px, 11.73vw, 60px); background: rgba(255, 255, 255, 0.85); @@ -1547,7 +1576,7 @@ body.design-survey-active .global-header { display: block; } .header-user-info .user-identity .user-name { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 16px; font-weight: 400; color: #515961; @@ -1559,7 +1588,7 @@ body.design-survey-active .global-header { line-height: 1; } .header-user-info .header-link { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 16px; font-weight: 400; color: #515961; @@ -1632,14 +1661,167 @@ body.design-survey-active .global-header { transform: translateY(0); } -@media (max-width: 768px) { +@media (max-width: 1024px) { .header-user-info { display: none; } } +/* 원래 있던 Footer 스타일 (주석 처리) .global-footer { background: #FFFFFF; border-top: 1px solid #E5E7EB; + + .container { + max-width: 1200px; + margin: 0 auto; + padding: 0 20px; + } +} + +.footer-content { + display: flex; + justify-content: space-between; + align-items: center; + min-height: 200px; + padding: 40px 0; + gap: 40px; + + @include respond-to('md') { + flex-direction: column; + align-items: flex-start; + gap: 32px; + padding: 32px 0; + } +} + +// Left Section +.footer-left { + display: flex; + flex-direction: column; + gap: 16px; + + .footer-logo { + width: 114px; + height: auto; + object-fit: contain; + } + + .footer-links { + display: flex; + align-items: center; + gap: 8px; + + .footer-link { + font-family: $font-family-primary; + font-size: 16px; + font-weight: 400; + color: #5F666C; + text-decoration: none; + transition: color 0.3s ease; + + &:hover { + color: #0049B4; + text-decoration: underline; + } + } + + .footer-link--external { + display: inline-flex; + align-items: center; + gap: 4px; + + .footer-link-external-icon { + flex-shrink: 0; + width: 14px; + height: 14px; + color: currentColor; + } + } + + .footer-separator { + color: #D1D5DB; + font-size: 16px; + user-select: none; + } + } + + .footer-copyright { + font-family: $font-family-primary; + font-size: 14px; + font-weight: 400; + color: #9CA3AF; + margin: 0; + line-height: 1.5; + } +} + +// Right Section +.footer-right { + display: flex; + flex-direction: column; + gap: 16px; + align-items: flex-end; + + @include respond-to('md') { + align-items: flex-start; + width: 100%; + } + + .footer-related-sites { + .related-sites-select { + width: 240px; + height: 44px; + padding: 0 16px; + font-family: $font-family-primary; + font-size: 16px; + font-weight: 400; + color: #5F666C; + background: #FFFFFF; + border: 1px solid #D1D5DB; + border-radius: 6px; + cursor: pointer; + transition: all 0.3s ease; + appearance: none; + background-image: url("data:image/svg+xml,%3Csvg width='12' height='8' viewBox='0 0 12 8' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1L6 6L11 1' stroke='%235F666C' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 16px center; + padding-right: 40px; + + &:hover { + border-color: #0049B4; + } + + &:focus { + outline: none; + border-color: #0049B4; + box-shadow: 0 0 0 3px rgba(0, 73, 180, 0.1); + } + + @include respond-to('md') { + width: 100%; + } + } + } + + .footer-contact { + font-family: $font-family-primary; + font-size: 18px; + font-weight: 700; + color: #1F2937; + margin: 0; + line-height: 1.2; + + @include respond-to('md') { + font-size: 16px; + } + } +} +*/ +/* 피그마 시안 적용 다크 모드 Footer 스타일 */ +.global-footer { + background-color: rgb(15, 23, 42); + color: rgb(100, 116, 139); + padding: 60px 0px; } .global-footer .container { max-width: 1200px; @@ -1651,8 +1833,6 @@ body.design-survey-active .global-header { display: flex; justify-content: space-between; align-items: center; - min-height: 200px; - padding: 40px 0; gap: 40px; } @media (max-width: 1024px) { @@ -1680,24 +1860,24 @@ body.design-survey-active .global-header { gap: 8px; } .footer-left .footer-links .footer-link { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 16px; font-weight: 400; - color: #5F666C; + color: #9CA3AF; text-decoration: none; transition: color 0.3s ease; } .footer-left .footer-links .footer-link:hover { - color: #0049B4; + color: #FFFFFF; text-decoration: underline; } .footer-left .footer-links .footer-separator { - color: #D1D5DB; + color: #9CA3AF; font-size: 16px; user-select: none; } .footer-left .footer-copyright { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 14px; font-weight: 400; color: #9CA3AF; @@ -1721,28 +1901,27 @@ body.design-survey-active .global-header { width: 240px; height: 44px; padding: 0 16px; - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 16px; font-weight: 400; - color: #5F666C; - background: #FFFFFF; - border: 1px solid #D1D5DB; + color: #9CA3AF; + background: rgb(30, 41, 59); + border: 1px solid rgb(71, 85, 105); border-radius: 6px; cursor: pointer; transition: all 0.3s ease; appearance: none; - background-image: url("data:image/svg+xml,%3Csvg width='12' height='8' viewBox='0 0 12 8' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1L6 6L11 1' stroke='%235F666C' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); + background-image: url("data:image/svg+xml,%3Csvg width='12' height='8' viewBox='0 0 12 8' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1L6 6L11 1' stroke='%239CA3AF' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 16px center; padding-right: 40px; } .footer-right .footer-related-sites .related-sites-select:hover { - border-color: #0049B4; + border-color: #FFFFFF; } .footer-right .footer-related-sites .related-sites-select:focus { outline: none; - border-color: #0049B4; - box-shadow: 0 0 0 3px rgba(0, 73, 180, 0.1); + border-color: #FFFFFF; } @media (max-width: 1024px) { .footer-right .footer-related-sites .related-sites-select { @@ -1750,10 +1929,10 @@ body.design-survey-active .global-header { } } .footer-right .footer-contact { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 18px; font-weight: 700; - color: #1F2937; + color: #9CA3AF; margin: 0; line-height: 1.2; } @@ -2128,7 +2307,7 @@ body.design-survey-active .global-header { .container { max-width: 1280px; margin: 0 auto; - padding: 0 26px; + padding: 0 20px; } @media (max-width: 768px) { .container { @@ -2775,6 +2954,43 @@ body.design-survey-active .global-header { } } +.btn-action-primary { + display: inline-flex; + align-items: center; + justify-content: center; + background: #2a69de; + color: #fff; + font-size: 20px; + font-weight: 700; + padding: 16px 48px; + border-radius: 10px; + text-decoration: none; + transition: background 0.2s, transform 0.2s; + cursor: pointer; + border: none; +} +.btn-action-primary:hover { + background: rgb(31.8731707317, 92.7219512195, 205.7268292683); + transform: translateY(-2px); + color: #fff; +} +.btn-action-primary:disabled { + background: #cbd5e1 !important; + color: #94a3b8 !important; + cursor: not-allowed !important; + transform: none !important; + pointer-events: none !important; +} +.btn-action-primary.md { + font-size: 14px; + padding: 11px 45px; +} +@media (max-width: 768px) { + .btn-action-primary { + font-size: 15px; + } +} + .status-badge { display: inline-flex; align-items: center; @@ -3296,9 +3512,7 @@ body.design-survey-active .global-header { } } .common-title-bar .common-title, .app-list-title-section .common-title, .register-title-bar .common-title { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 22px; - font-weight: 700; color: #1A1A2E; margin: 0; } @@ -3359,7 +3573,7 @@ body.design-survey-active .global-header { justify-content: center; padding: 5px 14px; border-radius: 40px; - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 13px; font-weight: 700; color: #FFFFFF; @@ -3470,14 +3684,14 @@ body.design-survey-active .global-header { opacity: 0.5; } .apikey-empty-state h3, .app-list-empty h3, .empty-state h3, .empty-api-state h3 { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 24px; font-weight: 700; color: #1A1A2E; margin: 0 0 16px; } .apikey-empty-state p, .app-list-empty p, .empty-state p, .empty-api-state p { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 16px; color: #6e7780; margin: 0; @@ -3491,7 +3705,7 @@ body.design-survey-active .global-header { height: 60px; border: none; border-radius: 12px; - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 18px; font-weight: 700; cursor: pointer; @@ -3755,7 +3969,7 @@ body.design-survey-active .global-header { .form-group label { display: block; font-size: 14px; - font-weight: 500; + font-weight: 600; color: #1A1A2E; margin-bottom: 8px; } @@ -3781,7 +3995,7 @@ body.design-survey-active .global-header { width: 100%; padding: 12px 16px; font-size: 16px; - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; color: #1A1A2E; background: #FFFFFF; border: 2px solid #E2E8F0; @@ -4152,7 +4366,7 @@ select.form-control { box-shadow: 0 0 0 3px rgba(0, 73, 180, 0.1); } .form-input:disabled, .form-input.input-readonly { - background: #F8FAFC; + background: #e9ecef !important; color: #64748B; cursor: not-allowed; } @@ -4446,7 +4660,7 @@ select.form-control { padding: 0 44px 0 16px; border: none; background: transparent; - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 14px; font-weight: 400; color: #1A1A2E; @@ -4554,6 +4768,65 @@ select.form-control { align-items: center; } +.djb-label { + display: block; + font-size: 14px; + font-weight: 700; + color: #000; + margin-bottom: 12px; +} +.djb-label .required { + color: #e53e3e; + margin-left: 4px; +} + +.djb-input { + width: 100%; + padding: 14px 18px; + border: 1px solid #DFDFDF; + border-radius: 8px; + font-size: 14px; + color: #000; + transition: border-color 0.2s; + background-color: #fcfcfc; +} +.djb-input::placeholder { + color: #94A3B8; +} +.djb-input:focus { + outline: none; + border-color: #2a69de; +} + +.djb-textarea { + width: 100%; + padding: 14px 18px; + border: 1px solid #DFDFDF; + border-radius: 8px; + font-size: 14px; + color: #000; + resize: none; + transition: border-color 0.2s; + min-height: 250px; + background-color: #fcfcfc; +} +.djb-textarea::placeholder { + color: #94A3B8; +} +.djb-textarea:focus { + outline: none; + border-color: #2a69de; +} + +.djb-char-counter-wrapper { + text-align: right; + margin-top: 8px; +} +.djb-char-counter-wrapper .char-counter { + font-size: 13px; + color: #7f8a95; +} + .modal-backdrop { position: fixed; top: 0; @@ -4561,7 +4834,7 @@ select.form-control { right: 0; bottom: 0; background: #385670; - z-index: 400; + z-index: 1040; opacity: 0.9; visibility: hidden; transition: all 0.3s ease; @@ -4577,7 +4850,7 @@ select.form-control { left: 0; width: 100%; height: 100vh; - z-index: 500; + z-index: 1050; display: flex; align-items: center; justify-content: center; @@ -4637,7 +4910,7 @@ select.form-control { display: flex; align-items: center; justify-content: center; - z-index: 500; + z-index: 1050; transition: all 0.3s ease; box-shadow: 0 8px 24px rgba(75, 155, 255, 0.15); } @@ -4675,7 +4948,7 @@ select.form-control { flex-direction: column; transform: scale(0.95); transition: transform 0.3s ease; - z-index: 500; + z-index: 1050; margin-left: auto; margin-right: auto; } @@ -4762,7 +5035,6 @@ select.form-control { .modal-footer .btn, .modal-footer .btn-modal-cancel, .modal-footer .btn-modal-confirm { - flex: 1; min-width: 0; height: 46px; font-size: 15px; @@ -4775,7 +5047,7 @@ select.form-control { border: 1px solid #E2E8F0; border-radius: 8px; font-size: 14px; - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; transition: all 0.3s ease; box-sizing: border-box; } @@ -5068,6 +5340,69 @@ select.form-control { transform: translateY(0); } } +.password-policy-checklist { + list-style: none; + padding: 0; + margin: 12px 0 0 0; +} +.password-policy-checklist li { + display: flex; + align-items: center; + gap: 8px; + font-size: 15px; + line-height: 20px; + margin-bottom: 6px; + transition: color 0.15s ease; +} +.password-policy-checklist li:last-child { + margin-bottom: 0; +} +.password-policy-checklist li .policy-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 18px; + height: 18px; + flex-shrink: 0; + font-size: 13px; + font-weight: 700; + line-height: 1; +} +.password-policy-checklist li .policy-icon::before { + content: "•"; +} +.password-policy-checklist li.is-idle { + color: #718096; +} +.password-policy-checklist li.is-idle .policy-icon { + color: #a0aec0; +} +.password-policy-checklist li.is-pass { + color: #1a8f4c; +} +.password-policy-checklist li.is-pass .policy-icon { + color: #1a8f4c; +} +.password-policy-checklist li.is-pass .policy-icon::before { + content: "✔"; +} +.password-policy-checklist li.is-fail { + color: #d63a3a; +} +.password-policy-checklist li.is-fail .policy-icon { + color: #d63a3a; +} +.password-policy-checklist li.is-fail .policy-icon::before { + content: "✖"; +} + +.password-policy-note { + margin: 10px 0 0 0; + font-size: 14px; + line-height: 18px; + color: #ef5555; +} + .auth-group { display: flex; align-items: center; @@ -5076,35 +5411,59 @@ select.form-control { .auth-group.authenticated { gap: 0; } +.auth-group .login-btn { + font-size: 15px; + font-weight: 500; + color: var(--primary-color); + padding: 10px 20px; + border-radius: 8px; + transition: var(--transition-smooth); +} +.auth-group .login-btn:hover { + background-color: var(--accent-light); +} +.auth-group .btn-login:hover { + background-color: var(--accent-light); +} .login-btn { - display: inline-flex; - align-items: center; + font-size: 15px; + font-weight: 500; + color: var(--primary-color); padding: 10px 20px; - color: #1A1A2E; - text-decoration: none; - border: 2px solid #0049b4; - border-radius: 50px; - font-weight: 600; - font-size: 14px; - transition: all 0.3s ease; + border-radius: 8px; + transition: var(--transition-smooth); + white-space: nowrap; } +.login-btn:hover { + background-color: var(--accent-light); +} + +.btn-signup { + font-size: 15px; + font-weight: 700; + color: #FFFFFF; + background-color: var(--primary-color); + padding: 10px 24px; + border-radius: 8px; + white-space: nowrap; + box-shadow: 0 4px 12px rgba(0, 73, 180, 0.15); + transition: var(--transition-smooth); +} +.btn-signup:hover { + background-color: var(--secondary-color); + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(0, 73, 180, 0.25); + color: white; +} + .login-btn-box { - height: 32px; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 6px; - padding: 6px 16px; - color: #212529; - text-decoration: none; - border: none; - border-radius: 6px; - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - font-weight: 600; - font-size: 16px; - background: transparent; - transition: all 0.3s ease; + font-size: 15px; + font-weight: 500; + color: var(--primary-color); + padding: 10px 20px; + border-radius: 8px; + transition: var(--transition-smooth); } .login-btn-box .user-icon { width: 18px; @@ -5125,7 +5484,7 @@ select.form-control { border-radius: 50px; cursor: pointer; transition: all 0.3s ease; - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 14px; } .user-profile-dropdown .user-profile-btn .user-name { @@ -5327,6 +5686,65 @@ select.form-control { right: -20px; } } +.session-timer { + display: inline-flex; + align-items: center; + gap: 4px; + white-space: nowrap; + font-size: 14px; + font-weight: 600; + color: #64748B; +} +.session-timer .session-timer-icon { + font-size: 12px; + color: #64748B; +} +.session-timer .session-timer-text { + font-variant-numeric: tabular-nums; + min-width: 40px; + text-align: center; +} +.session-timer .session-timer-text.session-timer-warning { + color: #e03131; + font-weight: 700; +} + +.session-float { + position: fixed; + top: 16px; + right: 20px; + z-index: 1000; + display: flex; + align-items: center; + gap: 8px; + padding: 6px 14px; + background: #fff; + border: 1px solid #e0e0e0; + border-radius: 20px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12); +} +.session-float .session-keepalive { + display: inline-flex; + align-items: center; + gap: 4px; + margin: 0; + padding-left: 8px; + border-left: 1px solid #e0e0e0; + font-size: 12px; + color: #64748B; + cursor: pointer; + white-space: nowrap; +} +.session-float .session-keepalive input { + margin: 0; + cursor: pointer; +} + +@media (max-width: 1699px) { + .session-float { + display: none; + } +} .data-table { width: 100%; background: #FFFFFF; @@ -5570,6 +5988,211 @@ select.form-control { } } +.board-table { + width: 100%; + border-top: 1px solid #c4c7c8; +} +.board-table-wrapper { + overflow-x: auto; + margin-bottom: 32px; +} +.board-table .board-table-header { + display: flex; + align-items: center; + background: #f9f9f9; + height: 50px; + padding: 0 45px; + border-bottom: 1px solid #dedede; +} +@media (max-width: 768px) { + .board-table .board-table-header { + display: none; + } +} +.board-table .board-table-header .header-cell { + font-size: 14px; + font-family: "Spoqa Han Sans Neo", sans-serif; + font-weight: 500; + color: #000; + text-align: center; +} +.board-table .board-table-body { + display: flex; + flex-direction: column; +} +.board-table .board-table-row { + display: flex; + align-items: center; + height: 50px; + padding: 0 45px; + border-bottom: 1px solid #dedede; + transition: background-color 0.2s; + background: #ffffff; +} +.board-table .board-table-row:hover { + background-color: #f8fbfe; +} +@media (max-width: 768px) { + .board-table .board-table-row { + flex-direction: column; + align-items: flex-start; + height: auto; + padding: 15px; + gap: 10px; + } +} +.board-table .board-table-row .row-cell { + display: flex; + align-items: center; + justify-content: center; + font-size: 14px; + font-family: "Spoqa Han Sans Neo", sans-serif; + font-weight: 400; + color: #000; + white-space: nowrap; +} +@media (max-width: 768px) { + .board-table .board-table-row .row-cell { + justify-content: flex-start; + width: 100% !important; + } + .board-table .board-table-row .row-cell::before { + content: attr(data-label); + font-weight: 500; + color: #64748b; + margin-right: 10px; + min-width: 60px; + } +} +.board-table .board-table-row .row-cell--title { + justify-content: flex-start; + white-space: normal; +} +@media (max-width: 768px) { + .board-table .board-table-row .row-cell--title::before { + display: none; + } +} +.board-table .board-table-row .row-cell--title .notice-title-link { + display: flex; + align-items: center; + gap: 10px; + color: inherit; + text-decoration: none; + width: 100%; +} +.board-table .board-table-row .row-cell--title .notice-title-link:hover { + font-weight: 600; +} +.board-table .board-table-row .row-cell--title .notice-title-link .notice-number { + display: none; + font-weight: 500; + color: #64748b; + flex-shrink: 0; +} +@media (max-width: 768px) { + .board-table .board-table-row .row-cell--title .notice-title-link .notice-number { + display: inline; + } +} +@media (max-width: 768px) { + .board-table .board-table-row .row-cell--number { + display: none; + } +} + +@media (max-width: 768px) { + .inquiry-list-container .table-controls { + flex-direction: column !important; + align-items: stretch !important; + gap: 24px !important; + } + .inquiry-list-container .table-controls .search-field { + order: 1 !important; + } + .inquiry-list-container .table-controls .total-count { + display: flex !important; + align-items: center; + width: 100%; + order: 2 !important; + text-align: left; + font-size: 16px; + color: #000; + padding-bottom: 6px; + border-bottom: 1.5px solid #212529; + margin-bottom: 0; + } + .inquiry-list-container .table-controls .total-count .inquiry-visibility-notice { + margin-left: auto !important; + } +} + +.inquiry-list-container .board-table .board-table-row .row-cell--number { + width: 80px; +} +.inquiry-list-container .board-table .board-table-row .row-cell--title { + flex: 1; + min-width: 200px; + max-width: 500px; +} +@media (max-width: 768px) { + .inquiry-list-container .board-table .board-table-row .row-cell--title { + max-width: none; + } +} +.inquiry-list-container .board-table .board-table-row .row-cell--title .notice-title-link { + display: inline-flex; + align-items: center; + max-width: 100%; +} +.inquiry-list-container .board-table .board-table-row .row-cell--title .notice-title-link .file-icon { + display: inline-flex; + align-items: center; + flex-shrink: 0; + margin-right: 6px; + color: #888; +} +.inquiry-list-container .board-table .board-table-row .row-cell--title .notice-title-link .inquiry-private-label { + color: #8c959f; + font-style: italic; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 320px; +} +.inquiry-list-container .board-table .board-table-row .row-cell--title .notice-title-link .inquiry-subject-text { + display: inline-block; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 240px; + vertical-align: middle; +} +.inquiry-list-container .board-table .board-table-row .row-cell--title .notice-title-link .inquiry-comment-count { + color: #0049b4; + font-weight: 600; + margin-left: 6px; + flex-shrink: 0; + font-size: 14px; +} +.inquiry-list-container .board-table .board-table-row .row-cell--writer { + width: 120px; +} +.inquiry-list-container .board-table .board-table-row .row-cell--writer .inquiry-writer-org { + font-size: 12px; + color: #666; + margin-left: 4px; +} +.inquiry-list-container .board-table .board-table-row .row-cell--status { + width: 120px; +} +.inquiry-list-container .board-table .board-table-row .row-cell--views { + width: 80px; +} +.inquiry-list-container .board-table .board-table-row .row-cell--date { + width: 120px; +} + .list-table { width: 100%; background: #FFFFFF; @@ -5725,17 +6348,17 @@ select.form-control { display: inline-flex; align-items: center; justify-content: center; - padding: 8px 16px; - height: 40px; + padding: 0 16px; + height: 28px; white-space: nowrap; - border-radius: 8px; - font-size: 16px; + border-radius: 5px; + font-size: 12px; font-weight: 400; + font-family: "Spoqa Han Sans Neo", sans-serif; color: #000000; - border: none; + border: 1px solid transparent; cursor: pointer; transition: all 0.3s ease; - white-space: nowrap; } .list-table-btn:hover { transform: translateY(-1px); @@ -5745,22 +6368,36 @@ select.form-control { transform: translateY(0); } .list-table-btn--default { - background-color: #DADADA; + background-color: #f5f5f4; + border-color: #bdc7cf; + color: #64748b; } .list-table-btn--default:hover { - background-color: rgb(207.1, 207.1, 207.1); + background-color: rgb(233.3571428571, 233.3571428571, 231.1928571429); } .list-table-btn--primary { - background-color: #A4D6EA; + background-color: #ecf0fa; + border-color: #4685ef; + color: #2a69de; } .list-table-btn--primary:hover { - background-color: rgb(147.83125, 206.7151785714, 230.26875); + background-color: rgb(216.7625, 224.8125, 244.9375); } .list-table-btn--secondary { - background-color: #CDD4F0; + background-color: #f5f5f4; + border-color: #bdc7cf; + color: #64748b; } .list-table-btn--secondary:hover { - background-color: rgb(187.8846153846, 197.2807692308, 234.8653846154); + background-color: rgb(233.3571428571, 233.3571428571, 231.1928571429); +} +.list-table-btn--danger { + background-color: #fbe7e9; + border-color: #f1c2d6; + color: #bb1026; +} +.list-table-btn--danger:hover { + background-color: rgb(247.5571428571, 210.3428571429, 214.0642857143); } .table-pagination { @@ -6030,7 +6667,8 @@ select.form-control { width: 20px; height: 20px; } - .pagination .pagination-btn i, .pagination .pagination-btn svg { + .pagination .pagination-btn i, + .pagination .pagination-btn svg { font-size: 14px; width: 14px; height: 14px; @@ -6050,58 +6688,84 @@ select.form-control { color: #212529; } } +@media (max-width: 768px) { + .faq-container .table-controls { + display: contents; + padding: 0; + } +} + .faq-accordion { - background: #F6F9FB; - border-radius: 12px; - overflow: hidden; + display: flex; + flex-direction: column; + gap: 6px; margin-bottom: 40px; } @media (max-width: 768px) { .faq-accordion { - background: #FFFFFF; - border-radius: 0; + margin-top: 24px; } } .faq-accordion .faq-item { - border-bottom: 1px solid #DADADA; + position: relative; + border-radius: 10px; + background: #fff; transition: all 0.3s ease; } -.faq-accordion .faq-item:last-child { - border-bottom: none; +.faq-accordion .faq-item:hover .faq-question { + background: #4685ef; + border-color: #e8dddd; } -.faq-accordion .faq-item.active .faq-question .faq-icon { - transform: rotate(180deg); +.faq-accordion .faq-item:hover .faq-question .faq-question-q, +.faq-accordion .faq-item:hover .faq-question .faq-question-text { + color: #fff !important; } -.faq-accordion .faq-item.active .faq-answer { - display: block; - animation: slideDown 0.3s ease; +.faq-accordion .faq-item:hover .faq-question .faq-icon { + color: #fff; } .faq-accordion .faq-question { display: flex; align-items: center; - justify-content: space-between; - padding: 16px 24px; - font-size: 18px; - font-weight: 700; - color: #212529; + min-height: 47px; + height: auto; + padding: 8px 35px 8px 21px; + background: #fff; + border: 1px solid #dedede; + border-radius: 10px; cursor: pointer; transition: all 0.3s ease; - gap: 95px; - min-height: 54px; } @media (max-width: 1024px) { .faq-accordion .faq-question { - padding: 16px 24px; - font-size: 16px; - gap: 24px; + padding: 12px 16px; + min-height: 44px; } } -.faq-accordion .faq-question:hover { - background: rgba(0, 73, 180, 0.02); +.faq-accordion .faq-question .faq-question-q { + font-size: 20px; + font-weight: 700; + color: #555; + margin-right: 15px; + line-height: 1; +} +@media (max-width: 1024px) { + .faq-accordion .faq-question .faq-question-q { + font-size: 16px; + margin-right: 10px; + } } .faq-accordion .faq-question .faq-question-text { flex: 1; - line-height: 1.6; + font-size: 14px; + color: #000; + line-height: normal; + word-break: keep-all; +} +@media (max-width: 1024px) { + .faq-accordion .faq-question .faq-question-text { + font-size: 13px; + line-height: 1.4; + } } .faq-accordion .faq-question .faq-icon { flex-shrink: 0; @@ -6110,7 +6774,7 @@ select.form-control { justify-content: center; width: 22px; height: 22px; - color: #212529; + color: #000; transition: transform 0.3s ease; } .faq-accordion .faq-question .faq-icon svg { @@ -6119,42 +6783,33 @@ select.form-control { } .faq-accordion .faq-answer { display: none; - background: #F6F9FB; - padding: 24px 20px 32px; - border-top: none; -} -@media (max-width: 768px) { - .faq-accordion .faq-answer { - background: #FFFFFF; - } + padding: 19px 21px 25px 21px; } @media (max-width: 1024px) { .faq-accordion .faq-answer { - padding: 16px 24px 24px; + padding: 16px; } } .faq-accordion .faq-answer .faq-answer-content { - font-size: 18px; - font-weight: 400; - color: #515151; - background-color: #ffffff; - padding: 24px; - line-height: 1.8; + font-size: 14px; + color: #000; + line-height: 1.6; } @media (max-width: 1024px) { .faq-accordion .faq-answer .faq-answer-content { - font-size: 14px; + font-size: 13px; } } .faq-accordion .faq-answer .faq-answer-content p { - margin-bottom: 16px; + margin-bottom: 8px; } .faq-accordion .faq-answer .faq-answer-content p:last-child { margin-bottom: 0; } -.faq-accordion .faq-answer .faq-answer-content ul, .faq-accordion .faq-answer .faq-answer-content ol { +.faq-accordion .faq-answer .faq-answer-content ul, +.faq-accordion .faq-answer .faq-answer-content ol { margin: 8px 0; - padding-left: 24px; + padding-left: 20px; } .faq-accordion .faq-answer .faq-answer-content li { margin-bottom: 4px; @@ -6414,11 +7069,85 @@ select.form-control { color: #c3dfea; } +.djb-toast-container { + position: fixed; + top: 24px; + left: 50%; + transform: translateX(-50%); + z-index: 12000; + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; + pointer-events: none; +} +@media (max-width: 768px) { + .djb-toast-container { + top: 12px; + left: 12px; + right: 12px; + transform: none; + align-items: stretch; + } +} + +.djb-toast { + pointer-events: auto; + min-width: 260px; + max-width: 380px; + padding: 14px 18px; + border-radius: 10px; + background: #f4f7fc; + color: #1f2328; + font-size: 14px; + line-height: 1.5; + border: 1px solid #e3e8f0; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); + cursor: pointer; + opacity: 0; + transform: translateY(-12px); + transition: opacity 0.22s ease, transform 0.22s ease; +} +.djb-toast.is-show { + opacity: 1; + transform: translateY(0); +} +.djb-toast { + border-left: 4px solid #0049B4; +} +.djb-toast--info { + border-left-color: #0049B4; +} +.djb-toast--success { + border-left-color: #1f883d; +} +.djb-toast--warn { + border-left-color: #bf8700; +} +.djb-toast--error { + border-left-color: #cf222e; +} +@media (max-width: 768px) { + .djb-toast { + max-width: none; + } +} +@media (min-width: 769px) { + .djb-toast { + min-width: 338px; + max-width: 494px; + padding: 18px 24px; + font-size: 18px; + border-radius: 13px; + } +} + .pagination { display: flex; align-items: center; justify-content: center; gap: 13px; + margin-top: 50px; } .pagination .page-first, .pagination .page-prev, @@ -6462,7 +7191,7 @@ select.form-control { justify-content: center; min-width: 10px; height: 21px; - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 18px; font-weight: 400; color: #5f666c; @@ -6563,7 +7292,7 @@ select.form-control { } } .breadcrumb-item { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 16px; font-weight: 400; line-height: 1; @@ -6642,7 +7371,7 @@ select.form-control { } } .breadcrumb-nav .breadcrumb-list-item a { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 16px; font-weight: 400; line-height: 1; @@ -6712,7 +7441,6 @@ select.form-control { margin: 0; font-size: 14px; color: #E65100; - font-weight: 500; } .test-env-notice__description { margin: 4px 0 0 0; @@ -6727,6 +7455,588 @@ select.form-control { font-weight: bold; } +.inquiry-comment-count { + display: inline-block; + margin-left: 6px; + color: #DC2626; + font-weight: 600; + font-size: 14px; +} + +.inquiry-header-right { + display: flex; + align-items: center; + gap: 8px; + font-size: 14px; + color: #6B7280; +} +.inquiry-header-right .inquiry-detail-writer { + font-weight: 600; + color: #374151; +} +.inquiry-header-right .inquiry-detail-divider { + color: #D1D5DB; +} +.inquiry-header-right .inquiry-detail-date { + font-size: inherit; + color: inherit; +} + +.djb-comment-section { + margin: 40px 0 48px 0; +} + +.djb-comment-list-box { + background-color: #FFFFFF; + border-radius: 8px; + min-height: 120px; + margin-bottom: 28px; +} + +.djb-comment-list { + list-style: none; + padding: 0; + margin: 0; +} + +.djb-comment-empty { + padding: 28px 0; + text-align: center; + font-size: 14px; + color: #9CA3AF; +} + +.djb-comment-item { + padding: 40px 0; + border-bottom: 1px solid #DFDFDF; +} +.djb-comment-item:first-child { + padding-top: 20px; +} +.djb-comment-item:last-child { + border-bottom: none; + padding-bottom: 20px; +} +.djb-comment-item--admin .djb-comment-writer { + color: #1D4ED8; +} +.djb-comment-item .djb-comment-meta { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 10px; +} +.djb-comment-item .djb-comment-meta-left { + display: flex; + align-items: center; + gap: 8px; +} +.djb-comment-item .djb-comment-writer { + font-size: 14px; + font-weight: 500; + color: #000; + letter-spacing: -0.2px; +} +.djb-comment-item .djb-comment-admin-badge { + display: inline-flex; + align-items: center; + padding: 3px 8px; + background-color: #ecf1ff; + color: #2a69de; + border-radius: 10px; + font-size: 9px; + font-weight: 700; +} +.djb-comment-item .djb-comment-meta-right { + display: flex; + align-items: center; + gap: 12px; +} +.djb-comment-item .djb-comment-date { + color: #6B7280; + font-size: 14px; +} +.djb-comment-item .djb-comment-delete-btn { + background: #fbe7e9; + border: 1px solid #f1c2d6; + border-radius: 5px; + padding: 3px 6px; + font-size: 10px; + font-weight: 500; + color: #bb1026; + cursor: pointer; + transition: all 0.15s ease; + display: flex; + align-items: center; + justify-content: center; + height: 20px; +} +.djb-comment-item .djb-comment-delete-btn:hover { + background-color: #f1c2d6; +} +.djb-comment-item .djb-comment-body { + padding: 24px; + background-color: #f4f5f9; + border-radius: 10px; + font-size: 14px; + line-height: 20px; + color: #000; + white-space: pre-wrap; + word-break: break-word; +} + +.djb-comment-form-wrapper { + display: block; + width: 100%; + margin-top: 8px; +} + +.djb-comment-form { + display: flex; + flex-direction: column; + width: 100%; +} + +textarea.djb-comment-input { + display: block; + width: 100%; + box-sizing: border-box; + padding: 14px 18px; + border: 1px solid #dfdfdf; + border-radius: 10px; + background-color: #ffffff; + font-size: 14px; + line-height: 1.6; + color: #000; + resize: vertical; + min-height: 103px; + font-family: inherit; + outline: none; + transition: border-color 0.15s ease, box-shadow 0.15s ease; + background-color: #fcfcfc; +} +textarea.djb-comment-input::placeholder { + color: #7f8a95; +} +textarea.djb-comment-input:hover { + border-color: #2a69de; +} +textarea.djb-comment-input:focus { + border-color: #2a69de; + background-color: #FFFFFF; + box-shadow: 0 0 0 3px rgba(42, 105, 222, 0.1); +} + +.djb-comment-form-actions { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + margin-top: 12px; + gap: 12px; +} +.djb-comment-form-actions .djb-comment-private-toggle { + display: inline-flex; + align-items: center; + gap: 6px; + font-size: 14px; + color: #374151; + cursor: pointer; +} + +.djb-comment-char-counter { + font-size: 13px; + color: #94A3B8; +} + +button.djb-comment-submit { + display: flex; + align-items: center; + justify-content: center; + width: 149px; + height: 39px; + background-color: #ffffff; + color: #4685ef; + border: 1px solid #2a69de; + border-radius: 10px; + font-size: 14px; + font-weight: 700; + cursor: pointer; + transition: all 0.2s ease; + flex-shrink: 0; + align-self: flex-end; + margin-top: 12px; +} +@media (max-width: 768px) { + button.djb-comment-submit { + width: 100%; + } +} +button.djb-comment-submit:hover { + background-color: #f8faff; +} +button.djb-comment-submit:active { + transform: translateY(1px); +} +button.djb-comment-submit:disabled { + background-color: #f4f5f9; + border-color: #dfdfdf; + color: #7f8a95; + cursor: not-allowed; +} + +.djb-comment-readonly-msg { + margin: 0; + padding: 16px 20px; + background-color: #F3F4F6; + border-radius: 6px; + text-align: center; + color: #6B7280; + font-size: 13px; +} + +.board-header { + margin-bottom: 24px; +} +.board-header .board-title { + font-size: 24px; + font-weight: 700; + color: #000000; + margin-bottom: 8px; + line-height: 1.2; +} +.board-header .board-desc { + font-size: 14px; + color: #7f8a95; + margin: 0; + line-height: 1.5; +} + +.tfa-card { + position: relative; + width: 520px; + max-width: 100%; + padding: 0; + border: 1px solid #DCE2ED; + border-radius: 4px; + background: #fff; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); + color: #0D0E11; + letter-spacing: -0.02em; +} + +.tfa-close { + position: absolute; + top: 16px; + right: 16px; + width: 28px; + height: 28px; + padding: 0; + border: 0; + background: none; + color: #7F8A95; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + border-radius: 4px; + transition: background 0.12s ease, color 0.12s ease; +} +.tfa-close:hover { + background: #F4F5F9; + color: #0D0E11; +} + +.tfa-head { + padding: 24px 32px; + padding-right: 60px; + border-bottom: 1px solid #DCE2ED; + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; +} +.tfa-head .tfa-head-title { + font-size: 27px; + font-weight: 700; + line-height: 1.35; + color: #0D0E11; +} +.tfa-head .tfa-head-brand { + font-size: 14px; + font-weight: 500; + color: #7F8A95; +} + +.tfa-body { + padding: 28px 32px 32px; +} + +.tfa-step { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 12px; +} +.tfa-step .tfa-step-num { + display: inline-flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + border-radius: 100px; + background: #BDC7CF; + color: #fff; + font-size: 11px; + font-weight: 500; + flex: none; +} +.tfa-step .tfa-step-num.is-active { + background: #4685EF; +} +.tfa-step .tfa-step-text { + font-size: 15px; + font-weight: 500; + color: #7F8A95; +} +.tfa-step .tfa-step-text.is-active { + color: #0D0E11; +} + +.tfa-segment { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px; + margin-bottom: 28px; +} +.tfa-segment .tfa-seg-btn { + padding: 14px 16px; + text-align: left; + cursor: pointer; + border: 1px solid #DCE2ED; + border-radius: 4px; + background: #fff; + transition: border-color 0.12s ease, background 0.12s ease; +} +.tfa-segment .tfa-seg-btn .tfa-seg-label { + display: block; + font-size: 15px; + font-weight: 500; + color: #0D0E11; +} +.tfa-segment .tfa-seg-btn .tfa-seg-masked { + display: block; + font-size: 14px; + font-weight: 300; + color: #7F8A95; + margin-top: 2px; +} +.tfa-segment .tfa-seg-btn:hover { + border-color: #4685EF; +} +.tfa-segment .tfa-seg-btn.is-active { + border: 1.5px solid #4685EF; + background: #ECF0FA; +} +.tfa-segment .tfa-seg-btn.is-active .tfa-seg-label { + color: #2A69DE; +} + +.tfa-hint { + border: 1px solid #DCE2ED; + border-radius: 4px; + background: #F4F5F9; + padding: 16px; + font-size: 14px; + font-weight: 300; + line-height: 1.5; + color: #7F8A95; + margin-bottom: 20px; +} + +.tfa-cta { + width: 100%; + height: 56px; + border: 0; + border-radius: 4px; + background: #4685EF; + color: #fff; + cursor: pointer; + font-size: 16px; + font-weight: 500; + letter-spacing: -0.02em; + transition: background 0.12s ease; +} +.tfa-cta:hover { + background: #2A69DE; +} +.tfa-cta:disabled { + background: #F4F5F9; + color: #BDC7CF; + cursor: default; +} + +.tfa-code-row { + display: flex; + gap: 8px; + margin-bottom: 8px; +} +.tfa-code-row .tfa-code-input-wrap { + flex: 1; + display: flex; + align-items: stretch; + height: 56px; + border: 1px solid #BDC7CF; + border-radius: 4px; + background: #fff; + transition: border-color 0.12s ease; +} +.tfa-code-row .tfa-code-input-wrap:focus-within { + border-color: #4685EF; +} +.tfa-code-row .tfa-code-input { + flex: 1; + min-width: 0; + height: 100%; + padding: 0 14px; + border: 0; + outline: none; + background: transparent; + font-size: 16px; + font-weight: 500; + letter-spacing: 0.02em; + color: #0D0E11; +} +.tfa-code-row .tfa-code-input::placeholder { + color: #BDC7CF; + font-weight: 400; +} +.tfa-code-row .tfa-resend { + flex: none; + height: 56px; + padding: 0 18px; + border: 1px solid #7F8A95; + border-radius: 4px; + background: #fff; + color: #0D0E11; + cursor: pointer; + font-size: 15px; + font-weight: 500; + white-space: nowrap; + transition: background 0.12s ease; +} +.tfa-code-row .tfa-resend:hover { + background: #F4F5F9; +} +.tfa-code-row .tfa-resend:disabled { + opacity: 0.5; + cursor: default; +} + +.tfa-timer-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + border: 1px solid #DCE2ED; + border-radius: 4px; + background: #F4F5F9; + padding: 12px 14px; + margin-bottom: 12px; +} +.tfa-timer-row .tfa-timer-label { + font-size: 14px; + font-weight: 300; + color: #7F8A95; +} +.tfa-timer-row .tfa-timer-clock { + display: flex; + align-items: baseline; + gap: 6px; +} +.tfa-timer-row .tfa-timer { + font-size: 20px; + font-weight: 500; + letter-spacing: 0.02em; + color: #4685EF; + font-variant-numeric: tabular-nums; +} +.tfa-timer-row .tfa-timer.is-warning { + color: #F4253C; +} +.tfa-timer-row .tfa-timer.is-expired { + color: #7F8A95; +} +.tfa-timer-row .tfa-timer-total { + font-size: 13px; + font-weight: 300; + color: #7F8A95; +} + +.tfa-progress { + height: 3px; + background: #DCE2ED; + border-radius: 2px; + overflow: hidden; + margin-bottom: 12px; +} +.tfa-progress .tfa-progress-bar { + height: 100%; + width: 100%; + background: #4685EF; + transition: width 1s linear; +} +.tfa-progress .tfa-progress-bar.is-warning { + background: #F4253C; +} + +.tfa-message { + min-height: 21px; + margin: 0 0 20px; + font-size: 14px; + font-weight: 300; + line-height: 1.5; + color: #7F8A95; +} +.tfa-message.is-error { + color: #F4253C; +} + +.tfa-test-notice { + margin: 0 0 16px; + padding: 10px 14px; + border: 1px dashed #F59E0B; + border-radius: 4px; + background: #FFFBEB; + color: #B45309; + font-size: 13px; + text-align: center; +} + +.tfa-foot { + margin-top: 24px; + padding-top: 16px; + border-top: 1px solid #DFDFDF; + font-size: 14px; + font-weight: 300; + line-height: 1.5; + color: #7F8A95; +} + +@media (max-width: 768px) { + .tfa-head { + padding: 20px 22px; + } + .tfa-head .tfa-head-title { + font-size: 23px; + } + .tfa-body { + padding: 22px 22px 26px; + } + .tfa-segment { + margin-bottom: 22px; + } +} .hero-carousel-section { position: relative; width: 100%; @@ -6736,18 +8046,20 @@ select.form-control { .hero-carousel-container { position: relative; width: 100%; - height: 580px; + height: 431px; overflow: hidden; background-color: #E9F9FF; + margin-top: 72px; } @media (max-width: 1024px) { .hero-carousel-container { - height: 800px; + height: 720px; } } @media (max-width: 768px) { .hero-carousel-container { - height: 480px; + height: 432px; + margin-top: 54px; } } @@ -6768,7 +8080,7 @@ select.form-control { top: 0; left: 0; width: 100%; - height: 440px; + height: 396px; display: flex; align-items: center; justify-content: center; @@ -6799,27 +8111,30 @@ select.form-control { justify-content: space-between; position: relative; margin: 0 auto; + margin-top: 45px; } @media (min-width: 1280px) { .hero-slide-content { - padding: 0 26px; + padding: 0 20px; } } @media (max-width: 1024px) { .hero-slide-content { flex-direction: column; - padding: 80px 40px; + padding: 72px 36px; justify-content: center; - gap: 40px; + gap: 36px; + margin-top: 296px; } } @media (max-width: 768px) { .hero-slide-content { - flex-direction: column-reverse; - padding: 60px 20px 80px; - gap: 70px; - justify-content: flex-end; + flex-direction: column; + padding: 30px 20px 20px; + gap: 20px; + justify-content: flex-start; align-items: center; + margin-top: 80px; } } @@ -6827,9 +8142,9 @@ select.form-control { flex: 0 0 auto; display: flex; flex-direction: column; - gap: 46px; + gap: 41px; z-index: 2; - width: 593px; + width: 534px; } @media (max-width: 1024px) { .hero-text-content { @@ -6839,54 +8154,53 @@ select.form-control { } @media (max-width: 768px) { .hero-text-content { - gap: 24px; - align-items: flex-start; - text-align: left; + gap: 16px; + align-items: center; + text-align: center; width: 100%; - padding: 0 48px; + padding: 0 10px; } } .hero-text .hero-subtitle { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - font-size: 32px; - font-weight: 400; - line-height: 99.94%; - color: #212529; - margin: 0 0 12px 0; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-size: 18px; + font-weight: 500; + color: var(--secondary-color); + letter-spacing: 2px; + text-transform: uppercase; + margin-bottom: 11px; } @media (max-width: 1024px) { .hero-text .hero-subtitle { - font-size: 28px; + font-size: 25px; } } @media (max-width: 768px) { .hero-text .hero-subtitle { - font-size: 16px; + font-size: 14px; font-weight: 500; - line-height: 34px; - letter-spacing: -0.32px; - margin: 0; + line-height: 1.4; + margin-bottom: 0; } } .hero-text .hero-title { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - font-size: 46px; + font-size: 38px; font-weight: 700; - line-height: 99.94%; - color: #000000; - margin: 0; + line-height: 1.25; + color: var(--text-dark); + letter-spacing: -1.5px; } @media (max-width: 1024px) { .hero-text .hero-title { - font-size: 40px; + font-size: 36px; } } @media (max-width: 768px) { .hero-text .hero-title { font-size: 24px; font-weight: 700; - line-height: 34px; + line-height: 1.3; letter-spacing: -0.48px; } } @@ -6895,45 +8209,45 @@ select.form-control { display: inline-flex; align-items: center; justify-content: center; - width: 178px; - height: 60px; - padding: 10px; - background: #0049B4; + gap: 7px; + width: 162px; + height: 50px; + background-color: var(--primary-color); color: #FFFFFF; - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - font-size: 20px; + font-size: 14px; font-weight: 700; - border-radius: 10px; - text-decoration: none; - transition: all 0.3s ease; + border-radius: 11px; + box-shadow: 0 7px 22px rgba(0, 73, 180, 0.2); + transition: var(--transition-smooth); } .btn-hero-signup:hover { - background: rgb(0, 65.7, 162); - transform: translateY(-2px); - box-shadow: 0 4px 12px rgba(0, 73, 180, 0.3); + background-color: var(--secondary-color); + transform: translateY(-3px); + box-shadow: 0 11px 27px rgba(0, 73, 180, 0.35); + color: white; } .btn-hero-signup:active { transform: translateY(0); } @media (max-width: 1024px) { .btn-hero-signup { - width: 200px; + width: 180px; } } @media (max-width: 768px) { .btn-hero-signup { - width: 144px; - height: 40px; - padding: 8px 10px; - font-size: 14px; - border-radius: 8px; + width: 130px; + height: 36px; + padding: 7px 9px; + font-size: 13px; + border-radius: 7px; } } .hero-image-content { flex: 0 0 auto; - width: 556px; - height: 455px; + width: 500px; + height: 410px; display: flex; align-items: center; justify-content: center; @@ -6941,20 +8255,20 @@ select.form-control { } @media (min-width: 1280px) { .hero-image-content { - width: 410px; - height: 300px; + width: 369px; + height: 270px; } } @media (max-width: 1024px) { .hero-image-content { - width: 410px; - height: 300px; + width: 369px; + height: 270px; } } @media (max-width: 768px) { .hero-image-content { - width: 218px; - height: 180px; + width: 196px; + height: 162px; max-width: none; flex-shrink: 0; } @@ -6969,11 +8283,11 @@ select.form-control { position: absolute; top: 50%; transform: translateY(-50%); - width: 60px; - height: 60px; + width: 54px; + height: 54px; background: rgba(255, 255, 255, 0.5); border: none; - border-radius: 50px; + border-radius: 45px; cursor: pointer; display: none; align-items: center; @@ -6989,8 +8303,8 @@ select.form-control { transform: translateY(-50%) scale(0.95); } .hero-nav-btn svg { - width: 22px; - height: 22px; + width: 20px; + height: 20px; } @media (min-width: 1280px) { .hero-nav-btn { @@ -6999,95 +8313,98 @@ select.form-control { } .hero-nav-prev { - left: 210px; + left: 189px; } .hero-nav-prev svg { transform: rotate(180deg); } @media (max-width: 768px) { .hero-nav-prev { - left: 20px; + left: 18px; } } @media (max-width: 1024px) { .hero-nav-prev { - left: 40px; + left: 36px; } } @media (min-width: 1280px) { .hero-nav-prev { - left: calc(50vw - 640px - 80px); + left: calc(50vw - 576px - 140px); } } .hero-nav-next { - right: 210px; + right: 189px; } @media (max-width: 768px) { .hero-nav-next { - right: 20px; + right: 18px; } } @media (max-width: 1024px) { .hero-nav-next { - right: 40px; + right: 36px; } } @media (min-width: 1280px) { .hero-nav-next { - right: calc(50vw - 640px - 80px); + right: calc(50vw - 576px - 140px); } } .hero-carousel-indicators { position: absolute; - bottom: 160px; - left: calc(50vw - 640px + 72px); + bottom: 5px; + left: 50%; transform: translateX(-50%); display: flex; - gap: 12px; + align-items: center; + gap: 11px; z-index: 10; } @media (max-width: 768px) { .hero-carousel-indicators { - top: 60px; - right: 28px; + top: 54px; + right: 25px; bottom: auto; left: auto; transform: none; flex-direction: row; - gap: 6px; + gap: 5px; align-items: center; } } .hero-indicator { - width: 14px; - height: 14px; + width: 9px; + height: 9px; border-radius: 50%; - background: #F1F1F1; - border: none; + background: transparent; + border: 2px solid var(--text-light); cursor: pointer; padding: 0; - transition: all 0.3s ease; + transition: var(--transition-smooth); } .hero-indicator.active { - background: #0049B4; + background-color: var(--primary-color); + border-color: var(--primary-color); + transform: scale(1.2); } .hero-indicator:hover:not(.active) { - background: rgba(0, 0, 0, 0.5); + background: #ffffff; } @media (max-width: 768px) { .hero-indicator { - width: 6px; - height: 6px; + width: 5px; + height: 5px; border-radius: 50%; background: transparent; border: 1px solid #8C959F; } .hero-indicator.active { - width: 6px; - height: 6px; + width: 5px; + height: 5px; border-radius: 50%; background: #0049B4; border: none; @@ -7098,7 +8415,7 @@ select.form-control { display: flex; align-items: center; justify-content: center; - margin-left: 8px; + margin-left: 7px; cursor: pointer; padding: 0; transition: all 0.3s ease; @@ -7115,7 +8432,7 @@ select.form-control { .hero-autoplay-toggle:hover { background: rgb(255, 255, 255); border-color: #0049B4; - box-shadow: 0 2px 8px rgba(0, 73, 180, 0.2); + box-shadow: 0 2px 7px rgba(0, 73, 180, 0.2); } @media (max-width: 768px) { .hero-autoplay-toggle { @@ -7123,12 +8440,39 @@ select.form-control { } } +/* 피그마 시안 적용 카드 레이아웃 스타일 */ +.search-container { + max-width: 1280px; + margin: 0 auto; + padding: 0 20px; + z-index: 9; + position: relative; + margin-top: 0px; +} +@media (max-width: 768px) { + .search-container { + margin-top: 0px; + padding: 0 16px; + } +} + .api-search-section { position: relative; padding: 0; - overflow: hidden; + padding-top: 63px; + padding-bottom: 40px; + overflow: visible; background: #e9f9ff; - height: 314px; +} +@media (max-width: 768px) { + .api-search-section { + padding-top: 30px; + } +} +@media (max-width: 1024px) { + .api-search-section { + padding-bottom: 30px; + } } .api-search-section .search-background { position: absolute; @@ -7139,348 +8483,274 @@ select.form-control { background-repeat: no-repeat; pointer-events: none; } -.api-search-section .search-content-wrapper { - position: relative; + +.search-card-container { display: flex; align-items: center; - justify-content: center; - gap: 40px; - z-index: 1; + justify-content: space-between; + background: linear-gradient(153deg, rgba(255, 255, 255, 0.53) 0%, rgba(255, 255, 255, 0.53) 50%, rgba(255, 255, 255, 0.53) 100%); + backdrop-filter: blur(7px); + border: 3px solid #FFFFFF; + border-radius: 27px; + height: 212px; + padding: 0 54px; + box-shadow: 0 11px 32px rgba(82, 127, 255, 0.12), inset 0 1px 1px rgba(255, 255, 255, 0.95), inset 0 -2px 7px rgba(255, 255, 255, 0.15), inset 0 0 36px rgba(255, 255, 255, 0.28); + gap: 36px; } @media (max-width: 1024px) { - .api-search-section .search-content-wrapper { + .search-card-container { + height: auto; + padding: 36px 27px; flex-direction: column; - gap: 24px; - padding-top: 40px; + gap: 22px; } } @media (max-width: 768px) { - .api-search-section .search-content-wrapper { - flex-direction: row; + .search-card-container { + padding: 24px 18px; + gap: 18px; + border-radius: 18px; + } +} + +.search-character-box { + width: 80px; +} +@media (max-width: 768px) { + .search-character-box { + width: 60px; + margin: 0 auto; + } +} + +.search-card-left { + flex: 0 0 35%; +} +@media (max-width: 1024px) { + .search-card-left { + text-align: center; + width: 100%; + } +} + +.search-card-title { + font-size: 32px; + font-weight: 700; + line-height: 1.35; + color: #000000; + letter-spacing: -1px; + margin-bottom: 0px; +} +@media (max-width: 768px) { + .search-card-title { + font-size: 22px; + } +} + +.search-card-right { + flex: 1; + display: flex; + flex-direction: column; + gap: 20px; +} +@media (max-width: 1024px) { + .search-card-right { + width: 100%; + } +} + +.search-tags-row { + display: flex; + align-items: center; + gap: 13px; + font-size: 14px; +} +@media (max-width: 1024px) { + .search-tags-row { + justify-content: center; flex-wrap: wrap; + } +} +@media (max-width: 768px) { + .search-tags-row { gap: 8px; - padding-top: 20px; + font-size: 12px; + } +} + +.search-tag-label { + font-weight: 700; + color: #000000; + white-space: nowrap; +} + +.search-tag-divider { + color: rgba(0, 0, 0, 0.15); + font-size: 13px; + user-select: none; +} + +.search-tag-list { + display: flex; + align-items: center; + gap: 7px; + flex-wrap: wrap; +} +@media (max-width: 1024px) { + .search-tag-list { justify-content: center; } } -.api-search-section .search-character { - width: 154px; - height: 148px; - flex-shrink: 0; -} -.api-search-section .search-character img { - width: 100%; - height: 100%; - object-fit: contain; -} -@media (max-width: 1024px) { - .api-search-section .search-character { - width: 120px; - height: 115px; - } -} -@media (max-width: 768px) { - .api-search-section .search-character { - width: 68px; - height: 65px; - } -} -.api-search-section .search-text-content { - text-align: left; -} -@media (max-width: 1024px) { - .api-search-section .search-text-content { - text-align: center; - } -} -@media (max-width: 768px) { - .api-search-section .search-text-content { - text-align: left; - } -} -.api-search-section .search-title { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - font-size: 36px; + +.search-tag-link { + font-size: 12px; font-weight: 700; - line-height: 1.3; - color: #000000; - margin: 0; + color: #FFFFFF; + background-color: #4546CE; + padding: 5px 13px; + border-radius: 18px; + transition: var(--transition-smooth); + display: inline-block; + text-decoration: none; } -@media (max-width: 1024px) { - .api-search-section .search-title { - font-size: 28px; - } +.search-tag-link:hover { + background-color: #1d1e75; + transform: translateY(-1px); + color: white; } -@media (max-width: 768px) { - .api-search-section .search-title { - font-size: 16px; - line-height: 1.4; - color: #212529; - } -} -.api-search-section .search-input-wrapper { - position: relative; - display: flex; - justify-content: center; - padding: 0px 0 30px; - z-index: 1; -} -@media (max-width: 1024px) { - .api-search-section .search-input-wrapper { - padding: 30px 20px 20px; - } -} -@media (max-width: 768px) { - .api-search-section .search-input-wrapper { - justify-content: flex-start; - padding: 0px 28px 12px 27px; - } -} -.api-search-section .search-form { + +.search-bar-row { width: 100%; - max-width: 816px; } -.api-search-section .search-box { + +.search-card-form { + width: 100%; +} + +.search-card-box { position: relative; width: 100%; - height: 80px; + height: 72px; background: #FFFFFF; - border: 6px solid #0049B4; - border-radius: 50px; + border: 5px solid #0049B4; + border-radius: 45px; display: flex; align-items: center; - padding: 0 24px; + padding: 0 29px; + transition: var(--transition-smooth); } -@media (max-width: 1024px) { - .api-search-section .search-box { - height: 60px; - border: 4px solid #0049B4; - padding: 0 16px; - } +.search-card-box:focus-within { + box-shadow: 0 18px 41px rgba(0, 73, 180, 0.18); + transform: translateY(-2px); + border-color: var(--secondary-color); } @media (max-width: 768px) { - .api-search-section .search-box { - height: 36px; - border: 3px solid #0049B4; - border-radius: 18px; - padding: 0 12px; + .search-card-box { + height: 52px; + border-width: 3px; + padding: 0 15px; } } -.api-search-section .search-input { + +.search-card-input { flex: 1; width: 100%; height: 100%; border: none; outline: none; background: transparent; - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - font-size: 17px; - font-weight: 500; - color: #000000; - padding: 0 20px; + font-size: 15px; + font-weight: 700; + color: var(--text-dark); + text-align: center; } -.api-search-section .search-input::placeholder { +.search-card-input::placeholder { color: #B3B3B3; - font-weight: 400; -} -@media (max-width: 1024px) { - .api-search-section .search-input { - font-size: 15px; - padding: 0 12px; - } + font-weight: 500; } @media (max-width: 768px) { - .api-search-section .search-input { - font-size: 11px; - font-weight: 500; - padding: 0 8px; - } - .api-search-section .search-input::placeholder { - color: #8C959F; - font-weight: 500; + .search-card-input { + font-size: 13px; } } -.api-search-section .search-icon-button { + +.search-card-btn { background: none; border: none; cursor: pointer; - padding: 0; display: flex; align-items: center; justify-content: center; - width: 40px; - height: 40px; - flex-shrink: 0; - transition: transform 0.3s ease; + position: absolute; + right: 29px; + width: 30px; + height: 31px; + padding: 0; + transition: var(--transition-smooth); } -.api-search-section .search-icon-button:hover { +.search-card-btn:hover { transform: scale(1.1); } -.api-search-section .search-icon-button:active { - transform: scale(0.95); +.search-card-btn:hover svg path { + fill: #008AE2; } -.api-search-section .search-icon-button svg { - width: 33px; - height: 34px; -} -@media (max-width: 1024px) { - .api-search-section .search-icon-button svg { - width: 28px; - height: 29px; - } -} -@media (max-width: 768px) { - .api-search-section .search-icon-button svg { - width: 16px; - height: 16px; - } -} -@media (max-width: 768px) { - .api-search-section .search-icon-button { - width: 20px; - height: 20px; - } -} -.api-search-section .hashtag-section { - position: relative; - display: flex; - align-items: center; - justify-content: center; - gap: 12px; - padding: 0 20px 0px; - z-index: 1; -} -@media (max-width: 1024px) { - .api-search-section .hashtag-section { - flex-direction: column; - align-items: center; - justify-content: center; - gap: 8px; - padding: 0 20px 30px; - } -} -@media (max-width: 768px) { - .api-search-section .hashtag-section { - flex-direction: row; - flex-wrap: wrap; - gap: 12px; - padding: 0 20px 20px; - justify-content: center; - } -} -.api-search-section .hashtag-label { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - font-size: 16px; - font-weight: 700; - color: #000000; - white-space: nowrap; -} -@media (max-width: 768px) { - .api-search-section .hashtag-label { - font-size: 12px; - color: #212529; - } -} -.api-search-section .hashtag-list { - display: flex; - align-items: center; - gap: 14px; - flex-wrap: wrap; -} -@media (max-width: 1024px) { - .api-search-section .hashtag-list { - justify-content: center; - gap: 10px; - } -} -@media (max-width: 768px) { - .api-search-section .hashtag-list { - gap: 8px; - justify-content: left; - } -} -.api-search-section .hashtag-link { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - font-size: 16px; - font-weight: 400; - color: #000000; - text-decoration: none; - white-space: nowrap; - transition: color 0.3s ease; -} -.api-search-section .hashtag-link:hover { - color: #0049B4; - text-decoration: underline; -} -@media (max-width: 1024px) { - .api-search-section .hashtag-link { - font-size: 14px; - } -} -@media (max-width: 768px) { - .api-search-section .hashtag-link { - font-size: 12px; - color: #212529; - } -} -.api-search-section .hashtag-separator { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - font-size: 16px; - color: #000000; - user-select: none; -} -@media (max-width: 1024px) { - .api-search-section .hashtag-separator { - font-size: 14px; - } -} -@media (max-width: 768px) { - .api-search-section .hashtag-separator { - font-size: 10px; - color: #212529; - display: flex; - align-items: center; - height: 10px; - } +.search-card-btn svg path { + transition: fill 0.3s ease; } .api-showcase { position: relative; display: flex; align-items: center; - padding: 75px 0 100px; + padding: 68px 0 90px; background: #FFFFFF; overflow: hidden; - height: 870px; + height: 720px; +} +@media (max-width: 1024px) { + .api-showcase { + height: auto; + padding: 50px 0 60px; + } } @media (max-width: 768px) { .api-showcase { height: auto; - padding: 40px 0 60px; + padding: 36px 0 54px; } } .api-showcase .showcase-background { position: absolute; inset: 0; - background-image: url("/img/bg_main_recommend_apis.png"); + /*background-image: url("/img/bg_main_recommend_apis.png");*/ background-size: cover; background-position: center; background-repeat: no-repeat; pointer-events: none; + background: linear-gradient(182deg, #d2efff, transparent 50%); + /* background: #cff1ff; */ } .api-showcase .showcase-wrapper { - max-width: 1228px; + max-width: 1280px; margin: 0 auto; + padding: 0 20px; position: relative; width: 100%; z-index: 1; } +@media (max-width: 768px) { + .api-showcase .showcase-wrapper { + padding: 0 20px; + } +} +.api-showcase .header-title-box .section-header { + text-align: left; +} .api-showcase .section-header { - text-align: center; - margin-bottom: 24px; - position: relative; display: flex; - flex-direction: column; + align-items: flex-end; + justify-content: space-between; + margin-bottom: 43px; } @media (max-width: 1024px) { .api-showcase .section-header { @@ -7493,41 +8763,38 @@ select.form-control { align-items: center; justify-content: center; padding-right: 0; - margin-bottom: 18px; + margin-bottom: 16px; position: relative; } } .api-showcase .section-title { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - font-size: 44px; + font-size: 34px; font-weight: 700; - line-height: 1.3; - color: #000000; - margin: 0; + color: var(--text-dark); + margin-bottom: 11px; + letter-spacing: -0.5px; + text-align: left; } @media (max-width: 1024px) { .api-showcase .section-title { - font-size: 36px; + font-size: 32px; } } @media (max-width: 768px) { .api-showcase .section-title { - font-size: 20px; + font-size: 24px; color: #212529; letter-spacing: -0.4px; } } .api-showcase .section-subtitle { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - font-size: 26px; - font-weight: 400; - line-height: 1.5; - color: #1E1E1E; - margin-bottom: 30px; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-size: 16px; + color: var(--text-gray); } @media (max-width: 1024px) { .api-showcase .section-subtitle { - font-size: 20px; + font-size: 18px; } } @media (max-width: 768px) { @@ -7536,29 +8803,24 @@ select.form-control { } } .api-showcase .btn-all-apis { - align-self: flex-end; display: inline-flex; align-items: center; justify-content: center; - gap: 21px; - padding: 12px 12px; - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - font-size: 15px; - font-weight: 500; - color: #FFFFFF; - background: #008ae2; - text-decoration: none; - border-radius: 10px; - transition: all 0.3s ease; + gap: 7px; + width: 108px; height: 40px; - width: 135px; - white-space: nowrap; + background: var(--secondary-color); + color: #FFFFFF; + font-size: 13px; + font-weight: 700; + border-radius: 7px; + box-shadow: 0 4px 11px rgba(0, 138, 226, 0.15); + transition: var(--transition-smooth); } @media (max-width: 1024px) { .api-showcase .btn-all-apis { position: static; transform: none; - margin: 24px auto 0; } } @media (max-width: 768px) { @@ -7568,18 +8830,19 @@ select.form-control { right: 0; transform: translateY(-50%); margin: 0; - width: 72px; - height: 24px; - padding: 10px; - font-size: 10px; + width: auto; + height: 28px; + padding: 0 12px; + font-size: 11px; font-weight: 700; border-radius: 4px; - gap: 10px; + gap: 4px; + white-space: nowrap; } } .api-showcase .btn-all-apis svg { - width: 11px; - height: 17px; + width: 10px; + height: 15px; transition: transform 0.3s ease; } .api-showcase .btn-all-apis svg path { @@ -7588,109 +8851,127 @@ select.form-control { @media (max-width: 768px) { .api-showcase .btn-all-apis svg { width: 5px; - height: 8px; + height: 7px; } } .api-showcase .btn-all-apis:hover { - background: rgb(0, 124.2, 203.4); + background: var(--primary-color); + transform: translateY(-2px); + box-shadow: 0 5px 16px rgba(0, 73, 180, 0.25); } .api-showcase .btn-all-apis:hover svg { transform: translateX(4px); } .api-showcase .api-cards-container { display: flex; - gap: 32px; - justify-content: center; + flex-wrap: wrap; + gap: 29px; + justify-content: flex-start; } @media (max-width: 1024px) { .api-showcase .api-cards-container { flex-wrap: wrap; - gap: 20px; + gap: 18px; justify-content: center; } } @media (max-width: 768px) { .api-showcase .api-cards-container { - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 12px 17px; + display: flex; + flex-direction: column; + gap: 15px; } } .api-showcase .api-card { - width: 283px; - height: 320px; + flex: 0 0 calc((100% - 87px) / 4); + max-width: calc((100% - 87px) / 4); + box-sizing: border-box; + height: 288px; background: #FFFFFF; - border: 1px solid #DDDDDD; - border-radius: 20px; - padding: 36px 38px; + border: 1px solid var(--border-color); + border-radius: 22px; + padding: 29px; display: flex; flex-direction: column; - align-items: flex-start; - position: relative; - transition: all 0.3s ease; + justify-content: space-between; + cursor: pointer; + transition: var(--transition-smooth); gap: 0px; cursor: pointer; } .api-showcase .api-card:hover { - transform: translateY(-4px); - box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1); - border-color: #008ae2; + background: linear-gradient(135deg, #50caff 0%, #2088ff 100%); + transform: translateY(-9px); + box-shadow: var(--shadow-hover); + color: white; +} +.api-showcase .api-card:hover .card-title, +.api-showcase .api-card:hover .card-description { + color: white; } @media (max-width: 1024px) { .api-showcase .api-card { - width: calc(50% - 10px); + flex: 0 0 calc(50% - 9px); + max-width: calc(50% - 9px); + width: calc(50% - 9px); min-width: 250px; } } @media (max-width: 768px) { .api-showcase .api-card { + flex: 1 1 auto; + max-width: 100%; width: 100%; height: auto; min-width: unset; - min-height: 200px; - padding: 20px 19px; - border-radius: 12px; + min-height: 180px; + padding: 18px 17px; + border-radius: 11px; border: 1px solid #DADADA; box-shadow: 2px 2px 5px 0px rgba(185, 204, 222, 0.3); - gap: 20px; + gap: 18px; } } .api-showcase .card-title { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - font-size: 24px; + font-size: 20px; font-weight: 700; - line-height: 1.67; - color: #2A2A2A; - margin: 0 0 20px 0; + color: var(--text-dark); + margin-bottom: 11px; + text-align: left; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; } @media (max-width: 768px) { .api-showcase .card-title { - font-size: 15px; + font-size: 17px; line-height: 1; color: #000000; - margin: 0 0 12px 0; + margin-bottom: 0; } } .api-showcase .card-description { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - font-size: 15px; - font-weight: 400; - line-height: 1.43; - color: #515151; - margin: 0; - flex: 1; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-size: 16px; + color: var(--text-gray); + line-height: 1.5; text-align: left; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; + line-height: 1.5; + max-height: 3em; } @media (max-width: 768px) { .api-showcase .card-description { - font-size: 12px; + font-size: 14px; font-weight: 500; line-height: 1; } } .api-showcase .card-illustration { - width: 90px; - height: 90px; + width: 81px; + height: 81px; display: flex; align-items: center; justify-content: center; @@ -7704,32 +8985,28 @@ select.form-control { } @media (max-width: 768px) { .api-showcase .card-illustration { - width: 48px; - height: 48px; + width: 70px; + height: 70px; } } .info-section { position: relative; - padding: 100px 0; - background: #FFFFFF; + padding: 108px 0; + background-color: #0E1B3E; + color: #FFFFFF; overflow: hidden; - height: 740px; - display: flex; - align-items: center; } @media (max-width: 768px) { .info-section { - min-height: 392px; - padding: 75px 20px 20px; + min-height: 353px; + padding: 68px 18px 18px; } } .info-section .info-background { position: absolute; inset: 0; - background-image: url("/img/bg_api_intro.svg"); - background-size: cover; - background-position: center; + background-image: radial-gradient(circle at 80% 20%, rgba(0, 73, 180, 0.25) 0%, rgba(14, 27, 62, 0) 60%); pointer-events: none; } .info-section .container { @@ -7748,20 +9025,22 @@ select.form-control { display: flex; align-items: center; justify-content: space-between; - gap: 80px; + gap: 54px; } @media (max-width: 1024px) { .info-section .info-wrapper { flex-direction: column; - gap: 64px; + gap: 36px; + text-align: center; } } @media (max-width: 768px) { .info-section .info-wrapper { - display: block; + display: flex; flex-direction: column; - gap: 20px; + gap: 18px; position: relative; + text-align: center; } } .info-section .info-content { @@ -7770,75 +9049,81 @@ select.form-control { } @media (max-width: 1024px) { .info-section .info-content { - text-align: center; - max-width: 100%; + display: contents; } } @media (max-width: 768px) { .info-section .info-content { - text-align: left; - max-width: 100%; - order: 1; + display: contents; } } .info-section .info-title { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - font-size: 28px; - font-weight: 400; - line-height: 1.4; - color: #000000; - margin: 0 0 24px 0; + display: flex; + flex-direction: column; + gap: 7px; + margin-bottom: 22px; } @media (max-width: 1024px) { .info-section .info-title { + font-size: 32px; + order: 1; + margin-bottom: 0; + } +} +@media (max-width: 768px) { + .info-section .info-title { + order: 1; + margin-bottom: 0; + } +} +.info-section .title-sub { + font-size: 16px; + font-weight: 500; + color: var(--secondary-color); + letter-spacing: 1px; +} +@media (max-width: 768px) { + .info-section .title-sub { + font-size: 14px; + } +} +.info-section .title-highlight { + font-size: 41px; + font-weight: 700; + line-height: 1.25; + background: linear-gradient(90deg, #FFFFFF 0%, #81d5ff 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; +} +@media (max-width: 1024px) { + .info-section .title-highlight { font-size: 36px; } } @media (max-width: 768px) { - .info-section .info-title { - font-size: 16px; - font-weight: 500; - line-height: 1; - color: #212529; - margin: 0 0 8px 0; - } - .info-section .info-title span { - margin-bottom: 10px; - } -} -.info-section .info-title .title-highlight { - font-weight: 700; - color: #0049B4; - font-size: 36px; -} -@media (max-width: 768px) { - .info-section .info-title .title-highlight { - font-size: 20px; - display: block; - letter-spacing: -0.4px; - margin-top: 8px; + .info-section .title-highlight { + font-size: 26px; } } .info-section .info-description { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - font-size: 20px; - font-weight: 400; - line-height: 1.6; - color: #000000; - margin: 0 0 48px 0; + font-size: 16px; + line-height: 1.7; + color: rgba(255, 255, 255, 0.75); + margin-bottom: 36px; } @media (max-width: 1024px) { .info-section .info-description { - font-size: 16px; + order: 3; + margin-bottom: 24px; } } @media (max-width: 768px) { .info-section .info-description { - font-size: 14px; - font-weight: 400; - line-height: 22px; - color: #515961; - margin: 24px 0 20px 0; + order: 3; + font-size: 15px; + line-height: 1.6; + margin-bottom: 24px; + word-break: keep-all; } } .info-section .action-buttons { @@ -7847,736 +9132,665 @@ select.form-control { } @media (max-width: 1024px) { .info-section .action-buttons { + order: 4; justify-content: center; } } @media (max-width: 768px) { .info-section .action-buttons { - flex-direction: row; - align-items: stretch; - gap: 15px; + order: 4; + flex-direction: column; + gap: 12px; width: 100%; } } +.info-section .action-buttons .btn-secondary { + background-color: rgba(255, 255, 255, 0.08); + color: rgb(255, 255, 255); + border-width: 1px; + border-style: solid; + border-color: rgba(255, 255, 255, 0.2); + border-image: initial; +} +.info-section .action-buttons .btn-primary { + background-color: var(--secondary-color); + color: rgb(255, 255, 255); + box-shadow: rgba(0, 138, 226, 0.3) 0px 4px 14px; +} +.info-section .action-buttons .btn-secondary:hover { + background-color: rgba(255, 255, 255, 0.15); + border-color: #FFFFFF; + transform: translateY(-2px); +} +.info-section .action-buttons .btn-primary:hover { + background-color: #00A3FF; + transform: translateY(-2px); + box-shadow: 0 7px 23px rgba(0, 138, 226, 0.45); +} .info-section .action-btn { display: inline-flex; align-items: center; justify-content: center; - padding: 18px 52px; - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - font-size: 24px; + gap: 7px; + height: 50px; + padding: 0 25px; + font-size: 14px; font-weight: 700; - line-height: 56px; - border-radius: 20px; - text-decoration: none; - white-space: nowrap; - transition: all 0.3s ease; - min-width: 310px; - height: 128px; + border-radius: 11px; + transition: var(--transition-smooth); } @media (max-width: 768px) { .info-section .action-btn { font-size: 14px; font-weight: 700; - line-height: 1.4; - padding: 8px 10px; + padding: 0 15px; min-width: unset; - width: calc(50% - 7.5px); - height: 72px; - border-radius: 8px; - white-space: normal; - word-break: keep-all; - text-align: center !important; + width: 100%; + height: 52px; + border-radius: 9px; + white-space: nowrap; display: flex !important; align-items: center !important; justify-content: center !important; - flex-direction: column; + flex-direction: row; } } .info-section .action-btn:hover { transform: translateY(-3px); - box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); + box-shadow: 0 7px 22px rgba(0, 0, 0, 0.15); } -.info-section .action-btn:active { - transform: translateY(0); +.info-section .btn-primary { + background-color: #FFFFFF; + color: #0E1B3E; } -.info-section .action-btn-primary { - background: #0049B4; +.info-section .btn-primary:hover { + background-color: rgba(255, 255, 255, 0.9); +} +.info-section .btn-secondary { + background-color: rgba(255, 255, 255, 0.08); color: #FFFFFF; + border: 1px solid rgba(255, 255, 255, 0.2); } -.info-section .action-btn-primary:hover { - background: rgb(0, 65.7, 162); +.info-section .btn-secondary:hover { + background-color: rgba(255, 255, 255, 0.15); } -.info-section .action-btn-secondary { - background: #00ACDD; - color: #FFFFFF; -} -.info-section .action-btn-secondary:hover { - background: rgb(0, 154.8, 198.9); -} -.info-section .info-image { - flex: 1; - max-width: 560px; +.info-section .info-image-box { + width: 45%; display: flex; - align-items: center; justify-content: center; } -.info-section .info-image img { +.info-section .info-image-box .info-vector-art { + width: 100%; + max-width: 400px; +} +.info-section .info-image-box img { width: 100%; height: auto; object-fit: contain; } @media (max-width: 1024px) { - .info-section .info-image { + .info-section .info-image-box { + order: 2; max-width: 100%; + width: 100%; + margin: 10px 0; } } @media (max-width: 768px) { - .info-section .info-image { - position: absolute; - top: -90px; + .info-section .info-image-box { + order: 2; + position: relative; + top: 0; right: 0; - width: 128px; - height: 128px; - max-width: 128px; + width: 100%; + height: auto; + max-width: 100%; + margin: 10px 0; flex: none; } - .info-section .info-image img { - width: 128px; - height: 128px; + .info-section .info-image-box .info-vector-art { + width: 100%; + max-width: 200px; + height: auto; + } + .info-section .info-image-box img { + width: 100%; + max-width: 200px; + height: auto; } } +/* 피그마 시안 적용 Support Center 그리드 레이아웃 스타일 */ .support-center { - position: relative; - padding: 0; - padding-top: 102px; - padding-bottom: 126px; - background: #eef7fd; - overflow: hidden; - height: 720px; + padding: 90px 0; + background-color: #F8FAFC; } @media (max-width: 768px) { .support-center { - min-height: 392px; - padding: 60px 20px; + padding: 54px 18px; } } -.support-center .support-background { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 100%; - height: 100%; - max-width: 1920px; - pointer-events: none; -} -.support-center .support-background::before { - content: ""; - position: absolute; - top: 17px; - left: 46%; - width: 693px; - height: 597px; - background-image: url("/img/bg_support_center.png"); - background-size: cover; - background-repeat: no-repeat; - opacity: 0.5; -} -@media (max-width: 768px) { - .support-center .support-background::before { - top: 0; - left: auto; - right: 0; - width: 187px; - height: 177px; - opacity: 1; - } -} -.support-center .container { - position: relative; - z-index: 1; - max-width: 1280px; - margin: 0 auto; - padding: 0 26px; - height: 492px; -} -@media (max-width: 768px) { - .support-center .container { - padding: 0; - max-width: 100%; - width: 100%; - } -} -.support-center .section-header { - margin-bottom: 40px; +.support-center .support-header { text-align: center; + margin-bottom: 50px; } -@media (max-width: 768px) { - .support-center .section-header { - margin-bottom: 24px; - text-align: left; - } -} -.support-center .section-header .section-title { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - font-size: 28px; +.support-center .support-title .title-regular { + font-size: 22px; font-weight: 400; - line-height: 1.4; - color: #000000; - margin: 0 0 24px 0; - text-align: left; + color: var(--text-gray); } -@media (max-width: 1024px) { - .support-center .section-header .section-title { - font-size: 36px; - line-height: 1.4; - } -} -@media (max-width: 768px) { - .support-center .section-header .section-title { - font-size: 16px; - line-height: 1; - letter-spacing: -0.32px; - } -} -@media (max-width: 768px) { - .support-center .section-header .section-title .title-regular { - font-weight: 500; - } -} -.support-center .section-header .section-title .title-bold { - font-size: 38px; +.support-center .support-title .title-bold { + font-size: 25px; font-weight: 700; -} -@media (max-width: 768px) { - .support-center .section-header .section-title .title-bold { - font-size: 20px; - letter-spacing: -0.4px; - } + color: var(--text-dark); + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; } .support-center .support-grid { - display: flex; - gap: 32px; - justify-content: center; - align-items: stretch; - max-width: 1228px; - height: 320px; - margin: 0 auto; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); + gap: 22px; } -@media (max-width: 1024px) { - .support-center .support-grid { - flex-direction: column; - align-items: center; - gap: 20px; - } +.support-center { + /* Large card for Notice */ } -@media (max-width: 768px) { - .support-center .support-grid { - flex-direction: row; - justify-content: space-between; - align-items: stretch; - gap: 12px; - max-width: 100%; - } +.support-center .support-grid .support-card-notice { + grid-column: span 1; } .support-center .support-card { - background: #FFFFFF; - border-radius: 20px; - text-decoration: none; - transition: all 0.3s ease; + background-color: #FFFFFF; + border: 1px solid var(--border-color); + border-radius: 18px; + padding: 36px; display: flex; - box-sizing: border-box; + flex-direction: column; position: relative; overflow: hidden; - height: 320px; -} -@media (max-width: 1024px) { - .support-center .support-card { - width: 100%; - max-width: 598px; - height: auto; - min-height: 260px; - } -} -@media (max-width: 768px) { - .support-center .support-card { - width: calc(33.333% - 8px); - min-width: 0; - height: 168px; - min-height: 168px; - max-width: none; - border-radius: 12px; - padding: 26px 0 25px; - flex-direction: column; - align-items: center; - justify-content: flex-start; - gap: 16px; - } + transition: var(--transition-smooth); } .support-center .support-card:hover { transform: translateY(-5px); - box-shadow: 0 10px 30px rgba(0, 73, 180, 0.15); -} -@media (max-width: 768px) { - .support-center .support-card:hover { - transform: translateY(-2px); - } + box-shadow: var(--shadow-premium); } .support-center .support-card .card-icon { - display: flex; - align-items: center; - justify-content: center; -} -.support-center .support-card .card-icon img { - width: 100%; - height: 100%; - object-fit: contain; -} -@media (max-width: 768px) { - .support-center .support-card .card-icon { - width: 48px !important; - height: 48px !important; - margin-bottom: 0 !important; - } -} -.support-center .support-card .card-content { - display: flex; - flex-direction: column; - justify-content: center; -} -@media (max-width: 768px) { - .support-center .support-card .card-content { - align-items: center; - text-align: center; - gap: 4px; - } -} -.support-center .support-card .card-content h3 { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 32px; + margin-bottom: 22px; + color: var(--primary-color); +} +.support-center .support-card h3 { + font-size: 20px; font-weight: 700; - color: #212529; - margin: 0 0 10px 0; - line-height: 1.3; + color: var(--text-dark); + margin-bottom: 11px; } -@media (max-width: 1024px) { - .support-center .support-card .card-content h3 { - font-size: 28px; - } +.support-center .support-card p { + font-size: 14px; + color: var(--text-gray); + line-height: 1.5; + width: 80%; } -@media (max-width: 768px) { - .support-center .support-card .card-content h3 { - font-size: 15px; - margin: 0; - line-height: 1; - } +.support-center .support-card .card-arrow { + position: absolute; + bottom: 36px; + right: 36px; + font-size: 22px; + color: var(--text-light); + transition: var(--transition-smooth); } -.support-center .support-card .card-content p { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - font-size: 18px; - font-weight: 400; - color: #515961; - margin: 0; - line-height: 1.4; +.support-center .support-card:hover .card-arrow { + color: var(--primary-color); + transform: translateX(5px); } -@media (max-width: 1024px) { - .support-center .support-card .card-content p { - font-size: 16px; - } +.support-center { + /* Custom backgrounds for specific cards */ } -@media (max-width: 768px) { - .support-center .support-card .card-content p { - font-size: 10px; - line-height: 1; - } -} -.support-center .support-card--notice { - width: 388px; - flex-shrink: 0; - flex-direction: column; - align-items: center; - padding: 50px 40px; - background: #0049B4; -} -@media (max-width: 1024px) { - .support-center .support-card--notice { - width: 100%; - max-width: 598px; - padding: 50px 40px; - } -} -@media (max-width: 768px) { - .support-center .support-card--notice { - width: calc(33.333% - 8px); - padding: 26px 0 25px; - gap: 19px; - } -} -.support-center .support-card--notice .card-icon { - width: 120px; - height: 120px; - margin-bottom: 36px; -} -.support-center .support-card--notice .card-content { - align-items: center; - text-align: center; -} -.support-center .support-card--notice .card-content h3 { +.support-center .support-card-notice { + background-color: var(--primary-color); color: #FFFFFF; - font-size: 30px; + border: none; } -@media (max-width: 1024px) { - .support-center .support-card--notice .card-content h3 { - font-size: 32px; - } +.support-center .support-card-notice h3, +.support-center .support-card-notice p, +.support-center .support-card-notice .card-icon, +.support-center .support-card-notice .card-arrow { + color: #FFFFFF; } -@media (max-width: 768px) { - .support-center .support-card--notice .card-content h3 { - font-size: 15px; - color: #FFFFFF; - } +.support-center .support-card-notice:hover { + box-shadow: 0 14px 32px rgba(0, 73, 180, 0.3); } -.support-center .support-card--notice .card-content p { - color: rgba(255, 255, 255, 0.9); - font-size: 22px; +.support-center .support-card-faq { + background-color: #EAF6FF; + border-color: #BDE0FF; } -@media (max-width: 1024px) { - .support-center .support-card--notice .card-content p { - font-size: 18px; - } +.support-center .support-card-faq .card-icon { + color: var(--secondary-color); } -@media (max-width: 768px) { - .support-center .support-card--notice .card-content p { - font-size: 10px; - color: rgba(255, 255, 255, 0.9); - } +.support-center .support-card-qna { + background-color: #E6FAF9; + border-color: #A9ECE7; } -.support-center .support-card--notice:hover { - background: rgb(0, 69.35, 171); -} -.support-center .support-card--faq, .support-center .support-card--qna { - width: 388px; - flex-shrink: 0; - flex-direction: column; - align-items: center; - padding: 50px 40px; -} -@media (max-width: 1024px) { - .support-center .support-card--faq, .support-center .support-card--qna { - width: 100%; - max-width: 598px; - padding: 40px; - } -} -@media (max-width: 768px) { - .support-center .support-card--faq, .support-center .support-card--qna { - width: calc(33.333% - 8px); - padding: 26px 0 25px; - } -} -.support-center .support-card--faq .card-icon, .support-center .support-card--qna .card-icon { - width: 120px; - height: 120px; - margin-bottom: 36px; -} -.support-center .support-card--faq .card-content, .support-center .support-card--qna .card-content { - align-items: center; - text-align: center; - flex: none; -} -.support-center .support-card--faq .card-content h3, .support-center .support-card--qna .card-content h3 { - font-size: 30px; -} -@media (max-width: 1024px) { - .support-center .support-card--faq .card-content h3, .support-center .support-card--qna .card-content h3 { - font-size: 32px; - } -} -@media (max-width: 768px) { - .support-center .support-card--faq .card-content h3, .support-center .support-card--qna .card-content h3 { - font-size: 16px; - } -} -.support-center .support-card--faq .card-content p, .support-center .support-card--qna .card-content p { - font-size: 22px; -} -@media (max-width: 1024px) { - .support-center .support-card--faq .card-content p, .support-center .support-card--qna .card-content p { - font-size: 18px; - } -} -@media (max-width: 768px) { - .support-center .support-card--faq .card-content p, .support-center .support-card--qna .card-content p { - font-size: 10px; - } -} -.support-center .support-card--faq { - background: #DAF0FF; -} -.support-center .support-card--faq .card-content h3 { - color: #212529; -} -.support-center .support-card--qna { - background: #D9F6F8; -} -.support-center .support-card--qna .card-content h3 { - color: #212529; +.support-center .support-card-qna .card-icon { + color: #0D9488; } +/* 원래 있던 api-stats-section 스타일 (주석 처리) .api-stats-section { position: relative; - height: 630px; + height: 567px; padding: 0; - padding-top: 132px; - padding-bottom: 134px; + padding-top: 119px; + padding-bottom: 121px; overflow: hidden; display: flex; align-items: center; justify-content: center; -} -@media (max-width: 1024px) { - .api-stats-section { - min-height: 600px; - padding: 64px 0; + + @include respond-to('md') { + min-height: 540px; + padding: $spacing-4xl 0; } -} -@media (max-width: 768px) { - .api-stats-section { - min-height: 320px; - padding: 60px 20px; + + @include respond-to('sm') { + // Figma 모바일: 288px 높이, 패딩 54px 18px + min-height: 288px; + padding: 54px 18px; align-items: flex-start; } } +// Background with image .stats-background { position: absolute; inset: 0; - background-image: url("/img/bg_main_api_usage.png"); + background-image: url('/img/bg_main_api_usage.png'); background-size: cover; background-position: center; background-repeat: no-repeat; overflow: hidden; } -.api-stats-section .container { +.api-stats-section { + .container { + position: relative; + z-index: 1; + + @include respond-to('sm') { + display: flex; + flex-direction: column; + gap: 22px; + width: 100%; + max-width: 100%; + padding: 0; + } + } + + .section-header { + text-align: center; + height: 90px; + + @include respond-to('sm') { + // Figma 모바일: 타이틀 왼쪽 정렬 + text-align: left; + margin-bottom: 0; + } + } + + .section-title { + line-height: 1.5; + margin: 0; + + .title-top { + display: block; + font-size: 25px; + font-weight: 400; + color: #E8E8E8; + + @include respond-to('md') { + font-size: 25px; + } + + @include respond-to('sm') { + // Figma 모바일: 14px Medium + font-size: 14px; + font-weight: 500; + letter-spacing: -0.32px; + margin-bottom: 0; + } + } + + .title-highlight { + font-size: 34px; + font-weight: 700; + color: #81D5FF; + + @include respond-to('md') { + font-size: 32px; + } + + @include respond-to('sm') { + // Figma 모바일: 18px Bold #81d5ff + font-size: 18px; + } + } + + // Rest of the title text + font-size: 34px; + font-weight: 700; + color: #E8E8E8; + + @include respond-to('md') { + font-size: 32px; + } + + @include respond-to('sm') { + // Figma 모바일: 18px Bold + font-size: 18px; + font-weight: 700; + line-height: 1; + letter-spacing: -0.4px; + } + } + + // Stats cards container + .stats-cards { + display: flex; + align-items: center; + justify-content: center; + gap: 0; + max-width: 1200px; + margin: 0 auto; + + @include respond-to('md') { + flex-direction: column; + gap: 36px; + } + + @include respond-to('sm') { + // Figma 모바일: 가로 배치, 구분선과 함께 + flex-direction: row !important; + justify-content: center; + align-items: center; + gap: 0 !important; + padding: 0; + height: 119px; + max-width: 100%; + margin: 0; + } + } + + // Individual stat card + .stat-card { + display: flex; + flex-direction: column; + align-items: center; + gap: 9px; + width: 131px; + + @include respond-to('md') { + width: auto; + } + + @include respond-to('sm') { + // Figma 모바일: 65px 너비, padding 13px 9px + gap: 10px; + padding: 9px 13px; + height: 119px; + justify-content: center; + width: 108px; + } + + .stat-label { + font-family: $font-family-primary; + font-size: 18px; + font-weight: 500; + color: #FFFFFF; + line-height: 36px; + margin: 0; + white-space: nowrap; + + @include respond-to('sm') { + // Figma 모바일: 9px Medium + font-size: 9px; + font-weight: 500; + line-height: 1; + order: -1; // 라벨이 위에 오도록 + } + } + + .stat-icon { + width: 63px; + height: 63px; + display: flex; + align-items: center; + justify-content: center; + transition: transform 0.3s ease; + + svg { + width: 100%; + height: 100%; + filter: drop-shadow(0 4px 7px rgba(0, 0, 0, 0.2)); + } + + img { + width: 100%; + height: 100%; + object-fit: contain; + } + + @include respond-to('sm') { + // Figma 모바일: 40x40px 아이콘 + width: 36px; + height: 36px; + } + } + + .stat-number { + font-family: $font-family-primary; + font-size: 40px; + font-weight: 700; + color: #EFDCB2; + line-height: 55px; + margin: 0; + text-align: center; + white-space: nowrap; + + @include respond-to('md') { + font-size: 36px; + } + + @include respond-to('sm') { + // Figma 모바일: 14px Bold #efdcb2 + font-size: 14px; + line-height: 1; + } + } + + // Hover effect + &:hover { + .stat-icon { + transform: scale(1.1); + } + } + } + + // Vertical divider between cards + .stats-divider { + width: 1px; + height: 180px; + margin: 0 54px; + flex-shrink: 0; + background-color: #ffffff; + + @include respond-to('md') { + display: none; + } + + // 1152px 이상: stat-card(131px) <- 108px -> divider <- 108px -> stat-card + @include respond-to('lg') { + margin: 0 108px; + } + + @include respond-to('sm') { + // Figma 모바일: 세로 구분선 표시 + display: block !important; + width: 1px; + height: 97px; + margin: 0; + background-color: rgba(250, 250, 250, 1); + } + } +} +*/ +/* 피그마 시안 적용 API Stats 스타일 */ +.api-stats-section { + padding: 90px 0; + background-color: #122144; + color: #FFFFFF; + position: relative; + overflow: hidden; +} +@media (max-width: 768px) { + .api-stats-section { + padding: 54px 0; + } +} +.api-stats-section .stats-background { + position: absolute; + inset: 0; + background: linear-gradient(135deg, #122144 0%, #0049B4 100%); + pointer-events: none; +} +.api-stats-section .stats-header { + text-align: center; + margin-bottom: 58px; position: relative; z-index: 1; } @media (max-width: 768px) { - .api-stats-section .container { - display: flex; - flex-direction: column; - gap: 24px; - width: 100%; - max-width: 100%; - padding: 0; - } -} -.api-stats-section .section-header { - text-align: center; - height: 100px; -} -@media (max-width: 768px) { - .api-stats-section .section-header { - text-align: left; - margin-bottom: 0; - } -} -.api-stats-section .section-title { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - line-height: 1.5; - margin: 0; -} -.api-stats-section .section-title .title-top { - display: block; - font-size: 28px; - font-weight: 400; - color: #E8E8E8; -} -@media (max-width: 1024px) { - .api-stats-section .section-title .title-top { - font-size: 28px; + .api-stats-section .stats-header { + margin-bottom: 30px; } } @media (max-width: 768px) { - .api-stats-section .section-title .title-top { - font-size: 16px; - font-weight: 500; - letter-spacing: -0.32px; - margin-bottom: 0; + .api-stats-section .stats-title { + font-size: 27px; } } -.api-stats-section .section-title .title-highlight { - font-size: 38px; +.api-stats-section .stats-title .title-top { + font-size: 20px; + font-weight: 500; + color: #81d5ff; +} +@media (max-width: 768px) { + .api-stats-section .stats-title .title-top { + font-size: 15px; + } +} +.api-stats-section .stats-title .title-highlight { + font-size: 34px; font-weight: 700; - color: #81D5FF; -} -@media (max-width: 1024px) { - .api-stats-section .section-title .title-highlight { - font-size: 36px; - } + color: #FFFFFF; + line-height: 1.35; + -webkit-text-fill-color: transparent; + background: linear-gradient(90deg, rgb(255, 255, 255) 0%, rgb(129, 213, 255) 100%) text; } @media (max-width: 768px) { - .api-stats-section .section-title .title-highlight { - font-size: 20px; + .api-stats-section .stats-title .title-highlight { + font-size: 27px; + word-break: keep-all; } } -.api-stats-section .section-title { - font-size: 38px; - font-weight: 700; - color: #E8E8E8; -} -@media (max-width: 1024px) { - .api-stats-section .section-title { - font-size: 36px; - } -} -@media (max-width: 768px) { - .api-stats-section .section-title { - font-size: 20px; - font-weight: 700; - line-height: 1; - letter-spacing: -0.4px; - } -} -.api-stats-section .stats-cards { +.api-stats-section .stats-grid { display: flex; align-items: center; - justify-content: center; - gap: 0; - max-width: 1200px; + justify-content: space-between; + max-width: 1000px; margin: 0 auto; -} -@media (max-width: 1024px) { - .api-stats-section .stats-cards { - flex-direction: column; - gap: 40px; - } + position: relative; + z-index: 1; } @media (max-width: 768px) { - .api-stats-section .stats-cards { - flex-direction: row !important; - justify-content: center; - align-items: center; - gap: 0 !important; - padding: 0; - height: 132px; - max-width: 100%; - margin: 0; + .api-stats-section .stats-grid { + flex-direction: column; + gap: 20px; } } .api-stats-section .stat-card { + flex: 1; + text-align: center; display: flex; flex-direction: column; align-items: center; - gap: 10px; - width: 145px; + gap: 14px; } -@media (max-width: 1024px) { - .api-stats-section .stat-card { - width: auto; - } +.api-stats-section .stat-card:hover .stat-icon-wrapper { + background-color: #FFFFFF; + color: var(--primary-color); + transform: translateY(-4px) scale(1.05); } @media (max-width: 768px) { .api-stats-section .stat-card { - gap: 11px; - padding: 10px 14px; - height: 132px; - justify-content: center; - width: 120px; + gap: 8px; } } -.api-stats-section .stat-card .stat-label { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - font-size: 20px; - font-weight: 500; - color: #FFFFFF; - line-height: 40px; - margin: 0; - white-space: nowrap; -} -@media (max-width: 768px) { - .api-stats-section .stat-card .stat-label { - font-size: 10px; - font-weight: 500; - line-height: 1; - order: -1; - } -} -.api-stats-section .stat-card .stat-icon { - width: 70px; - height: 70px; +.api-stats-section .stat-icon-wrapper { + width: 65px; + height: 65px; + background-color: rgba(255, 255, 255, 0.08); + border-radius: 50%; display: flex; align-items: center; justify-content: center; - transition: transform 0.3s ease; -} -.api-stats-section .stat-card .stat-icon svg { - width: 100%; - height: 100%; - filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.2)); -} -.api-stats-section .stat-card .stat-icon img { - width: 100%; - height: 100%; - object-fit: contain; + font-size: 29px; + color: #81d5ff; + border: 1px solid rgba(255, 255, 255, 0.15); + box-shadow: 0 7px 22px rgba(0, 0, 0, 0.15); + transition: var(--transition-smooth); } @media (max-width: 768px) { - .api-stats-section .stat-card .stat-icon { - width: 40px; - height: 40px; + .api-stats-section .stat-icon-wrapper { + width: 50px; + height: 50px; + font-size: 22px; } } -.api-stats-section .stat-card .stat-number { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - font-size: 44px; +.api-stats-section .stat-label { + font-size: 14px; + font-weight: 500; + color: #BAC7DE; +} +.api-stats-section .stat-number-box { + display: flex; + align-items: baseline; + justify-content: center; +} +.api-stats-section .stat-number { + font-family: "Outfit", sans-serif; + font-size: 41px; font-weight: 700; - color: #EFDCB2; - line-height: 61px; - margin: 0; - text-align: center; - white-space: nowrap; -} -@media (max-width: 1024px) { - .api-stats-section .stat-card .stat-number { - font-size: 40px; - } + color: var(--accent-color); + line-height: 1; } @media (max-width: 768px) { - .api-stats-section .stat-card .stat-number { - font-size: 16px; - line-height: 1; + .api-stats-section .stat-number { + font-size: 32px; } } -.api-stats-section .stat-card:hover .stat-icon { - transform: scale(1.1); +.api-stats-section .stat-unit { + font-size: 16px; + font-weight: 700; + color: #FFFFFF; + margin-left: 4px; } .api-stats-section .stats-divider { width: 1px; - height: 200px; - margin: 0 60px; - flex-shrink: 0; - background-color: #ffffff; -} -@media (max-width: 1024px) { - .api-stats-section .stats-divider { - display: none; - } -} -@media (min-width: 1280px) { - .api-stats-section .stats-divider { - margin: 0 120px; - } + height: 108px; + background: linear-gradient(180deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0) 100%); } @media (max-width: 768px) { .api-stats-section .stats-divider { - display: block !important; - width: 1px; - height: 108px; - margin: 0; - background-color: rgb(250, 250, 250); + width: 100%; + height: 1px; + background: linear-gradient(90deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0) 100%); } } @@ -8587,16 +9801,128 @@ select.form-control { @keyframes countUp { from { opacity: 0; - transform: translateY(20px); + transform: translateY(18px); } to { opacity: 1; transform: translateY(0); } } +/* 원래 있던 signup-cta-section 스타일 (주석 처리) .signup-cta-section { position: relative; - min-height: 329px; + min-height: 296px; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + + @include respond-to('sm') { + // Figma 모바일: 144px 높이, 패딩 40px + min-height: 144px; + padding: 40px 0; + } + + .cta-background { + position: absolute; + inset: 0; + background-image: url('/img/bg_main_join.png'); + background-size: cover; + background-position: center; + background-repeat: no-repeat; + overflow: hidden; + } + + .container { + position: relative; + z-index: 1; + } + + .cta-content { + display: flex; + flex-direction: column; + align-items: center; + gap: 36px; + text-align: center; + + @include respond-to('sm') { + // Figma 모바일: gap 11px + gap: 11px; + } + } + + .cta-title { + font-size: 36px; + color: #000000; + line-height: normal; + margin: 0; + + @include respond-to('md') { + font-size: 29px; + } + + @include respond-to('sm') { + // Figma 모바일: 14px Bold, #212529, tracking -0.32px + font-size: 14px; + font-weight: 700; + color: #212529; + letter-spacing: -0.32px; + line-height: 0.9994; + } + } + + .btn-signup-cta { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 9px; + min-height: 54px; + width: 160px; + background: #008ae2; + color: #FFFFFF; + font-family: $font-family-primary; + font-size: 18px; + font-weight: 700; + border-radius: 9px; + text-decoration: none; + transition: all 0.3s ease; + + &:hover { + background: darken(#008ae2, 10%); + transform: translateY(-2px); + box-shadow: 0 4px 11px rgba(0, 138, 226, 0.3); + } + + &:active { + transform: translateY(0); + } + + @include respond-to('sm') { + // Figma 모바일: 137px × 36px, border-radius 4px, 11px Bold + width: 137px; + min-height: 36px; + padding: 11px 31px; + font-size: 11px; + font-weight: 700; + border-radius: 4px; + line-height: 14px; + } + } + + .cta-divider { + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 1px; + background: #DDDDDD; + } +} +*/ +/* 피그마 시안 적용 CTA 스타일 */ +.signup-cta-section { + position: relative; + min-height: 296px; display: flex; align-items: center; justify-content: center; @@ -8604,14 +9930,13 @@ select.form-control { } @media (max-width: 768px) { .signup-cta-section { - min-height: 160px; - padding: 44px 0; + min-height: 144px; + padding: 40px 0; } } .signup-cta-section .cta-background { position: absolute; inset: 0; - background-image: url("/img/bg_main_join.png"); background-size: cover; background-position: center; background-repeat: no-repeat; @@ -8620,35 +9945,67 @@ select.form-control { .signup-cta-section .container { position: relative; z-index: 1; + background: #ECF0FA; + margin: 0; + width: 100%; + max-width: 100%; + padding: 23px 0; +} +.signup-cta-section .container .ctaImg { + position: absolute; + width: 293px; +} +@media (max-width: 1024px) { + .signup-cta-section .container .ctaImg { + width: 180px; + } +} +@media (max-width: 768px) { + .signup-cta-section .container .ctaImg { + display: none; + } +} +.signup-cta-section .container .ctaImg.left { + top: 0; + left: 0; +} +.signup-cta-section .container .ctaImg.right { + top: -23px; + right: 0; +} +@media (max-width: 768px) { + .signup-cta-section .container .ctaImg.right { + top: -10px; + } } .signup-cta-section .cta-content { display: flex; flex-direction: column; align-items: center; - gap: 40px; + gap: 18px; text-align: center; } @media (max-width: 768px) { .signup-cta-section .cta-content { - gap: 12px; + gap: 11px; } } .signup-cta-section .cta-title { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - font-size: 40px; - font-weight: 700; + font-size: 27px; color: #000000; line-height: normal; + font-weight: 600; margin: 0; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; } @media (max-width: 1024px) { .signup-cta-section .cta-title { - font-size: 32px; + font-size: 29px; } } @media (max-width: 768px) { .signup-cta-section .cta-title { - font-size: 16px; + font-size: 14px; font-weight: 700; color: #212529; letter-spacing: -0.32px; @@ -8659,35 +10016,35 @@ select.form-control { display: inline-flex; align-items: center; justify-content: center; - padding: 10px; - min-height: 60px; - width: 178px; - background: #008ae2; - color: #FFFFFF; - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - font-size: 20px; + height: 54px; + background-color: var(--primary-color); + color: rgb(255, 255, 255); + font-size: 16px; font-weight: 700; - border-radius: 10px; - text-decoration: none; - transition: all 0.3s ease; + box-shadow: rgba(0, 73, 180, 0.25) 0px 9px 23px; + gap: 9px; + padding: 0 36px; + border-radius: 13px; + transition: var(--transition-smooth); } .signup-cta-section .btn-signup-cta:hover { - background: rgb(0, 124.2, 203.4); + background: var(--secondary-color); transform: translateY(-2px); - box-shadow: 0 4px 12px rgba(0, 138, 226, 0.3); + box-shadow: 0 4px 11px rgba(0, 138, 226, 0.3); } .signup-cta-section .btn-signup-cta:active { transform: translateY(0); } @media (max-width: 768px) { .signup-cta-section .btn-signup-cta { - width: 152px; - min-height: 40px; - padding: 12px 34px; - font-size: 12px; + width: auto; + min-width: 160px; + height: 44px; + padding: 0 24px; + font-size: 14px; font-weight: 700; - border-radius: 4px; - line-height: 16px; + border-radius: 8px; + line-height: normal; } } .signup-cta-section .cta-divider { @@ -8699,28 +10056,144 @@ select.form-control { background: #DDDDDD; } +body.index-page-body { + zoom: 0.9; +} + .api-market-container { + max-width: 1240px; + margin: 0 auto; + padding: 60px 0px; display: flex; - min-height: 100vh; - background-color: #FFFFFF; + gap: 60px; + align-items: flex-start; + width: 100%; +} +@media (max-width: 1024px) { + .api-market-container { + flex-direction: column; + gap: 30px; + padding: 20px; + } +} +.api-market-container { + /* + .service-sidebar { + @media (max-width: 768px) { + position: fixed; + left: 0; + top: 0; + height: 100vh; + width: 280px; + margin: 0; + z-index: 1000; + transform: translateX(-100%); + transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); + background: #ffffff; + box-shadow: 0 8px 30px rgba(0, 0, 0, 0.15); + padding: 80px 24px 30px; // Clear header height if any + + &.mobile-open { + transform: translateX(0); + } + + .service-nav { + flex-direction: column !important; + gap: 16px !important; + border: none !important; + padding: 0 !important; + box-shadow: none !important; + background: transparent !important; + } + } + } + */ +} + +.api-market-hero { + background: linear-gradient(to right, #eff9fe, #fcfeff); + border: 1px solid #e3e8f0; + padding: 60px 0; + display: flex; + justify-content: center; + width: calc(100vw - 80px); position: relative; - margin-bottom: 20px; + left: 50%; + transform: translateX(-50%); + margin-top: 40px; + margin-bottom: 60px; +} +.api-market-hero__inner { + max-width: 1240px; + width: 100%; + display: flex; + align-items: center; + justify-content: center; + gap: 138px; + padding: 0 37px; +} +.api-market-hero__illust .cloud-image-placeholder { + width: 331px; + height: 229px; + background: url("../img/hero_image_1.png") no-repeat center center; + background-size: contain; +} +.api-market-hero__content { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 15px; +} +.api-market-hero__badge { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 8px 20px; + background: #fff; + border: 1px solid #d1e6f0; + border-radius: 38px; + font-size: 13px; + font-family: "Spoqa Han Sans Neo", sans-serif; + font-weight: 700; + color: #1b4ab7; +} +.api-market-hero__badge .dot { + width: 8px; + height: 8px; + border-radius: 50%; + background: #1b4ab7; +} +.api-market-hero__title { + font-size: 36px; + font-family: "Spoqa Han Sans Neo", sans-serif; + font-weight: 700; + color: #000; + margin: 0; +} +.api-market-hero__desc { + font-size: 16px; + font-family: "Spoqa Han Sans Neo", sans-serif; + color: #64748b; + line-height: 1.6; + margin: 0; } .api-market-sidebar { - width: 283px; - background-color: #F8FBFD; + width: 213px; + background-color: #ffffff; + border: 1px solid #e3e8f0; border-radius: 20px; - padding: 40px 24px; + padding: 28px 30px; position: sticky; - top: 24px; - height: calc(100vh - 32px); + top: 40px; + height: fit-content; flex-shrink: 0; - margin: 16px; + margin-right: 40px; } @media (max-width: 1024px) { .api-market-sidebar { - width: 240px; + width: 200px; + margin-right: 20px; } } @media (max-width: 768px) { @@ -8731,7 +10204,7 @@ select.form-control { transform: translateX(-100%); transition: transform 0.3s ease; box-shadow: 0 8px 24px rgba(75, 155, 255, 0.15); - z-index: 500; + z-index: 1050; height: calc(100vh - 60px); margin: 0; border-radius: 0; @@ -8741,60 +10214,35 @@ select.form-control { } } -.api-sidebar-header { +.api-sidebar-nav { display: flex; - justify-content: center; - margin-bottom: 32px; - padding-bottom: 24px; + flex-direction: column; + gap: 26px; } -.api-sidebar-header img { - max-width: 100%; - height: auto; -} - .api-sidebar-nav .menu-section { margin-bottom: 0; - border-bottom: 1px solid #DDDDDD; -} -.api-sidebar-nav .menu-section:last-child { border-bottom: none; } .api-sidebar-nav .menu-title { - padding: 16px 10px; - font-size: 20px; - font-weight: 400; - color: #1A1A2E; + padding: 0; + font-size: 16px; + font-family: "Spoqa Han Sans Neo", sans-serif; + font-weight: 500; + color: #0d0e11; cursor: pointer; - transition: all 0.3s ease; + transition: color 0.2s; display: flex; align-items: center; justify-content: space-between; position: relative; } .api-sidebar-nav .menu-title:hover { - background-color: rgba(0, 73, 180, 0.05); + color: #2a69de; + background-color: transparent; } .api-sidebar-nav .menu-title.active { font-weight: 700; - color: #0049b4; -} -.api-sidebar-nav .menu-title .api-count { - display: inline-flex; - align-items: center; - justify-content: center; - min-width: 24px; - height: 24px; - padding: 0 8px; - background-color: rgba(0, 73, 180, 0.1); - color: #0049b4; - font-size: 12px; - font-weight: 600; - border-radius: 50px; - transition: all 0.3s ease; -} -.api-sidebar-nav .menu-title.active .api-count { - background-color: #0049b4; - color: #FFFFFF; + color: #2a69de; } .api-sidebar-nav .accordion-section .menu-title.accordion-trigger .menu-title-text { flex: 1; @@ -8879,9 +10327,7 @@ select.form-control { .api-market-content { flex: 1; - padding-top: 80px; - padding-left: 8px; - padding-right: 8px; + width: 100%; min-height: 100vh; display: flex; flex-direction: column; @@ -8902,35 +10348,7 @@ select.form-control { } .api-mobile-toggle { - display: none; - position: fixed; - bottom: 24px; - right: 24px; - width: 56px; - height: 56px; - border-radius: 50%; - border: none; - color: #FFFFFF; - font-size: 24px; - cursor: pointer; - box-shadow: 0 8px 24px rgba(75, 155, 255, 0.15); - z-index: 501; - transition: all 0.3s ease; - background-color: #0049b4; -} -.api-mobile-toggle:hover { - transform: scale(1.05); - box-shadow: 0 16px 40px rgba(75, 155, 255, 0.2); -} -.api-mobile-toggle:active { - transform: scale(0.95); -} -@media (max-width: 768px) { - .api-mobile-toggle { - display: flex; - align-items: center; - justify-content: center; - } + display: none !important; } .api-market-header { @@ -8961,7 +10379,7 @@ select.form-control { .api-market-search { position: relative; - width: 300px; + width: 228px; } @media (max-width: 768px) { .api-market-search { @@ -8975,8 +10393,9 @@ select.form-control { } .api-market-search input { flex: 1; - padding: 12px 48px 12px 16px; - border: 1px solid #E2E8F0; + height: 44px; + padding: 0 40px 0 16px; + border: 1px solid #dadada; border-radius: 12px; font-size: 14px; outline: none; @@ -8987,128 +10406,183 @@ select.form-control { box-shadow: 0 0 0 3px rgba(0, 73, 180, 0.1); } .api-market-search input::placeholder { - color: #94A3B8; + color: #7f8a95; } .api-market-search .search-submit-btn { position: absolute; - right: 4px; + right: 8px; top: 50%; transform: translateY(-50%); - width: 36px; - height: 36px; + width: 20px; + height: 20px; background: transparent; border: none; - border-radius: 8px; cursor: pointer; display: flex; align-items: center; justify-content: center; - transition: all 0.3s ease; - color: #64748B; } -.api-market-search .search-submit-btn:hover { - background: #EFF6FF; - color: #0049b4; - transform: translateY(-50%) scale(1.05); -} -.api-market-search .search-submit-btn:active { - transform: translateY(-50%) scale(0.95); -} -.api-market-search .search-submit-btn .search-icon { - font-size: 18px; - display: block; +.api-market-search .search-submit-btn svg { + width: 100%; + height: 100%; } .api-card-grid { display: grid; - grid-template-columns: repeat(3, 320px); + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 24px; - justify-content: center; -} -@media (max-width: 1024px) { - .api-card-grid { - grid-template-columns: repeat(2, 320px); - } -} -@media (max-width: 768px) { - .api-card-grid { - grid-template-columns: 1fr; - } + width: 100%; } .api-card { background: #FFFFFF; - border-radius: 12px; - padding: 32px; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); + border-radius: 20px; + border: 1px solid #DDDDDD; + box-shadow: 0px 4px 2px rgba(194, 194, 194, 0.25); cursor: pointer; - display: flex; - flex-direction: column; - gap: 16px; - min-height: 180px; position: relative; - border: 1px solid #E2E8F0; + width: 100%; + height: 312px; + overflow: hidden; + transition: transform 0.3s ease, box-shadow 0.3s ease, background 0.3s ease; } -.api-card:active { - transform: scale(0.98); +.api-card:hover, .api-card:focus, .api-card:active { + transform: translateY(-5px); + box-shadow: 0px 8px 12px rgba(194, 194, 194, 0.4); + background: linear-gradient(to bottom, #50caff, #2088ff); + border: none; } - -.api-card-group { +.api-card:hover .api-card-badge, .api-card:focus .api-card-badge, .api-card:active .api-card-badge { + background-color: #d8f0ff; +} +.api-card:hover .api-card-title, +.api-card:hover .api-card-desc, .api-card:focus .api-card-title, +.api-card:focus .api-card-desc, .api-card:active .api-card-title, +.api-card:active .api-card-desc { + color: #ffffff; +} +.api-card { + /* 그룹 배지 + 현재 상태 태그를 한 줄에 둔다. + 기존 그룹 배지가 갖고 있던 절대 위치(top 39 / left 32)를 이 래퍼가 이어받는다 */ +} +.api-card-badges { + position: absolute; + top: 39px; + left: 32px; + right: 32px; display: flex; align-items: center; gap: 8px; + min-width: 0; } - -.api-card-group-icon { - width: 32px; - height: 32px; - display: flex; +.api-card-badge { + background-color: #f2f2f3; + border-radius: 10px; + padding: 2px 20px; + display: inline-flex; align-items: center; justify-content: center; - background: #F8FAFC; - border-radius: 8px; - flex-shrink: 0; + min-width: 0; } -.api-card-group-icon img { - width: 20px; - height: 20px; - object-fit: contain; -} -.api-card-group-icon i { - font-size: 16px; - color: #0049b4; -} - -.api-card-group-name { +.api-card-badge span { font-size: 14px; - color: #64748B; + font-family: "Spoqa Han Sans Neo", sans-serif; font-weight: 500; + color: #1b4ab7; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } - -.api-card-name { - font-size: 20px; +.api-card { + /* 현재 상태 태그. 색 규격은 API Status 화면(.as-badge)과 동일. + 상태를 못 받았을 때(빈 값)는 자리만 차지하지 않도록 숨긴다 */ +} +.api-card-status { + flex-shrink: 0; + display: inline-flex; + align-items: center; + padding: 3px 12px; + border-radius: 10px; + font-size: 12px; + font-family: "Spoqa Han Sans Neo", sans-serif; font-weight: 600; - color: #1A1A2E; - line-height: 1.2; + white-space: nowrap; + background: #eef1f5; + color: #4b5563; +} +.api-card-status:empty { + display: none; +} +.api-card-status.is-outage { + background: #feeae7; + color: #c8362f; +} +.api-card-status.is-maintenance { + background: #e7f0fb; + color: #0a66c2; +} +.api-card-status.is-degraded { + background: #fef3c7; + color: #c77800; +} +.api-card-status.is-normal { + background: #e7f6ec; + color: #1b8c4a; +} +.api-card-title { + position: absolute; + top: 86px; + left: 36px; + right: 40px; + font-size: 18px; + font-family: "Spoqa Han Sans Neo", sans-serif; + font-weight: 700; + color: #000000; margin: 0; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; + text-overflow: ellipsis; + word-break: break-word; text-align: left; + white-space: normal; } - -.api-card-description { +.api-card-desc { + position: absolute; + top: 135px; + left: 36px; + right: 40px; font-size: 14px; - color: #64748B; - line-height: 1.6; + font-family: "Spoqa Han Sans Neo", sans-serif; + font-weight: 400; + color: #64748b; + line-height: 1.5; margin: 0; - flex-grow: 1; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; + text-overflow: ellipsis; + word-break: break-word; text-align: left; + white-space: normal; +} +.api-card-image { + position: absolute; + bottom: 28px; + right: 20px; + width: 93px; + height: 88px; + display: flex; + align-items: center; + justify-content: center; + pointer-events: none; +} +.api-card-image img { + max-width: 100%; + max-height: 100%; + object-fit: contain; } .api-empty-state { @@ -9160,7 +10634,7 @@ select.form-control { right: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.5); - z-index: 499; + z-index: 1049; opacity: 0; transition: opacity 0.3s ease; pointer-events: none; @@ -9199,6 +10673,8 @@ select.form-control { transition: all 0.3s ease; position: relative; bottom: -2px; + display: inline-block; + text-decoration: none; } .api-detail-tabs .tab-button:hover { color: #0049b4; @@ -9220,6 +10696,222 @@ select.form-control { display: flex; } +#testbed-tab { + gap: 0; +} +#testbed-tab #swagger-ui .info { + margin: 24px 0; +} + +.testbed-auth-gate { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 16px; + padding: calc(32px * 2) 24px; + text-align: center; + background: #FFFFFF; + border: 1px solid #E2E8F0; + border-radius: 12px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); +} +.testbed-auth-gate .testbed-auth-gate__icon { + font-size: 40px; + line-height: 1; +} +.testbed-auth-gate .testbed-auth-gate__title { + margin: 0; + font-size: 20px; + font-weight: 700; + color: #1A1A2E; +} +.testbed-auth-gate .testbed-auth-gate__desc { + margin: 0; + font-size: 16px; + color: #64748B; +} +.testbed-auth-gate .btn { + margin-top: 8px; + min-width: 160px; +} + +.testbed-app-panel { + margin: 8px 0 0; + padding: 24px; + background: #FFFFFF; + border: none; + border-radius: 12px; + box-shadow: none; +} +.testbed-app-panel .testbed-app-panel__head { + display: flex; + align-items: baseline; + flex-wrap: wrap; + gap: 4px 8px; + margin-bottom: 16px; +} +.testbed-app-panel .testbed-app-panel__title { + position: relative; + padding-left: 16px; + font-size: 16px; + font-weight: 700; + color: #1A1A2E; + letter-spacing: -0.01em; +} +.testbed-app-panel .testbed-app-panel__title::before { + content: ""; + position: absolute; + left: 0; + top: 50%; + transform: translateY(-50%); + width: 4px; + height: 15px; + background: #0049b4; + border-radius: 2px; +} +.testbed-app-panel .testbed-app-panel__desc { + font-size: 14px; + color: #64748B; +} +.testbed-app-panel .testbed-app-field { + position: relative; + max-width: 380px; +} +.testbed-app-panel .testbed-app-field::after { + content: ""; + position: absolute; + right: 16px; + top: 50%; + width: 9px; + height: 9px; + margin-top: -6px; + border-right: 2px solid #64748B; + border-bottom: 2px solid #64748B; + transform: rotate(45deg); + pointer-events: none; +} +.testbed-app-panel #apps { + width: 100%; + height: 46px; + padding: 0 42px 0 16px; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-size: 14px; + color: #1A1A2E; + background: #F8FAFC; + border: 1px solid #E2E8F0; + border-radius: 8px; + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + cursor: pointer; + transition: all 0.15s ease; +} +.testbed-app-panel #apps:hover:not(:disabled) { + border-color: #94A3B8; + background: #FFFFFF; +} +.testbed-app-panel #apps:focus { + outline: none; + background: #FFFFFF; + border-color: #0049b4; + box-shadow: 0 0 0 3px rgba(0, 73, 180, 0.14); +} +.testbed-app-panel #apps:disabled { + color: #94A3B8; + background: #F8FAFC; + cursor: not-allowed; +} +.testbed-app-panel .testbed-app-notice { + margin: 16px 0 0; + padding: 11px 16px; + font-size: 14px; + line-height: 1.6; + color: #64748B; + background: #EFF6FF; + border: 1px solid rgba(0, 73, 180, 0.18); + border-left: 3px solid #0049b4; + border-radius: 6px; +} +.testbed-app-panel .testbed-app-notice::before { + content: "ⓘ "; + color: #0049b4; + font-weight: 700; +} +@media (max-width: 768px) { + .testbed-app-panel { + padding: 16px; + } + .testbed-app-panel .testbed-app-field { + max-width: 100%; + } +} + +/* "기본 정보" 헤더: 좌측 제목 + 우측 현재 상태/상태 이력 */ +.api-basic-info-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + flex-wrap: wrap; +} + +/* 현재 상태 배지 + "상태 이력 확인" 버튼. + 색 규격은 API Status 화면(.as-badge)과 동일하게 맞춘다 */ +.api-status-inline { + display: flex; + align-items: center; + gap: 10px; + flex-wrap: wrap; +} +.api-status-inline .api-status-inline__label { + font-size: 13px; + font-weight: 500; + color: #4b5563; +} +.api-status-inline .api-status-inline__badge { + display: inline-flex; + align-items: center; + font-size: 12px; + font-weight: 600; + padding: 5px 12px; + border-radius: 999px; + letter-spacing: 0.02em; + white-space: nowrap; + background: #eef1f5; + color: #4b5563; +} +.api-status-inline .api-status-inline__badge.is-outage { + background: #feeae7; + color: #c8362f; +} +.api-status-inline .api-status-inline__badge.is-maintenance { + background: #e7f0fb; + color: #0a66c2; +} +.api-status-inline .api-status-inline__badge.is-degraded { + background: #fef3c7; + color: #c77800; +} +.api-status-inline .api-status-inline__badge.is-normal { + background: #e7f6ec; + color: #1b8c4a; +} +.api-status-inline .api-status-inline__badge.is-loading { + color: #6b7280; +} +.api-status-inline .api-status-inline__badge.is-muted { + background: #eef1f5; + color: #6b7280; +} + +/* 하단 중앙 "API 사용 신청" 버튼 */ +.api-apply-footer { + display: flex; + justify-content: center; + margin-top: 32px; +} + .api-overview-card { background: transparent; border-radius: 0; @@ -9312,12 +11004,12 @@ select.form-control { padding-bottom: 0; border-bottom: none; } -.api-detail-card .detail-content { +.api-detail-card .detail-content:not(.editor-content) { font-size: 14px; color: #64748B; line-height: 1.6; } -.api-detail-card .detail-content pre { +.api-detail-card .detail-content:not(.editor-content) pre { background-color: #F8FAFC; border: 1px solid #E2E8F0; border-radius: 8px; @@ -9325,14 +11017,14 @@ select.form-control { overflow-x: auto; margin: 0; } -.api-detail-card .detail-content pre code { +.api-detail-card .detail-content:not(.editor-content) pre code { font-family: "Courier New", monospace; font-size: 14px; color: #1A1A2E; white-space: pre-wrap; word-break: break-word; } -.api-detail-card .detail-content table { +.api-detail-card .detail-content:not(.editor-content) table { width: 100%; border-collapse: collapse; margin: 16px 0; @@ -9341,10 +11033,10 @@ select.form-control { border-radius: 8px; overflow: hidden; } -.api-detail-card .detail-content table thead { +.api-detail-card .detail-content:not(.editor-content) table thead { background-color: #EFF6FF; } -.api-detail-card .detail-content table th { +.api-detail-card .detail-content:not(.editor-content) table th { text-align: left; padding: 8px 16px; font-size: 14px; @@ -9353,28 +11045,28 @@ select.form-control { border-bottom: 2px solid #E2E8F0; background-color: rgba(0, 73, 180, 0.05); } -.api-detail-card .detail-content table th:not(:last-child) { +.api-detail-card .detail-content:not(.editor-content) table th:not(:last-child) { border-right: 1px solid #E2E8F0; } -.api-detail-card .detail-content table td { +.api-detail-card .detail-content:not(.editor-content) table td { padding: 8px 16px; font-size: 14px; color: #64748B; border-bottom: 1px solid #E2E8F0; } -.api-detail-card .detail-content table td:not(:last-child) { +.api-detail-card .detail-content:not(.editor-content) table td:not(:last-child) { border-right: 1px solid #E2E8F0; } -.api-detail-card .detail-content table tr { +.api-detail-card .detail-content:not(.editor-content) table tr { transition: all 0.3s ease; } -.api-detail-card .detail-content table tr:last-child td { +.api-detail-card .detail-content:not(.editor-content) table tr:last-child td { border-bottom: none; } -.api-detail-card .detail-content table tr:hover { +.api-detail-card .detail-content:not(.editor-content) table tr:hover { background-color: rgba(0, 73, 180, 0.02); } -.api-detail-card .detail-content table tbody tr:nth-child(even) { +.api-detail-card .detail-content:not(.editor-content) table tbody tr:nth-child(even) { background-color: rgba(248, 250, 252, 0.3); } @@ -9481,26 +11173,31 @@ select.form-control { font-size: 16px; margin-bottom: 8px; } - .api-detail-card .detail-content { + .api-detail-card .detail-content.editor-content { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } + .api-detail-card .detail-content:not(.editor-content) { font-size: 12px; overflow-x: auto; -webkit-overflow-scrolling: touch; } - .api-detail-card .detail-content table { + .api-detail-card .detail-content:not(.editor-content) table { min-width: 500px; margin: 8px 0; } - .api-detail-card .detail-content table th, .api-detail-card .detail-content table td { + .api-detail-card .detail-content:not(.editor-content) table th, + .api-detail-card .detail-content:not(.editor-content) table td { padding: 4px 8px; font-size: 12px; white-space: nowrap; } - .api-detail-card .detail-content pre { + .api-detail-card .detail-content:not(.editor-content) pre { padding: 8px; overflow-x: auto; -webkit-overflow-scrolling: touch; } - .api-detail-card .detail-content pre code { + .api-detail-card .detail-content:not(.editor-content) pre code { font-size: 12px; white-space: pre; word-break: normal; @@ -9542,20 +11239,22 @@ select.form-control { overflow-x: hidden; } .api-market-content { - max-width: 100vw; + width: 100%; overflow-x: hidden; } + .api-card { + height: 285px; + } } .login-page { display: flex; align-items: center; justify-content: center; - background: #EDF9FE; - padding: 40px 20px; + padding: 100px 20px; position: relative; border-radius: 12px; margin-top: 60px; - margin-bottom: 60px; + margin-bottom: 50px; } .login-container { @@ -9578,56 +11277,95 @@ select.form-control { } } -.login-logo { - width: 90px; - height: 90px; - margin-bottom: 30px; -} -.login-logo img { +.login-header { + display: flex; + flex-direction: column; + align-items: center; + margin-bottom: 40px; width: 100%; - height: 100%; - object-fit: contain; +} +.login-header .header-main-row { + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + margin-bottom: 12px; } -.login-message { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - font-size: 20px; - font-weight: 400; +.login-lock-icon { + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; +} +.login-lock-icon svg { + stroke: #000; +} + +.login-title { + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-size: 36px; + font-weight: 700; color: #000000; - text-align: center; - margin-bottom: 40px; + margin: 0; line-height: 1; } +.login-message-sub { + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-size: 18px; + font-weight: 300; + color: #80878d; + text-align: center; + margin: 0; + line-height: 1.2; +} + .login-form { width: 100%; margin-bottom: 0; } .login-form .form-group { - margin-bottom: 24px; + margin-bottom: 16px; + position: relative; } -.login-form .form-group:last-of-type { - margin-bottom: 0; +.login-form .form-group.input-with-icon .input-icon { + position: absolute; + left: 20px; + top: 50%; + transform: translateY(-50%); + display: flex; + align-items: center; + justify-content: center; + z-index: 2; +} +.login-form .form-group.input-with-icon .input-icon svg { + stroke: #767e89; +} +.login-form .form-group.input-with-icon .form-input { + padding-left: 56px; } .login-form .form-input { width: 100%; - height: 70px; - padding: 0 32px; - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + height: 58px; + padding: 0 20px; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 16px; - font-weight: 400; - color: #5F666C; - background: #FFFFFF; - border: 1px solid #DDDDDD; - border-radius: 12px; + font-weight: 300; + color: #000000; + background: #fcfcfc; + border: 1px solid #dfdfdf; + border-radius: 5px; outline: none; transition: all 0.3s ease; } .login-form .form-input::placeholder { - color: #5F666C; + color: #767e89; } .login-form .form-input:focus { - border-color: #0049B4; + border-color: #1b4ab7; + background: #ffffff; } .login-form .form-input.error { border-color: #FF6B6B; @@ -9635,17 +11373,16 @@ select.form-control { .login-form .form-checkbox { display: flex; align-items: center; - justify-content: flex-end; - gap: 7px; - margin-top: 24px; - margin-bottom: 22px; - margin-right: 10px; + justify-content: flex-start; + gap: 8px; + margin-top: 14px; + margin-bottom: 30px; } .login-form .form-checkbox input[type=checkbox] { - width: 20px; - height: 20px; - border: 1.5px solid #000000; - border-radius: 4px; + width: 14px; + height: 14px; + border: 1px solid #dfdfdf; + border-radius: 2px; cursor: pointer; appearance: none; background: #FFFFFF; @@ -9653,8 +11390,8 @@ select.form-control { flex-shrink: 0; } .login-form .form-checkbox input[type=checkbox]:checked { - background: #FFFFFF; - border-color: #2C2C2C; + background: #1b4ab7; + border-color: #1b4ab7; } .login-form .form-checkbox input[type=checkbox]:checked::after { content: ""; @@ -9662,72 +11399,89 @@ select.form-control { left: 50%; top: 45%; transform: translate(-50%, -50%) rotate(45deg); - width: 6px; - height: 12px; - border: solid #2C2C2C; - border-width: 0 2px 2px 0; + width: 3px; + height: 6px; + border: solid #ffffff; + border-width: 0 1.5px 1.5px 0; } .login-form .form-checkbox label { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - font-size: 16px; - font-weight: 400; - color: #000000; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-size: 12px; + font-weight: 300; + color: #767e89; cursor: pointer; user-select: none; - line-height: 24px; + line-height: 1; white-space: nowrap; } .login-button { width: 100%; - height: 80px; + height: 58px; padding: 10px; - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - font-size: 20px; - font-weight: 700; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-size: 16px; + font-weight: 500; color: #FFFFFF; - background: #0049B4; + background: #1b4ab7; border: none; - border-radius: 12px; + border-radius: 5px; cursor: pointer; transition: all 0.3s ease; - margin-bottom: 40px; - line-height: 1; + margin-bottom: 30px; + line-height: 20px; } .login-button:hover { - background: rgb(0, 69.35, 171); + background: rgb(25.65, 70.3, 173.85); } .login-button:active { - background: rgb(0, 65.7, 162); + background: rgb(24.3, 66.6, 164.7); } .login-button:disabled { opacity: 0.6; cursor: not-allowed; } -.login-links { +.login-links-container { display: flex; justify-content: center; align-items: center; - gap: 26px; - flex-wrap: wrap; + width: 100%; } -.login-links a { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - font-size: 16px; +.login-links-container .link-btn { + flex: 1; + height: 38px; + display: inline-flex; + align-items: center; + justify-content: center; + background: #ecf0fa; + border: 1px solid #2a69de; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-size: 14px; font-weight: 400; - color: #000000; + color: #2a69de; text-decoration: none; - transition: color 0.3s ease; - line-height: 1; + transition: all 0.2s ease; + line-height: 20px; + text-align: center; } -.login-links a:hover { - color: #0049B4; - text-decoration: underline; +.login-links-container .link-btn.first { + border-top-left-radius: 8px; + border-bottom-left-radius: 8px; + border-right: none; } -.login-links .link-separator { - color: #000000; - font-size: 16px; +.login-links-container .link-btn.middle { + border-right: none; +} +.login-links-container .link-btn.last { + border-top-right-radius: 8px; + border-bottom-right-radius: 8px; +} +.login-links-container .link-btn:hover { + background: rgb(220.61, 227.85, 245.95); +} +.login-links-container .link-btn:active { + background: rgb(205.22, 215.7, 241.9); } .login-alert { @@ -9794,9 +11548,8 @@ select.form-control { } @media (max-width: 768px) { .login-page { - background: #ebfbff; min-height: calc(100vh - 44px); - padding: 40px 20px; + padding: 100px 20px; margin-top: 0; margin-bottom: 0; border-radius: 0; @@ -9881,26 +11634,27 @@ select.form-control { } .account-recovery-page { display: flex; - align-items: flex-start; + align-items: center; justify-content: center; - background: transparent; - padding: 0; + padding: 100px 20px; position: relative; - min-height: auto; - margin: 0; + border-radius: 12px; + margin-bottom: 50px; } .account-recovery-container { width: 100%; - max-width: 800px; + max-width: 650px; margin: 0 auto; position: relative; padding: 24px; } .account-recovery-card { - background: transparent; - padding: 0; + background: #FFFFFF; + border-radius: 20px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); + padding: 36px 36px 28px; display: flex; flex-direction: column; align-items: stretch; @@ -9908,7 +11662,8 @@ select.form-control { } @media (max-width: 576px) { .account-recovery-card { - padding: 0 20px; + padding: 24px 20px 20px; + border-radius: 16px; } } @@ -9923,10 +11678,9 @@ select.form-control { .account-recovery-tabs { display: flex; gap: 0; - margin-bottom: 0; + margin-bottom: 28px; width: 100%; background: transparent; - border-radius: 0; padding: 0; } .account-recovery-tabs .tab-link { @@ -9934,36 +11688,37 @@ select.form-control { display: flex; align-items: center; justify-content: center; - padding: 14px 24px; - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - font-size: 16px; - font-weight: 700; - color: #8c959f; + padding: 14px 20px; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-size: 15px; + font-weight: 400; + color: #adb5bd; text-decoration: none; text-align: center; - background: #eceff4; + background: #FFFFFF; + border: 1.5px solid transparent; + border-bottom: 1.5px solid #dee2e6; border-radius: 0; - transition: all 0.3s ease; -} -.account-recovery-tabs .tab-link:first-child { - border-radius: 30px 0 0 0; -} -.account-recovery-tabs .tab-link:last-child { - border-radius: 0 30px 0 0; + transition: all 0.2s ease; + cursor: pointer; + position: relative; } .account-recovery-tabs .tab-link:hover { - color: #3ba4ed; - background: #e4e8ed; + color: #0049b4; } .account-recovery-tabs .tab-link.active { - color: #FFFFFF; - background: #3ba4ed; + color: #0049b4; font-weight: 700; + border-top: 1.5px solid #0049b4; + border-left: 1.5px solid #0049b4; + border-right: 1.5px solid #0049b4; + border-bottom: 2px solid #FFFFFF; + border-radius: 8px 8px 0px 0px; } @media (max-width: 576px) { .account-recovery-tabs .tab-link { - padding: 12px 16px; - font-size: 14px; + padding: 12px 10px; + font-size: 13px; } } @@ -9972,7 +11727,7 @@ select.form-control { margin-bottom: 20px; padding: 14px 18px; border-radius: 12px; - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 15px; display: flex; align-items: center; @@ -10002,75 +11757,44 @@ select.form-control { .account-recovery-form { width: 100%; margin-bottom: 0; - background: #F6F9FB; - padding: 40px; - border-radius: 0 0 12px 12px; -} -@media (max-width: 576px) { - .account-recovery-form { - padding: 24px 16px; - } + background: transparent; + padding: 0; } .account-recovery-form .form-group { display: flex; - align-items: center; - gap: 20px; - margin-bottom: 20px; + flex-direction: column; + gap: 0; + margin-bottom: 12px; } .account-recovery-form .form-group:last-of-type { margin-bottom: 0; } -@media (max-width: 768px) { - .account-recovery-form .form-group { - flex-direction: column; - align-items: stretch; - gap: 10px; - } -} .account-recovery-form .form-label { - display: flex; - align-items: center; - flex-shrink: 0; - width: 170px; - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - font-size: 15px; - font-weight: 400; - color: #212529; - margin-bottom: 0; -} -.account-recovery-form .form-label .required { - color: #ed5b5b; - margin-left: 2px; -} -@media (max-width: 768px) { - .account-recovery-form .form-label { - width: 100%; - font-size: 14px; - } + display: none; } .account-recovery-form .form-input { - flex: 1; - height: 40px; + width: 100%; + height: 48px; padding: 0 16px; - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 14px; font-weight: 400; color: #212529; background: #FFFFFF; - border: 1px solid #dadada; - border-radius: 8px; + border: 1px solid #E2E8F0; + border-radius: 10px; outline: none; - transition: all 0.3s ease; + transition: all 0.25s ease; } .account-recovery-form .form-input::placeholder { - color: #dadada; + color: #B0B8C1; } .account-recovery-form .form-input:hover { border-color: #3ba4ed; } .account-recovery-form .form-input:focus { border-color: #3ba4ed; - box-shadow: 0 0 0 2px rgba(59, 164, 237, 0.1); + box-shadow: 0 0 0 3px rgba(59, 164, 237, 0.12); } .account-recovery-form .form-input:disabled { background: #F8F9FA; @@ -10082,24 +11806,24 @@ select.form-control { border-color: #ed5b5b; } .account-recovery-form .form-select { - height: 40px; - padding: 0 32px 0 16px; - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + height: 48px; + padding: 0 24px 0 12px; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 14px; font-weight: 400; color: #515151; background: #FFFFFF; - border: 1px solid #dadada; - border-radius: 8px; + border: 1px solid #E2E8F0; + border-radius: 10px; outline: none; cursor: pointer; - transition: all 0.3s ease; + transition: all 0.25s ease; appearance: none; -webkit-appearance: none; -moz-appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='5' viewBox='0 0 10 5'%3E%3Cpath fill='%231D1B20' d='M5 5L0 0h10z'/%3E%3C/svg%3E"); background-repeat: no-repeat; - background-position: right 12px center; + background-position: right 8px center; background-size: 10px 5px; } .account-recovery-form .form-select:hover { @@ -10107,7 +11831,7 @@ select.form-control { } .account-recovery-form .form-select:focus { border-color: #3ba4ed; - box-shadow: 0 0 0 2px rgba(59, 164, 237, 0.1); + box-shadow: 0 0 0 3px rgba(59, 164, 237, 0.12); } .account-recovery-form .form-select:disabled { background-color: #F8F9FA; @@ -10121,50 +11845,89 @@ select.form-control { font-size: 14px; } +.reset-method-group { + margin-bottom: 16px !important; +} + +.reset-method-tabs { + display: flex; + gap: 8px; + background: #f0f2f5; + border-radius: 10px; + padding: 4px; +} +.reset-method-tabs .reset-method-tab { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + margin: 0; +} +.reset-method-tabs .reset-method-tab input[type=radio] { + display: none; +} +.reset-method-tabs .reset-method-tab span { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + padding: 9px 12px; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-size: 13px; + font-weight: 600; + color: #8c959f; + border-radius: 7px; + transition: all 0.2s ease; + text-align: center; +} +.reset-method-tabs .reset-method-tab input[type=radio]:checked + span { + background: #FFFFFF; + color: #212529; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.09); +} +.reset-method-tabs .reset-method-tab:hover span { + color: #3ba4ed; +} + .phone-input-group { display: flex; align-items: center; - gap: 8px; - flex: 1; + gap: 6px; + width: 100%; } .phone-input-group .phone-prefix { - width: 100px; + width: 90px; + min-width: 90px; flex-shrink: 0; } .phone-input-group .phone-middle, .phone-input-group .phone-last { - width: 100px; - flex-shrink: 0; + flex: 1; + min-width: 0; } .phone-input-group .phone-separator { display: flex; align-items: center; justify-content: center; - width: 8px; - height: 1px; - background: #515151; + color: #B0B8C1; + font-size: 14px; flex-shrink: 0; + width: 10px; + height: auto; + background: transparent; } -@media (max-width: 768px) { +.phone-input-group .phone-separator::before { + content: "-"; +} +@media (max-width: 576px) { .phone-input-group { flex-wrap: wrap; gap: 6px; } - .phone-input-group .phone-prefix, - .phone-input-group .phone-middle, - .phone-input-group .phone-last { + .phone-input-group .phone-prefix { flex: 1; - min-width: 60px; - } - .phone-input-group .phone-separator { - width: 6px; - } -} -@media (max-width: 576px) { - .phone-input-group .phone-prefix, - .phone-input-group .phone-middle, - .phone-input-group .phone-last { - width: 100%; + width: auto; } .phone-input-group .phone-separator { display: none; @@ -10179,92 +11942,97 @@ select.form-control { position: relative; display: flex; align-items: center; - flex: 1; + width: 100%; + gap: 8px; +} +.auth-input-group.mg-y { + margin-top: 20px; + margin-bottom: 12px; } .auth-input-group .auth-input { flex: 1; - padding-right: 60px; + min-width: 0; } .auth-input-group .auth-timer { position: absolute; - right: 16px; + right: 20px; top: 50%; transform: translateY(-50%); - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - font-size: 14px; - font-weight: 400; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-size: 13px; + font-weight: 500; color: #ed5b5b; pointer-events: none; } @media (max-width: 576px) { .auth-input-group .auth-timer { font-size: 12px; - right: 12px; + right: 100px; } } .auth-request-button, .auth-verify-button { - width: 140px; - height: 40px; - padding: 8px; - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - font-size: 14px; + height: 48px; + padding: 0 16px; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-size: 13px; font-weight: 700; color: #FFFFFF; - background: #a4d6ea; + background: #3ba4ed; border: none; - border-radius: 8px; + border-radius: 10px; cursor: pointer; - transition: all 0.3s ease; - margin: 0; + transition: all 0.25s ease; flex-shrink: 0; + white-space: nowrap; line-height: 1; } .auth-request-button:hover, .auth-verify-button:hover { - background: rgb(147.83125, 206.7151785714, 230.26875); + background: rgb(37.3117757009, 153.9304672897, 235.0082242991); + transform: none !important; } .auth-request-button:active, .auth-verify-button:active { - background: rgb(131.6625, 199.4303571429, 226.5375); + background: rgb(21.411588785, 146.3125233645, 233.148411215); } .auth-request-button:disabled, .auth-verify-button:disabled { - opacity: 0.6; + opacity: 0.5; cursor: not-allowed; } @media (max-width: 576px) { .auth-request-button, .auth-verify-button { width: 100%; - height: 40px; + height: 44px; font-size: 13px; - margin-top: 8px; + margin-top: 4px; } } .account-recovery-card .form-actions { display: flex; justify-content: center; - gap: 20px; - margin-top: 32px; - padding: 32px 0; + gap: 12px; + margin-top: 28px; + padding: 0; border-top: none; background: transparent; } .account-recovery-card .form-actions .cancel-button, .account-recovery-card .form-actions .submit-button { - width: 160px; - height: 40px; - padding: 8px; - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - font-size: 14px; + flex: 1; + height: 48px; + padding: 0 8px; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-size: 15px; font-weight: 700; border: none; - border-radius: 8px; + border-radius: 10px; cursor: pointer; - transition: all 0.3s ease; + transition: all 0.25s ease; line-height: 1; display: flex; align-items: center; @@ -10273,20 +12041,20 @@ select.form-control { } .account-recovery-card .form-actions .cancel-button { color: #5f666c; - background: #e5e7eb; + background: #f0f2f5; } .account-recovery-card .form-actions .cancel-button:hover { - background: rgb(215.8869565217, 218.8956521739, 224.9130434783); + background: rgb(225.45, 229.39, 235.3); } .account-recovery-card .form-actions .cancel-button:active { - background: rgb(202.7739130435, 206.7913043478, 214.8260869565); + background: rgb(210.9, 216.78, 225.6); } .account-recovery-card .form-actions .submit-button { color: #FFFFFF; background: #0049B4; } .account-recovery-card .form-actions .submit-button:hover { - background: rgb(0, 69.35, 171); + background: rgb(6, 54, 125); } .account-recovery-card .form-actions .submit-button:active { background: rgb(0, 65.7, 162); @@ -10297,15 +12065,12 @@ select.form-control { } @media (max-width: 576px) { .account-recovery-card .form-actions { - flex-direction: column; gap: 10px; - padding: 20px 0; } .account-recovery-card .form-actions .cancel-button, .account-recovery-card .form-actions .submit-button { - width: 100%; - height: 40px; - font-size: 13px; + height: 44px; + font-size: 14px; } } @@ -10361,8 +12126,7 @@ select.form-control { } .account-recovery-result { width: 100%; - background: #F6F9FB; - padding: 60px 40px; + padding: 35px 0px; border-radius: 0 0 12px 12px; text-align: center; } @@ -10386,9 +12150,7 @@ select.form-control { } } .account-recovery-result .result-header .result-title { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 24px; - font-weight: 700; color: #212529; margin-bottom: 12px; } @@ -10398,7 +12160,7 @@ select.form-control { } } .account-recovery-result .result-header .result-description { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 16px; font-weight: 400; color: #5f666c; @@ -10447,7 +12209,7 @@ select.form-control { } } .found-users-list .found-user-item .user-email { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 20px; font-weight: 700; color: #212529; @@ -10458,7 +12220,7 @@ select.form-control { } } .found-users-list .found-user-item .user-date { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 16px; font-weight: 400; color: #8c959f; @@ -10477,8 +12239,39 @@ select.form-control { border-radius: 12px; padding: 16px 24px; } +.result-info-box.info-box-highlight { + background: linear-gradient(135deg, rgba(0, 73, 180, 0.06) 0%, rgba(0, 73, 180, 0.02) 100%); + border: 1.5px solid rgba(0, 73, 180, 0.25); + box-shadow: 0 8px 24px rgba(0, 73, 180, 0.08); + display: flex; + align-items: center; + gap: 12px; + animation: fadeInUp 0.6s cubic-bezier(0.16, 1, 0.3, 1) both; + transform: translateY(15px); + opacity: 0; +} +.result-info-box.info-box-highlight .highlight-icon { + display: flex; + justify-content: center; + align-items: center; + animation: pulseIcon 2s infinite ease-in-out; +} +.result-info-box.info-box-highlight .info-text { + font-size: 16px; + line-height: 1.6; + color: #1e293b; + text-align: center; +} +.result-info-box.info-box-highlight .info-text strong { + color: #0049B4; + font-weight: 700; + font-size: 19px; +} +.result-info-box.info-box-highlight .info-text p { + color: #4685ef; +} .result-info-box .info-text { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 15px; font-weight: 400; color: #5f666c; @@ -10509,134 +12302,84 @@ select.form-control { } } +@keyframes fadeInUp { + to { + transform: translateY(0); + opacity: 1; + } +} +@keyframes pulseIcon { + 0%, 100% { + transform: scale(1); + filter: drop-shadow(0 0 0px rgba(0, 73, 180, 0)); + } + 50% { + transform: scale(1.06); + filter: drop-shadow(0 0 6px rgba(0, 73, 180, 0.2)); + } +} @media (max-width: 768px) { .account-recovery-page { padding: 0; margin: 0; } .account-recovery-container { - padding: 20px; + padding: 16px; } .account-recovery-card { - padding: 0; + padding: 24px 16px 20px; + border-radius: 16px; + } + .account-recovery-tabs { + margin-bottom: 20px; } .account-recovery-tabs .tab-link { - height: 40px; - padding: 10px 16px; - font-size: 14px; - font-weight: 700; - } - .account-recovery-tabs .tab-link:first-child { - border-radius: 8px 0 0 0; - } - .account-recovery-tabs .tab-link:last-child { - border-radius: 0 8px 0 0; - } - .account-recovery-form { - padding: 20px; - border-radius: 0 0 8px 8px; + padding: 9px 12px; + font-size: 13px; } .account-recovery-form .form-group { - flex-direction: column; - align-items: stretch; - gap: 8px; - margin-bottom: 16px; - } - .account-recovery-form .form-label { - width: 100%; - font-size: 14px; - font-weight: 500; - margin-bottom: 0; + margin-bottom: 10px; } .account-recovery-form .form-input { - flex: none; - width: 100%; - height: 40px; - padding: 0 16px; - font-size: 12px; - border-radius: 8px; - } - .account-recovery-form .form-input::placeholder { - color: #8c959f; + height: 44px; + font-size: 13px; } .account-recovery-form .form-select { - height: 40px; - padding: 0 32px 0 12px; - font-size: 12px; - border-radius: 8px; - background-position: right 10px center; + height: 44px; + font-size: 13px; } .phone-input-group { flex-wrap: wrap; - gap: 8px; - align-items: center; - width: 100%; + gap: 6px; } .phone-input-group .phone-prefix { flex: 1; - min-width: 0; - width: auto; - } - .phone-input-group .phone-middle, - .phone-input-group .phone-last { - flex: 1; - min-width: 0; width: auto; } .phone-input-group .phone-separator { - display: flex; - width: auto; - height: auto; - background: transparent; - font-size: 12px; - color: #000; - } - .phone-input-group .phone-separator::before { - content: "-"; - } - .auth-input-group { - width: 100%; - } - .auth-input-group .auth-input { - width: 100%; - padding-right: 60px; + display: none; } .auth-input-group .auth-timer { font-size: 12px; - right: 12px; + right: 18px; } .auth-request-button, .auth-verify-button { width: 100%; - height: 40px; - padding: 10px; - font-size: 14px; - font-weight: 700; - border-radius: 8px; - margin-top: 8px; + height: 44px; + font-size: 13px; + margin-top: 4px; } .account-recovery-card .form-actions { flex-direction: row; - gap: 21px; - margin-top: 24px; - padding: 24px 0; + gap: 10px; + margin-top: 20px; } .account-recovery-card .form-actions .cancel-button, .account-recovery-card .form-actions .submit-button { flex: 1; - min-width: 144px; - height: 40px; + height: 44px; font-size: 14px; - font-weight: 700; - border-radius: 8px; - } - .account-recovery-card .form-actions .cancel-button { - background: #e5e7eb; - color: #5f666c; - } - .account-recovery-card .form-actions .submit-button { - background: #0049b4; - color: #ffffff; } .account-recovery-result { padding: 30px 20px; @@ -10679,16 +12422,14 @@ select.form-control { } } .signup-selection-page { - min-height: calc(100vh - 140px); display: flex; align-items: center; justify-content: center; - background: #EDF9FE; - padding: 30px 20px; + padding: 100px 20px; position: relative; border-radius: 12px; - margin-top: 30px; - margin-bottom: 30px; + margin-top: 60px; + margin-bottom: 50px; } .signup-selection-container { @@ -10711,104 +12452,195 @@ select.form-control { } } -.signup-logo { - width: 90px; - height: 90px; - margin-bottom: 15px; -} -.signup-logo img { +.signup-header { + display: flex; + flex-direction: column; + align-items: center; + margin-bottom: 40px; width: 100%; - height: 100%; - object-fit: contain; +} + +.signup-title-row { + display: flex; + align-items: center; + justify-content: center; + gap: 12px; + margin-bottom: 12px; + color: #000000; +} +.signup-title-row .signup-title-icon { + color: #000000; + flex-shrink: 0; +} +.signup-title-row .signup-title { + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-size: 36px; + font-weight: 700; + color: #000000; + margin: 0; + letter-spacing: -0.5px; } .signup-message { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - font-size: 20px; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-size: 16px; font-weight: 400; - color: #000000; + color: #718096; text-align: center; - margin-bottom: 20px; - line-height: 1; + margin: 0; + line-height: 1.5; } -.signup-buttons { +.signup-cards { width: 100%; - max-width: 504px; + max-width: 760px; + display: flex; + flex-direction: row; + gap: 28px; + margin-bottom: 45px; + justify-content: center; +} + +.signup-card-item { + flex: 1; + background: #FFFFFF; + border: 1px solid rgba(226, 232, 240, 0.8); + border-radius: 24px; + padding: 45px 30px; display: flex; flex-direction: column; - gap: 20px; - margin-bottom: 30px; -} - -.signup-btn { - width: 100%; - height: 80px; - display: flex; align-items: center; - justify-content: center; - gap: 10px; - padding: 10px; - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - font-size: 20px; - font-weight: 700; - color: #FFFFFF; + text-align: center; text-decoration: none; - border-radius: 12px; + transition: all 0.4s cubic-bezier(0.16, 1, 0.3, 1); + color: #1A202C; cursor: pointer; - transition: all 0.3s ease; - line-height: 1; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.03); position: relative; + overflow: hidden; } -.signup-btn:hover { - transform: translateY(-2px); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); +.signup-card-item::before { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: radial-gradient(circle at top right, rgba(255, 255, 255, 0.8), transparent 70%); + opacity: 0; + transition: opacity 0.4s ease; + pointer-events: none; } -.signup-btn:active { - transform: translateY(0); - box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); -} -.signup-btn .signup-btn-icon { - width: 26px; - height: 30px; - flex-shrink: 0; -} -.signup-btn span { - white-space: nowrap; -} - -.signup-btn-individual { - background: #0049B4; -} - -.signup-btn-organization { - background: #00A1D7; -} - -.signup-navigation { +.signup-card-item .card-icon-wrapper { + width: 76px; + height: 76px; + border-radius: 22px; display: flex; - justify-content: center; align-items: center; - gap: 26px; - flex-wrap: wrap; + justify-content: center; + margin-bottom: 24px; + transition: all 0.4s cubic-bezier(0.16, 1, 0.3, 1); + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.02); } -.signup-navigation .signup-nav-link { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - font-size: 16px; - font-weight: 400; - color: #000000; - text-decoration: none; - transition: color 0.3s ease; - line-height: 1; +.signup-card-item .card-text-wrapper { + display: flex; + flex-direction: column; + align-items: center; } -.signup-navigation .signup-nav-link:hover { +.signup-card-item .card-title { + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-size: 22px; + font-weight: 800; + margin-top: 0; + margin-bottom: 12px; + line-height: 1.3; + color: #1A202C; + transition: all 0.4s ease; +} +.signup-card-item .card-desc { + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-size: 14px; + color: #718096; + line-height: 1.6; + margin: 0; + max-width: 250px; + word-break: keep-all; + transition: color 0.4s ease; +} +.signup-card-item:hover { + transform: translateY(-8px); + background: #FFFFFF; +} +.signup-card-item:hover::before { + opacity: 1; +} +.signup-card-item.individual-card .card-icon-wrapper { + background: linear-gradient(135deg, #F0F5FF 0%, #D8E5FF 100%); color: #0049B4; - text-decoration: underline; } -.signup-navigation .signup-nav-separator { - color: #000000; - font-size: 16px; - line-height: 1; +.signup-card-item.individual-card:hover { + border-color: #0049B4; + box-shadow: 0 20px 40px rgba(0, 73, 180, 0.12); +} +.signup-card-item.individual-card:hover .card-title { + color: #0049B4; +} +.signup-card-item.individual-card:hover .card-icon-wrapper { + background: linear-gradient(135deg, #0049B4 0%, #0066FF 100%); + color: #FFFFFF; + transform: scale(1.05) rotate(3deg); + box-shadow: 0 10px 20px rgba(0, 73, 180, 0.25); +} +.signup-card-item.organization-card .card-icon-wrapper { + background: linear-gradient(135deg, #EBF8FF 0%, #CAF0F8 100%); + color: #00A1D7; +} +.signup-card-item.organization-card:hover { + border-color: #00A1D7; + box-shadow: 0 20px 40px rgba(0, 161, 215, 0.12); +} +.signup-card-item.organization-card:hover .card-title { + color: #00A1D7; +} +.signup-card-item.organization-card:hover .card-icon-wrapper { + background: linear-gradient(135deg, #00A1D7 0%, #33C2FF 100%); + color: #FFFFFF; + transform: scale(1.05) rotate(-3deg); + box-shadow: 0 10px 20px rgba(0, 161, 215, 0.25); +} + +.signup-navigation-box { + display: flex; + align-items: center; + background: #EDF4FF; + border: 1px solid #0049B4; + border-radius: 8px; + overflow: hidden; + height: 42px; + max-width: 280px; + width: 100%; + margin: 0 auto; +} +.signup-navigation-box .signup-nav-btn { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-size: 14px; + font-weight: 700; + color: #0049B4; + text-decoration: none; + height: 100%; + transition: all 0.2s ease; +} +.signup-navigation-box .signup-nav-btn:hover { + background: #DCE7FF; +} +.signup-navigation-box .signup-nav-divider { + width: 1px; + height: 100%; + background: #0049B4; } @media (max-width: 768px) { @@ -10824,59 +12656,70 @@ select.form-control { max-width: 100%; padding: 0; } - .signup-selection-card { - padding: 0; + .signup-header { + margin-bottom: 30px; } - .signup-logo { - width: 64px; - height: 64px; - margin-bottom: 24px; + .signup-header .signup-title-row { + margin-bottom: 8px; + } + .signup-header .signup-title-row .signup-title-icon { + width: 24px; + height: 24px; + } + .signup-header .signup-title-row .signup-title { + font-size: 24px; } .signup-message { font-size: 14px; font-weight: 400; color: #212529; - margin-bottom: 80px; - line-height: 26px; + margin-bottom: 40px; + line-height: 22px; } - .signup-buttons { + .signup-cards { max-width: 335px; - gap: 24px; - margin-bottom: 80px; + flex-direction: column; + gap: 16px; + margin-bottom: 40px; } - .signup-btn { + .signup-card-item { + padding: 24px 20px; + border-radius: 12px; + flex-direction: row; + text-align: left; + align-items: center; + gap: 16px; + } + .signup-card-item .card-icon-wrapper { + width: 44px; height: 44px; - font-size: 16px; - font-weight: 700; - border-radius: 8px; - padding: 8px 12px; - justify-content: center; - gap: 8px; - } - .signup-btn svg { - width: 14px; - height: 16px; + margin-bottom: 0; flex-shrink: 0; } - .signup-btn span { - text-align: center; + .signup-card-item .card-icon-wrapper svg { + width: 20px; + height: 20px; } - .signup-btn:hover { + .signup-card-item .card-text-wrapper { + align-items: flex-start; + } + .signup-card-item .card-title { + font-size: 16px; + margin-bottom: 4px; + } + .signup-card-item .card-desc { + font-size: 12px; + } + .signup-card-item:hover { transform: none; box-shadow: none; - opacity: 0.9; } - .signup-navigation { - gap: 12px; + .signup-navigation-box { + max-width: 260px; + height: 38px; } - .signup-navigation .signup-nav-link { - font-size: 14px; - font-weight: 400; - color: #515961; - } - .signup-navigation .signup-nav-separator { - font-size: 14px; - color: #515961; + .signup-navigation-box .signup-nav-btn { + font-size: 13px; } } .corporate-transfer-section { @@ -10887,6 +12730,313 @@ select.form-control { width: 100%; } +.corp-manager-wrapper, +.password-change-wrapper { + width: 100%; + margin: 0 auto; +} +.corp-manager-wrapper .page-outer-title, +.password-change-wrapper .page-outer-title { + font-size: 24px; + font-weight: 700; + color: #000; + margin-bottom: 24px; +} +.corp-manager-wrapper .register-form-container, +.password-change-wrapper .register-form-container { + background: #fff; + border: 1px solid #dfdfdf; + border-radius: 30px; + box-shadow: 0px 4px 26.9px 2px rgba(192, 192, 192, 0.25); + padding: 40px 48px; +} +.corp-manager-wrapper .common-title-bar, +.password-change-wrapper .common-title-bar { + display: none; +} +.corp-manager-wrapper .inner-section-title, +.password-change-wrapper .inner-section-title { + display: flex; + align-items: center; + gap: 15px; + border-bottom: 1px solid #818181; + padding-bottom: 12px; + margin-bottom: 32px; +} +.corp-manager-wrapper .inner-section-title h3, +.password-change-wrapper .inner-section-title h3 { + font-size: 18px; + font-weight: 700; + color: #0d0e11; + margin: 0; +} +.corp-manager-wrapper .inner-section-title svg, +.password-change-wrapper .inner-section-title svg { + width: 17px; + height: 17px; +} +.corp-manager-wrapper .form-row, +.password-change-wrapper .form-row { + display: flex; + flex-direction: column; + gap: 13px; + margin-bottom: 26px; +} +.corp-manager-wrapper .form-row.two-columns, +.password-change-wrapper .form-row.two-columns { + flex-direction: row; + gap: 34px; +} +.corp-manager-wrapper .form-row.two-columns > div, +.password-change-wrapper .form-row.two-columns > div { + flex: 1; + display: flex; + flex-direction: column; + gap: 13px; +} +@media (max-width: 768px) { + .corp-manager-wrapper .form-row.two-columns, + .password-change-wrapper .form-row.two-columns { + flex-direction: column; + gap: 26px; + } +} +.corp-manager-wrapper .form-label-wrapper, +.password-change-wrapper .form-label-wrapper { + margin: 0; + padding: 0; + width: auto; +} +.corp-manager-wrapper .form-field-wrapper, +.password-change-wrapper .form-field-wrapper { + width: 100%; +} +.corp-manager-wrapper #authNumberContainer .auth-input-group, +.password-change-wrapper #authNumberContainer .auth-input-group { + flex: none !important; + width: 440px !important; +} +.corp-manager-wrapper #authNumberContainer .input-with-button, +.password-change-wrapper #authNumberContainer .input-with-button { + justify-content: space-between; +} +.corp-manager-wrapper #authNumber, +.password-change-wrapper #authNumber { + width: 100% !important; +} +.corp-manager-wrapper .form-label-text, +.password-change-wrapper .form-label-text { + font-size: 14px; + font-weight: 700; + color: #000; +} +.corp-manager-wrapper .required-badge, +.password-change-wrapper .required-badge { + visibility: hidden; + position: relative; + width: 10px; +} +.corp-manager-wrapper .required-badge::after, +.password-change-wrapper .required-badge::after { + content: "*"; + visibility: visible; + position: absolute; + left: 4px; + top: 0; + color: #f4253c; + font-size: 16px; + font-weight: 700; +} +.corp-manager-wrapper .form-input, +.corp-manager-wrapper .form-select, +.corp-manager-wrapper .input-readonly, +.corp-manager-wrapper .info-value-box, +.password-change-wrapper .form-input, +.password-change-wrapper .form-select, +.password-change-wrapper .input-readonly, +.password-change-wrapper .info-value-box { + height: 47px; + background: #fcfcfc; + border: 1px solid #dfdfdf; + border-radius: 10px; + font-size: 16px; + color: #0d0e11; + padding: 0 20px; + width: 100%; + box-sizing: border-box; +} +.corp-manager-wrapper .form-input:disabled, +.corp-manager-wrapper .input-readonly, +.password-change-wrapper .form-input:disabled, +.password-change-wrapper .input-readonly { + background: #ededed !important; +} +.corp-manager-wrapper .compound-input, +.password-change-wrapper .compound-input { + display: flex; + align-items: center; + gap: 9px; + width: 100%; +} +.corp-manager-wrapper .compound-input .form-input, +.corp-manager-wrapper .compound-input .form-select, +.password-change-wrapper .compound-input .form-input, +.password-change-wrapper .compound-input .form-select { + flex: 1; + text-align: center; + padding: 0; +} +.corp-manager-wrapper .separator, +.password-change-wrapper .separator { + font-size: 16px; + color: #000; +} +.corp-manager-wrapper .input-with-button, +.password-change-wrapper .input-with-button { + display: flex; + align-items: center; + gap: 9px; +} +.corp-manager-wrapper .btn-change, +.corp-manager-wrapper .btn-auth, +.password-change-wrapper .btn-change, +.password-change-wrapper .btn-auth { + height: 47px; + background: #d5efff; + color: #2a69de; + font-size: 16px; + font-weight: 500; + border: none; + border-radius: 10px; + padding: 0 20px; + white-space: nowrap; + cursor: pointer; +} +.corp-manager-wrapper .auth-input-group, +.password-change-wrapper .auth-input-group { + display: flex; + align-items: center; + position: relative; + flex: 1; +} +.corp-manager-wrapper .auth-input-group .form-input, +.password-change-wrapper .auth-input-group .form-input { + padding-right: 70px; +} +.corp-manager-wrapper .auth-timer, +.password-change-wrapper .auth-timer { + position: absolute; + right: 20px; + color: #f4253c; + font-size: 16px; + font-weight: 500; +} +.corp-manager-wrapper .form-actions, +.password-change-wrapper .form-actions { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 0; +} +.corp-manager-wrapper .btn-withdrawal, +.password-change-wrapper .btn-withdrawal { + height: 45px; + background: #ff615c; + color: #fff; + font-size: 16px; + font-weight: 500; + border: none; + border-radius: 10px; + padding: 0 20px; + cursor: pointer; + width: 156px; + display: flex; + justify-content: center; + align-items: center; + text-decoration: none; +} +.corp-manager-wrapper .right-buttons, +.password-change-wrapper .right-buttons { + display: flex; + gap: 8px; +} +.corp-manager-wrapper .btn-cancel, +.password-change-wrapper .btn-cancel { + height: 45px; + background: #bdc7cf; + color: #fff; + font-size: 16px; + font-weight: 500; + border: none; + border-radius: 10px; + padding: 0 20px; + cursor: pointer; + width: 156px; + justify-content: center; +} +.corp-manager-wrapper .btn-apply, +.password-change-wrapper .btn-apply { + height: 45px; + background: #2a69de; + color: #fff; + font-size: 16px; + font-weight: 500; + border: none; + border-radius: 10px; + padding: 0 20px; + cursor: pointer; + width: 156px; +} +.corp-manager-wrapper .info-notice-box, +.password-change-wrapper .info-notice-box { + background: #ecf0fa; + border-radius: 7px; + padding: 13px 14px 12px; + display: flex; + align-items: center; + gap: 13px; + margin-bottom: 32px; +} +.corp-manager-wrapper .info-notice-box svg, +.password-change-wrapper .info-notice-box svg { + width: 24px; + height: 24px; + flex-shrink: 0; + color: #4685ef; +} +.corp-manager-wrapper .info-notice-box p, +.password-change-wrapper .info-notice-box p { + color: #4685ef; + font-size: 16px; + font-weight: 500; + margin: 0; +} +.corp-manager-wrapper .info-notice-box.danger, +.password-change-wrapper .info-notice-box.danger { + background: #fdf3f3; +} +.corp-manager-wrapper .info-notice-box.danger p, +.password-change-wrapper .info-notice-box.danger p { + color: #f4253c; +} +.corp-manager-wrapper .password-rules-list, +.password-change-wrapper .password-rules-list { + list-style-type: disc; + padding-left: 24px; + margin: 20px 0 0 0; +} +.corp-manager-wrapper .password-rules-list li, +.password-change-wrapper .password-rules-list li { + color: #888; + font-size: 16px; + line-height: 20px; + margin-bottom: 5px; +} +.corp-manager-wrapper .password-rules-list li:last-child, +.password-change-wrapper .password-rules-list li:last-child { + margin-bottom: 0; +} + .register-title-bar { padding: 18px 45px; display: flex; @@ -11003,65 +13153,6 @@ select.form-control { align-items: center; gap: 8px; } -.progress-step-item .step-icon-wrapper { - padding: 10px; -} -@media (max-width: 768px) { - .progress-step-item .step-icon-wrapper { - padding: 10px; - } -} -.progress-step-item .step-icon { - width: 50px; - height: 50px; - border-radius: 50%; - background-color: #e6e6e6; - display: flex; - align-items: center; - justify-content: center; - color: #999; -} -@media (max-width: 768px) { - .progress-step-item .step-icon { - width: 40px; - height: 40px; - } -} -.progress-step-item .step-icon .step-icon-img { - width: 28px; - height: 28px; -} -@media (max-width: 768px) { - .progress-step-item .step-icon .step-icon-img { - width: 22px; - height: 22px; - } -} -.progress-step-item.active .step-icon { - background-color: #0049b4; - color: #FFFFFF; -} -.progress-step-item .step-label { - font-size: 14px; - font-weight: 400; - color: #dadada; - text-align: center; - white-space: nowrap; -} -@media (max-width: 768px) { - .progress-step-item .step-label { - font-size: 10px; - } -} -.progress-step-item.active .step-label { - font-weight: 700; - color: #0049b4; -} -@media (max-width: 768px) { - .progress-step-item.active .step-label { - font-size: 10px; - } -} .register-form-container { padding: 30px 30px; @@ -11088,16 +13179,16 @@ select.form-control { .register-form-container .form-input { width: 100%; padding: 8px 12px; - border: 1px solid #dadada; + border: 1px solid #DFDFDF; border-radius: 8px; - background-color: #FFFFFF; + background-color: #fcfcfc; font-size: 14px; color: #1A1A2E; outline: none; transition: border-color 0.2s ease; } .register-form-container .form-input::placeholder { - color: #dadada; + color: #94A3B8; } .register-form-container .form-input:focus { border-color: #0049b4; @@ -11133,83 +13224,6 @@ select.form-control { } } -.icon-upload-box { - background-color: #FFFFFF; - border: 1px solid #dadada; - border-radius: 12px; - padding: 24px 20px; - display: flex; - flex-direction: column; - align-items: center; - gap: 16px; -} -@media (max-width: 1024px) { - .icon-upload-box { - padding: 20px 16px; - } -} - -.icon-preview-area { - width: 80px; - height: 80px; - position: relative; -} -.icon-preview-area img { - width: 100%; - height: 100%; - object-fit: cover; - border-radius: 8px; -} - -.icon-placeholder { - width: 100%; - height: 100%; - display: flex; - align-items: center; - justify-content: center; - border-radius: 8px; - color: #999; -} - -.icon-upload-box .btn-remove-icon { - position: absolute; - top: -8px; - right: -8px; - width: 24px; - height: 24px; - border-radius: 50%; - background-color: #dc3545; - color: #FFFFFF; - border: none; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - transition: background-color 0.2s ease; -} -.icon-upload-box .btn-remove-icon:hover { - background-color: #c82333; -} - -.icon-upload-info { - text-align: center; -} -.icon-upload-info .upload-title { - font-size: 14px; - font-weight: 600; - color: #515961; - margin: 0 0 8px; -} -.icon-upload-info .upload-hint { - font-size: 13px; - font-weight: 400; - color: #515961; - margin: 0; -} -.icon-upload-info .upload-hint strong { - font-weight: 600; -} - .ip-input-row { display: flex; gap: 21px; @@ -11373,22 +13387,6 @@ select.form-control { padding: 0; } -.register-progress { - margin-bottom: 48px; - padding: 0 24px; -} -@media (max-width: 768px) { - .register-progress { - margin-bottom: 32px; - margin-top: 8px; - } -} -.register-progress .progress-steps { - display: flex; - align-items: center; - justify-content: center; - position: relative; -} .register-progress .progress-step { display: flex; flex-direction: column; @@ -11441,9 +13439,6 @@ select.form-control { margin-bottom: 28px; position: relative; } -.register-progress .progress-line.filled { - background: #6BCF7F; -} .register-header { text-align: center; @@ -11494,7 +13489,7 @@ select.form-control { transform: translateX(-100%); transition: transform 0.3s ease; box-shadow: 0 8px 24px rgba(75, 155, 255, 0.15); - z-index: 500; + z-index: 1050; height: calc(100vh - 60px); max-height: none; margin: 0; @@ -11600,7 +13595,7 @@ select.form-control { color: #FFFFFF; font-size: 24px; cursor: pointer; - z-index: 501; + z-index: 1051; transition: all 0.3s ease; } .apikey-mobile-toggle:hover { @@ -11626,7 +13621,7 @@ select.form-control { right: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.5); - z-index: 499; + z-index: 1049; opacity: 0; transition: opacity 0.3s ease; pointer-events: none; @@ -11960,54 +13955,82 @@ input[type=checkbox]:checked + .custom-checkbox { margin-bottom: 32px; } .register-form .form-group { - margin-bottom: 32px; + display: flex !important; + flex-direction: column !important; + gap: 18px !important; + margin-bottom: 30px !important; } .register-form .form-group:last-child { - margin-bottom: 0; + margin-bottom: 0 !important; } .register-form .form-label { - display: block; - font-size: 14px; - font-weight: 600; - color: #1A1A2E; - margin-bottom: 8px; + font-size: 16px !important; + font-weight: 500 !important; + color: #000 !important; + line-height: 20px !important; + margin: 0 !important; + display: block !important; } -.register-form .form-label.required .required-mark { - color: #FF6B6B; - margin-left: 4px; +.register-form .form-label .required-star { + color: #f4253c !important; + font-size: 16px !important; + margin-left: 2px !important; } .register-form .form-input, -.register-form .form-select, -.register-form .form-textarea { - width: 100%; - padding: 12px 16px; - border: 1px solid #E2E8F0; - border-radius: 8px; - font-size: 16px; - transition: all 0.3s ease; - background: #FFFFFF; +.register-form .form-select { + width: 100% !important; + height: 47px !important; + background: #fcfcfc !important; + border: 1px solid #dfdfdf !important; + border-radius: 10px !important; + padding: 0 21px !important; + font-size: 16px !important; + font-weight: 300 !important; + color: #000 !important; + box-sizing: border-box !important; + box-shadow: none !important; + transition: border-color 0.2s !important; } .register-form .form-input:focus, -.register-form .form-select:focus, -.register-form .form-textarea:focus { - outline: none; - border-color: #0049b4; - box-shadow: 0 0 0 3px rgba(0, 73, 180, 0.1); +.register-form .form-select:focus { + outline: none !important; + border-color: #2a69de !important; + box-shadow: none !important; } .register-form .form-input::placeholder, -.register-form .form-select::placeholder, -.register-form .form-textarea::placeholder { - color: #94A3B8; +.register-form .form-select::placeholder { + color: #7f8a95 !important; + font-weight: 300 !important; } .register-form .form-input:disabled, -.register-form .form-select:disabled, -.register-form .form-textarea:disabled { - background-color: #E2E8F0; +.register-form .form-select:disabled { + background-color: #ededed !important; } .register-form .form-textarea { - resize: vertical; - min-height: 100px; - font-family: inherit; + width: 100% !important; + height: 299px !important; + background: #fcfcfc !important; + border: 1px solid #dfdfdf !important; + border-radius: 10px !important; + padding: 21px !important; + font-size: 16px !important; + font-weight: 300 !important; + color: #000 !important; + box-sizing: border-box !important; + resize: none !important; + box-shadow: none !important; + min-height: unset !important; + font-family: inherit !important; + transition: border-color 0.2s !important; +} +.register-form .form-textarea:focus { + outline: none !important; + border-color: #2a69de !important; + box-shadow: none !important; +} +.register-form .form-textarea::placeholder { + color: #7f8a95 !important; + font-weight: 300 !important; } .register-form .form-hint { display: block; @@ -12342,6 +14365,839 @@ input[type=checkbox]:checked + .custom-checkbox { margin-bottom: 24px; } } +.register-result .result-icon-wrapper { + /* ========================================================================== + Figma Style Overrides for App Register (Step 1) - node 451-828 + All values extracted directly from Figma MCP + ========================================================================== */ + /* Container */ +} +.register-result .result-icon-wrapper .apikey-register-container { + max-width: 980px !important; + margin: 0 !important; + padding: 0 !important; + background: transparent !important; + box-shadow: none !important; + border: none !important; +} +.register-result .result-icon-wrapper { + /* Title: "앱생성" (Figma: node 451-836, Spoqa Han Sans Neo Bold, 36px, #000) */ +} +.register-result .result-icon-wrapper .common-title-bar, .register-result .result-icon-wrapper .register-title-bar { + text-align: left !important; + margin-bottom: 24px !important; + border-bottom: none !important; + padding: 0 !important; + background: transparent !important; +} +.register-result .result-icon-wrapper .common-title-bar .common-title, .register-result .result-icon-wrapper .register-title-bar .common-title { + font-size: 36px !important; + font-weight: 700 !important; + color: #000 !important; + line-height: 1 !important; + margin: 0 !important; +} +.register-result .result-icon-wrapper { + /* Progress Bar (Figma: node 451-837, white box, 217px tall, rounded-20px, shadow) */ +} +.register-result .result-icon-wrapper .register-progress { + background: #ffffff !important; + border-radius: 20px !important; + box-shadow: 0px 4px 13.45px rgba(192, 192, 192, 0.25) !important; + height: 217px !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + margin-bottom: 24px !important; + padding: 0 !important; +} +.register-result .result-icon-wrapper .register-progress .progress-steps { + display: flex !important; + align-items: center !important; + gap: 0 !important; +} +.register-result .result-icon-wrapper .register-progress { + /* Step item: 71px wide, 99px tall (Figma values) */ +} +.register-result .result-icon-wrapper .register-progress .progress-step-item { + display: flex !important; + flex-direction: column !important; + align-items: center !important; + width: 71px !important; + position: relative !important; + /* Step icon circle: 55px (Figma: node 452-942) */ +} +.register-result .result-icon-wrapper .register-progress .progress-step-item .step-icon-wrapper { + width: 55px !important; + height: 55px !important; + border-radius: 50% !important; + background: #f0f4fa !important; + border: 1px solid #e2e8f0 !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + margin-bottom: 6px !important; + overflow: hidden !important; +} +.register-result .result-icon-wrapper .register-progress .progress-step-item .step-icon-wrapper .step-icon-img { + width: 27px !important; + height: 27px !important; + object-fit: contain !important; + filter: grayscale(100%) opacity(40%) !important; +} +.register-result .result-icon-wrapper .register-progress .progress-step-item { + /* Active step: solid blue circle (Figma: node 452-942, #2A69DE fill) */ +} +.register-result .result-icon-wrapper .register-progress .progress-step-item.active .step-icon-wrapper { + background: #2a69de !important; + border-color: #2a69de !important; +} +.register-result .result-icon-wrapper .register-progress .progress-step-item.active .step-icon-wrapper .step-icon-img { + filter: brightness(0) invert(1) !important; + /* white icon */ +} +.register-result .result-icon-wrapper .register-progress .progress-step-item { + /* 1단계 label (Figma: Medium 12px, #1b4ab7 active, #64748b inactive) */ +} +.register-result .result-icon-wrapper .register-progress .progress-step-item .step-num-label { + font-size: 12px !important; + font-weight: 500 !important; + color: #64748b !important; + text-align: center !important; + line-height: 20px !important; + margin: 0 !important; +} +.register-result .result-icon-wrapper .register-progress .progress-step-item.active .step-num-label { + color: #1b4ab7 !important; + font-weight: 700 !important; +} +.register-result .result-icon-wrapper .register-progress .progress-step-item { + /* 앱 정보 입력 label (Figma: Medium 14px, #000) */ +} +.register-result .result-icon-wrapper .register-progress .progress-step-item .step-label { + font-size: 14px !important; + font-weight: 500 !important; + color: #000 !important; + text-align: center !important; + line-height: 20px !important; + margin: 0 !important; + white-space: nowrap !important; +} +.register-result .result-icon-wrapper .register-progress .progress-step-item:not(.active) .step-label { + font-weight: 400 !important; +} +.register-result .result-icon-wrapper .register-progress { + /* Connecting line between steps (Figma: node 451-923, 134px wide horizontal line) */ +} +.register-result .result-icon-wrapper .register-progress .step-line { + width: 134px !important; + height: 2px !important; + background-color: #dfdfdf !important; + flex-shrink: 0 !important; + margin: 0 !important; + position: relative !important; + top: -24px !important; + /* align with center of icon */ +} +.register-result .result-icon-wrapper { + /* Form container white card (Figma: node 452-977, border-radius 30px, shadow) */ +} +.register-result .result-icon-wrapper .register-form-container { + background: #ffffff !important; + border: 1px solid #dfdfdf !important; + border-radius: 30px !important; + box-shadow: 0px 4px 26.9px 2px rgba(192, 192, 192, 0.25) !important; + padding: 60px !important; + margin-bottom: 24px !important; +} +.register-result .result-icon-wrapper { + /* Form groups: label then field, vertically stacked, gap 18px, mb 30px */ +} +.register-result .result-icon-wrapper .form-group { + display: flex !important; + flex-direction: column !important; + gap: 18px !important; + margin-bottom: 30px !important; +} +.register-result .result-icon-wrapper .form-group:last-child { + margin-bottom: 0 !important; +} +.register-result .result-icon-wrapper { + /* Labels: Spoqa Medium 16px, black (Figma: node 452-996, 452-1029, etc.) */ +} +.register-result .result-icon-wrapper .form-label { + font-size: 16px !important; + font-weight: 500 !important; + color: #000 !important; + line-height: 20px !important; + margin: 0 !important; + /* Required star (Figma: red #f4253c) */ +} +.register-result .result-icon-wrapper .form-label .required-star { + color: #f4253c !important; + font-size: 16px !important; + margin-left: 2px !important; +} +.register-result .result-icon-wrapper { + /* Text inputs (Figma: node 452-1030, bg #fcfcfc, border #dfdfdf, h 47px, r 10px) */ +} +.register-result .result-icon-wrapper .form-input { + width: 100% !important; + height: 47px !important; + background: #fcfcfc !important; + border: 1px solid #dfdfdf !important; + border-radius: 10px !important; + padding: 0 21px !important; + font-size: 16px !important; + font-weight: 300 !important; + color: #000 !important; + box-sizing: border-box !important; +} +.register-result .result-icon-wrapper .form-input::placeholder { + color: #7f8a95 !important; + font-weight: 300 !important; +} +.register-result .result-icon-wrapper .form-input:focus { + outline: none !important; + border-color: #2a69de !important; +} +.register-result .result-icon-wrapper { + /* Textarea (Figma: node I452:1033;224:519, h 299px, same style) */ +} +.register-result .result-icon-wrapper .form-textarea { + width: 100% !important; + height: 299px !important; + background: #fcfcfc !important; + border: 1px solid #dfdfdf !important; + border-radius: 10px !important; + padding: 21px !important; + font-size: 16px !important; + font-weight: 300 !important; + color: #000 !important; + box-sizing: border-box !important; + resize: none !important; +} +.register-result .result-icon-wrapper .form-textarea::placeholder { + color: #7f8a95 !important; + font-weight: 300 !important; +} +.register-result .result-icon-wrapper .form-textarea:focus { + outline: none !important; + border-color: #2a69de !important; +} +.register-result .result-icon-wrapper { + /* Char counter (Figma: node 452-987, Regular 15px, #64748b, right-aligned) */ +} +.register-result .result-icon-wrapper .char-counter-wrapper { + display: flex !important; + justify-content: flex-end !important; + margin-top: 8px !important; +} +.register-result .result-icon-wrapper .char-counter-wrapper .char-counter { + font-size: 15px !important; + font-weight: 400 !important; + color: #64748b !important; +} +.register-result .result-icon-wrapper { + /* App Icon Upload Box (Figma: node 452-997, h 242px, bg #fcfcfc, border #dfdfdf, r 10px) */ +} +.register-result .result-icon-wrapper .icon-upload-box { + width: 100% !important; + height: 242px !important; + background: #fcfcfc !important; + border: 1px solid #dfdfdf !important; + border-radius: 10px !important; + display: flex !important; + flex-direction: column !important; + align-items: center !important; + justify-content: center !important; + gap: 12px !important; + position: relative !important; + cursor: pointer !important; + transition: border-color 0.2s !important; +} +.register-result .result-icon-wrapper .icon-upload-box.drag-over { + border-color: #2a69de !important; + background: #f0f6ff !important; +} +.register-result .result-icon-wrapper .icon-upload-box { + /* Preview image (shown after upload) */ +} +.register-result .result-icon-wrapper .icon-upload-box .icon-preview-img { + width: 80px !important; + height: 80px !important; + object-fit: cover !important; + border-radius: 8px !important; +} +.register-result .result-icon-wrapper .icon-upload-box { + /* Placeholder icon SVG (Figma: node 452-1001 Group ~47px) */ +} +.register-result .result-icon-wrapper .icon-upload-box .icon-placeholder { + display: flex !important; + align-items: center !important; + justify-content: center !important; +} +.register-result .result-icon-wrapper .icon-upload-box .icon-placeholder svg { + width: 47px !important; + height: 47px !important; +} +.register-result .result-icon-wrapper .icon-upload-box { + /* Title text: Spoqa Medium 16px, black (Figma: node 452-998) */ +} +.register-result .result-icon-wrapper .icon-upload-box .upload-title { + font-size: 16px !important; + font-weight: 500 !important; + color: #000 !important; + text-align: center !important; + margin: 0 !important; + line-height: 20px !important; +} +.register-result .result-icon-wrapper .icon-upload-box { + /* Hint text: Light 13px, #7f8a95 (Figma: node 452-999) */ +} +.register-result .result-icon-wrapper .icon-upload-box .upload-hint { + font-size: 13px !important; + font-weight: 300 !important; + color: #7f8a95 !important; + text-align: center !important; + margin: 0 !important; + line-height: 20px !important; +} +.register-result .result-icon-wrapper .icon-upload-box { + /* Upload button: Figma node 452-1024, bg #2a69de, h 48px, w 157px, r 10px, Medium 16px white */ +} +.register-result .result-icon-wrapper .icon-upload-box .btn-upload { + background: #2a69de !important; + color: #fff !important; + border: none !important; + border-radius: 10px !important; + height: 48px !important; + width: 157px !important; + font-size: 16px !important; + font-weight: 500 !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + cursor: pointer !important; + margin: 0 !important; +} +.register-result .result-icon-wrapper .icon-upload-box .btn-upload:hover { + background: #1b4ab7 !important; +} +.register-result .result-icon-wrapper .icon-upload-box { + /* Remove icon button (X in corner) */ +} +.register-result .result-icon-wrapper .icon-upload-box .btn-remove-icon { + position: absolute !important; + top: 10px !important; + right: 10px !important; + width: 28px !important; + height: 28px !important; + background: rgba(0, 0, 0, 0.5) !important; + border: none !important; + border-radius: 50% !important; + color: #fff !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + cursor: pointer !important; +} +.register-result .result-icon-wrapper .icon-upload-box .btn-remove-icon:hover { + background: rgba(0, 0, 0, 0.7) !important; +} +.register-result .result-icon-wrapper .icon-upload-box { + /* Hidden file input */ +} +.register-result .result-icon-wrapper .icon-upload-box .icon-input { + display: none !important; +} +.register-result .result-icon-wrapper { + /* IP Whitelist input row (Figma: node 452-1066) */ +} +.register-result .result-icon-wrapper .ip-input-row { + display: flex !important; + align-items: center !important; + gap: 10px !important; + width: 100% !important; + margin-bottom: 10px !important; +} +.register-result .result-icon-wrapper .ip-input-row .ip-input { + flex: 1 !important; +} +.register-result .result-icon-wrapper { + /* IP added item row (Figma: node 452-1070, filled display bg #ededed) */ +} +.register-result .result-icon-wrapper .ip-item { + display: flex !important; + align-items: center !important; + gap: 10px !important; + width: 100% !important; + margin-bottom: 10px !important; +} +.register-result .result-icon-wrapper .ip-item .ip-display-filled { + flex: 1 !important; + height: 47px !important; + background: #ededed !important; + border: 1px solid #dfdfdf !important; + border-radius: 10px !important; + padding: 0 21px !important; + font-size: 16px !important; + font-weight: 300 !important; + color: #7f8a95 !important; + display: flex !important; + align-items: center !important; + box-sizing: border-box !important; +} +.register-result .result-icon-wrapper { + /* IP buttons: + 복사 (Figma: node 452-1044, bg #d5efff, color #2a69de, 91px × 47px, r 10px) */ + /* IP buttons: - 제거 (Figma: node 452-1072, bg #fbe7e9, color #bb1026, same size) */ +} +.register-result .result-icon-wrapper .btn-ip { + width: 91px !important; + height: 47px !important; + border: none !important; + border-radius: 10px !important; + font-size: 16px !important; + font-weight: 500 !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + gap: 4px !important; + cursor: pointer !important; + flex-shrink: 0 !important; +} +.register-result .result-icon-wrapper .btn-ip svg { + width: 16px !important; + height: 16px !important; + flex-shrink: 0 !important; +} +.register-result .result-icon-wrapper .btn-ip.btn-ip-add { + background: #d5efff !important; + color: #2a69de !important; +} +.register-result .result-icon-wrapper .btn-ip.btn-ip-add:hover { + background: #c3e5fe !important; +} +.register-result .result-icon-wrapper .btn-ip.btn-ip-remove { + background: #fbe7e9 !important; + color: #bb1026 !important; +} +.register-result .result-icon-wrapper .btn-ip.btn-ip-remove:hover { + background: #f9d2d6 !important; +} +.register-result .result-icon-wrapper { + /* 다음 button container (Figma: node 452-983, right-aligned) */ +} +.register-result .result-icon-wrapper .form-actions { + display: flex !important; + justify-content: flex-end !important; + margin-top: 0 !important; + padding-top: 0 !important; + border-top: none !important; + /* 다음 button: Figma node 452-983, bg #2a69de, h 48px, w 201px, r 10px, Medium 16px white */ +} +.register-result .result-icon-wrapper .form-actions .btn-submit { + background: #2a69de !important; + border: none !important; + border-radius: 10px !important; + width: 201px !important; + height: 48px !important; + color: #fff !important; + font-size: 16px !important; + font-weight: 500 !important; + cursor: pointer !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; +} +.register-result .result-icon-wrapper .form-actions .btn-submit:hover { + background: #1b4ab7 !important; +} +.register-result .result-icon-wrapper { + /* Title */ +} +.register-result .result-icon-wrapper .common-title-bar, .register-result .result-icon-wrapper .register-title-bar { + text-align: left; + margin-bottom: 24px; + border-bottom: none; + padding: 0; +} +.register-result .result-icon-wrapper .common-title-bar .common-title, .register-result .result-icon-wrapper .register-title-bar .common-title { + font-size: 36px; + font-weight: 700; + color: #000; + margin: 0; +} +.register-result .result-icon-wrapper { + /* Progress Indicator */ +} +.register-result .result-icon-wrapper .register-progress { + background: #ffffff; + border-radius: 20px; + box-shadow: 0px 4px 13.45px rgba(192, 192, 192, 0.25); + height: 217px; + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 24px; + padding: 0; +} +.register-result .result-icon-wrapper .register-progress .progress-steps { + display: flex; + align-items: center; + gap: 0; +} +.register-result .result-icon-wrapper .register-progress .progress-step-item { + display: flex; + flex-direction: column; + align-items: center; + position: relative; + width: 140px; +} +.register-result .result-icon-wrapper .register-progress .progress-step-item .step-icon-wrapper { + width: 55px; + height: 55px; + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 6px; + background: #f8fafc; + border-radius: 50%; + border: 1px solid #e2e8f0; +} +.register-result .result-icon-wrapper .register-progress .progress-step-item .step-icon { + display: flex; + align-items: center; + justify-content: center; +} +.register-result .result-icon-wrapper .register-progress .progress-step-item .step-icon img { + width: 27px; + height: 27px; + filter: grayscale(100%) opacity(50%); +} +.register-result .result-icon-wrapper .register-progress .progress-step-item .step-num-label { + font-size: 12px; + font-weight: 500; + color: #64748b; + margin-bottom: 4px; +} +.register-result .result-icon-wrapper .register-progress .progress-step-item .step-label { + font-size: 14px; + font-weight: 400; + color: #000; +} +.register-result .result-icon-wrapper .register-progress .progress-step-item.active .step-icon-wrapper { + background: #2a69de; + border-color: #2a69de; +} +.register-result .result-icon-wrapper .register-progress .progress-step-item.active .step-icon img { + filter: none; +} +.register-result .result-icon-wrapper .register-progress .progress-step-item.active .step-num-label { + color: #1b4ab7; + font-weight: 700; +} +.register-result .result-icon-wrapper .register-progress .progress-step-item.active .step-label { + font-weight: 700; +} +.register-result .result-icon-wrapper .register-progress .step-dots { + display: flex; + align-items: center; + justify-content: center; + width: 134px; + height: 2px; + background-color: #dfdfdf; + margin: 0 10px; + position: relative; + top: -24px; +} +.register-result .result-icon-wrapper .register-progress .step-dots span { + display: none; +} +.register-result .result-icon-wrapper { + /* Form Container */ +} +.register-result .result-icon-wrapper .register-form-container { + background: #ffffff !important; + border: 1px solid #dfdfdf !important; + border-radius: 30px !important; + box-shadow: 0px 4px 26.9px 2px rgba(192, 192, 192, 0.25) !important; + padding: 60px !important; + margin-bottom: 24px !important; +} +.register-result .result-icon-wrapper { + /* Form Rows */ +} +.register-result .result-icon-wrapper .form-row { + display: flex !important; + flex-direction: column !important; + gap: 18px !important; + margin-bottom: 30px !important; + border-bottom: none !important; + padding-bottom: 0 !important; + align-items: flex-start !important; +} +.register-result .result-icon-wrapper .form-row:last-child { + margin-bottom: 0 !important; +} +.register-result .result-icon-wrapper .form-label-wrapper { + display: flex !important; + align-items: center !important; + gap: 4px !important; + width: auto !important; + min-width: 0 !important; + margin-top: 0 !important; +} +.register-result .result-icon-wrapper .form-label-wrapper .form-label-text { + font-size: 16px !important; + font-weight: 700 !important; + color: #000 !important; +} +.register-result .result-icon-wrapper .form-label-wrapper .required-badge { + color: #f4253c !important; + background: none !important; + padding: 0 !important; + border-radius: 0 !important; + font-size: 0 !important; +} +.register-result .result-icon-wrapper .form-label-wrapper .required-badge::after { + content: " *" !important; + font-size: 16px !important; + color: #f4253c !important; +} +.register-result .result-icon-wrapper .form-field-wrapper { + width: 100% !important; +} +.register-result .result-icon-wrapper { + /* Inputs */ +} +.register-result .result-icon-wrapper .form-input { + background: #fcfcfc !important; + border: 1px solid #dfdfdf !important; + border-radius: 10px !important; + height: 47px !important; + padding: 0 20px !important; + font-size: 16px !important; + color: #000 !important; + width: 100% !important; + box-sizing: border-box !important; +} +.register-result .result-icon-wrapper .form-input::placeholder { + color: #7f8a95 !important; + font-weight: 300 !important; +} +.register-result .result-icon-wrapper .form-textarea { + background: #fcfcfc !important; + border: 1px solid #dfdfdf !important; + border-radius: 10px !important; + padding: 20px !important; + font-size: 16px !important; + color: #000 !important; + height: 299px !important; + width: 100% !important; + box-sizing: border-box !important; + resize: none !important; +} +.register-result .result-icon-wrapper .form-textarea::placeholder { + color: #7f8a95 !important; + font-weight: 300 !important; +} +.register-result .result-icon-wrapper .char-counter-wrapper { + display: flex; + justify-content: flex-end; + margin-top: 10px; +} +.register-result .result-icon-wrapper .char-counter-wrapper .char-counter { + font-size: 15px; + color: #64748b; +} +.register-result .result-icon-wrapper { + /* Icon Upload */ +} +.register-result .result-icon-wrapper .icon-upload-box { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 100%; + height: 242px; + background: #fcfcfc; + border: 1px solid #dfdfdf; + border-radius: 10px; + position: relative; + padding: 30px; +} +.register-result .result-icon-wrapper .icon-upload-box .icon-preview-area { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + border: none; + width: 100%; + height: auto; + background: transparent; +} +.register-result .result-icon-wrapper .icon-upload-box .icon-preview-area .icon-placeholder { + margin-bottom: 12px; + color: #2a69de; +} +.register-result .result-icon-wrapper .icon-upload-box .icon-preview-area .icon-placeholder svg { + width: 47px; + height: 47px; +} +.register-result .result-icon-wrapper .icon-upload-box .icon-preview-area .icon-placeholder svg rect { + fill: none; +} +.register-result .result-icon-wrapper .icon-upload-box .icon-preview-area .icon-placeholder svg { + /* Replace base64 image in placeholder with a modern icon */ +} +.register-result .result-icon-wrapper .icon-upload-box .icon-preview-area .icon-placeholder svg path { + fill: #2a69de; +} +.register-result .result-icon-wrapper .icon-upload-box .icon-upload-info { + position: static; + display: flex; + flex-direction: column; + align-items: center; + gap: 6px; + margin-bottom: 16px; + text-align: center; +} +.register-result .result-icon-wrapper .icon-upload-box .icon-upload-info .upload-title { + font-size: 16px; + font-weight: 500; + color: #000; + margin: 0; +} +.register-result .result-icon-wrapper .icon-upload-box .icon-upload-info .upload-hint { + font-size: 13px; + font-weight: 300; + color: #7f8a95; + margin: 0; +} +.register-result .result-icon-wrapper .icon-upload-box .btn-upload { + position: static; + background: #2a69de; + color: #fff; + border-radius: 10px; + border: none; + height: 48px; + padding: 0 26px; + font-size: 16px; + font-weight: 500; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + box-shadow: 0 2px 4px rgba(42, 105, 222, 0.15); +} +.register-result .result-icon-wrapper .icon-upload-box .btn-upload:hover { + background: #1b4ab7; +} +.register-result .result-icon-wrapper { + /* Form Actions */ +} +.register-result .result-icon-wrapper .form-actions { + display: flex !important; + justify-content: flex-end !important; + margin-top: 40px !important; + padding-top: 0 !important; + border-top: none !important; + width: 100% !important; +} +.register-result .result-icon-wrapper .form-actions .btn-submit { + background: #2a69de !important; + border-radius: 10px !important; + border: none !important; + width: 201px !important; + height: 48px !important; + color: #fff !important; + font-size: 16px !important; + font-weight: 500 !important; + cursor: pointer !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + margin-left: auto !important; +} +.register-result .result-icon-wrapper .form-actions .btn-submit:hover { + background: #1b4ab7 !important; +} +.register-result .result-icon-wrapper { + /* IP Whitelist */ +} +.register-result .result-icon-wrapper .ip-input-row, +.register-result .result-icon-wrapper .ip-list .ip-item { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 18px; +} +.register-result .result-icon-wrapper .ip-input-row .form-input.ip-input, +.register-result .result-icon-wrapper .ip-input-row .ip-display, +.register-result .result-icon-wrapper .ip-list .ip-item .form-input.ip-input, +.register-result .result-icon-wrapper .ip-list .ip-item .ip-display { + flex: 1; + background: #fcfcfc; + border: 1px solid #dfdfdf; + border-radius: 10px; + height: 47px; + padding: 0 20px; + font-size: 16px; + color: #000; + display: flex; + align-items: center; +} +.register-result .result-icon-wrapper .ip-input-row .form-input.ip-input::placeholder, +.register-result .result-icon-wrapper .ip-input-row .ip-display::placeholder, +.register-result .result-icon-wrapper .ip-list .ip-item .form-input.ip-input::placeholder, +.register-result .result-icon-wrapper .ip-list .ip-item .ip-display::placeholder { + color: #7f8a95; + font-weight: 300; +} +.register-result .result-icon-wrapper .ip-input-row.ip-item .ip-display, +.register-result .result-icon-wrapper .ip-list .ip-item.ip-item .ip-display { + background: #ededed; + color: #7f8a95; +} +.register-result .result-icon-wrapper .ip-input-row .btn-add-ip, +.register-result .result-icon-wrapper .ip-list .ip-item .btn-add-ip { + background: #d5efff; + color: #2a69de; + border: none; + border-radius: 10px; + width: 91px; + height: 47px; + font-size: 16px; + font-weight: 500; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; +} +.register-result .result-icon-wrapper .ip-input-row .btn-add-ip:hover, +.register-result .result-icon-wrapper .ip-list .ip-item .btn-add-ip:hover { + background: #c3e5fe; +} +.register-result .result-icon-wrapper .ip-input-row .btn-remove-ip, +.register-result .result-icon-wrapper .ip-list .ip-item .btn-remove-ip { + background: #fbe7e9; + color: #bb1026; + border: none; + border-radius: 10px; + width: 91px; + height: 47px; + font-size: 16px; + font-weight: 500; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; +} +.register-result .result-icon-wrapper .ip-input-row .btn-remove-ip:hover, +.register-result .result-icon-wrapper .ip-list .ip-item .btn-remove-ip:hover { + background: #f9d2d6; +} .register-result .result-icon-wrapper .result-icon { margin: 0 auto; display: flex; @@ -12758,6 +15614,1497 @@ input[type=checkbox]:checked + .custom-checkbox { } } +.step1-wrap, +.step2-wrap, +.step3-wrap { + width: 100%; + font-family: "Spoqa Han Sans Neo", "Noto Sans KR", sans-serif; +} +.step1-wrap .s1-title, +.step1-wrap .s2-title, +.step1-wrap .s3-title, +.step2-wrap .s1-title, +.step2-wrap .s2-title, +.step2-wrap .s3-title, +.step3-wrap .s1-title, +.step3-wrap .s2-title, +.step3-wrap .s3-title { + font-size: 24px; + font-weight: 700; + color: #0d0e11; + line-height: 1; + margin: 0 0 24px 0; + padding: 0; + border: none; + background: none; + font-family: "Spoqa Han Sans Neo", "Noto Sans KR", sans-serif; +} +@media (max-width: 768px) { + .step1-wrap .s1-title, + .step1-wrap .s2-title, + .step1-wrap .s3-title, + .step2-wrap .s1-title, + .step2-wrap .s2-title, + .step2-wrap .s3-title, + .step3-wrap .s1-title, + .step3-wrap .s2-title, + .step3-wrap .s3-title { + font-size: 23px !important; + } +} +.step1-wrap .s1-progress-card, +.step2-wrap .s1-progress-card, +.step3-wrap .s1-progress-card { + background: #ffffff; + border-radius: 20px; + box-shadow: 0 4px 13.45px rgba(192, 192, 192, 0.25); + height: 160px; + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 24px; + box-sizing: border-box; +} +@media (max-width: 768px) { + .step1-wrap .s1-progress-card, + .step2-wrap .s1-progress-card, + .step3-wrap .s1-progress-card { + height: 120px; + padding: 0 10px; + } +} +@media (max-width: 480px) { + .step1-wrap .s1-progress-card, + .step2-wrap .s1-progress-card, + .step3-wrap .s1-progress-card { + height: 100px; + } +} +.step1-wrap .s1-steps, +.step2-wrap .s1-steps, +.step3-wrap .s1-steps { + display: flex; + align-items: center; + gap: 0; +} +.step1-wrap .s1-step, +.step2-wrap .s1-step, +.step3-wrap .s1-step { + display: flex; + flex-direction: column; + align-items: center; + gap: 6px; + width: 80px; +} +@media (max-width: 768px) { + .step1-wrap .s1-step, + .step2-wrap .s1-step, + .step3-wrap .s1-step { + width: 70px; + } +} +@media (max-width: 480px) { + .step1-wrap .s1-step, + .step2-wrap .s1-step, + .step3-wrap .s1-step { + width: 60px; + } +} +.step1-wrap .s1-step--active .s1-step-circle, +.step2-wrap .s1-step--active .s1-step-circle, +.step3-wrap .s1-step--active .s1-step-circle { + background: #2a69de; + border-color: #2a69de; + color: #ffffff; +} +.step1-wrap .s1-step--active .s1-step-num, +.step2-wrap .s1-step--active .s1-step-num, +.step3-wrap .s1-step--active .s1-step-num { + color: #1b4ab7; + font-weight: 700; +} +.step1-wrap .s1-step--active .s1-step-name, +.step2-wrap .s1-step--active .s1-step-name, +.step3-wrap .s1-step--active .s1-step-name { + font-weight: 600; +} +.step1-wrap .s1-step-circle, +.step2-wrap .s1-step-circle, +.step3-wrap .s1-step-circle { + width: 55px; + height: 55px; + border-radius: 50%; + background: #f0f4fa; + border: 1.5px solid #dde4ee; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + color: #b0bec5; +} +.step1-wrap .s1-step-circle img, +.step2-wrap .s1-step-circle img, +.step3-wrap .s1-step-circle img { + display: block; + object-fit: contain; +} +@media (max-width: 768px) { + .step1-wrap .s1-step-circle img, + .step2-wrap .s1-step-circle img, + .step3-wrap .s1-step-circle img { + width: 24px !important; + height: 24px !important; + } +} +@media (max-width: 768px) { + .step1-wrap .s1-step-circle, + .step2-wrap .s1-step-circle, + .step3-wrap .s1-step-circle { + width: 44px; + height: 44px; + } +} +@media (max-width: 480px) { + .step1-wrap .s1-step-circle, + .step2-wrap .s1-step-circle, + .step3-wrap .s1-step-circle { + width: 36px; + height: 36px; + } +} +.step1-wrap .s1-step-svg, +.step2-wrap .s1-step-svg, +.step3-wrap .s1-step-svg { + display: block; + flex-shrink: 0; +} +@media (max-width: 768px) { + .step1-wrap .s1-step-svg, + .step2-wrap .s1-step-svg, + .step3-wrap .s1-step-svg { + width: 24px !important; + height: 24px !important; + } +} +@media (max-width: 480px) { + .step1-wrap .s1-step-svg, + .step2-wrap .s1-step-svg, + .step3-wrap .s1-step-svg { + width: 18px !important; + height: 18px !important; + } +} +.step1-wrap .s1-step-num, +.step2-wrap .s1-step-num, +.step3-wrap .s1-step-num { + font-size: 12px; + font-weight: 500; + color: #94a3b8; + line-height: 1; +} +@media (max-width: 768px) { + .step1-wrap .s1-step-num, + .step2-wrap .s1-step-num, + .step3-wrap .s1-step-num { + font-size: 11px; + } +} +@media (max-width: 480px) { + .step1-wrap .s1-step-num, + .step2-wrap .s1-step-num, + .step3-wrap .s1-step-num { + font-size: 10px; + } +} +.step1-wrap .s1-step-name, +.step2-wrap .s1-step-name, +.step3-wrap .s1-step-name { + font-size: 13px; + font-weight: 400; + color: #0d0e11; + text-align: center; + white-space: nowrap; + line-height: 1; +} +@media (max-width: 768px) { + .step1-wrap .s1-step-name, + .step2-wrap .s1-step-name, + .step3-wrap .s1-step-name { + font-size: 11px; + } +} +@media (max-width: 480px) { + .step1-wrap .s1-step-name, + .step2-wrap .s1-step-name, + .step3-wrap .s1-step-name { + font-size: 10px; + } +} +.step1-wrap .s1-step-line, +.step2-wrap .s1-step-line, +.step3-wrap .s1-step-line { + width: 120px; + height: 1.5px; + background: #dfdfdf; + flex-shrink: 0; + margin-bottom: 36px; +} +@media (max-width: 768px) { + .step1-wrap .s1-step-line, + .step2-wrap .s1-step-line, + .step3-wrap .s1-step-line { + width: 40px; + margin-bottom: 28px; + } +} +@media (max-width: 480px) { + .step1-wrap .s1-step-line, + .step2-wrap .s1-step-line, + .step3-wrap .s1-step-line { + width: 20px; + margin-bottom: 24px; + } +} +.step1-wrap .s1-alert, +.step2-wrap .s1-alert, +.step3-wrap .s1-alert { + background: #fff5f5; + border: 1px solid #fed7d7; + border-radius: 10px; + padding: 12px 20px; + color: #c53030; + font-size: 14px; + margin-bottom: 16px; +} +.step1-wrap .s1-form-card, +.step2-wrap .s1-form-card, +.step3-wrap .s1-form-card { + background: #ffffff; + border: 1px solid #e8edf5; + border-radius: 20px; + box-shadow: 0 4px 26.9px 2px rgba(192, 192, 192, 0.25); + padding: 48px 56px; + margin-bottom: 24px; +} +.step1-wrap .s1-field, +.step2-wrap .s1-field, +.step3-wrap .s1-field { + display: flex; + flex-direction: column; + gap: 12px; + margin-bottom: 32px; +} +.step1-wrap .s1-field:last-child, +.step2-wrap .s1-field:last-child, +.step3-wrap .s1-field:last-child { + margin-bottom: 0; +} +.step1-wrap .s1-label, +.step2-wrap .s1-label, +.step3-wrap .s1-label { + font-size: 15px; + font-weight: 600; + color: #0d0e11; + margin: 0; + padding: 0; + display: block; + line-height: 1.4; +} +.step1-wrap .s1-required, +.step2-wrap .s1-required, +.step3-wrap .s1-required { + color: #f4253c; + font-size: 15px; + margin-left: 2px; +} +.step1-wrap .s1-input, +.step2-wrap .s1-input, +.step3-wrap .s1-input { + display: block; + width: 100%; + height: 47px; + background: #fcfcfc; + border: 1px solid #dfdfdf; + border-radius: 10px; + padding: 0 20px; + font-size: 15px; + font-weight: 300; + color: #0d0e11; + box-sizing: border-box; + outline: none; + transition: border-color 0.2s; + font-family: inherit; + appearance: none; + box-shadow: none; +} +.step1-wrap .s1-input::placeholder, +.step2-wrap .s1-input::placeholder, +.step3-wrap .s1-input::placeholder { + color: #7f8a95; + font-weight: 300; +} +.step1-wrap .s1-input:focus, +.step2-wrap .s1-input:focus, +.step3-wrap .s1-input:focus { + border-color: #2a69de; +} +.step1-wrap .s1-textarea, +.step2-wrap .s1-textarea, +.step3-wrap .s1-textarea { + display: block; + width: 100%; + height: 220px; + background: #fcfcfc; + border: 1px solid #dfdfdf; + border-radius: 10px; + padding: 16px 20px; + font-size: 15px; + font-weight: 300; + color: #0d0e11; + box-sizing: border-box; + outline: none; + resize: none; + transition: border-color 0.2s; + font-family: inherit; + box-shadow: none; +} +.step1-wrap .s1-textarea::placeholder, +.step2-wrap .s1-textarea::placeholder, +.step3-wrap .s1-textarea::placeholder { + color: #7f8a95; + font-weight: 300; +} +.step1-wrap .s1-textarea:focus, +.step2-wrap .s1-textarea:focus, +.step3-wrap .s1-textarea:focus { + border-color: #2a69de; +} +.step1-wrap .s1-counter-row, +.step2-wrap .s1-counter-row, +.step3-wrap .s1-counter-row { + display: flex; + justify-content: flex-end; + margin-top: -4px; +} +.step1-wrap .s1-counter, +.step2-wrap .s1-counter, +.step3-wrap .s1-counter { + font-size: 13px; + color: #94a3b8; +} +.step1-wrap .s1-upload-box, +.step2-wrap .s1-upload-box, +.step3-wrap .s1-upload-box { + width: 100%; + min-height: 220px; + background: #fcfcfc; + border: 1px solid #dfdfdf; + border-radius: 10px; + display: flex; + align-items: center; + justify-content: center; + position: relative; + transition: border-color 0.2s; + cursor: default; +} +.step1-wrap .s1-upload-box.drag-over, +.step2-wrap .s1-upload-box.drag-over, +.step3-wrap .s1-upload-box.drag-over { + border-color: #2a69de; + background: #f0f7ff; +} +.step1-wrap .s1-upload-box--compact, +.step2-wrap .s1-upload-box--compact, +.step3-wrap .s1-upload-box--compact { + min-height: 96px; +} +.step1-wrap .s1-upload-box--compact .s1-upload-inner, +.step2-wrap .s1-upload-box--compact .s1-upload-inner, +.step3-wrap .s1-upload-box--compact .s1-upload-inner { + flex-direction: row; + flex-wrap: wrap; + justify-content: center; + gap: 6px 14px; + padding: 12px 16px; +} +.step1-wrap .s1-upload-box--compact .s1-upload-inner > img, +.step2-wrap .s1-upload-box--compact .s1-upload-inner > img, +.step3-wrap .s1-upload-box--compact .s1-upload-inner > img { + width: 28px; + height: 28px; +} +.step1-wrap .s1-upload-box--compact .s1-upload-title, +.step2-wrap .s1-upload-box--compact .s1-upload-title, +.step3-wrap .s1-upload-box--compact .s1-upload-title { + font-size: 13px; +} +.step1-wrap .s1-upload-box--compact .s1-btn-upload, +.step2-wrap .s1-upload-box--compact .s1-btn-upload, +.step3-wrap .s1-upload-box--compact .s1-btn-upload { + height: 34px; + padding: 0 16px; + font-size: 13px; +} +.step1-wrap .s1-upload-inner, +.step2-wrap .s1-upload-inner, +.step3-wrap .s1-upload-inner { + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; + padding: 24px; +} +.step1-wrap .s1-upload-title, +.step2-wrap .s1-upload-title, +.step3-wrap .s1-upload-title { + font-size: 15px; + font-weight: 600; + color: #0d0e11; + margin: 0; + text-align: center; +} +.step1-wrap .s1-upload-hint, +.step2-wrap .s1-upload-hint, +.step3-wrap .s1-upload-hint { + font-size: 12px; + font-weight: 300; + color: #7f8a95; + margin: 0; + text-align: center; +} +.step1-wrap .s1-btn-upload, +.step2-wrap .s1-btn-upload, +.step3-wrap .s1-btn-upload { + background: #2a69de; + color: #fff; + border: none; + border-radius: 10px; + height: 44px; + padding: 0 24px; + font-size: 15px; + font-weight: 500; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + margin-top: 4px; + font-family: inherit; + transition: background 0.15s; +} +.step1-wrap .s1-btn-upload:hover, +.step2-wrap .s1-btn-upload:hover, +.step3-wrap .s1-btn-upload:hover { + background: #1b4ab7; +} +.step1-wrap .s1-preview-img, +.step2-wrap .s1-preview-img, +.step3-wrap .s1-preview-img { + max-width: 120px; + max-height: 120px; + object-fit: cover; + border-radius: 10px; + display: block; +} +.step1-wrap .s1-btn-remove-icon, +.step2-wrap .s1-btn-remove-icon, +.step3-wrap .s1-btn-remove-icon { + position: absolute; + top: 10px; + right: 10px; + width: 28px; + height: 28px; + background: rgba(0, 0, 0, 0.45); + border: none; + border-radius: 50%; + color: #fff; + font-size: 14px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; +} +.step1-wrap .s1-ip-row, +.step2-wrap .s1-ip-row, +.step3-wrap .s1-ip-row { + display: flex; + gap: 10px; + align-items: center; + width: 100%; + margin-bottom: 10px; +} +.step1-wrap .s1-ip-row .s1-input, +.step2-wrap .s1-ip-row .s1-input, +.step3-wrap .s1-ip-row .s1-input { + flex: 1; +} +.step1-wrap .s1-ip-item, +.step2-wrap .s1-ip-item, +.step3-wrap .s1-ip-item { + display: flex; + gap: 10px; + align-items: center; + width: 100%; + margin-bottom: 10px; +} +.step1-wrap .s1-ip-display, +.step2-wrap .s1-ip-display, +.step3-wrap .s1-ip-display { + flex: 1; + height: 47px; + background: #ededed; + border: 1px solid #dfdfdf; + border-radius: 10px; + padding: 0 20px; + font-size: 15px; + font-weight: 300; + color: #7f8a95; + display: flex; + align-items: center; + box-sizing: border-box; +} +.step1-wrap .s1-ip-btn, +.step2-wrap .s1-ip-btn, +.step3-wrap .s1-ip-btn { + width: 88px; + height: 47px; + border: none; + border-radius: 10px; + font-size: 14px; + font-weight: 600; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 5px; + flex-shrink: 0; + font-family: inherit; + transition: opacity 0.15s; +} +.step1-wrap .s1-ip-btn:hover, +.step2-wrap .s1-ip-btn:hover, +.step3-wrap .s1-ip-btn:hover { + opacity: 0.85; +} +.step1-wrap .s1-ip-btn--add, +.step2-wrap .s1-ip-btn--add, +.step3-wrap .s1-ip-btn--add { + background: #d5efff; + color: #2a69de; +} +.step1-wrap .s1-ip-btn--remove, +.step2-wrap .s1-ip-btn--remove, +.step3-wrap .s1-ip-btn--remove { + background: #fbe7e9; + color: #bb1026; +} +.step1-wrap .s1-actions, +.step2-wrap .s1-actions, +.step3-wrap .s1-actions { + display: flex; + justify-content: flex-end; + padding: 0; +} +.step1-wrap .s1-btn-next, +.step2-wrap .s1-btn-next, +.step3-wrap .s1-btn-next { + width: 200px; + height: 48px; + background: #2a69de; + color: #fff; + border: none; + border-radius: 10px; + font-size: 16px; + font-weight: 500; + cursor: pointer; + font-family: inherit; + transition: background 0.15s; + display: inline-flex; + align-items: center; + justify-content: center; +} +.step1-wrap .s1-btn-next:hover, +.step2-wrap .s1-btn-next:hover, +.step3-wrap .s1-btn-next:hover { + background: #1b4ab7; +} +@media (max-width: 768px) { + .step1-wrap .s1-form-card, + .step2-wrap .s1-form-card, + .step3-wrap .s1-form-card { + padding: 28px 20px; + border-radius: 14px; + } + .step1-wrap .s1-steps, + .step2-wrap .s1-steps, + .step3-wrap .s1-steps { + gap: 0; + } + .step1-wrap .s1-step-line, + .step2-wrap .s1-step-line, + .step3-wrap .s1-step-line { + width: 60px; + } + .step1-wrap .s1-step, + .step2-wrap .s1-step, + .step3-wrap .s1-step { + width: 60px; + } + .step1-wrap .s1-step-name, + .step2-wrap .s1-step-name, + .step3-wrap .s1-step-name { + font-size: 11px; + white-space: normal; + text-align: center; + } + .step1-wrap .s1-ip-btn, + .step2-wrap .s1-ip-btn, + .step3-wrap .s1-ip-btn { + width: 72px; + font-size: 13px; + } + .step1-wrap .s1-btn-next, + .step2-wrap .s1-btn-next, + .step3-wrap .s1-btn-next { + width: 100%; + } +} + +.step2-wrap { + width: 100%; + font-family: "Spoqa Han Sans Neo", "Noto Sans KR", sans-serif; +} +.step2-wrap .s2-selection-container { + margin-bottom: 35px; +} +.step2-wrap .s2-category-carousel-container { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 24px; + width: 100%; + position: relative; + user-select: none; +} +@media (max-width: 768px) { + .step2-wrap .s2-category-carousel-container { + gap: 6px; + margin-bottom: 16px; + margin-top: 40px; + } +} +.step2-wrap .s2-category-list-wrapper { + flex: 1; + overflow-x: auto; + overflow-y: hidden; + scroll-behavior: smooth; + -ms-overflow-style: none; + /* IE and Edge */ + scrollbar-width: none; + /* Firefox */ +} +.step2-wrap .s2-category-list-wrapper::-webkit-scrollbar { + display: none; + /* Chrome, Safari and Opera */ +} +.step2-wrap .s2-category-list { + display: flex; + gap: 8px; + white-space: nowrap; + padding: 4px 0; +} +.step2-wrap .s2-category-tab { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 8px 22px; + height: 40px; + background: #e6f4ff; + border: 1.5px solid #2a69de; + border-radius: 20px; + font-size: 15px; + font-weight: 500; + color: #2a69de; + cursor: pointer; + transition: all 0.2s ease; + box-sizing: border-box; + font-family: "Spoqa Han Sans Neo", sans-serif; +} +.step2-wrap .s2-category-tab:hover { + background: rgba(42, 105, 222, 0.05); +} +.step2-wrap .s2-category-tab.active { + background: #2a69de; + color: #ffffff; +} +@media (max-width: 768px) { + .step2-wrap .s2-category-tab { + padding: 6px 14px; + height: 34px; + font-size: 13px; + } +} +.step2-wrap .s2-carousel-btn { + width: 36px; + height: 36px; + border-radius: 50%; + background: #ffffff; + border: 1px solid #dfdfdf; + color: #94a3b8; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all 0.2s ease; + flex-shrink: 0; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05); +} +.step2-wrap .s2-carousel-btn:hover { + border-color: #2a69de; + color: #2a69de; + background: #f8fafc; +} +.step2-wrap .s2-carousel-btn:disabled { + opacity: 0.35; + cursor: not-allowed; +} +.step2-wrap .s2-carousel-btn:disabled:hover { + border-color: #dfdfdf; + color: #94a3b8; + background: #ffffff; +} +@media (max-width: 768px) { + .step2-wrap .s2-carousel-btn { + width: 30px; + height: 30px; + } + .step2-wrap .s2-carousel-btn svg { + width: 14px; + height: 14px; + } +} +.step2-wrap .s2-form { + width: 100%; + display: flex; + flex-direction: column; +} +.step2-wrap .s2-filter-header { + display: flex; + justify-content: flex-end; + align-items: center; + margin-bottom: 24px; + gap: 16px; +} +@media (max-width: 768px) { + .step2-wrap .s2-filter-header { + flex-direction: row; + flex-wrap: wrap; + justify-content: space-between; + align-items: center; + gap: 12px; + } +} +.step2-wrap .s2-select-all { + display: flex; + align-items: center; + flex: 1; +} +.step2-wrap .s2-select-all .s2-select-all-label { + display: flex; + align-items: center; + gap: 8px; + cursor: pointer; + font-size: 15px; + color: #0d0e11; + font-weight: 500; + user-select: none; +} +.step2-wrap .s2-select-all .s2-select-all-label:hover { + color: #2a69de; +} +.step2-wrap .s2-search-box { + position: relative; + width: 228px; + height: 44px; + flex-shrink: 0; +} +@media (max-width: 768px) { + .step2-wrap .s2-search-box { + width: 100%; + } +} +.step2-wrap .s2-search-box .s2-search-input { + width: 100%; + height: 100%; + padding: 0 44px 0 17px; + border: 1px solid #dfdfdf; + border-radius: 10px; + background-color: #fcfcfc; + font-size: 14px; + outline: none; + transition: all 0.2s ease; +} +.step2-wrap .s2-search-box .s2-search-input:focus { + border-color: #2a69de; + background-color: #ffffff; +} +.step2-wrap .s2-search-box .s2-search-input::placeholder { + color: #7f8a95; +} +.step2-wrap .s2-search-box svg { + position: absolute; + right: 17px; + top: 50%; + transform: translateY(-50%); + pointer-events: none; +} +.step2-wrap .s2-result-count { + font-size: 15px; + color: #64748b; + margin-right: auto; +} +.step2-wrap .s2-result-count strong { + color: #2a69de; + font-weight: 600; +} +@media (max-width: 768px) { + .step2-wrap .s2-result-count { + margin-right: 0; + } +} +.step2-wrap .s2-cards-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 24px; + margin-bottom: 24px; +} +@media (max-width: 1200px) { + .step2-wrap .s2-cards-grid { + grid-template-columns: repeat(2, 1fr); + gap: 16px; + } +} +@media (max-width: 768px) { + .step2-wrap .s2-cards-grid { + grid-template-columns: 1fr; + gap: 16px; + } +} +.step2-wrap .s2-api-card { + background: #FFFFFF; + border-radius: 20px; + border: 1px solid #DDDDDD; + box-shadow: 0px 4px 2px rgba(194, 194, 194, 0.25); + cursor: pointer; + position: relative; + width: 100%; + height: 312px; + overflow: hidden; + transition: transform 0.3s ease, box-shadow 0.3s ease, background 0.3s ease; + box-sizing: border-box; +} +.step2-wrap .s2-api-card:hover, .step2-wrap .s2-api-card:focus { + transform: translateY(-5px); + box-shadow: 0px 8px 12px rgba(194, 194, 194, 0.4); + background: linear-gradient(to bottom, #50caff, #2088ff); + border: none; +} +.step2-wrap .s2-api-card:hover .s2-api-card-badge, .step2-wrap .s2-api-card:focus .s2-api-card-badge { + background-color: #d8f0ff; +} +.step2-wrap .s2-api-card:hover .s2-api-card-badge span, .step2-wrap .s2-api-card:focus .s2-api-card-badge span { + color: #1b4ab7; +} +.step2-wrap .s2-api-card:hover .s2-api-card-title, +.step2-wrap .s2-api-card:hover .s2-api-card-desc, .step2-wrap .s2-api-card:focus .s2-api-card-title, +.step2-wrap .s2-api-card:focus .s2-api-card-desc { + color: #ffffff; +} +.step2-wrap .s2-api-card.selected { + background: linear-gradient(to bottom, #50caff, #2088ff); + border-color: transparent; + box-shadow: 0px 8px 12px rgba(32, 136, 255, 0.3); +} +.step2-wrap .s2-api-card.selected .s2-api-card-badge { + background-color: #d8f0ff; +} +.step2-wrap .s2-api-card.selected .s2-api-card-badge span { + color: #1b4ab7; +} +.step2-wrap .s2-api-card.selected .s2-api-card-title, +.step2-wrap .s2-api-card.selected .s2-api-card-desc { + color: #ffffff; +} +.step2-wrap .s2-api-card .s2-api-card-badge { + position: absolute; + top: 39px; + left: 32px; + background-color: #f2f2f3; + border-radius: 10px; + padding: 2px 20px; + display: inline-flex; + align-items: center; + justify-content: center; +} +.step2-wrap .s2-api-card .s2-api-card-badge span { + font-size: 14px; + font-family: "Spoqa Han Sans Neo", sans-serif; + font-weight: 500; + color: #1b4ab7; + white-space: nowrap; +} +.step2-wrap .s2-api-card .s2-checkbox-wrapper { + position: absolute; + top: 39px; + right: 32px; + z-index: 2; +} +.step2-wrap .s2-api-card .s2-checkbox-wrapper .s2-api-checkbox { + width: 22px; + height: 22px; + cursor: pointer; +} +.step2-wrap .s2-api-card .s2-api-card-title { + position: absolute; + top: 86px; + left: 36px; + right: 40px; + font-size: 18px; + font-family: "Spoqa Han Sans Neo", sans-serif; + font-weight: 700; + color: #000000; + margin: 0; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; + word-break: break-word; + text-align: left; + white-space: normal; +} +.step2-wrap .s2-api-card .s2-api-card-desc { + position: absolute; + top: 135px; + left: 36px; + right: 40px; + font-size: 14px; + font-family: "Spoqa Han Sans Neo", sans-serif; + font-weight: 400; + color: #64748b; + line-height: 1.5; + margin: 0; + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; + word-break: break-word; + text-align: left; + white-space: normal; +} +.step2-wrap .s2-api-card .s2-api-card-image { + position: absolute; + bottom: 28px; + right: 20px; + width: 93px; + height: 88px; + display: flex; + align-items: center; + justify-content: center; + pointer-events: none; +} +.step2-wrap .s2-api-card .s2-api-card-image img { + width: 100%; + height: 100%; + object-fit: contain; +} +.step2-wrap .s2-api-card .s2-api-card-image i { + font-size: 32px; + color: #94a3b8; +} +@media (max-width: 768px) { + .step2-wrap .s2-api-card { + height: 260px; + } + .step2-wrap .s2-api-card .s2-api-card-badge { + top: 24px; + left: 24px; + } + .step2-wrap .s2-api-card .s2-checkbox-wrapper { + top: 24px; + right: 24px; + } + .step2-wrap .s2-api-card .s2-api-card-title { + top: 76px; + left: 24px; + right: 24px; + font-size: 16px; + } + .step2-wrap .s2-api-card .s2-api-card-desc { + top: 118px; + left: 24px; + right: 24px; + font-size: 13px; + -webkit-line-clamp: 2; + } + .step2-wrap .s2-api-card .s2-api-card-image { + bottom: 20px; + right: 16px; + width: 60px; + height: 60px; + } +} +.step2-wrap .s2-checkbox-custom { + display: inline-flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + border-radius: 50%; + border: 2px solid #cbd5e1; + background-color: #ffffff; + cursor: pointer; + transition: all 0.2s ease; + position: relative; + box-sizing: border-box; +} +.step2-wrap .s2-checkbox-custom::after { + content: ""; + width: 5px; + height: 9px; + border: solid transparent; + border-width: 0 2px 2px 0; + transform: rotate(45deg); + margin-top: -2px; + opacity: 0; + transition: all 0.2s ease; + box-sizing: border-box; +} +.step2-wrap .s2-api-checkbox:checked + .s2-checkbox-custom { + border-color: #2a69de; + background-color: #2a69de; +} +.step2-wrap .s2-api-checkbox:checked + .s2-checkbox-custom::after { + border-color: #ffffff; + opacity: 1; +} +.step2-wrap .s2-api-card.selected .s2-checkbox-custom { + border-color: #ffffff; + background-color: #ffffff; +} +.step2-wrap .s2-api-card.selected .s2-checkbox-custom::after { + border-color: #2a69de; + opacity: 1; +} +.step2-wrap .s2-loading { + padding: 48px; + text-align: center; + color: #64748b; +} +.step2-wrap .s2-loading .s2-spinner { + width: 48px; + height: 48px; + margin: 0 auto 16px; + border: 4px solid #dfdfdf; + border-top-color: #2a69de; + border-radius: 50%; + animation: spin 1s linear infinite; +} +.step2-wrap .s2-empty { + padding: 48px; + text-align: center; + color: #64748b; +} +.step2-wrap .s2-empty .s2-empty-icon { + font-size: 48px; + margin-bottom: 16px; +} +.step2-wrap .s2-empty h3 { + font-size: 18px; + font-weight: 600; + color: #0d0e11; + margin-bottom: 8px; +} +.step2-wrap .s2-actions { + display: flex; + justify-content: flex-end; + gap: 12px; + margin-top: 24px; +} +.step2-wrap .s2-actions .s2-btn-prev { + width: 144px; + height: 48px; + background: #f1f5f9; + color: #0d0e11; + border: 1px solid #dfdfdf; + border-radius: 10px; + font-size: 16px; + font-weight: 500; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + transition: background 0.15s; +} +.step2-wrap .s2-actions .s2-btn-prev:hover { + background: #e2e8f0; +} +.step2-wrap .s2-actions .s2-btn-save { + width: 200px; + height: 48px; + background: #2a69de; + color: #ffffff; + border: none; + border-radius: 10px; + font-size: 16px; + font-weight: 500; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + transition: background 0.15s; +} +.step2-wrap .s2-actions .s2-btn-save:hover { + background: #1b4ab7; +} +@media (max-width: 768px) { + .step2-wrap .s2-actions { + flex-direction: column; + } + .step2-wrap .s2-actions .s2-btn-prev, + .step2-wrap .s2-actions .s2-btn-save { + width: 100%; + } +} +.s2-floating-cart { + position: fixed; + bottom: 80px; + right: 40px; + width: 180px; + height: 66px; + padding: 10px 16px 10px 36px; + border-radius: 60px; + background-color: #3ba4ed; + border: none; + color: #ffffff; + display: flex; + align-items: center; + justify-content: space-between; + cursor: pointer; + box-shadow: 0 10px 25px rgba(59, 164, 237, 0.35); + transition: all 0.3s ease; + z-index: 1001; +} +.s2-floating-cart .s2-cart-label { + font-size: 14px; + font-weight: 700; + color: #ffffff; + white-space: nowrap; +} +.s2-floating-cart .s2-cart-badge { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + background: #ffffff; + width: 57px; + height: 57px; + border-radius: 50%; + flex-shrink: 0; + margin-right: -8px; +} +.s2-floating-cart .s2-cart-badge .s2-cart-count { + font-size: 24px; + font-weight: 700; + color: #212529; + line-height: 1; +} +.s2-floating-cart .s2-cart-badge .s2-cart-unit { + font-size: 14px; + font-weight: 700; + color: #212529; + line-height: 1; + margin-top: 2px; +} +.s2-floating-cart:hover { + transform: translateY(-4px); + box-shadow: 0 12px 32px rgba(59, 164, 237, 0.5); +} +@media (max-width: 768px) { + .s2-floating-cart { + bottom: 100px; + right: 24px; + width: 160px; + height: 56px; + padding: 8px 12px 8px 28px; + } + .s2-floating-cart .s2-cart-label { + font-size: 12px; + } + .s2-floating-cart .s2-cart-badge { + width: 48px; + height: 48px; + margin-right: -6px; + } + .s2-floating-cart .s2-cart-badge .s2-cart-count { + font-size: 20px; + } + .s2-floating-cart .s2-cart-badge .s2-cart-unit { + font-size: 12px; + } +} + +.s2-modal { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 1050; + display: flex; + align-items: center; + justify-content: center; +} +.s2-modal .s2-modal-backdrop { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); + backdrop-filter: blur(2px); +} +.s2-modal .s2-modal-dialog { + position: relative; + background: #ffffff; + border-radius: 20px; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); + width: 100%; + max-width: 500px; + margin: 16px; + display: flex; + flex-direction: column; + overflow: hidden; + z-index: 1; +} +.s2-modal .s2-modal-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 20px 24px; + border-bottom: 1px solid #e8edf5; +} +.s2-modal .s2-modal-header .s2-modal-title { + font-size: 18px; + font-weight: 700; + color: #0d0e11; + margin: 0; +} +.s2-modal .s2-modal-header .s2-modal-close { + background: none; + border: none; + font-size: 20px; + cursor: pointer; + color: #94a3b8; +} +.s2-modal .s2-modal-header .s2-modal-close:hover { + color: #0d0e11; +} +.s2-modal .s2-modal-body { + padding: 24px; + max-height: 300px; + overflow-y: auto; +} +.s2-modal .s2-selected-list { + display: flex; + flex-wrap: wrap; + gap: 8px; +} +.s2-modal .s2-api-pill { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 8px 16px; + background: #f1f5f9; + border-radius: 50px; + font-size: 14px; + color: #0d0e11; +} +.s2-modal .s2-api-pill .s2-api-pill-name { + max-width: 250px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.s2-modal .s2-api-pill .s2-api-pill-remove { + background: none; + border: none; + font-size: 14px; + color: #94a3b8; + cursor: pointer; + padding: 0; + display: flex; + align-items: center; + justify-content: center; +} +.s2-modal .s2-api-pill .s2-api-pill-remove:hover { + color: #f4253c; +} +.s2-modal .s2-modal-footer { + padding: 16px 24px; + border-top: 1px solid #e8edf5; + display: flex; + justify-content: flex-end; +} +.s2-modal .s2-btn-close-modal { + height: 40px; + padding: 0 20px; + background: #f1f5f9; + color: #0d0e11; + border: 1px solid #dfdfdf; + border-radius: 8px; + font-size: 14px; + font-weight: 500; + cursor: pointer; +} +.s2-modal .s2-btn-close-modal:hover { + background: #e2e8f0; +} + +.step3-wrap .s3-result-container { + background: #ffffff; + border: 1px solid #e8edf5; + border-radius: 20px; + box-shadow: 0 4px 26.9px 2px rgba(192, 192, 192, 0.25); + display: flex; + flex-direction: column; + align-items: center; + padding: 60px 40px; + margin-bottom: 24px; + text-align: center; +} +.step3-wrap .s3-graphic-wrapper { + margin-bottom: 20px; + display: flex; + align-items: center; + justify-content: center; + width: 100%; +} +.step3-wrap .s3-illustration { + width: 320px; + height: auto; + max-width: 100%; +} +.step3-wrap .s3-illustration .floating-slow { + animation: floatSlow 4s ease-in-out infinite alternate; +} +@keyframes floatSlow { + 0% { + transform: translateY(0); + } + 100% { + transform: translateY(-10px); + } +} +.step3-wrap .s3-message-wrapper { + margin-bottom: 40px; +} +.step3-wrap .s3-success-title { + font-size: 26px; + font-weight: 700; + color: #2a69de; + margin-bottom: 16px; + font-family: "Spoqa Han Sans Neo", sans-serif; +} +.step3-wrap .s3-success-desc { + font-size: 16px; + line-height: 1.6; + color: #64748b; + font-family: "Spoqa Han Sans Neo", sans-serif; + margin: 0; +} +.step3-wrap .s3-success-desc .s3-highlight { + color: #0d0e11; + font-weight: 700; +} +.step3-wrap .s3-actions { + display: flex; + justify-content: center; + width: 100%; +} +.step3-wrap .s3-btn-complete { + display: inline-flex; + align-items: center; + justify-content: center; + width: 200px; + height: 48px; + background: #2a69de; + color: #ffffff; + border-radius: 10px; + font-size: 16px; + font-weight: 500; + text-decoration: none; + transition: background 0.15s; +} +.step3-wrap .s3-btn-complete:hover { + background: #1b4ab7; + color: #ffffff; +} +.step3-wrap .s3-result-container--error { + padding: 60px 40px; +} +.step3-wrap .s3-error-icon { + font-size: 64px; + margin-bottom: 10px; +} +.step3-wrap .s3-error-title { + font-size: 24px; + font-weight: 700; + color: #f4253c; + margin-bottom: 16px; +} +.step3-wrap .s3-error-desc { + font-size: 15px; + color: #64748b; +} +.step3-wrap .s3-btn-retry { + display: inline-flex; + align-items: center; + justify-content: center; + width: 144px; + height: 48px; + background: #f1f5f9; + color: #0d0e11; + border: 1px solid #dfdfdf; + border-radius: 10px; + font-size: 16px; + font-weight: 500; + text-decoration: none; + margin-right: 12px; + transition: background 0.15s; +} +.step3-wrap .s3-btn-retry:hover { + background: #e2e8f0; +} +.step3-wrap .s3-btn-list { + display: inline-flex; + align-items: center; + justify-content: center; + width: 200px; + height: 48px; + background: #2a69de; + color: #ffffff; + border-radius: 10px; + font-size: 16px; + font-weight: 500; + text-decoration: none; + transition: background 0.15s; +} +.step3-wrap .s3-btn-list:hover { + background: #1b4ab7; + color: #ffffff; +} + .apikey-detail-container { padding: 40px 0; } @@ -12805,7 +17152,6 @@ input[type=checkbox]:checked + .custom-checkbox { font-weight: 700; color: #1A1A2E; margin-bottom: 24px; - padding-bottom: 16px; } .detail-label { @@ -13446,6 +17792,425 @@ input[type=checkbox]:checked + .custom-checkbox { display: none; } } +.detail-wrap { + width: 100%; + font-family: "Spoqa Han Sans Neo", "Noto Sans KR", sans-serif; +} +.detail-wrap .dt-title { + font-size: 30px; + font-weight: 700; + color: #000000; + margin: 0 0 28px 0; + text-align: left; + font-family: "Spoqa Han Sans Neo", "Noto Sans KR", sans-serif; +} +@media (max-width: 768px) { + .detail-wrap .dt-title { + font-size: 24px; + margin-bottom: 20px; + } +} +.detail-wrap .dt-app-card { + display: flex; + align-items: center; + gap: 16px; + background: #ffffff; + border: 1px solid #dfdfdf; + border-radius: 20px; + box-shadow: 0px 4px 13.45px rgba(192, 192, 192, 0.25); + padding: 20px 24px; + margin-bottom: 24px; + box-sizing: border-box; +} +@media (max-width: 768px) { + .detail-wrap .dt-app-card { + flex-direction: column; + align-items: flex-start; + gap: 12px; + padding: 16px; + } +} +.detail-wrap .dt-app-icon-box { + width: 68px; + height: 67px; + background: #f9f9f9; + border: 1px solid #dee3e7; + border-radius: 10px; + display: flex; + justify-content: center; + align-items: center; + flex-shrink: 0; +} +.detail-wrap .dt-app-icon-box img, +.detail-wrap .dt-app-icon-box svg { + width: 46px; + height: 46px; + object-fit: cover; + border-radius: 4px; +} +.detail-wrap .dt-app-info { + flex: 1; + display: flex; + flex-direction: column; + gap: 8px; + min-width: 0; +} +.detail-wrap .dt-app-header-row { + display: flex; + align-items: center; + gap: 10px; +} +.detail-wrap .dt-app-name { + font-size: 16px; + font-weight: 500; + color: #000; + margin: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.detail-wrap .dt-status-badge { + height: 20px; + border-radius: 5px; + padding: 0 8px; + font-size: 10px; + font-weight: 500; + display: inline-flex; + align-items: center; + justify-content: center; + white-space: nowrap; +} +.detail-wrap .dt-status-badge.status-approved, .detail-wrap .dt-status-badge.status-completed { + background: #1b4ab7; + color: #ffffff; +} +.detail-wrap .dt-status-badge.status-pending { + background: #fdf4d4; + color: #ef9546; +} +.detail-wrap .dt-status-badge.status-rejected { + background: #fef2f2; + color: #ef4444; + border: 1px solid #fee2e2; +} +.detail-wrap .dt-app-desc { + font-size: 14px; + color: #64748b; + margin: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.detail-wrap .dt-info-container { + background: #ffffff; + border: 1px solid #dfdfdf; + border-radius: 30px; + box-shadow: 0px 4px 26.9px 2px rgba(192, 192, 192, 0.25); + padding: 40px; + margin-bottom: 35px; + display: flex; + flex-direction: column; + gap: 28px; + box-sizing: border-box; +} +@media (max-width: 768px) { + .detail-wrap .dt-info-container { + padding: 24px 20px; + border-radius: 20px; + gap: 20px; + } +} +.detail-wrap .dt-row { + display: flex; + gap: 28px; + width: 100%; +} +@media (max-width: 768px) { + .detail-wrap .dt-row { + flex-direction: column; + gap: 20px; + } +} +.detail-wrap .dt-col { + flex: 1; + display: flex; + flex-direction: column; + gap: 12px; + min-width: 0; +} +.detail-wrap .dt-label { + font-size: 16px; + font-weight: 700; + color: #000000; + margin: 0; +} +.detail-wrap .dt-input-group { + display: flex; + gap: 7px; + width: 100%; +} +.detail-wrap .dt-input-box { + flex: 1; + height: 47px; + background: #ededed; + border: 1px solid #dfdfdf; + border-radius: 10px; + display: flex; + align-items: center; + padding: 0 20px; + font-size: 16px; + font-weight: 300; + color: #0d0e11; + box-sizing: border-box; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.detail-wrap .dt-input-box.text-secret { + letter-spacing: 2px; + font-weight: 700; +} +@media (max-width: 768px) { + .detail-wrap .dt-input-box { + padding: 0 14px; + font-size: 14px; + } +} +.detail-wrap .dt-btn-copy { + height: 47px; + background: #d5efff; + border: none; + border-radius: 10px; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; + padding: 0 20px; + font-size: 16px; + font-weight: 500; + color: #2a69de; + cursor: pointer; + transition: background 0.2s ease; + flex-shrink: 0; +} +.detail-wrap .dt-btn-copy:hover { + background: rgb(189.6, 230.0857142857, 255); +} +.detail-wrap .dt-btn-copy svg { + color: #2a69de; + flex-shrink: 0; +} +@media (max-width: 768px) { + .detail-wrap .dt-btn-copy { + padding: 0 12px; + font-size: 14px; + } +} +.detail-wrap .dt-btn-lookup { + width: 100%; + height: 47px; + background: #2a69de; + border: none; + border-radius: 10px; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; + font-size: 16px; + font-weight: 500; + color: #ffffff; + cursor: pointer; + transition: background 0.2s ease; +} +.detail-wrap .dt-btn-lookup:hover { + background: #1b4ab7; +} +.detail-wrap .dt-btn-lookup svg { + color: #ffffff; + flex-shrink: 0; +} +.detail-wrap .dt-api-list-box { + width: 100%; + background: #ffffff; + border: 1px solid #dfdfdf; + border-radius: 10px; + padding: 0 20px; + box-sizing: border-box; + max-height: 240px; + overflow-y: auto; +} +.detail-wrap .dt-api-list-box::-webkit-scrollbar { + width: 6px; +} +.detail-wrap .dt-api-list-box::-webkit-scrollbar-track { + background: transparent; +} +.detail-wrap .dt-api-list-box::-webkit-scrollbar-thumb { + background: #cbd5e1; + border-radius: 3px; +} +.detail-wrap .dt-api-list-box::-webkit-scrollbar-thumb:hover { + background: #94a3b8; +} +.detail-wrap .dt-api-item { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px 0; + border-bottom: 1px solid #efefef; +} +.detail-wrap .dt-api-item:last-child { + border-bottom: none; +} +.detail-wrap .dt-api-name { + font-size: 16px; + font-weight: 500; + color: #0d0e11; + margin: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + padding-right: 20px; + flex: 1; +} +@media (max-width: 768px) { + .detail-wrap .dt-api-name { + font-size: 14px; + } +} +.detail-wrap .dt-api-status-badge { + height: 18px; + background: #ecf0fa; + border: 1px solid #4685ef; + border-radius: 17px; + padding: 0 10px; + font-size: 10px; + font-weight: 500; + color: #2a69de; + display: inline-flex; + align-items: center; + justify-content: center; + white-space: nowrap; +} +.detail-wrap .dt-api-status-badge.status-pending { + background: #eceff4; + border: 1px solid #dee3e7; + color: #515151; +} +.detail-wrap .dt-empty-message { + padding: 30px; + text-align: center; + color: #64748b; + font-size: 14px; + margin: 0; +} +.detail-wrap .dt-cancel-notice { + margin: 10px 0 0; + text-align: center; + font-size: 14px; + color: #64748b; +} +.detail-wrap .info-notice-box { + background: #ecf0fa; + border-radius: 7px; + padding: 13px 14px 12px; + display: flex; + align-items: center; + gap: 13px; + margin-bottom: 32px; +} +.detail-wrap .info-notice-box svg { + width: 24px; + height: 24px; + flex-shrink: 0; +} +.detail-wrap .info-notice-box p { + color: #4685ef; + font-size: 16px; + font-weight: 500; + margin: 0; +} +.detail-wrap .info-notice-box.danger { + background: #fdf3f3; +} +.detail-wrap .info-notice-box.danger p { + color: #f4253c; +} +.detail-wrap .dt-actions { + display: flex; + justify-content: center; + align-items: center; + gap: 12px; + margin-top: 10px; +} +@media (max-width: 768px) { + .detail-wrap .dt-actions { + flex-direction: column; + width: 100%; + } + .detail-wrap .dt-actions .dt-btn-gray, + .detail-wrap .dt-actions .dt-btn-red, + .detail-wrap .dt-actions .dt-btn-blue { + width: 100%; + } +} +.detail-wrap .dt-btn-gray { + width: 156px; + height: 45px; + background: #bdc7cf; + border: none; + border-radius: 10px; + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 16px; + font-weight: 500; + color: #ffffff; + cursor: pointer; + text-decoration: none; + transition: background 0.2s ease; +} +.detail-wrap .dt-btn-gray:hover { + background: rgb(170.6589473684, 183.4378947368, 193.6610526316); +} +.detail-wrap .dt-btn-red { + width: 156px; + height: 45px; + background: #ff615c; + border: none; + border-radius: 10px; + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 16px; + font-weight: 500; + color: #ffffff; + cursor: pointer; + transition: background 0.2s ease; +} +.detail-wrap .dt-btn-red:hover { + background: rgb(255, 70.0915337423, 64.24); +} +.detail-wrap .dt-btn-blue { + width: 156px; + height: 45px; + background: #2a69de; + border: none; + border-radius: 10px; + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 16px; + font-weight: 500; + color: #ffffff; + cursor: pointer; + text-decoration: none; + transition: background 0.2s ease; +} +.detail-wrap .dt-btn-blue:hover { + background: #1b4ab7; +} + @media (max-width: 768px) { .app-management-container { padding: 24px 16px; @@ -13463,9 +18228,7 @@ input[type=checkbox]:checked + .custom-checkbox { } .app-list-title { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 28px; - font-weight: 700; color: #1A1A2E; margin: 0; } @@ -13589,7 +18352,7 @@ input[type=checkbox]:checked + .custom-checkbox { } .app-list-name { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 18px; font-weight: 700; color: #1A1A2E; @@ -13605,7 +18368,7 @@ input[type=checkbox]:checked + .custom-checkbox { } .app-list-description { - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 14px; font-weight: 400; color: #6e7780; @@ -13648,6 +18411,280 @@ input[type=checkbox]:checked + .custom-checkbox { } } +/* App Card Figma Styles */ +.app-management-layout { + display: flex; + gap: 36px; + max-width: 1200px; + margin: 40px auto 100px; + font-family: "Spoqa Han Sans Neo", "Noto Sans CJK KR", sans-serif; +} +@media (max-width: 768px) { + .app-management-layout { + flex-direction: column; + gap: 20px; + margin: 20px auto 50px; + padding: 0 16px; + } +} + +.app-management-content { + flex: 1; + min-width: 0; + width: 100%; +} + +.app-management-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 24px; +} +@media (max-width: 768px) { + .app-management-header { + margin-bottom: 16px; + } + .app-management-header h2 { + font-size: 20px !important; + } +} +.app-management-header h2 { + font-size: 24px; + font-weight: 700; + color: #000; + margin: 0; +} + +.btn-app-create-figma { + background-color: #2a69de; + color: #ffffff; + border-radius: 7px; + padding: 0 20px; + width: 122px; + height: 35px; + display: flex; + justify-content: center; + align-items: center; + font-size: 12px; + font-weight: 700; + border: none; + cursor: pointer; + text-decoration: none; + transition: background-color 0.2s; +} +.btn-app-create-figma:hover { + background-color: #1b4ab7; +} + +.app-list-container-figma { + display: flex; + flex-direction: column; + gap: 20px; +} + +.app-card-figma { + display: flex; + align-items: center; + gap: 14px; + height: 107px; + padding: 20px 31px 20px 17px; + background: #ffffff; + border: 1px solid #dfdfdf; + border-radius: 20px; + box-shadow: 0px 4px 5.45px rgba(192, 192, 192, 0.25); + text-decoration: none; + color: inherit; + transition: transform 0.2s ease, box-shadow 0.2s ease; +} +@media (max-width: 768px) { + .app-card-figma { + height: auto; + padding: 16px; + border-radius: 12px; + flex-direction: row; + align-items: flex-start; + } +} +.app-card-figma:hover { + transform: translateY(-2px); + box-shadow: 0px 6px 15px rgba(192, 192, 192, 0.35); +} + +.app-card-icon-box { + width: 68px; + height: 67px; + background: #f9f9f9; + border: 1px solid #dee3e7; + border-radius: 10px; + display: flex; + justify-content: center; + align-items: center; + flex-shrink: 0; +} +@media (max-width: 768px) { + .app-card-icon-box { + width: 56px; + height: 56px; + border-radius: 8px; + } +} +.app-card-icon-box img, +.app-card-icon-box svg { + width: 46px; + height: 46px; + object-fit: cover; + border-radius: 4px; +} +@media (max-width: 768px) { + .app-card-icon-box img, + .app-card-icon-box svg { + width: 36px; + height: 36px; + } +} + +.app-card-info { + flex: 1; + display: flex; + flex-direction: column; + gap: 10px; + min-width: 0; +} +@media (max-width: 768px) { + .app-card-info { + gap: 6px; + } +} + +.app-card-header { + display: flex; + justify-content: space-between; + align-items: center; +} + +.app-card-title { + font-size: 16px; + font-weight: 500; + color: #000; + margin: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +@media (max-width: 768px) { + .app-card-title { + font-size: 14px; + } +} + +.app-card-badge-container { + display: flex; + flex-direction: column; + align-items: flex-end; + gap: 4px; +} + +.app-card-badge { + height: 20px; + border-radius: 5px; + padding: 0 8px; + font-size: 10px; + font-weight: 500; + display: flex; + align-items: center; + justify-content: center; + white-space: nowrap; +} +.app-card-badge.badge-pending { + background: #fdf4d4; + color: #ef9546; +} +.app-card-badge.badge-requested { + background: #ffebeb; + color: #f43f5e; +} +.app-card-badge.badge-approved { + background: #1b4ab7; + color: #ffffff; +} +.app-card-badge.badge-inactive { + background: #f3f4f6; + color: #6b7280; +} + +.app-card-desc { + font-size: 14px; + color: #64748b; + margin: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + flex: 1; + min-width: 0; +} +@media (max-width: 768px) { + .app-card-desc { + font-size: 12px; + } +} + +.app-card-footer-row { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + min-width: 0; +} +@media (max-width: 768px) { + .app-card-footer-row { + flex-direction: column; + align-items: flex-start; + gap: 4px; + } +} + +.app-card-expected-date { + font-size: 14px; + color: #64748b; + margin-left: 16px; + flex-shrink: 0; + white-space: nowrap; +} +@media (max-width: 768px) { + .app-card-expected-date { + margin-left: 0; + font-size: 11px; + } +} + +.app-card-expected-date { + font-size: 12px; + color: #64748b; + text-align: right; +} + +.app-list-empty-figma { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 60px 0; + background: #ffffff; + border: 1px solid #dfdfdf; + border-radius: 20px; + text-align: center; +} +.app-list-empty-figma h3 { + font-size: 18px; + color: #000; + margin: 16px 0 8px; +} +.app-list-empty-figma p { + font-size: 14px; + color: #64748b; + margin: 0; +} + .notice-page { min-height: calc(100vh - 140px); background: #F8FAFC; @@ -13717,26 +18754,110 @@ input[type=checkbox]:checked + .custom-checkbox { } .notice-detail-header { - display: flex; - justify-content: space-between; - align-items: center; - padding-bottom: 16px; + padding-bottom: 24px; border-bottom: 1px solid #212529; - margin-bottom: 32px; } @media (max-width: 1024px) { .notice-detail-header { - flex-direction: column; + padding-bottom: 16px; + } +} + +.notice-detail-date { + font-size: 16px; + color: #767e89; + font-weight: 400; + margin: 0 0 17px 0; +} +@media (max-width: 1024px) { + .notice-detail-date { + font-size: 14px; + margin: 0 0 12px 0; + } +} + +.notice-detail-title-wrapper { + display: flex; + align-items: center; + gap: 15px; +} +@media (max-width: 1024px) { + .notice-detail-title-wrapper { align-items: flex-start; gap: 8px; } } -.notice-detail-title { - font-size: 24px; +.notice-type-badge { + display: inline-flex; + align-items: center; + justify-content: center; + height: 20px; + width: 50px; + margin-right: 6px; + border-radius: 5px; + font-size: 10px; + font-weight: 500; + box-sizing: border-box; +} +.notice-type-badge--incident { + border: 1px solid #f1c2d6; + background: #fbe7e9; + color: #bb1026; +} +.notice-type-badge--maintenance { + background: #fdf4d4; + color: #ef9546; + border: 1px solid #ffcea1; +} +.notice-type-badge--completed { + background: #d6fbd0; + color: #11af4c; + border: 1px solid #a0d4b2; +} +.notice-type-badge--closed { + background: #f5f5f5; + color: #888888; + border: 1px solid #dedede; +} +.notice-type-badge--pending { + background: #fdf4d4; + color: #ef9546; + border: 1px solid #ffcea1; +} +.notice-type-badge--fix { + background: #e5f0ff; + color: #0a4ea3; + border: 1px solid #b3d4ff; +} +.notice-type-badge--reviewing { + background: #e5f0ff; + color: #0a4ea3; + border: 1px solid #b3d4ff; +} +.notice-type-badge--test { + background: #e6fffa; + color: #0d9488; + border: 1px solid #99f6e4; +} +.notice-type-badge--normal { + background: #f3f4f6; + color: #374151; + border: 1px solid #e5e7eb; +} +.notice-type-badge.lg { + height: 28px; + width: auto; + min-width: 50px; + padding: 0 10px; + font-size: 13px; font-weight: 700; - color: #212529; - line-height: 1; +} + +.notice-detail-title { + font-size: 28px; + font-weight: 500; + color: #000; margin: 0; } @media (max-width: 1024px) { @@ -13745,19 +18866,14 @@ input[type=checkbox]:checked + .custom-checkbox { } } -.notice-detail-date { - font-size: 24px; - font-weight: 400; - color: #515151; -} -@media (max-width: 1024px) { - .notice-detail-date { - font-size: 16px; - } -} - .notice-detail-attachment { - margin-bottom: 24px; + margin-top: 20px; + margin-bottom: 20px; + padding: 15px 20px; + background: #f8fbfe; + border-radius: 8px; + display: flex; + align-items: center; } .notice-detail-attachment .attachment-list { display: flex; @@ -13770,54 +18886,195 @@ input[type=checkbox]:checked + .custom-checkbox { } .notice-attachment-link { - display: inline-flex; + display: flex; align-items: center; - gap: 4px; - color: #515151; - font-size: 18px; + gap: 8px; text-decoration: none; + color: #333; transition: all 0.3s ease; } -@media (max-width: 1024px) { - .notice-attachment-link { - font-size: 14px; - } -} .notice-attachment-link:hover { color: #0049b4; } .notice-attachment-link svg { flex-shrink: 0; - color: #515151; + color: #333; } .notice-attachment-link:hover svg { color: #0049b4; } .notice-attachment-link .attachment-label { - margin-right: 16px; + font-weight: 500; + font-size: 14px; } .notice-attachment-link .attachment-filename { - color: #515151; + font-size: 14px; + color: #666; + word-break: break-all; +} + +.notice-detail-incident-info, +.notice-detail-affected-info { + margin-bottom: 20px; +} +.notice-detail-incident-info .detail-table, +.notice-detail-affected-info .detail-table { + width: 100%; + border-collapse: collapse; + table-layout: fixed; + border-top: none; + border-bottom: 1px solid #e3e8f0; +} +.notice-detail-incident-info .detail-table tr, +.notice-detail-affected-info .detail-table tr { + border-bottom: 1px solid #e3e8f0; +} +.notice-detail-incident-info .detail-table th, +.notice-detail-affected-info .detail-table th { + background: #f9f9f9; + height: 43px; + font-weight: 500; + font-size: 14px; + text-align: center; + color: #000; +} +.notice-detail-incident-info .detail-table td, +.notice-detail-affected-info .detail-table td { + padding: 10px 19px; + font-size: 14px; + color: #000; + word-break: break-all; +} +@media (max-width: 768px) { + .notice-detail-incident-info .detail-table, + .notice-detail-affected-info .detail-table { + display: block; + } + .notice-detail-incident-info .detail-table colgroup, + .notice-detail-affected-info .detail-table colgroup { + display: none; + } + .notice-detail-incident-info .detail-table tbody, + .notice-detail-incident-info .detail-table tr, + .notice-detail-incident-info .detail-table th, + .notice-detail-incident-info .detail-table td, + .notice-detail-affected-info .detail-table tbody, + .notice-detail-affected-info .detail-table tr, + .notice-detail-affected-info .detail-table th, + .notice-detail-affected-info .detail-table td { + display: block; + width: 100% !important; + box-sizing: border-box; + } + .notice-detail-incident-info .detail-table tr, + .notice-detail-affected-info .detail-table tr { + border-bottom: none; + } + .notice-detail-incident-info .detail-table th, + .notice-detail-affected-info .detail-table th { + text-align: left; + background: #f8fafc; + height: auto; + padding: 10px 16px; + border-bottom: 1px solid #e2e8f0; + } + .notice-detail-incident-info .detail-table td, + .notice-detail-affected-info .detail-table td { + padding: 12px 16px; + border-bottom: 1px solid #e2e8f0; + } +} + +.affected-apis-wrapper { + display: flex; + flex-direction: column; + gap: 10px; + align-items: flex-start; + width: 100%; +} + +.affected-apis-list { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 8px; + width: 100%; +} + +.affected-api-badge { + display: inline-flex; + align-items: center; + padding: 6px 16px; + background-color: #f3f6fa; + border: 1px solid #e2e8f0; + border-radius: 8px; + font-size: 13px; + color: #555555; + font-weight: 500; + word-break: break-all; + max-width: 100%; + box-sizing: border-box; +} +.affected-api-badge strong { + color: #0a4ea3; + font-weight: 700; + flex-shrink: 0; +} +.affected-api-badge.is-gw { + background-color: #ffffff; + border-style: dashed; + border-color: #cbd5e1; + color: #6b7280; +} + +.btn-affected-toggle { + font-size: 13px; + color: #ffffff; + font-weight: 500; + display: inline-flex; + align-items: center; + gap: 6px; + cursor: pointer; + padding: 8px 16px; + border-radius: 4px; + background-color: #0049b4; + border: none; + transition: all 0.2s ease; + align-self: flex-end; + margin-top: 8px; + line-height: normal; +} +.btn-affected-toggle:hover { + background-color: #003685; +} +.btn-affected-toggle .arrow { + font-size: 10px; + display: inline-block; + transition: transform 0.2s ease; +} +.btn-affected-toggle.active .arrow { + transform: rotate(180deg); } .notice-detail-content { + padding: 20px 0 60px 0; + border-bottom: 1px solid #eee; + font-size: 16px; + line-height: 1.6; + color: #333; margin-bottom: 48px; + min-height: 300px; } .notice-content-body { - background: #F6F9FB; - border-radius: 20px; - padding: 40px; - min-height: 280px; font-size: 16px; - color: #1A1A2E; - line-height: 1.8; + color: #333; + line-height: 1.6; + min-height: 280px; } @media (max-width: 1024px) { .notice-content-body { - padding: 24px; min-height: 200px; - border-radius: 12px; } } .notice-content-body p { @@ -13839,56 +19096,112 @@ input[type=checkbox]:checked + .custom-checkbox { .notice-content-body a:hover { color: #c3dfea; } -.notice-content-body ul, .notice-content-body ol { +.notice-content-body ul, +.notice-content-body ol { margin: 16px 0; padding-left: 32px; } .notice-content-body li { margin-bottom: 8px; } -@media (max-width: 768px) { - .notice-content-body { - background: #FFFFFF; - padding: 0; + +.notice-detail-timeline { + padding-bottom: 40px; + border-bottom: 1px solid #eee; + margin-bottom: 48px; +} +.notice-detail-timeline .notice-timeline-title { + margin: 0 0 16px; + font-size: 18px; + font-weight: 700; + color: #000; +} +.notice-detail-timeline .timeline-table { + width: 100%; + border-collapse: collapse; + table-layout: fixed; + border-bottom: 1px solid #e3e8f0; +} +.notice-detail-timeline .timeline-table thead th { + background: #f9f9f9; + height: 43px; + font-weight: 500; + font-size: 14px; + text-align: center; + color: #000; + border-bottom: 1px solid #e3e8f0; +} +.notice-detail-timeline .timeline-table tbody tr { + border-bottom: 1px solid #e3e8f0; +} +.notice-detail-timeline .timeline-table td { + padding: 10px 16px; + font-size: 14px; + color: #000; + vertical-align: top; +} +.notice-detail-timeline .timeline-table .timeline-ts { + color: #555; + white-space: nowrap; + font-variant-numeric: tabular-nums; +} +.notice-detail-timeline .timeline-table .timeline-body { + line-height: 1.6; + white-space: pre-line; + word-break: break-all; +} +@media (max-width: 1024px) { + .notice-detail-timeline .timeline-table { + table-layout: auto; } + .notice-detail-timeline .timeline-table colgroup { + display: none; + } + .notice-detail-timeline .timeline-table thead th, + .notice-detail-timeline .timeline-table td { + padding: 8px 10px; + font-size: 13px; + } + .notice-detail-timeline .timeline-table .timeline-ts { + white-space: normal; + } +} +.notice-detail-timeline .timeline-state { + display: inline-block; + padding: 2px 10px; + border-radius: 999px; + font-size: 12px; + font-weight: 500; + white-space: nowrap; + background: #eef1f5; + color: #4b5563; +} +.notice-detail-timeline .timeline-state.state-RESOLVED { + background: #e7f6ec; + color: #1b8c4a; +} +.notice-detail-timeline .timeline-state.state-MONITORING, .notice-detail-timeline .timeline-state.state-INVESTIGATING { + background: #fef3c7; + color: #c77800; +} +.notice-detail-timeline .timeline-state.state-IDENTIFIED { + background: #fdeee2; + color: #c95a0f; +} +.notice-detail-timeline .timeline-state.state-CANCELED { + background: #eef1f5; + color: #6b7280; } .notice-detail-actions { display: flex; justify-content: center; + margin-top: 40px; } .btn-notice-list { - display: flex; - align-items: center; - justify-content: center; - width: 200px; + width: 247px; height: 60px; - background: #E5E7EB; - border: none; - border-radius: 12px; - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - font-size: 18px; - font-weight: 700; - color: #5F666C; - cursor: pointer; - transition: all 0.3s ease; -} -@media (max-width: 1024px) { - .btn-notice-list { - width: auto; - min-width: 80px; - padding: 0 16px; - height: 44px; - font-size: 15px; - border-radius: 8px; - } -} -.btn-notice-list:hover { - background: rgb(215.8869565217, 218.8956521739, 224.9130434783); -} -.btn-notice-list:active { - transform: scale(0.98); } .notice-detail-card { @@ -14014,7 +19327,12 @@ input[type=checkbox]:checked + .custom-checkbox { .notice-detail-card .detail-content .content-body p:last-child { margin-bottom: 0; } -.notice-detail-card .detail-content .content-body h1, .notice-detail-card .detail-content .content-body h2, .notice-detail-card .detail-content .content-body h3, .notice-detail-card .detail-content .content-body h4, .notice-detail-card .detail-content .content-body h5, .notice-detail-card .detail-content .content-body h6 { +.notice-detail-card .detail-content .content-body h1, +.notice-detail-card .detail-content .content-body h2, +.notice-detail-card .detail-content .content-body h3, +.notice-detail-card .detail-content .content-body h4, +.notice-detail-card .detail-content .content-body h5, +.notice-detail-card .detail-content .content-body h6 { margin-top: 32px; margin-bottom: 16px; font-weight: 600; @@ -14033,7 +19351,8 @@ input[type=checkbox]:checked + .custom-checkbox { .notice-detail-card .detail-content .content-body a:hover { color: #c3dfea; } -.notice-detail-card .detail-content .content-body ul, .notice-detail-card .detail-content .content-body ol { +.notice-detail-card .detail-content .content-body ul, +.notice-detail-card .detail-content .content-body ol { margin: 16px 0; padding-left: 32px; } @@ -14281,9 +19600,8 @@ input[type=checkbox]:checked + .custom-checkbox { } .inquiry-content-body { - background: #F6F9FB; border-radius: 20px; - padding: 40px; + padding: 10px 20px; min-height: 200px; font-size: 16px; color: #1A1A2E; @@ -14427,7 +19745,7 @@ input[type=checkbox]:checked + .custom-checkbox { background: #E5E7EB; border: none; border-radius: 12px; - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 18px; font-weight: 700; color: #5F666C; @@ -14460,7 +19778,7 @@ input[type=checkbox]:checked + .custom-checkbox { background: #0049b4; border: none; border-radius: 12px; - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 18px; font-weight: 700; color: #FFFFFF; @@ -14493,7 +19811,7 @@ input[type=checkbox]:checked + .custom-checkbox { background: #DC3545; border: none; border-radius: 12px; - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 18px; font-weight: 700; color: #FFFFFF; @@ -14557,7 +19875,7 @@ input[type=checkbox]:checked + .custom-checkbox { } .file-upload-inline .file-display-text { flex: 1; - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 16px; color: #94A3B8; overflow: hidden; @@ -14600,7 +19918,7 @@ input[type=checkbox]:checked + .custom-checkbox { background: #3ba4ed; border: none; border-radius: 12px; - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 18px; font-weight: 700; color: #FFFFFF; @@ -14925,7 +20243,7 @@ input[type=checkbox]:checked + .custom-checkbox { justify-content: center; align-items: center; gap: 16px; - margin-top: 32px; + margin-top: 40px; } @media (max-width: 768px) { .inquiry-actions { @@ -14933,6 +20251,55 @@ input[type=checkbox]:checked + .custom-checkbox { align-items: stretch; } } +.inquiry-actions .btn-inquiry-new { + display: flex; + align-items: center; + justify-content: center; + width: 247px; + height: 60px; + background: #2a69de; + color: #fff; + border: none; + font-size: 20px; + font-weight: 700; + border-radius: 10px; + cursor: pointer; + text-decoration: none; + transition: all 0.3s ease; +} +@media (max-width: 1024px) { + .inquiry-actions .btn-inquiry-new { + width: 100%; + height: 50px; + font-size: 16px; + } +} +.inquiry-actions .btn-inquiry-new:hover { + background: #1d52b5; + transform: translateY(-2px); +} +.inquiry-actions .btn-inquiry-new--outline { + background: #fff; + color: #2a69de; + border: 1px solid #2a69de; + width: 120px; + height: 50px; + font-size: 16px; +} +.inquiry-actions .btn-inquiry-new--outline:hover { + background: #f8faff; +} +.inquiry-actions .btn-inquiry-new--danger { + background: #fff; + color: #bb1026; + border: 1px solid #bb1026; + width: 120px; + height: 50px; + font-size: 16px; +} +.inquiry-actions .btn-inquiry-new--danger:hover { + background: #fdf5f6; +} .inquiry-actions .actions-left, .inquiry-actions .actions-right { display: flex; @@ -14970,85 +20337,120 @@ input[type=checkbox]:checked + .custom-checkbox { } .inquiry-detail-container { - min-height: 600px; - margin: 0 auto; + padding: 0; + background: transparent; + box-shadow: none; + min-height: auto; + margin: 0; } - -.inquiry-detail-card { - background: #FFFFFF; - border-radius: 12px; - box-shadow: 0 4px 12px rgba(75, 155, 255, 0.1); - overflow: hidden; - margin-bottom: 32px; +.inquiry-detail-container .notice-detail-header { + border-bottom: 1px solid #000; + padding-bottom: 25px; } -.inquiry-detail-card .detail-header { - padding: 48px 40px 32px; - border-bottom: 2px solid #E2E8F0; -} -@media (max-width: 768px) { - .inquiry-detail-card .detail-header { - padding: 40px 24px 16px; - } -} -.inquiry-detail-card .detail-header .inquiry-title { - font-size: 32px; - font-weight: 700; - color: #1A1A2E; - line-height: 1.2; - margin-bottom: 24px; -} -@media (max-width: 768px) { - .inquiry-detail-card .detail-header .inquiry-title { - font-size: 24px; - } -} -.inquiry-detail-card .detail-header .inquiry-meta { +.inquiry-detail-container .notice-detail-header .inquiry-header-meta { display: flex; - align-items: center; - gap: 24px; - flex-wrap: wrap; - font-size: 14px; - color: #64748B; + gap: 6px; + font-size: 16px; + color: #767e89; + margin-bottom: 17px; } -@media (max-width: 768px) { - .inquiry-detail-card .detail-header .inquiry-meta { - font-size: 12px; - gap: 16px; +.inquiry-detail-container .notice-detail-header .notice-detail-title-wrapper { + display: flex; + align-items: flex-end; + gap: 15px; +} +@media (max-width: 1024px) { + .inquiry-detail-container .notice-detail-header .notice-detail-title-wrapper { + flex-direction: column; + align-items: flex-start; + gap: 8px; } } -.inquiry-detail-card .detail-header .inquiry-meta .meta-item { - display: flex; - align-items: center; - gap: 4px; -} -.inquiry-detail-card .detail-header .inquiry-meta .meta-item i { - color: #0049b4; -} -.inquiry-detail-card .detail-header .inquiry-meta .meta-item strong { - color: #1A1A2E; +.inquiry-detail-container .notice-detail-header .notice-detail-title { + margin: 0; + font-size: 28px; font-weight: 500; } -.inquiry-detail-card .detail-attachments { +@media (max-width: 1024px) { + .inquiry-detail-container .notice-detail-header .notice-detail-title { + font-size: 22px; + } +} +.inquiry-detail-container .notice-detail-body { + padding-top: 20px; + border-bottom: 1px solid #dfdfdf; + font-size: 14px; + line-height: 20px; + color: #000; +} +.inquiry-detail-container .notice-detail-body img { + max-width: 100%; + height: auto; +} +.inquiry-detail-container .inquiry-response-section { + padding-top: 40px; + padding-bottom: 40px; + border-bottom: 1px solid #dfdfdf; +} +.inquiry-detail-container .inquiry-response-section .inquiry-response-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 0px; + border: none; +} +.inquiry-detail-container .inquiry-response-section .inquiry-response-header .response-admin { + display: flex; + gap: 9px; + align-items: center; +} +.inquiry-detail-container .inquiry-response-section .inquiry-response-header .response-admin .admin-label { + font-size: 14px; + font-weight: 500; + color: #000; +} +.inquiry-detail-container .inquiry-response-section .inquiry-response-header .response-admin .admin-badge { + background: #ecf1ff; + color: #2a69de; + font-size: 9px; + font-weight: 700; + padding: 3px 8px; + border-radius: 10px; +} +.inquiry-detail-container .inquiry-response-section .inquiry-response-header .response-meta { + color: #767e89; + font-size: 14px; +} +.inquiry-detail-container .inquiry-response-section .inquiry-response-body { + background: #f4f5f9; + border-radius: 10px; + padding: 24px; + font-size: 14px; + line-height: 20px; + color: #000; +} + +.detail-attachments { padding: 24px 40px; background: #F8FAFC; border-bottom: 1px solid #E2E8F0; } @media (max-width: 768px) { - .inquiry-detail-card .detail-attachments { + .detail-attachments { padding: 16px 24px; } } -.inquiry-detail-card .detail-attachments .attachment-list { +.detail-attachments .attachment-list { display: flex; flex-direction: column; gap: 8px; } -.inquiry-detail-card .detail-attachments .attachment-list .attachment-item { +.detail-attachments .attachment-list .attachment-item { display: flex; align-items: center; gap: 8px; } -.inquiry-detail-card .detail-attachments .attachment-list .attachment-item a { +.detail-attachments .attachment-list .attachment-item a { color: #1A1A2E; text-decoration: none; font-size: 14px; @@ -15058,37 +20460,38 @@ input[type=checkbox]:checked + .custom-checkbox { gap: 4px; } @media (max-width: 768px) { - .inquiry-detail-card .detail-attachments .attachment-list .attachment-item a { + .detail-attachments .attachment-list .attachment-item a { font-size: 12px; } } -.inquiry-detail-card .detail-attachments .attachment-list .attachment-item a:hover { +.detail-attachments .attachment-list .attachment-item a:hover { color: #0049b4; } -.inquiry-detail-card .detail-attachments .attachment-list .attachment-item a:hover i { +.detail-attachments .attachment-list .attachment-item a:hover i { transform: translateY(-2px); } -.inquiry-detail-card .detail-attachments .attachment-list .attachment-item a i { +.detail-attachments .attachment-list .attachment-item a i { color: #0049b4; transition: all 0.3s ease; } -.inquiry-detail-card .detail-content { + +.detail-content { padding: 48px 40px; min-height: 200px; } @media (max-width: 768px) { - .inquiry-detail-card .detail-content { + .detail-content { padding: 40px 24px; } } -.inquiry-detail-card .detail-content .content-body { +.detail-content .content-body { font-size: 16px; color: #1A1A2E; line-height: 1.8; white-space: pre-line; } @media (max-width: 768px) { - .inquiry-detail-card .detail-content .content-body { + .detail-content .content-body { font-size: 14px; } } @@ -15286,7 +20689,7 @@ input[type=checkbox]:checked + .custom-checkbox { border: 1px solid #E2E8F0; border-radius: 8px; font-size: 16px; - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; resize: vertical; transition: all 0.3s ease; } @@ -15319,6 +20722,84 @@ input[type=checkbox]:checked + .custom-checkbox { } } +.djb-board-write-container { + padding: 0; +} +.djb-board-write-container .djb-board-write-header { + margin-bottom: 30px; + padding-bottom: 20px; +} +.djb-board-write-container .djb-board-write-header .title { + font-size: 24px; + font-weight: 700; + color: #000; + margin-bottom: 8px; +} +.djb-board-write-container .djb-board-write-header .subtitle { + font-size: 14px; + color: #7f8a95; +} +.djb-board-write-container .djb-board-form .form-group { + margin-bottom: 30px; +} +.djb-board-write-container .form-actions { + display: flex; + justify-content: flex-end; + gap: 10px; + margin-top: 40px; +} +@media (max-width: 768px) { + .djb-board-write-container .form-actions { + flex-direction: column; + align-items: stretch; + width: 100%; + gap: 12px; + } +} +.djb-board-write-container .form-actions .btn-cancel { + width: 100px; + height: 48px; + background-color: #fff; + border: 1px solid #DFDFDF; + border-radius: 8px; + color: #515961; + font-size: 15px; + font-weight: 700; + cursor: pointer; + transition: background-color 0.2s; + display: block; +} +.djb-board-write-container .form-actions .btn-cancel:hover { + background-color: #f8f9fa; +} +@media (max-width: 768px) { + .djb-board-write-container .form-actions .btn-cancel { + width: 100%; + order: 2; + } +} +.djb-board-write-container .form-actions .btn-submit { + width: 100px; + height: 48px; + background-color: #2a69de; + border: 1px solid #2a69de; + border-radius: 8px; + color: #fff; + font-size: 15px; + font-weight: 700; + cursor: pointer; +} +.djb-board-write-container .form-actions .btn-submit:hover { + background-color: rgb(33.643902439, 97.8731707317, 217.156097561); +} +@media (max-width: 768px) { + .djb-board-write-container .form-actions .btn-submit { + width: 100%; + order: 1; + max-width: 100%; + } +} + .org-register-page { min-height: 100vh; padding: 0; @@ -15342,6 +20823,45 @@ input[type=checkbox]:checked + .custom-checkbox { padding: 24px 16px; } } +.org-register-container .invitation_alert_banner { + background-color: #f0f8ff; + border: 1.5px solid #2196F3; + border-radius: 12px; + padding: 24px; + margin: 20px 0; + box-shadow: 0 8px 24px rgba(33, 150, 243, 0.08); + animation: fadeInUp 0.6s cubic-bezier(0.16, 1, 0.3, 1) both; + transform: translateY(15px); + opacity: 0; + margin-bottom: 60px; +} +.org-register-container .invitation_alert_banner .banner-content-wrapper { + display: flex; + align-items: center; + gap: 16px; +} +.org-register-container .invitation_alert_banner .banner-icon-area { + display: flex; + align-items: center; + justify-content: center; +} +.org-register-container .invitation_alert_banner .banner-icon-area svg { + animation: pulseIcon 2s infinite ease-in-out; +} +.org-register-container .invitation_alert_banner .banner-text-area strong { + color: #1976D2; + font-size: 17px; + display: block; + margin-bottom: 4px; +} +.org-register-container .invitation_alert_banner .banner-text-area p { + margin: 0; + color: #475569; + font-size: 14px; + line-height: 1.5; + display: flex; + align-items: center; +} .org-register-title { font-size: 40px; @@ -15392,7 +20912,7 @@ input[type=checkbox]:checked + .custom-checkbox { background: none; padding: 0 0 16px 0; border-radius: 0; - border-bottom: 2px solid #212529; + border-bottom: 1px solid #818181; margin-bottom: 32px; } .org-section-header--agreement h3 { @@ -15420,76 +20940,69 @@ input[type=checkbox]:checked + .custom-checkbox { } .org-info-notice { + background: #FFF5F5; + border: 1px solid #FED7D7; border-radius: 8px; - margin-bottom: 16px; + padding: 14px 20px; + display: flex; + align-items: center; + gap: 12px; + margin-top: 16px; + margin-bottom: 32px; } -.org-info-notice ul { - list-style: none; - padding: 0; - margin: 0; +.org-info-notice .notice-icon-wrapper { + display: flex; + align-items: center; + justify-content: center; + color: #E53E3E; + flex-shrink: 0; } -.org-info-notice ul li { - position: relative; - padding-left: 24px; - color: #64748B; +.org-info-notice .notice-text { + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 14px; - line-height: 1.6; -} -.org-info-notice ul li::before { - content: "•"; - position: absolute; - left: 0; - color: #0049b4; - font-weight: 700; -} -.org-info-notice ul li + li { - margin-top: 8px; + font-weight: 500; + color: #C53030; + line-height: 1.5; + margin: 0; } .org-form-group { - margin-bottom: 16px; + margin-bottom: 26px; display: flex; - align-items: flex-start; - gap: 20px; + flex-direction: column; + align-items: stretch; + gap: 13px; } .org-form-group:last-child { margin-bottom: 0; } -@media (max-width: 768px) { - .org-form-group { - flex-direction: column; - gap: 10px; - margin-bottom: 12px; - } -} .org-form-label { display: flex; align-items: center; gap: 8px; - font-size: 15px; - font-weight: 400; - color: #1A1A2E; - min-width: 160px; - padding-top: 10px; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-size: 16px; + font-weight: 700; + color: #1A202C; + width: auto; + margin: 0; + padding: 0; } .org-form-label .required-badge { - display: inline-flex; - align-items: center; - justify-content: center; - padding: 2px 8px; - background: #3BA4ED; - color: #FFFFFF; - font-size: 12px; - font-weight: 500; - border-radius: 4px; - line-height: 1.2; + visibility: hidden; + position: relative; + width: 10px; } -@media (max-width: 768px) { - .org-form-label { - min-width: auto; - padding-top: 0; - } +.org-form-label .required-badge::after { + content: "*"; + visibility: visible; + position: absolute; + left: 4px; + top: 0; + color: #f4253c; + font-size: 16px; + font-weight: 700; } .org-form-input-wrapper { @@ -15508,6 +21021,8 @@ input[type=checkbox]:checked + .custom-checkbox { display: flex; align-items: center; gap: 16px; + width: 100%; + max-width: 650px; } .org-input-row .org-form-input, .org-input-row .org-compound-input, @@ -15536,18 +21051,20 @@ input[type=checkbox]:checked + .custom-checkbox { .org-form-input { width: 100%; - padding: 8px 12px; - border: 1px solid #E2E8F0; + padding: 10px 16px; + border: 1px solid #CBD5E0; border-radius: 8px; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 14px; - color: #1A1A2E; + color: #1A202C; transition: all 0.3s ease; - background: #FFFFFF; + background: #FCFDFD; } .org-form-input:focus { outline: none; - border-color: #0049b4; - box-shadow: 0 0 0 3px rgba(75, 155, 255, 0.1); + border-color: #0049B4; + box-shadow: 0 0 0 3px rgba(0, 73, 180, 0.15); + background: #FFFFFF; } .org-form-input:disabled { background: #F8FAFC; @@ -15561,7 +21078,7 @@ input[type=checkbox]:checked + .custom-checkbox { border-color: #6BCF7F; } .org-form-input::placeholder { - color: #94A3B8; + color: #A0AEC0; } .org-compound-input { @@ -15570,9 +21087,12 @@ input[type=checkbox]:checked + .custom-checkbox { gap: 8px; width: 100%; } +.org-compound-input.phone-input-group { + flex: none; + width: auto; +} .org-compound-input .org-form-input, .org-compound-input .org-form-select { - flex: 1; min-width: 0; } .org-compound-input .org-form-input.phone-number { @@ -15589,6 +21109,15 @@ input[type=checkbox]:checked + .custom-checkbox { background-repeat: no-repeat; background-position: right 8px center; } +.org-compound-input .org-form-select.phone-prefix { + flex: 1; + min-width: 90px; + max-width: 130px; + padding-right: 28px; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%2364748B' d='M6 9L1 4h10z'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 8px center; +} .org-compound-input .separator { color: #64748B; font-weight: 600; @@ -15608,27 +21137,29 @@ input[type=checkbox]:checked + .custom-checkbox { .org-form-select { width: 100%; - padding: 8px 12px; + height: 48px; + padding: 0 16px; border: 1px solid #E2E8F0; - border-radius: 8px; + border-radius: 10px; font-size: 14px; - color: #1A1A2E; + color: #212529; background: #FFFFFF; cursor: pointer; transition: all 0.3s ease; appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%2364748B' d='M6 9L1 4h10z'/%3E%3C/svg%3E"); background-repeat: no-repeat; - background-position: right 24px center; + background-position: right 16px center; padding-right: 48px; } .org-form-select:focus { outline: none; - border-color: #0049b4; - box-shadow: 0 0 0 3px rgba(75, 155, 255, 0.1); + border-color: #3ba4ed; + box-shadow: 0 0 0 3px rgba(59, 164, 237, 0.12); } .org-form-select:disabled { - background-color: #F8FAFC; + background-color: #F8F9FA; + border-color: #E2E8F0; color: #94A3B8; cursor: not-allowed; } @@ -15711,18 +21242,22 @@ input[type=checkbox]:checked + .custom-checkbox { } .org-btn-check, .org-file-label { - padding: 8px 16px; + padding: 10px 20px; width: auto; min-width: 100px; - background: #A4D6EA; color: #FFFFFF; - border: 1px solid #A4D6EA; border-radius: 8px; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 14px; + font-weight: 700; text-align: center; justify-content: center; + background: #3BA4ED; + border: 1px solid #3BA4ED; } .org-btn-check:not(:disabled):hover, .org-file-label:not(:disabled):hover { + background: #008BC0; + border-color: #008BC0; color: #FFFFFF; } @media (max-width: 768px) { @@ -15817,10 +21352,9 @@ input[type=checkbox]:checked + .custom-checkbox { .org-action-buttons { display: flex; gap: 24px; - justify-content: center; + justify-content: end; margin-top: 64px; padding-top: 48px; - border-top: 1px solid #E2E8F0; } .org-action-buttons .btn { width: 220px; @@ -15872,14 +21406,17 @@ input[type=checkbox]:checked + .custom-checkbox { padding: 8px 16px; border-radius: 6px; font-size: 14px; + display: none; } .org-validation-message.success { background: rgba(107, 207, 127, 0.1); color: #6BCF7F; + display: block; } .org-validation-message.error { background: rgba(255, 107, 107, 0.1); color: #FF6B6B; + display: block; } .org-loading-overlay { @@ -15942,6 +21479,7 @@ input[type=checkbox]:checked + .custom-checkbox { flex-direction: column; gap: 20px; margin-bottom: 90px; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; } .agreement-all-section { @@ -16060,13 +21598,14 @@ input[type=checkbox]:checked + .custom-checkbox { .agreement-content { margin-top: 16px; - background: #EDF9FE; + background: #f9f9f9; + border-radius: 10px; + padding: 22px; } .agreement-scroll { - max-height: 300px; + max-height: 400px; overflow-y: auto; - padding: 24px; } .agreement-scroll::-webkit-scrollbar { width: 8px; @@ -16087,7 +21626,12 @@ input[type=checkbox]:checked + .custom-checkbox { line-height: 1.8; color: #64748B; } -.agreement-scroll div h1, .agreement-scroll div h2, .agreement-scroll div h3, .agreement-scroll div h4, .agreement-scroll div h5, .agreement-scroll div h6 { +.agreement-scroll div h1, +.agreement-scroll div h2, +.agreement-scroll div h3, +.agreement-scroll div h4, +.agreement-scroll div h5, +.agreement-scroll div h6 { color: #1A1A2E; margin-top: 24px; margin-bottom: 16px; @@ -16096,7 +21640,8 @@ input[type=checkbox]:checked + .custom-checkbox { .agreement-scroll div p { margin-bottom: 16px; } -.agreement-scroll div ul, .agreement-scroll div ol { +.agreement-scroll div ul, +.agreement-scroll div ol { margin-left: 24px; margin-bottom: 16px; } @@ -16105,7 +21650,8 @@ input[type=checkbox]:checked + .custom-checkbox { border-collapse: collapse; margin-bottom: 16px; } -.agreement-scroll div table th, .agreement-scroll div table td { +.agreement-scroll div table th, +.agreement-scroll div table td { border: 1px solid #E2E8F0; padding: 8px; text-align: left; @@ -16141,6 +21687,25 @@ input[type=checkbox]:checked + .custom-checkbox { padding: 16px; } } +.register-card-wrapper { + background: #FFFFFF; + border: 1px solid rgba(226, 232, 240, 0.8); + border-radius: 20px; + padding: 48px 40px; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.03); + margin-top: 30px; + margin-bottom: 60px; + width: 100%; +} +@media (max-width: 768px) { + .register-card-wrapper { + padding: 24px 16px; + border-radius: 12px; + margin-top: 15px; + margin-bottom: 40px; + } +} + .page-header { margin-bottom: 64px; text-align: center; @@ -16150,11 +21715,84 @@ input[type=checkbox]:checked + .custom-checkbox { margin-bottom: 48px; } } -.page-header .page-header-content { - max-width: 800px; - margin: 0 auto; + +.recent-apps { + margin-bottom: 70px; } -.page-header .page-title { +.recent-apps .recent-apps-title { + font-size: 18px; + font-weight: 700; + color: #0f172a; + margin-bottom: 4px; +} +.recent-apps .recent-apps-list { + list-style: none; + padding: 0; + margin: 0; + display: flex; + flex-direction: column; + gap: 12px; +} +.recent-apps .recent-apps-item { + border: 1px solid #e2e8f0; + border-radius: 8px; + overflow: hidden; + background: #ffffff; + transition: border-color 0.2s ease; +} +.recent-apps .recent-apps-item.active { + border-color: #2a69de; + box-shadow: 0 4px 12px rgba(42, 105, 222, 0.08); +} +.recent-apps .recent-apps-item.active .recent-apps-arrow { + transform: rotate(180deg); +} +.recent-apps .recent-apps-head { + display: flex; + align-items: center; + width: 100%; + padding: 8px 20px; + background: none; + border: none; + text-align: left; + cursor: pointer; + gap: 16px; +} +.recent-apps .recent-apps-head:focus { + outline: none; +} +.recent-apps .recent-apps-date { + font-size: 14px; + color: #64748b; + flex-shrink: 0; + width: 90px; +} +.recent-apps .recent-apps-subject { + font-size: 15px; + font-weight: 600; + color: #1e293b; + flex: 1; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.recent-apps .recent-apps-arrow { + color: #64748b; + transition: transform 0.2s ease; + flex-shrink: 0; +} +.recent-apps .recent-apps-body { + display: none; + padding: 20px; + background: #f8fafc; + border-top: 1px solid #f1f5f9; + font-size: 14px; + line-height: 1.6; + color: #334155; + word-break: break-word; +} + +.page-title { font-size: 40px; font-weight: 700; color: #1A1A2E; @@ -16165,37 +21803,38 @@ input[type=checkbox]:checked + .custom-checkbox { gap: 16px; } @media (max-width: 768px) { - .page-header .page-title { + .page-title { font-size: 32px; flex-direction: column; gap: 8px; } } -.page-header .page-title i { +.page-title i { color: #0049b4; font-size: 40px; } @media (max-width: 768px) { - .page-header .page-title i { + .page-title i { font-size: 32px; } } -.page-header .page-description { + +.page-description { font-size: 20px; color: #64748B; line-height: 1.6; } @media (max-width: 768px) { - .page-header .page-description { + .page-description { font-size: 16px; } } -.page-header .page-description .highlight { +.page-description .highlight { color: #0049b4; font-weight: 600; position: relative; } -.page-header .page-description .highlight::after { +.page-description .highlight::after { content: ""; position: absolute; bottom: -2px; @@ -16308,20 +21947,50 @@ input[type=checkbox]:checked + .custom-checkbox { flex-shrink: 0; } -.partnership-card .file-upload-wrapper .file-name-display.has-file { +.partnership-card .file-upload-wrapper .file-name-display.has-file, +.service-content .file-upload-wrapper .file-name-display.has-file { color: #1A1A2E; background: rgba(0, 73, 180, 0.05); border-color: #0049b4; font-weight: 500; } +.partnership-card .file-upload-wrapper .file-upload-btn, +.service-content .file-upload-wrapper .file-upload-btn { + margin: 0; + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; + font-weight: 700; + height: 55px; + width: 120px; + border-radius: 10px; + font-size: 14px; + background-color: #ffffff; + color: #4685ef; + border: 1px solid #2a69de; + transition: all 0.2s ease; +} +.partnership-card .file-upload-wrapper .file-upload-btn:hover, +.service-content .file-upload-wrapper .file-upload-btn:hover { + background-color: #f8faff; +} +.partnership-card .file-upload-wrapper .file-upload-btn:active, +.service-content .file-upload-wrapper .file-upload-btn:active { + transform: translateY(1px); +} @media (max-width: 768px) { - .partnership-card .file-upload-wrapper .file-upload-btn { + .partnership-card .file-upload-wrapper .file-upload-btn, + .service-content .file-upload-wrapper .file-upload-btn { order: 1; + width: 100%; } - .partnership-card .file-upload-wrapper .file-name-display { + .partnership-card .file-upload-wrapper .file-name-display, + .service-content .file-upload-wrapper .file-name-display { order: 2; } - .partnership-card .file-upload-wrapper .file-remove-btn { + .partnership-card .file-upload-wrapper .file-remove-btn, + .service-content .file-upload-wrapper .file-remove-btn { order: 3; } } @@ -16353,10 +22022,9 @@ input[type=checkbox]:checked + .custom-checkbox { } } .user-management-container { - max-width: 1280px; - margin: 0 auto; padding: 48px 0; margin-bottom: 40px; + min-height: 650px; } @media (max-width: 768px) { .user-management-container { @@ -16401,17 +22069,51 @@ input[type=checkbox]:checked + .custom-checkbox { margin-top: 24px; } -.user-management-container .list-table { +.user-management-container .board-table { margin-bottom: 40px; + overflow-x: auto; } -.user-management-container .list-table .row-cell a { +.user-management-container .board-table .board-table-row { + height: auto; + min-height: 50px; +} +.user-management-container .board-table .board-table-row .row-cell.row-actions { + white-space: normal; +} +.user-management-container .board-table .actions-desktop { + flex-wrap: wrap; + justify-content: center; + gap: 6px; +} +.user-management-container .board-table .row-cell a { color: #1A1A2E; text-decoration: none; transition: all 0.3s ease; } -.user-management-container .list-table .row-cell a:hover { +.user-management-container .board-table .row-cell a:hover { color: #0049b4; } +.user-management-container .board-table--user-list .board-table-header, +.user-management-container .board-table--user-list .board-table-row { + display: grid; + grid-template-columns: 24px 112px 1fr 100px 100px 287px; + padding: 0 29px; + gap: 10px; +} +.user-management-container .board-table--user-list .header-cell, +.user-management-container .board-table--user-list .row-cell { + padding: 0; + width: 100%; + box-sizing: border-box; +} +.user-management-container .board-table--user-list .header-cell:first-child, +.user-management-container .board-table--user-list .row-cell:first-child { + padding-left: 0; +} +.user-management-container .board-table--user-list .header-cell:last-child, +.user-management-container .board-table--user-list .row-cell:last-child { + padding-right: 0; +} @media (max-width: 768px) { .user-management-container .table-controls { background-color: #FFFFFF; @@ -16419,21 +22121,33 @@ input[type=checkbox]:checked + .custom-checkbox { } } .user-management-container .table-controls .search-box .btn { - height: 44px; + height: 48px; + background-color: #2a69de; + border: 1px solid #2a69de; + border-radius: 9px; + color: #fff; + font-size: 15px; + font-weight: 700; + cursor: pointer; } .user-current-badge { display: inline-flex; - height: 40px; align-items: center; - gap: 6px; - padding: 6px 12px; - background-color: rgba(167, 139, 250, 0.1); - color: #A78BFA; - border-radius: 6px; - font-size: 14px; - font-weight: 500; - min-width: 116px; + justify-content: center; + padding: 0 16px; + height: 28px; + background-color: #ecf0fa; + color: #2a69de; + border: 1px solid #4685ef; + border-radius: 5px; + font-size: 12px; + font-weight: 400; + font-family: "Spoqa Han Sans Neo", sans-serif; + gap: 4px; +} +.user-current-badge i { + display: none; } @media (max-width: 768px) { .user-current-badge { @@ -16682,6 +22396,9 @@ input[type=checkbox]:checked + .custom-checkbox { .inquiry-status-badge--pending { background-color: #8c959f; } +.inquiry-status-badge--closed { + background-color: #4a5560; +} .inquiry-actions { display: flex; @@ -16717,150 +22434,111 @@ input[type=checkbox]:checked + .custom-checkbox { display: contents; } .user-management-container .table-controls .total-count { - order: 1; - text-align: left; - font-size: 16px; - color: #000000; - padding-bottom: 6px; - border-bottom: 1.5px solid #212529; + order: 1 !important; + display: flex !important; + align-items: center !important; + width: 100% !important; + text-align: left !important; + font-size: 16px !important; + color: #000000 !important; + padding-bottom: 6px !important; + border-bottom: 1.5px solid #212529 !important; + margin-bottom: 0 !important; } .user-management-container .table-controls .total-count strong { - color: #0049b4; - font-weight: 700; - font-size: 16px; + color: #0049b4 !important; + font-weight: 700 !important; + font-size: 16px !important; } .user-management-container .table-controls .search-box { - order: 4; - width: 100%; + order: 2 !important; + width: 100% !important; + margin-top: 10px !important; } .user-management-container .table-controls .search-box .btn { - display: flex; - align-items: center; - justify-content: center; - width: 100%; - height: 44px; + display: flex !important; + align-items: center !important; + justify-content: center !important; + width: 100% !important; + height: 44px !important; + margin-top: 0 !important; } - .user-management-container .list-table { - order: 2; + .user-management-container .board-table--user-list { + order: 3 !important; + background: transparent; + border-radius: 0; + overflow: visible; + } + .user-management-container .board-table--user-list .board-table-header { + display: none !important; + } + .user-management-container .board-table--user-list .board-table-row { + display: flex !important; + flex-direction: column !important; + align-items: flex-start !important; + height: auto !important; + padding: 20px 15px !important; + gap: 12px !important; + border-bottom: 1px solid #e2e8f0 !important; background: #FFFFFF; - border-radius: 12px; - overflow: visible; + margin-bottom: 10px; } - .user-management-container .list-table .list-table-header { - display: flex; - align-items: center; - height: 40px; - padding: 0 8px; - background: #3BA4ED; - border-radius: 8px 8px 0 0; - } - .user-management-container .list-table .list-table-header .header-cell { - font-size: 14px; - font-weight: 700; - color: #FFFFFF; - padding: 0 4px; - justify-content: center; - } - .user-management-container .list-table .list-table-header .header-cell:nth-child(1) { - display: none; - } - .user-management-container .list-table .list-table-header .header-cell:nth-child(2) { - width: 60px !important; - min-width: 60px; - flex: none; - } - .user-management-container .list-table .list-table-header .header-cell:nth-child(3) { - flex: 1 !important; - min-width: 80px; - } - .user-management-container .list-table .list-table-header .header-cell:nth-child(4) { - width: 39px !important; - min-width: 39px; - flex: none; - } - .user-management-container .list-table .list-table-header .header-cell:nth-child(5) { - width: 61px !important; - min-width: 61px; - flex: none; - } - .user-management-container .list-table .list-table-header .header-cell:nth-child(6) { + .user-management-container .board-table--user-list .board-table-row .row-cell { display: flex !important; - width: 40px !important; - min-width: 40px; - flex: none; + justify-content: flex-start !important; + width: 100% !important; + font-size: 14px !important; + color: #212529 !important; + padding: 0 !important; + white-space: normal !important; } - .user-management-container .list-table .list-table-body { - display: flex; - flex-direction: column; + .user-management-container .board-table--user-list .board-table-row .row-cell::before { + content: attr(data-label) !important; + display: inline-block !important; + font-weight: 500 !important; + color: #64748b !important; + margin-right: 15px !important; + min-width: 70px !important; + flex-shrink: 0 !important; } - .user-management-container .list-table .list-table-row { - display: flex; - flex-direction: row; - align-items: center; - height: 40px; - padding: 0 8px; - gap: 0; - position: relative; + .user-management-container .board-table--user-list .board-table-row .row-cell[data-label=NO] { + display: none !important; } - .user-management-container .list-table .list-table-row:nth-child(even) { - background-color: #EDF5FD; + .user-management-container .board-table--user-list .board-table-row .row-cell[data-label=성명] { + font-size: 16px !important; + font-weight: 600 !important; + color: #0f172a !important; + margin-bottom: 4px; } - .user-management-container .list-table .list-table-row:nth-child(odd) { - background-color: #FFFFFF; + .user-management-container .board-table--user-list .board-table-row .row-cell[data-label=성명]::before { + display: none !important; } - .user-management-container .list-table .list-table-row:hover { - background-color: rgba(0, 73, 180, 0.08); - } - .user-management-container .list-table .list-table-row .row-cell { - font-size: 13px; - font-weight: 400; - color: #212529; - padding: 0 4px; - justify-content: center; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - .user-management-container .list-table .list-table-row .row-cell::before { - display: none; - } - .user-management-container .list-table .list-table-row .row-cell:nth-child(1) { - display: none; - } - .user-management-container .list-table .list-table-row .row-cell:nth-child(2) { - width: 60px !important; - min-width: 60px; - flex: none; - } - .user-management-container .list-table .list-table-row .row-cell:nth-child(2) a { - color: #212529; + .user-management-container .board-table--user-list .board-table-row .row-cell[data-label=성명] a { + color: #0f172a !important; + font-weight: 600 !important; text-decoration: none; - font-size: 13px; } - .user-management-container .list-table .list-table-row .row-cell:nth-child(3) { - flex: 1 !important; - min-width: 80px; - justify-content: flex-start; + .user-management-container .board-table--user-list .board-table-row .row-cell[data-label=성명] a:hover { + color: #2a69de !important; } - .user-management-container .list-table .list-table-row .row-cell:nth-child(4) { - width: 39px !important; - min-width: 39px; - flex: none; + .user-management-container .board-table--user-list .board-table-row .row-cell.row-actions { + margin-top: 0; + padding-top: 0 !important; + border-top: none; + justify-content: flex-end !important; } - .user-management-container .list-table .list-table-row .row-cell:nth-child(5) { - width: 61px !important; - min-width: 61px; - flex: none; + .user-management-container .board-table--user-list .board-table-row .row-cell.row-actions.has-actions { + margin-top: 8px; + padding-top: 12px !important; + border-top: 1px dashed #e2e8f0; } - .user-management-container .list-table .list-table-row .row-cell:nth-child(6), .user-management-container .list-table .list-table-row .row-cell.row-actions { - display: flex !important; - width: 40px !important; - min-width: 40px; - flex: none; - justify-content: center; - overflow: visible; + .user-management-container .board-table--user-list .board-table-row .row-cell.row-actions::before { + display: none !important; } - .user-management-container .list-table .list-table-row .row-actions { + .user-management-container .board-table--user-list .board-table-row .row-cell.row-actions .actions-desktop { + display: none !important; + } + .user-management-container .board-table--user-list .board-table-row .row-cell.row-actions .actions-mobile { display: flex !important; } .user-management-container .user-empty-state { @@ -16876,7 +22554,7 @@ input[type=checkbox]:checked + .custom-checkbox { font-size: 14px; } .user-management-container .pagination { - order: 3; + order: 4 !important; gap: 13px; padding: 24px 0; } @@ -16884,7 +22562,8 @@ input[type=checkbox]:checked + .custom-checkbox { width: 20px; height: 20px; } - .user-management-container .pagination .pagination-btn i, .user-management-container .pagination .pagination-btn svg { + .user-management-container .pagination .pagination-btn i, + .user-management-container .pagination .pagination-btn svg { font-size: 14px; width: 14px; height: 14px; @@ -16907,19 +22586,19 @@ input[type=checkbox]:checked + .custom-checkbox { display: none; } .inquiry-list-container .list-table .list-table-header .header-cell:nth-child(2) { - flex: 1 !important; - min-width: 100px; + flex: 1 1 0 !important; + min-width: 0 !important; } .inquiry-list-container .list-table .list-table-header .header-cell:nth-child(3) { - width: 50px !important; - min-width: 50px; - flex: none; + width: 60px !important; + min-width: 60px !important; + flex: none !important; justify-content: center; } .inquiry-list-container .list-table .list-table-header .header-cell:nth-child(4) { - width: 65px !important; - min-width: 65px; - flex: none; + width: 78px !important; + min-width: 78px !important; + flex: none !important; justify-content: center; } .inquiry-list-container .list-table .list-table-header .header-cell:nth-child(5) { @@ -16929,8 +22608,9 @@ input[type=checkbox]:checked + .custom-checkbox { display: none; } .inquiry-list-container .list-table .list-table-row .row-cell:nth-child(2), .inquiry-list-container .list-table .list-table-row .row-cell.row-cell--title { - flex: 1 !important; - min-width: 100px; + flex: 1 1 0 !important; + min-width: 0 !important; + overflow: hidden; justify-content: flex-start; } .inquiry-list-container .list-table .list-table-row .row-cell:nth-child(2) .notice-title-link, .inquiry-list-container .list-table .list-table-row .row-cell.row-cell--title .notice-title-link { @@ -16957,16 +22637,19 @@ input[type=checkbox]:checked + .custom-checkbox { height: 16px; } .inquiry-list-container .list-table .list-table-row .row-cell:nth-child(3) { - width: 50px !important; - min-width: 50px; - flex: none; + width: 60px !important; + min-width: 60px !important; + flex: none !important; justify-content: center; text-align: center; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .inquiry-list-container .list-table .list-table-row .row-cell:nth-child(4) { - width: 65px !important; - min-width: 65px; - flex: none; + width: 78px !important; + min-width: 78px !important; + flex: none !important; justify-content: center; text-align: center; } @@ -17156,6 +22839,16 @@ input[type=checkbox]:checked + .custom-checkbox { .dropdown-wrapper .dropdown-menu .dropdown-item + .dropdown-item { border-top: 1px solid #eee; } +.dropdown-wrapper .dropdown-menu .dropdown-item--danger { + color: #B02A37; +} +.dropdown-wrapper .dropdown-menu .dropdown-item--danger:hover { + background: #fdeaec; + color: #B02A37; +} +.dropdown-wrapper .dropdown-menu .dropdown-item--danger:active { + background: #f8d7da; +} .dropdown-wrapper.open .dropdown-toggle { background: rgba(0, 73, 180, 0.1); color: #0049b4; @@ -17166,343 +22859,6 @@ input[type=checkbox]:checked + .custom-checkbox { transform: translateY(4px); } -.commission-container { - max-width: 1400px; - margin: 0 auto; -} - -.wrap { - max-width: 1400px; - margin: 0 auto; - background: #FFFFFF; - padding: 24px; - border-radius: 8px; - box-shadow: 0 4px 12px rgba(75, 155, 255, 0.1); -} - -.searchInfo { - display: inline-block; - margin-right: 8px; - margin-bottom: 16px; -} - -.searchBox { - background: #f8f9fa; - padding: 20px; - border-radius: 8px; - margin-bottom: 20px; -} -.searchBox.row-two { - display: flex; - align-items: center; - gap: 20px; - flex-wrap: wrap; -} -.searchBox label { - font-weight: 500; - color: #1A1A2E; - min-width: 80px; -} -.searchBox .form-control { - padding: 6px 12px; - border: 1px solid #E2E8F0; - border-radius: 6px; - font-size: 14px; - min-width: 100px; -} - -.form-inline { - display: flex; - align-items: center; - gap: 10px; -} -.form-inline .period-selects { - display: flex; - align-items: center; - gap: 10px; -} -.form-inline .period-selects .period-unit { - margin-right: 10px; -} - -.btnBox { - margin-left: auto; -} -.btnBox .btn + .btn { - margin-left: 5px; -} - -#printBtn { - display: none; -} - -.commission-notice { - color: #dc3545; - margin-top: 30px; - line-height: 1.6; -} - -.voffset3 { - margin-top: 30px; -} - -.color_red { - color: #FF6B6B; -} - -.color_blue { - color: #007bff; -} - -.color_darkRed { - color: #8b0000; -} - -.table-responsive { - overflow-x: auto; - margin-top: 30px; -} - -.table { - width: 100%; - border-collapse: collapse; - font-size: 14px; -} -.table th { - background: #F8FAFC; - font-weight: 500; - color: #1A1A2E; - padding: 16px; - text-align: center; - border: 1px solid #E2E8F0; -} -.table td { - padding: 16px; - text-align: center; - border: 1px solid #E2E8F0; -} - -.table-bordered { - border: 1px solid #E2E8F0; -} -.table-bordered th, -.table-bordered td { - border: 1px solid #E2E8F0; -} - -.border-type { - border: 2px solid #dee2e6; -} - -.commission-table-wrapper { - margin-top: 30px; -} -.commission-table-wrapper .table { - border: 2px solid #dee2e6; -} - -.summary-row { - background-color: #fff3cd !important; -} -.summary-row td { - font-weight: 600; -} -.summary-row .summary-amount { - font-size: 16px; - color: #007bff; -} - -.text-center { - text-align: center; -} - -.text-right { - text-align: right; -} - -.text-left { - text-align: left; -} - -.bg-warning { - background-color: #fff3cd !important; -} - -.glyphicon-search::before { - content: "🔍"; -} - -.info-table { - border-top: 10px solid #64748B; - width: 100%; - border-bottom: 2px solid #1A1A2E; - margin-bottom: 40px; -} -.info-table th { - text-align: left; - padding: 16px 24px; - font-weight: 600; - background: none; - border: none; -} -.info-table td { - padding: 16px 24px; - border: none; -} - -.commission-table { - width: 100%; - margin-top: 40px; -} -.commission-table th, -.commission-table td { - border: 1px solid #999 !important; - padding: 16px 24px; -} -.commission-table td { - text-align: right; -} - -.bg_ygreen { - background-color: aquamarine; - text-align: center; -} - -.bg_skyBlue { - background-color: skyblue; - text-align: center; -} - -.bg_yellow { - background-color: yellow; - text-align: center; -} - -@media print { - body { - padding: 0; - background: #FFFFFF; - } - .searchBox, - .searchInfo, - #printButton, - #backButton, - #printBtn { - display: none !important; - } - @page { - size: auto; - margin: 0 0cm; - } - html { - margin: 0 0cm; - } -} -@media screen and (max-width: 1024px) { - .wrap { - padding: 16px; - } - .searchBox { - padding: 16px; - } - .searchBox.row-two { - flex-direction: column; - align-items: flex-start; - gap: 16px; - } - .form-inline { - width: 100%; - flex-direction: column; - align-items: flex-start; - } - .form-inline label { - min-width: auto; - margin-bottom: 4px; - } - .form-inline .form-control { - width: 100%; - } - .form-inline--period .period-selects { - display: flex; - flex-direction: row; - align-items: center; - gap: 8px; - width: 100%; - } - .form-inline--period .period-selects .form-control { - flex: 1; - min-width: 0; - } - .form-inline--period .period-selects .period-unit { - flex-shrink: 0; - font-size: 14px; - color: #1A1A2E; - } - .btnBox { - margin-left: 0; - width: 100%; - } - .btnBox .btn { - width: 100%; - display: flex; - justify-content: center; - align-items: center; - } - .table-responsive { - overflow-x: scroll; - -webkit-overflow-scrolling: touch; - } -} -body.commission-print-page { - font-size: 12px; - width: 600px; - margin: 90px; - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; -} -body.commission-print-page div { - position: relative; -} -body.commission-print-page table { - border-collapse: collapse; -} -body.commission-print-page table th, -body.commission-print-page table td { - padding: 5px 10px; -} -body.commission-print-page table tr { - font-size: 12px; -} -body.commission-print-page h1 { - text-align: center; -} -body.commission-print-page h1 img { - width: 300px; -} -body.commission-print-page h2 { - margin-top: 40px; - float: left; -} -body.commission-print-page p { - line-height: 1.6; -} -body.commission-print-page p[style*="float: right"] { - margin-top: 25px; -} -body.commission-print-page .btn { - padding: 8px 16px; - border: none; - border-radius: 4px; - cursor: pointer; - font-size: 14px; -} -body.commission-print-page .btn-primary { - background: #667eea; - color: white; -} -body.commission-print-page .btn-primary:hover { - background: #5a67d8; -} - .terms-page { min-height: calc(100vh - 140px); background: #F8FAFC; @@ -17579,6 +22935,13 @@ body.commission-print-page .btn-primary:hover { @media (max-width: 768px) { .terms-tabs { padding: 0; + flex-wrap: nowrap; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + scrollbar-width: none; + } + .terms-tabs::-webkit-scrollbar { + display: none; } } .terms-tabs .tab-link { @@ -17600,8 +22963,10 @@ body.commission-print-page .btn-primary:hover { } @media (max-width: 768px) { .terms-tabs .tab-link { - padding: 16px 16px; - font-size: 16px; + flex: 1 0 auto; + white-space: nowrap; + padding: 8px 16px; + font-size: 14px; gap: 4px; } } @@ -17618,6 +22983,13 @@ body.commission-print-page .btn-primary:hover { font-weight: 700; } +.terms-empty { + padding: 32px 24px; + text-align: center; + color: #8c959f; + font-size: 16px; +} + .terms-selector { padding: 24px; background: #F6F9FB; @@ -17635,7 +23007,7 @@ body.commission-print-page .btn-primary:hover { background: #FFFFFF; border: 1px solid #dadada; border-radius: 12px; - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 14px; font-weight: 400; color: #212529; @@ -17930,7 +23302,6 @@ body.commission-print-page .btn-primary:hover { padding: 20px; background-color: #fff; color: #000; - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", sans-serif; display: flex; flex-direction: column; gap: 20px; @@ -17938,7 +23309,6 @@ body.commission-print-page .btn-primary:hover { .service-intro__title { margin: 0; font-size: 25px; - font-weight: 700; line-height: 1.4; letter-spacing: -0.01em; } @@ -17961,7 +23331,6 @@ body.commission-print-page .btn-primary:hover { .service-intro__section-title { margin: 0; font-size: 20px; - font-weight: 500; line-height: 1.5; } .service-intro__section-body { @@ -18005,263 +23374,650 @@ body.commission-print-page .btn-primary:hover { font-size: 14px; } } -.signup-guide { - max-width: 1200px; - margin: 0 auto; - padding: 64px 20px 80px; - background-color: #f9fafb; +.service-main { + padding: 60px 0; + display: flex; + gap: 60px; + align-items: flex-start; } -.signup-guide__header { - margin-bottom: 64px; + +.service-content { + flex: 1; + width: 100%; + display: flex; + flex-direction: column; + gap: 40px; + min-width: 0; } -.signup-guide__title { - font-size: 48px; - font-weight: 700; - line-height: 1; - color: #0049b4; - margin: 0 0 22px 0; +.service-content.n-gap { + gap: 0; } -.signup-guide__underline { - width: 96px; - height: 4px; - background-color: #0049b4; - margin-bottom: 36px; + +.service-sidebar { + width: 213px; + flex-shrink: 0; } -.signup-guide__desc p { - font-size: 20px; - font-weight: 400; - line-height: 1.625; - color: #1e2939; - margin: 0; -} -.signup-guide__card { + +.service-nav { + display: flex; + flex-direction: column; + gap: 26px; background: #fff; - border-radius: 16px; - padding: 48px 50px; - box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1); - margin-bottom: 64px; + border: 1px solid #e3e8f0; + border-radius: 20px; + padding: 28px 30px; } -.signup-guide__steps { - display: grid; - grid-template-columns: repeat(6, 1fr); - gap: 16px; +.service-nav:hover { + box-shadow: 0 4px 4px rgba(173, 173, 173, 0.1); + transition: transform 0.3s ease, box-shadow 0.3s ease; } -.signup-guide__step { +.service-nav .service-sidebar__profile { display: flex; flex-direction: column; align-items: center; - position: relative; + gap: 16px; } -.signup-guide__step--last .signup-guide__step-line { - display: none; -} -.signup-guide__step-icon { - width: 64px; - height: 64px; - border-radius: 50%; - background: #fff; - border: 1.6px solid #0049b4; +.service-nav .service-sidebar__profile .avatar { + width: 53px; + height: 52px; + background-color: #9ac8ee; + border-radius: 11px; display: flex; align-items: center; justify-content: center; - position: relative; - z-index: 2; } -.signup-guide__step-icon svg { - width: 32px; - height: 32px; +.service-nav .service-sidebar__profile .avatar svg { + width: 36px; + height: 36px; + fill: #ffffff; } -.signup-guide__step-dot { - width: 12px; - height: 12px; - background-color: #0049b4; - border-radius: 50%; - margin-top: 12px; - position: relative; - z-index: 2; -} -.signup-guide__step-label { - display: flex; - flex-direction: column; - align-items: center; - margin-top: 8px; +.service-nav .service-sidebar__profile .user-name { + font-size: 16px; + font-weight: 500; + color: #000000; text-align: center; + margin: 0; } -.signup-guide__step-num { - font-size: 14px; - font-weight: 400; - color: #6a7282; - line-height: 20px; +.service-nav .service-sidebar__divider { + height: 1px; + background-color: #e3e8f0; + margin: 4px 0; + width: 100%; } -.signup-guide__step-text { - font-size: 14px; - font-weight: 700; - color: #1e2939; - line-height: 20px; - margin-top: 4px; - white-space: nowrap; -} -.signup-guide__step-line { - position: absolute; - top: 32px; - left: calc(50% + 32px); - width: calc(100% - 32px); - height: 2px; - background: linear-gradient(to right, #0049b4, rgba(0, 73, 180, 0.3)); - z-index: 1; -} -.signup-guide__notes { - margin-bottom: 48px; -} -.signup-guide__note { - display: flex; - align-items: flex-start; - gap: 8px; - font-size: 18px; - font-weight: 400; - line-height: 32px; - color: #1e2939; - margin: 0 0 16px 0; -} -.signup-guide__note:last-child { - margin-bottom: 0; -} -.signup-guide__note-mark { - flex-shrink: 0; - width: 18px; -} -.signup-guide__action { - text-align: center; - padding-top: 16px; -} -.signup-guide__btn { - display: inline-block; - padding: 18px 64px; - background: #0049b4; - color: #fff; - font-size: 18px; - font-weight: 600; - border-radius: 8px; +.service-nav__item { + font-size: 16px; + font-weight: 500; + color: #0d0e11; text-decoration: none; - transition: background 0.2s ease, transform 0.2s ease; + transition: color 0.2s; } -.signup-guide__btn:hover { - background: rgb(0, 65.7, 162); - transform: translateY(-2px); +.service-nav__item:hover { + color: #2a69de; } -.signup-guide__btn:active { - transform: translateY(0); +.service-nav__item--active { + color: #2a69de; + font-weight: 700; } @media (max-width: 1024px) { - .signup-guide { - padding: 48px 24px 64px; + .service-main { + flex-direction: column; + padding: 40px 20px; } - .signup-guide__title { - font-size: 36px; + .service-sidebar { + width: 100%; + overflow: hidden; } - .signup-guide__steps { - grid-template-columns: repeat(3, 1fr); - gap: 24px 16px; + .service-nav { + flex-direction: row !important; + flex-wrap: nowrap !important; + justify-content: flex-start !important; + padding: 16px 20px !important; + overflow-x: auto !important; + -webkit-overflow-scrolling: touch !important; + gap: 24px !important; + border: 1px solid #e3e8f0 !important; + border-radius: 20px !important; + box-shadow: none !important; + background: #ffffff !important; } - .signup-guide__step:nth-child(3) .signup-guide__step-line { + .service-nav::-webkit-scrollbar { display: none; } - .signup-guide__step-line { - width: calc(100% - 24px); + .service-nav { + -ms-overflow-style: none; + /* IE and Edge */ + scrollbar-width: none; + /* Firefox */ + } + .service-nav .service-sidebar__profile, + .service-nav .service-sidebar__divider { + display: none !important; + } + .service-nav .service-nav__item { + flex-shrink: 0 !important; + white-space: nowrap !important; + margin: 0 !important; + padding: 0 !important; + } + .service-hero, + .oauth2-2legged__hero { + width: 100vw; + } +} +.service-hero { + background: linear-gradient(to right, #eff9fe, #fcfeff); + border: 1px solid #9cc2ff; + padding: 60px 0; + display: flex; + justify-content: center; + width: calc(100vw - 80px); + position: relative; + left: 50%; + transform: translateX(-50%); + margin-top: 40px; +} +.service-hero__inner { + max-width: 1240px; + width: 100%; + display: flex; + align-items: center; + gap: 138px; + padding: 0 37px; +} +.service-hero__icon-wrapper { + width: 230px; + height: 167px; + position: relative; + display: flex; + align-items: center; + justify-content: center; + font-size: 14px; + color: #adb5bd; +} +.service-hero__content { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 15px; +} +.service-hero__badge { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 8px 20px; + background: #fff; + border: 1px solid #d1e6f0; + border-radius: 38px; +} +.service-hero__badge-dot { + width: 8px; + height: 8px; + background: var(--primary-color); + border-radius: 50%; +} +.service-hero__badge-text { + font-size: 13px; + font-weight: 700; + color: #1b4ab7; +} +.service-hero__title { + font-size: 36px; + font-weight: 700; + color: #000; + margin: 0; +} +@media (max-width: 768px) { + .service-hero__title { + font-size: 29px; + } +} +.service-hero__desc { + font-size: 16px; + font-weight: 500; + color: #6678ad; + line-height: 1.5; + margin: 0; +} + +.service-keyImg { + width: 100%; + height: 100%; + object-fit: contain; +} +@media (max-width: 768px) { + .service-keyImg { + width: 90%; + height: 90%; + } +} + +.signup-guide-v2 { + background-color: #fff; + padding-bottom: 100px; +} +.signup-guide-v2 .signup-info-box { + background: #1d2a5e; + border: 1px solid rgba(255, 255, 255, 0.08); + border-left: 5px solid #2a69de; + border-radius: 16px; + padding: 28px 32px; + display: flex; + align-items: center; + gap: 28px; + animation: fadeInUp 0.6s cubic-bezier(0.16, 1, 0.3, 1) both; +} +.signup-guide-v2 .signup-info-box__icon { + width: 56px; + height: 56px; + background: #48507b; + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 14px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} +.signup-guide-v2 .signup-info-box__icon img { + width: 28px; + height: 28px; + filter: brightness(0) invert(1); + object-fit: contain; +} +.signup-guide-v2 .signup-info-box ul { + margin: 0; + padding-left: 0; + list-style: none; +} +.signup-guide-v2 .signup-info-box ul li { + position: relative; + padding-left: 20px; + color: #94a3b8; + font-size: 15px; + font-weight: 500; + line-height: 1.8; + word-break: keep-all; +} +.signup-guide-v2 .signup-info-box ul li strong { + color: #ffffff; + font-weight: 700; +} +.signup-guide-v2 .signup-info-box ul li::before { + content: ""; + position: absolute; + left: 4px; + top: 10px; + width: 5px; + height: 5px; + background: #38bdf8; + border-radius: 50%; +} +.signup-guide-v2 .signup-info-box ul li + li { + margin-top: 6px; +} + +.signup-tabs { + display: flex; + justify-content: flex-start; + border-bottom: 1px solid #e3e8f0; + margin-top: 40px; + margin-bottom: 50px; + width: 100%; +} +.signup-tabs .signup-tab-btn { + flex: 1; + max-width: 50%; + padding: 16px 20px; + font-size: 16px; + font-weight: 500; + color: #94a3b8; + background: none; + border: none; + border-bottom: 2px solid transparent; + cursor: pointer; + text-align: center; + transition: all 0.2s ease; + outline: none; +} +.signup-tabs .signup-tab-btn:hover { + color: #1e293b; +} +.signup-tabs .signup-tab-btn.active { + color: #2a69de; + font-weight: 700; + border-bottom: 2px solid #2a69de; + background: #ffffff; +} + +.signup-timeline-container { + margin-bottom: 50px; + min-height: 300px; +} + +.signup-timeline { + display: none; + flex-direction: column; + gap: 40px; + position: relative; + padding-left: 80px; +} +.signup-timeline.active { + display: flex; +} +.signup-timeline::before { + content: ""; + position: absolute; + left: 17px; + top: 47px; + bottom: 47px; + width: 2px; + background: #e6eefb; + z-index: 0; +} + +.signup-step { + display: flex; + align-items: center; + gap: 40px; + position: relative; + z-index: 1; +} +.signup-step::before { + content: ""; + position: absolute; + left: -71px; + top: 50%; + transform: translateY(-50%); + width: 16px; + height: 16px; + background: #2a69de; + border-radius: 50%; + box-shadow: 0 0 0 6px rgba(42, 105, 222, 0.15), 0 0 10px 6px rgba(42, 105, 222, 0.1); + z-index: 2; +} +.signup-step__icon-box { + width: 95px; + min-height: 95px; + background: #fff; + border: 1px solid #e3e8f0; + border-radius: 20px; + display: inline-flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + box-shadow: 0 4px 4px rgba(173, 173, 173, 0.1); +} +.signup-step__icon-box svg { + width: 30px; + height: 30px; +} +.signup-step__card { + flex: 1; + background: #fff; + border: 1px solid #e3e8f0; + border-radius: 20px; + padding: 20px 30px; + box-shadow: 0 4px 4px rgba(173, 173, 173, 0.1); + display: flex; + flex-direction: column; + justify-content: center; + min-height: 95px; +} +.signup-step__card-header { + display: flex; + align-items: center; + margin-bottom: 6px; +} +.signup-step__card-num { + font-size: 14px; + font-weight: 700; + color: #1b4ab7; +} +.signup-step__card-dot { + width: 4px; + height: 4px; + background: #1b4ab7; + border-radius: 50%; + margin: 0 8px; +} +.signup-step__card-title { + font-size: 18px; + font-weight: 700; + color: #000; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; +} +.signup-step__card-desc { + margin: 0; + font-size: 14px; + color: #64748b; + line-height: 1.5; +} +.signup-step .one { + color: #2C4A8C; +} +.signup-step .two { + color: #1F7A5C; +} +.signup-step .three { + color: #F98E24; +} +.signup-step .four { + color: #2480F9; +} +.signup-step .five { + color: #FF5C5C; +} +.signup-step .six { + color: #5B4AEE; +} + +.signup-action { + display: flex; + justify-content: center; + margin-top: 20px; +} + +.signup-roles-tbd { + margin-top: 40px; +} +.signup-roles-tbd__title { + font-size: 20px; + font-weight: 700; + color: #140064; + margin: 0 0 16px; +} +.signup-roles-tbd__placeholder { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 12px; + padding: 48px 24px; + background: #f8f9fa; + border: 1px dashed #e3e8f0; + border-radius: 12px; + text-align: center; +} +.signup-roles-tbd__badge { + display: inline-block; + padding: 4px 12px; + font-size: 12px; + font-weight: 700; + letter-spacing: 0.04em; + color: #0049B4; + background: #eff9fe; + border-radius: 20px; +} +.signup-roles-tbd__desc { + margin: 0; + font-size: 15px; + color: #6e7781; +} + +.signup-roles-table-section { + margin-top: 50px; + margin-bottom: 20px; +} +.signup-roles-table-section__title { + font-size: 20px; + font-weight: 700; + color: #1e293b; + margin: 0 0 20px; + position: relative; + padding-left: 12px; + margin-bottom: 30px; +} +.signup-roles-table-section__title::before { + content: ""; + position: absolute; + left: 0; + top: 4px; + bottom: 4px; + width: 4px; + background: #0049B4; + border-radius: 2px; +} +.signup-roles-table-section .signup-roles-table-wrapper { + width: 100%; + overflow-x: auto; + border: 1px solid #e2e8f0; + border-radius: 12px; + background: #ffffff; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.02); +} +.signup-roles-table-section .signup-roles-table-wrapper::-webkit-scrollbar { + height: 6px; +} +.signup-roles-table-section .signup-roles-table-wrapper::-webkit-scrollbar-thumb { + background: #cbd5e1; + border-radius: 3px; +} +.signup-roles-table-section .signup-roles-table { + width: 100%; + border-collapse: collapse; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + text-align: center; +} +.signup-roles-table-section .signup-roles-table th, +.signup-roles-table-section .signup-roles-table td { + padding: 14px 16px; + font-size: 14px; + border-bottom: 1px solid #e2e8f0; + border-right: 1px solid #e2e8f0; +} +.signup-roles-table-section .signup-roles-table th:last-child, +.signup-roles-table-section .signup-roles-table td:last-child { + border-right: none; +} +.signup-roles-table-section .signup-roles-table thead { + background: #f8fafc; +} +.signup-roles-table-section .signup-roles-table thead th { + font-weight: 700; + color: #334155; + font-size: 13px; +} +.signup-roles-table-section .signup-roles-table thead th.col-feature { + width: 35%; + font-size: 14px; + color: #1e293b; +} +.signup-roles-table-section .signup-roles-table thead th.col-role { + width: 15%; +} +.signup-roles-table-section .signup-roles-table thead th.col-role-group { + background: #f1f5f9; + color: #0f172a; +} +.signup-roles-table-section .signup-roles-table thead th.col-sub-role { + width: 17.5%; + background: #f8fafc; +} +.signup-roles-table-section .signup-roles-table tbody tr { + transition: background 0.2s ease; +} +.signup-roles-table-section .signup-roles-table tbody tr:hover { + background: #f8fafc; +} +.signup-roles-table-section .signup-roles-table tbody tr:last-child td { + border-bottom: none; +} +.signup-roles-table-section .signup-roles-table tbody .cell-feature { + font-weight: 600; + color: #475569; + text-align: left; + padding-left: 20px; +} +.signup-roles-table-section .signup-roles-table tbody .status-badge { + display: inline-flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + border-radius: 50%; + font-size: 14px; + font-weight: 700; +} +.signup-roles-table-section .signup-roles-table tbody .status-badge--allowed { + background: #e0f2fe; + color: #0369a1; +} +.signup-roles-table-section .signup-roles-table tbody .status-badge--denied { + background: #f1f5f9; + color: #94a3b8; +} + +@media (max-width: 1024px) { + .service-hero { + padding: 40px 0; + } + .service-hero__inner { + flex-direction: column; + gap: 30px; + text-align: center; + } + .service-hero__content { + align-items: center; + } + .signup-guide-v2 .signup-info-box { + flex-direction: column; + padding: 20px; + gap: 15px; + text-align: center; + } + .signup-guide-v2 .signup-info-box ul li { + text-align: left; } } @media (max-width: 768px) { - .signup-guide { - padding: 32px 16px 48px; + .signup-guide-v2 .signup-timeline { + padding-left: 0; } - .signup-guide__header { - margin-bottom: 32px; - } - .signup-guide__title { - font-size: 28px; - } - .signup-guide__underline { - width: 64px; - height: 3px; - margin-bottom: 24px; - } - .signup-guide__desc p { - font-size: 16px; - line-height: 1.75; - } - .signup-guide__card { - padding: 32px 20px; - margin-bottom: 32px; - } - .signup-guide__steps { - grid-template-columns: repeat(2, 1fr); - gap: 32px 16px; - } - .signup-guide__step:nth-child(2n) .signup-guide__step-line { + .signup-guide-v2 .signup-timeline::before { display: none; } - .signup-guide__step:nth-child(3) .signup-guide__step-line { - display: block; + .signup-guide-v2 .signup-step { + gap: 15px; } - .signup-guide__step-icon { - width: 56px; - height: 56px; - } - .signup-guide__step-icon svg { - width: 28px; - height: 28px; - } - .signup-guide__step-dot { - width: 10px; - height: 10px; - } - .signup-guide__step-text { - font-size: 13px; - white-space: normal; - } - .signup-guide__step-line { - top: 28px; - left: calc(50% + 28px); - width: calc(100% - 28px); - } - .signup-guide__notes { - margin-bottom: 32px; - } - .signup-guide__note { - font-size: 14px; - line-height: 24px; - } - .signup-guide__btn { - padding: 16px 48px; - font-size: 16px; - width: 100%; - max-width: 320px; - } -} -@media (max-width: 480px) { - .signup-guide__steps { - grid-template-columns: 1fr; - gap: 24px; - } - .signup-guide__step { - flex-direction: row; - align-items: center; - gap: 16px; - } - .signup-guide__step .signup-guide__step-line { - display: none !important; - } - .signup-guide__step-icon { - flex-shrink: 0; - } - .signup-guide__step-dot { + .signup-guide-v2 .signup-step::before { display: none; } - .signup-guide__step-label { - align-items: flex-start; - margin-top: 0; - text-align: left; + .signup-guide-v2 .signup-step__icon-box { + width: 60px; + height: 60px; + min-height: 60px; + } + .signup-guide-v2 .signup-step__icon-box svg { + width: 24px; + height: 24px; + } + .signup-guide-v2 .signup-step__card { + height: auto; + min-height: 60px; + padding: 12px 18px; + } + .signup-guide-v2 .signup-step__card-title { + font-size: 15px; } } .service_guide_header { @@ -18269,7 +24025,8 @@ body.commission-print-page .btn-primary:hover { text-align: center; background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); } -.service_guide_header .service_guide .title { + +.service_guide .title { font-size: 28px; font-weight: 700; line-height: 36px; @@ -18277,7 +24034,7 @@ body.commission-print-page .btn-primary:hover { color: #140064; margin-bottom: 16px; } -.service_guide_header .service_guide .detail { +.service_guide .detail { font-size: 14px; font-weight: 400; line-height: 22px; @@ -18326,7 +24083,8 @@ body.commission-print-page .btn-primary:hover { border-radius: 8px; overflow: hidden; } -.service_member_table table th, .service_member_table table td { +.service_member_table table th, +.service_member_table table td { padding: 16px; font-size: 14px; line-height: 22px; @@ -18411,7 +24169,8 @@ body.commission-print-page .btn-primary:hover { max-width: 800px; margin: 0 auto; } - .service_member_table table th, .service_member_table table td { + .service_member_table table th, + .service_member_table table td { padding: 20px 24px; font-size: 15px; } @@ -18429,17 +24188,26 @@ body.commission-print-page .btn-primary:hover { font-size: 18px; } } -.oauth2-2legged { - max-width: 1200px; +.oauth-main { + max-width: 1240px; margin: 0 auto; - padding: 20px; - background-color: #FFFFFF; - color: #1A1A2E; - font-family: "원신한", "OneShinhan", "Spoqa Han Sans Neo", sans-serif; + padding: 60px 40px; display: flex; - flex-direction: column; - gap: 24px; + gap: 60px; + align-items: flex-start; +} + +.oauth2-2legged { box-sizing: border-box; + color: #1A1A2E; +} +.oauth2-2legged *, +.oauth2-2legged *::before, +.oauth2-2legged *::after { + box-sizing: inherit; +} +.oauth2-2legged__prereq, .oauth2-2legged__sequence, .oauth2-2legged__step, .oauth2-2legged__errors, .oauth2-2legged__error-example { + margin-bottom: 50px; } .oauth2-2legged__eyebrow { display: inline-block; @@ -18450,7 +24218,7 @@ body.commission-print-page .btn-primary:hover { margin-bottom: 8px; } .oauth2-2legged__h2 { - margin: 0 0 8px; + margin: 0 0 20px; font-size: 26px; font-weight: 700; line-height: 1.3; @@ -18463,57 +24231,73 @@ body.commission-print-page .btn-primary:hover { color: #64748B; } .oauth2-2legged__hero { + background: linear-gradient(to right, #eff9fe, #fcfeff); + border: 1px solid #9cc2ff; + padding: 60px 0; + display: flex; + justify-content: center; + width: calc(100vw - 80px); position: relative; - display: grid; - grid-template-columns: 1fr 320px; - gap: 32px; - padding: 48px 80px; - border-radius: 20px; - border: 1px solid #E2E8F0; - background: linear-gradient(135deg, #EDF9FE 0%, #FFFFFF 100%); + left: 50%; + transform: translateX(-50%); + margin-top: 40px; +} +.oauth2-2legged__hero-inner { + max-width: 1240px; + width: 100%; + display: flex; + align-items: center; + gap: 138px; + padding: 0 37px; +} +.oauth2-2legged__hero-body { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 15px; } .oauth2-2legged__hero-eyebrow { display: inline-flex; align-items: center; - gap: 10px; - padding: 6px 18px; - border-radius: 14px; - background: #FFFFFF; - border: 1px solid #c3dfea; - color: #0049b4; + gap: 8px; + padding: 8px 20px; + background: #fff; + border: 1px solid #d1e6f0; + border-radius: 38px; font-size: 13px; - font-weight: 600; - margin-bottom: 18px; + font-weight: 700; + color: #1b4ab7; } .oauth2-2legged__hero-eyebrow-dot { width: 8px; height: 8px; border-radius: 50%; - background: #0049b4; + background: var(--primary-color); } .oauth2-2legged__hero-title { - margin: 0 0 16px; font-size: 36px; font-weight: 800; - color: #1A1A2E; - letter-spacing: -0.5px; - line-height: 1.2; - white-space: nowrap; + color: #000; + margin: 0; } .oauth2-2legged__hero-lead { - margin: 0; font-size: 16px; font-weight: 500; - color: #64748B; - line-height: 1.6; + color: #6678ad; + line-height: 1.5; + margin: 0; } .oauth2-2legged__hero-lead + .oauth2-2legged__hero-lead { - margin-top: 2px; + margin-top: 4px; +} +@media (max-width: 768px) { + .oauth2-2legged__hero-lead { + font-size: 13px; + } } .oauth2-2legged__hero-chips { display: flex; gap: 12px; - margin-top: 22px; flex-wrap: wrap; } .oauth2-2legged__chip { @@ -18533,20 +24317,23 @@ body.commission-print-page .btn-primary:hover { color: #FFFFFF; } .oauth2-2legged__hero-illust { - align-self: center; - background: #FFFFFF; - border: 1px solid #c3dfea; - border-radius: 20px; - overflow: hidden; + width: 230px; + height: 167px; + position: relative; + background: radial-gradient(circle at 50% 120%, rgba(0, 73, 180, 0.1), transparent 70%); + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; } .oauth2-2legged__hero-illust svg { display: block; width: 100%; - height: auto; + height: 100%; } .oauth2-2legged__prereq-grid { display: grid; - grid-template-columns: 1fr 1fr 1fr; + grid-template-columns: 1fr; gap: 16px; } .oauth2-2legged__prereq-card { @@ -18558,6 +24345,8 @@ body.commission-print-page .btn-primary:hover { background: #FFFFFF; border: 1px solid #E2E8F0; border-radius: 14px; + word-break: keep-all; + overflow-wrap: break-word; } .oauth2-2legged__prereq-num { display: inline-flex; @@ -18573,9 +24362,10 @@ body.commission-print-page .btn-primary:hover { } .oauth2-2legged__prereq-title { margin: 4px 0 8px; - font-size: 15px; + font-size: 16px; font-weight: 700; color: #1A1A2E; + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; } .oauth2-2legged__prereq-body p { margin: 0; @@ -18592,28 +24382,37 @@ body.commission-print-page .btn-primary:hover { background: #FFFFFF; border: 1px solid #E2E8F0; border-radius: 14px; + overflow-x: auto; + -webkit-overflow-scrolling: touch; } .oauth2-2legged__sequence-diagram svg { display: block; width: 100%; + min-width: 580px; height: auto; } .oauth2-2legged__step-grid { - display: grid; - grid-template-columns: 1fr 1fr; + display: flex; + flex-direction: column; gap: 24px; } +.oauth2-2legged__step-grid + .oauth2-2legged__step-grid { + margin-top: 24px; +} .oauth2-2legged__endpoint-box { display: grid; - grid-template-columns: auto 1fr auto; - align-items: center; - gap: 16px; + grid-template-columns: 1fr; + gap: 8px; padding: 16px 24px; margin-bottom: 20px; background: #FFFFFF; border: 1px solid #E2E8F0; border-radius: 12px; } +.oauth2-2legged__endpoint-box .oauth2-2legged__method, +.oauth2-2legged__endpoint-box .oauth2-2legged__endpoint-content-type { + justify-self: start; +} .oauth2-2legged__method { display: inline-block; padding: 5px 14px; @@ -18628,6 +24427,7 @@ body.commission-print-page .btn-primary:hover { font-family: "Fira Code", monospace; font-size: 16px; color: #1A1A2E; + word-break: break-all; } .oauth2-2legged__endpoint-content-type { display: inline-block; @@ -18644,6 +24444,9 @@ body.commission-print-page .btn-primary:hover { background: #FFFFFF; border: 1px solid #E2E8F0; border-radius: 12px; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + min-width: 0; } .oauth2-2legged__panel-title { margin: 0 0 14px; @@ -18659,6 +24462,7 @@ body.commission-print-page .btn-primary:hover { } .oauth2-2legged__table { width: 100%; + min-width: 600px; border-collapse: collapse; font-size: 13px; } @@ -18734,6 +24538,8 @@ body.commission-print-page .btn-primary:hover { border-radius: 12px; background: #1A1A2E; overflow: hidden; + min-width: 0; + margin-top: 24px; } .oauth2-2legged__code-panel::before { content: ""; @@ -18769,6 +24575,7 @@ body.commission-print-page .btn-primary:hover { line-height: 1.7; white-space: pre; overflow-x: auto; + display: block; } .oauth2-2legged .o2leg-c { color: #00D4FF; @@ -18790,6 +24597,10 @@ body.commission-print-page .btn-primary:hover { border-radius: 8px; background: #F8FAFC; margin-bottom: 6px; + display: flex; + flex-wrap: wrap; + gap: 8px 12px; + align-items: center; } .oauth2-2legged__header-key { display: block; @@ -18927,12 +24738,13 @@ body.commission-print-page .btn-primary:hover { } @media (max-width: 1024px) { - .oauth2-2legged__hero { - grid-template-columns: 1fr; - padding: 32px; + .oauth2-2legged__hero-inner { + flex-direction: column; + gap: 30px; + text-align: center; } - .oauth2-2legged__hero-illust { - max-width: 280px; + .oauth2-2legged__hero-body { + align-items: center; } .oauth2-2legged__prereq-grid { grid-template-columns: 1fr; @@ -18956,11 +24768,10 @@ body.commission-print-page .btn-primary:hover { } @media (max-width: 640px) { .oauth2-2legged { - padding: 16px; gap: 16px; } .oauth2-2legged__hero { - padding: 28px 24px; + padding: 40px 0; } .oauth2-2legged__hero-title { font-size: 28px; @@ -18983,13 +24794,80 @@ body.commission-print-page .btn-primary:hover { } .api-statistics-container { margin: 0 auto; - padding: 48px 0px; +} + +.statistics-notice { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 8px; + margin-bottom: 16px; + padding: 12px 18px; + background: #eef4fc; + border: 1px solid #d6e4f5; + border-radius: 10px; + font-size: 13px; + color: #4a5568; +} + +.statistics-notice-example { + flex-basis: 100%; /* flex-wrap 컨테이너에서 두 번째 줄로 강제 */ + color: #6b7280; + font-size: 12px; +} + +.statistics-notice-latest { + color: #0049B4; + font-weight: 600; } .statistics-search-section { display: flex; - justify-content: flex-end; + align-items: flex-end; + justify-content: space-between; + gap: 12px; margin-bottom: 30px; + margin-top: 50px; +} + +.search-mode-tabs { + display: inline-flex; + background: #eef1f5; + border-radius: 22px; + padding: 4px; + width: 150px; +} + +.mode-tab { + border: none; + background: transparent; + padding: 7px 22px; + border-radius: 18px; + font-size: 14px; + color: #666; + cursor: pointer; + transition: all 0.2s; +} +.mode-tab.active { + background: #0049B4; + color: #fff; + font-weight: 600; +} + +.month-range-picker { + display: flex; + align-items: center; + gap: 8px; +} + +.month-input { + border: none; + padding: 8px 12px; + font-size: 14px; + color: #333; + background: transparent; + outline: none; + cursor: pointer; } .search-filter-row { @@ -19116,6 +24994,9 @@ body.commission-print-page .btn-primary:hover { .summary-detail-item.success .detail-icon { color: #4A90D9; } +.summary-detail-item.timeout .detail-icon { + color: #F5C36B; +} .summary-detail-item.failure .detail-icon { color: #F5A3B5; } @@ -19176,6 +25057,9 @@ body.commission-print-page .btn-primary:hover { .rate-indicator.success { background: #4A90D9; } +.rate-indicator.timeout { + background: #F5C36B; +} .rate-indicator.failure { background: #F5A3B5; } @@ -19194,6 +25078,10 @@ body.commission-print-page .btn-primary:hover { color: #4A90D9; } +.timeout-rate .rate-value { + color: #F5C36B; +} + .failure-rate .rate-value { color: #F5A3B5; } @@ -19208,6 +25096,10 @@ body.commission-print-page .btn-primary:hover { border-radius: 16px; overflow: hidden; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + margin-bottom: 30px; +} +.statistics-table-card:last-child { + margin-bottom: 0; } .table-header { @@ -19226,9 +25118,10 @@ body.commission-print-page .btn-primary:hover { font-size: 18px; font-weight: 600; margin: 0; + margin-bottom: 10px; } .table-title svg { - stroke: #fff; + stroke: #1e293b; } .btn-download { @@ -19252,34 +25145,85 @@ body.commission-print-page .btn-primary:hover { padding: 0; } -.statistics-table { +.board-table { width: 100%; - border-collapse: collapse; + border-top: 1px solid #c4c7c8; + background: #fff; } -.statistics-table thead th { - padding: 16px 20px; - text-align: left; - font-weight: 500; - color: #666; - border-bottom: 1px solid #e0e0e0; - background: #fafafa; -} -.statistics-table tbody td { - padding: 16px 20px; - border-bottom: 1px solid #f0f0f0; - color: #333; -} -.statistics-table tbody tr:hover { +.board-table .board-table-header { + display: flex; + align-items: center; background: #f9f9f9; + height: 50px; + padding: 0 45px; + border-bottom: 1px solid #dedede; } -.statistics-table tfoot .total-row { - background: #e3f2fd; +@media (max-width: 768px) { + .board-table .board-table-header { + display: none; + } } -.statistics-table tfoot .total-row td { - padding: 16px 20px; +.board-table .board-table-header .header-cell { + font-size: 14px; + font-family: "Spoqa Han Sans Neo", sans-serif; + font-weight: 500; + color: #000; + text-align: center; +} +.board-table .board-table-body { + display: flex; + flex-direction: column; +} +.board-table .board-table-row { + display: flex; + align-items: center; + height: 52px; + padding: 0 45px; + border-bottom: 1px solid #dedede; + transition: background-color 0.2s; + background: #ffffff; +} +.board-table .board-table-row:hover { + background-color: #f8fbfe; +} +@media (max-width: 768px) { + .board-table .board-table-row { + flex-direction: column; + align-items: flex-start; + height: auto; + padding: 15px; + gap: 10px; + } +} +.board-table .board-table-row .row-cell { + display: flex; + align-items: center; + justify-content: center; + font-size: 14px; + font-family: "Spoqa Han Sans Neo", sans-serif; + font-weight: 400; + color: #000; + white-space: nowrap; +} +@media (max-width: 768px) { + .board-table .board-table-row .row-cell { + justify-content: flex-start; + width: 100% !important; + } + .board-table .board-table-row .row-cell::before { + content: attr(data-label); + font-weight: 500; + color: #64748b; + margin-right: 10px; + min-width: 80px; + } +} +.board-table .board-table-row.total-row { + background: #f8fbfe; font-weight: 600; +} +.board-table .board-table-row.total-row .row-cell { color: #0049B4; - border-bottom: none; } .api-name { @@ -19307,80 +25251,2171 @@ body.commission-print-page .btn-primary:hover { stroke: #0049B4; } -.progress-cell { - min-width: 200px; +.api-name-text { + display: inline-block; + vertical-align: middle; + max-width: 180px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +@media (max-width: 768px) { + .api-name-text { + max-width: 320px; + } } -.progress-bar { +.col-name { + flex: 2; + justify-content: flex-start !important; + padding-left: 10px; + min-width: 0; +} + +.board-table-header .col-name { + justify-content: center !important; + padding-left: 0; +} + +.col-num { + width: 100px; + justify-content: center !important; +} + +.col-chart { + width: 180px; + justify-content: center !important; +} + +.stats-progress { + width: 150px; + height: 8px; + background: #f1f5f9; + border-radius: 4px; display: flex; - height: 12px; - background: #e0e0e0; - border-radius: 6px; overflow: hidden; } -.progress-success { - background: #4A90D9; - transition: width 0.3s ease; +.stats-empty { + display: flex; + align-items: center; + justify-content: center; + height: 100px; + width: 100%; + color: #64748b; + font-weight: 400; + font-size: 16px; } -.progress-failure { - background: #F5A3B5; - transition: width 0.3s ease; -} - -.empty-row td { - text-align: center; - padding: 40px 20px; - color: #999; +.progress-cell { + min-width: 200px; } @media (max-width: 992px) { .summary-content { flex-direction: column; gap: 30px; + align-items: center; } .summary-left, .summary-center, .summary-right { flex: none; width: 100%; + align-items: center; + justify-content: center; + text-align: center; + } + .summary-info { + padding-left: 0; + } + .app-select-wrapper { + display: flex; + justify-content: center; + } + .summary-detail-list { + align-items: center; } .summary-center { order: -1; } .summary-right { flex-direction: row; + flex-wrap: wrap; justify-content: center; + gap: 20px; + } + .rate-display { + border-bottom: none; + padding: 0; } } @media (max-width: 768px) { - .api-statistics-container { - padding: 20px 16px; - } .statistics-summary-card { padding: 24px 20px; } - .app-toggle-buttons { - flex-wrap: wrap; - } - .toggle-btn { - flex: 1; - text-align: center; - } - .statistics-table thead th, - .statistics-table tbody td, - .statistics-table tfoot td { - padding: 12px 10px; - font-size: 13px; - } - .progress-cell { - min-width: 100px; + .statistics-search-section { + flex-direction: column; + align-items: stretch; + gap: 16px; } .search-filter-row { + flex-direction: row; + justify-content: space-between; + width: 100%; + } + .date-range-picker { + flex: 1; + justify-content: space-between; + } + .date-input { + width: 45%; + font-size: 13px; + padding: 6px 8px; + } + .board-table-header { + display: none !important; + } + .board-table-row { + flex-direction: column; + align-items: stretch; + height: auto; + padding: 10px 14px; + gap: 6px; + border-bottom: 1px solid #e2e8f0; + } + .board-table-row .row-cell { + display: flex !important; + justify-content: space-between !important; + align-items: center; + width: 100% !important; + text-align: right; + font-size: 13px; + padding: 0 !important; + } + .board-table-row .row-cell::before { + content: attr(data-label); + font-weight: 500; + color: #64748b; + text-align: left; + min-width: 80px; + } + .board-table-row .row-cell.col-name { + font-weight: 600; + justify-content: flex-start !important; + text-align: left; + border-bottom: none; + padding-bottom: 0 !important; + margin-bottom: 4px; + display: flex !important; + gap: 10px; + } + .board-table-row .row-cell.col-name::before { + display: none; + } + .stats-progress { + width: 100px; + } +} +.webhook-container { + max-width: 880px; + margin: 0 auto; + padding: 24px 16px 60px; + font-family: "Spoqa Han Sans Neo", "Noto Sans CJK KR", sans-serif; +} +.webhook-container .field-help { + color: #64748b; + font-size: 13px; + margin-top: 6px; +} +.webhook-container .field-help-red { + color: #f4253c; + font-size: 13px; + margin-top: 6px; + font-weight: 500; +} +@media (max-width: 768px) { + .webhook-container .field-help-red { + font-size: 11px; + } +} +.webhook-container .field-error, +.webhook-container .webhook-field-error { + color: #f4253c; + font-size: 13px; + margin-top: 6px; +} +.webhook-container .req { + color: #f4253c; +} + +.webhook-empty { + text-align: center; + padding: 60px 20px; + background: #ffffff; + border: 1px solid #dfdfdf; + border-radius: 20px; + box-shadow: 0px 4px 5.45px rgba(192, 192, 192, 0.25); +} +.webhook-empty .empty-icon { + display: inline-flex; + justify-content: center; + align-items: center; + width: 60px; + height: 60px; + background: #f8fafc; + border-radius: 50%; + margin-bottom: 16px; + color: #cbd5e1; + font-size: 25px; +} +.webhook-empty h3 { + margin: 0 0 8px; + font-size: 18px; + font-weight: 700; + color: #000000; +} +.webhook-empty p { + color: #64748b; + font-size: 14px; + margin: 0 0 24px; +} + +.webhook-steps { + display: flex; + align-items: center; + justify-content: center; + gap: 30px; + margin-bottom: 24px; + background: #ffffff; + border-radius: 20px; + box-shadow: 0 4px 13.45px rgba(192, 192, 192, 0.25); + height: 160px; + padding: 0 40px; +} +@media (max-width: 768px) { + .webhook-steps { + height: 120px; + gap: 16px; + padding: 0 20px; + } +} +.webhook-steps .webhook-step { + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + color: #94a3b8; + position: relative; + flex: 1; +} +.webhook-steps .webhook-step .step-no { + width: 55px; + height: 55px; + border-radius: 50%; + background: #f0f4fa; + border: 1.5px solid #dde4ee; + display: flex; + align-items: center; + justify-content: center; + color: #b0bec5; + font-size: 16px; + font-weight: 700; + transition: all 0.2s ease; +} +@media (max-width: 768px) { + .webhook-steps .webhook-step .step-no { + width: 44px; + height: 44px; + font-size: 14px; + } +} +.webhook-steps .webhook-step .step-name { + font-size: 14px; + font-weight: 500; + color: #94a3b8; +} +@media (max-width: 768px) { + .webhook-steps .webhook-step .step-name { + font-size: 12px; + } +} +.webhook-steps .webhook-step.active { + color: #2a69de; +} +.webhook-steps .webhook-step.active .step-no { + background: #2a69de; + border-color: #2a69de; + color: #ffffff; +} +.webhook-steps .webhook-step.active .step-name { + color: #0d0e11; + font-weight: 600; +} + +.webhook-field { + margin-bottom: 24px; +} +.webhook-field label { + display: block; + font-size: 15px; + font-weight: 700; + color: #0d0e11; + margin-bottom: 10px; +} +.webhook-field input[type=text], +.webhook-field input[type=password] { + width: 100%; + height: 46px; + padding: 0 16px; + border: 1.5px solid #dfdfdf; + border-radius: 8px; + font-size: 14px; + color: #0d0e11; + background: #ffffff; + transition: border-color 0.2s; +} +.webhook-field input[type=text]:focus, +.webhook-field input[type=password]:focus { + outline: none; + border-color: #2a69de; +} +.webhook-field input[type=text]::placeholder, +.webhook-field input[type=password]::placeholder { + color: #94a3b8; +} + +.webhook-eventtype-list { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 12px; +} +@media (max-width: 576px) { + .webhook-eventtype-list { + grid-template-columns: 1fr; + } +} +.webhook-eventtype-list .eventtype-item { + display: flex; + align-items: center; + gap: 12px; + padding: 14px 18px; + border: 1.5px solid #dfdfdf; + border-radius: 8px; + background: #ffffff; + cursor: pointer; + transition: all 0.2s; +} +.webhook-eventtype-list .eventtype-item input[type=checkbox] { + width: 18px; + height: 18px; + border: 1.5px solid #cbd5e1; + border-radius: 4px; + accent-color: #2a69de; +} +.webhook-eventtype-list .eventtype-item .eventtype-name { + font-size: 14px; + font-weight: 600; + color: #0d0e11; +} +.webhook-eventtype-list .eventtype-item .eventtype-code { + color: #64748b; + font-size: 12px; + margin-left: auto; +} +.webhook-eventtype-list .eventtype-item:hover { + border-color: #2a69de; + background: #f8fafc; +} + +.webhook-api-groups { + margin: 16px 0 8px; +} + +.webhook-api-group { + margin-bottom: 24px; +} +.webhook-api-group .group-name { + font-size: 16px; + font-weight: 700; + color: #0d0e11; + margin-bottom: 12px; +} + +.webhook-api-list { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); + gap: 10px; +} +.webhook-api-list .webhook-api-card { + display: flex; + align-items: center; + gap: 10px; + padding: 12px 16px; + border: 1.5px solid #dfdfdf; + border-radius: 8px; + background: #ffffff; + cursor: pointer; + transition: all 0.2s; +} +.webhook-api-list .webhook-api-card input[type=checkbox] { + width: 18px; + height: 18px; + accent-color: #2a69de; +} +.webhook-api-list .webhook-api-card .api-name { + font-size: 14px; + font-weight: 600; + color: #0d0e11; +} +.webhook-api-list .webhook-api-card .api-id { + color: #64748b; + font-size: 12px; + margin-left: auto; +} +.webhook-api-list .webhook-api-card:hover { + border-color: #2a69de; + background: #f8fafc; +} + +.webhook-actions { + display: flex; + justify-content: space-between; + gap: 12px; + margin-top: 32px; +} +.webhook-actions.center { + justify-content: center; +} + +.btn-webhook-next, +.btn-webhook-create { + display: inline-flex; + align-items: center; + justify-content: center; + width: 144px; + height: 40px; + background: #2a69de; + color: #ffffff; + border: none; + border-radius: 8px; + font-size: 14px; + font-weight: 700; + cursor: pointer; + text-decoration: none; + transition: background-color 0.2s; +} +.btn-webhook-next:hover, +.btn-webhook-create:hover { + background-color: #1b4ab7; +} + +.btn-webhook-cancel { + display: inline-flex; + align-items: center; + justify-content: center; + width: 144px; + height: 40px; + background: #ffffff; + color: #5f666c; + border: 1px solid #dfdfdf; + border-radius: 8px; + font-size: 14px; + font-weight: 700; + cursor: pointer; + text-decoration: none; + transition: all 0.2s; +} +.btn-webhook-cancel:hover { + background: #f8fafc; + border-color: #cbd5e1; +} + +.btn-webhook-danger { + display: inline-flex; + align-items: center; + justify-content: center; + width: 144px; + height: 40px; + background: #ffffff; + color: #f4253c; + border: 1px solid #f4253c; + border-radius: 8px; + font-size: 14px; + font-weight: 700; + cursor: pointer; + transition: all 0.2s; +} +.btn-webhook-danger:hover { + background: #f4253c; + color: #ffffff; +} + +.btn-mini { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0 14px; + height: 28px; + background: #f1f5f9; + color: #334155; + border: 1px solid #cbd5e1; + border-radius: 6px; + font-size: 11px; + font-weight: 600; + cursor: pointer; + transition: all 0.2s; +} +.btn-mini:hover { + background: #e2e8f0; + color: #0f172a; +} + +.btn-copy { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0 20px; + height: 46px; + background: #2a69de; + color: #ffffff; + border: none; + border-radius: 8px; + font-size: 14px; + font-weight: 700; + cursor: pointer; + transition: background-color 0.2s; +} +.btn-copy:hover { + background-color: #1b4ab7; +} + +.webhook-card { + border: 1px solid #dfdfdf; + border-radius: 20px; + padding: 28px 32px; + background: #ffffff; + box-shadow: 0px 4px 13.45px rgba(192, 192, 192, 0.25); +} +@media (max-width: 768px) { + .webhook-card { + padding: 20px; + } +} +.webhook-card .webhook-card-head { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 24px; +} +.webhook-card .webhook-card-head h3 { + font-size: 18px; + font-weight: 700; + color: #000000; + margin: 0; +} +.webhook-card .webhook-card-head .webhook-created { + color: #64748b; + font-size: 13px; +} +.webhook-card .webhook-card-row { + display: flex; + gap: 16px; + padding: 16px 0; + border-top: 1px solid #f1f5f9; + align-items: center; +} +@media (max-width: 576px) { + .webhook-card .webhook-card-row { + flex-direction: column; + align-items: flex-start; + gap: 6px; + padding: 14px 0; + } +} +.webhook-card .webhook-card-row .row-label { + width: 120px; + color: #64748b; + font-size: 14px; + font-weight: 700; + flex-shrink: 0; +} +.webhook-card .webhook-card-row .row-value { + flex: 1; + font-size: 14px; + color: #0f172a; + word-break: break-all; +} +.webhook-card .webhook-card-row.secret-row, +.webhook-card .webhook-card-row .secret-row { + display: flex; + align-items: center; + gap: 10px; + flex-wrap: wrap; +} +.webhook-card .webhook-card-row code { + font-family: Consolas, Monaco, monospace; + font-size: 14px; + background: #f1f5f9; + color: #334155; + padding: 4px 10px; + border-radius: 6px; +} +.webhook-card .eventtype-badge, +.webhook-card .api-badge { + display: inline-flex; + align-items: center; + padding: 4px 12px; + margin: 2px 6px 2px 0; + border-radius: 999px; + font-size: 12px; + font-weight: 500; + background: #e0f2fe; + color: #0369a1; +} +.webhook-card .api-badge { + background: #f1f5f9; + color: #475569; +} +.webhook-card .webhook-card-actions { + display: flex; + gap: 12px; + justify-content: flex-end; + margin-top: 24px; +} + +.webhook-complete { + text-align: center; + padding: 30px 20px; +} +.webhook-complete .complete-icon { + display: inline-flex; + justify-content: center; + align-items: center; + width: 64px; + height: 64px; + background: #f0fdf4; + border-radius: 50%; + color: #16a34a; + font-size: 32px; + margin-bottom: 20px; +} +.webhook-complete h3 { + font-size: 22px; + font-weight: 700; + color: #000000; + margin: 0 0 12px; +} +.webhook-complete .webhook-secret-box { + text-align: left; + max-width: 640px; + margin: 32px auto; + padding: 24px; + background: #ffffff; + border: 1px solid #dfdfdf; + border-radius: 20px; + box-shadow: 0 4px 13.45px rgba(192, 192, 192, 0.25); +} +.webhook-complete .webhook-secret-box .secret-warning { + color: #9a3412; + background: #fff7ed; + border: 1px solid #ffedd5; + border-radius: 8px; + padding: 14px 16px; + margin-bottom: 20px; + font-size: 13px; + line-height: 1.5; +} +.webhook-complete .webhook-secret-box label { + display: block; + font-size: 14px; + font-weight: 700; + color: #0f172a; + margin-bottom: 8px; +} +.webhook-complete .webhook-secret-box .secret-value-row { + display: flex; + gap: 10px; +} +.webhook-complete .webhook-secret-box .secret-value-row input { + flex: 1; + height: 46px; + padding: 0 16px; + border: 1.5px solid #dfdfdf; + border-radius: 8px; + font-family: Consolas, Monaco, monospace; + font-size: 14px; + color: #0f172a; + background: #f8fafc; +} +.webhook-complete .webhook-secret-box .secret-value-row input:focus { + outline: none; +} + +.webhook-header-row { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 24px; + width: 100%; +} +@media (max-width: 768px) { + .webhook-header-row { + flex-direction: column; + align-items: flex-start; + gap: 12px; + } +} +.webhook-header-row .s1-title { + font-size: 30px; + font-weight: 700; + color: #0d0e11; + margin: 0 !important; + padding: 0; +} +.webhook-header-row .webhook-guide-btn { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0 16px; + height: 35px; + background-color: #d8f0ff; + color: #0066ff; + font-size: 12px; + font-weight: 600; + border-radius: 7px; + text-decoration: none; + transition: background-color 0.2s; +} +.webhook-header-row .webhook-guide-btn:hover { + background-color: #c4e5ff; +} + +.step1-wrap .s1-form-card { + border: 1px solid #dfdfdf; + border-radius: 20px; + padding: 31px 35px; + background: #ffffff; + box-shadow: 0px 4px 13.45px rgba(192, 192, 192, 0.25); +} +@media (max-width: 768px) { + .step1-wrap .s1-form-card { + padding: 20px 16px; + } +} +.step1-wrap .s1-form-card .webhook-card-head { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 20px; + padding-bottom: 16px; + border-bottom: 1.5px solid #818181; +} +.step1-wrap .s1-form-card .webhook-card-head .head-title-group { + display: flex; + align-items: center; + gap: 10px; + color: #0d0e11; +} +.step1-wrap .s1-form-card .webhook-card-head .head-title-group .head-icon { + color: #0d0e11; + flex-shrink: 0; +} +.step1-wrap .s1-form-card .webhook-card-head h3 { + font-size: 18px; + font-weight: 700; + color: #000000; + margin: 0; +} +.step1-wrap .s1-form-card .webhook-card-head .webhook-created { + color: #64748b; + font-size: 13px; + font-weight: 500; +} +.step1-wrap .s1-form-card .webhook-card-content { + display: flex; + flex-direction: column; + gap: 28px; + margin-top: 24px; +} +.step1-wrap .s1-form-card .webhook-info-group { + display: flex; + flex-direction: column; + gap: 12px; + width: 100%; +} +.step1-wrap .s1-form-card .webhook-info-group .group-label { + font-size: 15px; + font-weight: 700; + color: #0d0e11; + line-height: 1; +} +.step1-wrap .s1-form-card .webhook-info-group .input-display-box { + width: 100%; + height: 48px; + background-color: #efefef; + border-radius: 10px; + padding: 0 20px; + display: flex; + align-items: center; + font-size: 14px; + color: #4e5968; + font-weight: 500; + word-break: break-all; + box-sizing: border-box; + border: 1px solid #DFDFDF; +} +.step1-wrap .s1-form-card .webhook-info-group .badges-row { + display: flex; + flex-wrap: wrap; + gap: 8px; +} +.step1-wrap .s1-form-card .webhook-info-group .eventtype-badge { + display: inline-flex; + align-items: center; + justify-content: center; + height: 28px; + padding: 0 14px; + border: 1px solid #99c2ff; + border-radius: 14px; + font-size: 12px; + font-weight: 500; + background-color: #e6f0ff; + color: #0055ff; + white-space: nowrap; +} +.step1-wrap .s1-form-card .webhook-info-group .api-badge { + display: inline-flex; + align-items: center; + justify-content: center; + height: 28px; + padding: 0 14px; + border: 1px solid #cbd5e1; + border-radius: 14px; + font-size: 12px; + font-weight: 500; + background-color: #f1f5f9; + color: #475569; + white-space: nowrap; +} +.step1-wrap .s1-form-card .webhook-info-group .secret-action-row { + display: flex; + align-items: center; + gap: 10px; + width: 100%; +} +@media (max-width: 576px) { + .step1-wrap .s1-form-card .webhook-info-group .secret-action-row { flex-wrap: wrap; } } +.step1-wrap .s1-form-card .webhook-info-group .secret-box { + flex: 1; + max-width: 250px; + height: 48px; + background-color: #efefef; + border-radius: 10px; + padding: 0 20px; + display: flex; + align-items: center; + justify-content: center; + font-size: 14px; + color: #4e5968; + font-weight: 500; + box-sizing: border-box; + letter-spacing: 1px; + border: 1px solid #DFDFDF; +} +@media (max-width: 576px) { + .step1-wrap .s1-form-card .webhook-info-group .secret-box { + max-width: 100%; + width: 100%; + flex: none; + } +} +.step1-wrap .s1-form-card .webhook-info-group .btn-reveal-action { + height: 48px; + padding: 0 24px; + background: #ffffff; + color: #0055ff; + border: 1px solid #99c2ff; + border-radius: 10px; + font-size: 14px; + font-weight: 600; + cursor: pointer; + transition: background-color 0.2s; +} +.step1-wrap .s1-form-card .webhook-info-group .btn-reveal-action:hover { + background-color: #f0f7ff; +} +.step1-wrap .s1-form-card .webhook-info-group .btn-regen-action { + height: 48px; + padding: 0 24px; + background: #ffffff; + color: #0055ff; + border: 1px solid #99c2ff; + border-radius: 10px; + font-size: 14px; + font-weight: 600; + cursor: pointer; + transition: background-color 0.2s; +} +.step1-wrap .s1-form-card .webhook-info-group .btn-regen-action:hover { + background-color: #f0f7ff; +} +.step1-wrap .s1-form-card .webhook-empty { + background: none; + border: none; + padding: 24px 0; + box-shadow: none; +} + +.btn-webhook-danger-figma { + display: inline-flex; + align-items: center; + justify-content: center; + width: 144px; + height: 48px; + background-color: #f25252; + color: #ffffff; + border: none; + border-radius: 10px; + font-size: 15px; + font-weight: 700; + cursor: pointer; + transition: background-color 0.2s; +} +.btn-webhook-danger-figma:hover { + background-color: #dc3c3c; +} + +.s1-btn-next-figma { + display: inline-flex; + align-items: center; + justify-content: center; + width: 144px; + height: 48px; + background-color: #3b76e3; + color: #ffffff; + border: none; + border-radius: 10px; + font-size: 15px; + font-weight: 700; + cursor: pointer; + text-decoration: none; + transition: background-color 0.2s; +} +.s1-btn-next-figma:hover { + background-color: #2762cf; +} + +.step1-wrap .s1-actions, +.step2-wrap .s1-actions, +.step3-wrap .s1-actions { + display: flex; + gap: 12px; + justify-content: flex-end; + margin-top: 32px; +} +.step1-wrap .s1-actions.s1-actions--figma-step, +.step2-wrap .s1-actions.s1-actions--figma-step, +.step3-wrap .s1-actions.s1-actions--figma-step { + justify-content: flex-end !important; +} +.step1-wrap .s1-actions.s1-actions--figma-step .btn-webhook-cancel-red, +.step2-wrap .s1-actions.s1-actions--figma-step .btn-webhook-cancel-red, +.step3-wrap .s1-actions.s1-actions--figma-step .btn-webhook-cancel-red { + margin-right: 0 !important; +} +.step1-wrap .s1-actions .btn-webhook-cancel, +.step1-wrap .s1-actions .btn-webhook-danger, +.step2-wrap .s1-actions .btn-webhook-cancel, +.step2-wrap .s1-actions .btn-webhook-danger, +.step3-wrap .s1-actions .btn-webhook-cancel, +.step3-wrap .s1-actions .btn-webhook-danger { + margin-right: auto; +} + +.btn-webhook-cancel-red { + display: inline-flex; + align-items: center; + justify-content: center; + width: 144px; + height: 48px; + background-color: #ffebeb; + color: #f4253c; + border: 1px solid #ffa3a3; + border-radius: 10px; + font-size: 15px; + font-weight: 700; + cursor: pointer; + text-decoration: none; + transition: all 0.2s; +} +.btn-webhook-cancel-red:hover { + background-color: #fdd8d8; + border-color: #ff8b8b; + color: #f4253c; +} + +.webhook-guide-link { + margin-top: 30px; + text-align: center; +} +.webhook-guide-link a { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 12px 20px; + border: 1px dashed #dfdfdf; + border-radius: 10px; + color: #2a69de; + font-size: 14px; + font-weight: 600; + text-decoration: none; + transition: all 0.2s; +} +.webhook-guide-link a:hover { + border-color: #2a69de; + background: #f0f7ff; +} + +.webhook-modal { + position: fixed; + inset: 0; + background: rgba(15, 23, 42, 0.4); + backdrop-filter: blur(4px); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; +} +.webhook-modal .webhook-modal-box { + background: #ffffff; + border-radius: 20px; + padding: 32px; + width: 400px; + max-width: 90vw; + box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); +} +.webhook-modal .webhook-modal-box h4 { + font-size: 18px; + font-weight: 700; + color: #0f172a; + margin: 0 0 8px; +} +.webhook-modal .webhook-modal-box p { + color: #64748b; + font-size: 14px; + line-height: 1.5; + margin: 0 0 20px; +} +.webhook-modal .webhook-modal-box input { + width: 100%; + height: 46px; + padding: 0 16px; + border: 1.5px solid #dfdfdf; + border-radius: 8px; + margin-bottom: 8px; + font-size: 14px; + color: #0f172a; +} +.webhook-modal .webhook-modal-box input:focus { + outline: none; + border-color: #2a69de; +} +.webhook-modal .webhook-modal-box .webhook-modal-actions { + display: flex; + gap: 10px; + justify-content: flex-end; + margin-top: 20px; +} +.webhook-modal .webhook-modal-box .webhook-modal-actions .btn-webhook-next, +.webhook-modal .webhook-modal-box .webhook-modal-actions .btn-webhook-cancel { + width: 90px; + height: 38px; + font-size: 13px; +} + +.step3-wrap .s3-result-container { + background: #ffffff; + border: 1px solid #dfdfdf; + border-radius: 20px; + box-shadow: 0 4px 26.9px 2px rgba(192, 192, 192, 0.25); + display: flex; + flex-direction: column; + align-items: center; + padding: 60px 40px; + margin-bottom: 24px; + text-align: center; +} +.step3-wrap .s3-graphic-wrapper { + margin-bottom: 20px; + display: flex; + align-items: center; + justify-content: center; + width: 100%; +} +.step3-wrap .s3-illustration { + width: 320px; + height: auto; + max-width: 100%; +} +.step3-wrap .s3-illustration .floating-slow { + animation: floatSlow 2s ease-in-out infinite alternate; +} +@keyframes floatSlow { + 0% { + transform: translateY(0); + } + 100% { + transform: translateY(-10px); + } +} +.step3-wrap .s3-message-wrapper { + margin-bottom: 40px; + text-align: center; +} +.step3-wrap .s3-success-title { + font-size: 26px; + font-weight: 700; + color: #2a69de; + margin-bottom: 16px; + font-family: "Spoqa Han Sans Neo", sans-serif; +} +.step3-wrap .s3-success-desc { + font-size: 16px; + line-height: 1.6; + color: #64748b; + font-family: "Spoqa Han Sans Neo", sans-serif; + margin: 0; +} +.step3-wrap .s3-success-desc .s3-highlight { + color: #0d0e11; + font-weight: 700; +} +.step3-wrap .s3-actions { + display: flex; + justify-content: center; + width: 100%; +} +.step3-wrap .s3-btn-complete { + display: inline-flex; + align-items: center; + justify-content: center; + width: 200px; + height: 48px; + background: #2a69de; + color: #ffffff; + border-radius: 10px; + font-size: 16px; + font-weight: 500; + text-decoration: none; + transition: background 0.15s; +} +.step3-wrap .s3-btn-complete:hover { + background: #1b4ab7; + color: #ffffff; +} + +.as-title-banner, +.as-title-banner h1, +.api-status, +.api-status h1, +.api-status h2, +.api-status h3, +.api-status h4 { + font-family: "Spoqa Han Sans Neo", "SpoqaHanSans", "Noto Sans KR", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; +} + +.as-title-banner { + margin: 44px 0 28px; + border-radius: 12px; + background: linear-gradient(110deg, #003080 0%, #0049b4 60%, #066ae5 100%); + color: #fff; + overflow: hidden; +} +.as-title-banner .as-title-banner-inner { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + flex-wrap: wrap; + padding: 32px; +} +.as-title-banner h1 { + margin: 0; + font-size: 24px; + font-weight: 700; + letter-spacing: -0.02em; + color: #fff; +} +.as-title-banner .as-title-meta { + text-align: right; +} +.as-title-banner .as-title-meta p { + margin: 0; + font-weight: 400; +} +.as-title-banner .as-title-meta-main { + font-size: 13px; + color: #fff; +} +.as-title-banner .as-title-meta-main strong { + font-weight: 500; +} +.as-title-banner .as-title-meta-sub { + margin-top: 2px; + font-size: 11px; + color: #c9d1dc; +} +.as-title-banner .as-title-dot { + display: inline-block; + width: 8px; + height: 8px; + margin-right: 6px; + border-radius: 50%; + background: #56e3a6; + box-shadow: 0 0 0 4px rgba(86, 227, 166, 0.25); +} +@media (max-width: 720px) { + .as-title-banner .as-title-banner-inner { + padding: 24px 20px; + } + .as-title-banner h1 { + font-size: 20px; + } + .as-title-banner .as-title-meta { + text-align: left; + } +} + +.api-status { + --as-ok: #1b8c4a; + --as-ok-bg: #e7f6ec; + --as-warn: #c77800; + --as-warn-bg: #fef3c7; + --as-err: #c8362f; + --as-err-bg: #feeae7; + --as-info: #0a66c2; + --as-info-bg: #e7f0fb; + --as-both: #6b21a8; + --as-gray-bg: #eef1f5; + --as-none-bg: var(--as-ok); + --as-border: #e0e6f1; + --as-border-strong: #d1d5db; + --as-text: #020616; + --as-text-2: #252b37; + --as-text-3: #4b5563; + --as-muted: #6b7280; + --as-radius: 12px; + --as-pill: 999px; + display: grid; + grid-template-columns: minmax(0, 1fr); + gap: 20px; + padding: 0 0 70px; +} +.api-status.is-standalone { + padding-top: 44px; +} +.api-status { + font-weight: 400; +} +.api-status p, +.api-status span, +.api-status li, +.api-status a, +.api-status td, +.api-status th, +.api-status label, +.api-status button, +.api-status input, +.api-status select { + font-weight: 400; +} +.api-status b, +.api-status strong { + font-weight: 500; +} +.api-status .as-card { + background: #fff; + border: 1px solid var(--as-border); + border-radius: var(--as-radius); + padding: 24px; + box-shadow: 0 1px 2px rgba(15, 23, 42, 0.05); +} +.api-status .as-card-head { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: 12px; + flex-wrap: wrap; + margin-bottom: 14px; +} +.api-status .as-card-head h2 { + margin: 0; + font-size: 18px; + letter-spacing: -0.01em; + color: var(--as-text); +} +.api-status .as-card-head .as-desc { + margin: 4px 0 0; + font-size: 13px; + color: var(--as-muted); +} +.api-status .as-badge { + display: inline-flex; + align-items: center; + gap: 4px; + font-size: 11px; + font-weight: 500; + padding: 4px 10px; + border-radius: var(--as-pill); + letter-spacing: 0.02em; + white-space: nowrap; +} +.api-status .as-badge.is-incident, .api-status .as-badge.is-outage { + background: var(--as-err-bg); + color: var(--as-err); +} +.api-status .as-badge.is-maintenance { + background: var(--as-info-bg); + color: var(--as-info); +} +.api-status .as-badge.is-degraded { + background: var(--as-warn-bg); + color: var(--as-warn); +} +.api-status .as-badge.is-normal { + background: var(--as-ok-bg); + color: var(--as-ok); +} +.api-status .as-badge.is-muted { + background: var(--as-gray-bg); + color: var(--as-text-3); +} +.api-status .as-section-title { + display: flex; + align-items: baseline; + gap: 12px; + flex-wrap: wrap; + margin: 6px 4px 14px; +} +.api-status .as-section-title h2 { + margin: 0; + font-size: 20px; + letter-spacing: -0.01em; +} +.api-status .as-section-title .as-desc { + font-size: 13px; + color: var(--as-muted); +} +.api-status .as-section-title .as-count { + font-size: 12px; + font-weight: 500; + color: var(--as-muted); + background: #f6f9fb; + padding: 2px 8px; + border-radius: var(--as-pill); +} +.api-status .as-empty { + color: var(--as-muted); + font-size: 14px; + padding: 26px; + background: #fff; + border: 1px dashed var(--as-border); + border-radius: var(--as-radius); + text-align: center; +} +.api-status .as-api-pills { + display: flex; + gap: 6px; + flex-wrap: wrap; + align-items: center; + margin-top: 12px; + font-size: 13px; + color: var(--as-text-3); +} +.api-status .as-api-pills .as-api-pill { + background: var(--as-gray-bg); + color: var(--as-text-2); + font-size: 12px; + font-weight: 500; + padding: 4px 10px; + border-radius: var(--as-pill); +} +.api-status .as-api-pills a.as-api-pill { + text-decoration: none; + transition: background 0.15s, color 0.15s; +} +.api-status .as-api-pills a.as-api-pill:hover, .api-status .as-api-pills a.as-api-pill:focus-visible { + background: var(--as-info-bg); + color: var(--as-info); +} +.api-status .as-api-pills .as-api-pill.is-unlinked { + background: #f3f5f8; + color: var(--as-muted); + cursor: default; +} +.api-status .as-api-pills .as-api-pill.is-gw { + background: #fff; + border: 1px dashed var(--as-border-strong); + color: var(--as-muted); + cursor: default; +} +.api-status .as-card-split { + margin-top: 16px; + display: grid; + gap: 16px; + align-items: start; +} +@media (min-width: 961px) { + .api-status .as-card-split { + grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); + } +} +.api-status .as-card-split > .as-alert-notice { + margin-top: 0; +} +.api-status .as-alert-card .as-card-split > .as-alert-timeline { + margin-top: 0; +} +.api-status .as-maint-card .as-card-split > .as-maint-body { + margin-top: 0; +} +.api-status .as-alert-notice { + margin-top: 16px; + border: 1px solid var(--as-border); + border-radius: var(--as-radius); + background: var(--as-gray-bg); + padding: 14px 16px; +} +.api-status .as-alert-notice .as-alert-notice-head { + display: flex; + align-items: center; + gap: 8px; + flex-wrap: wrap; + margin-bottom: 10px; +} +.api-status .as-alert-notice .as-alert-notice-head strong { + font-size: 14px; + color: var(--as-text); +} +.api-status .as-alert-notice .as-alert-notice-label { + font-size: 11px; + font-weight: 500; + padding: 2px 8px; + border-radius: var(--as-pill); + background: var(--as-info-bg); + color: var(--as-info); +} +.api-status .as-alert-notice .as-alert-notice-body { + max-height: 320px; + overflow-y: auto; + font-size: 14px; + line-height: 1.7; + color: var(--as-text-2); + word-break: break-word; +} +.api-status .as-alert-notice .as-alert-notice-body img, .api-status .as-alert-notice .as-alert-notice-body table { + max-width: 100%; +} +.api-status .as-alert-notice .as-alert-notice-body p:last-child { + margin-bottom: 0; +} +.api-status .as-alert-notice .as-alert-notice-link { + display: inline-block; + margin-top: 12px; + font-size: 13px; + font-weight: 500; + color: var(--as-info); + text-decoration: none; +} +.api-status .as-alert-card { + border-left: 4px solid var(--as-err); +} +.api-status .as-alert-card .as-alert-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + flex-wrap: wrap; +} +.api-status .as-alert-card .as-alert-title { + margin: 8px 0 6px; + font-size: 20px; + color: var(--as-err); +} +.api-status .as-alert-card .as-meta { + font-size: 13px; + color: var(--as-text-3); +} +.api-status .as-alert-card .as-alert-timeline { + margin-top: 16px; + border-top: 1px dashed var(--as-border); + padding-top: 14px; + display: grid; + gap: 10px; +} +.api-status .as-alert-card .as-tl-row { + display: grid; + grid-template-columns: 60px 76px 1fr; + gap: 12px; + font-size: 13px; +} +.api-status .as-alert-card .as-tl-row .as-tl-time { + color: var(--as-muted); + font-variant-numeric: tabular-nums; +} +.api-status .as-alert-card .as-tl-row .as-tl-who { + font-size: 11px; + font-weight: 500; + text-align: center; + height: -webkit-fit-content; + height: fit-content; + padding: 2px 8px; + border-radius: var(--as-pill); + background: var(--as-gray-bg); + color: var(--as-text-2); +} +.api-status .as-alert-card .as-tl-row .as-tl-who.is-system { + background: var(--as-info-bg); + color: var(--as-info); +} +.api-status .as-alert-overflow { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + flex-wrap: wrap; + padding: 14px 20px; + border: 1px dashed var(--as-err); + border-radius: var(--as-radius); + background: var(--as-err-bg); + font-size: 13px; + color: var(--as-err); +} +.api-status .as-alert-overflow a { + color: var(--as-err); + font-weight: 500; + text-decoration: none; +} +.api-status .as-uptime .as-bar-chart { + display: flex; + gap: 2px; + align-items: flex-end; + height: 56px; + overflow-x: auto; +} +.api-status .as-uptime .as-axis { + display: flex; + justify-content: space-between; + margin-top: 14px; + font-size: 11px; + color: var(--as-muted); +} +.api-status .as-bar { + flex: 1 0 4px; + height: 100%; + border: 0; + padding: 0; + border-radius: 2px; + background: var(--as-ok); + cursor: pointer; + transition: opacity 0.15s; +} +.api-status .as-bar.is-outage { + background: var(--as-err); +} +.api-status .as-bar.is-maintenance { + background: var(--as-info); +} +.api-status .as-bar.is-degraded { + background: var(--as-warn); +} +.api-status .as-bar:hover { + opacity: 0.75; +} +.api-status .as-legend { + display: flex; + gap: 14px; + flex-wrap: wrap; + margin-top: 18px; + font-size: 12px; + color: var(--as-muted); +} +.api-status .as-legend .as-sw { + display: inline-block; + width: 10px; + height: 10px; + border-radius: 2px; + margin-right: 4px; + vertical-align: middle; +} +.api-status .as-legend .as-sw.is-ok { + background: var(--as-ok); +} +.api-status .as-legend .as-sw.is-outage { + background: var(--as-err); +} +.api-status .as-legend .as-sw.is-maintenance { + background: var(--as-info); +} +.api-status .as-legend .as-sw.is-degraded { + background: var(--as-warn); +} +.api-status .as-legend .as-sw.is-none { + background: var(--as-none-bg); +} +.api-status .as-legend .as-sw.is-mixed, .api-status .as-legend .as-sw.is-both { + background: var(--as-both); +} +.api-status .as-my-apis table { + width: 100%; + border-collapse: collapse; + font-size: 13px; +} +.api-status .as-my-apis th, +.api-status .as-my-apis td { + text-align: left; + padding: 12px 14px; +} +.api-status .as-my-apis th { + background: #f6f9fb; + color: var(--as-text-3); + font-size: 12px; + font-weight: 500; + border-bottom: 1px solid var(--as-border); +} +.api-status .as-my-apis tbody tr { + border-bottom: 1px solid var(--as-border); + cursor: pointer; +} +.api-status .as-my-apis tbody tr:last-child { + border-bottom: 0; +} +.api-status .as-my-apis tbody tr:hover { + background: #f6f9fb; +} +.api-status .as-my-apis .as-api-name { + font-weight: 500; + color: var(--as-text); +} +.api-status .as-my-apis .as-num { + font-variant-numeric: tabular-nums; +} +.api-status .as-maint-list { + display: grid; + gap: 14px; +} +.api-status .as-maint-card { + background: #fff; + border: 1px solid var(--as-border); + border-left: 4px solid var(--as-info); + border-radius: var(--as-radius); + padding: 22px 24px; + box-shadow: 0 1px 2px rgba(15, 23, 42, 0.05); +} +.api-status .as-maint-card.is-ongoing { + border-left-color: var(--as-warn); +} +.api-status .as-maint-card .as-maint-head { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: 12px; + flex-wrap: wrap; +} +.api-status .as-maint-card .as-maint-title { + margin: 0; + font-size: 18px; + font-weight: 700; + color: var(--as-text); +} +.api-status .as-maint-card .as-schedule { + display: inline-flex; + align-items: center; + gap: 6px; + font-size: 12px; + font-weight: 500; + color: var(--as-info); + background: var(--as-info-bg); + padding: 4px 12px; + border-radius: var(--as-pill); + font-variant-numeric: tabular-nums; +} +.api-status .as-maint-card.is-ongoing .as-schedule { + color: var(--as-warn); + background: var(--as-warn-bg); +} +.api-status .as-maint-card .as-maint-body { + margin: 14px 0 12px; + font-size: 14px; + line-height: 1.6; + color: var(--as-text-3); +} +.api-status .as-maint-card .as-maint-meta { + text-align: right; + font-size: 12px; + color: var(--as-muted); + font-variant-numeric: tabular-nums; +} +.api-status .as-issue-list { + display: grid; + gap: 24px; +} +.api-status .as-issue-group .as-date-head { + display: flex; + align-items: center; + gap: 8px; + width: 100%; + margin: 0 4px 10px; + padding-bottom: 8px; + font-size: 14px; + font-weight: 500; + color: var(--as-text-2); + border-bottom: 1px solid var(--as-border); +} +.api-status .as-issue-card { + background: #fff; + border: 1px solid var(--as-border); + border-radius: var(--as-radius); + padding: 22px 26px 24px; + box-shadow: 0 1px 2px rgba(15, 23, 42, 0.05); +} +.api-status .as-issue-card.is-incident { + border-left: 4px solid var(--as-err); +} +.api-status .as-issue-card.is-degraded { + border-left: 4px solid var(--as-warn); +} +.api-status .as-issue-card.is-maintenance { + border-left: 4px solid var(--as-info); +} +.api-status .as-issue-card .as-issue-meta-row { + display: flex; + align-items: center; + gap: 12px; + flex-wrap: wrap; + font-size: 12px; + color: var(--as-muted); + margin-bottom: 10px; +} +.api-status .as-issue-card .as-issue-title { + margin: 0 0 18px; + font-size: 19px; + font-weight: 800; + line-height: 1.3; + letter-spacing: -0.01em; +} +.api-status .as-issue-card.is-incident .as-issue-title { + color: var(--as-err); +} +.api-status .as-issue-card.is-degraded .as-issue-title { + color: var(--as-warn); +} +.api-status .as-issue-card.is-maintenance .as-issue-title { + color: var(--as-info); +} +.api-status .as-tl-block { + position: relative; + padding-left: 24px; +} +.api-status .as-tl-block::before { + content: ""; + position: absolute; + left: 6px; + top: 8px; + bottom: 8px; + width: 1px; + background: var(--as-border); +} +.api-status .as-tl-item { + position: relative; + padding-bottom: 22px; +} +.api-status .as-tl-item:last-child { + padding-bottom: 0; +} +.api-status .as-tl-item + .as-tl-item { + margin-top: 4px; + padding-top: 22px; + border-top: 1px dashed var(--as-border); +} +.api-status .as-tl-item .as-dot { + position: absolute; + left: -24px; + top: 6px; + width: 12px; + height: 12px; + border-radius: 50%; + border: 2px solid #fff; + background: var(--as-muted); + box-shadow: 0 0 0 1px var(--as-border-strong); +} +.api-status .as-tl-item.state-RESOLVED .as-dot { + background: var(--as-ok); + box-shadow: 0 0 0 1px var(--as-ok); +} +.api-status .as-tl-item.state-MONITORING .as-dot { + background: var(--as-warn); + box-shadow: 0 0 0 1px var(--as-warn); +} +.api-status .as-tl-item.state-IDENTIFIED .as-dot { + background: #c95a0f; + box-shadow: 0 0 0 1px #c95a0f; +} +.api-status .as-tl-item.state-INVESTIGATING .as-dot { + background: var(--as-warn); + box-shadow: 0 0 0 1px var(--as-warn); +} +.api-status .as-tl-item.state-CANCELED .as-dot { + background: var(--as-muted); + box-shadow: 0 0 0 1px var(--as-muted); +} +.api-status .as-tl-item .as-tl-label { + margin: 0 0 8px; + font-size: 14px; + font-weight: 500; + color: var(--as-text); +} +.api-status .as-tl-item .as-tl-body { + margin: 0 0 8px; + font-size: 14px; + line-height: 1.6; + color: var(--as-text); + white-space: pre-line; +} +.api-status .as-tl-item .as-tl-ts { + margin: 0; + font-size: 13px; + color: var(--as-muted); + font-variant-numeric: tabular-nums; +} +.api-status .as-more-link { + display: block; + margin-top: 14px; + text-align: right; + font-size: 13px; + text-decoration: none; +} +.api-status .as-index-bar { + display: flex; + gap: 2px; + align-items: stretch; + height: 44px; + overflow-x: auto; +} +.api-status .as-index-cell { + flex: 1 0 4px; + border: 0; + padding: 0; + border-radius: 2px; + background: var(--as-none-bg); + cursor: pointer; + transition: opacity 0.15s; +} +.api-status .as-index-cell.has-incident { + background: var(--as-err); +} +.api-status .as-index-cell.has-degraded { + background: var(--as-warn); +} +.api-status .as-index-cell.has-maintenance { + background: var(--as-info); +} +.api-status .as-index-cell.has-mixed, .api-status .as-index-cell.has-both { + background: var(--as-both); +} +.api-status .as-index-cell.is-selected { + outline: 2px solid var(--as-text-2); + outline-offset: 1px; +} +.api-status .as-index-cell:hover { + opacity: 0.75; +} +.api-status .as-filter-bar { + display: flex; + align-items: center; + gap: 14px; + flex-wrap: wrap; + background: #fff; + border: 1px solid var(--as-border); + border-radius: var(--as-radius); + padding: 14px 18px; + font-size: 13px; +} +.api-status .as-filter-bar .as-filter-field { + display: flex; + align-items: center; + gap: 8px; + min-width: 0; +} +.api-status .as-filter-bar .as-filter-label { + font-weight: 500; + color: var(--as-text-2); + white-space: nowrap; +} +.api-status .as-filter-bar .as-filter-right { + display: flex; + align-items: center; + gap: 8px; + margin-left: auto; + color: var(--as-muted); + white-space: nowrap; +} +.api-status .as-filter-bar .as-filter-count b { + color: var(--as-text); +} +.api-status .as-input { + height: 34px; + padding: 0 10px; + border: 1px solid var(--as-border-strong); + border-radius: 6px; + font-size: 13px; + color: var(--as-text); + background: #fff; + max-width: 100%; +} +.api-status .as-input:focus { + outline: none; + border-color: var(--as-info); + box-shadow: 0 0 0 2px rgba(10, 102, 194, 0.15); +} +.api-status .as-field-control { + position: relative; + display: inline-flex; + align-items: center; +} +.api-status .as-field-control input { + padding-right: 30px; +} +.api-status .as-field-control input[type=search] { + -webkit-appearance: none; + appearance: none; +} +.api-status .as-field-control input[type=search]::-webkit-search-cancel-button, .api-status .as-field-control input[type=search]::-webkit-search-decoration { + -webkit-appearance: none; + appearance: none; + display: none; +} +.api-status .as-field-clear { + position: absolute; + top: 50%; + right: 6px; + transform: translateY(-50%); + display: none; + width: 20px; + height: 20px; + padding: 0; + border: 0; + border-radius: 50%; + background: var(--as-gray-bg); + color: var(--as-text-3); + font-size: 14px; + line-height: 1; + cursor: pointer; +} +.api-status .as-field-clear:hover { + background: var(--as-border-strong); + color: var(--as-text); +} +.api-status .as-filter-field.has-value .as-field-clear { + display: block; +} +.api-status #filterDateInput { + padding-right: 52px; +} +.api-status .as-combo .as-field-control { + min-width: 240px; + flex: 1 1 240px; +} +.api-status .as-combo input { + width: 100%; +} +.api-status .as-combo .as-combo-list { + position: absolute; + z-index: 20; + top: calc(100% + 4px); + left: 0; + right: 0; + max-height: 280px; + margin: 0; + padding: 4px; + overflow-y: auto; + list-style: none; + background: #fff; + border: 1px solid var(--as-border-strong); + border-radius: 8px; + box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12); +} +.api-status .as-combo .as-combo-item { + padding: 8px 10px; + border-radius: 6px; + cursor: pointer; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.api-status .as-combo .as-combo-item:hover { + background: var(--as-info-bg); +} +.api-status .as-combo .as-combo-empty { + padding: 10px; + color: var(--as-muted); + font-size: 12px; + text-align: center; +} +.api-status .as-unlisted-banner { + display: flex; + align-items: center; + gap: 10px; + padding: 12px 18px; + border: 1px solid var(--as-border); + border-left: 4px solid var(--as-muted); + border-radius: var(--as-radius); + background: #f6f8fa; + font-size: 13px; + color: var(--as-text-3); +} +.api-status .as-unlisted-banner b { + color: var(--as-text); +} +.api-status .as-unlisted-banner .as-unlisted-icon { + flex: 0 0 auto; + color: var(--as-muted); + font-size: 15px; +} +.api-status .as-pager { + display: flex; + justify-content: center; + gap: 6px; + margin-top: 6px; + flex-wrap: wrap; +} +.api-status .as-btn { + min-width: 32px; + height: 32px; + padding: 0 12px; + border: 1px solid var(--as-border-strong); + border-radius: 6px; + background: #fff; + color: var(--as-text-2); + font-size: 13px; + line-height: 1; + cursor: pointer; +} +.api-status .as-btn:hover { + background: #f6f9fb; +} +.api-status .as-btn.is-current { + border-color: var(--as-info); + background: var(--as-info); + color: #fff; + cursor: default; +} +@media (max-width: 960px) { + .api-status .as-filter-bar { + gap: 10px; + } + .api-status .as-filter-bar .as-filter-right { + margin-left: 0; + width: 100%; + justify-content: flex-end; + } + .api-status .as-my-apis table { + display: block; + overflow-x: auto; + white-space: nowrap; + } +} +@media (max-width: 720px) { + .api-status { + gap: 16px; + padding-bottom: 48px; + } + .api-status.is-standalone { + padding-top: 32px; + } + .api-status .as-card { + padding: 18px 16px; + } + .api-status .as-card-head { + margin-bottom: 12px; + } + .api-status .as-alert-card .as-tl-row { + grid-template-columns: 52px 1fr; + } + .api-status .as-alert-card .as-tl-who { + display: none; + } + .api-status .as-alert-card .as-alert-title { + font-size: 18px; + } + .api-status .as-filter-bar { + flex-direction: column; + align-items: stretch; + padding: 14px; + } + .api-status .as-filter-bar .as-filter-field { + flex-direction: column; + align-items: flex-start; + gap: 6px; + } + .api-status .as-filter-bar .as-input, + .api-status .as-filter-bar .as-field-control { + flex: 0 0 auto; + width: 100%; + min-width: 0; + } + .api-status .as-filter-bar .as-filter-right { + justify-content: space-between; + } + .api-status .as-index-bar { + height: 40px; + } + .api-status .as-index-bar .as-index-cell { + flex: 0 0 12px; + } + .api-status .as-uptime .as-bar-chart .as-bar { + flex: 0 0 12px; + } + .api-status .as-issue-card { + padding: 18px 16px; + } + .api-status .as-issue-title { + font-size: 17px; + } + .api-status .as-maint-card { + padding: 18px 16px; + } + .api-status .as-uptime .as-bar-chart { + height: 44px; + } + .api-status .as-issue-list { + gap: 16px; + } + .api-status .as-pager button { + min-width: 30px; + padding: 0 8px; + } +} +@media (max-width: 480px) { + .api-status .as-section-title h2 { + font-size: 18px; + } + .api-status .as-legend { + gap: 8px; + font-size: 11px; + } + .api-status .as-tl-block { + padding-left: 18px; + } + .api-status .as-tl-item .as-dot { + left: -18px; + } +} + .d-none { display: none !important; } diff --git a/src/main/resources/static/css/main.css.map b/src/main/resources/static/css/main.css.map index 257c255..fb55129 100644 --- a/src/main/resources/static/css/main.css.map +++ b/src/main/resources/static/css/main.css.map @@ -1 +1 @@ -{"version":3,"sourceRoot":"","sources":["../sass/base/_reset.scss","../sass/abstracts/_variables.scss","../sass/base/_typography.scss","../sass/abstracts/_mixins.scss","../sass/base/_animations.scss","../sass/base/_djb-font.scss","../sass/layout/_header.scss","../sass/layout/_footer.scss","../sass/layout/_grid.scss","../sass/layout/_container.scss","../sass/components/_buttons.scss","../sass/components/_badges.scss","../sass/components/_cards.scss","../sass/components/_apikey-common.scss","../sass/components/_forms.scss","../sass/components/_modals.scss","../sass/components/_navigation.scss","../sass/components/_partners.scss","../sass/components/_cta.scss","../sass/components/_password-popup.scss","../sass/components/_header-auth.scss","../sass/components/_tables.scss","../sass/components/_accordion.scss","../sass/components/_page-title-banner.scss","../sass/components/_alerts.scss","../sass/components/_pagination.scss","../sass/components/_breadcrumb.scss","../sass/components/_test-env-notice.scss","../sass/pages/_index.scss","../sass/pages/_api-market.scss","../sass/pages/_login.scss","../sass/pages/_account-recovery.scss","../sass/pages/_signup-selection.scss","../sass/pages/_mypage.scss","../sass/pages/_apikey-register.scss","../sass/pages/_apikey-detail.scss","../sass/pages/_apikey-list.scss","../sass/pages/_notice.scss","../sass/pages/_inquiry.scss","../sass/pages/_org-register.scss","../sass/pages/_partnership.scss","../sass/pages/_user-management.scss","../sass/pages/_commission.scss","../sass/pages/_terms-agreements.scss","../sass/pages/_service.scss","../sass/pages/_api-statistics.scss","../sass/base/_utilities.scss"],"names":[],"mappings":";AAQA;EACE;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE,aCUoB;EDTpB,aCiCmB;EDhCnB,OCXU;EDYV,kBCTM;EDUN;;;AAIF;EACE;;;AAIF;EACE;EACA;EACA,YCiEgB;;;AD7DlB;EACE;EACA;EACA;;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAIF;AAAA;AAAA;EAGE;EACA;EACA;EACA;EACA;;;AAIF;EACE;EACA;;;AAIF;EACE,aC3BiB;ED4BjB,aCxBkB;;;AD4BpB;AAAA;AAAA;EAGE;;;AAIF;EACE;EACA,OC/EU;;;ADkFZ;EACE;EACA,OCpFU;;;ACTZ;EACE;EACA;EACA;EACA;EACA;;AAIF;EACE,WDwBe;ECvBf,aDiCoB;EChCpB,ODHU;;;ACOZ;EACE,WDwBc;ECvBd,aD8BsB;EC7BtB,eDwCW;;AEzDT;EDcJ;IAMI,WDiBY;;;AEvCZ;EDgBJ;IAUI,WDYY;;;;ACRhB;EACE,WDSc;ECRd,aDeiB;ECdjB,eDyBW;;AExDT;ED4BJ;IAMI,WDEY;;;AEtCZ;ED8BJ;IAUI,WDHW;;;;ACOf;EACE,WDNc;ECOd,aDCiB;ECAjB,eDWW;;AExDT;ED0CJ;IAMI,WDbW;;;;ACiBf;EACE,WDjBc;ECkBd,aDVqB;ECWrB;;;AAGF;EACE,WDxBa;ECyBb,aDhBqB;ECiBrB,eDNW;;;ACSb;EACE,WD/Ba;ECgCb,aDtBqB;ECuBrB,eDZW;;;ACgBb;EACE,eDhBW;ECiBX,aDvBmB;;ACyBnB;EACE;;;AAIJ;EACE,ODrFa;;;ACwFf;EACE,ODxFe;;;AC2FjB;EACE,ODlFU;;;ACqFZ;EACE,ODrFU;;;ACwFZ;EACE,ODxFW;;;AC2Fb;EACE,OD3FM;;;AC+FR;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIF;EACE,aD7EoB;;;ACgFtB;EACE,aDhFmB;;;ACmFrB;EACE,aDnFqB;;;ACsFvB;EACE,aDtFiB;;;ACyFnB;EACE,aDzFsB;;;AC6FxB;EACE,WD9Ga;;;ACiHf;EACE,WDjHa;;;ACoHf;EACE,WDpHe;;;ACuHjB;EACE,WDtHa;;;ACyHf;EACE,WDzHa;;;AC6Hf;EACE,WD/Ha;ECgIb,aDxHoB;ECyHpB,aDhHkB;ECiHlB,OD5JU;;;AC+JZ;EACE,WDzIa;EC0Ib,ODjKU;;;ACoKZ;EACE,aDlJiB;ECmJjB,WD/Ia;ECgJb,YDpKQ;ECqKR;EACA,eD/GiB;;;ACmHnB;EACE,OD1La;;AC4Lb;EACE,OD5La;;;ACiMjB;EACE,SDvIW;ECwIX;EACA;EACA,YDvLQ;ECwLR;;AAEA;EACE;;;AAKJ;EACE;EACA;EACA;;;AEhNF;EACE;IACE;;EAEF;IACE;;;AAKJ;EACE;IACE;;EAEF;IACE;;;AAKJ;EACE;IACE;IACA;;EAEF;IACE;IACA;;;AAKJ;EACE;IACE;IACA;;EAEF;IACE;IACA;;;AAKJ;EACE;IACE;IACA;;EAEF;IACE;IACA;;;AAKJ;EACE;IACE;IACA;;EAEF;IACE;IACA;;;AAKJ;EACE;IACE;;EAEF;IACE;;;AAKJ;EACE;IACE;IACA;;EAEF;IACE;IACA;;;AAKJ;EACE;IACE;;EAEF;IACE;;;AAKJ;EACE;IACE;;EAEF;IACE;;EAEF;IACE;;;AAKJ;EACE;IACE;;EAEF;IACE;;;AAKJ;EACE;IACE;;EAEF;IACE;;EAEF;IACE;;EAEF;IACE;;EAEF;IACE;;EAEF;IACE;;EAEF;IACE;;EAEF;IACE;;;AAKJ;EACE;IACE;IACA;;EAEF;IACE;;EAEF;IACE;IACA;;;AAKJ;EACE;IACE;IACA;;EAEF;IACE;IACA;;;AAKJ;EACE;IACE;IACA;;EAEF;IACE;IACA;;;AAKJ;EACE;IACE;;EAEF;IACE;;EAEF;IACE;;;AAKJ;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIF;EACE;;AAEA;EACE;;;AAIJ;EACE;;AAEA;EACE;EACA,YHvQQ;;;AG2QZ;EACE;;AAEA;EACE;;;AAIJ;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;;;ACjUJ;EACE;EACA;EACA;;AAQF;EACE;EACA;EACA;;AAQF;AAAA;AAEA;EACE;EACA;EACA;EACA;;AAKF;EACE;EACA;EACA;EACA;;AAKF;EACE;EACA;EACA;EACA;;AAKF;EACE;EACA;EACA;EACA;;AAKF;AACA;EACE;EACA;EACA;EACA;;AAEF;EACE;EACA;EACA;EACA;;AAEF;EACE;EACA;EACA;EACA;;AAEF;EACE;EACA;EACA;EACA;;AAKF;AACA;EACE;EACA;EACA;;AAIF;EACE;EACA;EACA;;AAKF;AACA;EACE;EACA;EACA;;AAIF;EACE;EACA;EACA;;AAIF;EACE;EACA;EACA;;ACrHF;EAEE;EACA;EACA;EACA;EACA;EACA;EACA;EAGA;EACA;EACA;EACA;EACA;EACA;EACA;EAGA;EACA;EACA;EACA;;;AAMF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAIJ;EArDF;IAsDI;;EACA;IAAgB;;EAChB;IAAc;IAAmB;;;;AAMnC;EAAiB;;AACjB;EACE;IAAiB;;;;AAQrB;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAMF;EACE;EACA;EACA,SL5Be;EK6Bf;EACA;EACA;;AAGA;EACE;;AAEF;EACE;EACA;;AAEA;EACE;;;AAKN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAQF;EACE;EACA;;AAEA;EACE;EACA;;;AAKJ;EACE;EACA;EACA;;;AAIF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGA;EACE;;AAGF;EACE;;;AAKJ;AAAA;AAAA;EAGE;EACA;EACA;;AAEA;AAAA;AAAA;EACE;;AAGF;AAAA;AAAA;EACE;;;AAKJ;EACE;EACA;EACA;;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;;AAOJ;EACE;EACA;EACA;EACA;;AAEA;EANF;IAOI;;;;AAIJ;EACE;EACA;EACA;EACA;;AAEA;EANF;IAOI;;;;AAQJ;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EAGE;EACA;EACA;;AAGF;EACE,aLtRkB;EKuRlB;EACA;EACA;EACA;EACA;;AAEA;EACE;;;AAKN;EAEE;;AAEA;EACE;EACA;EACA;EACA;;;AAIJ;EACE;EACA;;AAEA;EAIE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAEA;EAEA;EACA;EACA;;AAEA;EACE;;AAGF;EAGE;EACA;EACA;EACA;EACA;;AAKE;EACE;;AAEF;EACE;;AAEF;EACE;;;AAUV;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAEA;EACE;;AAIJ;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE,aL3ac;EK4ad;EACA;EACA;EACA;EACA;;AAIJ;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;;AAMN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGA;EACE;;AAEA;EACE;EACA;;AAKJ;EACE,aLxfgB;EKyfhB;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE,aLlgBgB;EKmgBhB;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA,aL3hBgB;EK4hBhB;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA,aLhjBgB;EKijBhB;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAKJ;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAIF;EACE,aLjlBc;EKklBd;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAMN;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE,aLxnBc;EKynBd;EACA;EACA;EACA;;AAGF;EACE;;AAMN;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;;AAGA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,aL/pBc;EKgqBd;EACA;EACA;EACA;;AAEA;EACE;;AAKJ;EACE;EACA;EACA;EACA;EACA;;AAGE;EACE;EACA;EACA,aLtrBU;EKurBV;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAKJ;EACE;EACA;;AAOJ;EACE;;AAEA;EACE;;AAIJ;EACE;;;AAOV;EACE;EACA,OLjsBW;EKksBX;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA,YLjwBQ;EKkwBR;EACA;EACA;;AAEA;EAEE;EACA;EACA;EACA;EACA;EACA,YL7wBM;EK8wBN;;AAGF;EAAY;;AACZ;EAAW;;AAGb;EACE;;AACA;EAAY;EAAQ;;AACpB;EAAW;EAAQ;;;AAOvB;EAEE;EACA;EACA;EACA;EACA;EACA,YLnyBM;EKoyBN,SLntBc;EKotBd;EACA;EACA;EACA;EACA;;AAKA;EACE,SLhwBS;EKiwBT,YL/yBM;EKgzBN;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAEA;EACE;EACA;EACA,SLjxBK;EKkxBL,OLp0BI;EKq0BJ,WL3yBO;EK4yBP,aLlyBa;EKmyBb;EACA;;AAGF;EACE;EACA;EACA,OL5xBK;EK6xBL;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA,YL71BE;;AK+1BF;EACE;EACA;EACA,OLr2BE;EKs2BF,WL/0BK;EKg1BL;;AAEA;EACE,YLx2BJ;EKy2BI,OLv3BG;;AK43BT;EACE;;AAKN;EACE;EACA,YLt3BI;EKu3BJ;EACA;;;AAQJ;EACE;EACA;EACA;EACA;EACA;;AAGA;EACE;;AAGE;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA,eLr2Ba;EKs2Bb;EACA;EACA;EACA;EACA;EACA;EACA,SL11Ba;EK21Bb;EACA;;AAEA;EACE;;AAEA;EACE;EACA;EACA;EACA;EACA,WL75BK;EK85BL,aLl5BW;EKm5BX;;AAEA;EACE;EACA;;;AAQZ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;;AAIJ;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;;AA2EJ;EACE;IAGE;IACA;IACA;IACA;IACA;;EAEA;IACE;IACA;IACA;;EAGF;IAIE;IACA;IACA;;;AAUN;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE,aLjlCgB;EKklChB;EACA;EACA;EACA;;AAIJ;EACE;EACA;EACA;;AAGF;EACE,aLhmCkB;EKimClB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA,eLvlCa;EKwlCb;EACA;EACA;EACA;EACA;EACA;EACA,SL5kCa;;AK8kCb;EACE;EACA;EACA;;AAEA;EACE;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA,WLppCG;EKqpCH,aLzoCS;EK0oCT;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAOV;EACE;EACA;EACA;;;AAMN;EACE;IACE;;;ACptCJ;EACE;EACA;;AAEA;EACE;EACA;EACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AJZE;EIMJ;IASI;IACA;IACA;IACA;;;;AAKJ;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAEA;EACE,aNhBgB;EMiBhB;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;EACA;;AAIJ;EACE,aNrCkB;EMsClB;EACA;EACA;EACA;EACA;;;AAKJ;EACE;EACA;EACA;EACA;;AJ3EE;EIuEJ;IAOI;IACA;;;AAIA;EACE;EACA;EACA;EACA,aN/DgB;EMgEhB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;EACA;;AJ7GJ;EImFA;IA8BI;;;AAKN;EACE,aN/FkB;EMgGlB;EACA;EACA;EACA;EACA;;AJ5HA;EIsHF;IASI;;;;AJjIF;EIyIA;IACE;;EAIJ;IAEE;IACA;IACA;IACA;IACA;;EAGF;IAEE;IACA;IACA;;EAEA;IAEE;IACA;;EAGF;IACE;IACA;;EAEA;IAEE;IACA;;EAGF;IACE;IACA;;EAIJ;IAEE;IACA;IACA;;EAIJ;IAEE;IACA;IACA;IACA;;EAEA;IACE;IACA;;EAEA;IAEE;IACA;IACA;IACA;IACA;IACA;;EAIJ;IAEE;IACA;IACA;IACA;;;AJtNF;EI6NF;IACE;IACA;;EAGA;IACE;;EAEA;IACE;;EAGF;IACE;;EAGF;IACE;;EAIJ;IACE;;EAEA;IACE;IACA;;EAGF;IACE;;;AC7PR;EACE;EACA,KPyDW;;AOtDX;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAIF;EACE;;AAGF;EACE;;AAGF;EACE;;AAIF;EACE;;AAGF;EACE,KPKS;;AOFX;EACE,KPES;;AOCX;EACE,KPDS;;AOIX;EACE,KPJS;;AExDT;EKiEA;IACE;;EAGF;IACE;;;ALxEF;EK6EA;IACE;;EAGF;IACE;;;;AAMN;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIF;EACE;EACA;EACA,KP3EW;;AEvDT;EK+HJ;IAMI;;;ALvIA;EKiIJ;IAUI;;;;AAKJ;EACE;EACA;EACA,KP1FW;;AEzDT;EKgJJ;IAMI;;;;AAKJ;EACE;EACA;EACA,KPnGY;EOoGZ;;AL/JE;EK2JJ;IAOI;;;;AAKJ;EACE;EACA;EACA,KPhHW;;AE1DT;EKuKJ;IAMI;;;;AAKJ;EACE;EACA;EACA,KP1HY;EO2HZ;;ALpLE;EKgLJ;IAOI;;;;AAKJ;EACE;EACA;EACA,KPnIY;;AE5DV;EK4LJ;IAMI;IACA,KP1IU;;;AO6IZ;EACE;EACA;EACA,KPhJU;;AEzDV;EKsMF;IAMI;;;AL9MF;EKwMF;IAUI;IACA;;;;AAMN;EACE;EACA;EACA,KPnKW;EOoKX;;AL3NE;EKuNJ;IAOI;IACA,KPvKS;;;;AO4Kb;EACE;EACA;EACA,KPhLW;;AEzDT;EKsOJ;IAMI;;;;AAKJ;EACE;EACA;EACA,KP1LW;EO2LX;;ALnPE;EK+OJ;IAOI;;;ALxPA;EKiPJ;IAWI;;;;AAKJ;EACE;EACA;EACA,KPxMY;EOyMZ;;ALnQE;EK+PJ;IAOI;IACA,KP/MS;;;AOkNX;EACE;;AAEA;EACE;;AL9QF;EK0QF;IAQI;;;;AAMN;EACE;EACA;EACA,KPnOW;;AExDT;EKwRJ;IAMI;;;;AClSJ;EACE,WR4EoB;EQ3EpB;EACA;;ANDE;EMFJ;IAMI;;;AAIF;EACE;EACA;;AAGF;EACE;;AAGF;EACE;;;AAKJ;EACE;;ANxBE;EMuBJ;IAII;;;AAIF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;;AAMF;EACE,YRxCM;;AQ2CR;EACE,YR3CO;;AQ8CT;EACE,ORjDI;;AQoDN;EACE,YRxDQ;EQyDR,ORtDI;;;AQ2DR;EACE;EACA,eRVY;;AE7DV;EMqEJ;IAKI,eRdU;;;AQiBZ;EACE;EACA;EACA,KR1BS;EQ2BT;EACA,YRtEO;EQuEP,ORvFW;EQwFX,eRbiB;EQcjB,WRtDW;EQuDX,aR1CmB;EQ2CnB,eR/BS;;AQkCX;EACE,WRtDY;EQuDZ,aR/Ce;EQgDf,eRrCS;EQsCT,ORvFQ;;AEPR;EM0FF;IAOI,WR7DU;;;AQoEd;EACE,WRxEW;EQyEX,ORlGQ;EQmGR,aRzDiB;EQ0DjB;EACA;;AN7GA;EMwGF;IAQI,WRhFW;;;;AQsFjB;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA,aRlDY;;;AQuDhB;EACE;EACA;;;AAIF;EACE;EACA;;;AC7IF;EP+BE;EACA;EACA,KFwBW;EEvBX;EACA,eFmCiB;EElCjB,aFUqB;EETrB,WFHe;EEIf;EACA,YF4DgB;EE3DhB;EACA;;AAEA;EACE;;AO5CJ;EAEE;EACA;;AAGA;EACE;EACA,WTyBW;;AStBb;EACE;EACA,WTqBa;;ASlBf;EACE;EACA,WTiBW;;ASdb;EACE;EACA,WTaW;;ASTb;EACE,YT9BW;ES+BX,OTjBI;ESkBJ,YTPQ;;ASSR;EACE;EACA,YTTM;;ASYR;EACE;;AAIJ;EACE;EACA;;AAEA;EACE,YTjCK;ESkCL;;AAIJ;EACE,OTzCI;ES0CJ,YT/BQ;;ASiCR;EACE;EACA,YTjCM;;ASqCV;EACE,OTnDI;ESoDJ,YTzCQ;;AS2CR;EACE;EACA,YT3CM;;AS+CV;EACE,YTtEW;ESuEX,OT9DI;;ASgEJ;EACE;EACA;;AAIJ;EACE,YTjFY;ESkFZ,OTxEI;;AS0EJ;EACE;EACA;;AAIJ;EACE;EACA,OThGW;ESiGX;;AAEA;EACE;EACA,cTrGS;;ASyGb;EACE;EACA,OThGQ;ESiGR;;AAEA;EACE,cT/GS;ESgHT,OThHS;ESiHT;;AAKJ;EAEE;EACA;EACA;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EPrDJ;EACA;EACA;EACA;EOoDI;EACA;EACA;EACA;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA,eT1EmB;;AS4EnB;EACE;EACA;;AAGF;EACE;EACA;;AAKJ;EACE;EACA;;AAIF;EACE;EACA;;AAEA;EACE;;AAEA;EACE,wBT1GW;ES2GX,2BT3GW;;AS8Gb;EACE,yBT/GW;ESgHX,4BThHW;;;ASuHnB;EACE;EACA,QTnIW;ESoIX,OTpIW;ESqIX;EACA;EAEA;EACA,eT3HqB;ES4HrB,OT1LM;ES2LN,WTlKa;ESmKb;EACA,YTjLU;ESkLV,YTvGgB;ESwGhB,STjHc;EExEd;EACA;EACA;;AO0LA;EACE;EACA,YTvLQ;;ASyLR;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA,YTpNQ;ESqNR,OTlNI;ESmNJ;EACA,eT3Je;ES4Jf,WThMW;ESiMX;EACA;EACA,YTjIc;ESkId;;;AAMF;EP5MA;EACA;EACA,KFwBW;EEvBX;EACA,eFmCiB;EElCjB,aFUqB;EETrB,WFHe;EEIf;EACA,YF4DgB;EE3DhB;EACA;;AAEA;EACE;;AO+LF;EAEE,YTjOI;ESkOJ,OThPW;ESiPX;EACA,WT7MW;ES8MX,YTzNQ;;AS2NR;EACE;EACA,YT5NM;;ASgOV;EP1NA;EACA;EACA,KFwBW;EEvBX;EACA,eFmCiB;EElCjB,aFUqB;EETrB,WFHe;EEIf;EACA,YF4DgB;EE3DhB;EACA;;AAEA;EACE;;AO6MF;EAEE;EACA,OThPI;ESiPJ;EACA;EACA,WT5NW;;AS8NX;EACE;EACA;;;AAUN;AAAA;AAAA;AAAA;AAAA;AAAA;EAME;EACA,eT/MiB;ESgNjB,WTnPe;ESoPf,aTzOmB;ES0OnB;EACA,YTrLgB;ESsLhB;EACA;EACA;EACA,KTnOW;ESoOX;EACA;;AAEA;EAnBF;AAAA;AAAA;AAAA;AAAA;AAAA;IAoBI;IACA,WTjQW;;;ASoQb;AAAA;AAAA;AAAA;AAAA;AAAA;EACE;;;AAKJ;AAAA;EAGE,OTlSM;;ASoSN;AAAA;EACE;EACA,YT3RQ;;AS8RV;AAAA;EACE;;;AAKJ;EACE,YThTM;ESiTN,OTpTU;ESqTV;;AAEA;EACE,YTpTM;ESqTN,cTpUW;ESqUX,OTrUW;;ASwUb;EACE;;;AAKJ;EACE,YT/Ua;ESgVb,OTlUM;;ASoUN;EACE;EACA,YT3TQ;ES4TR,YTpVa;;ASuVf;EACE;;;AAKJ;EACE,YT3Vc;ES4Vd,OTlVM;;ASoVN;EACE;EACA,YT3UQ;ES4UR;;AAGF;EACE;;;AAMF;EACE,YTlWI;ESmWJ,OTtWQ;ESuWR;;AAEA;EACE,YTtWI;ESuWJ,cTtXS;ESuXT,OTvXS;;AS2Xb;EAEE,OT/WI;;ASiXJ;EACE;EACA,YTxWM;;AS4WV;EACE,YTlYY;ESmYZ,OTzXI;;AS2XJ;EACE;EACA,YTlXM;;;ASwXZ;EACE;EACA;EACA,OTpZa;ESqZb;EACA,eThViB;ESiVjB,WTrXa;ESsXb,aTzWmB;ES0WnB;EACA,YTrTgB;ESsThB;EACA;EACA,KTnWW;;ASqWX;EACE;EACA,cTjaW;;ASoab;EACE;;;AAIJ;EACE;EAEA,OT9ZM;ES+ZN;EACA,eTvWiB;ESwWjB,WT5Ya;ES6Yb,aTjYmB;ESkYnB;EACA,YT7UgB;ES8UhB;EACA;EACA,KT3XW;;AS6XX;EACE;EACA,YThaQ;;ASmaV;EACE;;;AAIJ;EACE;EACA,YTrbM;ESsbN,OTxbU;ESybV;EACA,eT/XiB;ESgYjB,WTpaa;ESqab,aTzZmB;ES0ZnB;EACA,YTrWgB;ESsWhB;EACA;EACA,KTnZW;;ASqZX;EACE,cThdW;ESidX,OTjdW;;ASodb;EACE;;;AAQJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,eT9ZiB;ES+ZjB,WTjca;ESkcb,aTvbiB;ESwbjB,OT3dM;ES4dN;EACA,YTtYgB;ESuYhB;;AAGA;EACE;;AAEA;EACE;EACA;EACA,YT5dM;;AS+dR;EACE;;AAKJ;EACE;;AAEA;EACE,YTjgBW;ESkgBX;EACA,YT3eM;;AS8eR;EACE;;AAGF;EACE,YT3fQ;ES4fR;EACA;EACA;;AAIJ;EAtDF;IAuDI;IACA;IACA;IACA,WTnfa;;;;AS2fjB;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA,OTzhBM;ES0hBN;EACA,eTleiB;ESmejB;EACA,aT3fqB;ES4frB;EACA;;AAEA;EACE;;AP1iBA;EOyhBJ;IAqBI;IACA;;;;ACnjBJ;EACE;EACA;EACA;EACA;EACA,eVoEmB;EUnEnB,WV0Ba;EUzBb,aVuCqB;EUtCrB;EACA,YV0FgB;;AUvFhB;EACE;EACA,OVZY;;AUed;EACE;EACA,OVhBW;;AUmBb;EACE;EACA,OV1BW;;AU6Bb;EACE;EACA,OVnBQ;;AUsBV;EACE;EACA;;AAGF;EACE;EACA,OVrCY;;AUwCd;EACE;EACA,OVzCW;;;AU8Cf;EACE;EACA;EACA;EACA;EACA,eVmBmB;EUlBnB,WVvBa;EUwBb,aVVqB;EUWrB,eVCW;EUAX,YVyCgB;;AUvChB;EACE;EACA,OV5DY;;AU+Dd;EACE;EACA,OVhEW;;AUmEb;EACE;EACA,OV1EW;;AU6Eb;EACE;EACA,OVnEQ;;AUsEV;EACE;EACA;;;AAKJ;EACE;EACA;EACA,eVtBiB;;;AU0BnB;EACE;EACA,WV/Da;EUgEb,eV5BiB;;;AUgCnB;EACE;EACA;;AAEA;EACE,OVxGY;EUyGZ,cVzGY;;AU4Gd;EACE,OV5GW;EU6GX,cV7GW;;AUgHb;EACE,OVtHW;EUuHX,cVvHW;;AU0Hb;EACE,OV/GQ;EUgHR,cVhHQ;;;AUsHV;EACE,cVzES;EU0ET;;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA,YV5Ic;EU6Id,OVnIM;EUoIN,eVtEqB;EUuErB;EACA,aVnGiB;EUoGjB;;AAEA;EACE,YVxJW;;AU2Jb;EACE,YVvJW;;AU0Jb;EACE,YV7JY;EU8JZ,OVtJQ;;AUyJV;EACE,YVjKY;;;AWFhB;ETkDE,YFtCM;EEuCN,eFqBkB;EEpBlB,SFsDa;EErDb,YF8CgB;;AE5ChB;EAEE,YFjCQ;;AWxBZ;EAEE;EACA;;;AAIF;ET2CE,YFtCM;EEuCN,eFqBkB;EEpBlB,SFsDa;EErDb,YF8CgB;;AE5ChB;EAEE,YFjCQ;;AWjBZ;EAEE;EACA;;AAEA;EACE,cXfW;;AWoBb;EACE,cXlBY;;AWsBd;EACE,cXrBW;;AWyBb;EACE;EACA;EACA;EACA;EACA,YXhCY;EWiCZ,OXzBQ;EW0BR,eXqCgB;EWpChB,WXJW;EWKX,aXUe;EWTf;;AAEA;EACE,YXtCS;EWuCT,OX9BE;;AWiCJ;EACE,YX1CU;EW2CV,OXnCE;;AWwCN;EACE;EACA;EACA;ETrCF;EACA;EACA;ESqCE;EACA,YX5CO;EW6CP,OX7DW;EW8DX,eXWe;EWVf,YXsCc;;AWnChB;EACE;EAEA,OXvDI;;AW2DN;EACE,WXpCW;EWqCX,aX1Be;EW2Bf,eXhBS;EWiBT,OXlEQ;;AWqEV;EACE,OXrEQ;EWsER,aX5BiB;EW6BjB,eXtBS;EWuBT,WXjDW;;AWqDb;EACE;EACA;EACA,KXhCS;EWiCT,eX/BS;EWgCT;;AAEA;EACE;EACA,YXlFI;EWmFJ,OXtFM;EWuFN,eXzBc;EW0Bd,WXlES;EWmET,aXtDe;;AW2DnB;EACE;EACA;EACA,KXnDS;EWoDT,OX9GW;EW+GX,aX/DmB;EWgEnB,WX7EW;EW8EX,YXZc;;AWcd;EACE,KXzDO;EW0DP,OXpHW;;;AW0HjB;ETvEE,YFtCM;EEuCN,eFqBkB;EEpBlB,SFsDa;EErDb,YF8CgB;;AE5ChB;EAEE,YFjCQ;;AWiGZ;EAEE;;AAEA;EACE;EACA;EACA;ET9GF;EACA;EACA;ES8GE,WX7FW;EW+FX,OXxHI;EWyHJ,eX9De;;AWiEjB;EACE,WXrGW;EWsGX,aX3Fe;EW4Ff,eXjFS;EWkFT,OXnIQ;;AWsIV;EACE,OXtIQ;EWuIR,aX7FiB;EW8FjB,eXvFS;EWwFT,WXlHW;;AWsHb;EACE;EACA;EACA;EACA,aXhGS;EWiGT;;AAEA;EACE,WX1HS;EW2HT,aXhHkB;EWiHlB,OXnKS;;AWsKX;EACE,WXrIS;EWsIT,OX5JM;EW6JN,aX1He;;;AWgIrB;ET3HE,YFtCM;EEuCN,eFqBkB;EEpBlB,SFsDa;EErDb,YF8CgB;;AE5ChB;EAEE,YFjCQ;;AWqJZ;EAEE;EACA;EACA,KXtHW;;AWwHX;EACE;EACA;EACA;ETpKF;EACA;EACA;ESqKE,OX7KI;EW8KJ,eXpHe;EWqHf,WXtJW;;AWyJb;EACE;;AAEA;EACE,WX/JS;EWgKT,aXrJiB;EWsJjB,eX3IO;EW4IP,OX5LM;;AW+LR;EACE,OX/LM;EWgMN,aXtJe;EWuJf,WX1KS;;;AWgLf;ET/JE,YFtCM;EEuCN,eFqBkB;EEpBlB,SFsDa;EErDb,YF8CgB;;AE5ChB;EAEE,YFjCQ;;AWyLZ;EAEE;;AAEA;EACE;EACA;EACA,KX9JS;EW+JT,MX/JS;EWgKT;EACA,OX7NW;EW8NX;EACA,aX9Ke;;AWiLjB;EACE;EACA;EACA,WXjMa;EWkMb,aX/KgB;EWgLhB,OX5NQ;EW6NR,eX3KS;EW4KT;;AAGF;EACE;EACA;EACA,KXnLS;;AWqLT;EACE;EACA;EACA,eXxKiB;EWyKjB;;AAIA;EACE,aX1Me;EW2Mf,OXhPI;EWiPJ;;AAGF;EACE,WX7NO;EW8NP,OXrPI;;;AW4PZ;ETpNE,YFtCM;EEuCN,eFqBkB;EEpBlB,SFsDa;EErDb,YF8CgB;;AE5ChB;EAEE,YFjCQ;;AW8OZ;EAEE;EACA;;AAEA;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EAEA,OX1QE;EW2QF,eX9Me;EW+Mf,WXxPS;EWyPT,aX1Oa;EW2Ob;;AAIJ;EACE,gBXpOS;EWqOT;EACA,eXtOS;;AWwOT;EACE,WX/PS;EWgQT,aXtPa;EWuPb,eX7OO;EW8OP,OX9RM;;AWiSR;EACE;EACA;EACA;EACA,KXtPO;;AWwPP;EACE,WX7QO;EW8QP,OXxSI;;AW2SN;EACE,WX9QQ;EW+QR,aXvQgB;;AW0QlB;EACE,WXzRS;EW0RT,OXlTI;;AWuTV;EACE;EACA,eXxQS;;AW0QT;EACE;EACA,OX7TM;EW8TN,WXvSS;EWwST;EACA;EACA,KXlRO;;AWoRP;EACE;EACA,OX5UO;EW6UP,aXjSW;;AWoSb;EACE;;AAEA;EACE;EACA,OX7UG;;AWmVX;EACE;;;AChVJ;EACE,WZ6DoB;EY5DpB;EACA;;AVhBE;EUaJ;IAMI;;;;AAOJ;EACE,kBAxBgB;EAyBhB,eZwCiB;EYvCjB;;AV7BE;EU0BJ;IAMI,SZyBS;;;;AYlBb;EACE,kBArCgB;EAsChB,eZ2BiB;EY1BjB;EACA;;AV3CE;EUuCJ;IASI;IACA;IACA;IACA;IACA;;;AAGF;EACE,aZ9BkB;EY+BlB;EACA,aZbe;EYcf,OZpDQ;EYqDR;;AV5DA;EUuDF;IAQI;IACA;IACA;IACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA,eZjBS;;AYmBT;EACE;EACA,aZlCa;EYmCb;EACA;;AAGF;EACE;;AVrFF;EUsEF;IAmBI;IACA;IACA;IACA;;EAEA;IACE;IACA;IACA;;;AAKN;EACE;EACA,aZ9DkB;EY+DlB;;AVzGA;EUsGF;IAMI;IACA;IACA;IACA;;;;AAQN;EACE;EACA;EACA;EACA;EACA;EACA,aZnGoB;EYoGpB;EACA,aZlFiB;EYmFjB,OZtHM;EYuHN;;AAEA;EACE;;AAGF;EAEE,kBArIkB;;AAwIpB;EACE;;AAGF;EAEE;;AAKA;EACE;EACA;;AAGF;EAEE;EACA;;AAGF;EAEE;EACA;;;AAQN;EACE;EACA;EACA,KZzHW;EY0HX;EACA,eZ7GiB;EY8GjB,WZpJa;EYqJb,aZxIqB;;AY0IrB;EACE;EACA;EACA,eZjHmB;EYkHnB;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;;AAOJ;EACE;EACA;;AAEA;EACE;EACA;;AVjNA;EU+MF;IAKI;IACA;;;AAIJ;EACE;EACA;;;AAIJ;EACE;EACA;EACA;EACA,eZ/JiB;EYgKjB;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA,eZ1KiB;EY2KjB;EACA;;AAEA;EACE;;;AAOJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA,eZ3MS;EY4MT;;AAGF;EACE,aZ/OkB;EYgPlB,WZvOW;EYwOX,aZ9Ne;EY+Nf,OZrQQ;EYsQR;;AAGF;EACE,aZvPkB;EYwPlB,WZlPa;EYmPb,OA7QoB;EA8QpB;;;AAOJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA,eZ9NiB;EY+NjB,aZzQoB;EY0QpB;EACA,aZxPiB;EYyPjB;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE,kBA/SkB;EAgTlB,OZ1SI;;AY4SJ;EACE;;AAIJ;EACE;EACA;;AAEA;EACE;;AAIJ;EACE,kBAhUoB;EAiUpB,OZ5TI;;AY8TJ;EACE;;AVzUF;EU2RJ;IAmDI;IACA;IACA;IACA,WZjTa;;;;AYwTjB;EACE;EACA;EACA,KZrSW;EYsSX;EACA,YZnVM;EYoVN;EACA,eZ7RiB;EY8RjB,WZjUa;EYkUb,OZ1VU;EY2VV;EACA,YZlQgB;;AYoQhB;EACE;EACA;;AAGF;EACE,YZ/WW;EYgXX,cZhXW;EYiXX,OZnWI;;AYsWN;EAxBF;IAyBI;IACA;;;;AAOJ;EACE,YZhXM;EYiXN,eZvTiB;EYwTjB,SZ/TY;EYgUZ,eZpUW;EYqUX,YZzWU;;AY2WV;EAPF;IAQI,SZvUS;;;AY0UX;EACE,WZlWW;EYmWX,aZzVe;EY0Vf,OZhYQ;EYiYR,eZ/US;EYgVT,gBZjVS;;;AYwVb;EACE;EACA;EACA,KZ1VW;;AY4VX;EALF;IAMI;;;;AAIJ;EACE;EACA;EACA,KZtWW;;AYwWX;EACE;;AAGF;EACE,WZrYW;EYsYX,aZzXmB;EY0XnB,OZ9ZQ;;AYiaV;EACE,WZ1Ya;EY2Yb,OZpaQ;EYqaR;;;AAOJ;EACE;EACA;EACA,KZ/XW;;;AYkYb;EACE;EACA;EACA,kBZhbS;EYibT,OZhce;EYicf,eZ5XiB;EY6XjB,WZhaa;EYiab,aZraiB;;;AY2anB;EACE;EACA;EACA,kBZ9bQ;EY+bR;EACA,eZzYiB;EY0YjB,aZjbiB;EYkbjB,WZ9aa;EY+ab,OZjde;;;AYudjB;EACE;EACA;EACA,KZ/ZW;;AYiaX;EALF;IAMI;IACA;;;;AAOJ;EACE;EACA;EACA,KZ9aW;;;AYibb;EACE;EACA;EACA,KZnbW;EYobX;EACA,YZneM;EYoeN;;AAEA;EACE,cZrfW;EYsfX,YZ9dQ;;AYieV;EAbF;IAcI,SZ9bS;IY+bT,KZhcS;;;AYmcX;EACE;EACA;EACA,eZ3be;EY4bf,WZ/dW;EYgeX,aZlde;EYmdf,aZree;EYsef;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE,kBZ5gBQ;EY6gBR,OZnhBM;;AYuhBV;EACE;EACA,WZhgBa;EYigBb,aZtfiB;EYufjB,OZ3h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wNjB;EACE;EACA,OdjSO;;AcoST;EACE;EACA,OdxSQ;;Ac2SV;EACE;EACA,Od5SQ;;Ac+SV;EACE;EACA,OdrTO;;AcyTX;EACE,WdnRS;EcoRT,ad3QiB;Ec4QjB,edjQO;EckQP,OdlTM;;AcqTR;EACE,OdrTM;EcsTN,Wd9RW;;;AelCjB;EACE;EACA;EACA,KfsDW;EerDX;EACA,Wf4Ba;;Ae1Bb;EACE;EACA;EACA,Kf+CS;Ee9CT,OfDQ;;AeGR;EACE,OfJM;EeKN,YfoFY;;AelFZ;EACE,OfpBO;;AewBX;EACE,OfdM;EeeN,afqBe;;AelBjB;EACE;EACA,OflBO;EemBP,af2BO;;;AerBb;EACE;EACA,KfkBW;EejBX;EACA,efoBW;;AelBX;EACE;;AAGF;EACE;EACA;EACA,KfQS;EePT;EACA,OfzCQ;Ee0CR,afPiB;EeQjB;EACA;EACA,Yf4Cc;;Ae1Cd;EACE,OfjDM;;AeoDR;EACE,OfhES;EeiET,qBfjES;;AeoEX;EACE;EACA,YfvDI;EewDJ,Of3DM;Ee4DN,efGe;EeFf;EACA,af1BiB;;;AegCvB;EACE;EACA,KfvBW;EewBX,SfzBW;Ee0BX,YfrEQ;EesER,efbiB;;AeejB;EACE;EACA,Of7EQ;Ee8ER,af3CiB;Ee4CjB,efpBe;EeqBf,YfSc;;AePd;EACE,OfpFM;EeqFN;;AAGF;EACE,YftFE;EeuFF,OfrGS;EesGT,Yf9EM;;;AeqFV;EACE,efhDS;;AekDT;EACE,Wf/ES;EegFT,aflEiB;EemEjB;EACA,OfxGM;EeyGN;EACA;;AAIJ;EACE;;AAEA;EACE,efpEO;;AeuET;EACE;EACA;EACA,KfxEO;EeyEP;EACA,Of3HM;Ee4HN,efhEa;EeiEb,YfnCY;;AeqCZ;EACE;EACA;EbxHN;EACA;EACA;EawHM,WfzGO;Ee0GP,OfnII;;AesIN;EACE,YfpIE;EeqIF,OfpJO;;AesJP;EACE,OfvJK;;Ae2JT;EACE,Yf5IG;Ee6IH,Of7JO;Ee8JP,af/Ga;;AeiHb;EACE,OfjKK;;AeuKX;EACE,afzGQ;Ee0GR,Yf/GO;EegHP;;AAEA;EACE,Wf1IO;Ee2IP;;;A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AoBqff;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,YpBrcc;;AoBucd;EACE,OpB7iBS;;AoBgjBX;EACE,apBhgBa;EoBigBb;;;AAMN;EACE;EACA;EACA;EACA,epB/fW;EoBggBX;EACA,KpBjgBW;;AoBmgBX;EARF;IASI;IACA;IACA,KpBvgBS;;;AoB0gBX;EACE,WpBniBa;EoBoiBb,OpB5jBQ;EoB6jBR;;AAEA;EACE,OpB5kBS;EoB6kBT,apB7hBiB;EoB8hBjB,WpBxiBS;;AoB2iBX;EAXF;IAYI;IACA;;;AAIJ;EACE;EACA,KpB9hBS;;AoBgiBT;EAJF;IAKI;;;AAGF;EACE;EACA;EACA;EACA;EACA,epB7hBa;EoB8hBb,WpBlkBS;EoBmkBT,YpBjgBY;;AoBmgBZ;EATF;IAUI;;;AAGF;EACE;EACA,cpB9mBO;EoB+mBP;;AAGF;EACE,OpBtmBK;;;AoB0oBb;EAEE;IACE;IACA;IACA;IACA;IACA,YpBhpBI;IoBipBJ;IACA;IACA;;EAEA;IACE;IACA;IACA;IACA;IACA;IACA;IACA;;EAEA;IACE;IACA;;EAGF;IACE;;EAIJ;IACE;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;EAEA;IACE;IACA;IACA;;EAMN;IACE;IACA;IACA;IACA;IACA;;EAEA;IACE;IACA;IACA;IACA,OpBvsBE;IoBwsBF;IACA;IACA;;EAEA;IACE;IACA,apB/qBW;IoBgrBX;;EAIJ;IACE;;EAKJ;IACE;IACA;;EAGA;IACE;;EAIF;IACE;IACA;;EAIF;IACE;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;EAEA;IACE;;EAIF;IACE;;EAIF;IACE;IACA;IACA;IACA;IACA,apBtuBc;IoBuuBd;;EAEA;IACE;;EAGF;IACE;IACA;IACA;IACA;IACA;;EAEA;IACE;;EAGF;IACE;IACA;IACA;IACA;;EAEA;IACE;IACA;;EAOR;IACE;IACA;IACA;IACA;IACA,apB5wBc;IoB6wBd;IACA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B;EACA;;AAIF;AAAA;AAAA;AAAA;EACE,OAvCiB;EAwCjB,QAxCiB;EAyCjB;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA,axB1BkB;EwB2BlB,WApDmB;EAqDnB,axBZkB;EwBalB,OApDuB;EAqDvB;EACA;EACA;EACA;EACA,YxBsCc;;AwBpCd;EACE,OxBnES;;AwBuEX;EACE,axBvBa;EwBwBb,OAnEmB;EAoEnB;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAKJ;EACE;EACA;EACA;EACA,YxBpCW;;;AyB1Db;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA,WzBgEoB;EyB/DpB;EACA;EACA;;AAEA;EATF;IAUI;IACA;;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA,YzBgEY;;AyB7Dd;EACE,MzB1CS;;AyB+Cb;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EAdF;IAeI;IACA;;EAEA;IACE;IACA;;;AAMN;EACE,azB5CkB;EyB6ClB;EACA,azB9BkB;EyB+BlB;EACA;EACA;EACA;EACA,YzBqBc;;AyBnBd;EACE,OzBpFS;;AyBwFX;EACE,azBxCa;EyByCb;;AAEA;EACE;EACA;;AAIJ;EAzBF;IA0BI;;;;AAMN;EACE;EACA;EACA;;AAEA;EALF;IAMI;;;AAGF;EACE;EACA;EACA;EACA,WzBvCkB;EyBwClB;EACA;EACA;EACA;;AAEA;EAVF;IAWI;IACA;;;AAIJ;EACE;EACA;EACA;;AAEA;EALF;IAMI;;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAVF;IAWI;IACA;;;AAIJ;EACE,azB/HgB;EyBgIhB;EACA,azBjHgB;EyBkHhB;EACA;EACA;EACA;EACA,YzB9DY;;AyBgEZ;EACE,OzBvKO;;AyB0KT;EAdF;IAeI;;;AAKJ;EACE,azBhIa;EyBiIb;EACA;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,YzBlGU;;AyBqGZ;EACE;;;AC9MR;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;;ACvCJ;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;AzBNE;EyBCJ;IAQI;;;AzBXA;EyBGJ;IAYI;;;;AAIJ;EACE;EACA;EACA;;AzBlBE;EyBeJ;IAMI;IACA;;;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAIF;EACE;;AAGF;EACE;;AAGF;EACE;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AzBnEE;EyB2DJ;IAWI;;;AzBxEA;EyB6DJ;IAeI;IACA;IACA;IACA;;;AzBjFA;EyB+DJ;IAuBI;IACA;IACA;IACA;IACA;;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AzBlGE;EyB4FJ;IASI;IACA;;;AzBxGA;EyB8FJ;IAeI;IACA;IACA;IACA;IACA;;;;AAKF;EACE,a3B7FkB;E2B8FlB;EACA;EACA;EACA;EACA;;AzB1HA;EyBoHF;IASI;;;AzB/HF;EyBsHF;IAcI;IACA;IACA;IACA;IACA;;;AAIJ;EACE,a3BnHkB;E2BoHlB;EACA;EACA;EACA;EACA;;AzBhJA;EyB0IF;IASI;;;AzBrJF;EyB4IF;IAcI;IACA;IACA;IACA;;;;AAKN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,a3BjJoB;E2BkJpB;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;;AzBvLA;EyBgKJ;IA2BI;;;AzB7LA;EyBkKJ;IAgCI;IACA;IACA;IACA;IACA;;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AzB7ME;EyBsMJ;IAUI;IACA;;;AzBnNA;E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ACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AzBvxBA;EyBkxBF;IAQI;;;AzB5xBF;EyBoxBF;IAaI;IACA;IACA;IACA;IACA;IACA;;;AAIJ;EACE,a3BjxBkB;E2BkxBlB;EACA;EACA;EACA;EACA;;AzB9yBA;EyBwyBF;IASI;;;AzBnzBF;EyB0yBF;IAcI;IACA;IACA;;;AAIJ;EACE,a3BryBkB;E2BsyBlB;EACA;EACA;EACA;EACA;;AzBl0BA;EyB4zBF;IASI;;;AzBv0BF;EyB8zBF;IAeI;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA,a3B9zBkB;E2B+zBlB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AzBh2BA;EyB+0BF;IAoBI;IACA;IACA;;;AzBv2BF;EyBi1BF;IA2BI;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;;AzBh4BJ;EyB03BA;IAWI;IACA;;;AAIJ;EACE;;AAEA;EACE;;AAKN;EACE;EACA;EACA;;AzBp5BA;EyBi5BF;IAUI;IACA;IACA;;;AzB/5BF;EyBm5BF;IAiBI;IACA;IACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AzB17BF;EyBw6BF;IAsBI;IACA;;;AzBj8BF;EyB06BF;IA4BI;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;;AAIJ;EACE,a3Bz7BkB;E2B07BlB;EACA;EACA;EACA;EACA;;AzBx9BA;EyBk9BF;IAUI;IACA;IACA;IACA;;;AAIJ;EACE,a3B18BkB;E2B28BlB;EACA;EACA;EACA;EACA;EACA;EACA;;AzB3+BA;EyBm+BF;IAYI;IACA;IACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AzBjgCF;EyBq/BF;IAiBI;IACA;;;;AAaN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AzB3hCE;EyBohCJ;IAWI;IACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAYF;EACE;EACA;;AzBvjCA;EyBqjCF;IAMI;IACA;IACA;IACA;;;AAIJ;EACE;EACA;EACA;EACA,K3BxgCU;;AE5DV;EyBgkCF;IAOI;IACA,K3B7gCQ;;;AE7DV;EyBkkCF;IAaI;IACA;IACA;IACA;;;AAIJ;EACE;EACA;;AzBtlCA;EyBolCF;IAKI;IACA;;;AzB5lCF;EyBslCF;IAWI;IACA;IACA;;;AAIJ;EACE,a3B9kCkB;E2B+kClB;EACA;EACA;EACA;EACA;;AzB3mCA;EyBqmCF;IASI;;;AzBhnCF;EyBumCF;IAcI;IACA;IACA;IAEA;IACA;;EAEA;IACE;;;AAIJ;EACE;EACA;EACA;;AzBpoCF;EyBioCA;IAOI;IACA;IACA;IACA;;;AAKN;EACE,a3BvnCkB;E2BwnClB;EACA;EACA;EACA;EACA;;AzBppCA;EyB8oCF;IASI;;;AzBzpCF;EyBgpCF;IAcI;IACA;IACA;IACA;IACA;;;AAIJ;EACE;EACA,K3B/mCS;;AEvDT;EyBoqCF;IAKI;;;AzB3qCF;EyBsqCF;IAUI;IACA;IACA;IACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA,a3BlqCkB;E2BmqClB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AzBrsCA;EyBurCF;IAkBI;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;;AAGF;EACE;EACA;;AAGF;EACE;;AAIJ;EACE;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AzB9vCF;EyBovCF;IAcI;;;AzBpwCF;EyBsvCF;IAmBI;IACA;IACA;IACA;IACA;IACA;IACA;;EAEA;IACE;IACA;;;;AAiBR;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AzB3yCE;EyBoyCJ;IAWI;IACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;A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gB;E2Bw0DhB;EACA;EACA;EACA;EACA;EACA;;AzBv2DF;EyBg2DA;IAWI;IACA;IACA;IACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AzBn4DJ;EyBk3DA;IAsBI;IACA;;;AAIJ;EACE,a3Bp3DgB;E2Bq3DhB;EACA;EACA;EACA;EACA;EACA;EACA;;AzBn5DF;EyB24DA;IAWI;;;AzBx5DJ;EyB64DA;IAgBI;IACA;;;AAMF;EACE;;AAMN;EACE;EACA;EACA;EACA;EACA;;AzB96DA;EyBy6DF;IAQI;;;AzB/6DF;EyBu6DF;IAaI;;;AzBx7DF;EyB26DF;IAkBI;IACA;IACA;IACA;IACA;;;;AAMN;EACE;;;AAGF;EACE;IACE;IACA;;EAEF;IACE;IACA;;;AAOJ;EACE;EACA;EACA;EACA;EACA;EACA;;AzB/9DE;EyBy9DJ;IAUI;IACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AzB3/DA;EyBs/DF;IASI;;;AAIJ;EACE,a3B1+DkB;E2B2+DlB;EACA;EACA;EACA;EACA;;AzBvgEA;EyBigEF;IASI;;;AzB5gEF;EyBmgEF;IAcI;IACA;IACA;IACA;IACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,a3BxgEkB;E2BygElB;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;;AzBhjEF;EyByhEF;IA4BI;IACA;IACA;IACA;IACA;IACA;IACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;;ACvkEJ;EACE;EACA;EACA,kB5BSM;E4BRN;EACA;;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA,K5B4CW;E4B3CX;EACA;EACA,Q5BwCW;;A4BtCX;EAXF;IAYI;;;AAGF;EAfF;IAgBI;IACA;IACA;IACA;IACA;IACA,Y5BNQ;I4BOR,S5B8DY;I4B7DZ;IACA;IACA;;EAEA;IACE;;;;AA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E4B80BT;IACE,W5Bx2BW;I4By2BX,e5Bl1BO;;E4Bs1BT;IACE,W5Bh3BS;I4Bi3BT;IACA;;EAGA;IACE;IACA;;EAEA;IACE;IACA,W5B33BK;I4B43BL;;EAKJ;IACE,S5Bz2BK;I4B02BL;IACA;;EAEA;IACE,W5Bv4BK;I4Bw4BL;IACA;;EAOR;IACE;IACA;IACA;;EAEA;IACE;;EAGF;IACE;IACA,W5B35BS;I4B45BT;IACA;IACA;;EAGF;IACE;IACA,W5Bn6BS;;E4Bq6BT;IACE;IACA;;EAMN;IACE,K5Bp5BS;;E4Bw5BX;IACE,K5Bx5BS;I4By5BT;IACA;IACA;;EAIF;IACE;IACA;;EAIF;IACE;IACA;;;ACn+BJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EAPF;IAQI;;;;AAIJ;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;;;AAIJ;EACE,a7BfoB;E6BgBpB;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;;AAEA;EACE;;AAEA;EACE;;AAIJ;EACE;EACA;EACA;EACA,a7BxCkB;E6ByClB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAKN;EACE,a7BtGgB;E6BuGhB;EACA;EACA;EACA;EACA;EACA;EACA;;;AAKN;EACE;EACA;EACA;EACA,a7BtHoB;E6BuHpB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;EACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE,a7BxJkB;E6ByJlB;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAIJ;EACE;IACE;;;A3BzQA;E2B+QF;IAEE;IACA;IACA;IACA;IACA;IACA;;EAGF;IACE;IACA;;EAGF;IACE;;EAGF;IAEE;IACA;IACA;;EAGF;IAEE;IACA;IACA;IACA;IACA;;EAIA;IACE;;EAGF;IAEE;IACA;IACA;IACA;IACA;IACA;;EAEA;IAEE;IACA;;EAIJ;IAEE;IACA;IACA;IACA;IACA;;EAEA;IAEE;IACA;IACA;;EAGF;IAEE;IACA;IACA;;EAKN;IAEE;IACA;IACA;IACA;IACA;IACA;;EAGF;IAEE;;EAEA;IACE;IACA;IACA;;EAGF;IACE;IACA;;EAIJ;IACE;IACA;IACA;IACA;;;AC/XJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA,S9B2CW;;;A8BxCb;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EARF;IASI;;;;AAKJ;EACE;;;AAIF;EACE;;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA,a9B9BkB;E8B+BlB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGA;EACE;;AAIF;EACE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EArCF;IAsCI;IACA;;;;AAMN;EACE;EACA;EACA;EACA;EACA,a9B1EoB;E8B2EpB;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;;AAKJ;EACE;EACA;EACA;EACA;EACA;;AAEA;EAPF;IAQI;;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EAVF;IAWI;IACA;IACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA,a9B3IkB;E8B4IlB;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EAhBF;IAiBI;IACA;;;AAIJ;EACE;EACA;EACA;EACA,a9BhKkB;E8BiKlB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;;AAIJ;EACE;EACA;EACA,a9BtMkB;E8BuMlB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;;AAMN;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;AAAA;EAEE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EA3BF;IA4BI;IACA;;EAEA;AAAA;AAAA;IAGE;IACA;;EAGF;IACE;;;AAIJ;EACE;AAAA;AAAA;IAGE;;EAGF;IACE;;;;AAMN;EACE;;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA,a9B9TkB;E8B+TlB;EACA;EACA;EACA;;AAEA;EAXF;IAYI;IACA;;;;AAMN;AAAA;EAEE;EACA;EACA;EACA,a9BjVoB;E8BkVpB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;AAAA;EACE;;AAGF;AAAA;EACE;;AAGF;AAAA;EACE;EACA;;AAGF;EA/BF;AAAA;IAgCI;IACA;IACA;IACA;;;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;AAAA;EAEE;EACA;EACA;EACA,a9BlYkB;E8BmYlB;EACA;EACA;EACA;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAEA;EACE;;AAGF;EACE;;AAIJ;EACE;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;EACA;;AAIJ;EA5DF;IA6DI;IACA;IACA;;EAEA;AAAA;IAEE;IACA;IACA;;;;AAMN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAKJ;EACE;IACE;;;AAIJ;EACE;IACE;IACA;;EAEF;IACE;IACA;;;AAKJ;EACE;IACE;IACA;IACA;;EAGF;IACE;;;AAOJ;EACE;EACA;EACA;EACA;EACA;;AAEA;EAPF;IAQI;;;AAGF;EACE;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EANF;IAOI;;;AAIJ;EACE,a9BthBgB;E8BuhBhB;EACA;EACA;EACA;;AAEA;EAPF;IAQI;;;AAIJ;EACE,a9BliBgB;E8BmiBhB;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EAZF;IAaI;;;;AAOR;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EAPF;IAQI;IACA;;;AAIJ;EACE,a9BxlBgB;E8BylBhB;EACA;EACA;;AAEA;EANF;IAOI;;;AAIJ;EACE,a9BnmBgB;E8BomBhB;EACA;EACA;;AAEA;EANF;IAOI;;;;AAOR;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE,a9BznBkB;E8B0nBlB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;EA5BF;IA6BI;IACA;;;;A5BhrBF;E4ByrBF;IACE;IACA;;EAGF;IACE;;EAGF;IACE;;EAKA;IACE;IACA;IACA;IACA;;EAEA;IACE;;EAGF;IACE;;EAMN;IACE;IACA;;EAEA;IACE;IACA;IACA;IACA;;EAIF;IACE;IACA;IACA;IACA;;EAIF;IACE;IACA;IACA;IACA;IACA;IACA;;EAEA;IACE;;EAKJ;IACE;IACA;IACA;IACA;IACA;;EAKJ;IACE;IACA;IACA;IACA;;EAEA;IACE;IACA;IACA;;EAGF;AAAA;IAEE;IACA;IACA;;EAGF;IACE;IACA;IACA;IACA;IACA;IACA;;EAEA;IACE;;EAMN;IACE;;EAEA;IACE;IACA;;EAIF;IACE;IACA;;EAKJ;AAAA;IAEE;IACA;IACA;IACA;IACA;IACA;IACA;;EAIF;IACE;IACA;IACA;IACA;;EAEA;AAAA;IAEE;IACA;IACA;IACA;IACA;IACA;;EAIF;IACE;IACA;;EAIF;IACE;IACA;;EAKJ;IACE;IACA;;EAEA;IACE;;EAEA;IACE;IACA;;EAGF;IACE;IACA;;EAGF;IACE;;EAMN;IACE;;EAEA;IACE;IACA;IACA;;EAEA;IACE;;EAGF;IACE;;EAMN;IACE;IACA;;EAEA;IACE;;;ACp5BN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EAPF;IAQI;;;;AAIJ;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;;;AAIJ;EACE,a/BhBoB;E+BiBpB;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA,a/B1CoB;E+B2CpB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;;;AAIJ;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE,a/B1FkB;E+B2FlB;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;EACA;;;A7BrIA;E6B2IF;IAEE;IACA;IACA;IACA;IACA;IACA;;EAGF;IACE;IACA;;EAGF;IACE;;EAGF;IAEE;IACA;IA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iC4lCd;EACE,SjC7lCY;EiC8lCZ;EACA,OjCnpCU;;AiCqpCV;EACE;EACA;EACA;EACA;EACA,kBjCtqCW;EiCuqCX,ejC3lCmB;EiC4lCnB;;AAGF;EACE,WjCxoCa;EiCyoCb,OjCjqCQ;;;AiCqqCZ;EACE;IACE;;;AAKJ;EACE;EACA;EACA;;AAEA;EACE,YjChrCI;EiCirCJ,ejCvnCe;EiCwnCf,SjCjoCU;EiCkoCV,YjCzqCQ;EiC0qCR,ejCpoCS;;AiCuoCX;EACE,ejCxoCS;;AiC0oCT;EACE;;AAIJ;EACE;EACA,WjC5qCW;EiC6qCX,ajChqCmB;EiCiqCnB,OjCtsCQ;EiCusCR,ejCvpCS;;AiCypCT;EACE,OjCjtCU;EiCktCV;;AAIJ;AAAA;AAAA;EAGE;EACA;EACA;EACA,ejCzpCe;EiC0pCf,WjC7rCa;EiC8rCb,YjC7nCc;EiC8nCd,YjCrtCI;;AiCutCJ;AAAA;AAAA;EACE;EACA,cjCvuCS;EiCwuCT;;AAGF;AAAA;AAAA;EACE,OjC/tCO;;AiCkuCT;AAAA;AAAA;EACE;;AAIJ;EACE;EACA;EACA;;AAGF;EACE;EACA,WjC1tCW;EiC2tCX,OjCjvCQ;EiCkvCR,YjCpsCS;;AiCssCT;EACE;EACA,OjCrvCO;;AiCyvCX;EACE;EACA,KjC3sCS;EiC4sCT;;AAEA;EACE;EACA;EACA;;AAEA;EACE,cjCttCK;;AiCytCP;EACE,WjCjvCS;EiCkvCT,OjC3wCI;;AiCixCV;EACE;EACA;EACA,KjCluCS;;AiCouCT;EACE;EACA;EACA;EACA,ejC7tCa;EiC8tCb;EACA;EACA;EACA,YjC1xCI;EiC2xCJ;EACA;EACA,YjCvsCY;;AiCysCZ;EACE,cjC/yCO;EiCgzCP;;AAGF;EACE;EACA;EACA;EACA;EACA,KjC7vCK;EiC8vCL,OjC7yCI;EiC8yCJ;EACA,SjC/vCK;;AiCiwCL;EACE;;AAGF;EACE,WjChyCK;EiCiyCL,ajCpxCW;;AiCwxCf;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,ejC1wCe;EiC2wCf,OjC30CI;EiC40CJ;EACA,YjCpvCU;EiCqvCV;EACA;EACA;EACA,YjCr0CI;EiCs0CJ;;AAEA;EACE;EACA;;AAGF;EACE,YjCl2CM;EiCm2CN,cjCn2CM;EiCo2CN,OjC11CF;EiC21CE;EACA,YjCj1CE;;AiCo1CJ;EACE;;AAKN;EACE;;AAGF;EACE;EACA,YjC32CE;EiC42CF;EACA,ejCpzCa;EiCqzCb,WjCz1CS;EiC01CT,ajC90Ce;EiC+0Cf,OjCn3CM;EiCo3CN;EACA,YjC3xCY;;AiC6xCZ;EACE,YjCn4CO;EiCo4CP,cjCp4CO;EiCq4CP,OjCv3CA;;AiC03CF;EACE;;AAMN;EACE;EACA,KjCt1CS;EiCu1CT,ejCt1CS;;AiCw1CT;EACE;;AAGF;EACE;EACA;EACA,KjCj2CO;EiCk2CP;EACA,YjC75CS;EiC85CT;EACA,ejCx1Ca;EiCy1Cb,OjCl5CE;EiCm5CF,WjC93CS;EiC+3CT,ajCn3Ce;EiCo3Cf;EACA,YjC/zCY;EiCg0CZ;;AAEA;EACE;;AAGF;EACE,YjC36CS;EiC46CT;EACA,YjCr5CI;;AiCw5CN;EACE;;AAKN;EACE,YjC53CS;EiC63CT;;AAEA;EACE,SjCh4CO;EiCi4CP,YjC96CI;EiC+6CJ;;AAEA;EACE,WjC95CO;EiC+5CP,OjCt7CI;;AiCw7CJ;EACE,OjCr8CK;EiCs8CL,ajCt5Ca;;AiC25CnB;EACE;EACA;;AAEA;EACE;;AAGF;EACE,YjCr8CE;;AiCw8CJ;EACE,YjCv8CM;EiCw8CN;;AAEA;EACE,YjC/8CG;;AiCo9CT;EACE;EACA;EACA;EACA;EACA,YjCj4CY;;AiCm4CZ;EACE;;AAGF;EACE,YjC99CE;;AiCi+CJ;EACE,ajCl9CW;EiCm9CX,WjC/8CO;EiCg9CP,OjCx+CI;EiCy+CJ,ajCr8Ca;;AiCw8Cf;EACE;EACA;EACA;EACA;EACA;EACA;EACA,YjCh/CA;EiCi/CA;EACA,ejC17CW;EiC27CX,OjCr/CI;EiCs/CJ;EACA,YjC95CU;;AiCg6CV;EACE;EACA;;AAGF;EACE;;;AASV;EACE;EACA;EACA,YjCx9CY;EiCy9CZ;EACA;;AAEA;EAPF;IAQI;;;;A/BxhDA;E+BmiDJ;IAEI;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;EAEA;IACE;IACA;IACA;IACA;IACA;IACA,ajCvgDa;IiCwgDb;IACA;;EAEA;IACE;IACA;IACA;;EAGF;IACE;IACA;IACA;;;;AAOR;EAEE;EACA;EACA;EACA;;A/B7kDE;E+BwkDJ;IASI;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;;AAIF;EAEE;;A/B9lDA;E+B4lDF;IAMI;IACA;IACA;IACA;IACA;;;AAMA;EACE;EACA;;A/B9mDJ;E+B4mDE;IAMI;IACA;;;AAON;EACE;EACA,OjC5nDU;;AiCgoDd;EACE,ejCvkDS;;AE1DT;E+BgoDF;IAKI;;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAIJ;EACE,ejCvlDU;;AE3DV;E+BipDF;IAKI;;;AAGF;EACE,WjCvnDS;EiCwnDT,ajC9mDa;EiC+mDb,OjChqDS;EiCiqDT,ejCrmDO;;AExDT;E+BypDA;IAQI;IACA;;;AAIJ;EACE,WjCvoDW;EiCwoDX,OjChqDM;EiCiqDN;;A/BzqDF;E+BsqDA;IAOI;IACA;;EAEA;IACE;;EAGF;IACE;;;;AASV;EACE,YjCrrDM;EiCsrDN,ejC5nDiB;EiC6nDjB,SjCvoDW;EiCwoDX,YjC7qDU;EiC8qDV,ejCzoDW;EiC0oDX;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE,WjCnrDS;EiCorDT,OjC3sDM;EiC4sDN,ajCzqDe;EiC0qDf;;AAGF;EACE,WjCzrDW;EiC0rDX,OjCntDM;EiCotDN,ajChrDe;;AiCmrDjB;EACE;EACA;EACA,KjC1qDO;EiC2qDP;EACA;;AAEA;EACE,ajC3sDW;EiC4sDX,WjCxsDO;EiCysDP,YjC7tDE;EiC8tDF;EACA,ejCxqDW;EiCyqDX,OjC9uDS;EiC+uDT,cjCrrDK;;AiCwrDP;AAAA;EAEE;EACA,YjCxuDA;EiCyuDA;EACA,ejClrDW;EiCmrDX;EACA,YjCrpDU;;AiCupDV;AAAA;EACE,YjC7vDK;EiC8vDL,cjC9vDK;EiC+vDL,OjCjvDF;;AiCuvDF;EACE;EACA;EACA;EACA,ejC9rDa;EiC+rDb,WjCxuDO;EiCyuDP,ajC3tDe;;;AiCiuDvB;EACE,YjCnwDQ;EiCowDR,ejC3sDiB;EiC4sDjB,SjCvtDW;EiCwtDX,ejCvtDW;EiCwtDX;;AAEA;EACE,WjCpvDW;EiCqvDX,ajC1uDmB;EiC2uDnB,OjChxDQ;EiCixDR,ejChuDS;;AiCmuDX;EACE;EACA;EACA,KjCvuDS;EiCwuDT,ejCvuDS;;AiCyuDT;EACE;EACA,YjCzxDE;EiC0xDF;EACA,ejC9tDe;EiC+tDf,WjCvwDS;EiCwwDT,OjC3yDS;;AiC+yDb;EACE,WjC9wDW;EiC+wDX,OjCryDQ;EiCsyDR;;;AAIJ;EACE,YjCvyDS;EiCwyDT,ejChvDiB;EiCivDjB,SjC3vDW;EiC4vDX,ejC5vDW;EiC6vDX;;AAEA;EACE,WjCzxDW;EiC0xDX,ajC/wDmB;EiCgxDnB,OjCrzDQ;EiCszDR,ejCrwDS;;AiCwwDX;EACE,ajCxwDS;EiCywDT,OjC1zDQ;EiC2zDR,WjCpyDW;EiCqyDX,ajCjxDgB;;AiCmxDhB;EACE,ejChxDO;;;AiCqxDb;EACE;EACA;EACA,KjCvxDW;EiCwxDX;;AAGA;EAPF;IAQI;IACA;IACA;IACA;IACA;IACA;;;AAGF;AAAA;EAEE;EACA;EACA,KjCzyDS;EiC0yDT;EACA,ejC/xDe;EiCgyDf,WjCn0Da;EiCo0Db,ajCzzDiB;EiC0zDjB,YjCpwDc;EiCqwDd;EACA;EACA;;AAEA;AAAA;EACE;;AAIF;EAnBF;AAAA;IAoBI;IACA;IACA;IACA;IACA;IACA;IACA,ajCz0Da;IiC00Db;;;AAIJ;EACE;EACA,OjCn3DI;EiCo3DJ,YjCz2DQ;;AiC22DR;EACE;EACA,YjC52DM;;AiCg3DR;EAXF;IAYI;;EAEA;IACE;IACA;;;AAKN;EACE,YjCv4DI;EiCw4DJ,OjC34DQ;EiC44DR;;AAEA;EACE,YjC34DI;EiC44DJ,cjC35DS;EiC45DT,OjC55DS;;AiCg6DX;EAZF;IAaI;IACA;IACA;;EAEA;IACE;IACA;;;;AAUR;EACE;EACA;EACA;EACA;EAC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;;;;AAIJ;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EAVF;IAWI;IACA;IACA,KlC/VS;IkCgWT;;;;AAIJ;EACE;;AAEA;EACE;;AAEA;EAHF;IAII;;;;AAKN;EACE;EACA;EACA;EACA,alC/XqB;EkCgYrB;;AAEA;EAPF;IAQI;IACA;;;;AAIJ;EACE;EACA;EACA,OlChbU;EkCibV;;AAEA;EANF;IAOI;;;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA,YlC3bQ;EkC4bR;EACA,elCrYiB;EkCsYjB;EACA,OlCncU;EkCocV;;AAEA;EAbF;IAcI;IACA;IACA;;;AAGF;EACE;;;AAMF;EACE;EACA;EACA;;;AAIJ;EACE;EACA;EACA;EACA,KlC/aW;EkCgbX;EACA;EACA;EACA;EACA;EACA,elCxaiB;EkCyajB;EACA,alCjcqB;EkCkcrB,OlCpeM;EkCqeN;EACA;EACA;;AAEA;EACE;;AAGF;EAtBF;IAuBI;IACA;IACA;;;;AAKJ;EACE;EACA;EACA;EACA,KlC5cW;EkC6cX;EACA;EACA;EACA;EACA,elCpciB;EkCqcjB;EACA,alC7diB;EkC8djB,OlCjgBM;EkCkgBN;EACA;;AAEA;EACE;;AAGF;EACE;EACA;;AAGF;EAzBF;IA0BI;IACA;IACA,WlC3fa;;;;AkCggBjB;EACE;EACA,YlCxhBM;EkCyhBN;EACA,elCheiB;EkCiejB,SlC5eW;;AkC8eX;EAPF;IAQI;IACA,SlCjfS;;;;AkCsfb;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;;AAEA;EAJF;IAKI,WlC9hBW;;;AkCkiBf;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA,elCngBiB;EkCogBjB,WlC3iBa;EkC4iBb;;AAEA;EAZF;IAaI;IACA,WlCjjBS;IkCkjBT;;;;AhCjlBF;EgCimBF;IACE;IACA;;EAIF;IACE;IACA;IACA;;EAEA;IACE;IACA,alCjkBa;IkCkkBb;;EAGF;IACE;IACA;;EAKJ;IACE;IACA;IACA;IACA;IACA;IACA;IACA,YlCtnBI;;EkCwnBJ;IACE;IACA;IACA;IACA;IACA;;EAEA;IACE;IACA;IACA;IACA;;EAIA;IACE;IACA;;EAKN;IACE;;EAGF;IACE;IACA;IACA;;EAEA;IACE;IACA,OlCzpBA;;EkC6pBJ;IACE;IACA,alC5nBa;;EkC+nBf;IACE;IACA;IACA;;EAKJ;IACE;IACA;IACA;IACA;;EAIF;IACE;IACA;IACA;IACA;;EAEA;IACE;;EAIJ;IACE;IACA,alC5pBe;IkC6pBf;IACA;;EAGF;IACE;;EAIF;IACE;IACA;IACA;IACA;IACA;IACA;;EAEA;IACE;;EAMF;IACE;IACA;IACA;IACA;;EAKJ;IACE;IACA;IACA;IACA;IACA,alCnsBe;IkCosBf;IACA;IACA;;EAEA;IACE;;EAKJ;IACE;IACA;IACA;IACA,alCltBe;IkCmtBf;IACA;IACA;;EAEA;IACE;IACA;;EAGF;IACE;;EAKJ;IACE;;EAGE;IACE;IACA;;EAMN;IACE;IACA;;EAIF;IACE;;EAEA;IACE;IACA;;EAGF;IACE;IACA;IACA;IACA;IACA;IACA;IACA;;EAMF;IACE;IACA;;EAKJ;IACE;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;EAEA;AAAA;IAEE;IACA;IACA;IACA;IACA;IACA,alCjyBa;IkCkyBb;IACA;;EAEA;AAAA;IACE;IACA;IACA;;EAGF;AAAA;IACE;IACA;IACA;;EAGF;AAAA;IAGE;;;AhCj2BJ;EiCGJ;IAKI;;;;AjCRA;EiCaJ;IAKI;IACA;IACA;IACA;IACA;;;;AAIJ;EACE,anCDoB;EmCEpB;EACA,anCgBiB;EmCfjB,OnCvBU;EmCwBV;;AjC/BE;EiC0BJ;IASI;IACA,anCOiB;;;;AmCFrB;EAEE;;AjC3CE;EiCyCJ;IAMI;IACA;IACA;IACA;IACA;;;;AAKJ;EACE;EACA;EACA,KnCFW;EmCGX;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA,enCRe;;AEnEf;EiCwDJ;IAwBI;IACA;IACA;IACA;IACA;;EAEA;IACE;IACA;IACA;IACA;;EAGF;IACE;;;;AAM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AoCoRP;EACE;EACA;EACA,KpCvRK;;AoCyRL;EACE,OpC1UE;EoC2UF;EACA,WpCpTK;EoCqTL,YpCnPQ;EoCoPR;EACA;EACA,KpCjSG;;AoCmSH;EATF;IAUI,WpC5TG;;;AoC+TL;EACE,OpClWG;;AoCoWH;EACE;;AAIJ;EACE,OpC1WG;EoC2WH,YpCtQM;;AoC8QhB;EACE;EACA;;AAEA;EAJF;IAKI;;;AAGF;EACE,WpCxVW;EoCyVX,OpClXM;EoCmXN,apCvUc;;AoCyUd;EALF;IAMI,WpC9VO;;;AoCkWT;EACE,epC1UK;;AoC4UL;EACE;;AAIJ;EACE,YpChVK;EoCiVL,epCnVK;EoCoVL,apChWe;EoCiWf,OpCtYI;;AoCyYN;EACE;EACA;EACA,epChVW;EoCiVX;;AAGF;EACE,OpC5ZO;EoC6ZP;;AAEA;EACE,OpC/ZO;;AoCmaX;EACE;EACA,cpCxWK;;AoC2WP;EACE,epC/WK;;AoCkXP;EACE;EACA,cpClXK;EoCmXL;EACA,OpCraI;EoCsaJ;;AAGF;EACE,YpCvaE;EoCwaF;EACA,epClXW;EoCmXX,apC1ZW;EoC2ZX;EACA,OpC3bO;;AoC8bT;EACE,YpChbE;EoCibF;EACA,epC1XW;EoC2XX,SpCtYK;EoCuYL;EACA;;AAEA;EACE;EACA;EACA,OpC9bE;;AoCkcN;EACE;EACA;EACA;EACA;EACA,epC3YW;EoC4YX;;AAEA;EACE,YpCtcC;EoCucD;EACA;EACA,apCzaa;EoC0ab;;AAGF;EACE;EACA;;AAGF;EACE;;;AAQV;EACE;EACA;EACA,KpClbW;EoCmbX,epC/aY;;AoCibZ;EANF;IAOI;IACA;;;;AAOJ;EACE,YpC7eM;EoC8eN,epCpbiB;EoCqbjB,YpCreU;EoCseV;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA,YpCjac;EoCkad;;AAEA;EATF;IAUI;IACA;IACA;IACA,KpCldO;;;AoCqdT;EACE;;AAGF;EACE,YpCtgBI;;AoCwgBJ;EACE,OpCxhBO;;AoC4hBX;EACE;EACA;EACA,KpCpeO;EoCqeP,WpC7fS;EoC8fT,apClfe;EoCmff,OpCthBM;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IAKI;;;AAKF;EACE,WrC9RU;EqC+RV,arCtRa;EqCuRb,OrC7TM;EqC8TN,arCpRc;EqCqRd,erC7QO;;AqC+QP;EAPF;IAQI,WrCtSO;;;AqC0SX;EACE;EACA;EACA,KrCvRO;EqCwRP;EACA,WrCnTS;EqCoTT,OrC3UM;;AqC6UN;EARF;IASI,WrCxTO;IqCyTP,KrC/RK;;;AqCkSP;EACE;EACA;EACA,KrCvSK;;AqCySL;EACE,OrCpWK;;AqCuWP;EACE,OrC7VE;EqC8VF,arC1TW;;AqCiUnB;EACE;EACA,YrCnWM;EqCoWN;;AAEA;EALF;IAMI;;;AAGF;EACE;EACA;EACA,KrCjUO;;AqCmUP;EACE;EACA;EACA,KrCtUK;;AqCwUL;EACE,OrCzXE;EqC0XF;EACA,WrCnWK;EqCoWL,YrClSQ;EqCmSR;EACA;EACA,KrChVG;;AqCkVH;EATF;IAUI,WrC3WG;;;AqC8WL;EACE,OrCjZG;;AqCmZH;EACE;;AAIJ;EACE,OrCzZG;EqC0ZH,YrCrTM;;AqC6ThB;EACE;EACA;;AAEA;EAJF;IAKI;;;AAGF;EACE,WrCvYW;EqCwYX,OrCjaM;EqCkaN,arCtXc;EqCuXd;;AAEA;EANF;IAOI,WrC9YO;;;;AqCqZf;EACE,YrC3aM;EqC4aN,erClXiB;EqCmXjB,YrClaU;EqCmaV;EACA,erC/XW;EqCgYX;;AAEA;EACE;EACA;EACA;;AAEA;EALF;IAMI,SrCzYO;;;AqC4YT;EACE;EACA;EACA,KrCjZO;EqCkZP,WrCvaS;EqCwaT,arC9ZiB;EqC+ZjB,OrCpcM;EqCqcN,erCrZO;;AqCuZP;EACE,OrC9cO;;AqCkdX;EACE,WrCrbS;EqCsbT,OrC7cM;;AqC+cN;EAJF;IAKI,WrC1bO;;;AqC+bb;EACE,SrCnaU;EqCoaV,WrC/ba;EqCgcb,OrCzdQ;EqC0dR,arC9agB;EqC+ahB;;AAEA;EAPF;IAQI,SrC5aO;IqC6aP,WrCvcS;;;;AqC6cf;EACE;EACA;EACA,KrCvbW;EqCwbX,erCpbY;EqCqbZ;;AAEA;EAPF;IAQI;IACA;;;AAGF;EACE;EACA,erCvbe;EqCwbf,WrC3da;EqC4db,arCjdiB;EqCkdjB;EACA,YrC7Zc;EqC8Zd;EACA;EACA;EACA,KrC3cS;EqC4cT;EACA;;AAEA;EAdF;IAeI;IACA,WrCzeS;;;AqC4eX;EACE,YrClgBE;EqCmgBF,OrCtgBM;EqCugBN;;AAEA;EACE,YrCtgBE;EqCugBF,crCthBO;;AqC0hBX;EACE,OrC7gBE;;AqC+gBF;EACE;EACA,YrCtgBI;;AqC0gBR;EACE,YrChiBU;EqCiiBV,OrCvhBE;;AqCyhBF;EACE;EACA,YrChhBI;;AqCohBR;EACE;;;AAQN;EACE;EACA,YrCziBQ;EqC0iBR;;AAEA;EALF;IAMI;;;;AAIJ;EACE;;;AAGF;EACE,YrCvjBM;EqCwjBN,erC9fiB;EqC+fjB,YrC9iBU;EqC+iBV,SrCxgBY;EqCygBZ,erC3gBW;;AqC6gBX;EAPF;IAQI;;;AAGF;EACE,erCjhBU;;AqCmhBV;EACE;;AAGF;EACE;EACA,WrCpjBW;EqCqjBX,arCziBiB;EqC0iBjB,OrC/kBM;EqCglBN,erC/hBO;;AqCiiBP;EAPF;IAQI,WrC3jBO;;;AqC8jBT;EACE;EACA,OrC/lBQ;EqCgmBR,arC1iBK;;AqC8iBT;EACE;EACA,SrC9iBO;EqC+iBP;EACA,erCriBa;EqCsiBb,WrCzkBW;EqC0kBX,YrCzgBY;;AqC2gBZ;EARF;IASI,WrC9kBO;IqC+kBP;;;AAGF;EACE;EACA,crCvnBO;EqCwnBP;;AAGF;EACE,OrC/mBK;;AqCmnBT;EACE;EACA;EACA,SrCvkBO;EqCwkBP;EACA,erC9jBa;EqC+jBb,WrClmBW;EqCmmBX,arCzmBgB;EqC0mBhB;EACA,YrCpiBY;;AqCsiBZ;EAXF;IAYI,WrCzmBO;IqC0mBP;IACA;;;AAGF;EACE;EACA,crCnpBO;EqCopBP;;AAGF;EACE,OrC3oBK;;;AqCopBb;EACE;EACA;EACA,KrCxmBW;EqCymBX,YrCvmBW;;AqCymBX;EANF;IAOI;IACA;;;;ACtqBJ;EACE;EACA;;ApCDE;EoCDJ;IAKI;;;AAKA;EACE;;AAEA;EAHF;IAII;;;;AASN;EAHF;IAII;;;;AAKJ;EACE,WtCQc;EsCPd,atCeiB;EsCdjB,OtCxBU;EsCyBV;EACA,etC2BY;;AE5DV;EoC4BJ;IAQI;IACA,etCqBS;;;;AsChBb;EACE;EACA;EACA;EACA;EACA;EACA,etCmBiB;EsClBjB,etCSW;;AsCPX;EACE;EACA,atCXkB;EsCYlB;EACA;;AAGF;EACE;EACA,OtClDI;EsCmDJ;EACA,etCSiB;EsCRjB,WtChCW;EsCiCX,atCrBiB;EsCsBjB;EACA;EACA,KtCbS;;AsCeT;EACE;EACA,OtCxEU;EsCyEV,atC3Ba;;AsCgCjB;EACE;EACA;EACA;EACA;EACA,etCxBS;;AsC0BT;EACE;EACA,atCzCa;EsC0Cb;EACA;;AAGF;EACE;;ApC5FF;EoC0CJ;IAuDI;;EAEA;IACE,WtCnES;;EsCsEX;IACE;;EAEA;IACE,WtC1EO;;;;AsCiFf;EACE,etChDiB;EsCiDjB,etC5DW;;AsC8DX;EACE;EACA;EACA;;AAEA;EACE;EACA,ctCpEO;EsCqEP,OtCtHM;EsCuHN,WtChGS;EsCiGT;;AAEA;EACE;EACA;EACA;EACA,OtC1IO;EsC2IP,atC1FW;;AsC6Fb;EACE,YtCpFK;;;AsC2Fb;EACE;EACA;EACA;EACA;;AAEA;EACE;;ApCzJA;EoCkJJ;IAWI;IACA;IACA;;;;AAIJ;EACE;EACA;EACA,KtC/GW;EsCgHX;EACA,atC9HoB;EsC+HpB,OtClKU;EsCmKV;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA,OtCzKI;EsC0KJ;EACA,atC1IiB;EsC2IjB;EACA;;ApCvLA;EoCmKJ;IAwBI;IACA;;;;AAIJ;EACE;EACA;EACA;EACA,KtC7IW;;AEvDT;EoCgMJ;IAOI;;;;AAKJ;EACE;EACA;EACA,KtCvJW;;AsCyJX;AAAA;AAAA;EAGE;EACA;;AAGF;EACE;;ApCzNA;EoC4MJ;IAiBI;IACA;IACA,KtCxKS;;EsC0KT;AAAA;AAAA;IAGE;;EAGF;IACE;;;;AAMN;EACE;EACA;EACA;EACA,etC/KiB;EsCgLjB;EACA,OtC7OU;EsC8OV,YtCpJgB;EsCqJhB,YtC5OM;;AsC8ON;EACE;EACA,ctC9PW;EsC+PX;;AAGF;EACE,YtCpPM;EsCqPN,OtCvPS;EsCwPT;;AAGF;EACE,ctCrQY;;AsCwQd;EACE,ctCxQW;;AsC2Qb;EACE,OtCpQS;;;AsCyQb;EACE;EACA;EACA,KtC9NW;EsC+NX;;AAEA;AAAA;EAEE;EACA;;AAIF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE,OtCxSQ;EsCySR,atCrQmB;EsCsQnB;EACA;;ApCnTA;EoCkRJ;IAqCI;;EAEA;AAAA;IAEE;IACA;;;;AAMN;EACE;EACA;EACA;EACA,etCnQiB;EsCoQjB;EACA,OtCjUU;EsCkUV,YtC/TM;EsCgUN;EACA,YtC1OgB;EsC2OhB;EACA;EACA;EACA;EACA,etCpRY;;AsCsRZ;EACE;EACA,ctCxVW;EsCyVX;;AAGF;EACE,kBtC9UM;EsC+UN,OtCjVS;EsCkVT;;;AAKJ;EACE;EACA,KtC1SW;;AsC4SX;EACE;;ApCrWA;EoCgWJ;IASI;;EAEA;IACE;;;;AAMN;EACE;EACA;EACA;;AAEA;EACE;;;AAIJ;EACE;EACA,OtCrUW;EsCsUX;EACA;EACA,WtClWa;EsCmWb,atCtVqB;EsCuVrB,OtCnYc;EsCoYd;EACA;;;AAIF;EACE;EACA,etCxUiB;EsCyUjB,WtC5We;EsC6Wf,atCjWqB;EsCkWrB;EACA,YtC9SgB;EsC+ShB;EACA;EACA;EACA;EACA,KtC7VW;EsC8VX;;AAEA;EACE;EACA;;AAGF;EACE;EACA,YtCzYQ;;AsC4YV;EACE;;;AAIJ;EACE,OtC7ZM;;;AsCiaR;EACE,YtClaM;EsCmaN,OtCraU;EsCsaV;;AAEA;EACE,ctCrbW;EsCsbX,OtCtbW;;;AsC0bf;EACE;EACA;EACA;EACA;EACA,OtCjbM;EsCkbN;EACA,etC1XiB;EsC2XjB;EACA;EACA;;AAEA;EACE,OtCzbI;;AEVJ;EoCsbJ;IAiBI;;;;AAKJ;EACE;EACA;EACA,KtCvZW;;AExDT;EoC4cJ;IAMI;IACA;;;;AAIJ;EACE;EACA;EACA;EACA,etCxZiB;EsCyZjB,YtCjdQ;EsCkdR,OtCpdW;EsCqdX;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA,ctCreW;EsCseX;EACA,OtCjeQ;;;AsCqeZ;EAEE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;;;AAIJ;EACE;EACA,YtClgBc;EsCmgBd,OtCzfM;EsC0fN;EACA,etClciB;EsCmcjB;EACA;EACA,YtCvagB;;AsCyahB;EACE;;;AAKJ;EACE,YtCzdW;EsC0dX,StC1dW;EsC2dX,etCjdiB;;AsCmdjB;EACE;EACA,WtCxfW;EsCyfX,OtChhBQ;EsCihBR;;AAEA;EACE,YtCteO;;AsCyeT;EACE;EACA,OtCliBU;EsCmiBV,atCrfa;;;AsC2fnB;EACE;EACA,KtCjfW;EsCkfX;EACA,YtC/eY;EsCgfZ,atCjfY;EsCkfZ;;AAEA;EACE;EACA;EACA;;ApCnjBA;EoCwiBJ;IAgBI;IACA;IACA,YtC/fU;IsCggBV,atCjgBS;IsCkgBT;;EAEA;IACE;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;EAIF;AAAA;IAEE;IACA;IACA;;EAEA;AAAA;IACE;IACA;;EAKJ;IACE;IACA;IACA;;EAEA;IACE;IACA;;;;AAOR;EACE,YtC/iBW;EsCgjBX;EACA,etCtiBiB;EsCuiBjB,WtC1kBa;;AsC4kBb;EACE;EACA,OtC5mBW;;AsC+mBb;EACE;EACA,OtClnBY;;;AsCunBhB;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;EACA;EACA,kBtCjoBI;EsCkoBJ;EACA;;;AAIJ;EACE;IACE;;;ApCnpBA;EoCypBF;IACE;;EAGF;IACE;;EAEA;AAAA;IAEE;IACA;IACA;IACA,WtCtoBS;IsCuoBT;;EAIJ;IACE;;EAEA;IACE;;;AAMN;EACE,YtC3qBM;EsC4qBN;EACA;EACA;EACA;EACA;EACA;EACA;;;AAIF;EACE;EACA;EACA;;;AAIF;EACE;EACA;EACA;;;AAIF;EACE;EACA;EACA;;;AAIF;EACE;EACA;EACA;EACA;EACA;;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAIF;EACE;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,YtCjqBgB;EsCkqBhB;;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;;AAKJ;EACE;EACA;;;AAIF;EACE;;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,OtCpyBU;EsCqyBV;EACA,YtC7sBgB;EsC8sBhB;;AAEA;EACE;EACA;;AAGF;EACE,OtC3zBW;;AsC8zBb;EACE,OtC/zBW;;AsCi0BX;EACE;;;AAMN;EACE,YtC7wBW;EsC8wBX,YtCl0BQ;;;AsCq0BV;EACE;EACA;EACA,StCnxBW;;AsCqxBX;EACE;;AAGF;EACE,YtCt0BU;EsCu0BV,etClxBe;;AsCqxBjB;EACE,YtC/0BS;EsCg1BT,etCvxBe;;AsCyxBf;EACE,YtCp1BM;;AsCy1BV;EACE,WtCn0BW;EsCo0BX;EACA,OtC51BQ;;AsC81BR;EACE,OtCh2BM;EsCi2BN,YtC/yBO;EsCgzBP,etCjzBO;EsCkzBP,atC9zBiB;;AsCi0BnB;EACE,etCtzBO;;AsCyzBT;EACE,atCzzBO;EsC0zBP,etC3zBO;;AsC8zBT;EACE;EACA;EACA,etCj0BO;;AsCm0BP;EACE;EACA,StCt0BK;EsCu0BL;;AAGF;EACE,YtCt3BG;EsCu3BH,atCv1Be;;;AE5CnB;EoC24BF;IACE;IACA;;EAGF;IACE;;EAGF;IACE;;EAGF;IACE;IACA;;EAGF;IACE,WtC/3BW;;EsCi4BX;IACE,WtCj4BW;;EsCq4Bf;IACE;IACA,StC/2BS;;;AuC1Db;EACE,evC8DY;EuC7DZ;;ArCAE;EqCFJ;IAKI,evCyDU;;;AuCtDZ;EACE;EACA;;AAGF;EACE,WvCyBY;EuCxBZ,avCgCe;EuC/Bf,OvCPQ;EuCQR,evCyCS;EuCxCT;EACA;EACA;EACA,KvCqCS;;AExDT;EqCWF;IAWI,WvCcU;IuCbV;IACA,KvC+BO;;;AuC5BT;EACE,OvChCS;EuCiCT,WvCQU;;AErCZ;EqC2BA;IAKI,WvCIQ;;;AuCCd;EACE,WvCJW;EuCKX,OvC/BQ;EuCgCR,avCUiB;;AElDjB;EqCqCF;IAMI,WvCXW;;;AuCcb;EACE,OvCnDS;EuCoDT,avCJiB;EuCKjB;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAOR;EACE;EACA;;ArCnEE;EqCiEJ;IAKI;;;;AAKJ;EACE,SvCfY;EuCgBZ,YvCnEM;EuCoEN,evCTiB;EuCUjB,YvCzDU;EuC0DV;;ArChFE;EqC2EJ;IAQI;IACA,evCjBe;;;AuCqBjB;EACE,evC9BU;;AE3DV;EqCwFF;IAII,evClCO;;;AuCqCT;EACE;EACA;EACA,KvC3CO;EuC4CP,WvCnEW;EuCoEX,avCxDiB;EuCyDjB,OvC9FM;EuC+FN,evC9CO;;AuCgDP;EACE,OvC7GO;EuC8GP,WvCzEO;;AuC4ET;EACE,OvC9GQ;EuC+GR;;AAKJ;EACE;EACA;EACA;;ArCvHF;EqCoHA;IAMI;;;AAMN;EACE;EACA;EACA,KvC7ES;EuC8ET,YvC7ES;EuC8ET,WvCtGW;EuCuGX,OvC9HQ;EuC+HR,avCrFiB;;AuCuFjB;EACE,OvC9IS;EuC+IT;EACA;;AAKJ;EACE;EACA;EACA,KvC9FS;EuC+FT,YvC9FS;EuC+FT,WvCvHW;EuCwHX,OvCvJY;EuCwJZ,avCtGiB;;AuCwGjB;EACE;EACA;;AAKJ;EACE,YvC1GS;;AuC4GT;EACE;EACA;EACA,KvCjHO;EuCkHP;EACA,WvC3IS;EuC4IT,OvCjKO;EuCkKP,avCzHe;;AuC2Hf;EACE,OvC7KO;EuC8KP;EACA;;;AAWJ;EACE,OvCrLM;EuCsLN;EACA,cvClMS;EuCmMT,avCpJe;;AuCwJnB;EACE;IACE;;EAGF;IACE;;EAGF;IACE;;;;AAQJ;EACE,cvC1NW;EuC2NX;EACA;;AAIF;EACE;;;AAKJ;EACE;IACE,evC1KS;;EuC6KX;IACE;IACA;IACA,SvChLS;;EuCmLX;IACE;;EAIA;AAAA;IAEE;;;ACrPN;EACE;EACA;EACA;EACA,exCwDY;;AwCtDZ;EANF;I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CA;;EAIF;IACE;IACA;;EAIF;IACE;IACA;IACA;;EAIF;IACE;IACA;IACA;;EAIF;IACE;IACA;IACA;IACA;;EAMN;IACE;IACA;;EAIF;IACE;IACA;IACA;IACA;IACA;IACA;IACA;;EAGA;IACE;;EAGF;IACE,kBxCjhBF;;EwCohBA;IACE;;EAGF;IACE;IACA,axC1fY;IwC2fZ;IACA;IACA;IACA;IACA;IACA;;EAGA;IACE;;EAIF;IACE;;EAIF;IACE;IACA;IACA;;EAEA;IACE;IACA;IACA;;EAKJ;IACE;IACA;IACA;;EAIF;IACE;IACA;IACA;;EAIF;IACE;IACA;IACA;;EAIF;IAEE;IACA;IACA;IACA;IACA;IACA;;EAKJ;IACE;;EAMN;IACE;;EAEA;IACE;;EAGF;IACE,WxCllBO;;EwCqlBT;IACE,WxCzlBO;;EwC8lBX;IACE;IACA;IACA;;EAEA;IACE;IACA;;EAEA;IACE;IACA;IACA;;EAIJ;IACE;;EAGF;IACE;IACA;IACA;IACA;IACA,axC5mBc;;EwC8mBd;IACE,axC5mBS;IwC6mBT;;EAeA;IACE;;EAIF;IACE;IACA;;EAIF;IACE;IACA;IACA;IACA;;EAIF;IACE;IACA;IACA;IACA;;EAIF;IACE;;EAQF;IAEE;;EAIF;IAEE;IACA;IACA;;EAEA;IACE;IACA;IACA;IACA;;EAGA;IACE;;EAGF;IACE;IACA;IACA;;EAGF;IACE;IACA;IACA;;EAEA;IACE;IACA;;EAOR;IACE;IACA;IACA;IACA;IACA;;EAIF;IACE;IACA;IACA;IACA;IACA;;EAEA;IACE;IACA;IACA;;EAKJ;IACE;;EAOR;IACE;IACA;IACA;;EAEA;IACE;IACA;IACA;IACA;IACA;;EAMF;IACE;IACA;;EAcE;IACE;;EAIF;IACE;IACA;;EAIF;IACE;IACA;IACA;;EAQF;IAEE;;EAIF;IAEE;IACA;IACA;;EAEA;IACE;IACA;IACA;IACA;;EAGA;IACE;;EAGF;IACE;IACA;IACA;;EAGF;IACE;IACA;IACA;;EAEA;IACE;IACA;;EAOR;IACE;IACA;IACA;IACA;IACA;;EAQN;IACE;IACA;;EASN;IACE;;EAGA;IACE,exC71BO;;EwC+1BP;IACE,WxCv3BO;;EwC43BX;IACE;IACA;IACA;IACA;;EAIF;IACE;IACA;IACA;IACA;IACA;IACA;;EAEA;IACE;IACA;IACA;;EAEA;IACE;IACA;IACA;IACA;IACA;IACA;;;AAUV;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,exCh5Be;EwCi5Bf;EACA;EACA,YxCp3Bc;;AwCs3Bd;EACE;EACA,OxC79BS;;AwCg+BX;EACE;EACA;;AAGF;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA,YxCl+BI;EwCm+BJ,exC16Be;EwC26Bf;EACA,SxCz5Be;EwC05Bf;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA,axCp9BgB;EwCq9BhB;EACA;EACA,YxC/5BY;EwCg6BZ;;AAEA;EACE;EACA,OxC1gCO;;AwC6gCT;EACE;;AAGF;EACE;;AAOJ;EACE;EACA,OxC3hCS;;AwC8hCX;EACE;EACA;EACA;;;AC7hCN;EACE;EACA;;;AAGF;EACE;EACA;EACA,YzCEM;EyCDN,SzCgDW;EyC/CX,ezCyDiB;EyCxDjB,YzCUU;;;AyCPZ;EACE;EACA,czCuCW;EyCtCX,ezCuCW;;;AyCnCb;EACE;EACA;EACA,ezC2CiB;EyC1CjB;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE,azCQiB;EyCPjB,OzC7BQ;EyC8BR;;AAGF;EACE;EACA;EACA,ezCuBe;EyCtBf,WzCbW;EyCcX;;;AAIJ;EACE;EACA;EACA;;AAGA;EACE;EACA;EACA;;AAEA;EACE;;;AAKN;EACE;;AAEA;EACE;;;AAKJ;EACE;;;AAIF;EACE;EACA;EACA;;;AAGF;EACE;;;AAIF;EACE,OzC5Fc;;;AyC+FhB;EACE;;;AAGF;EACE;;;AAIF;EACE;EACA;;;AAGF;EACE;EACA;EACA,WzCjFa;;AyCmFb;EACE,YzCxGM;EyCyGN,azCzEiB;EyC0EjB,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0CmPN,Y1ChMO;E0CiMP,e1CnMO;E0CoMP;;AAEA;EACE;;AAGF;EAZF;IAaI;;;AAKJ;EACE;EACA,a1C7Na;E0C8Nb,O1CpQM;E0CqQN,Y1CnNO;E0CoNP,e1CtNO;E0CuNP;;AAEA;EARF;IASI;;;AAIJ;EACE,W1CpPS;E0CqPT,a1C3OiB;E0C4OjB,O1CjRM;E0CkRN,Y1CjOO;E0CkOP,e1CnOO;;A0CqOP;EAPF;IAQI,W1C7PS;;;A0CiQb;EACE,a1CrPa;E0CsPb,O1C5RM;;A0C+RR;EACE;EACA,O1ChSM;;A0CmSR;EACE,O1ChTS;E0CiTT;EACA,Y1C7MY;;A0C+MZ;EACE,O1CpTS;;A0CwTb;EACE;EACA;EACA;;AAEA;EALF;IAMI;;;AAIJ;EACE,e1C1QO;E0C2QP;EACA;;AAEA;EACE;;AAIJ;EACE;EACA,c1ClRO;E0CmRP;EACA,O1CrUM;E0CsUN;EACA,Y1CpUI;E0CqUJ;EACA;;AAEA;EAVF;IAWI;;;AAIJ;EACE,Y1C9UI;E0C+UJ;EACA,e1CzRa;E0C0Rb,a1CjUa;E0CkUb;EACA,O1ClWS;;A0CqWX;EACE,Y1CvVI;E0CwVJ;EACA,e1CjSa;E0CkSb,S1C7SO;E0C8SP;EACA;;AAEA;EARF;IASI,S1CnTK;;;A0CsTP;EACE;EACA;EACA,O1CzWI;;A0C6WR;EACE;EACA;EACA;EACA;EACA,e1CtTa;E0CuTb;EACA,W1C5VS;;A0C8VT;EATF;IAUI,W1ChWO;;;A0CmWT;EACE,Y1CtXG;E0CuXH;EACA;EACA,a1CzVe;E0C0Vf,O1C/XI;E0CgYJ;;AAEA;EARF;IASI;;;AAIJ;EACE;EACA;EACA,O1C1YI;;A0C4YJ;EALF;IAMI;;;AAKF;EACE;;AAGF;EACE,Y1CnZA;;A0CwZN;EACE;EACA;EACA;;AAIF;EACE,Y1ChXQ;E0CiXR,a1CnXO;E0CoXP;;AAEA;EACE;EACA;EACA;;AAIJ;EACE;EACA;EACA,O1C7bS;E0C8bT,a1C7Ya;;;A0CoZjB;EACE;;AAGF;EACE,Y1C3bM;E0C4bN,e1CrYe;;A0CwYjB;EACE,Y1C/cW;E0CgdX,e1C1Ye;;A0C4Yf;EACE,Y1CldW;;;A2CmBjB;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAEA;EACE;;AAEA;EACE;;AAKN;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;;AAKN;EACE;IACE;IACA;;EAEA;IAAW;;EACX;IAAU;;EACV;IAAmB;;EACnB;IAEU;;;AASd;EACE;EACA;EACA;EACA,kBA3GW;;AA8GX;EACE;;AAGF;EACE;EACA;EACA;EACA,OA1HmB;EA2HnB;;AAGF;EACE;EACA;EACA,kBAjImB;EAkInB;;AAIA;EACE;EACA;EACA;EACA,OArIc;EAsId;;AAKJ;EACE;EACA;EACA;EACA,YA3IqB;EA4IrB;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGE;EACE;;AAKN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;EACA,kBAhMmB;EAiMnB;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA,OApNgB;EAqNhB;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAIF;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA,OAhPgB;EAiPhB;;AAEA;EACE;;AAIJ;EACE;EACA;;AAIF;EACE;EACA;;AAGF;EACE;EACA;EACA,YA3QmB;EA4QnB;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;;;AASN;EACE;IACE;;EAEA;IACE;;EAGF;IACE;IACA;;EAKE;IACE;;EAKN;IACE;;;AAKN;EACE;IACE;;EAEA;IACE;;EAGF;IACE;;EAGF;IACE;IACA;IACA;;EAIA;IACE;IACA;;EAIJ;IACE;IACA;;EAGF;IACE;IACA;;EAKE;IACE;;EAIF;IACE;;EAKN;IACE;IACA;;EAEA;IACE;IACA;;EAIJ;IACE;IACA;;EAGF;IACE;IACA;;EAGF;IACE;IACA;IACA;;EAGF;IACE;;EAGF;IACE;IACA;;EAGF;IACE;IACA;IACA;IACA;;;AAKN;EAEI;IACE;IACA;;EAGF;IACE;IACA;IACA;;EAEA;IACE;;EAIJ;IACE;;EAGF;IACE;;EAGF;IACE;IACA;IACA;;;AASN;EACE;EACA;EACA;;AAGE;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;;AAKN;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;;AAIJ;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;EACA;;;AAIJ;EACE;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAGF;AAAA;EAEE;;;AAKN;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;;;AAIJ;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;;AASN;EACE;IACE;;EAGE;IACE;IACA;;EAGF;IACE;IACA;;EAKN;IACE;;EAEA;IACE;;EAIJ;IACE;;EAEA;IACE;IACA;;EAGF;IACE;;EAIJ;IACE;IACA;IACA;;EAGE;IACE;IACA;;EAKN;IACE;;EAEA;IACE;;EAIJ;IACE;;EAEA;IACE;IACA;;;AA6BN;EACE;EACA;EACA;EACA;EACA,OAnBgB;EAoBhB;EACA;EACA;EACA;EACA;;AAGA;EACE;EACA;EACA;EACA,OAnCc;EAoCd;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA,OAzCc;;AA4ChB;EACE;EACA;EACA;EACA,OA/Cc;;AAmDhB;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA,OA3Ec;EA4Ed;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA,YArFc;;AAwFhB;EACE;EACA;EACA;EACA,OAxFc;EAyFd;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA,OAjGc;EAkGd;;AAEA;EAAQ;;AAGV;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,OAxHc;;AA0Hd;EACE,YA/HY;EAgIZ,cAhIY;EAiIZ;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAEA;EAAM;EAAgB;EAAa;;AAIrC;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA,YAxKc;EAyKd;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA,OA9Kc;;AAiLhB;EACE;EACA;EACA;EACA,OApLc;EAqLd;;AAEA;EAAQ;;AAIV;EACE;EACA;EACA;EACA;;AAEA;EAAM;EAAgB;EAAa;;AAIrC;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA,YAhOc;EAiOd;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA,OAtOc;;AAyOhB;EACE;EACA;EACA;EACA,YA9Oc;EA+Od;EACA,OA9Oc;EA+Od;EACA;;AAIF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA,OAhQc;;AAmQhB;EACE;EACA;EACA;EACA,OAvQc;;AA0QhB;EACE;EACA;EACA;;AAEA;EACE,YAjRY;;AAoRd;EACE;EACA;EACA;EACA;EACA,OAvRY;EAwRZ;;AAGF;EACE;EACA;EACA,OA9RY;EA+RZ;;AAGF;EAAyB;;AAEzB;EACE;EACA;EACA;EACA,OAzSY;;AA6SZ;EAAiB;;AACjB;EAAkB;;AAClB;EAAkB;;AAClB;EAAK,OA/SO;;AAmThB;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAAc,YAtUA;;AAuUd;EAAc,YAjUA;;AAoUhB;EACE;EACA;EACA;EACA,YAjUc;EAkUd,OAjUc;EAkUd;EACA;;AAIF;EACE;EACA;EACA;EACA,YArVc;EAsVd;;AAEA;EACE;EACA;EACA;EACA;EACA,YAjWY;;AAoWd;EAAmB,YAxVL;;AA2VhB;EACE;EACA;EACA;EACA,YA3Wc;EA4Wd;EACA;EACA;EACA;;AAEA;EAAW,YAjXG;;AAkXd;EAAW,YAtWG;;AAyWhB;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAIF;EAAY,OAxXI;;AAyXhB;EAAY,OAxXI;;AAyXhB;EAAY,OAxXI;;AAyXhB;EAAY,OAxXI;;AAyXhB;EAAY,OA9XI;;AAiYhB;EACE;EACA;EACA,YAvYc;EAwYd;;AAGF;EACE;EACA;EACA;EACA;EACA,OAnZc;EAoZd;;AAGF;EACE;EACA;EACA;EACA,OAvZc;;AA0ZhB;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA,OAnaY;EAoaZ;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA,YAnbU;;AAsbZ;EAAS;;AAKb;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAAS,YArbK;EAqbsB,OAxbtB;;AAybd;EAAS,YAxbK;EAwbuB,OAvbvB;;AA2bhB;EACE;EACA;EACA;EACA,YA9bc;EA+bd,OA9bc;EA+bd;EACA;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAIJ;EAAc;EAAoB;;AAElC;EACE;EACA;EACA;EACA,OA1ec;EA2ed;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA,OA3fc;;AA8fhB;EACE;EACA;EACA;EACA;EACA;EACA,OArgBc;EAsgBd;EACA;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAKN;EAEI;IACE;IACA;;EAEF;IAAiB;;EACjB;IAAiB;;EACjB;IAAe;;EACf;IACE;IACA;;EAEA;AAAA;IACyC;;EAGzC;AAAA;AAAA;IAEkB;;;AAKxB;EACE;IACE;IACA;;EAEA;IAAU;;EACV;IAAgB;;EAChB;IAAQ;;EACR;IAAS;;EACT;IAAe;;EACf;IAAW;;EACX;IAAgB;;;ACttCpB;EACE;EACA;;;AAIF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;;AAKJ;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;;;AAIJ;EACE;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;;AAIJ;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;;AAIJ;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAIF;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAIF;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;;AAIJ;EACE;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;;AAIJ;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAIF;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;;AAIJ;EACE;;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAIA;EACE;EACA;EACA;;AAGF;EACE;;AAIJ;EACE;;AAEA;EACE;EACA;EACA;EACA;;;AAKN;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAEA;EACE;;;AAMN;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAIF;EACE;EACA;EACA;;;AAIF;EACE;IACE;IACA;;EAGF;AAAA;AAAA;IAGE;IACA;;EAGF;IACE;;EAGF;IACE;IACA;;;AAIJ;EACE;IACE;;EAGF;IACE;;EAGF;IACE;;EAGF;IACE;IACA;;EAIA;AAAA;AAAA;IAGE;IACA;;EAIJ;IACE;;EAGF;IACE;;;ACrbJ;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIF;E3CRE;EACA;EACA;;;A2CUF;E3CNE;EACA;EACA;;;A2CQF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIF;EACE,K7CpBW;;;A6CuBb;EACE,K7CvBW;;;A6C0Bb;EACE,K7C1BW;;;A6C6Bb;EACE,K7C7BW;;;A6CgCb;EACE,K7ChCW;;;A6CgDX;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AA3BF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AA3BF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AA3BF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AA3BF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AA3BF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AA3BF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AA3BF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AA3BF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AA3BF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAgBF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AA3BF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AA3BF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AA3BF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AA3BF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AA3BF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AA3BF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AA3BF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AA3BF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAKJ;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIF;EACE,Y7CzPa;;;A6C4Pf;EACE,Y7C5Pe;;;A6C+PjB;EACE,Y7CnPM;;;A6CsPR;EACE,Y7CtPQ;;;A6CyPV;EACE,Y7CzPS;;;A6CkQX;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE,c7C3Sa;;;A6C+Sf;EACE;;;AAGF;EACE,e7C9OiB;;;A6CiPnB;EACE,e7CjPiB;;;A6CoPnB;EACE,e7CpPiB;;;A6CuPnB;EACE,e7CvPiB;;;A6C0PnB;EACE,e7CzPmB;;;A6C4PrB;EACE,e7C5PqB;;;A6CgQvB;EACE;;;AAGF;EACE,Y7CzTU;;;A6C4TZ;EACE,Y7C5TU;;;A6C+TZ;EACE,Y7C/TU;;;A6CkUZ;EACE,Y7ClUU;;;A6CsUZ;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;A3C3YE;E2CgZF;IACE;;;A3C/YA;E2CoZF;IACE;;;AAKF;EADF;IAEI;;;;A3C7ZA;E2CiaJ;IAEI;;;;AAKJ;EACE;;A3CzaE;E2CwaJ;IAII;;;;AAIJ;EACE;;A3CjbE;E2CgbJ;IAII;;;;AAKJ;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIF;EACE,Y7C3XgB;;;A6C8XlB;EACE,Y7C9XgB;;;A6CiYlB;EACE,Y7CjYgB;;;A6CoYlB;EACE;;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA","file":"main.css"} \ No newline at end of file +{"version":3,"sourceRoot":"","sources":["../sass/base/_reset.scss","../sass/abstracts/_variables.scss","../sass/base/_typography.scss","../sass/abstracts/_mixins.scss","../sass/base/_animations.scss","../sass/base/_djb-font.scss","../sass/layout/_header.scss","../sass/layout/_footer.scss","../sass/layout/_grid.scss","../sass/layout/_container.scss","../sass/components/_buttons.scss","../sass/components/_badges.scss","../sass/components/_cards.scss","../sass/components/_apikey-common.scss","../sass/components/_forms.scss","../sass/components/_modals.scss","../sass/components/_navigation.scss","../sass/components/_partners.scss","../sass/components/_cta.scss","../sass/components/_password-popup.scss","../sass/components/_password-policy.scss","../sass/components/_header-auth.scss","../sass/components/_session-timer.scss","../sass/components/_tables.scss","../sass/components/_accordion.scss","../sass/components/_page-title-banner.scss","../sass/components/_alerts.scss","../sass/components/_toast.scss","../sass/components/_pagination.scss","../sass/components/_breadcrumb.scss","../sass/components/_test-env-notice.scss","../sass/components/_djb-inquiry-comments.scss","../sass/components/_board-common.scss","../sass/components/_two-factor.scss","../sass/pages/_index.scss","../sass/pages/_api-market.scss","../sass/pages/_login.scss","../sass/pages/_account-recovery.scss","../sass/pages/_signup-selection.scss","../sass/pages/_mypage.scss","../sass/pages/_apikey-register.scss","../sass/pages/_apikey-detail.scss","../sass/pages/_apikey-list.scss","../sass/pages/_notice.scss","../sass/pages/_inquiry.scss","../sass/pages/_org-register.scss","../sass/pages/_partnership.scss","../sass/pages/_user-management.scss","../sass/pages/_terms-agreements.scss","../sass/pages/_service.scss","../sass/pages/_api-statistics.scss","../sass/pages/_webhook.scss","../sass/pages/_api-status.scss","../sass/base/_utilities.scss"],"names":[],"mappings":";AAQA;EACE;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE,aCWoB;EDVpB,aCmCmB;EDlCnB,OCVU;EDWV,kBCRM;EDSN;;;AAIF;EACE;;;AAIF;EACE;EACA;EACA,YCoEgB;;;ADhElB;EACE;EACA;EACA;;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAIF;AAAA;AAAA;EAGE;EACA;EACA;EACA;EACA;;;AAIF;EACE;EACA;;;AAIF;EACE,aCzBiB;ED0BjB,aCtBkB;;;AD0BpB;AAAA;AAAA;EAGE;;;AAIF;EACE;EACA,OC9EU;;;ADiFZ;EACE;EACA,OCnFU;;;ACVZ;EACE,aD4BoB;EC3BpB,WDkCe;ECjCf,aD2CoB;EC1CpB,ODMU;;;ACFZ;AAAA;AAAA;AAAA;AAAA;AAAA;EAME,aDgBoB;ECfpB,aDgCmB;;;AC1BrB;AAAA;AAAA;AAAA;EAIE,aDKoB;ECJpB,aDqBmB;;;AClBrB;EACE,WDac;ECZd,eD8BW;;AE3DT;ED2BJ;IAKI,WDOY;;;AEzCZ;ED6BJ;IASI,WDEY;;;;ACEhB;EACE,WDDc;ECEd,eDgBW;;AE1DT;EDwCJ;IAKI,WDPY;;;AExCZ;ED0CJ;IASI,WDZW;;;;ACgBf;EACE,WDfc;ECgBd,eDGW;;AE1DT;EDqDJ;IAKI,WDrBW;;;;ACyBf;EACE,WDzBc;EC0Bd,eDPW;;;ACUb;EACE,WD/Ba;ECgCb,eDZW;;;ACeb;EACE,WDrCa;ECsCb,eDjBW;;;ACqBb;EACE,eDrBW;ECsBX,aD5BmB;;AC8BnB;EACE;;;AAIJ;EACE,OD3Fa;;;AC8Ff;EACE,OD9Fe;;;ACiGjB;EACE,ODxFU;;;AC2FZ;EACE,OD3FU;;;AC8FZ;EACE,OD9FW;;;ACiGb;EACE,ODjGM;;;ACqGR;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIF;EACE,aDlFoB;;;ACqFtB;EACE,aDrFmB;;;ACwFrB;EACE,aDxFqB;;;AC2FvB;EACE,aD3FiB;;;AC8FnB;EACE,aD9FsB;;;ACkGxB;EACE,WDnHa;;;ACsHf;EACE,WDtHa;;;ACyHf;EACE,WDzHe;;;AC4HjB;EACE,WD3Ha;;;AC8Hf;EACE,WD9Ha;;;ACkIf;EACE,WDpIa;ECqIb,aD7HoB;EC8HpB,aDrHkB;ECsHlB,ODlKU;;;ACqKZ;EACE,WD9Ia;EC+Ib,ODvKU;;;AC0KZ;EACE,aDvJiB;ECwJjB,WDpJa;ECqJb,YD1KQ;EC2KR;EACA,eDpHiB;;;ACwHnB;EACE,ODhMa;;ACkMb;EACE,ODlMa;;;ACuMjB;EACE,SD5IW;EC6IX;EACA;EACA,YD7LQ;EC8LR;;AAEA;EACE;;;AAKJ;EACE;EACA;EACA;;;AEvNF;EAEE;IAEE;;EAGF;IACE;;;AAKJ;EAEE;IAEE;;EAGF;IACE;;;AAKJ;EACE;IACE;IACA;;EAGF;IACE;IACA;;;AAKJ;EACE;IACE;IACA;;EAGF;IACE;IACA;;;AAKJ;EACE;IACE;IACA;;EAGF;IACE;IACA;;;AAKJ;EACE;IACE;IACA;;EAGF;IACE;IACA;;;AAKJ;EACE;IACE;;EAGF;IACE;;;AAKJ;EACE;IACE;IACA;;EAGF;IACE;IACA;;;AAKJ;EACE;IACE;;EAGF;IACE;;;AAKJ;EACE;IACE;;EAGF;IACE;;EAGF;IACE;;;AAKJ;EACE;IACE;;EAGF;IACE;;;AAKJ;EACE;IACE;;EAGF;IACE;;EAGF;IACE;;EAGF;IACE;;EAGF;IACE;;EAGF;IACE;;EAGF;IACE;;EAGF;IACE;;;AAKJ;EACE;IACE;IACA;;EAGF;IACE;;EAGF;IACE;IACA;;;AAKJ;EACE;IACE;IACA;;EAGF;IACE;IACA;;;AAKJ;EACE;IACE;IACA;;EAGF;IACE;IACA;;;AAKJ;EACE;IACE;;EAGF;IACE;;EAGF;IACE;;;AAKJ;EACE;;;AAIF;EAEE;IAEE;;EAGF;IACE;;;AAIJ;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;IACE;;;AAIJ;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAIF;EAEE;IAEE;IACA;;EAGF;IACE;AACA;IACA;;;AAKJ;EAEE;IAEE;IACA;;EAGF;IACE;AACA;IACA;;;AAKJ;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIF;EACE;;AAEA;EACE;;;AAIJ;EACE;;AAEA;EACE;EACA,YH3WQ;;;AG+WZ;EACE;;AAEA;EACE;;;AAIJ;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;;;ACnaJ;AACA;EACE;EACA;EACA;EACA;EACA;;AAIF;EACE;EACA;EACA;EACA;EACA;;AAIF;EACE;EACA;EACA;EACA;EACA;;AAIF;EACE;EACA;EACA;EACA;EACA;;AAKF;AACA;EACE;EACA;EACA;EACA;EACA;;AAKF;EACE;EACA;EACA;EACA;EACA;;AAMF;AACA;EACE;EACA;EACA;EACA;EACA;;AAKF;EACE;EACA;EACA;EACA;EACA;;AAMF;AAAA;AAAA;AAAA;AAAA;AAKA;EACE;EACA;EACA;EACA;EACA;;AC1FF;EAEE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAGA;EACA;EACA;EACA;EACA;EACA;EACA;EAGA;EACA;EACA;EACA;EACA;EAGA;EAGA;;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAMF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGA;EACE;;AAGF;EACE;EACA;EACA;;AAEA;EACE;;AAIJ;EA7BF;IA8BI;IACA;IACA;IACA;IACA;;EAEA;IACE;IACA;IACA;;EAEA;IACE;IACA;IACA;;;;AAMR;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAQF;EACE;EACA;EAEA;;AAEA;EACE;EAEA;EACA;;AAGA;EAAM;;AAEN;EACE;EACA;;AAGF;EACE;EACA;EACA;EAEA;EACA;;;AAMN;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAIF;EACE;EACA;EACA;;;AAMJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGA;EACE;;AAGF;EACE;;;AAKJ;AAAA;EAEE;EACA;EACA;;AAEA;AAAA;EACE;;AAGF;AAAA;EACE;;;AAKJ;EACE;EACA;EACA;;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;;AAOJ;EACE;EACA;EACA;EACA;;AAEA;EANF;IAOI;;;;AAIJ;EACE;EACA;EACA;EACA;;AAEA;EANF;IAOI;;;;AAQJ;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EAGE;EACA;EACA;;AAGF;EACE,aLpRkB;EKqRlB;EACA;EACA;EACA;EACA;;AAEA;EACE;;;AAKN;EAEE;;AAEA;EACE;EACA;EACA;EACA;;;AAIJ;EACE;EACA;;AAEA;EAIE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAEA;EAEA;EACA;EACA;;AAEA;EACE;;AAGF;EAGE;EACA;EACA;EACA;EACA;;AAKE;EACE;;AAGF;EACE;;AAGF;EACE;;;AAUV;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAEA;EACE;;AAIJ;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE,aL3ac;EK4ad;EACA;EACA;EACA;EACA;;AAIJ;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;;AAMN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGA;EACE;;AAEA;EACE;EACA;;AAKJ;EACE,aLxfgB;EKyfhB;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE,aLlgBgB;EKmgBhB;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA,aL3hBgB;EK4hBhB;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA,aLhjBgB;EKijBhB;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAKJ;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAIF;EACE,aLjlBc;EKklBd;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAMN;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE,aLxnBc;EKynBd;EACA;EACA;EACA;;AAGF;EACE;;AAMN;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;;AAGA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,aL/pBc;EKgqBd;EACA;EACA;EACA;;AAEA;EACE;;AAKJ;EACE;EACA;EACA;EACA;EACA;;AAGE;EACE;EACA;EACA,aLtrBU;EKurBV;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAKJ;EACE;EACA;;AAOJ;EACE;;AAEA;EACE;;AAIJ;EACE;;;AAOV;EACE;EACA,OLhsBW;EKisBX;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA,YLjwBQ;EKkwBR;EACA;EACA;;AAEA;EAEE;EACA;EACA;EACA;EACA;EACA,YL7wBM;EK8wBN;;AAGF;EACE;;AAGF;EACE;;AAIJ;EACE;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;;AAQN;EAEE;EACA;EACA;EACA;EACA;EACA,YLhzBM;EKizBN,SL9tBc;EK+tBd;EACA;EACA;EACA;EACA;;AAKA;EACE,SL5wBS;EK6wBT,YL5zBM;EK6zBN;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAEA;EACE;EACA;EACA,SL7xBK;EK8xBL,OLj1BI;EKk1BJ,WLvzBO;EKwzBP,aL9yBa;EK+yBb;EACA;;AAGF;EACE;EACA;EACA,OLxyBK;EKyyBL;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA,YL12BE;;AK42BF;EACE;EACA;EAGF,OLp3BI;EKq3BJ,WL71BO;EK81BP;;AAEA;EACE,YLv3BF;EKw3BE,OLt4BK;;AK24BX;EACE;;AAKN;EACE;EACA,YLr4BM;EKs4BN;EACA;;;AAQF;EACE;EACA;EACA;EACA;EACA;;AAGA;EACE;;AAGE;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA,eLp3Ba;EKq3Bb;EACA;EACA;EACA;EACA;EACA;EACA,SLz2Ba;EK02Bb;EACA;;AAEA;EACE;;AAEA;EACE;EACA;EACA;EACA;EACA,WL56BK;EK66BL,aLj6BW;EKk6BX;;AAEA;EACE;EACA;;;AAQZ;EACE;EACA;EACA;EACA;EACA;EACA;EAEA;;AAEA;EACE;;;AAIJ;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;;AA0FJ;EACE;IAGE;IACA;IACA;IACA;IACA;;EAEA;IACE;IACA;IACA;;EAGF;IAIE;IACA;IACA;;;AAUN;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE,aL9mCgB;EK+mChB;EACA;EACA;EACA;;AAIJ;EACE;EACA;EACA;;AAGF;EACE,aL7nCkB;EK8nClB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;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ACA;EACA,KPjGY;EOkGZ;;AL/JE;EK2JJ;IAOI;;;;AAKJ;EACE;EACA;EACA,KP9GW;;AE5DT;EKuKJ;IAMI;;;;AAKJ;EACE;EACA;EACA,KPxHY;EOyHZ;;ALpLE;EKgLJ;IAOI;;;;AAKJ;EACE;EACA;EACA,KPjIY;;AE9DV;EK4LJ;IAMI;IACA,KPxIU;;;AO2IZ;EACE;EACA;EACA,KP9IU;;AE3DV;EKsMF;IAMI;;;AL9MF;EKwMF;IAUI;IACA;;;;AAMN;EACE;EACA;EACA,KPjKW;EOkKX;;AL3NE;EKuNJ;IAOI;IACA,KPrKS;;;;AO0Kb;EACE;EACA;EACA,KP9KW;;AE3DT;EKsOJ;IAMI;;;;AAKJ;EACE;EACA;EACA,KPxLW;EOyLX;;ALnPE;EK+OJ;IAOI;;;ALxPA;EKiPJ;IAWI;;;;AAKJ;EACE;EACA;EACA,KPtMY;EOuMZ;;ALnQE;EK+PJ;IAOI;IACA,KP7MS;;;AOgNX;EACE;;AAEA;EACE;;AL9QF;EK0QF;IAQI;;;;AAMN;EACE;EACA;EACA,KPjOW;;AE1DT;EKwRJ;IAMI;;;;AClSJ;EACE,WR8EoB;EQ7EpB;EACA;;ANDE;EMFJ;IAMI;;;AAIF;EACE;EACA;;AAGF;EACE;;AAGF;EACE;;;AAKJ;EACE;;ANxBE;EMuBJ;IAII;;;AAIF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;;AAMF;EACE,YRvCM;;AQ0CR;EACE,YR1CO;;AQ6CT;EACE,ORhDI;;AQmDN;EACE,YRvDQ;EQwDR,ORrDI;;;AQ0DR;EACE;EACA,eRRY;;AE/DV;EMqEJ;IAKI,eRZU;;;AQeZ;EACE;EACA;EACA,KRxBS;EQyBT;EACA,YRrEO;EQsEP,ORtFW;EQuFX,eRXiB;EQYjB,WRpDW;EQqDX,aRxCmB;EQyCnB,eR7BS;;AQgCX;EACE,WRpDY;EQqDZ,aR7Ce;EQ8Cf,eRnCS;EQoCT,ORtFQ;;AERR;EM0FF;IAOI,WR3DU;;;AQiEd;EACE,WRrEW;EQsEX,ORhGQ;EQiGR,aRtDiB;EQuDjB;EACA;;AN5GA;EMuGF;IAQI,WR7EW;;;;AQmFjB;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA,aR/CY;;;AQoDhB;EACE;EACA;;;AAIF;EACE;EACA;;;AC5IF;EP+BE;EACA;EACA,KF0BW;EEzBX;EACA,eFqCiB;EEpCjB,aFYqB;EEXrB,WFDe;EEEf;EACA,YF+DgB;EE9DhB;EACA;;AAEA;EACE;;AO5CJ;EAEE;EACA;;AAGA;EACE;EACA,WT2BW;;ASxBb;EACE;EACA,WTuBa;;ASpBf;EACE;EACA,WTmBW;;AShBb;EACE;EACA,WTeW;;ASXb;EACE,YT7BW;ES8BX,OThBI;ESiBJ,YTNQ;;ASQR;EACE;EACA,YTRM;;ASWR;EACE;;AAIJ;EACE;EACA;;AAEA;EACE,YThCK;ESiCL;;AAIJ;EACE,OTxCI;ESyCJ,YT9BQ;;ASgCR;EACE;EACA,YThCM;;ASoCV;EACE,OTlDI;ESmDJ,YTxCQ;;AS0CR;EACE;EACA,YT1CM;;AS8CV;EACE,YTrEW;ESsEX,OT7DI;;AS+DJ;EACE;EACA;;AAIJ;EACE,YThFY;ESiFZ,OTvEI;;ASyEJ;EACE;EACA;;AAIJ;EACE;EACA,OT/FW;ESgGX;;AAEA;EACE;EACA,cTpGS;;ASwGb;EACE;EACA,OT/FQ;ESgGR;;AAEA;EACE,cT9GS;ES+GT,OT/GS;ESgHT;;AAKJ;EAEE;EACA;EACA;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EPrDJ;EACA;EACA;EACA;EOoDI;EACA;EACA;EACA;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA,eTxEmB;;AS0EnB;EACE;EACA;;AAGF;EACE;EACA;;AAKJ;EACE;EACA;;AAIF;EACE;EACA;;AAEA;EACE;;AAEA;EACE,wBTxGW;ESyGX,2BTzGW;;AS4Gb;EACE,yBT7GW;ES8GX,4BT9GW;;;ASqHnB;EACE;EACA,QTjIW;ESkIX,OTlIW;ESmIX;EACA;EAEA;EACA,eTzHqB;ES0HrB,OTzLM;ES0LN,WThKa;ESiKb;EACA,YThLU;ESiLV,YTpGgB;ESqGhB,ST/Gc;EE1Ed;EACA;EACA;;AO0LA;EACE;EACA,YTtLQ;;ASwLR;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA,YTnNQ;ESoNR,OTjNI;ESkNJ;EACA,eTzJe;ES0Jf,WT9LW;ES+LX;EACA;EACA,YT9Hc;ES+Hd;;;AAMF;EP5MA;EACA;EACA,KF0BW;EEzBX;EACA,eFqCiB;EEpCjB,aFYqB;EEXrB,WFDe;EEEf;EACA,YF+DgB;EE9DhB;EACA;;AAEA;EACE;;AO+LF;EAEE,YThOI;ESiOJ,OT/OW;ESgPX;EACA,WT3MW;ES4MX,YTxNQ;;AS0NR;EACE;EACA,YT3NM;;AS+NV;EP1NA;EACA;EACA,KF0BW;EEzBX;EACA,eFqCiB;EEpCjB,aFYqB;EEXrB,WFDe;EEEf;EACA,YF+DgB;EE9DhB;EACA;;AAEA;EACE;;AO6MF;EAEE;EACA,OT/OI;ESgPJ;EACA;EACA,WT1NW;;AS4NX;EACE;EACA;;;AAUN;AAAA;AAAA;AAAA;AAAA;AAAA;EAME;EACA,eT7MiB;ES8MjB,WTjPe;ESkPf,aTvOmB;ESwOnB;EACA,YTlLgB;ESmLhB;EACA;EACA;EACA,KTjOW;ESkOX;EACA;;AAEA;EAnBF;AAAA;AAAA;AAAA;AAAA;AAAA;IAoBI;IACA,WT/PW;;;ASkQb;AAAA;AAAA;AAAA;AAAA;AAAA;EACE;;;AAKJ;AAAA;EAGE,OTjSM;;ASmSN;AAAA;EACE;EACA,YT1RQ;;AS6RV;AAAA;EACE;;;AAKJ;EACE,YT/SM;ESgTN,OTnTU;ESoTV;;AAEA;EACE,YTnTM;ESoTN,cTnUW;ESoUX,OTpUW;;ASuUb;EACE;;;AAKJ;EACE,YT9Ua;ES+Ub,OTjUM;;ASmUN;EACE;EACA,YT1TQ;ES2TR,YTnVa;;ASsVf;EACE;;;AAKJ;EACE,YT1Vc;ES2Vd,OTjVM;;ASmVN;EACE;EACA,YT1UQ;ES2UR;;AAGF;EACE;;;AAMF;EACE,YTjWI;ESkWJ,OTrWQ;ESsWR;;AAEA;EACE,YTrWI;ESsWJ,cTrXS;ESsXT,OTtXS;;AS0Xb;EAEE,OT9WI;;ASgXJ;EACE;EACA,YTvWM;;AS2WV;EACE,YTjYY;ESkYZ,OTxXI;;AS0XJ;EACE;EACA,YTjXM;;;ASuXZ;EACE;EACA;EACA,OTnZa;ESoZb;EACA,eT9UiB;ES+UjB,WTnXa;ESoXb,aTvWmB;ESwWnB;EACA,YTlTgB;ESmThB;EACA;EACA,KTjWW;;ASmWX;EACE;EACA,cThaW;;ASmab;EACE;;;AAIJ;EACE;EAEA,OT7ZM;ES8ZN;EACA,eTrWiB;ESsWjB,WT1Ya;ES2Yb,aT/XmB;ESgYnB;EACA,YT1UgB;ES2UhB;EACA;EACA,KTzXW;;AS2XX;EACE;EACA,YT/ZQ;;ASkaV;EACE;;;AAIJ;EACE;EACA,YTpbM;ESqbN,OTvbU;ESwbV;EACA,eT7XiB;ES8XjB,WTlaa;ESmab,aTvZmB;ESwZnB;EACA,YTlWgB;ESmWhB;EACA;EACA,KTjZW;;ASmZX;EACE,cT/cW;ESgdX,OThdW;;ASmdb;EACE;;;AAQJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,eT5ZiB;ES6ZjB,WT/ba;ESgcb,aTrbiB;ESsbjB,OT1dM;ES2dN;EACA,YTnYgB;ESoYhB;;AAGA;EACE;;AAEA;EACE;EACA;EACA,YT3dM;;AS8dR;EACE;;AAKJ;EACE;;AAEA;EACE,YThgBW;ESigBX;EACA,YT1eM;;AS6eR;EACE;;AAGF;EACE,YT1fQ;ES2fR;EACA;EACA;;AAIJ;EAtDF;IAuDI;IACA;IACA;IACA,WTjfa;;;;ASyfjB;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA,OTxhBM;ESyhBN;EACA,eTheiB;ESiejB;EACA,aTzfqB;ES0frB;EACA;;AAEA;EACE;;AP1iBA;EOyhBJ;IAqBI;IACA;;;;AAOJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;APvlBA;EOwjBJ;IAoCI;;;;AC9lBJ;EACE;EACA;EACA;EACA;EACA,eVsEmB;EUrEnB,WV4Ba;EU3Bb,aVyCqB;EUxCrB;EACA,YV6FgB;;AU1FhB;EACE;EACA,OVXY;;AUcd;EACE;EACA,OVfW;;AUkBb;EACE;EACA,OVzBW;;AU4Bb;EACE;EACA,OVlBQ;;AUqBV;EACE;EACA;;AAGF;EACE;EACA,OVpCY;;AUuCd;EACE;EACA,OVxCW;;;AU6Cf;EACE;EACA;EACA;EACA;EACA,eVqBmB;EUpBnB,WVrBa;EUsBb,aVRqB;EUSrB,eVGW;EUFX,YV4CgB;;AU1ChB;EACE;EACA,OV3DY;;AU8Dd;EACE;EACA,OV/DW;;AUkEb;EACE;EACA,OVzEW;;AU4Eb;EACE;EACA,OVlEQ;;AUqEV;EACE;EACA;;;AAKJ;EACE;EACA;EACA,eVpBiB;;;AUwBnB;EACE;EACA,WV7Da;EU8Db,eV1BiB;;;AU8BnB;EACE;EACA;;AAEA;EACE,OVvGY;EUwGZ,cVxGY;;AU2Gd;EACE,OV3GW;EU4GX,cV5GW;;AU+Gb;EACE,OVrHW;EUsHX,cVtHW;;AUyHb;EACE,OV9GQ;EU+GR,cV/GQ;;;AUqHV;EACE,cVvES;EUwET;;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA,YV3Ic;EU4Id,OVlIM;EUmIN,eVpEqB;EUqErB;EACA,aVjGiB;EUkGjB;;AAEA;EACE,YVvJW;;AU0Jb;EACE,YVtJW;;AUyJb;EACE,YV5JY;EU6JZ,OVrJQ;;AUwJV;EACE,YVhKY;;;AWHhB;ETkDE,YFrCM;EEsCN,eFuBkB;EEtBlB,SFyDa;EExDb,YFiDgB;;AE/ChB;EAEE,YFhCQ;;AWzBZ;EAEE;EACA;;;AAIF;ET2CE,YFrCM;EEsCN,eFuBkB;EEtBlB,SFyDa;EExDb,YFiDgB;;AE/ChB;EAEE,YFhCQ;;AWlBZ;EAEE;EACA;;AAEA;EACE,cXdW;;AWmBb;EACE,cXjBY;;AWqBd;EACE,cXpBW;;AWwBb;EACE;EACA;EACA;EACA;EACA,YX/BY;EWgCZ,OXxBQ;EWyBR,eXuCgB;EWtChB,WXFW;EWGX,aXYe;EWXf;;AAEA;EACE,YXrCS;EWsCT,OX7BE;;AWgCJ;EACE,YXzCU;EW0CV,OXlCE;;AWuCN;EACE;EACA;EACA;ETrCF;EACA;EACA;ESqCE;EACA,YX3CO;EW4CP,OX5DW;EW6DX,eXae;EWZf,YXyCc;;AWtChB;EACE;EAEA,OXtDI;;AW0DN;EACE,WXlCW;EWmCX,aXxBe;EWyBf,eXdS;EWeT,OXjEQ;;AWoEV;EACE,OXpEQ;EWqER,aX1BiB;EW2BjB,eXpBS;EWqBT,WX/CW;;AWmDb;EACE;EACA;EACA,KX9BS;EW+BT,eX7BS;EW8BT;;AAEA;EACE;EACA,YXjFI;EWkFJ,OXrFM;EWsFN,eXvBc;EWwBd,WXhES;EWiET,aXpDe;;AWyDnB;EACE;EACA;EACA,KXjDS;EWkDT,OX7GW;EW8GX,aX7DmB;EW8DnB,WX3EW;EW4EX,YXTc;;AWWd;EACE,KXvDO;EWwDP,OXnHW;;;AWyHjB;ETvEE,YFrCM;EEsCN,eFuBkB;EEtBlB,SFyDa;EExDb,YFiDgB;;AE/ChB;EAEE,YFhCQ;;AWgGZ;EAEE;;AAEA;EACE;EACA;EACA;ET9GF;EACA;EACA;ES8GE,WX3FW;EW6FX,OXvHI;EWwHJ,eX5De;;AW+DjB;EACE,WXnGW;EWoGX,aXzFe;EW0Ff,eX/ES;EWgFT,OXlIQ;;AWqIV;EACE,OXrIQ;EWsIR,aX3FiB;EW4FjB,eXrFS;EWsFT,WXhHW;;AWoHb;EACE;EACA;EACA;EACA,aX9FS;EW+FT;;AAEA;EACE,WXxHS;EWyHT,aX9GkB;EW+GlB,OXlKS;;AWqKX;EACE,WXnIS;EWoIT,OX3JM;EW4JN,aXxHe;;;AW8HrB;ET3HE,YFrCM;EEsCN,eFuBkB;EEtBlB,SFyDa;EExDb,YFiDgB;;AE/ChB;EAEE,YFhCQ;;AWoJZ;EAEE;EACA;EACA,KXpHW;;AWsHX;EACE;EACA;EACA;ETpKF;EACA;EACA;ESqKE,OX5KI;EW6KJ,eXlHe;EWmHf,WXpJW;;AWuJb;EACE;;AAEA;EACE,WX7JS;EW8JT,aXnJiB;EWoJjB,eXzIO;EW0IP,OX3LM;;AW8LR;EACE,OX9LM;EW+LN,aXpJe;EWqJf,WXxKS;;;AW8Kf;ET/JE,YFrCM;EEsCN,eFuBkB;EEtBlB,SFyDa;EExDb,YFiDgB;;AE/ChB;EAEE,YFhCQ;;AWwLZ;EAEE;;AAEA;EACE;EACA;EACA,KX5JS;EW6JT,MX7JS;EW8JT;EACA,OX5NW;EW6NX;EACA,aX5Ke;;AW+KjB;EACE;EACA;EACA,WX/La;EWgMb,aX7KgB;EW8KhB,OX3NQ;EW4NR,eXzKS;EW0KT;;AAGF;EACE;EACA;EACA,KXjLS;;AWmLT;EACE;EACA;EACA,eXtKiB;EWuKjB;;AAIA;EACE,aXxMe;EWyMf,OX/OI;EWgPJ;;AAGF;EACE,WX3NO;EW4NP,OXpPI;;;AW2PZ;ETpNE,YFrCM;EEsCN,eFuBkB;EEtBlB,SFyDa;EExDb,YFiDgB;;AE/ChB;EAEE,YFhCQ;;AW6OZ;EAEE;EACA;;AAEA;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EAEA,OXzQE;EW0QF,eX5Me;EW6Mf,WXtPS;EWuPT,aXxOa;EWyOb;;AAIJ;EACE,gBXlOS;EWmOT;EACA,eXpOS;;AWsOT;EACE,WX7PS;EW8PT,aXpPa;EWqPb,eX3OO;EW4OP,OX7RM;;AWgSR;EACE;EACA;EACA;EACA,KXpPO;;AWsPP;EACE,WX3QO;EW4QP,OXvSI;;AW0SN;EACE,WX5QQ;EW6QR,aXrQgB;;AWwQlB;EACE,WXvRS;EWwRT,OXjTI;;AWsTV;EACE;EACA,eXtQS;;AWwQT;EACE;EACA,OX5TM;EW6TN,WXrSS;EWsST;EACA;EACA,KXhRO;;AWkRP;EACE;EACA,OX3UO;EW4UP,aX/RW;;AWkSb;EACE;;AAEA;EACE;EACA,OX5UG;;AWkVX;EACE;;;AChVJ;EACE,WZ+DoB;EY9DpB;EACA;;AVhBE;EUaJ;IAMI;;;;AAOJ;EACE,kBAxBgB;EAyBhB,eZ0CiB;EYzCjB;;AV7BE;EU0BJ;IAMI,SZ2BS;;;;AYpBb;EACE,kBArCgB;EAsChB,eZ6BiB;EY5BjB;EACA;;AV3CE;EUuCJ;IASI;IACA;IACA;IACA;IACA;;;AAGF;EACE;EACA,OZjDQ;EYkDR;;AV1DA;EUuDF;IAMI;IACA;IACA;IACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA,eZbS;;AYeT;EACE;EACA,aZ9Ba;EY+Bb;EACA;;AAGF;EACE;;AVnFF;EUoEF;IAmBI;IACA;IACA;IACA;;EAEA;IACE;IACA;IACA;;;AAKN;EACE;EACA,aZ1DkB;EY2DlB;;AVvGA;EUoGF;IAMI;IACA;IACA;IACA;;;;AAQN;EACE;EACA;EACA;EACA;EACA;EACA,aZhGoB;EYiGpB;EACA,aZ9EiB;EY+EjB,OZnHM;EYoHN;;AAEA;EACE;;AAGF;EAEE,kBAnIkB;;AAsIpB;EACE;;AAGF;EAEE;;AAKA;EACE;EACA;;AAGF;EAEE;EACA;;AAGF;EAEE;EACA;;;AAQN;EACE;EACA;EACA,KZrHW;EYsHX;EACA,eZzGiB;EY0GjB,WZhJa;EYiJb,aZpIqB;;AYsIrB;EACE;EACA;EACA,eZ7GmB;EY8GnB;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;;AAOJ;EACE;EACA;;AAEA;EACE;EACA;;AV/MA;EU6MF;IAKI;IACA;;;AAIJ;EACE;EACA;;;AAIJ;EACE;EACA;EACA;EACA,eZ3JiB;EY4JjB;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA,eZtKiB;EYuKjB;EACA;;AAEA;EACE;;;AAOJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA,eZvMS;EYwMT;;AAGF;EACE,aZ5OkB;EY6OlB,WZnOW;EYoOX,aZ1Ne;EY2Nf,OZlQQ;EYmQR;;AAGF;EACE,aZpPkB;EYqPlB,WZ9Oa;EY+Ob,OA3QoB;EA4QpB;;;AAOJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA,eZ1NiB;EY2NjB,aZtQoB;EYuQpB;EACA,aZpPiB;EYqPjB;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE,kBA7SkB;EA8SlB,OZvSI;;AYySJ;EACE;;AAIJ;EACE;EACA;;AAEA;EACE;;AAIJ;EACE,kBA9ToB;EA+TpB,OZzTI;;AY2TJ;EACE;;AVvUF;EUyRJ;IAmDI;IACA;IACA;IACA,WZ7Sa;;;;AYoTjB;EACE;EACA;EACA,KZjSW;EYkSX;EACA,YZhVM;EYiVN;EACA,eZzRiB;EY0RjB,WZ7Ta;EY8Tb,OZvVU;EYwVV;EACA,YZ7PgB;;AY+PhB;EACE;EACA;;AAGF;EACE,YZ5WW;EY6WX,cZ7WW;EY8WX,OZhWI;;AYmWN;EAxBF;IAyBI;IACA;;;;AAOJ;EACE,YZ7WM;EY8WN,eZnTiB;EYoTjB,SZ3TY;EY4TZ,eZhUW;EYiUX,YZtWU;;AYwWV;EAPF;IAQI,SZnUS;;;AYsUX;EACE,WZ9VW;EY+VX,aZrVe;EYsVf,OZ7XQ;EY8XR,eZ3US;EY4UT,gBZ7US;;;AYoVb;EACE;EACA;EACA,KZtVW;;AYwVX;EALF;IAMI;;;;AAIJ;EACE;EACA;EACA,KZlWW;;AYoWX;EACE;;AAGF;EACE,WZjYW;EYkYX,aZrXmB;EYsXnB,OZ3ZQ;;AY8ZV;EACE,WZtYa;EYuYb,OZjaQ;EYkaR;;;AAOJ;EACE;EACA;EACA,KZ3XW;;;AY8Xb;EACE;EACA;EACA,kBZ7aS;EY8aT,OZ7be;EY8bf,eZxXiB;EYyXjB,WZ5Za;EY6Zb,aZjaiB;;;AYuanB;EACE;EACA;EACA,kBZ3bQ;EY4bR;EACA,eZrYiB;EYsYjB,aZ7aiB;EY8ajB,WZ1aa;EY2ab,OZ9ce;;;AYodjB;EACE;EACA;EACA,KZ3ZW;;AY6ZX;EALF;IAMI;IACA;;;;AAOJ;EACE;EACA;EACA,KZ1aW;;;AY6ab;EACE;EACA;EACA,KZ/aW;EYgbX;EACA,YZheM;EYieN;;AAEA;EACE,cZlfW;EYmfX,YZ3dQ;;AY8dV;EAbF;IAcI,SZ1bS;IY2bT,KZ5bS;;;AY+bX;EACE;EACA;EACA,eZvbe;EYwbf,WZ3dW;EY4dX,aZ9ce;EY+cf,aZjee;EYkef;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE,kBZzgBQ;EY0gBR,OZhhBM;;AYohBV;EACE;EACA,WZ5fa;EY6fb,aZlfiB;EYmfjB,OZxhBQ;EYyhBR;;AAGF;EACE;EACA,eZhee;EYief,WZtgBW;EYugBX,aZ1fmB;;AY4fnB;EACE;EACA;;AAGF;EACE;EACA;;;AAQN;EACE;EACA;EACA,KZlgBW;EYmgBX;EACA,YZ/fY;EYggBZ,aZhgBY;EYigBZ;;AAEA;EATF;IAUI;IACA;;;AAIA;EADF;IAEI;;;;AC3kBN;EACE,eb4DW;;Aa1DX;EACE;EACA,Wb8BW;Ea7BX,ab0CmB;EazCnB,ObGQ;EaFR,ebmDS;;AajDT;EACE,ObRU;EaSV;;AAIJ;EACE,WbiBW;Eah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CE,WdhRS;EciRT,adxQiB;EcyQjB,ed9PO;Ec+PP,OdhTM;;AcmTR;EACE,OdnTM;EcoTN,Wd3RW;;;AepCjB;EACE;EACA;EACA,KfwDW;EevDX;EACA,Wf8Ba;;Ae5Bb;EACE;EACA;EACA,KfiDS;EehDT;;AAEA;EACE,OfHM;EeIN,YfuFY;;AerFZ;EACE,OfnBO;;AeuBX;EACE,OfbM;EecN,afuBe;;AepBjB;EACE;EACA,OfjBO;EekBP,af6BO;;;AevBb;EACE;EACA,KfoBW;EenBX;EACA,efsBW;;AepBX;EACE;;AAGF;EACE;EACA;EACA,KfUS;EeTT;EACA,OfxCQ;EeyCR,afLiB;EeMjB;EACA;EACA,Yf+Cc;;Ae7Cd;EACE,OfhDM;;AemDR;EACE,Of/DS;EegET,qBfhES;;AemEX;EACE;EACA,YftDI;EeuDJ,Of1DM;Ee2DN,efKe;EeJf;EACA,afxBiB;;;Ae8BvB;EACE;EACA,KfrBW;EesBX,SfvBW;EewBX,YfpEQ;EeqER,efXiB;;AeajB;EACE;EACA,Of5EQ;Ee6ER,afzCiB;Ee0CjB,eflBe;EemBf,YfYc;;AeVd;EACE,OfnFM;EeoFN;;AAGF;EACE,YfrFE;EesFF,OfpGS;EeqGT,Yf7EM;;;AeoFV;EACE,ef9CS;;AegDT;EACE,Wf7ES;Ee8ET,afhEiB;EeiEjB;EACA,OfvGM;EewGN;EACA;;AAIJ;EACE;;AAEA;EACE,eflEO;;AeqET;EACE;EACA;EACA,KftEO;EeuEP;EACA,Of1HM;Ee2HN,ef9Da;Ee+Db,YfhCY;;AekCZ;EACE;EACA;EbxHN;EACA;EACA;EawHM,WfvGO;EewGP,OflII;;AeqIN;EACE,YfnIE;EeoIF,OfnJO;;AeqJP;EACE,OftJK;;Ae0JT;EACE,Yf3IG;Ee4IH,Of5JO;Ee6JP,af7Ga;;Ae+Gb;EACE,OfhKK;;AesKX;EACE,afvGQ;EewGR,Yf7GO;Ee8GP;;AAEA;EACE,WfxIO;EeyIP;;;AAOR;EACE;EACA;EACA,Kf5HW;;AegIP;EACE;EACA;EACA;;AAKF;EACE,YfpMO;EeqMP,OfvLA;EewLA,cftMO;;Ae2Mb;EACE;EACA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1He;;AsB4Hf;EARF;IASI;;;AAIJ;EACE;EACA;EACA,WtB/IW;EsBgJX,OtB1KM;EsB2KN,atBtIe;;AsBwIf;EAPF;IAQI,WtBrJO;;;AsBwJT;EACE;EACA;EACA;EACA;EACA,KtBrIK;EsBsIL,YtB3FU;;AsB6FV;EACE,OtBrMK;;AsByMT;EACE;EACA;EACA,atBhJK;EsBiJL;;AAEA;EACE;EACA;;AAKN;EACE;EACA;EACA;EACA,WtBtLS;EsBuLT,OtB9MO;;AsBgNP;EAPF;IAQI;IACA,WtB5LO;IsB6LP,atBrKK;;;AsB2KX;EACE;IACE;IACA,OtB7NM;IsB8NN,ctB/KO;;;;AsBqLb;EACE;EACA;EACA,YtBrOM;EsBsON,etB3KiB;;AsB6KjB;EACE,etBzLS;;AsB2LT;EACE;EACA;;AAEA;EAJF;IAKI;;;AAKN;EACE,WtB/Na;EsBgOb,OtBzPQ;;AsB2PR;EAJF;IAKI,WtBpOS;;;;AsB6Of;EACE;EACA;;AAEA;EACE;EACA,etBxNS;;AsB4NX;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EARF;IASI;;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAKJ;EACE;EACA;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EAbF;IAcI;IACA;IACA;IACA;IACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAVF;IAWI;IACA;;EAEA;IACE;IACA;IACA;IACA;IACA;;;AAIJ;EACE;EACA;;AAEA;EACE;IACE;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAIF;EACE;EACA;EACA;EACA;;AAEA;EANF;IAOI;;;AAON;EADF;IAEI;;;;AAaN;EADF;IAEI;IACA;IACA;;EAEA;IACE;;EAGF;IACE;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;EAEA;IACE;;;;AAYF;EACE;;AAGF;EACE;EACA;EACA;;AAEA;EALF;IAMI;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAKN;EACE;;AAEA;EACE;EACA;EACA;;AAIJ;EACE;;AAGF;EACE;;AAGF;EACE;;;AAWV;EACE;EACA,YtBhgBM;EsBigBN,etBtciB;EsBucjB;EACA;EACA;;AAEA;EACE;EACA,etBvdS;;AsB2dX;EACE;EACA;EACA;EACA;EACA;;AAEA;EAPF;IAQI;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA,atB1fiB;EsB2fjB,OtB9hBE;EsB+hBF;EACA;;AAKJ;EACE;EACA;;AAIF;EACE;EACA;EACA;EACA;EACA,YtBtdc;EsBudd;;AAEA;EACE;;AAIF;EACE;;AAGF;EACE,kBtB7jBE;;AsBgkBJ;EACE;;AAGF;EAzBF;IA0BI;IACA;IACA,KtBzhBO;IsB0hBP,StBzhBO;;;AsB4hBT;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAVF;IAWI,WtBhkBO;IsBikBP;;EAEA;IACE;IACA,atBxjBa;IsByjBb,OtB9lBE;IsB+lBF,ctB/iBG;IsBgjBH;IACA;;;AAKJ;EACE;;AAEA;EACE;IACE;;;AAIJ;EACE;EACA;EACA,KtBlkBG;EsBmkBH;EACA;EACA,YtB1hBQ;;AsB4hBR;EACE,OtBpoBG;;AsBwoBL;EACE;EACA,atB1lBS;EsB2lBT,OtB/nBA;EsBgoBA;;AAEA;EANF;IAOI;;;AAKN;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAOJ;EADF;IAEI;;;AAMN;EACE;EACA;EACA;EACA;EACA;;AAEA;EAPF;IAQI;IACA;IACA,atBxnBK;;;;AsB+nBb;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,atBrpBoB;EsBspBpB;EACA;EACA;EACA;EACA,YtBlmBgB;;AsBomBhB;EACE;EACA,YtBrrBQ;;AsBwrBV;EACE;;AAIF;EACE;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;EACA;;AAEA;EACE;;;AAQN;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,YtB3qBc;;AsB6qBd;EACE,OtBrxBS;;AsBwxBX;EACE;EACA;;AAGF;EACE,WtBzvBW;;AsB6vBf;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,YtB5sBc;;AsB8sBd;EACE,OtBtzBS;;AsByzBX;EACE,atBxwBa;EsBywBb;;;AAMN;EACE;EACA;EACA;EACA,etBvwBW;EsBwwBX;EACA,KtBzwBW;;AsB2wBX;EARF;IASI;IACA;IACA,KtB/wBS;;;AsBkxBX;EACE,WtB3yBa;EsB4yBb,OtBr0BQ;EsBs0BR;;AAEA;EACE,OtBr1BS;EsBs1BT,atBryBiB;EsBsyBjB,WtBhzBS;;AsBmzBX;EAXF;IAYI;IACA;;;AAIJ;EACE;EACA,KtBtyBS;;AsBwyBT;EAJF;IAKI;;;AAGF;EACE;EACA;EACA;EACA;EACA,etBryBa;EsBsyBb,WtB10BS;EsB20BT,YtBxwBY;;AsB0wBZ;EATF;IAUI;;;AAGF;EACE;EACA,ctBv3BO;EsBw3BP;;AAGF;EACE,OtB/2BK;;;AsBm5Bb;EAGE;IACE;IACA;IACA;IACA;IACA,YtB15BI;IsB25BJ;IACA;IACA;;EAEA;IACE;IACA;IACA;IACA;IACA;IACA;IACA;;EAEA;IACE;IACA;;EAGF;IACE;;EAIJ;IACE;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;EAEA;IACE;IACA;IACA;;EAMN;IACE;IACA;IACA;IACA;IACA;;EAEA;IACE;IACA;IACA;IACA,OtBj9BE;IsBk9BF;IACA;IACA;;EAEA;IACE;IACA,atBx7BW;IsBy7BX;;EAIJ;IACE;;EAKJ;IACE;IACA;;EAGA;IACE;;EAIF;IACE;IACA;;EAIF;IACE;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;EAEA;IACE;;EAIF;IACE;;EAIF;IACE;IACA;IACA;IACA;IACA,atB/+Bc;IsBg/Bd;;EAEA;IACE;;EAGF;IACE;IACA;IACA;IACA;IACA;;EAEA;IACE;;EAGF;IACE;IACA;IACA;IACA;;EAEA;IACE;IACA;;EAOR;IACE;IACA;IACA;IACA;IACA,atBrhCc;IsBshCd;IACA;;EAEA;IACE;;EAOR;IACE;IACA;;EAEA;IACE;IACA;;EAEA;AAAA;IAEE;IACA;IACA;;EAIJ;IACE;;EAGF;IACE;IACA;IACA;IACA;IACA,atB1jCgB;;EsB4jChB;IACE,atB1jCW;IsB2jCX;;;ACtmCJ;EADF;IAEI;IACA;;;;AASN;EACE;EACA;EACA;EACA,evB0CY;;AuBxCZ;EANF;IAOI;;;AAGF;EACE;EACA;EACA;EACA,YvBuEc;;AuBpEZ;EACE;EACA;;AAEA;AAAA;EAEE;;AAGF;EACE;;AAOR;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,YvByCc;;AuBvCd;EAZF;IAaI;IACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EAPF;IAQI;IACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAEA;EAPF;IAQI;IACA;;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAMN;EACE;EACA;;AAEA;EAJF;IAKI;;;AAGF;EACE;EACA;EACA;;AAEA;EALF;IAMI;;;AAIF;EACE;;AAEA;EACE;;AAIJ;AAAA;EAEE;EACA;;AAGF;EACE;;AAGF;EACE,OvBvJO;EuBwJP;;AAEA;EACE,OvB1JO;;AuB8JX;EACE,YvBjJE;EuBkJF;EACA,evB3FW;EuB4FX,avBnIW;EuBoIX;EACA,OvBrKO;;AuBwKT;EACE,YvB1JE;EuB2JF;EACA,evBnGW;EuBoGX,SvB/GK;EuBgHL;EACA;;AAEA;EACE;EACA;EACA,OvBxKE;;;AuBgLZ;EACE;EACA,YvB9KQ;EuB+KR;;AAEA;EALF;IAMI;;;;AAIJ;EACE,WvBtHoB;EuBuHpB;;;AAGF;EACE,evB1IY;;AuB4IZ;EAHF;IAII,evB9IU;;;AuBiJZ;EACE,WvBxKY;EuByKZ,avBjKe;EuBkKf,OvBzMQ;EuB0MR,evBxJS;EuB0JT;EACA;EACA;;AAEA;EAVF;IAWI,WvBnLU;;;AuBuLd;EACE,WvB5La;EuB6Lb,OvBtNQ;EuBuNR,avB5KiB;;AuB8KjB;EALF;IAMI,WvBlMS;;;;AuBwMf;EACE;IACE;IACA;;EAGF;IACE;IACA;;;AAKJ;EACE,YvB5OM;EuB6ON,evBlLiB;EuBmLjB,YvBpOU;EuBqOV;;AAEA;EACE;;AAEA;EACE;;AAIA;EACE;EACA,OvBzQO;;AuB2QP;EACE;;AAMR;EACE;EACA;EACA;EACA;EACA,WvBlPa;EuBmPb,avBxOiB;EuByOjB,OvB9QQ;EuB+QR;EACA,YvBpLc;;AuBsLd;EAXF;IAYI;IACA,WvB3PS;;;AuB8PX;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA,OvBjSM;EuBkSN,YvBvMY;;AuB2MhB;EACE;EACA;EACA,WvBjRW;EuBkRX,OvB1SQ;EuB2SR,avBhQiB;EuBiQjB;;AAEA;EARF;IASI;;;AAGF;EACE;EACA;;;AChUN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA,WxBQY;EwBPZ,axBee;EwBdf,OxBzBQ;EwB0BR;EACA;EACA;;AtBpCA;EsBHJ;IA4CI;IACA,exBkBS;;EwBhBT;IACE;;EAGF;IACE,WxBZS;;;;AyBvCf;EACE;EACA;EACA,KzByDW;EyBxDX;EACA,ezBkEiB;EyBjEjB,ezBwDW;;AyBtDX;EACE;EACA;;AAGF;EACE;EACA;;AAEA;EACE,azB8BiB;;AyBzBrB;EACE;EACA;EACA;;AAIF;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA,WzBPS;EyBQT,azBIe;;AyBCnB;EACE;EACA;EACA;;AAIF;EACE;EACA;EACA,OzB1Da;;;A0BNjB;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAZF;IAaI;IACA;IACA;IACA;IACA;;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAEA;EACA;EACA;;AAEA;EACE;EACA;;AApBJ;EAwBE;;AAEA;EAAa;;AACb;EAAa;;AACb;EAAa;;AACb;EAAa;;AAEb;EA/BF;IAgCI;;;AAIF;EApCF;IAqCI;IACA;IACA;IACA;IACA;;;;AChDJ;EACE;EACA;EACA;EACA,KAXe;EAYf;;AAGA;AAAA;AAAA;AAAA;EAIE;EACA;EACA;EACA,OArBmB;EAsBnB,QAtBmB;EAuBnB,OArBqB;EAsBrB;EACA;EACA,Y3B2Ec;;A2BzEd;AAAA;AAAA;AAAA;EACE,O3BhCS;;A2BmCX;AAAA;AAAA;AAAA;EACE,OA7BqB;EA8BrB;EACA;;AAIF;AAAA;AAAA;AAAA;EACE,OAxCiB;EAyCjB,QAzCiB;EA0CjB;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA,a3B1BkB;E2B2BlB,WArDmB;EAsDnB,a3BXkB;E2BYlB,OArDuB;EAsDvB;EACA;EACA;EACA;EACA,Y3BwCc;;A2BtCd;EACE,O3BnES;;A2BuEX;EACE,a3BtBa;E2BuBb,OApEmB;EAqEnB;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAKJ;EACE;EACA;EACA;EACA,Y3BnCW;;;A4B5Db;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA,W5BkEoB;E4BjEpB;EACA;EACA;;AAEA;EATF;IAUI;IACA;;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA,Y5BmEY;;A4BhEd;EACE,M5BzCS;;A4B8Cb;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EAdF;IAeI;IACA;;EAEA;IACE;IACA;;;AAMN;EACE,a5B3CkB;E4B4ClB;EACA,a5B5BkB;E4B6BlB;EACA;EACA;EACA;EACA,Y5BwBc;;A4BtBd;EACE,O5BnFS;;A4BuFX;EACE,a5BtCa;E4BuCb;;AAEA;EACE;EACA;;AAIJ;EAzBF;IA0BI;;;;AAMN;EACE;EACA;EACA;;AAEA;EALF;IAMI;;;AAGF;EACE;EACA;EACA;EACA,W5BrCkB;E4BsClB;EACA;EACA;EACA;;AAEA;EAVF;IAWI;IACA;;;AAIJ;EACE;EACA;EACA;;AAEA;EALF;IAMI;;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAVF;IAWI;IACA;;;AAIJ;EACE,a5B9HgB;E4B+HhB;EACA,a5B/GgB;E4BgHhB;EACA;EACA;EACA;EACA,Y5B3DY;;A4B6DZ;EACE,O5BtKO;;A4ByKT;EAdF;IAeI;;;AAKJ;EACE,a5B9Ha;E4B+Hb;EACA;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,Y5B/FU;;A4BkGZ;EACE;;;AC9MR;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;;ACtCJ;EACE;EACA;EACA;EACA;EACA;;;AAIF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;;AAGF;EACE;EACA;;;AAIJ;EACE;;;AAIF;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAIF;EACE;EACA;;AAEA;EACE;;AAGF;EACE;EACA;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAKJ;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAGA;EACE;EACA;EACA;EACA;EACA;EACA;;;AAIJ;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAlBF;IAmBI;;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AC3QF;EACE;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;;ACEJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,OAjBgB;EAkBhB;;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,OA9BgB;EA+BhB;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE,YApCc;EAqCd,OAzCc;;;AA8ClB;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA,OA3Dc;;AA8DhB;EACE;EACA;EACA,OAhEc;;;AAoElB;EACE;;;AAIF;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA,YApFc;EAqFd;EACA;EACA;EACA;;AAEA;EAAc,YAjGA;;AAoGhB;EACE;EACA;EACA,OAlGc;;AAoGd;EAAc,OArGA;;;AA0GlB;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA,OA7HY;;AAgId;EACE;EACA;EACA;EACA,OAnIY;EAoIZ;;AAGF;EAAU,cA5II;;AA8Id;EACE;EACA,YA9IY;;AAgJZ;EAAiB,OA/IL;;;AAqJlB;EACE;EACA;EACA,YAnJgB;EAoJhB;EACA;EACA;EACA;EACA,OA3JgB;EA4JhB;;;AAIF;EACE;EACA;EACA;EACA;EACA,YA1KgB;EA2KhB;EACA;EACA;EACA;EACA;EACA;;AAEA;EAAU,YAjLM;;AAmLhB;EACE,YA7Kc;EA8Kd,OA/Kc;EAgLd;;;AAKJ;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAAiB,cA3MH;;AA8MhB;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,OArNc;;AAuNd;EACE,OArNY;EAsNZ;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA,OApOc;EAqOd;EACA;EACA;EACA;EACA;;AAEA;EAAU,YAvOI;;AAwOd;EAAa;EAAc;;;AAK/B;EACE;EACA;EACA;EACA;EACA;EACA;EACA,YApPgB;EAqPhB;EACA;;AAEA;EACE;EACA;EACA,OA9Pc;;AAiQhB;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA,OAhRc;EAiRd;;AAEA;EAAe,OA1QD;;AA2Qd;EAAe,OA/QD;;AAkRhB;EACE;EACA;EACA,OArRc;;;AA0RlB;EACE;EACA,YA3RgB;EA4RhB;EACA;EACA;;AAEA;EACE;EACA;EACA,YAzSc;EA0Sd;;AAEA;EAAe,YAnSD;;;AAwSlB;EACE;EACA;EACA;EACA;EACA;EACA,OAlTgB;;AAoThB;EAAa,OAhTG;;;AAoTlB;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;EACA,OA3UgB;;;AA8UlB;EACE;IAAY;;EACZ;IAA4B;;EAC5B;IAAY;;EACZ;IAAe;;;ACxVjB;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;A/BPE;E+BCJ;IASI;;;A/BZA;E+BGJ;IAaI;IACA;;;;AAIJ;EACE;EACA;EACA;;A/BpBE;E+BiBJ;IAMI;IACA;;;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAIF;EACE;;AAGF;EACE;;AAGF;EACE;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;A/BtEE;E+B6DJ;IAYI;;;A/B3EA;E+B+DJ;IAgBI;IACA;IACA;IACA;IACA;;;A/BrFA;E+BiEJ;IAwBI;IACA;IACA;IACA;IACA;IACA;;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;A/BtGE;E+BgGJ;IASI;IACA;;;A/B5GA;E+BkGJ;IAcI;IACA;IACA;IACA;IACA;;;;AAKF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;A/B9HA;E+BuHF;IAUI;;;A/BnIF;E+ByHF;IAcI;IACA;IACA;IACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;;A/BjJA;E+B4IF;IAQI;;;A/BtJF;E+B8IF;IAYI;IACA;IACA;IACA;;;;AAKN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;;A/BvLA;E+BgKJ;IA2BI;;;A/B7LA;E+BkKJ;IAgCI;IACA;IACA;IACA;IACA;;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;A/B7ME;E+BsMJ;IAUI;IACA;;;A/BnNA;E+BwMJ;IAeI;IACA;;;A/B1NA;E+B0MJ;IAqBI;IACA;IACA;IACA;;;AAGF;EACE;EACA;EACA;;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;;AAGF;EACE;EACA;;A/BpQA;E+ByOJ;IAgCI;;;;AAIJ;EACE;;AAEA;EACE;;A/BrRA;E+BiRJ;IAQI;;;A/BvRA;E+B+QJ;IAYI;;;A/BzRA;E+B6QJ;IAkBI;;;;AAIJ;EACE;;A/BxSE;E+BuSJ;IAII;;;A/BzSA;E+BqSJ;IAQI;;;A/B3SA;E+BmSJ;IAcI;;;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;A/BlUE;E+B0TJ;IAYI;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;;A/BlWA;E+BiVJ;IAuBI;IACA;IACA;IACA;IACA;;EAEA;IAEE;IACA;IACA;IACA;IACA;;;;AAMN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;EACA;;A/BjZA;E+B0XJ;IA4BI;;;;AASJ;AACA;EACE;EACA;EACA;EACA;EACA;EACA;;A/BtaE;E+BgaJ;IASI;IACA;;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;A/BpbE;E+B8aJ;IASI;;;A/BrbA;E+B4aJ;IAaI;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,YACE;EAIF;;A/BtdE;E+BucJ;IAkBI;IACA;IACA;IACA;;;A/B9dA;E+BycJ;IAyBI;IACA;IACA;;;;AAIJ;EACE;;A/BzeE;E+BweJ;IAII;IACA;;;;AAIJ;EACE;;A/BhfE;E+B+eJ;IAII;IACA;;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;A/BhgBE;E+B0fJ;IASI;;;;AAIJ;EACE;EACA;EACA;EACA;;A/BzgBE;E+BqgBJ;IAOI;;;;AAIJ;EACE;EACA;EACA;EACA;;A/BphBE;E+BghBJ;IAOI;IACA;;;A/B1hBA;E+BkhBJ;IAYI;IACA;;;;AAIJ;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;;A/BjjBE;E+B6iBJ;IAOI;;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;;AAIJ;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;A/BnmBA;E+BolBJ;IAmBI;IACA;IACA;;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;A/B3nBA;E+B6mBJ;IAkBI;;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAEA;EACE;;AAIJ;EACE;;;AASJ;EACE;EACA;EACA;EACA;EACA,YjC7pBM;EiC8pBN;EACA;;A/BxqBE;E+BiqBJ;IAUI;IACA;;;A/B9qBA;E+BmqBJ;IAeI;IAEA;;;AAGF;EACE;EACA;AACA;EACA;EACA;EACA;EACA;EACA;AACA;;AAGF;EACE;EACA;EACA;EAEA;EACA;EACA;;A/B1sBA;E+BmsBF;IAWI;;;AAIJ;EACE;;AAIF;EACE;EACA;EACA;EACA;;A/BztBA;E+BqtBF;IAOI;;;A/B9tBF;E+ButBF;IAYI;IACA;IACA;IACA;IACA;IACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;A/BhvBA;E+B0uBF;IASI;;;A/BrvBF;E+B4uBF;IAcI;IACA;IACA;;;AAIJ;EACE,ajCtuBkB;EiCuuBlB;EACA;;A/BjwBA;E+B8vBF;IAMI;;;A/BtwBF;E+BgwBF;IAYI;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;A/B3xBA;E+B8wBF;IAgBI;IACA;;;A/BjyBF;E+BgxBF;IAsBI;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;;A/B3zBJ;E+BqzBA;IAWI;IACA;;;AAIJ;EACE;EACA;EACA;;AAEA;EACE;;AAKN;EACE;EACA;EACA;EACA;;A/Bl1BA;E+B80BF;IAWI;IACA;IACA;;;A/B71BF;E+Bg1BF;IAkBI;IACA;IACA;;;AAIJ;EAEE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGA;AAAA;EAEE;;A/Bj4BJ;E+Bs2BF;IAiCI;IACA;IACA;IACA;;;A/B54BF;E+Bw2BF;IAyCI;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;;AASJ;EACE;EACA;EACA;EACA;EACA;EACA;;A/B16BA;E+Bo6BF;IAUI;IACA;IACA;IACA;;;AAKJ;EACE,ajC55BkB;EiC65BlB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;A/Bl8BA;E+Bs7BF;IAgBI;IACA;IACA;;;AAOJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;A/B39BF;E+B+8BF;IAiBI;IACA;;;;AAaN;EACE;EACA;EACA;EACA;EACA;;A/Bn/BE;E+B8+BJ;IAQI;IACA;;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;;A/BngCA;E+BigCF;IAKI;IACA;IACA;IACA;;;AAIJ;EACE;EACA;EACA;EACA;;A/B/gCA;E+B2gCF;IAOI;IACA;IACA;;;A/BthCF;E+B6gCF;IAaI;IACA;IACA;IACA;IACA;;;AAIJ;EACE;EACA;;A/BliCA;E+BgiCF;IAKI;;;A/BviCF;E+BkiCF;IASI;;;AAIJ;EACE;EACA;EACA;EACA;;A/BjjCA;E+B6iCF;IAOI;IACA;IACA;;;A/BxjCF;E+B+iCF;IAaI;IACA;;;AAIJ;EACE;EACA;EACA;EACA;;A/BrkCA;E+BikCF;IAOI;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;A/BhlCA;E+B0kCF;IASI;;;A/BrlCF;E+B4kCF;IAaI;;;AAIJ;EACE;EACA;EACA;EACA;;A/B/lCA;E+B2lCF;IAOI;IACA;;;A/BrmCF;E+B6lCF;IAYI;IACA;IACA;IACA;IACA;;;AAIJ;EACE;EACA,KjCxjCS;;AEzDT;E+B+mCF;IAKI;IACA;;;A/BvnCF;E+BinCF;IAUI;IACA;IACA;IACA;;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAIA;EACE;EACA;EACA;;AAMF;EACE;EACA;EACA;;AAQN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;A/BhrCA;E+BsqCF;IAaI;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;;AAGF;EACE;EACA;;AAIJ;EACE;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;EACA;;AAGA;EACE;EACA;;AAGF;EACE;EACA;EACA;;A/BtuCF;E+BwtCF;IAkBI;IACA;IACA;IACA;;;A/B/uCF;E+B0tCF;IA0BI;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;EAEA;IACE;IACA;IACA;;EAGF;IACE;IACA;IACA;;;;AAsBR;AACA;EACE;EACA;;A/BhyCE;E+B8xCJ;IAKI;;;AAGF;EACE;EACA;;AAIA;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAIJ;EACE;EAEA;EACA;;AAhCJ;AAmCE;;AACA;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAKA;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AA1FN;AA8FE;;AACA;EACE;EACA;EACA;;AAEA;AAAA;AAAA;AAAA;EAIE;;AAGF;EACE;;AAIJ;EACE;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;;AAEA;EACE;;;AAUN;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AA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0/DA;IASI;;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;A/B7gEJ;E+B2gEE;IAKI;;;AAMR;EACE;EACA;EACA;EACA;EACA;;A/B3hEA;E+BshEF;IASI;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;A/BviEA;E+BiiEF;IASI;;;A/B5iEF;E+BmiEF;IAcI;IACA;IACA;IACA;IACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;;A/B/kEF;E+ByjEF;IA0BI;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;;AAIJ;EACE;;;AC3mEF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EATF;IAUI;IACA;IACA;;;AAZJ;AAiBE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;AAkCF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAIA;EACE;EACA;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAZF;IAaI;IACA;;;AAGF;EAjBF;IAkBI;IACA;IACA;IACA;IACA;IACA,YlC/HQ;IkCgIR,SlCzDY;IkC0DZ;IACA;IACA;;EAEA;IACE;;;;AAKN;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;AAOA;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA,OlC5ME;EkC6MF,YlClHQ;;AkCsHZ;EACE,OlC9NO;;AkCiOT;EACE;;AAGF;EACE,OlCtOO;;AkC4Ob;EACE,clChLS;EkCiLT;EACA;;AAGA;EACE;EACA;EACA;;AAEA;EACE;EACA,YlC9LK;EkC+LL,elC9LK;;AkCmMX;EACE;EACA,WlC7NW;EkC8NX,OlCtPQ;EkCuPR;EACA,elC7Le;EkC8Lf,YlC9Jc;EkC+Jd;EACA;EACA;EACA;;AAEA;EACE;EACA,clCjNO;EkCkNP,OlC9QS;EkC+QT;;AAGF;EACE;EACA,OlCzQM;EkC0QN;;AAEA;EACE;;AAIJ;EACE;EACA,OlC9RS;EkC+RT,alC/Oe;;AkCiPf;EACE;;AAIJ;EACE;EACA;EACA;EACA;;;AAMN;EACE;EAGA;EACA;EACA;EACA;;AAEA;EATF;IAUI;;;AAIF;EACE,WlCxRW;EkCyRX,OlC9SI;EkC+SJ;;AAEA;EACE,OlCpUS;EkCqUT,alCpRiB;;;AkC0RvB;EACE;;;AAIF;EACE;EACA;EACA;EACA,elCtRW;;AkCwRX;EANF;IAOI;IACA;IACA,KlC3RS;;;;AkCgSX;EACE,WlC3TW;EkC4TX,OlCpVQ;EkCqVR,alClTkB;EkCmTlB,elCvSS;;AkC0SX;EACE,WlC7TY;EkC8TZ,alCrTe;EkCsTf,OlC7VQ;;;AkCkWZ;EACE;EACA;;AAEA;EAJF;IAKI;;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA,YlC5Rc;;AkC8Rd;EACE;EACA;;AAGF;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;;AAQN;EACE;EACA;EACA;EACA;;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAGE;EACA;EACA;EACA;;AAEA;EACE;;AAGF;AAAA;AAAA;AAAA;EAEE;;AA1BN;AA8BE;AAAA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AA3DN;AA+DE;AAAA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAAU;;AAEV;EAAc;EAAqB;;AACnC;EAAmB;EAAqB;;AACxC;EAAgB;EAAqB;;AACrC;EAAc;EAAqB;;AAGrC;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;;AAMN;EACE;EACA;EACA,OlCzjBU;;AkC2jBV;EACE;EACA,elC3gBS;EkC4gBT;;AAGF;EACE,WlCtiBW;EkCuiBX,alC9hBmB;EkC+hBnB,OlCrkBQ;EkCskBR,elCphBS;;AkCuhBX;EACE,WlChjBa;EkCijBb,OlC1kBQ;;;AkC+kBZ;EACE;EACA;EACA;EACA,SlC5hBY;;AkC8hBZ;EACE;EACA;EACA;EACA,kBlCrmBW;EkCsmBX,elCzhBmB;EkC0hBnB;;;AAIJ;EACE;IACE;;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAbF;IAcI;;;AAGF;EACE;EACA;;;AAKJ;EACE;EACA;EACA,KlC9kBW;;;AkCklBb;EACE;EACA,KlCvlBW;EkCwlBX;EACA,elCtlBW;;AkCwlBX;EACE;EACA;EACA;EACA;EACA,WlCvnBa;EkCwnBb,alC7mBiB;EkC8mBjB,OlClpBQ;EkCmpBR;EACA,YlCzjBc;EkC0jBd;EACA;EAEA;EACA;;AAEA;EACE,OlCxqBS;EkCyqBT;;AAGF;EACE,OlC7qBS;EkC8qBT,alC7nBiB;EkC8nBjB,qBlC/qBS;;;AkCqrBf;EACE;EACA;EACA,KlCznBW;EkC0nBX;;AAEA;EACE;;;AAKJ;EACE;;AAEA;EACE;;;AAKJ;EACE;EACA;EACA;EACA;EACA,KlClpBW;EkCmpBX;EACA;EACA,YlCpsBM;EkCqsBN;EACA,elC3oBiB;EkC4oBjB,YlC7rBU;;AkC+rBV;EACE;EACA;;AAGF;EACE;EACA,WlCvrBW;EkCwrBX,alC7qBe;EkC8qBf,OlCrtBQ;;AkCwtBV;EACE;EACA,WlChsBa;EkCisBb,OlC1tBQ;;AkC6tBV;EACE,YlC9qBS;EkC+qBT;;;AAKJ;EACE;EACA,SlCprBW;EkCqrBX,YlCruBM;EkCsuBN;EACA,elC5qBiB;EkC6qBjB;;AAEA;EACE;EACA;EACA;EACA;EACA,elChsBS;;AkCmsBX;EACE;EACA,clCrsBS;EkCssBT,WlC9tBa;EkC+tBb,alCltBe;EkCmtBf,OlC1vBQ;EkC2vBR;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA,YlChxBS;EkCixBT;;AAIJ;EACE,WlClvBW;EkCmvBX,OlC3wBQ;;AkC8wBV;EACE;EACA;;AAEA;EACE;EACA;EACA,OlCpuBO;EkCquBP;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA,alCnxBkB;EkCoxBlB,WlC9wBW;EkC+wBX,OlCxyBQ;EkCyyBR,YlCryBM;EkCsyBN;EACA,elC9uBe;EkC+uBf;EACA;EACA;EACA;EACA,YlCntBc;;AkCqtBd;EACE,clCjzBO;EkCkzBP,YlCjzBE;;AkCozBJ;EACE;EACA,YlCtzBE;EkCuzBF,clCr0BS;EkCs0BT;;AAGF;EACE,OlC7zBO;EkC8zBP,YlC5zBI;EkC6zBJ;;AAIJ;EACE;EACA;EACA,WlC/yBW;EkCgzBX,alC7xBiB;EkC8xBjB,OlCz0BQ;EkC00BR,YlCt0BO;EkCu0BP;EACA;EACA,elClxBe;;AkCoxBf;EACE;EACA,OlC71BS;EkC81BT,alC5yBa;;A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CA,WlCjvCS;IkCkvCT;IACA;IACA;;EAGF;IACE;IACA,WlCzvCS;;EkC2vCT;IACE;IACA;;EAMN;IACE,KlC1uCS;;EkC8uCX;IACE,KlC9uCS;IkC+uCT;IACA;IACA;;EAIF;IACE;IACA;;EAIF;IACE;IACA;;EAGF;IACE;;;AC/zCJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EAPF;IAQI;;;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;;AAIJ;EACE,anC7BoB;EmC8BpB;EACA;EACA;EACA;EACA;;;AAGF;EACE,anCtCoB;EmCuCpB;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;;AAEA;EACE;EACA;;AAGE;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;;AAKN;EACE;EACA;EACA;EACA,anCjFkB;EmCkFlB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;;AAGF;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAKN;EACE,anC/IgB;EmCgJhB;EACA;EACA;EACA;EACA;EACA;EACA;;;AAKN;EACE;EACA;EACA;EACA,anC/JoB;EmCgKpB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;EACA;;;AAIJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA,anCvMkB;EmCwMlB;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;;AAGF;EACE;EACA;;AAGF;EACE;;AAGF;EACE;;;AAMN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAIJ;EACE;IACE;;;AjCvUA;EiC6UF;IAEE;IACA;IACA;IACA;IACA;;EAGF;IACE;IACA;;EAGF;IACE;;EAGF;IAEE;IACA;IACA;;EAGF;IAEE;IACA;IACA;IACA;IACA;;EAIA;IACE;;EAGF;IAEE;IACA;IACA;IACA;IACA;IACA;;EAEA;IAEE;IACA;;EAIJ;IAEE;IACA;IACA;IACA;IACA;;EAEA;IAEE;IACA;IACA;;EAGF;IAEE;IACA;IACA;;EAKN;IAEE;IACA;IACA;IACA;IACA;IACA;;EAGF;IAEE;;EAEA;IACE;IACA;IACA;;EAGF;IACE;IACA;;EAIJ;IACE;IACA;IACA;IACA;;;AC5bJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA,SpC8CW;;;AoC3Cb;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAVF;IAWI;IACA;;;;AAKJ;EACE;;;AAIF;EACE;;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA,apC9BkB;EoC+BlB;EACA;EACA;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE,OpC5ES;;AoC+EX;EACE,OpChFS;EoCiFT;EAEA;EACA;EACA;EACA;EACA;;AAGF;EApCF;IAqCI;IACA;;;;AAQN;EACE;EACA;EACA;EACA;EACA,apC3EoB;EoC4EpB;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;;AAKJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAKJ;EACE;;AAGF;EACE;EACA;EACA;EACA,apCrIkB;EoCsIlB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;;AAIJ;EACE;EACA;EACA,apC3KkB;EoC4KlB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;;AAMN;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA,apCnPgB;EoCoPhB;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;;;AAMN;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;AAAA;EAEE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;EAlCF;IAmCI;IACA;;EAEA;IACE;IACA;;EAGF;IACE;;;;AAMN;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAGA;EACE;EACA;;AAIF;EACE;EACA;;AAIF;EACE;EACA;EACA;EACA;EACA,apCxVkB;EoCyVlB;EACA;EACA;EACA;;AAEA;EAXF;IAYI;IACA;;;;AAMN;AAAA;EAEE;EACA;EACA,apC1WoB;EoC2WpB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;AAAA;EACE;EACA;;AAGF;AAAA;EACE;;AAGF;AAAA;EACE;EACA;;AAGF;EA/BF;AAAA;IAgCI;IACA;IACA;IACA;;;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;AAAA;EAEE;EACA;EACA;EACA,apC5ZkB;EoC6ZlB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAEA;EACE;;AAGF;EACE;;AAIJ;EACE;EACA;;AAEA;EAEE;;AAIF;EACE;;AAGF;EACE;EACA;;AAIJ;EA7DF;IA8DI;;EAEA;AAAA;IAEE;IACA;;;;AAMN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAKJ;EACE;IACE;;;AAIJ;EACE;IACE;IACA;;EAGF;IACE;IACA;;;AAKJ;EACE;IACE;IACA;IACA;;EAGF;IACE;;;AAOJ;EACE;EACA;EACA;EACA;;AAEA;EANF;IAOI;;;AAGF;EACE;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EANF;IAOI;;;AAIJ;EACE;EACA;EACA;;AAEA;EALF;IAMI;;;AAIJ;EACE,apCxjBgB;EoCyjBhB;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EAZF;IAaI;;;;AAOR;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EAPF;IAQI;IACA;;;AAIJ;EACE,apC9mBgB;EoC+mBhB;EACA;EACA;;AAEA;EANF;IAOI;;;AAIJ;EACE,apCznBgB;EoC0nBhB;EACA;EACA;;AAEA;EANF;IAOI;;;;AAOR;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;;AAON;EACE,apCrrBkB;EoCsrBlB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;EA5BF;IA6BI;IACA;;;;AAKN;EACE;IACE;IACA;;;AAIJ;EAEE;IAEE;IACA;;EAGF;IACE;IACA;;;AlCnwBA;EkC2wBF;IACE;IACA;;EAGF;IACE;;EAGF;IACE;IACA;;EAGF;IACE;;EAEA;IACE;IACA;;EAKF;IACE;;EAGF;IACE;IACA;;EAGF;IACE;IACA;;EAIJ;IACE;IACA;;EAEA;IACE;IACA;;EAGF;IACE;;EAKF;IACE;IACA;;EAIJ;AAAA;IAEE;IACA;IACA;IACA;;EAGF;IACE;IACA;IACA;;EAEA;AAAA;IAEE;IACA;IACA;;EAKJ;IACE;IACA;;EAEA;IACE;;EAEA;IACE;IACA;;EAGF;IACE;IACA;;EAGF;IACE;;EAMN;IACE;;EAEA;IACE;IACA;IACA;;EAEA;IACE;;EAGF;IACE;;EAMN;IACE;IACA;;EAEA;IACE;;;ACh5BN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EAPF;IAQI;;;;AAIJ;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE,arCtBkB;EqCuBlB;EACA;EACA;EACA;EACA;;;AAIJ;EACE,arChCoB;EqCiCpB;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE,arCtGkB;EqCuGlB;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE,arCjHkB;EqCkHlB;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAEA;EACE;;AAKF;EACE;EACA;;AAGF;EACE;EACA;;AAEA;EACE;;AAGF;EACE;EACA;EACA;EACA;;AAMJ;EACE;EACA;;AAGF;EACE;EACA;;AAEA;EACE;;AAGF;EACE;EACA;EACA;EACA;;;AAMR;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA,arCpMkB;EqCqMlB;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;EACA;;;AnC/OA;EmCqPF;IACE;IACA;IACA;IACA;IACA;IACA;;EAGF;IACE;IACA;;EAGF;IACE;;EAEA;IACE;;EAEA;IACE;IACA;;EAGF;IACE;;EAKN;IACE;IACA;IACA;IACA;IACA;;EAGF;IACE;IACA;IACA;IACA;;EAGF;IACE;IACA;IACA;IACA;IACA;IACA;;EAEA;IACE;IACA;IACA;IACA;;EAEA;IACE;IACA;;EAIJ;IACE;;EAGF;IACE;IACA;;EAGF;IACE;;EAGF;IACE;IACA;;EAIJ;IACE;IACA;;EAEA;IACE;;;AC9UN;EACE,YtC2DY;EsC1DZ,atCwDW;;AsCtDX;EACE;;;AAWJ;AAAA;EAEE;EACA;;AAGA;AAAA;EACE;EACA;EACA;EACA;;AAGF;AAAA;EACE;EACA;EACA;EACA;EACA;;AAGF;AAAA;EACE;;AAGF;AAAA;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;AAAA;EACE;EACA;EACA;EACA;;AAGF;AAAA;EACE;EACA;;AAIJ;AAAA;EACE;EACA;EACA;EACA;;AAEA;AAAA;EACE;EACA;;AAEA;AAAA;EACE;EACA;EACA;EACA;;AAGF;EAXF;AAAA;IAYI;IACA;;;AAKN;AAAA;EACE;EACA;EACA;;AAGF;AAAA;EACE;;AAGF;AAAA;EACE;EACA;;AAGF;AAAA;EACE;;AAGF;AAAA;EACE;;AAGF;AAAA;EACE;EACA;EACA;;AAGF;AAAA;EACE;EACA;EACA;;AAEA;AAAA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAIJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EAIE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;AAAA;AAAA;AAAA;EAEE;;AAGF;AAAA;EACE;EACA;EACA;EACA;;AAEA;AAAA;AAAA;AAAA;EAEE;EACA;EACA;;AAIJ;AAAA;EACE;EACA;;AAGF;AAAA;EACE;EACA;EACA;;AAGF;AAAA;AAAA;AAAA;EAEE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;AAAA;EACE;EACA;EACA;EACA;;AAEA;AAAA;EACE;;AAIJ;AAAA;EACE;EACA;EACA;EACA;EACA;;AAGF;AAAA;EACE;EACA;EACA;EACA;;AAGF;AAAA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;AAAA;EACE;EACA;;AAGF;AAAA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;AAAA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;AAAA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;AAAA;EACE;EACA;EACA;EACA;;AAGF;AAAA;EACE;EACA;EACA;EACA;;AAGF;AAAA;EACE;;AAEA;AAAA;EACE;;AAKN;AAAA;EACE;EACA;EACA;;AAEA;AAAA;EACE;EACA;EACA;EACA;;AAEA;AAAA;EACE;;;AClTR;EAEE;EACA;EACA;EACA;;ArCRE;EqCGJ;IAQI;IACA;IACA;;;ArCfA;EqCKJ;IAeI;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;;AAGF;EACE;EACA,avCce;EuCbf,OvC1BQ;EuC2BR;;ArCjCA;EqC6BF;IAOI;;;ArCtCF;EqC+BF;IAYI;IACA,avCCe;;;AuCGnB;EACE;EACA,avCNkB;EuCOlB;;ArCjDA;EqC8CF;IAMI;;;ArCtDF;EqCgDF;IAWI;;;;AAMN;EACE;EACA;EACA;;ArCpEE;EqCiEJ;IAOI;IACA;;;;AAIJ;EACE;EACA;EACA;;ArChFE;EqC6EJ;IAOI;IACA;;;;AAKJ;EACE;EACA;;AAEA;EACE;EACA;EACA,evCvBmB;EuCwBnB;;ArClGA;EqC0FJ;IAaI;IACA;IACA;IACA;IACA;IACA;;EAEA;IACE;IACA;IACA,evCvCiB;IuCwCjB;;;;AAMN;EACE;EACA;EACA;EACA;;;AAKF;EAEE;;ArCjIE;EqC+HJ;IAKI;;;AAIF;EACE;EACA;EACA;EACA;;ArC9IA;EqC0IF;IAOI;IACA;IACA,kBvCxIE;;;;AuCiJN;EACE;EACA;EACA;EACA,evC3Fe;EuC4Ff;EACA;EACA,OvC3JQ;EuC4JR;EACA;;AAEA;EACE;;AAGF;EACE;;ArC1KF;EqC0JF;IAoBI;;;AAIJ;EACE;EACA;EACA;EACA;EACA,evCpHe;EuCqHf,kBvC/KI;EuCgLJ;EACA,OvCpLQ;EuCqLR;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;ArCpMF;EqCkLF;IAsBI;IACA;;;;AAoBN;EACE;EACA;;AAEA;EACE;;ArClOA;EqC6NJ;IASI;IACA;;;ArCzOA;EqC+NJ;IAeI;IACA;;EAEA;IACE;IACA;IACA;IACA;IACA;IACA;;;;AAKN;EACE;EACA;EACA;EACA;EACA,OvCtPM;EuCuPN;EACA,evC9LiB;EuC+LjB;EACA,avCvNqB;EuCwNrB;EACA;EACA;;AAEA;EACE;;ArCzQA;EqC0PJ;IAmBI;IACA;IACA;;;ArCjRA;EqC4PJ;IA0BI;IACA;IACA;IACA;IACA;;;;AAKJ;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;;ArChTF;EqCySF;IAWI;IACA;;;ArCvTF;EqC2SF;IAiBI;IACA;;;AAGF;EACE;EACA;EACA;EACA;EACA,evChQa;EuCiQb,kBvC3TE;EuC4TF;EACA;EACA;EACA,OvClUM;;AERR;EqCgUA;IAcI;IACA;IACA;IACA;IACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA,avC/Sa;EuCgTb;EACA;EACA;EACA;EACA;EACA;;ArClWF;EqCoVA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CA;EACA;;AAEA;EACE;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAKJ;AAAA;EAEE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;AAAA;EACE;EACA;EACA;;AAGF;AAAA;EACE;EACA;;AAGF;AAAA;EACE;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAIJ;EACE;EACA,WvCrmCW;EuCsmCX,OvC7nCQ;EuC8nCR,YvC/kCS;;AuCilCT;EACE;EACA,OvCjoCO;;AuCqoCX;EACE;EACA,KvCtlCS;EuCulCT;;AAEA;EACE;EACA;EACA;;AAEA;EACE,cvCjmCK;;AuComCP;EACE,WvC5nCS;EuC6nCT,OvCvpCI;;AuC6pCV;EACE;EACA;EACA,KvC7mCS;;AuC+mCT;EACE;EACA;EACA;EACA,evCxmCa;EuCymCb;EACA;EACA;EACA,YvCtqCI;EuCuqCJ;EACA;EACA,YvCjlCY;;AuCmlCZ;EACE,cvC3rCO;EuC4rCP;;AAGF;EACE;EACA;EACA;EACA;EACA,KvCxoCK;EuCyoCL,OvCzrCI;EuC0rCJ;EACA,SvC1oCK;;AuC4oCL;EACE;;AAGF;EACE,WvC3qCK;EuC4qCL,avC/pCW;;AuCmqCf;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,evCrpCe;EuCspCf,OvCvtCI;EuCwtCJ;EACA,YvC9nCU;EuC+nCV;EACA;EACA;EACA,YvCjtCI;EuCktCJ;;AAEA;EACE;EACA;;AAGF;EACE,YvC9uCM;EuC+uCN,cvC/uCM;EuCgvCN,OvCtuCF;EuCuuCE;EACA,YvC7tCE;;AuCguCJ;EACE;;AAKN;EACE;;AAGF;EACE;EACA,YvCvvCE;EuCwvCF;EACA,evC/rCa;EuCgsCb,WvCpuCS;EuCquCT,avCztCe;EuC0tCf,OvC/vCM;EuCgwCN;EACA,YvCrqCY;;AuCuqCZ;EACE,YvC/wCO;EuCgxCP,cvChxCO;EuCixCP,OvCnwCA;;AuCswCF;EACE;;AAMN;EACE;EACA,KvCjuCS;EuCkuCT,evCjuCS;;AuCmuCT;EACE;;AAGF;EACE;EACA;EACA,KvC5uCO;EuC6uCP;EACA,YvCzyCS;EuC0yCT;EACA,evCnuCa;EuCouCb,OvC9xCE;EuC+xCF,WvCzwCS;EuC0wCT,avC9vCe;EuC+vCf;EACA,YvCzsCY;EuC0sCZ;;AAEA;EACE;;AAGF;EACE,YvCvzCS;EuCwzCT;EACA,YvCjyCI;;AuCoyCN;EACE;;AAKN;EACE,YvCvwCS;EuCwwCT;;AAEA;EACE,SvC3wCO;EuC4wCP,YvC1zCI;EuC2zCJ;;AAEA;EACE,WvCzyCO;EuC0yCP,OvCl0CI;;AuCo0CJ;EACE,OvCj1CK;EuCk1CL,avCjyCa;;AuCsyCnB;EACE;EACA;;AAEA;EACE;;AAGF;EACE,YvCj1CE;;AuCo1CJ;EACE,YvCn1CM;EuCo1CN;;AAEA;EACE,YvC31CG;;AuCg2CT;EACE;EACA;EACA;EACA;EACA,YvC3wCY;;AuC6wCZ;EACE;;AAGF;EACE,YvC12CE;;AuC62CJ;EACE,avC71CW;EuC81CX,WvC11CO;EuC21CP,OvCp3CI;EuCq3CJ,avCh1Ca;;AuCm1Cf;EACE;EACA;EACA;EACA;EACA;EACA;EACA,YvC53CA;EuC63CA;EACA,evCr0CW;EuCs0CX,OvCj4CI;EuCk4CJ;EACA,YvCxyCU;;AuC0yCV;EACE;EACA;;AAGF;EACE;;;AASV;EACE;EACA;EACA,YvCn2CY;EuCo2CZ;EACA;;AAEA;EAPF;IAQI;;;;ArCr6CA;EqCg7CJ;IAEI;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;EAEA;IACE;IACA;IACA;IACA;IACA;IACA,avCl5Ca;IuCm5Cb;IACA;;EAEA;IACE;IACA;IACA;;EAGF;IACE;IACA;IACA;;;;AAOR;EAEE;EACA;EACA;EACA;;ArC19CE;EqCq9CJ;IASI;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;;AAIF;EAEE;;ArC3+CA;EqCy+CF;IAMI;IACA;IACA;IACA;IACA;;;AAMA;EACE;EACA;;ArC3/CJ;EqCy/CE;IAMI;IACA;;;AAON;EACE;EACA,OvCxgDU;;AuC4gDd;EACE,evCl9CS;;AE5DT;EqC6gDF;IAKI;;;AALJ;AAQE;AAAA;AAAA;AAAA;AAKA;;AACA;EACE;EACA;EACA;EACA;EACA;EACA;;AApBJ;AAuBE;;AACA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AApCN;AAwCE;;AACA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAdJ;AAiBE;;AACA;EACE;EACA;EACA;EACA;EACA;AAEA;;AACA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAxBN;AA4BE;;AACA;EACE;EACA;;AAEA;EACE;AACA;;AAnCN;AAuCE;;AACA;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAnDJ;AAsDE;;AACA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;;AApFN;AAwFE;;AACA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;AACA;;AA1IN;AA8IE;;AACA;EACE;EACA;EACA;EACA;EACA;EACA;;AArJJ;AAwJE;;AACA;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAhKN;AAoKE;;AACA;EACE;EACA;EACA;EACA;EACA;AAEA;;AACA;EACE;EACA;EACA;;AAhLN;AAoLE;;AACA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;AAxMN;AA4ME;;AACA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;AAjON;AAqOE;;AACA;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;;AA9ON;AAkPE;;AACA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAjBJ;AAoBE;;AACA;EACE;EACA;EACA;EACA;;AAzBJ;AA4BE;;AACA;EACE;EACA;EACA;;AAEA;EACE;EACA;;AApCN;AAwCE;;AACA;EACE;EACA;EACA;EACA;EACA;EACA;;AA/CJ;AAkDE;;AACA;EACE;EACA;EACA;EACA;EACA;EACA;;AAzDJ;AA4DE;;AACA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AA7EN;AAiFE;;AACA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAlGN;AAsGE;;AACA;EACE;;AA3VN;AA+VE;;AACA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAxWN;AA4WE;;AACA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAhYN;AAoYE;AACA;;AACA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;;AAEA;EACE;;AAxaR;AA6aE;;AACA;EACE;EACA;EACA;EACA;EACA;AAEA;;AACA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AArcR;AA4cE;;AACA;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAvdN;AA2dE;;AACA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAIA;EACE;EACA;;AAGF;EACE;;AAGF;EACE;EACA;;AAGF;EACE;;AAKN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AA1jBR;AA+jBE;;AACA;EACE;EACA;EACA;EACA;EACA;EACA;;AAtkBJ;AAykBE;;AACA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAKN;EACE;;AAtnBJ;AAynBE;;AACA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAKJ;EACE;EACA;EACA;;AAEA;EACE;EACA;;AArqBN;AAyqBE;;AACA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAEA;EACE;EACA;;AAEA;EACE;;AALJ;AAQE;;AACA;EACE;;AAMR;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AA9vBR;AAmwBE;;AACA;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AA5xBR;AAiyBE;;AACA;AAAA;EAEE;EACA;EACA;EACA;;AAEA;AAAA;AAAA;AAAA;EAEE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;AAAA;AAAA;AAAA;EA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AAEA;EACE;EACA,avCrpFa;EuCspFb;EACA;;AAGF;EACE;EACA,avC5pFa;EuC6pFb;EACA;EACA;;AAIJ;EACE;EACA;;AAGF;EACE;;AAGF;EAjEF;IAmEI;IACA;IACA;IACA;IACA;;EAEA;IACE;;EAGF;IACE;IACA;IACA;;EAEA;IACE;;EAGF;IACE;;;;AAcN;EACE;EACA;EACA,KvCzsFS;EuC0sFT;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA,avC7uFgB;EuC8uFhB;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA,evC/tFiB;EuCguFjB;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;;AAIJ;EA3DF;IA4DI;IACA;;EAEA;IACE;IACA;;EAGF;IACE;IACA;;EAEA;IACE;IACA;;;;AAcV;AAAA;AAAA;EAGE;EACA;;AAGA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EAGE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAbF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;IAcI;;;AAKJ;AAAA;AAAA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAXF;AAAA;AAAA;IAYI;IACA;;;AAGF;EAhBF;AAAA;AAAA;IAiBI;;;AAIJ;AAAA;AAAA;EACE;EACA;EACA;;AAIF;AAAA;AAAA;EACE;EACA;EACA;EACA;EACA;;AAEA;EAPF;AAAA;AAAA;IAQI;;;AAGF;EAXF;AAAA;AAAA;IAYI;;;AAIA;AAAA;AAAA;EACE;EACA;EACA;;AAGF;AAAA;AAAA;EACE;EACA;;AAGF;AAAA;AAAA;EACE;;AAMN;AAAA;AAAA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;AAAA;AAAA;EACE;EACA;;AAEA;EAJF;AAAA;AAAA;IAKI;IACA;;;AAIJ;EAtBF;AAAA;AAAA;IAuBI;IACA;;;AAGF;EA3BF;AAAA;AAAA;IA4BI;IACA;;;AAKJ;AAAA;AAAA;EACE;EACA;;AAEA;EAJF;AAAA;AAAA;IAKI;IACA;;;AAGF;EATF;AAAA;AAAA;IAUI;IACA;;;AAKJ;AAAA;AAAA;EACE;EACA;EACA;EACA;;AAEA;EANF;AAAA;AAAA;IAOI;;;AAGF;EAVF;AAAA;AAAA;IAWI;;;AAKJ;AAAA;AAAA;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EARF;AAAA;AAAA;IASI;;;AAGF;EAZF;AAAA;AAAA;IAaI;;;AAKJ;AAAA;AAAA;EACE;EACA;EACA;EACA;EACA;;AAEA;EAPF;AAAA;AAAA;IAQI;IACA;;;AAGF;EAZF;AAAA;AAAA;IAaI;IACA;;;AAKJ;AAAA;AAAA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAIF;AAAA;AAAA;EACE;EACA;EACA;EACA;EACA;EACA;;AAIF;AAAA;AAAA;EACE;EACA;EACA;EACA;;AAEA;AAAA;AAAA;EACE;;AAKJ;AAAA;AAAA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;AAAA;AAAA;EACE;EACA;EACA;;AAIF;AAAA;AAAA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;AAAA;AAAA;EACE;EACA;;AAGF;AAAA;AAAA;EACE;;AAKJ;AAAA;AAAA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;AAAA;AAAA;EACE;EACA;;AAGF;AAAA;AAAA;EACE;;AAKJ;AAAA;AAAA;EACE;EACA;EACA;;AAGF;AAAA;AAAA;EACE;EACA;;AAIF;AAAA;AAAA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;AAAA;AAAA;EACE;EACA;;AAKJ;AAAA;AAAA;EACE;;AAEA;AAAA;AAAA;EACE;EACA;EACA;EACA;EACA;;AAGF;AAAA;AAAA;EACE;EACA;;AAGF;AAAA;AAAA;EACE;;AAGF;AAAA;AAAA;EACE;EACA;EACA;;AAIJ;AAAA;AAAA;EACE;EACA;EACA;EACA;EACA;;AAGF;AAAA;AAAA;EACE;EACA;EACA;EACA;EACA;;AAGF;AAAA;AAAA;EACE;EACA;EACA;EACA;EACA;;AAGF;AAAA;AAAA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;AAAA;AAAA;EACE;;AAIJ;AAAA;AAAA;EACE;EACA;EACA;EACA;EACA;;AAGF;AAAA;AAAA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAIF;AAAA;AAAA;EACE;EACA;EACA;EACA;EACA;;AAEA;AAAA;AAAA;EACE;;AAIJ;AAAA;AAAA;EACE;EACA;EACA;EACA;EACA;;AAGF;AAAA;AAAA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAIF;AAAA;AAAA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;AAAA;AAAA;EACE;;AAIF;AAAA;AAAA;EACE;EACA;;AAIF;AAAA;AAAA;EACE;EACA;;AAKJ;AAAA;AAAA;EACE;EACA;EACA;;AAGF;AAAA;AAAA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;AAAA;AAAA;EACE;;AAKJ;EACE;AAAA;AAAA;IACE;IACA;;EAGF;AAAA;AAAA;IACE;;EAGF;AAAA;AAAA;IACE;;EAGF;AAAA;AAAA;IACE;;EAGF;AAAA;AAAA;IACE;IACA;IACA;;EAGF;AAAA;AAAA;IACE;IACA;;EAGF;AAAA;AAAA;IACE;;;;AAUN;EACE;EACA;;AAGA;EACE;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EATF;IAUI;IACA;IACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;AACA;EACA;AACA;;AAEA;EACE;AACA;;AAIJ;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;;AAGF;EA1BF;IA2BI;IACA;IACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;;AAIJ;EAhCF;IAiCI;IACA;;EAEA;IACE;IACA;;;AAON;EACE;EACA;EACA;;AAIF;EACE;EACA;EACA;EACA;EACA;;AAEA;EAPF;IAQI;IACA;IACA;IACA;IACA;;;AAIJ;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAKN;EACE;EACA;EACA;EACA;;AAEA;EANF;IAOI;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAIJ;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EAVF;IAWI;;;AAKJ;EACE;EACA;EACA;EACA;;AAEA;EANF;IAOI;IACA;;;AAGF;EAXF;IAYI;IACA;;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAEE;EACA;EACA;EACA;;AAEA;EACE;;AAEA;EACE;;AAIJ;AAAA;AAAA;EAEE;;AAIJ;EACE;EACA;EACA;;AAEA;EACE;;AAEA;EACE;;AAIJ;AAAA;EAEE;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAKJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAKJ;EA5JF;IA6JI;;EAEA;IACE;IACA;;EAGF;IACE;IACA;;EAGF;IACE;IACA;IACA;IACA;;EAGF;IACE;IACA;IACA;IACA;IACA;;EAGF;IACE;IACA;IACA;IACA;;;AAMN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAIJ;EACE;EACA;;AAEA;EACE;EACA;;AAMF;EACE;EACA;;AAEA;EACE;EACA;;AAMN;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAIJ;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAKJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;EA9CF;IA+CI;;EAEA;AAAA;IAEE;;;AAOG;EACP;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAIJ;EACE;EACA;;AAGF;EA3DO;IA4DL;IACA;IACA;IACA;IACA;;EAEA;IACE;;EAGF;IACE;IACA;IACA;;EAEA;IACE;;EAGF;IACE;;;;AAQC;EACP;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAKN;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAKN;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;;AAQN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGA;EACE;;AAIJ;EACE;IACE;;EAGF;IACE;;;AAIJ;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAKJ;EACE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;;ACl1IN;EAEE;;AAEA;EAJF;IAKI;;;;AAIJ;EACE;EACA,exCkDY;;AwChDZ;EACE,WxCuBY;EwCtBZ,axC6Be;EwC5B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ACA;;AAEA;EAJF;IAKI,WxC3hBW;;;AwC+hBf;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA,exChgBiB;EwCigBjB,WxCxiBa;EwCyiBb;;AAEA;EAZF;IAaI;IACA,WxC9iBS;IwC+iBT;;;;AtChlBF;EsCimBF;IACE;IACA;;EAIF;IACE;IACA;IACA;;EAEA;IACE;IACA,axC/jBa;IwCgkBb;;EAGF;IACE;IACA;;EAKJ;IACE;IACA;IACA;IACA;IACA;IACA;IACA,YxCrnBI;;EwCunBJ;IACE;IACA;IACA;IACA;IACA;;EAEA;IACE;IACA;IACA;IACA;;EAIA;IACE;IACA;;EAKN;IACE;;EAGF;IACE;IACA;IACA;;EAEA;IACE;IACA,OxCxpBA;;EwC4pBJ;IACE;IACA,axC1nBa;;EwC6nBf;IACE;IACA;IACA;;EAKJ;IACE;IACA;IACA;IACA;;EAIF;IACE;IACA;IACA;IACA;;EAEA;IACE;;EAIJ;IACE;IACA,axC1pBe;IwC2pBf;IACA;;EAGF;IACE;;EAIF;IACE;IACA;IACA;IACA;IACA;IACA;;EAEA;IACE;;EAMF;IACE;IACA;IACA;IACA;;EAKJ;IACE;IACA;IACA;IACA;IACA,axCjsBe;IwCksBf;IACA;IACA;;EAEA;IACE;;EAKJ;IACE;IACA;IACA;IACA,axChtBe;IwCitBf;IACA;IACA;;EAEA;IACE;IACA;;EAGF;IACE;;EAKJ;IACE;;EAGE;IACE;IACA;;EAMN;IACE;IACA;;EAIF;IACE;;EAEA;IACE;IACA;;EAGF;IACE;IACA;IACA;IACA;IACA;IACA;IACA;;EAMF;IACE;IACA;;EAKJ;IACE;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;EAEA;AAAA;IAEE;IACA;IACA;IACA;IACA;IACA,axC/xBa;IwCgyBb;IACA;;EAEA;AAAA;IACE;IACA;IACA;;EAGF;AAAA;IACE;IACA;IACA;;EAGF;AAAA;IAGE;;;AASR;EACE;EACA;;AAGA;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EARF;IASI;IACA;;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAZF;IAaI;IACA;IACA;IACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;AAAA;EAEE;EACA;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAEE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAZF;IAaI;IACA;IACA;;;AAKJ;EACE;EACA;EACA;;AAEA;EALF;IAMI;IACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EAtBF;IAuBI;IACA;;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;;AAGF;EA1BF;IA2BI;IACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGA;EACE;;AAGF;EACE;;AAGF;EACE;EACA;;AAGF;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAXF;IAYI;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAIF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;;AACA;EACE;;AAMN;EACE;EACA;EACA;EACA;EACA;;AAEA;EAPF;IAQI;IACA;;EAEA;AAAA;AAAA;IAGE;;;AAKN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;;AtC3zCF;EuCGJ;IAKI;;;;AvCRA;EuCaJ;IAKI;IACA;IACA;IACA;IACA;;;;AAIJ;EACE;EACA,OzCpBU;EyCqBV;;AvC7BE;EuC0BJ;IAOI;IACA,azCWiB;;;;AyCNrB;EAEE;;AvCzCE;EuCuCJ;IAMI;IACA;IACA;IACA;IACA;;;;AAKJ;EACE;EACA;EACA,KzCEW;EyCDX;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA,ezCJe;;AErEf;EuCsDJ;IAwBI;IACA;IACA;IACA;IACA;;EAEA;IACE;IACA;IACA;IACA;;EAGF;IACE;;;;AAMN;EAEE;EACA;EACA;;AvCtGE;EuCkGJ;IAQI;IACA;;;AAGF;EAEE;;AAGF;EAEE;;;AAKJ;EACE;EACA;EACA;EACA;EACA;;AvC/HE;EuC0HJ;IASI;;;;AAIJ;EACE;EACA;EACA,KzCpEiB;;AEtEf;EuCuIJ;IAOI;IACA;;;;AvC/IA;EuCoJJ;IAKI;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA,azClHe;IyCmHf;IACA;IACA;;;;AAKJ;EACE,azC/IoB;EyCgJpB;EACA,azC7HiB;EyC8HjB,OzCrKU;EyCsKV;;AvC9KE;EuCyKJ;IASI;IACA;IACA;IACA;;;;AAKJ;EACE,azChKoB;EyCiKpB;EACA,azCjJoB;EyCkJpB;EACA;EACA;;AvChME;EuC0LJ;IAUI;IACA,OzC7LQ;IyC8LR;;;;AAUJ;EACE;EACA;EACA;;AvCnNE;EuCgNJ;IAOI;;;;AAKJ;EAEE;EACA,OzCpNM;;AyCsNN;EACE;;AvClOA;EuC4NJ;IAWI;IACA;IACA;IACA;IACA;;;;AAIJ;AACA;EACE;EACA;EACA;EACA;EACA;;AAEA;EAPF;IAQI;IACA;IACA;IACA;;;;AAIJ;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EANF;IAOI;;EAEA;IACE;;;AAIJ;EACE;EACA;EACA;EACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;;AAIJ;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAdF;IAeI;IACA;IACA;IACA;IACA;;;AAGF;EACE;EACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAXF;IAYI;IACA;IACA;;;AAGF;AAAA;EAEE;EACA;EACA;EACA;;AAEA;EAPF;AAAA;IAQI;IACA;;;;AAKN;EACE;EACA;EACA;EACA;EACA;;AAEA;EAPF;IAQI;;;;AAIJ;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EATF;IAUI;;;;AAIJ;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAVF;IAWI;;;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAEA;EAPF;IAQI;IACA;IACA;;;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAEA;EAPF;IAQI;IACA;;;;AAIJ;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;;AC/fJ;EACE;EACA,Y1CYQ;E0CXR;;AAEA;EALF;IAMI;;;;AAIJ;EACE,W1CoEoB;E0CnEpB;;;AAIF;EACE,e1C+CY;;A0C7CZ;EAHF;IAII,e1C2CU;;;A0CxCZ;EACE,W1CiBY;E0ChBZ,a1CwBe;E0CvBf,O1ChBQ;E0CiBR,e1CiCS;E0ChCT;EACA;EACA;;AAEA;EATF;IAUI,W1COU;;;A0CHd;EACE,W1CFa;E0CGb,O1C5BQ;E0C6BR,a1CciB;;A0CZjB;EALF;IAMI,W1CRS;;;;A0Ccf;EACE,Y1CrCM;E0CsCN,e1CqBiB;E0CpBjB,Y1C5BU;E0C6BV;EACA,e1CQW;;;A0CFb;EACE;EACA;EACA;;AAEA;EALF;IAMI;;;;AAKJ;EACE;EACA;;AAEA;EAJF;IAKI;;;;AAIJ;EACE;EACA;EACA;EACA;;AAEA;EANF;IAOI;IACA;;;;AAIJ;EACE;EACA;EACA;;AAEA;EALF;IAOI;IACA;;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAIJ;EACE;EACA;EACA;EACA;;AAEA;EANF;IAOI;;;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA,Y1CtHgB;;A0CwHhB;EACE,O1ChOW;;A0CmOb;EACE;EACA;;AAGF;EACE,O1CzOW;;A0C4Ob;EACE;EACA;;AAGF;EACE;EACA;EACA;;;AAKJ;AAAA;EAEE;;AAEA;AAAA;EACE;EACA;EACA;EACA;EACA;;AAEA;AAAA;EACE;;AAGF;AAAA;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;AAAA;EACE;EACA;EACA;EACA;;AAGF;EA3BF;AAAA;IA4BI;;EAEA;AAAA;IACE;;EAGF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;IAIE;IACA;IACA;;EAGF;AAAA;IACE;;EAGF;AAAA;IACE;IACA;IACA;IACA;IACA;;EAGF;AAAA;IACE;IACA;;;;AAOR;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAIF;EACE;EACA;EACA;EACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;EACA;;AAGF;EACE;;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA,e1ChVY;E0CiVZ;;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EANF;IAOI;;;AAIF;EACE,e1CpWS;;A0CsWT;EACE;;AAIJ;EACE;EACA;EACA,e1CnWe;E0CoWf;;AAGF;EACE,O1ChbW;E0CibX;;AAEA;EACE,O1CnbW;;A0Cubf;AAAA;EAEE;EACA,c1C5XS;;A0C+XX;EACE,e1CnYS;;;A0CwYb;EACE;EACA;EACA,e1CtYY;;A0CwYZ;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EAvCF;IAwCI;;EAEA;IACE;;EAGF;AAAA;IAEE;IACA;;EAGF;IACE;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EACA;EACA;EACA;EACA;;AAIJ;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;;AAQN;EACE;EACA,Y3CtcQ;E2CucR;;AAEA;EALF;IAMI;;;AAGF;EACE;EACA;EACA,K3CnaS;;A2CqaT;EACE;EACA;EACA,K3CxaO;;A2C0aP;EACE,O3C5dI;E2C6dJ;EACA,W3CrcO;E2CscP,Y3CnYU;E2CoYV;EACA;EACA,K3ClbK;;A2CobL;EATF;IAUI,W3C7cK;;;A2CgdP;EACE,O3CpfK;;A2CsfL;EACE;;AAIJ;EACE,O3C5fK;E2C6fL,Y3CtZQ;;;A2C8ZlB;EACE;EACA;;AAEA;EAJF;IAKI;;;AAGF;EACE,W3Czea;E2C0eb,O3CpgBQ;E2CqgBR,a3CxdgB;E2CydhB;;AAEA;EANF;IAOI,W3ChfS;;;;A2Cuff;EACE,Y3C9gBM;E2C+gBN,e3CpdiB;E2CqdjB,Y3CrgBU;E2CsgBV;EACA,e3CjeW;E2CkeX;;AAEA;EACE;EACA;EACA;;AAEA;EALF;IAMI,S3C3eO;;;A2C8eT;EACE;EACA;EACA,K3CnfO;E2CofP,W3CzgBS;E2C0gBT,a3ChgBiB;E2CigBjB,O3CviBM;E2CwiBN,e3CvfO;;A2CyfP;EACE,O3CjjBO;;A2CqjBX;EACE,W3CvhBS;E2CwhBT,O3ChjBM;;A2CkjBN;EAJF;IAKI,W3C5hBO;;;A2CiiBb;EACE,S3CrgBU;E2CsgBV,W3CjiBa;E2CkiBb,O3C5jBQ;E2C6jBR,a3ChhBgB;E2CihBhB;;AAEA;EAPF;IAQI,S3C9gBO;I2C+gBP,W3CziBS;;;;A2C+iBf;EACE;EACA;EACA,K3CzhBW;E2C0hBX,e3CthBY;E2CuhBZ;;AAEA;EAPF;IAQI;IACA;;;AAGF;EACE;EACA,e3CzhBe;E2C0hBf,W3C7jBa;E2C8jBb,a3CnjBiB;E2CojBjB;EACA,Y3C9fc;E2C+fd;EACA;EACA;EACA,K3C7iBS;E2C8iBT;EACA;;AAEA;EAdF;IAeI;IACA,W3C3kBS;;;A2C8kBX;EACE,Y3CrmBE;E2CsmBF,O3CzmBM;E2C0mBN;;AAEA;EACE,Y3CzmBE;E2C0mBF,c3Czn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A;IACA;IACA;;EAEA;IACE;IACA;IACA;;EAGF;IACE;;EAGF;IACE;;EAGF;IACE;;EAQV;IACE;;EAEA;IACE;;EAGF;IACE,W9CjlBO;;E8ColBT;IACE,W9CxlBO;;E8C6lBX;IACE;IACA;IACA;;EAEA;IACE;IACA;;EAEA;AAAA;IAEE;IACA;IACA;;EAIJ;IACE;;EAGF;IACE;IACA;IACA;IACA;IACA,a9C5mBc;;E8C8mBd;IACE,a9C5mBS;I8C6mBT;;EAgBA;IACE;;EAIF;IACE;IACA;;EAIF;IACE;IACA;IACA;IACA;;EAIF;IACE;IACA;IACA;IACA;;EAIF;IACE;;EASF;IAEE;;EAIF;IAEE;IACA;IACA;IACA;;EAEA;IACE;IACA;IACA;IACA;;EAGA;IACE;;EAGF;IACE;IACA;IACA;;EAGF;IACE;IACA;IACA;;EAEA;IACE;IACA;;EAOR;IACE;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;EAIF;IACE;IACA;IACA;IACA;IACA;;EAEA;IACE;IACA;IACA;;EAKJ;IACE;;EAOR;IACE;IACA;IACA;;EAEA;IACE;IACA;IACA;IACA;IACA;;EAMF;IACE;IACA;;EAeE;IACE;;EAIF;IACE;IACA;;EAIF;IACE;IACA;IACA;;EASF;IAEE;;EAIF;IAEE;IACA;IACA;;EAEA;IACE;IACA;IACA;IACA;;EAGA;IACE;;EAGF;IACE;IACA;IACA;;EAGF;IACE;IACA;IACA;;EAEA;IACE;IACA;;EAOR;IACE;IACA;IACA;IACA;IACA;;EAQN;IACE;IACA;;EASN;IACE;;EAGA;IACE,e9Cr2BO;;E8Cu2BP;IACE,W9C/3BO;;E8Co4BX;IACE;IACA;IACA;IACA;;EAIF;IACE;IACA;IACA;IACA;IACA;IACA;;EAEA;IACE;IACA;IACA;;EAEA;IACE;IACA;IACA;IACA;IACA;IACA;;;AAUV;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,e9Cx5Be;E8Cy5Bf;EACA;EACA,Y9C33Bc;;A8C63Bd;EACE;EAC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;EACA;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;;;AAKN;EACE;IACE;IACA;;EAGF;IACE;IACA;;EAGF;IACE;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;EAEA;IACE;;EAdJ;IAiBE;AACA;IACA;AACA;;EAEA;AAAA;IAEE;;EAGF;IACE;IACA;IACA;IACA;;EAKJ;AAAA;IAEE;;;AAOJ;EACE;EACA;EACA;EACA;EACA;EAGA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;;A9CpVA;E8CgVF;IAOI;;;AAIJ;EACE;EACA;EACA;EACA;EACA;;;AAKJ;EACE;EACA;EACA;;A9CxWE;E8CqWJ;IAMI;IACA;;;;AAQJ;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAIJ;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;;;AAQV;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;EACA;EACA;;;AAKN;EACE;EACA;;;AAIF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAGA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAIJ;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;;AAIJ;EACE;EACA;EACA;;;AAIF;EACE;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;;AAKJ;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;;AAIJ;EACE;EACA;EACA,ahDnrBkB;EgDorBlB;;AAEA;AAAA;EAEE;EACA;EACA;EACA;;AAEA;AAAA;EACE;;AAIJ;EACE;;AAEA;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAMJ;EACE;;AAEA;EACE;;AAGF;EACE;;AAIJ;EACE;EACA;EACA;EACA;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;;AAQV;EACE;IACE;;EAGF;IACE;IACA;IACA;;EAGF;IACE;;EAIA;IACE;IACA;IACA;IACA;;EAEA;IACE;;;AAMR;EAEI;IACE;;EAEA;IACE;;EAIJ;IACE;;EAEA;IACE;;EAGF;IACE;IACA;IACA;;EAEA;IACE;IACA;;EAIJ;IACE;IACA;IACA;;EAEA;IACE;;;AAWV;EACE;EACA;EACA;;;AAIA;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;;AAIJ;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;;AAIJ;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;EACA;;;AAIJ;EACE;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;AAAA;EAEE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAGF;AAAA;EAEE;;;AAKN;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;;;AAIJ;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;;AASN;EACE;IACE;;EAGE;IACE;IACA;;EAGF;IACE;IACA;;EAKN;IACE;;EAEA;IACE;;EAIJ;IACE;;EAEA;IACE;IACA;;EAGF;IACE;;EAIJ;IACE;IACA;IACA;;EAIE;AAAA;IAEE;IACA;;EAKN;IACE;;EAEA;IACE;;EAIJ;IACE;;EAEA;IACE;IACA;;;AAgCN;EACE;EACA;EACA;EACA;EACA;EACA;;;AAIF;EASE;EACA,OArCgB;;AAuChB;AAAA;AAAA;EAGE;;AAGF;EAKE;;AAKF;EACE;EACA;EACA;EACA,OA/DY;EAgEZ;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA,OArEc;;AAwEhB;EACE;EACA;EACA;EACA,OA3Ec;;AA+EhB;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;A9C3tCF;E8CmtCF;IAaI;;;AAIJ;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,OAxKc;;AA0Kd;EACE,YA/KU;EAgLV,cAhLU;EAiLV;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAKJ;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA,YAjOY;EAkOZ;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA,OAvOc;EAwOd;;AAGF;EACE;EACA;EACA;EACA,OA9Oc;EA+Od;;AAEA;EACE;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAKJ;EACE;EACA;EACA;;AAIA;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;AAAA;EAEE;;AAIJ;EACE;EACA;EACA;EACA,YA7SY;EA8SZ;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA,OAnTc;EAoTd;;AAGF;EACE;EACA;EACA;EACA,YA5Ta;EA6Tb;EACA,OA5Tc;EA6Td;EACA;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA,OAjVc;;AAoVhB;EACE;EACA;EACA;EACA,OAxVc;;AA2VhB;EACE;EACA;EACA;EACA;;AAEA;EACE,YAnWW;;AAsWb;EACE;EACA;EACA;EACA;EACA,OAzWY;EA0WZ;;AAGF;EACE;EACA;EACA,OAhXY;EAiXZ;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA,OA7XY;;AAiYZ;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE,OA7YU;;AAkZhB;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE,YAtaU;;AAyaZ;EACE,YApaY;;AAwahB;EACE;EACA;EACA;EACA,YAraY;EAsaZ,OAraY;EAsaZ;EACA;;AAIF;EACE;EACA;EACA;EACA,YAzbc;EA0bd;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA,YAvcU;;AA0cZ;EACE,YA/bM;;AAmcV;EACE;EACA;EACA;EACA,YAndY;EAodZ;EACA;EACA;EACA;;AAEA;EACE,YA1dU;;AA6dZ;EACE,YAldM;;AAsdV;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAIF;EACE,OAveS;;AA0eX;EACE,OA1eW;;AA6eb;EACE,OA7eU;;AAgfZ;EACE,OAhfW;;AAmfb;EACE,OAzfc;;AA6fhB;EACE;EACA;EACA,YAngBa;EAogBb;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA,OAnhBY;EAohBZ;;AAGF;EACE;EACA;EACA;EACA,OAvhBc;;AA0hBhB;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA,OAniBY;EAoiBZ;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA,YAnjBQ;;AAsjBV;EACE;;AAMN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE,YAxjBS;EAyjBT,OA5jBM;;AA+jBR;EACE,YA/jBU;EAgkBV,OA/jBU;;AAokBd;EACE;EACA;EACA;EACA,YAvkBW;EAwkBX,OAvkBW;EAwkBX;EACA;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA,OAtnBc;EAunBd;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA,OAvoBc;;AA0oBhB;EACE;EACA;EACA;EACA;EACA;EACA,OAjpBY;EAkpBZ;EACA;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAKN;EAEI;IACE;IACA;IACA;;EAGF;IACE;;EAGF;IACE;;EAGF;IACE;;EAGF;IACE;IACA;;EAEA;AAAA;IAEE;;EAMF;AAAA;AAAA;IAGE;;;AAMR;EACE;IACE;;EAEA;IACE;;EAGF;IACE;;EAGF;IACE;;EAGF;IACE;;EAGF;IACE;;EAGF;IACE;;EAGF;IACE;;;AC5zDN;EACE;;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAIF;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;;AAKJ;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;;AAKJ;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;;;AAIJ;EACE;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;;AAIJ;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;;;AAIJ;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAIF;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAIF;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;;AAIJ;EACE;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;;;AAIJ;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAIF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;;AAIJ;EACE;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EARF;IASI;;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAIJ;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EAbF;IAcI;IACA;IACA;IACA;IACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAVF;IAWI;IACA;;EAEA;IACE;IACA;IACA;IACA;IACA;;;AAKN;EACE;EACA;;AAEA;EACE;;;AAMR;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAEA;EACE;;;AAKN;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EARF;IASI;;;;AAKJ;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;;;AAIF;EACE;IACE;IACA;IACA;;EAGF;AAAA;AAAA;IAGE;IACA;IACA;IACA;IACA;;EAGF;IACE;;EAGF;IACE;IACA;;EAGF;IACE;;EAGF;IACE;;EAGF;IACE;IACA;IACA;IACA;;EAGF;IACE;IACA;;;AAIJ;EAKE;IACE;;EAGF;IACE;IACA;IACA;;EAGF;IACE;IACA;IACA;;EAGF;IACE;IACA;;EAGF;IACE;IACA;IACA;;EAGF;IACE;;EAGF;IACE;IACA;IACA;IACA;IACA;IACA;;EAEA;IACE;IACA;IACA;IACA;IACA;IACA;IACA;;EAEA;IACE;IACA;IACA;IACA;IACA;;EAGF;IACE;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;EAEA;IAME;;EAMR;IACE;;;ACzqBJ;EACE;EACA;EACA;EACA;;AAEA;EACE,OAVO;EAWP;EACA;;AAGF;EACE,OAnBQ;EAoBR;EACA;EACA;;AAEA;EANF;IAOI;;;AAIJ;AAAA;EAEE,OA/BQ;EAgCR;EACA;;AAGF;EACE,OArCQ;;;AA0CZ;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE,OApEO;EAqEP;EACA;;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAZF;IAaI;IACA;IACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAdF;IAeI;IACA;IACA;;;AAIJ;EACE;EACA;EACA;;AAEA;EALF;IAMI;;;AAIJ;EACE,OA3IO;;AA6IP;EACE,YA9IK;EA+IL,cA/IK;EAgJL;;AAGF;EACE;EACA;;;AAOR;EACE;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAGF;AAAA;EAEE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;AAAA;EACE;EACA,cArLO;;AAwLT;AAAA;EACE;;;AAKN;EACE;EACA;EACA;;AAEA;EALF;IAMI;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA,cAvNO;;AA0NT;EACE;EACA;EACA;;AAGF;EACE,OA5NK;EA6NL;EACA;;AAGF;EACE,cAvOO;EAwOP;;;AAMN;EACE;;;AAGF;EACE;;AAEA;EACE;EACA;EACA;EACA;;;AAIJ;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA,cAhRO;;AAmRT;EACE;EACA;EACA;;AAGF;EACE,OArRK;EAsRL;EACA;;AAGF;EACE,cAhSO;EAiSP;;;AAMN;EACE;EACA;EACA;EACA;;AAEA;EACE;;;AAIJ;AAAA;EAEE;EACA;EACA;EACA;EACA;EACA,YAzTW;EA0TX;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;AAAA;EACE,kBAnUc;;;AAuUlB;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA,OAnWU;EAoWV;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE,YA5WQ;EA6WR;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA,YA9YW;EA+YX;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE,kBAvZc;;;AA4ZlB;EACE;EACA;EACA;EACA;EACA;;AAEA;EAPF;IAQI;;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE,OAjbK;EAkbL;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAEA;EAPF;IAQI;IACA;IACA;IACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;AAAA;EAEE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAIJ;AAAA;EAEE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;;AAKJ;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;;AASV;EACE;EACA;EACA;EACA;EACA;;AAEA;EAPF;IAQI;IACA;IACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;;AAKN;EACE;EACA;EACA;EACA;EACA;;AAEA;EAPF;IAQI;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EANF;IAOI;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAjBF;IAkBI;IACA;IACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAMN;EACE;EACA;EACA;EACA;;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;;AAKJ;AAAA;AAAA;EAGE;EACA;EACA;EACA;;AAEA;AAAA;AAAA;EACE;;AAEA;AAAA;AAAA;EACE;;AAIJ;AAAA;AAAA;AAAA;AAAA;AAAA;EAEE;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;;AAMJ;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA,OAr6BS;EAs6BT;EACA;EACA;EACA;;AAEA;EACE,cA56BO;EA66BP;;;AAMN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA,cA/9BK;;AAm+BT;EACE;EACA;EACA;EACA;;AAEA;AAAA;EAEE;EACA;EACA;;;AAQN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGA;EACE;;AAIJ;EACE;IACE;;EAGF;IACE;;;AAIJ;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;;ACzkCN;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EAOE,anDsBoB;;;AmDlBtB;EAEE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;;AAEA;EAAI;EAAW;;AAGjB;EACE;EACA;;AAEA;EAAS;;AAGX;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;IAAyB;;EAEzB;IAAK;;EAEL;IAAiB;;;;AAIrB;EAEE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAEA;EAGA;EACA;EACA;;AAGA;EAAkB;;AA/BpB;EAmCE;;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EASS;;AAET;AAAA;EACS;;AAGT;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACc;EAA8B;;AAC5C;EAAmB;EAA+B;;AAClD;EAAgB;EAA+B;;AAC/C;EAAc;EAA6B;;AAC3C;EAAa;EAA+B;;AAG9C;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EAAW;EAAiB;;AAE5B;EACE;EACA;EACA;EACA;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAIF;EACE;EACA;;AAEA;EAEE;EACA;;AAKJ;EACE;EACA;EACA;;AAIF;EACE;EACA;EACA;EACA;;AAMJ;EACE;EACA;EACA;EACA;;AAEA;EANF;IAOI;;;AAMJ;EAAoC;;AACpC;EAAqD;;AACrD;EAAiD;;AAIjD;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EAAS;EAAiB;;AAG5B;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EAAa;;AACb;EAAe;;AAGjB;EACE;EACA;EACA;EACA;EACA;EACA;;AAKJ;EACE;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EAAW;EAAiB;;AAE5B;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAAc;EAA+B;;AAOnD;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAAI;EAAsB;EAAkB;;AAK5C;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAAc;;AACd;EAAmB;;AACnB;EAAgB;;AAChB;EAAU;;AAGZ;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EAAU;;AACV;EAAc;;AACd;EAAmB;;AACnB;EAAgB;;AAChB;EAAY;;AAEZ;EACY;;AAMd;EACE;EACA;EACA;;AAGF;AAAA;EACK;EAAkB;;AAEvB;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAEA;EAAe;;AACf;EAAU;;AAGZ;EAAe;EAAkB;;AACjC;EAAU;;AAIZ;EAAiB;EAAe;;AAEhC;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EAAe;;AAEf;EACE;EACA;EACA;EACA;EACA;;AAGF;EAAkB;EAAW;EAAiB;EAAkB;;AAEhE;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EAA4B;EAAuB;;AAEnD;EAAiB;EAAqB;EAAiB;EAAkB;;AAEzE;EACE;EACA;EACA;EACA;;AAKJ;EAAiB;EAAe;;AAG9B;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAEA;EAAgB;;AAChB;EAAgB;;AAChB;EAAmB;;AAEnB;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAGF;EAAgC;;AAChC;EAAgC;;AAChC;EAAmC;;AAIrC;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAIJ;EACE;EACA;;AAEA;EAAe;;AAEf;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EAA2B;EAA0B;;AACrD;EAA6B;EAA4B;;AACzD;EAA6B;EAAqB;;AAClD;EAAgC;EAA4B;;AAC5D;EAA2B;EAA6B;;AAExD;EAAe;EAAiB;EAAiB;EAAkB;;AACnE;EAAc;EAAiB;EAAiB;EAAkB;EAAuB;;AAEzF;EACE;EACA;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAIF;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAAiB;;AACjB;EAAiB;;AACjB;EAAoB;;AAEpB;EACa;;AACb;EAAgB;EAAqC;;AACrD;EAAU;;AAGZ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EAAqB;;AAGvB;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAKJ;EACE;EACA;EACA;;AAEA;EAAQ;;AAGR;EACE;EACA;;AAEA;EAC+B;EAA0B;EAAkB;;AAI/E;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAAU;EAAqC;;AAGjD;EAA6C;;AAG7C;EAAmB;;AAIjB;EACE;EACA;;AAGF;EAAQ;;AAER;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EAAU;;AAGZ;EACE;EACA;EACA;EACA;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAAI;;AAEJ;EACE;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAAU;;AAEV;EACE;EACA;EACA;EACA;;AAKJ;EACE;IACE;;EAEA;IAAmB;IAAgB;IAAa;;EAIhD;IAAQ;IAAgB;IAAkB;;;AAI9C;EAjzBF;IAkzBI;IACA;;EAEA;IAAkB;;EAElB;IAAW;;EAEX;IAAgB;;EAEhB;IAA4B;;EAC5B;IAA4B;;EAC5B;IAAiC;;EAGjC;IACE;IACA;IACA;;EAEA;IAAmB;IAAwB;IAAyB;;EAGpE;AAAA;IACoB;IAAgB;IAAa;;EAEjD;IAAmB;;EAIrB;IACE;;EAEA;IAAiB;;EAGnB;IAAmC;;EAEnC;IAAiB;;EACjB;IAAkB;;EAElB;IAAiB;;EAEjB;IAA2B;;EAE3B;IAAiB;;EAEjB;IAAmB;IAAiB;;;AAGtC;EACE;IAAuB;;EAEvB;IAAa;IAAU;;EAEvB;IAAe;;EACf;IAAsB;;;;ACl7B1B;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIF;ElDRE;EACA;EACA;;;AkDUF;ElDNE;EACA;EACA;;;AkDQF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIF;EACE,KpDlBW;;;AoDqBb;EACE,KpDrBW;;;AoDwBb;EACE,KpDxBW;;;AoD2Bb;EACE,KpD3BW;;;AoD8Bb;EACE,KpD9BW;;;AoD8CX;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AA3BF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AA3BF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AA3BF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AA3BF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AA3BF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AA3BF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AA3BF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AA3BF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AA3BF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAgBF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AA3BF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AA3BF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AA3BF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AA3BF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AA3BF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AA3BF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AA3BF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AA3BF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAKJ;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIF;EACE,YpDxPa;;;AoD2Pf;EACE,YpD3Pe;;;AoD8PjB;EACE,YpDlPM;;;AoDqPR;EACE,YpDrPQ;;;AoDwPV;EACE,YpDxPS;;;AoDiQX;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE,cpD1Sa;;;AoD8Sf;EACE;;;AAGF;EACE,epD5OiB;;;AoD+OnB;EACE,epD/OiB;;;AoDkPnB;EACE,epDlPiB;;;AoDqPnB;EACE,epDrPiB;;;AoDwPnB;EACE,epDvPmB;;;AoD0PrB;EACE,epD1PqB;;;AoD8PvB;EACE;;;AAGF;EACE,YpDxTU;;;AoD2TZ;EACE,YpD3TU;;;AoD8TZ;EACE,YpD9TU;;;AoDiUZ;EACE,YpDjUU;;;AoDqUZ;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AlD3YE;EkDgZF;IACE;;;AlD/YA;EkDoZF;IACE;;;AAKF;EADF;IAEI;;;;AlD7ZA;EkDiaJ;IAEI;;;;AAKJ;EACE;;AlDzaE;EkDwaJ;IAII;;;;AAIJ;EACE;;AlDjbE;EkDgbJ;IAII;;;;AAKJ;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIF;EACE,YpDxXgB;;;AoD2XlB;EACE,YpD3XgB;;;AoD8XlB;EACE,YpD9XgB;;;AoDiYlB;EACE;;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA","file":"main.css"} \ No newline at end of file diff --git a/src/main/resources/static/css/main.min.css b/src/main/resources/static/css/main.min.css index cf910da..db935aa 100644 --- a/src/main/resources/static/css/main.min.css +++ b/src/main/resources/static/css/main.min.css @@ -1 +1 @@ -:root{--font-weight-primary-regular: 400;--font-weight-primary-bold: 700}*{margin:0;padding:0;box-sizing:border-box}html{font-size:16px;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-rendering:optimizeLegibility}body{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;line-height:1.6;color:#1a1a2e;background-color:#fff;overflow-x:hidden}ul,ol{list-style:none}a{text-decoration:none;color:inherit;transition:all .3s ease}img{max-width:100%;height:auto;display:block}button{font-family:inherit;font-size:inherit;line-height:inherit;cursor:pointer;background:rgba(0,0,0,0);border:none;padding:0}input,textarea,select{font-family:inherit;font-size:inherit;line-height:inherit;border:none;outline:none}table{border-collapse:collapse;border-spacing:0}h1,h2,h3,h4,h5,h6{font-weight:700;line-height:1.2}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}::selection{background-color:rgba(0,73,180,.2);color:#1a1a2e}::-moz-selection{background-color:rgba(0,73,180,.2);color:#1a1a2e}@font-face{font-family:"Noto Sans KR";src:url("/font/NotoSansKR-VariableFont_wght.ttf") format("truetype");font-weight:100 900;font-style:normal;font-display:swap}body{font-size:16px;font-weight:400;color:#1a1a2e}h1,.h1{font-size:56px;font-weight:800;margin-bottom:24px}@media(max-width: 768px){h1,.h1{font-size:40px}}@media(max-width: 480px){h1,.h1{font-size:32px}}h2,.h2{font-size:48px;font-weight:700;margin-bottom:16px}@media(max-width: 768px){h2,.h2{font-size:32px}}@media(max-width: 480px){h2,.h2{font-size:24px}}h3,.h3{font-size:40px;font-weight:700;margin-bottom:16px}@media(max-width: 768px){h3,.h3{font-size:24px}}h4,.h4{font-size:32px;font-weight:600;margin-bottom:8px}h5,.h5{font-size:24px;font-weight:600;margin-bottom:8px}h6,.h6{font-size:20px;font-weight:600;margin-bottom:8px}p{margin-bottom:16px;line-height:1.6}p:last-child{margin-bottom:0}.text-primary{color:#0049b4}.text-secondary{color:#c3dfea}.text-dark{color:#1a1a2e}.text-gray{color:#64748b}.text-light{color:#94a3b8}.text-white{color:#fff}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-regular{font-weight:400}.font-medium{font-weight:500}.font-semibold{font-weight:600}.font-bold{font-weight:700}.font-extrabold{font-weight:800}.text-xs{font-size:12px}.text-sm{font-size:14px}.text-base{font-size:16px}.text-lg{font-size:20px}.text-xl{font-size:24px}.lead{font-size:20px;font-weight:400;line-height:1.8;color:#64748b}.caption{font-size:14px;color:#64748b}.code{font-family:"Fira Code","Courier New",monospace;font-size:14px;background:#f8fafc;padding:2px 6px;border-radius:6px}a{color:#0049b4}a:hover{color:#c3dfea}blockquote{padding:16px;margin:16px 0;border-left:4px solid #0049b4;background:#f8fafc;font-style:italic}blockquote p{margin-bottom:0}hr{border:none;border-top:1px solid #e2e8f0;margin:32px 0}@keyframes float{0%,100%{transform:translateY(0) rotate(0deg)}50%{transform:translateY(-30px) rotate(10deg)}}@keyframes bounce{0%,100%{transform:translateY(0)}50%{transform:translateY(-10px)}}@keyframes slideInDown{from{opacity:0;transform:translateY(-30px)}to{opacity:1;transform:translateY(0)}}@keyframes slideInUp{from{opacity:0;transform:translateY(30px)}to{opacity:1;transform:translateY(0)}}@keyframes slideInLeft{from{opacity:0;transform:translateX(-30px)}to{opacity:1;transform:translateX(0)}}@keyframes slideInRight{from{opacity:0;transform:translateX(30px)}to{opacity:1;transform:translateX(0)}}@keyframes fadeIn{from{opacity:0}to{opacity:1}}@keyframes fadeInScale{from{opacity:0;transform:scale(0.9)}to{opacity:1;transform:scale(1)}}@keyframes spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}@keyframes pulse{0%{transform:scale(1)}50%{transform:scale(1.05)}100%{transform:scale(1)}}@keyframes shimmer{0%{transform:translateX(-100%) rotate(45deg)}100%{transform:translateX(100%) rotate(45deg)}}@keyframes wave{0%{transform:rotate(0deg)}10%{transform:rotate(14deg)}20%{transform:rotate(-8deg)}30%{transform:rotate(14deg)}40%{transform:rotate(-4deg)}50%{transform:rotate(10deg)}60%{transform:rotate(0deg)}100%{transform:rotate(0deg)}}@keyframes popIn{0%{opacity:0;transform:scale(0.5)}80%{transform:scale(1.1)}100%{opacity:1;transform:scale(1)}}@keyframes popupFadeIn{from{opacity:0;transform:scale(0.95)}to{opacity:1;transform:scale(1)}}@keyframes ripple{0%{transform:scale(0);opacity:1}100%{transform:scale(4);opacity:0}}@keyframes gradientShift{0%{background-position:0% 50%}50%{background-position:100% 50%}100%{background-position:0% 50%}}.animate-float{animation:float 20s ease-in-out infinite}.animate-bounce{animation:bounce 2s infinite}.animate-spin{animation:spin 1s linear infinite}.animate-pulse{animation:pulse 2s ease-in-out infinite}.animate-fade-in{animation:fadeIn .6s ease-out}.animate-slide-up{animation:slideInUp .8s ease-out}.animate-slide-down{animation:slideInDown .6s ease-out}.animate-pop{animation:popIn .5s cubic-bezier(0.68, -0.55, 0.265, 1.55)}.delay-1{animation-delay:.1s}.delay-2{animation-delay:.2s}.delay-3{animation-delay:.3s}.delay-4{animation-delay:.4s}.delay-5{animation-delay:.5s}.duration-fast{animation-duration:.3s}.duration-normal{animation-duration:.6s}.duration-slow{animation-duration:1s}.duration-slower{animation-duration:2s}.hover-grow{transition:transform .3s ease}.hover-grow:hover{transform:scale(1.05)}.hover-lift{transition:transform .3s ease,box-shadow .3s ease}.hover-lift:hover{transform:translateY(-5px);box-shadow:0 8px 24px rgba(75,155,255,.15)}.hover-rotate{transition:transform .3s ease}.hover-rotate:hover{transform:rotate(5deg)}.hover-shine{position:relative;overflow:hidden}.hover-shine::before{content:"";position:absolute;top:-50%;left:-50%;width:200%;height:200%;transform:rotate(45deg);transition:transform .6s ease;transform:translateX(-200%)}.hover-shine:hover::before{transform:translateX(200%) rotate(45deg)}@font-face{font-family:"Spoqa Han Sans Neo";font-weight:700;src:local("Spoqa Han Sans Neo Bold"),url("/font/kjb/SpoqaHanSansNeo-Bold.woff2") format("woff2"),url("/font/kjb/SpoqaHanSansNeo-Bold.woff") format("woff"),url("/font/kjb/SpoqaHanSansNeo-Bold.ttf") format("truetype")}@font-face{font-family:"Spoqa Han Sans Neo";font-weight:400;src:local("Spoqa Han Sans Neo Regular"),url("/font/kjb/SpoqaHanSansNeo-Regular.woff2") format("woff2"),url("/font/kjb/SpoqaHanSansNeo-Regular.woff") format("woff"),url("/font/kjb/SpoqaHanSansNeo-Regular.ttf") format("truetype")}@font-face{font-family:"OneShinhan";font-weight:300;font-style:normal;src:local("원신한 Light"),local("OneShinhan Light"),local("OneShinhan-Light"),url("/font/djb/OneShinhanLight.woff") format("woff")}@font-face{font-family:"OneShinhan";font-weight:400;font-style:normal;src:local("원신한 Medium"),local("OneShinhan Medium"),local("OneShinhan-Medium"),url("/font/djb/OneShinhanMedium.woff") format("woff")}@font-face{font-family:"OneShinhan";font-weight:500;font-style:normal;src:local("원신한 Medium"),local("OneShinhan Medium"),local("OneShinhan-Medium"),url("/font/djb/OneShinhanMedium.woff") format("woff")}@font-face{font-family:"OneShinhan";font-weight:700;font-style:normal;src:local("원신한 Bold"),local("OneShinhan Bold"),local("OneShinhan-Bold"),url("/font/djb/OneShinhanBold.woff") format("woff")}@font-face{font-family:"원신한";font-weight:300;font-style:normal;src:url("/font/djb/OneShinhanLight.woff") format("woff")}@font-face{font-family:"원신한";font-weight:400;font-style:normal;src:url("/font/djb/OneShinhanMedium.woff") format("woff")}@font-face{font-family:"원신한";font-weight:500;font-style:normal;src:url("/font/djb/OneShinhanMedium.woff") format("woff")}@font-face{font-family:"원신한";font-weight:700;font-style:normal;src:url("/font/djb/OneShinhanBold.woff") format("woff")}@font-face{font-family:"SpoqaHanSans";font-weight:400;src:url("/font/kjb/SpoqaHanSansRegular.woff") format("woff"),url("/font/kjb/SpoqaHanSansRegular.woff2") format("woff2"),url("/font/kjb/SpoqaHanSansRegular.ttf") format("truetype")}@font-face{font-family:"SpoqaHanSansBold";font-weight:700;src:url("/font/kjb/SpoqaHanSansBold.woff") format("woff"),url("/font/kjb/SpoqaHanSansBold.woff2") format("woff2"),url("/font/kjb/SpoqaHanSansBold.ttf") format("truetype")}@font-face{font-family:"HGGGothicssi";font-weight:300;src:url("/font/kjb/HGGGothicssi40g.woff") format("woff"),url("/font/kjb/HGGGothicssi40g.woff2") format("woff2"),url("/font/kjb/HGGGothicssi40g.ttf") format("truetype")}@font-face{font-family:"HGGGothicssi";font-weight:400;src:url("/font/kjb/HGGGothicssi60g.woff") format("woff"),url("/font/kjb/HGGGothicssi60g.woff2") format("woff2"),url("/font/kjb/HGGGothicssi60g.ttf") format("truetype")}@font-face{font-family:"HGGGothicssi";font-weight:700;src:url("/font/kjb/HGGGothicssi80g.woff") format("woff"),url("/font/kjb/HGGGothicssi80g.woff2") format("woff2"),url("/font/kjb/HGGGothicssi80g.ttf") format("truetype")}:root{--primary-blue: #4B9BFF;--secondary-blue: #2E7FF7;--accent-cyan: #00D4FF;--accent-yellow: #FFD93D;--accent-orange: #FF6B6B;--accent-green: #6BCF7F;--accent-purple: #A78BFA;--text-dark: #1A1A2E;--text-gray: #64748B;--text-light: #94A3B8;--white: #FFFFFF;--gray-bg: #F8FAFC;--light-bg: #EFF6FF;--border-gray: #E2E8F0;--shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.05);--shadow-md: 0 4px 12px rgba(75, 155, 255, 0.1);--shadow-lg: 0 8px 24px rgba(75, 155, 255, 0.15);--shadow-xl: 0 16px 40px rgba(75, 155, 255, 0.2)}.design-survey-bar{position:fixed;top:0;left:0;right:0;height:48px;background:linear-gradient(90deg, #667eea 0%, #764ba2 100%);z-index:400;display:flex;align-items:center;justify-content:center;box-shadow:0 2px 8px rgba(0,0,0,.15)}.design-survey-bar .survey-container{display:flex;align-items:center;gap:16px}.design-survey-bar .survey-label{color:#fff;font-size:14px;font-weight:500}.design-survey-bar .survey-buttons{display:flex;gap:8px}.design-survey-bar .survey-btn{padding:6px 16px;border:2px solid hsla(0,0%,100%,.5);border-radius:20px;background:rgba(0,0,0,0);color:#fff;font-size:14px;font-weight:600;cursor:pointer;transition:all .3s ease}.design-survey-bar .survey-btn:hover{background:hsla(0,0%,100%,.2);border-color:#fff}.design-survey-bar .survey-btn.active{background:#fff;color:#667eea;border-color:#fff}@media(max-width: 768px){.design-survey-bar{height:40px}.design-survey-bar .survey-label{display:none}.design-survey-bar .survey-btn{padding:4px 12px;font-size:12px}}body.design-survey-active .global-header{margin-top:48px}@media(max-width: 768px){body.design-survey-active .global-header{margin-top:40px}}.blind{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);white-space:nowrap;border:0}.global-header{position:relative;background-color:#fff;z-index:350;height:112px;transition:all .3s ease;border-bottom:1px solid #ddd}.global-header.index-header{background-color:#e5f8ff}.global-header .container{height:100%;padding:0}.global-header .container .header-content{height:100%}.header-content{display:flex;align-items:center;justify-content:space-between;height:65px;max-width:1920px;margin:0 auto;padding:0 20px}.header-left{display:flex;align-items:center}.header-left .logo{height:32px;width:114px}.logo-wrapper{display:flex;align-items:center;gap:12px}.logo{display:block;height:32px;width:114px;vertical-align:middle;z-index:10}.logo a{display:flex;align-items:center;gap:12px;height:100%;text-decoration:none}.logo img{height:32px;width:114px;display:block}.logo-text{font-size:22px;font-weight:600;line-height:32px;color:var(--text-dark);text-decoration:none;display:inline-flex;align-items:center;height:32px;padding-top:4px;transition:all .3s ease}.logo-text:hover{color:var(--primary-blue)}.logo-text:visited{color:var(--text-dark)}.logo-link,.mobile-logo-link,.drawer-logo-link{display:inline-block;text-decoration:none;transition:transform .3s ease}.logo-link:hover,.mobile-logo-link:hover,.drawer-logo-link:hover{transform:scale(1.05)}.logo-link img,.mobile-logo-link img,.drawer-logo-link img{display:block}.header-right{display:flex;align-items:center;gap:16px}.search-btn{width:40px;height:40px;background:var(--gray-bg);border:none;border-radius:50%;cursor:pointer;font-size:16px;color:var(--text-gray);transition:all .3s ease}.search-btn:hover{background:var(--light-bg);color:var(--primary-blue);transform:scale(1.1)}.desktop-header{display:flex;align-items:center;justify-content:space-between;width:100%}@media(max-width: 768px){.desktop-header{display:none}}.mobile-header{display:none;align-items:center;justify-content:space-between;width:100%}@media(max-width: 768px){.mobile-header{display:flex}}.mobile-left{display:flex;align-items:center;gap:clamp(8px,2.13vw,12px)}.mobile-left .mobile-logo-link{display:flex;align-items:center}.mobile-left .mobile-logo{height:clamp(16px,4.27vw,24px);width:auto;object-fit:contain}.mobile-left .mobile-logo-text{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:clamp(14px,3.73vw,18px);font-weight:700;color:#212529;text-decoration:none;white-space:nowrap}.mobile-left .mobile-logo-text:hover{color:#0049b4}.mobile-center{display:none}.mobile-center .mobile-title{font-size:18px;font-weight:700;color:var(--text-dark);margin:0}.mobile-right{display:flex;align-items:center}.mobile-right .mobile-menu-btn{width:clamp(24px,6.4vw,32px);height:clamp(24px,6.4vw,32px);background:rgba(0,0,0,0);border:none;cursor:pointer;display:flex;flex-direction:column;justify-content:center;align-items:center;gap:clamp(3px,.8vw,5px);padding:clamp(4px,1.07vw,6px);border-radius:4px;transition:background-color .3s ease}.mobile-right .mobile-menu-btn:hover{background:rgba(0,0,0,.05)}.mobile-right .mobile-menu-btn .hamburger-line{width:clamp(16px,4.27vw,22px);height:2px;background:#515961;transition:all .3s ease;border-radius:1px}.mobile-right .mobile-menu-btn.active .hamburger-line:nth-child(1){transform:rotate(45deg) translateY(5px)}.mobile-right .mobile-menu-btn.active .hamburger-line:nth-child(2){opacity:0}.mobile-right .mobile-menu-btn.active .hamburger-line:nth-child(3){transform:rotate(-45deg) translateY(-5px)}.mobile-drawer{position:fixed;top:0;left:0;right:0;bottom:0;z-index:9999;visibility:hidden;opacity:0;transition:all .3s ease}.mobile-drawer.active{visibility:visible;opacity:1}.mobile-drawer.active .drawer-content{transform:translateY(0)}.mobile-drawer .drawer-overlay{display:none}.mobile-drawer .drawer-content{position:absolute;top:0;left:0;right:0;bottom:0;width:100%;height:100%;background:#fff;transform:translateY(100%);transition:transform .3s ease;display:flex;flex-direction:column;overflow-y:auto}.mobile-drawer .drawer-header{display:flex;align-items:center;justify-content:space-between;height:44px;min-height:44px;padding:0 16px 0 20px;background:#fff}.mobile-drawer .drawer-header .drawer-header-left{display:flex;align-items:center;gap:clamp(8px,2.13vw,12px)}.mobile-drawer .drawer-header .drawer-header-left .drawer-logo-link{display:flex;align-items:center}.mobile-drawer .drawer-header .drawer-header-left .drawer-logo{height:clamp(16px,4.27vw,24px);width:auto;object-fit:contain}.mobile-drawer .drawer-header .drawer-header-left .drawer-logo-text{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:clamp(14px,3.73vw,18px);font-weight:700;color:#212529;text-decoration:none;white-space:nowrap}.mobile-drawer .drawer-header .drawer-header-right{display:flex;align-items:center;gap:8px}.mobile-drawer .drawer-header .drawer-home-btn{width:24px;height:24px;display:flex;align-items:center;justify-content:center}.mobile-drawer .drawer-header .drawer-home-btn svg{width:24px;height:24px}.mobile-drawer .drawer-header .drawer-home-btn:hover{opacity:.7}.mobile-drawer .drawer-header .drawer-close{width:24px;height:24px;background:rgba(0,0,0,0);border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;padding:0}.mobile-drawer .drawer-header .drawer-close svg{width:24px;height:24px}.mobile-drawer .drawer-header .drawer-close:hover{opacity:.7}.mobile-drawer .drawer-welcome{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:214px;padding:40px 20px;background:#ebfbff;border-bottom:1px solid #dadada}.mobile-drawer .drawer-welcome .welcome-user-icon{margin-bottom:12px}.mobile-drawer .drawer-welcome .welcome-user-icon img{width:56px;height:56px}.mobile-drawer .drawer-welcome .welcome-login-prompt{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:16px;font-weight:700;color:#212529;line-height:26px;margin:0 0 8px 0;text-align:center}.mobile-drawer .drawer-welcome .welcome-text{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:14px;font-weight:400;color:#212529;line-height:26px;margin:0 0 24px 0;text-align:center}.mobile-drawer .drawer-welcome .welcome-buttons{display:flex;gap:21px;width:100%;max-width:309px}.mobile-drawer .drawer-welcome .btn-drawer-signup{flex:1;display:flex;align-items:center;justify-content:center;height:40px;background:#e5e7eb;color:#5f666c;font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:14px;font-weight:700;border-radius:8px;text-decoration:none;transition:all .3s ease}.mobile-drawer .drawer-welcome .btn-drawer-signup:hover{background:#d1d5db}.mobile-drawer .drawer-welcome .btn-drawer-login{flex:1;display:flex;align-items:center;justify-content:center;height:40px;background:#0049b4;color:#fff;font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:14px;font-weight:700;border-radius:8px;text-decoration:none;transition:all .3s ease}.mobile-drawer .drawer-welcome .btn-drawer-login:hover{background:rgb(0,65.7,162)}.mobile-drawer .drawer-welcome.authenticated{flex-direction:row;min-height:138px;padding:40px 54px;gap:16px;justify-content:flex-start}.mobile-drawer .drawer-welcome.authenticated .welcome-user-icon{margin-bottom:0;flex-shrink:0}.mobile-drawer .drawer-welcome.authenticated .welcome-user-info{display:flex;flex-direction:column;gap:8px}.mobile-drawer .drawer-welcome.authenticated .welcome-user-name{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:16px;font-weight:700;color:#212529;line-height:22px;margin:0}.mobile-drawer .drawer-welcome.authenticated .welcome-text{margin:0;text-align:left}.mobile-drawer .drawer-footer{display:flex;justify-content:flex-end;padding:20px;background:#fff;border-top:1px solid #dadada}.mobile-drawer .drawer-footer .drawer-logout-btn{display:flex;align-items:center;gap:4px;padding:10px;background:rgba(0,0,0,0);border:none;cursor:pointer;text-decoration:none}.mobile-drawer .drawer-footer .drawer-logout-btn svg{width:16px;height:16px}.mobile-drawer .drawer-footer .drawer-logout-btn span{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:14px;font-weight:500;color:#0049b4;line-height:18px}.mobile-drawer .drawer-footer .drawer-logout-btn:hover{opacity:.8}.mobile-drawer .drawer-nav{flex:1;padding:0 20px;background:#fff}.mobile-drawer .drawer-nav .drawer-menu-list{list-style:none;margin:0;padding:0}.mobile-drawer .drawer-nav .drawer-menu-item{border-bottom:1px solid #dadada}.mobile-drawer .drawer-nav .drawer-menu-item .drawer-menu-btn{display:flex;align-items:center;justify-content:space-between;width:100%;height:56px;padding:0 8px;background:rgba(0,0,0,0);border:none;cursor:pointer;font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:16px;font-weight:500;color:#212529;text-align:left}.mobile-drawer .drawer-nav .drawer-menu-item .drawer-menu-btn .accordion-icon{transition:transform .3s ease}.mobile-drawer .drawer-nav .drawer-menu-item .drawer-submenu{display:none;list-style:none;margin:0;padding:12px 0;background:#fafafa}.mobile-drawer .drawer-nav .drawer-menu-item .drawer-submenu li a{display:block;padding:8px 16px 8px 57px;font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:14px;font-weight:500;color:#212529;text-decoration:none;line-height:28px;transition:color .3s ease}.mobile-drawer .drawer-nav .drawer-menu-item .drawer-submenu li a:hover{color:#0049b4}.mobile-drawer .drawer-nav .drawer-menu-item .drawer-submenu li:not(:first-child) a{font-weight:400;color:#515151}.mobile-drawer .drawer-nav .drawer-menu-item.open .drawer-menu-btn{font-weight:700}.mobile-drawer .drawer-nav .drawer-menu-item.open .drawer-menu-btn .accordion-icon{transform:rotate(180deg)}.mobile-drawer .drawer-nav .drawer-menu-item.open .drawer-submenu{display:block}.btn-mobilemenu{position:absolute;right:16px;top:50%;transform:translateY(-50%);width:40px;height:40px;background:rgba(0,0,0,0);border:none;cursor:pointer;padding:8px;z-index:501}.btn-mobilemenu span{display:block;width:24px;height:2px;background:#1a1a2e;position:relative;transition:all all .15s ease;text-indent:-9999px}.btn-mobilemenu span::before,.btn-mobilemenu span::after{content:"";position:absolute;left:0;width:24px;height:2px;background:#1a1a2e;transition:all all .15s ease}.btn-mobilemenu span::before{top:-8px}.btn-mobilemenu span::after{top:8px}.btn-mobilemenu.on span{background:rgba(0,0,0,0)}.btn-mobilemenu.on span::before{top:0;transform:rotate(45deg)}.btn-mobilemenu.on span::after{top:0;transform:rotate(-45deg)}.main-nav.m-nav{position:fixed;top:0;left:0;right:0;bottom:0;background:#fff;z-index:500;right:-100%;transition:none;overflow-y:auto;display:flex;flex-direction:column}.main-nav.m-nav .nav-header{padding:24px;background:#f8fafc;border-bottom:1px solid #e2e8f0}.main-nav.m-nav .nav-list{list-style:none;margin:0;padding:0;flex-grow:1}.main-nav.m-nav .nav-list .nav-item{border-bottom:1px solid #e2e8f0}.main-nav.m-nav .nav-list .nav-item>a{display:flex;align-items:center;padding:24px;color:#1a1a2e;font-size:18px;font-weight:500;text-decoration:none;position:relative}.main-nav.m-nav .nav-list .nav-item.has-submenu>a::after{content:"";position:absolute;right:24px;top:50%;width:8px;height:8px;border-right:2px solid #64748b;border-bottom:2px solid #64748b;transform:translateY(-50%) rotate(45deg);transition:transform all .15s ease}.main-nav.m-nav .nav-list .nav-item.expanded>a::after{transform:translateY(-25%) rotate(-135deg)}.main-nav.m-nav .nav-list .nav-item .sub-menu{display:none;list-style:none;padding:0;margin:0;background:#f8fafc}.main-nav.m-nav .nav-list .nav-item .sub-menu a{display:block;padding:16px 24px 16px 48px;color:#64748b;font-size:14px;text-decoration:none}.main-nav.m-nav .nav-list .nav-item .sub-menu a:hover{background:#fff;color:#0049b4}.main-nav.m-nav .nav-list .nav-item.expanded .sub-menu{display:block}.main-nav.m-nav .info_wrap{padding:32px 24px;background:#fff;border-top:1px solid #e2e8f0;margin-top:auto}.nav-menu{display:flex;list-style:none;gap:8px;margin:0;padding:0}.nav-menu>li{position:relative}.nav-menu>li.submenu-open>.nav-link{background:var(--light-bg);color:var(--primary-blue)}.nav-menu>li.submenu-open .sub-menu{visibility:visible;opacity:1;transform:translateY(0)}.nav-menu>li .sub-menu{position:absolute;top:calc(100% + 8px);left:0;min-width:200px;background:var(--white);border:1px solid var(--border-gray);border-radius:12px;box-shadow:var(--shadow-xl);padding:8px 0;visibility:hidden;opacity:0;transform:translateY(-10px);transition:all .3s ease;z-index:100;list-style:none;margin:0}.nav-menu>li .sub-menu li{margin:0}.nav-menu>li .sub-menu li a{display:block;padding:8px 24px;color:var(--text-dark);text-decoration:none;font-size:14px;font-weight:500;transition:all .3s ease}.nav-menu>li .sub-menu li a:hover{background:var(--light-bg);color:var(--primary-blue)}.nav-link{display:flex;align-items:center;gap:6px;text-decoration:none;color:var(--text-dark);font-weight:500;font-size:20px;padding:8px 16px;border-radius:8px;transition:all .3s ease}.nav-link:hover{color:var(--primary-blue)}.nav-icon{font-size:18px}.signup-btn{display:flex;align-items:center;gap:8px;text-decoration:none;color:var(--white);padding:12px 24px;border-radius:50px;font-weight:600;transition:all .3s ease;box-shadow:var(--shadow-md)}.signup-btn:hover{transform:translateY(-2px);box-shadow:var(--shadow-lg)}@media(max-width: 768px){.global-header{height:clamp(44px,11.73vw,60px);background:hsla(0,0%,100%,.85);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border-bottom:none}.global-header .container{padding:0;max-width:100%;background-color:#fefefe}.global-header .container .header-content{padding:0 clamp(16px,4.27vw,24px) 0 clamp(20px,5.33vw,28px);height:clamp(44px,11.73vw,60px);margin-top:0}}.header-user-info{display:flex;align-items:center;gap:6px;padding:4px 6px;border-radius:6px}.header-user-info .user-identity{display:flex;align-items:center;gap:2px}.header-user-info .user-identity .user-icon{width:16px;height:18px;display:block}.header-user-info .user-identity .user-name{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:16px;font-weight:400;color:#515961;white-space:nowrap}.header-user-info .divider{font-size:8px;color:var(--text-gray);line-height:1}.header-user-info .header-link{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:16px;font-weight:400;color:#515961;text-decoration:none;white-space:nowrap;background:none;border:none;padding:0;cursor:pointer;transition:color .3s ease}.header-user-info .header-link:hover{color:var(--primary-blue)}.header-user-info .mypage-dropdown{position:relative}.header-user-info .mypage-dropdown .mypage-toggle{display:flex;align-items:center;gap:4px}.header-user-info .mypage-dropdown .mypage-dropdown-menu{position:absolute;top:calc(100% + 8px);right:0;min-width:200px;background:var(--white);border:1px solid var(--border-gray);border-radius:12px;box-shadow:var(--shadow-xl);padding:8px 0;visibility:hidden;opacity:0;transform:translateY(-10px);transition:all .3s ease;z-index:100}.header-user-info .mypage-dropdown .mypage-dropdown-menu .mypage-menu-list{list-style:none;margin:0;padding:0}.header-user-info .mypage-dropdown .mypage-dropdown-menu .mypage-menu-list li{margin:0}.header-user-info .mypage-dropdown .mypage-dropdown-menu .mypage-menu-list li a{display:flex;align-items:center;gap:8px;padding:8px 24px;color:var(--text-dark);text-decoration:none;font-size:14px;font-weight:500;transition:all .3s ease}.header-user-info .mypage-dropdown .mypage-dropdown-menu .mypage-menu-list li a i{font-size:14px;width:16px;text-align:center}.header-user-info .mypage-dropdown .mypage-dropdown-menu .mypage-menu-list li a:hover{background:var(--light-bg);color:var(--primary-blue)}.header-user-info .mypage-dropdown.active .mypage-dropdown-menu{visibility:visible;opacity:1;transform:translateY(0)}@media(max-width: 768px){.header-user-info{display:none}}.global-footer{background:#fff;border-top:1px solid #e5e7eb}.global-footer .container{max-width:1200px;margin:0 auto;padding:0 20px}.footer-content{display:flex;justify-content:space-between;align-items:center;min-height:200px;padding:40px 0;gap:40px}@media(max-width: 1024px){.footer-content{flex-direction:column;align-items:flex-start;gap:32px;padding:32px 0}}.footer-left{display:flex;flex-direction:column;gap:16px}.footer-left .footer-logo{width:114px;height:auto;object-fit:contain}.footer-left .footer-links{display:flex;align-items:center;gap:8px}.footer-left .footer-links .footer-link{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:16px;font-weight:400;color:#5f666c;text-decoration:none;transition:color .3s ease}.footer-left .footer-links .footer-link:hover{color:#0049b4;text-decoration:underline}.footer-left .footer-links .footer-separator{color:#d1d5db;font-size:16px;user-select:none}.footer-left .footer-copyright{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:14px;font-weight:400;color:#9ca3af;margin:0;line-height:1.5}.footer-right{display:flex;flex-direction:column;gap:16px;align-items:flex-end}@media(max-width: 1024px){.footer-right{align-items:flex-start;width:100%}}.footer-right .footer-related-sites .related-sites-select{width:240px;height:44px;padding:0 16px;font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:16px;font-weight:400;color:#5f666c;background:#fff;border:1px solid #d1d5db;border-radius:6px;cursor:pointer;transition:all .3s ease;appearance:none;background-image:url("data:image/svg+xml,%3Csvg width='12' height='8' viewBox='0 0 12 8' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1L6 6L11 1' stroke='%235F666C' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 16px center;padding-right:40px}.footer-right .footer-related-sites .related-sites-select:hover{border-color:#0049b4}.footer-right .footer-related-sites .related-sites-select:focus{outline:none;border-color:#0049b4;box-shadow:0 0 0 3px rgba(0,73,180,.1)}@media(max-width: 1024px){.footer-right .footer-related-sites .related-sites-select{width:100%}}.footer-right .footer-contact{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:18px;font-weight:700;color:#1f2937;margin:0;line-height:1.2}@media(max-width: 1024px){.footer-right .footer-contact{font-size:16px}}@media(max-width: 768px){.global-footer .container{padding:0 20px}.footer-content{flex-direction:column;align-items:flex-start;padding:24px 0;gap:12px;min-height:auto}.footer-left{gap:8px;width:100%;order:1}.footer-left .footer-logo{width:72px;order:-2}.footer-left .footer-links{gap:9px;order:1}.footer-left .footer-links .footer-link{font-size:11px;color:#212529}.footer-left .footer-links .footer-separator{font-size:11px;color:#212529}.footer-left .footer-copyright{font-size:10px;color:#5f666c;order:3}.footer-right{width:100%;align-items:flex-start;gap:8px;order:0}.footer-right .footer-related-sites{width:100%;order:-1}.footer-right .footer-related-sites .related-sites-select{width:100%;height:44px;font-size:12px;border-radius:8px;border-color:#dadada;color:#8c959f}.footer-right .footer-contact{font-size:11px;font-weight:400;color:#212529;order:2}}@media(max-width: 768px){.footer-content{display:flex;flex-direction:column}.footer-content .footer-left{display:contents}.footer-content .footer-left .footer-logo{order:1}.footer-content .footer-left .footer-links{order:3}.footer-content .footer-left .footer-copyright{order:5}.footer-content .footer-right{display:contents}.footer-content .footer-right .footer-related-sites{order:2;width:100%}.footer-content .footer-right .footer-contact{order:4}}.grid{display:grid;gap:24px}.grid-cols-1{grid-template-columns:1fr}.grid-cols-2{grid-template-columns:repeat(2, 1fr)}.grid-cols-3{grid-template-columns:repeat(3, 1fr)}.grid-cols-4{grid-template-columns:repeat(4, 1fr)}.grid-cols-5{grid-template-columns:repeat(5, 1fr)}.grid-cols-6{grid-template-columns:repeat(6, 1fr)}.grid-cols-12{grid-template-columns:repeat(12, 1fr)}.grid-auto{grid-template-columns:repeat(auto-fit, minmax(280px, 1fr))}.grid-auto-sm{grid-template-columns:repeat(auto-fit, minmax(200px, 1fr))}.grid-auto-lg{grid-template-columns:repeat(auto-fit, minmax(350px, 1fr))}.grid.gap-0{gap:0}.grid.gap-sm{gap:8px}.grid.gap-md{gap:16px}.grid.gap-lg{gap:24px}.grid.gap-xl{gap:32px}@media(max-width: 1024px){.grid-md-cols-1{grid-template-columns:1fr}.grid-md-cols-2{grid-template-columns:repeat(2, 1fr)}}@media(max-width: 768px){.grid-sm-cols-1{grid-template-columns:1fr}.grid-sm-cols-2{grid-template-columns:repeat(2, 1fr)}}.col-span-1{grid-column:span 1}.col-span-2{grid-column:span 2}.col-span-3{grid-column:span 3}.col-span-4{grid-column:span 4}.col-span-5{grid-column:span 5}.col-span-6{grid-column:span 6}.col-span-full{grid-column:1/-1}.row-span-1{grid-row:span 1}.row-span-2{grid-row:span 2}.row-span-3{grid-row:span 3}.api-grid{display:grid;grid-template-columns:repeat(auto-fit, minmax(280px, 1fr));gap:24px}@media(max-width: 1024px){.api-grid{grid-template-columns:repeat(2, 1fr)}}@media(max-width: 768px){.api-grid{grid-template-columns:1fr}}.experience-grid{display:grid;grid-template-columns:repeat(auto-fit, minmax(280px, 1fr));gap:24px}@media(max-width: 768px){.experience-grid{grid-template-columns:1fr}}.partners-grid{display:grid;grid-template-columns:repeat(auto-fit, minmax(200px, 1fr));gap:40px;align-items:center}@media(max-width: 768px){.partners-grid{grid-template-columns:repeat(2, 1fr)}}.feature-grid{display:grid;grid-template-columns:repeat(2, 1fr);gap:32px}@media(max-width: 768px){.feature-grid{grid-template-columns:1fr}}.demo-wrapper{display:grid;grid-template-columns:1fr 1fr;gap:40px;align-items:start}@media(max-width: 1024px){.demo-wrapper{grid-template-columns:1fr}}.footer-grid{display:grid;grid-template-columns:2fr 3fr;gap:80px}@media(max-width: 1024px){.footer-grid{grid-template-columns:1fr;gap:40px}}.footer-grid .footer-links{display:grid;grid-template-columns:repeat(4, 1fr);gap:40px}@media(max-width: 1024px){.footer-grid .footer-links{grid-template-columns:repeat(2, 1fr)}}@media(max-width: 768px){.footer-grid .footer-links{grid-template-columns:1fr;text-align:center}}.pricing-grid{display:grid;grid-template-columns:repeat(3, 1fr);gap:24px;align-items:center}@media(max-width: 1024px){.pricing-grid{grid-template-columns:1fr;gap:32px}}.testimonial-grid{display:grid;grid-template-columns:repeat(auto-fit, minmax(350px, 1fr));gap:24px}@media(max-width: 768px){.testimonial-grid{grid-template-columns:1fr}}.stats-grid{display:grid;grid-template-columns:repeat(4, 1fr);gap:32px;text-align:center}@media(max-width: 1024px){.stats-grid{grid-template-columns:repeat(2, 1fr)}}@media(max-width: 768px){.stats-grid{grid-template-columns:1fr}}.two-column{display:grid;grid-template-columns:1fr 1fr;gap:48px;align-items:center}@media(max-width: 1024px){.two-column{grid-template-columns:1fr;gap:32px}}.two-column.reverse{direction:rtl}.two-column.reverse>*{direction:ltr}@media(max-width: 1024px){.two-column.reverse{direction:ltr}}.three-column{display:grid;grid-template-columns:repeat(3, 1fr);gap:32px}@media(max-width: 1024px){.three-column{grid-template-columns:1fr}}.container{max-width:1280px;margin:0 auto;padding:0 26px}@media(max-width: 768px){.container{padding:0 16px}}.container-fluid{max-width:100%;padding:0 24px}.container-narrow{max-width:960px}.container-wide{max-width:1440px}.section{padding:100px 0}@media(max-width: 768px){.section{padding:64px 0}}.section-sm{padding:48px 0}.section-lg{padding:150px 0}.section-no-top{padding-top:0}.section-no-bottom{padding-bottom:0}.section-bg-gray{background:#f8fafc}.section-bg-light{background:#eff6ff}.section-bg-primary{color:#fff}.section-bg-dark{background:#1a1a2e;color:#fff}.section-header{text-align:center;margin-bottom:64px}@media(max-width: 768px){.section-header{margin-bottom:48px}}.section-header .section-badge{display:inline-flex;align-items:center;gap:4px;padding:4px 16px;background:#eff6ff;color:#0049b4;border-radius:50px;font-size:14px;font-weight:600;margin-bottom:16px}.section-header .section-title{font-size:40px;font-weight:700;margin-bottom:16px;color:#1a1a2e}@media(max-width: 768px){.section-header .section-title{font-size:32px}}.section-header .section-subtitle{font-size:18px;color:#64748b;line-height:1.6;max-width:600px;margin:0 auto}@media(max-width: 768px){.section-header .section-subtitle{font-size:16px}}.content-wrapper{position:relative;min-height:100vh;display:flex;flex-direction:column}.content-wrapper .main-content{flex:1;padding-top:80px}.page-wrapper{position:relative;overflow:hidden}.inner-content{position:relative;z-index:1}.btn{display:inline-flex;align-items:center;gap:8px;padding:16px 32px;border-radius:12px;font-weight:600;font-size:16px;text-decoration:none;transition:all .3s ease;cursor:pointer;border:none}.btn:hover{transform:translateY(-3px)}.btn{position:relative;overflow:hidden}.btn-sm{padding:8px 16px;font-size:14px}.btn-md{padding:16px 24px;font-size:16px}.btn-lg{padding:16px 32px;font-size:18px}.btn-xl{padding:24px 40px;font-size:20px}.btn-primary{background:#0049b4;color:#fff;box-shadow:0 4px 12px rgba(75,155,255,.1)}.btn-primary:hover{transform:translateY(-3px);box-shadow:0 16px 40px rgba(75,155,255,.2)}.btn-primary:active{transform:translateY(-1px)}.btn-secondary{background:#e5e7eb;color:#5f666c}.btn-secondary:hover{background:#eff6ff;transform:translateY(-3px)}.btn-accent{color:#fff;box-shadow:0 4px 12px rgba(75,155,255,.1)}.btn-accent:hover{transform:translateY(-3px);box-shadow:0 16px 40px rgba(75,155,255,.2)}.btn-warm{color:#fff;box-shadow:0 4px 12px rgba(75,155,255,.1)}.btn-warm:hover{transform:translateY(-3px);box-shadow:0 16px 40px rgba(75,155,255,.2)}.btn-success{background:#6bcf7f;color:#fff}.btn-success:hover{background:hsl(132,51.0204081633%,55.4117647059%);transform:translateY(-3px)}.btn-danger{background:#ff6b6b;color:#fff}.btn-danger:hover{background:rgb(255,70.8,70.8);transform:translateY(-3px)}.btn-ghost{background:rgba(0,0,0,0);color:#0049b4;border:2px solid rgba(0,0,0,0)}.btn-ghost:hover{background:rgba(0,73,180,.1);border-color:#0049b4}.btn-outline{background:rgba(0,0,0,0);color:#1a1a2e;border:2px solid #e2e8f0}.btn-outline:hover{border-color:#0049b4;color:#0049b4;transform:translateY(-3px)}.btn:disabled,.btn.disabled{opacity:.5;cursor:not-allowed;pointer-events:none}.btn.loading{color:rgba(0,0,0,0);pointer-events:none}.btn.loading::after{content:"";position:absolute;width:16px;height:16px;position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);border:2px solid #fff;border-radius:50%;border-top-color:rgba(0,0,0,0);animation:spin .6s linear infinite}.btn-icon{display:inline-flex;align-items:center;justify-content:center;width:44px;height:44px;padding:0;border-radius:50%}.btn-icon.btn-sm{width:32px;height:32px}.btn-icon.btn-lg{width:56px;height:56px}.btn-block{width:100%;justify-content:center}.btn-group{display:inline-flex;gap:-1px}.btn-group .btn{border-radius:0}.btn-group .btn:first-child{border-top-left-radius:12px;border-bottom-left-radius:12px}.btn-group .btn:last-child{border-top-right-radius:12px;border-bottom-right-radius:12px}.fab{position:fixed;bottom:32px;right:32px;width:60px;height:60px;border:none;border-radius:50%;color:#fff;font-size:24px;cursor:pointer;box-shadow:0 8px 24px rgba(75,155,255,.15);transition:all .3s ease;z-index:300;display:flex;justify-content:center;align-items:center}.fab:hover{transform:scale(1.1);box-shadow:0 16px 40px rgba(75,155,255,.2)}.fab:hover .fab-label{opacity:1;transform:translateX(-10px)}.fab .fab-label{position:absolute;right:70px;top:50%;transform:translateY(-50%);background:#1a1a2e;color:#fff;padding:8px 16px;border-radius:8px;font-size:14px;white-space:nowrap;opacity:0;transition:all .3s ease;pointer-events:none}.btn-cta-primary{display:inline-flex;align-items:center;gap:8px;padding:16px 32px;border-radius:12px;font-weight:600;font-size:16px;text-decoration:none;transition:all .3s ease;cursor:pointer;border:none}.btn-cta-primary:hover{transform:translateY(-3px)}.btn-cta-primary{background:#fff;color:#0049b4;padding:16px 40px;font-size:18px;box-shadow:0 8px 24px rgba(75,155,255,.15)}.btn-cta-primary:hover{transform:translateY(-3px);box-shadow:0 16px 40px rgba(75,155,255,.2)}.btn-cta-secondary{display:inline-flex;align-items:center;gap:8px;padding:16px 32px;border-radius:12px;font-weight:600;font-size:16px;text-decoration:none;transition:all .3s ease;cursor:pointer;border:none}.btn-cta-secondary:hover{transform:translateY(-3px)}.btn-cta-secondary{background:rgba(0,0,0,0);color:#fff;border:2px solid #fff;padding:16px 40px;font-size:18px}.btn-cta-secondary:hover{background:hsla(0,0%,100%,.1);transform:translateY(-3px)}.action-btn,.action-btn-new,.action-btn-primary,.action-btn-secondary,.action-btn-edit,.action-btn-delete{padding:16px 40px;border-radius:8px;font-size:16px;font-weight:500;text-decoration:none;transition:all .3s ease;display:inline-flex;align-items:center;justify-content:center;gap:8px;cursor:pointer;border:none}@media(max-width: 768px){.action-btn,.action-btn-new,.action-btn-primary,.action-btn-secondary,.action-btn-edit,.action-btn-delete{padding:16px 24px;font-size:14px}}.action-btn i,.action-btn-new i,.action-btn-primary i,.action-btn-secondary i,.action-btn-edit i,.action-btn-delete i{font-size:16px}.action-btn-new,.action-btn-primary{color:#fff}.action-btn-new:hover,.action-btn-primary:hover{transform:translateY(-2px);box-shadow:0 4px 12px rgba(75,155,255,.1)}.action-btn-new:active,.action-btn-primary:active{transform:translateY(0)}.action-btn-secondary{background:#fff;color:#1a1a2e;border:1px solid #e2e8f0}.action-btn-secondary:hover{background:#f8fafc;border-color:#0049b4;color:#0049b4}.action-btn-secondary:active{transform:scale(0.98)}.action-btn-edit{background:#0049b4;color:#fff}.action-btn-edit:hover{transform:translateY(-2px);box-shadow:0 4px 12px rgba(75,155,255,.1);background:#c3dfea}.action-btn-edit:active{transform:translateY(0)}.action-btn-delete{background:#ff6b6b;color:#fff}.action-btn-delete:hover{transform:translateY(-2px);box-shadow:0 4px 12px rgba(75,155,255,.1);background:rgb(255,88.9,88.9)}.action-btn-delete:active{transform:translateY(0)}.action-btn.btn-list{background:#fff;color:#1a1a2e;border:1px solid #e2e8f0}.action-btn.btn-list:hover{background:#f8fafc;border-color:#0049b4;color:#0049b4}.action-btn.btn-edit{color:#fff}.action-btn.btn-edit:hover{transform:translateY(-2px);box-shadow:0 4px 12px rgba(75,155,255,.1)}.action-btn.btn-delete{background:#ff6b6b;color:#fff}.action-btn.btn-delete:hover{transform:translateY(-2px);box-shadow:0 4px 12px rgba(75,155,255,.1)}.btn-copy{padding:8px 16px;background:rgba(0,73,180,.1);color:#0049b4;border:1px solid rgba(0,73,180,.2);border-radius:6px;font-size:12px;font-weight:500;cursor:pointer;transition:all .3s ease;display:inline-flex;align-items:center;gap:4px}.btn-copy:hover{background:rgba(0,73,180,.2);border-color:#0049b4}.btn-copy i{font-size:12px}.btn-action{padding:8px 24px;color:#fff;border:none;border-radius:8px;font-size:14px;font-weight:500;cursor:pointer;transition:all .3s ease;display:inline-flex;align-items:center;gap:4px}.btn-action:hover{transform:translateY(-2px);box-shadow:0 4px 12px rgba(75,155,255,.1)}.btn-action i{font-size:14px}.btn-cancel{padding:8px 24px;background:#fff;color:#64748b;border:1px solid #e2e8f0;border-radius:8px;font-size:14px;font-weight:500;cursor:pointer;transition:all .3s ease;display:inline-flex;align-items:center;gap:4px}.btn-cancel:hover{border-color:#0049b4;color:#0049b4}.btn-cancel i{font-size:14px}.btn-input-action{flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;min-width:172px;height:60px;padding:10px 24px;border:none;border-radius:12px;font-size:20px;font-weight:700;color:#fff;cursor:pointer;transition:all .3s ease;white-space:nowrap}.btn-input-action.btn-change{background:#a4d6ea}.btn-input-action.btn-change:hover{background:rgb(131.6625,199.4303571429,226.5375);transform:translateY(-2px);box-shadow:0 4px 12px rgba(75,155,255,.1)}.btn-input-action.btn-change:active{transform:translateY(0)}.btn-input-action.btn-auth{background:#a4d6ea}.btn-input-action.btn-auth:hover{background:#c3dfea;transform:translateY(-2px);box-shadow:0 4px 12px rgba(75,155,255,.1)}.btn-input-action.btn-auth:active{transform:translateY(0)}.btn-input-action.btn-auth:disabled{background:#e2e8f0;cursor:not-allowed;transform:none;box-shadow:none}@media(max-width: 768px){.btn-input-action{width:100%;min-width:auto;height:52px;font-size:16px}}.btn-upload{display:inline-flex;align-items:center;justify-content:center;gap:6px;height:40px;padding:8px 16px;background-color:#3ba4ed;color:#fff;border:none;border-radius:8px;font-size:14px;font-weight:600;cursor:pointer;transition:background-color .2s ease}.btn-upload:hover{background-color:#2b94dd}@media(max-width: 1024px){.btn-upload{font-size:13px;height:36px}}.status-badge{display:inline-flex;align-items:center;justify-content:center;padding:4px 16px;border-radius:50px;font-size:12px;font-weight:600;white-space:nowrap;transition:all .3s ease}.status-badge.status-pending{background:rgba(255,107,107,.1);color:#ff6b6b}.status-badge.status-completed{background:rgba(107,207,127,.1);color:#6bcf7f}.status-badge.status-active{background:rgba(0,73,180,.1);color:#0049b4}.status-badge.status-inactive{background:rgba(100,116,139,.1);color:#64748b}.status-badge.status-processing{background:rgba(255,217,61,.1);color:rgb(221.2,177.8721649485,0)}.status-badge.status-failed{background:rgba(255,107,107,.1);color:#ff6b6b}.status-badge.status-success{background:rgba(107,207,127,.1);color:#6bcf7f}.status-badge-header{display:inline-flex;align-items:center;justify-content:center;padding:4px 16px;border-radius:50px;font-size:12px;font-weight:600;margin-bottom:16px;transition:all .3s ease}.status-badge-header.status-pending{background:rgba(255,107,107,.1);color:#ff6b6b}.status-badge-header.status-completed{background:rgba(107,207,127,.1);color:#6bcf7f}.status-badge-header.status-active{background:rgba(0,73,180,.1);color:#0049b4}.status-badge-header.status-inactive{background:rgba(100,116,139,.1);color:#64748b}.status-badge-header.status-processing{background:rgba(255,217,61,.1);color:rgb(221.2,177.8721649485,0)}.badge-sm{padding:2px 8px;font-size:10px;border-radius:6px}.badge-lg{padding:8px 24px;font-size:14px;border-radius:8px}.badge-outline{background:rgba(0,0,0,0);border:1px solid currentColor}.badge-outline.status-pending{color:#ff6b6b;border-color:#ff6b6b}.badge-outline.status-completed{color:#6bcf7f;border-color:#6bcf7f}.badge-outline.status-active{color:#0049b4;border-color:#0049b4}.badge-outline.status-inactive{color:#64748b;border-color:#64748b}.badge-icon i{margin-right:4px;font-size:.9em}.notification-badge{display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:20px;padding:0 4px;background:#ff6b6b;color:#fff;border-radius:50%;font-size:11px;font-weight:700;line-height:1}.notification-badge.badge-primary{background:#0049b4}.notification-badge.badge-success{background:#6bcf7f}.notification-badge.badge-warning{background:#ffd93d;color:#1a1a2e}.notification-badge.badge-danger{background:#ff6b6b}.card{background:#fff;border-radius:20px;padding:32px;transition:all .3s ease}.card:hover{box-shadow:0 8px 24px rgba(75,155,255,.15)}.card{position:relative;overflow:hidden}.api-card{background:#fff;border-radius:20px;padding:32px;transition:all .3s ease}.api-card:hover{box-shadow:0 8px 24px rgba(75,155,255,.15)}.api-card{text-align:center;border:2px solid #e2e8f0}.api-card:hover{border-color:#0049b4}.api-card-popular{border-color:#ffd93d}.api-card-new{border-color:#6bcf7f}.api-card .api-badge{position:absolute;top:-12px;right:20px;padding:4px 16px;background:#ffd93d;color:#1a1a2e;border-radius:20px;font-size:12px;font-weight:700;text-transform:uppercase}.api-card .api-badge.new{background:#6bcf7f;color:#fff}.api-card .api-badge.beta{background:#a78bfa;color:#fff}.api-card .api-icon{width:60px;height:60px;margin:0 auto 24px;display:flex;justify-content:center;align-items:center;font-size:28px;background:#eff6ff;color:#0049b4;border-radius:16px;transition:all .3s ease}.api-card:hover .api-icon{transform:scale(1.1);color:#fff}.api-card .api-name{font-size:20px;font-weight:700;margin-bottom:16px;color:#1a1a2e}.api-card .api-description{color:#64748b;line-height:1.6;margin-bottom:24px;font-size:14px}.api-card .api-features{display:flex;justify-content:center;gap:8px;margin-bottom:24px;flex-wrap:wrap}.api-card .api-features .feature-tag{padding:4px 8px;background:#f8fafc;color:#64748b;border-radius:20px;font-size:12px;font-weight:500}.api-card .api-link{display:inline-flex;align-items:center;gap:4px;color:#0049b4;font-weight:600;font-size:14px;transition:all .3s ease}.api-card .api-link:hover{gap:8px;color:#c3dfea}.experience-card{background:#fff;border-radius:20px;padding:32px;transition:all .3s ease}.experience-card:hover{box-shadow:0 8px 24px rgba(75,155,255,.15)}.experience-card{text-align:center}.experience-card .card-icon{width:56px;height:56px;margin:0 auto 24px;display:flex;justify-content:center;align-items:center;font-size:24px;color:#fff;border-radius:16px}.experience-card h3{font-size:20px;font-weight:700;margin-bottom:16px;color:#1a1a2e}.experience-card p{color:#64748b;line-height:1.6;margin-bottom:24px;font-size:14px}.experience-card .card-metric{display:flex;flex-direction:column;align-items:center;padding-top:24px;border-top:1px solid #e2e8f0}.experience-card .card-metric .metric-value{font-size:24px;font-weight:800;color:#0049b4}.experience-card .card-metric .metric-label{font-size:12px;color:#64748b;font-weight:500}.feature-card{background:#fff;border-radius:20px;padding:32px;transition:all .3s ease}.feature-card:hover{box-shadow:0 8px 24px rgba(75,155,255,.15)}.feature-card{display:flex;align-items:flex-start;gap:24px}.feature-card .feature-icon{flex-shrink:0;width:48px;height:48px;display:flex;justify-content:center;align-items:center;color:#fff;border-radius:12px;font-size:24px}.feature-card .feature-content{flex:1}.feature-card .feature-content h4{font-size:18px;font-weight:600;margin-bottom:8px;color:#1a1a2e}.feature-card .feature-content p{color:#64748b;line-height:1.6;font-size:14px}.testimonial-card{background:#fff;border-radius:20px;padding:32px;transition:all .3s ease}.testimonial-card:hover{box-shadow:0 8px 24px rgba(75,155,255,.15)}.testimonial-card{position:relative}.testimonial-card::before{content:'"';position:absolute;top:16px;left:16px;font-size:60px;color:#0049b4;opacity:.1;font-weight:700}.testimonial-card .testimonial-content{position:relative;z-index:1;font-size:16px;line-height:1.8;color:#1a1a2e;margin-bottom:24px;font-style:italic}.testimonial-card .testimonial-author{display:flex;align-items:center;gap:16px}.testimonial-card .testimonial-author .author-avatar{width:48px;height:48px;border-radius:50%;object-fit:cover}.testimonial-card .testimonial-author .author-info .author-name{font-weight:600;color:#1a1a2e;margin-bottom:2px}.testimonial-card .testimonial-author .author-info .author-title{font-size:14px;color:#64748b}.pricing-card{background:#fff;border-radius:20px;padding:32px;transition:all .3s ease}.pricing-card:hover{box-shadow:0 8px 24px rgba(75,155,255,.15)}.pricing-card{text-align:center;position:relative}.pricing-card.featured{border:2px solid #0049b4;transform:scale(1.05)}.pricing-card.featured .pricing-badge{position:absolute;top:-14px;left:50%;transform:translateX(-50%);padding:4px 16px;color:#fff;border-radius:50px;font-size:12px;font-weight:700;text-transform:uppercase}.pricing-card .pricing-header{padding-bottom:24px;border-bottom:1px solid #e2e8f0;margin-bottom:24px}.pricing-card .pricing-header h3{font-size:24px;font-weight:700;margin-bottom:8px;color:#1a1a2e}.pricing-card .pricing-header .price{display:flex;align-items:baseline;justify-content:center;gap:4px}.pricing-card .pricing-header .price .currency{font-size:20px;color:#64748b}.pricing-card .pricing-header .price .amount{font-size:48px;font-weight:800}.pricing-card .pricing-header .price .period{font-size:16px;color:#64748b}.pricing-card .pricing-features{list-style:none;margin-bottom:24px}.pricing-card .pricing-features li{padding:8px 0;color:#64748b;font-size:14px;display:flex;align-items:center;gap:8px}.pricing-card .pricing-features li::before{content:"✓";color:#6bcf7f;font-weight:700}.pricing-card .pricing-features li.disabled{opacity:.5}.pricing-card .pricing-features li.disabled::before{content:"×";color:#94a3b8}.pricing-card .pricing-cta{margin-top:auto}.common-container,.org-register-container,.app-management-container,.apikey-detail-container,.apikey-register-container{max-width:1280px;margin:0 auto;padding:48px 0}@media(max-width: 768px){.common-container,.org-register-container,.app-management-container,.apikey-detail-container,.apikey-register-container{padding:40px 16px 60px}}.common-section-box,.app-list-container,.register-form-container,.register-result{background-color:#f6f9fb;border-radius:12px;padding:30px 45px}@media(max-width: 768px){.common-section-box,.app-list-container,.register-form-container,.register-result{padding:24px}}.common-title-bar,.app-list-title-section,.register-title-bar{background-color:#f6f9fb;border-radius:12px;padding:14px 45px;margin-bottom:30px}@media(max-width: 768px){.common-title-bar,.app-list-title-section,.register-title-bar{background-color:rgba(0,0,0,0);border-radius:0;padding:20px 20px 0;margin-bottom:0;border-bottom:1.5px solid #212529}}.common-title-bar .common-title,.app-list-title-section .common-title,.register-title-bar .common-title{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:22px;font-weight:700;color:#1a1a2e;margin:0}@media(max-width: 768px){.common-title-bar .common-title,.app-list-title-section .common-title,.register-title-bar .common-title{font-size:16px;color:#000;margin-bottom:4px;display:inline}}.common-title-bar--simple{background:none;padding:0 0 16px 0;border-radius:0;border-bottom:2px solid #212529;margin-bottom:32px}.common-title-bar--simple h3{font-size:22px;font-weight:700;color:#212529;margin:0}.common-title-bar--simple .required-badge{display:none}@media(max-width: 768px){.common-title-bar--simple{padding:20px;padding-bottom:12px;margin-bottom:0;border-bottom:1.5px solid #212529}.common-title-bar--simple h3{font-size:16px;color:#000;display:inline}}.common-title-bar .common-subtitle,.app-list-title-section .common-subtitle,.register-title-bar .common-subtitle{font-size:16px;font-weight:400;color:#515151}@media(max-width: 768px){.common-title-bar .common-subtitle,.app-list-title-section .common-subtitle,.register-title-bar .common-subtitle{font-size:12px;color:#515151;display:inline;margin-left:8px}}.apikey-status-badge,.app-status-badge,.status-badge{display:inline-flex;align-items:center;justify-content:center;padding:5px 14px;border-radius:40px;font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:13px;font-weight:700;color:#fff;white-space:nowrap}.apikey-status-badge.status-pending,.status-pending.app-status-badge,.status-pending.status-badge{background-color:#8c959f}.apikey-status-badge.status-approved,.status-approved.app-status-badge,.status-approved.status-badge,.apikey-status-badge.status-active,.status-active.app-status-badge,.status-active.status-badge{background-color:#0049b4}.apikey-status-badge.status-changing,.status-changing.app-status-badge,.status-changing.status-badge{background-color:#88a5f3}.apikey-status-badge.status-inactive,.status-inactive.app-status-badge,.status-inactive.status-badge,.apikey-status-badge.status-rejected,.status-rejected.app-status-badge,.status-rejected.status-badge{background-color:#dc3545}.apikey-status-badge.status-light.status-pending,.status-light.status-pending.app-status-badge,.status-light.status-pending.status-badge{background-color:#fff3cd;color:#856404}.apikey-status-badge.status-light.status-approved,.status-light.status-approved.app-status-badge,.status-light.status-approved.status-badge,.apikey-status-badge.status-light.status-active,.status-light.status-active.app-status-badge,.status-light.status-active.status-badge{background-color:#d4edda;color:#155724}.apikey-status-badge.status-light.status-rejected,.status-light.status-rejected.app-status-badge,.status-light.status-rejected.status-badge,.apikey-status-badge.status-light.status-inactive,.status-light.status-inactive.app-status-badge,.status-light.status-inactive.status-badge{background-color:#f8d7da;color:#721c24}.apikey-status-indicator,.status-indicator{display:inline-flex;align-items:center;gap:8px;padding:4px 16px;border-radius:16px;font-size:14px;font-weight:600}.apikey-status-indicator .status-dot,.status-indicator .status-dot{width:8px;height:8px;border-radius:50%;background-color:currentColor}.apikey-status-indicator.status-active,.status-active.status-indicator{background-color:#d4edda;color:#155724}.apikey-status-indicator.status-inactive,.status-inactive.status-indicator{background-color:#f8d7da;color:#721c24}.apikey-icon-display,.app-list-icon,.app-icon-display{width:120px;height:120px}.apikey-icon-display.icon-sm,.icon-sm.app-list-icon,.icon-sm.app-icon-display{width:70px;height:70px}@media(max-width: 768px){.apikey-icon-display.icon-sm,.icon-sm.app-list-icon,.icon-sm.app-icon-display{width:60px;height:60px}}.apikey-icon-display.icon-md,.icon-md.app-list-icon,.icon-md.app-icon-display{width:100px;height:100px}.apikey-icon-image,.app-list-icon .app-icon-image,.app-icon-image{width:100%;height:100%;object-fit:cover;border-radius:12px;border:2px solid #e2e8f0}.apikey-icon-placeholder,.app-list-icon .app-icon-placeholder,.app-icon-placeholder{width:100%;height:100%;display:flex;align-items:center;justify-content:center;background-color:#e9ecef;border-radius:12px;color:#868e96;font-size:48px}.apikey-icon-placeholder.placeholder-sm,.placeholder-sm.app-icon-placeholder{font-size:24px}.apikey-empty-state,.app-list-empty,.empty-state,.empty-api-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:80px 24px;text-align:center}.apikey-empty-state .empty-icon,.app-list-empty .empty-icon,.empty-state .empty-icon,.empty-api-state .empty-icon{font-size:64px;margin-bottom:24px;opacity:.5}.apikey-empty-state h3,.app-list-empty h3,.empty-state h3,.empty-api-state h3{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:24px;font-weight:700;color:#1a1a2e;margin:0 0 16px}.apikey-empty-state p,.app-list-empty p,.empty-state p,.empty-api-state p{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:16px;color:#6e7780;margin:0}.apikey-action-btn,.btn-app-create,.btn-submit{display:inline-flex;align-items:center;justify-content:center;width:220px;height:60px;border:none;border-radius:12px;font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:18px;font-weight:700;cursor:pointer;text-decoration:none;transition:background-color .2s ease,transform .2s ease}.apikey-action-btn:hover,.btn-app-create:hover,.btn-submit:hover{transform:translateY(-2px)}.apikey-action-btn:active,.btn-app-create:active,.btn-submit:active{transform:translateY(0)}.apikey-action-btn.btn-primary,.btn-primary.btn-app-create,.btn-primary.btn-submit{background-color:#0049b4;color:#fff}.apikey-action-btn.btn-primary:hover,.btn-primary.btn-app-create:hover,.btn-primary.btn-submit:hover{background-color:#003a91}.apikey-action-btn.btn-secondary,.btn-secondary.btn-app-create,.btn-secondary.btn-submit{background-color:#e5e7eb;color:#5f666c}.apikey-action-btn.btn-secondary:hover,.btn-secondary.btn-app-create:hover,.btn-secondary.btn-submit:hover{background-color:#d1d5db}.apikey-action-btn.btn-accent,.btn-accent.btn-app-create,.btn-accent.btn-submit{background-color:#3ba4ed;color:#fff}.apikey-action-btn.btn-accent:hover,.btn-accent.btn-app-create:hover,.btn-accent.btn-submit:hover{background-color:#2b94dd}@media(max-width: 768px){.apikey-action-btn,.btn-app-create,.btn-submit{width:100%;max-width:300px;height:54px;font-size:16px}}.apikey-btn-copy,.btn-copy{display:inline-flex;align-items:center;gap:4px;padding:6px 12px;background:#fff;border:1px solid #e2e8f0;border-radius:6px;font-size:14px;color:#1a1a2e;cursor:pointer;transition:all .3s ease}.apikey-btn-copy svg,.btn-copy svg{width:16px;height:16px}.apikey-btn-copy:hover,.btn-copy:hover{background:#0049b4;border-color:#0049b4;color:#fff}@media(max-width: 768px){.apikey-btn-copy,.btn-copy{align-self:stretch;justify-content:center}}.apikey-detail-section,.detail-section{background:#fff;border-radius:12px;padding:64px;margin-bottom:24px;box-shadow:0 4px 12px rgba(75,155,255,.1)}@media(max-width: 768px){.apikey-detail-section,.detail-section{padding:32px}}.apikey-detail-section .section-title,.detail-section .section-title{font-size:24px;font-weight:700;color:#1a1a2e;margin-bottom:24px;padding-bottom:16px}.apikey-detail-grid,.detail-grid{display:grid;grid-template-columns:repeat(2, 1fr);gap:24px}@media(max-width: 768px){.apikey-detail-grid,.detail-grid{grid-template-columns:1fr}}.apikey-detail-item,.detail-item{display:flex;flex-direction:column;gap:8px}.apikey-detail-item.full-width,.full-width.detail-item{grid-column:1/-1}.apikey-detail-item .detail-label,.detail-item .detail-label{font-size:14px;font-weight:600;color:#64748b}.apikey-detail-item .detail-value,.detail-item .detail-value{font-size:16px;color:#1a1a2e;word-break:break-word}.apikey-ip-list,.ip-list{display:flex;flex-wrap:wrap;gap:8px}.apikey-ip-tag,.ip-tag{display:inline-block;padding:4px 16px;background-color:#eff6ff;color:#c3dfea;border-radius:6px;font-size:14px;font-family:"Fira Code","Courier New",monospace}.apikey-credential-code,.credential-code{display:inline-block;padding:8px 16px;background-color:#f8fafc;border:1px solid #e2e8f0;border-radius:6px;font-family:"Fira Code","Courier New",monospace;font-size:14px;color:#c3dfea}.apikey-secret-box,.secret-box{display:flex;align-items:center;gap:16px}@media(max-width: 768px){.apikey-secret-box,.secret-box{flex-direction:column;align-items:flex-start}}.apikey-api-list,.api-detail-list{display:flex;flex-direction:column;gap:8px}.apikey-api-item,.api-list-item{display:flex;align-items:center;gap:16px;padding:16px 24px;background:#fff;transition:all .2s}.apikey-api-item:hover,.api-list-item:hover{border-color:#0049b4;box-shadow:0 2px 4px rgba(0,0,0,.05)}@media(max-width: 768px){.apikey-api-item,.api-list-item{padding:16px;gap:8px}}.apikey-api-item .api-method,.api-list-item .api-method{display:inline-block;padding:4px 16px;border-radius:6px;font-size:14px;font-weight:700;font-family:"Fira Code","Courier New",monospace;min-width:80px;text-align:center}.apikey-api-item .api-method.method-get,.api-list-item .api-method.method-get{background-color:#d1fae5;color:#065f46}.apikey-api-item .api-method.method-post,.api-list-item .api-method.method-post{background-color:#dbeafe;color:#1e40af}.apikey-api-item .api-method.method-put,.api-list-item .api-method.method-put{background-color:#fef3c7;color:#92400e}.apikey-api-item .api-method.method-delete,.api-list-item .api-method.method-delete{background-color:#fee2e2;color:#991b1b}.apikey-api-item .api-method.method-other,.api-list-item .api-method.method-other{background-color:#e2e8f0;color:#1a1a2e}.apikey-api-item .api-name,.api-list-item .api-name{flex:1;font-size:16px;font-weight:500;color:#1a1a2e;margin:0}.apikey-api-item .api-status,.api-list-item .api-status{padding:4px 16px;border-radius:12px;font-size:14px;font-weight:600}.apikey-api-item .api-status.status-pending,.api-list-item .api-status.status-pending{background-color:#fff3cd;color:#856404}.apikey-api-item .api-status.status-active,.api-list-item .api-status.status-active{background-color:#d4edda;color:#155724}.apikey-form-actions,.detail-actions{display:flex;justify-content:center;gap:16px;flex-wrap:wrap;margin-top:64px;padding-top:64px;border-top:1px solid #e2e8f0}@media(max-width: 768px){.apikey-form-actions,.detail-actions{flex-direction:column;align-items:stretch}}@media(max-width: 768px){.apikey-form-actions .btn,.detail-actions .btn{width:100%}}.form-group{margin-bottom:24px}.form-group label{display:block;font-size:14px;font-weight:500;color:#1a1a2e;margin-bottom:8px}.form-group label .required{color:#ff6b6b;margin-left:2px}.form-group .form-hint{font-size:12px;color:#64748b;margin-top:4px}.form-group .form-error{font-size:12px;color:#ff6b6b;margin-top:4px;display:flex;align-items:center;gap:4px}.form-control{width:100%;padding:12px 16px;font-size:16px;font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;color:#1a1a2e;background:#fff;border:2px solid #e2e8f0;border-radius:8px;transition:all .3s ease}.form-control::placeholder{color:#94a3b8}.form-control:focus{outline:none;border-color:#0049b4;box-shadow:0 0 0 3px rgba(0,73,180,.1)}.form-control:disabled{background:#f8fafc;color:#64748b;cursor:not-allowed}.form-control-sm{padding:8px 16px;font-size:14px}.form-control-lg{padding:16px 24px;font-size:18px}.form-control.is-valid{border-color:#6bcf7f}.form-control.is-valid:focus{box-shadow:0 0 0 3px rgba(107,207,127,.1)}.form-control.is-invalid{border-color:#ff6b6b}.form-control.is-invalid:focus{box-shadow:0 0 0 3px rgba(255,107,107,.1)}textarea.form-control{min-height:120px;resize:vertical}select.form-control{appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath d='M6 8L0 0h12z' fill='%2364748B'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 16px center;background-size:12px;padding-right:40px}.form-check{display:flex;align-items:center;margin-bottom:16px}.form-check input[type=checkbox],.form-check input[type=radio]{width:20px;height:20px;margin-right:8px;cursor:pointer}.form-check label{margin-bottom:0;cursor:pointer;user-select:none}.form-check.disabled{opacity:.5;cursor:not-allowed}.form-check.disabled input,.form-check.disabled label{cursor:not-allowed}.form-switch{display:flex;align-items:center;gap:16px}.form-switch .switch{position:relative;width:48px;height:24px;background:#e2e8f0;border-radius:50px;cursor:pointer;transition:all .3s ease}.form-switch .switch::after{content:"";position:absolute;top:2px;left:2px;width:20px;height:20px;background:#fff;border-radius:50%;transition:all .3s ease}.form-switch .switch.active{background:#0049b4}.form-switch .switch.active::after{transform:translateX(24px)}.input-group{display:flex;width:100%}.input-group .input-group-text{padding:12px 16px;font-size:16px;color:#64748b;background:#f8fafc;border:2px solid #e2e8f0;border-radius:8px 0 0 8px;border-right:none}.input-group .form-control{border-radius:0 8px 8px 0}.input-group.input-group-append .input-group-text{border-radius:0 8px 8px 0;border-right:2px solid #e2e8f0;border-left:none}.input-group.input-group-append .form-control{border-radius:8px 0 0 8px}.form-inline{display:flex;align-items:flex-end;gap:16px}.form-inline .form-group{margin-bottom:0}@media(max-width: 768px){.form-inline{flex-direction:column;align-items:stretch}.form-inline .form-group{margin-bottom:16px}}.form-row{display:grid;grid-template-columns:repeat(2, 1fr);gap:24px}@media(max-width: 768px){.form-row{grid-template-columns:1fr}}.file-upload-wrapper{display:flex;gap:16px;align-items:center;width:100%}@media(max-width: 768px){.file-upload-wrapper{flex-direction:column;align-items:stretch}}.file-upload-wrapper .file-name-display{flex:1;padding:16px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;font-size:14px;color:#64748b;cursor:default;transition:all .3s ease}.file-upload-wrapper .file-name-display.has-file{color:#1a1a2e;background:rgba(0,73,180,.05);border-color:#0049b4}.file-upload-wrapper .file-name-display:focus{outline:none;border-color:#0049b4;box-shadow:0 0 0 3px rgba(0,73,180,.1)}.file-upload-wrapper .file-upload-btn{padding:16px 24px;color:#fff;border-radius:8px;font-size:14px;font-weight:500;cursor:pointer;transition:all .3s ease;white-space:nowrap;display:inline-flex;align-items:center;gap:4px;border:none}@media(max-width: 768px){.file-upload-wrapper .file-upload-btn{justify-content:center}}.file-upload-wrapper .file-upload-btn:hover{transform:translateY(-2px);box-shadow:0 4px 12px rgba(75,155,255,.1)}.file-upload-wrapper .file-upload-btn:active{transform:translateY(0)}.file-upload-wrapper .file-upload-btn i{font-size:14px}.file-upload-wrapper .file-remove-btn{padding:16px;background:#ff6b6b;color:#fff;border-radius:8px;border:none;cursor:pointer;transition:all .3s ease;display:inline-flex;align-items:center;justify-content:center;width:40px;height:40px}@media(max-width: 768px){.file-upload-wrapper .file-remove-btn{width:100%;height:auto;padding:16px}}.file-upload-wrapper .file-remove-btn:hover{transform:translateY(-2px);box-shadow:0 4px 12px rgba(75,155,255,.1);background:rgb(255,88.9,88.9)}.file-upload-wrapper .file-remove-btn:active{transform:translateY(0)}.file-upload-wrapper .file-remove-btn i{font-size:14px}.file-upload-wrapper input[type=file]{display:none}.form-help-text{margin-top:16px;font-size:12px;color:#94a3b8;line-height:1.6}.form-help-text p{margin:4px 0;display:flex;align-items:flex-start;gap:4px}.form-help-text p i{color:#0049b4;margin-top:2px;flex-shrink:0}.form-row{display:flex;align-items:flex-start;gap:20px;margin-bottom:16px}@media(max-width: 1024px){.form-row{flex-direction:column;gap:10px;margin-bottom:12px}}.form-label-wrapper{display:flex;align-items:center;gap:5px;min-width:160px;width:160px;flex-shrink:0}@media(max-width: 1024px){.form-label-wrapper{padding-top:0;width:auto}}.form-label-wrapper.label-offset{margin-top:10px}@media(max-width: 1024px){.form-label-wrapper.label-offset{margin-top:0}}.form-label-text{font-size:15px;font-weight:400;color:#000}@media(max-width: 1024px){.form-label-text{font-size:14px}}.form-label-text.required::after{content:"*";color:#ff6b6b;margin-left:4px}.required-badge{background-color:#3ba4ed;color:#fff;font-size:11px;font-weight:400;padding:3px 4px 4px;border-radius:4px;line-height:1}.form-field-wrapper{flex:1}@media(max-width: 768px){.form-field-wrapper{width:100%}}.form-input{width:100%;padding:8px 12px;border:1px solid #e2e8f0;border-radius:8px;font-size:14px;color:#1a1a2e;background:#fff;transition:all .3s ease}.form-input:focus{outline:none;border-color:#0049b4;box-shadow:0 0 0 3px rgba(0,73,180,.1)}.form-input:disabled,.form-input.input-readonly{background:#f8fafc;color:#64748b;cursor:not-allowed}.form-input::placeholder{color:#94a3b8}.form-select{width:100%;padding:8px 12px;border:1px solid #e2e8f0;border-radius:8px;font-size:14px;color:#1a1a2e;background:#fff;cursor:pointer;transition:all .3s ease;appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%2364748B' d='M6 9L1 4h10z'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 24px center;padding-right:48px}.form-select:focus{outline:none;border-color:#0049b4;box-shadow:0 0 0 3px rgba(0,73,180,.1)}.form-select:disabled{background-color:#f8fafc;color:#94a3b8;cursor:not-allowed}.char-counter-wrapper{display:block;text-align:right;margin-top:8px}.char-counter-wrapper .char-counter{font-size:14px;color:#94a3b8}.section-divider{margin-top:48px;padding-top:32px;border-top:1px solid #e2e8f0}.compound-input{display:flex;align-items:center;gap:8px;width:100%}.compound-input .form-input,.compound-input .form-select{flex:1;min-width:0}.compound-input .separator{color:#64748b;font-weight:600;user-select:none;flex-shrink:0}.compound-input .form-input.phone-number{flex:none;width:130px;max-width:130px}.compound-input .form-select.phone-number{flex:none;width:130px;max-width:130px;padding-right:28px;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%2364748B' d='M6 9L1 4h10z'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 8px center}.compound-input .biz-reg-number{flex:1;width:100%}@media(max-width: 768px){.compound-input{gap:4px}.compound-input .separator{font-size:14px}}.input-with-button{display:flex;align-items:center;gap:16px}.input-with-button .compound-input,.input-with-button .auth-input-group{flex:1}@media(max-width: 768px){.input-with-button{flex-direction:column;gap:8px}.input-with-button .compound-input,.input-with-button .auth-input-group{width:100%}}.auth-input-group{display:flex;align-items:center;position:relative}.auth-input-group .form-input{flex:1;padding-right:100px}.auth-input-group .auth-timer{position:absolute;right:16px;top:50%;transform:translateY(-50%);font-size:14px;font-weight:600;color:#ff6b6b;white-space:nowrap}.form-actions,.form-actions-center{display:flex;justify-content:center;gap:16px;flex-wrap:wrap;padding-top:40px}@media(max-width: 768px){.form-actions,.form-actions-center{flex-direction:column-reverse;align-items:stretch;gap:8px;margin-top:48px;padding-top:32px}}.form-actions .btn,.form-actions-center .btn,.form-actions .action-btn-primary,.form-actions-center .action-btn-primary,.form-actions .action-btn-secondary,.form-actions-center .action-btn-secondary{min-width:160px;padding:16px 40px}@media(max-width: 768px){.form-actions .btn,.form-actions-center .btn,.form-actions .action-btn-primary,.form-actions-center .action-btn-primary,.form-actions .action-btn-secondary,.form-actions-center .action-btn-secondary{width:100%;min-width:auto}}.form-actions--between{justify-content:space-between}.form-actions--no-border{border-top:none;padding-top:0}.form-actions--compact{margin-top:40px;padding-top:24px}.form-actions--with-withdrawal{position:relative;justify-content:center}.form-actions--with-withdrawal .withdrawal-link{position:absolute;left:0;display:inline-flex;align-items:center;justify-content:center;gap:3px;width:150px;padding:8px;background:#e5f2f8;border-radius:12px;color:#5f666c;font-size:16px;font-weight:400;text-decoration:none;cursor:pointer;transition:all .3s ease}.form-actions--with-withdrawal .withdrawal-link:hover{background:rgb(210.2090909091,232.6045454545,242.9409090909)}.form-actions--with-withdrawal .withdrawal-link img{width:22px;height:22px;object-fit:contain}@media(max-width: 768px){.form-actions--with-withdrawal .withdrawal-link{position:static;width:300px;height:40px;padding:8px 10px;border-radius:8px;font-size:14px;font-weight:700;color:#515961;order:3}}.form-actions--with-withdrawal .form-actions-buttons{display:flex;gap:16px}@media(max-width: 768px){.form-actions--with-withdrawal .form-actions-buttons{flex-direction:row !important;justify-content:center;width:100%;gap:12px;order:1}}@media(max-width: 768px){.form-actions--with-withdrawal .form-actions-buttons .btn{width:144px !important;height:40px !important;min-height:40px;padding:8px 10px !important;font-size:14px !important;font-weight:700;border-radius:8px !important;flex-shrink:0}}@media(max-width: 768px){.form-actions--with-withdrawal .form-actions-buttons .btn.btn-secondary{background-color:#e5e7eb !important;color:#5f666c !important;border:none !important}}@media(max-width: 768px){.form-actions--with-withdrawal .form-actions-buttons .btn.btn-primary{background-color:#0049b4 !important;color:#fff !important;border:none !important}}@media(max-width: 768px){.form-actions--with-withdrawal{flex-direction:column;align-items:center;gap:16px;padding:24px 20px}}.search-field{position:relative;display:flex;align-items:center;width:340px;height:44px;background:#fff;border:1px solid #dadada;border-radius:12px;overflow:hidden}@media(max-width: 768px){.search-field{width:100%;margin-top:10px}}.search-field input{flex:1;height:100%;padding:0 44px 0 16px;border:none;background:rgba(0,0,0,0);font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:14px;font-weight:400;color:#1a1a2e;outline:none}.search-field input::placeholder{color:#8c959f}.search-field .search-field-btn{position:absolute;right:0;top:0;display:flex;align-items:center;justify-content:center;width:44px;height:100%;background:rgba(0,0,0,0);border:none;cursor:pointer;color:#515961;transition:all .15s ease}.search-field .search-field-btn:hover{color:#0049b4}.search-field .search-field-btn svg{width:20px;height:20px}.search-field:focus-within{border-color:#0049b4;box-shadow:0 0 0 2px rgba(0,73,180,.1)}.notice-content-box{padding:24px;background:#fff;border:1px solid #e2e8f0;border-radius:8px;min-height:300px;line-height:1.8;font-size:16px;color:#1a1a2e}@media(max-width: 768px){.notice-content-box{padding:16px;min-height:200px}}.notice-content-box p{margin-bottom:16px}.notice-content-box img{max-width:100%;height:auto}.notice-content-box a{color:#0049b4;text-decoration:underline}.notice-content-box a:hover{color:rgb(0,65.7,162)}.form-row--content .form-label-wrapper{align-self:flex-start}.attachment-link{display:inline-flex;align-items:center;gap:8px;padding:8px 16px;background:#eff6ff;border:1px solid #e2e8f0;border-radius:8px;color:#1a1a2e;font-size:14px;text-decoration:none;transition:all .3s ease}.attachment-link:hover{background:rgba(0,73,180,.05);border-color:#0049b4;color:#0049b4}.attachment-link svg{flex-shrink:0;color:#64748b}.attachment-link:hover svg{color:#0049b4}.attachment-list{display:flex;flex-direction:column;gap:8px}.attachment-item{display:flex;align-items:center}.modal-backdrop{position:fixed;top:0;left:0;right:0;bottom:0;background:#385670;z-index:400;opacity:.9;visibility:hidden;transition:all .3s ease}.modal-backdrop.show{opacity:.9;visibility:visible}.modal{position:fixed;top:0;left:0;width:100%;height:100vh;z-index:500;display:flex;align-items:center;justify-content:center;padding:24px;opacity:0;visibility:hidden;transition:opacity .3s ease,visibility .3s ease;pointer-events:none;overflow-y:auto}.modal.show{opacity:1;visibility:visible;pointer-events:auto}.modal.show .modal-dialog{transform:scale(1)}@media(max-width: 768px){.modal{padding:16px}}.modal-container{position:relative;max-width:820px;width:100%}.modal-container.modal-sm{max-width:500px}.modal-container.modal-lg{max-width:1000px}.modal-container.modal-xl{max-width:1200px}@media(max-width: 768px){.modal-container{max-width:100%}}.modal-close{position:absolute;top:0;right:0;transform:translate(100%, -100%);width:50px;height:50px;background:#0049b4;border:none;border-radius:50%;color:#fff;cursor:pointer;display:flex;align-items:center;justify-content:center;z-index:500;transition:all .3s ease;box-shadow:0 8px 24px rgba(75,155,255,.15)}.modal-close svg{width:14px;height:14px}.modal-close i{font-size:16px}@media(max-width: 768px){.modal-close{width:40px;height:40px}.modal-close svg{width:12px;height:12px}.modal-close i{font-size:14px}}.modal-dialog{position:relative;background:#fff;padding:0 24px;border-radius:12px;box-shadow:0 20px 60px rgba(0,0,0,.3);max-width:700px;width:calc(100% - 48px);max-height:85vh;display:flex;flex-direction:column;transform:scale(0.95);transition:transform .3s ease;z-index:500;margin-left:auto;margin-right:auto}.modal-dialog.modal-fullscreen{max-width:100%;max-height:100%;height:100%;margin:0;border-radius:0}@media(max-width: 768px){.modal-dialog{padding:0}}.modal-header{padding:30px 50px 20px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid #000;border-top-left-radius:12px;border-top-right-radius:12px}.modal-header .modal-title{font-size:24px;font-weight:600;color:#212529;margin:0}@media(max-width: 768px){.modal-header{padding:20px 24px 16px}.modal-header .modal-title{font-size:18px}}.modal-body{padding:30px 50px;flex:1;overflow-y:auto;color:#212529;font-size:20px;line-height:1.6}.modal-body p{margin:0}.modal-body p+p{margin-top:16px}.modal-body.text-center{text-align:center}@media(max-width: 768px){.modal-body{padding:20px 24px;font-size:16px}}.modal-footer{padding:20px 50px 40px;display:flex;gap:10px;justify-content:center;border-top:none}.modal-footer.modal-footer-end{justify-content:flex-end}.modal-footer.modal-footer-between{justify-content:space-between}@media(max-width: 768px){.modal-footer{padding:16px 24px 24px;flex-direction:row;gap:10px}.modal-footer .btn,.modal-footer .btn-modal-cancel,.modal-footer .btn-modal-confirm{flex:1;min-width:0;height:46px;font-size:15px}}.pop_input_field{width:100%;padding:12px 16px;border:1px solid #e2e8f0;border-radius:8px;font-size:14px;font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;transition:all .3s ease;box-sizing:border-box}.pop_input_field:focus{outline:none;border-color:#0049b4;box-shadow:0 0 0 3px rgba(0,73,180,.1)}.pop_input_field::placeholder{color:#94a3b8}.pop_input_field.error{border-color:#ff6b6b}.pop_input_field.error:focus{border-color:#ff6b6b;box-shadow:0 0 0 3px rgba(255,107,107,.1)}.modal-alert .modal-container{max-width:500px}.modal-alert .modal-body{text-align:center;padding:32px 50px}.modal-alert .modal-body .alert-icon{width:64px;height:64px;margin:0 auto 24px;display:flex;justify-content:center;align-items:center;font-size:32px;border-radius:50%}.modal-alert .modal-body .alert-icon.alert-success{background:rgba(107,207,127,.1);color:#6bcf7f}.modal-alert .modal-body .alert-icon.alert-warning{background:rgba(255,217,61,.1);color:#ffd93d}.modal-alert .modal-body .alert-icon.alert-danger{background:rgba(255,107,107,.1);color:#ff6b6b}.modal-alert .modal-body .alert-icon.alert-info{background:rgba(0,73,180,.1);color:#0049b4}.modal-alert .modal-body .alert-title{font-size:24px;font-weight:600;margin-bottom:8px;color:#1a1a2e}.modal-alert .modal-body .alert-message{color:#64748b;font-size:16px}.breadcrumb{display:flex;align-items:center;gap:8px;padding:16px 0;font-size:14px}.breadcrumb .breadcrumb-item{display:flex;align-items:center;gap:8px;color:#64748b}.breadcrumb .breadcrumb-item a{color:#64748b;transition:all .3s ease}.breadcrumb .breadcrumb-item a:hover{color:#0049b4}.breadcrumb .breadcrumb-item.active{color:#1a1a2e;font-weight:500}.breadcrumb .breadcrumb-item:not(:last-child)::after{content:"/";color:#94a3b8;margin-left:8px}.nav-tabs{display:flex;gap:4px;border-bottom:2px solid #e2e8f0;margin-bottom:32px}.nav-tabs .nav-item{position:relative}.nav-tabs .nav-link{display:flex;align-items:center;gap:8px;padding:16px 24px;color:#64748b;font-weight:500;border-bottom:2px solid rgba(0,0,0,0);margin-bottom:-2px;transition:all .3s ease}.nav-tabs .nav-link:hover{color:#1a1a2e}.nav-tabs .nav-link.active{color:#0049b4;border-bottom-color:#0049b4}.nav-tabs .nav-link .badge{padding:2px 6px;background:#f8fafc;color:#64748b;border-radius:50px;font-size:11px;font-weight:600}.nav-pills{display:flex;gap:8px;padding:4px;background:#f8fafc;border-radius:12px}.nav-pills .nav-link{padding:8px 24px;color:#64748b;font-weight:500;border-radius:8px;transition:all .3s ease}.nav-pills .nav-link:hover{color:#1a1a2e;background:hsla(0,0%,100%,.5)}.nav-pills .nav-link.active{background:#fff;color:#0049b4;box-shadow:0 2px 4px rgba(0,0,0,.05)}.sidebar-nav .nav-section{margin-bottom:32px}.sidebar-nav .nav-section .nav-title{font-size:12px;font-weight:600;text-transform:uppercase;color:#64748b;padding:8px 16px;letter-spacing:.05em}.sidebar-nav .nav-menu{list-style:none}.sidebar-nav .nav-menu .nav-item{margin-bottom:4px}.sidebar-nav .nav-menu .nav-link{display:flex;align-items:center;gap:16px;padding:8px 16px;color:#1a1a2e;border-radius:8px;transition:all .3s ease}.sidebar-nav .nav-menu .nav-link .nav-icon{width:20px;height:20px;display:flex;justify-content:center;align-items:center;font-size:18px;color:#64748b}.sidebar-nav .nav-menu .nav-link:hover{background:#f8fafc;color:#0049b4}.sidebar-nav .nav-menu .nav-link:hover .nav-icon{color:#0049b4}.sidebar-nav .nav-menu .nav-link.active{background:#eff6ff;color:#0049b4;font-weight:500}.sidebar-nav .nav-menu .nav-link.active .nav-icon{color:#0049b4}.sidebar-nav .nav-menu .nav-submenu{margin-left:40px;margin-top:4px;list-style:none}.sidebar-nav .nav-menu .nav-submenu .nav-link{font-size:14px;padding:4px 16px}.pagination{display:flex;align-items:center;gap:4px}.pagination .page-item.disabled .page-link{opacity:.5;cursor:not-allowed;pointer-events:none}.pagination .page-item.active .page-link{background:#0049b4;color:#fff;border-color:#0049b4}.pagination .page-link{display:flex;align-items:center;justify-content:center;min-width:36px;height:36px;padding:0 8px;background:#fff;border:1px solid #e2e8f0;border-radius:8px;color:#1a1a2e;font-size:14px;font-weight:500;transition:all .3s ease}.pagination .page-link:hover{background:#f8fafc;border-color:#0049b4;color:#0049b4}.pagination .page-link.page-prev,.pagination .page-link.page-next{font-size:18px}.pagination .page-dots{padding:0 8px;color:#64748b}.partners{padding:80px 0;background:#fff}.final-cta{position:relative;padding:150px 0;overflow:hidden}#passwordInputPopup .pop_input_group{text-align:left;margin:0}#passwordInputPopup .error-message{color:#ff6b6b;font-size:12px;margin-top:4px;display:none;animation:fadeInDown .3s ease}#passwordInputPopup .error-message.show{display:block}@keyframes fadeInDown{from{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}.auth-group{display:flex;align-items:center;gap:16px}.auth-group.authenticated{gap:0}.login-btn{display:inline-flex;align-items:center;padding:10px 20px;color:#1a1a2e;text-decoration:none;border:2px solid #0049b4;border-radius:50px;font-weight:600;font-size:14px;transition:all .3s ease}.login-btn-box{height:32px;display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:6px 16px;color:#212529;text-decoration:none;border:none;border-radius:6px;font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-weight:600;font-size:16px;background:rgba(0,0,0,0);transition:all .3s ease}.login-btn-box .user-icon{width:18px;height:18px;flex-shrink:0}.user-profile-dropdown{position:relative}.user-profile-dropdown .user-profile-btn{display:flex;align-items:center;gap:8px;padding:10px 20px;background:#eff6ff;border:2px solid rgba(0,0,0,0);border-radius:50px;cursor:pointer;transition:all .3s ease;font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:14px}.user-profile-dropdown .user-profile-btn .user-name{color:#1a1a2e;font-weight:600}.user-profile-dropdown .user-profile-btn i{color:#64748b;font-size:12px;transition:transform all .15s ease}.user-profile-dropdown .user-profile-btn:hover{background:#fff;border-color:#0049b4;box-shadow:0 4px 12px rgba(75,155,255,.1)}.user-profile-dropdown .user-profile-btn:hover .user-name{color:#0049b4}.user-profile-dropdown .user-profile-btn:hover i{color:#0049b4}.user-profile-dropdown.active .user-profile-btn i{transform:rotate(180deg)}.user-profile-dropdown.active .profile-dropdown-menu{visibility:visible;opacity:1;transform:translateY(0)}.user-profile-dropdown .profile-dropdown-menu{position:absolute;top:calc(100% + 12px);right:0;width:280px;background:#fff;border:1px solid #e2e8f0;border-radius:12px;box-shadow:0 16px 40px rgba(75,155,255,.2);visibility:hidden;opacity:0;transform:translateY(-10px);transition:all all .3s ease;z-index:100;overflow:hidden}.user-profile-dropdown .profile-header{padding:24px;color:#fff;text-align:center}.user-profile-dropdown .profile-header .user-greeting{margin:0 0 4px 0;font-size:18px;font-weight:700}.user-profile-dropdown .profile-header .user-message{font-size:12px;opacity:.9}.user-profile-dropdown .profile-menu-list{list-style:none;margin:0;padding:8px 0}.user-profile-dropdown .profile-menu-list li{margin:0}.user-profile-dropdown .profile-menu-list li a{display:flex;align-items:center;gap:12px;padding:12px 24px;color:#1a1a2e;text-decoration:none;font-size:14px;transition:all .15s ease}.user-profile-dropdown .profile-menu-list li a i{width:20px;color:#64748b;font-size:14px}.user-profile-dropdown .profile-menu-list li a:hover{background:#eff6ff;color:#0049b4}.user-profile-dropdown .profile-menu-list li a:hover i{color:#0049b4}.user-profile-dropdown .profile-footer{padding:8px 24px 24px;border-top:1px solid #e2e8f0}.user-profile-dropdown .profile-footer .logout-btn{display:flex;align-items:center;justify-content:center;gap:8px;width:100%;padding:12px;background:#f8fafc;color:#1a1a2e;text-decoration:none;border-radius:8px;font-weight:600;font-size:14px;transition:all .3s ease}.user-profile-dropdown .profile-footer .logout-btn i{font-size:14px}.user-profile-dropdown .profile-footer .logout-btn:hover{background:#ff6b6b;color:#fff;transform:translateY(-2px);box-shadow:0 4px 12px rgba(75,155,255,.1)}.drawer-user-info{padding:24px;border-bottom:1px solid hsla(0,0%,100%,.2)}.drawer-user-info .drawer-profile{display:flex;align-items:center;gap:16px}.drawer-user-info .drawer-profile .profile-avatar{width:60px;height:60px;display:flex;align-items:center;justify-content:center;background:hsla(0,0%,100%,.2);border-radius:50%;color:#fff}.drawer-user-info .drawer-profile .profile-avatar i{font-size:32px}.drawer-user-info .drawer-profile .profile-info{flex:1;color:#fff}.drawer-user-info .drawer-profile .profile-info .profile-name{margin:0 0 4px 0;font-size:18px;font-weight:700}.drawer-user-info .drawer-profile .profile-info .profile-greeting{font-size:12px;opacity:.9}.drawer-logout-btn{display:flex;align-items:center;justify-content:center;gap:8px;width:100%;padding:14px 20px;background:#ff6b6b;color:#fff;text-decoration:none;border-radius:12px;font-weight:600;font-size:16px;transition:all .3s ease;box-shadow:0 4px 12px rgba(75,155,255,.1)}.drawer-logout-btn i{font-size:16px}.drawer-logout-btn:hover{background:rgb(255,70.8,70.8);transform:translateY(-2px);box-shadow:0 8px 24px rgba(75,155,255,.15)}@media(max-width: 768px){.user-profile-dropdown .profile-dropdown-menu{width:260px;right:-10px}}@media(max-width: 480px){.user-profile-dropdown .user-profile-btn{padding:8px 16px}.user-profile-dropdown .user-profile-btn .user-name{max-width:100px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.user-profile-dropdown .profile-dropdown-menu{width:240px;right:-20px}}.data-table{width:100%;background:#fff;border-radius:12px;overflow:hidden;box-shadow:0 2px 4px rgba(0,0,0,.05)}.data-table-wrapper{overflow-x:auto;margin-bottom:32px}@media(max-width: 768px){.data-table-wrapper{border-radius:12px;box-shadow:0 2px 4px rgba(0,0,0,.05)}}.data-table table{width:100%;border-collapse:collapse}.data-table table thead{background:#eff6ff}.data-table table thead tr{border-bottom:2px solid #e2e8f0}.data-table table thead th{padding:16px 24px;text-align:left;font-size:14px;font-weight:600;color:#1a1a2e;white-space:nowrap}@media(max-width: 768px){.data-table table thead th{padding:8px 16px;font-size:12px}}.data-table table tbody tr{border-bottom:1px solid #e2e8f0;transition:all .3s ease}.data-table table tbody tr:last-child{border-bottom:none}.data-table table tbody tr:hover{background-color:rgba(0,73,180,.02)}.data-table table tbody td{padding:16px 24px;font-size:14px;color:#64748b}@media(max-width: 768px){.data-table table tbody td{padding:8px 16px;font-size:12px}}.data-table table tbody td a{color:#1a1a2e;text-decoration:none;transition:all .3s ease}.data-table table tbody td a:hover{color:#0049b4}.notice-table{width:100%;background:#fff;border-radius:12px;overflow:hidden;box-shadow:0 2px 4px rgba(0,0,0,.05)}.notice-table-wrapper{overflow-x:auto}@media(max-width: 768px){.notice-table-wrapper{border-radius:0;box-shadow:none}}.notice-table .table-header{display:grid;grid-template-columns:80px 1fr 120px;gap:16px;padding:16px 24px;background:#eff6ff;border-bottom:2px solid #e2e8f0;font-size:14px;font-weight:600;color:#1a1a2e}@media(max-width: 1024px){.notice-table .table-header{grid-template-columns:60px 1fr 100px;padding:8px 16px;font-size:12px}}@media(max-width: 768px){.notice-table .table-header{display:none}}.notice-table .table-header .col-num{text-align:center}.notice-table .table-header .col-title{text-align:left}.notice-table .table-header .col-date{text-align:center}.notice-table .table-row{display:grid;grid-template-columns:80px 1fr 120px;gap:16px;padding:24px;border-bottom:1px solid #e2e8f0;transition:all .3s ease;cursor:pointer}@media(max-width: 1024px){.notice-table .table-row{grid-template-columns:60px 1fr 100px;padding:16px}}@media(max-width: 768px){.notice-table .table-row{grid-template-columns:1fr;gap:8px;padding:16px}}.notice-table .table-row:last-child{border-bottom:none}.notice-table .table-row:hover{background-color:rgba(0,73,180,.02)}.notice-table .table-row .col-num{display:flex;align-items:center;justify-content:center;font-size:14px;color:#64748b;font-weight:500}@media(max-width: 768px){.notice-table .table-row .col-num{display:none}}.notice-table .table-row .col-title{display:flex;align-items:center;font-size:16px;color:#1a1a2e;font-weight:500}@media(max-width: 768px){.notice-table .table-row .col-title{font-size:14px}}.notice-table .table-row .col-title a{color:inherit;text-decoration:none;display:flex;align-items:center;gap:8px;transition:all .3s ease}.notice-table .table-row .col-title a:hover{color:#0049b4}.notice-table .table-row .col-title .file-icon{display:inline-flex;align-items:center;margin-left:8px;opacity:.6}.notice-table .table-row .col-title .file-icon img{width:16px;height:16px}.notice-table .table-row .col-date{display:flex;align-items:center;justify-content:center;font-size:14px;color:#94a3b8}@media(max-width: 768px){.notice-table .table-row .col-date{justify-content:flex-start;font-size:12px;padding-top:4px}}@media(max-width: 768px){.notice-table .table-row .col-date::before{content:"등록일: ";color:#64748b;margin-right:4px}}.table-empty{padding:80px 24px;text-align:center;background:#fff;border-radius:12px}.table-empty .empty-icon{margin-bottom:24px}.table-empty .empty-icon img{max-width:200px;opacity:.7}@media(max-width: 768px){.table-empty .empty-icon img{max-width:150px}}.table-empty .empty-text{font-size:16px;color:#64748b}@media(max-width: 768px){.table-empty .empty-text{font-size:14px}}.list-table{width:100%;background:#fff;border-radius:12px;overflow:hidden;border:1px solid #e5e7eb;box-shadow:0 1px 3px rgba(0,0,0,.06)}.list-table-wrapper{overflow-x:auto;margin-bottom:32px}.list-table .list-table-header{display:flex;align-items:center;background:#3ba4ed;height:48px;padding:0 24px}@media(max-width: 1024px){.list-table .list-table-header{display:none}}.list-table .list-table-header .header-cell{display:flex;align-items:center;justify-content:center;padding:8px 16px;font-size:13px;font-weight:600;color:#fff;text-align:center;letter-spacing:.3px}.list-table .list-table-body{display:flex;flex-direction:column}.list-table .list-table-row{display:flex;align-items:center;height:48px;padding:8px 24px;transition:all .15s ease;border-bottom:1px solid #f3f4f6}.list-table .list-table-row:last-child{border-bottom:none}.list-table .list-table-row:nth-child(even){background-color:#f8fbfe}.list-table .list-table-row:nth-child(odd){background-color:#fff}.list-table .list-table-row:hover{background-color:#edf5fd}@media(max-width: 1024px){.list-table .list-table-row{flex-direction:column;align-items:flex-start;gap:8px;padding:16px}}.list-table .list-table-row .row-cell{display:flex;align-items:center;justify-content:center;padding:8px 16px;font-size:14px;color:#374151;text-align:center;white-space:nowrap}@media(max-width: 1024px){.list-table .list-table-row .row-cell{font-size:14px;padding:4px 0}.list-table .list-table-row .row-cell::before{content:attr(data-label);font-weight:600;color:#64748b;margin-right:8px;min-width:80px;text-align:left}}.list-table .list-table-row .row-cell--title{justify-content:flex-start}@media(max-width: 1024px){.list-table .list-table-row .row-cell--title::before{display:none}}.list-table .list-table-row .row-cell--title .notice-title-link{display:inline-flex;align-items:center;gap:8px;color:inherit;text-decoration:none;transition:all .3s ease}.list-table .list-table-row .row-cell--title .notice-title-link:hover{color:#0049b4}.list-table .list-table-row .row-cell--title .notice-title-link .notice-number{display:none;font-weight:500;color:#64748b;flex-shrink:0}@media(max-width: 1024px){.list-table .list-table-row .row-cell--title .notice-title-link .notice-number{display:inline}}.list-table .list-table-row .row-cell--title .file-icon{display:inline-flex;align-items:center;flex-shrink:0;color:#212529}.list-table .list-table-row .row-cell--title .file-icon svg{width:24px;height:24px}@media(max-width: 1024px){.list-table .list-table-row .row-cell--number{display:none}}.list-table .list-table-row .row-actions{display:flex;align-items:center;justify-content:center;gap:14px;flex-wrap:nowrap}@media(max-width: 1024px){.list-table .list-table-row .row-actions{width:100%;justify-content:flex-start;padding-top:8px}}.list-table-btn{display:inline-flex;align-items:center;justify-content:center;padding:8px 16px;height:40px;white-space:nowrap;border-radius:8px;font-size:16px;font-weight:400;color:#000;border:none;cursor:pointer;transition:all .3s ease;white-space:nowrap}.list-table-btn:hover{transform:translateY(-1px);box-shadow:0 2px 4px rgba(0,0,0,.05)}.list-table-btn:active{transform:translateY(0)}.list-table-btn--default{background-color:#dadada}.list-table-btn--default:hover{background-color:rgb(207.1,207.1,207.1)}.list-table-btn--primary{background-color:#a4d6ea}.list-table-btn--primary:hover{background-color:rgb(147.83125,206.7151785714,230.26875)}.list-table-btn--secondary{background-color:#cdd4f0}.list-table-btn--secondary:hover{background-color:hsl(228,53.8461538462%,82.8921568627%)}.table-pagination{display:flex;align-items:center;justify-content:center;gap:13px;padding:32px 0}.table-pagination .pagination-btn{display:flex;align-items:center;justify-content:center;width:24px;height:24px;background:none;border:none;cursor:pointer;color:#212529;transition:all .3s ease}.table-pagination .pagination-btn:hover{color:#0049b4}.table-pagination .pagination-btn:disabled{opacity:.4;cursor:not-allowed}.table-pagination .pagination-btn i{font-size:16px}.table-pagination .pagination-numbers{display:flex;align-items:center;gap:13px}.table-pagination .pagination-number{display:flex;align-items:center;justify-content:center;min-width:10px;height:21px;font-size:18px;color:#5f666c;background:none;border:none;cursor:pointer;transition:all .3s ease}.table-pagination .pagination-number:hover{color:#0049b4}.table-pagination .pagination-number.active{font-weight:700;color:#212529}.table-controls{display:flex;justify-content:space-between;align-items:center;margin-bottom:24px;padding:0 8px;gap:24px}@media(max-width: 768px){.table-controls{flex-direction:column;align-items:stretch;gap:16px}}.table-controls .total-count{font-size:16px;color:#64748b;margin-bottom:0}.table-controls .total-count strong{color:#0049b4;font-weight:600;font-size:20px}@media(max-width: 768px){.table-controls .total-count{order:2;text-align:center}}.table-controls .search-box{display:flex;gap:8px}@media(max-width: 768px){.table-controls .search-box{order:1}}.table-controls .search-box input{flex:1;min-width:250px;padding:8px 16px;border:1px solid #e2e8f0;border-radius:8px;font-size:14px;transition:all .3s ease}@media(max-width: 768px){.table-controls .search-box input{min-width:auto}}.table-controls .search-box input:focus{outline:none;border-color:#0049b4;box-shadow:0 0 0 3px rgba(0,73,180,.1)}.table-controls .search-box input::placeholder{color:#94a3b8}@media(max-width: 768px){.search-field{display:flex;align-items:center;width:100%;height:40px;background:#fff;border:1px solid #dadada;border-radius:8px;position:relative}.search-field input{flex:1;height:100%;padding:0 40px 0 16px;border:none;background:rgba(0,0,0,0);font-size:12px;color:#212529}.search-field input::placeholder{color:#8c959f;font-size:12px}.search-field input:focus{outline:none}.search-field .search-field-btn{position:absolute;right:12px;top:50%;transform:translateY(-50%);width:18px;height:18px;padding:0;background:none;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center}.search-field .search-field-btn svg{width:18px;height:18px;color:#515961}.table-controls{flex-direction:column;align-items:stretch;gap:24px;padding:0;margin-bottom:0}.table-controls .total-count{order:2;text-align:left;font-size:16px;color:#000;padding-bottom:6px;border-bottom:1.5px solid #212529;margin-bottom:0}.table-controls .total-count strong{color:#0049b4;font-weight:700;font-size:16px}.table-controls .search-field{order:1}.list-table{background:rgba(0,0,0,0);border-radius:0}.list-table .list-table-header{display:none}.list-table .list-table-body{display:flex;flex-direction:column}.list-table .list-table-row{display:flex;flex-direction:row;align-items:center;justify-content:space-between;height:56px;padding:0 8px;background:rgba(0,0,0,0) !important;border-bottom:1px solid #dadada}.list-table .list-table-row:hover{background:rgba(0,73,180,.02) !important}.list-table .list-table-row .row-cell--number{display:none}.list-table .list-table-row .row-cell--title{flex:1;justify-content:flex-start;padding:0;font-size:15px;font-weight:400;color:#212529}.list-table .list-table-row .row-cell--title::before{display:none}.list-table .list-table-row .row-cell--title .notice-title-link{display:flex;align-items:center;gap:8px;color:inherit;text-decoration:none}.list-table .list-table-row .row-cell--title .notice-title-link .notice-number{display:none}.list-table .list-table-row .row-cell--title .notice-title-link .file-icon{display:inline-flex;align-items:center;flex-shrink:0;margin-left:4px}.list-table .list-table-row .row-cell--title .notice-title-link .file-icon svg{width:18px;height:18px}.list-table .list-table-row .row-cell:last-child{flex-shrink:0;width:auto;padding:0;font-size:14px;font-weight:400;color:#212529;justify-content:flex-end}.list-table .list-table-row .row-cell:last-child::before{display:none}.pagination{gap:13px;padding:24px 0}.pagination .pagination-btn{width:20px;height:20px}.pagination .pagination-btn i,.pagination .pagination-btn svg{font-size:14px;width:14px;height:14px}.pagination .pagination-numbers{gap:13px}.pagination .pagination-number{min-width:10px;height:21px;font-size:15px;color:#5f666c;font-weight:400}.pagination .pagination-number.active{font-weight:700;color:#212529}}.faq-accordion{background:#f6f9fb;border-radius:12px;overflow:hidden;margin-bottom:40px}@media(max-width: 768px){.faq-accordion{background:#fff;border-radius:0}}.faq-accordion .faq-item{border-bottom:1px solid #dadada;transition:all .3s ease}.faq-accordion .faq-item:last-child{border-bottom:none}.faq-accordion .faq-item.active .faq-question .faq-icon{transform:rotate(180deg)}.faq-accordion .faq-item.active .faq-answer{display:block;animation:slideDown .3s ease}.faq-accordion .faq-question{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;font-size:18px;font-weight:700;color:#212529;cursor:pointer;transition:all .3s ease;gap:95px;min-height:54px}@media(max-width: 1024px){.faq-accordion .faq-question{padding:16px 24px;font-size:16px;gap:24px}}.faq-accordion .faq-question:hover{background:rgba(0,73,180,.02)}.faq-accordion .faq-question .faq-question-text{flex:1;line-height:1.6}.faq-accordion .faq-question .faq-icon{flex-shrink:0;display:flex;align-items:center;justify-content:center;width:22px;height:22px;color:#212529;transition:transform .3s ease}.faq-accordion .faq-question .faq-icon svg{width:14px;height:8px}.faq-accordion .faq-answer{display:none;background:#f6f9fb;padding:24px 20px 32px;border-top:none}@media(max-width: 768px){.faq-accordion .faq-answer{background:#fff}}@media(max-width: 1024px){.faq-accordion .faq-answer{padding:16px 24px 24px}}.faq-accordion .faq-answer .faq-answer-content{font-size:18px;font-weight:400;color:#515151;background-color:#fff;padding:24px;line-height:1.8}@media(max-width: 1024px){.faq-accordion .faq-answer .faq-answer-content{font-size:14px}}.faq-accordion .faq-answer .faq-answer-content p{margin-bottom:16px}.faq-accordion .faq-answer .faq-answer-content p:last-child{margin-bottom:0}.faq-accordion .faq-answer .faq-answer-content ul,.faq-accordion .faq-answer .faq-answer-content ol{margin:8px 0;padding-left:24px}.faq-accordion .faq-answer .faq-answer-content li{margin-bottom:4px}.faq-accordion .faq-answer .faq-answer-content a{color:#0049b4;text-decoration:underline}.faq-accordion .faq-answer .faq-answer-content a:hover{color:#c3dfea}.faq-accordion .faq-answer .faq-answer-content code{background:#f8fafc;padding:2px 6px;border-radius:6px;font-family:"Fira Code","Courier New",monospace;font-size:.9em;color:#0049b4}.faq-accordion .faq-answer .faq-answer-content pre{background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;padding:16px;overflow-x:auto;margin:16px 0}.faq-accordion .faq-answer .faq-answer-content pre code{background:rgba(0,0,0,0);padding:0;color:#1a1a2e}.faq-page{min-height:calc(100vh - 140px);background:#f8fafc;padding:48px 24px}@media(max-width: 768px){.faq-page{padding:40px 16px}}.faq-container{max-width:1280px;margin:0 auto}.faq-header{margin-bottom:48px}@media(max-width: 768px){.faq-header{margin-bottom:40px}}.faq-header .page-title{font-size:40px;font-weight:700;color:#1a1a2e;margin-bottom:16px;-webkit-background-clip:text;-webkit-text-fill-color:rgba(0,0,0,0);background-clip:text}@media(max-width: 768px){.faq-header .page-title{font-size:32px}}.faq-header .page-description{font-size:16px;color:#64748b;line-height:1.6}@media(max-width: 768px){.faq-header .page-description{font-size:14px}}@keyframes slideDown{from{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}.accordion{background:#fff;border-radius:12px;box-shadow:0 2px 4px rgba(0,0,0,.05);overflow:hidden}.accordion .accordion-item{border-bottom:1px solid #e2e8f0}.accordion .accordion-item:last-child{border-bottom:none}.accordion .accordion-item.active .accordion-header{background:rgba(0,73,180,.03);color:#0049b4}.accordion .accordion-item.active .accordion-header .accordion-icon{transform:rotate(180deg)}.accordion .accordion-header{display:flex;align-items:center;justify-content:space-between;padding:24px 32px;font-size:16px;font-weight:500;color:#1a1a2e;cursor:pointer;transition:all .3s ease}@media(max-width: 768px){.accordion .accordion-header{padding:16px 24px;font-size:14px}}.accordion .accordion-header:hover{background:rgba(0,73,180,.02)}.accordion .accordion-header .accordion-title{flex:1}.accordion .accordion-header .accordion-icon{flex-shrink:0;font-size:20px;color:#64748b;transition:all .3s ease}.accordion .accordion-content{display:none;padding:24px 32px;font-size:14px;color:#64748b;line-height:1.6;border-top:1px solid #e2e8f0}@media(max-width: 768px){.accordion .accordion-content{padding:16px 24px}}.accordion .accordion-content.show{display:block;animation:slideDown .3s ease}.page-title-banner{background:#e9f8ff;position:relative;width:100%;height:110px;display:flex;align-items:center;justify-content:center;overflow:hidden}.page-title-banner::before{content:"";position:absolute;top:0;left:0;right:0;bottom:0;background-image:url("data:image/svg+xml,%3Csvg width='100' height='100' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0h100v100H0z' fill='%23ffffff' opacity='0.03'/%3E%3C/svg%3E");background-size:50px 50px;opacity:.5}.page-title-banner .title-image{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);height:220px;width:auto;z-index:1}.page-title-banner h1{position:relative;font-size:40px;font-weight:700;color:#1a1a2e;margin:0;z-index:2;letter-spacing:-0.5px}@media(max-width: 768px){.page-title-banner{height:120px;margin-bottom:32px}.page-title-banner .title-image{height:120px}.page-title-banner h1{font-size:24px}}.alert{display:flex;align-items:flex-start;gap:16px;padding:16px 24px;border-radius:8px;margin-bottom:32px}.alert .alert-icon{font-size:20px;flex-shrink:0}.alert .alert-content{flex:1;text-align:left}.alert .alert-content strong{font-weight:600}.alert.alert-warning{background:rgba(255,217,61,.1);border:1px solid rgba(255,217,61,.3);color:#b89900}.alert.alert-error{background:rgba(255,107,107,.1);border:1px solid rgba(255,107,107,.3);color:rgb(255,70.8,70.8);align-items:center}.alert.alert-error svg{flex-shrink:0}.alert.alert-error span{flex:1;font-size:14px;font-weight:500}.alert.alert-success{background:rgba(107,207,127,.1);border:1px solid rgba(107,207,127,.3);color:hsl(132,51.0204081633%,49.2549019608%)}.alert.alert-info{background:rgba(0,73,180,.1);border:1px solid rgba(0,73,180,.3);color:#c3dfea}.pagination{display:flex;align-items:center;justify-content:center;gap:13px}.pagination .page-first,.pagination .page-prev,.pagination .page-next,.pagination .page-last{display:flex;align-items:center;justify-content:center;width:24px;height:24px;color:#212529;text-decoration:none;cursor:pointer;transition:all .15s ease}.pagination .page-first:hover:not(.disabled),.pagination .page-prev:hover:not(.disabled),.pagination .page-next:hover:not(.disabled),.pagination .page-last:hover:not(.disabled){color:#0049b4}.pagination .page-first.disabled,.pagination .page-prev.disabled,.pagination .page-next.disabled,.pagination .page-last.disabled{color:#adb5bd;cursor:not-allowed;pointer-events:none}.pagination .page-first svg,.pagination .page-prev svg,.pagination .page-next svg,.pagination .page-last svg{width:24px;height:24px;flex-shrink:0}.pagination .page-num{display:flex;align-items:center;justify-content:center;min-width:10px;height:21px;font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:18px;font-weight:400;color:#5f666c;text-decoration:none;text-align:center;line-height:1;cursor:pointer;transition:all .15s ease}.pagination .page-num:hover:not(.page-current){color:#0049b4}.pagination .page-num.page-current{font-weight:700;color:#212529;cursor:default}.pagination .blind{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);white-space:nowrap;border:0}.pagination-wrapper{display:flex;justify-content:center;padding:24px 0;margin-top:24px}.breadcrumb-container{background-color:#e9f8ff;height:60px;width:100%;display:flex;align-items:center}.breadcrumb{display:flex;align-items:center;gap:20px;max-width:1280px;margin:0 auto;padding:0 24px;width:100%}@media(max-width: 768px){.breadcrumb{gap:12px;padding:0 16px}}.breadcrumb-home{display:flex;align-items:center;justify-content:center;width:24px;height:24px;flex-shrink:0}.breadcrumb-home svg{width:18px;height:17px;fill:#8c959f;transition:all .15s ease}.breadcrumb-home:hover svg{fill:#0049b4}.breadcrumb-separator{display:flex;align-items:center;justify-content:center;width:24px;height:24px;flex-shrink:0}.breadcrumb-separator svg{width:6.5px;height:12px;fill:#5f666c}@media(max-width: 768px){.breadcrumb-separator{width:16px;height:16px}.breadcrumb-separator svg{width:5px;height:10px}}.breadcrumb-item{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:16px;font-weight:400;line-height:1;color:#5f666c;text-decoration:none;white-space:nowrap;transition:all .15s ease}.breadcrumb-item:hover{color:#0049b4}.breadcrumb-item--active{font-weight:700;color:#212529}.breadcrumb-item--active:hover{color:#212529;cursor:default}@media(max-width: 768px){.breadcrumb-item{font-size:14px}}.breadcrumb-nav{background-color:#e9f8ff;height:60px;width:100%}@media(max-width: 768px){.breadcrumb-nav{height:48px}}.breadcrumb-nav .breadcrumb-list{display:flex;align-items:center;gap:20px;max-width:1280px;margin:0 auto;padding:0 26px;height:100%;list-style:none}@media(max-width: 768px){.breadcrumb-nav .breadcrumb-list{gap:12px;padding:0 16px}}.breadcrumb-nav .breadcrumb-list-item{display:flex;align-items:center;gap:20px}@media(max-width: 768px){.breadcrumb-nav .breadcrumb-list-item{gap:12px}}.breadcrumb-nav .breadcrumb-list-item:not(:last-child)::after{content:"";display:block;width:6.5px;height:12px;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='7' height='12' viewBox='0 0 7 12' fill='none'%3E%3Cpath d='M1 1L6 6L1 11' stroke='%235F666C' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:center;background-size:contain}@media(max-width: 768px){.breadcrumb-nav .breadcrumb-list-item:not(:last-child)::after{width:5px;height:10px}}.breadcrumb-nav .breadcrumb-list-item a{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:16px;font-weight:400;line-height:1;color:#5f666c;text-decoration:none;white-space:nowrap;transition:all .15s ease}.breadcrumb-nav .breadcrumb-list-item a:hover{color:#0049b4}@media(max-width: 768px){.breadcrumb-nav .breadcrumb-list-item a{font-size:14px}}.breadcrumb-nav .breadcrumb-list-item:last-child a{font-weight:700;color:#212529;pointer-events:none}.breadcrumb-nav .breadcrumb-list-item:first-child a{display:flex;align-items:center;justify-content:center;width:24px;height:24px;font-size:0}.breadcrumb-nav .breadcrumb-list-item:first-child a::before{content:"";display:block;width:18px;height:17px;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='17' viewBox='0 0 18 17' fill='none'%3E%3Cpath d='M1 6.5L9 1L17 6.5V15C17 15.5304 16.7893 16.0391 16.4142 16.4142C16.0391 16.7893 15.5304 17 15 17H3C2.46957 17 1.96086 16.7893 1.58579 16.4142C1.21071 16.0391 1 15.5304 1 15V6.5Z' stroke='%238C959F' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:center;background-size:contain;transition:all .15s ease}.breadcrumb-nav .breadcrumb-list-item:first-child a:hover::before{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='17' viewBox='0 0 18 17' fill='none'%3E%3Cpath d='M1 6.5L9 1L17 6.5V15C17 15.5304 16.7893 16.0391 16.4142 16.4142C16.0391 16.7893 15.5304 17 15 17H3C2.46957 17 1.96086 16.7893 1.58579 16.4142C1.21071 16.0391 1 15.5304 1 15V6.5Z' stroke='%230049B4' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E")}.test-env-notice{margin-top:8px;padding:12px 16px;background-color:#fff4e6;border:1px solid #ffb84d;border-radius:4px}.test-env-notice__content{display:flex;align-items:flex-start;gap:8px}.test-env-notice__icon{flex-shrink:0;margin-top:2px;width:20px;height:20px}.test-env-notice__text-container{flex:1}.test-env-notice__title{margin:0;font-size:14px;color:#e65100;font-weight:500}.test-env-notice__description{margin:4px 0 0 0;font-size:13px;color:#5d4037;line-height:1.5}.test-env-notice__auth-code{color:#e65100;font-family:monospace;font-size:14px;font-weight:bold}.hero-carousel-section{position:relative;width:100%;overflow:hidden}.hero-carousel-container{position:relative;width:100%;height:580px;overflow:hidden;background-color:#e9f9ff}@media(max-width: 1024px){.hero-carousel-container{height:800px}}@media(max-width: 768px){.hero-carousel-container{height:480px}}.hero-carousel-track{position:relative;width:100%;height:100%}@media(min-width: 1280px){.hero-carousel-track{max-width:1280px;margin:0 auto}}.hero-slide{position:absolute;top:0;left:0;width:100%;height:440px;display:flex;align-items:center;justify-content:center;opacity:0;visibility:hidden;transition:opacity .6s ease-in-out,visibility .6s ease-in-out}.hero-slide.active{opacity:1;visibility:visible}.hero-slide[data-slide="0"]{background:#e9f9ff}.hero-slide[data-slide="1"]{background:#e9f9ff}.hero-slide[data-slide="2"]{background:#e9f9ff}.hero-slide-content{width:100%;max-width:1280px;height:100%;display:flex;align-items:center;justify-content:space-between;position:relative;margin:0 auto}@media(min-width: 1280px){.hero-slide-content{padding:0 26px}}@media(max-width: 1024px){.hero-slide-content{flex-direction:column;padding:80px 40px;justify-content:center;gap:40px}}@media(max-width: 768px){.hero-slide-content{flex-direction:column-reverse;padding:60px 20px 80px;gap:70px;justify-content:flex-end;align-items:center}}.hero-text-content{flex:0 0 auto;display:flex;flex-direction:column;gap:46px;z-index:2;width:593px}@media(max-width: 1024px){.hero-text-content{align-items:center;text-align:center}}@media(max-width: 768px){.hero-text-content{gap:24px;align-items:flex-start;text-align:left;width:100%;padding:0 48px}}.hero-text .hero-subtitle{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:32px;font-weight:400;line-height:99.94%;color:#212529;margin:0 0 12px 0}@media(max-width: 1024px){.hero-text .hero-subtitle{font-size:28px}}@media(max-width: 768px){.hero-text .hero-subtitle{font-size:16px;font-weight:500;line-height:34px;letter-spacing:-0.32px;margin:0}}.hero-text .hero-title{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:46px;font-weight:700;line-height:99.94%;color:#000;margin:0}@media(max-width: 1024px){.hero-text .hero-title{font-size:40px}}@media(max-width: 768px){.hero-text .hero-title{font-size:24px;font-weight:700;line-height:34px;letter-spacing:-0.48px}}.btn-hero-signup{display:inline-flex;align-items:center;justify-content:center;width:178px;height:60px;padding:10px;background:#0049b4;color:#fff;font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:20px;font-weight:700;border-radius:10px;text-decoration:none;transition:all .3s ease}.btn-hero-signup:hover{background:rgb(0,65.7,162);transform:translateY(-2px);box-shadow:0 4px 12px rgba(0,73,180,.3)}.btn-hero-signup:active{transform:translateY(0)}@media(max-width: 1024px){.btn-hero-signup{width:200px}}@media(max-width: 768px){.btn-hero-signup{width:144px;height:40px;padding:8px 10px;font-size:14px;border-radius:8px}}.hero-image-content{flex:0 0 auto;width:556px;height:455px;display:flex;align-items:center;justify-content:center;z-index:1}@media(min-width: 1280px){.hero-image-content{width:410px;height:300px}}@media(max-width: 1024px){.hero-image-content{width:410px;height:300px}}@media(max-width: 768px){.hero-image-content{width:218px;height:180px;max-width:none;flex-shrink:0}}.hero-image-content .hero-image{width:100%;height:100%;object-fit:contain}.hero-nav-btn{position:absolute;top:50%;transform:translateY(-50%);width:60px;height:60px;background:hsla(0,0%,100%,.5);border:none;border-radius:50px;cursor:pointer;display:none;align-items:center;justify-content:center;z-index:10;transition:all .3s ease}.hero-nav-btn:hover{background:hsla(0,0%,100%,.8);transform:translateY(-50%) scale(1.1)}.hero-nav-btn:active{transform:translateY(-50%) scale(0.95)}.hero-nav-btn svg{width:22px;height:22px}@media(min-width: 1280px){.hero-nav-btn{display:flex}}.hero-nav-prev{left:210px}.hero-nav-prev svg{transform:rotate(180deg)}@media(max-width: 768px){.hero-nav-prev{left:20px}}@media(max-width: 1024px){.hero-nav-prev{left:40px}}@media(min-width: 1280px){.hero-nav-prev{left:calc(50vw - 640px - 80px)}}.hero-nav-next{right:210px}@media(max-width: 768px){.hero-nav-next{right:20px}}@media(max-width: 1024px){.hero-nav-next{right:40px}}@media(min-width: 1280px){.hero-nav-next{right:calc(50vw - 640px - 80px)}}.hero-carousel-indicators{position:absolute;bottom:160px;left:calc(50vw - 640px + 72px);transform:translateX(-50%);display:flex;gap:12px;z-index:10}@media(max-width: 768px){.hero-carousel-indicators{top:60px;right:28px;bottom:auto;left:auto;transform:none;flex-direction:row;gap:6px;align-items:center}}.hero-indicator{width:14px;height:14px;border-radius:50%;background:#f1f1f1;border:none;cursor:pointer;padding:0;transition:all .3s ease}.hero-indicator.active{background:#0049b4}.hero-indicator:hover:not(.active){background:rgba(0,0,0,.5)}@media(max-width: 768px){.hero-indicator{width:6px;height:6px;border-radius:50%;background:rgba(0,0,0,0);border:1px solid #8c959f}.hero-indicator.active{width:6px;height:6px;border-radius:50%;background:#0049b4;border:none}}.hero-autoplay-toggle{display:flex;align-items:center;justify-content:center;margin-left:8px;cursor:pointer;padding:0;transition:all .3s ease;position:relative}.hero-autoplay-toggle svg{position:absolute;opacity:0;transition:opacity .3s ease}.hero-autoplay-toggle svg.active{opacity:1}.hero-autoplay-toggle:hover{background:#fff;border-color:#0049b4;box-shadow:0 2px 8px rgba(0,73,180,.2)}@media(max-width: 768px){.hero-autoplay-toggle{display:none}}.api-search-section{position:relative;padding:0;overflow:hidden;background:#e9f9ff;height:314px}.api-search-section .search-background{position:absolute;inset:0;background-image:url("/img/bg_main_intersect.svg");background-size:cover;background-position:center;background-repeat:no-repeat;pointer-events:none}.api-search-section .search-content-wrapper{position:relative;display:flex;align-items:center;justify-content:center;gap:40px;z-index:1}@media(max-width: 1024px){.api-search-section .search-content-wrapper{flex-direction:column;gap:24px;padding-top:40px}}@media(max-width: 768px){.api-search-section .search-content-wrapper{flex-direction:row;flex-wrap:wrap;gap:8px;padding-top:20px;justify-content:center}}.api-search-section .search-character{width:154px;height:148px;flex-shrink:0}.api-search-section .search-character img{width:100%;height:100%;object-fit:contain}@media(max-width: 1024px){.api-search-section .search-character{width:120px;height:115px}}@media(max-width: 768px){.api-search-section .search-character{width:68px;height:65px}}.api-search-section .search-text-content{text-align:left}@media(max-width: 1024px){.api-search-section .search-text-content{text-align:center}}@media(max-width: 768px){.api-search-section .search-text-content{text-align:left}}.api-search-section .search-title{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:36px;font-weight:700;line-height:1.3;color:#000;margin:0}@media(max-width: 1024px){.api-search-section .search-title{font-size:28px}}@media(max-width: 768px){.api-search-section .search-title{font-size:16px;line-height:1.4;color:#212529}}.api-search-section .search-input-wrapper{position:relative;display:flex;justify-content:center;padding:0px 0 30px;z-index:1}@media(max-width: 1024px){.api-search-section .search-input-wrapper{padding:30px 20px 20px}}@media(max-width: 768px){.api-search-section .search-input-wrapper{justify-content:flex-start;padding:0px 28px 12px 27px}}.api-search-section .search-form{width:100%;max-width:816px}.api-search-section .search-box{position:relative;width:100%;height:80px;background:#fff;border:6px solid #0049b4;border-radius:50px;display:flex;align-items:center;padding:0 24px}@media(max-width: 1024px){.api-search-section .search-box{height:60px;border:4px solid #0049b4;padding:0 16px}}@media(max-width: 768px){.api-search-section .search-box{height:36px;border:3px solid #0049b4;border-radius:18px;padding:0 12px}}.api-search-section .search-input{flex:1;width:100%;height:100%;border:none;outline:none;background:rgba(0,0,0,0);font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:17px;font-weight:500;color:#000;padding:0 20px}.api-search-section .search-input::placeholder{color:#b3b3b3;font-weight:400}@media(max-width: 1024px){.api-search-section .search-input{font-size:15px;padding:0 12px}}@media(max-width: 768px){.api-search-section .search-input{font-size:11px;font-weight:500;padding:0 8px}.api-search-section .search-input::placeholder{color:#8c959f;font-weight:500}}.api-search-section .search-icon-button{background:none;border:none;cursor:pointer;padding:0;display:flex;align-items:center;justify-content:center;width:40px;height:40px;flex-shrink:0;transition:transform .3s ease}.api-search-section .search-icon-button:hover{transform:scale(1.1)}.api-search-section .search-icon-button:active{transform:scale(0.95)}.api-search-section .search-icon-button svg{width:33px;height:34px}@media(max-width: 1024px){.api-search-section .search-icon-button svg{width:28px;height:29px}}@media(max-width: 768px){.api-search-section .search-icon-button svg{width:16px;height:16px}}@media(max-width: 768px){.api-search-section .search-icon-button{width:20px;height:20px}}.api-search-section .hashtag-section{position:relative;display:flex;align-items:center;justify-content:center;gap:12px;padding:0 20px 0px;z-index:1}@media(max-width: 1024px){.api-search-section .hashtag-section{flex-direction:column;align-items:center;justify-content:center;gap:8px;padding:0 20px 30px}}@media(max-width: 768px){.api-search-section .hashtag-section{flex-direction:row;flex-wrap:wrap;gap:12px;padding:0 20px 20px;justify-content:center}}.api-search-section .hashtag-label{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:16px;font-weight:700;color:#000;white-space:nowrap}@media(max-width: 768px){.api-search-section .hashtag-label{font-size:12px;color:#212529}}.api-search-section .hashtag-list{display:flex;align-items:center;gap:14px;flex-wrap:wrap}@media(max-width: 1024px){.api-search-section .hashtag-list{justify-content:center;gap:10px}}@media(max-width: 768px){.api-search-section .hashtag-list{gap:8px;justify-content:left}}.api-search-section .hashtag-link{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:16px;font-weight:400;color:#000;text-decoration:none;white-space:nowrap;transition:color .3s ease}.api-search-section .hashtag-link:hover{color:#0049b4;text-decoration:underline}@media(max-width: 1024px){.api-search-section .hashtag-link{font-size:14px}}@media(max-width: 768px){.api-search-section .hashtag-link{font-size:12px;color:#212529}}.api-search-section .hashtag-separator{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:16px;color:#000;user-select:none}@media(max-width: 1024px){.api-search-section .hashtag-separator{font-size:14px}}@media(max-width: 768px){.api-search-section .hashtag-separator{font-size:10px;color:#212529;display:flex;align-items:center;height:10px}}.api-showcase{position:relative;display:flex;align-items:center;padding:75px 0 100px;background:#fff;overflow:hidden;height:870px}@media(max-width: 768px){.api-showcase{height:auto;padding:40px 0 60px}}.api-showcase .showcase-background{position:absolute;inset:0;background-image:url("/img/bg_main_recommend_apis.png");background-size:cover;background-position:center;background-repeat:no-repeat;pointer-events:none}.api-showcase .showcase-wrapper{max-width:1228px;margin:0 auto;position:relative;width:100%;z-index:1}.api-showcase .section-header{text-align:center;margin-bottom:24px;position:relative;display:flex;flex-direction:column}@media(max-width: 1024px){.api-showcase .section-header{padding-right:0}}@media(max-width: 768px){.api-showcase .section-header{display:flex;align-items:center;justify-content:center;padding-right:0;margin-bottom:18px;position:relative}}.api-showcase .section-title{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:44px;font-weight:700;line-height:1.3;color:#000;margin:0}@media(max-width: 1024px){.api-showcase .section-title{font-size:36px}}@media(max-width: 768px){.api-showcase .section-title{font-size:20px;color:#212529;letter-spacing:-0.4px}}.api-showcase .section-subtitle{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:26px;font-weight:400;line-height:1.5;color:#1e1e1e;margin-bottom:30px}@media(max-width: 1024px){.api-showcase .section-subtitle{font-size:20px}}@media(max-width: 768px){.api-showcase .section-subtitle{display:none}}.api-showcase .btn-all-apis{align-self:flex-end;display:inline-flex;align-items:center;justify-content:center;gap:21px;padding:12px 12px;font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:15px;font-weight:500;color:#fff;background:#008ae2;text-decoration:none;border-radius:10px;transition:all .3s ease;height:40px;width:135px;white-space:nowrap}@media(max-width: 1024px){.api-showcase .btn-all-apis{position:static;transform:none;margin:24px auto 0}}@media(max-width: 768px){.api-showcase .btn-all-apis{position:absolute;top:50%;right:0;transform:translateY(-50%);margin:0;width:72px;height:24px;padding:10px;font-size:10px;font-weight:700;border-radius:4px;gap:10px}}.api-showcase .btn-all-apis svg{width:11px;height:17px;transition:transform .3s ease}.api-showcase .btn-all-apis svg path{stroke:#fff}@media(max-width: 768px){.api-showcase .btn-all-apis svg{width:5px;height:8px}}.api-showcase .btn-all-apis:hover{background:rgb(0,124.2,203.4)}.api-showcase .btn-all-apis:hover svg{transform:translateX(4px)}.api-showcase .api-cards-container{display:flex;gap:32px;justify-content:center}@media(max-width: 1024px){.api-showcase .api-cards-container{flex-wrap:wrap;gap:20px;justify-content:center}}@media(max-width: 768px){.api-showcase .api-cards-container{display:grid;grid-template-columns:repeat(2, 1fr);gap:12px 17px}}.api-showcase .api-card{width:283px;height:320px;background:#fff;border:1px solid #ddd;border-radius:20px;padding:36px 38px;display:flex;flex-direction:column;align-items:flex-start;position:relative;transition:all .3s ease;gap:0px;cursor:pointer}.api-showcase .api-card:hover{transform:translateY(-4px);box-shadow:0 8px 24px rgba(0,0,0,.1);border-color:#008ae2}@media(max-width: 1024px){.api-showcase .api-card{width:calc(50% - 10px);min-width:250px}}@media(max-width: 768px){.api-showcase .api-card{width:100%;height:auto;min-width:unset;min-height:200px;padding:20px 19px;border-radius:12px;border:1px solid #dadada;box-shadow:2px 2px 5px 0px rgba(185,204,222,.3);gap:20px}}.api-showcase .card-title{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:24px;font-weight:700;line-height:1.67;color:#2a2a2a;margin:0 0 20px 0}@media(max-width: 768px){.api-showcase .card-title{font-size:15px;line-height:1;color:#000;margin:0 0 12px 0}}.api-showcase .card-description{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:15px;font-weight:400;line-height:1.43;color:#515151;margin:0;flex:1;text-align:left}@media(max-width: 768px){.api-showcase .card-description{font-size:12px;font-weight:500;line-height:1}}.api-showcase .card-illustration{width:90px;height:90px;display:flex;align-items:center;justify-content:center;margin-top:auto;align-self:flex-end}.api-showcase .card-illustration img{width:100%;height:100%;object-fit:contain}@media(max-width: 768px){.api-showcase .card-illustration{width:48px;height:48px}}.info-section{position:relative;padding:100px 0;background:#fff;overflow:hidden;height:740px;display:flex;align-items:center}@media(max-width: 768px){.info-section{min-height:392px;padding:75px 20px 20px}}.info-section .info-background{position:absolute;inset:0;background-image:url("/img/bg_api_intro.svg");background-size:cover;background-position:center;pointer-events:none}.info-section .container{position:relative;z-index:1}@media(max-width: 768px){.info-section .container{margin:0;padding:0;max-width:100%;width:100%}}.info-section .info-wrapper{display:flex;align-items:center;justify-content:space-between;gap:80px}@media(max-width: 1024px){.info-section .info-wrapper{flex-direction:column;gap:64px}}@media(max-width: 768px){.info-section .info-wrapper{display:block;flex-direction:column;gap:20px;position:relative}}.info-section .info-content{flex:1;max-width:600px}@media(max-width: 1024px){.info-section .info-content{text-align:center;max-width:100%}}@media(max-width: 768px){.info-section .info-content{text-align:left;max-width:100%;order:1}}.info-section .info-title{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:28px;font-weight:400;line-height:1.4;color:#000;margin:0 0 24px 0}@media(max-width: 1024px){.info-section .info-title{font-size:36px}}@media(max-width: 768px){.info-section .info-title{font-size:16px;font-weight:500;line-height:1;color:#212529;margin:0 0 8px 0}.info-section .info-title span{margin-bottom:10px}}.info-section .info-title .title-highlight{font-weight:700;color:#0049b4;font-size:36px}@media(max-width: 768px){.info-section .info-title .title-highlight{font-size:20px;display:block;letter-spacing:-0.4px;margin-top:8px}}.info-section .info-description{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:20px;font-weight:400;line-height:1.6;color:#000;margin:0 0 48px 0}@media(max-width: 1024px){.info-section .info-description{font-size:16px}}@media(max-width: 768px){.info-section .info-description{font-size:14px;font-weight:400;line-height:22px;color:#515961;margin:24px 0 20px 0}}.info-section .action-buttons{display:flex;gap:24px}@media(max-width: 1024px){.info-section .action-buttons{justify-content:center}}@media(max-width: 768px){.info-section .action-buttons{flex-direction:row;align-items:stretch;gap:15px;width:100%}}.info-section .action-btn{display:inline-flex;align-items:center;justify-content:center;padding:18px 52px;font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:24px;font-weight:700;line-height:56px;border-radius:20px;text-decoration:none;white-space:nowrap;transition:all .3s ease;min-width:310px;height:128px}@media(max-width: 768px){.info-section .action-btn{font-size:14px;font-weight:700;line-height:1.4;padding:8px 10px;min-width:unset;width:calc(50% - 7.5px);height:72px;border-radius:8px;white-space:normal;word-break:keep-all;text-align:center !important;display:flex !important;align-items:center !important;justify-content:center !important;flex-direction:column}}.info-section .action-btn:hover{transform:translateY(-3px);box-shadow:0 8px 24px rgba(0,0,0,.15)}.info-section .action-btn:active{transform:translateY(0)}.info-section .action-btn-primary{background:#0049b4;color:#fff}.info-section .action-btn-primary:hover{background:rgb(0,65.7,162)}.info-section .action-btn-secondary{background:#00acdd;color:#fff}.info-section .action-btn-secondary:hover{background:rgb(0,154.8,198.9)}.info-section .info-image{flex:1;max-width:560px;display:flex;align-items:center;justify-content:center}.info-section .info-image img{width:100%;height:auto;object-fit:contain}@media(max-width: 1024px){.info-section .info-image{max-width:100%}}@media(max-width: 768px){.info-section .info-image{position:absolute;top:-90px;right:0;width:128px;height:128px;max-width:128px;flex:none}.info-section .info-image img{width:128px;height:128px}}.support-center{position:relative;padding:0;padding-top:102px;padding-bottom:126px;background:#eef7fd;overflow:hidden;height:720px}@media(max-width: 768px){.support-center{min-height:392px;padding:60px 20px}}.support-center .support-background{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);width:100%;height:100%;max-width:1920px;pointer-events:none}.support-center .support-background::before{content:"";position:absolute;top:17px;left:46%;width:693px;height:597px;background-image:url("/img/bg_support_center.png");background-size:cover;background-repeat:no-repeat;opacity:.5}@media(max-width: 768px){.support-center .support-background::before{top:0;left:auto;right:0;width:187px;height:177px;opacity:1}}.support-center .container{position:relative;z-index:1;max-width:1280px;margin:0 auto;padding:0 26px;height:492px}@media(max-width: 768px){.support-center .container{padding:0;max-width:100%;width:100%}}.support-center .section-header{margin-bottom:40px;text-align:center}@media(max-width: 768px){.support-center .section-header{margin-bottom:24px;text-align:left}}.support-center .section-header .section-title{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:28px;font-weight:400;line-height:1.4;color:#000;margin:0 0 24px 0;text-align:left}@media(max-width: 1024px){.support-center .section-header .section-title{font-size:36px;line-height:1.4}}@media(max-width: 768px){.support-center .section-header .section-title{font-size:16px;line-height:1;letter-spacing:-0.32px}}@media(max-width: 768px){.support-center .section-header .section-title .title-regular{font-weight:500}}.support-center .section-header .section-title .title-bold{font-size:38px;font-weight:700}@media(max-width: 768px){.support-center .section-header .section-title .title-bold{font-size:20px;letter-spacing:-0.4px}}.support-center .support-grid{display:flex;gap:32px;justify-content:center;align-items:stretch;max-width:1228px;height:320px;margin:0 auto}@media(max-width: 1024px){.support-center .support-grid{flex-direction:column;align-items:center;gap:20px}}@media(max-width: 768px){.support-center .support-grid{flex-direction:row;justify-content:space-between;align-items:stretch;gap:12px;max-width:100%}}.support-center .support-card{background:#fff;border-radius:20px;text-decoration:none;transition:all .3s ease;display:flex;box-sizing:border-box;position:relative;overflow:hidden;height:320px}@media(max-width: 1024px){.support-center .support-card{width:100%;max-width:598px;height:auto;min-height:260px}}@media(max-width: 768px){.support-center .support-card{width:calc(33.333% - 8px);min-width:0;height:168px;min-height:168px;max-width:none;border-radius:12px;padding:26px 0 25px;flex-direction:column;align-items:center;justify-content:flex-start;gap:16px}}.support-center .support-card:hover{transform:translateY(-5px);box-shadow:0 10px 30px rgba(0,73,180,.15)}@media(max-width: 768px){.support-center .support-card:hover{transform:translateY(-2px)}}.support-center .support-card .card-icon{display:flex;align-items:center;justify-content:center}.support-center .support-card .card-icon img{width:100%;height:100%;object-fit:contain}@media(max-width: 768px){.support-center .support-card .card-icon{width:48px !important;height:48px !important;margin-bottom:0 !important}}.support-center .support-card .card-content{display:flex;flex-direction:column;justify-content:center}@media(max-width: 768px){.support-center .support-card .card-content{align-items:center;text-align:center;gap:4px}}.support-center .support-card .card-content h3{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:32px;font-weight:700;color:#212529;margin:0 0 10px 0;line-height:1.3}@media(max-width: 1024px){.support-center .support-card .card-content h3{font-size:28px}}@media(max-width: 768px){.support-center .support-card .card-content h3{font-size:15px;margin:0;line-height:1}}.support-center .support-card .card-content p{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:18px;font-weight:400;color:#515961;margin:0;line-height:1.4}@media(max-width: 1024px){.support-center .support-card .card-content p{font-size:16px}}@media(max-width: 768px){.support-center .support-card .card-content p{font-size:10px;line-height:1}}.support-center .support-card--notice{width:388px;flex-shrink:0;flex-direction:column;align-items:center;padding:50px 40px;background:#0049b4}@media(max-width: 1024px){.support-center .support-card--notice{width:100%;max-width:598px;padding:50px 40px}}@media(max-width: 768px){.support-center .support-card--notice{width:calc(33.333% - 8px);padding:26px 0 25px;gap:19px}}.support-center .support-card--notice .card-icon{width:120px;height:120px;margin-bottom:36px}.support-center .support-card--notice .card-content{align-items:center;text-align:center}.support-center .support-card--notice .card-content h3{color:#fff;font-size:30px}@media(max-width: 1024px){.support-center .support-card--notice .card-content h3{font-size:32px}}@media(max-width: 768px){.support-center .support-card--notice .card-content h3{font-size:15px;color:#fff}}.support-center .support-card--notice .card-content p{color:hsla(0,0%,100%,.9);font-size:22px}@media(max-width: 1024px){.support-center .support-card--notice .card-content p{font-size:18px}}@media(max-width: 768px){.support-center .support-card--notice .card-content p{font-size:10px;color:hsla(0,0%,100%,.9)}}.support-center .support-card--notice:hover{background:rgb(0,69.35,171)}.support-center .support-card--faq,.support-center .support-card--qna{width:388px;flex-shrink:0;flex-direction:column;align-items:center;padding:50px 40px}@media(max-width: 1024px){.support-center .support-card--faq,.support-center .support-card--qna{width:100%;max-width:598px;padding:40px}}@media(max-width: 768px){.support-center .support-card--faq,.support-center .support-card--qna{width:calc(33.333% - 8px);padding:26px 0 25px}}.support-center .support-card--faq .card-icon,.support-center .support-card--qna .card-icon{width:120px;height:120px;margin-bottom:36px}.support-center .support-card--faq .card-content,.support-center .support-card--qna .card-content{align-items:center;text-align:center;flex:none}.support-center .support-card--faq .card-content h3,.support-center .support-card--qna .card-content h3{font-size:30px}@media(max-width: 1024px){.support-center .support-card--faq .card-content h3,.support-center .support-card--qna .card-content h3{font-size:32px}}@media(max-width: 768px){.support-center .support-card--faq .card-content h3,.support-center .support-card--qna .card-content h3{font-size:16px}}.support-center .support-card--faq .card-content p,.support-center .support-card--qna .card-content p{font-size:22px}@media(max-width: 1024px){.support-center .support-card--faq .card-content p,.support-center .support-card--qna .card-content p{font-size:18px}}@media(max-width: 768px){.support-center .support-card--faq .card-content p,.support-center .support-card--qna .card-content p{font-size:10px}}.support-center .support-card--faq{background:#daf0ff}.support-center .support-card--faq .card-content h3{color:#212529}.support-center .support-card--qna{background:#d9f6f8}.support-center .support-card--qna .card-content h3{color:#212529}.api-stats-section{position:relative;height:630px;padding:0;padding-top:132px;padding-bottom:134px;overflow:hidden;display:flex;align-items:center;justify-content:center}@media(max-width: 1024px){.api-stats-section{min-height:600px;padding:64px 0}}@media(max-width: 768px){.api-stats-section{min-height:320px;padding:60px 20px;align-items:flex-start}}.stats-background{position:absolute;inset:0;background-image:url("/img/bg_main_api_usage.png");background-size:cover;background-position:center;background-repeat:no-repeat;overflow:hidden}.api-stats-section .container{position:relative;z-index:1}@media(max-width: 768px){.api-stats-section .container{display:flex;flex-direction:column;gap:24px;width:100%;max-width:100%;padding:0}}.api-stats-section .section-header{text-align:center;height:100px}@media(max-width: 768px){.api-stats-section .section-header{text-align:left;margin-bottom:0}}.api-stats-section .section-title{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;line-height:1.5;margin:0}.api-stats-section .section-title .title-top{display:block;font-size:28px;font-weight:400;color:#e8e8e8}@media(max-width: 1024px){.api-stats-section .section-title .title-top{font-size:28px}}@media(max-width: 768px){.api-stats-section .section-title .title-top{font-size:16px;font-weight:500;letter-spacing:-0.32px;margin-bottom:0}}.api-stats-section .section-title .title-highlight{font-size:38px;font-weight:700;color:#81d5ff}@media(max-width: 1024px){.api-stats-section .section-title .title-highlight{font-size:36px}}@media(max-width: 768px){.api-stats-section .section-title .title-highlight{font-size:20px}}.api-stats-section .section-title{font-size:38px;font-weight:700;color:#e8e8e8}@media(max-width: 1024px){.api-stats-section .section-title{font-size:36px}}@media(max-width: 768px){.api-stats-section .section-title{font-size:20px;font-weight:700;line-height:1;letter-spacing:-0.4px}}.api-stats-section .stats-cards{display:flex;align-items:center;justify-content:center;gap:0;max-width:1200px;margin:0 auto}@media(max-width: 1024px){.api-stats-section .stats-cards{flex-direction:column;gap:40px}}@media(max-width: 768px){.api-stats-section .stats-cards{flex-direction:row !important;justify-content:center;align-items:center;gap:0 !important;padding:0;height:132px;max-width:100%;margin:0}}.api-stats-section .stat-card{display:flex;flex-direction:column;align-items:center;gap:10px;width:145px}@media(max-width: 1024px){.api-stats-section .stat-card{width:auto}}@media(max-width: 768px){.api-stats-section .stat-card{gap:11px;padding:10px 14px;height:132px;justify-content:center;width:120px}}.api-stats-section .stat-card .stat-label{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:20px;font-weight:500;color:#fff;line-height:40px;margin:0;white-space:nowrap}@media(max-width: 768px){.api-stats-section .stat-card .stat-label{font-size:10px;font-weight:500;line-height:1;order:-1}}.api-stats-section .stat-card .stat-icon{width:70px;height:70px;display:flex;align-items:center;justify-content:center;transition:transform .3s ease}.api-stats-section .stat-card .stat-icon svg{width:100%;height:100%;filter:drop-shadow(0 4px 8px rgba(0, 0, 0, 0.2))}.api-stats-section .stat-card .stat-icon img{width:100%;height:100%;object-fit:contain}@media(max-width: 768px){.api-stats-section .stat-card .stat-icon{width:40px;height:40px}}.api-stats-section .stat-card .stat-number{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:44px;font-weight:700;color:#efdcb2;line-height:61px;margin:0;text-align:center;white-space:nowrap}@media(max-width: 1024px){.api-stats-section .stat-card .stat-number{font-size:40px}}@media(max-width: 768px){.api-stats-section .stat-card .stat-number{font-size:16px;line-height:1}}.api-stats-section .stat-card:hover .stat-icon{transform:scale(1.1)}.api-stats-section .stats-divider{width:1px;height:200px;margin:0 60px;flex-shrink:0;background-color:#fff}@media(max-width: 1024px){.api-stats-section .stats-divider{display:none}}@media(min-width: 1280px){.api-stats-section .stats-divider{margin:0 120px}}@media(max-width: 768px){.api-stats-section .stats-divider{display:block !important;width:1px;height:108px;margin:0;background-color:#fafafa}}.stat-number{animation:countUp .6s ease-out forwards}@keyframes countUp{from{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.signup-cta-section{position:relative;min-height:329px;display:flex;align-items:center;justify-content:center;overflow:hidden}@media(max-width: 768px){.signup-cta-section{min-height:160px;padding:44px 0}}.signup-cta-section .cta-background{position:absolute;inset:0;background-image:url("/img/bg_main_join.png");background-size:cover;background-position:center;background-repeat:no-repeat;overflow:hidden}.signup-cta-section .container{position:relative;z-index:1}.signup-cta-section .cta-content{display:flex;flex-direction:column;align-items:center;gap:40px;text-align:center}@media(max-width: 768px){.signup-cta-section .cta-content{gap:12px}}.signup-cta-section .cta-title{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:40px;font-weight:700;color:#000;line-height:normal;margin:0}@media(max-width: 1024px){.signup-cta-section .cta-title{font-size:32px}}@media(max-width: 768px){.signup-cta-section .cta-title{font-size:16px;font-weight:700;color:#212529;letter-spacing:-0.32px;line-height:.9994}}.signup-cta-section .btn-signup-cta{display:inline-flex;align-items:center;justify-content:center;padding:10px;min-height:60px;width:178px;background:#008ae2;color:#fff;font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:20px;font-weight:700;border-radius:10px;text-decoration:none;transition:all .3s ease}.signup-cta-section .btn-signup-cta:hover{background:rgb(0,124.2,203.4);transform:translateY(-2px);box-shadow:0 4px 12px rgba(0,138,226,.3)}.signup-cta-section .btn-signup-cta:active{transform:translateY(0)}@media(max-width: 768px){.signup-cta-section .btn-signup-cta{width:152px;min-height:40px;padding:12px 34px;font-size:12px;font-weight:700;border-radius:4px;line-height:16px}}.signup-cta-section .cta-divider{position:absolute;bottom:0;left:0;right:0;height:1px;background:#ddd}.api-market-container{display:flex;min-height:100vh;background-color:#fff;position:relative;margin-bottom:20px}.api-market-sidebar{width:283px;background-color:#f8fbfd;border-radius:20px;padding:40px 24px;position:sticky;top:24px;height:calc(100vh - 32px);flex-shrink:0;margin:16px}@media(max-width: 1024px){.api-market-sidebar{width:240px}}@media(max-width: 768px){.api-market-sidebar{position:fixed;left:0;top:60px;transform:translateX(-100%);transition:transform .3s ease;box-shadow:0 8px 24px rgba(75,155,255,.15);z-index:500;height:calc(100vh - 60px);margin:0;border-radius:0}.api-market-sidebar.mobile-open{transform:translateX(0)}}.api-sidebar-header{display:flex;justify-content:center;margin-bottom:32px;padding-bottom:24px}.api-sidebar-header img{max-width:100%;height:auto}.api-sidebar-nav .menu-section{margin-bottom:0;border-bottom:1px solid #ddd}.api-sidebar-nav .menu-section:last-child{border-bottom:none}.api-sidebar-nav .menu-title{padding:16px 10px;font-size:20px;font-weight:400;color:#1a1a2e;cursor:pointer;transition:all .3s ease;display:flex;align-items:center;justify-content:space-between;position:relative}.api-sidebar-nav .menu-title:hover{background-color:rgba(0,73,180,.05)}.api-sidebar-nav .menu-title.active{font-weight:700;color:#0049b4}.api-sidebar-nav .menu-title .api-count{display:inline-flex;align-items:center;justify-content:center;min-width:24px;height:24px;padding:0 8px;background-color:rgba(0,73,180,.1);color:#0049b4;font-size:12px;font-weight:600;border-radius:50px;transition:all .3s ease}.api-sidebar-nav .menu-title.active .api-count{background-color:#0049b4;color:#fff}.api-sidebar-nav .accordion-section .menu-title.accordion-trigger .menu-title-text{flex:1}.api-sidebar-nav .accordion-section .menu-title.accordion-trigger .accordion-icon{display:flex;align-items:center;justify-content:center;width:20px;height:20px;transition:transform .3s ease}.api-sidebar-nav .accordion-section .menu-title.accordion-trigger .accordion-icon i{font-size:12px;color:#64748b;transition:all .3s ease}.api-sidebar-nav .accordion-section .menu-title.accordion-trigger:hover .accordion-icon i{color:#0049b4}.api-sidebar-nav .accordion-section .menu-title.accordion-trigger.expanded .accordion-icon{transform:rotate(180deg)}.api-sidebar-nav .accordion-section .menu-title.accordion-trigger.active .accordion-icon i{color:#0049b4}.api-sidebar-nav .api-list{padding-left:16px;margin-top:0;margin-bottom:0}.api-sidebar-nav .api-list.accordion-content{max-height:0;overflow:hidden;transition:max-height .3s ease,padding .3s ease,margin .3s ease}.api-sidebar-nav .api-list.accordion-content.expanded{max-height:500px;margin-top:4px;margin-bottom:8px}.api-sidebar-nav .api-item{padding:8px 16px;font-size:14px;color:#64748b;cursor:pointer;border-radius:6px;transition:all .3s ease;display:flex;align-items:center;position:relative;margin-bottom:4px}.api-sidebar-nav .api-item::before{content:"•";margin-right:8px;color:#0049b4;opacity:.5}.api-sidebar-nav .api-item:hover{background-color:rgba(0,73,180,.05);color:#1a1a2e;padding-left:20px}.api-sidebar-nav .api-item:hover::before{opacity:1}.api-sidebar-nav .api-item.active{background-color:rgba(0,73,180,.1);color:#0049b4;font-weight:500}.api-sidebar-nav .api-item.active::before{opacity:1}.api-sidebar-nav .api-item .api-name{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.api-market-content{flex:1;padding-top:80px;padding-left:8px;padding-right:8px;min-height:100vh;display:flex;flex-direction:column}@media(max-width: 768px){.api-market-content{padding:24px 16px}}.api-market-content .api-result-count{font-size:20px;color:#000;white-space:nowrap}.api-market-content .api-result-count strong{color:#0049b4;font-weight:600}.api-mobile-toggle{display:none;position:fixed;bottom:24px;right:24px;width:56px;height:56px;border-radius:50%;border:none;color:#fff;font-size:24px;cursor:pointer;box-shadow:0 8px 24px rgba(75,155,255,.15);z-index:501;transition:all .3s ease;background-color:#0049b4}.api-mobile-toggle:hover{transform:scale(1.05);box-shadow:0 16px 40px rgba(75,155,255,.2)}.api-mobile-toggle:active{transform:scale(0.95)}@media(max-width: 768px){.api-mobile-toggle{display:flex;align-items:center;justify-content:center}}.api-market-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:24px}@media(max-width: 768px){.api-market-header{flex-direction:column;align-items:flex-start;gap:24px}}.api-market-title h1{font-size:14px;color:#64748b;font-weight:400;margin-bottom:4px}.api-market-title h2{font-size:32px;font-weight:700;color:#1a1a2e}.api-market-search{position:relative;width:300px}@media(max-width: 768px){.api-market-search{width:100%}}.api-market-search form{position:relative;display:flex;align-items:center}.api-market-search input{flex:1;padding:12px 48px 12px 16px;border:1px solid #e2e8f0;border-radius:12px;font-size:14px;outline:none;transition:all .3s ease}.api-market-search input:focus{border-color:#0049b4;box-shadow:0 0 0 3px rgba(0,73,180,.1)}.api-market-search input::placeholder{color:#94a3b8}.api-market-search .search-submit-btn{position:absolute;right:4px;top:50%;transform:translateY(-50%);width:36px;height:36px;background:rgba(0,0,0,0);border:none;border-radius:8px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .3s ease;color:#64748b}.api-market-search .search-submit-btn:hover{background:#eff6ff;color:#0049b4;transform:translateY(-50%) scale(1.05)}.api-market-search .search-submit-btn:active{transform:translateY(-50%) scale(0.95)}.api-market-search .search-submit-btn .search-icon{font-size:18px;display:block}.api-card-grid{display:grid;grid-template-columns:repeat(3, 320px);gap:24px;justify-content:center}@media(max-width: 1024px){.api-card-grid{grid-template-columns:repeat(2, 320px)}}@media(max-width: 768px){.api-card-grid{grid-template-columns:1fr}}.api-card{background:#fff;border-radius:12px;padding:32px;box-shadow:0 2px 4px rgba(0,0,0,.05);cursor:pointer;display:flex;flex-direction:column;gap:16px;min-height:180px;position:relative;border:1px solid #e2e8f0}.api-card:active{transform:scale(0.98)}.api-card-group{display:flex;align-items:center;gap:8px}.api-card-group-icon{width:32px;height:32px;display:flex;align-items:center;justify-content:center;background:#f8fafc;border-radius:8px;flex-shrink:0}.api-card-group-icon img{width:20px;height:20px;object-fit:contain}.api-card-group-icon i{font-size:16px;color:#0049b4}.api-card-group-name{font-size:14px;color:#64748b;font-weight:500}.api-card-name{font-size:20px;font-weight:600;color:#1a1a2e;line-height:1.2;margin:0;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;text-align:left}.api-card-description{font-size:14px;color:#64748b;line-height:1.6;margin:0;flex-grow:1;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;text-align:left}.api-empty-state{text-align:center;padding:80px 24px;color:#64748b}.api-empty-state .empty-icon{font-size:80px;margin-bottom:24px;opacity:.5}.api-empty-state h3{font-size:24px;font-weight:600;color:#1a1a2e;margin-bottom:16px}.api-empty-state p{font-size:16px;color:#64748b}.api-loading{display:flex;justify-content:center;align-items:center;padding:80px}.api-loading .spinner{width:48px;height:48px;border:4px solid #e2e8f0;border-top-color:#0049b4;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.api-mobile-overlay{display:none;position:fixed;top:60px;left:0;right:0;bottom:0;background-color:rgba(0,0,0,.5);z-index:499;opacity:0;transition:opacity .3s ease;pointer-events:none}@media(max-width: 768px){.api-mobile-overlay{display:block}}.api-mobile-overlay.active{opacity:1;pointer-events:auto}.api-detail-content{display:flex;flex-direction:column;gap:32px}.api-detail-tabs{display:flex;gap:8px;border-bottom:2px solid #e2e8f0;margin-bottom:32px}.api-detail-tabs .tab-button{padding:16px 24px;background:rgba(0,0,0,0);border:none;border-bottom:3px solid rgba(0,0,0,0);font-size:16px;font-weight:500;color:#64748b;cursor:pointer;transition:all .3s ease;position:relative;bottom:-2px}.api-detail-tabs .tab-button:hover{color:#0049b4;background-color:rgba(0,73,180,.05)}.api-detail-tabs .tab-button.active{color:#0049b4;font-weight:600;border-bottom-color:#0049b4}.tab-content{display:none;flex-direction:column;gap:32px;min-height:200px}.tab-content.active{display:flex}.api-overview-card{background:rgba(0,0,0,0);border-radius:0;padding:0;padding-bottom:32px;display:flex;flex-direction:column;gap:24px}.api-overview-card .api-overview-header{display:flex;flex-direction:row;align-items:center;gap:16px}@media(max-width: 768px){.api-overview-card .api-overview-header{flex-direction:column;align-items:flex-start}}.api-overview-card .api-method-badge{background:#0049b4;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;padding:8px 16px;height:100%;color:#fff;font-size:14px;font-weight:600;border-radius:6px;text-transform:uppercase;letter-spacing:.5px}.api-overview-card .api-endpoint{flex:1}.api-overview-card .api-endpoint code{display:block;padding:8px 16px;background-color:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;font-size:18px;font-family:"Courier New",monospace;color:#1a1a2e;word-break:break-all}.api-overview-card .api-simple-description{padding:16px 0;border-bottom:1px solid #e2e8f0}.api-overview-card .api-simple-description p{font-size:16px;color:#64748b;line-height:1.6;margin:0}.api-overview-card .api-overview-info h4{font-size:18px;font-weight:600;color:#1a1a2e;margin-bottom:16px}.api-details-grid{display:flex;flex-direction:column;gap:24px}.api-detail-card{background:rgba(0,0,0,0);border-radius:0;padding:0;padding-bottom:32px;border-bottom:1px solid #e2e8f0}.api-detail-card:last-child{border-bottom:none;padding-bottom:0}.api-detail-card h3{font-size:20px;font-weight:600;color:#1a1a2e;margin-bottom:16px;padding-bottom:0;border-bottom:none}.api-detail-card .detail-content{font-size:14px;color:#64748b;line-height:1.6}.api-detail-card .detail-content pre{background-color:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;padding:16px;overflow-x:auto;margin:0}.api-detail-card .detail-content pre code{font-family:"Courier New",monospace;font-size:14px;color:#1a1a2e;white-space:pre-wrap;word-break:break-word}.api-detail-card .detail-content table{width:100%;border-collapse:collapse;margin:16px 0;background-color:#fff;border:1px solid #e2e8f0;border-radius:8px;overflow:hidden}.api-detail-card .detail-content table thead{background-color:#eff6ff}.api-detail-card .detail-content table th{text-align:left;padding:8px 16px;font-size:14px;font-weight:600;color:#1a1a2e;border-bottom:2px solid #e2e8f0;background-color:rgba(0,73,180,.05)}.api-detail-card .detail-content table th:not(:last-child){border-right:1px solid #e2e8f0}.api-detail-card .detail-content table td{padding:8px 16px;font-size:14px;color:#64748b;border-bottom:1px solid #e2e8f0}.api-detail-card .detail-content table td:not(:last-child){border-right:1px solid #e2e8f0}.api-detail-card .detail-content table tr{transition:all .3s ease}.api-detail-card .detail-content table tr:last-child td{border-bottom:none}.api-detail-card .detail-content table tr:hover{background-color:rgba(0,73,180,.02)}.api-detail-card .detail-content table tbody tr:nth-child(even){background-color:rgba(248,250,252,.3)}.api-info-table{width:100%;border-collapse:collapse}.api-info-table tr{border-bottom:1px solid #e2e8f0}.api-info-table tr:last-child{border-bottom:none}.api-info-table th{text-align:left;padding:8px 16px;font-size:14px;font-weight:600;color:#1a1a2e;width:30%;background-color:#f8fafc}.api-info-table td{padding:8px 16px;font-size:14px;color:#64748b}.api-info-table td code{display:inline-block;padding:2px 8px;background-color:#f8fafc;border-radius:6px;font-family:"Courier New",monospace;font-size:12px;color:#0049b4}@media(max-width: 768px){.api-detail-tabs{gap:0;margin-bottom:24px}.api-detail-tabs .tab-button{flex:1;padding:8px 16px;font-size:14px;text-align:center}.api-overview-card{display:flex;flex-direction:column;gap:16px;padding-bottom:24px}.api-overview-card .api-overview-header{display:flex;flex-direction:column;align-items:flex-start;gap:8px;width:100%}.api-overview-card .api-method-badge{padding:6px 12px;font-size:12px;flex-shrink:0;height:auto}.api-overview-card .api-endpoint{width:100%;flex:none}.api-overview-card .api-endpoint code{display:block;padding:4px 8px;font-size:14px;word-break:break-all;overflow-wrap:break-word}.api-overview-card .api-overview-info{width:100%;margin-top:8px}.api-overview-card .api-overview-info h4{font-size:14px;margin-bottom:8px}.api-overview-card .api-overview-info .api-info-table{width:100%}.api-overview-card .api-simple-description{width:100%;padding:8px 0}.api-overview-card .api-simple-description p{font-size:14px}.api-overview-card .detail-content{width:100%}.api-detail-card{padding-bottom:24px}.api-detail-card h3{font-size:16px;margin-bottom:8px}.api-detail-card .detail-content{font-size:12px;overflow-x:auto;-webkit-overflow-scrolling:touch}.api-detail-card .detail-content table{min-width:500px;margin:8px 0}.api-detail-card .detail-content table th,.api-detail-card .detail-content table td{padding:4px 8px;font-size:12px;white-space:nowrap}.api-detail-card .detail-content pre{padding:8px;overflow-x:auto;-webkit-overflow-scrolling:touch}.api-detail-card .detail-content pre code{font-size:12px;white-space:pre;word-break:normal}.api-info-table{display:block;overflow-x:auto;-webkit-overflow-scrolling:touch}.api-info-table tr{display:table-row}.api-info-table th{padding:4px 8px;font-size:12px;width:auto;min-width:80px;white-space:nowrap}.api-info-table td{padding:4px 8px;font-size:12px}.api-info-table td code{padding:1px 4px;font-size:10px}.api-details-grid{gap:16px}.tab-content{gap:24px;min-height:150px;max-width:100%;overflow-x:hidden}.api-detail-content{max-width:100%;overflow-x:hidden}.api-market-content{max-width:100vw;overflow-x:hidden}}.login-page{display:flex;align-items:center;justify-content:center;background:#edf9fe;padding:40px 20px;position:relative;border-radius:12px;margin-top:60px;margin-bottom:60px}.login-container{width:100%;max-width:504px;margin:0 auto;position:relative}.login-card{background:rgba(0,0,0,0);padding:0;display:flex;flex-direction:column;align-items:center}@media(max-width: 576px){.login-card{padding:0 20px}}.login-logo{width:90px;height:90px;margin-bottom:30px}.login-logo img{width:100%;height:100%;object-fit:contain}.login-message{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:20px;font-weight:400;color:#000;text-align:center;margin-bottom:40px;line-height:1}.login-form{width:100%;margin-bottom:0}.login-form .form-group{margin-bottom:24px}.login-form .form-group:last-of-type{margin-bottom:0}.login-form .form-input{width:100%;height:70px;padding:0 32px;font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:16px;font-weight:400;color:#5f666c;background:#fff;border:1px solid #ddd;border-radius:12px;outline:none;transition:all .3s ease}.login-form .form-input::placeholder{color:#5f666c}.login-form .form-input:focus{border-color:#0049b4}.login-form .form-input.error{border-color:#ff6b6b}.login-form .form-checkbox{display:flex;align-items:center;justify-content:flex-end;gap:7px;margin-top:24px;margin-bottom:22px;margin-right:10px}.login-form .form-checkbox input[type=checkbox]{width:20px;height:20px;border:1.5px solid #000;border-radius:4px;cursor:pointer;appearance:none;background:#fff;position:relative;flex-shrink:0}.login-form .form-checkbox input[type=checkbox]:checked{background:#fff;border-color:#2c2c2c}.login-form .form-checkbox input[type=checkbox]:checked::after{content:"";position:absolute;left:50%;top:45%;transform:translate(-50%, -50%) rotate(45deg);width:6px;height:12px;border:solid #2c2c2c;border-width:0 2px 2px 0}.login-form .form-checkbox label{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:16px;font-weight:400;color:#000;cursor:pointer;user-select:none;line-height:24px;white-space:nowrap}.login-button{width:100%;height:80px;padding:10px;font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:20px;font-weight:700;color:#fff;background:#0049b4;border:none;border-radius:12px;cursor:pointer;transition:all .3s ease;margin-bottom:40px;line-height:1}.login-button:hover{background:rgb(0,69.35,171)}.login-button:active{background:rgb(0,65.7,162)}.login-button:disabled{opacity:.6;cursor:not-allowed}.login-links{display:flex;justify-content:center;align-items:center;gap:26px;flex-wrap:wrap}.login-links a{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:16px;font-weight:400;color:#000;text-decoration:none;transition:color .3s ease;line-height:1}.login-links a:hover{color:#0049b4;text-decoration:underline}.login-links .link-separator{color:#000;font-size:16px}.login-alert{width:100%;margin-bottom:24px;padding:12px 16px;border-radius:8px;font-size:14px;display:flex;align-items:center;gap:8px}.login-alert.alert-error{background:rgba(255,107,107,.1);color:#ff6b6b;border:1px solid rgba(255,107,107,.2)}.login-alert.alert-success{background:rgba(107,207,127,.1);color:#6bcf7f;border:1px solid rgba(107,207,127,.2)}.login-alert.alert-info{background:rgba(0,73,180,.1);color:#0049b4;border:1px solid rgba(0,73,180,.2)}.login-alert i{font-size:18px}.login-loading{position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(237,249,254,.9);display:flex;align-items:center;justify-content:center;z-index:10;opacity:0;pointer-events:none;transition:opacity .3s ease}.login-loading.active{opacity:1;pointer-events:all}.login-loading .spinner{width:40px;height:40px;border:3px solid #ddd;border-top-color:#0049b4;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}@media(max-width: 768px){.login-page{background:#ebfbff;min-height:calc(100vh - 44px);padding:40px 20px;margin-top:0;margin-bottom:0;border-radius:0}.login-container{max-width:100%;padding:0}.login-card{padding:0}.login-logo{width:64px;height:64px;margin-bottom:20px}.login-message{font-size:14px;font-weight:400;color:#212529;margin-bottom:32px;line-height:1.4}.login-form .form-group{margin-bottom:12px}.login-form .form-input{width:100%;height:44px;padding:0 16px;font-size:14px;border-radius:8px;border-color:#dadada}.login-form .form-input::placeholder{font-size:14px;color:#8c959f}.login-form .form-checkbox{justify-content:flex-end;margin-top:16px;margin-bottom:20px;margin-right:0;gap:6px}.login-form .form-checkbox input[type=checkbox]{width:20px;height:20px;border-radius:4px}.login-form .form-checkbox label{font-size:13px;color:#212529;line-height:20px}.login-button{width:100%;height:44px;font-size:16px;font-weight:700;border-radius:8px;margin-bottom:24px}.login-links{gap:12px}.login-links a{font-size:14px;font-weight:400;color:#515961}.login-links .link-separator{font-size:14px;color:#515961}.login-alert{max-width:335px;margin-bottom:16px;padding:10px 14px;font-size:13px}}.account-recovery-page{display:flex;align-items:flex-start;justify-content:center;background:rgba(0,0,0,0);padding:0;position:relative;min-height:auto;margin:0}.account-recovery-container{width:100%;max-width:800px;margin:0 auto;position:relative;padding:24px}.account-recovery-card{background:rgba(0,0,0,0);padding:0;display:flex;flex-direction:column;align-items:stretch;position:relative}@media(max-width: 576px){.account-recovery-card{padding:0 20px}}.account-recovery-logo{display:none}.account-recovery-title{display:none}.account-recovery-tabs{display:flex;gap:0;margin-bottom:0;width:100%;background:rgba(0,0,0,0);border-radius:0;padding:0}.account-recovery-tabs .tab-link{flex:1;display:flex;align-items:center;justify-content:center;padding:14px 24px;font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:16px;font-weight:700;color:#8c959f;text-decoration:none;text-align:center;background:#eceff4;border-radius:0;transition:all .3s ease}.account-recovery-tabs .tab-link:first-child{border-radius:30px 0 0 0}.account-recovery-tabs .tab-link:last-child{border-radius:0 30px 0 0}.account-recovery-tabs .tab-link:hover{color:#3ba4ed;background:#e4e8ed}.account-recovery-tabs .tab-link.active{color:#fff;background:#3ba4ed;font-weight:700}@media(max-width: 576px){.account-recovery-tabs .tab-link{padding:12px 16px;font-size:14px}}.account-alert{width:100%;margin-bottom:20px;padding:14px 18px;border-radius:12px;font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:15px;display:flex;align-items:center;gap:10px;animation:slideDown .3s ease}.account-alert i{font-size:18px;flex-shrink:0}.account-alert.alert-error{background:rgba(255,107,107,.1);color:#ff6b6b;border:1px solid rgba(255,107,107,.3)}.account-alert.alert-success{background:rgba(107,207,127,.1);color:#6bcf7f;border:1px solid rgba(107,207,127,.3)}.account-alert.alert-info{background:rgba(0,73,180,.1);color:#0049b4;border:1px solid rgba(0,73,180,.3)}.account-recovery-form{width:100%;margin-bottom:0;background:#f6f9fb;padding:40px;border-radius:0 0 12px 12px}@media(max-width: 576px){.account-recovery-form{padding:24px 16px}}.account-recovery-form .form-group{display:flex;align-items:center;gap:20px;margin-bottom:20px}.account-recovery-form .form-group:last-of-type{margin-bottom:0}@media(max-width: 768px){.account-recovery-form .form-group{flex-direction:column;align-items:stretch;gap:10px}}.account-recovery-form .form-label{display:flex;align-items:center;flex-shrink:0;width:170px;font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:15px;font-weight:400;color:#212529;margin-bottom:0}.account-recovery-form .form-label .required{color:#ed5b5b;margin-left:2px}@media(max-width: 768px){.account-recovery-form .form-label{width:100%;font-size:14px}}.account-recovery-form .form-input{flex:1;height:40px;padding:0 16px;font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:14px;font-weight:400;color:#212529;background:#fff;border:1px solid #dadada;border-radius:8px;outline:none;transition:all .3s ease}.account-recovery-form .form-input::placeholder{color:#dadada}.account-recovery-form .form-input:hover{border-color:#3ba4ed}.account-recovery-form .form-input:focus{border-color:#3ba4ed;box-shadow:0 0 0 2px rgba(59,164,237,.1)}.account-recovery-form .form-input:disabled{background:#f8f9fa;border-color:#e2e8f0;color:#94a3b8;cursor:not-allowed}.account-recovery-form .form-input.error{border-color:#ed5b5b}.account-recovery-form .form-select{height:40px;padding:0 32px 0 16px;font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:14px;font-weight:400;color:#515151;background:#fff;border:1px solid #dadada;border-radius:8px;outline:none;cursor:pointer;transition:all .3s ease;appearance:none;-webkit-appearance:none;-moz-appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='5' viewBox='0 0 10 5'%3E%3Cpath fill='%231D1B20' d='M5 5L0 0h10z'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 12px center;background-size:10px 5px}.account-recovery-form .form-select:hover{border-color:#3ba4ed}.account-recovery-form .form-select:focus{border-color:#3ba4ed;box-shadow:0 0 0 2px rgba(59,164,237,.1)}.account-recovery-form .form-select:disabled{background-color:#f8f9fa;border-color:#e2e8f0;color:#94a3b8;cursor:not-allowed;opacity:.7}.account-recovery-form .form-select option{padding:8px;font-size:14px}.phone-input-group{display:flex;align-items:center;gap:8px;flex:1}.phone-input-group .phone-prefix{width:100px;flex-shrink:0}.phone-input-group .phone-middle,.phone-input-group .phone-last{width:100px;flex-shrink:0}.phone-input-group .phone-separator{display:flex;align-items:center;justify-content:center;width:8px;height:1px;background:#515151;flex-shrink:0}@media(max-width: 768px){.phone-input-group{flex-wrap:wrap;gap:6px}.phone-input-group .phone-prefix,.phone-input-group .phone-middle,.phone-input-group .phone-last{flex:1;min-width:60px}.phone-input-group .phone-separator{width:6px}}@media(max-width: 576px){.phone-input-group .phone-prefix,.phone-input-group .phone-middle,.phone-input-group .phone-last{width:100%}.phone-input-group .phone-separator{display:none}}.auth-number-group{margin-top:0}.auth-input-group{position:relative;display:flex;align-items:center;flex:1}.auth-input-group .auth-input{flex:1;padding-right:60px}.auth-input-group .auth-timer{position:absolute;right:16px;top:50%;transform:translateY(-50%);font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:14px;font-weight:400;color:#ed5b5b;pointer-events:none}@media(max-width: 576px){.auth-input-group .auth-timer{font-size:12px;right:12px}}.auth-request-button,.auth-verify-button{width:140px;height:40px;padding:8px;font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:14px;font-weight:700;color:#fff;background:#a4d6ea;border:none;border-radius:8px;cursor:pointer;transition:all .3s ease;margin:0;flex-shrink:0;line-height:1}.auth-request-button:hover,.auth-verify-button:hover{background:rgb(147.83125,206.7151785714,230.26875)}.auth-request-button:active,.auth-verify-button:active{background:rgb(131.6625,199.4303571429,226.5375)}.auth-request-button:disabled,.auth-verify-button:disabled{opacity:.6;cursor:not-allowed}@media(max-width: 576px){.auth-request-button,.auth-verify-button{width:100%;height:40px;font-size:13px;margin-top:8px}}.account-recovery-card .form-actions{display:flex;justify-content:center;gap:20px;margin-top:32px;padding:32px 0;border-top:none;background:rgba(0,0,0,0)}.account-recovery-card .form-actions .cancel-button,.account-recovery-card .form-actions .submit-button{width:160px;height:40px;padding:8px;font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:14px;font-weight:700;border:none;border-radius:8px;cursor:pointer;transition:all .3s ease;line-height:1;display:flex;align-items:center;justify-content:center;text-decoration:none}.account-recovery-card .form-actions .cancel-button{color:#5f666c;background:#e5e7eb}.account-recovery-card .form-actions .cancel-button:hover{background:hsl(220,13.0434782609%,86.431372549%)}.account-recovery-card .form-actions .cancel-button:active{background:hsl(220,13.0434782609%,81.8823529412%)}.account-recovery-card .form-actions .submit-button{color:#fff;background:#0049b4}.account-recovery-card .form-actions .submit-button:hover{background:rgb(0,69.35,171)}.account-recovery-card .form-actions .submit-button:active{background:rgb(0,65.7,162)}.account-recovery-card .form-actions .submit-button:disabled{opacity:.6;cursor:not-allowed}@media(max-width: 576px){.account-recovery-card .form-actions{flex-direction:column;gap:10px;padding:20px 0}.account-recovery-card .form-actions .cancel-button,.account-recovery-card .form-actions .submit-button{width:100%;height:40px;font-size:13px}}.loading-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(237,249,254,.9);display:none;align-items:center;justify-content:center;z-index:1000;transition:opacity .3s ease}.loading-overlay.active{display:flex}.loading-overlay .spinner{width:40px;height:40px;border:3px solid #ddd;border-top-color:#0049b4;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}@keyframes slideDown{from{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}@media(max-width: 768px){.account-recovery-page{padding:20px 12px;margin-top:40px;margin-bottom:40px}.account-recovery-container{max-width:100%}}.account-recovery-result{width:100%;background:#f6f9fb;padding:60px 40px;border-radius:0 0 12px 12px;text-align:center}@media(max-width: 576px){.account-recovery-result{padding:40px 20px}}.account-recovery-result .result-header{margin-bottom:40px}.account-recovery-result .result-header .result-icon{font-size:60px;color:#6bcf7f;margin-bottom:20px;display:block}@media(max-width: 576px){.account-recovery-result .result-header .result-icon{font-size:48px}}.account-recovery-result .result-header .result-title{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:24px;font-weight:700;color:#212529;margin-bottom:12px}@media(max-width: 576px){.account-recovery-result .result-header .result-title{font-size:20px}}.account-recovery-result .result-header .result-description{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:16px;font-weight:400;color:#5f666c;margin:0}.account-recovery-result .result-header .result-description strong{color:#0049b4;font-weight:700}@media(max-width: 576px){.account-recovery-result .result-header .result-description{font-size:14px}}.found-users-list{max-width:600px;margin:0 auto 40px}.found-users-list .found-user-item{background:#fff;border:1px solid #dadada;border-radius:12px;padding:20px 24px;margin-bottom:12px;transition:all .3s ease}.found-users-list .found-user-item:last-child{margin-bottom:0}.found-users-list .found-user-item:hover{border-color:#3ba4ed;box-shadow:0 2px 8px rgba(59,164,237,.15)}.found-users-list .found-user-item .user-info{display:flex;align-items:center;justify-content:center;gap:12px;flex-wrap:wrap}@media(max-width: 576px){.found-users-list .found-user-item .user-info{flex-direction:column;gap:6px}}.found-users-list .found-user-item .user-email{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:20px;font-weight:700;color:#212529}@media(max-width: 576px){.found-users-list .found-user-item .user-email{font-size:18px}}.found-users-list .found-user-item .user-date{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:16px;font-weight:400;color:#8c959f}@media(max-width: 576px){.found-users-list .found-user-item .user-date{font-size:14px}}.result-info-box{max-width:600px;margin:0 auto;background:rgba(0,73,180,.05);border:1px solid rgba(0,73,180,.2);border-radius:12px;padding:16px 24px}.result-info-box .info-text{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:15px;font-weight:400;color:#5f666c;margin:0;display:flex;align-items:center;justify-content:center;gap:8px;flex-wrap:wrap}.result-info-box .info-text i{color:#0049b4;font-size:16px}.result-info-box .info-text .info-link{color:#0049b4;font-weight:700;text-decoration:underline;transition:color .3s ease}.result-info-box .info-text .info-link:hover{color:rgb(0,65.7,162)}@media(max-width: 576px){.result-info-box .info-text{font-size:14px;text-align:center}}@media(max-width: 768px){.account-recovery-page{padding:0;margin:0}.account-recovery-container{padding:20px}.account-recovery-card{padding:0}.account-recovery-tabs .tab-link{height:40px;padding:10px 16px;font-size:14px;font-weight:700}.account-recovery-tabs .tab-link:first-child{border-radius:8px 0 0 0}.account-recovery-tabs .tab-link:last-child{border-radius:0 8px 0 0}.account-recovery-form{padding:20px;border-radius:0 0 8px 8px}.account-recovery-form .form-group{flex-direction:column;align-items:stretch;gap:8px;margin-bottom:16px}.account-recovery-form .form-label{width:100%;font-size:14px;font-weight:500;margin-bottom:0}.account-recovery-form .form-input{flex:none;width:100%;height:40px;padding:0 16px;font-size:12px;border-radius:8px}.account-recovery-form .form-input::placeholder{color:#8c959f}.account-recovery-form .form-select{height:40px;padding:0 32px 0 12px;font-size:12px;border-radius:8px;background-position:right 10px center}.phone-input-group{flex-wrap:wrap;gap:8px;align-items:center;width:100%}.phone-input-group .phone-prefix{flex:1;min-width:0;width:auto}.phone-input-group .phone-middle,.phone-input-group .phone-last{flex:1;min-width:0;width:auto}.phone-input-group .phone-separator{display:flex;width:auto;height:auto;background:rgba(0,0,0,0);font-size:12px;color:#000}.phone-input-group .phone-separator::before{content:"-"}.auth-input-group{width:100%}.auth-input-group .auth-input{width:100%;padding-right:60px}.auth-input-group .auth-timer{font-size:12px;right:12px}.auth-request-button,.auth-verify-button{width:100%;height:40px;padding:10px;font-size:14px;font-weight:700;border-radius:8px;margin-top:8px}.account-recovery-card .form-actions{flex-direction:row;gap:21px;margin-top:24px;padding:24px 0}.account-recovery-card .form-actions .cancel-button,.account-recovery-card .form-actions .submit-button{flex:1;min-width:144px;height:40px;font-size:14px;font-weight:700;border-radius:8px}.account-recovery-card .form-actions .cancel-button{background:#e5e7eb;color:#5f666c}.account-recovery-card .form-actions .submit-button{background:#0049b4;color:#fff}.account-recovery-result{padding:30px 20px;border-radius:0 0 8px 8px}.account-recovery-result .result-header{margin-bottom:24px}.account-recovery-result .result-header .result-icon{font-size:40px;margin-bottom:16px}.account-recovery-result .result-header .result-title{font-size:18px;margin-bottom:8px}.account-recovery-result .result-header .result-description{font-size:14px}.found-users-list{margin-bottom:24px}.found-users-list .found-user-item{padding:16px;border-radius:8px;margin-bottom:8px}.found-users-list .found-user-item .user-email{font-size:16px}.found-users-list .found-user-item .user-date{font-size:12px}.result-info-box{border-radius:8px;padding:12px 16px}.result-info-box .info-text{font-size:12px}}.signup-selection-page{min-height:calc(100vh - 140px);display:flex;align-items:center;justify-content:center;background:#edf9fe;padding:30px 20px;position:relative;border-radius:12px;margin-top:30px;margin-bottom:30px}.signup-selection-container{width:100%;max-width:1018px;margin:0 auto;position:relative}.signup-selection-card{background:rgba(0,0,0,0);padding:0;display:flex;flex-direction:column;align-items:center}@media(max-width: 576px){.signup-selection-card{padding:0 20px}}.signup-logo{width:90px;height:90px;margin-bottom:15px}.signup-logo img{width:100%;height:100%;object-fit:contain}.signup-message{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:20px;font-weight:400;color:#000;text-align:center;margin-bottom:20px;line-height:1}.signup-buttons{width:100%;max-width:504px;display:flex;flex-direction:column;gap:20px;margin-bottom:30px}.signup-btn{width:100%;height:80px;display:flex;align-items:center;justify-content:center;gap:10px;padding:10px;font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:20px;font-weight:700;color:#fff;text-decoration:none;border-radius:12px;cursor:pointer;transition:all .3s ease;line-height:1;position:relative}.signup-btn:hover{transform:translateY(-2px);box-shadow:0 4px 12px rgba(0,0,0,.15)}.signup-btn:active{transform:translateY(0);box-shadow:0 2px 6px rgba(0,0,0,.1)}.signup-btn .signup-btn-icon{width:26px;height:30px;flex-shrink:0}.signup-btn span{white-space:nowrap}.signup-btn-individual{background:#0049b4}.signup-btn-organization{background:#00a1d7}.signup-navigation{display:flex;justify-content:center;align-items:center;gap:26px;flex-wrap:wrap}.signup-navigation .signup-nav-link{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:16px;font-weight:400;color:#000;text-decoration:none;transition:color .3s ease;line-height:1}.signup-navigation .signup-nav-link:hover{color:#0049b4;text-decoration:underline}.signup-navigation .signup-nav-separator{color:#000;font-size:16px;line-height:1}@media(max-width: 768px){.signup-selection-page{background:#ebfbff;min-height:calc(100vh - 44px);padding:40px 20px;margin-top:0;margin-bottom:0;border-radius:0}.signup-selection-container{max-width:100%;padding:0}.signup-selection-card{padding:0}.signup-logo{width:64px;height:64px;margin-bottom:24px}.signup-message{font-size:14px;font-weight:400;color:#212529;margin-bottom:80px;line-height:26px}.signup-buttons{max-width:335px;gap:24px;margin-bottom:80px}.signup-btn{height:44px;font-size:16px;font-weight:700;border-radius:8px;padding:8px 12px;justify-content:center;gap:8px}.signup-btn svg{width:14px;height:16px;flex-shrink:0}.signup-btn span{text-align:center}.signup-btn:hover{transform:none;box-shadow:none;opacity:.9}.signup-navigation{gap:12px}.signup-navigation .signup-nav-link{font-size:14px;font-weight:400;color:#515961}.signup-navigation .signup-nav-separator{font-size:14px;color:#515961}}.corporate-transfer-section{margin-top:40px;padding-top:24px}.corporate-transfer-section .btn-block{width:100%}.register-title-bar{padding:18px 45px;display:flex;align-items:baseline;gap:16px}@media(max-width: 1024px){.register-title-bar{flex-direction:column;gap:8px;padding:14px 20px}}@media(max-width: 768px){.register-title-bar{flex-direction:row;flex-wrap:wrap;align-items:baseline;gap:8px;padding:0 20px 6px;background-color:rgba(0,0,0,0);border-radius:0;border-bottom:1px solid #212529;margin-bottom:20px}}.register-title-bar .register-title{font-size:28px;font-weight:700;color:#1a1a2e;margin:0}@media(max-width: 1024px){.register-title-bar .register-title{font-size:22px}}@media(max-width: 768px){.register-title-bar .register-title{font-size:16px;font-weight:500}}.register-title-bar .register-subtitle{font-size:16px;font-weight:400;color:#515151}@media(max-width: 1024px){.register-title-bar .register-subtitle{font-size:14px}}@media(max-width: 768px){.register-title-bar .register-subtitle{font-size:12px}}.register-progress{display:flex;justify-content:center;margin-bottom:24px}@media(max-width: 768px){.register-progress{margin-bottom:20px;padding:0}}.progress-steps{display:flex;align-items:flex-start;gap:30px}@media(max-width: 768px){.progress-steps{gap:12px;align-items:center}}.step-dots{display:flex;gap:6px}.step-dots span{width:6px;height:6px;border-radius:50%;background-color:#dadada}@media(max-width: 768px){.step-dots{display:flex;flex-direction:row;align-items:center;gap:4px;width:auto;height:auto}.step-dots span{width:4px;height:4px;border-radius:50%;background-color:#dadada}}.progress-step-item{display:flex;flex-direction:column;align-items:center;gap:8px}.progress-step-item .step-icon-wrapper{padding:10px}@media(max-width: 768px){.progress-step-item .step-icon-wrapper{padding:10px}}.progress-step-item .step-icon{width:50px;height:50px;border-radius:50%;background-color:#e6e6e6;display:flex;align-items:center;justify-content:center;color:#999}@media(max-width: 768px){.progress-step-item .step-icon{width:40px;height:40px}}.progress-step-item .step-icon .step-icon-img{width:28px;height:28px}@media(max-width: 768px){.progress-step-item .step-icon .step-icon-img{width:22px;height:22px}}.progress-step-item.active .step-icon{background-color:#0049b4;color:#fff}.progress-step-item .step-label{font-size:14px;font-weight:400;color:#dadada;text-align:center;white-space:nowrap}@media(max-width: 768px){.progress-step-item .step-label{font-size:10px}}.progress-step-item.active .step-label{font-weight:700;color:#0049b4}@media(max-width: 768px){.progress-step-item.active .step-label{font-size:10px}}.register-form-container{padding:30px 30px}@media(max-width: 1024px){.register-form-container{padding:20px 16px}}.register-form-container.with-sidebar{display:flex;gap:0;padding:28px;min-height:600px}@media(max-width: 768px){.register-form-container.with-sidebar{flex-direction:column;padding:0;background-color:#fff}}.register-form-container .form-input{width:100%;padding:8px 12px;border:1px solid #dadada;border-radius:8px;background-color:#fff;font-size:14px;color:#1a1a2e;outline:none;transition:border-color .2s ease}.register-form-container .form-input::placeholder{color:#dadada}.register-form-container .form-input:focus{border-color:#0049b4}@media(max-width: 1024px){.register-form-container .form-input{font-size:14px}}.register-form-container .form-textarea{width:100%;min-height:120px;padding:8px 12px;border:1px solid #dadada;border-radius:8px;background-color:#fff;font-size:14px;color:#1a1a2e;outline:none;resize:vertical;transition:border-color .2s ease}.register-form-container .form-textarea::placeholder{color:#dadada}.register-form-container .form-textarea:focus{border-color:#0049b4}@media(max-width: 1024px){.register-form-container .form-textarea{min-height:140px;font-size:16px}}.icon-upload-box{background-color:#fff;border:1px solid #dadada;border-radius:12px;padding:24px 20px;display:flex;flex-direction:column;align-items:center;gap:16px}@media(max-width: 1024px){.icon-upload-box{padding:20px 16px}}.icon-preview-area{width:80px;height:80px;position:relative}.icon-preview-area img{width:100%;height:100%;object-fit:cover;border-radius:8px}.icon-placeholder{width:100%;height:100%;display:flex;align-items:center;justify-content:center;border-radius:8px;color:#999}.icon-upload-box .btn-remove-icon{position:absolute;top:-8px;right:-8px;width:24px;height:24px;border-radius:50%;background-color:#dc3545;color:#fff;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background-color .2s ease}.icon-upload-box .btn-remove-icon:hover{background-color:#c82333}.icon-upload-info{text-align:center}.icon-upload-info .upload-title{font-size:14px;font-weight:600;color:#515961;margin:0 0 8px}.icon-upload-info .upload-hint{font-size:13px;font-weight:400;color:#515961;margin:0}.icon-upload-info .upload-hint strong{font-weight:600}.ip-input-row{display:flex;gap:21px}.ip-input-row .ip-input{flex:1}@media(max-width: 1024px){.ip-input-row{flex-direction:column;gap:12px}}@media(max-width: 768px){.ip-input-row{flex-direction:row;gap:10px}.ip-input-row .ip-input{flex:1;min-width:0;height:40px;font-size:14px;border-radius:8px;padding:0 12px}}.btn-add-ip{width:auto;height:40px;padding:0 16px;background-color:#3ba4ed;color:#fff;border:none;border-radius:8px;font-size:14px;font-weight:600;cursor:pointer;transition:background-color .2s ease;flex-shrink:0}.btn-add-ip:hover{background-color:#2b94dd}@media(max-width: 1024px){.btn-add-ip{width:auto;font-size:14px;height:38px}}@media(max-width: 768px){.btn-add-ip{width:auto;height:36px;font-size:13px;border-radius:8px;padding:0 12px}}.register-form-container .ip-list{margin-top:20px;border:none;background:rgba(0,0,0,0)}.register-form-container .ip-list .ip-items{display:flex;flex-direction:column;gap:10px;max-height:none}.register-form-container .ip-list .ip-item{display:flex;gap:21px;padding:0;border-bottom:none}.register-form-container .ip-list .ip-item:hover{background:rgba(0,0,0,0)}@media(max-width: 1024px){.register-form-container .ip-list .ip-item{flex-direction:column;gap:12px}}@media(max-width: 768px){.register-form-container .ip-list .ip-item{flex-direction:row;gap:10px}}.register-form-container .ip-list .ip-item .ip-display{flex:1;height:60px;padding:0 20px;border:1px solid #dadada;border-radius:8px;background-color:#fff;display:flex;align-items:center;font-size:20px;color:#1a1a2e}@media(max-width: 768px){.register-form-container .ip-list .ip-item .ip-display{height:40px;padding:0 12px;font-size:14px;border-radius:8px;min-width:0}}.register-form-container .ip-list .ip-item .btn-remove-ip{width:158px !important;height:60px !important;background-color:#e5e7eb !important;color:#5f666c !important;border:none !important;border-radius:12px !important;font-size:20px !important;font-weight:700;cursor:pointer;transition:background-color .2s ease;flex-shrink:0;display:flex !important;align-items:center;justify-content:center}@media(max-width: 1024px){.register-form-container .ip-list .ip-item .btn-remove-ip{width:100% !important;font-size:16px !important;height:50px !important}}@media(max-width: 768px){.register-form-container .ip-list .ip-item .btn-remove-ip{width:89px !important;height:40px !important;font-size:12px !important;border-radius:8px !important;padding:0 !important}}.apikey-register-container{padding:48px 0}@media(max-width: 768px){.apikey-register-container{padding:24px 16px}}.apikey-register-container.with-sidebar{display:flex;min-height:calc(100vh - 200px);position:relative;padding:0}.register-progress{margin-bottom:48px;padding:0 24px}@media(max-width: 768px){.register-progress{margin-bottom:32px;margin-top:8px}}.register-progress .progress-steps{display:flex;align-items:center;justify-content:center;position:relative}.register-progress .progress-step{display:flex;flex-direction:column;align-items:center;position:relative;z-index:2}.register-progress .progress-step .step-number{width:40px;height:40px;border-radius:50%;background:#fff;border:2px solid #e2e8f0;display:flex;align-items:center;justify-content:center;font-weight:600;color:#64748b;transition:all .3s ease;margin-bottom:8px}.register-progress .progress-step .step-label{font-size:14px;color:#64748b;white-space:nowrap}.register-progress .progress-step.active .step-number{background:#0049b4;border-color:#0049b4;color:#fff;box-shadow:0 0 0 4px rgba(0,73,180,.1)}.register-progress .progress-step.active .step-label{color:#0049b4;font-weight:500}.register-progress .progress-step.completed .step-number{background:#6bcf7f;border-color:#6bcf7f;color:#fff}.register-progress .progress-step.completed .step-label{color:#1a1a2e}.register-progress .progress-line{flex:1;height:2px;background:#e2e8f0;margin:0 16px;margin-bottom:28px;position:relative}.register-progress .progress-line.filled{background:#6bcf7f}.register-header{text-align:center;margin-bottom:48px;padding:0 24px}.register-header h1{font-size:32px;font-weight:700;color:#1a1a2e;margin-bottom:8px}.register-header h2{font-size:24px;font-weight:600;color:#0049b4;margin-bottom:16px}.register-header .header-description{font-size:16px;color:#64748b;max-width:600px;margin:0 auto}.apikey-register-sidebar{width:254px;background-color:#eceff4;border-radius:12px;padding:23px 12px;position:sticky;top:0;height:fit-content;max-height:calc(100vh - 100px);overflow-y:auto;flex-shrink:0}@media(max-width: 1024px){.apikey-register-sidebar{width:240px}}@media(max-width: 768px){.apikey-register-sidebar{position:fixed;left:0;top:60px;transform:translateX(-100%);transition:transform .3s ease;box-shadow:0 8px 24px rgba(75,155,255,.15);z-index:500;height:calc(100vh - 60px);max-height:none;margin:0;border-radius:0}.apikey-register-sidebar.mobile-open{transform:translateX(0)}}.apikey-sidebar-nav .sidebar-title{font-size:20px;font-weight:700;color:#212529;text-align:center;padding:20px 14px 45px}.apikey-sidebar-nav .menu-section{margin-bottom:0;border-bottom:1px solid #dadada}.apikey-sidebar-nav .menu-section:last-child{border-bottom:none}.apikey-sidebar-nav .menu-title{padding:22px 14px;font-size:20px;font-weight:400;color:#212529;cursor:pointer;transition:all .3s ease;display:flex;align-items:center;justify-content:space-between;gap:10px}.apikey-sidebar-nav .menu-title:hover{background-color:rgba(0,0,0,.02)}.apikey-sidebar-nav .menu-title.active{font-weight:700;color:#212529}.apikey-sidebar-nav .menu-title .menu-icon{width:20px;height:20px;flex-shrink:0}.apikey-sidebar-nav .menu-title .menu-icon img{width:100%;height:100%;object-fit:contain}.apikey-sidebar-nav .menu-title .menu-text{flex:1}.apikey-sidebar-nav .menu-title .api-count{width:8px;height:8px;padding:0;min-width:auto;background-color:#dadada;border-radius:50%;font-size:0;flex-shrink:0}.apikey-sidebar-nav .menu-title.active .api-count{background-color:#3ba4ed}.apikey-register-content{flex:1;padding:23px 30px;min-height:auto;display:flex;flex-direction:column}.apikey-register-content .api-result-count{font-size:16px;color:#64748b;margin-bottom:32px}.apikey-register-content .api-result-count strong{color:#0049b4;font-weight:600}@media(max-width: 768px){.apikey-register-content{padding:0}}.apikey-mobile-toggle{display:none;position:fixed;bottom:24px;right:24px;width:56px;height:56px;background-color:#0049b4;border-radius:50%;border:none;color:#fff;font-size:24px;cursor:pointer;z-index:501;transition:all .3s ease}.apikey-mobile-toggle:hover{transform:scale(1.05);box-shadow:0 16px 40px rgba(75,155,255,.2)}.apikey-mobile-toggle:active{transform:scale(0.95)}@media(max-width: 768px){.apikey-mobile-toggle{display:flex;align-items:center;justify-content:center}}.apikey-mobile-overlay{display:none;position:fixed;top:60px;left:0;right:0;bottom:0;background-color:rgba(0,0,0,.5);z-index:499;opacity:0;transition:opacity .3s ease;pointer-events:none}@media(max-width: 768px){.apikey-mobile-overlay{display:block}}.apikey-mobile-overlay.active{opacity:1;pointer-events:auto}.api-filter-header{display:flex;justify-content:flex-end;align-items:center;margin-bottom:32px;gap:16px}@media(max-width: 768px){.api-filter-header{justify-content:stretch}}.api-search-box{position:relative;width:228px;height:44px;flex-shrink:0;margin-left:auto}@media(max-width: 768px){.api-search-box{width:100%;margin-left:0}}.api-search-box .search-input{width:100%;height:100%;padding:0 44px 0 17px;border:1px solid #dadada;border-radius:12px;background-color:#fff;font-size:14px;font-family:inherit;outline:none;transition:all .3s ease}.api-search-box .search-input:focus{border-color:#0049b4;box-shadow:0 0 0 3px rgba(0,73,180,.1)}.api-search-box .search-input::placeholder{color:#8c959f}.api-search-box svg{position:absolute;right:17px;top:50%;transform:translateY(-50%);pointer-events:none}.selection-counter{font-size:16px;color:#64748b;white-space:nowrap}.selection-counter .counter-value{font-weight:600;color:#0049b4}.api-filter-header{display:flex;align-items:center;justify-content:space-between;gap:24px;margin-bottom:24px}@media(max-width: 768px){.api-filter-header{flex-direction:column;align-items:stretch;gap:16px}}.api-filter-header .select-all-wrapper{display:flex;align-items:center;flex:1}@media(max-width: 768px){.api-filter-header .select-all-wrapper{order:2}}.api-filter-header .select-all-label{display:flex;align-items:center;gap:8px;cursor:pointer;font-size:16px;color:#1a1a2e;user-select:none}.api-filter-header .select-all-label:hover{color:#0049b4}.api-filter-header .select-all-checkbox{width:18px;height:18px;cursor:pointer;accent-color:#0049b4}.api-filter-header .select-all-checkbox:indeterminate{opacity:.6}@media(max-width: 768px){.api-filter-header .api-search-box{order:1}}.api-selection-card-grid{display:grid;grid-template-columns:repeat(3, 1fr);gap:24px;margin-bottom:32px}@media(max-width: 1024px){.api-selection-card-grid{grid-template-columns:repeat(2, 1fr);gap:16px}}@media(max-width: 768px){.api-selection-card-grid{grid-template-columns:repeat(2, 1fr);gap:16px}}.custom-checkbox{display:inline-block;width:22px;height:22px;flex-shrink:0;cursor:pointer;background-image:url("/img/checkbox-unchecked.svg");background-size:contain;background-repeat:no-repeat;background-position:center;border-radius:50%}input[type=checkbox]:checked+.custom-checkbox{background-color:#0049b4;background-image:url("/img/checkbox-checked.svg")}.select-all-label .custom-checkbox{margin-right:8px}.api-selection-card{background:#fff;border:1px solid #dadada;border-radius:12px;transition:all .3s ease;cursor:pointer;position:relative}.api-selection-card:hover{border-color:#0049b4;box-shadow:0 4px 12px rgba(75,155,255,.1)}.api-selection-card.selected{border-color:#0049b4;background-color:rgba(0,73,180,.02)}.api-selection-card .api-card-content{display:flex;flex-direction:column;gap:14px;padding:26px 28px;cursor:pointer}.api-selection-card .api-card-content .api-card-header{display:flex;justify-content:space-between;align-items:center;gap:13px}.api-selection-card .api-card-content .api-card-header .api-card-category{font-size:14px;font-weight:400;color:#6e7780;flex:1}.api-selection-card .api-card-content .api-card-header .checkbox-wrapper{flex-shrink:0}.api-selection-card .api-card-content .api-card-header .checkbox-wrapper .api-checkbox{width:22px;height:22px;cursor:pointer;accent-color:#0049b4}.api-selection-card .api-card-content .api-name{font-size:18px;font-weight:700;color:#212529;margin:0;line-height:1}.api-selection-card .api-card-content .api-description{font-size:15px;font-weight:400;color:#515961;margin:0;line-height:1.2;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.api-selection-card .api-card-content .api-card-icon{display:flex;justify-content:flex-end;margin-top:auto}.api-selection-card .api-card-content .api-card-icon img{width:50px;height:50px;object-fit:contain}.api-selection-card .api-card-content .api-card-icon i{width:50px;height:50px;display:flex;align-items:center;justify-content:center;font-size:32px;color:#999}.selected-summary{background:#eff6ff;border-radius:12px;padding:24px;margin-bottom:32px}.selected-summary h3{font-size:18px;font-weight:600;color:#1a1a2e;margin-bottom:16px}.selected-summary .selected-list{display:flex;flex-wrap:wrap;gap:8px}.selected-summary .selected-list .selected-chip{display:inline-flex;align-items:center;gap:4px;padding:6px 12px;background:#fff;border:1px solid #0049b4;border-radius:50px;font-size:14px;color:#0049b4}.selected-summary .selected-list .selected-chip .remove-chip{background:none;border:none;color:#0049b4;font-size:18px;cursor:pointer;padding:0;margin-left:4px}.selected-summary .selected-list .selected-chip .remove-chip:hover{color:#ff6b6b}.empty-api-state{padding:48px}.loading-state{padding:48px;text-align:center;color:#64748b}.loading-state .loading-spinner{width:48px;height:48px;margin:0 auto 24px;border:4px solid #e2e8f0;border-top-color:#0049b4;border-radius:50%;animation:spin 1s linear infinite}.loading-state p{font-size:16px;color:#64748b}@keyframes spin{to{transform:rotate(360deg)}}.register-form{width:100%;max-width:1021px;margin:0 auto}.register-form .form-card{background:#fff;border-radius:12px;padding:40px;box-shadow:0 2px 4px rgba(0,0,0,.05);margin-bottom:32px}.register-form .form-group{margin-bottom:32px}.register-form .form-group:last-child{margin-bottom:0}.register-form .form-label{display:block;font-size:14px;font-weight:600;color:#1a1a2e;margin-bottom:8px}.register-form .form-label.required .required-mark{color:#ff6b6b;margin-left:4px}.register-form .form-input,.register-form .form-select,.register-form .form-textarea{width:100%;padding:12px 16px;border:1px solid #e2e8f0;border-radius:8px;font-size:16px;transition:all .3s ease;background:#fff}.register-form .form-input:focus,.register-form .form-select:focus,.register-form .form-textarea:focus{outline:none;border-color:#0049b4;box-shadow:0 0 0 3px rgba(0,73,180,.1)}.register-form .form-input::placeholder,.register-form .form-select::placeholder,.register-form .form-textarea::placeholder{color:#94a3b8}.register-form .form-input:disabled,.register-form .form-select:disabled,.register-form .form-textarea:disabled{background-color:#e2e8f0}.register-form .form-textarea{resize:vertical;min-height:100px;font-family:inherit}.register-form .form-hint{display:block;font-size:12px;color:#64748b;margin-top:4px}.register-form .form-hint .char-counter{float:right;color:#94a3b8}.register-form .radio-group{display:flex;gap:24px;flex-wrap:wrap}.register-form .radio-group .radio-label{display:flex;align-items:center;cursor:pointer}.register-form .radio-group .radio-label input[type=radio]{margin-right:8px}.register-form .radio-group .radio-label .radio-text{font-size:16px;color:#1a1a2e}.register-form .icon-upload-wrapper{display:flex;align-items:center;gap:24px}.register-form .icon-upload-wrapper .icon-preview{width:120px;height:120px;border:2px dashed #e2e8f0;border-radius:12px;display:flex;align-items:center;justify-content:center;background:#f8fafc;overflow:hidden;position:relative;transition:all .3s ease}.register-form .icon-upload-wrapper .icon-preview:hover{border-color:#0049b4;background:rgba(0,73,180,.05)}.register-form .icon-upload-wrapper .icon-preview .icon-placeholder{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;color:#64748b;text-align:center;padding:16px}.register-form .icon-upload-wrapper .icon-preview .icon-placeholder svg{opacity:.5}.register-form .icon-upload-wrapper .icon-preview .icon-placeholder span{font-size:12px;font-weight:500}.register-form .icon-upload-wrapper .icon-preview img{width:100%;height:100%;object-fit:cover}.register-form .icon-upload-wrapper .icon-preview .btn-remove-icon{position:absolute;top:8px;right:8px;width:28px;height:28px;padding:0;background:hsla(0,0%,100%,.95);border:1px solid #e2e8f0;border-radius:50%;color:#64748b;cursor:pointer;transition:all .3s ease;display:none;align-items:center;justify-content:center;box-shadow:0 2px 4px rgba(0,0,0,.05);z-index:10}.register-form .icon-upload-wrapper .icon-preview .btn-remove-icon svg{width:14px;height:14px}.register-form .icon-upload-wrapper .icon-preview .btn-remove-icon:hover{background:#ff6b6b;border-color:#ff6b6b;color:#fff;transform:scale(1.1);box-shadow:0 4px 12px rgba(75,155,255,.1)}.register-form .icon-upload-wrapper .icon-preview .btn-remove-icon:active{transform:scale(0.95)}.register-form .icon-upload-wrapper .icon-input{display:none}.register-form .icon-upload-wrapper .btn-icon-select{padding:10px 20px;background:#fff;border:1px solid #e2e8f0;border-radius:8px;font-size:14px;font-weight:500;color:#1a1a2e;cursor:pointer;transition:all .3s ease}.register-form .icon-upload-wrapper .btn-icon-select:hover{background:#0049b4;border-color:#0049b4;color:#fff}.register-form .icon-upload-wrapper .btn-icon-select:active{transform:scale(0.98)}.register-form .ip-input-group{display:flex;gap:8px;margin-bottom:16px}.register-form .ip-input-group .form-input{flex:1}.register-form .ip-input-group .btn-add-ip{display:inline-flex;align-items:center;gap:4px;padding:10px 16px;background:#0049b4;border:none;border-radius:8px;color:#fff;font-size:14px;font-weight:500;cursor:pointer;transition:all .3s ease;white-space:nowrap}.register-form .ip-input-group .btn-add-ip svg{flex-shrink:0}.register-form .ip-input-group .btn-add-ip:hover{background:#c3dfea;transform:translateY(-1px);box-shadow:0 4px 12px rgba(75,155,255,.1)}.register-form .ip-input-group .btn-add-ip:active{transform:translateY(0)}.register-form .ip-list{margin-top:16px;overflow:hidden}.register-form .ip-list .ip-list-header{padding:16px;background:#f8fafc;border-bottom:1px solid #e2e8f0}.register-form .ip-list .ip-list-header .ip-count{font-size:14px;color:#64748b}.register-form .ip-list .ip-list-header .ip-count strong{color:#0049b4;font-weight:600}.register-form .ip-list .ip-items{max-height:240px;overflow-y:auto}.register-form .ip-list .ip-items::-webkit-scrollbar{width:6px}.register-form .ip-list .ip-items::-webkit-scrollbar-track{background:#f8fafc}.register-form .ip-list .ip-items::-webkit-scrollbar-thumb{background:#e2e8f0;border-radius:3px}.register-form .ip-list .ip-items::-webkit-scrollbar-thumb:hover{background:#94a3b8}.register-form .ip-list .ip-item{display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid #e2e8f0;transition:all .3s ease}.register-form .ip-list .ip-item:last-child{border-bottom:none}.register-form .ip-list .ip-item:hover{background:#f8fafc}.register-form .ip-list .ip-item .ip-address{font-family:"Fira Code","Courier New",monospace;font-size:14px;color:#1a1a2e;font-weight:500}.register-form .ip-list .ip-item .btn-remove-ip{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;padding:0;background:#fff;border:1px solid #e2e8f0;border-radius:6px;color:#64748b;cursor:pointer;transition:all .3s ease}.register-form .ip-list .ip-item .btn-remove-ip svg{width:14px;height:14px}.register-form .ip-list .ip-item .btn-remove-ip:active{transform:scale(0.95)}.register-form-container .form-actions{justify-content:space-between;padding:0 24px 24px;margin-top:40px;border-top:none;padding-top:0}@media(max-width: 768px){.register-form-container .form-actions{padding:0 16px 16px}}@media(max-width: 768px){.apikey-register-container>.form-actions{display:flex;flex-direction:row !important;justify-content:center !important;align-items:center;gap:12px;padding:24px 20px;margin-top:0;background-color:rgba(0,0,0,0)}.apikey-register-container>.form-actions .btn-submit{width:144px !important;height:40px !important;min-height:40px;padding:8px 10px !important;font-size:14px !important;font-weight:700;border-radius:8px !important;flex-shrink:0}.apikey-register-container>.form-actions .btn-submit.btn-secondary{background-color:#e5e7eb !important;color:#5f666c !important;border:none !important}.apikey-register-container>.form-actions .btn-submit.btn-primary{background-color:#0049b4 !important;color:#fff !important;border:none !important}}.register-result{text-align:center;margin:0 auto;height:1045px;padding-top:262px}@media(max-width: 768px){.register-result{height:auto;min-height:calc(100vh - 200px);padding:0 0 40px;background-color:rgba(0,0,0,0);display:flex;flex-direction:column;align-items:center;justify-content:flex-start}}.register-result .result-box{display:block}@media(max-width: 768px){.register-result .result-box{background-color:#f6f9fb;border-radius:8px;padding:54px 20px 40px;width:100%;margin-bottom:24px}}.register-result.success .result-icon img{width:250px;height:250px}@media(max-width: 768px){.register-result.success .result-icon img{width:104px;height:104px}}.register-result.error .result-icon{background:rgba(255,107,107,.1);color:#ff6b6b}.register-result .result-icon-wrapper{margin-bottom:32px}@media(max-width: 768px){.register-result .result-icon-wrapper{margin-bottom:24px}}.register-result .result-icon-wrapper .result-icon{margin:0 auto;display:flex;align-items:center;justify-content:center;font-size:40px}.register-result .result-header{margin-bottom:40px}@media(max-width: 768px){.register-result .result-header{margin-bottom:0}}.register-result .result-header h1{font-size:24px;font-weight:700;color:#0049b4;margin-bottom:16px}@media(max-width: 768px){.register-result .result-header h1{font-size:16px;margin-bottom:12px}}.register-result .result-header .result-description{font-size:16px;color:#64748b;margin:0 auto}@media(max-width: 768px){.register-result .result-header .result-description{font-size:12px;line-height:1.5}.register-result .result-header .result-description .text-gray{color:#5f666c}.register-result .result-header .result-description .text-dark{color:#212529}}.key-info-card{background:#fff;border-radius:12px;padding:32px;box-shadow:0 4px 12px rgba(75,155,255,.1);margin-bottom:32px;text-align:left}.key-info-card .info-row{display:flex;align-items:center;justify-content:space-between;padding:16px 0;border-bottom:1px solid #e2e8f0}.key-info-card .info-row:last-child{border-bottom:none}.key-info-card .info-row .info-label{font-size:14px;color:#64748b;font-weight:500;min-width:120px}.key-info-card .info-row .info-value{font-size:16px;color:#1a1a2e;font-weight:500}.key-info-card .info-row .key-display{display:flex;align-items:center;gap:8px;flex:1;justify-content:flex-end}.key-info-card .info-row .key-display .key-value{font-family:"Fira Code","Courier New",monospace;font-size:14px;background:#f8fafc;padding:8px 12px;border-radius:6px;color:#c3dfea;margin-right:8px}.key-info-card .info-row .key-display .btn-copy,.key-info-card .info-row .key-display .btn-toggle-visibility{padding:6px 10px;background:#fff;border:1px solid #e2e8f0;border-radius:6px;cursor:pointer;transition:all .3s ease}.key-info-card .info-row .key-display .btn-copy:hover,.key-info-card .info-row .key-display .btn-toggle-visibility:hover{background:#0049b4;border-color:#0049b4;color:#fff}.key-info-card .info-row .status-badge.active{padding:4px 12px;background:rgba(107,207,127,.15);color:#4fa065;border-radius:50px;font-size:12px;font-weight:600}.selected-apis-summary{background:#f8fafc;border-radius:12px;padding:24px;margin-bottom:32px;text-align:left}.selected-apis-summary h3{font-size:18px;font-weight:600;color:#1a1a2e;margin-bottom:16px}.selected-apis-summary .api-chips{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:16px}.selected-apis-summary .api-chips .api-chip{padding:6px 12px;background:#fff;border:1px solid #0049b4;border-radius:50px;font-size:14px;color:#0049b4}.selected-apis-summary .api-note{font-size:12px;color:#64748b;font-style:italic}.next-steps-card{background:#eff6ff;border-radius:12px;padding:32px;margin-bottom:32px;text-align:left}.next-steps-card h3{font-size:18px;font-weight:600;color:#1a1a2e;margin-bottom:16px}.next-steps-card .steps-list{margin-left:24px;color:#64748b;font-size:14px;line-height:1.8}.next-steps-card .steps-list li{margin-bottom:8px}.result-actions{display:flex;justify-content:center;gap:16px;flex-wrap:wrap}@media(max-width: 768px){.result-actions{flex-direction:row;align-items:center;justify-content:center;gap:12px;padding:0;width:100%}}.result-actions .btn-primary,.result-actions .btn-secondary{display:inline-flex;align-items:center;gap:8px;padding:12px 24px;border-radius:8px;font-size:16px;font-weight:500;transition:all .3s ease;text-decoration:none;cursor:pointer;border:none}.result-actions .btn-primary .icon,.result-actions .btn-secondary .icon{font-size:18px}@media(max-width: 768px){.result-actions .btn-primary,.result-actions .btn-secondary{width:144px;height:40px;min-height:40px;padding:8px 10px;border-radius:8px;font-size:14px;font-weight:700;justify-content:center}}.result-actions .btn-primary{background-color:#0049b4;color:#fff;box-shadow:0 4px 12px rgba(75,155,255,.1)}.result-actions .btn-primary:hover{transform:translateY(-2px);box-shadow:0 8px 24px rgba(75,155,255,.15)}@media(max-width: 768px){.result-actions .btn-primary{box-shadow:none}.result-actions .btn-primary:hover{transform:none;box-shadow:none}}.result-actions .btn-secondary{background:#fff;color:#1a1a2e;border:1px solid #e2e8f0}.result-actions .btn-secondary:hover{background:#f8fafc;border-color:#0049b4;color:#0049b4}@media(max-width: 768px){.result-actions .btn-secondary{background-color:#e5e7eb;color:#5f666c;border:none}.result-actions .btn-secondary:hover{background-color:#d1d5db;color:#5f666c}}.floating-cart-btn{position:fixed;bottom:80px;right:40px;width:180px;height:66px;padding:10px 16px 10px 36px;border-radius:60px;background-color:#3ba4ed;border:none;color:#fff;display:flex;align-items:center;justify-content:space-between;cursor:pointer;box-shadow:0 16px 40px rgba(75,155,255,.2);transition:all .3s ease;z-index:300}.floating-cart-btn .cart-label{font-size:14px;font-weight:700;color:#fff;white-space:nowrap}.floating-cart-btn .cart-badge{display:flex;flex-direction:row;align-items:center;justify-content:center;background:#fff;width:57px;height:57px;border-radius:120px;flex-shrink:0;margin-right:-8px}.floating-cart-btn .cart-badge .cart-count{font-size:24px;font-weight:700;color:#212529;line-height:1}.floating-cart-btn .cart-badge .cart-unit{font-size:14px;font-weight:700;color:#212529;line-height:1;margin-top:2px}.floating-cart-btn:hover{transform:translateY(-4px);box-shadow:0 12px 32px rgba(59,164,237,.4)}.floating-cart-btn:active{transform:translateY(-2px)}@media(max-width: 768px){.floating-cart-btn{bottom:100px;right:24px;width:160px;height:56px;padding:8px 12px 8px 28px}.floating-cart-btn .cart-label{font-size:12px}.floating-cart-btn .cart-badge{width:48px;height:48px;margin-right:-6px}.floating-cart-btn .cart-badge .cart-count{font-size:20px}.floating-cart-btn .cart-badge .cart-unit{font-size:12px}}.modal-dialog .selected-apis-list{display:flex;flex-wrap:wrap;gap:8px;align-items:flex-start}.modal-dialog .api-pill{display:inline-flex;align-items:center;justify-content:center;gap:8px;padding:10px 16px;background:#f2f2f2;border:none;border-radius:50px;transition:all .2s ease;max-width:100%;height:47px}.modal-dialog .api-pill:hover{background:#e8e8e8}.modal-dialog .api-pill .api-pill-name{font-size:16px;font-weight:400;color:#000;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:280px;text-align:center}.modal-dialog .api-pill .api-pill-remove{flex-shrink:0;width:20px;height:20px;padding:0;background:rgba(0,0,0,0);border:none;border-radius:50%;color:#212529;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s ease;overflow:hidden}.modal-dialog .api-pill .api-pill-remove svg{width:12px;height:12px}.modal-dialog .api-pill .api-pill-remove:hover{background:rgba(0,0,0,.1);transform:scale(1.1)}.modal-dialog .api-pill .api-pill-remove:active{transform:scale(0.95)}@media(max-width: 768px){.modal-dialog .api-pill{padding:8px 14px;height:42px}.modal-dialog .api-pill .api-pill-name{font-size:14px;max-width:200px}.modal-dialog .api-pill .api-pill-remove{width:18px;height:18px}.modal-dialog .api-pill .api-pill-remove svg{width:10px;height:10px}}.apikey-detail-container{padding:40px 0}@media(max-width: 768px){.apikey-detail-container{padding:24px 16px}}.register-header{text-align:center;margin-bottom:80px}.register-header h1{font-size:48px;font-weight:700;color:#1a1a2e;margin-bottom:8px}.register-header .header-description{font-size:16px;color:#64748b}.status-section{text-align:center;margin-bottom:64px}.status-badge.badge-pending{background-color:#fff3cd;color:#856404}.status-badge.badge-approved{background-color:#d4edda;color:#155724}.status-badge.badge-rejected{background-color:#f8d7da;color:#721c24}.section-title{font-size:24px;font-weight:700;color:#1a1a2e;margin-bottom:24px;padding-bottom:16px}.detail-label{font-size:14px;font-weight:600;color:#64748b}.detail-value{font-size:16px;color:#1a1a2e;word-break:break-word}.status-dot{width:8px;height:8px;border-radius:50%;background-color:currentColor}.api-list-item .api-name{flex:1;font-size:16px;font-weight:500;color:#1a1a2e;margin:0}.api-method{display:inline-block;padding:4px 16px;border-radius:6px;font-size:14px;font-weight:700;font-family:"Fira Code","Courier New",monospace;min-width:80px;text-align:center}.api-method.method-get{background-color:#d1fae5;color:#065f46}.api-method.method-post{background-color:#dbeafe;color:#1e40af}.api-method.method-put{background-color:#fef3c7;color:#92400e}.api-method.method-delete{background-color:#fee2e2;color:#991b1b}.api-method.method-other{background-color:#e2e8f0;color:#1a1a2e}.api-status{padding:4px 16px;border-radius:12px;font-size:14px;font-weight:600}.api-status.status-pending{background-color:#fff3cd;color:#856404}.api-status.status-active{background-color:#d4edda;color:#155724}.history-table-wrapper{overflow-x:auto}.history-table{width:100%;border-collapse:collapse}.history-table th{background-color:#f8fafc;padding:16px 16px;text-align:left;font-weight:600;color:#64748b;border-bottom:2px solid #e2e8f0}.history-table td{padding:16px;border-bottom:1px solid #e2e8f0}.history-status{display:inline-block;padding:4px 16px;background-color:#eff6ff;color:#c3dfea;border-radius:12px;font-size:14px;font-weight:600}.empty-state{grid-column:1/-1;padding:48px 24px}@media(max-width: 768px){.btn-action,.btn-secondary,.btn-cancel{width:100%}}.app-info-card{display:flex;align-items:flex-start;gap:20px;padding:0;padding-top:20px;margin-bottom:30px}@media(max-width: 768px){.app-info-card{flex-direction:column;align-items:center;text-align:center;padding:16px 0;gap:16px}}.app-info-icon{flex-shrink:0;width:100px;height:100px;border-radius:12px;overflow:hidden;background:#f8fafc;display:flex;align-items:center;justify-content:center;margin-left:40px}@media(max-width: 768px){.app-info-icon{width:80px;height:80px;margin-left:0}}.app-info-icon .app-icon-image{width:100%;height:100%;object-fit:cover}.app-info-icon .app-icon-placeholder{color:#94a3b8;display:flex;align-items:center;justify-content:center}.app-info-content{flex:1;display:flex;flex-direction:column;gap:8px}.app-status-badge{display:inline-block;padding:3px 12px;border-radius:50px;font-size:12px;font-weight:600;width:fit-content}.app-status-badge.status-approved{background-color:#0049b4;color:#fff}.app-status-badge.status-pending{background-color:#fff3cd;color:#856404}.app-status-badge.status-inactive{background-color:#e2e8f0;color:#64748b}.app-status-badge.status-rejected{background-color:#f8d7da;color:#721c24}.app-info-name{font-size:18px;font-weight:700;color:#1a1a2e;margin:0}@media(max-width: 768px){.app-info-name{font-size:16px}}.app-info-description{font-size:14px;color:#515151;margin:0;line-height:1.5}@media(max-width: 768px){.app-info-description{font-size:13px}}.apikey-info-section{background:#f6f9fb;border-radius:12px;padding:24px 30px;margin-bottom:30px}@media(max-width: 768px){.apikey-info-section{padding:20px}}.info-row{display:flex;align-items:center;gap:20px;margin-bottom:16px}.info-row:last-child{margin-bottom:0}@media(max-width: 768px){.info-row{flex-direction:column;align-items:flex-start;gap:8px;margin-bottom:12px}}.info-row-vertical{align-items:flex-start}.info-row-vertical .info-value{width:100%}@media(max-width: 768px){.info-row-vertical .info-value{max-width:100%}}.info-label{flex-shrink:0;width:140px;font-size:15px;font-weight:600;color:#212529}@media(max-width: 768px){.info-label{width:100%;font-size:14px}}.info-value{flex:1;font-size:14px;color:#1a1a2e;word-break:break-word}@media(max-width: 768px){.info-value{width:100%}}.info-value-box{display:flex;align-items:center;justify-content:center;height:40px;padding:0 16px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;font-size:14px;color:#1a1a2e;text-align:center}@media(max-width: 768px){.info-value-box{height:36px;font-size:14px;padding:0 12px}}.info-value-box.with-copy{flex:1}.info-row-with-action .info-value{display:flex;gap:12px;align-items:center}.btn-copy-action{display:flex;align-items:center;justify-content:center;gap:4px;height:40px;width:auto;padding:0 16px;background:#a4d6ea;border:none;border-radius:8px;font-size:14px;font-weight:600;color:#fff;cursor:pointer;white-space:nowrap;transition:background .2s ease}.btn-copy-action:hover{background:rgb(131.6625,199.4303571429,226.5375)}@media(max-width: 768px){.btn-copy-action{height:36px;font-size:13px;padding:0 12px}}.btn-view-secret{display:flex;align-items:center;justify-content:center;gap:8px;width:100%;height:60px;background:#3ba4ed;border:none;border-radius:12px;font-size:20px;font-weight:700;color:#fff;cursor:pointer;transition:background .2s ease}.btn-view-secret:hover{background:rgb(31.8897196262,151.4130841121,234.5102803738)}.btn-view-secret svg{width:20px;height:20px}@media(max-width: 768px){.btn-view-secret{max-width:100%;height:50px;font-size:16px}}.api-list-box{width:100%;background:#fff;border:1px solid #dadada;border-radius:12px;padding:24px}@media(max-width: 768px){.api-list-box{max-width:100%;padding:16px}}.api-list-item{display:flex;align-items:center;justify-content:space-between;padding:16px 0;border-bottom:1px solid #dadada}.api-list-item:last-child{border-bottom:none}.api-list-item .api-item-name{font-size:18px;color:#000}@media(max-width: 768px){.api-list-item .api-item-name{font-size:16px}}.api-list-item .api-item-status{display:flex;align-items:center;justify-content:center;min-width:100px;height:30px;padding:0 24px;background:#eceff4;border-radius:50px;font-size:16px;color:#515151}@media(max-width: 768px){.api-list-item .api-item-status{min-width:80px;font-size:14px;padding:0 16px}}@media(max-width: 768px){.apikey-detail-container{padding:0 !important;max-width:100%}.register-title-bar{padding:20px;margin-bottom:0;border-bottom:1px solid #dadada}.register-title-bar .register-title{font-size:16px;font-weight:700;margin-bottom:4px}.register-title-bar .register-subtitle{font-size:12px;color:#666}.app-info-card{flex-direction:row !important;align-items:flex-start !important;text-align:left !important;padding:20px;gap:16px;margin-bottom:0;background:#fff}.app-info-card .app-info-icon{width:56px !important;height:56px !important;min-width:56px;margin-left:0;border-radius:8px}.app-info-card .app-info-icon .app-icon-image{width:100%;height:100%;object-fit:cover;border-radius:8px}.app-info-card .app-info-icon .app-icon-placeholder svg{width:32px;height:32px}.app-info-card .app-info-content{gap:4px}.app-info-card .app-status-badge{padding:4px 12px;font-size:12px;border-radius:20px}.app-info-card .app-status-badge.status-approved{background-color:#0049b4;color:#fff}.app-info-card .app-info-name{font-size:16px !important;font-weight:700}.app-info-card .app-info-description{font-size:12px !important;color:#666;line-height:1.4}.apikey-info-section{background:#f6f9fb;border-radius:8px;padding:20px;margin:0px}.info-row{flex-direction:column;align-items:flex-start;gap:8px;margin-bottom:16px}.info-row:last-child{margin-bottom:0}.info-label{font-size:14px !important;font-weight:700;color:#212529;width:100%}.info-value{width:100%}.info-value-box{height:40px !important;font-size:14px !important;background:#f2f2f2;border:1px solid #dadada;border-radius:8px;padding:0 12px}.info-value-box.with-copy{flex:1}.info-row-with-action .info-value{display:flex;flex-direction:row;gap:8px;align-items:center}.btn-copy-action{width:89px !important;min-width:89px;height:40px !important;font-size:14px !important;font-weight:700;padding:0 12px;background:#a4d6ea;border-radius:8px}.btn-copy-action:hover{background:rgb(131.6625,199.4303571429,226.5375)}.btn-view-secret{width:100% !important;height:40px !important;font-size:14px !important;font-weight:700;background:#3ba4ed;border-radius:8px;gap:8px}.btn-view-secret svg{width:16px;height:16px}.btn-view-secret:hover{background:rgb(31.8897196262,151.4130841121,234.5102803738)}#revealedSecretBox{width:100%}#revealedSecretBox .info-row-with-action .info-value{flex-direction:row;gap:8px}.api-list-box{padding:12px;border-radius:8px}.api-list-item{padding:12px 0}.api-list-item .api-item-name{font-size:14px !important;color:#000}.api-list-item .api-item-status{min-width:auto;height:24px !important;padding:0 12px;font-size:12px !important;background:#eceff4;border-radius:20px;color:#515151}.info-row-vertical .info-value{width:100%;max-width:100%}.form-actions-center{display:flex;flex-direction:row !important;justify-content:center !important;align-items:center;gap:12px;padding:24px 20px;margin-top:0;background-color:rgba(0,0,0,0)}.form-actions-center .btn-submit,.form-actions-center .btn{width:144px !important;height:40px !important;min-height:40px;padding:8px 10px !important;font-size:14px !important;font-weight:700;border-radius:8px !important;flex-shrink:0}.form-actions-center .btn-submit.btn-secondary,.form-actions-center .btn.btn-secondary{background-color:#e5e7eb !important;color:#5f666c !important;border:none !important}.form-actions-center .btn-submit.btn-primary,.form-actions-center .btn.btn-primary{background-color:#0049b4 !important;color:#fff !important;border:none !important}.form-actions-center .btn-submit.btn-danger,.form-actions-center .btn.btn-danger{display:none}}@media(max-width: 768px){.app-management-container{padding:24px 16px}}@media(max-width: 768px){.app-list-title-section{background-color:rgba(0,0,0,0);border-radius:0;padding:0 0 6px 0;margin-bottom:0;border-bottom:2px solid #1a1a2e}}.app-list-title{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:28px;font-weight:700;color:#1a1a2e;margin:0}@media(max-width: 768px){.app-list-title{font-size:16px;font-weight:500}}.app-list-container{min-height:400px}@media(max-width: 768px){.app-list-container{background:rgba(0,0,0,0);box-shadow:none;border:none;padding:0;min-height:auto}}.app-list-item{display:flex;align-items:center;gap:24px;padding:40px 0;border-bottom:1px solid #e0e0e0;text-decoration:none;color:inherit;transition:background-color .2s ease}.app-list-item:last-child{border-bottom:none}.app-list-item:hover{background-color:hsla(0,0%,100%,.5);margin:0 -20px;padding-left:20px;padding-right:20px;border-radius:8px}@media(max-width: 768px){.app-list-item{flex-direction:row;align-items:center;padding:14px 0;gap:16px;min-height:85px}.app-list-item:hover{margin:0;padding-left:0;padding-right:0;background-color:rgba(0,0,0,0)}.app-list-item:last-child{border-bottom:none}}.app-list-icon{width:70px;height:70px;flex-shrink:0}@media(max-width: 768px){.app-list-icon{width:56px;height:56px}}.app-list-icon .app-icon-image{border:none}.app-list-icon .app-icon-placeholder{font-size:inherit}.app-list-info{display:flex;flex-direction:column;gap:14px;flex:1;min-width:0}@media(max-width: 768px){.app-list-info{gap:8px}}.app-list-header{display:flex;align-items:center;gap:12px}@media(max-width: 768px){.app-list-header{flex-wrap:nowrap;gap:8px}}@media(max-width: 768px){.app-status-badge{display:flex;align-items:center;justify-content:center;width:70px;height:24px;padding:0 10px;border-radius:12px;font-size:12px;font-weight:700;text-align:center;white-space:nowrap;flex-shrink:0}}.app-list-name{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:18px;font-weight:700;color:#1a1a2e;margin:0}@media(max-width: 768px){.app-list-name{font-size:16px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}}.app-list-description{font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:14px;font-weight:400;color:#6e7780;margin:0;line-height:1.5}@media(max-width: 768px){.app-list-description{font-size:12px;color:#1a1a2e;line-height:1}}.app-create-button-wrapper{display:flex;justify-content:center;margin-top:60px}@media(max-width: 768px){.app-create-button-wrapper{margin-top:30px}}.btn-app-create{background-color:#0049b4;color:#fff}.btn-app-create:hover{background-color:#003a91}@media(max-width: 768px){.btn-app-create{width:144px;height:40px;padding:8px 10px;font-size:14px;border-radius:8px}}.notice-page{min-height:calc(100vh - 140px);background:#f8fafc;padding:48px 24px}@media(max-width: 768px){.notice-page{padding:40px 16px}}.notice-container{max-width:1280px;margin:0 auto}.notice-header{margin-bottom:48px}@media(max-width: 768px){.notice-header{margin-bottom:40px}}.notice-header .page-title{font-size:40px;font-weight:700;color:#1a1a2e;margin-bottom:16px;-webkit-background-clip:text;-webkit-text-fill-color:rgba(0,0,0,0);background-clip:text}@media(max-width: 768px){.notice-header .page-title{font-size:32px}}.notice-header .page-description{font-size:16px;color:#64748b;line-height:1.6}@media(max-width: 768px){.notice-header .page-description{font-size:14px}}.notice-list-wrapper{background:#fff;border-radius:12px;box-shadow:0 4px 12px rgba(75,155,255,.1);overflow:hidden;margin-bottom:32px}.notice-detail-container{margin:0 auto;padding:48px 0;min-height:600px}@media(max-width: 1024px){.notice-detail-container{padding:40px 16px}}.notice-detail-header{display:flex;justify-content:space-between;align-items:center;padding-bottom:16px;border-bottom:1px solid #212529;margin-bottom:32px}@media(max-width: 1024px){.notice-detail-header{flex-direction:column;align-items:flex-start;gap:8px}}.notice-detail-title{font-size:24px;font-weight:700;color:#212529;line-height:1;margin:0}@media(max-width: 1024px){.notice-detail-title{font-size:20px}}.notice-detail-date{font-size:24px;font-weight:400;color:#515151}@media(max-width: 1024px){.notice-detail-date{font-size:16px}}.notice-detail-attachment{margin-bottom:24px}.notice-detail-attachment .attachment-list{display:flex;flex-direction:column;gap:8px}.notice-detail-attachment .attachment-item{display:flex;align-items:center}.notice-attachment-link{display:inline-flex;align-items:center;gap:4px;color:#515151;font-size:18px;text-decoration:none;transition:all .3s ease}@media(max-width: 1024px){.notice-attachment-link{font-size:14px}}.notice-attachment-link:hover{color:#0049b4}.notice-attachment-link svg{flex-shrink:0;color:#515151}.notice-attachment-link:hover svg{color:#0049b4}.notice-attachment-link .attachment-label{margin-right:16px}.notice-attachment-link .attachment-filename{color:#515151}.notice-detail-content{margin-bottom:48px}.notice-content-body{background:#f6f9fb;border-radius:20px;padding:40px;min-height:280px;font-size:16px;color:#1a1a2e;line-height:1.8}@media(max-width: 1024px){.notice-content-body{padding:24px;min-height:200px;border-radius:12px}}.notice-content-body p{margin-bottom:16px}.notice-content-body p:last-child{margin-bottom:0}.notice-content-body img{max-width:100%;height:auto;border-radius:8px;margin:24px 0}.notice-content-body a{color:#0049b4;text-decoration:underline}.notice-content-body a:hover{color:#c3dfea}.notice-content-body ul,.notice-content-body ol{margin:16px 0;padding-left:32px}.notice-content-body li{margin-bottom:8px}@media(max-width: 768px){.notice-content-body{background:#fff;padding:0}}.notice-detail-actions{display:flex;justify-content:center}.btn-notice-list{display:flex;align-items:center;justify-content:center;width:200px;height:60px;background:#e5e7eb;border:none;border-radius:12px;font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:18px;font-weight:700;color:#5f666c;cursor:pointer;transition:all .3s ease}@media(max-width: 1024px){.btn-notice-list{width:auto;min-width:80px;padding:0 16px;height:44px;font-size:15px;border-radius:8px}}.btn-notice-list:hover{background:hsl(220,13.0434782609%,86.431372549%)}.btn-notice-list:active{transform:scale(0.98)}.notice-detail-card{background:#fff;border-radius:12px;box-shadow:0 4px 12px rgba(75,155,255,.1);overflow:hidden;margin-bottom:32px}.notice-detail-card .detail-header{padding:48px 40px 32px;border-bottom:2px solid #e2e8f0}@media(max-width: 768px){.notice-detail-card .detail-header{padding:40px 24px 16px}}.notice-detail-card .detail-header .notice-title{font-size:32px;font-weight:700;color:#1a1a2e;line-height:1.2;margin-bottom:24px}@media(max-width: 768px){.notice-detail-card .detail-header .notice-title{font-size:24px}}.notice-detail-card .detail-header .notice-meta{display:flex;align-items:center;gap:24px;flex-wrap:wrap;font-size:14px;color:#64748b}@media(max-width: 768px){.notice-detail-card .detail-header .notice-meta{font-size:12px;gap:16px}}.notice-detail-card .detail-header .notice-meta .meta-item{display:flex;align-items:center;gap:4px}.notice-detail-card .detail-header .notice-meta .meta-item i{color:#0049b4}.notice-detail-card .detail-header .notice-meta .meta-item strong{color:#1a1a2e;font-weight:500}.notice-detail-card .detail-attachments{padding:24px 40px;background:#f8fafc;border-bottom:1px solid #e2e8f0}@media(max-width: 768px){.notice-detail-card .detail-attachments{padding:16px 24px}}.notice-detail-card .detail-attachments .attachment-list{display:flex;flex-direction:column;gap:8px}.notice-detail-card .detail-attachments .attachment-list .attachment-item{display:flex;align-items:center;gap:8px}.notice-detail-card .detail-attachments .attachment-list .attachment-item a{color:#1a1a2e;text-decoration:none;font-size:14px;transition:all .3s ease;display:flex;align-items:center;gap:4px}@media(max-width: 768px){.notice-detail-card .detail-attachments .attachment-list .attachment-item a{font-size:12px}}.notice-detail-card .detail-attachments .attachment-list .attachment-item a:hover{color:#0049b4}.notice-detail-card .detail-attachments .attachment-list .attachment-item a:hover i{transform:translateY(-2px)}.notice-detail-card .detail-attachments .attachment-list .attachment-item a i{color:#0049b4;transition:all .3s ease}.notice-detail-card .detail-content{padding:48px 40px;min-height:300px}@media(max-width: 768px){.notice-detail-card .detail-content{padding:40px 24px}}.notice-detail-card .detail-content .content-body{font-size:16px;color:#1a1a2e;line-height:1.8}@media(max-width: 768px){.notice-detail-card .detail-content .content-body{font-size:14px}}.notice-detail-card .detail-content .content-body p{margin-bottom:16px}.notice-detail-card .detail-content .content-body p:last-child{margin-bottom:0}.notice-detail-card .detail-content .content-body h1,.notice-detail-card .detail-content .content-body h2,.notice-detail-card .detail-content .content-body h3,.notice-detail-card .detail-content .content-body h4,.notice-detail-card .detail-content .content-body h5,.notice-detail-card .detail-content .content-body h6{margin-top:32px;margin-bottom:16px;font-weight:600;color:#1a1a2e}.notice-detail-card .detail-content .content-body img{max-width:100%;height:auto;border-radius:8px;margin:24px 0}.notice-detail-card .detail-content .content-body a{color:#0049b4;text-decoration:underline}.notice-detail-card .detail-content .content-body a:hover{color:#c3dfea}.notice-detail-card .detail-content .content-body ul,.notice-detail-card .detail-content .content-body ol{margin:16px 0;padding-left:32px}.notice-detail-card .detail-content .content-body li{margin-bottom:8px}.notice-detail-card .detail-content .content-body blockquote{border-left:4px solid #0049b4;padding-left:24px;margin:24px 0;color:#64748b;font-style:italic}.notice-detail-card .detail-content .content-body code{background:#f8fafc;padding:2px 6px;border-radius:6px;font-family:"Fira Code","Courier New",monospace;font-size:.9em;color:#0049b4}.notice-detail-card .detail-content .content-body pre{background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;padding:16px;overflow-x:auto;margin:24px 0}.notice-detail-card .detail-content .content-body pre code{background:rgba(0,0,0,0);padding:0;color:#1a1a2e}.notice-detail-card .detail-content .content-body table{width:100%;border-collapse:collapse;margin:24px 0;border:1px solid #e2e8f0;border-radius:8px;overflow:hidden}.notice-detail-card .detail-content .content-body table th{background:#eff6ff;padding:8px 16px;text-align:left;font-weight:600;border-bottom:2px solid #e2e8f0}.notice-detail-card .detail-content .content-body table td{padding:8px 16px;border-bottom:1px solid #e2e8f0}.notice-detail-card .detail-content .content-body table tr:last-child td{border-bottom:none}.notice-actions{display:flex;justify-content:center;gap:16px;margin-bottom:48px}@media(max-width: 768px){.notice-actions{flex-direction:column;align-items:stretch}}.notice-navigation{background:#fff;border-radius:12px;box-shadow:0 2px 4px rgba(0,0,0,.05);overflow:hidden}.notice-navigation .nav-item{display:flex;align-items:center;justify-content:space-between;padding:24px 40px;border-bottom:1px solid #e2e8f0;transition:all .3s ease;cursor:pointer}@media(max-width: 768px){.notice-navigation .nav-item{flex-direction:column;align-items:flex-start;padding:16px 24px;gap:8px}}.notice-navigation .nav-item:last-child{border-bottom:none}.notice-navigation .nav-item:hover{background:#f8fafc}.notice-navigation .nav-item:hover .nav-title{color:#0049b4}.notice-navigation .nav-item .nav-label{display:flex;align-items:center;gap:8px;font-size:14px;font-weight:500;color:#64748b;min-width:80px}@media(max-width: 768px){.notice-navigation .nav-item .nav-label{font-size:12px}}.notice-navigation .nav-item .nav-label i{color:#0049b4}.notice-navigation .nav-item .nav-title{flex:1;font-size:16px;color:#1a1a2e;transition:all .3s ease}@media(max-width: 768px){.notice-navigation .nav-item .nav-title{font-size:14px}}.notice-navigation .nav-item .nav-date{font-size:14px;color:#94a3b8;min-width:100px;text-align:right}@media(max-width: 768px){.notice-navigation .nav-item .nav-date{font-size:12px;text-align:left}}.inquiry-detail-container{max-width:1228px;margin:0 auto;padding:48px 24px}@media(max-width: 1024px){.inquiry-detail-container{padding:40px 16px}}.inquiry-detail-header{padding-bottom:16px;border-bottom:1px solid #212529;margin-bottom:32px}.inquiry-header-top{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px}@media(max-width: 1024px){.inquiry-header-top{flex-direction:column;align-items:flex-start;gap:8px}}.inquiry-detail-title{font-size:24px;font-weight:700;color:#212529;line-height:1.4;margin:0}@media(max-width: 1024px){.inquiry-detail-title{font-size:20px}}.inquiry-detail-date{font-size:18px;font-weight:400;color:#515151}@media(max-width: 1024px){.inquiry-detail-date{font-size:16px}}.inquiry-detail-attachment{margin-bottom:24px}.inquiry-detail-attachment .attachment-list{display:flex;flex-direction:column;gap:8px}.inquiry-detail-attachment .attachment-item{display:flex;align-items:center}.inquiry-attachment-link{display:inline-flex;align-items:center;gap:4px;color:#515151;font-size:18px;text-decoration:none;transition:all .3s ease}@media(max-width: 1024px){.inquiry-attachment-link{font-size:14px}}.inquiry-attachment-link:hover{color:#0049b4}.inquiry-attachment-link svg{flex-shrink:0;color:#515151}.inquiry-attachment-link:hover svg{color:#0049b4}.inquiry-attachment-link .attachment-label{margin-right:16px}.inquiry-attachment-link .attachment-filename{color:#515151}.inquiry-detail-content{margin-bottom:40px}.inquiry-content-body{background:#f6f9fb;border-radius:20px;padding:40px;min-height:200px;font-size:16px;color:#1a1a2e;line-height:1.8;white-space:pre-line}@media(max-width: 1024px){.inquiry-content-body{padding:24px;min-height:150px;border-radius:12px}}@media(max-width: 768px){.inquiry-content-body{background:rgba(0,0,0,0);border-radius:0;padding:0;min-height:auto}}.inquiry-content-body p{margin-bottom:16px}.inquiry-content-body p:last-child{margin-bottom:0}.inquiry-content-body img{max-width:100%;height:auto;border-radius:8px;margin:24px 0}.inquiry-content-body a{color:#0049b4;text-decoration:underline}.inquiry-content-body a:hover{color:#c3dfea}.inquiry-response-section{margin-bottom:40px}.inquiry-response-header{display:flex;justify-content:space-between;align-items:center;padding-bottom:16px;border-bottom:1px solid #dadada;margin-bottom:24px}@media(max-width: 1024px){.inquiry-response-header{flex-direction:column;align-items:flex-start;gap:8px}}.response-label{font-size:20px;font-weight:700;color:#0049b4}@media(max-width: 1024px){.response-label{font-size:18px}}.response-date{font-size:18px;font-weight:400;color:#515151}@media(max-width: 1024px){.response-date{font-size:16px}}.inquiry-response-body{background:#fff;border:1px solid #e5e7eb;border-radius:20px;padding:40px;min-height:150px;font-size:16px;color:#1a1a2e;line-height:1.8;white-space:pre-line}@media(max-width: 1024px){.inquiry-response-body{padding:24px;min-height:100px;border-radius:12px}}.inquiry-detail-actions{display:flex;justify-content:space-between;align-items:center;gap:16px}@media(max-width: 1024px){.inquiry-detail-actions{flex-direction:row;justify-content:center;gap:8px}}.inquiry-detail-actions .actions-left{display:flex;gap:16px}@media(max-width: 1024px){.inquiry-detail-actions .actions-left{gap:8px}}.inquiry-detail-actions .actions-right{display:flex;gap:16px}@media(max-width: 1024px){.inquiry-detail-actions .actions-right{gap:8px}}.btn-inquiry-list{display:flex;align-items:center;justify-content:center;width:200px;height:60px;background:#e5e7eb;border:none;border-radius:12px;font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:18px;font-weight:700;color:#5f666c;cursor:pointer;transition:all .3s ease}@media(max-width: 1024px){.btn-inquiry-list{width:auto;min-width:80px;padding:0 16px;height:44px;font-size:15px;border-radius:8px}}.btn-inquiry-list:hover{background:hsl(220,13.0434782609%,86.431372549%)}.btn-inquiry-list:active{transform:scale(0.98)}.btn-inquiry-edit{display:flex;align-items:center;justify-content:center;width:200px;height:60px;background:#0049b4;border:none;border-radius:12px;font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:18px;font-weight:700;color:#fff;cursor:pointer;transition:all .3s ease}@media(max-width: 1024px){.btn-inquiry-edit{width:auto;min-width:80px;padding:0 16px;height:44px;font-size:15px;border-radius:8px}}.btn-inquiry-edit:hover{background:rgb(0,69.35,171)}.btn-inquiry-edit:active{transform:scale(0.98)}.btn-inquiry-delete{display:flex;align-items:center;justify-content:center;width:200px;height:60px;background:#dc3545;border:none;border-radius:12px;font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:18px;font-weight:700;color:#fff;cursor:pointer;transition:all .3s ease}@media(max-width: 1024px){.btn-inquiry-delete{width:auto;min-width:80px;padding:0 16px;height:44px;font-size:15px;border-radius:8px}}.btn-inquiry-delete:hover{background:rgb(217.9841772152,41.3658227848,58.2873417722)}.btn-inquiry-delete:active{transform:scale(0.98)}.inquiry-form-container{max-width:1228px;margin:0 auto;padding:48px 24px}@media(max-width: 1024px){.inquiry-form-container{padding:40px 16px}}.file-upload-inline{display:flex;align-items:center;gap:20px}@media(max-width: 1024px){.file-upload-inline{flex-direction:column;align-items:stretch;gap:16px}}.file-upload-inline .file-input-display{flex:1;display:flex;align-items:center;justify-content:space-between;height:60px;padding:0 20px;background:#fff;border:1px solid #dadada;border-radius:12px;min-width:0}.file-upload-inline .file-input-display.has-file{border-color:#3ba4ed}.file-upload-inline .file-display-text{flex:1;font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:16px;color:#94a3b8;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.file-upload-inline .file-display-text.has-file{color:#1a1a2e}.file-upload-inline .btn-remove-file-inline{display:flex;align-items:center;justify-content:center;width:24px;height:24px;background:#dc3545;border:none;border-radius:50%;color:#fff;cursor:pointer;transition:all .3s ease;flex-shrink:0;margin-left:8px}.file-upload-inline .btn-remove-file-inline:hover{background:rgb(209.4151898734,36.2848101266,52.8721518987)}.file-upload-inline .btn-remove-file-inline svg{width:12px;height:12px}.file-upload-inline .btn-file-attach{display:inline-flex;align-items:center;justify-content:center;gap:10px;width:172px;height:60px;padding:10px;background:#3ba4ed;border:none;border-radius:12px;font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:18px;font-weight:700;color:#fff;cursor:pointer;transition:all .3s ease;white-space:nowrap;flex-shrink:0}@media(max-width: 1024px){.file-upload-inline .btn-file-attach{width:100%}}.file-upload-inline .btn-file-attach:hover{background:rgb(37.3117757009,153.9304672897,235.0082242991)}.file-upload-inline .btn-file-attach svg{width:22px;height:22px;flex-shrink:0}.file-upload-hint{margin-top:8px;font-size:14px;color:#94a3b8;line-height:1.6}.file-upload-hint strong{color:#1a1a2e}@media(max-width: 768px){.inquiry-form-container{padding:24px 16px}.inquiry-form-container .register-title-bar{background:rgba(0,0,0,0);padding:16px 0;margin-bottom:0}.inquiry-form-container .register-form-container{box-shadow:none !important;border:none !important;background:rgba(0,0,0,0) !important;padding:0 !important}.inquiry-form-container .form-row{padding:16px 0}.inquiry-form-container .form-actions{display:flex;flex-direction:row !important;justify-content:center !important;align-items:center !important;gap:8px !important;padding:32px 0 0;margin-top:16px}.inquiry-form-container .form-actions .btn{width:auto !important;min-width:80px;padding:0 16px;height:44px;font-size:15px;border-radius:8px;flex:0 0 auto}.inquiry-form-container .form-actions .btn-secondary{background:#e5e7eb;color:#5f666c;border:none}.inquiry-form-container .form-actions .btn-secondary:hover{background:hsl(220,13.0434782609%,86.431372549%)}.inquiry-form-container .form-actions .btn-primary{background:#0049b4;color:#fff;border:none}.inquiry-form-container .form-actions .btn-primary:hover{background:rgb(0,69.35,171)}.inquiry-form-container .file-upload-inline .file-input-display{min-height:50px;padding:0 16px}.inquiry-form-container .file-upload-inline .btn-file-attach{height:50px;font-size:15px}}.inquiry-page{min-height:calc(100vh - 140px);background:#f8fafc;padding:48px 24px}@media(max-width: 768px){.inquiry-page{padding:40px 16px}}.inquiry-container{max-width:1280px;margin:0 auto}.inquiry-header{margin-bottom:48px}@media(max-width: 768px){.inquiry-header{margin-bottom:40px}}.inquiry-header .page-title{font-size:40px;font-weight:700;color:#1a1a2e;margin-bottom:16px;-webkit-background-clip:text;-webkit-text-fill-color:rgba(0,0,0,0);background-clip:text}@media(max-width: 768px){.inquiry-header .page-title{font-size:32px}}.inquiry-header .page-description{font-size:16px;color:#64748b;line-height:1.6}@media(max-width: 768px){.inquiry-header .page-description{font-size:14px}}.inquiry-list-wrapper{background:#fff;border-radius:12px;box-shadow:0 4px 12px rgba(75,155,255,.1);overflow:hidden;margin-bottom:32px}.inquiry-table{width:100%;background:#fff;border-radius:12px;overflow:hidden}.inquiry-table .table-header{display:grid;grid-template-columns:80px 1fr 100px 100px 120px;gap:16px;padding:16px 24px;background:#eff6ff;border-bottom:2px solid #e2e8f0;font-size:14px;font-weight:600;color:#1a1a2e}@media(max-width: 1024px){.inquiry-table .table-header{grid-template-columns:60px 1fr 80px 100px;padding:8px 16px;font-size:12px}.inquiry-table .table-header .col-name{display:none}}@media(max-width: 768px){.inquiry-table .table-header{display:none}}.inquiry-table .table-header .col-num{text-align:center}.inquiry-table .table-header .col-title{text-align:left}.inquiry-table .table-header .col-name{text-align:center}.inquiry-table .table-header .col-status{text-align:center}.inquiry-table .table-header .col-date{text-align:center}.inquiry-table .table-row{display:grid;grid-template-columns:80px 1fr 100px 100px 120px;gap:16px;padding:24px;border-bottom:1px solid #e2e8f0;transition:all .3s ease;cursor:pointer}@media(max-width: 1024px){.inquiry-table .table-row{grid-template-columns:60px 1fr 80px 100px;padding:16px}.inquiry-table .table-row .col-name{display:none}}@media(max-width: 768px){.inquiry-table .table-row{grid-template-columns:1fr;gap:8px;padding:16px}}.inquiry-table .table-row:last-child{border-bottom:none}.inquiry-table .table-row:hover{background-color:rgba(0,73,180,.02)}.inquiry-table .table-row .col-num{display:flex;align-items:center;justify-content:center;font-size:14px;color:#64748b;font-weight:500}@media(max-width: 768px){.inquiry-table .table-row .col-num{display:none}}.inquiry-table .table-row .col-title{display:flex;align-items:center;font-size:16px;color:#1a1a2e;font-weight:500}@media(max-width: 768px){.inquiry-table .table-row .col-title{font-size:14px}}.inquiry-table .table-row .col-title a{color:inherit;text-decoration:none;display:flex;align-items:center;gap:8px;transition:all .3s ease}.inquiry-table .table-row .col-title a:hover{color:#0049b4}.inquiry-table .table-row .col-title .file-icon{display:inline-flex;align-items:center;margin-left:8px;opacity:.6}.inquiry-table .table-row .col-title .file-icon img{width:16px;height:16px}.inquiry-table .table-row .col-name{display:flex;align-items:center;justify-content:center;font-size:14px;color:#64748b}@media(max-width: 1024px){.inquiry-table .table-row .col-name{display:none}}.inquiry-table .table-row .col-status{display:flex;align-items:center;justify-content:center}@media(max-width: 768px){.inquiry-table .table-row .col-status{justify-content:flex-start}}.inquiry-table .table-row .col-date{display:flex;align-items:center;justify-content:center;font-size:14px;color:#94a3b8}@media(max-width: 768px){.inquiry-table .table-row .col-date{justify-content:flex-start;font-size:12px;padding-top:4px}}@media(max-width: 768px){.inquiry-table .table-row .col-date::before{content:"등록일: ";color:#64748b;margin-right:4px}}.inquiry-actions{display:flex;justify-content:center;align-items:center;gap:16px;margin-top:32px}@media(max-width: 768px){.inquiry-actions{flex-direction:column;align-items:stretch}}.inquiry-actions .actions-left,.inquiry-actions .actions-right{display:flex;align-items:center;gap:16px}@media(max-width: 768px){.inquiry-actions .actions-left,.inquiry-actions .actions-right{width:100%;justify-content:center}}@media(max-width: 768px){.inquiry-actions{flex-direction:column;align-items:stretch;gap:16px}.inquiry-actions .actions-left,.inquiry-actions .actions-right{flex-direction:column}}.inquiry-detail-page{min-height:calc(100vh - 140px);background:#f8fafc;padding:48px 24px}@media(max-width: 768px){.inquiry-detail-page{padding:40px 16px}}.inquiry-detail-container{min-height:600px;margin:0 auto}.inquiry-detail-card{background:#fff;border-radius:12px;box-shadow:0 4px 12px rgba(75,155,255,.1);overflow:hidden;margin-bottom:32px}.inquiry-detail-card .detail-header{padding:48px 40px 32px;border-bottom:2px solid #e2e8f0}@media(max-width: 768px){.inquiry-detail-card .detail-header{padding:40px 24px 16px}}.inquiry-detail-card .detail-header .inquiry-title{font-size:32px;font-weight:700;color:#1a1a2e;line-height:1.2;margin-bottom:24px}@media(max-width: 768px){.inquiry-detail-card .detail-header .inquiry-title{font-size:24px}}.inquiry-detail-card .detail-header .inquiry-meta{display:flex;align-items:center;gap:24px;flex-wrap:wrap;font-size:14px;color:#64748b}@media(max-width: 768px){.inquiry-detail-card .detail-header .inquiry-meta{font-size:12px;gap:16px}}.inquiry-detail-card .detail-header .inquiry-meta .meta-item{display:flex;align-items:center;gap:4px}.inquiry-detail-card .detail-header .inquiry-meta .meta-item i{color:#0049b4}.inquiry-detail-card .detail-header .inquiry-meta .meta-item strong{color:#1a1a2e;font-weight:500}.inquiry-detail-card .detail-attachments{padding:24px 40px;background:#f8fafc;border-bottom:1px solid #e2e8f0}@media(max-width: 768px){.inquiry-detail-card .detail-attachments{padding:16px 24px}}.inquiry-detail-card .detail-attachments .attachment-list{display:flex;flex-direction:column;gap:8px}.inquiry-detail-card .detail-attachments .attachment-list .attachment-item{display:flex;align-items:center;gap:8px}.inquiry-detail-card .detail-attachments .attachment-list .attachment-item a{color:#1a1a2e;text-decoration:none;font-size:14px;transition:all .3s ease;display:flex;align-items:center;gap:4px}@media(max-width: 768px){.inquiry-detail-card .detail-attachments .attachment-list .attachment-item a{font-size:12px}}.inquiry-detail-card .detail-attachments .attachment-list .attachment-item a:hover{color:#0049b4}.inquiry-detail-card .detail-attachments .attachment-list .attachment-item a:hover i{transform:translateY(-2px)}.inquiry-detail-card .detail-attachments .attachment-list .attachment-item a i{color:#0049b4;transition:all .3s ease}.inquiry-detail-card .detail-content{padding:48px 40px;min-height:200px}@media(max-width: 768px){.inquiry-detail-card .detail-content{padding:40px 24px}}.inquiry-detail-card .detail-content .content-body{font-size:16px;color:#1a1a2e;line-height:1.8;white-space:pre-line}@media(max-width: 768px){.inquiry-detail-card .detail-content .content-body{font-size:14px}}.inquiry-response-card{background:#fff;border-radius:12px;box-shadow:0 4px 12px rgba(75,155,255,.1);overflow:hidden;margin-bottom:32px;border-left:4px solid #6bcf7f}.inquiry-response-card .response-header{padding:32px 40px;background:rgba(107,207,127,.03);border-bottom:1px solid #e2e8f0}@media(max-width: 768px){.inquiry-response-card .response-header{padding:24px}}.inquiry-response-card .response-header .response-title{display:flex;align-items:center;gap:8px;font-size:20px;font-weight:600;color:#1a1a2e;margin-bottom:8px}.inquiry-response-card .response-header .response-title i{color:#6bcf7f}.inquiry-response-card .response-header .response-meta{font-size:14px;color:#64748b}@media(max-width: 768px){.inquiry-response-card .response-header .response-meta{font-size:12px}}.inquiry-response-card .response-content{padding:40px;font-size:16px;color:#1a1a2e;line-height:1.8;white-space:pre-line}@media(max-width: 768px){.inquiry-response-card .response-content{padding:24px;font-size:14px}}.inquiry-detail-actions{display:flex;justify-content:center;gap:16px;margin-bottom:48px;flex-wrap:wrap}@media(max-width: 768px){.inquiry-detail-actions{flex-direction:row;align-items:stretch}}.inquiry-detail-actions .action-btn{padding:16px 40px;border-radius:8px;font-size:16px;font-weight:500;text-decoration:none;transition:all .3s ease;display:inline-flex;align-items:center;justify-content:center;gap:8px;cursor:pointer;border:none}@media(max-width: 768px){.inquiry-detail-actions .action-btn{padding:16px 24px;font-size:14px}}.inquiry-detail-actions .action-btn.btn-list{background:#fff;color:#1a1a2e;border:1px solid #e2e8f0}.inquiry-detail-actions .action-btn.btn-list:hover{background:#f8fafc;border-color:#0049b4}.inquiry-detail-actions .action-btn.btn-edit{color:#fff}.inquiry-detail-actions .action-btn.btn-edit:hover{transform:translateY(-2px);box-shadow:0 4px 12px rgba(75,155,255,.1)}.inquiry-detail-actions .action-btn.btn-delete{background:#ff6b6b;color:#fff}.inquiry-detail-actions .action-btn.btn-delete:hover{transform:translateY(-2px);box-shadow:0 4px 12px rgba(75,155,255,.1)}.inquiry-detail-actions .action-btn i{font-size:16px}.inquiry-form-page{min-height:calc(100vh - 140px);background:#f8fafc;padding:48px 24px}@media(max-width: 768px){.inquiry-form-page{padding:40px 16px}}.inquiry-form-container{margin:0 auto}.inquiry-form-card{background:#fff;border-radius:12px;box-shadow:0 4px 12px rgba(75,155,255,.1);padding:48px;margin-bottom:32px}@media(max-width: 768px){.inquiry-form-card{padding:40px 24px}}.inquiry-form-card .form-group{margin-bottom:40px}.inquiry-form-card .form-group:last-child{margin-bottom:0}.inquiry-form-card .form-group .form-label{display:block;font-size:16px;font-weight:600;color:#1a1a2e;margin-bottom:16px}@media(max-width: 768px){.inquiry-form-card .form-group .form-label{font-size:14px}}.inquiry-form-card .form-group .form-label.required::after{content:"*";color:#ff6b6b;margin-left:4px}.inquiry-form-card .form-group .form-input{width:100%;padding:16px;border:1px solid #e2e8f0;border-radius:8px;font-size:16px;transition:all .3s ease}@media(max-width: 768px){.inquiry-form-card .form-group .form-input{font-size:14px;padding:8px 16px}}.inquiry-form-card .form-group .form-input:focus{outline:none;border-color:#0049b4;box-shadow:0 0 0 3px rgba(0,73,180,.1)}.inquiry-form-card .form-group .form-input::placeholder{color:#94a3b8}.inquiry-form-card .form-group .form-textarea{width:100%;min-height:200px;padding:16px;border:1px solid #e2e8f0;border-radius:8px;font-size:16px;font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;resize:vertical;transition:all .3s ease}@media(max-width: 768px){.inquiry-form-card .form-group .form-textarea{font-size:14px;padding:8px 16px;min-height:150px}}.inquiry-form-card .form-group .form-textarea:focus{outline:none;border-color:#0049b4;box-shadow:0 0 0 3px rgba(0,73,180,.1)}.inquiry-form-card .form-group .form-textarea::placeholder{color:#94a3b8}.inquiry-form-actions{display:flex;justify-content:center;gap:16px;margin-top:32px}@media(max-width: 768px){.inquiry-form-actions{flex-direction:column-reverse;align-items:stretch}}.org-register-page{min-height:100vh;padding:0}@media(max-width: 768px){.org-register-page{padding:0}}.org-register-page.corporate-register .org-register-container{padding:0}@media(max-width: 768px){.org-register-page.corporate-register .org-register-container{padding:0 16px}}@media(max-width: 768px){.org-register-container{padding:24px 16px}}.org-register-title{font-size:40px;font-weight:700;color:#1a1a2e;text-align:center;margin-bottom:48px}@media(max-width: 768px){.org-register-title{font-size:32px;margin-bottom:32px}}.org-section-header{display:flex;align-items:center;justify-content:space-between;padding:24px 32px;background:#edf9fe;border-radius:8px;margin-bottom:32px}.org-section-header h3{font-size:16px;font-weight:400;color:#515961;margin:0}.org-section-header .required-badge{background:hsla(0,0%,100%,.2);color:#fff;padding:4px 16px;border-radius:50px;font-size:14px;font-weight:500;display:inline-flex;align-items:center;gap:4px}.org-section-header .required-badge::before{content:"*";color:#ffd93d;font-weight:700}.org-section-header--agreement{background:none;padding:0 0 16px 0;border-radius:0;border-bottom:2px solid #212529;margin-bottom:32px}.org-section-header--agreement h3{font-size:22px;font-weight:700;color:#212529;margin:0}.org-section-header--agreement .required-badge{display:none}@media(max-width: 768px){.org-section-header{padding:16px 24px}.org-section-header h3{font-size:18px}.org-section-header--agreement{padding:0 0 8px 0}.org-section-header--agreement h3{font-size:18px}}.org-info-notice{border-radius:8px;margin-bottom:16px}.org-info-notice ul{list-style:none;padding:0;margin:0}.org-info-notice ul li{position:relative;padding-left:24px;color:#64748b;font-size:14px;line-height:1.6}.org-info-notice ul li::before{content:"•";position:absolute;left:0;color:#0049b4;font-weight:700}.org-info-notice ul li+li{margin-top:8px}.org-form-group{margin-bottom:16px;display:flex;align-items:flex-start;gap:20px}.org-form-group:last-child{margin-bottom:0}@media(max-width: 768px){.org-form-group{flex-direction:column;gap:10px;margin-bottom:12px}}.org-form-label{display:flex;align-items:center;gap:8px;font-size:15px;font-weight:400;color:#1a1a2e;min-width:160px;padding-top:10px}.org-form-label .required-badge{display:inline-flex;align-items:center;justify-content:center;padding:2px 8px;background:#3ba4ed;color:#fff;font-size:12px;font-weight:500;border-radius:4px;line-height:1.2}@media(max-width: 768px){.org-form-label{min-width:auto;padding-top:0}}.org-form-input-wrapper{flex:1;display:flex;flex-direction:column;gap:8px}@media(max-width: 768px){.org-form-input-wrapper{width:100%}}.org-input-row{display:flex;align-items:center;gap:16px}.org-input-row .org-form-input,.org-input-row .org-compound-input,.org-input-row .org-auth-input-group{flex:1;min-width:0}.org-input-row .org-btn-check,.org-input-row .org-file-label{flex-shrink:0}@media(max-width: 768px){.org-input-row{flex-direction:column;align-items:stretch;gap:8px}.org-input-row .org-form-input,.org-input-row .org-compound-input,.org-input-row .org-auth-input-group{width:100%}.org-input-row .org-btn-check,.org-input-row .org-file-label{width:100%}}.org-form-input{width:100%;padding:8px 12px;border:1px solid #e2e8f0;border-radius:8px;font-size:14px;color:#1a1a2e;transition:all .3s ease;background:#fff}.org-form-input:focus{outline:none;border-color:#0049b4;box-shadow:0 0 0 3px rgba(75,155,255,.1)}.org-form-input:disabled{background:#f8fafc;color:#94a3b8;cursor:not-allowed}.org-form-input.is-invalid{border-color:#ff6b6b}.org-form-input.is-valid{border-color:#6bcf7f}.org-form-input::placeholder{color:#94a3b8}.org-compound-input{display:flex;align-items:center;gap:8px;width:100%}.org-compound-input .org-form-input,.org-compound-input .org-form-select{flex:1;min-width:0}.org-compound-input .org-form-input.phone-number{flex:none;width:130px;max-width:130px}.org-compound-input .org-form-select.phone-number{flex:none;width:130px;max-width:130px;padding-right:28px;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%2364748B' d='M6 9L1 4h10z'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 8px center}.org-compound-input .separator{color:#64748b;font-weight:600;user-select:none;flex-shrink:0}@media(max-width: 768px){.org-compound-input{width:100%}.org-compound-input .org-form-input,.org-compound-input .org-form-select{flex:1;min-width:0}}.org-form-select{width:100%;padding:8px 12px;border:1px solid #e2e8f0;border-radius:8px;font-size:14px;color:#1a1a2e;background:#fff;cursor:pointer;transition:all .3s ease;appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%2364748B' d='M6 9L1 4h10z'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 24px center;padding-right:48px}.org-form-select:focus{outline:none;border-color:#0049b4;box-shadow:0 0 0 3px rgba(75,155,255,.1)}.org-form-select:disabled{background-color:#f8fafc;color:#94a3b8;cursor:not-allowed}.org-input-button-group{display:flex;gap:16px}.org-input-button-group .org-form-input{flex:1}@media(max-width: 768px){.org-input-button-group{flex-direction:column}.org-input-button-group .org-form-input{flex:none}}.org-auth-input-group{position:relative;display:flex;align-items:center}.org-auth-input-group .org-form-input{padding-right:100px}.org-timer{position:absolute;right:24px;top:50%;transform:translateY(-50%);font-size:14px;font-weight:600;color:#ff6b6b;pointer-events:none;user-select:none}.org-btn{padding:16px 40px;border-radius:8px;font-size:16px;font-weight:600;cursor:pointer;transition:all .3s ease;border:none;display:inline-flex;align-items:center;justify-content:center;gap:8px;white-space:nowrap}.org-btn:disabled{opacity:.5;cursor:not-allowed}.org-btn:not(:disabled):hover{transform:translateY(-1px);box-shadow:0 4px 12px rgba(75,155,255,.1)}.org-btn:not(:disabled):active{transform:translateY(0)}.org-btn-primary{color:#fff}.org-btn-secondary{background:#fff;color:#64748b;border:2px solid #e2e8f0}.org-btn-secondary:not(:disabled):hover{border-color:#0049b4;color:#0049b4}.org-btn-check,.org-file-label{padding:8px 16px;width:auto;min-width:100px;background:#a4d6ea;color:#fff;border:1px solid #a4d6ea;border-radius:8px;font-size:14px;text-align:center;justify-content:center}.org-btn-check:not(:disabled):hover,.org-file-label:not(:disabled):hover{color:#fff}@media(max-width: 768px){.org-btn-check,.org-file-label{width:100%}}.org-file-upload{display:flex;align-items:center;gap:16px}@media(max-width: 768px){.org-file-upload{flex-direction:column;align-items:stretch}}.org-file-display{flex:1;padding:8px 12px;border:1px dashed #e2e8f0;border-radius:8px;background:#f8fafc;color:#94a3b8;font-size:14px;cursor:default;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.org-file-display.has-file{border-style:solid;border-color:#6bcf7f;background:rgba(107,207,127,.05);color:#1a1a2e}.org-file-label{display:inline-flex;align-items:center;gap:6px;cursor:pointer;background:#3ba4ed;border:1px solid #3ba4ed}.org-file-label svg{width:16px;height:16px;flex-shrink:0}.org-file-label input[type=file]{display:none}.org-file-remove{padding:8px 12px;background:#ff6b6b;color:#fff;border:none;border-radius:8px;font-size:14px;cursor:pointer;transition:all .3s ease}.org-file-remove:hover{background:rgb(255,70.8,70.8)}.org-file-notice{margin-top:16px;padding:16px;border-radius:6px}.org-file-notice p{margin:0;font-size:14px;color:#64748b;line-height:1.5}.org-file-notice p+p{margin-top:4px}.org-file-notice p::before{content:"• ";color:#ffd93d;font-weight:700}.org-action-buttons{display:flex;gap:24px;justify-content:center;margin-top:64px;padding-top:48px;border-top:1px solid #e2e8f0}.org-action-buttons .btn{width:220px;text-align:center;justify-content:center}@media(max-width: 768px){.org-action-buttons{flex-direction:row;gap:21px;margin-top:40px;padding-top:32px;border-top:none}.org-action-buttons .btn{flex:1;width:auto;min-width:144px;height:40px;padding:8px 10px;font-size:14px;font-weight:700;border-radius:8px}.org-action-buttons .btn-secondary,.org-action-buttons .btn-outline-secondary{background:#e5e7eb;color:#5f666c;border:none}.org-action-buttons .btn-secondary:hover,.org-action-buttons .btn-outline-secondary:hover{background:#d1d5db;color:#5f666c}.org-action-buttons .btn-primary{background:#0049b4;color:#fff;border:none}.org-action-buttons .btn-primary:hover{background:#003d99;color:#fff}}.org-validation-message{margin-top:8px;padding:8px 16px;border-radius:6px;font-size:14px}.org-validation-message.success{background:rgba(107,207,127,.1);color:#6bcf7f}.org-validation-message.error{background:rgba(255,107,107,.1);color:#ff6b6b}.org-loading-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(26,26,46,.7);display:none;align-items:center;justify-content:center;z-index:9999}.org-loading-overlay.active{display:flex}.org-loading-overlay .spinner{width:50px;height:50px;border:4px solid hsla(0,0%,100%,.2);border-top-color:#fff;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}@media(max-width: 768px){.org-form-input{width:100%}.org-compound-input{width:100%}.org-compound-input .org-form-select,.org-compound-input .org-form-input{flex:1;min-width:0;width:auto;font-size:14px;padding:8px 16px}.org-auth-input-group{width:100%}.org-auth-input-group .org-form-input{width:100%}}.agreement-form{background:#fff;border:1px solid #ddd;border-radius:12px;padding:30px 20px;display:flex;flex-direction:column;gap:20px;margin-bottom:90px}.agreement-all-section{display:flex;align-items:center;padding:10px 0}.agreement-divider{height:1px;background:#ddd;width:100%}.agreement-items-list{display:flex;flex-direction:column;gap:20px}.agreement-item-row{display:flex;align-items:center;justify-content:space-between;gap:4px;height:28px}.agreement-checkbox-label{display:flex;align-items:center;gap:4px;cursor:pointer;flex:1;user-select:none}.agreement-checkbox-input{position:absolute;opacity:0;pointer-events:none}.agreement-checkbox-input:checked+.agreement-checkbox-custom{background-image:url("/img/checkbox-checked.png")}.agreement-checkbox-input:focus+.agreement-checkbox-custom{box-shadow:0 0 0 3px rgba(140,149,159,.1)}.agreement-checkbox-custom{position:relative;width:26px;height:26px;min-width:26px;border:none;border-radius:50%;background-image:url("/img/checkbox-unchecked.png");background-size:contain;background-repeat:no-repeat;background-position:center;transition:all .3s ease;padding:0}.agreement-checkbox-text{font-size:16px;font-weight:400;color:#515961;line-height:100.02%;display:flex;align-items:center;gap:4px}.agreement-checkbox-text.agreement-all-text{font-size:18px;font-weight:700;color:#212529}.agreement-required{color:#0049b4;font-weight:700}.agreement-title{color:#515961}.agreement-toggle-icon{display:flex;align-items:center;justify-content:center;width:22px;height:22px;min-width:22px;border:none;background:rgba(0,0,0,0);color:#64748b;cursor:pointer;transition:all .3s ease;padding:8px 4px 10px 7px}.agreement-toggle-icon i{font-size:12px;transition:transform .3s ease}.agreement-toggle-icon:hover{color:#0049b4}.agreement-toggle-icon.active{color:#0049b4}.agreement-toggle-icon.active i{transform:rotate(180deg)}.agreement-content{margin-top:16px;background:#edf9fe}.agreement-scroll{max-height:300px;overflow-y:auto;padding:24px}.agreement-scroll::-webkit-scrollbar{width:8px}.agreement-scroll::-webkit-scrollbar-track{background:#e2e8f0;border-radius:6px}.agreement-scroll::-webkit-scrollbar-thumb{background:#94a3b8;border-radius:6px}.agreement-scroll::-webkit-scrollbar-thumb:hover{background:#64748b}.agreement-scroll div{font-size:14px;line-height:1.8;color:#64748b}.agreement-scroll div h1,.agreement-scroll div h2,.agreement-scroll div h3,.agreement-scroll div h4,.agreement-scroll div h5,.agreement-scroll div h6{color:#1a1a2e;margin-top:24px;margin-bottom:16px;font-weight:600}.agreement-scroll div p{margin-bottom:16px}.agreement-scroll div ul,.agreement-scroll div ol{margin-left:24px;margin-bottom:16px}.agreement-scroll div table{width:100%;border-collapse:collapse;margin-bottom:16px}.agreement-scroll div table th,.agreement-scroll div table td{border:1px solid #e2e8f0;padding:8px;text-align:left}.agreement-scroll div table th{background:#eff6ff;font-weight:600}@media(max-width: 768px){.agreement-form{padding:20px 16px;gap:16px}.agreement-all-section{padding:8px 0}.agreement-items-list{gap:16px}.agreement-item-row{height:auto;min-height:28px}.agreement-checkbox-text{font-size:14px}.agreement-checkbox-text.agreement-all-text{font-size:16px}.agreement-scroll{max-height:250px;padding:16px}}.page-header{margin-bottom:64px;text-align:center}@media(max-width: 768px){.page-header{margin-bottom:48px}}.page-header .page-header-content{max-width:800px;margin:0 auto}.page-header .page-title{font-size:40px;font-weight:700;color:#1a1a2e;margin-bottom:16px;display:flex;align-items:center;justify-content:center;gap:16px}@media(max-width: 768px){.page-header .page-title{font-size:32px;flex-direction:column;gap:8px}}.page-header .page-title i{color:#0049b4;font-size:40px}@media(max-width: 768px){.page-header .page-title i{font-size:32px}}.page-header .page-description{font-size:20px;color:#64748b;line-height:1.6}@media(max-width: 768px){.page-header .page-description{font-size:16px}}.page-header .page-description .highlight{color:#0049b4;font-weight:600;position:relative}.page-header .page-description .highlight::after{content:"";position:absolute;bottom:-2px;left:0;right:0;height:2px;border-radius:2px}.partnership-form-container{max-width:800px;margin:0 auto}@media(max-width: 768px){.partnership-form-container{padding:0 16px}}.partnership-card{padding:64px;background:#fff;border-radius:16px;box-shadow:0 8px 24px rgba(75,155,255,.15);border:1px solid #e2e8f0}@media(max-width: 768px){.partnership-card{padding:40px 24px;border-radius:8px}}.partnership-card .form-group{margin-bottom:40px}@media(max-width: 768px){.partnership-card .form-group{margin-bottom:32px}}.partnership-card .form-group label{display:flex;align-items:center;gap:8px;font-size:16px;font-weight:600;color:#1a1a2e;margin-bottom:16px}.partnership-card .form-group label i{color:#0049b4;font-size:18px}.partnership-card .form-group label .required{color:#ff6b6b;margin-left:2px}.partnership-card .form-group textarea.form-control{min-height:200px;resize:vertical;line-height:1.6}@media(max-width: 768px){.partnership-card .form-group textarea.form-control{min-height:150px}}.partnership-card .form-hint{display:flex;align-items:flex-start;gap:4px;margin-top:8px;font-size:14px;color:#64748b;line-height:1.6}.partnership-card .form-hint i{color:#0049b4;margin-top:2px;flex-shrink:0}.partnership-card .form-error{display:flex;align-items:flex-start;gap:4px;margin-top:8px;font-size:14px;color:#ff6b6b;line-height:1.6}.partnership-card .form-error i{margin-top:2px;flex-shrink:0}.partnership-card .form-help-text{margin-top:16px}.partnership-card .form-help-text p{display:flex;align-items:flex-start;gap:4px;margin:4px 0;font-size:12px;color:#94a3b8;line-height:1.6}.partnership-card .form-help-text p i{color:#6bcf7f;margin-top:2px;flex-shrink:0}.partnership-card .file-upload-wrapper .file-name-display.has-file{color:#1a1a2e;background:rgba(0,73,180,.05);border-color:#0049b4;font-weight:500}@media(max-width: 768px){.partnership-card .file-upload-wrapper .file-upload-btn{order:1}.partnership-card .file-upload-wrapper .file-name-display{order:2}.partnership-card .file-upload-wrapper .file-remove-btn{order:3}}.partnership-card .form-control:focus{border-color:#0049b4;box-shadow:0 0 0 3px rgba(0,73,180,.1);outline:none}.partnership-card textarea.form-control:focus{box-shadow:0 0 0 4px rgba(0,73,180,.15)}@media print{.page-header{margin-bottom:32px}.partnership-card{box-shadow:none;border:1px solid #e2e8f0;padding:32px}.form-actions{display:none}.file-upload-wrapper .file-upload-btn,.file-upload-wrapper .file-remove-btn{display:none}}.user-management-container{max-width:1280px;margin:0 auto;padding:48px 0;margin-bottom:40px}@media(max-width: 768px){.user-management-container{padding:24px 16px}}.user-management-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:40px}@media(max-width: 768px){.user-management-header{flex-direction:column;align-items:flex-start;gap:24px}}.user-management-title h1{font-size:14px;color:#64748b;font-weight:400;margin-bottom:4px}.user-management-title h2{font-size:32px;font-weight:700;color:#1a1a2e}.user-management-actions{display:flex;gap:16px}.btn-create-user-large{padding:16px 40px;font-size:18px;margin-top:24px}.user-management-container .list-table{margin-bottom:40px}.user-management-container .list-table .row-cell a{color:#1a1a2e;text-decoration:none;transition:all .3s ease}.user-management-container .list-table .row-cell a:hover{color:#0049b4}@media(max-width: 768px){.user-management-container .table-controls{background-color:#fff;padding:0}}.user-management-container .table-controls .search-box .btn{height:44px}.user-current-badge{display:inline-flex;height:40px;align-items:center;gap:6px;padding:6px 12px;background-color:rgba(167,139,250,.1);color:#a78bfa;border-radius:6px;font-size:14px;font-weight:500;min-width:116px}@media(max-width: 768px){.user-current-badge{display:none}}.user-empty-state{text-align:center;padding:80px 24px;color:#64748b}.user-empty-state .empty-icon{font-size:80px;margin-bottom:24px;opacity:.5}.user-empty-state h3{font-size:24px;font-weight:600;color:#1a1a2e;margin-bottom:16px}.user-empty-state p{font-size:16px;color:#64748b}.user-detail-container{max-width:900px;margin:0 auto;padding:40px}@media(max-width: 768px){.user-detail-container{padding:24px 16px}}.user-profile-card{color:#fff;margin-bottom:32px;position:relative;overflow:hidden}.user-profile-card::before{content:"";position:absolute;top:-50%;right:-20%;width:400px;height:400px;background:hsla(0,0%,100%,.05);border-radius:50%}.user-profile-card .detail-grid{border:none}.user-profile-header{display:flex;align-items:center;gap:32px;position:relative;z-index:1}@media(max-width: 768px){.user-profile-header{flex-direction:column;text-align:center}}.user-profile-avatar{flex-shrink:0}.avatar-circle-large{width:100px;height:100px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:40px;font-weight:700;background:hsla(0,0%,100%,.2);color:#fff;border:4px solid hsla(0,0%,100%,.3);box-shadow:0 8px 24px rgba(75,155,255,.15)}.user-profile-info{flex:1}.user-profile-info h2{font-size:32px;font-weight:700;margin-bottom:8px}.user-profile-info .user-profile-email{font-size:16px;opacity:.9;margin-bottom:16px}.user-profile-badges{display:flex;gap:8px;flex-wrap:wrap}@media(max-width: 768px){.user-profile-badges{justify-content:center}}.user-profile-badges .user-status-badge,.user-profile-badges .user-role-badge{background:hsla(0,0%,100%,.2);color:#fff;backdrop-filter:blur(10px);border:1px solid hsla(0,0%,100%,.3)}.detail-section{background:#fff;border-radius:12px;padding:32px;box-shadow:0 2px 4px rgba(0,0,0,.05);margin-bottom:24px}.detail-section h2{font-size:20px;font-weight:600;color:#1a1a2e;margin-bottom:24px;padding-bottom:8px;border-bottom:2px solid #eff6ff}.detail-grid{display:grid;grid-template-columns:repeat(2, 1fr);gap:24px}@media(max-width: 768px){.detail-grid{grid-template-columns:1fr}}.detail-item{display:flex;flex-direction:column;gap:4px}.detail-item.full-width{grid-column:1/-1}.detail-label{font-size:14px;font-weight:600;color:#64748b;display:flex;align-items:center;gap:4px}.detail-label i{color:#0049b4;font-size:14px}.detail-value{font-size:16px;color:#1a1a2e;padding:8px 0}.status-indicator{display:inline-flex;align-items:center;gap:4px;padding:6px 12px;border-radius:6px;font-size:14px;font-weight:500}.status-indicator.status-active{background-color:rgba(107,207,127,.1);color:hsl(132,51.0204081633%,55.4117647059%)}.status-indicator.status-active .status-dot{background-color:#6bcf7f}.status-indicator.status-inactive{background-color:rgba(100,116,139,.1);color:#64748b}.status-indicator.status-inactive .status-dot{background-color:#64748b}.status-dot{width:8px;height:8px;border-radius:50%;animation:pulse 2s infinite}@keyframes pulse{0%,100%{opacity:1}50%{opacity:.5}}.detail-actions{display:flex;gap:16px;justify-content:center;padding-top:24px}@media(max-width: 768px){.detail-actions{flex-direction:column}}.inquiry-status-badge{display:inline-flex;align-items:center;justify-content:center;height:30px;padding:0 14px;border-radius:50px;font-size:15px;font-weight:700;color:#fff;white-space:nowrap}.inquiry-status-badge--completed{background-color:#0049b4}.inquiry-status-badge--pending{background-color:#8c959f}.inquiry-actions{display:flex;justify-content:center;margin-top:40px}.actions-desktop{display:flex;gap:4px;flex-wrap:nowrap}.actions-mobile{display:none}@media(max-width: 768px){.actions-desktop{display:none !important}.actions-mobile{display:flex !important}.user-management-container{display:flex;flex-direction:column;gap:24px;padding:24px 16px;margin-bottom:0}.user-management-container .table-controls{display:contents}.user-management-container .table-controls .total-count{order:1;text-align:left;font-size:16px;color:#000;padding-bottom:6px;border-bottom:1.5px solid #212529}.user-management-container .table-controls .total-count strong{color:#0049b4;font-weight:700;font-size:16px}.user-management-container .table-controls .search-box{order:4;width:100%}.user-management-container .table-controls .search-box .btn{display:flex;align-items:center;justify-content:center;width:100%;height:44px}.user-management-container .list-table{order:2;background:#fff;border-radius:12px;overflow:visible}.user-management-container .list-table .list-table-header{display:flex;align-items:center;height:40px;padding:0 8px;background:#3ba4ed;border-radius:8px 8px 0 0}.user-management-container .list-table .list-table-header .header-cell{font-size:14px;font-weight:700;color:#fff;padding:0 4px;justify-content:center}.user-management-container .list-table .list-table-header .header-cell:nth-child(1){display:none}.user-management-container .list-table .list-table-header .header-cell:nth-child(2){width:60px !important;min-width:60px;flex:none}.user-management-container .list-table .list-table-header .header-cell:nth-child(3){flex:1 !important;min-width:80px}.user-management-container .list-table .list-table-header .header-cell:nth-child(4){width:39px !important;min-width:39px;flex:none}.user-management-container .list-table .list-table-header .header-cell:nth-child(5){width:61px !important;min-width:61px;flex:none}.user-management-container .list-table .list-table-header .header-cell:nth-child(6){display:flex !important;width:40px !important;min-width:40px;flex:none}.user-management-container .list-table .list-table-body{display:flex;flex-direction:column}.user-management-container .list-table .list-table-row{display:flex;flex-direction:row;align-items:center;height:40px;padding:0 8px;gap:0;position:relative}.user-management-container .list-table .list-table-row:nth-child(even){background-color:#edf5fd}.user-management-container .list-table .list-table-row:nth-child(odd){background-color:#fff}.user-management-container .list-table .list-table-row:hover{background-color:rgba(0,73,180,.08)}.user-management-container .list-table .list-table-row .row-cell{font-size:13px;font-weight:400;color:#212529;padding:0 4px;justify-content:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.user-management-container .list-table .list-table-row .row-cell::before{display:none}.user-management-container .list-table .list-table-row .row-cell:nth-child(1){display:none}.user-management-container .list-table .list-table-row .row-cell:nth-child(2){width:60px !important;min-width:60px;flex:none}.user-management-container .list-table .list-table-row .row-cell:nth-child(2) a{color:#212529;text-decoration:none;font-size:13px}.user-management-container .list-table .list-table-row .row-cell:nth-child(3){flex:1 !important;min-width:80px;justify-content:flex-start}.user-management-container .list-table .list-table-row .row-cell:nth-child(4){width:39px !important;min-width:39px;flex:none}.user-management-container .list-table .list-table-row .row-cell:nth-child(5){width:61px !important;min-width:61px;flex:none}.user-management-container .list-table .list-table-row .row-cell:nth-child(6),.user-management-container .list-table .list-table-row .row-cell.row-actions{display:flex !important;width:40px !important;min-width:40px;flex:none;justify-content:center;overflow:visible}.user-management-container .list-table .list-table-row .row-actions{display:flex !important}.user-management-container .user-empty-state{padding:48px 16px}.user-management-container .user-empty-state .empty-icon{font-size:60px}.user-management-container .user-empty-state h3{font-size:20px}.user-management-container .user-empty-state p{font-size:14px}.user-management-container .pagination{order:3;gap:13px;padding:24px 0}.user-management-container .pagination .pagination-btn{width:20px;height:20px}.user-management-container .pagination .pagination-btn i,.user-management-container .pagination .pagination-btn svg{font-size:14px;width:14px;height:14px}.user-management-container .pagination .pagination-numbers{gap:13px}.user-management-container .pagination .pagination-number{min-width:10px;height:21px;font-size:15px;color:#5f666c;font-weight:400}.user-management-container .pagination .pagination-number.active{font-weight:700;color:#212529}.inquiry-list-container .list-table .list-table-header .header-cell:nth-child(1){display:none}.inquiry-list-container .list-table .list-table-header .header-cell:nth-child(2){flex:1 !important;min-width:100px}.inquiry-list-container .list-table .list-table-header .header-cell:nth-child(3){width:50px !important;min-width:50px;flex:none;justify-content:center}.inquiry-list-container .list-table .list-table-header .header-cell:nth-child(4){width:65px !important;min-width:65px;flex:none;justify-content:center}.inquiry-list-container .list-table .list-table-header .header-cell:nth-child(5){display:none}.inquiry-list-container .list-table .list-table-row .row-cell:nth-child(1),.inquiry-list-container .list-table .list-table-row .row-cell.row-cell--number{display:none}.inquiry-list-container .list-table .list-table-row .row-cell:nth-child(2),.inquiry-list-container .list-table .list-table-row .row-cell.row-cell--title{flex:1 !important;min-width:100px;justify-content:flex-start}.inquiry-list-container .list-table .list-table-row .row-cell:nth-child(2) .notice-title-link,.inquiry-list-container .list-table .list-table-row .row-cell.row-cell--title .notice-title-link{display:flex;align-items:center;gap:4px;overflow:hidden}.inquiry-list-container .list-table .list-table-row .row-cell:nth-child(2) .notice-title-link .notice-number,.inquiry-list-container .list-table .list-table-row .row-cell.row-cell--title .notice-title-link .notice-number{display:none !important}.inquiry-list-container .list-table .list-table-row .row-cell:nth-child(2) .notice-title-link>span:not(.notice-number):not(.file-icon),.inquiry-list-container .list-table .list-table-row .row-cell.row-cell--title .notice-title-link>span:not(.notice-number):not(.file-icon){overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.inquiry-list-container .list-table .list-table-row .row-cell:nth-child(2) .notice-title-link .file-icon,.inquiry-list-container .list-table .list-table-row .row-cell.row-cell--title .notice-title-link .file-icon{flex-shrink:0;width:16px;height:16px}.inquiry-list-container .list-table .list-table-row .row-cell:nth-child(2) .notice-title-link .file-icon svg,.inquiry-list-container .list-table .list-table-row .row-cell.row-cell--title .notice-title-link .file-icon svg{width:16px;height:16px}.inquiry-list-container .list-table .list-table-row .row-cell:nth-child(3){width:50px !important;min-width:50px;flex:none;justify-content:center;text-align:center}.inquiry-list-container .list-table .list-table-row .row-cell:nth-child(4){width:65px !important;min-width:65px;flex:none;justify-content:center;text-align:center}.inquiry-list-container .list-table .list-table-row .row-cell:nth-child(4) .inquiry-status-badge{height:24px;padding:0 8px;font-size:12px}.inquiry-list-container .list-table .list-table-row .row-cell:nth-child(5){display:none}.inquiry-list-container .inquiry-actions{order:4;width:100%;margin-top:0}.inquiry-list-container .inquiry-actions .btn{width:100%;height:44px;display:flex;align-items:center;justify-content:center}.inquiry-list-container .table-controls .search-field{order:2;width:100%}.notice-list-container .list-table .list-table-header .header-cell:nth-child(1){display:none}.notice-list-container .list-table .list-table-header .header-cell:nth-child(2){flex:1 !important;min-width:100px}.notice-list-container .list-table .list-table-header .header-cell:nth-child(3){width:70px !important;min-width:70px;flex:none}.notice-list-container .list-table .list-table-row .row-cell:nth-child(1),.notice-list-container .list-table .list-table-row .row-cell.row-cell--number{display:none}.notice-list-container .list-table .list-table-row .row-cell:nth-child(2),.notice-list-container .list-table .list-table-row .row-cell.row-cell--title{flex:1 !important;min-width:100px;justify-content:flex-start}.notice-list-container .list-table .list-table-row .row-cell:nth-child(2) .notice-title-link,.notice-list-container .list-table .list-table-row .row-cell.row-cell--title .notice-title-link{display:flex;align-items:center;gap:4px;overflow:hidden}.notice-list-container .list-table .list-table-row .row-cell:nth-child(2) .notice-title-link .notice-number,.notice-list-container .list-table .list-table-row .row-cell.row-cell--title .notice-title-link .notice-number{display:none !important}.notice-list-container .list-table .list-table-row .row-cell:nth-child(2) .notice-title-link>span:not(.notice-number):not(.file-icon),.notice-list-container .list-table .list-table-row .row-cell.row-cell--title .notice-title-link>span:not(.notice-number):not(.file-icon){overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.notice-list-container .list-table .list-table-row .row-cell:nth-child(2) .notice-title-link .file-icon,.notice-list-container .list-table .list-table-row .row-cell.row-cell--title .notice-title-link .file-icon{flex-shrink:0;width:16px;height:16px}.notice-list-container .list-table .list-table-row .row-cell:nth-child(2) .notice-title-link .file-icon svg,.notice-list-container .list-table .list-table-row .row-cell.row-cell--title .notice-title-link .file-icon svg{width:16px;height:16px}.notice-list-container .list-table .list-table-row .row-cell:nth-child(3){width:70px !important;min-width:70px;flex:none;justify-content:center;text-align:center}.notice-list-container .table-controls .search-field{order:2;width:100%}.org-register-container{padding:24px 16px}.org-register-container .common-title-bar,.org-register-container .user-management-container .table-controls,.user-management-container .org-register-container .table-controls{margin-bottom:24px}.org-register-container .common-title-bar h3,.org-register-container .user-management-container .table-controls h3,.user-management-container .org-register-container .table-controls h3{font-size:20px}.org-register-container .register-form-container{box-shadow:none !important;border:none !important;background:rgba(0,0,0,0) !important;padding:0 !important}.org-register-container .form-actions{display:flex;flex-direction:row !important;justify-content:center !important;align-items:center !important;padding:32px 0 0;margin-top:0}.org-register-container .form-actions .form-actions-buttons{width:100%;display:flex;justify-content:center}.org-register-container .form-actions .form-actions-buttons .btn{display:flex;align-items:center;justify-content:center;width:100%;height:44px;font-size:15px}}.dropdown-wrapper{position:relative;display:inline-flex;align-items:center;justify-content:center}.dropdown-wrapper .dropdown-toggle{display:flex;align-items:center;justify-content:center;width:32px;height:32px;padding:0;background:rgba(0,0,0,0);border:none;border-radius:6px;color:#5f666c;cursor:pointer;transition:all .3s ease}.dropdown-wrapper .dropdown-toggle:hover{background:rgba(0,73,180,.1);color:#0049b4}.dropdown-wrapper .dropdown-toggle:focus{outline:none;background:rgba(0,73,180,.1)}.dropdown-wrapper .dropdown-toggle svg{width:20px;height:20px}.dropdown-wrapper .dropdown-menu{position:absolute;top:100%;right:0;min-width:140px;background:#fff;border-radius:8px;box-shadow:0 4px 20px rgba(0,0,0,.15);z-index:100;opacity:0;visibility:hidden;transform:translateY(-8px);transition:all .2s ease;overflow:hidden}.dropdown-wrapper .dropdown-menu .dropdown-item{display:block;width:100%;padding:10px 16px;background:rgba(0,0,0,0);border:none;text-align:left;font-size:14px;font-weight:400;color:#212529;cursor:pointer;transition:all .15s ease;white-space:nowrap}.dropdown-wrapper .dropdown-menu .dropdown-item:hover{background:#f0f7ff;color:#0049b4}.dropdown-wrapper .dropdown-menu .dropdown-item:active{background:#e0efff}.dropdown-wrapper .dropdown-menu .dropdown-item+.dropdown-item{border-top:1px solid #eee}.dropdown-wrapper.open .dropdown-toggle{background:rgba(0,73,180,.1);color:#0049b4}.dropdown-wrapper.open .dropdown-menu{opacity:1;visibility:visible;transform:translateY(4px)}.commission-container{max-width:1400px;margin:0 auto}.wrap{max-width:1400px;margin:0 auto;background:#fff;padding:24px;border-radius:8px;box-shadow:0 4px 12px rgba(75,155,255,.1)}.searchInfo{display:inline-block;margin-right:8px;margin-bottom:16px}.searchBox{background:#f8f9fa;padding:20px;border-radius:8px;margin-bottom:20px}.searchBox.row-two{display:flex;align-items:center;gap:20px;flex-wrap:wrap}.searchBox label{font-weight:500;color:#1a1a2e;min-width:80px}.searchBox .form-control{padding:6px 12px;border:1px solid #e2e8f0;border-radius:6px;font-size:14px;min-width:100px}.form-inline{display:flex;align-items:center;gap:10px}.form-inline .period-selects{display:flex;align-items:center;gap:10px}.form-inline .period-selects .period-unit{margin-right:10px}.btnBox{margin-left:auto}.btnBox .btn+.btn{margin-left:5px}#printBtn{display:none}.commission-notice{color:#dc3545;margin-top:30px;line-height:1.6}.voffset3{margin-top:30px}.color_red{color:#ff6b6b}.color_blue{color:#007bff}.color_darkRed{color:darkred}.table-responsive{overflow-x:auto;margin-top:30px}.table{width:100%;border-collapse:collapse;font-size:14px}.table th{background:#f8fafc;font-weight:500;color:#1a1a2e;padding:16px;text-align:center;border:1px solid #e2e8f0}.table td{padding:16px;text-align:center;border:1px solid #e2e8f0}.table-bordered{border:1px solid #e2e8f0}.table-bordered th,.table-bordered td{border:1px solid #e2e8f0}.border-type{border:2px solid #dee2e6}.commission-table-wrapper{margin-top:30px}.commission-table-wrapper .table{border:2px solid #dee2e6}.summary-row{background-color:#fff3cd !important}.summary-row td{font-weight:600}.summary-row .summary-amount{font-size:16px;color:#007bff}.text-center{text-align:center}.text-right{text-align:right}.text-left{text-align:left}.bg-warning{background-color:#fff3cd !important}.glyphicon-search::before{content:"🔍"}.info-table{border-top:10px solid #64748b;width:100%;border-bottom:2px solid #1a1a2e;margin-bottom:40px}.info-table th{text-align:left;padding:16px 24px;font-weight:600;background:none;border:none}.info-table td{padding:16px 24px;border:none}.commission-table{width:100%;margin-top:40px}.commission-table th,.commission-table td{border:1px solid #999 !important;padding:16px 24px}.commission-table td{text-align:right}.bg_ygreen{background-color:#7fffd4;text-align:center}.bg_skyBlue{background-color:skyblue;text-align:center}.bg_yellow{background-color:#ff0;text-align:center}@media print{body{padding:0;background:#fff}.searchBox,.searchInfo,#printButton,#backButton,#printBtn{display:none !important}@page{size:auto;margin:0 0cm}html{margin:0 0cm}}@media screen and (max-width: 1024px){.wrap{padding:16px}.searchBox{padding:16px}.searchBox.row-two{flex-direction:column;align-items:flex-start;gap:16px}.form-inline{width:100%;flex-direction:column;align-items:flex-start}.form-inline label{min-width:auto;margin-bottom:4px}.form-inline .form-control{width:100%}.form-inline--period .period-selects{display:flex;flex-direction:row;align-items:center;gap:8px;width:100%}.form-inline--period .period-selects .form-control{flex:1;min-width:0}.form-inline--period .period-selects .period-unit{flex-shrink:0;font-size:14px;color:#1a1a2e}.btnBox{margin-left:0;width:100%}.btnBox .btn{width:100%;display:flex;justify-content:center;align-items:center}.table-responsive{overflow-x:scroll;-webkit-overflow-scrolling:touch}}body.commission-print-page{font-size:12px;width:600px;margin:90px;font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}body.commission-print-page div{position:relative}body.commission-print-page table{border-collapse:collapse}body.commission-print-page table th,body.commission-print-page table td{padding:5px 10px}body.commission-print-page table tr{font-size:12px}body.commission-print-page h1{text-align:center}body.commission-print-page h1 img{width:300px}body.commission-print-page h2{margin-top:40px;float:left}body.commission-print-page p{line-height:1.6}body.commission-print-page p[style*="float: right"]{margin-top:25px}body.commission-print-page .btn{padding:8px 16px;border:none;border-radius:4px;cursor:pointer;font-size:14px}body.commission-print-page .btn-primary{background:#667eea;color:#fff}body.commission-print-page .btn-primary:hover{background:#5a67d8}.terms-page{min-height:calc(100vh - 140px);background:#f8fafc;padding:48px 24px}@media(max-width: 768px){.terms-page{padding:40px 16px}}.terms-container{max-width:1280px;margin:0 auto;position:relative;padding:20px}@media(max-width: 768px){.terms-container .common-title-bar{display:none}}.terms-header{background:#f6f9fb;border-radius:12px;padding:20px 40px;margin-bottom:32px}@media(max-width: 768px){.terms-header{padding:16px 24px;margin-bottom:24px}}.terms-header .page-title{font-size:28px;font-weight:700;color:#1a1a2e;margin:0;line-height:1}@media(max-width: 768px){.terms-header .page-title{font-size:24px}}.terms-header .page-description{font-size:16px;color:#64748b;line-height:1.6;margin-top:8px}@media(max-width: 768px){.terms-header .page-description{font-size:14px}}.terms-content-card{background:#fff;border-radius:12px;box-shadow:0 4px 12px rgba(75,155,255,.1);overflow:hidden}.terms-tabs{display:flex;gap:0;background:rgba(0,0,0,0);padding:0;border-bottom:none}@media(max-width: 768px){.terms-tabs{padding:0}}.terms-tabs .tab-link{flex:1;display:flex;align-items:center;justify-content:center;gap:8px;padding:18px 24px;font-size:22px;font-weight:700;color:#8c959f;text-decoration:none;background:#eceff4;transition:all .3s ease;text-align:center;position:relative;border-radius:12px 12px 0 0}@media(max-width: 768px){.terms-tabs .tab-link{padding:16px 16px;font-size:16px;gap:4px}}.terms-tabs .tab-link i{display:none}.terms-tabs .tab-link:hover{color:#0049b4;background:#e4e8ed}.terms-tabs .tab-link.active{color:#fff;background:#3ba4ed;font-weight:700}.terms-selector{padding:24px;background:#f6f9fb;border-bottom:none}@media(max-width: 768px){.terms-selector{padding:16px 16px}}.terms-selector .terms-select{width:340px;height:44px;padding:9px 22px 9px 20px;background:#fff;border:1px solid #dadada;border-radius:12px;font-family:"원신한","OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:14px;font-weight:400;color:#212529;cursor:pointer;transition:all .3s ease;appearance:none;-webkit-appearance:none;-moz-appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='5' viewBox='0 0 10 5'%3E%3Cpath fill='%23515961' d='M5 5L0 0h10z'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 16px center;background-size:10px 5px}@media(max-width: 768px){.terms-selector .terms-select{width:100%;height:40px;padding:8px 36px 8px 16px;font-size:12px}}.terms-selector .terms-select:hover{border-color:#0049b4}.terms-selector .terms-select:focus{outline:none;border-color:#0049b4}.terms-selector .terms-select option{padding:8px 16px;font-size:14px;color:#212529}.terms-content{padding:0 32px}.terms-content .terms-attachment{margin-bottom:16px}.terms-content .terms-attachment .attachment-link{display:inline-flex;align-items:center;gap:8px;padding:10px 20px;background:#fff;border:1px solid #0049b4;border-radius:6px;color:#0049b4;font-size:14px;font-weight:500;text-decoration:none;transition:all .3s ease}.terms-content .terms-attachment .attachment-link:hover{background:#0049b4;color:#fff}.terms-content .terms-attachment .attachment-link svg{flex-shrink:0}.terms-content{min-height:400px;background-color:#f6f9fb}@media(max-width: 768px){.terms-content{padding:24px 24px;min-height:300px}}.terms-content .content-body{padding-top:24px;padding-bottom:24px;font-size:16px;color:#1a1a2e;line-height:1.8;word-wrap:break-word}@media(max-width: 768px){.terms-content .content-body{padding-top:16px;padding-bottom:16px;font-size:14px}}.terms-content .content-body p{margin-bottom:16px}.terms-content .content-body p:last-child{margin-bottom:0}.terms-content .content-body h1,.terms-content .content-body .chapter-title{font-size:20px;font-weight:700;color:#1a1a2e;margin-top:32px;margin-bottom:16px;line-height:1.4}.terms-content .content-body h1:first-child,.terms-content .content-body .chapter-title:first-child{margin-top:0}@media(max-width: 768px){.terms-content .content-body h1,.terms-content .content-body .chapter-title{font-size:18px}}.terms-content .content-body h2,.terms-content .content-body .article-title{font-size:18px;font-weight:700;color:#1a1a2e;margin-top:24px;margin-bottom:8px;line-height:1.4}@media(max-width: 768px){.terms-content .content-body h2,.terms-content .content-body .article-title{font-size:16px}}.terms-content .content-body h3{font-size:20px;font-weight:600;color:#1a1a2e;margin-top:16px;margin-bottom:8px}@media(max-width: 768px){.terms-content .content-body h3{font-size:16px}}.terms-content .content-body strong{font-weight:700;color:#1a1a2e}.terms-content .content-body em{font-style:italic;color:#64748b}.terms-content .content-body a{color:#0049b4;text-decoration:underline;transition:all .3s ease}.terms-content .content-body a:hover{color:#c3dfea}.terms-content .content-body ul,.terms-content .content-body ol{margin:16px 0;padding-left:0;list-style:none}@media(max-width: 768px){.terms-content .content-body ul,.terms-content .content-body ol{padding-left:0}}.terms-content .content-body li{margin-bottom:4px;line-height:1.8;padding-left:0}.terms-content .content-body li:last-child{margin-bottom:0}.terms-content .content-body blockquote{border-left:4px solid #0049b4;padding-left:24px;margin:24px 0;color:#64748b;font-style:italic;background:#f8fafc;padding:16px 24px;border-radius:0 8px 8px 0}@media(max-width: 768px){.terms-content .content-body blockquote{padding:8px 16px}}.terms-content .content-body code{background:#f8fafc;padding:2px 6px;border-radius:6px;font-family:"Fira Code","Courier New",monospace;font-size:.9em;color:#0049b4}.terms-content .content-body pre{background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;padding:16px;overflow-x:auto;margin:24px 0}@media(max-width: 768px){.terms-content .content-body pre{padding:8px}}.terms-content .content-body pre code{background:rgba(0,0,0,0);padding:0;color:#1a1a2e}.terms-content .content-body table{width:100%;border-collapse:collapse;margin:24px 0;border:1px solid #e2e8f0;border-radius:8px;overflow:hidden;font-size:14px}@media(max-width: 768px){.terms-content .content-body table{font-size:12px}}.terms-content .content-body table th{background:#eff6ff;padding:8px 16px;text-align:left;font-weight:600;color:#1a1a2e;border-bottom:2px solid #e2e8f0}@media(max-width: 768px){.terms-content .content-body table th{padding:4px 8px}}.terms-content .content-body table td{padding:8px 16px;border-bottom:1px solid #e2e8f0;color:#1a1a2e}@media(max-width: 768px){.terms-content .content-body table td{padding:4px 8px}}.terms-content .content-body table tr:last-child td{border-bottom:none}.terms-content .content-body table tr:hover{background:#f8fafc}.terms-content .content-body hr{border:none;border-top:1px solid #e2e8f0;margin:40px 0}.terms-content .content-body .article{margin-top:40px;padding-top:24px;border-top:1px solid #e2e8f0}.terms-content .content-body .article:first-child{margin-top:0;padding-top:0;border-top:none}.terms-content .content-body .section-number{display:inline-block;min-width:30px;color:#0049b4;font-weight:700}.terms-selector .select-items::-webkit-scrollbar{width:6px}.terms-selector .select-items::-webkit-scrollbar-track{background:#f8fafc;border-radius:6px}.terms-selector .select-items::-webkit-scrollbar-thumb{background:#0049b4;border-radius:6px}.terms-selector .select-items::-webkit-scrollbar-thumb:hover{background:#c3dfea}.service-intro{max-width:1200px;margin:0 auto;padding:20px;background-color:#fff;color:#000;font-family:"원신한","OneShinhan","Spoqa Han Sans Neo",sans-serif;display:flex;flex-direction:column;gap:20px}.service-intro__title{margin:0;font-size:25px;font-weight:700;line-height:1.4;letter-spacing:-0.01em}.service-intro__lead{margin:0;font-size:20px;font-weight:700;line-height:1.5;letter-spacing:-0.01em}.service-intro__desc{margin:0;font-size:15px;font-weight:500;line-height:1.7}.service-intro__spacer{height:10px}.service-intro__section-title{margin:0;font-size:20px;font-weight:500;line-height:1.5}.service-intro__section-body{font-size:15px;font-weight:500;line-height:1.7}.service-intro__section-body p{margin:0}.service-intro__section-body p+p{margin-top:8px}.service-intro__list{margin:0;padding-left:22px;list-style:disc;font-size:15px;font-weight:500;line-height:1.7}.service-intro__list li+li{margin-top:4px}@media(max-width: 768px){.service-intro{padding:16px;gap:16px}.service-intro__title{font-size:22px}.service-intro__lead{font-size:17px}.service-intro__section-title{font-size:17px}.service-intro__desc,.service-intro__section-body,.service-intro__list{font-size:14px}}.signup-guide{max-width:1200px;margin:0 auto;padding:64px 20px 80px;background-color:#f9fafb}.signup-guide__header{margin-bottom:64px}.signup-guide__title{font-size:48px;font-weight:700;line-height:1;color:#0049b4;margin:0 0 22px 0}.signup-guide__underline{width:96px;height:4px;background-color:#0049b4;margin-bottom:36px}.signup-guide__desc p{font-size:20px;font-weight:400;line-height:1.625;color:#1e2939;margin:0}.signup-guide__card{background:#fff;border-radius:16px;padding:48px 50px;box-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1);margin-bottom:64px}.signup-guide__steps{display:grid;grid-template-columns:repeat(6, 1fr);gap:16px}.signup-guide__step{display:flex;flex-direction:column;align-items:center;position:relative}.signup-guide__step--last .signup-guide__step-line{display:none}.signup-guide__step-icon{width:64px;height:64px;border-radius:50%;background:#fff;border:1.6px solid #0049b4;display:flex;align-items:center;justify-content:center;position:relative;z-index:2}.signup-guide__step-icon svg{width:32px;height:32px}.signup-guide__step-dot{width:12px;height:12px;background-color:#0049b4;border-radius:50%;margin-top:12px;position:relative;z-index:2}.signup-guide__step-label{display:flex;flex-direction:column;align-items:center;margin-top:8px;text-align:center}.signup-guide__step-num{font-size:14px;font-weight:400;color:#6a7282;line-height:20px}.signup-guide__step-text{font-size:14px;font-weight:700;color:#1e2939;line-height:20px;margin-top:4px;white-space:nowrap}.signup-guide__step-line{position:absolute;top:32px;left:calc(50% + 32px);width:calc(100% - 32px);height:2px;background:linear-gradient(to right, #0049b4, rgba(0, 73, 180, 0.3));z-index:1}.signup-guide__notes{margin-bottom:48px}.signup-guide__note{display:flex;align-items:flex-start;gap:8px;font-size:18px;font-weight:400;line-height:32px;color:#1e2939;margin:0 0 16px 0}.signup-guide__note:last-child{margin-bottom:0}.signup-guide__note-mark{flex-shrink:0;width:18px}.signup-guide__action{text-align:center;padding-top:16px}.signup-guide__btn{display:inline-block;padding:18px 64px;background:#0049b4;color:#fff;font-size:18px;font-weight:600;border-radius:8px;text-decoration:none;transition:background .2s ease,transform .2s ease}.signup-guide__btn:hover{background:rgb(0,65.7,162);transform:translateY(-2px)}.signup-guide__btn:active{transform:translateY(0)}@media(max-width: 1024px){.signup-guide{padding:48px 24px 64px}.signup-guide__title{font-size:36px}.signup-guide__steps{grid-template-columns:repeat(3, 1fr);gap:24px 16px}.signup-guide__step:nth-child(3) .signup-guide__step-line{display:none}.signup-guide__step-line{width:calc(100% - 24px)}}@media(max-width: 768px){.signup-guide{padding:32px 16px 48px}.signup-guide__header{margin-bottom:32px}.signup-guide__title{font-size:28px}.signup-guide__underline{width:64px;height:3px;margin-bottom:24px}.signup-guide__desc p{font-size:16px;line-height:1.75}.signup-guide__card{padding:32px 20px;margin-bottom:32px}.signup-guide__steps{grid-template-columns:repeat(2, 1fr);gap:32px 16px}.signup-guide__step:nth-child(2n) .signup-guide__step-line{display:none}.signup-guide__step:nth-child(3) .signup-guide__step-line{display:block}.signup-guide__step-icon{width:56px;height:56px}.signup-guide__step-icon svg{width:28px;height:28px}.signup-guide__step-dot{width:10px;height:10px}.signup-guide__step-text{font-size:13px;white-space:normal}.signup-guide__step-line{top:28px;left:calc(50% + 28px);width:calc(100% - 28px)}.signup-guide__notes{margin-bottom:32px}.signup-guide__note{font-size:14px;line-height:24px}.signup-guide__btn{padding:16px 48px;font-size:16px;width:100%;max-width:320px}}@media(max-width: 480px){.signup-guide__steps{grid-template-columns:1fr;gap:24px}.signup-guide__step{flex-direction:row;align-items:center;gap:16px}.signup-guide__step .signup-guide__step-line{display:none !important}.signup-guide__step-icon{flex-shrink:0}.signup-guide__step-dot{display:none}.signup-guide__step-label{align-items:flex-start;margin-top:0;text-align:left}}.service_guide_header{padding:56px 16px 32px;text-align:center;background:linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%)}.service_guide_header .service_guide .title{font-size:28px;font-weight:700;line-height:36px;letter-spacing:-0.02em;color:#140064;margin-bottom:16px}.service_guide_header .service_guide .detail{font-size:14px;font-weight:400;line-height:22px;color:#495057}.service_guide_image{padding:32px 16px;text-align:center;background:#fff}.service_guide_image img{max-width:100%;height:auto;border-radius:8px;box-shadow:0 4px 12px rgba(0,0,0,.1)}.service_guide_info{padding:24px 16px;text-align:center}.service_guide_info .info_text{font-size:14px;line-height:24px;color:#212529;margin-bottom:12px}.service_guide_info .info_text strong{color:#140064;font-weight:600}.service_guide_info .info_notice{font-size:13px;color:#0049b4;font-weight:500}.service_member_table{padding:16px}.service_member_table table{width:100%;border-collapse:collapse;border:1px solid #dee2e6;border-radius:8px;overflow:hidden}.service_member_table table th,.service_member_table table td{padding:16px;font-size:14px;line-height:22px;border-bottom:1px solid #dee2e6}.service_member_table table th{background:#f8f9fa;color:#140064;font-weight:600;text-align:center;vertical-align:middle}.service_member_table table td{color:#495057;text-align:left}.service_member_table table tr:last-child th,.service_member_table table tr:last-child td{border-bottom:none}.service_guide_notice{padding:16px;text-align:center}.service_guide_notice .notice_text{font-size:13px;color:#0049b4;font-weight:500;line-height:20px}.service_guide_action{padding:32px 16px 56px;text-align:center}.service_guide_action .btn_register{display:inline-block;padding:16px 48px;background:#140064;color:#fff;font-size:16px;font-weight:600;border-radius:8px;text-decoration:none;transition:background .2s ease}.service_guide_action .btn_register:hover{background:#0049b4}@media(min-width: 768px){.service_guide_header{padding:80px 32px 48px}.service_guide_header .service_guide .title{font-size:40px;line-height:48px}.service_guide_header .service_guide .detail{font-size:18px;line-height:28px}.service_guide_image{padding:48px 32px}.service_guide_image img{max-width:800px}.service_guide_info{padding:32px}.service_guide_info .info_text{font-size:16px;line-height:28px}.service_guide_info .info_notice{font-size:14px}.service_member_table{padding:32px;max-width:800px;margin:0 auto}.service_member_table table th,.service_member_table table td{padding:20px 24px;font-size:15px}.service_guide_notice{padding:24px 32px}.service_guide_notice .notice_text{font-size:14px}.service_guide_action{padding:48px 32px 80px}.service_guide_action .btn_register{padding:18px 64px;font-size:18px}}.oauth2-2legged{max-width:1200px;margin:0 auto;padding:20px;background-color:#fff;color:#1a1a2e;font-family:"원신한","OneShinhan","Spoqa Han Sans Neo",sans-serif;display:flex;flex-direction:column;gap:24px;box-sizing:border-box}.oauth2-2legged__eyebrow{display:inline-block;font-size:14px;font-weight:700;color:#0049b4;letter-spacing:1.2px;margin-bottom:8px}.oauth2-2legged__h2{margin:0 0 8px;font-size:26px;font-weight:700;line-height:1.3;color:#1a1a2e}.oauth2-2legged__desc{margin:0 0 16px;font-size:14px;font-weight:500;color:#64748b}.oauth2-2legged__hero{position:relative;display:grid;grid-template-columns:1fr 320px;gap:32px;padding:48px 80px;border-radius:20px;border:1px solid #e2e8f0;background:linear-gradient(135deg, #EDF9FE 0%, #FFFFFF 100%)}.oauth2-2legged__hero-eyebrow{display:inline-flex;align-items:center;gap:10px;padding:6px 18px;border-radius:14px;background:#fff;border:1px solid #c3dfea;color:#0049b4;font-size:13px;font-weight:600;margin-bottom:18px}.oauth2-2legged__hero-eyebrow-dot{width:8px;height:8px;border-radius:50%;background:#0049b4}.oauth2-2legged__hero-title{margin:0 0 16px;font-size:36px;font-weight:800;color:#1a1a2e;letter-spacing:-0.5px;line-height:1.2;white-space:nowrap}.oauth2-2legged__hero-lead{margin:0;font-size:16px;font-weight:500;color:#64748b;line-height:1.6}.oauth2-2legged__hero-lead+.oauth2-2legged__hero-lead{margin-top:2px}.oauth2-2legged__hero-chips{display:flex;gap:12px;margin-top:22px;flex-wrap:wrap}.oauth2-2legged__chip{display:inline-flex;align-items:center;padding:6px 18px;border-radius:15px;background:#fff;border:1px solid #e2e8f0;font-size:13px;font-weight:600;color:#1a1a2e}.oauth2-2legged__chip--primary{background:#0049b4;border-color:#0049b4;color:#fff}.oauth2-2legged__hero-illust{align-self:center;background:#fff;border:1px solid #c3dfea;border-radius:20px;overflow:hidden}.oauth2-2legged__hero-illust svg{display:block;width:100%;height:auto}.oauth2-2legged__prereq-grid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px}.oauth2-2legged__prereq-card{display:grid;grid-template-columns:44px 1fr;gap:12px;align-items:start;padding:24px;background:#fff;border:1px solid #e2e8f0;border-radius:14px}.oauth2-2legged__prereq-num{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;border-radius:50%;background:#0049b4;color:#fff;font-size:16px;font-weight:800}.oauth2-2legged__prereq-title{margin:4px 0 8px;font-size:15px;font-weight:700;color:#1a1a2e}.oauth2-2legged__prereq-body p{margin:0;font-size:13px;font-weight:500;color:#64748b;line-height:1.6}.oauth2-2legged__prereq-body p+p{margin-top:2px}.oauth2-2legged__sequence-diagram{padding:16px;background:#fff;border:1px solid #e2e8f0;border-radius:14px}.oauth2-2legged__sequence-diagram svg{display:block;width:100%;height:auto}.oauth2-2legged__step-grid{display:grid;grid-template-columns:1fr 1fr;gap:24px}.oauth2-2legged__endpoint-box{display:grid;grid-template-columns:auto 1fr auto;align-items:center;gap:16px;padding:16px 24px;margin-bottom:20px;background:#fff;border:1px solid #e2e8f0;border-radius:12px}.oauth2-2legged__method{display:inline-block;padding:5px 14px;border-radius:6px;background:#0049b4;color:#fff;font-size:13px;font-weight:800;letter-spacing:.5px}.oauth2-2legged__endpoint-path{font-family:"Fira Code",monospace;font-size:16px;color:#1a1a2e}.oauth2-2legged__endpoint-content-type{display:inline-block;padding:5px 14px;border-radius:14px;background:#f8fafc;border:1px solid #e2e8f0;color:#64748b;font-size:11px;font-weight:700}.oauth2-2legged__panel{padding:24px 28px;background:#fff;border:1px solid #e2e8f0;border-radius:12px}.oauth2-2legged__panel-title{margin:0 0 14px;font-size:14px;font-weight:700;color:#1a1a2e}.oauth2-2legged__panel-subtitle{margin:20px 0 10px;font-size:13px;font-weight:700;color:#1a1a2e}.oauth2-2legged__table{width:100%;border-collapse:collapse;font-size:13px}.oauth2-2legged__table thead{background:#f8fafc}.oauth2-2legged__table th{text-align:left;padding:10px 4px;font-size:12px;font-weight:700;color:#64748b;letter-spacing:.4px}.oauth2-2legged__table td{padding:12px 4px;border-bottom:1px solid #e2e8f0;color:#64748b;vertical-align:middle}.oauth2-2legged__table tbody tr:last-child td{border-bottom:none}.oauth2-2legged__table code{font-family:"Fira Code",monospace;font-size:13px;font-weight:700;color:#1a1a2e}.oauth2-2legged__table--errors td:first-child{width:64px}.oauth2-2legged__table--errors td:nth-child(2){width:180px}.oauth2-2legged__table--errors td:nth-child(3){width:280px}.oauth2-2legged__table--errors td{color:#64748b}.oauth2-2legged__field-name{color:#0049b4 !important}.oauth2-2legged__req-badge{display:inline-block;min-width:32px;padding:2px 10px;border-radius:9px;text-align:center;font-size:10px;font-weight:700;color:#fff}.oauth2-2legged__req-badge--required{background:#0049b4}.oauth2-2legged__req-badge--optional{background:#94a3b8}.oauth2-2legged__warning{margin:16px 0 0;padding:6px 14px;border-radius:6px;background:#fff9e6;color:#a07a00;font-size:12px;font-weight:600}.oauth2-2legged__code-panel{position:relative;padding:24px 28px;border-radius:12px;background:#1a1a2e;overflow:hidden}.oauth2-2legged__code-panel::before{content:"";position:absolute;inset:0 auto 0 0;width:6px;background:#0049b4}.oauth2-2legged__code-panel--error::before{background:#ff6b6b}.oauth2-2legged__code-tag{display:inline-block;padding:4px 14px;border-radius:11px;background:#0049b4;color:#fff;font-size:11px;font-weight:700;margin-bottom:16px}.oauth2-2legged__code-tag--ok{background:#0049b4}.oauth2-2legged__code-tag--error{background:#ff6b6b}.oauth2-2legged__code-block{margin:0;color:#fff;font-family:"Fira Code",monospace;font-size:13px;line-height:1.7;white-space:pre;overflow-x:auto}.oauth2-2legged .o2leg-c{color:#00d4ff}.oauth2-2legged .o2leg-y{color:#ffd93d}.oauth2-2legged .o2leg-g{color:#6bcf7f}.oauth2-2legged .o2leg-p{color:#a78bfa}.oauth2-2legged .o2leg-cm{color:#94a3b8}.oauth2-2legged__header-box{padding:14px 16px;border-radius:8px;background:#f8fafc;margin-bottom:6px}.oauth2-2legged__header-key{display:block;font-family:"Fira Code",monospace;font-size:13px;font-weight:700;color:#0049b4;margin-bottom:4px}.oauth2-2legged__header-value{display:block;font-family:"Fira Code",monospace;font-size:13px;color:#1a1a2e}.oauth2-2legged__tips{list-style:none;margin:0;padding:0}.oauth2-2legged__tips li{position:relative;padding-left:18px;font-size:13px;font-weight:500;color:#64748b;line-height:1.6}.oauth2-2legged__tips li::before{content:"";position:absolute;left:4px;top:9px;width:6px;height:6px;border-radius:50%;background:#0049b4}.oauth2-2legged__tips li+li{margin-top:4px}.oauth2-2legged__status-badge{display:inline-block;min-width:40px;padding:2px 12px;border-radius:11px;text-align:center;font-size:12px;font-weight:700}.oauth2-2legged__status-badge--400{background:#ffe9e9;color:#ff6b6b}.oauth2-2legged__status-badge--403{background:#fff9e6;color:#a07a00}.oauth2-2legged__error-note{margin:14px 0 0;padding:6px 14px;border-radius:6px;background:#ffe9e9;color:#a23b3b;font-size:12px;font-weight:600}.oauth2-2legged__cta{position:relative;display:block;padding:36px 48px;border-radius:20px;background:linear-gradient(90deg, #0049b4 0%, #1f6fd0 100%);color:#fff;text-decoration:none;overflow:hidden;transition:transform .2s ease,box-shadow .2s ease}.oauth2-2legged__cta:hover{transform:translateY(-2px);box-shadow:0 12px 28px rgba(0,73,180,.25)}.oauth2-2legged__cta-body{position:relative;z-index:1}.oauth2-2legged__cta-eyebrow{display:inline-block;font-size:13px;font-weight:700;color:#c3dfea;letter-spacing:1.2px;margin-bottom:12px}.oauth2-2legged__cta-title{margin:0 0 10px;font-size:26px;font-weight:800;color:#fff;line-height:1.3}.oauth2-2legged__cta-desc{margin:0 0 18px;font-size:14px;font-weight:500;color:#c3dfea}.oauth2-2legged__cta-button{display:inline-flex;align-items:center;padding:10px 24px;border-radius:19px;background:#fff;color:#0049b4;font-size:14px;font-weight:700}.oauth2-2legged__cta-deco{position:absolute;border-radius:50%;background:hsla(0,0%,100%,.08)}.oauth2-2legged__cta-deco--lg{width:120px;height:120px;right:80px;bottom:30px}.oauth2-2legged__cta-deco--sm{width:80px;height:80px;right:20px;bottom:-10px;background:hsla(0,0%,100%,.1)}@media(max-width: 1024px){.oauth2-2legged__hero{grid-template-columns:1fr;padding:32px}.oauth2-2legged__hero-illust{max-width:280px}.oauth2-2legged__prereq-grid{grid-template-columns:1fr}.oauth2-2legged__step-grid{grid-template-columns:1fr}.oauth2-2legged__endpoint-box{grid-template-columns:1fr;gap:8px}.oauth2-2legged__endpoint-box .oauth2-2legged__method,.oauth2-2legged__endpoint-box .oauth2-2legged__endpoint-content-type{justify-self:start}.oauth2-2legged__table--errors td:first-child,.oauth2-2legged__table--errors td:nth-child(2),.oauth2-2legged__table--errors td:nth-child(3){width:auto}}@media(max-width: 640px){.oauth2-2legged{padding:16px;gap:16px}.oauth2-2legged__hero{padding:28px 24px}.oauth2-2legged__hero-title{font-size:28px}.oauth2-2legged__h2{font-size:22px}.oauth2-2legged__cta{padding:28px 24px}.oauth2-2legged__cta-title{font-size:22px}.oauth2-2legged__panel{padding:18px 20px}.oauth2-2legged__code-panel{padding:18px 20px}}.api-statistics-container{margin:0 auto;padding:48px 0px}.statistics-search-section{display:flex;justify-content:flex-end;margin-bottom:30px}.search-filter-row{display:flex;align-items:center;gap:10px;background:#fff;padding:8px 16px;border-radius:30px;border:1px solid #e0e0e0}.date-range-picker{display:flex;align-items:center;gap:8px}.date-input{border:none;padding:8px 12px;font-size:14px;color:#333;background:rgba(0,0,0,0);outline:none}.date-separator{color:#666}.btn-search{background:#0049b4;border:none;border-radius:50%;width:36px;height:36px;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:background .2s}.btn-search:hover{background:#003d99}.btn-search svg{stroke:#fff}.statistics-summary-card{background:#f5f8fc;border-radius:16px;padding:40px;margin-bottom:30px}.summary-content{display:flex;align-items:center;justify-content:space-between;gap:40px}.summary-left{flex:1}.app-select-wrapper{margin-bottom:24px}.app-select{padding:12px 40px 12px 20px;border-radius:25px;border:1px solid #0049b4;background:#fff;color:#0049b4;font-size:14px;font-weight:500;cursor:pointer;appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%230049B4' d='M6 8L1 3h10z'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 16px center;min-width:150px;outline:none;transition:all .2s}.app-select:hover{background-color:#f0f5ff}.app-select:focus{border-color:#003d99;box-shadow:0 0 0 3px rgba(0,73,180,.1)}.summary-info{padding-left:10px}.summary-total-text{font-size:18px;color:#333;margin-bottom:16px}.summary-total-text strong{font-size:24px;color:#0049b4}.summary-detail-list{display:flex;flex-direction:column;gap:12px}.summary-detail-item{display:flex;align-items:center;gap:8px;font-size:14px}.summary-detail-item.success .detail-icon{color:#4a90d9}.summary-detail-item.failure .detail-icon{color:#f5a3b5}.detail-label{color:#666;min-width:30px}.detail-value{font-weight:600;color:#333}.detail-percent{color:#999}.summary-center{flex:0 0 200px;display:flex;align-items:center;justify-content:center}#donutChart{max-width:200px;max-height:200px}.donut-segment{transition:stroke-dasharray .3s ease,stroke-dashoffset .3s ease}.summary-right{flex:0 0 230px;display:flex;flex-direction:column;gap:20px}.rate-display{display:flex;align-items:center;gap:8px;padding:16px 0;border-bottom:1px solid #e0e0e0}.rate-display:last-child{border-bottom:none}.rate-indicator{width:12px;height:12px;border-radius:50%}.rate-indicator.success{background:#4a90d9}.rate-indicator.failure{background:#f5a3b5}.rate-label{font-size:14px;color:#666}.rate-value{font-size:32px;font-weight:700}.success-rate .rate-value{color:#4a90d9}.failure-rate .rate-value{color:#f5a3b5}.rate-percent{font-size:18px;color:#999}.statistics-table-card{background:#fff;border-radius:16px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,.08)}.table-header{display:flex;justify-content:space-between;align-items:center;padding:20px 24px;background:#0049b4;color:#fff}.table-title{display:flex;align-items:center;gap:10px;font-size:18px;font-weight:600;margin:0}.table-title svg{stroke:#fff}.btn-download{display:flex;align-items:center;gap:6px;padding:8px 16px;background:hsla(0,0%,100%,.2);border-radius:20px;color:#fff;text-decoration:none;font-size:14px;transition:background .2s}.btn-download:hover{background:hsla(0,0%,100%,.3);color:#fff}.table-container{padding:0}.statistics-table{width:100%;border-collapse:collapse}.statistics-table thead th{padding:16px 20px;text-align:left;font-weight:500;color:#666;border-bottom:1px solid #e0e0e0;background:#fafafa}.statistics-table tbody td{padding:16px 20px;border-bottom:1px solid #f0f0f0;color:#333}.statistics-table tbody tr:hover{background:#f9f9f9}.statistics-table tfoot .total-row{background:#e3f2fd}.statistics-table tfoot .total-row td{padding:16px 20px;font-weight:600;color:#0049b4;border-bottom:none}.api-name{display:flex;align-items:center;gap:10px}.api-icon{display:flex;align-items:center;justify-content:center;width:28px;height:28px;background:#f0f0f0;border-radius:6px}.api-icon svg{stroke:#666}.api-icon.total{background:#e3f2fd}.api-icon.total svg{stroke:#0049b4}.progress-cell{min-width:200px}.progress-bar{display:flex;height:12px;background:#e0e0e0;border-radius:6px;overflow:hidden}.progress-success{background:#4a90d9;transition:width .3s ease}.progress-failure{background:#f5a3b5;transition:width .3s ease}.empty-row td{text-align:center;padding:40px 20px;color:#999}@media(max-width: 992px){.summary-content{flex-direction:column;gap:30px}.summary-left,.summary-center,.summary-right{flex:none;width:100%}.summary-center{order:-1}.summary-right{flex-direction:row;justify-content:center}}@media(max-width: 768px){.api-statistics-container{padding:20px 16px}.statistics-summary-card{padding:24px 20px}.app-toggle-buttons{flex-wrap:wrap}.toggle-btn{flex:1;text-align:center}.statistics-table thead th,.statistics-table tbody td,.statistics-table tfoot td{padding:12px 10px;font-size:13px}.progress-cell{min-width:100px}.search-filter-row{flex-wrap:wrap}}.d-none{display:none !important}.d-block{display:block !important}.d-inline-block{display:inline-block !important}.d-flex{display:flex !important}.d-inline-flex{display:inline-flex !important}.d-grid{display:grid !important}.flex-center{display:flex;justify-content:center;align-items:center}.flex-between{display:flex;justify-content:space-between;align-items:center}.flex-column{flex-direction:column}.flex-wrap{flex-wrap:wrap}.align-center{align-items:center}.align-start{align-items:flex-start}.align-end{align-items:flex-end}.justify-center{justify-content:center}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-between{justify-content:space-between}.justify-around{justify-content:space-around}.gap-xs{gap:4px}.gap-sm{gap:8px}.gap-md{gap:16px}.gap-lg{gap:24px}.gap-xl{gap:32px}.m-0{margin:0 !important}.mt-0{margin-top:0 !important}.mb-0{margin-bottom:0 !important}.ml-0{margin-left:0 !important}.mr-0{margin-right:0 !important}.mx-0{margin-left:0 !important;margin-right:0 !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.m-xs{margin:4px !important}.mt-xs{margin-top:4px !important}.mb-xs{margin-bottom:4px !important}.ml-xs{margin-left:4px !important}.mr-xs{margin-right:4px !important}.mx-xs{margin-left:4px !important;margin-right:4px !important}.my-xs{margin-top:4px !important;margin-bottom:4px !important}.m-sm{margin:8px !important}.mt-sm{margin-top:8px !important}.mb-sm{margin-bottom:8px !important}.ml-sm{margin-left:8px !important}.mr-sm{margin-right:8px !important}.mx-sm{margin-left:8px !important;margin-right:8px !important}.my-sm{margin-top:8px !important;margin-bottom:8px !important}.m-md{margin:16px !important}.mt-md{margin-top:16px !important}.mb-md{margin-bottom:16px !important}.ml-md{margin-left:16px !important}.mr-md{margin-right:16px !important}.mx-md{margin-left:16px !important;margin-right:16px !important}.my-md{margin-top:16px !important;margin-bottom:16px !important}.m-lg{margin:24px !important}.mt-lg{margin-top:24px !important}.mb-lg{margin-bottom:24px !important}.ml-lg{margin-left:24px !important}.mr-lg{margin-right:24px !important}.mx-lg{margin-left:24px !important;margin-right:24px !important}.my-lg{margin-top:24px !important;margin-bottom:24px !important}.m-xl{margin:32px !important}.mt-xl{margin-top:32px !important}.mb-xl{margin-bottom:32px !important}.ml-xl{margin-left:32px !important}.mr-xl{margin-right:32px !important}.mx-xl{margin-left:32px !important;margin-right:32px !important}.my-xl{margin-top:32px !important;margin-bottom:32px !important}.m-2xl{margin:40px !important}.mt-2xl{margin-top:40px !important}.mb-2xl{margin-bottom:40px !important}.ml-2xl{margin-left:40px !important}.mr-2xl{margin-right:40px !important}.mx-2xl{margin-left:40px !important;margin-right:40px !important}.my-2xl{margin-top:40px !important;margin-bottom:40px !important}.m-3xl{margin:48px !important}.mt-3xl{margin-top:48px !important}.mb-3xl{margin-bottom:48px !important}.ml-3xl{margin-left:48px !important}.mr-3xl{margin-right:48px !important}.mx-3xl{margin-left:48px !important;margin-right:48px !important}.my-3xl{margin-top:48px !important;margin-bottom:48px !important}.m-4xl{margin:64px !important}.mt-4xl{margin-top:64px !important}.mb-4xl{margin-bottom:64px !important}.ml-4xl{margin-left:64px !important}.mr-4xl{margin-right:64px !important}.mx-4xl{margin-left:64px !important;margin-right:64px !important}.my-4xl{margin-top:64px !important;margin-bottom:64px !important}.m-auto{margin:auto !important}.mt-auto{margin-top:auto !important}.mb-auto{margin-bottom:auto !important}.ml-auto{margin-left:auto !important}.mr-auto{margin-right:auto !important}.mx-auto{margin-left:auto !important;margin-right:auto !important}.my-auto{margin-top:auto !important;margin-bottom:auto !important}.p-0{padding:0 !important}.pt-0{padding-top:0 !important}.pb-0{padding-bottom:0 !important}.pl-0{padding-left:0 !important}.pr-0{padding-right:0 !important}.px-0{padding-left:0 !important;padding-right:0 !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.p-xs{padding:4px !important}.pt-xs{padding-top:4px !important}.pb-xs{padding-bottom:4px !important}.pl-xs{padding-left:4px !important}.pr-xs{padding-right:4px !important}.px-xs{padding-left:4px !important;padding-right:4px !important}.py-xs{padding-top:4px !important;padding-bottom:4px !important}.p-sm{padding:8px !important}.pt-sm{padding-top:8px !important}.pb-sm{padding-bottom:8px !important}.pl-sm{padding-left:8px !important}.pr-sm{padding-right:8px !important}.px-sm{padding-left:8px !important;padding-right:8px !important}.py-sm{padding-top:8px !important;padding-bottom:8px !important}.p-md{padding:16px !important}.pt-md{padding-top:16px !important}.pb-md{padding-bottom:16px !important}.pl-md{padding-left:16px !important}.pr-md{padding-right:16px !important}.px-md{padding-left:16px !important;padding-right:16px !important}.py-md{padding-top:16px !important;padding-bottom:16px !important}.p-lg{padding:24px !important}.pt-lg{padding-top:24px !important}.pb-lg{padding-bottom:24px !important}.pl-lg{padding-left:24px !important}.pr-lg{padding-right:24px !important}.px-lg{padding-left:24px !important;padding-right:24px !important}.py-lg{padding-top:24px !important;padding-bottom:24px !important}.p-xl{padding:32px !important}.pt-xl{padding-top:32px !important}.pb-xl{padding-bottom:32px !important}.pl-xl{padding-left:32px !important}.pr-xl{padding-right:32px !important}.px-xl{padding-left:32px !important;padding-right:32px !important}.py-xl{padding-top:32px !important;padding-bottom:32px !important}.p-2xl{padding:40px !important}.pt-2xl{padding-top:40px !important}.pb-2xl{padding-bottom:40px !important}.pl-2xl{padding-left:40px !important}.pr-2xl{padding-right:40px !important}.px-2xl{padding-left:40px !important;padding-right:40px !important}.py-2xl{padding-top:40px !important;padding-bottom:40px !important}.p-3xl{padding:48px !important}.pt-3xl{padding-top:48px !important}.pb-3xl{padding-bottom:48px !important}.pl-3xl{padding-left:48px !important}.pr-3xl{padding-right:48px !important}.px-3xl{padding-left:48px !important;padding-right:48px !important}.py-3xl{padding-top:48px !important;padding-bottom:48px !important}.p-4xl{padding:64px !important}.pt-4xl{padding-top:64px !important}.pb-4xl{padding-bottom:64px !important}.pl-4xl{padding-left:64px !important}.pr-4xl{padding-right:64px !important}.px-4xl{padding-left:64px !important;padding-right:64px !important}.py-4xl{padding-top:64px !important;padding-bottom:64px !important}.w-25{width:25%}.w-50{width:50%}.w-75{width:75%}.w-100{width:100%}.w-auto{width:auto}.h-25{height:25%}.h-50{height:50%}.h-75{height:75%}.h-100{height:100%}.h-auto{height:auto}.vh-100{height:100vh}.position-relative{position:relative}.position-absolute{position:absolute}.position-fixed{position:fixed}.position-sticky{position:sticky}.bg-primary{background:#0049b4}.bg-secondary{background:#c3dfea}.bg-white{background:#fff}.bg-gray{background:#f8fafc}.bg-light{background:#eff6ff}.border{border:1px solid #e2e8f0}.border-0{border:none !important}.border-top{border-top:1px solid #e2e8f0}.border-bottom{border-bottom:1px solid #e2e8f0}.border-left{border-left:1px solid #e2e8f0}.border-right{border-right:1px solid #e2e8f0}.border-primary{border-color:#0049b4}.rounded-0{border-radius:0}.rounded-sm{border-radius:6px}.rounded{border-radius:8px}.rounded-lg{border-radius:12px}.rounded-xl{border-radius:16px}.rounded-full{border-radius:50px}.rounded-circle{border-radius:50%}.shadow-none{box-shadow:none !important}.shadow-sm{box-shadow:0 2px 4px rgba(0,0,0,.05)}.shadow{box-shadow:0 4px 12px rgba(75,155,255,.1)}.shadow-lg{box-shadow:0 8px 24px rgba(75,155,255,.15)}.shadow-xl{box-shadow:0 16px 40px rgba(75,155,255,.2)}.overflow-hidden{overflow:hidden}.overflow-auto{overflow:auto}.overflow-x-hidden{overflow-x:hidden}.overflow-y-hidden{overflow-y:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.z-0{z-index:0}.z-10{z-index:10}.z-20{z-index:20}.z-30{z-index:30}.z-40{z-index:40}.z-50{z-index:50}@media(max-width: 768px){.sm-hide{display:none !important}}@media(max-width: 1024px){.md-hide{display:none !important}}@media(min-width: 769px){.mobile-only{display:none !important}}@media(max-width: 768px){.desktop-only{display:none !important}}.pc{display:block}@media(max-width: 768px){.pc{display:none !important}}.mobile{display:none}@media(max-width: 768px){.mobile{display:block !important}}.cursor-pointer{cursor:pointer}.cursor-default{cursor:default}.cursor-not-allowed{cursor:not-allowed}.opacity-0{opacity:0}.opacity-25{opacity:.25}.opacity-50{opacity:.5}.opacity-75{opacity:.75}.opacity-100{opacity:1}.transition{transition:all .3s ease}.transition-fast{transition:all .15s ease}.transition-slow{transition:all .5s ease}.transition-none{transition:none}.visually-hidden,.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);white-space:nowrap;border:0}/*# sourceMappingURL=main.min.css.map */ +:root{--font-weight-primary-regular: 400;--font-weight-primary-bold: 700}*{margin:0;padding:0;box-sizing:border-box}html{font-size:16px;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-rendering:optimizeLegibility}body{font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;line-height:1.6;color:#1a1a2e;background-color:#fff;overflow-x:hidden}ul,ol{list-style:none}a{text-decoration:none;color:inherit;transition:all .3s ease}img{max-width:100%;height:auto;display:block}button{font-family:inherit;font-size:inherit;line-height:inherit;cursor:pointer;background:rgba(0,0,0,0);border:none;padding:0}input,textarea,select{font-family:inherit;font-size:inherit;line-height:inherit;border:none;outline:none}table{border-collapse:collapse;border-spacing:0}h1,h2,h3,h4,h5,h6{font-weight:700;line-height:1.2}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}::selection{background-color:rgba(0,73,180,.2);color:#1a1a2e}::-moz-selection{background-color:rgba(0,73,180,.2);color:#1a1a2e}body{font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:16px;font-weight:400;color:#1a1a2e}h1,.h1,h2,.h2,h3,.h3,h4,.h4,h5,.h5,h6,.h6{font-family:"OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-weight:500}[class$=-title],[class*="-title "],[class$=__title],[class*="__title "]{font-family:"OneShinhan","Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-weight:500}h1,.h1{font-size:56px;margin-bottom:24px}@media(max-width: 768px){h1,.h1{font-size:40px}}@media(max-width: 480px){h1,.h1{font-size:32px}}h2,.h2{font-size:48px;margin-bottom:16px}@media(max-width: 768px){h2,.h2{font-size:32px}}@media(max-width: 480px){h2,.h2{font-size:24px}}h3,.h3{font-size:40px;margin-bottom:16px}@media(max-width: 768px){h3,.h3{font-size:24px}}h4,.h4{font-size:32px;margin-bottom:8px}h5,.h5{font-size:24px;margin-bottom:8px}h6,.h6{font-size:20px;margin-bottom:8px}p{margin-bottom:16px;line-height:1.6}p:last-child{margin-bottom:0}.text-primary{color:#0049b4}.text-secondary{color:#c3dfea}.text-dark{color:#1a1a2e}.text-gray{color:#64748b}.text-light{color:#94a3b8}.text-white{color:#fff}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-regular{font-weight:400}.font-medium{font-weight:500}.font-semibold{font-weight:600}.font-bold{font-weight:700}.font-extrabold{font-weight:800}.text-xs{font-size:12px}.text-sm{font-size:14px}.text-base{font-size:16px}.text-lg{font-size:20px}.text-xl{font-size:24px}.lead{font-size:20px;font-weight:400;line-height:1.8;color:#64748b}.caption{font-size:14px;color:#64748b}.code{font-family:"Fira Code","Courier New",monospace;font-size:14px;background:#f8fafc;padding:2px 6px;border-radius:6px}a{color:#0049b4}a:hover{color:#c3dfea}blockquote{padding:16px;margin:16px 0;border-left:4px solid #0049b4;background:#f8fafc;font-style:italic}blockquote p{margin-bottom:0}hr{border:none;border-top:1px solid #e2e8f0;margin:32px 0}@keyframes float{0%,100%{transform:translateY(0) rotate(0deg)}50%{transform:translateY(-30px) rotate(10deg)}}@keyframes bounce{0%,100%{transform:translateY(0)}50%{transform:translateY(-10px)}}@keyframes slideInDown{from{opacity:0;transform:translateY(-30px)}to{opacity:1;transform:translateY(0)}}@keyframes slideInUp{from{opacity:0;transform:translateY(30px)}to{opacity:1;transform:translateY(0)}}@keyframes slideInLeft{from{opacity:0;transform:translateX(-30px)}to{opacity:1;transform:translateX(0)}}@keyframes slideInRight{from{opacity:0;transform:translateX(30px)}to{opacity:1;transform:translateX(0)}}@keyframes fadeIn{from{opacity:0}to{opacity:1}}@keyframes fadeInScale{from{opacity:0;transform:scale(0.9)}to{opacity:1;transform:scale(1)}}@keyframes spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}@keyframes pulse{0%{transform:scale(1)}50%{transform:scale(1.05)}100%{transform:scale(1)}}@keyframes shimmer{0%{transform:translateX(-100%) rotate(45deg)}100%{transform:translateX(100%) rotate(45deg)}}@keyframes wave{0%{transform:rotate(0deg)}10%{transform:rotate(14deg)}20%{transform:rotate(-8deg)}30%{transform:rotate(14deg)}40%{transform:rotate(-4deg)}50%{transform:rotate(10deg)}60%{transform:rotate(0deg)}100%{transform:rotate(0deg)}}@keyframes popIn{0%{opacity:0;transform:scale(0.5)}80%{transform:scale(1.1)}100%{opacity:1;transform:scale(1)}}@keyframes popupFadeIn{from{opacity:0;transform:scale(0.95)}to{opacity:1;transform:scale(1)}}@keyframes ripple{0%{transform:scale(0);opacity:1}100%{transform:scale(4);opacity:0}}@keyframes gradientShift{0%{background-position:0% 50%}50%{background-position:100% 50%}100%{background-position:0% 50%}}.animate-float{animation:float 20s ease-in-out infinite}@keyframes float{0%,100%{transform:translateY(0px)}50%{transform:translateY(-15px)}}.floating-slow{animation:float 6s ease-in-out infinite}.floating-fast{animation:float 4s ease-in-out infinite}.animate-bounce{animation:bounce 2s infinite}.animate-spin{animation:spin 1s linear infinite}@keyframes drawLine{to{stroke-dashoffset:0}}.draw-line{stroke-dasharray:1000;stroke-dashoffset:1000;animation:drawLine 3s ease-out forwards}.pulsing{transform-origin:400px 110px;transform-box:fill-box;animation:pulseLine 2s ease-in-out infinite}@keyframes pulseLine{0%,100%{transform:translate(0, 0) scale(1);opacity:.9}50%{transform:translateX(75px) scale(1.1);opacity:.7}}@keyframes pulse{0%,100%{transform:translate(0, 0) scale(1);opacity:.9}50%{transform:translateX(75px) scale(1.1);opacity:.7}}.animate-pulse{animation:pulse 2s ease-in-out infinite}.animate-fade-in{animation:fadeIn .6s ease-out}.animate-slide-up{animation:slideInUp .8s ease-out}.animate-slide-down{animation:slideInDown .6s ease-out}.animate-pop{animation:popIn .5s cubic-bezier(0.68, -0.55, 0.265, 1.55)}.delay-1{animation-delay:.1s}.delay-2{animation-delay:.2s}.delay-3{animation-delay:.3s}.delay-4{animation-delay:.4s}.delay-5{animation-delay:.5s}.duration-fast{animation-duration:.3s}.duration-normal{animation-duration:.6s}.duration-slow{animation-duration:1s}.duration-slower{animation-duration:2s}.hover-grow{transition:transform .3s ease}.hover-grow:hover{transform:scale(1.05)}.hover-lift{transition:transform .3s ease,box-shadow .3s ease}.hover-lift:hover{transform:translateY(-5px);box-shadow:0 8px 24px rgba(75,155,255,.15)}.hover-rotate{transition:transform .3s ease}.hover-rotate:hover{transform:rotate(5deg)}.hover-shine{position:relative;overflow:hidden}.hover-shine::before{content:"";position:absolute;top:-50%;left:-50%;width:200%;height:200%;transform:rotate(45deg);transition:transform .6s ease;transform:translateX(-200%)}.hover-shine:hover::before{transform:translateX(200%) rotate(45deg)}@font-face{font-family:"OneShinhan";font-weight:300;font-style:normal;font-display:swap;src:local("OneShinhan Light"),local("OneShinhan-Light"),url("/font/shinhan/OneShinhanLight.woff") format("woff")}@font-face{font-family:"OneShinhan";font-weight:400;font-style:normal;font-display:swap;src:local("OneShinhan Medium"),local("OneShinhan-Medium"),url("/font/shinhan/OneShinhanMedium.woff") format("woff")}@font-face{font-family:"OneShinhan";font-weight:500;font-style:normal;font-display:swap;src:local("OneShinhan Medium"),local("OneShinhan-Medium"),url("/font/shinhan/OneShinhanMedium.woff") format("woff")}@font-face{font-family:"OneShinhan";font-weight:700;font-style:normal;font-display:swap;src:local("OneShinhan Bold"),local("OneShinhan-Bold"),url("/font/shinhan/OneShinhanBold.woff") format("woff")}@font-face{font-family:"Spoqa Han Sans Neo";font-weight:400;font-style:normal;font-display:swap;src:local("Spoqa Han Sans Neo Regular"),url("/font/spoqa-hans/SpoqaHanSansNeo-Regular.woff2") format("woff2"),url("/font/spoqa-hans/SpoqaHanSansNeo-Regular.woff") format("woff"),url("/font/spoqa-hans/SpoqaHanSansNeo-Regular.ttf") format("truetype")}@font-face{font-family:"Spoqa Han Sans Neo";font-weight:700;font-style:normal;font-display:swap;src:local("Spoqa Han Sans Neo Bold"),url("/font/spoqa-hans/SpoqaHanSansNeo-Bold.woff2") format("woff2"),url("/font/spoqa-hans/SpoqaHanSansNeo-Bold.woff") format("woff"),url("/font/spoqa-hans/SpoqaHanSansNeo-Bold.ttf") format("truetype")}@font-face{font-family:"SpoqaHanSans";font-weight:400;font-style:normal;font-display:swap;src:local("Spoqa Han Sans Regular"),url("/font/spoqa-hans/SpoqaHanSansRegular.woff2") format("woff2"),url("/font/spoqa-hans/SpoqaHanSansRegular.woff") format("woff"),url("/font/spoqa-hans/SpoqaHanSansRegular.ttf") format("truetype")}@font-face{font-family:"SpoqaHanSans";font-weight:700;font-style:normal;font-display:swap;src:local("Spoqa Han Sans Bold"),url("/font/spoqa-hans/SpoqaHanSansBold.woff2") format("woff2"),url("/font/spoqa-hans/SpoqaHanSansBold.woff") format("woff"),url("/font/spoqa-hans/SpoqaHanSansBold.ttf") format("truetype")}@font-face{font-family:"Noto Sans KR";font-weight:100 900;font-style:normal;font-display:swap;src:local("Noto Sans KR"),local("NotoSansKR"),local("Noto Sans CJK KR"),url("/font/noto-sans/NotoSansKR-subset.woff2") format("woff2")}:root{--primary-color: #0049b4;--secondary-color: #008ae2;--primary-blue: #4B9BFF;--secondary-blue: #2E7FF7;--accent-cyan: #00D4FF;--accent-yellow: #FFD93D;--accent-orange: #FF6B6B;--accent-green: #6BCF7F;--accent-purple: #A78BFA;--accent-color: #efdcb2;--accent-light: #E9F9FF;--text-dark: #1A1A2E;--text-gray: #64748B;--text-light: #94A3B8;--white: #FFFFFF;--gray-bg: #F8FAFC;--light-bg: #EFF6FF;--border-gray: #E2E8F0;--shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.05);--shadow-md: 0 4px 12px rgba(75, 155, 255, 0.1);--shadow-lg: 0 8px 24px rgba(75, 155, 255, 0.15);--shadow-xl: 0 16px 40px rgba(75, 155, 255, 0.2);--shadow-hover: 0 30px 60px -10px rgba(0, 73, 180, 0.2);--border-color: #E2E8F0;--transition-smooth: all 0.4s cubic-bezier(0.16, 1, 0.3, 1)}.blind{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);white-space:nowrap;border:0}.global-header{position:fixed;top:0;left:0;width:100%;height:80px;background:hsla(0,0%,100%,.85);backdrop-filter:blur(15px);border-bottom:1px solid rgba(0,73,180,.08);z-index:1000;display:flex;align-items:center;transition:var(--transition-smooth)}.global-header.index-header{background-color:#fff}.global-header .container{height:100%;padding:0;width:100%}.global-header .container .header-content{height:100%}@media(max-width: 1024px){.global-header{height:clamp(44px,11.73vw,60px);background:hsla(0,0%,100%,.85);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border-bottom:none}.global-header .container{padding:0;max-width:100%;background-color:#fefefe}.global-header .container .header-content{padding:0 clamp(16px,4.27vw,24px) 0 clamp(20px,5.33vw,28px);height:clamp(44px,11.73vw,60px);margin-top:0}}.header-content{width:100%;max-width:1280px;margin:0 auto;padding:0 24px;display:flex;align-items:center;justify-content:space-between}.header-left{display:flex;align-items:center;flex-shrink:0}.header-left .logo{height:32px;align-items:flex-end;flex-wrap:nowrap}.header-left .logo img{flex-shrink:0}.header-left .logo .mobile-logo-link{align-items:flex-end;flex-shrink:0}.header-left .logo .mobile-logo-text{align-items:flex-end;line-height:1;padding-bottom:2px;white-space:nowrap;flex-shrink:0}.logo-wrapper{display:flex;align-items:center;gap:12px;font-size:24px}.logo-wrapper .logo-text-bold{font-weight:700;color:var(--primary-color);letter-spacing:-0.5px}.logo-wrapper .logo-text-thin{font-weight:300;color:var(--secondary-color);margin-left:4px}.logo{display:flex;height:32px;vertical-align:middle;z-index:10}.logo a{display:flex;align-items:center;gap:12px;height:100%;text-decoration:none}.logo img{height:32px;width:114px;display:block}.logo-text{font-size:22px;font-weight:600;line-height:32px;color:var(--text-dark);text-decoration:none;display:inline-flex;align-items:center;height:32px;padding-top:4px;transition:all .3s ease}.logo-text:hover{color:var(--primary-blue)}.logo-text:visited{color:var(--text-dark)}.logo-link,.drawer-logo-link{display:inline-block;text-decoration:none;transition:transform .3s ease}.logo-link:hover,.drawer-logo-link:hover{transform:scale(1.05)}.logo-link img,.drawer-logo-link img{display:block}.header-right{display:flex;align-items:center;gap:16px}.search-btn{width:40px;height:40px;background:var(--gray-bg);border:none;border-radius:50%;cursor:pointer;font-size:16px;color:var(--text-gray);transition:all .3s ease}.search-btn:hover{background:var(--light-bg);color:var(--primary-blue);transform:scale(1.1)}.desktop-header{display:flex;align-items:center;justify-content:space-between;width:100%}@media(max-width: 1024px){.desktop-header{display:none}}.mobile-header{display:none;align-items:center;justify-content:space-between;width:100%}@media(max-width: 1024px){.mobile-header{display:flex}}.mobile-left{display:flex;align-items:center;gap:clamp(8px,2.13vw,12px)}.mobile-left .mobile-logo-link{display:flex;align-items:center}.mobile-left .mobile-logo{height:clamp(16px,4.27vw,24px);width:auto;object-fit:contain}.mobile-left .mobile-logo-text{font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:clamp(14px,3.73vw,18px);font-weight:700;color:#212529;text-decoration:none;white-space:nowrap}.mobile-left .mobile-logo-text:hover{color:#0049b4}.mobile-center{display:none}.mobile-center .mobile-title{font-size:18px;font-weight:700;color:var(--text-dark);margin:0}.mobile-right{display:flex;align-items:center}.mobile-right .mobile-menu-btn{width:clamp(24px,6.4vw,32px);height:clamp(24px,6.4vw,32px);background:rgba(0,0,0,0);border:none;cursor:pointer;display:flex;flex-direction:column;justify-content:center;align-items:center;gap:clamp(3px,.8vw,5px);padding:clamp(4px,1.07vw,6px);border-radius:4px;transition:background-color .3s ease}.mobile-right .mobile-menu-btn:hover{background:rgba(0,0,0,.05)}.mobile-right .mobile-menu-btn .hamburger-line{width:clamp(16px,4.27vw,22px);height:2px;background:#515961;transition:all .3s ease;border-radius:1px}.mobile-right .mobile-menu-btn.active .hamburger-line:nth-child(1){transform:rotate(45deg) translateY(5px)}.mobile-right .mobile-menu-btn.active .hamburger-line:nth-child(2){opacity:0}.mobile-right .mobile-menu-btn.active .hamburger-line:nth-child(3){transform:rotate(-45deg) translateY(-5px)}.mobile-drawer{position:fixed;top:0;left:0;right:0;bottom:0;z-index:9999;visibility:hidden;opacity:0;transition:all .3s ease}.mobile-drawer.active{visibility:visible;opacity:1}.mobile-drawer.active .drawer-content{transform:translateY(0)}.mobile-drawer .drawer-overlay{display:none}.mobile-drawer .drawer-content{position:absolute;top:0;left:0;right:0;bottom:0;width:100%;height:100%;background:#fff;transform:translateY(100%);transition:transform .3s ease;display:flex;flex-direction:column;overflow-y:auto}.mobile-drawer .drawer-header{display:flex;align-items:center;justify-content:space-between;height:44px;min-height:44px;padding:0 16px 0 20px;background:#fff}.mobile-drawer .drawer-header .drawer-header-left{display:flex;align-items:center;gap:clamp(8px,2.13vw,12px)}.mobile-drawer .drawer-header .drawer-header-left .drawer-logo-link{display:flex;align-items:center}.mobile-drawer .drawer-header .drawer-header-left .drawer-logo{height:clamp(16px,4.27vw,24px);width:auto;object-fit:contain}.mobile-drawer .drawer-header .drawer-header-left .drawer-logo-text{font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:clamp(14px,3.73vw,18px);font-weight:700;color:#212529;text-decoration:none;white-space:nowrap}.mobile-drawer .drawer-header .drawer-header-right{display:flex;align-items:center;gap:8px}.mobile-drawer .drawer-header .drawer-home-btn{width:24px;height:24px;display:flex;align-items:center;justify-content:center}.mobile-drawer .drawer-header .drawer-home-btn svg{width:24px;height:24px}.mobile-drawer .drawer-header .drawer-home-btn:hover{opacity:.7}.mobile-drawer .drawer-header .drawer-close{width:24px;height:24px;background:rgba(0,0,0,0);border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;padding:0}.mobile-drawer .drawer-header .drawer-close svg{width:24px;height:24px}.mobile-drawer .drawer-header .drawer-close:hover{opacity:.7}.mobile-drawer .drawer-welcome{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:214px;padding:40px 20px;background:#ebfbff;border-bottom:1px solid #dadada}.mobile-drawer .drawer-welcome .welcome-user-icon{margin-bottom:12px}.mobile-drawer .drawer-welcome .welcome-user-icon img{width:56px;height:56px}.mobile-drawer .drawer-welcome .welcome-login-prompt{font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:16px;font-weight:700;color:#212529;line-height:26px;margin:0 0 8px 0;text-align:center}.mobile-drawer .drawer-welcome .welcome-text{font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:14px;font-weight:400;color:#212529;line-height:26px;margin:0 0 24px 0;text-align:center}.mobile-drawer .drawer-welcome .welcome-buttons{display:flex;gap:21px;width:100%;max-width:309px}.mobile-drawer .drawer-welcome .btn-drawer-signup{flex:1;display:flex;align-items:center;justify-content:center;height:40px;background:#e5e7eb;color:#5f666c;font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:14px;font-weight:700;border-radius:8px;text-decoration:none;transition:all .3s ease}.mobile-drawer .drawer-welcome .btn-drawer-signup:hover{background:#d1d5db}.mobile-drawer .drawer-welcome .btn-drawer-login{flex:1;display:flex;align-items:center;justify-content:center;height:40px;background:#0049b4;color:#fff;font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:14px;font-weight:700;border-radius:8px;text-decoration:none;transition:all .3s ease}.mobile-drawer .drawer-welcome .btn-drawer-login:hover{background:rgb(0,65.7,162)}.mobile-drawer .drawer-welcome.authenticated{flex-direction:row;min-height:138px;padding:40px 54px;gap:16px;justify-content:flex-start}.mobile-drawer .drawer-welcome.authenticated .welcome-user-icon{margin-bottom:0;flex-shrink:0}.mobile-drawer .drawer-welcome.authenticated .welcome-user-info{display:flex;flex-direction:column;gap:8px}.mobile-drawer .drawer-welcome.authenticated .welcome-user-name{font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:16px;font-weight:700;color:#212529;line-height:22px;margin:0}.mobile-drawer .drawer-welcome.authenticated .welcome-text{margin:0;text-align:left}.mobile-drawer .drawer-footer{display:flex;justify-content:flex-end;padding:20px;background:#fff;border-top:1px solid #dadada}.mobile-drawer .drawer-footer .drawer-logout-btn{display:flex;align-items:center;gap:4px;padding:10px;background:rgba(0,0,0,0);border:none;cursor:pointer;text-decoration:none}.mobile-drawer .drawer-footer .drawer-logout-btn svg{width:16px;height:16px}.mobile-drawer .drawer-footer .drawer-logout-btn span{font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:14px;font-weight:500;color:#0049b4;line-height:18px}.mobile-drawer .drawer-footer .drawer-logout-btn:hover{opacity:.8}.mobile-drawer .drawer-nav{flex:1;padding:0 20px;background:#fff}.mobile-drawer .drawer-nav .drawer-menu-list{list-style:none;margin:0;padding:0}.mobile-drawer .drawer-nav .drawer-menu-item{border-bottom:1px solid #dadada}.mobile-drawer .drawer-nav .drawer-menu-item .drawer-menu-btn{display:flex;align-items:center;justify-content:space-between;width:100%;height:56px;padding:0 8px;background:rgba(0,0,0,0);border:none;cursor:pointer;font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:16px;font-weight:500;color:#212529;text-align:left}.mobile-drawer .drawer-nav .drawer-menu-item .drawer-menu-btn .accordion-icon{transition:transform .3s ease}.mobile-drawer .drawer-nav .drawer-menu-item .drawer-submenu{display:none;list-style:none;margin:0;padding:12px 0;background:#fafafa}.mobile-drawer .drawer-nav .drawer-menu-item .drawer-submenu li a{display:block;padding:8px 16px 8px 57px;font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:14px;font-weight:500;color:#212529;text-decoration:none;line-height:28px;transition:color .3s ease}.mobile-drawer .drawer-nav .drawer-menu-item .drawer-submenu li a:hover{color:#0049b4}.mobile-drawer .drawer-nav .drawer-menu-item .drawer-submenu li:not(:first-child) a{font-weight:400;color:#515151}.mobile-drawer .drawer-nav .drawer-menu-item.open .drawer-menu-btn{font-weight:700}.mobile-drawer .drawer-nav .drawer-menu-item.open .drawer-menu-btn .accordion-icon{transform:rotate(180deg)}.mobile-drawer .drawer-nav .drawer-menu-item.open .drawer-submenu{display:block}.btn-mobilemenu{position:absolute;right:16px;top:50%;transform:translateY(-50%);width:40px;height:40px;background:rgba(0,0,0,0);border:none;cursor:pointer;padding:8px;z-index:1051}.btn-mobilemenu span{display:block;width:24px;height:2px;background:#1a1a2e;position:relative;transition:all all .15s ease;text-indent:-9999px}.btn-mobilemenu span::before,.btn-mobilemenu span::after{content:"";position:absolute;left:0;width:24px;height:2px;background:#1a1a2e;transition:all all .15s ease}.btn-mobilemenu span::before{top:-8px}.btn-mobilemenu span::after{top:8px}.btn-mobilemenu.on span{background:rgba(0,0,0,0)}.btn-mobilemenu.on span::before{top:0;transform:rotate(45deg)}.btn-mobilemenu.on span::after{top:0;transform:rotate(-45deg)}.main-nav.m-nav{position:fixed;top:0;left:0;right:0;bottom:0;background:#fff;z-index:1050;right:-100%;transition:none;overflow-y:auto;display:flex;flex-direction:column}.main-nav.m-nav .nav-header{padding:24px;background:#f8fafc;border-bottom:1px solid #e2e8f0}.main-nav.m-nav .nav-list{list-style:none;margin:0;padding:0;flex-grow:1}.main-nav.m-nav .nav-list .nav-item{border-bottom:1px solid #e2e8f0}.main-nav.m-nav .nav-list .nav-item>a{display:flex;align-items:center;padding:24px;color:#1a1a2e;font-size:18px;font-weight:500;text-decoration:none;position:relative}.main-nav.m-nav .nav-list .nav-item.has-submenu>a::after{content:"";position:absolute;right:24px;top:50%;width:8px;height:8px;border-right:2px solid #64748b;border-bottom:2px solid #64748b;transform:translateY(-50%) rotate(45deg);transition:transform all .15s ease}.main-nav.m-nav .nav-list .nav-item.expanded>a::after{transform:translateY(-25%) rotate(-135deg)}.main-nav.m-nav .nav-list .nav-item .sub-menu{display:none;list-style:none;padding:0;margin:0;background:#f8fafc}.main-nav.m-nav .nav-list .nav-item .sub-menu a{display:block;padding:16px 24px 16px 48px;color:#64748b;font-size:14px;text-decoration:none}.main-nav.m-nav .nav-list .nav-item .sub-menu a:hover{background:#fff;color:#0049b4}.main-nav.m-nav .nav-list .nav-item.expanded .sub-menu{display:block}.main-nav.m-nav .info_wrap{padding:32px 24px;background:#fff;border-top:1px solid #e2e8f0;margin-top:auto}.nav-menu{display:flex;list-style:none;gap:24px;margin:0;padding:0}.nav-menu>li{position:relative}.nav-menu>li.submenu-open>.nav-link{background:var(--light-bg);color:var(--primary-blue);border-radius:9px}.nav-menu>li.submenu-open .sub-menu{visibility:visible;opacity:1;transform:translateY(0)}.nav-menu>li .sub-menu{position:absolute;top:calc(100% + 17px);left:0;min-width:200px;background:var(--white);border:1px solid var(--border-gray);border-radius:12px;box-shadow:var(--shadow-xl);padding:8px 0;visibility:hidden;opacity:0;transform:translateY(-10px);transition:all .3s ease;z-index:100;list-style:none;margin:0}.nav-menu>li .sub-menu li{margin:0}.nav-menu>li .sub-menu li a{display:block;padding:8px 24px;color:var(--text-dark);text-decoration:none;font-size:14px;font-weight:500;transition:all .3s ease}.nav-menu>li .sub-menu li a:hover{background:var(--light-bg);color:var(--primary-blue)}.nav-link{font-size:16px;font-weight:500;color:var(--text-gray);padding:12px;position:relative;transition:var(--transition-smooth);white-space:nowrap}.nav-link:hover{color:var(--primary-blue)}.nav-icon{font-size:18px}.signup-btn{display:flex;align-items:center;gap:8px;text-decoration:none;color:var(--white);padding:12px 24px;border-radius:50px;font-weight:600;transition:all .3s ease;box-shadow:var(--shadow-md)}.signup-btn:hover{transform:translateY(-2px);box-shadow:var(--shadow-lg)}@media(max-width: 1024px){.global-header{height:clamp(44px,11.73vw,60px);background:hsla(0,0%,100%,.85);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border-bottom:none}.global-header .container{padding:0;max-width:100%;background-color:#fefefe}.global-header .container .header-content{padding:0 clamp(16px,4.27vw,24px) 0 clamp(20px,5.33vw,28px);height:clamp(44px,11.73vw,60px);margin-top:0}}.header-user-info{display:flex;align-items:center;gap:6px;padding:4px 6px;border-radius:6px}.header-user-info .user-identity{display:flex;align-items:center;gap:2px}.header-user-info .user-identity .user-icon{width:16px;height:18px;display:block}.header-user-info .user-identity .user-name{font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:16px;font-weight:400;color:#515961;white-space:nowrap}.header-user-info .divider{font-size:8px;color:var(--text-gray);line-height:1}.header-user-info .header-link{font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:16px;font-weight:400;color:#515961;text-decoration:none;white-space:nowrap;background:none;border:none;padding:0;cursor:pointer;transition:color .3s ease}.header-user-info .header-link:hover{color:var(--primary-blue)}.header-user-info .mypage-dropdown{position:relative}.header-user-info .mypage-dropdown .mypage-toggle{display:flex;align-items:center;gap:4px}.header-user-info .mypage-dropdown .mypage-dropdown-menu{position:absolute;top:calc(100% + 8px);right:0;min-width:200px;background:var(--white);border:1px solid var(--border-gray);border-radius:12px;box-shadow:var(--shadow-xl);padding:8px 0;visibility:hidden;opacity:0;transform:translateY(-10px);transition:all .3s ease;z-index:100}.header-user-info .mypage-dropdown .mypage-dropdown-menu .mypage-menu-list{list-style:none;margin:0;padding:0}.header-user-info .mypage-dropdown .mypage-dropdown-menu .mypage-menu-list li{margin:0}.header-user-info .mypage-dropdown .mypage-dropdown-menu .mypage-menu-list li a{display:flex;align-items:center;gap:8px;padding:8px 24px;color:var(--text-dark);text-decoration:none;font-size:14px;font-weight:500;transition:all .3s ease}.header-user-info .mypage-dropdown .mypage-dropdown-menu .mypage-menu-list li a i{font-size:14px;width:16px;text-align:center}.header-user-info .mypage-dropdown .mypage-dropdown-menu .mypage-menu-list li a:hover{background:var(--light-bg);color:var(--primary-blue)}.header-user-info .mypage-dropdown.active .mypage-dropdown-menu{visibility:visible;opacity:1;transform:translateY(0)}@media(max-width: 1024px){.header-user-info{display:none}}.global-footer{background-color:#0f172a;color:#64748b;padding:60px 0px}.global-footer .container{max-width:1200px;margin:0 auto;padding:0 20px}.footer-content{display:flex;justify-content:space-between;align-items:center;gap:40px}@media(max-width: 1024px){.footer-content{flex-direction:column;align-items:flex-start;gap:32px;padding:32px 0}}.footer-left{display:flex;flex-direction:column;gap:16px}.footer-left .footer-logo{width:114px;height:auto;object-fit:contain}.footer-left .footer-links{display:flex;align-items:center;gap:8px}.footer-left .footer-links .footer-link{font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:16px;font-weight:400;color:#9ca3af;text-decoration:none;transition:color .3s ease}.footer-left .footer-links .footer-link:hover{color:#fff;text-decoration:underline}.footer-left .footer-links .footer-separator{color:#9ca3af;font-size:16px;user-select:none}.footer-left .footer-copyright{font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:14px;font-weight:400;color:#9ca3af;margin:0;line-height:1.5}.footer-right{display:flex;flex-direction:column;gap:16px;align-items:flex-end}@media(max-width: 1024px){.footer-right{align-items:flex-start;width:100%}}.footer-right .footer-related-sites .related-sites-select{width:240px;height:44px;padding:0 16px;font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:16px;font-weight:400;color:#9ca3af;background:#1e293b;border:1px solid #475569;border-radius:6px;cursor:pointer;transition:all .3s ease;appearance:none;background-image:url("data:image/svg+xml,%3Csvg width='12' height='8' viewBox='0 0 12 8' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1L6 6L11 1' stroke='%239CA3AF' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 16px center;padding-right:40px}.footer-right .footer-related-sites .related-sites-select:hover{border-color:#fff}.footer-right .footer-related-sites .related-sites-select:focus{outline:none;border-color:#fff}@media(max-width: 1024px){.footer-right .footer-related-sites .related-sites-select{width:100%}}.footer-right .footer-contact{font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:18px;font-weight:700;color:#9ca3af;margin:0;line-height:1.2}@media(max-width: 1024px){.footer-right .footer-contact{font-size:16px}}@media(max-width: 768px){.global-footer .container{padding:0 20px}.footer-content{flex-direction:column;align-items:flex-start;padding:24px 0;gap:12px;min-height:auto}.footer-left{gap:8px;width:100%;order:1}.footer-left .footer-logo{width:72px;order:-2}.footer-left .footer-links{gap:9px;order:1}.footer-left .footer-links .footer-link{font-size:11px;color:#212529}.footer-left .footer-links .footer-separator{font-size:11px;color:#212529}.footer-left .footer-copyright{font-size:10px;color:#5f666c;order:3}.footer-right{width:100%;align-items:flex-start;gap:8px;order:0}.footer-right .footer-related-sites{width:100%;order:-1}.footer-right .footer-related-sites .related-sites-select{width:100%;height:44px;font-size:12px;border-radius:8px;border-color:#dadada;color:#8c959f}.footer-right .footer-contact{font-size:11px;font-weight:400;color:#212529;order:2}}@media(max-width: 768px){.footer-content{display:flex;flex-direction:column}.footer-content .footer-left{display:contents}.footer-content .footer-left .footer-logo{order:1}.footer-content .footer-left .footer-links{order:3}.footer-content .footer-left .footer-copyright{order:5}.footer-content .footer-right{display:contents}.footer-content .footer-right .footer-related-sites{order:2;width:100%}.footer-content .footer-right .footer-contact{order:4}}.grid{display:grid;gap:24px}.grid-cols-1{grid-template-columns:1fr}.grid-cols-2{grid-template-columns:repeat(2, 1fr)}.grid-cols-3{grid-template-columns:repeat(3, 1fr)}.grid-cols-4{grid-template-columns:repeat(4, 1fr)}.grid-cols-5{grid-template-columns:repeat(5, 1fr)}.grid-cols-6{grid-template-columns:repeat(6, 1fr)}.grid-cols-12{grid-template-columns:repeat(12, 1fr)}.grid-auto{grid-template-columns:repeat(auto-fit, minmax(280px, 1fr))}.grid-auto-sm{grid-template-columns:repeat(auto-fit, minmax(200px, 1fr))}.grid-auto-lg{grid-template-columns:repeat(auto-fit, minmax(350px, 1fr))}.grid.gap-0{gap:0}.grid.gap-sm{gap:8px}.grid.gap-md{gap:16px}.grid.gap-lg{gap:24px}.grid.gap-xl{gap:32px}@media(max-width: 1024px){.grid-md-cols-1{grid-template-columns:1fr}.grid-md-cols-2{grid-template-columns:repeat(2, 1fr)}}@media(max-width: 768px){.grid-sm-cols-1{grid-template-columns:1fr}.grid-sm-cols-2{grid-template-columns:repeat(2, 1fr)}}.col-span-1{grid-column:span 1}.col-span-2{grid-column:span 2}.col-span-3{grid-column:span 3}.col-span-4{grid-column:span 4}.col-span-5{grid-column:span 5}.col-span-6{grid-column:span 6}.col-span-full{grid-column:1/-1}.row-span-1{grid-row:span 1}.row-span-2{grid-row:span 2}.row-span-3{grid-row:span 3}.api-grid{display:grid;grid-template-columns:repeat(auto-fit, minmax(280px, 1fr));gap:24px}@media(max-width: 1024px){.api-grid{grid-template-columns:repeat(2, 1fr)}}@media(max-width: 768px){.api-grid{grid-template-columns:1fr}}.experience-grid{display:grid;grid-template-columns:repeat(auto-fit, minmax(280px, 1fr));gap:24px}@media(max-width: 768px){.experience-grid{grid-template-columns:1fr}}.partners-grid{display:grid;grid-template-columns:repeat(auto-fit, minmax(200px, 1fr));gap:40px;align-items:center}@media(max-width: 768px){.partners-grid{grid-template-columns:repeat(2, 1fr)}}.feature-grid{display:grid;grid-template-columns:repeat(2, 1fr);gap:32px}@media(max-width: 768px){.feature-grid{grid-template-columns:1fr}}.demo-wrapper{display:grid;grid-template-columns:1fr 1fr;gap:40px;align-items:start}@media(max-width: 1024px){.demo-wrapper{grid-template-columns:1fr}}.footer-grid{display:grid;grid-template-columns:2fr 3fr;gap:80px}@media(max-width: 1024px){.footer-grid{grid-template-columns:1fr;gap:40px}}.footer-grid .footer-links{display:grid;grid-template-columns:repeat(4, 1fr);gap:40px}@media(max-width: 1024px){.footer-grid .footer-links{grid-template-columns:repeat(2, 1fr)}}@media(max-width: 768px){.footer-grid .footer-links{grid-template-columns:1fr;text-align:center}}.pricing-grid{display:grid;grid-template-columns:repeat(3, 1fr);gap:24px;align-items:center}@media(max-width: 1024px){.pricing-grid{grid-template-columns:1fr;gap:32px}}.testimonial-grid{display:grid;grid-template-columns:repeat(auto-fit, minmax(350px, 1fr));gap:24px}@media(max-width: 768px){.testimonial-grid{grid-template-columns:1fr}}.stats-grid{display:grid;grid-template-columns:repeat(4, 1fr);gap:32px;text-align:center}@media(max-width: 1024px){.stats-grid{grid-template-columns:repeat(2, 1fr)}}@media(max-width: 768px){.stats-grid{grid-template-columns:1fr}}.two-column{display:grid;grid-template-columns:1fr 1fr;gap:48px;align-items:center}@media(max-width: 1024px){.two-column{grid-template-columns:1fr;gap:32px}}.two-column.reverse{direction:rtl}.two-column.reverse>*{direction:ltr}@media(max-width: 1024px){.two-column.reverse{direction:ltr}}.three-column{display:grid;grid-template-columns:repeat(3, 1fr);gap:32px}@media(max-width: 1024px){.three-column{grid-template-columns:1fr}}.container{max-width:1280px;margin:0 auto;padding:0 20px}@media(max-width: 768px){.container{padding:0 16px}}.container-fluid{max-width:100%;padding:0 24px}.container-narrow{max-width:960px}.container-wide{max-width:1440px}.section{padding:100px 0}@media(max-width: 768px){.section{padding:64px 0}}.section-sm{padding:48px 0}.section-lg{padding:150px 0}.section-no-top{padding-top:0}.section-no-bottom{padding-bottom:0}.section-bg-gray{background:#f8fafc}.section-bg-light{background:#eff6ff}.section-bg-primary{color:#fff}.section-bg-dark{background:#1a1a2e;color:#fff}.section-header{text-align:center;margin-bottom:64px}@media(max-width: 768px){.section-header{margin-bottom:48px}}.section-header .section-badge{display:inline-flex;align-items:center;gap:4px;padding:4px 16px;background:#eff6ff;color:#0049b4;border-radius:50px;font-size:14px;font-weight:600;margin-bottom:16px}.section-header .section-title{font-size:40px;font-weight:700;margin-bottom:16px;color:#1a1a2e}@media(max-width: 768px){.section-header .section-title{font-size:32px}}.section-header .section-subtitle{font-size:18px;color:#64748b;line-height:1.6;max-width:600px;margin:0 auto}@media(max-width: 768px){.section-header .section-subtitle{font-size:16px}}.content-wrapper{position:relative;min-height:100vh;display:flex;flex-direction:column}.content-wrapper .main-content{flex:1;padding-top:80px}.page-wrapper{position:relative;overflow:hidden}.inner-content{position:relative;z-index:1}.btn{display:inline-flex;align-items:center;gap:8px;padding:16px 32px;border-radius:12px;font-weight:600;font-size:16px;text-decoration:none;transition:all .3s ease;cursor:pointer;border:none}.btn:hover{transform:translateY(-3px)}.btn{position:relative;overflow:hidden}.btn-sm{padding:8px 16px;font-size:14px}.btn-md{padding:16px 24px;font-size:16px}.btn-lg{padding:16px 32px;font-size:18px}.btn-xl{padding:24px 40px;font-size:20px}.btn-primary{background:#0049b4;color:#fff;box-shadow:0 4px 12px rgba(75,155,255,.1)}.btn-primary:hover{transform:translateY(-3px);box-shadow:0 16px 40px rgba(75,155,255,.2)}.btn-primary:active{transform:translateY(-1px)}.btn-secondary{background:#e5e7eb;color:#5f666c}.btn-secondary:hover{background:#eff6ff;transform:translateY(-3px)}.btn-accent{color:#fff;box-shadow:0 4px 12px rgba(75,155,255,.1)}.btn-accent:hover{transform:translateY(-3px);box-shadow:0 16px 40px rgba(75,155,255,.2)}.btn-warm{color:#fff;box-shadow:0 4px 12px rgba(75,155,255,.1)}.btn-warm:hover{transform:translateY(-3px);box-shadow:0 16px 40px rgba(75,155,255,.2)}.btn-success{background:#6bcf7f;color:#fff}.btn-success:hover{background:hsl(132,51.0204081633%,55.4117647059%);transform:translateY(-3px)}.btn-danger{background:#ff6b6b;color:#fff}.btn-danger:hover{background:rgb(255,70.8,70.8);transform:translateY(-3px)}.btn-ghost{background:rgba(0,0,0,0);color:#0049b4;border:2px solid rgba(0,0,0,0)}.btn-ghost:hover{background:rgba(0,73,180,.1);border-color:#0049b4}.btn-outline{background:rgba(0,0,0,0);color:#1a1a2e;border:2px solid #e2e8f0}.btn-outline:hover{border-color:#0049b4;color:#0049b4;transform:translateY(-3px)}.btn:disabled,.btn.disabled{opacity:.5;cursor:not-allowed;pointer-events:none}.btn.loading{color:rgba(0,0,0,0);pointer-events:none}.btn.loading::after{content:"";position:absolute;width:16px;height:16px;position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);border:2px solid #fff;border-radius:50%;border-top-color:rgba(0,0,0,0);animation:spin .6s linear infinite}.btn-icon{display:inline-flex;align-items:center;justify-content:center;width:44px;height:44px;padding:0;border-radius:50%}.btn-icon.btn-sm{width:32px;height:32px}.btn-icon.btn-lg{width:56px;height:56px}.btn-block{width:100%;justify-content:center}.btn-group{display:inline-flex;gap:-1px}.btn-group .btn{border-radius:0}.btn-group .btn:first-child{border-top-left-radius:12px;border-bottom-left-radius:12px}.btn-group .btn:last-child{border-top-right-radius:12px;border-bottom-right-radius:12px}.fab{position:fixed;bottom:32px;right:32px;width:60px;height:60px;border:none;border-radius:50%;color:#fff;font-size:24px;cursor:pointer;box-shadow:0 8px 24px rgba(75,155,255,.15);transition:all .3s ease;z-index:300;display:flex;justify-content:center;align-items:center}.fab:hover{transform:scale(1.1);box-shadow:0 16px 40px rgba(75,155,255,.2)}.fab:hover .fab-label{opacity:1;transform:translateX(-10px)}.fab .fab-label{position:absolute;right:70px;top:50%;transform:translateY(-50%);background:#1a1a2e;color:#fff;padding:8px 16px;border-radius:8px;font-size:14px;white-space:nowrap;opacity:0;transition:all .3s ease;pointer-events:none}.btn-cta-primary{display:inline-flex;align-items:center;gap:8px;padding:16px 32px;border-radius:12px;font-weight:600;font-size:16px;text-decoration:none;transition:all .3s ease;cursor:pointer;border:none}.btn-cta-primary:hover{transform:translateY(-3px)}.btn-cta-primary{background:#fff;color:#0049b4;padding:16px 40px;font-size:18px;box-shadow:0 8px 24px rgba(75,155,255,.15)}.btn-cta-primary:hover{transform:translateY(-3px);box-shadow:0 16px 40px rgba(75,155,255,.2)}.btn-cta-secondary{display:inline-flex;align-items:center;gap:8px;padding:16px 32px;border-radius:12px;font-weight:600;font-size:16px;text-decoration:none;transition:all .3s ease;cursor:pointer;border:none}.btn-cta-secondary:hover{transform:translateY(-3px)}.btn-cta-secondary{background:rgba(0,0,0,0);color:#fff;border:2px solid #fff;padding:16px 40px;font-size:18px}.btn-cta-secondary:hover{background:hsla(0,0%,100%,.1);transform:translateY(-3px)}.action-btn,.action-btn-new,.action-btn-primary,.action-btn-secondary,.action-btn-edit,.action-btn-delete{padding:16px 40px;border-radius:8px;font-size:16px;font-weight:500;text-decoration:none;transition:all .3s ease;display:inline-flex;align-items:center;justify-content:center;gap:8px;cursor:pointer;border:none}@media(max-width: 768px){.action-btn,.action-btn-new,.action-btn-primary,.action-btn-secondary,.action-btn-edit,.action-btn-delete{padding:16px 24px;font-size:14px}}.action-btn i,.action-btn-new i,.action-btn-primary i,.action-btn-secondary i,.action-btn-edit i,.action-btn-delete i{font-size:16px}.action-btn-new,.action-btn-primary{color:#fff}.action-btn-new:hover,.action-btn-primary:hover{transform:translateY(-2px);box-shadow:0 4px 12px rgba(75,155,255,.1)}.action-btn-new:active,.action-btn-primary:active{transform:translateY(0)}.action-btn-secondary{background:#fff;color:#1a1a2e;border:1px solid #e2e8f0}.action-btn-secondary:hover{background:#f8fafc;border-color:#0049b4;color:#0049b4}.action-btn-secondary:active{transform:scale(0.98)}.action-btn-edit{background:#0049b4;color:#fff}.action-btn-edit:hover{transform:translateY(-2px);box-shadow:0 4px 12px rgba(75,155,255,.1);background:#c3dfea}.action-btn-edit:active{transform:translateY(0)}.action-btn-delete{background:#ff6b6b;color:#fff}.action-btn-delete:hover{transform:translateY(-2px);box-shadow:0 4px 12px rgba(75,155,255,.1);background:rgb(255,88.9,88.9)}.action-btn-delete:active{transform:translateY(0)}.action-btn.btn-list{background:#fff;color:#1a1a2e;border:1px solid #e2e8f0}.action-btn.btn-list:hover{background:#f8fafc;border-color:#0049b4;color:#0049b4}.action-btn.btn-edit{color:#fff}.action-btn.btn-edit:hover{transform:translateY(-2px);box-shadow:0 4px 12px rgba(75,155,255,.1)}.action-btn.btn-delete{background:#ff6b6b;color:#fff}.action-btn.btn-delete:hover{transform:translateY(-2px);box-shadow:0 4px 12px rgba(75,155,255,.1)}.btn-copy{padding:8px 16px;background:rgba(0,73,180,.1);color:#0049b4;border:1px solid rgba(0,73,180,.2);border-radius:6px;font-size:12px;font-weight:500;cursor:pointer;transition:all .3s ease;display:inline-flex;align-items:center;gap:4px}.btn-copy:hover{background:rgba(0,73,180,.2);border-color:#0049b4}.btn-copy i{font-size:12px}.btn-action{padding:8px 24px;color:#fff;border:none;border-radius:8px;font-size:14px;font-weight:500;cursor:pointer;transition:all .3s ease;display:inline-flex;align-items:center;gap:4px}.btn-action:hover{transform:translateY(-2px);box-shadow:0 4px 12px rgba(75,155,255,.1)}.btn-action i{font-size:14px}.btn-cancel{padding:8px 24px;background:#fff;color:#64748b;border:1px solid #e2e8f0;border-radius:8px;font-size:14px;font-weight:500;cursor:pointer;transition:all .3s ease;display:inline-flex;align-items:center;gap:4px}.btn-cancel:hover{border-color:#0049b4;color:#0049b4}.btn-cancel i{font-size:14px}.btn-input-action{flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;min-width:172px;height:60px;padding:10px 24px;border:none;border-radius:12px;font-size:20px;font-weight:700;color:#fff;cursor:pointer;transition:all .3s ease;white-space:nowrap}.btn-input-action.btn-change{background:#a4d6ea}.btn-input-action.btn-change:hover{background:rgb(131.6625,199.4303571429,226.5375);transform:translateY(-2px);box-shadow:0 4px 12px rgba(75,155,255,.1)}.btn-input-action.btn-change:active{transform:translateY(0)}.btn-input-action.btn-auth{background:#a4d6ea}.btn-input-action.btn-auth:hover{background:#c3dfea;transform:translateY(-2px);box-shadow:0 4px 12px rgba(75,155,255,.1)}.btn-input-action.btn-auth:active{transform:translateY(0)}.btn-input-action.btn-auth:disabled{background:#e2e8f0;cursor:not-allowed;transform:none;box-shadow:none}@media(max-width: 768px){.btn-input-action{width:100%;min-width:auto;height:52px;font-size:16px}}.btn-upload{display:inline-flex;align-items:center;justify-content:center;gap:6px;height:40px;padding:8px 16px;background-color:#3ba4ed;color:#fff;border:none;border-radius:8px;font-size:14px;font-weight:600;cursor:pointer;transition:background-color .2s ease}.btn-upload:hover{background-color:#2b94dd}@media(max-width: 1024px){.btn-upload{font-size:13px;height:36px}}.btn-action-primary{display:inline-flex;align-items:center;justify-content:center;background:#2a69de;color:#fff;font-size:20px;font-weight:700;padding:16px 48px;border-radius:10px;text-decoration:none;transition:background .2s,transform .2s;cursor:pointer;border:none}.btn-action-primary:hover{background:hsl(219,73.1707317073%,46.5882352941%);transform:translateY(-2px);color:#fff}.btn-action-primary:disabled{background:#cbd5e1 !important;color:#94a3b8 !important;cursor:not-allowed !important;transform:none !important;pointer-events:none !important}.btn-action-primary.md{font-size:14px;padding:11px 45px}@media(max-width: 768px){.btn-action-primary{font-size:15px}}.status-badge{display:inline-flex;align-items:center;justify-content:center;padding:4px 16px;border-radius:50px;font-size:12px;font-weight:600;white-space:nowrap;transition:all .3s ease}.status-badge.status-pending{background:rgba(255,107,107,.1);color:#ff6b6b}.status-badge.status-completed{background:rgba(107,207,127,.1);color:#6bcf7f}.status-badge.status-active{background:rgba(0,73,180,.1);color:#0049b4}.status-badge.status-inactive{background:rgba(100,116,139,.1);color:#64748b}.status-badge.status-processing{background:rgba(255,217,61,.1);color:rgb(221.2,177.8721649485,0)}.status-badge.status-failed{background:rgba(255,107,107,.1);color:#ff6b6b}.status-badge.status-success{background:rgba(107,207,127,.1);color:#6bcf7f}.status-badge-header{display:inline-flex;align-items:center;justify-content:center;padding:4px 16px;border-radius:50px;font-size:12px;font-weight:600;margin-bottom:16px;transition:all .3s ease}.status-badge-header.status-pending{background:rgba(255,107,107,.1);color:#ff6b6b}.status-badge-header.status-completed{background:rgba(107,207,127,.1);color:#6bcf7f}.status-badge-header.status-active{background:rgba(0,73,180,.1);color:#0049b4}.status-badge-header.status-inactive{background:rgba(100,116,139,.1);color:#64748b}.status-badge-header.status-processing{background:rgba(255,217,61,.1);color:rgb(221.2,177.8721649485,0)}.badge-sm{padding:2px 8px;font-size:10px;border-radius:6px}.badge-lg{padding:8px 24px;font-size:14px;border-radius:8px}.badge-outline{background:rgba(0,0,0,0);border:1px solid currentColor}.badge-outline.status-pending{color:#ff6b6b;border-color:#ff6b6b}.badge-outline.status-completed{color:#6bcf7f;border-color:#6bcf7f}.badge-outline.status-active{color:#0049b4;border-color:#0049b4}.badge-outline.status-inactive{color:#64748b;border-color:#64748b}.badge-icon i{margin-right:4px;font-size:.9em}.notification-badge{display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:20px;padding:0 4px;background:#ff6b6b;color:#fff;border-radius:50%;font-size:11px;font-weight:700;line-height:1}.notification-badge.badge-primary{background:#0049b4}.notification-badge.badge-success{background:#6bcf7f}.notification-badge.badge-warning{background:#ffd93d;color:#1a1a2e}.notification-badge.badge-danger{background:#ff6b6b}.card{background:#fff;border-radius:20px;padding:32px;transition:all .3s ease}.card:hover{box-shadow:0 8px 24px rgba(75,155,255,.15)}.card{position:relative;overflow:hidden}.api-card{background:#fff;border-radius:20px;padding:32px;transition:all .3s ease}.api-card:hover{box-shadow:0 8px 24px rgba(75,155,255,.15)}.api-card{text-align:center;border:2px solid #e2e8f0}.api-card:hover{border-color:#0049b4}.api-card-popular{border-color:#ffd93d}.api-card-new{border-color:#6bcf7f}.api-card .api-badge{position:absolute;top:-12px;right:20px;padding:4px 16px;background:#ffd93d;color:#1a1a2e;border-radius:20px;font-size:12px;font-weight:700;text-transform:uppercase}.api-card .api-badge.new{background:#6bcf7f;color:#fff}.api-card .api-badge.beta{background:#a78bfa;color:#fff}.api-card .api-icon{width:60px;height:60px;margin:0 auto 24px;display:flex;justify-content:center;align-items:center;font-size:28px;background:#eff6ff;color:#0049b4;border-radius:16px;transition:all .3s ease}.api-card:hover .api-icon{transform:scale(1.1);color:#fff}.api-card .api-name{font-size:20px;font-weight:700;margin-bottom:16px;color:#1a1a2e}.api-card .api-description{color:#64748b;line-height:1.6;margin-bottom:24px;font-size:14px}.api-card .api-features{display:flex;justify-content:center;gap:8px;margin-bottom:24px;flex-wrap:wrap}.api-card .api-features .feature-tag{padding:4px 8px;background:#f8fafc;color:#64748b;border-radius:20px;font-size:12px;font-weight:500}.api-card .api-link{display:inline-flex;align-items:center;gap:4px;color:#0049b4;font-weight:600;font-size:14px;transition:all .3s ease}.api-card .api-link:hover{gap:8px;color:#c3dfea}.experience-card{background:#fff;border-radius:20px;padding:32px;transition:all .3s ease}.experience-card:hover{box-shadow:0 8px 24px rgba(75,155,255,.15)}.experience-card{text-align:center}.experience-card .card-icon{width:56px;height:56px;margin:0 auto 24px;display:flex;justify-content:center;align-items:center;font-size:24px;color:#fff;border-radius:16px}.experience-card h3{font-size:20px;font-weight:700;margin-bottom:16px;color:#1a1a2e}.experience-card p{color:#64748b;line-height:1.6;margin-bottom:24px;font-size:14px}.experience-card .card-metric{display:flex;flex-direction:column;align-items:center;padding-top:24px;border-top:1px solid #e2e8f0}.experience-card .card-metric .metric-value{font-size:24px;font-weight:800;color:#0049b4}.experience-card .card-metric .metric-label{font-size:12px;color:#64748b;font-weight:500}.feature-card{background:#fff;border-radius:20px;padding:32px;transition:all .3s ease}.feature-card:hover{box-shadow:0 8px 24px rgba(75,155,255,.15)}.feature-card{display:flex;align-items:flex-start;gap:24px}.feature-card .feature-icon{flex-shrink:0;width:48px;height:48px;display:flex;justify-content:center;align-items:center;color:#fff;border-radius:12px;font-size:24px}.feature-card .feature-content{flex:1}.feature-card .feature-content h4{font-size:18px;font-weight:600;margin-bottom:8px;color:#1a1a2e}.feature-card .feature-content p{color:#64748b;line-height:1.6;font-size:14px}.testimonial-card{background:#fff;border-radius:20px;padding:32px;transition:all .3s ease}.testimonial-card:hover{box-shadow:0 8px 24px rgba(75,155,255,.15)}.testimonial-card{position:relative}.testimonial-card::before{content:'"';position:absolute;top:16px;left:16px;font-size:60px;color:#0049b4;opacity:.1;font-weight:700}.testimonial-card .testimonial-content{position:relative;z-index:1;font-size:16px;line-height:1.8;color:#1a1a2e;margin-bottom:24px;font-style:italic}.testimonial-card .testimonial-author{display:flex;align-items:center;gap:16px}.testimonial-card .testimonial-author .author-avatar{width:48px;height:48px;border-radius:50%;object-fit:cover}.testimonial-card .testimonial-author .author-info .author-name{font-weight:600;color:#1a1a2e;margin-bottom:2px}.testimonial-card .testimonial-author .author-info .author-title{font-size:14px;color:#64748b}.pricing-card{background:#fff;border-radius:20px;padding:32px;transition:all .3s ease}.pricing-card:hover{box-shadow:0 8px 24px rgba(75,155,255,.15)}.pricing-card{text-align:center;position:relative}.pricing-card.featured{border:2px solid #0049b4;transform:scale(1.05)}.pricing-card.featured .pricing-badge{position:absolute;top:-14px;left:50%;transform:translateX(-50%);padding:4px 16px;color:#fff;border-radius:50px;font-size:12px;font-weight:700;text-transform:uppercase}.pricing-card .pricing-header{padding-bottom:24px;border-bottom:1px solid #e2e8f0;margin-bottom:24px}.pricing-card .pricing-header h3{font-size:24px;font-weight:700;margin-bottom:8px;color:#1a1a2e}.pricing-card .pricing-header .price{display:flex;align-items:baseline;justify-content:center;gap:4px}.pricing-card .pricing-header .price .currency{font-size:20px;color:#64748b}.pricing-card .pricing-header .price .amount{font-size:48px;font-weight:800}.pricing-card .pricing-header .price .period{font-size:16px;color:#64748b}.pricing-card .pricing-features{list-style:none;margin-bottom:24px}.pricing-card .pricing-features li{padding:8px 0;color:#64748b;font-size:14px;display:flex;align-items:center;gap:8px}.pricing-card .pricing-features li::before{content:"✓";color:#6bcf7f;font-weight:700}.pricing-card .pricing-features li.disabled{opacity:.5}.pricing-card .pricing-features li.disabled::before{content:"×";color:#94a3b8}.pricing-card .pricing-cta{margin-top:auto}.common-container,.org-register-container,.app-management-container,.apikey-detail-container,.apikey-register-container{max-width:1280px;margin:0 auto;padding:48px 0}@media(max-width: 768px){.common-container,.org-register-container,.app-management-container,.apikey-detail-container,.apikey-register-container{padding:40px 16px 60px}}.common-section-box,.app-list-container,.register-form-container,.register-result{background-color:#f6f9fb;border-radius:12px;padding:30px 45px}@media(max-width: 768px){.common-section-box,.app-list-container,.register-form-container,.register-result{padding:24px}}.common-title-bar,.app-list-title-section,.register-title-bar{background-color:#f6f9fb;border-radius:12px;padding:14px 45px;margin-bottom:30px}@media(max-width: 768px){.common-title-bar,.app-list-title-section,.register-title-bar{background-color:rgba(0,0,0,0);border-radius:0;padding:20px 20px 0;margin-bottom:0;border-bottom:1.5px solid #212529}}.common-title-bar .common-title,.app-list-title-section .common-title,.register-title-bar .common-title{font-size:22px;color:#1a1a2e;margin:0}@media(max-width: 768px){.common-title-bar .common-title,.app-list-title-section .common-title,.register-title-bar .common-title{font-size:16px;color:#000;margin-bottom:4px;display:inline}}.common-title-bar--simple{background:none;padding:0 0 16px 0;border-radius:0;border-bottom:2px solid #212529;margin-bottom:32px}.common-title-bar--simple h3{font-size:22px;font-weight:700;color:#212529;margin:0}.common-title-bar--simple .required-badge{display:none}@media(max-width: 768px){.common-title-bar--simple{padding:20px;padding-bottom:12px;margin-bottom:0;border-bottom:1.5px solid #212529}.common-title-bar--simple h3{font-size:16px;color:#000;display:inline}}.common-title-bar .common-subtitle,.app-list-title-section .common-subtitle,.register-title-bar .common-subtitle{font-size:16px;font-weight:400;color:#515151}@media(max-width: 768px){.common-title-bar .common-subtitle,.app-list-title-section .common-subtitle,.register-title-bar .common-subtitle{font-size:12px;color:#515151;display:inline;margin-left:8px}}.apikey-status-badge,.app-status-badge,.status-badge{display:inline-flex;align-items:center;justify-content:center;padding:5px 14px;border-radius:40px;font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:13px;font-weight:700;color:#fff;white-space:nowrap}.apikey-status-badge.status-pending,.status-pending.app-status-badge,.status-pending.status-badge{background-color:#8c959f}.apikey-status-badge.status-approved,.status-approved.app-status-badge,.status-approved.status-badge,.apikey-status-badge.status-active,.status-active.app-status-badge,.status-active.status-badge{background-color:#0049b4}.apikey-status-badge.status-changing,.status-changing.app-status-badge,.status-changing.status-badge{background-color:#88a5f3}.apikey-status-badge.status-inactive,.status-inactive.app-status-badge,.status-inactive.status-badge,.apikey-status-badge.status-rejected,.status-rejected.app-status-badge,.status-rejected.status-badge{background-color:#dc3545}.apikey-status-badge.status-light.status-pending,.status-light.status-pending.app-status-badge,.status-light.status-pending.status-badge{background-color:#fff3cd;color:#856404}.apikey-status-badge.status-light.status-approved,.status-light.status-approved.app-status-badge,.status-light.status-approved.status-badge,.apikey-status-badge.status-light.status-active,.status-light.status-active.app-status-badge,.status-light.status-active.status-badge{background-color:#d4edda;color:#155724}.apikey-status-badge.status-light.status-rejected,.status-light.status-rejected.app-status-badge,.status-light.status-rejected.status-badge,.apikey-status-badge.status-light.status-inactive,.status-light.status-inactive.app-status-badge,.status-light.status-inactive.status-badge{background-color:#f8d7da;color:#721c24}.apikey-status-indicator,.status-indicator{display:inline-flex;align-items:center;gap:8px;padding:4px 16px;border-radius:16px;font-size:14px;font-weight:600}.apikey-status-indicator .status-dot,.status-indicator .status-dot{width:8px;height:8px;border-radius:50%;background-color:currentColor}.apikey-status-indicator.status-active,.status-active.status-indicator{background-color:#d4edda;color:#155724}.apikey-status-indicator.status-inactive,.status-inactive.status-indicator{background-color:#f8d7da;color:#721c24}.apikey-icon-display,.app-list-icon,.app-icon-display{width:120px;height:120px}.apikey-icon-display.icon-sm,.icon-sm.app-list-icon,.icon-sm.app-icon-display{width:70px;height:70px}@media(max-width: 768px){.apikey-icon-display.icon-sm,.icon-sm.app-list-icon,.icon-sm.app-icon-display{width:60px;height:60px}}.apikey-icon-display.icon-md,.icon-md.app-list-icon,.icon-md.app-icon-display{width:100px;height:100px}.apikey-icon-image,.app-list-icon .app-icon-image,.app-icon-image{width:100%;height:100%;object-fit:cover;border-radius:12px;border:2px solid #e2e8f0}.apikey-icon-placeholder,.app-list-icon .app-icon-placeholder,.app-icon-placeholder{width:100%;height:100%;display:flex;align-items:center;justify-content:center;background-color:#e9ecef;border-radius:12px;color:#868e96;font-size:48px}.apikey-icon-placeholder.placeholder-sm,.placeholder-sm.app-icon-placeholder{font-size:24px}.apikey-empty-state,.app-list-empty,.empty-state,.empty-api-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:80px 24px;text-align:center}.apikey-empty-state .empty-icon,.app-list-empty .empty-icon,.empty-state .empty-icon,.empty-api-state .empty-icon{font-size:64px;margin-bottom:24px;opacity:.5}.apikey-empty-state h3,.app-list-empty h3,.empty-state h3,.empty-api-state h3{font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:24px;font-weight:700;color:#1a1a2e;margin:0 0 16px}.apikey-empty-state p,.app-list-empty p,.empty-state p,.empty-api-state p{font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:16px;color:#6e7780;margin:0}.apikey-action-btn,.btn-app-create,.btn-submit{display:inline-flex;align-items:center;justify-content:center;width:220px;height:60px;border:none;border-radius:12px;font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:18px;font-weight:700;cursor:pointer;text-decoration:none;transition:background-color .2s ease,transform .2s ease}.apikey-action-btn:hover,.btn-app-create:hover,.btn-submit:hover{transform:translateY(-2px)}.apikey-action-btn:active,.btn-app-create:active,.btn-submit:active{transform:translateY(0)}.apikey-action-btn.btn-primary,.btn-primary.btn-app-create,.btn-primary.btn-submit{background-color:#0049b4;color:#fff}.apikey-action-btn.btn-primary:hover,.btn-primary.btn-app-create:hover,.btn-primary.btn-submit:hover{background-color:#003a91}.apikey-action-btn.btn-secondary,.btn-secondary.btn-app-create,.btn-secondary.btn-submit{background-color:#e5e7eb;color:#5f666c}.apikey-action-btn.btn-secondary:hover,.btn-secondary.btn-app-create:hover,.btn-secondary.btn-submit:hover{background-color:#d1d5db}.apikey-action-btn.btn-accent,.btn-accent.btn-app-create,.btn-accent.btn-submit{background-color:#3ba4ed;color:#fff}.apikey-action-btn.btn-accent:hover,.btn-accent.btn-app-create:hover,.btn-accent.btn-submit:hover{background-color:#2b94dd}@media(max-width: 768px){.apikey-action-btn,.btn-app-create,.btn-submit{width:100%;max-width:300px;height:54px;font-size:16px}}.apikey-btn-copy,.btn-copy{display:inline-flex;align-items:center;gap:4px;padding:6px 12px;background:#fff;border:1px solid #e2e8f0;border-radius:6px;font-size:14px;color:#1a1a2e;cursor:pointer;transition:all .3s ease}.apikey-btn-copy svg,.btn-copy svg{width:16px;height:16px}.apikey-btn-copy:hover,.btn-copy:hover{background:#0049b4;border-color:#0049b4;color:#fff}@media(max-width: 768px){.apikey-btn-copy,.btn-copy{align-self:stretch;justify-content:center}}.apikey-detail-section,.detail-section{background:#fff;border-radius:12px;padding:64px;margin-bottom:24px;box-shadow:0 4px 12px rgba(75,155,255,.1)}@media(max-width: 768px){.apikey-detail-section,.detail-section{padding:32px}}.apikey-detail-section .section-title,.detail-section .section-title{font-size:24px;font-weight:700;color:#1a1a2e;margin-bottom:24px;padding-bottom:16px}.apikey-detail-grid,.detail-grid{display:grid;grid-template-columns:repeat(2, 1fr);gap:24px}@media(max-width: 768px){.apikey-detail-grid,.detail-grid{grid-template-columns:1fr}}.apikey-detail-item,.detail-item{display:flex;flex-direction:column;gap:8px}.apikey-detail-item.full-width,.full-width.detail-item{grid-column:1/-1}.apikey-detail-item .detail-label,.detail-item .detail-label{font-size:14px;font-weight:600;color:#64748b}.apikey-detail-item .detail-value,.detail-item .detail-value{font-size:16px;color:#1a1a2e;word-break:break-word}.apikey-ip-list,.ip-list{display:flex;flex-wrap:wrap;gap:8px}.apikey-ip-tag,.ip-tag{display:inline-block;padding:4px 16px;background-color:#eff6ff;color:#c3dfea;border-radius:6px;font-size:14px;font-family:"Fira Code","Courier New",monospace}.apikey-credential-code,.credential-code{display:inline-block;padding:8px 16px;background-color:#f8fafc;border:1px solid #e2e8f0;border-radius:6px;font-family:"Fira Code","Courier New",monospace;font-size:14px;color:#c3dfea}.apikey-secret-box,.secret-box{display:flex;align-items:center;gap:16px}@media(max-width: 768px){.apikey-secret-box,.secret-box{flex-direction:column;align-items:flex-start}}.apikey-api-list,.api-detail-list{display:flex;flex-direction:column;gap:8px}.apikey-api-item,.api-list-item{display:flex;align-items:center;gap:16px;padding:16px 24px;background:#fff;transition:all .2s}.apikey-api-item:hover,.api-list-item:hover{border-color:#0049b4;box-shadow:0 2px 4px rgba(0,0,0,.05)}@media(max-width: 768px){.apikey-api-item,.api-list-item{padding:16px;gap:8px}}.apikey-api-item .api-method,.api-list-item .api-method{display:inline-block;padding:4px 16px;border-radius:6px;font-size:14px;font-weight:700;font-family:"Fira Code","Courier New",monospace;min-width:80px;text-align:center}.apikey-api-item .api-method.method-get,.api-list-item .api-method.method-get{background-color:#d1fae5;color:#065f46}.apikey-api-item .api-method.method-post,.api-list-item .api-method.method-post{background-color:#dbeafe;color:#1e40af}.apikey-api-item .api-method.method-put,.api-list-item .api-method.method-put{background-color:#fef3c7;color:#92400e}.apikey-api-item .api-method.method-delete,.api-list-item .api-method.method-delete{background-color:#fee2e2;color:#991b1b}.apikey-api-item .api-method.method-other,.api-list-item .api-method.method-other{background-color:#e2e8f0;color:#1a1a2e}.apikey-api-item .api-name,.api-list-item .api-name{flex:1;font-size:16px;font-weight:500;color:#1a1a2e;margin:0}.apikey-api-item .api-status,.api-list-item .api-status{padding:4px 16px;border-radius:12px;font-size:14px;font-weight:600}.apikey-api-item .api-status.status-pending,.api-list-item .api-status.status-pending{background-color:#fff3cd;color:#856404}.apikey-api-item .api-status.status-active,.api-list-item .api-status.status-active{background-color:#d4edda;color:#155724}.apikey-form-actions,.detail-actions{display:flex;justify-content:center;gap:16px;flex-wrap:wrap;margin-top:64px;padding-top:64px;border-top:1px solid #e2e8f0}@media(max-width: 768px){.apikey-form-actions,.detail-actions{flex-direction:column;align-items:stretch}}@media(max-width: 768px){.apikey-form-actions .btn,.detail-actions .btn{width:100%}}.form-group{margin-bottom:24px}.form-group label{display:block;font-size:14px;font-weight:600;color:#1a1a2e;margin-bottom:8px}.form-group label .required{color:#ff6b6b;margin-left:2px}.form-group .form-hint{font-size:12px;color:#64748b;margin-top:4px}.form-group .form-error{font-size:12px;color:#ff6b6b;margin-top:4px;display:flex;align-items:center;gap:4px}.form-control{width:100%;padding:12px 16px;font-size:16px;font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;color:#1a1a2e;background:#fff;border:2px solid #e2e8f0;border-radius:8px;transition:all .3s ease}.form-control::placeholder{color:#94a3b8}.form-control:focus{outline:none;border-color:#0049b4;box-shadow:0 0 0 3px rgba(0,73,180,.1)}.form-control:disabled{background:#f8fafc;color:#64748b;cursor:not-allowed}.form-control-sm{padding:8px 16px;font-size:14px}.form-control-lg{padding:16px 24px;font-size:18px}.form-control.is-valid{border-color:#6bcf7f}.form-control.is-valid:focus{box-shadow:0 0 0 3px rgba(107,207,127,.1)}.form-control.is-invalid{border-color:#ff6b6b}.form-control.is-invalid:focus{box-shadow:0 0 0 3px rgba(255,107,107,.1)}textarea.form-control{min-height:120px;resize:vertical}select.form-control{appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath d='M6 8L0 0h12z' fill='%2364748B'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 16px center;background-size:12px;padding-right:40px}.form-check{display:flex;align-items:center;margin-bottom:16px}.form-check input[type=checkbox],.form-check input[type=radio]{width:20px;height:20px;margin-right:8px;cursor:pointer}.form-check label{margin-bottom:0;cursor:pointer;user-select:none}.form-check.disabled{opacity:.5;cursor:not-allowed}.form-check.disabled input,.form-check.disabled label{cursor:not-allowed}.form-switch{display:flex;align-items:center;gap:16px}.form-switch .switch{position:relative;width:48px;height:24px;background:#e2e8f0;border-radius:50px;cursor:pointer;transition:all .3s ease}.form-switch .switch::after{content:"";position:absolute;top:2px;left:2px;width:20px;height:20px;background:#fff;border-radius:50%;transition:all .3s ease}.form-switch .switch.active{background:#0049b4}.form-switch .switch.active::after{transform:translateX(24px)}.input-group{display:flex;width:100%}.input-group .input-group-text{padding:12px 16px;font-size:16px;color:#64748b;background:#f8fafc;border:2px solid #e2e8f0;border-radius:8px 0 0 8px;border-right:none}.input-group .form-control{border-radius:0 8px 8px 0}.input-group.input-group-append .input-group-text{border-radius:0 8px 8px 0;border-right:2px solid #e2e8f0;border-left:none}.input-group.input-group-append .form-control{border-radius:8px 0 0 8px}.form-inline{display:flex;align-items:flex-end;gap:16px}.form-inline .form-group{margin-bottom:0}@media(max-width: 768px){.form-inline{flex-direction:column;align-items:stretch}.form-inline .form-group{margin-bottom:16px}}.form-row{display:grid;grid-template-columns:repeat(2, 1fr);gap:24px}@media(max-width: 768px){.form-row{grid-template-columns:1fr}}.file-upload-wrapper{display:flex;gap:16px;align-items:center;width:100%}@media(max-width: 768px){.file-upload-wrapper{flex-direction:column;align-items:stretch}}.file-upload-wrapper .file-name-display{flex:1;padding:16px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;font-size:14px;color:#64748b;cursor:default;transition:all .3s ease}.file-upload-wrapper .file-name-display.has-file{color:#1a1a2e;background:rgba(0,73,180,.05);border-color:#0049b4}.file-upload-wrapper .file-name-display:focus{outline:none;border-color:#0049b4;box-shadow:0 0 0 3px rgba(0,73,180,.1)}.file-upload-wrapper .file-upload-btn{padding:16px 24px;color:#fff;border-radius:8px;font-size:14px;font-weight:500;cursor:pointer;transition:all .3s ease;white-space:nowrap;display:inline-flex;align-items:center;gap:4px;border:none}@media(max-width: 768px){.file-upload-wrapper .file-upload-btn{justify-content:center}}.file-upload-wrapper .file-upload-btn:hover{transform:translateY(-2px);box-shadow:0 4px 12px rgba(75,155,255,.1)}.file-upload-wrapper .file-upload-btn:active{transform:translateY(0)}.file-upload-wrapper .file-upload-btn i{font-size:14px}.file-upload-wrapper .file-remove-btn{padding:16px;background:#ff6b6b;color:#fff;border-radius:8px;border:none;cursor:pointer;transition:all .3s ease;display:inline-flex;align-items:center;justify-content:center;width:40px;height:40px}@media(max-width: 768px){.file-upload-wrapper .file-remove-btn{width:100%;height:auto;padding:16px}}.file-upload-wrapper .file-remove-btn:hover{transform:translateY(-2px);box-shadow:0 4px 12px rgba(75,155,255,.1);background:rgb(255,88.9,88.9)}.file-upload-wrapper .file-remove-btn:active{transform:translateY(0)}.file-upload-wrapper .file-remove-btn i{font-size:14px}.file-upload-wrapper input[type=file]{display:none}.form-help-text{margin-top:16px;font-size:12px;color:#94a3b8;line-height:1.6}.form-help-text p{margin:4px 0;display:flex;align-items:flex-start;gap:4px}.form-help-text p i{color:#0049b4;margin-top:2px;flex-shrink:0}.form-row{display:flex;align-items:flex-start;gap:20px;margin-bottom:16px}@media(max-width: 1024px){.form-row{flex-direction:column;gap:10px;margin-bottom:12px}}.form-label-wrapper{display:flex;align-items:center;gap:5px;min-width:160px;width:160px;flex-shrink:0}@media(max-width: 1024px){.form-label-wrapper{padding-top:0;width:auto}}.form-label-wrapper.label-offset{margin-top:10px}@media(max-width: 1024px){.form-label-wrapper.label-offset{margin-top:0}}.form-label-text{font-size:15px;font-weight:400;color:#000}@media(max-width: 1024px){.form-label-text{font-size:14px}}.form-label-text.required::after{content:"*";color:#ff6b6b;margin-left:4px}.required-badge{background-color:#3ba4ed;color:#fff;font-size:11px;font-weight:400;padding:3px 4px 4px;border-radius:4px;line-height:1}.form-field-wrapper{flex:1}@media(max-width: 768px){.form-field-wrapper{width:100%}}.form-input{width:100%;padding:8px 12px;border:1px solid #e2e8f0;border-radius:8px;font-size:14px;color:#1a1a2e;background:#fff;transition:all .3s ease}.form-input:focus{outline:none;border-color:#0049b4;box-shadow:0 0 0 3px rgba(0,73,180,.1)}.form-input:disabled,.form-input.input-readonly{background:#e9ecef !important;color:#64748b;cursor:not-allowed}.form-input::placeholder{color:#94a3b8}.form-select{width:100%;padding:8px 12px;border:1px solid #e2e8f0;border-radius:8px;font-size:14px;color:#1a1a2e;background:#fff;cursor:pointer;transition:all .3s ease;appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%2364748B' d='M6 9L1 4h10z'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 24px center;padding-right:48px}.form-select:focus{outline:none;border-color:#0049b4;box-shadow:0 0 0 3px rgba(0,73,180,.1)}.form-select:disabled{background-color:#f8fafc;color:#94a3b8;cursor:not-allowed}.char-counter-wrapper{display:block;text-align:right;margin-top:8px}.char-counter-wrapper .char-counter{font-size:14px;color:#94a3b8}.section-divider{margin-top:48px;padding-top:32px;border-top:1px solid #e2e8f0}.compound-input{display:flex;align-items:center;gap:8px;width:100%}.compound-input .form-input,.compound-input .form-select{flex:1;min-width:0}.compound-input .separator{color:#64748b;font-weight:600;user-select:none;flex-shrink:0}.compound-input .form-input.phone-number{flex:none;width:130px;max-width:130px}.compound-input .form-select.phone-number{flex:none;width:130px;max-width:130px;padding-right:28px;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%2364748B' d='M6 9L1 4h10z'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 8px center}.compound-input .biz-reg-number{flex:1;width:100%}@media(max-width: 768px){.compound-input{gap:4px}.compound-input .separator{font-size:14px}}.input-with-button{display:flex;align-items:center;gap:16px}.input-with-button .compound-input,.input-with-button .auth-input-group{flex:1}@media(max-width: 768px){.input-with-button{flex-direction:column;gap:8px}.input-with-button .compound-input,.input-with-button .auth-input-group{width:100%}}.auth-input-group{display:flex;align-items:center;position:relative}.auth-input-group .form-input{flex:1;padding-right:100px}.auth-input-group .auth-timer{position:absolute;right:16px;top:50%;transform:translateY(-50%);font-size:14px;font-weight:600;color:#ff6b6b;white-space:nowrap}.form-actions,.form-actions-center{display:flex;justify-content:center;gap:16px;flex-wrap:wrap;padding-top:40px}@media(max-width: 768px){.form-actions,.form-actions-center{flex-direction:column-reverse;align-items:stretch;gap:8px;margin-top:48px;padding-top:32px}}.form-actions .btn,.form-actions-center .btn,.form-actions .action-btn-primary,.form-actions-center .action-btn-primary,.form-actions .action-btn-secondary,.form-actions-center .action-btn-secondary{min-width:160px;padding:16px 40px}@media(max-width: 768px){.form-actions .btn,.form-actions-center .btn,.form-actions .action-btn-primary,.form-actions-center .action-btn-primary,.form-actions .action-btn-secondary,.form-actions-center .action-btn-secondary{width:100%;min-width:auto}}.form-actions--between{justify-content:space-between}.form-actions--no-border{border-top:none;padding-top:0}.form-actions--compact{margin-top:40px;padding-top:24px}.form-actions--with-withdrawal{position:relative;justify-content:center}.form-actions--with-withdrawal .withdrawal-link{position:absolute;left:0;display:inline-flex;align-items:center;justify-content:center;gap:3px;width:150px;padding:8px;background:#e5f2f8;border-radius:12px;color:#5f666c;font-size:16px;font-weight:400;text-decoration:none;cursor:pointer;transition:all .3s ease}.form-actions--with-withdrawal .withdrawal-link:hover{background:rgb(210.2090909091,232.6045454545,242.9409090909)}.form-actions--with-withdrawal .withdrawal-link img{width:22px;height:22px;object-fit:contain}@media(max-width: 768px){.form-actions--with-withdrawal .withdrawal-link{position:static;width:300px;height:40px;padding:8px 10px;border-radius:8px;font-size:14px;font-weight:700;color:#515961;order:3}}.form-actions--with-withdrawal .form-actions-buttons{display:flex;gap:16px}@media(max-width: 768px){.form-actions--with-withdrawal .form-actions-buttons{flex-direction:row !important;justify-content:center;width:100%;gap:12px;order:1}}@media(max-width: 768px){.form-actions--with-withdrawal .form-actions-buttons .btn{width:144px !important;height:40px !important;min-height:40px;padding:8px 10px !important;font-size:14px !important;font-weight:700;border-radius:8px !important;flex-shrink:0}}@media(max-width: 768px){.form-actions--with-withdrawal .form-actions-buttons .btn.btn-secondary{background-color:#e5e7eb !important;color:#5f666c !important;border:none !important}}@media(max-width: 768px){.form-actions--with-withdrawal .form-actions-buttons .btn.btn-primary{background-color:#0049b4 !important;color:#fff !important;border:none !important}}@media(max-width: 768px){.form-actions--with-withdrawal{flex-direction:column;align-items:center;gap:16px;padding:24px 20px}}.search-field{position:relative;display:flex;align-items:center;width:340px;height:44px;background:#fff;border:1px solid #dadada;border-radius:12px;overflow:hidden}@media(max-width: 768px){.search-field{width:100%;margin-top:10px}}.search-field input{flex:1;height:100%;padding:0 44px 0 16px;border:none;background:rgba(0,0,0,0);font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:14px;font-weight:400;color:#1a1a2e;outline:none}.search-field input::placeholder{color:#8c959f}.search-field .search-field-btn{position:absolute;right:0;top:0;display:flex;align-items:center;justify-content:center;width:44px;height:100%;background:rgba(0,0,0,0);border:none;cursor:pointer;color:#515961;transition:all .15s ease}.search-field .search-field-btn:hover{color:#0049b4}.search-field .search-field-btn svg{width:20px;height:20px}.search-field:focus-within{border-color:#0049b4;box-shadow:0 0 0 2px rgba(0,73,180,.1)}.notice-content-box{padding:24px;background:#fff;border:1px solid #e2e8f0;border-radius:8px;min-height:300px;line-height:1.8;font-size:16px;color:#1a1a2e}@media(max-width: 768px){.notice-content-box{padding:16px;min-height:200px}}.notice-content-box p{margin-bottom:16px}.notice-content-box img{max-width:100%;height:auto}.notice-content-box a{color:#0049b4;text-decoration:underline}.notice-content-box a:hover{color:rgb(0,65.7,162)}.form-row--content .form-label-wrapper{align-self:flex-start}.attachment-link{display:inline-flex;align-items:center;gap:8px;padding:8px 16px;background:#eff6ff;border:1px solid #e2e8f0;border-radius:8px;color:#1a1a2e;font-size:14px;text-decoration:none;transition:all .3s ease}.attachment-link:hover{background:rgba(0,73,180,.05);border-color:#0049b4;color:#0049b4}.attachment-link svg{flex-shrink:0;color:#64748b}.attachment-link:hover svg{color:#0049b4}.attachment-list{display:flex;flex-direction:column;gap:8px}.attachment-item{display:flex;align-items:center}.djb-label{display:block;font-size:14px;font-weight:700;color:#000;margin-bottom:12px}.djb-label .required{color:#e53e3e;margin-left:4px}.djb-input{width:100%;padding:14px 18px;border:1px solid #dfdfdf;border-radius:8px;font-size:14px;color:#000;transition:border-color .2s;background-color:#fcfcfc}.djb-input::placeholder{color:#94a3b8}.djb-input:focus{outline:none;border-color:#2a69de}.djb-textarea{width:100%;padding:14px 18px;border:1px solid #dfdfdf;border-radius:8px;font-size:14px;color:#000;resize:none;transition:border-color .2s;min-height:250px;background-color:#fcfcfc}.djb-textarea::placeholder{color:#94a3b8}.djb-textarea:focus{outline:none;border-color:#2a69de}.djb-char-counter-wrapper{text-align:right;margin-top:8px}.djb-char-counter-wrapper .char-counter{font-size:13px;color:#7f8a95}.modal-backdrop{position:fixed;top:0;left:0;right:0;bottom:0;background:#385670;z-index:1040;opacity:.9;visibility:hidden;transition:all .3s ease}.modal-backdrop.show{opacity:.9;visibility:visible}.modal{position:fixed;top:0;left:0;width:100%;height:100vh;z-index:1050;display:flex;align-items:center;justify-content:center;padding:24px;opacity:0;visibility:hidden;transition:opacity .3s ease,visibility .3s ease;pointer-events:none;overflow-y:auto}.modal.show{opacity:1;visibility:visible;pointer-events:auto}.modal.show .modal-dialog{transform:scale(1)}@media(max-width: 768px){.modal{padding:16px}}.modal-container{position:relative;max-width:820px;width:100%}.modal-container.modal-sm{max-width:500px}.modal-container.modal-lg{max-width:1000px}.modal-container.modal-xl{max-width:1200px}@media(max-width: 768px){.modal-container{max-width:100%}}.modal-close{position:absolute;top:0;right:0;transform:translate(100%, -100%);width:50px;height:50px;background:#0049b4;border:none;border-radius:50%;color:#fff;cursor:pointer;display:flex;align-items:center;justify-content:center;z-index:1050;transition:all .3s ease;box-shadow:0 8px 24px rgba(75,155,255,.15)}.modal-close svg{width:14px;height:14px}.modal-close i{font-size:16px}@media(max-width: 768px){.modal-close{width:40px;height:40px}.modal-close svg{width:12px;height:12px}.modal-close i{font-size:14px}}.modal-dialog{position:relative;background:#fff;padding:0 24px;border-radius:12px;box-shadow:0 20px 60px rgba(0,0,0,.3);max-width:700px;width:calc(100% - 48px);max-height:85vh;display:flex;flex-direction:column;transform:scale(0.95);transition:transform .3s ease;z-index:1050;margin-left:auto;margin-right:auto}.modal-dialog.modal-fullscreen{max-width:100%;max-height:100%;height:100%;margin:0;border-radius:0}@media(max-width: 768px){.modal-dialog{padding:0}}.modal-header{padding:30px 50px 20px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid #000;border-top-left-radius:12px;border-top-right-radius:12px}.modal-header .modal-title{font-size:24px;font-weight:600;color:#212529;margin:0}@media(max-width: 768px){.modal-header{padding:20px 24px 16px}.modal-header .modal-title{font-size:18px}}.modal-body{padding:30px 50px;flex:1;overflow-y:auto;color:#212529;font-size:20px;line-height:1.6}.modal-body p{margin:0}.modal-body p+p{margin-top:16px}.modal-body.text-center{text-align:center}@media(max-width: 768px){.modal-body{padding:20px 24px;font-size:16px}}.modal-footer{padding:20px 50px 40px;display:flex;gap:10px;justify-content:center;border-top:none}.modal-footer.modal-footer-end{justify-content:flex-end}.modal-footer.modal-footer-between{justify-content:space-between}@media(max-width: 768px){.modal-footer{padding:16px 24px 24px;flex-direction:row;gap:10px}.modal-footer .btn,.modal-footer .btn-modal-cancel,.modal-footer .btn-modal-confirm{min-width:0;height:46px;font-size:15px}}.pop_input_field{width:100%;padding:12px 16px;border:1px solid #e2e8f0;border-radius:8px;font-size:14px;font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;transition:all .3s ease;box-sizing:border-box}.pop_input_field:focus{outline:none;border-color:#0049b4;box-shadow:0 0 0 3px rgba(0,73,180,.1)}.pop_input_field::placeholder{color:#94a3b8}.pop_input_field.error{border-color:#ff6b6b}.pop_input_field.error:focus{border-color:#ff6b6b;box-shadow:0 0 0 3px rgba(255,107,107,.1)}.modal-alert .modal-container{max-width:500px}.modal-alert .modal-body{text-align:center;padding:32px 50px}.modal-alert .modal-body .alert-icon{width:64px;height:64px;margin:0 auto 24px;display:flex;justify-content:center;align-items:center;font-size:32px;border-radius:50%}.modal-alert .modal-body .alert-icon.alert-success{background:rgba(107,207,127,.1);color:#6bcf7f}.modal-alert .modal-body .alert-icon.alert-warning{background:rgba(255,217,61,.1);color:#ffd93d}.modal-alert .modal-body .alert-icon.alert-danger{background:rgba(255,107,107,.1);color:#ff6b6b}.modal-alert .modal-body .alert-icon.alert-info{background:rgba(0,73,180,.1);color:#0049b4}.modal-alert .modal-body .alert-title{font-size:24px;font-weight:600;margin-bottom:8px;color:#1a1a2e}.modal-alert .modal-body .alert-message{color:#64748b;font-size:16px}.breadcrumb{display:flex;align-items:center;gap:8px;padding:16px 0;font-size:14px}.breadcrumb .breadcrumb-item{display:flex;align-items:center;gap:8px;color:#64748b}.breadcrumb .breadcrumb-item a{color:#64748b;transition:all .3s ease}.breadcrumb .breadcrumb-item a:hover{color:#0049b4}.breadcrumb .breadcrumb-item.active{color:#1a1a2e;font-weight:500}.breadcrumb .breadcrumb-item:not(:last-child)::after{content:"/";color:#94a3b8;margin-left:8px}.nav-tabs{display:flex;gap:4px;border-bottom:2px solid #e2e8f0;margin-bottom:32px}.nav-tabs .nav-item{position:relative}.nav-tabs .nav-link{display:flex;align-items:center;gap:8px;padding:16px 24px;color:#64748b;font-weight:500;border-bottom:2px solid rgba(0,0,0,0);margin-bottom:-2px;transition:all .3s ease}.nav-tabs .nav-link:hover{color:#1a1a2e}.nav-tabs .nav-link.active{color:#0049b4;border-bottom-color:#0049b4}.nav-tabs .nav-link .badge{padding:2px 6px;background:#f8fafc;color:#64748b;border-radius:50px;font-size:11px;font-weight:600}.nav-pills{display:flex;gap:8px;padding:4px;background:#f8fafc;border-radius:12px}.nav-pills .nav-link{padding:8px 24px;color:#64748b;font-weight:500;border-radius:8px;transition:all .3s ease}.nav-pills .nav-link:hover{color:#1a1a2e;background:hsla(0,0%,100%,.5)}.nav-pills .nav-link.active{background:#fff;color:#0049b4;box-shadow:0 2px 4px rgba(0,0,0,.05)}.sidebar-nav .nav-section{margin-bottom:32px}.sidebar-nav .nav-section .nav-title{font-size:12px;font-weight:600;text-transform:uppercase;color:#64748b;padding:8px 16px;letter-spacing:.05em}.sidebar-nav .nav-menu{list-style:none}.sidebar-nav .nav-menu .nav-item{margin-bottom:4px}.sidebar-nav .nav-menu .nav-link{display:flex;align-items:center;gap:16px;padding:8px 16px;color:#1a1a2e;border-radius:8px;transition:all .3s ease}.sidebar-nav .nav-menu .nav-link .nav-icon{width:20px;height:20px;display:flex;justify-content:center;align-items:center;font-size:18px;color:#64748b}.sidebar-nav .nav-menu .nav-link:hover{background:#f8fafc;color:#0049b4}.sidebar-nav .nav-menu .nav-link:hover .nav-icon{color:#0049b4}.sidebar-nav .nav-menu .nav-link.active{background:#eff6ff;color:#0049b4;font-weight:500}.sidebar-nav .nav-menu .nav-link.active .nav-icon{color:#0049b4}.sidebar-nav .nav-menu .nav-submenu{margin-left:40px;margin-top:4px;list-style:none}.sidebar-nav .nav-menu .nav-submenu .nav-link{font-size:14px;padding:4px 16px}.pagination{display:flex;align-items:center;gap:4px}.pagination .page-item.disabled .page-link{opacity:.5;cursor:not-allowed;pointer-events:none}.pagination .page-item.active .page-link{background:#0049b4;color:#fff;border-color:#0049b4}.pagination .page-link{display:flex;align-items:center;justify-content:center;min-width:36px;height:36px;padding:0 8px;background:#fff;border:1px solid #e2e8f0;border-radius:8px;color:#1a1a2e;font-size:14px;font-weight:500;transition:all .3s ease}.pagination .page-link:hover{background:#f8fafc;border-color:#0049b4;color:#0049b4}.pagination .page-link.page-prev,.pagination .page-link.page-next{font-size:18px}.pagination .page-dots{padding:0 8px;color:#64748b}.partners{padding:80px 0;background:#fff}.final-cta{position:relative;padding:150px 0;overflow:hidden}#passwordInputPopup .pop_input_group{text-align:left;margin:0}#passwordInputPopup .error-message{color:#ff6b6b;font-size:12px;margin-top:4px;display:none;animation:fadeInDown .3s ease}#passwordInputPopup .error-message.show{display:block}@keyframes fadeInDown{from{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}.password-policy-checklist{list-style:none;padding:0;margin:12px 0 0 0}.password-policy-checklist li{display:flex;align-items:center;gap:8px;font-size:15px;line-height:20px;margin-bottom:6px;transition:color .15s ease}.password-policy-checklist li:last-child{margin-bottom:0}.password-policy-checklist li .policy-icon{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;flex-shrink:0;font-size:13px;font-weight:700;line-height:1}.password-policy-checklist li .policy-icon::before{content:"•"}.password-policy-checklist li.is-idle{color:#718096}.password-policy-checklist li.is-idle .policy-icon{color:#a0aec0}.password-policy-checklist li.is-pass{color:#1a8f4c}.password-policy-checklist li.is-pass .policy-icon{color:#1a8f4c}.password-policy-checklist li.is-pass .policy-icon::before{content:"✔"}.password-policy-checklist li.is-fail{color:#d63a3a}.password-policy-checklist li.is-fail .policy-icon{color:#d63a3a}.password-policy-checklist li.is-fail .policy-icon::before{content:"✖"}.password-policy-note{margin:10px 0 0 0;font-size:14px;line-height:18px;color:#ef5555}.auth-group{display:flex;align-items:center;gap:16px}.auth-group.authenticated{gap:0}.auth-group .login-btn{font-size:15px;font-weight:500;color:var(--primary-color);padding:10px 20px;border-radius:8px;transition:var(--transition-smooth)}.auth-group .login-btn:hover{background-color:var(--accent-light)}.auth-group .btn-login:hover{background-color:var(--accent-light)}.login-btn{font-size:15px;font-weight:500;color:var(--primary-color);padding:10px 20px;border-radius:8px;transition:var(--transition-smooth);white-space:nowrap}.login-btn:hover{background-color:var(--accent-light)}.btn-signup{font-size:15px;font-weight:700;color:#fff;background-color:var(--primary-color);padding:10px 24px;border-radius:8px;white-space:nowrap;box-shadow:0 4px 12px rgba(0,73,180,.15);transition:var(--transition-smooth)}.btn-signup:hover{background-color:var(--secondary-color);transform:translateY(-2px);box-shadow:0 6px 20px rgba(0,73,180,.25);color:#fff}.login-btn-box{font-size:15px;font-weight:500;color:var(--primary-color);padding:10px 20px;border-radius:8px;transition:var(--transition-smooth)}.login-btn-box .user-icon{width:18px;height:18px;flex-shrink:0}.user-profile-dropdown{position:relative}.user-profile-dropdown .user-profile-btn{display:flex;align-items:center;gap:8px;padding:10px 20px;background:#eff6ff;border:2px solid rgba(0,0,0,0);border-radius:50px;cursor:pointer;transition:all .3s ease;font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:14px}.user-profile-dropdown .user-profile-btn .user-name{color:#1a1a2e;font-weight:600}.user-profile-dropdown .user-profile-btn i{color:#64748b;font-size:12px;transition:transform all .15s ease}.user-profile-dropdown .user-profile-btn:hover{background:#fff;border-color:#0049b4;box-shadow:0 4px 12px rgba(75,155,255,.1)}.user-profile-dropdown .user-profile-btn:hover .user-name{color:#0049b4}.user-profile-dropdown .user-profile-btn:hover i{color:#0049b4}.user-profile-dropdown.active .user-profile-btn i{transform:rotate(180deg)}.user-profile-dropdown.active .profile-dropdown-menu{visibility:visible;opacity:1;transform:translateY(0)}.user-profile-dropdown .profile-dropdown-menu{position:absolute;top:calc(100% + 12px);right:0;width:280px;background:#fff;border:1px solid #e2e8f0;border-radius:12px;box-shadow:0 16px 40px rgba(75,155,255,.2);visibility:hidden;opacity:0;transform:translateY(-10px);transition:all all .3s ease;z-index:100;overflow:hidden}.user-profile-dropdown .profile-header{padding:24px;color:#fff;text-align:center}.user-profile-dropdown .profile-header .user-greeting{margin:0 0 4px 0;font-size:18px;font-weight:700}.user-profile-dropdown .profile-header .user-message{font-size:12px;opacity:.9}.user-profile-dropdown .profile-menu-list{list-style:none;margin:0;padding:8px 0}.user-profile-dropdown .profile-menu-list li{margin:0}.user-profile-dropdown .profile-menu-list li a{display:flex;align-items:center;gap:12px;padding:12px 24px;color:#1a1a2e;text-decoration:none;font-size:14px;transition:all .15s ease}.user-profile-dropdown .profile-menu-list li a i{width:20px;color:#64748b;font-size:14px}.user-profile-dropdown .profile-menu-list li a:hover{background:#eff6ff;color:#0049b4}.user-profile-dropdown .profile-menu-list li a:hover i{color:#0049b4}.user-profile-dropdown .profile-footer{padding:8px 24px 24px;border-top:1px solid #e2e8f0}.user-profile-dropdown .profile-footer .logout-btn{display:flex;align-items:center;justify-content:center;gap:8px;width:100%;padding:12px;background:#f8fafc;color:#1a1a2e;text-decoration:none;border-radius:8px;font-weight:600;font-size:14px;transition:all .3s ease}.user-profile-dropdown .profile-footer .logout-btn i{font-size:14px}.user-profile-dropdown .profile-footer .logout-btn:hover{background:#ff6b6b;color:#fff;transform:translateY(-2px);box-shadow:0 4px 12px rgba(75,155,255,.1)}.drawer-user-info{padding:24px;border-bottom:1px solid hsla(0,0%,100%,.2)}.drawer-user-info .drawer-profile{display:flex;align-items:center;gap:16px}.drawer-user-info .drawer-profile .profile-avatar{width:60px;height:60px;display:flex;align-items:center;justify-content:center;background:hsla(0,0%,100%,.2);border-radius:50%;color:#fff}.drawer-user-info .drawer-profile .profile-avatar i{font-size:32px}.drawer-user-info .drawer-profile .profile-info{flex:1;color:#fff}.drawer-user-info .drawer-profile .profile-info .profile-name{margin:0 0 4px 0;font-size:18px;font-weight:700}.drawer-user-info .drawer-profile .profile-info .profile-greeting{font-size:12px;opacity:.9}.drawer-logout-btn{display:flex;align-items:center;justify-content:center;gap:8px;width:100%;padding:14px 20px;background:#ff6b6b;color:#fff;text-decoration:none;border-radius:12px;font-weight:600;font-size:16px;transition:all .3s ease;box-shadow:0 4px 12px rgba(75,155,255,.1)}.drawer-logout-btn i{font-size:16px}.drawer-logout-btn:hover{background:rgb(255,70.8,70.8);transform:translateY(-2px);box-shadow:0 8px 24px rgba(75,155,255,.15)}@media(max-width: 768px){.user-profile-dropdown .profile-dropdown-menu{width:260px;right:-10px}}@media(max-width: 480px){.user-profile-dropdown .user-profile-btn{padding:8px 16px}.user-profile-dropdown .user-profile-btn .user-name{max-width:100px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.user-profile-dropdown .profile-dropdown-menu{width:240px;right:-20px}}.session-timer{display:inline-flex;align-items:center;gap:4px;white-space:nowrap;font-size:14px;font-weight:600;color:#64748b}.session-timer .session-timer-icon{font-size:12px;color:#64748b}.session-timer .session-timer-text{font-variant-numeric:tabular-nums;min-width:40px;text-align:center}.session-timer .session-timer-text.session-timer-warning{color:#e03131;font-weight:700}.session-float{position:fixed;top:16px;right:20px;z-index:1000;display:flex;align-items:center;gap:8px;padding:6px 14px;background:#fff;border:1px solid #e0e0e0;border-radius:20px;box-shadow:0 2px 8px rgba(0,0,0,.12)}.session-float .session-keepalive{display:inline-flex;align-items:center;gap:4px;margin:0;padding-left:8px;border-left:1px solid #e0e0e0;font-size:12px;color:#64748b;cursor:pointer;white-space:nowrap}.session-float .session-keepalive input{margin:0;cursor:pointer}@media(max-width: 1699px){.session-float{display:none}}.data-table{width:100%;background:#fff;border-radius:12px;overflow:hidden;box-shadow:0 2px 4px rgba(0,0,0,.05)}.data-table-wrapper{overflow-x:auto;margin-bottom:32px}@media(max-width: 768px){.data-table-wrapper{border-radius:12px;box-shadow:0 2px 4px rgba(0,0,0,.05)}}.data-table table{width:100%;border-collapse:collapse}.data-table table thead{background:#eff6ff}.data-table table thead tr{border-bottom:2px solid #e2e8f0}.data-table table thead th{padding:16px 24px;text-align:left;font-size:14px;font-weight:600;color:#1a1a2e;white-space:nowrap}@media(max-width: 768px){.data-table table thead th{padding:8px 16px;font-size:12px}}.data-table table tbody tr{border-bottom:1px solid #e2e8f0;transition:all .3s ease}.data-table table tbody tr:last-child{border-bottom:none}.data-table table tbody tr:hover{background-color:rgba(0,73,180,.02)}.data-table table tbody td{padding:16px 24px;font-size:14px;color:#64748b}@media(max-width: 768px){.data-table table tbody td{padding:8px 16px;font-size:12px}}.data-table table tbody td a{color:#1a1a2e;text-decoration:none;transition:all .3s ease}.data-table table tbody td a:hover{color:#0049b4}.notice-table{width:100%;background:#fff;border-radius:12px;overflow:hidden;box-shadow:0 2px 4px rgba(0,0,0,.05)}.notice-table-wrapper{overflow-x:auto}@media(max-width: 768px){.notice-table-wrapper{border-radius:0;box-shadow:none}}.notice-table .table-header{display:grid;grid-template-columns:80px 1fr 120px;gap:16px;padding:16px 24px;background:#eff6ff;border-bottom:2px solid #e2e8f0;font-size:14px;font-weight:600;color:#1a1a2e}@media(max-width: 1024px){.notice-table .table-header{grid-template-columns:60px 1fr 100px;padding:8px 16px;font-size:12px}}@media(max-width: 768px){.notice-table .table-header{display:none}}.notice-table .table-header .col-num{text-align:center}.notice-table .table-header .col-title{text-align:left}.notice-table .table-header .col-date{text-align:center}.notice-table .table-row{display:grid;grid-template-columns:80px 1fr 120px;gap:16px;padding:24px;border-bottom:1px solid #e2e8f0;transition:all .3s ease;cursor:pointer}@media(max-width: 1024px){.notice-table .table-row{grid-template-columns:60px 1fr 100px;padding:16px}}@media(max-width: 768px){.notice-table .table-row{grid-template-columns:1fr;gap:8px;padding:16px}}.notice-table .table-row:last-child{border-bottom:none}.notice-table .table-row:hover{background-color:rgba(0,73,180,.02)}.notice-table .table-row .col-num{display:flex;align-items:center;justify-content:center;font-size:14px;color:#64748b;font-weight:500}@media(max-width: 768px){.notice-table .table-row .col-num{display:none}}.notice-table .table-row .col-title{display:flex;align-items:center;font-size:16px;color:#1a1a2e;font-weight:500}@media(max-width: 768px){.notice-table .table-row .col-title{font-size:14px}}.notice-table .table-row .col-title a{color:inherit;text-decoration:none;display:flex;align-items:center;gap:8px;transition:all .3s ease}.notice-table .table-row .col-title a:hover{color:#0049b4}.notice-table .table-row .col-title .file-icon{display:inline-flex;align-items:center;margin-left:8px;opacity:.6}.notice-table .table-row .col-title .file-icon img{width:16px;height:16px}.notice-table .table-row .col-date{display:flex;align-items:center;justify-content:center;font-size:14px;color:#94a3b8}@media(max-width: 768px){.notice-table .table-row .col-date{justify-content:flex-start;font-size:12px;padding-top:4px}}@media(max-width: 768px){.notice-table .table-row .col-date::before{content:"등록일: ";color:#64748b;margin-right:4px}}.table-empty{padding:80px 24px;text-align:center;background:#fff;border-radius:12px}.table-empty .empty-icon{margin-bottom:24px}.table-empty .empty-icon img{max-width:200px;opacity:.7}@media(max-width: 768px){.table-empty .empty-icon img{max-width:150px}}.table-empty .empty-text{font-size:16px;color:#64748b}@media(max-width: 768px){.table-empty .empty-text{font-size:14px}}.board-table{width:100%;border-top:1px solid #c4c7c8}.board-table-wrapper{overflow-x:auto;margin-bottom:32px}.board-table .board-table-header{display:flex;align-items:center;background:#f9f9f9;height:50px;padding:0 45px;border-bottom:1px solid #dedede}@media(max-width: 768px){.board-table .board-table-header{display:none}}.board-table .board-table-header .header-cell{font-size:14px;font-family:"Spoqa Han Sans Neo",sans-serif;font-weight:500;color:#000;text-align:center}.board-table .board-table-body{display:flex;flex-direction:column}.board-table .board-table-row{display:flex;align-items:center;height:50px;padding:0 45px;border-bottom:1px solid #dedede;transition:background-color .2s;background:#fff}.board-table .board-table-row:hover{background-color:#f8fbfe}@media(max-width: 768px){.board-table .board-table-row{flex-direction:column;align-items:flex-start;height:auto;padding:15px;gap:10px}}.board-table .board-table-row .row-cell{display:flex;align-items:center;justify-content:center;font-size:14px;font-family:"Spoqa Han Sans Neo",sans-serif;font-weight:400;color:#000;white-space:nowrap}@media(max-width: 768px){.board-table .board-table-row .row-cell{justify-content:flex-start;width:100% !important}.board-table .board-table-row .row-cell::before{content:attr(data-label);font-weight:500;color:#64748b;margin-right:10px;min-width:60px}}.board-table .board-table-row .row-cell--title{justify-content:flex-start;white-space:normal}@media(max-width: 768px){.board-table .board-table-row .row-cell--title::before{display:none}}.board-table .board-table-row .row-cell--title .notice-title-link{display:flex;align-items:center;gap:10px;color:inherit;text-decoration:none;width:100%}.board-table .board-table-row .row-cell--title .notice-title-link:hover{font-weight:600}.board-table .board-table-row .row-cell--title .notice-title-link .notice-number{display:none;font-weight:500;color:#64748b;flex-shrink:0}@media(max-width: 768px){.board-table .board-table-row .row-cell--title .notice-title-link .notice-number{display:inline}}@media(max-width: 768px){.board-table .board-table-row .row-cell--number{display:none}}@media(max-width: 768px){.inquiry-list-container .table-controls{flex-direction:column !important;align-items:stretch !important;gap:24px !important}.inquiry-list-container .table-controls .search-field{order:1 !important}.inquiry-list-container .table-controls .total-count{display:flex !important;align-items:center;width:100%;order:2 !important;text-align:left;font-size:16px;color:#000;padding-bottom:6px;border-bottom:1.5px solid #212529;margin-bottom:0}.inquiry-list-container .table-controls .total-count .inquiry-visibility-notice{margin-left:auto !important}}.inquiry-list-container .board-table .board-table-row .row-cell--number{width:80px}.inquiry-list-container .board-table .board-table-row .row-cell--title{flex:1;min-width:200px;max-width:500px}@media(max-width: 768px){.inquiry-list-container .board-table .board-table-row .row-cell--title{max-width:none}}.inquiry-list-container .board-table .board-table-row .row-cell--title .notice-title-link{display:inline-flex;align-items:center;max-width:100%}.inquiry-list-container .board-table .board-table-row .row-cell--title .notice-title-link .file-icon{display:inline-flex;align-items:center;flex-shrink:0;margin-right:6px;color:#888}.inquiry-list-container .board-table .board-table-row .row-cell--title .notice-title-link .inquiry-private-label{color:#8c959f;font-style:italic;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:320px}.inquiry-list-container .board-table .board-table-row .row-cell--title .notice-title-link .inquiry-subject-text{display:inline-block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:240px;vertical-align:middle}.inquiry-list-container .board-table .board-table-row .row-cell--title .notice-title-link .inquiry-comment-count{color:#0049b4;font-weight:600;margin-left:6px;flex-shrink:0;font-size:14px}.inquiry-list-container .board-table .board-table-row .row-cell--writer{width:120px}.inquiry-list-container .board-table .board-table-row .row-cell--writer .inquiry-writer-org{font-size:12px;color:#666;margin-left:4px}.inquiry-list-container .board-table .board-table-row .row-cell--status{width:120px}.inquiry-list-container .board-table .board-table-row .row-cell--views{width:80px}.inquiry-list-container .board-table .board-table-row .row-cell--date{width:120px}.list-table{width:100%;background:#fff;border-radius:12px;overflow:hidden;border:1px solid #e5e7eb;box-shadow:0 1px 3px rgba(0,0,0,.06)}.list-table-wrapper{overflow-x:auto;margin-bottom:32px}.list-table .list-table-header{display:flex;align-items:center;background:#3ba4ed;height:48px;padding:0 24px}@media(max-width: 1024px){.list-table .list-table-header{display:none}}.list-table .list-table-header .header-cell{display:flex;align-items:center;justify-content:center;padding:8px 16px;font-size:13px;font-weight:600;color:#fff;text-align:center;letter-spacing:.3px}.list-table .list-table-body{display:flex;flex-direction:column}.list-table .list-table-row{display:flex;align-items:center;height:48px;padding:8px 24px;transition:all .15s ease;border-bottom:1px solid #f3f4f6}.list-table .list-table-row:last-child{border-bottom:none}.list-table .list-table-row:nth-child(even){background-color:#f8fbfe}.list-table .list-table-row:nth-child(odd){background-color:#fff}.list-table .list-table-row:hover{background-color:#edf5fd}@media(max-width: 1024px){.list-table .list-table-row{flex-direction:column;align-items:flex-start;gap:8px;padding:16px}}.list-table .list-table-row .row-cell{display:flex;align-items:center;justify-content:center;padding:8px 16px;font-size:14px;color:#374151;text-align:center;white-space:nowrap}@media(max-width: 1024px){.list-table .list-table-row .row-cell{font-size:14px;padding:4px 0}.list-table .list-table-row .row-cell::before{content:attr(data-label);font-weight:600;color:#64748b;margin-right:8px;min-width:80px;text-align:left}}.list-table .list-table-row .row-cell--title{justify-content:flex-start}@media(max-width: 1024px){.list-table .list-table-row .row-cell--title::before{display:none}}.list-table .list-table-row .row-cell--title .notice-title-link{display:inline-flex;align-items:center;gap:8px;color:inherit;text-decoration:none;transition:all .3s ease}.list-table .list-table-row .row-cell--title .notice-title-link:hover{color:#0049b4}.list-table .list-table-row .row-cell--title .notice-title-link .notice-number{display:none;font-weight:500;color:#64748b;flex-shrink:0}@media(max-width: 1024px){.list-table .list-table-row .row-cell--title .notice-title-link .notice-number{display:inline}}.list-table .list-table-row .row-cell--title .file-icon{display:inline-flex;align-items:center;flex-shrink:0;color:#212529}.list-table .list-table-row .row-cell--title .file-icon svg{width:24px;height:24px}@media(max-width: 1024px){.list-table .list-table-row .row-cell--number{display:none}}.list-table .list-table-row .row-actions{display:flex;align-items:center;justify-content:center;gap:14px;flex-wrap:nowrap}@media(max-width: 1024px){.list-table .list-table-row .row-actions{width:100%;justify-content:flex-start;padding-top:8px}}.list-table-btn{display:inline-flex;align-items:center;justify-content:center;padding:0 16px;height:28px;white-space:nowrap;border-radius:5px;font-size:12px;font-weight:400;font-family:"Spoqa Han Sans Neo",sans-serif;color:#000;border:1px solid rgba(0,0,0,0);cursor:pointer;transition:all .3s ease}.list-table-btn:hover{transform:translateY(-1px);box-shadow:0 2px 4px rgba(0,0,0,.05)}.list-table-btn:active{transform:translateY(0)}.list-table-btn--default{background-color:#f5f5f4;border-color:#bdc7cf;color:#64748b}.list-table-btn--default:hover{background-color:hsl(60,4.7619047619%,91.0882352941%)}.list-table-btn--primary{background-color:#ecf0fa;border-color:#4685ef;color:#2a69de}.list-table-btn--primary:hover{background-color:rgb(216.7625,224.8125,244.9375)}.list-table-btn--secondary{background-color:#f5f5f4;border-color:#bdc7cf;color:#64748b}.list-table-btn--secondary:hover{background-color:hsl(60,4.7619047619%,91.0882352941%)}.list-table-btn--danger{background-color:#fbe7e9;border-color:#f1c2d6;color:#bb1026}.list-table-btn--danger:hover{background-color:hsl(354,71.4285714286%,89.7843137255%)}.table-pagination{display:flex;align-items:center;justify-content:center;gap:13px;padding:32px 0}.table-pagination .pagination-btn{display:flex;align-items:center;justify-content:center;width:24px;height:24px;background:none;border:none;cursor:pointer;color:#212529;transition:all .3s ease}.table-pagination .pagination-btn:hover{color:#0049b4}.table-pagination .pagination-btn:disabled{opacity:.4;cursor:not-allowed}.table-pagination .pagination-btn i{font-size:16px}.table-pagination .pagination-numbers{display:flex;align-items:center;gap:13px}.table-pagination .pagination-number{display:flex;align-items:center;justify-content:center;min-width:10px;height:21px;font-size:18px;color:#5f666c;background:none;border:none;cursor:pointer;transition:all .3s ease}.table-pagination .pagination-number:hover{color:#0049b4}.table-pagination .pagination-number.active{font-weight:700;color:#212529}.table-controls{display:flex;justify-content:space-between;align-items:center;margin-bottom:24px;padding:0 8px;gap:24px}@media(max-width: 768px){.table-controls{flex-direction:column;align-items:stretch;gap:16px}}.table-controls .total-count{font-size:16px;color:#64748b;margin-bottom:0}.table-controls .total-count strong{color:#0049b4;font-weight:600;font-size:20px}@media(max-width: 768px){.table-controls .total-count{order:2;text-align:center}}.table-controls .search-box{display:flex;gap:8px}@media(max-width: 768px){.table-controls .search-box{order:1}}.table-controls .search-box input{flex:1;min-width:250px;padding:8px 16px;border:1px solid #e2e8f0;border-radius:8px;font-size:14px;transition:all .3s ease}@media(max-width: 768px){.table-controls .search-box input{min-width:auto}}.table-controls .search-box input:focus{outline:none;border-color:#0049b4;box-shadow:0 0 0 3px rgba(0,73,180,.1)}.table-controls .search-box input::placeholder{color:#94a3b8}@media(max-width: 768px){.search-field{display:flex;align-items:center;width:100%;height:40px;background:#fff;border:1px solid #dadada;border-radius:8px;position:relative}.search-field input{flex:1;height:100%;padding:0 40px 0 16px;border:none;background:rgba(0,0,0,0);font-size:12px;color:#212529}.search-field input::placeholder{color:#8c959f;font-size:12px}.search-field input:focus{outline:none}.search-field .search-field-btn{position:absolute;right:12px;top:50%;transform:translateY(-50%);width:18px;height:18px;padding:0;background:none;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center}.search-field .search-field-btn svg{width:18px;height:18px;color:#515961}.table-controls{flex-direction:column;align-items:stretch;gap:24px;padding:0;margin-bottom:0}.table-controls .total-count{order:2;text-align:left;font-size:16px;color:#000;padding-bottom:6px;border-bottom:1.5px solid #212529;margin-bottom:0}.table-controls .total-count strong{color:#0049b4;font-weight:700;font-size:16px}.table-controls .search-field{order:1}.list-table{background:rgba(0,0,0,0);border-radius:0}.list-table .list-table-header{display:none}.list-table .list-table-body{display:flex;flex-direction:column}.list-table .list-table-row{display:flex;flex-direction:row;align-items:center;justify-content:space-between;height:56px;padding:0 8px;background:rgba(0,0,0,0) !important;border-bottom:1px solid #dadada}.list-table .list-table-row:hover{background:rgba(0,73,180,.02) !important}.list-table .list-table-row .row-cell--number{display:none}.list-table .list-table-row .row-cell--title{flex:1;justify-content:flex-start;padding:0;font-size:15px;font-weight:400;color:#212529}.list-table .list-table-row .row-cell--title::before{display:none}.list-table .list-table-row .row-cell--title .notice-title-link{display:flex;align-items:center;gap:8px;color:inherit;text-decoration:none}.list-table .list-table-row .row-cell--title .notice-title-link .notice-number{display:none}.list-table .list-table-row .row-cell--title .notice-title-link .file-icon{display:inline-flex;align-items:center;flex-shrink:0;margin-left:4px}.list-table .list-table-row .row-cell--title .notice-title-link .file-icon svg{width:18px;height:18px}.list-table .list-table-row .row-cell:last-child{flex-shrink:0;width:auto;padding:0;font-size:14px;font-weight:400;color:#212529;justify-content:flex-end}.list-table .list-table-row .row-cell:last-child::before{display:none}.pagination{gap:13px;padding:24px 0}.pagination .pagination-btn{width:20px;height:20px}.pagination .pagination-btn i,.pagination .pagination-btn svg{font-size:14px;width:14px;height:14px}.pagination .pagination-numbers{gap:13px}.pagination .pagination-number{min-width:10px;height:21px;font-size:15px;color:#5f666c;font-weight:400}.pagination .pagination-number.active{font-weight:700;color:#212529}}@media(max-width: 768px){.faq-container .table-controls{display:contents;padding:0}}.faq-accordion{display:flex;flex-direction:column;gap:6px;margin-bottom:40px}@media(max-width: 768px){.faq-accordion{margin-top:24px}}.faq-accordion .faq-item{position:relative;border-radius:10px;background:#fff;transition:all .3s ease}.faq-accordion .faq-item:hover .faq-question{background:#4685ef;border-color:#e8dddd}.faq-accordion .faq-item:hover .faq-question .faq-question-q,.faq-accordion .faq-item:hover .faq-question .faq-question-text{color:#fff !important}.faq-accordion .faq-item:hover .faq-question .faq-icon{color:#fff}.faq-accordion .faq-question{display:flex;align-items:center;min-height:47px;height:auto;padding:8px 35px 8px 21px;background:#fff;border:1px solid #dedede;border-radius:10px;cursor:pointer;transition:all .3s ease}@media(max-width: 1024px){.faq-accordion .faq-question{padding:12px 16px;min-height:44px}}.faq-accordion .faq-question .faq-question-q{font-size:20px;font-weight:700;color:#555;margin-right:15px;line-height:1}@media(max-width: 1024px){.faq-accordion .faq-question .faq-question-q{font-size:16px;margin-right:10px}}.faq-accordion .faq-question .faq-question-text{flex:1;font-size:14px;color:#000;line-height:normal;word-break:keep-all}@media(max-width: 1024px){.faq-accordion .faq-question .faq-question-text{font-size:13px;line-height:1.4}}.faq-accordion .faq-question .faq-icon{flex-shrink:0;display:flex;align-items:center;justify-content:center;width:22px;height:22px;color:#000;transition:transform .3s ease}.faq-accordion .faq-question .faq-icon svg{width:14px;height:8px}.faq-accordion .faq-answer{display:none;padding:19px 21px 25px 21px}@media(max-width: 1024px){.faq-accordion .faq-answer{padding:16px}}.faq-accordion .faq-answer .faq-answer-content{font-size:14px;color:#000;line-height:1.6}@media(max-width: 1024px){.faq-accordion .faq-answer .faq-answer-content{font-size:13px}}.faq-accordion .faq-answer .faq-answer-content p{margin-bottom:8px}.faq-accordion .faq-answer .faq-answer-content p:last-child{margin-bottom:0}.faq-accordion .faq-answer .faq-answer-content ul,.faq-accordion .faq-answer .faq-answer-content ol{margin:8px 0;padding-left:20px}.faq-accordion .faq-answer .faq-answer-content li{margin-bottom:4px}.faq-accordion .faq-answer .faq-answer-content a{color:#0049b4;text-decoration:underline}.faq-accordion .faq-answer .faq-answer-content a:hover{color:#c3dfea}.faq-accordion .faq-answer .faq-answer-content code{background:#f8fafc;padding:2px 6px;border-radius:6px;font-family:"Fira Code","Courier New",monospace;font-size:.9em;color:#0049b4}.faq-accordion .faq-answer .faq-answer-content pre{background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;padding:16px;overflow-x:auto;margin:16px 0}.faq-accordion .faq-answer .faq-answer-content pre code{background:rgba(0,0,0,0);padding:0;color:#1a1a2e}.faq-page{min-height:calc(100vh - 140px);background:#f8fafc;padding:48px 24px}@media(max-width: 768px){.faq-page{padding:40px 16px}}.faq-container{max-width:1280px;margin:0 auto}.faq-header{margin-bottom:48px}@media(max-width: 768px){.faq-header{margin-bottom:40px}}.faq-header .page-title{font-size:40px;font-weight:700;color:#1a1a2e;margin-bottom:16px;-webkit-background-clip:text;-webkit-text-fill-color:rgba(0,0,0,0);background-clip:text}@media(max-width: 768px){.faq-header .page-title{font-size:32px}}.faq-header .page-description{font-size:16px;color:#64748b;line-height:1.6}@media(max-width: 768px){.faq-header .page-description{font-size:14px}}@keyframes slideDown{from{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}.accordion{background:#fff;border-radius:12px;box-shadow:0 2px 4px rgba(0,0,0,.05);overflow:hidden}.accordion .accordion-item{border-bottom:1px solid #e2e8f0}.accordion .accordion-item:last-child{border-bottom:none}.accordion .accordion-item.active .accordion-header{background:rgba(0,73,180,.03);color:#0049b4}.accordion .accordion-item.active .accordion-header .accordion-icon{transform:rotate(180deg)}.accordion .accordion-header{display:flex;align-items:center;justify-content:space-between;padding:24px 32px;font-size:16px;font-weight:500;color:#1a1a2e;cursor:pointer;transition:all .3s ease}@media(max-width: 768px){.accordion .accordion-header{padding:16px 24px;font-size:14px}}.accordion .accordion-header:hover{background:rgba(0,73,180,.02)}.accordion .accordion-header .accordion-title{flex:1}.accordion .accordion-header .accordion-icon{flex-shrink:0;font-size:20px;color:#64748b;transition:all .3s ease}.accordion .accordion-content{display:none;padding:24px 32px;font-size:14px;color:#64748b;line-height:1.6;border-top:1px solid #e2e8f0}@media(max-width: 768px){.accordion .accordion-content{padding:16px 24px}}.accordion .accordion-content.show{display:block;animation:slideDown .3s ease}.page-title-banner{background:#e9f8ff;position:relative;width:100%;height:110px;display:flex;align-items:center;justify-content:center;overflow:hidden}.page-title-banner::before{content:"";position:absolute;top:0;left:0;right:0;bottom:0;background-image:url("data:image/svg+xml,%3Csvg width='100' height='100' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0h100v100H0z' fill='%23ffffff' opacity='0.03'/%3E%3C/svg%3E");background-size:50px 50px;opacity:.5}.page-title-banner .title-image{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);height:220px;width:auto;z-index:1}.page-title-banner h1{position:relative;font-size:40px;font-weight:700;color:#1a1a2e;margin:0;z-index:2;letter-spacing:-0.5px}@media(max-width: 768px){.page-title-banner{height:120px;margin-bottom:32px}.page-title-banner .title-image{height:120px}.page-title-banner h1{font-size:24px}}.alert{display:flex;align-items:flex-start;gap:16px;padding:16px 24px;border-radius:8px;margin-bottom:32px}.alert .alert-icon{font-size:20px;flex-shrink:0}.alert .alert-content{flex:1;text-align:left}.alert .alert-content strong{font-weight:600}.alert.alert-warning{background:rgba(255,217,61,.1);border:1px solid rgba(255,217,61,.3);color:#b89900}.alert.alert-error{background:rgba(255,107,107,.1);border:1px solid rgba(255,107,107,.3);color:rgb(255,70.8,70.8);align-items:center}.alert.alert-error svg{flex-shrink:0}.alert.alert-error span{flex:1;font-size:14px;font-weight:500}.alert.alert-success{background:rgba(107,207,127,.1);border:1px solid rgba(107,207,127,.3);color:hsl(132,51.0204081633%,49.2549019608%)}.alert.alert-info{background:rgba(0,73,180,.1);border:1px solid rgba(0,73,180,.3);color:#c3dfea}.djb-toast-container{position:fixed;top:24px;left:50%;transform:translateX(-50%);z-index:12000;display:flex;flex-direction:column;align-items:center;gap:10px;pointer-events:none}@media(max-width: 768px){.djb-toast-container{top:12px;left:12px;right:12px;transform:none;align-items:stretch}}.djb-toast{pointer-events:auto;min-width:260px;max-width:380px;padding:14px 18px;border-radius:10px;background:#f4f7fc;color:#1f2328;font-size:14px;line-height:1.5;border:1px solid #e3e8f0;box-shadow:0 8px 24px rgba(0,0,0,.12);cursor:pointer;opacity:0;transform:translateY(-12px);transition:opacity .22s ease,transform .22s ease}.djb-toast.is-show{opacity:1;transform:translateY(0)}.djb-toast{border-left:4px solid #0049b4}.djb-toast--info{border-left-color:#0049b4}.djb-toast--success{border-left-color:#1f883d}.djb-toast--warn{border-left-color:#bf8700}.djb-toast--error{border-left-color:#cf222e}@media(max-width: 768px){.djb-toast{max-width:none}}@media(min-width: 769px){.djb-toast{min-width:338px;max-width:494px;padding:18px 24px;font-size:18px;border-radius:13px}}.pagination{display:flex;align-items:center;justify-content:center;gap:13px;margin-top:50px}.pagination .page-first,.pagination .page-prev,.pagination .page-next,.pagination .page-last{display:flex;align-items:center;justify-content:center;width:24px;height:24px;color:#212529;text-decoration:none;cursor:pointer;transition:all .15s ease}.pagination .page-first:hover:not(.disabled),.pagination .page-prev:hover:not(.disabled),.pagination .page-next:hover:not(.disabled),.pagination .page-last:hover:not(.disabled){color:#0049b4}.pagination .page-first.disabled,.pagination .page-prev.disabled,.pagination .page-next.disabled,.pagination .page-last.disabled{color:#adb5bd;cursor:not-allowed;pointer-events:none}.pagination .page-first svg,.pagination .page-prev svg,.pagination .page-next svg,.pagination .page-last svg{width:24px;height:24px;flex-shrink:0}.pagination .page-num{display:flex;align-items:center;justify-content:center;min-width:10px;height:21px;font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:18px;font-weight:400;color:#5f666c;text-decoration:none;text-align:center;line-height:1;cursor:pointer;transition:all .15s ease}.pagination .page-num:hover:not(.page-current){color:#0049b4}.pagination .page-num.page-current{font-weight:700;color:#212529;cursor:default}.pagination .blind{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);white-space:nowrap;border:0}.pagination-wrapper{display:flex;justify-content:center;padding:24px 0;margin-top:24px}.breadcrumb-container{background-color:#e9f8ff;height:60px;width:100%;display:flex;align-items:center}.breadcrumb{display:flex;align-items:center;gap:20px;max-width:1280px;margin:0 auto;padding:0 24px;width:100%}@media(max-width: 768px){.breadcrumb{gap:12px;padding:0 16px}}.breadcrumb-home{display:flex;align-items:center;justify-content:center;width:24px;height:24px;flex-shrink:0}.breadcrumb-home svg{width:18px;height:17px;fill:#8c959f;transition:all .15s ease}.breadcrumb-home:hover svg{fill:#0049b4}.breadcrumb-separator{display:flex;align-items:center;justify-content:center;width:24px;height:24px;flex-shrink:0}.breadcrumb-separator svg{width:6.5px;height:12px;fill:#5f666c}@media(max-width: 768px){.breadcrumb-separator{width:16px;height:16px}.breadcrumb-separator svg{width:5px;height:10px}}.breadcrumb-item{font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:16px;font-weight:400;line-height:1;color:#5f666c;text-decoration:none;white-space:nowrap;transition:all .15s ease}.breadcrumb-item:hover{color:#0049b4}.breadcrumb-item--active{font-weight:700;color:#212529}.breadcrumb-item--active:hover{color:#212529;cursor:default}@media(max-width: 768px){.breadcrumb-item{font-size:14px}}.breadcrumb-nav{background-color:#e9f8ff;height:60px;width:100%}@media(max-width: 768px){.breadcrumb-nav{height:48px}}.breadcrumb-nav .breadcrumb-list{display:flex;align-items:center;gap:20px;max-width:1280px;margin:0 auto;padding:0 26px;height:100%;list-style:none}@media(max-width: 768px){.breadcrumb-nav .breadcrumb-list{gap:12px;padding:0 16px}}.breadcrumb-nav .breadcrumb-list-item{display:flex;align-items:center;gap:20px}@media(max-width: 768px){.breadcrumb-nav .breadcrumb-list-item{gap:12px}}.breadcrumb-nav .breadcrumb-list-item:not(:last-child)::after{content:"";display:block;width:6.5px;height:12px;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='7' height='12' viewBox='0 0 7 12' fill='none'%3E%3Cpath d='M1 1L6 6L1 11' stroke='%235F666C' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:center;background-size:contain}@media(max-width: 768px){.breadcrumb-nav .breadcrumb-list-item:not(:last-child)::after{width:5px;height:10px}}.breadcrumb-nav .breadcrumb-list-item a{font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:16px;font-weight:400;line-height:1;color:#5f666c;text-decoration:none;white-space:nowrap;transition:all .15s ease}.breadcrumb-nav .breadcrumb-list-item a:hover{color:#0049b4}@media(max-width: 768px){.breadcrumb-nav .breadcrumb-list-item a{font-size:14px}}.breadcrumb-nav .breadcrumb-list-item:last-child a{font-weight:700;color:#212529;pointer-events:none}.breadcrumb-nav .breadcrumb-list-item:first-child a{display:flex;align-items:center;justify-content:center;width:24px;height:24px;font-size:0}.breadcrumb-nav .breadcrumb-list-item:first-child a::before{content:"";display:block;width:18px;height:17px;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='17' viewBox='0 0 18 17' fill='none'%3E%3Cpath d='M1 6.5L9 1L17 6.5V15C17 15.5304 16.7893 16.0391 16.4142 16.4142C16.0391 16.7893 15.5304 17 15 17H3C2.46957 17 1.96086 16.7893 1.58579 16.4142C1.21071 16.0391 1 15.5304 1 15V6.5Z' stroke='%238C959F' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:center;background-size:contain;transition:all .15s ease}.breadcrumb-nav .breadcrumb-list-item:first-child a:hover::before{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='17' viewBox='0 0 18 17' fill='none'%3E%3Cpath d='M1 6.5L9 1L17 6.5V15C17 15.5304 16.7893 16.0391 16.4142 16.4142C16.0391 16.7893 15.5304 17 15 17H3C2.46957 17 1.96086 16.7893 1.58579 16.4142C1.21071 16.0391 1 15.5304 1 15V6.5Z' stroke='%230049B4' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E")}.test-env-notice{margin-top:8px;padding:12px 16px;background-color:#fff4e6;border:1px solid #ffb84d;border-radius:4px}.test-env-notice__content{display:flex;align-items:flex-start;gap:8px}.test-env-notice__icon{flex-shrink:0;margin-top:2px;width:20px;height:20px}.test-env-notice__text-container{flex:1}.test-env-notice__title{margin:0;font-size:14px;color:#e65100}.test-env-notice__description{margin:4px 0 0 0;font-size:13px;color:#5d4037;line-height:1.5}.test-env-notice__auth-code{color:#e65100;font-family:monospace;font-size:14px;font-weight:bold}.inquiry-comment-count{display:inline-block;margin-left:6px;color:#dc2626;font-weight:600;font-size:14px}.inquiry-header-right{display:flex;align-items:center;gap:8px;font-size:14px;color:#6b7280}.inquiry-header-right .inquiry-detail-writer{font-weight:600;color:#374151}.inquiry-header-right .inquiry-detail-divider{color:#d1d5db}.inquiry-header-right .inquiry-detail-date{font-size:inherit;color:inherit}.djb-comment-section{margin:40px 0 48px 0}.djb-comment-list-box{background-color:#fff;border-radius:8px;min-height:120px;margin-bottom:28px}.djb-comment-list{list-style:none;padding:0;margin:0}.djb-comment-empty{padding:28px 0;text-align:center;font-size:14px;color:#9ca3af}.djb-comment-item{padding:40px 0;border-bottom:1px solid #dfdfdf}.djb-comment-item:first-child{padding-top:20px}.djb-comment-item:last-child{border-bottom:none;padding-bottom:20px}.djb-comment-item--admin .djb-comment-writer{color:#1d4ed8}.djb-comment-item .djb-comment-meta{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}.djb-comment-item .djb-comment-meta-left{display:flex;align-items:center;gap:8px}.djb-comment-item .djb-comment-writer{font-size:14px;font-weight:500;color:#000;letter-spacing:-0.2px}.djb-comment-item .djb-comment-admin-badge{display:inline-flex;align-items:center;padding:3px 8px;background-color:#ecf1ff;color:#2a69de;border-radius:10px;font-size:9px;font-weight:700}.djb-comment-item .djb-comment-meta-right{display:flex;align-items:center;gap:12px}.djb-comment-item .djb-comment-date{color:#6b7280;font-size:14px}.djb-comment-item .djb-comment-delete-btn{background:#fbe7e9;border:1px solid #f1c2d6;border-radius:5px;padding:3px 6px;font-size:10px;font-weight:500;color:#bb1026;cursor:pointer;transition:all .15s ease;display:flex;align-items:center;justify-content:center;height:20px}.djb-comment-item .djb-comment-delete-btn:hover{background-color:#f1c2d6}.djb-comment-item .djb-comment-body{padding:24px;background-color:#f4f5f9;border-radius:10px;font-size:14px;line-height:20px;color:#000;white-space:pre-wrap;word-break:break-word}.djb-comment-form-wrapper{display:block;width:100%;margin-top:8px}.djb-comment-form{display:flex;flex-direction:column;width:100%}textarea.djb-comment-input{display:block;width:100%;box-sizing:border-box;padding:14px 18px;border:1px solid #dfdfdf;border-radius:10px;background-color:#fff;font-size:14px;line-height:1.6;color:#000;resize:vertical;min-height:103px;font-family:inherit;outline:none;transition:border-color .15s ease,box-shadow .15s ease;background-color:#fcfcfc}textarea.djb-comment-input::placeholder{color:#7f8a95}textarea.djb-comment-input:hover{border-color:#2a69de}textarea.djb-comment-input:focus{border-color:#2a69de;background-color:#fff;box-shadow:0 0 0 3px rgba(42,105,222,.1)}.djb-comment-form-actions{display:flex;align-items:center;justify-content:space-between;width:100%;margin-top:12px;gap:12px}.djb-comment-form-actions .djb-comment-private-toggle{display:inline-flex;align-items:center;gap:6px;font-size:14px;color:#374151;cursor:pointer}.djb-comment-char-counter{font-size:13px;color:#94a3b8}button.djb-comment-submit{display:flex;align-items:center;justify-content:center;width:149px;height:39px;background-color:#fff;color:#4685ef;border:1px solid #2a69de;border-radius:10px;font-size:14px;font-weight:700;cursor:pointer;transition:all .2s ease;flex-shrink:0;align-self:flex-end;margin-top:12px}@media(max-width: 768px){button.djb-comment-submit{width:100%}}button.djb-comment-submit:hover{background-color:#f8faff}button.djb-comment-submit:active{transform:translateY(1px)}button.djb-comment-submit:disabled{background-color:#f4f5f9;border-color:#dfdfdf;color:#7f8a95;cursor:not-allowed}.djb-comment-readonly-msg{margin:0;padding:16px 20px;background-color:#f3f4f6;border-radius:6px;text-align:center;color:#6b7280;font-size:13px}.board-header{margin-bottom:24px}.board-header .board-title{font-size:24px;font-weight:700;color:#000;margin-bottom:8px;line-height:1.2}.board-header .board-desc{font-size:14px;color:#7f8a95;margin:0;line-height:1.5}.tfa-card{position:relative;width:520px;max-width:100%;padding:0;border:1px solid #dce2ed;border-radius:4px;background:#fff;box-shadow:0 4px 20px rgba(0,0,0,.08);color:#0d0e11;letter-spacing:-0.02em}.tfa-close{position:absolute;top:16px;right:16px;width:28px;height:28px;padding:0;border:0;background:none;color:#7f8a95;cursor:pointer;display:flex;align-items:center;justify-content:center;border-radius:4px;transition:background .12s ease,color .12s ease}.tfa-close:hover{background:#f4f5f9;color:#0d0e11}.tfa-head{padding:24px 32px;padding-right:60px;border-bottom:1px solid #dce2ed;display:flex;align-items:center;justify-content:space-between;gap:16px}.tfa-head .tfa-head-title{font-size:27px;font-weight:700;line-height:1.35;color:#0d0e11}.tfa-head .tfa-head-brand{font-size:14px;font-weight:500;color:#7f8a95}.tfa-body{padding:28px 32px 32px}.tfa-step{display:flex;align-items:center;gap:8px;margin-bottom:12px}.tfa-step .tfa-step-num{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:100px;background:#bdc7cf;color:#fff;font-size:11px;font-weight:500;flex:none}.tfa-step .tfa-step-num.is-active{background:#4685ef}.tfa-step .tfa-step-text{font-size:15px;font-weight:500;color:#7f8a95}.tfa-step .tfa-step-text.is-active{color:#0d0e11}.tfa-segment{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:28px}.tfa-segment .tfa-seg-btn{padding:14px 16px;text-align:left;cursor:pointer;border:1px solid #dce2ed;border-radius:4px;background:#fff;transition:border-color .12s ease,background .12s ease}.tfa-segment .tfa-seg-btn .tfa-seg-label{display:block;font-size:15px;font-weight:500;color:#0d0e11}.tfa-segment .tfa-seg-btn .tfa-seg-masked{display:block;font-size:14px;font-weight:300;color:#7f8a95;margin-top:2px}.tfa-segment .tfa-seg-btn:hover{border-color:#4685ef}.tfa-segment .tfa-seg-btn.is-active{border:1.5px solid #4685ef;background:#ecf0fa}.tfa-segment .tfa-seg-btn.is-active .tfa-seg-label{color:#2a69de}.tfa-hint{border:1px solid #dce2ed;border-radius:4px;background:#f4f5f9;padding:16px;font-size:14px;font-weight:300;line-height:1.5;color:#7f8a95;margin-bottom:20px}.tfa-cta{width:100%;height:56px;border:0;border-radius:4px;background:#4685ef;color:#fff;cursor:pointer;font-size:16px;font-weight:500;letter-spacing:-0.02em;transition:background .12s ease}.tfa-cta:hover{background:#2a69de}.tfa-cta:disabled{background:#f4f5f9;color:#bdc7cf;cursor:default}.tfa-code-row{display:flex;gap:8px;margin-bottom:8px}.tfa-code-row .tfa-code-input-wrap{flex:1;display:flex;align-items:stretch;height:56px;border:1px solid #bdc7cf;border-radius:4px;background:#fff;transition:border-color .12s ease}.tfa-code-row .tfa-code-input-wrap:focus-within{border-color:#4685ef}.tfa-code-row .tfa-code-input{flex:1;min-width:0;height:100%;padding:0 14px;border:0;outline:none;background:rgba(0,0,0,0);font-size:16px;font-weight:500;letter-spacing:.02em;color:#0d0e11}.tfa-code-row .tfa-code-input::placeholder{color:#bdc7cf;font-weight:400}.tfa-code-row .tfa-resend{flex:none;height:56px;padding:0 18px;border:1px solid #7f8a95;border-radius:4px;background:#fff;color:#0d0e11;cursor:pointer;font-size:15px;font-weight:500;white-space:nowrap;transition:background .12s ease}.tfa-code-row .tfa-resend:hover{background:#f4f5f9}.tfa-code-row .tfa-resend:disabled{opacity:.5;cursor:default}.tfa-timer-row{display:flex;align-items:center;justify-content:space-between;gap:12px;border:1px solid #dce2ed;border-radius:4px;background:#f4f5f9;padding:12px 14px;margin-bottom:12px}.tfa-timer-row .tfa-timer-label{font-size:14px;font-weight:300;color:#7f8a95}.tfa-timer-row .tfa-timer-clock{display:flex;align-items:baseline;gap:6px}.tfa-timer-row .tfa-timer{font-size:20px;font-weight:500;letter-spacing:.02em;color:#4685ef;font-variant-numeric:tabular-nums}.tfa-timer-row .tfa-timer.is-warning{color:#f4253c}.tfa-timer-row .tfa-timer.is-expired{color:#7f8a95}.tfa-timer-row .tfa-timer-total{font-size:13px;font-weight:300;color:#7f8a95}.tfa-progress{height:3px;background:#dce2ed;border-radius:2px;overflow:hidden;margin-bottom:12px}.tfa-progress .tfa-progress-bar{height:100%;width:100%;background:#4685ef;transition:width 1s linear}.tfa-progress .tfa-progress-bar.is-warning{background:#f4253c}.tfa-message{min-height:21px;margin:0 0 20px;font-size:14px;font-weight:300;line-height:1.5;color:#7f8a95}.tfa-message.is-error{color:#f4253c}.tfa-test-notice{margin:0 0 16px;padding:10px 14px;border:1px dashed #f59e0b;border-radius:4px;background:#fffbeb;color:#b45309;font-size:13px;text-align:center}.tfa-foot{margin-top:24px;padding-top:16px;border-top:1px solid #dfdfdf;font-size:14px;font-weight:300;line-height:1.5;color:#7f8a95}@media(max-width: 768px){.tfa-head{padding:20px 22px}.tfa-head .tfa-head-title{font-size:23px}.tfa-body{padding:22px 22px 26px}.tfa-segment{margin-bottom:22px}}.hero-carousel-section{position:relative;width:100%;overflow:hidden}.hero-carousel-container{position:relative;width:100%;height:431px;overflow:hidden;background-color:#e9f9ff;margin-top:72px}@media(max-width: 1024px){.hero-carousel-container{height:720px}}@media(max-width: 768px){.hero-carousel-container{height:432px;margin-top:54px}}.hero-carousel-track{position:relative;width:100%;height:100%}@media(min-width: 1280px){.hero-carousel-track{max-width:1280px;margin:0 auto}}.hero-slide{position:absolute;top:0;left:0;width:100%;height:396px;display:flex;align-items:center;justify-content:center;opacity:0;visibility:hidden;transition:opacity .6s ease-in-out,visibility .6s ease-in-out}.hero-slide.active{opacity:1;visibility:visible}.hero-slide[data-slide="0"]{background:#e9f9ff}.hero-slide[data-slide="1"]{background:#e9f9ff}.hero-slide[data-slide="2"]{background:#e9f9ff}.hero-slide-content{width:100%;max-width:1280px;height:100%;display:flex;align-items:center;justify-content:space-between;position:relative;margin:0 auto;margin-top:45px}@media(min-width: 1280px){.hero-slide-content{padding:0 20px}}@media(max-width: 1024px){.hero-slide-content{flex-direction:column;padding:72px 36px;justify-content:center;gap:36px;margin-top:296px}}@media(max-width: 768px){.hero-slide-content{flex-direction:column;padding:30px 20px 20px;gap:20px;justify-content:flex-start;align-items:center;margin-top:80px}}.hero-text-content{flex:0 0 auto;display:flex;flex-direction:column;gap:41px;z-index:2;width:534px}@media(max-width: 1024px){.hero-text-content{align-items:center;text-align:center}}@media(max-width: 768px){.hero-text-content{gap:16px;align-items:center;text-align:center;width:100%;padding:0 10px}}.hero-text .hero-subtitle{font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:18px;font-weight:500;color:var(--secondary-color);letter-spacing:2px;text-transform:uppercase;margin-bottom:11px}@media(max-width: 1024px){.hero-text .hero-subtitle{font-size:25px}}@media(max-width: 768px){.hero-text .hero-subtitle{font-size:14px;font-weight:500;line-height:1.4;margin-bottom:0}}.hero-text .hero-title{font-size:38px;font-weight:700;line-height:1.25;color:var(--text-dark);letter-spacing:-1.5px}@media(max-width: 1024px){.hero-text .hero-title{font-size:36px}}@media(max-width: 768px){.hero-text .hero-title{font-size:24px;font-weight:700;line-height:1.3;letter-spacing:-0.48px}}.btn-hero-signup{display:inline-flex;align-items:center;justify-content:center;gap:7px;width:162px;height:50px;background-color:var(--primary-color);color:#fff;font-size:14px;font-weight:700;border-radius:11px;box-shadow:0 7px 22px rgba(0,73,180,.2);transition:var(--transition-smooth)}.btn-hero-signup:hover{background-color:var(--secondary-color);transform:translateY(-3px);box-shadow:0 11px 27px rgba(0,73,180,.35);color:#fff}.btn-hero-signup:active{transform:translateY(0)}@media(max-width: 1024px){.btn-hero-signup{width:180px}}@media(max-width: 768px){.btn-hero-signup{width:130px;height:36px;padding:7px 9px;font-size:13px;border-radius:7px}}.hero-image-content{flex:0 0 auto;width:500px;height:410px;display:flex;align-items:center;justify-content:center;z-index:1}@media(min-width: 1280px){.hero-image-content{width:369px;height:270px}}@media(max-width: 1024px){.hero-image-content{width:369px;height:270px}}@media(max-width: 768px){.hero-image-content{width:196px;height:162px;max-width:none;flex-shrink:0}}.hero-image-content .hero-image{width:100%;height:100%;object-fit:contain}.hero-nav-btn{position:absolute;top:50%;transform:translateY(-50%);width:54px;height:54px;background:hsla(0,0%,100%,.5);border:none;border-radius:45px;cursor:pointer;display:none;align-items:center;justify-content:center;z-index:10;transition:all .3s ease}.hero-nav-btn:hover{background:hsla(0,0%,100%,.8);transform:translateY(-50%) scale(1.1)}.hero-nav-btn:active{transform:translateY(-50%) scale(0.95)}.hero-nav-btn svg{width:20px;height:20px}@media(min-width: 1280px){.hero-nav-btn{display:flex}}.hero-nav-prev{left:189px}.hero-nav-prev svg{transform:rotate(180deg)}@media(max-width: 768px){.hero-nav-prev{left:18px}}@media(max-width: 1024px){.hero-nav-prev{left:36px}}@media(min-width: 1280px){.hero-nav-prev{left:calc(50vw - 576px - 140px)}}.hero-nav-next{right:189px}@media(max-width: 768px){.hero-nav-next{right:18px}}@media(max-width: 1024px){.hero-nav-next{right:36px}}@media(min-width: 1280px){.hero-nav-next{right:calc(50vw - 576px - 140px)}}.hero-carousel-indicators{position:absolute;bottom:5px;left:50%;transform:translateX(-50%);display:flex;align-items:center;gap:11px;z-index:10}@media(max-width: 768px){.hero-carousel-indicators{top:54px;right:25px;bottom:auto;left:auto;transform:none;flex-direction:row;gap:5px;align-items:center}}.hero-indicator{width:9px;height:9px;border-radius:50%;background:rgba(0,0,0,0);border:2px solid var(--text-light);cursor:pointer;padding:0;transition:var(--transition-smooth)}.hero-indicator.active{background-color:var(--primary-color);border-color:var(--primary-color);transform:scale(1.2)}.hero-indicator:hover:not(.active){background:#fff}@media(max-width: 768px){.hero-indicator{width:5px;height:5px;border-radius:50%;background:rgba(0,0,0,0);border:1px solid #8c959f}.hero-indicator.active{width:5px;height:5px;border-radius:50%;background:#0049b4;border:none}}.hero-autoplay-toggle{display:flex;align-items:center;justify-content:center;margin-left:7px;cursor:pointer;padding:0;transition:all .3s ease;position:relative}.hero-autoplay-toggle svg{position:absolute;opacity:0;transition:opacity .3s ease}.hero-autoplay-toggle svg.active{opacity:1}.hero-autoplay-toggle:hover{background:#fff;border-color:#0049b4;box-shadow:0 2px 7px rgba(0,73,180,.2)}@media(max-width: 768px){.hero-autoplay-toggle{display:none}}.search-container{max-width:1280px;margin:0 auto;padding:0 20px;z-index:9;position:relative;margin-top:0px}@media(max-width: 768px){.search-container{margin-top:0px;padding:0 16px}}.api-search-section{position:relative;padding:0;padding-top:63px;padding-bottom:40px;overflow:visible;background:#e9f9ff}@media(max-width: 768px){.api-search-section{padding-top:30px}}@media(max-width: 1024px){.api-search-section{padding-bottom:30px}}.api-search-section .search-background{position:absolute;inset:0;background-image:url("/img/bg_main_intersect.svg");background-size:cover;background-position:center;background-repeat:no-repeat;pointer-events:none}.search-card-container{display:flex;align-items:center;justify-content:space-between;background:linear-gradient(153deg, rgba(255, 255, 255, 0.53) 0%, rgba(255, 255, 255, 0.53) 50%, rgba(255, 255, 255, 0.53) 100%);backdrop-filter:blur(7px);border:3px solid #fff;border-radius:27px;height:212px;padding:0 54px;box-shadow:0 11px 32px rgba(82,127,255,.12),inset 0 1px 1px hsla(0,0%,100%,.95),inset 0 -2px 7px hsla(0,0%,100%,.15),inset 0 0 36px hsla(0,0%,100%,.28);gap:36px}@media(max-width: 1024px){.search-card-container{height:auto;padding:36px 27px;flex-direction:column;gap:22px}}@media(max-width: 768px){.search-card-container{padding:24px 18px;gap:18px;border-radius:18px}}.search-character-box{width:80px}@media(max-width: 768px){.search-character-box{width:60px;margin:0 auto}}.search-card-left{flex:0 0 35%}@media(max-width: 1024px){.search-card-left{text-align:center;width:100%}}.search-card-title{font-size:32px;font-weight:700;line-height:1.35;color:#000;letter-spacing:-1px;margin-bottom:0px}@media(max-width: 768px){.search-card-title{font-size:22px}}.search-card-right{flex:1;display:flex;flex-direction:column;gap:20px}@media(max-width: 1024px){.search-card-right{width:100%}}.search-tags-row{display:flex;align-items:center;gap:13px;font-size:14px}@media(max-width: 1024px){.search-tags-row{justify-content:center;flex-wrap:wrap}}@media(max-width: 768px){.search-tags-row{gap:8px;font-size:12px}}.search-tag-label{font-weight:700;color:#000;white-space:nowrap}.search-tag-divider{color:rgba(0,0,0,.15);font-size:13px;user-select:none}.search-tag-list{display:flex;align-items:center;gap:7px;flex-wrap:wrap}@media(max-width: 1024px){.search-tag-list{justify-content:center}}.search-tag-link{font-size:12px;font-weight:700;color:#fff;background-color:#4546ce;padding:5px 13px;border-radius:18px;transition:var(--transition-smooth);display:inline-block;text-decoration:none}.search-tag-link:hover{background-color:#1d1e75;transform:translateY(-1px);color:#fff}.search-bar-row{width:100%}.search-card-form{width:100%}.search-card-box{position:relative;width:100%;height:72px;background:#fff;border:5px solid #0049b4;border-radius:45px;display:flex;align-items:center;padding:0 29px;transition:var(--transition-smooth)}.search-card-box:focus-within{box-shadow:0 18px 41px rgba(0,73,180,.18);transform:translateY(-2px);border-color:var(--secondary-color)}@media(max-width: 768px){.search-card-box{height:52px;border-width:3px;padding:0 15px}}.search-card-input{flex:1;width:100%;height:100%;border:none;outline:none;background:rgba(0,0,0,0);font-size:15px;font-weight:700;color:var(--text-dark);text-align:center}.search-card-input::placeholder{color:#b3b3b3;font-weight:500}@media(max-width: 768px){.search-card-input{font-size:13px}}.search-card-btn{background:none;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;position:absolute;right:29px;width:30px;height:31px;padding:0;transition:var(--transition-smooth)}.search-card-btn:hover{transform:scale(1.1)}.search-card-btn:hover svg path{fill:#008ae2}.search-card-btn svg path{transition:fill .3s ease}.api-showcase{position:relative;display:flex;align-items:center;padding:68px 0 90px;background:#fff;overflow:hidden;height:720px}@media(max-width: 1024px){.api-showcase{height:auto;padding:50px 0 60px}}@media(max-width: 768px){.api-showcase{height:auto;padding:36px 0 54px}}.api-showcase .showcase-background{position:absolute;inset:0;background-size:cover;background-position:center;background-repeat:no-repeat;pointer-events:none;background:linear-gradient(182deg, #d2efff, transparent 50%)}.api-showcase .showcase-wrapper{max-width:1280px;margin:0 auto;padding:0 20px;position:relative;width:100%;z-index:1}@media(max-width: 768px){.api-showcase .showcase-wrapper{padding:0 20px}}.api-showcase .header-title-box .section-header{text-align:left}.api-showcase .section-header{display:flex;align-items:flex-end;justify-content:space-between;margin-bottom:43px}@media(max-width: 1024px){.api-showcase .section-header{padding-right:0}}@media(max-width: 768px){.api-showcase .section-header{display:flex;align-items:center;justify-content:center;padding-right:0;margin-bottom:16px;position:relative}}.api-showcase .section-title{font-size:34px;font-weight:700;color:var(--text-dark);margin-bottom:11px;letter-spacing:-0.5px;text-align:left}@media(max-width: 1024px){.api-showcase .section-title{font-size:32px}}@media(max-width: 768px){.api-showcase .section-title{font-size:24px;color:#212529;letter-spacing:-0.4px}}.api-showcase .section-subtitle{font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:16px;color:var(--text-gray)}@media(max-width: 1024px){.api-showcase .section-subtitle{font-size:18px}}@media(max-width: 768px){.api-showcase .section-subtitle{display:none}}.api-showcase .btn-all-apis{display:inline-flex;align-items:center;justify-content:center;gap:7px;width:108px;height:40px;background:var(--secondary-color);color:#fff;font-size:13px;font-weight:700;border-radius:7px;box-shadow:0 4px 11px rgba(0,138,226,.15);transition:var(--transition-smooth)}@media(max-width: 1024px){.api-showcase .btn-all-apis{position:static;transform:none}}@media(max-width: 768px){.api-showcase .btn-all-apis{position:absolute;top:50%;right:0;transform:translateY(-50%);margin:0;width:auto;height:28px;padding:0 12px;font-size:11px;font-weight:700;border-radius:4px;gap:4px;white-space:nowrap}}.api-showcase .btn-all-apis svg{width:10px;height:15px;transition:transform .3s ease}.api-showcase .btn-all-apis svg path{stroke:#fff}@media(max-width: 768px){.api-showcase .btn-all-apis svg{width:5px;height:7px}}.api-showcase .btn-all-apis:hover{background:var(--primary-color);transform:translateY(-2px);box-shadow:0 5px 16px rgba(0,73,180,.25)}.api-showcase .btn-all-apis:hover svg{transform:translateX(4px)}.api-showcase .api-cards-container{display:flex;flex-wrap:wrap;gap:29px;justify-content:flex-start}@media(max-width: 1024px){.api-showcase .api-cards-container{flex-wrap:wrap;gap:18px;justify-content:center}}@media(max-width: 768px){.api-showcase .api-cards-container{display:flex;flex-direction:column;gap:15px}}.api-showcase .api-card{flex:0 0 calc((100% - 87px)/4);max-width:calc((100% - 87px)/4);box-sizing:border-box;height:288px;background:#fff;border:1px solid var(--border-color);border-radius:22px;padding:29px;display:flex;flex-direction:column;justify-content:space-between;cursor:pointer;transition:var(--transition-smooth);gap:0px;cursor:pointer}.api-showcase .api-card:hover{background:linear-gradient(135deg, #50caff 0%, #2088ff 100%);transform:translateY(-9px);box-shadow:var(--shadow-hover);color:#fff}.api-showcase .api-card:hover .card-title,.api-showcase .api-card:hover .card-description{color:#fff}@media(max-width: 1024px){.api-showcase .api-card{flex:0 0 calc(50% - 9px);max-width:calc(50% - 9px);width:calc(50% - 9px);min-width:250px}}@media(max-width: 768px){.api-showcase .api-card{flex:1 1 auto;max-width:100%;width:100%;height:auto;min-width:unset;min-height:180px;padding:18px 17px;border-radius:11px;border:1px solid #dadada;box-shadow:2px 2px 5px 0px rgba(185,204,222,.3);gap:18px}}.api-showcase .card-title{font-size:20px;font-weight:700;color:var(--text-dark);margin-bottom:11px;text-align:left;font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}@media(max-width: 768px){.api-showcase .card-title{font-size:17px;line-height:1;color:#000;margin-bottom:0}}.api-showcase .card-description{font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:16px;color:var(--text-gray);line-height:1.5;text-align:left;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;text-overflow:ellipsis;line-height:1.5;max-height:3em}@media(max-width: 768px){.api-showcase .card-description{font-size:14px;font-weight:500;line-height:1}}.api-showcase .card-illustration{width:81px;height:81px;display:flex;align-items:center;justify-content:center;margin-top:auto;align-self:flex-end}.api-showcase .card-illustration img{width:100%;height:100%;object-fit:contain}@media(max-width: 768px){.api-showcase .card-illustration{width:70px;height:70px}}.info-section{position:relative;padding:108px 0;background-color:#0e1b3e;color:#fff;overflow:hidden}@media(max-width: 768px){.info-section{min-height:353px;padding:68px 18px 18px}}.info-section .info-background{position:absolute;inset:0;background-image:radial-gradient(circle at 80% 20%, rgba(0, 73, 180, 0.25) 0%, rgba(14, 27, 62, 0) 60%);pointer-events:none}.info-section .container{position:relative;z-index:1}@media(max-width: 768px){.info-section .container{margin:0;padding:0;max-width:100%;width:100%}}.info-section .info-wrapper{display:flex;align-items:center;justify-content:space-between;gap:54px}@media(max-width: 1024px){.info-section .info-wrapper{flex-direction:column;gap:36px;text-align:center}}@media(max-width: 768px){.info-section .info-wrapper{display:flex;flex-direction:column;gap:18px;position:relative;text-align:center}}.info-section .info-content{flex:1;max-width:600px}@media(max-width: 1024px){.info-section .info-content{display:contents}}@media(max-width: 768px){.info-section .info-content{display:contents}}.info-section .info-title{display:flex;flex-direction:column;gap:7px;margin-bottom:22px}@media(max-width: 1024px){.info-section .info-title{font-size:32px;order:1;margin-bottom:0}}@media(max-width: 768px){.info-section .info-title{order:1;margin-bottom:0}}.info-section .title-sub{font-size:16px;font-weight:500;color:var(--secondary-color);letter-spacing:1px}@media(max-width: 768px){.info-section .title-sub{font-size:14px}}.info-section .title-highlight{font-size:41px;font-weight:700;line-height:1.25;background:linear-gradient(90deg, #FFFFFF 0%, #81d5ff 100%);-webkit-background-clip:text;-webkit-text-fill-color:rgba(0,0,0,0)}@media(max-width: 1024px){.info-section .title-highlight{font-size:36px}}@media(max-width: 768px){.info-section .title-highlight{font-size:26px}}.info-section .info-description{font-size:16px;line-height:1.7;color:hsla(0,0%,100%,.75);margin-bottom:36px}@media(max-width: 1024px){.info-section .info-description{order:3;margin-bottom:24px}}@media(max-width: 768px){.info-section .info-description{order:3;font-size:15px;line-height:1.6;margin-bottom:24px;word-break:keep-all}}.info-section .action-buttons{display:flex;gap:24px}@media(max-width: 1024px){.info-section .action-buttons{order:4;justify-content:center}}@media(max-width: 768px){.info-section .action-buttons{order:4;flex-direction:column;gap:12px;width:100%}}.info-section .action-buttons .btn-secondary{background-color:hsla(0,0%,100%,.08);color:#fff;border-width:1px;border-style:solid;border-color:hsla(0,0%,100%,.2);border-image:initial}.info-section .action-buttons .btn-primary{background-color:var(--secondary-color);color:#fff;box-shadow:rgba(0,138,226,.3) 0px 4px 14px}.info-section .action-buttons .btn-secondary:hover{background-color:hsla(0,0%,100%,.15);border-color:#fff;transform:translateY(-2px)}.info-section .action-buttons .btn-primary:hover{background-color:#00a3ff;transform:translateY(-2px);box-shadow:0 7px 23px rgba(0,138,226,.45)}.info-section .action-btn{display:inline-flex;align-items:center;justify-content:center;gap:7px;height:50px;padding:0 25px;font-size:14px;font-weight:700;border-radius:11px;transition:var(--transition-smooth)}@media(max-width: 768px){.info-section .action-btn{font-size:14px;font-weight:700;padding:0 15px;min-width:unset;width:100%;height:52px;border-radius:9px;white-space:nowrap;display:flex !important;align-items:center !important;justify-content:center !important;flex-direction:row}}.info-section .action-btn:hover{transform:translateY(-3px);box-shadow:0 7px 22px rgba(0,0,0,.15)}.info-section .btn-primary{background-color:#fff;color:#0e1b3e}.info-section .btn-primary:hover{background-color:hsla(0,0%,100%,.9)}.info-section .btn-secondary{background-color:hsla(0,0%,100%,.08);color:#fff;border:1px solid hsla(0,0%,100%,.2)}.info-section .btn-secondary:hover{background-color:hsla(0,0%,100%,.15)}.info-section .info-image-box{width:45%;display:flex;justify-content:center}.info-section .info-image-box .info-vector-art{width:100%;max-width:400px}.info-section .info-image-box img{width:100%;height:auto;object-fit:contain}@media(max-width: 1024px){.info-section .info-image-box{order:2;max-width:100%;width:100%;margin:10px 0}}@media(max-width: 768px){.info-section .info-image-box{order:2;position:relative;top:0;right:0;width:100%;height:auto;max-width:100%;margin:10px 0;flex:none}.info-section .info-image-box .info-vector-art{width:100%;max-width:200px;height:auto}.info-section .info-image-box img{width:100%;max-width:200px;height:auto}}.support-center{padding:90px 0;background-color:#f8fafc}@media(max-width: 768px){.support-center{padding:54px 18px}}.support-center .support-header{text-align:center;margin-bottom:50px}.support-center .support-title .title-regular{font-size:22px;font-weight:400;color:var(--text-gray)}.support-center .support-title .title-bold{font-size:25px;font-weight:700;color:var(--text-dark);font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}.support-center .support-grid{display:grid;grid-template-columns:repeat(auto-fit, minmax(320px, 1fr));gap:22px}.support-center .support-grid .support-card-notice{grid-column:span 1}.support-center .support-card{background-color:#fff;border:1px solid var(--border-color);border-radius:18px;padding:36px;display:flex;flex-direction:column;position:relative;overflow:hidden;transition:var(--transition-smooth)}.support-center .support-card:hover{transform:translateY(-5px);box-shadow:var(--shadow-premium)}.support-center .support-card .card-icon{font-size:32px;margin-bottom:22px;color:var(--primary-color)}.support-center .support-card h3{font-size:20px;font-weight:700;color:var(--text-dark);margin-bottom:11px}.support-center .support-card p{font-size:14px;color:var(--text-gray);line-height:1.5;width:80%}.support-center .support-card .card-arrow{position:absolute;bottom:36px;right:36px;font-size:22px;color:var(--text-light);transition:var(--transition-smooth)}.support-center .support-card:hover .card-arrow{color:var(--primary-color);transform:translateX(5px)}.support-center .support-card-notice{background-color:var(--primary-color);color:#fff;border:none}.support-center .support-card-notice h3,.support-center .support-card-notice p,.support-center .support-card-notice .card-icon,.support-center .support-card-notice .card-arrow{color:#fff}.support-center .support-card-notice:hover{box-shadow:0 14px 32px rgba(0,73,180,.3)}.support-center .support-card-faq{background-color:#eaf6ff;border-color:#bde0ff}.support-center .support-card-faq .card-icon{color:var(--secondary-color)}.support-center .support-card-qna{background-color:#e6faf9;border-color:#a9ece7}.support-center .support-card-qna .card-icon{color:#0d9488}.api-stats-section{padding:90px 0;background-color:#122144;color:#fff;position:relative;overflow:hidden}@media(max-width: 768px){.api-stats-section{padding:54px 0}}.api-stats-section .stats-background{position:absolute;inset:0;background:linear-gradient(135deg, #122144 0%, #0049B4 100%);pointer-events:none}.api-stats-section .stats-header{text-align:center;margin-bottom:58px;position:relative;z-index:1}@media(max-width: 768px){.api-stats-section .stats-header{margin-bottom:30px}}@media(max-width: 768px){.api-stats-section .stats-title{font-size:27px}}.api-stats-section .stats-title .title-top{font-size:20px;font-weight:500;color:#81d5ff}@media(max-width: 768px){.api-stats-section .stats-title .title-top{font-size:15px}}.api-stats-section .stats-title .title-highlight{font-size:34px;font-weight:700;color:#fff;line-height:1.35;-webkit-text-fill-color:rgba(0,0,0,0);background:linear-gradient(90deg, rgb(255, 255, 255) 0%, rgb(129, 213, 255) 100%) text}@media(max-width: 768px){.api-stats-section .stats-title .title-highlight{font-size:27px;word-break:keep-all}}.api-stats-section .stats-grid{display:flex;align-items:center;justify-content:space-between;max-width:1000px;margin:0 auto;position:relative;z-index:1}@media(max-width: 768px){.api-stats-section .stats-grid{flex-direction:column;gap:20px}}.api-stats-section .stat-card{flex:1;text-align:center;display:flex;flex-direction:column;align-items:center;gap:14px}.api-stats-section .stat-card:hover .stat-icon-wrapper{background-color:#fff;color:var(--primary-color);transform:translateY(-4px) scale(1.05)}@media(max-width: 768px){.api-stats-section .stat-card{gap:8px}}.api-stats-section .stat-icon-wrapper{width:65px;height:65px;background-color:hsla(0,0%,100%,.08);border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:29px;color:#81d5ff;border:1px solid hsla(0,0%,100%,.15);box-shadow:0 7px 22px rgba(0,0,0,.15);transition:var(--transition-smooth)}@media(max-width: 768px){.api-stats-section .stat-icon-wrapper{width:50px;height:50px;font-size:22px}}.api-stats-section .stat-label{font-size:14px;font-weight:500;color:#bac7de}.api-stats-section .stat-number-box{display:flex;align-items:baseline;justify-content:center}.api-stats-section .stat-number{font-family:"Outfit",sans-serif;font-size:41px;font-weight:700;color:var(--accent-color);line-height:1}@media(max-width: 768px){.api-stats-section .stat-number{font-size:32px}}.api-stats-section .stat-unit{font-size:16px;font-weight:700;color:#fff;margin-left:4px}.api-stats-section .stats-divider{width:1px;height:108px;background:linear-gradient(180deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0) 100%)}@media(max-width: 768px){.api-stats-section .stats-divider{width:100%;height:1px;background:linear-gradient(90deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0) 100%)}}.stat-number{animation:countUp .6s ease-out forwards}@keyframes countUp{from{opacity:0;transform:translateY(18px)}to{opacity:1;transform:translateY(0)}}.signup-cta-section{position:relative;min-height:296px;display:flex;align-items:center;justify-content:center;overflow:hidden}@media(max-width: 768px){.signup-cta-section{min-height:144px;padding:40px 0}}.signup-cta-section .cta-background{position:absolute;inset:0;background-size:cover;background-position:center;background-repeat:no-repeat;overflow:hidden}.signup-cta-section .container{position:relative;z-index:1;background:#ecf0fa;margin:0;width:100%;max-width:100%;padding:23px 0}.signup-cta-section .container .ctaImg{position:absolute;width:293px}@media(max-width: 1024px){.signup-cta-section .container .ctaImg{width:180px}}@media(max-width: 768px){.signup-cta-section .container .ctaImg{display:none}}.signup-cta-section .container .ctaImg.left{top:0;left:0}.signup-cta-section .container .ctaImg.right{top:-23px;right:0}@media(max-width: 768px){.signup-cta-section .container .ctaImg.right{top:-10px}}.signup-cta-section .cta-content{display:flex;flex-direction:column;align-items:center;gap:18px;text-align:center}@media(max-width: 768px){.signup-cta-section .cta-content{gap:11px}}.signup-cta-section .cta-title{font-size:27px;color:#000;line-height:normal;font-weight:600;margin:0;font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}@media(max-width: 1024px){.signup-cta-section .cta-title{font-size:29px}}@media(max-width: 768px){.signup-cta-section .cta-title{font-size:14px;font-weight:700;color:#212529;letter-spacing:-0.32px;line-height:.9994}}.signup-cta-section .btn-signup-cta{display:inline-flex;align-items:center;justify-content:center;height:54px;background-color:var(--primary-color);color:#fff;font-size:16px;font-weight:700;box-shadow:rgba(0,73,180,.25) 0px 9px 23px;gap:9px;padding:0 36px;border-radius:13px;transition:var(--transition-smooth)}.signup-cta-section .btn-signup-cta:hover{background:var(--secondary-color);transform:translateY(-2px);box-shadow:0 4px 11px rgba(0,138,226,.3)}.signup-cta-section .btn-signup-cta:active{transform:translateY(0)}@media(max-width: 768px){.signup-cta-section .btn-signup-cta{width:auto;min-width:160px;height:44px;padding:0 24px;font-size:14px;font-weight:700;border-radius:8px;line-height:normal}}.signup-cta-section .cta-divider{position:absolute;bottom:0;left:0;right:0;height:1px;background:#ddd}body.index-page-body{zoom:.9}.api-market-container{max-width:1240px;margin:0 auto;padding:60px 0px;display:flex;gap:60px;align-items:flex-start;width:100%}@media(max-width: 1024px){.api-market-container{flex-direction:column;gap:30px;padding:20px}}.api-market-hero{background:linear-gradient(to right, #eff9fe, #fcfeff);border:1px solid #e3e8f0;padding:60px 0;display:flex;justify-content:center;width:calc(100vw - 80px);position:relative;left:50%;transform:translateX(-50%);margin-top:40px;margin-bottom:60px}.api-market-hero__inner{max-width:1240px;width:100%;display:flex;align-items:center;justify-content:center;gap:138px;padding:0 37px}.api-market-hero__illust .cloud-image-placeholder{width:331px;height:229px;background:url("../img/hero_image_1.png") no-repeat center center;background-size:contain}.api-market-hero__content{display:flex;flex-direction:column;align-items:flex-start;gap:15px}.api-market-hero__badge{display:inline-flex;align-items:center;gap:8px;padding:8px 20px;background:#fff;border:1px solid #d1e6f0;border-radius:38px;font-size:13px;font-family:"Spoqa Han Sans Neo",sans-serif;font-weight:700;color:#1b4ab7}.api-market-hero__badge .dot{width:8px;height:8px;border-radius:50%;background:#1b4ab7}.api-market-hero__title{font-size:36px;font-family:"Spoqa Han Sans Neo",sans-serif;font-weight:700;color:#000;margin:0}.api-market-hero__desc{font-size:16px;font-family:"Spoqa Han Sans Neo",sans-serif;color:#64748b;line-height:1.6;margin:0}.api-market-sidebar{width:213px;background-color:#fff;border:1px solid #e3e8f0;border-radius:20px;padding:28px 30px;position:sticky;top:40px;height:fit-content;flex-shrink:0;margin-right:40px}@media(max-width: 1024px){.api-market-sidebar{width:200px;margin-right:20px}}@media(max-width: 768px){.api-market-sidebar{position:fixed;left:0;top:60px;transform:translateX(-100%);transition:transform .3s ease;box-shadow:0 8px 24px rgba(75,155,255,.15);z-index:1050;height:calc(100vh - 60px);margin:0;border-radius:0}.api-market-sidebar.mobile-open{transform:translateX(0)}}.api-sidebar-nav{display:flex;flex-direction:column;gap:26px}.api-sidebar-nav .menu-section{margin-bottom:0;border-bottom:none}.api-sidebar-nav .menu-title{padding:0;font-size:16px;font-family:"Spoqa Han Sans Neo",sans-serif;font-weight:500;color:#0d0e11;cursor:pointer;transition:color .2s;display:flex;align-items:center;justify-content:space-between;position:relative}.api-sidebar-nav .menu-title:hover{color:#2a69de;background-color:rgba(0,0,0,0)}.api-sidebar-nav .menu-title.active{font-weight:700;color:#2a69de}.api-sidebar-nav .accordion-section .menu-title.accordion-trigger .menu-title-text{flex:1}.api-sidebar-nav .accordion-section .menu-title.accordion-trigger .accordion-icon{display:flex;align-items:center;justify-content:center;width:20px;height:20px;transition:transform .3s ease}.api-sidebar-nav .accordion-section .menu-title.accordion-trigger .accordion-icon i{font-size:12px;color:#64748b;transition:all .3s ease}.api-sidebar-nav .accordion-section .menu-title.accordion-trigger:hover .accordion-icon i{color:#0049b4}.api-sidebar-nav .accordion-section .menu-title.accordion-trigger.expanded .accordion-icon{transform:rotate(180deg)}.api-sidebar-nav .accordion-section .menu-title.accordion-trigger.active .accordion-icon i{color:#0049b4}.api-sidebar-nav .api-list{padding-left:16px;margin-top:0;margin-bottom:0}.api-sidebar-nav .api-list.accordion-content{max-height:0;overflow:hidden;transition:max-height .3s ease,padding .3s ease,margin .3s ease}.api-sidebar-nav .api-list.accordion-content.expanded{max-height:500px;margin-top:4px;margin-bottom:8px}.api-sidebar-nav .api-item{padding:8px 16px;font-size:14px;color:#64748b;cursor:pointer;border-radius:6px;transition:all .3s ease;display:flex;align-items:center;position:relative;margin-bottom:4px}.api-sidebar-nav .api-item::before{content:"•";margin-right:8px;color:#0049b4;opacity:.5}.api-sidebar-nav .api-item:hover{background-color:rgba(0,73,180,.05);color:#1a1a2e;padding-left:20px}.api-sidebar-nav .api-item:hover::before{opacity:1}.api-sidebar-nav .api-item.active{background-color:rgba(0,73,180,.1);color:#0049b4;font-weight:500}.api-sidebar-nav .api-item.active::before{opacity:1}.api-sidebar-nav .api-item .api-name{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.api-market-content{flex:1;width:100%;min-height:100vh;display:flex;flex-direction:column}@media(max-width: 768px){.api-market-content{padding:24px 16px}}.api-market-content .api-result-count{font-size:20px;color:#000;white-space:nowrap}.api-market-content .api-result-count strong{color:#0049b4;font-weight:600}.api-mobile-toggle{display:none !important}.api-market-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:24px}@media(max-width: 768px){.api-market-header{flex-direction:column;align-items:flex-start;gap:24px}}.api-market-title h1{font-size:14px;color:#64748b;font-weight:400;margin-bottom:4px}.api-market-title h2{font-size:32px;font-weight:700;color:#1a1a2e}.api-market-search{position:relative;width:228px}@media(max-width: 768px){.api-market-search{width:100%}}.api-market-search form{position:relative;display:flex;align-items:center}.api-market-search input{flex:1;height:44px;padding:0 40px 0 16px;border:1px solid #dadada;border-radius:12px;font-size:14px;outline:none;transition:all .3s ease}.api-market-search input:focus{border-color:#0049b4;box-shadow:0 0 0 3px rgba(0,73,180,.1)}.api-market-search input::placeholder{color:#7f8a95}.api-market-search .search-submit-btn{position:absolute;right:8px;top:50%;transform:translateY(-50%);width:20px;height:20px;background:rgba(0,0,0,0);border:none;cursor:pointer;display:flex;align-items:center;justify-content:center}.api-market-search .search-submit-btn svg{width:100%;height:100%}.api-card-grid{display:grid;grid-template-columns:repeat(auto-fill, minmax(280px, 1fr));gap:24px;width:100%}.api-card{background:#fff;border-radius:20px;border:1px solid #ddd;box-shadow:0px 4px 2px rgba(194,194,194,.25);cursor:pointer;position:relative;width:100%;height:312px;overflow:hidden;transition:transform .3s ease,box-shadow .3s ease,background .3s ease}.api-card:hover,.api-card:focus,.api-card:active{transform:translateY(-5px);box-shadow:0px 8px 12px rgba(194,194,194,.4);background:linear-gradient(to bottom, #50caff, #2088ff);border:none}.api-card:hover .api-card-badge,.api-card:focus .api-card-badge,.api-card:active .api-card-badge{background-color:#d8f0ff}.api-card:hover .api-card-title,.api-card:hover .api-card-desc,.api-card:focus .api-card-title,.api-card:focus .api-card-desc,.api-card:active .api-card-title,.api-card:active .api-card-desc{color:#fff}.api-card-badges{position:absolute;top:39px;left:32px;right:32px;display:flex;align-items:center;gap:8px;min-width:0}.api-card-badge{background-color:#f2f2f3;border-radius:10px;padding:2px 20px;display:inline-flex;align-items:center;justify-content:center;min-width:0}.api-card-badge span{font-size:14px;font-family:"Spoqa Han Sans Neo",sans-serif;font-weight:500;color:#1b4ab7;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.api-card-status{flex-shrink:0;display:inline-flex;align-items:center;padding:3px 12px;border-radius:10px;font-size:12px;font-family:"Spoqa Han Sans Neo",sans-serif;font-weight:600;white-space:nowrap;background:#eef1f5;color:#4b5563}.api-card-status:empty{display:none}.api-card-status.is-outage{background:#feeae7;color:#c8362f}.api-card-status.is-maintenance{background:#e7f0fb;color:#0a66c2}.api-card-status.is-degraded{background:#fef3c7;color:#c77800}.api-card-status.is-normal{background:#e7f6ec;color:#1b8c4a}.api-card-title{position:absolute;top:86px;left:36px;right:40px;font-size:18px;font-family:"Spoqa Han Sans Neo",sans-serif;font-weight:700;color:#000;margin:0;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;text-overflow:ellipsis;word-break:break-word;text-align:left;white-space:normal}.api-card-desc{position:absolute;top:135px;left:36px;right:40px;font-size:14px;font-family:"Spoqa Han Sans Neo",sans-serif;font-weight:400;color:#64748b;line-height:1.5;margin:0;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;text-overflow:ellipsis;word-break:break-word;text-align:left;white-space:normal}.api-card-image{position:absolute;bottom:28px;right:20px;width:93px;height:88px;display:flex;align-items:center;justify-content:center;pointer-events:none}.api-card-image img{max-width:100%;max-height:100%;object-fit:contain}.api-empty-state{text-align:center;padding:80px 24px;color:#64748b}.api-empty-state .empty-icon{font-size:80px;margin-bottom:24px;opacity:.5}.api-empty-state h3{font-size:24px;font-weight:600;color:#1a1a2e;margin-bottom:16px}.api-empty-state p{font-size:16px;color:#64748b}.api-loading{display:flex;justify-content:center;align-items:center;padding:80px}.api-loading .spinner{width:48px;height:48px;border:4px solid #e2e8f0;border-top-color:#0049b4;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.api-mobile-overlay{display:none;position:fixed;top:60px;left:0;right:0;bottom:0;background-color:rgba(0,0,0,.5);z-index:1049;opacity:0;transition:opacity .3s ease;pointer-events:none}@media(max-width: 768px){.api-mobile-overlay{display:block}}.api-mobile-overlay.active{opacity:1;pointer-events:auto}.api-detail-content{display:flex;flex-direction:column;gap:32px}.api-detail-tabs{display:flex;gap:8px;border-bottom:2px solid #e2e8f0;margin-bottom:32px}.api-detail-tabs .tab-button{padding:16px 24px;background:rgba(0,0,0,0);border:none;border-bottom:3px solid rgba(0,0,0,0);font-size:16px;font-weight:500;color:#64748b;cursor:pointer;transition:all .3s ease;position:relative;bottom:-2px;display:inline-block;text-decoration:none}.api-detail-tabs .tab-button:hover{color:#0049b4;background-color:rgba(0,73,180,.05)}.api-detail-tabs .tab-button.active{color:#0049b4;font-weight:600;border-bottom-color:#0049b4}.tab-content{display:none;flex-direction:column;gap:32px;min-height:200px}.tab-content.active{display:flex}#testbed-tab{gap:0}#testbed-tab #swagger-ui .info{margin:24px 0}.testbed-auth-gate{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;padding:calc(32px*2) 24px;text-align:center;background:#fff;border:1px solid #e2e8f0;border-radius:12px;box-shadow:0 2px 4px rgba(0,0,0,.05)}.testbed-auth-gate .testbed-auth-gate__icon{font-size:40px;line-height:1}.testbed-auth-gate .testbed-auth-gate__title{margin:0;font-size:20px;font-weight:700;color:#1a1a2e}.testbed-auth-gate .testbed-auth-gate__desc{margin:0;font-size:16px;color:#64748b}.testbed-auth-gate .btn{margin-top:8px;min-width:160px}.testbed-app-panel{margin:8px 0 0;padding:24px;background:#fff;border:none;border-radius:12px;box-shadow:none}.testbed-app-panel .testbed-app-panel__head{display:flex;align-items:baseline;flex-wrap:wrap;gap:4px 8px;margin-bottom:16px}.testbed-app-panel .testbed-app-panel__title{position:relative;padding-left:16px;font-size:16px;font-weight:700;color:#1a1a2e;letter-spacing:-0.01em}.testbed-app-panel .testbed-app-panel__title::before{content:"";position:absolute;left:0;top:50%;transform:translateY(-50%);width:4px;height:15px;background:#0049b4;border-radius:2px}.testbed-app-panel .testbed-app-panel__desc{font-size:14px;color:#64748b}.testbed-app-panel .testbed-app-field{position:relative;max-width:380px}.testbed-app-panel .testbed-app-field::after{content:"";position:absolute;right:16px;top:50%;width:9px;height:9px;margin-top:-6px;border-right:2px solid #64748b;border-bottom:2px solid #64748b;transform:rotate(45deg);pointer-events:none}.testbed-app-panel #apps{width:100%;height:46px;padding:0 42px 0 16px;font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:14px;color:#1a1a2e;background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;appearance:none;-webkit-appearance:none;-moz-appearance:none;cursor:pointer;transition:all .15s ease}.testbed-app-panel #apps:hover:not(:disabled){border-color:#94a3b8;background:#fff}.testbed-app-panel #apps:focus{outline:none;background:#fff;border-color:#0049b4;box-shadow:0 0 0 3px rgba(0,73,180,.14)}.testbed-app-panel #apps:disabled{color:#94a3b8;background:#f8fafc;cursor:not-allowed}.testbed-app-panel .testbed-app-notice{margin:16px 0 0;padding:11px 16px;font-size:14px;line-height:1.6;color:#64748b;background:#eff6ff;border:1px solid rgba(0,73,180,.18);border-left:3px solid #0049b4;border-radius:6px}.testbed-app-panel .testbed-app-notice::before{content:"ⓘ ";color:#0049b4;font-weight:700}@media(max-width: 768px){.testbed-app-panel{padding:16px}.testbed-app-panel .testbed-app-field{max-width:100%}}.api-basic-info-header{display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap}.api-status-inline{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.api-status-inline .api-status-inline__label{font-size:13px;font-weight:500;color:#4b5563}.api-status-inline .api-status-inline__badge{display:inline-flex;align-items:center;font-size:12px;font-weight:600;padding:5px 12px;border-radius:999px;letter-spacing:.02em;white-space:nowrap;background:#eef1f5;color:#4b5563}.api-status-inline .api-status-inline__badge.is-outage{background:#feeae7;color:#c8362f}.api-status-inline .api-status-inline__badge.is-maintenance{background:#e7f0fb;color:#0a66c2}.api-status-inline .api-status-inline__badge.is-degraded{background:#fef3c7;color:#c77800}.api-status-inline .api-status-inline__badge.is-normal{background:#e7f6ec;color:#1b8c4a}.api-status-inline .api-status-inline__badge.is-loading{color:#6b7280}.api-status-inline .api-status-inline__badge.is-muted{background:#eef1f5;color:#6b7280}.api-apply-footer{display:flex;justify-content:center;margin-top:32px}.api-overview-card{background:rgba(0,0,0,0);border-radius:0;padding:0;padding-bottom:32px;display:flex;flex-direction:column;gap:24px}.api-overview-card .api-overview-header{display:flex;flex-direction:row;align-items:center;gap:16px}@media(max-width: 768px){.api-overview-card .api-overview-header{flex-direction:column;align-items:flex-start}}.api-overview-card .api-method-badge{background:#0049b4;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;padding:8px 16px;height:100%;color:#fff;font-size:14px;font-weight:600;border-radius:6px;text-transform:uppercase;letter-spacing:.5px}.api-overview-card .api-endpoint{flex:1}.api-overview-card .api-endpoint code{display:block;padding:8px 16px;background-color:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;font-size:18px;font-family:"Courier New",monospace;color:#1a1a2e;word-break:break-all}.api-overview-card .api-simple-description{padding:16px 0;border-bottom:1px solid #e2e8f0}.api-overview-card .api-simple-description p{font-size:16px;color:#64748b;line-height:1.6;margin:0}.api-overview-card .api-overview-info h4{font-size:18px;font-weight:600;color:#1a1a2e;margin-bottom:16px}.api-details-grid{display:flex;flex-direction:column;gap:24px}.api-detail-card{background:rgba(0,0,0,0);border-radius:0;padding:0;padding-bottom:32px;border-bottom:1px solid #e2e8f0}.api-detail-card:last-child{border-bottom:none;padding-bottom:0}.api-detail-card h3{font-size:20px;font-weight:600;color:#1a1a2e;margin-bottom:16px;padding-bottom:0;border-bottom:none}.api-detail-card .detail-content:not(.editor-content){font-size:14px;color:#64748b;line-height:1.6}.api-detail-card .detail-content:not(.editor-content) pre{background-color:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;padding:16px;overflow-x:auto;margin:0}.api-detail-card .detail-content:not(.editor-content) pre code{font-family:"Courier New",monospace;font-size:14px;color:#1a1a2e;white-space:pre-wrap;word-break:break-word}.api-detail-card .detail-content:not(.editor-content) table{width:100%;border-collapse:collapse;margin:16px 0;background-color:#fff;border:1px solid #e2e8f0;border-radius:8px;overflow:hidden}.api-detail-card .detail-content:not(.editor-content) table thead{background-color:#eff6ff}.api-detail-card .detail-content:not(.editor-content) table th{text-align:left;padding:8px 16px;font-size:14px;font-weight:600;color:#1a1a2e;border-bottom:2px solid #e2e8f0;background-color:rgba(0,73,180,.05)}.api-detail-card .detail-content:not(.editor-content) table th:not(:last-child){border-right:1px solid #e2e8f0}.api-detail-card .detail-content:not(.editor-content) table td{padding:8px 16px;font-size:14px;color:#64748b;border-bottom:1px solid #e2e8f0}.api-detail-card .detail-content:not(.editor-content) table td:not(:last-child){border-right:1px solid #e2e8f0}.api-detail-card .detail-content:not(.editor-content) table tr{transition:all .3s ease}.api-detail-card .detail-content:not(.editor-content) table tr:last-child td{border-bottom:none}.api-detail-card .detail-content:not(.editor-content) table tr:hover{background-color:rgba(0,73,180,.02)}.api-detail-card .detail-content:not(.editor-content) table tbody tr:nth-child(even){background-color:rgba(248,250,252,.3)}.api-info-table{width:100%;border-collapse:collapse}.api-info-table tr{border-bottom:1px solid #e2e8f0}.api-info-table tr:last-child{border-bottom:none}.api-info-table th{text-align:left;padding:8px 16px;font-size:14px;font-weight:600;color:#1a1a2e;width:30%;background-color:#f8fafc}.api-info-table td{padding:8px 16px;font-size:14px;color:#64748b}.api-info-table td code{display:inline-block;padding:2px 8px;background-color:#f8fafc;border-radius:6px;font-family:"Courier New",monospace;font-size:12px;color:#0049b4}@media(max-width: 768px){.api-detail-tabs{gap:0;margin-bottom:24px}.api-detail-tabs .tab-button{flex:1;padding:8px 16px;font-size:14px;text-align:center}.api-overview-card{display:flex;flex-direction:column;gap:16px;padding-bottom:24px}.api-overview-card .api-overview-header{display:flex;flex-direction:column;align-items:flex-start;gap:8px;width:100%}.api-overview-card .api-method-badge{padding:6px 12px;font-size:12px;flex-shrink:0;height:auto}.api-overview-card .api-endpoint{width:100%;flex:none}.api-overview-card .api-endpoint code{display:block;padding:4px 8px;font-size:14px;word-break:break-all;overflow-wrap:break-word}.api-overview-card .api-overview-info{width:100%;margin-top:8px}.api-overview-card .api-overview-info h4{font-size:14px;margin-bottom:8px}.api-overview-card .api-overview-info .api-info-table{width:100%}.api-overview-card .api-simple-description{width:100%;padding:8px 0}.api-overview-card .api-simple-description p{font-size:14px}.api-overview-card .detail-content{width:100%}.api-detail-card{padding-bottom:24px}.api-detail-card h3{font-size:16px;margin-bottom:8px}.api-detail-card .detail-content.editor-content{overflow-x:auto;-webkit-overflow-scrolling:touch}.api-detail-card .detail-content:not(.editor-content){font-size:12px;overflow-x:auto;-webkit-overflow-scrolling:touch}.api-detail-card .detail-content:not(.editor-content) table{min-width:500px;margin:8px 0}.api-detail-card .detail-content:not(.editor-content) table th,.api-detail-card .detail-content:not(.editor-content) table td{padding:4px 8px;font-size:12px;white-space:nowrap}.api-detail-card .detail-content:not(.editor-content) pre{padding:8px;overflow-x:auto;-webkit-overflow-scrolling:touch}.api-detail-card .detail-content:not(.editor-content) pre code{font-size:12px;white-space:pre;word-break:normal}.api-info-table{display:block;overflow-x:auto;-webkit-overflow-scrolling:touch}.api-info-table tr{display:table-row}.api-info-table th{padding:4px 8px;font-size:12px;width:auto;min-width:80px;white-space:nowrap}.api-info-table td{padding:4px 8px;font-size:12px}.api-info-table td code{padding:1px 4px;font-size:10px}.api-details-grid{gap:16px}.tab-content{gap:24px;min-height:150px;max-width:100%;overflow-x:hidden}.api-detail-content{max-width:100%;overflow-x:hidden}.api-market-content{width:100%;overflow-x:hidden}.api-card{height:285px}}.login-page{display:flex;align-items:center;justify-content:center;padding:100px 20px;position:relative;border-radius:12px;margin-top:60px;margin-bottom:50px}.login-container{width:100%;max-width:504px;margin:0 auto;position:relative}.login-card{background:rgba(0,0,0,0);padding:0;display:flex;flex-direction:column;align-items:center}@media(max-width: 576px){.login-card{padding:0 20px}}.login-header{display:flex;flex-direction:column;align-items:center;margin-bottom:40px;width:100%}.login-header .header-main-row{display:flex;align-items:center;justify-content:center;gap:8px;margin-bottom:12px}.login-lock-icon{width:40px;height:40px;display:flex;align-items:center;justify-content:center}.login-lock-icon svg{stroke:#000}.login-title{font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:36px;font-weight:700;color:#000;margin:0;line-height:1}.login-message-sub{font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:18px;font-weight:300;color:#80878d;text-align:center;margin:0;line-height:1.2}.login-form{width:100%;margin-bottom:0}.login-form .form-group{margin-bottom:16px;position:relative}.login-form .form-group.input-with-icon .input-icon{position:absolute;left:20px;top:50%;transform:translateY(-50%);display:flex;align-items:center;justify-content:center;z-index:2}.login-form .form-group.input-with-icon .input-icon svg{stroke:#767e89}.login-form .form-group.input-with-icon .form-input{padding-left:56px}.login-form .form-input{width:100%;height:58px;padding:0 20px;font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:16px;font-weight:300;color:#000;background:#fcfcfc;border:1px solid #dfdfdf;border-radius:5px;outline:none;transition:all .3s ease}.login-form .form-input::placeholder{color:#767e89}.login-form .form-input:focus{border-color:#1b4ab7;background:#fff}.login-form .form-input.error{border-color:#ff6b6b}.login-form .form-checkbox{display:flex;align-items:center;justify-content:flex-start;gap:8px;margin-top:14px;margin-bottom:30px}.login-form .form-checkbox input[type=checkbox]{width:14px;height:14px;border:1px solid #dfdfdf;border-radius:2px;cursor:pointer;appearance:none;background:#fff;position:relative;flex-shrink:0}.login-form .form-checkbox input[type=checkbox]:checked{background:#1b4ab7;border-color:#1b4ab7}.login-form .form-checkbox input[type=checkbox]:checked::after{content:"";position:absolute;left:50%;top:45%;transform:translate(-50%, -50%) rotate(45deg);width:3px;height:6px;border:solid #fff;border-width:0 1.5px 1.5px 0}.login-form .form-checkbox label{font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:12px;font-weight:300;color:#767e89;cursor:pointer;user-select:none;line-height:1;white-space:nowrap}.login-button{width:100%;height:58px;padding:10px;font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:16px;font-weight:500;color:#fff;background:#1b4ab7;border:none;border-radius:5px;cursor:pointer;transition:all .3s ease;margin-bottom:30px;line-height:20px}.login-button:hover{background:rgb(25.65,70.3,173.85)}.login-button:active{background:rgb(24.3,66.6,164.7)}.login-button:disabled{opacity:.6;cursor:not-allowed}.login-links-container{display:flex;justify-content:center;align-items:center;width:100%}.login-links-container .link-btn{flex:1;height:38px;display:inline-flex;align-items:center;justify-content:center;background:#ecf0fa;border:1px solid #2a69de;font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:14px;font-weight:400;color:#2a69de;text-decoration:none;transition:all .2s ease;line-height:20px;text-align:center}.login-links-container .link-btn.first{border-top-left-radius:8px;border-bottom-left-radius:8px;border-right:none}.login-links-container .link-btn.middle{border-right:none}.login-links-container .link-btn.last{border-top-right-radius:8px;border-bottom-right-radius:8px}.login-links-container .link-btn:hover{background:rgb(220.61,227.85,245.95)}.login-links-container .link-btn:active{background:rgb(205.22,215.7,241.9)}.login-alert{width:100%;margin-bottom:24px;padding:12px 16px;border-radius:8px;font-size:14px;display:flex;align-items:center;gap:8px}.login-alert.alert-error{background:rgba(255,107,107,.1);color:#ff6b6b;border:1px solid rgba(255,107,107,.2)}.login-alert.alert-success{background:rgba(107,207,127,.1);color:#6bcf7f;border:1px solid rgba(107,207,127,.2)}.login-alert.alert-info{background:rgba(0,73,180,.1);color:#0049b4;border:1px solid rgba(0,73,180,.2)}.login-alert i{font-size:18px}.login-loading{position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(237,249,254,.9);display:flex;align-items:center;justify-content:center;z-index:10;opacity:0;pointer-events:none;transition:opacity .3s ease}.login-loading.active{opacity:1;pointer-events:all}.login-loading .spinner{width:40px;height:40px;border:3px solid #ddd;border-top-color:#0049b4;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}@media(max-width: 768px){.login-page{min-height:calc(100vh - 44px);padding:100px 20px;margin-top:0;margin-bottom:0;border-radius:0}.login-container{max-width:100%;padding:0}.login-card{padding:0}.login-logo{width:64px;height:64px;margin-bottom:20px}.login-message{font-size:14px;font-weight:400;color:#212529;margin-bottom:32px;line-height:1.4}.login-form .form-group{margin-bottom:12px}.login-form .form-input{width:100%;height:44px;padding:0 16px;font-size:14px;border-radius:8px;border-color:#dadada}.login-form .form-input::placeholder{font-size:14px;color:#8c959f}.login-form .form-checkbox{justify-content:flex-end;margin-top:16px;margin-bottom:20px;margin-right:0;gap:6px}.login-form .form-checkbox input[type=checkbox]{width:20px;height:20px;border-radius:4px}.login-form .form-checkbox label{font-size:13px;color:#212529;line-height:20px}.login-button{width:100%;height:44px;font-size:16px;font-weight:700;border-radius:8px;margin-bottom:24px}.login-links{gap:12px}.login-links a{font-size:14px;font-weight:400;color:#515961}.login-links .link-separator{font-size:14px;color:#515961}.login-alert{max-width:335px;margin-bottom:16px;padding:10px 14px;font-size:13px}}.account-recovery-page{display:flex;align-items:center;justify-content:center;padding:100px 20px;position:relative;border-radius:12px;margin-bottom:50px}.account-recovery-container{width:100%;max-width:650px;margin:0 auto;position:relative;padding:24px}.account-recovery-card{background:#fff;border-radius:20px;box-shadow:0 8px 32px rgba(0,0,0,.1);padding:36px 36px 28px;display:flex;flex-direction:column;align-items:stretch;position:relative}@media(max-width: 576px){.account-recovery-card{padding:24px 20px 20px;border-radius:16px}}.account-recovery-logo{display:none}.account-recovery-title{display:none}.account-recovery-tabs{display:flex;gap:0;margin-bottom:28px;width:100%;background:rgba(0,0,0,0);padding:0}.account-recovery-tabs .tab-link{flex:1;display:flex;align-items:center;justify-content:center;padding:14px 20px;font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:15px;font-weight:400;color:#adb5bd;text-decoration:none;text-align:center;background:#fff;border:1.5px solid rgba(0,0,0,0);border-bottom:1.5px solid #dee2e6;border-radius:0;transition:all .2s ease;cursor:pointer;position:relative}.account-recovery-tabs .tab-link:hover{color:#0049b4}.account-recovery-tabs .tab-link.active{color:#0049b4;font-weight:700;border-top:1.5px solid #0049b4;border-left:1.5px solid #0049b4;border-right:1.5px solid #0049b4;border-bottom:2px solid #fff;border-radius:8px 8px 0px 0px}@media(max-width: 576px){.account-recovery-tabs .tab-link{padding:12px 10px;font-size:13px}}.account-alert{width:100%;margin-bottom:20px;padding:14px 18px;border-radius:12px;font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:15px;display:flex;align-items:center;gap:10px;animation:slideDown .3s ease}.account-alert i{font-size:18px;flex-shrink:0}.account-alert.alert-error{background:rgba(255,107,107,.1);color:#ff6b6b;border:1px solid rgba(255,107,107,.3)}.account-alert.alert-success{background:rgba(107,207,127,.1);color:#6bcf7f;border:1px solid rgba(107,207,127,.3)}.account-alert.alert-info{background:rgba(0,73,180,.1);color:#0049b4;border:1px solid rgba(0,73,180,.3)}.account-recovery-form{width:100%;margin-bottom:0;background:rgba(0,0,0,0);padding:0}.account-recovery-form .form-group{display:flex;flex-direction:column;gap:0;margin-bottom:12px}.account-recovery-form .form-group:last-of-type{margin-bottom:0}.account-recovery-form .form-label{display:none}.account-recovery-form .form-input{width:100%;height:48px;padding:0 16px;font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:14px;font-weight:400;color:#212529;background:#fff;border:1px solid #e2e8f0;border-radius:10px;outline:none;transition:all .25s ease}.account-recovery-form .form-input::placeholder{color:#b0b8c1}.account-recovery-form .form-input:hover{border-color:#3ba4ed}.account-recovery-form .form-input:focus{border-color:#3ba4ed;box-shadow:0 0 0 3px rgba(59,164,237,.12)}.account-recovery-form .form-input:disabled{background:#f8f9fa;border-color:#e2e8f0;color:#94a3b8;cursor:not-allowed}.account-recovery-form .form-input.error{border-color:#ed5b5b}.account-recovery-form .form-select{height:48px;padding:0 24px 0 12px;font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:14px;font-weight:400;color:#515151;background:#fff;border:1px solid #e2e8f0;border-radius:10px;outline:none;cursor:pointer;transition:all .25s ease;appearance:none;-webkit-appearance:none;-moz-appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='5' viewBox='0 0 10 5'%3E%3Cpath fill='%231D1B20' d='M5 5L0 0h10z'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 8px center;background-size:10px 5px}.account-recovery-form .form-select:hover{border-color:#3ba4ed}.account-recovery-form .form-select:focus{border-color:#3ba4ed;box-shadow:0 0 0 3px rgba(59,164,237,.12)}.account-recovery-form .form-select:disabled{background-color:#f8f9fa;border-color:#e2e8f0;color:#94a3b8;cursor:not-allowed;opacity:.7}.account-recovery-form .form-select option{padding:8px;font-size:14px}.reset-method-group{margin-bottom:16px !important}.reset-method-tabs{display:flex;gap:8px;background:#f0f2f5;border-radius:10px;padding:4px}.reset-method-tabs .reset-method-tab{flex:1;display:flex;align-items:center;justify-content:center;cursor:pointer;margin:0}.reset-method-tabs .reset-method-tab input[type=radio]{display:none}.reset-method-tabs .reset-method-tab span{display:flex;align-items:center;justify-content:center;width:100%;padding:9px 12px;font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:13px;font-weight:600;color:#8c959f;border-radius:7px;transition:all .2s ease;text-align:center}.reset-method-tabs .reset-method-tab input[type=radio]:checked+span{background:#fff;color:#212529;box-shadow:0 2px 6px rgba(0,0,0,.09)}.reset-method-tabs .reset-method-tab:hover span{color:#3ba4ed}.phone-input-group{display:flex;align-items:center;gap:6px;width:100%}.phone-input-group .phone-prefix{width:90px;min-width:90px;flex-shrink:0}.phone-input-group .phone-middle,.phone-input-group .phone-last{flex:1;min-width:0}.phone-input-group .phone-separator{display:flex;align-items:center;justify-content:center;color:#b0b8c1;font-size:14px;flex-shrink:0;width:10px;height:auto;background:rgba(0,0,0,0)}.phone-input-group .phone-separator::before{content:"-"}@media(max-width: 576px){.phone-input-group{flex-wrap:wrap;gap:6px}.phone-input-group .phone-prefix{flex:1;width:auto}.phone-input-group .phone-separator{display:none}}.auth-number-group{margin-top:0}.auth-input-group{position:relative;display:flex;align-items:center;width:100%;gap:8px}.auth-input-group.mg-y{margin-top:20px;margin-bottom:12px}.auth-input-group .auth-input{flex:1;min-width:0}.auth-input-group .auth-timer{position:absolute;right:20px;top:50%;transform:translateY(-50%);font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:13px;font-weight:500;color:#ed5b5b;pointer-events:none}@media(max-width: 576px){.auth-input-group .auth-timer{font-size:12px;right:100px}}.auth-request-button,.auth-verify-button{height:48px;padding:0 16px;font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:13px;font-weight:700;color:#fff;background:#3ba4ed;border:none;border-radius:10px;cursor:pointer;transition:all .25s ease;flex-shrink:0;white-space:nowrap;line-height:1}.auth-request-button:hover,.auth-verify-button:hover{background:rgb(37.3117757009,153.9304672897,235.0082242991);transform:none !important}.auth-request-button:active,.auth-verify-button:active{background:rgb(21.411588785,146.3125233645,233.148411215)}.auth-request-button:disabled,.auth-verify-button:disabled{opacity:.5;cursor:not-allowed}@media(max-width: 576px){.auth-request-button,.auth-verify-button{width:100%;height:44px;font-size:13px;margin-top:4px}}.account-recovery-card .form-actions{display:flex;justify-content:center;gap:12px;margin-top:28px;padding:0;border-top:none;background:rgba(0,0,0,0)}.account-recovery-card .form-actions .cancel-button,.account-recovery-card .form-actions .submit-button{flex:1;height:48px;padding:0 8px;font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:15px;font-weight:700;border:none;border-radius:10px;cursor:pointer;transition:all .25s ease;line-height:1;display:flex;align-items:center;justify-content:center;text-decoration:none}.account-recovery-card .form-actions .cancel-button{color:#5f666c;background:#f0f2f5}.account-recovery-card .form-actions .cancel-button:hover{background:rgb(225.45,229.39,235.3)}.account-recovery-card .form-actions .cancel-button:active{background:rgb(210.9,216.78,225.6)}.account-recovery-card .form-actions .submit-button{color:#fff;background:#0049b4}.account-recovery-card .form-actions .submit-button:hover{background:#06367d}.account-recovery-card .form-actions .submit-button:active{background:rgb(0,65.7,162)}.account-recovery-card .form-actions .submit-button:disabled{opacity:.6;cursor:not-allowed}@media(max-width: 576px){.account-recovery-card .form-actions{gap:10px}.account-recovery-card .form-actions .cancel-button,.account-recovery-card .form-actions .submit-button{height:44px;font-size:14px}}.loading-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(237,249,254,.9);display:none;align-items:center;justify-content:center;z-index:1000;transition:opacity .3s ease}.loading-overlay.active{display:flex}.loading-overlay .spinner{width:40px;height:40px;border:3px solid #ddd;border-top-color:#0049b4;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}@keyframes slideDown{from{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}@media(max-width: 768px){.account-recovery-page{padding:20px 12px;margin-top:40px;margin-bottom:40px}.account-recovery-container{max-width:100%}}.account-recovery-result{width:100%;padding:35px 0px;border-radius:0 0 12px 12px;text-align:center}@media(max-width: 576px){.account-recovery-result{padding:40px 20px}}.account-recovery-result .result-header{margin-bottom:40px}.account-recovery-result .result-header .result-icon{font-size:60px;color:#6bcf7f;margin-bottom:20px;display:block}@media(max-width: 576px){.account-recovery-result .result-header .result-icon{font-size:48px}}.account-recovery-result .result-header .result-title{font-size:24px;color:#212529;margin-bottom:12px}@media(max-width: 576px){.account-recovery-result .result-header .result-title{font-size:20px}}.account-recovery-result .result-header .result-description{font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:16px;font-weight:400;color:#5f666c;margin:0}.account-recovery-result .result-header .result-description strong{color:#0049b4;font-weight:700}@media(max-width: 576px){.account-recovery-result .result-header .result-description{font-size:14px}}.found-users-list{max-width:600px;margin:0 auto 40px}.found-users-list .found-user-item{background:#fff;border:1px solid #dadada;border-radius:12px;padding:20px 24px;margin-bottom:12px;transition:all .3s ease}.found-users-list .found-user-item:last-child{margin-bottom:0}.found-users-list .found-user-item:hover{border-color:#3ba4ed;box-shadow:0 2px 8px rgba(59,164,237,.15)}.found-users-list .found-user-item .user-info{display:flex;align-items:center;justify-content:center;gap:12px;flex-wrap:wrap}@media(max-width: 576px){.found-users-list .found-user-item .user-info{flex-direction:column;gap:6px}}.found-users-list .found-user-item .user-email{font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:20px;font-weight:700;color:#212529}@media(max-width: 576px){.found-users-list .found-user-item .user-email{font-size:18px}}.found-users-list .found-user-item .user-date{font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:16px;font-weight:400;color:#8c959f}@media(max-width: 576px){.found-users-list .found-user-item .user-date{font-size:14px}}.result-info-box{max-width:600px;margin:0 auto;background:rgba(0,73,180,.05);border:1px solid rgba(0,73,180,.2);border-radius:12px;padding:16px 24px}.result-info-box.info-box-highlight{background:linear-gradient(135deg, rgba(0, 73, 180, 0.06) 0%, rgba(0, 73, 180, 0.02) 100%);border:1.5px solid rgba(0,73,180,.25);box-shadow:0 8px 24px rgba(0,73,180,.08);display:flex;align-items:center;gap:12px;animation:fadeInUp .6s cubic-bezier(0.16, 1, 0.3, 1) both;transform:translateY(15px);opacity:0}.result-info-box.info-box-highlight .highlight-icon{display:flex;justify-content:center;align-items:center;animation:pulseIcon 2s infinite ease-in-out}.result-info-box.info-box-highlight .info-text{font-size:16px;line-height:1.6;color:#1e293b;text-align:center}.result-info-box.info-box-highlight .info-text strong{color:#0049b4;font-weight:700;font-size:19px}.result-info-box.info-box-highlight .info-text p{color:#4685ef}.result-info-box .info-text{font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:15px;font-weight:400;color:#5f666c;margin:0;display:flex;align-items:center;justify-content:center;gap:8px;flex-wrap:wrap}.result-info-box .info-text i{color:#0049b4;font-size:16px}.result-info-box .info-text .info-link{color:#0049b4;font-weight:700;text-decoration:underline;transition:color .3s ease}.result-info-box .info-text .info-link:hover{color:rgb(0,65.7,162)}@media(max-width: 576px){.result-info-box .info-text{font-size:14px;text-align:center}}@keyframes fadeInUp{to{transform:translateY(0);opacity:1}}@keyframes pulseIcon{0%,100%{transform:scale(1);filter:drop-shadow(0 0 0px rgba(0, 73, 180, 0))}50%{transform:scale(1.06);filter:drop-shadow(0 0 6px rgba(0, 73, 180, 0.2))}}@media(max-width: 768px){.account-recovery-page{padding:0;margin:0}.account-recovery-container{padding:16px}.account-recovery-card{padding:24px 16px 20px;border-radius:16px}.account-recovery-tabs{margin-bottom:20px}.account-recovery-tabs .tab-link{padding:9px 12px;font-size:13px}.account-recovery-form .form-group{margin-bottom:10px}.account-recovery-form .form-input{height:44px;font-size:13px}.account-recovery-form .form-select{height:44px;font-size:13px}.phone-input-group{flex-wrap:wrap;gap:6px}.phone-input-group .phone-prefix{flex:1;width:auto}.phone-input-group .phone-separator{display:none}.auth-input-group .auth-timer{font-size:12px;right:18px}.auth-request-button,.auth-verify-button{width:100%;height:44px;font-size:13px;margin-top:4px}.account-recovery-card .form-actions{flex-direction:row;gap:10px;margin-top:20px}.account-recovery-card .form-actions .cancel-button,.account-recovery-card .form-actions .submit-button{flex:1;height:44px;font-size:14px}.account-recovery-result{padding:30px 20px;border-radius:0 0 8px 8px}.account-recovery-result .result-header{margin-bottom:24px}.account-recovery-result .result-header .result-icon{font-size:40px;margin-bottom:16px}.account-recovery-result .result-header .result-title{font-size:18px;margin-bottom:8px}.account-recovery-result .result-header .result-description{font-size:14px}.found-users-list{margin-bottom:24px}.found-users-list .found-user-item{padding:16px;border-radius:8px;margin-bottom:8px}.found-users-list .found-user-item .user-email{font-size:16px}.found-users-list .found-user-item .user-date{font-size:12px}.result-info-box{border-radius:8px;padding:12px 16px}.result-info-box .info-text{font-size:12px}}.signup-selection-page{display:flex;align-items:center;justify-content:center;padding:100px 20px;position:relative;border-radius:12px;margin-top:60px;margin-bottom:50px}.signup-selection-container{width:100%;max-width:1018px;margin:0 auto;position:relative}.signup-selection-card{background:rgba(0,0,0,0);padding:0;display:flex;flex-direction:column;align-items:center}@media(max-width: 576px){.signup-selection-card{padding:0 20px}}.signup-header{display:flex;flex-direction:column;align-items:center;margin-bottom:40px;width:100%}.signup-title-row{display:flex;align-items:center;justify-content:center;gap:12px;margin-bottom:12px;color:#000}.signup-title-row .signup-title-icon{color:#000;flex-shrink:0}.signup-title-row .signup-title{font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:36px;font-weight:700;color:#000;margin:0;letter-spacing:-0.5px}.signup-message{font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:16px;font-weight:400;color:#718096;text-align:center;margin:0;line-height:1.5}.signup-cards{width:100%;max-width:760px;display:flex;flex-direction:row;gap:28px;margin-bottom:45px;justify-content:center}.signup-card-item{flex:1;background:#fff;border:1px solid rgba(226,232,240,.8);border-radius:24px;padding:45px 30px;display:flex;flex-direction:column;align-items:center;text-align:center;text-decoration:none;transition:all .4s cubic-bezier(0.16, 1, 0.3, 1);color:#1a202c;cursor:pointer;box-shadow:0 10px 30px rgba(0,0,0,.03);position:relative;overflow:hidden}.signup-card-item::before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background:radial-gradient(circle at top right, rgba(255, 255, 255, 0.8), transparent 70%);opacity:0;transition:opacity .4s ease;pointer-events:none}.signup-card-item .card-icon-wrapper{width:76px;height:76px;border-radius:22px;display:flex;align-items:center;justify-content:center;margin-bottom:24px;transition:all .4s cubic-bezier(0.16, 1, 0.3, 1);box-shadow:0 8px 16px rgba(0,0,0,.02)}.signup-card-item .card-text-wrapper{display:flex;flex-direction:column;align-items:center}.signup-card-item .card-title{font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:22px;font-weight:800;margin-top:0;margin-bottom:12px;line-height:1.3;color:#1a202c;transition:all .4s ease}.signup-card-item .card-desc{font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:14px;color:#718096;line-height:1.6;margin:0;max-width:250px;word-break:keep-all;transition:color .4s ease}.signup-card-item:hover{transform:translateY(-8px);background:#fff}.signup-card-item:hover::before{opacity:1}.signup-card-item.individual-card .card-icon-wrapper{background:linear-gradient(135deg, #F0F5FF 0%, #D8E5FF 100%);color:#0049b4}.signup-card-item.individual-card:hover{border-color:#0049b4;box-shadow:0 20px 40px rgba(0,73,180,.12)}.signup-card-item.individual-card:hover .card-title{color:#0049b4}.signup-card-item.individual-card:hover .card-icon-wrapper{background:linear-gradient(135deg, #0049B4 0%, #0066FF 100%);color:#fff;transform:scale(1.05) rotate(3deg);box-shadow:0 10px 20px rgba(0,73,180,.25)}.signup-card-item.organization-card .card-icon-wrapper{background:linear-gradient(135deg, #EBF8FF 0%, #CAF0F8 100%);color:#00a1d7}.signup-card-item.organization-card:hover{border-color:#00a1d7;box-shadow:0 20px 40px rgba(0,161,215,.12)}.signup-card-item.organization-card:hover .card-title{color:#00a1d7}.signup-card-item.organization-card:hover .card-icon-wrapper{background:linear-gradient(135deg, #00A1D7 0%, #33C2FF 100%);color:#fff;transform:scale(1.05) rotate(-3deg);box-shadow:0 10px 20px rgba(0,161,215,.25)}.signup-navigation-box{display:flex;align-items:center;background:#edf4ff;border:1px solid #0049b4;border-radius:8px;overflow:hidden;height:42px;max-width:280px;width:100%;margin:0 auto}.signup-navigation-box .signup-nav-btn{flex:1;display:flex;align-items:center;justify-content:center;font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:14px;font-weight:700;color:#0049b4;text-decoration:none;height:100%;transition:all .2s ease}.signup-navigation-box .signup-nav-btn:hover{background:#dce7ff}.signup-navigation-box .signup-nav-divider{width:1px;height:100%;background:#0049b4}@media(max-width: 768px){.signup-selection-page{background:#ebfbff;min-height:calc(100vh - 44px);padding:40px 20px;margin-top:0;margin-bottom:0;border-radius:0}.signup-selection-container{max-width:100%;padding:0}.signup-header{margin-bottom:30px}.signup-header .signup-title-row{margin-bottom:8px}.signup-header .signup-title-row .signup-title-icon{width:24px;height:24px}.signup-header .signup-title-row .signup-title{font-size:24px}.signup-message{font-size:14px;font-weight:400;color:#212529;margin-bottom:40px;line-height:22px}.signup-cards{max-width:335px;flex-direction:column;gap:16px;margin-bottom:40px}.signup-card-item{padding:24px 20px;border-radius:12px;flex-direction:row;text-align:left;align-items:center;gap:16px}.signup-card-item .card-icon-wrapper{width:44px;height:44px;margin-bottom:0;flex-shrink:0}.signup-card-item .card-icon-wrapper svg{width:20px;height:20px}.signup-card-item .card-text-wrapper{align-items:flex-start}.signup-card-item .card-title{font-size:16px;margin-bottom:4px}.signup-card-item .card-desc{font-size:12px}.signup-card-item:hover{transform:none;box-shadow:none}.signup-navigation-box{max-width:260px;height:38px}.signup-navigation-box .signup-nav-btn{font-size:13px}}.corporate-transfer-section{margin-top:40px;padding-top:24px}.corporate-transfer-section .btn-block{width:100%}.corp-manager-wrapper,.password-change-wrapper{width:100%;margin:0 auto}.corp-manager-wrapper .page-outer-title,.password-change-wrapper .page-outer-title{font-size:24px;font-weight:700;color:#000;margin-bottom:24px}.corp-manager-wrapper .register-form-container,.password-change-wrapper .register-form-container{background:#fff;border:1px solid #dfdfdf;border-radius:30px;box-shadow:0px 4px 26.9px 2px rgba(192,192,192,.25);padding:40px 48px}.corp-manager-wrapper .common-title-bar,.password-change-wrapper .common-title-bar{display:none}.corp-manager-wrapper .inner-section-title,.password-change-wrapper .inner-section-title{display:flex;align-items:center;gap:15px;border-bottom:1px solid #818181;padding-bottom:12px;margin-bottom:32px}.corp-manager-wrapper .inner-section-title h3,.password-change-wrapper .inner-section-title h3{font-size:18px;font-weight:700;color:#0d0e11;margin:0}.corp-manager-wrapper .inner-section-title svg,.password-change-wrapper .inner-section-title svg{width:17px;height:17px}.corp-manager-wrapper .form-row,.password-change-wrapper .form-row{display:flex;flex-direction:column;gap:13px;margin-bottom:26px}.corp-manager-wrapper .form-row.two-columns,.password-change-wrapper .form-row.two-columns{flex-direction:row;gap:34px}.corp-manager-wrapper .form-row.two-columns>div,.password-change-wrapper .form-row.two-columns>div{flex:1;display:flex;flex-direction:column;gap:13px}@media(max-width: 768px){.corp-manager-wrapper .form-row.two-columns,.password-change-wrapper .form-row.two-columns{flex-direction:column;gap:26px}}.corp-manager-wrapper .form-label-wrapper,.password-change-wrapper .form-label-wrapper{margin:0;padding:0;width:auto}.corp-manager-wrapper .form-field-wrapper,.password-change-wrapper .form-field-wrapper{width:100%}.corp-manager-wrapper #authNumberContainer .auth-input-group,.password-change-wrapper #authNumberContainer .auth-input-group{flex:none !important;width:440px !important}.corp-manager-wrapper #authNumberContainer .input-with-button,.password-change-wrapper #authNumberContainer .input-with-button{justify-content:space-between}.corp-manager-wrapper #authNumber,.password-change-wrapper #authNumber{width:100% !important}.corp-manager-wrapper .form-label-text,.password-change-wrapper .form-label-text{font-size:14px;font-weight:700;color:#000}.corp-manager-wrapper .required-badge,.password-change-wrapper .required-badge{visibility:hidden;position:relative;width:10px}.corp-manager-wrapper .required-badge::after,.password-change-wrapper .required-badge::after{content:"*";visibility:visible;position:absolute;left:4px;top:0;color:#f4253c;font-size:16px;font-weight:700}.corp-manager-wrapper .form-input,.corp-manager-wrapper .form-select,.corp-manager-wrapper .input-readonly,.corp-manager-wrapper .info-value-box,.password-change-wrapper .form-input,.password-change-wrapper .form-select,.password-change-wrapper .input-readonly,.password-change-wrapper .info-value-box{height:47px;background:#fcfcfc;border:1px solid #dfdfdf;border-radius:10px;font-size:16px;color:#0d0e11;padding:0 20px;width:100%;box-sizing:border-box}.corp-manager-wrapper .form-input:disabled,.corp-manager-wrapper .input-readonly,.password-change-wrapper .form-input:disabled,.password-change-wrapper .input-readonly{background:#ededed !important}.corp-manager-wrapper .compound-input,.password-change-wrapper .compound-input{display:flex;align-items:center;gap:9px;width:100%}.corp-manager-wrapper .compound-input .form-input,.corp-manager-wrapper .compound-input .form-select,.password-change-wrapper .compound-input .form-input,.password-change-wrapper .compound-input .form-select{flex:1;text-align:center;padding:0}.corp-manager-wrapper .separator,.password-change-wrapper .separator{font-size:16px;color:#000}.corp-manager-wrapper .input-with-button,.password-change-wrapper .input-with-button{display:flex;align-items:center;gap:9px}.corp-manager-wrapper .btn-change,.corp-manager-wrapper .btn-auth,.password-change-wrapper .btn-change,.password-change-wrapper .btn-auth{height:47px;background:#d5efff;color:#2a69de;font-size:16px;font-weight:500;border:none;border-radius:10px;padding:0 20px;white-space:nowrap;cursor:pointer}.corp-manager-wrapper .auth-input-group,.password-change-wrapper .auth-input-group{display:flex;align-items:center;position:relative;flex:1}.corp-manager-wrapper .auth-input-group .form-input,.password-change-wrapper .auth-input-group .form-input{padding-right:70px}.corp-manager-wrapper .auth-timer,.password-change-wrapper .auth-timer{position:absolute;right:20px;color:#f4253c;font-size:16px;font-weight:500}.corp-manager-wrapper .form-actions,.password-change-wrapper .form-actions{display:flex;justify-content:space-between;align-items:center;margin-top:0}.corp-manager-wrapper .btn-withdrawal,.password-change-wrapper .btn-withdrawal{height:45px;background:#ff615c;color:#fff;font-size:16px;font-weight:500;border:none;border-radius:10px;padding:0 20px;cursor:pointer;width:156px;display:flex;justify-content:center;align-items:center;text-decoration:none}.corp-manager-wrapper .right-buttons,.password-change-wrapper .right-buttons{display:flex;gap:8px}.corp-manager-wrapper .btn-cancel,.password-change-wrapper .btn-cancel{height:45px;background:#bdc7cf;color:#fff;font-size:16px;font-weight:500;border:none;border-radius:10px;padding:0 20px;cursor:pointer;width:156px;justify-content:center}.corp-manager-wrapper .btn-apply,.password-change-wrapper .btn-apply{height:45px;background:#2a69de;color:#fff;font-size:16px;font-weight:500;border:none;border-radius:10px;padding:0 20px;cursor:pointer;width:156px}.corp-manager-wrapper .info-notice-box,.password-change-wrapper .info-notice-box{background:#ecf0fa;border-radius:7px;padding:13px 14px 12px;display:flex;align-items:center;gap:13px;margin-bottom:32px}.corp-manager-wrapper .info-notice-box svg,.password-change-wrapper .info-notice-box svg{width:24px;height:24px;flex-shrink:0;color:#4685ef}.corp-manager-wrapper .info-notice-box p,.password-change-wrapper .info-notice-box p{color:#4685ef;font-size:16px;font-weight:500;margin:0}.corp-manager-wrapper .info-notice-box.danger,.password-change-wrapper .info-notice-box.danger{background:#fdf3f3}.corp-manager-wrapper .info-notice-box.danger p,.password-change-wrapper .info-notice-box.danger p{color:#f4253c}.corp-manager-wrapper .password-rules-list,.password-change-wrapper .password-rules-list{list-style-type:disc;padding-left:24px;margin:20px 0 0 0}.corp-manager-wrapper .password-rules-list li,.password-change-wrapper .password-rules-list li{color:#888;font-size:16px;line-height:20px;margin-bottom:5px}.corp-manager-wrapper .password-rules-list li:last-child,.password-change-wrapper .password-rules-list li:last-child{margin-bottom:0}.register-title-bar{padding:18px 45px;display:flex;align-items:baseline;gap:16px}@media(max-width: 1024px){.register-title-bar{flex-direction:column;gap:8px;padding:14px 20px}}@media(max-width: 768px){.register-title-bar{flex-direction:row;flex-wrap:wrap;align-items:baseline;gap:8px;padding:0 20px 6px;background-color:rgba(0,0,0,0);border-radius:0;border-bottom:1px solid #212529;margin-bottom:20px}}.register-title-bar .register-title{font-size:28px;font-weight:700;color:#1a1a2e;margin:0}@media(max-width: 1024px){.register-title-bar .register-title{font-size:22px}}@media(max-width: 768px){.register-title-bar .register-title{font-size:16px;font-weight:500}}.register-title-bar .register-subtitle{font-size:16px;font-weight:400;color:#515151}@media(max-width: 1024px){.register-title-bar .register-subtitle{font-size:14px}}@media(max-width: 768px){.register-title-bar .register-subtitle{font-size:12px}}.register-progress{display:flex;justify-content:center;margin-bottom:24px}@media(max-width: 768px){.register-progress{margin-bottom:20px;padding:0}}.progress-steps{display:flex;align-items:flex-start;gap:30px}@media(max-width: 768px){.progress-steps{gap:12px;align-items:center}}.step-dots{display:flex;gap:6px}.step-dots span{width:6px;height:6px;border-radius:50%;background-color:#dadada}@media(max-width: 768px){.step-dots{display:flex;flex-direction:row;align-items:center;gap:4px;width:auto;height:auto}.step-dots span{width:4px;height:4px;border-radius:50%;background-color:#dadada}}.progress-step-item{display:flex;flex-direction:column;align-items:center;gap:8px}.register-form-container{padding:30px 30px}@media(max-width: 1024px){.register-form-container{padding:20px 16px}}.register-form-container.with-sidebar{display:flex;gap:0;padding:28px;min-height:600px}@media(max-width: 768px){.register-form-container.with-sidebar{flex-direction:column;padding:0;background-color:#fff}}.register-form-container .form-input{width:100%;padding:8px 12px;border:1px solid #dfdfdf;border-radius:8px;background-color:#fcfcfc;font-size:14px;color:#1a1a2e;outline:none;transition:border-color .2s ease}.register-form-container .form-input::placeholder{color:#94a3b8}.register-form-container .form-input:focus{border-color:#0049b4}@media(max-width: 1024px){.register-form-container .form-input{font-size:14px}}.register-form-container .form-textarea{width:100%;min-height:120px;padding:8px 12px;border:1px solid #dadada;border-radius:8px;background-color:#fff;font-size:14px;color:#1a1a2e;outline:none;resize:vertical;transition:border-color .2s ease}.register-form-container .form-textarea::placeholder{color:#dadada}.register-form-container .form-textarea:focus{border-color:#0049b4}@media(max-width: 1024px){.register-form-container .form-textarea{min-height:140px;font-size:16px}}.ip-input-row{display:flex;gap:21px}.ip-input-row .ip-input{flex:1}@media(max-width: 1024px){.ip-input-row{flex-direction:column;gap:12px}}@media(max-width: 768px){.ip-input-row{flex-direction:row;gap:10px}.ip-input-row .ip-input{flex:1;min-width:0;height:40px;font-size:14px;border-radius:8px;padding:0 12px}}.btn-add-ip{width:auto;height:40px;padding:0 16px;background-color:#3ba4ed;color:#fff;border:none;border-radius:8px;font-size:14px;font-weight:600;cursor:pointer;transition:background-color .2s ease;flex-shrink:0}.btn-add-ip:hover{background-color:#2b94dd}@media(max-width: 1024px){.btn-add-ip{width:auto;font-size:14px;height:38px}}@media(max-width: 768px){.btn-add-ip{width:auto;height:36px;font-size:13px;border-radius:8px;padding:0 12px}}.register-form-container .ip-list{margin-top:20px;border:none;background:rgba(0,0,0,0)}.register-form-container .ip-list .ip-items{display:flex;flex-direction:column;gap:10px;max-height:none}.register-form-container .ip-list .ip-item{display:flex;gap:21px;padding:0;border-bottom:none}.register-form-container .ip-list .ip-item:hover{background:rgba(0,0,0,0)}@media(max-width: 1024px){.register-form-container .ip-list .ip-item{flex-direction:column;gap:12px}}@media(max-width: 768px){.register-form-container .ip-list .ip-item{flex-direction:row;gap:10px}}.register-form-container .ip-list .ip-item .ip-display{flex:1;height:60px;padding:0 20px;border:1px solid #dadada;border-radius:8px;background-color:#fff;display:flex;align-items:center;font-size:20px;color:#1a1a2e}@media(max-width: 768px){.register-form-container .ip-list .ip-item .ip-display{height:40px;padding:0 12px;font-size:14px;border-radius:8px;min-width:0}}.register-form-container .ip-list .ip-item .btn-remove-ip{width:158px !important;height:60px !important;background-color:#e5e7eb !important;color:#5f666c !important;border:none !important;border-radius:12px !important;font-size:20px !important;font-weight:700;cursor:pointer;transition:background-color .2s ease;flex-shrink:0;display:flex !important;align-items:center;justify-content:center}@media(max-width: 1024px){.register-form-container .ip-list .ip-item .btn-remove-ip{width:100% !important;font-size:16px !important;height:50px !important}}@media(max-width: 768px){.register-form-container .ip-list .ip-item .btn-remove-ip{width:89px !important;height:40px !important;font-size:12px !important;border-radius:8px !important;padding:0 !important}}.apikey-register-container{padding:48px 0}@media(max-width: 768px){.apikey-register-container{padding:24px 16px}}.apikey-register-container.with-sidebar{display:flex;min-height:calc(100vh - 200px);position:relative;padding:0}.register-progress .progress-step{display:flex;flex-direction:column;align-items:center;position:relative;z-index:2}.register-progress .progress-step .step-number{width:40px;height:40px;border-radius:50%;background:#fff;border:2px solid #e2e8f0;display:flex;align-items:center;justify-content:center;font-weight:600;color:#64748b;transition:all .3s ease;margin-bottom:8px}.register-progress .progress-step .step-label{font-size:14px;color:#64748b;white-space:nowrap}.register-progress .progress-step.active .step-number{background:#0049b4;border-color:#0049b4;color:#fff;box-shadow:0 0 0 4px rgba(0,73,180,.1)}.register-progress .progress-step.active .step-label{color:#0049b4;font-weight:500}.register-progress .progress-step.completed .step-number{background:#6bcf7f;border-color:#6bcf7f;color:#fff}.register-progress .progress-step.completed .step-label{color:#1a1a2e}.register-progress .progress-line{flex:1;height:2px;background:#e2e8f0;margin:0 16px;margin-bottom:28px;position:relative}.register-header{text-align:center;margin-bottom:48px;padding:0 24px}.register-header h1{font-size:32px;font-weight:700;color:#1a1a2e;margin-bottom:8px}.register-header h2{font-size:24px;font-weight:600;color:#0049b4;margin-bottom:16px}.register-header .header-description{font-size:16px;color:#64748b;max-width:600px;margin:0 auto}.apikey-register-sidebar{width:254px;background-color:#eceff4;border-radius:12px;padding:23px 12px;position:sticky;top:0;height:fit-content;max-height:calc(100vh - 100px);overflow-y:auto;flex-shrink:0}@media(max-width: 1024px){.apikey-register-sidebar{width:240px}}@media(max-width: 768px){.apikey-register-sidebar{position:fixed;left:0;top:60px;transform:translateX(-100%);transition:transform .3s ease;box-shadow:0 8px 24px rgba(75,155,255,.15);z-index:1050;height:calc(100vh - 60px);max-height:none;margin:0;border-radius:0}.apikey-register-sidebar.mobile-open{transform:translateX(0)}}.apikey-sidebar-nav .sidebar-title{font-size:20px;font-weight:700;color:#212529;text-align:center;padding:20px 14px 45px}.apikey-sidebar-nav .menu-section{margin-bottom:0;border-bottom:1px solid #dadada}.apikey-sidebar-nav .menu-section:last-child{border-bottom:none}.apikey-sidebar-nav .menu-title{padding:22px 14px;font-size:20px;font-weight:400;color:#212529;cursor:pointer;transition:all .3s ease;display:flex;align-items:center;justify-content:space-between;gap:10px}.apikey-sidebar-nav .menu-title:hover{background-color:rgba(0,0,0,.02)}.apikey-sidebar-nav .menu-title.active{font-weight:700;color:#212529}.apikey-sidebar-nav .menu-title .menu-icon{width:20px;height:20px;flex-shrink:0}.apikey-sidebar-nav .menu-title .menu-icon img{width:100%;height:100%;object-fit:contain}.apikey-sidebar-nav .menu-title .menu-text{flex:1}.apikey-sidebar-nav .menu-title .api-count{width:8px;height:8px;padding:0;min-width:auto;background-color:#dadada;border-radius:50%;font-size:0;flex-shrink:0}.apikey-sidebar-nav .menu-title.active .api-count{background-color:#3ba4ed}.apikey-register-content{flex:1;padding:23px 30px;min-height:auto;display:flex;flex-direction:column}.apikey-register-content .api-result-count{font-size:16px;color:#64748b;margin-bottom:32px}.apikey-register-content .api-result-count strong{color:#0049b4;font-weight:600}@media(max-width: 768px){.apikey-register-content{padding:0}}.apikey-mobile-toggle{display:none;position:fixed;bottom:24px;right:24px;width:56px;height:56px;background-color:#0049b4;border-radius:50%;border:none;color:#fff;font-size:24px;cursor:pointer;z-index:1051;transition:all .3s ease}.apikey-mobile-toggle:hover{transform:scale(1.05);box-shadow:0 16px 40px rgba(75,155,255,.2)}.apikey-mobile-toggle:active{transform:scale(0.95)}@media(max-width: 768px){.apikey-mobile-toggle{display:flex;align-items:center;justify-content:center}}.apikey-mobile-overlay{display:none;position:fixed;top:60px;left:0;right:0;bottom:0;background-color:rgba(0,0,0,.5);z-index:1049;opacity:0;transition:opacity .3s ease;pointer-events:none}@media(max-width: 768px){.apikey-mobile-overlay{display:block}}.apikey-mobile-overlay.active{opacity:1;pointer-events:auto}.api-filter-header{display:flex;justify-content:flex-end;align-items:center;margin-bottom:32px;gap:16px}@media(max-width: 768px){.api-filter-header{justify-content:stretch}}.api-search-box{position:relative;width:228px;height:44px;flex-shrink:0;margin-left:auto}@media(max-width: 768px){.api-search-box{width:100%;margin-left:0}}.api-search-box .search-input{width:100%;height:100%;padding:0 44px 0 17px;border:1px solid #dadada;border-radius:12px;background-color:#fff;font-size:14px;font-family:inherit;outline:none;transition:all .3s ease}.api-search-box .search-input:focus{border-color:#0049b4;box-shadow:0 0 0 3px rgba(0,73,180,.1)}.api-search-box .search-input::placeholder{color:#8c959f}.api-search-box svg{position:absolute;right:17px;top:50%;transform:translateY(-50%);pointer-events:none}.selection-counter{font-size:16px;color:#64748b;white-space:nowrap}.selection-counter .counter-value{font-weight:600;color:#0049b4}.api-filter-header{display:flex;align-items:center;justify-content:space-between;gap:24px;margin-bottom:24px}@media(max-width: 768px){.api-filter-header{flex-direction:column;align-items:stretch;gap:16px}}.api-filter-header .select-all-wrapper{display:flex;align-items:center;flex:1}@media(max-width: 768px){.api-filter-header .select-all-wrapper{order:2}}.api-filter-header .select-all-label{display:flex;align-items:center;gap:8px;cursor:pointer;font-size:16px;color:#1a1a2e;user-select:none}.api-filter-header .select-all-label:hover{color:#0049b4}.api-filter-header .select-all-checkbox{width:18px;height:18px;cursor:pointer;accent-color:#0049b4}.api-filter-header .select-all-checkbox:indeterminate{opacity:.6}@media(max-width: 768px){.api-filter-header .api-search-box{order:1}}.api-selection-card-grid{display:grid;grid-template-columns:repeat(3, 1fr);gap:24px;margin-bottom:32px}@media(max-width: 1024px){.api-selection-card-grid{grid-template-columns:repeat(2, 1fr);gap:16px}}@media(max-width: 768px){.api-selection-card-grid{grid-template-columns:repeat(2, 1fr);gap:16px}}.custom-checkbox{display:inline-block;width:22px;height:22px;flex-shrink:0;cursor:pointer;background-image:url("/img/checkbox-unchecked.svg");background-size:contain;background-repeat:no-repeat;background-position:center;border-radius:50%}input[type=checkbox]:checked+.custom-checkbox{background-color:#0049b4;background-image:url("/img/checkbox-checked.svg")}.select-all-label .custom-checkbox{margin-right:8px}.api-selection-card{background:#fff;border:1px solid #dadada;border-radius:12px;transition:all .3s ease;cursor:pointer;position:relative}.api-selection-card:hover{border-color:#0049b4;box-shadow:0 4px 12px rgba(75,155,255,.1)}.api-selection-card.selected{border-color:#0049b4;background-color:rgba(0,73,180,.02)}.api-selection-card .api-card-content{display:flex;flex-direction:column;gap:14px;padding:26px 28px;cursor:pointer}.api-selection-card .api-card-content .api-card-header{display:flex;justify-content:space-between;align-items:center;gap:13px}.api-selection-card .api-card-content .api-card-header .api-card-category{font-size:14px;font-weight:400;color:#6e7780;flex:1}.api-selection-card .api-card-content .api-card-header .checkbox-wrapper{flex-shrink:0}.api-selection-card .api-card-content .api-card-header .checkbox-wrapper .api-checkbox{width:22px;height:22px;cursor:pointer;accent-color:#0049b4}.api-selection-card .api-card-content .api-name{font-size:18px;font-weight:700;color:#212529;margin:0;line-height:1}.api-selection-card .api-card-content .api-description{font-size:15px;font-weight:400;color:#515961;margin:0;line-height:1.2;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.api-selection-card .api-card-content .api-card-icon{display:flex;justify-content:flex-end;margin-top:auto}.api-selection-card .api-card-content .api-card-icon img{width:50px;height:50px;object-fit:contain}.api-selection-card .api-card-content .api-card-icon i{width:50px;height:50px;display:flex;align-items:center;justify-content:center;font-size:32px;color:#999}.selected-summary{background:#eff6ff;border-radius:12px;padding:24px;margin-bottom:32px}.selected-summary h3{font-size:18px;font-weight:600;color:#1a1a2e;margin-bottom:16px}.selected-summary .selected-list{display:flex;flex-wrap:wrap;gap:8px}.selected-summary .selected-list .selected-chip{display:inline-flex;align-items:center;gap:4px;padding:6px 12px;background:#fff;border:1px solid #0049b4;border-radius:50px;font-size:14px;color:#0049b4}.selected-summary .selected-list .selected-chip .remove-chip{background:none;border:none;color:#0049b4;font-size:18px;cursor:pointer;padding:0;margin-left:4px}.selected-summary .selected-list .selected-chip .remove-chip:hover{color:#ff6b6b}.empty-api-state{padding:48px}.loading-state{padding:48px;text-align:center;color:#64748b}.loading-state .loading-spinner{width:48px;height:48px;margin:0 auto 24px;border:4px solid #e2e8f0;border-top-color:#0049b4;border-radius:50%;animation:spin 1s linear infinite}.loading-state p{font-size:16px;color:#64748b}@keyframes spin{to{transform:rotate(360deg)}}.register-form{width:100%;max-width:1021px;margin:0 auto}.register-form .form-card{background:#fff;border-radius:12px;padding:40px;box-shadow:0 2px 4px rgba(0,0,0,.05);margin-bottom:32px}.register-form .form-group{display:flex !important;flex-direction:column !important;gap:18px !important;margin-bottom:30px !important}.register-form .form-group:last-child{margin-bottom:0 !important}.register-form .form-label{font-size:16px !important;font-weight:500 !important;color:#000 !important;line-height:20px !important;margin:0 !important;display:block !important}.register-form .form-label .required-star{color:#f4253c !important;font-size:16px !important;margin-left:2px !important}.register-form .form-input,.register-form .form-select{width:100% !important;height:47px !important;background:#fcfcfc !important;border:1px solid #dfdfdf !important;border-radius:10px !important;padding:0 21px !important;font-size:16px !important;font-weight:300 !important;color:#000 !important;box-sizing:border-box !important;box-shadow:none !important;transition:border-color .2s !important}.register-form .form-input:focus,.register-form .form-select:focus{outline:none !important;border-color:#2a69de !important;box-shadow:none !important}.register-form .form-input::placeholder,.register-form .form-select::placeholder{color:#7f8a95 !important;font-weight:300 !important}.register-form .form-input:disabled,.register-form .form-select:disabled{background-color:#ededed !important}.register-form .form-textarea{width:100% !important;height:299px !important;background:#fcfcfc !important;border:1px solid #dfdfdf !important;border-radius:10px !important;padding:21px !important;font-size:16px !important;font-weight:300 !important;color:#000 !important;box-sizing:border-box !important;resize:none !important;box-shadow:none !important;min-height:unset !important;font-family:inherit !important;transition:border-color .2s !important}.register-form .form-textarea:focus{outline:none !important;border-color:#2a69de !important;box-shadow:none !important}.register-form .form-textarea::placeholder{color:#7f8a95 !important;font-weight:300 !important}.register-form .form-hint{display:block;font-size:12px;color:#64748b;margin-top:4px}.register-form .form-hint .char-counter{float:right;color:#94a3b8}.register-form .radio-group{display:flex;gap:24px;flex-wrap:wrap}.register-form .radio-group .radio-label{display:flex;align-items:center;cursor:pointer}.register-form .radio-group .radio-label input[type=radio]{margin-right:8px}.register-form .radio-group .radio-label .radio-text{font-size:16px;color:#1a1a2e}.register-form .icon-upload-wrapper{display:flex;align-items:center;gap:24px}.register-form .icon-upload-wrapper .icon-preview{width:120px;height:120px;border:2px dashed #e2e8f0;border-radius:12px;display:flex;align-items:center;justify-content:center;background:#f8fafc;overflow:hidden;position:relative;transition:all .3s ease}.register-form .icon-upload-wrapper .icon-preview:hover{border-color:#0049b4;background:rgba(0,73,180,.05)}.register-form .icon-upload-wrapper .icon-preview .icon-placeholder{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;color:#64748b;text-align:center;padding:16px}.register-form .icon-upload-wrapper .icon-preview .icon-placeholder svg{opacity:.5}.register-form .icon-upload-wrapper .icon-preview .icon-placeholder span{font-size:12px;font-weight:500}.register-form .icon-upload-wrapper .icon-preview img{width:100%;height:100%;object-fit:cover}.register-form .icon-upload-wrapper .icon-preview .btn-remove-icon{position:absolute;top:8px;right:8px;width:28px;height:28px;padding:0;background:hsla(0,0%,100%,.95);border:1px solid #e2e8f0;border-radius:50%;color:#64748b;cursor:pointer;transition:all .3s ease;display:none;align-items:center;justify-content:center;box-shadow:0 2px 4px rgba(0,0,0,.05);z-index:10}.register-form .icon-upload-wrapper .icon-preview .btn-remove-icon svg{width:14px;height:14px}.register-form .icon-upload-wrapper .icon-preview .btn-remove-icon:hover{background:#ff6b6b;border-color:#ff6b6b;color:#fff;transform:scale(1.1);box-shadow:0 4px 12px rgba(75,155,255,.1)}.register-form .icon-upload-wrapper .icon-preview .btn-remove-icon:active{transform:scale(0.95)}.register-form .icon-upload-wrapper .icon-input{display:none}.register-form .icon-upload-wrapper .btn-icon-select{padding:10px 20px;background:#fff;border:1px solid #e2e8f0;border-radius:8px;font-size:14px;font-weight:500;color:#1a1a2e;cursor:pointer;transition:all .3s ease}.register-form .icon-upload-wrapper .btn-icon-select:hover{background:#0049b4;border-color:#0049b4;color:#fff}.register-form .icon-upload-wrapper .btn-icon-select:active{transform:scale(0.98)}.register-form .ip-input-group{display:flex;gap:8px;margin-bottom:16px}.register-form .ip-input-group .form-input{flex:1}.register-form .ip-input-group .btn-add-ip{display:inline-flex;align-items:center;gap:4px;padding:10px 16px;background:#0049b4;border:none;border-radius:8px;color:#fff;font-size:14px;font-weight:500;cursor:pointer;transition:all .3s ease;white-space:nowrap}.register-form .ip-input-group .btn-add-ip svg{flex-shrink:0}.register-form .ip-input-group .btn-add-ip:hover{background:#c3dfea;transform:translateY(-1px);box-shadow:0 4px 12px rgba(75,155,255,.1)}.register-form .ip-input-group .btn-add-ip:active{transform:translateY(0)}.register-form .ip-list{margin-top:16px;overflow:hidden}.register-form .ip-list .ip-list-header{padding:16px;background:#f8fafc;border-bottom:1px solid #e2e8f0}.register-form .ip-list .ip-list-header .ip-count{font-size:14px;color:#64748b}.register-form .ip-list .ip-list-header .ip-count strong{color:#0049b4;font-weight:600}.register-form .ip-list .ip-items{max-height:240px;overflow-y:auto}.register-form .ip-list .ip-items::-webkit-scrollbar{width:6px}.register-form .ip-list .ip-items::-webkit-scrollbar-track{background:#f8fafc}.register-form .ip-list .ip-items::-webkit-scrollbar-thumb{background:#e2e8f0;border-radius:3px}.register-form .ip-list .ip-items::-webkit-scrollbar-thumb:hover{background:#94a3b8}.register-form .ip-list .ip-item{display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid #e2e8f0;transition:all .3s ease}.register-form .ip-list .ip-item:last-child{border-bottom:none}.register-form .ip-list .ip-item:hover{background:#f8fafc}.register-form .ip-list .ip-item .ip-address{font-family:"Fira Code","Courier New",monospace;font-size:14px;color:#1a1a2e;font-weight:500}.register-form .ip-list .ip-item .btn-remove-ip{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;padding:0;background:#fff;border:1px solid #e2e8f0;border-radius:6px;color:#64748b;cursor:pointer;transition:all .3s ease}.register-form .ip-list .ip-item .btn-remove-ip svg{width:14px;height:14px}.register-form .ip-list .ip-item .btn-remove-ip:active{transform:scale(0.95)}.register-form-container .form-actions{justify-content:space-between;padding:0 24px 24px;margin-top:40px;border-top:none;padding-top:0}@media(max-width: 768px){.register-form-container .form-actions{padding:0 16px 16px}}@media(max-width: 768px){.apikey-register-container>.form-actions{display:flex;flex-direction:row !important;justify-content:center !important;align-items:center;gap:12px;padding:24px 20px;margin-top:0;background-color:rgba(0,0,0,0)}.apikey-register-container>.form-actions .btn-submit{width:144px !important;height:40px !important;min-height:40px;padding:8px 10px !important;font-size:14px !important;font-weight:700;border-radius:8px !important;flex-shrink:0}.apikey-register-container>.form-actions .btn-submit.btn-secondary{background-color:#e5e7eb !important;color:#5f666c !important;border:none !important}.apikey-register-container>.form-actions .btn-submit.btn-primary{background-color:#0049b4 !important;color:#fff !important;border:none !important}}.register-result{text-align:center;margin:0 auto;height:1045px;padding-top:262px}@media(max-width: 768px){.register-result{height:auto;min-height:calc(100vh - 200px);padding:0 0 40px;background-color:rgba(0,0,0,0);display:flex;flex-direction:column;align-items:center;justify-content:flex-start}}.register-result .result-box{display:block}@media(max-width: 768px){.register-result .result-box{background-color:#f6f9fb;border-radius:8px;padding:54px 20px 40px;width:100%;margin-bottom:24px}}.register-result.success .result-icon img{width:250px;height:250px}@media(max-width: 768px){.register-result.success .result-icon img{width:104px;height:104px}}.register-result.error .result-icon{background:rgba(255,107,107,.1);color:#ff6b6b}.register-result .result-icon-wrapper{margin-bottom:32px}@media(max-width: 768px){.register-result .result-icon-wrapper{margin-bottom:24px}}.register-result .result-icon-wrapper .apikey-register-container{max-width:980px !important;margin:0 !important;padding:0 !important;background:rgba(0,0,0,0) !important;box-shadow:none !important;border:none !important}.register-result .result-icon-wrapper .common-title-bar,.register-result .result-icon-wrapper .register-title-bar{text-align:left !important;margin-bottom:24px !important;border-bottom:none !important;padding:0 !important;background:rgba(0,0,0,0) !important}.register-result .result-icon-wrapper .common-title-bar .common-title,.register-result .result-icon-wrapper .register-title-bar .common-title{font-size:36px !important;font-weight:700 !important;color:#000 !important;line-height:1 !important;margin:0 !important}.register-result .result-icon-wrapper .register-progress{background:#fff !important;border-radius:20px !important;box-shadow:0px 4px 13.45px rgba(192,192,192,.25) !important;height:217px !important;display:flex !important;align-items:center !important;justify-content:center !important;margin-bottom:24px !important;padding:0 !important}.register-result .result-icon-wrapper .register-progress .progress-steps{display:flex !important;align-items:center !important;gap:0 !important}.register-result .result-icon-wrapper .register-progress .progress-step-item{display:flex !important;flex-direction:column !important;align-items:center !important;width:71px !important;position:relative !important}.register-result .result-icon-wrapper .register-progress .progress-step-item .step-icon-wrapper{width:55px !important;height:55px !important;border-radius:50% !important;background:#f0f4fa !important;border:1px solid #e2e8f0 !important;display:flex !important;align-items:center !important;justify-content:center !important;margin-bottom:6px !important;overflow:hidden !important}.register-result .result-icon-wrapper .register-progress .progress-step-item .step-icon-wrapper .step-icon-img{width:27px !important;height:27px !important;object-fit:contain !important;filter:grayscale(100%) opacity(40%) !important}.register-result .result-icon-wrapper .register-progress .progress-step-item.active .step-icon-wrapper{background:#2a69de !important;border-color:#2a69de !important}.register-result .result-icon-wrapper .register-progress .progress-step-item.active .step-icon-wrapper .step-icon-img{filter:brightness(0) invert(1) !important}.register-result .result-icon-wrapper .register-progress .progress-step-item .step-num-label{font-size:12px !important;font-weight:500 !important;color:#64748b !important;text-align:center !important;line-height:20px !important;margin:0 !important}.register-result .result-icon-wrapper .register-progress .progress-step-item.active .step-num-label{color:#1b4ab7 !important;font-weight:700 !important}.register-result .result-icon-wrapper .register-progress .progress-step-item .step-label{font-size:14px !important;font-weight:500 !important;color:#000 !important;text-align:center !important;line-height:20px !important;margin:0 !important;white-space:nowrap !important}.register-result .result-icon-wrapper .register-progress .progress-step-item:not(.active) .step-label{font-weight:400 !important}.register-result .result-icon-wrapper .register-progress .step-line{width:134px !important;height:2px !important;background-color:#dfdfdf !important;flex-shrink:0 !important;margin:0 !important;position:relative !important;top:-24px !important}.register-result .result-icon-wrapper .register-form-container{background:#fff !important;border:1px solid #dfdfdf !important;border-radius:30px !important;box-shadow:0px 4px 26.9px 2px rgba(192,192,192,.25) !important;padding:60px !important;margin-bottom:24px !important}.register-result .result-icon-wrapper .form-group{display:flex !important;flex-direction:column !important;gap:18px !important;margin-bottom:30px !important}.register-result .result-icon-wrapper .form-group:last-child{margin-bottom:0 !important}.register-result .result-icon-wrapper .form-label{font-size:16px !important;font-weight:500 !important;color:#000 !important;line-height:20px !important;margin:0 !important}.register-result .result-icon-wrapper .form-label .required-star{color:#f4253c !important;font-size:16px !important;margin-left:2px !important}.register-result .result-icon-wrapper .form-input{width:100% !important;height:47px !important;background:#fcfcfc !important;border:1px solid #dfdfdf !important;border-radius:10px !important;padding:0 21px !important;font-size:16px !important;font-weight:300 !important;color:#000 !important;box-sizing:border-box !important}.register-result .result-icon-wrapper .form-input::placeholder{color:#7f8a95 !important;font-weight:300 !important}.register-result .result-icon-wrapper .form-input:focus{outline:none !important;border-color:#2a69de !important}.register-result .result-icon-wrapper .form-textarea{width:100% !important;height:299px !important;background:#fcfcfc !important;border:1px solid #dfdfdf !important;border-radius:10px !important;padding:21px !important;font-size:16px !important;font-weight:300 !important;color:#000 !important;box-sizing:border-box !important;resize:none !important}.register-result .result-icon-wrapper .form-textarea::placeholder{color:#7f8a95 !important;font-weight:300 !important}.register-result .result-icon-wrapper .form-textarea:focus{outline:none !important;border-color:#2a69de !important}.register-result .result-icon-wrapper .char-counter-wrapper{display:flex !important;justify-content:flex-end !important;margin-top:8px !important}.register-result .result-icon-wrapper .char-counter-wrapper .char-counter{font-size:15px !important;font-weight:400 !important;color:#64748b !important}.register-result .result-icon-wrapper .icon-upload-box{width:100% !important;height:242px !important;background:#fcfcfc !important;border:1px solid #dfdfdf !important;border-radius:10px !important;display:flex !important;flex-direction:column !important;align-items:center !important;justify-content:center !important;gap:12px !important;position:relative !important;cursor:pointer !important;transition:border-color .2s !important}.register-result .result-icon-wrapper .icon-upload-box.drag-over{border-color:#2a69de !important;background:#f0f6ff !important}.register-result .result-icon-wrapper .icon-upload-box .icon-preview-img{width:80px !important;height:80px !important;object-fit:cover !important;border-radius:8px !important}.register-result .result-icon-wrapper .icon-upload-box .icon-placeholder{display:flex !important;align-items:center !important;justify-content:center !important}.register-result .result-icon-wrapper .icon-upload-box .icon-placeholder svg{width:47px !important;height:47px !important}.register-result .result-icon-wrapper .icon-upload-box .upload-title{font-size:16px !important;font-weight:500 !important;color:#000 !important;text-align:center !important;margin:0 !important;line-height:20px !important}.register-result .result-icon-wrapper .icon-upload-box .upload-hint{font-size:13px !important;font-weight:300 !important;color:#7f8a95 !important;text-align:center !important;margin:0 !important;line-height:20px !important}.register-result .result-icon-wrapper .icon-upload-box .btn-upload{background:#2a69de !important;color:#fff !important;border:none !important;border-radius:10px !important;height:48px !important;width:157px !important;font-size:16px !important;font-weight:500 !important;display:flex !important;align-items:center !important;justify-content:center !important;cursor:pointer !important;margin:0 !important}.register-result .result-icon-wrapper .icon-upload-box .btn-upload:hover{background:#1b4ab7 !important}.register-result .result-icon-wrapper .icon-upload-box .btn-remove-icon{position:absolute !important;top:10px !important;right:10px !important;width:28px !important;height:28px !important;background:rgba(0,0,0,.5) !important;border:none !important;border-radius:50% !important;color:#fff !important;display:flex !important;align-items:center !important;justify-content:center !important;cursor:pointer !important}.register-result .result-icon-wrapper .icon-upload-box .btn-remove-icon:hover{background:rgba(0,0,0,.7) !important}.register-result .result-icon-wrapper .icon-upload-box .icon-input{display:none !important}.register-result .result-icon-wrapper .ip-input-row{display:flex !important;align-items:center !important;gap:10px !important;width:100% !important;margin-bottom:10px !important}.register-result .result-icon-wrapper .ip-input-row .ip-input{flex:1 !important}.register-result .result-icon-wrapper .ip-item{display:flex !important;align-items:center !important;gap:10px !important;width:100% !important;margin-bottom:10px !important}.register-result .result-icon-wrapper .ip-item .ip-display-filled{flex:1 !important;height:47px !important;background:#ededed !important;border:1px solid #dfdfdf !important;border-radius:10px !important;padding:0 21px !important;font-size:16px !important;font-weight:300 !important;color:#7f8a95 !important;display:flex !important;align-items:center !important;box-sizing:border-box !important}.register-result .result-icon-wrapper .btn-ip{width:91px !important;height:47px !important;border:none !important;border-radius:10px !important;font-size:16px !important;font-weight:500 !important;display:flex !important;align-items:center !important;justify-content:center !important;gap:4px !important;cursor:pointer !important;flex-shrink:0 !important}.register-result .result-icon-wrapper .btn-ip svg{width:16px !important;height:16px !important;flex-shrink:0 !important}.register-result .result-icon-wrapper .btn-ip.btn-ip-add{background:#d5efff !important;color:#2a69de !important}.register-result .result-icon-wrapper .btn-ip.btn-ip-add:hover{background:#c3e5fe !important}.register-result .result-icon-wrapper .btn-ip.btn-ip-remove{background:#fbe7e9 !important;color:#bb1026 !important}.register-result .result-icon-wrapper .btn-ip.btn-ip-remove:hover{background:#f9d2d6 !important}.register-result .result-icon-wrapper .form-actions{display:flex !important;justify-content:flex-end !important;margin-top:0 !important;padding-top:0 !important;border-top:none !important}.register-result .result-icon-wrapper .form-actions .btn-submit{background:#2a69de !important;border:none !important;border-radius:10px !important;width:201px !important;height:48px !important;color:#fff !important;font-size:16px !important;font-weight:500 !important;cursor:pointer !important;display:flex !important;align-items:center !important;justify-content:center !important}.register-result .result-icon-wrapper .form-actions .btn-submit:hover{background:#1b4ab7 !important}.register-result .result-icon-wrapper .common-title-bar,.register-result .result-icon-wrapper .register-title-bar{text-align:left;margin-bottom:24px;border-bottom:none;padding:0}.register-result .result-icon-wrapper .common-title-bar .common-title,.register-result .result-icon-wrapper .register-title-bar .common-title{font-size:36px;font-weight:700;color:#000;margin:0}.register-result .result-icon-wrapper .register-progress{background:#fff;border-radius:20px;box-shadow:0px 4px 13.45px rgba(192,192,192,.25);height:217px;display:flex;align-items:center;justify-content:center;margin-bottom:24px;padding:0}.register-result .result-icon-wrapper .register-progress .progress-steps{display:flex;align-items:center;gap:0}.register-result .result-icon-wrapper .register-progress .progress-step-item{display:flex;flex-direction:column;align-items:center;position:relative;width:140px}.register-result .result-icon-wrapper .register-progress .progress-step-item .step-icon-wrapper{width:55px;height:55px;display:flex;align-items:center;justify-content:center;margin-bottom:6px;background:#f8fafc;border-radius:50%;border:1px solid #e2e8f0}.register-result .result-icon-wrapper .register-progress .progress-step-item .step-icon{display:flex;align-items:center;justify-content:center}.register-result .result-icon-wrapper .register-progress .progress-step-item .step-icon img{width:27px;height:27px;filter:grayscale(100%) opacity(50%)}.register-result .result-icon-wrapper .register-progress .progress-step-item .step-num-label{font-size:12px;font-weight:500;color:#64748b;margin-bottom:4px}.register-result .result-icon-wrapper .register-progress .progress-step-item .step-label{font-size:14px;font-weight:400;color:#000}.register-result .result-icon-wrapper .register-progress .progress-step-item.active .step-icon-wrapper{background:#2a69de;border-color:#2a69de}.register-result .result-icon-wrapper .register-progress .progress-step-item.active .step-icon img{filter:none}.register-result .result-icon-wrapper .register-progress .progress-step-item.active .step-num-label{color:#1b4ab7;font-weight:700}.register-result .result-icon-wrapper .register-progress .progress-step-item.active .step-label{font-weight:700}.register-result .result-icon-wrapper .register-progress .step-dots{display:flex;align-items:center;justify-content:center;width:134px;height:2px;background-color:#dfdfdf;margin:0 10px;position:relative;top:-24px}.register-result .result-icon-wrapper .register-progress .step-dots span{display:none}.register-result .result-icon-wrapper .register-form-container{background:#fff !important;border:1px solid #dfdfdf !important;border-radius:30px !important;box-shadow:0px 4px 26.9px 2px rgba(192,192,192,.25) !important;padding:60px !important;margin-bottom:24px !important}.register-result .result-icon-wrapper .form-row{display:flex !important;flex-direction:column !important;gap:18px !important;margin-bottom:30px !important;border-bottom:none !important;padding-bottom:0 !important;align-items:flex-start !important}.register-result .result-icon-wrapper .form-row:last-child{margin-bottom:0 !important}.register-result .result-icon-wrapper .form-label-wrapper{display:flex !important;align-items:center !important;gap:4px !important;width:auto !important;min-width:0 !important;margin-top:0 !important}.register-result .result-icon-wrapper .form-label-wrapper .form-label-text{font-size:16px !important;font-weight:700 !important;color:#000 !important}.register-result .result-icon-wrapper .form-label-wrapper .required-badge{color:#f4253c !important;background:none !important;padding:0 !important;border-radius:0 !important;font-size:0 !important}.register-result .result-icon-wrapper .form-label-wrapper .required-badge::after{content:" *" !important;font-size:16px !important;color:#f4253c !important}.register-result .result-icon-wrapper .form-field-wrapper{width:100% !important}.register-result .result-icon-wrapper .form-input{background:#fcfcfc !important;border:1px solid #dfdfdf !important;border-radius:10px !important;height:47px !important;padding:0 20px !important;font-size:16px !important;color:#000 !important;width:100% !important;box-sizing:border-box !important}.register-result .result-icon-wrapper .form-input::placeholder{color:#7f8a95 !important;font-weight:300 !important}.register-result .result-icon-wrapper .form-textarea{background:#fcfcfc !important;border:1px solid #dfdfdf !important;border-radius:10px !important;padding:20px !important;font-size:16px !important;color:#000 !important;height:299px !important;width:100% !important;box-sizing:border-box !important;resize:none !important}.register-result .result-icon-wrapper .form-textarea::placeholder{color:#7f8a95 !important;font-weight:300 !important}.register-result .result-icon-wrapper .char-counter-wrapper{display:flex;justify-content:flex-end;margin-top:10px}.register-result .result-icon-wrapper .char-counter-wrapper .char-counter{font-size:15px;color:#64748b}.register-result .result-icon-wrapper .icon-upload-box{display:flex;flex-direction:column;align-items:center;justify-content:center;width:100%;height:242px;background:#fcfcfc;border:1px solid #dfdfdf;border-radius:10px;position:relative;padding:30px}.register-result .result-icon-wrapper .icon-upload-box .icon-preview-area{display:flex;flex-direction:column;align-items:center;justify-content:center;border:none;width:100%;height:auto;background:rgba(0,0,0,0)}.register-result .result-icon-wrapper .icon-upload-box .icon-preview-area .icon-placeholder{margin-bottom:12px;color:#2a69de}.register-result .result-icon-wrapper .icon-upload-box .icon-preview-area .icon-placeholder svg{width:47px;height:47px}.register-result .result-icon-wrapper .icon-upload-box .icon-preview-area .icon-placeholder svg rect{fill:none}.register-result .result-icon-wrapper .icon-upload-box .icon-preview-area .icon-placeholder svg path{fill:#2a69de}.register-result .result-icon-wrapper .icon-upload-box .icon-upload-info{position:static;display:flex;flex-direction:column;align-items:center;gap:6px;margin-bottom:16px;text-align:center}.register-result .result-icon-wrapper .icon-upload-box .icon-upload-info .upload-title{font-size:16px;font-weight:500;color:#000;margin:0}.register-result .result-icon-wrapper .icon-upload-box .icon-upload-info .upload-hint{font-size:13px;font-weight:300;color:#7f8a95;margin:0}.register-result .result-icon-wrapper .icon-upload-box .btn-upload{position:static;background:#2a69de;color:#fff;border-radius:10px;border:none;height:48px;padding:0 26px;font-size:16px;font-weight:500;display:flex;align-items:center;justify-content:center;cursor:pointer;box-shadow:0 2px 4px rgba(42,105,222,.15)}.register-result .result-icon-wrapper .icon-upload-box .btn-upload:hover{background:#1b4ab7}.register-result .result-icon-wrapper .form-actions{display:flex !important;justify-content:flex-end !important;margin-top:40px !important;padding-top:0 !important;border-top:none !important;width:100% !important}.register-result .result-icon-wrapper .form-actions .btn-submit{background:#2a69de !important;border-radius:10px !important;border:none !important;width:201px !important;height:48px !important;color:#fff !important;font-size:16px !important;font-weight:500 !important;cursor:pointer !important;display:flex !important;align-items:center !important;justify-content:center !important;margin-left:auto !important}.register-result .result-icon-wrapper .form-actions .btn-submit:hover{background:#1b4ab7 !important}.register-result .result-icon-wrapper .ip-input-row,.register-result .result-icon-wrapper .ip-list .ip-item{display:flex;align-items:center;gap:10px;margin-bottom:18px}.register-result .result-icon-wrapper .ip-input-row .form-input.ip-input,.register-result .result-icon-wrapper .ip-input-row .ip-display,.register-result .result-icon-wrapper .ip-list .ip-item .form-input.ip-input,.register-result .result-icon-wrapper .ip-list .ip-item .ip-display{flex:1;background:#fcfcfc;border:1px solid #dfdfdf;border-radius:10px;height:47px;padding:0 20px;font-size:16px;color:#000;display:flex;align-items:center}.register-result .result-icon-wrapper .ip-input-row .form-input.ip-input::placeholder,.register-result .result-icon-wrapper .ip-input-row .ip-display::placeholder,.register-result .result-icon-wrapper .ip-list .ip-item .form-input.ip-input::placeholder,.register-result .result-icon-wrapper .ip-list .ip-item .ip-display::placeholder{color:#7f8a95;font-weight:300}.register-result .result-icon-wrapper .ip-input-row.ip-item .ip-display,.register-result .result-icon-wrapper .ip-list .ip-item.ip-item .ip-display{background:#ededed;color:#7f8a95}.register-result .result-icon-wrapper .ip-input-row .btn-add-ip,.register-result .result-icon-wrapper .ip-list .ip-item .btn-add-ip{background:#d5efff;color:#2a69de;border:none;border-radius:10px;width:91px;height:47px;font-size:16px;font-weight:500;display:flex;align-items:center;justify-content:center;cursor:pointer}.register-result .result-icon-wrapper .ip-input-row .btn-add-ip:hover,.register-result .result-icon-wrapper .ip-list .ip-item .btn-add-ip:hover{background:#c3e5fe}.register-result .result-icon-wrapper .ip-input-row .btn-remove-ip,.register-result .result-icon-wrapper .ip-list .ip-item .btn-remove-ip{background:#fbe7e9;color:#bb1026;border:none;border-radius:10px;width:91px;height:47px;font-size:16px;font-weight:500;display:flex;align-items:center;justify-content:center;cursor:pointer}.register-result .result-icon-wrapper .ip-input-row .btn-remove-ip:hover,.register-result .result-icon-wrapper .ip-list .ip-item .btn-remove-ip:hover{background:#f9d2d6}.register-result .result-icon-wrapper .result-icon{margin:0 auto;display:flex;align-items:center;justify-content:center;font-size:40px}.register-result .result-header{margin-bottom:40px}@media(max-width: 768px){.register-result .result-header{margin-bottom:0}}.register-result .result-header h1{font-size:24px;font-weight:700;color:#0049b4;margin-bottom:16px}@media(max-width: 768px){.register-result .result-header h1{font-size:16px;margin-bottom:12px}}.register-result .result-header .result-description{font-size:16px;color:#64748b;margin:0 auto}@media(max-width: 768px){.register-result .result-header .result-description{font-size:12px;line-height:1.5}.register-result .result-header .result-description .text-gray{color:#5f666c}.register-result .result-header .result-description .text-dark{color:#212529}}.key-info-card{background:#fff;border-radius:12px;padding:32px;box-shadow:0 4px 12px rgba(75,155,255,.1);margin-bottom:32px;text-align:left}.key-info-card .info-row{display:flex;align-items:center;justify-content:space-between;padding:16px 0;border-bottom:1px solid #e2e8f0}.key-info-card .info-row:last-child{border-bottom:none}.key-info-card .info-row .info-label{font-size:14px;color:#64748b;font-weight:500;min-width:120px}.key-info-card .info-row .info-value{font-size:16px;color:#1a1a2e;font-weight:500}.key-info-card .info-row .key-display{display:flex;align-items:center;gap:8px;flex:1;justify-content:flex-end}.key-info-card .info-row .key-display .key-value{font-family:"Fira Code","Courier New",monospace;font-size:14px;background:#f8fafc;padding:8px 12px;border-radius:6px;color:#c3dfea;margin-right:8px}.key-info-card .info-row .key-display .btn-copy,.key-info-card .info-row .key-display .btn-toggle-visibility{padding:6px 10px;background:#fff;border:1px solid #e2e8f0;border-radius:6px;cursor:pointer;transition:all .3s ease}.key-info-card .info-row .key-display .btn-copy:hover,.key-info-card .info-row .key-display .btn-toggle-visibility:hover{background:#0049b4;border-color:#0049b4;color:#fff}.key-info-card .info-row .status-badge.active{padding:4px 12px;background:rgba(107,207,127,.15);color:#4fa065;border-radius:50px;font-size:12px;font-weight:600}.selected-apis-summary{background:#f8fafc;border-radius:12px;padding:24px;margin-bottom:32px;text-align:left}.selected-apis-summary h3{font-size:18px;font-weight:600;color:#1a1a2e;margin-bottom:16px}.selected-apis-summary .api-chips{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:16px}.selected-apis-summary .api-chips .api-chip{padding:6px 12px;background:#fff;border:1px solid #0049b4;border-radius:50px;font-size:14px;color:#0049b4}.selected-apis-summary .api-note{font-size:12px;color:#64748b;font-style:italic}.next-steps-card{background:#eff6ff;border-radius:12px;padding:32px;margin-bottom:32px;text-align:left}.next-steps-card h3{font-size:18px;font-weight:600;color:#1a1a2e;margin-bottom:16px}.next-steps-card .steps-list{margin-left:24px;color:#64748b;font-size:14px;line-height:1.8}.next-steps-card .steps-list li{margin-bottom:8px}.result-actions{display:flex;justify-content:center;gap:16px;flex-wrap:wrap}@media(max-width: 768px){.result-actions{flex-direction:row;align-items:center;justify-content:center;gap:12px;padding:0;width:100%}}.result-actions .btn-primary,.result-actions .btn-secondary{display:inline-flex;align-items:center;gap:8px;padding:12px 24px;border-radius:8px;font-size:16px;font-weight:500;transition:all .3s ease;text-decoration:none;cursor:pointer;border:none}.result-actions .btn-primary .icon,.result-actions .btn-secondary .icon{font-size:18px}@media(max-width: 768px){.result-actions .btn-primary,.result-actions .btn-secondary{width:144px;height:40px;min-height:40px;padding:8px 10px;border-radius:8px;font-size:14px;font-weight:700;justify-content:center}}.result-actions .btn-primary{background-color:#0049b4;color:#fff;box-shadow:0 4px 12px rgba(75,155,255,.1)}.result-actions .btn-primary:hover{transform:translateY(-2px);box-shadow:0 8px 24px rgba(75,155,255,.15)}@media(max-width: 768px){.result-actions .btn-primary{box-shadow:none}.result-actions .btn-primary:hover{transform:none;box-shadow:none}}.result-actions .btn-secondary{background:#fff;color:#1a1a2e;border:1px solid #e2e8f0}.result-actions .btn-secondary:hover{background:#f8fafc;border-color:#0049b4;color:#0049b4}@media(max-width: 768px){.result-actions .btn-secondary{background-color:#e5e7eb;color:#5f666c;border:none}.result-actions .btn-secondary:hover{background-color:#d1d5db;color:#5f666c}}.floating-cart-btn{position:fixed;bottom:80px;right:40px;width:180px;height:66px;padding:10px 16px 10px 36px;border-radius:60px;background-color:#3ba4ed;border:none;color:#fff;display:flex;align-items:center;justify-content:space-between;cursor:pointer;box-shadow:0 16px 40px rgba(75,155,255,.2);transition:all .3s ease;z-index:300}.floating-cart-btn .cart-label{font-size:14px;font-weight:700;color:#fff;white-space:nowrap}.floating-cart-btn .cart-badge{display:flex;flex-direction:row;align-items:center;justify-content:center;background:#fff;width:57px;height:57px;border-radius:120px;flex-shrink:0;margin-right:-8px}.floating-cart-btn .cart-badge .cart-count{font-size:24px;font-weight:700;color:#212529;line-height:1}.floating-cart-btn .cart-badge .cart-unit{font-size:14px;font-weight:700;color:#212529;line-height:1;margin-top:2px}.floating-cart-btn:hover{transform:translateY(-4px);box-shadow:0 12px 32px rgba(59,164,237,.4)}.floating-cart-btn:active{transform:translateY(-2px)}@media(max-width: 768px){.floating-cart-btn{bottom:100px;right:24px;width:160px;height:56px;padding:8px 12px 8px 28px}.floating-cart-btn .cart-label{font-size:12px}.floating-cart-btn .cart-badge{width:48px;height:48px;margin-right:-6px}.floating-cart-btn .cart-badge .cart-count{font-size:20px}.floating-cart-btn .cart-badge .cart-unit{font-size:12px}}.modal-dialog .selected-apis-list{display:flex;flex-wrap:wrap;gap:8px;align-items:flex-start}.modal-dialog .api-pill{display:inline-flex;align-items:center;justify-content:center;gap:8px;padding:10px 16px;background:#f2f2f2;border:none;border-radius:50px;transition:all .2s ease;max-width:100%;height:47px}.modal-dialog .api-pill:hover{background:#e8e8e8}.modal-dialog .api-pill .api-pill-name{font-size:16px;font-weight:400;color:#000;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:280px;text-align:center}.modal-dialog .api-pill .api-pill-remove{flex-shrink:0;width:20px;height:20px;padding:0;background:rgba(0,0,0,0);border:none;border-radius:50%;color:#212529;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s ease;overflow:hidden}.modal-dialog .api-pill .api-pill-remove svg{width:12px;height:12px}.modal-dialog .api-pill .api-pill-remove:hover{background:rgba(0,0,0,.1);transform:scale(1.1)}.modal-dialog .api-pill .api-pill-remove:active{transform:scale(0.95)}@media(max-width: 768px){.modal-dialog .api-pill{padding:8px 14px;height:42px}.modal-dialog .api-pill .api-pill-name{font-size:14px;max-width:200px}.modal-dialog .api-pill .api-pill-remove{width:18px;height:18px}.modal-dialog .api-pill .api-pill-remove svg{width:10px;height:10px}}.step1-wrap,.step2-wrap,.step3-wrap{width:100%;font-family:"Spoqa Han Sans Neo","Noto Sans KR",sans-serif}.step1-wrap .s1-title,.step1-wrap .s2-title,.step1-wrap .s3-title,.step2-wrap .s1-title,.step2-wrap .s2-title,.step2-wrap .s3-title,.step3-wrap .s1-title,.step3-wrap .s2-title,.step3-wrap .s3-title{font-size:24px;font-weight:700;color:#0d0e11;line-height:1;margin:0 0 24px 0;padding:0;border:none;background:none;font-family:"Spoqa Han Sans Neo","Noto Sans KR",sans-serif}@media(max-width: 768px){.step1-wrap .s1-title,.step1-wrap .s2-title,.step1-wrap .s3-title,.step2-wrap .s1-title,.step2-wrap .s2-title,.step2-wrap .s3-title,.step3-wrap .s1-title,.step3-wrap .s2-title,.step3-wrap .s3-title{font-size:23px !important}}.step1-wrap .s1-progress-card,.step2-wrap .s1-progress-card,.step3-wrap .s1-progress-card{background:#fff;border-radius:20px;box-shadow:0 4px 13.45px rgba(192,192,192,.25);height:160px;display:flex;align-items:center;justify-content:center;margin-bottom:24px;box-sizing:border-box}@media(max-width: 768px){.step1-wrap .s1-progress-card,.step2-wrap .s1-progress-card,.step3-wrap .s1-progress-card{height:120px;padding:0 10px}}@media(max-width: 480px){.step1-wrap .s1-progress-card,.step2-wrap .s1-progress-card,.step3-wrap .s1-progress-card{height:100px}}.step1-wrap .s1-steps,.step2-wrap .s1-steps,.step3-wrap .s1-steps{display:flex;align-items:center;gap:0}.step1-wrap .s1-step,.step2-wrap .s1-step,.step3-wrap .s1-step{display:flex;flex-direction:column;align-items:center;gap:6px;width:80px}@media(max-width: 768px){.step1-wrap .s1-step,.step2-wrap .s1-step,.step3-wrap .s1-step{width:70px}}@media(max-width: 480px){.step1-wrap .s1-step,.step2-wrap .s1-step,.step3-wrap .s1-step{width:60px}}.step1-wrap .s1-step--active .s1-step-circle,.step2-wrap .s1-step--active .s1-step-circle,.step3-wrap .s1-step--active .s1-step-circle{background:#2a69de;border-color:#2a69de;color:#fff}.step1-wrap .s1-step--active .s1-step-num,.step2-wrap .s1-step--active .s1-step-num,.step3-wrap .s1-step--active .s1-step-num{color:#1b4ab7;font-weight:700}.step1-wrap .s1-step--active .s1-step-name,.step2-wrap .s1-step--active .s1-step-name,.step3-wrap .s1-step--active .s1-step-name{font-weight:600}.step1-wrap .s1-step-circle,.step2-wrap .s1-step-circle,.step3-wrap .s1-step-circle{width:55px;height:55px;border-radius:50%;background:#f0f4fa;border:1.5px solid #dde4ee;display:flex;align-items:center;justify-content:center;flex-shrink:0;color:#b0bec5}.step1-wrap .s1-step-circle img,.step2-wrap .s1-step-circle img,.step3-wrap .s1-step-circle img{display:block;object-fit:contain}@media(max-width: 768px){.step1-wrap .s1-step-circle img,.step2-wrap .s1-step-circle img,.step3-wrap .s1-step-circle img{width:24px !important;height:24px !important}}@media(max-width: 768px){.step1-wrap .s1-step-circle,.step2-wrap .s1-step-circle,.step3-wrap .s1-step-circle{width:44px;height:44px}}@media(max-width: 480px){.step1-wrap .s1-step-circle,.step2-wrap .s1-step-circle,.step3-wrap .s1-step-circle{width:36px;height:36px}}.step1-wrap .s1-step-svg,.step2-wrap .s1-step-svg,.step3-wrap .s1-step-svg{display:block;flex-shrink:0}@media(max-width: 768px){.step1-wrap .s1-step-svg,.step2-wrap .s1-step-svg,.step3-wrap .s1-step-svg{width:24px !important;height:24px !important}}@media(max-width: 480px){.step1-wrap .s1-step-svg,.step2-wrap .s1-step-svg,.step3-wrap .s1-step-svg{width:18px !important;height:18px !important}}.step1-wrap .s1-step-num,.step2-wrap .s1-step-num,.step3-wrap .s1-step-num{font-size:12px;font-weight:500;color:#94a3b8;line-height:1}@media(max-width: 768px){.step1-wrap .s1-step-num,.step2-wrap .s1-step-num,.step3-wrap .s1-step-num{font-size:11px}}@media(max-width: 480px){.step1-wrap .s1-step-num,.step2-wrap .s1-step-num,.step3-wrap .s1-step-num{font-size:10px}}.step1-wrap .s1-step-name,.step2-wrap .s1-step-name,.step3-wrap .s1-step-name{font-size:13px;font-weight:400;color:#0d0e11;text-align:center;white-space:nowrap;line-height:1}@media(max-width: 768px){.step1-wrap .s1-step-name,.step2-wrap .s1-step-name,.step3-wrap .s1-step-name{font-size:11px}}@media(max-width: 480px){.step1-wrap .s1-step-name,.step2-wrap .s1-step-name,.step3-wrap .s1-step-name{font-size:10px}}.step1-wrap .s1-step-line,.step2-wrap .s1-step-line,.step3-wrap .s1-step-line{width:120px;height:1.5px;background:#dfdfdf;flex-shrink:0;margin-bottom:36px}@media(max-width: 768px){.step1-wrap .s1-step-line,.step2-wrap .s1-step-line,.step3-wrap .s1-step-line{width:40px;margin-bottom:28px}}@media(max-width: 480px){.step1-wrap .s1-step-line,.step2-wrap .s1-step-line,.step3-wrap .s1-step-line{width:20px;margin-bottom:24px}}.step1-wrap .s1-alert,.step2-wrap .s1-alert,.step3-wrap .s1-alert{background:#fff5f5;border:1px solid #fed7d7;border-radius:10px;padding:12px 20px;color:#c53030;font-size:14px;margin-bottom:16px}.step1-wrap .s1-form-card,.step2-wrap .s1-form-card,.step3-wrap .s1-form-card{background:#fff;border:1px solid #e8edf5;border-radius:20px;box-shadow:0 4px 26.9px 2px rgba(192,192,192,.25);padding:48px 56px;margin-bottom:24px}.step1-wrap .s1-field,.step2-wrap .s1-field,.step3-wrap .s1-field{display:flex;flex-direction:column;gap:12px;margin-bottom:32px}.step1-wrap .s1-field:last-child,.step2-wrap .s1-field:last-child,.step3-wrap .s1-field:last-child{margin-bottom:0}.step1-wrap .s1-label,.step2-wrap .s1-label,.step3-wrap .s1-label{font-size:15px;font-weight:600;color:#0d0e11;margin:0;padding:0;display:block;line-height:1.4}.step1-wrap .s1-required,.step2-wrap .s1-required,.step3-wrap .s1-required{color:#f4253c;font-size:15px;margin-left:2px}.step1-wrap .s1-input,.step2-wrap .s1-input,.step3-wrap .s1-input{display:block;width:100%;height:47px;background:#fcfcfc;border:1px solid #dfdfdf;border-radius:10px;padding:0 20px;font-size:15px;font-weight:300;color:#0d0e11;box-sizing:border-box;outline:none;transition:border-color .2s;font-family:inherit;appearance:none;box-shadow:none}.step1-wrap .s1-input::placeholder,.step2-wrap .s1-input::placeholder,.step3-wrap .s1-input::placeholder{color:#7f8a95;font-weight:300}.step1-wrap .s1-input:focus,.step2-wrap .s1-input:focus,.step3-wrap .s1-input:focus{border-color:#2a69de}.step1-wrap .s1-textarea,.step2-wrap .s1-textarea,.step3-wrap .s1-textarea{display:block;width:100%;height:220px;background:#fcfcfc;border:1px solid #dfdfdf;border-radius:10px;padding:16px 20px;font-size:15px;font-weight:300;color:#0d0e11;box-sizing:border-box;outline:none;resize:none;transition:border-color .2s;font-family:inherit;box-shadow:none}.step1-wrap .s1-textarea::placeholder,.step2-wrap .s1-textarea::placeholder,.step3-wrap .s1-textarea::placeholder{color:#7f8a95;font-weight:300}.step1-wrap .s1-textarea:focus,.step2-wrap .s1-textarea:focus,.step3-wrap .s1-textarea:focus{border-color:#2a69de}.step1-wrap .s1-counter-row,.step2-wrap .s1-counter-row,.step3-wrap .s1-counter-row{display:flex;justify-content:flex-end;margin-top:-4px}.step1-wrap .s1-counter,.step2-wrap .s1-counter,.step3-wrap .s1-counter{font-size:13px;color:#94a3b8}.step1-wrap .s1-upload-box,.step2-wrap .s1-upload-box,.step3-wrap .s1-upload-box{width:100%;min-height:220px;background:#fcfcfc;border:1px solid #dfdfdf;border-radius:10px;display:flex;align-items:center;justify-content:center;position:relative;transition:border-color .2s;cursor:default}.step1-wrap .s1-upload-box.drag-over,.step2-wrap .s1-upload-box.drag-over,.step3-wrap .s1-upload-box.drag-over{border-color:#2a69de;background:#f0f7ff}.step1-wrap .s1-upload-box--compact,.step2-wrap .s1-upload-box--compact,.step3-wrap .s1-upload-box--compact{min-height:96px}.step1-wrap .s1-upload-box--compact .s1-upload-inner,.step2-wrap .s1-upload-box--compact .s1-upload-inner,.step3-wrap .s1-upload-box--compact .s1-upload-inner{flex-direction:row;flex-wrap:wrap;justify-content:center;gap:6px 14px;padding:12px 16px}.step1-wrap .s1-upload-box--compact .s1-upload-inner>img,.step2-wrap .s1-upload-box--compact .s1-upload-inner>img,.step3-wrap .s1-upload-box--compact .s1-upload-inner>img{width:28px;height:28px}.step1-wrap .s1-upload-box--compact .s1-upload-title,.step2-wrap .s1-upload-box--compact .s1-upload-title,.step3-wrap .s1-upload-box--compact .s1-upload-title{font-size:13px}.step1-wrap .s1-upload-box--compact .s1-btn-upload,.step2-wrap .s1-upload-box--compact .s1-btn-upload,.step3-wrap .s1-upload-box--compact .s1-btn-upload{height:34px;padding:0 16px;font-size:13px}.step1-wrap .s1-upload-inner,.step2-wrap .s1-upload-inner,.step3-wrap .s1-upload-inner{display:flex;flex-direction:column;align-items:center;gap:10px;padding:24px}.step1-wrap .s1-upload-title,.step2-wrap .s1-upload-title,.step3-wrap .s1-upload-title{font-size:15px;font-weight:600;color:#0d0e11;margin:0;text-align:center}.step1-wrap .s1-upload-hint,.step2-wrap .s1-upload-hint,.step3-wrap .s1-upload-hint{font-size:12px;font-weight:300;color:#7f8a95;margin:0;text-align:center}.step1-wrap .s1-btn-upload,.step2-wrap .s1-btn-upload,.step3-wrap .s1-btn-upload{background:#2a69de;color:#fff;border:none;border-radius:10px;height:44px;padding:0 24px;font-size:15px;font-weight:500;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;margin-top:4px;font-family:inherit;transition:background .15s}.step1-wrap .s1-btn-upload:hover,.step2-wrap .s1-btn-upload:hover,.step3-wrap .s1-btn-upload:hover{background:#1b4ab7}.step1-wrap .s1-preview-img,.step2-wrap .s1-preview-img,.step3-wrap .s1-preview-img{max-width:120px;max-height:120px;object-fit:cover;border-radius:10px;display:block}.step1-wrap .s1-btn-remove-icon,.step2-wrap .s1-btn-remove-icon,.step3-wrap .s1-btn-remove-icon{position:absolute;top:10px;right:10px;width:28px;height:28px;background:rgba(0,0,0,.45);border:none;border-radius:50%;color:#fff;font-size:14px;cursor:pointer;display:flex;align-items:center;justify-content:center}.step1-wrap .s1-ip-row,.step2-wrap .s1-ip-row,.step3-wrap .s1-ip-row{display:flex;gap:10px;align-items:center;width:100%;margin-bottom:10px}.step1-wrap .s1-ip-row .s1-input,.step2-wrap .s1-ip-row .s1-input,.step3-wrap .s1-ip-row .s1-input{flex:1}.step1-wrap .s1-ip-item,.step2-wrap .s1-ip-item,.step3-wrap .s1-ip-item{display:flex;gap:10px;align-items:center;width:100%;margin-bottom:10px}.step1-wrap .s1-ip-display,.step2-wrap .s1-ip-display,.step3-wrap .s1-ip-display{flex:1;height:47px;background:#ededed;border:1px solid #dfdfdf;border-radius:10px;padding:0 20px;font-size:15px;font-weight:300;color:#7f8a95;display:flex;align-items:center;box-sizing:border-box}.step1-wrap .s1-ip-btn,.step2-wrap .s1-ip-btn,.step3-wrap .s1-ip-btn{width:88px;height:47px;border:none;border-radius:10px;font-size:14px;font-weight:600;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;gap:5px;flex-shrink:0;font-family:inherit;transition:opacity .15s}.step1-wrap .s1-ip-btn:hover,.step2-wrap .s1-ip-btn:hover,.step3-wrap .s1-ip-btn:hover{opacity:.85}.step1-wrap .s1-ip-btn--add,.step2-wrap .s1-ip-btn--add,.step3-wrap .s1-ip-btn--add{background:#d5efff;color:#2a69de}.step1-wrap .s1-ip-btn--remove,.step2-wrap .s1-ip-btn--remove,.step3-wrap .s1-ip-btn--remove{background:#fbe7e9;color:#bb1026}.step1-wrap .s1-actions,.step2-wrap .s1-actions,.step3-wrap .s1-actions{display:flex;justify-content:flex-end;padding:0}.step1-wrap .s1-btn-next,.step2-wrap .s1-btn-next,.step3-wrap .s1-btn-next{width:200px;height:48px;background:#2a69de;color:#fff;border:none;border-radius:10px;font-size:16px;font-weight:500;cursor:pointer;font-family:inherit;transition:background .15s;display:inline-flex;align-items:center;justify-content:center}.step1-wrap .s1-btn-next:hover,.step2-wrap .s1-btn-next:hover,.step3-wrap .s1-btn-next:hover{background:#1b4ab7}@media(max-width: 768px){.step1-wrap .s1-form-card,.step2-wrap .s1-form-card,.step3-wrap .s1-form-card{padding:28px 20px;border-radius:14px}.step1-wrap .s1-steps,.step2-wrap .s1-steps,.step3-wrap .s1-steps{gap:0}.step1-wrap .s1-step-line,.step2-wrap .s1-step-line,.step3-wrap .s1-step-line{width:60px}.step1-wrap .s1-step,.step2-wrap .s1-step,.step3-wrap .s1-step{width:60px}.step1-wrap .s1-step-name,.step2-wrap .s1-step-name,.step3-wrap .s1-step-name{font-size:11px;white-space:normal;text-align:center}.step1-wrap .s1-ip-btn,.step2-wrap .s1-ip-btn,.step3-wrap .s1-ip-btn{width:72px;font-size:13px}.step1-wrap .s1-btn-next,.step2-wrap .s1-btn-next,.step3-wrap .s1-btn-next{width:100%}}.step2-wrap{width:100%;font-family:"Spoqa Han Sans Neo","Noto Sans KR",sans-serif}.step2-wrap .s2-selection-container{margin-bottom:35px}.step2-wrap .s2-category-carousel-container{display:flex;align-items:center;gap:12px;margin-bottom:24px;width:100%;position:relative;user-select:none}@media(max-width: 768px){.step2-wrap .s2-category-carousel-container{gap:6px;margin-bottom:16px;margin-top:40px}}.step2-wrap .s2-category-list-wrapper{flex:1;overflow-x:auto;overflow-y:hidden;scroll-behavior:smooth;-ms-overflow-style:none;scrollbar-width:none}.step2-wrap .s2-category-list-wrapper::-webkit-scrollbar{display:none}.step2-wrap .s2-category-list{display:flex;gap:8px;white-space:nowrap;padding:4px 0}.step2-wrap .s2-category-tab{display:inline-flex;align-items:center;justify-content:center;padding:8px 22px;height:40px;background:#e6f4ff;border:1.5px solid #2a69de;border-radius:20px;font-size:15px;font-weight:500;color:#2a69de;cursor:pointer;transition:all .2s ease;box-sizing:border-box;font-family:"Spoqa Han Sans Neo",sans-serif}.step2-wrap .s2-category-tab:hover{background:rgba(42,105,222,.05)}.step2-wrap .s2-category-tab.active{background:#2a69de;color:#fff}@media(max-width: 768px){.step2-wrap .s2-category-tab{padding:6px 14px;height:34px;font-size:13px}}.step2-wrap .s2-carousel-btn{width:36px;height:36px;border-radius:50%;background:#fff;border:1px solid #dfdfdf;color:#94a3b8;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:all .2s ease;flex-shrink:0;box-shadow:0 2px 5px rgba(0,0,0,.05)}.step2-wrap .s2-carousel-btn:hover{border-color:#2a69de;color:#2a69de;background:#f8fafc}.step2-wrap .s2-carousel-btn:disabled{opacity:.35;cursor:not-allowed}.step2-wrap .s2-carousel-btn:disabled:hover{border-color:#dfdfdf;color:#94a3b8;background:#fff}@media(max-width: 768px){.step2-wrap .s2-carousel-btn{width:30px;height:30px}.step2-wrap .s2-carousel-btn svg{width:14px;height:14px}}.step2-wrap .s2-form{width:100%;display:flex;flex-direction:column}.step2-wrap .s2-filter-header{display:flex;justify-content:flex-end;align-items:center;margin-bottom:24px;gap:16px}@media(max-width: 768px){.step2-wrap .s2-filter-header{flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:12px}}.step2-wrap .s2-select-all{display:flex;align-items:center;flex:1}.step2-wrap .s2-select-all .s2-select-all-label{display:flex;align-items:center;gap:8px;cursor:pointer;font-size:15px;color:#0d0e11;font-weight:500;user-select:none}.step2-wrap .s2-select-all .s2-select-all-label:hover{color:#2a69de}.step2-wrap .s2-search-box{position:relative;width:228px;height:44px;flex-shrink:0}@media(max-width: 768px){.step2-wrap .s2-search-box{width:100%}}.step2-wrap .s2-search-box .s2-search-input{width:100%;height:100%;padding:0 44px 0 17px;border:1px solid #dfdfdf;border-radius:10px;background-color:#fcfcfc;font-size:14px;outline:none;transition:all .2s ease}.step2-wrap .s2-search-box .s2-search-input:focus{border-color:#2a69de;background-color:#fff}.step2-wrap .s2-search-box .s2-search-input::placeholder{color:#7f8a95}.step2-wrap .s2-search-box svg{position:absolute;right:17px;top:50%;transform:translateY(-50%);pointer-events:none}.step2-wrap .s2-result-count{font-size:15px;color:#64748b;margin-right:auto}.step2-wrap .s2-result-count strong{color:#2a69de;font-weight:600}@media(max-width: 768px){.step2-wrap .s2-result-count{margin-right:0}}.step2-wrap .s2-cards-grid{display:grid;grid-template-columns:repeat(3, 1fr);gap:24px;margin-bottom:24px}@media(max-width: 1200px){.step2-wrap .s2-cards-grid{grid-template-columns:repeat(2, 1fr);gap:16px}}@media(max-width: 768px){.step2-wrap .s2-cards-grid{grid-template-columns:1fr;gap:16px}}.step2-wrap .s2-api-card{background:#fff;border-radius:20px;border:1px solid #ddd;box-shadow:0px 4px 2px rgba(194,194,194,.25);cursor:pointer;position:relative;width:100%;height:312px;overflow:hidden;transition:transform .3s ease,box-shadow .3s ease,background .3s ease;box-sizing:border-box}.step2-wrap .s2-api-card:hover,.step2-wrap .s2-api-card:focus{transform:translateY(-5px);box-shadow:0px 8px 12px rgba(194,194,194,.4);background:linear-gradient(to bottom, #50caff, #2088ff);border:none}.step2-wrap .s2-api-card:hover .s2-api-card-badge,.step2-wrap .s2-api-card:focus .s2-api-card-badge{background-color:#d8f0ff}.step2-wrap .s2-api-card:hover .s2-api-card-badge span,.step2-wrap .s2-api-card:focus .s2-api-card-badge span{color:#1b4ab7}.step2-wrap .s2-api-card:hover .s2-api-card-title,.step2-wrap .s2-api-card:hover .s2-api-card-desc,.step2-wrap .s2-api-card:focus .s2-api-card-title,.step2-wrap .s2-api-card:focus .s2-api-card-desc{color:#fff}.step2-wrap .s2-api-card.selected{background:linear-gradient(to bottom, #50caff, #2088ff);border-color:rgba(0,0,0,0);box-shadow:0px 8px 12px rgba(32,136,255,.3)}.step2-wrap .s2-api-card.selected .s2-api-card-badge{background-color:#d8f0ff}.step2-wrap .s2-api-card.selected .s2-api-card-badge span{color:#1b4ab7}.step2-wrap .s2-api-card.selected .s2-api-card-title,.step2-wrap .s2-api-card.selected .s2-api-card-desc{color:#fff}.step2-wrap .s2-api-card .s2-api-card-badge{position:absolute;top:39px;left:32px;background-color:#f2f2f3;border-radius:10px;padding:2px 20px;display:inline-flex;align-items:center;justify-content:center}.step2-wrap .s2-api-card .s2-api-card-badge span{font-size:14px;font-family:"Spoqa Han Sans Neo",sans-serif;font-weight:500;color:#1b4ab7;white-space:nowrap}.step2-wrap .s2-api-card .s2-checkbox-wrapper{position:absolute;top:39px;right:32px;z-index:2}.step2-wrap .s2-api-card .s2-checkbox-wrapper .s2-api-checkbox{width:22px;height:22px;cursor:pointer}.step2-wrap .s2-api-card .s2-api-card-title{position:absolute;top:86px;left:36px;right:40px;font-size:18px;font-family:"Spoqa Han Sans Neo",sans-serif;font-weight:700;color:#000;margin:0;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;text-overflow:ellipsis;word-break:break-word;text-align:left;white-space:normal}.step2-wrap .s2-api-card .s2-api-card-desc{position:absolute;top:135px;left:36px;right:40px;font-size:14px;font-family:"Spoqa Han Sans Neo",sans-serif;font-weight:400;color:#64748b;line-height:1.5;margin:0;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;text-overflow:ellipsis;word-break:break-word;text-align:left;white-space:normal}.step2-wrap .s2-api-card .s2-api-card-image{position:absolute;bottom:28px;right:20px;width:93px;height:88px;display:flex;align-items:center;justify-content:center;pointer-events:none}.step2-wrap .s2-api-card .s2-api-card-image img{width:100%;height:100%;object-fit:contain}.step2-wrap .s2-api-card .s2-api-card-image i{font-size:32px;color:#94a3b8}@media(max-width: 768px){.step2-wrap .s2-api-card{height:260px}.step2-wrap .s2-api-card .s2-api-card-badge{top:24px;left:24px}.step2-wrap .s2-api-card .s2-checkbox-wrapper{top:24px;right:24px}.step2-wrap .s2-api-card .s2-api-card-title{top:76px;left:24px;right:24px;font-size:16px}.step2-wrap .s2-api-card .s2-api-card-desc{top:118px;left:24px;right:24px;font-size:13px;-webkit-line-clamp:2}.step2-wrap .s2-api-card .s2-api-card-image{bottom:20px;right:16px;width:60px;height:60px}}.step2-wrap .s2-checkbox-custom{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;border-radius:50%;border:2px solid #cbd5e1;background-color:#fff;cursor:pointer;transition:all .2s ease;position:relative;box-sizing:border-box}.step2-wrap .s2-checkbox-custom::after{content:"";width:5px;height:9px;border:solid rgba(0,0,0,0);border-width:0 2px 2px 0;transform:rotate(45deg);margin-top:-2px;opacity:0;transition:all .2s ease;box-sizing:border-box}.step2-wrap .s2-api-checkbox:checked+.s2-checkbox-custom{border-color:#2a69de;background-color:#2a69de}.step2-wrap .s2-api-checkbox:checked+.s2-checkbox-custom::after{border-color:#fff;opacity:1}.step2-wrap .s2-api-card.selected .s2-checkbox-custom{border-color:#fff;background-color:#fff}.step2-wrap .s2-api-card.selected .s2-checkbox-custom::after{border-color:#2a69de;opacity:1}.step2-wrap .s2-loading{padding:48px;text-align:center;color:#64748b}.step2-wrap .s2-loading .s2-spinner{width:48px;height:48px;margin:0 auto 16px;border:4px solid #dfdfdf;border-top-color:#2a69de;border-radius:50%;animation:spin 1s linear infinite}.step2-wrap .s2-empty{padding:48px;text-align:center;color:#64748b}.step2-wrap .s2-empty .s2-empty-icon{font-size:48px;margin-bottom:16px}.step2-wrap .s2-empty h3{font-size:18px;font-weight:600;color:#0d0e11;margin-bottom:8px}.step2-wrap .s2-actions{display:flex;justify-content:flex-end;gap:12px;margin-top:24px}.step2-wrap .s2-actions .s2-btn-prev{width:144px;height:48px;background:#f1f5f9;color:#0d0e11;border:1px solid #dfdfdf;border-radius:10px;font-size:16px;font-weight:500;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;transition:background .15s}.step2-wrap .s2-actions .s2-btn-prev:hover{background:#e2e8f0}.step2-wrap .s2-actions .s2-btn-save{width:200px;height:48px;background:#2a69de;color:#fff;border:none;border-radius:10px;font-size:16px;font-weight:500;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;transition:background .15s}.step2-wrap .s2-actions .s2-btn-save:hover{background:#1b4ab7}@media(max-width: 768px){.step2-wrap .s2-actions{flex-direction:column}.step2-wrap .s2-actions .s2-btn-prev,.step2-wrap .s2-actions .s2-btn-save{width:100%}}.s2-floating-cart{position:fixed;bottom:80px;right:40px;width:180px;height:66px;padding:10px 16px 10px 36px;border-radius:60px;background-color:#3ba4ed;border:none;color:#fff;display:flex;align-items:center;justify-content:space-between;cursor:pointer;box-shadow:0 10px 25px rgba(59,164,237,.35);transition:all .3s ease;z-index:1001}.s2-floating-cart .s2-cart-label{font-size:14px;font-weight:700;color:#fff;white-space:nowrap}.s2-floating-cart .s2-cart-badge{display:flex;flex-direction:row;align-items:center;justify-content:center;background:#fff;width:57px;height:57px;border-radius:50%;flex-shrink:0;margin-right:-8px}.s2-floating-cart .s2-cart-badge .s2-cart-count{font-size:24px;font-weight:700;color:#212529;line-height:1}.s2-floating-cart .s2-cart-badge .s2-cart-unit{font-size:14px;font-weight:700;color:#212529;line-height:1;margin-top:2px}.s2-floating-cart:hover{transform:translateY(-4px);box-shadow:0 12px 32px rgba(59,164,237,.5)}@media(max-width: 768px){.s2-floating-cart{bottom:100px;right:24px;width:160px;height:56px;padding:8px 12px 8px 28px}.s2-floating-cart .s2-cart-label{font-size:12px}.s2-floating-cart .s2-cart-badge{width:48px;height:48px;margin-right:-6px}.s2-floating-cart .s2-cart-badge .s2-cart-count{font-size:20px}.s2-floating-cart .s2-cart-badge .s2-cart-unit{font-size:12px}}.s2-modal{position:fixed;top:0;left:0;width:100%;height:100%;z-index:1050;display:flex;align-items:center;justify-content:center}.s2-modal .s2-modal-backdrop{position:absolute;top:0;left:0;width:100%;height:100%;background-color:rgba(0,0,0,.5);backdrop-filter:blur(2px)}.s2-modal .s2-modal-dialog{position:relative;background:#fff;border-radius:20px;box-shadow:0 10px 30px rgba(0,0,0,.3);width:100%;max-width:500px;margin:16px;display:flex;flex-direction:column;overflow:hidden;z-index:1}.s2-modal .s2-modal-header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px;border-bottom:1px solid #e8edf5}.s2-modal .s2-modal-header .s2-modal-title{font-size:18px;font-weight:700;color:#0d0e11;margin:0}.s2-modal .s2-modal-header .s2-modal-close{background:none;border:none;font-size:20px;cursor:pointer;color:#94a3b8}.s2-modal .s2-modal-header .s2-modal-close:hover{color:#0d0e11}.s2-modal .s2-modal-body{padding:24px;max-height:300px;overflow-y:auto}.s2-modal .s2-selected-list{display:flex;flex-wrap:wrap;gap:8px}.s2-modal .s2-api-pill{display:inline-flex;align-items:center;gap:8px;padding:8px 16px;background:#f1f5f9;border-radius:50px;font-size:14px;color:#0d0e11}.s2-modal .s2-api-pill .s2-api-pill-name{max-width:250px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.s2-modal .s2-api-pill .s2-api-pill-remove{background:none;border:none;font-size:14px;color:#94a3b8;cursor:pointer;padding:0;display:flex;align-items:center;justify-content:center}.s2-modal .s2-api-pill .s2-api-pill-remove:hover{color:#f4253c}.s2-modal .s2-modal-footer{padding:16px 24px;border-top:1px solid #e8edf5;display:flex;justify-content:flex-end}.s2-modal .s2-btn-close-modal{height:40px;padding:0 20px;background:#f1f5f9;color:#0d0e11;border:1px solid #dfdfdf;border-radius:8px;font-size:14px;font-weight:500;cursor:pointer}.s2-modal .s2-btn-close-modal:hover{background:#e2e8f0}.step3-wrap .s3-result-container{background:#fff;border:1px solid #e8edf5;border-radius:20px;box-shadow:0 4px 26.9px 2px rgba(192,192,192,.25);display:flex;flex-direction:column;align-items:center;padding:60px 40px;margin-bottom:24px;text-align:center}.step3-wrap .s3-graphic-wrapper{margin-bottom:20px;display:flex;align-items:center;justify-content:center;width:100%}.step3-wrap .s3-illustration{width:320px;height:auto;max-width:100%}.step3-wrap .s3-illustration .floating-slow{animation:floatSlow 4s ease-in-out infinite alternate}@keyframes floatSlow{0%{transform:translateY(0)}100%{transform:translateY(-10px)}}.step3-wrap .s3-message-wrapper{margin-bottom:40px}.step3-wrap .s3-success-title{font-size:26px;font-weight:700;color:#2a69de;margin-bottom:16px;font-family:"Spoqa Han Sans Neo",sans-serif}.step3-wrap .s3-success-desc{font-size:16px;line-height:1.6;color:#64748b;font-family:"Spoqa Han Sans Neo",sans-serif;margin:0}.step3-wrap .s3-success-desc .s3-highlight{color:#0d0e11;font-weight:700}.step3-wrap .s3-actions{display:flex;justify-content:center;width:100%}.step3-wrap .s3-btn-complete{display:inline-flex;align-items:center;justify-content:center;width:200px;height:48px;background:#2a69de;color:#fff;border-radius:10px;font-size:16px;font-weight:500;text-decoration:none;transition:background .15s}.step3-wrap .s3-btn-complete:hover{background:#1b4ab7;color:#fff}.step3-wrap .s3-result-container--error{padding:60px 40px}.step3-wrap .s3-error-icon{font-size:64px;margin-bottom:10px}.step3-wrap .s3-error-title{font-size:24px;font-weight:700;color:#f4253c;margin-bottom:16px}.step3-wrap .s3-error-desc{font-size:15px;color:#64748b}.step3-wrap .s3-btn-retry{display:inline-flex;align-items:center;justify-content:center;width:144px;height:48px;background:#f1f5f9;color:#0d0e11;border:1px solid #dfdfdf;border-radius:10px;font-size:16px;font-weight:500;text-decoration:none;margin-right:12px;transition:background .15s}.step3-wrap .s3-btn-retry:hover{background:#e2e8f0}.step3-wrap .s3-btn-list{display:inline-flex;align-items:center;justify-content:center;width:200px;height:48px;background:#2a69de;color:#fff;border-radius:10px;font-size:16px;font-weight:500;text-decoration:none;transition:background .15s}.step3-wrap .s3-btn-list:hover{background:#1b4ab7;color:#fff}.apikey-detail-container{padding:40px 0}@media(max-width: 768px){.apikey-detail-container{padding:24px 16px}}.register-header{text-align:center;margin-bottom:80px}.register-header h1{font-size:48px;font-weight:700;color:#1a1a2e;margin-bottom:8px}.register-header .header-description{font-size:16px;color:#64748b}.status-section{text-align:center;margin-bottom:64px}.status-badge.badge-pending{background-color:#fff3cd;color:#856404}.status-badge.badge-approved{background-color:#d4edda;color:#155724}.status-badge.badge-rejected{background-color:#f8d7da;color:#721c24}.section-title{font-size:24px;font-weight:700;color:#1a1a2e;margin-bottom:24px}.detail-label{font-size:14px;font-weight:600;color:#64748b}.detail-value{font-size:16px;color:#1a1a2e;word-break:break-word}.status-dot{width:8px;height:8px;border-radius:50%;background-color:currentColor}.api-list-item .api-name{flex:1;font-size:16px;font-weight:500;color:#1a1a2e;margin:0}.api-method{display:inline-block;padding:4px 16px;border-radius:6px;font-size:14px;font-weight:700;font-family:"Fira Code","Courier New",monospace;min-width:80px;text-align:center}.api-method.method-get{background-color:#d1fae5;color:#065f46}.api-method.method-post{background-color:#dbeafe;color:#1e40af}.api-method.method-put{background-color:#fef3c7;color:#92400e}.api-method.method-delete{background-color:#fee2e2;color:#991b1b}.api-method.method-other{background-color:#e2e8f0;color:#1a1a2e}.api-status{padding:4px 16px;border-radius:12px;font-size:14px;font-weight:600}.api-status.status-pending{background-color:#fff3cd;color:#856404}.api-status.status-active{background-color:#d4edda;color:#155724}.history-table-wrapper{overflow-x:auto}.history-table{width:100%;border-collapse:collapse}.history-table th{background-color:#f8fafc;padding:16px 16px;text-align:left;font-weight:600;color:#64748b;border-bottom:2px solid #e2e8f0}.history-table td{padding:16px;border-bottom:1px solid #e2e8f0}.history-status{display:inline-block;padding:4px 16px;background-color:#eff6ff;color:#c3dfea;border-radius:12px;font-size:14px;font-weight:600}.empty-state{grid-column:1/-1;padding:48px 24px}@media(max-width: 768px){.btn-action,.btn-secondary,.btn-cancel{width:100%}}.app-info-card{display:flex;align-items:flex-start;gap:20px;padding:0;padding-top:20px;margin-bottom:30px}@media(max-width: 768px){.app-info-card{flex-direction:column;align-items:center;text-align:center;padding:16px 0;gap:16px}}.app-info-icon{flex-shrink:0;width:100px;height:100px;border-radius:12px;overflow:hidden;background:#f8fafc;display:flex;align-items:center;justify-content:center;margin-left:40px}@media(max-width: 768px){.app-info-icon{width:80px;height:80px;margin-left:0}}.app-info-icon .app-icon-image{width:100%;height:100%;object-fit:cover}.app-info-icon .app-icon-placeholder{color:#94a3b8;display:flex;align-items:center;justify-content:center}.app-info-content{flex:1;display:flex;flex-direction:column;gap:8px}.app-status-badge{display:inline-block;padding:3px 12px;border-radius:50px;font-size:12px;font-weight:600;width:fit-content}.app-status-badge.status-approved{background-color:#0049b4;color:#fff}.app-status-badge.status-pending{background-color:#fff3cd;color:#856404}.app-status-badge.status-inactive{background-color:#e2e8f0;color:#64748b}.app-status-badge.status-rejected{background-color:#f8d7da;color:#721c24}.app-info-name{font-size:18px;font-weight:700;color:#1a1a2e;margin:0}@media(max-width: 768px){.app-info-name{font-size:16px}}.app-info-description{font-size:14px;color:#515151;margin:0;line-height:1.5}@media(max-width: 768px){.app-info-description{font-size:13px}}.apikey-info-section{background:#f6f9fb;border-radius:12px;padding:24px 30px;margin-bottom:30px}@media(max-width: 768px){.apikey-info-section{padding:20px}}.info-row{display:flex;align-items:center;gap:20px;margin-bottom:16px}.info-row:last-child{margin-bottom:0}@media(max-width: 768px){.info-row{flex-direction:column;align-items:flex-start;gap:8px;margin-bottom:12px}}.info-row-vertical{align-items:flex-start}.info-row-vertical .info-value{width:100%}@media(max-width: 768px){.info-row-vertical .info-value{max-width:100%}}.info-label{flex-shrink:0;width:140px;font-size:15px;font-weight:600;color:#212529}@media(max-width: 768px){.info-label{width:100%;font-size:14px}}.info-value{flex:1;font-size:14px;color:#1a1a2e;word-break:break-word}@media(max-width: 768px){.info-value{width:100%}}.info-value-box{display:flex;align-items:center;justify-content:center;height:40px;padding:0 16px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;font-size:14px;color:#1a1a2e;text-align:center}@media(max-width: 768px){.info-value-box{height:36px;font-size:14px;padding:0 12px}}.info-value-box.with-copy{flex:1}.info-row-with-action .info-value{display:flex;gap:12px;align-items:center}.btn-copy-action{display:flex;align-items:center;justify-content:center;gap:4px;height:40px;width:auto;padding:0 16px;background:#a4d6ea;border:none;border-radius:8px;font-size:14px;font-weight:600;color:#fff;cursor:pointer;white-space:nowrap;transition:background .2s ease}.btn-copy-action:hover{background:rgb(131.6625,199.4303571429,226.5375)}@media(max-width: 768px){.btn-copy-action{height:36px;font-size:13px;padding:0 12px}}.btn-view-secret{display:flex;align-items:center;justify-content:center;gap:8px;width:100%;height:60px;background:#3ba4ed;border:none;border-radius:12px;font-size:20px;font-weight:700;color:#fff;cursor:pointer;transition:background .2s ease}.btn-view-secret:hover{background:rgb(31.8897196262,151.4130841121,234.5102803738)}.btn-view-secret svg{width:20px;height:20px}@media(max-width: 768px){.btn-view-secret{max-width:100%;height:50px;font-size:16px}}.api-list-box{width:100%;background:#fff;border:1px solid #dadada;border-radius:12px;padding:24px}@media(max-width: 768px){.api-list-box{max-width:100%;padding:16px}}.api-list-item{display:flex;align-items:center;justify-content:space-between;padding:16px 0;border-bottom:1px solid #dadada}.api-list-item:last-child{border-bottom:none}.api-list-item .api-item-name{font-size:18px;color:#000}@media(max-width: 768px){.api-list-item .api-item-name{font-size:16px}}.api-list-item .api-item-status{display:flex;align-items:center;justify-content:center;min-width:100px;height:30px;padding:0 24px;background:#eceff4;border-radius:50px;font-size:16px;color:#515151}@media(max-width: 768px){.api-list-item .api-item-status{min-width:80px;font-size:14px;padding:0 16px}}@media(max-width: 768px){.apikey-detail-container{padding:0 !important;max-width:100%}.register-title-bar{padding:20px;margin-bottom:0;border-bottom:1px solid #dadada}.register-title-bar .register-title{font-size:16px;font-weight:700;margin-bottom:4px}.register-title-bar .register-subtitle{font-size:12px;color:#666}.app-info-card{flex-direction:row !important;align-items:flex-start !important;text-align:left !important;padding:20px;gap:16px;margin-bottom:0;background:#fff}.app-info-card .app-info-icon{width:56px !important;height:56px !important;min-width:56px;margin-left:0;border-radius:8px}.app-info-card .app-info-icon .app-icon-image{width:100%;height:100%;object-fit:cover;border-radius:8px}.app-info-card .app-info-icon .app-icon-placeholder svg{width:32px;height:32px}.app-info-card .app-info-content{gap:4px}.app-info-card .app-status-badge{padding:4px 12px;font-size:12px;border-radius:20px}.app-info-card .app-status-badge.status-approved{background-color:#0049b4;color:#fff}.app-info-card .app-info-name{font-size:16px !important;font-weight:700}.app-info-card .app-info-description{font-size:12px !important;color:#666;line-height:1.4}.apikey-info-section{background:#f6f9fb;border-radius:8px;padding:20px;margin:0px}.info-row{flex-direction:column;align-items:flex-start;gap:8px;margin-bottom:16px}.info-row:last-child{margin-bottom:0}.info-label{font-size:14px !important;font-weight:700;color:#212529;width:100%}.info-value{width:100%}.info-value-box{height:40px !important;font-size:14px !important;background:#f2f2f2;border:1px solid #dadada;border-radius:8px;padding:0 12px}.info-value-box.with-copy{flex:1}.info-row-with-action .info-value{display:flex;flex-direction:row;gap:8px;align-items:center}.btn-copy-action{width:89px !important;min-width:89px;height:40px !important;font-size:14px !important;font-weight:700;padding:0 12px;background:#a4d6ea;border-radius:8px}.btn-copy-action:hover{background:rgb(131.6625,199.4303571429,226.5375)}.btn-view-secret{width:100% !important;height:40px !important;font-size:14px !important;font-weight:700;background:#3ba4ed;border-radius:8px;gap:8px}.btn-view-secret svg{width:16px;height:16px}.btn-view-secret:hover{background:rgb(31.8897196262,151.4130841121,234.5102803738)}#revealedSecretBox{width:100%}#revealedSecretBox .info-row-with-action .info-value{flex-direction:row;gap:8px}.api-list-box{padding:12px;border-radius:8px}.api-list-item{padding:12px 0}.api-list-item .api-item-name{font-size:14px !important;color:#000}.api-list-item .api-item-status{min-width:auto;height:24px !important;padding:0 12px;font-size:12px !important;background:#eceff4;border-radius:20px;color:#515151}.info-row-vertical .info-value{width:100%;max-width:100%}.form-actions-center{display:flex;flex-direction:row !important;justify-content:center !important;align-items:center;gap:12px;padding:24px 20px;margin-top:0;background-color:rgba(0,0,0,0)}.form-actions-center .btn-submit,.form-actions-center .btn{width:144px !important;height:40px !important;min-height:40px;padding:8px 10px !important;font-size:14px !important;font-weight:700;border-radius:8px !important;flex-shrink:0}.form-actions-center .btn-submit.btn-secondary,.form-actions-center .btn.btn-secondary{background-color:#e5e7eb !important;color:#5f666c !important;border:none !important}.form-actions-center .btn-submit.btn-primary,.form-actions-center .btn.btn-primary{background-color:#0049b4 !important;color:#fff !important;border:none !important}.form-actions-center .btn-submit.btn-danger,.form-actions-center .btn.btn-danger{display:none}}.detail-wrap{width:100%;font-family:"Spoqa Han Sans Neo","Noto Sans KR",sans-serif}.detail-wrap .dt-title{font-size:30px;font-weight:700;color:#000;margin:0 0 28px 0;text-align:left;font-family:"Spoqa Han Sans Neo","Noto Sans KR",sans-serif}@media(max-width: 768px){.detail-wrap .dt-title{font-size:24px;margin-bottom:20px}}.detail-wrap .dt-app-card{display:flex;align-items:center;gap:16px;background:#fff;border:1px solid #dfdfdf;border-radius:20px;box-shadow:0px 4px 13.45px rgba(192,192,192,.25);padding:20px 24px;margin-bottom:24px;box-sizing:border-box}@media(max-width: 768px){.detail-wrap .dt-app-card{flex-direction:column;align-items:flex-start;gap:12px;padding:16px}}.detail-wrap .dt-app-icon-box{width:68px;height:67px;background:#f9f9f9;border:1px solid #dee3e7;border-radius:10px;display:flex;justify-content:center;align-items:center;flex-shrink:0}.detail-wrap .dt-app-icon-box img,.detail-wrap .dt-app-icon-box svg{width:46px;height:46px;object-fit:cover;border-radius:4px}.detail-wrap .dt-app-info{flex:1;display:flex;flex-direction:column;gap:8px;min-width:0}.detail-wrap .dt-app-header-row{display:flex;align-items:center;gap:10px}.detail-wrap .dt-app-name{font-size:16px;font-weight:500;color:#000;margin:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.detail-wrap .dt-status-badge{height:20px;border-radius:5px;padding:0 8px;font-size:10px;font-weight:500;display:inline-flex;align-items:center;justify-content:center;white-space:nowrap}.detail-wrap .dt-status-badge.status-approved,.detail-wrap .dt-status-badge.status-completed{background:#1b4ab7;color:#fff}.detail-wrap .dt-status-badge.status-pending{background:#fdf4d4;color:#ef9546}.detail-wrap .dt-status-badge.status-rejected{background:#fef2f2;color:#ef4444;border:1px solid #fee2e2}.detail-wrap .dt-app-desc{font-size:14px;color:#64748b;margin:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.detail-wrap .dt-info-container{background:#fff;border:1px solid #dfdfdf;border-radius:30px;box-shadow:0px 4px 26.9px 2px rgba(192,192,192,.25);padding:40px;margin-bottom:35px;display:flex;flex-direction:column;gap:28px;box-sizing:border-box}@media(max-width: 768px){.detail-wrap .dt-info-container{padding:24px 20px;border-radius:20px;gap:20px}}.detail-wrap .dt-row{display:flex;gap:28px;width:100%}@media(max-width: 768px){.detail-wrap .dt-row{flex-direction:column;gap:20px}}.detail-wrap .dt-col{flex:1;display:flex;flex-direction:column;gap:12px;min-width:0}.detail-wrap .dt-label{font-size:16px;font-weight:700;color:#000;margin:0}.detail-wrap .dt-input-group{display:flex;gap:7px;width:100%}.detail-wrap .dt-input-box{flex:1;height:47px;background:#ededed;border:1px solid #dfdfdf;border-radius:10px;display:flex;align-items:center;padding:0 20px;font-size:16px;font-weight:300;color:#0d0e11;box-sizing:border-box;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.detail-wrap .dt-input-box.text-secret{letter-spacing:2px;font-weight:700}@media(max-width: 768px){.detail-wrap .dt-input-box{padding:0 14px;font-size:14px}}.detail-wrap .dt-btn-copy{height:47px;background:#d5efff;border:none;border-radius:10px;display:inline-flex;align-items:center;justify-content:center;gap:8px;padding:0 20px;font-size:16px;font-weight:500;color:#2a69de;cursor:pointer;transition:background .2s ease;flex-shrink:0}.detail-wrap .dt-btn-copy:hover{background:rgb(189.6,230.0857142857,255)}.detail-wrap .dt-btn-copy svg{color:#2a69de;flex-shrink:0}@media(max-width: 768px){.detail-wrap .dt-btn-copy{padding:0 12px;font-size:14px}}.detail-wrap .dt-btn-lookup{width:100%;height:47px;background:#2a69de;border:none;border-radius:10px;display:inline-flex;align-items:center;justify-content:center;gap:8px;font-size:16px;font-weight:500;color:#fff;cursor:pointer;transition:background .2s ease}.detail-wrap .dt-btn-lookup:hover{background:#1b4ab7}.detail-wrap .dt-btn-lookup svg{color:#fff;flex-shrink:0}.detail-wrap .dt-api-list-box{width:100%;background:#fff;border:1px solid #dfdfdf;border-radius:10px;padding:0 20px;box-sizing:border-box;max-height:240px;overflow-y:auto}.detail-wrap .dt-api-list-box::-webkit-scrollbar{width:6px}.detail-wrap .dt-api-list-box::-webkit-scrollbar-track{background:rgba(0,0,0,0)}.detail-wrap .dt-api-list-box::-webkit-scrollbar-thumb{background:#cbd5e1;border-radius:3px}.detail-wrap .dt-api-list-box::-webkit-scrollbar-thumb:hover{background:#94a3b8}.detail-wrap .dt-api-item{display:flex;align-items:center;justify-content:space-between;padding:16px 0;border-bottom:1px solid #efefef}.detail-wrap .dt-api-item:last-child{border-bottom:none}.detail-wrap .dt-api-name{font-size:16px;font-weight:500;color:#0d0e11;margin:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding-right:20px;flex:1}@media(max-width: 768px){.detail-wrap .dt-api-name{font-size:14px}}.detail-wrap .dt-api-status-badge{height:18px;background:#ecf0fa;border:1px solid #4685ef;border-radius:17px;padding:0 10px;font-size:10px;font-weight:500;color:#2a69de;display:inline-flex;align-items:center;justify-content:center;white-space:nowrap}.detail-wrap .dt-api-status-badge.status-pending{background:#eceff4;border:1px solid #dee3e7;color:#515151}.detail-wrap .dt-empty-message{padding:30px;text-align:center;color:#64748b;font-size:14px;margin:0}.detail-wrap .dt-cancel-notice{margin:10px 0 0;text-align:center;font-size:14px;color:#64748b}.detail-wrap .info-notice-box{background:#ecf0fa;border-radius:7px;padding:13px 14px 12px;display:flex;align-items:center;gap:13px;margin-bottom:32px}.detail-wrap .info-notice-box svg{width:24px;height:24px;flex-shrink:0}.detail-wrap .info-notice-box p{color:#4685ef;font-size:16px;font-weight:500;margin:0}.detail-wrap .info-notice-box.danger{background:#fdf3f3}.detail-wrap .info-notice-box.danger p{color:#f4253c}.detail-wrap .dt-actions{display:flex;justify-content:center;align-items:center;gap:12px;margin-top:10px}@media(max-width: 768px){.detail-wrap .dt-actions{flex-direction:column;width:100%}.detail-wrap .dt-actions .dt-btn-gray,.detail-wrap .dt-actions .dt-btn-red,.detail-wrap .dt-actions .dt-btn-blue{width:100%}}.detail-wrap .dt-btn-gray{width:156px;height:45px;background:#bdc7cf;border:none;border-radius:10px;display:inline-flex;align-items:center;justify-content:center;font-size:16px;font-weight:500;color:#fff;cursor:pointer;text-decoration:none;transition:background .2s ease}.detail-wrap .dt-btn-gray:hover{background:rgb(170.6589473684,183.4378947368,193.6610526316)}.detail-wrap .dt-btn-red{width:156px;height:45px;background:#ff615c;border:none;border-radius:10px;display:inline-flex;align-items:center;justify-content:center;font-size:16px;font-weight:500;color:#fff;cursor:pointer;transition:background .2s ease}.detail-wrap .dt-btn-red:hover{background:rgb(255,70.0915337423,64.24)}.detail-wrap .dt-btn-blue{width:156px;height:45px;background:#2a69de;border:none;border-radius:10px;display:inline-flex;align-items:center;justify-content:center;font-size:16px;font-weight:500;color:#fff;cursor:pointer;text-decoration:none;transition:background .2s ease}.detail-wrap .dt-btn-blue:hover{background:#1b4ab7}@media(max-width: 768px){.app-management-container{padding:24px 16px}}@media(max-width: 768px){.app-list-title-section{background-color:rgba(0,0,0,0);border-radius:0;padding:0 0 6px 0;margin-bottom:0;border-bottom:2px solid #1a1a2e}}.app-list-title{font-size:28px;color:#1a1a2e;margin:0}@media(max-width: 768px){.app-list-title{font-size:16px;font-weight:500}}.app-list-container{min-height:400px}@media(max-width: 768px){.app-list-container{background:rgba(0,0,0,0);box-shadow:none;border:none;padding:0;min-height:auto}}.app-list-item{display:flex;align-items:center;gap:24px;padding:40px 0;border-bottom:1px solid #e0e0e0;text-decoration:none;color:inherit;transition:background-color .2s ease}.app-list-item:last-child{border-bottom:none}.app-list-item:hover{background-color:hsla(0,0%,100%,.5);margin:0 -20px;padding-left:20px;padding-right:20px;border-radius:8px}@media(max-width: 768px){.app-list-item{flex-direction:row;align-items:center;padding:14px 0;gap:16px;min-height:85px}.app-list-item:hover{margin:0;padding-left:0;padding-right:0;background-color:rgba(0,0,0,0)}.app-list-item:last-child{border-bottom:none}}.app-list-icon{width:70px;height:70px;flex-shrink:0}@media(max-width: 768px){.app-list-icon{width:56px;height:56px}}.app-list-icon .app-icon-image{border:none}.app-list-icon .app-icon-placeholder{font-size:inherit}.app-list-info{display:flex;flex-direction:column;gap:14px;flex:1;min-width:0}@media(max-width: 768px){.app-list-info{gap:8px}}.app-list-header{display:flex;align-items:center;gap:12px}@media(max-width: 768px){.app-list-header{flex-wrap:nowrap;gap:8px}}@media(max-width: 768px){.app-status-badge{display:flex;align-items:center;justify-content:center;width:70px;height:24px;padding:0 10px;border-radius:12px;font-size:12px;font-weight:700;text-align:center;white-space:nowrap;flex-shrink:0}}.app-list-name{font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:18px;font-weight:700;color:#1a1a2e;margin:0}@media(max-width: 768px){.app-list-name{font-size:16px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}}.app-list-description{font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:14px;font-weight:400;color:#6e7780;margin:0;line-height:1.5}@media(max-width: 768px){.app-list-description{font-size:12px;color:#1a1a2e;line-height:1}}.app-create-button-wrapper{display:flex;justify-content:center;margin-top:60px}@media(max-width: 768px){.app-create-button-wrapper{margin-top:30px}}.btn-app-create{background-color:#0049b4;color:#fff}.btn-app-create:hover{background-color:#003a91}@media(max-width: 768px){.btn-app-create{width:144px;height:40px;padding:8px 10px;font-size:14px;border-radius:8px}}.app-management-layout{display:flex;gap:36px;max-width:1200px;margin:40px auto 100px;font-family:"Spoqa Han Sans Neo","Noto Sans CJK KR",sans-serif}@media(max-width: 768px){.app-management-layout{flex-direction:column;gap:20px;margin:20px auto 50px;padding:0 16px}}.app-management-content{flex:1;min-width:0;width:100%}.app-management-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:24px}@media(max-width: 768px){.app-management-header{margin-bottom:16px}.app-management-header h2{font-size:20px !important}}.app-management-header h2{font-size:24px;font-weight:700;color:#000;margin:0}.btn-app-create-figma{background-color:#2a69de;color:#fff;border-radius:7px;padding:0 20px;width:122px;height:35px;display:flex;justify-content:center;align-items:center;font-size:12px;font-weight:700;border:none;cursor:pointer;text-decoration:none;transition:background-color .2s}.btn-app-create-figma:hover{background-color:#1b4ab7}.app-list-container-figma{display:flex;flex-direction:column;gap:20px}.app-card-figma{display:flex;align-items:center;gap:14px;height:107px;padding:20px 31px 20px 17px;background:#fff;border:1px solid #dfdfdf;border-radius:20px;box-shadow:0px 4px 5.45px rgba(192,192,192,.25);text-decoration:none;color:inherit;transition:transform .2s ease,box-shadow .2s ease}@media(max-width: 768px){.app-card-figma{height:auto;padding:16px;border-radius:12px;flex-direction:row;align-items:flex-start}}.app-card-figma:hover{transform:translateY(-2px);box-shadow:0px 6px 15px rgba(192,192,192,.35)}.app-card-icon-box{width:68px;height:67px;background:#f9f9f9;border:1px solid #dee3e7;border-radius:10px;display:flex;justify-content:center;align-items:center;flex-shrink:0}@media(max-width: 768px){.app-card-icon-box{width:56px;height:56px;border-radius:8px}}.app-card-icon-box img,.app-card-icon-box svg{width:46px;height:46px;object-fit:cover;border-radius:4px}@media(max-width: 768px){.app-card-icon-box img,.app-card-icon-box svg{width:36px;height:36px}}.app-card-info{flex:1;display:flex;flex-direction:column;gap:10px;min-width:0}@media(max-width: 768px){.app-card-info{gap:6px}}.app-card-header{display:flex;justify-content:space-between;align-items:center}.app-card-title{font-size:16px;font-weight:500;color:#000;margin:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}@media(max-width: 768px){.app-card-title{font-size:14px}}.app-card-badge-container{display:flex;flex-direction:column;align-items:flex-end;gap:4px}.app-card-badge{height:20px;border-radius:5px;padding:0 8px;font-size:10px;font-weight:500;display:flex;align-items:center;justify-content:center;white-space:nowrap}.app-card-badge.badge-pending{background:#fdf4d4;color:#ef9546}.app-card-badge.badge-requested{background:#ffebeb;color:#f43f5e}.app-card-badge.badge-approved{background:#1b4ab7;color:#fff}.app-card-badge.badge-inactive{background:#f3f4f6;color:#6b7280}.app-card-desc{font-size:14px;color:#64748b;margin:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:1;min-width:0}@media(max-width: 768px){.app-card-desc{font-size:12px}}.app-card-footer-row{display:flex;justify-content:space-between;align-items:center;width:100%;min-width:0}@media(max-width: 768px){.app-card-footer-row{flex-direction:column;align-items:flex-start;gap:4px}}.app-card-expected-date{font-size:14px;color:#64748b;margin-left:16px;flex-shrink:0;white-space:nowrap}@media(max-width: 768px){.app-card-expected-date{margin-left:0;font-size:11px}}.app-card-expected-date{font-size:12px;color:#64748b;text-align:right}.app-list-empty-figma{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:60px 0;background:#fff;border:1px solid #dfdfdf;border-radius:20px;text-align:center}.app-list-empty-figma h3{font-size:18px;color:#000;margin:16px 0 8px}.app-list-empty-figma p{font-size:14px;color:#64748b;margin:0}.notice-page{min-height:calc(100vh - 140px);background:#f8fafc;padding:48px 24px}@media(max-width: 768px){.notice-page{padding:40px 16px}}.notice-container{max-width:1280px;margin:0 auto}.notice-header{margin-bottom:48px}@media(max-width: 768px){.notice-header{margin-bottom:40px}}.notice-header .page-title{font-size:40px;font-weight:700;color:#1a1a2e;margin-bottom:16px;-webkit-background-clip:text;-webkit-text-fill-color:rgba(0,0,0,0);background-clip:text}@media(max-width: 768px){.notice-header .page-title{font-size:32px}}.notice-header .page-description{font-size:16px;color:#64748b;line-height:1.6}@media(max-width: 768px){.notice-header .page-description{font-size:14px}}.notice-list-wrapper{background:#fff;border-radius:12px;box-shadow:0 4px 12px rgba(75,155,255,.1);overflow:hidden;margin-bottom:32px}.notice-detail-container{margin:0 auto;padding:48px 0;min-height:600px}@media(max-width: 1024px){.notice-detail-container{padding:40px 16px}}.notice-detail-header{padding-bottom:24px;border-bottom:1px solid #212529}@media(max-width: 1024px){.notice-detail-header{padding-bottom:16px}}.notice-detail-date{font-size:16px;color:#767e89;font-weight:400;margin:0 0 17px 0}@media(max-width: 1024px){.notice-detail-date{font-size:14px;margin:0 0 12px 0}}.notice-detail-title-wrapper{display:flex;align-items:center;gap:15px}@media(max-width: 1024px){.notice-detail-title-wrapper{align-items:flex-start;gap:8px}}.notice-type-badge{display:inline-flex;align-items:center;justify-content:center;height:20px;width:50px;margin-right:6px;border-radius:5px;font-size:10px;font-weight:500;box-sizing:border-box}.notice-type-badge--incident{border:1px solid #f1c2d6;background:#fbe7e9;color:#bb1026}.notice-type-badge--maintenance{background:#fdf4d4;color:#ef9546;border:1px solid #ffcea1}.notice-type-badge--completed{background:#d6fbd0;color:#11af4c;border:1px solid #a0d4b2}.notice-type-badge--closed{background:#f5f5f5;color:#888;border:1px solid #dedede}.notice-type-badge--pending{background:#fdf4d4;color:#ef9546;border:1px solid #ffcea1}.notice-type-badge--fix{background:#e5f0ff;color:#0a4ea3;border:1px solid #b3d4ff}.notice-type-badge--reviewing{background:#e5f0ff;color:#0a4ea3;border:1px solid #b3d4ff}.notice-type-badge--test{background:#e6fffa;color:#0d9488;border:1px solid #99f6e4}.notice-type-badge--normal{background:#f3f4f6;color:#374151;border:1px solid #e5e7eb}.notice-type-badge.lg{height:28px;width:auto;min-width:50px;padding:0 10px;font-size:13px;font-weight:700}.notice-detail-title{font-size:28px;font-weight:500;color:#000;margin:0}@media(max-width: 1024px){.notice-detail-title{font-size:20px}}.notice-detail-attachment{margin-top:20px;margin-bottom:20px;padding:15px 20px;background:#f8fbfe;border-radius:8px;display:flex;align-items:center}.notice-detail-attachment .attachment-list{display:flex;flex-direction:column;gap:8px}.notice-detail-attachment .attachment-item{display:flex;align-items:center}.notice-attachment-link{display:flex;align-items:center;gap:8px;text-decoration:none;color:#333;transition:all .3s ease}.notice-attachment-link:hover{color:#0049b4}.notice-attachment-link svg{flex-shrink:0;color:#333}.notice-attachment-link:hover svg{color:#0049b4}.notice-attachment-link .attachment-label{font-weight:500;font-size:14px}.notice-attachment-link .attachment-filename{font-size:14px;color:#666;word-break:break-all}.notice-detail-incident-info,.notice-detail-affected-info{margin-bottom:20px}.notice-detail-incident-info .detail-table,.notice-detail-affected-info .detail-table{width:100%;border-collapse:collapse;table-layout:fixed;border-top:none;border-bottom:1px solid #e3e8f0}.notice-detail-incident-info .detail-table tr,.notice-detail-affected-info .detail-table tr{border-bottom:1px solid #e3e8f0}.notice-detail-incident-info .detail-table th,.notice-detail-affected-info .detail-table th{background:#f9f9f9;height:43px;font-weight:500;font-size:14px;text-align:center;color:#000}.notice-detail-incident-info .detail-table td,.notice-detail-affected-info .detail-table td{padding:10px 19px;font-size:14px;color:#000;word-break:break-all}@media(max-width: 768px){.notice-detail-incident-info .detail-table,.notice-detail-affected-info .detail-table{display:block}.notice-detail-incident-info .detail-table colgroup,.notice-detail-affected-info .detail-table colgroup{display:none}.notice-detail-incident-info .detail-table tbody,.notice-detail-incident-info .detail-table tr,.notice-detail-incident-info .detail-table th,.notice-detail-incident-info .detail-table td,.notice-detail-affected-info .detail-table tbody,.notice-detail-affected-info .detail-table tr,.notice-detail-affected-info .detail-table th,.notice-detail-affected-info .detail-table td{display:block;width:100% !important;box-sizing:border-box}.notice-detail-incident-info .detail-table tr,.notice-detail-affected-info .detail-table tr{border-bottom:none}.notice-detail-incident-info .detail-table th,.notice-detail-affected-info .detail-table th{text-align:left;background:#f8fafc;height:auto;padding:10px 16px;border-bottom:1px solid #e2e8f0}.notice-detail-incident-info .detail-table td,.notice-detail-affected-info .detail-table td{padding:12px 16px;border-bottom:1px solid #e2e8f0}}.affected-apis-wrapper{display:flex;flex-direction:column;gap:10px;align-items:flex-start;width:100%}.affected-apis-list{display:flex;flex-direction:column;align-items:flex-start;gap:8px;width:100%}.affected-api-badge{display:inline-flex;align-items:center;padding:6px 16px;background-color:#f3f6fa;border:1px solid #e2e8f0;border-radius:8px;font-size:13px;color:#555;font-weight:500;word-break:break-all;max-width:100%;box-sizing:border-box}.affected-api-badge strong{color:#0a4ea3;font-weight:700;flex-shrink:0}.affected-api-badge.is-gw{background-color:#fff;border-style:dashed;border-color:#cbd5e1;color:#6b7280}.btn-affected-toggle{font-size:13px;color:#fff;font-weight:500;display:inline-flex;align-items:center;gap:6px;cursor:pointer;padding:8px 16px;border-radius:4px;background-color:#0049b4;border:none;transition:all .2s ease;align-self:flex-end;margin-top:8px;line-height:normal}.btn-affected-toggle:hover{background-color:#003685}.btn-affected-toggle .arrow{font-size:10px;display:inline-block;transition:transform .2s ease}.btn-affected-toggle.active .arrow{transform:rotate(180deg)}.notice-detail-content{padding:20px 0 60px 0;border-bottom:1px solid #eee;font-size:16px;line-height:1.6;color:#333;margin-bottom:48px;min-height:300px}.notice-content-body{font-size:16px;color:#333;line-height:1.6;min-height:280px}@media(max-width: 1024px){.notice-content-body{min-height:200px}}.notice-content-body p{margin-bottom:16px}.notice-content-body p:last-child{margin-bottom:0}.notice-content-body img{max-width:100%;height:auto;border-radius:8px;margin:24px 0}.notice-content-body a{color:#0049b4;text-decoration:underline}.notice-content-body a:hover{color:#c3dfea}.notice-content-body ul,.notice-content-body ol{margin:16px 0;padding-left:32px}.notice-content-body li{margin-bottom:8px}.notice-detail-timeline{padding-bottom:40px;border-bottom:1px solid #eee;margin-bottom:48px}.notice-detail-timeline .notice-timeline-title{margin:0 0 16px;font-size:18px;font-weight:700;color:#000}.notice-detail-timeline .timeline-table{width:100%;border-collapse:collapse;table-layout:fixed;border-bottom:1px solid #e3e8f0}.notice-detail-timeline .timeline-table thead th{background:#f9f9f9;height:43px;font-weight:500;font-size:14px;text-align:center;color:#000;border-bottom:1px solid #e3e8f0}.notice-detail-timeline .timeline-table tbody tr{border-bottom:1px solid #e3e8f0}.notice-detail-timeline .timeline-table td{padding:10px 16px;font-size:14px;color:#000;vertical-align:top}.notice-detail-timeline .timeline-table .timeline-ts{color:#555;white-space:nowrap;font-variant-numeric:tabular-nums}.notice-detail-timeline .timeline-table .timeline-body{line-height:1.6;white-space:pre-line;word-break:break-all}@media(max-width: 1024px){.notice-detail-timeline .timeline-table{table-layout:auto}.notice-detail-timeline .timeline-table colgroup{display:none}.notice-detail-timeline .timeline-table thead th,.notice-detail-timeline .timeline-table td{padding:8px 10px;font-size:13px}.notice-detail-timeline .timeline-table .timeline-ts{white-space:normal}}.notice-detail-timeline .timeline-state{display:inline-block;padding:2px 10px;border-radius:999px;font-size:12px;font-weight:500;white-space:nowrap;background:#eef1f5;color:#4b5563}.notice-detail-timeline .timeline-state.state-RESOLVED{background:#e7f6ec;color:#1b8c4a}.notice-detail-timeline .timeline-state.state-MONITORING,.notice-detail-timeline .timeline-state.state-INVESTIGATING{background:#fef3c7;color:#c77800}.notice-detail-timeline .timeline-state.state-IDENTIFIED{background:#fdeee2;color:#c95a0f}.notice-detail-timeline .timeline-state.state-CANCELED{background:#eef1f5;color:#6b7280}.notice-detail-actions{display:flex;justify-content:center;margin-top:40px}.btn-notice-list{width:247px;height:60px}.notice-detail-card{background:#fff;border-radius:12px;box-shadow:0 4px 12px rgba(75,155,255,.1);overflow:hidden;margin-bottom:32px}.notice-detail-card .detail-header{padding:48px 40px 32px;border-bottom:2px solid #e2e8f0}@media(max-width: 768px){.notice-detail-card .detail-header{padding:40px 24px 16px}}.notice-detail-card .detail-header .notice-title{font-size:32px;font-weight:700;color:#1a1a2e;line-height:1.2;margin-bottom:24px}@media(max-width: 768px){.notice-detail-card .detail-header .notice-title{font-size:24px}}.notice-detail-card .detail-header .notice-meta{display:flex;align-items:center;gap:24px;flex-wrap:wrap;font-size:14px;color:#64748b}@media(max-width: 768px){.notice-detail-card .detail-header .notice-meta{font-size:12px;gap:16px}}.notice-detail-card .detail-header .notice-meta .meta-item{display:flex;align-items:center;gap:4px}.notice-detail-card .detail-header .notice-meta .meta-item i{color:#0049b4}.notice-detail-card .detail-header .notice-meta .meta-item strong{color:#1a1a2e;font-weight:500}.notice-detail-card .detail-attachments{padding:24px 40px;background:#f8fafc;border-bottom:1px solid #e2e8f0}@media(max-width: 768px){.notice-detail-card .detail-attachments{padding:16px 24px}}.notice-detail-card .detail-attachments .attachment-list{display:flex;flex-direction:column;gap:8px}.notice-detail-card .detail-attachments .attachment-list .attachment-item{display:flex;align-items:center;gap:8px}.notice-detail-card .detail-attachments .attachment-list .attachment-item a{color:#1a1a2e;text-decoration:none;font-size:14px;transition:all .3s ease;display:flex;align-items:center;gap:4px}@media(max-width: 768px){.notice-detail-card .detail-attachments .attachment-list .attachment-item a{font-size:12px}}.notice-detail-card .detail-attachments .attachment-list .attachment-item a:hover{color:#0049b4}.notice-detail-card .detail-attachments .attachment-list .attachment-item a:hover i{transform:translateY(-2px)}.notice-detail-card .detail-attachments .attachment-list .attachment-item a i{color:#0049b4;transition:all .3s ease}.notice-detail-card .detail-content{padding:48px 40px;min-height:300px}@media(max-width: 768px){.notice-detail-card .detail-content{padding:40px 24px}}.notice-detail-card .detail-content .content-body{font-size:16px;color:#1a1a2e;line-height:1.8}@media(max-width: 768px){.notice-detail-card .detail-content .content-body{font-size:14px}}.notice-detail-card .detail-content .content-body p{margin-bottom:16px}.notice-detail-card .detail-content .content-body p:last-child{margin-bottom:0}.notice-detail-card .detail-content .content-body h1,.notice-detail-card .detail-content .content-body h2,.notice-detail-card .detail-content .content-body h3,.notice-detail-card .detail-content .content-body h4,.notice-detail-card .detail-content .content-body h5,.notice-detail-card .detail-content .content-body h6{margin-top:32px;margin-bottom:16px;font-weight:600;color:#1a1a2e}.notice-detail-card .detail-content .content-body img{max-width:100%;height:auto;border-radius:8px;margin:24px 0}.notice-detail-card .detail-content .content-body a{color:#0049b4;text-decoration:underline}.notice-detail-card .detail-content .content-body a:hover{color:#c3dfea}.notice-detail-card .detail-content .content-body ul,.notice-detail-card .detail-content .content-body ol{margin:16px 0;padding-left:32px}.notice-detail-card .detail-content .content-body li{margin-bottom:8px}.notice-detail-card .detail-content .content-body blockquote{border-left:4px solid #0049b4;padding-left:24px;margin:24px 0;color:#64748b;font-style:italic}.notice-detail-card .detail-content .content-body code{background:#f8fafc;padding:2px 6px;border-radius:6px;font-family:"Fira Code","Courier New",monospace;font-size:.9em;color:#0049b4}.notice-detail-card .detail-content .content-body pre{background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;padding:16px;overflow-x:auto;margin:24px 0}.notice-detail-card .detail-content .content-body pre code{background:rgba(0,0,0,0);padding:0;color:#1a1a2e}.notice-detail-card .detail-content .content-body table{width:100%;border-collapse:collapse;margin:24px 0;border:1px solid #e2e8f0;border-radius:8px;overflow:hidden}.notice-detail-card .detail-content .content-body table th{background:#eff6ff;padding:8px 16px;text-align:left;font-weight:600;border-bottom:2px solid #e2e8f0}.notice-detail-card .detail-content .content-body table td{padding:8px 16px;border-bottom:1px solid #e2e8f0}.notice-detail-card .detail-content .content-body table tr:last-child td{border-bottom:none}.notice-actions{display:flex;justify-content:center;gap:16px;margin-bottom:48px}@media(max-width: 768px){.notice-actions{flex-direction:column;align-items:stretch}}.notice-navigation{background:#fff;border-radius:12px;box-shadow:0 2px 4px rgba(0,0,0,.05);overflow:hidden}.notice-navigation .nav-item{display:flex;align-items:center;justify-content:space-between;padding:24px 40px;border-bottom:1px solid #e2e8f0;transition:all .3s ease;cursor:pointer}@media(max-width: 768px){.notice-navigation .nav-item{flex-direction:column;align-items:flex-start;padding:16px 24px;gap:8px}}.notice-navigation .nav-item:last-child{border-bottom:none}.notice-navigation .nav-item:hover{background:#f8fafc}.notice-navigation .nav-item:hover .nav-title{color:#0049b4}.notice-navigation .nav-item .nav-label{display:flex;align-items:center;gap:8px;font-size:14px;font-weight:500;color:#64748b;min-width:80px}@media(max-width: 768px){.notice-navigation .nav-item .nav-label{font-size:12px}}.notice-navigation .nav-item .nav-label i{color:#0049b4}.notice-navigation .nav-item .nav-title{flex:1;font-size:16px;color:#1a1a2e;transition:all .3s ease}@media(max-width: 768px){.notice-navigation .nav-item .nav-title{font-size:14px}}.notice-navigation .nav-item .nav-date{font-size:14px;color:#94a3b8;min-width:100px;text-align:right}@media(max-width: 768px){.notice-navigation .nav-item .nav-date{font-size:12px;text-align:left}}.inquiry-detail-container{max-width:1228px;margin:0 auto;padding:48px 24px}@media(max-width: 1024px){.inquiry-detail-container{padding:40px 16px}}.inquiry-detail-header{padding-bottom:16px;border-bottom:1px solid #212529;margin-bottom:32px}.inquiry-header-top{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px}@media(max-width: 1024px){.inquiry-header-top{flex-direction:column;align-items:flex-start;gap:8px}}.inquiry-detail-title{font-size:24px;font-weight:700;color:#212529;line-height:1.4;margin:0}@media(max-width: 1024px){.inquiry-detail-title{font-size:20px}}.inquiry-detail-date{font-size:18px;font-weight:400;color:#515151}@media(max-width: 1024px){.inquiry-detail-date{font-size:16px}}.inquiry-detail-attachment{margin-bottom:24px}.inquiry-detail-attachment .attachment-list{display:flex;flex-direction:column;gap:8px}.inquiry-detail-attachment .attachment-item{display:flex;align-items:center}.inquiry-attachment-link{display:inline-flex;align-items:center;gap:4px;color:#515151;font-size:18px;text-decoration:none;transition:all .3s ease}@media(max-width: 1024px){.inquiry-attachment-link{font-size:14px}}.inquiry-attachment-link:hover{color:#0049b4}.inquiry-attachment-link svg{flex-shrink:0;color:#515151}.inquiry-attachment-link:hover svg{color:#0049b4}.inquiry-attachment-link .attachment-label{margin-right:16px}.inquiry-attachment-link .attachment-filename{color:#515151}.inquiry-detail-content{margin-bottom:40px}.inquiry-content-body{border-radius:20px;padding:10px 20px;min-height:200px;font-size:16px;color:#1a1a2e;line-height:1.8;white-space:pre-line}@media(max-width: 1024px){.inquiry-content-body{padding:24px;min-height:150px;border-radius:12px}}@media(max-width: 768px){.inquiry-content-body{background:rgba(0,0,0,0);border-radius:0;padding:0;min-height:auto}}.inquiry-content-body p{margin-bottom:16px}.inquiry-content-body p:last-child{margin-bottom:0}.inquiry-content-body img{max-width:100%;height:auto;border-radius:8px;margin:24px 0}.inquiry-content-body a{color:#0049b4;text-decoration:underline}.inquiry-content-body a:hover{color:#c3dfea}.inquiry-response-section{margin-bottom:40px}.inquiry-response-header{display:flex;justify-content:space-between;align-items:center;padding-bottom:16px;border-bottom:1px solid #dadada;margin-bottom:24px}@media(max-width: 1024px){.inquiry-response-header{flex-direction:column;align-items:flex-start;gap:8px}}.response-label{font-size:20px;font-weight:700;color:#0049b4}@media(max-width: 1024px){.response-label{font-size:18px}}.response-date{font-size:18px;font-weight:400;color:#515151}@media(max-width: 1024px){.response-date{font-size:16px}}.inquiry-response-body{background:#fff;border:1px solid #e5e7eb;border-radius:20px;padding:40px;min-height:150px;font-size:16px;color:#1a1a2e;line-height:1.8;white-space:pre-line}@media(max-width: 1024px){.inquiry-response-body{padding:24px;min-height:100px;border-radius:12px}}.inquiry-detail-actions{display:flex;justify-content:space-between;align-items:center;gap:16px}@media(max-width: 1024px){.inquiry-detail-actions{flex-direction:row;justify-content:center;gap:8px}}.inquiry-detail-actions .actions-left{display:flex;gap:16px}@media(max-width: 1024px){.inquiry-detail-actions .actions-left{gap:8px}}.inquiry-detail-actions .actions-right{display:flex;gap:16px}@media(max-width: 1024px){.inquiry-detail-actions .actions-right{gap:8px}}.btn-inquiry-list{display:flex;align-items:center;justify-content:center;width:200px;height:60px;background:#e5e7eb;border:none;border-radius:12px;font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:18px;font-weight:700;color:#5f666c;cursor:pointer;transition:all .3s ease}@media(max-width: 1024px){.btn-inquiry-list{width:auto;min-width:80px;padding:0 16px;height:44px;font-size:15px;border-radius:8px}}.btn-inquiry-list:hover{background:hsl(220,13.0434782609%,86.431372549%)}.btn-inquiry-list:active{transform:scale(0.98)}.btn-inquiry-edit{display:flex;align-items:center;justify-content:center;width:200px;height:60px;background:#0049b4;border:none;border-radius:12px;font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:18px;font-weight:700;color:#fff;cursor:pointer;transition:all .3s ease}@media(max-width: 1024px){.btn-inquiry-edit{width:auto;min-width:80px;padding:0 16px;height:44px;font-size:15px;border-radius:8px}}.btn-inquiry-edit:hover{background:rgb(0,69.35,171)}.btn-inquiry-edit:active{transform:scale(0.98)}.btn-inquiry-delete{display:flex;align-items:center;justify-content:center;width:200px;height:60px;background:#dc3545;border:none;border-radius:12px;font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:18px;font-weight:700;color:#fff;cursor:pointer;transition:all .3s ease}@media(max-width: 1024px){.btn-inquiry-delete{width:auto;min-width:80px;padding:0 16px;height:44px;font-size:15px;border-radius:8px}}.btn-inquiry-delete:hover{background:rgb(217.9841772152,41.3658227848,58.2873417722)}.btn-inquiry-delete:active{transform:scale(0.98)}.inquiry-form-container{max-width:1228px;margin:0 auto;padding:48px 24px}@media(max-width: 1024px){.inquiry-form-container{padding:40px 16px}}.file-upload-inline{display:flex;align-items:center;gap:20px}@media(max-width: 1024px){.file-upload-inline{flex-direction:column;align-items:stretch;gap:16px}}.file-upload-inline .file-input-display{flex:1;display:flex;align-items:center;justify-content:space-between;height:60px;padding:0 20px;background:#fff;border:1px solid #dadada;border-radius:12px;min-width:0}.file-upload-inline .file-input-display.has-file{border-color:#3ba4ed}.file-upload-inline .file-display-text{flex:1;font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:16px;color:#94a3b8;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.file-upload-inline .file-display-text.has-file{color:#1a1a2e}.file-upload-inline .btn-remove-file-inline{display:flex;align-items:center;justify-content:center;width:24px;height:24px;background:#dc3545;border:none;border-radius:50%;color:#fff;cursor:pointer;transition:all .3s ease;flex-shrink:0;margin-left:8px}.file-upload-inline .btn-remove-file-inline:hover{background:rgb(209.4151898734,36.2848101266,52.8721518987)}.file-upload-inline .btn-remove-file-inline svg{width:12px;height:12px}.file-upload-inline .btn-file-attach{display:inline-flex;align-items:center;justify-content:center;gap:10px;width:172px;height:60px;padding:10px;background:#3ba4ed;border:none;border-radius:12px;font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:18px;font-weight:700;color:#fff;cursor:pointer;transition:all .3s ease;white-space:nowrap;flex-shrink:0}@media(max-width: 1024px){.file-upload-inline .btn-file-attach{width:100%}}.file-upload-inline .btn-file-attach:hover{background:rgb(37.3117757009,153.9304672897,235.0082242991)}.file-upload-inline .btn-file-attach svg{width:22px;height:22px;flex-shrink:0}.file-upload-hint{margin-top:8px;font-size:14px;color:#94a3b8;line-height:1.6}.file-upload-hint strong{color:#1a1a2e}@media(max-width: 768px){.inquiry-form-container{padding:24px 16px}.inquiry-form-container .register-title-bar{background:rgba(0,0,0,0);padding:16px 0;margin-bottom:0}.inquiry-form-container .register-form-container{box-shadow:none !important;border:none !important;background:rgba(0,0,0,0) !important;padding:0 !important}.inquiry-form-container .form-row{padding:16px 0}.inquiry-form-container .form-actions{display:flex;flex-direction:row !important;justify-content:center !important;align-items:center !important;gap:8px !important;padding:32px 0 0;margin-top:16px}.inquiry-form-container .form-actions .btn{width:auto !important;min-width:80px;padding:0 16px;height:44px;font-size:15px;border-radius:8px;flex:0 0 auto}.inquiry-form-container .form-actions .btn-secondary{background:#e5e7eb;color:#5f666c;border:none}.inquiry-form-container .form-actions .btn-secondary:hover{background:hsl(220,13.0434782609%,86.431372549%)}.inquiry-form-container .form-actions .btn-primary{background:#0049b4;color:#fff;border:none}.inquiry-form-container .form-actions .btn-primary:hover{background:rgb(0,69.35,171)}.inquiry-form-container .file-upload-inline .file-input-display{min-height:50px;padding:0 16px}.inquiry-form-container .file-upload-inline .btn-file-attach{height:50px;font-size:15px}}.inquiry-page{min-height:calc(100vh - 140px);background:#f8fafc;padding:48px 24px}@media(max-width: 768px){.inquiry-page{padding:40px 16px}}.inquiry-container{max-width:1280px;margin:0 auto}.inquiry-header{margin-bottom:48px}@media(max-width: 768px){.inquiry-header{margin-bottom:40px}}.inquiry-header .page-title{font-size:40px;font-weight:700;color:#1a1a2e;margin-bottom:16px;-webkit-background-clip:text;-webkit-text-fill-color:rgba(0,0,0,0);background-clip:text}@media(max-width: 768px){.inquiry-header .page-title{font-size:32px}}.inquiry-header .page-description{font-size:16px;color:#64748b;line-height:1.6}@media(max-width: 768px){.inquiry-header .page-description{font-size:14px}}.inquiry-list-wrapper{background:#fff;border-radius:12px;box-shadow:0 4px 12px rgba(75,155,255,.1);overflow:hidden;margin-bottom:32px}.inquiry-table{width:100%;background:#fff;border-radius:12px;overflow:hidden}.inquiry-table .table-header{display:grid;grid-template-columns:80px 1fr 100px 100px 120px;gap:16px;padding:16px 24px;background:#eff6ff;border-bottom:2px solid #e2e8f0;font-size:14px;font-weight:600;color:#1a1a2e}@media(max-width: 1024px){.inquiry-table .table-header{grid-template-columns:60px 1fr 80px 100px;padding:8px 16px;font-size:12px}.inquiry-table .table-header .col-name{display:none}}@media(max-width: 768px){.inquiry-table .table-header{display:none}}.inquiry-table .table-header .col-num{text-align:center}.inquiry-table .table-header .col-title{text-align:left}.inquiry-table .table-header .col-name{text-align:center}.inquiry-table .table-header .col-status{text-align:center}.inquiry-table .table-header .col-date{text-align:center}.inquiry-table .table-row{display:grid;grid-template-columns:80px 1fr 100px 100px 120px;gap:16px;padding:24px;border-bottom:1px solid #e2e8f0;transition:all .3s ease;cursor:pointer}@media(max-width: 1024px){.inquiry-table .table-row{grid-template-columns:60px 1fr 80px 100px;padding:16px}.inquiry-table .table-row .col-name{display:none}}@media(max-width: 768px){.inquiry-table .table-row{grid-template-columns:1fr;gap:8px;padding:16px}}.inquiry-table .table-row:last-child{border-bottom:none}.inquiry-table .table-row:hover{background-color:rgba(0,73,180,.02)}.inquiry-table .table-row .col-num{display:flex;align-items:center;justify-content:center;font-size:14px;color:#64748b;font-weight:500}@media(max-width: 768px){.inquiry-table .table-row .col-num{display:none}}.inquiry-table .table-row .col-title{display:flex;align-items:center;font-size:16px;color:#1a1a2e;font-weight:500}@media(max-width: 768px){.inquiry-table .table-row .col-title{font-size:14px}}.inquiry-table .table-row .col-title a{color:inherit;text-decoration:none;display:flex;align-items:center;gap:8px;transition:all .3s ease}.inquiry-table .table-row .col-title a:hover{color:#0049b4}.inquiry-table .table-row .col-title .file-icon{display:inline-flex;align-items:center;margin-left:8px;opacity:.6}.inquiry-table .table-row .col-title .file-icon img{width:16px;height:16px}.inquiry-table .table-row .col-name{display:flex;align-items:center;justify-content:center;font-size:14px;color:#64748b}@media(max-width: 1024px){.inquiry-table .table-row .col-name{display:none}}.inquiry-table .table-row .col-status{display:flex;align-items:center;justify-content:center}@media(max-width: 768px){.inquiry-table .table-row .col-status{justify-content:flex-start}}.inquiry-table .table-row .col-date{display:flex;align-items:center;justify-content:center;font-size:14px;color:#94a3b8}@media(max-width: 768px){.inquiry-table .table-row .col-date{justify-content:flex-start;font-size:12px;padding-top:4px}}@media(max-width: 768px){.inquiry-table .table-row .col-date::before{content:"등록일: ";color:#64748b;margin-right:4px}}.inquiry-actions{display:flex;justify-content:center;align-items:center;gap:16px;margin-top:40px}@media(max-width: 768px){.inquiry-actions{flex-direction:column;align-items:stretch}}.inquiry-actions .btn-inquiry-new{display:flex;align-items:center;justify-content:center;width:247px;height:60px;background:#2a69de;color:#fff;border:none;font-size:20px;font-weight:700;border-radius:10px;cursor:pointer;text-decoration:none;transition:all .3s ease}@media(max-width: 1024px){.inquiry-actions .btn-inquiry-new{width:100%;height:50px;font-size:16px}}.inquiry-actions .btn-inquiry-new:hover{background:#1d52b5;transform:translateY(-2px)}.inquiry-actions .btn-inquiry-new--outline{background:#fff;color:#2a69de;border:1px solid #2a69de;width:120px;height:50px;font-size:16px}.inquiry-actions .btn-inquiry-new--outline:hover{background:#f8faff}.inquiry-actions .btn-inquiry-new--danger{background:#fff;color:#bb1026;border:1px solid #bb1026;width:120px;height:50px;font-size:16px}.inquiry-actions .btn-inquiry-new--danger:hover{background:#fdf5f6}.inquiry-actions .actions-left,.inquiry-actions .actions-right{display:flex;align-items:center;gap:16px}@media(max-width: 768px){.inquiry-actions .actions-left,.inquiry-actions .actions-right{width:100%;justify-content:center}}@media(max-width: 768px){.inquiry-actions{flex-direction:column;align-items:stretch;gap:16px}.inquiry-actions .actions-left,.inquiry-actions .actions-right{flex-direction:column}}.inquiry-detail-page{min-height:calc(100vh - 140px);background:#f8fafc;padding:48px 24px}@media(max-width: 768px){.inquiry-detail-page{padding:40px 16px}}.inquiry-detail-container{padding:0;background:rgba(0,0,0,0);box-shadow:none;min-height:auto;margin:0}.inquiry-detail-container .notice-detail-header{border-bottom:1px solid #000;padding-bottom:25px}.inquiry-detail-container .notice-detail-header .inquiry-header-meta{display:flex;gap:6px;font-size:16px;color:#767e89;margin-bottom:17px}.inquiry-detail-container .notice-detail-header .notice-detail-title-wrapper{display:flex;align-items:flex-end;gap:15px}@media(max-width: 1024px){.inquiry-detail-container .notice-detail-header .notice-detail-title-wrapper{flex-direction:column;align-items:flex-start;gap:8px}}.inquiry-detail-container .notice-detail-header .notice-detail-title{margin:0;font-size:28px;font-weight:500}@media(max-width: 1024px){.inquiry-detail-container .notice-detail-header .notice-detail-title{font-size:22px}}.inquiry-detail-container .notice-detail-body{padding-top:20px;border-bottom:1px solid #dfdfdf;font-size:14px;line-height:20px;color:#000}.inquiry-detail-container .notice-detail-body img{max-width:100%;height:auto}.inquiry-detail-container .inquiry-response-section{padding-top:40px;padding-bottom:40px;border-bottom:1px solid #dfdfdf}.inquiry-detail-container .inquiry-response-section .inquiry-response-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:0px;border:none}.inquiry-detail-container .inquiry-response-section .inquiry-response-header .response-admin{display:flex;gap:9px;align-items:center}.inquiry-detail-container .inquiry-response-section .inquiry-response-header .response-admin .admin-label{font-size:14px;font-weight:500;color:#000}.inquiry-detail-container .inquiry-response-section .inquiry-response-header .response-admin .admin-badge{background:#ecf1ff;color:#2a69de;font-size:9px;font-weight:700;padding:3px 8px;border-radius:10px}.inquiry-detail-container .inquiry-response-section .inquiry-response-header .response-meta{color:#767e89;font-size:14px}.inquiry-detail-container .inquiry-response-section .inquiry-response-body{background:#f4f5f9;border-radius:10px;padding:24px;font-size:14px;line-height:20px;color:#000}.detail-attachments{padding:24px 40px;background:#f8fafc;border-bottom:1px solid #e2e8f0}@media(max-width: 768px){.detail-attachments{padding:16px 24px}}.detail-attachments .attachment-list{display:flex;flex-direction:column;gap:8px}.detail-attachments .attachment-list .attachment-item{display:flex;align-items:center;gap:8px}.detail-attachments .attachment-list .attachment-item a{color:#1a1a2e;text-decoration:none;font-size:14px;transition:all .3s ease;display:flex;align-items:center;gap:4px}@media(max-width: 768px){.detail-attachments .attachment-list .attachment-item a{font-size:12px}}.detail-attachments .attachment-list .attachment-item a:hover{color:#0049b4}.detail-attachments .attachment-list .attachment-item a:hover i{transform:translateY(-2px)}.detail-attachments .attachment-list .attachment-item a i{color:#0049b4;transition:all .3s ease}.detail-content{padding:48px 40px;min-height:200px}@media(max-width: 768px){.detail-content{padding:40px 24px}}.detail-content .content-body{font-size:16px;color:#1a1a2e;line-height:1.8;white-space:pre-line}@media(max-width: 768px){.detail-content .content-body{font-size:14px}}.inquiry-response-card{background:#fff;border-radius:12px;box-shadow:0 4px 12px rgba(75,155,255,.1);overflow:hidden;margin-bottom:32px;border-left:4px solid #6bcf7f}.inquiry-response-card .response-header{padding:32px 40px;background:rgba(107,207,127,.03);border-bottom:1px solid #e2e8f0}@media(max-width: 768px){.inquiry-response-card .response-header{padding:24px}}.inquiry-response-card .response-header .response-title{display:flex;align-items:center;gap:8px;font-size:20px;font-weight:600;color:#1a1a2e;margin-bottom:8px}.inquiry-response-card .response-header .response-title i{color:#6bcf7f}.inquiry-response-card .response-header .response-meta{font-size:14px;color:#64748b}@media(max-width: 768px){.inquiry-response-card .response-header .response-meta{font-size:12px}}.inquiry-response-card .response-content{padding:40px;font-size:16px;color:#1a1a2e;line-height:1.8;white-space:pre-line}@media(max-width: 768px){.inquiry-response-card .response-content{padding:24px;font-size:14px}}.inquiry-detail-actions{display:flex;justify-content:center;gap:16px;margin-bottom:48px;flex-wrap:wrap}@media(max-width: 768px){.inquiry-detail-actions{flex-direction:row;align-items:stretch}}.inquiry-detail-actions .action-btn{padding:16px 40px;border-radius:8px;font-size:16px;font-weight:500;text-decoration:none;transition:all .3s ease;display:inline-flex;align-items:center;justify-content:center;gap:8px;cursor:pointer;border:none}@media(max-width: 768px){.inquiry-detail-actions .action-btn{padding:16px 24px;font-size:14px}}.inquiry-detail-actions .action-btn.btn-list{background:#fff;color:#1a1a2e;border:1px solid #e2e8f0}.inquiry-detail-actions .action-btn.btn-list:hover{background:#f8fafc;border-color:#0049b4}.inquiry-detail-actions .action-btn.btn-edit{color:#fff}.inquiry-detail-actions .action-btn.btn-edit:hover{transform:translateY(-2px);box-shadow:0 4px 12px rgba(75,155,255,.1)}.inquiry-detail-actions .action-btn.btn-delete{background:#ff6b6b;color:#fff}.inquiry-detail-actions .action-btn.btn-delete:hover{transform:translateY(-2px);box-shadow:0 4px 12px rgba(75,155,255,.1)}.inquiry-detail-actions .action-btn i{font-size:16px}.inquiry-form-page{min-height:calc(100vh - 140px);background:#f8fafc;padding:48px 24px}@media(max-width: 768px){.inquiry-form-page{padding:40px 16px}}.inquiry-form-container{margin:0 auto}.inquiry-form-card{background:#fff;border-radius:12px;box-shadow:0 4px 12px rgba(75,155,255,.1);padding:48px;margin-bottom:32px}@media(max-width: 768px){.inquiry-form-card{padding:40px 24px}}.inquiry-form-card .form-group{margin-bottom:40px}.inquiry-form-card .form-group:last-child{margin-bottom:0}.inquiry-form-card .form-group .form-label{display:block;font-size:16px;font-weight:600;color:#1a1a2e;margin-bottom:16px}@media(max-width: 768px){.inquiry-form-card .form-group .form-label{font-size:14px}}.inquiry-form-card .form-group .form-label.required::after{content:"*";color:#ff6b6b;margin-left:4px}.inquiry-form-card .form-group .form-input{width:100%;padding:16px;border:1px solid #e2e8f0;border-radius:8px;font-size:16px;transition:all .3s ease}@media(max-width: 768px){.inquiry-form-card .form-group .form-input{font-size:14px;padding:8px 16px}}.inquiry-form-card .form-group .form-input:focus{outline:none;border-color:#0049b4;box-shadow:0 0 0 3px rgba(0,73,180,.1)}.inquiry-form-card .form-group .form-input::placeholder{color:#94a3b8}.inquiry-form-card .form-group .form-textarea{width:100%;min-height:200px;padding:16px;border:1px solid #e2e8f0;border-radius:8px;font-size:16px;font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;resize:vertical;transition:all .3s ease}@media(max-width: 768px){.inquiry-form-card .form-group .form-textarea{font-size:14px;padding:8px 16px;min-height:150px}}.inquiry-form-card .form-group .form-textarea:focus{outline:none;border-color:#0049b4;box-shadow:0 0 0 3px rgba(0,73,180,.1)}.inquiry-form-card .form-group .form-textarea::placeholder{color:#94a3b8}.inquiry-form-actions{display:flex;justify-content:center;gap:16px;margin-top:32px}@media(max-width: 768px){.inquiry-form-actions{flex-direction:column-reverse;align-items:stretch}}.djb-board-write-container{padding:0}.djb-board-write-container .djb-board-write-header{margin-bottom:30px;padding-bottom:20px}.djb-board-write-container .djb-board-write-header .title{font-size:24px;font-weight:700;color:#000;margin-bottom:8px}.djb-board-write-container .djb-board-write-header .subtitle{font-size:14px;color:#7f8a95}.djb-board-write-container .djb-board-form .form-group{margin-bottom:30px}.djb-board-write-container .form-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:40px}@media(max-width: 768px){.djb-board-write-container .form-actions{flex-direction:column;align-items:stretch;width:100%;gap:12px}}.djb-board-write-container .form-actions .btn-cancel{width:100px;height:48px;background-color:#fff;border:1px solid #dfdfdf;border-radius:8px;color:#515961;font-size:15px;font-weight:700;cursor:pointer;transition:background-color .2s;display:block}.djb-board-write-container .form-actions .btn-cancel:hover{background-color:#f8f9fa}@media(max-width: 768px){.djb-board-write-container .form-actions .btn-cancel{width:100%;order:2}}.djb-board-write-container .form-actions .btn-submit{width:100px;height:48px;background-color:#2a69de;border:1px solid #2a69de;border-radius:8px;color:#fff;font-size:15px;font-weight:700;cursor:pointer}.djb-board-write-container .form-actions .btn-submit:hover{background-color:hsl(219,73.1707317073%,49.1764705882%)}@media(max-width: 768px){.djb-board-write-container .form-actions .btn-submit{width:100%;order:1;max-width:100%}}.org-register-page{min-height:100vh;padding:0}@media(max-width: 768px){.org-register-page{padding:0}}.org-register-page.corporate-register .org-register-container{padding:0}@media(max-width: 768px){.org-register-page.corporate-register .org-register-container{padding:0 16px}}@media(max-width: 768px){.org-register-container{padding:24px 16px}}.org-register-container .invitation_alert_banner{background-color:#f0f8ff;border:1.5px solid #2196f3;border-radius:12px;padding:24px;margin:20px 0;box-shadow:0 8px 24px rgba(33,150,243,.08);animation:fadeInUp .6s cubic-bezier(0.16, 1, 0.3, 1) both;transform:translateY(15px);opacity:0;margin-bottom:60px}.org-register-container .invitation_alert_banner .banner-content-wrapper{display:flex;align-items:center;gap:16px}.org-register-container .invitation_alert_banner .banner-icon-area{display:flex;align-items:center;justify-content:center}.org-register-container .invitation_alert_banner .banner-icon-area svg{animation:pulseIcon 2s infinite ease-in-out}.org-register-container .invitation_alert_banner .banner-text-area strong{color:#1976d2;font-size:17px;display:block;margin-bottom:4px}.org-register-container .invitation_alert_banner .banner-text-area p{margin:0;color:#475569;font-size:14px;line-height:1.5;display:flex;align-items:center}.org-register-title{font-size:40px;font-weight:700;color:#1a1a2e;text-align:center;margin-bottom:48px}@media(max-width: 768px){.org-register-title{font-size:32px;margin-bottom:32px}}.org-section-header{display:flex;align-items:center;justify-content:space-between;padding:24px 32px;background:#edf9fe;border-radius:8px;margin-bottom:32px}.org-section-header h3{font-size:16px;font-weight:400;color:#515961;margin:0}.org-section-header .required-badge{background:hsla(0,0%,100%,.2);color:#fff;padding:4px 16px;border-radius:50px;font-size:14px;font-weight:500;display:inline-flex;align-items:center;gap:4px}.org-section-header .required-badge::before{content:"*";color:#ffd93d;font-weight:700}.org-section-header--agreement{background:none;padding:0 0 16px 0;border-radius:0;border-bottom:1px solid #818181;margin-bottom:32px}.org-section-header--agreement h3{font-size:22px;font-weight:700;color:#212529;margin:0}.org-section-header--agreement .required-badge{display:none}@media(max-width: 768px){.org-section-header{padding:16px 24px}.org-section-header h3{font-size:18px}.org-section-header--agreement{padding:0 0 8px 0}.org-section-header--agreement h3{font-size:18px}}.org-info-notice{background:#fff5f5;border:1px solid #fed7d7;border-radius:8px;padding:14px 20px;display:flex;align-items:center;gap:12px;margin-top:16px;margin-bottom:32px}.org-info-notice .notice-icon-wrapper{display:flex;align-items:center;justify-content:center;color:#e53e3e;flex-shrink:0}.org-info-notice .notice-text{font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:14px;font-weight:500;color:#c53030;line-height:1.5;margin:0}.org-form-group{margin-bottom:26px;display:flex;flex-direction:column;align-items:stretch;gap:13px}.org-form-group:last-child{margin-bottom:0}.org-form-label{display:flex;align-items:center;gap:8px;font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:16px;font-weight:700;color:#1a202c;width:auto;margin:0;padding:0}.org-form-label .required-badge{visibility:hidden;position:relative;width:10px}.org-form-label .required-badge::after{content:"*";visibility:visible;position:absolute;left:4px;top:0;color:#f4253c;font-size:16px;font-weight:700}.org-form-input-wrapper{flex:1;display:flex;flex-direction:column;gap:8px}@media(max-width: 768px){.org-form-input-wrapper{width:100%}}.org-input-row{display:flex;align-items:center;gap:16px;width:100%;max-width:650px}.org-input-row .org-form-input,.org-input-row .org-compound-input,.org-input-row .org-auth-input-group{flex:1;min-width:0}.org-input-row .org-btn-check,.org-input-row .org-file-label{flex-shrink:0}@media(max-width: 768px){.org-input-row{flex-direction:column;align-items:stretch;gap:8px}.org-input-row .org-form-input,.org-input-row .org-compound-input,.org-input-row .org-auth-input-group{width:100%}.org-input-row .org-btn-check,.org-input-row .org-file-label{width:100%}}.org-form-input{width:100%;padding:10px 16px;border:1px solid #cbd5e0;border-radius:8px;font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:14px;color:#1a202c;transition:all .3s ease;background:#fcfdfd}.org-form-input:focus{outline:none;border-color:#0049b4;box-shadow:0 0 0 3px rgba(0,73,180,.15);background:#fff}.org-form-input:disabled{background:#f8fafc;color:#94a3b8;cursor:not-allowed}.org-form-input.is-invalid{border-color:#ff6b6b}.org-form-input.is-valid{border-color:#6bcf7f}.org-form-input::placeholder{color:#a0aec0}.org-compound-input{display:flex;align-items:center;gap:8px;width:100%}.org-compound-input.phone-input-group{flex:none;width:auto}.org-compound-input .org-form-input,.org-compound-input .org-form-select{min-width:0}.org-compound-input .org-form-input.phone-number{flex:none;width:130px;max-width:130px}.org-compound-input .org-form-select.phone-number{flex:none;width:130px;max-width:130px;padding-right:28px;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%2364748B' d='M6 9L1 4h10z'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 8px center}.org-compound-input .org-form-select.phone-prefix{flex:1;min-width:90px;max-width:130px;padding-right:28px;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%2364748B' d='M6 9L1 4h10z'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 8px center}.org-compound-input .separator{color:#64748b;font-weight:600;user-select:none;flex-shrink:0}@media(max-width: 768px){.org-compound-input{width:100%}.org-compound-input .org-form-input,.org-compound-input .org-form-select{flex:1;min-width:0}}.org-form-select{width:100%;height:48px;padding:0 16px;border:1px solid #e2e8f0;border-radius:10px;font-size:14px;color:#212529;background:#fff;cursor:pointer;transition:all .3s ease;appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%2364748B' d='M6 9L1 4h10z'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 16px center;padding-right:48px}.org-form-select:focus{outline:none;border-color:#3ba4ed;box-shadow:0 0 0 3px rgba(59,164,237,.12)}.org-form-select:disabled{background-color:#f8f9fa;border-color:#e2e8f0;color:#94a3b8;cursor:not-allowed}.org-input-button-group{display:flex;gap:16px}.org-input-button-group .org-form-input{flex:1}@media(max-width: 768px){.org-input-button-group{flex-direction:column}.org-input-button-group .org-form-input{flex:none}}.org-auth-input-group{position:relative;display:flex;align-items:center}.org-auth-input-group .org-form-input{padding-right:100px}.org-timer{position:absolute;right:24px;top:50%;transform:translateY(-50%);font-size:14px;font-weight:600;color:#ff6b6b;pointer-events:none;user-select:none}.org-btn{padding:16px 40px;border-radius:8px;font-size:16px;font-weight:600;cursor:pointer;transition:all .3s ease;border:none;display:inline-flex;align-items:center;justify-content:center;gap:8px;white-space:nowrap}.org-btn:disabled{opacity:.5;cursor:not-allowed}.org-btn:not(:disabled):hover{transform:translateY(-1px);box-shadow:0 4px 12px rgba(75,155,255,.1)}.org-btn:not(:disabled):active{transform:translateY(0)}.org-btn-primary{color:#fff}.org-btn-secondary{background:#fff;color:#64748b;border:2px solid #e2e8f0}.org-btn-secondary:not(:disabled):hover{border-color:#0049b4;color:#0049b4}.org-btn-check,.org-file-label{padding:10px 20px;width:auto;min-width:100px;color:#fff;border-radius:8px;font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:14px;font-weight:700;text-align:center;justify-content:center;background:#3ba4ed;border:1px solid #3ba4ed}.org-btn-check:not(:disabled):hover,.org-file-label:not(:disabled):hover{background:#008bc0;border-color:#008bc0;color:#fff}@media(max-width: 768px){.org-btn-check,.org-file-label{width:100%}}.org-file-upload{display:flex;align-items:center;gap:16px}@media(max-width: 768px){.org-file-upload{flex-direction:column;align-items:stretch}}.org-file-display{flex:1;padding:8px 12px;border:1px dashed #e2e8f0;border-radius:8px;background:#f8fafc;color:#94a3b8;font-size:14px;cursor:default;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.org-file-display.has-file{border-style:solid;border-color:#6bcf7f;background:rgba(107,207,127,.05);color:#1a1a2e}.org-file-label{display:inline-flex;align-items:center;gap:6px;cursor:pointer;background:#3ba4ed;border:1px solid #3ba4ed}.org-file-label svg{width:16px;height:16px;flex-shrink:0}.org-file-label input[type=file]{display:none}.org-file-remove{padding:8px 12px;background:#ff6b6b;color:#fff;border:none;border-radius:8px;font-size:14px;cursor:pointer;transition:all .3s ease}.org-file-remove:hover{background:rgb(255,70.8,70.8)}.org-file-notice{margin-top:16px;padding:16px;border-radius:6px}.org-file-notice p{margin:0;font-size:14px;color:#64748b;line-height:1.5}.org-file-notice p+p{margin-top:4px}.org-file-notice p::before{content:"• ";color:#ffd93d;font-weight:700}.org-action-buttons{display:flex;gap:24px;justify-content:end;margin-top:64px;padding-top:48px}.org-action-buttons .btn{width:220px;text-align:center;justify-content:center}@media(max-width: 768px){.org-action-buttons{flex-direction:row;gap:21px;margin-top:40px;padding-top:32px;border-top:none}.org-action-buttons .btn{flex:1;width:auto;min-width:144px;height:40px;padding:8px 10px;font-size:14px;font-weight:700;border-radius:8px}.org-action-buttons .btn-secondary,.org-action-buttons .btn-outline-secondary{background:#e5e7eb;color:#5f666c;border:none}.org-action-buttons .btn-secondary:hover,.org-action-buttons .btn-outline-secondary:hover{background:#d1d5db;color:#5f666c}.org-action-buttons .btn-primary{background:#0049b4;color:#fff;border:none}.org-action-buttons .btn-primary:hover{background:#003d99;color:#fff}}.org-validation-message{margin-top:8px;padding:8px 16px;border-radius:6px;font-size:14px;display:none}.org-validation-message.success{background:rgba(107,207,127,.1);color:#6bcf7f;display:block}.org-validation-message.error{background:rgba(255,107,107,.1);color:#ff6b6b;display:block}.org-loading-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(26,26,46,.7);display:none;align-items:center;justify-content:center;z-index:9999}.org-loading-overlay.active{display:flex}.org-loading-overlay .spinner{width:50px;height:50px;border:4px solid hsla(0,0%,100%,.2);border-top-color:#fff;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}@media(max-width: 768px){.org-form-input{width:100%}.org-compound-input{width:100%}.org-compound-input .org-form-select,.org-compound-input .org-form-input{flex:1;min-width:0;width:auto;font-size:14px;padding:8px 16px}.org-auth-input-group{width:100%}.org-auth-input-group .org-form-input{width:100%}}.agreement-form{background:#fff;border:1px solid #ddd;border-radius:12px;padding:30px 20px;display:flex;flex-direction:column;gap:20px;margin-bottom:90px;font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}.agreement-all-section{display:flex;align-items:center;padding:10px 0}.agreement-divider{height:1px;background:#ddd;width:100%}.agreement-items-list{display:flex;flex-direction:column;gap:20px}.agreement-item-row{display:flex;align-items:center;justify-content:space-between;gap:4px;height:28px}.agreement-checkbox-label{display:flex;align-items:center;gap:4px;cursor:pointer;flex:1;user-select:none}.agreement-checkbox-input{position:absolute;opacity:0;pointer-events:none}.agreement-checkbox-input:checked+.agreement-checkbox-custom{background-image:url("/img/checkbox-checked.png")}.agreement-checkbox-input:focus+.agreement-checkbox-custom{box-shadow:0 0 0 3px rgba(140,149,159,.1)}.agreement-checkbox-custom{position:relative;width:26px;height:26px;min-width:26px;border:none;border-radius:50%;background-image:url("/img/checkbox-unchecked.png");background-size:contain;background-repeat:no-repeat;background-position:center;transition:all .3s ease;padding:0}.agreement-checkbox-text{font-size:16px;font-weight:400;color:#515961;line-height:100.02%;display:flex;align-items:center;gap:4px}.agreement-checkbox-text.agreement-all-text{font-size:18px;font-weight:700;color:#212529}.agreement-required{color:#0049b4;font-weight:700}.agreement-title{color:#515961}.agreement-toggle-icon{display:flex;align-items:center;justify-content:center;width:22px;height:22px;min-width:22px;border:none;background:rgba(0,0,0,0);color:#64748b;cursor:pointer;transition:all .3s ease;padding:8px 4px 10px 7px}.agreement-toggle-icon i{font-size:12px;transition:transform .3s ease}.agreement-toggle-icon:hover{color:#0049b4}.agreement-toggle-icon.active{color:#0049b4}.agreement-toggle-icon.active i{transform:rotate(180deg)}.agreement-content{margin-top:16px;background:#f9f9f9;border-radius:10px;padding:22px}.agreement-scroll{max-height:400px;overflow-y:auto}.agreement-scroll::-webkit-scrollbar{width:8px}.agreement-scroll::-webkit-scrollbar-track{background:#e2e8f0;border-radius:6px}.agreement-scroll::-webkit-scrollbar-thumb{background:#94a3b8;border-radius:6px}.agreement-scroll::-webkit-scrollbar-thumb:hover{background:#64748b}.agreement-scroll div{font-size:14px;line-height:1.8;color:#64748b}.agreement-scroll div h1,.agreement-scroll div h2,.agreement-scroll div h3,.agreement-scroll div h4,.agreement-scroll div h5,.agreement-scroll div h6{color:#1a1a2e;margin-top:24px;margin-bottom:16px;font-weight:600}.agreement-scroll div p{margin-bottom:16px}.agreement-scroll div ul,.agreement-scroll div ol{margin-left:24px;margin-bottom:16px}.agreement-scroll div table{width:100%;border-collapse:collapse;margin-bottom:16px}.agreement-scroll div table th,.agreement-scroll div table td{border:1px solid #e2e8f0;padding:8px;text-align:left}.agreement-scroll div table th{background:#eff6ff;font-weight:600}@media(max-width: 768px){.agreement-form{padding:20px 16px;gap:16px}.agreement-all-section{padding:8px 0}.agreement-items-list{gap:16px}.agreement-item-row{height:auto;min-height:28px}.agreement-checkbox-text{font-size:14px}.agreement-checkbox-text.agreement-all-text{font-size:16px}.agreement-scroll{max-height:250px;padding:16px}}.register-card-wrapper{background:#fff;border:1px solid rgba(226,232,240,.8);border-radius:20px;padding:48px 40px;box-shadow:0 10px 30px rgba(0,0,0,.03);margin-top:30px;margin-bottom:60px;width:100%}@media(max-width: 768px){.register-card-wrapper{padding:24px 16px;border-radius:12px;margin-top:15px;margin-bottom:40px}}.page-header{margin-bottom:64px;text-align:center}@media(max-width: 768px){.page-header{margin-bottom:48px}}.recent-apps{margin-bottom:70px}.recent-apps .recent-apps-title{font-size:18px;font-weight:700;color:#0f172a;margin-bottom:4px}.recent-apps .recent-apps-list{list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:12px}.recent-apps .recent-apps-item{border:1px solid #e2e8f0;border-radius:8px;overflow:hidden;background:#fff;transition:border-color .2s ease}.recent-apps .recent-apps-item.active{border-color:#2a69de;box-shadow:0 4px 12px rgba(42,105,222,.08)}.recent-apps .recent-apps-item.active .recent-apps-arrow{transform:rotate(180deg)}.recent-apps .recent-apps-head{display:flex;align-items:center;width:100%;padding:8px 20px;background:none;border:none;text-align:left;cursor:pointer;gap:16px}.recent-apps .recent-apps-head:focus{outline:none}.recent-apps .recent-apps-date{font-size:14px;color:#64748b;flex-shrink:0;width:90px}.recent-apps .recent-apps-subject{font-size:15px;font-weight:600;color:#1e293b;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.recent-apps .recent-apps-arrow{color:#64748b;transition:transform .2s ease;flex-shrink:0}.recent-apps .recent-apps-body{display:none;padding:20px;background:#f8fafc;border-top:1px solid #f1f5f9;font-size:14px;line-height:1.6;color:#334155;word-break:break-word}.page-title{font-size:40px;font-weight:700;color:#1a1a2e;margin-bottom:16px;display:flex;align-items:center;justify-content:center;gap:16px}@media(max-width: 768px){.page-title{font-size:32px;flex-direction:column;gap:8px}}.page-title i{color:#0049b4;font-size:40px}@media(max-width: 768px){.page-title i{font-size:32px}}.page-description{font-size:20px;color:#64748b;line-height:1.6}@media(max-width: 768px){.page-description{font-size:16px}}.page-description .highlight{color:#0049b4;font-weight:600;position:relative}.page-description .highlight::after{content:"";position:absolute;bottom:-2px;left:0;right:0;height:2px;border-radius:2px}.partnership-form-container{max-width:800px;margin:0 auto}@media(max-width: 768px){.partnership-form-container{padding:0 16px}}.partnership-card{padding:64px;background:#fff;border-radius:16px;box-shadow:0 8px 24px rgba(75,155,255,.15);border:1px solid #e2e8f0}@media(max-width: 768px){.partnership-card{padding:40px 24px;border-radius:8px}}.partnership-card .form-group{margin-bottom:40px}@media(max-width: 768px){.partnership-card .form-group{margin-bottom:32px}}.partnership-card .form-group label{display:flex;align-items:center;gap:8px;font-size:16px;font-weight:600;color:#1a1a2e;margin-bottom:16px}.partnership-card .form-group label i{color:#0049b4;font-size:18px}.partnership-card .form-group label .required{color:#ff6b6b;margin-left:2px}.partnership-card .form-group textarea.form-control{min-height:200px;resize:vertical;line-height:1.6}@media(max-width: 768px){.partnership-card .form-group textarea.form-control{min-height:150px}}.partnership-card .form-hint{display:flex;align-items:flex-start;gap:4px;margin-top:8px;font-size:14px;color:#64748b;line-height:1.6}.partnership-card .form-hint i{color:#0049b4;margin-top:2px;flex-shrink:0}.partnership-card .form-error{display:flex;align-items:flex-start;gap:4px;margin-top:8px;font-size:14px;color:#ff6b6b;line-height:1.6}.partnership-card .form-error i{margin-top:2px;flex-shrink:0}.partnership-card .form-help-text{margin-top:16px}.partnership-card .form-help-text p{display:flex;align-items:flex-start;gap:4px;margin:4px 0;font-size:12px;color:#94a3b8;line-height:1.6}.partnership-card .form-help-text p i{color:#6bcf7f;margin-top:2px;flex-shrink:0}.partnership-card .file-upload-wrapper .file-name-display.has-file,.service-content .file-upload-wrapper .file-name-display.has-file{color:#1a1a2e;background:rgba(0,73,180,.05);border-color:#0049b4;font-weight:500}.partnership-card .file-upload-wrapper .file-upload-btn,.service-content .file-upload-wrapper .file-upload-btn{margin:0;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;font-weight:700;height:55px;width:120px;border-radius:10px;font-size:14px;background-color:#fff;color:#4685ef;border:1px solid #2a69de;transition:all .2s ease}.partnership-card .file-upload-wrapper .file-upload-btn:hover,.service-content .file-upload-wrapper .file-upload-btn:hover{background-color:#f8faff}.partnership-card .file-upload-wrapper .file-upload-btn:active,.service-content .file-upload-wrapper .file-upload-btn:active{transform:translateY(1px)}@media(max-width: 768px){.partnership-card .file-upload-wrapper .file-upload-btn,.service-content .file-upload-wrapper .file-upload-btn{order:1;width:100%}.partnership-card .file-upload-wrapper .file-name-display,.service-content .file-upload-wrapper .file-name-display{order:2}.partnership-card .file-upload-wrapper .file-remove-btn,.service-content .file-upload-wrapper .file-remove-btn{order:3}}.partnership-card .form-control:focus{border-color:#0049b4;box-shadow:0 0 0 3px rgba(0,73,180,.1);outline:none}.partnership-card textarea.form-control:focus{box-shadow:0 0 0 4px rgba(0,73,180,.15)}@media print{.page-header{margin-bottom:32px}.partnership-card{box-shadow:none;border:1px solid #e2e8f0;padding:32px}.form-actions{display:none}.file-upload-wrapper .file-upload-btn,.file-upload-wrapper .file-remove-btn{display:none}}.user-management-container{padding:48px 0;margin-bottom:40px;min-height:650px}@media(max-width: 768px){.user-management-container{padding:24px 16px}}.user-management-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:40px}@media(max-width: 768px){.user-management-header{flex-direction:column;align-items:flex-start;gap:24px}}.user-management-title h1{font-size:14px;color:#64748b;font-weight:400;margin-bottom:4px}.user-management-title h2{font-size:32px;font-weight:700;color:#1a1a2e}.user-management-actions{display:flex;gap:16px}.btn-create-user-large{padding:16px 40px;font-size:18px;margin-top:24px}.user-management-container .board-table{margin-bottom:40px;overflow-x:auto}.user-management-container .board-table .board-table-row{height:auto;min-height:50px}.user-management-container .board-table .board-table-row .row-cell.row-actions{white-space:normal}.user-management-container .board-table .actions-desktop{flex-wrap:wrap;justify-content:center;gap:6px}.user-management-container .board-table .row-cell a{color:#1a1a2e;text-decoration:none;transition:all .3s ease}.user-management-container .board-table .row-cell a:hover{color:#0049b4}.user-management-container .board-table--user-list .board-table-header,.user-management-container .board-table--user-list .board-table-row{display:grid;grid-template-columns:24px 112px 1fr 100px 100px 287px;padding:0 29px;gap:10px}.user-management-container .board-table--user-list .header-cell,.user-management-container .board-table--user-list .row-cell{padding:0;width:100%;box-sizing:border-box}.user-management-container .board-table--user-list .header-cell:first-child,.user-management-container .board-table--user-list .row-cell:first-child{padding-left:0}.user-management-container .board-table--user-list .header-cell:last-child,.user-management-container .board-table--user-list .row-cell:last-child{padding-right:0}@media(max-width: 768px){.user-management-container .table-controls{background-color:#fff;padding:0}}.user-management-container .table-controls .search-box .btn{height:48px;background-color:#2a69de;border:1px solid #2a69de;border-radius:9px;color:#fff;font-size:15px;font-weight:700;cursor:pointer}.user-current-badge{display:inline-flex;align-items:center;justify-content:center;padding:0 16px;height:28px;background-color:#ecf0fa;color:#2a69de;border:1px solid #4685ef;border-radius:5px;font-size:12px;font-weight:400;font-family:"Spoqa Han Sans Neo",sans-serif;gap:4px}.user-current-badge i{display:none}@media(max-width: 768px){.user-current-badge{display:none}}.user-empty-state{text-align:center;padding:80px 24px;color:#64748b}.user-empty-state .empty-icon{font-size:80px;margin-bottom:24px;opacity:.5}.user-empty-state h3{font-size:24px;font-weight:600;color:#1a1a2e;margin-bottom:16px}.user-empty-state p{font-size:16px;color:#64748b}.user-detail-container{max-width:900px;margin:0 auto;padding:40px}@media(max-width: 768px){.user-detail-container{padding:24px 16px}}.user-profile-card{color:#fff;margin-bottom:32px;position:relative;overflow:hidden}.user-profile-card::before{content:"";position:absolute;top:-50%;right:-20%;width:400px;height:400px;background:hsla(0,0%,100%,.05);border-radius:50%}.user-profile-card .detail-grid{border:none}.user-profile-header{display:flex;align-items:center;gap:32px;position:relative;z-index:1}@media(max-width: 768px){.user-profile-header{flex-direction:column;text-align:center}}.user-profile-avatar{flex-shrink:0}.avatar-circle-large{width:100px;height:100px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:40px;font-weight:700;background:hsla(0,0%,100%,.2);color:#fff;border:4px solid hsla(0,0%,100%,.3);box-shadow:0 8px 24px rgba(75,155,255,.15)}.user-profile-info{flex:1}.user-profile-info h2{font-size:32px;font-weight:700;margin-bottom:8px}.user-profile-info .user-profile-email{font-size:16px;opacity:.9;margin-bottom:16px}.user-profile-badges{display:flex;gap:8px;flex-wrap:wrap}@media(max-width: 768px){.user-profile-badges{justify-content:center}}.user-profile-badges .user-status-badge,.user-profile-badges .user-role-badge{background:hsla(0,0%,100%,.2);color:#fff;backdrop-filter:blur(10px);border:1px solid hsla(0,0%,100%,.3)}.detail-section{background:#fff;border-radius:12px;padding:32px;box-shadow:0 2px 4px rgba(0,0,0,.05);margin-bottom:24px}.detail-section h2{font-size:20px;font-weight:600;color:#1a1a2e;margin-bottom:24px;padding-bottom:8px;border-bottom:2px solid #eff6ff}.detail-grid{display:grid;grid-template-columns:repeat(2, 1fr);gap:24px}@media(max-width: 768px){.detail-grid{grid-template-columns:1fr}}.detail-item{display:flex;flex-direction:column;gap:4px}.detail-item.full-width{grid-column:1/-1}.detail-label{font-size:14px;font-weight:600;color:#64748b;display:flex;align-items:center;gap:4px}.detail-label i{color:#0049b4;font-size:14px}.detail-value{font-size:16px;color:#1a1a2e;padding:8px 0}.status-indicator{display:inline-flex;align-items:center;gap:4px;padding:6px 12px;border-radius:6px;font-size:14px;font-weight:500}.status-indicator.status-active{background-color:rgba(107,207,127,.1);color:hsl(132,51.0204081633%,55.4117647059%)}.status-indicator.status-active .status-dot{background-color:#6bcf7f}.status-indicator.status-inactive{background-color:rgba(100,116,139,.1);color:#64748b}.status-indicator.status-inactive .status-dot{background-color:#64748b}.status-dot{width:8px;height:8px;border-radius:50%;animation:pulse 2s infinite}@keyframes pulse{0%,100%{opacity:1}50%{opacity:.5}}.detail-actions{display:flex;gap:16px;justify-content:center;padding-top:24px}@media(max-width: 768px){.detail-actions{flex-direction:column}}.inquiry-status-badge{display:inline-flex;align-items:center;justify-content:center;height:30px;padding:0 14px;border-radius:50px;font-size:15px;font-weight:700;color:#fff;white-space:nowrap}.inquiry-status-badge--completed{background-color:#0049b4}.inquiry-status-badge--pending{background-color:#8c959f}.inquiry-status-badge--closed{background-color:#4a5560}.inquiry-actions{display:flex;justify-content:center;margin-top:40px}.actions-desktop{display:flex;gap:4px;flex-wrap:nowrap}.actions-mobile{display:none}@media(max-width: 768px){.actions-desktop{display:none !important}.actions-mobile{display:flex !important}.user-management-container{display:flex;flex-direction:column;gap:24px;padding:24px 16px;margin-bottom:0}.user-management-container .table-controls{display:contents}.user-management-container .table-controls .total-count{order:1 !important;display:flex !important;align-items:center !important;width:100% !important;text-align:left !important;font-size:16px !important;color:#000 !important;padding-bottom:6px !important;border-bottom:1.5px solid #212529 !important;margin-bottom:0 !important}.user-management-container .table-controls .total-count strong{color:#0049b4 !important;font-weight:700 !important;font-size:16px !important}.user-management-container .table-controls .search-box{order:2 !important;width:100% !important;margin-top:10px !important}.user-management-container .table-controls .search-box .btn{display:flex !important;align-items:center !important;justify-content:center !important;width:100% !important;height:44px !important;margin-top:0 !important}.user-management-container .board-table--user-list{order:3 !important;background:rgba(0,0,0,0);border-radius:0;overflow:visible}.user-management-container .board-table--user-list .board-table-header{display:none !important}.user-management-container .board-table--user-list .board-table-row{display:flex !important;flex-direction:column !important;align-items:flex-start !important;height:auto !important;padding:20px 15px !important;gap:12px !important;border-bottom:1px solid #e2e8f0 !important;background:#fff;margin-bottom:10px}.user-management-container .board-table--user-list .board-table-row .row-cell{display:flex !important;justify-content:flex-start !important;width:100% !important;font-size:14px !important;color:#212529 !important;padding:0 !important;white-space:normal !important}.user-management-container .board-table--user-list .board-table-row .row-cell::before{content:attr(data-label) !important;display:inline-block !important;font-weight:500 !important;color:#64748b !important;margin-right:15px !important;min-width:70px !important;flex-shrink:0 !important}.user-management-container .board-table--user-list .board-table-row .row-cell[data-label=NO]{display:none !important}.user-management-container .board-table--user-list .board-table-row .row-cell[data-label=성명]{font-size:16px !important;font-weight:600 !important;color:#0f172a !important;margin-bottom:4px}.user-management-container .board-table--user-list .board-table-row .row-cell[data-label=성명]::before{display:none !important}.user-management-container .board-table--user-list .board-table-row .row-cell[data-label=성명] a{color:#0f172a !important;font-weight:600 !important;text-decoration:none}.user-management-container .board-table--user-list .board-table-row .row-cell[data-label=성명] a:hover{color:#2a69de !important}.user-management-container .board-table--user-list .board-table-row .row-cell.row-actions{margin-top:0;padding-top:0 !important;border-top:none;justify-content:flex-end !important}.user-management-container .board-table--user-list .board-table-row .row-cell.row-actions.has-actions{margin-top:8px;padding-top:12px !important;border-top:1px dashed #e2e8f0}.user-management-container .board-table--user-list .board-table-row .row-cell.row-actions::before{display:none !important}.user-management-container .board-table--user-list .board-table-row .row-cell.row-actions .actions-desktop{display:none !important}.user-management-container .board-table--user-list .board-table-row .row-cell.row-actions .actions-mobile{display:flex !important}.user-management-container .user-empty-state{padding:48px 16px}.user-management-container .user-empty-state .empty-icon{font-size:60px}.user-management-container .user-empty-state h3{font-size:20px}.user-management-container .user-empty-state p{font-size:14px}.user-management-container .pagination{order:4 !important;gap:13px;padding:24px 0}.user-management-container .pagination .pagination-btn{width:20px;height:20px}.user-management-container .pagination .pagination-btn i,.user-management-container .pagination .pagination-btn svg{font-size:14px;width:14px;height:14px}.user-management-container .pagination .pagination-numbers{gap:13px}.user-management-container .pagination .pagination-number{min-width:10px;height:21px;font-size:15px;color:#5f666c;font-weight:400}.user-management-container .pagination .pagination-number.active{font-weight:700;color:#212529}.inquiry-list-container .list-table .list-table-header .header-cell:nth-child(1){display:none}.inquiry-list-container .list-table .list-table-header .header-cell:nth-child(2){flex:1 1 0 !important;min-width:0 !important}.inquiry-list-container .list-table .list-table-header .header-cell:nth-child(3){width:60px !important;min-width:60px !important;flex:none !important;justify-content:center}.inquiry-list-container .list-table .list-table-header .header-cell:nth-child(4){width:78px !important;min-width:78px !important;flex:none !important;justify-content:center}.inquiry-list-container .list-table .list-table-header .header-cell:nth-child(5){display:none}.inquiry-list-container .list-table .list-table-row .row-cell:nth-child(1),.inquiry-list-container .list-table .list-table-row .row-cell.row-cell--number{display:none}.inquiry-list-container .list-table .list-table-row .row-cell:nth-child(2),.inquiry-list-container .list-table .list-table-row .row-cell.row-cell--title{flex:1 1 0 !important;min-width:0 !important;overflow:hidden;justify-content:flex-start}.inquiry-list-container .list-table .list-table-row .row-cell:nth-child(2) .notice-title-link,.inquiry-list-container .list-table .list-table-row .row-cell.row-cell--title .notice-title-link{display:flex;align-items:center;gap:4px;overflow:hidden}.inquiry-list-container .list-table .list-table-row .row-cell:nth-child(2) .notice-title-link .notice-number,.inquiry-list-container .list-table .list-table-row .row-cell.row-cell--title .notice-title-link .notice-number{display:none !important}.inquiry-list-container .list-table .list-table-row .row-cell:nth-child(2) .notice-title-link>span:not(.notice-number):not(.file-icon),.inquiry-list-container .list-table .list-table-row .row-cell.row-cell--title .notice-title-link>span:not(.notice-number):not(.file-icon){overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.inquiry-list-container .list-table .list-table-row .row-cell:nth-child(2) .notice-title-link .file-icon,.inquiry-list-container .list-table .list-table-row .row-cell.row-cell--title .notice-title-link .file-icon{flex-shrink:0;width:16px;height:16px}.inquiry-list-container .list-table .list-table-row .row-cell:nth-child(2) .notice-title-link .file-icon svg,.inquiry-list-container .list-table .list-table-row .row-cell.row-cell--title .notice-title-link .file-icon svg{width:16px;height:16px}.inquiry-list-container .list-table .list-table-row .row-cell:nth-child(3){width:60px !important;min-width:60px !important;flex:none !important;justify-content:center;text-align:center;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.inquiry-list-container .list-table .list-table-row .row-cell:nth-child(4){width:78px !important;min-width:78px !important;flex:none !important;justify-content:center;text-align:center}.inquiry-list-container .list-table .list-table-row .row-cell:nth-child(4) .inquiry-status-badge{height:24px;padding:0 8px;font-size:12px}.inquiry-list-container .list-table .list-table-row .row-cell:nth-child(5){display:none}.inquiry-list-container .inquiry-actions{order:4;width:100%;margin-top:0}.inquiry-list-container .inquiry-actions .btn{width:100%;height:44px;display:flex;align-items:center;justify-content:center}.inquiry-list-container .table-controls .search-field{order:2;width:100%}.notice-list-container .list-table .list-table-header .header-cell:nth-child(1){display:none}.notice-list-container .list-table .list-table-header .header-cell:nth-child(2){flex:1 !important;min-width:100px}.notice-list-container .list-table .list-table-header .header-cell:nth-child(3){width:70px !important;min-width:70px;flex:none}.notice-list-container .list-table .list-table-row .row-cell:nth-child(1),.notice-list-container .list-table .list-table-row .row-cell.row-cell--number{display:none}.notice-list-container .list-table .list-table-row .row-cell:nth-child(2),.notice-list-container .list-table .list-table-row .row-cell.row-cell--title{flex:1 !important;min-width:100px;justify-content:flex-start}.notice-list-container .list-table .list-table-row .row-cell:nth-child(2) .notice-title-link,.notice-list-container .list-table .list-table-row .row-cell.row-cell--title .notice-title-link{display:flex;align-items:center;gap:4px;overflow:hidden}.notice-list-container .list-table .list-table-row .row-cell:nth-child(2) .notice-title-link .notice-number,.notice-list-container .list-table .list-table-row .row-cell.row-cell--title .notice-title-link .notice-number{display:none !important}.notice-list-container .list-table .list-table-row .row-cell:nth-child(2) .notice-title-link>span:not(.notice-number):not(.file-icon),.notice-list-container .list-table .list-table-row .row-cell.row-cell--title .notice-title-link>span:not(.notice-number):not(.file-icon){overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.notice-list-container .list-table .list-table-row .row-cell:nth-child(2) .notice-title-link .file-icon,.notice-list-container .list-table .list-table-row .row-cell.row-cell--title .notice-title-link .file-icon{flex-shrink:0;width:16px;height:16px}.notice-list-container .list-table .list-table-row .row-cell:nth-child(2) .notice-title-link .file-icon svg,.notice-list-container .list-table .list-table-row .row-cell.row-cell--title .notice-title-link .file-icon svg{width:16px;height:16px}.notice-list-container .list-table .list-table-row .row-cell:nth-child(3){width:70px !important;min-width:70px;flex:none;justify-content:center;text-align:center}.notice-list-container .table-controls .search-field{order:2;width:100%}.org-register-container{padding:24px 16px}.org-register-container .common-title-bar,.org-register-container .user-management-container .table-controls,.user-management-container .org-register-container .table-controls{margin-bottom:24px}.org-register-container .common-title-bar h3,.org-register-container .user-management-container .table-controls h3,.user-management-container .org-register-container .table-controls h3{font-size:20px}.org-register-container .register-form-container{box-shadow:none !important;border:none !important;background:rgba(0,0,0,0) !important;padding:0 !important}.org-register-container .form-actions{display:flex;flex-direction:row !important;justify-content:center !important;align-items:center !important;padding:32px 0 0;margin-top:0}.org-register-container .form-actions .form-actions-buttons{width:100%;display:flex;justify-content:center}.org-register-container .form-actions .form-actions-buttons .btn{display:flex;align-items:center;justify-content:center;width:100%;height:44px;font-size:15px}}.dropdown-wrapper{position:relative;display:inline-flex;align-items:center;justify-content:center}.dropdown-wrapper .dropdown-toggle{display:flex;align-items:center;justify-content:center;width:32px;height:32px;padding:0;background:rgba(0,0,0,0);border:none;border-radius:6px;color:#5f666c;cursor:pointer;transition:all .3s ease}.dropdown-wrapper .dropdown-toggle:hover{background:rgba(0,73,180,.1);color:#0049b4}.dropdown-wrapper .dropdown-toggle:focus{outline:none;background:rgba(0,73,180,.1)}.dropdown-wrapper .dropdown-toggle svg{width:20px;height:20px}.dropdown-wrapper .dropdown-menu{position:absolute;top:100%;right:0;min-width:140px;background:#fff;border-radius:8px;box-shadow:0 4px 20px rgba(0,0,0,.15);z-index:100;opacity:0;visibility:hidden;transform:translateY(-8px);transition:all .2s ease;overflow:hidden}.dropdown-wrapper .dropdown-menu .dropdown-item{display:block;width:100%;padding:10px 16px;background:rgba(0,0,0,0);border:none;text-align:left;font-size:14px;font-weight:400;color:#212529;cursor:pointer;transition:all .15s ease;white-space:nowrap}.dropdown-wrapper .dropdown-menu .dropdown-item:hover{background:#f0f7ff;color:#0049b4}.dropdown-wrapper .dropdown-menu .dropdown-item:active{background:#e0efff}.dropdown-wrapper .dropdown-menu .dropdown-item+.dropdown-item{border-top:1px solid #eee}.dropdown-wrapper .dropdown-menu .dropdown-item--danger{color:#b02a37}.dropdown-wrapper .dropdown-menu .dropdown-item--danger:hover{background:#fdeaec;color:#b02a37}.dropdown-wrapper .dropdown-menu .dropdown-item--danger:active{background:#f8d7da}.dropdown-wrapper.open .dropdown-toggle{background:rgba(0,73,180,.1);color:#0049b4}.dropdown-wrapper.open .dropdown-menu{opacity:1;visibility:visible;transform:translateY(4px)}.terms-page{min-height:calc(100vh - 140px);background:#f8fafc;padding:48px 24px}@media(max-width: 768px){.terms-page{padding:40px 16px}}.terms-container{max-width:1280px;margin:0 auto;position:relative;padding:20px}@media(max-width: 768px){.terms-container .common-title-bar{display:none}}.terms-header{background:#f6f9fb;border-radius:12px;padding:20px 40px;margin-bottom:32px}@media(max-width: 768px){.terms-header{padding:16px 24px;margin-bottom:24px}}.terms-header .page-title{font-size:28px;font-weight:700;color:#1a1a2e;margin:0;line-height:1}@media(max-width: 768px){.terms-header .page-title{font-size:24px}}.terms-header .page-description{font-size:16px;color:#64748b;line-height:1.6;margin-top:8px}@media(max-width: 768px){.terms-header .page-description{font-size:14px}}.terms-content-card{background:#fff;border-radius:12px;box-shadow:0 4px 12px rgba(75,155,255,.1);overflow:hidden}.terms-tabs{display:flex;gap:0;background:rgba(0,0,0,0);padding:0;border-bottom:none}@media(max-width: 768px){.terms-tabs{padding:0;flex-wrap:nowrap;overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:none}.terms-tabs::-webkit-scrollbar{display:none}}.terms-tabs .tab-link{flex:1;display:flex;align-items:center;justify-content:center;gap:8px;padding:18px 24px;font-size:22px;font-weight:700;color:#8c959f;text-decoration:none;background:#eceff4;transition:all .3s ease;text-align:center;position:relative;border-radius:12px 12px 0 0}@media(max-width: 768px){.terms-tabs .tab-link{flex:1 0 auto;white-space:nowrap;padding:8px 16px;font-size:14px;gap:4px}}.terms-tabs .tab-link i{display:none}.terms-tabs .tab-link:hover{color:#0049b4;background:#e4e8ed}.terms-tabs .tab-link.active{color:#fff;background:#3ba4ed;font-weight:700}.terms-empty{padding:32px 24px;text-align:center;color:#8c959f;font-size:16px}.terms-selector{padding:24px;background:#f6f9fb;border-bottom:none}@media(max-width: 768px){.terms-selector{padding:16px 16px}}.terms-selector .terms-select{width:340px;height:44px;padding:9px 22px 9px 20px;background:#fff;border:1px solid #dadada;border-radius:12px;font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:14px;font-weight:400;color:#212529;cursor:pointer;transition:all .3s ease;appearance:none;-webkit-appearance:none;-moz-appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='5' viewBox='0 0 10 5'%3E%3Cpath fill='%23515961' d='M5 5L0 0h10z'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 16px center;background-size:10px 5px}@media(max-width: 768px){.terms-selector .terms-select{width:100%;height:40px;padding:8px 36px 8px 16px;font-size:12px}}.terms-selector .terms-select:hover{border-color:#0049b4}.terms-selector .terms-select:focus{outline:none;border-color:#0049b4}.terms-selector .terms-select option{padding:8px 16px;font-size:14px;color:#212529}.terms-content{padding:0 32px}.terms-content .terms-attachment{margin-bottom:16px}.terms-content .terms-attachment .attachment-link{display:inline-flex;align-items:center;gap:8px;padding:10px 20px;background:#fff;border:1px solid #0049b4;border-radius:6px;color:#0049b4;font-size:14px;font-weight:500;text-decoration:none;transition:all .3s ease}.terms-content .terms-attachment .attachment-link:hover{background:#0049b4;color:#fff}.terms-content .terms-attachment .attachment-link svg{flex-shrink:0}.terms-content{min-height:400px;background-color:#f6f9fb}@media(max-width: 768px){.terms-content{padding:24px 24px;min-height:300px}}.terms-content .content-body{padding-top:24px;padding-bottom:24px;font-size:16px;color:#1a1a2e;line-height:1.8;word-wrap:break-word}@media(max-width: 768px){.terms-content .content-body{padding-top:16px;padding-bottom:16px;font-size:14px}}.terms-content .content-body p{margin-bottom:16px}.terms-content .content-body p:last-child{margin-bottom:0}.terms-content .content-body h1,.terms-content .content-body .chapter-title{font-size:20px;font-weight:700;color:#1a1a2e;margin-top:32px;margin-bottom:16px;line-height:1.4}.terms-content .content-body h1:first-child,.terms-content .content-body .chapter-title:first-child{margin-top:0}@media(max-width: 768px){.terms-content .content-body h1,.terms-content .content-body .chapter-title{font-size:18px}}.terms-content .content-body h2,.terms-content .content-body .article-title{font-size:18px;font-weight:700;color:#1a1a2e;margin-top:24px;margin-bottom:8px;line-height:1.4}@media(max-width: 768px){.terms-content .content-body h2,.terms-content .content-body .article-title{font-size:16px}}.terms-content .content-body h3{font-size:20px;font-weight:600;color:#1a1a2e;margin-top:16px;margin-bottom:8px}@media(max-width: 768px){.terms-content .content-body h3{font-size:16px}}.terms-content .content-body strong{font-weight:700;color:#1a1a2e}.terms-content .content-body em{font-style:italic;color:#64748b}.terms-content .content-body a{color:#0049b4;text-decoration:underline;transition:all .3s ease}.terms-content .content-body a:hover{color:#c3dfea}.terms-content .content-body ul,.terms-content .content-body ol{margin:16px 0;padding-left:0;list-style:none}@media(max-width: 768px){.terms-content .content-body ul,.terms-content .content-body ol{padding-left:0}}.terms-content .content-body li{margin-bottom:4px;line-height:1.8;padding-left:0}.terms-content .content-body li:last-child{margin-bottom:0}.terms-content .content-body blockquote{border-left:4px solid #0049b4;padding-left:24px;margin:24px 0;color:#64748b;font-style:italic;background:#f8fafc;padding:16px 24px;border-radius:0 8px 8px 0}@media(max-width: 768px){.terms-content .content-body blockquote{padding:8px 16px}}.terms-content .content-body code{background:#f8fafc;padding:2px 6px;border-radius:6px;font-family:"Fira Code","Courier New",monospace;font-size:.9em;color:#0049b4}.terms-content .content-body pre{background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;padding:16px;overflow-x:auto;margin:24px 0}@media(max-width: 768px){.terms-content .content-body pre{padding:8px}}.terms-content .content-body pre code{background:rgba(0,0,0,0);padding:0;color:#1a1a2e}.terms-content .content-body table{width:100%;border-collapse:collapse;margin:24px 0;border:1px solid #e2e8f0;border-radius:8px;overflow:hidden;font-size:14px}@media(max-width: 768px){.terms-content .content-body table{font-size:12px}}.terms-content .content-body table th{background:#eff6ff;padding:8px 16px;text-align:left;font-weight:600;color:#1a1a2e;border-bottom:2px solid #e2e8f0}@media(max-width: 768px){.terms-content .content-body table th{padding:4px 8px}}.terms-content .content-body table td{padding:8px 16px;border-bottom:1px solid #e2e8f0;color:#1a1a2e}@media(max-width: 768px){.terms-content .content-body table td{padding:4px 8px}}.terms-content .content-body table tr:last-child td{border-bottom:none}.terms-content .content-body table tr:hover{background:#f8fafc}.terms-content .content-body hr{border:none;border-top:1px solid #e2e8f0;margin:40px 0}.terms-content .content-body .article{margin-top:40px;padding-top:24px;border-top:1px solid #e2e8f0}.terms-content .content-body .article:first-child{margin-top:0;padding-top:0;border-top:none}.terms-content .content-body .section-number{display:inline-block;min-width:30px;color:#0049b4;font-weight:700}.terms-selector .select-items::-webkit-scrollbar{width:6px}.terms-selector .select-items::-webkit-scrollbar-track{background:#f8fafc;border-radius:6px}.terms-selector .select-items::-webkit-scrollbar-thumb{background:#0049b4;border-radius:6px}.terms-selector .select-items::-webkit-scrollbar-thumb:hover{background:#c3dfea}.service-intro{max-width:1200px;margin:0 auto;padding:20px;background-color:#fff;color:#000;display:flex;flex-direction:column;gap:20px}.service-intro__title{margin:0;font-size:25px;line-height:1.4;letter-spacing:-0.01em}.service-intro__lead{margin:0;font-size:20px;font-weight:700;line-height:1.5;letter-spacing:-0.01em}.service-intro__desc{margin:0;font-size:15px;font-weight:500;line-height:1.7}.service-intro__spacer{height:10px}.service-intro__section-title{margin:0;font-size:20px;line-height:1.5}.service-intro__section-body{font-size:15px;font-weight:500;line-height:1.7}.service-intro__section-body p{margin:0}.service-intro__section-body p+p{margin-top:8px}.service-intro__list{margin:0;padding-left:22px;list-style:disc;font-size:15px;font-weight:500;line-height:1.7}.service-intro__list li+li{margin-top:4px}@media(max-width: 768px){.service-intro{padding:16px;gap:16px}.service-intro__title{font-size:22px}.service-intro__lead{font-size:17px}.service-intro__section-title{font-size:17px}.service-intro__desc,.service-intro__section-body,.service-intro__list{font-size:14px}}.service-main{padding:60px 0;display:flex;gap:60px;align-items:flex-start}.service-content{flex:1;width:100%;display:flex;flex-direction:column;gap:40px;min-width:0}.service-content.n-gap{gap:0}.service-sidebar{width:213px;flex-shrink:0}.service-nav{display:flex;flex-direction:column;gap:26px;background:#fff;border:1px solid #e3e8f0;border-radius:20px;padding:28px 30px}.service-nav:hover{box-shadow:0 4px 4px rgba(173,173,173,.1);transition:transform .3s ease,box-shadow .3s ease}.service-nav .service-sidebar__profile{display:flex;flex-direction:column;align-items:center;gap:16px}.service-nav .service-sidebar__profile .avatar{width:53px;height:52px;background-color:#9ac8ee;border-radius:11px;display:flex;align-items:center;justify-content:center}.service-nav .service-sidebar__profile .avatar svg{width:36px;height:36px;fill:#fff}.service-nav .service-sidebar__profile .user-name{font-size:16px;font-weight:500;color:#000;text-align:center;margin:0}.service-nav .service-sidebar__divider{height:1px;background-color:#e3e8f0;margin:4px 0;width:100%}.service-nav__item{font-size:16px;font-weight:500;color:#0d0e11;text-decoration:none;transition:color .2s}.service-nav__item:hover{color:#2a69de}.service-nav__item--active{color:#2a69de;font-weight:700}@media(max-width: 1024px){.service-main{flex-direction:column;padding:40px 20px}.service-sidebar{width:100%;overflow:hidden}.service-nav{flex-direction:row !important;flex-wrap:nowrap !important;justify-content:flex-start !important;padding:16px 20px !important;overflow-x:auto !important;-webkit-overflow-scrolling:touch !important;gap:24px !important;border:1px solid #e3e8f0 !important;border-radius:20px !important;box-shadow:none !important;background:#fff !important}.service-nav::-webkit-scrollbar{display:none}.service-nav{-ms-overflow-style:none;scrollbar-width:none}.service-nav .service-sidebar__profile,.service-nav .service-sidebar__divider{display:none !important}.service-nav .service-nav__item{flex-shrink:0 !important;white-space:nowrap !important;margin:0 !important;padding:0 !important}.service-hero,.oauth2-2legged__hero{width:100vw}}.service-hero{background:linear-gradient(to right, #eff9fe, #fcfeff);border:1px solid #9cc2ff;padding:60px 0;display:flex;justify-content:center;width:calc(100vw - 80px);position:relative;left:50%;transform:translateX(-50%);margin-top:40px}.service-hero__inner{max-width:1240px;width:100%;display:flex;align-items:center;gap:138px;padding:0 37px}.service-hero__icon-wrapper{width:230px;height:167px;position:relative;display:flex;align-items:center;justify-content:center;font-size:14px;color:#adb5bd}.service-hero__content{display:flex;flex-direction:column;align-items:flex-start;gap:15px}.service-hero__badge{display:inline-flex;align-items:center;gap:8px;padding:8px 20px;background:#fff;border:1px solid #d1e6f0;border-radius:38px}.service-hero__badge-dot{width:8px;height:8px;background:var(--primary-color);border-radius:50%}.service-hero__badge-text{font-size:13px;font-weight:700;color:#1b4ab7}.service-hero__title{font-size:36px;font-weight:700;color:#000;margin:0}@media(max-width: 768px){.service-hero__title{font-size:29px}}.service-hero__desc{font-size:16px;font-weight:500;color:#6678ad;line-height:1.5;margin:0}.service-keyImg{width:100%;height:100%;object-fit:contain}@media(max-width: 768px){.service-keyImg{width:90%;height:90%}}.signup-guide-v2{background-color:#fff;padding-bottom:100px}.signup-guide-v2 .signup-info-box{background:#1d2a5e;border:1px solid hsla(0,0%,100%,.08);border-left:5px solid #2a69de;border-radius:16px;padding:28px 32px;display:flex;align-items:center;gap:28px;animation:fadeInUp .6s cubic-bezier(0.16, 1, 0.3, 1) both}.signup-guide-v2 .signup-info-box__icon{width:56px;height:56px;background:#48507b;border:1px solid hsla(0,0%,100%,.1);border-radius:14px;display:flex;align-items:center;justify-content:center;flex-shrink:0}.signup-guide-v2 .signup-info-box__icon img{width:28px;height:28px;filter:brightness(0) invert(1);object-fit:contain}.signup-guide-v2 .signup-info-box ul{margin:0;padding-left:0;list-style:none}.signup-guide-v2 .signup-info-box ul li{position:relative;padding-left:20px;color:#94a3b8;font-size:15px;font-weight:500;line-height:1.8;word-break:keep-all}.signup-guide-v2 .signup-info-box ul li strong{color:#fff;font-weight:700}.signup-guide-v2 .signup-info-box ul li::before{content:"";position:absolute;left:4px;top:10px;width:5px;height:5px;background:#38bdf8;border-radius:50%}.signup-guide-v2 .signup-info-box ul li+li{margin-top:6px}.signup-tabs{display:flex;justify-content:flex-start;border-bottom:1px solid #e3e8f0;margin-top:40px;margin-bottom:50px;width:100%}.signup-tabs .signup-tab-btn{flex:1;max-width:50%;padding:16px 20px;font-size:16px;font-weight:500;color:#94a3b8;background:none;border:none;border-bottom:2px solid rgba(0,0,0,0);cursor:pointer;text-align:center;transition:all .2s ease;outline:none}.signup-tabs .signup-tab-btn:hover{color:#1e293b}.signup-tabs .signup-tab-btn.active{color:#2a69de;font-weight:700;border-bottom:2px solid #2a69de;background:#fff}.signup-timeline-container{margin-bottom:50px;min-height:300px}.signup-timeline{display:none;flex-direction:column;gap:40px;position:relative;padding-left:80px}.signup-timeline.active{display:flex}.signup-timeline::before{content:"";position:absolute;left:17px;top:47px;bottom:47px;width:2px;background:#e6eefb;z-index:0}.signup-step{display:flex;align-items:center;gap:40px;position:relative;z-index:1}.signup-step::before{content:"";position:absolute;left:-71px;top:50%;transform:translateY(-50%);width:16px;height:16px;background:#2a69de;border-radius:50%;box-shadow:0 0 0 6px rgba(42,105,222,.15),0 0 10px 6px rgba(42,105,222,.1);z-index:2}.signup-step__icon-box{width:95px;min-height:95px;background:#fff;border:1px solid #e3e8f0;border-radius:20px;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;box-shadow:0 4px 4px rgba(173,173,173,.1)}.signup-step__icon-box svg{width:30px;height:30px}.signup-step__card{flex:1;background:#fff;border:1px solid #e3e8f0;border-radius:20px;padding:20px 30px;box-shadow:0 4px 4px rgba(173,173,173,.1);display:flex;flex-direction:column;justify-content:center;min-height:95px}.signup-step__card-header{display:flex;align-items:center;margin-bottom:6px}.signup-step__card-num{font-size:14px;font-weight:700;color:#1b4ab7}.signup-step__card-dot{width:4px;height:4px;background:#1b4ab7;border-radius:50%;margin:0 8px}.signup-step__card-title{font-size:18px;font-weight:700;color:#000;font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}.signup-step__card-desc{margin:0;font-size:14px;color:#64748b;line-height:1.5}.signup-step .one{color:#2c4a8c}.signup-step .two{color:#1f7a5c}.signup-step .three{color:#f98e24}.signup-step .four{color:#2480f9}.signup-step .five{color:#ff5c5c}.signup-step .six{color:#5b4aee}.signup-action{display:flex;justify-content:center;margin-top:20px}.signup-roles-tbd{margin-top:40px}.signup-roles-tbd__title{font-size:20px;font-weight:700;color:#140064;margin:0 0 16px}.signup-roles-tbd__placeholder{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;padding:48px 24px;background:#f8f9fa;border:1px dashed #e3e8f0;border-radius:12px;text-align:center}.signup-roles-tbd__badge{display:inline-block;padding:4px 12px;font-size:12px;font-weight:700;letter-spacing:.04em;color:#0049b4;background:#eff9fe;border-radius:20px}.signup-roles-tbd__desc{margin:0;font-size:15px;color:#6e7781}.signup-roles-table-section{margin-top:50px;margin-bottom:20px}.signup-roles-table-section__title{font-size:20px;font-weight:700;color:#1e293b;margin:0 0 20px;position:relative;padding-left:12px;margin-bottom:30px}.signup-roles-table-section__title::before{content:"";position:absolute;left:0;top:4px;bottom:4px;width:4px;background:#0049b4;border-radius:2px}.signup-roles-table-section .signup-roles-table-wrapper{width:100%;overflow-x:auto;border:1px solid #e2e8f0;border-radius:12px;background:#fff;box-shadow:0 4px 20px rgba(0,0,0,.02)}.signup-roles-table-section .signup-roles-table-wrapper::-webkit-scrollbar{height:6px}.signup-roles-table-section .signup-roles-table-wrapper::-webkit-scrollbar-thumb{background:#cbd5e1;border-radius:3px}.signup-roles-table-section .signup-roles-table{width:100%;border-collapse:collapse;font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;text-align:center}.signup-roles-table-section .signup-roles-table th,.signup-roles-table-section .signup-roles-table td{padding:14px 16px;font-size:14px;border-bottom:1px solid #e2e8f0;border-right:1px solid #e2e8f0}.signup-roles-table-section .signup-roles-table th:last-child,.signup-roles-table-section .signup-roles-table td:last-child{border-right:none}.signup-roles-table-section .signup-roles-table thead{background:#f8fafc}.signup-roles-table-section .signup-roles-table thead th{font-weight:700;color:#334155;font-size:13px}.signup-roles-table-section .signup-roles-table thead th.col-feature{width:35%;font-size:14px;color:#1e293b}.signup-roles-table-section .signup-roles-table thead th.col-role{width:15%}.signup-roles-table-section .signup-roles-table thead th.col-role-group{background:#f1f5f9;color:#0f172a}.signup-roles-table-section .signup-roles-table thead th.col-sub-role{width:17.5%;background:#f8fafc}.signup-roles-table-section .signup-roles-table tbody tr{transition:background .2s ease}.signup-roles-table-section .signup-roles-table tbody tr:hover{background:#f8fafc}.signup-roles-table-section .signup-roles-table tbody tr:last-child td{border-bottom:none}.signup-roles-table-section .signup-roles-table tbody .cell-feature{font-weight:600;color:#475569;text-align:left;padding-left:20px}.signup-roles-table-section .signup-roles-table tbody .status-badge{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:50%;font-size:14px;font-weight:700}.signup-roles-table-section .signup-roles-table tbody .status-badge--allowed{background:#e0f2fe;color:#0369a1}.signup-roles-table-section .signup-roles-table tbody .status-badge--denied{background:#f1f5f9;color:#94a3b8}@media(max-width: 1024px){.service-hero{padding:40px 0}.service-hero__inner{flex-direction:column;gap:30px;text-align:center}.service-hero__content{align-items:center}.signup-guide-v2 .signup-info-box{flex-direction:column;padding:20px;gap:15px;text-align:center}.signup-guide-v2 .signup-info-box ul li{text-align:left}}@media(max-width: 768px){.signup-guide-v2 .signup-timeline{padding-left:0}.signup-guide-v2 .signup-timeline::before{display:none}.signup-guide-v2 .signup-step{gap:15px}.signup-guide-v2 .signup-step::before{display:none}.signup-guide-v2 .signup-step__icon-box{width:60px;height:60px;min-height:60px}.signup-guide-v2 .signup-step__icon-box svg{width:24px;height:24px}.signup-guide-v2 .signup-step__card{height:auto;min-height:60px;padding:12px 18px}.signup-guide-v2 .signup-step__card-title{font-size:15px}}.service_guide_header{padding:56px 16px 32px;text-align:center;background:linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%)}.service_guide .title{font-size:28px;font-weight:700;line-height:36px;letter-spacing:-0.02em;color:#140064;margin-bottom:16px}.service_guide .detail{font-size:14px;font-weight:400;line-height:22px;color:#495057}.service_guide_image{padding:32px 16px;text-align:center;background:#fff}.service_guide_image img{max-width:100%;height:auto;border-radius:8px;box-shadow:0 4px 12px rgba(0,0,0,.1)}.service_guide_info{padding:24px 16px;text-align:center}.service_guide_info .info_text{font-size:14px;line-height:24px;color:#212529;margin-bottom:12px}.service_guide_info .info_text strong{color:#140064;font-weight:600}.service_guide_info .info_notice{font-size:13px;color:#0049b4;font-weight:500}.service_member_table{padding:16px}.service_member_table table{width:100%;border-collapse:collapse;border:1px solid #dee2e6;border-radius:8px;overflow:hidden}.service_member_table table th,.service_member_table table td{padding:16px;font-size:14px;line-height:22px;border-bottom:1px solid #dee2e6}.service_member_table table th{background:#f8f9fa;color:#140064;font-weight:600;text-align:center;vertical-align:middle}.service_member_table table td{color:#495057;text-align:left}.service_member_table table tr:last-child th,.service_member_table table tr:last-child td{border-bottom:none}.service_guide_notice{padding:16px;text-align:center}.service_guide_notice .notice_text{font-size:13px;color:#0049b4;font-weight:500;line-height:20px}.service_guide_action{padding:32px 16px 56px;text-align:center}.service_guide_action .btn_register{display:inline-block;padding:16px 48px;background:#140064;color:#fff;font-size:16px;font-weight:600;border-radius:8px;text-decoration:none;transition:background .2s ease}.service_guide_action .btn_register:hover{background:#0049b4}@media(min-width: 768px){.service_guide_header{padding:80px 32px 48px}.service_guide_header .service_guide .title{font-size:40px;line-height:48px}.service_guide_header .service_guide .detail{font-size:18px;line-height:28px}.service_guide_image{padding:48px 32px}.service_guide_image img{max-width:800px}.service_guide_info{padding:32px}.service_guide_info .info_text{font-size:16px;line-height:28px}.service_guide_info .info_notice{font-size:14px}.service_member_table{padding:32px;max-width:800px;margin:0 auto}.service_member_table table th,.service_member_table table td{padding:20px 24px;font-size:15px}.service_guide_notice{padding:24px 32px}.service_guide_notice .notice_text{font-size:14px}.service_guide_action{padding:48px 32px 80px}.service_guide_action .btn_register{padding:18px 64px;font-size:18px}}.oauth-main{max-width:1240px;margin:0 auto;padding:60px 40px;display:flex;gap:60px;align-items:flex-start}.oauth2-2legged{box-sizing:border-box;color:#1a1a2e}.oauth2-2legged *,.oauth2-2legged *::before,.oauth2-2legged *::after{box-sizing:inherit}.oauth2-2legged__prereq,.oauth2-2legged__sequence,.oauth2-2legged__step,.oauth2-2legged__errors,.oauth2-2legged__error-example{margin-bottom:50px}.oauth2-2legged__eyebrow{display:inline-block;font-size:14px;font-weight:700;color:#0049b4;letter-spacing:1.2px;margin-bottom:8px}.oauth2-2legged__h2{margin:0 0 20px;font-size:26px;font-weight:700;line-height:1.3;color:#1a1a2e}.oauth2-2legged__desc{margin:0 0 16px;font-size:14px;font-weight:500;color:#64748b}.oauth2-2legged__hero{background:linear-gradient(to right, #eff9fe, #fcfeff);border:1px solid #9cc2ff;padding:60px 0;display:flex;justify-content:center;width:calc(100vw - 80px);position:relative;left:50%;transform:translateX(-50%);margin-top:40px}.oauth2-2legged__hero-inner{max-width:1240px;width:100%;display:flex;align-items:center;gap:138px;padding:0 37px}.oauth2-2legged__hero-body{display:flex;flex-direction:column;align-items:flex-start;gap:15px}.oauth2-2legged__hero-eyebrow{display:inline-flex;align-items:center;gap:8px;padding:8px 20px;background:#fff;border:1px solid #d1e6f0;border-radius:38px;font-size:13px;font-weight:700;color:#1b4ab7}.oauth2-2legged__hero-eyebrow-dot{width:8px;height:8px;border-radius:50%;background:var(--primary-color)}.oauth2-2legged__hero-title{font-size:36px;font-weight:800;color:#000;margin:0}.oauth2-2legged__hero-lead{font-size:16px;font-weight:500;color:#6678ad;line-height:1.5;margin:0}.oauth2-2legged__hero-lead+.oauth2-2legged__hero-lead{margin-top:4px}@media(max-width: 768px){.oauth2-2legged__hero-lead{font-size:13px}}.oauth2-2legged__hero-chips{display:flex;gap:12px;flex-wrap:wrap}.oauth2-2legged__chip{display:inline-flex;align-items:center;padding:6px 18px;border-radius:15px;background:#fff;border:1px solid #e2e8f0;font-size:13px;font-weight:600;color:#1a1a2e}.oauth2-2legged__chip--primary{background:#0049b4;border-color:#0049b4;color:#fff}.oauth2-2legged__hero-illust{width:230px;height:167px;position:relative;background:radial-gradient(circle at 50% 120%, rgba(0, 73, 180, 0.1), transparent 70%);display:flex;align-items:center;justify-content:center;flex-shrink:0}.oauth2-2legged__hero-illust svg{display:block;width:100%;height:100%}.oauth2-2legged__prereq-grid{display:grid;grid-template-columns:1fr;gap:16px}.oauth2-2legged__prereq-card{display:grid;grid-template-columns:44px 1fr;gap:12px;align-items:start;padding:24px;background:#fff;border:1px solid #e2e8f0;border-radius:14px;word-break:keep-all;overflow-wrap:break-word}.oauth2-2legged__prereq-num{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;border-radius:50%;background:#0049b4;color:#fff;font-size:16px;font-weight:800}.oauth2-2legged__prereq-title{margin:4px 0 8px;font-size:16px;font-weight:700;color:#1a1a2e;font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}.oauth2-2legged__prereq-body p{margin:0;font-size:13px;font-weight:500;color:#64748b;line-height:1.6}.oauth2-2legged__prereq-body p+p{margin-top:2px}.oauth2-2legged__sequence-diagram{padding:16px;background:#fff;border:1px solid #e2e8f0;border-radius:14px;overflow-x:auto;-webkit-overflow-scrolling:touch}.oauth2-2legged__sequence-diagram svg{display:block;width:100%;min-width:580px;height:auto}.oauth2-2legged__step-grid{display:flex;flex-direction:column;gap:24px}.oauth2-2legged__step-grid+.oauth2-2legged__step-grid{margin-top:24px}.oauth2-2legged__endpoint-box{display:grid;grid-template-columns:1fr;gap:8px;padding:16px 24px;margin-bottom:20px;background:#fff;border:1px solid #e2e8f0;border-radius:12px}.oauth2-2legged__endpoint-box .oauth2-2legged__method,.oauth2-2legged__endpoint-box .oauth2-2legged__endpoint-content-type{justify-self:start}.oauth2-2legged__method{display:inline-block;padding:5px 14px;border-radius:6px;background:#0049b4;color:#fff;font-size:13px;font-weight:800;letter-spacing:.5px}.oauth2-2legged__endpoint-path{font-family:"Fira Code",monospace;font-size:16px;color:#1a1a2e;word-break:break-all}.oauth2-2legged__endpoint-content-type{display:inline-block;padding:5px 14px;border-radius:14px;background:#f8fafc;border:1px solid #e2e8f0;color:#64748b;font-size:11px;font-weight:700}.oauth2-2legged__panel{padding:24px 28px;background:#fff;border:1px solid #e2e8f0;border-radius:12px;overflow-x:auto;-webkit-overflow-scrolling:touch;min-width:0}.oauth2-2legged__panel-title{margin:0 0 14px;font-size:14px;font-weight:700;color:#1a1a2e}.oauth2-2legged__panel-subtitle{margin:20px 0 10px;font-size:13px;font-weight:700;color:#1a1a2e}.oauth2-2legged__table{width:100%;min-width:600px;border-collapse:collapse;font-size:13px}.oauth2-2legged__table thead{background:#f8fafc}.oauth2-2legged__table th{text-align:left;padding:10px 4px;font-size:12px;font-weight:700;color:#64748b;letter-spacing:.4px}.oauth2-2legged__table td{padding:12px 4px;border-bottom:1px solid #e2e8f0;color:#64748b;vertical-align:middle}.oauth2-2legged__table tbody tr:last-child td{border-bottom:none}.oauth2-2legged__table code{font-family:"Fira Code",monospace;font-size:13px;font-weight:700;color:#1a1a2e}.oauth2-2legged__table--errors td:first-child{width:64px}.oauth2-2legged__table--errors td:nth-child(2){width:180px}.oauth2-2legged__table--errors td:nth-child(3){width:280px}.oauth2-2legged__table--errors td{color:#64748b}.oauth2-2legged__field-name{color:#0049b4 !important}.oauth2-2legged__req-badge{display:inline-block;min-width:32px;padding:2px 10px;border-radius:9px;text-align:center;font-size:10px;font-weight:700;color:#fff}.oauth2-2legged__req-badge--required{background:#0049b4}.oauth2-2legged__req-badge--optional{background:#94a3b8}.oauth2-2legged__warning{margin:16px 0 0;padding:6px 14px;border-radius:6px;background:#fff9e6;color:#a07a00;font-size:12px;font-weight:600}.oauth2-2legged__code-panel{position:relative;padding:24px 28px;border-radius:12px;background:#1a1a2e;overflow:hidden;min-width:0;margin-top:24px}.oauth2-2legged__code-panel::before{content:"";position:absolute;inset:0 auto 0 0;width:6px;background:#0049b4}.oauth2-2legged__code-panel--error::before{background:#ff6b6b}.oauth2-2legged__code-tag{display:inline-block;padding:4px 14px;border-radius:11px;background:#0049b4;color:#fff;font-size:11px;font-weight:700;margin-bottom:16px}.oauth2-2legged__code-tag--ok{background:#0049b4}.oauth2-2legged__code-tag--error{background:#ff6b6b}.oauth2-2legged__code-block{margin:0;color:#fff;font-family:"Fira Code",monospace;font-size:13px;line-height:1.7;white-space:pre;overflow-x:auto;display:block}.oauth2-2legged .o2leg-c{color:#00d4ff}.oauth2-2legged .o2leg-y{color:#ffd93d}.oauth2-2legged .o2leg-g{color:#6bcf7f}.oauth2-2legged .o2leg-p{color:#a78bfa}.oauth2-2legged .o2leg-cm{color:#94a3b8}.oauth2-2legged__header-box{padding:14px 16px;border-radius:8px;background:#f8fafc;margin-bottom:6px;display:flex;flex-wrap:wrap;gap:8px 12px;align-items:center}.oauth2-2legged__header-key{display:block;font-family:"Fira Code",monospace;font-size:13px;font-weight:700;color:#0049b4;margin-bottom:4px}.oauth2-2legged__header-value{display:block;font-family:"Fira Code",monospace;font-size:13px;color:#1a1a2e}.oauth2-2legged__tips{list-style:none;margin:0;padding:0}.oauth2-2legged__tips li{position:relative;padding-left:18px;font-size:13px;font-weight:500;color:#64748b;line-height:1.6}.oauth2-2legged__tips li::before{content:"";position:absolute;left:4px;top:9px;width:6px;height:6px;border-radius:50%;background:#0049b4}.oauth2-2legged__tips li+li{margin-top:4px}.oauth2-2legged__status-badge{display:inline-block;min-width:40px;padding:2px 12px;border-radius:11px;text-align:center;font-size:12px;font-weight:700}.oauth2-2legged__status-badge--400{background:#ffe9e9;color:#ff6b6b}.oauth2-2legged__status-badge--403{background:#fff9e6;color:#a07a00}.oauth2-2legged__error-note{margin:14px 0 0;padding:6px 14px;border-radius:6px;background:#ffe9e9;color:#a23b3b;font-size:12px;font-weight:600}.oauth2-2legged__cta{position:relative;display:block;padding:36px 48px;border-radius:20px;background:linear-gradient(90deg, #0049b4 0%, #1f6fd0 100%);color:#fff;text-decoration:none;overflow:hidden;transition:transform .2s ease,box-shadow .2s ease}.oauth2-2legged__cta:hover{transform:translateY(-2px);box-shadow:0 12px 28px rgba(0,73,180,.25)}.oauth2-2legged__cta-body{position:relative;z-index:1}.oauth2-2legged__cta-eyebrow{display:inline-block;font-size:13px;font-weight:700;color:#c3dfea;letter-spacing:1.2px;margin-bottom:12px}.oauth2-2legged__cta-title{margin:0 0 10px;font-size:26px;font-weight:800;color:#fff;line-height:1.3}.oauth2-2legged__cta-desc{margin:0 0 18px;font-size:14px;font-weight:500;color:#c3dfea}.oauth2-2legged__cta-button{display:inline-flex;align-items:center;padding:10px 24px;border-radius:19px;background:#fff;color:#0049b4;font-size:14px;font-weight:700}.oauth2-2legged__cta-deco{position:absolute;border-radius:50%;background:hsla(0,0%,100%,.08)}.oauth2-2legged__cta-deco--lg{width:120px;height:120px;right:80px;bottom:30px}.oauth2-2legged__cta-deco--sm{width:80px;height:80px;right:20px;bottom:-10px;background:hsla(0,0%,100%,.1)}@media(max-width: 1024px){.oauth2-2legged__hero-inner{flex-direction:column;gap:30px;text-align:center}.oauth2-2legged__hero-body{align-items:center}.oauth2-2legged__prereq-grid{grid-template-columns:1fr}.oauth2-2legged__step-grid{grid-template-columns:1fr}.oauth2-2legged__endpoint-box{grid-template-columns:1fr;gap:8px}.oauth2-2legged__endpoint-box .oauth2-2legged__method,.oauth2-2legged__endpoint-box .oauth2-2legged__endpoint-content-type{justify-self:start}.oauth2-2legged__table--errors td:first-child,.oauth2-2legged__table--errors td:nth-child(2),.oauth2-2legged__table--errors td:nth-child(3){width:auto}}@media(max-width: 640px){.oauth2-2legged{gap:16px}.oauth2-2legged__hero{padding:40px 0}.oauth2-2legged__hero-title{font-size:28px}.oauth2-2legged__h2{font-size:22px}.oauth2-2legged__cta{padding:28px 24px}.oauth2-2legged__cta-title{font-size:22px}.oauth2-2legged__panel{padding:18px 20px}.oauth2-2legged__code-panel{padding:18px 20px}}.api-statistics-container{margin:0 auto}.statistics-notice{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-bottom:16px;padding:12px 18px;background:#eef4fc;border:1px solid #d6e4f5;border-radius:10px;font-size:13px;color:#4a5568}.statistics-notice-example{flex-basis:100%;color:#6b7280;font-size:12px}.statistics-notice-latest{color:#0049b4;font-weight:600}.statistics-search-section{display:flex;align-items:flex-end;justify-content:space-between;gap:12px;margin-bottom:30px;margin-top:50px}.search-mode-tabs{display:inline-flex;background:#eef1f5;border-radius:22px;padding:4px;width:150px}.mode-tab{border:none;background:rgba(0,0,0,0);padding:7px 22px;border-radius:18px;font-size:14px;color:#666;cursor:pointer;transition:all .2s}.mode-tab.active{background:#0049b4;color:#fff;font-weight:600}.month-range-picker{display:flex;align-items:center;gap:8px}.month-input{border:none;padding:8px 12px;font-size:14px;color:#333;background:rgba(0,0,0,0);outline:none;cursor:pointer}.search-filter-row{display:flex;align-items:center;gap:10px;background:#fff;padding:8px 16px;border-radius:30px;border:1px solid #e0e0e0}.date-range-picker{display:flex;align-items:center;gap:8px}.date-input{border:none;padding:8px 12px;font-size:14px;color:#333;background:rgba(0,0,0,0);outline:none}.date-separator{color:#666}.btn-search{background:#0049b4;border:none;border-radius:50%;width:36px;height:36px;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:background .2s}.btn-search:hover{background:#003d99}.btn-search svg{stroke:#fff}.statistics-summary-card{background:#f5f8fc;border-radius:16px;padding:40px;margin-bottom:30px}.summary-content{display:flex;align-items:center;justify-content:space-between;gap:40px}.summary-left{flex:1}.app-select-wrapper{margin-bottom:24px}.app-select{padding:12px 40px 12px 20px;border-radius:25px;border:1px solid #0049b4;background:#fff;color:#0049b4;font-size:14px;font-weight:500;cursor:pointer;appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%230049B4' d='M6 8L1 3h10z'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 16px center;min-width:150px;outline:none;transition:all .2s}.app-select:hover{background-color:#f0f5ff}.app-select:focus{border-color:#003d99;box-shadow:0 0 0 3px rgba(0,73,180,.1)}.summary-info{padding-left:10px}.summary-total-text{font-size:18px;color:#333;margin-bottom:16px}.summary-total-text strong{font-size:24px;color:#0049b4}.summary-detail-list{display:flex;flex-direction:column;gap:12px}.summary-detail-item{display:flex;align-items:center;gap:8px;font-size:14px}.summary-detail-item.success .detail-icon{color:#4a90d9}.summary-detail-item.timeout .detail-icon{color:#f5c36b}.summary-detail-item.failure .detail-icon{color:#f5a3b5}.detail-label{color:#666;min-width:30px}.detail-value{font-weight:600;color:#333}.detail-percent{color:#999}.summary-center{flex:0 0 200px;display:flex;align-items:center;justify-content:center}#donutChart{max-width:200px;max-height:200px}.donut-segment{transition:stroke-dasharray .3s ease,stroke-dashoffset .3s ease}.summary-right{flex:0 0 230px;display:flex;flex-direction:column;gap:20px}.rate-display{display:flex;align-items:center;gap:8px;padding:16px 0;border-bottom:1px solid #e0e0e0}.rate-display:last-child{border-bottom:none}.rate-indicator{width:12px;height:12px;border-radius:50%}.rate-indicator.success{background:#4a90d9}.rate-indicator.timeout{background:#f5c36b}.rate-indicator.failure{background:#f5a3b5}.rate-label{font-size:14px;color:#666}.rate-value{font-size:32px;font-weight:700}.success-rate .rate-value{color:#4a90d9}.timeout-rate .rate-value{color:#f5c36b}.failure-rate .rate-value{color:#f5a3b5}.rate-percent{font-size:18px;color:#999}.statistics-table-card{background:#fff;border-radius:16px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,.08);margin-bottom:30px}.statistics-table-card:last-child{margin-bottom:0}.table-header{display:flex;justify-content:space-between;align-items:center;padding:20px 24px;background:#0049b4;color:#fff}.table-title{display:flex;align-items:center;gap:10px;font-size:18px;font-weight:600;margin:0;margin-bottom:10px}.table-title svg{stroke:#1e293b}.btn-download{display:flex;align-items:center;gap:6px;padding:8px 16px;background:hsla(0,0%,100%,.2);border-radius:20px;color:#fff;text-decoration:none;font-size:14px;transition:background .2s}.btn-download:hover{background:hsla(0,0%,100%,.3);color:#fff}.table-container{padding:0}.board-table{width:100%;border-top:1px solid #c4c7c8;background:#fff}.board-table .board-table-header{display:flex;align-items:center;background:#f9f9f9;height:50px;padding:0 45px;border-bottom:1px solid #dedede}@media(max-width: 768px){.board-table .board-table-header{display:none}}.board-table .board-table-header .header-cell{font-size:14px;font-family:"Spoqa Han Sans Neo",sans-serif;font-weight:500;color:#000;text-align:center}.board-table .board-table-body{display:flex;flex-direction:column}.board-table .board-table-row{display:flex;align-items:center;height:52px;padding:0 45px;border-bottom:1px solid #dedede;transition:background-color .2s;background:#fff}.board-table .board-table-row:hover{background-color:#f8fbfe}@media(max-width: 768px){.board-table .board-table-row{flex-direction:column;align-items:flex-start;height:auto;padding:15px;gap:10px}}.board-table .board-table-row .row-cell{display:flex;align-items:center;justify-content:center;font-size:14px;font-family:"Spoqa Han Sans Neo",sans-serif;font-weight:400;color:#000;white-space:nowrap}@media(max-width: 768px){.board-table .board-table-row .row-cell{justify-content:flex-start;width:100% !important}.board-table .board-table-row .row-cell::before{content:attr(data-label);font-weight:500;color:#64748b;margin-right:10px;min-width:80px}}.board-table .board-table-row.total-row{background:#f8fbfe;font-weight:600}.board-table .board-table-row.total-row .row-cell{color:#0049b4}.api-name{display:flex;align-items:center;gap:10px}.api-icon{display:flex;align-items:center;justify-content:center;width:28px;height:28px;background:#f0f0f0;border-radius:6px}.api-icon svg{stroke:#666}.api-icon.total{background:#e3f2fd}.api-icon.total svg{stroke:#0049b4}.api-name-text{display:inline-block;vertical-align:middle;max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}@media(max-width: 768px){.api-name-text{max-width:320px}}.col-name{flex:2;justify-content:flex-start !important;padding-left:10px;min-width:0}.board-table-header .col-name{justify-content:center !important;padding-left:0}.col-num{width:100px;justify-content:center !important}.col-chart{width:180px;justify-content:center !important}.stats-progress{width:150px;height:8px;background:#f1f5f9;border-radius:4px;display:flex;overflow:hidden}.stats-empty{display:flex;align-items:center;justify-content:center;height:100px;width:100%;color:#64748b;font-weight:400;font-size:16px}.progress-cell{min-width:200px}@media(max-width: 992px){.summary-content{flex-direction:column;gap:30px;align-items:center}.summary-left,.summary-center,.summary-right{flex:none;width:100%;align-items:center;justify-content:center;text-align:center}.summary-info{padding-left:0}.app-select-wrapper{display:flex;justify-content:center}.summary-detail-list{align-items:center}.summary-center{order:-1}.summary-right{flex-direction:row;flex-wrap:wrap;justify-content:center;gap:20px}.rate-display{border-bottom:none;padding:0}}@media(max-width: 768px){.statistics-summary-card{padding:24px 20px}.statistics-search-section{flex-direction:column;align-items:stretch;gap:16px}.search-filter-row{flex-direction:row;justify-content:space-between;width:100%}.date-range-picker{flex:1;justify-content:space-between}.date-input{width:45%;font-size:13px;padding:6px 8px}.board-table-header{display:none !important}.board-table-row{flex-direction:column;align-items:stretch;height:auto;padding:10px 14px;gap:6px;border-bottom:1px solid #e2e8f0}.board-table-row .row-cell{display:flex !important;justify-content:space-between !important;align-items:center;width:100% !important;text-align:right;font-size:13px;padding:0 !important}.board-table-row .row-cell::before{content:attr(data-label);font-weight:500;color:#64748b;text-align:left;min-width:80px}.board-table-row .row-cell.col-name{font-weight:600;justify-content:flex-start !important;text-align:left;border-bottom:none;padding-bottom:0 !important;margin-bottom:4px;display:flex !important;gap:10px}.board-table-row .row-cell.col-name::before{display:none}.stats-progress{width:100px}}.webhook-container{max-width:880px;margin:0 auto;padding:24px 16px 60px;font-family:"Spoqa Han Sans Neo","Noto Sans CJK KR",sans-serif}.webhook-container .field-help{color:#64748b;font-size:13px;margin-top:6px}.webhook-container .field-help-red{color:#f4253c;font-size:13px;margin-top:6px;font-weight:500}@media(max-width: 768px){.webhook-container .field-help-red{font-size:11px}}.webhook-container .field-error,.webhook-container .webhook-field-error{color:#f4253c;font-size:13px;margin-top:6px}.webhook-container .req{color:#f4253c}.webhook-empty{text-align:center;padding:60px 20px;background:#fff;border:1px solid #dfdfdf;border-radius:20px;box-shadow:0px 4px 5.45px rgba(192,192,192,.25)}.webhook-empty .empty-icon{display:inline-flex;justify-content:center;align-items:center;width:60px;height:60px;background:#f8fafc;border-radius:50%;margin-bottom:16px;color:#cbd5e1;font-size:25px}.webhook-empty h3{margin:0 0 8px;font-size:18px;font-weight:700;color:#000}.webhook-empty p{color:#64748b;font-size:14px;margin:0 0 24px}.webhook-steps{display:flex;align-items:center;justify-content:center;gap:30px;margin-bottom:24px;background:#fff;border-radius:20px;box-shadow:0 4px 13.45px rgba(192,192,192,.25);height:160px;padding:0 40px}@media(max-width: 768px){.webhook-steps{height:120px;gap:16px;padding:0 20px}}.webhook-steps .webhook-step{display:flex;flex-direction:column;align-items:center;gap:8px;color:#94a3b8;position:relative;flex:1}.webhook-steps .webhook-step .step-no{width:55px;height:55px;border-radius:50%;background:#f0f4fa;border:1.5px solid #dde4ee;display:flex;align-items:center;justify-content:center;color:#b0bec5;font-size:16px;font-weight:700;transition:all .2s ease}@media(max-width: 768px){.webhook-steps .webhook-step .step-no{width:44px;height:44px;font-size:14px}}.webhook-steps .webhook-step .step-name{font-size:14px;font-weight:500;color:#94a3b8}@media(max-width: 768px){.webhook-steps .webhook-step .step-name{font-size:12px}}.webhook-steps .webhook-step.active{color:#2a69de}.webhook-steps .webhook-step.active .step-no{background:#2a69de;border-color:#2a69de;color:#fff}.webhook-steps .webhook-step.active .step-name{color:#0d0e11;font-weight:600}.webhook-field{margin-bottom:24px}.webhook-field label{display:block;font-size:15px;font-weight:700;color:#0d0e11;margin-bottom:10px}.webhook-field input[type=text],.webhook-field input[type=password]{width:100%;height:46px;padding:0 16px;border:1.5px solid #dfdfdf;border-radius:8px;font-size:14px;color:#0d0e11;background:#fff;transition:border-color .2s}.webhook-field input[type=text]:focus,.webhook-field input[type=password]:focus{outline:none;border-color:#2a69de}.webhook-field input[type=text]::placeholder,.webhook-field input[type=password]::placeholder{color:#94a3b8}.webhook-eventtype-list{display:grid;grid-template-columns:repeat(2, 1fr);gap:12px}@media(max-width: 576px){.webhook-eventtype-list{grid-template-columns:1fr}}.webhook-eventtype-list .eventtype-item{display:flex;align-items:center;gap:12px;padding:14px 18px;border:1.5px solid #dfdfdf;border-radius:8px;background:#fff;cursor:pointer;transition:all .2s}.webhook-eventtype-list .eventtype-item input[type=checkbox]{width:18px;height:18px;border:1.5px solid #cbd5e1;border-radius:4px;accent-color:#2a69de}.webhook-eventtype-list .eventtype-item .eventtype-name{font-size:14px;font-weight:600;color:#0d0e11}.webhook-eventtype-list .eventtype-item .eventtype-code{color:#64748b;font-size:12px;margin-left:auto}.webhook-eventtype-list .eventtype-item:hover{border-color:#2a69de;background:#f8fafc}.webhook-api-groups{margin:16px 0 8px}.webhook-api-group{margin-bottom:24px}.webhook-api-group .group-name{font-size:16px;font-weight:700;color:#0d0e11;margin-bottom:12px}.webhook-api-list{display:grid;grid-template-columns:repeat(auto-fill, minmax(220px, 1fr));gap:10px}.webhook-api-list .webhook-api-card{display:flex;align-items:center;gap:10px;padding:12px 16px;border:1.5px solid #dfdfdf;border-radius:8px;background:#fff;cursor:pointer;transition:all .2s}.webhook-api-list .webhook-api-card input[type=checkbox]{width:18px;height:18px;accent-color:#2a69de}.webhook-api-list .webhook-api-card .api-name{font-size:14px;font-weight:600;color:#0d0e11}.webhook-api-list .webhook-api-card .api-id{color:#64748b;font-size:12px;margin-left:auto}.webhook-api-list .webhook-api-card:hover{border-color:#2a69de;background:#f8fafc}.webhook-actions{display:flex;justify-content:space-between;gap:12px;margin-top:32px}.webhook-actions.center{justify-content:center}.btn-webhook-next,.btn-webhook-create{display:inline-flex;align-items:center;justify-content:center;width:144px;height:40px;background:#2a69de;color:#fff;border:none;border-radius:8px;font-size:14px;font-weight:700;cursor:pointer;text-decoration:none;transition:background-color .2s}.btn-webhook-next:hover,.btn-webhook-create:hover{background-color:#1b4ab7}.btn-webhook-cancel{display:inline-flex;align-items:center;justify-content:center;width:144px;height:40px;background:#fff;color:#5f666c;border:1px solid #dfdfdf;border-radius:8px;font-size:14px;font-weight:700;cursor:pointer;text-decoration:none;transition:all .2s}.btn-webhook-cancel:hover{background:#f8fafc;border-color:#cbd5e1}.btn-webhook-danger{display:inline-flex;align-items:center;justify-content:center;width:144px;height:40px;background:#fff;color:#f4253c;border:1px solid #f4253c;border-radius:8px;font-size:14px;font-weight:700;cursor:pointer;transition:all .2s}.btn-webhook-danger:hover{background:#f4253c;color:#fff}.btn-mini{display:inline-flex;align-items:center;justify-content:center;padding:0 14px;height:28px;background:#f1f5f9;color:#334155;border:1px solid #cbd5e1;border-radius:6px;font-size:11px;font-weight:600;cursor:pointer;transition:all .2s}.btn-mini:hover{background:#e2e8f0;color:#0f172a}.btn-copy{display:inline-flex;align-items:center;justify-content:center;padding:0 20px;height:46px;background:#2a69de;color:#fff;border:none;border-radius:8px;font-size:14px;font-weight:700;cursor:pointer;transition:background-color .2s}.btn-copy:hover{background-color:#1b4ab7}.webhook-card{border:1px solid #dfdfdf;border-radius:20px;padding:28px 32px;background:#fff;box-shadow:0px 4px 13.45px rgba(192,192,192,.25)}@media(max-width: 768px){.webhook-card{padding:20px}}.webhook-card .webhook-card-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:24px}.webhook-card .webhook-card-head h3{font-size:18px;font-weight:700;color:#000;margin:0}.webhook-card .webhook-card-head .webhook-created{color:#64748b;font-size:13px}.webhook-card .webhook-card-row{display:flex;gap:16px;padding:16px 0;border-top:1px solid #f1f5f9;align-items:center}@media(max-width: 576px){.webhook-card .webhook-card-row{flex-direction:column;align-items:flex-start;gap:6px;padding:14px 0}}.webhook-card .webhook-card-row .row-label{width:120px;color:#64748b;font-size:14px;font-weight:700;flex-shrink:0}.webhook-card .webhook-card-row .row-value{flex:1;font-size:14px;color:#0f172a;word-break:break-all}.webhook-card .webhook-card-row.secret-row,.webhook-card .webhook-card-row .secret-row{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.webhook-card .webhook-card-row code{font-family:Consolas,Monaco,monospace;font-size:14px;background:#f1f5f9;color:#334155;padding:4px 10px;border-radius:6px}.webhook-card .eventtype-badge,.webhook-card .api-badge{display:inline-flex;align-items:center;padding:4px 12px;margin:2px 6px 2px 0;border-radius:999px;font-size:12px;font-weight:500;background:#e0f2fe;color:#0369a1}.webhook-card .api-badge{background:#f1f5f9;color:#475569}.webhook-card .webhook-card-actions{display:flex;gap:12px;justify-content:flex-end;margin-top:24px}.webhook-complete{text-align:center;padding:30px 20px}.webhook-complete .complete-icon{display:inline-flex;justify-content:center;align-items:center;width:64px;height:64px;background:#f0fdf4;border-radius:50%;color:#16a34a;font-size:32px;margin-bottom:20px}.webhook-complete h3{font-size:22px;font-weight:700;color:#000;margin:0 0 12px}.webhook-complete .webhook-secret-box{text-align:left;max-width:640px;margin:32px auto;padding:24px;background:#fff;border:1px solid #dfdfdf;border-radius:20px;box-shadow:0 4px 13.45px rgba(192,192,192,.25)}.webhook-complete .webhook-secret-box .secret-warning{color:#9a3412;background:#fff7ed;border:1px solid #ffedd5;border-radius:8px;padding:14px 16px;margin-bottom:20px;font-size:13px;line-height:1.5}.webhook-complete .webhook-secret-box label{display:block;font-size:14px;font-weight:700;color:#0f172a;margin-bottom:8px}.webhook-complete .webhook-secret-box .secret-value-row{display:flex;gap:10px}.webhook-complete .webhook-secret-box .secret-value-row input{flex:1;height:46px;padding:0 16px;border:1.5px solid #dfdfdf;border-radius:8px;font-family:Consolas,Monaco,monospace;font-size:14px;color:#0f172a;background:#f8fafc}.webhook-complete .webhook-secret-box .secret-value-row input:focus{outline:none}.webhook-header-row{display:flex;justify-content:space-between;align-items:center;margin-bottom:24px;width:100%}@media(max-width: 768px){.webhook-header-row{flex-direction:column;align-items:flex-start;gap:12px}}.webhook-header-row .s1-title{font-size:30px;font-weight:700;color:#0d0e11;margin:0 !important;padding:0}.webhook-header-row .webhook-guide-btn{display:inline-flex;align-items:center;justify-content:center;padding:0 16px;height:35px;background-color:#d8f0ff;color:#06f;font-size:12px;font-weight:600;border-radius:7px;text-decoration:none;transition:background-color .2s}.webhook-header-row .webhook-guide-btn:hover{background-color:#c4e5ff}.step1-wrap .s1-form-card{border:1px solid #dfdfdf;border-radius:20px;padding:31px 35px;background:#fff;box-shadow:0px 4px 13.45px rgba(192,192,192,.25)}@media(max-width: 768px){.step1-wrap .s1-form-card{padding:20px 16px}}.step1-wrap .s1-form-card .webhook-card-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:20px;padding-bottom:16px;border-bottom:1.5px solid #818181}.step1-wrap .s1-form-card .webhook-card-head .head-title-group{display:flex;align-items:center;gap:10px;color:#0d0e11}.step1-wrap .s1-form-card .webhook-card-head .head-title-group .head-icon{color:#0d0e11;flex-shrink:0}.step1-wrap .s1-form-card .webhook-card-head h3{font-size:18px;font-weight:700;color:#000;margin:0}.step1-wrap .s1-form-card .webhook-card-head .webhook-created{color:#64748b;font-size:13px;font-weight:500}.step1-wrap .s1-form-card .webhook-card-content{display:flex;flex-direction:column;gap:28px;margin-top:24px}.step1-wrap .s1-form-card .webhook-info-group{display:flex;flex-direction:column;gap:12px;width:100%}.step1-wrap .s1-form-card .webhook-info-group .group-label{font-size:15px;font-weight:700;color:#0d0e11;line-height:1}.step1-wrap .s1-form-card .webhook-info-group .input-display-box{width:100%;height:48px;background-color:#efefef;border-radius:10px;padding:0 20px;display:flex;align-items:center;font-size:14px;color:#4e5968;font-weight:500;word-break:break-all;box-sizing:border-box;border:1px solid #dfdfdf}.step1-wrap .s1-form-card .webhook-info-group .badges-row{display:flex;flex-wrap:wrap;gap:8px}.step1-wrap .s1-form-card .webhook-info-group .eventtype-badge{display:inline-flex;align-items:center;justify-content:center;height:28px;padding:0 14px;border:1px solid #99c2ff;border-radius:14px;font-size:12px;font-weight:500;background-color:#e6f0ff;color:#05f;white-space:nowrap}.step1-wrap .s1-form-card .webhook-info-group .api-badge{display:inline-flex;align-items:center;justify-content:center;height:28px;padding:0 14px;border:1px solid #cbd5e1;border-radius:14px;font-size:12px;font-weight:500;background-color:#f1f5f9;color:#475569;white-space:nowrap}.step1-wrap .s1-form-card .webhook-info-group .secret-action-row{display:flex;align-items:center;gap:10px;width:100%}@media(max-width: 576px){.step1-wrap .s1-form-card .webhook-info-group .secret-action-row{flex-wrap:wrap}}.step1-wrap .s1-form-card .webhook-info-group .secret-box{flex:1;max-width:250px;height:48px;background-color:#efefef;border-radius:10px;padding:0 20px;display:flex;align-items:center;justify-content:center;font-size:14px;color:#4e5968;font-weight:500;box-sizing:border-box;letter-spacing:1px;border:1px solid #dfdfdf}@media(max-width: 576px){.step1-wrap .s1-form-card .webhook-info-group .secret-box{max-width:100%;width:100%;flex:none}}.step1-wrap .s1-form-card .webhook-info-group .btn-reveal-action{height:48px;padding:0 24px;background:#fff;color:#05f;border:1px solid #99c2ff;border-radius:10px;font-size:14px;font-weight:600;cursor:pointer;transition:background-color .2s}.step1-wrap .s1-form-card .webhook-info-group .btn-reveal-action:hover{background-color:#f0f7ff}.step1-wrap .s1-form-card .webhook-info-group .btn-regen-action{height:48px;padding:0 24px;background:#fff;color:#05f;border:1px solid #99c2ff;border-radius:10px;font-size:14px;font-weight:600;cursor:pointer;transition:background-color .2s}.step1-wrap .s1-form-card .webhook-info-group .btn-regen-action:hover{background-color:#f0f7ff}.step1-wrap .s1-form-card .webhook-empty{background:none;border:none;padding:24px 0;box-shadow:none}.btn-webhook-danger-figma{display:inline-flex;align-items:center;justify-content:center;width:144px;height:48px;background-color:#f25252;color:#fff;border:none;border-radius:10px;font-size:15px;font-weight:700;cursor:pointer;transition:background-color .2s}.btn-webhook-danger-figma:hover{background-color:#dc3c3c}.s1-btn-next-figma{display:inline-flex;align-items:center;justify-content:center;width:144px;height:48px;background-color:#3b76e3;color:#fff;border:none;border-radius:10px;font-size:15px;font-weight:700;cursor:pointer;text-decoration:none;transition:background-color .2s}.s1-btn-next-figma:hover{background-color:#2762cf}.step1-wrap .s1-actions,.step2-wrap .s1-actions,.step3-wrap .s1-actions{display:flex;gap:12px;justify-content:flex-end;margin-top:32px}.step1-wrap .s1-actions.s1-actions--figma-step,.step2-wrap .s1-actions.s1-actions--figma-step,.step3-wrap .s1-actions.s1-actions--figma-step{justify-content:flex-end !important}.step1-wrap .s1-actions.s1-actions--figma-step .btn-webhook-cancel-red,.step2-wrap .s1-actions.s1-actions--figma-step .btn-webhook-cancel-red,.step3-wrap .s1-actions.s1-actions--figma-step .btn-webhook-cancel-red{margin-right:0 !important}.step1-wrap .s1-actions .btn-webhook-cancel,.step1-wrap .s1-actions .btn-webhook-danger,.step2-wrap .s1-actions .btn-webhook-cancel,.step2-wrap .s1-actions .btn-webhook-danger,.step3-wrap .s1-actions .btn-webhook-cancel,.step3-wrap .s1-actions .btn-webhook-danger{margin-right:auto}.btn-webhook-cancel-red{display:inline-flex;align-items:center;justify-content:center;width:144px;height:48px;background-color:#ffebeb;color:#f4253c;border:1px solid #ffa3a3;border-radius:10px;font-size:15px;font-weight:700;cursor:pointer;text-decoration:none;transition:all .2s}.btn-webhook-cancel-red:hover{background-color:#fdd8d8;border-color:#ff8b8b;color:#f4253c}.webhook-guide-link{margin-top:30px;text-align:center}.webhook-guide-link a{display:inline-flex;align-items:center;gap:8px;padding:12px 20px;border:1px dashed #dfdfdf;border-radius:10px;color:#2a69de;font-size:14px;font-weight:600;text-decoration:none;transition:all .2s}.webhook-guide-link a:hover{border-color:#2a69de;background:#f0f7ff}.webhook-modal{position:fixed;inset:0;background:rgba(15,23,42,.4);backdrop-filter:blur(4px);display:flex;align-items:center;justify-content:center;z-index:1000}.webhook-modal .webhook-modal-box{background:#fff;border-radius:20px;padding:32px;width:400px;max-width:90vw;box-shadow:0 20px 25px -5px rgba(0,0,0,.1),0 10px 10px -5px rgba(0,0,0,.04)}.webhook-modal .webhook-modal-box h4{font-size:18px;font-weight:700;color:#0f172a;margin:0 0 8px}.webhook-modal .webhook-modal-box p{color:#64748b;font-size:14px;line-height:1.5;margin:0 0 20px}.webhook-modal .webhook-modal-box input{width:100%;height:46px;padding:0 16px;border:1.5px solid #dfdfdf;border-radius:8px;margin-bottom:8px;font-size:14px;color:#0f172a}.webhook-modal .webhook-modal-box input:focus{outline:none;border-color:#2a69de}.webhook-modal .webhook-modal-box .webhook-modal-actions{display:flex;gap:10px;justify-content:flex-end;margin-top:20px}.webhook-modal .webhook-modal-box .webhook-modal-actions .btn-webhook-next,.webhook-modal .webhook-modal-box .webhook-modal-actions .btn-webhook-cancel{width:90px;height:38px;font-size:13px}.step3-wrap .s3-result-container{background:#fff;border:1px solid #dfdfdf;border-radius:20px;box-shadow:0 4px 26.9px 2px rgba(192,192,192,.25);display:flex;flex-direction:column;align-items:center;padding:60px 40px;margin-bottom:24px;text-align:center}.step3-wrap .s3-graphic-wrapper{margin-bottom:20px;display:flex;align-items:center;justify-content:center;width:100%}.step3-wrap .s3-illustration{width:320px;height:auto;max-width:100%}.step3-wrap .s3-illustration .floating-slow{animation:floatSlow 2s ease-in-out infinite alternate}@keyframes floatSlow{0%{transform:translateY(0)}100%{transform:translateY(-10px)}}.step3-wrap .s3-message-wrapper{margin-bottom:40px;text-align:center}.step3-wrap .s3-success-title{font-size:26px;font-weight:700;color:#2a69de;margin-bottom:16px;font-family:"Spoqa Han Sans Neo",sans-serif}.step3-wrap .s3-success-desc{font-size:16px;line-height:1.6;color:#64748b;font-family:"Spoqa Han Sans Neo",sans-serif;margin:0}.step3-wrap .s3-success-desc .s3-highlight{color:#0d0e11;font-weight:700}.step3-wrap .s3-actions{display:flex;justify-content:center;width:100%}.step3-wrap .s3-btn-complete{display:inline-flex;align-items:center;justify-content:center;width:200px;height:48px;background:#2a69de;color:#fff;border-radius:10px;font-size:16px;font-weight:500;text-decoration:none;transition:background .15s}.step3-wrap .s3-btn-complete:hover{background:#1b4ab7;color:#fff}.as-title-banner,.as-title-banner h1,.api-status,.api-status h1,.api-status h2,.api-status h3,.api-status h4{font-family:"Spoqa Han Sans Neo","SpoqaHanSans","Noto Sans KR",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}.as-title-banner{margin:44px 0 28px;border-radius:12px;background:linear-gradient(110deg, #003080 0%, #0049b4 60%, #066ae5 100%);color:#fff;overflow:hidden}.as-title-banner .as-title-banner-inner{display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap;padding:32px}.as-title-banner h1{margin:0;font-size:24px;font-weight:700;letter-spacing:-0.02em;color:#fff}.as-title-banner .as-title-meta{text-align:right}.as-title-banner .as-title-meta p{margin:0;font-weight:400}.as-title-banner .as-title-meta-main{font-size:13px;color:#fff}.as-title-banner .as-title-meta-main strong{font-weight:500}.as-title-banner .as-title-meta-sub{margin-top:2px;font-size:11px;color:#c9d1dc}.as-title-banner .as-title-dot{display:inline-block;width:8px;height:8px;margin-right:6px;border-radius:50%;background:#56e3a6;box-shadow:0 0 0 4px rgba(86,227,166,.25)}@media(max-width: 720px){.as-title-banner .as-title-banner-inner{padding:24px 20px}.as-title-banner h1{font-size:20px}.as-title-banner .as-title-meta{text-align:left}}.api-status{--as-ok: #1b8c4a;--as-ok-bg: #e7f6ec;--as-warn: #c77800;--as-warn-bg: #fef3c7;--as-err: #c8362f;--as-err-bg: #feeae7;--as-info: #0a66c2;--as-info-bg: #e7f0fb;--as-both: #6b21a8;--as-gray-bg: #eef1f5;--as-none-bg: var(--as-ok);--as-border: #e0e6f1;--as-border-strong: #d1d5db;--as-text: #020616;--as-text-2: #252b37;--as-text-3: #4b5563;--as-muted: #6b7280;--as-radius: 12px;--as-pill: 999px;display:grid;grid-template-columns:minmax(0, 1fr);gap:20px;padding:0 0 70px}.api-status.is-standalone{padding-top:44px}.api-status{font-weight:400}.api-status p,.api-status span,.api-status li,.api-status a,.api-status td,.api-status th,.api-status label,.api-status button,.api-status input,.api-status select{font-weight:400}.api-status b,.api-status strong{font-weight:500}.api-status .as-card{background:#fff;border:1px solid var(--as-border);border-radius:var(--as-radius);padding:24px;box-shadow:0 1px 2px rgba(15,23,42,.05)}.api-status .as-card-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-bottom:14px}.api-status .as-card-head h2{margin:0;font-size:18px;letter-spacing:-0.01em;color:var(--as-text)}.api-status .as-card-head .as-desc{margin:4px 0 0;font-size:13px;color:var(--as-muted)}.api-status .as-badge{display:inline-flex;align-items:center;gap:4px;font-size:11px;font-weight:500;padding:4px 10px;border-radius:var(--as-pill);letter-spacing:.02em;white-space:nowrap}.api-status .as-badge.is-incident,.api-status .as-badge.is-outage{background:var(--as-err-bg);color:var(--as-err)}.api-status .as-badge.is-maintenance{background:var(--as-info-bg);color:var(--as-info)}.api-status .as-badge.is-degraded{background:var(--as-warn-bg);color:var(--as-warn)}.api-status .as-badge.is-normal{background:var(--as-ok-bg);color:var(--as-ok)}.api-status .as-badge.is-muted{background:var(--as-gray-bg);color:var(--as-text-3)}.api-status .as-section-title{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap;margin:6px 4px 14px}.api-status .as-section-title h2{margin:0;font-size:20px;letter-spacing:-0.01em}.api-status .as-section-title .as-desc{font-size:13px;color:var(--as-muted)}.api-status .as-section-title .as-count{font-size:12px;font-weight:500;color:var(--as-muted);background:#f6f9fb;padding:2px 8px;border-radius:var(--as-pill)}.api-status .as-empty{color:var(--as-muted);font-size:14px;padding:26px;background:#fff;border:1px dashed var(--as-border);border-radius:var(--as-radius);text-align:center}.api-status .as-api-pills{display:flex;gap:6px;flex-wrap:wrap;align-items:center;margin-top:12px;font-size:13px;color:var(--as-text-3)}.api-status .as-api-pills .as-api-pill{background:var(--as-gray-bg);color:var(--as-text-2);font-size:12px;font-weight:500;padding:4px 10px;border-radius:var(--as-pill)}.api-status .as-api-pills a.as-api-pill{text-decoration:none;transition:background .15s,color .15s}.api-status .as-api-pills a.as-api-pill:hover,.api-status .as-api-pills a.as-api-pill:focus-visible{background:var(--as-info-bg);color:var(--as-info)}.api-status .as-api-pills .as-api-pill.is-unlinked{background:#f3f5f8;color:var(--as-muted);cursor:default}.api-status .as-api-pills .as-api-pill.is-gw{background:#fff;border:1px dashed var(--as-border-strong);color:var(--as-muted);cursor:default}.api-status .as-card-split{margin-top:16px;display:grid;gap:16px;align-items:start}@media(min-width: 961px){.api-status .as-card-split{grid-template-columns:minmax(0, 1fr) minmax(0, 1fr)}}.api-status .as-card-split>.as-alert-notice{margin-top:0}.api-status .as-alert-card .as-card-split>.as-alert-timeline{margin-top:0}.api-status .as-maint-card .as-card-split>.as-maint-body{margin-top:0}.api-status .as-alert-notice{margin-top:16px;border:1px solid var(--as-border);border-radius:var(--as-radius);background:var(--as-gray-bg);padding:14px 16px}.api-status .as-alert-notice .as-alert-notice-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:10px}.api-status .as-alert-notice .as-alert-notice-head strong{font-size:14px;color:var(--as-text)}.api-status .as-alert-notice .as-alert-notice-label{font-size:11px;font-weight:500;padding:2px 8px;border-radius:var(--as-pill);background:var(--as-info-bg);color:var(--as-info)}.api-status .as-alert-notice .as-alert-notice-body{max-height:320px;overflow-y:auto;font-size:14px;line-height:1.7;color:var(--as-text-2);word-break:break-word}.api-status .as-alert-notice .as-alert-notice-body img,.api-status .as-alert-notice .as-alert-notice-body table{max-width:100%}.api-status .as-alert-notice .as-alert-notice-body p:last-child{margin-bottom:0}.api-status .as-alert-notice .as-alert-notice-link{display:inline-block;margin-top:12px;font-size:13px;font-weight:500;color:var(--as-info);text-decoration:none}.api-status .as-alert-card{border-left:4px solid var(--as-err)}.api-status .as-alert-card .as-alert-head{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}.api-status .as-alert-card .as-alert-title{margin:8px 0 6px;font-size:20px;color:var(--as-err)}.api-status .as-alert-card .as-meta{font-size:13px;color:var(--as-text-3)}.api-status .as-alert-card .as-alert-timeline{margin-top:16px;border-top:1px dashed var(--as-border);padding-top:14px;display:grid;gap:10px}.api-status .as-alert-card .as-tl-row{display:grid;grid-template-columns:60px 76px 1fr;gap:12px;font-size:13px}.api-status .as-alert-card .as-tl-row .as-tl-time{color:var(--as-muted);font-variant-numeric:tabular-nums}.api-status .as-alert-card .as-tl-row .as-tl-who{font-size:11px;font-weight:500;text-align:center;height:-webkit-fit-content;height:fit-content;padding:2px 8px;border-radius:var(--as-pill);background:var(--as-gray-bg);color:var(--as-text-2)}.api-status .as-alert-card .as-tl-row .as-tl-who.is-system{background:var(--as-info-bg);color:var(--as-info)}.api-status .as-alert-overflow{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;padding:14px 20px;border:1px dashed var(--as-err);border-radius:var(--as-radius);background:var(--as-err-bg);font-size:13px;color:var(--as-err)}.api-status .as-alert-overflow a{color:var(--as-err);font-weight:500;text-decoration:none}.api-status .as-uptime .as-bar-chart{display:flex;gap:2px;align-items:flex-end;height:56px;overflow-x:auto}.api-status .as-uptime .as-axis{display:flex;justify-content:space-between;margin-top:14px;font-size:11px;color:var(--as-muted)}.api-status .as-bar{flex:1 0 4px;height:100%;border:0;padding:0;border-radius:2px;background:var(--as-ok);cursor:pointer;transition:opacity .15s}.api-status .as-bar.is-outage{background:var(--as-err)}.api-status .as-bar.is-maintenance{background:var(--as-info)}.api-status .as-bar.is-degraded{background:var(--as-warn)}.api-status .as-bar:hover{opacity:.75}.api-status .as-legend{display:flex;gap:14px;flex-wrap:wrap;margin-top:18px;font-size:12px;color:var(--as-muted)}.api-status .as-legend .as-sw{display:inline-block;width:10px;height:10px;border-radius:2px;margin-right:4px;vertical-align:middle}.api-status .as-legend .as-sw.is-ok{background:var(--as-ok)}.api-status .as-legend .as-sw.is-outage{background:var(--as-err)}.api-status .as-legend .as-sw.is-maintenance{background:var(--as-info)}.api-status .as-legend .as-sw.is-degraded{background:var(--as-warn)}.api-status .as-legend .as-sw.is-none{background:var(--as-none-bg)}.api-status .as-legend .as-sw.is-mixed,.api-status .as-legend .as-sw.is-both{background:var(--as-both)}.api-status .as-my-apis table{width:100%;border-collapse:collapse;font-size:13px}.api-status .as-my-apis th,.api-status .as-my-apis td{text-align:left;padding:12px 14px}.api-status .as-my-apis th{background:#f6f9fb;color:var(--as-text-3);font-size:12px;font-weight:500;border-bottom:1px solid var(--as-border)}.api-status .as-my-apis tbody tr{border-bottom:1px solid var(--as-border);cursor:pointer}.api-status .as-my-apis tbody tr:last-child{border-bottom:0}.api-status .as-my-apis tbody tr:hover{background:#f6f9fb}.api-status .as-my-apis .as-api-name{font-weight:500;color:var(--as-text)}.api-status .as-my-apis .as-num{font-variant-numeric:tabular-nums}.api-status .as-maint-list{display:grid;gap:14px}.api-status .as-maint-card{background:#fff;border:1px solid var(--as-border);border-left:4px solid var(--as-info);border-radius:var(--as-radius);padding:22px 24px;box-shadow:0 1px 2px rgba(15,23,42,.05)}.api-status .as-maint-card.is-ongoing{border-left-color:var(--as-warn)}.api-status .as-maint-card .as-maint-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;flex-wrap:wrap}.api-status .as-maint-card .as-maint-title{margin:0;font-size:18px;font-weight:700;color:var(--as-text)}.api-status .as-maint-card .as-schedule{display:inline-flex;align-items:center;gap:6px;font-size:12px;font-weight:500;color:var(--as-info);background:var(--as-info-bg);padding:4px 12px;border-radius:var(--as-pill);font-variant-numeric:tabular-nums}.api-status .as-maint-card.is-ongoing .as-schedule{color:var(--as-warn);background:var(--as-warn-bg)}.api-status .as-maint-card .as-maint-body{margin:14px 0 12px;font-size:14px;line-height:1.6;color:var(--as-text-3)}.api-status .as-maint-card .as-maint-meta{text-align:right;font-size:12px;color:var(--as-muted);font-variant-numeric:tabular-nums}.api-status .as-issue-list{display:grid;gap:24px}.api-status .as-issue-group .as-date-head{display:flex;align-items:center;gap:8px;width:100%;margin:0 4px 10px;padding-bottom:8px;font-size:14px;font-weight:500;color:var(--as-text-2);border-bottom:1px solid var(--as-border)}.api-status .as-issue-card{background:#fff;border:1px solid var(--as-border);border-radius:var(--as-radius);padding:22px 26px 24px;box-shadow:0 1px 2px rgba(15,23,42,.05)}.api-status .as-issue-card.is-incident{border-left:4px solid var(--as-err)}.api-status .as-issue-card.is-degraded{border-left:4px solid var(--as-warn)}.api-status .as-issue-card.is-maintenance{border-left:4px solid var(--as-info)}.api-status .as-issue-card .as-issue-meta-row{display:flex;align-items:center;gap:12px;flex-wrap:wrap;font-size:12px;color:var(--as-muted);margin-bottom:10px}.api-status .as-issue-card .as-issue-title{margin:0 0 18px;font-size:19px;font-weight:800;line-height:1.3;letter-spacing:-0.01em}.api-status .as-issue-card.is-incident .as-issue-title{color:var(--as-err)}.api-status .as-issue-card.is-degraded .as-issue-title{color:var(--as-warn)}.api-status .as-issue-card.is-maintenance .as-issue-title{color:var(--as-info)}.api-status .as-tl-block{position:relative;padding-left:24px}.api-status .as-tl-block::before{content:"";position:absolute;left:6px;top:8px;bottom:8px;width:1px;background:var(--as-border)}.api-status .as-tl-item{position:relative;padding-bottom:22px}.api-status .as-tl-item:last-child{padding-bottom:0}.api-status .as-tl-item+.as-tl-item{margin-top:4px;padding-top:22px;border-top:1px dashed var(--as-border)}.api-status .as-tl-item .as-dot{position:absolute;left:-24px;top:6px;width:12px;height:12px;border-radius:50%;border:2px solid #fff;background:var(--as-muted);box-shadow:0 0 0 1px var(--as-border-strong)}.api-status .as-tl-item.state-RESOLVED .as-dot{background:var(--as-ok);box-shadow:0 0 0 1px var(--as-ok)}.api-status .as-tl-item.state-MONITORING .as-dot{background:var(--as-warn);box-shadow:0 0 0 1px var(--as-warn)}.api-status .as-tl-item.state-IDENTIFIED .as-dot{background:#c95a0f;box-shadow:0 0 0 1px #c95a0f}.api-status .as-tl-item.state-INVESTIGATING .as-dot{background:var(--as-warn);box-shadow:0 0 0 1px var(--as-warn)}.api-status .as-tl-item.state-CANCELED .as-dot{background:var(--as-muted);box-shadow:0 0 0 1px var(--as-muted)}.api-status .as-tl-item .as-tl-label{margin:0 0 8px;font-size:14px;font-weight:500;color:var(--as-text)}.api-status .as-tl-item .as-tl-body{margin:0 0 8px;font-size:14px;line-height:1.6;color:var(--as-text);white-space:pre-line}.api-status .as-tl-item .as-tl-ts{margin:0;font-size:13px;color:var(--as-muted);font-variant-numeric:tabular-nums}.api-status .as-more-link{display:block;margin-top:14px;text-align:right;font-size:13px;text-decoration:none}.api-status .as-index-bar{display:flex;gap:2px;align-items:stretch;height:44px;overflow-x:auto}.api-status .as-index-cell{flex:1 0 4px;border:0;padding:0;border-radius:2px;background:var(--as-none-bg);cursor:pointer;transition:opacity .15s}.api-status .as-index-cell.has-incident{background:var(--as-err)}.api-status .as-index-cell.has-degraded{background:var(--as-warn)}.api-status .as-index-cell.has-maintenance{background:var(--as-info)}.api-status .as-index-cell.has-mixed,.api-status .as-index-cell.has-both{background:var(--as-both)}.api-status .as-index-cell.is-selected{outline:2px solid var(--as-text-2);outline-offset:1px}.api-status .as-index-cell:hover{opacity:.75}.api-status .as-filter-bar{display:flex;align-items:center;gap:14px;flex-wrap:wrap;background:#fff;border:1px solid var(--as-border);border-radius:var(--as-radius);padding:14px 18px;font-size:13px}.api-status .as-filter-bar .as-filter-field{display:flex;align-items:center;gap:8px;min-width:0}.api-status .as-filter-bar .as-filter-label{font-weight:500;color:var(--as-text-2);white-space:nowrap}.api-status .as-filter-bar .as-filter-right{display:flex;align-items:center;gap:8px;margin-left:auto;color:var(--as-muted);white-space:nowrap}.api-status .as-filter-bar .as-filter-count b{color:var(--as-text)}.api-status .as-input{height:34px;padding:0 10px;border:1px solid var(--as-border-strong);border-radius:6px;font-size:13px;color:var(--as-text);background:#fff;max-width:100%}.api-status .as-input:focus{outline:none;border-color:var(--as-info);box-shadow:0 0 0 2px rgba(10,102,194,.15)}.api-status .as-field-control{position:relative;display:inline-flex;align-items:center}.api-status .as-field-control input{padding-right:30px}.api-status .as-field-control input[type=search]{-webkit-appearance:none;appearance:none}.api-status .as-field-control input[type=search]::-webkit-search-cancel-button,.api-status .as-field-control input[type=search]::-webkit-search-decoration{-webkit-appearance:none;appearance:none;display:none}.api-status .as-field-clear{position:absolute;top:50%;right:6px;transform:translateY(-50%);display:none;width:20px;height:20px;padding:0;border:0;border-radius:50%;background:var(--as-gray-bg);color:var(--as-text-3);font-size:14px;line-height:1;cursor:pointer}.api-status .as-field-clear:hover{background:var(--as-border-strong);color:var(--as-text)}.api-status .as-filter-field.has-value .as-field-clear{display:block}.api-status #filterDateInput{padding-right:52px}.api-status .as-combo .as-field-control{min-width:240px;flex:1 1 240px}.api-status .as-combo input{width:100%}.api-status .as-combo .as-combo-list{position:absolute;z-index:20;top:calc(100% + 4px);left:0;right:0;max-height:280px;margin:0;padding:4px;overflow-y:auto;list-style:none;background:#fff;border:1px solid var(--as-border-strong);border-radius:8px;box-shadow:0 8px 24px rgba(15,23,42,.12)}.api-status .as-combo .as-combo-item{padding:8px 10px;border-radius:6px;cursor:pointer;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.api-status .as-combo .as-combo-item:hover{background:var(--as-info-bg)}.api-status .as-combo .as-combo-empty{padding:10px;color:var(--as-muted);font-size:12px;text-align:center}.api-status .as-unlisted-banner{display:flex;align-items:center;gap:10px;padding:12px 18px;border:1px solid var(--as-border);border-left:4px solid var(--as-muted);border-radius:var(--as-radius);background:#f6f8fa;font-size:13px;color:var(--as-text-3)}.api-status .as-unlisted-banner b{color:var(--as-text)}.api-status .as-unlisted-banner .as-unlisted-icon{flex:0 0 auto;color:var(--as-muted);font-size:15px}.api-status .as-pager{display:flex;justify-content:center;gap:6px;margin-top:6px;flex-wrap:wrap}.api-status .as-btn{min-width:32px;height:32px;padding:0 12px;border:1px solid var(--as-border-strong);border-radius:6px;background:#fff;color:var(--as-text-2);font-size:13px;line-height:1;cursor:pointer}.api-status .as-btn:hover{background:#f6f9fb}.api-status .as-btn.is-current{border-color:var(--as-info);background:var(--as-info);color:#fff;cursor:default}@media(max-width: 960px){.api-status .as-filter-bar{gap:10px}.api-status .as-filter-bar .as-filter-right{margin-left:0;width:100%;justify-content:flex-end}.api-status .as-my-apis table{display:block;overflow-x:auto;white-space:nowrap}}@media(max-width: 720px){.api-status{gap:16px;padding-bottom:48px}.api-status.is-standalone{padding-top:32px}.api-status .as-card{padding:18px 16px}.api-status .as-card-head{margin-bottom:12px}.api-status .as-alert-card .as-tl-row{grid-template-columns:52px 1fr}.api-status .as-alert-card .as-tl-who{display:none}.api-status .as-alert-card .as-alert-title{font-size:18px}.api-status .as-filter-bar{flex-direction:column;align-items:stretch;padding:14px}.api-status .as-filter-bar .as-filter-field{flex-direction:column;align-items:flex-start;gap:6px}.api-status .as-filter-bar .as-input,.api-status .as-filter-bar .as-field-control{flex:0 0 auto;width:100%;min-width:0}.api-status .as-filter-bar .as-filter-right{justify-content:space-between}.api-status .as-index-bar{height:40px}.api-status .as-index-bar .as-index-cell{flex:0 0 12px}.api-status .as-uptime .as-bar-chart .as-bar{flex:0 0 12px}.api-status .as-issue-card{padding:18px 16px}.api-status .as-issue-title{font-size:17px}.api-status .as-maint-card{padding:18px 16px}.api-status .as-uptime .as-bar-chart{height:44px}.api-status .as-issue-list{gap:16px}.api-status .as-pager button{min-width:30px;padding:0 8px}}@media(max-width: 480px){.api-status .as-section-title h2{font-size:18px}.api-status .as-legend{gap:8px;font-size:11px}.api-status .as-tl-block{padding-left:18px}.api-status .as-tl-item .as-dot{left:-18px}}.d-none{display:none !important}.d-block{display:block !important}.d-inline-block{display:inline-block !important}.d-flex{display:flex !important}.d-inline-flex{display:inline-flex !important}.d-grid{display:grid !important}.flex-center{display:flex;justify-content:center;align-items:center}.flex-between{display:flex;justify-content:space-between;align-items:center}.flex-column{flex-direction:column}.flex-wrap{flex-wrap:wrap}.align-center{align-items:center}.align-start{align-items:flex-start}.align-end{align-items:flex-end}.justify-center{justify-content:center}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-between{justify-content:space-between}.justify-around{justify-content:space-around}.gap-xs{gap:4px}.gap-sm{gap:8px}.gap-md{gap:16px}.gap-lg{gap:24px}.gap-xl{gap:32px}.m-0{margin:0 !important}.mt-0{margin-top:0 !important}.mb-0{margin-bottom:0 !important}.ml-0{margin-left:0 !important}.mr-0{margin-right:0 !important}.mx-0{margin-left:0 !important;margin-right:0 !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.m-xs{margin:4px !important}.mt-xs{margin-top:4px !important}.mb-xs{margin-bottom:4px !important}.ml-xs{margin-left:4px !important}.mr-xs{margin-right:4px !important}.mx-xs{margin-left:4px !important;margin-right:4px !important}.my-xs{margin-top:4px !important;margin-bottom:4px !important}.m-sm{margin:8px !important}.mt-sm{margin-top:8px !important}.mb-sm{margin-bottom:8px !important}.ml-sm{margin-left:8px !important}.mr-sm{margin-right:8px !important}.mx-sm{margin-left:8px !important;margin-right:8px !important}.my-sm{margin-top:8px !important;margin-bottom:8px !important}.m-md{margin:16px !important}.mt-md{margin-top:16px !important}.mb-md{margin-bottom:16px !important}.ml-md{margin-left:16px !important}.mr-md{margin-right:16px !important}.mx-md{margin-left:16px !important;margin-right:16px !important}.my-md{margin-top:16px !important;margin-bottom:16px !important}.m-lg{margin:24px !important}.mt-lg{margin-top:24px !important}.mb-lg{margin-bottom:24px !important}.ml-lg{margin-left:24px !important}.mr-lg{margin-right:24px !important}.mx-lg{margin-left:24px !important;margin-right:24px !important}.my-lg{margin-top:24px !important;margin-bottom:24px !important}.m-xl{margin:32px !important}.mt-xl{margin-top:32px !important}.mb-xl{margin-bottom:32px !important}.ml-xl{margin-left:32px !important}.mr-xl{margin-right:32px !important}.mx-xl{margin-left:32px !important;margin-right:32px !important}.my-xl{margin-top:32px !important;margin-bottom:32px !important}.m-2xl{margin:40px !important}.mt-2xl{margin-top:40px !important}.mb-2xl{margin-bottom:40px !important}.ml-2xl{margin-left:40px !important}.mr-2xl{margin-right:40px !important}.mx-2xl{margin-left:40px !important;margin-right:40px !important}.my-2xl{margin-top:40px !important;margin-bottom:40px !important}.m-3xl{margin:48px !important}.mt-3xl{margin-top:48px !important}.mb-3xl{margin-bottom:48px !important}.ml-3xl{margin-left:48px !important}.mr-3xl{margin-right:48px !important}.mx-3xl{margin-left:48px !important;margin-right:48px !important}.my-3xl{margin-top:48px !important;margin-bottom:48px !important}.m-4xl{margin:64px !important}.mt-4xl{margin-top:64px !important}.mb-4xl{margin-bottom:64px !important}.ml-4xl{margin-left:64px !important}.mr-4xl{margin-right:64px !important}.mx-4xl{margin-left:64px !important;margin-right:64px !important}.my-4xl{margin-top:64px !important;margin-bottom:64px !important}.m-auto{margin:auto !important}.mt-auto{margin-top:auto !important}.mb-auto{margin-bottom:auto !important}.ml-auto{margin-left:auto !important}.mr-auto{margin-right:auto !important}.mx-auto{margin-left:auto !important;margin-right:auto !important}.my-auto{margin-top:auto !important;margin-bottom:auto !important}.p-0{padding:0 !important}.pt-0{padding-top:0 !important}.pb-0{padding-bottom:0 !important}.pl-0{padding-left:0 !important}.pr-0{padding-right:0 !important}.px-0{padding-left:0 !important;padding-right:0 !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.p-xs{padding:4px !important}.pt-xs{padding-top:4px !important}.pb-xs{padding-bottom:4px !important}.pl-xs{padding-left:4px !important}.pr-xs{padding-right:4px !important}.px-xs{padding-left:4px !important;padding-right:4px !important}.py-xs{padding-top:4px !important;padding-bottom:4px !important}.p-sm{padding:8px !important}.pt-sm{padding-top:8px !important}.pb-sm{padding-bottom:8px !important}.pl-sm{padding-left:8px !important}.pr-sm{padding-right:8px !important}.px-sm{padding-left:8px !important;padding-right:8px !important}.py-sm{padding-top:8px !important;padding-bottom:8px !important}.p-md{padding:16px !important}.pt-md{padding-top:16px !important}.pb-md{padding-bottom:16px !important}.pl-md{padding-left:16px !important}.pr-md{padding-right:16px !important}.px-md{padding-left:16px !important;padding-right:16px !important}.py-md{padding-top:16px !important;padding-bottom:16px !important}.p-lg{padding:24px !important}.pt-lg{padding-top:24px !important}.pb-lg{padding-bottom:24px !important}.pl-lg{padding-left:24px !important}.pr-lg{padding-right:24px !important}.px-lg{padding-left:24px !important;padding-right:24px !important}.py-lg{padding-top:24px !important;padding-bottom:24px !important}.p-xl{padding:32px !important}.pt-xl{padding-top:32px !important}.pb-xl{padding-bottom:32px !important}.pl-xl{padding-left:32px !important}.pr-xl{padding-right:32px !important}.px-xl{padding-left:32px !important;padding-right:32px !important}.py-xl{padding-top:32px !important;padding-bottom:32px !important}.p-2xl{padding:40px !important}.pt-2xl{padding-top:40px !important}.pb-2xl{padding-bottom:40px !important}.pl-2xl{padding-left:40px !important}.pr-2xl{padding-right:40px !important}.px-2xl{padding-left:40px !important;padding-right:40px !important}.py-2xl{padding-top:40px !important;padding-bottom:40px !important}.p-3xl{padding:48px !important}.pt-3xl{padding-top:48px !important}.pb-3xl{padding-bottom:48px !important}.pl-3xl{padding-left:48px !important}.pr-3xl{padding-right:48px !important}.px-3xl{padding-left:48px !important;padding-right:48px !important}.py-3xl{padding-top:48px !important;padding-bottom:48px !important}.p-4xl{padding:64px !important}.pt-4xl{padding-top:64px !important}.pb-4xl{padding-bottom:64px !important}.pl-4xl{padding-left:64px !important}.pr-4xl{padding-right:64px !important}.px-4xl{padding-left:64px !important;padding-right:64px !important}.py-4xl{padding-top:64px !important;padding-bottom:64px !important}.w-25{width:25%}.w-50{width:50%}.w-75{width:75%}.w-100{width:100%}.w-auto{width:auto}.h-25{height:25%}.h-50{height:50%}.h-75{height:75%}.h-100{height:100%}.h-auto{height:auto}.vh-100{height:100vh}.position-relative{position:relative}.position-absolute{position:absolute}.position-fixed{position:fixed}.position-sticky{position:sticky}.bg-primary{background:#0049b4}.bg-secondary{background:#c3dfea}.bg-white{background:#fff}.bg-gray{background:#f8fafc}.bg-light{background:#eff6ff}.border{border:1px solid #e2e8f0}.border-0{border:none !important}.border-top{border-top:1px solid #e2e8f0}.border-bottom{border-bottom:1px solid #e2e8f0}.border-left{border-left:1px solid #e2e8f0}.border-right{border-right:1px solid #e2e8f0}.border-primary{border-color:#0049b4}.rounded-0{border-radius:0}.rounded-sm{border-radius:6px}.rounded{border-radius:8px}.rounded-lg{border-radius:12px}.rounded-xl{border-radius:16px}.rounded-full{border-radius:50px}.rounded-circle{border-radius:50%}.shadow-none{box-shadow:none !important}.shadow-sm{box-shadow:0 2px 4px rgba(0,0,0,.05)}.shadow{box-shadow:0 4px 12px rgba(75,155,255,.1)}.shadow-lg{box-shadow:0 8px 24px rgba(75,155,255,.15)}.shadow-xl{box-shadow:0 16px 40px rgba(75,155,255,.2)}.overflow-hidden{overflow:hidden}.overflow-auto{overflow:auto}.overflow-x-hidden{overflow-x:hidden}.overflow-y-hidden{overflow-y:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.z-0{z-index:0}.z-10{z-index:10}.z-20{z-index:20}.z-30{z-index:30}.z-40{z-index:40}.z-50{z-index:50}@media(max-width: 768px){.sm-hide{display:none !important}}@media(max-width: 1024px){.md-hide{display:none !important}}@media(min-width: 769px){.mobile-only{display:none !important}}@media(max-width: 768px){.desktop-only{display:none !important}}.pc{display:block}@media(max-width: 768px){.pc{display:none !important}}.mobile{display:none}@media(max-width: 768px){.mobile{display:block !important}}.cursor-pointer{cursor:pointer}.cursor-default{cursor:default}.cursor-not-allowed{cursor:not-allowed}.opacity-0{opacity:0}.opacity-25{opacity:.25}.opacity-50{opacity:.5}.opacity-75{opacity:.75}.opacity-100{opacity:1}.transition{transition:all .3s ease}.transition-fast{transition:all .15s ease}.transition-slow{transition:all .5s ease}.transition-none{transition:none}.visually-hidden,.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);white-space:nowrap;border:0}/*# sourceMappingURL=main.min.css.map */ diff --git a/src/main/resources/static/css/main.min.css.map b/src/main/resources/static/css/main.min.css.map index 8133653..fb2541e 100644 --- a/src/main/resources/static/css/main.min.css.map +++ b/src/main/resources/static/css/main.min.css.map @@ -1 +1 @@ -{"version":3,"sourceRoot":"","sources":["../sass/base/_reset.scss","../sass/abstracts/_variables.scss","../sass/base/_typography.scss","../sass/abstracts/_mixins.scss","../sass/base/_animations.scss","../sass/base/_djb-font.scss","../sass/layout/_header.scss","../sass/layout/_footer.scss","../sass/layout/_grid.scss","../sass/layout/_container.scss","../sass/components/_buttons.scss","../sass/components/_badges.scss","../sass/components/_cards.scss","../sass/components/_apikey-common.scss","../sass/components/_forms.scss","../sass/components/_modals.scss","../sass/components/_navigation.scss","../sass/components/_partners.scss","../sass/components/_cta.scss","../sass/components/_password-popup.scss","../sass/components/_header-auth.scss","../sass/components/_tables.scss","../sass/components/_accordion.scss","../sass/components/_page-title-banner.scss","../sass/components/_alerts.scss","../sass/components/_pagination.scss","../sass/components/_breadcrumb.scss","../sass/components/_test-env-notice.scss","../sass/pages/_index.scss","../sass/pages/_api-market.scss","../sass/pages/_login.scss","../sass/pages/_account-recovery.scss","../sass/pages/_signup-selection.scss","../sass/pages/_mypage.scss","../sass/pages/_apikey-register.scss","../sass/pages/_apikey-detail.scss","../sass/pages/_apikey-list.scss","../sass/pages/_notice.scss","../sass/pages/_inquiry.scss","../sass/pages/_org-register.scss","../sass/pages/_partnership.scss","../sass/pages/_user-management.scss","../sass/pages/_commission.scss","../sass/pages/_terms-agreements.scss","../sass/pages/_service.scss","../sass/pages/_api-statistics.scss","../sass/base/_utilities.scss"],"names":[],"mappings":"CAQA,MACE,mCACA,gCAGF,EACE,SACA,UACA,sBAGF,KACE,eACA,mCACA,kCACA,kCAGF,KACE,YCUoB,6HDTpB,YCiCmB,IDhCnB,MCXU,QDYV,iBCTM,KDUN,kBAIF,MACE,gBAIF,EACE,qBACA,cACA,WCiEgB,aD7DlB,IACE,eACA,YACA,cAIF,OACE,oBACA,kBACA,oBACA,eACA,yBACA,YACA,UAIF,sBAGE,oBACA,kBACA,oBACA,YACA,aAIF,MACE,yBACA,iBAIF,kBACE,YC3BiB,ID4BjB,YCxBkB,ID4BpB,2FAGE,cAIF,YACE,mCACA,MC/EU,QDkFZ,iBACE,mCACA,MCpFU,QCTZ,WACE,2BACA,qEACA,oBACA,kBACA,kBAIF,KACE,UDwBe,KCvBf,YDiCoB,IChCpB,MDHU,QCOZ,OACE,UDwBc,KCvBd,YD8BsB,IC7BtB,cDwCW,KEzDT,yBDcJ,OAMI,UDiBY,MEvCZ,yBDgBJ,OAUI,UDYY,MCRhB,OACE,UDSc,KCRd,YDeiB,ICdjB,cDyBW,KExDT,yBD4BJ,OAMI,UDEY,MEtCZ,yBD8BJ,OAUI,UDHW,MCOf,OACE,UDNc,KCOd,YDCiB,kBAWN,KExDT,yBD0CJ,OAMI,UDbW,MCiBf,OACE,UDjBc,KCkBd,YDVqB,ICWrB,kBAGF,OACE,UDxBa,KCyBb,YDhBqB,ICiBrB,cDNW,ICSb,OACE,UD/Ba,KCgCb,YDtBqB,ICuBrB,cDZW,ICgBb,EACE,cDhBW,KCiBX,YDvBmB,ICyBnB,aACE,gBAIJ,cACE,MDrFa,QCwFf,gBACE,MDxFe,QC2FjB,WACE,MDlFU,QCqFZ,WACE,MDrFU,QCwFZ,YACE,MDxFW,QC2Fb,YACE,MD3FM,KC+FR,WACE,gBAGF,aACE,kBAGF,YACE,iBAIF,cACE,YD7EoB,ICgFtB,aACE,YDhFmB,ICmFrB,eACE,YDnFqB,ICsFvB,WACE,YDtFiB,ICyFnB,gBACE,YDzFsB,IC6FxB,SACE,UD9Ga,KCiHf,SACE,UDjHa,KCoHf,WACE,UDpHe,KCuHjB,SACE,UDtHa,KCyHf,SACE,UDzHa,KC6Hf,MACE,UD/Ha,KCgIb,YDxHoB,ICyHpB,YDhHkB,ICiHlB,MD5JU,QC+JZ,SACE,UDzIa,KC0Ib,MDjKU,QCoKZ,MACE,YDlJiB,oCCmJjB,UD/Ia,KCgJb,WDpKQ,QCqKR,gBACA,cD/GiB,ICmHnB,EACE,MD1La,QC4Lb,QACE,MD5La,QCiMjB,WACE,QDvIW,KCwIX,cACA,8BACA,WDvLQ,QCwLR,kBAEA,aACE,gBAKJ,GACE,YACA,6BACA,cEhNF,iBACE,QACE,qCAEF,IACE,2CAKJ,kBACE,QACE,wBAEF,IACE,6BAKJ,uBACE,KACE,UACA,4BAEF,GACE,UACA,yBAKJ,qBACE,KACE,UACA,2BAEF,GACE,UACA,yBAKJ,uBACE,KACE,UACA,4BAEF,GACE,UACA,yBAKJ,wBACE,KACE,UACA,2BAEF,GACE,UACA,yBAKJ,kBACE,KACE,UAEF,GACE,WAKJ,uBACE,KACE,UACA,qBAEF,GACE,UACA,oBAKJ,gBACE,KACE,uBAEF,GACE,0BAKJ,iBACE,GACE,mBAEF,IACE,sBAEF,KACE,oBAKJ,mBACE,GACE,0CAEF,KACE,0CAKJ,gBACE,GACE,uBAEF,IACE,wBAEF,IACE,wBAEF,IACE,wBAEF,IACE,wBAEF,IACE,wBAEF,IACE,uBAEF,KACE,wBAKJ,iBACE,GACE,UACA,qBAEF,IACE,qBAEF,KACE,UACA,oBAKJ,uBACE,KACE,UACA,sBAEF,GACE,UACA,oBAKJ,kBACE,GACE,mBACA,UAEF,KACE,mBACA,WAKJ,yBACE,GACE,2BAEF,IACE,6BAEF,KACE,4BAKJ,eACE,yCAGF,gBACE,6BAGF,cACE,kCAGF,eACE,wCAGF,iBACE,8BAGF,kBACE,iCAGF,oBACE,mCAGF,aACE,2DAIF,SACE,oBAGF,SACE,oBAGF,SACE,oBAGF,SACE,oBAGF,SACE,oBAIF,eACE,uBAGF,iBACE,uBAGF,eACE,sBAGF,iBACE,sBAIF,YACE,8BAEA,kBACE,sBAIJ,YACE,kDAEA,kBACE,2BACA,WHvQQ,gCG2QZ,cACE,8BAEA,oBACE,uBAIJ,aACE,kBACA,gBAEA,qBACE,WACA,kBACA,SACA,UACA,WACA,YACA,wBACA,8BACA,4BAGF,2BACE,yCCjUJ,WACE,iCACA,gBACA,wNAQF,WACE,iCACA,gBACA,oOAUF,WACE,yBACA,gBACA,kBACA,gIAKF,WACE,yBACA,gBACA,kBACA,oIAKF,WACE,yBACA,gBACA,kBACA,oIAKF,WACE,yBACA,gBACA,kBACA,4HAMF,WACE,kBACA,gBACA,kBACA,yDAEF,WACE,kBACA,gBACA,kBACA,0DAEF,WACE,kBACA,gBACA,kBACA,0DAEF,WACE,kBACA,gBACA,kBACA,wDAMF,WACE,2BACA,gBACA,oLAIF,WACE,+BACA,gBACA,2KAMF,WACE,2BACA,gBACA,wKAIF,WACE,2BACA,gBACA,wKAIF,WACE,2BACA,gBACA,wKCrHF,MAEE,wBACA,0BACA,uBACA,yBACA,yBACA,wBACA,yBAGA,qBACA,qBACA,sBACA,iBACA,mBACA,oBACA,uBAGA,2CACA,gDACA,iDACA,iDAMF,mBACE,eACA,MACA,OACA,QACA,YACA,4DACA,YACA,aACA,mBACA,uBACA,qCAEA,qCACE,aACA,mBACA,SAGF,iCACE,WACA,eACA,gBAGF,mCACE,aACA,QAGF,+BACE,iBACA,oCACA,mBACA,yBACA,WACA,eACA,gBACA,eACA,wBAEA,qCACE,8BACA,kBAGF,sCACE,gBACA,cACA,kBAIJ,yBArDF,mBAsDI,YACA,8CACA,gEAMF,yDACA,yBACE,0DAQJ,OACE,kBACA,UACA,WACA,UACA,YACA,gBACA,sBACA,mBACA,SAMF,eACE,kBACA,sBACA,QL5Be,IK6Bf,aACA,wBACA,6BAGA,4BACE,yBAEF,0BACE,YACA,UAEA,0CACE,YAKN,gBACE,aACA,mBACA,8BACA,YACA,iBACA,cACA,eAQF,aACE,aACA,mBAEA,mBACE,YACA,YAKJ,cACE,aACA,mBACA,SAIF,MACE,cACA,YACA,YACA,sBACA,WAEA,QACE,aACA,mBACA,SACA,YACA,qBAGF,UACE,YACA,YACA,cAIJ,WACE,eACA,gBACA,iBACA,uBACA,qBACA,oBACA,mBACA,YACA,gBACA,wBAGA,iBACE,0BAGF,mBACE,uBAKJ,+CAGE,qBACA,qBACA,8BAEA,iEACE,sBAGF,2DACE,cAKJ,cACE,aACA,mBACA,SAIF,YACE,WACA,YACA,0BACA,YACA,kBACA,eACA,eACA,uBACA,wBAEA,kBACE,2BACA,0BACA,qBAOJ,gBACE,aACA,mBACA,8BACA,WAEA,yBANF,gBAOI,cAIJ,eACE,aACA,mBACA,8BACA,WAEA,yBANF,eAOI,cAQJ,aACE,aACA,mBACA,2BAEA,+BACE,aACA,mBAGF,0BAGE,+BACA,WACA,mBAGF,+BACE,YLtRkB,6HKuRlB,kCACA,gBACA,cACA,qBACA,mBAEA,qCACE,cAKN,eAEE,aAEA,6BACE,eACA,gBACA,uBACA,SAIJ,cACE,aACA,mBAEA,+BAIE,6BACA,8BACA,yBACA,YACA,eACA,aACA,sBACA,uBACA,mBAEA,wBAEA,8BACA,kBACA,qCAEA,qCACE,2BAGF,+CAGE,8BACA,WACA,mBACA,wBACA,kBAKE,mEACE,wCAEF,mEACE,UAEF,mEACE,0CAUV,eACE,eACA,MACA,OACA,QACA,SACA,aACA,kBACA,UACA,wBAEA,sBACE,mBACA,UAEA,sCACE,wBAIJ,+BACE,aAGF,+BACE,kBACA,MACA,OACA,QACA,SACA,WACA,YACA,gBACA,2BACA,8BACA,aACA,sBACA,gBAIF,8BACE,aACA,mBACA,8BACA,YACA,gBACA,sBACA,gBAEA,kDACE,aACA,mBACA,2BAEA,oEACE,aACA,mBAGF,+DACE,+BACA,WACA,mBAGF,oEACE,YL3ac,6HK4ad,kCACA,gBACA,cACA,qBACA,mBAIJ,mDACE,aACA,mBACA,QAGF,+CACE,WACA,YACA,aACA,mBACA,uBAEA,mDACE,WACA,YAGF,qDACE,WAIJ,4CACE,WACA,YACA,yBACA,YACA,eACA,aACA,mBACA,uBACA,UAEA,gDACE,WACA,YAGF,kDACE,WAMN,+BACE,aACA,sBACA,mBACA,uBACA,iBACA,kBACA,mBACA,gCAGA,kDACE,mBAEA,sDACE,WACA,YAKJ,qDACE,YLxfgB,6HKyfhB,eACA,gBACA,cACA,iBACA,iBACA,kBAGF,6CACE,YLlgBgB,6HKmgBhB,eACA,gBACA,cACA,iBACA,kBACA,kBAGF,gDACE,aACA,SACA,WACA,gBAIF,kDACE,OACA,aACA,mBACA,uBACA,YACA,mBACA,cACA,YL3hBgB,6HK4hBhB,eACA,gBACA,kBACA,qBACA,wBAEA,wDACE,mBAKJ,iDACE,OACA,aACA,mBACA,uBACA,YACA,mBACA,WACA,YLhjBgB,6HKijBhB,eACA,gBACA,kBACA,qBACA,wBAEA,uDACE,2BAKJ,6CACE,mBACA,iBACA,kBACA,SACA,2BAEA,gEACE,gBACA,cAGF,gEACE,aACA,sBACA,QAIF,gEACE,YLjlBc,6HKklBd,eACA,gBACA,cACA,iBACA,SAGF,2DACE,SACA,gBAMN,8BACE,aACA,yBACA,aACA,gBACA,6BAEA,iDACE,aACA,mBACA,QACA,aACA,yBACA,YACA,eACA,qBAEA,qDACE,WACA,YAGF,sDACE,YLxnBc,6HKynBd,eACA,gBACA,cACA,iBAGF,uDACE,WAMN,2BACE,OACA,eACA,gBAEA,6CACE,gBACA,SACA,UAGF,6CACE,gCAGA,8DACE,aACA,mBACA,8BACA,WACA,YACA,cACA,yBACA,YACA,eACA,YL/pBc,6HKgqBd,eACA,gBACA,cACA,gBAEA,8EACE,8BAKJ,6DACE,aACA,gBACA,SACA,eACA,mBAGE,kEACE,cACA,0BACA,YLtrBU,6HKurBV,eACA,gBACA,cACA,qBACA,iBACA,0BAEA,wEACE,cAKJ,oFACE,gBACA,cAOJ,mEACE,gBAEA,mFACE,yBAIJ,kEACE,cAOV,gBACE,kBACA,MLjsBW,KKksBX,QACA,2BACA,WACA,YACA,yBACA,YACA,eACA,YACA,YAEA,qBACE,cACA,WACA,WACA,WLjwBQ,QKkwBR,kBACA,6BACA,oBAEA,yDAEE,WACA,kBACA,OACA,WACA,WACA,WL7wBM,QK8wBN,6BAGF,sCACA,oCAGF,wBACE,yBACA,8DACA,8DAOJ,gBAEE,eACA,MACA,OACA,QACA,SACA,WLnyBM,KKoyBN,QLntBc,IKotBd,YACA,gBACA,gBACA,aACA,sBAKA,4BACE,QLhwBS,KKiwBT,WL/yBM,QKgzBN,gCAGF,0BACE,gBACA,SACA,UACA,YAEA,oCACE,gCAEA,sCACE,aACA,mBACA,QLjxBK,KKkxBL,MLp0BI,QKq0BJ,UL3yBO,KK4yBP,YLlyBa,IKmyBb,qBACA,kBAGF,yDACE,WACA,kBACA,ML5xBK,KK6xBL,QACA,UACA,WACA,+BACA,gCACA,yCACA,mCAGF,sDACE,2CAGF,8CACE,aACA,gBACA,UACA,SACA,WL71BE,QK+1BF,gDACE,cACA,4BACA,MLr2BE,QKs2BF,UL/0BK,KKg1BL,qBAEA,sDACE,WLx2BJ,KKy2BI,MLv3BG,QK43BT,uDACE,cAKN,2BACE,kBACA,WLt3BI,KKu3BJ,6BACA,gBAQJ,UACE,aACA,gBACA,QACA,SACA,UAGA,aACE,kBAGE,oCACE,2BACA,0BAGF,oCACE,mBACA,UACA,wBAIJ,uBACE,kBACA,qBACA,OACA,gBACA,wBACA,oCACA,cLr2Ba,KKs2Bb,4BACA,cACA,kBACA,UACA,4BACA,wBACA,QL11Ba,IK21Bb,gBACA,SAEA,0BACE,SAEA,4BACE,cACA,iBACA,uBACA,qBACA,UL75BK,KK85BL,YLl5BW,IKm5BX,wBAEA,kCACE,2BACA,0BAQZ,UACE,aACA,mBACA,QACA,qBACA,uBACA,gBACA,eACA,iBACA,kBACA,wBAEA,gBACE,0BAIJ,UACE,eAGF,YACE,aACA,mBACA,QACA,qBACA,mBACA,kBACA,mBACA,gBACA,wBACA,4BAEA,kBACE,2BACA,4BA2EJ,yBACE,eAGE,gCACA,+BACA,2BACA,mCACA,mBAEA,0BACE,UACA,eACA,yBAGF,0CAIE,4DACA,gCACA,cAUN,kBACE,aACA,mBACA,QACA,gBACA,kBAEA,iCACE,aACA,mBACA,QAEA,4CACE,WACA,YACA,cAGF,4CACE,YLjlCgB,6HKklChB,eACA,gBACA,cACA,mBAIJ,2BACE,cACA,uBACA,cAGF,+BACE,YLhmCkB,6HKimClB,eACA,gBACA,cACA,qBACA,mBACA,gBACA,YACA,UACA,eACA,0BAEA,qCACE,0BAIJ,mCACE,kBAEA,kDACE,aACA,mBACA,QAGF,yDACE,kBACA,qBACA,QACA,gBACA,wBACA,oCACA,cLvlCa,KKwlCb,4BACA,cACA,kBACA,UACA,4BACA,wBACA,QL5kCa,IK8kCb,2EACE,gBACA,SACA,UAEA,8EACE,SAEA,gFACE,aACA,mBACA,QACA,iBACA,uBACA,qBACA,ULppCG,KKqpCH,YLzoCS,IK0oCT,wBAEA,kFACE,eACA,WACA,kBAGF,sFACE,2BACA,0BAOV,gEACE,mBACA,UACA,wBAMN,yBACE,kBACE,cCptCJ,eACE,gBACA,6BAEA,0BACE,iBACA,cACA,eAIJ,gBACE,aACA,8BACA,mBACA,iBACA,eACA,SJZE,0BIMJ,gBASI,sBACA,uBACA,SACA,gBAKJ,aACE,aACA,sBACA,SAEA,0BACE,YACA,YACA,mBAGF,2BACE,aACA,mBACA,QAEA,wCACE,YNhBgB,6HMiBhB,eACA,gBACA,cACA,qBACA,0BAEA,8CACE,cACA,0BAIJ,6CACE,cACA,eACA,iBAIJ,+BACE,YNrCkB,6HMsClB,eACA,gBACA,cACA,SACA,gBAKJ,cACE,aACA,sBACA,SACA,qBJ3EE,0BIuEJ,cAOI,uBACA,YAIA,0DACE,YACA,YACA,eACA,YN/DgB,6HMgEhB,eACA,gBACA,cACA,gBACA,yBACA,kBACA,eACA,wBACA,gBACA,uQACA,4BACA,sCACA,mBAEA,gEACE,qBAGF,gEACE,aACA,qBACA,uCJ7GJ,0BImFA,0DA8BI,YAKN,8BACE,YN/FkB,6HMgGlB,eACA,gBACA,cACA,SACA,gBJ5HA,0BIsHF,8BASI,gBJjIF,yBIyIA,0BACE,eAIJ,gBAEE,sBACA,uBACA,eACA,SACA,gBAGF,aAEE,QACA,WACA,QAEA,0BAEE,WACA,SAGF,2BACE,QACA,QAEA,wCAEE,eACA,cAGF,6CACE,eACA,cAIJ,+BAEE,eACA,cACA,QAIJ,cAEE,WACA,uBACA,QACA,QAEA,oCACE,WACA,SAEA,0DAEE,WACA,YACA,eACA,kBACA,qBACA,cAIJ,8BAEE,eACA,gBACA,cACA,SJtNF,yBI6NF,gBACE,aACA,sBAGA,6BACE,iBAEA,0CACE,QAGF,2CACE,QAGF,+CACE,QAIJ,8BACE,iBAEA,oDACE,QACA,WAGF,8CACE,SC7PR,MACE,aACA,IPyDW,KOtDX,aACE,0BAGF,aACE,qCAGF,aACE,qCAGF,aACE,qCAGF,aACE,qCAGF,aACE,qCAGF,cACE,sCAIF,WACE,2DAGF,cACE,2DAGF,cACE,2DAIF,YACE,MAGF,aACE,IPKS,IOFX,aACE,IPES,KOCX,aACE,IPDS,KOIX,aACE,IPJS,KExDT,0BKiEA,gBACE,0BAGF,gBACE,sCLxEF,yBK6EA,gBACE,0BAGF,gBACE,sCAMN,YACE,mBAGF,YACE,mBAGF,YACE,mBAGF,YACE,mBAGF,YACE,mBAGF,YACE,mBAGF,eACE,iBAGF,YACE,gBAGF,YACE,gBAGF,YACE,gBAIF,UACE,aACA,2DACA,IP3EW,KEvDT,0BK+HJ,UAMI,sCLvIA,yBKiIJ,UAUI,2BAKJ,iBACE,aACA,2DACA,IP1FW,KEzDT,yBKgJJ,iBAMI,2BAKJ,eACE,aACA,2DACA,IPnGY,KOoGZ,mBL/JE,yBK2JJ,eAOI,sCAKJ,cACE,aACA,qCACA,IPhHW,KE1DT,yBKuKJ,cAMI,2BAKJ,cACE,aACA,8BACA,IP1HY,KO2HZ,kBLpLE,0BKgLJ,cAOI,2BAKJ,aACE,aACA,8BACA,IPnIY,KE5DV,0BK4LJ,aAMI,0BACA,IP1IU,MO6IZ,2BACE,aACA,qCACA,IPhJU,KEzDV,0BKsMF,2BAMI,sCL9MF,yBKwMF,2BAUI,0BACA,mBAMN,cACE,aACA,qCACA,IPnKW,KOoKX,mBL3NE,0BKuNJ,cAOI,0BACA,IPvKS,MO4Kb,kBACE,aACA,2DACA,IPhLW,KEzDT,yBKsOJ,kBAMI,2BAKJ,YACE,aACA,qCACA,IP1LW,KO2LX,kBLnPE,0BK+OJ,YAOI,sCLxPA,yBKiPJ,YAWI,2BAKJ,YACE,aACA,8BACA,IPxMY,KOyMZ,mBLnQE,0BK+PJ,YAOI,0BACA,IP/MS,MOkNX,oBACE,cAEA,sBACE,cL9QF,0BK0QF,oBAQI,eAMN,cACE,aACA,qCACA,IPnOW,KExDT,0BKwRJ,cAMI,2BClSJ,WACE,UR4EoB,OQ3EpB,cACA,eNDE,yBMFJ,WAMI,gBAIF,iBACE,eACA,eAGF,kBACE,gBAGF,gBACE,iBAKJ,SACE,gBNxBE,yBMuBJ,SAII,gBAIF,YACE,eAGF,YACE,gBAGF,gBACE,cAGF,mBACE,iBAMF,iBACE,WRxCM,QQ2CR,kBACE,WR3CO,QQ8CT,oBACE,MRjDI,KQoDN,iBACE,WRxDQ,QQyDR,MRtDI,KQ2DR,gBACE,kBACA,cRVY,KE7DV,yBMqEJ,gBAKI,cRdU,MQiBZ,+BACE,oBACA,mBACA,IR1BS,IQ2BT,iBACA,WRtEO,QQuEP,MRvFW,QQwFX,cRbiB,KQcjB,URtDW,KQuDX,YR1CmB,IQ2CnB,cR/BS,KQkCX,+BACE,URtDY,KQuDZ,YR/Ce,IQgDf,cRrCS,KQsCT,MRvFQ,QEPR,yBM0FF,+BAOI,UR7DU,MQoEd,kCACE,URxEW,KQyEX,MRlGQ,QQmGR,YRzDiB,IQ0DjB,gBACA,cN7GA,yBMwGF,kCAQI,URhFW,MQsFjB,iBACE,kBACA,iBACA,aACA,sBAEA,+BACE,OACA,YRlDY,KQuDhB,cACE,kBACA,gBAIF,eACE,kBACA,UC7IF,KP+BE,oBACA,mBACA,IFwBW,IEvBX,kBACA,cFmCiB,KElCjB,YFUqB,IETrB,UFHe,KEIf,qBACA,WF4DgB,aE3DhB,eACA,YAEA,WACE,2BO5CJ,KAEE,kBACA,gBAGA,QACE,iBACA,UTyBW,KStBb,QACE,kBACA,UTqBa,KSlBf,QACE,kBACA,UTiBW,KSdb,QACE,kBACA,UTaW,KSTb,aACE,WT9BW,QS+BX,MTjBI,KSkBJ,WTPQ,+BSSR,mBACE,2BACA,WTTM,gCSYR,oBACE,2BAIJ,eACE,mBACA,cAEA,qBACE,WTjCK,QSkCL,2BAIJ,YACE,MTzCI,KS0CJ,WT/BQ,+BSiCR,kBACE,2BACA,WTjCM,gCSqCV,UACE,MTnDI,KSoDJ,WTzCQ,+BS2CR,gBACE,2BACA,WT3CM,gCS+CV,aACE,WTtEW,QSuEX,MT9DI,KSgEJ,mBACE,kDACA,2BAIJ,YACE,WTjFY,QSkFZ,MTxEI,KS0EJ,kBACE,8BACA,2BAIJ,WACE,yBACA,MThGW,QSiGX,+BAEA,iBACE,6BACA,aTrGS,QSyGb,aACE,yBACA,MThGQ,QSiGR,yBAEA,mBACE,aT/GS,QSgHT,MThHS,QSiHT,2BAKJ,4BAEE,WACA,mBACA,oBAGF,aACE,oBACA,oBAEA,oBACE,WACA,kBACA,WACA,YPrDJ,kBACA,QACA,SACA,gCOoDI,sBACA,kBACA,+BACA,mCAKJ,UACE,oBACA,mBACA,uBACA,WACA,YACA,UACA,cT1EmB,IS4EnB,iBACE,WACA,YAGF,iBACE,WACA,YAKJ,WACE,WACA,uBAIF,WACE,oBACA,SAEA,gBACE,gBAEA,4BACE,uBT1GW,KS2GX,0BT3GW,KS8Gb,2BACE,wBT/GW,KSgHX,2BThHW,KSuHnB,KACE,eACA,OTnIW,KSoIX,MTpIW,KSqIX,WACA,YAEA,YACA,cT3HqB,IS4HrB,MT1LM,KS2LN,UTlKa,KSmKb,eACA,WTjLU,gCSkLV,WTvGgB,aSwGhB,QTjHc,IExEd,aACA,uBACA,mBO0LA,WACE,qBACA,WTvLQ,gCSyLR,sBACE,UACA,4BAIJ,gBACE,kBACA,WACA,QACA,2BACA,WTpNQ,QSqNR,MTlNI,KSmNJ,iBACA,cT3Je,IS4Jf,UThMW,KSiMX,mBACA,UACA,WTjIc,aSkId,oBAMF,iBP5MA,oBACA,mBACA,IFwBW,IEvBX,kBACA,cFmCiB,KElCjB,YFUqB,IETrB,UFHe,KEIf,qBACA,WF4DgB,aE3DhB,eACA,YAEA,uBACE,2BO+LF,iBAEE,WTjOI,KSkOJ,MThPW,QSiPX,kBACA,UT7MW,KS8MX,WTzNQ,gCS2NR,uBACE,2BACA,WT5NM,gCSgOV,mBP1NA,oBACA,mBACA,IFwBW,IEvBX,kBACA,cFmCiB,KElCjB,YFUqB,IETrB,UFHe,KEIf,qBACA,WF4DgB,aE3DhB,eACA,YAEA,yBACE,2BO6MF,mBAEE,yBACA,MThPI,KSiPJ,sBACA,kBACA,UT5NW,KS8NX,yBACE,8BACA,2BAUN,0GAME,kBACA,cT/MiB,ISgNjB,UTnPe,KSoPf,YTzOmB,IS0OnB,qBACA,WTrLgB,aSsLhB,oBACA,mBACA,uBACA,ITnOW,ISoOX,eACA,YAEA,yBAnBF,0GAoBI,kBACA,UTjQW,MSoQb,sHACE,eAKJ,oCAGE,MTlSM,KSoSN,gDACE,2BACA,WT3RQ,+BS8RV,kDACE,wBAKJ,sBACE,WThTM,KSiTN,MTpTU,QSqTV,yBAEA,4BACE,WTpTM,QSqTN,aTpUW,QSqUX,MTrUW,QSwUb,6BACE,sBAKJ,iBACE,WT/Ua,QSgVb,MTlUM,KSoUN,uBACE,2BACA,WT3TQ,+BS4TR,WTpVa,QSuVf,wBACE,wBAKJ,mBACE,WT3Vc,QS4Vd,MTlVM,KSoVN,yBACE,2BACA,WT3UQ,+BS4UR,8BAGF,0BACE,wBAMF,qBACE,WTlWI,KSmWJ,MTtWQ,QSuWR,yBAEA,2BACE,WTtWI,QSuWJ,aTtXS,QSuXT,MTvXS,QS2Xb,qBAEE,MT/WI,KSiXJ,2BACE,2BACA,WTxWM,+BS4WV,uBACE,WTlYY,QSmYZ,MTzXI,KS2XJ,6BACE,2BACA,WTlXM,+BSwXZ,UACE,iBACA,6BACA,MTpZa,QSqZb,mCACA,cThViB,ISiVjB,UTrXa,KSsXb,YTzWmB,IS0WnB,eACA,WTrTgB,aSsThB,oBACA,mBACA,ITnWW,ISqWX,gBACE,6BACA,aTjaW,QSoab,YACE,eAIJ,YACE,iBAEA,MT9ZM,KS+ZN,YACA,cTvWiB,ISwWjB,UT5Ya,KS6Yb,YTjYmB,ISkYnB,eACA,WT7UgB,aS8UhB,oBACA,mBACA,IT3XW,IS6XX,kBACE,2BACA,WThaQ,+BSmaV,cACE,eAIJ,YACE,iBACA,WTrbM,KSsbN,MTxbU,QSybV,yBACA,cT/XiB,ISgYjB,UTpaa,KSqab,YTzZmB,IS0ZnB,eACA,WTrWgB,aSsWhB,oBACA,mBACA,ITnZW,ISqZX,kBACE,aThdW,QSidX,MTjdW,QSodb,cACE,eAQJ,kBACE,cACA,oBACA,mBACA,uBACA,gBACA,YACA,kBACA,YACA,cT9ZiB,KS+ZjB,UTjca,KSkcb,YTvbiB,ISwbjB,MT3dM,KS4dN,eACA,WTtYgB,aSuYhB,mBAGA,6BACE,mBAEA,mCACE,iDACA,2BACA,WT5dM,+BS+dR,oCACE,wBAKJ,2BACE,mBAEA,iCACE,WTjgBW,QSkgBX,2BACA,WT3eM,+BS8eR,kCACE,wBAGF,oCACE,WT3fQ,QS4fR,mBACA,eACA,gBAIJ,yBAtDF,kBAuDI,WACA,eACA,YACA,UTnfa,MS2fjB,YACE,oBACA,mBACA,uBACA,QACA,YACA,iBACA,yBACA,MTzhBM,KS0hBN,YACA,cTleiB,ISmejB,eACA,YT3fqB,IS4frB,eACA,qCAEA,kBACE,yBP1iBA,0BOyhBJ,YAqBI,eACA,aCnjBJ,cACE,oBACA,mBACA,uBACA,iBACA,cVoEmB,KUnEnB,UV0Ba,KUzBb,YVuCqB,IUtCrB,mBACA,WV0FgB,aUvFhB,6BACE,gCACA,MVZY,QUed,+BACE,gCACA,MVhBW,QUmBb,4BACE,6BACA,MV1BW,QU6Bb,8BACE,gCACA,MVnBQ,QUsBV,gCACE,+BACA,kCAGF,4BACE,gCACA,MVrCY,QUwCd,6BACE,gCACA,MVzCW,QU8Cf,qBACE,oBACA,mBACA,uBACA,iBACA,cVmBmB,KUlBnB,UVvBa,KUwBb,YVVqB,IUWrB,cVCW,gBAyCK,aUvChB,oCACE,gCACA,MV5DY,QU+Dd,sCACE,gCACA,MVhEW,QUmEb,mCACE,6BACA,MV1EW,QU6Eb,qCACE,gCACA,MVnEQ,QUsEV,uCACE,+BACA,kCAKJ,UACE,gBACA,eACA,cVtBiB,IU0BnB,UACE,iBACA,UV/Da,KUgEb,cV5BiB,IUgCnB,eACE,yBACA,8BAEA,8BACE,MVxGY,QUyGZ,aVzGY,QU4Gd,gCACE,MV5GW,QU6GX,aV7GW,QUgHb,6BACE,MVtHW,QUuHX,aVvHW,QU0Hb,+BACE,MV/GQ,QUgHR,aVhHQ,QUsHV,cACE,aVzES,IU0ET,eAKJ,oBACE,oBACA,mBACA,uBACA,eACA,YACA,cACA,WV5Ic,QU6Id,MVnIM,KUoIN,cVtEqB,IUuErB,eACA,YVnGiB,IUoGjB,cAEA,kCACE,WVxJW,QU2Jb,kCACE,WVvJW,QU0Jb,kCACE,WV7JY,QU8JZ,MVtJQ,QUyJV,iCACE,WVjKY,QWFhB,MTkDE,WFtCM,KEuCN,cFqBkB,KEpBlB,QFsDa,KErDb,WF8CgB,aE5ChB,YAEE,WFjCQ,gCWxBZ,MAEE,kBACA,gBAIF,UT2CE,WFtCM,KEuCN,cFqBkB,KEpBlB,QFsDa,KErDb,WF8CgB,aE5ChB,gBAEE,WFjCQ,gCWjBZ,UAEE,kBACA,yBAEA,gBACE,aXfW,QWoBb,kBACE,aXlBY,QWsBd,cACE,aXrBW,QWyBb,qBACE,kBACA,UACA,WACA,iBACA,WXhCY,QWiCZ,MXzBQ,QW0BR,cXqCgB,KWpChB,UXJW,KWKX,YXUe,IWTf,yBAEA,yBACE,WXtCS,QWuCT,MX9BE,KWiCJ,0BACE,WX1CU,QW2CV,MXnCE,KWwCN,oBACE,WACA,YACA,mBTrCF,aACA,uBACA,mBSqCE,eACA,WX5CO,QW6CP,MX7DW,QW8DX,cXWe,KWVf,WXsCc,aWnChB,0BACE,qBAEA,MXvDI,KW2DN,oBACE,UXpCW,KWqCX,YX1Be,IW2Bf,cXhBS,KWiBT,MXlEQ,QWqEV,2BACE,MXrEQ,QWsER,YX5BiB,IW6BjB,cXtBS,KWuBT,UXjDW,KWqDb,wBACE,aACA,uBACA,IXhCS,IWiCT,cX/BS,KWgCT,eAEA,qCACE,gBACA,WXlFI,QWmFJ,MXtFM,QWuFN,cXzBc,KW0Bd,UXlES,KWmET,YXtDe,IW2DnB,oBACE,oBACA,mBACA,IXnDS,IWoDT,MX9GW,QW+GX,YX/DmB,IWgEnB,UX7EW,KW8EX,WXZc,aWcd,0BACE,IXzDO,IW0DP,MXpHW,QW0HjB,iBTvEE,WFtCM,KEuCN,cFqBkB,KEpBlB,QFsDa,KErDb,WF8CgB,aE5ChB,uBAEE,WFjCQ,gCWiGZ,iBAEE,kBAEA,4BACE,WACA,YACA,mBT9GF,aACA,uBACA,mBS8GE,UX7FW,KW+FX,MXxHI,KWyHJ,cX9De,KWiEjB,oBACE,UXrGW,KWsGX,YX3Fe,IW4Ff,cXjFS,KWkFT,MXnIQ,QWsIV,mBACE,MXtIQ,QWuIR,YX7FiB,IW8FjB,cXvFS,KWwFT,UXlHW,KWsHb,8BACE,aACA,sBACA,mBACA,YXhGS,KWiGT,6BAEA,4CACE,UX1HS,KW2HT,YXhHkB,IWiHlB,MXnKS,QWsKX,4CACE,UXrIS,KWsIT,MX5JM,QW6JN,YX1He,IWgIrB,cT3HE,WFtCM,KEuCN,cFqBkB,KEpBlB,QFsDa,KErDb,WF8CgB,aE5ChB,oBAEE,WFjCQ,gCWqJZ,cAEE,aACA,uBACA,IXtHW,KWwHX,4BACE,cACA,WACA,YTpKF,aACA,uBACA,mBSqKE,MX7KI,KW8KJ,cXpHe,KWqHf,UXtJW,KWyJb,+BACE,OAEA,kCACE,UX/JS,KWgKT,YXrJiB,IWsJjB,cX3IO,IW4IP,MX5LM,QW+LR,iCACE,MX/LM,QWgMN,YXtJe,IWuJf,UX1KS,KWgLf,kBT/JE,WFtCM,KEuCN,cFqBkB,KEpBlB,QFsDa,KErDb,WF8CgB,aE5ChB,wBAEE,WFjCQ,gCWyLZ,kBAEE,kBAEA,0BACE,YACA,kBACA,IX9JS,KW+JT,KX/JS,KWgKT,eACA,MX7NW,QW8NX,WACA,YX9Ke,IWiLjB,uCACE,kBACA,UACA,UXjMa,KWkMb,YX/KgB,IWgLhB,MX5NQ,QW6NR,cX3KS,KW4KT,kBAGF,sCACE,aACA,mBACA,IXnLS,KWqLT,qDACE,WACA,YACA,cXxKiB,IWyKjB,iBAIA,gEACE,YX1Me,IW2Mf,MXhPI,QWiPJ,kBAGF,iEACE,UX7NO,KW8NP,MXrPI,QW4PZ,cTpNE,WFtCM,KEuCN,cFqBkB,KEpBlB,QFsDa,KErDb,WF8CgB,aE5ChB,oBAEE,WFjCQ,gCW8OZ,cAEE,kBACA,kBAEA,uBACE,yBACA,sBAEA,sCACE,kBACA,UACA,SACA,2BACA,iBAEA,MX1QE,KW2QF,cX9Me,KW+Mf,UXxPS,KWyPT,YX1Oa,IW2Ob,yBAIJ,8BACE,eXpOS,KWqOT,gCACA,cXtOS,KWwOT,iCACE,UX/PS,KWgQT,YXtPa,IWuPb,cX7OO,IW8OP,MX9RM,QWiSR,qCACE,aACA,qBACA,uBACA,IXtPO,IWwPP,+CACE,UX7QO,KW8QP,MXxSI,QW2SN,6CACE,UX9QQ,KW+QR,YXvQgB,IW0QlB,6CACE,UXzRS,KW0RT,MXlTI,QWuTV,gCACE,gBACA,cXxQS,KW0QT,mCACE,cACA,MX7TM,QW8TN,UXvSS,KWwST,aACA,mBACA,IXlRO,IWoRP,2CACE,YACA,MX5UO,QW6UP,YXjSW,IWoSb,4CACE,WAEA,oDACE,YACA,MX7UG,QWmVX,2BACE,gBChVJ,wHACE,UZ6DoB,OY5DpB,cACA,eVhBE,yBUaJ,wHAMI,wBAOJ,kFACE,iBAxBgB,QAyBhB,cZwCiB,KYvCjB,kBV7BE,yBU0BJ,kFAMI,QZyBS,MYlBb,8DACE,iBArCgB,QAsChB,cZ2BiB,KY1BjB,kBACA,mBV3CE,yBUuCJ,8DASI,+BACA,gBACA,oBACA,gBACA,mCAGF,wGACE,YZ9BkB,6HY+BlB,eACA,YZbe,IYcf,MZpDQ,QYqDR,SV5DA,yBUuDF,wGAQI,eACA,WACA,kBACA,gBAIJ,0BACE,gBACA,mBACA,gBACA,gCACA,cZjBS,KYmBT,6BACE,eACA,YZlCa,IYmCb,cACA,SAGF,0CACE,aVrFF,yBUsEF,0BAmBI,aACA,oBACA,gBACA,kCAEA,6BACE,eACA,WACA,gBAKN,iHACE,eACA,YZ9DkB,IY+DlB,cVzGA,yBUsGF,iHAMI,eACA,cACA,eACA,iBAQN,qDACE,oBACA,mBACA,uBACA,iBACA,mBACA,YZnGoB,6HYoGpB,eACA,YZlFiB,IYmFjB,MZtHM,KYuHN,mBAEA,kGACE,yBAGF,oMAEE,iBArIkB,QAwIpB,qGACE,yBAGF,0MAEE,yBAKA,yIACE,yBACA,cAGF,kRAEE,yBACA,cAGF,wRAEE,yBACA,cAQN,2CACE,oBACA,mBACA,IZzHW,IY0HX,iBACA,cZ7GiB,KY8GjB,UZpJa,KYqJb,YZxIqB,IY0IrB,mEACE,UACA,WACA,cZjHmB,IYkHnB,8BAGF,uEACE,yBACA,cAGF,2EACE,yBACA,cAOJ,sDACE,YACA,aAEA,8EACE,WACA,YVjNA,yBU+MF,8EAKI,WACA,aAIJ,8EACE,YACA,aAIJ,kEACE,WACA,YACA,iBACA,cZ/JiB,KYgKjB,yBAGF,oFACE,WACA,YACA,aACA,mBACA,uBACA,yBACA,cZ1KiB,KY2KjB,cACA,eAEA,6EACE,eAOJ,kEACE,aACA,sBACA,mBACA,uBACA,kBACA,kBAEA,kHACE,eACA,cZ3MS,KY4MT,WAGF,8EACE,YZ/OkB,6HYgPlB,UZvOW,KYwOX,YZ9Ne,IY+Nf,MZrQQ,QYsQR,gBAGF,0EACE,YZvPkB,6HYwPlB,UZlPa,KYmPb,MA7QoB,QA8QpB,SAOJ,+CACE,oBACA,mBACA,uBACA,YACA,YACA,YACA,cZ9NiB,KY+NjB,YZzQoB,6HY0QpB,eACA,YZxPiB,IYyPjB,eACA,qBACA,wDAEA,iEACE,2BAGF,oEACE,wBAGF,mFACE,iBA/SkB,QAgTlB,MZ1SI,KY4SJ,qGACE,yBAIJ,yFACE,yBACA,cAEA,2GACE,yBAIJ,gFACE,iBAhUoB,QAiUpB,MZ5TI,KY8TJ,kGACE,yBVzUF,yBU2RJ,+CAmDI,WACA,gBACA,YACA,UZjTa,MYwTjB,2BACE,oBACA,mBACA,IZrSW,IYsSX,iBACA,WZnVM,KYoVN,yBACA,cZ7RiB,IY8RjB,UZjUa,KYkUb,MZ1VU,QY2VV,eACA,WZlQgB,aYoQhB,mCACE,WACA,YAGF,uCACE,WZ/WW,QYgXX,aZhXW,QYiXX,MZnWI,KYsWN,yBAxBF,2BAyBI,mBACA,wBAOJ,uCACE,WZhXM,KYiXN,cZvTiB,KYwTjB,QZ/TY,KYgUZ,cZpUW,KYqUX,WZzWU,+BY2WV,yBAPF,uCAQI,QZvUS,MY0UX,qEACE,UZlWW,KYmWX,YZzVe,IY0Vf,MZhYQ,QYiYR,cZ/US,KYgVT,eZjVS,KYwVb,iCACE,aACA,qCACA,IZ1VW,KY4VX,yBALF,iCAMI,2BAIJ,iCACE,aACA,sBACA,IZtWW,IYwWX,uDACE,iBAGF,6DACE,UZrYW,KYsYX,YZzXmB,IY0XnB,MZ9ZQ,QYiaV,6DACE,UZ1Ya,KY2Yb,MZpaQ,QYqaR,sBAOJ,yBACE,aACA,eACA,IZ/XW,IYkYb,uBACE,qBACA,iBACA,iBZhbS,QYibT,MZhce,QYicf,cZ5XiB,IY6XjB,UZhaa,KYiab,YZraiB,oCY2anB,yCACE,qBACA,iBACA,iBZ9bQ,QY+bR,yBACA,cZzYiB,IY0YjB,YZjbiB,oCYkbjB,UZ9aa,KY+ab,MZjde,QYudjB,+BACE,aACA,mBACA,IZ/ZW,KYiaX,yBALF,+BAMI,sBACA,wBAOJ,kCACE,aACA,sBACA,IZ9aW,IYibb,gCACE,aACA,mBACA,IZnbW,KYobX,kBACA,WZneM,KYoeN,mBAEA,4CACE,aZrfW,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CA,gBAKJ,cACE,uBACA,aACA,SACA,uBACA,gBAEA,+BACE,yBAGF,mCACE,8BAIF,yBAhBF,cAiBI,uBACA,mBACA,SAEA,oFAGE,OACA,YACA,YACA,gBAKN,iBACE,WACA,kBACA,yBACA,cdhLiB,IciLjB,UdrNa,KcsNb,Yd3NoB,6Hc4NpB,wBACA,sBAEA,uBACE,aACA,ad/PW,QcgQX,uCAGF,8BACE,MdvPS,Qc2PX,uBACE,adrQY,QcuQZ,6BACE,adxQU,QcyQV,0CAOJ,8BACE,gBAGF,yBACE,kBACA,kBAEA,qCACE,WACA,YACA,mBZ3QJ,aACA,uBACA,mBY2QI,UdzPU,Kc0PV,cdtNiB,IcwNjB,mDACE,gCACA,MdjSO,QcoST,mDACE,+BACA,MdxSQ,Qc2SV,kDACE,gCACA,Md5SQ,Qc+SV,gDACE,6BACA,MdrTO,QcyTX,sCACE,UdnRS,KcoRT,Yd3QiB,Ic4QjB,cdjQO,IckQP,MdlTM,QcqTR,wCACE,MdrTM,QcsTN,Ud9RW,KelCjB,YACE,aACA,mBACA,IfsDW,IerDX,eACA,Uf4Ba,Ke1Bb,6BACE,aACA,mBACA,If+CS,Ie9CT,MfDQ,QeGR,+BACE,MfJM,QeKN,WfoFY,aelFZ,qCACE,MfpBO,QewBX,oCACE,MfdM,QeeN,YfqBe,IelBjB,qDACE,YACA,MflBO,QemBP,Yf2BO,IerBb,UACE,aACA,IfkBW,IejBX,gCACA,cfoBW,KelBX,oBACE,kBAGF,oBACE,aACA,mBACA,IfQS,IePT,kBACA,MfzCQ,Qe0CR,YfPiB,IeQjB,sCACA,mBACA,Wf4Cc,ae1Cd,0BACE,MfjDM,QeoDR,2BACE,MfhES,QeiET,oBfjES,QeoEX,2BACE,gBACA,WfvDI,QewDJ,Mf3DM,Qe4DN,cfGe,KeFf,eACA,Yf1BiB,IegCvB,WACE,aACA,IfvBW,IewBX,QfzBW,Ie0BX,WfrEQ,QesER,cfbiB,KeejB,qBACE,iBACA,Mf7EQ,Qe8ER,Yf3CiB,Ie4CjB,cfpBe,IeqBf,WfSc,aePd,2BACE,MfpFM,QeqFN,8BAGF,4BACE,WftFE,KeuFF,MfrGS,QesGT,Wf9EM,0BeqFV,0BACE,cfhDS,KekDT,qCACE,Uf/ES,KegFT,YflEiB,IemEjB,yBACA,MfxGM,QeyGN,iBACA,qBAIJ,uBACE,gBAEA,iCACE,cfpEO,IeuET,iCACE,aACA,mBACA,IfxEO,KeyEP,iBACA,Mf3HM,Qe4HN,cfhEa,IeiEb,WfnCY,aeqCZ,2CACE,WACA,YbxHN,aACA,uBACA,mBawHM,UfzGO,Ke0GP,MfnII,QesIN,uCACE,WfpIE,QeqIF,MfpJO,QesJP,iDACE,MfvJK,Qe2JT,wCACE,Wf5IG,Qe6IH,Mf7JO,Qe8JP,Yf/Ga,IeiHb,kDACE,MfjKK,QeuKX,oCACE,YfzGQ,Ke0GR,Wf/GO,IegHP,gBAEA,8CACE,Uf1IO,Ke2IP,iBAOR,YACE,aACA,mBACA,If9HW,IekIP,2CACE,WACA,mBACA,oBAKF,yCACE,WfrMO,QesMP,MfxLA,KeyLA,afvMO,Qe4Mb,uBACE,aACA,mBACA,uBACA,eACA,YACA,cACA,WfrMI,KesMJ,yBACA,cf9Ie,Ie+If,Mf3MQ,Qe4MR,UfpLW,KeqLX,YfzKiB,Ie0KjB,WfpHc,aesHd,6BACE,Wf7MI,Qe8MJ,af7NS,Qe8NT,Mf9NS,QeiOX,kEAEE,Uf9LS,KekMb,uBACE,cACA,Mf7NQ,QgBXZ,UACE,eACA,WhBWM,KiBbR,WACE,kBACA,gBAEA,gBCKA,qCACE,gBACA,SAGF,mCACE,MlBZY,QkBaZ,UlBiBW,KkBhBX,WlBwCS,IkBvCT,aACA,8BAEA,wCACE,cAMN,sBACE,KACE,UACA,4BAEF,GACE,UACA,yBC/BJ,YACE,aACA,mBACA,InBoDW,KmBlDX,0BACE,MAOJ,WACE,oBACA,mBACA,kBACA,MnBXU,QmBYV,qBACA,yBACA,cnBkDmB,KmBjDnB,YnBsBqB,ImBrBrB,UnBQa,KmBPb,WnByEgB,amBtEhB,eACE,YACA,oBACA,mBACA,uBACA,QACA,iBACA,cACA,qBACA,YACA,kBACA,YnBZkB,6HmBalB,YnBKmB,ImBJnB,eACA,yBACA,WnBuDc,amBrDd,0BACE,WACA,YACA,cAQN,uBACE,kBAEA,yCACE,aACA,mBACA,InBNS,ImBOT,kBACA,WnBnDO,QmBoDP,+BACA,cnBMiB,KmBLjB,eACA,WnB8Bc,amB7Bd,YnB1CkB,6HmB2ClB,UnBtCW,KmBwCX,oDACE,MnBjEM,QmBkEN,YnB7BiB,ImBgCnB,2CACE,MnBrEM,QmBsEN,eACA,mCAGF,+CACE,WnBzEE,KmB0EF,anBxFS,QmByFT,WnBhEM,+BmBkEN,0DACE,MnB5FO,QmB+FT,iDACE,MnBhGO,QmBsGX,kDACE,yBAGF,qDACE,mBACA,UACA,wBAIJ,8CACE,kBACA,sBACA,QACA,YACA,WnBxGI,KmByGJ,yBACA,cnBhDe,KmBiDf,WnB9FQ,gCmB+FR,kBACA,UACA,4BACA,4BACA,QnBpCe,ImBqCf,gBAGF,uCACE,QnBtES,KmBwET,MnBvHI,KmBwHJ,kBAEA,sDACE,iBACA,UnBrGS,KmBsGT,YnB1Fa,ImB6Ff,qDACE,UnB7GS,KmB8GT,WAIJ,0CACE,gBACA,SACA,cAEA,6CACE,SAEA,+CACE,aACA,mBACA,SACA,kBACA,MnBtJI,QmBuJJ,qBACA,UnBhIO,KmBiIP,WnB9DU,cmBgEV,iDACE,WACA,MnB5JE,QmB6JF,eAGF,qDACE,WnB7JC,QmB8JD,MnB9KK,QmBgLL,uDACE,MnBjLG,QmBwLb,uCACE,sBACA,6BAEA,mDACE,aACA,mBACA,uBACA,InBrIO,ImBsIP,WACA,aACA,WnBpLI,QmBqLJ,MnBzLM,QmB0LN,qBACA,cnB/Ha,ImBgIb,YnBvJiB,ImBwJjB,UnBrKS,KmBsKT,WnBpGY,amBsGZ,qDACE,eAGF,yDACE,WnB5MQ,QmB6MR,MnBnMA,KmBoMA,2BACA,WnB1LI,+BmBmMZ,kBACE,QnBhKW,KmBkKX,2CAEA,kCACE,aACA,mBACA,InBxKS,KmB0KT,kDACE,WACA,YACA,aACA,mBACA,uBACA,8BACA,cnBjKiB,ImBkKjB,MnBhOE,KmBkOF,oDACE,eAIJ,gDACE,OACA,MnBzOE,KmB2OF,8DACE,iBACA,UnBtNO,KmBuNP,YnB3MW,ImB8Mb,kEACE,UnB9NO,KmB+NP,WASR,mBACE,aACA,mBACA,uBACA,InBnNW,ImBoNX,WACA,kBACA,WnB7Qc,QmB8Qd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,sBACA,YACA,yBACA,eACA,cAEA,iCACE,cACA,eAGF,0BACE,aAIJ,gCACE,kBACA,WACA,QACA,2BACA,WACA,YACA,UACA,gBACA,YACA,eACA,aACA,mBACA,uBAEA,oCACE,WACA,YACA,cAMN,gBACE,sBACA,oBACA,SACA,UACA,gBAEA,6BACE,QACA,gBACA,eACA,MpBvsBE,KoBwsBF,mBACA,kCACA,gBAEA,oCACE,cACA,YpB/qBW,IoBgrBX,eAIJ,8BACE,QAKJ,YACE,yBACA,gBAGA,+BACE,aAIF,6BACE,aACA,sBAIF,4BACE,aACA,mBACA,mBACA,8BACA,YACA,cACA,oCACA,gCAEA,kCACE,yCAIF,8CACE,aAIF,6CACE,OACA,2BACA,UACA,eACA,YpBtuBc,IoBuuBd,cAEA,qDACE,aAGF,gEACE,aACA,mBACA,QACA,cACA,qBAEA,+EACE,aAGF,2EACE,oBACA,mBACA,cACA,gBAEA,+EACE,WACA,YAOR,iDACE,cACA,WACA,UACA,eACA,YpB5wBc,IoB6wBd,cACA,yBAEA,yDACE,aAOR,YACE,SACA,eAEA,4BACE,WACA,YAEA,8DACE,eACA,WACA,YAIJ,gCACE,SAGF,+BACE,eACA,YACA,eACA,cACA,YpBhzBgB,IoBkzBhB,sCACE,YpBhzBW,IoBizBX,eC/1BR,eACE,mBACA,crBmEiB,KqBlEjB,gBACA,crBwDY,KE3DV,yBmBDJ,eAOI,gBACA,iBAGF,yBACE,gCACA,WrBqFc,aqBnFd,oCACE,mBAME,wDACE,yBAIJ,4CACE,cACA,6BAMN,6BACE,aACA,mBACA,8BACA,kBACA,eACA,YrBKe,IqBJf,cACA,eACA,WrBsDc,aqBrDd,SACA,gBAEA,0BAbF,6BAcI,kBACA,eACA,IrBOO,MqBJT,mCACE,8BAGF,gDACE,OACA,YrBTe,IqBajB,uCACE,cACA,aACA,mBACA,uBACA,WACA,YACA,cACA,8BAEA,2CACE,WACA,WAMN,2BACE,aACA,mBACA,uBACA,gBnBrFA,yBmBiFF,2BAOI,iBAGF,0BAVF,2BAWI,wBAGF,+CACE,eACA,YrBvDgB,IqBwDhB,cACA,sBACA,QrB3CO,KqB4CP,YrBlDc,IqBoDd,0BARF,+CASI,gBAIF,iDACE,crBrDK,KqBuDL,4DACE,gBAIJ,oGACE,aACA,arB7DK,KqBgEP,kDACE,crBpEK,IqBuEP,iDACE,MrBlIO,QqBmIP,0BAEA,uDACE,MrBrIO,QqByIX,oDACE,WrB5HE,QqB6HF,gBACA,crBvEW,IqBwEX,YrB/GW,oCqBgHX,eACA,MrBhJO,QqBmJT,mDACE,WrBrIE,QqBsIF,yBACA,crB/EW,IqBgFX,QrB3FK,KqB4FL,gBACA,cAEA,wDACE,yBACA,UACA,MrBnJE,QqB2JZ,UACE,+BACA,WrBzJQ,QqB0JR,kBAEA,yBALF,UAMI,mBAIJ,eACE,UrBlGoB,OqBmGpB,cAGF,YACE,crBtHY,KqBwHZ,yBAHF,YAII,crB1HU,MqB6HZ,wBACE,UrBpJY,KqBqJZ,YrB7Ie,IqB8If,MrBpLQ,QqBqLR,crBpIS,KqBsIT,6BACA,sCACA,qBAEA,yBAVF,wBAWI,UrB/JU,MqBmKd,8BACE,UrBxKa,KqByKb,MrBjMQ,QqBkMR,YrBxJiB,IqB0JjB,yBALF,8BAMI,UrB9KS,MqBoLf,qBACE,KACE,UACA,4BAEF,GACE,UACA,yBAKJ,WACE,WrBtNM,KqBuNN,crB7JiB,KqB8JjB,WrB9MU,0BqB+MV,gBAEA,2BACE,gCAEA,sCACE,mBAIA,oDACE,8BACA,MrBnPO,QqBqPP,oEACE,yBAMR,6BACE,aACA,mBACA,8BACA,kBACA,UrB7Na,KqB8Nb,YrBnNiB,IqBoNjB,MrBxPQ,QqByPR,eACA,WrBhKc,aqBkKd,yBAXF,6BAYI,kBACA,UrBtOS,MqByOX,mCACE,8BAGF,8CACE,OAGF,6CACE,cACA,eACA,MrB3QM,QqB4QN,WrBnLY,aqBuLhB,8BACE,aACA,kBACA,UrB5PW,KqB6PX,MrBpRQ,QqBqRR,YrB3OiB,IqB4OjB,6BAEA,yBARF,8BASI,mBAGF,mCACE,cACA,6BCzSN,mBACE,mBACA,kBACA,WACA,aACA,aACA,mBACA,uBACA,gBAEA,2BACE,WACA,kBACA,MACA,OACA,QACA,SACA,0LACA,0BACA,WAGF,gCACE,kBACA,QACA,SACA,gCACA,aACA,WACA,UAGF,sBACE,kBACA,UtBMY,KsBLZ,YtBae,IsBZf,MtB1BQ,QsB2BR,SACA,UACA,sBpBpCA,yBoBHJ,mBA4CI,aACA,ctBgBS,KsBdT,gCACE,aAGF,sBACE,UtBdS,MuBrCf,OACE,aACA,uBACA,IvBuDW,KuBtDX,kBACA,cvBgEiB,IuB/DjB,cvBsDW,KuBpDX,mBACE,eACA,cAGF,sBACE,OACA,gBAEA,6BACE,YvB4BiB,IuBvBrB,qBACE,+BACA,qCACA,cAIF,mBACE,gCACA,sCACA,yBACA,mBAEA,uBACE,cAGF,wBACE,OACA,UvBTS,KuBUT,YvBEe,IuBGnB,qBACE,gCACA,sCACA,6CAIF,kBACE,6BACA,mCACA,MvB3Da,QwBSjB,YACE,aACA,mBACA,uBACA,IAXe,KAcf,6FAIE,aACA,mBACA,uBACA,MApBmB,KAqBnB,OArBmB,KAsBnB,MApBqB,QAqBrB,qBACA,eACA,WxByEc,cwBvEd,iLACE,MxBhCS,QwBmCX,iIACE,MA5BqB,QA6BrB,mBACA,oBAIF,6GACE,MAvCiB,KAwCjB,OAxCiB,KAyCjB,cAKJ,sBACE,aACA,mBACA,uBACA,eACA,YACA,YxB1BkB,6HwB2BlB,UApDmB,KAqDnB,YxBZkB,IwBalB,MApDuB,QAqDvB,qBACA,kBACA,cACA,eACA,WxBsCc,cwBpCd,+CACE,MxBnES,QwBuEX,mCACE,YxBvBa,IwBwBb,MAnEmB,QAoEnB,eAKJ,mBACE,kBACA,UACA,WACA,UACA,YACA,gBACA,sBACA,mBACA,SAKJ,oBACE,aACA,uBACA,eACA,WxBpCW,KyB1Db,sBACE,yBACA,YACA,WACA,aACA,mBAGF,YACE,aACA,mBACA,SACA,UzBgEoB,OyB/DpB,cACA,eACA,WAEA,yBATF,YAUI,SACA,gBAIF,iBACE,aACA,mBACA,uBACA,WACA,YACA,cAEA,qBACE,WACA,YACA,aACA,WzBgEY,cyB7Dd,2BACE,KzB1CS,QyB+Cb,sBACE,aACA,mBACA,uBACA,WACA,YACA,cAEA,0BACE,YACA,YACA,aAGF,yBAdF,sBAeI,WACA,YAEA,0BACE,UACA,aAMN,iBACE,YzB5CkB,6HyB6ClB,eACA,YzB9BkB,IyB+BlB,cACA,cACA,qBACA,mBACA,WzBqBc,cyBnBd,uBACE,MzBpFS,QyBwFX,yBACE,YzBxCa,IyByCb,cAEA,+BACE,cACA,eAIJ,yBAzBF,iBA0BI,gBAMN,gBACE,yBACA,YACA,WAEA,yBALF,gBAMI,aAGF,iCACE,aACA,mBACA,SACA,UzBvCkB,OyBwClB,cACA,eACA,YACA,gBAEA,yBAVF,iCAWI,SACA,gBAIJ,sCACE,aACA,mBACA,SAEA,yBALF,sCAMI,UAIF,8DACE,WACA,cACA,YACA,YACA,yQACA,4BACA,2BACA,wBAEA,yBAVF,8DAWI,UACA,aAIJ,wCACE,YzB/HgB,6HyBgIhB,eACA,YzBjHgB,IyBkHhB,cACA,cACA,qBACA,mBACA,WzB9DY,cyBgEZ,8CACE,MzBvKO,QyB0KT,yBAdF,wCAeI,gBAKJ,mDACE,YzBhIa,IyBiIb,cACA,oBAIF,oDACE,aACA,mBACA,uBACA,WACA,YACA,YAEA,4DACE,WACA,cACA,WACA,YACA,+aACA,4BACA,2BACA,wBACA,WzBlGU,cyBqGZ,kEACE,+aC9MR,iBACE,eACA,kBACA,yBACA,yBACA,kBAEA,0BACE,aACA,uBACA,QAGF,uBACE,cACA,eACA,WACA,YAGF,iCACE,OAGF,wBACE,SACA,eACA,cACA,gBAGF,8BACE,iBACA,eACA,cACA,gBAGF,4BACE,cACA,sBACA,eACA,iBCvCJ,uBACE,kBACA,WACA,gBAGF,yBACE,kBACA,WACA,aACA,gBACA,yBzBNE,0ByBCJ,yBAQI,czBXA,yByBGJ,yBAYI,cAIJ,qBACE,kBACA,WACA,YzBlBE,0ByBeJ,qBAMI,iBACA,eAKJ,YACE,kBACA,MACA,OACA,WACA,aACA,aACA,mBACA,uBACA,UACA,kBACA,8DAEA,mBACE,UACA,mBAIF,4BACE,mBAGF,4BACE,mBAGF,4BACE,mBAIJ,oBACE,WACA,iBACA,YACA,aACA,mBACA,8BACA,kBACA,czBnEE,0ByB2DJ,oBAWI,gBzBxEA,0ByB6DJ,oBAeI,sBACA,kBACA,uBACA,UzBjFA,yByB+DJ,oBAuBI,8BACA,uBACA,SACA,yBACA,oBAIJ,mBACE,cACA,aACA,sBACA,SACA,UACA,YzBlGE,0ByB4FJ,mBASI,mBACA,mBzBxGA,yByB8FJ,mBAeI,SACA,uBACA,gBACA,WACA,gBAKF,0BACE,Y3B7FkB,6H2B8FlB,eACA,gBACA,mBACA,cACA,kBzB1HA,0ByBoHF,0BASI,gBzB/HF,yByBsHF,0BAcI,eACA,gBACA,iBACA,uBACA,UAIJ,uBACE,Y3BnHkB,6H2BoHlB,eACA,gBACA,mBACA,WACA,SzBhJA,0ByB0IF,uBASI,gBzBrJF,yByB4IF,uBAcI,eACA,gBACA,iBACA,wBAKN,iBACE,oBACA,mBACA,uBACA,YACA,YACA,aACA,mBACA,WACA,Y3BjJoB,6H2BkJpB,eACA,gBACA,mBACA,qBACA,wBAEA,uBACE,2BACA,2BACA,wCAGF,wBACE,wBzBvLA,0ByBgKJ,iBA2BI,azB7LA,yByBkKJ,iBAgCI,YACA,YACA,iBACA,eACA,mBAIJ,oBACE,cACA,YACA,aACA,aACA,mBACA,uBACA,UzB7ME,0ByBsMJ,oBAUI,YACA,czBnNA,0ByBwMJ,oBAeI,YACA,czB1NA,yByB0MJ,oBAqBI,YACA,aACA,eACA,eAGF,gCACE,WACA,YACA,mBAKJ,cACE,kBACA,QACA,2BACA,WACA,YACA,8BACA,YACA,mBACA,eACA,aACA,mBACA,uBACA,WACA,wBAEA,oBACE,8BACA,sCAGF,qBACE,uCAGF,kBACE,WACA,YzBpQA,0ByByOJ,cAgCI,cAIJ,eACE,WAEA,mBACE,yBzBrRA,yByBiRJ,eAQI,WzBvRA,0ByB+QJ,eAYI,WzBzRA,0ByB6QJ,eAkBI,gCAIJ,eACE,YzBxSE,yByBuSJ,eAII,YzBzSA,0ByBqSJ,eAQI,YzB3SA,0ByBmSJ,eAcI,iCAKJ,0BACE,kBACA,aACA,+BACA,2BACA,aACA,SACA,WzBjUE,yByB0TJ,0BAWI,SACA,WACA,YACA,UACA,eACA,mBACA,QACA,oBAIJ,gBACE,WACA,YACA,kBACA,mBACA,YACA,eACA,UACA,wBAEA,uBACE,mBAGF,mCACE,0BzB/VA,yByBgVJ,gBAsBI,UACA,WACA,kBACA,yBACA,yBAEA,uBAEE,UACA,WACA,kBACA,mBACA,aAMN,sBACE,aACA,mBACA,uBACA,gBACA,eACA,UACA,wBACA,kBAEA,0BACE,kBACA,UACA,4BAEA,iCACE,UAIJ,4BACE,gBACA,qBACA,uCzB/YA,yByBwXJ,sBA4BI,cAQJ,oBACE,kBACA,UAEA,gBACA,mBACA,aAEA,uCACE,kBACA,QACA,mDACA,sBACA,2BACA,4BACA,oBAGF,4CACE,kBACA,aACA,mBACA,uBACA,SACA,UzBlbA,0ByB4aF,4CASI,sBACA,SACA,kBzBzbF,yByB8aF,4CAgBI,mBACA,eACA,QACA,iBACA,wBAIJ,sCACE,YACA,aACA,cAEA,0CACE,WACA,YACA,mBzB5cF,0ByBocF,sCAYI,YACA,czBndF,yByBscF,sCAkBI,WACA,aAIJ,yCACE,gBzB5dA,0ByB2dF,yCAII,mBzBjeF,yByB6dF,yCASI,iBAIJ,kCACE,Y3BjdkB,6H2BkdlB,eACA,gBACA,gBACA,WACA,SzB9eA,0ByBweF,kCASI,gBzBnfF,yByB0eF,kCAcI,eACA,gBACA,eAIJ,0CACE,kBACA,aACA,uBACA,mBACA,UzBjgBA,0ByB4fF,0CAQI,wBzBtgBF,yByB8fF,0CAaI,2BACA,4BAIJ,iCACE,WACA,gBASF,gCACE,kBACA,WACA,YACA,gBACA,yBACA,mBACA,aACA,mBACA,ezBliBA,0ByByhBF,gCAYI,YACA,yBACA,gBzBziBF,yByB2hBF,gCAmBI,YACA,yBACA,mBACA,gBAIJ,kCACE,OACA,WACA,YACA,YACA,aACA,yBACA,Y3BliBkB,6H2BmiBlB,eACA,gBACA,WACA,eAEA,+CACE,cACA,gBzBlkBF,0ByBmjBF,kCAmBI,eACA,gBzBzkBF,yByBqjBF,kCAyBI,eACA,gBACA,cAEA,+CACE,cACA,iBAKN,wCACE,gBACA,YACA,eACA,UACA,aACA,mBACA,uBACA,WACA,YACA,cACA,8BAEA,8CACE,qBAGF,+CACE,sBAGF,4CACE,WACA,YzB9mBF,0ByB4mBA,4CAKI,WACA,azBpnBJ,yByB8mBA,4CAWI,WACA,azB1nBJ,yByBylBF,wCAsCI,WACA,aAIJ,qCACE,kBACA,aACA,mBACA,uBACA,SACA,mBACA,UzBzoBA,0ByBkoBF,qCAUI,sBACA,mBACA,uBACA,QACA,qBzBlpBF,yByBooBF,qCAmBI,mBACA,eACA,SACA,oBACA,wBAIJ,mCACE,Y3BtoBkB,6H2BuoBlB,eACA,gBACA,WACA,mBzBpqBA,yByB+pBF,mCASI,eACA,eAIJ,kCACE,aACA,mBACA,SACA,ezB/qBA,0ByB2qBF,kCAOI,uBACA,UzBrrBF,yByB6qBF,kCAaI,QACA,sBAIJ,kCACE,Y3BtqBkB,6H2BuqBlB,eACA,gBACA,WACA,qBACA,mBACA,0BAEA,wCACE,cACA,0BzBxsBF,0ByB6rBF,kCAeI,gBzB9sBF,yByB+rBF,kCAoBI,eACA,eAIJ,uCACE,Y3B/rBkB,6H2BgsBlB,eACA,WACA,iBzB1tBA,0ByBstBF,uCAOI,gBzB/tBF,yByBwtBF,uCAYI,eACA,cACA,aACA,mBACA,aAUN,cACE,kBACA,aACA,mBACA,qBACA,W3B7uBM,K2B8uBN,gBACA,azBzvBE,yByBkvBJ,cAUI,YAEA,qBAGF,mCACE,kBACA,QACA,wDACA,sBACA,2BACA,4BACA,oBAGF,gCACE,iBACA,cAEA,kBACA,WACA,UAGF,8BACE,kBACA,mBACA,kBACA,aACA,sBzBvxBA,0ByBkxBF,8BAQI,iBzB5xBF,yByBoxBF,8BAaI,aACA,mBACA,uBACA,gBACA,mBACA,mBAIJ,6BACE,Y3BjxBkB,6H2BkxBlB,eACA,gBACA,gBACA,WACA,SzB9yBA,0ByBwyBF,6BASI,gBzBnzBF,yByB0yBF,6BAcI,eACA,cACA,uBAIJ,gCACE,Y3BryBkB,6H2BsyBlB,eACA,gBACA,gBACA,cACA,mBzBl0BA,0ByB4zBF,gCASI,gBzBv0BF,yByB8zBF,gCAeI,cAIJ,4BACE,oBACA,oBACA,mBACA,uBACA,SACA,kBACA,Y3B9zBkB,6H2B+zBlB,eACA,gBACA,WACA,mBACA,qBACA,mBACA,wBACA,YACA,YACA,mBzBh2BA,0ByB+0BF,4BAoBI,gBACA,eACA,oBzBv2BF,yByBi1BF,4BA2BI,kBACA,QACA,QACA,2BACA,SACA,WACA,YACA,aACA,eACA,gBACA,kBACA,UAGF,gCACE,WACA,YACA,8BAEA,qCACE,YzBh4BJ,yByB03BA,gCAWI,UACA,YAIJ,kCACE,8BAEA,sCACE,0BAKN,mCACE,aACA,SACA,uBzBp5BA,0ByBi5BF,mCAUI,eACA,SACA,wBzB/5BF,yByBm5BF,mCAiBI,aACA,qCACA,eAIJ,wBACE,YACA,aACA,gBACA,sBACA,mBACA,kBACA,aACA,sBACA,uBACA,kBACA,wBACA,QACA,eAEA,8BACE,2BACA,qCACA,qBzB17BF,0ByBw6BF,wBAsBI,uBACA,iBzBj8BF,yByB06BF,wBA4BI,WACA,YACA,gBACA,iBACA,kBACA,mBACA,yBACA,gDACA,UAIJ,0BACE,Y3Bz7BkB,6H2B07BlB,eACA,gBACA,iBACA,cACA,kBzBx9BA,yByBk9BF,0BAUI,eACA,cACA,WACA,mBAIJ,gCACE,Y3B18BkB,6H2B28BlB,eACA,gBACA,iBACA,cACA,SACA,OACA,gBzB3+BA,yByBm+BF,gCAYI,eACA,gBACA,eAIJ,iCACE,WACA,YACA,aACA,mBACA,uBACA,gBACA,oBAEA,qCACE,WACA,YACA,mBzBjgCF,yByBq/BF,iCAiBI,WACA,aAaN,cACE,kBACA,gBACA,gBACA,gBACA,aACA,aACA,mBzB3hCE,yByBohCJ,cAWI,iBACA,wBAGF,+BACE,kBACA,QACA,8CACA,sBACA,2BACA,oBAYF,yBACE,kBACA,UzBvjCA,yByBqjCF,yBAMI,SACA,UACA,eACA,YAIJ,4BACE,aACA,mBACA,8BACA,I3BxgCU,KE5DV,0ByBgkCF,4BAOI,sBACA,I3B7gCQ,ME7DV,yByBkkCF,4BAaI,cACA,sBACA,SACA,mBAIJ,4BACE,OACA,gBzBtlCA,0ByBolCF,4BAKI,kBACA,gBzB5lCF,yByBslCF,4BAWI,gBACA,eACA,SAIJ,0BACE,Y3B9kCkB,6H2B+kClB,eACA,gBACA,gBACA,WACA,kBzB3mCA,0ByBqmCF,0BASI,gBzBhnCF,yByBumCF,0BAcI,eACA,gBACA,cAEA,cACA,iBAEA,+BACE,oBAIJ,2CACE,gBACA,cACA,ezBpoCF,yByBioCA,2CAOI,eACA,cACA,sBACA,gBAKN,gCACE,Y3BvnCkB,6H2BwnClB,eACA,gBACA,gBACA,WACA,kBzBppCA,0ByB8oCF,gCASI,gBzBzpCF,yByBgpCF,gCAcI,eACA,gBACA,iBACA,cACA,sBAIJ,8BACE,aACA,I3B/mCS,KEvDT,0ByBoqCF,8BAKI,wBzB3qCF,yByBsqCF,8BAUI,mBACA,oBACA,SACA,YAIJ,0BACE,oBACA,mBACA,uBACA,kBACA,Y3BlqCkB,6H2BmqClB,eACA,gBACA,iBACA,mBACA,qBACA,mBACA,wBACA,gBACA,azBrsCA,yByBurCF,0BAkBI,eACA,gBACA,gBACA,iBACA,gBACA,wBACA,YACA,kBACA,mBACA,oBACA,6BACA,wBACA,8BACA,kCACA,uBAGF,gCACE,2BACA,sCAGF,iCACE,wBAIJ,kCACE,mBACA,WAEA,wCACE,2BAIJ,oCACE,mBACA,WAEA,0CACE,8BAIJ,0BACE,OACA,gBACA,aACA,mBACA,uBAEA,8BACE,WACA,YACA,mBzB9vCF,0ByBovCF,0BAcI,gBzBpwCF,yByBsvCF,0BAmBI,kBACA,UACA,QACA,YACA,aACA,gBACA,UAEA,8BACE,YACA,cAiBR,gBACE,kBACA,UACA,kBACA,qBACA,mBACA,gBACA,azB3yCE,yByBoyCJ,gBAWI,iBACA,mBAGF,oCACE,kBACA,QACA,SACA,gCACA,WACA,YACA,iBACA,oBAEA,4CACE,WACA,kBACA,SACA,SACA,YACA,aACA,mDACA,sBACA,4BACA,WzBv0CF,yByB40CE,4CACE,MACA,UACA,QACA,YACA,aACA,WAKN,2BACE,kBACA,UACA,iBACA,cACA,eACA,azB71CA,yByBu1CF,2BASI,UACA,eACA,YAIJ,gCACE,mBACA,kBzBx2CA,yByBs2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CA,gBACA,cACA,UAIJ,yCACE,WACA,YACA,aACA,mBACA,uBACA,8BAEA,6CACE,WACA,YACA,iDAGF,6CACE,WACA,YACA,mBzBn4DJ,yByBk3DA,yCAsBI,WACA,aAIJ,2CACE,Y3Bp3DgB,6H2Bq3DhB,eACA,gBACA,cACA,iBACA,SACA,kBACA,mBzBn5DF,0ByB24DA,2CAWI,gBzBx5DJ,yByB64DA,2CAgBI,eACA,eAMF,+CACE,qBAMN,kCACE,UACA,aACA,cACA,cACA,sBzB96DA,0ByBy6DF,kCAQI,czB/6DF,0ByBu6DF,kCAaI,gBzBx7DF,yByB26DF,kCAkBI,yBACA,UACA,aACA,SACA,0BAMN,aACE,wCAGF,mBACE,KACE,UACA,2BAEF,GACE,UACA,yBAOJ,oBACE,kBACA,iBACA,aACA,mBACA,uBACA,gBzB/9DE,yByBy9DJ,oBAUI,iBACA,gBAGF,oCACE,kBACA,QACA,8CACA,sBACA,2BACA,4BACA,gBAGF,+BACE,kBACA,UAGF,iCACE,aACA,sBACA,mBACA,SACA,kBzB3/DA,yByBs/DF,iCASI,UAIJ,+BACE,Y3B1+DkB,6H2B2+DlB,eACA,gBACA,WACA,mBACA,SzBvgEA,0ByBigEF,+BASI,gBzB5gEF,yByBmgEF,+BAcI,eACA,gBACA,cACA,uBACA,mBAIJ,oCACE,oBACA,mBACA,uBACA,aACA,gBACA,YACA,mBACA,WACA,Y3BxgEkB,6H2BygElB,eACA,gBACA,mBACA,qBACA,wBAEA,0CACE,8BACA,2BACA,yCAGF,2CACE,wBzBhjEF,yByByhEF,oCA4BI,YACA,gBACA,kBACA,eACA,gBACA,kBACA,kBAIJ,iCACE,kBACA,SACA,OACA,QACA,WACA,gBCvkEJ,sBACE,aACA,iBACA,iB5BSM,K4BRN,kBACA,mBAIF,oBACE,YACA,yBACA,mBACA,kBACA,gBACA,I5B4CW,K4B3CX,0BACA,cACA,O5BwCW,K4BtCX,0BAXF,oBAYI,aAGF,yBAfF,oBAgBI,eACA,OACA,SACA,4BACA,8BACA,W5BNQ,gC4BOR,Q5B8DY,I4B7DZ,0BACA,SACA,gBAEA,g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ACE,WACA,W5BlzBO,I4BozBP,yCACE,U5B70BO,K4B80BP,c5BtzBK,I4ByzBP,sDACE,WAIJ,2CACE,WACA,cAEA,6CACE,U5B31BO,K4B+1BX,mCACE,WAKJ,iBACE,e5B50BS,K4B80BT,oBACE,U5Bx2BW,K4By2BX,c5Bl1BO,I4Bs1BT,iCACE,U5Bh3BS,K4Bi3BT,gBACA,iCAGA,uCACE,gBACA,aAEA,oFACE,gBACA,U5B33BK,K4B43BL,mBAKJ,qCACE,Q5Bz2BK,I4B02BL,gBACA,iCAEA,0CACE,U5Bv4BK,K4Bw4BL,gBACA,kBAOR,gBACE,cACA,gBACA,iCAEA,mBACE,kBAGF,mBACE,gBACA,U5B35BS,K4B45BT,WACA,eACA,mBAGF,mBACE,gBACA,U5Bn6BS,K4Bq6BT,wBACE,gBACA,eAMN,kBACE,I5Bp5BS,K4Bw5BX,aACE,I5Bx5BS,K4By5BT,iBACA,eACA,kBAIF,oBACE,eACA,kBAIF,oBACE,gBACA,mBCn+BJ,YACE,aACA,mBACA,uBACA,mBACA,kBACA,kBACA,mBACA,gBACA,mBAGF,iBACE,WACA,gBACA,cACA,kBAGF,YACE,yBACA,UACA,aACA,sBACA,mBAEA,yBAPF,YAQI,gBAIJ,YACE,WACA,YACA,mBAEA,gBACE,WACA,YACA,mBAIJ,eACE,Y7BfoB,6H6BgBpB,eACA,gBACA,WACA,kBACA,mBACA,cAGF,YACE,WACA,gBAEA,wBACE,mBAEA,qCACE,gBAIJ,wBACE,WACA,YACA,eACA,Y7BxCkB,6H6ByClB,eACA,gBACA,cACA,gBACA,sBACA,mBACA,aACA,wBAEA,qCACE,cAGF,8BACE,qBAGF,8BACE,qBAIJ,2BACE,aACA,mBACA,yBACA,QACA,gBACA,mBACA,kBAEA,gDACE,WACA,YACA,wBACA,kBACA,eACA,gBACA,gBACA,kBACA,cAEA,wDACE,gBACA,qBAEA,+DACE,WACA,kBACA,SACA,QACA,8CACA,UACA,YACA,qBACA,yBAKN,iCACE,Y7BtGgB,6H6BuGhB,eACA,gBACA,WACA,eACA,iBACA,iBACA,mBAKN,cACE,WACA,YACA,aACA,Y7BtHoB,6H6BuHpB,eACA,gBACA,WACA,mBACA,YACA,mBACA,eACA,wBACA,mBACA,cAEA,oBACE,4BAGF,qBACE,2BAGF,uBACE,WACA,mBAIJ,aACE,aACA,uBACA,mBACA,SACA,eAEA,eACE,Y7BxJkB,6H6ByJlB,eACA,gBACA,WACA,qBACA,0BACA,cAEA,qBACE,cACA,0BAIJ,6BACE,WACA,eAKJ,aACE,WACA,mBACA,kBACA,kBACA,eACA,aACA,mBACA,QAEA,yBACE,gCACA,cACA,sCAGF,2BACE,gCACA,cACA,sCAGF,wBACE,6BACA,cACA,mCAGF,eACE,eAKJ,eACE,eACA,MACA,OACA,QACA,SACA,gCACA,aACA,mBACA,uBACA,WACA,UACA,oBACA,4BAEA,sBACE,UACA,mBAGF,wBACE,WACA,YACA,sBACA,yBACA,kBACA,kCAIJ,gBACE,GACE,0B3BzQA,yB2B+QF,YAEE,mBACA,8BACA,kBACA,aACA,gBACA,gBAGF,iBACE,eACA,UAGF,YACE,UAGF,YAEE,WACA,YACA,mBAGF,eAEE,eACA,gBACA,cACA,mBACA,gBAIA,wBACE,mBAGF,wBAEE,WACA,YACA,eACA,eACA,kBACA,qBAEA,qCAEE,eACA,cAIJ,2BAEE,yBACA,gBACA,mBACA,eACA,QAEA,gDAEE,WACA,YACA,kBAGF,iCAEE,eACA,cACA,iBAKN,cAEE,WACA,YACA,eACA,gBACA,kBACA,mBAGF,aAEE,SAEA,eACE,eACA,gBACA,cAGF,6BACE,eACA,cAIJ,aACE,gBACA,mBACA,kBACA,gBC/XJ,uBACE,aACA,uBACA,uBACA,yBACA,UACA,kBACA,gBACA,SAGF,4BACE,WACA,gBACA,cACA,kBACA,Q9B2CW,K8BxCb,uBACE,yBACA,UACA,aACA,sBACA,oBACA,kBAEA,yBARF,uBASI,gBAKJ,uBACE,aAIF,wBACE,aAIF,uBACE,aACA,MACA,gBACA,WACA,yBACA,gBACA,UAEA,iCACE,OACA,aACA,mBACA,uBACA,kBACA,Y9B9BkB,6H8B+BlB,eACA,gBACA,cACA,qBACA,kBACA,mBACA,gBACA,wBAGA,6CACE,yBAIF,4CACE,yBAGF,uCACE,cACA,mBAGF,wCACE,WACA,mBACA,gBAGF,yBArCF,iCAsCI,kBACA,gBAMN,eACE,WACA,mBACA,kBACA,mBACA,Y9B1EoB,6H8B2EpB,eACA,aACA,mBACA,SACA,6BAEA,iBACE,eACA,cAGF,2BACE,gCACA,cACA,sCAGF,6BACE,gCACA,cACA,sCAGF,0BACE,6BACA,cACA,mCAKJ,uBACE,WACA,gBACA,mBACA,aACA,4BAEA,yBAPF,uBAQI,mBAGF,mCACE,aACA,mBACA,SACA,mBAEA,gDACE,gBAGF,yBAVF,mCAWI,sBACA,oBACA,UAIJ,mCACE,aACA,mBACA,cACA,YACA,Y9B3IkB,6H8B4IlB,eACA,gBACA,cACA,gBAEA,6CACE,cACA,gBAGF,yBAhBF,mCAiBI,WACA,gBAIJ,mCACE,OACA,YACA,eACA,Y9BhKkB,6H8BiKlB,eACA,gBACA,cACA,gBACA,yBACA,kBACA,aACA,wBAEA,gDACE,cAGF,yCACE,qBAGF,yCACE,qBACA,yCAGF,4CACE,mBACA,qBACA,cACA,mBAGF,yCACE,qBAIJ,oCACE,YACA,sBACA,Y9BtMkB,6H8BuMlB,eACA,gBACA,cACA,gBACA,yBACA,kBACA,aACA,eACA,wBACA,gBACA,wBACA,qBACA,wLACA,4BACA,sCACA,yBAEA,0CACE,qBAGF,0CACE,qBACA,yCAGF,6CACE,yBACA,qBACA,cACA,mBACA,WAGF,2CACE,YACA,eAMN,mBACE,aACA,mBACA,QACA,OAEA,iCACE,YACA,cAGF,gEAEE,YACA,cAGF,oCACE,aACA,mBACA,uBACA,UACA,WACA,mBACA,cAGF,yBA3BF,mBA4BI,eACA,QAEA,iGAGE,OACA,eAGF,oCACE,WAIJ,yBACE,iGAGE,WAGF,oCACE,cAMN,mBACE,aAGF,kBACE,kBACA,aACA,mBACA,OAEA,8BACE,OACA,mBAGF,8BACE,kBACA,WACA,QACA,2BACA,Y9B9TkB,6H8B+TlB,eACA,gBACA,cACA,oBAEA,yBAXF,8BAYI,eACA,YAMN,yCAEE,YACA,YACA,YACA,Y9BjVoB,6H8BkVpB,eACA,gBACA,WACA,mBACA,YACA,kBACA,eACA,wBACA,SACA,cACA,cAEA,qDACE,mDAGF,uDACE,iDAGF,2DACE,WACA,mBAGF,yBA/BF,yCAgCI,WACA,YACA,eACA,gBAKJ,qCACE,aACA,uBACA,SACA,gBACA,eACA,gBACA,yBAEA,wGAEE,YACA,YACA,YACA,Y9BlYkB,6H8BmYlB,eACA,gBACA,YACA,kBACA,eACA,wBACA,cAEA,aACA,mBACA,uBACA,qBAGF,oDACE,cACA,mBAEA,0DACE,iDAGF,2DACE,kDAIJ,oDACE,WACA,mBAEA,0DACE,4BAGF,2DACE,2BAGF,6DACE,WACA,mBAIJ,yBA5DF,qCA6DI,sBACA,SACA,eAEA,wGAEE,WACA,YACA,gBAMN,iBACE,eACA,MACA,OACA,QACA,SACA,gCACA,aACA,mBACA,uBACA,aACA,4BAEA,wBACE,aAGF,0BACE,WACA,YACA,sBACA,yBACA,kBACA,kCAKJ,gBACE,GACE,0BAIJ,qBACE,KACE,UACA,4BAEF,GACE,UACA,yBAKJ,yBACE,uBACE,kBACA,gBACA,mBAGF,4BACE,gBAOJ,yBACE,WACA,mBACA,kBACA,4BACA,kBAEA,yBAPF,yBAQI,mBAGF,wCACE,mBAEA,qDACE,eACA,cACA,mBACA,cAEA,yBANF,qDAOI,gBAIJ,sDACE,Y9BthBgB,6H8BuhBhB,eACA,gBACA,cACA,mBAEA,yBAPF,sDAQI,gBAIJ,4DACE,Y9BliBgB,6H8BmiBhB,eACA,gBACA,cACA,SAEA,mEACE,cACA,gBAGF,yBAZF,4DAaI,gBAOR,kBACE,gBACA,mBAEA,mCACE,gBACA,yBACA,mBACA,kBACA,mBACA,wBAEA,8CACE,gBAGF,yCACE,qBACA,0CAGF,8CACE,aACA,mBACA,uBACA,SACA,eAEA,yBAPF,8CAQI,sBACA,SAIJ,+CACE,Y9BxlBgB,6H8BylBhB,eACA,gBACA,cAEA,yBANF,+CAOI,gBAIJ,8CACE,Y9BnmBgB,6H8BomBhB,eACA,gBACA,cAEA,yBANF,8CAOI,gBAOR,iBACE,gBACA,cACA,8BACA,mCACA,mBACA,kBAEA,4BACE,Y9BznBkB,6H8B0nBlB,eACA,gBACA,cACA,SACA,aACA,mBACA,uBACA,QACA,eAEA,8BACE,cACA,eAGF,uCACE,cACA,gBACA,0BACA,0BAEA,6CACE,sBAIJ,yBA5BF,4BA6BI,eACA,mB5BhrBF,yB4ByrBF,uBACE,UACA,SAGF,4BACE,aAGF,uBACE,UAKA,iCACE,YACA,kBACA,eACA,gBAEA,6CACE,wBAGF,4CACE,wBAMN,uBACE,aACA,0BAEA,mCACE,sBACA,oBACA,QACA,mBAIF,mCACE,WACA,eACA,gBACA,gBAIF,mCACE,UACA,WACA,YACA,eACA,eACA,kBAEA,gDACE,cAKJ,oCACE,YACA,sBACA,eACA,kBACA,sCAKJ,mBACE,eACA,QACA,mBACA,WAEA,iCACE,OACA,YACA,WAGF,gEAEE,OACA,YACA,WAGF,oCACE,aACA,WACA,YACA,yBACA,eACA,WAEA,4CACE,YAMN,kBACE,WAEA,8BACE,WACA,mBAIF,8BACE,eACA,WAKJ,yCAEE,WACA,YACA,aACA,eACA,gBACA,kBACA,eAIF,qCACE,mBACA,SACA,gBACA,eAEA,wGAEE,OACA,gBACA,YACA,eACA,gBACA,kBAIF,oDACE,mBACA,cAIF,oDACE,mBACA,WAKJ,yBACE,kBACA,0BAEA,wCACE,mBAEA,qDACE,eACA,mBAGF,sDACE,eACA,kBAGF,4DACE,eAMN,kBACE,mBAEA,mCACE,aACA,kBACA,kBAEA,+CACE,eAGF,8CACE,eAMN,iBACE,kBACA,kBAEA,4BACE,gBCp5BN,uBACE,+BACA,aACA,mBACA,uBACA,mBACA,kBACA,kBACA,mBACA,gBACA,mBAGF,4BACE,WACA,iBACA,cACA,kBAGF,uBACE,yBACA,UACA,aACA,sBACA,mBAEA,yBAPF,uBAQI,gBAIJ,aACE,WACA,YACA,mBAEA,iBACE,WACA,YACA,mBAIJ,gBACE,Y/BhBoB,6H+BiBpB,eACA,gBACA,WACA,kBACA,mBACA,cAGF,gBACE,WACA,gBACA,aACA,sBACA,SACA,mBAGF,YACE,WACA,YACA,aACA,mBACA,uBACA,SACA,aACA,Y/B1CoB,6H+B2CpB,eACA,gBACA,WACA,qBACA,mBACA,eACA,wBACA,cACA,kBAEA,kBACE,2BACA,sCAGF,mBACE,wBACA,oCAGF,6BACE,WACA,YACA,cAGF,iBACE,mBAIJ,uBACE,mBAGF,yBACE,mBAGF,mBACE,aACA,uBACA,mBACA,SACA,eAEA,oCACE,Y/B1FkB,6H+B2FlB,eACA,gBACA,WACA,qBACA,0BACA,cAEA,0CACE,cACA,0BAIJ,yCACE,WACA,eACA,c7BrIA,yB6B2IF,uBAEE,mBACA,8BACA,kBACA,aACA,gBACA,gBAGF,4BACE,eACA,UAGF,uBACE,UAGF,aAEE,WACA,YACA,mBAGF,gBAEE,eACA,gBACA,cACA,mBACA,iBAGF,gBAEE,gBACA,SACA,mBAGF,YAEE,YACA,eACA,gBACA,kBACA,iBACA,uBACA,QAEA,gBACE,WACA,YACA,cAGF,iBACE,kBAGF,kBACE,eACA,gBACA,WAIJ,mBAEE,SAEA,oCACE,eACA,gBACA,cAGF,yCACE,eACA,eC3NN,4BACE,WhCyDY,KgCxDZ,YhCsDW,KgCpDX,uCACE,WCDJ,oBAEE,kBACA,aACA,qBACA,S/BRE,0B+BGJ,oBAQI,sBACA,QACA,mB/BfA,yB+BKJ,oBAeI,mBACA,eACA,qBACA,QACA,mBACA,+BACA,gBACA,gCACA,oBAGF,oCACE,eACA,YjCYe,IiCXf,MjC3BQ,QiC4BR,S/BjCA,0B+B6BF,oCAOI,gB/BtCF,yB+B+BF,oCAYI,eACA,YjCDe,KiCKnB,uCACE,eACA,YjCRkB,IiCSlB,c/BjDA,0B+B8CF,uCAMI,gB/BtDF,yB+BgDF,uCAWI,gBAMN,mBACE,aACA,uBACA,mB/BpEE,yB+BiEJ,mBAOI,mBACA,WAIJ,gBACE,aACA,uBACA,S/BhFE,yB+B6EJ,gBAOI,SACA,oBAKJ,WACE,aACA,QAEA,gBACE,UACA,WACA,cjCzBmB,IiC0BnB,yB/BlGA,yB+B0FJ,WAaI,aACA,mBACA,mBACA,QACA,WACA,YAEA,gBACE,UACA,WACA,cjCzCiB,IiC0CjB,0BAMN,oBACE,aACA,sBACA,mBACA,QAEA,uCACE,a/B/HA,yB+B8HF,uCAKI,cAIJ,+BACE,WACA,YACA,cjClEmB,IiCmEnB,yBACA,aACA,mBACA,uBACA,W/B/IA,yB+BuIF,+BAYI,WACA,aAGF,8CACE,WACA,Y/BzJF,yB+BuJA,8CAMI,WACA,aAKN,sCACE,yBACA,MjC3JI,KiC8JN,gCACE,eACA,YjChIkB,IiCiIlB,cACA,kBACA,mB/B7KA,yB+BwKF,gCASI,gBAIJ,uCACE,YjCzIe,IiC0If,c/BvLA,yB+BqLF,uCAMI,gBAMN,yBAEE,kB/BjME,0B+B+LJ,yBAKI,mBAIF,sCACE,aACA,MACA,aACA,iB/B9MA,yB+B0MF,sCAOI,sBACA,UACA,iBjCzME,MiCkNN,qCACE,WACA,iBACA,yBACA,cjC7Je,IiC8Jf,iBjCvNI,KiCwNJ,eACA,MjC5NQ,QiC6NR,aACA,iCAEA,kDACE,cAGF,2CACE,qB/B1OF,0B+B0NF,qCAoBI,gBAIJ,wCACE,WACA,iBACA,iBACA,yBACA,cjCtLe,IiCuLf,iBjChPI,KiCiPJ,eACA,MjCrPQ,QiCsPR,aACA,gBACA,iCAEA,qDACE,cAGF,8CACE,qB/BpQF,0B+BkPF,wCAsBI,iBACA,gBAQN,iBACE,iBjC1QM,KiC2QN,yBACA,cjClNiB,KiCmNjB,kBACA,aACA,sBACA,mBACA,S/BzRE,0B+BiRJ,iBAWI,mBAIJ,mBACE,WACA,YACA,kBAEA,uBACE,WACA,YACA,iBACA,cjCxOe,IiC4OnB,kBACE,WACA,YACA,aACA,mBACA,uBACA,cjClPiB,IiCmPjB,WAGF,kCACE,kBACA,SACA,WACA,WACA,YACA,cjCvPqB,IiCwPrB,yBACA,MjCvTM,KiCwTN,YACA,eACA,aACA,mBACA,uBACA,qCAEA,wCACE,yBAIJ,kBACE,kBAEA,gCACE,eACA,YjCvSmB,IiCwSnB,cACA,eAGF,+BACE,eACA,YjChTkB,IiCiTlB,cACA,SAEA,sCACE,YjCnTiB,IiC2TvB,cACE,aACA,SAEA,wBACE,O/B1WA,0B+BqWJ,cASI,sBACA,U/BjXA,yB+BuWJ,cAeI,mBACA,SAEA,wBACE,OACA,YACA,YACA,eACA,kBACA,gBAKN,YACE,WACA,YACA,eACA,yBACA,MjC/XM,KiCgYN,YACA,cjCxUiB,IiCyUjB,eACA,YjCjWqB,IiCkWrB,eACA,qCACA,cAEA,kBACE,yB/BjZA,0B+BkYJ,YAmBI,WACA,eACA,a/BzZA,yB+BoYJ,YA0BI,WACA,YACA,eACA,kBACA,gBAKJ,kCACE,gBACA,YACA,yBAEA,4CACE,aACA,sBACA,SACA,gBAGF,2CACE,aACA,SACA,UACA,mBAEA,iDACE,yB/BxbF,0B+BibF,2CAWI,sBACA,U/B/bF,yB+BmbF,2CAiBI,mBACA,UAGF,uDACE,OACA,YACA,eACA,yBACA,cjC1Ya,IiC2Yb,iBjCpcE,KiCqcF,aACA,mBACA,eACA,MjC3cM,QEPR,yB+BwcA,uDAcI,YACA,eACA,eACA,kBACA,aAIJ,0DACE,uBACA,uBACA,oCACA,yBACA,uBACA,8BACA,0BACA,YjCzba,IiC0bb,eACA,qCACA,cACA,wBACA,mBACA,uB/B1eF,0B+B4dA,0DAiBI,sBACA,0BACA,wB/BjfJ,yB+B8dA,0DAwBI,sBACA,uBACA,0BACA,6BACA,sBAgBR,2BAEE,eAEA,yBAJF,2BAKI,mBAIF,wCACE,aACA,+BACA,kBACA,UAKJ,mBACE,cjCjeY,KiCkeZ,eAEA,yBAJF,mBAKI,cjCveS,KiCweT,WjC3eS,KiC8eX,mCACE,aACA,mBACA,uBACA,kBAGF,kCACE,aACA,sBACA,mBACA,kBACA,UAEA,+CACE,WACA,YACA,cjC9eiB,IiC+ejB,WjC7iBE,KiC8iBF,yBACA,aACA,mBACA,uBACA,YjChhBiB,IiCihBjB,MjCrjBM,QiCsjBN,WjC7dY,aiC8dZ,cjCxgBO,IiC2gBT,8CACE,UjCpiBS,KiCqiBT,MjC5jBM,QiC6jBN,mBAIA,sDACE,WjC9kBO,QiC+kBP,ajC/kBO,QiCglBP,MjClkBA,KiCmkBA,uCAGF,qDACE,MjCrlBO,QiCslBP,YjCviBa,IiC4iBf,yDACE,WjCvlBO,QiCwlBP,ajCxlBO,QiCylBP,MjChlBA,KiCmlBF,wDACE,MjCvlBI,QiC4lBV,kCACE,OACA,WACA,WjCzlBU,QiC0lBV,cACA,mBACA,kBAEA,yCACE,WjC3mBS,QiCinBf,iBACE,kBACA,cjCxjBY,KiCyjBZ,eAEA,oBACE,UjCplBY,KiCqlBZ,YjC5kBe,IiC6kBf,MjCnnBQ,QiConBR,cjCpkBS,IiCukBX,oBACE,UjC5lBW,KiC6lBX,YjCplBmB,IiCqlBnB,MjCroBW,QiCsoBX,cjC1kBS,KiC6kBX,qCACE,UjCtmBa,KiCumBb,MjC/nBQ,QiCgoBR,gBACA,cAKJ,yBACE,YACA,yBACA,cjC7kBiB,KiC8kBjB,kBACA,gBACA,MACA,mBACA,+BACA,gBACA,cAEA,0BAZF,yBAaI,aAGF,yBAhBF,yBAiBI,eACA,OACA,SACA,4BACA,8BACA,WjC9oBQ,gCiC+oBR,QjC1kBY,IiC2kBZ,0BACA,gBACA,SACA,gBAEA,qCACE,yBAOJ,mCACE,eACA,YjCxoBe,IiCyoBf,cACA,kBACA,uBAGF,kCACE,gBACA,gCAEA,6CACE,mBAIJ,gCACE,kBACA,eACA,YjC7pBkB,IiC8pBlB,cACA,eACA,WjCzmBc,aiC0mBd,aACA,mBACA,8BACA,SAEA,sCACE,iCAGF,uCACE,YjCxqBa,IiCyqBb,cAIF,2CACE,WACA,YACA,cAEA,+CACE,WACA,YACA,mBAKJ,2CACE,OAIF,2CACE,UACA,WACA,UACA,eACA,yBACA,kBACA,YACA,cAGF,kDAEE,yBAMN,yBACE,OACA,kBACA,gBACA,aACA,sBAGA,2CACE,UjCxuBa,KiCyuBb,MjCjwBQ,QiCkwBR,cjChtBS,KiCktBT,kDACE,MjCjxBS,QiCkxBT,YjCluBiB,IiCsuBrB,yBAnBF,yBAoBI,WAKJ,sBACE,aACA,eACA,OjCluBW,KiCmuBX,MjCnuBW,KiCouBX,WACA,YACA,iBjCnyBa,QiCoyBb,cjCxtBqB,IiCytBrB,YACA,MjCxxBM,KiCyxBN,eACA,eACA,YACA,WjCrsBgB,aiCusBhB,4BACE,sBACA,WjCnxBQ,gCiCsxBV,6BACE,sBAGF,yBAzBF,sBA0BI,aACA,mBACA,wBAKJ,uBACE,aACA,eACA,SACA,OACA,QACA,SACA,gCACA,YACA,UACA,4BACA,oBAEA,yBAbF,uBAcI,eAGF,8BACE,UACA,oBAKJ,mBACE,aACA,yBACA,mBACA,cjC3xBW,KiC4xBX,IjC9xBW,KiCgyBX,yBAPF,mBAQI,yBAIJ,gBACE,kBACA,YACA,YACA,cACA,iBAEA,yBAPF,gBAQI,WACA,eAGF,8BACE,WACA,YACA,sBACA,yBACA,mBACA,iBjCr2BI,KiCs2BJ,eACA,oBACA,aACA,WjClxBc,aiCoxBd,oCACE,ajC13BS,QiC23BT,uCAGF,2CACE,cAKJ,oBACE,kBACA,WACA,QACA,2BACA,oBAIJ,mBACE,UjC12Be,KiC22Bf,MjCn4BU,QiCo4BV,mBAEA,kCACE,YjCn2BmB,IiCo2BnB,MjCp5BW,QiCy5Bf,mBACE,aACA,mBACA,8BACA,IjCh2BW,KiCi2BX,cjCj2BW,KiCm2BX,yBAPF,mBAQI,sBACA,oBACA,IjCv2BS,MiC02BX,uCACE,aACA,mBACA,OAEA,yBALF,uCAMI,SAIJ,qCACE,aACA,mBACA,IjCx3BS,IiCy3BT,eACA,UjCj5Ba,KiCk5Bb,MjC36BQ,QiC46BR,iBAEA,2CACE,MjC17BS,QiC87Bb,wCACE,WACA,YACA,eACA,ajCl8BW,QiCo8BX,sDACE,WAKF,yBADF,mCAEI,SAQN,yBACE,aACA,qCACA,IjCz5BW,KiC05BX,cjCz5BW,KiC45BX,0BAPF,yBAQI,qCACA,IjCh6BS,MiCo6BX,yBAbF,yBAcI,qCACA,IjCt6BS,MiC66Bb,iBACE,qBACA,WACA,YACA,cACA,eACA,oDACA,wBACA,4BACA,2BACA,kBAIF,8CACE,yBACA,kDAKA,mCACE,iBAKJ,oBACE,WjCv/BM,KiCw/BN,yBACA,mBACA,WjCn6BgB,aiCo6BhB,eACA,kBAEA,0BACE,ajC7gCW,QiC8gCX,WjCr/BQ,+BiCw/BV,6BACE,qBACA,oCAGF,sCACE,aACA,sBACA,SACA,kBACA,eAGA,uDACE,aACA,8BACA,mBACA,SAEA,0EACE,eACA,YjCx/Bc,IiCy/Bd,cACA,OAGF,yEACE,cAEA,uFACE,WACA,YACA,eACA,qBAMN,gDACE,eACA,YjCzgCa,IiC0gCb,cACA,SACA,cAIF,uDACE,eACA,YjCrhCgB,IiCshChB,cACA,SACA,gBACA,oBACA,qBACA,4BACA,gBAIF,qDACE,aACA,yBACA,gBAEA,yDACE,WACA,YACA,mBAGF,uDACE,WACA,YACA,aACA,mBACA,uBACA,eACA,WAOR,kBACE,WjCxlCS,QiCylCT,cjCjiCiB,KiCkiCjB,QjC7iCW,KiC8iCX,cjC7iCW,KiC+iCX,qBACE,UjCzkCW,KiC0kCX,YjC/jCmB,IiCgkCnB,MjCrmCQ,QiCsmCR,cjCrjCS,KiCwjCX,iCACE,aACA,eACA,IjC5jCS,IiC8jCT,gDACE,oBACA,mBACA,IjClkCO,IiCmkCP,iBACA,WjChnCE,KiCinCF,yBACA,cjCrjCe,KiCsjCf,UjC9lCS,KiC+lCT,MjCloCS,QiCooCT,6DACE,gBACA,YACA,MjCvoCO,QiCwoCP,eACA,eACA,UACA,gBAEA,mEACE,MjC1oCM,QiCkpChB,iBAEE,QjCxlCY,KiC4lCd,eACE,QjC7lCY,KiC8lCZ,kBACA,MjCnpCU,QiCqpCV,gCACE,WACA,YACA,mBACA,yBACA,iBjCtqCW,QiCuqCX,cjC3lCmB,IiC4lCnB,kCAGF,iBACE,UjCxoCa,KiCyoCb,MjCjqCQ,QiCqqCZ,gBACE,GACE,0BAKJ,eACE,WACA,iBACA,cAEA,0BACE,WjChrCI,KiCirCJ,cjCvnCe,KiCwnCf,QjCjoCU,KiCkoCV,WjCzqCQ,0BiC0qCR,cjCpoCS,KiCuoCX,2BACE,cjCxoCS,KiC0oCT,sCACE,gBAIJ,2BACE,cACA,UjC5qCW,KiC6qCX,YjChqCmB,IiCiqCnB,MjCtsCQ,QiCusCR,cjCvpCS,IiCypCT,mDACE,MjCjtCU,QiCktCV,gBAIJ,qFAGE,WACA,kBACA,yBACA,cjCzpCe,IiC0pCf,UjC7rCa,KiC8rCb,WjC7nCc,aiC8nCd,WjCrtCI,KiCutCJ,uGACE,aACA,ajCvuCS,QiCwuCT,uCAGF,4HACE,MjC/tCO,QiCkuCT,gHACE,yBAIJ,8BACE,gBACA,iBACA,oBAGF,0BACE,cACA,UjC1tCW,KiC2tCX,MjCjvCQ,QiCkvCR,WjCpsCS,IiCssCT,wCACE,YACA,MjCrvCO,QiCyvCX,4BACE,aACA,IjC3sCS,KiC4sCT,eAEA,yCACE,aACA,mBACA,eAEA,2DACE,ajCttCK,IiCytCP,qDACE,UjCjvCS,KiCkvCT,MjC3wCI,QiCixCV,oCACE,aACA,mBACA,IjCluCS,KiCouCT,kDACE,YACA,aACA,0BACA,cjC7tCa,KiC8tCb,aACA,mBACA,uBACA,WjC1xCI,QiC2xCJ,gBACA,kBACA,WjCvsCY,aiCysCZ,wDACE,ajC/yCO,QiCgzCP,8BAGF,oEACE,aACA,sBACA,mBACA,uBACA,IjC7vCK,IiC8vCL,MjC7yCI,QiC8yCJ,kBACA,QjC/vCK,KiCiwCL,wEACE,WAGF,yEACE,UjChyCK,KiCiyCL,YjCpxCW,IiCwxCf,sDACE,WACA,YACA,iBAGF,mEACE,kBACA,QACA,UACA,WACA,YACA,UACA,+BACA,yBACA,cjC1wCe,IiC2wCf,MjC30CI,QiC40CJ,eACA,WjCpvCU,aiCqvCV,aACA,mBACA,uBACA,WjCr0CI,0BiCs0CJ,WAEA,uEACE,WACA,YAGF,yEACE,WjCl2CM,QiCm2CN,ajCn2CM,QiCo2CN,MjC11CF,KiC21CE,qBACA,WjCj1CE,+BiCo1CJ,0EACE,sBAKN,gDACE,aAGF,qDACE,kBACA,WjC32CE,KiC42CF,yBACA,cjCpzCa,IiCqzCb,UjCz1CS,KiC01CT,YjC90Ce,IiC+0Cf,MjCn3CM,QiCo3CN,eACA,WjC3xCY,aiC6xCZ,2DACE,WjCn4CO,QiCo4CP,ajCp4CO,QiCq4CP,MjCv3CA,KiC03CF,4DACE,sBAMN,+BACE,aACA,IjCt1CS,IiCu1CT,cjCt1CS,KiCw1CT,2CACE,OAGF,2CACE,oBACA,mBACA,IjCj2CO,IiCk2CP,kBACA,WjC75CS,QiC85CT,YACA,cjCx1Ca,IiCy1Cb,MjCl5CE,KiCm5CF,UjC93CS,KiC+3CT,YjCn3Ce,IiCo3Cf,eACA,WjC/zCY,aiCg0CZ,mBAEA,+CACE,cAGF,iDACE,WjC36CS,QiC46CT,2BACA,WjCr5CI,+BiCw5CN,kDACE,wBAKN,wBACE,WjC53CS,KiC63CT,gBAEA,wCACE,QjCh4CO,KiCi4CP,WjC96CI,QiC+6CJ,gCAEA,kDACE,UjC95CO,KiC+5CP,MjCt7CI,QiCw7CJ,yDACE,MjCr8CK,QiCs8CL,YjCt5Ca,IiC25CnB,kCACE,iBACA,gBAEA,qDACE,UAGF,2DACE,WjCr8CE,QiCw8CJ,2DACE,WjCv8CM,QiCw8CN,kBAEA,iEACE,WjC/8CG,QiCo9CT,iCACE,aACA,mBACA,8BACA,gCACA,WjCj4CY,aiCm4CZ,4CACE,mBAGF,uCACE,WjC99CE,QiCi+CJ,6CACE,YjCl9CW,oCiCm9CX,UjC/8CO,KiCg9CP,MjCx+CI,QiCy+CJ,YjCr8Ca,IiCw8Cf,gDACE,oBACA,mBACA,uBACA,WACA,YACA,UACA,WjCh/CA,KiCi/CA,yBACA,cjC17CW,IiC27CX,MjCr/CI,QiCs/CJ,eACA,WjC95CU,aiCg6CV,oDACE,WACA,YAGF,uDACE,sBASV,uCACE,8BACA,oBACA,WjCx9CY,KiCy9CZ,gBACA,cAEA,yBAPF,uCAQI,qB/BxhDA,yB+BmiDJ,yCAEI,aACA,8BACA,kCACA,mBACA,SACA,kBACA,aACA,+BAEA,qDACE,uBACA,uBACA,gBACA,4BACA,0BACA,YjCvgDa,IiCwgDb,6BACA,cAEA,mEACE,oCACA,yBACA,uBAGF,iEACE,oCACA,sBACA,wBAOR,iBAEE,kBACA,cACA,cACA,kB/B7kDE,yB+BwkDJ,iBASI,YACA,+BACA,iBACA,+BACA,aACA,sBACA,mBACA,4BAIF,6BAEE,c/B9lDA,yB+B4lDF,6BAMI,yBACA,kBACA,uBACA,WACA,oBAMA,0CACE,YACA,a/B9mDJ,yB+B4mDE,0CAMI,YACA,cAON,oCACE,gCACA,MjC5nDU,QiCgoDd,sCACE,cjCvkDS,KE1DT,yB+BgoDF,sCAKI,oBAGF,mDACE,cACA,aACA,mBACA,uBACA,eAIJ,gCACE,cjCvlDU,KE3DV,yB+BipDF,gCAKI,iBAGF,mCACE,UjCvnDS,KiCwnDT,YjC9mDa,IiC+mDb,MjChqDS,QiCiqDT,cjCrmDO,KExDT,yB+BypDA,mCAQI,eACA,oBAIJ,oDACE,UjCvoDW,KiCwoDX,MjChqDM,QiCiqDN,c/BzqDF,yB+BsqDA,oDAOI,eACA,gBAEA,+DACE,cAGF,+DACE,eASV,eACE,WjCrrDM,KiCsrDN,cjC5nDiB,KiC6nDjB,QjCvoDW,KiCwoDX,WjC7qDU,+BiC8qDV,cjCzoDW,KiC0oDX,gBAEA,yBACE,aACA,mBACA,8BACA,eACA,gCAEA,oCACE,mBAGF,qCACE,UjCnrDS,KiCorDT,MjC3sDM,QiC4sDN,YjCzqDe,IiC0qDf,gBAGF,qCACE,UjCzrDW,KiC0rDX,MjCntDM,QiCotDN,YjChrDe,IiCmrDjB,sCACE,aACA,mBACA,IjC1qDO,IiC2qDP,OACA,yBAEA,iDACE,YjC3sDW,oCiC4sDX,UjCxsDO,KiCysDP,WjC7tDE,QiC8tDF,iBACA,cjCxqDW,IiCyqDX,MjC9uDS,QiC+uDT,ajCrrDK,IiCwrDP,6GAEE,iBACA,WjCxuDA,KiCyuDA,yBACA,cjClrDW,IiCmrDX,eACA,WjCrpDU,aiCupDV,yHACE,WjC7vDK,QiC8vDL,ajC9vDK,QiC+vDL,MjCjvDF,KiCuvDF,8CACE,iBACA,iCACA,cACA,cjC9rDa,KiC+rDb,UjCxuDO,KiCyuDP,YjC3tDe,IiCiuDvB,uBACE,WjCnwDQ,QiCowDR,cjC3sDiB,KiC4sDjB,QjCvtDW,KiCwtDX,cjCvtDW,KiCwtDX,gBAEA,0BACE,UjCpvDW,KiCqvDX,YjC1uDmB,IiC2uDnB,MjChxDQ,QiCixDR,cjChuDS,KiCmuDX,kCACE,aACA,eACA,IjCvuDS,IiCwuDT,cjCvuDS,KiCyuDT,4CACE,iBACA,WjCzxDE,KiC0xDF,yBACA,cjC9tDe,KiC+tDf,UjCvwDS,KiCwwDT,MjC3yDS,QiC+yDb,iCACE,UjC9wDW,KiC+wDX,MjCryDQ,QiCsyDR,kBAIJ,iBACE,WjCvyDS,QiCwyDT,cjChvDiB,KiCivDjB,QjC3vDW,KiC4vDX,cjC5vDW,KiC6vDX,gBAEA,oBACE,UjCzxDW,KiC0xDX,YjC/wDmB,IiCgxDnB,MjCrzDQ,QiCszD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eACA,YlCjcqB,IkCkcrB,MlCpeM,KkCqeN,eACA,mBACA,+BAEA,uBACE,iDAGF,yBAtBF,iBAuBI,YACA,eACA,gBAKJ,iBACE,aACA,mBACA,uBACA,IlC5cW,IkC6cX,WACA,YACA,mBACA,YACA,clCpciB,KkCqcjB,eACA,YlC7diB,IkC8djB,MlCjgBM,KkCkgBN,eACA,+BAEA,uBACE,4DAGF,qBACE,WACA,YAGF,yBAzBF,iBA0BI,eACA,YACA,UlC3fa,MkCggBjB,cACE,WACA,WlCxhBM,KkCyhBN,yBACA,clCheiB,KkCiejB,QlC5eW,KkC8eX,yBAPF,cAQI,eACA,QlCjfS,MkCsfb,eACE,aACA,mBACA,8BACA,eACA,gCAEA,0BACE,mBAGF,8BACE,eACA,WAEA,yBAJF,8BAKI,UlC9hBW,MkCkiBf,gCACE,aACA,mBACA,uBACA,gBACA,YACA,eACA,mBACA,clCngBiB,KkCogBjB,UlC3iBa,KkC4iBb,cAEA,yBAZF,gCAaI,eACA,UlCjjBS,KkCkjBT,gBhCjlBF,yBgCimBF,yBACE,qBACA,eAIF,oBACE,aACA,gBACA,gCAEA,oCACE,eACA,YlCjkBa,IkCkkBb,kBAGF,uCACE,eACA,WAKJ,eACE,8BACA,kCACA,2BACA,aACA,SACA,gBACA,WlCtnBI,KkCwnBJ,8BACE,sBACA,uBACA,eACA,cACA,kBAEA,8CACE,WACA,YACA,iBACA,kBAIA,wDACE,WACA,YAKN,iCACE,QAGF,iCACE,iBACA,eACA,mBAEA,iDACE,yBACA,MlCzpBA,KkC6pBJ,8BACE,0BACA,YlC5nBa,IkC+nBf,qCACE,0BACA,WACA,gBAKJ,qBACE,mBACA,kBACA,aACA,WAIF,UACE,sBACA,uBACA,QACA,mBAEA,qBACE,gBAIJ,YACE,0BACA,YlC5pBe,IkC6pBf,cACA,WAGF,YACE,WAIF,gBACE,uBACA,0BACA,mBACA,yBACA,kBACA,eAEA,0BACE,OAMF,kCACE,aACA,mBACA,QACA,mBAKJ,iBACE,sBACA,eACA,uBACA,0BACA,YlCnsBe,IkCosBf,eACA,mBACA,kBAEA,uBACE,iD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P,IrC/RK,MqCkSP,6DACE,aACA,mBACA,IrCvSK,IqCySL,+DACE,MrCpWK,QqCuWP,oEACE,MrC7VE,QqC8VF,YrC1TW,IqCiUnB,yCACE,kBACA,WrCnWM,QqCoWN,gCAEA,yBALF,yCAMI,mBAGF,0DACE,aACA,sBACA,IrCjUO,IqCmUP,2EACE,aACA,mBACA,IrCtUK,IqCwUL,6EACE,MrCzXE,QqC0XF,qBACA,UrCnWK,KqCoWL,WrClSQ,aqCmSR,aACA,mBACA,IrChVG,IqCkVH,yBATF,6EAUI,UrC3WG,MqC8WL,mFACE,MrCjZG,QqCmZH,qFACE,2BAIJ,+EACE,MrCzZG,QqC0ZH,WrCrTM,aqC6ThB,qCACE,kBACA,iBAEA,yBAJF,qCAKI,mBAGF,mDACE,UrCvYW,KqCwYX,MrCjaM,QqCkaN,YrCtXc,IqCuXd,qBAEA,yBANF,mDAOI,UrC9YO,MqCqZf,uBACE,WrC3aM,KqC4aN,crClXiB,KqCmXjB,WrClaU,+BqCmaV,gBACA,crC/XW,KqCgYX,8BAEA,wCACE,kBACA,iCACA,gCAEA,yBALF,wCAMI,QrCzYO,MqC4YT,wDACE,aACA,mBACA,IrCjZO,IqCkZP,UrCvaS,KqCwaT,YrC9ZiB,IqC+ZjB,MrCpcM,QqCqcN,crCrZO,IqCuZP,0DACE,MrC9cO,QqCkdX,uDACE,UrCrbS,KqCsbT,MrC7cM,QqC+cN,yBAJF,uDAKI,UrC1bO,MqC+bb,yCACE,QrCnaU,KqCoaV,UrC/ba,KqCgcb,MrCzdQ,QqC0dR,YrC9agB,IqC+ahB,qBAEA,yBAPF,yCAQI,QrC5aO,KqC6aP,UrCvcS,MqC6cf,wBACE,aACA,uBACA,IrCvbW,KqCwbX,crCpbY,KqCqbZ,eAEA,yBAPF,wBAQI,mBACA,qBAGF,oCACE,kBACA,crCvbe,IqCwbf,UrC3da,KqC4db,YrCjdiB,IqCkdjB,qBACA,WrC7Zc,aqC8Zd,oBACA,mBACA,uBACA,IrC3cS,IqC4cT,eACA,YAEA,yBAdF,oCAeI,kBACA,UrCzeS,MqC4eX,6CACE,WrClgBE,KqCmgBF,MrCtgBM,QqCugBN,yBAEA,mDACE,WrCtgBE,QqCugBF,arCthBO,QqC0hBX,6CACE,MrC7gBE,KqC+gBF,mDACE,2BACA,WrCtgBI,+BqC0gBR,+CACE,WrChiBU,QqCiiBV,MrCvhBE,KqCyhBF,qDACE,2BACA,WrChhBI,+BqCohBR,sCACE,eAQN,mBACE,+BACA,WrCziBQ,QqC0iBR,kBAEA,yBALF,mBAMI,mBAIJ,wBACE,cAGF,mBACE,WrCvjBM,KqCwjBN,crC9fiB,KqC+fjB,WrC9iBU,+BqC+iBV,QrCxgBY,KqCygBZ,crC3gBW,KqC6gBX,yBAPF,mBAQI,mBAGF,+BACE,crCjhBU,KqCmhBV,0CACE,gBAGF,2CACE,cACA,UrCpjBW,KqCqjBX,YrCziBiB,IqC0iBjB,MrC/kBM,QqCglBN,crC/hBO,KqCiiBP,yBAPF,2CAQI,UrC3jBO,MqC8jBT,2DACE,YACA,MrC/lBQ,QqCgmBR,YrC1iBK,IqC8iBT,2CACE,WACA,QrC9iBO,KqC+iBP,yBACA,crCriBa,IqCsiBb,UrCzkBW,KqC0kBX,WrCzgBY,aqC2gBZ,yBARF,2CASI,UrC9kBO,KqC+kBP,kBAGF,iDACE,aACA,arCvnBO,QqCwnBP,uCAGF,wDACE,MrC/mBK,QqCmnBT,8CACE,WACA,iBACA,QrCvkBO,KqCwkBP,yBACA,crC9jBa,IqC+jBb,UrClmBW,KqCmmBX,YrCzmBgB,6HqC0mBhB,gBACA,WrCpiBY,aqCsiBZ,yBAXF,8CAYI,UrCzmBO,KqC0mBP,iBACA,kBAGF,oDACE,aACA,arCnpBO,QqCopBP,uCAGF,2DACE,MrC3oBK,QqCopBb,sBACE,aACA,uBACA,IrCxmBW,KqCymBX,WrCvmBW,KqCymBX,yBANF,sBAOI,8BACA,qBCtqBJ,mBACE,iBACA,UpCDE,yBoCDJ,mBAKI,WAKA,8DACE,UAEA,yBAHF,8DAII,gBASN,yBAHF,wBAII,mBAKJ,oBACE,UtCQc,KsCPd,YtCeiB,IsCdjB,MtCxBU,QsCyBV,kBACA,ctC2BY,KE5DV,yBoC4BJ,oBAQI,eACA,ctCqBS,MsChBb,oBACE,aACA,mBACA,8BACA,kBACA,mBACA,ctCmBiB,IsClBjB,ctCSW,KsCPX,uBACE,eACA,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AEA,oBACE,WACA,YACA,cAGF,iCACE,aAIJ,iBACE,iBACA,WtClgBc,QsCmgBd,MtCzfM,KsC0fN,YACA,ctClciB,IsCmcjB,eACA,eACA,WtCvagB,asCyahB,uBACE,8BAKJ,iBACE,WtCzdW,KsC0dX,QtC1dW,KsC2dX,ctCjdiB,IsCmdjB,mBACE,SACA,UtCxfW,KsCyfX,MtChhBQ,QsCihBR,gBAEA,qBACE,WtCteO,IsCyeT,2BACE,aACA,MtCliBU,QsCmiBV,YtCrfa,IsC2fnB,oBACE,aACA,ItCjfW,KsCkfX,uBACA,WtC/eY,KsCgfZ,YtCjfY,KsCkfZ,6BAEA,yBACE,YACA,kBACA,uBpCnjBA,yBoCwiBJ,oBAgBI,mBACA,SACA,WtC/fU,KsCggBV,YtCjgBS,KsCkgBT,gBAEA,yBACE,OACA,WACA,gBACA,YACA,iBACA,eACA,gBACA,kBAIF,8EAEE,mBACA,cACA,YAEA,0FACE,mBACA,cAKJ,iCACE,mBACA,WACA,YAEA,uCACE,mBACA,YAOR,wBACE,WtC/iBW,IsCgjBX,iBACA,ctCtiBiB,IsCuiBjB,UtC1kBa,KsC4kBb,gCACE,gCACA,MtC5mBW,QsC+mBb,8BACE,gCACA,MtClnBY,QsCunBhB,qBACE,eACA,MACA,OACA,WACA,YACA,6BACA,aACA,mBACA,uBACA,aAEA,4BACE,aAGF,8BACE,WACA,YACA,oCACA,iBtCjoBI,KsCkoBJ,kBACA,kCAIJ,gBACE,GACE,0BpCnpBA,yBoCypBF,gBACE,WAGF,oBACE,WAEA,yEAEE,OACA,YACA,WACA,UtCtoBS,KsCuoBT,iBAIJ,sBACE,WAEA,sCACE,YAMN,gBACE,WtC3qBM,KsC4qBN,sBACA,mBACA,kBACA,aACA,sBACA,SACA,mBAIF,uBACE,aACA,mBACA,eAIF,mBACE,WACA,gBACA,WAIF,sBACE,aACA,sBACA,SAIF,oBACE,aACA,mBACA,8BACA,QACA,YAIF,0BACE,aACA,mBACA,QACA,eACA,OACA,iBAIF,0BACE,kBACA,UACA,oBAEA,6DACE,kDAGF,2DACE,0CAKJ,2BACE,kBACA,WACA,YACA,eACA,YACA,kBACA,oDACA,wBACA,4BACA,2BACA,WtCjqBgB,asCkqBhB,UAIF,yBACE,eACA,gBACA,cACA,oBACA,aACA,mBACA,QAEA,4CACE,eACA,gBACA,cAKJ,oBACE,cACA,gBAIF,iBACE,cAIF,uBACE,aACA,mBACA,uBACA,WACA,YACA,eACA,YACA,yBACA,MtCpyBU,QsCqyBV,eACA,WtC7sBgB,asC8sBhB,yBAEA,yBACE,eACA,8BAGF,6BACE,MtC3zBW,QsC8zBb,8BACE,MtC/zBW,QsCi0BX,gCACE,yBAMN,mBACE,WtC7wBW,KsC8wBX,WtCl0BQ,QsCq0BV,kBACE,iBACA,gBACA,QtCnxBW,KsCqxBX,qCACE,UAGF,2CACE,WtCt0BU,QsCu0BV,ctClxBe,IsCqxBjB,2CACE,WtC/0BS,QsCg1BT,ctCvxBe,IsCyxBf,iDACE,WtCp1BM,QsCy1BV,sBACE,UtCn0BW,KsCo0BX,gBACA,MtC51BQ,QsC81BR,sJACE,MtCh2BM,QsCi2BN,WtC/yBO,KsCgzBP,ctCjzBO,KsCkzBP,YtC9zBiB,IsCi0BnB,wBACE,ctCtzBO,KsCyzBT,kDACE,YtCzzBO,KsC0zBP,ctC3zBO,KsC8zBT,4BACE,WACA,yBACA,ctCj0BO,KsCm0BP,8DACE,yBACA,QtCt0BK,IsCu0BL,gBAGF,+BACE,WtCt3BG,QsCu3BH,YtCv1Be,IE5CnB,yBoC24BF,gBACE,kBACA,SAGF,uBACE,cAGF,sBACE,SAGF,oBACE,YACA,gBAGF,yBACE,UtC/3BW,KsCi4BX,4CACE,UtCj4BW,KsCq4Bf,kBACE,iBACA,QtC/2BS,MuC1Db,aACE,cvC8DY,KuC7DZ,2CAFF,aAKI,cvCyDU,MuCtDZ,kCACE,gBACA,cAGF,yBACE,UvCyBY,KuCxBZ,YvCgCe,IuC/Bf,MvCPQ,QuCQR,cvCyCS,KuCxCT,aACA,mBACA,uBACA,IvCqCS,KExDT,yBqCWF,yBAWI,UvCcU,KuCbV,sBACA,IvC+BO,KuC5BT,2BACE,MvChCS,QuCiCT,UvCQU,KErCZ,yBqC2BA,2BAKI,UvCIQ,MuCCd,+BACE,UvCJW,KuCKX,MvC/BQ,QuCgCR,YvCUiB,IElDjB,yBqCqCF,+BAMI,UvCXW,MuCcb,0CACE,MvCnDS,QuCoDT,YvCJiB,IuCKjB,kBAEA,iDACE,WACA,kBACA,YACA,OACA,QACA,WACA,kBAOR,4BACE,gBACA,crCnEE,yBqCiEJ,4BAKI,gBAKJ,kBACE,QvCfY,KuCgBZ,WvCnEM,KuCoEN,cvCTiB,KuCUjB,WvCzDU,gCuC0DV,yBrChFE,yBqC2EJ,kBAQI,kBACA,cvCjBe,KuCqBjB,8BACE,cvC9BU,KE3DV,yBqCwFF,8BAII,cvClCO,MuCqCT,oCACE,aACA,mBACA,IvC3CO,IuC4CP,UvCnEW,KuCoEX,YvCxDiB,IuCyDjB,MvC9FM,QuC+FN,cvC9CO,KuCgDP,sCACE,MvC7GO,QuC8GP,UvCzEO,KuC4ET,8CACE,MvC9GQ,QuC+GR,gBAKJ,oDACE,iBACA,gBACA,gBrCvHF,yBqCoHA,oDAMI,kBAMN,6BACE,aACA,uBACA,IvC7ES,IuC8ET,WvC7ES,IuC8ET,UvCtGW,KuCuGX,MvC9HQ,QuC+HR,YvCrFiB,IuCuFjB,+BACE,MvC9IS,QuC+IT,eACA,cAKJ,8BACE,aACA,uBACA,IvC9FS,IuC+FT,WvC9FS,IuC+FT,UvCvHW,KuCwHX,MvCvJY,QuCwJZ,YvCtGiB,IuCwGjB,gCACE,eACA,cAKJ,kCACE,WvC1GS,KuC4GT,oCACE,aACA,uBACA,IvCjHO,IuCkHP,aACA,UvC3IS,KuC4IT,MvCjKO,QuCkKP,YvCzHe,IuC2Hf,sCACE,MvC7KO,QuC8KP,eACA,cAWJ,mEACE,MvCrLM,QuCsLN,8BACA,avClMS,QuCmMT,YvCpJe,IuCwJnB,yBACE,wDACE,QAGF,0DACE,QAGF,wDACE,SAQJ,sCACE,avC1NW,QuC2NX,uCACA,aAIF,8CACE,wCAKJ,aACE,aACE,cvC1KS,KuC6KX,kBACE,gBACA,yBACA,QvChLS,KuCmLX,cACE,aAIA,4EAEE,cCrPN,2BACE,iBACA,cACA,eACA,cxCwDY,KwCtDZ,yBANF,2BAOI,mBAKJ,wBACE,aACA,8BACA,mBACA,cxC4CY,KwC1CZ,yBANF,wBAOI,sBACA,uBACA,IxCqCS,MwChCX,0BACE,UxCKW,KwCJX,MxCnBQ,QwCoBR,YxCckB,IwCblB,cxCyBS,IwCtBX,0BACE,UxCGY,KwCFZ,YxCWe,IwCVf,MxC5BQ,QwCgCZ,yBACE,aACA,IxCeW,KwCZb,uBACE,kBACA,UxCba,KwCcb,WxCUW,KwCCX,uCACE,mBAGA,mDACE,MxCxDM,QwCyDN,qBACA,WxCgCY,awC9BZ,yDACE,MxCxEO,QEIX,yBsCyEF,2CAII,iBxCnEE,KwCoEF,WAIA,4DACE,YAOR,oBACE,oBACA,YACA,mBACA,QACA,iBACA,sCACA,MxC/Fc,QwCgGd,cxChCiB,IwCiCjB,UxCpEa,KwCqEb,YxCzDmB,IwC0DnB,gBAGA,yBAdF,oBAeI,cAKJ,kBACE,kBACA,kBACA,MxCzGU,QwC2GV,8BACE,eACA,cxC5DS,KwC6DT,WAGF,qBACE,UxCvFW,KwCwFX,YxC/EmB,IwCgFnB,MxCrHQ,QwCsHR,cxCrES,KwCwEX,oBACE,UxCjGa,KwCkGb,MxC1HQ,QwCkIZ,uBACE,gBACA,cACA,QxClFY,KwCoFZ,yBALF,uBAMI,mBAKJ,mBACE,MxC5IM,KwC6IN,cxC7FW,KwC8FX,kBACA,gBAEA,2BACE,WACA,kBACA,SACA,WACA,YACA,aACA,+BACA,cxC3FmB,IwC8FrB,gCACE,YAIJ,qBACE,aACA,mBACA,IxCpHW,KwCqHX,kBACA,UAEA,yBAPF,qBAQI,sBACA,mBAIJ,qBACE,cAGF,qBACE,YACA,aACA,cxCvHqB,IwCwHrB,aACA,mBACA,uBACA,UxC9Jc,KwC+Jd,YxCvJiB,IwCwJjB,8BACA,MxC5LM,KwC6LN,oCACA,WxClLU,gCwCqLZ,mBACE,OAEA,sBACE,UxC3KY,KwC4KZ,YxCnKe,IwCoKf,cxC1JS,IwC6JX,uCACE,UxCrLa,KwCsLb,WACA,cxC/JS,KwCmKb,qBACE,aACA,IxCtKW,IwCuKX,eAEA,yBALF,qBAMI,wBAGF,8EAEE,8BACA,MxC7NI,KwC8NJ,2BACA,oCAKJ,gBACE,WxCrOM,KwCsON,cxC5KiB,KwC6KjB,QxCvLW,KwCwLX,WxC9NU,0BwC+NV,cxC1LW,KwC4LX,mBACE,UxCpNW,KwCqNX,YxC3MmB,IwC4MnB,MxCjPQ,QwCkPR,cxChMS,KwCiMT,exCnMS,IwCoMT,gCAIJ,aACE,aACA,qCACA,IxCzMW,KwC2MX,yBALF,aAMI,2BAIJ,aACE,aACA,sBACA,IxCtNW,IwCwNX,wBACE,iBAIJ,cACE,UxCrPa,KwCsPb,YxCzOqB,IwC0OrB,MxC9QU,QwC+QV,aACA,mBACA,IxCnOW,IwCqOX,gBACE,MxChSW,QwCiSX,UxC9PW,KwCkQf,cACE,UxClQe,KwCmQf,MxC5RU,QwC6RV,cAGF,kBACE,oBACA,mBACA,IxCpPW,IwCqPX,iBACA,cxC1OiB,IwC2OjB,UxC9Qa,KwC+Qb,YxCnQmB,IwCqQnB,gCACE,sCACA,6CAEA,4CACE,iBxCpTS,QwCwTb,kCACE,sCACA,MxCnTQ,QwCqTR,8CACE,iBxCtTM,QwC2TZ,YACE,UACA,WACA,cxC9PqB,IwC+PrB,4BAGF,iBACE,QACE,UAEF,IACE,YAKJ,gBACE,aACA,IxC9RW,KwC+RX,uBACA,YxC/RW,KwCiSX,yBANF,gBAOI,uBAOJ,sBACE,oBACA,mBACA,uBACA,YACA,eACA,mBACA,eACA,YxC7TiB,IwC8TjB,MxCjWM,KwCkWN,mBAGA,iCACE,yBAIF,+BACE,yBAKJ,iBACE,aACA,uBACA,WxClUY,KwCwUd,iBACE,aACA,IxC/UW,IwCgVX,iBAGF,gBACE,aAOF,yBAEE,iBACE,wBAGF,gBACE,wBAGF,2BACE,aACA,sBACA,IxCrWS,KwCsWT,kBACA,gBAGA,2CACE,iBAEA,wDACE,QACA,gBACA,eACA,MxC5ZA,KwC6ZA,mBACA,kCAEA,+DACE,cACA,YxCnYS,IwCoYT,eAKJ,uDACE,QACA,WAEA,4DACE,aACA,mBACA,uBACA,WACA,YAMN,uCACE,QACA,WxC7bE,KwC8bF,cxCpYa,KwCqYb,iBAGA,0DACE,aACA,mBACA,YACA,cACA,mBACA,0BAEA,uEACE,eACA,YxCzaS,IwC0aT,MxC7cF,KwC8cE,cACA,uBAGA,oFACE,aAIF,oFACE,sBACA,eACA,UAIF,oFACE,kBACA,eAIF,oFACE,sBACA,eACA,UAIF,oFACE,sBACA,eACA,UAIF,oFACE,wBACA,sBACA,eACA,UAMN,wDACE,aACA,sBAIF,uDACE,aACA,mBACA,mBACA,YACA,cACA,MACA,kBAGA,uEACE,yBAGF,sEACE,iBxCjhBF,KwCohBA,6DACE,oCAGF,iEACE,eACA,YxC1fY,IwC2fZ,cACA,cACA,uBACA,mBACA,gBACA,uBAGA,yEACE,aAIF,8EACE,aAIF,8EACE,sBACA,eACA,UAEA,gFACE,cACA,qBACA,eAKJ,8EACE,kBACA,eACA,2BAIF,8EACE,sBACA,eACA,UAIF,8EACE,sBACA,eACA,UAIF,2JAEE,wBACA,sBACA,eACA,UACA,uBACA,iBAKJ,oEACE,wBAMN,6CACE,kBAEA,yDACE,eAGF,gDACE,UxCllBO,KwCqlBT,+CACE,UxCzlBO,KwC8lBX,uCACE,QACA,SACA,eAEA,uDACE,WACA,YAEA,oHACE,eACA,WACA,YAIJ,2DACE,SAGF,0DACE,eACA,YACA,eACA,cACA,YxC5mBc,IwC8mBd,iEACE,YxC5mBS,IwC6mBT,cAeA,iFACE,aAIF,iFACE,kBACA,gBAIF,iFACE,sBACA,eACA,UACA,uBAIF,iFACE,sBACA,eACA,UACA,uBAIF,iFACE,aAQF,0JAEE,aAIF,yJAEE,kBACA,gBACA,2BAEA,+LACE,aACA,mBACA,QACA,gBAGA,6NACE,wBAGF,iRACE,gBACA,uBACA,mBAGF,qNACE,cACA,WACA,YAEA,6NACE,WACA,YAOR,2EACE,sBACA,eACA,UACA,uBACA,kBAIF,2EACE,sBACA,eACA,UACA,uBACA,kBAEA,iGACE,YACA,cACA,eAKJ,2EACE,aAOR,yCACE,QACA,WACA,aAEA,8CACE,WACA,YACA,aACA,mBACA,uBAMF,sDACE,QACA,WAcE,gFACE,aAIF,gFACE,kBACA,gBAIF,gFACE,sBACA,eACA,UAQF,wJAEE,aAIF,uJAEE,kBACA,gBACA,2BAEA,6LACE,aACA,mBACA,QACA,gBAGA,2NACE,wBAGF,+QACE,gBACA,uBACA,mBAGF,mNACE,cACA,WACA,YAEA,2NACE,WACA,YAOR,0EACE,sBACA,eACA,UACA,uBACA,kBAQN,qDACE,QACA,WASN,wBACE,kBAGA,gLACE,cxC71BO,KwC+1BP,yLACE,UxCv3BO,KwC43BX,iDACE,2BACA,uBACA,oCACA,qBAIF,sCACE,aACA,8BACA,kCACA,8BACA,iBACA,aAEA,4DACE,WACA,aACA,uBAEA,iEACE,aACA,mBACA,uBACA,WACA,YACA,gBAUV,kBACE,kBACA,oBACA,mBACA,uBAEA,mCACE,aACA,mBACA,uBACA,WACA,YACA,UACA,yBACA,YACA,cxCh5Be,IwCi5Bf,cACA,eACA,WxCp3Bc,awCs3Bd,yCACE,6BACA,MxC79BS,QwCg+BX,yCACE,aACA,6BAGF,uCACE,WACA,YAIJ,iCACE,kBACA,SACA,QACA,gBACA,WxCl+BI,KwCm+BJ,cxC16Be,IwC26Bf,sCACA,QxCz5Be,IwC05Bf,UACA,kBACA,2BACA,wBACA,gBAEA,gDACE,cACA,WACA,kBACA,yBACA,YACA,gBACA,eACA,YxCp9BgB,IwCq9BhB,cACA,eACA,WxC/5BY,cwCg6BZ,mBAEA,sDACE,mBACA,MxC1gCO,QwC6gCT,uDACE,mBAGF,+DACE,0BAOJ,wCACE,6BACA,MxC3hCS,QwC8hCX,sCACE,UACA,mBACA,0BC7hCN,sBACE,iBACA,cAGF,MACE,iBACA,cACA,WzCEM,KyCDN,QzCgDW,KyC/CX,czCyDiB,IyCxDjB,WzCUU,+ByCPZ,YACE,qBACA,azCuCW,IyCtCX,czCuCW,KyCnCb,WACE,mBACA,aACA,czC2CiB,IyC1CjB,mBAEA,mBACE,aACA,mBACA,SACA,eAGF,iBACE,YzCQiB,IyCPjB,MzC7BQ,QyC8BR,eAGF,yBACE,iBACA,yBACA,czCuBe,IyCtBf,UzCbW,KyCcX,gBAIJ,aACE,aACA,mBACA,SAGA,6BACE,aACA,mBACA,SAEA,0CACE,kBAKN,QACE,iBAEA,kBACE,gBAKJ,UACE,aAIF,mBACE,cACA,gBACA,gBAGF,UACE,gBAIF,WACE,MzC5Fc,QyC+FhB,YACE,cAGF,eACE,cAIF,kBACE,gBACA,gBAGF,O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C8bT,Y1C7Ya,I0CoZjB,iDACE,UAGF,uDACE,W1C3bM,Q0C4bN,c1CrYe,I0CwYjB,uDACE,W1C/cW,Q0CgdX,c1C1Ye,I0C4Yf,6DACE,W1CldW,Q2CmBjB,eACE,iBACA,cACA,aACA,sBACA,WACA,+DACA,aACA,sBACA,SAEA,sBACE,SACA,eACA,gBACA,gBACA,uBAGF,qBACE,SACA,eACA,gBACA,gBACA,uBAGF,qBACE,SACA,eACA,gBACA,gBAGF,uBACE,YAGF,8BACE,SACA,eACA,gBACA,gBAGF,6BACE,eACA,gBACA,gBAEA,+BACE,SAEA,iCACE,eAKN,qBACE,SACA,kBACA,gBACA,eACA,gBACA,gBAEA,2BACE,eAKN,yBACE,eACE,aACA,SAEA,qCACA,oCACA,6CACA,uEAEU,gBASd,cACE,iBACA,cACA,uBACA,iBA3GW,QA8GX,sBACE,mBAGF,qBACE,eACA,gBACA,cACA,MA1HmB,QA2HnB,kBAGF,yBACE,WACA,WACA,iBAjImB,QAkInB,mBAIA,sBACE,eACA,gBACA,kBACA,MArIc,QAsId,SAKJ,oBACE,gBACA,mBACA,kBACA,WA3IqB,8DA4IrB,mBAGF,qBACE,aACA,qCACA,SAGF,oBACE,aACA,sBACA,mBACA,kBAGE,mDACE,aAKN,yBACE,WACA,YACA,kBACA,gBACA,2BACA,aACA,mBACA,uBACA,kBACA,UAEA,6BACE,WACA,YAIJ,wBACE,WACA,YACA,iBAhMmB,QAiMnB,kBACA,gBACA,kBACA,UAGF,0BACE,aACA,sBACA,mBACA,eACA,kBAGF,wBACE,eACA,gBACA,cACA,iBAGF,yBACE,eACA,gBACA,MApNgB,QAqNhB,iBACA,eACA,mBAGF,yBACE,kBACA,SACA,sBACA,wBACA,WACA,qEACA,UAIF,qBACE,mBAGF,oBACE,aACA,uBACA,QACA,eACA,gBACA,iBACA,MAhPgB,QAiPhB,kBAEA,+BACE,gBAIJ,yBACE,cACA,WAIF,sBACE,kBACA,iBAGF,mBACE,qBACA,kBACA,WA3QmB,QA4QnB,WACA,eACA,gBACA,kBACA,qBACA,kDAEA,yBACE,2BACA,2BAGF,0BACE,wBASN,0BACE,cACE,uBAEA,qBACE,eAGF,qBACE,qCACA,cAKE,0DACE,aAKN,yBACE,yBAKN,yBACE,cACE,uBAEA,sBACE,mBAGF,qBACE,eAGF,yBACE,WACA,WACA,mBAIA,sBACE,eACA,iBAIJ,oBACE,kBACA,mBAGF,qBACE,qCACA,cAKE,2DACE,aAIF,0DACE,cAKN,yBACE,WACA,YAEA,6BACE,WACA,YAIJ,wBACE,WACA,YAGF,yBACE,eACA,mBAGF,yBACE,SACA,sBACA,wBAGF,qBACE,mBAGF,oBACE,eACA,iBAGF,mBACE,kBACA,eACA,WACA,iBAKN,yBAEI,qBACE,0BACA,SAGF,oBACE,mBACA,mBACA,SAEA,6CACE,wBAIJ,yBACE,cAGF,wBACE,aAGF,0BACE,uBACA,aACA,iBASN,sBACE,uBACA,kBACA,6DAGE,4CACE,eACA,gBACA,iBACA,uBACA,cACA,mBAGF,6CACE,eACA,gBACA,iBACA,cAKN,qBACE,kBACA,kBACA,gBAEA,yBACE,eACA,YACA,kBACA,qCAIJ,oBACE,kBACA,kBAEA,+BACE,eACA,iBACA,cACA,mBAEA,sCACE,cACA,gBAIJ,iCACE,eACA,cACA,gBAIJ,sBACE,aAEA,4BACE,WACA,yBACA,yBACA,kBACA,gBAEA,8DACE,aACA,eACA,iBACA,gCAGF,+BACE,mBACA,cACA,gBACA,kBACA,sBAGF,+BACE,cACA,gBAGF,0FAEE,mBAKN,sBACE,aACA,kBAEA,mCACE,eACA,cACA,gBACA,iBAIJ,sBACE,uBACA,kBAEA,oCACE,qBACA,kBACA,mBACA,WACA,eACA,gBACA,kBACA,qBACA,+BAEA,0CACE,mBASN,yBACE,sBACE,uBAGE,4CACE,eACA,iBAGF,6CACE,eACA,iBAKN,qBACE,kBAEA,yBACE,gBAIJ,oBACE,aAEA,+BACE,eACA,iBAGF,iCACE,eAIJ,sBACE,aACA,gBACA,cAGE,8DACE,kBACA,eAKN,sBACE,kBAEA,mCACE,eAIJ,sBACE,uBAEA,oCACE,kBACA,gBA6BN,gBACE,iBACA,cACA,aACA,sBACA,MAnBgB,QAoBhB,+DACA,aACA,sBACA,SACA,sBAGA,yBACE,qBACA,eACA,gBACA,MAnCc,QAoCd,qBACA,kBAGF,oBACE,eACA,eACA,gBACA,gBACA,MAzCc,QA4ChB,sBACE,gBACA,eACA,gBACA,MA/Cc,QAmDhB,sBACE,kBACA,aACA,gCACA,SACA,kBACA,mBACA,yBACA,6DAGF,8BACE,oBACA,mBACA,SACA,iBACA,mBACA,gBACA,yBACA,MA3Ec,QA4Ed,eACA,gBACA,mBAGF,kCACE,UACA,WACA,kBACA,WArFc,QAwFhB,4BACE,gBACA,eACA,gBACA,MAxFc,QAyFd,sBACA,gBACA,mBAGF,2BACE,SACA,eACA,gBACA,MAjGc,QAkGd,gBAEA,qEAGF,4BACE,aACA,SACA,gBACA,eAGF,sBACE,oBACA,mBACA,iBACA,mBACA,gBACA,yBACA,eACA,gBACA,MAxHc,QA0Hd,+BACE,WA/HY,QAgIZ,aAhIY,QAiIZ,WAIJ,6BACE,kBACA,gBACA,yBACA,mBACA,gBAEA,sEAIF,6BACE,aACA,kCACA,SAGF,6BACE,aACA,+BACA,SACA,kBACA,aACA,gBACA,yBACA,mBAGF,4BACE,oBACA,mBACA,uBACA,WACA,YACA,kBACA,WAxKc,QAyKd,WACA,eACA,gBAGF,8BACE,iBACA,eACA,gBACA,MA9Kc,QAiLhB,+BACE,SACA,eACA,gBACA,MApLc,QAqLd,gBAEA,gDAIF,kCACE,aACA,gBACA,yBACA,mBAEA,2EAIF,2BACE,aACA,8BACA,SAGF,8BACE,aACA,oCACA,mBACA,SACA,kBACA,mBACA,gBACA,yBACA,mBAGF,wBACE,qBACA,iBACA,kBACA,WAhOc,QAiOd,WACA,eACA,gBACA,oBAGF,+BACE,kCACA,eACA,MAtOc,QAyOhB,uCACE,qBACA,iBACA,mBACA,WA9Oc,QA+Od,yBACA,MA9Oc,QA+Od,eACA,gBAIF,uBACE,kBACA,gBACA,yBACA,mBAGF,6BACE,gBACA,eACA,gBACA,MAhQc,QAmQhB,gCACE,mBACA,eACA,gBACA,MAvQc,QA0QhB,uBACE,WACA,yBACA,eAEA,6BACE,WAjRY,QAoRd,0BACE,gBACA,iBACA,eACA,gBACA,MAvRY,QAwRZ,oBAGF,0BACE,iBACA,gCACA,MA9RY,QA+RZ,sBAGF,iEAEA,4BACE,kCACA,eACA,gBACA,MAzSY,QA6SZ,yDACA,2DACA,2DACA,wCA/SY,QAmThB,4BACE,yBAGF,2BACE,qBACA,eACA,iBACA,kBACA,kBACA,eACA,gBACA,WAEA,gDAtUc,QAuUd,gDAjUc,QAoUhB,yBACE,gBACA,iBACA,kBACA,WAjUc,QAkUd,MAjUc,QAkUd,eACA,gBAIF,4BACE,kBACA,kBACA,mBACA,WArVc,QAsVd,gBAEA,oCACE,WACA,kBACA,iBACA,UACA,WAjWY,QAoWd,sDAxVc,QA2VhB,0BACE,qBACA,iBACA,mBACA,WA3Wc,QA4Wd,WACA,eACA,gBACA,mBAEA,yCAjXc,QAkXd,4CAtWc,QAyWhB,4BACE,SACA,WACA,kCACA,eACA,gBACA,gBACA,gBAIF,+BAxXgB,QAyXhB,+BAxXgB,QAyXhB,+BAxXgB,QAyXhB,+BAxXgB,QAyXhB,gCA9XgB,QAiYhB,4BACE,kBACA,kBACA,WAvYc,QAwYd,kBAGF,4BACE,cACA,kCACA,eACA,gBACA,MAnZc,QAoZd,kBAGF,8BACE,cACA,kCACA,eACA,MAvZc,QA0ZhB,sBACE,gBACA,SACA,UAEA,yBACE,kBACA,kBACA,eACA,gBACA,MAnaY,QAoaZ,gBAEA,iCACE,WACA,kBACA,SACA,QACA,UACA,WACA,kBACA,WAnbU,QAsbZ,2CAKJ,8BACE,qBACA,eACA,iBACA,mBACA,kBACA,eACA,gBAEA,8CArbc,QAqbsB,MAxbtB,QAybd,8CAxbc,QAwbuB,MAvbvB,QA2bhB,4BACE,gBACA,iBACA,kBACA,WA9bc,QA+bd,MA9bc,QA+bd,eACA,gBAIF,qBACE,kBACA,cACA,kBACA,mBACA,4DACA,WACA,qBACA,gBACA,kDAEA,2BACE,2BACA,0CAIJ,sDAEA,6BACE,qBACA,eACA,gBACA,MA1ec,QA2ed,qBACA,mBAGF,2BACE,gBACA,eACA,gBACA,WACA,gBAGF,0BACE,gBACA,eACA,gBACA,MA3fc,QA8fhB,4BACE,oBACA,mBACA,kBACA,mBACA,gBACA,MArgBc,QAsgBd,eACA,gBAGF,0BACE,kBACA,kBACA,+BAEA,8BACE,YACA,aACA,WACA,YAGF,8BACE,WACA,YACA,WACA,aACA,8BAKN,0BAEI,sBACE,0BACA,aAEF,6CACA,uDACA,qDACA,8BACE,0BACA,QAEA,2HACyC,mBAGzC,4IAEkB,YAKxB,yBACE,gBACE,aACA,SAEA,wCACA,2CACA,mCACA,uCACA,0CACA,yCACA,+CCttCJ,0BACE,cACA,iBAIF,2BACE,aACA,yBACA,mBAGF,mBACE,aACA,mBACA,SACA,gBACA,iBACA,mBACA,yBAGF,mBACE,aACA,mBACA,QAGF,YACE,YACA,iBACA,eACA,WACA,yBACA,aAGF,gBACE,WAGF,YACE,mBACA,YACA,kBACA,WACA,YACA,aACA,mBACA,uBACA,eACA,0BAEA,kBACE,mBAGF,gBACE,YAKJ,yBACE,mBACA,mBACA,aACA,mBAGF,iBACE,aACA,mBACA,8BACA,SAGF,cACE,OAGF,oBACE,mBAGF,YACE,4BACA,mBACA,yBACA,gBACA,cACA,eACA,gBACA,eACA,gBACA,0LACA,4BACA,sCACA,gBACA,aACA,mBAEA,kBACE,yBAGF,kBACE,qBACA,uCAIJ,cACE,kBAGF,oBACE,eACA,WACA,mBAEA,2BACE,eACA,cAIJ,qBACE,aACA,sBACA,SAGF,qBACE,aACA,mBACA,QACA,eAEA,0CACE,cAGF,0CACE,cAIJ,cACE,WACA,eAGF,cACE,gBACA,WAGF,gBACE,WAIF,gBACE,eACA,aACA,mBACA,uBAGF,YACE,gBACA,iBAGF,eACE,gEAIF,eACE,eACA,aACA,sBACA,SAGF,cACE,aACA,mBACA,QACA,eACA,gCAEA,yBACE,mBAIJ,gBACE,WACA,YACA,kBAEA,wBACE,mBAGF,wBACE,mBAIJ,YACE,eACA,WAGF,YACE,eACA,gBAGF,0BACE,cAGF,0BACE,cAGF,cACE,eACA,WAIF,uBACE,gBACA,mBACA,gBACA,qCAGF,cACE,aACA,8BACA,mBACA,kBACA,mBACA,WAGF,aACE,aACA,mBACA,SACA,eACA,gBACA,SAEA,iBACE,YAIJ,cACE,aACA,mBACA,QACA,iBACA,8BACA,mBACA,WACA,qBACA,eACA,0BAEA,oBACE,8BACA,WAIJ,iBACE,UAGF,kBACE,WACA,yBAEA,2BACE,kBACA,gBACA,gBACA,WACA,gCACA,mBAIA,2BACE,kBACA,gCACA,WAGF,iCACE,mBAIJ,mCACE,mBAEA,sCACE,kBACA,gBACA,cACA,mBAKN,UACE,aACA,mBACA,SAGF,UACE,aACA,mBACA,uBACA,WACA,YACA,mBACA,kBAEA,cACE,YAGF,gBACE,mBAEA,oBACE,eAMN,eACE,gBAGF,cACE,aACA,YACA,mBACA,kBACA,gBAGF,kBACE,mBACA,0BAGF,kBACE,mBACA,0BAIF,cACE,kBACA,kBACA,WAIF,yBACE,iBACE,sBACA,SAGF,6CAGE,UACA,WAGF,gBACE,SAGF,eACE,mBACA,wBAIJ,yBACE,0BACE,kBAGF,yBACE,kBAGF,oBACE,eAGF,YACE,OACA,kBAIA,iFAGE,kBACA,eAIJ,eACE,gBAGF,mBACE,gBCrbJ,QACE,wBAGF,SACE,yBAGF,gBACE,gCAGF,QACE,wBAGF,eACE,+BAGF,QACE,wBAIF,a3CRE,aACA,uBACA,mB2CUF,c3CNE,aACA,8BACA,mB2CQF,aACE,sBAGF,WACE,eAGF,cACE,mBAGF,aACE,uBAGF,WACE,qBAGF,gBACE,uBAGF,eACE,2BAGF,aACE,yBAGF,iBACE,8BAGF,gBACE,6BAIF,QACE,I7CpBW,I6CuBb,QACE,I7CvBW,I6C0Bb,QACE,I7C1BW,K6C6Bb,QACE,I7C7BW,K6CgCb,QACE,I7ChCW,K6CgDX,KACE,oBAGF,MACE,wBAGF,MACE,2BAGF,MACE,yBAGF,MACE,0BAGF,MACE,yBACA,0BAGF,MACE,wBACA,2BA3BF,MACE,sBAGF,OACE,0BAGF,OACE,6BAGF,OACE,2BAGF,OACE,4BAGF,OACE,2BACA,4BAGF,OACE,0BACA,6BA3BF,MACE,sBAGF,OACE,0BAGF,OACE,6BAGF,OACE,2BAGF,OACE,4BAGF,OACE,2BACA,4BAGF,OACE,0BACA,6BA3BF,MACE,uBAGF,OACE,2BAGF,OACE,8BAGF,OACE,4BAGF,OACE,6BAGF,OACE,4BACA,6BAGF,OACE,2BACA,8BA3BF,MACE,uBAGF,OACE,2BAGF,OACE,8BAGF,OACE,4BAGF,OACE,6BAGF,OACE,4BACA,6BAGF,OACE,2BACA,8BA3BF,MACE,uBAGF,OACE,2BAGF,OACE,8BAGF,OACE,4BAGF,OACE,6BAGF,OACE,4BACA,6BAGF,OACE,2BACA,8BA3BF,OACE,uBAGF,QACE,2BAGF,QACE,8BAGF,QACE,4BAGF,QACE,6BAGF,QACE,4BACA,6BAGF,QACE,2BACA,8BA3BF,OACE,uBAGF,QACE,2BAGF,QACE,8BAGF,QACE,4BAGF,QACE,6BAGF,QACE,4BACA,6BAGF,QACE,2BACA,8BA3BF,OACE,uBAGF,QACE,2BAGF,QACE,8BAGF,QACE,4BAGF,QACE,6BAGF,QACE,4BACA,6BAGF,QACE,2BACA,8BA3BF,QACE,uBAGF,SACE,2BAGF,SACE,8BAGF,SACE,4BAGF,SACE,6BAGF,SACE,4BACA,6BAGF,SACE,2BACA,8BAgBF,KACE,qBAGF,MACE,yBAGF,MACE,4BAGF,MACE,0BAGF,MACE,2BAGF,MACE,0BACA,2BAGF,MACE,yBACA,4BA3BF,MACE,uBAGF,OACE,2BAGF,OACE,8BAGF,OACE,4BAGF,OACE,6BAGF,OACE,4BACA,6BAGF,OACE,2BACA,8BA3BF,MACE,uBAGF,OACE,2BAGF,OACE,8BAGF,OACE,4BAGF,OACE,6BAGF,OACE,4BACA,6BAGF,OACE,2BACA,8BA3BF,MACE,wBAGF,OACE,4BAGF,OACE,+BAGF,OACE,6BAGF,OACE,8BAGF,OACE,6BACA,8BAGF,OACE,4BACA,+BA3BF,MACE,wBAGF,OACE,4BAGF,OACE,+BAGF,OACE,6BAGF,OACE,8BAGF,OACE,6BACA,8BAGF,OACE,4BACA,+BA3BF,MACE,wBAGF,OACE,4BAGF,OACE,+BAGF,OACE,6BAGF,OACE,8BAGF,OACE,6BACA,8BAGF,OACE,4BACA,+BA3BF,OACE,wBAGF,QACE,4BAGF,QACE,+BAGF,QACE,6BAGF,QACE,8BAGF,QACE,6BACA,8BAGF,QACE,4BACA,+BA3BF,OACE,wBAGF,QACE,4BAGF,QACE,+BAGF,QACE,6BAGF,QACE,8BAGF,QACE,6BACA,8BAGF,QACE,4BACA,+BA3BF,OACE,wBAGF,QACE,4BAGF,QACE,+BAGF,QACE,6BAGF,QACE,8BAGF,QACE,6BACA,8BAGF,QACE,4BACA,+BAKJ,MACE,UAGF,MACE,UAGF,MACE,UAGF,OACE,WAGF,QACE,WAIF,MACE,WAGF,MACE,WAGF,MACE,WAGF,OACE,YAGF,QACE,YAGF,QACE,aAIF,mBACE,kBAGF,mBACE,kBAGF,gBACE,eAGF,iBACE,gBAIF,YACE,W7CzPa,Q6C4Pf,cACE,W7C5Pe,Q6C+PjB,UACE,W7CnPM,K6CsPR,SACE,W7CtPQ,Q6CyPV,UACE,W7CzPS,Q6CkQX,QACE,yBAGF,UACE,uBAGF,YACE,6BAGF,eACE,gCAGF,aACE,8BAGF,cACE,+BAGF,gBACE,a7C3Sa,Q6C+Sf,WACE,gBAGF,YACE,c7C9OiB,I6CiPnB,SACE,c7CjPiB,I6CoPnB,YACE,c7CpPiB,K6CuPnB,YACE,c7CvPiB,K6C0PnB,cACE,c7CzPmB,K6C4PrB,gBACE,c7C5PqB,I6CgQvB,aACE,2BAGF,WACE,W7CzTU,0B6C4TZ,QACE,W7C5TU,+B6C+TZ,WACE,W7C/TU,gC6CkUZ,WACE,W7ClUU,gC6CsUZ,iBACE,gBAGF,eACE,cAGF,mBACE,kBAGF,mBACE,kBAGF,iBACE,gBAGF,iBACE,gBAIF,KACE,UAGF,MACE,WAGF,MACE,WAGF,MACE,WAGF,MACE,WAGF,MACE,W3C3YE,yB2CgZF,SACE,yB3C/YA,0B2CoZF,SACE,yBAKF,yBADF,aAEI,yB3C7ZA,yB2CiaJ,cAEI,yBAKJ,IACE,c3CzaE,yB2CwaJ,IAII,yBAIJ,QACE,a3CjbE,yB2CgbJ,QAII,0BAKJ,gBACE,eAGF,gBACE,eAGF,oBACE,mBAIF,WACE,UAGF,YACE,YAGF,YACE,WAGF,YACE,YAGF,aACE,UAIF,YACE,W7C3XgB,a6C8XlB,iBACE,W7C9XgB,c6CiYlB,iBACE,W7CjYgB,a6CoYlB,iBACE,gBAIF,0BACE,kBACA,UACA,WACA,UACA,YACA,gBACA,sBACA,mBACA","file":"main.min.css"} \ No newline at end of file +{"version":3,"sourceRoot":"","sources":["../sass/base/_reset.scss","../sass/abstracts/_variables.scss","../sass/base/_typography.scss","../sass/abstracts/_mixins.scss","../sass/base/_animations.scss","../sass/base/_djb-font.scss","../sass/layout/_header.scss","../sass/layout/_footer.scss","../sass/layout/_grid.scss","../sass/layout/_container.scss","../sass/components/_buttons.scss","../sass/components/_badges.scss","../sass/components/_cards.scss","../sass/components/_apikey-common.scss","../sass/components/_forms.scss","../sass/components/_modals.scss","../sass/components/_navigation.scss","../sass/components/_partners.scss","../sass/components/_cta.scss","../sass/components/_password-popup.scss","../sass/components/_password-policy.scss","../sass/components/_header-auth.scss","../sass/components/_session-timer.scss","../sass/components/_tables.scss","../sass/components/_accordion.scss","../sass/components/_page-title-banner.scss","../sass/components/_alerts.scss","../sass/components/_toast.scss","../sass/components/_pagination.scss","../sass/components/_breadcrumb.scss","../sass/components/_test-env-notice.scss","../sass/components/_djb-inquiry-comments.scss","../sass/components/_board-common.scss","../sass/components/_two-factor.scss","../sass/pages/_index.scss","../sass/pages/_api-market.scss","../sass/pages/_login.scss","../sass/pages/_account-recovery.scss","../sass/pages/_signup-selection.scss","../sass/pages/_mypage.scss","../sass/pages/_apikey-register.scss","../sass/pages/_apikey-detail.scss","../sass/pages/_apikey-list.scss","../sass/pages/_notice.scss","../sass/pages/_inquiry.scss","../sass/pages/_org-register.scss","../sass/pages/_partnership.scss","../sass/pages/_user-management.scss","../sass/pages/_terms-agreements.scss","../sass/pages/_service.scss","../sass/pages/_api-statistics.scss","../sass/pages/_webhook.scss","../sass/pages/_api-status.scss","../sass/base/_utilities.scss"],"names":[],"mappings":"CAQA,MACE,mCACA,gCAGF,EACE,SACA,UACA,sBAGF,KACE,eACA,mCACA,kCACA,kCAGF,KACE,YCWoB,0GDVpB,YCmCmB,IDlCnB,MCVU,QDWV,iBCRM,KDSN,kBAIF,MACE,gBAIF,EACE,qBACA,cACA,WCoEgB,aDhElB,IACE,eACA,YACA,cAIF,OACE,oBACA,kBACA,oBACA,eACA,yBACA,YACA,UAIF,sBAGE,oBACA,kBACA,oBACA,YACA,aAIF,MACE,yBACA,iBAIF,kBACE,YCzBiB,ID0BjB,YCtBkB,ID0BpB,2FAGE,cAIF,YACE,mCACA,MC9EU,QDiFZ,iBACE,mCACA,MCnFU,QCVZ,KACE,YD4BoB,0GC3BpB,UDkCe,KCjCf,YD2CoB,IC1CpB,MDMU,QCFZ,0CAME,YDgBoB,uHCfpB,YDgCmB,IC1BrB,wEAIE,YDKoB,uHCJpB,YDqBmB,IClBrB,OACE,UDac,KCZd,cD8BW,KE3DT,yBD2BJ,OAKI,UDOY,MEzCZ,yBD6BJ,OASI,UDEY,MCEhB,OACE,UDDc,KCEd,cDgBW,KE1DT,yBDwCJ,OAKI,UDPY,MExCZ,yBD0CJ,OASI,UDZW,MCgBf,OACE,UDfc,KCgBd,cDGW,KE1DT,yBDqDJ,OAKI,UDrBW,MCyBf,OACE,UDzBc,KC0Bd,cDPW,ICUb,OACE,UD/Ba,KCgCb,cDZW,ICeb,OACE,UDrCa,KCsCb,cDjBW,ICqBb,EACE,cDrBW,KCsBX,YD5BmB,IC8BnB,aACE,gBAIJ,cACE,MD3Fa,QC8Ff,gBACE,MD9Fe,QCiGjB,WACE,MDxFU,QC2FZ,WACE,MD3FU,QC8FZ,YACE,MD9FW,QCiGb,YACE,MDjGM,KCqGR,WACE,gBAGF,aACE,kBAGF,YACE,iBAIF,cACE,YDlFoB,ICqFtB,aACE,YDrFmB,ICwFrB,eACE,YDxFqB,IC2FvB,WACE,YD3FiB,IC8FnB,gBACE,YD9FsB,ICkGxB,SACE,UDnHa,KCsHf,SACE,UDtHa,KCyHf,WACE,UDzHe,KC4HjB,SACE,UD3Ha,KC8Hf,SACE,UD9Ha,KCkIf,MACE,UDpIa,KCqIb,YD7HoB,IC8HpB,YDrHkB,ICsHlB,MDlKU,QCqKZ,SACE,UD9Ia,KC+Ib,MDvKU,QC0KZ,MACE,YDvJiB,oCCwJjB,UDpJa,KCqJb,WD1KQ,QC2KR,gBACA,cDpHiB,ICwHnB,EACE,MDhMa,QCkMb,QACE,MDlMa,QCuMjB,WACE,QD5IW,KC6IX,cACA,8BACA,WD7LQ,QC8LR,kBAEA,aACE,gBAKJ,GACE,YACA,6BACA,cEvNF,iBAEE,QAEE,qCAGF,IACE,2CAKJ,kBAEE,QAEE,wBAGF,IACE,6BAKJ,uBACE,KACE,UACA,4BAGF,GACE,UACA,yBAKJ,qBACE,KACE,UACA,2BAGF,GACE,UACA,yBAKJ,uBACE,KACE,UACA,4BAGF,GACE,UACA,yBAKJ,wBACE,KACE,UACA,2BAGF,GACE,UACA,yBAKJ,kBACE,KACE,UAGF,GACE,WAKJ,uBACE,KACE,UACA,qBAGF,GACE,UACA,oBAKJ,gBACE,KACE,uBAGF,GACE,0BAKJ,iBACE,GACE,mBAGF,IACE,sBAGF,KACE,oBAKJ,mBACE,GACE,0CAGF,KACE,0CAKJ,gBACE,GACE,uBAGF,IACE,wBAGF,IACE,wBAGF,IACE,wBAGF,IACE,wBAGF,IACE,wBAGF,IACE,uBAGF,KACE,wBAKJ,iBACE,GACE,UACA,qBAGF,IACE,qBAGF,KACE,UACA,oBAKJ,uBACE,KACE,UACA,sBAGF,GACE,UACA,oBAKJ,kBACE,GACE,mBACA,UAGF,KACE,mBACA,WAKJ,yBACE,GACE,2BAGF,IACE,6BAGF,KACE,4BAKJ,eACE,yCAIF,iBAEE,QAEE,0BAGF,IACE,6BAIJ,eACE,wCAGF,eACE,wCAGF,gBACE,6BAGF,cACE,kCAGF,oBACE,GACE,qBAIJ,WACE,sBACA,uBACA,wCAGF,SACE,6BACA,uBACA,4CAIF,qBAEE,QAEE,mCACA,WAGF,IACE,sCAEA,YAKJ,iBAEE,QAEE,mCACA,WAGF,IACE,sCAEA,YAKJ,eACE,wCAGF,iBACE,8BAGF,kBACE,iCAGF,oBACE,mCAGF,aACE,2DAIF,SACE,oBAGF,SACE,oBAGF,SACE,oBAGF,SACE,oBAGF,SACE,oBAIF,eACE,uBAGF,iBACE,uBAGF,eACE,sBAGF,iBACE,sBAIF,YACE,8BAEA,kBACE,sBAIJ,YACE,kDAEA,kBACE,2BACA,WH3WQ,gCG+WZ,cACE,8BAEA,oBACE,uBAIJ,aACE,kBACA,gBAEA,qBACE,WACA,kBACA,SACA,UACA,WACA,YACA,wBACA,8BACA,4BAGF,2BACE,yCClaJ,WACE,yBACA,gBACA,kBACA,kBACA,iHAIF,WACE,yBACA,gBACA,kBACA,kBACA,oHAIF,WACE,yBACA,gBACA,kBACA,kBACA,oHAIF,WACE,yBACA,gBACA,kBACA,kBACA,8GAMF,WACE,iCACA,gBACA,kBACA,kBACA,yPAKF,WACE,iCACA,gBACA,kBACA,kBACA,6OAOF,WACE,2BACA,gBACA,kBACA,kBACA,yOAKF,WACE,2BACA,gBACA,kBACA,kBACA,6NAWF,WACE,2BACA,oBACA,kBACA,kBACA,uIC1FF,MAEE,yBACA,2BACA,wBACA,0BACA,uBACA,yBACA,yBACA,wBACA,yBACA,wBACA,wBAGA,qBACA,qBACA,sBACA,iBACA,mBACA,oBACA,uBAGA,2CACA,gDACA,iDACA,iDACA,wDAGA,wBAGA,4DAIF,OACE,kBACA,UACA,WACA,UACA,YACA,gBACA,sBACA,mBACA,SAMF,eACE,eACA,MACA,OACA,WACA,YACA,+BACA,2BACA,2CACA,aACA,aACA,mBACA,oCAGA,4BACE,sBAGF,0BACE,YACA,UACA,WAEA,0CACE,YAIJ,0BA7BF,eA8BI,gCACA,+BACA,2BACA,mCACA,mBAEA,0BACE,UACA,eACA,yBAEA,0CACE,4DACA,gCACA,cAMR,gBACE,WACA,iBACA,cACA,eACA,aACA,mBACA,8BAQF,aACE,aACA,mBAEA,cAEA,mBACE,YAEA,qBACA,iBAGA,qCAEA,qCACE,qBACA,cAGF,qCACE,qBACA,cACA,mBAEA,mBACA,cAMN,cACE,aACA,mBACA,SACA,eAEA,8BACE,gBACA,2BACA,sBAIF,8BACE,gBACA,6BACA,gBAMJ,MACE,aACA,YACA,sBACA,WAEA,QACE,aACA,mBACA,SACA,YACA,qBAGF,UACE,YACA,YACA,cAIJ,WACE,eACA,gBACA,iBACA,uBACA,qBACA,oBACA,mBACA,YACA,gBACA,wBAGA,iBACE,0BAGF,mBACE,uBAKJ,6BAEE,qBACA,qBACA,8BAEA,yCACE,sBAGF,qCACE,cAKJ,cACE,aACA,mBACA,SAIF,YACE,WACA,YACA,0BACA,YACA,kBACA,eACA,eACA,uBACA,wBAEA,kBACE,2BACA,0BACA,qBAOJ,gBACE,aACA,mBACA,8BACA,WAEA,0BANF,gBAOI,cAIJ,eACE,aACA,mBACA,8BACA,WAEA,0BANF,eAOI,cAQJ,aACE,aACA,mBACA,2BAEA,+BACE,aACA,mBAGF,0BAGE,+BACA,WACA,mBAGF,+BACE,YLpRkB,0GKqRlB,kCACA,gBACA,cACA,qBACA,mBAEA,qCACE,cAKN,eAEE,aAEA,6BACE,eACA,gBACA,uBACA,SAIJ,cACE,aACA,mBAEA,+BAIE,6BACA,8BACA,yBACA,YACA,eACA,aACA,sBACA,uBACA,mBAEA,wBAEA,8BACA,kBACA,qCAEA,qCACE,2BAGF,+CAGE,8BACA,WACA,mBACA,wBACA,kBAKE,mEACE,wCAGF,mEACE,UAGF,mEACE,0CAUV,eACE,eACA,MACA,OACA,QACA,SACA,aACA,kBACA,UACA,wBAEA,sBACE,mBACA,UAEA,sCACE,wBAIJ,+BACE,aAGF,+BACE,kBACA,MACA,OACA,QACA,SACA,WACA,YACA,gBACA,2BACA,8BACA,aACA,sBACA,gBAIF,8BACE,aACA,mBACA,8BACA,YACA,gBACA,sBACA,gBAEA,kDACE,aACA,mBACA,2BAEA,oEACE,aACA,mBAGF,+DACE,+BACA,WACA,mBAGF,oEACE,YL3ac,0GK4ad,kCACA,gBACA,cACA,qBACA,mBAIJ,mDACE,aACA,mBACA,QAGF,+CACE,WACA,YACA,aACA,mBACA,uBAEA,mDACE,WACA,YAGF,qDACE,WAIJ,4CACE,WACA,YACA,yBACA,YACA,eACA,aACA,mBACA,uBACA,UAEA,gDACE,WACA,YAGF,kDACE,WAMN,+BACE,aACA,sBACA,mBACA,uBACA,iBACA,kBACA,mBACA,gCAGA,kDACE,mBAEA,sDACE,WACA,YAKJ,qDACE,YLxfgB,0GKyfhB,eACA,gBACA,cACA,iBACA,iBACA,kBAGF,6CACE,YLlgBgB,0GKmgBhB,eACA,gBACA,cACA,iBACA,kBACA,kBAGF,gDACE,aACA,SACA,WACA,gBAIF,kDACE,OACA,aACA,mBACA,uBACA,YACA,mBACA,cACA,YL3hBgB,0GK4hBhB,eACA,gBACA,kBACA,qBACA,wBAEA,wDACE,mBAKJ,iDACE,OACA,aACA,mBACA,uBACA,YACA,mBACA,WACA,YLhjBgB,0GKijBhB,eACA,gBACA,kBACA,qBACA,wBAEA,uDACE,2BAKJ,6CACE,mBACA,iBACA,kBACA,SACA,2BAEA,gEACE,gBACA,cAGF,gEACE,aACA,sBACA,QAIF,gEACE,YLjlBc,0GKklBd,eACA,gBACA,cACA,iBACA,SAGF,2DACE,SACA,gBAMN,8BACE,aACA,yBACA,aACA,gBACA,6BAEA,iDACE,aACA,mBACA,QACA,aACA,yBACA,YACA,eACA,qBAEA,qDACE,WACA,YAGF,sDACE,YLxnBc,0GKynBd,eACA,gBACA,cACA,iBAGF,uDACE,WAMN,2BACE,OACA,eACA,gBAEA,6CACE,gBACA,SACA,UAGF,6CACE,gCAGA,8DACE,aACA,mBACA,8BACA,WACA,YACA,cACA,yBACA,YACA,eACA,YL/pBc,0GKgqBd,eACA,gBACA,cACA,gBAEA,8EACE,8BAKJ,6DACE,aACA,gBACA,SACA,eACA,mBAGE,kEACE,cACA,0BACA,YLtrBU,0GKurBV,eACA,gBACA,cACA,qBACA,iBACA,0BAEA,wEACE,cAKJ,oFACE,gBACA,cAOJ,mEACE,gBAEA,mFACE,yBAIJ,kEACE,cAOV,gBACE,kBACA,MLhsBW,KKisBX,QACA,2BACA,WACA,YACA,yBACA,YACA,eACA,YACA,aAEA,qBACE,cACA,WACA,WACA,WLjwBQ,QKkwBR,kBACA,6BACA,oBAEA,yDAEE,WACA,kBACA,OACA,WACA,WACA,WL7wBM,QK8wBN,6BAGF,6BACE,SAGF,4BACE,QAIJ,wBACE,yBAEA,gCACE,MACA,wBAGF,+BACE,MACA,yBAQN,gBAEE,eACA,MACA,OACA,QACA,SACA,WLhzBM,KKizBN,QL9tBc,KK+tBd,YACA,gBACA,gBACA,aACA,sBAKA,4BACE,QL5wBS,KK6wBT,WL5zBM,QK6zBN,gCAGF,0BACE,gBACA,SACA,UACA,YAEA,oCACE,gCAEA,sCACE,aACA,mBACA,QL7xBK,KK8xBL,MLj1BI,QKk1BJ,ULvzBO,KKwzBP,YL9yBa,IK+yBb,qBACA,kBAGF,yDACE,WACA,kBACA,MLxyBK,KKyyBL,QACA,UACA,WACA,+BACA,gCACA,yCACA,mCAGF,sDACE,2CAGF,8CACE,aACA,gBACA,UACA,SACA,WL12BE,QK42BF,gDACE,cACA,4BAGF,MLp3BI,QKq3BJ,UL71BO,KK81BP,qBAEA,sDACE,WLv3BF,KKw3BE,MLt4BK,QK24BX,uDACE,cAKN,2BACE,kBACA,WLr4BM,KKs4BN,6BACA,gBAQF,UACE,aACA,gBACA,SACA,SACA,UAGA,aACE,kBAGE,oCACE,2BACA,0BACA,kBAGF,oCACE,mBACA,UACA,wBAIJ,uBACE,kBACA,sBACA,OACA,gBACA,wBACA,oCACA,cLp3Ba,KKq3Bb,4BACA,cACA,kBACA,UACA,4BACA,wBACA,QLz2Ba,IK02Bb,gBACA,SAEA,0BACE,SAEA,4BACE,cACA,iBACA,uBACA,qBACA,UL56BK,KK66BL,YLj6BW,IKk6BX,wBAEA,kCACE,2BACA,0BAQZ,UACE,eACA,gBACA,uBACA,aACA,kBACA,oCAEA,mBAEA,gBACE,0BAIJ,UACE,eAGF,YACE,aACA,mBACA,QACA,qBACA,mBACA,kBACA,mBACA,gBACA,wBACA,4BAEA,kBACE,2BACA,4BA0FJ,0BACE,eAGE,gCACA,+BACA,2BACA,mCACA,mBAEA,0BACE,UACA,eACA,yBAGF,0CAIE,4DACA,gCACA,cAUN,kBACE,aACA,mBACA,QACA,gBACA,kBAEA,iCACE,aACA,mBACA,QAEA,4CACE,WACA,YACA,cAGF,4CACE,YL9mCgB,0GK+mChB,eACA,gBACA,cACA,mBAIJ,2BACE,cACA,uBACA,cAGF,+BACE,YL7nCkB,0GK8nClB,eACA,gBACA,cACA,qBACA,mBACA,gBACA,YACA,UACA,eACA,0BAEA,qCACE,0BAIJ,mCACE,kBAEA,kDACE,aACA,mBACA,QAGF,yDACE,kBACA,qBACA,QACA,gBACA,wBACA,oCACA,cLnnCa,KKonCb,4BACA,cACA,kBACA,UACA,4BACA,wBACA,QLxmCa,IK0mCb,2EACE,gBACA,SACA,UAEA,8EACE,SAEA,gFACE,aACA,mBACA,QACA,iBACA,uBACA,qBACA,ULhrCG,KKirCH,YLrqCS,IKsqCT,wBAEA,kFACE,eACA,WACA,kBAGF,sFACE,2BACA,0BAOV,gEACE,mBACA,UACA,wBAMN,0BACE,kBACE,cCzlCJ,eACE,yBACA,cACA,iBAEA,0BACE,iBACA,cACA,eAIJ,gBACE,aACA,8BACA,mBACA,SJpKE,0BIgKJ,gBAOI,sBACA,uBACA,SACA,gBAKJ,aACE,aACA,sBACA,SAEA,0BACE,YACA,YACA,mBAGF,2BACE,aACA,mBACA,QAEA,wCACE,sHACA,eACA,gBACA,cACA,qBACA,0BAEA,8CACE,WACA,0BAIJ,6CACE,cACA,eACA,iBAIJ,+BACE,YN5LkB,0GM6LlB,eACA,gBACA,cACA,SACA,gBAKJ,cACE,aACA,sBACA,SACA,qBJnOE,0BI+NJ,cAOI,uBACA,YAIA,0DACE,YACA,YACA,eACA,YNtNgB,0GMuNhB,eACA,gBACA,cACA,mBACA,yBACA,kBACA,eACA,wBACA,gBACA,uQACA,4BACA,sCACA,mBAEA,gEACE,kBAGF,gEACE,aACA,kBJpQJ,0BI2OA,0DA6BI,YAKN,8BACE,sHACA,eACA,gBACA,cACA,SACA,gBJnRA,0BI6QF,8BASI,gBJxRF,yBIgSA,0BACE,eAIJ,gBAEE,sBACA,uBACA,eACA,SACA,gBAGF,aAEE,QACA,WACA,QAEA,0BAEE,WACA,SAGF,2BACE,QACA,QAEA,wCAEE,eACA,cAGF,6CACE,eACA,cAIJ,+BAEE,eACA,cACA,QAIJ,cAEE,WACA,uBACA,QACA,QAEA,oCACE,WACA,SAEA,0DAEE,WACA,YACA,eACA,kBACA,qBACA,cAIJ,8BAEE,eACA,gBACA,cACA,SJ7WF,yBIoXF,gBACE,aACA,sBAGA,6BACE,iBAEA,0CACE,QAGF,2CACE,QAGF,+CACE,QAIJ,8BACE,iBAEA,oDACE,QACA,WAGF,8CACE,SCpZR,MACE,aACA,IP2DW,KOxDX,aACE,0BAGF,aACE,qCAGF,aACE,qCAGF,aACE,qCAGF,aACE,qCAGF,aACE,qCAGF,cACE,sCAIF,WACE,2DAGF,cACE,2DAGF,cACE,2DAIF,YACE,MAGF,aACE,IPOS,IOJX,aACE,IPIS,KODX,aACE,IPCS,KOEX,aACE,IPFS,KE1DT,0BKiEA,gBACE,0BAGF,gBACE,sCLxEF,yBK6EA,gBACE,0BAGF,gBACE,sCAMN,YACE,mBAGF,YACE,mBAGF,YACE,mBAGF,YACE,mBAGF,YACE,mBAGF,YACE,mBAGF,eACE,iBAGF,YACE,gBAGF,YACE,gBAGF,YACE,gBAIF,UACE,aACA,2DACA,IPzEW,KEzDT,0BK+HJ,UAMI,sCLvIA,yBKiIJ,UAUI,2BAKJ,iBACE,aACA,2DACA,IPxFW,KE3DT,yBKgJJ,iBAMI,2BAKJ,eACE,aACA,2DACA,IPjGY,KOkGZ,mBL/JE,yBK2JJ,eAOI,sCAKJ,cACE,aACA,qCACA,IP9GW,KE5DT,yBKuKJ,cAMI,2BAKJ,cACE,aACA,8BACA,IPxHY,KOyHZ,kBLpLE,0BKgLJ,cAOI,2BAKJ,aACE,aACA,8BACA,IPjIY,KE9DV,0BK4LJ,aAMI,0BACA,IPxIU,MO2IZ,2BACE,aACA,qCACA,IP9IU,KE3DV,0BKsMF,2BAMI,sCL9MF,yBKwMF,2BAUI,0BACA,mBAMN,cACE,aACA,qCACA,IPjKW,KOkKX,mBL3NE,0BKuNJ,cAOI,0BACA,IPrKS,MO0Kb,kBACE,aACA,2DACA,IP9KW,KE3DT,yBKsOJ,kBAMI,2BAKJ,YACE,aACA,qCACA,IPxLW,KOyLX,kBLnPE,0BK+OJ,YAOI,sCLxPA,yBKiPJ,YAWI,2BAKJ,YACE,aACA,8BACA,IPtMY,KOuMZ,mBLnQE,0BK+PJ,YAOI,0BACA,IP7MS,MOgNX,oBACE,cAEA,sBACE,cL9QF,0BK0QF,oBAQI,eAMN,cACE,aACA,qCACA,IPjOW,KE1DT,0BKwRJ,cAMI,2BClSJ,WACE,UR8EoB,OQ7EpB,cACA,eNDE,yBMFJ,WAMI,gBAIF,iBACE,eACA,eAGF,kBACE,gBAGF,gBACE,iBAKJ,SACE,gBNxBE,yBMuBJ,SAII,gBAIF,YACE,eAGF,YACE,gBAGF,gBACE,cAGF,mBACE,iBAMF,iBACE,WRvCM,QQ0CR,kBACE,WR1CO,QQ6CT,oBACE,MRhDI,KQmDN,iBACE,WRvDQ,QQwDR,MRrDI,KQ0DR,gBACE,kBACA,cRRY,KE/DV,yBMqEJ,gBAKI,cRZU,MQeZ,+BACE,oBACA,mBACA,IRxBS,IQyBT,iBACA,WRrEO,QQsEP,MRtFW,QQuFX,cRXiB,KQYjB,URpDW,KQqDX,YRxCmB,IQyCnB,cR7BS,KQgCX,+BACE,URpDY,KQqDZ,YR7Ce,IQ8Cf,cRnCS,KQoCT,MRtFQ,QERR,yBM0FF,+BAOI,UR3DU,MQiEd,kCACE,URrEW,KQsEX,MRhGQ,QQiGR,YRtDiB,IQuDjB,gBACA,cN5GA,yBMuGF,kCAQI,UR7EW,MQmFjB,iBACE,kBACA,iBACA,aACA,sBAEA,+BACE,OACA,YR/CY,KQoDhB,cACE,kBACA,gBAIF,eACE,kBACA,UC5IF,KP+BE,oBACA,mBACA,IF0BW,IEzBX,kBACA,cFqCiB,KEpCjB,YFYqB,IEXrB,UFDe,KEEf,qBACA,WF+DgB,aE9DhB,eACA,YAEA,WACE,2BO5CJ,KAEE,kBACA,gBAGA,QACE,iBACA,UT2BW,KSxBb,QACE,kBACA,UTuBa,KSpBf,QACE,kBACA,UTmBW,KShBb,QACE,kBACA,UTeW,KSXb,aACE,WT7BW,QS8BX,MThBI,KSiBJ,WTNQ,+BSQR,mBACE,2BACA,WTRM,gCSWR,oBACE,2BAIJ,eACE,mBACA,cAEA,qBACE,WThCK,QSiCL,2BAIJ,YACE,MTxCI,KSyCJ,WT9BQ,+BSgCR,kBACE,2BACA,WThCM,gCSoCV,UACE,MTlDI,KSmDJ,WTxCQ,+BS0CR,gBACE,2BACA,WT1CM,gCS8CV,aACE,WTrEW,QSsEX,MT7DI,KS+DJ,mBACE,kDACA,2BAIJ,YACE,WThFY,QSiFZ,MTvEI,KSyEJ,kBACE,8BACA,2BAIJ,WACE,yBACA,MT/FW,QSgGX,+BAEA,iBACE,6BACA,aTpGS,QSwGb,aACE,yBACA,MT/FQ,QSgGR,yBAEA,mBACE,aT9GS,QS+GT,MT/GS,QSgHT,2BAKJ,4BAEE,WACA,mBACA,oBAGF,aACE,oBACA,oBAEA,oBACE,WACA,kBACA,WACA,YPrDJ,kBACA,QACA,SACA,gCOoDI,sBACA,kBACA,+BACA,mCAKJ,UACE,oBACA,mBACA,uBACA,WACA,YACA,UACA,cTxEmB,IS0EnB,iBACE,WACA,YAGF,iBACE,WACA,YAKJ,WACE,WACA,uBAIF,WACE,oBACA,SAEA,gBACE,gBAEA,4BACE,uBTxGW,KSyGX,0BTzGW,KS4Gb,2BACE,wBT7GW,KS8GX,2BT9GW,KSqHnB,KACE,eACA,OTjIW,KSkIX,MTlIW,KSmIX,WACA,YAEA,YACA,cTzHqB,IS0HrB,MTzLM,KS0LN,UThKa,KSiKb,eACA,WThLU,gCSiLV,WTpGgB,aSqGhB,QT/Gc,IE1Ed,aACA,uBACA,mBO0LA,WACE,qBACA,WTtLQ,gCSwLR,sBACE,UACA,4BAIJ,gBACE,kBACA,WACA,QACA,2BACA,WTnNQ,QSoNR,MTjNI,KSkNJ,iBACA,cTzJe,IS0Jf,UT9LW,KS+LX,mBACA,UACA,WT9Hc,aS+Hd,oBAMF,iBP5MA,oBACA,mBACA,IF0BW,IEzBX,kBACA,cFqCiB,KEpCjB,YFYqB,IEXrB,UFDe,KEEf,qBACA,WF+DgB,aE9DhB,eACA,YAEA,uBACE,2BO+LF,iBAEE,WThOI,KSiOJ,MT/OW,QSgPX,kBACA,UT3MW,KS4MX,WTxNQ,gCS0NR,uBACE,2BACA,WT3NM,gCS+NV,mBP1NA,oBACA,mBACA,IF0BW,IEzBX,kBACA,cFqCiB,KEpCjB,YFYqB,IEXrB,UFDe,KEEf,qBACA,WF+DgB,aE9DhB,eACA,YAEA,yBACE,2BO6MF,mBAEE,yBACA,MT/OI,KSgPJ,sBACA,kBACA,UT1NW,KS4NX,yBACE,8BACA,2BAUN,0GAME,kBACA,cT7MiB,IS8MjB,UTjPe,KSkPf,YTvOmB,ISwOnB,qBACA,WTlLgB,aSmLhB,oBACA,mBACA,uBACA,ITjOW,ISkOX,eACA,YAEA,yBAnBF,0GAoBI,kBACA,UT/PW,MSkQb,sHACE,eAKJ,oCAGE,MTjSM,KSmSN,gDACE,2BACA,WT1RQ,+BS6RV,kDACE,wBAKJ,sBACE,WT/SM,KSgTN,MTnTU,QSoTV,yBAEA,4BACE,WTnTM,QSoTN,aTnUW,QSoUX,MTpUW,QSuUb,6BACE,sBAKJ,iBACE,WT9Ua,QS+Ub,MTjUM,KSmUN,uBACE,2BACA,WT1TQ,+BS2TR,WTnVa,QSsVf,wBACE,wBAKJ,mBACE,WT1Vc,QS2Vd,MTjVM,KSmVN,yBACE,2BACA,WT1UQ,+BS2UR,8BAGF,0BACE,wBAMF,qBACE,WTjWI,KSkWJ,MTrWQ,QSsWR,yBAEA,2BACE,WTrWI,QSsWJ,aTrXS,QSsXT,MTtXS,QS0Xb,qBAEE,MT9WI,KSgXJ,2BACE,2BACA,WTvWM,+BS2WV,uBACE,WTjYY,QSkYZ,MTxXI,KS0XJ,6BACE,2BACA,WTjXM,+BSuXZ,UACE,iBACA,6BACA,MTnZa,QSoZb,mCACA,cT9UiB,IS+UjB,UTnXa,KSoXb,YTvWmB,ISwWnB,eACA,WTlTgB,aSmThB,oBACA,mBACA,ITjWW,ISmWX,gBACE,6BACA,aThaW,QSmab,YACE,eAIJ,YACE,iBAEA,MT7ZM,KS8ZN,YACA,cTrWiB,ISsWjB,UT1Ya,KS2Yb,YT/XmB,ISgYnB,eACA,WT1UgB,aS2UhB,oBACA,mBACA,ITzXW,IS2XX,kBACE,2BACA,WT/ZQ,+BSkaV,cACE,eAIJ,YACE,iBACA,WTpbM,KSqbN,MTvbU,QSwbV,yBACA,cT7XiB,IS8XjB,UTlaa,KSmab,YTvZmB,ISwZnB,eACA,WTlWgB,aSmWhB,oBACA,mBACA,ITjZW,ISmZX,kBACE,aT/cW,QSgdX,MThdW,QSmdb,cACE,eAQJ,kBACE,cACA,oBACA,mBACA,uBACA,gBACA,YACA,kBACA,YACA,cT5ZiB,KS6ZjB,UT/ba,KSgcb,YTrbiB,ISsbjB,MT1dM,KS2dN,eACA,WTnYgB,aSoYhB,mBAGA,6BACE,mBAEA,mCACE,iDACA,2BACA,WT3dM,+BS8dR,oCACE,wBAKJ,2BACE,mBAEA,iCACE,WThgBW,QSigBX,2BACA,WT1eM,+BS6eR,kCACE,wBAGF,oCACE,WT1fQ,QS2fR,mBACA,eACA,gBAIJ,yBAtDF,kBAuDI,WACA,eACA,YACA,UTjfa,MSyfjB,YACE,oBACA,mBACA,uBACA,QACA,YACA,iBACA,yBACA,MTxhBM,KSyhBN,YACA,cTheiB,ISiejB,eACA,YTzfqB,IS0frB,eACA,qCAEA,kBACE,yBP1iBA,0BOyhBJ,YAqBI,eACA,aAOJ,oBACE,oBACA,mBACA,uBACA,mBACA,WACA,eACA,gBACA,kBACA,mBACA,qBACA,wCACA,eACA,YAEA,0BACE,kDACA,2BACA,WAGF,6BACE,8BACA,yBACA,8BACA,0BACA,+BAGF,uBACE,eACA,kBPvlBA,yBOwjBJ,oBAoCI,gBC9lBJ,cACE,oBACA,mBACA,uBACA,iBACA,cVsEmB,KUrEnB,UV4Ba,KU3Bb,YVyCqB,IUxCrB,mBACA,WV6FgB,aU1FhB,6BACE,gCACA,MVXY,QUcd,+BACE,gCACA,MVfW,QUkBb,4BACE,6BACA,MVzBW,QU4Bb,8BACE,gCACA,MVlBQ,QUqBV,gCACE,+BACA,kCAGF,4BACE,gCACA,MVpCY,QUuCd,6BACE,gCACA,MVxCW,QU6Cf,qBACE,oBACA,mBACA,uBACA,iBACA,cVqBmB,KUpBnB,UVrBa,KUsBb,YVRqB,IUSrB,cVGW,KUFX,WV4CgB,aU1ChB,oCACE,gCACA,MV3DY,QU8Dd,sCACE,gCACA,MV/DW,QUkEb,mCACE,6BACA,MVzEW,QU4Eb,qCACE,gCACA,MVlEQ,QUqEV,uCACE,+BACA,kCAKJ,UACE,gBACA,eACA,cVpBiB,IUwBnB,UACE,iBACA,UV7Da,KU8Db,cV1BiB,IU8BnB,eACE,yBACA,8BAEA,8BACE,MVvGY,QUwGZ,aVxGY,QU2Gd,gCACE,MV3GW,QU4GX,aV5GW,QU+Gb,6BACE,MVrHW,QUsHX,aVtHW,QUyHb,+BACE,MV9GQ,QU+GR,aV/GQ,QUqHV,cACE,aVvES,IUwET,eAKJ,oBACE,oBACA,mBACA,uBACA,eACA,YACA,cACA,WV3Ic,QU4Id,MVlIM,KUmIN,cVpEqB,IUqErB,eACA,YVjGiB,IUkGjB,cAEA,kCACE,WVvJW,QU0Jb,kCACE,WVtJW,QUyJb,kCACE,WV5JY,QU6JZ,MVrJQ,QUwJV,iCACE,WVhKY,QWHhB,MTkDE,WFrCM,KEsCN,cFuBkB,KEtBlB,QFyDa,KExDb,WFiDgB,aE/ChB,YAEE,WFhCQ,gCWzBZ,MAEE,kBACA,gBAIF,UT2CE,WFrCM,KEsCN,cFuBkB,KEtBlB,QFyDa,KExDb,WFiDgB,aE/ChB,gBAEE,WFhCQ,gCWlBZ,UAEE,kBACA,yBAEA,gBACE,aXdW,QWmBb,kBACE,aXjBY,QWqBd,cACE,aXpBW,QWwBb,qBACE,kBACA,UACA,WACA,iBACA,WX/BY,QWgCZ,MXxBQ,QWyBR,cXuCgB,KWtChB,UXFW,KWGX,YXYe,IWXf,yBAEA,yBACE,WXrCS,QWsCT,MX7BE,KWgCJ,0BACE,WXzCU,QW0CV,MXlCE,KWuCN,oBACE,WACA,YACA,mBTrCF,aACA,uBACA,mBSqCE,eACA,WX3CO,QW4CP,MX5DW,QW6DX,cXae,KWZf,WXyCc,aWtChB,0BACE,qBAEA,MXtDI,KW0DN,oBACE,UXlCW,KWmCX,YXxBe,IWyBf,cXdS,KWeT,MXjEQ,QWoEV,2BACE,MXpEQ,QWqER,YX1BiB,IW2BjB,cXpBS,KWqBT,UX/CW,KWmDb,wBACE,aACA,uBACA,IX9BS,IW+BT,cX7BS,KW8BT,eAEA,qCACE,gBACA,WXjFI,QWkFJ,MXrFM,QWsFN,cXvBc,KWwBd,UXhES,KWiET,YXpDe,IWyDnB,oBACE,oBACA,mBACA,IXjDS,IWkDT,MX7GW,QW8GX,YX7DmB,IW8DnB,UX3EW,KW4EX,WXTc,aWWd,0BACE,IXvDO,IWwDP,MXnHW,QWyHjB,iBTvEE,WFrCM,KEsCN,cFuBkB,KEtBlB,QFyDa,KExDb,WFiDgB,aE/ChB,uBAEE,WFhCQ,gCWgGZ,iBAEE,kBAEA,4BACE,WACA,YACA,mBT9GF,aACA,uBACA,mBS8GE,UX3FW,KW6FX,MXvHI,KWwHJ,cX5De,KW+DjB,oBACE,UXnGW,KWoGX,YXzFe,IW0Ff,cX/ES,KWgFT,MXlIQ,QWqIV,mBACE,MXrIQ,QWsIR,YX3FiB,IW4FjB,cXrFS,KWsFT,UXhHW,KWoHb,8BACE,aACA,sBACA,mBACA,YX9FS,KW+FT,6BAEA,4CACE,UXxHS,KWyHT,YX9GkB,IW+GlB,MXlKS,QWqKX,4CACE,UXnIS,KWoIT,MX3JM,QW4JN,YXxHe,IW8HrB,cT3HE,WFrCM,KEsCN,cFuBkB,KEtBlB,QFyDa,KExDb,WFiDgB,aE/ChB,oBAEE,WFhCQ,gCWoJZ,cAEE,aACA,uBACA,IXpHW,KWsHX,4BACE,cACA,WACA,YTpKF,aACA,uBACA,mBSqKE,MX5KI,KW6KJ,cXlHe,KWmHf,UXpJW,KWuJb,+BACE,OAEA,kCACE,UX7JS,KW8JT,YXnJiB,IWoJjB,cXzIO,IW0IP,MX3LM,QW8LR,iCACE,MX9LM,QW+LN,YXpJe,IWqJf,UXxKS,KW8Kf,kBT/JE,WFrCM,KEsCN,cFuBkB,KEtBlB,QFyDa,KExDb,WFiDgB,aE/ChB,wBAEE,WFhCQ,gCWwLZ,kBAEE,kBAEA,0BACE,YACA,kBACA,IX5JS,KW6JT,KX7JS,KW8JT,eACA,MX5NW,QW6NX,WACA,YX5Ke,IW+KjB,uCACE,kBACA,UACA,UX/La,KWgMb,YX7KgB,IW8KhB,MX3NQ,QW4NR,cXzKS,KW0KT,kBAGF,sCACE,aACA,mBACA,IXjLS,KWmLT,qDACE,WACA,YACA,cXtKiB,IWuKjB,iBAIA,gEACE,YXxMe,IWyMf,MX/OI,QWgPJ,kBAGF,iEACE,UX3NO,KW4NP,MXpPI,QW2PZ,cTpNE,WFrCM,KEsCN,cFuBkB,KEtBlB,QFyDa,KExDb,WFiDgB,aE/ChB,oBAEE,WFhCQ,gCW6OZ,cAEE,kBACA,kBAEA,uBACE,yBACA,sBAEA,sCACE,kBACA,UACA,SACA,2BACA,iBAEA,MXzQE,KW0QF,cX5Me,KW6Mf,UXtPS,KWuPT,YXxOa,IWyOb,yBAIJ,8BACE,eXlOS,KWmOT,gCACA,cXpOS,KWsOT,iCACE,UX7PS,KW8PT,YXpPa,IWqPb,cX3OO,IW4OP,MX7RM,QWgSR,qCACE,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anB,yCACE,qBACA,iBACA,iBZ3bQ,QY4bR,yBACA,cZrYiB,IYsYjB,YZ7aiB,oCY8ajB,UZ1aa,KY2ab,MZ9ce,QYodjB,+BACE,aACA,mBACA,IZ3ZW,KY6ZX,yBALF,+BAMI,sBACA,wBAOJ,kCACE,aACA,sBACA,IZ1aW,IY6ab,gCACE,aACA,mBACA,IZ/aW,KYgbX,kBACA,WZheM,KYieN,mBAEA,4CACE,aZlfW,QYmfX,WZ3dQ,0BY8dV,yBAbF,gCAcI,QZ1bS,KY2bT,IZ5bS,KY+bX,wDACE,qBACA,iBACA,cZvbe,IYwbf,UZ3dW,KY4dX,YZ9ce,IY+cf,YZjee,oCYkef,eACA,kBAEA,8EACE,yBACA,cAGF,gFACE,yBACA,cAGF,8EACE,yBACA,cAGF,oFACE,yBACA,cAGF,kFACE,iBZzgBQ,QY0gBR,MZhhBM,QYohBV,oDACE,OACA,UZ5fa,KY6fb,YZlfiB,IYmfjB,MZxhBQ,QYyhBR,SAGF,wDACE,iBACA,cZhee,KYief,UZtgBW,KYugBX,YZ1fmB,IY4fnB,sFACE,yBACA,cAGF,oFACE,yBACA,cAQN,qCACE,aACA,uBACA,IZlgBW,KYmgBX,eACA,WZ/fY,KYggBZ,YZhgBY,KYigBZ,6BAEA,yBATF,qCAUI,sBACA,qBAIA,yBADF,+CAEI,YC3kBN,YACE,cb4DW,Ka1DX,kBACE,cACA,Ub8BW,Ka7BX,Yb0CmB,IazCnB,MbGQ,QaFR,cbmDS,IajDT,4BACE,MbRU,QaSV,gBAIJ,uBACE,UbiBW,KahBX,MbPQ,QaQR,WbuCS,IapCX,wBACE,UbWW,KaVX,MbrBY,QasBZ,WbiCS,IahCT,aACA,mBACA,Ib8BS,IazBb,cACE,WACA,kBACA,eACA,YbRoB,0GaSpB,Mb5BU,Qa6BV,Wb1BM,Ka2BN,yBACA,cb8BiB,Ia7BjB,Wb4DgB,aa1DhB,2BACE,MbjCS,QaoCX,oBACE,aACA,abnDW,QaoDX,uCAGF,uBACE,WbzCM,Qa0CN,Mb7CQ,Qa8CR,mBAIF,iBACE,iBACA,Ub5BW,Ka+Bb,iBACE,kBACA,Ub/BW,KamCb,uBACE,abrEW,QauEX,6BACE,0CAIJ,yBACE,ab9EY,QagFZ,+BACE,0CAMN,sBACE,iBACA,gBAIF,oBACE,gBACA,qKACA,4BACA,sCACA,qBACA,cbvCY,Ka2Cd,YACE,aACA,mBACA,cbjDW,KamDX,+DAEE,WACA,YACA,abxDS,IayDT,eAGF,kBACE,gBACA,eACA,iBAGF,qBACE,WACA,mBAEA,sDAEE,mBAMN,aACE,aACA,mBACA,IbhFW,KakFX,qBACE,kBACA,WACA,YACA,WblIU,QamIV,cbxEiB,KayEjB,eACA,Wb/Cc,aaiDd,4BACE,WACA,kBACA,QACA,SACA,WACA,YACA,WbjJE,KakJF,cbnFiB,IaoFjB,Wb1DY,aa6Dd,4BACE,WbrKS,QauKT,mCACE,2BAOR,aACE,aACA,WAEA,+BACE,kBACA,UbhJa,KaiJb,Mb1KQ,Qa2KR,WbxKM,QayKN,yBACA,0BACA,kBAGF,2BACE,0BAIA,kDACE,0BACA,+BACA,iBAGF,8CACE,0BAMN,aACE,aACA,qBACA,IbrJW,KauJX,yBACE,gBXlNA,yBW4MJ,aAUI,sBACA,oBAEA,yBACE,cbhKO,MasKb,UACE,aACA,qCACA,IbxKW,KE3DT,yBWgOJ,UAMI,2BASJ,qBACE,aACA,IbvLW,KawLX,mBACA,WAEA,yBANF,qBAOI,sBACA,qBAIF,wCACE,OACA,QbnMS,KaoMT,WblPM,QamPN,yBACA,cb3Le,Ia4Lf,UbhOW,KaiOX,MbzPQ,Qa0PR,eACA,WbhKc,aakKd,iDACE,Mb/PM,QagQN,8BACA,ab5QS,Qa+QX,8CACE,aACA,abjRS,QakRT,uCAKJ,sCACE,kBAEA,Mb5QI,Ka6QJ,cbnNe,IaoNf,UbxPW,KayPX,Yb7OiB,Ia8OjB,eACA,WbxLc,aayLd,mBACA,oBACA,mBACA,IbxOS,IayOT,YAEA,yBAfF,sCAgBI,wBAGF,4CACE,2BACA,WbnRM,+BasRR,6CACE,wBAGF,wCACE,eAKJ,sCACE,Qb7PS,Ka8PT,WbvTY,QawTZ,Mb9SI,Ka+SJ,cbrPe,IasPf,YACA,eACA,WbzNc,aa0Nd,oBACA,mBACA,uBACA,WACA,YAEA,yBAdF,sCAeI,WACA,YACA,Qb7QO,MagRT,4CACE,2BACA,WbtTM,+BauTN,8BAGF,6CACE,wBAGF,wCACE,eAKJ,sCACE,aAKJ,gBACE,WbvSW,KawSX,UblUa,KamUb,MbzVW,Qa0VX,YbhTmB,IakTnB,kBACE,aACA,aACA,uBACA,IblTS,IaoTT,oBACE,MbhXS,QaiXT,eACA,cASN,UACE,aACA,uBACA,SACA,mBX1XE,0BWsXJ,UAWI,sBACA,SACA,oBAIJ,oBACE,aACA,mBACA,QACA,gBACA,YACA,cX7YE,0BWuYJ,oBASI,cACA,YAGF,iCACE,gBXrZA,0BWoZF,iCAII,cAKN,iBACE,eACA,YbrXoB,IasXpB,WXhaE,0BW6ZJ,iBAMI,gBAIF,iCACE,YACA,Mb1aY,Qa2aZ,gBAIJ,gBACE,yBACA,MbvaM,KawaN,eACA,YbxYoB,IayYpB,oBACA,kBACA,cAGF,oBACE,OX3bE,yBW0bJ,oBAII,YAKJ,YACE,WACA,iBACA,yBACA,cblYiB,IamYjB,eACA,MbjcU,QakcV,Wb/bM,KagcN,WbvWgB,aayWhB,kBACE,aACA,abldW,QamdX,uCAGF,gDAEE,8BACA,Mb7cQ,Qa8cR,mBAGF,yBACE,MbjdS,Qasdb,aACE,WACA,iBACA,yBACA,cb/ZiB,IagajB,eACA,Mb9dU,Qa+dV,Wb5dM,Ka6dN,eACA,WbrYgB,aasYhB,gBACA,0LACA,4BACA,sCACA,cbhbY,KakbZ,mBACE,aACA,abrfW,QasfX,uCAGF,sBACE,iBb3eM,Qa4eN,Mb9eS,Qa+eT,mBAOJ,sBACE,cACA,iBACA,eAEA,oCACE,eACA,cAQJ,iBACE,WbldY,KamdZ,YbrdW,KasdX,6BAOF,gBACE,aACA,mBACA,IbneW,IaoeX,WAEA,yDAEE,OACA,YAGF,2BACE,Mb7hBQ,Qa8hBR,YbzfmB,Ia0fnB,iBACA,cAIF,yCACE,UACA,YACA,gBAGF,0CACE,UACA,YACA,gBACA,mBACA,0LACA,4BACA,qCAIF,gCACE,OACA,WAGF,yBA1CF,gBA2CI,Ib5gBS,Ia8gBT,2BACE,UbtiBS,Ma+iBf,mBACE,aACA,mBACA,IbzhBW,Ka2hBX,wEAEE,OAGF,yBAVF,mBAWI,sBACA,IbniBS,IaqiBT,wEAEE,YAQN,kBACE,aACA,mBACA,kBAEA,8BACE,OACA,oBAGF,8BACE,kBACA,Mb1jBS,Ka2jBT,QACA,2BACA,UbtlBW,KaulBX,Yb1kBmB,Ia2kBnB,MbxnBY,QaynBZ,mBAQJ,mCACE,aACA,uBACA,Ib3kBW,Ka4kBX,eACA,Yb1kBY,KE7DV,yBWkoBJ,mCAQI,8BACA,oBACA,IbnlBS,IaolBT,Wb/kBU,KaglBV,YbllBS,MaslBX,uMAGE,gBACA,kBXtpBA,yBWkpBF,uMAOI,WACA,gBAKJ,uBACE,8BAIF,yBACE,gBACA,cAIF,uBACE,Wb9mBU,Ka+mBV,YbjnBS,KasnBX,+BACE,kBACA,uBAEA,gDACE,kBACA,OACA,oBACA,mBACA,uBACA,QACA,YACA,QbpoBO,IaqoBP,mBACA,cbznBa,Ka0nBb,cACA,Ub/pBW,KagqBX,YbtpBgB,IaupBhB,qBACA,eACA,WbjmBY,aammBZ,sDACE,6DAGF,oDACE,WACA,YACA,mBX9sBJ,yBWqrBA,gDA8BI,gBACA,YACA,YACA,iBACA,kBACA,eACA,Yb1qBW,Ia2qBX,cACA,SAIJ,qDACE,aACA,IbvqBO,KE1DT,yBW+tBA,qDAMI,8BACA,uBACA,WACA,SACA,SXzuBJ,yBW4uBE,0DAII,uBACA,uBACA,gBACA,4BACA,0BACA,YbtsBS,IausBT,6BACA,eXvvBN,yBW2vBI,wEAEI,oCACA,yBACA,wBX/vBR,yBWowBI,sEAEI,oCACA,sBACA,wBXxwBR,yBWirBF,+BA+FI,sBACA,mBACA,SACA,mBAcN,cACE,kBACA,aACA,mBACA,YACA,YACA,Wb5xBM,Ka6xBN,yBACA,mBACA,gBX1yBE,yBWiyBJ,cAYI,WACA,iBAGF,oBACE,OACA,YACA,sBACA,YACA,yBACA,Yb5xBkB,0Ga6xBlB,eACA,Yb7wBkB,Ia8wBlB,MblzBQ,QamzBR,aAEA,iCACE,cAIJ,gCACE,kBACA,QACA,MACA,aACA,mBACA,uBACA,WACA,YACA,yBACA,YACA,eACA,cACA,Wb1uBc,ca4uBd,sCACE,Mbr1BS,Qaw1BX,oCACE,WACA,YAIJ,2BACE,ab/1BW,Qag2BX,uCAOJ,oBACE,Qb1yBW,Ka2yBX,Wb31BM,Ka41BN,yBACA,cbnyBiB,IaoyBjB,iBACA,YbrzBkB,IaszBlB,Ubz0Be,Ka00Bf,Mbp2BU,QERR,yBWo2BJ,oBAWI,QbrzBS,KaszBT,kBAIF,sBACE,cb3zBS,Ka8zBX,wBACE,eACA,YAGF,sBACE,Mbj4BW,Qak4BX,0BAEA,4BACE,sBAOJ,uCACE,sBAOJ,iBACE,oBACA,mBACA,Ib31BW,Ia41BX,iBACA,Wbz4BS,Qa04BT,yBACA,cbn1BiB,Iao1BjB,Mbj5BU,Qak5BV,Ubz3Ba,Ka03Bb,qBACA,WbxzBgB,aa0zBhB,uBACE,8BACA,abn6BW,Qao6BX,Mbp6BW,Qau6Bb,qBACE,cACA,Mb75BQ,Qag6BV,2BACE,Mb76BW,Qak7Bf,iBACE,aACA,sBACA,Ibz3BW,Ia43Bb,iBACE,aACA,mBAOF,WACE,cACA,eACA,gBACA,WACA,mBAEA,qBACE,cACA,gBAIJ,WACE,WACA,kBACA,yBACA,kBACA,eACA,WACA,4BACA,yBAEA,wBACE,cAGF,iBACE,aACA,qBAIJ,cACE,WACA,kBACA,yBACA,kBACA,eACA,WACA,YACA,4BACA,iBACA,yBAEA,2BACE,cAGF,oBACE,aACA,qBAIJ,0BACE,iBACA,eAEA,wCACE,eACA,cC3/BJ,gBACE,eACA,MACA,OACA,QACA,SACA,mBACA,QdsFuB,KcrFvB,WACA,kBACA,Wd0FgB,acxFhB,qBACE,WACA,mBAKJ,OACE,eACA,MACA,OACA,WACA,aACA,QdqEc,KcpEd,aACA,mBACA,uBACA,Qd8BW,Kc7BX,UACA,kBACA,gDACA,oBACA,gBAEA,YACE,UACA,mBACA,oBAEA,0BACE,mBAKJ,yBA5BF,OA6BI,QdUS,McLb,iBACE,kBACA,gBACA,WAIA,0BACE,gBAGF,0BACE,iBAGF,0BACE,iBAGF,yBAnBF,iBAoBI,gBAKJ,aACE,kBACA,MACA,QACA,iCACA,WACA,YACA,mBACA,YACA,kBACA,Md7EM,Kc8EN,eACA,aACA,mBACA,uBACA,QdCc,gBAME,acLhB,WdxEU,gCc0EV,iBACE,WACA,YAGF,eACE,eAGF,yBA5BF,aA6BI,WACA,YAEA,iBACE,WACA,YAGF,eACE,gBAMN,cACE,kBACA,WdjHM,KckHN,eACA,cdxDiB,KcyDjB,sCACA,gBACA,wBACA,gBACA,aACA,sBACA,sBACA,8BACA,QdzCc,Kc0Cd,iBACA,kBAEA,+BACE,eACA,gBACA,YACA,SACA,gBAGF,yBAzBF,cA0BI,WAKJ,cACE,uBACA,aACA,mBACA,8BACA,6BACA,uBdzFiB,Kc0FjB,wBd1FiB,Kc4FjB,2BACE,Ud9HW,Kc+HX,YdtHmB,IcuHnB,cACA,SAGF,yBAhBF,cAiBI,uBAEA,2BACE,gBAMN,YACE,kBACA,OACA,gBACA,cACA,eACA,YdrImB,IcuInB,cACE,SAEA,gBACE,WdrIO,Kc0IX,wBACE,kBAGF,yBArBF,YAsBI,kBACA,gBAKJ,cACE,uBACA,aACA,SACA,uBACA,gBAEA,+BACE,yBAGF,mCACE,8BAIF,yBAhBF,cAiBI,uBACA,mBACA,SAEA,oFAGE,YACA,YACA,gBAKN,iBACE,WACA,kBACA,yBACA,cd7KiB,Ic8KjB,UdlNa,KcmNb,YdzNoB,0Gc0NpB,wBACA,sBAEA,uBACE,aACA,ad7PW,Qc8PX,uCAGF,8BACE,MdrPS,QcyPX,uBACE,adnQY,QcqQZ,6BACE,adtQU,QcuQV,0CAOJ,8BACE,gBAGF,yBACE,kBACA,kBAEA,qCACE,WACA,YACA,mBZ1QJ,aACA,uBACA,mBY0QI,UdtPU,KcuPV,cdnNiB,IcqNjB,mDACE,gCACA,Md/RO,QckST,mDACE,+BACA,MdtSQ,QcySV,kDACE,gCACA,Md1SQ,Qc6SV,gDACE,6BACA,MdnTO,QcuTX,sCACE,UdhRS,KciRT,YdxQiB,IcyQjB,cd9PO,Ic+PP,MdhTM,QcmTR,wCACE,MdnTM,QcoTN,Ud3RW,KepCjB,YACE,aACA,mBACA,IfwDW,IevDX,eACA,Uf8Ba,Ke5Bb,6BACE,aACA,mBACA,IfiDS,IehDT,cAEA,+BACE,MfHM,QeIN,WfuFY,aerFZ,qCACE,MfnBO,QeuBX,oCACE,MfbM,QecN,YfuBe,IepBjB,qDACE,YACA,MfjBO,QekBP,Yf6BO,IevBb,UACE,aACA,IfoBW,IenBX,gCACA,cfsBW,KepBX,oBACE,kBAGF,oBACE,aACA,mBACA,IfUS,IeTT,kBACA,MfxCQ,QeyCR,YfLiB,IeMjB,sCACA,mBACA,Wf+Cc,ae7Cd,0BACE,MfhDM,QemDR,2BACE,Mf/DS,QegET,oBfhES,QemEX,2BACE,gBACA,WftDI,QeuDJ,Mf1DM,Qe2DN,cfKe,KeJf,eACA,YfxBiB,Ie8BvB,WACE,aACA,IfrBW,IesBX,QfvBW,IewBX,WfpEQ,QeqER,cfXiB,KeajB,qBACE,iBACA,Mf5EQ,Qe6ER,YfzCiB,Ie0CjB,cflBe,IemBf,WfYc,aeVd,2BACE,MfnFM,QeoFN,8BAGF,4BACE,WfrFE,KesFF,MfpGS,QeqGT,Wf7EM,0BeoFV,0BACE,cf9CS,KegDT,qCACE,Uf7ES,Ke8ET,YfhEiB,IeiEjB,yBACA,MfvGM,QewGN,iBACA,qBAIJ,uBACE,gBAEA,iCACE,cflEO,IeqET,iCACE,aACA,mBACA,IftEO,KeuEP,iBACA,Mf1HM,Qe2HN,cf9Da,Ie+Db,WfhCY,aekCZ,2CACE,WACA,YbxHN,aACA,uBACA,mBawHM,UfvGO,KewGP,MflII,QeqIN,uCACE,WfnIE,QeoIF,MfnJO,QeqJP,iDACE,MftJK,Qe0JT,wCACE,Wf3IG,Qe4IH,Mf5JO,Qe6JP,Yf7Ga,Ie+Gb,kDACE,MfhKK,QesKX,oCACE,YfvGQ,KewGR,Wf7GO,Ie8GP,gBAEA,8CACE,UfxIO,KeyIP,iBAOR,YACE,aACA,mBACA,If5HW,IegIP,2CACE,WACA,mBACA,oBAKF,yCACE,WfpMO,QeqMP,MfvLA,KewLA,aftMO,Qe2Mb,uBACE,aACA,mBACA,uBACA,eACA,YACA,cACA,WfpMI,KeqMJ,yBACA,cf5Ie,Ie6If,Mf1MQ,Qe2MR,UflLW,KemLX,YfvKiB,IewKjB,WfjHc,aemHd,6BACE,Wf5MI,Qe6MJ,af5NS,Qe6NT,Mf7NS,QegOX,kEAEE,Uf5LS,KegMb,uBACE,cACA,Mf5NQ,QgBZZ,UACE,eACA,WhBYM,KiBdR,WACE,kBACA,gBAEA,gB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,sCACE,cACA,qBACA,aACA,mBACA,ItBrIK,IsBsIL,WtB3FU,asB6FV,4CACE,MtBrMK,QsByMT,+CACE,oBACA,mBACA,YtBhJK,IsBiJL,WAEA,mDACE,WACA,YAKN,mCACE,aACA,mBACA,uBACA,UtBtLS,KsBuLT,MtB9MO,QsBgNP,yBAPF,mCAQI,2BACA,UtB5LO,KsB6LP,YtBrKK,KsB2KX,yBACE,2CACE,gBACA,MtB7NM,QsB8NN,atB/KO,KsBqLb,aACE,kBACA,kBACA,WtBrOM,KsBsON,ctB3KiB,KsB6KjB,yBACE,ctBzLS,KsB2LT,6BACE,gBACA,WAEA,yBAJF,6BAKI,iBAKN,yBACE,UtB/Na,KsBgOb,MtBzPQ,QsB2PR,yBAJF,yBAKI,UtBpOS,MsB6Of,aACE,WACA,6BAEA,qBACE,gBACA,ctBxNS,KsB4NX,iCACE,aACA,mBACA,mBACA,YACA,eACA,gCAEA,yBARF,iCASI,cAGF,8CACE,eACA,4CACA,gBACA,WACA,kBAKJ,+BACE,aACA,sBAIF,8BACE,aACA,mBACA,YACA,eACA,gCACA,gCACA,gBAEA,oCACE,yBAGF,yBAbF,8BAcI,sBACA,uBACA,YACA,aACA,UAGF,wCACE,aACA,mBACA,uBACA,eACA,4CACA,gBACA,WACA,mBAEA,yBAVF,wCAWI,2BACA,sBAEA,gDACE,yBACA,gBACA,cACA,kBACA,gBAIJ,+CACE,2BACA,mBAEA,yBACE,uDACE,cAIJ,kEACE,aACA,mBACA,SACA,cACA,qBACA,WAEA,wEACE,gBAIF,iFACE,aACA,gBACA,cACA,cAEA,yBANF,iFAOI,gBAON,yBADF,gDAEI,cAaN,yBADF,wCAEI,iCACA,+BACA,oBAEA,sDACE,mBAGF,qDACE,wBACA,mBACA,WACA,mBACA,gBACA,eACA,WACA,mBACA,kCACA,gBAEA,gFACE,6BAYF,wEACE,WAGF,uEACE,OACA,gBACA,gBAEA,yBALF,uEAMI,gBAGF,0FACE,oBACA,mBACA,eAEA,qGACE,oBACA,mBACA,cACA,iBACA,WAGF,iHACE,cACA,kBACA,mBACA,gBACA,uBACA,gBAGF,gHACE,qBACA,mBACA,gBACA,uBACA,gBACA,sBAGF,iHACE,cACA,gBACA,gBACA,cACA,eAKN,wEACE,YAEA,4FACE,eACA,WACA,gBAIJ,wEACE,YAGF,uEACE,WAGF,sEACE,YAWV,YACE,WACA,WtBhgBM,KsBigBN,ctBtciB,KsBucjB,gBACA,yBACA,qCAEA,oBACE,gBACA,ctBvdS,KsB2dX,+BACE,aACA,mBACA,mBACA,YACA,eAEA,0BAPF,+BAQI,cAGF,4CACE,aACA,mBACA,uBACA,iBACA,eACA,YtB1fiB,IsB2fjB,MtB9hBE,KsB+hBF,kBACA,oBAKJ,6BACE,aACA,sBAIF,4BACE,aACA,mBACA,YACA,iBACA,WtBtdc,csBudd,gCAEA,uCACE,mBAIF,4CACE,yBAGF,2CACE,iBtB7jBE,KsBgkBJ,kCACE,yBAGF,0BAzBF,4BA0BI,sBACA,uBACA,ItBzhBO,IsB0hBP,QtBzhBO,MsB4hBT,sCACE,aACA,mBACA,uBACA,iBACA,eACA,cACA,kBACA,mBAEA,0BAVF,sCAWI,UtBhkBO,KsBikBP,cAEA,8CACE,yBACA,YtBxjBa,IsByjBb,MtB9lBE,QsB+lBF,atB/iBG,IsBgjBH,eACA,iBAKJ,6CACE,2BAEA,0BACE,qDACE,cAIJ,gEACE,oBACA,mBACA,ItBlkBG,IsBmkBH,cACA,qBACA,WtB1hBQ,asB4hBR,sEACE,MtBpoBG,QsBwoBL,+EACE,aACA,YtB1lBS,IsB2lBT,MtB/nBA,QsBgoBA,cAEA,0BANF,+EAOI,gBAKN,wDACE,oBACA,mBACA,cACA,cAEA,4DACE,WACA,YAOJ,0BADF,8CAEI,cAMN,yCACE,aACA,mBACA,uBACA,SACA,iBAEA,0BAPF,yCAQI,WACA,2BACA,YtBxnBK,KsB+nBb,gBACE,oBACA,mBACA,uBACA,eACA,YACA,mBACA,kBACA,eACA,YtBrpBoB,IsBspBpB,4CACA,WACA,+BACA,eACA,WtBlmBgB,asBomBhB,sBACE,2BACA,WtBrrBQ,0BsBwrBV,uBACE,wBAIF,yBACE,yBACA,qBACA,cAEA,+BACE,sDAIJ,yBACE,yBACA,qBACA,cAEA,+BACE,iDAIJ,2BACE,yBACA,qBACA,cAEA,iCACE,sDAIJ,wBACE,yBACA,qBACA,cAEA,8BACE,wDAQN,kBACE,aACA,mBACA,uBACA,SACA,eAEA,kCACE,aACA,mBACA,uBACA,WACA,YACA,gBACA,YACA,eACA,cACA,WtB3qBc,asB6qBd,wCACE,MtBrxBS,QsBwxBX,2CACE,WACA,mBAGF,oCACE,UtBzvBW,KsB6vBf,sCACE,aACA,mBACA,SAGF,qCACE,aACA,mBACA,uBACA,eACA,YACA,eACA,cACA,gBACA,YACA,eACA,WtB5sBc,asB8sBd,2CACE,MtBtzBS,QsByzBX,4CACE,YtBxwBa,IsBywBb,cAMN,gBACE,aACA,8BACA,mBACA,ctBvwBW,KsBwwBX,cACA,ItBzwBW,KsB2wBX,yBARF,gBASI,sBACA,oBACA,ItB/wBS,MsBkxBX,6BACE,UtB3yBa,KsB4yBb,MtBr0BQ,QsBs0BR,gBAEA,oCACE,MtBr1BS,QsBs1BT,YtBryBiB,IsBsyBjB,UtBhzBS,KsBmzBX,yBAXF,6BAYI,QACA,mBAIJ,4BACE,aACA,ItBtyBS,IsBwyBT,yBAJF,4BAKI,SAGF,kCACE,OACA,gBACA,iBACA,yBACA,ctBryBa,IsBsyBb,UtB10BS,KsB20BT,WtBxwBY,asB0wBZ,yBATF,kCAUI,gBAGF,wCACE,aACA,atBv3BO,QsBw3BP,uCAGF,+CACE,MtB/2BK,QsBm5Bb,yBAGE,cACE,aACA,mBACA,WACA,YACA,WtB15BI,KsB25BJ,yBACA,kBACA,kBAEA,oBACE,OACA,YACA,sBACA,YACA,yBACA,eACA,cAEA,iCACE,cACA,eAGF,0BACE,aAIJ,gCACE,kBACA,WACA,QACA,2BACA,WACA,YACA,UACA,gBACA,YACA,eACA,aACA,mBACA,uBAEA,oCACE,WACA,YACA,cAMN,gBACE,sBACA,oBACA,SACA,UACA,gBAEA,6BACE,QACA,gBACA,eACA,MtBj9BE,KsBk9BF,mBACA,kCACA,gBAEA,oCACE,cACA,YtBx7BW,IsBy7BX,eAIJ,8BACE,QAKJ,YACE,yBACA,gBAGA,+BACE,aAIF,6BACE,aACA,sBAIF,4BACE,aACA,mBACA,mBACA,8BACA,YACA,cACA,oCACA,gCAEA,kCACE,yCAIF,8CACE,aAIF,6CACE,OACA,2BACA,UACA,eACA,YtB/+Bc,IsBg/Bd,cAEA,qDACE,aAGF,gEACE,aACA,mBACA,QACA,cACA,qBAEA,+EACE,aAGF,2EACE,oBACA,mBACA,cACA,gBAEA,+EACE,WACA,YAOR,iDACE,cACA,WACA,UACA,eACA,YtBrhCc,IsBshCd,cACA,yBAEA,yDACE,aAOR,YACE,SACA,eAEA,4BACE,WACA,YAEA,8DAEE,eACA,WACA,YAIJ,gCACE,SAGF,+BACE,eACA,YACA,eACA,cACA,YtB1jCgB,IsB4jChB,sCACE,YtB1jCW,IsB2jCX,eCtmCJ,yBADF,+BAEI,iBACA,WASN,eACE,aACA,sBACA,QACA,cvB0CY,KuBxCZ,yBANF,eAOI,iBAGF,yBACE,kBACA,mBACA,gBACA,WvBuEc,auBpEZ,6CACE,mBACA,qBAEA,6HAEE,sBAGF,uDACE,WAOR,6BACE,aACA,mBACA,gBACA,YACA,0BACA,gBACA,yBACA,mBACA,eACA,WvByCc,auBvCd,0BAZF,6BAaI,kBACA,iBAGF,6CACE,eACA,gBACA,WACA,kBACA,cAEA,0BAPF,6CAQI,eACA,mBAIJ,gDACE,OACA,eACA,WACA,mBACA,oBAEA,0BAPF,gDAQI,eACA,iBAKJ,uCACE,cACA,aACA,mBACA,uBACA,WACA,YACA,WACA,8BAEA,2CACE,WACA,WAMN,2BACE,aACA,4BAEA,0BAJF,2BAKI,cAGF,+CACE,eACA,WACA,gBAEA,0BALF,+CAMI,gBAIF,iDACE,kBAEA,4DACE,gBAIJ,oGAEE,aACA,kBAGF,kDACE,kBAGF,iDACE,MvBvJO,QuBwJP,0BAEA,uDACE,MvB1JO,QuB8JX,oDACE,WvBjJE,QuBkJF,gBACA,cvB3FW,IuB4FX,YvBnIW,oCuBoIX,eACA,MvBrKO,QuBwKT,mDACE,WvB1JE,QuB2JF,yBACA,cvBnGW,IuBoGX,QvB/GK,KuBgHL,gBACA,cAEA,wDACE,yBACA,UACA,MvBxKE,QuBgLZ,UACE,+BACA,WvB9KQ,QuB+KR,kBAEA,yBALF,UAMI,mBAIJ,eACE,UvBtHoB,OuBuHpB,cAGF,YACE,cvB1IY,KuB4IZ,yBAHF,YAII,cvB9IU,MuBiJZ,wBACE,UvBxKY,KuByKZ,YvBjKe,IuBkKf,MvBzMQ,QuB0MR,cvBxJS,KuB0JT,6BACA,sCACA,qBAEA,yBAVF,wBAWI,UvBnLU,MuBuLd,8BACE,UvB5La,KuB6Lb,MvBtNQ,QuBuNR,YvB5KiB,IuB8KjB,yBALF,8BAMI,UvBlMS,MuBwMf,qBACE,KACE,UACA,4BAGF,GACE,UACA,yBAKJ,WACE,WvB5OM,KuB6ON,cvBlLiB,KuBmLjB,WvBpOU,0BuBqOV,gBAEA,2BACE,gCAEA,sCACE,mBAIA,oDACE,8BACA,MvBzQO,QuB2QP,oEACE,yBAMR,6BACE,aACA,mBACA,8BACA,kBACA,UvBlPa,KuBmPb,YvBxOiB,IuByOjB,MvB9QQ,QuB+QR,eACA,WvBpLc,auBsLd,yBAXF,6BAYI,kBACA,UvB3PS,MuB8PX,mCACE,8BAGF,8CACE,OAGF,6CACE,cACA,eACA,MvBjSM,QuBkSN,WvBvMY,auB2MhB,8BACE,aACA,kBACA,UvBjRW,KuBkRX,MvB1SQ,QuB2SR,YvBhQiB,IuBiQjB,6BAEA,yBARF,8BASI,mBAGF,mCACE,cACA,6BChUN,mBACE,mBACA,kBACA,WACA,aACA,aACA,mBACA,uBACA,gBAEA,2BACE,WACA,kBACA,MACA,OACA,QACA,SACA,0LACA,0BACA,WAGF,gCACE,kBACA,QACA,SACA,gCACA,aACA,WACA,UAGF,sBACE,kBACA,UxBQY,KwBPZ,YxBee,IwBdf,MxBzBQ,QwB0BR,SACA,UACA,sBtBpCA,yBsBHJ,mBA4CI,aACA,cxBkBS,KwBhBT,gCACE,aAGF,sBACE,UxBZS,MyBvCf,OACE,aACA,uBACA,IzByDW,KyBxDX,kBACA,czBkEiB,IyBjEjB,czBwDW,KyBtDX,mBACE,eACA,cAGF,sBACE,OACA,gBAEA,6BACE,YzB8BiB,IyBzBrB,qBACE,+BACA,qCACA,cAIF,mBACE,gCACA,sCACA,yBACA,mBAEA,uBACE,cAGF,wBACE,OACA,UzBPS,KyBQT,YzBIe,IyBCnB,qBACE,gCACA,sCACA,6CAIF,kBACE,6BACA,mCACA,MzB1Da,Q0BNjB,qBACE,eACA,SACA,SACA,2BACA,cACA,aACA,sBACA,mBACA,SACA,oBAEA,yBAZF,qBAaI,SACA,UACA,WACA,eACA,qBAIJ,WACE,oBACA,gBACA,gBACA,kBACA,mBACA,mBACA,cACA,eACA,gBACA,yBACA,sCACA,eAEA,UACA,4BACA,iDAEA,mBACE,UACA,wBApBJ,WAwBE,8BAEA,2CACA,8CACA,2CACA,4CAEA,yBA/BF,WAgCI,gBAIF,yBApCF,WAqCI,gBACA,gBACA,kBACA,eACA,oBChDJ,YACE,aACA,mBACA,uBACA,IAXe,KAYf,gBAGA,6FAIE,aACA,mBACA,uBACA,MArBmB,KAsBnB,OAtBmB,KAuBnB,MArBqB,QAsBrB,qBACA,eACA,W3B2Ec,c2BzEd,iLACE,M3BhCS,Q2BmCX,iIACE,MA7BqB,QA8BrB,mBACA,oBAIF,6GACE,MAxCiB,KAyCjB,OAzCiB,KA0CjB,cAKJ,sBACE,aACA,mBACA,uBACA,eACA,YACA,Y3B1BkB,0G2B2BlB,UArDmB,KAsDnB,Y3BXkB,I2BYlB,MArDuB,QAsDvB,qBACA,kBACA,cACA,eACA,W3BwCc,c2BtCd,+CACE,M3BnES,Q2BuEX,mCACE,Y3BtBa,I2BuBb,MApEmB,QAqEnB,eAKJ,mBACE,kBACA,UACA,WACA,UACA,YACA,gBACA,sBACA,mBACA,SAKJ,oBACE,aACA,uBACA,eACA,W3BnCW,K4B5Db,sBACE,yBACA,YACA,WACA,aACA,mBAGF,YACE,aACA,mBACA,SACA,U5BkEoB,O4BjEpB,cACA,eACA,WAEA,yBATF,YAUI,SACA,gBAIF,iBACE,aACA,mBACA,uBACA,WACA,YACA,cAEA,qBACE,WACA,YACA,aACA,W5BmEY,c4BhEd,2BACE,K5BzCS,Q4B8Cb,sBACE,aACA,mBACA,uBACA,WACA,YACA,cAEA,0BACE,YACA,YACA,aAGF,yBAdF,sBAeI,WACA,YAEA,0BACE,UACA,aAMN,iBACE,Y5B3CkB,0G4B4ClB,eACA,Y5B5BkB,I4B6BlB,cACA,cACA,qBACA,mBACA,W5BwBc,c4BtBd,uBACE,M5BnFS,Q4BuFX,yBACE,Y5BtCa,I4BuCb,cAEA,+BACE,cACA,eAIJ,yBAzBF,iBA0BI,gBAMN,gBACE,yBACA,YACA,WAEA,yBALF,gBAMI,aAGF,iCACE,aACA,mBACA,SACA,U5BrCkB,O4BsClB,cACA,eACA,YACA,gBAEA,yBAVF,iCAWI,SACA,gBAIJ,sCACE,aACA,mBACA,SAEA,yBALF,sCAMI,UAIF,8DACE,WACA,cACA,YACA,YACA,yQACA,4BACA,2BACA,wBAEA,yBAVF,8DAWI,UACA,aAIJ,wCACE,Y5B9HgB,0G4B+HhB,eACA,Y5B/GgB,I4BgHhB,cACA,cACA,qBACA,mBACA,W5B3DY,c4B6DZ,8CACE,M5BtKO,Q4ByKT,yBAdF,wCAeI,gBAKJ,mDACE,Y5B9Ha,I4B+Hb,cACA,oBAIF,oDACE,aACA,mBACA,uBACA,WACA,YACA,YAEA,4DACE,WACA,cACA,WACA,YACA,+aACA,4BACA,2BACA,wBACA,W5B/FU,c4BkGZ,kEACE,+aC9MR,iBACE,eACA,kBACA,yBACA,yBACA,kBAEA,0BACE,aACA,uBACA,QAGF,uBACE,cACA,eACA,WACA,YAGF,iCACE,OAGF,wBACE,SACA,eACA,cAGF,8BACE,iBACA,eACA,cACA,gBAGF,4BACE,cACA,sBACA,eACA,iBCtCJ,uBACE,qBACA,gBACA,cACA,gBACA,eAIF,sBACE,aACA,mBACA,QACA,eACA,cAEA,6CACE,gBACA,cAGF,8CACE,cAGF,2CACE,kBACA,cAIJ,qBACE,qBAIF,sBACE,sBACA,kBACA,iBACA,mBAGF,kBACE,gBACA,UACA,SAGF,mBACE,eACA,kBACA,eACA,cAIF,kBACE,eACA,gCAEA,8BACE,iBAGF,6BACE,mBACA,oBAGF,6CACE,cAGF,oCACE,aACA,mBACA,8BACA,mBAGF,yCACE,aACA,mBACA,QAGF,sCACE,eACA,gBACA,WACA,sBAGF,2CACE,oBACA,mBACA,gBACA,yBACA,cACA,mBACA,cACA,gBAGF,0CACE,aACA,mBACA,SAGF,oCACE,cACA,eAGF,0CACE,mBACA,yBACA,kBACA,gBACA,eACA,gBACA,cACA,eACA,yBACA,aACA,mBACA,uBACA,YAEA,gDACE,yBAIJ,oCACE,aACA,yBACA,mBACA,eACA,iBACA,WACA,qBACA,sBAKJ,0BACE,cACA,WACA,eAGF,kBACE,aACA,sBACA,WAGF,2BACE,cACA,WACA,sBACA,kBACA,yBACA,mBACA,sBACA,eACA,gBACA,WACA,gBACA,iBACA,oBACA,aACA,uDACA,yBAEA,wCACE,cAGF,iCACE,qBAGF,iCACE,qBACA,sBACA,yCAIJ,0BACE,aACA,mBACA,8BACA,WACA,gBACA,SAGA,sDACE,oBACA,mBACA,QACA,eACA,cACA,eAIJ,0BACE,eACA,cAGF,0BACE,aACA,mBACA,uBACA,YACA,YACA,sBACA,cACA,yBACA,mBACA,eACA,gBACA,eACA,wBACA,cACA,oBACA,gBAEA,yBAlBF,0BAmBI,YAGF,gCACE,yBAGF,iCACE,0BAGF,mCACE,yBACA,qBACA,cACA,mBAIJ,0BACE,SACA,kBACA,yBACA,kBACA,kBACA,cACA,eC3QF,cACE,mBAEA,2BACE,eACA,gBACA,WACA,kBACA,gBAGF,0BACE,eACA,cACA,SACA,gBCEJ,UACE,kBACA,YACA,eACA,UACA,yBACA,kBACA,gBACA,sCACA,MAjBgB,QAkBhB,uBAIF,WACE,kBACA,SACA,WACA,WACA,YACA,UACA,SACA,gBACA,MA9BgB,QA+BhB,eACA,aACA,mBACA,uBACA,kBACA,gDAEA,iBACE,WApCc,QAqCd,MAzCc,QA8ClB,UACE,kBACA,mBACA,gCACA,aACA,mBACA,8BACA,SAEA,0BACE,eACA,gBACA,iBACA,MA3Dc,QA8DhB,0BACE,eACA,gBACA,MAhEc,QAoElB,UACE,uBAIF,UACE,aACA,mBACA,QACA,mBAEA,wBACE,oBACA,mBACA,uBACA,WACA,YACA,oBACA,WApFc,QAqFd,WACA,eACA,gBACA,UAEA,6CAjGc,QAoGhB,yBACE,eACA,gBACA,MAlGc,QAoGd,yCArGc,QA0GlB,aACE,aACA,8BACA,QACA,mBAEA,0BACE,kBACA,gBACA,eACA,yBACA,kBACA,gBACA,uDAEA,yCACE,cACA,eACA,gBACA,MA7HY,QAgId,0CACE,cACA,eACA,gBACA,MAnIY,QAoIZ,eAGF,6CA5Ic,QA8Id,oCACE,2BACA,WA9IY,QAgJZ,yDA/IY,QAqJlB,UACE,yBACA,kBACA,WAnJgB,QAoJhB,aACA,eACA,gBACA,gBACA,MA3JgB,QA4JhB,mBAIF,SACE,WACA,YACA,SACA,kBACA,WA1KgB,QA2KhB,WACA,eACA,eACA,gBACA,uBACA,gCAEA,0BAjLgB,QAmLhB,kBACE,WA7Kc,QA8Kd,MA/Kc,QAgLd,eAKJ,cACE,aACA,QACA,kBAEA,mCACE,OACA,aACA,oBACA,YACA,yBACA,kBACA,gBACA,kCAEA,6DA3Mc,QA8MhB,8BACE,OACA,YACA,YACA,eACA,SACA,aACA,yBACA,eACA,gBACA,qBACA,MArNc,QAuNd,2CACE,MArNY,QAsNZ,gBAIJ,0BACE,UACA,YACA,eACA,yBACA,kBACA,gBACA,MApOc,QAqOd,eACA,eACA,gBACA,mBACA,gCAEA,2CAvOc,QAwOd,6DAKJ,eACE,aACA,mBACA,8BACA,SACA,yBACA,kBACA,WApPgB,QAqPhB,kBACA,mBAEA,gCACE,eACA,gBACA,MA9Pc,QAiQhB,gCACE,aACA,qBACA,QAGF,0BACE,eACA,gBACA,qBACA,MAhRc,QAiRd,kCAEA,2CA1Qc,QA2Qd,2CA/Qc,QAkRhB,gCACE,eACA,gBACA,MArRc,QA0RlB,cACE,WACA,WA3RgB,QA4RhB,kBACA,gBACA,mBAEA,gCACE,YACA,WACA,WAzSc,QA0Sd,2BAEA,sDAnSc,QAwSlB,aACE,gBACA,gBACA,eACA,gBACA,gBACA,MAlTgB,QAoThB,4BAhTgB,QAoTlB,iBACE,gBACA,kBACA,0BACA,kBACA,mBACA,cACA,eACA,kBAIF,UACE,gBACA,iBACA,6BACA,eACA,gBACA,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A,mBACA,SACA,e/BphBE,0B+BghBJ,iBAOI,uBACA,gB/B1hBA,yB+BkhBJ,iBAYI,QACA,gBAIJ,kBACE,gBACA,WACA,mBAGF,oBACE,sBACA,eACA,iBAGF,iBACE,aACA,mBACA,QACA,e/BjjBE,0B+B6iBJ,iBAOI,wBAIJ,iBACE,eACA,gBACA,WACA,yBACA,iBACA,mBACA,oCACA,qBACA,qBAEA,uBACE,yBACA,2BACA,WAIJ,gBACE,WAGF,kBACE,WAGF,iBACE,kBACA,WACA,YACA,gBACA,yBACA,mBACA,aACA,mBACA,eACA,oCAEA,8BACE,0CACA,2BACA,oC/BnmBA,yB+BolBJ,iBAmBI,YACA,iBACA,gBAIJ,mBACE,OACA,WACA,YACA,YACA,aACA,yBACA,eACA,gBACA,uBACA,kBAEA,gCACE,cACA,gB/B3nBA,yB+B6mBJ,mBAkBI,gBAIJ,iBACE,gBACA,YACA,eACA,aACA,mBACA,uBACA,kBACA,WACA,WACA,YACA,UACA,oCAEA,uBACE,qBAEA,gCACE,aAIJ,0BACE,yBASJ,cACE,kBACA,aACA,mBACA,oBACA,WjC7pBM,KiC8pBN,gBACA,a/BxqBE,0B+BiqBJ,cAUI,YACA,qB/B9qBA,yB+BmqBJ,cAeI,YAEA,qBAGF,mCACE,kBACA,QAEA,sBACA,2BACA,4BACA,oBACA,6DAIF,gCACE,iBACA,cACA,eAEA,kBACA,WACA,U/B1sBA,yB+BmsBF,gCAWI,gBAIJ,gDACE,gBAIF,8BACE,aACA,qBACA,8BACA,mB/BztBA,0B+BqtBF,8BAOI,iB/B9tBF,yB+ButBF,8BAYI,aACA,mBACA,uBACA,gBACA,mBACA,mBAIJ,6BACE,eACA,gBACA,uBACA,mBACA,sBACA,gB/BhvBA,0B+B0uBF,6BASI,gB/BrvBF,yB+B4uBF,6BAcI,eACA,cACA,uBAIJ,gCACE,YjCtuBkB,0GiCuuBlB,eACA,uB/BjwBA,0B+B8vBF,gCAMI,gB/BtwBF,yB+BgwBF,gCAYI,cAIJ,4BACE,oBACA,mBACA,uBACA,QACA,YACA,YACA,kCACA,WACA,eACA,gBACA,kBACA,0CACA,oC/B3xBA,0B+B8wBF,4BAgBI,gBACA,gB/BjyBF,yB+BgxBF,4BAsBI,kBACA,QACA,QACA,2BACA,SACA,WACA,YACA,eACA,eACA,gBACA,kBACA,QACA,oBAGF,gCACE,WACA,YACA,8BAEA,qCACE,Y/B3zBJ,yB+BqzBA,gCAWI,UACA,YAIJ,kCACE,gCACA,2BACA,yCAEA,sCACE,0BAKN,mCACE,aACA,eACA,SACA,2B/Bl1BA,0B+B80BF,mCAWI,eACA,SACA,wB/B71BF,yB+Bg1BF,mCAkBI,aACA,sBACA,UAIJ,wBAEE,+BACA,gCACA,sBACA,aACA,gBACA,qCACA,mBACA,aACA,aACA,sBACA,8BACA,eACA,oCACA,QACA,eAEA,8BACE,6DACA,2BACA,+BACA,WAGA,0FAEE,W/Bj4BJ,0B+Bs2BF,wBAiCI,yBACA,0BACA,sBACA,iB/B54BF,yB+Bw2BF,wBAyCI,cACA,eACA,WACA,YACA,gBACA,iBACA,kBACA,mBACA,yBACA,gDACA,UASJ,0BACE,eACA,gBACA,uBACA,mBACA,gBACA,sH/B16BA,yB+Bo6BF,0BAUI,eACA,cACA,WACA,iBAKJ,gCACE,YjC55BkB,0GiC65BlB,eACA,uBACA,gBACA,gBACA,oBACA,qBACA,4BACA,gBACA,uBACA,gBACA,e/Bl8BA,yB+Bs7BF,gCAgBI,eACA,gBACA,eAOJ,iCACE,WACA,YACA,aACA,mBACA,uBACA,gBACA,oBAEA,qCACE,WACA,YACA,mB/B39BF,yB+B+8BF,iCAiBI,WACA,aAaN,cACE,kBACA,gBACA,yBACA,WACA,gB/Bn/BE,yB+B8+BJ,cAQI,iBACA,wBAGF,+BACE,kBACA,QACA,wGACA,oBAGF,yBACE,kBACA,U/BngCA,yB+BigCF,yBAKI,SACA,UACA,eACA,YAIJ,4BACE,aACA,mBACA,8BACA,S/B/gCA,0B+B2gCF,4BAOI,sBACA,SACA,mB/BthCF,yB+B6gCF,4BAaI,aACA,sBACA,SACA,kBACA,mBAIJ,4BACE,OACA,gB/BliCA,0B+BgiCF,4BAKI,kB/BviCF,yB+BkiCF,4BASI,kBAIJ,0BACE,aACA,sBACA,QACA,mB/BjjCA,0B+B6iCF,0BAOI,eACA,QACA,iB/BxjCF,yB+B+iCF,0BAaI,QACA,iBAIJ,yBACE,eACA,gBACA,6BACA,mB/BrkCA,yB+BikCF,yBAOI,gBAIJ,+BACE,eACA,gBACA,iBACA,4DACA,6BACA,sC/BhlCA,0B+B0kCF,+BASI,gB/BrlCF,yB+B4kCF,+BAaI,gBAIJ,gCACE,eACA,gBACA,0BACA,mB/B/lCA,0B+B2lCF,gCAOI,QACA,oB/BrmCF,yB+B6lCF,gCAYI,QACA,eACA,gBACA,mBACA,qBAIJ,8BACE,aACA,IjCxjCS,KEzDT,0B+B+mCF,8BAKI,QACA,wB/BvnCF,yB+BinCF,8BAUI,QACA,sBACA,SACA,YAIF,6CACE,qCACA,WACA,iBACA,mBACA,gCACA,qBAGF,2CACE,wCACA,WACA,2CAIA,mDACE,qCACA,kBACA,2BAMF,iDACE,yBACA,2BACA,0CAQN,0BACE,oBACA,mBACA,uBACA,QACA,YACA,eACA,eACA,gBACA,mBACA,oC/BhrCA,yB+BsqCF,0BAaI,eACA,gBACA,eACA,gBACA,WACA,YACA,kBACA,mBACA,wBACA,8BACA,kCACA,oBAGF,gCACE,2BACA,sCAIJ,2BACE,sBACA,cAEA,iCACE,oCAIJ,6BACE,qCACA,WACA,oCAEA,mCACE,qCAIJ,8BACE,UACA,aACA,uBAGA,+CACE,WACA,gBAGF,kCACE,WACA,YACA,mB/BtuCF,0B+BwtCF,8BAkBI,QACA,eACA,WACA,e/B/uCF,yB+B0tCF,8BA0BI,QACA,kBACA,MACA,QACA,WACA,YACA,eACA,cACA,UAEA,+CACE,WACA,gBACA,YAGF,kCACE,WACA,gBACA,aAuBR,gBACE,eACA,yB/BhyCE,yB+B8xCJ,gBAKI,mBAGF,gCACE,kBACA,mBAIA,8CACE,eACA,gBACA,uBAGF,2CACE,eACA,gBACA,uBACA,sHAIJ,8BACE,aAEA,2DACA,SAIF,mDACE,mBAGF,8BACE,sBACA,qCACA,mBACA,aACA,aACA,sBACA,kBACA,gBACA,oCAKA,oCACE,2BACA,iCAGF,yCACE,eACA,mBACA,2BAGF,iCACE,eACA,gBACA,uBACA,mBAGF,gCACE,eACA,uBACA,gBACA,UAGF,0CACE,kBACA,YACA,WACA,eACA,wBACA,oCAGF,gDACE,2BACA,0BAKJ,qCACE,sCACA,WACA,YAEA,gLAIE,WAGF,2CACE,yCAIJ,kCACE,yBACA,qBAEA,6CACE,6BAIJ,kCACE,yBACA,qBAEA,6CACE,cAwRN,mBACE,eACA,yBACA,WACA,kBACA,gB/BzrDE,yB+BorDJ,mBAQI,gBAGF,qCACE,kBACA,QACA,6DACA,oBAGF,iCACE,kBACA,mBACA,kBACA,U/B1sDA,yB+BssDF,iCAOI,oB/B7sDF,yB+BitDF,gCAGI,gBAIF,2CACE,eACA,gBACA,c/B3tDF,yB+BwtDA,2CAMI,gBAIJ,iDACE,eACA,gBACA,WACA,iBACA,sCACA,uF/BxuDF,yB+BkuDA,iDASI,eACA,qBAKN,+BACE,aACA,mBACA,8BACA,iBACA,cACA,kBACA,U/BxvDA,yB+BivDF,+BAUI,sBACA,UAIJ,8BACE,OACA,kBACA,aACA,sBACA,mBACA,SAEA,uDACE,sBACA,2BACA,uC/B3wDF,yB+BgwDF,8BAeI,SAIJ,sCACE,WACA,YACA,qCACA,kBACA,aACA,mBACA,uBACA,eACA,cACA,qCACA,sCACA,oC/B/xDA,yB+BmxDF,sCAeI,WACA,YACA,gBAIJ,+BACE,eACA,gBACA,cAGF,oCACE,aACA,qBACA,uBAGF,gCACE,gCACA,eACA,gBACA,0BACA,c/BzzDA,yB+BozDF,gCAQI,gBAIJ,8BACE,eACA,gBACA,WACA,gBAGF,kCACE,UACA,aACA,yH/B10DA,yB+Bu0DF,kCAMI,WACA,WACA,yHAMN,aACE,wCAGF,mBACE,KACE,UACA,2BAGF,GACE,UACA,yBAwHJ,oBACE,kBACA,iBACA,aACA,mBACA,uBACA,gB/B/9DE,yB+By9DJ,oBAUI,iBACA,gBAGF,oCACE,kBACA,QAEA,sBACA,2BACA,4BACA,gBAGF,+BACE,kBACA,UACA,mBACA,SACA,WACA,eACA,eAEA,uCACE,kBACA,Y/B1/DF,0B+Bw/DA,uCAKI,a/B//DJ,yB+B0/DA,uCASI,cAGF,4CACE,MACA,OAGF,6CACE,UACA,Q/B7gEJ,yB+B2gEE,6CAKI,WAMR,iCACE,aACA,sBACA,mBACA,SACA,kB/B3hEA,yB+BshEF,iCASI,UAIJ,+BACE,eACA,WACA,mBACA,gBACA,SACA,sH/BviEA,0B+BiiEF,+BASI,gB/B5iEF,yB+BmiEF,+BAcI,eACA,gBACA,cACA,uBACA,mBAIJ,oCACE,oBACA,mBACA,uBACA,YACA,sCACA,WACA,eACA,gBACA,2CACA,QACA,eACA,mBACA,oCAEA,0CACE,kCACA,2BACA,yCAGF,2CACE,wB/B/kEF,yB+ByjEF,oCA0BI,WACA,gBACA,YACA,eACA,eACA,gBACA,kBACA,oBAIJ,iCACE,kBACA,SACA,OACA,QACA,WACA,gBAIJ,qBACE,QC3mEF,sBACE,iBACA,cACA,iBACA,aACA,SACA,uBACA,WAEA,0BATF,sBAUI,sBACA,SACA,cAuCJ,iBACE,uDACA,yBACA,eACA,aACA,uBACA,yBACA,kBACA,SACA,2BACA,gBACA,mBAEA,wBACE,iBACA,WACA,aACA,mBACA,uBACA,UACA,eAIA,kDACE,YACA,aACA,kEACA,wBAIJ,0BACE,aACA,sBACA,uBACA,SAGF,wBACE,oBACA,mBACA,QACA,iBACA,gBACA,yBACA,mBACA,eACA,4CACA,gBACA,cAEA,6BACE,UACA,WACA,kBACA,mBAIJ,wBACE,eACA,4CACA,gBACA,WACA,SAGF,uBACE,eACA,4CACA,cACA,gBACA,SAKJ,oBACE,YACA,sBACA,yBACA,mBACA,kBACA,gBACA,SACA,mBACA,cACA,kBAEA,0BAZF,oBAaI,YACA,mBAGF,yBAjBF,oBAkBI,eACA,OACA,SACA,4BACA,8BACA,WlC/HQ,gCkCgIR,QlCzDY,KkC0DZ,0BACA,SACA,gBAEA,gCACE,yBAKN,iBACE,aACA,sBACA,SAEA,+BACE,gBACA,mBAGF,6BACE,UACA,eACA,4CACA,gBACA,cACA,eACA,qBACA,aACA,mBACA,8BACA,kBAEA,mCACE,cACA,+BAGF,oCACE,gBACA,cAOA,mFACE,OAGF,kFACE,aACA,mBACA,uBACA,WACA,YACA,8BAEA,oFACE,eACA,MlC5ME,QkC6MF,WlClHQ,akCsHZ,0FACE,MlC9NO,QkCiOT,2FACE,yBAGF,2FACE,MlCtOO,QkC4Ob,2BACE,alChLS,KkCiLT,aACA,gBAGA,6CACE,aACA,gBACA,gEAEA,sDACE,iBACA,WlC9LK,IkC+LL,clC9LK,IkCmMX,2BACE,iBACA,UlC7NW,KkC8NX,MlCtPQ,QkCuPR,eACA,clC7Le,IkC8Lf,WlC9Jc,akC+Jd,aACA,mBACA,kBACA,kBAEA,mCACE,YACA,alCjNO,IkCkNP,MlC9QS,QkC+QT,WAGF,iCACE,oCACA,MlCzQM,QkC0QN,kBAEA,yCACE,UAIJ,kCACE,mCACA,MlC9RS,QkC+RT,YlC/Oe,IkCiPf,0CACE,UAIJ,qCACE,OACA,mBACA,gBACA,uBAMN,oBACE,OAGA,WACA,iBACA,aACA,sBAEA,yBATF,oBAUI,mBAIF,sCACE,UlCxRW,KkCyRX,MlC9SI,KkC+SJ,mBAEA,6CACE,MlCpUS,QkCqUT,YlCpRiB,IkC0RvB,mBACE,wBAIF,mBACE,aACA,8BACA,mBACA,clCtRW,KkCwRX,yBANF,mBAOI,sBACA,uBACA,IlC3RS,MkCgSX,qBACE,UlC3TW,KkC4TX,MlCpVQ,QkCqVR,YlClTkB,IkCmTlB,clCvSS,IkC0SX,qBACE,UlC7TY,KkC8TZ,YlCrTe,IkCsTf,MlC7VQ,QkCkWZ,mBACE,kBACA,YAEA,yBAJF,mBAKI,YAGF,wBACE,kBACA,aACA,mBAGF,yBACE,OACA,YACA,sBACA,yBACA,mBACA,eACA,aACA,WlC5Rc,akC8Rd,+BACE,qBACA,uCAGF,sCACE,cAIJ,sCACE,kBACA,UACA,QACA,2BACA,WACA,YACA,yBACA,YACA,eACA,aACA,mBACA,uBAEA,0CACE,WACA,YAQN,eACE,aACA,4DACA,SACA,WAIF,UACE,gBACA,mBACA,sBACA,6CACA,eACA,kBACA,WACA,aACA,gBACA,sEAEA,iDAGE,2BACA,6CACA,wDACA,YAEA,iGACE,yBAGF,+LAEE,WAMJ,iBACE,kBACA,SACA,UACA,WACA,aACA,mBACA,QACA,YAGF,gBACE,yBACA,mBACA,iBACA,oBACA,mBACA,uBACA,YAEA,qBACE,eACA,4CACA,gBACA,cACA,mBACA,gBACA,uBAMJ,iBACE,cACA,oBACA,mBACA,iBACA,mBACA,eACA,4CACA,gBACA,mBACA,mBACA,cAEA,oCAEA,4DACA,iEACA,8DACA,4DAGF,gBACE,kBACA,SACA,UACA,WACA,eACA,4CACA,gBACA,WACA,SACA,oBACA,qBACA,4BACA,gBACA,uBACA,sBACA,gBACA,mBAGF,eACE,kBACA,UACA,UACA,WACA,eACA,4CACA,gBACA,cACA,gBACA,SACA,oBACA,qBACA,4BACA,gBACA,uBACA,sBACA,gBACA,mBAGF,gBACE,kBACA,YACA,WACA,WACA,YACA,aACA,mBACA,uBACA,oBAEA,oBACE,eACA,gBACA,mBAMN,iBACE,kBACA,kBACA,MlCzjBU,QkC2jBV,6BACE,eACA,clC3gBS,KkC4gBT,WAGF,oBACE,UlCtiBW,KkCuiBX,YlC9hBmB,IkC+hBnB,MlCrkBQ,QkCskBR,clCphBS,KkCuhBX,mBACE,UlChjBa,KkCijBb,MlC1kBQ,QkC+kBZ,aACE,aACA,uBACA,mBACA,QlC5hBY,KkC8hBZ,sBACE,WACA,YACA,yBACA,iBlCrmBW,QkCsmBX,clCzhBmB,IkC0hBnB,kCAIJ,gBACE,GACE,0BAKJ,oBACE,aACA,eACA,SACA,OACA,QACA,SACA,gCACA,aACA,UACA,4BACA,oBAEA,yBAbF,oBAcI,eAGF,2BACE,UACA,oBAKJ,oBACE,aACA,sBACA,IlC9kBW,KkCklBb,iBACE,aACA,IlCvlBW,IkCwlBX,gCACA,clCtlBW,KkCwlBX,6BACE,kBACA,yBACA,YACA,sCACA,UlCvnBa,KkCwnBb,YlC7mBiB,IkC8mBjB,MlClpBQ,QkCmpBR,eACA,WlCzjBc,akC0jBd,kBACA,YAEA,qBACA,qBAEA,mCACE,MlCxqBS,QkCyqBT,oCAGF,oCACE,MlC7qBS,QkC8qBT,YlC7nBiB,IkC8nBjB,oBlC/qBS,QkCqrBf,aACE,aACA,sBACA,IlCznBW,KkC0nBX,iBAEA,oBACE,aAKJ,aACE,MAEA,+BACE,cAKJ,mBACE,aACA,sBACA,mBACA,uBACA,IlClpBW,KkCmpBX,0BACA,kBACA,WlCpsBM,KkCqsBN,yBACA,clC3oBiB,KkC4oBjB,WlC7rBU,0BkC+rBV,4CACE,eACA,cAGF,6CACE,SACA,UlCvrBW,KkCwrBX,YlC7qBe,IkC8qBf,MlCrtBQ,QkCwtBV,4CACE,SACA,UlChsBa,KkCisBb,MlC1tBQ,QkC6tBV,wBACE,WlC9qBS,IkC+qBT,gBAKJ,mBACE,eACA,QlCprBW,KkCqrBX,WlCruBM,KkCsuBN,YACA,clC5qBiB,KkC6qBjB,gBAEA,4CACE,aACA,qBACA,eACA,YACA,clChsBS,KkCmsBX,6CACE,kBACA,alCrsBS,KkCssBT,UlC9tBa,KkC+tBb,YlCltBe,IkCmtBf,MlC1vBQ,QkC2vBR,uBAEA,qDACE,WACA,kBACA,OACA,QACA,2BACA,UACA,YACA,WlChxBS,QkCixBT,kBAIJ,4CACE,UlClvBW,KkCmvBX,MlC3wBQ,QkC8wBV,sCACE,kBACA,gBAEA,6CACE,WACA,kBACA,MlCpuBO,KkCquBP,QACA,UACA,WACA,gBACA,+BACA,gCACA,wBACA,oBAIJ,yBACE,WACA,YACA,sBACA,YlCnxBkB,0GkCoxBlB,UlC9wBW,KkC+wBX,MlCxyBQ,QkCyyBR,WlCryBM,QkCsyBN,yBACA,clC9uBe,IkC+uBf,gBACA,wBACA,qBACA,eACA,WlCntBc,ckCqtBd,8CACE,alCjzBO,QkCkzBP,WlCjzBE,KkCozBJ,+BACE,aACA,WlCtzBE,KkCuzBF,alCr0BS,QkCs0BT,wCAGF,kCACE,MlC7zBO,QkC8zBP,WlC5zBI,QkC6zBJ,mBAIJ,uCACE,gBACA,kBACA,UlC/yBW,KkCgzBX,YlC7xBiB,IkC8xBjB,MlCz0BQ,QkC00BR,WlCt0BO,QkCu0BP,oCACA,8BACA,clClxBe,IkCoxBf,+CACE,aACA,MlC71BS,QkC81BT,YlC5yBa,IkCgzBjB,yBAlHF,mBAmHI,Ql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,mBACE,gBACA,UlCzvCS,KkC2vCT,wBACE,gBACA,eAMN,kBACE,IlC1uCS,KkC8uCX,aACE,IlC9uCS,KkC+uCT,iBACA,eACA,kBAIF,oBACE,eACA,kBAIF,oBACE,WACA,kBAGF,UACE,cC/zCJ,YACE,aACA,mBACA,uBACA,mBACA,kBACA,mBACA,gBACA,mBAGF,iBACE,WACA,gBACA,cACA,kBAGF,YACE,yBACA,UACA,aACA,sBACA,mBAEA,yBAPF,YAQI,gBAIJ,cACE,aACA,sBACA,mBACA,mBACA,WAEA,+BACE,aACA,mBACA,uBACA,QACA,mBAIJ,iBACE,WACA,YACA,aACA,mBACA,uBAEA,qBACE,YAIJ,aACE,YnC7BoB,0GmC8BpB,eACA,gBACA,WACA,SACA,cAGF,mBACE,YnCtCoB,0GmCuCpB,eACA,gBACA,cACA,kBACA,SACA,gBAGF,YACE,WACA,gBAEA,wBACE,mBACA,kBAGE,oDACE,kBACA,UACA,QACA,2BACA,aACA,mBACA,uBACA,UAEA,wDACE,eAIJ,oDACE,kBAKN,wBACE,WACA,YACA,eACA,YnCjFkB,0GmCkFlB,eACA,gBACA,WACA,mBACA,yBACA,kBACA,aACA,wBAEA,qCACE,cAGF,8BACE,qBACA,gBAGF,8BACE,qBAIJ,2BACE,aACA,mBACA,2BACA,QACA,gBACA,mBAEA,gDACE,WACA,YACA,yBACA,kBACA,eACA,gBACA,gBACA,kBACA,cAEA,wDACE,mBACA,qBAEA,+DACE,WACA,kBACA,SACA,QACA,8CACA,UACA,WACA,kBACA,6BAKN,iCACE,YnC/IgB,0GmCgJhB,eACA,gBACA,cACA,eACA,iBACA,cACA,mBAKN,cACE,WACA,YACA,aACA,YnC/JoB,0GmCgKpB,eACA,gBACA,WACA,mBACA,YACA,kBACA,eACA,wBACA,mBACA,iBAEA,oBACE,kCAGF,qBACE,gCAGF,uBACE,WACA,mBAIJ,uBACE,aACA,uBACA,mBACA,WAEA,iCACE,OACA,YACA,oBACA,mBACA,uBACA,mBACA,yBACA,YnCvMkB,0GmCwMlB,eACA,gBACA,cACA,qBACA,wBACA,iBACA,kBAEA,uCACE,2BACA,8BACA,kBAGF,wCACE,kBAGF,sCACE,4BACA,+BAGF,uCACE,qCAGF,wCACE,mCAMN,aACE,WACA,mBACA,kBACA,kBACA,eACA,aACA,mBACA,QAEA,yBACE,gCACA,cACA,sCAGF,2BACE,gCACA,cACA,sCAGF,wBACE,6BACA,cACA,mCAGF,eACE,eAKJ,eACE,eACA,MACA,OACA,QACA,SACA,gCACA,aACA,mBACA,uBACA,WACA,UACA,oBACA,4BAEA,sBACE,UACA,mBAGF,wBACE,WACA,YACA,sBACA,yBACA,kBACA,kCAIJ,gBACE,GACE,0BjCvUA,yBiC6UF,YAEE,8BACA,mBACA,aACA,gBACA,gBAGF,iBACE,eACA,UAGF,YACE,UAGF,YAEE,WACA,YACA,mBAGF,eAEE,eACA,gBACA,cACA,mBACA,gBAIA,wBACE,mBAGF,wBAEE,WACA,YACA,eACA,eACA,kBACA,qBAEA,qCAEE,eACA,cAIJ,2BAEE,yBACA,gBACA,mBACA,eACA,QAEA,gDAEE,WACA,YACA,kBAGF,iCAEE,eACA,cACA,iBAKN,cAEE,WACA,YACA,eACA,gBACA,kBACA,mBAGF,aAEE,SAEA,eACE,eACA,gBACA,cAGF,6BACE,eACA,cAIJ,aACE,gBACA,mBACA,kBACA,gBC5bJ,uBACE,aACA,mBACA,uBACA,mBACA,kBACA,mBACA,mBAGF,4BACE,WACA,gBACA,cACA,kBACA,QpC8CW,KoC3Cb,uBACE,gBACA,mBACA,qCACA,uBACA,aACA,sBACA,oBACA,kBAEA,yBAVF,uBAWI,uBACA,oBAKJ,uBACE,aAIF,wBACE,aAIF,uBACE,aACA,MACA,mBACA,WACA,yBACA,UAEA,iCACE,OACA,aACA,mBACA,uBACA,kBACA,YpC9BkB,0GoC+BlB,eACA,gBACA,cACA,qBACA,kBACA,gBAEA,iCACA,kCACA,gBACA,wBACA,eACA,kBAEA,uCACE,MpC5ES,QoC+EX,wCACE,MpChFS,QoCiFT,gBAEA,+BACA,gCACA,iCACA,6BACA,8BAGF,yBApCF,iCAqCI,kBACA,gBAQN,eACE,WACA,mBACA,kBACA,mBACA,YpC3EoB,0GoC4EpB,eACA,aACA,mBACA,SACA,6BAEA,iBACE,eACA,cAGF,2BACE,gCACA,cACA,sCAGF,6BACE,gCACA,cACA,sCAGF,0BACE,6BACA,cACA,mCAKJ,uBACE,WACA,gBACA,yBACA,UAEA,mCACE,aACA,sBACA,MACA,mBAEA,gDACE,gBAKJ,mCACE,aAGF,mCACE,WACA,YACA,eACA,YpCrIkB,0GoCsIlB,eACA,gBACA,cACA,gBACA,yBACA,mBACA,aACA,yBAEA,gDACE,cAGF,yCACE,qBAGF,yCACE,qBACA,0CAGF,4CACE,mBACA,qBACA,cACA,mBAGF,yCACE,qBAIJ,oCACE,YACA,sBACA,YpC3KkB,0GoC4KlB,eACA,gBACA,cACA,gBACA,yBACA,mBACA,aACA,eACA,yBACA,gBACA,wBACA,qBACA,wLACA,4BACA,qCACA,yBAEA,0CACE,qBAGF,0CACE,qBACA,0CAGF,6CACE,yBACA,qBACA,cACA,mBACA,WAGF,2CACE,YACA,eAMN,oBACE,8BAGF,mBACE,aACA,QACA,mBACA,mBACA,YAEA,qCACE,OACA,aACA,mBACA,uBACA,eACA,SAEA,uDACE,aAGF,0CACE,aACA,mBACA,uBACA,WACA,iBACA,YpCnPgB,0GoCoPhB,eACA,gBACA,cACA,kBACA,wBACA,kBAGF,oEACE,gBACA,cACA,qCAGF,gDACE,cAMN,mBACE,aACA,mBACA,QACA,WAEA,iCACE,WACA,eACA,cAGF,gEAEE,OACA,YAGF,oCACE,aACA,mBACA,uBACA,cACA,eACA,cACA,WACA,YACA,yBAEA,4CACE,YAIJ,yBAlCF,mBAmCI,eACA,QAEA,iCACE,OACA,WAGF,oCACE,cAMN,mBACE,aAGF,kBACE,kBACA,aACA,mBACA,WACA,QAGA,uBACE,gBACA,mBAIF,8BACE,OACA,YAIF,8BACE,kBACA,WACA,QACA,2BACA,YpCxVkB,0GoCyVlB,eACA,gBACA,cACA,oBAEA,yBAXF,8BAYI,eACA,aAMN,yCAEE,YACA,eACA,YpC1WoB,0GoC2WpB,eACA,gBACA,WACA,mBACA,YACA,mBACA,eACA,yBACA,cACA,mBACA,cAEA,qDACE,4DACA,0BAGF,uDACE,0DAGF,2DACE,WACA,mBAGF,yBA/BF,yCAgCI,WACA,YACA,eACA,gBAKJ,qCACE,aACA,uBACA,SACA,gBACA,UACA,gBACA,yBAEA,wGAEE,OACA,YACA,cACA,YpC5ZkB,0GoC6ZlB,eACA,gBACA,YACA,mBACA,eACA,yBACA,cACA,aACA,mBACA,uBACA,qBAGF,oDACE,cACA,mBAEA,0DACE,oCAGF,2DACE,mCAIJ,oDACE,WACA,mBAEA,0DAEE,mBAIF,2DACE,2BAGF,6DACE,WACA,mBAIJ,yBA7DF,qCA8DI,SAEA,wGAEE,YACA,gBAMN,iBACE,eACA,MACA,OACA,QACA,SACA,gCACA,aACA,mBACA,uBACA,aACA,4BAEA,wBACE,aAGF,0BACE,WACA,YACA,sBACA,yBACA,kBACA,kCAKJ,gBACE,GACE,0BAIJ,qBACE,KACE,UACA,4BAGF,GACE,UACA,yBAKJ,yBACE,uBACE,kBACA,gBACA,mBAGF,4BACE,gBAOJ,yBACE,WACA,iBACA,4BACA,kBAEA,yBANF,yBAOI,mBAGF,wCACE,mBAEA,qDACE,eACA,cACA,mBACA,cAEA,yBANF,qDAOI,gBAIJ,sDACE,eACA,cACA,mBAEA,yBALF,sDAMI,gBAIJ,4DACE,YpCxjBgB,0GoCyjBhB,eACA,gBACA,cACA,SAEA,mEACE,cACA,gBAGF,yBAZF,4DAaI,gBAOR,kBACE,gBACA,mBAEA,mCACE,gBACA,yBACA,mBACA,kBACA,mBACA,wBAEA,8CACE,gBAGF,yCACE,qBACA,0CAGF,8CACE,aACA,mBACA,uBACA,SACA,eAEA,yBAPF,8CAQI,sBACA,SAIJ,+CACE,YpC9mBgB,0GoC+mBhB,eACA,gBACA,cAEA,yBANF,+CAOI,gBAIJ,8CACE,YpCznBgB,0GoC0nBhB,eACA,gBACA,cAEA,yBANF,8CAOI,gBAOR,iBACE,gBACA,cACA,8BACA,mCACA,mBACA,kBAEA,oCACE,2FACA,sCACA,yCACA,aACA,mBACA,SACA,0DACA,2BACA,UAEA,oDACE,aACA,uBACA,mBACA,4CAGF,+CACE,eACA,gBACA,cACA,kBAEA,sDACE,cACA,gBACA,eAGF,iDACE,cAON,4BACE,YpCrrBkB,0GoCsrBlB,eACA,gBACA,cACA,SACA,aACA,mBACA,uBACA,QACA,eAEA,8BACE,cACA,eAGF,uCACE,cACA,gBACA,0BACA,0BAEA,6CACE,sBAIJ,yBA5BF,4BA6BI,eACA,mBAKN,oBACE,GACE,wBACA,WAIJ,qBAEE,QAEE,mBACA,gDAGF,IACE,sBACA,mDlCnwBA,yBkC2wBF,uBACE,UACA,SAGF,4BACE,aAGF,uBACE,uBACA,mBAGF,uBACE,mBAEA,iCACE,iBACA,eAKF,mCACE,mBAGF,mCACE,YACA,eAGF,oCACE,YACA,eAIJ,mBACE,eACA,QAEA,iCACE,OACA,WAGF,oCACE,aAKF,8BACE,eACA,WAIJ,yCAEE,WACA,YACA,eACA,eAGF,qCACE,mBACA,SACA,gBAEA,wGAEE,OACA,YACA,eAKJ,yBACE,kBACA,0BAEA,wCACE,mBAEA,qDACE,eACA,mBAGF,sDACE,eACA,kBAGF,4DACE,eAMN,kBACE,mBAEA,mCACE,aACA,kBACA,kBAEA,+CACE,eAGF,8CACE,eAMN,iBACE,kBACA,kBAEA,4BACE,gBCh5BN,uBACE,aACA,mBACA,uBACA,mBACA,kBACA,mBACA,gBACA,mBAGF,4BACE,WACA,iBACA,cACA,kBAGF,uBACE,yBACA,UACA,aACA,sBACA,mBAEA,yBAPF,uBAQI,gBAIJ,eACE,aACA,sBACA,mBACA,mBACA,WAGF,kBACE,aACA,mBACA,uBACA,SACA,mBACA,WAEA,qCACE,WACA,cAGF,gCACE,YrCtBkB,0GqCuBlB,eACA,gBACA,WACA,SACA,sBAIJ,gBACE,YrChCoB,0GqCiCpB,eACA,gBACA,cACA,kBACA,SACA,gBAGF,cACE,WACA,gBACA,aACA,mBACA,SACA,mBACA,uBAGF,kBACE,OACA,gBACA,sCACA,mBACA,kBACA,aACA,sBACA,mBACA,kBACA,qBACA,iDACA,cACA,eACA,uCACA,kBACA,gBAGA,0BACE,WACA,kBACA,MACA,OACA,WACA,YACA,2FACA,UACA,4BACA,oBAGF,qCACE,WACA,YACA,mBACA,aACA,mBACA,uBACA,mBACA,iDACA,sCAGF,qCACE,aACA,sBACA,mBAGF,8BACE,YrCtGkB,0GqCuGlB,eACA,gBACA,aACA,mBACA,gBACA,cACA,wBAGF,6BACE,YrCjHkB,0GqCkHlB,eACA,cACA,gBACA,SACA,gBACA,oBACA,0BAGF,wBACE,2BACA,gBAEA,gCACE,UAKF,qDACE,6DACA,cAGF,wCACE,qBACA,0CAEA,oDACE,cAGF,2DACE,6DACA,WACA,mCACA,0CAMJ,uDACE,6DACA,cAGF,0CACE,qBACA,2CAEA,sDACE,cAGF,6DACE,6DACA,WACA,oCACA,2CAMR,uBACE,aACA,mBACA,mBACA,yBACA,kBACA,gBACA,YACA,gBACA,WACA,cAEA,uCACE,OACA,aACA,mBACA,uBACA,YrCpMkB,0GqCqMlB,eACA,gBACA,cACA,qBACA,YACA,wBAEA,6CACE,mBAIJ,2CACE,UACA,YACA,mBnC/OA,yBmCqPF,uBACE,mBACA,8BACA,kBACA,aACA,gBACA,gBAGF,4BACE,eACA,UAGF,eACE,mBAEA,iCACE,kBAEA,oDACE,WACA,YAGF,+CACE,eAKN,gBACE,eACA,gBACA,cACA,mBACA,iBAGF,cACE,gBACA,sBACA,SACA,mBAGF,kBACE,kBACA,mBACA,mBACA,gBACA,mBACA,SAEA,qCACE,WACA,YACA,gBACA,cAEA,yCACE,WACA,YAIJ,qCACE,uBAGF,8BACE,eACA,kBAGF,6BACE,eAGF,wBACE,eACA,gBAIJ,uBACE,gBACA,YAEA,uCACE,gBC9UN,4BACE,WtC2DY,KsC1DZ,YtCwDW,KsCtDX,uCACE,WAWJ,+CAEE,WACA,cAGA,mFACE,eACA,gBACA,WACA,mBAGF,iGACE,gBACA,yBACA,mBACA,oDACA,kBAGF,mFACE,aAGF,yFACE,aACA,mBACA,SACA,gCACA,oBACA,mBAEA,+FACE,eACA,gBACA,cACA,SAGF,iGACE,WACA,YAIJ,mEACE,aACA,sBACA,SACA,mBAEA,2FACE,mBACA,SAEA,mGACE,OACA,aACA,sBACA,SAGF,yBAXF,2FAYI,sBACA,UAKN,uFACE,SACA,UACA,WAGF,uFACE,WAGF,6HACE,qBACA,uBAGF,+HACE,8BAGF,uEACE,sBAGF,iFACE,eACA,gBACA,WAGF,+EACE,kBACA,kBACA,WAEA,6FACE,YACA,mBACA,kBACA,SACA,MACA,cACA,eACA,gBAIJ,8SAIE,YACA,mBACA,yBACA,mBACA,eACA,cACA,eACA,WACA,sBAGF,wKAEE,8BAGF,+EACE,aACA,mBACA,QACA,WAEA,gNAEE,OACA,kBACA,UAIJ,qEACE,eACA,WAGF,qFACE,aACA,mBACA,QAGF,0IAEE,YACA,mBACA,cACA,eACA,gBACA,YACA,mBACA,eACA,mBACA,eAGF,mFACE,aACA,mBACA,kBACA,OAEA,2GACE,mBAIJ,uEACE,kBACA,WACA,cACA,eACA,gBAGF,2EACE,aACA,8BACA,mBACA,aAGF,+EACE,YACA,mBACA,WACA,eACA,gBACA,YACA,mBACA,eACA,eACA,YACA,aACA,uBACA,mBACA,qBAGF,6EACE,aACA,QAGF,uEACE,YACA,mBACA,WACA,eACA,gBACA,YACA,mBACA,eACA,eACA,YACA,uBAGF,qEACE,YACA,mBACA,WACA,eACA,gBACA,YACA,mBACA,eACA,eACA,YAGF,iFACE,mBACA,kBACA,uBACA,aACA,mBACA,SACA,mBAEA,yFACE,WACA,YACA,cACA,cAGF,qFACE,cACA,eACA,gBACA,SAGF,+FACE,mBAEA,mGACE,cAKN,yFACE,qBACA,kBACA,kBAEA,+FACE,WACA,eACA,iBACA,kBAEA,qHACE,gBClTR,oBAEE,kBACA,aACA,qBACA,SrCRE,0BqCGJ,oBAQI,sBACA,QACA,mBrCfA,yBqCKJ,oBAeI,mBACA,eACA,qBACA,QACA,mBACA,+BACA,gBACA,gCACA,oBAGF,oCACE,eACA,YvCce,IuCbf,MvC1BQ,QuC2BR,SrCjCA,0BqC6BF,oCAOI,gBrCtCF,yBqC+BF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A,QACA,UACA,WACA,YACA,UACA,+BACA,yBACA,cvCrpCe,IuCspCf,MvCvtCI,QuCwtCJ,eACA,WvC9nCU,auC+nCV,aACA,mBACA,uBACA,WvCjtCI,0BuCktCJ,WAEA,uEACE,WACA,YAGF,yEACE,WvC9uCM,QuC+uCN,avC/uCM,QuCgvCN,MvCtuCF,KuCuuCE,qBACA,WvC7tCE,+BuCguCJ,0EACE,sBAKN,gDACE,aAGF,qDACE,kBACA,WvCvvCE,KuCwvCF,yBACA,cvC/rCa,IuCgsCb,UvCpuCS,KuCquCT,YvCztCe,IuC0tCf,MvC/vCM,QuCgwCN,eACA,WvCrqCY,auCuqCZ,2DACE,WvC/wCO,QuCgxCP,avChxCO,QuCixCP,MvCnwCA,KuCswCF,4DACE,sBAMN,+BACE,aACA,IvCjuCS,IuCkuCT,cvCjuCS,KuCmuCT,2CACE,OAGF,2CACE,oBACA,mBACA,IvC5uCO,IuC6uCP,kBACA,WvCzyCS,QuC0yCT,YACA,cvCnuCa,IuCouCb,MvC9xCE,KuC+xCF,UvCzwCS,KuC0wCT,YvC9vCe,IuC+vCf,eACA,WvCzsCY,auC0sCZ,mBAEA,+CACE,cAGF,iDACE,WvCvzCS,QuCwzCT,2BACA,WvCjyCI,+BuCoyCN,kDACE,wBAKN,wBACE,WvCvwCS,KuCwwCT,gBAEA,wCACE,QvC3wCO,KuC4wCP,WvC1zCI,QuC2zCJ,gCAEA,kDACE,UvCzyCO,KuC0yCP,MvCl0CI,QuCo0CJ,yDACE,MvCj1CK,QuCk1CL,YvCjyCa,IuCsyCnB,kCACE,iBACA,gBAEA,qDACE,UAGF,2DACE,WvCj1CE,QuCo1CJ,2DACE,WvCn1CM,QuCo1CN,kBAEA,iEACE,WvC31CG,QuCg2CT,iCACE,aACA,mBACA,8BACA,gCACA,WvC3wCY,auC6wCZ,4CACE,mBAGF,uCACE,WvC12CE,QuC62CJ,6CACE,YvC71CW,oCuC81CX,UvC11CO,KuC21CP,MvCp3CI,QuCq3CJ,YvCh1Ca,IuCm1Cf,gDACE,oBACA,mBACA,uBACA,WACA,YACA,UACA,WvC53CA,KuC63CA,yBACA,cvCr0CW,IuCs0CX,MvCj4CI,QuCk4CJ,eACA,WvCxyCU,auC0yCV,oDACE,WACA,YAGF,uDACE,sBASV,uCACE,8BACA,oBACA,WvCn2CY,KuCo2CZ,gBACA,cAEA,yBAPF,uCAQI,qBrCr6CA,yBqCg7CJ,yCAEI,aACA,8BACA,kCACA,mBACA,SACA,kBACA,aACA,+BAEA,qDACE,uBACA,uBACA,gBACA,4BACA,0BACA,YvCl5Ca,IuCm5Cb,6BACA,cAEA,mEACE,oCACA,yBACA,uBAGF,iEACE,oCACA,sBACA,wBAOR,iBAEE,kBACA,cACA,cACA,kBrC19CE,yBqCq9CJ,iBASI,YACA,+BACA,iBACA,+BACA,aACA,sBACA,mBACA,4BAIF,6BAEE,crC3+CA,yBqCy+CF,6BAMI,yBACA,kBACA,uBACA,WACA,oBAMA,0CACE,YACA,arC3/CJ,yBqCy/CE,0CAMI,YACA,cAON,oCACE,gCACA,MvCxgDU,QuC4gDd,sCACE,cvCl9CS,KE5DT,yBqC6gDF,sCAKI,oBASF,iEACE,2BACA,oBACA,qBACA,oCACA,2BACA,uBAIF,kHACE,2BACA,8BACA,8BACA,qBACA,oCAEA,8IACE,0BACA,2BACA,sBACA,yBACA,oBAKJ,yDACE,2BACA,8BACA,4DACA,wBACA,wBACA,8BACA,kCACA,8BACA,qBAEA,yEACE,wBACA,8BACA,iBAIF,6EACE,wBACA,iCACA,8BACA,sBACA,6BAGA,gGACE,sBACA,uBACA,6BACA,8BACA,oCACA,wBACA,8BACA,kCACA,6BACA,2BAEA,+GACE,sBACA,uBACA,8BACA,+CAKJ,uGACE,8BACA,gCAEA,sHACE,0CAMJ,6FACE,0BACA,2BACA,yBACA,6BACA,4BACA,oBAGF,oGACE,yBACA,2BAIF,yFACE,0BACA,2BACA,sBACA,6BACA,4BACA,oBACA,8BAGF,sGACE,2BAKJ,oEACE,uBACA,sBACA,oCACA,yBACA,oBACA,6BACA,qBAMJ,+DACE,2BACA,oCACA,8BACA,+DACA,wBACA,8BAIF,kDACE,wBACA,iCACA,oBACA,8BAEA,6DACE,2BAKJ,kDACE,0BACA,2BACA,sBACA,4BACA,oBAGA,iEACE,yBACA,0BACA,2BAKJ,kDACE,sBACA,uBACA,8BACA,oCACA,8BACA,0BACA,0BACA,2BACA,sBACA,iCAEA,+DACE,yBACA,2BAGF,wDACE,wBACA,gCAKJ,qDACE,sBACA,wBACA,8BACA,oCACA,8BACA,wBACA,0BACA,2BACA,sBACA,iCACA,uBAEA,kEACE,yBACA,2BAGF,2DACE,wBACA,gCAKJ,4DACE,wBACA,oCACA,0BAEA,0EACE,0BACA,2BACA,yBAKJ,uDACE,sBACA,wBACA,8BACA,oCACA,8BACA,wBACA,iCACA,8BACA,kCACA,oBACA,6BACA,0BACA,uCAEA,iEACE,gCACA,8BAIF,yEACE,sBACA,uBACA,4BACA,6BAIF,yEACE,wBACA,8BACA,kCAEA,6EACE,sBACA,uBAKJ,qEACE,0BACA,2BACA,sBACA,6BACA,oBACA,4BAIF,oEACE,0BACA,2BACA,yBACA,6BACA,oBACA,4BAIF,mEACE,8BACA,sBACA,uBACA,8BACA,uBACA,uBACA,0BACA,2BACA,wBACA,8BACA,kCACA,0BACA,oBAEA,yEACE,8BAKJ,wEACE,6BACA,oBACA,sBACA,sBACA,uBACA,qCACA,uBACA,6BACA,sBACA,wBACA,8BACA,kCACA,0BAEA,8EACE,qCAKJ,mEACE,wBAKJ,oDACE,wBACA,8BACA,oBACA,sBACA,8BAEA,8DACE,kBAKJ,+CACE,wBACA,8BACA,oBACA,sBACA,8BAEA,kEACE,kBACA,uBACA,8BACA,oCACA,8BACA,0BACA,0BACA,2BACA,yBACA,wBACA,8BACA,iCAMJ,8CACE,sBACA,uBACA,uBACA,8BACA,0BACA,2BACA,wBACA,8BACA,kCACA,mBACA,0BACA,yBAEA,kDACE,sBACA,uBACA,yBAGF,yDACE,8BACA,yBAEA,+DACE,8BAIJ,4DACE,8BACA,yBAEA,kEACE,8BAMN,oDACE,wBACA,oCACA,wBACA,yBACA,2BAGA,gEACE,8BACA,uBACA,8BACA,uBACA,uBACA,sBACA,0BACA,2BACA,0BACA,wBACA,8BACA,kCAEA,sEACE,8BAQN,kHACE,gBACA,mBACA,mBACA,UAEA,8IACE,eACA,gBACA,WACA,SAKJ,yDACE,gBACA,mBACA,iDACA,aACA,aACA,mBACA,uBACA,mBACA,UAEA,yEACE,aACA,mBACA,MAGF,6EACE,aACA,sBACA,mBACA,kBACA,YAEA,gGACE,WACA,YACA,aACA,mBACA,uBACA,kBACA,mBACA,kBACA,yBAGF,wFACE,aACA,mBACA,uBAEA,4FACE,WACA,YACA,oCAIJ,6FACE,eACA,gBACA,cACA,kBAGF,yFACE,eACA,gBACA,WAIA,uGACE,mBACA,qBAGF,mGACE,YAGF,oGACE,cACA,gBAGF,gGACE,gBAKN,oEACE,aACA,mBACA,uBACA,YACA,WACA,yBACA,cACA,kBACA,UAEA,yEACE,aAMN,+DACE,2BACA,oCACA,8BACA,+DACA,wBACA,8BAIF,gDACE,wBACA,iCACA,oBACA,8BACA,8BACA,4BACA,kCAEA,2DACE,2BAIJ,0DACE,wBACA,8BACA,mBACA,sBACA,uBACA,wBAEA,2EACE,0BACA,2BACA,sBAGF,0EACE,yBACA,2BACA,qBACA,2BACA,uBAEA,iFACE,wBACA,0BACA,yBAKN,0DACE,sBAIF,kDACE,8BACA,oCACA,8BACA,uBACA,0BACA,0BACA,sBACA,sBACA,iCAEA,+DACE,yBACA,2BAIJ,qDACE,8BACA,oCACA,8BACA,wBACA,0BACA,sBACA,wBACA,sBACA,iCACA,uBAEA,kEACE,yBACA,2BAKJ,4DACE,aACA,yBACA,gBAEA,0EACE,eACA,cAKJ,uDACE,aACA,sBACA,mBACA,uBACA,WACA,aACA,mBACA,yBACA,mBACA,kBACA,aAEA,0EACE,aACA,sBACA,mBACA,uBACA,YACA,WACA,YACA,yBAEA,4FACE,mBACA,cAEA,gGACE,WACA,YAEA,qGACE,UAIF,qGACE,aAMR,yEACE,gBACA,aACA,sBACA,mBACA,QACA,mBACA,kBAEA,uFACE,eACA,gBACA,WACA,SAGF,sFACE,eACA,gBACA,cACA,SAIJ,mEACE,gBACA,mBACA,WACA,mBACA,YACA,YACA,eACA,eACA,gBACA,aACA,mBACA,uBACA,eACA,0CAEA,yEACE,mBAMN,oDACE,wBACA,oCACA,2BACA,yBACA,2BACA,sBAEA,gEACE,8BACA,8BACA,uBACA,uBACA,uBACA,sBACA,0BACA,2BACA,0BACA,wBACA,8BACA,kCACA,4BAEA,sEACE,8BAMN,4GAEE,aACA,mBACA,SACA,mBAEA,0RAEE,OACA,mBACA,yBACA,mBACA,YACA,eACA,eACA,WACA,aACA,mBAEA,8UACE,cACA,gBAIJ,oJACE,mBACA,cAGF,oIACE,mBACA,cACA,YACA,mBACA,WACA,YACA,eACA,gBACA,aACA,mBACA,uBACA,eAEA,gJACE,mBAIJ,0IACE,mBACA,cACA,YACA,mBACA,WACA,YACA,eACA,gBACA,aACA,mBACA,uBACA,eAEA,sJACE,mBAKN,mDACE,cACA,aACA,mBACA,uBACA,eAIJ,gCACE,cvCl0EU,KE7DV,yBqC83EF,gCAKI,iBAGF,mCACE,UvCl2ES,KuCm2ET,YvCz1Ea,IuC01Eb,MvC54ES,QuC64ET,cvCh1EO,KE1DT,yBqCs4EA,mCAQI,eACA,oBAIJ,oDACE,UvCl3EW,KuCm3EX,MvC54EM,QuC64EN,crCt5EF,yBqCm5EA,oDAOI,eACA,gBAEA,+DACE,cAGF,+DACE,eASV,eACE,WvCj6EM,KuCk6EN,cvCv2EiB,KuCw2EjB,QvCl3EW,KuCm3EX,WvCz5EU,+BuC05EV,cvCp3EW,KuCq3EX,gBAEA,yBACE,aACA,mBACA,8BACA,eACA,gCAEA,oCACE,mBAGF,qCACE,UvC95ES,KuC+5ET,MvCv7EM,QuCw7EN,YvCp5Ee,IuCq5Ef,gBAGF,qCACE,UvCp6EW,KuCq6EX,MvC/7EM,QuCg8EN,YvC35Ee,IuC85EjB,sCACE,aACA,mBACA,IvCr5EO,IuCs5EP,OACA,yBAEA,iDACE,YvCt7EW,oCuCu7EX,UvCn7EO,KuCo7EP,WvCz8EE,QuC08EF,iBACA,cvCn5EW,IuCo5EX,MvC19ES,QuC29ET,avCh6EK,IuCm6EP,6GAEE,iBACA,WvCp9EA,KuCq9EA,yBACA,cvC75EW,IuC85EX,eACA,WvC/3EU,auCi4EV,yHACE,WvCz+EK,QuC0+EL,avC1+EK,QuC2+EL,MvC79EF,KuCm+EF,8CACE,iBACA,iCACA,cACA,cvCz6Ea,KuC06Eb,UvCn9EO,KuCo9EP,YvCt8Ee,IuC48EvB,uBACE,WvC/+EQ,QuCg/ER,cvCt7EiB,KuCu7EjB,QvCl8EW,KuCm8EX,cvCl8EW,KuCm8EX,gBAEA,0BACE,UvC/9EW,KuCg+EX,YvCr9EmB,IuCs9EnB,MvC5/EQ,QuC6/ER,cvC38ES,KuC88EX,kCACE,aACA,eACA,IvCl9ES,IuCm9ET,cvCl9ES,KuCo9ET,4CACE,iBACA,WvCrgFE,KuCsgFF,yBACA,cvCz8Ee,KuC08Ef,UvCl/ES,KuCm/ET,MvCvhFS,QuC2hFb,iCACE,UvCz/EW,KuC0/EX,MvCjhFQ,QuCkhFR,kBAIJ,iBACE,WvCnhFS,QuCohFT,cvC39EiB,KuC49EjB,QvCt+EW,KuCu+EX,cvCv+EW,KuCw+EX,gBAEA,oBACE,UvCpgFW,KuCqgFX,YvC1/EmB,IuC2/EnB,MvCjiFQ,QuCkiFR,cvCh/ES,KuCm/EX,6BACE,YvCn/ES,KuCo/ET,MvCtiFQ,QuCuiFR,UvC/gFW,KuCghFX,YvC5/EgB,IuC8/EhB,gCACE,cvC3/EO,IuCggFb,gBACE,aACA,uBACA,IvClgFW,KuCmgFX,eAGA,yBAPF,gBAQI,mBACA,mBACA,uBACA,SACA,UACA,YAGF,4DAEE,oBACA,mBACA,IvCphFS,IuCqhFT,kBACA,cvC1gFe,IuC2gFf,UvC9iFa,KuC+iFb,YvCpiFiB,IuCqiFjB,WvC9+Ec,auC++Ed,qBACA,eACA,YAEA,wEACE,eAIF,yBAnBF,4DAoBI,YACA,YACA,gBACA,iBACA,kBACA,eACA,YvCpjFa,IuCqjFb,wBAIJ,6BACE,yBACA,MvC/lFI,KuCgmFJ,WvCrlFQ,+BuCulFR,mCACE,2BACA,WvCxlFM,gCuC4lFR,yBAXF,6BAYI,gBAEA,mCACE,eACA,iBAKN,+BACE,WvCnnFI,KuConFJ,MvCvnFQ,QuCwnFR,yBAEA,qCACE,WvCvnFI,QuCwnFJ,avCvoFS,QuCwoFT,MvCxoFS,QuC4oFX,yBAZF,+BAaI,yBACA,cACA,YAEA,qCACE,yBACA,eAUR,mBACE,eACA,YACA,WACA,YACA,YACA,4BACA,mBACA,yBACA,YACA,MvCzpFM,KuC0pFN,aACA,mBACA,8BACA,eACA,WvCjpFU,gCuCkpFV,wBACA,QvCjlFc,IuColFd,+BACE,eACA,YvCjoFe,IuCkoFf,MvCtqFI,KuCuqFJ,mBAIF,+BACE,aACA,mBACA,mBACA,uBACA,WvChrFI,KuCirFJ,WACA,YACA,oBACA,cACA,kBAEA,2CACE,eACA,YvCrpFa,IuCspFb,cACA,cAGF,0CACE,eACA,YvC5pFa,IuC6pFb,cACA,cACA,eAIJ,yBACE,2BACA,2CAGF,0BACE,2BAGF,yBAjEF,mBAmEI,aACA,WACA,YACA,YACA,0BAEA,+BACE,eAGF,+BACE,WACA,YACA,kBAEA,2CACE,eAGF,0CACE,gBAcN,kCACE,aACA,eACA,IvCzsFS,IuC0sFT,uBAGF,wBACE,oBACA,mBACA,uBACA,QACA,kBACA,mBACA,YACA,mBACA,wBACA,eACA,YAEA,8BACE,mBAGF,uCACE,eACA,YvC7uFgB,IuC8uFhB,WACA,mBACA,gBACA,uBACA,gBACA,kBAGF,yCACE,cACA,WACA,YACA,UACA,yBACA,YACA,cvC/tFiB,IuCguFjB,cACA,eACA,aACA,mBACA,uBACA,wBACA,gBAEA,6CACE,WACA,YAGF,+CACE,0BACA,qBAGF,gDACE,sBAIJ,yBA3DF,wBA4DI,iBACA,YAEA,uCACE,eACA,gBAGF,yCACE,WACA,YAEA,6CACE,WACA,aAcV,oCAGE,WACA,2DAGA,sMAGE,eACA,gBACA,cACA,cACA,kBACA,UACA,YACA,gBACA,2DAEA,yBAbF,sMAcI,2BAKJ,0FACE,gBACA,mBACA,+CACA,aACA,aACA,mBACA,uBACA,mBACA,sBAEA,yBAXF,0FAYI,aACA,gBAGF,yBAhBF,0FAiBI,cAIJ,kEACE,aACA,mBACA,MAIF,+DACE,aACA,sBACA,mBACA,QACA,WAEA,yBAPF,+DAQI,YAGF,yBAXF,+DAYI,YAIA,uIACE,mBACA,qBACA,WAGF,8HACE,cACA,gBAGF,iIACE,gBAMN,oFACE,WACA,YACA,kBACA,mBACA,2BACA,aACA,mBACA,uBACA,cACA,cAEA,gGACE,cACA,mBAEA,yBAJF,gGAKI,sBACA,wBAIJ,yBAtBF,oFAuBI,WACA,aAGF,yBA3BF,oFA4BI,WACA,aAKJ,2EACE,cACA,cAEA,yBAJF,2EAKI,sBACA,wBAGF,yBATF,2EAUI,sBACA,wBAKJ,2EACE,eACA,gBACA,cACA,cAEA,yBANF,2EAOI,gBAGF,yBAVF,2EAWI,gBAKJ,8EACE,eACA,gBACA,cACA,kBACA,mBACA,cAEA,yBARF,8EASI,gBAGF,yBAZF,8EAaI,gBAKJ,8EACE,YACA,aACA,mBACA,cACA,mBAEA,yBAPF,8EAQI,WACA,oBAGF,yBAZF,8EAaI,WACA,oBAKJ,kEACE,mBACA,yBACA,mBACA,kBACA,cACA,eACA,mBAIF,8EACE,gBACA,yBACA,mBACA,kDACA,kBACA,mBAIF,kEACE,aACA,sBACA,SACA,mBAEA,mGACE,gBAKJ,kEACE,eACA,gBACA,cACA,SACA,UACA,cACA,gBAGF,2EACE,cACA,eACA,gBAIF,kEACE,cACA,WACA,YACA,mBACA,yBACA,mBACA,eACA,eACA,gBACA,cACA,sBACA,aACA,4BACA,oBACA,gBACA,gBAEA,yGACE,cACA,gBAGF,oFACE,qBAKJ,2EACE,cACA,WACA,aACA,mBACA,yBACA,mBACA,kBACA,eACA,gBACA,cACA,sBACA,aACA,YACA,4BACA,oBACA,gBAEA,kHACE,cACA,gBAGF,6FACE,qBAKJ,oFACE,aACA,yBACA,gBAGF,wEACE,eACA,cAIF,iFACE,WACA,iBACA,mBACA,yBACA,mBACA,aACA,mBACA,uBACA,kBACA,4BACA,eAEA,+GACE,qBACA,mBAKJ,4GACE,gBAEA,+JACE,mBACA,eACA,uBACA,aACA,kBAGF,2KACE,WACA,YAGF,+JACE,eAGF,yJACE,YACA,eACA,eAIJ,uFACE,aACA,sBACA,mBACA,SACA,aAGF,uFACE,eACA,gBACA,cACA,SACA,kBAGF,oFACE,eACA,gBACA,cACA,SACA,kBAGF,iFACE,mBACA,WACA,YACA,mBACA,YACA,eACA,eACA,gBACA,eACA,oBACA,mBACA,uBACA,eACA,oBACA,2BAEA,mGACE,mBAIJ,oFACE,gBACA,iBACA,iBACA,mBACA,cAGF,gGACE,kBACA,SACA,WACA,WACA,YACA,2BACA,YACA,kBACA,WACA,eACA,eACA,aACA,mBACA,uBAIF,qEACE,aACA,SACA,mBACA,WACA,mBAEA,mGACE,OAIJ,wEACE,aACA,SACA,mBACA,WACA,mBAGF,iFACE,OACA,YACA,mBACA,yBACA,mBACA,eACA,eACA,gBACA,cACA,aACA,mBACA,sBAIF,qEACE,WACA,YACA,YACA,mBACA,eACA,gBACA,eACA,oBACA,mBACA,uBACA,QACA,cACA,oBACA,wBAEA,uFACE,YAIF,oFACE,mBACA,cAIF,6FACE,mBACA,cAKJ,wEACE,aACA,yBACA,UAGF,2EACE,YACA,YACA,mBACA,WACA,YACA,mBACA,eACA,gBACA,eACA,oBACA,2BACA,oBACA,mBACA,uBAEA,6FACE,mBAKJ,yBACE,8EACE,kBACA,mBAGF,kEACE,MAGF,8EACE,WAGF,+DACE,WAGF,8EACE,eACA,mBACA,kBAGF,qEACE,WACA,eAGF,2EACE,YAUN,YACE,WACA,2DAGA,oCACE,mBAIF,4CACE,aACA,mBACA,SACA,mBACA,WACA,kBACA,iBAEA,yBATF,4CAUI,QACA,mBACA,iBAIJ,sCACE,OACA,gBACA,kBACA,uBACA,wBAEA,qBAGA,yDACE,aAKJ,8BACE,aACA,QACA,mBACA,cAGF,6BACE,oBACA,mBACA,uBACA,iBACA,YACA,mBACA,2BACA,mBACA,eACA,gBACA,cACA,eACA,wBACA,sBACA,4CAEA,mCACE,gCAGF,oCACE,mBACA,WAGF,yBA1BF,6BA2BI,iBACA,YACA,gBAIJ,6BACE,WACA,YACA,kBACA,gBACA,yBACA,cACA,aACA,mBACA,uBACA,eACA,wBACA,cACA,qCAEA,mCACE,qBACA,cACA,mBAGF,sCACE,YACA,mBAEA,4CACE,qBACA,cACA,gBAIJ,yBAhCF,6BAiCI,WACA,YAEA,iCACE,WACA,aAON,qBACE,WACA,aACA,sBAIF,8BACE,aACA,yBACA,mBACA,mBACA,SAEA,yBAPF,8BAQI,mBACA,eACA,8BACA,mBACA,UAIJ,2BACE,aACA,mBACA,OAEA,gDACE,aACA,mBACA,QACA,eACA,eACA,cACA,gBACA,iBAEA,sDACE,cAKN,2BACE,kBACA,YACA,YACA,cAEA,yBANF,2BAOI,YAGF,4CACE,WACA,YACA,sBACA,yBACA,mBACA,yBACA,eACA,aACA,wBAEA,kDACE,qBACA,sBAGF,yDACE,cAIJ,+BACE,kBACA,WACA,QACA,2BACA,oBAIJ,6BACE,eACA,cACA,kBAEA,oCACE,cACA,gBAGF,yBAVF,6BAWI,gBAKJ,2BACE,aACA,qCACA,SACA,mBAEA,0BANF,2BAOI,qCACA,UAGF,yBAXF,2BAYI,0BACA,UAKJ,yBACE,gBACA,mBACA,sBACA,6CACA,eACA,kBACA,WACA,aACA,gBACA,sEACA,sBAEA,8DAEE,2BACA,6CACA,wDACA,YAEA,oGACE,yBAEA,8GACE,cAIJ,sMAEE,WAIJ,kCACE,wDACA,2BACA,4CAEA,qDACE,yBAEA,0DACE,cAIJ,yGAEE,WAKJ,4CACE,kBACA,SACA,UACA,yBACA,mBACA,iBACA,oBACA,mBACA,uBAEA,iDACE,eACA,4CACA,gBACA,cACA,mBAKJ,8CACE,kBACA,SACA,WACA,UAEA,+DACE,WACA,YACA,eAKJ,4CACE,kBACA,SACA,UACA,WACA,eACA,4CACA,gBACA,WACA,SACA,oBACA,qBACA,4BACA,gBACA,uBACA,sBACA,gBACA,mBAIF,2CACE,kBACA,UACA,UACA,WACA,eACA,4CACA,gBACA,cACA,gBACA,SACA,oBACA,qBACA,4BACA,gBACA,uBACA,sBACA,gBACA,mBAIF,4CACE,kBACA,YACA,WACA,WACA,YACA,aACA,mBACA,uBACA,oBAEA,gDACE,WACA,YACA,mBAGF,8CACE,eACA,cAKJ,yBA5JF,yBA6JI,aAEA,4CACE,SACA,UAGF,8CACE,SACA,WAGF,4CACE,SACA,UACA,WACA,eAGF,2CACE,UACA,UACA,WACA,eACA,qBAGF,4CACE,YACA,WACA,WACA,aAMN,gCACE,oBACA,mBACA,uBACA,WACA,YACA,kBACA,yBACA,sBACA,eACA,wBACA,kBACA,sBAEA,uCACE,WACA,UACA,WACA,2BACA,yBACA,wBACA,gBACA,UACA,wBACA,sBAIJ,yDACE,qBACA,yBAEA,gEACE,kBACA,UAMF,sDACE,kBACA,sBAEA,6DACE,qBACA,UAMN,wBACE,aACA,kBACA,cAEA,oCACE,WACA,YACA,mBACA,yBACA,yBACA,kBACA,kCAIJ,sBACE,aACA,kBACA,cAEA,qCACE,eACA,mBAGF,yBACE,eACA,gBACA,cACA,kBAKJ,wBACE,aACA,yBACA,SACA,gBAEA,qCACE,YACA,YACA,mBACA,cACA,yBACA,mBACA,eACA,gBACA,eACA,oBACA,mBACA,uBACA,2BAEA,2CACE,mBAIJ,qCACE,YACA,YACA,mBACA,WACA,YACA,mBACA,eACA,gBACA,eACA,oBACA,mBACA,uBACA,2BAEA,2CACE,mBAIJ,yBA9CF,wBA+CI,sBAEA,0EAEE,YAOG,kBACP,eACA,YACA,WACA,YACA,YACA,4BACA,mBACA,yBACA,YACA,WACA,aACA,mBACA,8BACA,eACA,4CACA,wBACA,aAEA,iCACE,eACA,gBACA,WACA,mBAGF,iCACE,aACA,mBACA,mBACA,uBACA,gBACA,WACA,YACA,kBACA,cACA,kBAEA,gDACE,eACA,gBACA,cACA,cAGF,+CACE,eACA,gBACA,cACA,cACA,eAIJ,wBACE,2BACA,2CAGF,yBA3DO,kBA4DL,aACA,WACA,YACA,YACA,0BAEA,iCACE,eAGF,iCACE,WACA,YACA,kBAEA,gDACE,eAGF,+CACE,gBAQC,UACP,eACA,MACA,OACA,WACA,YACA,aACA,aACA,mBACA,uBAEA,6BACE,kBACA,MACA,OACA,WACA,YACA,gCACA,0BAGF,2BACE,kBACA,gBACA,mBACA,sCACA,WACA,gBACA,YACA,aACA,sBACA,gBACA,UAGF,2BACE,aACA,mBACA,8BACA,kBACA,gCAEA,2CACE,eACA,gBACA,cACA,SAGF,2CACE,gBACA,YACA,eACA,eACA,cAEA,iDACE,cAKN,yBACE,aACA,iBACA,gBAGF,4BACE,aACA,eACA,QAGF,uBACE,oBACA,mBACA,QACA,iBACA,mBACA,mBACA,eACA,cAEA,yCACE,gBACA,mBACA,gBACA,uBAGF,2CACE,gBACA,YACA,eACA,cACA,eACA,UACA,aACA,mBACA,uBAEA,iDACE,cAKN,2BACE,kBACA,6BACA,aACA,yBAGF,8BACE,YACA,eACA,mBACA,cACA,yBACA,kBACA,eACA,gBACA,eAEA,oCACE,mBAQN,iCACE,gBACA,yBACA,mBACA,kDACA,aACA,sBACA,mBACA,kBACA,mBACA,kBAGF,gCACE,mBACA,aACA,mBACA,uBACA,WAGF,6BACE,YACA,YACA,eAGA,4CACE,sDAIJ,qBACE,GACE,wBAGF,KACE,6BAIJ,gCACE,mBAGF,8BACE,eACA,gBACA,cACA,mBACA,4CAGF,6BACE,eACA,gBACA,cACA,4CACA,SAEA,2CACE,cACA,gBAIJ,wBACE,aACA,uBACA,WAGF,6BACE,oBACA,mBACA,uBACA,YACA,YACA,mBACA,WACA,mBACA,eACA,gBACA,qBACA,2BAEA,mCACE,mBACA,WAKJ,wCACE,kBAGF,2BACE,eACA,mBAGF,4BACE,eACA,gBACA,cACA,mBAGF,2BACE,eACA,cAGF,0BACE,oBACA,mBACA,uBACA,YACA,YACA,mBACA,cACA,yBACA,mBACA,eACA,gBACA,qBACA,kBACA,2BAEA,gCACE,mBAIJ,yBACE,oBACA,mBACA,uBACA,YACA,YACA,mBACA,WACA,mBACA,eACA,gBACA,qBACA,2BAEA,+BACE,mBACA,WCl1IN,yBAEE,eAEA,yBAJF,yBAKI,mBAIJ,iBACE,kBACA,cxCkDY,KwChDZ,oBACE,UxCuBY,KwCtBZ,YxC6Be,IwC5Bf,MxCXQ,QwCYR,cxCqCS,IwClCX,qCACE,UxCUa,KwCTb,MxChBQ,QwCqBZ,gBACE,kBACA,cxC+BY,KwCxBZ,4BACE,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,eACA,mBACA,cxChgBiB,KwCigBjB,UxCxiBa,KwCyiBb,cAEA,yBAZF,gCAaI,eACA,UxC9iBS,KwC+iBT,gBtChlBF,yBsCimBF,yBACE,qBACA,eAIF,oBACE,aACA,gBACA,gCAEA,oCACE,eACA,YxC/jBa,IwCgkBb,kBAGF,uCACE,eACA,WAKJ,eACE,8BACA,kCACA,2BACA,aACA,SACA,gBACA,WxCrnBI,KwCunBJ,8BACE,sBACA,uBACA,eACA,cACA,kBAEA,8CACE,WACA,YACA,iBACA,kBAIA,wDACE,WACA,YAKN,iCACE,QAGF,iCACE,iBACA,eACA,mBAEA,iDACE,yBACA,MxCxpBA,KwC4pBJ,8BACE,0BACA,YxC1nBa,IwC6nBf,qCACE,0BACA,WACA,gBAKJ,qBACE,mBACA,kBACA,aACA,WAIF,UACE,sBACA,uBACA,QACA,mBAEA,qBACE,gBAIJ,YACE,0BACA,YxC1pBe,IwC2pBf,cACA,WAGF,YACE,WAIF,gBACE,uBACA,0BACA,mBACA,yBACA,kBACA,eAEA,0BACE,OAMF,kCACE,aACA,mBACA,QACA,mBAKJ,iBACE,sBACA,eACA,uBACA,0BACA,YxCjsBe,IwCksBf,eACA,mBACA,kBAEA,uBACE,iDAKJ,iBACE,sBACA,uBACA,0BACA,YxChtBe,IwCitBf,mBACA,kBACA,QAEA,qBACE,WACA,YAGF,uBACE,4DAKJ,mBACE,WAGE,qDACE,mBACA,QAMN,cACE,aACA,kBAIF,eACE,eAEA,8BACE,0BACA,WAGF,gCACE,eACA,uBACA,eACA,0BACA,mBACA,mBACA,cAMF,+BACE,WACA,eAKJ,qBACE,aACA,8BACA,kCACA,mBACA,SACA,kBACA,aACA,+BAEA,2DAEE,uBACA,uBACA,gBACA,4BACA,0BACA,YxC/xBa,IwCgyBb,6BACA,cAEA,uFACE,oCACA,yBACA,uBAGF,mFACE,oCACA,sBACA,uBAGF,iFAGE,cASR,aACE,WACA,2DAGA,uBACE,eACA,gBACA,WACA,kBACA,gBACA,2DAEA,yBARF,uBASI,eACA,oBAKJ,0BACE,aACA,mBACA,SACA,gBACA,yBACA,mBACA,iDACA,kBACA,mBACA,sBAEA,yBAZF,0BAaI,sBACA,uBACA,SACA,cAIJ,8BACE,WACA,YACA,mBACA,yBACA,mBACA,aACA,uBACA,mBACA,cAEA,oEAEE,WACA,YACA,iBACA,kBAIJ,0BACE,OACA,aACA,sBACA,QACA,YAGF,gCACE,aACA,mBACA,SAGF,0BACE,eACA,gBACA,WACA,SACA,mBACA,gBACA,uBAGF,8BACE,YACA,kBACA,cACA,eACA,gBACA,oBACA,mBACA,uBACA,mBAEA,6FAEE,mBACA,WAGF,6CACE,mBACA,cAGF,8CACE,mBACA,cACA,yBAIJ,0BACE,eACA,cACA,SACA,mBACA,gBACA,uBAIF,gCACE,gBACA,yBACA,mBACA,oDACA,aACA,mBACA,aACA,sBACA,SACA,sBAEA,yBAZF,gCAaI,kBACA,mBACA,UAKJ,qBACE,aACA,SACA,WAEA,yBALF,qBAMI,sBACA,UAIJ,qBACE,OACA,aACA,sBACA,SACA,YAGF,uBACE,eACA,gBACA,WACA,SAGF,6BACE,aACA,QACA,WAGF,2BACE,OACA,YACA,mBACA,yBACA,mBACA,aACA,mBACA,eACA,eACA,gBACA,cACA,sBACA,mBACA,gBACA,uBAEA,uCACE,mBACA,gBAGF,yBAtBF,2BAuBI,eACA,gBAKJ,0BACE,YACA,mBACA,YACA,mBACA,oBACA,mBACA,uBACA,QACA,eACA,eACA,gBACA,cACA,eACA,+BACA,cAEA,gCACE,yCAGF,8BACE,cACA,cAGF,yBA1BF,0BA2BI,eACA,gBAIJ,4BACE,WACA,YACA,mBACA,YACA,mBACA,oBACA,mBACA,uBACA,QACA,eACA,gBACA,WACA,eACA,+BAEA,kCACE,mBAGF,gCACE,WACA,cAKJ,8BACE,WACA,gBACA,yBACA,mBACA,eACA,sBACA,iBACA,gBAGA,iDACE,UAGF,uDACE,yBAGF,uDACE,mBACA,kBAGF,6DACE,mBAIJ,0BACE,aACA,mBACA,8BACA,eACA,gCAEA,qCACE,mBAIJ,0BACE,eACA,gBACA,cACA,SACA,mBACA,gBACA,uBACA,mBACA,OAEA,yBAXF,0BAYI,gBAIJ,kCACE,YACA,mBACA,yBACA,mBACA,eACA,eACA,gBACA,cACA,oBACA,mBACA,uBACA,mBAEA,iDACE,mBACA,yBACA,cAIJ,+BACE,aACA,kBACA,cACA,eACA,SAIF,+BACE,gBACA,kBACA,eACA,cAGF,8BACE,mBACA,kBACA,uBACA,aACA,mBACA,SACA,mBAEA,kCACE,WACA,YACA,cAGF,gCACE,cACA,eACA,gBACA,SAGF,qCACE,mBACA,uCACE,cAMN,yBACE,aACA,uBACA,mBACA,SACA,gBAEA,yBAPF,yBAQI,sBACA,WAEA,iHAGE,YAKN,0BACE,YACA,YACA,mBACA,YACA,mBACA,oBACA,mBACA,uBACA,eACA,gBACA,WACA,eACA,qBACA,+BAEA,gCACE,6DAIJ,yBACE,YACA,YACA,mBACA,YACA,mBACA,oBACA,mBACA,uBACA,eACA,gBACA,WACA,eACA,+BAEA,+BACE,wCAIJ,0BACE,YACA,YACA,mBACA,YACA,mBACA,oBACA,mBACA,uBACA,eACA,gBACA,WACA,eACA,qBACA,+BAEA,gCACE,mBtC3zCF,yBuCGJ,0BAKI,mBvCRA,yBuCaJ,wBAKI,+BACA,gBACA,kBACA,gBACA,iCAIJ,gBACE,eACA,MzCpBU,QyCqBV,SvC7BE,yBuC0BJ,gBAOI,eACA,YzCWiB,KyCNrB,oBAEE,iBvCzCE,yBuCuCJ,oBAMI,yBACA,gBACA,YACA,UACA,iBAKJ,eACE,aACA,mBACA,IzCEW,KyCDX,eACA,gCACA,qBACA,cACA,qCAEA,0BACE,mBAGF,qBACE,oCACA,eACA,kBACA,mBACA,czCJe,IErEf,yBuCsDJ,eAwBI,mBACA,mBACA,eACA,SACA,gBAEA,qBACE,SACA,eACA,gBACA,+BAGF,0BACE,oBAMN,eAEE,WACA,YACA,cvCtGE,yBuCkGJ,eAQI,WACA,aAGF,+BAEE,YAGF,qCAEE,kBAKJ,eACE,aACA,sBACA,SACA,OACA,YvC/HE,yBuC0HJ,eASI,SAIJ,iBACE,aACA,mBACA,IzCpEiB,KEtEf,yBuCuIJ,iBAOI,iBACA,SvC/IA,yBuCoJJ,kBAKI,aACA,mBACA,uBACA,WACA,YACA,eACA,mBACA,eACA,YzClHe,IyCmHf,kBACA,mBACA,eAKJ,eACE,YzC/IoB,0GyCgJpB,eACA,YzC7HiB,IyC8HjB,MzCrKU,QyCsKV,SvC9KE,yBuCyKJ,eASI,eACA,mBACA,gBACA,wBAKJ,sBACE,YzChKoB,0GyCiKpB,eACA,YzCjJoB,IyCkJpB,cACA,SACA,gBvChME,yBuC0LJ,sBAUI,eACA,MzC7LQ,QyC8LR,eAUJ,2BACE,aACA,uBACA,gBvCnNE,yBuCgNJ,2BAOI,iBAKJ,gBAEE,yBACA,MzCpNM,KyCsNN,sBACE,yBvClOA,yBuC4NJ,gBAWI,YACA,YACA,iBACA,eACA,mBAKJ,uBACE,aACA,SACA,iBACA,uBACA,+DAEA,yBAPF,uBAQI,sBACA,SACA,sBACA,gBAIJ,wBACE,OACA,YACA,WAGF,uBACE,aACA,8BACA,mBACA,mBAEA,yBANF,uBAOI,mBAEA,0BACE,2BAIJ,0BACE,eACA,gBACA,WACA,SAIJ,sBACE,yBACA,WACA,kBACA,eACA,YACA,YACA,aACA,uBACA,mBACA,eACA,gBACA,YACA,eACA,qBACA,gCAEA,4BACE,yBAIJ,0BACE,aACA,sBACA,SAGF,gBACE,aACA,mBACA,SACA,aACA,4BACA,gBACA,yBACA,mBACA,gDACA,qBACA,cACA,kDAEA,yBAdF,gBAeI,YACA,aACA,mBACA,mBACA,wBAGF,sBACE,2BACA,8CAIJ,mBACE,WACA,YACA,mBACA,yBACA,mBACA,aACA,uBACA,mBACA,cAEA,yBAXF,mBAYI,WACA,YACA,mBAGF,8CAEE,WACA,YACA,iBACA,kBAEA,yBAPF,8CAQI,WACA,aAKN,eACE,OACA,aACA,sBACA,SACA,YAEA,yBAPF,eAQI,SAIJ,iBACE,aACA,8BACA,mBAGF,gBACE,eACA,gBACA,WACA,SACA,mBACA,gBACA,uBAEA,yBATF,gBAUI,gBAIJ,0BACE,aACA,sBACA,qBACA,QAGF,gBACE,YACA,kBACA,cACA,eACA,gBACA,aACA,mBACA,uBACA,mBAEA,8BACE,mBACA,cAGF,gCACE,mBACA,cAGF,+BACE,mBACA,WAGF,+BACE,mBACA,cAIJ,eACE,eACA,cACA,SACA,mBACA,gBACA,uBACA,OACA,YAEA,yBAVF,eAWI,gBAIJ,qBACE,aACA,8BACA,mBACA,WACA,YAEA,yBAPF,qBAQI,sBACA,uBACA,SAIJ,wBACE,eACA,cACA,iBACA,cACA,mBAEA,yBAPF,wBAQI,cACA,gBAIJ,wBACE,eACA,cACA,iBAGF,sBACE,aACA,sBACA,mBACA,uBACA,eACA,gBACA,yBACA,mBACA,kBAEA,yBACE,eACA,WACA,kBAGF,wBACE,eACA,cACA,SC/fJ,aACE,+BACA,W1CYQ,Q0CXR,kBAEA,yBALF,aAMI,mBAIJ,kBACE,U1CoEoB,O0CnEpB,cAIF,eACE,c1C+CY,K0C7CZ,yBAHF,eAII,c1C2CU,M0CxCZ,2BACE,U1CiBY,K0ChBZ,Y1CwBe,I0CvBf,M1ChBQ,Q0CiBR,c1CiCS,K0ChCT,6BACA,sCACA,qBAEA,yBATF,2BAUI,U1COU,M0CHd,iCACE,U1CFa,K0CGb,M1C5BQ,Q0C6BR,Y1CciB,I0CZjB,yBALF,iCAMI,U1CRS,M0Ccf,qBACE,W1CrCM,K0CsCN,c1CqBiB,K0CpBjB,W1C5BU,+B0C6BV,gBACA,c1CQW,K0CFb,yBACE,cACA,eACA,iBAEA,0BALF,yBAMI,mBAKJ,sBACE,oBACA,gCAEA,0BAJF,sBAKI,qBAIJ,oBACE,eACA,cACA,gBACA,kBAEA,0BANF,oBAOI,eACA,mBAIJ,6BACE,aACA,mBACA,SAEA,0BALF,6BAOI,uBACA,SAIJ,mBACE,oBACA,mBACA,uBACA,YACA,WACA,iBACA,kBACA,eACA,gBACA,sBAEA,6BACE,yBACA,mBACA,cAGF,gCACE,mBACA,cACA,yBAGF,8BACE,mBACA,cACA,yBAGF,2BACE,mBACA,WACA,yBAGF,4BACE,mBACA,cACA,yBAGF,wBACE,mBACA,cACA,yBAGF,8BACE,mBACA,cACA,yBAGF,yBACE,mBACA,cACA,yBAGF,2BACE,mBACA,cACA,yBAIF,sBACE,YACA,WACA,eACA,eACA,eACA,gBAIJ,qBACE,eACA,gBACA,WACA,SAEA,0BANF,qBAOI,gBAKJ,0BACE,gBACA,mBACA,kBACA,mBACA,kBACA,aACA,mBAEA,2CACE,aACA,sBACA,QAGF,2CACE,aACA,mBAIJ,wBACE,aACA,mBACA,QACA,qBACA,WACA,W1CtHgB,a0CwHhB,8BACE,M1ChOW,Q0CmOb,4BACE,cACA,WAGF,kCACE,M1CzOW,Q0C4Ob,0CACE,gBACA,eAGF,6CACE,eACA,WACA,qBAKJ,0DAEE,mBAEA,sFACE,WACA,yBACA,mBACA,gBACA,gCAEA,4FACE,gCAGF,4FACE,mBACA,YACA,gBACA,eACA,kBACA,WAGF,4FACE,kBACA,eACA,WACA,qBAGF,yBA3BF,sFA4BI,cAEA,wGACE,aAGF,sXAIE,cACA,sBACA,sBAGF,4FACE,mBAGF,4FACE,gBACA,mBACA,YACA,kBACA,gCAGF,4FACE,kBACA,iCAOR,uBACE,aACA,sBACA,SACA,uBACA,WAGF,oBACE,aACA,sBACA,uBACA,QACA,WAGF,oBACE,oBACA,mBACA,iBACA,yBACA,yBACA,kBACA,eACA,WACA,gBACA,qBACA,eACA,sBAEA,2BACE,cACA,gBACA,cAIF,0BACE,sBACA,oBACA,qBACA,cAIJ,qBACE,eACA,WACA,gBACA,oBACA,mBACA,QACA,eACA,iBACA,kBACA,yBACA,YACA,wBACA,oBACA,eACA,mBAEA,2BACE,yBAGF,4BACE,eACA,qBACA,8BAGF,mCACE,yBAKJ,uBACE,sBACA,6BACA,eACA,gBACA,WACA,c1ChVY,K0CiVZ,iBAGF,qBACE,eACA,WACA,gBACA,iBAEA,0BANF,qBAOI,kBAIF,uBACE,c1CpWS,K0CsWT,kCACE,gBAIJ,yBACE,eACA,YACA,c1CnWe,I0CoWf,cAGF,uBACE,M1ChbW,Q0CibX,0BAEA,6BACE,M1CnbW,Q0Cubf,gDAEE,cACA,a1C5XS,K0C+XX,wBACE,c1CnYS,I0CwYb,wBACE,oBACA,6BACA,c1CtYY,K0CwYZ,+CACE,gBACA,eACA,gBACA,WAGF,wCACE,WACA,yBACA,mBACA,gCAEA,iDACE,mBACA,YACA,gBACA,eACA,kBACA,WACA,gCAGF,iDACE,gCAGF,2CACE,kBACA,eACA,WACA,mBAGF,qDACE,WACA,mBACA,kCAGF,uDACE,gBACA,qBACA,qBAGF,0BAvCF,wCAwCI,kBAEA,iDACE,aAGF,4FAEE,iBACA,eAGF,qDACE,oBAMN,wCACE,qBACA,iBACA,oBACA,eACA,gBACA,mBACA,mBACA,cAEA,wFACA,qHACwB,iCACxB,0FACA,wFAKJ,uBACE,aACA,uBACA,gBAIF,iBACE,YACA,YAIF,oBACE,W1C9hBM,K0C+hBN,c1CpeiB,K0CqejB,W1CrhBU,+B0CshBV,gBACA,c1CjfW,K0CofX,mCACE,uBACA,gCAEA,yBAJF,mCAKI,wBAGF,iDACE,U1CnhBU,K0CohBV,Y1C3gBa,I0C4gBb,M1CnjBM,Q0CojBN,Y1CzgBc,I0C0gBd,c1ClgBO,K0CogBP,yBAPF,iDAQI,U1C3hBO,M0C+hBX,gDACE,aACA,mBACA,I1C5gBO,K0C6gBP,eACA,U1CxiBS,K0CyiBT,M1CjkBM,Q0CmkBN,yBARF,gDASI,U1C7iBO,K0C8iBP,I1CphBK,M0CuhBP,2DACE,aACA,mBACA,I1C5hBK,I0C8hBL,6DACE,M1C1lBK,Q0C6lBP,kEACE,M1CnlBE,Q0ColBF,Y1C/iBW,I0CsjBnB,wCACE,kBACA,W1CzlBM,Q0C0lBN,gCAEA,yBALF,wCAMI,mBAGF,yDACE,aACA,sBACA,I1CtjBO,I0CwjBP,0EACE,aACA,mBACA,I1C3jBK,I0C6jBL,4EACE,M1C/mBE,Q0CgnBF,qBACA,U1CxlBK,K0CylBL,W1CthBQ,a0CuhBR,aACA,mBACA,I1CrkBG,I0CukBH,yBATF,4EAUI,U1ChmBG,M0CmmBL,kFACE,M1CvoBG,Q0CyoBH,oFACE,2BAIJ,8EACE,M1C/oBG,Q0CgpBH,W1CziBM,a0CijBhB,oCACE,kBACA,iBAEA,yBAJF,oCAKI,mBAGF,kDACE,U1C5nBW,K0C6nBX,M1CvpBM,Q0CwpBN,Y1C3mBc,I0C6mBd,yBALF,kDAMI,U1CloBO,M0CsoBT,oDACE,c1C9mBK,K0CgnBL,+DACE,gBAIJ,8TAME,W1CznBK,K0C0nBL,c1C5nBK,K0C6nBL,Y1CzoBe,I0C0oBf,M1ChrBI,Q0CmrBN,sDACE,eACA,YACA,c1CznBW,I0C0nBX,cAGF,oDACE,M1CtsBO,Q0CusBP,0BAEA,0DACE,M1CzsBO,Q0C6sBX,0GAEE,cACA,a1ClpBK,K0CqpBP,qDACE,c1CzpBK,I0C4pBP,6DACE,8BACA,a1C5pBK,K0C6pBL,cACA,M1ChtBI,Q0CitBJ,kBAGF,uDACE,W1CltBE,Q0CmtBF,gBACA,c1C5pBW,I0C6pBX,Y1CpsBW,oC0CqsBX,eACA,M1CtuBO,Q0CyuBT,sDACE,W1C3tBE,Q0C4tBF,yBACA,c1CpqBW,I0CqqBX,Q1ChrBK,K0CirBL,gBACA,cAEA,2DACE,yBACA,UACA,M1CzuBE,Q0C6uBN,wDACE,WACA,yBACA,cACA,yBACA,c1CrrBW,I0CsrBX,gBAEA,2DACE,W1CjvBC,Q0CkvBD,iBACA,gBACA,Y1CntBa,I0CotBb,gCAGF,2DACE,iBACA,gCAGF,yEACE,mBAQV,gBACE,aACA,uBACA,I1C5tBW,K0C6tBX,c1CztBY,K0C2tBZ,yBANF,gBAOI,sBACA,qBAOJ,mBACE,W1CxxBM,K0CyxBN,c1C9tBiB,K0C+tBjB,W1ChxBU,0B0CixBV,gBAEA,6BACE,aACA,mBACA,8BACA,kBACA,gCACA,W1C1sBc,a0C2sBd,eAEA,yBATF,6BAUI,sBACA,uBACA,kBACA,I1C5vBO,K0C+vBT,wCACE,mBAGF,mCACE,W1CjzBI,Q0CmzBJ,8CACE,M1Cn0BO,Q0Cu0BX,wCACE,aACA,mBACA,I1C9wBO,I0C+wBP,U1CvyBS,K0CwyBT,Y1C5xBe,I0C6xBf,M1Cj0BM,Q0Ck0BN,eAEA,yBATF,wCAUI,U1C9yBO,M0CizBT,0CACE,M1Cr1BO,Q0Cy1BX,wCACE,OACA,U1CtzBW,K0CuzBX,M1Cj1BM,Q0Ck1BN,W1CtvBY,a0CwvBZ,yBANF,wCAOI,U1C5zBO,M0Cg0BX,uCACE,U1Cj0BS,K0Ck0BT,M1Cz1BO,Q0C01BP,gBACA,iBAEA,yBANF,uCAOI,U1Cx0BO,K0Cy0B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3ChhBgB,I2CihBhB,qBAEA,yBAPF,yCAQI,Q3C9gBO,K2C+gBP,U3CziBS,M2C+iBf,wBACE,aACA,uBACA,I3CzhBW,K2C0hBX,c3CthBY,K2CuhBZ,eAEA,yBAPF,wBAQI,mBACA,qBAGF,oCACE,kBACA,c3CzhBe,I2C0hBf,U3C7jBa,K2C8jBb,Y3CnjBiB,I2CojBjB,qBACA,W3C9fc,a2C+fd,oBACA,mBACA,uBACA,I3C7iBS,I2C8iBT,eACA,YAEA,yBAdF,oCAeI,kBACA,U3C3kBS,M2C8kBX,6CACE,W3CrmBE,K2CsmBF,M3CzmBM,Q2C0mBN,yBAEA,mDACE,W3CzmBE,Q2C0mBF,a3CznBO,Q2C6nBX,6CACE,M3ChnBE,K2CknBF,mDACE,2BACA,W3CzmBI,+B2C6mBR,+CACE,W3CnoBU,Q2CooBV,M3C1nBE,K2C4nBF,qDACE,2BACA,W3CnnBI,+B2CunBR,sCACE,eAQN,mBACE,+BACA,W3C5oBQ,Q2C6oBR,kBAEA,yBALF,mBAMI,mBAIJ,wBACE,cAGF,mBACE,W3C1pBM,K2C2pBN,c3ChmBiB,K2CimBjB,W3CjpBU,+B2CkpBV,Q3C1mBY,K2C2mBZ,c3C7mBW,K2C+mBX,yBAPF,mBAQI,mBAGF,+BACE,c3CnnBU,K2CqnBV,0CACE,gBAGF,2CACE,cACA,U3CtpBW,K2CupBX,Y3C3oBiB,I2C4oBjB,M3ClrBM,Q2CmrBN,c3CjoBO,K2CmoBP,yBAPF,2CAQI,U3C7pBO,M2CgqBT,2DACE,YACA,M3ClsBQ,Q2CmsBR,Y3C5oBK,I2CgpBT,2CACE,WACA,Q3ChpBO,K2CipBP,yBACA,c3CvoBa,I2CwoBb,U3C3qBW,K2C4qBX,W3C1mBY,a2C4mBZ,yBARF,2CASI,U3ChrBO,K2CirBP,kBAGF,iDACE,aACA,a3C1tBO,Q2C2tBP,uCAGF,wDACE,M3CltBK,Q2CstBT,8CACE,WACA,iBACA,Q3CzqBO,K2C0qBP,yBACA,c3ChqBa,I2CiqBb,U3CpsBW,K2CqsBX,Y3C5sBgB,0G2C6sBhB,gBACA,W3CroBY,a2CuoBZ,yBAXF,8CAYI,U3C3sBO,K2C4sBP,iBACA,kBAGF,oDACE,aACA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CmjBX,oBACA,W5ChjBY,K4CijBZ,Y5CljBY,K4CojBZ,yBACE,YACA,kBACA,uB1CrnBA,yB0C2mBJ,oBAeI,mBACA,SACA,W5C/jBU,K4CgkBV,Y5CjkBS,K4CkkBT,gBAEA,yBACE,OACA,WACA,gBACA,YACA,iBACA,eACA,gBACA,kBAIF,8EAEE,mBACA,cACA,YAEA,0FACE,mBACA,cAKJ,iCACE,mBACA,WACA,YAEA,uCACE,mBACA,YAOR,wBACE,W5C/mBW,I4CgnBX,iBACA,c5CtmBiB,I4CumBjB,U5C1oBa,K4C2oBb,aAEA,gCACE,gCACA,M5C9qBW,Q4C+qBX,cAGF,8BACE,gCACA,M5CrrBY,Q4CsrBZ,cAKJ,qBACE,eACA,MACA,OACA,WACA,YACA,6BACA,aACA,mBACA,uBACA,aAEA,4BACE,aAGF,8BACE,WACA,YACA,oCACA,iB5CrsBI,K4CssBJ,kBACA,kCAIJ,gBACE,GACE,0B1CxtBA,yB0C8tBF,gBACE,WAGF,oBACE,WAEA,yEAEE,OACA,YACA,WACA,U5CzsBS,K4C0sBT,iBAIJ,sBACE,WAEA,sCACE,YAMN,gBACE,W5C/uBM,K4CgvBN,sBACA,mBACA,kBACA,aACA,sBACA,SACA,mBACA,Y5CvuBoB,0G4C2uBtB,uBACE,aACA,mBACA,eAIF,mBACE,WACA,gBACA,WAIF,sBACE,aACA,sBACA,SAIF,oBACE,aACA,mBACA,8BACA,QACA,YAIF,0BACE,aACA,mBACA,QACA,eACA,OACA,iBAIF,0BACE,kBACA,UACA,oBAEA,6DACE,kDAGF,2DACE,0CAKJ,2BACE,kBACA,WACA,YACA,eACA,YACA,kBACA,oDACA,wBACA,4BACA,2BACA,W5CpuBgB,a4CquBhB,UAIF,yBACE,eACA,gBACA,cACA,oBACA,aACA,mBACA,QAEA,4CACE,eACA,gBACA,cAKJ,oBACE,cACA,gBAIF,iBACE,cAIF,uBACE,aACA,mBACA,uBACA,WACA,YACA,eACA,YACA,yBACA,M5Cz2BU,Q4C02BV,eACA,W5ChxBgB,a4CixBhB,yBAEA,yBACE,eACA,8BAGF,6BACE,M5Ch4BW,Q4Cm4Bb,8BACE,M5Cp4BW,Q4Cs4BX,gCACE,yBAMN,mBACE,W5Cj1BW,K4Ck1BX,mBACA,mBACA,aAGF,kBACE,iBACA,gBAGA,qCACE,UAGF,2CACE,W5C74BU,Q4C84BV,c5Cx1Be,I4C21BjB,2CACE,W5Ct5BS,Q4Cu5BT,c5C71Be,I4C+1Bf,iDACE,W5C35BM,Q4Cg6BV,sBACE,U5Cz4BW,K4C04BX,gBACA,M5Cn6BQ,Q4Cq6BR,sJAME,M5C56BM,Q4C66BN,W5C13BO,K4C23BP,c5C53BO,K4C63BP,Y5Cz4BiB,I4C44BnB,wBACE,c5Cj4BO,K4Co4BT,kDAEE,Y5Cr4BO,K4Cs4BP,c5Cv4BO,K4C04BT,4BACE,WACA,yBACA,c5C74BO,K4C+4BP,8DAEE,yBACA,Q5Cn5BK,I4Co5BL,gBAGF,+BACE,W5Cp8BG,Q4Cq8BH,Y5Cp6Be,IE9CnB,yB0C09BF,gBACE,kBACA,SAGF,uBACE,cAGF,sBACE,SAGF,oBACE,YACA,gBAGF,yBACE,U5C58BW,K4C88BX,4CACE,U5C98BW,K4Ck9Bf,kBACE,iBACA,Q5C57BS,M4Ci8Bb,uBACE,gBACA,sCACA,mBACA,kBACA,uCACA,gBACA,mBACA,W1CngCE,yB0C2/BJ,uBAWI,kBACA,mBACA,gBACA,oBC3gCJ,aACE,c7CgEY,K6C/DZ,2CAFF,aAKI,c7C2DU,M6CtDd,aACE,mBAEA,gCACE,eACA,gBACA,cACA,kBAGF,+BACE,gBACA,UACA,SACA,aACA,sBACA,SAGF,+BACE,yBACA,kBACA,gBACA,gBACA,iCAEA,sCACE,qBACA,2CAEA,yDACE,yBAKN,+BACE,aACA,mBACA,WACA,iBACA,gBACA,YACA,gBACA,eACA,SAEA,qCACE,aAIJ,+BACE,eACA,cACA,cACA,WAGF,kCACE,eACA,gBACA,cACA,OACA,mBACA,gBACA,uBAGF,gCACE,cACA,8BACA,cAGF,+BACE,aACA,aACA,mBACA,6BACA,eACA,gBACA,cACA,sBAIJ,YACE,U7CzDc,K6C0Dd,Y7ClDiB,I6CmDjB,M7C1FU,Q6C2FV,c7CzCW,K6C0CX,aACA,mBACA,uBACA,I7C7CW,KE1DT,yB2C+FJ,YAWI,U7CpEY,K6CqEZ,sBACA,I7CnDS,K6CsDX,cACE,M7CnHW,Q6CoHX,U7C1EY,KEvCZ,yB2C+GF,cAKI,U7C9EU,M6CmFhB,kBACE,U7CtFa,K6CuFb,M7ClHU,Q6CmHV,Y7CxEmB,IEpDjB,yB2CyHJ,kBAMI,U7C7Fa,M6CgGf,6BACE,M7CtIW,Q6CuIX,Y7CtFmB,I6CuFnB,kBAEA,oCACE,WACA,kBACA,YACA,OACA,QACA,WACA,kBAMN,4BACE,gBACA,c3CtJE,yB2CoJJ,4BAKI,gBAKJ,kBACE,Q7ChGY,K6CiGZ,W7CrJM,K6CsJN,c7C1FiB,K6C2FjB,W7C3IU,gC6C4IV,yB3CnKE,yB2C8JJ,kBAQI,kBACA,c7ClGe,K6CsGjB,8BACE,c7C/GU,KE7DV,yB2C2KF,8BAII,c7CnHO,M6CsHT,oCACE,aACA,mBACA,I7C5HO,I6C6HP,U7CpJW,K6CqJX,Y7CzIiB,I6C0IjB,M7ChLM,Q6CiLN,c7C/HO,K6CiIP,sCACE,M7C/LO,Q6CgMP,U7C1JO,K6C6JT,8CACE,M7ChMQ,Q6CiMR,gBAKJ,oDACE,iBACA,gBACA,gB3C1MF,yB2CuMA,oDAMI,kBAMN,6BACE,aACA,uBACA,I7C9JS,I6C+JT,W7C9JS,I6C+JT,U7CvLW,K6CwLX,M7ChNQ,Q6CiNR,Y7CtKiB,I6CwKjB,+BACE,M7ChOS,Q6CiOT,eACA,cAKJ,8BACE,aACA,uBACA,I7C/KS,I6CgLT,W7C/KS,I6CgLT,U7CxMW,K6CyMX,M7CzOY,Q6C0OZ,Y7CvLiB,I6CyLjB,gCACE,eACA,cAKJ,kCACE,W7C3LS,K6C6LT,oCACE,aACA,uBACA,I7ClMO,I6CmMP,aACA,U7C5NS,K6C6NT,M7CnPO,Q6CoPP,Y7C1Me,I6C4Mf,sCACE,M7C/PO,Q6CgQP,eACA,cAaF,qIACE,M7CzQI,Q6C0QJ,8BACA,a7CtRO,Q6CuRP,Y7CvOa,I6C2OjB,+GACE,SACA,oBACA,mBACA,uBACA,eACA,gBACA,YACA,YACA,mBACA,eACA,sBACA,cACA,yBACA,wBAEA,2HACE,yBAGF,6HACE,0BAIJ,yBACE,+GACE,QA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A,I9C7YW,I8C8YX,iBAGF,gBACE,aAOF,yBAGE,iBACE,wBAGF,gBACE,wBAGF,2BACE,aACA,sBACA,I9CpaS,K8CqaT,kBACA,gBAGA,2CACE,iBAEA,wDACE,mBACA,wBACA,8BACA,sBACA,2BACA,0BACA,sBACA,8BACA,6CACA,2BAEA,+DACE,yBACA,2BACA,0BAKJ,uDACE,mBACA,sBACA,2BAEA,4DACE,wBACA,8BACA,kCACA,sBACA,uBACA,wBAMN,mDACE,mBACA,yBACA,gBACA,iBAEA,uEACE,wBAGF,oEACE,wBACA,iCACA,kCACA,uBACA,6BACA,oBACA,2CACA,W9CnhBA,K8CohBA,mBAEA,8EACE,wBACA,sCACA,sBACA,0BACA,yBACA,qBACA,8BAEA,sFACE,oCACA,gCACA,2BACA,yBACA,6BACA,0BACA,yBAIF,6FACE,wBAIF,6FACE,0BACA,2BACA,yBACA,kBAEA,qGACE,wBAGF,+FACE,yBACA,2BACA,qBAEA,qGACE,yBAMN,0FACE,aACA,yBACA,gBACA,oCAEA,sGACE,eACA,4BACA,8BAGF,kGACE,wBAGF,2GACE,wBAGF,0GACE,wBAQV,6CACE,kBAEA,yDACE,eAGF,gDACE,U9CjlBO,K8ColBT,+CACE,U9CxlBO,K8C6lBX,uCACE,mBACA,SACA,eAEA,uDACE,WACA,YAEA,oHAEE,eACA,WACA,YAIJ,2DACE,SAGF,0DACE,eACA,YACA,eACA,cACA,Y9C5mBc,I8C8mBd,iEACE,Y9C5mBS,I8C6mBT,cAgBA,iFACE,aAIF,iFACE,sBACA,uBAIF,iFACE,sBACA,0BACA,qBACA,uBAIF,iFACE,sBACA,0BACA,qBACA,uBAIF,iFACE,aASF,0JAEE,aAIF,yJAEE,sBACA,uBACA,gBACA,2BAEA,+LACE,aACA,mBACA,QACA,gBAGA,6NACE,wBAGF,iRACE,gBACA,uBACA,mBAGF,qNACE,cACA,WACA,YAEA,6NACE,WACA,YAOR,2EACE,sBACA,0BACA,qBACA,uBACA,kBACA,gBACA,uBACA,mBAIF,2EACE,sBACA,0BACA,qBACA,uBACA,kBAEA,i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OQ,Q+C+OR,gBACA,qBAEA,yBARF,6BASI,Y/CjMO,K+CkMP,e/ClMO,K+CmMP,U/C5NS,M+CgOX,+BACE,c/CxMO,K+C0MP,0CACE,gBAKJ,4EACE,eACA,Y/C7Na,I+C8Nb,M/CrQM,Q+CsQN,W/ClNO,K+CmNP,c/CrNO,K+CsNP,gBAEA,oGACE,aAGF,yBAZF,4EAaI,gBAKJ,4EACE,eACA,Y/C/Oa,I+CgPb,M/CvRM,Q+CwRN,W/CrOO,K+CsOP,c/CxOO,I+CyOP,gBAEA,yBARF,4EASI,gBAIJ,gCACE,U/CtQS,K+CuQT,Y/C7PiB,I+C8PjB,M/CpSM,Q+CqSN,W/CnPO,K+CoPP,c/CrPO,I+CuPP,yBAPF,gCAQI,U/C/QS,M+CmRb,oCACE,Y/CvQa,I+CwQb,M/C/SM,Q+CkTR,gCACE,kBACA,M/CnTM,Q+CsTR,+BACE,M/CnUS,Q+CoUT,0BACA,W/C9NY,a+CgOZ,qCACE,M/CvUS,Q+C2Ub,gEACE,cACA,eACA,gBAEA,yBALF,gEAMI,gBAIJ,gCACE,c/C5RO,I+C6RP,gBACA,eAEA,2CACE,gBAIJ,wCACE,8BACA,a/CpSO,K+CqSP,cACA,M/CxVM,Q+CyVN,kBACA,W/CvVI,Q+CwVJ,kBACA,0BAEA,yBAVF,wCAWI,kBAIJ,kCACE,W/CjWI,Q+CkWJ,gBACA,c/C3Sa,I+C4Sb,Y/CnVa,oC+CoVb,eACA,M/CrXS,Q+CwXX,iCACE,W/C1WI,Q+C2WJ,yBACA,c/CnTa,I+CoTb,Q/C/TO,K+CgUP,gBACA,cAEA,yBARF,iCASI,Q/CrUK,K+CwUP,sCACE,yBACA,UACA,M/C5XI,Q+CgYR,mCACE,WACA,yBACA,cACA,yBACA,c/CxUa,I+CyUb,gBACA,U/C9WS,K+CgXT,yBATF,mCAUI,U/ClXO,M+CqXT,sCACE,W/CzYG,Q+C0YH,iBACA,gBACA,Y/C3We,I+C4Wf,M/ClZI,Q+CmZJ,gCAEA,yBARF,sCASI,iBAIJ,sCACE,iBACA,gCACA,M/C7ZI,Q+C+ZJ,yBALF,sCAMI,iBAKF,oDACE,mBAGF,4CACE,W/CtaA,Q+C2aN,gCACE,YACA,6BACA,cAIF,sCACE,W/ClYQ,K+CmYR,Y/CrYO,K+CsYP,6BAEA,kDACE,aACA,cACA,gBAIJ,6CACE,qBACA,eACA,M/ChdS,Q+CidT,Y/C/Za,I+CsajB,iDACE,UAGF,uDACE,W/C9cM,Q+C+cN,c/CvZe,I+C0ZjB,uDACE,W/CleW,Q+CmeX,c/C5Ze,I+C8Zf,6DACE,W/CreW,QgDkBjB,eACE,iBACA,cACA,aACA,sBACA,WACA,aACA,sBACA,SAEA,sBACE,SACA,eACA,gBACA,uBAGF,qBACE,SACA,eACA,gBACA,gBACA,uBAGF,qBACE,SACA,eACA,gBACA,gBAGF,uBACE,YAGF,8BACE,SACA,eACA,gBAGF,6BACE,eACA,gBACA,gBAEA,+BACE,SAEA,iCACE,eAKN,qBACE,SACA,kBACA,gBACA,eACA,gBACA,gBAEA,2BACE,eAKN,yBACE,eACE,aACA,SAEA,sBACE,eAGF,qBACE,eAGF,8BACE,eAGF,uEAGE,gBAUN,cACE,eACA,aACA,SACA,uBAGF,iBACE,OACA,WACA,aACA,sBACA,SACA,YAEA,uBACE,MAMJ,iBACE,YACA,cAKF,aACE,aACA,sBACA,SACA,gBACA,yBACA,mBACA,kBAGA,mBACE,0CACA,kDAGF,uCACE,aACA,sBACA,mBACA,SAEA,+CACE,WACA,YACA,yBACA,mBACA,aACA,mBACA,uBAEA,mDACE,WACA,YACA,UAIJ,kDACE,eACA,gBACA,WACA,kBACA,SAIJ,uCACE,WACA,yBACA,aACA,WAGF,mBACE,eACA,gBACA,cACA,qBACA,qBAEA,yBACE,cAGF,2BACE,cACA,gBAKN,0BACE,cACE,sBACA,kBAGF,iBACE,WACA,gBAGF,aACE,8BACA,4BACA,sCACA,6BACA,2BACA,4CACA,oBACA,oCACA,8BACA,2BACA,2BAEA,gCACE,aAdJ,aAiBE,wBAEA,qBAGA,8EAEE,wBAGF,gCACE,yBACA,8BACA,oBACA,qBAKJ,oCAEE,aAOJ,cACE,uDACA,yBACA,eACA,aACA,uBAGA,yBACA,kBACA,SACA,2BACA,gBAEA,qBACE,iBACA,WACA,aACA,mBACA,UACA,eAGF,4BACE,YACA,aACA,kBACA,aACA,mBACA,uBACA,eACA,cAGF,uBACE,aACA,sBACA,uBACA,SAGF,qBACE,oBACA,mBACA,QACA,iBACA,gBACA,yBACA,mBAEA,yBACE,UACA,WACA,gCACA,kBAGF,0BACE,eACA,gBACA,cAIJ,qBACE,eACA,gBACA,WACA,S9CpVA,yB8CgVF,qBAOI,gBAIJ,oBACE,eACA,gBACA,cACA,gBACA,SAKJ,gBACE,WACA,YACA,mB9CxWE,yB8CqWJ,gBAMI,UACA,YAQJ,iBACE,sBACA,qBAEA,kCACE,mBACA,qCACA,8BACA,mBACA,kBACA,aACA,mBACA,SACA,0DAEA,wCACE,WACA,YACA,mBACA,oCACA,mBACA,aACA,mBACA,uBACA,cAEA,4CACE,WACA,YACA,+BACA,mBAIJ,qCACE,SACA,eACA,gBAEA,wCACE,kBACA,kBACA,cACA,eACA,gBACA,gBACA,oBAEA,+CACE,WACA,gBAGF,gDACE,WACA,kBACA,SACA,SACA,UACA,WACA,mBACA,kBAGF,2CACE,eAQV,aACE,aACA,2BACA,gCACA,gBACA,mBACA,WAEA,6BACE,OACA,cACA,kBACA,eACA,gBACA,cACA,gBACA,YACA,sCACA,eACA,kBACA,wBACA,aAEA,mCACE,cAGF,oCACE,cACA,gBACA,gCACA,gBAKN,2BACE,mBACA,iBAIF,iBACE,aACA,sBACA,SACA,kBACA,kBAEA,wBACE,aAGF,yBACE,WACA,kBACA,UACA,SACA,YACA,UACA,mBACA,UAIJ,aACE,aACA,mBACA,SACA,kBACA,UAGA,qBACE,WACA,kBACA,WACA,QACA,2BACA,WACA,YACA,mBACA,kBACA,2EACA,UAGF,uBACE,WACA,gBACA,gBACA,yBACA,mBACA,oBACA,mBACA,uBACA,cACA,0CAEA,2BACE,WACA,YAIJ,mBACE,OACA,gBACA,yBACA,mBACA,kBACA,0CACA,aACA,sBACA,uBACA,gBAEA,0BACE,aACA,mBACA,kBAGF,uBACE,eACA,gBACA,cAGF,uBACE,UACA,WACA,mBACA,kBACA,aAGF,yBACE,eACA,gBACA,WACA,sHAGF,wBACE,SACA,eACA,cACA,gBAIJ,kBACE,cAGF,kBACE,cAGF,oBACE,cAGF,mBACE,cAGF,mBACE,cAGF,kBACE,cAIJ,eACE,aACA,uBACA,gBAIF,kBACE,gBAEA,yBACE,eACA,gBACA,cACA,gBAGF,+BACE,aACA,sBACA,mBACA,uBACA,SACA,kBACA,mBACA,0BACA,mBACA,kBAGF,yBACE,qBACA,iBACA,eACA,gBACA,qBACA,cACA,mBACA,mBAGF,wBACE,SACA,eACA,cAKJ,4BACE,gBACA,mBAEA,mCACE,eACA,gBACA,cACA,gBACA,kBACA,kBACA,mBAGA,2CACE,WACA,kBACA,OACA,QACA,WACA,UACA,mBACA,kBAIJ,wDACE,WACA,gBACA,yBACA,mBACA,gBACA,sCAEA,2EACE,WAGF,iFACE,mBACA,kBAIJ,gDACE,WACA,yBACA,YhDnrBkB,0GgDorBlB,kBAEA,sGAEE,kBACA,eACA,gCACA,+BAEA,4HACE,kBAIJ,sDACE,mBAEA,yDACE,gBACA,cACA,eAEA,qEACE,UACA,eACA,cAGF,kEACE,UAGF,wEACE,mBACA,cAGF,sEACE,YACA,mBAMJ,yDACE,+BAEA,+DACE,mBAGF,uEACE,mBAIJ,oEACE,gBACA,cACA,gBACA,kBAIF,oEACE,oBACA,mBACA,uBACA,WACA,YACA,kBACA,eACA,gBAEA,6EACE,mBACA,cAGF,4EACE,mBACA,cAQV,0BACE,cACE,eAGF,qBACE,sBACA,SACA,kBAGF,uBACE,mBAIA,kCACE,sBACA,aACA,SACA,kBAEA,wCACE,iBAMR,yBAEI,kCACE,eAEA,0CACE,aAIJ,8BACE,SAEA,sCACE,aAGF,wCACE,WACA,YACA,gBAEA,4CACE,WACA,YAIJ,oCACE,YACA,gBACA,kBAEA,0CACE,gBAWV,sBACE,uBACA,kBACA,6DAIA,sBACE,eACA,gBACA,iBACA,uBACA,cACA,mBAGF,uBACE,eACA,gBACA,iBACA,cAIJ,qBACE,kBACA,kBACA,gBAEA,yBACE,eACA,YACA,kBACA,qCAIJ,oBACE,kBACA,kBAEA,+BACE,eACA,iBACA,cACA,mBAEA,sCACE,cACA,gBAIJ,iCACE,eACA,cACA,gBAIJ,sBACE,aAEA,4BACE,WACA,yBACA,yBACA,kBACA,gBAEA,8DAEE,aACA,eACA,iBACA,gCAGF,+BACE,mBACA,cACA,gBACA,kBACA,sBAGF,+BACE,cACA,gBAGF,0FAEE,mBAKN,sBACE,aACA,kBAEA,mCACE,eACA,cACA,gBACA,iBAIJ,sBACE,uBACA,kBAEA,oCACE,qBACA,kBACA,mBACA,WACA,eACA,gBACA,kBACA,qBACA,+BAEA,0CACE,mBASN,yBACE,sBACE,uBAGE,4CACE,eACA,iBAGF,6CACE,eACA,iBAKN,qBACE,kBAEA,yBACE,gBAIJ,oBACE,aAEA,+BACE,eACA,iBAGF,iCACE,eAIJ,sBACE,aACA,gBACA,cAIE,8DAEE,kBACA,eAKN,sBACE,kBAEA,mCACE,eAIJ,sBACE,uBAEA,oCACE,kBACA,gBAgCN,YACE,iBACA,cACA,kBACA,aACA,SACA,uBAIF,gBASE,sBACA,MArCgB,QAuChB,qEAGE,mBAGF,+HAKE,mBAKF,yBACE,qBACA,eACA,gBACA,MA/DY,QAgEZ,qBACA,kBAGF,oBACE,gBACA,eACA,gBACA,gBACA,MArEc,QAwEhB,sBACE,gBACA,eACA,gBACA,MA3Ec,QA+EhB,sBACE,uDACA,yBACA,eACA,aACA,uBACA,yBACA,kBACA,SACA,2BACA,gBAGF,4BACE,iBACA,WACA,aACA,mBACA,UACA,eAGF,2BACE,aACA,sBACA,uBACA,SAGF,8BACE,oBACA,mBACA,QACA,iBACA,gBACA,yBACA,mBACA,eACA,gBACA,cAGF,kCACE,UACA,WACA,kBACA,gCAGF,4BACE,eACA,gBACA,WACA,SAGF,2BACE,eACA,gBACA,cACA,gBACA,SAEA,sDACE,e9C3tCF,yB8CmtCF,2BAaI,gBAIJ,4BACE,aACA,SACA,eAGF,sBACE,oBACA,mBACA,iBACA,mBACA,gBACA,yBACA,eACA,gBACA,MAxKc,QA0Kd,+BACE,WA/KU,QAgLV,aAhLU,QAiLV,WAIJ,6BACE,YACA,aACA,kBACA,uFACA,aACA,mBACA,uBACA,cAEA,iCACE,cACA,WACA,YAKJ,6BACE,aACA,0BACA,SAGF,6BACE,aACA,+BACA,SACA,kBACA,aACA,gBACA,yBACA,mBACA,oBACA,yBAGF,4BACE,oBACA,mBACA,uBACA,WACA,YACA,kBACA,WAjOY,QAkOZ,WACA,eACA,gBAGF,8BACE,iBACA,eACA,gBACA,MAvOc,QAwOd,sHAGF,+BACE,SACA,eACA,gBACA,MA9Oc,QA+Od,gBAEA,iCACE,eAKJ,kCACE,aACA,gBACA,yBACA,mBACA,gBACA,iCAEA,sCACE,cACA,WACA,gBACA,YAKJ,2BACE,aACA,sBACA,SAIA,sDACE,gBAIJ,8BACE,aACA,0BACA,QACA,kBACA,mBACA,gBACA,yBACA,mBAEA,2HAEE,mBAIJ,wBACE,qBACA,iBACA,kBACA,WA7SY,QA8SZ,WACA,eACA,gBACA,oBAGF,+BACE,kCACA,eACA,MAnTc,QAoTd,qBAGF,uCACE,qBACA,iBACA,mBACA,WA5Ta,QA6Tb,yBACA,MA5Tc,QA6Td,eACA,gBAIF,uBACE,kBACA,gBACA,yBACA,mBACA,gBACA,iCACA,YAGF,6BACE,gBACA,eACA,gBACA,MAjVc,QAoVhB,gCACE,mBACA,eACA,gBACA,MAxVc,QA2VhB,uBACE,WACA,gBACA,yBACA,eAEA,6BACE,WAnWW,QAsWb,0BACE,gBACA,iBACA,eACA,gBACA,MAzWY,QA0WZ,oBAGF,0BACE,iBACA,gCACA,MAhXY,QAiXZ,sBAGF,8CACE,mBAGF,4BACE,kCACA,eACA,gBACA,MA7XY,QAiYZ,8CACE,WAGF,+CACE,YAGF,+CACE,YAGF,kCACE,MA7YU,QAkZhB,4BACE,yBAGF,2BACE,qBACA,eACA,iBACA,kBACA,kBACA,eACA,gBACA,WAEA,qCACE,WAtaU,QAyaZ,qCACE,WApaY,QAwahB,yBACE,gBACA,iBACA,kBACA,WAraY,QAsaZ,MAraY,QAsaZ,eACA,gBAIF,4BACE,kBACA,kBACA,mBACA,WAzbc,QA0bd,gBACA,YACA,gBAEA,oCACE,WACA,kBACA,iBACA,UACA,WAvcU,QA0cZ,2CACE,WA/bM,QAmcV,0BACE,qBACA,iBACA,mBACA,WAndY,QAodZ,WACA,eACA,gBACA,mBAEA,8BACE,WA1dU,QA6dZ,iCACE,WAldM,QAsdV,4BACE,SACA,WACA,kCACA,eACA,gBACA,gBACA,gBACA,cAIF,yBACE,MAveS,QA0eX,yBACE,MA1eW,QA6eb,yBACE,MA7eU,QAgfZ,yBACE,MAhfW,QAmfb,0BACE,MAzfc,QA6fhB,4BACE,kBACA,kBACA,WAngBa,QAogBb,kBACA,aACA,eACA,aACA,mBAGF,4BACE,cACA,kCACA,eACA,gBACA,MAnhBY,QAohBZ,kBAGF,8BACE,cACA,kCACA,eACA,MAvhBc,QA0hBhB,sBACE,gBACA,SACA,UAEA,yBACE,kBACA,kBACA,eACA,gBACA,MAniBY,QAoiBZ,gBAEA,iCACE,WACA,kBACA,SACA,QACA,UACA,WACA,kBACA,WAnjBQ,QAsjBV,4BACE,eAMN,8BACE,qBACA,eACA,iBACA,mBACA,kBACA,eACA,gBAEA,mCACE,WAxjBS,QAyjBT,MA5jBM,QA+jBR,mCACE,WA/jBU,QAgkBV,MA/jBU,QAokBd,4BACE,gBACA,iBACA,kBACA,WAvkBW,QAwkBX,MAvkBW,QAwkBX,eACA,gBAIF,qBACE,kBACA,cACA,kBACA,mBACA,4DACA,WACA,qBACA,gBACA,kDAEA,2BACE,2BACA,0CAIJ,0BACE,kBACA,UAGF,6BACE,qBACA,eACA,gBACA,MAtnBc,QAunBd,qBACA,mBAGF,2BACE,gBACA,eACA,gBACA,WACA,gBAGF,0BACE,gBACA,eACA,gBACA,MAvoBc,QA0oBhB,4BACE,oBACA,mBACA,kBACA,mBACA,gBACA,MAjpBY,QAkpBZ,eACA,gBAGF,0BACE,kBACA,kBACA,+BAEA,8BACE,YACA,aACA,WACA,YAGF,8BACE,WACA,YACA,WACA,aACA,8BAKN,0BAEI,4BACE,sBACA,SACA,kBAGF,2BACE,mBAGF,6BACE,0BAGF,2BACE,0BAGF,8BACE,0BACA,QAEA,2HAEE,mBAMF,4IAGE,YAMR,yBACE,gBACE,SAEA,sBACE,eAGF,4BACE,eAGF,oBACE,eAGF,qBACE,kBAGF,2BACE,eAGF,uBACE,kBAGF,4BACE,mBC5zDN,0BACE,cAIF,mBACE,aACA,eACA,mBACA,QACA,mBACA,kBACA,mBACA,yBACA,mBACA,eACA,cAGF,2BACE,gBACA,cACA,eAGF,0BACE,cACA,gBAIF,2BACE,aACA,qBACA,8BACA,SACA,mBACA,gBAIF,kBACE,oBACA,mBACA,mBACA,YACA,YAGF,UACE,YACA,yBACA,iBACA,mBACA,eACA,WACA,eACA,mBAEA,iBACE,mBACA,WACA,gBAKJ,oBACE,aACA,mBACA,QAGF,aACE,YACA,iBACA,eACA,WACA,yBACA,aACA,eAGF,mBACE,aACA,mBACA,SACA,gBACA,iBACA,mBACA,yBAGF,mBACE,aACA,mBACA,QAGF,YACE,YACA,iBACA,eACA,WACA,yBACA,aAGF,gBACE,WAGF,YACE,mBACA,YACA,kBACA,WACA,YACA,aACA,mBACA,uBACA,eACA,0BAEA,kBACE,mBAGF,gBACE,YAKJ,yBACE,mBACA,mBACA,aACA,mBAGF,iBACE,aACA,mBACA,8BACA,SAGF,cACE,OAGF,oBACE,mBAGF,YACE,4BACA,mBACA,yBACA,gBACA,cACA,eACA,gBACA,eACA,gBACA,0LACA,4BACA,sCACA,gBACA,aACA,mBAEA,kBACE,yBAGF,kBACE,qBACA,uCAIJ,cACE,kBAGF,oBACE,eACA,WACA,mBAEA,2BACE,eACA,cAIJ,qBACE,aACA,sBACA,SAGF,qBACE,aACA,mBACA,QACA,eAEA,0CACE,cAGF,0CACE,cAGF,0CACE,cAIJ,cACE,WACA,eAGF,cACE,gBACA,WAGF,gBACE,WAIF,gBACE,eACA,aACA,mBACA,uBAGF,YACE,gBACA,iBAGF,eACE,gEAIF,eACE,eACA,aACA,sBACA,SAGF,cACE,aACA,mBACA,QACA,eACA,gCAEA,yBACE,mBAIJ,gBACE,WACA,YACA,kBAEA,wBACE,mBAGF,wBACE,mBAGF,wBACE,mBAIJ,YACE,eACA,WAGF,YACE,eACA,gBAGF,0BACE,cAGF,0BACE,cAGF,0BACE,cAGF,cACE,eACA,WAIF,uBACE,gBACA,mBACA,gBACA,qCACA,mBAEA,kCACE,gBAIJ,cACE,aACA,8BACA,mBACA,kBACA,mBACA,WAGF,aACE,aACA,mBACA,SACA,eACA,gBACA,SACA,mBAEA,iBACE,eAIJ,cACE,aACA,mBACA,QACA,iBACA,8BACA,mBACA,WACA,qBACA,eACA,0BAEA,oBACE,8BACA,WAIJ,iBACE,UAGF,aACE,WACA,6BACA,gBAEA,iCACE,aACA,mBACA,mBACA,YACA,eACA,gCAEA,yBARF,iCASI,cAGF,8CACE,eACA,4CACA,gBACA,WACA,kBAIJ,+BACE,aACA,sBAGF,8BACE,aACA,mBACA,YACA,eACA,gCACA,gCACA,gBAEA,oCACE,yBAGF,yBAbF,8BAcI,sBACA,uBACA,YACA,aACA,UAGF,wCACE,aACA,mBACA,uBACA,eACA,4CACA,gBACA,WACA,mBAEA,yBAVF,wCAWI,2BACA,sBAEA,gDACE,yBACA,gBACA,cACA,kBACA,gBAKN,wCACE,mBACA,gBAEA,kDACE,cAMR,UACE,aACA,mBACA,SAGF,UACE,aACA,mBACA,uBACA,WACA,YACA,mBACA,kBAEA,cACE,YAGF,gBACE,mBAEA,oBACE,eAKN,eACE,qBACA,sBACA,gBACA,gBACA,uBACA,mBAEA,yBARF,eASI,iBAKJ,UACE,OACA,sCACA,kBACA,YAGF,8BACE,kCACA,eAGF,SACE,YACA,kCAGF,WACE,YACA,kCAGF,gBACE,YACA,WACA,mBACA,kBACA,aACA,gBAGF,aACE,aACA,mBACA,uBACA,aACA,WACA,cACA,gBACA,eAGF,eACE,gBAIF,yBACE,iBACE,sBACA,SACA,mBAGF,6CAGE,UACA,WACA,mBACA,uBACA,kBAGF,cACE,eAGF,oBACE,aACA,uBAGF,qBACE,mBAGF,gBACE,SAGF,eACE,mBACA,eACA,uBACA,SAGF,cACE,mBACA,WAIJ,yBAKE,yBACE,kBAGF,2BACE,sBACA,oBACA,SAGF,mBACE,mBACA,8BACA,WAGF,mBACE,OACA,8BAGF,YACE,UACA,eACA,gBAGF,oBACE,wBAGF,iBACE,sBACA,oBACA,YACA,kBACA,QACA,gCAEA,2BACE,wBACA,yCACA,mBACA,sBACA,iBACA,eACA,qBAEA,mCACE,yBACA,gBACA,cACA,gBACA,eAGF,oCACE,gBACA,sCACA,gBACA,mBACA,4BACA,kBACA,wBACA,SAEA,4CAME,aAMR,gBACE,aCzqBJ,mBACE,gBACA,cACA,uBACA,+DAEA,+BACE,MAVO,QAWP,eACA,eAGF,mCACE,MAnBQ,QAoBR,eACA,eACA,gBAEA,yBANF,mCAOI,gBAIJ,wEAEE,MA/BQ,QAgCR,eACA,eAGF,wBACE,MArCQ,QA0CZ,eACE,kBACA,kBACA,gBACA,yBACA,mBACA,gDAEA,2BACE,oBACA,uBACA,mBACA,WACA,YACA,mBACA,kBACA,mBACA,cACA,eAGF,kBACE,eACA,eACA,gBACA,WAGF,iBACE,MApEO,QAqEP,eACA,gBAKJ,eACE,aACA,mBACA,uBACA,SACA,mBACA,gBACA,mBACA,+CACA,aACA,eAEA,yBAZF,eAaI,aACA,SACA,gBAGF,6BACE,aACA,sBACA,mBACA,QACA,cACA,kBACA,OAEA,sCACE,WACA,YACA,kBACA,mBACA,2BACA,aACA,mBACA,uBACA,cACA,eACA,gBACA,wBAEA,yBAdF,sCAeI,WACA,YACA,gBAIJ,wCACE,eACA,gBACA,cAEA,yBALF,wCAMI,gBAIJ,oCACE,MA3IO,QA6IP,6CACE,WA9IK,QA+IL,aA/IK,QAgJL,WAGF,+CACE,cACA,gBAOR,eACE,mBAEA,qBACE,cACA,eACA,gBACA,cACA,mBAGF,oEAEE,WACA,YACA,eACA,2BACA,kBACA,eACA,cACA,gBACA,4BAEA,gFACE,aACA,aArLO,QAwLT,8FACE,cAKN,wBACE,aACA,qCACA,SAEA,yBALF,wBAMI,2BAGF,wCACE,aACA,mBACA,SACA,kBACA,2BACA,kBACA,gBACA,eACA,mBAEA,6DACE,WACA,YACA,2BACA,kBACA,aAvNO,QA0NT,wDACE,eACA,gBACA,cAGF,wDACE,MA5NK,QA6NL,eACA,iBAGF,8CACE,aAvOO,QAwOP,mBAMN,oBACE,kBAGF,mBACE,mBAEA,+BACE,eACA,gBACA,cACA,mBAIJ,kBACE,aACA,4DACA,SAEA,oCACE,aACA,mBACA,SACA,kBACA,2BACA,kBACA,gBACA,eACA,mBAEA,yDACE,WACA,YACA,aAhRO,QAmRT,8CACE,eACA,gBACA,cAGF,4CACE,MArRK,QAsRL,eACA,iBAGF,0CACE,aAhSO,QAiSP,mBAMN,iBACE,aACA,8BACA,SACA,gBAEA,wBACE,uBAIJ,sCAEE,oBACA,mBACA,uBACA,YACA,YACA,WAzTW,QA0TX,WACA,YACA,kBACA,eACA,gBACA,eACA,qBACA,gCAEA,kDACE,iBAnUc,QAuUlB,oBACE,oBACA,mBACA,uBACA,YACA,YACA,gBACA,cACA,yBACA,kBACA,eACA,gBACA,eACA,qBACA,mBAEA,0BACE,mBACA,qBAIJ,oBACE,oBACA,mBACA,uBACA,YACA,YACA,gBACA,MAnWU,QAoWV,yBACA,kBACA,eACA,gBACA,eACA,mBAEA,0BACE,WA5WQ,QA6WR,WAIJ,UACE,oBACA,mBACA,uBACA,eACA,YACA,mBACA,cACA,yBACA,kBACA,eACA,gBACA,eACA,mBAEA,gBACE,mBACA,cAIJ,UACE,oBACA,mBACA,uBACA,eACA,YACA,WA9YW,QA+YX,WACA,YACA,kBACA,eACA,gBACA,eACA,gCAEA,gBACE,iBAvZc,QA4ZlB,cACE,yBACA,mBACA,kBACA,gBACA,iDAEA,yBAPF,cAQI,cAGF,iCACE,aACA,mBACA,8BACA,mBAEA,oCACE,eACA,gBACA,WACA,SAGF,kDACE,MAjbK,QAkbL,eAIJ,gCACE,aACA,SACA,eACA,6BACA,mBAEA,yBAPF,gCAQI,sBACA,uBACA,QACA,gBAGF,2CACE,YACA,cACA,eACA,gBACA,cAGF,2CACE,OACA,eACA,cACA,qBAGF,uFAEE,aACA,mBACA,SACA,eAGF,qCACE,sCACA,eACA,mBACA,cACA,iBACA,kBAIJ,wDAEE,oBACA,mBACA,iBACA,qBACA,oBACA,eACA,gBACA,mBACA,cAGF,yBACE,mBACA,cAGF,oCACE,aACA,SACA,yBACA,gBAKJ,kBACE,kBACA,kBAEA,iCACE,oBACA,uBACA,mBACA,WACA,YACA,mBACA,kBACA,cACA,eACA,mBAGF,qBACE,eACA,gBACA,WACA,gBAGF,sCACE,gBACA,gBACA,iBACA,aACA,gBACA,yBACA,mBACA,+CAEA,sDACE,cACA,mBACA,yBACA,kBACA,kBACA,mBACA,eACA,gBAGF,4CACE,cACA,eACA,gBACA,cACA,kBAGF,wDACE,aACA,SAEA,8DACE,OACA,YACA,eACA,2BACA,kBACA,sCACA,eACA,cACA,mBAEA,oEACE,aASV,oBACE,aACA,8BACA,mBACA,mBACA,WAEA,yBAPF,oBAQI,sBACA,uBACA,UAGF,8BACE,eACA,gBACA,cACA,oBACA,UAGF,uCACE,oBACA,mBACA,uBACA,eACA,YACA,yBACA,WACA,eACA,gBACA,kBACA,qBACA,gCAEA,6CACE,yBAKN,0BACE,yBACA,mBACA,kBACA,gBACA,iDAEA,yBAPF,0BAQI,mBAGF,6CACE,aACA,mBACA,8BACA,mBACA,oBACA,kCAEA,+DACE,aACA,mBACA,SACA,cAEA,0EACE,cACA,cAIJ,gDACE,eACA,gBACA,WACA,SAGF,8DACE,cACA,eACA,gBAIJ,gDACE,aACA,sBACA,SACA,gBAGF,8CACE,aACA,sBACA,SACA,WAEA,2DACE,eACA,gBACA,cACA,cAGF,iEACE,WACA,YACA,yBACA,mBACA,eACA,aACA,mBACA,eACA,cACA,gBACA,qBACA,sBACA,yBAGF,0DACE,aACA,eACA,QAGF,+DACE,oBACA,mBACA,uBACA,YACA,eACA,yBACA,mBACA,eACA,gBACA,yBACA,WACA,mBAGF,yDACE,oBACA,mBACA,uBACA,YACA,eACA,yBACA,mBACA,eACA,gBACA,yBACA,cACA,mBAGF,iEACE,aACA,mBACA,SACA,WAEA,yBANF,iEAOI,gBAIJ,0DACE,OACA,gBACA,YACA,yBACA,mBACA,eACA,aACA,mBACA,uBACA,eACA,cACA,gBACA,sBACA,mBACA,yBAEA,yBAjBF,0DAkBI,eACA,WACA,WAIJ,iEACE,YACA,eACA,gBACA,WACA,yBACA,mBACA,eACA,gBACA,eACA,gCAEA,uEACE,yBAIJ,gEACE,YACA,eACA,gBACA,WACA,yBACA,mBACA,eACA,gBACA,eACA,gCAEA,sEACE,yBAMN,yCACE,gBACA,YACA,eACA,gBAKJ,0BACE,oBACA,mBACA,uBACA,YACA,YACA,yBACA,WACA,YACA,mBACA,eACA,gBACA,eACA,gCAEA,gCACE,yBAIJ,mBACE,oBACA,mBACA,uBACA,YACA,YACA,yBACA,WACA,YACA,mBACA,eACA,gBACA,eACA,qBACA,gCAEA,yBACE,yBAKJ,wEAGE,aACA,SACA,yBACA,gBAEA,6IACE,oCAEA,qNACE,0BAIJ,wQAEE,kBAIJ,wBACE,oBACA,mBACA,uBACA,YACA,YACA,yBACA,cACA,yBACA,mBACA,eACA,gBACA,eACA,qBACA,mBAEA,8BACE,yBACA,qBACA,cAMJ,oBACE,gBACA,kBAEA,sBACE,oBACA,mBACA,QACA,kBACA,0BACA,mBACA,MAr6BS,QAs6BT,eACA,gBACA,qBACA,mBAEA,4BACE,aA56BO,QA66BP,mBAMN,eACE,eACA,QACA,6BACA,0BACA,aACA,mBACA,uBACA,aAEA,kCACE,gBACA,mBACA,aACA,YACA,eACA,4EAEA,qCACE,eACA,gBACA,cACA,eAGF,oCACE,cACA,eACA,gBACA,gBAGF,wCACE,WACA,YACA,eACA,2BACA,kBACA,kBACA,eACA,cAEA,8CACE,aACA,aA/9BK,QAm+BT,yDACE,aACA,SACA,yBACA,gBAEA,wJAEE,WACA,YACA,eAQN,iCACE,gBACA,yBACA,mBACA,kDACA,aACA,sBACA,mBACA,kBACA,mBACA,kBAGF,gCACE,mBACA,aACA,mBACA,uBACA,WAGF,6BACE,YACA,YACA,eAGA,4CACE,sDAIJ,qBACE,GACE,wBAGF,KACE,6BAIJ,gCACE,mBACA,kBAGF,8BACE,eACA,gBACA,cACA,mBACA,4CAGF,6BACE,eACA,gBACA,cACA,4CACA,SAEA,2CACE,cACA,gBAIJ,wBACE,aACA,uBACA,WAGF,6BACE,oBACA,mBACA,uBACA,YACA,YACA,mBACA,WACA,mBACA,eACA,gBACA,qBACA,2BAEA,mCACE,mBACA,WCzkCN,6GAOE,YnDsBoB,0GmDlBtB,iBAEE,mBACA,mBACA,0EACA,WACA,gBAEA,wCACE,aACA,mBACA,8BACA,SACA,eACA,aAGF,oBACE,SACA,eACA,gBACA,uBACA,WAGF,gCACE,iBAEA,2DAGF,qCACE,eACA,WAEA,4DAGF,oCACE,eACA,eACA,cAGF,+BACE,qBACA,UACA,WACA,iBACA,kBACA,mBACA,0CAGF,yBACE,0DAEA,mCAEA,iDAIJ,YAEE,iBACA,oBACA,mBACA,sBACA,kBACA,qBACA,mBACA,sBACA,mBACA,sBAEA,2BACA,qBACA,4BACA,mBACA,qBACA,qBACA,oBACA,kBACA,iBAEA,aAGA,qCACA,SACA,iBAGA,2CA/BF,YAmCE,gBAEA,oKASS,gBAET,iCACS,gBAGT,qBACE,gBACA,kCACA,+BACA,aACA,wCAGF,0BACE,aACA,qBACA,8BACA,SACA,eACA,mBAEA,6BACE,SACA,eACA,uBACA,qBAGF,mCACE,eACA,eACA,sBAIJ,sBACE,oBACA,mBACA,QACA,eACA,gBACA,iBACA,6BACA,qBACA,mBAEA,kEACc,gDACd,uFACA,oFACA,8EACA,mFAGF,8BACE,aACA,qBACA,SACA,eACA,oBAEA,iCACE,SACA,eACA,uBAGF,4EAEA,wCACE,eACA,gBACA,sBACA,mBACA,gBACA,6BAIJ,sBACE,sBACA,eACA,aACA,gBACA,mCACA,+BACA,kBAGF,0BACE,aACA,QACA,eACA,mBACA,gBACA,eACA,uBAEA,uCACE,6BACA,uBACA,eACA,gBACA,iBACA,6BAIF,wCACE,qBACA,sCAEA,oGAEE,6BACA,qBAKJ,mDACE,mBACA,sBACA,eAIF,6CACE,gBACA,0CACA,sBACA,eAMJ,2BACE,gBACA,aACA,SACA,kBAEA,yBANF,2BAOI,qDAMJ,yDACA,0EACA,sEAIA,6BACE,gBACA,kCACA,+BACA,6BACA,kBAEA,mDACE,aACA,mBACA,QACA,eACA,mBAEA,8FAGF,oDACE,eACA,gBACA,gBACA,6BACA,6BACA,qBAGF,mDACE,iBACA,gBACA,eACA,gBACA,uBACA,sBAEA,+HACA,gFAGF,mDACE,qBACA,gBACA,eACA,gBACA,qBACA,qBAKJ,2BACE,oCAEA,0CACE,aACA,mBACA,8BACA,SACA,eAGF,2CACE,iBACA,eACA,oBAGF,0EAEA,8CACE,gBACA,uCACA,iBACA,aACA,SAGF,sCACE,aACA,oCACA,SACA,eAEA,kDACE,sBACA,kCAGF,iDACE,eACA,gBACA,kBACA,2BACA,mBACA,gBACA,6BACA,6BACA,uBAEA,6GAON,+BACE,aACA,mBACA,8BACA,SACA,eACA,kBACA,gCACA,+BACA,4BACA,eACA,oBAEA,0FAKA,qCACE,aACA,QACA,qBACA,YACA,gBAGF,gCACE,aACA,8BACA,gBACA,eACA,sBAIJ,oBACE,aACA,YACA,SACA,UACA,kBACA,wBACA,eACA,wBAEA,uDACA,6DACA,0DACA,sCAGF,uBACE,aACA,SACA,eACA,gBACA,eACA,sBAEA,8BACE,qBACA,WACA,YACA,kBACA,iBACA,sBAEA,4DACA,iEACA,uEACA,oEACA,mEAEA,6EACY,0BAMd,8BACE,WACA,yBACA,eAGF,sDACK,kCAEL,2BACE,mBACA,uBACA,eACA,gBACA,yCAGF,iCACE,yCACA,eAEA,4DACA,0DAGF,0EACA,kEAIF,iDAEA,2BACE,gBACA,kCACA,qCACA,+BACA,kBACA,wCAEA,uEAEA,0CACE,aACA,qBACA,8BACA,SACA,eAGF,wGAEA,wCACE,oBACA,mBACA,QACA,eACA,gBACA,qBACA,6BACA,iBACA,6BACA,kCAGF,qGAEA,mHAEA,0CACE,iBACA,eACA,sBACA,kCAKJ,iDAGE,0CACE,aACA,mBACA,QACA,WACA,kBACA,mBACA,eACA,gBACA,uBACA,yCAIJ,2BACE,gBACA,kCACA,+BACA,uBACA,wCAEA,2EACA,4EACA,+EAEA,8CACE,aACA,mBACA,SACA,eACA,eACA,sBACA,mBAGF,2CACE,gBACA,eACA,gBACA,gBACA,uBAGF,2EACA,4EACA,+EAIF,yBACE,kBACA,kBAEA,iCACE,WACA,kBACA,SACA,QACA,WACA,UACA,4BAIJ,wBACE,kBACA,oBAEA,oDAEA,oCACE,eACA,iBACA,uCAGF,gCACE,kBACA,WACA,QACA,WACA,YACA,kBACA,sBACA,2BACA,6CAGF,yGACA,+GACA,iGACA,kHACA,+GAEA,wGACA,4HAEA,kCACE,SACA,eACA,sBACA,kCAIJ,0BACE,cACA,gBACA,iBACA,eACA,qBAIF,0BACE,aACA,QACA,oBACA,YACA,gBAGF,2BACE,aACA,SACA,UACA,kBACA,6BACA,eACA,wBAEA,iEACA,kEACA,qEAEA,yEACa,0BACb,6FACA,6CAGF,2BACE,aACA,mBACA,SACA,eACA,gBACA,kCACA,+BACA,kBACA,eAEA,4CACE,aACA,mBACA,QACA,YAGF,4CACE,gBACA,uBACA,mBAGF,4CACE,aACA,mBACA,QACA,iBACA,sBACA,mBAGF,mEAGF,sBACE,YACA,eACA,yCACA,kBACA,eACA,qBACA,gBACA,eAEA,4BACE,aACA,4BACA,0CAKJ,8BACE,kBACA,oBACA,mBAEA,uDAGA,iDACE,wBACA,gBAEA,2JAC+B,qDAInC,4BACE,kBACA,QACA,UACA,2BACA,aACA,WACA,YACA,UACA,SACA,kBACA,6BACA,uBACA,eACA,cACA,eAEA,0FAGF,qEAGA,gDAIE,wCACE,gBACA,eAGF,uCAEA,qCACE,kBACA,WACA,qBACA,OACA,QACA,iBACA,SACA,YACA,gBACA,gBACA,gBACA,yCACA,kBACA,yCAGF,qCACE,iBACA,kBACA,eACA,gBACA,uBACA,mBAEA,wEAGF,sCACE,aACA,sBACA,eACA,kBAKJ,gCACE,aACA,mBACA,SACA,kBACA,kCACA,sCACA,+BACA,mBACA,eACA,uBAEA,uDAEA,kDACE,cACA,sBACA,eAIJ,sBACE,aACA,uBACA,QACA,eACA,eAIF,oBACE,eACA,YACA,eACA,yCACA,kBACA,gBACA,uBACA,eACA,cACA,eAEA,6CAEA,+BACE,4BACA,0BACA,WACA,eAKJ,yBACE,2BACE,SAEA,8FAIA,gFAIJ,yBAjzBF,YAkzBI,SACA,oBAEA,2CAEA,uCAEA,6CAEA,qEACA,mDACA,0DAGA,2BACE,sBACA,oBACA,aAEA,iGAGA,kFACoB,qCAEpB,0EAIF,0BACE,YAEA,uDAGF,2DAEA,6CACA,2CAEA,6CAEA,iDAEA,oCAEA,2DAGF,yBACE,gDAEA,8CAEA,2CACA,4CCl7BJ,QACE,wBAGF,SACE,yBAGF,gBACE,gCAGF,QACE,wBAGF,eACE,+BAGF,QACE,wBAIF,alDRE,aACA,uBACA,mBkDUF,clDNE,aACA,8BACA,mBkDQF,aACE,sBAGF,WACE,eAGF,cACE,mBAGF,aACE,uBAGF,WACE,qBAGF,gBACE,uBAGF,eACE,2BAGF,aACE,yBAGF,iBACE,8BAGF,gBACE,6BAIF,QACE,IpDlBW,IoDqBb,QACE,IpDrBW,IoDwBb,QACE,IpDxBW,KoD2Bb,QACE,IpD3BW,KoD8Bb,QACE,IpD9BW,KoD8CX,KACE,oBAGF,MACE,wBAGF,MACE,2BAGF,MACE,yBAGF,MACE,0BAGF,MACE,yBACA,0BAGF,MACE,wBACA,2BA3BF,MACE,sBAGF,OACE,0BAGF,OACE,6BAGF,OACE,2BAGF,OACE,4BAGF,OACE,2BACA,4BAGF,OACE,0BACA,6BA3BF,MACE,sBAGF,OACE,0BAGF,OACE,6BAGF,OACE,2BAGF,OACE,4BAGF,OACE,2BACA,4BAGF,OACE,0BACA,6BA3BF,MACE,uBAGF,OACE,2BAGF,OACE,8BAGF,OACE,4BAGF,OACE,6BAGF,OACE,4BACA,6BAGF,OACE,2BACA,8BA3BF,MACE,uBAGF,OACE,2BAGF,OACE,8BAGF,OACE,4BAGF,OACE,6BAGF,OACE,4BACA,6BAGF,OACE,2BACA,8BA3BF,MACE,uBAGF,OACE,2BAGF,OACE,8BAGF,OACE,4BAGF,OACE,6BAGF,OACE,4BACA,6BAGF,OACE,2BACA,8BA3BF,OACE,uBAGF,QACE,2BAGF,QACE,8BAGF,QACE,4BAGF,QACE,6BAGF,QACE,4BACA,6BAGF,QACE,2BACA,8BA3BF,OACE,uBAGF,QACE,2BAGF,QACE,8BAGF,QACE,4BAGF,QACE,6BAGF,QACE,4BACA,6BAGF,QACE,2BACA,8BA3BF,OACE,uBAGF,QACE,2BAGF,QACE,8BAGF,QACE,4BAGF,QACE,6BAGF,QACE,4BACA,6BAGF,QACE,2BACA,8BA3BF,QACE,uBAGF,SACE,2BAGF,SACE,8BAGF,SACE,4BAGF,SACE,6BAGF,SACE,4BACA,6BAGF,SACE,2BACA,8BAgBF,KACE,qBAGF,MACE,yBAGF,MACE,4BAGF,MACE,0BAGF,MACE,2BAGF,MACE,0BACA,2BAGF,MACE,yBACA,4BA3BF,MACE,uBAGF,OACE,2BAGF,OACE,8BAGF,OACE,4BAGF,OACE,6BAGF,OACE,4BACA,6BAGF,OACE,2BACA,8BA3BF,MACE,uBAGF,OACE,2BAGF,OACE,8BAGF,OACE,4BAGF,OACE,6BAGF,OACE,4BACA,6BAGF,OACE,2BACA,8BA3BF,MACE,wBAGF,OACE,4BAGF,OACE,+BAGF,OACE,6BAGF,OACE,8BAGF,OACE,6BACA,8BAGF,OACE,4BACA,+BA3BF,MACE,wBAGF,OACE,4BAGF,OACE,+BAGF,OACE,6BAGF,OACE,8BAGF,OACE,6BACA,8BAGF,OACE,4BACA,+BA3BF,MACE,wBAGF,OACE,4BAGF,OACE,+BAGF,OACE,6BAGF,OACE,8BAGF,OACE,6BACA,8BAGF,OACE,4BACA,+BA3BF,OACE,wBAGF,QACE,4BAGF,QACE,+BAGF,QACE,6BAGF,QACE,8BAGF,QACE,6BACA,8BAGF,QACE,4BACA,+BA3BF,OACE,wBAGF,QACE,4BAGF,QACE,+BAGF,QACE,6BAGF,QACE,8BAGF,QACE,6BACA,8BAGF,QACE,4BACA,+BA3BF,OACE,wBAGF,QACE,4BAGF,QACE,+BAGF,QACE,6BAGF,QACE,8BAGF,QACE,6BACA,8BAGF,QACE,4BACA,+BAKJ,MACE,UAGF,MACE,UAGF,MACE,UAGF,OACE,WAGF,QACE,WAIF,MACE,WAGF,MACE,WAGF,MACE,WAGF,OACE,YAGF,QACE,YAGF,QACE,aAIF,mBACE,kBAGF,mBACE,kBAGF,gBACE,eAGF,iBACE,gBAIF,YACE,WpDxPa,QoD2Pf,cACE,WpD3Pe,QoD8PjB,UACE,WpDlPM,KoDqPR,SACE,WpDrPQ,QoDwPV,UACE,WpDxPS,QoDiQX,QACE,yBAGF,UACE,uBAGF,YACE,6BAGF,eACE,gCAGF,aACE,8BAGF,cACE,+BAGF,gBACE,apD1Sa,QoD8Sf,WACE,gBAGF,YACE,cpD5OiB,IoD+OnB,SACE,cpD/OiB,IoDkPnB,YACE,cpDlPiB,KoDqPnB,YACE,cpDrPiB,KoDwPnB,cACE,cpDvPmB,KoD0PrB,gBACE,cpD1PqB,IoD8PvB,aACE,2BAGF,WACE,WpDxTU,0BoD2TZ,QACE,WpD3TU,+BoD8TZ,WACE,WpD9TU,gCoDiUZ,WACE,WpDjUU,gCoDqUZ,iBACE,gBAGF,eACE,cAGF,mBACE,kBAGF,mBACE,kBAGF,iBACE,gBAGF,iBACE,gBAIF,KACE,UAGF,MACE,WAGF,MACE,WAGF,MACE,WAGF,MACE,WAGF,MACE,WlD3YE,yBkDgZF,SACE,yBlD/YA,0BkDoZF,SACE,yBAKF,yBADF,aAEI,yBlD7ZA,yBkDiaJ,cAEI,yBAKJ,IACE,clDzaE,yBkDwaJ,IAII,yBAIJ,QACE,alDjbE,yBkDgbJ,QAII,0BAKJ,gBACE,eAGF,gBACE,eAGF,oBACE,mBAIF,WACE,UAGF,YACE,YAGF,YACE,WAGF,YACE,YAGF,aACE,UAIF,YACE,WpDxXgB,aoD2XlB,iBACE,WpD3XgB,coD8XlB,iBACE,WpD9XgB,aoDiYlB,iBACE,gBAIF,0BACE,kBACA,UACA,WACA,UACA,YACA,gBACA,sBACA,mBACA","file":"main.min.css"} \ No newline at end of file diff --git a/src/main/resources/static/favicon.png b/src/main/resources/static/favicon.png new file mode 100644 index 0000000..99941c9 Binary files /dev/null and b/src/main/resources/static/favicon.png differ diff --git a/src/main/resources/static/font/NotoSansKR-VariableFont_wght.ttf b/src/main/resources/static/font/NotoSansKR-VariableFont_wght.ttf deleted file mode 100644 index d19eb43..0000000 Binary files a/src/main/resources/static/font/NotoSansKR-VariableFont_wght.ttf and /dev/null differ diff --git a/src/main/resources/static/font/kjb/HGGGothicssi40g.ttf b/src/main/resources/static/font/kjb/HGGGothicssi40g.ttf deleted file mode 100644 index 3891426..0000000 Binary files a/src/main/resources/static/font/kjb/HGGGothicssi40g.ttf and /dev/null differ diff --git a/src/main/resources/static/font/kjb/HGGGothicssi40g.woff b/src/main/resources/static/font/kjb/HGGGothicssi40g.woff deleted file mode 100644 index 2fd96d3..0000000 Binary files a/src/main/resources/static/font/kjb/HGGGothicssi40g.woff and /dev/null differ diff --git a/src/main/resources/static/font/kjb/HGGGothicssi40g.woff2 b/src/main/resources/static/font/kjb/HGGGothicssi40g.woff2 deleted file mode 100644 index c272914..0000000 Binary files a/src/main/resources/static/font/kjb/HGGGothicssi40g.woff2 and /dev/null differ diff --git a/src/main/resources/static/font/kjb/HGGGothicssi60g.ttf b/src/main/resources/static/font/kjb/HGGGothicssi60g.ttf deleted file mode 100644 index cc4dc7f..0000000 Binary files a/src/main/resources/static/font/kjb/HGGGothicssi60g.ttf and /dev/null differ diff --git a/src/main/resources/static/font/kjb/HGGGothicssi60g.woff b/src/main/resources/static/font/kjb/HGGGothicssi60g.woff deleted file mode 100644 index 9a6b093..0000000 Binary files a/src/main/resources/static/font/kjb/HGGGothicssi60g.woff and /dev/null differ diff --git a/src/main/resources/static/font/kjb/HGGGothicssi60g.woff2 b/src/main/resources/static/font/kjb/HGGGothicssi60g.woff2 deleted file mode 100644 index 0eef338..0000000 Binary files a/src/main/resources/static/font/kjb/HGGGothicssi60g.woff2 and /dev/null differ diff --git a/src/main/resources/static/font/kjb/HGGGothicssi80g.ttf b/src/main/resources/static/font/kjb/HGGGothicssi80g.ttf deleted file mode 100644 index 19d807a..0000000 Binary files a/src/main/resources/static/font/kjb/HGGGothicssi80g.ttf and /dev/null differ diff --git a/src/main/resources/static/font/kjb/HGGGothicssi80g.woff b/src/main/resources/static/font/kjb/HGGGothicssi80g.woff deleted file mode 100644 index 05acd6e..0000000 Binary files a/src/main/resources/static/font/kjb/HGGGothicssi80g.woff and /dev/null differ diff --git a/src/main/resources/static/font/kjb/HGGGothicssi80g.woff2 b/src/main/resources/static/font/kjb/HGGGothicssi80g.woff2 deleted file mode 100644 index 58582a9..0000000 Binary files a/src/main/resources/static/font/kjb/HGGGothicssi80g.woff2 and /dev/null differ diff --git a/src/main/resources/static/font/noto-sans/NotoSansKR-subset.woff2 b/src/main/resources/static/font/noto-sans/NotoSansKR-subset.woff2 new file mode 100644 index 0000000..389e4a5 Binary files /dev/null and b/src/main/resources/static/font/noto-sans/NotoSansKR-subset.woff2 differ diff --git a/src/main/resources/static/font/djb/OneShinhanBold.woff b/src/main/resources/static/font/shinhan/OneShinhanBold.woff similarity index 100% rename from src/main/resources/static/font/djb/OneShinhanBold.woff rename to src/main/resources/static/font/shinhan/OneShinhanBold.woff diff --git a/src/main/resources/static/font/djb/OneShinhanLight.woff b/src/main/resources/static/font/shinhan/OneShinhanLight.woff similarity index 100% rename from src/main/resources/static/font/djb/OneShinhanLight.woff rename to src/main/resources/static/font/shinhan/OneShinhanLight.woff diff --git a/src/main/resources/static/font/djb/OneShinhanMedium.woff b/src/main/resources/static/font/shinhan/OneShinhanMedium.woff similarity index 100% rename from src/main/resources/static/font/djb/OneShinhanMedium.woff rename to src/main/resources/static/font/shinhan/OneShinhanMedium.woff diff --git a/src/main/resources/static/font/kjb/SpoqaHanSansBold.ttf b/src/main/resources/static/font/spoqa-hans/SpoqaHanSansBold.ttf similarity index 100% rename from src/main/resources/static/font/kjb/SpoqaHanSansBold.ttf rename to src/main/resources/static/font/spoqa-hans/SpoqaHanSansBold.ttf diff --git a/src/main/resources/static/font/kjb/SpoqaHanSansBold.woff b/src/main/resources/static/font/spoqa-hans/SpoqaHanSansBold.woff similarity index 100% rename from src/main/resources/static/font/kjb/SpoqaHanSansBold.woff rename to src/main/resources/static/font/spoqa-hans/SpoqaHanSansBold.woff diff --git a/src/main/resources/static/font/kjb/SpoqaHanSansBold.woff2 b/src/main/resources/static/font/spoqa-hans/SpoqaHanSansBold.woff2 similarity index 100% rename from src/main/resources/static/font/kjb/SpoqaHanSansBold.woff2 rename to src/main/resources/static/font/spoqa-hans/SpoqaHanSansBold.woff2 diff --git a/src/main/resources/static/font/kjb/SpoqaHanSansNeo-Bold.ttf b/src/main/resources/static/font/spoqa-hans/SpoqaHanSansNeo-Bold.ttf similarity index 100% rename from src/main/resources/static/font/kjb/SpoqaHanSansNeo-Bold.ttf rename to src/main/resources/static/font/spoqa-hans/SpoqaHanSansNeo-Bold.ttf diff --git a/src/main/resources/static/font/kjb/SpoqaHanSansNeo-Bold.woff b/src/main/resources/static/font/spoqa-hans/SpoqaHanSansNeo-Bold.woff similarity index 100% rename from src/main/resources/static/font/kjb/SpoqaHanSansNeo-Bold.woff rename to src/main/resources/static/font/spoqa-hans/SpoqaHanSansNeo-Bold.woff diff --git a/src/main/resources/static/font/kjb/SpoqaHanSansNeo-Bold.woff2 b/src/main/resources/static/font/spoqa-hans/SpoqaHanSansNeo-Bold.woff2 similarity index 100% rename from src/main/resources/static/font/kjb/SpoqaHanSansNeo-Bold.woff2 rename to src/main/resources/static/font/spoqa-hans/SpoqaHanSansNeo-Bold.woff2 diff --git a/src/main/resources/static/font/kjb/SpoqaHanSansNeo-Regular.ttf b/src/main/resources/static/font/spoqa-hans/SpoqaHanSansNeo-Regular.ttf similarity index 100% rename from src/main/resources/static/font/kjb/SpoqaHanSansNeo-Regular.ttf rename to src/main/resources/static/font/spoqa-hans/SpoqaHanSansNeo-Regular.ttf diff --git a/src/main/resources/static/font/kjb/SpoqaHanSansNeo-Regular.woff b/src/main/resources/static/font/spoqa-hans/SpoqaHanSansNeo-Regular.woff similarity index 100% rename from src/main/resources/static/font/kjb/SpoqaHanSansNeo-Regular.woff rename to src/main/resources/static/font/spoqa-hans/SpoqaHanSansNeo-Regular.woff diff --git a/src/main/resources/static/font/kjb/SpoqaHanSansNeo-Regular.woff2 b/src/main/resources/static/font/spoqa-hans/SpoqaHanSansNeo-Regular.woff2 similarity index 100% rename from src/main/resources/static/font/kjb/SpoqaHanSansNeo-Regular.woff2 rename to src/main/resources/static/font/spoqa-hans/SpoqaHanSansNeo-Regular.woff2 diff --git a/src/main/resources/static/font/kjb/SpoqaHanSansRegular.ttf b/src/main/resources/static/font/spoqa-hans/SpoqaHanSansRegular.ttf similarity index 100% rename from src/main/resources/static/font/kjb/SpoqaHanSansRegular.ttf rename to src/main/resources/static/font/spoqa-hans/SpoqaHanSansRegular.ttf diff --git a/src/main/resources/static/font/kjb/SpoqaHanSansRegular.woff b/src/main/resources/static/font/spoqa-hans/SpoqaHanSansRegular.woff similarity index 100% rename from src/main/resources/static/font/kjb/SpoqaHanSansRegular.woff rename to src/main/resources/static/font/spoqa-hans/SpoqaHanSansRegular.woff diff --git a/src/main/resources/static/font/kjb/SpoqaHanSansRegular.woff2 b/src/main/resources/static/font/spoqa-hans/SpoqaHanSansRegular.woff2 similarity index 100% rename from src/main/resources/static/font/kjb/SpoqaHanSansRegular.woff2 rename to src/main/resources/static/font/spoqa-hans/SpoqaHanSansRegular.woff2 diff --git a/src/main/resources/static/img/avatar1.svg b/src/main/resources/static/img/avatar1.svg new file mode 100644 index 0000000..c363977 --- /dev/null +++ b/src/main/resources/static/img/avatar1.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/main/resources/static/img/avatar2.svg b/src/main/resources/static/img/avatar2.svg new file mode 100644 index 0000000..b54163e --- /dev/null +++ b/src/main/resources/static/img/avatar2.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/main/resources/static/img/bg_main_intersect.svg b/src/main/resources/static/img/bg_main_intersect.svg index acd996c..fb30477 100644 --- a/src/main/resources/static/img/bg_main_intersect.svg +++ b/src/main/resources/static/img/bg_main_intersect.svg @@ -1,3 +1,4 @@ - - + + + diff --git a/src/main/resources/static/img/favicon/apple-touch-icon.png b/src/main/resources/static/img/favicon/apple-touch-icon.png new file mode 100644 index 0000000..9fdc76d Binary files /dev/null and b/src/main/resources/static/img/favicon/apple-touch-icon.png differ diff --git a/src/main/resources/static/img/favicon/favicon-16x16.png b/src/main/resources/static/img/favicon/favicon-16x16.png new file mode 100644 index 0000000..eca6bcb Binary files /dev/null and b/src/main/resources/static/img/favicon/favicon-16x16.png differ diff --git a/src/main/resources/static/img/favicon/favicon-180x180.png b/src/main/resources/static/img/favicon/favicon-180x180.png new file mode 100644 index 0000000..9fdc76d Binary files /dev/null and b/src/main/resources/static/img/favicon/favicon-180x180.png differ diff --git a/src/main/resources/static/img/favicon/favicon-192x192.png b/src/main/resources/static/img/favicon/favicon-192x192.png new file mode 100644 index 0000000..52a6efe Binary files /dev/null and b/src/main/resources/static/img/favicon/favicon-192x192.png differ diff --git a/src/main/resources/static/img/favicon/favicon-32x32.png b/src/main/resources/static/img/favicon/favicon-32x32.png new file mode 100644 index 0000000..99941c9 Binary files /dev/null and b/src/main/resources/static/img/favicon/favicon-32x32.png differ diff --git a/src/main/resources/static/img/favicon/favicon-48x48.png b/src/main/resources/static/img/favicon/favicon-48x48.png new file mode 100644 index 0000000..7a5c0b2 Binary files /dev/null and b/src/main/resources/static/img/favicon/favicon-48x48.png differ diff --git a/src/main/resources/static/img/favicon/favicon-512x512.png b/src/main/resources/static/img/favicon/favicon-512x512.png new file mode 100644 index 0000000..e255763 Binary files /dev/null and b/src/main/resources/static/img/favicon/favicon-512x512.png differ diff --git a/src/main/resources/static/img/favicon/favicon.png b/src/main/resources/static/img/favicon/favicon.png new file mode 100644 index 0000000..99941c9 Binary files /dev/null and b/src/main/resources/static/img/favicon/favicon.png differ diff --git a/src/main/resources/static/img/icon/icon_check_green.png b/src/main/resources/static/img/icon/icon_check_green.png new file mode 100644 index 0000000..64e569c Binary files /dev/null and b/src/main/resources/static/img/icon/icon_check_green.png differ diff --git a/src/main/resources/static/img/icon/img_icon.png b/src/main/resources/static/img/icon/img_icon.png new file mode 100644 index 0000000..18d7aec Binary files /dev/null and b/src/main/resources/static/img/icon/img_icon.png differ diff --git a/src/main/resources/static/img/keyimage/api_img.png b/src/main/resources/static/img/keyimage/api_img.png new file mode 100644 index 0000000..8dd9b89 Binary files /dev/null and b/src/main/resources/static/img/keyimage/api_img.png differ diff --git a/src/main/resources/static/img/keyimage/api_img.svg b/src/main/resources/static/img/keyimage/api_img.svg new file mode 100644 index 0000000..0621625 --- /dev/null +++ b/src/main/resources/static/img/keyimage/api_img.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/static/img/keyimage/faq_img.png b/src/main/resources/static/img/keyimage/faq_img.png new file mode 100644 index 0000000..eae8bfd Binary files /dev/null and b/src/main/resources/static/img/keyimage/faq_img.png differ diff --git a/src/main/resources/static/img/keyimage/faq_img.svg b/src/main/resources/static/img/keyimage/faq_img.svg new file mode 100644 index 0000000..9f2c477 --- /dev/null +++ b/src/main/resources/static/img/keyimage/faq_img.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/static/img/keyimage/feedback_img.svg b/src/main/resources/static/img/keyimage/feedback_img.svg new file mode 100644 index 0000000..c7c2438 --- /dev/null +++ b/src/main/resources/static/img/keyimage/feedback_img.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/main/resources/static/img/keyimage/notice_img.png b/src/main/resources/static/img/keyimage/notice_img.png new file mode 100644 index 0000000..e43bab6 Binary files /dev/null and b/src/main/resources/static/img/keyimage/notice_img.png differ diff --git a/src/main/resources/static/img/keyimage/notice_img.svg b/src/main/resources/static/img/keyimage/notice_img.svg new file mode 100644 index 0000000..869c45f --- /dev/null +++ b/src/main/resources/static/img/keyimage/notice_img.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/static/img/keyimage/q&a_img.png b/src/main/resources/static/img/keyimage/q&a_img.png new file mode 100644 index 0000000..c6daafd Binary files /dev/null and b/src/main/resources/static/img/keyimage/q&a_img.png differ diff --git a/src/main/resources/static/img/keyimage/q&a_img.svg b/src/main/resources/static/img/keyimage/q&a_img.svg new file mode 100644 index 0000000..0064a97 --- /dev/null +++ b/src/main/resources/static/img/keyimage/q&a_img.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/static/img/keyimage/sigdnUp_img.svg b/src/main/resources/static/img/keyimage/sigdnUp_img.svg new file mode 100644 index 0000000..1dd4217 --- /dev/null +++ b/src/main/resources/static/img/keyimage/sigdnUp_img.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/static/img/keyimage/signUp_img.png b/src/main/resources/static/img/keyimage/signUp_img.png new file mode 100644 index 0000000..a24ba5e Binary files /dev/null and b/src/main/resources/static/img/keyimage/signUp_img.png differ diff --git a/src/main/resources/static/img/logo/logo-jjb_white.png b/src/main/resources/static/img/logo/logo-jjb_white.png new file mode 100644 index 0000000..78609d5 Binary files /dev/null and b/src/main/resources/static/img/logo/logo-jjb_white.png differ diff --git a/src/main/resources/static/js/api-selector.js b/src/main/resources/static/js/api-selector.js new file mode 100644 index 0000000..9b3fe9d --- /dev/null +++ b/src/main/resources/static/js/api-selector.js @@ -0,0 +1,491 @@ +/** + * API 선택 공용 모듈 (fragment/api_selector.html 전용, figma s2 디자인) + * + * 사용처: 앱(API Key) 신청/수정 step2, Webhook 신청/수정 step2 + * + * 계약: + * - 폼: #apiSelectorForm (data-save-action = "이전" 저장 POST 경로) + * - 기선택: window.API_SELECTOR_SELECTED / 목록 URL: window.API_SELECTOR_LIST_URL (fragment 인라인 주입) + * - "이전" 버튼: 호출 페이지의 #btnPrevStep (없으면 스킵) + * - 카트/모달: fragment `apiSelectorPopups` 를 pagePopups 슬롯에서 호출(body 직속) + * + * design(figma s2) 인라인 스크립트 대비 패치 3건: + * 1) 모달 열 때마다 updateModalList() 재빌드 — 세션 복원 직후(카드 렌더 전) 빈 모달 방지 + * 2) 모달 리스트를 DOM 체크박스가 아닌 selectedApis Set 기준으로 생성 — 미렌더/타 카테고리 누락 방지 + * 3) 제출/이전 시 DOM에 없는 선택분을 hidden input으로 주입 — 카테고리 필터 상태 전송 유실 방지 + */ +document.addEventListener('DOMContentLoaded', function() { + const form = document.getElementById('apiSelectorForm'); + if (!form) { + return; // 모듈 미사용 페이지 + } + + // DOM Elements + const searchInput = document.getElementById('apiSearch'); + const menuTitles = document.querySelectorAll('.s2-category-tab'); + const apiCardGrid = document.getElementById('apiCardGrid'); + const loadingState = document.getElementById('loadingState'); + const emptyState = document.getElementById('emptyState'); + + let currentFilter = ''; // Empty string means "all" + let currentServiceName = '전체'; + let allApis = []; + let selectedApis = new Set(); + + // Restore selected APIs from session (fragment 인라인 주입) + const sessionSelectedApis = window.API_SELECTOR_SELECTED; + if (sessionSelectedApis && Array.isArray(sessionSelectedApis)) { + sessionSelectedApis.forEach(function(apiId) { + selectedApis.add(apiId); + }); + } + + // Load APIs via AJAX + function loadApis(groupId) { + loadingState.style.display = 'block'; + emptyState.style.display = 'none'; + + document.querySelectorAll('.s2-api-card').forEach(card => card.remove()); + + const baseUrl = window.API_SELECTOR_LIST_URL || '/apis/for_request'; + let url = baseUrl; + if (groupId) { + url += '?groupIds=' + encodeURIComponent(groupId); + } + + fetch(url).then(response => response.json()).then(apis => { + allApis = apis; + loadingState.style.display = 'none'; + + if (apis.length === 0) { + emptyState.style.display = 'block'; + document.getElementById('apiResultCount').textContent = '0'; + return; + } + + document.getElementById('apiResultCount').textContent = apis.length; + renderApiCards(apis); + updateSelectAllUI(); + }).catch(error => { + console.error('Failed to load APIs:', error); + loadingState.style.display = 'none'; + emptyState.querySelector('h3').textContent = 'API 로드 실패'; + emptyState.querySelector('p').textContent = '다시 시도해주세요.'; + emptyState.style.display = 'block'; + document.getElementById('apiResultCount').textContent = '0'; + }); + } + + // Render API cards + function renderApiCards(apis) { + const fragment = document.createDocumentFragment(); + + apis.forEach(api => { + fragment.appendChild(createApiCard(api)); + }); + + apiCardGrid.appendChild(fragment); + attachCardEventListeners(); + } + + // Create API card element (figma s2 card) + function createApiCard(api) { + const card = document.createElement('div'); + card.className = 's2-api-card'; + card.setAttribute('data-group', api.apiGroupId || ''); + card.setAttribute('data-name', (api.apiName || '').toLowerCase()); + card.setAttribute('data-desc', (api.apiSimpleDescription || '').toLowerCase()); + card.setAttribute('data-api-id', api.apiId); + + const isSelected = selectedApis.has(api.apiId); + if (isSelected) { + card.classList.add('selected'); + } + + const mainIconHtml = api.mainIcon + ? `${api.apiName}` + : ``; + + card.innerHTML = ` +

+ ${api.apiGroupName || api.service || '카테고리'} +
+ + + +

${api.apiName || 'API 이름'}

+

${api.apiSimpleDescription || 'API 설명이 없습니다.'}

+ +
+ ${mainIconHtml} +
+ `; + + return card; + } + + // Attach event listeners to cards + function attachCardEventListeners() { + const apiCards = document.querySelectorAll('.s2-api-card'); + + apiCards.forEach(card => { + card.addEventListener('click', function(e) { + const checkbox = card.querySelector('.s2-api-checkbox'); + if (checkbox) { + checkbox.checked = !checkbox.checked; + updateCardSelection(checkbox); + updateSelectedCount(); + } + }); + }); + + // Prevent double toggle when clicking the checkbox wrapper + const checkboxWrappers = document.querySelectorAll('.s2-checkbox-wrapper'); + checkboxWrappers.forEach(wrapper => { + wrapper.addEventListener('click', function(e) { + e.stopPropagation(); // Stop click from bubbling to card! + }); + + const checkbox = wrapper.querySelector('.s2-api-checkbox'); + if (checkbox) { + checkbox.addEventListener('change', function() { + updateCardSelection(this); + updateSelectedCount(); + }); + } + }); + } + + // Update card visual state + function updateCardSelection(checkbox) { + const card = checkbox.closest('.s2-api-card'); + if (checkbox.checked) { + card.classList.add('selected'); + selectedApis.add(checkbox.value); + } else { + card.classList.remove('selected'); + selectedApis.delete(checkbox.value); + } + } + + // Update selected count + function updateSelectedCount() { + const floatingCartBtn = document.getElementById('floatingCartBtn'); + const cartCount = document.querySelector('.s2-cart-count'); + + if (selectedApis.size > 0) { + floatingCartBtn.style.display = 'flex'; + cartCount.textContent = selectedApis.size; + } else { + floatingCartBtn.style.display = 'none'; + } + + updateModalList(); + updateSelectAllCheckboxState(); + } + + // Update select all UI visibility and text + function updateSelectAllUI() { + const selectAllWrapper = document.getElementById('selectAllWrapper'); + const selectAllText = document.getElementById('selectAllText'); + + if (currentFilter === '') { + selectAllWrapper.style.display = 'none'; + } else { + selectAllWrapper.style.display = 'flex'; + selectAllText.textContent = currentServiceName + ' API 전체 선택'; + } + + updateSelectAllCheckboxState(); + } + + // Update select all checkbox state based on visible cards + function updateSelectAllCheckboxState() { + const selectAllCheckbox = document.getElementById('selectAllCheckbox'); + const visibleCards = Array.from(document.querySelectorAll('.s2-api-card')).filter(card => card.style.display !== 'none'); + + if (visibleCards.length === 0) { + selectAllCheckbox.checked = false; + selectAllCheckbox.indeterminate = false; + return; + } + + const visibleCheckboxes = visibleCards.map(card => card.querySelector('.s2-api-checkbox')); + const checkedCount = visibleCheckboxes.filter(cb => cb.checked).length; + + if (checkedCount === 0) { + selectAllCheckbox.checked = false; + selectAllCheckbox.indeterminate = false; + } else if (checkedCount === visibleCheckboxes.length) { + selectAllCheckbox.checked = true; + selectAllCheckbox.indeterminate = false; + } else { + selectAllCheckbox.checked = false; + selectAllCheckbox.indeterminate = true; + } + } + + // Update modal selected APIs list — selectedApis Set 기준 (패치 2) + function updateModalList() { + const modalSelectedList = document.getElementById('modalSelectedList'); + modalSelectedList.innerHTML = ''; + + if (selectedApis.size === 0) { + modalSelectedList.innerHTML = '

선택된 API가 없습니다.

'; + return; + } + + selectedApis.forEach(function(apiId) { + const card = document.querySelector('.s2-api-card[data-api-id="' + apiId + '"]'); + const apiName = card ? card.querySelector('.s2-api-card-title').textContent : apiId; + + const apiPill = document.createElement('div'); + apiPill.className = 's2-api-pill'; + apiPill.innerHTML = ` + ${apiName} + + `; + modalSelectedList.appendChild(apiPill); + }); + + document.querySelectorAll('.s2-api-pill-remove').forEach(function(btn) { + btn.addEventListener('click', function() { + const value = this.getAttribute('data-value'); + selectedApis.delete(value); + const checkbox = document.querySelector('.s2-api-checkbox[value="' + value + '"]'); + if (checkbox) { + checkbox.checked = false; + updateCardSelection(checkbox); + } + updateSelectedCount(); + }); + }); + } + + // Search functionality + if (searchInput) { + searchInput.addEventListener('input', function() { + const searchTerm = this.value.toLowerCase(); + + const apiCards = document.querySelectorAll('.s2-api-card'); + let visibleCount = 0; + apiCards.forEach(function(card) { + const apiName = card.getAttribute('data-name'); + const apiDesc = card.getAttribute('data-desc'); + const matchesSearch = apiName.includes(searchTerm) || apiDesc.includes(searchTerm); + + if (matchesSearch) { + card.style.display = ''; + visibleCount++; + } else { + card.style.display = 'none'; + } + }); + + document.getElementById('apiResultCount').textContent = visibleCount; + updateSelectAllCheckboxState(); + }); + } + + // Category tab selection + menuTitles.forEach(function(title) { + title.addEventListener('click', function(e) { + e.preventDefault(); + + menuTitles.forEach(t => t.classList.remove('active')); + this.classList.add('active'); + + const groupId = this.getAttribute('data-group'); + currentFilter = groupId; + currentServiceName = this.textContent.trim(); + + loadApis(groupId); + + if (searchInput) { + searchInput.value = ''; + } + }); + }); + + // Category Carousel Scroll + const categoryListWrapper = document.getElementById('categoryListWrapper'); + const btnPrevCategory = document.getElementById('btnPrevCategory'); + const btnNextCategory = document.getElementById('btnNextCategory'); + + if (categoryListWrapper && btnPrevCategory && btnNextCategory) { + const scrollAmount = 200; + + btnPrevCategory.addEventListener('click', function() { + categoryListWrapper.scrollBy({ left: -scrollAmount, behavior: 'smooth' }); + }); + + btnNextCategory.addEventListener('click', function() { + categoryListWrapper.scrollBy({ left: scrollAmount, behavior: 'smooth' }); + }); + + // Toggle buttons visibility/disabled state based on scroll position + function updateCarouselButtons() { + const scrollLeft = categoryListWrapper.scrollLeft; + const maxScrollLeft = categoryListWrapper.scrollWidth - categoryListWrapper.clientWidth; + + btnPrevCategory.disabled = scrollLeft <= 0; + btnNextCategory.disabled = scrollLeft >= maxScrollLeft - 1; + } + + categoryListWrapper.addEventListener('scroll', updateCarouselButtons); + window.addEventListener('resize', updateCarouselButtons); + + // Initial check after loading categories + setTimeout(updateCarouselButtons, 150); + } + + // Modal control + const floatingCartBtn = document.getElementById('floatingCartBtn'); + const selectedApisModal = document.getElementById('selectedApisModal'); + const modalOverlay = document.getElementById('modalOverlay'); + const modalCloseBtn = document.getElementById('modalCloseBtn'); + const modalCancelBtn = document.getElementById('modalCancelBtn'); + + function openModal() { + updateModalList(); // 열 때마다 최신 선택 상태로 재빌드 (패치 1) + selectedApisModal.style.display = 'flex'; // .s2-modal은 flex 중앙정렬 → block 금지 + setTimeout(function() { + if (modalOverlay) { + modalOverlay.classList.add('show'); + } + selectedApisModal.classList.add('show'); + }, 10); + document.body.style.overflow = 'hidden'; + } + + function closeModal() { + if (modalOverlay) { + modalOverlay.classList.remove('show'); + } + selectedApisModal.classList.remove('show'); + + setTimeout(function() { + selectedApisModal.style.display = 'none'; + }, 300); + + document.body.style.overflow = ''; + } + + if (floatingCartBtn) { + floatingCartBtn.addEventListener('click', openModal); + } + if (modalOverlay) { + modalOverlay.addEventListener('click', closeModal); + } + if (modalCloseBtn) { + modalCloseBtn.addEventListener('click', closeModal); + } + if (modalCancelBtn) { + modalCancelBtn.addEventListener('click', closeModal); + } + + document.addEventListener('keydown', function(e) { + if (e.key === 'Escape' && selectedApisModal.style.display === 'flex') { + closeModal(); + } + }); + + // Select All checkbox event + const selectAllCheckbox = document.getElementById('selectAllCheckbox'); + if (selectAllCheckbox) { + selectAllCheckbox.addEventListener('change', function() { + const isChecked = this.checked; + const visibleCards = Array.from(document.querySelectorAll('.s2-api-card')).filter(card => card.style.display !== 'none'); + + visibleCards.forEach(function(card) { + const checkbox = card.querySelector('.s2-api-checkbox'); + if (checkbox) { + checkbox.checked = isChecked; + updateCardSelection(checkbox); + } + }); + + updateSelectedCount(); + }); + } + + // DOM에 렌더되지 않은 선택분을 hidden input으로 주입 — 전송 유실 방지 (패치 3) + function syncHiddenSelected() { + document.querySelectorAll('input.hidden-selected-api').forEach(el => el.remove()); + selectedApis.forEach(function(apiId) { + if (!document.querySelector('.s2-api-checkbox[value="' + apiId + '"]')) { + const input = document.createElement('input'); + input.type = 'hidden'; + input.name = 'selectedApis'; + input.value = apiId; + input.className = 'hidden-selected-api'; + form.appendChild(input); + } + }); + } + + // Previous step button — 선택 저장 후 step1 복귀 + const btnPrevStep = document.getElementById('btnPrevStep'); + if (btnPrevStep) { + btnPrevStep.addEventListener('click', function(e) { + e.preventDefault(); + + const saveAction = form.getAttribute('data-save-action'); + if (saveAction) { + form.action = saveAction; + } + syncHiddenSelected(); + form.submit(); + }); + } + + // Form submit + // - Webhook/앱 변경은 API 가 필수(form data-api-required=true)이므로 미선택 시 차단한다. + // - 앱 신청(clients register)은 선택 사항이라, 미선택 시 확인 팝업으로 한 번 더 묻고 진행한다. + form.addEventListener('submit', function(e) { + if (selectedApis.size !== 0) { + syncHiddenSelected(); + return; + } + + var apiRequired = form.dataset.apiRequired === 'true'; + if (apiRequired) { + e.preventDefault(); + if (typeof customPopups !== 'undefined' && customPopups.showAlert) { + customPopups.showAlert('최소 1개 이상의 API를 선택해주세요.'); + } else { + alert('최소 1개 이상의 API를 선택해주세요.'); + } + return false; + } + + // 미선택 상태로 앱 신청 → 확인 후 진행 + e.preventDefault(); + var message = '선택한 API가 없습니다. API 없이 앱을 신청하시겠습니까?'; + // custom-popups.js 는 top-level const 라 window.customPopups 가 아닌 렉시컬 전역이다. + if (typeof customPopups !== 'undefined' && customPopups.showConfirm) { + customPopups.showConfirm(message, function (ok) { + if (ok) { + syncHiddenSelected(); + form.submit(); // submit() 은 submit 이벤트를 재발생시키지 않음 → 재확인 루프 없음 + } + }); + } else if (window.confirm(message)) { + syncHiddenSelected(); + form.submit(); + } + return false; + }); + + // Initialize + updateSelectedCount(); + loadApis(''); +}); diff --git a/src/main/resources/static/js/djb/api-status-issues.js b/src/main/resources/static/js/djb/api-status-issues.js new file mode 100644 index 0000000..c84376c --- /dev/null +++ b/src/main/resources/static/js/djb/api-status-issues.js @@ -0,0 +1,498 @@ +(function () { + 'use strict'; + + const page = document.getElementById('issueHistoryPage'); + if (!page) return; + + const windowDays = parseInt(page.getAttribute('data-window-days'), 10) || 90; + const base = page.getAttribute('data-base') || '/apistatus'; + const apiDetailBase = page.getAttribute('data-api-detail-base') || '/apis/detail'; + const noticeDetailBase = page.getAttribute('data-notice-detail-base') || '/portalnotice/detail'; + + /** 오픈 API 목록에 노출되는 API (링크 가능 대상) */ + const linkableApiIds = new Set(); + + const KIND_LABEL = { INCIDENT: '장애', DELAY: '지연', MAINTENANCE: '점검' }; + const KIND_CLASS = { INCIDENT: 'is-incident', DELAY: 'is-degraded', MAINTENANCE: 'is-maintenance' }; + const PAGE_SIZE = 10; + const API_LIST_LIMIT = 50; // 라이브서치 결과 표시 상한 + + const today = page.getAttribute('data-today') || ''; + // My APIs 등에서 넘어올 때 표시용 API 명 (필터 자체는 apiId 로 동작) + const urlApiName = new URLSearchParams(window.location.search).get('apiName') || ''; + const minDate = page.getAttribute('data-min-date') || ''; + + // 진입 시 날짜 미지정 = 조회 기간 전체 + const state = { + date: page.getAttribute('data-selected-date') || '', + apiId: page.getAttribute('data-selected-api') || '', + kind: page.getAttribute('data-selected-kind') || '', + page: 0 + }; + + const indexBar = document.getElementById('issueIndexBar'); + const listEl = document.getElementById('issueList'); + const pagerEl = document.getElementById('issuePager'); + const totalCountEl = document.getElementById('issueTotalCount'); + const selectedDateLabel = document.getElementById('selectedDateLabel'); + const dateField = document.getElementById('dateField'); + const dateInput = document.getElementById('filterDateInput'); + const dateClearBtn = document.getElementById('filterDateClear'); + const kindSelect = document.getElementById('filterKindSelect'); + const apiCombo = document.getElementById('apiCombo'); + const apiInput = document.getElementById('filterApiInput'); + const apiClearBtn = document.getElementById('filterApiClear'); + const apiListEl = document.getElementById('filterApiList'); + const unlistedBanner = document.getElementById('unlistedApiBanner'); + const unlistedName = document.getElementById('unlistedApiName'); + + let apiOptions = []; + + function escapeHtml(text) { + if (text == null) return ''; + return String(text) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + } + + function fetchJson(url) { + return fetch(url, { credentials: 'same-origin', headers: { Accept: 'application/json' } }) + .then(function (response) { + if (!response.ok) throw new Error('HTTP ' + response.status); + return response.json(); + }); + } + + function parseDateTime(value) { + if (!value) return null; + const parsed = new Date(String(value).replace(' ', 'T')); + return isNaN(parsed.getTime()) ? null : parsed; + } + + function pad(value) { + return value < 10 ? '0' + value : String(value); + } + + function formatDate(value) { + const date = parseDateTime(value); + if (!date) return '-'; + return date.getFullYear() + '-' + pad(date.getMonth() + 1) + '-' + pad(date.getDate()); + } + + function formatDateTime(value) { + const date = parseDateTime(value); + if (!date) return '-'; + return formatDate(value) + ' ' + pad(date.getHours()) + ':' + pad(date.getMinutes()); + } + + function formatDuration(minutes) { + if (minutes == null) return ''; + if (minutes < 60) return minutes + '분'; + const hours = Math.floor(minutes / 60); + const rest = minutes % 60; + return rest === 0 ? hours + '시간' : hours + '시간 ' + rest + '분'; + } + + function buildQuery(params) { + const query = []; + Object.keys(params).forEach(function (key) { + if (params[key] !== '' && params[key] != null) { + query.push(key + '=' + encodeURIComponent(params[key])); + } + }); + return query.length ? '?' + query.join('&') : ''; + } + + function syncBrowserUrl() { + if (!window.history || !window.history.replaceState) return; + window.history.replaceState(null, '', + base + '/issues' + buildQuery({ date: state.date, apiId: state.apiId, kind: state.kind })); + } + + /** 필터 변경 후 목록/인덱스 재조회 */ + function applyFilters(reloadIndex) { + state.page = 0; + syncBrowserUrl(); + renderIndexBarSelection(); + if (reloadIndex) loadIndexBar(); + loadIssues(); + } + + // ---------------- 90일 인덱스바 ---------------- + function renderIndexBar(entries) { + if (!entries || !entries.length) { + indexBar.innerHTML = ''; + return; + } + + indexBar.innerHTML = entries.map(function (entry) { + const hasIncident = entry.incCount > 0; + const hasDelay = entry.dlyCount > 0; + const hasMaintenance = entry.mntCount > 0; + + // 유형이 2종 이상 섞인 날은 개별 색 대신 복합 색으로 표시한다 + const kindCount = (hasIncident ? 1 : 0) + (hasDelay ? 1 : 0) + (hasMaintenance ? 1 : 0); + let cls = ''; + if (kindCount > 1) cls = ' has-mixed'; + else if (hasIncident) cls = ' has-incident'; + else if (hasDelay) cls = ' has-degraded'; + else if (hasMaintenance) cls = ' has-maintenance'; + if (state.date && state.date === entry.date) cls += ' is-selected'; + + const parts = []; + if (hasIncident) parts.push('장애 ' + entry.incCount + '건'); + if (hasDelay) parts.push('지연 ' + entry.dlyCount + '건'); + if (hasMaintenance) parts.push('점검 ' + entry.mntCount + '건'); + const tooltip = entry.date + (parts.length ? ' · ' + parts.join(' · ') : ' · 이슈 없음'); + + return ''; + }).join(''); + + indexBar.querySelectorAll('.as-index-cell').forEach(function (cell) { + cell.addEventListener('click', function () { + const clicked = cell.getAttribute('data-date'); + state.date = state.date === clicked ? '' : clicked; + applyFilters(false); + }); + }); + } + + function renderIndexBarSelection() { + indexBar.querySelectorAll('.as-index-cell').forEach(function (cell) { + cell.classList.toggle('is-selected', state.date === cell.getAttribute('data-date')); + }); + selectedDateLabel.textContent = state.date || '전체 기간'; + if (dateInput.value !== state.date) { + dateInput.value = state.date; + } + dateField.classList.toggle('has-value', !!state.date); + } + + // ---------------- 이슈 카드 ---------------- + /** + * 영향 API 태그. + * + * 서버가 이미 현재 사용자에게 게시된 API 만 내려주므로 여기 오는 항목은 전부 링크 대상이다. + * 게시되지 않은 GW 인터페이스는 개별 노출하지 않고 hiddenApiCount 로만 받아 건수로 묶는다. + */ + function apiPillsHtml(apis, hiddenCount) { + const hidden = hiddenCount || 0; + if ((!apis || !apis.length) && hidden === 0) return ''; + + const pills = (apis || []).map(function (api) { + const text = escapeHtml(api.apiName || api.apiId); + if (!linkableApiIds.has(api.apiId)) { + return '' + text + ''; + } + return '' + + text + ''; + }).join(''); + + // 개발자포탈에 게시되지 않은 GW 인터페이스는 개별 노출 없이 건수로만 알린다 + const hiddenPill = hidden > 0 + ? '' + + 'GW 인터페이스 ' + hidden + '건' + : ''; + + return '
영향 API:' + pills + hiddenPill + '
'; + } + + /** + * 이슈에 연결된 공지 본문. 서버가 게시 중(USE_YN='Y')인 공지만 내려주므로 여기서는 존재 여부만 본다. + * 본문은 관리자가 에디터로 작성한 HTML 이라 공지 상세(th:utext)와 같이 그대로 렌더한다. + */ + function noticeHtml(issue) { + if (!issue.noticeDetail) return ''; + const link = issue.noticeId + ? '공지 전체 보기 →' + : ''; + return '
' + + '
' + + '공지사항' + + (issue.noticeSubject ? '' + escapeHtml(issue.noticeSubject) + '' : '') + + '
' + + '
' + issue.noticeDetail + '
' + + link + + '
'; + } + + /** + * 타임라인 | 공지 2열 배치. 데스크탑에서만 두 칸으로 갈라지고 좁은 화면에서는 세로로 쌓인다. + * 한쪽만 있으면 감싸지 않는다 (혼자 반쪽 폭을 차지하지 않게). + */ + function splitHtml(left, right) { + if (!left) return right; + if (!right) return left; + return '
' + left + right + '
'; + } + + function issueCardHtml(issue) { + const kindClass = KIND_CLASS[issue.kind] || 'is-incident'; + + let inner; + if (issue.kind === 'MAINTENANCE') { + inner = '

' + escapeHtml(issue.summary || '점검 안내') + '

'; + } else { + const items = (issue.timeline || []).map(function (entry) { + return '
' + + '' + + '

' + escapeHtml(entry.labelKo || '진행 상황') + '

' + + '

' + escapeHtml(entry.body) + '

' + + '

' + escapeHtml(formatDateTime(entry.eventAt)) + '

' + + '
'; + }).join(''); + inner = items + ? '
' + items + '
' + : '

' + escapeHtml(issue.summary || '상세 진행 내역이 등록되지 않았습니다.') + '

'; + } + + const period = formatDateTime(issue.startedAt) + + (issue.endAt ? ' ~ ' + formatDateTime(issue.endAt) : ' ~ 진행 중') + + (issue.durationMinutes != null ? ' (' + formatDuration(issue.durationMinutes) + ')' : ''); + + return '
' + + '
' + + ' ' + escapeHtml(KIND_LABEL[issue.kind] || '') + '' + + ' ' + escapeHtml(period) + '' + + (issue.stateLabel ? '' + escapeHtml(issue.stateLabel) + '' : '') + + '
' + + '

' + escapeHtml(issue.title) + '

' + + splitHtml(inner, noticeHtml(issue)) + + apiPillsHtml(issue.impactedApis, issue.hiddenApiCount) + + '
'; + } + + function renderPager(pageData) { + const totalPages = pageData.totalPages || 0; + if (totalPages <= 1) { + pagerEl.innerHTML = ''; + return; + } + let html = ''; + for (let index = 0; index < totalPages; index++) { + const current = index === pageData.number; + html += ''; + } + pagerEl.innerHTML = html; + pagerEl.querySelectorAll('button[data-page]').forEach(function (button) { + button.addEventListener('click', function () { + state.page = parseInt(button.getAttribute('data-page'), 10) || 0; + loadIssues(); + window.scrollTo({ top: listEl.offsetTop - 80, behavior: 'smooth' }); + }); + }); + } + + function loadIssues() { + const query = buildQuery({ + date: state.date, + apiId: state.apiId, + kind: state.kind, + page: state.page, + size: PAGE_SIZE + }); + + fetchJson(base + '/issues/list.json' + query) + .then(function (pageData) { + const content = pageData.content || []; + totalCountEl.textContent = pageData.totalElements != null ? pageData.totalElements : content.length; + + listEl.innerHTML = content.length + ? content.map(issueCardHtml).join('') + : '
조건에 해당하는 이슈가 없습니다.
'; + renderPager(pageData); + }) + .catch(function () { + listEl.innerHTML = '
이슈 목록을 불러올 수 없습니다.
'; + pagerEl.innerHTML = ''; + }); + } + + // ---------------- API 필터 (라이브서치 콤보박스) ---------------- + + /** 옵션 = 오픈 API 목록과 동일 기준(그룹 편성 + 사용자 공개). 표시는 API 명만. */ + function loadApiOptions() { + return fetchJson(base + '/apis.json') + .then(function (apis) { + apiOptions = (apis || []).filter(function (api) { return api.apiId; }); + apiOptions.forEach(function (api) { linkableApiIds.add(api.apiId); }); + // URL 로 들어온 apiId 는 옵션(오픈 API 목록)에 없어도 필터를 유지한다. + // My APIs(기관 계약 API)처럼 오픈 API 목록 밖의 API 로도 진입하기 때문. + renderApiInput(); + }) + .catch(function () { /* 옵션 조회 실패 시 전체 API 기준으로 동작 */ }); + } + + function findApiOption(apiId) { + if (!apiId) return null; + for (let index = 0; index < apiOptions.length; index++) { + if (apiOptions[index].apiId === apiId) return apiOptions[index]; + } + return null; + } + + /** 표시 형태: "[그룹명] API명" (그룹 없으면 API명만) */ + function optionLabel(api) { + if (!api) return ''; + return api.groupName ? '[' + api.groupName + '] ' + api.apiName : api.apiName; + } + + function renderApiInput() { + const selected = findApiOption(state.apiId); + // 옵션에 없는 API(오픈 API 목록 밖)는 전달받은 API 명 또는 ID 로 표시해 필터 상태를 보이게 한다 + apiInput.value = selected ? optionLabel(selected) : (state.apiId ? (urlApiName || state.apiId) : ''); + apiCombo.classList.toggle('has-value', !!state.apiId); + + // 포탈 미게시 API 필터 안내 배너 (옵션 목록을 받은 뒤에만 판단) + const unlisted = !!state.apiId && apiOptions.length > 0 && !selected; + if (unlisted) { + unlistedName.textContent = urlApiName || state.apiId; + } + unlistedBanner.style.display = unlisted ? '' : 'none'; + } + + function closeApiList() { + apiListEl.hidden = true; + apiInput.setAttribute('aria-expanded', 'false'); + } + + function openApiList(keyword) { + const query = (keyword || '').trim().toLowerCase(); + const matched = apiOptions.filter(function (api) { + if (!query) return true; + return optionLabel(api).toLowerCase().indexOf(query) >= 0; + }); + + if (!matched.length) { + apiListEl.innerHTML = '
  • 검색 결과가 없습니다
  • '; + } else { + const items = matched.slice(0, API_LIST_LIMIT).map(function (api) { + return '
  • ' + + escapeHtml(optionLabel(api)) + '
  • '; + }); + if (matched.length > API_LIST_LIMIT) { + items.push('
  • 이하 ' + + (matched.length - API_LIST_LIMIT) + '건은 검색어를 입력해 좁혀주세요
  • '); + } + apiListEl.innerHTML = '
  • 전체 API
  • ' + + items.join(''); + } + + apiListEl.hidden = false; + apiInput.setAttribute('aria-expanded', 'true'); + } + + function selectApi(apiId) { + state.apiId = apiId || ''; + renderApiInput(); + closeApiList(); + applyFilters(true); + } + + function loadIndexBar() { + fetchJson(base + '/issues/dates.json' + + buildQuery({ days: windowDays, apiId: state.apiId, kind: state.kind })) + .then(function (entries) { + renderIndexBar(entries); + renderIndexBarSelection(); + }) + .catch(function () { indexBar.innerHTML = ''; }); + } + + // ---------------- 필터 이벤트 ---------------- + + dateInput.addEventListener('change', function () { + const value = dateInput.value; + // 조회 기간(90일) 밖 날짜는 되돌린다 + if (value && ((minDate && value < minDate) || (today && value > today))) { + dateInput.value = state.date; + return; + } + state.date = value; + applyFilters(false); + }); + + dateClearBtn.addEventListener('click', function () { + state.date = ''; + dateInput.value = ''; + applyFilters(false); + }); + + kindSelect.addEventListener('change', function () { + state.kind = kindSelect.value; + applyFilters(true); + }); + + // readonly 자동완성 차단 트릭: 브라우저 autofill 은 readonly 입력을 후보에서 제외한다. + // 사용자가 실제로 만지는 순간에만 readonly 를 풀어 입력을 허용한다 (모바일은 touchstart 가 먼저 온다). + function unlockApiInput() { + if (apiInput.hasAttribute('readonly')) { + apiInput.removeAttribute('readonly'); + } + } + apiInput.addEventListener('touchstart', unlockApiInput, { passive: true }); + apiInput.addEventListener('mousedown', unlockApiInput); + + apiInput.addEventListener('focus', function () { + unlockApiInput(); + openApiList(''); + }); + + apiInput.addEventListener('input', function () { openApiList(apiInput.value); }); + + apiInput.addEventListener('keydown', function (event) { + if (event.key === 'Escape') { + renderApiInput(); + closeApiList(); + } else if (event.key === 'Enter') { + event.preventDefault(); + const first = apiListEl.querySelector('.as-combo-item'); + if (first) selectApi(first.getAttribute('data-api-id')); + } + }); + + apiListEl.addEventListener('mousedown', function (event) { + const item = event.target.closest('.as-combo-item'); + if (!item) return; + event.preventDefault(); + selectApi(item.getAttribute('data-api-id')); + }); + + apiClearBtn.addEventListener('click', function () { + apiInput.value = ''; + selectApi(''); + }); + + document.addEventListener('click', function (event) { + if (!apiCombo.contains(event.target)) { + renderApiInput(); + closeApiList(); + } + }); + + document.getElementById('filterResetBtn').addEventListener('click', function () { + state.date = ''; + state.apiId = ''; + state.kind = ''; + dateInput.value = ''; + kindSelect.value = ''; + renderApiInput(); + closeApiList(); + applyFilters(true); + }); + + // ---------------- 초기 로딩 ---------------- + dateInput.value = state.date; + kindSelect.value = state.kind; + renderIndexBarSelection(); + loadIndexBar(); + // 영향 API 태그의 링크 여부 판단에 오픈 API 목록이 필요하므로 옵션을 먼저 받는다 + loadApiOptions().then(loadIssues); +})(); diff --git a/src/main/resources/static/js/djb/api-status.js b/src/main/resources/static/js/djb/api-status.js new file mode 100644 index 0000000..ab27ef9 --- /dev/null +++ b/src/main/resources/static/js/djb/api-status.js @@ -0,0 +1,399 @@ +(function () { + 'use strict'; + + const page = document.getElementById('apiStatusPage'); + if (!page) return; + + const windowDays = parseInt(page.getAttribute('data-window-days'), 10) || 90; + const authenticated = page.classList.contains('is-authenticated'); + const base = page.getAttribute('data-base') || '/apistatus'; + const apiDetailBase = page.getAttribute('data-api-detail-base') || '/apis/detail'; + const noticeDetailBase = page.getAttribute('data-notice-detail-base') || '/portalnotice/detail'; + + /** 오픈 API 목록에 노출되는 API (링크 가능 대상) */ + const linkableApiIds = new Set(); + + const KIND_LABEL = { INCIDENT: '장애', DELAY: '지연', MAINTENANCE: '점검' }; + const KIND_CLASS = { INCIDENT: 'is-incident', DELAY: 'is-degraded', MAINTENANCE: 'is-maintenance' }; + + /** 진행 중 장애 카드 노출 상한. 초과분은 전체 이력 링크로 넘긴다. */ + const ACTIVE_INCIDENT_LIMIT = 1; + + function escapeHtml(text) { + if (text == null) return ''; + return String(text) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + } + + function fetchJson(url) { + return fetch(url, { credentials: 'same-origin', headers: { Accept: 'application/json' } }) + .then(function (response) { + if (!response.ok) throw new Error('HTTP ' + response.status); + return response.json(); + }); + } + + /** "2026-07-30T09:15:00" → Date. 서버는 서버 timezone(KST) 기준 LocalDateTime 을 보낸다. */ + function parseDateTime(value) { + if (!value) return null; + const parsed = new Date(String(value).replace(' ', 'T')); + return isNaN(parsed.getTime()) ? null : parsed; + } + + function pad(value) { + return value < 10 ? '0' + value : String(value); + } + + function formatDate(value) { + const date = parseDateTime(value); + if (!date) return '-'; + return date.getFullYear() + '-' + pad(date.getMonth() + 1) + '-' + pad(date.getDate()); + } + + function formatDateTime(value) { + const date = parseDateTime(value); + if (!date) return '-'; + return formatDate(value) + ' ' + pad(date.getHours()) + ':' + pad(date.getMinutes()); + } + + function formatTime(value) { + const date = parseDateTime(value); + if (!date) return '-'; + return pad(date.getHours()) + ':' + pad(date.getMinutes()); + } + + function formatDuration(minutes) { + if (minutes == null) return ''; + if (minutes < 60) return minutes + '분'; + const hours = Math.floor(minutes / 60); + const rest = minutes % 60; + return rest === 0 ? hours + '시간' : hours + '시간 ' + rest + '분'; + } + + function formatPercent(ratio) { + if (ratio == null) return '-'; + return (ratio * 100).toFixed(2); + } + + function issuesHref(params) { + const query = []; + if (params.date) query.push('date=' + encodeURIComponent(params.date)); + if (params.apiId) query.push('apiId=' + encodeURIComponent(params.apiId)); + if (params.apiName) query.push('apiName=' + encodeURIComponent(params.apiName)); + return base + '/issues' + (query.length ? '?' + query.join('&') : ''); + } + + /** + * 영향 API 태그. + * + * 서버가 이미 현재 사용자에게 게시된 API 만 내려주므로 여기 오는 항목은 전부 링크 대상이다. + * 게시되지 않은 GW 인터페이스는 개별 노출하지 않고 hiddenApiCount 로만 받아 건수로 묶는다. + */ + function apiPillsHtml(apis, label, hiddenCount) { + const hidden = hiddenCount || 0; + if ((!apis || !apis.length) && hidden === 0) return ''; + + const pills = (apis || []).map(function (api) { + const text = escapeHtml(api.apiName || api.apiId); + if (!linkableApiIds.has(api.apiId)) { + return '' + text + ''; + } + return '' + + text + ''; + }).join(''); + + const hiddenPill = hidden > 0 + ? '' + + 'GW 인터페이스 ' + hidden + '건' + : ''; + + return '
    ' + escapeHtml(label) + '' + pills + hiddenPill + '
    '; + } + + /** + * 이슈에 연결된 공지 본문. 서버가 게시 중(USE_YN='Y')인 공지만 내려주므로 여기서는 존재 여부만 본다. + * 본문은 관리자가 에디터로 작성한 HTML 이라 공지 상세(th:utext)와 같이 그대로 렌더한다. + */ + function noticeHtml(incident) { + if (!incident.noticeDetail) return ''; + const link = incident.noticeId + ? '공지 전체 보기 →' + : ''; + return '
    ' + + '
    ' + + '공지사항' + + (incident.noticeSubject ? '' + escapeHtml(incident.noticeSubject) + '' : '') + + '
    ' + + '
    ' + incident.noticeDetail + '
    ' + + link + + '
    '; + } + + /** + * 타임라인 | 공지 2열 배치. 데스크탑에서만 두 칸으로 갈라지고 좁은 화면에서는 세로로 쌓인다. + * 한쪽만 있으면 감싸지 않는다 (혼자 반쪽 폭을 차지하지 않게). + */ + function splitHtml(left, right) { + if (!left) return right; + if (!right) return left; + return '
    ' + left + right + '
    '; + } + + // ---------------- ❶ 진행 중 장애 ---------------- + function renderActiveIncidents(incidents) { + const container = document.getElementById('activeIncidents'); + if (!incidents || !incidents.length) { + container.innerHTML = '
    ' + + '

    현재 진행 중인 장애가 없습니다

    ' + + '

    모든 API 가 정상 동작 중입니다.

    ' + + '정상
    '; + return; + } + + const visible = incidents.slice(0, ACTIVE_INCIDENT_LIMIT); + const hidden = incidents.length - visible.length; + + const cards = visible.map(function (incident) { + const timeline = (incident.recentTimeline || []).map(function (entry) { + const who = entry.authorType === 'SYSTEM' ? 'SYSTEM' : '운영자'; + const whoClass = entry.authorType === 'SYSTEM' ? ' is-system' : ''; + return '
    ' + + '' + escapeHtml(formatTime(entry.eventAt)) + '' + + '' + escapeHtml(who) + '' + + '' + escapeHtml(entry.body) + '' + + '
    '; + }).join(''); + + return '
    ' + + '
    ' + + '
    장애' + + (incident.stateLabel ? ' ' + escapeHtml(incident.stateLabel) + '' : '') + + '
    ' + + '
    시작 ' + escapeHtml(formatDateTime(incident.startedAt)) + '' + + ' · 경과 ' + escapeHtml(formatDuration(incident.elapsedMinutes)) + '
    ' + + '
    ' + + '

    ' + escapeHtml(incident.title) + '

    ' + + (incident.summary ? '

    ' + escapeHtml(incident.summary) + '

    ' : '') + + apiPillsHtml(incident.apis, '영향 API:', incident.hiddenApiCount) + + splitHtml(timeline ? '
    ' + timeline + '
    ' : '', + noticeHtml(incident)) + + '
    '; + }).join(''); + + const overflow = hidden > 0 + ? '
    ' + + '진행 중인 장애가 ' + escapeHtml(String(hidden)) + '건 더 있습니다.' + + '전체 이슈 이력에서 보기 →' + + '
    ' + : ''; + + container.innerHTML = cards + overflow; + } + + // ---------------- ❷ 90일 서비스 상태 ---------------- + function renderUptime(stats) { + const chart = document.getElementById('uptimeChart'); + const fromLabel = document.getElementById('uptimeFromLabel'); + + if (!stats || !stats.length) { + chart.innerHTML = ''; + return; + } + + const STATUS_LABEL = { OUTAGE: '장애', MAINTENANCE: '점검', DEGRADED: '지연' }; + + chart.innerHTML = stats.map(function (stat) { + const statusClass = stat.status === 'OUTAGE' ? ' is-outage' + : stat.status === 'MAINTENANCE' ? ' is-maintenance' + : stat.status === 'DEGRADED' ? ' is-degraded' : ''; + const titles = (stat.issues || []).map(function (issue) { + return (KIND_LABEL[issue.kind] || '') + ' ' + issue.title; + }); + const tooltip = stat.statDate + ' · ' + (STATUS_LABEL[stat.status] || '정상') + + (titles.length ? '\n' + titles.join('\n') : ''); + return ''; + }).join(''); + + if (fromLabel) fromLabel.textContent = stats[0].statDate; + + chart.querySelectorAll('.as-bar').forEach(function (bar) { + bar.addEventListener('click', function () { + window.location.href = issuesHref({ date: bar.getAttribute('data-date') }); + }); + }); + } + + // ---------------- ❸ My APIs ---------------- + function renderMyApis(apis) { + const card = document.getElementById('myApisCard'); + if (!card) return; + if (!apis || !apis.length) { + card.style.display = 'none'; + return; + } + + const badgeClass = { + OUTAGE: 'is-outage', + DEGRADED: 'is-degraded', + MAINTENANCE: 'is-maintenance', + NORMAL: 'is-normal' + }; + + document.getElementById('myApisCount').textContent = apis.length + '건'; + document.getElementById('myApisBody').innerHTML = apis.map(function (api) { + const cls = badgeClass[api.currentStatus] || 'is-muted'; + return '' + + '' + escapeHtml(api.apiName) + '' + + '' + escapeHtml(api.currentStatusLabel) + '' + + '' + formatPercent(api.uptime90d) + '%' + + ''; + }).join(''); + + card.style.display = ''; + card.querySelectorAll('tbody tr').forEach(function (row) { + row.addEventListener('click', function () { + window.location.href = issuesHref({ + apiId: row.getAttribute('data-api-id'), + apiName: row.getAttribute('data-api-name') + }); + }); + }); + } + + // ---------------- ❹ 점검 사항 ---------------- + function renderMaintenance(cards) { + const list = document.getElementById('maintenanceList'); + const count = document.getElementById('maintenanceCount'); + + if (!cards || !cards.length) { + count.textContent = '0건'; + list.innerHTML = '
    예정된 점검이 없습니다.
    '; + return; + } + + const now = new Date(); + count.textContent = cards.length + '건'; + list.innerHTML = cards.map(function (card) { + const start = parseDateTime(card.startedAt); + const ongoing = start && start <= now; + const phase = ongoing ? '진행중' : '예정'; + const range = formatDateTime(card.startedAt) + + (card.endAt ? ' ~ ' + formatDateTime(card.endAt) : '') + + (card.durationMinutes != null ? ' (' + formatDuration(card.durationMinutes) + ')' : ''); + + return '
    ' + + '
    ' + + '

    ' + escapeHtml(card.title) + '

    ' + + ' ' + escapeHtml(phase + ' · ' + range) + '' + + '
    ' + + splitHtml(card.summary ? '
    ' + escapeHtml(card.summary) + '
    ' : '', + noticeHtml(card)) + + apiPillsHtml(card.impactedApis, '영향 API:', card.hiddenApiCount) + + '
    등록 ' + escapeHtml(formatDateTime(card.registeredAt)) + + (card.lastModifiedAt ? ' (최종 수정 ' + escapeHtml(formatDateTime(card.lastModifiedAt)) + ')' : '') + + '
    ' + + '
    '; + }).join(''); + } + + // ---------------- ❺ 지난 이슈 사항 ---------------- + function issueCardHtml(issue) { + const kindClass = KIND_CLASS[issue.kind] || 'is-incident'; + const kindBadge = kindClass; + + let inner; + if (issue.kind === 'MAINTENANCE') { + inner = '
    ' + + '' + + '

    점검 완료

    ' + + (issue.summary ? '

    ' + escapeHtml(issue.summary) + '

    ' : '') + + '

    ' + escapeHtml(formatDateTime(issue.startedAt)) + + ' ~ ' + escapeHtml(formatDateTime(issue.endAt)) + '

    ' + + '
    '; + } else { + const items = (issue.timeline || []).map(function (entry) { + return '
    ' + + '' + + '

    ' + escapeHtml(entry.labelKo || '진행 상황') + '

    ' + + '

    ' + escapeHtml(entry.body) + '

    ' + + '

    ' + escapeHtml(formatDateTime(entry.eventAt)) + '

    ' + + '
    '; + }).join(''); + inner = items + ? '
    ' + items + '
    ' + : '

    ' + escapeHtml(issue.summary || '상세 진행 내역이 등록되지 않았습니다.') + '

    '; + } + + const duration = issue.durationMinutes != null ? ' · ' + formatDuration(issue.durationMinutes) : ''; + + return '
    ' + + '
    ' + escapeHtml(formatDate(issue.startedAt)) + '
    ' + + '
    ' + + '
    ' + + ' ' + escapeHtml(KIND_LABEL[issue.kind] || '') + '' + + ' ' + escapeHtml(formatDateTime(issue.startedAt) + duration) + '' + + (issue.stateLabel ? '' + escapeHtml(issue.stateLabel) + '' : '') + + '
    ' + + '

    ' + escapeHtml(issue.title) + '

    ' + + splitHtml(inner, noticeHtml(issue)) + + apiPillsHtml(issue.impactedApis, '영향 API:', issue.hiddenApiCount) + + '
    ' + + '
    '; + } + + function renderRecentIssues(issues) { + const list = document.getElementById('recentIssueList'); + if (!issues || !issues.length) { + list.innerHTML = '
    종결된 이슈가 없습니다.
    '; + return; + } + list.innerHTML = issues.map(issueCardHtml).join(''); + } + + function handleError(target, message) { + const element = document.getElementById(target); + if (element) element.innerHTML = '
    ' + escapeHtml(message) + '
    '; + } + + // ---------------- 초기 로딩 ---------------- + // 영향 API 태그의 링크 여부를 판단해야 하므로 오픈 API 목록을 먼저 받는다 + fetchJson(base + '/apis.json') + .then(function (apis) { + (apis || []).forEach(function (api) { + if (api.apiId) linkableApiIds.add(api.apiId); + }); + }) + .catch(function () { /* 실패 시 전부 링크 없이 표시된다 */ }) + .then(loadSections); + + function loadSections() { + fetchJson(base + '/active.json') + .then(renderActiveIncidents) + .catch(function () { handleError('activeIncidents', '진행 중 장애 정보를 불러올 수 없습니다.'); }); + + fetchJson(base + '/uptime.json?days=' + windowDays) + .then(renderUptime) + .catch(function () { handleError('uptimeChart', '서비스 상태 정보를 불러올 수 없습니다.'); }); + + fetchJson(base + '/maintenance.json') + .then(renderMaintenance) + .catch(function () { handleError('maintenanceList', '점검 정보를 불러올 수 없습니다.'); }); + + fetchJson(base + '/recent-issues.json?size=5') + .then(renderRecentIssues) + .catch(function () { handleError('recentIssueList', '지난 이슈를 불러올 수 없습니다.'); }); + + if (authenticated) { + fetchJson(base + '/my-apis.json') + .then(renderMyApis) + .catch(function () { /* 비인증/권한 없음은 조용히 숨긴다 */ }); + } + } +})(); diff --git a/src/main/resources/static/js/djb/client-guard.js b/src/main/resources/static/js/djb/client-guard.js new file mode 100644 index 0000000..eab6af6 --- /dev/null +++ b/src/main/resources/static/js/djb/client-guard.js @@ -0,0 +1,60 @@ +/** + * client-guard.js + * + * 클라이언트측 하드닝. 서버가 내려준 window.__CLIENT_GUARD__ 플래그에 따라 각 기능을 독립 적용한다. + * - contextmenu: 우클릭(컨텍스트 메뉴) 차단 + * - devtools : 창 크기 휴리스틱으로 DevTools 열림 감지 시 화면 전체 요소 영구 삭제 + * + * 주의(한계): 창 크기 휴리스틱은 오탐/미탐이 있다(줌·북마크바·사이드패널·좁은창=오탐, undock 창=미탐). + * JS 비활성화·breakpoint·DOM 편집으로 우회 가능하므로 실질 보안이 아니라 억제용 데코이다. + */ +(function () { + 'use strict'; + + var cfg = window.__CLIENT_GUARD__ || {}; + + // ── 우클릭(contextmenu) 차단 ───────────────────────────────────────── + if (cfg.contextmenu) { + document.addEventListener('contextmenu', function (e) { + e.preventDefault(); + }, true); + } + + // ── DevTools 감지 → 화면 영구 삭제 ─────────────────────────────────── + if (cfg.devtools) { + var THRESHOLD = 160; // 도킹된 DevTools 패널이 차지하는 최소 폭/높이(px) 추정값 + var POLL_MS = 500; + var wiped = false; + + function isDevtoolsOpen() { + var widthGap = window.outerWidth - window.innerWidth; + var heightGap = window.outerHeight - window.innerHeight; + return widthGap > THRESHOLD || heightGap > THRESHOLD; + } + + function wipe() { + if (wiped) { + return; + } + wiped = true; + // 화면 전체 요소 영구 삭제 (닫아도 복구되지 않음, 새로고침 필요) + try { + document.documentElement.replaceChildren(); + } catch (ignore) { + if (document.body) { + document.body.innerHTML = ''; + } + } + } + + function check() { + if (!wiped && isDevtoolsOpen()) { + wipe(); + } + } + + window.setInterval(check, POLL_MS); + window.addEventListener('resize', check); + check(); + } +})(); diff --git a/src/main/resources/static/js/djb/inquiry-comments.js b/src/main/resources/static/js/djb/inquiry-comments.js new file mode 100644 index 0000000..a78f3fb --- /dev/null +++ b/src/main/resources/static/js/djb/inquiry-comments.js @@ -0,0 +1,199 @@ +(function () { + 'use strict'; + + const section = document.querySelector('.djb-comment-section'); + if (!section) return; + + const inquiryId = section.getAttribute('data-inquiry-id'); + const closed = section.getAttribute('data-closed') === 'true'; + const listEl = document.getElementById('djbCommentList'); + const emptyEl = document.getElementById('djbCommentEmpty'); + const countEl = document.getElementById('djbCommentCount'); + const formEl = document.getElementById('djbCommentForm'); + const inputEl = document.getElementById('djbCommentInput'); + const counterEl = document.getElementById('djbCommentCharCounter'); + const privateEl = document.getElementById('djbCommentPrivate'); + + function getCsrfToken() { + // 세션 기반 CSRF: 쿠키 대신 에서 토큰을 읽는다. + const meta = document.querySelector('meta[name="_csrf"]'); + return meta ? meta.getAttribute('content') : ''; + } + + function fetchJson(url, options) { + options = options || {}; + const headers = Object.assign({}, options.headers || {}); + const method = (options.method || 'GET').toUpperCase(); + if (method !== 'GET' && method !== 'HEAD') { + headers['X-XSRF-TOKEN'] = getCsrfToken(); + } + if (options.body && !headers['Content-Type']) { + headers['Content-Type'] = 'application/json;charset=UTF-8'; + } + return fetch(url, Object.assign({ credentials: 'same-origin' }, options, { headers: headers })); + } + + function escapeHtml(text) { + if (text == null) return ''; + return String(text) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + } + + function formatDate(value) { + if (!value) return ''; + try { + var d; + if (Array.isArray(value)) { + // Jackson LocalDateTime 직렬화(timestamp 배열): [year, month(1-base), day, hour, minute, second, nano] + var y = value[0], mo = value[1] || 1, da = value[2] || 1; + var h = value[3] || 0, mi = value[4] || 0, s = value[5] || 0; + d = new Date(y, mo - 1, da, h, mi, s); + } else { + d = new Date(value); + } + if (isNaN(d.getTime())) return ''; + const pad = function (n) { return n < 10 ? '0' + n : '' + n; }; + return d.getFullYear() + '.' + pad(d.getMonth() + 1) + '.' + pad(d.getDate()) + + ' ' + pad(d.getHours()) + ':' + pad(d.getMinutes()); + } catch (e) { + return ''; + } + } + + function render(comments) { + listEl.innerHTML = ''; + if (!comments || comments.length === 0) { + const li = document.createElement('li'); + li.className = 'djb-comment-empty'; + li.textContent = '아직 등록된 댓글이 없습니다.'; + listEl.appendChild(li); + return; + } + comments.forEach(function (c) { + const li = document.createElement('li'); + var cls = 'djb-comment-item'; + if (c.adminYn === 'Y') cls += ' djb-comment-item--admin'; + if (c.privatePlaceholder) cls += ' djb-comment-item--private-hidden'; + else if (c.privateComment) cls += ' djb-comment-item--private'; + li.className = cls; + li.setAttribute('data-comment-id', c.id); + + var writerHtml = (c.adminYn === 'Y') + ? '관리자' + : '' + escapeHtml(c.writerName || '(알 수 없음)') + ''; + // 비공개 댓글(열람 권한 있는 경우)에는 "비공개" 태그 표시 + var privateTag = (c.privateComment && !c.privatePlaceholder) + ? '비공개' : ''; + + li.innerHTML = '' + + '
    ' + + '
    ' + + ' ' + + ' ' + + ' ' + + writerHtml + + privateTag + + '
    ' + + '
    ' + + ' ' + escapeHtml(formatDate(c.createdDate)) + '' + + (c.deletable ? ' ' : '') + + '
    ' + + '
    ' + + '
    ' + escapeHtml(c.content) + '
    '; + listEl.appendChild(li); + }); + } + + function load() { + fetchJson('/djb/inquiry/' + encodeURIComponent(inquiryId) + '/comments') + .then(function (res) { + if (!res.ok) throw new Error('댓글 조회에 실패했습니다.'); + return res.json(); + }) + .then(render) + .catch(function (err) { + console.error('[djb-comments] load error', err); + }); + } + + function handleSubmit() { + const content = (inputEl.value || '').trim(); + if (!content) { + if (window.customPopups) window.customPopups.showAlert('댓글 내용을 입력해 주세요.'); + inputEl.focus(); + return; + } + var visibility = (privateEl && privateEl.checked) ? 'PRIVATE' : 'ALL'; + fetchJson('/djb/inquiry/' + encodeURIComponent(inquiryId) + '/comments', { + method: 'POST', + body: JSON.stringify({ content: content, visibility: visibility }) + }) + .then(function (res) { + if (res.status === 409) throw new Error('종료된 문의에는 댓글을 작성할 수 없습니다.'); + if (!res.ok) throw new Error('댓글 등록에 실패했습니다.'); + return res.json(); + }) + .then(function () { + inputEl.value = ''; + if (privateEl) privateEl.checked = false; + updateCounter(); + load(); + }) + .catch(function (err) { + if (window.customPopups) window.customPopups.showAlert(err.message); + else alert(err.message); + }); + } + + function handleDelete(commentId) { + if (!commentId) return; + function doDelete() { + fetchJson('/djb/inquiry/comments/' + encodeURIComponent(commentId), { method: 'DELETE' }) + .then(function (res) { + if (res.status === 403) throw new Error('본인이 작성한 댓글만 삭제할 수 있습니다.'); + if (res.status === 409) throw new Error('종료된 문의에는 댓글을 삭제할 수 없습니다.'); + if (!res.ok && res.status !== 204) throw new Error('댓글 삭제에 실패했습니다.'); + load(); + }) + .catch(function (err) { + if (window.customPopups) window.customPopups.showAlert(err.message); + else alert(err.message); + }); + } + if (window.customPopups) { + window.customPopups.showConfirm('댓글을 삭제하시겠습니까?', function (ok) { + if (ok) doDelete(); + }); + } else if (window.confirm('댓글을 삭제하시겠습니까?')) { + doDelete(); + } + } + + function updateCounter() { + if (!counterEl || !inputEl) return; + const max = inputEl.getAttribute('maxlength') || '2000'; + counterEl.textContent = (inputEl.value || '').length + ' / ' + max; + } + + if (!closed && formEl) { + formEl.addEventListener('submit', function (e) { + e.preventDefault(); + handleSubmit(); + }); + inputEl.addEventListener('input', updateCounter); + updateCounter(); + } + + listEl.addEventListener('click', function (e) { + const target = e.target; + if (target && target.classList.contains('djb-comment-delete-btn')) { + handleDelete(target.getAttribute('data-id')); + } + }); + + load(); +})(); diff --git a/src/main/resources/static/js/djb/inquiry-view.js b/src/main/resources/static/js/djb/inquiry-view.js new file mode 100644 index 0000000..32f093d --- /dev/null +++ b/src/main/resources/static/js/djb/inquiry-view.js @@ -0,0 +1,35 @@ +(function () { + 'use strict'; + + // 조회수 비정상 증가 억제: sessionStorage 로 한 세션 내 중복 증가 방지. + var container = document.querySelector('.inquiry-detail-container[data-inquiry-id]'); + if (!container) return; + + var inquiryId = container.getAttribute('data-inquiry-id'); + if (!inquiryId) return; + + var viewedKey = 'inquiry_viewed_' + inquiryId; + if (sessionStorage.getItem(viewedKey)) { + return; // 이미 이번 세션에서 조회 → 증가하지 않음 + } + + function getCsrfToken() { + // 세션 기반 CSRF: 쿠키 대신 에서 토큰을 읽는다. + var meta = document.querySelector('meta[name="_csrf"]'); + return meta ? meta.getAttribute('content') : ''; + } + + fetch('/inquiry/' + encodeURIComponent(inquiryId) + '/view', { + method: 'POST', + credentials: 'same-origin', + headers: { 'X-XSRF-TOKEN': getCsrfToken() } + }) + .then(function (res) { + if (res.ok) { + sessionStorage.setItem(viewedKey, 'true'); + } + }) + .catch(function (err) { + console.error('[inquiry-view] increment error', err); + }); +})(); diff --git a/src/main/resources/static/js/djb/toast.js b/src/main/resources/static/js/djb/toast.js new file mode 100644 index 0000000..511765a --- /dev/null +++ b/src/main/resources/static/js/djb/toast.js @@ -0,0 +1,81 @@ +/*! + * DjbToast — 경량 전역 토스트 알림. + * 사용: window.djbToast('메시지') 또는 djbToast('메시지', { type: 'info', duration: 3000 }) + * + * 추가로, 페이지 진입 시 URL 쿼리 `apiApplyToast` 값에 따라 안내 토스트를 자동 노출한다. + * - new : 신규 클라이언트 생성 페이지 진입 안내 + * - existing : 클라이언트 목록(선택) 진입 안내 + * - modify : 클라이언트 1건 보유 → 수정 플로우 API 선택 직행 안내 + * 토스트 노출 후 쿼리를 정리(replaceState)해 새로고침 시 재노출을 막는다. + */ +(function (global) { + "use strict"; + + var CONTAINER_ID = "djb-toast-container"; + + function ensureContainer() { + var el = document.getElementById(CONTAINER_ID); + if (!el) { + el = document.createElement("div"); + el.id = CONTAINER_ID; + el.className = "djb-toast-container"; + document.body.appendChild(el); + } + return el; + } + + function showToast(message, opts) { + if (!message) return; + opts = opts || {}; + var duration = typeof opts.duration === "number" ? opts.duration : 3200; + var type = opts.type || "info"; + + var container = ensureContainer(); + var toast = document.createElement("div"); + toast.className = "djb-toast djb-toast--" + type; + toast.setAttribute("role", "status"); + toast.innerHTML = String(message); // 호출부에서만 신뢰 문자열 전달 + + container.appendChild(toast); + // enter 애니메이션 + requestAnimationFrame(function () { toast.classList.add("is-show"); }); + + var remove = function () { + toast.classList.remove("is-show"); + setTimeout(function () { + if (toast.parentNode) toast.parentNode.removeChild(toast); + }, 250); + }; + var timer = setTimeout(remove, duration); + toast.addEventListener("click", function () { clearTimeout(timer); remove(); }); + } + + global.djbToast = showToast; + + // ── URL 쿼리 기반 자동 안내 토스트 ── + var API_APPLY_MESSAGES = { + "new": "API 를 사용할 클라이언트 생성을 먼저 진행해주세요.", + "existing": "API 를 추가 사용할 클라이언트를 선택해주세요", + "modify": "보유 클라이언트에 추가 사용할 API 를 선택해주세요" + }; + + function handleApiApplyToast() { + var params = new URLSearchParams(global.location.search); + var key = params.get("apiApplyToast"); + if (!key || !API_APPLY_MESSAGES[key]) return; + + showToast(API_APPLY_MESSAGES[key], { type: "info" }); + + // 쿼리 제거 후 URL 정리 (새로고침 재노출 방지) + params.delete("apiApplyToast"); + var qs = params.toString(); + var newUrl = global.location.pathname + (qs ? "?" + qs : "") + global.location.hash; + try { global.history.replaceState(null, "", newUrl); } catch (e) { /* noop */ } + } + + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", handleApiApplyToast); + } else { + handleApiApplyToast(); + } +})(window); diff --git a/src/main/resources/static/js/login-success-handler.js b/src/main/resources/static/js/login-success-handler.js index a811257..c180347 100644 --- a/src/main/resources/static/js/login-success-handler.js +++ b/src/main/resources/static/js/login-success-handler.js @@ -31,6 +31,7 @@ var LoginSuccessHandler = (function() { pendingInvitation: false, pendingInvitationToken: '', pendingInvitationOrgName: '', + needClientRegister: false, sessionSuccessMsg: '', redirectUrl: '' }; @@ -117,6 +118,19 @@ var LoginSuccessHandler = (function() { } } ); + return; // 다른 체크 중단 + } + + // 6. 클라이언트(앱/API키) 미보유 법인 사용자 → 신규 신청 유도 (로그인 직후 1회) + if (config.needClientRegister) { + customPopups.showConfirm( + 'API 사용을 위해서는 클라이언트 신규 신청이 필요합니다.
    지금 신청하시겠습니까?', + function(selection) { + if (selection) { + window.location.href = '/clients/register/step1'; + } + } + ); } } diff --git a/src/main/resources/static/js/password-policy.js b/src/main/resources/static/js/password-policy.js new file mode 100644 index 0000000..d9dab8e --- /dev/null +++ b/src/main/resources/static/js/password-policy.js @@ -0,0 +1,160 @@ +/** + * 비밀번호 문자열 정책 라이브 검증 (공용) + * + * 서버 검증기 PasswordRuleValidator.isValid(= @PasswordRule) 의 + * 규칙을 클라이언트로 포팅한다. 아이디/휴대전화 포함 규칙(noid/nomobile)은 + * 사용자가 폼에 직접 입력한 값이 페이지에 이미 있을 때만 ul 의 + * data-context-loginid/data-context-mobile 로 연결해 쓴다 — DB 값을 새로 + * 내려받아야 하는 화면(비밀번호 변경)은 서버 AJAX(/password/content-check)로 판정한다. + * + * 사용법(마크업 구동): + *
      + *
    • ...
    • + * ... (rule: length | letter | digit | special | nospace | norepeat | noseq) + *
    + * + * + * DOM ready 시 자동으로 스캔하여 대상 input 에 바인딩한다. + */ +(function (global) { + 'use strict'; + + // 3자리 연속(오름/내림) 문자·숫자 검사 — 서버 로직과 동일하게 대문자로 비교 + function hasSequential(pw) { + var s = pw.toUpperCase(); + for (var i = 0; i < s.length - 2; i++) { + var a = s.charCodeAt(i), b = s.charCodeAt(i + 1), c = s.charCodeAt(i + 2); + var sameCategory = (a > 47 && c < 58) || (a > 64 && c < 91); // 숫자 0-9 또는 영문 A-Z + var consecutive = Math.abs(c - b) === 1 && Math.abs(c - a) === 2; + if (sameCategory && consecutive) { + return true; + } + } + return false; + } + + // 규칙별 판정 함수 (통과=true). ctx = { loginId, mobile } — 값이 없으면 해당 규칙은 통과 처리 + var RULES = { + length: function (pw) { return pw.length >= 8 && pw.length <= 50; }, + letter: function (pw) { return /[a-zA-Z]/.test(pw); }, + digit: function (pw) { return /[0-9]/.test(pw); }, + special: function (pw) { return /[^A-Za-z0-9_]/.test(pw); }, // 서버 정규식 \W 기준 (밑줄 제외) + nospace: function (pw) { return !/\s/.test(pw); }, + norepeat: function (pw) { return !/(\w)\1\1/.test(pw.toUpperCase()); }, + noseq: function (pw) { return !hasSequential(pw); }, + // 아이디(이메일 local part) 포함 금지 — 서버 PasswordRuleValidator.containsLoginIdLocalPart 포팅 + noid: function (pw, ctx) { + var id = ctx && ctx.loginId ? String(ctx.loginId).split('@')[0].toUpperCase() : ''; + return !id || pw.toUpperCase().indexOf(id) === -1; + }, + // 휴대전화 하이픈 세그먼트 포함 금지 — 서버 containsMobileSegment 포팅 + nomobile: function (pw, ctx) { + var m = ctx && ctx.mobile ? String(ctx.mobile) : ''; + if (!m) { return true; } + var up = pw.toUpperCase(); + var parts = m.split('-'); + for (var i = 0; i < parts.length; i++) { + if (parts[i] && up.indexOf(parts[i]) !== -1) { + return false; + } + } + return true; + } + }; + + // 전체 문자열 규칙 통과 여부 (ctx 미전달 시 noid/nomobile 은 통과 — 서버 검증에 위임) + function isValid(pw, ctx) { + if (!pw) { + return false; + } + for (var key in RULES) { + if (RULES.hasOwnProperty(key) && !RULES[key](pw, ctx || {})) { + return false; + } + } + return true; + } + + // bind 된 체크리스트들의 update 함수 목록 (컨텍스트 값 변경 시 refresh 용) + var updaters = []; + + // 체크리스트(ul) 하나를 대상 input 에 바인딩. + // ul 의 data-context-loginid / data-context-mobile 속성에 소스 input 의 id 를 주면 + // noid/nomobile 규칙이 해당 값 기준으로 라이브 판정된다. + function bind(input, list) { + var $input = (input && input.jquery) ? input : $(input); + var $list = (list && list.jquery) ? list : $(list); + var $items = $list.find('li[data-rule]'); + if (!$input.length || !$items.length) { + return; + } + + function ctxValue(attr) { + var id = $list.attr(attr); + var el = id ? document.getElementById(id) : null; + return el ? el.value : ''; + } + + function update() { + var pw = $input.val() || ''; + var ctx = { + loginId: ctxValue('data-context-loginid'), + mobile: ctxValue('data-context-mobile') + }; + $items.each(function () { + var $li = $(this); + var rule = RULES[$li.attr('data-rule')]; + if (!rule) { + return; + } + $li.removeClass('is-idle is-pass is-fail'); + if (pw.length === 0) { + $li.addClass('is-idle'); + } else { + $li.addClass(rule(pw, ctx) ? 'is-pass' : 'is-fail'); + } + }); + } + + // 컨텍스트 소스 input 이 직접 타이핑되는 경우도 즉시 반영 + ['data-context-loginid', 'data-context-mobile'].forEach(function (attr) { + var id = $list.attr(attr); + if (id && document.getElementById(id)) { + $(document.getElementById(id)).on('input.passwordPolicy change.passwordPolicy', update); + } + }); + + updaters.push(update); + $input.on('input.passwordPolicy', update); + update(); + } + + // hidden input 등 이벤트 없이 값이 세팅되는 컨텍스트 변경 후 수동 재판정 + function refresh() { + updaters.forEach(function (u) { u(); }); + } + + // 마크업 구동 자동 초기화 + function init(root) { + var $root = root ? $(root) : $(document); + $root.find('ul.password-policy-checklist[data-password-input]').each(function () { + var $list = $(this); + var input = document.getElementById($list.attr('data-password-input')); + if (input) { + bind(input, $list); + } + }); + } + + global.PasswordPolicy = { + RULES: RULES, + isValid: isValid, + bind: bind, + init: init, + refresh: refresh + }; + + $(function () { + init(document); + }); +})(window); diff --git a/src/main/resources/static/js/popup/custom-popups.js b/src/main/resources/static/js/popup/custom-popups.js index 715ef4e..303d125 100644 --- a/src/main/resources/static/js/popup/custom-popups.js +++ b/src/main/resources/static/js/popup/custom-popups.js @@ -133,6 +133,7 @@ const customPopups = { // 기본값 설정 const title = options.title || '비밀번호 입력'; const message = options.message || '계속하려면 비밀번호를 입력해주세요.'; + const placeholder = options.placeholder || '비밀번호를 입력하세요'; const onConfirm = options.onConfirm; const onCancel = options.onCancel; @@ -140,8 +141,12 @@ const customPopups = { $('#passwordPopupTitle').text(title); $('#passwordPopupMessage').html(customPopups._sanitizeHtml(message)); - // 입력 필드 및 에러 초기화 - $('#passwordPopupInput').val('').removeClass('error'); + // 입력 필드 및 에러 초기화 (placeholder 는 호출부 커스텀 허용) + // type 은 열려 있는 동안만 password 로 전환한다. + // 상주 DOM 에 password 입력이 있으면 브라우저가 페이지의 다른 텍스트 입력을 + // 아이디 칸으로 오인해 계정 자동완성을 띄우기 때문 (닫을 때 text 로 복원). + $('#passwordPopupInput').val('').removeClass('error') + .attr('type', 'password').attr('placeholder', placeholder); $('#passwordPopupError').removeClass('show').text(''); // 팝업 표시 (modal 구조 사용) @@ -218,8 +223,8 @@ const customPopups = { // Body 스크롤 복원 $('body').css('overflow', ''); - // 입력 필드 초기화 - $('#passwordPopupInput').val('').removeClass('error'); + // 입력 필드 초기화 (type 도 text 로 복원 — 상주 password 로 남기지 않는다) + $('#passwordPopupInput').val('').removeClass('error').attr('type', 'text'); $('#passwordPopupError').removeClass('show').text(''); // 이벤트 리스너 제거 @@ -245,6 +250,110 @@ const customPopups = { $('#passwordPopupError').removeClass('show'); $('#passwordPopupInput').removeClass('error'); }, + /** + * API 이용 해지 신청 팝업 표시 (경고문 + 사유 필수 — 본인 확인은 step-up 2FA가 담당) + * @param {Object} options - 팝업 옵션 + * @param {Function} options.onConfirm - 해지 신청 버튼 클릭 시 호출되는 콜백 (파라미터: reason) + * @param {Function} options.onCancel - 취소 버튼 클릭 시 호출되는 콜백 (선택사항) + */ + showTerminateRequest: function (options) { + options = options || {}; + + const onConfirm = options.onConfirm; + const onCancel = options.onCancel; + + // 입력 필드 및 에러 초기화 + $('#terminateReasonInput').val('').removeClass('error'); + $('#terminatePopupError').removeClass('show').text(''); + + // 팝업 표시 (modal 구조 사용) + $('#terminateRequestPopup').show(); + setTimeout(function() { + $('#terminateModalBackdrop').addClass('show'); + $('#terminateModal').addClass('show'); + }, 10); + + // Body 스크롤 방지 + $('body').css('overflow', 'hidden'); + + // 사유 입력 필드에 포커스 + setTimeout(function() { + $('#terminateReasonInput').focus(); + }, 350); + + // 확인 버튼 이벤트 (기존 이벤트 제거 후 재등록) + $('#terminatePopupConfirmButton').off('click').on('click', function () { + const reason = $('#terminateReasonInput').val().trim(); + + if (!reason) { + customPopups.showTerminateError('해지 사유를 입력해주세요.'); + $('#terminateReasonInput').addClass('error').focus(); + return; + } + + if (typeof onConfirm === 'function') { + onConfirm(reason); + } + }); + + // 취소 버튼 이벤트 + $('#terminatePopupCancelButton').off('click').on('click', function () { + customPopups.hideTerminateRequest(); + if (typeof onCancel === 'function') { + onCancel(); + } + }); + + // 닫기 버튼 이벤트 + $('#terminatePopupCloseButton').off('click').on('click', function () { + customPopups.hideTerminateRequest(); + if (typeof onCancel === 'function') { + onCancel(); + } + }); + + // 입력 시 에러 초기화 + $('#terminateReasonInput').off('input').on('input', function () { + $(this).removeClass('error'); + $('#terminatePopupError').removeClass('show').text(''); + }); + }, + + /** + * 해지 신청 팝업 숨기기 + */ + hideTerminateRequest: function () { + // Modal 숨김 애니메이션 + $('#terminateModalBackdrop').removeClass('show'); + $('#terminateModal').removeClass('show'); + + // 애니메이션 완료 후 숨김 + setTimeout(function() { + $('#terminateRequestPopup').hide(); + }, 300); + + // Body 스크롤 복원 + $('body').css('overflow', ''); + + // 입력 필드 초기화 + $('#terminateReasonInput').val('').removeClass('error'); + $('#terminatePopupError').removeClass('show').text(''); + + // 이벤트 리스너 제거 + $('#terminatePopupConfirmButton').off('click'); + $('#terminatePopupCancelButton').off('click'); + $('#terminatePopupCloseButton').off('click'); + $('#terminateReasonInput').off('input'); + }, + + /** + * 해지 신청 팝업 에러 메시지 표시 + * @param {string} message - 에러 메시지 + */ + showTerminateError: function (message) { + $('#terminatePopupError').text(message).addClass('show'); + }, + /** * 확인 팝업 표시 * @param {string} message - 확인 메시지 @@ -355,16 +464,11 @@ const customPopups = { $('#emailValidationPopup').hide(); }); }, - showEmailResendPopup: function (message, loginId) { - $('#emailResendMessage').html(customPopups._sanitizeHtml(message)); - $('#userLoginId').val(loginId); - $('#emailResend').show(); - }, /** * 사용자 초대 팝업 표시 * @param {Object} options - 팝업 옵션 - * @param {Function} options.onConfirm - 확인 버튼 클릭 시 호출되는 콜백 (파라미터: email) + * @param {Function} options.onConfirm - 확인 버튼 클릭 시 호출되는 콜백 (파라미터: mobile, notifyConsent) * @param {Function} options.onCancel - 취소 버튼 클릭 시 호출되는 콜백 (선택사항) */ showUserInvite: function (options) { @@ -374,7 +478,8 @@ const customPopups = { const onCancel = options.onCancel; // 입력 필드 및 에러 초기화 - $('#userInviteEmailInput').val('').removeClass('error'); + $('#userInviteMobileInput').val('').removeClass('error'); + $('#userInviteNotifyConsent').prop('checked', false); $('#userInvitePopupError').removeClass('show').text(''); // 팝업 표시 (modal 구조 사용) @@ -389,28 +494,33 @@ const customPopups = { // 입력 필드에 포커스 setTimeout(function() { - $('#userInviteEmailInput').focus(); + $('#userInviteMobileInput').focus(); }, 350); // 확인 버튼 이벤트 (기존 이벤트 제거 후 재등록) $('#userInvitePopupConfirmButton').off('click').on('click', function () { - const email = $('#userInviteEmailInput').val().trim(); + const mobile = $('#userInviteMobileInput').val().trim(); + const notifyConsent = $('#userInviteNotifyConsent').is(':checked'); - // 이메일 유효성 검사 - if (!email) { - customPopups.showUserInviteError('이메일 주소를 입력해주세요.'); + // 휴대폰 번호 유효성 검사 + if (!mobile) { + customPopups.showUserInviteError('휴대폰 번호를 입력해주세요.'); return; } - // 이메일 형식 검증 - const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - if (!emailPattern.test(email)) { - customPopups.showUserInviteError('올바른 이메일 형식이 아닙니다.'); + // 휴대폰 번호 형식 검증 (하이픈 유무 모두 허용) + const mobilePattern = /^01[016-9]-?\d{3,4}-?\d{4}$/; + if (!mobilePattern.test(mobile)) { + customPopups.showUserInviteError('올바른 휴대폰 번호 형식이 아닙니다.'); return; } + // 하이픈 유무 무관 입력을 저장 표준(010-1234-5678)으로 통일해 전달 + const formattedMobile = mobile.replace(/-/g, '') + .replace(/^(01[016-9])(\d{3,4})(\d{4})$/, '$1-$2-$3'); + if (typeof onConfirm === 'function') { - onConfirm(email); + onConfirm(formattedMobile, notifyConsent); } }); @@ -431,14 +541,14 @@ const customPopups = { }); // Enter 키 이벤트 - $('#userInviteEmailInput').off('keypress').on('keypress', function (e) { + $('#userInviteMobileInput').off('keypress').on('keypress', function (e) { if (e.which === 13) { $('#userInvitePopupConfirmButton').click(); } }); // 입력 시 에러 초기화 - $('#userInviteEmailInput').off('input').on('input', function () { + $('#userInviteMobileInput').off('input').on('input', function () { customPopups.clearUserInviteError(); }); }, @@ -460,14 +570,15 @@ const customPopups = { $('body').css('overflow', ''); // 입력 필드 초기화 - $('#userInviteEmailInput').val('').removeClass('error'); + $('#userInviteMobileInput').val('').removeClass('error'); + $('#userInviteNotifyConsent').prop('checked', false); $('#userInvitePopupError').removeClass('show').text(''); // 이벤트 리스너 제거 $('#userInvitePopupConfirmButton').off('click'); $('#userInvitePopupCancelButton').off('click'); $('#userInvitePopupCloseButton').off('click'); - $('#userInviteEmailInput').off('keypress').off('input'); + $('#userInviteMobileInput').off('keypress').off('input'); }, /** @@ -476,7 +587,7 @@ const customPopups = { */ showUserInviteError: function (message) { $('#userInvitePopupError').text(message).addClass('show'); - $('#userInviteEmailInput').addClass('error'); + $('#userInviteMobileInput').addClass('error'); }, /** @@ -484,7 +595,96 @@ const customPopups = { */ clearUserInviteError: function () { $('#userInvitePopupError').removeClass('show'); - $('#userInviteEmailInput').removeClass('error'); + $('#userInviteMobileInput').removeClass('error'); + }, + + /** + * 초대취소 팝업 표시 (알림 수신 동의 체크박스 포함) + * @param {Object} options - 팝업 옵션 + * @param {string} options.target - 취소 대상 표시 문자열 (마스킹된 이메일/휴대폰) + * @param {Function} options.onConfirm - 확인 버튼 클릭 시 호출되는 콜백 (파라미터: notifyConsent) + * @param {Function} options.onCancel - 취소(닫기) 버튼 클릭 시 호출되는 콜백 (선택사항) + */ + showCancelInvitation: function (options) { + options = options || {}; + + const target = options.target || ''; + const onConfirm = options.onConfirm; + const onCancel = options.onCancel; + + // 대상 정보 및 체크박스 초기화 + $('#cancelInvitationTarget').text(target); + $('#cancelInvitationNotifyConsent').prop('checked', false); + + // 팝업 표시 (modal 구조 사용) + $('#cancelInvitationPopup').show(); + setTimeout(function() { + $('#cancelInvitationModalBackdrop').addClass('show'); + $('#cancelInvitationModal').addClass('show'); + }, 10); + + // Body 스크롤 방지 + $('body').css('overflow', 'hidden'); + + // 확인 버튼 이벤트 (기존 이벤트 제거 후 재등록) + $('#cancelInvitationPopupConfirmButton').off('click').on('click', function () { + const notifyConsent = $('#cancelInvitationNotifyConsent').is(':checked'); + if (typeof onConfirm === 'function') { + onConfirm(notifyConsent); + } + }); + + // 닫기 버튼 이벤트 + $('#cancelInvitationPopupCancelButton').off('click').on('click', function () { + customPopups.hideCancelInvitation(); + if (typeof onCancel === 'function') { + onCancel(); + } + }); + + // X 버튼 이벤트 + $('#cancelInvitationPopupCloseButton').off('click').on('click', function () { + customPopups.hideCancelInvitation(); + if (typeof onCancel === 'function') { + onCancel(); + } + }); + + // Escape 키 이벤트 + $(document).off('keydown.cancelInvitation').on('keydown.cancelInvitation', function (e) { + if (e.key === 'Escape') { + customPopups.hideCancelInvitation(); + if (typeof onCancel === 'function') { + onCancel(); + } + } + }); + }, + + /** + * 초대취소 팝업 숨기기 + */ + hideCancelInvitation: function () { + // Modal 숨김 애니메이션 + $('#cancelInvitationModalBackdrop').removeClass('show'); + $('#cancelInvitationModal').removeClass('show'); + + // 애니메이션 완료 후 숨김 + setTimeout(function() { + $('#cancelInvitationPopup').hide(); + }, 300); + + // Body 스크롤 복원 + $('body').css('overflow', ''); + + // 체크박스 초기화 + $('#cancelInvitationNotifyConsent').prop('checked', false); + + // 이벤트 리스너 제거 + $('#cancelInvitationPopupConfirmButton').off('click'); + $('#cancelInvitationPopupCancelButton').off('click'); + $('#cancelInvitationPopupCloseButton').off('click'); + $(document).off('keydown.cancelInvitation'); }, /** @@ -585,9 +785,36 @@ const customPopups = { $('body').css('overflow', 'hidden'); + // 열 때마다 사유/에러 초기화 + $('#withdrawalReasonInput').val(''); + $('#withdrawalReasonError').hide(); + $('#withdrawalReasonInput').off('input.withdrawal').on('input.withdrawal', function () { + $('#withdrawalReasonError').hide(); + }); + $('#withdrawalPopupConfirmButton').off('click').on('click', function () { + // 탈퇴 사유 필수 입력 + var reason = $.trim($('#withdrawalReasonInput').val() || ''); + if (!reason) { + $('#withdrawalReasonError').show(); + $('#withdrawalReasonInput').focus(); + return; + } + $('#withdrawalReasonHidden').val(reason); + customPopups.hideWithdrawal(); - $('#withdrawalForm').submit(); + + // step-up 2FA 활성 시: 인증 성공 후에만 제출 + var twofaRequired = $('#withdrawalPopup').attr('data-twofa-required') === 'true'; + if (twofaRequired && typeof TwoFactorAuth !== 'undefined') { + TwoFactorAuth.open({ + purpose: '/withdraw', + onSuccess: function () { $('#withdrawalForm').submit(); }, + onCancel: function () { /* 사용자 취소 — 탈퇴 중단 */ } + }); + } else { + $('#withdrawalForm').submit(); + } }); $('#withdrawalPopupCancelButton').off('click').on('click', function () { diff --git a/src/main/resources/static/js/popup/two-factor-auth.js b/src/main/resources/static/js/popup/two-factor-auth.js new file mode 100644 index 0000000..18f47a0 --- /dev/null +++ b/src/main/resources/static/js/popup/two-factor-auth.js @@ -0,0 +1,348 @@ +/* + * 공통 2FA(추가 인증) 팝업 모듈. + * + * TwoFactorAuth.open({ + * mode: 'login' | 'stepup', // 참고용(서버가 세션으로 판별). 로깅/분기용 + * purpose: '/clients/...', // step-up 대상 보호 경로(로그인은 생략) + * onSuccess: function(res){}, // 검증 성공. login 이면 res.redirect 사용 + * onCancel: function(reason){}// 닫기/타임아웃/실패로 종료 + * }); + * + * 수신처는 서버가 세션 대상 사용자로부터 결정한다(클라이언트는 채널만 선택). + * 모든 POST 는 세션 CSRF(meta[name=_csrf]) 헤더를 함께 보낸다. + */ +(function (global) { + 'use strict'; + + var CTX = (function () { + var el = document.querySelector('base'); + return (window.__contextPath !== undefined) ? window.__contextPath : ''; + })(); + + function csrf() { + var t = document.querySelector('meta[name="_csrf"]'); + var h = document.querySelector('meta[name="_csrf_header"]'); + return { + header: h ? h.getAttribute('content') : 'X-XSRF-TOKEN', + token: t ? t.getAttribute('content') : '' + }; + } + + function url(path) { + return CTX + path; + } + + function postForm(path, params) { + var c = csrf(); + var headers = { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }; + if (c.token) { headers[c.header] = c.token; } + var body = Object.keys(params || {}).map(function (k) { + return encodeURIComponent(k) + '=' + encodeURIComponent(params[k]); + }).join('&'); + return fetch(url(path), { + method: 'POST', + headers: headers, + credentials: 'same-origin', + body: body + }).then(function (r) { + // cancel 은 본문이 없다 + return r.status === 200 ? r.text().then(function (t) { return t ? JSON.parse(t) : {}; }) : Promise.reject(r); + }); + } + + function getJson(path) { + return fetch(url(path), { credentials: 'same-origin' }).then(function (r) { return r.json(); }); + } + + var TwoFactorAuth = { + _opts: null, + _timer: null, + _channel: null, + _closing: false, + + open: function (options) { + var self = this; + self._opts = options || {}; + self._closing = false; + var purpose = self._opts.purpose || ''; + + getJson('/auth/2fa/info' + (purpose ? ('?purpose=' + encodeURIComponent(purpose)) : '')) + .then(function (info) { + if (!info || !info.available) { + self._fail('인증 대상 정보가 없습니다. 다시 시도해주세요.'); + return; + } + self._render(info); + self._show(); + if (info.inProgress) { + self._confirmForce(info.message || '진행 중인 다른 인증 절차가 있습니다.'); + } + }) + .catch(function () { + self._fail('추가 인증을 시작할 수 없습니다.'); + }); + }, + + _render: function (info) { + var self = this; + self._ttl = info.ttlSeconds || 180; + self._testNotice = !!info.testNoticeEnabled; + + var seg = document.getElementById('tfaSegment'); + seg.innerHTML = ''; + self._maskedByType = {}; + var channels = info.channels || []; + channels.forEach(function (ch, idx) { + self._maskedByType[ch.type] = ch.masked; + var btn = document.createElement('button'); + btn.type = 'button'; + btn.className = 'tfa-seg-btn' + (idx === 0 ? ' is-active' : ''); + btn.setAttribute('data-channel', ch.type); + btn.innerHTML = '' + (ch.type === 'EMAIL' ? '이메일' : '휴대폰 문자') + '' + + '' + ch.masked + ''; + btn.addEventListener('click', function () { self._selectChannel(ch.type); }); + seg.appendChild(btn); + }); + self._channel = channels.length ? channels[0].type : null; + + // 초기 상태: 발송 단계 + document.getElementById('tfaSendStep').style.display = ''; + document.getElementById('tfaVerifyStep').style.display = 'none'; + self._setMessage('', false); + document.getElementById('tfaCodeInput').value = ''; + document.getElementById('tfaVerifyButton').disabled = true; + self._setStep2Active(false); + self._resetTimerUi(); + var notice = document.getElementById('tfaTestNotice'); + notice.style.display = 'none'; + notice.textContent = ''; + + // 핸들러 바인딩 + document.getElementById('tfaSendButton').onclick = function () { self._send(false); }; + document.getElementById('tfaResendButton').onclick = function () { self._send(false); }; + document.getElementById('tfaVerifyButton').onclick = function () { self._verify(); }; + document.getElementById('tfaCloseButton').onclick = function () { self._cancel('CANCELLED'); }; + document.getElementById('tfaBackdrop').onclick = function () { self._cancel('CANCELLED'); }; + document.getElementById('tfaCodeInput').oninput = function () { + var v = (this.value || '').replace(/\D/g, '').slice(0, 6); + this.value = v; + document.getElementById('tfaVerifyButton').disabled = v.length < 6; + }; + document.getElementById('tfaCodeInput').onkeydown = function (e) { + if (e.key === 'Enter') { self._verify(); } + }; + }, + + _selectChannel: function (type) { + this._channel = type; + var btns = document.querySelectorAll('#tfaSegment .tfa-seg-btn'); + Array.prototype.forEach.call(btns, function (b) { + b.classList.toggle('is-active', b.getAttribute('data-channel') === type); + }); + }, + + _send: function (force) { + var self = this; + if (!self._channel) { return; } + var sendBtn = document.getElementById('tfaSendButton'); + var resendBtn = document.getElementById('tfaResendButton'); + sendBtn.disabled = true; + resendBtn.disabled = true; + + postForm('/auth/2fa/send', { + channel: self._channel, + purpose: self._opts.purpose || '', + force: force ? 'true' : 'false' + }).then(function (res) { + sendBtn.disabled = false; + resendBtn.disabled = false; + if (res.inProgress) { + self._confirmForce(res.message || '진행 중인 다른 인증 절차가 있습니다.'); + return; + } + if (!res.valid) { + self._setMessage(res.message || '인증번호 발송에 실패했습니다.', true); + return; + } + // 발송 성공 → 검증 단계 노출 + 타이머 + document.getElementById('tfaSendStep').style.display = 'none'; + document.getElementById('tfaVerifyStep').style.display = ''; + self._setStep2Active(true); + var masked = self._maskedByType ? (self._maskedByType[self._channel] || '') : ''; + self._setMessage((masked ? masked + ' 으로 ' : '') + '인증번호를 보냈습니다.', false); + document.getElementById('tfaCodeInput').value = ''; + document.getElementById('tfaVerifyButton').disabled = true; + document.getElementById('tfaCodeInput').focus(); + if (self._testNotice && res.testAuthNumber) { + var notice = document.getElementById('tfaTestNotice'); + notice.style.display = ''; + notice.textContent = '[테스트] 인증번호: ' + res.testAuthNumber; + } + self._startTimer(res.ttlSeconds || self._ttl); + }).catch(function () { + sendBtn.disabled = false; + resendBtn.disabled = false; + self._setMessage('인증번호 발송 중 오류가 발생했습니다.', true); + }); + }, + + _confirmForce: function (message) { + var self = this; + var ok = window.confirm(message + '\n강제 종료하고 새로 진행하시겠습니까?'); + if (ok) { + self._send(true); + } + }, + + _verify: function () { + var self = this; + var code = (document.getElementById('tfaCodeInput').value || '').trim(); + if (!/^[0-9]{6}$/.test(code)) { + self._setMessage('6자리 인증번호를 입력해주세요.', true); + return; + } + var btn = document.getElementById('tfaVerifyButton'); + btn.disabled = true; + postForm('/auth/2fa/verify', { code: code }).then(function (res) { + btn.disabled = false; + if (res.valid) { + self._stopTimer(); + self._closing = true; + self._hide(); + if (typeof self._opts.onSuccess === 'function') { + self._opts.onSuccess(res); + } + return; + } + if (res.terminated) { + self._stopTimer(); + self._fail(res.message || '인증에 실패했습니다. 처음부터 다시 진행해주세요.'); + return; + } + self._setMessage(res.message || '인증번호가 일치하지 않습니다.', true); + }).catch(function () { + btn.disabled = false; + self._setMessage('인증 처리 중 오류가 발생했습니다.', true); + }); + }, + + _cancel: function (reason) { + var self = this; + if (self._closing) { return; } + self._closing = true; + self._stopTimer(); + postForm('/auth/2fa/cancel', { reason: reason }).catch(function () {}).then(function () { + self._hide(); + if (typeof self._opts.onCancel === 'function') { + self._opts.onCancel(reason); + } + }); + }, + + _fmtClock: function (n) { + var m = Math.floor(n / 60); + var s = n % 60; + return m + ':' + (s < 10 ? '0' + s : s); + }, + + _setStep2Active: function (active) { + var num = document.getElementById('tfaStepNum'); + var text = document.getElementById('tfaStepText'); + if (num) { num.classList.toggle('is-active', active); } + if (text) { text.classList.toggle('is-active', active); } + }, + + _resetTimerUi: function () { + var base = this._ttl || 180; + var clock = document.getElementById('tfaTimer'); + var bar = document.getElementById('tfaProgressBar'); + var label = document.getElementById('tfaTimerLabel'); + var total = document.getElementById('tfaTimerTotal'); + if (clock) { clock.className = 'tfa-timer'; clock.textContent = this._fmtClock(base); } + if (bar) { bar.className = 'tfa-progress-bar'; bar.style.width = '100%'; } + if (label) { label.textContent = '남은 인증 시간'; } + if (total) { total.textContent = '/ ' + this._fmtClock(base); } + }, + + _startTimer: function (seconds) { + var self = this; + self._stopTimer(); + var total = seconds || self._ttl || 180; + var remaining = seconds; + var clock = document.getElementById('tfaTimer'); + var bar = document.getElementById('tfaProgressBar'); + var label = document.getElementById('tfaTimerLabel'); + var totalEl = document.getElementById('tfaTimerTotal'); + if (label) { label.textContent = '남은 인증 시간'; } + if (totalEl) { totalEl.textContent = '/ ' + self._fmtClock(total); } + function tick() { + if (remaining <= 0) { + self._stopTimer(); + if (clock) { clock.textContent = '0:00'; clock.className = 'tfa-timer is-expired'; } + if (bar) { bar.style.width = '0%'; } + if (label) { label.textContent = '인증 시간이 만료되었습니다. 재전송해 주세요.'; } + self._cancel('TIMEOUT'); + return; + } + var warn = remaining <= 30; + if (clock) { + clock.textContent = self._fmtClock(remaining); + clock.className = 'tfa-timer' + (warn ? ' is-warning' : ''); + } + if (bar) { + bar.style.width = Math.round((remaining / total) * 100) + '%'; + bar.className = 'tfa-progress-bar' + (warn ? ' is-warning' : ''); + } + remaining--; + } + tick(); + self._timer = setInterval(tick, 1000); + }, + + _stopTimer: function () { + if (this._timer) { + clearInterval(this._timer); + this._timer = null; + } + }, + + _setMessage: function (msg, isError) { + var el = document.getElementById('tfaMessage'); + el.textContent = msg || ''; + el.className = 'tfa-message' + (isError ? ' is-error' : ''); + }, + + _fail: function (message) { + var self = this; + self._stopTimer(); + self._hide(); + if (typeof window.customPopups !== 'undefined' && customPopups.showAlert) { + customPopups.showAlert(message); + } else if (message) { + window.alert(message); + } + if (typeof self._opts.onCancel === 'function') { + self._opts.onCancel('FAILED'); + } + }, + + _show: function () { + // 모달은 컨테이너 display + backdrop/modal 의 .show 클래스(visibility/opacity) 둘 다 필요 + document.getElementById('tfaPopup').style.display = 'block'; + var bd = document.getElementById('tfaBackdrop'); + var md = document.getElementById('tfaModal'); + if (bd) bd.classList.add('show'); + if (md) md.classList.add('show'); + }, + + _hide: function () { + var bd = document.getElementById('tfaBackdrop'); + var md = document.getElementById('tfaModal'); + if (bd) bd.classList.remove('show'); + if (md) md.classList.remove('show'); + document.getElementById('tfaPopup').style.display = 'none'; + } + }; + + global.TwoFactorAuth = TwoFactorAuth; +})(window); diff --git a/src/main/resources/static/plugins/swaggerUI/djb-swagger-i18n.js b/src/main/resources/static/plugins/swaggerUI/djb-swagger-i18n.js new file mode 100644 index 0000000..426c399 --- /dev/null +++ b/src/main/resources/static/plugins/swaggerUI/djb-swagger-i18n.js @@ -0,0 +1,161 @@ +/*! + * djb-swagger-i18n.js + * Swagger UI 영문 라벨을 한글로 치환하는 i18n 패치 (방법 B - DOM patch) + * 배포 경로 권장: static/plugins/swaggerUI/djb-swagger-i18n.js + * index.html 의 swagger-initializer.js 직전 또는 직후에 + diff --git a/src/main/resources/static/plugins/swaggerUI/swagger-ui-bundle.js b/src/main/resources/static/plugins/swaggerUI/swagger-ui-bundle.js index 551e172..a80cbc2 100644 --- a/src/main/resources/static/plugins/swaggerUI/swagger-ui-bundle.js +++ b/src/main/resources/static/plugins/swaggerUI/swagger-ui-bundle.js @@ -1,2 +1,2 @@ /*! For license information please see swagger-ui-bundle.js.LICENSE.txt */ -!function webpackUniversalModuleDefinition(o,s){"object"==typeof exports&&"object"==typeof module?module.exports=s():"function"==typeof define&&define.amd?define([],s):"object"==typeof exports?exports.SwaggerUIBundle=s():o.SwaggerUIBundle=s()}(this,(()=>(()=>{var o,s,i={69119:(o,s)=>{"use strict";Object.defineProperty(s,"__esModule",{value:!0}),s.BLANK_URL=s.relativeFirstCharacters=s.whitespaceEscapeCharsRegex=s.urlSchemeRegex=s.ctrlCharactersRegex=s.htmlCtrlEntityRegex=s.htmlEntitiesRegex=s.invalidProtocolRegex=void 0,s.invalidProtocolRegex=/^([^\w]*)(javascript|data|vbscript)/im,s.htmlEntitiesRegex=/&#(\w+)(^\w|;)?/g,s.htmlCtrlEntityRegex=/&(newline|tab);/gi,s.ctrlCharactersRegex=/[\u0000-\u001F\u007F-\u009F\u2000-\u200D\uFEFF]/gim,s.urlSchemeRegex=/^.+(:|:)/gim,s.whitespaceEscapeCharsRegex=/(\\|%5[cC])((%(6[eE]|72|74))|[nrt])/g,s.relativeFirstCharacters=[".","/"],s.BLANK_URL="about:blank"},16750:(o,s,i)=>{"use strict";s.J=void 0;var u=i(69119);function decodeURI(o){try{return decodeURIComponent(o)}catch(s){return o}}s.J=function sanitizeUrl(o){if(!o)return u.BLANK_URL;var s,i,_=decodeURI(o);do{s=(_=decodeURI(_=(i=_,i.replace(u.ctrlCharactersRegex,"").replace(u.htmlEntitiesRegex,(function(o,s){return String.fromCharCode(s)}))).replace(u.htmlCtrlEntityRegex,"").replace(u.ctrlCharactersRegex,"").replace(u.whitespaceEscapeCharsRegex,"").trim())).match(u.ctrlCharactersRegex)||_.match(u.htmlEntitiesRegex)||_.match(u.htmlCtrlEntityRegex)||_.match(u.whitespaceEscapeCharsRegex)}while(s&&s.length>0);var w=_;if(!w)return u.BLANK_URL;if(function isRelativeUrlWithoutProtocol(o){return u.relativeFirstCharacters.indexOf(o[0])>-1}(w))return w;var x=w.match(u.urlSchemeRegex);if(!x)return w;var C=x[0];return u.invalidProtocolRegex.test(C)?u.BLANK_URL:w}},67526:(o,s)=>{"use strict";s.byteLength=function byteLength(o){var s=getLens(o),i=s[0],u=s[1];return 3*(i+u)/4-u},s.toByteArray=function toByteArray(o){var s,i,w=getLens(o),x=w[0],C=w[1],j=new _(function _byteLength(o,s,i){return 3*(s+i)/4-i}(0,x,C)),L=0,B=C>0?x-4:x;for(i=0;i>16&255,j[L++]=s>>8&255,j[L++]=255&s;2===C&&(s=u[o.charCodeAt(i)]<<2|u[o.charCodeAt(i+1)]>>4,j[L++]=255&s);1===C&&(s=u[o.charCodeAt(i)]<<10|u[o.charCodeAt(i+1)]<<4|u[o.charCodeAt(i+2)]>>2,j[L++]=s>>8&255,j[L++]=255&s);return j},s.fromByteArray=function fromByteArray(o){for(var s,u=o.length,_=u%3,w=[],x=16383,C=0,j=u-_;Cj?j:C+x));1===_?(s=o[u-1],w.push(i[s>>2]+i[s<<4&63]+"==")):2===_&&(s=(o[u-2]<<8)+o[u-1],w.push(i[s>>10]+i[s>>4&63]+i[s<<2&63]+"="));return w.join("")};for(var i=[],u=[],_="undefined"!=typeof Uint8Array?Uint8Array:Array,w="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",x=0;x<64;++x)i[x]=w[x],u[w.charCodeAt(x)]=x;function getLens(o){var s=o.length;if(s%4>0)throw new Error("Invalid string. Length must be a multiple of 4");var i=o.indexOf("=");return-1===i&&(i=s),[i,i===s?0:4-i%4]}function encodeChunk(o,s,u){for(var _,w,x=[],C=s;C>18&63]+i[w>>12&63]+i[w>>6&63]+i[63&w]);return x.join("")}u["-".charCodeAt(0)]=62,u["_".charCodeAt(0)]=63},48287:(o,s,i)=>{"use strict";const u=i(67526),_=i(251),w="function"==typeof Symbol&&"function"==typeof Symbol.for?Symbol.for("nodejs.util.inspect.custom"):null;s.Buffer=Buffer,s.SlowBuffer=function SlowBuffer(o){+o!=o&&(o=0);return Buffer.alloc(+o)},s.INSPECT_MAX_BYTES=50;const x=2147483647;function createBuffer(o){if(o>x)throw new RangeError('The value "'+o+'" is invalid for option "size"');const s=new Uint8Array(o);return Object.setPrototypeOf(s,Buffer.prototype),s}function Buffer(o,s,i){if("number"==typeof o){if("string"==typeof s)throw new TypeError('The "string" argument must be of type string. Received type number');return allocUnsafe(o)}return from(o,s,i)}function from(o,s,i){if("string"==typeof o)return function fromString(o,s){"string"==typeof s&&""!==s||(s="utf8");if(!Buffer.isEncoding(s))throw new TypeError("Unknown encoding: "+s);const i=0|byteLength(o,s);let u=createBuffer(i);const _=u.write(o,s);_!==i&&(u=u.slice(0,_));return u}(o,s);if(ArrayBuffer.isView(o))return function fromArrayView(o){if(isInstance(o,Uint8Array)){const s=new Uint8Array(o);return fromArrayBuffer(s.buffer,s.byteOffset,s.byteLength)}return fromArrayLike(o)}(o);if(null==o)throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof o);if(isInstance(o,ArrayBuffer)||o&&isInstance(o.buffer,ArrayBuffer))return fromArrayBuffer(o,s,i);if("undefined"!=typeof SharedArrayBuffer&&(isInstance(o,SharedArrayBuffer)||o&&isInstance(o.buffer,SharedArrayBuffer)))return fromArrayBuffer(o,s,i);if("number"==typeof o)throw new TypeError('The "value" argument must not be of type number. Received type number');const u=o.valueOf&&o.valueOf();if(null!=u&&u!==o)return Buffer.from(u,s,i);const _=function fromObject(o){if(Buffer.isBuffer(o)){const s=0|checked(o.length),i=createBuffer(s);return 0===i.length||o.copy(i,0,0,s),i}if(void 0!==o.length)return"number"!=typeof o.length||numberIsNaN(o.length)?createBuffer(0):fromArrayLike(o);if("Buffer"===o.type&&Array.isArray(o.data))return fromArrayLike(o.data)}(o);if(_)return _;if("undefined"!=typeof Symbol&&null!=Symbol.toPrimitive&&"function"==typeof o[Symbol.toPrimitive])return Buffer.from(o[Symbol.toPrimitive]("string"),s,i);throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof o)}function assertSize(o){if("number"!=typeof o)throw new TypeError('"size" argument must be of type number');if(o<0)throw new RangeError('The value "'+o+'" is invalid for option "size"')}function allocUnsafe(o){return assertSize(o),createBuffer(o<0?0:0|checked(o))}function fromArrayLike(o){const s=o.length<0?0:0|checked(o.length),i=createBuffer(s);for(let u=0;u=x)throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+x.toString(16)+" bytes");return 0|o}function byteLength(o,s){if(Buffer.isBuffer(o))return o.length;if(ArrayBuffer.isView(o)||isInstance(o,ArrayBuffer))return o.byteLength;if("string"!=typeof o)throw new TypeError('The "string" argument must be one of type string, Buffer, or ArrayBuffer. Received type '+typeof o);const i=o.length,u=arguments.length>2&&!0===arguments[2];if(!u&&0===i)return 0;let _=!1;for(;;)switch(s){case"ascii":case"latin1":case"binary":return i;case"utf8":case"utf-8":return utf8ToBytes(o).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*i;case"hex":return i>>>1;case"base64":return base64ToBytes(o).length;default:if(_)return u?-1:utf8ToBytes(o).length;s=(""+s).toLowerCase(),_=!0}}function slowToString(o,s,i){let u=!1;if((void 0===s||s<0)&&(s=0),s>this.length)return"";if((void 0===i||i>this.length)&&(i=this.length),i<=0)return"";if((i>>>=0)<=(s>>>=0))return"";for(o||(o="utf8");;)switch(o){case"hex":return hexSlice(this,s,i);case"utf8":case"utf-8":return utf8Slice(this,s,i);case"ascii":return asciiSlice(this,s,i);case"latin1":case"binary":return latin1Slice(this,s,i);case"base64":return base64Slice(this,s,i);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return utf16leSlice(this,s,i);default:if(u)throw new TypeError("Unknown encoding: "+o);o=(o+"").toLowerCase(),u=!0}}function swap(o,s,i){const u=o[s];o[s]=o[i],o[i]=u}function bidirectionalIndexOf(o,s,i,u,_){if(0===o.length)return-1;if("string"==typeof i?(u=i,i=0):i>2147483647?i=2147483647:i<-2147483648&&(i=-2147483648),numberIsNaN(i=+i)&&(i=_?0:o.length-1),i<0&&(i=o.length+i),i>=o.length){if(_)return-1;i=o.length-1}else if(i<0){if(!_)return-1;i=0}if("string"==typeof s&&(s=Buffer.from(s,u)),Buffer.isBuffer(s))return 0===s.length?-1:arrayIndexOf(o,s,i,u,_);if("number"==typeof s)return s&=255,"function"==typeof Uint8Array.prototype.indexOf?_?Uint8Array.prototype.indexOf.call(o,s,i):Uint8Array.prototype.lastIndexOf.call(o,s,i):arrayIndexOf(o,[s],i,u,_);throw new TypeError("val must be string, number or Buffer")}function arrayIndexOf(o,s,i,u,_){let w,x=1,C=o.length,j=s.length;if(void 0!==u&&("ucs2"===(u=String(u).toLowerCase())||"ucs-2"===u||"utf16le"===u||"utf-16le"===u)){if(o.length<2||s.length<2)return-1;x=2,C/=2,j/=2,i/=2}function read(o,s){return 1===x?o[s]:o.readUInt16BE(s*x)}if(_){let u=-1;for(w=i;wC&&(i=C-j),w=i;w>=0;w--){let i=!0;for(let u=0;u_&&(u=_):u=_;const w=s.length;let x;for(u>w/2&&(u=w/2),x=0;x>8,_=i%256,w.push(_),w.push(u);return w}(s,o.length-i),o,i,u)}function base64Slice(o,s,i){return 0===s&&i===o.length?u.fromByteArray(o):u.fromByteArray(o.slice(s,i))}function utf8Slice(o,s,i){i=Math.min(o.length,i);const u=[];let _=s;for(;_239?4:s>223?3:s>191?2:1;if(_+x<=i){let i,u,C,j;switch(x){case 1:s<128&&(w=s);break;case 2:i=o[_+1],128==(192&i)&&(j=(31&s)<<6|63&i,j>127&&(w=j));break;case 3:i=o[_+1],u=o[_+2],128==(192&i)&&128==(192&u)&&(j=(15&s)<<12|(63&i)<<6|63&u,j>2047&&(j<55296||j>57343)&&(w=j));break;case 4:i=o[_+1],u=o[_+2],C=o[_+3],128==(192&i)&&128==(192&u)&&128==(192&C)&&(j=(15&s)<<18|(63&i)<<12|(63&u)<<6|63&C,j>65535&&j<1114112&&(w=j))}}null===w?(w=65533,x=1):w>65535&&(w-=65536,u.push(w>>>10&1023|55296),w=56320|1023&w),u.push(w),_+=x}return function decodeCodePointsArray(o){const s=o.length;if(s<=C)return String.fromCharCode.apply(String,o);let i="",u=0;for(;uu.length?(Buffer.isBuffer(s)||(s=Buffer.from(s)),s.copy(u,_)):Uint8Array.prototype.set.call(u,s,_);else{if(!Buffer.isBuffer(s))throw new TypeError('"list" argument must be an Array of Buffers');s.copy(u,_)}_+=s.length}return u},Buffer.byteLength=byteLength,Buffer.prototype._isBuffer=!0,Buffer.prototype.swap16=function swap16(){const o=this.length;if(o%2!=0)throw new RangeError("Buffer size must be a multiple of 16-bits");for(let s=0;si&&(o+=" ... "),""},w&&(Buffer.prototype[w]=Buffer.prototype.inspect),Buffer.prototype.compare=function compare(o,s,i,u,_){if(isInstance(o,Uint8Array)&&(o=Buffer.from(o,o.offset,o.byteLength)),!Buffer.isBuffer(o))throw new TypeError('The "target" argument must be one of type Buffer or Uint8Array. Received type '+typeof o);if(void 0===s&&(s=0),void 0===i&&(i=o?o.length:0),void 0===u&&(u=0),void 0===_&&(_=this.length),s<0||i>o.length||u<0||_>this.length)throw new RangeError("out of range index");if(u>=_&&s>=i)return 0;if(u>=_)return-1;if(s>=i)return 1;if(this===o)return 0;let w=(_>>>=0)-(u>>>=0),x=(i>>>=0)-(s>>>=0);const C=Math.min(w,x),j=this.slice(u,_),L=o.slice(s,i);for(let o=0;o>>=0,isFinite(i)?(i>>>=0,void 0===u&&(u="utf8")):(u=i,i=void 0)}const _=this.length-s;if((void 0===i||i>_)&&(i=_),o.length>0&&(i<0||s<0)||s>this.length)throw new RangeError("Attempt to write outside buffer bounds");u||(u="utf8");let w=!1;for(;;)switch(u){case"hex":return hexWrite(this,o,s,i);case"utf8":case"utf-8":return utf8Write(this,o,s,i);case"ascii":case"latin1":case"binary":return asciiWrite(this,o,s,i);case"base64":return base64Write(this,o,s,i);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return ucs2Write(this,o,s,i);default:if(w)throw new TypeError("Unknown encoding: "+u);u=(""+u).toLowerCase(),w=!0}},Buffer.prototype.toJSON=function toJSON(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};const C=4096;function asciiSlice(o,s,i){let u="";i=Math.min(o.length,i);for(let _=s;_u)&&(i=u);let _="";for(let u=s;ui)throw new RangeError("Trying to access beyond buffer length")}function checkInt(o,s,i,u,_,w){if(!Buffer.isBuffer(o))throw new TypeError('"buffer" argument must be a Buffer instance');if(s>_||so.length)throw new RangeError("Index out of range")}function wrtBigUInt64LE(o,s,i,u,_){checkIntBI(s,u,_,o,i,7);let w=Number(s&BigInt(4294967295));o[i++]=w,w>>=8,o[i++]=w,w>>=8,o[i++]=w,w>>=8,o[i++]=w;let x=Number(s>>BigInt(32)&BigInt(4294967295));return o[i++]=x,x>>=8,o[i++]=x,x>>=8,o[i++]=x,x>>=8,o[i++]=x,i}function wrtBigUInt64BE(o,s,i,u,_){checkIntBI(s,u,_,o,i,7);let w=Number(s&BigInt(4294967295));o[i+7]=w,w>>=8,o[i+6]=w,w>>=8,o[i+5]=w,w>>=8,o[i+4]=w;let x=Number(s>>BigInt(32)&BigInt(4294967295));return o[i+3]=x,x>>=8,o[i+2]=x,x>>=8,o[i+1]=x,x>>=8,o[i]=x,i+8}function checkIEEE754(o,s,i,u,_,w){if(i+u>o.length)throw new RangeError("Index out of range");if(i<0)throw new RangeError("Index out of range")}function writeFloat(o,s,i,u,w){return s=+s,i>>>=0,w||checkIEEE754(o,0,i,4),_.write(o,s,i,u,23,4),i+4}function writeDouble(o,s,i,u,w){return s=+s,i>>>=0,w||checkIEEE754(o,0,i,8),_.write(o,s,i,u,52,8),i+8}Buffer.prototype.slice=function slice(o,s){const i=this.length;(o=~~o)<0?(o+=i)<0&&(o=0):o>i&&(o=i),(s=void 0===s?i:~~s)<0?(s+=i)<0&&(s=0):s>i&&(s=i),s>>=0,s>>>=0,i||checkOffset(o,s,this.length);let u=this[o],_=1,w=0;for(;++w>>=0,s>>>=0,i||checkOffset(o,s,this.length);let u=this[o+--s],_=1;for(;s>0&&(_*=256);)u+=this[o+--s]*_;return u},Buffer.prototype.readUint8=Buffer.prototype.readUInt8=function readUInt8(o,s){return o>>>=0,s||checkOffset(o,1,this.length),this[o]},Buffer.prototype.readUint16LE=Buffer.prototype.readUInt16LE=function readUInt16LE(o,s){return o>>>=0,s||checkOffset(o,2,this.length),this[o]|this[o+1]<<8},Buffer.prototype.readUint16BE=Buffer.prototype.readUInt16BE=function readUInt16BE(o,s){return o>>>=0,s||checkOffset(o,2,this.length),this[o]<<8|this[o+1]},Buffer.prototype.readUint32LE=Buffer.prototype.readUInt32LE=function readUInt32LE(o,s){return o>>>=0,s||checkOffset(o,4,this.length),(this[o]|this[o+1]<<8|this[o+2]<<16)+16777216*this[o+3]},Buffer.prototype.readUint32BE=Buffer.prototype.readUInt32BE=function readUInt32BE(o,s){return o>>>=0,s||checkOffset(o,4,this.length),16777216*this[o]+(this[o+1]<<16|this[o+2]<<8|this[o+3])},Buffer.prototype.readBigUInt64LE=defineBigIntMethod((function readBigUInt64LE(o){validateNumber(o>>>=0,"offset");const s=this[o],i=this[o+7];void 0!==s&&void 0!==i||boundsError(o,this.length-8);const u=s+256*this[++o]+65536*this[++o]+this[++o]*2**24,_=this[++o]+256*this[++o]+65536*this[++o]+i*2**24;return BigInt(u)+(BigInt(_)<>>=0,"offset");const s=this[o],i=this[o+7];void 0!==s&&void 0!==i||boundsError(o,this.length-8);const u=s*2**24+65536*this[++o]+256*this[++o]+this[++o],_=this[++o]*2**24+65536*this[++o]+256*this[++o]+i;return(BigInt(u)<>>=0,s>>>=0,i||checkOffset(o,s,this.length);let u=this[o],_=1,w=0;for(;++w=_&&(u-=Math.pow(2,8*s)),u},Buffer.prototype.readIntBE=function readIntBE(o,s,i){o>>>=0,s>>>=0,i||checkOffset(o,s,this.length);let u=s,_=1,w=this[o+--u];for(;u>0&&(_*=256);)w+=this[o+--u]*_;return _*=128,w>=_&&(w-=Math.pow(2,8*s)),w},Buffer.prototype.readInt8=function readInt8(o,s){return o>>>=0,s||checkOffset(o,1,this.length),128&this[o]?-1*(255-this[o]+1):this[o]},Buffer.prototype.readInt16LE=function readInt16LE(o,s){o>>>=0,s||checkOffset(o,2,this.length);const i=this[o]|this[o+1]<<8;return 32768&i?4294901760|i:i},Buffer.prototype.readInt16BE=function readInt16BE(o,s){o>>>=0,s||checkOffset(o,2,this.length);const i=this[o+1]|this[o]<<8;return 32768&i?4294901760|i:i},Buffer.prototype.readInt32LE=function readInt32LE(o,s){return o>>>=0,s||checkOffset(o,4,this.length),this[o]|this[o+1]<<8|this[o+2]<<16|this[o+3]<<24},Buffer.prototype.readInt32BE=function readInt32BE(o,s){return o>>>=0,s||checkOffset(o,4,this.length),this[o]<<24|this[o+1]<<16|this[o+2]<<8|this[o+3]},Buffer.prototype.readBigInt64LE=defineBigIntMethod((function readBigInt64LE(o){validateNumber(o>>>=0,"offset");const s=this[o],i=this[o+7];void 0!==s&&void 0!==i||boundsError(o,this.length-8);const u=this[o+4]+256*this[o+5]+65536*this[o+6]+(i<<24);return(BigInt(u)<>>=0,"offset");const s=this[o],i=this[o+7];void 0!==s&&void 0!==i||boundsError(o,this.length-8);const u=(s<<24)+65536*this[++o]+256*this[++o]+this[++o];return(BigInt(u)<>>=0,s||checkOffset(o,4,this.length),_.read(this,o,!0,23,4)},Buffer.prototype.readFloatBE=function readFloatBE(o,s){return o>>>=0,s||checkOffset(o,4,this.length),_.read(this,o,!1,23,4)},Buffer.prototype.readDoubleLE=function readDoubleLE(o,s){return o>>>=0,s||checkOffset(o,8,this.length),_.read(this,o,!0,52,8)},Buffer.prototype.readDoubleBE=function readDoubleBE(o,s){return o>>>=0,s||checkOffset(o,8,this.length),_.read(this,o,!1,52,8)},Buffer.prototype.writeUintLE=Buffer.prototype.writeUIntLE=function writeUIntLE(o,s,i,u){if(o=+o,s>>>=0,i>>>=0,!u){checkInt(this,o,s,i,Math.pow(2,8*i)-1,0)}let _=1,w=0;for(this[s]=255&o;++w>>=0,i>>>=0,!u){checkInt(this,o,s,i,Math.pow(2,8*i)-1,0)}let _=i-1,w=1;for(this[s+_]=255&o;--_>=0&&(w*=256);)this[s+_]=o/w&255;return s+i},Buffer.prototype.writeUint8=Buffer.prototype.writeUInt8=function writeUInt8(o,s,i){return o=+o,s>>>=0,i||checkInt(this,o,s,1,255,0),this[s]=255&o,s+1},Buffer.prototype.writeUint16LE=Buffer.prototype.writeUInt16LE=function writeUInt16LE(o,s,i){return o=+o,s>>>=0,i||checkInt(this,o,s,2,65535,0),this[s]=255&o,this[s+1]=o>>>8,s+2},Buffer.prototype.writeUint16BE=Buffer.prototype.writeUInt16BE=function writeUInt16BE(o,s,i){return o=+o,s>>>=0,i||checkInt(this,o,s,2,65535,0),this[s]=o>>>8,this[s+1]=255&o,s+2},Buffer.prototype.writeUint32LE=Buffer.prototype.writeUInt32LE=function writeUInt32LE(o,s,i){return o=+o,s>>>=0,i||checkInt(this,o,s,4,4294967295,0),this[s+3]=o>>>24,this[s+2]=o>>>16,this[s+1]=o>>>8,this[s]=255&o,s+4},Buffer.prototype.writeUint32BE=Buffer.prototype.writeUInt32BE=function writeUInt32BE(o,s,i){return o=+o,s>>>=0,i||checkInt(this,o,s,4,4294967295,0),this[s]=o>>>24,this[s+1]=o>>>16,this[s+2]=o>>>8,this[s+3]=255&o,s+4},Buffer.prototype.writeBigUInt64LE=defineBigIntMethod((function writeBigUInt64LE(o,s=0){return wrtBigUInt64LE(this,o,s,BigInt(0),BigInt("0xffffffffffffffff"))})),Buffer.prototype.writeBigUInt64BE=defineBigIntMethod((function writeBigUInt64BE(o,s=0){return wrtBigUInt64BE(this,o,s,BigInt(0),BigInt("0xffffffffffffffff"))})),Buffer.prototype.writeIntLE=function writeIntLE(o,s,i,u){if(o=+o,s>>>=0,!u){const u=Math.pow(2,8*i-1);checkInt(this,o,s,i,u-1,-u)}let _=0,w=1,x=0;for(this[s]=255&o;++_>>=0,!u){const u=Math.pow(2,8*i-1);checkInt(this,o,s,i,u-1,-u)}let _=i-1,w=1,x=0;for(this[s+_]=255&o;--_>=0&&(w*=256);)o<0&&0===x&&0!==this[s+_+1]&&(x=1),this[s+_]=(o/w|0)-x&255;return s+i},Buffer.prototype.writeInt8=function writeInt8(o,s,i){return o=+o,s>>>=0,i||checkInt(this,o,s,1,127,-128),o<0&&(o=255+o+1),this[s]=255&o,s+1},Buffer.prototype.writeInt16LE=function writeInt16LE(o,s,i){return o=+o,s>>>=0,i||checkInt(this,o,s,2,32767,-32768),this[s]=255&o,this[s+1]=o>>>8,s+2},Buffer.prototype.writeInt16BE=function writeInt16BE(o,s,i){return o=+o,s>>>=0,i||checkInt(this,o,s,2,32767,-32768),this[s]=o>>>8,this[s+1]=255&o,s+2},Buffer.prototype.writeInt32LE=function writeInt32LE(o,s,i){return o=+o,s>>>=0,i||checkInt(this,o,s,4,2147483647,-2147483648),this[s]=255&o,this[s+1]=o>>>8,this[s+2]=o>>>16,this[s+3]=o>>>24,s+4},Buffer.prototype.writeInt32BE=function writeInt32BE(o,s,i){return o=+o,s>>>=0,i||checkInt(this,o,s,4,2147483647,-2147483648),o<0&&(o=4294967295+o+1),this[s]=o>>>24,this[s+1]=o>>>16,this[s+2]=o>>>8,this[s+3]=255&o,s+4},Buffer.prototype.writeBigInt64LE=defineBigIntMethod((function writeBigInt64LE(o,s=0){return wrtBigUInt64LE(this,o,s,-BigInt("0x8000000000000000"),BigInt("0x7fffffffffffffff"))})),Buffer.prototype.writeBigInt64BE=defineBigIntMethod((function writeBigInt64BE(o,s=0){return wrtBigUInt64BE(this,o,s,-BigInt("0x8000000000000000"),BigInt("0x7fffffffffffffff"))})),Buffer.prototype.writeFloatLE=function writeFloatLE(o,s,i){return writeFloat(this,o,s,!0,i)},Buffer.prototype.writeFloatBE=function writeFloatBE(o,s,i){return writeFloat(this,o,s,!1,i)},Buffer.prototype.writeDoubleLE=function writeDoubleLE(o,s,i){return writeDouble(this,o,s,!0,i)},Buffer.prototype.writeDoubleBE=function writeDoubleBE(o,s,i){return writeDouble(this,o,s,!1,i)},Buffer.prototype.copy=function copy(o,s,i,u){if(!Buffer.isBuffer(o))throw new TypeError("argument should be a Buffer");if(i||(i=0),u||0===u||(u=this.length),s>=o.length&&(s=o.length),s||(s=0),u>0&&u=this.length)throw new RangeError("Index out of range");if(u<0)throw new RangeError("sourceEnd out of bounds");u>this.length&&(u=this.length),o.length-s>>=0,i=void 0===i?this.length:i>>>0,o||(o=0),"number"==typeof o)for(_=s;_=u+4;i-=3)s=`_${o.slice(i-3,i)}${s}`;return`${o.slice(0,i)}${s}`}function checkIntBI(o,s,i,u,_,w){if(o>i||o3?0===s||s===BigInt(0)?`>= 0${u} and < 2${u} ** ${8*(w+1)}${u}`:`>= -(2${u} ** ${8*(w+1)-1}${u}) and < 2 ** ${8*(w+1)-1}${u}`:`>= ${s}${u} and <= ${i}${u}`,new j.ERR_OUT_OF_RANGE("value",_,o)}!function checkBounds(o,s,i){validateNumber(s,"offset"),void 0!==o[s]&&void 0!==o[s+i]||boundsError(s,o.length-(i+1))}(u,_,w)}function validateNumber(o,s){if("number"!=typeof o)throw new j.ERR_INVALID_ARG_TYPE(s,"number",o)}function boundsError(o,s,i){if(Math.floor(o)!==o)throw validateNumber(o,i),new j.ERR_OUT_OF_RANGE(i||"offset","an integer",o);if(s<0)throw new j.ERR_BUFFER_OUT_OF_BOUNDS;throw new j.ERR_OUT_OF_RANGE(i||"offset",`>= ${i?1:0} and <= ${s}`,o)}E("ERR_BUFFER_OUT_OF_BOUNDS",(function(o){return o?`${o} is outside of buffer bounds`:"Attempt to access memory outside buffer bounds"}),RangeError),E("ERR_INVALID_ARG_TYPE",(function(o,s){return`The "${o}" argument must be of type number. Received type ${typeof s}`}),TypeError),E("ERR_OUT_OF_RANGE",(function(o,s,i){let u=`The value of "${o}" is out of range.`,_=i;return Number.isInteger(i)&&Math.abs(i)>2**32?_=addNumericalSeparator(String(i)):"bigint"==typeof i&&(_=String(i),(i>BigInt(2)**BigInt(32)||i<-(BigInt(2)**BigInt(32)))&&(_=addNumericalSeparator(_)),_+="n"),u+=` It must be ${s}. Received ${_}`,u}),RangeError);const L=/[^+/0-9A-Za-z-_]/g;function utf8ToBytes(o,s){let i;s=s||1/0;const u=o.length;let _=null;const w=[];for(let x=0;x55295&&i<57344){if(!_){if(i>56319){(s-=3)>-1&&w.push(239,191,189);continue}if(x+1===u){(s-=3)>-1&&w.push(239,191,189);continue}_=i;continue}if(i<56320){(s-=3)>-1&&w.push(239,191,189),_=i;continue}i=65536+(_-55296<<10|i-56320)}else _&&(s-=3)>-1&&w.push(239,191,189);if(_=null,i<128){if((s-=1)<0)break;w.push(i)}else if(i<2048){if((s-=2)<0)break;w.push(i>>6|192,63&i|128)}else if(i<65536){if((s-=3)<0)break;w.push(i>>12|224,i>>6&63|128,63&i|128)}else{if(!(i<1114112))throw new Error("Invalid code point");if((s-=4)<0)break;w.push(i>>18|240,i>>12&63|128,i>>6&63|128,63&i|128)}}return w}function base64ToBytes(o){return u.toByteArray(function base64clean(o){if((o=(o=o.split("=")[0]).trim().replace(L,"")).length<2)return"";for(;o.length%4!=0;)o+="=";return o}(o))}function blitBuffer(o,s,i,u){let _;for(_=0;_=s.length||_>=o.length);++_)s[_+i]=o[_];return _}function isInstance(o,s){return o instanceof s||null!=o&&null!=o.constructor&&null!=o.constructor.name&&o.constructor.name===s.name}function numberIsNaN(o){return o!=o}const B=function(){const o="0123456789abcdef",s=new Array(256);for(let i=0;i<16;++i){const u=16*i;for(let _=0;_<16;++_)s[u+_]=o[i]+o[_]}return s}();function defineBigIntMethod(o){return"undefined"==typeof BigInt?BufferBigIntNotDefined:o}function BufferBigIntNotDefined(){throw new Error("BigInt not supported")}},38075:(o,s,i)=>{"use strict";var u=i(70453),_=i(10487),w=_(u("String.prototype.indexOf"));o.exports=function callBoundIntrinsic(o,s){var i=u(o,!!s);return"function"==typeof i&&w(o,".prototype.")>-1?_(i):i}},10487:(o,s,i)=>{"use strict";var u=i(66743),_=i(70453),w=i(96897),x=i(69675),C=_("%Function.prototype.apply%"),j=_("%Function.prototype.call%"),L=_("%Reflect.apply%",!0)||u.call(j,C),B=i(30655),$=_("%Math.max%");o.exports=function callBind(o){if("function"!=typeof o)throw new x("a function is required");var s=L(u,j,arguments);return w(s,1+$(0,o.length-(arguments.length-1)),!0)};var V=function applyBind(){return L(u,C,arguments)};B?B(o.exports,"apply",{value:V}):o.exports.apply=V},57427:(o,s)=>{"use strict";s.parse=function parse(o,s){if("string"!=typeof o)throw new TypeError("argument str must be a string");var i={},u=(s||{}).decode||decode,_=0;for(;_{"use strict";var u=i(16426),_={"text/plain":"Text","text/html":"Url",default:"Text"};o.exports=function copy(o,s){var i,w,x,C,j,L,B=!1;s||(s={}),i=s.debug||!1;try{if(x=u(),C=document.createRange(),j=document.getSelection(),(L=document.createElement("span")).textContent=o,L.ariaHidden="true",L.style.all="unset",L.style.position="fixed",L.style.top=0,L.style.clip="rect(0, 0, 0, 0)",L.style.whiteSpace="pre",L.style.webkitUserSelect="text",L.style.MozUserSelect="text",L.style.msUserSelect="text",L.style.userSelect="text",L.addEventListener("copy",(function(u){if(u.stopPropagation(),s.format)if(u.preventDefault(),void 0===u.clipboardData){i&&console.warn("unable to use e.clipboardData"),i&&console.warn("trying IE specific stuff"),window.clipboardData.clearData();var w=_[s.format]||_.default;window.clipboardData.setData(w,o)}else u.clipboardData.clearData(),u.clipboardData.setData(s.format,o);s.onCopy&&(u.preventDefault(),s.onCopy(u.clipboardData))})),document.body.appendChild(L),C.selectNodeContents(L),j.addRange(C),!document.execCommand("copy"))throw new Error("copy command was unsuccessful");B=!0}catch(u){i&&console.error("unable to copy using execCommand: ",u),i&&console.warn("trying IE specific stuff");try{window.clipboardData.setData(s.format||"text",o),s.onCopy&&s.onCopy(window.clipboardData),B=!0}catch(u){i&&console.error("unable to copy using clipboardData: ",u),i&&console.error("falling back to prompt"),w=function format(o){var s=(/mac os x/i.test(navigator.userAgent)?"⌘":"Ctrl")+"+C";return o.replace(/#{\s*key\s*}/g,s)}("message"in s?s.message:"Copy to clipboard: #{key}, Enter"),window.prompt(w,o)}}finally{j&&("function"==typeof j.removeRange?j.removeRange(C):j.removeAllRanges()),L&&document.body.removeChild(L),x()}return B}},2205:function(o,s,i){var u;u=void 0!==i.g?i.g:this,o.exports=function(o){if(o.CSS&&o.CSS.escape)return o.CSS.escape;var cssEscape=function(o){if(0==arguments.length)throw new TypeError("`CSS.escape` requires an argument.");for(var s,i=String(o),u=i.length,_=-1,w="",x=i.charCodeAt(0);++_=1&&s<=31||127==s||0==_&&s>=48&&s<=57||1==_&&s>=48&&s<=57&&45==x?"\\"+s.toString(16)+" ":0==_&&1==u&&45==s||!(s>=128||45==s||95==s||s>=48&&s<=57||s>=65&&s<=90||s>=97&&s<=122)?"\\"+i.charAt(_):i.charAt(_):w+="�";return w};return o.CSS||(o.CSS={}),o.CSS.escape=cssEscape,cssEscape}(u)},81919:(o,s,i)=>{"use strict";var u=i(48287).Buffer;function isSpecificValue(o){return o instanceof u||o instanceof Date||o instanceof RegExp}function cloneSpecificValue(o){if(o instanceof u){var s=u.alloc?u.alloc(o.length):new u(o.length);return o.copy(s),s}if(o instanceof Date)return new Date(o.getTime());if(o instanceof RegExp)return new RegExp(o);throw new Error("Unexpected situation")}function deepCloneArray(o){var s=[];return o.forEach((function(o,i){"object"==typeof o&&null!==o?Array.isArray(o)?s[i]=deepCloneArray(o):isSpecificValue(o)?s[i]=cloneSpecificValue(o):s[i]=_({},o):s[i]=o})),s}function safeGetProperty(o,s){return"__proto__"===s?void 0:o[s]}var _=o.exports=function(){if(arguments.length<1||"object"!=typeof arguments[0])return!1;if(arguments.length<2)return arguments[0];var o,s,i=arguments[0];return Array.prototype.slice.call(arguments,1).forEach((function(u){"object"!=typeof u||null===u||Array.isArray(u)||Object.keys(u).forEach((function(w){return s=safeGetProperty(i,w),(o=safeGetProperty(u,w))===i?void 0:"object"!=typeof o||null===o?void(i[w]=o):Array.isArray(o)?void(i[w]=deepCloneArray(o)):isSpecificValue(o)?void(i[w]=cloneSpecificValue(o)):"object"!=typeof s||null===s||Array.isArray(s)?void(i[w]=_({},o)):void(i[w]=_(s,o))}))})),i}},14744:o=>{"use strict";var s=function isMergeableObject(o){return function isNonNullObject(o){return!!o&&"object"==typeof o}(o)&&!function isSpecial(o){var s=Object.prototype.toString.call(o);return"[object RegExp]"===s||"[object Date]"===s||function isReactElement(o){return o.$$typeof===i}(o)}(o)};var i="function"==typeof Symbol&&Symbol.for?Symbol.for("react.element"):60103;function cloneUnlessOtherwiseSpecified(o,s){return!1!==s.clone&&s.isMergeableObject(o)?deepmerge(function emptyTarget(o){return Array.isArray(o)?[]:{}}(o),o,s):o}function defaultArrayMerge(o,s,i){return o.concat(s).map((function(o){return cloneUnlessOtherwiseSpecified(o,i)}))}function getKeys(o){return Object.keys(o).concat(function getEnumerableOwnPropertySymbols(o){return Object.getOwnPropertySymbols?Object.getOwnPropertySymbols(o).filter((function(s){return Object.propertyIsEnumerable.call(o,s)})):[]}(o))}function propertyIsOnObject(o,s){try{return s in o}catch(o){return!1}}function mergeObject(o,s,i){var u={};return i.isMergeableObject(o)&&getKeys(o).forEach((function(s){u[s]=cloneUnlessOtherwiseSpecified(o[s],i)})),getKeys(s).forEach((function(_){(function propertyIsUnsafe(o,s){return propertyIsOnObject(o,s)&&!(Object.hasOwnProperty.call(o,s)&&Object.propertyIsEnumerable.call(o,s))})(o,_)||(propertyIsOnObject(o,_)&&i.isMergeableObject(s[_])?u[_]=function getMergeFunction(o,s){if(!s.customMerge)return deepmerge;var i=s.customMerge(o);return"function"==typeof i?i:deepmerge}(_,i)(o[_],s[_],i):u[_]=cloneUnlessOtherwiseSpecified(s[_],i))})),u}function deepmerge(o,i,u){(u=u||{}).arrayMerge=u.arrayMerge||defaultArrayMerge,u.isMergeableObject=u.isMergeableObject||s,u.cloneUnlessOtherwiseSpecified=cloneUnlessOtherwiseSpecified;var _=Array.isArray(i);return _===Array.isArray(o)?_?u.arrayMerge(o,i,u):mergeObject(o,i,u):cloneUnlessOtherwiseSpecified(i,u)}deepmerge.all=function deepmergeAll(o,s){if(!Array.isArray(o))throw new Error("first argument should be an array");return o.reduce((function(o,i){return deepmerge(o,i,s)}),{})};var u=deepmerge;o.exports=u},30041:(o,s,i)=>{"use strict";var u=i(30655),_=i(58068),w=i(69675),x=i(75795);o.exports=function defineDataProperty(o,s,i){if(!o||"object"!=typeof o&&"function"!=typeof o)throw new w("`obj` must be an object or a function`");if("string"!=typeof s&&"symbol"!=typeof s)throw new w("`property` must be a string or a symbol`");if(arguments.length>3&&"boolean"!=typeof arguments[3]&&null!==arguments[3])throw new w("`nonEnumerable`, if provided, must be a boolean or null");if(arguments.length>4&&"boolean"!=typeof arguments[4]&&null!==arguments[4])throw new w("`nonWritable`, if provided, must be a boolean or null");if(arguments.length>5&&"boolean"!=typeof arguments[5]&&null!==arguments[5])throw new w("`nonConfigurable`, if provided, must be a boolean or null");if(arguments.length>6&&"boolean"!=typeof arguments[6])throw new w("`loose`, if provided, must be a boolean");var C=arguments.length>3?arguments[3]:null,j=arguments.length>4?arguments[4]:null,L=arguments.length>5?arguments[5]:null,B=arguments.length>6&&arguments[6],$=!!x&&x(o,s);if(u)u(o,s,{configurable:null===L&&$?$.configurable:!L,enumerable:null===C&&$?$.enumerable:!C,value:i,writable:null===j&&$?$.writable:!j});else{if(!B&&(C||j||L))throw new _("This environment does not support defining a property as non-configurable, non-writable, or non-enumerable.");o[s]=i}}},42838:function(o){o.exports=function(){"use strict";const{entries:o,setPrototypeOf:s,isFrozen:i,getPrototypeOf:u,getOwnPropertyDescriptor:_}=Object;let{freeze:w,seal:x,create:C}=Object,{apply:j,construct:L}="undefined"!=typeof Reflect&&Reflect;w||(w=function freeze(o){return o}),x||(x=function seal(o){return o}),j||(j=function apply(o,s,i){return o.apply(s,i)}),L||(L=function construct(o,s){return new o(...s)});const B=unapply(Array.prototype.forEach),$=unapply(Array.prototype.pop),V=unapply(Array.prototype.push),U=unapply(String.prototype.toLowerCase),z=unapply(String.prototype.toString),Y=unapply(String.prototype.match),Z=unapply(String.prototype.replace),ee=unapply(String.prototype.indexOf),ie=unapply(String.prototype.trim),ae=unapply(Object.prototype.hasOwnProperty),ce=unapply(RegExp.prototype.test),le=unconstruct(TypeError);function numberIsNaN(o){return"number"==typeof o&&isNaN(o)}function unapply(o){return function(s){for(var i=arguments.length,u=new Array(i>1?i-1:0),_=1;_2&&void 0!==arguments[2]?arguments[2]:U;s&&s(o,null);let w=u.length;for(;w--;){let s=u[w];if("string"==typeof s){const o=_(s);o!==s&&(i(u)||(u[w]=o),s=o)}o[s]=!0}return o}function cleanArray(o){for(let s=0;s/gm),$e=x(/\${[\w\W]*}/gm),ze=x(/^data-[\-\w.\u00B7-\uFFFF]/),We=x(/^aria-[\-\w]+$/),He=x(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),Ye=x(/^(?:\w+script|data):/i),Xe=x(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),Qe=x(/^html$/i),et=x(/^[a-z][.\w]*(-[.\w]+)+$/i);var tt=Object.freeze({__proto__:null,MUSTACHE_EXPR:Re,ERB_EXPR:qe,TMPLIT_EXPR:$e,DATA_ATTR:ze,ARIA_ATTR:We,IS_ALLOWED_URI:He,IS_SCRIPT_OR_DATA:Ye,ATTR_WHITESPACE:Xe,DOCTYPE_NAME:Qe,CUSTOM_ELEMENT:et});const rt={element:1,attribute:2,text:3,cdataSection:4,entityReference:5,entityNode:6,progressingInstruction:7,comment:8,document:9,documentType:10,documentFragment:11,notation:12},nt=function getGlobal(){return"undefined"==typeof window?null:window},ot=function _createTrustedTypesPolicy(o,s){if("object"!=typeof o||"function"!=typeof o.createPolicy)return null;let i=null;const u="data-tt-policy-suffix";s&&s.hasAttribute(u)&&(i=s.getAttribute(u));const _="dompurify"+(i?"#"+i:"");try{return o.createPolicy(_,{createHTML:o=>o,createScriptURL:o=>o})}catch(o){return console.warn("TrustedTypes policy "+_+" could not be created."),null}};function createDOMPurify(){let s=arguments.length>0&&void 0!==arguments[0]?arguments[0]:nt();const DOMPurify=o=>createDOMPurify(o);if(DOMPurify.version="3.1.4",DOMPurify.removed=[],!s||!s.document||s.document.nodeType!==rt.document)return DOMPurify.isSupported=!1,DOMPurify;let{document:i}=s;const u=i,_=u.currentScript,{DocumentFragment:x,HTMLTemplateElement:j,Node:L,Element:Re,NodeFilter:qe,NamedNodeMap:$e=s.NamedNodeMap||s.MozNamedAttrMap,HTMLFormElement:ze,DOMParser:We,trustedTypes:Ye}=s,Xe=Re.prototype,et=lookupGetter(Xe,"cloneNode"),st=lookupGetter(Xe,"nextSibling"),it=lookupGetter(Xe,"childNodes"),at=lookupGetter(Xe,"parentNode");if("function"==typeof j){const o=i.createElement("template");o.content&&o.content.ownerDocument&&(i=o.content.ownerDocument)}let ct,lt="";const{implementation:ut,createNodeIterator:pt,createDocumentFragment:ht,getElementsByTagName:dt}=i,{importNode:mt}=u;let gt={};DOMPurify.isSupported="function"==typeof o&&"function"==typeof at&&ut&&void 0!==ut.createHTMLDocument;const{MUSTACHE_EXPR:yt,ERB_EXPR:vt,TMPLIT_EXPR:bt,DATA_ATTR:_t,ARIA_ATTR:Et,IS_SCRIPT_OR_DATA:wt,ATTR_WHITESPACE:St,CUSTOM_ELEMENT:xt}=tt;let{IS_ALLOWED_URI:kt}=tt,Ot=null;const Ct=addToSet({},[...pe,...de,...fe,...be,...we]);let At=null;const jt=addToSet({},[...Se,...xe,...Pe,...Te]);let Pt=Object.seal(C(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),It=null,Mt=null,Nt=!0,Tt=!0,Rt=!1,Dt=!0,Lt=!1,Bt=!0,Ft=!1,qt=!1,$t=!1,Vt=!1,Ut=!1,zt=!1,Wt=!0,Kt=!1;const Ht="user-content-";let Jt=!0,Gt=!1,Yt={},Xt=null;const Qt=addToSet({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","style","svg","template","thead","title","video","xmp"]);let Zt=null;const er=addToSet({},["audio","video","img","source","image","track"]);let tr=null;const rr=addToSet({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),nr="http://www.w3.org/1998/Math/MathML",sr="http://www.w3.org/2000/svg",ir="http://www.w3.org/1999/xhtml";let ar=ir,cr=!1,lr=null;const ur=addToSet({},[nr,sr,ir],z);let pr=null;const dr=["application/xhtml+xml","text/html"],fr="text/html";let mr=null,gr=null;const yr=255,vr=i.createElement("form"),br=function isRegexOrFunction(o){return o instanceof RegExp||o instanceof Function},_r=function _parseConfig(){let o=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};if(!gr||gr!==o){if(o&&"object"==typeof o||(o={}),o=clone(o),pr=-1===dr.indexOf(o.PARSER_MEDIA_TYPE)?fr:o.PARSER_MEDIA_TYPE,mr="application/xhtml+xml"===pr?z:U,Ot=ae(o,"ALLOWED_TAGS")?addToSet({},o.ALLOWED_TAGS,mr):Ct,At=ae(o,"ALLOWED_ATTR")?addToSet({},o.ALLOWED_ATTR,mr):jt,lr=ae(o,"ALLOWED_NAMESPACES")?addToSet({},o.ALLOWED_NAMESPACES,z):ur,tr=ae(o,"ADD_URI_SAFE_ATTR")?addToSet(clone(rr),o.ADD_URI_SAFE_ATTR,mr):rr,Zt=ae(o,"ADD_DATA_URI_TAGS")?addToSet(clone(er),o.ADD_DATA_URI_TAGS,mr):er,Xt=ae(o,"FORBID_CONTENTS")?addToSet({},o.FORBID_CONTENTS,mr):Qt,It=ae(o,"FORBID_TAGS")?addToSet({},o.FORBID_TAGS,mr):{},Mt=ae(o,"FORBID_ATTR")?addToSet({},o.FORBID_ATTR,mr):{},Yt=!!ae(o,"USE_PROFILES")&&o.USE_PROFILES,Nt=!1!==o.ALLOW_ARIA_ATTR,Tt=!1!==o.ALLOW_DATA_ATTR,Rt=o.ALLOW_UNKNOWN_PROTOCOLS||!1,Dt=!1!==o.ALLOW_SELF_CLOSE_IN_ATTR,Lt=o.SAFE_FOR_TEMPLATES||!1,Bt=!1!==o.SAFE_FOR_XML,Ft=o.WHOLE_DOCUMENT||!1,Vt=o.RETURN_DOM||!1,Ut=o.RETURN_DOM_FRAGMENT||!1,zt=o.RETURN_TRUSTED_TYPE||!1,$t=o.FORCE_BODY||!1,Wt=!1!==o.SANITIZE_DOM,Kt=o.SANITIZE_NAMED_PROPS||!1,Jt=!1!==o.KEEP_CONTENT,Gt=o.IN_PLACE||!1,kt=o.ALLOWED_URI_REGEXP||He,ar=o.NAMESPACE||ir,Pt=o.CUSTOM_ELEMENT_HANDLING||{},o.CUSTOM_ELEMENT_HANDLING&&br(o.CUSTOM_ELEMENT_HANDLING.tagNameCheck)&&(Pt.tagNameCheck=o.CUSTOM_ELEMENT_HANDLING.tagNameCheck),o.CUSTOM_ELEMENT_HANDLING&&br(o.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)&&(Pt.attributeNameCheck=o.CUSTOM_ELEMENT_HANDLING.attributeNameCheck),o.CUSTOM_ELEMENT_HANDLING&&"boolean"==typeof o.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements&&(Pt.allowCustomizedBuiltInElements=o.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements),Lt&&(Tt=!1),Ut&&(Vt=!0),Yt&&(Ot=addToSet({},we),At=[],!0===Yt.html&&(addToSet(Ot,pe),addToSet(At,Se)),!0===Yt.svg&&(addToSet(Ot,de),addToSet(At,xe),addToSet(At,Te)),!0===Yt.svgFilters&&(addToSet(Ot,fe),addToSet(At,xe),addToSet(At,Te)),!0===Yt.mathMl&&(addToSet(Ot,be),addToSet(At,Pe),addToSet(At,Te))),o.ADD_TAGS&&(Ot===Ct&&(Ot=clone(Ot)),addToSet(Ot,o.ADD_TAGS,mr)),o.ADD_ATTR&&(At===jt&&(At=clone(At)),addToSet(At,o.ADD_ATTR,mr)),o.ADD_URI_SAFE_ATTR&&addToSet(tr,o.ADD_URI_SAFE_ATTR,mr),o.FORBID_CONTENTS&&(Xt===Qt&&(Xt=clone(Xt)),addToSet(Xt,o.FORBID_CONTENTS,mr)),Jt&&(Ot["#text"]=!0),Ft&&addToSet(Ot,["html","head","body"]),Ot.table&&(addToSet(Ot,["tbody"]),delete It.tbody),o.TRUSTED_TYPES_POLICY){if("function"!=typeof o.TRUSTED_TYPES_POLICY.createHTML)throw le('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');if("function"!=typeof o.TRUSTED_TYPES_POLICY.createScriptURL)throw le('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');ct=o.TRUSTED_TYPES_POLICY,lt=ct.createHTML("")}else void 0===ct&&(ct=ot(Ye,_)),null!==ct&&"string"==typeof lt&&(lt=ct.createHTML(""));w&&w(o),gr=o}},Er=addToSet({},["mi","mo","mn","ms","mtext"]),wr=addToSet({},["foreignobject","annotation-xml"]),Sr=addToSet({},["title","style","font","a","script"]),xr=addToSet({},[...de,...fe,...ye]),kr=addToSet({},[...be,..._e]),Or=function _checkValidNamespace(o){let s=at(o);s&&s.tagName||(s={namespaceURI:ar,tagName:"template"});const i=U(o.tagName),u=U(s.tagName);return!!lr[o.namespaceURI]&&(o.namespaceURI===sr?s.namespaceURI===ir?"svg"===i:s.namespaceURI===nr?"svg"===i&&("annotation-xml"===u||Er[u]):Boolean(xr[i]):o.namespaceURI===nr?s.namespaceURI===ir?"math"===i:s.namespaceURI===sr?"math"===i&&wr[u]:Boolean(kr[i]):o.namespaceURI===ir?!(s.namespaceURI===sr&&!wr[u])&&!(s.namespaceURI===nr&&!Er[u])&&!kr[i]&&(Sr[i]||!xr[i]):!("application/xhtml+xml"!==pr||!lr[o.namespaceURI]))},Cr=function _forceRemove(o){V(DOMPurify.removed,{element:o});try{o.parentNode.removeChild(o)}catch(s){o.remove()}},Ar=function _removeAttribute(o,s){try{V(DOMPurify.removed,{attribute:s.getAttributeNode(o),from:s})}catch(o){V(DOMPurify.removed,{attribute:null,from:s})}if(s.removeAttribute(o),"is"===o&&!At[o])if(Vt||Ut)try{Cr(s)}catch(o){}else try{s.setAttribute(o,"")}catch(o){}},jr=function _initDocument(o){let s=null,u=null;if($t)o=""+o;else{const s=Y(o,/^[\r\n\t ]+/);u=s&&s[0]}"application/xhtml+xml"===pr&&ar===ir&&(o=''+o+"");const _=ct?ct.createHTML(o):o;if(ar===ir)try{s=(new We).parseFromString(_,pr)}catch(o){}if(!s||!s.documentElement){s=ut.createDocument(ar,"template",null);try{s.documentElement.innerHTML=cr?lt:_}catch(o){}}const w=s.body||s.documentElement;return o&&u&&w.insertBefore(i.createTextNode(u),w.childNodes[0]||null),ar===ir?dt.call(s,Ft?"html":"body")[0]:Ft?s.documentElement:w},Pr=function _createNodeIterator(o){return pt.call(o.ownerDocument||o,o,qe.SHOW_ELEMENT|qe.SHOW_COMMENT|qe.SHOW_TEXT|qe.SHOW_PROCESSING_INSTRUCTION|qe.SHOW_CDATA_SECTION,null)},Ir=function _isClobbered(o){return o instanceof ze&&(void 0!==o.__depth&&"number"!=typeof o.__depth||void 0!==o.__removalCount&&"number"!=typeof o.__removalCount||"string"!=typeof o.nodeName||"string"!=typeof o.textContent||"function"!=typeof o.removeChild||!(o.attributes instanceof $e)||"function"!=typeof o.removeAttribute||"function"!=typeof o.setAttribute||"string"!=typeof o.namespaceURI||"function"!=typeof o.insertBefore||"function"!=typeof o.hasChildNodes)},Mr=function _isNode(o){return"function"==typeof L&&o instanceof L},Nr=function _executeHook(o,s,i){gt[o]&&B(gt[o],(o=>{o.call(DOMPurify,s,i,gr)}))},Tr=function _sanitizeElements(o){let s=null;if(Nr("beforeSanitizeElements",o,null),Ir(o))return Cr(o),!0;const i=mr(o.nodeName);if(Nr("uponSanitizeElement",o,{tagName:i,allowedTags:Ot}),o.hasChildNodes()&&!Mr(o.firstElementChild)&&ce(/<[/\w]/g,o.innerHTML)&&ce(/<[/\w]/g,o.textContent))return Cr(o),!0;if(o.nodeType===rt.progressingInstruction)return Cr(o),!0;if(Bt&&o.nodeType===rt.comment&&ce(/<[/\w]/g,o.data))return Cr(o),!0;if(!Ot[i]||It[i]){if(!It[i]&&Dr(i)){if(Pt.tagNameCheck instanceof RegExp&&ce(Pt.tagNameCheck,i))return!1;if(Pt.tagNameCheck instanceof Function&&Pt.tagNameCheck(i))return!1}if(Jt&&!Xt[i]){const s=at(o)||o.parentNode,i=it(o)||o.childNodes;if(i&&s)for(let u=i.length-1;u>=0;--u){const _=et(i[u],!0);_.__removalCount=(o.__removalCount||0)+1,s.insertBefore(_,st(o))}}return Cr(o),!0}return o instanceof Re&&!Or(o)?(Cr(o),!0):"noscript"!==i&&"noembed"!==i&&"noframes"!==i||!ce(/<\/no(script|embed|frames)/i,o.innerHTML)?(Lt&&o.nodeType===rt.text&&(s=o.textContent,B([yt,vt,bt],(o=>{s=Z(s,o," ")})),o.textContent!==s&&(V(DOMPurify.removed,{element:o.cloneNode()}),o.textContent=s)),Nr("afterSanitizeElements",o,null),!1):(Cr(o),!0)},Rr=function _isValidAttribute(o,s,u){if(Wt&&("id"===s||"name"===s)&&(u in i||u in vr||"__depth"===u||"__removalCount"===u))return!1;if(Tt&&!Mt[s]&&ce(_t,s));else if(Nt&&ce(Et,s));else if(!At[s]||Mt[s]){if(!(Dr(o)&&(Pt.tagNameCheck instanceof RegExp&&ce(Pt.tagNameCheck,o)||Pt.tagNameCheck instanceof Function&&Pt.tagNameCheck(o))&&(Pt.attributeNameCheck instanceof RegExp&&ce(Pt.attributeNameCheck,s)||Pt.attributeNameCheck instanceof Function&&Pt.attributeNameCheck(s))||"is"===s&&Pt.allowCustomizedBuiltInElements&&(Pt.tagNameCheck instanceof RegExp&&ce(Pt.tagNameCheck,u)||Pt.tagNameCheck instanceof Function&&Pt.tagNameCheck(u))))return!1}else if(tr[s]);else if(ce(kt,Z(u,St,"")));else if("src"!==s&&"xlink:href"!==s&&"href"!==s||"script"===o||0!==ee(u,"data:")||!Zt[o])if(Rt&&!ce(wt,Z(u,St,"")));else if(u)return!1;return!0},Dr=function _isBasicCustomElement(o){return"annotation-xml"!==o&&Y(o,xt)},Lr=function _sanitizeAttributes(o){Nr("beforeSanitizeAttributes",o,null);const{attributes:s}=o;if(!s)return;const i={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:At};let u=s.length;for(;u--;){const _=s[u],{name:w,namespaceURI:x,value:C}=_,j=mr(w);let L="value"===w?C:ie(C);if(i.attrName=j,i.attrValue=L,i.keepAttr=!0,i.forceKeepAttr=void 0,Nr("uponSanitizeAttribute",o,i),L=i.attrValue,i.forceKeepAttr)continue;if(Ar(w,o),!i.keepAttr)continue;if(!Dt&&ce(/\/>/i,L)){Ar(w,o);continue}if(Bt&&ce(/((--!?|])>)|<\/(style|title)/i,L)){Ar(w,o);continue}Lt&&B([yt,vt,bt],(o=>{L=Z(L,o," ")}));const V=mr(o.nodeName);if(Rr(V,j,L)){if(!Kt||"id"!==j&&"name"!==j||(Ar(w,o),L=Ht+L),ct&&"object"==typeof Ye&&"function"==typeof Ye.getAttributeType)if(x);else switch(Ye.getAttributeType(V,j)){case"TrustedHTML":L=ct.createHTML(L);break;case"TrustedScriptURL":L=ct.createScriptURL(L)}try{x?o.setAttributeNS(x,w,L):o.setAttribute(w,L),Ir(o)?Cr(o):$(DOMPurify.removed)}catch(o){}}}Nr("afterSanitizeAttributes",o,null)},Br=function _sanitizeShadowDOM(o){let s=null;const i=Pr(o);for(Nr("beforeSanitizeShadowDOM",o,null);s=i.nextNode();){if(Nr("uponSanitizeShadowNode",s,null),Tr(s))continue;const o=at(s);s.nodeType===rt.element&&(o&&o.__depth?s.__depth=(s.__removalCount||0)+o.__depth+1:s.__depth=1),(s.__depth>=yr||s.__depth<0||numberIsNaN(s.__depth))&&Cr(s),s.content instanceof x&&(s.content.__depth=s.__depth,_sanitizeShadowDOM(s.content)),Lr(s)}Nr("afterSanitizeShadowDOM",o,null)};return DOMPurify.sanitize=function(o){let s=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},i=null,_=null,w=null,C=null;if(cr=!o,cr&&(o="\x3c!--\x3e"),"string"!=typeof o&&!Mr(o)){if("function"!=typeof o.toString)throw le("toString is not a function");if("string"!=typeof(o=o.toString()))throw le("dirty is not a string, aborting")}if(!DOMPurify.isSupported)return o;if(qt||_r(s),DOMPurify.removed=[],"string"==typeof o&&(Gt=!1),Gt){if(o.nodeName){const s=mr(o.nodeName);if(!Ot[s]||It[s])throw le("root node is forbidden and cannot be sanitized in-place")}}else if(o instanceof L)i=jr("\x3c!----\x3e"),_=i.ownerDocument.importNode(o,!0),_.nodeType===rt.element&&"BODY"===_.nodeName||"HTML"===_.nodeName?i=_:i.appendChild(_);else{if(!Vt&&!Lt&&!Ft&&-1===o.indexOf("<"))return ct&&zt?ct.createHTML(o):o;if(i=jr(o),!i)return Vt?null:zt?lt:""}i&&$t&&Cr(i.firstChild);const j=Pr(Gt?o:i);for(;w=j.nextNode();){if(Tr(w))continue;const o=at(w);w.nodeType===rt.element&&(o&&o.__depth?w.__depth=(w.__removalCount||0)+o.__depth+1:w.__depth=1),(w.__depth>=yr||w.__depth<0||numberIsNaN(w.__depth))&&Cr(w),w.content instanceof x&&(w.content.__depth=w.__depth,Br(w.content)),Lr(w)}if(Gt)return o;if(Vt){if(Ut)for(C=ht.call(i.ownerDocument);i.firstChild;)C.appendChild(i.firstChild);else C=i;return(At.shadowroot||At.shadowrootmode)&&(C=mt.call(u,C,!0)),C}let $=Ft?i.outerHTML:i.innerHTML;return Ft&&Ot["!doctype"]&&i.ownerDocument&&i.ownerDocument.doctype&&i.ownerDocument.doctype.name&&ce(Qe,i.ownerDocument.doctype.name)&&($="\n"+$),Lt&&B([yt,vt,bt],(o=>{$=Z($,o," ")})),ct&&zt?ct.createHTML($):$},DOMPurify.setConfig=function(){_r(arguments.length>0&&void 0!==arguments[0]?arguments[0]:{}),qt=!0},DOMPurify.clearConfig=function(){gr=null,qt=!1},DOMPurify.isValidAttribute=function(o,s,i){gr||_r({});const u=mr(o),_=mr(s);return Rr(u,_,i)},DOMPurify.addHook=function(o,s){"function"==typeof s&&(gt[o]=gt[o]||[],V(gt[o],s))},DOMPurify.removeHook=function(o){if(gt[o])return $(gt[o])},DOMPurify.removeHooks=function(o){gt[o]&&(gt[o]=[])},DOMPurify.removeAllHooks=function(){gt={}},DOMPurify}return createDOMPurify()}()},78004:o=>{"use strict";class SubRange{constructor(o,s){this.low=o,this.high=s,this.length=1+s-o}overlaps(o){return!(this.higho.high)}touches(o){return!(this.high+1o.high)}add(o){return new SubRange(Math.min(this.low,o.low),Math.max(this.high,o.high))}subtract(o){return o.low<=this.low&&o.high>=this.high?[]:o.low>this.low&&o.higho+s.length),0)}add(o,s){var _add=o=>{for(var s=0;s{for(var s=0;s{for(var s=0;s{for(var i=s.low;i<=s.high;)o.push(i),i++;return o}),[])}subranges(){return this.ranges.map((o=>({low:o.low,high:o.high,length:1+o.high-o.low})))}}o.exports=DRange},30655:(o,s,i)=>{"use strict";var u=i(70453)("%Object.defineProperty%",!0)||!1;if(u)try{u({},"a",{value:1})}catch(o){u=!1}o.exports=u},41237:o=>{"use strict";o.exports=EvalError},69383:o=>{"use strict";o.exports=Error},79290:o=>{"use strict";o.exports=RangeError},79538:o=>{"use strict";o.exports=ReferenceError},58068:o=>{"use strict";o.exports=SyntaxError},69675:o=>{"use strict";o.exports=TypeError},35345:o=>{"use strict";o.exports=URIError},37007:o=>{"use strict";var s,i="object"==typeof Reflect?Reflect:null,u=i&&"function"==typeof i.apply?i.apply:function ReflectApply(o,s,i){return Function.prototype.apply.call(o,s,i)};s=i&&"function"==typeof i.ownKeys?i.ownKeys:Object.getOwnPropertySymbols?function ReflectOwnKeys(o){return Object.getOwnPropertyNames(o).concat(Object.getOwnPropertySymbols(o))}:function ReflectOwnKeys(o){return Object.getOwnPropertyNames(o)};var _=Number.isNaN||function NumberIsNaN(o){return o!=o};function EventEmitter(){EventEmitter.init.call(this)}o.exports=EventEmitter,o.exports.once=function once(o,s){return new Promise((function(i,u){function errorListener(i){o.removeListener(s,resolver),u(i)}function resolver(){"function"==typeof o.removeListener&&o.removeListener("error",errorListener),i([].slice.call(arguments))}eventTargetAgnosticAddListener(o,s,resolver,{once:!0}),"error"!==s&&function addErrorHandlerIfEventEmitter(o,s,i){"function"==typeof o.on&&eventTargetAgnosticAddListener(o,"error",s,i)}(o,errorListener,{once:!0})}))},EventEmitter.EventEmitter=EventEmitter,EventEmitter.prototype._events=void 0,EventEmitter.prototype._eventsCount=0,EventEmitter.prototype._maxListeners=void 0;var w=10;function checkListener(o){if("function"!=typeof o)throw new TypeError('The "listener" argument must be of type Function. Received type '+typeof o)}function _getMaxListeners(o){return void 0===o._maxListeners?EventEmitter.defaultMaxListeners:o._maxListeners}function _addListener(o,s,i,u){var _,w,x;if(checkListener(i),void 0===(w=o._events)?(w=o._events=Object.create(null),o._eventsCount=0):(void 0!==w.newListener&&(o.emit("newListener",s,i.listener?i.listener:i),w=o._events),x=w[s]),void 0===x)x=w[s]=i,++o._eventsCount;else if("function"==typeof x?x=w[s]=u?[i,x]:[x,i]:u?x.unshift(i):x.push(i),(_=_getMaxListeners(o))>0&&x.length>_&&!x.warned){x.warned=!0;var C=new Error("Possible EventEmitter memory leak detected. "+x.length+" "+String(s)+" listeners added. Use emitter.setMaxListeners() to increase limit");C.name="MaxListenersExceededWarning",C.emitter=o,C.type=s,C.count=x.length,function ProcessEmitWarning(o){console&&console.warn&&console.warn(o)}(C)}return o}function onceWrapper(){if(!this.fired)return this.target.removeListener(this.type,this.wrapFn),this.fired=!0,0===arguments.length?this.listener.call(this.target):this.listener.apply(this.target,arguments)}function _onceWrap(o,s,i){var u={fired:!1,wrapFn:void 0,target:o,type:s,listener:i},_=onceWrapper.bind(u);return _.listener=i,u.wrapFn=_,_}function _listeners(o,s,i){var u=o._events;if(void 0===u)return[];var _=u[s];return void 0===_?[]:"function"==typeof _?i?[_.listener||_]:[_]:i?function unwrapListeners(o){for(var s=new Array(o.length),i=0;i0&&(x=s[0]),x instanceof Error)throw x;var C=new Error("Unhandled error."+(x?" ("+x.message+")":""));throw C.context=x,C}var j=w[o];if(void 0===j)return!1;if("function"==typeof j)u(j,this,s);else{var L=j.length,B=arrayClone(j,L);for(i=0;i=0;w--)if(i[w]===s||i[w].listener===s){x=i[w].listener,_=w;break}if(_<0)return this;0===_?i.shift():function spliceOne(o,s){for(;s+1=0;u--)this.removeListener(o,s[u]);return this},EventEmitter.prototype.listeners=function listeners(o){return _listeners(this,o,!0)},EventEmitter.prototype.rawListeners=function rawListeners(o){return _listeners(this,o,!1)},EventEmitter.listenerCount=function(o,s){return"function"==typeof o.listenerCount?o.listenerCount(s):listenerCount.call(o,s)},EventEmitter.prototype.listenerCount=listenerCount,EventEmitter.prototype.eventNames=function eventNames(){return this._eventsCount>0?s(this._events):[]}},85587:(o,s,i)=>{"use strict";var u=i(26311),_=create(Error);function create(o){return FormattedError.displayName=o.displayName||o.name,FormattedError;function FormattedError(s){return s&&(s=u.apply(null,arguments)),new o(s)}}o.exports=_,_.eval=create(EvalError),_.range=create(RangeError),_.reference=create(ReferenceError),_.syntax=create(SyntaxError),_.type=create(TypeError),_.uri=create(URIError),_.create=create},26311:o=>{!function(){var s;function format(o){for(var s,i,u,_,w=1,x=[].slice.call(arguments),C=0,j=o.length,L="",B=!1,$=!1,nextArg=function(){return x[w++]},slurpNumber=function(){for(var i="";/\d/.test(o[C]);)i+=o[C++],s=o[C];return i.length>0?parseInt(i):null};C{"use strict";var s=Object.prototype.toString,i=Math.max,u=function concatty(o,s){for(var i=[],u=0;u{"use strict";var u=i(89353);o.exports=Function.prototype.bind||u},70453:(o,s,i)=>{"use strict";var u,_=i(69383),w=i(41237),x=i(79290),C=i(79538),j=i(58068),L=i(69675),B=i(35345),$=Function,getEvalledConstructor=function(o){try{return $('"use strict"; return ('+o+").constructor;")()}catch(o){}},V=Object.getOwnPropertyDescriptor;if(V)try{V({},"")}catch(o){V=null}var throwTypeError=function(){throw new L},U=V?function(){try{return throwTypeError}catch(o){try{return V(arguments,"callee").get}catch(o){return throwTypeError}}}():throwTypeError,z=i(64039)(),Y=i(80024)(),Z=Object.getPrototypeOf||(Y?function(o){return o.__proto__}:null),ee={},ie="undefined"!=typeof Uint8Array&&Z?Z(Uint8Array):u,ae={__proto__:null,"%AggregateError%":"undefined"==typeof AggregateError?u:AggregateError,"%Array%":Array,"%ArrayBuffer%":"undefined"==typeof ArrayBuffer?u:ArrayBuffer,"%ArrayIteratorPrototype%":z&&Z?Z([][Symbol.iterator]()):u,"%AsyncFromSyncIteratorPrototype%":u,"%AsyncFunction%":ee,"%AsyncGenerator%":ee,"%AsyncGeneratorFunction%":ee,"%AsyncIteratorPrototype%":ee,"%Atomics%":"undefined"==typeof Atomics?u:Atomics,"%BigInt%":"undefined"==typeof BigInt?u:BigInt,"%BigInt64Array%":"undefined"==typeof BigInt64Array?u:BigInt64Array,"%BigUint64Array%":"undefined"==typeof BigUint64Array?u:BigUint64Array,"%Boolean%":Boolean,"%DataView%":"undefined"==typeof DataView?u:DataView,"%Date%":Date,"%decodeURI%":decodeURI,"%decodeURIComponent%":decodeURIComponent,"%encodeURI%":encodeURI,"%encodeURIComponent%":encodeURIComponent,"%Error%":_,"%eval%":eval,"%EvalError%":w,"%Float32Array%":"undefined"==typeof Float32Array?u:Float32Array,"%Float64Array%":"undefined"==typeof Float64Array?u:Float64Array,"%FinalizationRegistry%":"undefined"==typeof FinalizationRegistry?u:FinalizationRegistry,"%Function%":$,"%GeneratorFunction%":ee,"%Int8Array%":"undefined"==typeof Int8Array?u:Int8Array,"%Int16Array%":"undefined"==typeof Int16Array?u:Int16Array,"%Int32Array%":"undefined"==typeof Int32Array?u:Int32Array,"%isFinite%":isFinite,"%isNaN%":isNaN,"%IteratorPrototype%":z&&Z?Z(Z([][Symbol.iterator]())):u,"%JSON%":"object"==typeof JSON?JSON:u,"%Map%":"undefined"==typeof Map?u:Map,"%MapIteratorPrototype%":"undefined"!=typeof Map&&z&&Z?Z((new Map)[Symbol.iterator]()):u,"%Math%":Math,"%Number%":Number,"%Object%":Object,"%parseFloat%":parseFloat,"%parseInt%":parseInt,"%Promise%":"undefined"==typeof Promise?u:Promise,"%Proxy%":"undefined"==typeof Proxy?u:Proxy,"%RangeError%":x,"%ReferenceError%":C,"%Reflect%":"undefined"==typeof Reflect?u:Reflect,"%RegExp%":RegExp,"%Set%":"undefined"==typeof Set?u:Set,"%SetIteratorPrototype%":"undefined"!=typeof Set&&z&&Z?Z((new Set)[Symbol.iterator]()):u,"%SharedArrayBuffer%":"undefined"==typeof SharedArrayBuffer?u:SharedArrayBuffer,"%String%":String,"%StringIteratorPrototype%":z&&Z?Z(""[Symbol.iterator]()):u,"%Symbol%":z?Symbol:u,"%SyntaxError%":j,"%ThrowTypeError%":U,"%TypedArray%":ie,"%TypeError%":L,"%Uint8Array%":"undefined"==typeof Uint8Array?u:Uint8Array,"%Uint8ClampedArray%":"undefined"==typeof Uint8ClampedArray?u:Uint8ClampedArray,"%Uint16Array%":"undefined"==typeof Uint16Array?u:Uint16Array,"%Uint32Array%":"undefined"==typeof Uint32Array?u:Uint32Array,"%URIError%":B,"%WeakMap%":"undefined"==typeof WeakMap?u:WeakMap,"%WeakRef%":"undefined"==typeof WeakRef?u:WeakRef,"%WeakSet%":"undefined"==typeof WeakSet?u:WeakSet};if(Z)try{null.error}catch(o){var ce=Z(Z(o));ae["%Error.prototype%"]=ce}var le=function doEval(o){var s;if("%AsyncFunction%"===o)s=getEvalledConstructor("async function () {}");else if("%GeneratorFunction%"===o)s=getEvalledConstructor("function* () {}");else if("%AsyncGeneratorFunction%"===o)s=getEvalledConstructor("async function* () {}");else if("%AsyncGenerator%"===o){var i=doEval("%AsyncGeneratorFunction%");i&&(s=i.prototype)}else if("%AsyncIteratorPrototype%"===o){var u=doEval("%AsyncGenerator%");u&&Z&&(s=Z(u.prototype))}return ae[o]=s,s},pe={__proto__:null,"%ArrayBufferPrototype%":["ArrayBuffer","prototype"],"%ArrayPrototype%":["Array","prototype"],"%ArrayProto_entries%":["Array","prototype","entries"],"%ArrayProto_forEach%":["Array","prototype","forEach"],"%ArrayProto_keys%":["Array","prototype","keys"],"%ArrayProto_values%":["Array","prototype","values"],"%AsyncFunctionPrototype%":["AsyncFunction","prototype"],"%AsyncGenerator%":["AsyncGeneratorFunction","prototype"],"%AsyncGeneratorPrototype%":["AsyncGeneratorFunction","prototype","prototype"],"%BooleanPrototype%":["Boolean","prototype"],"%DataViewPrototype%":["DataView","prototype"],"%DatePrototype%":["Date","prototype"],"%ErrorPrototype%":["Error","prototype"],"%EvalErrorPrototype%":["EvalError","prototype"],"%Float32ArrayPrototype%":["Float32Array","prototype"],"%Float64ArrayPrototype%":["Float64Array","prototype"],"%FunctionPrototype%":["Function","prototype"],"%Generator%":["GeneratorFunction","prototype"],"%GeneratorPrototype%":["GeneratorFunction","prototype","prototype"],"%Int8ArrayPrototype%":["Int8Array","prototype"],"%Int16ArrayPrototype%":["Int16Array","prototype"],"%Int32ArrayPrototype%":["Int32Array","prototype"],"%JSONParse%":["JSON","parse"],"%JSONStringify%":["JSON","stringify"],"%MapPrototype%":["Map","prototype"],"%NumberPrototype%":["Number","prototype"],"%ObjectPrototype%":["Object","prototype"],"%ObjProto_toString%":["Object","prototype","toString"],"%ObjProto_valueOf%":["Object","prototype","valueOf"],"%PromisePrototype%":["Promise","prototype"],"%PromiseProto_then%":["Promise","prototype","then"],"%Promise_all%":["Promise","all"],"%Promise_reject%":["Promise","reject"],"%Promise_resolve%":["Promise","resolve"],"%RangeErrorPrototype%":["RangeError","prototype"],"%ReferenceErrorPrototype%":["ReferenceError","prototype"],"%RegExpPrototype%":["RegExp","prototype"],"%SetPrototype%":["Set","prototype"],"%SharedArrayBufferPrototype%":["SharedArrayBuffer","prototype"],"%StringPrototype%":["String","prototype"],"%SymbolPrototype%":["Symbol","prototype"],"%SyntaxErrorPrototype%":["SyntaxError","prototype"],"%TypedArrayPrototype%":["TypedArray","prototype"],"%TypeErrorPrototype%":["TypeError","prototype"],"%Uint8ArrayPrototype%":["Uint8Array","prototype"],"%Uint8ClampedArrayPrototype%":["Uint8ClampedArray","prototype"],"%Uint16ArrayPrototype%":["Uint16Array","prototype"],"%Uint32ArrayPrototype%":["Uint32Array","prototype"],"%URIErrorPrototype%":["URIError","prototype"],"%WeakMapPrototype%":["WeakMap","prototype"],"%WeakSetPrototype%":["WeakSet","prototype"]},de=i(66743),fe=i(9957),ye=de.call(Function.call,Array.prototype.concat),be=de.call(Function.apply,Array.prototype.splice),_e=de.call(Function.call,String.prototype.replace),we=de.call(Function.call,String.prototype.slice),Se=de.call(Function.call,RegExp.prototype.exec),xe=/[^%.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|%$))/g,Pe=/\\(\\)?/g,Te=function getBaseIntrinsic(o,s){var i,u=o;if(fe(pe,u)&&(u="%"+(i=pe[u])[0]+"%"),fe(ae,u)){var _=ae[u];if(_===ee&&(_=le(u)),void 0===_&&!s)throw new L("intrinsic "+o+" exists, but is not available. Please file an issue!");return{alias:i,name:u,value:_}}throw new j("intrinsic "+o+" does not exist!")};o.exports=function GetIntrinsic(o,s){if("string"!=typeof o||0===o.length)throw new L("intrinsic name must be a non-empty string");if(arguments.length>1&&"boolean"!=typeof s)throw new L('"allowMissing" argument must be a boolean');if(null===Se(/^%?[^%]*%?$/,o))throw new j("`%` may not be present anywhere but at the beginning and end of the intrinsic name");var i=function stringToPath(o){var s=we(o,0,1),i=we(o,-1);if("%"===s&&"%"!==i)throw new j("invalid intrinsic syntax, expected closing `%`");if("%"===i&&"%"!==s)throw new j("invalid intrinsic syntax, expected opening `%`");var u=[];return _e(o,xe,(function(o,s,i,_){u[u.length]=i?_e(_,Pe,"$1"):s||o})),u}(o),u=i.length>0?i[0]:"",_=Te("%"+u+"%",s),w=_.name,x=_.value,C=!1,B=_.alias;B&&(u=B[0],be(i,ye([0,1],B)));for(var $=1,U=!0;$=i.length){var ee=V(x,z);x=(U=!!ee)&&"get"in ee&&!("originalValue"in ee.get)?ee.get:x[z]}else U=fe(x,z),x=x[z];U&&!C&&(ae[w]=x)}}return x}},75795:(o,s,i)=>{"use strict";var u=i(70453)("%Object.getOwnPropertyDescriptor%",!0);if(u)try{u([],"length")}catch(o){u=null}o.exports=u},30592:(o,s,i)=>{"use strict";var u=i(30655),_=function hasPropertyDescriptors(){return!!u};_.hasArrayLengthDefineBug=function hasArrayLengthDefineBug(){if(!u)return null;try{return 1!==u([],"length",{value:1}).length}catch(o){return!0}},o.exports=_},80024:o=>{"use strict";var s={__proto__:null,foo:{}},i=Object;o.exports=function hasProto(){return{__proto__:s}.foo===s.foo&&!(s instanceof i)}},64039:(o,s,i)=>{"use strict";var u="undefined"!=typeof Symbol&&Symbol,_=i(41333);o.exports=function hasNativeSymbols(){return"function"==typeof u&&("function"==typeof Symbol&&("symbol"==typeof u("foo")&&("symbol"==typeof Symbol("bar")&&_())))}},41333:o=>{"use strict";o.exports=function hasSymbols(){if("function"!=typeof Symbol||"function"!=typeof Object.getOwnPropertySymbols)return!1;if("symbol"==typeof Symbol.iterator)return!0;var o={},s=Symbol("test"),i=Object(s);if("string"==typeof s)return!1;if("[object Symbol]"!==Object.prototype.toString.call(s))return!1;if("[object Symbol]"!==Object.prototype.toString.call(i))return!1;for(s in o[s]=42,o)return!1;if("function"==typeof Object.keys&&0!==Object.keys(o).length)return!1;if("function"==typeof Object.getOwnPropertyNames&&0!==Object.getOwnPropertyNames(o).length)return!1;var u=Object.getOwnPropertySymbols(o);if(1!==u.length||u[0]!==s)return!1;if(!Object.prototype.propertyIsEnumerable.call(o,s))return!1;if("function"==typeof Object.getOwnPropertyDescriptor){var _=Object.getOwnPropertyDescriptor(o,s);if(42!==_.value||!0!==_.enumerable)return!1}return!0}},9957:(o,s,i)=>{"use strict";var u=Function.prototype.call,_=Object.prototype.hasOwnProperty,w=i(66743);o.exports=w.call(u,_)},45981:o=>{function deepFreeze(o){return o instanceof Map?o.clear=o.delete=o.set=function(){throw new Error("map is read-only")}:o instanceof Set&&(o.add=o.clear=o.delete=function(){throw new Error("set is read-only")}),Object.freeze(o),Object.getOwnPropertyNames(o).forEach((function(s){var i=o[s];"object"!=typeof i||Object.isFrozen(i)||deepFreeze(i)})),o}var s=deepFreeze,i=deepFreeze;s.default=i;class Response{constructor(o){void 0===o.data&&(o.data={}),this.data=o.data,this.isMatchIgnored=!1}ignoreMatch(){this.isMatchIgnored=!0}}function escapeHTML(o){return o.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function inherit(o,...s){const i=Object.create(null);for(const s in o)i[s]=o[s];return s.forEach((function(o){for(const s in o)i[s]=o[s]})),i}const emitsWrappingTags=o=>!!o.kind;class HTMLRenderer{constructor(o,s){this.buffer="",this.classPrefix=s.classPrefix,o.walk(this)}addText(o){this.buffer+=escapeHTML(o)}openNode(o){if(!emitsWrappingTags(o))return;let s=o.kind;o.sublanguage||(s=`${this.classPrefix}${s}`),this.span(s)}closeNode(o){emitsWrappingTags(o)&&(this.buffer+="")}value(){return this.buffer}span(o){this.buffer+=``}}class TokenTree{constructor(){this.rootNode={children:[]},this.stack=[this.rootNode]}get top(){return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(o){this.top.children.push(o)}openNode(o){const s={kind:o,children:[]};this.add(s),this.stack.push(s)}closeNode(){if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}walk(o){return this.constructor._walk(o,this.rootNode)}static _walk(o,s){return"string"==typeof s?o.addText(s):s.children&&(o.openNode(s),s.children.forEach((s=>this._walk(o,s))),o.closeNode(s)),o}static _collapse(o){"string"!=typeof o&&o.children&&(o.children.every((o=>"string"==typeof o))?o.children=[o.children.join("")]:o.children.forEach((o=>{TokenTree._collapse(o)})))}}class TokenTreeEmitter extends TokenTree{constructor(o){super(),this.options=o}addKeyword(o,s){""!==o&&(this.openNode(s),this.addText(o),this.closeNode())}addText(o){""!==o&&this.add(o)}addSublanguage(o,s){const i=o.root;i.kind=s,i.sublanguage=!0,this.add(i)}toHTML(){return new HTMLRenderer(this,this.options).value()}finalize(){return!0}}function source(o){return o?"string"==typeof o?o:o.source:null}const u=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./;const _="[a-zA-Z]\\w*",w="[a-zA-Z_]\\w*",x="\\b\\d+(\\.\\d+)?",C="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",j="\\b(0b[01]+)",L={begin:"\\\\[\\s\\S]",relevance:0},B={className:"string",begin:"'",end:"'",illegal:"\\n",contains:[L]},$={className:"string",begin:'"',end:'"',illegal:"\\n",contains:[L]},V={begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},COMMENT=function(o,s,i={}){const u=inherit({className:"comment",begin:o,end:s,contains:[]},i);return u.contains.push(V),u.contains.push({className:"doctag",begin:"(?:TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):",relevance:0}),u},U=COMMENT("//","$"),z=COMMENT("/\\*","\\*/"),Y=COMMENT("#","$"),Z={className:"number",begin:x,relevance:0},ee={className:"number",begin:C,relevance:0},ie={className:"number",begin:j,relevance:0},ae={className:"number",begin:x+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},ce={begin:/(?=\/[^/\n]*\/)/,contains:[{className:"regexp",begin:/\//,end:/\/[gimuy]*/,illegal:/\n/,contains:[L,{begin:/\[/,end:/\]/,relevance:0,contains:[L]}]}]},le={className:"title",begin:_,relevance:0},pe={className:"title",begin:w,relevance:0},de={begin:"\\.\\s*"+w,relevance:0};var fe=Object.freeze({__proto__:null,MATCH_NOTHING_RE:/\b\B/,IDENT_RE:_,UNDERSCORE_IDENT_RE:w,NUMBER_RE:x,C_NUMBER_RE:C,BINARY_NUMBER_RE:j,RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",SHEBANG:(o={})=>{const s=/^#![ ]*\//;return o.binary&&(o.begin=function concat(...o){return o.map((o=>source(o))).join("")}(s,/.*\b/,o.binary,/\b.*/)),inherit({className:"meta",begin:s,end:/$/,relevance:0,"on:begin":(o,s)=>{0!==o.index&&s.ignoreMatch()}},o)},BACKSLASH_ESCAPE:L,APOS_STRING_MODE:B,QUOTE_STRING_MODE:$,PHRASAL_WORDS_MODE:V,COMMENT,C_LINE_COMMENT_MODE:U,C_BLOCK_COMMENT_MODE:z,HASH_COMMENT_MODE:Y,NUMBER_MODE:Z,C_NUMBER_MODE:ee,BINARY_NUMBER_MODE:ie,CSS_NUMBER_MODE:ae,REGEXP_MODE:ce,TITLE_MODE:le,UNDERSCORE_TITLE_MODE:pe,METHOD_GUARD:de,END_SAME_AS_BEGIN:function(o){return Object.assign(o,{"on:begin":(o,s)=>{s.data._beginMatch=o[1]},"on:end":(o,s)=>{s.data._beginMatch!==o[1]&&s.ignoreMatch()}})}});function skipIfhasPrecedingDot(o,s){"."===o.input[o.index-1]&&s.ignoreMatch()}function beginKeywords(o,s){s&&o.beginKeywords&&(o.begin="\\b("+o.beginKeywords.split(" ").join("|")+")(?!\\.)(?=\\b|\\s)",o.__beforeBegin=skipIfhasPrecedingDot,o.keywords=o.keywords||o.beginKeywords,delete o.beginKeywords,void 0===o.relevance&&(o.relevance=0))}function compileIllegal(o,s){Array.isArray(o.illegal)&&(o.illegal=function either(...o){return"("+o.map((o=>source(o))).join("|")+")"}(...o.illegal))}function compileMatch(o,s){if(o.match){if(o.begin||o.end)throw new Error("begin & end are not supported with match");o.begin=o.match,delete o.match}}function compileRelevance(o,s){void 0===o.relevance&&(o.relevance=1)}const ye=["of","and","for","in","not","or","if","then","parent","list","value"],be="keyword";function compileKeywords(o,s,i=be){const u={};return"string"==typeof o?compileList(i,o.split(" ")):Array.isArray(o)?compileList(i,o):Object.keys(o).forEach((function(i){Object.assign(u,compileKeywords(o[i],s,i))})),u;function compileList(o,i){s&&(i=i.map((o=>o.toLowerCase()))),i.forEach((function(s){const i=s.split("|");u[i[0]]=[o,scoreForKeyword(i[0],i[1])]}))}}function scoreForKeyword(o,s){return s?Number(s):function commonKeyword(o){return ye.includes(o.toLowerCase())}(o)?0:1}function compileLanguage(o,{plugins:s}){function langRe(s,i){return new RegExp(source(s),"m"+(o.case_insensitive?"i":"")+(i?"g":""))}class MultiRegex{constructor(){this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}addRule(o,s){s.position=this.position++,this.matchIndexes[this.matchAt]=s,this.regexes.push([s,o]),this.matchAt+=function countMatchGroups(o){return new RegExp(o.toString()+"|").exec("").length-1}(o)+1}compile(){0===this.regexes.length&&(this.exec=()=>null);const o=this.regexes.map((o=>o[1]));this.matcherRe=langRe(function join(o,s="|"){let i=0;return o.map((o=>{i+=1;const s=i;let _=source(o),w="";for(;_.length>0;){const o=u.exec(_);if(!o){w+=_;break}w+=_.substring(0,o.index),_=_.substring(o.index+o[0].length),"\\"===o[0][0]&&o[1]?w+="\\"+String(Number(o[1])+s):(w+=o[0],"("===o[0]&&i++)}return w})).map((o=>`(${o})`)).join(s)}(o),!0),this.lastIndex=0}exec(o){this.matcherRe.lastIndex=this.lastIndex;const s=this.matcherRe.exec(o);if(!s)return null;const i=s.findIndex(((o,s)=>s>0&&void 0!==o)),u=this.matchIndexes[i];return s.splice(0,i),Object.assign(s,u)}}class ResumableMultiRegex{constructor(){this.rules=[],this.multiRegexes=[],this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(o){if(this.multiRegexes[o])return this.multiRegexes[o];const s=new MultiRegex;return this.rules.slice(o).forEach((([o,i])=>s.addRule(o,i))),s.compile(),this.multiRegexes[o]=s,s}resumingScanAtSamePosition(){return 0!==this.regexIndex}considerAll(){this.regexIndex=0}addRule(o,s){this.rules.push([o,s]),"begin"===s.type&&this.count++}exec(o){const s=this.getMatcher(this.regexIndex);s.lastIndex=this.lastIndex;let i=s.exec(o);if(this.resumingScanAtSamePosition())if(i&&i.index===this.lastIndex);else{const s=this.getMatcher(0);s.lastIndex=this.lastIndex+1,i=s.exec(o)}return i&&(this.regexIndex+=i.position+1,this.regexIndex===this.count&&this.considerAll()),i}}if(o.compilerExtensions||(o.compilerExtensions=[]),o.contains&&o.contains.includes("self"))throw new Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.");return o.classNameAliases=inherit(o.classNameAliases||{}),function compileMode(s,i){const u=s;if(s.isCompiled)return u;[compileMatch].forEach((o=>o(s,i))),o.compilerExtensions.forEach((o=>o(s,i))),s.__beforeBegin=null,[beginKeywords,compileIllegal,compileRelevance].forEach((o=>o(s,i))),s.isCompiled=!0;let _=null;if("object"==typeof s.keywords&&(_=s.keywords.$pattern,delete s.keywords.$pattern),s.keywords&&(s.keywords=compileKeywords(s.keywords,o.case_insensitive)),s.lexemes&&_)throw new Error("ERR: Prefer `keywords.$pattern` to `mode.lexemes`, BOTH are not allowed. (see mode reference) ");return _=_||s.lexemes||/\w+/,u.keywordPatternRe=langRe(_,!0),i&&(s.begin||(s.begin=/\B|\b/),u.beginRe=langRe(s.begin),s.endSameAsBegin&&(s.end=s.begin),s.end||s.endsWithParent||(s.end=/\B|\b/),s.end&&(u.endRe=langRe(s.end)),u.terminatorEnd=source(s.end)||"",s.endsWithParent&&i.terminatorEnd&&(u.terminatorEnd+=(s.end?"|":"")+i.terminatorEnd)),s.illegal&&(u.illegalRe=langRe(s.illegal)),s.contains||(s.contains=[]),s.contains=[].concat(...s.contains.map((function(o){return function expandOrCloneMode(o){o.variants&&!o.cachedVariants&&(o.cachedVariants=o.variants.map((function(s){return inherit(o,{variants:null},s)})));if(o.cachedVariants)return o.cachedVariants;if(dependencyOnParent(o))return inherit(o,{starts:o.starts?inherit(o.starts):null});if(Object.isFrozen(o))return inherit(o);return o}("self"===o?s:o)}))),s.contains.forEach((function(o){compileMode(o,u)})),s.starts&&compileMode(s.starts,i),u.matcher=function buildModeRegex(o){const s=new ResumableMultiRegex;return o.contains.forEach((o=>s.addRule(o.begin,{rule:o,type:"begin"}))),o.terminatorEnd&&s.addRule(o.terminatorEnd,{type:"end"}),o.illegal&&s.addRule(o.illegal,{type:"illegal"}),s}(u),u}(o)}function dependencyOnParent(o){return!!o&&(o.endsWithParent||dependencyOnParent(o.starts))}function BuildVuePlugin(o){const s={props:["language","code","autodetect"],data:function(){return{detectedLanguage:"",unknownLanguage:!1}},computed:{className(){return this.unknownLanguage?"":"hljs "+this.detectedLanguage},highlighted(){if(!this.autoDetect&&!o.getLanguage(this.language))return console.warn(`The language "${this.language}" you specified could not be found.`),this.unknownLanguage=!0,escapeHTML(this.code);let s={};return this.autoDetect?(s=o.highlightAuto(this.code),this.detectedLanguage=s.language):(s=o.highlight(this.language,this.code,this.ignoreIllegals),this.detectedLanguage=this.language),s.value},autoDetect(){return!this.language||function hasValueOrEmptyAttribute(o){return Boolean(o||""===o)}(this.autodetect)},ignoreIllegals:()=>!0},render(o){return o("pre",{},[o("code",{class:this.className,domProps:{innerHTML:this.highlighted}})])}};return{Component:s,VuePlugin:{install(o){o.component("highlightjs",s)}}}}const _e={"after:highlightElement":({el:o,result:s,text:i})=>{const u=nodeStream(o);if(!u.length)return;const _=document.createElement("div");_.innerHTML=s.value,s.value=function mergeStreams(o,s,i){let u=0,_="";const w=[];function selectStream(){return o.length&&s.length?o[0].offset!==s[0].offset?o[0].offset"}function close(o){_+=""}function render(o){("start"===o.event?open:close)(o.node)}for(;o.length||s.length;){let s=selectStream();if(_+=escapeHTML(i.substring(u,s[0].offset)),u=s[0].offset,s===o){w.reverse().forEach(close);do{render(s.splice(0,1)[0]),s=selectStream()}while(s===o&&s.length&&s[0].offset===u);w.reverse().forEach(open)}else"start"===s[0].event?w.push(s[0].node):w.pop(),render(s.splice(0,1)[0])}return _+escapeHTML(i.substr(u))}(u,nodeStream(_),i)}};function tag(o){return o.nodeName.toLowerCase()}function nodeStream(o){const s=[];return function _nodeStream(o,i){for(let u=o.firstChild;u;u=u.nextSibling)3===u.nodeType?i+=u.nodeValue.length:1===u.nodeType&&(s.push({event:"start",offset:i,node:u}),i=_nodeStream(u,i),tag(u).match(/br|hr|img|input/)||s.push({event:"stop",offset:i,node:u}));return i}(o,0),s}const we={},error=o=>{console.error(o)},warn=(o,...s)=>{console.log(`WARN: ${o}`,...s)},deprecated=(o,s)=>{we[`${o}/${s}`]||(console.log(`Deprecated as of ${o}. ${s}`),we[`${o}/${s}`]=!0)},Se=escapeHTML,xe=inherit,Pe=Symbol("nomatch");var Te=function(o){const i=Object.create(null),u=Object.create(null),_=[];let w=!0;const x=/(^(<[^>]+>|\t|)+|\n)/gm,C="Could not find the language '{}', did you forget to load/include a language module?",j={disableAutodetect:!0,name:"Plain text",contains:[]};let L={noHighlightRe:/^(no-?highlight)$/i,languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:null,__emitter:TokenTreeEmitter};function shouldNotHighlight(o){return L.noHighlightRe.test(o)}function highlight(o,s,i,u){let _="",w="";"object"==typeof s?(_=o,i=s.ignoreIllegals,w=s.language,u=void 0):(deprecated("10.7.0","highlight(lang, code, ...args) has been deprecated."),deprecated("10.7.0","Please use highlight(code, options) instead.\nhttps://github.com/highlightjs/highlight.js/issues/2277"),w=o,_=s);const x={code:_,language:w};fire("before:highlight",x);const C=x.result?x.result:_highlight(x.language,x.code,i,u);return C.code=x.code,fire("after:highlight",C),C}function _highlight(o,s,u,x){function keywordData(o,s){const i=B.case_insensitive?s[0].toLowerCase():s[0];return Object.prototype.hasOwnProperty.call(o.keywords,i)&&o.keywords[i]}function processBuffer(){null!=U.subLanguage?function processSubLanguage(){if(""===Z)return;let o=null;if("string"==typeof U.subLanguage){if(!i[U.subLanguage])return void Y.addText(Z);o=_highlight(U.subLanguage,Z,!0,z[U.subLanguage]),z[U.subLanguage]=o.top}else o=highlightAuto(Z,U.subLanguage.length?U.subLanguage:null);U.relevance>0&&(ee+=o.relevance),Y.addSublanguage(o.emitter,o.language)}():function processKeywords(){if(!U.keywords)return void Y.addText(Z);let o=0;U.keywordPatternRe.lastIndex=0;let s=U.keywordPatternRe.exec(Z),i="";for(;s;){i+=Z.substring(o,s.index);const u=keywordData(U,s);if(u){const[o,_]=u;if(Y.addText(i),i="",ee+=_,o.startsWith("_"))i+=s[0];else{const i=B.classNameAliases[o]||o;Y.addKeyword(s[0],i)}}else i+=s[0];o=U.keywordPatternRe.lastIndex,s=U.keywordPatternRe.exec(Z)}i+=Z.substr(o),Y.addText(i)}(),Z=""}function startNewMode(o){return o.className&&Y.openNode(B.classNameAliases[o.className]||o.className),U=Object.create(o,{parent:{value:U}}),U}function endOfMode(o,s,i){let u=function startsWith(o,s){const i=o&&o.exec(s);return i&&0===i.index}(o.endRe,i);if(u){if(o["on:end"]){const i=new Response(o);o["on:end"](s,i),i.isMatchIgnored&&(u=!1)}if(u){for(;o.endsParent&&o.parent;)o=o.parent;return o}}if(o.endsWithParent)return endOfMode(o.parent,s,i)}function doIgnore(o){return 0===U.matcher.regexIndex?(Z+=o[0],1):(ce=!0,0)}function doBeginMatch(o){const s=o[0],i=o.rule,u=new Response(i),_=[i.__beforeBegin,i["on:begin"]];for(const i of _)if(i&&(i(o,u),u.isMatchIgnored))return doIgnore(s);return i&&i.endSameAsBegin&&(i.endRe=function escape(o){return new RegExp(o.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&"),"m")}(s)),i.skip?Z+=s:(i.excludeBegin&&(Z+=s),processBuffer(),i.returnBegin||i.excludeBegin||(Z=s)),startNewMode(i),i.returnBegin?0:s.length}function doEndMatch(o){const i=o[0],u=s.substr(o.index),_=endOfMode(U,o,u);if(!_)return Pe;const w=U;w.skip?Z+=i:(w.returnEnd||w.excludeEnd||(Z+=i),processBuffer(),w.excludeEnd&&(Z=i));do{U.className&&Y.closeNode(),U.skip||U.subLanguage||(ee+=U.relevance),U=U.parent}while(U!==_.parent);return _.starts&&(_.endSameAsBegin&&(_.starts.endRe=_.endRe),startNewMode(_.starts)),w.returnEnd?0:i.length}let j={};function processLexeme(i,_){const x=_&&_[0];if(Z+=i,null==x)return processBuffer(),0;if("begin"===j.type&&"end"===_.type&&j.index===_.index&&""===x){if(Z+=s.slice(_.index,_.index+1),!w){const s=new Error("0 width match regex");throw s.languageName=o,s.badRule=j.rule,s}return 1}if(j=_,"begin"===_.type)return doBeginMatch(_);if("illegal"===_.type&&!u){const o=new Error('Illegal lexeme "'+x+'" for mode "'+(U.className||"")+'"');throw o.mode=U,o}if("end"===_.type){const o=doEndMatch(_);if(o!==Pe)return o}if("illegal"===_.type&&""===x)return 1;if(ae>1e5&&ae>3*_.index){throw new Error("potential infinite loop, way more iterations than matches")}return Z+=x,x.length}const B=getLanguage(o);if(!B)throw error(C.replace("{}",o)),new Error('Unknown language: "'+o+'"');const $=compileLanguage(B,{plugins:_});let V="",U=x||$;const z={},Y=new L.__emitter(L);!function processContinuations(){const o=[];for(let s=U;s!==B;s=s.parent)s.className&&o.unshift(s.className);o.forEach((o=>Y.openNode(o)))}();let Z="",ee=0,ie=0,ae=0,ce=!1;try{for(U.matcher.considerAll();;){ae++,ce?ce=!1:U.matcher.considerAll(),U.matcher.lastIndex=ie;const o=U.matcher.exec(s);if(!o)break;const i=processLexeme(s.substring(ie,o.index),o);ie=o.index+i}return processLexeme(s.substr(ie)),Y.closeAllNodes(),Y.finalize(),V=Y.toHTML(),{relevance:Math.floor(ee),value:V,language:o,illegal:!1,emitter:Y,top:U}}catch(i){if(i.message&&i.message.includes("Illegal"))return{illegal:!0,illegalBy:{msg:i.message,context:s.slice(ie-100,ie+100),mode:i.mode},sofar:V,relevance:0,value:Se(s),emitter:Y};if(w)return{illegal:!1,relevance:0,value:Se(s),emitter:Y,language:o,top:U,errorRaised:i};throw i}}function highlightAuto(o,s){s=s||L.languages||Object.keys(i);const u=function justTextHighlightResult(o){const s={relevance:0,emitter:new L.__emitter(L),value:Se(o),illegal:!1,top:j};return s.emitter.addText(o),s}(o),_=s.filter(getLanguage).filter(autoDetection).map((s=>_highlight(s,o,!1)));_.unshift(u);const w=_.sort(((o,s)=>{if(o.relevance!==s.relevance)return s.relevance-o.relevance;if(o.language&&s.language){if(getLanguage(o.language).supersetOf===s.language)return 1;if(getLanguage(s.language).supersetOf===o.language)return-1}return 0})),[x,C]=w,B=x;return B.second_best=C,B}const B={"before:highlightElement":({el:o})=>{L.useBR&&(o.innerHTML=o.innerHTML.replace(/\n/g,"").replace(//g,"\n"))},"after:highlightElement":({result:o})=>{L.useBR&&(o.value=o.value.replace(/\n/g,"
    "))}},$=/^(<[^>]+>|\t)+/gm,V={"after:highlightElement":({result:o})=>{L.tabReplace&&(o.value=o.value.replace($,(o=>o.replace(/\t/g,L.tabReplace))))}};function highlightElement(o){let s=null;const i=function blockLanguage(o){let s=o.className+" ";s+=o.parentNode?o.parentNode.className:"";const i=L.languageDetectRe.exec(s);if(i){const s=getLanguage(i[1]);return s||(warn(C.replace("{}",i[1])),warn("Falling back to no-highlight mode for this block.",o)),s?i[1]:"no-highlight"}return s.split(/\s+/).find((o=>shouldNotHighlight(o)||getLanguage(o)))}(o);if(shouldNotHighlight(i))return;fire("before:highlightElement",{el:o,language:i}),s=o;const _=s.textContent,w=i?highlight(_,{language:i,ignoreIllegals:!0}):highlightAuto(_);fire("after:highlightElement",{el:o,result:w,text:_}),o.innerHTML=w.value,function updateClassName(o,s,i){const _=s?u[s]:i;o.classList.add("hljs"),_&&o.classList.add(_)}(o,i,w.language),o.result={language:w.language,re:w.relevance,relavance:w.relevance},w.second_best&&(o.second_best={language:w.second_best.language,re:w.second_best.relevance,relavance:w.second_best.relevance})}const initHighlighting=()=>{if(initHighlighting.called)return;initHighlighting.called=!0,deprecated("10.6.0","initHighlighting() is deprecated. Use highlightAll() instead.");document.querySelectorAll("pre code").forEach(highlightElement)};let U=!1;function highlightAll(){if("loading"===document.readyState)return void(U=!0);document.querySelectorAll("pre code").forEach(highlightElement)}function getLanguage(o){return o=(o||"").toLowerCase(),i[o]||i[u[o]]}function registerAliases(o,{languageName:s}){"string"==typeof o&&(o=[o]),o.forEach((o=>{u[o.toLowerCase()]=s}))}function autoDetection(o){const s=getLanguage(o);return s&&!s.disableAutodetect}function fire(o,s){const i=o;_.forEach((function(o){o[i]&&o[i](s)}))}"undefined"!=typeof window&&window.addEventListener&&window.addEventListener("DOMContentLoaded",(function boot(){U&&highlightAll()}),!1),Object.assign(o,{highlight,highlightAuto,highlightAll,fixMarkup:function deprecateFixMarkup(o){return deprecated("10.2.0","fixMarkup will be removed entirely in v11.0"),deprecated("10.2.0","Please see https://github.com/highlightjs/highlight.js/issues/2534"),function fixMarkup(o){return L.tabReplace||L.useBR?o.replace(x,(o=>"\n"===o?L.useBR?"
    ":o:L.tabReplace?o.replace(/\t/g,L.tabReplace):o)):o}(o)},highlightElement,highlightBlock:function deprecateHighlightBlock(o){return deprecated("10.7.0","highlightBlock will be removed entirely in v12.0"),deprecated("10.7.0","Please use highlightElement now."),highlightElement(o)},configure:function configure(o){o.useBR&&(deprecated("10.3.0","'useBR' will be removed entirely in v11.0"),deprecated("10.3.0","Please see https://github.com/highlightjs/highlight.js/issues/2559")),L=xe(L,o)},initHighlighting,initHighlightingOnLoad:function initHighlightingOnLoad(){deprecated("10.6.0","initHighlightingOnLoad() is deprecated. Use highlightAll() instead."),U=!0},registerLanguage:function registerLanguage(s,u){let _=null;try{_=u(o)}catch(o){if(error("Language definition for '{}' could not be registered.".replace("{}",s)),!w)throw o;error(o),_=j}_.name||(_.name=s),i[s]=_,_.rawDefinition=u.bind(null,o),_.aliases&®isterAliases(_.aliases,{languageName:s})},unregisterLanguage:function unregisterLanguage(o){delete i[o];for(const s of Object.keys(u))u[s]===o&&delete u[s]},listLanguages:function listLanguages(){return Object.keys(i)},getLanguage,registerAliases,requireLanguage:function requireLanguage(o){deprecated("10.4.0","requireLanguage will be removed entirely in v11."),deprecated("10.4.0","Please see https://github.com/highlightjs/highlight.js/pull/2844");const s=getLanguage(o);if(s)return s;throw new Error("The '{}' language is required, but not loaded.".replace("{}",o))},autoDetection,inherit:xe,addPlugin:function addPlugin(o){!function upgradePluginAPI(o){o["before:highlightBlock"]&&!o["before:highlightElement"]&&(o["before:highlightElement"]=s=>{o["before:highlightBlock"](Object.assign({block:s.el},s))}),o["after:highlightBlock"]&&!o["after:highlightElement"]&&(o["after:highlightElement"]=s=>{o["after:highlightBlock"](Object.assign({block:s.el},s))})}(o),_.push(o)},vuePlugin:BuildVuePlugin(o).VuePlugin}),o.debugMode=function(){w=!1},o.safeMode=function(){w=!0},o.versionString="10.7.3";for(const o in fe)"object"==typeof fe[o]&&s(fe[o]);return Object.assign(o,fe),o.addPlugin(B),o.addPlugin(_e),o.addPlugin(V),o}({});o.exports=Te},35344:o=>{function concat(...o){return o.map((o=>function source(o){return o?"string"==typeof o?o:o.source:null}(o))).join("")}o.exports=function bash(o){const s={},i={begin:/\$\{/,end:/\}/,contains:["self",{begin:/:-/,contains:[s]}]};Object.assign(s,{className:"variable",variants:[{begin:concat(/\$[\w\d#@][\w\d_]*/,"(?![\\w\\d])(?![$])")},i]});const u={className:"subst",begin:/\$\(/,end:/\)/,contains:[o.BACKSLASH_ESCAPE]},_={begin:/<<-?\s*(?=\w+)/,starts:{contains:[o.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/,className:"string"})]}},w={className:"string",begin:/"/,end:/"/,contains:[o.BACKSLASH_ESCAPE,s,u]};u.contains.push(w);const x={begin:/\$\(\(/,end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},o.NUMBER_MODE,s]},C=o.SHEBANG({binary:`(${["fish","bash","zsh","sh","csh","ksh","tcsh","dash","scsh"].join("|")})`,relevance:10}),j={className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0,contains:[o.inherit(o.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0};return{name:"Bash",aliases:["sh","zsh"],keywords:{$pattern:/\b[a-z._-]+\b/,keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp"},contains:[C,o.SHEBANG(),j,x,o.HASH_COMMENT_MODE,_,w,{className:"",begin:/\\"/},{className:"string",begin:/'/,end:/'/},s]}}},73402:o=>{function concat(...o){return o.map((o=>function source(o){return o?"string"==typeof o?o:o.source:null}(o))).join("")}o.exports=function http(o){const s="HTTP/(2|1\\.[01])",i={className:"attribute",begin:concat("^",/[A-Za-z][A-Za-z0-9-]*/,"(?=\\:\\s)"),starts:{contains:[{className:"punctuation",begin:/: /,relevance:0,starts:{end:"$",relevance:0}}]}},u=[i,{begin:"\\n\\n",starts:{subLanguage:[],endsWithParent:!0}}];return{name:"HTTP",aliases:["https"],illegal:/\S/,contains:[{begin:"^(?="+s+" \\d{3})",end:/$/,contains:[{className:"meta",begin:s},{className:"number",begin:"\\b\\d{3}\\b"}],starts:{end:/\b\B/,illegal:/\S/,contains:u}},{begin:"(?=^[A-Z]+ (.*?) "+s+"$)",end:/$/,contains:[{className:"string",begin:" ",end:" ",excludeBegin:!0,excludeEnd:!0},{className:"meta",begin:s},{className:"keyword",begin:"[A-Z]+"}],starts:{end:/\b\B/,illegal:/\S/,contains:u}},o.inherit(i,{relevance:0})]}}},95089:o=>{const s="[A-Za-z$_][0-9A-Za-z$_]*",i=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],u=["true","false","null","undefined","NaN","Infinity"],_=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer","BigInt64Array","BigUint64Array","BigInt"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);function lookahead(o){return concat("(?=",o,")")}function concat(...o){return o.map((o=>function source(o){return o?"string"==typeof o?o:o.source:null}(o))).join("")}o.exports=function javascript(o){const w=s,x="<>",C="",j={begin:/<[A-Za-z0-9\\._:-]+/,end:/\/[A-Za-z0-9\\._:-]+>|\/>/,isTrulyOpeningTag:(o,s)=>{const i=o[0].length+o.index,u=o.input[i];"<"!==u?">"===u&&(((o,{after:s})=>{const i="",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:o.UNDERSCORE_IDENT_RE,relevance:0},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:L,contains:le}]}]},{begin:/,/,relevance:0},{className:"",begin:/\s/,end:/\s*/,skip:!0},{variants:[{begin:x,end:C},{begin:j.begin,"on:begin":j.isTrulyOpeningTag,end:j.end}],subLanguage:"xml",contains:[{begin:j.begin,end:j.end,skip:!0,contains:["self"]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/[{;]/,excludeEnd:!0,keywords:L,contains:["self",o.inherit(o.TITLE_MODE,{begin:w}),pe],illegal:/%/},{beginKeywords:"while if switch catch for"},{className:"function",begin:o.UNDERSCORE_IDENT_RE+"\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{",returnBegin:!0,contains:[pe,o.inherit(o.TITLE_MODE,{begin:w})]},{variants:[{begin:"\\."+w},{begin:"\\$"+w}],relevance:0},{className:"class",beginKeywords:"class",end:/[{;=]/,excludeEnd:!0,illegal:/[:"[\]]/,contains:[{beginKeywords:"extends"},o.UNDERSCORE_TITLE_MODE]},{begin:/\b(?=constructor)/,end:/[{;]/,excludeEnd:!0,contains:[o.inherit(o.TITLE_MODE,{begin:w}),"self",pe]},{begin:"(get|set)\\s+(?="+w+"\\()",end:/\{/,keywords:"get set",contains:[o.inherit(o.TITLE_MODE,{begin:w}),{begin:/\(\)/},pe]},{begin:/\$[(.]/}]}}},65772:o=>{o.exports=function json(o){const s={literal:"true false null"},i=[o.C_LINE_COMMENT_MODE,o.C_BLOCK_COMMENT_MODE],u=[o.QUOTE_STRING_MODE,o.C_NUMBER_MODE],_={end:",",endsWithParent:!0,excludeEnd:!0,contains:u,keywords:s},w={begin:/\{/,end:/\}/,contains:[{className:"attr",begin:/"/,end:/"/,contains:[o.BACKSLASH_ESCAPE],illegal:"\\n"},o.inherit(_,{begin:/:/})].concat(i),illegal:"\\S"},x={begin:"\\[",end:"\\]",contains:[o.inherit(_)],illegal:"\\S"};return u.push(w,x),i.forEach((function(o){u.push(o)})),{name:"JSON",contains:u,keywords:s,illegal:"\\S"}}},26571:o=>{o.exports=function powershell(o){const s={$pattern:/-?[A-z\.\-]+\b/,keyword:"if else foreach return do while until elseif begin for trap data dynamicparam end break throw param continue finally in switch exit filter try process catch hidden static parameter",built_in:"ac asnp cat cd CFS chdir clc clear clhy cli clp cls clv cnsn compare copy cp cpi cpp curl cvpa dbp del diff dir dnsn ebp echo|0 epal epcsv epsn erase etsn exsn fc fhx fl ft fw gal gbp gc gcb gci gcm gcs gdr gerr ghy gi gin gjb gl gm gmo gp gps gpv group gsn gsnp gsv gtz gu gv gwmi h history icm iex ihy ii ipal ipcsv ipmo ipsn irm ise iwmi iwr kill lp ls man md measure mi mount move mp mv nal ndr ni nmo npssc nsn nv ogv oh popd ps pushd pwd r rbp rcjb rcsn rd rdr ren ri rjb rm rmdir rmo rni rnp rp rsn rsnp rujb rv rvpa rwmi sajb sal saps sasv sbp sc scb select set shcm si sl sleep sls sort sp spjb spps spsv start stz sujb sv swmi tee trcm type wget where wjb write"},i={begin:"`[\\s\\S]",relevance:0},u={className:"variable",variants:[{begin:/\$\B/},{className:"keyword",begin:/\$this/},{begin:/\$[\w\d][\w\d_:]*/}]},_={className:"string",variants:[{begin:/"/,end:/"/},{begin:/@"/,end:/^"@/}],contains:[i,u,{className:"variable",begin:/\$[A-z]/,end:/[^A-z]/}]},w={className:"string",variants:[{begin:/'/,end:/'/},{begin:/@'/,end:/^'@/}]},x=o.inherit(o.COMMENT(null,null),{variants:[{begin:/#/,end:/$/},{begin:/<#/,end:/#>/}],contains:[{className:"doctag",variants:[{begin:/\.(synopsis|description|example|inputs|outputs|notes|link|component|role|functionality)/},{begin:/\.(parameter|forwardhelptargetname|forwardhelpcategory|remotehelprunspace|externalhelp)\s+\S+/}]}]}),C={className:"built_in",variants:[{begin:"(".concat("Add|Clear|Close|Copy|Enter|Exit|Find|Format|Get|Hide|Join|Lock|Move|New|Open|Optimize|Pop|Push|Redo|Remove|Rename|Reset|Resize|Search|Select|Set|Show|Skip|Split|Step|Switch|Undo|Unlock|Watch|Backup|Checkpoint|Compare|Compress|Convert|ConvertFrom|ConvertTo|Dismount|Edit|Expand|Export|Group|Import|Initialize|Limit|Merge|Mount|Out|Publish|Restore|Save|Sync|Unpublish|Update|Approve|Assert|Build|Complete|Confirm|Deny|Deploy|Disable|Enable|Install|Invoke|Register|Request|Restart|Resume|Start|Stop|Submit|Suspend|Uninstall|Unregister|Wait|Debug|Measure|Ping|Repair|Resolve|Test|Trace|Connect|Disconnect|Read|Receive|Send|Write|Block|Grant|Protect|Revoke|Unblock|Unprotect|Use|ForEach|Sort|Tee|Where",")+(-)[\\w\\d]+")}]},j={className:"class",beginKeywords:"class enum",end:/\s*[{]/,excludeEnd:!0,relevance:0,contains:[o.TITLE_MODE]},L={className:"function",begin:/function\s+/,end:/\s*\{|$/,excludeEnd:!0,returnBegin:!0,relevance:0,contains:[{begin:"function",relevance:0,className:"keyword"},{className:"title",begin:/\w[\w\d]*((-)[\w\d]+)*/,relevance:0},{begin:/\(/,end:/\)/,className:"params",relevance:0,contains:[u]}]},B={begin:/using\s/,end:/$/,returnBegin:!0,contains:[_,w,{className:"keyword",begin:/(using|assembly|command|module|namespace|type)/}]},$={variants:[{className:"operator",begin:"(".concat("-and|-as|-band|-bnot|-bor|-bxor|-casesensitive|-ccontains|-ceq|-cge|-cgt|-cle|-clike|-clt|-cmatch|-cne|-cnotcontains|-cnotlike|-cnotmatch|-contains|-creplace|-csplit|-eq|-exact|-f|-file|-ge|-gt|-icontains|-ieq|-ige|-igt|-ile|-ilike|-ilt|-imatch|-in|-ine|-inotcontains|-inotlike|-inotmatch|-ireplace|-is|-isnot|-isplit|-join|-le|-like|-lt|-match|-ne|-not|-notcontains|-notin|-notlike|-notmatch|-or|-regex|-replace|-shl|-shr|-split|-wildcard|-xor",")\\b")},{className:"literal",begin:/(-)[\w\d]+/,relevance:0}]},V={className:"function",begin:/\[.*\]\s*[\w]+[ ]??\(/,end:/$/,returnBegin:!0,relevance:0,contains:[{className:"keyword",begin:"(".concat(s.keyword.toString().replace(/\s/g,"|"),")\\b"),endsParent:!0,relevance:0},o.inherit(o.TITLE_MODE,{endsParent:!0})]},U=[V,x,i,o.NUMBER_MODE,_,w,C,u,{className:"literal",begin:/\$(null|true|false)\b/},{className:"selector-tag",begin:/@\B/,relevance:0}],z={begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[].concat("self",U,{begin:"("+["string","char","byte","int","long","bool","decimal","single","double","DateTime","xml","array","hashtable","void"].join("|")+")",className:"built_in",relevance:0},{className:"type",begin:/[\.\w\d]+/,relevance:0})};return V.contains.unshift(z),{name:"PowerShell",aliases:["ps","ps1"],case_insensitive:!0,keywords:s,contains:U.concat(j,L,B,$,z)}}},17285:o=>{function source(o){return o?"string"==typeof o?o:o.source:null}function lookahead(o){return concat("(?=",o,")")}function concat(...o){return o.map((o=>source(o))).join("")}function either(...o){return"("+o.map((o=>source(o))).join("|")+")"}o.exports=function xml(o){const s=concat(/[A-Z_]/,function optional(o){return concat("(",o,")?")}(/[A-Z0-9_.-]*:/),/[A-Z0-9_.-]*/),i={className:"symbol",begin:/&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;/},u={begin:/\s/,contains:[{className:"meta-keyword",begin:/#?[a-z_][a-z1-9_-]+/,illegal:/\n/}]},_=o.inherit(u,{begin:/\(/,end:/\)/}),w=o.inherit(o.APOS_STRING_MODE,{className:"meta-string"}),x=o.inherit(o.QUOTE_STRING_MODE,{className:"meta-string"}),C={endsWithParent:!0,illegal:/`]+/}]}]}]};return{name:"HTML, XML",aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],case_insensitive:!0,contains:[{className:"meta",begin://,relevance:10,contains:[u,x,w,_,{begin:/\[/,end:/\]/,contains:[{className:"meta",begin://,contains:[u,_,x,w]}]}]},o.COMMENT(//,{relevance:10}),{begin://,relevance:10},i,{className:"meta",begin:/<\?xml/,end:/\?>/,relevance:10},{className:"tag",begin:/)/,end:/>/,keywords:{name:"style"},contains:[C],starts:{end:/<\/style>/,returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag",begin:/)/,end:/>/,keywords:{name:"script"},contains:[C],starts:{end:/<\/script>/,returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{className:"tag",begin:/<>|<\/>/},{className:"tag",begin:concat(//,/>/,/\s/)))),end:/\/?>/,contains:[{className:"name",begin:s,relevance:0,starts:C}]},{className:"tag",begin:concat(/<\//,lookahead(concat(s,/>/))),contains:[{className:"name",begin:s,relevance:0},{begin:/>/,relevance:0,endsParent:!0}]}]}}},17533:o=>{o.exports=function yaml(o){var s="true false yes no null",i="[\\w#;/?:@&=+$,.~*'()[\\]]+",u={className:"string",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/\S+/}],contains:[o.BACKSLASH_ESCAPE,{className:"template-variable",variants:[{begin:/\{\{/,end:/\}\}/},{begin:/%\{/,end:/\}/}]}]},_=o.inherit(u,{variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/[^\s,{}[\]]+/}]}),w={className:"number",begin:"\\b[0-9]{4}(-[0-9][0-9]){0,2}([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?(\\.[0-9]*)?([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?\\b"},x={end:",",endsWithParent:!0,excludeEnd:!0,keywords:s,relevance:0},C={begin:/\{/,end:/\}/,contains:[x],illegal:"\\n",relevance:0},j={begin:"\\[",end:"\\]",contains:[x],illegal:"\\n",relevance:0},L=[{className:"attr",variants:[{begin:"\\w[\\w :\\/.-]*:(?=[ \t]|$)"},{begin:'"\\w[\\w :\\/.-]*":(?=[ \t]|$)'},{begin:"'\\w[\\w :\\/.-]*':(?=[ \t]|$)"}]},{className:"meta",begin:"^---\\s*$",relevance:10},{className:"string",begin:"[\\|>]([1-9]?[+-])?[ ]*\\n( +)[^ ][^\\n]*\\n(\\2[^\\n]+\\n?)*"},{begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:"!\\w+!"+i},{className:"type",begin:"!<"+i+">"},{className:"type",begin:"!"+i},{className:"type",begin:"!!"+i},{className:"meta",begin:"&"+o.UNDERSCORE_IDENT_RE+"$"},{className:"meta",begin:"\\*"+o.UNDERSCORE_IDENT_RE+"$"},{className:"bullet",begin:"-(?=[ ]|$)",relevance:0},o.HASH_COMMENT_MODE,{beginKeywords:s,keywords:{literal:s}},w,{className:"number",begin:o.C_NUMBER_RE+"\\b",relevance:0},C,j,u],B=[...L];return B.pop(),B.push(_),x.contains=B,{name:"YAML",case_insensitive:!0,aliases:["yml"],contains:L}}},251:(o,s)=>{s.read=function(o,s,i,u,_){var w,x,C=8*_-u-1,j=(1<>1,B=-7,$=i?_-1:0,V=i?-1:1,U=o[s+$];for($+=V,w=U&(1<<-B)-1,U>>=-B,B+=C;B>0;w=256*w+o[s+$],$+=V,B-=8);for(x=w&(1<<-B)-1,w>>=-B,B+=u;B>0;x=256*x+o[s+$],$+=V,B-=8);if(0===w)w=1-L;else{if(w===j)return x?NaN:1/0*(U?-1:1);x+=Math.pow(2,u),w-=L}return(U?-1:1)*x*Math.pow(2,w-u)},s.write=function(o,s,i,u,_,w){var x,C,j,L=8*w-_-1,B=(1<>1,V=23===_?Math.pow(2,-24)-Math.pow(2,-77):0,U=u?0:w-1,z=u?1:-1,Y=s<0||0===s&&1/s<0?1:0;for(s=Math.abs(s),isNaN(s)||s===1/0?(C=isNaN(s)?1:0,x=B):(x=Math.floor(Math.log(s)/Math.LN2),s*(j=Math.pow(2,-x))<1&&(x--,j*=2),(s+=x+$>=1?V/j:V*Math.pow(2,1-$))*j>=2&&(x++,j/=2),x+$>=B?(C=0,x=B):x+$>=1?(C=(s*j-1)*Math.pow(2,_),x+=$):(C=s*Math.pow(2,$-1)*Math.pow(2,_),x=0));_>=8;o[i+U]=255&C,U+=z,C/=256,_-=8);for(x=x<<_|C,L+=_;L>0;o[i+U]=255&x,U+=z,x/=256,L-=8);o[i+U-z]|=128*Y}},9404:function(o){o.exports=function(){"use strict";var o=Array.prototype.slice;function createClass(o,s){s&&(o.prototype=Object.create(s.prototype)),o.prototype.constructor=o}function Iterable(o){return isIterable(o)?o:Seq(o)}function KeyedIterable(o){return isKeyed(o)?o:KeyedSeq(o)}function IndexedIterable(o){return isIndexed(o)?o:IndexedSeq(o)}function SetIterable(o){return isIterable(o)&&!isAssociative(o)?o:SetSeq(o)}function isIterable(o){return!(!o||!o[s])}function isKeyed(o){return!(!o||!o[i])}function isIndexed(o){return!(!o||!o[u])}function isAssociative(o){return isKeyed(o)||isIndexed(o)}function isOrdered(o){return!(!o||!o[_])}createClass(KeyedIterable,Iterable),createClass(IndexedIterable,Iterable),createClass(SetIterable,Iterable),Iterable.isIterable=isIterable,Iterable.isKeyed=isKeyed,Iterable.isIndexed=isIndexed,Iterable.isAssociative=isAssociative,Iterable.isOrdered=isOrdered,Iterable.Keyed=KeyedIterable,Iterable.Indexed=IndexedIterable,Iterable.Set=SetIterable;var s="@@__IMMUTABLE_ITERABLE__@@",i="@@__IMMUTABLE_KEYED__@@",u="@@__IMMUTABLE_INDEXED__@@",_="@@__IMMUTABLE_ORDERED__@@",w="delete",x=5,C=1<>>0;if(""+i!==s||4294967295===i)return NaN;s=i}return s<0?ensureSize(o)+s:s}function returnTrue(){return!0}function wholeSlice(o,s,i){return(0===o||void 0!==i&&o<=-i)&&(void 0===s||void 0!==i&&s>=i)}function resolveBegin(o,s){return resolveIndex(o,s,0)}function resolveEnd(o,s){return resolveIndex(o,s,s)}function resolveIndex(o,s,i){return void 0===o?i:o<0?Math.max(0,s+o):void 0===s?o:Math.min(s,o)}var V=0,U=1,z=2,Y="function"==typeof Symbol&&Symbol.iterator,Z="@@iterator",ee=Y||Z;function Iterator(o){this.next=o}function iteratorValue(o,s,i,u){var _=0===o?s:1===o?i:[s,i];return u?u.value=_:u={value:_,done:!1},u}function iteratorDone(){return{value:void 0,done:!0}}function hasIterator(o){return!!getIteratorFn(o)}function isIterator(o){return o&&"function"==typeof o.next}function getIterator(o){var s=getIteratorFn(o);return s&&s.call(o)}function getIteratorFn(o){var s=o&&(Y&&o[Y]||o[Z]);if("function"==typeof s)return s}function isArrayLike(o){return o&&"number"==typeof o.length}function Seq(o){return null==o?emptySequence():isIterable(o)?o.toSeq():seqFromValue(o)}function KeyedSeq(o){return null==o?emptySequence().toKeyedSeq():isIterable(o)?isKeyed(o)?o.toSeq():o.fromEntrySeq():keyedSeqFromValue(o)}function IndexedSeq(o){return null==o?emptySequence():isIterable(o)?isKeyed(o)?o.entrySeq():o.toIndexedSeq():indexedSeqFromValue(o)}function SetSeq(o){return(null==o?emptySequence():isIterable(o)?isKeyed(o)?o.entrySeq():o:indexedSeqFromValue(o)).toSetSeq()}Iterator.prototype.toString=function(){return"[Iterator]"},Iterator.KEYS=V,Iterator.VALUES=U,Iterator.ENTRIES=z,Iterator.prototype.inspect=Iterator.prototype.toSource=function(){return this.toString()},Iterator.prototype[ee]=function(){return this},createClass(Seq,Iterable),Seq.of=function(){return Seq(arguments)},Seq.prototype.toSeq=function(){return this},Seq.prototype.toString=function(){return this.__toString("Seq {","}")},Seq.prototype.cacheResult=function(){return!this._cache&&this.__iterateUncached&&(this._cache=this.entrySeq().toArray(),this.size=this._cache.length),this},Seq.prototype.__iterate=function(o,s){return seqIterate(this,o,s,!0)},Seq.prototype.__iterator=function(o,s){return seqIterator(this,o,s,!0)},createClass(KeyedSeq,Seq),KeyedSeq.prototype.toKeyedSeq=function(){return this},createClass(IndexedSeq,Seq),IndexedSeq.of=function(){return IndexedSeq(arguments)},IndexedSeq.prototype.toIndexedSeq=function(){return this},IndexedSeq.prototype.toString=function(){return this.__toString("Seq [","]")},IndexedSeq.prototype.__iterate=function(o,s){return seqIterate(this,o,s,!1)},IndexedSeq.prototype.__iterator=function(o,s){return seqIterator(this,o,s,!1)},createClass(SetSeq,Seq),SetSeq.of=function(){return SetSeq(arguments)},SetSeq.prototype.toSetSeq=function(){return this},Seq.isSeq=isSeq,Seq.Keyed=KeyedSeq,Seq.Set=SetSeq,Seq.Indexed=IndexedSeq;var ie,ae,ce,le="@@__IMMUTABLE_SEQ__@@";function ArraySeq(o){this._array=o,this.size=o.length}function ObjectSeq(o){var s=Object.keys(o);this._object=o,this._keys=s,this.size=s.length}function IterableSeq(o){this._iterable=o,this.size=o.length||o.size}function IteratorSeq(o){this._iterator=o,this._iteratorCache=[]}function isSeq(o){return!(!o||!o[le])}function emptySequence(){return ie||(ie=new ArraySeq([]))}function keyedSeqFromValue(o){var s=Array.isArray(o)?new ArraySeq(o).fromEntrySeq():isIterator(o)?new IteratorSeq(o).fromEntrySeq():hasIterator(o)?new IterableSeq(o).fromEntrySeq():"object"==typeof o?new ObjectSeq(o):void 0;if(!s)throw new TypeError("Expected Array or iterable object of [k, v] entries, or keyed object: "+o);return s}function indexedSeqFromValue(o){var s=maybeIndexedSeqFromValue(o);if(!s)throw new TypeError("Expected Array or iterable object of values: "+o);return s}function seqFromValue(o){var s=maybeIndexedSeqFromValue(o)||"object"==typeof o&&new ObjectSeq(o);if(!s)throw new TypeError("Expected Array or iterable object of values, or keyed object: "+o);return s}function maybeIndexedSeqFromValue(o){return isArrayLike(o)?new ArraySeq(o):isIterator(o)?new IteratorSeq(o):hasIterator(o)?new IterableSeq(o):void 0}function seqIterate(o,s,i,u){var _=o._cache;if(_){for(var w=_.length-1,x=0;x<=w;x++){var C=_[i?w-x:x];if(!1===s(C[1],u?C[0]:x,o))return x+1}return x}return o.__iterateUncached(s,i)}function seqIterator(o,s,i,u){var _=o._cache;if(_){var w=_.length-1,x=0;return new Iterator((function(){var o=_[i?w-x:x];return x++>w?iteratorDone():iteratorValue(s,u?o[0]:x-1,o[1])}))}return o.__iteratorUncached(s,i)}function fromJS(o,s){return s?fromJSWith(s,o,"",{"":o}):fromJSDefault(o)}function fromJSWith(o,s,i,u){return Array.isArray(s)?o.call(u,i,IndexedSeq(s).map((function(i,u){return fromJSWith(o,i,u,s)}))):isPlainObj(s)?o.call(u,i,KeyedSeq(s).map((function(i,u){return fromJSWith(o,i,u,s)}))):s}function fromJSDefault(o){return Array.isArray(o)?IndexedSeq(o).map(fromJSDefault).toList():isPlainObj(o)?KeyedSeq(o).map(fromJSDefault).toMap():o}function isPlainObj(o){return o&&(o.constructor===Object||void 0===o.constructor)}function is(o,s){if(o===s||o!=o&&s!=s)return!0;if(!o||!s)return!1;if("function"==typeof o.valueOf&&"function"==typeof s.valueOf){if((o=o.valueOf())===(s=s.valueOf())||o!=o&&s!=s)return!0;if(!o||!s)return!1}return!("function"!=typeof o.equals||"function"!=typeof s.equals||!o.equals(s))}function deepEqual(o,s){if(o===s)return!0;if(!isIterable(s)||void 0!==o.size&&void 0!==s.size&&o.size!==s.size||void 0!==o.__hash&&void 0!==s.__hash&&o.__hash!==s.__hash||isKeyed(o)!==isKeyed(s)||isIndexed(o)!==isIndexed(s)||isOrdered(o)!==isOrdered(s))return!1;if(0===o.size&&0===s.size)return!0;var i=!isAssociative(o);if(isOrdered(o)){var u=o.entries();return s.every((function(o,s){var _=u.next().value;return _&&is(_[1],o)&&(i||is(_[0],s))}))&&u.next().done}var _=!1;if(void 0===o.size)if(void 0===s.size)"function"==typeof o.cacheResult&&o.cacheResult();else{_=!0;var w=o;o=s,s=w}var x=!0,C=s.__iterate((function(s,u){if(i?!o.has(s):_?!is(s,o.get(u,L)):!is(o.get(u,L),s))return x=!1,!1}));return x&&o.size===C}function Repeat(o,s){if(!(this instanceof Repeat))return new Repeat(o,s);if(this._value=o,this.size=void 0===s?1/0:Math.max(0,s),0===this.size){if(ae)return ae;ae=this}}function invariant(o,s){if(!o)throw new Error(s)}function Range(o,s,i){if(!(this instanceof Range))return new Range(o,s,i);if(invariant(0!==i,"Cannot step a Range by 0"),o=o||0,void 0===s&&(s=1/0),i=void 0===i?1:Math.abs(i),su?iteratorDone():iteratorValue(o,_,i[s?u-_++:_++])}))},createClass(ObjectSeq,KeyedSeq),ObjectSeq.prototype.get=function(o,s){return void 0===s||this.has(o)?this._object[o]:s},ObjectSeq.prototype.has=function(o){return this._object.hasOwnProperty(o)},ObjectSeq.prototype.__iterate=function(o,s){for(var i=this._object,u=this._keys,_=u.length-1,w=0;w<=_;w++){var x=u[s?_-w:w];if(!1===o(i[x],x,this))return w+1}return w},ObjectSeq.prototype.__iterator=function(o,s){var i=this._object,u=this._keys,_=u.length-1,w=0;return new Iterator((function(){var x=u[s?_-w:w];return w++>_?iteratorDone():iteratorValue(o,x,i[x])}))},ObjectSeq.prototype[_]=!0,createClass(IterableSeq,IndexedSeq),IterableSeq.prototype.__iterateUncached=function(o,s){if(s)return this.cacheResult().__iterate(o,s);var i=getIterator(this._iterable),u=0;if(isIterator(i))for(var _;!(_=i.next()).done&&!1!==o(_.value,u++,this););return u},IterableSeq.prototype.__iteratorUncached=function(o,s){if(s)return this.cacheResult().__iterator(o,s);var i=getIterator(this._iterable);if(!isIterator(i))return new Iterator(iteratorDone);var u=0;return new Iterator((function(){var s=i.next();return s.done?s:iteratorValue(o,u++,s.value)}))},createClass(IteratorSeq,IndexedSeq),IteratorSeq.prototype.__iterateUncached=function(o,s){if(s)return this.cacheResult().__iterate(o,s);for(var i,u=this._iterator,_=this._iteratorCache,w=0;w<_.length;)if(!1===o(_[w],w++,this))return w;for(;!(i=u.next()).done;){var x=i.value;if(_[w]=x,!1===o(x,w++,this))break}return w},IteratorSeq.prototype.__iteratorUncached=function(o,s){if(s)return this.cacheResult().__iterator(o,s);var i=this._iterator,u=this._iteratorCache,_=0;return new Iterator((function(){if(_>=u.length){var s=i.next();if(s.done)return s;u[_]=s.value}return iteratorValue(o,_,u[_++])}))},createClass(Repeat,IndexedSeq),Repeat.prototype.toString=function(){return 0===this.size?"Repeat []":"Repeat [ "+this._value+" "+this.size+" times ]"},Repeat.prototype.get=function(o,s){return this.has(o)?this._value:s},Repeat.prototype.includes=function(o){return is(this._value,o)},Repeat.prototype.slice=function(o,s){var i=this.size;return wholeSlice(o,s,i)?this:new Repeat(this._value,resolveEnd(s,i)-resolveBegin(o,i))},Repeat.prototype.reverse=function(){return this},Repeat.prototype.indexOf=function(o){return is(this._value,o)?0:-1},Repeat.prototype.lastIndexOf=function(o){return is(this._value,o)?this.size:-1},Repeat.prototype.__iterate=function(o,s){for(var i=0;i=0&&s=0&&ii?iteratorDone():iteratorValue(o,w++,x)}))},Range.prototype.equals=function(o){return o instanceof Range?this._start===o._start&&this._end===o._end&&this._step===o._step:deepEqual(this,o)},createClass(Collection,Iterable),createClass(KeyedCollection,Collection),createClass(IndexedCollection,Collection),createClass(SetCollection,Collection),Collection.Keyed=KeyedCollection,Collection.Indexed=IndexedCollection,Collection.Set=SetCollection;var pe="function"==typeof Math.imul&&-2===Math.imul(4294967295,2)?Math.imul:function imul(o,s){var i=65535&(o|=0),u=65535&(s|=0);return i*u+((o>>>16)*u+i*(s>>>16)<<16>>>0)|0};function smi(o){return o>>>1&1073741824|3221225471&o}function hash(o){if(!1===o||null==o)return 0;if("function"==typeof o.valueOf&&(!1===(o=o.valueOf())||null==o))return 0;if(!0===o)return 1;var s=typeof o;if("number"===s){if(o!=o||o===1/0)return 0;var i=0|o;for(i!==o&&(i^=4294967295*o);o>4294967295;)i^=o/=4294967295;return smi(i)}if("string"===s)return o.length>Se?cachedHashString(o):hashString(o);if("function"==typeof o.hashCode)return o.hashCode();if("object"===s)return hashJSObj(o);if("function"==typeof o.toString)return hashString(o.toString());throw new Error("Value type "+s+" cannot be hashed.")}function cachedHashString(o){var s=Te[o];return void 0===s&&(s=hashString(o),Pe===xe&&(Pe=0,Te={}),Pe++,Te[o]=s),s}function hashString(o){for(var s=0,i=0;i0)switch(o.nodeType){case 1:return o.uniqueID;case 9:return o.documentElement&&o.documentElement.uniqueID}}var ye,be="function"==typeof WeakMap;be&&(ye=new WeakMap);var _e=0,we="__immutablehash__";"function"==typeof Symbol&&(we=Symbol(we));var Se=16,xe=255,Pe=0,Te={};function assertNotInfinite(o){invariant(o!==1/0,"Cannot perform this action with an infinite size.")}function Map(o){return null==o?emptyMap():isMap(o)&&!isOrdered(o)?o:emptyMap().withMutations((function(s){var i=KeyedIterable(o);assertNotInfinite(i.size),i.forEach((function(o,i){return s.set(i,o)}))}))}function isMap(o){return!(!o||!o[qe])}createClass(Map,KeyedCollection),Map.of=function(){var s=o.call(arguments,0);return emptyMap().withMutations((function(o){for(var i=0;i=s.length)throw new Error("Missing value for key: "+s[i]);o.set(s[i],s[i+1])}}))},Map.prototype.toString=function(){return this.__toString("Map {","}")},Map.prototype.get=function(o,s){return this._root?this._root.get(0,void 0,o,s):s},Map.prototype.set=function(o,s){return updateMap(this,o,s)},Map.prototype.setIn=function(o,s){return this.updateIn(o,L,(function(){return s}))},Map.prototype.remove=function(o){return updateMap(this,o,L)},Map.prototype.deleteIn=function(o){return this.updateIn(o,(function(){return L}))},Map.prototype.update=function(o,s,i){return 1===arguments.length?o(this):this.updateIn([o],s,i)},Map.prototype.updateIn=function(o,s,i){i||(i=s,s=void 0);var u=updateInDeepMap(this,forceIterator(o),s,i);return u===L?void 0:u},Map.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._root=null,this.__hash=void 0,this.__altered=!0,this):emptyMap()},Map.prototype.merge=function(){return mergeIntoMapWith(this,void 0,arguments)},Map.prototype.mergeWith=function(s){return mergeIntoMapWith(this,s,o.call(arguments,1))},Map.prototype.mergeIn=function(s){var i=o.call(arguments,1);return this.updateIn(s,emptyMap(),(function(o){return"function"==typeof o.merge?o.merge.apply(o,i):i[i.length-1]}))},Map.prototype.mergeDeep=function(){return mergeIntoMapWith(this,deepMerger,arguments)},Map.prototype.mergeDeepWith=function(s){var i=o.call(arguments,1);return mergeIntoMapWith(this,deepMergerWith(s),i)},Map.prototype.mergeDeepIn=function(s){var i=o.call(arguments,1);return this.updateIn(s,emptyMap(),(function(o){return"function"==typeof o.mergeDeep?o.mergeDeep.apply(o,i):i[i.length-1]}))},Map.prototype.sort=function(o){return OrderedMap(sortFactory(this,o))},Map.prototype.sortBy=function(o,s){return OrderedMap(sortFactory(this,s,o))},Map.prototype.withMutations=function(o){var s=this.asMutable();return o(s),s.wasAltered()?s.__ensureOwner(this.__ownerID):this},Map.prototype.asMutable=function(){return this.__ownerID?this:this.__ensureOwner(new OwnerID)},Map.prototype.asImmutable=function(){return this.__ensureOwner()},Map.prototype.wasAltered=function(){return this.__altered},Map.prototype.__iterator=function(o,s){return new MapIterator(this,o,s)},Map.prototype.__iterate=function(o,s){var i=this,u=0;return this._root&&this._root.iterate((function(s){return u++,o(s[1],s[0],i)}),s),u},Map.prototype.__ensureOwner=function(o){return o===this.__ownerID?this:o?makeMap(this.size,this._root,o,this.__hash):(this.__ownerID=o,this.__altered=!1,this)},Map.isMap=isMap;var Re,qe="@@__IMMUTABLE_MAP__@@",$e=Map.prototype;function ArrayMapNode(o,s){this.ownerID=o,this.entries=s}function BitmapIndexedNode(o,s,i){this.ownerID=o,this.bitmap=s,this.nodes=i}function HashArrayMapNode(o,s,i){this.ownerID=o,this.count=s,this.nodes=i}function HashCollisionNode(o,s,i){this.ownerID=o,this.keyHash=s,this.entries=i}function ValueNode(o,s,i){this.ownerID=o,this.keyHash=s,this.entry=i}function MapIterator(o,s,i){this._type=s,this._reverse=i,this._stack=o._root&&mapIteratorFrame(o._root)}function mapIteratorValue(o,s){return iteratorValue(o,s[0],s[1])}function mapIteratorFrame(o,s){return{node:o,index:0,__prev:s}}function makeMap(o,s,i,u){var _=Object.create($e);return _.size=o,_._root=s,_.__ownerID=i,_.__hash=u,_.__altered=!1,_}function emptyMap(){return Re||(Re=makeMap(0))}function updateMap(o,s,i){var u,_;if(o._root){var w=MakeRef(B),x=MakeRef($);if(u=updateNode(o._root,o.__ownerID,0,void 0,s,i,w,x),!x.value)return o;_=o.size+(w.value?i===L?-1:1:0)}else{if(i===L)return o;_=1,u=new ArrayMapNode(o.__ownerID,[[s,i]])}return o.__ownerID?(o.size=_,o._root=u,o.__hash=void 0,o.__altered=!0,o):u?makeMap(_,u):emptyMap()}function updateNode(o,s,i,u,_,w,x,C){return o?o.update(s,i,u,_,w,x,C):w===L?o:(SetRef(C),SetRef(x),new ValueNode(s,u,[_,w]))}function isLeafNode(o){return o.constructor===ValueNode||o.constructor===HashCollisionNode}function mergeIntoNode(o,s,i,u,_){if(o.keyHash===u)return new HashCollisionNode(s,u,[o.entry,_]);var w,C=(0===i?o.keyHash:o.keyHash>>>i)&j,L=(0===i?u:u>>>i)&j;return new BitmapIndexedNode(s,1<>>=1)x[j]=1&i?s[w++]:void 0;return x[u]=_,new HashArrayMapNode(o,w+1,x)}function mergeIntoMapWith(o,s,i){for(var u=[],_=0;_>1&1431655765))+(o>>2&858993459))+(o>>4)&252645135,o+=o>>8,127&(o+=o>>16)}function setIn(o,s,i,u){var _=u?o:arrCopy(o);return _[s]=i,_}function spliceIn(o,s,i,u){var _=o.length+1;if(u&&s+1===_)return o[s]=i,o;for(var w=new Array(_),x=0,C=0;C<_;C++)C===s?(w[C]=i,x=-1):w[C]=o[C+x];return w}function spliceOut(o,s,i){var u=o.length-1;if(i&&s===u)return o.pop(),o;for(var _=new Array(u),w=0,x=0;x=ze)return createNodes(o,j,u,_);var U=o&&o===this.ownerID,z=U?j:arrCopy(j);return V?C?B===$-1?z.pop():z[B]=z.pop():z[B]=[u,_]:z.push([u,_]),U?(this.entries=z,this):new ArrayMapNode(o,z)}},BitmapIndexedNode.prototype.get=function(o,s,i,u){void 0===s&&(s=hash(i));var _=1<<((0===o?s:s>>>o)&j),w=this.bitmap;return w&_?this.nodes[popCount(w&_-1)].get(o+x,s,i,u):u},BitmapIndexedNode.prototype.update=function(o,s,i,u,_,w,C){void 0===i&&(i=hash(u));var B=(0===s?i:i>>>s)&j,$=1<=We)return expandNodes(o,Y,V,B,ee);if(U&&!ee&&2===Y.length&&isLeafNode(Y[1^z]))return Y[1^z];if(U&&ee&&1===Y.length&&isLeafNode(ee))return ee;var ie=o&&o===this.ownerID,ae=U?ee?V:V^$:V|$,ce=U?ee?setIn(Y,z,ee,ie):spliceOut(Y,z,ie):spliceIn(Y,z,ee,ie);return ie?(this.bitmap=ae,this.nodes=ce,this):new BitmapIndexedNode(o,ae,ce)},HashArrayMapNode.prototype.get=function(o,s,i,u){void 0===s&&(s=hash(i));var _=(0===o?s:s>>>o)&j,w=this.nodes[_];return w?w.get(o+x,s,i,u):u},HashArrayMapNode.prototype.update=function(o,s,i,u,_,w,C){void 0===i&&(i=hash(u));var B=(0===s?i:i>>>s)&j,$=_===L,V=this.nodes,U=V[B];if($&&!U)return this;var z=updateNode(U,o,s+x,i,u,_,w,C);if(z===U)return this;var Y=this.count;if(U){if(!z&&--Y0&&u=0&&o>>s&j;if(u>=this.array.length)return new VNode([],o);var _,w=0===u;if(s>0){var C=this.array[u];if((_=C&&C.removeBefore(o,s-x,i))===C&&w)return this}if(w&&!_)return this;var L=editableVNode(this,o);if(!w)for(var B=0;B>>s&j;if(_>=this.array.length)return this;if(s>0){var w=this.array[_];if((u=w&&w.removeAfter(o,s-x,i))===w&&_===this.array.length-1)return this}var C=editableVNode(this,o);return C.array.splice(_+1),u&&(C.array[_]=u),C};var Qe,et,tt={};function iterateList(o,s){var i=o._origin,u=o._capacity,_=getTailOffset(u),w=o._tail;return iterateNodeOrLeaf(o._root,o._level,0);function iterateNodeOrLeaf(o,s,i){return 0===s?iterateLeaf(o,i):iterateNode(o,s,i)}function iterateLeaf(o,x){var j=x===_?w&&w.array:o&&o.array,L=x>i?0:i-x,B=u-x;return B>C&&(B=C),function(){if(L===B)return tt;var o=s?--B:L++;return j&&j[o]}}function iterateNode(o,_,w){var j,L=o&&o.array,B=w>i?0:i-w>>_,$=1+(u-w>>_);return $>C&&($=C),function(){for(;;){if(j){var o=j();if(o!==tt)return o;j=null}if(B===$)return tt;var i=s?--$:B++;j=iterateNodeOrLeaf(L&&L[i],_-x,w+(i<<_))}}}}function makeList(o,s,i,u,_,w,x){var C=Object.create(Xe);return C.size=s-o,C._origin=o,C._capacity=s,C._level=i,C._root=u,C._tail=_,C.__ownerID=w,C.__hash=x,C.__altered=!1,C}function emptyList(){return Qe||(Qe=makeList(0,0,x))}function updateList(o,s,i){if((s=wrapIndex(o,s))!=s)return o;if(s>=o.size||s<0)return o.withMutations((function(o){s<0?setListBounds(o,s).set(0,i):setListBounds(o,0,s+1).set(s,i)}));s+=o._origin;var u=o._tail,_=o._root,w=MakeRef($);return s>=getTailOffset(o._capacity)?u=updateVNode(u,o.__ownerID,0,s,i,w):_=updateVNode(_,o.__ownerID,o._level,s,i,w),w.value?o.__ownerID?(o._root=_,o._tail=u,o.__hash=void 0,o.__altered=!0,o):makeList(o._origin,o._capacity,o._level,_,u):o}function updateVNode(o,s,i,u,_,w){var C,L=u>>>i&j,B=o&&L0){var $=o&&o.array[L],V=updateVNode($,s,i-x,u,_,w);return V===$?o:((C=editableVNode(o,s)).array[L]=V,C)}return B&&o.array[L]===_?o:(SetRef(w),C=editableVNode(o,s),void 0===_&&L===C.array.length-1?C.array.pop():C.array[L]=_,C)}function editableVNode(o,s){return s&&o&&s===o.ownerID?o:new VNode(o?o.array.slice():[],s)}function listNodeFor(o,s){if(s>=getTailOffset(o._capacity))return o._tail;if(s<1<0;)i=i.array[s>>>u&j],u-=x;return i}}function setListBounds(o,s,i){void 0!==s&&(s|=0),void 0!==i&&(i|=0);var u=o.__ownerID||new OwnerID,_=o._origin,w=o._capacity,C=_+s,L=void 0===i?w:i<0?w+i:_+i;if(C===_&&L===w)return o;if(C>=L)return o.clear();for(var B=o._level,$=o._root,V=0;C+V<0;)$=new VNode($&&$.array.length?[void 0,$]:[],u),V+=1<<(B+=x);V&&(C+=V,_+=V,L+=V,w+=V);for(var U=getTailOffset(w),z=getTailOffset(L);z>=1<U?new VNode([],u):Y;if(Y&&z>U&&Cx;ie-=x){var ae=U>>>ie&j;ee=ee.array[ae]=editableVNode(ee.array[ae],u)}ee.array[U>>>x&j]=Y}if(L=z)C-=z,L-=z,B=x,$=null,Z=Z&&Z.removeBefore(u,0,C);else if(C>_||z>>B&j;if(ce!==z>>>B&j)break;ce&&(V+=(1<_&&($=$.removeBefore(u,B,C-V)),$&&z_&&(_=C.size),isIterable(x)||(C=C.map((function(o){return fromJS(o)}))),u.push(C)}return _>o.size&&(o=o.setSize(_)),mergeIntoCollectionWith(o,s,u)}function getTailOffset(o){return o>>x<=C&&x.size>=2*w.size?(u=(_=x.filter((function(o,s){return void 0!==o&&j!==s}))).toKeyedSeq().map((function(o){return o[0]})).flip().toMap(),o.__ownerID&&(u.__ownerID=_.__ownerID=o.__ownerID)):(u=w.remove(s),_=j===x.size-1?x.pop():x.set(j,void 0))}else if(B){if(i===x.get(j)[1])return o;u=w,_=x.set(j,[s,i])}else u=w.set(s,x.size),_=x.set(x.size,[s,i]);return o.__ownerID?(o.size=u.size,o._map=u,o._list=_,o.__hash=void 0,o):makeOrderedMap(u,_)}function ToKeyedSequence(o,s){this._iter=o,this._useKeys=s,this.size=o.size}function ToIndexedSequence(o){this._iter=o,this.size=o.size}function ToSetSequence(o){this._iter=o,this.size=o.size}function FromEntriesSequence(o){this._iter=o,this.size=o.size}function flipFactory(o){var s=makeSequence(o);return s._iter=o,s.size=o.size,s.flip=function(){return o},s.reverse=function(){var s=o.reverse.apply(this);return s.flip=function(){return o.reverse()},s},s.has=function(s){return o.includes(s)},s.includes=function(s){return o.has(s)},s.cacheResult=cacheResultThrough,s.__iterateUncached=function(s,i){var u=this;return o.__iterate((function(o,i){return!1!==s(i,o,u)}),i)},s.__iteratorUncached=function(s,i){if(s===z){var u=o.__iterator(s,i);return new Iterator((function(){var o=u.next();if(!o.done){var s=o.value[0];o.value[0]=o.value[1],o.value[1]=s}return o}))}return o.__iterator(s===U?V:U,i)},s}function mapFactory(o,s,i){var u=makeSequence(o);return u.size=o.size,u.has=function(s){return o.has(s)},u.get=function(u,_){var w=o.get(u,L);return w===L?_:s.call(i,w,u,o)},u.__iterateUncached=function(u,_){var w=this;return o.__iterate((function(o,_,x){return!1!==u(s.call(i,o,_,x),_,w)}),_)},u.__iteratorUncached=function(u,_){var w=o.__iterator(z,_);return new Iterator((function(){var _=w.next();if(_.done)return _;var x=_.value,C=x[0];return iteratorValue(u,C,s.call(i,x[1],C,o),_)}))},u}function reverseFactory(o,s){var i=makeSequence(o);return i._iter=o,i.size=o.size,i.reverse=function(){return o},o.flip&&(i.flip=function(){var s=flipFactory(o);return s.reverse=function(){return o.flip()},s}),i.get=function(i,u){return o.get(s?i:-1-i,u)},i.has=function(i){return o.has(s?i:-1-i)},i.includes=function(s){return o.includes(s)},i.cacheResult=cacheResultThrough,i.__iterate=function(s,i){var u=this;return o.__iterate((function(o,i){return s(o,i,u)}),!i)},i.__iterator=function(s,i){return o.__iterator(s,!i)},i}function filterFactory(o,s,i,u){var _=makeSequence(o);return u&&(_.has=function(u){var _=o.get(u,L);return _!==L&&!!s.call(i,_,u,o)},_.get=function(u,_){var w=o.get(u,L);return w!==L&&s.call(i,w,u,o)?w:_}),_.__iterateUncached=function(_,w){var x=this,C=0;return o.__iterate((function(o,w,j){if(s.call(i,o,w,j))return C++,_(o,u?w:C-1,x)}),w),C},_.__iteratorUncached=function(_,w){var x=o.__iterator(z,w),C=0;return new Iterator((function(){for(;;){var w=x.next();if(w.done)return w;var j=w.value,L=j[0],B=j[1];if(s.call(i,B,L,o))return iteratorValue(_,u?L:C++,B,w)}}))},_}function countByFactory(o,s,i){var u=Map().asMutable();return o.__iterate((function(_,w){u.update(s.call(i,_,w,o),0,(function(o){return o+1}))})),u.asImmutable()}function groupByFactory(o,s,i){var u=isKeyed(o),_=(isOrdered(o)?OrderedMap():Map()).asMutable();o.__iterate((function(w,x){_.update(s.call(i,w,x,o),(function(o){return(o=o||[]).push(u?[x,w]:w),o}))}));var w=iterableClass(o);return _.map((function(s){return reify(o,w(s))}))}function sliceFactory(o,s,i,u){var _=o.size;if(void 0!==s&&(s|=0),void 0!==i&&(i===1/0?i=_:i|=0),wholeSlice(s,i,_))return o;var w=resolveBegin(s,_),x=resolveEnd(i,_);if(w!=w||x!=x)return sliceFactory(o.toSeq().cacheResult(),s,i,u);var C,j=x-w;j==j&&(C=j<0?0:j);var L=makeSequence(o);return L.size=0===C?C:o.size&&C||void 0,!u&&isSeq(o)&&C>=0&&(L.get=function(s,i){return(s=wrapIndex(this,s))>=0&&sC)return iteratorDone();var o=_.next();return u||s===U?o:iteratorValue(s,j-1,s===V?void 0:o.value[1],o)}))},L}function takeWhileFactory(o,s,i){var u=makeSequence(o);return u.__iterateUncached=function(u,_){var w=this;if(_)return this.cacheResult().__iterate(u,_);var x=0;return o.__iterate((function(o,_,C){return s.call(i,o,_,C)&&++x&&u(o,_,w)})),x},u.__iteratorUncached=function(u,_){var w=this;if(_)return this.cacheResult().__iterator(u,_);var x=o.__iterator(z,_),C=!0;return new Iterator((function(){if(!C)return iteratorDone();var o=x.next();if(o.done)return o;var _=o.value,j=_[0],L=_[1];return s.call(i,L,j,w)?u===z?o:iteratorValue(u,j,L,o):(C=!1,iteratorDone())}))},u}function skipWhileFactory(o,s,i,u){var _=makeSequence(o);return _.__iterateUncached=function(_,w){var x=this;if(w)return this.cacheResult().__iterate(_,w);var C=!0,j=0;return o.__iterate((function(o,w,L){if(!C||!(C=s.call(i,o,w,L)))return j++,_(o,u?w:j-1,x)})),j},_.__iteratorUncached=function(_,w){var x=this;if(w)return this.cacheResult().__iterator(_,w);var C=o.__iterator(z,w),j=!0,L=0;return new Iterator((function(){var o,w,B;do{if((o=C.next()).done)return u||_===U?o:iteratorValue(_,L++,_===V?void 0:o.value[1],o);var $=o.value;w=$[0],B=$[1],j&&(j=s.call(i,B,w,x))}while(j);return _===z?o:iteratorValue(_,w,B,o)}))},_}function concatFactory(o,s){var i=isKeyed(o),u=[o].concat(s).map((function(o){return isIterable(o)?i&&(o=KeyedIterable(o)):o=i?keyedSeqFromValue(o):indexedSeqFromValue(Array.isArray(o)?o:[o]),o})).filter((function(o){return 0!==o.size}));if(0===u.length)return o;if(1===u.length){var _=u[0];if(_===o||i&&isKeyed(_)||isIndexed(o)&&isIndexed(_))return _}var w=new ArraySeq(u);return i?w=w.toKeyedSeq():isIndexed(o)||(w=w.toSetSeq()),(w=w.flatten(!0)).size=u.reduce((function(o,s){if(void 0!==o){var i=s.size;if(void 0!==i)return o+i}}),0),w}function flattenFactory(o,s,i){var u=makeSequence(o);return u.__iterateUncached=function(u,_){var w=0,x=!1;function flatDeep(o,C){var j=this;o.__iterate((function(o,_){return(!s||C0}function zipWithFactory(o,s,i){var u=makeSequence(o);return u.size=new ArraySeq(i).map((function(o){return o.size})).min(),u.__iterate=function(o,s){for(var i,u=this.__iterator(U,s),_=0;!(i=u.next()).done&&!1!==o(i.value,_++,this););return _},u.__iteratorUncached=function(o,u){var _=i.map((function(o){return o=Iterable(o),getIterator(u?o.reverse():o)})),w=0,x=!1;return new Iterator((function(){var i;return x||(i=_.map((function(o){return o.next()})),x=i.some((function(o){return o.done}))),x?iteratorDone():iteratorValue(o,w++,s.apply(null,i.map((function(o){return o.value}))))}))},u}function reify(o,s){return isSeq(o)?s:o.constructor(s)}function validateEntry(o){if(o!==Object(o))throw new TypeError("Expected [K, V] tuple: "+o)}function resolveSize(o){return assertNotInfinite(o.size),ensureSize(o)}function iterableClass(o){return isKeyed(o)?KeyedIterable:isIndexed(o)?IndexedIterable:SetIterable}function makeSequence(o){return Object.create((isKeyed(o)?KeyedSeq:isIndexed(o)?IndexedSeq:SetSeq).prototype)}function cacheResultThrough(){return this._iter.cacheResult?(this._iter.cacheResult(),this.size=this._iter.size,this):Seq.prototype.cacheResult.call(this)}function defaultComparator(o,s){return o>s?1:o=0;i--)s={value:arguments[i],next:s};return this.__ownerID?(this.size=o,this._head=s,this.__hash=void 0,this.__altered=!0,this):makeStack(o,s)},Stack.prototype.pushAll=function(o){if(0===(o=IndexedIterable(o)).size)return this;assertNotInfinite(o.size);var s=this.size,i=this._head;return o.reverse().forEach((function(o){s++,i={value:o,next:i}})),this.__ownerID?(this.size=s,this._head=i,this.__hash=void 0,this.__altered=!0,this):makeStack(s,i)},Stack.prototype.pop=function(){return this.slice(1)},Stack.prototype.unshift=function(){return this.push.apply(this,arguments)},Stack.prototype.unshiftAll=function(o){return this.pushAll(o)},Stack.prototype.shift=function(){return this.pop.apply(this,arguments)},Stack.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._head=void 0,this.__hash=void 0,this.__altered=!0,this):emptyStack()},Stack.prototype.slice=function(o,s){if(wholeSlice(o,s,this.size))return this;var i=resolveBegin(o,this.size);if(resolveEnd(s,this.size)!==this.size)return IndexedCollection.prototype.slice.call(this,o,s);for(var u=this.size-i,_=this._head;i--;)_=_.next;return this.__ownerID?(this.size=u,this._head=_,this.__hash=void 0,this.__altered=!0,this):makeStack(u,_)},Stack.prototype.__ensureOwner=function(o){return o===this.__ownerID?this:o?makeStack(this.size,this._head,o,this.__hash):(this.__ownerID=o,this.__altered=!1,this)},Stack.prototype.__iterate=function(o,s){if(s)return this.reverse().__iterate(o);for(var i=0,u=this._head;u&&!1!==o(u.value,i++,this);)u=u.next;return i},Stack.prototype.__iterator=function(o,s){if(s)return this.reverse().__iterator(o);var i=0,u=this._head;return new Iterator((function(){if(u){var s=u.value;return u=u.next,iteratorValue(o,i++,s)}return iteratorDone()}))},Stack.isStack=isStack;var ct,lt="@@__IMMUTABLE_STACK__@@",ut=Stack.prototype;function makeStack(o,s,i,u){var _=Object.create(ut);return _.size=o,_._head=s,_.__ownerID=i,_.__hash=u,_.__altered=!1,_}function emptyStack(){return ct||(ct=makeStack(0))}function mixin(o,s){var keyCopier=function(i){o.prototype[i]=s[i]};return Object.keys(s).forEach(keyCopier),Object.getOwnPropertySymbols&&Object.getOwnPropertySymbols(s).forEach(keyCopier),o}ut[lt]=!0,ut.withMutations=$e.withMutations,ut.asMutable=$e.asMutable,ut.asImmutable=$e.asImmutable,ut.wasAltered=$e.wasAltered,Iterable.Iterator=Iterator,mixin(Iterable,{toArray:function(){assertNotInfinite(this.size);var o=new Array(this.size||0);return this.valueSeq().__iterate((function(s,i){o[i]=s})),o},toIndexedSeq:function(){return new ToIndexedSequence(this)},toJS:function(){return this.toSeq().map((function(o){return o&&"function"==typeof o.toJS?o.toJS():o})).__toJS()},toJSON:function(){return this.toSeq().map((function(o){return o&&"function"==typeof o.toJSON?o.toJSON():o})).__toJS()},toKeyedSeq:function(){return new ToKeyedSequence(this,!0)},toMap:function(){return Map(this.toKeyedSeq())},toObject:function(){assertNotInfinite(this.size);var o={};return this.__iterate((function(s,i){o[i]=s})),o},toOrderedMap:function(){return OrderedMap(this.toKeyedSeq())},toOrderedSet:function(){return OrderedSet(isKeyed(this)?this.valueSeq():this)},toSet:function(){return Set(isKeyed(this)?this.valueSeq():this)},toSetSeq:function(){return new ToSetSequence(this)},toSeq:function(){return isIndexed(this)?this.toIndexedSeq():isKeyed(this)?this.toKeyedSeq():this.toSetSeq()},toStack:function(){return Stack(isKeyed(this)?this.valueSeq():this)},toList:function(){return List(isKeyed(this)?this.valueSeq():this)},toString:function(){return"[Iterable]"},__toString:function(o,s){return 0===this.size?o+s:o+" "+this.toSeq().map(this.__toStringMapper).join(", ")+" "+s},concat:function(){return reify(this,concatFactory(this,o.call(arguments,0)))},includes:function(o){return this.some((function(s){return is(s,o)}))},entries:function(){return this.__iterator(z)},every:function(o,s){assertNotInfinite(this.size);var i=!0;return this.__iterate((function(u,_,w){if(!o.call(s,u,_,w))return i=!1,!1})),i},filter:function(o,s){return reify(this,filterFactory(this,o,s,!0))},find:function(o,s,i){var u=this.findEntry(o,s);return u?u[1]:i},forEach:function(o,s){return assertNotInfinite(this.size),this.__iterate(s?o.bind(s):o)},join:function(o){assertNotInfinite(this.size),o=void 0!==o?""+o:",";var s="",i=!0;return this.__iterate((function(u){i?i=!1:s+=o,s+=null!=u?u.toString():""})),s},keys:function(){return this.__iterator(V)},map:function(o,s){return reify(this,mapFactory(this,o,s))},reduce:function(o,s,i){var u,_;return assertNotInfinite(this.size),arguments.length<2?_=!0:u=s,this.__iterate((function(s,w,x){_?(_=!1,u=s):u=o.call(i,u,s,w,x)})),u},reduceRight:function(o,s,i){var u=this.toKeyedSeq().reverse();return u.reduce.apply(u,arguments)},reverse:function(){return reify(this,reverseFactory(this,!0))},slice:function(o,s){return reify(this,sliceFactory(this,o,s,!0))},some:function(o,s){return!this.every(not(o),s)},sort:function(o){return reify(this,sortFactory(this,o))},values:function(){return this.__iterator(U)},butLast:function(){return this.slice(0,-1)},isEmpty:function(){return void 0!==this.size?0===this.size:!this.some((function(){return!0}))},count:function(o,s){return ensureSize(o?this.toSeq().filter(o,s):this)},countBy:function(o,s){return countByFactory(this,o,s)},equals:function(o){return deepEqual(this,o)},entrySeq:function(){var o=this;if(o._cache)return new ArraySeq(o._cache);var s=o.toSeq().map(entryMapper).toIndexedSeq();return s.fromEntrySeq=function(){return o.toSeq()},s},filterNot:function(o,s){return this.filter(not(o),s)},findEntry:function(o,s,i){var u=i;return this.__iterate((function(i,_,w){if(o.call(s,i,_,w))return u=[_,i],!1})),u},findKey:function(o,s){var i=this.findEntry(o,s);return i&&i[0]},findLast:function(o,s,i){return this.toKeyedSeq().reverse().find(o,s,i)},findLastEntry:function(o,s,i){return this.toKeyedSeq().reverse().findEntry(o,s,i)},findLastKey:function(o,s){return this.toKeyedSeq().reverse().findKey(o,s)},first:function(){return this.find(returnTrue)},flatMap:function(o,s){return reify(this,flatMapFactory(this,o,s))},flatten:function(o){return reify(this,flattenFactory(this,o,!0))},fromEntrySeq:function(){return new FromEntriesSequence(this)},get:function(o,s){return this.find((function(s,i){return is(i,o)}),void 0,s)},getIn:function(o,s){for(var i,u=this,_=forceIterator(o);!(i=_.next()).done;){var w=i.value;if((u=u&&u.get?u.get(w,L):L)===L)return s}return u},groupBy:function(o,s){return groupByFactory(this,o,s)},has:function(o){return this.get(o,L)!==L},hasIn:function(o){return this.getIn(o,L)!==L},isSubset:function(o){return o="function"==typeof o.includes?o:Iterable(o),this.every((function(s){return o.includes(s)}))},isSuperset:function(o){return(o="function"==typeof o.isSubset?o:Iterable(o)).isSubset(this)},keyOf:function(o){return this.findKey((function(s){return is(s,o)}))},keySeq:function(){return this.toSeq().map(keyMapper).toIndexedSeq()},last:function(){return this.toSeq().reverse().first()},lastKeyOf:function(o){return this.toKeyedSeq().reverse().keyOf(o)},max:function(o){return maxFactory(this,o)},maxBy:function(o,s){return maxFactory(this,s,o)},min:function(o){return maxFactory(this,o?neg(o):defaultNegComparator)},minBy:function(o,s){return maxFactory(this,s?neg(s):defaultNegComparator,o)},rest:function(){return this.slice(1)},skip:function(o){return this.slice(Math.max(0,o))},skipLast:function(o){return reify(this,this.toSeq().reverse().skip(o).reverse())},skipWhile:function(o,s){return reify(this,skipWhileFactory(this,o,s,!0))},skipUntil:function(o,s){return this.skipWhile(not(o),s)},sortBy:function(o,s){return reify(this,sortFactory(this,s,o))},take:function(o){return this.slice(0,Math.max(0,o))},takeLast:function(o){return reify(this,this.toSeq().reverse().take(o).reverse())},takeWhile:function(o,s){return reify(this,takeWhileFactory(this,o,s))},takeUntil:function(o,s){return this.takeWhile(not(o),s)},valueSeq:function(){return this.toIndexedSeq()},hashCode:function(){return this.__hash||(this.__hash=hashIterable(this))}});var pt=Iterable.prototype;pt[s]=!0,pt[ee]=pt.values,pt.__toJS=pt.toArray,pt.__toStringMapper=quoteString,pt.inspect=pt.toSource=function(){return this.toString()},pt.chain=pt.flatMap,pt.contains=pt.includes,mixin(KeyedIterable,{flip:function(){return reify(this,flipFactory(this))},mapEntries:function(o,s){var i=this,u=0;return reify(this,this.toSeq().map((function(_,w){return o.call(s,[w,_],u++,i)})).fromEntrySeq())},mapKeys:function(o,s){var i=this;return reify(this,this.toSeq().flip().map((function(u,_){return o.call(s,u,_,i)})).flip())}});var ht=KeyedIterable.prototype;function keyMapper(o,s){return s}function entryMapper(o,s){return[s,o]}function not(o){return function(){return!o.apply(this,arguments)}}function neg(o){return function(){return-o.apply(this,arguments)}}function quoteString(o){return"string"==typeof o?JSON.stringify(o):String(o)}function defaultZipper(){return arrCopy(arguments)}function defaultNegComparator(o,s){return os?-1:0}function hashIterable(o){if(o.size===1/0)return 0;var s=isOrdered(o),i=isKeyed(o),u=s?1:0;return murmurHashOfSize(o.__iterate(i?s?function(o,s){u=31*u+hashMerge(hash(o),hash(s))|0}:function(o,s){u=u+hashMerge(hash(o),hash(s))|0}:s?function(o){u=31*u+hash(o)|0}:function(o){u=u+hash(o)|0}),u)}function murmurHashOfSize(o,s){return s=pe(s,3432918353),s=pe(s<<15|s>>>-15,461845907),s=pe(s<<13|s>>>-13,5),s=pe((s=s+3864292196^o)^s>>>16,2246822507),s=smi((s=pe(s^s>>>13,3266489909))^s>>>16)}function hashMerge(o,s){return o^s+2654435769+(o<<6)+(o>>2)}return ht[i]=!0,ht[ee]=pt.entries,ht.__toJS=pt.toObject,ht.__toStringMapper=function(o,s){return JSON.stringify(s)+": "+quoteString(o)},mixin(IndexedIterable,{toKeyedSeq:function(){return new ToKeyedSequence(this,!1)},filter:function(o,s){return reify(this,filterFactory(this,o,s,!1))},findIndex:function(o,s){var i=this.findEntry(o,s);return i?i[0]:-1},indexOf:function(o){var s=this.keyOf(o);return void 0===s?-1:s},lastIndexOf:function(o){var s=this.lastKeyOf(o);return void 0===s?-1:s},reverse:function(){return reify(this,reverseFactory(this,!1))},slice:function(o,s){return reify(this,sliceFactory(this,o,s,!1))},splice:function(o,s){var i=arguments.length;if(s=Math.max(0|s,0),0===i||2===i&&!s)return this;o=resolveBegin(o,o<0?this.count():this.size);var u=this.slice(0,o);return reify(this,1===i?u:u.concat(arrCopy(arguments,2),this.slice(o+s)))},findLastIndex:function(o,s){var i=this.findLastEntry(o,s);return i?i[0]:-1},first:function(){return this.get(0)},flatten:function(o){return reify(this,flattenFactory(this,o,!1))},get:function(o,s){return(o=wrapIndex(this,o))<0||this.size===1/0||void 0!==this.size&&o>this.size?s:this.find((function(s,i){return i===o}),void 0,s)},has:function(o){return(o=wrapIndex(this,o))>=0&&(void 0!==this.size?this.size===1/0||o{"function"==typeof Object.create?o.exports=function inherits(o,s){s&&(o.super_=s,o.prototype=Object.create(s.prototype,{constructor:{value:o,enumerable:!1,writable:!0,configurable:!0}}))}:o.exports=function inherits(o,s){if(s){o.super_=s;var TempCtor=function(){};TempCtor.prototype=s.prototype,o.prototype=new TempCtor,o.prototype.constructor=o}}},5419:o=>{o.exports=function(o,s,i,u){var _=new Blob(void 0!==u?[u,o]:[o],{type:i||"application/octet-stream"});if(void 0!==window.navigator.msSaveBlob)window.navigator.msSaveBlob(_,s);else{var w=window.URL&&window.URL.createObjectURL?window.URL.createObjectURL(_):window.webkitURL.createObjectURL(_),x=document.createElement("a");x.style.display="none",x.href=w,x.setAttribute("download",s),void 0===x.download&&x.setAttribute("target","_blank"),document.body.appendChild(x),x.click(),setTimeout((function(){document.body.removeChild(x),window.URL.revokeObjectURL(w)}),200)}}},20181:(o,s,i)=>{var u=NaN,_="[object Symbol]",w=/^\s+|\s+$/g,x=/^[-+]0x[0-9a-f]+$/i,C=/^0b[01]+$/i,j=/^0o[0-7]+$/i,L=parseInt,B="object"==typeof i.g&&i.g&&i.g.Object===Object&&i.g,$="object"==typeof self&&self&&self.Object===Object&&self,V=B||$||Function("return this")(),U=Object.prototype.toString,z=Math.max,Y=Math.min,now=function(){return V.Date.now()};function isObject(o){var s=typeof o;return!!o&&("object"==s||"function"==s)}function toNumber(o){if("number"==typeof o)return o;if(function isSymbol(o){return"symbol"==typeof o||function isObjectLike(o){return!!o&&"object"==typeof o}(o)&&U.call(o)==_}(o))return u;if(isObject(o)){var s="function"==typeof o.valueOf?o.valueOf():o;o=isObject(s)?s+"":s}if("string"!=typeof o)return 0===o?o:+o;o=o.replace(w,"");var i=C.test(o);return i||j.test(o)?L(o.slice(2),i?2:8):x.test(o)?u:+o}o.exports=function debounce(o,s,i){var u,_,w,x,C,j,L=0,B=!1,$=!1,V=!0;if("function"!=typeof o)throw new TypeError("Expected a function");function invokeFunc(s){var i=u,w=_;return u=_=void 0,L=s,x=o.apply(w,i)}function shouldInvoke(o){var i=o-j;return void 0===j||i>=s||i<0||$&&o-L>=w}function timerExpired(){var o=now();if(shouldInvoke(o))return trailingEdge(o);C=setTimeout(timerExpired,function remainingWait(o){var i=s-(o-j);return $?Y(i,w-(o-L)):i}(o))}function trailingEdge(o){return C=void 0,V&&u?invokeFunc(o):(u=_=void 0,x)}function debounced(){var o=now(),i=shouldInvoke(o);if(u=arguments,_=this,j=o,i){if(void 0===C)return function leadingEdge(o){return L=o,C=setTimeout(timerExpired,s),B?invokeFunc(o):x}(j);if($)return C=setTimeout(timerExpired,s),invokeFunc(j)}return void 0===C&&(C=setTimeout(timerExpired,s)),x}return s=toNumber(s)||0,isObject(i)&&(B=!!i.leading,w=($="maxWait"in i)?z(toNumber(i.maxWait)||0,s):w,V="trailing"in i?!!i.trailing:V),debounced.cancel=function cancel(){void 0!==C&&clearTimeout(C),L=0,u=j=_=C=void 0},debounced.flush=function flush(){return void 0===C?x:trailingEdge(now())},debounced}},55580:(o,s,i)=>{var u=i(56110)(i(9325),"DataView");o.exports=u},21549:(o,s,i)=>{var u=i(22032),_=i(63862),w=i(66721),x=i(12749),C=i(35749);function Hash(o){var s=-1,i=null==o?0:o.length;for(this.clear();++s{var u=i(39344),_=i(94033);function LazyWrapper(o){this.__wrapped__=o,this.__actions__=[],this.__dir__=1,this.__filtered__=!1,this.__iteratees__=[],this.__takeCount__=4294967295,this.__views__=[]}LazyWrapper.prototype=u(_.prototype),LazyWrapper.prototype.constructor=LazyWrapper,o.exports=LazyWrapper},80079:(o,s,i)=>{var u=i(63702),_=i(70080),w=i(24739),x=i(48655),C=i(31175);function ListCache(o){var s=-1,i=null==o?0:o.length;for(this.clear();++s{var u=i(39344),_=i(94033);function LodashWrapper(o,s){this.__wrapped__=o,this.__actions__=[],this.__chain__=!!s,this.__index__=0,this.__values__=void 0}LodashWrapper.prototype=u(_.prototype),LodashWrapper.prototype.constructor=LodashWrapper,o.exports=LodashWrapper},68223:(o,s,i)=>{var u=i(56110)(i(9325),"Map");o.exports=u},53661:(o,s,i)=>{var u=i(63040),_=i(17670),w=i(90289),x=i(4509),C=i(72949);function MapCache(o){var s=-1,i=null==o?0:o.length;for(this.clear();++s{var u=i(56110)(i(9325),"Promise");o.exports=u},76545:(o,s,i)=>{var u=i(56110)(i(9325),"Set");o.exports=u},38859:(o,s,i)=>{var u=i(53661),_=i(31380),w=i(51459);function SetCache(o){var s=-1,i=null==o?0:o.length;for(this.__data__=new u;++s{var u=i(80079),_=i(51420),w=i(90938),x=i(63605),C=i(29817),j=i(80945);function Stack(o){var s=this.__data__=new u(o);this.size=s.size}Stack.prototype.clear=_,Stack.prototype.delete=w,Stack.prototype.get=x,Stack.prototype.has=C,Stack.prototype.set=j,o.exports=Stack},51873:(o,s,i)=>{var u=i(9325).Symbol;o.exports=u},37828:(o,s,i)=>{var u=i(9325).Uint8Array;o.exports=u},28303:(o,s,i)=>{var u=i(56110)(i(9325),"WeakMap");o.exports=u},91033:o=>{o.exports=function apply(o,s,i){switch(i.length){case 0:return o.call(s);case 1:return o.call(s,i[0]);case 2:return o.call(s,i[0],i[1]);case 3:return o.call(s,i[0],i[1],i[2])}return o.apply(s,i)}},83729:o=>{o.exports=function arrayEach(o,s){for(var i=-1,u=null==o?0:o.length;++i{o.exports=function arrayFilter(o,s){for(var i=-1,u=null==o?0:o.length,_=0,w=[];++i{var u=i(96131);o.exports=function arrayIncludes(o,s){return!!(null==o?0:o.length)&&u(o,s,0)>-1}},70695:(o,s,i)=>{var u=i(78096),_=i(72428),w=i(56449),x=i(3656),C=i(30361),j=i(37167),L=Object.prototype.hasOwnProperty;o.exports=function arrayLikeKeys(o,s){var i=w(o),B=!i&&_(o),$=!i&&!B&&x(o),V=!i&&!B&&!$&&j(o),U=i||B||$||V,z=U?u(o.length,String):[],Y=z.length;for(var Z in o)!s&&!L.call(o,Z)||U&&("length"==Z||$&&("offset"==Z||"parent"==Z)||V&&("buffer"==Z||"byteLength"==Z||"byteOffset"==Z)||C(Z,Y))||z.push(Z);return z}},34932:o=>{o.exports=function arrayMap(o,s){for(var i=-1,u=null==o?0:o.length,_=Array(u);++i{o.exports=function arrayPush(o,s){for(var i=-1,u=s.length,_=o.length;++i{o.exports=function arrayReduce(o,s,i,u){var _=-1,w=null==o?0:o.length;for(u&&w&&(i=o[++_]);++_{o.exports=function arraySome(o,s){for(var i=-1,u=null==o?0:o.length;++i{o.exports=function asciiToArray(o){return o.split("")}},1733:o=>{var s=/[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g;o.exports=function asciiWords(o){return o.match(s)||[]}},87805:(o,s,i)=>{var u=i(43360),_=i(75288);o.exports=function assignMergeValue(o,s,i){(void 0!==i&&!_(o[s],i)||void 0===i&&!(s in o))&&u(o,s,i)}},16547:(o,s,i)=>{var u=i(43360),_=i(75288),w=Object.prototype.hasOwnProperty;o.exports=function assignValue(o,s,i){var x=o[s];w.call(o,s)&&_(x,i)&&(void 0!==i||s in o)||u(o,s,i)}},26025:(o,s,i)=>{var u=i(75288);o.exports=function assocIndexOf(o,s){for(var i=o.length;i--;)if(u(o[i][0],s))return i;return-1}},74733:(o,s,i)=>{var u=i(21791),_=i(95950);o.exports=function baseAssign(o,s){return o&&u(s,_(s),o)}},43838:(o,s,i)=>{var u=i(21791),_=i(37241);o.exports=function baseAssignIn(o,s){return o&&u(s,_(s),o)}},43360:(o,s,i)=>{var u=i(93243);o.exports=function baseAssignValue(o,s,i){"__proto__"==s&&u?u(o,s,{configurable:!0,enumerable:!0,value:i,writable:!0}):o[s]=i}},9999:(o,s,i)=>{var u=i(37217),_=i(83729),w=i(16547),x=i(74733),C=i(43838),j=i(93290),L=i(23007),B=i(92271),$=i(48948),V=i(50002),U=i(83349),z=i(5861),Y=i(76189),Z=i(77199),ee=i(35529),ie=i(56449),ae=i(3656),ce=i(87730),le=i(23805),pe=i(38440),de=i(95950),fe=i(37241),ye="[object Arguments]",be="[object Function]",_e="[object Object]",we={};we[ye]=we["[object Array]"]=we["[object ArrayBuffer]"]=we["[object DataView]"]=we["[object Boolean]"]=we["[object Date]"]=we["[object Float32Array]"]=we["[object Float64Array]"]=we["[object Int8Array]"]=we["[object Int16Array]"]=we["[object Int32Array]"]=we["[object Map]"]=we["[object Number]"]=we[_e]=we["[object RegExp]"]=we["[object Set]"]=we["[object String]"]=we["[object Symbol]"]=we["[object Uint8Array]"]=we["[object Uint8ClampedArray]"]=we["[object Uint16Array]"]=we["[object Uint32Array]"]=!0,we["[object Error]"]=we[be]=we["[object WeakMap]"]=!1,o.exports=function baseClone(o,s,i,Se,xe,Pe){var Te,Re=1&s,qe=2&s,$e=4&s;if(i&&(Te=xe?i(o,Se,xe,Pe):i(o)),void 0!==Te)return Te;if(!le(o))return o;var ze=ie(o);if(ze){if(Te=Y(o),!Re)return L(o,Te)}else{var We=z(o),He=We==be||"[object GeneratorFunction]"==We;if(ae(o))return j(o,Re);if(We==_e||We==ye||He&&!xe){if(Te=qe||He?{}:ee(o),!Re)return qe?$(o,C(Te,o)):B(o,x(Te,o))}else{if(!we[We])return xe?o:{};Te=Z(o,We,Re)}}Pe||(Pe=new u);var Ye=Pe.get(o);if(Ye)return Ye;Pe.set(o,Te),pe(o)?o.forEach((function(u){Te.add(baseClone(u,s,i,u,o,Pe))})):ce(o)&&o.forEach((function(u,_){Te.set(_,baseClone(u,s,i,_,o,Pe))}));var Xe=ze?void 0:($e?qe?U:V:qe?fe:de)(o);return _(Xe||o,(function(u,_){Xe&&(u=o[_=u]),w(Te,_,baseClone(u,s,i,_,o,Pe))})),Te}},39344:(o,s,i)=>{var u=i(23805),_=Object.create,w=function(){function object(){}return function(o){if(!u(o))return{};if(_)return _(o);object.prototype=o;var s=new object;return object.prototype=void 0,s}}();o.exports=w},80909:(o,s,i)=>{var u=i(30641),_=i(38329)(u);o.exports=_},2523:o=>{o.exports=function baseFindIndex(o,s,i,u){for(var _=o.length,w=i+(u?1:-1);u?w--:++w<_;)if(s(o[w],w,o))return w;return-1}},83120:(o,s,i)=>{var u=i(14528),_=i(45891);o.exports=function baseFlatten(o,s,i,w,x){var C=-1,j=o.length;for(i||(i=_),x||(x=[]);++C0&&i(L)?s>1?baseFlatten(L,s-1,i,w,x):u(x,L):w||(x[x.length]=L)}return x}},86649:(o,s,i)=>{var u=i(83221)();o.exports=u},30641:(o,s,i)=>{var u=i(86649),_=i(95950);o.exports=function baseForOwn(o,s){return o&&u(o,s,_)}},47422:(o,s,i)=>{var u=i(31769),_=i(77797);o.exports=function baseGet(o,s){for(var i=0,w=(s=u(s,o)).length;null!=o&&i{var u=i(14528),_=i(56449);o.exports=function baseGetAllKeys(o,s,i){var w=s(o);return _(o)?w:u(w,i(o))}},72552:(o,s,i)=>{var u=i(51873),_=i(659),w=i(59350),x=u?u.toStringTag:void 0;o.exports=function baseGetTag(o){return null==o?void 0===o?"[object Undefined]":"[object Null]":x&&x in Object(o)?_(o):w(o)}},20426:o=>{var s=Object.prototype.hasOwnProperty;o.exports=function baseHas(o,i){return null!=o&&s.call(o,i)}},28077:o=>{o.exports=function baseHasIn(o,s){return null!=o&&s in Object(o)}},96131:(o,s,i)=>{var u=i(2523),_=i(85463),w=i(76959);o.exports=function baseIndexOf(o,s,i){return s==s?w(o,s,i):u(o,_,i)}},27534:(o,s,i)=>{var u=i(72552),_=i(40346);o.exports=function baseIsArguments(o){return _(o)&&"[object Arguments]"==u(o)}},60270:(o,s,i)=>{var u=i(87068),_=i(40346);o.exports=function baseIsEqual(o,s,i,w,x){return o===s||(null==o||null==s||!_(o)&&!_(s)?o!=o&&s!=s:u(o,s,i,w,baseIsEqual,x))}},87068:(o,s,i)=>{var u=i(37217),_=i(25911),w=i(21986),x=i(50689),C=i(5861),j=i(56449),L=i(3656),B=i(37167),$="[object Arguments]",V="[object Array]",U="[object Object]",z=Object.prototype.hasOwnProperty;o.exports=function baseIsEqualDeep(o,s,i,Y,Z,ee){var ie=j(o),ae=j(s),ce=ie?V:C(o),le=ae?V:C(s),pe=(ce=ce==$?U:ce)==U,de=(le=le==$?U:le)==U,fe=ce==le;if(fe&&L(o)){if(!L(s))return!1;ie=!0,pe=!1}if(fe&&!pe)return ee||(ee=new u),ie||B(o)?_(o,s,i,Y,Z,ee):w(o,s,ce,i,Y,Z,ee);if(!(1&i)){var ye=pe&&z.call(o,"__wrapped__"),be=de&&z.call(s,"__wrapped__");if(ye||be){var _e=ye?o.value():o,we=be?s.value():s;return ee||(ee=new u),Z(_e,we,i,Y,ee)}}return!!fe&&(ee||(ee=new u),x(o,s,i,Y,Z,ee))}},29172:(o,s,i)=>{var u=i(5861),_=i(40346);o.exports=function baseIsMap(o){return _(o)&&"[object Map]"==u(o)}},41799:(o,s,i)=>{var u=i(37217),_=i(60270);o.exports=function baseIsMatch(o,s,i,w){var x=i.length,C=x,j=!w;if(null==o)return!C;for(o=Object(o);x--;){var L=i[x];if(j&&L[2]?L[1]!==o[L[0]]:!(L[0]in o))return!1}for(;++x{o.exports=function baseIsNaN(o){return o!=o}},45083:(o,s,i)=>{var u=i(1882),_=i(87296),w=i(23805),x=i(47473),C=/^\[object .+?Constructor\]$/,j=Function.prototype,L=Object.prototype,B=j.toString,$=L.hasOwnProperty,V=RegExp("^"+B.call($).replace(/[\\^$.*+?()[\]{}|]/g,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$");o.exports=function baseIsNative(o){return!(!w(o)||_(o))&&(u(o)?V:C).test(x(o))}},16038:(o,s,i)=>{var u=i(5861),_=i(40346);o.exports=function baseIsSet(o){return _(o)&&"[object Set]"==u(o)}},4901:(o,s,i)=>{var u=i(72552),_=i(30294),w=i(40346),x={};x["[object Float32Array]"]=x["[object Float64Array]"]=x["[object Int8Array]"]=x["[object Int16Array]"]=x["[object Int32Array]"]=x["[object Uint8Array]"]=x["[object Uint8ClampedArray]"]=x["[object Uint16Array]"]=x["[object Uint32Array]"]=!0,x["[object Arguments]"]=x["[object Array]"]=x["[object ArrayBuffer]"]=x["[object Boolean]"]=x["[object DataView]"]=x["[object Date]"]=x["[object Error]"]=x["[object Function]"]=x["[object Map]"]=x["[object Number]"]=x["[object Object]"]=x["[object RegExp]"]=x["[object Set]"]=x["[object String]"]=x["[object WeakMap]"]=!1,o.exports=function baseIsTypedArray(o){return w(o)&&_(o.length)&&!!x[u(o)]}},15389:(o,s,i)=>{var u=i(93663),_=i(87978),w=i(83488),x=i(56449),C=i(50583);o.exports=function baseIteratee(o){return"function"==typeof o?o:null==o?w:"object"==typeof o?x(o)?_(o[0],o[1]):u(o):C(o)}},88984:(o,s,i)=>{var u=i(55527),_=i(3650),w=Object.prototype.hasOwnProperty;o.exports=function baseKeys(o){if(!u(o))return _(o);var s=[];for(var i in Object(o))w.call(o,i)&&"constructor"!=i&&s.push(i);return s}},72903:(o,s,i)=>{var u=i(23805),_=i(55527),w=i(90181),x=Object.prototype.hasOwnProperty;o.exports=function baseKeysIn(o){if(!u(o))return w(o);var s=_(o),i=[];for(var C in o)("constructor"!=C||!s&&x.call(o,C))&&i.push(C);return i}},94033:o=>{o.exports=function baseLodash(){}},93663:(o,s,i)=>{var u=i(41799),_=i(10776),w=i(67197);o.exports=function baseMatches(o){var s=_(o);return 1==s.length&&s[0][2]?w(s[0][0],s[0][1]):function(i){return i===o||u(i,o,s)}}},87978:(o,s,i)=>{var u=i(60270),_=i(58156),w=i(80631),x=i(28586),C=i(30756),j=i(67197),L=i(77797);o.exports=function baseMatchesProperty(o,s){return x(o)&&C(s)?j(L(o),s):function(i){var x=_(i,o);return void 0===x&&x===s?w(i,o):u(s,x,3)}}},85250:(o,s,i)=>{var u=i(37217),_=i(87805),w=i(86649),x=i(42824),C=i(23805),j=i(37241),L=i(14974);o.exports=function baseMerge(o,s,i,B,$){o!==s&&w(s,(function(w,j){if($||($=new u),C(w))x(o,s,j,i,baseMerge,B,$);else{var V=B?B(L(o,j),w,j+"",o,s,$):void 0;void 0===V&&(V=w),_(o,j,V)}}),j)}},42824:(o,s,i)=>{var u=i(87805),_=i(93290),w=i(71961),x=i(23007),C=i(35529),j=i(72428),L=i(56449),B=i(83693),$=i(3656),V=i(1882),U=i(23805),z=i(11331),Y=i(37167),Z=i(14974),ee=i(69884);o.exports=function baseMergeDeep(o,s,i,ie,ae,ce,le){var pe=Z(o,i),de=Z(s,i),fe=le.get(de);if(fe)u(o,i,fe);else{var ye=ce?ce(pe,de,i+"",o,s,le):void 0,be=void 0===ye;if(be){var _e=L(de),we=!_e&&$(de),Se=!_e&&!we&&Y(de);ye=de,_e||we||Se?L(pe)?ye=pe:B(pe)?ye=x(pe):we?(be=!1,ye=_(de,!0)):Se?(be=!1,ye=w(de,!0)):ye=[]:z(de)||j(de)?(ye=pe,j(pe)?ye=ee(pe):U(pe)&&!V(pe)||(ye=C(de))):be=!1}be&&(le.set(de,ye),ae(ye,de,ie,ce,le),le.delete(de)),u(o,i,ye)}}},47237:o=>{o.exports=function baseProperty(o){return function(s){return null==s?void 0:s[o]}}},17255:(o,s,i)=>{var u=i(47422);o.exports=function basePropertyDeep(o){return function(s){return u(s,o)}}},54552:o=>{o.exports=function basePropertyOf(o){return function(s){return null==o?void 0:o[s]}}},85558:o=>{o.exports=function baseReduce(o,s,i,u,_){return _(o,(function(o,_,w){i=u?(u=!1,o):s(i,o,_,w)})),i}},69302:(o,s,i)=>{var u=i(83488),_=i(56757),w=i(32865);o.exports=function baseRest(o,s){return w(_(o,s,u),o+"")}},73170:(o,s,i)=>{var u=i(16547),_=i(31769),w=i(30361),x=i(23805),C=i(77797);o.exports=function baseSet(o,s,i,j){if(!x(o))return o;for(var L=-1,B=(s=_(s,o)).length,$=B-1,V=o;null!=V&&++L{var u=i(83488),_=i(48152),w=_?function(o,s){return _.set(o,s),o}:u;o.exports=w},19570:(o,s,i)=>{var u=i(37334),_=i(93243),w=i(83488),x=_?function(o,s){return _(o,"toString",{configurable:!0,enumerable:!1,value:u(s),writable:!0})}:w;o.exports=x},25160:o=>{o.exports=function baseSlice(o,s,i){var u=-1,_=o.length;s<0&&(s=-s>_?0:_+s),(i=i>_?_:i)<0&&(i+=_),_=s>i?0:i-s>>>0,s>>>=0;for(var w=Array(_);++u<_;)w[u]=o[u+s];return w}},90916:(o,s,i)=>{var u=i(80909);o.exports=function baseSome(o,s){var i;return u(o,(function(o,u,_){return!(i=s(o,u,_))})),!!i}},78096:o=>{o.exports=function baseTimes(o,s){for(var i=-1,u=Array(o);++i{var u=i(51873),_=i(34932),w=i(56449),x=i(44394),C=u?u.prototype:void 0,j=C?C.toString:void 0;o.exports=function baseToString(o){if("string"==typeof o)return o;if(w(o))return _(o,baseToString)+"";if(x(o))return j?j.call(o):"";var s=o+"";return"0"==s&&1/o==-1/0?"-0":s}},54128:(o,s,i)=>{var u=i(31800),_=/^\s+/;o.exports=function baseTrim(o){return o?o.slice(0,u(o)+1).replace(_,""):o}},27301:o=>{o.exports=function baseUnary(o){return function(s){return o(s)}}},19931:(o,s,i)=>{var u=i(31769),_=i(68090),w=i(68969),x=i(77797);o.exports=function baseUnset(o,s){return s=u(s,o),null==(o=w(o,s))||delete o[x(_(s))]}},51234:o=>{o.exports=function baseZipObject(o,s,i){for(var u=-1,_=o.length,w=s.length,x={};++u<_;){var C=u{o.exports=function cacheHas(o,s){return o.has(s)}},31769:(o,s,i)=>{var u=i(56449),_=i(28586),w=i(61802),x=i(13222);o.exports=function castPath(o,s){return u(o)?o:_(o,s)?[o]:w(x(o))}},28754:(o,s,i)=>{var u=i(25160);o.exports=function castSlice(o,s,i){var _=o.length;return i=void 0===i?_:i,!s&&i>=_?o:u(o,s,i)}},49653:(o,s,i)=>{var u=i(37828);o.exports=function cloneArrayBuffer(o){var s=new o.constructor(o.byteLength);return new u(s).set(new u(o)),s}},93290:(o,s,i)=>{o=i.nmd(o);var u=i(9325),_=s&&!s.nodeType&&s,w=_&&o&&!o.nodeType&&o,x=w&&w.exports===_?u.Buffer:void 0,C=x?x.allocUnsafe:void 0;o.exports=function cloneBuffer(o,s){if(s)return o.slice();var i=o.length,u=C?C(i):new o.constructor(i);return o.copy(u),u}},76169:(o,s,i)=>{var u=i(49653);o.exports=function cloneDataView(o,s){var i=s?u(o.buffer):o.buffer;return new o.constructor(i,o.byteOffset,o.byteLength)}},73201:o=>{var s=/\w*$/;o.exports=function cloneRegExp(o){var i=new o.constructor(o.source,s.exec(o));return i.lastIndex=o.lastIndex,i}},93736:(o,s,i)=>{var u=i(51873),_=u?u.prototype:void 0,w=_?_.valueOf:void 0;o.exports=function cloneSymbol(o){return w?Object(w.call(o)):{}}},71961:(o,s,i)=>{var u=i(49653);o.exports=function cloneTypedArray(o,s){var i=s?u(o.buffer):o.buffer;return new o.constructor(i,o.byteOffset,o.length)}},91596:o=>{var s=Math.max;o.exports=function composeArgs(o,i,u,_){for(var w=-1,x=o.length,C=u.length,j=-1,L=i.length,B=s(x-C,0),$=Array(L+B),V=!_;++j{var s=Math.max;o.exports=function composeArgsRight(o,i,u,_){for(var w=-1,x=o.length,C=-1,j=u.length,L=-1,B=i.length,$=s(x-j,0),V=Array($+B),U=!_;++w<$;)V[w]=o[w];for(var z=w;++L{o.exports=function copyArray(o,s){var i=-1,u=o.length;for(s||(s=Array(u));++i{var u=i(16547),_=i(43360);o.exports=function copyObject(o,s,i,w){var x=!i;i||(i={});for(var C=-1,j=s.length;++C{var u=i(21791),_=i(4664);o.exports=function copySymbols(o,s){return u(o,_(o),s)}},48948:(o,s,i)=>{var u=i(21791),_=i(86375);o.exports=function copySymbolsIn(o,s){return u(o,_(o),s)}},55481:(o,s,i)=>{var u=i(9325)["__core-js_shared__"];o.exports=u},58523:o=>{o.exports=function countHolders(o,s){for(var i=o.length,u=0;i--;)o[i]===s&&++u;return u}},20999:(o,s,i)=>{var u=i(69302),_=i(36800);o.exports=function createAssigner(o){return u((function(s,i){var u=-1,w=i.length,x=w>1?i[w-1]:void 0,C=w>2?i[2]:void 0;for(x=o.length>3&&"function"==typeof x?(w--,x):void 0,C&&_(i[0],i[1],C)&&(x=w<3?void 0:x,w=1),s=Object(s);++u{var u=i(64894);o.exports=function createBaseEach(o,s){return function(i,_){if(null==i)return i;if(!u(i))return o(i,_);for(var w=i.length,x=s?w:-1,C=Object(i);(s?x--:++x{o.exports=function createBaseFor(o){return function(s,i,u){for(var _=-1,w=Object(s),x=u(s),C=x.length;C--;){var j=x[o?C:++_];if(!1===i(w[j],j,w))break}return s}}},11842:(o,s,i)=>{var u=i(82819),_=i(9325);o.exports=function createBind(o,s,i){var w=1&s,x=u(o);return function wrapper(){return(this&&this!==_&&this instanceof wrapper?x:o).apply(w?i:this,arguments)}}},12507:(o,s,i)=>{var u=i(28754),_=i(49698),w=i(63912),x=i(13222);o.exports=function createCaseFirst(o){return function(s){s=x(s);var i=_(s)?w(s):void 0,C=i?i[0]:s.charAt(0),j=i?u(i,1).join(""):s.slice(1);return C[o]()+j}}},45539:(o,s,i)=>{var u=i(40882),_=i(50828),w=i(66645),x=RegExp("['’]","g");o.exports=function createCompounder(o){return function(s){return u(w(_(s).replace(x,"")),o,"")}}},82819:(o,s,i)=>{var u=i(39344),_=i(23805);o.exports=function createCtor(o){return function(){var s=arguments;switch(s.length){case 0:return new o;case 1:return new o(s[0]);case 2:return new o(s[0],s[1]);case 3:return new o(s[0],s[1],s[2]);case 4:return new o(s[0],s[1],s[2],s[3]);case 5:return new o(s[0],s[1],s[2],s[3],s[4]);case 6:return new o(s[0],s[1],s[2],s[3],s[4],s[5]);case 7:return new o(s[0],s[1],s[2],s[3],s[4],s[5],s[6])}var i=u(o.prototype),w=o.apply(i,s);return _(w)?w:i}}},77078:(o,s,i)=>{var u=i(91033),_=i(82819),w=i(37471),x=i(18073),C=i(11287),j=i(36306),L=i(9325);o.exports=function createCurry(o,s,i){var B=_(o);return function wrapper(){for(var _=arguments.length,$=Array(_),V=_,U=C(wrapper);V--;)$[V]=arguments[V];var z=_<3&&$[0]!==U&&$[_-1]!==U?[]:j($,U);return(_-=z.length){var u=i(15389),_=i(64894),w=i(95950);o.exports=function createFind(o){return function(s,i,x){var C=Object(s);if(!_(s)){var j=u(i,3);s=w(s),i=function(o){return j(C[o],o,C)}}var L=o(s,i,x);return L>-1?C[j?s[L]:L]:void 0}}},37471:(o,s,i)=>{var u=i(91596),_=i(53320),w=i(58523),x=i(82819),C=i(18073),j=i(11287),L=i(68294),B=i(36306),$=i(9325);o.exports=function createHybrid(o,s,i,V,U,z,Y,Z,ee,ie){var ae=128&s,ce=1&s,le=2&s,pe=24&s,de=512&s,fe=le?void 0:x(o);return function wrapper(){for(var ye=arguments.length,be=Array(ye),_e=ye;_e--;)be[_e]=arguments[_e];if(pe)var we=j(wrapper),Se=w(be,we);if(V&&(be=u(be,V,U,pe)),z&&(be=_(be,z,Y,pe)),ye-=Se,pe&&ye1&&be.reverse(),ae&&ee{var u=i(91033),_=i(82819),w=i(9325);o.exports=function createPartial(o,s,i,x){var C=1&s,j=_(o);return function wrapper(){for(var s=-1,_=arguments.length,L=-1,B=x.length,$=Array(B+_),V=this&&this!==w&&this instanceof wrapper?j:o;++L{var u=i(85087),_=i(54641),w=i(70981);o.exports=function createRecurry(o,s,i,x,C,j,L,B,$,V){var U=8&s;s|=U?32:64,4&(s&=~(U?64:32))||(s&=-4);var z=[o,s,C,U?j:void 0,U?L:void 0,U?void 0:j,U?void 0:L,B,$,V],Y=i.apply(void 0,z);return u(o)&&_(Y,z),Y.placeholder=x,w(Y,o,s)}},66977:(o,s,i)=>{var u=i(68882),_=i(11842),w=i(77078),x=i(37471),C=i(24168),j=i(37381),L=i(3209),B=i(54641),$=i(70981),V=i(61489),U=Math.max;o.exports=function createWrap(o,s,i,z,Y,Z,ee,ie){var ae=2&s;if(!ae&&"function"!=typeof o)throw new TypeError("Expected a function");var ce=z?z.length:0;if(ce||(s&=-97,z=Y=void 0),ee=void 0===ee?ee:U(V(ee),0),ie=void 0===ie?ie:V(ie),ce-=Y?Y.length:0,64&s){var le=z,pe=Y;z=Y=void 0}var de=ae?void 0:j(o),fe=[o,s,i,z,Y,le,pe,Z,ee,ie];if(de&&L(fe,de),o=fe[0],s=fe[1],i=fe[2],z=fe[3],Y=fe[4],!(ie=fe[9]=void 0===fe[9]?ae?0:o.length:U(fe[9]-ce,0))&&24&s&&(s&=-25),s&&1!=s)ye=8==s||16==s?w(o,s,ie):32!=s&&33!=s||Y.length?x.apply(void 0,fe):C(o,s,i,z);else var ye=_(o,s,i);return $((de?u:B)(ye,fe),o,s)}},53138:(o,s,i)=>{var u=i(11331);o.exports=function customOmitClone(o){return u(o)?void 0:o}},24647:(o,s,i)=>{var u=i(54552)({À:"A",Á:"A",Â:"A",Ã:"A",Ä:"A",Å:"A",à:"a",á:"a",â:"a",ã:"a",ä:"a",å:"a",Ç:"C",ç:"c",Ð:"D",ð:"d",È:"E",É:"E",Ê:"E",Ë:"E",è:"e",é:"e",ê:"e",ë:"e",Ì:"I",Í:"I",Î:"I",Ï:"I",ì:"i",í:"i",î:"i",ï:"i",Ñ:"N",ñ:"n",Ò:"O",Ó:"O",Ô:"O",Õ:"O",Ö:"O",Ø:"O",ò:"o",ó:"o",ô:"o",õ:"o",ö:"o",ø:"o",Ù:"U",Ú:"U",Û:"U",Ü:"U",ù:"u",ú:"u",û:"u",ü:"u",Ý:"Y",ý:"y",ÿ:"y",Æ:"Ae",æ:"ae",Þ:"Th",þ:"th",ß:"ss",Ā:"A",Ă:"A",Ą:"A",ā:"a",ă:"a",ą:"a",Ć:"C",Ĉ:"C",Ċ:"C",Č:"C",ć:"c",ĉ:"c",ċ:"c",č:"c",Ď:"D",Đ:"D",ď:"d",đ:"d",Ē:"E",Ĕ:"E",Ė:"E",Ę:"E",Ě:"E",ē:"e",ĕ:"e",ė:"e",ę:"e",ě:"e",Ĝ:"G",Ğ:"G",Ġ:"G",Ģ:"G",ĝ:"g",ğ:"g",ġ:"g",ģ:"g",Ĥ:"H",Ħ:"H",ĥ:"h",ħ:"h",Ĩ:"I",Ī:"I",Ĭ:"I",Į:"I",İ:"I",ĩ:"i",ī:"i",ĭ:"i",į:"i",ı:"i",Ĵ:"J",ĵ:"j",Ķ:"K",ķ:"k",ĸ:"k",Ĺ:"L",Ļ:"L",Ľ:"L",Ŀ:"L",Ł:"L",ĺ:"l",ļ:"l",ľ:"l",ŀ:"l",ł:"l",Ń:"N",Ņ:"N",Ň:"N",Ŋ:"N",ń:"n",ņ:"n",ň:"n",ŋ:"n",Ō:"O",Ŏ:"O",Ő:"O",ō:"o",ŏ:"o",ő:"o",Ŕ:"R",Ŗ:"R",Ř:"R",ŕ:"r",ŗ:"r",ř:"r",Ś:"S",Ŝ:"S",Ş:"S",Š:"S",ś:"s",ŝ:"s",ş:"s",š:"s",Ţ:"T",Ť:"T",Ŧ:"T",ţ:"t",ť:"t",ŧ:"t",Ũ:"U",Ū:"U",Ŭ:"U",Ů:"U",Ű:"U",Ų:"U",ũ:"u",ū:"u",ŭ:"u",ů:"u",ű:"u",ų:"u",Ŵ:"W",ŵ:"w",Ŷ:"Y",ŷ:"y",Ÿ:"Y",Ź:"Z",Ż:"Z",Ž:"Z",ź:"z",ż:"z",ž:"z",IJ:"IJ",ij:"ij",Œ:"Oe",œ:"oe",ʼn:"'n",ſ:"s"});o.exports=u},93243:(o,s,i)=>{var u=i(56110),_=function(){try{var o=u(Object,"defineProperty");return o({},"",{}),o}catch(o){}}();o.exports=_},25911:(o,s,i)=>{var u=i(38859),_=i(14248),w=i(19219);o.exports=function equalArrays(o,s,i,x,C,j){var L=1&i,B=o.length,$=s.length;if(B!=$&&!(L&&$>B))return!1;var V=j.get(o),U=j.get(s);if(V&&U)return V==s&&U==o;var z=-1,Y=!0,Z=2&i?new u:void 0;for(j.set(o,s),j.set(s,o);++z{var u=i(51873),_=i(37828),w=i(75288),x=i(25911),C=i(20317),j=i(84247),L=u?u.prototype:void 0,B=L?L.valueOf:void 0;o.exports=function equalByTag(o,s,i,u,L,$,V){switch(i){case"[object DataView]":if(o.byteLength!=s.byteLength||o.byteOffset!=s.byteOffset)return!1;o=o.buffer,s=s.buffer;case"[object ArrayBuffer]":return!(o.byteLength!=s.byteLength||!$(new _(o),new _(s)));case"[object Boolean]":case"[object Date]":case"[object Number]":return w(+o,+s);case"[object Error]":return o.name==s.name&&o.message==s.message;case"[object RegExp]":case"[object String]":return o==s+"";case"[object Map]":var U=C;case"[object Set]":var z=1&u;if(U||(U=j),o.size!=s.size&&!z)return!1;var Y=V.get(o);if(Y)return Y==s;u|=2,V.set(o,s);var Z=x(U(o),U(s),u,L,$,V);return V.delete(o),Z;case"[object Symbol]":if(B)return B.call(o)==B.call(s)}return!1}},50689:(o,s,i)=>{var u=i(50002),_=Object.prototype.hasOwnProperty;o.exports=function equalObjects(o,s,i,w,x,C){var j=1&i,L=u(o),B=L.length;if(B!=u(s).length&&!j)return!1;for(var $=B;$--;){var V=L[$];if(!(j?V in s:_.call(s,V)))return!1}var U=C.get(o),z=C.get(s);if(U&&z)return U==s&&z==o;var Y=!0;C.set(o,s),C.set(s,o);for(var Z=j;++${var u=i(35970),_=i(56757),w=i(32865);o.exports=function flatRest(o){return w(_(o,void 0,u),o+"")}},34840:(o,s,i)=>{var u="object"==typeof i.g&&i.g&&i.g.Object===Object&&i.g;o.exports=u},50002:(o,s,i)=>{var u=i(82199),_=i(4664),w=i(95950);o.exports=function getAllKeys(o){return u(o,w,_)}},83349:(o,s,i)=>{var u=i(82199),_=i(86375),w=i(37241);o.exports=function getAllKeysIn(o){return u(o,w,_)}},37381:(o,s,i)=>{var u=i(48152),_=i(63950),w=u?function(o){return u.get(o)}:_;o.exports=w},62284:(o,s,i)=>{var u=i(84629),_=Object.prototype.hasOwnProperty;o.exports=function getFuncName(o){for(var s=o.name+"",i=u[s],w=_.call(u,s)?i.length:0;w--;){var x=i[w],C=x.func;if(null==C||C==o)return x.name}return s}},11287:o=>{o.exports=function getHolder(o){return o.placeholder}},12651:(o,s,i)=>{var u=i(74218);o.exports=function getMapData(o,s){var i=o.__data__;return u(s)?i["string"==typeof s?"string":"hash"]:i.map}},10776:(o,s,i)=>{var u=i(30756),_=i(95950);o.exports=function getMatchData(o){for(var s=_(o),i=s.length;i--;){var w=s[i],x=o[w];s[i]=[w,x,u(x)]}return s}},56110:(o,s,i)=>{var u=i(45083),_=i(10392);o.exports=function getNative(o,s){var i=_(o,s);return u(i)?i:void 0}},28879:(o,s,i)=>{var u=i(74335)(Object.getPrototypeOf,Object);o.exports=u},659:(o,s,i)=>{var u=i(51873),_=Object.prototype,w=_.hasOwnProperty,x=_.toString,C=u?u.toStringTag:void 0;o.exports=function getRawTag(o){var s=w.call(o,C),i=o[C];try{o[C]=void 0;var u=!0}catch(o){}var _=x.call(o);return u&&(s?o[C]=i:delete o[C]),_}},4664:(o,s,i)=>{var u=i(79770),_=i(63345),w=Object.prototype.propertyIsEnumerable,x=Object.getOwnPropertySymbols,C=x?function(o){return null==o?[]:(o=Object(o),u(x(o),(function(s){return w.call(o,s)})))}:_;o.exports=C},86375:(o,s,i)=>{var u=i(14528),_=i(28879),w=i(4664),x=i(63345),C=Object.getOwnPropertySymbols?function(o){for(var s=[];o;)u(s,w(o)),o=_(o);return s}:x;o.exports=C},5861:(o,s,i)=>{var u=i(55580),_=i(68223),w=i(32804),x=i(76545),C=i(28303),j=i(72552),L=i(47473),B="[object Map]",$="[object Promise]",V="[object Set]",U="[object WeakMap]",z="[object DataView]",Y=L(u),Z=L(_),ee=L(w),ie=L(x),ae=L(C),ce=j;(u&&ce(new u(new ArrayBuffer(1)))!=z||_&&ce(new _)!=B||w&&ce(w.resolve())!=$||x&&ce(new x)!=V||C&&ce(new C)!=U)&&(ce=function(o){var s=j(o),i="[object Object]"==s?o.constructor:void 0,u=i?L(i):"";if(u)switch(u){case Y:return z;case Z:return B;case ee:return $;case ie:return V;case ae:return U}return s}),o.exports=ce},10392:o=>{o.exports=function getValue(o,s){return null==o?void 0:o[s]}},75251:o=>{var s=/\{\n\/\* \[wrapped with (.+)\] \*/,i=/,? & /;o.exports=function getWrapDetails(o){var u=o.match(s);return u?u[1].split(i):[]}},49326:(o,s,i)=>{var u=i(31769),_=i(72428),w=i(56449),x=i(30361),C=i(30294),j=i(77797);o.exports=function hasPath(o,s,i){for(var L=-1,B=(s=u(s,o)).length,$=!1;++L{var s=RegExp("[\\u200d\\ud800-\\udfff\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff\\ufe0e\\ufe0f]");o.exports=function hasUnicode(o){return s.test(o)}},45434:o=>{var s=/[a-z][A-Z]|[A-Z]{2}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/;o.exports=function hasUnicodeWord(o){return s.test(o)}},22032:(o,s,i)=>{var u=i(81042);o.exports=function hashClear(){this.__data__=u?u(null):{},this.size=0}},63862:o=>{o.exports=function hashDelete(o){var s=this.has(o)&&delete this.__data__[o];return this.size-=s?1:0,s}},66721:(o,s,i)=>{var u=i(81042),_=Object.prototype.hasOwnProperty;o.exports=function hashGet(o){var s=this.__data__;if(u){var i=s[o];return"__lodash_hash_undefined__"===i?void 0:i}return _.call(s,o)?s[o]:void 0}},12749:(o,s,i)=>{var u=i(81042),_=Object.prototype.hasOwnProperty;o.exports=function hashHas(o){var s=this.__data__;return u?void 0!==s[o]:_.call(s,o)}},35749:(o,s,i)=>{var u=i(81042);o.exports=function hashSet(o,s){var i=this.__data__;return this.size+=this.has(o)?0:1,i[o]=u&&void 0===s?"__lodash_hash_undefined__":s,this}},76189:o=>{var s=Object.prototype.hasOwnProperty;o.exports=function initCloneArray(o){var i=o.length,u=new o.constructor(i);return i&&"string"==typeof o[0]&&s.call(o,"index")&&(u.index=o.index,u.input=o.input),u}},77199:(o,s,i)=>{var u=i(49653),_=i(76169),w=i(73201),x=i(93736),C=i(71961);o.exports=function initCloneByTag(o,s,i){var j=o.constructor;switch(s){case"[object ArrayBuffer]":return u(o);case"[object Boolean]":case"[object Date]":return new j(+o);case"[object DataView]":return _(o,i);case"[object Float32Array]":case"[object Float64Array]":case"[object Int8Array]":case"[object Int16Array]":case"[object Int32Array]":case"[object Uint8Array]":case"[object Uint8ClampedArray]":case"[object Uint16Array]":case"[object Uint32Array]":return C(o,i);case"[object Map]":case"[object Set]":return new j;case"[object Number]":case"[object String]":return new j(o);case"[object RegExp]":return w(o);case"[object Symbol]":return x(o)}}},35529:(o,s,i)=>{var u=i(39344),_=i(28879),w=i(55527);o.exports=function initCloneObject(o){return"function"!=typeof o.constructor||w(o)?{}:u(_(o))}},62060:o=>{var s=/\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/;o.exports=function insertWrapDetails(o,i){var u=i.length;if(!u)return o;var _=u-1;return i[_]=(u>1?"& ":"")+i[_],i=i.join(u>2?", ":" "),o.replace(s,"{\n/* [wrapped with "+i+"] */\n")}},45891:(o,s,i)=>{var u=i(51873),_=i(72428),w=i(56449),x=u?u.isConcatSpreadable:void 0;o.exports=function isFlattenable(o){return w(o)||_(o)||!!(x&&o&&o[x])}},30361:o=>{var s=/^(?:0|[1-9]\d*)$/;o.exports=function isIndex(o,i){var u=typeof o;return!!(i=null==i?9007199254740991:i)&&("number"==u||"symbol"!=u&&s.test(o))&&o>-1&&o%1==0&&o{var u=i(75288),_=i(64894),w=i(30361),x=i(23805);o.exports=function isIterateeCall(o,s,i){if(!x(i))return!1;var C=typeof s;return!!("number"==C?_(i)&&w(s,i.length):"string"==C&&s in i)&&u(i[s],o)}},28586:(o,s,i)=>{var u=i(56449),_=i(44394),w=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,x=/^\w*$/;o.exports=function isKey(o,s){if(u(o))return!1;var i=typeof o;return!("number"!=i&&"symbol"!=i&&"boolean"!=i&&null!=o&&!_(o))||(x.test(o)||!w.test(o)||null!=s&&o in Object(s))}},74218:o=>{o.exports=function isKeyable(o){var s=typeof o;return"string"==s||"number"==s||"symbol"==s||"boolean"==s?"__proto__"!==o:null===o}},85087:(o,s,i)=>{var u=i(30980),_=i(37381),w=i(62284),x=i(53758);o.exports=function isLaziable(o){var s=w(o),i=x[s];if("function"!=typeof i||!(s in u.prototype))return!1;if(o===i)return!0;var C=_(i);return!!C&&o===C[0]}},87296:(o,s,i)=>{var u,_=i(55481),w=(u=/[^.]+$/.exec(_&&_.keys&&_.keys.IE_PROTO||""))?"Symbol(src)_1."+u:"";o.exports=function isMasked(o){return!!w&&w in o}},55527:o=>{var s=Object.prototype;o.exports=function isPrototype(o){var i=o&&o.constructor;return o===("function"==typeof i&&i.prototype||s)}},30756:(o,s,i)=>{var u=i(23805);o.exports=function isStrictComparable(o){return o==o&&!u(o)}},63702:o=>{o.exports=function listCacheClear(){this.__data__=[],this.size=0}},70080:(o,s,i)=>{var u=i(26025),_=Array.prototype.splice;o.exports=function listCacheDelete(o){var s=this.__data__,i=u(s,o);return!(i<0)&&(i==s.length-1?s.pop():_.call(s,i,1),--this.size,!0)}},24739:(o,s,i)=>{var u=i(26025);o.exports=function listCacheGet(o){var s=this.__data__,i=u(s,o);return i<0?void 0:s[i][1]}},48655:(o,s,i)=>{var u=i(26025);o.exports=function listCacheHas(o){return u(this.__data__,o)>-1}},31175:(o,s,i)=>{var u=i(26025);o.exports=function listCacheSet(o,s){var i=this.__data__,_=u(i,o);return _<0?(++this.size,i.push([o,s])):i[_][1]=s,this}},63040:(o,s,i)=>{var u=i(21549),_=i(80079),w=i(68223);o.exports=function mapCacheClear(){this.size=0,this.__data__={hash:new u,map:new(w||_),string:new u}}},17670:(o,s,i)=>{var u=i(12651);o.exports=function mapCacheDelete(o){var s=u(this,o).delete(o);return this.size-=s?1:0,s}},90289:(o,s,i)=>{var u=i(12651);o.exports=function mapCacheGet(o){return u(this,o).get(o)}},4509:(o,s,i)=>{var u=i(12651);o.exports=function mapCacheHas(o){return u(this,o).has(o)}},72949:(o,s,i)=>{var u=i(12651);o.exports=function mapCacheSet(o,s){var i=u(this,o),_=i.size;return i.set(o,s),this.size+=i.size==_?0:1,this}},20317:o=>{o.exports=function mapToArray(o){var s=-1,i=Array(o.size);return o.forEach((function(o,u){i[++s]=[u,o]})),i}},67197:o=>{o.exports=function matchesStrictComparable(o,s){return function(i){return null!=i&&(i[o]===s&&(void 0!==s||o in Object(i)))}}},62224:(o,s,i)=>{var u=i(50104);o.exports=function memoizeCapped(o){var s=u(o,(function(o){return 500===i.size&&i.clear(),o})),i=s.cache;return s}},3209:(o,s,i)=>{var u=i(91596),_=i(53320),w=i(36306),x="__lodash_placeholder__",C=128,j=Math.min;o.exports=function mergeData(o,s){var i=o[1],L=s[1],B=i|L,$=B<131,V=L==C&&8==i||L==C&&256==i&&o[7].length<=s[8]||384==L&&s[7].length<=s[8]&&8==i;if(!$&&!V)return o;1&L&&(o[2]=s[2],B|=1&i?0:4);var U=s[3];if(U){var z=o[3];o[3]=z?u(z,U,s[4]):U,o[4]=z?w(o[3],x):s[4]}return(U=s[5])&&(z=o[5],o[5]=z?_(z,U,s[6]):U,o[6]=z?w(o[5],x):s[6]),(U=s[7])&&(o[7]=U),L&C&&(o[8]=null==o[8]?s[8]:j(o[8],s[8])),null==o[9]&&(o[9]=s[9]),o[0]=s[0],o[1]=B,o}},48152:(o,s,i)=>{var u=i(28303),_=u&&new u;o.exports=_},81042:(o,s,i)=>{var u=i(56110)(Object,"create");o.exports=u},3650:(o,s,i)=>{var u=i(74335)(Object.keys,Object);o.exports=u},90181:o=>{o.exports=function nativeKeysIn(o){var s=[];if(null!=o)for(var i in Object(o))s.push(i);return s}},86009:(o,s,i)=>{o=i.nmd(o);var u=i(34840),_=s&&!s.nodeType&&s,w=_&&o&&!o.nodeType&&o,x=w&&w.exports===_&&u.process,C=function(){try{var o=w&&w.require&&w.require("util").types;return o||x&&x.binding&&x.binding("util")}catch(o){}}();o.exports=C},59350:o=>{var s=Object.prototype.toString;o.exports=function objectToString(o){return s.call(o)}},74335:o=>{o.exports=function overArg(o,s){return function(i){return o(s(i))}}},56757:(o,s,i)=>{var u=i(91033),_=Math.max;o.exports=function overRest(o,s,i){return s=_(void 0===s?o.length-1:s,0),function(){for(var w=arguments,x=-1,C=_(w.length-s,0),j=Array(C);++x{var u=i(47422),_=i(25160);o.exports=function parent(o,s){return s.length<2?o:u(o,_(s,0,-1))}},84629:o=>{o.exports={}},68294:(o,s,i)=>{var u=i(23007),_=i(30361),w=Math.min;o.exports=function reorder(o,s){for(var i=o.length,x=w(s.length,i),C=u(o);x--;){var j=s[x];o[x]=_(j,i)?C[j]:void 0}return o}},36306:o=>{var s="__lodash_placeholder__";o.exports=function replaceHolders(o,i){for(var u=-1,_=o.length,w=0,x=[];++u<_;){var C=o[u];C!==i&&C!==s||(o[u]=s,x[w++]=u)}return x}},9325:(o,s,i)=>{var u=i(34840),_="object"==typeof self&&self&&self.Object===Object&&self,w=u||_||Function("return this")();o.exports=w},14974:o=>{o.exports=function safeGet(o,s){if(("constructor"!==s||"function"!=typeof o[s])&&"__proto__"!=s)return o[s]}},31380:o=>{o.exports=function setCacheAdd(o){return this.__data__.set(o,"__lodash_hash_undefined__"),this}},51459:o=>{o.exports=function setCacheHas(o){return this.__data__.has(o)}},54641:(o,s,i)=>{var u=i(68882),_=i(51811)(u);o.exports=_},84247:o=>{o.exports=function setToArray(o){var s=-1,i=Array(o.size);return o.forEach((function(o){i[++s]=o})),i}},32865:(o,s,i)=>{var u=i(19570),_=i(51811)(u);o.exports=_},70981:(o,s,i)=>{var u=i(75251),_=i(62060),w=i(32865),x=i(75948);o.exports=function setWrapToString(o,s,i){var C=s+"";return w(o,_(C,x(u(C),i)))}},51811:o=>{var s=Date.now;o.exports=function shortOut(o){var i=0,u=0;return function(){var _=s(),w=16-(_-u);if(u=_,w>0){if(++i>=800)return arguments[0]}else i=0;return o.apply(void 0,arguments)}}},51420:(o,s,i)=>{var u=i(80079);o.exports=function stackClear(){this.__data__=new u,this.size=0}},90938:o=>{o.exports=function stackDelete(o){var s=this.__data__,i=s.delete(o);return this.size=s.size,i}},63605:o=>{o.exports=function stackGet(o){return this.__data__.get(o)}},29817:o=>{o.exports=function stackHas(o){return this.__data__.has(o)}},80945:(o,s,i)=>{var u=i(80079),_=i(68223),w=i(53661);o.exports=function stackSet(o,s){var i=this.__data__;if(i instanceof u){var x=i.__data__;if(!_||x.length<199)return x.push([o,s]),this.size=++i.size,this;i=this.__data__=new w(x)}return i.set(o,s),this.size=i.size,this}},76959:o=>{o.exports=function strictIndexOf(o,s,i){for(var u=i-1,_=o.length;++u<_;)if(o[u]===s)return u;return-1}},63912:(o,s,i)=>{var u=i(61074),_=i(49698),w=i(42054);o.exports=function stringToArray(o){return _(o)?w(o):u(o)}},61802:(o,s,i)=>{var u=i(62224),_=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,w=/\\(\\)?/g,x=u((function(o){var s=[];return 46===o.charCodeAt(0)&&s.push(""),o.replace(_,(function(o,i,u,_){s.push(u?_.replace(w,"$1"):i||o)})),s}));o.exports=x},77797:(o,s,i)=>{var u=i(44394);o.exports=function toKey(o){if("string"==typeof o||u(o))return o;var s=o+"";return"0"==s&&1/o==-1/0?"-0":s}},47473:o=>{var s=Function.prototype.toString;o.exports=function toSource(o){if(null!=o){try{return s.call(o)}catch(o){}try{return o+""}catch(o){}}return""}},31800:o=>{var s=/\s/;o.exports=function trimmedEndIndex(o){for(var i=o.length;i--&&s.test(o.charAt(i)););return i}},42054:o=>{var s="\\ud800-\\udfff",i="["+s+"]",u="[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]",_="\\ud83c[\\udffb-\\udfff]",w="[^"+s+"]",x="(?:\\ud83c[\\udde6-\\uddff]){2}",C="[\\ud800-\\udbff][\\udc00-\\udfff]",j="(?:"+u+"|"+_+")"+"?",L="[\\ufe0e\\ufe0f]?",B=L+j+("(?:\\u200d(?:"+[w,x,C].join("|")+")"+L+j+")*"),$="(?:"+[w+u+"?",u,x,C,i].join("|")+")",V=RegExp(_+"(?="+_+")|"+$+B,"g");o.exports=function unicodeToArray(o){return o.match(V)||[]}},22225:o=>{var s="\\ud800-\\udfff",i="\\u2700-\\u27bf",u="a-z\\xdf-\\xf6\\xf8-\\xff",_="A-Z\\xc0-\\xd6\\xd8-\\xde",w="\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000",x="["+w+"]",C="\\d+",j="["+i+"]",L="["+u+"]",B="[^"+s+w+C+i+u+_+"]",$="(?:\\ud83c[\\udde6-\\uddff]){2}",V="[\\ud800-\\udbff][\\udc00-\\udfff]",U="["+_+"]",z="(?:"+L+"|"+B+")",Y="(?:"+U+"|"+B+")",Z="(?:['’](?:d|ll|m|re|s|t|ve))?",ee="(?:['’](?:D|LL|M|RE|S|T|VE))?",ie="(?:[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]|\\ud83c[\\udffb-\\udfff])?",ae="[\\ufe0e\\ufe0f]?",ce=ae+ie+("(?:\\u200d(?:"+["[^"+s+"]",$,V].join("|")+")"+ae+ie+")*"),le="(?:"+[j,$,V].join("|")+")"+ce,pe=RegExp([U+"?"+L+"+"+Z+"(?="+[x,U,"$"].join("|")+")",Y+"+"+ee+"(?="+[x,U+z,"$"].join("|")+")",U+"?"+z+"+"+Z,U+"+"+ee,"\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])","\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])",C,le].join("|"),"g");o.exports=function unicodeWords(o){return o.match(pe)||[]}},75948:(o,s,i)=>{var u=i(83729),_=i(15325),w=[["ary",128],["bind",1],["bindKey",2],["curry",8],["curryRight",16],["flip",512],["partial",32],["partialRight",64],["rearg",256]];o.exports=function updateWrapDetails(o,s){return u(w,(function(i){var u="_."+i[0];s&i[1]&&!_(o,u)&&o.push(u)})),o.sort()}},80257:(o,s,i)=>{var u=i(30980),_=i(56017),w=i(23007);o.exports=function wrapperClone(o){if(o instanceof u)return o.clone();var s=new _(o.__wrapped__,o.__chain__);return s.__actions__=w(o.__actions__),s.__index__=o.__index__,s.__values__=o.__values__,s}},64626:(o,s,i)=>{var u=i(66977);o.exports=function ary(o,s,i){return s=i?void 0:s,s=o&&null==s?o.length:s,u(o,128,void 0,void 0,void 0,void 0,s)}},84058:(o,s,i)=>{var u=i(14792),_=i(45539)((function(o,s,i){return s=s.toLowerCase(),o+(i?u(s):s)}));o.exports=_},14792:(o,s,i)=>{var u=i(13222),_=i(55808);o.exports=function capitalize(o){return _(u(o).toLowerCase())}},32629:(o,s,i)=>{var u=i(9999);o.exports=function clone(o){return u(o,4)}},37334:o=>{o.exports=function constant(o){return function(){return o}}},49747:(o,s,i)=>{var u=i(66977);function curry(o,s,i){var _=u(o,8,void 0,void 0,void 0,void 0,void 0,s=i?void 0:s);return _.placeholder=curry.placeholder,_}curry.placeholder={},o.exports=curry},38221:(o,s,i)=>{var u=i(23805),_=i(10124),w=i(99374),x=Math.max,C=Math.min;o.exports=function debounce(o,s,i){var j,L,B,$,V,U,z=0,Y=!1,Z=!1,ee=!0;if("function"!=typeof o)throw new TypeError("Expected a function");function invokeFunc(s){var i=j,u=L;return j=L=void 0,z=s,$=o.apply(u,i)}function shouldInvoke(o){var i=o-U;return void 0===U||i>=s||i<0||Z&&o-z>=B}function timerExpired(){var o=_();if(shouldInvoke(o))return trailingEdge(o);V=setTimeout(timerExpired,function remainingWait(o){var i=s-(o-U);return Z?C(i,B-(o-z)):i}(o))}function trailingEdge(o){return V=void 0,ee&&j?invokeFunc(o):(j=L=void 0,$)}function debounced(){var o=_(),i=shouldInvoke(o);if(j=arguments,L=this,U=o,i){if(void 0===V)return function leadingEdge(o){return z=o,V=setTimeout(timerExpired,s),Y?invokeFunc(o):$}(U);if(Z)return clearTimeout(V),V=setTimeout(timerExpired,s),invokeFunc(U)}return void 0===V&&(V=setTimeout(timerExpired,s)),$}return s=w(s)||0,u(i)&&(Y=!!i.leading,B=(Z="maxWait"in i)?x(w(i.maxWait)||0,s):B,ee="trailing"in i?!!i.trailing:ee),debounced.cancel=function cancel(){void 0!==V&&clearTimeout(V),z=0,j=U=L=V=void 0},debounced.flush=function flush(){return void 0===V?$:trailingEdge(_())},debounced}},50828:(o,s,i)=>{var u=i(24647),_=i(13222),w=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,x=RegExp("[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]","g");o.exports=function deburr(o){return(o=_(o))&&o.replace(w,u).replace(x,"")}},75288:o=>{o.exports=function eq(o,s){return o===s||o!=o&&s!=s}},60680:(o,s,i)=>{var u=i(13222),_=/[\\^$.*+?()[\]{}|]/g,w=RegExp(_.source);o.exports=function escapeRegExp(o){return(o=u(o))&&w.test(o)?o.replace(_,"\\$&"):o}},7309:(o,s,i)=>{var u=i(62006)(i(24713));o.exports=u},24713:(o,s,i)=>{var u=i(2523),_=i(15389),w=i(61489),x=Math.max;o.exports=function findIndex(o,s,i){var C=null==o?0:o.length;if(!C)return-1;var j=null==i?0:w(i);return j<0&&(j=x(C+j,0)),u(o,_(s,3),j)}},35970:(o,s,i)=>{var u=i(83120);o.exports=function flatten(o){return(null==o?0:o.length)?u(o,1):[]}},73424:(o,s,i)=>{var u=i(16962),_=i(2874),w=Array.prototype.push;function baseAry(o,s){return 2==s?function(s,i){return o(s,i)}:function(s){return o(s)}}function cloneArray(o){for(var s=o?o.length:0,i=Array(s);s--;)i[s]=o[s];return i}function wrapImmutable(o,s){return function(){var i=arguments.length;if(i){for(var u=Array(i);i--;)u[i]=arguments[i];var _=u[0]=s.apply(void 0,u);return o.apply(void 0,u),_}}}o.exports=function baseConvert(o,s,i,x){var C="function"==typeof s,j=s===Object(s);if(j&&(x=i,i=s,s=void 0),null==i)throw new TypeError;x||(x={});var L={cap:!("cap"in x)||x.cap,curry:!("curry"in x)||x.curry,fixed:!("fixed"in x)||x.fixed,immutable:!("immutable"in x)||x.immutable,rearg:!("rearg"in x)||x.rearg},B=C?i:_,$="curry"in x&&x.curry,V="fixed"in x&&x.fixed,U="rearg"in x&&x.rearg,z=C?i.runInContext():void 0,Y=C?i:{ary:o.ary,assign:o.assign,clone:o.clone,curry:o.curry,forEach:o.forEach,isArray:o.isArray,isError:o.isError,isFunction:o.isFunction,isWeakMap:o.isWeakMap,iteratee:o.iteratee,keys:o.keys,rearg:o.rearg,toInteger:o.toInteger,toPath:o.toPath},Z=Y.ary,ee=Y.assign,ie=Y.clone,ae=Y.curry,ce=Y.forEach,le=Y.isArray,pe=Y.isError,de=Y.isFunction,fe=Y.isWeakMap,ye=Y.keys,be=Y.rearg,_e=Y.toInteger,we=Y.toPath,Se=ye(u.aryMethod),xe={castArray:function(o){return function(){var s=arguments[0];return le(s)?o(cloneArray(s)):o.apply(void 0,arguments)}},iteratee:function(o){return function(){var s=arguments[1],i=o(arguments[0],s),u=i.length;return L.cap&&"number"==typeof s?(s=s>2?s-2:1,u&&u<=s?i:baseAry(i,s)):i}},mixin:function(o){return function(s){var i=this;if(!de(i))return o(i,Object(s));var u=[];return ce(ye(s),(function(o){de(s[o])&&u.push([o,i.prototype[o]])})),o(i,Object(s)),ce(u,(function(o){var s=o[1];de(s)?i.prototype[o[0]]=s:delete i.prototype[o[0]]})),i}},nthArg:function(o){return function(s){var i=s<0?1:_e(s)+1;return ae(o(s),i)}},rearg:function(o){return function(s,i){var u=i?i.length:0;return ae(o(s,i),u)}},runInContext:function(s){return function(i){return baseConvert(o,s(i),x)}}};function castCap(o,s){if(L.cap){var i=u.iterateeRearg[o];if(i)return function iterateeRearg(o,s){return overArg(o,(function(o){var i=s.length;return function baseArity(o,s){return 2==s?function(s,i){return o.apply(void 0,arguments)}:function(s){return o.apply(void 0,arguments)}}(be(baseAry(o,i),s),i)}))}(s,i);var _=!C&&u.iterateeAry[o];if(_)return function iterateeAry(o,s){return overArg(o,(function(o){return"function"==typeof o?baseAry(o,s):o}))}(s,_)}return s}function castFixed(o,s,i){if(L.fixed&&(V||!u.skipFixed[o])){var _=u.methodSpread[o],x=_&&_.start;return void 0===x?Z(s,i):function flatSpread(o,s){return function(){for(var i=arguments.length,u=i-1,_=Array(i);i--;)_[i]=arguments[i];var x=_[s],C=_.slice(0,s);return x&&w.apply(C,x),s!=u&&w.apply(C,_.slice(s+1)),o.apply(this,C)}}(s,x)}return s}function castRearg(o,s,i){return L.rearg&&i>1&&(U||!u.skipRearg[o])?be(s,u.methodRearg[o]||u.aryRearg[i]):s}function cloneByPath(o,s){for(var i=-1,u=(s=we(s)).length,_=u-1,w=ie(Object(o)),x=w;null!=x&&++i1?ae(s,i):s}(0,_=castCap(w,_),o),!1}})),!_})),_||(_=x),_==s&&(_=$?ae(_,1):function(){return s.apply(this,arguments)}),_.convert=createConverter(w,s),_.placeholder=s.placeholder=i,_}if(!j)return wrap(s,i,B);var Pe=i,Te=[];return ce(Se,(function(o){ce(u.aryMethod[o],(function(o){var s=Pe[u.remap[o]||o];s&&Te.push([o,wrap(o,s,Pe)])}))})),ce(ye(Pe),(function(o){var s=Pe[o];if("function"==typeof s){for(var i=Te.length;i--;)if(Te[i][0]==o)return;s.convert=createConverter(o,s),Te.push([o,s])}})),ce(Te,(function(o){Pe[o[0]]=o[1]})),Pe.convert=function convertLib(o){return Pe.runInContext.convert(o)(void 0)},Pe.placeholder=Pe,ce(ye(Pe),(function(o){ce(u.realToAlias[o]||[],(function(s){Pe[s]=Pe[o]}))})),Pe}},16962:(o,s)=>{s.aliasToReal={each:"forEach",eachRight:"forEachRight",entries:"toPairs",entriesIn:"toPairsIn",extend:"assignIn",extendAll:"assignInAll",extendAllWith:"assignInAllWith",extendWith:"assignInWith",first:"head",conforms:"conformsTo",matches:"isMatch",property:"get",__:"placeholder",F:"stubFalse",T:"stubTrue",all:"every",allPass:"overEvery",always:"constant",any:"some",anyPass:"overSome",apply:"spread",assoc:"set",assocPath:"set",complement:"negate",compose:"flowRight",contains:"includes",dissoc:"unset",dissocPath:"unset",dropLast:"dropRight",dropLastWhile:"dropRightWhile",equals:"isEqual",identical:"eq",indexBy:"keyBy",init:"initial",invertObj:"invert",juxt:"over",omitAll:"omit",nAry:"ary",path:"get",pathEq:"matchesProperty",pathOr:"getOr",paths:"at",pickAll:"pick",pipe:"flow",pluck:"map",prop:"get",propEq:"matchesProperty",propOr:"getOr",props:"at",symmetricDifference:"xor",symmetricDifferenceBy:"xorBy",symmetricDifferenceWith:"xorWith",takeLast:"takeRight",takeLastWhile:"takeRightWhile",unapply:"rest",unnest:"flatten",useWith:"overArgs",where:"conformsTo",whereEq:"isMatch",zipObj:"zipObject"},s.aryMethod={1:["assignAll","assignInAll","attempt","castArray","ceil","create","curry","curryRight","defaultsAll","defaultsDeepAll","floor","flow","flowRight","fromPairs","invert","iteratee","memoize","method","mergeAll","methodOf","mixin","nthArg","over","overEvery","overSome","rest","reverse","round","runInContext","spread","template","trim","trimEnd","trimStart","uniqueId","words","zipAll"],2:["add","after","ary","assign","assignAllWith","assignIn","assignInAllWith","at","before","bind","bindAll","bindKey","chunk","cloneDeepWith","cloneWith","concat","conformsTo","countBy","curryN","curryRightN","debounce","defaults","defaultsDeep","defaultTo","delay","difference","divide","drop","dropRight","dropRightWhile","dropWhile","endsWith","eq","every","filter","find","findIndex","findKey","findLast","findLastIndex","findLastKey","flatMap","flatMapDeep","flattenDepth","forEach","forEachRight","forIn","forInRight","forOwn","forOwnRight","get","groupBy","gt","gte","has","hasIn","includes","indexOf","intersection","invertBy","invoke","invokeMap","isEqual","isMatch","join","keyBy","lastIndexOf","lt","lte","map","mapKeys","mapValues","matchesProperty","maxBy","meanBy","merge","mergeAllWith","minBy","multiply","nth","omit","omitBy","overArgs","pad","padEnd","padStart","parseInt","partial","partialRight","partition","pick","pickBy","propertyOf","pull","pullAll","pullAt","random","range","rangeRight","rearg","reject","remove","repeat","restFrom","result","sampleSize","some","sortBy","sortedIndex","sortedIndexOf","sortedLastIndex","sortedLastIndexOf","sortedUniqBy","split","spreadFrom","startsWith","subtract","sumBy","take","takeRight","takeRightWhile","takeWhile","tap","throttle","thru","times","trimChars","trimCharsEnd","trimCharsStart","truncate","union","uniqBy","uniqWith","unset","unzipWith","without","wrap","xor","zip","zipObject","zipObjectDeep"],3:["assignInWith","assignWith","clamp","differenceBy","differenceWith","findFrom","findIndexFrom","findLastFrom","findLastIndexFrom","getOr","includesFrom","indexOfFrom","inRange","intersectionBy","intersectionWith","invokeArgs","invokeArgsMap","isEqualWith","isMatchWith","flatMapDepth","lastIndexOfFrom","mergeWith","orderBy","padChars","padCharsEnd","padCharsStart","pullAllBy","pullAllWith","rangeStep","rangeStepRight","reduce","reduceRight","replace","set","slice","sortedIndexBy","sortedLastIndexBy","transform","unionBy","unionWith","update","xorBy","xorWith","zipWith"],4:["fill","setWith","updateWith"]},s.aryRearg={2:[1,0],3:[2,0,1],4:[3,2,0,1]},s.iterateeAry={dropRightWhile:1,dropWhile:1,every:1,filter:1,find:1,findFrom:1,findIndex:1,findIndexFrom:1,findKey:1,findLast:1,findLastFrom:1,findLastIndex:1,findLastIndexFrom:1,findLastKey:1,flatMap:1,flatMapDeep:1,flatMapDepth:1,forEach:1,forEachRight:1,forIn:1,forInRight:1,forOwn:1,forOwnRight:1,map:1,mapKeys:1,mapValues:1,partition:1,reduce:2,reduceRight:2,reject:1,remove:1,some:1,takeRightWhile:1,takeWhile:1,times:1,transform:2},s.iterateeRearg={mapKeys:[1],reduceRight:[1,0]},s.methodRearg={assignInAllWith:[1,0],assignInWith:[1,2,0],assignAllWith:[1,0],assignWith:[1,2,0],differenceBy:[1,2,0],differenceWith:[1,2,0],getOr:[2,1,0],intersectionBy:[1,2,0],intersectionWith:[1,2,0],isEqualWith:[1,2,0],isMatchWith:[2,1,0],mergeAllWith:[1,0],mergeWith:[1,2,0],padChars:[2,1,0],padCharsEnd:[2,1,0],padCharsStart:[2,1,0],pullAllBy:[2,1,0],pullAllWith:[2,1,0],rangeStep:[1,2,0],rangeStepRight:[1,2,0],setWith:[3,1,2,0],sortedIndexBy:[2,1,0],sortedLastIndexBy:[2,1,0],unionBy:[1,2,0],unionWith:[1,2,0],updateWith:[3,1,2,0],xorBy:[1,2,0],xorWith:[1,2,0],zipWith:[1,2,0]},s.methodSpread={assignAll:{start:0},assignAllWith:{start:0},assignInAll:{start:0},assignInAllWith:{start:0},defaultsAll:{start:0},defaultsDeepAll:{start:0},invokeArgs:{start:2},invokeArgsMap:{start:2},mergeAll:{start:0},mergeAllWith:{start:0},partial:{start:1},partialRight:{start:1},without:{start:1},zipAll:{start:0}},s.mutate={array:{fill:!0,pull:!0,pullAll:!0,pullAllBy:!0,pullAllWith:!0,pullAt:!0,remove:!0,reverse:!0},object:{assign:!0,assignAll:!0,assignAllWith:!0,assignIn:!0,assignInAll:!0,assignInAllWith:!0,assignInWith:!0,assignWith:!0,defaults:!0,defaultsAll:!0,defaultsDeep:!0,defaultsDeepAll:!0,merge:!0,mergeAll:!0,mergeAllWith:!0,mergeWith:!0},set:{set:!0,setWith:!0,unset:!0,update:!0,updateWith:!0}},s.realToAlias=function(){var o=Object.prototype.hasOwnProperty,i=s.aliasToReal,u={};for(var _ in i){var w=i[_];o.call(u,w)?u[w].push(_):u[w]=[_]}return u}(),s.remap={assignAll:"assign",assignAllWith:"assignWith",assignInAll:"assignIn",assignInAllWith:"assignInWith",curryN:"curry",curryRightN:"curryRight",defaultsAll:"defaults",defaultsDeepAll:"defaultsDeep",findFrom:"find",findIndexFrom:"findIndex",findLastFrom:"findLast",findLastIndexFrom:"findLastIndex",getOr:"get",includesFrom:"includes",indexOfFrom:"indexOf",invokeArgs:"invoke",invokeArgsMap:"invokeMap",lastIndexOfFrom:"lastIndexOf",mergeAll:"merge",mergeAllWith:"mergeWith",padChars:"pad",padCharsEnd:"padEnd",padCharsStart:"padStart",propertyOf:"get",rangeStep:"range",rangeStepRight:"rangeRight",restFrom:"rest",spreadFrom:"spread",trimChars:"trim",trimCharsEnd:"trimEnd",trimCharsStart:"trimStart",zipAll:"zip"},s.skipFixed={castArray:!0,flow:!0,flowRight:!0,iteratee:!0,mixin:!0,rearg:!0,runInContext:!0},s.skipRearg={add:!0,assign:!0,assignIn:!0,bind:!0,bindKey:!0,concat:!0,difference:!0,divide:!0,eq:!0,gt:!0,gte:!0,isEqual:!0,lt:!0,lte:!0,matchesProperty:!0,merge:!0,multiply:!0,overArgs:!0,partial:!0,partialRight:!0,propertyOf:!0,random:!0,range:!0,rangeRight:!0,subtract:!0,zip:!0,zipObject:!0,zipObjectDeep:!0}},47934:(o,s,i)=>{o.exports={ary:i(64626),assign:i(74733),clone:i(32629),curry:i(49747),forEach:i(83729),isArray:i(56449),isError:i(23546),isFunction:i(1882),isWeakMap:i(47886),iteratee:i(33855),keys:i(88984),rearg:i(84195),toInteger:i(61489),toPath:i(42072)}},56367:(o,s,i)=>{o.exports=i(77731)},79920:(o,s,i)=>{var u=i(73424),_=i(47934);o.exports=function convert(o,s,i){return u(_,o,s,i)}},2874:o=>{o.exports={}},77731:(o,s,i)=>{var u=i(79920)("set",i(63560));u.placeholder=i(2874),o.exports=u},58156:(o,s,i)=>{var u=i(47422);o.exports=function get(o,s,i){var _=null==o?void 0:u(o,s);return void 0===_?i:_}},61448:(o,s,i)=>{var u=i(20426),_=i(49326);o.exports=function has(o,s){return null!=o&&_(o,s,u)}},80631:(o,s,i)=>{var u=i(28077),_=i(49326);o.exports=function hasIn(o,s){return null!=o&&_(o,s,u)}},83488:o=>{o.exports=function identity(o){return o}},72428:(o,s,i)=>{var u=i(27534),_=i(40346),w=Object.prototype,x=w.hasOwnProperty,C=w.propertyIsEnumerable,j=u(function(){return arguments}())?u:function(o){return _(o)&&x.call(o,"callee")&&!C.call(o,"callee")};o.exports=j},56449:o=>{var s=Array.isArray;o.exports=s},64894:(o,s,i)=>{var u=i(1882),_=i(30294);o.exports=function isArrayLike(o){return null!=o&&_(o.length)&&!u(o)}},83693:(o,s,i)=>{var u=i(64894),_=i(40346);o.exports=function isArrayLikeObject(o){return _(o)&&u(o)}},53812:(o,s,i)=>{var u=i(72552),_=i(40346);o.exports=function isBoolean(o){return!0===o||!1===o||_(o)&&"[object Boolean]"==u(o)}},3656:(o,s,i)=>{o=i.nmd(o);var u=i(9325),_=i(89935),w=s&&!s.nodeType&&s,x=w&&o&&!o.nodeType&&o,C=x&&x.exports===w?u.Buffer:void 0,j=(C?C.isBuffer:void 0)||_;o.exports=j},62193:(o,s,i)=>{var u=i(88984),_=i(5861),w=i(72428),x=i(56449),C=i(64894),j=i(3656),L=i(55527),B=i(37167),$=Object.prototype.hasOwnProperty;o.exports=function isEmpty(o){if(null==o)return!0;if(C(o)&&(x(o)||"string"==typeof o||"function"==typeof o.splice||j(o)||B(o)||w(o)))return!o.length;var s=_(o);if("[object Map]"==s||"[object Set]"==s)return!o.size;if(L(o))return!u(o).length;for(var i in o)if($.call(o,i))return!1;return!0}},2404:(o,s,i)=>{var u=i(60270);o.exports=function isEqual(o,s){return u(o,s)}},23546:(o,s,i)=>{var u=i(72552),_=i(40346),w=i(11331);o.exports=function isError(o){if(!_(o))return!1;var s=u(o);return"[object Error]"==s||"[object DOMException]"==s||"string"==typeof o.message&&"string"==typeof o.name&&!w(o)}},1882:(o,s,i)=>{var u=i(72552),_=i(23805);o.exports=function isFunction(o){if(!_(o))return!1;var s=u(o);return"[object Function]"==s||"[object GeneratorFunction]"==s||"[object AsyncFunction]"==s||"[object Proxy]"==s}},30294:o=>{o.exports=function isLength(o){return"number"==typeof o&&o>-1&&o%1==0&&o<=9007199254740991}},87730:(o,s,i)=>{var u=i(29172),_=i(27301),w=i(86009),x=w&&w.isMap,C=x?_(x):u;o.exports=C},5187:o=>{o.exports=function isNull(o){return null===o}},98023:(o,s,i)=>{var u=i(72552),_=i(40346);o.exports=function isNumber(o){return"number"==typeof o||_(o)&&"[object Number]"==u(o)}},23805:o=>{o.exports=function isObject(o){var s=typeof o;return null!=o&&("object"==s||"function"==s)}},40346:o=>{o.exports=function isObjectLike(o){return null!=o&&"object"==typeof o}},11331:(o,s,i)=>{var u=i(72552),_=i(28879),w=i(40346),x=Function.prototype,C=Object.prototype,j=x.toString,L=C.hasOwnProperty,B=j.call(Object);o.exports=function isPlainObject(o){if(!w(o)||"[object Object]"!=u(o))return!1;var s=_(o);if(null===s)return!0;var i=L.call(s,"constructor")&&s.constructor;return"function"==typeof i&&i instanceof i&&j.call(i)==B}},38440:(o,s,i)=>{var u=i(16038),_=i(27301),w=i(86009),x=w&&w.isSet,C=x?_(x):u;o.exports=C},85015:(o,s,i)=>{var u=i(72552),_=i(56449),w=i(40346);o.exports=function isString(o){return"string"==typeof o||!_(o)&&w(o)&&"[object String]"==u(o)}},44394:(o,s,i)=>{var u=i(72552),_=i(40346);o.exports=function isSymbol(o){return"symbol"==typeof o||_(o)&&"[object Symbol]"==u(o)}},37167:(o,s,i)=>{var u=i(4901),_=i(27301),w=i(86009),x=w&&w.isTypedArray,C=x?_(x):u;o.exports=C},47886:(o,s,i)=>{var u=i(5861),_=i(40346);o.exports=function isWeakMap(o){return _(o)&&"[object WeakMap]"==u(o)}},33855:(o,s,i)=>{var u=i(9999),_=i(15389);o.exports=function iteratee(o){return _("function"==typeof o?o:u(o,1))}},95950:(o,s,i)=>{var u=i(70695),_=i(88984),w=i(64894);o.exports=function keys(o){return w(o)?u(o):_(o)}},37241:(o,s,i)=>{var u=i(70695),_=i(72903),w=i(64894);o.exports=function keysIn(o){return w(o)?u(o,!0):_(o)}},68090:o=>{o.exports=function last(o){var s=null==o?0:o.length;return s?o[s-1]:void 0}},50104:(o,s,i)=>{var u=i(53661);function memoize(o,s){if("function"!=typeof o||null!=s&&"function"!=typeof s)throw new TypeError("Expected a function");var memoized=function(){var i=arguments,u=s?s.apply(this,i):i[0],_=memoized.cache;if(_.has(u))return _.get(u);var w=o.apply(this,i);return memoized.cache=_.set(u,w)||_,w};return memoized.cache=new(memoize.Cache||u),memoized}memoize.Cache=u,o.exports=memoize},55364:(o,s,i)=>{var u=i(85250),_=i(20999)((function(o,s,i){u(o,s,i)}));o.exports=_},6048:o=>{o.exports=function negate(o){if("function"!=typeof o)throw new TypeError("Expected a function");return function(){var s=arguments;switch(s.length){case 0:return!o.call(this);case 1:return!o.call(this,s[0]);case 2:return!o.call(this,s[0],s[1]);case 3:return!o.call(this,s[0],s[1],s[2])}return!o.apply(this,s)}}},63950:o=>{o.exports=function noop(){}},10124:(o,s,i)=>{var u=i(9325);o.exports=function(){return u.Date.now()}},90179:(o,s,i)=>{var u=i(34932),_=i(9999),w=i(19931),x=i(31769),C=i(21791),j=i(53138),L=i(38816),B=i(83349),$=L((function(o,s){var i={};if(null==o)return i;var L=!1;s=u(s,(function(s){return s=x(s,o),L||(L=s.length>1),s})),C(o,B(o),i),L&&(i=_(i,7,j));for(var $=s.length;$--;)w(i,s[$]);return i}));o.exports=$},50583:(o,s,i)=>{var u=i(47237),_=i(17255),w=i(28586),x=i(77797);o.exports=function property(o){return w(o)?u(x(o)):_(o)}},84195:(o,s,i)=>{var u=i(66977),_=i(38816),w=_((function(o,s){return u(o,256,void 0,void 0,void 0,s)}));o.exports=w},40860:(o,s,i)=>{var u=i(40882),_=i(80909),w=i(15389),x=i(85558),C=i(56449);o.exports=function reduce(o,s,i){var j=C(o)?u:x,L=arguments.length<3;return j(o,w(s,4),i,L,_)}},63560:(o,s,i)=>{var u=i(73170);o.exports=function set(o,s,i){return null==o?o:u(o,s,i)}},42426:(o,s,i)=>{var u=i(14248),_=i(15389),w=i(90916),x=i(56449),C=i(36800);o.exports=function some(o,s,i){var j=x(o)?u:w;return i&&C(o,s,i)&&(s=void 0),j(o,_(s,3))}},63345:o=>{o.exports=function stubArray(){return[]}},89935:o=>{o.exports=function stubFalse(){return!1}},17400:(o,s,i)=>{var u=i(99374),_=1/0;o.exports=function toFinite(o){return o?(o=u(o))===_||o===-1/0?17976931348623157e292*(o<0?-1:1):o==o?o:0:0===o?o:0}},61489:(o,s,i)=>{var u=i(17400);o.exports=function toInteger(o){var s=u(o),i=s%1;return s==s?i?s-i:s:0}},80218:(o,s,i)=>{var u=i(13222);o.exports=function toLower(o){return u(o).toLowerCase()}},99374:(o,s,i)=>{var u=i(54128),_=i(23805),w=i(44394),x=/^[-+]0x[0-9a-f]+$/i,C=/^0b[01]+$/i,j=/^0o[0-7]+$/i,L=parseInt;o.exports=function toNumber(o){if("number"==typeof o)return o;if(w(o))return NaN;if(_(o)){var s="function"==typeof o.valueOf?o.valueOf():o;o=_(s)?s+"":s}if("string"!=typeof o)return 0===o?o:+o;o=u(o);var i=C.test(o);return i||j.test(o)?L(o.slice(2),i?2:8):x.test(o)?NaN:+o}},42072:(o,s,i)=>{var u=i(34932),_=i(23007),w=i(56449),x=i(44394),C=i(61802),j=i(77797),L=i(13222);o.exports=function toPath(o){return w(o)?u(o,j):x(o)?[o]:_(C(L(o)))}},69884:(o,s,i)=>{var u=i(21791),_=i(37241);o.exports=function toPlainObject(o){return u(o,_(o))}},13222:(o,s,i)=>{var u=i(77556);o.exports=function toString(o){return null==o?"":u(o)}},55808:(o,s,i)=>{var u=i(12507)("toUpperCase");o.exports=u},66645:(o,s,i)=>{var u=i(1733),_=i(45434),w=i(13222),x=i(22225);o.exports=function words(o,s,i){return o=w(o),void 0===(s=i?void 0:s)?_(o)?x(o):u(o):o.match(s)||[]}},53758:(o,s,i)=>{var u=i(30980),_=i(56017),w=i(94033),x=i(56449),C=i(40346),j=i(80257),L=Object.prototype.hasOwnProperty;function lodash(o){if(C(o)&&!x(o)&&!(o instanceof u)){if(o instanceof _)return o;if(L.call(o,"__wrapped__"))return j(o)}return new _(o)}lodash.prototype=w.prototype,lodash.prototype.constructor=lodash,o.exports=lodash},47248:(o,s,i)=>{var u=i(16547),_=i(51234);o.exports=function zipObject(o,s){return _(o||[],s||[],u)}},43768:(o,s,i)=>{"use strict";var u=i(45981),_=i(85587);s.highlight=highlight,s.highlightAuto=function highlightAuto(o,s){var i,x,C,j,L=s||{},B=L.subset||u.listLanguages(),$=L.prefix,V=B.length,U=-1;null==$&&($=w);if("string"!=typeof o)throw _("Expected `string` for value, got `%s`",o);x={relevance:0,language:null,value:[]},i={relevance:0,language:null,value:[]};for(;++Ux.relevance&&(x=C),C.relevance>i.relevance&&(x=i,i=C));x.language&&(i.secondBest=x);return i},s.registerLanguage=function registerLanguage(o,s){u.registerLanguage(o,s)},s.listLanguages=function listLanguages(){return u.listLanguages()},s.registerAlias=function registerAlias(o,s){var i,_=o;s&&((_={})[o]=s);for(i in _)u.registerAliases(_[i],{languageName:i})},Emitter.prototype.addText=function text(o){var s,i,u=this.stack;if(""===o)return;s=u[u.length-1],(i=s.children[s.children.length-1])&&"text"===i.type?i.value+=o:s.children.push({type:"text",value:o})},Emitter.prototype.addKeyword=function addKeyword(o,s){this.openNode(s),this.addText(o),this.closeNode()},Emitter.prototype.addSublanguage=function addSublanguage(o,s){var i=this.stack,u=i[i.length-1],_=o.rootNode.children,w=s?{type:"element",tagName:"span",properties:{className:[s]},children:_}:_;u.children=u.children.concat(w)},Emitter.prototype.openNode=function open(o){var s=this.stack,i=this.options.classPrefix+o,u=s[s.length-1],_={type:"element",tagName:"span",properties:{className:[i]},children:[]};u.children.push(_),s.push(_)},Emitter.prototype.closeNode=function close(){this.stack.pop()},Emitter.prototype.closeAllNodes=noop,Emitter.prototype.finalize=noop,Emitter.prototype.toHTML=function toHtmlNoop(){return""};var w="hljs-";function highlight(o,s,i){var x,C=u.configure({}),j=(i||{}).prefix;if("string"!=typeof o)throw _("Expected `string` for name, got `%s`",o);if(!u.getLanguage(o))throw _("Unknown language: `%s` is not registered",o);if("string"!=typeof s)throw _("Expected `string` for value, got `%s`",s);if(null==j&&(j=w),u.configure({__emitter:Emitter,classPrefix:j}),x=u.highlight(s,{language:o,ignoreIllegals:!0}),u.configure(C||{}),x.errorRaised)throw x.errorRaised;return{relevance:x.relevance,language:x.language,value:x.emitter.rootNode.children}}function Emitter(o){this.options=o,this.rootNode={children:[]},this.stack=[this.rootNode]}function noop(){}},92340:(o,s,i)=>{const u=i(6048);function coerceElementMatchingCallback(o){return"string"==typeof o?s=>s.element===o:o.constructor&&o.extend?s=>s instanceof o:o}class ArraySlice{constructor(o){this.elements=o||[]}toValue(){return this.elements.map((o=>o.toValue()))}map(o,s){return this.elements.map(o,s)}flatMap(o,s){return this.map(o,s).reduce(((o,s)=>o.concat(s)),[])}compactMap(o,s){const i=[];return this.forEach((u=>{const _=o.bind(s)(u);_&&i.push(_)})),i}filter(o,s){return o=coerceElementMatchingCallback(o),new ArraySlice(this.elements.filter(o,s))}reject(o,s){return o=coerceElementMatchingCallback(o),new ArraySlice(this.elements.filter(u(o),s))}find(o,s){return o=coerceElementMatchingCallback(o),this.elements.find(o,s)}forEach(o,s){this.elements.forEach(o,s)}reduce(o,s){return this.elements.reduce(o,s)}includes(o){return this.elements.some((s=>s.equals(o)))}shift(){return this.elements.shift()}unshift(o){this.elements.unshift(this.refract(o))}push(o){return this.elements.push(this.refract(o)),this}add(o){this.push(o)}get(o){return this.elements[o]}getValue(o){const s=this.elements[o];if(s)return s.toValue()}get length(){return this.elements.length}get isEmpty(){return 0===this.elements.length}get first(){return this.elements[0]}}"undefined"!=typeof Symbol&&(ArraySlice.prototype[Symbol.iterator]=function symbol(){return this.elements[Symbol.iterator]()}),o.exports=ArraySlice},55973:o=>{class KeyValuePair{constructor(o,s){this.key=o,this.value=s}clone(){const o=new KeyValuePair;return this.key&&(o.key=this.key.clone()),this.value&&(o.value=this.value.clone()),o}}o.exports=KeyValuePair},3110:(o,s,i)=>{const u=i(5187),_=i(85015),w=i(98023),x=i(53812),C=i(23805),j=i(85105),L=i(86804);class Namespace{constructor(o){this.elementMap={},this.elementDetection=[],this.Element=L.Element,this.KeyValuePair=L.KeyValuePair,o&&o.noDefault||this.useDefault(),this._attributeElementKeys=[],this._attributeElementArrayKeys=[]}use(o){return o.namespace&&o.namespace({base:this}),o.load&&o.load({base:this}),this}useDefault(){return this.register("null",L.NullElement).register("string",L.StringElement).register("number",L.NumberElement).register("boolean",L.BooleanElement).register("array",L.ArrayElement).register("object",L.ObjectElement).register("member",L.MemberElement).register("ref",L.RefElement).register("link",L.LinkElement),this.detect(u,L.NullElement,!1).detect(_,L.StringElement,!1).detect(w,L.NumberElement,!1).detect(x,L.BooleanElement,!1).detect(Array.isArray,L.ArrayElement,!1).detect(C,L.ObjectElement,!1),this}register(o,s){return this._elements=void 0,this.elementMap[o]=s,this}unregister(o){return this._elements=void 0,delete this.elementMap[o],this}detect(o,s,i){return void 0===i||i?this.elementDetection.unshift([o,s]):this.elementDetection.push([o,s]),this}toElement(o){if(o instanceof this.Element)return o;let s;for(let i=0;i{const s=o[0].toUpperCase()+o.substr(1);this._elements[s]=this.elementMap[o]}))),this._elements}get serialiser(){return new j(this)}}j.prototype.Namespace=Namespace,o.exports=Namespace},10866:(o,s,i)=>{const u=i(6048),_=i(92340);class ObjectSlice extends _{map(o,s){return this.elements.map((i=>o.bind(s)(i.value,i.key,i)))}filter(o,s){return new ObjectSlice(this.elements.filter((i=>o.bind(s)(i.value,i.key,i))))}reject(o,s){return this.filter(u(o.bind(s)))}forEach(o,s){return this.elements.forEach(((i,u)=>{o.bind(s)(i.value,i.key,i,u)}))}keys(){return this.map(((o,s)=>s.toValue()))}values(){return this.map((o=>o.toValue()))}}o.exports=ObjectSlice},86804:(o,s,i)=>{const u=i(10316),_=i(41067),w=i(71167),x=i(40239),C=i(12242),j=i(6233),L=i(87726),B=i(61045),$=i(86303),V=i(14540),U=i(92340),z=i(10866),Y=i(55973);function refract(o){if(o instanceof u)return o;if("string"==typeof o)return new w(o);if("number"==typeof o)return new x(o);if("boolean"==typeof o)return new C(o);if(null===o)return new _;if(Array.isArray(o))return new j(o.map(refract));if("object"==typeof o){return new B(o)}return o}u.prototype.ObjectElement=B,u.prototype.RefElement=V,u.prototype.MemberElement=L,u.prototype.refract=refract,U.prototype.refract=refract,o.exports={Element:u,NullElement:_,StringElement:w,NumberElement:x,BooleanElement:C,ArrayElement:j,MemberElement:L,ObjectElement:B,LinkElement:$,RefElement:V,refract,ArraySlice:U,ObjectSlice:z,KeyValuePair:Y}},86303:(o,s,i)=>{const u=i(10316);o.exports=class LinkElement extends u{constructor(o,s,i){super(o||[],s,i),this.element="link"}get relation(){return this.attributes.get("relation")}set relation(o){this.attributes.set("relation",o)}get href(){return this.attributes.get("href")}set href(o){this.attributes.set("href",o)}}},14540:(o,s,i)=>{const u=i(10316);o.exports=class RefElement extends u{constructor(o,s,i){super(o||[],s,i),this.element="ref",this.path||(this.path="element")}get path(){return this.attributes.get("path")}set path(o){this.attributes.set("path",o)}}},34035:(o,s,i)=>{const u=i(3110),_=i(86804);s.g$=u,s.KeyValuePair=i(55973),s.G6=_.ArraySlice,s.ot=_.ObjectSlice,s.Hg=_.Element,s.Om=_.StringElement,s.kT=_.NumberElement,s.bd=_.BooleanElement,s.Os=_.NullElement,s.wE=_.ArrayElement,s.Sh=_.ObjectElement,s.Pr=_.MemberElement,s.sI=_.RefElement,s.Ft=_.LinkElement,s.e=_.refract,i(85105),i(75147)},6233:(o,s,i)=>{const u=i(6048),_=i(10316),w=i(92340);class ArrayElement extends _{constructor(o,s,i){super(o||[],s,i),this.element="array"}primitive(){return"array"}get(o){return this.content[o]}getValue(o){const s=this.get(o);if(s)return s.toValue()}getIndex(o){return this.content[o]}set(o,s){return this.content[o]=this.refract(s),this}remove(o){const s=this.content.splice(o,1);return s.length?s[0]:null}map(o,s){return this.content.map(o,s)}flatMap(o,s){return this.map(o,s).reduce(((o,s)=>o.concat(s)),[])}compactMap(o,s){const i=[];return this.forEach((u=>{const _=o.bind(s)(u);_&&i.push(_)})),i}filter(o,s){return new w(this.content.filter(o,s))}reject(o,s){return this.filter(u(o),s)}reduce(o,s){let i,u;void 0!==s?(i=0,u=this.refract(s)):(i=1,u="object"===this.primitive()?this.first.value:this.first);for(let s=i;s{o.bind(s)(i,this.refract(u))}))}shift(){return this.content.shift()}unshift(o){this.content.unshift(this.refract(o))}push(o){return this.content.push(this.refract(o)),this}add(o){this.push(o)}findElements(o,s){const i=s||{},u=!!i.recursive,_=void 0===i.results?[]:i.results;return this.forEach(((s,i,w)=>{u&&void 0!==s.findElements&&s.findElements(o,{results:_,recursive:u}),o(s,i,w)&&_.push(s)})),_}find(o){return new w(this.findElements(o,{recursive:!0}))}findByElement(o){return this.find((s=>s.element===o))}findByClass(o){return this.find((s=>s.classes.includes(o)))}getById(o){return this.find((s=>s.id.toValue()===o)).first}includes(o){return this.content.some((s=>s.equals(o)))}contains(o){return this.includes(o)}empty(){return new this.constructor([])}"fantasy-land/empty"(){return this.empty()}concat(o){return new this.constructor(this.content.concat(o.content))}"fantasy-land/concat"(o){return this.concat(o)}"fantasy-land/map"(o){return new this.constructor(this.map(o))}"fantasy-land/chain"(o){return this.map((s=>o(s)),this).reduce(((o,s)=>o.concat(s)),this.empty())}"fantasy-land/filter"(o){return new this.constructor(this.content.filter(o))}"fantasy-land/reduce"(o,s){return this.content.reduce(o,s)}get length(){return this.content.length}get isEmpty(){return 0===this.content.length}get first(){return this.getIndex(0)}get second(){return this.getIndex(1)}get last(){return this.getIndex(this.length-1)}}ArrayElement.empty=function empty(){return new this},ArrayElement["fantasy-land/empty"]=ArrayElement.empty,"undefined"!=typeof Symbol&&(ArrayElement.prototype[Symbol.iterator]=function symbol(){return this.content[Symbol.iterator]()}),o.exports=ArrayElement},12242:(o,s,i)=>{const u=i(10316);o.exports=class BooleanElement extends u{constructor(o,s,i){super(o,s,i),this.element="boolean"}primitive(){return"boolean"}}},10316:(o,s,i)=>{const u=i(2404),_=i(55973),w=i(92340);class Element{constructor(o,s,i){s&&(this.meta=s),i&&(this.attributes=i),this.content=o}freeze(){Object.isFrozen(this)||(this._meta&&(this.meta.parent=this,this.meta.freeze()),this._attributes&&(this.attributes.parent=this,this.attributes.freeze()),this.children.forEach((o=>{o.parent=this,o.freeze()}),this),this.content&&Array.isArray(this.content)&&Object.freeze(this.content),Object.freeze(this))}primitive(){}clone(){const o=new this.constructor;return o.element=this.element,this.meta.length&&(o._meta=this.meta.clone()),this.attributes.length&&(o._attributes=this.attributes.clone()),this.content?this.content.clone?o.content=this.content.clone():Array.isArray(this.content)?o.content=this.content.map((o=>o.clone())):o.content=this.content:o.content=this.content,o}toValue(){return this.content instanceof Element?this.content.toValue():this.content instanceof _?{key:this.content.key.toValue(),value:this.content.value?this.content.value.toValue():void 0}:this.content&&this.content.map?this.content.map((o=>o.toValue()),this):this.content}toRef(o){if(""===this.id.toValue())throw Error("Cannot create reference to an element that does not contain an ID");const s=new this.RefElement(this.id.toValue());return o&&(s.path=o),s}findRecursive(...o){if(arguments.length>1&&!this.isFrozen)throw new Error("Cannot find recursive with multiple element names without first freezing the element. Call `element.freeze()`");const s=o.pop();let i=new w;const append=(o,s)=>(o.push(s),o),checkElement=(o,i)=>{i.element===s&&o.push(i);const u=i.findRecursive(s);return u&&u.reduce(append,o),i.content instanceof _&&(i.content.key&&checkElement(o,i.content.key),i.content.value&&checkElement(o,i.content.value)),o};return this.content&&(this.content.element&&checkElement(i,this.content),Array.isArray(this.content)&&this.content.reduce(checkElement,i)),o.isEmpty||(i=i.filter((s=>{let i=s.parents.map((o=>o.element));for(const s in o){const u=o[s],_=i.indexOf(u);if(-1===_)return!1;i=i.splice(0,_)}return!0}))),i}set(o){return this.content=o,this}equals(o){return u(this.toValue(),o)}getMetaProperty(o,s){if(!this.meta.hasKey(o)){if(this.isFrozen){const o=this.refract(s);return o.freeze(),o}this.meta.set(o,s)}return this.meta.get(o)}setMetaProperty(o,s){this.meta.set(o,s)}get element(){return this._storedElement||"element"}set element(o){this._storedElement=o}get content(){return this._content}set content(o){if(o instanceof Element)this._content=o;else if(o instanceof w)this.content=o.elements;else if("string"==typeof o||"number"==typeof o||"boolean"==typeof o||"null"===o||null==o)this._content=o;else if(o instanceof _)this._content=o;else if(Array.isArray(o))this._content=o.map(this.refract);else{if("object"!=typeof o)throw new Error("Cannot set content to given value");this._content=Object.keys(o).map((s=>new this.MemberElement(s,o[s])))}}get meta(){if(!this._meta){if(this.isFrozen){const o=new this.ObjectElement;return o.freeze(),o}this._meta=new this.ObjectElement}return this._meta}set meta(o){o instanceof this.ObjectElement?this._meta=o:this.meta.set(o||{})}get attributes(){if(!this._attributes){if(this.isFrozen){const o=new this.ObjectElement;return o.freeze(),o}this._attributes=new this.ObjectElement}return this._attributes}set attributes(o){o instanceof this.ObjectElement?this._attributes=o:this.attributes.set(o||{})}get id(){return this.getMetaProperty("id","")}set id(o){this.setMetaProperty("id",o)}get classes(){return this.getMetaProperty("classes",[])}set classes(o){this.setMetaProperty("classes",o)}get title(){return this.getMetaProperty("title","")}set title(o){this.setMetaProperty("title",o)}get description(){return this.getMetaProperty("description","")}set description(o){this.setMetaProperty("description",o)}get links(){return this.getMetaProperty("links",[])}set links(o){this.setMetaProperty("links",o)}get isFrozen(){return Object.isFrozen(this)}get parents(){let{parent:o}=this;const s=new w;for(;o;)s.push(o),o=o.parent;return s}get children(){if(Array.isArray(this.content))return new w(this.content);if(this.content instanceof _){const o=new w([this.content.key]);return this.content.value&&o.push(this.content.value),o}return this.content instanceof Element?new w([this.content]):new w}get recursiveChildren(){const o=new w;return this.children.forEach((s=>{o.push(s),s.recursiveChildren.forEach((s=>{o.push(s)}))})),o}}o.exports=Element},87726:(o,s,i)=>{const u=i(55973),_=i(10316);o.exports=class MemberElement extends _{constructor(o,s,i,_){super(new u,i,_),this.element="member",this.key=o,this.value=s}get key(){return this.content.key}set key(o){this.content.key=this.refract(o)}get value(){return this.content.value}set value(o){this.content.value=this.refract(o)}}},41067:(o,s,i)=>{const u=i(10316);o.exports=class NullElement extends u{constructor(o,s,i){super(o||null,s,i),this.element="null"}primitive(){return"null"}set(){return new Error("Cannot set the value of null")}}},40239:(o,s,i)=>{const u=i(10316);o.exports=class NumberElement extends u{constructor(o,s,i){super(o,s,i),this.element="number"}primitive(){return"number"}}},61045:(o,s,i)=>{const u=i(6048),_=i(23805),w=i(6233),x=i(87726),C=i(10866);o.exports=class ObjectElement extends w{constructor(o,s,i){super(o||[],s,i),this.element="object"}primitive(){return"object"}toValue(){return this.content.reduce(((o,s)=>(o[s.key.toValue()]=s.value?s.value.toValue():void 0,o)),{})}get(o){const s=this.getMember(o);if(s)return s.value}getMember(o){if(void 0!==o)return this.content.find((s=>s.key.toValue()===o))}remove(o){let s=null;return this.content=this.content.filter((i=>i.key.toValue()!==o||(s=i,!1))),s}getKey(o){const s=this.getMember(o);if(s)return s.key}set(o,s){if(_(o))return Object.keys(o).forEach((s=>{this.set(s,o[s])})),this;const i=o,u=this.getMember(i);return u?u.value=s:this.content.push(new x(i,s)),this}keys(){return this.content.map((o=>o.key.toValue()))}values(){return this.content.map((o=>o.value.toValue()))}hasKey(o){return this.content.some((s=>s.key.equals(o)))}items(){return this.content.map((o=>[o.key.toValue(),o.value.toValue()]))}map(o,s){return this.content.map((i=>o.bind(s)(i.value,i.key,i)))}compactMap(o,s){const i=[];return this.forEach(((u,_,w)=>{const x=o.bind(s)(u,_,w);x&&i.push(x)})),i}filter(o,s){return new C(this.content).filter(o,s)}reject(o,s){return this.filter(u(o),s)}forEach(o,s){return this.content.forEach((i=>o.bind(s)(i.value,i.key,i)))}}},71167:(o,s,i)=>{const u=i(10316);o.exports=class StringElement extends u{constructor(o,s,i){super(o,s,i),this.element="string"}primitive(){return"string"}get length(){return this.content.length}}},75147:(o,s,i)=>{const u=i(85105);o.exports=class JSON06Serialiser extends u{serialise(o){if(!(o instanceof this.namespace.elements.Element))throw new TypeError(`Given element \`${o}\` is not an Element instance`);let s;o._attributes&&o.attributes.get("variable")&&(s=o.attributes.get("variable"));const i={element:o.element};o._meta&&o._meta.length>0&&(i.meta=this.serialiseObject(o.meta));const u="enum"===o.element||-1!==o.attributes.keys().indexOf("enumerations");if(u){const s=this.enumSerialiseAttributes(o);s&&(i.attributes=s)}else if(o._attributes&&o._attributes.length>0){let{attributes:u}=o;u.get("metadata")&&(u=u.clone(),u.set("meta",u.get("metadata")),u.remove("metadata")),"member"===o.element&&s&&(u=u.clone(),u.remove("variable")),u.length>0&&(i.attributes=this.serialiseObject(u))}if(u)i.content=this.enumSerialiseContent(o,i);else if(this[`${o.element}SerialiseContent`])i.content=this[`${o.element}SerialiseContent`](o,i);else if(void 0!==o.content){let u;s&&o.content.key?(u=o.content.clone(),u.key.attributes.set("variable",s),u=this.serialiseContent(u)):u=this.serialiseContent(o.content),this.shouldSerialiseContent(o,u)&&(i.content=u)}else this.shouldSerialiseContent(o,o.content)&&o instanceof this.namespace.elements.Array&&(i.content=[]);return i}shouldSerialiseContent(o,s){return"parseResult"===o.element||"httpRequest"===o.element||"httpResponse"===o.element||"category"===o.element||"link"===o.element||void 0!==s&&(!Array.isArray(s)||0!==s.length)}refSerialiseContent(o,s){return delete s.attributes,{href:o.toValue(),path:o.path.toValue()}}sourceMapSerialiseContent(o){return o.toValue()}dataStructureSerialiseContent(o){return[this.serialiseContent(o.content)]}enumSerialiseAttributes(o){const s=o.attributes.clone(),i=s.remove("enumerations")||new this.namespace.elements.Array([]),u=s.get("default");let _=s.get("samples")||new this.namespace.elements.Array([]);if(u&&u.content&&(u.content.attributes&&u.content.attributes.remove("typeAttributes"),s.set("default",new this.namespace.elements.Array([u.content]))),_.forEach((o=>{o.content&&o.content.element&&o.content.attributes.remove("typeAttributes")})),o.content&&0!==i.length&&_.unshift(o.content),_=_.map((o=>o instanceof this.namespace.elements.Array?[o]:new this.namespace.elements.Array([o.content]))),_.length&&s.set("samples",_),s.length>0)return this.serialiseObject(s)}enumSerialiseContent(o){if(o._attributes){const s=o.attributes.get("enumerations");if(s&&s.length>0)return s.content.map((o=>{const s=o.clone();return s.attributes.remove("typeAttributes"),this.serialise(s)}))}if(o.content){const s=o.content.clone();return s.attributes.remove("typeAttributes"),[this.serialise(s)]}return[]}deserialise(o){if("string"==typeof o)return new this.namespace.elements.String(o);if("number"==typeof o)return new this.namespace.elements.Number(o);if("boolean"==typeof o)return new this.namespace.elements.Boolean(o);if(null===o)return new this.namespace.elements.Null;if(Array.isArray(o))return new this.namespace.elements.Array(o.map(this.deserialise,this));const s=this.namespace.getElementClass(o.element),i=new s;i.element!==o.element&&(i.element=o.element),o.meta&&this.deserialiseObject(o.meta,i.meta),o.attributes&&this.deserialiseObject(o.attributes,i.attributes);const u=this.deserialiseContent(o.content);if(void 0===u&&null!==i.content||(i.content=u),"enum"===i.element){i.content&&i.attributes.set("enumerations",i.content);let o=i.attributes.get("samples");if(i.attributes.remove("samples"),o){const u=o;o=new this.namespace.elements.Array,u.forEach((u=>{u.forEach((u=>{const _=new s(u);_.element=i.element,o.push(_)}))}));const _=o.shift();i.content=_?_.content:void 0,i.attributes.set("samples",o)}else i.content=void 0;let u=i.attributes.get("default");if(u&&u.length>0){u=u.get(0);const o=new s(u);o.element=i.element,i.attributes.set("default",o)}}else if("dataStructure"===i.element&&Array.isArray(i.content))[i.content]=i.content;else if("category"===i.element){const o=i.attributes.get("meta");o&&(i.attributes.set("metadata",o),i.attributes.remove("meta"))}else"member"===i.element&&i.key&&i.key._attributes&&i.key._attributes.getValue("variable")&&(i.attributes.set("variable",i.key.attributes.get("variable")),i.key.attributes.remove("variable"));return i}serialiseContent(o){if(o instanceof this.namespace.elements.Element)return this.serialise(o);if(o instanceof this.namespace.KeyValuePair){const s={key:this.serialise(o.key)};return o.value&&(s.value=this.serialise(o.value)),s}return o&&o.map?o.map(this.serialise,this):o}deserialiseContent(o){if(o){if(o.element)return this.deserialise(o);if(o.key){const s=new this.namespace.KeyValuePair(this.deserialise(o.key));return o.value&&(s.value=this.deserialise(o.value)),s}if(o.map)return o.map(this.deserialise,this)}return o}shouldRefract(o){return!!(o._attributes&&o.attributes.keys().length||o._meta&&o.meta.keys().length)||"enum"!==o.element&&(o.element!==o.primitive()||"member"===o.element)}convertKeyToRefract(o,s){return this.shouldRefract(s)?this.serialise(s):"enum"===s.element?this.serialiseEnum(s):"array"===s.element?s.map((s=>this.shouldRefract(s)||"default"===o?this.serialise(s):"array"===s.element||"object"===s.element||"enum"===s.element?s.children.map((o=>this.serialise(o))):s.toValue())):"object"===s.element?(s.content||[]).map(this.serialise,this):s.toValue()}serialiseEnum(o){return o.children.map((o=>this.serialise(o)))}serialiseObject(o){const s={};return o.forEach(((o,i)=>{if(o){const u=i.toValue();s[u]=this.convertKeyToRefract(u,o)}})),s}deserialiseObject(o,s){Object.keys(o).forEach((i=>{s.set(i,this.deserialise(o[i]))}))}}},85105:o=>{o.exports=class JSONSerialiser{constructor(o){this.namespace=o||new this.Namespace}serialise(o){if(!(o instanceof this.namespace.elements.Element))throw new TypeError(`Given element \`${o}\` is not an Element instance`);const s={element:o.element};o._meta&&o._meta.length>0&&(s.meta=this.serialiseObject(o.meta)),o._attributes&&o._attributes.length>0&&(s.attributes=this.serialiseObject(o.attributes));const i=this.serialiseContent(o.content);return void 0!==i&&(s.content=i),s}deserialise(o){if(!o.element)throw new Error("Given value is not an object containing an element name");const s=new(this.namespace.getElementClass(o.element));s.element!==o.element&&(s.element=o.element),o.meta&&this.deserialiseObject(o.meta,s.meta),o.attributes&&this.deserialiseObject(o.attributes,s.attributes);const i=this.deserialiseContent(o.content);return void 0===i&&null!==s.content||(s.content=i),s}serialiseContent(o){if(o instanceof this.namespace.elements.Element)return this.serialise(o);if(o instanceof this.namespace.KeyValuePair){const s={key:this.serialise(o.key)};return o.value&&(s.value=this.serialise(o.value)),s}if(o&&o.map){if(0===o.length)return;return o.map(this.serialise,this)}return o}deserialiseContent(o){if(o){if(o.element)return this.deserialise(o);if(o.key){const s=new this.namespace.KeyValuePair(this.deserialise(o.key));return o.value&&(s.value=this.deserialise(o.value)),s}if(o.map)return o.map(this.deserialise,this)}return o}serialiseObject(o){const s={};if(o.forEach(((o,i)=>{o&&(s[i.toValue()]=this.serialise(o))})),0!==Object.keys(s).length)return s}deserialiseObject(o,s){Object.keys(o).forEach((i=>{s.set(i,this.deserialise(o[i]))}))}}},58859:(o,s,i)=>{var u="function"==typeof Map&&Map.prototype,_=Object.getOwnPropertyDescriptor&&u?Object.getOwnPropertyDescriptor(Map.prototype,"size"):null,w=u&&_&&"function"==typeof _.get?_.get:null,x=u&&Map.prototype.forEach,C="function"==typeof Set&&Set.prototype,j=Object.getOwnPropertyDescriptor&&C?Object.getOwnPropertyDescriptor(Set.prototype,"size"):null,L=C&&j&&"function"==typeof j.get?j.get:null,B=C&&Set.prototype.forEach,$="function"==typeof WeakMap&&WeakMap.prototype?WeakMap.prototype.has:null,V="function"==typeof WeakSet&&WeakSet.prototype?WeakSet.prototype.has:null,U="function"==typeof WeakRef&&WeakRef.prototype?WeakRef.prototype.deref:null,z=Boolean.prototype.valueOf,Y=Object.prototype.toString,Z=Function.prototype.toString,ee=String.prototype.match,ie=String.prototype.slice,ae=String.prototype.replace,ce=String.prototype.toUpperCase,le=String.prototype.toLowerCase,pe=RegExp.prototype.test,de=Array.prototype.concat,fe=Array.prototype.join,ye=Array.prototype.slice,be=Math.floor,_e="function"==typeof BigInt?BigInt.prototype.valueOf:null,we=Object.getOwnPropertySymbols,Se="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?Symbol.prototype.toString:null,xe="function"==typeof Symbol&&"object"==typeof Symbol.iterator,Pe="function"==typeof Symbol&&Symbol.toStringTag&&(typeof Symbol.toStringTag===xe||"symbol")?Symbol.toStringTag:null,Te=Object.prototype.propertyIsEnumerable,Re=("function"==typeof Reflect?Reflect.getPrototypeOf:Object.getPrototypeOf)||([].__proto__===Array.prototype?function(o){return o.__proto__}:null);function addNumericSeparator(o,s){if(o===1/0||o===-1/0||o!=o||o&&o>-1e3&&o<1e3||pe.call(/e/,s))return s;var i=/[0-9](?=(?:[0-9]{3})+(?![0-9]))/g;if("number"==typeof o){var u=o<0?-be(-o):be(o);if(u!==o){var _=String(u),w=ie.call(s,_.length+1);return ae.call(_,i,"$&_")+"."+ae.call(ae.call(w,/([0-9]{3})/g,"$&_"),/_$/,"")}}return ae.call(s,i,"$&_")}var qe=i(42634),$e=qe.custom,ze=isSymbol($e)?$e:null;function wrapQuotes(o,s,i){var u="double"===(i.quoteStyle||s)?'"':"'";return u+o+u}function quote(o){return ae.call(String(o),/"/g,""")}function isArray(o){return!("[object Array]"!==toStr(o)||Pe&&"object"==typeof o&&Pe in o)}function isRegExp(o){return!("[object RegExp]"!==toStr(o)||Pe&&"object"==typeof o&&Pe in o)}function isSymbol(o){if(xe)return o&&"object"==typeof o&&o instanceof Symbol;if("symbol"==typeof o)return!0;if(!o||"object"!=typeof o||!Se)return!1;try{return Se.call(o),!0}catch(o){}return!1}o.exports=function inspect_(o,s,u,_){var C=s||{};if(has(C,"quoteStyle")&&"single"!==C.quoteStyle&&"double"!==C.quoteStyle)throw new TypeError('option "quoteStyle" must be "single" or "double"');if(has(C,"maxStringLength")&&("number"==typeof C.maxStringLength?C.maxStringLength<0&&C.maxStringLength!==1/0:null!==C.maxStringLength))throw new TypeError('option "maxStringLength", if provided, must be a positive integer, Infinity, or `null`');var j=!has(C,"customInspect")||C.customInspect;if("boolean"!=typeof j&&"symbol"!==j)throw new TypeError("option \"customInspect\", if provided, must be `true`, `false`, or `'symbol'`");if(has(C,"indent")&&null!==C.indent&&"\t"!==C.indent&&!(parseInt(C.indent,10)===C.indent&&C.indent>0))throw new TypeError('option "indent" must be "\\t", an integer > 0, or `null`');if(has(C,"numericSeparator")&&"boolean"!=typeof C.numericSeparator)throw new TypeError('option "numericSeparator", if provided, must be `true` or `false`');var Y=C.numericSeparator;if(void 0===o)return"undefined";if(null===o)return"null";if("boolean"==typeof o)return o?"true":"false";if("string"==typeof o)return inspectString(o,C);if("number"==typeof o){if(0===o)return 1/0/o>0?"0":"-0";var ce=String(o);return Y?addNumericSeparator(o,ce):ce}if("bigint"==typeof o){var pe=String(o)+"n";return Y?addNumericSeparator(o,pe):pe}var be=void 0===C.depth?5:C.depth;if(void 0===u&&(u=0),u>=be&&be>0&&"object"==typeof o)return isArray(o)?"[Array]":"[Object]";var we=function getIndent(o,s){var i;if("\t"===o.indent)i="\t";else{if(!("number"==typeof o.indent&&o.indent>0))return null;i=fe.call(Array(o.indent+1)," ")}return{base:i,prev:fe.call(Array(s+1),i)}}(C,u);if(void 0===_)_=[];else if(indexOf(_,o)>=0)return"[Circular]";function inspect(o,s,i){if(s&&(_=ye.call(_)).push(s),i){var w={depth:C.depth};return has(C,"quoteStyle")&&(w.quoteStyle=C.quoteStyle),inspect_(o,w,u+1,_)}return inspect_(o,C,u+1,_)}if("function"==typeof o&&!isRegExp(o)){var $e=function nameOf(o){if(o.name)return o.name;var s=ee.call(Z.call(o),/^function\s*([\w$]+)/);if(s)return s[1];return null}(o),We=arrObjKeys(o,inspect);return"[Function"+($e?": "+$e:" (anonymous)")+"]"+(We.length>0?" { "+fe.call(We,", ")+" }":"")}if(isSymbol(o)){var He=xe?ae.call(String(o),/^(Symbol\(.*\))_[^)]*$/,"$1"):Se.call(o);return"object"!=typeof o||xe?He:markBoxed(He)}if(function isElement(o){if(!o||"object"!=typeof o)return!1;if("undefined"!=typeof HTMLElement&&o instanceof HTMLElement)return!0;return"string"==typeof o.nodeName&&"function"==typeof o.getAttribute}(o)){for(var Ye="<"+le.call(String(o.nodeName)),Xe=o.attributes||[],Qe=0;Qe"}if(isArray(o)){if(0===o.length)return"[]";var et=arrObjKeys(o,inspect);return we&&!function singleLineValues(o){for(var s=0;s=0)return!1;return!0}(et)?"["+indentedJoin(et,we)+"]":"[ "+fe.call(et,", ")+" ]"}if(function isError(o){return!("[object Error]"!==toStr(o)||Pe&&"object"==typeof o&&Pe in o)}(o)){var tt=arrObjKeys(o,inspect);return"cause"in Error.prototype||!("cause"in o)||Te.call(o,"cause")?0===tt.length?"["+String(o)+"]":"{ ["+String(o)+"] "+fe.call(tt,", ")+" }":"{ ["+String(o)+"] "+fe.call(de.call("[cause]: "+inspect(o.cause),tt),", ")+" }"}if("object"==typeof o&&j){if(ze&&"function"==typeof o[ze]&&qe)return qe(o,{depth:be-u});if("symbol"!==j&&"function"==typeof o.inspect)return o.inspect()}if(function isMap(o){if(!w||!o||"object"!=typeof o)return!1;try{w.call(o);try{L.call(o)}catch(o){return!0}return o instanceof Map}catch(o){}return!1}(o)){var rt=[];return x&&x.call(o,(function(s,i){rt.push(inspect(i,o,!0)+" => "+inspect(s,o))})),collectionOf("Map",w.call(o),rt,we)}if(function isSet(o){if(!L||!o||"object"!=typeof o)return!1;try{L.call(o);try{w.call(o)}catch(o){return!0}return o instanceof Set}catch(o){}return!1}(o)){var nt=[];return B&&B.call(o,(function(s){nt.push(inspect(s,o))})),collectionOf("Set",L.call(o),nt,we)}if(function isWeakMap(o){if(!$||!o||"object"!=typeof o)return!1;try{$.call(o,$);try{V.call(o,V)}catch(o){return!0}return o instanceof WeakMap}catch(o){}return!1}(o))return weakCollectionOf("WeakMap");if(function isWeakSet(o){if(!V||!o||"object"!=typeof o)return!1;try{V.call(o,V);try{$.call(o,$)}catch(o){return!0}return o instanceof WeakSet}catch(o){}return!1}(o))return weakCollectionOf("WeakSet");if(function isWeakRef(o){if(!U||!o||"object"!=typeof o)return!1;try{return U.call(o),!0}catch(o){}return!1}(o))return weakCollectionOf("WeakRef");if(function isNumber(o){return!("[object Number]"!==toStr(o)||Pe&&"object"==typeof o&&Pe in o)}(o))return markBoxed(inspect(Number(o)));if(function isBigInt(o){if(!o||"object"!=typeof o||!_e)return!1;try{return _e.call(o),!0}catch(o){}return!1}(o))return markBoxed(inspect(_e.call(o)));if(function isBoolean(o){return!("[object Boolean]"!==toStr(o)||Pe&&"object"==typeof o&&Pe in o)}(o))return markBoxed(z.call(o));if(function isString(o){return!("[object String]"!==toStr(o)||Pe&&"object"==typeof o&&Pe in o)}(o))return markBoxed(inspect(String(o)));if("undefined"!=typeof window&&o===window)return"{ [object Window] }";if(o===i.g)return"{ [object globalThis] }";if(!function isDate(o){return!("[object Date]"!==toStr(o)||Pe&&"object"==typeof o&&Pe in o)}(o)&&!isRegExp(o)){var ot=arrObjKeys(o,inspect),st=Re?Re(o)===Object.prototype:o instanceof Object||o.constructor===Object,it=o instanceof Object?"":"null prototype",at=!st&&Pe&&Object(o)===o&&Pe in o?ie.call(toStr(o),8,-1):it?"Object":"",ct=(st||"function"!=typeof o.constructor?"":o.constructor.name?o.constructor.name+" ":"")+(at||it?"["+fe.call(de.call([],at||[],it||[]),": ")+"] ":"");return 0===ot.length?ct+"{}":we?ct+"{"+indentedJoin(ot,we)+"}":ct+"{ "+fe.call(ot,", ")+" }"}return String(o)};var We=Object.prototype.hasOwnProperty||function(o){return o in this};function has(o,s){return We.call(o,s)}function toStr(o){return Y.call(o)}function indexOf(o,s){if(o.indexOf)return o.indexOf(s);for(var i=0,u=o.length;is.maxStringLength){var i=o.length-s.maxStringLength,u="... "+i+" more character"+(i>1?"s":"");return inspectString(ie.call(o,0,s.maxStringLength),s)+u}return wrapQuotes(ae.call(ae.call(o,/(['\\])/g,"\\$1"),/[\x00-\x1f]/g,lowbyte),"single",s)}function lowbyte(o){var s=o.charCodeAt(0),i={8:"b",9:"t",10:"n",12:"f",13:"r"}[s];return i?"\\"+i:"\\x"+(s<16?"0":"")+ce.call(s.toString(16))}function markBoxed(o){return"Object("+o+")"}function weakCollectionOf(o){return o+" { ? }"}function collectionOf(o,s,i,u){return o+" ("+s+") {"+(u?indentedJoin(i,u):fe.call(i,", "))+"}"}function indentedJoin(o,s){if(0===o.length)return"";var i="\n"+s.prev+s.base;return i+fe.call(o,","+i)+"\n"+s.prev}function arrObjKeys(o,s){var i=isArray(o),u=[];if(i){u.length=o.length;for(var _=0;_{var s,i,u=o.exports={};function defaultSetTimout(){throw new Error("setTimeout has not been defined")}function defaultClearTimeout(){throw new Error("clearTimeout has not been defined")}function runTimeout(o){if(s===setTimeout)return setTimeout(o,0);if((s===defaultSetTimout||!s)&&setTimeout)return s=setTimeout,setTimeout(o,0);try{return s(o,0)}catch(i){try{return s.call(null,o,0)}catch(i){return s.call(this,o,0)}}}!function(){try{s="function"==typeof setTimeout?setTimeout:defaultSetTimout}catch(o){s=defaultSetTimout}try{i="function"==typeof clearTimeout?clearTimeout:defaultClearTimeout}catch(o){i=defaultClearTimeout}}();var _,w=[],x=!1,C=-1;function cleanUpNextTick(){x&&_&&(x=!1,_.length?w=_.concat(w):C=-1,w.length&&drainQueue())}function drainQueue(){if(!x){var o=runTimeout(cleanUpNextTick);x=!0;for(var s=w.length;s;){for(_=w,w=[];++C1)for(var i=1;i{"use strict";var u=i(6925);function emptyFunction(){}function emptyFunctionWithReset(){}emptyFunctionWithReset.resetWarningCache=emptyFunction,o.exports=function(){function shim(o,s,i,_,w,x){if(x!==u){var C=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw C.name="Invariant Violation",C}}function getShim(){return shim}shim.isRequired=shim;var o={array:shim,bigint:shim,bool:shim,func:shim,number:shim,object:shim,string:shim,symbol:shim,any:shim,arrayOf:getShim,element:shim,elementType:shim,instanceOf:getShim,node:shim,objectOf:getShim,oneOf:getShim,oneOfType:getShim,shape:getShim,exact:getShim,checkPropTypes:emptyFunctionWithReset,resetWarningCache:emptyFunction};return o.PropTypes=o,o}},5556:(o,s,i)=>{o.exports=i(2694)()},6925:o=>{"use strict";o.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"},74765:o=>{"use strict";var s=String.prototype.replace,i=/%20/g,u="RFC1738",_="RFC3986";o.exports={default:_,formatters:{RFC1738:function(o){return s.call(o,i,"+")},RFC3986:function(o){return String(o)}},RFC1738:u,RFC3986:_}},55373:(o,s,i)=>{"use strict";var u=i(98636),_=i(62642),w=i(74765);o.exports={formats:w,parse:_,stringify:u}},62642:(o,s,i)=>{"use strict";var u=i(37720),_=Object.prototype.hasOwnProperty,w=Array.isArray,x={allowDots:!1,allowPrototypes:!1,allowSparse:!1,arrayLimit:20,charset:"utf-8",charsetSentinel:!1,comma:!1,decoder:u.decode,delimiter:"&",depth:5,ignoreQueryPrefix:!1,interpretNumericEntities:!1,parameterLimit:1e3,parseArrays:!0,plainObjects:!1,strictNullHandling:!1},interpretNumericEntities=function(o){return o.replace(/&#(\d+);/g,(function(o,s){return String.fromCharCode(parseInt(s,10))}))},parseArrayValue=function(o,s){return o&&"string"==typeof o&&s.comma&&o.indexOf(",")>-1?o.split(","):o},C=function parseQueryStringKeys(o,s,i,u){if(o){var w=i.allowDots?o.replace(/\.([^.[]+)/g,"[$1]"):o,x=/(\[[^[\]]*])/g,C=i.depth>0&&/(\[[^[\]]*])/.exec(w),j=C?w.slice(0,C.index):w,L=[];if(j){if(!i.plainObjects&&_.call(Object.prototype,j)&&!i.allowPrototypes)return;L.push(j)}for(var B=0;i.depth>0&&null!==(C=x.exec(w))&&B=0;--w){var x,C=o[w];if("[]"===C&&i.parseArrays)x=[].concat(_);else{x=i.plainObjects?Object.create(null):{};var j="["===C.charAt(0)&&"]"===C.charAt(C.length-1)?C.slice(1,-1):C,L=parseInt(j,10);i.parseArrays||""!==j?!isNaN(L)&&C!==j&&String(L)===j&&L>=0&&i.parseArrays&&L<=i.arrayLimit?(x=[])[L]=_:"__proto__"!==j&&(x[j]=_):x={0:_}}_=x}return _}(L,s,i,u)}};o.exports=function(o,s){var i=function normalizeParseOptions(o){if(!o)return x;if(null!==o.decoder&&void 0!==o.decoder&&"function"!=typeof o.decoder)throw new TypeError("Decoder has to be a function.");if(void 0!==o.charset&&"utf-8"!==o.charset&&"iso-8859-1"!==o.charset)throw new TypeError("The charset option must be either utf-8, iso-8859-1, or undefined");var s=void 0===o.charset?x.charset:o.charset;return{allowDots:void 0===o.allowDots?x.allowDots:!!o.allowDots,allowPrototypes:"boolean"==typeof o.allowPrototypes?o.allowPrototypes:x.allowPrototypes,allowSparse:"boolean"==typeof o.allowSparse?o.allowSparse:x.allowSparse,arrayLimit:"number"==typeof o.arrayLimit?o.arrayLimit:x.arrayLimit,charset:s,charsetSentinel:"boolean"==typeof o.charsetSentinel?o.charsetSentinel:x.charsetSentinel,comma:"boolean"==typeof o.comma?o.comma:x.comma,decoder:"function"==typeof o.decoder?o.decoder:x.decoder,delimiter:"string"==typeof o.delimiter||u.isRegExp(o.delimiter)?o.delimiter:x.delimiter,depth:"number"==typeof o.depth||!1===o.depth?+o.depth:x.depth,ignoreQueryPrefix:!0===o.ignoreQueryPrefix,interpretNumericEntities:"boolean"==typeof o.interpretNumericEntities?o.interpretNumericEntities:x.interpretNumericEntities,parameterLimit:"number"==typeof o.parameterLimit?o.parameterLimit:x.parameterLimit,parseArrays:!1!==o.parseArrays,plainObjects:"boolean"==typeof o.plainObjects?o.plainObjects:x.plainObjects,strictNullHandling:"boolean"==typeof o.strictNullHandling?o.strictNullHandling:x.strictNullHandling}}(s);if(""===o||null==o)return i.plainObjects?Object.create(null):{};for(var j="string"==typeof o?function parseQueryStringValues(o,s){var i,C={},j=s.ignoreQueryPrefix?o.replace(/^\?/,""):o,L=s.parameterLimit===1/0?void 0:s.parameterLimit,B=j.split(s.delimiter,L),$=-1,V=s.charset;if(s.charsetSentinel)for(i=0;i-1&&(z=w(z)?[z]:z),_.call(C,U)?C[U]=u.combine(C[U],z):C[U]=z}return C}(o,i):o,L=i.plainObjects?Object.create(null):{},B=Object.keys(j),$=0;${"use strict";var u=i(920),_=i(37720),w=i(74765),x=Object.prototype.hasOwnProperty,C={brackets:function brackets(o){return o+"[]"},comma:"comma",indices:function indices(o,s){return o+"["+s+"]"},repeat:function repeat(o){return o}},j=Array.isArray,L=String.prototype.split,B=Array.prototype.push,pushToArray=function(o,s){B.apply(o,j(s)?s:[s])},$=Date.prototype.toISOString,V=w.default,U={addQueryPrefix:!1,allowDots:!1,charset:"utf-8",charsetSentinel:!1,delimiter:"&",encode:!0,encoder:_.encode,encodeValuesOnly:!1,format:V,formatter:w.formatters[V],indices:!1,serializeDate:function serializeDate(o){return $.call(o)},skipNulls:!1,strictNullHandling:!1},z={},Y=function stringify(o,s,i,w,x,C,B,$,V,Y,Z,ee,ie,ae,ce,le){for(var pe=o,de=le,fe=0,ye=!1;void 0!==(de=de.get(z))&&!ye;){var be=de.get(o);if(fe+=1,void 0!==be){if(be===fe)throw new RangeError("Cyclic object value");ye=!0}void 0===de.get(z)&&(fe=0)}if("function"==typeof $?pe=$(s,pe):pe instanceof Date?pe=Z(pe):"comma"===i&&j(pe)&&(pe=_.maybeMap(pe,(function(o){return o instanceof Date?Z(o):o}))),null===pe){if(x)return B&&!ae?B(s,U.encoder,ce,"key",ee):s;pe=""}if(function isNonNullishPrimitive(o){return"string"==typeof o||"number"==typeof o||"boolean"==typeof o||"symbol"==typeof o||"bigint"==typeof o}(pe)||_.isBuffer(pe)){if(B){var _e=ae?s:B(s,U.encoder,ce,"key",ee);if("comma"===i&&ae){for(var we=L.call(String(pe),","),Se="",xe=0;xe0?pe.join(",")||null:void 0}];else if(j($))Pe=$;else{var Re=Object.keys(pe);Pe=V?Re.sort(V):Re}for(var qe=w&&j(pe)&&1===pe.length?s+"[]":s,$e=0;$e0?ce+ae:""}},37720:(o,s,i)=>{"use strict";var u=i(74765),_=Object.prototype.hasOwnProperty,w=Array.isArray,x=function(){for(var o=[],s=0;s<256;++s)o.push("%"+((s<16?"0":"")+s.toString(16)).toUpperCase());return o}(),C=function arrayToObject(o,s){for(var i=s&&s.plainObjects?Object.create(null):{},u=0;u1;){var s=o.pop(),i=s.obj[s.prop];if(w(i)){for(var u=[],_=0;_=48&&B<=57||B>=65&&B<=90||B>=97&&B<=122||w===u.RFC1738&&(40===B||41===B)?j+=C.charAt(L):B<128?j+=x[B]:B<2048?j+=x[192|B>>6]+x[128|63&B]:B<55296||B>=57344?j+=x[224|B>>12]+x[128|B>>6&63]+x[128|63&B]:(L+=1,B=65536+((1023&B)<<10|1023&C.charCodeAt(L)),j+=x[240|B>>18]+x[128|B>>12&63]+x[128|B>>6&63]+x[128|63&B])}return j},isBuffer:function isBuffer(o){return!(!o||"object"!=typeof o)&&!!(o.constructor&&o.constructor.isBuffer&&o.constructor.isBuffer(o))},isRegExp:function isRegExp(o){return"[object RegExp]"===Object.prototype.toString.call(o)},maybeMap:function maybeMap(o,s){if(w(o)){for(var i=[],u=0;u{"use strict";var i=Object.prototype.hasOwnProperty;function decode(o){try{return decodeURIComponent(o.replace(/\+/g," "))}catch(o){return null}}function encode(o){try{return encodeURIComponent(o)}catch(o){return null}}s.stringify=function querystringify(o,s){s=s||"";var u,_,w=[];for(_ in"string"!=typeof s&&(s="?"),o)if(i.call(o,_)){if((u=o[_])||null!=u&&!isNaN(u)||(u=""),_=encode(_),u=encode(u),null===_||null===u)continue;w.push(_+"="+u)}return w.length?s+w.join("&"):""},s.parse=function querystring(o){for(var s,i=/([^=?#&]+)=?([^&]*)/g,u={};s=i.exec(o);){var _=decode(s[1]),w=decode(s[2]);null===_||null===w||_ in u||(u[_]=w)}return u}},41859:(o,s,i)=>{const u=i(27096),_=i(78004),w=u.types;o.exports=class RandExp{constructor(o,s){if(this._setDefaults(o),o instanceof RegExp)this.ignoreCase=o.ignoreCase,this.multiline=o.multiline,o=o.source;else{if("string"!=typeof o)throw new Error("Expected a regexp or string");this.ignoreCase=s&&-1!==s.indexOf("i"),this.multiline=s&&-1!==s.indexOf("m")}this.tokens=u(o)}_setDefaults(o){this.max=null!=o.max?o.max:null!=RandExp.prototype.max?RandExp.prototype.max:100,this.defaultRange=o.defaultRange?o.defaultRange:this.defaultRange.clone(),o.randInt&&(this.randInt=o.randInt)}gen(){return this._gen(this.tokens,[])}_gen(o,s){var i,u,_,x,C;switch(o.type){case w.ROOT:case w.GROUP:if(o.followedBy||o.notFollowedBy)return"";for(o.remember&&void 0===o.groupNumber&&(o.groupNumber=s.push(null)-1),u="",x=0,C=(i=o.options?this._randSelect(o.options):o.stack).length;x{"use strict";var u=i(65606),_=65536,w=4294967295;var x=i(92861).Buffer,C=i.g.crypto||i.g.msCrypto;C&&C.getRandomValues?o.exports=function randomBytes(o,s){if(o>w)throw new RangeError("requested too many random bytes");var i=x.allocUnsafe(o);if(o>0)if(o>_)for(var j=0;j{"use strict";function _typeof(o){return _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(o){return typeof o}:function(o){return o&&"function"==typeof Symbol&&o.constructor===Symbol&&o!==Symbol.prototype?"symbol":typeof o},_typeof(o)}Object.defineProperty(s,"__esModule",{value:!0}),s.CopyToClipboard=void 0;var u=_interopRequireDefault(i(96540)),_=_interopRequireDefault(i(17965)),w=["text","onCopy","options","children"];function _interopRequireDefault(o){return o&&o.__esModule?o:{default:o}}function ownKeys(o,s){var i=Object.keys(o);if(Object.getOwnPropertySymbols){var u=Object.getOwnPropertySymbols(o);s&&(u=u.filter((function(s){return Object.getOwnPropertyDescriptor(o,s).enumerable}))),i.push.apply(i,u)}return i}function _objectSpread(o){for(var s=1;s=0||(_[i]=o[i]);return _}(o,s);if(Object.getOwnPropertySymbols){var w=Object.getOwnPropertySymbols(o);for(u=0;u=0||Object.prototype.propertyIsEnumerable.call(o,i)&&(_[i]=o[i])}return _}function _defineProperties(o,s){for(var i=0;i{"use strict";var u=i(25264).CopyToClipboard;u.CopyToClipboard=u,o.exports=u},81214:(o,s,i)=>{"use strict";function _typeof(o){return _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(o){return typeof o}:function(o){return o&&"function"==typeof Symbol&&o.constructor===Symbol&&o!==Symbol.prototype?"symbol":typeof o},_typeof(o)}Object.defineProperty(s,"__esModule",{value:!0}),s.DebounceInput=void 0;var u=_interopRequireDefault(i(96540)),_=_interopRequireDefault(i(20181)),w=["element","onChange","value","minLength","debounceTimeout","forceNotifyByEnter","forceNotifyOnBlur","onKeyDown","onBlur","inputRef"];function _interopRequireDefault(o){return o&&o.__esModule?o:{default:o}}function _objectWithoutProperties(o,s){if(null==o)return{};var i,u,_=function _objectWithoutPropertiesLoose(o,s){if(null==o)return{};var i,u,_={},w=Object.keys(o);for(u=0;u=0||(_[i]=o[i]);return _}(o,s);if(Object.getOwnPropertySymbols){var w=Object.getOwnPropertySymbols(o);for(u=0;u=0||Object.prototype.propertyIsEnumerable.call(o,i)&&(_[i]=o[i])}return _}function ownKeys(o,s){var i=Object.keys(o);if(Object.getOwnPropertySymbols){var u=Object.getOwnPropertySymbols(o);s&&(u=u.filter((function(s){return Object.getOwnPropertyDescriptor(o,s).enumerable}))),i.push.apply(i,u)}return i}function _objectSpread(o){for(var s=1;s=u?i.notify(o):s.length>_.length&&i.notify(_objectSpread(_objectSpread({},o),{},{target:_objectSpread(_objectSpread({},o.target),{},{value:""})}))}))})),_defineProperty(_assertThisInitialized(i),"onKeyDown",(function(o){"Enter"===o.key&&i.forceNotify(o);var s=i.props.onKeyDown;s&&(o.persist(),s(o))})),_defineProperty(_assertThisInitialized(i),"onBlur",(function(o){i.forceNotify(o);var s=i.props.onBlur;s&&(o.persist(),s(o))})),_defineProperty(_assertThisInitialized(i),"createNotifier",(function(o){if(o<0)i.notify=function(){return null};else if(0===o)i.notify=i.doNotify;else{var s=(0,_.default)((function(o){i.isDebouncing=!1,i.doNotify(o)}),o);i.notify=function(o){i.isDebouncing=!0,s(o)},i.flush=function(){return s.flush()},i.cancel=function(){i.isDebouncing=!1,s.cancel()}}})),_defineProperty(_assertThisInitialized(i),"doNotify",(function(){i.props.onChange.apply(void 0,arguments)})),_defineProperty(_assertThisInitialized(i),"forceNotify",(function(o){var s=i.props.debounceTimeout;if(i.isDebouncing||!(s>0)){i.cancel&&i.cancel();var u=i.state.value,_=i.props.minLength;u.length>=_?i.doNotify(o):i.doNotify(_objectSpread(_objectSpread({},o),{},{target:_objectSpread(_objectSpread({},o.target),{},{value:u})}))}})),i.isDebouncing=!1,i.state={value:void 0===o.value||null===o.value?"":o.value};var u=i.props.debounceTimeout;return i.createNotifier(u),i}return function _createClass(o,s,i){return s&&_defineProperties(o.prototype,s),i&&_defineProperties(o,i),Object.defineProperty(o,"prototype",{writable:!1}),o}(DebounceInput,[{key:"componentDidUpdate",value:function componentDidUpdate(o){if(!this.isDebouncing){var s=this.props,i=s.value,u=s.debounceTimeout,_=o.debounceTimeout,w=o.value,x=this.state.value;void 0!==i&&w!==i&&x!==i&&this.setState({value:i}),u!==_&&this.createNotifier(u)}}},{key:"componentWillUnmount",value:function componentWillUnmount(){this.flush&&this.flush()}},{key:"render",value:function render(){var o,s,i=this.props,_=i.element,x=(i.onChange,i.value,i.minLength,i.debounceTimeout,i.forceNotifyByEnter),C=i.forceNotifyOnBlur,j=i.onKeyDown,L=i.onBlur,B=i.inputRef,$=_objectWithoutProperties(i,w),V=this.state.value;o=x?{onKeyDown:this.onKeyDown}:j?{onKeyDown:j}:{},s=C?{onBlur:this.onBlur}:L?{onBlur:L}:{};var U=B?{ref:B}:{};return u.default.createElement(_,_objectSpread(_objectSpread(_objectSpread(_objectSpread({},$),{},{onChange:this.onChange,value:V},o),s),U))}}]),DebounceInput}(u.default.PureComponent);s.DebounceInput=x,_defineProperty(x,"defaultProps",{element:"input",type:"text",onKeyDown:void 0,onBlur:void 0,value:void 0,minLength:0,debounceTimeout:100,forceNotifyByEnter:!0,forceNotifyOnBlur:!0,inputRef:void 0})},24677:(o,s,i)=>{"use strict";var u=i(81214).DebounceInput;u.DebounceInput=u,o.exports=u},22551:(o,s,i)=>{"use strict";var u=i(96540),_=i(69982);function p(o){for(var s="https://reactjs.org/docs/error-decoder.html?invariant="+o,i=1;i
    ")}value(){return this.buffer}span(s){this.buffer+=``}}class TokenTree{constructor(){this.rootNode={children:[]},this.stack=[this.rootNode]}get top(){return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(s){this.top.children.push(s)}openNode(s){const o={kind:s,children:[]};this.add(o),this.stack.push(o)}closeNode(){if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}walk(s){return this.constructor._walk(s,this.rootNode)}static _walk(s,o){return"string"==typeof o?s.addText(o):o.children&&(s.openNode(o),o.children.forEach((o=>this._walk(s,o))),s.closeNode(o)),s}static _collapse(s){"string"!=typeof s&&s.children&&(s.children.every((s=>"string"==typeof s))?s.children=[s.children.join("")]:s.children.forEach((s=>{TokenTree._collapse(s)})))}}class TokenTreeEmitter extends TokenTree{constructor(s){super(),this.options=s}addKeyword(s,o){""!==s&&(this.openNode(o),this.addText(s),this.closeNode())}addText(s){""!==s&&this.add(s)}addSublanguage(s,o){const i=s.root;i.kind=o,i.sublanguage=!0,this.add(i)}toHTML(){return new HTMLRenderer(this,this.options).value()}finalize(){return!0}}function source(s){return s?"string"==typeof s?s:s.source:null}const a=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./;const u="[a-zA-Z]\\w*",_="[a-zA-Z_]\\w*",w="\\b\\d+(\\.\\d+)?",x="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",C="\\b(0b[01]+)",j={begin:"\\\\[\\s\\S]",relevance:0},L={className:"string",begin:"'",end:"'",illegal:"\\n",contains:[j]},B={className:"string",begin:'"',end:'"',illegal:"\\n",contains:[j]},$={begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},COMMENT=function(s,o,i={}){const a=inherit({className:"comment",begin:s,end:o,contains:[]},i);return a.contains.push($),a.contains.push({className:"doctag",begin:"(?:TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):",relevance:0}),a},U=COMMENT("//","$"),V=COMMENT("/\\*","\\*/"),z=COMMENT("#","$"),Y={className:"number",begin:w,relevance:0},Z={className:"number",begin:x,relevance:0},ee={className:"number",begin:C,relevance:0},ie={className:"number",begin:w+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},ae={begin:/(?=\/[^/\n]*\/)/,contains:[{className:"regexp",begin:/\//,end:/\/[gimuy]*/,illegal:/\n/,contains:[j,{begin:/\[/,end:/\]/,relevance:0,contains:[j]}]}]},ce={className:"title",begin:u,relevance:0},le={className:"title",begin:_,relevance:0},pe={begin:"\\.\\s*"+_,relevance:0};var de=Object.freeze({__proto__:null,MATCH_NOTHING_RE:/\b\B/,IDENT_RE:u,UNDERSCORE_IDENT_RE:_,NUMBER_RE:w,C_NUMBER_RE:x,BINARY_NUMBER_RE:C,RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",SHEBANG:(s={})=>{const o=/^#![ ]*\//;return s.binary&&(s.begin=function concat(...s){return s.map((s=>source(s))).join("")}(o,/.*\b/,s.binary,/\b.*/)),inherit({className:"meta",begin:o,end:/$/,relevance:0,"on:begin":(s,o)=>{0!==s.index&&o.ignoreMatch()}},s)},BACKSLASH_ESCAPE:j,APOS_STRING_MODE:L,QUOTE_STRING_MODE:B,PHRASAL_WORDS_MODE:$,COMMENT,C_LINE_COMMENT_MODE:U,C_BLOCK_COMMENT_MODE:V,HASH_COMMENT_MODE:z,NUMBER_MODE:Y,C_NUMBER_MODE:Z,BINARY_NUMBER_MODE:ee,CSS_NUMBER_MODE:ie,REGEXP_MODE:ae,TITLE_MODE:ce,UNDERSCORE_TITLE_MODE:le,METHOD_GUARD:pe,END_SAME_AS_BEGIN:function(s){return Object.assign(s,{"on:begin":(s,o)=>{o.data._beginMatch=s[1]},"on:end":(s,o)=>{o.data._beginMatch!==s[1]&&o.ignoreMatch()}})}});function skipIfhasPrecedingDot(s,o){"."===s.input[s.index-1]&&o.ignoreMatch()}function beginKeywords(s,o){o&&s.beginKeywords&&(s.begin="\\b("+s.beginKeywords.split(" ").join("|")+")(?!\\.)(?=\\b|\\s)",s.__beforeBegin=skipIfhasPrecedingDot,s.keywords=s.keywords||s.beginKeywords,delete s.beginKeywords,void 0===s.relevance&&(s.relevance=0))}function compileIllegal(s,o){Array.isArray(s.illegal)&&(s.illegal=function either(...s){return"("+s.map((s=>source(s))).join("|")+")"}(...s.illegal))}function compileMatch(s,o){if(s.match){if(s.begin||s.end)throw new Error("begin & end are not supported with match");s.begin=s.match,delete s.match}}function compileRelevance(s,o){void 0===s.relevance&&(s.relevance=1)}const fe=["of","and","for","in","not","or","if","then","parent","list","value"];function compileKeywords(s,o,i="keyword"){const a={};return"string"==typeof s?compileList(i,s.split(" ")):Array.isArray(s)?compileList(i,s):Object.keys(s).forEach((function(i){Object.assign(a,compileKeywords(s[i],o,i))})),a;function compileList(s,i){o&&(i=i.map((s=>s.toLowerCase()))),i.forEach((function(o){const i=o.split("|");a[i[0]]=[s,scoreForKeyword(i[0],i[1])]}))}}function scoreForKeyword(s,o){return o?Number(o):function commonKeyword(s){return fe.includes(s.toLowerCase())}(s)?0:1}function compileLanguage(s,{plugins:o}){function langRe(o,i){return new RegExp(source(o),"m"+(s.case_insensitive?"i":"")+(i?"g":""))}class MultiRegex{constructor(){this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}addRule(s,o){o.position=this.position++,this.matchIndexes[this.matchAt]=o,this.regexes.push([o,s]),this.matchAt+=function countMatchGroups(s){return new RegExp(s.toString()+"|").exec("").length-1}(s)+1}compile(){0===this.regexes.length&&(this.exec=()=>null);const s=this.regexes.map((s=>s[1]));this.matcherRe=langRe(function join(s,o="|"){let i=0;return s.map((s=>{i+=1;const o=i;let u=source(s),_="";for(;u.length>0;){const s=a.exec(u);if(!s){_+=u;break}_+=u.substring(0,s.index),u=u.substring(s.index+s[0].length),"\\"===s[0][0]&&s[1]?_+="\\"+String(Number(s[1])+o):(_+=s[0],"("===s[0]&&i++)}return _})).map((s=>`(${s})`)).join(o)}(s),!0),this.lastIndex=0}exec(s){this.matcherRe.lastIndex=this.lastIndex;const o=this.matcherRe.exec(s);if(!o)return null;const i=o.findIndex(((s,o)=>o>0&&void 0!==s)),a=this.matchIndexes[i];return o.splice(0,i),Object.assign(o,a)}}class ResumableMultiRegex{constructor(){this.rules=[],this.multiRegexes=[],this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(s){if(this.multiRegexes[s])return this.multiRegexes[s];const o=new MultiRegex;return this.rules.slice(s).forEach((([s,i])=>o.addRule(s,i))),o.compile(),this.multiRegexes[s]=o,o}resumingScanAtSamePosition(){return 0!==this.regexIndex}considerAll(){this.regexIndex=0}addRule(s,o){this.rules.push([s,o]),"begin"===o.type&&this.count++}exec(s){const o=this.getMatcher(this.regexIndex);o.lastIndex=this.lastIndex;let i=o.exec(s);if(this.resumingScanAtSamePosition())if(i&&i.index===this.lastIndex);else{const o=this.getMatcher(0);o.lastIndex=this.lastIndex+1,i=o.exec(s)}return i&&(this.regexIndex+=i.position+1,this.regexIndex===this.count&&this.considerAll()),i}}if(s.compilerExtensions||(s.compilerExtensions=[]),s.contains&&s.contains.includes("self"))throw new Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.");return s.classNameAliases=inherit(s.classNameAliases||{}),function compileMode(o,i){const a=o;if(o.isCompiled)return a;[compileMatch].forEach((s=>s(o,i))),s.compilerExtensions.forEach((s=>s(o,i))),o.__beforeBegin=null,[beginKeywords,compileIllegal,compileRelevance].forEach((s=>s(o,i))),o.isCompiled=!0;let u=null;if("object"==typeof o.keywords&&(u=o.keywords.$pattern,delete o.keywords.$pattern),o.keywords&&(o.keywords=compileKeywords(o.keywords,s.case_insensitive)),o.lexemes&&u)throw new Error("ERR: Prefer `keywords.$pattern` to `mode.lexemes`, BOTH are not allowed. (see mode reference) ");return u=u||o.lexemes||/\w+/,a.keywordPatternRe=langRe(u,!0),i&&(o.begin||(o.begin=/\B|\b/),a.beginRe=langRe(o.begin),o.endSameAsBegin&&(o.end=o.begin),o.end||o.endsWithParent||(o.end=/\B|\b/),o.end&&(a.endRe=langRe(o.end)),a.terminatorEnd=source(o.end)||"",o.endsWithParent&&i.terminatorEnd&&(a.terminatorEnd+=(o.end?"|":"")+i.terminatorEnd)),o.illegal&&(a.illegalRe=langRe(o.illegal)),o.contains||(o.contains=[]),o.contains=[].concat(...o.contains.map((function(s){return function expandOrCloneMode(s){s.variants&&!s.cachedVariants&&(s.cachedVariants=s.variants.map((function(o){return inherit(s,{variants:null},o)})));if(s.cachedVariants)return s.cachedVariants;if(dependencyOnParent(s))return inherit(s,{starts:s.starts?inherit(s.starts):null});if(Object.isFrozen(s))return inherit(s);return s}("self"===s?o:s)}))),o.contains.forEach((function(s){compileMode(s,a)})),o.starts&&compileMode(o.starts,i),a.matcher=function buildModeRegex(s){const o=new ResumableMultiRegex;return s.contains.forEach((s=>o.addRule(s.begin,{rule:s,type:"begin"}))),s.terminatorEnd&&o.addRule(s.terminatorEnd,{type:"end"}),s.illegal&&o.addRule(s.illegal,{type:"illegal"}),o}(a),a}(s)}function dependencyOnParent(s){return!!s&&(s.endsWithParent||dependencyOnParent(s.starts))}function BuildVuePlugin(s){const o={props:["language","code","autodetect"],data:function(){return{detectedLanguage:"",unknownLanguage:!1}},computed:{className(){return this.unknownLanguage?"":"hljs "+this.detectedLanguage},highlighted(){if(!this.autoDetect&&!s.getLanguage(this.language))return console.warn(`The language "${this.language}" you specified could not be found.`),this.unknownLanguage=!0,escapeHTML(this.code);let o={};return this.autoDetect?(o=s.highlightAuto(this.code),this.detectedLanguage=o.language):(o=s.highlight(this.language,this.code,this.ignoreIllegals),this.detectedLanguage=this.language),o.value},autoDetect(){return!this.language||function hasValueOrEmptyAttribute(s){return Boolean(s||""===s)}(this.autodetect)},ignoreIllegals:()=>!0},render(s){return s("pre",{},[s("code",{class:this.className,domProps:{innerHTML:this.highlighted}})])}};return{Component:o,VuePlugin:{install(s){s.component("highlightjs",o)}}}}const ye={"after:highlightElement":({el:s,result:o,text:i})=>{const a=nodeStream(s);if(!a.length)return;const u=document.createElement("div");u.innerHTML=o.value,o.value=function mergeStreams(s,o,i){let a=0,u="";const _=[];function selectStream(){return s.length&&o.length?s[0].offset!==o[0].offset?s[0].offset"}function close(s){u+=""}function render(s){("start"===s.event?open:close)(s.node)}for(;s.length||o.length;){let o=selectStream();if(u+=escapeHTML(i.substring(a,o[0].offset)),a=o[0].offset,o===s){_.reverse().forEach(close);do{render(o.splice(0,1)[0]),o=selectStream()}while(o===s&&o.length&&o[0].offset===a);_.reverse().forEach(open)}else"start"===o[0].event?_.push(o[0].node):_.pop(),render(o.splice(0,1)[0])}return u+escapeHTML(i.substr(a))}(a,nodeStream(u),i)}};function tag(s){return s.nodeName.toLowerCase()}function nodeStream(s){const o=[];return function _nodeStream(s,i){for(let a=s.firstChild;a;a=a.nextSibling)3===a.nodeType?i+=a.nodeValue.length:1===a.nodeType&&(o.push({event:"start",offset:i,node:a}),i=_nodeStream(a,i),tag(a).match(/br|hr|img|input/)||o.push({event:"stop",offset:i,node:a}));return i}(s,0),o}const be={},error=s=>{console.error(s)},warn=(s,...o)=>{console.log(`WARN: ${s}`,...o)},deprecated=(s,o)=>{be[`${s}/${o}`]||(console.log(`Deprecated as of ${s}. ${o}`),be[`${s}/${o}`]=!0)},Se=escapeHTML,_e=inherit,we=Symbol("nomatch");var xe=function(s){const i=Object.create(null),a=Object.create(null),u=[];let _=!0;const w=/(^(<[^>]+>|\t|)+|\n)/gm,x="Could not find the language '{}', did you forget to load/include a language module?",C={disableAutodetect:!0,name:"Plain text",contains:[]};let j={noHighlightRe:/^(no-?highlight)$/i,languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:null,__emitter:TokenTreeEmitter};function shouldNotHighlight(s){return j.noHighlightRe.test(s)}function highlight(s,o,i,a){let u="",_="";"object"==typeof o?(u=s,i=o.ignoreIllegals,_=o.language,a=void 0):(deprecated("10.7.0","highlight(lang, code, ...args) has been deprecated."),deprecated("10.7.0","Please use highlight(code, options) instead.\nhttps://github.com/highlightjs/highlight.js/issues/2277"),_=s,u=o);const w={code:u,language:_};fire("before:highlight",w);const x=w.result?w.result:_highlight(w.language,w.code,i,a);return x.code=w.code,fire("after:highlight",x),x}function _highlight(s,o,a,w){function keywordData(s,o){const i=L.case_insensitive?o[0].toLowerCase():o[0];return Object.prototype.hasOwnProperty.call(s.keywords,i)&&s.keywords[i]}function processBuffer(){null!=U.subLanguage?function processSubLanguage(){if(""===Y)return;let s=null;if("string"==typeof U.subLanguage){if(!i[U.subLanguage])return void z.addText(Y);s=_highlight(U.subLanguage,Y,!0,V[U.subLanguage]),V[U.subLanguage]=s.top}else s=highlightAuto(Y,U.subLanguage.length?U.subLanguage:null);U.relevance>0&&(Z+=s.relevance),z.addSublanguage(s.emitter,s.language)}():function processKeywords(){if(!U.keywords)return void z.addText(Y);let s=0;U.keywordPatternRe.lastIndex=0;let o=U.keywordPatternRe.exec(Y),i="";for(;o;){i+=Y.substring(s,o.index);const a=keywordData(U,o);if(a){const[s,u]=a;if(z.addText(i),i="",Z+=u,s.startsWith("_"))i+=o[0];else{const i=L.classNameAliases[s]||s;z.addKeyword(o[0],i)}}else i+=o[0];s=U.keywordPatternRe.lastIndex,o=U.keywordPatternRe.exec(Y)}i+=Y.substr(s),z.addText(i)}(),Y=""}function startNewMode(s){return s.className&&z.openNode(L.classNameAliases[s.className]||s.className),U=Object.create(s,{parent:{value:U}}),U}function endOfMode(s,o,i){let a=function startsWith(s,o){const i=s&&s.exec(o);return i&&0===i.index}(s.endRe,i);if(a){if(s["on:end"]){const i=new Response(s);s["on:end"](o,i),i.isMatchIgnored&&(a=!1)}if(a){for(;s.endsParent&&s.parent;)s=s.parent;return s}}if(s.endsWithParent)return endOfMode(s.parent,o,i)}function doIgnore(s){return 0===U.matcher.regexIndex?(Y+=s[0],1):(ae=!0,0)}function doBeginMatch(s){const o=s[0],i=s.rule,a=new Response(i),u=[i.__beforeBegin,i["on:begin"]];for(const i of u)if(i&&(i(s,a),a.isMatchIgnored))return doIgnore(o);return i&&i.endSameAsBegin&&(i.endRe=function escape(s){return new RegExp(s.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&"),"m")}(o)),i.skip?Y+=o:(i.excludeBegin&&(Y+=o),processBuffer(),i.returnBegin||i.excludeBegin||(Y=o)),startNewMode(i),i.returnBegin?0:o.length}function doEndMatch(s){const i=s[0],a=o.substr(s.index),u=endOfMode(U,s,a);if(!u)return we;const _=U;_.skip?Y+=i:(_.returnEnd||_.excludeEnd||(Y+=i),processBuffer(),_.excludeEnd&&(Y=i));do{U.className&&z.closeNode(),U.skip||U.subLanguage||(Z+=U.relevance),U=U.parent}while(U!==u.parent);return u.starts&&(u.endSameAsBegin&&(u.starts.endRe=u.endRe),startNewMode(u.starts)),_.returnEnd?0:i.length}let C={};function processLexeme(i,u){const w=u&&u[0];if(Y+=i,null==w)return processBuffer(),0;if("begin"===C.type&&"end"===u.type&&C.index===u.index&&""===w){if(Y+=o.slice(u.index,u.index+1),!_){const o=new Error("0 width match regex");throw o.languageName=s,o.badRule=C.rule,o}return 1}if(C=u,"begin"===u.type)return doBeginMatch(u);if("illegal"===u.type&&!a){const s=new Error('Illegal lexeme "'+w+'" for mode "'+(U.className||"")+'"');throw s.mode=U,s}if("end"===u.type){const s=doEndMatch(u);if(s!==we)return s}if("illegal"===u.type&&""===w)return 1;if(ie>1e5&&ie>3*u.index){throw new Error("potential infinite loop, way more iterations than matches")}return Y+=w,w.length}const L=getLanguage(s);if(!L)throw error(x.replace("{}",s)),new Error('Unknown language: "'+s+'"');const B=compileLanguage(L,{plugins:u});let $="",U=w||B;const V={},z=new j.__emitter(j);!function processContinuations(){const s=[];for(let o=U;o!==L;o=o.parent)o.className&&s.unshift(o.className);s.forEach((s=>z.openNode(s)))}();let Y="",Z=0,ee=0,ie=0,ae=!1;try{for(U.matcher.considerAll();;){ie++,ae?ae=!1:U.matcher.considerAll(),U.matcher.lastIndex=ee;const s=U.matcher.exec(o);if(!s)break;const i=processLexeme(o.substring(ee,s.index),s);ee=s.index+i}return processLexeme(o.substr(ee)),z.closeAllNodes(),z.finalize(),$=z.toHTML(),{relevance:Math.floor(Z),value:$,language:s,illegal:!1,emitter:z,top:U}}catch(i){if(i.message&&i.message.includes("Illegal"))return{illegal:!0,illegalBy:{msg:i.message,context:o.slice(ee-100,ee+100),mode:i.mode},sofar:$,relevance:0,value:Se(o),emitter:z};if(_)return{illegal:!1,relevance:0,value:Se(o),emitter:z,language:s,top:U,errorRaised:i};throw i}}function highlightAuto(s,o){o=o||j.languages||Object.keys(i);const a=function justTextHighlightResult(s){const o={relevance:0,emitter:new j.__emitter(j),value:Se(s),illegal:!1,top:C};return o.emitter.addText(s),o}(s),u=o.filter(getLanguage).filter(autoDetection).map((o=>_highlight(o,s,!1)));u.unshift(a);const _=u.sort(((s,o)=>{if(s.relevance!==o.relevance)return o.relevance-s.relevance;if(s.language&&o.language){if(getLanguage(s.language).supersetOf===o.language)return 1;if(getLanguage(o.language).supersetOf===s.language)return-1}return 0})),[w,x]=_,L=w;return L.second_best=x,L}const L={"before:highlightElement":({el:s})=>{j.useBR&&(s.innerHTML=s.innerHTML.replace(/\n/g,"").replace(//g,"\n"))},"after:highlightElement":({result:s})=>{j.useBR&&(s.value=s.value.replace(/\n/g,"
    "))}},B=/^(<[^>]+>|\t)+/gm,$={"after:highlightElement":({result:s})=>{j.tabReplace&&(s.value=s.value.replace(B,(s=>s.replace(/\t/g,j.tabReplace))))}};function highlightElement(s){let o=null;const i=function blockLanguage(s){let o=s.className+" ";o+=s.parentNode?s.parentNode.className:"";const i=j.languageDetectRe.exec(o);if(i){const o=getLanguage(i[1]);return o||(warn(x.replace("{}",i[1])),warn("Falling back to no-highlight mode for this block.",s)),o?i[1]:"no-highlight"}return o.split(/\s+/).find((s=>shouldNotHighlight(s)||getLanguage(s)))}(s);if(shouldNotHighlight(i))return;fire("before:highlightElement",{el:s,language:i}),o=s;const u=o.textContent,_=i?highlight(u,{language:i,ignoreIllegals:!0}):highlightAuto(u);fire("after:highlightElement",{el:s,result:_,text:u}),s.innerHTML=_.value,function updateClassName(s,o,i){const u=o?a[o]:i;s.classList.add("hljs"),u&&s.classList.add(u)}(s,i,_.language),s.result={language:_.language,re:_.relevance,relavance:_.relevance},_.second_best&&(s.second_best={language:_.second_best.language,re:_.second_best.relevance,relavance:_.second_best.relevance})}const initHighlighting=()=>{if(initHighlighting.called)return;initHighlighting.called=!0,deprecated("10.6.0","initHighlighting() is deprecated. Use highlightAll() instead.");document.querySelectorAll("pre code").forEach(highlightElement)};let U=!1;function highlightAll(){if("loading"===document.readyState)return void(U=!0);document.querySelectorAll("pre code").forEach(highlightElement)}function getLanguage(s){return s=(s||"").toLowerCase(),i[s]||i[a[s]]}function registerAliases(s,{languageName:o}){"string"==typeof s&&(s=[s]),s.forEach((s=>{a[s.toLowerCase()]=o}))}function autoDetection(s){const o=getLanguage(s);return o&&!o.disableAutodetect}function fire(s,o){const i=s;u.forEach((function(s){s[i]&&s[i](o)}))}"undefined"!=typeof window&&window.addEventListener&&window.addEventListener("DOMContentLoaded",(function boot(){U&&highlightAll()}),!1),Object.assign(s,{highlight,highlightAuto,highlightAll,fixMarkup:function deprecateFixMarkup(s){return deprecated("10.2.0","fixMarkup will be removed entirely in v11.0"),deprecated("10.2.0","Please see https://github.com/highlightjs/highlight.js/issues/2534"),function fixMarkup(s){return j.tabReplace||j.useBR?s.replace(w,(s=>"\n"===s?j.useBR?"
    ":s:j.tabReplace?s.replace(/\t/g,j.tabReplace):s)):s}(s)},highlightElement,highlightBlock:function deprecateHighlightBlock(s){return deprecated("10.7.0","highlightBlock will be removed entirely in v12.0"),deprecated("10.7.0","Please use highlightElement now."),highlightElement(s)},configure:function configure(s){s.useBR&&(deprecated("10.3.0","'useBR' will be removed entirely in v11.0"),deprecated("10.3.0","Please see https://github.com/highlightjs/highlight.js/issues/2559")),j=_e(j,s)},initHighlighting,initHighlightingOnLoad:function initHighlightingOnLoad(){deprecated("10.6.0","initHighlightingOnLoad() is deprecated. Use highlightAll() instead."),U=!0},registerLanguage:function registerLanguage(o,a){let u=null;try{u=a(s)}catch(s){if(error("Language definition for '{}' could not be registered.".replace("{}",o)),!_)throw s;error(s),u=C}u.name||(u.name=o),i[o]=u,u.rawDefinition=a.bind(null,s),u.aliases&®isterAliases(u.aliases,{languageName:o})},unregisterLanguage:function unregisterLanguage(s){delete i[s];for(const o of Object.keys(a))a[o]===s&&delete a[o]},listLanguages:function listLanguages(){return Object.keys(i)},getLanguage,registerAliases,requireLanguage:function requireLanguage(s){deprecated("10.4.0","requireLanguage will be removed entirely in v11."),deprecated("10.4.0","Please see https://github.com/highlightjs/highlight.js/pull/2844");const o=getLanguage(s);if(o)return o;throw new Error("The '{}' language is required, but not loaded.".replace("{}",s))},autoDetection,inherit:_e,addPlugin:function addPlugin(s){!function upgradePluginAPI(s){s["before:highlightBlock"]&&!s["before:highlightElement"]&&(s["before:highlightElement"]=o=>{s["before:highlightBlock"](Object.assign({block:o.el},o))}),s["after:highlightBlock"]&&!s["after:highlightElement"]&&(s["after:highlightElement"]=o=>{s["after:highlightBlock"](Object.assign({block:o.el},o))})}(s),u.push(s)},vuePlugin:BuildVuePlugin(s).VuePlugin}),s.debugMode=function(){_=!1},s.safeMode=function(){_=!0},s.versionString="10.7.3";for(const s in de)"object"==typeof de[s]&&o(de[s]);return Object.assign(s,de),s.addPlugin(L),s.addPlugin(ye),s.addPlugin($),s}({});s.exports=xe},35344(s){function concat(...s){return s.map((s=>function source(s){return s?"string"==typeof s?s:s.source:null}(s))).join("")}s.exports=function bash(s){const o={},i={begin:/\$\{/,end:/\}/,contains:["self",{begin:/:-/,contains:[o]}]};Object.assign(o,{className:"variable",variants:[{begin:concat(/\$[\w\d#@][\w\d_]*/,"(?![\\w\\d])(?![$])")},i]});const a={className:"subst",begin:/\$\(/,end:/\)/,contains:[s.BACKSLASH_ESCAPE]},u={begin:/<<-?\s*(?=\w+)/,starts:{contains:[s.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/,className:"string"})]}},_={className:"string",begin:/"/,end:/"/,contains:[s.BACKSLASH_ESCAPE,o,a]};a.contains.push(_);const w={begin:/\$\(\(/,end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},s.NUMBER_MODE,o]},x=s.SHEBANG({binary:`(${["fish","bash","zsh","sh","csh","ksh","tcsh","dash","scsh"].join("|")})`,relevance:10}),C={className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0,contains:[s.inherit(s.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0};return{name:"Bash",aliases:["sh","zsh"],keywords:{$pattern:/\b[a-z._-]+\b/,keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp"},contains:[x,s.SHEBANG(),C,w,s.HASH_COMMENT_MODE,u,_,{className:"",begin:/\\"/},{className:"string",begin:/'/,end:/'/},o]}}},73402(s){function concat(...s){return s.map((s=>function source(s){return s?"string"==typeof s?s:s.source:null}(s))).join("")}s.exports=function http(s){const o="HTTP/(2|1\\.[01])",i={className:"attribute",begin:concat("^",/[A-Za-z][A-Za-z0-9-]*/,"(?=\\:\\s)"),starts:{contains:[{className:"punctuation",begin:/: /,relevance:0,starts:{end:"$",relevance:0}}]}},a=[i,{begin:"\\n\\n",starts:{subLanguage:[],endsWithParent:!0}}];return{name:"HTTP",aliases:["https"],illegal:/\S/,contains:[{begin:"^(?="+o+" \\d{3})",end:/$/,contains:[{className:"meta",begin:o},{className:"number",begin:"\\b\\d{3}\\b"}],starts:{end:/\b\B/,illegal:/\S/,contains:a}},{begin:"(?=^[A-Z]+ (.*?) "+o+"$)",end:/$/,contains:[{className:"string",begin:" ",end:" ",excludeBegin:!0,excludeEnd:!0},{className:"meta",begin:o},{className:"keyword",begin:"[A-Z]+"}],starts:{end:/\b\B/,illegal:/\S/,contains:a}},s.inherit(i,{relevance:0})]}}},95089(s){const o="[A-Za-z$_][0-9A-Za-z$_]*",i=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],a=["true","false","null","undefined","NaN","Infinity"],u=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer","BigInt64Array","BigUint64Array","BigInt"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);function lookahead(s){return concat("(?=",s,")")}function concat(...s){return s.map((s=>function source(s){return s?"string"==typeof s?s:s.source:null}(s))).join("")}s.exports=function javascript(s){const _=o,w="<>",x="",C={begin:/<[A-Za-z0-9\\._:-]+/,end:/\/[A-Za-z0-9\\._:-]+>|\/>/,isTrulyOpeningTag:(s,o)=>{const i=s[0].length+s.index,a=s.input[i];"<"!==a?">"===a&&(((s,{after:o})=>{const i="",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:s.UNDERSCORE_IDENT_RE,relevance:0},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:j,contains:ce}]}]},{begin:/,/,relevance:0},{className:"",begin:/\s/,end:/\s*/,skip:!0},{variants:[{begin:w,end:x},{begin:C.begin,"on:begin":C.isTrulyOpeningTag,end:C.end}],subLanguage:"xml",contains:[{begin:C.begin,end:C.end,skip:!0,contains:["self"]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/[{;]/,excludeEnd:!0,keywords:j,contains:["self",s.inherit(s.TITLE_MODE,{begin:_}),le],illegal:/%/},{beginKeywords:"while if switch catch for"},{className:"function",begin:s.UNDERSCORE_IDENT_RE+"\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{",returnBegin:!0,contains:[le,s.inherit(s.TITLE_MODE,{begin:_})]},{variants:[{begin:"\\."+_},{begin:"\\$"+_}],relevance:0},{className:"class",beginKeywords:"class",end:/[{;=]/,excludeEnd:!0,illegal:/[:"[\]]/,contains:[{beginKeywords:"extends"},s.UNDERSCORE_TITLE_MODE]},{begin:/\b(?=constructor)/,end:/[{;]/,excludeEnd:!0,contains:[s.inherit(s.TITLE_MODE,{begin:_}),"self",le]},{begin:"(get|set)\\s+(?="+_+"\\()",end:/\{/,keywords:"get set",contains:[s.inherit(s.TITLE_MODE,{begin:_}),{begin:/\(\)/},le]},{begin:/\$[(.]/}]}}},65772(s){s.exports=function json(s){const o={literal:"true false null"},i=[s.C_LINE_COMMENT_MODE,s.C_BLOCK_COMMENT_MODE],a=[s.QUOTE_STRING_MODE,s.C_NUMBER_MODE],u={end:",",endsWithParent:!0,excludeEnd:!0,contains:a,keywords:o},_={begin:/\{/,end:/\}/,contains:[{className:"attr",begin:/"/,end:/"/,contains:[s.BACKSLASH_ESCAPE],illegal:"\\n"},s.inherit(u,{begin:/:/})].concat(i),illegal:"\\S"},w={begin:"\\[",end:"\\]",contains:[s.inherit(u)],illegal:"\\S"};return a.push(_,w),i.forEach((function(s){a.push(s)})),{name:"JSON",contains:a,keywords:o,illegal:"\\S"}}},26571(s){s.exports=function powershell(s){const o={$pattern:/-?[A-z\.\-]+\b/,keyword:"if else foreach return do while until elseif begin for trap data dynamicparam end break throw param continue finally in switch exit filter try process catch hidden static parameter",built_in:"ac asnp cat cd CFS chdir clc clear clhy cli clp cls clv cnsn compare copy cp cpi cpp curl cvpa dbp del diff dir dnsn ebp echo|0 epal epcsv epsn erase etsn exsn fc fhx fl ft fw gal gbp gc gcb gci gcm gcs gdr gerr ghy gi gin gjb gl gm gmo gp gps gpv group gsn gsnp gsv gtz gu gv gwmi h history icm iex ihy ii ipal ipcsv ipmo ipsn irm ise iwmi iwr kill lp ls man md measure mi mount move mp mv nal ndr ni nmo npssc nsn nv ogv oh popd ps pushd pwd r rbp rcjb rcsn rd rdr ren ri rjb rm rmdir rmo rni rnp rp rsn rsnp rujb rv rvpa rwmi sajb sal saps sasv sbp sc scb select set shcm si sl sleep sls sort sp spjb spps spsv start stz sujb sv swmi tee trcm type wget where wjb write"},i={begin:"`[\\s\\S]",relevance:0},a={className:"variable",variants:[{begin:/\$\B/},{className:"keyword",begin:/\$this/},{begin:/\$[\w\d][\w\d_:]*/}]},u={className:"string",variants:[{begin:/"/,end:/"/},{begin:/@"/,end:/^"@/}],contains:[i,a,{className:"variable",begin:/\$[A-z]/,end:/[^A-z]/}]},_={className:"string",variants:[{begin:/'/,end:/'/},{begin:/@'/,end:/^'@/}]},w=s.inherit(s.COMMENT(null,null),{variants:[{begin:/#/,end:/$/},{begin:/<#/,end:/#>/}],contains:[{className:"doctag",variants:[{begin:/\.(synopsis|description|example|inputs|outputs|notes|link|component|role|functionality)/},{begin:/\.(parameter|forwardhelptargetname|forwardhelpcategory|remotehelprunspace|externalhelp)\s+\S+/}]}]}),x={className:"built_in",variants:[{begin:"(".concat("Add|Clear|Close|Copy|Enter|Exit|Find|Format|Get|Hide|Join|Lock|Move|New|Open|Optimize|Pop|Push|Redo|Remove|Rename|Reset|Resize|Search|Select|Set|Show|Skip|Split|Step|Switch|Undo|Unlock|Watch|Backup|Checkpoint|Compare|Compress|Convert|ConvertFrom|ConvertTo|Dismount|Edit|Expand|Export|Group|Import|Initialize|Limit|Merge|Mount|Out|Publish|Restore|Save|Sync|Unpublish|Update|Approve|Assert|Build|Complete|Confirm|Deny|Deploy|Disable|Enable|Install|Invoke|Register|Request|Restart|Resume|Start|Stop|Submit|Suspend|Uninstall|Unregister|Wait|Debug|Measure|Ping|Repair|Resolve|Test|Trace|Connect|Disconnect|Read|Receive|Send|Write|Block|Grant|Protect|Revoke|Unblock|Unprotect|Use|ForEach|Sort|Tee|Where",")+(-)[\\w\\d]+")}]},C={className:"class",beginKeywords:"class enum",end:/\s*[{]/,excludeEnd:!0,relevance:0,contains:[s.TITLE_MODE]},j={className:"function",begin:/function\s+/,end:/\s*\{|$/,excludeEnd:!0,returnBegin:!0,relevance:0,contains:[{begin:"function",relevance:0,className:"keyword"},{className:"title",begin:/\w[\w\d]*((-)[\w\d]+)*/,relevance:0},{begin:/\(/,end:/\)/,className:"params",relevance:0,contains:[a]}]},L={begin:/using\s/,end:/$/,returnBegin:!0,contains:[u,_,{className:"keyword",begin:/(using|assembly|command|module|namespace|type)/}]},B={variants:[{className:"operator",begin:"(".concat("-and|-as|-band|-bnot|-bor|-bxor|-casesensitive|-ccontains|-ceq|-cge|-cgt|-cle|-clike|-clt|-cmatch|-cne|-cnotcontains|-cnotlike|-cnotmatch|-contains|-creplace|-csplit|-eq|-exact|-f|-file|-ge|-gt|-icontains|-ieq|-ige|-igt|-ile|-ilike|-ilt|-imatch|-in|-ine|-inotcontains|-inotlike|-inotmatch|-ireplace|-is|-isnot|-isplit|-join|-le|-like|-lt|-match|-ne|-not|-notcontains|-notin|-notlike|-notmatch|-or|-regex|-replace|-shl|-shr|-split|-wildcard|-xor",")\\b")},{className:"literal",begin:/(-)[\w\d]+/,relevance:0}]},$={className:"function",begin:/\[.*\]\s*[\w]+[ ]??\(/,end:/$/,returnBegin:!0,relevance:0,contains:[{className:"keyword",begin:"(".concat(o.keyword.toString().replace(/\s/g,"|"),")\\b"),endsParent:!0,relevance:0},s.inherit(s.TITLE_MODE,{endsParent:!0})]},U=[$,w,i,s.NUMBER_MODE,u,_,x,a,{className:"literal",begin:/\$(null|true|false)\b/},{className:"selector-tag",begin:/@\B/,relevance:0}],V={begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[].concat("self",U,{begin:"("+["string","char","byte","int","long","bool","decimal","single","double","DateTime","xml","array","hashtable","void"].join("|")+")",className:"built_in",relevance:0},{className:"type",begin:/[\.\w\d]+/,relevance:0})};return $.contains.unshift(V),{name:"PowerShell",aliases:["ps","ps1"],case_insensitive:!0,keywords:o,contains:U.concat(C,j,L,B,V)}}},17285(s){function source(s){return s?"string"==typeof s?s:s.source:null}function lookahead(s){return concat("(?=",s,")")}function concat(...s){return s.map((s=>source(s))).join("")}function either(...s){return"("+s.map((s=>source(s))).join("|")+")"}s.exports=function xml(s){const o=concat(/[A-Z_]/,function optional(s){return concat("(",s,")?")}(/[A-Z0-9_.-]*:/),/[A-Z0-9_.-]*/),i={className:"symbol",begin:/&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;/},a={begin:/\s/,contains:[{className:"meta-keyword",begin:/#?[a-z_][a-z1-9_-]+/,illegal:/\n/}]},u=s.inherit(a,{begin:/\(/,end:/\)/}),_=s.inherit(s.APOS_STRING_MODE,{className:"meta-string"}),w=s.inherit(s.QUOTE_STRING_MODE,{className:"meta-string"}),x={endsWithParent:!0,illegal:/`]+/}]}]}]};return{name:"HTML, XML",aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],case_insensitive:!0,contains:[{className:"meta",begin://,relevance:10,contains:[a,w,_,u,{begin:/\[/,end:/\]/,contains:[{className:"meta",begin://,contains:[a,u,w,_]}]}]},s.COMMENT(//,{relevance:10}),{begin://,relevance:10},i,{className:"meta",begin:/<\?xml/,end:/\?>/,relevance:10},{className:"tag",begin:/)/,end:/>/,keywords:{name:"style"},contains:[x],starts:{end:/<\/style>/,returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag",begin:/)/,end:/>/,keywords:{name:"script"},contains:[x],starts:{end:/<\/script>/,returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{className:"tag",begin:/<>|<\/>/},{className:"tag",begin:concat(//,/>/,/\s/)))),end:/\/?>/,contains:[{className:"name",begin:o,relevance:0,starts:x}]},{className:"tag",begin:concat(/<\//,lookahead(concat(o,/>/))),contains:[{className:"name",begin:o,relevance:0},{begin:/>/,relevance:0,endsParent:!0}]}]}}},17533(s){s.exports=function yaml(s){var o="true false yes no null",i="[\\w#;/?:@&=+$,.~*'()[\\]]+",a={className:"string",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/\S+/}],contains:[s.BACKSLASH_ESCAPE,{className:"template-variable",variants:[{begin:/\{\{/,end:/\}\}/},{begin:/%\{/,end:/\}/}]}]},u=s.inherit(a,{variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/[^\s,{}[\]]+/}]}),_={className:"number",begin:"\\b[0-9]{4}(-[0-9][0-9]){0,2}([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?(\\.[0-9]*)?([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?\\b"},w={end:",",endsWithParent:!0,excludeEnd:!0,keywords:o,relevance:0},x={begin:/\{/,end:/\}/,contains:[w],illegal:"\\n",relevance:0},C={begin:"\\[",end:"\\]",contains:[w],illegal:"\\n",relevance:0},j=[{className:"attr",variants:[{begin:"\\w[\\w :\\/.-]*:(?=[ \t]|$)"},{begin:'"\\w[\\w :\\/.-]*":(?=[ \t]|$)'},{begin:"'\\w[\\w :\\/.-]*':(?=[ \t]|$)"}]},{className:"meta",begin:"^---\\s*$",relevance:10},{className:"string",begin:"[\\|>]([1-9]?[+-])?[ ]*\\n( +)[^ ][^\\n]*\\n(\\2[^\\n]+\\n?)*"},{begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:"!\\w+!"+i},{className:"type",begin:"!<"+i+">"},{className:"type",begin:"!"+i},{className:"type",begin:"!!"+i},{className:"meta",begin:"&"+s.UNDERSCORE_IDENT_RE+"$"},{className:"meta",begin:"\\*"+s.UNDERSCORE_IDENT_RE+"$"},{className:"bullet",begin:"-(?=[ ]|$)",relevance:0},s.HASH_COMMENT_MODE,{beginKeywords:o,keywords:{literal:o}},_,{className:"number",begin:s.C_NUMBER_RE+"\\b",relevance:0},x,C,a],L=[...j];return L.pop(),L.push(u),w.contains=L,{name:"YAML",case_insensitive:!0,aliases:["yml"],contains:j}}},251(s,o){o.read=function(s,o,i,a,u){var _,w,x=8*u-a-1,C=(1<>1,L=-7,B=i?u-1:0,$=i?-1:1,U=s[o+B];for(B+=$,_=U&(1<<-L)-1,U>>=-L,L+=x;L>0;_=256*_+s[o+B],B+=$,L-=8);for(w=_&(1<<-L)-1,_>>=-L,L+=a;L>0;w=256*w+s[o+B],B+=$,L-=8);if(0===_)_=1-j;else{if(_===C)return w?NaN:1/0*(U?-1:1);w+=Math.pow(2,a),_-=j}return(U?-1:1)*w*Math.pow(2,_-a)},o.write=function(s,o,i,a,u,_){var w,x,C,j=8*_-u-1,L=(1<>1,$=23===u?Math.pow(2,-24)-Math.pow(2,-77):0,U=a?0:_-1,V=a?1:-1,z=o<0||0===o&&1/o<0?1:0;for(o=Math.abs(o),isNaN(o)||o===1/0?(x=isNaN(o)?1:0,w=L):(w=Math.floor(Math.log(o)/Math.LN2),o*(C=Math.pow(2,-w))<1&&(w--,C*=2),(o+=w+B>=1?$/C:$*Math.pow(2,1-B))*C>=2&&(w++,C/=2),w+B>=L?(x=0,w=L):w+B>=1?(x=(o*C-1)*Math.pow(2,u),w+=B):(x=o*Math.pow(2,B-1)*Math.pow(2,u),w=0));u>=8;s[i+U]=255&x,U+=V,x/=256,u-=8);for(w=w<0;s[i+U]=255&w,U+=V,w/=256,j-=8);s[i+U-V]|=128*z}},9404(s){s.exports=function(){"use strict";var s=Array.prototype.slice;function createClass(s,o){o&&(s.prototype=Object.create(o.prototype)),s.prototype.constructor=s}function Iterable(s){return isIterable(s)?s:Seq(s)}function KeyedIterable(s){return isKeyed(s)?s:KeyedSeq(s)}function IndexedIterable(s){return isIndexed(s)?s:IndexedSeq(s)}function SetIterable(s){return isIterable(s)&&!isAssociative(s)?s:SetSeq(s)}function isIterable(s){return!(!s||!s[o])}function isKeyed(s){return!(!s||!s[i])}function isIndexed(s){return!(!s||!s[a])}function isAssociative(s){return isKeyed(s)||isIndexed(s)}function isOrdered(s){return!(!s||!s[u])}createClass(KeyedIterable,Iterable),createClass(IndexedIterable,Iterable),createClass(SetIterable,Iterable),Iterable.isIterable=isIterable,Iterable.isKeyed=isKeyed,Iterable.isIndexed=isIndexed,Iterable.isAssociative=isAssociative,Iterable.isOrdered=isOrdered,Iterable.Keyed=KeyedIterable,Iterable.Indexed=IndexedIterable,Iterable.Set=SetIterable;var o="@@__IMMUTABLE_ITERABLE__@@",i="@@__IMMUTABLE_KEYED__@@",a="@@__IMMUTABLE_INDEXED__@@",u="@@__IMMUTABLE_ORDERED__@@",_="delete",w=5,x=1<>>0;if(""+i!==o||4294967295===i)return NaN;o=i}return o<0?ensureSize(s)+o:o}function returnTrue(){return!0}function wholeSlice(s,o,i){return(0===s||void 0!==i&&s<=-i)&&(void 0===o||void 0!==i&&o>=i)}function resolveBegin(s,o){return resolveIndex(s,o,0)}function resolveEnd(s,o){return resolveIndex(s,o,o)}function resolveIndex(s,o,i){return void 0===s?i:s<0?Math.max(0,o+s):void 0===o?s:Math.min(o,s)}var $=0,U=1,V=2,z="function"==typeof Symbol&&Symbol.iterator,Y="@@iterator",Z=z||Y;function Iterator(s){this.next=s}function iteratorValue(s,o,i,a){var u=0===s?o:1===s?i:[o,i];return a?a.value=u:a={value:u,done:!1},a}function iteratorDone(){return{value:void 0,done:!0}}function hasIterator(s){return!!getIteratorFn(s)}function isIterator(s){return s&&"function"==typeof s.next}function getIterator(s){var o=getIteratorFn(s);return o&&o.call(s)}function getIteratorFn(s){var o=s&&(z&&s[z]||s[Y]);if("function"==typeof o)return o}function isArrayLike(s){return s&&"number"==typeof s.length}function Seq(s){return null==s?emptySequence():isIterable(s)?s.toSeq():seqFromValue(s)}function KeyedSeq(s){return null==s?emptySequence().toKeyedSeq():isIterable(s)?isKeyed(s)?s.toSeq():s.fromEntrySeq():keyedSeqFromValue(s)}function IndexedSeq(s){return null==s?emptySequence():isIterable(s)?isKeyed(s)?s.entrySeq():s.toIndexedSeq():indexedSeqFromValue(s)}function SetSeq(s){return(null==s?emptySequence():isIterable(s)?isKeyed(s)?s.entrySeq():s:indexedSeqFromValue(s)).toSetSeq()}Iterator.prototype.toString=function(){return"[Iterator]"},Iterator.KEYS=$,Iterator.VALUES=U,Iterator.ENTRIES=V,Iterator.prototype.inspect=Iterator.prototype.toSource=function(){return this.toString()},Iterator.prototype[Z]=function(){return this},createClass(Seq,Iterable),Seq.of=function(){return Seq(arguments)},Seq.prototype.toSeq=function(){return this},Seq.prototype.toString=function(){return this.__toString("Seq {","}")},Seq.prototype.cacheResult=function(){return!this._cache&&this.__iterateUncached&&(this._cache=this.entrySeq().toArray(),this.size=this._cache.length),this},Seq.prototype.__iterate=function(s,o){return seqIterate(this,s,o,!0)},Seq.prototype.__iterator=function(s,o){return seqIterator(this,s,o,!0)},createClass(KeyedSeq,Seq),KeyedSeq.prototype.toKeyedSeq=function(){return this},createClass(IndexedSeq,Seq),IndexedSeq.of=function(){return IndexedSeq(arguments)},IndexedSeq.prototype.toIndexedSeq=function(){return this},IndexedSeq.prototype.toString=function(){return this.__toString("Seq [","]")},IndexedSeq.prototype.__iterate=function(s,o){return seqIterate(this,s,o,!1)},IndexedSeq.prototype.__iterator=function(s,o){return seqIterator(this,s,o,!1)},createClass(SetSeq,Seq),SetSeq.of=function(){return SetSeq(arguments)},SetSeq.prototype.toSetSeq=function(){return this},Seq.isSeq=isSeq,Seq.Keyed=KeyedSeq,Seq.Set=SetSeq,Seq.Indexed=IndexedSeq;var ee,ie,ae,ce="@@__IMMUTABLE_SEQ__@@";function ArraySeq(s){this._array=s,this.size=s.length}function ObjectSeq(s){var o=Object.keys(s);this._object=s,this._keys=o,this.size=o.length}function IterableSeq(s){this._iterable=s,this.size=s.length||s.size}function IteratorSeq(s){this._iterator=s,this._iteratorCache=[]}function isSeq(s){return!(!s||!s[ce])}function emptySequence(){return ee||(ee=new ArraySeq([]))}function keyedSeqFromValue(s){var o=Array.isArray(s)?new ArraySeq(s).fromEntrySeq():isIterator(s)?new IteratorSeq(s).fromEntrySeq():hasIterator(s)?new IterableSeq(s).fromEntrySeq():"object"==typeof s?new ObjectSeq(s):void 0;if(!o)throw new TypeError("Expected Array or iterable object of [k, v] entries, or keyed object: "+s);return o}function indexedSeqFromValue(s){var o=maybeIndexedSeqFromValue(s);if(!o)throw new TypeError("Expected Array or iterable object of values: "+s);return o}function seqFromValue(s){var o=maybeIndexedSeqFromValue(s)||"object"==typeof s&&new ObjectSeq(s);if(!o)throw new TypeError("Expected Array or iterable object of values, or keyed object: "+s);return o}function maybeIndexedSeqFromValue(s){return isArrayLike(s)?new ArraySeq(s):isIterator(s)?new IteratorSeq(s):hasIterator(s)?new IterableSeq(s):void 0}function seqIterate(s,o,i,a){var u=s._cache;if(u){for(var _=u.length-1,w=0;w<=_;w++){var x=u[i?_-w:w];if(!1===o(x[1],a?x[0]:w,s))return w+1}return w}return s.__iterateUncached(o,i)}function seqIterator(s,o,i,a){var u=s._cache;if(u){var _=u.length-1,w=0;return new Iterator((function(){var s=u[i?_-w:w];return w++>_?iteratorDone():iteratorValue(o,a?s[0]:w-1,s[1])}))}return s.__iteratorUncached(o,i)}function fromJS(s,o){return o?fromJSWith(o,s,"",{"":s}):fromJSDefault(s)}function fromJSWith(s,o,i,a){return Array.isArray(o)?s.call(a,i,IndexedSeq(o).map((function(i,a){return fromJSWith(s,i,a,o)}))):isPlainObj(o)?s.call(a,i,KeyedSeq(o).map((function(i,a){return fromJSWith(s,i,a,o)}))):o}function fromJSDefault(s){return Array.isArray(s)?IndexedSeq(s).map(fromJSDefault).toList():isPlainObj(s)?KeyedSeq(s).map(fromJSDefault).toMap():s}function isPlainObj(s){return s&&(s.constructor===Object||void 0===s.constructor)}function is(s,o){if(s===o||s!=s&&o!=o)return!0;if(!s||!o)return!1;if("function"==typeof s.valueOf&&"function"==typeof o.valueOf){if((s=s.valueOf())===(o=o.valueOf())||s!=s&&o!=o)return!0;if(!s||!o)return!1}return!("function"!=typeof s.equals||"function"!=typeof o.equals||!s.equals(o))}function deepEqual(s,o){if(s===o)return!0;if(!isIterable(o)||void 0!==s.size&&void 0!==o.size&&s.size!==o.size||void 0!==s.__hash&&void 0!==o.__hash&&s.__hash!==o.__hash||isKeyed(s)!==isKeyed(o)||isIndexed(s)!==isIndexed(o)||isOrdered(s)!==isOrdered(o))return!1;if(0===s.size&&0===o.size)return!0;var i=!isAssociative(s);if(isOrdered(s)){var a=s.entries();return o.every((function(s,o){var u=a.next().value;return u&&is(u[1],s)&&(i||is(u[0],o))}))&&a.next().done}var u=!1;if(void 0===s.size)if(void 0===o.size)"function"==typeof s.cacheResult&&s.cacheResult();else{u=!0;var _=s;s=o,o=_}var w=!0,x=o.__iterate((function(o,a){if(i?!s.has(o):u?!is(o,s.get(a,j)):!is(s.get(a,j),o))return w=!1,!1}));return w&&s.size===x}function Repeat(s,o){if(!(this instanceof Repeat))return new Repeat(s,o);if(this._value=s,this.size=void 0===o?1/0:Math.max(0,o),0===this.size){if(ie)return ie;ie=this}}function invariant(s,o){if(!s)throw new Error(o)}function Range(s,o,i){if(!(this instanceof Range))return new Range(s,o,i);if(invariant(0!==i,"Cannot step a Range by 0"),s=s||0,void 0===o&&(o=1/0),i=void 0===i?1:Math.abs(i),oa?iteratorDone():iteratorValue(s,u,i[o?a-u++:u++])}))},createClass(ObjectSeq,KeyedSeq),ObjectSeq.prototype.get=function(s,o){return void 0===o||this.has(s)?this._object[s]:o},ObjectSeq.prototype.has=function(s){return this._object.hasOwnProperty(s)},ObjectSeq.prototype.__iterate=function(s,o){for(var i=this._object,a=this._keys,u=a.length-1,_=0;_<=u;_++){var w=a[o?u-_:_];if(!1===s(i[w],w,this))return _+1}return _},ObjectSeq.prototype.__iterator=function(s,o){var i=this._object,a=this._keys,u=a.length-1,_=0;return new Iterator((function(){var w=a[o?u-_:_];return _++>u?iteratorDone():iteratorValue(s,w,i[w])}))},ObjectSeq.prototype[u]=!0,createClass(IterableSeq,IndexedSeq),IterableSeq.prototype.__iterateUncached=function(s,o){if(o)return this.cacheResult().__iterate(s,o);var i=getIterator(this._iterable),a=0;if(isIterator(i))for(var u;!(u=i.next()).done&&!1!==s(u.value,a++,this););return a},IterableSeq.prototype.__iteratorUncached=function(s,o){if(o)return this.cacheResult().__iterator(s,o);var i=getIterator(this._iterable);if(!isIterator(i))return new Iterator(iteratorDone);var a=0;return new Iterator((function(){var o=i.next();return o.done?o:iteratorValue(s,a++,o.value)}))},createClass(IteratorSeq,IndexedSeq),IteratorSeq.prototype.__iterateUncached=function(s,o){if(o)return this.cacheResult().__iterate(s,o);for(var i,a=this._iterator,u=this._iteratorCache,_=0;_=a.length){var o=i.next();if(o.done)return o;a[u]=o.value}return iteratorValue(s,u,a[u++])}))},createClass(Repeat,IndexedSeq),Repeat.prototype.toString=function(){return 0===this.size?"Repeat []":"Repeat [ "+this._value+" "+this.size+" times ]"},Repeat.prototype.get=function(s,o){return this.has(s)?this._value:o},Repeat.prototype.includes=function(s){return is(this._value,s)},Repeat.prototype.slice=function(s,o){var i=this.size;return wholeSlice(s,o,i)?this:new Repeat(this._value,resolveEnd(o,i)-resolveBegin(s,i))},Repeat.prototype.reverse=function(){return this},Repeat.prototype.indexOf=function(s){return is(this._value,s)?0:-1},Repeat.prototype.lastIndexOf=function(s){return is(this._value,s)?this.size:-1},Repeat.prototype.__iterate=function(s,o){for(var i=0;i=0&&o=0&&ii?iteratorDone():iteratorValue(s,_++,w)}))},Range.prototype.equals=function(s){return s instanceof Range?this._start===s._start&&this._end===s._end&&this._step===s._step:deepEqual(this,s)},createClass(Collection,Iterable),createClass(KeyedCollection,Collection),createClass(IndexedCollection,Collection),createClass(SetCollection,Collection),Collection.Keyed=KeyedCollection,Collection.Indexed=IndexedCollection,Collection.Set=SetCollection;var le="function"==typeof Math.imul&&-2===Math.imul(4294967295,2)?Math.imul:function imul(s,o){var i=65535&(s|=0),a=65535&(o|=0);return i*a+((s>>>16)*a+i*(o>>>16)<<16>>>0)|0};function smi(s){return s>>>1&1073741824|3221225471&s}function hash(s){if(!1===s||null==s)return 0;if("function"==typeof s.valueOf&&(!1===(s=s.valueOf())||null==s))return 0;if(!0===s)return 1;var o=typeof s;if("number"===o){if(s!=s||s===1/0)return 0;var i=0|s;for(i!==s&&(i^=4294967295*s);s>4294967295;)i^=s/=4294967295;return smi(i)}if("string"===o)return s.length>_e?cachedHashString(s):hashString(s);if("function"==typeof s.hashCode)return s.hashCode();if("object"===o)return hashJSObj(s);if("function"==typeof s.toString)return hashString(s.toString());throw new Error("Value type "+o+" cannot be hashed.")}function cachedHashString(s){var o=Pe[s];return void 0===o&&(o=hashString(s),xe===we&&(xe=0,Pe={}),xe++,Pe[s]=o),o}function hashString(s){for(var o=0,i=0;i0)switch(s.nodeType){case 1:return s.uniqueID;case 9:return s.documentElement&&s.documentElement.uniqueID}}var fe,ye="function"==typeof WeakMap;ye&&(fe=new WeakMap);var be=0,Se="__immutablehash__";"function"==typeof Symbol&&(Se=Symbol(Se));var _e=16,we=255,xe=0,Pe={};function assertNotInfinite(s){invariant(s!==1/0,"Cannot perform this action with an infinite size.")}function Map(s){return null==s?emptyMap():isMap(s)&&!isOrdered(s)?s:emptyMap().withMutations((function(o){var i=KeyedIterable(s);assertNotInfinite(i.size),i.forEach((function(s,i){return o.set(i,s)}))}))}function isMap(s){return!(!s||!s[Re])}createClass(Map,KeyedCollection),Map.of=function(){var o=s.call(arguments,0);return emptyMap().withMutations((function(s){for(var i=0;i=o.length)throw new Error("Missing value for key: "+o[i]);s.set(o[i],o[i+1])}}))},Map.prototype.toString=function(){return this.__toString("Map {","}")},Map.prototype.get=function(s,o){return this._root?this._root.get(0,void 0,s,o):o},Map.prototype.set=function(s,o){return updateMap(this,s,o)},Map.prototype.setIn=function(s,o){return this.updateIn(s,j,(function(){return o}))},Map.prototype.remove=function(s){return updateMap(this,s,j)},Map.prototype.deleteIn=function(s){return this.updateIn(s,(function(){return j}))},Map.prototype.update=function(s,o,i){return 1===arguments.length?s(this):this.updateIn([s],o,i)},Map.prototype.updateIn=function(s,o,i){i||(i=o,o=void 0);var a=updateInDeepMap(this,forceIterator(s),o,i);return a===j?void 0:a},Map.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._root=null,this.__hash=void 0,this.__altered=!0,this):emptyMap()},Map.prototype.merge=function(){return mergeIntoMapWith(this,void 0,arguments)},Map.prototype.mergeWith=function(o){return mergeIntoMapWith(this,o,s.call(arguments,1))},Map.prototype.mergeIn=function(o){var i=s.call(arguments,1);return this.updateIn(o,emptyMap(),(function(s){return"function"==typeof s.merge?s.merge.apply(s,i):i[i.length-1]}))},Map.prototype.mergeDeep=function(){return mergeIntoMapWith(this,deepMerger,arguments)},Map.prototype.mergeDeepWith=function(o){var i=s.call(arguments,1);return mergeIntoMapWith(this,deepMergerWith(o),i)},Map.prototype.mergeDeepIn=function(o){var i=s.call(arguments,1);return this.updateIn(o,emptyMap(),(function(s){return"function"==typeof s.mergeDeep?s.mergeDeep.apply(s,i):i[i.length-1]}))},Map.prototype.sort=function(s){return OrderedMap(sortFactory(this,s))},Map.prototype.sortBy=function(s,o){return OrderedMap(sortFactory(this,o,s))},Map.prototype.withMutations=function(s){var o=this.asMutable();return s(o),o.wasAltered()?o.__ensureOwner(this.__ownerID):this},Map.prototype.asMutable=function(){return this.__ownerID?this:this.__ensureOwner(new OwnerID)},Map.prototype.asImmutable=function(){return this.__ensureOwner()},Map.prototype.wasAltered=function(){return this.__altered},Map.prototype.__iterator=function(s,o){return new MapIterator(this,s,o)},Map.prototype.__iterate=function(s,o){var i=this,a=0;return this._root&&this._root.iterate((function(o){return a++,s(o[1],o[0],i)}),o),a},Map.prototype.__ensureOwner=function(s){return s===this.__ownerID?this:s?makeMap(this.size,this._root,s,this.__hash):(this.__ownerID=s,this.__altered=!1,this)},Map.isMap=isMap;var Te,Re="@@__IMMUTABLE_MAP__@@",$e=Map.prototype;function ArrayMapNode(s,o){this.ownerID=s,this.entries=o}function BitmapIndexedNode(s,o,i){this.ownerID=s,this.bitmap=o,this.nodes=i}function HashArrayMapNode(s,o,i){this.ownerID=s,this.count=o,this.nodes=i}function HashCollisionNode(s,o,i){this.ownerID=s,this.keyHash=o,this.entries=i}function ValueNode(s,o,i){this.ownerID=s,this.keyHash=o,this.entry=i}function MapIterator(s,o,i){this._type=o,this._reverse=i,this._stack=s._root&&mapIteratorFrame(s._root)}function mapIteratorValue(s,o){return iteratorValue(s,o[0],o[1])}function mapIteratorFrame(s,o){return{node:s,index:0,__prev:o}}function makeMap(s,o,i,a){var u=Object.create($e);return u.size=s,u._root=o,u.__ownerID=i,u.__hash=a,u.__altered=!1,u}function emptyMap(){return Te||(Te=makeMap(0))}function updateMap(s,o,i){var a,u;if(s._root){var _=MakeRef(L),w=MakeRef(B);if(a=updateNode(s._root,s.__ownerID,0,void 0,o,i,_,w),!w.value)return s;u=s.size+(_.value?i===j?-1:1:0)}else{if(i===j)return s;u=1,a=new ArrayMapNode(s.__ownerID,[[o,i]])}return s.__ownerID?(s.size=u,s._root=a,s.__hash=void 0,s.__altered=!0,s):a?makeMap(u,a):emptyMap()}function updateNode(s,o,i,a,u,_,w,x){return s?s.update(o,i,a,u,_,w,x):_===j?s:(SetRef(x),SetRef(w),new ValueNode(o,a,[u,_]))}function isLeafNode(s){return s.constructor===ValueNode||s.constructor===HashCollisionNode}function mergeIntoNode(s,o,i,a,u){if(s.keyHash===a)return new HashCollisionNode(o,a,[s.entry,u]);var _,x=(0===i?s.keyHash:s.keyHash>>>i)&C,j=(0===i?a:a>>>i)&C;return new BitmapIndexedNode(o,1<>>=1)w[C]=1&i?o[_++]:void 0;return w[a]=u,new HashArrayMapNode(s,_+1,w)}function mergeIntoMapWith(s,o,i){for(var a=[],u=0;u>1&1431655765))+(s>>2&858993459))+(s>>4)&252645135,s+=s>>8,127&(s+=s>>16)}function setIn(s,o,i,a){var u=a?s:arrCopy(s);return u[o]=i,u}function spliceIn(s,o,i,a){var u=s.length+1;if(a&&o+1===u)return s[o]=i,s;for(var _=new Array(u),w=0,x=0;x=qe)return createNodes(s,C,a,u);var U=s&&s===this.ownerID,V=U?C:arrCopy(C);return $?x?L===B-1?V.pop():V[L]=V.pop():V[L]=[a,u]:V.push([a,u]),U?(this.entries=V,this):new ArrayMapNode(s,V)}},BitmapIndexedNode.prototype.get=function(s,o,i,a){void 0===o&&(o=hash(i));var u=1<<((0===s?o:o>>>s)&C),_=this.bitmap;return _&u?this.nodes[popCount(_&u-1)].get(s+w,o,i,a):a},BitmapIndexedNode.prototype.update=function(s,o,i,a,u,_,x){void 0===i&&(i=hash(a));var L=(0===o?i:i>>>o)&C,B=1<=ze)return expandNodes(s,z,$,L,Z);if(U&&!Z&&2===z.length&&isLeafNode(z[1^V]))return z[1^V];if(U&&Z&&1===z.length&&isLeafNode(Z))return Z;var ee=s&&s===this.ownerID,ie=U?Z?$:$^B:$|B,ae=U?Z?setIn(z,V,Z,ee):spliceOut(z,V,ee):spliceIn(z,V,Z,ee);return ee?(this.bitmap=ie,this.nodes=ae,this):new BitmapIndexedNode(s,ie,ae)},HashArrayMapNode.prototype.get=function(s,o,i,a){void 0===o&&(o=hash(i));var u=(0===s?o:o>>>s)&C,_=this.nodes[u];return _?_.get(s+w,o,i,a):a},HashArrayMapNode.prototype.update=function(s,o,i,a,u,_,x){void 0===i&&(i=hash(a));var L=(0===o?i:i>>>o)&C,B=u===j,$=this.nodes,U=$[L];if(B&&!U)return this;var V=updateNode(U,s,o+w,i,a,u,_,x);if(V===U)return this;var z=this.count;if(U){if(!V&&--z0&&a=0&&s>>o&C;if(a>=this.array.length)return new VNode([],s);var u,_=0===a;if(o>0){var x=this.array[a];if((u=x&&x.removeBefore(s,o-w,i))===x&&_)return this}if(_&&!u)return this;var j=editableVNode(this,s);if(!_)for(var L=0;L>>o&C;if(u>=this.array.length)return this;if(o>0){var _=this.array[u];if((a=_&&_.removeAfter(s,o-w,i))===_&&u===this.array.length-1)return this}var x=editableVNode(this,s);return x.array.splice(u+1),a&&(x.array[u]=a),x};var Xe,Qe,et={};function iterateList(s,o){var i=s._origin,a=s._capacity,u=getTailOffset(a),_=s._tail;return iterateNodeOrLeaf(s._root,s._level,0);function iterateNodeOrLeaf(s,o,i){return 0===o?iterateLeaf(s,i):iterateNode(s,o,i)}function iterateLeaf(s,w){var C=w===u?_&&_.array:s&&s.array,j=w>i?0:i-w,L=a-w;return L>x&&(L=x),function(){if(j===L)return et;var s=o?--L:j++;return C&&C[s]}}function iterateNode(s,u,_){var C,j=s&&s.array,L=_>i?0:i-_>>u,B=1+(a-_>>u);return B>x&&(B=x),function(){for(;;){if(C){var s=C();if(s!==et)return s;C=null}if(L===B)return et;var i=o?--B:L++;C=iterateNodeOrLeaf(j&&j[i],u-w,_+(i<=s.size||o<0)return s.withMutations((function(s){o<0?setListBounds(s,o).set(0,i):setListBounds(s,0,o+1).set(o,i)}));o+=s._origin;var a=s._tail,u=s._root,_=MakeRef(B);return o>=getTailOffset(s._capacity)?a=updateVNode(a,s.__ownerID,0,o,i,_):u=updateVNode(u,s.__ownerID,s._level,o,i,_),_.value?s.__ownerID?(s._root=u,s._tail=a,s.__hash=void 0,s.__altered=!0,s):makeList(s._origin,s._capacity,s._level,u,a):s}function updateVNode(s,o,i,a,u,_){var x,j=a>>>i&C,L=s&&j0){var B=s&&s.array[j],$=updateVNode(B,o,i-w,a,u,_);return $===B?s:((x=editableVNode(s,o)).array[j]=$,x)}return L&&s.array[j]===u?s:(SetRef(_),x=editableVNode(s,o),void 0===u&&j===x.array.length-1?x.array.pop():x.array[j]=u,x)}function editableVNode(s,o){return o&&s&&o===s.ownerID?s:new VNode(s?s.array.slice():[],o)}function listNodeFor(s,o){if(o>=getTailOffset(s._capacity))return s._tail;if(o<1<0;)i=i.array[o>>>a&C],a-=w;return i}}function setListBounds(s,o,i){void 0!==o&&(o|=0),void 0!==i&&(i|=0);var a=s.__ownerID||new OwnerID,u=s._origin,_=s._capacity,x=u+o,j=void 0===i?_:i<0?_+i:u+i;if(x===u&&j===_)return s;if(x>=j)return s.clear();for(var L=s._level,B=s._root,$=0;x+$<0;)B=new VNode(B&&B.array.length?[void 0,B]:[],a),$+=1<<(L+=w);$&&(x+=$,u+=$,j+=$,_+=$);for(var U=getTailOffset(_),V=getTailOffset(j);V>=1<U?new VNode([],a):z;if(z&&V>U&&x<_&&z.array.length){for(var Z=B=editableVNode(B,a),ee=L;ee>w;ee-=w){var ie=U>>>ee&C;Z=Z.array[ie]=editableVNode(Z.array[ie],a)}Z.array[U>>>w&C]=z}if(j<_&&(Y=Y&&Y.removeAfter(a,0,j)),x>=V)x-=V,j-=V,L=w,B=null,Y=Y&&Y.removeBefore(a,0,x);else if(x>u||V>>L&C;if(ae!==V>>>L&C)break;ae&&($+=(1<u&&(B=B.removeBefore(a,L,x-$)),B&&Vu&&(u=x.size),isIterable(w)||(x=x.map((function(s){return fromJS(s)}))),a.push(x)}return u>s.size&&(s=s.setSize(u)),mergeIntoCollectionWith(s,o,a)}function getTailOffset(s){return s>>w<=x&&w.size>=2*_.size?(a=(u=w.filter((function(s,o){return void 0!==s&&C!==o}))).toKeyedSeq().map((function(s){return s[0]})).flip().toMap(),s.__ownerID&&(a.__ownerID=u.__ownerID=s.__ownerID)):(a=_.remove(o),u=C===w.size-1?w.pop():w.set(C,void 0))}else if(L){if(i===w.get(C)[1])return s;a=_,u=w.set(C,[o,i])}else a=_.set(o,w.size),u=w.set(w.size,[o,i]);return s.__ownerID?(s.size=a.size,s._map=a,s._list=u,s.__hash=void 0,s):makeOrderedMap(a,u)}function ToKeyedSequence(s,o){this._iter=s,this._useKeys=o,this.size=s.size}function ToIndexedSequence(s){this._iter=s,this.size=s.size}function ToSetSequence(s){this._iter=s,this.size=s.size}function FromEntriesSequence(s){this._iter=s,this.size=s.size}function flipFactory(s){var o=makeSequence(s);return o._iter=s,o.size=s.size,o.flip=function(){return s},o.reverse=function(){var o=s.reverse.apply(this);return o.flip=function(){return s.reverse()},o},o.has=function(o){return s.includes(o)},o.includes=function(o){return s.has(o)},o.cacheResult=cacheResultThrough,o.__iterateUncached=function(o,i){var a=this;return s.__iterate((function(s,i){return!1!==o(i,s,a)}),i)},o.__iteratorUncached=function(o,i){if(o===V){var a=s.__iterator(o,i);return new Iterator((function(){var s=a.next();if(!s.done){var o=s.value[0];s.value[0]=s.value[1],s.value[1]=o}return s}))}return s.__iterator(o===U?$:U,i)},o}function mapFactory(s,o,i){var a=makeSequence(s);return a.size=s.size,a.has=function(o){return s.has(o)},a.get=function(a,u){var _=s.get(a,j);return _===j?u:o.call(i,_,a,s)},a.__iterateUncached=function(a,u){var _=this;return s.__iterate((function(s,u,w){return!1!==a(o.call(i,s,u,w),u,_)}),u)},a.__iteratorUncached=function(a,u){var _=s.__iterator(V,u);return new Iterator((function(){var u=_.next();if(u.done)return u;var w=u.value,x=w[0];return iteratorValue(a,x,o.call(i,w[1],x,s),u)}))},a}function reverseFactory(s,o){var i=makeSequence(s);return i._iter=s,i.size=s.size,i.reverse=function(){return s},s.flip&&(i.flip=function(){var o=flipFactory(s);return o.reverse=function(){return s.flip()},o}),i.get=function(i,a){return s.get(o?i:-1-i,a)},i.has=function(i){return s.has(o?i:-1-i)},i.includes=function(o){return s.includes(o)},i.cacheResult=cacheResultThrough,i.__iterate=function(o,i){var a=this;return s.__iterate((function(s,i){return o(s,i,a)}),!i)},i.__iterator=function(o,i){return s.__iterator(o,!i)},i}function filterFactory(s,o,i,a){var u=makeSequence(s);return a&&(u.has=function(a){var u=s.get(a,j);return u!==j&&!!o.call(i,u,a,s)},u.get=function(a,u){var _=s.get(a,j);return _!==j&&o.call(i,_,a,s)?_:u}),u.__iterateUncached=function(u,_){var w=this,x=0;return s.__iterate((function(s,_,C){if(o.call(i,s,_,C))return x++,u(s,a?_:x-1,w)}),_),x},u.__iteratorUncached=function(u,_){var w=s.__iterator(V,_),x=0;return new Iterator((function(){for(;;){var _=w.next();if(_.done)return _;var C=_.value,j=C[0],L=C[1];if(o.call(i,L,j,s))return iteratorValue(u,a?j:x++,L,_)}}))},u}function countByFactory(s,o,i){var a=Map().asMutable();return s.__iterate((function(u,_){a.update(o.call(i,u,_,s),0,(function(s){return s+1}))})),a.asImmutable()}function groupByFactory(s,o,i){var a=isKeyed(s),u=(isOrdered(s)?OrderedMap():Map()).asMutable();s.__iterate((function(_,w){u.update(o.call(i,_,w,s),(function(s){return(s=s||[]).push(a?[w,_]:_),s}))}));var _=iterableClass(s);return u.map((function(o){return reify(s,_(o))}))}function sliceFactory(s,o,i,a){var u=s.size;if(void 0!==o&&(o|=0),void 0!==i&&(i===1/0?i=u:i|=0),wholeSlice(o,i,u))return s;var _=resolveBegin(o,u),w=resolveEnd(i,u);if(_!=_||w!=w)return sliceFactory(s.toSeq().cacheResult(),o,i,a);var x,C=w-_;C==C&&(x=C<0?0:C);var j=makeSequence(s);return j.size=0===x?x:s.size&&x||void 0,!a&&isSeq(s)&&x>=0&&(j.get=function(o,i){return(o=wrapIndex(this,o))>=0&&ox)return iteratorDone();var s=u.next();return a||o===U?s:iteratorValue(o,C-1,o===$?void 0:s.value[1],s)}))},j}function takeWhileFactory(s,o,i){var a=makeSequence(s);return a.__iterateUncached=function(a,u){var _=this;if(u)return this.cacheResult().__iterate(a,u);var w=0;return s.__iterate((function(s,u,x){return o.call(i,s,u,x)&&++w&&a(s,u,_)})),w},a.__iteratorUncached=function(a,u){var _=this;if(u)return this.cacheResult().__iterator(a,u);var w=s.__iterator(V,u),x=!0;return new Iterator((function(){if(!x)return iteratorDone();var s=w.next();if(s.done)return s;var u=s.value,C=u[0],j=u[1];return o.call(i,j,C,_)?a===V?s:iteratorValue(a,C,j,s):(x=!1,iteratorDone())}))},a}function skipWhileFactory(s,o,i,a){var u=makeSequence(s);return u.__iterateUncached=function(u,_){var w=this;if(_)return this.cacheResult().__iterate(u,_);var x=!0,C=0;return s.__iterate((function(s,_,j){if(!x||!(x=o.call(i,s,_,j)))return C++,u(s,a?_:C-1,w)})),C},u.__iteratorUncached=function(u,_){var w=this;if(_)return this.cacheResult().__iterator(u,_);var x=s.__iterator(V,_),C=!0,j=0;return new Iterator((function(){var s,_,L;do{if((s=x.next()).done)return a||u===U?s:iteratorValue(u,j++,u===$?void 0:s.value[1],s);var B=s.value;_=B[0],L=B[1],C&&(C=o.call(i,L,_,w))}while(C);return u===V?s:iteratorValue(u,_,L,s)}))},u}function concatFactory(s,o){var i=isKeyed(s),a=[s].concat(o).map((function(s){return isIterable(s)?i&&(s=KeyedIterable(s)):s=i?keyedSeqFromValue(s):indexedSeqFromValue(Array.isArray(s)?s:[s]),s})).filter((function(s){return 0!==s.size}));if(0===a.length)return s;if(1===a.length){var u=a[0];if(u===s||i&&isKeyed(u)||isIndexed(s)&&isIndexed(u))return u}var _=new ArraySeq(a);return i?_=_.toKeyedSeq():isIndexed(s)||(_=_.toSetSeq()),(_=_.flatten(!0)).size=a.reduce((function(s,o){if(void 0!==s){var i=o.size;if(void 0!==i)return s+i}}),0),_}function flattenFactory(s,o,i){var a=makeSequence(s);return a.__iterateUncached=function(a,u){var _=0,w=!1;function flatDeep(s,x){var C=this;s.__iterate((function(s,u){return(!o||x0}function zipWithFactory(s,o,i){var a=makeSequence(s);return a.size=new ArraySeq(i).map((function(s){return s.size})).min(),a.__iterate=function(s,o){for(var i,a=this.__iterator(U,o),u=0;!(i=a.next()).done&&!1!==s(i.value,u++,this););return u},a.__iteratorUncached=function(s,a){var u=i.map((function(s){return s=Iterable(s),getIterator(a?s.reverse():s)})),_=0,w=!1;return new Iterator((function(){var i;return w||(i=u.map((function(s){return s.next()})),w=i.some((function(s){return s.done}))),w?iteratorDone():iteratorValue(s,_++,o.apply(null,i.map((function(s){return s.value}))))}))},a}function reify(s,o){return isSeq(s)?o:s.constructor(o)}function validateEntry(s){if(s!==Object(s))throw new TypeError("Expected [K, V] tuple: "+s)}function resolveSize(s){return assertNotInfinite(s.size),ensureSize(s)}function iterableClass(s){return isKeyed(s)?KeyedIterable:isIndexed(s)?IndexedIterable:SetIterable}function makeSequence(s){return Object.create((isKeyed(s)?KeyedSeq:isIndexed(s)?IndexedSeq:SetSeq).prototype)}function cacheResultThrough(){return this._iter.cacheResult?(this._iter.cacheResult(),this.size=this._iter.size,this):Seq.prototype.cacheResult.call(this)}function defaultComparator(s,o){return s>o?1:s=0;i--)o={value:arguments[i],next:o};return this.__ownerID?(this.size=s,this._head=o,this.__hash=void 0,this.__altered=!0,this):makeStack(s,o)},Stack.prototype.pushAll=function(s){if(0===(s=IndexedIterable(s)).size)return this;assertNotInfinite(s.size);var o=this.size,i=this._head;return s.reverse().forEach((function(s){o++,i={value:s,next:i}})),this.__ownerID?(this.size=o,this._head=i,this.__hash=void 0,this.__altered=!0,this):makeStack(o,i)},Stack.prototype.pop=function(){return this.slice(1)},Stack.prototype.unshift=function(){return this.push.apply(this,arguments)},Stack.prototype.unshiftAll=function(s){return this.pushAll(s)},Stack.prototype.shift=function(){return this.pop.apply(this,arguments)},Stack.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._head=void 0,this.__hash=void 0,this.__altered=!0,this):emptyStack()},Stack.prototype.slice=function(s,o){if(wholeSlice(s,o,this.size))return this;var i=resolveBegin(s,this.size);if(resolveEnd(o,this.size)!==this.size)return IndexedCollection.prototype.slice.call(this,s,o);for(var a=this.size-i,u=this._head;i--;)u=u.next;return this.__ownerID?(this.size=a,this._head=u,this.__hash=void 0,this.__altered=!0,this):makeStack(a,u)},Stack.prototype.__ensureOwner=function(s){return s===this.__ownerID?this:s?makeStack(this.size,this._head,s,this.__hash):(this.__ownerID=s,this.__altered=!1,this)},Stack.prototype.__iterate=function(s,o){if(o)return this.reverse().__iterate(s);for(var i=0,a=this._head;a&&!1!==s(a.value,i++,this);)a=a.next;return i},Stack.prototype.__iterator=function(s,o){if(o)return this.reverse().__iterator(s);var i=0,a=this._head;return new Iterator((function(){if(a){var o=a.value;return a=a.next,iteratorValue(s,i++,o)}return iteratorDone()}))},Stack.isStack=isStack;var at,ct="@@__IMMUTABLE_STACK__@@",lt=Stack.prototype;function makeStack(s,o,i,a){var u=Object.create(lt);return u.size=s,u._head=o,u.__ownerID=i,u.__hash=a,u.__altered=!1,u}function emptyStack(){return at||(at=makeStack(0))}function mixin(s,o){var keyCopier=function(i){s.prototype[i]=o[i]};return Object.keys(o).forEach(keyCopier),Object.getOwnPropertySymbols&&Object.getOwnPropertySymbols(o).forEach(keyCopier),s}function isProtoKey(s){return"string"==typeof s&&("__proto__"===s||"constructor"===s)}lt[ct]=!0,lt.withMutations=$e.withMutations,lt.asMutable=$e.asMutable,lt.asImmutable=$e.asImmutable,lt.wasAltered=$e.wasAltered,Iterable.Iterator=Iterator,mixin(Iterable,{toArray:function(){assertNotInfinite(this.size);var s=new Array(this.size||0);return this.valueSeq().__iterate((function(o,i){s[i]=o})),s},toIndexedSeq:function(){return new ToIndexedSequence(this)},toJS:function(){return this.toSeq().map((function(s){return s&&"function"==typeof s.toJS?s.toJS():s})).__toJS()},toJSON:function(){return this.toSeq().map((function(s){return s&&"function"==typeof s.toJSON?s.toJSON():s})).__toJS()},toKeyedSeq:function(){return new ToKeyedSequence(this,!0)},toMap:function(){return Map(this.toKeyedSeq())},toObject:function(){assertNotInfinite(this.size);var s={};return this.__iterate((function(o,i){isProtoKey(i)||(s[i]=o)})),s},toOrderedMap:function(){return OrderedMap(this.toKeyedSeq())},toOrderedSet:function(){return OrderedSet(isKeyed(this)?this.valueSeq():this)},toSet:function(){return Set(isKeyed(this)?this.valueSeq():this)},toSetSeq:function(){return new ToSetSequence(this)},toSeq:function(){return isIndexed(this)?this.toIndexedSeq():isKeyed(this)?this.toKeyedSeq():this.toSetSeq()},toStack:function(){return Stack(isKeyed(this)?this.valueSeq():this)},toList:function(){return List(isKeyed(this)?this.valueSeq():this)},toString:function(){return"[Iterable]"},__toString:function(s,o){return 0===this.size?s+o:s+" "+this.toSeq().map(this.__toStringMapper).join(", ")+" "+o},concat:function(){return reify(this,concatFactory(this,s.call(arguments,0)))},includes:function(s){return this.some((function(o){return is(o,s)}))},entries:function(){return this.__iterator(V)},every:function(s,o){assertNotInfinite(this.size);var i=!0;return this.__iterate((function(a,u,_){if(!s.call(o,a,u,_))return i=!1,!1})),i},filter:function(s,o){return reify(this,filterFactory(this,s,o,!0))},find:function(s,o,i){var a=this.findEntry(s,o);return a?a[1]:i},forEach:function(s,o){return assertNotInfinite(this.size),this.__iterate(o?s.bind(o):s)},join:function(s){assertNotInfinite(this.size),s=void 0!==s?""+s:",";var o="",i=!0;return this.__iterate((function(a){i?i=!1:o+=s,o+=null!=a?a.toString():""})),o},keys:function(){return this.__iterator($)},map:function(s,o){return reify(this,mapFactory(this,s,o))},reduce:function(s,o,i){var a,u;return assertNotInfinite(this.size),arguments.length<2?u=!0:a=o,this.__iterate((function(o,_,w){u?(u=!1,a=o):a=s.call(i,a,o,_,w)})),a},reduceRight:function(s,o,i){var a=this.toKeyedSeq().reverse();return a.reduce.apply(a,arguments)},reverse:function(){return reify(this,reverseFactory(this,!0))},slice:function(s,o){return reify(this,sliceFactory(this,s,o,!0))},some:function(s,o){return!this.every(not(s),o)},sort:function(s){return reify(this,sortFactory(this,s))},values:function(){return this.__iterator(U)},butLast:function(){return this.slice(0,-1)},isEmpty:function(){return void 0!==this.size?0===this.size:!this.some((function(){return!0}))},count:function(s,o){return ensureSize(s?this.toSeq().filter(s,o):this)},countBy:function(s,o){return countByFactory(this,s,o)},equals:function(s){return deepEqual(this,s)},entrySeq:function(){var s=this;if(s._cache)return new ArraySeq(s._cache);var o=s.toSeq().map(entryMapper).toIndexedSeq();return o.fromEntrySeq=function(){return s.toSeq()},o},filterNot:function(s,o){return this.filter(not(s),o)},findEntry:function(s,o,i){var a=i;return this.__iterate((function(i,u,_){if(s.call(o,i,u,_))return a=[u,i],!1})),a},findKey:function(s,o){var i=this.findEntry(s,o);return i&&i[0]},findLast:function(s,o,i){return this.toKeyedSeq().reverse().find(s,o,i)},findLastEntry:function(s,o,i){return this.toKeyedSeq().reverse().findEntry(s,o,i)},findLastKey:function(s,o){return this.toKeyedSeq().reverse().findKey(s,o)},first:function(){return this.find(returnTrue)},flatMap:function(s,o){return reify(this,flatMapFactory(this,s,o))},flatten:function(s){return reify(this,flattenFactory(this,s,!0))},fromEntrySeq:function(){return new FromEntriesSequence(this)},get:function(s,o){return this.find((function(o,i){return is(i,s)}),void 0,o)},getIn:function(s,o){for(var i,a=this,u=forceIterator(s);!(i=u.next()).done;){var _=i.value;if((a=a&&a.get?a.get(_,j):j)===j)return o}return a},groupBy:function(s,o){return groupByFactory(this,s,o)},has:function(s){return this.get(s,j)!==j},hasIn:function(s){return this.getIn(s,j)!==j},isSubset:function(s){return s="function"==typeof s.includes?s:Iterable(s),this.every((function(o){return s.includes(o)}))},isSuperset:function(s){return(s="function"==typeof s.isSubset?s:Iterable(s)).isSubset(this)},keyOf:function(s){return this.findKey((function(o){return is(o,s)}))},keySeq:function(){return this.toSeq().map(keyMapper).toIndexedSeq()},last:function(){return this.toSeq().reverse().first()},lastKeyOf:function(s){return this.toKeyedSeq().reverse().keyOf(s)},max:function(s){return maxFactory(this,s)},maxBy:function(s,o){return maxFactory(this,o,s)},min:function(s){return maxFactory(this,s?neg(s):defaultNegComparator)},minBy:function(s,o){return maxFactory(this,o?neg(o):defaultNegComparator,s)},rest:function(){return this.slice(1)},skip:function(s){return this.slice(Math.max(0,s))},skipLast:function(s){return reify(this,this.toSeq().reverse().skip(s).reverse())},skipWhile:function(s,o){return reify(this,skipWhileFactory(this,s,o,!0))},skipUntil:function(s,o){return this.skipWhile(not(s),o)},sortBy:function(s,o){return reify(this,sortFactory(this,o,s))},take:function(s){return this.slice(0,Math.max(0,s))},takeLast:function(s){return reify(this,this.toSeq().reverse().take(s).reverse())},takeWhile:function(s,o){return reify(this,takeWhileFactory(this,s,o))},takeUntil:function(s,o){return this.takeWhile(not(s),o)},valueSeq:function(){return this.toIndexedSeq()},hashCode:function(){return this.__hash||(this.__hash=hashIterable(this))}});var ut=Iterable.prototype;ut[o]=!0,ut[Z]=ut.values,ut.__toJS=ut.toArray,ut.__toStringMapper=quoteString,ut.inspect=ut.toSource=function(){return this.toString()},ut.chain=ut.flatMap,ut.contains=ut.includes,mixin(KeyedIterable,{flip:function(){return reify(this,flipFactory(this))},mapEntries:function(s,o){var i=this,a=0;return reify(this,this.toSeq().map((function(u,_){return s.call(o,[_,u],a++,i)})).fromEntrySeq())},mapKeys:function(s,o){var i=this;return reify(this,this.toSeq().flip().map((function(a,u){return s.call(o,a,u,i)})).flip())}});var pt=KeyedIterable.prototype;function keyMapper(s,o){return o}function entryMapper(s,o){return[o,s]}function not(s){return function(){return!s.apply(this,arguments)}}function neg(s){return function(){return-s.apply(this,arguments)}}function quoteString(s){return"string"==typeof s?JSON.stringify(s):String(s)}function defaultZipper(){return arrCopy(arguments)}function defaultNegComparator(s,o){return so?-1:0}function hashIterable(s){if(s.size===1/0)return 0;var o=isOrdered(s),i=isKeyed(s),a=o?1:0;return murmurHashOfSize(s.__iterate(i?o?function(s,o){a=31*a+hashMerge(hash(s),hash(o))|0}:function(s,o){a=a+hashMerge(hash(s),hash(o))|0}:o?function(s){a=31*a+hash(s)|0}:function(s){a=a+hash(s)|0}),a)}function murmurHashOfSize(s,o){return o=le(o,3432918353),o=le(o<<15|o>>>-15,461845907),o=le(o<<13|o>>>-13,5),o=le((o=o+3864292196^s)^o>>>16,2246822507),o=smi((o=le(o^o>>>13,3266489909))^o>>>16)}function hashMerge(s,o){return s^o+2654435769+(s<<6)+(s>>2)}return pt[i]=!0,pt[Z]=ut.entries,pt.__toJS=ut.toObject,pt.__toStringMapper=function(s,o){return JSON.stringify(o)+": "+quoteString(s)},mixin(IndexedIterable,{toKeyedSeq:function(){return new ToKeyedSequence(this,!1)},filter:function(s,o){return reify(this,filterFactory(this,s,o,!1))},findIndex:function(s,o){var i=this.findEntry(s,o);return i?i[0]:-1},indexOf:function(s){var o=this.keyOf(s);return void 0===o?-1:o},lastIndexOf:function(s){var o=this.lastKeyOf(s);return void 0===o?-1:o},reverse:function(){return reify(this,reverseFactory(this,!1))},slice:function(s,o){return reify(this,sliceFactory(this,s,o,!1))},splice:function(s,o){var i=arguments.length;if(o=Math.max(0|o,0),0===i||2===i&&!o)return this;s=resolveBegin(s,s<0?this.count():this.size);var a=this.slice(0,s);return reify(this,1===i?a:a.concat(arrCopy(arguments,2),this.slice(s+o)))},findLastIndex:function(s,o){var i=this.findLastEntry(s,o);return i?i[0]:-1},first:function(){return this.get(0)},flatten:function(s){return reify(this,flattenFactory(this,s,!1))},get:function(s,o){return(s=wrapIndex(this,s))<0||this.size===1/0||void 0!==this.size&&s>this.size?o:this.find((function(o,i){return i===s}),void 0,o)},has:function(s){return(s=wrapIndex(this,s))>=0&&(void 0!==this.size?this.size===1/0||s=o||i<0||B&&s-j>=_}function timerExpired(){var s=now();if(shouldInvoke(s))return trailingEdge(s);x=setTimeout(timerExpired,function remainingWait(s){var i=o-(s-C);return B?U(i,_-(s-j)):i}(s))}function trailingEdge(s){return x=void 0,V&&a?invokeFunc(s):(a=u=void 0,w)}function debounced(){var s=now(),i=shouldInvoke(s);if(a=arguments,u=this,C=s,i){if(void 0===x)return function leadingEdge(s){return j=s,x=setTimeout(timerExpired,o),L?invokeFunc(s):w}(C);if(B)return x=setTimeout(timerExpired,o),invokeFunc(C)}return void 0===x&&(x=setTimeout(timerExpired,o)),w}return o=toNumber(o)||0,isObject(i)&&(L=!!i.leading,_=(B="maxWait"in i)?$(toNumber(i.maxWait)||0,o):_,V="trailing"in i?!!i.trailing:V),debounced.cancel=function cancel(){void 0!==x&&clearTimeout(x),j=0,a=C=u=x=void 0},debounced.flush=function flush(){return void 0===x?w:trailingEdge(now())},debounced}},55580(s,o,i){var a=i(56110)(i(9325),"DataView");s.exports=a},21549(s,o,i){var a=i(22032),u=i(63862),_=i(66721),w=i(12749),x=i(35749);function Hash(s){var o=-1,i=null==s?0:s.length;for(this.clear();++o-1}},70695(s,o,i){var a=i(78096),u=i(72428),_=i(56449),w=i(3656),x=i(30361),C=i(37167),j=Object.prototype.hasOwnProperty;s.exports=function arrayLikeKeys(s,o){var i=_(s),L=!i&&u(s),B=!i&&!L&&w(s),$=!i&&!L&&!B&&C(s),U=i||L||B||$,V=U?a(s.length,String):[],z=V.length;for(var Y in s)!o&&!j.call(s,Y)||U&&("length"==Y||B&&("offset"==Y||"parent"==Y)||$&&("buffer"==Y||"byteLength"==Y||"byteOffset"==Y)||x(Y,z))||V.push(Y);return V}},34932(s){s.exports=function arrayMap(s,o){for(var i=-1,a=null==s?0:s.length,u=Array(a);++i0&&i(j)?o>1?baseFlatten(j,o-1,i,_,w):a(w,j):_||(w[w.length]=j)}return w}},86649(s,o,i){var a=i(83221)();s.exports=a},30641(s,o,i){var a=i(86649),u=i(95950);s.exports=function baseForOwn(s,o){return s&&a(s,o,u)}},47422(s,o,i){var a=i(31769),u=i(77797);s.exports=function baseGet(s,o){for(var i=0,_=(o=a(o,s)).length;null!=s&&i<_;)s=s[u(o[i++])];return i&&i==_?s:void 0}},82199(s,o,i){var a=i(14528),u=i(56449);s.exports=function baseGetAllKeys(s,o,i){var _=o(s);return u(s)?_:a(_,i(s))}},72552(s,o,i){var a=i(51873),u=i(659),_=i(59350),w=a?a.toStringTag:void 0;s.exports=function baseGetTag(s){return null==s?void 0===s?"[object Undefined]":"[object Null]":w&&w in Object(s)?u(s):_(s)}},20426(s){var o=Object.prototype.hasOwnProperty;s.exports=function baseHas(s,i){return null!=s&&o.call(s,i)}},28077(s){s.exports=function baseHasIn(s,o){return null!=s&&o in Object(s)}},96131(s,o,i){var a=i(2523),u=i(85463),_=i(76959);s.exports=function baseIndexOf(s,o,i){return o==o?_(s,o,i):a(s,u,i)}},27534(s,o,i){var a=i(72552),u=i(40346);s.exports=function baseIsArguments(s){return u(s)&&"[object Arguments]"==a(s)}},60270(s,o,i){var a=i(87068),u=i(40346);s.exports=function baseIsEqual(s,o,i,_,w){return s===o||(null==s||null==o||!u(s)&&!u(o)?s!=s&&o!=o:a(s,o,i,_,baseIsEqual,w))}},87068(s,o,i){var a=i(37217),u=i(25911),_=i(21986),w=i(50689),x=i(5861),C=i(56449),j=i(3656),L=i(37167),B="[object Arguments]",$="[object Array]",U="[object Object]",V=Object.prototype.hasOwnProperty;s.exports=function baseIsEqualDeep(s,o,i,z,Y,Z){var ee=C(s),ie=C(o),ae=ee?$:x(s),ce=ie?$:x(o),le=(ae=ae==B?U:ae)==U,pe=(ce=ce==B?U:ce)==U,de=ae==ce;if(de&&j(s)){if(!j(o))return!1;ee=!0,le=!1}if(de&&!le)return Z||(Z=new a),ee||L(s)?u(s,o,i,z,Y,Z):_(s,o,ae,i,z,Y,Z);if(!(1&i)){var fe=le&&V.call(s,"__wrapped__"),ye=pe&&V.call(o,"__wrapped__");if(fe||ye){var be=fe?s.value():s,Se=ye?o.value():o;return Z||(Z=new a),Y(be,Se,i,z,Z)}}return!!de&&(Z||(Z=new a),w(s,o,i,z,Y,Z))}},29172(s,o,i){var a=i(5861),u=i(40346);s.exports=function baseIsMap(s){return u(s)&&"[object Map]"==a(s)}},41799(s,o,i){var a=i(37217),u=i(60270);s.exports=function baseIsMatch(s,o,i,_){var w=i.length,x=w,C=!_;if(null==s)return!x;for(s=Object(s);w--;){var j=i[w];if(C&&j[2]?j[1]!==s[j[0]]:!(j[0]in s))return!1}for(;++wu?0:u+o),(i=i>u?u:i)<0&&(i+=u),u=o>i?0:i-o>>>0,o>>>=0;for(var _=Array(u);++a=u?s:a(s,o,i)}},49653(s,o,i){var a=i(37828);s.exports=function cloneArrayBuffer(s){var o=new s.constructor(s.byteLength);return new a(o).set(new a(s)),o}},93290(s,o,i){s=i.nmd(s);var a=i(9325),u=o&&!o.nodeType&&o,_=u&&s&&!s.nodeType&&s,w=_&&_.exports===u?a.Buffer:void 0,x=w?w.allocUnsafe:void 0;s.exports=function cloneBuffer(s,o){if(o)return s.slice();var i=s.length,a=x?x(i):new s.constructor(i);return s.copy(a),a}},76169(s,o,i){var a=i(49653);s.exports=function cloneDataView(s,o){var i=o?a(s.buffer):s.buffer;return new s.constructor(i,s.byteOffset,s.byteLength)}},73201(s){var o=/\w*$/;s.exports=function cloneRegExp(s){var i=new s.constructor(s.source,o.exec(s));return i.lastIndex=s.lastIndex,i}},93736(s,o,i){var a=i(51873),u=a?a.prototype:void 0,_=u?u.valueOf:void 0;s.exports=function cloneSymbol(s){return _?Object(_.call(s)):{}}},71961(s,o,i){var a=i(49653);s.exports=function cloneTypedArray(s,o){var i=o?a(s.buffer):s.buffer;return new s.constructor(i,s.byteOffset,s.length)}},91596(s){var o=Math.max;s.exports=function composeArgs(s,i,a,u){for(var _=-1,w=s.length,x=a.length,C=-1,j=i.length,L=o(w-x,0),B=Array(j+L),$=!u;++C1?i[_-1]:void 0,x=_>2?i[2]:void 0;for(w=s.length>3&&"function"==typeof w?(_--,w):void 0,x&&u(i[0],i[1],x)&&(w=_<3?void 0:w,_=1),o=Object(o);++a<_;){var C=i[a];C&&s(o,C,a,w)}return o}))}},38329(s,o,i){var a=i(64894);s.exports=function createBaseEach(s,o){return function(i,u){if(null==i)return i;if(!a(i))return s(i,u);for(var _=i.length,w=o?_:-1,x=Object(i);(o?w--:++w<_)&&!1!==u(x[w],w,x););return i}}},83221(s){s.exports=function createBaseFor(s){return function(o,i,a){for(var u=-1,_=Object(o),w=a(o),x=w.length;x--;){var C=w[s?x:++u];if(!1===i(_[C],C,_))break}return o}}},11842(s,o,i){var a=i(82819),u=i(9325);s.exports=function createBind(s,o,i){var _=1&o,w=a(s);return function wrapper(){return(this&&this!==u&&this instanceof wrapper?w:s).apply(_?i:this,arguments)}}},12507(s,o,i){var a=i(28754),u=i(49698),_=i(63912),w=i(13222);s.exports=function createCaseFirst(s){return function(o){o=w(o);var i=u(o)?_(o):void 0,x=i?i[0]:o.charAt(0),C=i?a(i,1).join(""):o.slice(1);return x[s]()+C}}},45539(s,o,i){var a=i(40882),u=i(50828),_=i(66645),w=RegExp("['’]","g");s.exports=function createCompounder(s){return function(o){return a(_(u(o).replace(w,"")),s,"")}}},82819(s,o,i){var a=i(39344),u=i(23805);s.exports=function createCtor(s){return function(){var o=arguments;switch(o.length){case 0:return new s;case 1:return new s(o[0]);case 2:return new s(o[0],o[1]);case 3:return new s(o[0],o[1],o[2]);case 4:return new s(o[0],o[1],o[2],o[3]);case 5:return new s(o[0],o[1],o[2],o[3],o[4]);case 6:return new s(o[0],o[1],o[2],o[3],o[4],o[5]);case 7:return new s(o[0],o[1],o[2],o[3],o[4],o[5],o[6])}var i=a(s.prototype),_=s.apply(i,o);return u(_)?_:i}}},77078(s,o,i){var a=i(91033),u=i(82819),_=i(37471),w=i(18073),x=i(11287),C=i(36306),j=i(9325);s.exports=function createCurry(s,o,i){var L=u(s);return function wrapper(){for(var u=arguments.length,B=Array(u),$=u,U=x(wrapper);$--;)B[$]=arguments[$];var V=u<3&&B[0]!==U&&B[u-1]!==U?[]:C(B,U);return(u-=V.length)-1?x[C?o[j]:j]:void 0}}},37471(s,o,i){var a=i(91596),u=i(53320),_=i(58523),w=i(82819),x=i(18073),C=i(11287),j=i(68294),L=i(36306),B=i(9325);s.exports=function createHybrid(s,o,i,$,U,V,z,Y,Z,ee){var ie=128&o,ae=1&o,ce=2&o,le=24&o,pe=512&o,de=ce?void 0:w(s);return function wrapper(){for(var fe=arguments.length,ye=Array(fe),be=fe;be--;)ye[be]=arguments[be];if(le)var Se=C(wrapper),_e=_(ye,Se);if($&&(ye=a(ye,$,U,le)),V&&(ye=u(ye,V,z,le)),fe-=_e,le&&fe1&&ye.reverse(),ie&&ZL))return!1;var $=C.get(s),U=C.get(o);if($&&U)return $==o&&U==s;var V=-1,z=!0,Y=2&i?new a:void 0;for(C.set(s,o),C.set(o,s);++V1?"& ":"")+i[u],i=i.join(a>2?", ":" "),s.replace(o,"{\n/* [wrapped with "+i+"] */\n")}},45891(s,o,i){var a=i(51873),u=i(72428),_=i(56449),w=a?a.isConcatSpreadable:void 0;s.exports=function isFlattenable(s){return _(s)||u(s)||!!(w&&s&&s[w])}},30361(s){var o=/^(?:0|[1-9]\d*)$/;s.exports=function isIndex(s,i){var a=typeof s;return!!(i=null==i?9007199254740991:i)&&("number"==a||"symbol"!=a&&o.test(s))&&s>-1&&s%1==0&&s-1}},31175(s,o,i){var a=i(26025);s.exports=function listCacheSet(s,o){var i=this.__data__,u=a(i,s);return u<0?(++this.size,i.push([s,o])):i[u][1]=o,this}},63040(s,o,i){var a=i(21549),u=i(80079),_=i(68223);s.exports=function mapCacheClear(){this.size=0,this.__data__={hash:new a,map:new(_||u),string:new a}}},17670(s,o,i){var a=i(12651);s.exports=function mapCacheDelete(s){var o=a(this,s).delete(s);return this.size-=o?1:0,o}},90289(s,o,i){var a=i(12651);s.exports=function mapCacheGet(s){return a(this,s).get(s)}},4509(s,o,i){var a=i(12651);s.exports=function mapCacheHas(s){return a(this,s).has(s)}},72949(s,o,i){var a=i(12651);s.exports=function mapCacheSet(s,o){var i=a(this,s),u=i.size;return i.set(s,o),this.size+=i.size==u?0:1,this}},20317(s){s.exports=function mapToArray(s){var o=-1,i=Array(s.size);return s.forEach((function(s,a){i[++o]=[a,s]})),i}},67197(s){s.exports=function matchesStrictComparable(s,o){return function(i){return null!=i&&(i[s]===o&&(void 0!==o||s in Object(i)))}}},62224(s,o,i){var a=i(50104);s.exports=function memoizeCapped(s){var o=a(s,(function(s){return 500===i.size&&i.clear(),s})),i=o.cache;return o}},3209(s,o,i){var a=i(91596),u=i(53320),_=i(36306),w="__lodash_placeholder__",x=128,C=Math.min;s.exports=function mergeData(s,o){var i=s[1],j=o[1],L=i|j,B=L<131,$=j==x&&8==i||j==x&&256==i&&s[7].length<=o[8]||384==j&&o[7].length<=o[8]&&8==i;if(!B&&!$)return s;1&j&&(s[2]=o[2],L|=1&i?0:4);var U=o[3];if(U){var V=s[3];s[3]=V?a(V,U,o[4]):U,s[4]=V?_(s[3],w):o[4]}return(U=o[5])&&(V=s[5],s[5]=V?u(V,U,o[6]):U,s[6]=V?_(s[5],w):o[6]),(U=o[7])&&(s[7]=U),j&x&&(s[8]=null==s[8]?o[8]:C(s[8],o[8])),null==s[9]&&(s[9]=o[9]),s[0]=o[0],s[1]=L,s}},48152(s,o,i){var a=i(28303),u=a&&new a;s.exports=u},81042(s,o,i){var a=i(56110)(Object,"create");s.exports=a},3650(s,o,i){var a=i(74335)(Object.keys,Object);s.exports=a},90181(s){s.exports=function nativeKeysIn(s){var o=[];if(null!=s)for(var i in Object(s))o.push(i);return o}},86009(s,o,i){s=i.nmd(s);var a=i(34840),u=o&&!o.nodeType&&o,_=u&&s&&!s.nodeType&&s,w=_&&_.exports===u&&a.process,x=function(){try{var s=_&&_.require&&_.require("util").types;return s||w&&w.binding&&w.binding("util")}catch(s){}}();s.exports=x},59350(s){var o=Object.prototype.toString;s.exports=function objectToString(s){return o.call(s)}},74335(s){s.exports=function overArg(s,o){return function(i){return s(o(i))}}},56757(s,o,i){var a=i(91033),u=Math.max;s.exports=function overRest(s,o,i){return o=u(void 0===o?s.length-1:o,0),function(){for(var _=arguments,w=-1,x=u(_.length-o,0),C=Array(x);++w0){if(++i>=800)return arguments[0]}else i=0;return s.apply(void 0,arguments)}}},51420(s,o,i){var a=i(80079);s.exports=function stackClear(){this.__data__=new a,this.size=0}},90938(s){s.exports=function stackDelete(s){var o=this.__data__,i=o.delete(s);return this.size=o.size,i}},63605(s){s.exports=function stackGet(s){return this.__data__.get(s)}},29817(s){s.exports=function stackHas(s){return this.__data__.has(s)}},80945(s,o,i){var a=i(80079),u=i(68223),_=i(53661);s.exports=function stackSet(s,o){var i=this.__data__;if(i instanceof a){var w=i.__data__;if(!u||w.length<199)return w.push([s,o]),this.size=++i.size,this;i=this.__data__=new _(w)}return i.set(s,o),this.size=i.size,this}},76959(s){s.exports=function strictIndexOf(s,o,i){for(var a=i-1,u=s.length;++a=o||i<0||Y&&s-V>=L}function timerExpired(){var s=u();if(shouldInvoke(s))return trailingEdge(s);$=setTimeout(timerExpired,function remainingWait(s){var i=o-(s-U);return Y?x(i,L-(s-V)):i}(s))}function trailingEdge(s){return $=void 0,Z&&C?invokeFunc(s):(C=j=void 0,B)}function debounced(){var s=u(),i=shouldInvoke(s);if(C=arguments,j=this,U=s,i){if(void 0===$)return function leadingEdge(s){return V=s,$=setTimeout(timerExpired,o),z?invokeFunc(s):B}(U);if(Y)return clearTimeout($),$=setTimeout(timerExpired,o),invokeFunc(U)}return void 0===$&&($=setTimeout(timerExpired,o)),B}return o=_(o)||0,a(i)&&(z=!!i.leading,L=(Y="maxWait"in i)?w(_(i.maxWait)||0,o):L,Z="trailing"in i?!!i.trailing:Z),debounced.cancel=function cancel(){void 0!==$&&clearTimeout($),V=0,C=U=j=$=void 0},debounced.flush=function flush(){return void 0===$?B:trailingEdge(u())},debounced}},50828(s,o,i){var a=i(24647),u=i(13222),_=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,w=RegExp("[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]","g");s.exports=function deburr(s){return(s=u(s))&&s.replace(_,a).replace(w,"")}},75288(s){s.exports=function eq(s,o){return s===o||s!=s&&o!=o}},60680(s,o,i){var a=i(13222),u=/[\\^$.*+?()[\]{}|]/g,_=RegExp(u.source);s.exports=function escapeRegExp(s){return(s=a(s))&&_.test(s)?s.replace(u,"\\$&"):s}},7309(s,o,i){var a=i(62006)(i(24713));s.exports=a},24713(s,o,i){var a=i(2523),u=i(15389),_=i(61489),w=Math.max;s.exports=function findIndex(s,o,i){var x=null==s?0:s.length;if(!x)return-1;var C=null==i?0:_(i);return C<0&&(C=w(x+C,0)),a(s,u(o,3),C)}},35970(s,o,i){var a=i(83120);s.exports=function flatten(s){return(null==s?0:s.length)?a(s,1):[]}},73424(s,o,i){var a=i(16962),u=i(2874),_=Array.prototype.push;function baseAry(s,o){return 2==o?function(o,i){return s(o,i)}:function(o){return s(o)}}function cloneArray(s){for(var o=s?s.length:0,i=Array(o);o--;)i[o]=s[o];return i}function wrapImmutable(s,o){return function(){var i=arguments.length;if(i){for(var a=Array(i);i--;)a[i]=arguments[i];var u=a[0]=o.apply(void 0,a);return s.apply(void 0,a),u}}}s.exports=function baseConvert(s,o,i,w){var x="function"==typeof o,C=o===Object(o);if(C&&(w=i,i=o,o=void 0),null==i)throw new TypeError;w||(w={});var j=!("cap"in w)||w.cap,L=!("curry"in w)||w.curry,B=!("fixed"in w)||w.fixed,$=!("immutable"in w)||w.immutable,U=!("rearg"in w)||w.rearg,V=x?i:u,z="curry"in w&&w.curry,Y="fixed"in w&&w.fixed,Z="rearg"in w&&w.rearg,ee=x?i.runInContext():void 0,ie=x?i:{ary:s.ary,assign:s.assign,clone:s.clone,curry:s.curry,forEach:s.forEach,isArray:s.isArray,isError:s.isError,isFunction:s.isFunction,isWeakMap:s.isWeakMap,iteratee:s.iteratee,keys:s.keys,rearg:s.rearg,toInteger:s.toInteger,toPath:s.toPath},ae=ie.ary,ce=ie.assign,le=ie.clone,pe=ie.curry,de=ie.forEach,fe=ie.isArray,ye=ie.isError,be=ie.isFunction,Se=ie.isWeakMap,_e=ie.keys,we=ie.rearg,xe=ie.toInteger,Pe=ie.toPath,Te=_e(a.aryMethod),Re={castArray:function(s){return function(){var o=arguments[0];return fe(o)?s(cloneArray(o)):s.apply(void 0,arguments)}},iteratee:function(s){return function(){var o=arguments[1],i=s(arguments[0],o),a=i.length;return j&&"number"==typeof o?(o=o>2?o-2:1,a&&a<=o?i:baseAry(i,o)):i}},mixin:function(s){return function(o){var i=this;if(!be(i))return s(i,Object(o));var a=[];return de(_e(o),(function(s){be(o[s])&&a.push([s,i.prototype[s]])})),s(i,Object(o)),de(a,(function(s){var o=s[1];be(o)?i.prototype[s[0]]=o:delete i.prototype[s[0]]})),i}},nthArg:function(s){return function(o){var i=o<0?1:xe(o)+1;return pe(s(o),i)}},rearg:function(s){return function(o,i){var a=i?i.length:0;return pe(s(o,i),a)}},runInContext:function(o){return function(i){return baseConvert(s,o(i),w)}}};function castCap(s,o){if(j){var i=a.iterateeRearg[s];if(i)return function iterateeRearg(s,o){return overArg(s,(function(s){var i=o.length;return function baseArity(s,o){return 2==o?function(o,i){return s.apply(void 0,arguments)}:function(o){return s.apply(void 0,arguments)}}(we(baseAry(s,i),o),i)}))}(o,i);var u=!x&&a.iterateeAry[s];if(u)return function iterateeAry(s,o){return overArg(s,(function(s){return"function"==typeof s?baseAry(s,o):s}))}(o,u)}return o}function castFixed(s,o,i){if(B&&(Y||!a.skipFixed[s])){var u=a.methodSpread[s],w=u&&u.start;return void 0===w?ae(o,i):function flatSpread(s,o){return function(){for(var i=arguments.length,a=i-1,u=Array(i);i--;)u[i]=arguments[i];var w=u[o],x=u.slice(0,o);return w&&_.apply(x,w),o!=a&&_.apply(x,u.slice(o+1)),s.apply(this,x)}}(o,w)}return o}function castRearg(s,o,i){return U&&i>1&&(Z||!a.skipRearg[s])?we(o,a.methodRearg[s]||a.aryRearg[i]):o}function cloneByPath(s,o){for(var i=-1,a=(o=Pe(o)).length,u=a-1,_=le(Object(s)),w=_;null!=w&&++i1?pe(o,i):o}(0,u=castCap(_,u),s),!1}})),!u})),u||(u=w),u==o&&(u=z?pe(u,1):function(){return o.apply(this,arguments)}),u.convert=createConverter(_,o),u.placeholder=o.placeholder=i,u}if(!C)return wrap(o,i,V);var $e=i,qe=[];return de(Te,(function(s){de(a.aryMethod[s],(function(s){var o=$e[a.remap[s]||s];o&&qe.push([s,wrap(s,o,$e)])}))})),de(_e($e),(function(s){var o=$e[s];if("function"==typeof o){for(var i=qe.length;i--;)if(qe[i][0]==s)return;o.convert=createConverter(s,o),qe.push([s,o])}})),de(qe,(function(s){$e[s[0]]=s[1]})),$e.convert=function convertLib(s){return $e.runInContext.convert(s)(void 0)},$e.placeholder=$e,de(_e($e),(function(s){de(a.realToAlias[s]||[],(function(o){$e[o]=$e[s]}))})),$e}},16962(s,o){o.aliasToReal={each:"forEach",eachRight:"forEachRight",entries:"toPairs",entriesIn:"toPairsIn",extend:"assignIn",extendAll:"assignInAll",extendAllWith:"assignInAllWith",extendWith:"assignInWith",first:"head",conforms:"conformsTo",matches:"isMatch",property:"get",__:"placeholder",F:"stubFalse",T:"stubTrue",all:"every",allPass:"overEvery",always:"constant",any:"some",anyPass:"overSome",apply:"spread",assoc:"set",assocPath:"set",complement:"negate",compose:"flowRight",contains:"includes",dissoc:"unset",dissocPath:"unset",dropLast:"dropRight",dropLastWhile:"dropRightWhile",equals:"isEqual",identical:"eq",indexBy:"keyBy",init:"initial",invertObj:"invert",juxt:"over",omitAll:"omit",nAry:"ary",path:"get",pathEq:"matchesProperty",pathOr:"getOr",paths:"at",pickAll:"pick",pipe:"flow",pluck:"map",prop:"get",propEq:"matchesProperty",propOr:"getOr",props:"at",symmetricDifference:"xor",symmetricDifferenceBy:"xorBy",symmetricDifferenceWith:"xorWith",takeLast:"takeRight",takeLastWhile:"takeRightWhile",unapply:"rest",unnest:"flatten",useWith:"overArgs",where:"conformsTo",whereEq:"isMatch",zipObj:"zipObject"},o.aryMethod={1:["assignAll","assignInAll","attempt","castArray","ceil","create","curry","curryRight","defaultsAll","defaultsDeepAll","floor","flow","flowRight","fromPairs","invert","iteratee","memoize","method","mergeAll","methodOf","mixin","nthArg","over","overEvery","overSome","rest","reverse","round","runInContext","spread","template","trim","trimEnd","trimStart","uniqueId","words","zipAll"],2:["add","after","ary","assign","assignAllWith","assignIn","assignInAllWith","at","before","bind","bindAll","bindKey","chunk","cloneDeepWith","cloneWith","concat","conformsTo","countBy","curryN","curryRightN","debounce","defaults","defaultsDeep","defaultTo","delay","difference","divide","drop","dropRight","dropRightWhile","dropWhile","endsWith","eq","every","filter","find","findIndex","findKey","findLast","findLastIndex","findLastKey","flatMap","flatMapDeep","flattenDepth","forEach","forEachRight","forIn","forInRight","forOwn","forOwnRight","get","groupBy","gt","gte","has","hasIn","includes","indexOf","intersection","invertBy","invoke","invokeMap","isEqual","isMatch","join","keyBy","lastIndexOf","lt","lte","map","mapKeys","mapValues","matchesProperty","maxBy","meanBy","merge","mergeAllWith","minBy","multiply","nth","omit","omitBy","overArgs","pad","padEnd","padStart","parseInt","partial","partialRight","partition","pick","pickBy","propertyOf","pull","pullAll","pullAt","random","range","rangeRight","rearg","reject","remove","repeat","restFrom","result","sampleSize","some","sortBy","sortedIndex","sortedIndexOf","sortedLastIndex","sortedLastIndexOf","sortedUniqBy","split","spreadFrom","startsWith","subtract","sumBy","take","takeRight","takeRightWhile","takeWhile","tap","throttle","thru","times","trimChars","trimCharsEnd","trimCharsStart","truncate","union","uniqBy","uniqWith","unset","unzipWith","without","wrap","xor","zip","zipObject","zipObjectDeep"],3:["assignInWith","assignWith","clamp","differenceBy","differenceWith","findFrom","findIndexFrom","findLastFrom","findLastIndexFrom","getOr","includesFrom","indexOfFrom","inRange","intersectionBy","intersectionWith","invokeArgs","invokeArgsMap","isEqualWith","isMatchWith","flatMapDepth","lastIndexOfFrom","mergeWith","orderBy","padChars","padCharsEnd","padCharsStart","pullAllBy","pullAllWith","rangeStep","rangeStepRight","reduce","reduceRight","replace","set","slice","sortedIndexBy","sortedLastIndexBy","transform","unionBy","unionWith","update","xorBy","xorWith","zipWith"],4:["fill","setWith","updateWith"]},o.aryRearg={2:[1,0],3:[2,0,1],4:[3,2,0,1]},o.iterateeAry={dropRightWhile:1,dropWhile:1,every:1,filter:1,find:1,findFrom:1,findIndex:1,findIndexFrom:1,findKey:1,findLast:1,findLastFrom:1,findLastIndex:1,findLastIndexFrom:1,findLastKey:1,flatMap:1,flatMapDeep:1,flatMapDepth:1,forEach:1,forEachRight:1,forIn:1,forInRight:1,forOwn:1,forOwnRight:1,map:1,mapKeys:1,mapValues:1,partition:1,reduce:2,reduceRight:2,reject:1,remove:1,some:1,takeRightWhile:1,takeWhile:1,times:1,transform:2},o.iterateeRearg={mapKeys:[1],reduceRight:[1,0]},o.methodRearg={assignInAllWith:[1,0],assignInWith:[1,2,0],assignAllWith:[1,0],assignWith:[1,2,0],differenceBy:[1,2,0],differenceWith:[1,2,0],getOr:[2,1,0],intersectionBy:[1,2,0],intersectionWith:[1,2,0],isEqualWith:[1,2,0],isMatchWith:[2,1,0],mergeAllWith:[1,0],mergeWith:[1,2,0],padChars:[2,1,0],padCharsEnd:[2,1,0],padCharsStart:[2,1,0],pullAllBy:[2,1,0],pullAllWith:[2,1,0],rangeStep:[1,2,0],rangeStepRight:[1,2,0],setWith:[3,1,2,0],sortedIndexBy:[2,1,0],sortedLastIndexBy:[2,1,0],unionBy:[1,2,0],unionWith:[1,2,0],updateWith:[3,1,2,0],xorBy:[1,2,0],xorWith:[1,2,0],zipWith:[1,2,0]},o.methodSpread={assignAll:{start:0},assignAllWith:{start:0},assignInAll:{start:0},assignInAllWith:{start:0},defaultsAll:{start:0},defaultsDeepAll:{start:0},invokeArgs:{start:2},invokeArgsMap:{start:2},mergeAll:{start:0},mergeAllWith:{start:0},partial:{start:1},partialRight:{start:1},without:{start:1},zipAll:{start:0}},o.mutate={array:{fill:!0,pull:!0,pullAll:!0,pullAllBy:!0,pullAllWith:!0,pullAt:!0,remove:!0,reverse:!0},object:{assign:!0,assignAll:!0,assignAllWith:!0,assignIn:!0,assignInAll:!0,assignInAllWith:!0,assignInWith:!0,assignWith:!0,defaults:!0,defaultsAll:!0,defaultsDeep:!0,defaultsDeepAll:!0,merge:!0,mergeAll:!0,mergeAllWith:!0,mergeWith:!0},set:{set:!0,setWith:!0,unset:!0,update:!0,updateWith:!0}},o.realToAlias=function(){var s=Object.prototype.hasOwnProperty,i=o.aliasToReal,a={};for(var u in i){var _=i[u];s.call(a,_)?a[_].push(u):a[_]=[u]}return a}(),o.remap={assignAll:"assign",assignAllWith:"assignWith",assignInAll:"assignIn",assignInAllWith:"assignInWith",curryN:"curry",curryRightN:"curryRight",defaultsAll:"defaults",defaultsDeepAll:"defaultsDeep",findFrom:"find",findIndexFrom:"findIndex",findLastFrom:"findLast",findLastIndexFrom:"findLastIndex",getOr:"get",includesFrom:"includes",indexOfFrom:"indexOf",invokeArgs:"invoke",invokeArgsMap:"invokeMap",lastIndexOfFrom:"lastIndexOf",mergeAll:"merge",mergeAllWith:"mergeWith",padChars:"pad",padCharsEnd:"padEnd",padCharsStart:"padStart",propertyOf:"get",rangeStep:"range",rangeStepRight:"rangeRight",restFrom:"rest",spreadFrom:"spread",trimChars:"trim",trimCharsEnd:"trimEnd",trimCharsStart:"trimStart",zipAll:"zip"},o.skipFixed={castArray:!0,flow:!0,flowRight:!0,iteratee:!0,mixin:!0,rearg:!0,runInContext:!0},o.skipRearg={add:!0,assign:!0,assignIn:!0,bind:!0,bindKey:!0,concat:!0,difference:!0,divide:!0,eq:!0,gt:!0,gte:!0,isEqual:!0,lt:!0,lte:!0,matchesProperty:!0,merge:!0,multiply:!0,overArgs:!0,partial:!0,partialRight:!0,propertyOf:!0,random:!0,range:!0,rangeRight:!0,subtract:!0,zip:!0,zipObject:!0,zipObjectDeep:!0}},47934(s,o,i){s.exports={ary:i(64626),assign:i(74733),clone:i(32629),curry:i(49747),forEach:i(83729),isArray:i(56449),isError:i(23546),isFunction:i(1882),isWeakMap:i(47886),iteratee:i(33855),keys:i(88984),rearg:i(84195),toInteger:i(61489),toPath:i(42072)}},56367(s,o,i){s.exports=i(77731)},79920(s,o,i){var a=i(73424),u=i(47934);s.exports=function convert(s,o,i){return a(u,s,o,i)}},2874(s){s.exports={}},77731(s,o,i){var a=i(79920)("set",i(63560));a.placeholder=i(2874),s.exports=a},58156(s,o,i){var a=i(47422);s.exports=function get(s,o,i){var u=null==s?void 0:a(s,o);return void 0===u?i:u}},61448(s,o,i){var a=i(20426),u=i(49326);s.exports=function has(s,o){return null!=s&&u(s,o,a)}},80631(s,o,i){var a=i(28077),u=i(49326);s.exports=function hasIn(s,o){return null!=s&&u(s,o,a)}},83488(s){s.exports=function identity(s){return s}},72428(s,o,i){var a=i(27534),u=i(40346),_=Object.prototype,w=_.hasOwnProperty,x=_.propertyIsEnumerable,C=a(function(){return arguments}())?a:function(s){return u(s)&&w.call(s,"callee")&&!x.call(s,"callee")};s.exports=C},56449(s){var o=Array.isArray;s.exports=o},64894(s,o,i){var a=i(1882),u=i(30294);s.exports=function isArrayLike(s){return null!=s&&u(s.length)&&!a(s)}},83693(s,o,i){var a=i(64894),u=i(40346);s.exports=function isArrayLikeObject(s){return u(s)&&a(s)}},53812(s,o,i){var a=i(72552),u=i(40346);s.exports=function isBoolean(s){return!0===s||!1===s||u(s)&&"[object Boolean]"==a(s)}},3656(s,o,i){s=i.nmd(s);var a=i(9325),u=i(89935),_=o&&!o.nodeType&&o,w=_&&s&&!s.nodeType&&s,x=w&&w.exports===_?a.Buffer:void 0,C=(x?x.isBuffer:void 0)||u;s.exports=C},62193(s,o,i){var a=i(88984),u=i(5861),_=i(72428),w=i(56449),x=i(64894),C=i(3656),j=i(55527),L=i(37167),B=Object.prototype.hasOwnProperty;s.exports=function isEmpty(s){if(null==s)return!0;if(x(s)&&(w(s)||"string"==typeof s||"function"==typeof s.splice||C(s)||L(s)||_(s)))return!s.length;var o=u(s);if("[object Map]"==o||"[object Set]"==o)return!s.size;if(j(s))return!a(s).length;for(var i in s)if(B.call(s,i))return!1;return!0}},2404(s,o,i){var a=i(60270);s.exports=function isEqual(s,o){return a(s,o)}},23546(s,o,i){var a=i(72552),u=i(40346),_=i(11331);s.exports=function isError(s){if(!u(s))return!1;var o=a(s);return"[object Error]"==o||"[object DOMException]"==o||"string"==typeof s.message&&"string"==typeof s.name&&!_(s)}},1882(s,o,i){var a=i(72552),u=i(23805);s.exports=function isFunction(s){if(!u(s))return!1;var o=a(s);return"[object Function]"==o||"[object GeneratorFunction]"==o||"[object AsyncFunction]"==o||"[object Proxy]"==o}},30294(s){s.exports=function isLength(s){return"number"==typeof s&&s>-1&&s%1==0&&s<=9007199254740991}},87730(s,o,i){var a=i(29172),u=i(27301),_=i(86009),w=_&&_.isMap,x=w?u(w):a;s.exports=x},5187(s){s.exports=function isNull(s){return null===s}},98023(s,o,i){var a=i(72552),u=i(40346);s.exports=function isNumber(s){return"number"==typeof s||u(s)&&"[object Number]"==a(s)}},23805(s){s.exports=function isObject(s){var o=typeof s;return null!=s&&("object"==o||"function"==o)}},40346(s){s.exports=function isObjectLike(s){return null!=s&&"object"==typeof s}},11331(s,o,i){var a=i(72552),u=i(28879),_=i(40346),w=Function.prototype,x=Object.prototype,C=w.toString,j=x.hasOwnProperty,L=C.call(Object);s.exports=function isPlainObject(s){if(!_(s)||"[object Object]"!=a(s))return!1;var o=u(s);if(null===o)return!0;var i=j.call(o,"constructor")&&o.constructor;return"function"==typeof i&&i instanceof i&&C.call(i)==L}},38440(s,o,i){var a=i(16038),u=i(27301),_=i(86009),w=_&&_.isSet,x=w?u(w):a;s.exports=x},85015(s,o,i){var a=i(72552),u=i(56449),_=i(40346);s.exports=function isString(s){return"string"==typeof s||!u(s)&&_(s)&&"[object String]"==a(s)}},44394(s,o,i){var a=i(72552),u=i(40346);s.exports=function isSymbol(s){return"symbol"==typeof s||u(s)&&"[object Symbol]"==a(s)}},37167(s,o,i){var a=i(4901),u=i(27301),_=i(86009),w=_&&_.isTypedArray,x=w?u(w):a;s.exports=x},47886(s,o,i){var a=i(5861),u=i(40346);s.exports=function isWeakMap(s){return u(s)&&"[object WeakMap]"==a(s)}},33855(s,o,i){var a=i(9999),u=i(15389);s.exports=function iteratee(s){return u("function"==typeof s?s:a(s,1))}},95950(s,o,i){var a=i(70695),u=i(88984),_=i(64894);s.exports=function keys(s){return _(s)?a(s):u(s)}},37241(s,o,i){var a=i(70695),u=i(72903),_=i(64894);s.exports=function keysIn(s){return _(s)?a(s,!0):u(s)}},68090(s){s.exports=function last(s){var o=null==s?0:s.length;return o?s[o-1]:void 0}},50104(s,o,i){var a=i(53661);function memoize(s,o){if("function"!=typeof s||null!=o&&"function"!=typeof o)throw new TypeError("Expected a function");var memoized=function(){var i=arguments,a=o?o.apply(this,i):i[0],u=memoized.cache;if(u.has(a))return u.get(a);var _=s.apply(this,i);return memoized.cache=u.set(a,_)||u,_};return memoized.cache=new(memoize.Cache||a),memoized}memoize.Cache=a,s.exports=memoize},55364(s,o,i){var a=i(85250),u=i(20999)((function(s,o,i){a(s,o,i)}));s.exports=u},6048(s){s.exports=function negate(s){if("function"!=typeof s)throw new TypeError("Expected a function");return function(){var o=arguments;switch(o.length){case 0:return!s.call(this);case 1:return!s.call(this,o[0]);case 2:return!s.call(this,o[0],o[1]);case 3:return!s.call(this,o[0],o[1],o[2])}return!s.apply(this,o)}}},63950(s){s.exports=function noop(){}},10124(s,o,i){var a=i(9325);s.exports=function(){return a.Date.now()}},90179(s,o,i){var a=i(34932),u=i(9999),_=i(19931),w=i(31769),x=i(21791),C=i(53138),j=i(38816),L=i(83349),B=j((function(s,o){var i={};if(null==s)return i;var j=!1;o=a(o,(function(o){return o=w(o,s),j||(j=o.length>1),o})),x(s,L(s),i),j&&(i=u(i,7,C));for(var B=o.length;B--;)_(i,o[B]);return i}));s.exports=B},50583(s,o,i){var a=i(47237),u=i(17255),_=i(28586),w=i(77797);s.exports=function property(s){return _(s)?a(w(s)):u(s)}},84195(s,o,i){var a=i(66977),u=i(38816),_=u((function(s,o){return a(s,256,void 0,void 0,void 0,o)}));s.exports=_},40860(s,o,i){var a=i(40882),u=i(80909),_=i(15389),w=i(85558),x=i(56449);s.exports=function reduce(s,o,i){var C=x(s)?a:w,j=arguments.length<3;return C(s,_(o,4),i,j,u)}},63560(s,o,i){var a=i(73170);s.exports=function set(s,o,i){return null==s?s:a(s,o,i)}},42426(s,o,i){var a=i(14248),u=i(15389),_=i(90916),w=i(56449),x=i(36800);s.exports=function some(s,o,i){var C=w(s)?a:_;return i&&x(s,o,i)&&(o=void 0),C(s,u(o,3))}},63345(s){s.exports=function stubArray(){return[]}},89935(s){s.exports=function stubFalse(){return!1}},17400(s,o,i){var a=i(99374),u=1/0;s.exports=function toFinite(s){return s?(s=a(s))===u||s===-1/0?17976931348623157e292*(s<0?-1:1):s==s?s:0:0===s?s:0}},61489(s,o,i){var a=i(17400);s.exports=function toInteger(s){var o=a(s),i=o%1;return o==o?i?o-i:o:0}},80218(s,o,i){var a=i(13222);s.exports=function toLower(s){return a(s).toLowerCase()}},99374(s,o,i){var a=i(54128),u=i(23805),_=i(44394),w=/^[-+]0x[0-9a-f]+$/i,x=/^0b[01]+$/i,C=/^0o[0-7]+$/i,j=parseInt;s.exports=function toNumber(s){if("number"==typeof s)return s;if(_(s))return NaN;if(u(s)){var o="function"==typeof s.valueOf?s.valueOf():s;s=u(o)?o+"":o}if("string"!=typeof s)return 0===s?s:+s;s=a(s);var i=x.test(s);return i||C.test(s)?j(s.slice(2),i?2:8):w.test(s)?NaN:+s}},42072(s,o,i){var a=i(34932),u=i(23007),_=i(56449),w=i(44394),x=i(61802),C=i(77797),j=i(13222);s.exports=function toPath(s){return _(s)?a(s,C):w(s)?[s]:u(x(j(s)))}},69884(s,o,i){var a=i(21791),u=i(37241);s.exports=function toPlainObject(s){return a(s,u(s))}},13222(s,o,i){var a=i(77556);s.exports=function toString(s){return null==s?"":a(s)}},55808(s,o,i){var a=i(12507)("toUpperCase");s.exports=a},66645(s,o,i){var a=i(1733),u=i(45434),_=i(13222),w=i(22225);s.exports=function words(s,o,i){return s=_(s),void 0===(o=i?void 0:o)?u(s)?w(s):a(s):s.match(o)||[]}},53758(s,o,i){var a=i(30980),u=i(56017),_=i(94033),w=i(56449),x=i(40346),C=i(80257),j=Object.prototype.hasOwnProperty;function lodash(s){if(x(s)&&!w(s)&&!(s instanceof a)){if(s instanceof u)return s;if(j.call(s,"__wrapped__"))return C(s)}return new u(s)}lodash.prototype=_.prototype,lodash.prototype.constructor=lodash,s.exports=lodash},47248(s,o,i){var a=i(16547),u=i(51234);s.exports=function zipObject(s,o){return u(s||[],o||[],a)}},43768(s,o,i){"use strict";var a=i(45981),u=i(85587);o.highlight=highlight,o.highlightAuto=function highlightAuto(s,o){var i,w,x,C,j=o||{},L=j.subset||a.listLanguages(),B=j.prefix,$=L.length,U=-1;null==B&&(B=_);if("string"!=typeof s)throw u("Expected `string` for value, got `%s`",s);w={relevance:0,language:null,value:[]},i={relevance:0,language:null,value:[]};for(;++U<$;)C=L[U],a.getLanguage(C)&&((x=highlight(C,s,o)).language=C,x.relevance>w.relevance&&(w=x),x.relevance>i.relevance&&(w=i,i=x));w.language&&(i.secondBest=w);return i},o.registerLanguage=function registerLanguage(s,o){a.registerLanguage(s,o)},o.listLanguages=function listLanguages(){return a.listLanguages()},o.registerAlias=function registerAlias(s,o){var i,u=s;o&&((u={})[s]=o);for(i in u)a.registerAliases(u[i],{languageName:i})},Emitter.prototype.addText=function text(s){var o,i,a=this.stack;if(""===s)return;o=a[a.length-1],(i=o.children[o.children.length-1])&&"text"===i.type?i.value+=s:o.children.push({type:"text",value:s})},Emitter.prototype.addKeyword=function addKeyword(s,o){this.openNode(o),this.addText(s),this.closeNode()},Emitter.prototype.addSublanguage=function addSublanguage(s,o){var i=this.stack,a=i[i.length-1],u=s.rootNode.children,_=o?{type:"element",tagName:"span",properties:{className:[o]},children:u}:u;a.children=a.children.concat(_)},Emitter.prototype.openNode=function open(s){var o=this.stack,i=this.options.classPrefix+s,a=o[o.length-1],u={type:"element",tagName:"span",properties:{className:[i]},children:[]};a.children.push(u),o.push(u)},Emitter.prototype.closeNode=function close(){this.stack.pop()},Emitter.prototype.closeAllNodes=noop,Emitter.prototype.finalize=noop,Emitter.prototype.toHTML=function toHtmlNoop(){return""};var _="hljs-";function highlight(s,o,i){var w,x=a.configure({}),C=(i||{}).prefix;if("string"!=typeof s)throw u("Expected `string` for name, got `%s`",s);if(!a.getLanguage(s))throw u("Unknown language: `%s` is not registered",s);if("string"!=typeof o)throw u("Expected `string` for value, got `%s`",o);if(null==C&&(C=_),a.configure({__emitter:Emitter,classPrefix:C}),w=a.highlight(o,{language:s,ignoreIllegals:!0}),a.configure(x||{}),w.errorRaised)throw w.errorRaised;return{relevance:w.relevance,language:w.language,value:w.emitter.rootNode.children}}function Emitter(s){this.options=s,this.rootNode={children:[]},this.stack=[this.rootNode]}function noop(){}},71514(s){"use strict";s.exports=Math.abs},58968(s){"use strict";s.exports=Math.floor},94459(s){"use strict";s.exports=Number.isNaN||function isNaN(s){return s!=s}},6188(s){"use strict";s.exports=Math.max},68002(s){"use strict";s.exports=Math.min},75880(s){"use strict";s.exports=Math.pow},70414(s){"use strict";s.exports=Math.round},73093(s,o,i){"use strict";var a=i(94459);s.exports=function sign(s){return a(s)||0===s?s:s<0?-1:1}},92340(s,o,i){const a=i(6048);function coerceElementMatchingCallback(s){return"string"==typeof s?o=>o.element===s:s.constructor&&s.extend?o=>o instanceof s:s}class ArraySlice{constructor(s){this.elements=s||[]}toValue(){return this.elements.map((s=>s.toValue()))}map(s,o){return this.elements.map(s,o)}flatMap(s,o){return this.map(s,o).reduce(((s,o)=>s.concat(o)),[])}compactMap(s,o){const i=[];return this.forEach((a=>{const u=s.bind(o)(a);u&&i.push(u)})),i}filter(s,o){return s=coerceElementMatchingCallback(s),new ArraySlice(this.elements.filter(s,o))}reject(s,o){return s=coerceElementMatchingCallback(s),new ArraySlice(this.elements.filter(a(s),o))}find(s,o){return s=coerceElementMatchingCallback(s),this.elements.find(s,o)}forEach(s,o){this.elements.forEach(s,o)}reduce(s,o){return this.elements.reduce(s,o)}includes(s){return this.elements.some((o=>o.equals(s)))}shift(){return this.elements.shift()}unshift(s){this.elements.unshift(this.refract(s))}push(s){return this.elements.push(this.refract(s)),this}add(s){this.push(s)}get(s){return this.elements[s]}getValue(s){const o=this.elements[s];if(o)return o.toValue()}get length(){return this.elements.length}get isEmpty(){return 0===this.elements.length}get first(){return this.elements[0]}}"undefined"!=typeof Symbol&&(ArraySlice.prototype[Symbol.iterator]=function symbol(){return this.elements[Symbol.iterator]()}),s.exports=ArraySlice},55973(s){class KeyValuePair{constructor(s,o){this.key=s,this.value=o}clone(){const s=new KeyValuePair;return this.key&&(s.key=this.key.clone()),this.value&&(s.value=this.value.clone()),s}}s.exports=KeyValuePair},3110(s,o,i){const a=i(5187),u=i(85015),_=i(98023),w=i(53812),x=i(23805),C=i(85105),j=i(86804);class Namespace{constructor(s){this.elementMap={},this.elementDetection=[],this.Element=j.Element,this.KeyValuePair=j.KeyValuePair,s&&s.noDefault||this.useDefault(),this._attributeElementKeys=[],this._attributeElementArrayKeys=[]}use(s){return s.namespace&&s.namespace({base:this}),s.load&&s.load({base:this}),this}useDefault(){return this.register("null",j.NullElement).register("string",j.StringElement).register("number",j.NumberElement).register("boolean",j.BooleanElement).register("array",j.ArrayElement).register("object",j.ObjectElement).register("member",j.MemberElement).register("ref",j.RefElement).register("link",j.LinkElement),this.detect(a,j.NullElement,!1).detect(u,j.StringElement,!1).detect(_,j.NumberElement,!1).detect(w,j.BooleanElement,!1).detect(Array.isArray,j.ArrayElement,!1).detect(x,j.ObjectElement,!1),this}register(s,o){return this._elements=void 0,this.elementMap[s]=o,this}unregister(s){return this._elements=void 0,delete this.elementMap[s],this}detect(s,o,i){return void 0===i||i?this.elementDetection.unshift([s,o]):this.elementDetection.push([s,o]),this}toElement(s){if(s instanceof this.Element)return s;let o;for(let i=0;i{const o=s[0].toUpperCase()+s.substr(1);this._elements[o]=this.elementMap[s]}))),this._elements}get serialiser(){return new C(this)}}C.prototype.Namespace=Namespace,s.exports=Namespace},10866(s,o,i){const a=i(6048),u=i(92340);class ObjectSlice extends u{map(s,o){return this.elements.map((i=>s.bind(o)(i.value,i.key,i)))}filter(s,o){return new ObjectSlice(this.elements.filter((i=>s.bind(o)(i.value,i.key,i))))}reject(s,o){return this.filter(a(s.bind(o)))}forEach(s,o){return this.elements.forEach(((i,a)=>{s.bind(o)(i.value,i.key,i,a)}))}keys(){return this.map(((s,o)=>o.toValue()))}values(){return this.map((s=>s.toValue()))}}s.exports=ObjectSlice},86804(s,o,i){const a=i(10316),u=i(41067),_=i(71167),w=i(40239),x=i(12242),C=i(6233),j=i(87726),L=i(61045),B=i(86303),$=i(14540),U=i(92340),V=i(10866),z=i(55973);function refract(s){if(s instanceof a)return s;if("string"==typeof s)return new _(s);if("number"==typeof s)return new w(s);if("boolean"==typeof s)return new x(s);if(null===s)return new u;if(Array.isArray(s))return new C(s.map(refract));if("object"==typeof s){return new L(s)}return s}a.prototype.ObjectElement=L,a.prototype.RefElement=$,a.prototype.MemberElement=j,a.prototype.refract=refract,U.prototype.refract=refract,s.exports={Element:a,NullElement:u,StringElement:_,NumberElement:w,BooleanElement:x,ArrayElement:C,MemberElement:j,ObjectElement:L,LinkElement:B,RefElement:$,refract,ArraySlice:U,ObjectSlice:V,KeyValuePair:z}},86303(s,o,i){const a=i(10316);s.exports=class LinkElement extends a{constructor(s,o,i){super(s||[],o,i),this.element="link"}get relation(){return this.attributes.get("relation")}set relation(s){this.attributes.set("relation",s)}get href(){return this.attributes.get("href")}set href(s){this.attributes.set("href",s)}}},14540(s,o,i){const a=i(10316);s.exports=class RefElement extends a{constructor(s,o,i){super(s||[],o,i),this.element="ref",this.path||(this.path="element")}get path(){return this.attributes.get("path")}set path(s){this.attributes.set("path",s)}}},34035(s,o,i){const a=i(3110),u=i(86804);o.g$=a,o.KeyValuePair=i(55973),o.G6=u.ArraySlice,o.ot=u.ObjectSlice,o.Hg=u.Element,o.Om=u.StringElement,o.kT=u.NumberElement,o.bd=u.BooleanElement,o.Os=u.NullElement,o.wE=u.ArrayElement,o.Sh=u.ObjectElement,o.Pr=u.MemberElement,o.sI=u.RefElement,o.Ft=u.LinkElement,o.e=u.refract,i(85105),i(75147)},6233(s,o,i){const a=i(6048),u=i(10316),_=i(92340);class ArrayElement extends u{constructor(s,o,i){super(s||[],o,i),this.element="array"}primitive(){return"array"}get(s){return this.content[s]}getValue(s){const o=this.get(s);if(o)return o.toValue()}getIndex(s){return this.content[s]}set(s,o){return this.content[s]=this.refract(o),this}remove(s){const o=this.content.splice(s,1);return o.length?o[0]:null}map(s,o){return this.content.map(s,o)}flatMap(s,o){return this.map(s,o).reduce(((s,o)=>s.concat(o)),[])}compactMap(s,o){const i=[];return this.forEach((a=>{const u=s.bind(o)(a);u&&i.push(u)})),i}filter(s,o){return new _(this.content.filter(s,o))}reject(s,o){return this.filter(a(s),o)}reduce(s,o){let i,a;void 0!==o?(i=0,a=this.refract(o)):(i=1,a="object"===this.primitive()?this.first.value:this.first);for(let o=i;o{s.bind(o)(i,this.refract(a))}))}shift(){return this.content.shift()}unshift(s){this.content.unshift(this.refract(s))}push(s){return this.content.push(this.refract(s)),this}add(s){this.push(s)}findElements(s,o){const i=o||{},a=!!i.recursive,u=void 0===i.results?[]:i.results;return this.forEach(((o,i,_)=>{a&&void 0!==o.findElements&&o.findElements(s,{results:u,recursive:a}),s(o,i,_)&&u.push(o)})),u}find(s){return new _(this.findElements(s,{recursive:!0}))}findByElement(s){return this.find((o=>o.element===s))}findByClass(s){return this.find((o=>o.classes.includes(s)))}getById(s){return this.find((o=>o.id.toValue()===s)).first}includes(s){return this.content.some((o=>o.equals(s)))}contains(s){return this.includes(s)}empty(){return new this.constructor([])}"fantasy-land/empty"(){return this.empty()}concat(s){return new this.constructor(this.content.concat(s.content))}"fantasy-land/concat"(s){return this.concat(s)}"fantasy-land/map"(s){return new this.constructor(this.map(s))}"fantasy-land/chain"(s){return this.map((o=>s(o)),this).reduce(((s,o)=>s.concat(o)),this.empty())}"fantasy-land/filter"(s){return new this.constructor(this.content.filter(s))}"fantasy-land/reduce"(s,o){return this.content.reduce(s,o)}get length(){return this.content.length}get isEmpty(){return 0===this.content.length}get first(){return this.getIndex(0)}get second(){return this.getIndex(1)}get last(){return this.getIndex(this.length-1)}}ArrayElement.empty=function empty(){return new this},ArrayElement["fantasy-land/empty"]=ArrayElement.empty,"undefined"!=typeof Symbol&&(ArrayElement.prototype[Symbol.iterator]=function symbol(){return this.content[Symbol.iterator]()}),s.exports=ArrayElement},12242(s,o,i){const a=i(10316);s.exports=class BooleanElement extends a{constructor(s,o,i){super(s,o,i),this.element="boolean"}primitive(){return"boolean"}}},10316(s,o,i){const a=i(2404),u=i(55973),_=i(92340);class Element{constructor(s,o,i){o&&(this.meta=o),i&&(this.attributes=i),this.content=s}freeze(){Object.isFrozen(this)||(this._meta&&(this.meta.parent=this,this.meta.freeze()),this._attributes&&(this.attributes.parent=this,this.attributes.freeze()),this.children.forEach((s=>{s.parent=this,s.freeze()}),this),this.content&&Array.isArray(this.content)&&Object.freeze(this.content),Object.freeze(this))}primitive(){}clone(){const s=new this.constructor;return s.element=this.element,this.meta.length&&(s._meta=this.meta.clone()),this.attributes.length&&(s._attributes=this.attributes.clone()),this.content?this.content.clone?s.content=this.content.clone():Array.isArray(this.content)?s.content=this.content.map((s=>s.clone())):s.content=this.content:s.content=this.content,s}toValue(){return this.content instanceof Element?this.content.toValue():this.content instanceof u?{key:this.content.key.toValue(),value:this.content.value?this.content.value.toValue():void 0}:this.content&&this.content.map?this.content.map((s=>s.toValue()),this):this.content}toRef(s){if(""===this.id.toValue())throw Error("Cannot create reference to an element that does not contain an ID");const o=new this.RefElement(this.id.toValue());return s&&(o.path=s),o}findRecursive(...s){if(arguments.length>1&&!this.isFrozen)throw new Error("Cannot find recursive with multiple element names without first freezing the element. Call `element.freeze()`");const o=s.pop();let i=new _;const append=(s,o)=>(s.push(o),s),checkElement=(s,i)=>{i.element===o&&s.push(i);const a=i.findRecursive(o);return a&&a.reduce(append,s),i.content instanceof u&&(i.content.key&&checkElement(s,i.content.key),i.content.value&&checkElement(s,i.content.value)),s};return this.content&&(this.content.element&&checkElement(i,this.content),Array.isArray(this.content)&&this.content.reduce(checkElement,i)),s.isEmpty||(i=i.filter((o=>{let i=o.parents.map((s=>s.element));for(const o in s){const a=s[o],u=i.indexOf(a);if(-1===u)return!1;i=i.splice(0,u)}return!0}))),i}set(s){return this.content=s,this}equals(s){return a(this.toValue(),s)}getMetaProperty(s,o){if(!this.meta.hasKey(s)){if(this.isFrozen){const s=this.refract(o);return s.freeze(),s}this.meta.set(s,o)}return this.meta.get(s)}setMetaProperty(s,o){this.meta.set(s,o)}get element(){return this._storedElement||"element"}set element(s){this._storedElement=s}get content(){return this._content}set content(s){if(s instanceof Element)this._content=s;else if(s instanceof _)this.content=s.elements;else if("string"==typeof s||"number"==typeof s||"boolean"==typeof s||"null"===s||null==s)this._content=s;else if(s instanceof u)this._content=s;else if(Array.isArray(s))this._content=s.map(this.refract);else{if("object"!=typeof s)throw new Error("Cannot set content to given value");this._content=Object.keys(s).map((o=>new this.MemberElement(o,s[o])))}}get meta(){if(!this._meta){if(this.isFrozen){const s=new this.ObjectElement;return s.freeze(),s}this._meta=new this.ObjectElement}return this._meta}set meta(s){s instanceof this.ObjectElement?this._meta=s:this.meta.set(s||{})}get attributes(){if(!this._attributes){if(this.isFrozen){const s=new this.ObjectElement;return s.freeze(),s}this._attributes=new this.ObjectElement}return this._attributes}set attributes(s){s instanceof this.ObjectElement?this._attributes=s:this.attributes.set(s||{})}get id(){return this.getMetaProperty("id","")}set id(s){this.setMetaProperty("id",s)}get classes(){return this.getMetaProperty("classes",[])}set classes(s){this.setMetaProperty("classes",s)}get title(){return this.getMetaProperty("title","")}set title(s){this.setMetaProperty("title",s)}get description(){return this.getMetaProperty("description","")}set description(s){this.setMetaProperty("description",s)}get links(){return this.getMetaProperty("links",[])}set links(s){this.setMetaProperty("links",s)}get isFrozen(){return Object.isFrozen(this)}get parents(){let{parent:s}=this;const o=new _;for(;s;)o.push(s),s=s.parent;return o}get children(){if(Array.isArray(this.content))return new _(this.content);if(this.content instanceof u){const s=new _([this.content.key]);return this.content.value&&s.push(this.content.value),s}return this.content instanceof Element?new _([this.content]):new _}get recursiveChildren(){const s=new _;return this.children.forEach((o=>{s.push(o),o.recursiveChildren.forEach((o=>{s.push(o)}))})),s}}s.exports=Element},87726(s,o,i){const a=i(55973),u=i(10316);s.exports=class MemberElement extends u{constructor(s,o,i,u){super(new a,i,u),this.element="member",this.key=s,this.value=o}get key(){return this.content.key}set key(s){this.content.key=this.refract(s)}get value(){return this.content.value}set value(s){this.content.value=this.refract(s)}}},41067(s,o,i){const a=i(10316);s.exports=class NullElement extends a{constructor(s,o,i){super(s||null,o,i),this.element="null"}primitive(){return"null"}set(){return new Error("Cannot set the value of null")}}},40239(s,o,i){const a=i(10316);s.exports=class NumberElement extends a{constructor(s,o,i){super(s,o,i),this.element="number"}primitive(){return"number"}}},61045(s,o,i){const a=i(6048),u=i(23805),_=i(6233),w=i(87726),x=i(10866);s.exports=class ObjectElement extends _{constructor(s,o,i){super(s||[],o,i),this.element="object"}primitive(){return"object"}toValue(){return this.content.reduce(((s,o)=>(s[o.key.toValue()]=o.value?o.value.toValue():void 0,s)),{})}get(s){const o=this.getMember(s);if(o)return o.value}getMember(s){if(void 0!==s)return this.content.find((o=>o.key.toValue()===s))}remove(s){let o=null;return this.content=this.content.filter((i=>i.key.toValue()!==s||(o=i,!1))),o}getKey(s){const o=this.getMember(s);if(o)return o.key}set(s,o){if(u(s))return Object.keys(s).forEach((o=>{this.set(o,s[o])})),this;const i=s,a=this.getMember(i);return a?a.value=o:this.content.push(new w(i,o)),this}keys(){return this.content.map((s=>s.key.toValue()))}values(){return this.content.map((s=>s.value.toValue()))}hasKey(s){return this.content.some((o=>o.key.equals(s)))}items(){return this.content.map((s=>[s.key.toValue(),s.value.toValue()]))}map(s,o){return this.content.map((i=>s.bind(o)(i.value,i.key,i)))}compactMap(s,o){const i=[];return this.forEach(((a,u,_)=>{const w=s.bind(o)(a,u,_);w&&i.push(w)})),i}filter(s,o){return new x(this.content).filter(s,o)}reject(s,o){return this.filter(a(s),o)}forEach(s,o){return this.content.forEach((i=>s.bind(o)(i.value,i.key,i)))}}},71167(s,o,i){const a=i(10316);s.exports=class StringElement extends a{constructor(s,o,i){super(s,o,i),this.element="string"}primitive(){return"string"}get length(){return this.content.length}}},75147(s,o,i){const a=i(85105);s.exports=class JSON06Serialiser extends a{serialise(s){if(!(s instanceof this.namespace.elements.Element))throw new TypeError(`Given element \`${s}\` is not an Element instance`);let o;s._attributes&&s.attributes.get("variable")&&(o=s.attributes.get("variable"));const i={element:s.element};s._meta&&s._meta.length>0&&(i.meta=this.serialiseObject(s.meta));const a="enum"===s.element||-1!==s.attributes.keys().indexOf("enumerations");if(a){const o=this.enumSerialiseAttributes(s);o&&(i.attributes=o)}else if(s._attributes&&s._attributes.length>0){let{attributes:a}=s;a.get("metadata")&&(a=a.clone(),a.set("meta",a.get("metadata")),a.remove("metadata")),"member"===s.element&&o&&(a=a.clone(),a.remove("variable")),a.length>0&&(i.attributes=this.serialiseObject(a))}if(a)i.content=this.enumSerialiseContent(s,i);else if(this[`${s.element}SerialiseContent`])i.content=this[`${s.element}SerialiseContent`](s,i);else if(void 0!==s.content){let a;o&&s.content.key?(a=s.content.clone(),a.key.attributes.set("variable",o),a=this.serialiseContent(a)):a=this.serialiseContent(s.content),this.shouldSerialiseContent(s,a)&&(i.content=a)}else this.shouldSerialiseContent(s,s.content)&&s instanceof this.namespace.elements.Array&&(i.content=[]);return i}shouldSerialiseContent(s,o){return"parseResult"===s.element||"httpRequest"===s.element||"httpResponse"===s.element||"category"===s.element||"link"===s.element||void 0!==o&&(!Array.isArray(o)||0!==o.length)}refSerialiseContent(s,o){return delete o.attributes,{href:s.toValue(),path:s.path.toValue()}}sourceMapSerialiseContent(s){return s.toValue()}dataStructureSerialiseContent(s){return[this.serialiseContent(s.content)]}enumSerialiseAttributes(s){const o=s.attributes.clone(),i=o.remove("enumerations")||new this.namespace.elements.Array([]),a=o.get("default");let u=o.get("samples")||new this.namespace.elements.Array([]);if(a&&a.content&&(a.content.attributes&&a.content.attributes.remove("typeAttributes"),o.set("default",new this.namespace.elements.Array([a.content]))),u.forEach((s=>{s.content&&s.content.element&&s.content.attributes.remove("typeAttributes")})),s.content&&0!==i.length&&u.unshift(s.content),u=u.map((s=>s instanceof this.namespace.elements.Array?[s]:new this.namespace.elements.Array([s.content]))),u.length&&o.set("samples",u),o.length>0)return this.serialiseObject(o)}enumSerialiseContent(s){if(s._attributes){const o=s.attributes.get("enumerations");if(o&&o.length>0)return o.content.map((s=>{const o=s.clone();return o.attributes.remove("typeAttributes"),this.serialise(o)}))}if(s.content){const o=s.content.clone();return o.attributes.remove("typeAttributes"),[this.serialise(o)]}return[]}deserialise(s){if("string"==typeof s)return new this.namespace.elements.String(s);if("number"==typeof s)return new this.namespace.elements.Number(s);if("boolean"==typeof s)return new this.namespace.elements.Boolean(s);if(null===s)return new this.namespace.elements.Null;if(Array.isArray(s))return new this.namespace.elements.Array(s.map(this.deserialise,this));const o=this.namespace.getElementClass(s.element),i=new o;i.element!==s.element&&(i.element=s.element),s.meta&&this.deserialiseObject(s.meta,i.meta),s.attributes&&this.deserialiseObject(s.attributes,i.attributes);const a=this.deserialiseContent(s.content);if(void 0===a&&null!==i.content||(i.content=a),"enum"===i.element){i.content&&i.attributes.set("enumerations",i.content);let s=i.attributes.get("samples");if(i.attributes.remove("samples"),s){const a=s;s=new this.namespace.elements.Array,a.forEach((a=>{a.forEach((a=>{const u=new o(a);u.element=i.element,s.push(u)}))}));const u=s.shift();i.content=u?u.content:void 0,i.attributes.set("samples",s)}else i.content=void 0;let a=i.attributes.get("default");if(a&&a.length>0){a=a.get(0);const s=new o(a);s.element=i.element,i.attributes.set("default",s)}}else if("dataStructure"===i.element&&Array.isArray(i.content))[i.content]=i.content;else if("category"===i.element){const s=i.attributes.get("meta");s&&(i.attributes.set("metadata",s),i.attributes.remove("meta"))}else"member"===i.element&&i.key&&i.key._attributes&&i.key._attributes.getValue("variable")&&(i.attributes.set("variable",i.key.attributes.get("variable")),i.key.attributes.remove("variable"));return i}serialiseContent(s){if(s instanceof this.namespace.elements.Element)return this.serialise(s);if(s instanceof this.namespace.KeyValuePair){const o={key:this.serialise(s.key)};return s.value&&(o.value=this.serialise(s.value)),o}return s&&s.map?s.map(this.serialise,this):s}deserialiseContent(s){if(s){if(s.element)return this.deserialise(s);if(s.key){const o=new this.namespace.KeyValuePair(this.deserialise(s.key));return s.value&&(o.value=this.deserialise(s.value)),o}if(s.map)return s.map(this.deserialise,this)}return s}shouldRefract(s){return!!(s._attributes&&s.attributes.keys().length||s._meta&&s.meta.keys().length)||"enum"!==s.element&&(s.element!==s.primitive()||"member"===s.element)}convertKeyToRefract(s,o){return this.shouldRefract(o)?this.serialise(o):"enum"===o.element?this.serialiseEnum(o):"array"===o.element?o.map((o=>this.shouldRefract(o)||"default"===s?this.serialise(o):"array"===o.element||"object"===o.element||"enum"===o.element?o.children.map((s=>this.serialise(s))):o.toValue())):"object"===o.element?(o.content||[]).map(this.serialise,this):o.toValue()}serialiseEnum(s){return s.children.map((s=>this.serialise(s)))}serialiseObject(s){const o={};return s.forEach(((s,i)=>{if(s){const a=i.toValue();o[a]=this.convertKeyToRefract(a,s)}})),o}deserialiseObject(s,o){Object.keys(s).forEach((i=>{o.set(i,this.deserialise(s[i]))}))}}},85105(s){s.exports=class JSONSerialiser{constructor(s){this.namespace=s||new this.Namespace}serialise(s){if(!(s instanceof this.namespace.elements.Element))throw new TypeError(`Given element \`${s}\` is not an Element instance`);const o={element:s.element};s._meta&&s._meta.length>0&&(o.meta=this.serialiseObject(s.meta)),s._attributes&&s._attributes.length>0&&(o.attributes=this.serialiseObject(s.attributes));const i=this.serialiseContent(s.content);return void 0!==i&&(o.content=i),o}deserialise(s){if(!s.element)throw new Error("Given value is not an object containing an element name");const o=new(this.namespace.getElementClass(s.element));o.element!==s.element&&(o.element=s.element),s.meta&&this.deserialiseObject(s.meta,o.meta),s.attributes&&this.deserialiseObject(s.attributes,o.attributes);const i=this.deserialiseContent(s.content);return void 0===i&&null!==o.content||(o.content=i),o}serialiseContent(s){if(s instanceof this.namespace.elements.Element)return this.serialise(s);if(s instanceof this.namespace.KeyValuePair){const o={key:this.serialise(s.key)};return s.value&&(o.value=this.serialise(s.value)),o}if(s&&s.map){if(0===s.length)return;return s.map(this.serialise,this)}return s}deserialiseContent(s){if(s){if(s.element)return this.deserialise(s);if(s.key){const o=new this.namespace.KeyValuePair(this.deserialise(s.key));return s.value&&(o.value=this.deserialise(s.value)),o}if(s.map)return s.map(this.deserialise,this)}return s}serialiseObject(s){const o={};if(s.forEach(((s,i)=>{s&&(o[i.toValue()]=this.serialise(s))})),0!==Object.keys(o).length)return o}deserialiseObject(s,o){Object.keys(s).forEach((i=>{o.set(i,this.deserialise(s[i]))}))}}},76578(s){"use strict";s.exports=["Float16Array","Float32Array","Float64Array","Int8Array","Int16Array","Int32Array","Uint8Array","Uint8ClampedArray","Uint16Array","Uint32Array","BigInt64Array","BigUint64Array"]},65606(s){var o,i,a=s.exports={};function defaultSetTimout(){throw new Error("setTimeout has not been defined")}function defaultClearTimeout(){throw new Error("clearTimeout has not been defined")}function runTimeout(s){if(o===setTimeout)return setTimeout(s,0);if((o===defaultSetTimout||!o)&&setTimeout)return o=setTimeout,setTimeout(s,0);try{return o(s,0)}catch(i){try{return o.call(null,s,0)}catch(i){return o.call(this,s,0)}}}!function(){try{o="function"==typeof setTimeout?setTimeout:defaultSetTimout}catch(s){o=defaultSetTimout}try{i="function"==typeof clearTimeout?clearTimeout:defaultClearTimeout}catch(s){i=defaultClearTimeout}}();var u,_=[],w=!1,x=-1;function cleanUpNextTick(){w&&u&&(w=!1,u.length?_=u.concat(_):x=-1,_.length&&drainQueue())}function drainQueue(){if(!w){var s=runTimeout(cleanUpNextTick);w=!0;for(var o=_.length;o;){for(u=_,_=[];++x1)for(var i=1;i_)throw new RangeError("requested too many random bytes");var i=w.allocUnsafe(s);if(s>0)if(s>u)for(var C=0;C=0||(u[i]=s[i]);return u}(s,o);if(Object.getOwnPropertySymbols){var _=Object.getOwnPropertySymbols(s);for(a=0;a<_.length;a++)i=_[a],o.indexOf(i)>=0||Object.prototype.propertyIsEnumerable.call(s,i)&&(u[i]=s[i])}return u}function _defineProperties(s,o){for(var i=0;i=0||(u[i]=s[i]);return u}(s,o);if(Object.getOwnPropertySymbols){var _=Object.getOwnPropertySymbols(s);for(a=0;a<_.length;a++)i=_[a],o.indexOf(i)>=0||Object.prototype.propertyIsEnumerable.call(s,i)&&(u[i]=s[i])}return u}function ownKeys(s,o){var i=Object.keys(s);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(s);o&&(a=a.filter((function(o){return Object.getOwnPropertyDescriptor(s,o).enumerable}))),i.push.apply(i,a)}return i}function _objectSpread(s){for(var o=1;o=a?i.notify(s):o.length>u.length&&i.notify(_objectSpread(_objectSpread({},s),{},{target:_objectSpread(_objectSpread({},s.target),{},{value:""})}))}))})),_defineProperty(_assertThisInitialized(i),"onKeyDown",(function(s){"Enter"===s.key&&i.forceNotify(s);var o=i.props.onKeyDown;o&&(s.persist(),o(s))})),_defineProperty(_assertThisInitialized(i),"onBlur",(function(s){i.forceNotify(s);var o=i.props.onBlur;o&&(s.persist(),o(s))})),_defineProperty(_assertThisInitialized(i),"createNotifier",(function(s){if(s<0)i.notify=function(){return null};else if(0===s)i.notify=i.doNotify;else{var o=(0,u.default)((function(s){i.isDebouncing=!1,i.doNotify(s)}),s);i.notify=function(s){i.isDebouncing=!0,o(s)},i.flush=function(){return o.flush()},i.cancel=function(){i.isDebouncing=!1,o.cancel()}}})),_defineProperty(_assertThisInitialized(i),"doNotify",(function(){i.props.onChange.apply(void 0,arguments)})),_defineProperty(_assertThisInitialized(i),"forceNotify",(function(s){var o=i.props.debounceTimeout;if(i.isDebouncing||!(o>0)){i.cancel&&i.cancel();var a=i.state.value,u=i.props.minLength;a.length>=u?i.doNotify(s):i.doNotify(_objectSpread(_objectSpread({},s),{},{target:_objectSpread(_objectSpread({},s.target),{},{value:a})}))}})),i.isDebouncing=!1,i.state={value:void 0===s.value||null===s.value?"":s.value};var a=i.props.debounceTimeout;return i.createNotifier(a),i}return function _createClass(s,o,i){return o&&_defineProperties(s.prototype,o),i&&_defineProperties(s,i),Object.defineProperty(s,"prototype",{writable:!1}),s}(DebounceInput,[{key:"componentDidUpdate",value:function componentDidUpdate(s){if(!this.isDebouncing){var o=this.props,i=o.value,a=o.debounceTimeout,u=s.debounceTimeout,_=s.value,w=this.state.value;void 0!==i&&_!==i&&w!==i&&this.setState({value:i}),a!==u&&this.createNotifier(a)}}},{key:"componentWillUnmount",value:function componentWillUnmount(){this.flush&&this.flush()}},{key:"render",value:function render(){var s,o,i=this.props,u=i.element,w=(i.onChange,i.value,i.minLength,i.debounceTimeout,i.forceNotifyByEnter),x=i.forceNotifyOnBlur,C=i.onKeyDown,j=i.onBlur,L=i.inputRef,B=_objectWithoutProperties(i,_),$=this.state.value;s=w?{onKeyDown:this.onKeyDown}:C?{onKeyDown:C}:{},o=x?{onBlur:this.onBlur}:j?{onBlur:j}:{};var U=L?{ref:L}:{};return a.default.createElement(u,_objectSpread(_objectSpread(_objectSpread(_objectSpread({},B),{},{onChange:this.onChange,value:$},s),o),U))}}]),DebounceInput}(a.default.PureComponent);o.DebounceInput=w,_defineProperty(w,"defaultProps",{element:"input",type:"text",onKeyDown:void 0,onBlur:void 0,value:void 0,minLength:0,debounceTimeout:100,forceNotifyByEnter:!0,forceNotifyOnBlur:!0,inputRef:void 0})},24677(s,o,i){"use strict";var a=i(81214).DebounceInput;a.DebounceInput=a,s.exports=a},22551(s,o,i){"use strict";var a=i(96540),u=i(69982);function p(s){for(var o="https://reactjs.org/docs/error-decoder.html?invariant="+s,i=1;i